OpenSSLCipher.java revision 81508e23428cffc070132f461c1166077910e836
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.apache.harmony.xnet.provider.jsse;
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.spec.AlgorithmParameterSpec;
28import java.security.spec.InvalidKeySpecException;
29import java.security.spec.InvalidParameterSpecException;
30import java.security.spec.PKCS8EncodedKeySpec;
31import java.security.spec.X509EncodedKeySpec;
32import java.util.Arrays;
33import java.util.Locale;
34
35import javax.crypto.BadPaddingException;
36import javax.crypto.Cipher;
37import javax.crypto.CipherSpi;
38import javax.crypto.IllegalBlockSizeException;
39import javax.crypto.NoSuchPaddingException;
40import javax.crypto.SecretKey;
41import javax.crypto.ShortBufferException;
42import javax.crypto.spec.IvParameterSpec;
43import javax.crypto.spec.SecretKeySpec;
44
45import libcore.util.EmptyArray;
46
47public abstract class OpenSSLCipher extends CipherSpi {
48
49    /**
50     * Modes that a block cipher may support.
51     */
52    protected static enum Mode {
53        CBC,
54        CFB, CFB1, CFB8, CFB128,
55        CTR,
56        CTS,
57        ECB,
58        OFB, OFB64, OFB128,
59        PCBC,
60    }
61
62    /**
63     * Paddings that a block cipher may support.
64     */
65    protected static enum Padding {
66        NOPADDING,
67        PKCS5PADDING,
68        ISO10126PADDING,
69    }
70
71    /**
72     * Native pointer for the OpenSSL EVP_CIPHER context.
73     */
74    private OpenSSLCipherContext cipherCtx = new OpenSSLCipherContext(
75            NativeCrypto.EVP_CIPHER_CTX_new());
76
77    /**
78     * The current cipher mode.
79     */
80    private Mode mode = Mode.ECB;
81
82    /**
83     * The current cipher padding.
84     */
85    private Padding padding = Padding.PKCS5PADDING;
86
87    /**
88     * The Initial Vector (IV) used for the current cipher.
89     */
90    private byte[] iv;
91
92    /**
93     * Current cipher mode: encrypting or decrypting.
94     */
95    private boolean encrypting;
96
97    /**
98     * The block size of the current cipher.
99     */
100    private int blockSize;
101
102    /**
103     * The block size of the current mode.
104     */
105    private int modeBlockSize;
106
107    /**
108     * Whether the cipher has processed any data yet. OpenSSL doesn't like
109     * calling "doFinal()" in decryption mode without processing any updates.
110     */
111    private boolean calledUpdate;
112
113    protected OpenSSLCipher() {
114    }
115
116    protected OpenSSLCipher(Mode mode, Padding padding) {
117        this.mode = mode;
118        this.padding = padding;
119        blockSize = getCipherBlockSize();
120    }
121
122    /**
123     * Returns the OpenSSL cipher name for the particular {@code keySize} and
124     * cipher {@code mode}.
125     */
126    protected abstract String getCipherName(int keySize, Mode mode);
127
128    /**
129     * Checks whether the cipher supports this particular {@code keySize} (in
130     * bytes) and throws {@code InvalidKeyException} if it doesn't.
131     */
132    protected abstract void checkSupportedKeySize(int keySize) throws InvalidKeyException;
133
134    /**
135     * Checks whether the cipher supports this particular cipher {@code mode}
136     * and throws {@code NoSuchAlgorithmException} if it doesn't.
137     */
138    protected abstract void checkSupportedMode(Mode mode) throws NoSuchAlgorithmException;
139
140    /**
141     * Checks whether the cipher supports this particular cipher {@code padding}
142     * and throws {@code NoSuchPaddingException} if it doesn't.
143     */
144    protected abstract void checkSupportedPadding(Padding padding) throws NoSuchPaddingException;
145
146    protected abstract int getCipherBlockSize();
147
148    @Override
149    protected void engineSetMode(String modeStr) throws NoSuchAlgorithmException {
150        final Mode mode;
151        try {
152            mode = Mode.valueOf(modeStr.toUpperCase(Locale.US));
153        } catch (IllegalArgumentException e) {
154            NoSuchAlgorithmException newE = new NoSuchAlgorithmException("No such mode: "
155                    + modeStr);
156            newE.initCause(e);
157            throw newE;
158        }
159        checkSupportedMode(mode);
160        this.mode = mode;
161    }
162
163    @Override
164    protected void engineSetPadding(String paddingStr) throws NoSuchPaddingException {
165        final String paddingStrUpper = paddingStr.toUpperCase(Locale.US);
166        final Padding padding;
167        try {
168            padding = Padding.valueOf(paddingStrUpper);
169        } catch (IllegalArgumentException e) {
170            NoSuchPaddingException newE = new NoSuchPaddingException("No such padding: "
171                    + paddingStr);
172            newE.initCause(e);
173            throw newE;
174        }
175        checkSupportedPadding(padding);
176        this.padding = padding;
177    }
178
179    @Override
180    protected int engineGetBlockSize() {
181        return blockSize;
182    }
183
184    /**
185     * The size of output if {@code doFinal()} is called with this
186     * {@code inputLen}. If padding is enabled and the size of the input puts it
187     * right at the block size, it will add another block for the padding.
188     */
189    private int getOutputSize(int inputLen) {
190        if (modeBlockSize == 1) {
191            return inputLen;
192        } else {
193            final int buffered = NativeCrypto.get_EVP_CIPHER_CTX_buf_len(cipherCtx.getContext());
194            if (!encrypting || padding == Padding.NOPADDING) {
195                return buffered + inputLen;
196            } else {
197                final int totalLen = inputLen + buffered + modeBlockSize;
198                return totalLen - (totalLen % modeBlockSize);
199            }
200        }
201    }
202
203    @Override
204    protected int engineGetOutputSize(int inputLen) {
205        return getOutputSize(inputLen);
206    }
207
208    @Override
209    protected byte[] engineGetIV() {
210        return iv;
211    }
212
213    @Override
214    protected AlgorithmParameters engineGetParameters() {
215        return null;
216    }
217
218    private void engineInitInternal(int opmode, Key key, byte[] iv) throws InvalidKeyException, InvalidAlgorithmParameterException {
219        if (opmode == Cipher.ENCRYPT_MODE || opmode == Cipher.WRAP_MODE) {
220            encrypting = true;
221        } else if (opmode == Cipher.DECRYPT_MODE || opmode == Cipher.UNWRAP_MODE) {
222            encrypting = false;
223        } else {
224            throw new InvalidParameterException("Unsupported opmode " + opmode);
225        }
226
227        if (!(key instanceof SecretKey)) {
228            throw new InvalidKeyException("Only SecretKey is supported");
229        }
230
231        final byte[] encodedKey = key.getEncoded();
232        if (encodedKey == null) {
233            throw new InvalidKeyException("key.getEncoded() == null");
234        }
235
236        checkSupportedKeySize(encodedKey.length);
237
238        final int cipherType = NativeCrypto.EVP_get_cipherbyname(getCipherName(encodedKey.length,
239                mode));
240        if (cipherType == 0) {
241            throw new InvalidAlgorithmParameterException("Cannot find name for key length = "
242                    + (encodedKey.length * 8) + " and mode = " + mode);
243        }
244
245        final int ivLength = NativeCrypto.EVP_CIPHER_iv_length(cipherType);
246        if (iv == null) {
247            iv = new byte[ivLength];
248        } else if (iv.length != ivLength) {
249            throw new InvalidAlgorithmParameterException("expected IV length of " + ivLength);
250        }
251
252        this.iv = iv;
253
254        NativeCrypto.EVP_CipherInit_ex(cipherCtx.getContext(), cipherType, encodedKey, iv,
255                encrypting);
256
257        // OpenSSL only supports PKCS5 Padding.
258        NativeCrypto.EVP_CIPHER_CTX_set_padding(cipherCtx.getContext(),
259                padding == Padding.PKCS5PADDING);
260        modeBlockSize = NativeCrypto.EVP_CIPHER_CTX_block_size(cipherCtx.getContext());
261        calledUpdate = false;
262    }
263
264    @Override
265    protected void engineInit(int opmode, Key key, SecureRandom random) throws InvalidKeyException {
266        try {
267            engineInitInternal(opmode, key, null);
268        } catch (InvalidAlgorithmParameterException e) {
269            throw new RuntimeException(e);
270        }
271    }
272
273    @Override
274    protected void engineInit(int opmode, Key key, AlgorithmParameterSpec params,
275            SecureRandom random) throws InvalidKeyException, InvalidAlgorithmParameterException {
276        final byte[] iv;
277        if (params instanceof IvParameterSpec) {
278            IvParameterSpec ivParams = (IvParameterSpec) params;
279            iv = ivParams.getIV();
280        } else {
281            iv = null;
282        }
283
284        engineInitInternal(opmode, key, iv);
285    }
286
287    @Override
288    protected void engineInit(int opmode, Key key, AlgorithmParameters params, SecureRandom random)
289            throws InvalidKeyException, InvalidAlgorithmParameterException {
290        final AlgorithmParameterSpec spec;
291        try {
292            spec = params.getParameterSpec(IvParameterSpec.class);
293        } catch (InvalidParameterSpecException e) {
294            throw new InvalidAlgorithmParameterException(e);
295        }
296
297        engineInit(opmode, key, spec, random);
298    }
299
300    private final int updateInternal(byte[] input, int inputOffset, int inputLen, byte[] output,
301            int outputOffset, int maximumLen) throws ShortBufferException {
302        final int intialOutputOffset = outputOffset;
303
304        final int bytesLeft = output.length - outputOffset;
305        if (bytesLeft < maximumLen) {
306            throw new ShortBufferException("output buffer too small during update: " + bytesLeft
307                    + " < " + output.length);
308        }
309
310        outputOffset += NativeCrypto.EVP_CipherUpdate(cipherCtx.getContext(), output, outputOffset,
311                input, inputOffset, inputLen);
312
313        calledUpdate = true;
314
315        return outputOffset - intialOutputOffset;
316    }
317
318    @Override
319    protected byte[] engineUpdate(byte[] input, int inputOffset, int inputLen) {
320        final int maximumLen = getOutputSize(inputLen);
321
322        /* See how large our output buffer would need to be. */
323        final byte[] output;
324        if (maximumLen > 0) {
325            output = new byte[maximumLen];
326        } else {
327            output = EmptyArray.BYTE;
328        }
329
330        final int bytesWritten;
331        try {
332            bytesWritten = updateInternal(input, inputOffset, inputLen, output, 0, maximumLen);
333        } catch (ShortBufferException e) {
334            /* This shouldn't happen. */
335            throw new RuntimeException("calculated buffer size was wrong: " + maximumLen);
336        }
337
338        if (output.length == bytesWritten) {
339            return output;
340        } else if (bytesWritten == 0) {
341            return EmptyArray.BYTE;
342        } else {
343            return Arrays.copyOfRange(output, 0, bytesWritten);
344        }
345    }
346
347    @Override
348    protected int engineUpdate(byte[] input, int inputOffset, int inputLen, byte[] output,
349            int outputOffset) throws ShortBufferException {
350        final int maximumLen = getOutputSize(inputLen);
351        return updateInternal(input, inputOffset, inputLen, output, outputOffset, maximumLen);
352    }
353
354    /**
355     * Reset this Cipher instance state to process a new chunk of data.
356     */
357    private void reset() {
358        NativeCrypto.EVP_CipherInit_ex(cipherCtx.getContext(), 0, null, null, encrypting);
359        calledUpdate = false;
360    }
361
362    private int doFinalInternal(byte[] input, int inputOffset, int inputLen, byte[] output,
363            int outputOffset, int maximumLen) throws IllegalBlockSizeException,
364            BadPaddingException, ShortBufferException {
365        /* Remember this so we can tell how many characters were written. */
366        final int initialOutputOffset = outputOffset;
367
368        if (inputLen > 0) {
369            final int updateBytesWritten = updateInternal(input, inputOffset, inputLen, output,
370                    outputOffset, maximumLen);
371            outputOffset += updateBytesWritten;
372            maximumLen -= updateBytesWritten;
373        }
374
375        /*
376         * If we're decrypting and haven't had any input, we should return null.
377         * Otherwise OpenSSL will complain if we call final.
378         */
379        if (!encrypting && !calledUpdate) {
380            return 0;
381        }
382
383        /* Allow OpenSSL to pad if necessary and clean up state. */
384        final int bytesLeft = output.length - outputOffset;
385        final int writtenBytes;
386        if (bytesLeft >= maximumLen) {
387            writtenBytes = NativeCrypto.EVP_CipherFinal_ex(cipherCtx.getContext(), output,
388                    outputOffset);
389        } else {
390            final byte[] lastBlock = new byte[maximumLen];
391            writtenBytes = NativeCrypto.EVP_CipherFinal_ex(cipherCtx.getContext(), lastBlock, 0);
392            if (writtenBytes > bytesLeft) {
393                throw new ShortBufferException("buffer is too short: " + writtenBytes + " > "
394                        + bytesLeft);
395            } else if (writtenBytes > 0) {
396                System.arraycopy(lastBlock, 0, output, outputOffset, writtenBytes);
397            }
398        }
399        outputOffset += writtenBytes;
400
401        reset();
402
403        return outputOffset - initialOutputOffset;
404    }
405
406    @Override
407    protected byte[] engineDoFinal(byte[] input, int inputOffset, int inputLen)
408            throws IllegalBlockSizeException, BadPaddingException {
409        /*
410         * Other implementations return null if we've never called update()
411         * while decrypting.
412         */
413        if (!encrypting && !calledUpdate && inputLen == 0) {
414            reset();
415            return null;
416        }
417
418        final int maximumLen = getOutputSize(inputLen);
419        /* Assume that we'll output exactly on a byte boundary. */
420        byte[] output = new byte[maximumLen];
421        final int bytesWritten;
422        try {
423            bytesWritten = doFinalInternal(input, inputOffset, inputLen, output, 0, maximumLen);
424        } catch (ShortBufferException e) {
425            /* This should not happen since we sized our own buffer. */
426            throw new RuntimeException("our calculated buffer was too small", e);
427        }
428
429        if (bytesWritten == output.length) {
430            return output;
431        } else if (bytesWritten == 0) {
432            return EmptyArray.BYTE;
433        } else {
434            return Arrays.copyOfRange(output, 0, bytesWritten);
435        }
436    }
437
438    @Override
439    protected int engineDoFinal(byte[] input, int inputOffset, int inputLen, byte[] output,
440            int outputOffset) throws ShortBufferException, IllegalBlockSizeException,
441            BadPaddingException {
442        if (output == null) {
443            throw new NullPointerException("output == null");
444        }
445
446        final int maximumLen = getOutputSize(inputLen);
447        return doFinalInternal(input, inputOffset, inputLen, output, outputOffset, maximumLen);
448    }
449
450    @Override
451    protected byte[] engineWrap(Key key) throws IllegalBlockSizeException, InvalidKeyException {
452        try {
453            byte[] encoded = key.getEncoded();
454            return engineDoFinal(encoded, 0, encoded.length);
455        } catch (BadPaddingException e) {
456            IllegalBlockSizeException newE = new IllegalBlockSizeException();
457            newE.initCause(e);
458            throw newE;
459        }
460    }
461
462    @Override
463    protected Key engineUnwrap(byte[] wrappedKey, String wrappedKeyAlgorithm, int wrappedKeyType)
464            throws InvalidKeyException, NoSuchAlgorithmException {
465        try {
466            byte[] encoded = engineDoFinal(wrappedKey, 0, wrappedKey.length);
467            if (wrappedKeyType == Cipher.PUBLIC_KEY) {
468                KeyFactory keyFactory = KeyFactory.getInstance(wrappedKeyAlgorithm);
469                return keyFactory.generatePublic(new X509EncodedKeySpec(encoded));
470            } else if (wrappedKeyType == Cipher.PRIVATE_KEY) {
471                KeyFactory keyFactory = KeyFactory.getInstance(wrappedKeyAlgorithm);
472                return keyFactory.generatePrivate(new PKCS8EncodedKeySpec(encoded));
473            } else if (wrappedKeyType == Cipher.SECRET_KEY) {
474                return new SecretKeySpec(encoded, wrappedKeyAlgorithm);
475            } else {
476                throw new UnsupportedOperationException("wrappedKeyType == " + wrappedKeyType);
477            }
478        } catch (IllegalBlockSizeException e) {
479            throw new InvalidKeyException(e);
480        } catch (BadPaddingException e) {
481            throw new InvalidKeyException(e);
482        } catch (InvalidKeySpecException e) {
483            throw new InvalidKeyException(e);
484        }
485    }
486
487    public static class AES extends OpenSSLCipher {
488        private static final int AES_BLOCK_SIZE = 16;
489
490        protected AES(Mode mode, Padding padding) {
491            super(mode, padding);
492        }
493
494        public static class CBC extends AES {
495            public CBC(Padding padding) {
496                super(Mode.CBC, padding);
497            }
498
499            public static class NoPadding extends CBC {
500                public NoPadding() {
501                    super(Padding.NOPADDING);
502                }
503            }
504
505            public static class PKCS5Padding extends CBC {
506                public PKCS5Padding() {
507                    super(Padding.PKCS5PADDING);
508                }
509            }
510        }
511
512        public static class CFB extends AES {
513            public CFB(Padding padding) {
514                super(Mode.CFB, padding);
515            }
516
517            public static class NoPadding extends CFB {
518                public NoPadding() {
519                    super(Padding.NOPADDING);
520                }
521            }
522
523            public static class PKCS5Padding extends CFB {
524                public PKCS5Padding() {
525                    super(Padding.PKCS5PADDING);
526                }
527            }
528        }
529
530        public static class CTR extends AES {
531            public CTR(Padding padding) {
532                super(Mode.CTR, padding);
533            }
534
535            public static class NoPadding extends CTR {
536                public NoPadding() {
537                    super(Padding.NOPADDING);
538                }
539            }
540
541            public static class PKCS5Padding extends CTR {
542                public PKCS5Padding() {
543                    super(Padding.PKCS5PADDING);
544                }
545            }
546        }
547
548        public static class ECB extends AES {
549            public ECB(Padding padding) {
550                super(Mode.ECB, padding);
551            }
552
553            public static class NoPadding extends ECB {
554                public NoPadding() {
555                    super(Padding.NOPADDING);
556                }
557            }
558
559            public static class PKCS5Padding extends ECB {
560                public PKCS5Padding() {
561                    super(Padding.PKCS5PADDING);
562                }
563            }
564        }
565
566        public static class OFB extends AES {
567            public OFB(Padding padding) {
568                super(Mode.OFB, padding);
569            }
570
571            public static class NoPadding extends OFB {
572                public NoPadding() {
573                    super(Padding.NOPADDING);
574                }
575            }
576
577            public static class PKCS5Padding extends OFB {
578                public PKCS5Padding() {
579                    super(Padding.PKCS5PADDING);
580                }
581            }
582        }
583
584        @Override
585        protected void checkSupportedKeySize(int keyLength) throws InvalidKeyException {
586            switch (keyLength) {
587                case 16: // AES 128
588                case 24: // AES 192
589                case 32: // AES 256
590                    return;
591                default:
592                    throw new InvalidKeyException("Unsupported key size: " + keyLength + " bytes");
593            }
594        }
595
596        @Override
597        protected void checkSupportedMode(Mode mode) throws NoSuchAlgorithmException {
598            switch (mode) {
599                case CBC:
600                case CFB:
601                case CFB1:
602                case CFB8:
603                case CFB128:
604                case CTR:
605                case ECB:
606                case OFB:
607                    return;
608                default:
609                    throw new NoSuchAlgorithmException("Unsupported mode " + mode.toString());
610            }
611        }
612
613        @Override
614        protected void checkSupportedPadding(Padding padding) throws NoSuchPaddingException {
615            switch (padding) {
616                case NOPADDING:
617                case PKCS5PADDING:
618                    return;
619                default:
620                    throw new NoSuchPaddingException("Unsupported padding " + padding.toString());
621            }
622        }
623
624        @Override
625        protected String getCipherName(int keyLength, Mode mode) {
626            return "aes-" + (keyLength * 8) + "-" + mode.toString().toLowerCase(Locale.US);
627        }
628
629        @Override
630        protected int getCipherBlockSize() {
631            return AES_BLOCK_SIZE;
632        }
633    }
634
635    public static class DESEDE extends OpenSSLCipher {
636        private static int DES_BLOCK_SIZE = 8;
637
638        public DESEDE(Mode mode, Padding padding) {
639            super(mode, padding);
640        }
641
642        public static class CBC extends DESEDE {
643            public CBC(Padding padding) {
644                super(Mode.CBC, padding);
645            }
646
647            public static class NoPadding extends CBC {
648                public NoPadding() {
649                    super(Padding.NOPADDING);
650                }
651            }
652
653            public static class PKCS5Padding extends CBC {
654                public PKCS5Padding() {
655                    super(Padding.PKCS5PADDING);
656                }
657            }
658        }
659
660        public static class CFB extends DESEDE {
661            public CFB(Padding padding) {
662                super(Mode.CFB, padding);
663            }
664
665            public static class NoPadding extends CFB {
666                public NoPadding() {
667                    super(Padding.NOPADDING);
668                }
669            }
670
671            public static class PKCS5Padding extends CFB {
672                public PKCS5Padding() {
673                    super(Padding.PKCS5PADDING);
674                }
675            }
676        }
677
678        public static class ECB extends DESEDE {
679            public ECB(Padding padding) {
680                super(Mode.ECB, padding);
681            }
682
683            public static class NoPadding extends ECB {
684                public NoPadding() {
685                    super(Padding.NOPADDING);
686                }
687            }
688
689            public static class PKCS5Padding extends ECB {
690                public PKCS5Padding() {
691                    super(Padding.PKCS5PADDING);
692                }
693            }
694        }
695
696        public static class OFB extends DESEDE {
697            public OFB(Padding padding) {
698                super(Mode.OFB, padding);
699            }
700
701            public static class NoPadding extends OFB {
702                public NoPadding() {
703                    super(Padding.NOPADDING);
704                }
705            }
706
707            public static class PKCS5Padding extends OFB {
708                public PKCS5Padding() {
709                    super(Padding.PKCS5PADDING);
710                }
711            }
712        }
713
714        @Override
715        protected String getCipherName(int keySize, Mode mode) {
716            final String baseCipherName;
717            if (keySize == 16) {
718                baseCipherName = "des-ede";
719            } else {
720                baseCipherName = "des-ede3";
721            }
722
723            if (mode == Mode.ECB) {
724                return baseCipherName;
725            } else {
726                return baseCipherName + "-" + mode.toString().toLowerCase(Locale.US);
727            }
728        }
729
730        @Override
731        protected void checkSupportedKeySize(int keySize) throws InvalidKeyException {
732            if (keySize != 16 && keySize != 24) {
733                throw new InvalidKeyException("key size must be 128 or 192 bits");
734            }
735        }
736
737        @Override
738        protected void checkSupportedMode(Mode mode) throws NoSuchAlgorithmException {
739            switch (mode) {
740                case CBC:
741                case CFB:
742                case CFB1:
743                case CFB8:
744                case ECB:
745                case OFB:
746                    return;
747                default:
748                    throw new NoSuchAlgorithmException("Unsupported mode " + mode.toString());
749            }
750        }
751
752        @Override
753        protected void checkSupportedPadding(Padding padding) throws NoSuchPaddingException {
754            switch (padding) {
755                case NOPADDING:
756                case PKCS5PADDING:
757                    return;
758                default:
759                    throw new NoSuchPaddingException("Unsupported padding " + padding.toString());
760            }
761        }
762
763        @Override
764        protected int getCipherBlockSize() {
765            return DES_BLOCK_SIZE;
766        }
767    }
768}
769