/src/javapns/communication/KeystoreManager.java

https://bitbucket.org/adri11/dmoda_mobile_daemon · Java · 232 lines · 128 code · 30 blank · 74 comment · 43 complexity · 9d3fa90a996c16341fd17aea7c73d44a MD5 · raw file

  1. package javapns.communication;
  2. import java.io.*;
  3. import java.security.*;
  4. import java.security.cert.*;
  5. import java.security.cert.Certificate;
  6. import java.util.*;
  7. import javapns.communication.exceptions.*;
  8. /**
  9. * Class responsible for dealing with keystores.
  10. *
  11. * @author Sylvain Pedneault
  12. */
  13. public class KeystoreManager {
  14. private static final String REVIEW_MESSAGE = " Please review the procedure for generating a keystore for JavaPNS.";
  15. /**
  16. * Loads a keystore.
  17. *
  18. * @param server The server the keystore is intended for
  19. * @return A loaded keystore
  20. * @throws KeystoreException
  21. */
  22. static KeyStore loadKeystore(AppleServer server) throws KeystoreException {
  23. return loadKeystore(server, server.getKeystoreStream());
  24. }
  25. /**
  26. * Loads a keystore.
  27. *
  28. * @param server the server the keystore is intended for
  29. * @param keystore a keystore containing your private key and the certificate signed by Apple (File, InputStream, byte[], KeyStore or String for a file path)
  30. * @return a loaded keystore
  31. * @throws KeystoreException
  32. */
  33. static KeyStore loadKeystore(AppleServer server, Object keystore) throws KeystoreException {
  34. return loadKeystore(server, keystore, false);
  35. }
  36. /**
  37. * Loads a keystore.
  38. *
  39. * @param server the server the keystore is intended for
  40. * @param keystore a keystore containing your private key and the certificate signed by Apple (File, InputStream, byte[], KeyStore or String for a file path)
  41. * @param verifyKeystore whether or not to perform basic verifications on the keystore to detect common mistakes.
  42. * @return a loaded keystore
  43. * @throws KeystoreException
  44. */
  45. public static KeyStore loadKeystore(AppleServer server, Object keystore, boolean verifyKeystore) throws KeystoreException {
  46. if (keystore instanceof KeyStore) return (KeyStore) keystore;
  47. synchronized (server) {
  48. InputStream keystoreStream = streamKeystore(keystore);
  49. if (keystoreStream instanceof WrappedKeystore) return ((WrappedKeystore) keystoreStream).getKeystore();
  50. KeyStore keyStore;
  51. try {
  52. keyStore = KeyStore.getInstance(server.getKeystoreType());
  53. char[] password = KeystoreManager.getKeystorePasswordForSSL(server);
  54. keyStore.load(keystoreStream, password);
  55. } catch (Exception e) {
  56. throw wrapKeystoreException(e);
  57. } finally {
  58. try {
  59. keystoreStream.close();
  60. } catch (Exception e) {
  61. }
  62. }
  63. return keyStore;
  64. }
  65. }
  66. /**
  67. * Make sure that the provided keystore will be reusable.
  68. *
  69. * @param server the server the keystore is intended for
  70. * @param keystore a keystore containing your private key and the certificate signed by Apple (File, InputStream, byte[], KeyStore or String for a file path)
  71. * @return a reusable keystore
  72. * @throws KeystoreException
  73. */
  74. static Object ensureReusableKeystore(AppleServer server, Object keystore) throws KeystoreException {
  75. if (keystore instanceof InputStream) keystore = loadKeystore(server, keystore, false);
  76. return keystore;
  77. }
  78. /**
  79. * Perform basic tests on a keystore to detect common user mistakes.
  80. * If a problem is found, a KeystoreException is thrown.
  81. * If no problem is found, this method simply returns without exceptions.
  82. *
  83. * @param server the server the keystore is intended for
  84. * @param keystore a keystore containing your private key and the certificate signed by Apple (File, InputStream, byte[], KeyStore or String for a file path)
  85. * @throws KeystoreException
  86. */
  87. public static void verifyKeystoreContent(AppleServer server, Object keystore) throws KeystoreException {
  88. KeyStore keystoreToValidate = null;
  89. if (keystore instanceof KeyStore) keystoreToValidate = (KeyStore) keystore;
  90. else keystoreToValidate = loadKeystore(server, keystore);
  91. verifyKeystoreContent(keystoreToValidate);
  92. }
  93. /**
  94. * Perform basic tests on a keystore to detect common user mistakes (experimental).
  95. * If a problem is found, a KeystoreException is thrown.
  96. * If no problem is found, this method simply returns without exceptions.
  97. *
  98. * @param keystore a keystore to verify
  99. * @throws KeystoreException thrown if a problem was detected
  100. */
  101. public static void verifyKeystoreContent(KeyStore keystore) throws KeystoreException {
  102. try {
  103. int numberOfCertificates = 0;
  104. Enumeration<String> aliases = keystore.aliases();
  105. while (aliases.hasMoreElements()) {
  106. String alias = aliases.nextElement();
  107. Certificate certificate = keystore.getCertificate(alias);
  108. if (certificate instanceof X509Certificate) {
  109. X509Certificate xcert = (X509Certificate) certificate;
  110. numberOfCertificates++;
  111. /* Check validity dates */
  112. xcert.checkValidity();
  113. /* Check issuer */
  114. boolean issuerIsApple = xcert.getIssuerDN().toString().contains("Apple");
  115. if (!issuerIsApple) throw new KeystoreException("Certificate was not issued by Apple." + REVIEW_MESSAGE);
  116. /* Check certificate key usage */
  117. boolean[] keyUsage = xcert.getKeyUsage();
  118. if (!keyUsage[0]) throw new KeystoreException("Certificate usage is incorrect." + REVIEW_MESSAGE);
  119. }
  120. }
  121. if (numberOfCertificates == 0) throw new KeystoreException("Keystore does not contain any valid certificate." + REVIEW_MESSAGE);
  122. if (numberOfCertificates > 1) throw new KeystoreException("Keystore contains too many certificates." + REVIEW_MESSAGE);
  123. } catch (KeystoreException e) {
  124. throw e;
  125. } catch (CertificateExpiredException e) {
  126. throw new KeystoreException("Certificate is expired. A new one must be issued.", e);
  127. } catch (CertificateNotYetValidException e) {
  128. throw new KeystoreException("Certificate is not yet valid. Wait until the validity period is reached or issue a new certificate.", e);
  129. } catch (Exception e) {
  130. /* We ignore any other exception, as we do not want to interrupt the process because of an error we did not expect. */
  131. }
  132. }
  133. static char[] getKeystorePasswordForSSL(AppleServer server) {
  134. String password = server.getKeystorePassword();
  135. if (password == null) password = "";
  136. // if (password != null && password.length() == 0) password = null;
  137. char[] passchars = password != null ? password.toCharArray() : null;
  138. return passchars;
  139. }
  140. static KeystoreException wrapKeystoreException(Exception e) {
  141. if (e != null) {
  142. String msg = e.toString();
  143. if (msg.contains("javax.crypto.BadPaddingException")) {
  144. return new InvalidKeystorePasswordException();
  145. }
  146. if (msg.contains("DerInputStream.getLength(): lengthTag=127, too big")) {
  147. return new InvalidKeystoreFormatException();
  148. }
  149. if (msg.contains("java.lang.ArithmeticException: / by zero") || msg.contains("java.security.UnrecoverableKeyException: Get Key failed: / by zero")) {
  150. return new InvalidKeystorePasswordException("Blank passwords not supported (#38). You must create your keystore with a non-empty password.");
  151. }
  152. }
  153. return new KeystoreException("Keystore exception: " + e.getMessage(), e);
  154. }
  155. /**
  156. * Given an object representing a keystore, returns an actual stream for that keystore.
  157. * Allows you to provide an actual keystore as an InputStream or a byte[] array,
  158. * or a reference to a keystore file as a File object or a String path.
  159. *
  160. * @param keystore a keystore containing your private key and the certificate signed by Apple (File, InputStream, byte[], KeyStore or String for a file path)
  161. * @return A stream to the keystore.
  162. * @throws FileNotFoundException
  163. */
  164. static InputStream streamKeystore(Object keystore) throws InvalidKeystoreReferenceException {
  165. validateKeystoreParameter(keystore);
  166. try {
  167. if (keystore instanceof InputStream) return (InputStream) keystore;
  168. else if (keystore instanceof KeyStore) return new WrappedKeystore((KeyStore) keystore);
  169. else if (keystore instanceof File) return new BufferedInputStream(new FileInputStream((File) keystore));
  170. else if (keystore instanceof String) return new BufferedInputStream(new FileInputStream((String) keystore));
  171. else if (keystore instanceof byte[]) return new ByteArrayInputStream((byte[]) keystore);
  172. else return null; // we should not get here since validateKeystore ensures that the reference is valid
  173. } catch (Exception e) {
  174. throw new InvalidKeystoreReferenceException("Invalid keystore reference: " + e.getMessage());
  175. }
  176. }
  177. /**
  178. * Ensures that a keystore parameter is actually supported by the KeystoreManager.
  179. *
  180. * @param keystore a keystore containing your private key and the certificate signed by Apple (File, InputStream, byte[], KeyStore or String for a file path)
  181. * @throws InvalidKeystoreReferenceException thrown if the provided keystore parameter is not supported
  182. */
  183. public static void validateKeystoreParameter(Object keystore) throws InvalidKeystoreReferenceException {
  184. if (keystore == null) throw new InvalidKeystoreReferenceException((Object) null);
  185. if (keystore instanceof KeyStore) return;
  186. if (keystore instanceof InputStream) return;
  187. if (keystore instanceof String) keystore = new File((String) keystore);
  188. if (keystore instanceof File) {
  189. File file = (File) keystore;
  190. if (!file.exists()) throw new InvalidKeystoreReferenceException("Invalid keystore reference. File does not exist: " + file.getAbsolutePath());
  191. if (!file.isFile()) throw new InvalidKeystoreReferenceException("Invalid keystore reference. Path does not refer to a valid file: " + file.getAbsolutePath());
  192. if (file.length() <= 0) throw new InvalidKeystoreReferenceException("Invalid keystore reference. File is empty: " + file.getAbsolutePath());
  193. return;
  194. }
  195. if (keystore instanceof byte[]) {
  196. byte[] bytes = (byte[]) keystore;
  197. if (bytes.length == 0) throw new InvalidKeystoreReferenceException("Invalid keystore reference. Byte array is empty");
  198. return;
  199. }
  200. throw new InvalidKeystoreReferenceException(keystore);
  201. }
  202. }