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                         // ignore.
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              // expected
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              // expected
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             // expected
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             // expected
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             // expected
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             // expected - will be a SystemException under IBM JDK due to SER-118
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             // expected
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             // expected
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         // Decrypt to get the timestamp out
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             // private static final String SECRET_KEY = "6FcMEe0BgY6ohvXJrjnuig==";
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 }