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 7e669f8b78ba7d97faca1cf6a45772f6b547fb8f..770e2a3be9d172f848b0f3ed48bf841e93695040 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 final class NativeCrypto { 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 0000000000000000000000000000000000000000..096e300b693c651d45a41d717b64d1f6fe46d562 --- /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 7ac4022808df83b348c6b3cbe4d66637ff71fb4e..59e20d6d0ddfdb4c3831fd00245b19b1f59a806e 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 @@ public final class OpenSSLProvider extends Provider { 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");