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.PrivateKey;
27import java.security.PublicKey;
28import java.security.SecureRandom;
29import java.security.SignatureException;
30import java.security.interfaces.RSAPrivateCrtKey;
31import java.security.interfaces.RSAPrivateKey;
32import java.security.interfaces.RSAPublicKey;
33import java.security.spec.AlgorithmParameterSpec;
34import java.security.spec.InvalidKeySpecException;
35import java.security.spec.InvalidParameterSpecException;
36import java.security.spec.MGF1ParameterSpec;
37import java.security.spec.PKCS8EncodedKeySpec;
38import java.security.spec.X509EncodedKeySpec;
39import java.util.Arrays;
40import java.util.Locale;
41import javax.crypto.BadPaddingException;
42import javax.crypto.Cipher;
43import javax.crypto.CipherSpi;
44import javax.crypto.IllegalBlockSizeException;
45import javax.crypto.NoSuchPaddingException;
46import javax.crypto.ShortBufferException;
47import javax.crypto.spec.OAEPParameterSpec;
48import javax.crypto.spec.PSource;
49import javax.crypto.spec.SecretKeySpec;
50
51/**
52 * @hide
53 */
54@Internal
55abstract class OpenSSLCipherRSA extends CipherSpi {
56    /**
57     * The current OpenSSL key we're operating on.
58     */
59    OpenSSLKey key;
60
61    /**
62     * Current key type: private or public.
63     */
64    boolean usingPrivateKey;
65
66    /**
67     * Current cipher mode: encrypting or decrypting.
68     */
69    boolean encrypting;
70
71    /**
72     * Buffer for operations
73     */
74    private byte[] buffer;
75
76    /**
77     * Current offset in the buffer.
78     */
79    private int bufferOffset;
80
81    /**
82     * Flag that indicates an exception should be thrown when the input is too
83     * large during doFinal.
84     */
85    private boolean inputTooLarge;
86
87    /**
88     * Current padding mode
89     */
90    int padding = NativeConstants.RSA_PKCS1_PADDING;
91
92    OpenSSLCipherRSA(int padding) {
93        this.padding = padding;
94    }
95
96    @Override
97    protected void engineSetMode(String mode) throws NoSuchAlgorithmException {
98        final String modeUpper = mode.toUpperCase(Locale.ROOT);
99        if ("NONE".equals(modeUpper) || "ECB".equals(modeUpper)) {
100            return;
101        }
102
103        throw new NoSuchAlgorithmException("mode not supported: " + mode);
104    }
105
106    @Override
107    protected void engineSetPadding(String padding) throws NoSuchPaddingException {
108        final String paddingUpper = padding.toUpperCase(Locale.ROOT);
109        if ("PKCS1PADDING".equals(paddingUpper)) {
110            this.padding = NativeConstants.RSA_PKCS1_PADDING;
111            return;
112        }
113        if ("NOPADDING".equals(paddingUpper)) {
114            this.padding = NativeConstants.RSA_NO_PADDING;
115            return;
116        }
117
118        throw new NoSuchPaddingException("padding not supported: " + padding);
119    }
120
121    @Override
122    protected int engineGetBlockSize() {
123        if (encrypting) {
124            return paddedBlockSizeBytes();
125        }
126        return keySizeBytes();
127    }
128
129    @Override
130    protected int engineGetOutputSize(int inputLen) {
131        if (encrypting) {
132            return keySizeBytes();
133        }
134        return paddedBlockSizeBytes();
135    }
136
137    int paddedBlockSizeBytes() {
138        int paddedBlockSizeBytes = keySizeBytes();
139        if (padding == NativeConstants.RSA_PKCS1_PADDING) {
140            paddedBlockSizeBytes--;  // for 0 prefix
141            paddedBlockSizeBytes -= 10;  // PKCS1 padding header length
142        }
143        return paddedBlockSizeBytes;
144    }
145
146    int keySizeBytes() {
147        if (!isInitialized()) {
148            throw new IllegalStateException("cipher is not initialized");
149        }
150        return NativeCrypto.RSA_size(this.key.getNativeRef());
151    }
152
153    /**
154     * Returns {@code true} if the cipher has been initialized.
155     */
156    boolean isInitialized() {
157        return key != null;
158    }
159
160    @Override
161    protected byte[] engineGetIV() {
162        return null;
163    }
164
165    @Override
166    protected AlgorithmParameters engineGetParameters() {
167        return null;
168    }
169
170    void doCryptoInit(AlgorithmParameterSpec spec)
171            throws InvalidAlgorithmParameterException {}
172
173    void engineInitInternal(int opmode, Key key, AlgorithmParameterSpec spec)
174            throws InvalidKeyException, InvalidAlgorithmParameterException {
175        if (opmode == Cipher.ENCRYPT_MODE || opmode == Cipher.WRAP_MODE) {
176            encrypting = true;
177        } else if (opmode == Cipher.DECRYPT_MODE || opmode == Cipher.UNWRAP_MODE) {
178            encrypting = false;
179        } else {
180            throw new InvalidParameterException("Unsupported opmode " + opmode);
181        }
182
183        if (key instanceof OpenSSLRSAPrivateKey) {
184            OpenSSLRSAPrivateKey rsaPrivateKey = (OpenSSLRSAPrivateKey) key;
185            usingPrivateKey = true;
186            this.key = rsaPrivateKey.getOpenSSLKey();
187        } else if (key instanceof RSAPrivateCrtKey) {
188            RSAPrivateCrtKey rsaPrivateKey = (RSAPrivateCrtKey) key;
189            usingPrivateKey = true;
190            this.key = OpenSSLRSAPrivateCrtKey.getInstance(rsaPrivateKey);
191        } else if (key instanceof RSAPrivateKey) {
192            RSAPrivateKey rsaPrivateKey = (RSAPrivateKey) key;
193            usingPrivateKey = true;
194            this.key = OpenSSLRSAPrivateKey.getInstance(rsaPrivateKey);
195        } else if (key instanceof OpenSSLRSAPublicKey) {
196            OpenSSLRSAPublicKey rsaPublicKey = (OpenSSLRSAPublicKey) key;
197            usingPrivateKey = false;
198            this.key = rsaPublicKey.getOpenSSLKey();
199        } else if (key instanceof RSAPublicKey) {
200            RSAPublicKey rsaPublicKey = (RSAPublicKey) key;
201            usingPrivateKey = false;
202            this.key = OpenSSLRSAPublicKey.getInstance(rsaPublicKey);
203        } else {
204            if (null == key) {
205                throw new InvalidKeyException("RSA private or public key is null");
206            }
207
208            throw new InvalidKeyException("Need RSA private or public key");
209        }
210
211        buffer = new byte[NativeCrypto.RSA_size(this.key.getNativeRef())];
212        bufferOffset = 0;
213        inputTooLarge = false;
214
215        doCryptoInit(spec);
216    }
217
218    @Override
219    protected void engineInit(int opmode, Key key, SecureRandom random) throws InvalidKeyException {
220        try {
221            engineInitInternal(opmode, key, null);
222        } catch (InvalidAlgorithmParameterException e) {
223            throw new InvalidKeyException("Algorithm parameters rejected when none supplied", e);
224        }
225    }
226
227    @Override
228    protected void engineInit(int opmode, Key key, AlgorithmParameterSpec params,
229            SecureRandom random) throws InvalidKeyException, InvalidAlgorithmParameterException {
230        if (params != null) {
231            throw new InvalidAlgorithmParameterException("unknown param type: "
232                    + params.getClass().getName());
233        }
234
235        engineInitInternal(opmode, key, params);
236    }
237
238    @Override
239    protected void engineInit(int opmode, Key key, AlgorithmParameters params, SecureRandom random)
240            throws InvalidKeyException, InvalidAlgorithmParameterException {
241        if (params != null) {
242            throw new InvalidAlgorithmParameterException("unknown param type: "
243                    + params.getClass().getName());
244        }
245
246        engineInitInternal(opmode, key, null);
247    }
248
249    @Override
250    protected byte[] engineUpdate(byte[] input, int inputOffset, int inputLen) {
251        if (bufferOffset + inputLen > buffer.length) {
252            inputTooLarge = true;
253            return EmptyArray.BYTE;
254        }
255
256        System.arraycopy(input, inputOffset, buffer, bufferOffset, inputLen);
257        bufferOffset += inputLen;
258        return EmptyArray.BYTE;
259    }
260
261    @Override
262    protected int engineUpdate(byte[] input, int inputOffset, int inputLen, byte[] output,
263            int outputOffset) throws ShortBufferException {
264        engineUpdate(input, inputOffset, inputLen);
265        return 0;
266    }
267
268    @Override
269    protected byte[] engineDoFinal(byte[] input, int inputOffset, int inputLen)
270            throws IllegalBlockSizeException, BadPaddingException {
271        if (input != null) {
272            engineUpdate(input, inputOffset, inputLen);
273        }
274
275        if (inputTooLarge) {
276            throw new IllegalBlockSizeException("input must be under " + buffer.length + " bytes");
277        }
278
279        final byte[] tmpBuf;
280        if (bufferOffset != buffer.length) {
281            if (padding == NativeConstants.RSA_NO_PADDING) {
282                tmpBuf = new byte[buffer.length];
283                System.arraycopy(buffer, 0, tmpBuf, buffer.length - bufferOffset, bufferOffset);
284            } else {
285                tmpBuf = Arrays.copyOf(buffer, bufferOffset);
286            }
287        } else {
288            tmpBuf = buffer;
289        }
290
291        byte[] output = new byte[buffer.length];
292        int resultSize = doCryptoOperation(tmpBuf, output);
293        if (!encrypting && resultSize != output.length) {
294            output = Arrays.copyOf(output, resultSize);
295        }
296
297        bufferOffset = 0;
298        return output;
299    }
300
301    abstract int doCryptoOperation(final byte[] tmpBuf, byte[] output)
302            throws BadPaddingException, IllegalBlockSizeException;
303
304    @Override
305    protected int engineDoFinal(byte[] input, int inputOffset, int inputLen, byte[] output,
306            int outputOffset) throws ShortBufferException, IllegalBlockSizeException,
307            BadPaddingException {
308        byte[] b = engineDoFinal(input, inputOffset, inputLen);
309
310        final int lastOffset = outputOffset + b.length;
311        if (lastOffset > output.length) {
312            throw new ShortBufferException("output buffer is too small " + output.length + " < "
313                    + lastOffset);
314        }
315
316        System.arraycopy(b, 0, output, outputOffset, b.length);
317        return b.length;
318    }
319
320    @Override
321    protected byte[] engineWrap(Key key) throws IllegalBlockSizeException, InvalidKeyException {
322        try {
323            byte[] encoded = key.getEncoded();
324            return engineDoFinal(encoded, 0, encoded.length);
325        } catch (BadPaddingException e) {
326            IllegalBlockSizeException newE = new IllegalBlockSizeException();
327            newE.initCause(e);
328            throw newE;
329        }
330    }
331
332    @Override
333    protected Key engineUnwrap(byte[] wrappedKey, String wrappedKeyAlgorithm,
334            int wrappedKeyType) throws InvalidKeyException, NoSuchAlgorithmException {
335        try {
336            byte[] encoded = engineDoFinal(wrappedKey, 0, wrappedKey.length);
337            if (wrappedKeyType == Cipher.PUBLIC_KEY) {
338                KeyFactory keyFactory = KeyFactory.getInstance(wrappedKeyAlgorithm);
339                return keyFactory.generatePublic(new X509EncodedKeySpec(encoded));
340            } else if (wrappedKeyType == Cipher.PRIVATE_KEY) {
341                KeyFactory keyFactory = KeyFactory.getInstance(wrappedKeyAlgorithm);
342                return keyFactory.generatePrivate(new PKCS8EncodedKeySpec(encoded));
343            } else if (wrappedKeyType == Cipher.SECRET_KEY) {
344                return new SecretKeySpec(encoded, wrappedKeyAlgorithm);
345            } else {
346                throw new UnsupportedOperationException("wrappedKeyType == " + wrappedKeyType);
347            }
348        } catch (IllegalBlockSizeException e) {
349            throw new InvalidKeyException(e);
350        } catch (BadPaddingException e) {
351            throw new InvalidKeyException(e);
352        } catch (InvalidKeySpecException e) {
353            throw new InvalidKeyException(e);
354        }
355    }
356
357    public abstract static class DirectRSA extends OpenSSLCipherRSA {
358        public DirectRSA(int padding) {
359            super(padding);
360        }
361
362        @Override
363        int doCryptoOperation(final byte[] tmpBuf, byte[] output)
364                throws BadPaddingException, IllegalBlockSizeException {
365            int resultSize;
366            if (encrypting) {
367                if (usingPrivateKey) {
368                    resultSize = NativeCrypto.RSA_private_encrypt(
369                            tmpBuf.length, tmpBuf, output, key.getNativeRef(), padding);
370                } else {
371                    resultSize = NativeCrypto.RSA_public_encrypt(
372                            tmpBuf.length, tmpBuf, output, key.getNativeRef(), padding);
373                }
374            } else {
375                try {
376                    if (usingPrivateKey) {
377                        resultSize = NativeCrypto.RSA_private_decrypt(
378                                tmpBuf.length, tmpBuf, output, key.getNativeRef(), padding);
379                    } else {
380                        resultSize = NativeCrypto.RSA_public_decrypt(
381                                tmpBuf.length, tmpBuf, output, key.getNativeRef(), padding);
382                    }
383                } catch (SignatureException e) {
384                    IllegalBlockSizeException newE = new IllegalBlockSizeException();
385                    newE.initCause(e);
386                    throw newE;
387                }
388            }
389            return resultSize;
390        }
391    }
392
393    public static final class PKCS1 extends DirectRSA {
394        public PKCS1() {
395            super(NativeConstants.RSA_PKCS1_PADDING);
396        }
397    }
398
399    public static final class Raw extends DirectRSA {
400        public Raw() {
401            super(NativeConstants.RSA_NO_PADDING);
402        }
403    }
404
405    static class OAEP extends OpenSSLCipherRSA {
406        private long oaepMd;
407        private int oaepMdSizeBytes;
408
409        private long mgf1Md;
410
411        private byte[] label;
412
413        private NativeRef.EVP_PKEY_CTX pkeyCtx;
414
415        public OAEP(long defaultMd, int defaultMdSizeBytes) {
416            super(NativeConstants.RSA_PKCS1_OAEP_PADDING);
417            oaepMd = mgf1Md = defaultMd;
418            oaepMdSizeBytes = defaultMdSizeBytes;
419        }
420
421        @Override
422        protected AlgorithmParameters engineGetParameters() {
423            if (!isInitialized()) {
424                return null;
425            }
426
427            try {
428                AlgorithmParameters params = AlgorithmParameters.getInstance("OAEP");
429
430                final PSource pSrc;
431                if (label == null) {
432                    pSrc = PSource.PSpecified.DEFAULT;
433                } else {
434                    pSrc = new PSource.PSpecified(label);
435                }
436
437                params.init(new OAEPParameterSpec(
438                        EvpMdRef.getJcaDigestAlgorithmStandardNameFromEVP_MD(oaepMd),
439                        EvpMdRef.MGF1_ALGORITHM_NAME,
440                        new MGF1ParameterSpec(
441                                EvpMdRef.getJcaDigestAlgorithmStandardNameFromEVP_MD(mgf1Md)),
442                        pSrc));
443                return params;
444            } catch (NoSuchAlgorithmException | InvalidParameterSpecException e) {
445                throw new RuntimeException("No providers of AlgorithmParameters.OAEP available");
446            }
447        }
448
449        @Override
450        protected void engineSetPadding(String padding) throws NoSuchPaddingException {
451            String paddingUpper = padding.toUpperCase(Locale.US);
452            if (paddingUpper.equals("OAEPPadding")) {
453                this.padding = NativeConstants.RSA_PKCS1_OAEP_PADDING;
454                return;
455            }
456
457            throw new NoSuchPaddingException("Only OAEP padding is supported");
458        }
459
460        @Override
461        protected void engineInit(
462                int opmode, Key key, AlgorithmParameterSpec spec, SecureRandom random)
463                throws InvalidKeyException, InvalidAlgorithmParameterException {
464            if (spec != null && !(spec instanceof OAEPParameterSpec)) {
465                throw new InvalidAlgorithmParameterException(
466                        "Only OAEPParameterSpec accepted in OAEP mode");
467            }
468
469            engineInitInternal(opmode, key, spec);
470        }
471
472        @Override
473        protected void engineInit(
474                int opmode, Key key, AlgorithmParameters params, SecureRandom random)
475                throws InvalidKeyException, InvalidAlgorithmParameterException {
476            OAEPParameterSpec spec = null;
477            if (params != null) {
478                try {
479                    spec = params.getParameterSpec(OAEPParameterSpec.class);
480                } catch (InvalidParameterSpecException e) {
481                    throw new InvalidAlgorithmParameterException(
482                            "Only OAEP parameters are supported", e);
483                }
484            }
485
486            engineInitInternal(opmode, key, spec);
487        }
488
489        @Override
490        void engineInitInternal(int opmode, Key key, AlgorithmParameterSpec spec)
491                throws InvalidKeyException, InvalidAlgorithmParameterException {
492            if (opmode == Cipher.ENCRYPT_MODE || opmode == Cipher.WRAP_MODE) {
493                if (!(key instanceof PublicKey)) {
494                    throw new InvalidKeyException("Only public keys may be used to encrypt");
495                }
496            } else if (opmode == Cipher.DECRYPT_MODE || opmode == Cipher.UNWRAP_MODE) {
497                if (!(key instanceof PrivateKey)) {
498                    throw new InvalidKeyException("Only private keys may be used to decrypt");
499                }
500            }
501            super.engineInitInternal(opmode, key, spec);
502        }
503
504        @Override
505        void doCryptoInit(AlgorithmParameterSpec spec)
506                throws InvalidAlgorithmParameterException {
507            pkeyCtx = new NativeRef.EVP_PKEY_CTX(encrypting
508                            ? NativeCrypto.EVP_PKEY_encrypt_init(key.getNativeRef())
509                            : NativeCrypto.EVP_PKEY_decrypt_init(key.getNativeRef()));
510
511            if (spec instanceof OAEPParameterSpec) {
512                readOAEPParameters((OAEPParameterSpec) spec);
513            }
514
515            NativeCrypto.EVP_PKEY_CTX_set_rsa_padding(
516                    pkeyCtx.context, NativeConstants.RSA_PKCS1_OAEP_PADDING);
517            NativeCrypto.EVP_PKEY_CTX_set_rsa_oaep_md(pkeyCtx.context, oaepMd);
518            NativeCrypto.EVP_PKEY_CTX_set_rsa_mgf1_md(pkeyCtx.context, mgf1Md);
519            if (label != null && label.length > 0) {
520                NativeCrypto.EVP_PKEY_CTX_set_rsa_oaep_label(pkeyCtx.context, label);
521            }
522        }
523
524        @Override
525        int paddedBlockSizeBytes() {
526            int paddedBlockSizeBytes = keySizeBytes();
527            // Size described in step 2 of decoding algorithm, but extra byte
528            // needed to make sure it's smaller than the RSA key modulus size.
529            // https://tools.ietf.org/html/rfc2437#section-9.1.1.2
530            return paddedBlockSizeBytes - (2 * oaepMdSizeBytes + 2);
531        }
532
533        private void readOAEPParameters(OAEPParameterSpec spec)
534                throws InvalidAlgorithmParameterException {
535            String mgfAlgUpper = spec.getMGFAlgorithm().toUpperCase(Locale.US);
536            AlgorithmParameterSpec mgfSpec = spec.getMGFParameters();
537            if ((!EvpMdRef.MGF1_ALGORITHM_NAME.equals(mgfAlgUpper)
538                        && !EvpMdRef.MGF1_OID.equals(mgfAlgUpper))
539                    || !(mgfSpec instanceof MGF1ParameterSpec)) {
540                throw new InvalidAlgorithmParameterException(
541                        "Only MGF1 supported as mask generation function");
542            }
543
544            MGF1ParameterSpec mgf1spec = (MGF1ParameterSpec) mgfSpec;
545            String oaepAlgUpper = spec.getDigestAlgorithm().toUpperCase(Locale.US);
546            try {
547                oaepMd = EvpMdRef.getEVP_MDByJcaDigestAlgorithmStandardName(oaepAlgUpper);
548                oaepMdSizeBytes =
549                        EvpMdRef.getDigestSizeBytesByJcaDigestAlgorithmStandardName(oaepAlgUpper);
550                mgf1Md = EvpMdRef.getEVP_MDByJcaDigestAlgorithmStandardName(
551                        mgf1spec.getDigestAlgorithm());
552            } catch (NoSuchAlgorithmException e) {
553                throw new InvalidAlgorithmParameterException(e);
554            }
555
556            PSource pSource = spec.getPSource();
557            if (!"PSpecified".equals(pSource.getAlgorithm())
558                    || !(pSource instanceof PSource.PSpecified)) {
559                throw new InvalidAlgorithmParameterException(
560                        "Only PSpecified accepted for PSource");
561            }
562            label = ((PSource.PSpecified) pSource).getValue();
563        }
564
565        @Override
566        int doCryptoOperation(byte[] tmpBuf, byte[] output)
567                throws BadPaddingException, IllegalBlockSizeException {
568            if (encrypting) {
569                return NativeCrypto.EVP_PKEY_encrypt(pkeyCtx, output, 0, tmpBuf, 0, tmpBuf.length);
570            } else {
571                return NativeCrypto.EVP_PKEY_decrypt(pkeyCtx, output, 0, tmpBuf, 0, tmpBuf.length);
572            }
573        }
574
575        public static final class SHA1 extends OAEP {
576            public SHA1() {
577                super(EvpMdRef.SHA1.EVP_MD, EvpMdRef.SHA1.SIZE_BYTES);
578            }
579        }
580
581        public static final class SHA224 extends OAEP {
582            public SHA224() {
583                super(EvpMdRef.SHA224.EVP_MD, EvpMdRef.SHA224.SIZE_BYTES);
584            }
585        }
586
587        public static final class SHA256 extends OAEP {
588            public SHA256() {
589                super(EvpMdRef.SHA256.EVP_MD, EvpMdRef.SHA256.SIZE_BYTES);
590            }
591        }
592
593        public static final class SHA384 extends OAEP {
594            public SHA384() {
595                super(EvpMdRef.SHA384.EVP_MD, EvpMdRef.SHA384.SIZE_BYTES);
596            }
597        }
598
599        public static final class SHA512 extends OAEP {
600            public SHA512() {
601                super(EvpMdRef.SHA512.EVP_MD, EvpMdRef.SHA512.SIZE_BYTES);
602            }
603        }
604    }
605}
606