Home | History | Annotate | Download | only in conscrypt
      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 = translateKeyToEcOpenSSLKey(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 = translateKeyToEcOpenSSLKey(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     private static OpenSSLKey translateKeyToEcOpenSSLKey(Key key) throws InvalidKeyException {
    151         try {
    152             return ((OpenSSLKeyHolder) KeyFactory.getInstance(
    153                     "EC", OpenSSLProvider.PROVIDER_NAME).translateKey(key)).getOpenSSLKey();
    154         } catch (Exception e) {
    155             throw new InvalidKeyException("Failed to translate key to OpenSSL EC key", e);
    156         }
    157     }
    158 }
    159