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.BouncyEllipticKeyPair.BouncyECPrivateKey;
25  import io.github.tonywasher.joceanus.gordianknot.impl.bc.BouncyEllipticKeyPair.BouncyECPublicKey;
26  import io.github.tonywasher.joceanus.gordianknot.impl.bc.BouncySignature.BouncyDSACoder;
27  import io.github.tonywasher.joceanus.gordianknot.impl.bc.BouncySignature.BouncyDigestSignature;
28  import io.github.tonywasher.joceanus.gordianknot.impl.core.base.GordianBaseFactory;
29  import io.github.tonywasher.joceanus.gordianknot.impl.core.base.GordianDataConverter;
30  import io.github.tonywasher.joceanus.gordianknot.impl.core.exc.GordianCryptoException;
31  import io.github.tonywasher.joceanus.gordianknot.impl.core.exc.GordianIOException;
32  import io.github.tonywasher.joceanus.gordianknot.impl.core.keypair.GordianKeyPairValidity;
33  import org.bouncycastle.asn1.ASN1BitString;
34  import org.bouncycastle.asn1.ASN1Encodable;
35  import org.bouncycastle.asn1.ASN1Integer;
36  import org.bouncycastle.asn1.ASN1ObjectIdentifier;
37  import org.bouncycastle.asn1.ASN1OctetString;
38  import org.bouncycastle.asn1.ASN1Primitive;
39  import org.bouncycastle.asn1.DEROctetString;
40  import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
41  import org.bouncycastle.asn1.sec.ECPrivateKey;
42  import org.bouncycastle.asn1.ua.DSTU4145NamedCurves;
43  import org.bouncycastle.asn1.ua.DSTU4145Params;
44  import org.bouncycastle.asn1.ua.DSTU4145PointEncoder;
45  import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
46  import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
47  import org.bouncycastle.crypto.Digest;
48  import org.bouncycastle.crypto.digests.GOST3411Digest;
49  import org.bouncycastle.crypto.generators.DSTU4145KeyPairGenerator;
50  import org.bouncycastle.crypto.generators.ECKeyPairGenerator;
51  import org.bouncycastle.crypto.params.ECDomainParameters;
52  import org.bouncycastle.crypto.params.ECKeyGenerationParameters;
53  import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
54  import org.bouncycastle.crypto.params.ECPublicKeyParameters;
55  import org.bouncycastle.crypto.params.ParametersWithRandom;
56  import org.bouncycastle.crypto.signers.DSTU4145Signer;
57  import org.bouncycastle.jcajce.provider.asymmetric.dstu.BCDSTU4145PrivateKey;
58  import org.bouncycastle.jcajce.provider.asymmetric.dstu.BCDSTU4145PublicKey;
59  import org.bouncycastle.jcajce.provider.asymmetric.util.EC5Util;
60  import org.bouncycastle.jce.spec.ECNamedCurveSpec;
61  import org.bouncycastle.math.ec.ECCurve;
62  import org.bouncycastle.math.ec.ECPoint;
63  
64  import java.io.IOException;
65  import java.math.BigInteger;
66  import java.security.spec.PKCS8EncodedKeySpec;
67  import java.security.spec.X509EncodedKeySpec;
68  
69  /**
70   * DSTU KeyPair classes.
71   */
72  public final class BouncyDSTUKeyPair {
73      /**
74       * DSTU algorithm.
75       */
76      private static final String ALGO = "DSTU4145";
77  
78      /**
79       * Expanded sandBox length.
80       */
81      private static final int EXPANDED_LEN = 128;
82  
83      /**
84       * Private constructor.
85       */
86      private BouncyDSTUKeyPair() {
87      }
88  
89      /**
90       * BouncyCastle DSTU KeyPair generator.
91       */
92      public static class BouncyDSTUKeyPairGenerator
93              extends BouncyKeyPairGenerator {
94          /**
95           * Generator.
96           */
97          private final ECKeyPairGenerator theGenerator;
98  
99          /**
100          * Domain.
101          */
102         private final ECDomainParameters theDomain;
103 
104         /**
105          * Spec.
106          */
107         private final ECNamedCurveSpec theSpec;
108 
109         /**
110          * Constructor.
111          *
112          * @param pFactory the Security Factory
113          * @param pKeySpec the keySpec
114          */
115         BouncyDSTUKeyPairGenerator(final GordianBaseFactory pFactory,
116                                    final GordianKeyPairSpec pKeySpec) {
117             /* Initialise underlying class */
118             super(pFactory, pKeySpec);
119 
120             /* Create the generator */
121             theGenerator = new DSTU4145KeyPairGenerator();
122 
123             /* Determine domain */
124             final String myCurveName = pKeySpec.getElliptic().getCurveName();
125             theDomain = DSTU4145NamedCurves.getByOID(new ASN1ObjectIdentifier(myCurveName));
126             theSpec = new ECNamedCurveSpec(myCurveName,
127                     theDomain.getCurve(),
128                     theDomain.getG(),
129                     theDomain.getN(),
130                     theDomain.getH(),
131                     theDomain.getSeed());
132 
133             /* Perform conversion */
134             final ECCurve myCurve = EC5Util.convertCurve(theSpec.getCurve());
135             final ECPoint myG = EC5Util.convertPoint(myCurve, theSpec.getGenerator());
136 
137             /* Initialise the generator */
138             final ECKeyGenerationParameters myParams = new ECKeyGenerationParameters(
139                     new ECDomainParameters(myCurve, myG, theSpec.getOrder(), BigInteger.valueOf(theSpec.getCofactor())), getRandom());
140             theGenerator.init(myParams);
141         }
142 
143         @Override
144         public BouncyKeyPair generateKeyPair() {
145             /* Generate and return the keyPair */
146             final AsymmetricCipherKeyPair myPair = theGenerator.generateKeyPair();
147             final BouncyECPublicKey myPublic = new BouncyECPublicKey(getKeySpec(), (ECPublicKeyParameters) myPair.getPublic());
148             final BouncyECPrivateKey myPrivate = new BouncyECPrivateKey(getKeySpec(), (ECPrivateKeyParameters) myPair.getPrivate());
149             return new BouncyKeyPair(myPublic, myPrivate);
150         }
151 
152         @Override
153         public PKCS8EncodedKeySpec getPKCS8Encoding(final GordianKeyPair pKeyPair) throws GordianException {
154             /* Check the keyPair type and keySpecs */
155             BouncyKeyPair.checkKeyPair(pKeyPair, getKeySpec());
156 
157             /* build and return the encoding */
158             final BouncyECPrivateKey myPrivateKey = (BouncyECPrivateKey) getPrivateKey(pKeyPair);
159             final ECPrivateKeyParameters myParms = myPrivateKey.getPrivateKey();
160             final BouncyECPublicKey myPublicKey = (BouncyECPublicKey) getPublicKey(pKeyPair);
161             final ECPublicKeyParameters myPubParms = myPublicKey.getPublicKey();
162             final BCDSTU4145PublicKey pubKey = new BCDSTU4145PublicKey(ALGO, myPubParms, theSpec);
163             final BCDSTU4145PrivateKey privKey = new BCDSTU4145PrivateKey(ALGO, myParms, pubKey, theSpec);
164             return new PKCS8EncodedKeySpec(privKey.getEncoded());
165         }
166 
167         @Override
168         public BouncyKeyPair deriveKeyPair(final X509EncodedKeySpec pPublicKey,
169                                            final PKCS8EncodedKeySpec pPrivateKey) throws GordianException {
170             /* Check the keySpecs */
171             checkKeySpec(pPrivateKey);
172 
173             /* derive keyPair */
174             final BouncyECPublicKey myPublic = derivePublicKey(pPublicKey);
175             final PrivateKeyInfo myInfo = PrivateKeyInfo.getInstance(pPrivateKey.getEncoded());
176             final ECPrivateKeyParameters myParms = deriveFromPrivKeyInfo(myInfo);
177             final BouncyECPrivateKey myPrivate = new BouncyECPrivateKey(getKeySpec(), myParms);
178             final BouncyKeyPair myPair = new BouncyKeyPair(myPublic, myPrivate);
179 
180             /* Check that we have a matching pair */
181             GordianKeyPairValidity.checkValidity(getFactory(), myPair);
182 
183             /* Return the keyPair */
184             return myPair;
185         }
186 
187         @Override
188         public X509EncodedKeySpec getX509Encoding(final GordianKeyPair pKeyPair) throws GordianException {
189             /* Check the keyPair type and keySpecs */
190             BouncyKeyPair.checkKeyPair(pKeyPair, getKeySpec());
191 
192             /* build and return the encoding */
193             final BouncyECPublicKey myPublicKey = (BouncyECPublicKey) getPublicKey(pKeyPair);
194             final ECPublicKeyParameters myParms = myPublicKey.getPublicKey();
195             final BCDSTU4145PublicKey pubKey = new BCDSTU4145PublicKey(ALGO, myParms, theSpec);
196             return new X509EncodedKeySpec(pubKey.getEncoded());
197         }
198 
199         @Override
200         public BouncyKeyPair derivePublicOnlyKeyPair(final X509EncodedKeySpec pEncodedKey) throws GordianException {
201             final BouncyECPublicKey myPublic = derivePublicKey(pEncodedKey);
202             return new BouncyKeyPair(myPublic);
203         }
204 
205         /**
206          * Derive public key from encoded.
207          *
208          * @param pEncodedKey the encoded key
209          * @return the public key
210          * @throws GordianException on error
211          */
212         private BouncyECPublicKey derivePublicKey(final X509EncodedKeySpec pEncodedKey) throws GordianException {
213             /* Check the keySpecs */
214             checkKeySpec(pEncodedKey);
215 
216             /* derive publicKey */
217             final SubjectPublicKeyInfo myInfo = SubjectPublicKeyInfo.getInstance(pEncodedKey.getEncoded());
218             final ECPublicKeyParameters myParms = deriveFromPubKeyInfo(myInfo);
219             return new BouncyECPublicKey(getKeySpec(), myParms);
220         }
221 
222         /**
223          * Derive Public Key parameters from SubjectPublicKeyInfo. (extracted from BouncyCastle initialiser)
224          *
225          * @param pKeyInfo the keyInfo
226          * @return the PrivateKeyParameters
227          * @throws GordianException on error
228          */
229         private ECPublicKeyParameters deriveFromPubKeyInfo(final SubjectPublicKeyInfo pKeyInfo) throws GordianException {
230             final ASN1BitString bits = pKeyInfo.getPublicKeyData();
231             final ASN1OctetString key;
232 
233             try {
234                 key = (ASN1OctetString) ASN1Primitive.fromByteArray(bits.getBytes());
235             } catch (IOException ex) {
236                 throw new GordianIOException("error recovering public key", ex);
237             }
238 
239             final byte[] keyEnc = key.getOctets();
240             final ECCurve curve = theDomain.getCurve();
241             return new ECPublicKeyParameters(DSTU4145PointEncoder.decodePoint(curve, keyEnc), theDomain);
242         }
243 
244         /**
245          * Derive Private Key parameters from PrivateKeyInfo. (extracted from BouncyCastle initialiser)
246          *
247          * @param pKeyInfo the keyInfo
248          * @return the PrivateKeyParameters
249          * @throws GordianException on error
250          */
251         private ECPrivateKeyParameters deriveFromPrivKeyInfo(final PrivateKeyInfo pKeyInfo) throws GordianException {
252             try {
253                 final ASN1Encodable privKey = pKeyInfo.parsePrivateKey();
254                 final BigInteger myD;
255                 if (privKey instanceof ASN1Integer) {
256                     myD = ASN1Integer.getInstance(privKey).getPositiveValue();
257                 } else {
258                     final ECPrivateKey ec = ECPrivateKey.getInstance(privKey);
259 
260                     myD = ec.getKey();
261                 }
262                 return new ECPrivateKeyParameters(myD, theDomain);
263 
264             } catch (IOException e) {
265                 throw new GordianCryptoException(ERROR_PARSE, e);
266             }
267         }
268     }
269 
270     /**
271      * DSTU encoder.
272      */
273     static final class BouncyDSTUCoder implements BouncyDSACoder {
274         @Override
275         public byte[] dsaEncode(final BigInteger r,
276                                 final BigInteger s) throws GordianException {
277             /* Protect against exceptions */
278             try {
279                 /* Access byteArrays */
280                 final byte[] myFirst = s.toByteArray();
281                 final byte[] mySecond = r.toByteArray();
282                 final byte[] myResult = myFirst.length > mySecond.length
283                         ? new byte[myFirst.length * 2]
284                         : new byte[mySecond.length * 2];
285 
286                 /* Build array and return */
287                 System.arraycopy(myFirst, 0, myResult, myResult.length / 2 - myFirst.length, myFirst.length);
288                 System.arraycopy(mySecond, 0, myResult, myResult.length - mySecond.length, mySecond.length);
289                 return new DEROctetString(myResult).getEncoded();
290             } catch (Exception e) {
291                 throw new GordianCryptoException(BouncySignature.ERROR_SIGGEN, e);
292             }
293         }
294 
295         @Override
296         public BigInteger[] dsaDecode(final byte[] pEncoded) throws GordianException {
297             /* Protect against exceptions */
298             try {
299                 /* Access the bytes */
300                 final byte[] bytes = ((ASN1OctetString) ASN1Primitive.fromByteArray(pEncoded)).getOctets();
301 
302                 /* Build the value arrays */
303                 final byte[] myFirst = new byte[bytes.length / 2];
304                 final byte[] mySecond = new byte[bytes.length / 2];
305                 System.arraycopy(bytes, 0, myFirst, 0, myFirst.length);
306                 System.arraycopy(bytes, myFirst.length, mySecond, 0, mySecond.length);
307 
308                 /* Create the signature values and return */
309                 final BigInteger[] sig = new BigInteger[2];
310                 sig[1] = new BigInteger(1, myFirst);
311                 sig[0] = new BigInteger(1, mySecond);
312                 return sig;
313             } catch (Exception e) {
314                 throw new GordianCryptoException(BouncySignature.ERROR_SIGPARSE, e);
315             }
316         }
317     }
318 
319     /**
320      * DSTU signer.
321      */
322     public static class BouncyDSTUSignature
323             extends BouncyDigestSignature {
324         /**
325          * The Signer.
326          */
327         private final DSTU4145Signer theSigner;
328 
329         /**
330          * The Coder.
331          */
332         private final BouncyDSTUCoder theCoder;
333 
334         /**
335          * Constructor.
336          *
337          * @param pFactory the factory
338          * @param pSpec    the signatureSpec.
339          */
340         BouncyDSTUSignature(final GordianBaseFactory pFactory,
341                             final GordianSignatureSpec pSpec) {
342             /* Initialise underlying class */
343             super(pFactory, pSpec, newDigest());
344 
345             /* Create the signer and Coder */
346             theSigner = new DSTU4145Signer();
347             theCoder = new BouncyDSTUCoder();
348         }
349 
350         @Override
351         public void initForSigning(final GordianSignParams pParams) throws GordianException {
352             /* Initialise detail */
353             super.initForSigning(pParams);
354             final BouncyKeyPair myPair = getKeyPair();
355             BouncyKeyPair.checkKeyPair(myPair);
356 
357             /* Initialise and set the signer */
358             final BouncyECPrivateKey myPrivate = (BouncyECPrivateKey) myPair.getPrivateKey();
359             final ParametersWithRandom myParms = new ParametersWithRandom(myPrivate.getPrivateKey(), getRandom());
360             theSigner.init(true, myParms);
361         }
362 
363         @Override
364         public void initForVerify(final GordianSignParams pParams) throws GordianException {
365             /* Initialise detail */
366             super.initForVerify(pParams);
367             final BouncyKeyPair myPair = getKeyPair();
368             BouncyKeyPair.checkKeyPair(myPair);
369 
370             /* Initialise and set the signer */
371             final BouncyECPublicKey myPublic = (BouncyECPublicKey) myPair.getPublicKey();
372             theSigner.init(false, myPublic.getPublicKey());
373         }
374 
375         @Override
376         public byte[] sign() throws GordianException {
377             /* Check that we are in signing mode */
378             checkMode(GordianSignatureMode.SIGN);
379 
380             /* Sign the message */
381             final BigInteger[] myValues = theSigner.generateSignature(getDigest());
382             return theCoder.dsaEncode(myValues[0], myValues[1]);
383         }
384 
385         @Override
386         public boolean verify(final byte[] pSignature) throws GordianException {
387             /* Check that we are in verify mode */
388             checkMode(GordianSignatureMode.VERIFY);
389 
390             /* Verify the message */
391             final BigInteger[] myValues = theCoder.dsaDecode(pSignature);
392             return theSigner.verifySignature(getDigest(), myValues[0], myValues[1]);
393         }
394 
395         /**
396          * Obtain new digest for DSTU signer.
397          *
398          * @return the new digest
399          */
400         private static Digest newDigest() {
401             final byte[] myCompressed = DSTU4145Params.getDefaultDKE();
402             final byte[] myExpanded = new byte[EXPANDED_LEN];
403 
404             for (int i = 0; i < myCompressed.length; i++) {
405                 myExpanded[i * 2] = (byte) ((myCompressed[i] >> GordianDataConverter.NYBBLE_SHIFT)
406                         & GordianDataConverter.NYBBLE_MASK);
407                 myExpanded[i * 2 + 1] = (byte) (myCompressed[i] & GordianDataConverter.NYBBLE_MASK);
408             }
409             return new GOST3411Digest(myExpanded);
410         }
411     }
412 }