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