View Javadoc
1   /*
2    * GordianKnot: Security Suite
3    * Copyright 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  
18  package io.github.tonywasher.joceanus.gordianknot.impl.bc.sign;
19  
20  import io.github.tonywasher.joceanus.gordianknot.api.base.GordianException;
21  import io.github.tonywasher.joceanus.gordianknot.api.sign.GordianNewSignParams;
22  import io.github.tonywasher.joceanus.gordianknot.api.sign.spec.GordianSignatureSpec;
23  import io.github.tonywasher.joceanus.gordianknot.impl.bc.keypair.BouncyEllipticKeyPair.BouncyECPrivateKey;
24  import io.github.tonywasher.joceanus.gordianknot.impl.bc.keypair.BouncyEllipticKeyPair.BouncyECPublicKey;
25  import io.github.tonywasher.joceanus.gordianknot.impl.bc.keypair.BouncyKeyPair;
26  import io.github.tonywasher.joceanus.gordianknot.impl.bc.sign.BouncySignature.BouncyDSACoder;
27  import io.github.tonywasher.joceanus.gordianknot.impl.core.base.GordianBaseFactory;
28  import io.github.tonywasher.joceanus.gordianknot.impl.core.base.GordianDataConverter;
29  import io.github.tonywasher.joceanus.gordianknot.impl.core.exc.GordianCryptoException;
30  import org.bouncycastle.asn1.ASN1OctetString;
31  import org.bouncycastle.asn1.ASN1Primitive;
32  import org.bouncycastle.asn1.DEROctetString;
33  import org.bouncycastle.asn1.ua.DSTU4145Params;
34  import org.bouncycastle.crypto.Digest;
35  import org.bouncycastle.crypto.digests.GOST3411Digest;
36  import org.bouncycastle.crypto.params.ParametersWithRandom;
37  import org.bouncycastle.crypto.signers.DSTU4145Signer;
38  
39  import java.math.BigInteger;
40  
41  /**
42   * DSTU signer.
43   */
44  public class BouncyDSTUSignature
45          extends BouncyDigestSignature {
46      /**
47       * Expanded sandBox length.
48       */
49      private static final int EXPANDED_LEN = 128;
50  
51      /**
52       * The Signer.
53       */
54      private final DSTU4145Signer theSigner;
55  
56      /**
57       * The Coder.
58       */
59      private final BouncyDSTUCoder theCoder;
60  
61      /**
62       * Constructor.
63       *
64       * @param pFactory the factory
65       * @param pSpec    the signatureSpec.
66       */
67      BouncyDSTUSignature(final GordianBaseFactory pFactory,
68                          final GordianSignatureSpec pSpec) {
69          /* Initialise underlying class */
70          super(pFactory, pSpec, newDigest());
71  
72          /* Create the signer and Coder */
73          theSigner = new DSTU4145Signer();
74          theCoder = new BouncyDSTUCoder();
75      }
76  
77      @Override
78      public void initForSigning(final GordianNewSignParams pParams) throws GordianException {
79          /* Initialise detail */
80          super.initForSigning(pParams);
81          final BouncyKeyPair myPair = getKeyPair();
82          BouncyKeyPair.checkKeyPair(myPair);
83  
84          /* Initialise and set the signer */
85          final BouncyECPrivateKey myPrivate = (BouncyECPrivateKey) myPair.getPrivateKey();
86          final ParametersWithRandom myParms = new ParametersWithRandom(myPrivate.getPrivateKey(), getRandom());
87          theSigner.init(true, myParms);
88      }
89  
90      @Override
91      public void initForVerify(final GordianNewSignParams pParams) throws GordianException {
92          /* Initialise detail */
93          super.initForVerify(pParams);
94          final BouncyKeyPair myPair = getKeyPair();
95          BouncyKeyPair.checkKeyPair(myPair);
96  
97          /* Initialise and set the signer */
98          final BouncyECPublicKey myPublic = (BouncyECPublicKey) myPair.getPublicKey();
99          theSigner.init(false, myPublic.getPublicKey());
100     }
101 
102     @Override
103     public byte[] sign() throws GordianException {
104         /* Check that we are in signing mode */
105         checkMode(GordianSignatureMode.SIGN);
106 
107         /* Sign the message */
108         final BigInteger[] myValues = theSigner.generateSignature(getDigest());
109         return theCoder.dsaEncode(myValues[0], myValues[1]);
110     }
111 
112     @Override
113     public boolean verify(final byte[] pSignature) throws GordianException {
114         /* Check that we are in verify mode */
115         checkMode(GordianSignatureMode.VERIFY);
116 
117         /* Verify the message */
118         final BigInteger[] myValues = theCoder.dsaDecode(pSignature);
119         return theSigner.verifySignature(getDigest(), myValues[0], myValues[1]);
120     }
121 
122     /**
123      * Obtain new digest for DSTU signer.
124      *
125      * @return the new digest
126      */
127     private static Digest newDigest() {
128         final byte[] myCompressed = DSTU4145Params.getDefaultDKE();
129         final byte[] myExpanded = new byte[EXPANDED_LEN];
130 
131         for (int i = 0; i < myCompressed.length; i++) {
132             myExpanded[i * 2] = (byte) ((myCompressed[i] >> GordianDataConverter.NYBBLE_SHIFT)
133                     & GordianDataConverter.NYBBLE_MASK);
134             myExpanded[i * 2 + 1] = (byte) (myCompressed[i] & GordianDataConverter.NYBBLE_MASK);
135         }
136         return new GOST3411Digest(myExpanded);
137     }
138 
139     /**
140      * DSTU encoder.
141      */
142     static final class BouncyDSTUCoder implements BouncyDSACoder {
143         @Override
144         public byte[] dsaEncode(final BigInteger r,
145                                 final BigInteger s) throws GordianException {
146             /* Protect against exceptions */
147             try {
148                 /* Access byteArrays */
149                 final byte[] myFirst = s.toByteArray();
150                 final byte[] mySecond = r.toByteArray();
151                 final byte[] myResult = myFirst.length > mySecond.length
152                         ? new byte[myFirst.length * 2]
153                         : new byte[mySecond.length * 2];
154 
155                 /* Build array and return */
156                 System.arraycopy(myFirst, 0, myResult, myResult.length / 2 - myFirst.length, myFirst.length);
157                 System.arraycopy(mySecond, 0, myResult, myResult.length - mySecond.length, mySecond.length);
158                 return new DEROctetString(myResult).getEncoded();
159             } catch (Exception e) {
160                 throw new GordianCryptoException(BouncySignature.ERROR_SIGGEN, e);
161             }
162         }
163 
164         @Override
165         public BigInteger[] dsaDecode(final byte[] pEncoded) throws GordianException {
166             /* Protect against exceptions */
167             try {
168                 /* Access the bytes */
169                 final byte[] bytes = ((ASN1OctetString) ASN1Primitive.fromByteArray(pEncoded)).getOctets();
170 
171                 /* Build the value arrays */
172                 final byte[] myFirst = new byte[bytes.length / 2];
173                 final byte[] mySecond = new byte[bytes.length / 2];
174                 System.arraycopy(bytes, 0, myFirst, 0, myFirst.length);
175                 System.arraycopy(bytes, myFirst.length, mySecond, 0, mySecond.length);
176 
177                 /* Create the signature values and return */
178                 final BigInteger[] sig = new BigInteger[2];
179                 sig[1] = new BigInteger(1, myFirst);
180                 sig[0] = new BigInteger(1, mySecond);
181                 return sig;
182             } catch (Exception e) {
183                 throw new GordianCryptoException(BouncySignature.ERROR_SIGPARSE, e);
184             }
185         }
186     }
187 }