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