OpenSSLCipherRSA.java revision cc73183fa6f5f6f9935307aacfbfc5d93a867a23
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.AlgorithmParameters;
20import java.security.InvalidAlgorithmParameterException;
21import java.security.InvalidKeyException;
22import java.security.InvalidParameterException;
23import java.security.Key;
24import java.security.KeyFactory;
25import java.security.NoSuchAlgorithmException;
26import java.security.SecureRandom;
27import java.security.SignatureException;
28import java.security.interfaces.RSAPrivateCrtKey;
29import java.security.interfaces.RSAPrivateKey;
30import java.security.interfaces.RSAPublicKey;
31import java.security.spec.AlgorithmParameterSpec;
32import java.security.spec.InvalidKeySpecException;
33import java.security.spec.PKCS8EncodedKeySpec;
34import java.security.spec.X509EncodedKeySpec;
35import java.util.Arrays;
36import javax.crypto.BadPaddingException;
37import javax.crypto.Cipher;
38import javax.crypto.CipherSpi;
39import javax.crypto.IllegalBlockSizeException;
40import javax.crypto.NoSuchPaddingException;
41import javax.crypto.ShortBufferException;
42import javax.crypto.spec.SecretKeySpec;
43import org.conscrypt.util.EmptyArray;
44
45public abstract class OpenSSLCipherRSA extends CipherSpi {
46    /**
47     * The current OpenSSL key we're operating on.
48     */
49    private OpenSSLKey key;
50
51    /**
52     * Current key type: private or public.
53     */
54    private boolean usingPrivateKey;
55
56    /**
57     * Current cipher mode: encrypting or decrypting.
58     */
59    private boolean encrypting;
60
61    /**
62     * Buffer for operations
63     */
64    private byte[] buffer;
65
66    /**
67     * Current offset in the buffer.
68     */
69    private int bufferOffset;
70
71    /**
72     * Flag that indicates an exception should be thrown when the input is too
73     * large during doFinal.
74     */
75    private boolean inputTooLarge;
76
77    /**
78     * Current padding mode
79     */
80    private int padding = NativeCrypto.RSA_PKCS1_PADDING;
81
82    protected OpenSSLCipherRSA(int padding) {
83        this.padding = padding;
84    }
85
86    @Override
87    protected void engineSetMode(String mode) throws NoSuchAlgorithmException {
88        final String modeUpper = mode.toUpperCase();
89        if ("NONE".equals(modeUpper) || "ECB".equals(modeUpper)) {
90            return;
91        }
92
93        throw new NoSuchAlgorithmException("mode not supported: " + mode);
94    }
95
96    @Override
97    protected void engineSetPadding(String padding) throws NoSuchPaddingException {
98        final String paddingUpper = padding.toUpperCase();
99        if ("PKCS1PADDING".equals(paddingUpper)) {
100            this.padding = NativeCrypto.RSA_PKCS1_PADDING;
101            return;
102        }
103        if ("NOPADDING".equals(paddingUpper)) {
104            this.padding = NativeCrypto.RSA_NO_PADDING;
105            return;
106        }
107
108        throw new NoSuchPaddingException("padding not supported: " + padding);
109    }
110
111    @Override
112    protected int engineGetBlockSize() {
113        if (encrypting) {
114            return paddedBlockSizeBytes();
115        }
116        return keySizeBytes();
117    }
118
119    @Override
120    protected int engineGetOutputSize(int inputLen) {
121        if (encrypting) {
122            return keySizeBytes();
123        }
124        return paddedBlockSizeBytes();
125    }
126
127    private int paddedBlockSizeBytes() {
128        int paddedBlockSizeBytes = keySizeBytes();
129        if (padding == NativeCrypto.RSA_PKCS1_PADDING) {
130            paddedBlockSizeBytes--;  // for 0 prefix
131            paddedBlockSizeBytes -= 10;  // PKCS1 padding header length
132        }
133        return paddedBlockSizeBytes;
134    }
135
136    private int keySizeBytes() {
137        if (key == null) {
138            throw new IllegalStateException("cipher is not initialized");
139        }
140        return NativeCrypto.RSA_size(this.key.getPkeyContext());
141    }
142
143    @Override
144    protected byte[] engineGetIV() {
145        return null;
146    }
147
148    @Override
149    protected AlgorithmParameters engineGetParameters() {
150        return null;
151    }
152
153    private void engineInitInternal(int opmode, Key key) throws InvalidKeyException {
154        if (opmode == Cipher.ENCRYPT_MODE || opmode == Cipher.WRAP_MODE) {
155            encrypting = true;
156        } else if (opmode == Cipher.DECRYPT_MODE || opmode == Cipher.UNWRAP_MODE) {
157            encrypting = false;
158        } else {
159            throw new InvalidParameterException("Unsupported opmode " + opmode);
160        }
161
162        if (key instanceof OpenSSLRSAPrivateKey) {
163            OpenSSLRSAPrivateKey rsaPrivateKey = (OpenSSLRSAPrivateKey) key;
164            usingPrivateKey = true;
165            this.key = rsaPrivateKey.getOpenSSLKey();
166        } else if (key instanceof RSAPrivateCrtKey) {
167            RSAPrivateCrtKey rsaPrivateKey = (RSAPrivateCrtKey) key;
168            usingPrivateKey = true;
169            this.key = OpenSSLRSAPrivateCrtKey.getInstance(rsaPrivateKey);
170        } else if (key instanceof RSAPrivateKey) {
171            RSAPrivateKey rsaPrivateKey = (RSAPrivateKey) key;
172            usingPrivateKey = true;
173            this.key = OpenSSLRSAPrivateKey.getInstance(rsaPrivateKey);
174        } else if (key instanceof OpenSSLRSAPublicKey) {
175            OpenSSLRSAPublicKey rsaPublicKey = (OpenSSLRSAPublicKey) key;
176            usingPrivateKey = false;
177            this.key = rsaPublicKey.getOpenSSLKey();
178        } else if (key instanceof RSAPublicKey) {
179            RSAPublicKey rsaPublicKey = (RSAPublicKey) key;
180            usingPrivateKey = false;
181            this.key = OpenSSLRSAPublicKey.getInstance(rsaPublicKey);
182        } else {
183            throw new InvalidKeyException("Need RSA private or public key");
184        }
185
186        buffer = new byte[NativeCrypto.RSA_size(this.key.getPkeyContext())];
187        inputTooLarge = false;
188    }
189
190    @Override
191    protected void engineInit(int opmode, Key key, SecureRandom random) throws InvalidKeyException {
192        engineInitInternal(opmode, key);
193    }
194
195    @Override
196    protected void engineInit(int opmode, Key key, AlgorithmParameterSpec params,
197            SecureRandom random) throws InvalidKeyException, InvalidAlgorithmParameterException {
198        if (params != null) {
199            throw new InvalidAlgorithmParameterException("unknown param type: "
200                    + params.getClass().getName());
201        }
202
203        engineInitInternal(opmode, key);
204    }
205
206    @Override
207    protected void engineInit(int opmode, Key key, AlgorithmParameters params, SecureRandom random)
208            throws InvalidKeyException, InvalidAlgorithmParameterException {
209        if (params != null) {
210            throw new InvalidAlgorithmParameterException("unknown param type: "
211                    + params.getClass().getName());
212        }
213
214        engineInitInternal(opmode, key);
215    }
216
217    @Override
218    protected byte[] engineUpdate(byte[] input, int inputOffset, int inputLen) {
219        if (bufferOffset + inputLen > buffer.length) {
220            inputTooLarge = true;
221            return EmptyArray.BYTE;
222        }
223
224        System.arraycopy(input, inputOffset, buffer, bufferOffset, inputLen);
225        bufferOffset += inputLen;
226        return EmptyArray.BYTE;
227    }
228
229    @Override
230    protected int engineUpdate(byte[] input, int inputOffset, int inputLen, byte[] output,
231            int outputOffset) throws ShortBufferException {
232        engineUpdate(input, inputOffset, inputLen);
233        return 0;
234    }
235
236    @Override
237    protected byte[] engineDoFinal(byte[] input, int inputOffset, int inputLen)
238            throws IllegalBlockSizeException, BadPaddingException {
239        if (input != null) {
240            engineUpdate(input, inputOffset, inputLen);
241        }
242
243        if (inputTooLarge) {
244            throw new IllegalBlockSizeException("input must be under " + buffer.length + " bytes");
245        }
246
247        final byte[] tmpBuf;
248        if (bufferOffset != buffer.length) {
249            if (padding == NativeCrypto.RSA_NO_PADDING) {
250                tmpBuf = new byte[buffer.length];
251                System.arraycopy(buffer, 0, tmpBuf, buffer.length - bufferOffset, bufferOffset);
252            } else {
253                tmpBuf = Arrays.copyOf(buffer, bufferOffset);
254            }
255        } else {
256            tmpBuf = buffer;
257        }
258
259        byte[] output = new byte[buffer.length];
260        int resultSize;
261        if (encrypting) {
262            if (usingPrivateKey) {
263                resultSize = NativeCrypto.RSA_private_encrypt(tmpBuf.length, tmpBuf, output,
264                                                              key.getPkeyContext(), padding);
265            } else {
266                resultSize = NativeCrypto.RSA_public_encrypt(tmpBuf.length, tmpBuf, output,
267                                                             key.getPkeyContext(), padding);
268            }
269        } else {
270            try {
271                if (usingPrivateKey) {
272                    resultSize = NativeCrypto.RSA_private_decrypt(tmpBuf.length, tmpBuf, output,
273                                                                  key.getPkeyContext(), padding);
274                } else {
275                    resultSize = NativeCrypto.RSA_public_decrypt(tmpBuf.length, tmpBuf, output,
276                                                                 key.getPkeyContext(), padding);
277                }
278            } catch (SignatureException e) {
279                IllegalBlockSizeException newE = new IllegalBlockSizeException();
280                newE.initCause(e);
281                throw newE;
282            }
283        }
284        if (!encrypting && resultSize != output.length) {
285            output = Arrays.copyOf(output, resultSize);
286        }
287
288        bufferOffset = 0;
289        return output;
290    }
291
292    @Override
293    protected int engineDoFinal(byte[] input, int inputOffset, int inputLen, byte[] output,
294            int outputOffset) throws ShortBufferException, IllegalBlockSizeException,
295            BadPaddingException {
296        byte[] b = engineDoFinal(input, inputOffset, inputLen);
297
298        final int lastOffset = outputOffset + b.length;
299        if (lastOffset > output.length) {
300            throw new ShortBufferException("output buffer is too small " + output.length + " < "
301                    + lastOffset);
302        }
303
304        System.arraycopy(b, 0, output, outputOffset, b.length);
305        return b.length;
306    }
307
308    @Override
309    protected byte[] engineWrap(Key key) throws IllegalBlockSizeException, InvalidKeyException {
310        try {
311            byte[] encoded = key.getEncoded();
312            return engineDoFinal(encoded, 0, encoded.length);
313        } catch (BadPaddingException e) {
314            IllegalBlockSizeException newE = new IllegalBlockSizeException();
315            newE.initCause(e);
316            throw newE;
317        }
318    }
319
320    @Override
321    protected Key engineUnwrap(byte[] wrappedKey, String wrappedKeyAlgorithm,
322            int wrappedKeyType) throws InvalidKeyException, NoSuchAlgorithmException {
323        try {
324            byte[] encoded = engineDoFinal(wrappedKey, 0, wrappedKey.length);
325            if (wrappedKeyType == Cipher.PUBLIC_KEY) {
326                KeyFactory keyFactory = KeyFactory.getInstance(wrappedKeyAlgorithm);
327                return keyFactory.generatePublic(new X509EncodedKeySpec(encoded));
328            } else if (wrappedKeyType == Cipher.PRIVATE_KEY) {
329                KeyFactory keyFactory = KeyFactory.getInstance(wrappedKeyAlgorithm);
330                return keyFactory.generatePrivate(new PKCS8EncodedKeySpec(encoded));
331            } else if (wrappedKeyType == Cipher.SECRET_KEY) {
332                return new SecretKeySpec(encoded, wrappedKeyAlgorithm);
333            } else {
334                throw new UnsupportedOperationException("wrappedKeyType == " + wrappedKeyType);
335            }
336        } catch (IllegalBlockSizeException e) {
337            throw new InvalidKeyException(e);
338        } catch (BadPaddingException e) {
339            throw new InvalidKeyException(e);
340        } catch (InvalidKeySpecException e) {
341            throw new InvalidKeyException(e);
342        }
343    }
344
345    public static class PKCS1 extends OpenSSLCipherRSA {
346        public PKCS1() {
347            super(NativeCrypto.RSA_PKCS1_PADDING);
348        }
349    }
350
351    public static class Raw extends OpenSSLCipherRSA {
352        public Raw() {
353            super(NativeCrypto.RSA_NO_PADDING);
354        }
355    }
356}
357