am b8eba478: am 25b8e1d3: Merge "Add support for ECDH KeyAgreement to OpenSSLProvider."

* commit 'b8eba478d096712b7fa81ba8c2c70f002372d5b5':
  Add support for ECDH KeyAgreement to OpenSSLProvider.
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 7e669f8..770e2a3 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
@@ -226,6 +226,9 @@
 
     public static native long EC_KEY_get_public_key(long keyRef);
 
+    public static native int ECDH_compute_key(
+            byte[] out, int outOffset, long publicKeyRef, long privateKeyRef);
+
     // --- Message digest functions --------------
 
     public static native long EVP_get_digestbyname(String name);
diff --git a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLECDHKeyAgreement.java b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLECDHKeyAgreement.java
new file mode 100644
index 0000000..096e300
--- /dev/null
+++ b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLECDHKeyAgreement.java
@@ -0,0 +1,191 @@
+/*
+ * Copyright (C) 2013 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.apache.harmony.xnet.provider.jsse;
+
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.Key;
+import java.security.SecureRandom;
+import java.security.interfaces.ECPrivateKey;
+import java.security.interfaces.ECPublicKey;
+import java.security.spec.AlgorithmParameterSpec;
+
+import javax.crypto.KeyAgreementSpi;
+import javax.crypto.SecretKey;
+import javax.crypto.ShortBufferException;
+import javax.crypto.spec.SecretKeySpec;
+
+/**
+ * Elliptic Curve Diffie-Hellman key agreement backed by the OpenSSL engine.
+ */
+public final class OpenSSLECDHKeyAgreement extends KeyAgreementSpi {
+
+    /** OpenSSL handle of the private key. Only available after the engine has been initialized. */
+    private OpenSSLKey mOpenSslPrivateKey;
+
+    /**
+     * Expected length (in bytes) of the agreed key ({@link #mResult}). Only available after the
+     * engine has been initialized.
+     */
+    private int mExpectedResultLength;
+
+    /** Agreed key. Only available after {@link #engineDoPhase(Key, boolean)} completes. */
+    private byte[] mResult;
+
+    @Override
+    public Key engineDoPhase(Key key, boolean lastPhase) throws InvalidKeyException {
+        if (mOpenSslPrivateKey == null) {
+            throw new IllegalStateException("Not initialized");
+        }
+        if (!lastPhase) {
+            throw new IllegalStateException("ECDH only has one phase");
+        }
+        if (key == null) {
+            throw new InvalidKeyException("key == null");
+        }
+        if (!(key instanceof ECPublicKey)) {
+            throw new InvalidKeyException("This phase requires an ECPublicKey. Actual key type: "
+                + key.getClass());
+        }
+        ECPublicKey publicKey = (ECPublicKey) key;
+
+        OpenSSLKey openSslPublicKey;
+        if (publicKey instanceof OpenSSLECPublicKey) {
+            // OpenSSL-backed key
+            openSslPublicKey = ((OpenSSLECPublicKey) publicKey).getOpenSSLKey();
+        } else {
+            // Not an OpenSSL-backed key -- create an OpenSSL-backed key from its X.509 encoding
+            if (!"X.509".equals(publicKey.getFormat())) {
+                throw new InvalidKeyException("Non-OpenSSL public key (" + publicKey.getClass()
+                    + ") offers unsupported encoding format: " + publicKey.getFormat());
+            }
+            byte[] encoded = publicKey.getEncoded();
+            if (encoded == null) {
+                throw new InvalidKeyException("Non-OpenSSL public key (" + publicKey.getClass()
+                    + ") does not provide encoded form");
+            }
+            try {
+                openSslPublicKey = new OpenSSLKey(NativeCrypto.d2i_PUBKEY(encoded));
+            } catch (Exception e) {
+                throw new InvalidKeyException("Failed to decode X.509 encoded public key", e);
+            }
+        }
+
+        byte[] buffer = new byte[mExpectedResultLength];
+        int actualResultLength = NativeCrypto.ECDH_compute_key(
+                buffer,
+                0,
+                openSslPublicKey.getPkeyContext(),
+                mOpenSslPrivateKey.getPkeyContext());
+        byte[] result;
+        if (actualResultLength == -1) {
+            throw new RuntimeException("Engine returned " + actualResultLength);
+        } else if (actualResultLength == mExpectedResultLength) {
+            // The output is as long as expected -- use the whole buffer
+            result = buffer;
+        } else if (actualResultLength < mExpectedResultLength) {
+            // The output is shorter than expected -- use only what's produced by the engine
+            result = new byte[actualResultLength];
+            System.arraycopy(buffer, 0, mResult, 0, mResult.length);
+        } else {
+            // The output is longer than expected
+            throw new RuntimeException("Engine produced a longer than expected result. Expected: "
+                + mExpectedResultLength + ", actual: " + actualResultLength);
+        }
+        mResult = result;
+
+        return null; // No intermediate key
+    }
+
+    @Override
+    protected int engineGenerateSecret(byte[] sharedSecret, int offset)
+            throws ShortBufferException {
+        checkCompleted();
+        int available = sharedSecret.length - offset;
+        if (mResult.length > available) {
+            throw new ShortBufferException(
+                    "Needed: " + mResult.length + ", available: " + available);
+        }
+
+        System.arraycopy(mResult, 0, sharedSecret, offset, mResult.length);
+        return mResult.length;
+    }
+
+    @Override
+    protected byte[] engineGenerateSecret() {
+        checkCompleted();
+        return mResult;
+    }
+
+    @Override
+    protected SecretKey engineGenerateSecret(String algorithm) {
+        checkCompleted();
+        return new SecretKeySpec(engineGenerateSecret(), algorithm);
+    }
+
+    @Override
+    protected void engineInit(Key key, SecureRandom random) throws InvalidKeyException {
+        if (key == null) {
+            throw new InvalidKeyException("key == null");
+        }
+        if (!(key instanceof ECPrivateKey)) {
+            throw new InvalidKeyException("Not an EC private key: " + key.getClass());
+        }
+        ECPrivateKey privateKey = (ECPrivateKey) key;
+        mExpectedResultLength =
+                (privateKey.getParams().getCurve().getField().getFieldSize() + 7) / 8;
+
+        OpenSSLKey openSslPrivateKey;
+        if (privateKey instanceof OpenSSLECPrivateKey) {
+            // OpenSSL-backed key
+            openSslPrivateKey = ((OpenSSLECPrivateKey) privateKey).getOpenSSLKey();
+        } else {
+            // Not an OpenSSL-backed key -- create an OpenSSL-backed key from its PKCS#8 encoding
+            if (!"PKCS#8".equals(privateKey.getFormat())) {
+                throw new InvalidKeyException("Non-OpenSSL private key (" + privateKey.getClass()
+                    + ") offers unsupported encoding format: " + privateKey.getFormat());
+            }
+            byte[] encoded = privateKey.getEncoded();
+            if (encoded == null) {
+                throw new InvalidKeyException("Non-OpenSSL private key (" + privateKey.getClass()
+                    + ") does not provide encoded form");
+            }
+            try {
+                openSslPrivateKey = new OpenSSLKey(NativeCrypto.d2i_PKCS8_PRIV_KEY_INFO(encoded));
+            } catch (Exception e) {
+                throw new InvalidKeyException("Failed to decode PKCS#8 encoded private key", e);
+            }
+        }
+        mOpenSslPrivateKey = openSslPrivateKey;
+    }
+
+    @Override
+    protected void engineInit(Key key, AlgorithmParameterSpec params,
+            SecureRandom random) throws InvalidKeyException, InvalidAlgorithmParameterException {
+        // ECDH doesn't need an AlgorithmParameterSpec
+        if (params != null) {
+          throw new InvalidAlgorithmParameterException("No algorithm parameters supported");
+        }
+        engineInit(key, random);
+    }
+
+    private void checkCompleted() {
+        if (mResult == null) {
+            throw new IllegalStateException("Key agreement not completed");
+        }
+    }
+}
diff --git a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLProvider.java b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLProvider.java
index 7ac4022..59e20d6 100644
--- a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLProvider.java
+++ b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLProvider.java
@@ -86,6 +86,9 @@
 
         put("KeyFactory.EC", OpenSSLECKeyFactory.class.getName());
 
+        /* == KeyAgreement == */
+        put("KeyAgreement.ECDH", OpenSSLECDHKeyAgreement.class.getName());
+
         /* == Signatures == */
         put("Signature.MD5WithRSA", OpenSSLSignature.MD5RSA.class.getName());
         put("Alg.Alias.Signature.MD5WithRSAEncryption", "MD5WithRSA");
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 ba68bd0..a8740ba 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
@@ -2672,6 +2672,60 @@
     return reinterpret_cast<uintptr_t>(dup.release());
 }
 
+static jint NativeCrypto_ECDH_compute_key(JNIEnv* env, jclass,
+     jbyteArray outArray, jint outOffset, jlong pubkeyRef, jlong privkeyRef)
+{
+    EVP_PKEY* pubPkey = reinterpret_cast<EVP_PKEY*>(pubkeyRef);
+    EVP_PKEY* privPkey = reinterpret_cast<EVP_PKEY*>(privkeyRef);
+    JNI_TRACE("ECDH_compute_key(%p, %d, %p, %p)", outArray, outOffset, pubPkey, privPkey);
+
+    ScopedByteArrayRW out(env, outArray);
+    if (out.get() == NULL) {
+        JNI_TRACE("ECDH_compute_key(%p, %d, %p, %p) can't get output buffer",
+                outArray, outOffset, pubPkey, privPkey);
+        return -1;
+    }
+
+    if ((outOffset < 0) || ((size_t) outOffset >= out.size())) {
+        jniThrowException(env, "java/lang/ArrayIndexOutOfBoundsException", NULL);
+        return -1;
+    }
+
+    Unique_EC_KEY pubkey(EVP_PKEY_get1_EC_KEY(pubPkey));
+    if (pubkey.get() == NULL) {
+        JNI_TRACE("ECDH_compute_key(%p) => can't get public key", pubPkey);
+        throwExceptionIfNecessary(env, "EVP_PKEY_get1_EC_KEY public");
+        return -1;
+    }
+
+    const EC_POINT* pubkeyPoint = EC_KEY_get0_public_key(pubkey.get());
+    if (pubkeyPoint == NULL) {
+        JNI_TRACE("ECDH_compute_key(%p) => can't get public key point", pubPkey);
+        throwExceptionIfNecessary(env, "EVP_PKEY_get1_EC_KEY public");
+        return -1;
+    }
+
+    Unique_EC_KEY privkey(EVP_PKEY_get1_EC_KEY(privPkey));
+    if (privkey.get() == NULL) {
+        throwExceptionIfNecessary(env, "EVP_PKEY_get1_EC_KEY private");
+        return -1;
+    }
+
+    int outputLength = ECDH_compute_key(
+            &out[outOffset],
+            out.size() - outOffset,
+            pubkeyPoint,
+            privkey.get(),
+            NULL // No KDF
+            );
+    if (outputLength == -1) {
+        throwExceptionIfNecessary(env, "ECDH_compute_key");
+        return -1;
+    }
+
+    return outputLength;
+}
+
 static jlong NativeCrypto_EVP_MD_CTX_create(JNIEnv* env, jclass) {
     JNI_TRACE("EVP_MD_CTX_create()");
 
@@ -7681,6 +7735,7 @@
     NATIVE_METHOD(NativeCrypto, EC_KEY_get0_group, "(J)J"),
     NATIVE_METHOD(NativeCrypto, EC_KEY_get_private_key, "(J)[B"),
     NATIVE_METHOD(NativeCrypto, EC_KEY_get_public_key, "(J)J"),
+    NATIVE_METHOD(NativeCrypto, ECDH_compute_key, "([BIJJ)I"),
     NATIVE_METHOD(NativeCrypto, EVP_MD_CTX_create, "()J"),
     NATIVE_METHOD(NativeCrypto, EVP_MD_CTX_init, "(J)V"),
     NATIVE_METHOD(NativeCrypto, EVP_MD_CTX_destroy, "(J)V"),