PageRenderTime 51ms CodeModel.GetById 21ms RepoModel.GetById 1ms app.codeStats 0ms

/android/jni/src/main/java/net/sourceforge/qpwmc/jni/KeyChainKeyManager.java

https://gitlab.com/bjk/qpwmc
Java | 203 lines | 140 code | 28 blank | 35 comment | 31 complexity | 77f10570a8395c84dff61c6813cb117f MD5 | raw file
Possible License(s): LGPL-2.1
  1. /* Borrowed from K-9. */
  2. package net.sourceforge.qpwmc.jni;
  3. import java.net.Socket;
  4. import java.security.Principal;
  5. import java.security.PrivateKey;
  6. import java.security.cert.CertificateException;
  7. import java.security.cert.X509Certificate;
  8. import java.util.Arrays;
  9. import java.util.List;
  10. import java.util.Locale;
  11. import javax.net.ssl.SSLEngine;
  12. import javax.net.ssl.X509ExtendedKeyManager;
  13. import javax.security.auth.x500.X500Principal;
  14. import android.content.Context;
  15. import android.os.Build;
  16. import android.security.KeyChain;
  17. import android.security.KeyChainException;
  18. import android.util.Log;
  19. /**
  20. * For client certificate authentication! Provide private keys and certificates
  21. * during the TLS handshake using the Android 4.0 KeyChain API.
  22. */
  23. class KeyChainKeyManager extends X509ExtendedKeyManager {
  24. private static PrivateKey sClientCertificateReferenceWorkaround;
  25. private static synchronized void savePrivateKeyReference(PrivateKey privateKey) {
  26. if (sClientCertificateReferenceWorkaround == null) {
  27. sClientCertificateReferenceWorkaround = privateKey;
  28. }
  29. }
  30. private String mAlias = null;
  31. private X509Certificate[] mChain = null;
  32. private PrivateKey mPrivateKey = null;
  33. public KeyChainKeyManager(Context context, String alias) {
  34. mAlias = alias;
  35. try {
  36. mChain = fetchCertificateChain(context, alias);
  37. mPrivateKey = fetchPrivateKey(context, alias);
  38. } catch (Exception e) {
  39. System.err.printf("keychain exception");
  40. }
  41. }
  42. private X509Certificate[] fetchCertificateChain(Context context, String alias) {
  43. X509Certificate[] chain = null;
  44. try {
  45. chain = KeyChain.getCertificateChain(context, alias);
  46. if (chain == null || chain.length == 0) {
  47. System.err.printf("no certificate chain");
  48. }
  49. } catch (Exception e) {
  50. }
  51. try {
  52. for (X509Certificate certificate : chain) {
  53. certificate.checkValidity();
  54. }
  55. } catch (CertificateException e) {
  56. System.err.printf("certificate xception 2");
  57. }
  58. return chain;
  59. }
  60. private PrivateKey fetchPrivateKey(Context context, String alias) {
  61. PrivateKey privateKey = null;
  62. try {
  63. privateKey = KeyChain.getPrivateKey(context, alias);
  64. } catch (Exception e) {
  65. System.err.printf("no private key");
  66. }
  67. /*
  68. * We need to keep reference to the first private key retrieved so
  69. * it won't get garbage collected. If it will then the whole app
  70. * will crash on Android < 4.2 with "Fatal signal 11 code=1". See
  71. * https://code.google.com/p/android/issues/detail?id=62319
  72. */
  73. /*
  74. if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1) {
  75. savePrivateKeyReference(privateKey);
  76. }
  77. */
  78. return privateKey;
  79. }
  80. @Override
  81. public String chooseClientAlias(String[] keyTypes, Principal[] issuers, Socket socket) {
  82. return chooseAlias(keyTypes, issuers);
  83. }
  84. @Override
  85. public X509Certificate[] getCertificateChain(String alias) {
  86. return (mAlias.equals(alias) ? mChain : null);
  87. }
  88. @Override
  89. public PrivateKey getPrivateKey(String alias) {
  90. return (mAlias.equals(alias) ? mPrivateKey : null);
  91. }
  92. @Override
  93. public String chooseServerAlias(String keyType, Principal[] issuers, Socket socket) {
  94. return chooseAlias(new String[] { keyType }, issuers);
  95. }
  96. @Override
  97. public String[] getClientAliases(String keyType, Principal[] issuers) {
  98. final String al = chooseAlias(new String[] { keyType }, issuers);
  99. return (al == null ? null : new String[] { al });
  100. }
  101. @Override
  102. public String[] getServerAliases(String keyType, Principal[] issuers) {
  103. final String al = chooseAlias(new String[] { keyType }, issuers);
  104. return (al == null ? null : new String[] { al });
  105. }
  106. @Override
  107. public String chooseEngineClientAlias(String[] keyTypes, Principal[] issuers, SSLEngine engine) {
  108. return chooseAlias(keyTypes, issuers);
  109. }
  110. @Override
  111. public String chooseEngineServerAlias(String keyType, Principal[] issuers, SSLEngine engine) {
  112. return chooseAlias(new String[] { keyType }, issuers);
  113. }
  114. private String chooseAlias(String[] keyTypes, Principal[] issuers) {
  115. if (keyTypes == null || keyTypes.length == 0) {
  116. return null;
  117. }
  118. final X509Certificate cert = mChain[0];
  119. final String certKeyAlg = cert.getPublicKey().getAlgorithm();
  120. final String certSigAlg = cert.getSigAlgName().toUpperCase(Locale.US);
  121. for (String keyAlgorithm : keyTypes) {
  122. if (keyAlgorithm == null) {
  123. continue;
  124. }
  125. final String sigAlgorithm;
  126. // handle cases like EC_EC and EC_RSA
  127. int index = keyAlgorithm.indexOf('_');
  128. if (index == -1) {
  129. sigAlgorithm = null;
  130. } else {
  131. sigAlgorithm = keyAlgorithm.substring(index + 1);
  132. keyAlgorithm = keyAlgorithm.substring(0, index);
  133. }
  134. // key algorithm does not match
  135. if (!certKeyAlg.equals(keyAlgorithm)) {
  136. continue;
  137. }
  138. /*
  139. * TODO find a more reliable test for signature
  140. * algorithm. Unfortunately value varies with
  141. * provider. For example for "EC" it could be
  142. * "SHA1WithECDSA" or simply "ECDSA".
  143. */
  144. // sig algorithm does not match
  145. if (sigAlgorithm != null && certSigAlg != null
  146. && !certSigAlg.contains(sigAlgorithm)) {
  147. continue;
  148. }
  149. // no issuers to match
  150. if (issuers == null || issuers.length == 0) {
  151. return mAlias;
  152. }
  153. List<Principal> issuersList = Arrays.asList(issuers);
  154. // check that a certificate in the chain was issued by one of the specified issuers
  155. for (X509Certificate certFromChain : mChain) {
  156. /*
  157. * Note use of X500Principal from
  158. * getIssuerX500Principal as opposed to Principal
  159. * from getIssuerDN. Principal.equals test does
  160. * not work in the case where
  161. * xcertFromChain.getIssuerDN is a bouncycastle
  162. * org.bouncycastle.jce.X509Principal.
  163. */
  164. X500Principal issuerFromChain = certFromChain.getIssuerX500Principal();
  165. if (issuersList.contains(issuerFromChain)) {
  166. return mAlias;
  167. }
  168. }
  169. Log.w("QPWMC", "Client certificate " + mAlias + " not issued by any of the requested issuers");
  170. return null;
  171. }
  172. Log.w("QPWMC", "Client certificate " + mAlias + " does not match any of the requested key types");
  173. return null;
  174. }
  175. }