PageRenderTime 87ms CodeModel.GetById 29ms RepoModel.GetById 0ms app.codeStats 1ms

/app/com/atlassian/connect/play/java/plugin/KeyPairPlugin.java

https://bitbucket.org/awei/ac-play-java
Java | 480 lines | 423 code | 56 blank | 1 comment | 21 complexity | 63f9b94399254058e84708f5858b59d2 MD5 | raw file
Possible License(s): Apache-2.0
  1. package com.atlassian.connect.play.java.plugin;
  2. import com.atlassian.connect.play.java.AC;
  3. import com.atlassian.connect.play.java.ConfigurationException;
  4. import com.atlassian.connect.play.java.util.Environment;
  5. import com.google.common.io.Files;
  6. import com.google.common.io.LineProcessor;
  7. import org.apache.commons.lang3.RandomStringUtils;
  8. import org.bouncycastle.jce.provider.BouncyCastleProvider;
  9. import org.bouncycastle.openssl.PEMReader;
  10. import org.bouncycastle.openssl.PEMWriter;
  11. import play.Application;
  12. import play.Play;
  13. import java.io.File;
  14. import java.io.IOException;
  15. import java.io.StringReader;
  16. import java.io.StringWriter;
  17. import java.io.Writer;
  18. import java.nio.charset.Charset;
  19. import java.security.InvalidKeyException;
  20. import java.security.Key;
  21. import java.security.KeyPair;
  22. import java.security.KeyPairGenerator;
  23. import java.security.MessageDigest;
  24. import java.security.NoSuchAlgorithmException;
  25. import java.security.NoSuchProviderException;
  26. import java.security.PrivateKey;
  27. import java.security.PublicKey;
  28. import java.security.SecureRandom;
  29. import java.security.Security;
  30. import java.security.Signature;
  31. import java.security.SignatureException;
  32. import static com.atlassian.connect.play.java.util.Utils.LOGGER;
  33. import static com.google.common.base.Preconditions.checkNotNull;
  34. import static java.lang.String.format;
  35. public class KeyPairPlugin extends AbstractPlugin
  36. {
  37. private static final String BOUNCY_CASTLE_PROVIDER = "BC";
  38. private static final String RSA = "RSA";
  39. private static final String SHA_1_PRNG = "SHA1PRNG";
  40. private static final String SHA_1_WITH_RSA = "SHA1withRSA";
  41. static final String PUBLIC_KEY_PEM = "public-key.pem";
  42. static final String PRIVATE_KEY_PEM = "private-key.pem";
  43. static final Charset UTF_8 = Charset.forName("UTF-8");
  44. static
  45. {
  46. if (Security.getProvider(BOUNCY_CASTLE_PROVIDER) == null)
  47. {
  48. Security.addProvider(new BouncyCastleProvider());
  49. }
  50. }
  51. public KeyPairPlugin(Application application)
  52. {
  53. super(application);
  54. }
  55. @Override
  56. public void onStart()
  57. {
  58. handleKeyPair();
  59. checkKeyPair();
  60. updateGitIgnore();
  61. }
  62. private void updateGitIgnore()
  63. {
  64. final File gitIgnore = new File(".gitignore");
  65. boolean hasPublicKeyIgnore = false;
  66. boolean hasPrivateKeyIgnore = false;
  67. if (gitIgnore.exists())
  68. {
  69. hasPublicKeyIgnore = hasLine(gitIgnore, PUBLIC_KEY_PEM);
  70. hasPrivateKeyIgnore = hasLine(gitIgnore, PRIVATE_KEY_PEM);
  71. }
  72. else
  73. {
  74. try
  75. {
  76. gitIgnore.createNewFile();
  77. }
  78. catch (IOException e)
  79. {
  80. LOGGER.error("Could not create .gitignore file.");
  81. LOGGER.debug("Here is why:", e);
  82. return;
  83. }
  84. }
  85. if (!hasPublicKeyIgnore)
  86. {
  87. addLine(gitIgnore, PUBLIC_KEY_PEM);
  88. }
  89. if (!hasPrivateKeyIgnore)
  90. {
  91. addLine(gitIgnore, PRIVATE_KEY_PEM);
  92. }
  93. if (!hasPrivateKeyIgnore || !hasPublicKeyIgnore)
  94. {
  95. LOGGER.info("Added key pair file to .gitignore");
  96. }
  97. }
  98. private void addLine(File gitIgnore, String line)
  99. {
  100. try
  101. {
  102. Files.append("\n" + line, gitIgnore, UTF_8);
  103. }
  104. catch (IOException e)
  105. {
  106. LOGGER.error("Could not append content to .gitignore");
  107. LOGGER.debug("Here is why:", e);
  108. }
  109. }
  110. private Boolean hasLine(File file, String expectedLine)
  111. {
  112. try
  113. {
  114. return Files.readLines(file, UTF_8, new HasLineProcessor(expectedLine));
  115. }
  116. catch (IOException e)
  117. {
  118. return false;
  119. }
  120. }
  121. private void handleKeyPair()
  122. {
  123. final String publicKey = Environment.getOptionalEnv(Environment.OAUTH_LOCAL_PUBLIC_KEY, null);
  124. final String privateKey = Environment.getOptionalEnv(Environment.OAUTH_LOCAL_PRIVATE_KEY, null);
  125. if (publicKey == null || privateKey == null)
  126. {
  127. if (AC.isDev())
  128. {
  129. LOGGER.info(format("Generating key pair for OAuth signing, into %s and %s files.", PRIVATE_KEY_PEM, PUBLIC_KEY_PEM));
  130. LOGGER.warn("Do NOT add those files into your VCS.");
  131. LOGGER.info("In production, you will have to define the two following environment variables:");
  132. LOGGER.info("\t* " + Environment.OAUTH_LOCAL_PUBLIC_KEY);
  133. LOGGER.info("\t* " + Environment.OAUTH_LOCAL_PRIVATE_KEY);
  134. generateKeyFiles();
  135. }
  136. }
  137. else
  138. {
  139. LOGGER.debug("Using defined environment properties for OAuth:");
  140. LOGGER.debug("\t* " + Environment.OAUTH_LOCAL_PUBLIC_KEY);
  141. LOGGER.debug("\t* " + Environment.OAUTH_LOCAL_PRIVATE_KEY);
  142. }
  143. }
  144. private void generateKeyFiles()
  145. {
  146. final File privateKeyFile = new File(PRIVATE_KEY_PEM);
  147. final File publicKeyFile = new File(PUBLIC_KEY_PEM);
  148. if (!privateKeyFile.exists() && !publicKeyFile.exists())
  149. {
  150. final AcKeyPair<CharSequence> pair = newKeyPair();
  151. writeKey(pair.getPrivateKey(), privateKeyFile);
  152. writeKey(pair.getPublicKey(), publicKeyFile);
  153. LOGGER.info("Generated public/private key pair.");
  154. LOGGER.debug(format("Public key is available at '%s'", publicKeyFile.getAbsolutePath()));
  155. LOGGER.debug(format("Private key is available at '%s'", privateKeyFile.getAbsolutePath()));
  156. }
  157. else if (!publicKeyFile.exists())
  158. {
  159. LOGGER.warn("Private key file exists, but couldn't find corresponding public key file. Not generating any new files.");
  160. LOGGER.debug(format("Private key file is at '%s'", privateKeyFile.getAbsolutePath()));
  161. LOGGER.debug(format("Expected public key file at '%s'", publicKeyFile.getAbsolutePath()));
  162. }
  163. else if (!privateKeyFile.exists())
  164. {
  165. LOGGER.warn("Public key file exists, but couldn't find corresponding private key file. Not generating any new files.");
  166. LOGGER.debug(format("Public key file is at '%s'", publicKeyFile.getAbsolutePath()));
  167. LOGGER.debug(format("Expected private key file at '%s'", privateKeyFile.getAbsolutePath()));
  168. }
  169. else
  170. {
  171. LOGGER.debug("Key files exists. Not generating any new files.");
  172. LOGGER.debug(format("Public key file is at '%s'", publicKeyFile.getAbsolutePath()));
  173. LOGGER.debug(format("Private key file is at '%s'", privateKeyFile.getAbsolutePath()));
  174. }
  175. }
  176. private void checkKeyPair()
  177. {
  178. try
  179. {
  180. final PrivateKey privateKey = getPrivateKey();
  181. final PublicKey publicKey = getPublicKey();
  182. final Signature signature = getSignature();
  183. final byte[] message = RandomStringUtils.random(10).getBytes();
  184. final byte[] sigBytes = sign(signature, privateKey, message);
  185. final boolean verify = verify(signature, publicKey, message, sigBytes);
  186. if (!verify)
  187. {
  188. throw new ConfigurationException("Checking key pair failed. It seems the public and private key don't" +
  189. " belong to the same key pair. Please check your configuration. We're using the following keys," +
  190. " is that what you expected?\n" +
  191. AC.publicKey.get() + "\n" +
  192. AC.privateKey.get());
  193. }
  194. }
  195. catch (InvalidKeyException | SignatureException e)
  196. {
  197. throw new ConfigurationException("Couldn't check key pair because of the following exception.", e);
  198. }
  199. }
  200. private boolean verify(Signature signature, PublicKey publicKey, byte[] message, byte[] signatureBytes) throws InvalidKeyException, SignatureException
  201. {
  202. signature.initVerify(publicKey);
  203. signature.update(message);
  204. return signature.verify(signatureBytes);
  205. }
  206. private byte[] sign(Signature signature, PrivateKey key, byte[] message) throws InvalidKeyException, SignatureException
  207. {
  208. signature.initSign(key, getSecureRandom());
  209. signature.update(message);
  210. return signature.sign();
  211. }
  212. private Signature getSignature() throws InvalidKeyException
  213. {
  214. final Signature signature;
  215. try
  216. {
  217. signature = Signature.getInstance(SHA_1_WITH_RSA, BOUNCY_CASTLE_PROVIDER);
  218. }
  219. catch (NoSuchAlgorithmException e)
  220. {
  221. throw new IllegalStateException("Can't find algorithm: " + SHA_1_WITH_RSA, e);
  222. }
  223. catch (NoSuchProviderException e)
  224. {
  225. throw new IllegalStateException("Can't find provider: " + BOUNCY_CASTLE_PROVIDER, e);
  226. }
  227. return signature;
  228. }
  229. private PublicKey getPublicKey()
  230. {
  231. final PEMReader publicPemReader = new PEMReader(new StringReader(AC.publicKey.get()));
  232. try
  233. {
  234. return ((PublicKey) publicPemReader.readObject());
  235. }
  236. catch (IOException e)
  237. {
  238. throw new IllegalStateException("Can't happen, we use a StringReader. Or have I missed something?", e);
  239. }
  240. }
  241. private PrivateKey getPrivateKey()
  242. {
  243. final PEMReader privatePemReader = new PEMReader(new StringReader(AC.privateKey.get()));
  244. try
  245. {
  246. return ((KeyPair) privatePemReader.readObject()).getPrivate();
  247. }
  248. catch (IOException e)
  249. {
  250. throw new IllegalStateException("Can't happen, we use a StringReader. Or have I missed something?", e);
  251. }
  252. }
  253. private static AcKeyPair<CharSequence> newKeyPair()
  254. {
  255. return new PemKeyPair(new KeyAcKeyPair(getRsaKeyPair()));
  256. }
  257. private void writeKey(CharSequence key, File file)
  258. {
  259. LOGGER.debug(format("Writing key:\n%s\n", key));
  260. try
  261. {
  262. Files.write(key, file, UTF_8);
  263. }
  264. catch (IOException e)
  265. {
  266. LOGGER.error(format("There was an error writing key to '%s'", file.getAbsolutePath()), e);
  267. }
  268. }
  269. private static KeyPair getRsaKeyPair()
  270. {
  271. final KeyPairGenerator generator = getKeyPairGenerator();
  272. generator.initialize(1024, getSecureRandom());
  273. return generator.generateKeyPair();
  274. }
  275. private static KeyPairGenerator getKeyPairGenerator()
  276. {
  277. try
  278. {
  279. return KeyPairGenerator.getInstance(RSA, BOUNCY_CASTLE_PROVIDER);
  280. }
  281. catch (NoSuchAlgorithmException e)
  282. {
  283. throw new IllegalStateException("Can't find algorithm: " + RSA, e);
  284. }
  285. catch (NoSuchProviderException e)
  286. {
  287. throw new IllegalStateException("Can't find provider: " + BOUNCY_CASTLE_PROVIDER, e);
  288. }
  289. }
  290. private static SecureRandom getSecureRandom()
  291. {
  292. try
  293. {
  294. return SecureRandom.getInstance(SHA_1_PRNG);
  295. }
  296. catch (NoSuchAlgorithmException e)
  297. {
  298. throw new IllegalStateException("Could not find algorithm for secure random: " + SHA_1_PRNG, e);
  299. }
  300. }
  301. private static SecureRandom createFixedRandom()
  302. {
  303. return new FixedRand();
  304. }
  305. private static final class FixedRand extends SecureRandom
  306. {
  307. private final MessageDigest sha;
  308. private byte[] state;
  309. private FixedRand()
  310. {
  311. try
  312. {
  313. this.sha = MessageDigest.getInstance("SHA-1");
  314. this.state = sha.digest();
  315. }
  316. catch (NoSuchAlgorithmException e)
  317. {
  318. throw new RuntimeException("Can't find SHA-1!");
  319. }
  320. }
  321. public void nextBytes(byte[] bytes)
  322. {
  323. int off = 0;
  324. sha.update(state);
  325. while (off < bytes.length)
  326. {
  327. state = sha.digest();
  328. if (bytes.length - off > state.length)
  329. {
  330. System.arraycopy(state, 0, bytes, off, state.length);
  331. }
  332. else
  333. {
  334. System.arraycopy(state, 0, bytes, off, bytes.length - off);
  335. }
  336. off += state.length;
  337. sha.update(state);
  338. }
  339. }
  340. }
  341. private static class KeyAcKeyPair implements AcKeyPair<Key>
  342. {
  343. private final KeyPair kp;
  344. public KeyAcKeyPair(KeyPair kp)
  345. {
  346. this.kp = kp;
  347. }
  348. @Override
  349. public Key getPublicKey()
  350. {
  351. return kp.getPublic();
  352. }
  353. @Override
  354. public Key getPrivateKey()
  355. {
  356. return kp.getPrivate();
  357. }
  358. }
  359. private static final class PemKeyPair implements AcKeyPair<CharSequence>
  360. {
  361. private final AcKeyPair<Key> kp;
  362. private PemKeyPair(AcKeyPair<Key> kp)
  363. {
  364. this.kp = checkNotNull(kp);
  365. }
  366. @Override
  367. public CharSequence getPublicKey()
  368. {
  369. return getKeyAsPem(kp.getPublicKey());
  370. }
  371. @Override
  372. public CharSequence getPrivateKey()
  373. {
  374. return getKeyAsPem(kp.getPrivateKey());
  375. }
  376. private String getKeyAsPem(Key key)
  377. {
  378. try (Writer sw = new StringWriter())
  379. {
  380. try (PEMWriter pemWriter = new PEMWriter(sw))
  381. {
  382. pemWriter.writeObject(key);
  383. }
  384. catch (IOException e)
  385. {
  386. throw new RuntimeException(e);
  387. }
  388. return sw.toString();
  389. }
  390. catch (IOException e)
  391. {
  392. throw new RuntimeException(e);
  393. }
  394. }
  395. }
  396. private static class HasLineProcessor implements LineProcessor<Boolean>
  397. {
  398. private final String expectedLine;
  399. private boolean hasLine;
  400. public HasLineProcessor(String expectedLine)
  401. {
  402. this.expectedLine = expectedLine;
  403. hasLine = false;
  404. }
  405. @Override
  406. public boolean processLine(String line) throws IOException
  407. {
  408. hasLine = line.equals(expectedLine);
  409. return !hasLine;
  410. }
  411. @Override
  412. public Boolean getResult()
  413. {
  414. return hasLine;
  415. }
  416. }
  417. // a main to generate keys manually
  418. public static void main(String[] args)
  419. {
  420. final AcKeyPair<CharSequence> keyPair = newKeyPair();
  421. System.out.println(keyPair.getPublicKey());
  422. System.out.println(keyPair.getPrivateKey());
  423. }
  424. }