View Javadoc
1   /*
2    * GordianKnot: Security Suite
3    * Copyright 2012-2026. Tony Washer
4    *
5    * Licensed under the Apache License, Version 2.0 (the "License"); you may not
6    * use this file except in compliance with the License.  You may obtain a copy
7    * of the License at
8    *
9    *   http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
14   * License for the specific language governing permissions and limitations under
15   * the License.
16   */
17  package io.github.tonywasher.joceanus.gordianknot.impl.bc;
18  
19  import io.github.tonywasher.joceanus.gordianknot.api.base.GordianException;
20  import io.github.tonywasher.joceanus.gordianknot.api.keypair.GordianKeyPair;
21  import io.github.tonywasher.joceanus.gordianknot.api.keypair.GordianKeyPairSpec;
22  import io.github.tonywasher.joceanus.gordianknot.api.sign.GordianSignParams;
23  import io.github.tonywasher.joceanus.gordianknot.api.sign.GordianSignatureSpec;
24  import io.github.tonywasher.joceanus.gordianknot.impl.bc.BouncyKeyPair.BouncyPrivateKey;
25  import io.github.tonywasher.joceanus.gordianknot.impl.bc.BouncyKeyPair.BouncyPublicKey;
26  import io.github.tonywasher.joceanus.gordianknot.impl.core.base.GordianBaseFactory;
27  import io.github.tonywasher.joceanus.gordianknot.impl.core.exc.GordianCryptoException;
28  import io.github.tonywasher.joceanus.gordianknot.impl.core.keypair.GordianKeyPairValidity;
29  import io.github.tonywasher.joceanus.gordianknot.impl.core.sign.GordianCoreSignature;
30  import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
31  import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
32  import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
33  import org.bouncycastle.crypto.CipherParameters;
34  import org.bouncycastle.crypto.CryptoException;
35  import org.bouncycastle.crypto.Signer;
36  import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
37  import org.bouncycastle.crypto.params.ParametersWithContext;
38  import org.bouncycastle.crypto.params.ParametersWithRandom;
39  import org.bouncycastle.pqc.crypto.mldsa.HashMLDSASigner;
40  import org.bouncycastle.pqc.crypto.mldsa.MLDSAKeyGenerationParameters;
41  import org.bouncycastle.pqc.crypto.mldsa.MLDSAKeyPairGenerator;
42  import org.bouncycastle.pqc.crypto.mldsa.MLDSAParameters;
43  import org.bouncycastle.pqc.crypto.mldsa.MLDSAPrivateKeyParameters;
44  import org.bouncycastle.pqc.crypto.mldsa.MLDSAPublicKeyParameters;
45  import org.bouncycastle.pqc.crypto.mldsa.MLDSASigner;
46  import org.bouncycastle.pqc.crypto.util.PrivateKeyFactory;
47  import org.bouncycastle.pqc.crypto.util.PrivateKeyInfoFactory;
48  import org.bouncycastle.pqc.crypto.util.PublicKeyFactory;
49  import org.bouncycastle.pqc.crypto.util.SubjectPublicKeyInfoFactory;
50  
51  import java.io.IOException;
52  import java.security.spec.PKCS8EncodedKeySpec;
53  import java.security.spec.X509EncodedKeySpec;
54  import java.util.Arrays;
55  
56  /**
57   * MLDSA KeyPair classes.
58   */
59  public final class BouncyMLDSAKeyPair {
60      /**
61       * Private constructor.
62       */
63      private BouncyMLDSAKeyPair() {
64      }
65  
66      /**
67       * Bouncy MLDSA PublicKey.
68       */
69      public static class BouncyMLDSAPublicKey
70              extends BouncyPublicKey<MLDSAPublicKeyParameters> {
71          /**
72           * Constructor.
73           *
74           * @param pKeySpec   the keySpec
75           * @param pPublicKey the public key
76           */
77          BouncyMLDSAPublicKey(final GordianKeyPairSpec pKeySpec,
78                               final MLDSAPublicKeyParameters pPublicKey) {
79              super(pKeySpec, pPublicKey);
80          }
81  
82          @Override
83          protected boolean matchKey(final AsymmetricKeyParameter pThat) {
84              /* Access keys */
85              final MLDSAPublicKeyParameters myThis = getPublicKey();
86              final MLDSAPublicKeyParameters myThat = (MLDSAPublicKeyParameters) pThat;
87  
88              /* Compare keys */
89              return compareKeys(myThis, myThat);
90          }
91  
92          /**
93           * CompareKeys.
94           *
95           * @param pFirst  the first key
96           * @param pSecond the second key
97           * @return true/false
98           */
99          private static boolean compareKeys(final MLDSAPublicKeyParameters pFirst,
100                                            final MLDSAPublicKeyParameters pSecond) {
101             return Arrays.equals(pFirst.getEncoded(), pSecond.getEncoded());
102         }
103     }
104 
105     /**
106      * Bouncy MLDSA PrivateKey.
107      */
108     public static class BouncyMLDSAPrivateKey
109             extends BouncyPrivateKey<MLDSAPrivateKeyParameters> {
110         /**
111          * Constructor.
112          *
113          * @param pKeySpec    the keySpec
114          * @param pPrivateKey the private key
115          */
116         BouncyMLDSAPrivateKey(final GordianKeyPairSpec pKeySpec,
117                               final MLDSAPrivateKeyParameters pPrivateKey) {
118             super(pKeySpec, pPrivateKey);
119         }
120 
121 
122         @Override
123         protected boolean matchKey(final AsymmetricKeyParameter pThat) {
124             /* Access keys */
125             final MLDSAPrivateKeyParameters myThis = getPrivateKey();
126             final MLDSAPrivateKeyParameters myThat = (MLDSAPrivateKeyParameters) pThat;
127 
128             /* Compare keys */
129             return compareKeys(myThis, myThat);
130         }
131 
132         /**
133          * CompareKeys.
134          *
135          * @param pFirst  the first key
136          * @param pSecond the second key
137          * @return true/false
138          */
139         private static boolean compareKeys(final MLDSAPrivateKeyParameters pFirst,
140                                            final MLDSAPrivateKeyParameters pSecond) {
141             return Arrays.equals(pFirst.getEncoded(), pSecond.getEncoded());
142         }
143     }
144 
145     /**
146      * BouncyCastle MLDSA KeyPair generator.
147      */
148     public static class BouncyMLDSAKeyPairGenerator
149             extends BouncyKeyPairGenerator {
150         /**
151          * Generator.
152          */
153         private final MLDSAKeyPairGenerator theGenerator;
154 
155         /**
156          * Constructor.
157          *
158          * @param pFactory the Security Factory
159          * @param pKeySpec the keySpec
160          */
161         BouncyMLDSAKeyPairGenerator(final GordianBaseFactory pFactory,
162                                     final GordianKeyPairSpec pKeySpec) {
163             /* Initialise underlying class */
164             super(pFactory, pKeySpec);
165 
166             /* Determine the parameters */
167             final MLDSAParameters myParms = pKeySpec.getMLDSAKeySpec().getParameters();
168 
169             /* Create and initialise the generator */
170             theGenerator = new MLDSAKeyPairGenerator();
171             final MLDSAKeyGenerationParameters myParams = new MLDSAKeyGenerationParameters(getRandom(), myParms);
172             theGenerator.init(myParams);
173         }
174 
175         @Override
176         public BouncyKeyPair generateKeyPair() {
177             /* Generate and return the keyPair */
178             final AsymmetricCipherKeyPair myPair = theGenerator.generateKeyPair();
179             final BouncyMLDSAPublicKey myPublic = new BouncyMLDSAPublicKey(getKeySpec(), (MLDSAPublicKeyParameters) myPair.getPublic());
180             final BouncyMLDSAPrivateKey myPrivate = new BouncyMLDSAPrivateKey(getKeySpec(), (MLDSAPrivateKeyParameters) myPair.getPrivate());
181             return new BouncyKeyPair(myPublic, myPrivate);
182         }
183 
184         @Override
185         public PKCS8EncodedKeySpec getPKCS8Encoding(final GordianKeyPair pKeyPair) throws GordianException {
186             /* Protect against exceptions */
187             try {
188                 /* Check the keyPair type and keySpecs */
189                 BouncyKeyPair.checkKeyPair(pKeyPair, getKeySpec());
190 
191                 /* build and return the encoding */
192                 final BouncyMLDSAPrivateKey myPrivateKey = (BouncyMLDSAPrivateKey) getPrivateKey(pKeyPair);
193                 final MLDSAPrivateKeyParameters myParms = myPrivateKey.getPrivateKey();
194                 final PrivateKeyInfo myInfo = PrivateKeyInfoFactory.createPrivateKeyInfo(myParms, null);
195                 return new PKCS8EncodedKeySpec(myInfo.getEncoded());
196 
197             } catch (IOException e) {
198                 throw new GordianCryptoException(ERROR_PARSE, e);
199             }
200         }
201 
202         @Override
203         public BouncyKeyPair deriveKeyPair(final X509EncodedKeySpec pPublicKey,
204                                            final PKCS8EncodedKeySpec pPrivateKey) throws GordianException {
205             /* Protect against exceptions */
206             try {
207                 /* Check the keySpecs */
208                 checkKeySpec(pPrivateKey);
209 
210                 /* derive keyPair */
211                 final BouncyMLDSAPublicKey myPublic = derivePublicKey(pPublicKey);
212                 final PrivateKeyInfo myInfo = PrivateKeyInfo.getInstance(pPrivateKey.getEncoded());
213                 final MLDSAPrivateKeyParameters myParms = (MLDSAPrivateKeyParameters) PrivateKeyFactory.createKey(myInfo);
214                 final BouncyMLDSAPrivateKey myPrivate = new BouncyMLDSAPrivateKey(getKeySpec(), myParms);
215                 final BouncyKeyPair myPair = new BouncyKeyPair(myPublic, myPrivate);
216 
217                 /* Check that we have a matching pair */
218                 GordianKeyPairValidity.checkValidity(getFactory(), myPair);
219 
220                 /* Return the keyPair */
221                 return myPair;
222 
223             } catch (IOException e) {
224                 throw new GordianCryptoException(ERROR_PARSE, e);
225             }
226         }
227 
228         @Override
229         public X509EncodedKeySpec getX509Encoding(final GordianKeyPair pKeyPair) throws GordianException {
230             /* Protect against exceptions */
231             try {
232                 /* Check the keyPair type and keySpecs */
233                 BouncyKeyPair.checkKeyPair(pKeyPair, getKeySpec());
234 
235                 /* build and return the encoding */
236                 final BouncyMLDSAPublicKey myPublicKey = (BouncyMLDSAPublicKey) getPublicKey(pKeyPair);
237                 final MLDSAPublicKeyParameters myParms = myPublicKey.getPublicKey();
238                 final SubjectPublicKeyInfo myInfo = SubjectPublicKeyInfoFactory.createSubjectPublicKeyInfo(myParms);
239                 return new X509EncodedKeySpec(myInfo.getEncoded());
240 
241             } catch (IOException e) {
242                 throw new GordianCryptoException(ERROR_PARSE, e);
243             }
244         }
245 
246         @Override
247         public BouncyKeyPair derivePublicOnlyKeyPair(final X509EncodedKeySpec pEncodedKey) throws GordianException {
248             final BouncyMLDSAPublicKey myPublic = derivePublicKey(pEncodedKey);
249             return new BouncyKeyPair(myPublic);
250         }
251 
252         /**
253          * Derive public key from encoded.
254          *
255          * @param pEncodedKey the encoded key
256          * @return the public key
257          * @throws GordianException on error
258          */
259         private BouncyMLDSAPublicKey derivePublicKey(final X509EncodedKeySpec pEncodedKey) throws GordianException {
260             /* Protect against exceptions */
261             try {
262                 /* Check the keySpecs */
263                 checkKeySpec(pEncodedKey);
264 
265                 /* derive publicKey */
266                 final SubjectPublicKeyInfo myInfo = SubjectPublicKeyInfo.getInstance(pEncodedKey.getEncoded());
267                 final MLDSAPublicKeyParameters myParms = (MLDSAPublicKeyParameters) PublicKeyFactory.createKey(myInfo);
268                 return new BouncyMLDSAPublicKey(getKeySpec(), myParms);
269 
270             } catch (IOException e) {
271                 throw new GordianCryptoException(ERROR_PARSE, e);
272             }
273         }
274     }
275 
276     /**
277      * MLDSA signer.
278      */
279     public static class BouncyMLDSASignature
280             extends GordianCoreSignature {
281         /**
282          * The MLDSA Signer.
283          */
284         private Signer theSigner;
285 
286         /**
287          * Constructor.
288          *
289          * @param pFactory the factory
290          * @param pSpec    the signatureSpec.
291          */
292         BouncyMLDSASignature(final GordianBaseFactory pFactory,
293                              final GordianSignatureSpec pSpec) {
294             /* Initialise underlying class */
295             super(pFactory, pSpec);
296         }
297 
298         /**
299          * Create the signer according to the keyPair.
300          *
301          * @param pKeyPair the keyPair
302          * @return the signer
303          */
304         private static Signer createSigner(final GordianKeyPair pKeyPair) {
305             /* Determine whether this is a hashSigner */
306             final boolean isHash = pKeyPair.getKeyPairSpec().getMLDSAKeySpec().isHash();
307 
308             /* Create the internal digests */
309             return isHash
310                     ? new HashMLDSASigner()
311                     : new MLDSASigner();
312         }
313 
314         @Override
315         public void initForSigning(final GordianSignParams pParams) throws GordianException {
316             /* Initialise detail */
317             super.initForSigning(pParams);
318             final BouncyKeyPair myPair = getKeyPair();
319             final byte[] myContext = getContext();
320             BouncyKeyPair.checkKeyPair(myPair);
321 
322             /* Initialise and set the signer */
323             theSigner = createSigner(myPair);
324             final BouncyMLDSAPrivateKey myPrivate = (BouncyMLDSAPrivateKey) myPair.getPrivateKey();
325             CipherParameters myParms = new ParametersWithRandom(myPrivate.getPrivateKey(), getRandom());
326             if (myContext != null) {
327                 myParms = new ParametersWithContext(myParms, myContext);
328             }
329             theSigner.init(true, myParms);
330         }
331 
332         @Override
333         public void initForVerify(final GordianSignParams pParams) throws GordianException {
334             /* Initialise detail */
335             super.initForVerify(pParams);
336             final BouncyKeyPair myPair = getKeyPair();
337             final byte[] myContext = getContext();
338             BouncyKeyPair.checkKeyPair(myPair);
339 
340             /* Initialise and set the signer */
341             theSigner = createSigner(myPair);
342             final BouncyMLDSAPublicKey myPublic = (BouncyMLDSAPublicKey) myPair.getPublicKey();
343             CipherParameters myParms = myPublic.getPublicKey();
344             if (myContext != null) {
345                 myParms = new ParametersWithContext(myParms, myContext);
346             }
347             theSigner.init(false, myParms);
348         }
349 
350         @Override
351         public void update(final byte[] pBytes,
352                            final int pOffset,
353                            final int pLength) {
354             theSigner.update(pBytes, pOffset, pLength);
355         }
356 
357         @Override
358         public void update(final byte pByte) {
359             theSigner.update(pByte);
360         }
361 
362         @Override
363         public void update(final byte[] pBytes) {
364             theSigner.update(pBytes, 0, pBytes.length);
365         }
366 
367         @Override
368         public void reset() {
369             theSigner.reset();
370         }
371 
372         @Override
373         protected BouncyKeyPair getKeyPair() {
374             return (BouncyKeyPair) super.getKeyPair();
375         }
376 
377         @Override
378         public GordianBaseFactory getFactory() {
379             return (GordianBaseFactory) super.getFactory();
380         }
381 
382         @Override
383         public byte[] sign() throws GordianException {
384             /* Check that we are in signing mode */
385             checkMode(GordianSignatureMode.SIGN);
386 
387             /* Sign the message */
388             try {
389                 return theSigner.generateSignature();
390             } catch (CryptoException e) {
391                 throw new GordianCryptoException("Failed to sign message", e);
392             }
393         }
394 
395         @Override
396         public boolean verify(final byte[] pSignature) throws GordianException {
397             /* Check that we are in verify mode */
398             checkMode(GordianSignatureMode.VERIFY);
399 
400             /* Verify the message */
401             return theSigner.verifySignature(pSignature);
402         }
403     }
404 }