OpenSSLECDHKeyAgreement.java revision e741559fd878ee6e3deca9102f7c27e1c1ca70d0
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 17package org.apache.harmony.xnet.provider.jsse; 18 19import java.security.InvalidAlgorithmParameterException; 20import java.security.InvalidKeyException; 21import java.security.Key; 22import java.security.SecureRandom; 23import java.security.interfaces.ECPrivateKey; 24import java.security.interfaces.ECPublicKey; 25import java.security.spec.AlgorithmParameterSpec; 26 27import javax.crypto.KeyAgreementSpi; 28import javax.crypto.SecretKey; 29import javax.crypto.ShortBufferException; 30import javax.crypto.spec.SecretKeySpec; 31 32/** 33 * Elliptic Curve Diffie-Hellman key agreement backed by the OpenSSL engine. 34 */ 35public 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 if (key == null) { 58 throw new InvalidKeyException("key == null"); 59 } 60 if (!(key instanceof ECPublicKey)) { 61 throw new InvalidKeyException("This phase requires an ECPublicKey. Actual key type: " 62 + key.getClass()); 63 } 64 ECPublicKey publicKey = (ECPublicKey) key; 65 66 OpenSSLKey openSslPublicKey; 67 if (publicKey instanceof OpenSSLECPublicKey) { 68 // OpenSSL-backed key 69 openSslPublicKey = ((OpenSSLECPublicKey) publicKey).getOpenSSLKey(); 70 } else { 71 // Not an OpenSSL-backed key -- create an OpenSSL-backed key from its X.509 encoding 72 if (!"X.509".equals(publicKey.getFormat())) { 73 throw new InvalidKeyException("Non-OpenSSL public key (" + publicKey.getClass() 74 + ") offers unsupported encoding format: " + publicKey.getFormat()); 75 } 76 byte[] encoded = publicKey.getEncoded(); 77 if (encoded == null) { 78 throw new InvalidKeyException("Non-OpenSSL public key (" + publicKey.getClass() 79 + ") does not provide encoded form"); 80 } 81 try { 82 openSslPublicKey = new OpenSSLKey(NativeCrypto.d2i_PUBKEY(encoded)); 83 } catch (Exception e) { 84 throw new InvalidKeyException("Failed to decode X.509 encoded public key", e); 85 } 86 } 87 88 byte[] buffer = new byte[mExpectedResultLength]; 89 int actualResultLength = NativeCrypto.ECDH_compute_key( 90 buffer, 91 0, 92 openSslPublicKey.getPkeyContext(), 93 mOpenSslPrivateKey.getPkeyContext()); 94 byte[] result; 95 if (actualResultLength == -1) { 96 throw new RuntimeException("Engine returned " + actualResultLength); 97 } else if (actualResultLength == mExpectedResultLength) { 98 // The output is as long as expected -- use the whole buffer 99 result = buffer; 100 } else if (actualResultLength < mExpectedResultLength) { 101 // The output is shorter than expected -- use only what's produced by the engine 102 result = new byte[actualResultLength]; 103 System.arraycopy(buffer, 0, mResult, 0, mResult.length); 104 } else { 105 // The output is longer than expected 106 throw new RuntimeException("Engine produced a longer than expected result. Expected: " 107 + mExpectedResultLength + ", actual: " + actualResultLength); 108 } 109 mResult = result; 110 111 return null; // No intermediate key 112 } 113 114 @Override 115 protected int engineGenerateSecret(byte[] sharedSecret, int offset) 116 throws ShortBufferException { 117 checkCompleted(); 118 int available = sharedSecret.length - offset; 119 if (mResult.length > available) { 120 throw new ShortBufferException( 121 "Needed: " + mResult.length + ", available: " + available); 122 } 123 124 System.arraycopy(mResult, 0, sharedSecret, offset, mResult.length); 125 return mResult.length; 126 } 127 128 @Override 129 protected byte[] engineGenerateSecret() { 130 checkCompleted(); 131 return mResult; 132 } 133 134 @Override 135 protected SecretKey engineGenerateSecret(String algorithm) { 136 checkCompleted(); 137 return new SecretKeySpec(engineGenerateSecret(), algorithm); 138 } 139 140 @Override 141 protected void engineInit(Key key, SecureRandom random) throws InvalidKeyException { 142 if (key == null) { 143 throw new InvalidKeyException("key == null"); 144 } 145 if (!(key instanceof ECPrivateKey)) { 146 throw new InvalidKeyException("Not an EC private key: " + key.getClass()); 147 } 148 ECPrivateKey privateKey = (ECPrivateKey) key; 149 mExpectedResultLength = 150 (privateKey.getParams().getCurve().getField().getFieldSize() + 7) / 8; 151 152 OpenSSLKey openSslPrivateKey; 153 if (privateKey instanceof OpenSSLECPrivateKey) { 154 // OpenSSL-backed key 155 openSslPrivateKey = ((OpenSSLECPrivateKey) privateKey).getOpenSSLKey(); 156 } else { 157 // Not an OpenSSL-backed key -- create an OpenSSL-backed key from its PKCS#8 encoding 158 if (!"PKCS#8".equals(privateKey.getFormat())) { 159 throw new InvalidKeyException("Non-OpenSSL private key (" + privateKey.getClass() 160 + ") offers unsupported encoding format: " + privateKey.getFormat()); 161 } 162 byte[] encoded = privateKey.getEncoded(); 163 if (encoded == null) { 164 throw new InvalidKeyException("Non-OpenSSL private key (" + privateKey.getClass() 165 + ") does not provide encoded form"); 166 } 167 try { 168 openSslPrivateKey = new OpenSSLKey(NativeCrypto.d2i_PKCS8_PRIV_KEY_INFO(encoded)); 169 } catch (Exception e) { 170 throw new InvalidKeyException("Failed to decode PKCS#8 encoded private key", e); 171 } 172 } 173 mOpenSslPrivateKey = openSslPrivateKey; 174 } 175 176 @Override 177 protected void engineInit(Key key, AlgorithmParameterSpec params, 178 SecureRandom random) throws InvalidKeyException, InvalidAlgorithmParameterException { 179 // ECDH doesn't need an AlgorithmParameterSpec 180 if (params != null) { 181 throw new InvalidAlgorithmParameterException("No algorithm parameters supported"); 182 } 183 engineInit(key, random); 184 } 185 186 private void checkCompleted() { 187 if (mResult == null) { 188 throw new IllegalStateException("Key agreement not completed"); 189 } 190 } 191} 192