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