Add methods to parse keys from PEM input.

Change-Id: Ia79da618d3f7b749a194ab80ac5cb7821a26261d
diff --git a/src/main/java/org/conscrypt/NativeCrypto.java b/src/main/java/org/conscrypt/NativeCrypto.java
index 437316b..073e051 100644
--- a/src/main/java/org/conscrypt/NativeCrypto.java
+++ b/src/main/java/org/conscrypt/NativeCrypto.java
@@ -104,6 +104,10 @@
 
     public static native long d2i_PUBKEY(byte[] data);
 
+    public static native long PEM_read_bio_PUBKEY(long bioCtx);
+
+    public static native long PEM_read_bio_PrivateKey(long bioCtx);
+
     public static native long getRSAPrivateKeyWrapper(PrivateKey key, byte[] modulus);
 
     public static native long getECPrivateKeyWrapper(PrivateKey key,
diff --git a/src/main/java/org/conscrypt/OpenSSLKey.java b/src/main/java/org/conscrypt/OpenSSLKey.java
index 599fd2b..aa9a91e 100644
--- a/src/main/java/org/conscrypt/OpenSSLKey.java
+++ b/src/main/java/org/conscrypt/OpenSSLKey.java
@@ -16,6 +16,7 @@
 
 package org.conscrypt;
 
+import java.io.InputStream;
 import java.security.InvalidKeyException;
 import java.security.NoSuchAlgorithmException;
 import java.security.PrivateKey;
@@ -99,6 +100,28 @@
     }
 
     /**
+     * Parse a private key in PEM encoding from the provided input stream.
+     *
+     * @throws InvalidKeyException if parsing fails
+     */
+    public static OpenSSLKey fromPrivateKeyPemInputStream(InputStream is)
+            throws InvalidKeyException {
+        OpenSSLBIOInputStream bis = new OpenSSLBIOInputStream(is, true);
+        try {
+            long keyCtx = NativeCrypto.PEM_read_bio_PrivateKey(bis.getBioContext());
+            if (keyCtx == 0L) {
+                return null;
+            }
+
+            return new OpenSSLKey(keyCtx);
+        } catch (Exception e) {
+            throw new InvalidKeyException(e);
+        } finally {
+            bis.release();
+        }
+    }
+
+    /**
      * Gets an {@code OpenSSLKey} instance backed by the provided private key. The resulting key is
      * usable only by this provider's TLS/SSL stack.
      *
@@ -228,6 +251,28 @@
         }
     }
 
+    /**
+     * Parse a public key in PEM encoding from the provided input stream.
+     *
+     * @throws InvalidKeyException if parsing fails
+     */
+    public static OpenSSLKey fromPublicKeyPemInputStream(InputStream is)
+            throws InvalidKeyException {
+        OpenSSLBIOInputStream bis = new OpenSSLBIOInputStream(is, true);
+        try {
+            long keyCtx = NativeCrypto.PEM_read_bio_PUBKEY(bis.getBioContext());
+            if (keyCtx == 0L) {
+                return null;
+            }
+
+            return new OpenSSLKey(keyCtx);
+        } catch (Exception e) {
+            throw new InvalidKeyException(e);
+        } finally {
+            bis.release();
+        }
+    }
+
     public PublicKey getPublicKey() throws NoSuchAlgorithmException {
         switch (NativeCrypto.EVP_PKEY_type(ctx)) {
             case NativeConstants.EVP_PKEY_RSA:
diff --git a/src/main/native/org_conscrypt_NativeCrypto.cpp b/src/main/native/org_conscrypt_NativeCrypto.cpp
index df78810..f7fbfe5 100644
--- a/src/main/native/org_conscrypt_NativeCrypto.cpp
+++ b/src/main/native/org_conscrypt_NativeCrypto.cpp
@@ -6419,39 +6419,49 @@
 
 
 template<typename T, T* (*PEM_read_func)(BIO*, T**, pem_password_cb*, void*)>
-static jlong PEM_ASN1Object_to_jlong(JNIEnv* env, jlong bioRef) {
+static jlong PEM_to_jlong(JNIEnv* env, jlong bioRef) {
     BIO* bio = reinterpret_cast<BIO*>(static_cast<uintptr_t>(bioRef));
-    JNI_TRACE("PEM_ASN1Object_to_jlong(%p)", bio);
+    JNI_TRACE("PEM_to_jlong(%p)", bio);
 
     if (bio == NULL) {
         jniThrowNullPointerException(env, "bio == null");
-        JNI_TRACE("PEM_ASN1Object_to_jlong(%p) => bio == null", bio);
+        JNI_TRACE("PEM_to_jlong(%p) => bio == null", bio);
         return 0;
     }
 
     T* x = PEM_read_func(bio, NULL, NULL, NULL);
     if (x == NULL) {
-        throwExceptionIfNecessary(env, "PEM_ASN1Object_to_jlong");
+        throwExceptionIfNecessary(env, "PEM_to_jlong");
         // Sometimes the PEM functions fail without pushing an error
         if (!env->ExceptionCheck()) {
             jniThrowRuntimeException(env, "Failure parsing PEM");
         }
-        JNI_TRACE("PEM_ASN1Object_to_jlong(%p) => threw exception", bio);
+        JNI_TRACE("PEM_to_jlong(%p) => threw exception", bio);
         return 0;
     }
 
-    JNI_TRACE("PEM_ASN1Object_to_jlong(%p) => %p", bio, x);
+    JNI_TRACE("PEM_to_jlong(%p) => %p", bio, x);
     return reinterpret_cast<uintptr_t>(x);
 }
 
 static jlong NativeCrypto_PEM_read_bio_X509(JNIEnv* env, jclass, jlong bioRef) {
     JNI_TRACE("PEM_read_bio_X509(0x%llx)", (long long) bioRef);
-    return PEM_ASN1Object_to_jlong<X509, PEM_read_bio_X509>(env, bioRef);
+    return PEM_to_jlong<X509, PEM_read_bio_X509>(env, bioRef);
 }
 
 static jlong NativeCrypto_PEM_read_bio_X509_CRL(JNIEnv* env, jclass, jlong bioRef) {
     JNI_TRACE("PEM_read_bio_X509_CRL(0x%llx)", (long long) bioRef);
-    return PEM_ASN1Object_to_jlong<X509_CRL, PEM_read_bio_X509_CRL>(env, bioRef);
+    return PEM_to_jlong<X509_CRL, PEM_read_bio_X509_CRL>(env, bioRef);
+}
+
+static jlong NativeCrypto_PEM_read_bio_PUBKEY(JNIEnv* env, jclass, jlong bioRef) {
+    JNI_TRACE("PEM_read_bio_PUBKEY(0x%llx)", (long long) bioRef);
+    return PEM_to_jlong<EVP_PKEY, PEM_read_bio_PUBKEY>(env, bioRef);
+}
+
+static jlong NativeCrypto_PEM_read_bio_PrivateKey(JNIEnv* env, jclass, jlong bioRef) {
+    JNI_TRACE("PEM_read_bio_PrivateKey(0x%llx)", (long long) bioRef);
+    return PEM_to_jlong<EVP_PKEY, PEM_read_bio_PrivateKey>(env, bioRef);
 }
 
 template <typename T, typename T_stack>
@@ -10641,6 +10651,8 @@
     NATIVE_METHOD(NativeCrypto, d2i_PKCS8_PRIV_KEY_INFO, "([B)J"),
     NATIVE_METHOD(NativeCrypto, i2d_PUBKEY, "(" REF_EVP_PKEY ")[B"),
     NATIVE_METHOD(NativeCrypto, d2i_PUBKEY, "([B)J"),
+    NATIVE_METHOD(NativeCrypto, PEM_read_bio_PUBKEY, "(J)J"),
+    NATIVE_METHOD(NativeCrypto, PEM_read_bio_PrivateKey, "(J)J"),
     NATIVE_METHOD(NativeCrypto, getRSAPrivateKeyWrapper, "(Ljava/security/PrivateKey;[B)J"),
     NATIVE_METHOD(NativeCrypto, getECPrivateKeyWrapper, "(Ljava/security/PrivateKey;" REF_EC_GROUP ")J"),
     NATIVE_METHOD(NativeCrypto, RSA_generate_key_ex, "(I[B)J"),
diff --git a/src/test/java/org/conscrypt/OpenSSLKeyTest.java b/src/test/java/org/conscrypt/OpenSSLKeyTest.java
new file mode 100644
index 0000000..10f65f0
--- /dev/null
+++ b/src/test/java/org/conscrypt/OpenSSLKeyTest.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.conscrypt;
+
+import junit.framework.TestCase;
+import java.io.ByteArrayInputStream;
+import java.math.BigInteger;
+
+public class OpenSSLKeyTest extends TestCase {
+    static final String RSA_PUBLIC_KEY =
+        "-----BEGIN PUBLIC KEY-----\n" +
+        "MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAOHsK2E2FLYfEMWEVH/rJMTqDZLLLysh\n" +
+        "AH5odcfhYdF9xvFFU9rqJT7zXUDH4SjdhZGUUAO5IOC1e8ZIyRsbiY0CAwEAAQ==\n" +
+        "-----END PUBLIC KEY-----";
+
+    static final String RSA_PRIVATE_KEY =
+        "-----BEGIN RSA PRIVATE KEY-----\n" +
+        "MIIBOgIBAAJBAOHsK2E2FLYfEMWEVH/rJMTqDZLLLyshAH5odcfhYdF9xvFFU9rq\n" +
+        "JT7zXUDH4SjdhZGUUAO5IOC1e8ZIyRsbiY0CAwEAAQJBALcu+oGJC0QcbknpIWbT\n" +
+        "L+4mZTkYXLeYu8DDTHT0j47+6eEyYBOoRGcZDdlMWquvFIrV48RSot0GPh1MBE1p\n" +
+        "lKECIQD4krM4UshCwUHH9ZVkoxcPsxzPTTW7ukky4RZVN6mgWQIhAOisOAXVVjon\n" +
+        "fbGNQ6CezH7oOttEeZmiWCu48AVCyixVAiAaDZ41OA//Vywi3i2jV6iyH47Ud347\n" +
+        "R+ImMAtcMTJZOQIgF0+Z1UvIdc8bErzad68xQc22h91WaYQQXWEL+xrz8nkCIDcA\n" +
+        "MpCP/H5qTCj/l5rxQg+/NUGCg2pHHNLL+cy5N5RM\n" +
+        "-----END RSA PRIVATE KEY-----";
+
+    static final BigInteger RSA_MODULUS = new BigInteger(
+        "e1ec2b613614b61f10c584547feb24c4ea0d92cb2f2b21007e6875c7e161d17d" +
+        "c6f14553daea253ef35d40c7e128dd8591945003b920e0b57bc648c91b1b898d", 16);
+
+    static final BigInteger RSA_PUBLIC_EXPONENT = new BigInteger("10001", 16);
+    static final BigInteger RSA_PRIVATE_EXPONENT = new BigInteger(
+        "b72efa81890b441c6e49e92166d32fee266539185cb798bbc0c34c74f48f8efe" +
+        "e9e1326013a84467190dd94c5aabaf148ad5e3c452a2dd063e1d4c044d6994a1", 16);
+
+    public void test_fromPublicKeyPemInputStream() throws Exception {
+        ByteArrayInputStream is = new ByteArrayInputStream(RSA_PUBLIC_KEY.getBytes());
+        OpenSSLKey key = OpenSSLKey.fromPublicKeyPemInputStream(is);
+        OpenSSLRSAPublicKey publicKey = (OpenSSLRSAPublicKey)key.getPublicKey();
+        assertEquals(RSA_MODULUS, publicKey.getModulus());
+        assertEquals(RSA_PUBLIC_EXPONENT, publicKey.getPublicExponent());
+    }
+
+    public void test_fromPrivateKeyPemInputStream() throws Exception {
+        ByteArrayInputStream is = new ByteArrayInputStream(RSA_PRIVATE_KEY.getBytes());
+        OpenSSLKey key = OpenSSLKey.fromPrivateKeyPemInputStream(is);
+        OpenSSLRSAPrivateKey privateKey = (OpenSSLRSAPrivateKey)key.getPrivateKey();
+        assertEquals(RSA_MODULUS, privateKey.getModulus());
+        assertEquals(RSA_PRIVATE_EXPONENT, privateKey.getPrivateExponent());
+    }
+}
+