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.conscrypt;
18
19import java.security.InvalidAlgorithmParameterException;
20import java.security.InvalidKeyException;
21import java.security.Key;
22import java.security.KeyFactory;
23import java.security.PrivateKey;
24import java.security.PublicKey;
25import java.security.SecureRandom;
26import java.security.spec.AlgorithmParameterSpec;
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
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 = OpenSSLKey.fromPublicKey((PublicKey) 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 = OpenSSLKey.fromPrivateKey((PrivateKey) 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