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