Add method to delete extension from a certificate

The OpenSSLX509Certificate is still immutable. Instead a modified copy is returned.
The use case for this is recreating the TBS component of a Precertificate as
described by RFC6962 section 3.2.

Change-Id: I2a9305ae7464642910decaf5ab46121a6f15d722
diff --git a/Android.mk b/Android.mk
index 4b9031e..4223eef 100644
--- a/Android.mk
+++ b/Android.mk
@@ -96,6 +96,7 @@
 # Make the conscrypt-tests library.
 include $(CLEAR_VARS)
 LOCAL_SRC_FILES := $(call all-java-files-under,src/test/java)
+LOCAL_JAVA_RESOURCE_DIRS := src/test/resources
 LOCAL_NO_STANDARD_LIBRARIES := true
 LOCAL_JAVA_LIBRARIES := core-libart core-junit bouncycastle
 LOCAL_STATIC_JAVA_LIBRARIES := core-tests-support conscrypt-nojarjar
@@ -231,6 +232,7 @@
 ifeq ($(LIBCORE_SKIP_TESTS),)
     include $(CLEAR_VARS)
     LOCAL_SRC_FILES := $(call all-java-files-under,src/test/java)
+    LOCAL_JAVA_RESOURCE_DIRS := src/test/resources
     LOCAL_JAVA_LIBRARIES := bouncycastle-hostdex core-junit-hostdex core-tests-support-hostdex conscrypt-hostdex-nojarjar
     LOCAL_JAVACFLAGS := $(local_javac_flags)
     LOCAL_MODULE_TAGS := optional
diff --git a/src/main/java/org/conscrypt/NativeCrypto.java b/src/main/java/org/conscrypt/NativeCrypto.java
index 073e051..2ac2834 100644
--- a/src/main/java/org/conscrypt/NativeCrypto.java
+++ b/src/main/java/org/conscrypt/NativeCrypto.java
@@ -426,6 +426,8 @@
 
     public static native void X509_free(long x509ctx);
 
+    public static native long X509_dup(long x509ctx);
+
     public static native int X509_cmp(long x509ctx1, long x509ctx2);
 
     public static native int get_X509_hashCode(long x509ctx);
@@ -528,6 +530,8 @@
 
     public static native byte[] X509_CRL_get_ext_oid(long x509CrlCtx, String oid);
 
+    public static native void X509_delete_ext(long x509, String oid);
+
     public static native long X509_CRL_get_version(long x509CrlCtx);
 
     public static native long X509_CRL_get_ext(long x509CrlCtx, String oid);
diff --git a/src/main/java/org/conscrypt/OpenSSLX509Certificate.java b/src/main/java/org/conscrypt/OpenSSLX509Certificate.java
index ff7aa84..55c282a 100644
--- a/src/main/java/org/conscrypt/OpenSSLX509Certificate.java
+++ b/src/main/java/org/conscrypt/OpenSSLX509Certificate.java
@@ -522,6 +522,19 @@
         return mContext;
     }
 
+    /**
+     * Delete an extension.
+     *
+     * A modified copy of the certificate is returned. The original object
+     * is unchanged.
+     * If the extension is not present, an unmodified copy is returned.
+     */
+    public OpenSSLX509Certificate withDeletedExtension(String oid) {
+        OpenSSLX509Certificate copy = new OpenSSLX509Certificate(NativeCrypto.X509_dup(mContext));
+        NativeCrypto.X509_delete_ext(copy.getContext(), oid);
+        return copy;
+    }
+
     @Override
     protected void finalize() throws Throwable {
         try {
diff --git a/src/main/native/org_conscrypt_NativeCrypto.cpp b/src/main/native/org_conscrypt_NativeCrypto.cpp
index c8c4fcc..06b62d0 100644
--- a/src/main/native/org_conscrypt_NativeCrypto.cpp
+++ b/src/main/native/org_conscrypt_NativeCrypto.cpp
@@ -6860,6 +6860,19 @@
     X509_free(x509);
 }
 
+static jlong NativeCrypto_X509_dup(JNIEnv* env, jclass, jlong x509Ref) {
+    X509* x509 = reinterpret_cast<X509*>(static_cast<uintptr_t>(x509Ref));
+    JNI_TRACE("X509_dup(%p)", x509);
+
+    if (x509 == NULL) {
+        jniThrowNullPointerException(env, "x509 == null");
+        JNI_TRACE("X509_dup(%p) => x509 == null", x509);
+        return 0;
+    }
+
+    return reinterpret_cast<uintptr_t>(X509_dup(x509));
+}
+
 static jint NativeCrypto_X509_cmp(JNIEnv* env, jclass, jlong x509Ref1, jlong x509Ref2) {
     X509* x509_1 = reinterpret_cast<X509*>(static_cast<uintptr_t>(x509Ref1));
     X509* x509_2 = reinterpret_cast<X509*>(static_cast<uintptr_t>(x509Ref2));
@@ -6882,6 +6895,47 @@
     return ret;
 }
 
+static void NativeCrypto_X509_delete_ext(JNIEnv* env, jclass, jlong x509Ref,
+        jstring oidString) {
+    X509* x509 = reinterpret_cast<X509*>(static_cast<uintptr_t>(x509Ref));
+    JNI_TRACE("X509_delete_ext(%p, %p)", x509, oidString);
+
+    if (x509 == NULL) {
+        jniThrowNullPointerException(env, "x509 == null");
+        JNI_TRACE("X509_delete_ext(%p, %p) => x509 == null", x509, oidString);
+        return;
+    }
+
+    ScopedUtfChars oid(env, oidString);
+    if (oid.c_str() == NULL) {
+        JNI_TRACE("X509_delete_ext(%p, %p) => oidString == null", x509, oidString);
+        return;
+    }
+
+    Unique_ASN1_OBJECT obj(OBJ_txt2obj(oid.c_str(), 1 /* allow numerical form only */));
+    if (obj.get() == NULL) {
+        JNI_TRACE("X509_delete_ext(%p, %s) => oid conversion failed", x509, oid.c_str());
+        freeOpenSslErrorState();
+        jniThrowException(env, "java/lang/IllegalArgumentException",
+                               "Invalid OID.");
+        return;
+    }
+
+    int extIndex = X509_get_ext_by_OBJ(x509, obj.get(), -1);
+    if (extIndex == -1) {
+        JNI_TRACE("X509_delete_ext(%p, %s) => ext not found", x509, oid.c_str());
+        return;
+    }
+
+    X509_EXTENSION* ext = X509_delete_ext(x509, extIndex);
+    if (ext != NULL) {
+        X509_EXTENSION_free(ext);
+
+        // Invalidate the cached encoding
+        X509_CINF_set_modified(X509_get_cert_info(x509));
+    }
+}
+
 static jint NativeCrypto_get_X509_hashCode(JNIEnv* env, jclass, jlong x509Ref) {
     X509* x509 = reinterpret_cast<X509*>(static_cast<uintptr_t>(x509Ref));
 
@@ -10756,6 +10810,7 @@
     NATIVE_METHOD(NativeCrypto, ASN1_seq_unpack_X509_bio, "(J)[J"),
     NATIVE_METHOD(NativeCrypto, ASN1_seq_pack_X509, "([J)[B"),
     NATIVE_METHOD(NativeCrypto, X509_free, "(J)V"),
+    NATIVE_METHOD(NativeCrypto, X509_dup, "(J)J"),
     NATIVE_METHOD(NativeCrypto, X509_cmp, "(JJ)I"),
     NATIVE_METHOD(NativeCrypto, get_X509_hashCode, "(J)I"),
     NATIVE_METHOD(NativeCrypto, X509_print_ex, "(JJJJ)V"),
@@ -10772,6 +10827,7 @@
     NATIVE_METHOD(NativeCrypto, get_X509_ex_pathlen, "(J)I"),
     NATIVE_METHOD(NativeCrypto, X509_get_ext_oid, "(JLjava/lang/String;)[B"),
     NATIVE_METHOD(NativeCrypto, X509_CRL_get_ext_oid, "(JLjava/lang/String;)[B"),
+    NATIVE_METHOD(NativeCrypto, X509_delete_ext, "(JLjava/lang/String;)V"),
     NATIVE_METHOD(NativeCrypto, get_X509_CRL_crl_enc, "(J)[B"),
     NATIVE_METHOD(NativeCrypto, X509_CRL_verify, "(J" REF_EVP_PKEY ")V"),
     NATIVE_METHOD(NativeCrypto, X509_CRL_get_lastUpdate, "(J)J"),
diff --git a/src/test/java/org/conscrypt/OpenSSLX509CertificateTest.java b/src/test/java/org/conscrypt/OpenSSLX509CertificateTest.java
new file mode 100644
index 0000000..228e472
--- /dev/null
+++ b/src/test/java/org/conscrypt/OpenSSLX509CertificateTest.java
@@ -0,0 +1,74 @@
+/*
+ * 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.InputStream;
+import java.util.Arrays;
+
+import org.conscrypt.OpenSSLX509CertificateFactory.ParsingException;
+
+public class OpenSSLX509CertificateTest extends TestCase {
+    static final String CT_POISON_EXTENSION = "1.3.6.1.4.1.11129.2.4.3";
+
+    private OpenSSLX509Certificate loadTestCertificate(String name) throws ParsingException {
+        InputStream is = getClass().getResourceAsStream("/" + name);
+        return OpenSSLX509Certificate.fromX509PemInputStream(is);
+    }
+
+    public void test_deletingCTPoisonExtension() throws Exception {
+        /* certPoisoned has an extra poison extension.
+         * With the extension, the certificates have different TBS.
+         * Without it, the certificates should have the same TBS.
+         */
+        OpenSSLX509Certificate cert = loadTestCertificate("cert.pem");
+        OpenSSLX509Certificate certPoisoned = loadTestCertificate("cert-ct-poisoned.pem");
+
+        assertFalse(Arrays.equals(
+                certPoisoned.getTBSCertificate(),
+                cert.getTBSCertificate()));
+
+        assertTrue(Arrays.equals(
+                certPoisoned.withDeletedExtension(CT_POISON_EXTENSION).getTBSCertificate(),
+                cert.getTBSCertificate()));
+    }
+
+    public void test_deletingExtensionMakesCopy() throws Exception {
+        /* Calling withDeletedExtension should not modify the original certificate, only make a copy.
+         * Make sure the extension is still present in the original object.
+         */
+        OpenSSLX509Certificate certPoisoned = loadTestCertificate("cert-ct-poisoned.pem");
+        assertTrue(certPoisoned.getCriticalExtensionOIDs().contains(CT_POISON_EXTENSION));
+
+        OpenSSLX509Certificate certWithoutExtension = certPoisoned.withDeletedExtension(CT_POISON_EXTENSION);
+
+        assertTrue(certPoisoned.getCriticalExtensionOIDs().contains(CT_POISON_EXTENSION));
+        assertFalse(certWithoutExtension.getCriticalExtensionOIDs().contains(CT_POISON_EXTENSION));
+    }
+
+    public void test_deletingMissingExtension() throws Exception {
+        /* withDeletedExtension should be safe to call on a certificate without the extension, and
+         * return an identical copy.
+         */
+        OpenSSLX509Certificate cert = loadTestCertificate("cert.pem");
+        assertFalse(cert.getCriticalExtensionOIDs().contains(CT_POISON_EXTENSION));
+
+        OpenSSLX509Certificate cert2 = cert.withDeletedExtension(CT_POISON_EXTENSION);
+        assertEquals(cert, cert2);
+    }
+}
+
diff --git a/src/test/resources/cert-ct-poisoned.pem b/src/test/resources/cert-ct-poisoned.pem
new file mode 100644
index 0000000..4475dd5
--- /dev/null
+++ b/src/test/resources/cert-ct-poisoned.pem
@@ -0,0 +1,63 @@
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number: 7 (0x7)
+    Signature Algorithm: sha1WithRSAEncryption
+        Issuer: C=GB, O=Certificate Transparency CA, ST=Wales, L=Erw Wen
+        Validity
+            Not Before: Jun  1 00:00:00 2012 GMT
+            Not After : Jun  1 00:00:00 2022 GMT
+        Subject: C=GB, O=Certificate Transparency, ST=Wales, L=Erw Wen
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+                Public-Key: (1024 bit)
+                Modulus:
+                    00:be:ef:98:e7:c2:68:77:ae:38:5f:75:32:5a:0c:
+                    1d:32:9b:ed:f1:8f:aa:f4:d7:96:bf:04:7e:b7:e1:
+                    ce:15:c9:5b:a2:f8:0e:e4:58:bd:7d:b8:6f:8a:4b:
+                    25:21:91:a7:9b:d7:00:c3:8e:9c:03:89:b4:5c:d4:
+                    dc:9a:12:0a:b2:1e:0c:b4:1c:d0:e7:28:05:a4:10:
+                    cd:9c:5b:db:5d:49:27:72:6d:af:17:10:f6:01:87:
+                    37:7e:a2:5b:1a:1e:39:ee:d0:b8:81:19:dc:15:4d:
+                    c6:8f:7d:a8:e3:0c:af:15:8a:33:e6:c9:50:9f:4a:
+                    05:b0:14:09:ff:5d:d8:7e:b5
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            X509v3 Subject Key Identifier: 
+                20:31:54:1A:F2:5C:05:FF:D8:65:8B:68:43:79:4F:5E:90:36:F7:B4
+            X509v3 Authority Key Identifier: 
+                keyid:5F:9D:88:0D:C8:73:E6:54:D4:F8:0D:D8:E6:B0:C1:24:B4:47:C3:55
+                DirName:/C=GB/O=Certificate Transparency CA/ST=Wales/L=Erw Wen
+                serial:00
+
+            X509v3 Basic Constraints: 
+                CA:FALSE
+            1.3.6.1.4.1.11129.2.4.3: critical
+                ..
+    Signature Algorithm: sha1WithRSAEncryption
+         02:a1:c3:9e:01:5a:f5:4d:ff:02:3c:33:60:87:5f:ff:34:37:
+         55:2f:1f:09:01:bd:c2:54:31:5f:33:72:b7:23:fb:15:fb:ce:
+         cc:4d:f4:71:a0:ce:4d:8c:54:65:5d:84:87:97:fb:28:1e:3d:
+         fa:bb:46:2d:2c:68:4b:05:6f:ea:7b:63:b4:70:ff:16:6e:32:
+         d4:46:06:35:b3:d2:bc:6d:a8:24:9b:26:30:e7:1f:c3:4f:08:
+         f2:3d:d4:ee:22:8f:8f:74:f6:3d:78:63:11:dd:0a:58:11:40:
+         5f:90:6c:ca:2c:2d:3e:eb:fc:81:99:64:eb:d8:cf:7c:08:86:
+         3f:be
+-----BEGIN CERTIFICATE-----
+MIIC3zCCAkigAwIBAgIBBzANBgkqhkiG9w0BAQUFADBVMQswCQYDVQQGEwJHQjEk
+MCIGA1UEChMbQ2VydGlmaWNhdGUgVHJhbnNwYXJlbmN5IENBMQ4wDAYDVQQIEwVX
+YWxlczEQMA4GA1UEBxMHRXJ3IFdlbjAeFw0xMjA2MDEwMDAwMDBaFw0yMjA2MDEw
+MDAwMDBaMFIxCzAJBgNVBAYTAkdCMSEwHwYDVQQKExhDZXJ0aWZpY2F0ZSBUcmFu
+c3BhcmVuY3kxDjAMBgNVBAgTBVdhbGVzMRAwDgYDVQQHEwdFcncgV2VuMIGfMA0G
+CSqGSIb3DQEBAQUAA4GNADCBiQKBgQC+75jnwmh3rjhfdTJaDB0ym+3xj6r015a/
+BH634c4VyVui+A7kWL19uG+KSyUhkaeb1wDDjpwDibRc1NyaEgqyHgy0HNDnKAWk
+EM2cW9tdSSdyba8XEPYBhzd+olsaHjnu0LiBGdwVTcaPfajjDK8VijPmyVCfSgWw
+FAn/Xdh+tQIDAQABo4HBMIG+MB0GA1UdDgQWBBQgMVQa8lwF/9hli2hDeU9ekDb3
+tDB9BgNVHSMEdjB0gBRfnYgNyHPmVNT4DdjmsMEktEfDVaFZpFcwVTELMAkGA1UE
+BhMCR0IxJDAiBgNVBAoTG0NlcnRpZmljYXRlIFRyYW5zcGFyZW5jeSBDQTEOMAwG
+A1UECBMFV2FsZXMxEDAOBgNVBAcTB0VydyBXZW6CAQAwCQYDVR0TBAIwADATBgor
+BgEEAdZ5AgQDAQH/BAIFADANBgkqhkiG9w0BAQUFAAOBgQACocOeAVr1Tf8CPDNg
+h1//NDdVLx8JAb3CVDFfM3K3I/sV+87MTfRxoM5NjFRlXYSHl/soHj36u0YtLGhL
+BW/qe2O0cP8WbjLURgY1s9K8bagkmyYw5x/DTwjyPdTuIo+PdPY9eGMR3QpYEUBf
+kGzKLC0+6/yBmWTr2M98CIY/vg==
+-----END CERTIFICATE-----
diff --git a/src/test/resources/cert.pem b/src/test/resources/cert.pem
new file mode 100644
index 0000000..8864f1a
--- /dev/null
+++ b/src/test/resources/cert.pem
@@ -0,0 +1,60 @@
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number: 7 (0x7)
+    Signature Algorithm: sha1WithRSAEncryption
+        Issuer: C=GB, O=Certificate Transparency CA, ST=Wales, L=Erw Wen
+        Validity
+            Not Before: Jun  1 00:00:00 2012 GMT
+            Not After : Jun  1 00:00:00 2022 GMT
+        Subject: C=GB, O=Certificate Transparency, ST=Wales, L=Erw Wen
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+                Public-Key: (1024 bit)
+                Modulus:
+                    00:be:ef:98:e7:c2:68:77:ae:38:5f:75:32:5a:0c:
+                    1d:32:9b:ed:f1:8f:aa:f4:d7:96:bf:04:7e:b7:e1:
+                    ce:15:c9:5b:a2:f8:0e:e4:58:bd:7d:b8:6f:8a:4b:
+                    25:21:91:a7:9b:d7:00:c3:8e:9c:03:89:b4:5c:d4:
+                    dc:9a:12:0a:b2:1e:0c:b4:1c:d0:e7:28:05:a4:10:
+                    cd:9c:5b:db:5d:49:27:72:6d:af:17:10:f6:01:87:
+                    37:7e:a2:5b:1a:1e:39:ee:d0:b8:81:19:dc:15:4d:
+                    c6:8f:7d:a8:e3:0c:af:15:8a:33:e6:c9:50:9f:4a:
+                    05:b0:14:09:ff:5d:d8:7e:b5
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            X509v3 Subject Key Identifier: 
+                20:31:54:1A:F2:5C:05:FF:D8:65:8B:68:43:79:4F:5E:90:36:F7:B4
+            X509v3 Authority Key Identifier: 
+                keyid:5F:9D:88:0D:C8:73:E6:54:D4:F8:0D:D8:E6:B0:C1:24:B4:47:C3:55
+                DirName:/C=GB/O=Certificate Transparency CA/ST=Wales/L=Erw Wen
+                serial:00
+
+            X509v3 Basic Constraints: 
+                CA:FALSE
+    Signature Algorithm: sha1WithRSAEncryption
+         04:59:00:c8:0d:db:36:35:30:ed:e3:24:8e:e9:f0:ed:85:45:
+         e8:05:ca:7e:2c:cd:62:a6:4e:9d:28:a8:02:f7:82:79:93:59:
+         6d:6b:ff:f5:b2:5e:3f:3c:48:04:2a:7b:a4:e4:03:c7:f3:bc:
+         e0:cc:6d:22:b7:c1:23:ba:b4:20:d9:23:65:1e:58:46:67:ce:
+         80:99:94:82:c0:7b:e6:93:cc:fd:83:b1:d2:54:82:ad:ac:a3:
+         9d:32:1a:c7:13:79:d6:eb:8f:4f:59:47:51:71:b1:a3:09:e6:
+         82:f4:c4:f1:5a:ff:5c:18:2e:cd:a8:32:91:35:c1:96:5a:23:
+         77:0e
+-----BEGIN CERTIFICATE-----
+MIICyjCCAjOgAwIBAgIBBzANBgkqhkiG9w0BAQUFADBVMQswCQYDVQQGEwJHQjEk
+MCIGA1UEChMbQ2VydGlmaWNhdGUgVHJhbnNwYXJlbmN5IENBMQ4wDAYDVQQIEwVX
+YWxlczEQMA4GA1UEBxMHRXJ3IFdlbjAeFw0xMjA2MDEwMDAwMDBaFw0yMjA2MDEw
+MDAwMDBaMFIxCzAJBgNVBAYTAkdCMSEwHwYDVQQKExhDZXJ0aWZpY2F0ZSBUcmFu
+c3BhcmVuY3kxDjAMBgNVBAgTBVdhbGVzMRAwDgYDVQQHEwdFcncgV2VuMIGfMA0G
+CSqGSIb3DQEBAQUAA4GNADCBiQKBgQC+75jnwmh3rjhfdTJaDB0ym+3xj6r015a/
+BH634c4VyVui+A7kWL19uG+KSyUhkaeb1wDDjpwDibRc1NyaEgqyHgy0HNDnKAWk
+EM2cW9tdSSdyba8XEPYBhzd+olsaHjnu0LiBGdwVTcaPfajjDK8VijPmyVCfSgWw
+FAn/Xdh+tQIDAQABo4GsMIGpMB0GA1UdDgQWBBQgMVQa8lwF/9hli2hDeU9ekDb3
+tDB9BgNVHSMEdjB0gBRfnYgNyHPmVNT4DdjmsMEktEfDVaFZpFcwVTELMAkGA1UE
+BhMCR0IxJDAiBgNVBAoTG0NlcnRpZmljYXRlIFRyYW5zcGFyZW5jeSBDQTEOMAwG
+A1UECBMFV2FsZXMxEDAOBgNVBAcTB0VydyBXZW6CAQAwCQYDVR0TBAIwADANBgkq
+hkiG9w0BAQUFAAOBgQAEWQDIDds2NTDt4ySO6fDthUXoBcp+LM1ipk6dKKgC94J5
+k1lta//1sl4/PEgEKnuk5APH87zgzG0it8EjurQg2SNlHlhGZ86AmZSCwHvmk8z9
+g7HSVIKtrKOdMhrHE3nW649PWUdRcbGjCeaC9MTxWv9cGC7NqDKRNcGWWiN3Dg==
+-----END CERTIFICATE-----