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.digest.GordianDigestFactory;
21  import io.github.tonywasher.joceanus.gordianknot.api.digest.spec.GordianDigestSpec;
22  import io.github.tonywasher.joceanus.gordianknot.api.digest.spec.GordianDigestType;
23  import io.github.tonywasher.joceanus.gordianknot.api.encrypt.spec.GordianSM2EncryptionSpec;
24  import io.github.tonywasher.joceanus.gordianknot.api.encrypt.spec.GordianSM2EncryptionType;
25  import io.github.tonywasher.joceanus.gordianknot.api.keypair.GordianKeyPair;
26  import io.github.tonywasher.joceanus.gordianknot.api.sign.GordianNewSignParams;
27  import io.github.tonywasher.joceanus.gordianknot.api.sign.spec.GordianSignatureSpec;
28  import io.github.tonywasher.joceanus.gordianknot.impl.bc.BouncyEllipticKeyPair.BouncyECPrivateKey;
29  import io.github.tonywasher.joceanus.gordianknot.impl.bc.BouncyEllipticKeyPair.BouncyECPublicKey;
30  import io.github.tonywasher.joceanus.gordianknot.impl.bc.BouncyKeyPair.BouncyPrivateKey;
31  import io.github.tonywasher.joceanus.gordianknot.impl.bc.BouncyKeyPair.BouncyPublicKey;
32  import io.github.tonywasher.joceanus.gordianknot.impl.core.agree.GordianCoreAgreementFactory;
33  import io.github.tonywasher.joceanus.gordianknot.impl.core.base.GordianBaseFactory;
34  import io.github.tonywasher.joceanus.gordianknot.impl.core.encrypt.GordianCoreEncryptor;
35  import io.github.tonywasher.joceanus.gordianknot.impl.core.exc.GordianCryptoException;
36  import io.github.tonywasher.joceanus.gordianknot.impl.core.sign.GordianCoreSignature;
37  import io.github.tonywasher.joceanus.gordianknot.impl.core.spec.agree.GordianCoreAgreementSpec;
38  import io.github.tonywasher.joceanus.gordianknot.impl.core.spec.encrypt.GordianCoreEncryptorSpec;
39  import io.github.tonywasher.joceanus.gordianknot.impl.core.spec.sign.GordianCoreSignatureSpec;
40  import org.bouncycastle.crypto.CryptoException;
41  import org.bouncycastle.crypto.InvalidCipherTextException;
42  import org.bouncycastle.crypto.agreement.SM2KeyExchange;
43  import org.bouncycastle.crypto.engines.SM2Engine;
44  import org.bouncycastle.crypto.engines.SM2Engine.Mode;
45  import org.bouncycastle.crypto.params.ParametersWithID;
46  import org.bouncycastle.crypto.params.ParametersWithRandom;
47  import org.bouncycastle.crypto.params.SM2KeyExchangePrivateParameters;
48  import org.bouncycastle.crypto.params.SM2KeyExchangePublicParameters;
49  import org.bouncycastle.crypto.signers.SM2Signer;
50  
51  /**
52   * SM2 KeyPair classes.
53   */
54  public final class BouncySM2KeyPair {
55      /**
56       * Private constructor.
57       */
58      private BouncySM2KeyPair() {
59      }
60  
61      /**
62       * SM2 signature.
63       */
64      public static class BouncySM2Signature
65              extends GordianCoreSignature {
66          /**
67           * The Signer.
68           */
69          private final SM2Signer theSigner;
70  
71          /**
72           * Constructor.
73           *
74           * @param pFactory the factory
75           * @param pSpec    the signatureSpec.
76           * @throws GordianException on error
77           */
78          BouncySM2Signature(final GordianBaseFactory pFactory,
79                             final GordianSignatureSpec pSpec) throws GordianException {
80              /* Initialise underlying class */
81              super(pFactory, pSpec);
82  
83              /* Create the signer */
84              final GordianDigestSpec mySpec = ((GordianCoreSignatureSpec) pSpec).getDigestSpec();
85              if (GordianDigestType.SM3.equals(mySpec.getDigestType())) {
86                  theSigner = new SM2Signer();
87              } else {
88                  final BouncyDigest myDigest = (BouncyDigest) pFactory.getDigestFactory().createDigest(mySpec);
89                  theSigner = new SM2Signer(myDigest.getDigest());
90              }
91          }
92  
93          @Override
94          public void update(final byte[] pBytes,
95                             final int pOffset,
96                             final int pLength) {
97              theSigner.update(pBytes, pOffset, pLength);
98          }
99  
100         @Override
101         public void update(final byte pByte) {
102             theSigner.update(pByte);
103         }
104 
105         @Override
106         public void update(final byte[] pBytes) {
107             theSigner.update(pBytes, 0, pBytes.length);
108         }
109 
110         @Override
111         public void reset() {
112             theSigner.reset();
113         }
114 
115         @Override
116         protected BouncyKeyPair getKeyPair() {
117             return (BouncyKeyPair) super.getKeyPair();
118         }
119 
120         @Override
121         public void initForSigning(final GordianNewSignParams pParams) throws GordianException {
122             /* Initialise detail */
123             super.initForSigning(pParams);
124             final BouncyKeyPair myPair = getKeyPair();
125             BouncyKeyPair.checkKeyPair(myPair);
126 
127             /* Initialise and set the signer */
128             final BouncyECPrivateKey myPrivate = (BouncyECPrivateKey) myPair.getPrivateKey();
129             final ParametersWithRandom myParms = new ParametersWithRandom(myPrivate.getPrivateKey(), getRandom());
130             theSigner.init(true, myParms);
131         }
132 
133         @Override
134         public void initForVerify(final GordianNewSignParams pParams) throws GordianException {
135             /* Initialise detail */
136             super.initForVerify(pParams);
137             final BouncyKeyPair myPair = getKeyPair();
138             BouncyKeyPair.checkKeyPair(myPair);
139 
140             /* Initialise and set the signer */
141             final BouncyECPublicKey myPublic = (BouncyECPublicKey) myPair.getPublicKey();
142             theSigner.init(false, myPublic.getPublicKey());
143         }
144 
145         @Override
146         public byte[] sign() throws GordianException {
147             /* Check that we are in signing mode */
148             checkMode(GordianSignatureMode.SIGN);
149 
150             /* Sign the message */
151             try {
152                 return theSigner.generateSignature();
153             } catch (CryptoException e) {
154                 throw new GordianCryptoException(BouncySignature.ERROR_SIGGEN, e);
155             }
156         }
157 
158         @Override
159         public boolean verify(final byte[] pSignature) throws GordianException {
160             /* Check that we are in verify mode */
161             checkMode(GordianSignatureMode.VERIFY);
162 
163             /* Verify the message */
164             return theSigner.verifySignature(pSignature);
165         }
166     }
167 
168     /**
169      * SM2 Agreement Engine.
170      */
171     public static class BouncySM2AgreementEngine
172             extends BouncyAgreementBase {
173         /**
174          * Empty byteArray.
175          */
176         private static final byte[] EMPTY = new byte[0];
177 
178         /**
179          * Key length.
180          */
181         private static final int KEYLEN = 128;
182 
183         /**
184          * The agreement.
185          */
186         private final SM2KeyExchange theAgreement;
187 
188         /**
189          * Constructor.
190          *
191          * @param pFactory the security factory
192          * @param pSpec    the agreementSpec
193          * @throws GordianException on error
194          */
195         BouncySM2AgreementEngine(final GordianCoreAgreementFactory pFactory,
196                                  final GordianCoreAgreementSpec pSpec) throws GordianException {
197             /* Initialize underlying class */
198             super(pFactory, pSpec);
199 
200             /* Create the agreement */
201             theAgreement = new SM2KeyExchange();
202         }
203 
204         @Override
205         public void processClientHello() throws GordianException {
206             /* Access keys */
207             final BouncyECPublicKey myClientPublic = (BouncyECPublicKey) getPublicKey(getClientKeyPair());
208             final BouncyECPublicKey myClientEphPublic = (BouncyECPublicKey) getPublicKey(getClientEphemeral());
209             final BouncyECPrivateKey myPrivate = (BouncyECPrivateKey) getPrivateKey(getServerKeyPair());
210             final BouncyECPrivateKey myEphPrivate = (BouncyECPrivateKey) getPrivateKey(getServerEphemeral());
211 
212             /* Access IDs */
213             final byte[] myClientID = getClientName() == null ? EMPTY : getClientName();
214             final byte[] myServerID = getServerName() == null ? EMPTY : getServerName();
215 
216             /* Derive the secret */
217             final SM2KeyExchangePrivateParameters myPrivParams = new SM2KeyExchangePrivateParameters(false,
218                     myPrivate.getPrivateKey(), myEphPrivate.getPrivateKey());
219             final ParametersWithID myPrivIDParams = new ParametersWithID(myPrivParams, myServerID);
220             theAgreement.init(myPrivIDParams);
221             final SM2KeyExchangePublicParameters myPubParams = new SM2KeyExchangePublicParameters(myClientPublic.getPublicKey(),
222                     myClientEphPublic.getPublicKey());
223             final ParametersWithID myPubIDParams = new ParametersWithID(myPubParams, myClientID);
224 
225             /* If we are confirming */
226             if (getSpec().withConfirm()) {
227                 /* Create agreement and confirmation tags */
228                 final byte[][] myResults = theAgreement.calculateKeyWithConfirmation(KEYLEN, null, myPubIDParams);
229 
230                 /* Store the confirmationTags */
231                 setServerConfirm(myResults[1]);
232                 setClientConfirm(myResults[2]);
233 
234                 /* Store the secret */
235                 storeSecret(myResults[0]);
236 
237                 /* else standard agreement */
238             } else {
239                 /* Calculate and store the secret */
240                 storeSecret(theAgreement.calculateKey(KEYLEN, myPubIDParams));
241             }
242         }
243 
244         @Override
245         public void processServerHello() throws GordianException {
246             /* Access keys */
247             final BouncyECPublicKey myServerPublic = (BouncyECPublicKey) getPublicKey(getServerKeyPair());
248             final BouncyECPublicKey myServerEphPublic = (BouncyECPublicKey) getPublicKey(getServerEphemeral());
249             final BouncyECPrivateKey myPrivate = (BouncyECPrivateKey) getPrivateKey(getClientKeyPair());
250             final BouncyECPrivateKey myEphPrivate = (BouncyECPrivateKey) getPrivateKey(getClientEphemeral());
251 
252             /* Access IDs */
253             final byte[] myClientID = getClientName() == null ? EMPTY : getClientName();
254             final byte[] myServerID = getServerName() == null ? EMPTY : getServerName();
255 
256             /* Derive the secret */
257             final SM2KeyExchangePrivateParameters myPrivParams = new SM2KeyExchangePrivateParameters(true,
258                     myPrivate.getPrivateKey(), myEphPrivate.getPrivateKey());
259             final ParametersWithID myPrivIDParams = new ParametersWithID(myPrivParams, myClientID);
260             theAgreement.init(myPrivIDParams);
261             final SM2KeyExchangePublicParameters myPubParams = new SM2KeyExchangePublicParameters(myServerPublic.getPublicKey(),
262                     myServerEphPublic.getPublicKey());
263             final ParametersWithID myPubIDParams = new ParametersWithID(myPubParams, myServerID);
264 
265             /* If we are confirming */
266             if (getSpec().withConfirm()) {
267                 /* Obtain confirmationTag in serverHello */
268                 final byte[] myConfirm = getServerConfirm();
269 
270                 /* Protect against exception */
271                 try {
272                     /* Create agreement and confirmation tags */
273                     final byte[][] myResults = theAgreement.calculateKeyWithConfirmation(KEYLEN, myConfirm, myPubIDParams);
274 
275                     /* Store the confirmationTag */
276                     if (setClientConfirm(myResults[1])) {
277                         /* Store the secret */
278                         storeSecret(myResults[0]);
279                     }
280 
281                     /* Catch mismatch on confirmation tag */
282                 } catch (IllegalStateException e) {
283                     getBuilder().setError("Server Confirmation failed");
284                 }
285 
286                 /* else standard agreement */
287             } else {
288                 /* Calculate and store the secret */
289                 storeSecret(theAgreement.calculateKey(KEYLEN, myPubIDParams));
290             }
291         }
292     }
293 
294     /**
295      * SM2 Encryptor.
296      */
297     public static class BouncySM2Encryptor
298             extends GordianCoreEncryptor {
299         /**
300          * The underlying encryptor.
301          */
302         private final SM2Engine theEncryptor;
303 
304         /**
305          * Constructor.
306          *
307          * @param pFactory the factory
308          * @param pSpec    the encryptorSpec
309          * @throws GordianException on error
310          */
311         BouncySM2Encryptor(final GordianBaseFactory pFactory,
312                            final GordianCoreEncryptorSpec pSpec) throws GordianException {
313             /* Initialise underlying cipher */
314             super(pFactory, pSpec);
315             final GordianDigestFactory myFactory = pFactory.getDigestFactory();
316             final GordianSM2EncryptionSpec mySpec = pSpec.getSM2EncryptionSpec();
317             final BouncyDigest myDigest = (BouncyDigest) myFactory.createDigest(mySpec.getDigestSpec());
318             final Mode mySM2Mode = mySpec.getEncryptionType() == GordianSM2EncryptionType.C1C2C3
319                     ? Mode.C1C2C3 : Mode.C1C3C2;
320             theEncryptor = new SM2Engine(myDigest.getDigest(), mySM2Mode);
321         }
322 
323         @Override
324         protected BouncyPublicKey<?> getPublicKey() {
325             return (BouncyPublicKey<?>) super.getPublicKey();
326         }
327 
328         @Override
329         protected BouncyPrivateKey<?> getPrivateKey() {
330             return (BouncyPrivateKey<?>) super.getPrivateKey();
331         }
332 
333         @Override
334         public void initForEncrypt(final GordianKeyPair pKeyPair) throws GordianException {
335             /* Initialise underlying cipher */
336             BouncyKeyPair.checkKeyPair(pKeyPair);
337             super.initForEncrypt(pKeyPair);
338 
339             /* Initialise for encryption */
340             final ParametersWithRandom myParms = new ParametersWithRandom(getPublicKey().getPublicKey(), getRandom());
341             theEncryptor.init(true, myParms);
342         }
343 
344         @Override
345         public void initForDecrypt(final GordianKeyPair pKeyPair) throws GordianException {
346             /* Initialise underlying cipher */
347             BouncyKeyPair.checkKeyPair(pKeyPair);
348             super.initForDecrypt(pKeyPair);
349 
350             /* Initialise for decryption */
351             theEncryptor.init(false, getPrivateKey().getPrivateKey());
352         }
353 
354         @Override
355         public byte[] encrypt(final byte[] pBytes) throws GordianException {
356             try {
357                 /* Check that we are in encryption mode */
358                 checkMode(GordianEncryptMode.ENCRYPT);
359 
360                 /* Encrypt the message */
361                 return theEncryptor.processBlock(pBytes, 0, pBytes.length);
362             } catch (InvalidCipherTextException e) {
363                 throw new GordianCryptoException("Failed to encrypt data", e);
364             }
365         }
366 
367         @Override
368         public byte[] decrypt(final byte[] pBytes) throws GordianException {
369             try {
370                 /* Check that we are in decryption mode */
371                 checkMode(GordianEncryptMode.DECRYPT);
372 
373                 /* Decrypt the message */
374                 return theEncryptor.processBlock(pBytes, 0, pBytes.length);
375             } catch (InvalidCipherTextException e) {
376                 throw new GordianCryptoException("Failed to decrypt data", e);
377             }
378         }
379     }
380 }