View Javadoc
1   /*
2    * Copyright 2025 The Netty Project
3    *
4    * The Netty Project licenses this file to you under the Apache License,
5    * version 2.0 (the "License"); you may not use this file except in compliance
6    * with the License. You may obtain a copy of the License at:
7    *
8    *   https://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13   * License for the specific language governing permissions and limitations
14   * under the License.
15   */
16  package io.netty.pkitesting;
17  
18  import org.bouncycastle.asn1.ASN1Encodable;
19  import org.bouncycastle.asn1.ASN1Integer;
20  import org.bouncycastle.asn1.ASN1ObjectIdentifier;
21  import org.bouncycastle.asn1.DEROctetString;
22  import org.bouncycastle.asn1.DERSequence;
23  
24  import java.io.IOException;
25  import java.io.UncheckedIOException;
26  import java.lang.reflect.InvocationTargetException;
27  import java.security.PrivateKey;
28  import java.security.spec.AlgorithmParameterSpec;
29  import java.util.Arrays;
30  import javax.security.auth.DestroyFailedException;
31  
32  /**
33   * ML-DSA needs to be represented in a seed-form when serialized, but the current encoding the JDK is using
34   * the expanded form, and throws away the seed during the key generation.
35   * <p>
36   * This class preserves the seed, and keeps a seed-form encoding of the key, which we can use when creating PEM files.
37   * It is important to unwrap the key to its inner representation, before interacting with the JDK and 3rd-party
38   * libraries, like BouncyCastle, since they may have expectations around the concrete class used.
39   */
40  final class MLDSASeedPrivateKey implements PrivateKey {
41      private static final long serialVersionUID = 4206741400099880395L;
42      private final PrivateKey key;
43      private final byte[] seedFormat;
44  
45      MLDSASeedPrivateKey(PrivateKey key, CertificateBuilder.Algorithm algorithm, byte[] seed) {
46          this.key = key;
47          // See https://www.ietf.org/archive/id/draft-ietf-lamps-dilithium-certificates-06.html#name-private-key-format
48          try {
49              seedFormat = new DERSequence(new ASN1Encodable[]{
50                      new ASN1Integer(0),
51                      new DERSequence(new ASN1ObjectIdentifier(Algorithms.oidForAlgorithmName(algorithm.signatureType))),
52                      new DEROctetString(seed)
53              }).getEncoded("DER");
54          } catch (IOException e) {
55              throw new UncheckedIOException("Unexpected problem encoding private key DER", e);
56          }
57      }
58  
59      public AlgorithmParameterSpec getParams() {
60          try {
61              return (AlgorithmParameterSpec) key.getClass().getMethod("getParams").invoke(key);
62          } catch (Exception e) {
63              throw new UnsupportedOperationException(e);
64          }
65      }
66  
67      @Override
68      public String getAlgorithm() {
69          return key.getAlgorithm();
70      }
71  
72      @Override
73      public String getFormat() {
74          return key.getFormat();
75      }
76  
77      @Override
78      public byte[] getEncoded() {
79          return seedFormat.clone();
80      }
81  
82      @Override
83      public void destroy() throws DestroyFailedException {
84          key.destroy();
85          Arrays.fill(seedFormat, (byte) 0);
86      }
87  
88      @Override
89      public boolean isDestroyed() {
90          return key.isDestroyed();
91      }
92  
93      static PrivateKey unwrap(PrivateKey key) {
94          if (key instanceof MLDSASeedPrivateKey) {
95              return ((MLDSASeedPrivateKey) key).key;
96          }
97          return key;
98      }
99  }