blob: 2e0e4b0c9bcaa29fca91666cd91f8a9f8bd6845b [file] [log] [blame]
/*
* 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.conscrypt;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
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 PublicKey)) {
throw new InvalidKeyException("Not a public key: " + key.getClass());
}
OpenSSLKey openSslPublicKey = OpenSSLKey.fromPublicKey((PublicKey) key);
byte[] buffer = new byte[mExpectedResultLength];
int actualResultLength = NativeCrypto.ECDH_compute_key(
buffer,
0,
openSslPublicKey.getNativeRef(),
mOpenSslPrivateKey.getNativeRef());
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 PrivateKey)) {
throw new InvalidKeyException("Not a private key: " + key.getClass());
}
OpenSSLKey openSslKey = OpenSSLKey.fromPrivateKey((PrivateKey) key);
int fieldSizeBits = NativeCrypto.EC_GROUP_get_degree(new NativeRef.EC_GROUP(
NativeCrypto.EC_KEY_get1_group(openSslKey.getNativeRef())));
mExpectedResultLength = (fieldSizeBits + 7) / 8;
mOpenSslPrivateKey = openSslKey;
}
@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");
}
}
}