1 /* 2 * Copyright (C) 2013 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package org.conscrypt; 18 19 import java.security.InvalidAlgorithmParameterException; 20 import java.security.InvalidKeyException; 21 import java.security.Key; 22 import java.security.PrivateKey; 23 import java.security.PublicKey; 24 import java.security.SecureRandom; 25 import java.security.spec.AlgorithmParameterSpec; 26 import javax.crypto.KeyAgreementSpi; 27 import javax.crypto.SecretKey; 28 import javax.crypto.ShortBufferException; 29 import javax.crypto.spec.SecretKeySpec; 30 31 /** 32 * Elliptic Curve Diffie-Hellman key agreement backed by the OpenSSL engine. 33 * 34 * @hide 35 */ 36 @Internal 37 public final class OpenSSLECDHKeyAgreement extends KeyAgreementSpi { 38 39 /** OpenSSL handle of the private key. Only available after the engine has been initialized. */ 40 private OpenSSLKey mOpenSslPrivateKey; 41 42 /** 43 * Expected length (in bytes) of the agreed key ({@link #mResult}). Only available after the 44 * engine has been initialized. 45 */ 46 private int mExpectedResultLength; 47 48 /** Agreed key. Only available after {@link #engineDoPhase(Key, boolean)} completes. */ 49 private byte[] mResult; 50 51 @Override 52 public Key engineDoPhase(Key key, boolean lastPhase) throws InvalidKeyException { 53 if (mOpenSslPrivateKey == null) { 54 throw new IllegalStateException("Not initialized"); 55 } 56 if (!lastPhase) { 57 throw new IllegalStateException("ECDH only has one phase"); 58 } 59 60 if (key == null) { 61 throw new InvalidKeyException("key == null"); 62 } 63 if (!(key instanceof PublicKey)) { 64 throw new InvalidKeyException("Not a public key: " + key.getClass()); 65 } 66 OpenSSLKey openSslPublicKey = OpenSSLKey.fromPublicKey((PublicKey) key); 67 68 byte[] buffer = new byte[mExpectedResultLength]; 69 int actualResultLength = NativeCrypto.ECDH_compute_key( 70 buffer, 71 0, 72 openSslPublicKey.getNativeRef(), 73 mOpenSslPrivateKey.getNativeRef()); 74 byte[] result; 75 if (actualResultLength == -1) { 76 throw new RuntimeException("Engine returned " + actualResultLength); 77 } else if (actualResultLength == mExpectedResultLength) { 78 // The output is as long as expected -- use the whole buffer 79 result = buffer; 80 } else if (actualResultLength < mExpectedResultLength) { 81 // The output is shorter than expected -- use only what's produced by the engine 82 result = new byte[actualResultLength]; 83 System.arraycopy(buffer, 0, mResult, 0, mResult.length); 84 } else { 85 // The output is longer than expected 86 throw new RuntimeException("Engine produced a longer than expected result. Expected: " 87 + mExpectedResultLength + ", actual: " + actualResultLength); 88 } 89 mResult = result; 90 91 return null; // No intermediate key 92 } 93 94 @Override 95 protected int engineGenerateSecret(byte[] sharedSecret, int offset) 96 throws ShortBufferException { 97 checkCompleted(); 98 int available = sharedSecret.length - offset; 99 if (mResult.length > available) { 100 throw new ShortBufferException( 101 "Needed: " + mResult.length + ", available: " + available); 102 } 103 104 System.arraycopy(mResult, 0, sharedSecret, offset, mResult.length); 105 return mResult.length; 106 } 107 108 @Override 109 protected byte[] engineGenerateSecret() { 110 checkCompleted(); 111 return mResult; 112 } 113 114 @Override 115 protected SecretKey engineGenerateSecret(String algorithm) { 116 checkCompleted(); 117 return new SecretKeySpec(engineGenerateSecret(), algorithm); 118 } 119 120 @Override 121 protected void engineInit(Key key, SecureRandom random) throws InvalidKeyException { 122 if (key == null) { 123 throw new InvalidKeyException("key == null"); 124 } 125 if (!(key instanceof PrivateKey)) { 126 throw new InvalidKeyException("Not a private key: " + key.getClass()); 127 } 128 129 OpenSSLKey openSslKey = OpenSSLKey.fromPrivateKey((PrivateKey) key); 130 int fieldSizeBits = NativeCrypto.EC_GROUP_get_degree(new NativeRef.EC_GROUP( 131 NativeCrypto.EC_KEY_get1_group(openSslKey.getNativeRef()))); 132 mExpectedResultLength = (fieldSizeBits + 7) / 8; 133 mOpenSslPrivateKey = openSslKey; 134 } 135 136 @Override 137 protected void engineInit(Key key, AlgorithmParameterSpec params, 138 SecureRandom random) throws InvalidKeyException, InvalidAlgorithmParameterException { 139 // ECDH doesn't need an AlgorithmParameterSpec 140 if (params != null) { 141 throw new InvalidAlgorithmParameterException("No algorithm parameters supported"); 142 } 143 engineInit(key, random); 144 } 145 146 private void checkCompleted() { 147 if (mResult == null) { 148 throw new IllegalStateException("Key agreement not completed"); 149 } 150 } 151 } 152