View Javadoc

1   package com.atlassian.security.auth.trustedapps;
2   
3   import com.atlassian.security.auth.trustedapps.Transcoder.Base64Transcoder;
4   
5   import org.bouncycastle.jce.provider.BouncyCastleProvider;
6   
7   import java.io.BufferedReader;
8   import java.io.ByteArrayInputStream;
9   import java.io.CharConversionException;
10  import java.io.IOException;
11  import java.io.InputStreamReader;
12  import java.io.StringWriter;
13  import java.security.InvalidKeyException;
14  import java.security.KeyFactory;
15  import java.security.KeyPair;
16  import java.security.KeyPairGenerator;
17  import java.security.NoSuchAlgorithmException;
18  import java.security.NoSuchProviderException;
19  import java.security.PrivateKey;
20  import java.security.Provider;
21  import java.security.PublicKey;
22  import java.security.spec.InvalidKeySpecException;
23  import java.security.spec.PKCS8EncodedKeySpec;
24  import java.security.spec.X509EncodedKeySpec;
25  
26  import javax.crypto.BadPaddingException;
27  import javax.crypto.Cipher;
28  import javax.crypto.IllegalBlockSizeException;
29  import javax.crypto.KeyGenerator;
30  import javax.crypto.NoSuchPaddingException;
31  import javax.crypto.SecretKey;
32  import javax.crypto.spec.SecretKeySpec;
33  
34  public class BouncyCastleEncryptionProvider extends BaseEncryptionProvider
35  {
36      private static final Provider PROVIDER = new BouncyCastleProvider();
37  
38      private static final String STREAM_CIPHER = "RC4";
39      private static final String ASYM_CIPHER = "RSA/NONE/NoPadding";
40      private static final String ASYM_ALGORITHM = "RSA";
41  
42      private final SecretKeyFactory secretKeyFactory;
43      private final Transcoder transcoder;
44      
45      public BouncyCastleEncryptionProvider()
46      {
47          this(new ValidatingSecretKeyFactory(new BCKeyFactory(), new TransmissionValidator()), new Base64Transcoder());
48      }
49  
50      private BouncyCastleEncryptionProvider(SecretKeyFactory secretKeyFactory, Transcoder transcoder)
51      {
52          Null.not("secretKeyFactory", secretKeyFactory);
53          Null.not("transcoder", transcoder);
54  
55          this.secretKeyFactory = secretKeyFactory;
56          this.transcoder = transcoder;
57      }
58  
59      /**
60       * Decodes the given form into the real key object according to the given algorithm Uses Bouncy Castle as a provider
61       * 
62       * @param encodedForm
63       *            the byte[] containing the key data
64       * @return the generated PublicKey
65       * @throws NoSuchAlgorithmException
66       * @throws InvalidKeySpecException
67       * @throws NoSuchProviderException
68       */
69      public PublicKey toPublicKey(byte[] encodedForm) throws NoSuchAlgorithmException, InvalidKeySpecException, NoSuchProviderException
70      {
71          final X509EncodedKeySpec pubKeySpec = new X509EncodedKeySpec(encodedForm);
72          final KeyFactory keyFactory = KeyFactory.getInstance(ASYM_ALGORITHM, PROVIDER);
73          return keyFactory.generatePublic(pubKeySpec);
74      }
75  
76      /**
77       * Decodes the given form into the real key object according to the given algorithm Uses Bouncy Castle as a provider
78       * 
79       * @param encodedForm
80       *            the PKS8 encoded key data
81       * @return a fully formed PrivateKey
82       * @throws NoSuchAlgorithmException
83       * @throws InvalidKeySpecException
84       * @throws NoSuchProviderException
85       */
86      public PrivateKey toPrivateKey(byte[] encodedForm) throws NoSuchAlgorithmException, InvalidKeySpecException, NoSuchProviderException
87      {
88          final PKCS8EncodedKeySpec pubKeySpec = new PKCS8EncodedKeySpec(encodedForm);
89          final KeyFactory keyFactory = KeyFactory.getInstance(ASYM_ALGORITHM, PROVIDER);
90          return keyFactory.generatePrivate(pubKeySpec);
91      }
92  
93      /**
94       * Generates a new KeyPair.
95       * <p>
96       * Given algorithm name will be used to generate the key pair. It is mandatory. Security provides parameter is
97       * optional and can be null in which case the choice of a provider is left to the VM. Key size is optional and can
98       * be set to -1 in which case the default size is used.
99       * 
100      * @throws NoSuchAlgorithmException
101      * @throws NoSuchProviderException
102      */
103     public KeyPair generateNewKeyPair() throws NoSuchAlgorithmException, NoSuchProviderException
104     {
105         final KeyPairGenerator gen = KeyPairGenerator.getInstance(ASYM_ALGORITHM, PROVIDER);
106         return gen.generateKeyPair();
107     }
108 
109     public ApplicationCertificate decodeEncryptedCertificate(EncryptedCertificate encCert, PublicKey publicKey, String appId) throws InvalidCertificateException
110     {
111         final BufferedReader in;
112         try
113         {
114             final Cipher asymCipher = Cipher.getInstance(ASYM_CIPHER, PROVIDER);
115             asymCipher.init(Cipher.DECRYPT_MODE, publicKey);
116 
117             /**
118              * this should only happen with protocol version#1 or greater
119              */
120             final String encryptedMagicNumber = encCert.getMagicNumber();
121             if (encryptedMagicNumber != null)
122             {
123                 final String magicNumber = new String(asymCipher.doFinal(transcoder.decode(encryptedMagicNumber)), TrustedApplicationUtils.Constant.CHARSET_NAME);
124                 TrustedApplicationUtils.validateMagicNumber("public key", appId, encCert.getProtocolVersion(), magicNumber);
125             }
126             else if (encCert.getProtocolVersion() != null)
127             {
128                 throw new InvalidCertificateException(new TransportErrorMessage.BadMagicNumber("public key", appId));
129             }
130 
131             final byte[] secretKeyData = asymCipher.doFinal(transcoder.decode(encCert.getSecretKey()));
132             final SecretKeySpec secretKeySpec = new SecretKeySpec(secretKeyData, STREAM_CIPHER);
133 
134             final Cipher symCipher = Cipher.getInstance(STREAM_CIPHER, PROVIDER);
135             symCipher.init(Cipher.DECRYPT_MODE, secretKeySpec);
136             final byte[] decryptedData = symCipher.doFinal(transcoder.decode(encCert.getCertificate()));
137             in = new BufferedReader(new InputStreamReader(new ByteArrayInputStream(decryptedData), TrustedApplicationUtils.Constant.CHARSET_NAME));
138         }
139         // ///CLOVER:OFF
140         catch (NoSuchAlgorithmException e)
141         {
142             throw new AssertionError(e);
143         }
144         catch (NoSuchPaddingException e)
145         {
146             throw new AssertionError(e);
147         }
148         catch (NumberFormatException e)
149         {
150             throw new SystemException(appId, e);
151         }
152         catch (IllegalBlockSizeException e)
153         {
154             throw new SystemException(appId, e);
155         }
156         catch (BadPaddingException e)
157         {
158             throw new SystemException(appId, e);
159         }
160         catch (InvalidKeyException e)
161         {
162             throw new InvalidCertificateException(new TransportErrorMessage.BadMagicNumber("secret key", appId));
163         }
164         catch (SecurityException e)
165         {
166             // this is here for Java 1.4 only where this exception is thrown when a bad secret key is encountered
167             throw new InvalidCertificateException(new TransportErrorMessage.BadMagicNumber("secret key", appId));
168         }
169         catch (IOException e)
170         {
171             throw new RuntimeException(e);
172         }
173         // /CLOVER:ON
174 
175         try
176         {
177             final String created = in.readLine();
178             final String userName = in.readLine();
179             // validate the magic number before trying to parse the timestamp
180             TrustedApplicationUtils.validateMagicNumber("secret key", appId, encCert.getProtocolVersion(), in.readLine());
181             final long timeCreated = Long.parseLong(created);
182 
183             return new DefaultApplicationCertificate(appId, userName, timeCreated);
184         }
185         catch (NumberFormatException e)
186         {
187             throw new SystemException(appId, e);
188         }
189         // ///CLOVER:OFF
190         catch (CharConversionException e)
191         {
192             // only thrown under IBM JDK when unsupported utf8 chars are encountered
193             throw new SystemException(appId, e);
194         }
195         catch (IOException e)
196         {
197             throw new RuntimeException(e);
198         }
199         // /CLOVER:ON
200     }
201 
202     public EncryptedCertificate createEncryptedCertificate(String userName, PrivateKey privateKey, String appId)
203     {
204         try
205         {
206             final SecretKey secretKey = secretKeyFactory.generateSecretKey();
207             final Cipher symmetricCipher = Cipher.getInstance(STREAM_CIPHER, PROVIDER);
208             symmetricCipher.init(Cipher.ENCRYPT_MODE, secretKey);
209 
210             final Cipher asymCipher = Cipher.getInstance(ASYM_CIPHER, PROVIDER);
211             asymCipher.init(Cipher.ENCRYPT_MODE, privateKey);
212 
213             final String encryptedKey = transcoder.encode(asymCipher.doFinal(secretKey.getEncoded()));
214             final String encryptedMagic = transcoder.encode(asymCipher.doFinal(transcoder.getBytes(TrustedApplicationUtils.Constant.MAGIC)));
215 
216             final StringWriter writer = new StringWriter();
217             writer.write(String.valueOf(System.currentTimeMillis()));
218             writer.write('\n');
219             writer.write(userName);
220             writer.write('\n');
221             writer.write(TrustedApplicationUtils.Constant.MAGIC);
222             writer.flush();
223             final byte[] encryptedData = symmetricCipher.doFinal(transcoder.getBytes(writer.toString()));
224             final String encodedData = transcoder.encode(encryptedData);
225 
226             return new DefaultEncryptedCertificate(appId, encryptedKey, encodedData, TrustedApplicationUtils.Constant.VERSION, encryptedMagic);
227         }
228         // ///CLOVER:OFF
229         catch (NoSuchAlgorithmException e)
230         {
231             throw new AssertionError(e);
232         }
233         catch (NoSuchPaddingException e)
234         {
235             throw new AssertionError(e);
236         }
237         catch (InvalidKeyException e)
238         {
239             // only thrown under some JDKs, make it consistent
240             throw new IllegalKeyException(e);
241         }
242         catch (IllegalBlockSizeException e)
243         {
244             throw new IllegalKeyException(e);
245         }
246         catch (BadPaddingException e)
247         {
248             throw new IllegalKeyException(e);
249         }
250         // /CLOVER:ON
251     }
252 
253     interface SecretKeyFactory
254     {
255         SecretKey generateSecretKey();
256     }
257 
258     static class BCKeyFactory implements SecretKeyFactory
259     {
260         public SecretKey generateSecretKey()
261         {
262             try
263             {
264                 return KeyGenerator.getInstance(STREAM_CIPHER, PROVIDER).generateKey();
265             }
266             // /CLOVER:OFF
267             catch (NoSuchAlgorithmException e)
268             {
269                 throw new AssertionError(e);
270             }
271             // /CLOVER:ON
272         }
273     }
274 
275     static class ValidatingSecretKeyFactory implements SecretKeyFactory
276     {
277         private final SecretKeyFactory delegate;
278         private final SecretKeyValidator validator;
279 
280         ValidatingSecretKeyFactory(SecretKeyFactory secretKeyFactory, SecretKeyValidator validator)
281         {
282             this.delegate = secretKeyFactory;
283             this.validator = validator;
284         }
285 
286         public SecretKey generateSecretKey()
287         {
288             SecretKey result = delegate.generateSecretKey();
289             while (!validator.isValid(result))
290             {
291                 result = delegate.generateSecretKey();
292             }
293             return result;
294         }
295     }
296 
297     /**
298      * check that a secret key is valid
299      */
300     interface SecretKeyValidator
301     {
302         boolean isValid(SecretKey secretKey);
303     }
304 
305     /**
306      * leading zero's in the sevret key byte array lead to transmission problems
307      */
308     static class TransmissionValidator implements SecretKeyValidator
309     {
310         public boolean isValid(SecretKey secretKey)
311         {
312             final byte[] encoded = secretKey.getEncoded();
313             if (encoded.length != 16)
314             {
315                 return false;
316             }
317             if (encoded[0] == 0)
318             {
319                 return false;
320             }
321             return true;
322         }
323     }
324 
325     static class IllegalKeyException extends IllegalArgumentException
326     {
327         IllegalKeyException(Exception ex)
328         {
329             super(ex.toString());
330             this.initCause(ex);
331         }
332     }
333 }