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 public final class OpenSSLECDHKeyAgreement extends KeyAgreementSpi { 35 36 /** OpenSSL handle of the private key. Only available after the engine has been initialized. */ 37 private OpenSSLKey mOpenSslPrivateKey; 38 39 /** 40 * Expected length (in bytes) of the agreed key ({@link #mResult}). Only available after the 41 * engine has been initialized. 42 */ 43 private int mExpectedResultLength; 44 45 /** Agreed key. Only available after {@link #engineDoPhase(Key, boolean)} completes. */ 46 private byte[] mResult; 47 48 @Override engineDoPhase(Key key, boolean lastPhase)49 public Key engineDoPhase(Key key, boolean lastPhase) throws InvalidKeyException { 50 if (mOpenSslPrivateKey == null) { 51 throw new IllegalStateException("Not initialized"); 52 } 53 if (!lastPhase) { 54 throw new IllegalStateException("ECDH only has one phase"); 55 } 56 57 if (key == null) { 58 throw new InvalidKeyException("key == null"); 59 } 60 if (!(key instanceof PublicKey)) { 61 throw new InvalidKeyException("Not a public key: " + key.getClass()); 62 } 63 OpenSSLKey openSslPublicKey = OpenSSLKey.fromPublicKey((PublicKey) key); 64 65 byte[] buffer = new byte[mExpectedResultLength]; 66 int actualResultLength = NativeCrypto.ECDH_compute_key( 67 buffer, 68 0, 69 openSslPublicKey.getNativeRef(), 70 mOpenSslPrivateKey.getNativeRef()); 71 byte[] result; 72 if (actualResultLength == -1) { 73 throw new RuntimeException("Engine returned " + actualResultLength); 74 } else if (actualResultLength == mExpectedResultLength) { 75 // The output is as long as expected -- use the whole buffer 76 result = buffer; 77 } else if (actualResultLength < mExpectedResultLength) { 78 // The output is shorter than expected -- use only what's produced by the engine 79 result = new byte[actualResultLength]; 80 System.arraycopy(buffer, 0, mResult, 0, mResult.length); 81 } else { 82 // The output is longer than expected 83 throw new RuntimeException("Engine produced a longer than expected result. Expected: " 84 + mExpectedResultLength + ", actual: " + actualResultLength); 85 } 86 mResult = result; 87 88 return null; // No intermediate key 89 } 90 91 @Override engineGenerateSecret(byte[] sharedSecret, int offset)92 protected int engineGenerateSecret(byte[] sharedSecret, int offset) 93 throws ShortBufferException { 94 checkCompleted(); 95 int available = sharedSecret.length - offset; 96 if (mResult.length > available) { 97 throw new ShortBufferException( 98 "Needed: " + mResult.length + ", available: " + available); 99 } 100 101 System.arraycopy(mResult, 0, sharedSecret, offset, mResult.length); 102 return mResult.length; 103 } 104 105 @Override engineGenerateSecret()106 protected byte[] engineGenerateSecret() { 107 checkCompleted(); 108 return mResult; 109 } 110 111 @Override engineGenerateSecret(String algorithm)112 protected SecretKey engineGenerateSecret(String algorithm) { 113 checkCompleted(); 114 return new SecretKeySpec(engineGenerateSecret(), algorithm); 115 } 116 117 @Override engineInit(Key key, SecureRandom random)118 protected void engineInit(Key key, SecureRandom random) throws InvalidKeyException { 119 if (key == null) { 120 throw new InvalidKeyException("key == null"); 121 } 122 if (!(key instanceof PrivateKey)) { 123 throw new InvalidKeyException("Not a private key: " + key.getClass()); 124 } 125 126 OpenSSLKey openSslKey = OpenSSLKey.fromPrivateKey((PrivateKey) key); 127 int fieldSizeBits = NativeCrypto.EC_GROUP_get_degree(new NativeRef.EC_GROUP( 128 NativeCrypto.EC_KEY_get1_group(openSslKey.getNativeRef()))); 129 mExpectedResultLength = (fieldSizeBits + 7) / 8; 130 mOpenSslPrivateKey = openSslKey; 131 } 132 133 @Override engineInit(Key key, AlgorithmParameterSpec params, SecureRandom random)134 protected void engineInit(Key key, AlgorithmParameterSpec params, 135 SecureRandom random) throws InvalidKeyException, InvalidAlgorithmParameterException { 136 // ECDH doesn't need an AlgorithmParameterSpec 137 if (params != null) { 138 throw new InvalidAlgorithmParameterException("No algorithm parameters supported"); 139 } 140 engineInit(key, random); 141 } 142 checkCompleted()143 private void checkCompleted() { 144 if (mResult == null) { 145 throw new IllegalStateException("Key agreement not completed"); 146 } 147 } 148 } 149