Home | History | Annotate | Download | only in jsse
      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