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"),