1/*
2 * Copyright (C) 2012 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.InvalidKeyException;
20import java.security.InvalidParameterException;
21import java.security.PrivateKey;
22import java.security.PublicKey;
23import java.security.SignatureException;
24import java.security.SignatureSpi;
25import java.security.interfaces.RSAPrivateCrtKey;
26import java.security.interfaces.RSAPrivateKey;
27import java.security.interfaces.RSAPublicKey;
28
29/**
30 * Implements the JDK Signature interface needed for RAW RSA signature
31 * generation and verification using BoringSSL.
32 *
33 * @hide
34 */
35@Internal
36public final class OpenSSLSignatureRawRSA extends SignatureSpi {
37    /**
38     * The current OpenSSL key we're operating on.
39     */
40    private OpenSSLKey key;
41
42    /**
43     * Buffer to hold value to be signed or verified.
44     */
45    private byte[] inputBuffer;
46
47    /**
48     * Current offset in input buffer.
49     */
50    private int inputOffset;
51
52    /**
53     * Provides a flag to specify when the input is too long.
54     */
55    private boolean inputIsTooLong;
56
57    @Override
58    protected void engineUpdate(byte input) {
59        final int oldOffset = inputOffset++;
60
61        if (inputOffset > inputBuffer.length) {
62            inputIsTooLong = true;
63            return;
64        }
65
66        inputBuffer[oldOffset] = input;
67    }
68
69    @Override
70    protected void engineUpdate(byte[] input, int offset, int len) {
71        final int oldOffset = inputOffset;
72        inputOffset += len;
73
74        if (inputOffset > inputBuffer.length) {
75            inputIsTooLong = true;
76            return;
77        }
78
79        System.arraycopy(input, offset, inputBuffer, oldOffset, len);
80    }
81
82    @Override
83    @SuppressWarnings("deprecation")
84    protected Object engineGetParameter(String param) throws InvalidParameterException {
85        return null;
86    }
87
88    @Override
89    protected void engineInitSign(PrivateKey privateKey) throws InvalidKeyException {
90        if (privateKey instanceof OpenSSLRSAPrivateKey) {
91            OpenSSLRSAPrivateKey rsaPrivateKey = (OpenSSLRSAPrivateKey) privateKey;
92            key = rsaPrivateKey.getOpenSSLKey();
93        } else if (privateKey instanceof RSAPrivateCrtKey) {
94            RSAPrivateCrtKey rsaPrivateKey = (RSAPrivateCrtKey) privateKey;
95            key = OpenSSLRSAPrivateCrtKey.getInstance(rsaPrivateKey);
96        } else if (privateKey instanceof RSAPrivateKey) {
97            RSAPrivateKey rsaPrivateKey = (RSAPrivateKey) privateKey;
98            key = OpenSSLRSAPrivateKey.getInstance(rsaPrivateKey);
99        } else {
100            throw new InvalidKeyException("Need RSA private key");
101        }
102
103        // Allocate buffer according to RSA modulus size.
104        int maxSize = NativeCrypto.RSA_size(key.getNativeRef());
105        inputBuffer = new byte[maxSize];
106        inputOffset = 0;
107    }
108
109    @Override
110    protected void engineInitVerify(PublicKey publicKey) throws InvalidKeyException {
111        if (publicKey instanceof OpenSSLRSAPublicKey) {
112            OpenSSLRSAPublicKey rsaPublicKey = (OpenSSLRSAPublicKey) publicKey;
113            key = rsaPublicKey.getOpenSSLKey();
114        } else if (publicKey instanceof RSAPublicKey) {
115            RSAPublicKey rsaPublicKey = (RSAPublicKey) publicKey;
116            key = OpenSSLRSAPublicKey.getInstance(rsaPublicKey);
117        } else {
118            throw new InvalidKeyException("Need RSA public key");
119        }
120
121        // Allocate buffer according to RSA modulus size.
122        int maxSize = NativeCrypto.RSA_size(key.getNativeRef());
123        inputBuffer = new byte[maxSize];
124        inputOffset = 0;
125    }
126
127    @Override
128    @SuppressWarnings("deprecation")
129    protected void engineSetParameter(String param, Object value) throws InvalidParameterException {
130    }
131
132    @Override
133    protected byte[] engineSign() throws SignatureException {
134        if (key == null) {
135            // This can't actually happen, but you never know...
136            throw new SignatureException("Need RSA private key");
137        }
138
139        if (inputIsTooLong) {
140            throw new SignatureException("input length " + inputOffset + " != "
141                    + inputBuffer.length + " (modulus size)");
142        }
143
144        byte[] outputBuffer = new byte[inputBuffer.length];
145        try {
146            NativeCrypto.RSA_private_encrypt(inputOffset, inputBuffer, outputBuffer,
147                    key.getNativeRef(), NativeConstants.RSA_PKCS1_PADDING);
148            return outputBuffer;
149        } catch (Exception ex) {
150            throw new SignatureException(ex);
151        } finally {
152            inputOffset = 0;
153        }
154    }
155
156    @Override
157    protected boolean engineVerify(byte[] sigBytes) throws SignatureException {
158        if (key == null) {
159            // This can't actually happen, but you never know...
160            throw new SignatureException("Need RSA public key");
161        }
162
163        if (inputIsTooLong) {
164            return false;
165        }
166
167        // We catch this case here instead of BoringSSL so we can throw an
168        // exception that matches other implementations.
169        if (sigBytes.length > inputBuffer.length) {
170            throw new SignatureException("Input signature length is too large: " + sigBytes.length
171                    + " > " + inputBuffer.length);
172        }
173
174        byte[] outputBuffer = new byte[inputBuffer.length];
175        try {
176            final int resultSize;
177            try {
178                resultSize = NativeCrypto.RSA_public_decrypt(sigBytes.length, sigBytes,
179                        outputBuffer, key.getNativeRef(), NativeConstants.RSA_PKCS1_PADDING);
180            } catch (SignatureException e) {
181                throw e;
182            } catch (Exception e) {
183                return false;
184            }
185            /* Make this constant time by comparing every byte. */
186            boolean matches = (resultSize == inputOffset);
187            for (int i = 0; i < resultSize; i++) {
188                if (inputBuffer[i] != outputBuffer[i]) {
189                    matches = false;
190                }
191            }
192            return matches;
193        } catch (Exception ex) {
194            throw new SignatureException(ex);
195        } finally {
196            inputOffset = 0;
197        }
198    }
199}
200