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
68
69
70
71
72
73
74
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
85
86
87
88
89
90
91
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
102
103
104
105
106
107
108
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
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
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
174 throw new InvalidCertificateException(new TransportErrorMessage.BadMagicNumber("secret key", appId));
175 }
176 catch (IOException e)
177 {
178 throw new RuntimeException(e);
179 }
180
181
182 try
183 {
184 final String created = in.readLine();
185 final String userName = in.readLine();
186
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
197 catch (CharConversionException e)
198 {
199
200 throw new SystemException(appId, e);
201 }
202 catch (IOException e)
203 {
204 throw new RuntimeException(e);
205 }
206
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
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
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
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
309 catch (NoSuchAlgorithmException e)
310 {
311 throw new AssertionError(e);
312 }
313
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
341
342 interface SecretKeyValidator
343 {
344 boolean isValid(SecretKey secretKey);
345 }
346
347
348
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 }