NativeCrypto: add CertPath encoding PkiPath

Set the default encoding to be PkiPath to conform to other
implementations. This now passes all the tests.

Change-Id: I8475e328e8440aa3ecccd88c34e2aba6bc169be5
diff --git a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/NativeCrypto.java b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/NativeCrypto.java
index a357319..7e669f8 100644
--- a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/NativeCrypto.java
+++ b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/NativeCrypto.java
@@ -374,6 +374,10 @@
     /** Takes an X509 context not an X509_PUBKEY context. */
     public static native byte[] i2d_X509_PUBKEY(long x509ctx);
 
+    public static native byte[] ASN1_seq_pack_X509(long[] x509CertRefs);
+
+    public static native long[] ASN1_seq_unpack_X509_bio(long bioRef);
+
     public static native void X509_free(long x509ctx);
 
     public static native int X509_cmp(long x509ctx1, long x509ctx2);
diff --git a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLX509CertPath.java b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLX509CertPath.java
index 6cd31ee..4639dfd 100644
--- a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLX509CertPath.java
+++ b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLX509CertPath.java
@@ -18,8 +18,6 @@
 
 import org.apache.harmony.xnet.provider.jsse.OpenSSLX509CertificateFactory.ParsingException;
 
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.PushbackInputStream;
@@ -28,6 +26,7 @@
 import java.security.cert.CertificateEncodingException;
 import java.security.cert.CertificateException;
 import java.security.cert.X509Certificate;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.Iterator;
@@ -43,6 +42,7 @@
      * encode this into bytes such as {@link #getEncoded()}.
      */
     private enum Encoding {
+        PKI_PATH("PkiPath"),
         PKCS7("PKCS7");
 
         private final String apiName;
@@ -65,10 +65,11 @@
     /** Unmodifiable list of encodings for the API. */
     private static final List<String> ALL_ENCODINGS = Collections.unmodifiableList(Arrays
             .asList(new String[] {
-                Encoding.PKCS7.apiName,
+                    Encoding.PKI_PATH.apiName,
+                    Encoding.PKCS7.apiName,
             }));
 
-    private static final Encoding DEFAULT_ENCODING = Encoding.PKCS7;
+    private static final Encoding DEFAULT_ENCODING = Encoding.PKI_PATH;
 
     private final List<? extends X509Certificate> mCertificates;
 
@@ -88,28 +89,29 @@
     }
 
     private byte[] getEncoded(Encoding encoding) throws CertificateEncodingException {
-        switch (encoding) {
-            case PKCS7:
-                return toPkcs7Format();
-            default:
-                throw new CertificateEncodingException("Unknown encoding");
-        }
-    }
-
-    private byte[] toPkcs7Format() throws CertificateEncodingException {
         final OpenSSLX509Certificate[] certs = new OpenSSLX509Certificate[mCertificates.size()];
         final long[] certRefs = new long[certs.length];
 
-        for (int i = 0; i < certs.length; i++) {
-            if (certs[i] instanceof OpenSSLX509Certificate) {
-                certs[i] = (OpenSSLX509Certificate) mCertificates.get(i);
+        for (int i = 0, j = certs.length - 1; j >= 0; i++, j--) {
+            final X509Certificate cert = mCertificates.get(i);
+
+            if (cert instanceof OpenSSLX509Certificate) {
+                certs[j] = (OpenSSLX509Certificate) cert;
             } else {
-                certs[i] = OpenSSLX509Certificate.fromX509Der(mCertificates.get(i).getEncoded());
+                certs[j] = OpenSSLX509Certificate.fromX509Der(cert.getEncoded());
             }
-            certRefs[i] = certs[i].getContext();
+
+            certRefs[j] = certs[j].getContext();
         }
 
-        return NativeCrypto.i2d_PKCS7(certRefs);
+        switch (encoding) {
+            case PKI_PATH:
+                return NativeCrypto.ASN1_seq_pack_X509(certRefs);
+            case PKCS7:
+                return NativeCrypto.i2d_PKCS7(certRefs);
+            default:
+                throw new CertificateEncodingException("Unknown encoding");
+        }
     }
 
     @Override
@@ -124,7 +126,7 @@
             throw new CertificateEncodingException("Invalid encoding: " + encoding);
         }
 
-        return getEncoded();
+        return getEncoded(enc);
     }
 
     @Override
@@ -132,6 +134,34 @@
         return getEncodingsIterator();
     }
 
+    private static CertPath fromPkiPathEncoding(InputStream inStream) throws CertificateException {
+        OpenSSLBIOInputStream bis = new OpenSSLBIOInputStream(inStream);
+
+        final long[] certRefs;
+        try {
+            certRefs = NativeCrypto.ASN1_seq_unpack_X509_bio(bis.getBioContext());
+        } catch (Exception e) {
+            throw new CertificateException(e);
+        } finally {
+            NativeCrypto.BIO_free(bis.getBioContext());
+        }
+
+        if (certRefs == null) {
+            return new OpenSSLX509CertPath(Collections.<X509Certificate> emptyList());
+        }
+
+        final List<OpenSSLX509Certificate> certs =
+                new ArrayList<OpenSSLX509Certificate>(certRefs.length);
+        for (int i = certRefs.length - 1; i >= 0; i--) {
+            if (certRefs[i] == 0) {
+                continue;
+            }
+            certs.add(new OpenSSLX509Certificate(certRefs[i]));
+        }
+
+        return new OpenSSLX509CertPath(certs);
+    }
+
     private static CertPath fromPkcs7Encoding(InputStream inStream) throws CertificateException {
         try {
             if (inStream == null || inStream.available() == 0) {
@@ -177,6 +207,8 @@
     private static CertPath fromEncoding(InputStream inStream, Encoding encoding)
             throws CertificateException {
         switch (encoding) {
+            case PKI_PATH:
+                return fromPkiPathEncoding(inStream);
             case PKCS7:
                 return fromPkcs7Encoding(inStream);
             default:
diff --git a/luni/src/main/native/org_apache_harmony_xnet_provider_jsse_NativeCrypto.cpp b/luni/src/main/native/org_apache_harmony_xnet_provider_jsse_NativeCrypto.cpp
index 853cf4a..8795428 100644
--- a/luni/src/main/native/org_apache_harmony_xnet_provider_jsse_NativeCrypto.cpp
+++ b/luni/src/main/native/org_apache_harmony_xnet_provider_jsse_NativeCrypto.cpp
@@ -29,6 +29,7 @@
 
 #include <jni.h>
 
+#include <openssl/asn1t.h>
 #include <openssl/dsa.h>
 #include <openssl/engine.h>
 #include <openssl/err.h>
@@ -76,6 +77,13 @@
 static jmethodID outputStream_writeMethod;
 static jmethodID outputStream_flushMethod;
 
+struct OPENSSL_Delete {
+    void operator()(void* p) const {
+        OPENSSL_free(p);
+    }
+};
+typedef UniquePtr<unsigned char, OPENSSL_Delete> Unique_OPENSSL_str;
+
 struct BIO_Delete {
     void operator()(BIO* p) const {
         BIO_free(p);
@@ -697,7 +705,7 @@
 T* ByteArrayToASN1(JNIEnv* env, jbyteArray byteArray) {
     ScopedByteArrayRO bytes(env, byteArray);
     if (bytes.get() == NULL) {
-        JNI_TRACE("ByteArrayToASN1(%p) => using byte array failed", obj);
+        JNI_TRACE("ByteArrayToASN1(%p) => using byte array failed", byteArray);
         return 0;
     }
 
@@ -4619,18 +4627,18 @@
 
 static jlongArray NativeCrypto_d2i_PKCS7_bio(JNIEnv* env, jclass, jlong bioRef, jint which) {
     BIO* bio = reinterpret_cast<BIO*>(static_cast<uintptr_t>(bioRef));
-    JNI_TRACE("d2i_PKCS7_bio_certs(%p)", bio);
+    JNI_TRACE("d2i_PKCS7_bio(%p, %d)", bio, which);
 
     if (bio == NULL) {
         jniThrowNullPointerException(env, "bio == null");
-        JNI_TRACE("d2i_PKCS7_bio_certs(%p) => bio == null", bio);
+        JNI_TRACE("d2i_PKCS7_bio(%p, %d) => bio == null", bio, which);
         return 0;
     }
 
     Unique_PKCS7 pkcs7(d2i_PKCS7_bio(bio, NULL));
     if (pkcs7.get() == NULL) {
-        throwExceptionIfNecessary(env, "d2i_PKCS7_bio_certs");
-        JNI_TRACE("d2i_PKCS7_bio_certs(%p) => threw exception", bio);
+        throwExceptionIfNecessary(env, "d2i_PKCS7_bio");
+        JNI_TRACE("d2i_PKCS7_bio(%p, %d) => threw exception", bio, which);
         return 0;
     }
 
@@ -4674,6 +4682,82 @@
     return ASN1ToByteArray<PKCS7, i2d_PKCS7>(env, pkcs7.get());
 }
 
+typedef STACK_OF(X509) PKIPATH;
+
+ASN1_ITEM_TEMPLATE(PKIPATH) =
+    ASN1_EX_TEMPLATE_TYPE(ASN1_TFLG_SEQUENCE_OF, 0, PkiPath, X509)
+ASN1_ITEM_TEMPLATE_END(PKIPATH)
+
+static jlongArray NativeCrypto_ASN1_seq_unpack_X509_bio(JNIEnv* env, jclass, jlong bioRef) {
+    BIO* bio = reinterpret_cast<BIO*>(static_cast<uintptr_t>(bioRef));
+    JNI_TRACE("ASN1_seq_unpack_X509_bio(%p)", bio);
+
+    Unique_sk_X509 path((PKIPATH*) ASN1_item_d2i_bio(ASN1_ITEM_rptr(PKIPATH), bio, NULL));
+    if (path.get() == NULL) {
+        throwExceptionIfNecessary(env, "ASN1_seq_unpack_X509_bio");
+        return NULL;
+    }
+
+    size_t size = sk_X509_num(path.get());
+
+    ScopedLocalRef<jlongArray> certArray(env, env->NewLongArray(size));
+    ScopedLongArrayRW certs(env, certArray.get());
+    for (size_t i = 0; i < size; i++) {
+        X509* item = reinterpret_cast<X509*>(sk_X509_value(path.get(), i));
+        certs[i] = reinterpret_cast<uintptr_t>(item);
+    }
+
+    JNI_TRACE("ASN1_seq_unpack_X509_bio(%p) => returns %d items", bio, size);
+    return certArray.release();
+}
+
+static jbyteArray NativeCrypto_ASN1_seq_pack_X509(JNIEnv* env, jclass, jlongArray certs) {
+    JNI_TRACE("ASN1_seq_pack_X509(%p)", certs);
+    ScopedLongArrayRO certsArray(env, certs);
+    if (certsArray.get() == NULL) {
+        JNI_TRACE("ASN1_seq_pack_X509(%p) => failed to get certs array", certs);
+        return NULL;
+    }
+
+    Unique_sk_X509 certStack(sk_X509_new_null());
+    if (certStack.get() == NULL) {
+        JNI_TRACE("ASN1_seq_pack_X509(%p) => failed to make cert stack", certs);
+        return NULL;
+    }
+
+    for (size_t i = 0; i < certsArray.size(); i++) {
+        X509* x509 = reinterpret_cast<X509*>(static_cast<uintptr_t>(certsArray[i]));
+        sk_X509_push(certStack.get(), X509_dup(x509));
+    }
+
+    int len;
+    Unique_OPENSSL_str encoded(ASN1_seq_pack(
+                    reinterpret_cast<STACK_OF(OPENSSL_BLOCK)*>(
+                            reinterpret_cast<uintptr_t>(certStack.get())),
+                    reinterpret_cast<int (*)(void*, unsigned char**)>(i2d_X509), NULL, &len));
+    if (encoded.get() == NULL) {
+        JNI_TRACE("ASN1_seq_pack_X509(%p) => trouble encoding", certs);
+        return NULL;
+    }
+
+    ScopedLocalRef<jbyteArray> byteArray(env, env->NewByteArray(len));
+    if (byteArray.get() == NULL) {
+        JNI_TRACE("ASN1_seq_pack_X509(%p) => creating byte array failed", certs);
+        return NULL;
+    }
+
+    ScopedByteArrayRW bytes(env, byteArray.get());
+    if (bytes.get() == NULL) {
+        JNI_TRACE("ASN1_seq_pack_X509(%p) => using byte array failed", certs);
+        return NULL;
+    }
+
+    unsigned char* p = reinterpret_cast<unsigned char*>(bytes.get());
+    memcpy(p, encoded.get(), len);
+
+    return byteArray.release();
+}
+
 static void NativeCrypto_X509_free(JNIEnv* env, jclass, jlong x509Ref) {
     X509* x509 = reinterpret_cast<X509*>(static_cast<uintptr_t>(x509Ref));
     JNI_TRACE("X509_free(%p)", x509);
@@ -7647,6 +7731,8 @@
     NATIVE_METHOD(NativeCrypto, PEM_read_bio_PKCS7, "(JI)[J"),
     NATIVE_METHOD(NativeCrypto, d2i_PKCS7_bio, "(JI)[J"),
     NATIVE_METHOD(NativeCrypto, i2d_PKCS7, "([J)[B"),
+    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_cmp, "(JJ)I"),
     NATIVE_METHOD(NativeCrypto, get_X509_hashCode, "(J)I"),