1 package com.atlassian.security.auth.trustedapps;
2
3 import java.io.*;
4 import java.net.MalformedURLException;
5 import java.net.URI;
6 import java.net.URL;
7 import java.security.KeyFactory;
8 import java.security.NoSuchAlgorithmException;
9 import java.security.PrivateKey;
10 import java.security.Provider;
11 import java.security.PublicKey;
12 import java.security.Signature;
13 import java.security.spec.PKCS8EncodedKeySpec;
14 import java.security.spec.X509EncodedKeySpec;
15
16 import javax.crypto.Cipher;
17 import javax.crypto.SecretKey;
18 import javax.crypto.spec.SecretKeySpec;
19
20 import com.atlassian.security.auth.trustedapps.ApplicationRetriever.ApplicationNotFoundException;
21 import com.atlassian.security.auth.trustedapps.ApplicationRetriever.RemoteSystemNotFoundException;
22
23 import org.bouncycastle.jce.provider.BouncyCastleProvider;
24 import org.bouncycastle.util.encoders.Base64;
25
26 import junit.framework.TestCase;
27
28 public class TestBouncyCastleEncryptionProvider extends TestCase
29 {
30 private final EncryptionProvider encryptionProvider = new BouncyCastleEncryptionProvider() {
31 @Override
32 public Application getApplicationCertificate(String baseUrl) throws ApplicationRetriever.RetrievalException {
33 InputStream inputStream = null;
34 try {
35 URL url = new URL(baseUrl + TrustedApplicationUtils.Constant.CERTIFICATE_URL_PATH);
36 inputStream = new FileInputStream(url.getFile());
37 return new InputStreamApplicationRetriever(inputStream, this).getApplication();
38 } catch (FileNotFoundException e) {
39 throw new ApplicationNotFoundException(e);
40 } catch (MalformedURLException e) {
41 throw new RemoteSystemNotFoundException(e);
42 } finally {
43 if (inputStream != null) {
44 try {
45 inputStream.close();
46 } catch (IOException e) {
47
48 }
49 }
50 }
51 }
52 };
53
54 public void testGetApplicationCertificateReturnsEmpty() throws Exception
55 {
56 URL url = this.getClass().getResource("/trustedapps");
57 File root = new File(new URI(url.toString()));
58 assertTrue(root.isDirectory());
59 File cert = new File(root, "admin/appTrustCertificate");
60 cert.delete();
61 File certDir = new File(root, "admin");
62 certDir.mkdirs();
63 assertTrue(cert.getCanonicalPath(), cert.createNewFile());
64 FileWriter writer = new FileWriter(cert);
65 writer.write("");
66 writer.close();
67 try
68 {
69 encryptionProvider.getApplicationCertificate(root.toURI().toString());
70 fail("CertNotFound expected");
71 }
72 catch (ApplicationNotFoundException e)
73 {
74
75 }
76 }
77
78 public void testGetApplicationCertificateNotFoundAtAll() throws Exception
79 {
80 URL url = this.getClass().getResource("/");
81 File root = new File(new URI(url.toString()));
82 assertTrue(root.isDirectory());
83 try
84 {
85 encryptionProvider.getApplicationCertificate(root.toURI().toString());
86 fail("FNFE expected");
87 }
88 catch (ApplicationNotFoundException e)
89 {
90
91 }
92 }
93
94 public void testGetApplicationCertificateMalformedUrl() throws Exception
95 {
96 try
97 {
98 encryptionProvider.getApplicationCertificate("noscheme://some/url");
99 fail("InvalidApplicationDetailsException expected");
100 }
101 catch (RemoteSystemNotFoundException yay)
102 {
103
104 }
105 }
106
107 public void testGetApplicationCertificateReturnsProperly() throws Exception
108 {
109 final String publicKeyData = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAu6q2RIBqhT0Ny59W7cZ1zwSHFBAJeCpkbzY0nCMUK5eiim/9TUfNvlpSJj5Ks/zs5Ll4R129rYtTfIfvIq4sSMjXKB8rftswet4uQWdTcTJyesLHbYgHqSIS0b+1JtQsDvjB5aEYApS4nc+fjZmwJQkpN++J8QpqQeoGDzq+zVxAzuGdVrkMEuCXcAS4znYFqW7VytvS0DHhreSIh1fGWLJIqE15Jih91Up2W+dRdLAdW0OVozOsWYFx5+L325PWkSgsDCMGVyNCdTnwCzI4FiYsT4MLODkOnOt9gDQTMjn8H1NM5K3d3Sb7WH2hKwuBxLyDRUY7qUB1bwtGU6LgjQIDAQAB";
110
111 URL url = this.getClass().getResource("/trustedapps");
112 File root = new File(new URI(url.toString()));
113 assertTrue(root.isDirectory());
114 File cert = new File(root, "admin/appTrustCertificate");
115 cert.delete();
116 File certDir = new File(root, "admin");
117 certDir.mkdirs();
118 assertTrue(cert.getCanonicalPath(), cert.createNewFile());
119 FileWriter writer = new FileWriter(cert);
120 writer.write("myApplicationId\n");
121 writer.write(publicKeyData);
122 writer.close();
123 Application app = encryptionProvider.getApplicationCertificate(root.toURI().toString());
124 assertEquals("myApplicationId", app.getID());
125 }
126
127 public void testDecodeBadSecretKey() throws Exception
128 {
129 Token token = new Token();
130 long time = System.currentTimeMillis();
131 EncryptedCertificate encrypted = new DefaultEncryptedCertificate("id", "bad-secret-key-bad!", token.encrypt("fred", time), TrustedApplicationUtils.Constant.VERSION, token.getMagic());
132 try
133 {
134 encryptionProvider.decodeEncryptedCertificate(encrypted, token.getPublicKey(), "id");
135 fail("InvalidCertificateException expected");
136 }
137 catch (InvalidCertificateException e)
138 {
139
140 }
141 }
142
143 public void testDecodeSecretKey() throws Exception
144 {
145 Token token = new Token();
146 long time = System.currentTimeMillis();
147 EncryptedCertificate encrypted = new DefaultEncryptedCertificate("id", token.getSecretKey(), token.encrypt("fred", time), TrustedApplicationUtils.Constant.VERSION, token.getMagic());
148 ApplicationCertificate cert = encryptionProvider.decodeEncryptedCertificate(encrypted, token.getPublicKey(), "id");
149 assertEquals(time, cert.getCreationTime().getTime());
150 assertEquals("fred", cert.getUserName());
151 assertEquals("id", cert.getApplicationID());
152 }
153
154 public void testDecodeBadMagicNumber() throws Exception
155 {
156 Token token = new Token();
157 long time = System.currentTimeMillis();
158 EncryptedCertificate encrypted = new DefaultEncryptedCertificate("id", token.getSecretKey(), token.encrypt("fred", time), TrustedApplicationUtils.Constant.VERSION, "bad-magic, bad!");
159 try
160 {
161 encryptionProvider.decodeEncryptedCertificate(encrypted, token.getPublicKey(), "id");
162 fail("InvalidCertificateException expected");
163 }
164 catch (InvalidCertificateException e)
165 {
166
167 }
168 }
169
170 public void testDecodeInvalidCertificate() throws Exception
171 {
172 Token token = new Token();
173 EncryptedCertificate encrypted = new DefaultEncryptedCertificate("id", token.getSecretKey(), "TestTrustedApplicationClient.id", TrustedApplicationUtils.Constant.VERSION, token.getMagic());
174 try
175 {
176 encryptionProvider.decodeEncryptedCertificate(encrypted, token.getPublicKey(), "id");
177 fail("InvalidCertificateException expected");
178 }
179 catch (InvalidCertificateException e)
180 {
181
182 }
183 }
184
185 public void testDecodeNumberFormatException() throws Exception
186 {
187 Token token = new Token()
188 {
189 String encrypt(String userName, long time)
190 {
191 try
192 {
193 Cipher cipher = Cipher.getInstance(KeyData.STREAM_CIPHER, KeyData.BOUNCY_CASTLE_PROVIDER);
194 cipher.init(Cipher.ENCRYPT_MODE, secretKey);
195 StringBuffer buffer = new StringBuffer("not-a-number").append("\n").append(userName).append("\n").append(TrustedApplicationUtils.Constant.MAGIC);
196 return new String(Base64.encode(cipher.doFinal(buffer.toString().getBytes())), TrustedApplicationUtils.Constant.CHARSET_NAME);
197 }
198 catch (Exception e)
199 {
200 throw new RuntimeException(e);
201 }
202 }
203 };
204 long time = System.currentTimeMillis();
205 EncryptedCertificate encrypted = new DefaultEncryptedCertificate("id", token.getSecretKey(), token.encrypt("fred", time), TrustedApplicationUtils.Constant.VERSION, token.getMagic());
206 try
207 {
208 encryptionProvider.decodeEncryptedCertificate(encrypted, token.getPublicKey(), "id");
209 fail("InvalidCertificateException expected");
210 }
211 catch (SystemException e)
212 {
213
214 }
215 }
216
217 public void testDecodeNullMagicNumberVersion0() throws Exception
218 {
219 Token token = new Token();
220 long time = System.currentTimeMillis();
221 EncryptedCertificate encrypted = new DefaultEncryptedCertificate("id", token.getSecretKey(), token.encrypt("fred", time), null, null);
222 ApplicationCertificate cert = encryptionProvider.decodeEncryptedCertificate(encrypted, token.getPublicKey(), "id");
223 assertEquals(time, cert.getCreationTime().getTime());
224 assertEquals("fred", cert.getUserName());
225 assertEquals("id", cert.getApplicationID());
226 }
227
228 public void testDecodeNullMagicNumberVersion1() throws Exception
229 {
230 Token token = new Token();
231 long time = System.currentTimeMillis();
232 EncryptedCertificate encrypted = new DefaultEncryptedCertificate("id", token.getSecretKey(), token.encrypt("fred", time), TrustedApplicationUtils.Constant.VERSION, null);
233 try
234 {
235 encryptionProvider.decodeEncryptedCertificate(encrypted, token.getPublicKey(), "id");
236 fail("InvalidCertificateException expected");
237 }
238 catch (InvalidCertificateException e)
239 {
240
241 }
242 }
243
244 public void testBCSecretKeyFactory()
245 {
246 BouncyCastleEncryptionProvider.SecretKeyFactory keyFactory = new BouncyCastleEncryptionProvider.BCKeyFactory();
247 SecretKey secretKey = keyFactory.generateSecretKey();
248 assertNotNull(secretKey);
249 assertEquals("RC4", secretKey.getAlgorithm());
250 assertEquals("RAW", secretKey.getFormat());
251 assertNotNull(secretKey.getEncoded());
252 assertEquals(16, secretKey.getEncoded().length);
253 }
254
255 public void testSecretKeyValidatorValidatesLength16()
256 {
257 BouncyCastleEncryptionProvider.SecretKeyValidator keyValidator = new BouncyCastleEncryptionProvider.TransmissionValidator();
258 SecretKey secretKey = new SecretKeySpec(new byte[] { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }, "ALG");
259 assertTrue(keyValidator.isValid(secretKey));
260 }
261
262 public void testSecretKeyValidatorInValidatesLengthLessThan16()
263 {
264 BouncyCastleEncryptionProvider.SecretKeyValidator keyValidator = new BouncyCastleEncryptionProvider.TransmissionValidator();
265 SecretKey secretKey = new SecretKeySpec(new byte[] { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }, "ALG");
266 assertFalse(keyValidator.isValid(secretKey));
267 }
268
269 public void testSecretKeyValidatorInValidatesLengthGreaterThan16()
270 {
271 BouncyCastleEncryptionProvider.SecretKeyValidator keyValidator = new BouncyCastleEncryptionProvider.TransmissionValidator();
272 SecretKey secretKey = new SecretKeySpec(new byte[] { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }, "ALG");
273 assertFalse(keyValidator.isValid(secretKey));
274 }
275
276 public void testSecretKeyValidatorInValidatesLeadingZero()
277 {
278 BouncyCastleEncryptionProvider.SecretKeyValidator keyValidator = new BouncyCastleEncryptionProvider.TransmissionValidator();
279 SecretKey secretKey = new SecretKeySpec(new byte[] { 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }, "ALG");
280 assertFalse(keyValidator.isValid(secretKey));
281 }
282
283 public void testValidatingSecretKeyFactory()
284 {
285 final int[] called = new int[2];
286 final int factory = 0;
287 final int validation = 1;
288 final int expectedCallCount = 5;
289
290 BouncyCastleEncryptionProvider.SecretKeyFactory mockFactory = new BouncyCastleEncryptionProvider.SecretKeyFactory()
291 {
292 public SecretKey generateSecretKey()
293 {
294 ++called[factory];
295 return new SecretKeySpec(new byte[] { (byte) called[factory], 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }, "ALG");
296 }
297 };
298
299 BouncyCastleEncryptionProvider.SecretKeyValidator mockValidator = new BouncyCastleEncryptionProvider.SecretKeyValidator()
300 {
301 public boolean isValid(SecretKey secretKey)
302 {
303 return ++called[validation] == expectedCallCount;
304 }
305 };
306
307 BouncyCastleEncryptionProvider.SecretKeyFactory testFactory = new BouncyCastleEncryptionProvider.ValidatingSecretKeyFactory(mockFactory, mockValidator);
308 SecretKey secretKey = testFactory.generateSecretKey();
309 assertNotNull(secretKey);
310 assertEquals(expectedCallCount, called[factory]);
311 assertEquals(expectedCallCount, called[validation]);
312 assertEquals(expectedCallCount, called[factory]);
313 assertNotNull(secretKey.getEncoded());
314 assertEquals(16, secretKey.getEncoded().length);
315 assertEquals(expectedCallCount, secretKey.getEncoded()[0]);
316 }
317
318 public void testEncryptedCertificateOmitsSignatureWhenUrlIsNotProvided() throws NoSuchAlgorithmException
319 {
320 Token t = new Token();
321
322 EncryptedCertificate c = encryptionProvider.createEncryptedCertificate("user", t.privateKey, "appId", null);
323 assertNull(c.getSignature());
324 assertEquals(Integer.valueOf(1), c.getProtocolVersion());
325 }
326
327 public void testEncryptedCertificateIncludesUrlSignature() throws NoSuchAlgorithmException
328 {
329 Token t = new Token();
330
331 EncryptedCertificate c = encryptionProvider.createEncryptedCertificate("user", t.privateKey, "appId", "http://www.example.com/");
332 assertNotNull(c.getSignature());
333 assertEquals(Integer.valueOf(2), c.getProtocolVersion());
334 }
335
336 public void testEncryptedCertificateSignatureValidates() throws Exception
337 {
338 Token t = new Token();
339
340 EncryptedCertificate c = encryptionProvider.createEncryptedCertificate("user", t.privateKey, "appId", "http://www.example.com/");
341
342
343 ApplicationCertificate cert = encryptionProvider.decodeEncryptedCertificate(c, t.publicKey, "appId");
344
345 Signature s = Signature.getInstance("SHA1withRSA");
346 s.initVerify(t.publicKey);
347 s.update((cert.getCreationTime().getTime() + "\n" + "http://www.example.com/").getBytes("us-ascii"));
348 assertTrue(s.verify(Base64.decode(c.getSignature())));
349 }
350
351
352 private static class Token
353 {
354 static final class KeyData
355 {
356 private static final String PUBLIC_KEY = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCySptbugHAzWUJY3ALWhuSCPhVXnwbUBfsRExYQitBCVny4V1DcU2SAx22bH9dSM0X7NdMObF74r+Wd77QoPAtaySqFLqCeRCbFmhHgVSi+pGeCipTpueefSkz2AX8Aj+9x27tqjBsX1LtNWVLDsinEhBWN68R+iEOmf/6jGWObQIDAQAB";
357 private static final String PRIVATE_KEY = "MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBALJKm1u6AcDNZQljcAtaG5II+FVefBtQF+xETFhCK0EJWfLhXUNxTZIDHbZsf11IzRfs10w5sXviv5Z3vtCg8C1rJKoUuoJ5EJsWaEeBVKL6kZ4KKlOm5559KTPYBfwCP73Hbu2qMGxfUu01ZUsOyKcSEFY3rxH6IQ6Z//qMZY5tAgMBAAECgYB4QXJAkFmWXfOEPZnZTlHCUmKN0kkLcx5vsjF8ZkUefNw6wl9Rmh6kGY30+YF+vhf3xzwAoflggjSPnP0LY0Ibf0XxMcNjR1zBsl9X7gKfXghIunS6gbcwrEwBNc5GR4zkYjYaZQ4zVvm3oMS2glV9NlXAUl41VL2XAQC/ENwbUQJBAOdoAz4hZGgke9AxoKLZh215gY+PLXqVLlWf14Ypk70Efk/bVvF10EsAOuAm9queCyr0qNf/vgHrm4HHXwJz4SsCQQDFPXir5qs+Kf2Y0KQ+WO5IRaNmrOlNvWDqJP/tDGfF/TYo6nSI0dGtWNfwZyDB47PbUq3zxCHYjExBJ9vQNZLHAkEA4JlCtHYCl1X52jug1w7c9DN/vc/Q626J909aB3ypSUdoNagFPf0EexcxDcijmDSgUEQA8Qzm5cRBPfg9Tgsc2wJBAIKbiv2hmEFowtHfTvMuJlNbMbF6zF67CaLib0oEDe+QFb4QSqyS69py20MItytM4btYy3GArbzcYl4+y5La9t8CQE2BkMV3MLcpAKjxtK5SYwCyLT591k35isGxmIlSQBQbDmGP9L5ZeXmVGVxRCGbBQjCzeoafPvUZo65kaRQHUJc=";
358
359 private static final String SECRET_KEY = "T52KBiVRRol8V2/DS7cy9G9iRdv+vSH4wLqH9q7wdSiFtZw9MRG3ihhLSmmH1MnkwPeLg+y0wtDZuokMr6eJwPo+1dHO3t2pb7IwVJg+UqGgn97LdihAMgwqLApnQIMquDe5uDuuK6Qaey+D6EXu2E90FI4Z0mHqaE3Wbo+HC50=";
360 private static final String ALGORITHM = "RSA";
361 private static final Provider BOUNCY_CASTLE_PROVIDER = new BouncyCastleProvider();
362 private static final String STREAM_CIPHER = "RC4";
363 private static final String ASYM_CIPHER = "RSA/NONE/NoPadding";
364 }
365
366 final PrivateKey privateKey;
367 final PublicKey publicKey;
368 final SecretKey secretKey;
369
370 Token()
371 {
372 try
373 {
374 KeyFactory keyFactory = KeyFactory.getInstance(KeyData.ALGORITHM, KeyData.BOUNCY_CASTLE_PROVIDER);
375 privateKey = keyFactory.generatePrivate(new PKCS8EncodedKeySpec(Base64.decode(KeyData.PRIVATE_KEY.getBytes(TrustedApplicationUtils.Constant.CHARSET_NAME))));
376 publicKey = keyFactory.generatePublic(new X509EncodedKeySpec(Base64.decode(KeyData.PUBLIC_KEY.getBytes(TrustedApplicationUtils.Constant.CHARSET_NAME))));
377
378 Cipher cipher = Cipher.getInstance(KeyData.ASYM_CIPHER, KeyData.BOUNCY_CASTLE_PROVIDER);
379 cipher.init(Cipher.DECRYPT_MODE, publicKey);
380
381 byte[] secretKeyData = cipher.doFinal(Base64.decode(KeyData.SECRET_KEY.getBytes(TrustedApplicationUtils.Constant.CHARSET_NAME)));
382
383 secretKey = new SecretKeySpec(secretKeyData, KeyData.STREAM_CIPHER);
384 }
385 catch (Exception e)
386 {
387 throw new RuntimeException(e);
388 }
389 }
390
391 PublicKey getPublicKey()
392 {
393 return publicKey;
394 }
395
396 String getSecretKey()
397 {
398 try
399 {
400 Cipher cipher = Cipher.getInstance(KeyData.ASYM_CIPHER, KeyData.BOUNCY_CASTLE_PROVIDER);
401 cipher.init(Cipher.ENCRYPT_MODE, privateKey);
402 return new String(Base64.encode(cipher.doFinal(secretKey.getEncoded())), TrustedApplicationUtils.Constant.CHARSET_NAME);
403 }
404 catch (Exception e)
405 {
406 throw new RuntimeException(e);
407 }
408 }
409
410 String getMagic()
411 {
412 try
413 {
414 Cipher cipher = Cipher.getInstance(KeyData.ASYM_CIPHER, KeyData.BOUNCY_CASTLE_PROVIDER);
415 cipher.init(Cipher.ENCRYPT_MODE, privateKey);
416 return new String(Base64.encode(cipher.doFinal(TrustedApplicationUtils.Constant.MAGIC.getBytes())), TrustedApplicationUtils.Constant.CHARSET_NAME);
417 }
418 catch (Exception e)
419 {
420 throw new RuntimeException(e);
421 }
422 }
423
424 String encrypt(String userName, long time)
425 {
426 try
427 {
428 Cipher cipher = Cipher.getInstance(KeyData.STREAM_CIPHER, KeyData.BOUNCY_CASTLE_PROVIDER);
429 cipher.init(Cipher.ENCRYPT_MODE, secretKey);
430 StringBuffer buffer = new StringBuffer(String.valueOf(time)).append("\n").append(userName).append("\n").append(TrustedApplicationUtils.Constant.MAGIC);
431 return new String(Base64.encode(cipher.doFinal(buffer.toString().getBytes(TrustedApplicationUtils.Constant.CHARSET_NAME))), TrustedApplicationUtils.Constant.CHARSET_NAME);
432 }
433 catch (Exception e)
434 {
435 throw new RuntimeException(e);
436 }
437 }
438 }
439 }