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