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