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