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.jca;
18  
19  import io.github.tonywasher.joceanus.gordianknot.api.base.GordianException;
20  import io.github.tonywasher.joceanus.gordianknot.api.base.GordianKeySpec;
21  import io.github.tonywasher.joceanus.gordianknot.api.base.GordianLength;
22  import io.github.tonywasher.joceanus.gordianknot.api.cipher.GordianCipherParams;
23  import io.github.tonywasher.joceanus.gordianknot.api.cipher.GordianStreamCipher;
24  import io.github.tonywasher.joceanus.gordianknot.api.cipher.GordianSymCipher;
25  import io.github.tonywasher.joceanus.gordianknot.api.cipher.spec.GordianCipherSpec;
26  import io.github.tonywasher.joceanus.gordianknot.api.cipher.spec.GordianStreamCipherSpec;
27  import io.github.tonywasher.joceanus.gordianknot.api.cipher.spec.GordianStreamKeySpec;
28  import io.github.tonywasher.joceanus.gordianknot.api.cipher.spec.GordianSymCipherSpec;
29  import io.github.tonywasher.joceanus.gordianknot.api.cipher.spec.GordianSymKeySpec;
30  import io.github.tonywasher.joceanus.gordianknot.api.cipher.spec.GordianSymKeyType;
31  import io.github.tonywasher.joceanus.gordianknot.impl.core.base.GordianBaseData;
32  import io.github.tonywasher.joceanus.gordianknot.impl.core.base.GordianBaseFactory;
33  import io.github.tonywasher.joceanus.gordianknot.impl.core.cipher.GordianCoreCipher;
34  import io.github.tonywasher.joceanus.gordianknot.impl.core.exc.GordianCryptoException;
35  import io.github.tonywasher.joceanus.gordianknot.impl.core.spec.cipher.GordianCoreSymCipherSpec;
36  
37  import javax.crypto.BadPaddingException;
38  import javax.crypto.Cipher;
39  import javax.crypto.IllegalBlockSizeException;
40  import javax.crypto.SecretKey;
41  import javax.crypto.ShortBufferException;
42  import javax.crypto.spec.IvParameterSpec;
43  import javax.crypto.spec.RC2ParameterSpec;
44  import javax.crypto.spec.RC5ParameterSpec;
45  import java.security.InvalidAlgorithmParameterException;
46  import java.security.InvalidKeyException;
47  import java.security.spec.AlgorithmParameterSpec;
48  
49  /**
50   * Cipher for JCA BouncyCastle Ciphers.
51   *
52   * @param <T> the key Type
53   */
54  public abstract class JcaCipher<T extends GordianKeySpec>
55          extends GordianCoreCipher<T> {
56      /**
57       * Cipher.
58       */
59      private final Cipher theCipher;
60  
61      /**
62       * is the cipher encrypting?
63       */
64      private boolean isEncrypting;
65  
66      /**
67       * Constructor.
68       *
69       * @param pFactory    the Security Factory
70       * @param pCipherSpec the cipherSpec
71       * @param pCipher     the cipher
72       */
73      JcaCipher(final GordianBaseFactory pFactory,
74                final GordianCipherSpec<T> pCipherSpec,
75                final Cipher pCipher) {
76          super(pFactory, pCipherSpec);
77          theCipher = pCipher;
78      }
79  
80      @Override
81      public JcaKey<T> getKey() {
82          return (JcaKey<T>) super.getKey();
83      }
84  
85      @Override
86      public void init(final boolean pEncrypt,
87                       final GordianCipherParams pParams) throws GordianException {
88          /* Process the parameters and access the key */
89          processParameters(pParams);
90          final JcaKey<T> myJcaKey = JcaKey.accessKey(getKey());
91  
92          /* Access details */
93          final int myMode = pEncrypt
94                  ? Cipher.ENCRYPT_MODE
95                  : Cipher.DECRYPT_MODE;
96          final SecretKey myKey = myJcaKey.getKey();
97          final byte[] myIV = getInitVector();
98  
99          /* Protect against exceptions */
100         try {
101             /* Careful of RC5 */
102             final T myKeyType = myJcaKey.getKeyType();
103             final boolean isRC5 = myKeyType instanceof GordianSymKeySpec mySymKeySpec
104                     && GordianSymKeyType.RC5.equals(mySymKeySpec.getSymKeyType());
105 
106             /* Initialise as required */
107             if (myIV != null || isRC5) {
108                 final AlgorithmParameterSpec myParms = generateParameters(myJcaKey, myIV);
109                 theCipher.init(myMode, myKey, myParms);
110             } else {
111                 theCipher.init(myMode, myKey);
112             }
113             isEncrypting = pEncrypt;
114         } catch (InvalidKeyException
115                  | InvalidAlgorithmParameterException e) {
116             throw new GordianCryptoException("Failed to initialise cipher", e);
117         }
118     }
119 
120     /**
121      * Generate AlgorithmParameters.
122      *
123      * @param pKey the key
124      * @param pIV  the Initialisation vector
125      * @return the parameters
126      */
127     static AlgorithmParameterSpec generateParameters(final JcaKey<?> pKey,
128                                                      final byte[] pIV) {
129         final Object myKeyType = pKey.getKeyType();
130         if (myKeyType instanceof GordianSymKeySpec mySpec) {
131             final GordianSymKeyType myType = mySpec.getSymKeyType();
132             final GordianLength myLen = mySpec.getBlockLength();
133             if (GordianSymKeyType.RC2.equals(myType)) {
134                 return new RC2ParameterSpec(pKey.getKeyBytes().length * Byte.SIZE, pIV);
135             }
136             if (GordianSymKeyType.RC5.equals(myType)) {
137                 return pIV == null
138                         ? new RC5ParameterSpec(1, GordianBaseData.RC5_ROUNDS, myLen.getLength() >> 1)
139                         : new RC5ParameterSpec(1, GordianBaseData.RC5_ROUNDS, myLen.getLength() >> 1, pIV);
140             }
141         }
142         return new IvParameterSpec(pIV);
143     }
144 
145     @Override
146     public int getOutputLength(final int pLength) {
147         return theCipher.getOutputSize(pLength);
148     }
149 
150     @Override
151     public int doUpdate(final byte[] pBytes,
152                         final int pOffset,
153                         final int pLength,
154                         final byte[] pOutput,
155                         final int pOutOffset) throws GordianException {
156         /* Protect against exceptions */
157         try {
158             /* Process the bytes */
159             return theCipher.update(pBytes, pOffset, pLength, pOutput, pOutOffset);
160 
161             /* Handle exceptions */
162         } catch (ShortBufferException e) {
163             throw new GordianCryptoException("Failed to process bytes", e);
164         }
165     }
166 
167     @Override
168     public int doFinish(final byte[] pOutput,
169                         final int pOutOffset) throws GordianException {
170         /* Protect against exceptions */
171         try {
172             /* Finish the operation */
173             return theCipher.doFinal(pOutput, pOutOffset);
174 
175             /* Handle exceptions */
176         } catch (ShortBufferException
177                  | IllegalBlockSizeException
178                  | BadPaddingException e) {
179             throw new GordianCryptoException("Failed to finish operation", e);
180         }
181     }
182 
183     @Override
184     public int getBlockSize() {
185         final GordianCipherSpec<T> mySpec = getCipherSpec();
186         return (mySpec instanceof GordianCoreSymCipherSpec mySymCipherSpec
187                 && mySymCipherSpec.getCoreCipherMode().hasPadding())
188                 ? theCipher.getBlockSize() : 0;
189     }
190 
191     /**
192      * JcaSymCipher.
193      */
194     public static class JcaSymCipher
195             extends JcaCipher<GordianSymKeySpec>
196             implements GordianSymCipher {
197         /**
198          * Constructor.
199          *
200          * @param pFactory    the Security Factory
201          * @param pCipherSpec the cipherSpec
202          * @param pCipher     the cipher
203          */
204         JcaSymCipher(final GordianBaseFactory pFactory,
205                      final GordianSymCipherSpec pCipherSpec,
206                      final Cipher pCipher) {
207             super(pFactory, pCipherSpec, pCipher);
208         }
209     }
210 
211     /**
212      * JcaStreamCipher.
213      */
214     public static class JcaStreamCipher
215             extends JcaCipher<GordianStreamKeySpec>
216             implements GordianStreamCipher {
217         /**
218          * Constructor.
219          *
220          * @param pFactory    the Security Factory
221          * @param pCipherSpec the cipherSpec
222          * @param pCipher     the cipher
223          */
224         JcaStreamCipher(final GordianBaseFactory pFactory,
225                         final GordianStreamCipherSpec pCipherSpec,
226                         final Cipher pCipher) {
227             super(pFactory, pCipherSpec, pCipher);
228         }
229     }
230 
231     @Override
232     public boolean equals(final Object pThat) {
233         /* Handle trivial cases */
234         if (this == pThat) {
235             return true;
236         }
237         if (pThat == null) {
238             return false;
239         }
240 
241         /* Make sure that the classes are the same */
242         if (!(pThat instanceof JcaCipher<?> myThat)) {
243             return false;
244         }
245 
246         /* Check that the fields are equal */
247         return isEncrypting == myThat.isEncrypting
248                 && super.equals(myThat);
249     }
250 
251     @Override
252     public int hashCode() {
253         return super.hashCode()
254                 + (isEncrypting ? 1 : 0);
255     }
256 }