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