OpenSSLCipher.java revision fac659c013ec9c2783f60afce39e83eb107f117d
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 final int getFinalOutputSize(int inputLen) {
190        if (modeBlockSize == 1) {
191            return getUpdateOutputSize(inputLen);
192        } else {
193            return getUpdateOutputSize(inputLen) + modeBlockSize;
194        }
195    }
196
197    @Override
198    protected int engineGetOutputSize(int inputLen) {
199        return getFinalOutputSize(inputLen);
200    }
201
202    @Override
203    protected byte[] engineGetIV() {
204        return iv;
205    }
206
207    @Override
208    protected AlgorithmParameters engineGetParameters() {
209        return null;
210    }
211
212    private void engineInitInternal(int opmode, Key key, byte[] iv) throws InvalidKeyException, InvalidAlgorithmParameterException {
213        if (opmode == Cipher.ENCRYPT_MODE || opmode == Cipher.WRAP_MODE) {
214            encrypting = true;
215        } else if (opmode == Cipher.DECRYPT_MODE || opmode == Cipher.UNWRAP_MODE) {
216            encrypting = false;
217        } else {
218            throw new InvalidParameterException("Unsupported opmode " + opmode);
219        }
220
221        if (!(key instanceof SecretKey)) {
222            throw new InvalidKeyException("Only SecretKey is supported");
223        }
224
225        final byte[] encodedKey = key.getEncoded();
226        if (encodedKey == null) {
227            throw new InvalidKeyException("key.getEncoded() == null");
228        }
229
230        checkSupportedKeySize(encodedKey.length);
231
232        final int cipherType = NativeCrypto.EVP_get_cipherbyname(getCipherName(encodedKey.length,
233                mode));
234        if (cipherType == 0) {
235            throw new InvalidAlgorithmParameterException("Cannot find name for key length = "
236                    + (encodedKey.length * 8) + " and mode = " + mode);
237        }
238
239        final int ivLength = NativeCrypto.EVP_CIPHER_iv_length(cipherType);
240        if (iv == null) {
241            iv = new byte[ivLength];
242        } else if (iv.length != ivLength) {
243            throw new InvalidAlgorithmParameterException("expected IV length of " + ivLength);
244        }
245
246        this.iv = iv;
247
248        NativeCrypto.EVP_CipherInit_ex(cipherCtx.getContext(), cipherType, encodedKey, iv,
249                encrypting);
250
251        // OpenSSL only supports PKCS5 Padding.
252        NativeCrypto.EVP_CIPHER_CTX_set_padding(cipherCtx.getContext(),
253                padding == Padding.PKCS5PADDING);
254        modeBlockSize = NativeCrypto.EVP_CIPHER_CTX_block_size(cipherCtx.getContext());
255        calledUpdate = false;
256    }
257
258    @Override
259    protected void engineInit(int opmode, Key key, SecureRandom random) throws InvalidKeyException {
260        try {
261            engineInitInternal(opmode, key, null);
262        } catch (InvalidAlgorithmParameterException e) {
263            throw new RuntimeException(e);
264        }
265    }
266
267    @Override
268    protected void engineInit(int opmode, Key key, AlgorithmParameterSpec params,
269            SecureRandom random) throws InvalidKeyException, InvalidAlgorithmParameterException {
270        final byte[] iv;
271        if (params instanceof IvParameterSpec) {
272            IvParameterSpec ivParams = (IvParameterSpec) params;
273            iv = ivParams.getIV();
274        } else {
275            iv = null;
276        }
277
278        engineInitInternal(opmode, key, iv);
279    }
280
281    @Override
282    protected void engineInit(int opmode, Key key, AlgorithmParameters params, SecureRandom random)
283            throws InvalidKeyException, InvalidAlgorithmParameterException {
284        final AlgorithmParameterSpec spec;
285        try {
286            spec = params.getParameterSpec(IvParameterSpec.class);
287        } catch (InvalidParameterSpecException e) {
288            throw new InvalidAlgorithmParameterException(e);
289        }
290
291        engineInit(opmode, key, spec, random);
292    }
293
294    private final int updateInternal(byte[] input, int inputOffset, int inputLen, byte[] output,
295            int outputOffset) throws ShortBufferException {
296        final int intialOutputOffset = outputOffset;
297
298        final int bytesLeft = output.length - outputOffset;
299        if (bytesLeft < getUpdateOutputSize(inputLen)) {
300            throw new ShortBufferException("output buffer too small during update: " + bytesLeft
301                    + " < " + output.length);
302        }
303
304        outputOffset += NativeCrypto.EVP_CipherUpdate(cipherCtx.getContext(), output, outputOffset,
305                input, inputOffset, inputLen);
306
307        calledUpdate = true;
308
309        return outputOffset - intialOutputOffset;
310    }
311
312    private int getUpdateOutputSize(int inputLen) {
313        if (encrypting) {
314            return inputLen + modeBlockSize - 1;
315        } else if (modeBlockSize == 1) {
316            return inputLen;
317        } else {
318            return inputLen + modeBlockSize;
319        }
320    }
321
322    @Override
323    protected byte[] engineUpdate(byte[] input, int inputOffset, int inputLen) {
324        final int maximumLen = getUpdateOutputSize(inputLen);
325
326        /* See how large our output buffer would need to be. */
327        final byte[] output;
328        if (maximumLen > 0) {
329            output = new byte[maximumLen];
330        } else {
331            output = EmptyArray.BYTE;
332        }
333
334        final int bytesWritten;
335        try {
336            bytesWritten = updateInternal(input, inputOffset, inputLen, output, 0);
337        } catch (ShortBufferException e) {
338            /* This shouldn't happen. */
339            throw new RuntimeException("calculated buffer size was wrong: " + maximumLen);
340        }
341
342        if (output.length == bytesWritten) {
343            return output;
344        } else if (bytesWritten == 0) {
345            return EmptyArray.BYTE;
346        } else {
347            return Arrays.copyOfRange(output, 0, bytesWritten);
348        }
349    }
350
351    @Override
352    protected int engineUpdate(byte[] input, int inputOffset, int inputLen, byte[] output,
353            int outputOffset) throws ShortBufferException {
354        return updateInternal(input, inputOffset, inputLen, output, outputOffset);
355    }
356
357    /**
358     * Reset this Cipher instance state to process a new chunk of data.
359     */
360    private void reset() {
361        NativeCrypto.EVP_CipherInit_ex(cipherCtx.getContext(), 0, null, null, encrypting);
362        calledUpdate = false;
363    }
364
365    private int doFinalInternal(byte[] input, int inputOffset, int inputLen, byte[] output,
366            int outputOffset) throws IllegalBlockSizeException, BadPaddingException,
367            ShortBufferException {
368        /* Remember this so we can tell how many characters were written. */
369        final int initialOutputOffset = outputOffset;
370
371        if (inputLen > 0) {
372            final int updateBytesWritten = updateInternal(input, inputOffset, inputLen, output,
373                    outputOffset);
374            outputOffset += updateBytesWritten;
375        }
376
377        /*
378         * If we're decrypting and haven't had any input, we should return null.
379         * Otherwise OpenSSL will complain if we call final.
380         */
381        if (!encrypting && !calledUpdate) {
382            return 0;
383        }
384
385        /* Allow OpenSSL to pad if necessary and clean up state. */
386        final int bytesLeft = output.length - outputOffset;
387        final int writtenBytes;
388        if (bytesLeft >= blockSize) {
389            writtenBytes = NativeCrypto.EVP_CipherFinal_ex(cipherCtx.getContext(), output,
390                    outputOffset);
391        } else {
392            final byte[] lastBlock = new byte[modeBlockSize];
393            writtenBytes = NativeCrypto.EVP_CipherFinal_ex(cipherCtx.getContext(), lastBlock, 0);
394            if (writtenBytes > bytesLeft) {
395                throw new ShortBufferException("buffer is too short: " + writtenBytes + " > "
396                        + bytesLeft);
397            } else if (writtenBytes > 0) {
398                System.arraycopy(lastBlock, 0, output, outputOffset, writtenBytes);
399            }
400        }
401        outputOffset += writtenBytes;
402
403        reset();
404
405        return outputOffset - initialOutputOffset;
406    }
407
408    @Override
409    protected byte[] engineDoFinal(byte[] input, int inputOffset, int inputLen)
410            throws IllegalBlockSizeException, BadPaddingException {
411        /*
412         * Other implementations return null if we've never called update()
413         * while decrypting.
414         */
415        if (!encrypting && !calledUpdate && inputLen == 0) {
416            reset();
417            return null;
418        }
419
420        final int maximumSize = getFinalOutputSize(inputLen);
421        /* Assume that we'll output exactly on a byte boundary. */
422        byte[] output = new byte[maximumSize];
423        final int bytesWritten;
424        try {
425            bytesWritten = doFinalInternal(input, inputOffset, inputLen, output, 0);
426        } catch (ShortBufferException e) {
427            /* This should not happen since we sized our own buffer. */
428            throw new RuntimeException("our calculated buffer was too small", e);
429        }
430
431        if (bytesWritten == output.length) {
432            return output;
433        } else if (bytesWritten == 0) {
434            return EmptyArray.BYTE;
435        } else {
436            return Arrays.copyOfRange(output, 0, bytesWritten);
437        }
438    }
439
440    @Override
441    protected int engineDoFinal(byte[] input, int inputOffset, int inputLen, byte[] output,
442            int outputOffset) throws ShortBufferException, IllegalBlockSizeException,
443            BadPaddingException {
444        if (output == null) {
445            throw new NullPointerException("output == null");
446        }
447
448        return doFinalInternal(input, inputOffset, inputLen, output, outputOffset);
449    }
450
451    @Override
452    protected byte[] engineWrap(Key key) throws IllegalBlockSizeException, InvalidKeyException {
453        try {
454            byte[] encoded = key.getEncoded();
455            return engineDoFinal(encoded, 0, encoded.length);
456        } catch (BadPaddingException e) {
457            IllegalBlockSizeException newE = new IllegalBlockSizeException();
458            newE.initCause(e);
459            throw newE;
460        }
461    }
462
463    @Override
464    protected Key engineUnwrap(byte[] wrappedKey, String wrappedKeyAlgorithm, int wrappedKeyType)
465            throws InvalidKeyException, NoSuchAlgorithmException {
466        try {
467            byte[] encoded = engineDoFinal(wrappedKey, 0, wrappedKey.length);
468            if (wrappedKeyType == Cipher.PUBLIC_KEY) {
469                KeyFactory keyFactory = KeyFactory.getInstance(wrappedKeyAlgorithm);
470                return keyFactory.generatePublic(new X509EncodedKeySpec(encoded));
471            } else if (wrappedKeyType == Cipher.PRIVATE_KEY) {
472                KeyFactory keyFactory = KeyFactory.getInstance(wrappedKeyAlgorithm);
473                return keyFactory.generatePrivate(new PKCS8EncodedKeySpec(encoded));
474            } else if (wrappedKeyType == Cipher.SECRET_KEY) {
475                return new SecretKeySpec(encoded, wrappedKeyAlgorithm);
476            } else {
477                throw new UnsupportedOperationException("wrappedKeyType == " + wrappedKeyType);
478            }
479        } catch (IllegalBlockSizeException e) {
480            throw new InvalidKeyException(e);
481        } catch (BadPaddingException e) {
482            throw new InvalidKeyException(e);
483        } catch (InvalidKeySpecException e) {
484            throw new InvalidKeyException(e);
485        }
486    }
487
488    public static class AES extends OpenSSLCipher {
489        private static final int AES_BLOCK_SIZE = 16;
490
491        protected AES(Mode mode, Padding padding) {
492            super(mode, padding);
493        }
494
495        public static class CBC extends AES {
496            public CBC(Padding padding) {
497                super(Mode.CBC, padding);
498            }
499
500            public static class NoPadding extends CBC {
501                public NoPadding() {
502                    super(Padding.NOPADDING);
503                }
504            }
505
506            public static class PKCS5Padding extends CBC {
507                public PKCS5Padding() {
508                    super(Padding.PKCS5PADDING);
509                }
510            }
511        }
512
513        public static class CFB extends AES {
514            public CFB(Padding padding) {
515                super(Mode.CFB, padding);
516            }
517
518            public static class NoPadding extends CFB {
519                public NoPadding() {
520                    super(Padding.NOPADDING);
521                }
522            }
523
524            public static class PKCS5Padding extends CFB {
525                public PKCS5Padding() {
526                    super(Padding.PKCS5PADDING);
527                }
528            }
529        }
530
531        public static class CTR extends AES {
532            public CTR(Padding padding) {
533                super(Mode.CTR, padding);
534            }
535
536            public static class NoPadding extends CTR {
537                public NoPadding() {
538                    super(Padding.NOPADDING);
539                }
540            }
541
542            public static class PKCS5Padding extends CTR {
543                public PKCS5Padding() {
544                    super(Padding.PKCS5PADDING);
545                }
546            }
547        }
548
549        public static class ECB extends AES {
550            public ECB(Padding padding) {
551                super(Mode.ECB, padding);
552            }
553
554            public static class NoPadding extends ECB {
555                public NoPadding() {
556                    super(Padding.NOPADDING);
557                }
558            }
559
560            public static class PKCS5Padding extends ECB {
561                public PKCS5Padding() {
562                    super(Padding.PKCS5PADDING);
563                }
564            }
565        }
566
567        public static class OFB extends AES {
568            public OFB(Padding padding) {
569                super(Mode.OFB, padding);
570            }
571
572            public static class NoPadding extends OFB {
573                public NoPadding() {
574                    super(Padding.NOPADDING);
575                }
576            }
577
578            public static class PKCS5Padding extends OFB {
579                public PKCS5Padding() {
580                    super(Padding.PKCS5PADDING);
581                }
582            }
583        }
584
585        @Override
586        protected void checkSupportedKeySize(int keyLength) throws InvalidKeyException {
587            switch (keyLength) {
588                case 16: // AES 128
589                case 24: // AES 192
590                case 32: // AES 256
591                    return;
592                default:
593                    throw new InvalidKeyException("Unsupported key size: " + keyLength + " bytes");
594            }
595        }
596
597        @Override
598        protected void checkSupportedMode(Mode mode) throws NoSuchAlgorithmException {
599            switch (mode) {
600                case CBC:
601                case CFB:
602                case CFB1:
603                case CFB8:
604                case CFB128:
605                case CTR:
606                case ECB:
607                case OFB:
608                    return;
609                default:
610                    throw new NoSuchAlgorithmException("Unsupported mode " + mode.toString());
611            }
612        }
613
614        @Override
615        protected void checkSupportedPadding(Padding padding) throws NoSuchPaddingException {
616            switch (padding) {
617                case NOPADDING:
618                case PKCS5PADDING:
619                    return;
620                default:
621                    throw new NoSuchPaddingException("Unsupported padding " + padding.toString());
622            }
623        }
624
625        @Override
626        protected String getCipherName(int keyLength, Mode mode) {
627            return "aes-" + (keyLength * 8) + "-" + mode.toString().toLowerCase(Locale.US);
628        }
629
630        @Override
631        protected int getCipherBlockSize() {
632            return AES_BLOCK_SIZE;
633        }
634    }
635
636    public static class DESEDE extends OpenSSLCipher {
637        private static int DES_BLOCK_SIZE = 8;
638
639        public DESEDE(Mode mode, Padding padding) {
640            super(mode, padding);
641        }
642
643        public static class CBC extends DESEDE {
644            public CBC(Padding padding) {
645                super(Mode.CBC, padding);
646            }
647
648            public static class NoPadding extends CBC {
649                public NoPadding() {
650                    super(Padding.NOPADDING);
651                }
652            }
653
654            public static class PKCS5Padding extends CBC {
655                public PKCS5Padding() {
656                    super(Padding.PKCS5PADDING);
657                }
658            }
659        }
660
661        public static class CFB extends DESEDE {
662            public CFB(Padding padding) {
663                super(Mode.CFB, padding);
664            }
665
666            public static class NoPadding extends CFB {
667                public NoPadding() {
668                    super(Padding.NOPADDING);
669                }
670            }
671
672            public static class PKCS5Padding extends CFB {
673                public PKCS5Padding() {
674                    super(Padding.PKCS5PADDING);
675                }
676            }
677        }
678
679        public static class ECB extends DESEDE {
680            public ECB(Padding padding) {
681                super(Mode.ECB, padding);
682            }
683
684            public static class NoPadding extends ECB {
685                public NoPadding() {
686                    super(Padding.NOPADDING);
687                }
688            }
689
690            public static class PKCS5Padding extends ECB {
691                public PKCS5Padding() {
692                    super(Padding.PKCS5PADDING);
693                }
694            }
695        }
696
697        public static class OFB extends DESEDE {
698            public OFB(Padding padding) {
699                super(Mode.OFB, padding);
700            }
701
702            public static class NoPadding extends OFB {
703                public NoPadding() {
704                    super(Padding.NOPADDING);
705                }
706            }
707
708            public static class PKCS5Padding extends OFB {
709                public PKCS5Padding() {
710                    super(Padding.PKCS5PADDING);
711                }
712            }
713        }
714
715        @Override
716        protected String getCipherName(int keySize, Mode mode) {
717            final String baseCipherName;
718            if (keySize == 16) {
719                baseCipherName = "des-ede";
720            } else {
721                baseCipherName = "des-ede3";
722            }
723
724            if (mode == Mode.ECB) {
725                return baseCipherName;
726            } else {
727                return baseCipherName + "-" + mode.toString().toLowerCase(Locale.US);
728            }
729        }
730
731        @Override
732        protected void checkSupportedKeySize(int keySize) throws InvalidKeyException {
733            if (keySize != 16 && keySize != 24) {
734                throw new InvalidKeyException("key size must be 128 or 192 bits");
735            }
736        }
737
738        @Override
739        protected void checkSupportedMode(Mode mode) throws NoSuchAlgorithmException {
740            switch (mode) {
741                case CBC:
742                case CFB:
743                case CFB1:
744                case CFB8:
745                case ECB:
746                case OFB:
747                    return;
748                default:
749                    throw new NoSuchAlgorithmException("Unsupported mode " + mode.toString());
750            }
751        }
752
753        @Override
754        protected void checkSupportedPadding(Padding padding) throws NoSuchPaddingException {
755            switch (padding) {
756                case NOPADDING:
757                case PKCS5PADDING:
758                    return;
759                default:
760                    throw new NoSuchPaddingException("Unsupported padding " + padding.toString());
761            }
762        }
763
764        @Override
765        protected int getCipherBlockSize() {
766            return DES_BLOCK_SIZE;
767        }
768    }
769}
770