OpenSSLCipher.java revision 13cf08b2f06e1f5f0278c449072898f5e147db49
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     * Buffer to hold a block-sized entry before calling into OpenSSL.
109     */
110    private byte[] buffer;
111
112    /**
113     * Current offset in the buffer.
114     */
115    private int bufferOffset;
116
117    protected OpenSSLCipher() {
118    }
119
120    protected OpenSSLCipher(Mode mode, Padding padding) {
121        this.mode = mode;
122        this.padding = padding;
123        blockSize = getCipherBlockSize();
124    }
125
126    /**
127     * Returns the OpenSSL cipher name for the particular {@code keySize} and
128     * cipher {@code mode}.
129     */
130    protected abstract String getCipherName(int keySize, Mode mode);
131
132    /**
133     * Checks whether the cipher supports this particular {@code keySize} (in
134     * bytes) and throws {@code InvalidKeyException} if it doesn't.
135     */
136    protected abstract void checkSupportedKeySize(int keySize) throws InvalidKeyException;
137
138    /**
139     * Checks whether the cipher supports this particular cipher {@code mode}
140     * and throws {@code NoSuchAlgorithmException} if it doesn't.
141     */
142    protected abstract void checkSupportedMode(Mode mode) throws NoSuchAlgorithmException;
143
144    /**
145     * Checks whether the cipher supports this particular cipher {@code padding}
146     * and throws {@code NoSuchPaddingException} if it doesn't.
147     */
148    protected abstract void checkSupportedPadding(Padding padding) throws NoSuchPaddingException;
149
150    protected abstract int getCipherBlockSize();
151
152    @Override
153    protected void engineSetMode(String modeStr) throws NoSuchAlgorithmException {
154        final Mode mode;
155        try {
156            mode = Mode.valueOf(modeStr.toUpperCase(Locale.US));
157        } catch (IllegalArgumentException e) {
158            NoSuchAlgorithmException newE = new NoSuchAlgorithmException("No such mode: "
159                    + modeStr);
160            newE.initCause(e);
161            throw newE;
162        }
163        checkSupportedMode(mode);
164        this.mode = mode;
165    }
166
167    @Override
168    protected void engineSetPadding(String paddingStr) throws NoSuchPaddingException {
169        final String paddingStrUpper = paddingStr.toUpperCase(Locale.US);
170        final Padding padding;
171        try {
172            padding = Padding.valueOf(paddingStrUpper);
173        } catch (IllegalArgumentException e) {
174            NoSuchPaddingException newE = new NoSuchPaddingException("No such padding: "
175                    + paddingStr);
176            newE.initCause(e);
177            throw newE;
178        }
179        checkSupportedPadding(padding);
180        this.padding = padding;
181    }
182
183    @Override
184    protected int engineGetBlockSize() {
185        return blockSize;
186    }
187
188    /**
189     * The size of output if {@code doFinal()} is called with this
190     * {@code inputLen}. If padding is enabled and the size of the input puts it
191     * right at the block size, it will add another block for the padding.
192     */
193    private final int getFinalOutputSize(int inputLen) {
194        final int totalLen = bufferOffset + inputLen;
195        final int overrunLen = totalLen % blockSize;
196
197        if (overrunLen == 0) {
198            if ((padding == Padding.NOPADDING) && (totalLen > 0)) {
199                return totalLen;
200            } else {
201                return totalLen + blockSize;
202            }
203        } else {
204            return totalLen - overrunLen + blockSize;
205        }
206    }
207
208    @Override
209    protected int engineGetOutputSize(int inputLen) {
210        return getFinalOutputSize(inputLen);
211    }
212
213    @Override
214    protected byte[] engineGetIV() {
215        return iv;
216    }
217
218    @Override
219    protected AlgorithmParameters engineGetParameters() {
220        return null;
221    }
222
223    private void engineInitInternal(int opmode, Key key, byte[] iv) throws InvalidKeyException, InvalidAlgorithmParameterException {
224        if (opmode == Cipher.ENCRYPT_MODE || opmode == Cipher.WRAP_MODE) {
225            encrypting = true;
226        } else if (opmode == Cipher.DECRYPT_MODE || opmode == Cipher.UNWRAP_MODE) {
227            encrypting = false;
228        } else {
229            throw new InvalidParameterException("Unsupported opmode " + opmode);
230        }
231
232        if (!(key instanceof SecretKey)) {
233            throw new InvalidKeyException("Only SecretKey is supported");
234        }
235
236        final byte[] encodedKey = key.getEncoded();
237        if (encodedKey == null) {
238            throw new InvalidKeyException("key.getEncoded() == null");
239        }
240
241        checkSupportedKeySize(encodedKey.length);
242
243        final int cipherType = NativeCrypto.EVP_get_cipherbyname(getCipherName(encodedKey.length,
244                mode));
245        if (cipherType == 0) {
246            throw new InvalidAlgorithmParameterException("Cannot find name for key length = "
247                    + (encodedKey.length * 8) + " and mode = " + mode);
248        }
249
250        final int ivLength = NativeCrypto.EVP_CIPHER_iv_length(cipherType);
251        if (iv == null) {
252            iv = new byte[ivLength];
253        } else if (iv.length != ivLength) {
254            throw new InvalidAlgorithmParameterException("expected IV length of " + ivLength);
255        }
256
257        this.iv = iv;
258
259        NativeCrypto.EVP_CipherInit_ex(cipherCtx.getContext(), cipherType, encodedKey, iv,
260                encrypting);
261
262        // OpenSSL only supports PKCS5 Padding.
263        NativeCrypto.EVP_CIPHER_CTX_set_padding(cipherCtx.getContext(),
264                padding == Padding.PKCS5PADDING);
265        modeBlockSize = NativeCrypto.EVP_CIPHER_CTX_block_size(cipherCtx.getContext());
266
267        buffer = new byte[blockSize];
268        bufferOffset = 0;
269    }
270
271    @Override
272    protected void engineInit(int opmode, Key key, SecureRandom random) throws InvalidKeyException {
273        try {
274            engineInitInternal(opmode, key, null);
275        } catch (InvalidAlgorithmParameterException e) {
276            throw new RuntimeException(e);
277        }
278    }
279
280    @Override
281    protected void engineInit(int opmode, Key key, AlgorithmParameterSpec params,
282            SecureRandom random) throws InvalidKeyException, InvalidAlgorithmParameterException {
283        final byte[] iv;
284        if (params instanceof IvParameterSpec) {
285            IvParameterSpec ivParams = (IvParameterSpec) params;
286            iv = ivParams.getIV();
287        } else {
288            iv = null;
289        }
290
291        engineInitInternal(opmode, key, iv);
292    }
293
294    @Override
295    protected void engineInit(int opmode, Key key, AlgorithmParameters params, SecureRandom random)
296            throws InvalidKeyException, InvalidAlgorithmParameterException {
297        final AlgorithmParameterSpec spec;
298        try {
299            spec = params.getParameterSpec(IvParameterSpec.class);
300        } catch (InvalidParameterSpecException e) {
301            throw new InvalidAlgorithmParameterException(e);
302        }
303
304        engineInit(opmode, key, spec, random);
305    }
306
307    private final int updateInternal(byte[] input, int inputOffset, int inputLen, byte[] output,
308            int outputOffset, int totalLen, int fullBlocksSize) throws ShortBufferException {
309        final int intialOutputOffset = outputOffset;
310
311        /* Take care of existing buffered bytes. */
312        final int remainingBuffer = buffer.length - bufferOffset;
313        if (bufferOffset > 0 && inputLen >= remainingBuffer) {
314            System.arraycopy(input, inputOffset, buffer, bufferOffset, remainingBuffer);
315            final int writtenBytes = NativeCrypto.EVP_CipherUpdate(cipherCtx.getContext(), output,
316                    outputOffset, buffer, 0, blockSize);
317            fullBlocksSize -= writtenBytes;
318            outputOffset += writtenBytes;
319
320            inputLen -= remainingBuffer;
321            inputOffset += remainingBuffer;
322
323            bufferOffset = 0;
324        }
325
326        /* Take care of the bytes that would fill up our block-sized buffer. */
327        if (fullBlocksSize > 0) {
328            final int bytesLeft = output.length - outputOffset;
329            if (bytesLeft < fullBlocksSize) {
330                throw new ShortBufferException("output buffer too small during update: "
331                        + bytesLeft + " < " + fullBlocksSize);
332            }
333
334            outputOffset += NativeCrypto.EVP_CipherUpdate(cipherCtx.getContext(), output,
335                    outputOffset, input, inputOffset, fullBlocksSize);
336            inputLen -= fullBlocksSize;
337            inputOffset += fullBlocksSize;
338        }
339
340        /* Put the rest into the buffer for next time. */
341        if (inputLen > 0) {
342            System.arraycopy(input, inputOffset, buffer, bufferOffset, inputLen);
343            bufferOffset += inputLen;
344        }
345
346        return outputOffset - intialOutputOffset;
347    }
348
349    @Override
350    protected byte[] engineUpdate(byte[] input, int inputOffset, int inputLen) {
351        final int totalLen = bufferOffset + inputLen;
352        final int fullBlocksSize = totalLen - (totalLen % blockSize);
353
354        /* See how large our output buffer would need to be. */
355        final byte[] output;
356        if (fullBlocksSize > 0) {
357            output = new byte[fullBlocksSize];
358        } else {
359            output = EmptyArray.BYTE;
360        }
361
362        try {
363            updateInternal(input, inputOffset, inputLen, output, 0, totalLen, fullBlocksSize);
364        } catch (ShortBufferException e) {
365            /* This shouldn't happen. */
366            throw new AssertionError("calculated buffer size was wrong: " + fullBlocksSize);
367        }
368
369        return output;
370    }
371
372    @Override
373    protected int engineUpdate(byte[] input, int inputOffset, int inputLen, byte[] output,
374            int outputOffset) throws ShortBufferException {
375        final int totalLen = bufferOffset + inputLen;
376        final int fullBlocksSize = totalLen - (totalLen % modeBlockSize);
377        return updateInternal(input, inputOffset, inputLen, output, outputOffset, totalLen, fullBlocksSize);
378    }
379
380    private int doFinalInternal(byte[] input, int inputOffset, int inputLen, byte[] output,
381            int outputOffset, int totalLen, int trailingLen, int maximumPossibleSize)
382            throws IllegalBlockSizeException, BadPaddingException, ShortBufferException {
383        if ((trailingLen != 0) && (padding == Padding.NOPADDING)) {
384            throw new IllegalBlockSizeException("not multiple of block size " + trailingLen
385                    + " != " + modeBlockSize);
386        }
387
388        /* Remember this so we can tell how many characters were written. */
389        final int initialOutputOffset = outputOffset;
390
391        if (inputLen > 0) {
392            /*
393             * First run update to set up our invariant that we have less than
394             * {@code blockSize} worth of bytes for the next CipherUpdate call.
395             */
396            final int updateSize;
397            if (trailingLen == 0 && maximumPossibleSize >= blockSize) {
398                updateSize = maximumPossibleSize - blockSize;
399            } else {
400                updateSize = maximumPossibleSize - trailingLen;
401            }
402            final int updateBytesWritten = updateInternal(input, inputOffset, inputLen, output,
403                    outputOffset, totalLen, updateSize);
404            outputOffset += updateBytesWritten;
405        }
406
407        /* Take care of existing buffered bytes. */
408        if (bufferOffset > 0) {
409            final int bytesLeft = output.length - outputOffset;
410
411            final int bytesNeeded = bufferOffset + modeBlockSize - 1;
412            final int writtenBytes;
413            if (bytesLeft < bytesNeeded) {
414                final byte[] tmpBuf = new byte[bytesNeeded];
415                writtenBytes = NativeCrypto.EVP_CipherUpdate(cipherCtx.getContext(), tmpBuf, 0,
416                        buffer, 0, bufferOffset);
417                if (writtenBytes > 0) {
418                    if (writtenBytes > bytesLeft) {
419                        System.arraycopy(tmpBuf, 0, output, outputOffset, bytesLeft);
420                    } else {
421                        System.arraycopy(tmpBuf, 0, output, outputOffset, writtenBytes);
422                    }
423                }
424            } else {
425                writtenBytes = NativeCrypto.EVP_CipherUpdate(cipherCtx.getContext(), output,
426                        outputOffset, buffer, 0, bufferOffset);
427            }
428
429            outputOffset += writtenBytes;
430            bufferOffset = 0;
431        }
432
433        /* Allow OpenSSL to pad if necessary and clean up state. */
434        final int bytesLeft = output.length - outputOffset;
435        final int writtenBytes;
436        if (bytesLeft >= blockSize) {
437            writtenBytes = NativeCrypto.EVP_CipherFinal_ex(cipherCtx.getContext(), output,
438                    outputOffset);
439        } else {
440            writtenBytes = NativeCrypto.EVP_CipherFinal_ex(cipherCtx.getContext(), buffer, 0);
441            if (writtenBytes > bytesLeft) {
442                throw new ShortBufferException("buffer is too short: " + writtenBytes + " > "
443                        + bytesLeft);
444            } else if (writtenBytes > 0) {
445                System.arraycopy(buffer, 0, output, outputOffset, writtenBytes);
446            }
447        }
448        outputOffset += writtenBytes;
449
450        /* Re-initialize the cipher for the next time. */
451        NativeCrypto.EVP_CipherInit_ex(cipherCtx.getContext(), 0, null, null, encrypting);
452
453        return outputOffset - initialOutputOffset;
454    }
455
456    @Override
457    protected byte[] engineDoFinal(byte[] input, int inputOffset, int inputLen)
458            throws IllegalBlockSizeException, BadPaddingException {
459        final int totalLen = bufferOffset + inputLen;
460        final int trailingLen = totalLen % modeBlockSize;
461
462        final int maximumPossibleSize = calculateMaximumPossibleSize(totalLen, trailingLen);
463        /* Assume that we'll output exactly on a byte boundary. */
464        byte[] output = new byte[maximumPossibleSize];
465        final int bytesWritten;
466        try {
467            bytesWritten = doFinalInternal(input, inputOffset, inputLen, output, 0, totalLen,
468                    trailingLen, maximumPossibleSize);
469        } catch (ShortBufferException e) {
470            /* This should not happen since we sized our own buffer. */
471            throw new RuntimeException("our calculated buffer was too small", e);
472        }
473
474        if (bytesWritten == output.length) {
475            return output;
476        } else {
477            return Arrays.copyOfRange(output, 0, bytesWritten);
478        }
479    }
480
481    @Override
482    protected int engineDoFinal(byte[] input, int inputOffset, int inputLen, byte[] output,
483            int outputOffset) throws ShortBufferException, IllegalBlockSizeException,
484            BadPaddingException {
485        if (output == null) {
486            throw new NullPointerException("output == null");
487        }
488
489        final int totalLen = bufferOffset + inputLen;
490        final int trailingLen = totalLen % modeBlockSize;
491
492        final int maximumPossibleSize = calculateMaximumPossibleSize(totalLen, trailingLen);
493
494        return doFinalInternal(input, inputOffset, inputLen, output, outputOffset, totalLen,
495                trailingLen, maximumPossibleSize);
496    }
497
498    private int calculateMaximumPossibleSize(final int totalLen, final int trailingLen) {
499        if (encrypting && (modeBlockSize > 1) && (padding != Padding.NOPADDING)) {
500            return totalLen - trailingLen + modeBlockSize;
501        } else {
502            return totalLen;
503        }
504    }
505
506    @Override
507    protected byte[] engineWrap(Key key) throws IllegalBlockSizeException, InvalidKeyException {
508        try {
509            byte[] encoded = key.getEncoded();
510            return engineDoFinal(encoded, 0, encoded.length);
511        } catch (BadPaddingException e) {
512            IllegalBlockSizeException newE = new IllegalBlockSizeException();
513            newE.initCause(e);
514            throw newE;
515        }
516    }
517
518    @Override
519    protected Key engineUnwrap(byte[] wrappedKey, String wrappedKeyAlgorithm, int wrappedKeyType)
520            throws InvalidKeyException, NoSuchAlgorithmException {
521        try {
522            byte[] encoded = engineDoFinal(wrappedKey, 0, wrappedKey.length);
523            if (wrappedKeyType == Cipher.PUBLIC_KEY) {
524                KeyFactory keyFactory = KeyFactory.getInstance(wrappedKeyAlgorithm);
525                return keyFactory.generatePublic(new X509EncodedKeySpec(encoded));
526            } else if (wrappedKeyType == Cipher.PRIVATE_KEY) {
527                KeyFactory keyFactory = KeyFactory.getInstance(wrappedKeyAlgorithm);
528                return keyFactory.generatePrivate(new PKCS8EncodedKeySpec(encoded));
529            } else if (wrappedKeyType == Cipher.SECRET_KEY) {
530                return new SecretKeySpec(encoded, wrappedKeyAlgorithm);
531            } else {
532                throw new UnsupportedOperationException("wrappedKeyType == " + wrappedKeyType);
533            }
534        } catch (IllegalBlockSizeException e) {
535            throw new InvalidKeyException(e);
536        } catch (BadPaddingException e) {
537            throw new InvalidKeyException(e);
538        } catch (InvalidKeySpecException e) {
539            throw new InvalidKeyException(e);
540        }
541    }
542
543    public static class AES extends OpenSSLCipher {
544        private static final int AES_BLOCK_SIZE = 16;
545
546        protected AES(Mode mode, Padding padding) {
547            super(mode, padding);
548        }
549
550        public static class CBC extends AES {
551            public CBC(Padding padding) {
552                super(Mode.CBC, padding);
553            }
554
555            public static class NoPadding extends CBC {
556                public NoPadding() {
557                    super(Padding.NOPADDING);
558                }
559            }
560
561            public static class PKCS5Padding extends CBC {
562                public PKCS5Padding() {
563                    super(Padding.PKCS5PADDING);
564                }
565            }
566        }
567
568        public static class CFB extends AES {
569            public CFB(Padding padding) {
570                super(Mode.CFB, padding);
571            }
572
573            public static class NoPadding extends CFB {
574                public NoPadding() {
575                    super(Padding.NOPADDING);
576                }
577            }
578
579            public static class PKCS5Padding extends CFB {
580                public PKCS5Padding() {
581                    super(Padding.PKCS5PADDING);
582                }
583            }
584        }
585
586        public static class CTR extends AES {
587            public CTR(Padding padding) {
588                super(Mode.CTR, padding);
589            }
590
591            public static class NoPadding extends CTR {
592                public NoPadding() {
593                    super(Padding.NOPADDING);
594                }
595            }
596
597            public static class PKCS5Padding extends CTR {
598                public PKCS5Padding() {
599                    super(Padding.PKCS5PADDING);
600                }
601            }
602        }
603
604        public static class ECB extends AES {
605            public ECB(Padding padding) {
606                super(Mode.ECB, padding);
607            }
608
609            public static class NoPadding extends ECB {
610                public NoPadding() {
611                    super(Padding.NOPADDING);
612                }
613            }
614
615            public static class PKCS5Padding extends ECB {
616                public PKCS5Padding() {
617                    super(Padding.PKCS5PADDING);
618                }
619            }
620        }
621
622        public static class OFB extends AES {
623            public OFB(Padding padding) {
624                super(Mode.OFB, padding);
625            }
626
627            public static class NoPadding extends OFB {
628                public NoPadding() {
629                    super(Padding.NOPADDING);
630                }
631            }
632
633            public static class PKCS5Padding extends OFB {
634                public PKCS5Padding() {
635                    super(Padding.PKCS5PADDING);
636                }
637            }
638        }
639
640        @Override
641        protected void checkSupportedKeySize(int keyLength) throws InvalidKeyException {
642            switch (keyLength) {
643                case 16: // AES 128
644                case 24: // AES 192
645                case 32: // AES 256
646                    return;
647                default:
648                    throw new InvalidKeyException("Unsupported key size: " + keyLength + " bytes");
649            }
650        }
651
652        @Override
653        protected void checkSupportedMode(Mode mode) throws NoSuchAlgorithmException {
654            switch (mode) {
655                case CBC:
656                case CFB:
657                case CFB1:
658                case CFB8:
659                case CFB128:
660                case CTR:
661                case ECB:
662                case OFB:
663                    return;
664                default:
665                    throw new NoSuchAlgorithmException("Unsupported mode " + mode.toString());
666            }
667        }
668
669        @Override
670        protected void checkSupportedPadding(Padding padding) throws NoSuchPaddingException {
671            switch (padding) {
672                case NOPADDING:
673                case PKCS5PADDING:
674                    return;
675                default:
676                    throw new NoSuchPaddingException("Unsupported padding " + padding.toString());
677            }
678        }
679
680        @Override
681        protected String getCipherName(int keyLength, Mode mode) {
682            return "aes-" + (keyLength * 8) + "-" + mode.toString().toLowerCase(Locale.US);
683        }
684
685        @Override
686        protected int getCipherBlockSize() {
687            return AES_BLOCK_SIZE;
688        }
689    }
690}
691