001    /*
002     * $HeadURL: http://juliusdavies.ca/svn/not-yet-commons-ssl/tags/commons-ssl-0.3.9/src/java/org/apache/commons/ssl/PKCS8Key.java $
003     * $Revision: 121 $
004     * $Date: 2007-11-13 21:26:57 -0800 (Tue, 13 Nov 2007) $
005     *
006     * ====================================================================
007     * Licensed to the Apache Software Foundation (ASF) under one
008     * or more contributor license agreements.  See the NOTICE file
009     * distributed with this work for additional information
010     * regarding copyright ownership.  The ASF licenses this file
011     * to you under the Apache License, Version 2.0 (the
012     * "License"); you may not use this file except in compliance
013     * with the License.  You may obtain a copy of the License at
014     *
015     *   http://www.apache.org/licenses/LICENSE-2.0
016     *
017     * Unless required by applicable law or agreed to in writing,
018     * software distributed under the License is distributed on an
019     * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
020     * KIND, either express or implied.  See the License for the
021     * specific language governing permissions and limitations
022     * under the License.
023     * ====================================================================
024     *
025     * This software consists of voluntary contributions made by many
026     * individuals on behalf of the Apache Software Foundation.  For more
027     * information on the Apache Software Foundation, please see
028     * <http://www.apache.org/>.
029     *
030     */
031    
032    package org.apache.commons.ssl;
033    
034    import org.apache.commons.ssl.asn1.ASN1EncodableVector;
035    import org.apache.commons.ssl.asn1.ASN1OutputStream;
036    import org.apache.commons.ssl.asn1.DEREncodable;
037    import org.apache.commons.ssl.asn1.DERInteger;
038    import org.apache.commons.ssl.asn1.DERNull;
039    import org.apache.commons.ssl.asn1.DERObjectIdentifier;
040    import org.apache.commons.ssl.asn1.DEROctetString;
041    import org.apache.commons.ssl.asn1.DERSequence;
042    
043    import javax.crypto.BadPaddingException;
044    import javax.crypto.Cipher;
045    import javax.crypto.IllegalBlockSizeException;
046    import javax.crypto.Mac;
047    import javax.crypto.NoSuchPaddingException;
048    import javax.crypto.SecretKey;
049    import javax.crypto.spec.IvParameterSpec;
050    import javax.crypto.spec.RC2ParameterSpec;
051    import javax.crypto.spec.RC5ParameterSpec;
052    import javax.crypto.spec.SecretKeySpec;
053    import java.io.ByteArrayInputStream;
054    import java.io.ByteArrayOutputStream;
055    import java.io.File;
056    import java.io.FileInputStream;
057    import java.io.IOException;
058    import java.io.InputStream;
059    import java.math.BigInteger;
060    import java.security.GeneralSecurityException;
061    import java.security.InvalidAlgorithmParameterException;
062    import java.security.InvalidKeyException;
063    import java.security.KeyFactory;
064    import java.security.MessageDigest;
065    import java.security.NoSuchAlgorithmException;
066    import java.security.PrivateKey;
067    import java.security.spec.KeySpec;
068    import java.security.spec.PKCS8EncodedKeySpec;
069    import java.util.Arrays;
070    import java.util.Collections;
071    import java.util.Iterator;
072    import java.util.List;
073    
074    /**
075     * Utility for decrypting PKCS8 private keys.  Way easier to use than
076     * javax.crypto.EncryptedPrivateKeyInfo since all you need is the byte[] array
077     * and the password.  You don't need to know anything else about the PKCS8
078     * key you pass in.
079     * </p><p>
080     * Can handle base64 PEM, or raw DER.
081     * Can handle PKCS8 Version 1.5 and 2.0.
082     * Can also handle OpenSSL encrypted or unencrypted private keys (DSA or RSA).
083     * </p><p>
084     * The PKCS12 key derivation (the "pkcs12()" method) comes from BouncyCastle.
085     * </p>
086     *
087     * @author Credit Union Central of British Columbia
088     * @author <a href="http://www.cucbc.com/">www.cucbc.com</a>
089     * @author <a href="mailto:juliusdavies@cucbc.com">juliusdavies@cucbc.com</a>
090     * @author <a href="bouncycastle.org">bouncycastle.org</a>
091     * @since 7-Nov-2006
092     */
093    public class PKCS8Key {
094        public final static String RSA_OID = "1.2.840.113549.1.1.1";
095        public final static String DSA_OID = "1.2.840.10040.4.1";
096    
097        public final static String PKCS8_UNENCRYPTED = "PRIVATE KEY";
098        public final static String PKCS8_ENCRYPTED = "ENCRYPTED PRIVATE KEY";
099        public final static String OPENSSL_RSA = "RSA PRIVATE KEY";
100        public final static String OPENSSL_DSA = "DSA PRIVATE KEY";
101    
102        private final PrivateKey privateKey;
103        private final byte[] decryptedBytes;
104        private final String transformation;
105        private final int keySize;
106        private final boolean isDSA;
107        private final boolean isRSA;
108    
109        static {
110            JavaImpl.load();
111        }
112    
113        /**
114         * @param in       pkcs8 file to parse (pem or der, encrypted or unencrypted)
115         * @param password password to decrypt the pkcs8 file.  Ignored if the
116         *                 supplied pkcs8 is already unencrypted.
117         * @throws GeneralSecurityException If a parsing or decryption problem
118         *                                  occured.
119         * @throws IOException              If the supplied InputStream could not be read.
120         */
121        public PKCS8Key(final InputStream in, char[] password)
122            throws GeneralSecurityException, IOException {
123            this(Util.streamToBytes(in), password);
124        }
125    
126        /**
127         * @param in       pkcs8 file to parse (pem or der, encrypted or unencrypted)
128         * @param password password to decrypt the pkcs8 file.  Ignored if the
129         *                 supplied pkcs8 is already unencrypted.
130         * @throws GeneralSecurityException If a parsing or decryption problem
131         *                                  occured.
132         */
133        public PKCS8Key(final ByteArrayInputStream in, char[] password)
134            throws GeneralSecurityException {
135            this(Util.streamToBytes(in), password);
136        }
137    
138        /**
139         * @param encoded  pkcs8 file to parse (pem or der, encrypted or unencrypted)
140         * @param password password to decrypt the pkcs8 file.  Ignored if the
141         *                 supplied pkcs8 is already unencrypted.
142         * @throws GeneralSecurityException If a parsing or decryption problem
143         *                                  occured.
144         */
145        public PKCS8Key(final byte[] encoded, char[] password)
146            throws GeneralSecurityException {
147            DecryptResult decryptResult =
148                new DecryptResult("UNENCRYPTED", 0, encoded);
149    
150            List pemItems = PEMUtil.decode(encoded);
151            PEMItem keyItem = null;
152            byte[] derBytes = null;
153            if (pemItems.isEmpty()) {
154                // must be DER encoded - PEMUtil wasn't able to extract anything.
155                derBytes = encoded;
156            } else {
157                Iterator it = pemItems.iterator();
158                boolean opensslRSA = false;
159                boolean opensslDSA = false;
160    
161                while (it.hasNext()) {
162                    PEMItem item = (PEMItem) it.next();
163                    String type = item.pemType.trim().toUpperCase();
164                    boolean plainPKCS8 = type.startsWith(PKCS8_UNENCRYPTED);
165                    boolean encryptedPKCS8 = type.startsWith(PKCS8_ENCRYPTED);
166                    boolean rsa = type.startsWith(OPENSSL_RSA);
167                    boolean dsa = type.startsWith(OPENSSL_DSA);
168                    if (plainPKCS8 || encryptedPKCS8 || rsa || dsa) {
169                        opensslRSA = opensslRSA || rsa;
170                        opensslDSA = opensslDSA || dsa;
171                        if (derBytes != null) {
172                            throw new ProbablyNotPKCS8Exception("More than one pkcs8 or OpenSSL key found in the supplied PEM Base64 stream");
173                        }
174                        derBytes = item.getDerBytes();
175                        keyItem = item;
176                        decryptResult = new DecryptResult("UNENCRYPTED", 0, derBytes);
177                    }
178                }
179                // after the loop is finished, did we find anything?
180                if (derBytes == null) {
181                    throw new ProbablyNotPKCS8Exception("No pkcs8 or OpenSSL key found in the supplied PEM Base64 stream");
182                }
183    
184                if (opensslDSA || opensslRSA) {
185                    String c = keyItem.cipher.trim();
186                    boolean encrypted = !"UNKNOWN".equals(c) && !"".equals(c);
187                    if (encrypted) {
188                        decryptResult = opensslDecrypt(keyItem, password);
189                    }
190    
191                    String oid = RSA_OID;
192                    if (opensslDSA) {
193                        oid = DSA_OID;
194                    }
195                    derBytes = formatAsPKCS8(decryptResult.bytes, oid, null);
196    
197                    String tf = decryptResult.transformation;
198                    int ks = decryptResult.keySize;
199                    decryptResult = new DecryptResult(tf, ks, derBytes);
200                }
201            }
202    
203            ASN1Structure pkcs8;
204            try {
205                pkcs8 = ASN1Util.analyze(derBytes);
206            }
207            catch (Exception e) {
208                throw new ProbablyNotPKCS8Exception("asn1 parse failure: " + e);
209            }
210    
211            String oid = RSA_OID;
212            // With the OpenSSL unencrypted private keys in DER format, the only way
213            // to even have a hope of guessing what we've got (DSA or RSA?) is to
214            // count the number of DERIntegers occurring in the first DERSequence.
215            int derIntegerCount = -1;
216            if (pkcs8.derIntegers != null) {
217                derIntegerCount = pkcs8.derIntegers.size();
218            }
219            switch (derIntegerCount) {
220                case 6:
221                    oid = DSA_OID;
222                case 9:
223                    derBytes = formatAsPKCS8(derBytes, oid, pkcs8);
224                    pkcs8.oid1 = oid;
225    
226                    String tf = decryptResult.transformation;
227                    int ks = decryptResult.keySize;
228                    decryptResult = new DecryptResult(tf, ks, derBytes);
229                    break;
230                default:
231                    break;
232            }
233    
234            oid = pkcs8.oid1;
235            if (!oid.startsWith("1.2.840.113549.1")) {
236                boolean isOkay = false;
237                if (oid.startsWith("1.2.840.10040.4.")) {
238                    String s = oid.substring("1.2.840.10040.4.".length());
239                    // 1.2.840.10040.4.1 -- id-dsa
240                    // 1.2.840.10040.4.3 -- id-dsa-with-sha1
241                    isOkay = s.equals("1") || s.startsWith("1.") ||
242                             s.equals("3") || s.startsWith("3.");
243                }
244                if (!isOkay) {
245                    throw new ProbablyNotPKCS8Exception("Valid ASN.1, but not PKCS8 or OpenSSL format.  OID=" + oid);
246                }
247            }
248    
249            boolean isRSA = RSA_OID.equals(oid);
250            boolean isDSA = DSA_OID.equals(oid);
251            boolean encrypted = !isRSA && !isDSA;
252            byte[] decryptedPKCS8 = encrypted ? null : derBytes;
253    
254            if (encrypted) {
255                decryptResult = decryptPKCS8(pkcs8, password);
256                decryptedPKCS8 = decryptResult.bytes;
257            }
258            if (encrypted) {
259                try {
260                    pkcs8 = ASN1Util.analyze(decryptedPKCS8);
261                }
262                catch (Exception e) {
263                    throw new ProbablyBadPasswordException("Decrypted stream not ASN.1.  Probably bad decryption password.");
264                }
265                oid = pkcs8.oid1;
266                isDSA = DSA_OID.equals(oid);
267            }
268    
269            KeySpec spec = new PKCS8EncodedKeySpec(decryptedPKCS8);
270            String type = "RSA";
271            PrivateKey pk;
272            try {
273                KeyFactory KF;
274                if (isDSA) {
275                    type = "DSA";
276                    KF = KeyFactory.getInstance("DSA");
277                } else {
278                    KF = KeyFactory.getInstance("RSA");
279                }
280                pk = KF.generatePrivate(spec);
281            }
282            catch (Exception e) {
283                throw new ProbablyBadPasswordException("Cannot create " + type + " private key from decrypted stream.  Probably bad decryption password. " + e);
284            }
285            if (pk != null) {
286                this.privateKey = pk;
287                this.isDSA = isDSA;
288                this.isRSA = !isDSA;
289                this.decryptedBytes = decryptedPKCS8;
290                this.transformation = decryptResult.transformation;
291                this.keySize = decryptResult.keySize;
292            } else {
293                throw new GeneralSecurityException("KeyFactory.generatePrivate() returned null and didn't throw exception!");
294            }
295        }
296    
297        public boolean isRSA() {
298            return isRSA;
299        }
300    
301        public boolean isDSA() {
302            return isDSA;
303        }
304    
305        public String getTransformation() {
306            return transformation;
307        }
308    
309        public int getKeySize() {
310            return keySize;
311        }
312    
313        public byte[] getDecryptedBytes() {
314            return decryptedBytes;
315        }
316    
317        public PrivateKey getPrivateKey() {
318            return privateKey;
319        }
320    
321        public static class DecryptResult {
322            public final String transformation;
323            public final int keySize;
324            public final byte[] bytes;
325    
326            protected DecryptResult(String transformation, int keySize,
327                                    byte[] decryptedBytes) {
328                this.transformation = transformation;
329                this.keySize = keySize;
330                this.bytes = decryptedBytes;
331            }
332        }
333    
334        private static DecryptResult opensslDecrypt(final PEMItem item,
335                                                    final char[] password)
336            throws GeneralSecurityException {
337            final String cipher = item.cipher;
338            final String mode = item.mode;
339            final int keySize = item.keySizeInBits;
340            final byte[] salt = item.iv;
341            final boolean des2 = item.des2;
342            final DerivedKey dk = OpenSSL.deriveKey(password, salt, keySize, des2);
343            return decrypt(cipher, mode, dk, des2, null, item.getDerBytes());
344        }
345    
346        public static Cipher generateCipher(String cipher, String mode,
347                                            final DerivedKey dk,
348                                            final boolean des2,
349                                            final byte[] iv,
350                                            final boolean decryptMode)
351            throws NoSuchAlgorithmException, NoSuchPaddingException,
352            InvalidKeyException, InvalidAlgorithmParameterException {
353            if (des2 && dk.key.length >= 24) {
354                // copy first 8 bytes into last 8 bytes to create 2DES key.
355                System.arraycopy(dk.key, 0, dk.key, 16, 8);
356            }
357    
358            final int keySize = dk.key.length * 8;
359            cipher = cipher.trim();
360            String cipherUpper = cipher.toUpperCase();
361            mode = mode.trim().toUpperCase();
362            // Is the cipher even available?
363            Cipher.getInstance(cipher);
364            String padding = "PKCS5Padding";
365            if (mode.startsWith("CFB") || mode.startsWith("OFB")) {
366                padding = "NoPadding";
367            }
368    
369            String transformation = cipher + "/" + mode + "/" + padding;
370            if (cipherUpper.startsWith("RC4")) {
371                // RC4 does not take mode or padding.
372                transformation = cipher;
373            }
374    
375            SecretKey secret = new SecretKeySpec(dk.key, cipher);
376            IvParameterSpec ivParams;
377            if (iv != null) {
378                ivParams = new IvParameterSpec(iv);
379            } else {
380                ivParams = dk.iv != null ? new IvParameterSpec(dk.iv) : null;
381            }
382    
383            Cipher c = Cipher.getInstance(transformation);
384            int cipherMode = Cipher.ENCRYPT_MODE;
385            if (decryptMode) {
386                cipherMode = Cipher.DECRYPT_MODE;
387            }
388    
389            // RC2 requires special params to inform engine of keysize.
390            if (cipherUpper.startsWith("RC2")) {
391                RC2ParameterSpec rcParams;
392                if (mode.startsWith("ECB") || ivParams == null) {
393                    // ECB doesn't take an IV.
394                    rcParams = new RC2ParameterSpec(keySize);
395                } else {
396                    rcParams = new RC2ParameterSpec(keySize, ivParams.getIV());
397                }
398                c.init(cipherMode, secret, rcParams);
399            } else if (cipherUpper.startsWith("RC5")) {
400                RC5ParameterSpec rcParams;
401                if (mode.startsWith("ECB") || ivParams == null) {
402                    // ECB doesn't take an IV.
403                    rcParams = new RC5ParameterSpec(16, 12, 32);
404                } else {
405                    rcParams = new RC5ParameterSpec(16, 12, 32, ivParams.getIV());
406                }
407                c.init(cipherMode, secret, rcParams);
408            } else if (mode.startsWith("ECB") || cipherUpper.startsWith("RC4")) {
409                // RC4 doesn't require any params.
410                // Any cipher using ECB does not require an IV.
411                c.init(cipherMode, secret);
412            } else {
413                // DES, DESede, AES, BlowFish require IVParams (when in CBC, CFB,
414                // or OFB mode).  (In ECB mode they don't require IVParams).
415                c.init(cipherMode, secret, ivParams);
416            }
417            return c;
418        }
419    
420        public static DecryptResult decrypt(String cipher, String mode,
421                                            final DerivedKey dk,
422                                            final boolean des2,
423                                            final byte[] iv,
424                                            final byte[] encryptedBytes)
425    
426            throws NoSuchAlgorithmException, NoSuchPaddingException,
427            InvalidKeyException, InvalidAlgorithmParameterException,
428            IllegalBlockSizeException, BadPaddingException {
429            Cipher c = generateCipher(cipher, mode, dk, des2, iv, true);
430            final String transformation = c.getAlgorithm();
431            final int keySize = dk.key.length * 8;
432            byte[] decryptedBytes = c.doFinal(encryptedBytes);
433            return new DecryptResult(transformation, keySize, decryptedBytes);
434        }
435    
436        private static DecryptResult decryptPKCS8(ASN1Structure pkcs8,
437                                                  char[] password)
438            throws GeneralSecurityException {
439            boolean isVersion1 = true;
440            boolean isVersion2 = false;
441            boolean usePKCS12PasswordPadding = false;
442            boolean use2DES = false;
443            String cipher = null;
444            String hash = null;
445            int keySize = -1;
446            // Almost all PKCS8 encrypted keys use CBC.  Looks like the AES OID's can
447            // support different modes, and RC4 doesn't use any mode at all!
448            String mode = "CBC";
449    
450            // In PKCS8 Version 2 the IV is stored in the ASN.1 structure for
451            // us, so we don't need to derive it.  Just leave "ivSize" set to 0 for
452            // those ones.
453            int ivSize = 0;
454    
455            String oid = pkcs8.oid1;
456            if (oid.startsWith("1.2.840.113549.1.12."))  // PKCS12 key derivation!
457            {
458                usePKCS12PasswordPadding = true;
459    
460                // Let's trim this OID to make life a little easier.
461                oid = oid.substring("1.2.840.113549.1.12.".length());
462    
463                if (oid.equals("1.1") || oid.startsWith("1.1.")) {
464                    // 1.2.840.113549.1.12.1.1
465                    hash = "SHA1";
466                    cipher = "RC4";
467                    keySize = 128;
468                } else if (oid.equals("1.2") || oid.startsWith("1.2.")) {
469                    // 1.2.840.113549.1.12.1.2
470                    hash = "SHA1";
471                    cipher = "RC4";
472                    keySize = 40;
473                } else if (oid.equals("1.3") || oid.startsWith("1.3.")) {
474                    // 1.2.840.113549.1.12.1.3
475                    hash = "SHA1";
476                    cipher = "DESede";
477                    keySize = 192;
478                } else if (oid.equals("1.4") || oid.startsWith("1.4.")) {
479                    // DES2 !!!
480    
481                    // 1.2.840.113549.1.12.1.4
482                    hash = "SHA1";
483                    cipher = "DESede";
484                    keySize = 192;
485                    use2DES = true;
486                    // later on we'll copy the first 8 bytes of the 24 byte DESede key
487                    // over top the last 8 bytes, making the key look like K1-K2-K1
488                    // instead of the usual K1-K2-K3.
489                } else if (oid.equals("1.5") || oid.startsWith("1.5.")) {
490                    // 1.2.840.113549.1.12.1.5
491                    hash = "SHA1";
492                    cipher = "RC2";
493                    keySize = 128;
494                } else if (oid.equals("1.6") || oid.startsWith("1.6.")) {
495                    // 1.2.840.113549.1.12.1.6
496                    hash = "SHA1";
497                    cipher = "RC2";
498                    keySize = 40;
499                }
500            } else if (oid.startsWith("1.2.840.113549.1.5.")) {
501                // Let's trim this OID to make life a little easier.
502                oid = oid.substring("1.2.840.113549.1.5.".length());
503    
504                if (oid.equals("1") || oid.startsWith("1.")) {
505                    // 1.2.840.113549.1.5.1 -- pbeWithMD2AndDES-CBC
506                    hash = "MD2";
507                    cipher = "DES";
508                    keySize = 64;
509                } else if (oid.equals("3") || oid.startsWith("3.")) {
510                    // 1.2.840.113549.1.5.3 -- pbeWithMD5AndDES-CBC
511                    hash = "MD5";
512                    cipher = "DES";
513                    keySize = 64;
514                } else if (oid.equals("4") || oid.startsWith("4.")) {
515                    // 1.2.840.113549.1.5.4 -- pbeWithMD2AndRC2_CBC
516                    hash = "MD2";
517                    cipher = "RC2";
518                    keySize = 64;
519                } else if (oid.equals("6") || oid.startsWith("6.")) {
520                    // 1.2.840.113549.1.5.6 -- pbeWithMD5AndRC2_CBC
521                    hash = "MD5";
522                    cipher = "RC2";
523                    keySize = 64;
524                } else if (oid.equals("10") || oid.startsWith("10.")) {
525                    // 1.2.840.113549.1.5.10 -- pbeWithSHA1AndDES-CBC
526                    hash = "SHA1";
527                    cipher = "DES";
528                    keySize = 64;
529                } else if (oid.equals("11") || oid.startsWith("11.")) {
530                    // 1.2.840.113549.1.5.11 -- pbeWithSHA1AndRC2_CBC
531                    hash = "SHA1";
532                    cipher = "RC2";
533                    keySize = 64;
534                } else if (oid.equals("12") || oid.startsWith("12.")) {
535                    // 1.2.840.113549.1.5.12 - id-PBKDF2 - Key Derivation Function
536                    isVersion2 = true;
537                } else if (oid.equals("13") || oid.startsWith("13.")) {
538                    // 1.2.840.113549.1.5.13 - id-PBES2: PBES2 encryption scheme
539                    isVersion2 = true;
540                } else if (oid.equals("14") || oid.startsWith("14.")) {
541                    // 1.2.840.113549.1.5.14 - id-PBMAC1 message authentication scheme
542                    isVersion2 = true;
543                }
544            }
545            if (isVersion2) {
546                isVersion1 = false;
547                hash = "HmacSHA1";
548                oid = pkcs8.oid2;
549    
550                // really ought to be:
551                //
552                // if ( oid.startsWith( "1.2.840.113549.1.5.12" ) )
553                //
554                // but all my tests still pass, and I figure this to be more robust:
555                if (pkcs8.oid3 != null) {
556                    oid = pkcs8.oid3;
557                }
558                if (oid.startsWith("1.3.6.1.4.1.3029.1.2")) {
559                    // 1.3.6.1.4.1.3029.1.2 - Blowfish
560                    cipher = "Blowfish";
561                    mode = "CBC";
562                    keySize = 128;
563                } else if (oid.startsWith("1.3.14.3.2.")) {
564                    oid = oid.substring("1.3.14.3.2.".length());
565                    if (oid.equals("6") || oid.startsWith("6.")) {
566                        // 1.3.14.3.2.6 - desECB
567                        cipher = "DES";
568                        mode = "ECB";
569                        keySize = 64;
570                    } else if (oid.equals("7") || oid.startsWith("7.")) {
571                        // 1.3.14.3.2.7 - desCBC
572                        cipher = "DES";
573                        mode = "CBC";
574                        keySize = 64;
575                    } else if (oid.equals("8") || oid.startsWith("8.")) {
576                        // 1.3.14.3.2.8 - desOFB
577                        cipher = "DES";
578                        mode = "OFB";
579                        keySize = 64;
580                    } else if (oid.equals("9") || oid.startsWith("9.")) {
581                        // 1.3.14.3.2.9 - desCFB
582                        cipher = "DES";
583                        mode = "CFB";
584                        keySize = 64;
585                    } else if (oid.equals("17") || oid.startsWith("17.")) {
586                        // 1.3.14.3.2.17 - desEDE
587                        cipher = "DESede";
588                        mode = "CBC";
589                        keySize = 192;
590    
591                        // If the supplied IV is all zeroes, then this is DES2
592                        // (Well, that's what happened when I played with OpenSSL!)
593                        if (allZeroes(pkcs8.iv)) {
594                            mode = "ECB";
595                            use2DES = true;
596                            pkcs8.iv = null;
597                        }
598                    }
599                }
600    
601                // AES
602                // 2.16.840.1.101.3.4.1.1  - id-aes128-ECB
603                // 2.16.840.1.101.3.4.1.2  - id-aes128-CBC
604                // 2.16.840.1.101.3.4.1.3  - id-aes128-OFB
605                // 2.16.840.1.101.3.4.1.4  - id-aes128-CFB
606                // 2.16.840.1.101.3.4.1.21 - id-aes192-ECB
607                // 2.16.840.1.101.3.4.1.22 - id-aes192-CBC
608                // 2.16.840.1.101.3.4.1.23 - id-aes192-OFB
609                // 2.16.840.1.101.3.4.1.24 - id-aes192-CFB
610                // 2.16.840.1.101.3.4.1.41 - id-aes256-ECB
611                // 2.16.840.1.101.3.4.1.42 - id-aes256-CBC
612                // 2.16.840.1.101.3.4.1.43 - id-aes256-OFB
613                // 2.16.840.1.101.3.4.1.44 - id-aes256-CFB
614                else if (oid.startsWith("2.16.840.1.101.3.4.1.")) {
615                    cipher = "AES";
616                    if (pkcs8.iv == null) {
617                        ivSize = 128;
618                    }
619                    oid = oid.substring("2.16.840.1.101.3.4.1.".length());
620                    int x = oid.indexOf('.');
621                    int finalDigit;
622                    if (x >= 0) {
623                        finalDigit = Integer.parseInt(oid.substring(0, x));
624                    } else {
625                        finalDigit = Integer.parseInt(oid);
626                    }
627                    switch (finalDigit % 10) {
628                        case 1:
629                            mode = "ECB";
630                            break;
631                        case 2:
632                            mode = "CBC";
633                            break;
634                        case 3:
635                            mode = "OFB";
636                            break;
637                        case 4:
638                            mode = "CFB";
639                            break;
640                        default:
641                            throw new RuntimeException("Unknown AES final digit: " + finalDigit);
642                    }
643                    switch (finalDigit / 10) {
644                        case 0:
645                            keySize = 128;
646                            break;
647                        case 2:
648                            keySize = 192;
649                            break;
650                        case 4:
651                            keySize = 256;
652                            break;
653                        default:
654                            throw new RuntimeException("Unknown AES final digit: " + finalDigit);
655                    }
656                } else if (oid.startsWith("1.2.840.113549.3.")) {
657                    // Let's trim this OID to make life a little easier.
658                    oid = oid.substring("1.2.840.113549.3.".length());
659    
660                    if (oid.equals("2") || oid.startsWith("2.")) {
661                        // 1.2.840.113549.3.2 - RC2-CBC
662                        // Note:  keysize determined in PKCS8 Version 2.0 ASN.1 field.
663                        cipher = "RC2";
664                        keySize = pkcs8.keySize * 8;
665                    } else if (oid.equals("4") || oid.startsWith("4.")) {
666                        // 1.2.840.113549.3.4 - RC4
667                        // Note:  keysize determined in PKCS8 Version 2.0 ASN.1 field.
668                        cipher = "RC4";
669                        keySize = pkcs8.keySize * 8;
670                    } else if (oid.equals("7") || oid.startsWith("7.")) {
671                        // 1.2.840.113549.3.7 - DES-EDE3-CBC
672                        cipher = "DESede";
673                        keySize = 192;
674                    } else if (oid.equals("9") || oid.startsWith("9.")) {
675                        // 1.2.840.113549.3.9 - RC5 CBC Pad
676                        // Note:  keysize determined in PKCS8 Version 2.0 ASN.1 field.
677                        keySize = pkcs8.keySize * 8;
678                        cipher = "RC5";
679    
680                        // Need to find out more about RC5.
681                        // How do I create the RC5ParameterSpec?
682                        // (int version, int rounds, int wordSize, byte[] iv)
683                    }
684                }
685            }
686    
687            // The pkcs8 structure has been thoroughly examined.  If we don't have
688            // a cipher or hash at this point, then we don't support the file we
689            // were given.
690            if (cipher == null || hash == null) {
691                throw new ProbablyNotPKCS8Exception("Unsupported PKCS8 format. oid1=[" + pkcs8.oid1 + "], oid2=[" + pkcs8.oid2 + "]");
692            }
693    
694            // In PKCS8 Version 1.5 we need to derive an 8 byte IV.  In those cases
695            // the ASN.1 structure doesn't have the IV, anyway, so I can use that
696            // to decide whether to derive one or not.
697            //
698            // Note:  if AES, then IV has to be 16 bytes.
699            if (pkcs8.iv == null) {
700                ivSize = 64;
701            }
702    
703            byte[] salt = pkcs8.salt;
704            int ic = pkcs8.iterationCount;
705    
706            // PKCS8 converts the password to a byte[] array using a simple
707            // cast.  This byte[] array is ignored if we're using the PKCS12
708            // key derivation, since that employs a different technique.
709            byte[] pwd = new byte[password.length];
710            for (int i = 0; i < pwd.length; i++) {
711                pwd[i] = (byte) password[i];
712            }
713    
714            DerivedKey dk;
715            if (usePKCS12PasswordPadding) {
716                MessageDigest md = MessageDigest.getInstance(hash);
717                dk = deriveKeyPKCS12(password, salt, ic, keySize, ivSize, md);
718            } else {
719                if (isVersion1) {
720                    MessageDigest md = MessageDigest.getInstance(hash);
721                    dk = deriveKeyV1(pwd, salt, ic, keySize, ivSize, md);
722                } else {
723                    Mac mac = Mac.getInstance(hash);
724                    dk = deriveKeyV2(pwd, salt, ic, keySize, ivSize, mac);
725                }
726            }
727    
728    
729            return decrypt(cipher, mode, dk, use2DES, pkcs8.iv, pkcs8.bigPayload);
730        }
731    
732    
733        public static DerivedKey deriveKeyV1(byte[] password, byte[] salt,
734                                             int iterations, int keySizeInBits,
735                                             int ivSizeInBits, MessageDigest md) {
736            int keySize = keySizeInBits / 8;
737            int ivSize = ivSizeInBits / 8;
738            md.reset();
739            md.update(password);
740            byte[] result = md.digest(salt);
741            for (int i = 1; i < iterations; i++) {
742                // Hash of the hash for each of the iterations.
743                result = md.digest(result);
744            }
745            byte[] key = new byte[keySize];
746            byte[] iv = new byte[ivSize];
747            System.arraycopy(result, 0, key, 0, key.length);
748            System.arraycopy(result, key.length, iv, 0, iv.length);
749            return new DerivedKey(key, iv);
750        }
751    
752        public static DerivedKey deriveKeyPKCS12(char[] password, byte[] salt,
753                                                 int iterations, int keySizeInBits,
754                                                 int ivSizeInBits,
755                                                 MessageDigest md) {
756            byte[] pwd;
757            if (password.length > 0) {
758                pwd = new byte[(password.length + 1) * 2];
759                for (int i = 0; i < password.length; i++) {
760                    pwd[i * 2] = (byte) (password[i] >>> 8);
761                    pwd[i * 2 + 1] = (byte) password[i];
762                }
763            } else {
764                pwd = new byte[0];
765            }
766            int keySize = keySizeInBits / 8;
767            int ivSize = ivSizeInBits / 8;
768            byte[] key = pkcs12(1, keySize, salt, pwd, iterations, md);
769            byte[] iv = pkcs12(2, ivSize, salt, pwd, iterations, md);
770            return new DerivedKey(key, iv);
771        }
772    
773        /**
774         * This PKCS12 key derivation code comes from BouncyCastle.
775         *
776         * @param idByte         1 == key, 2 == iv
777         * @param n              keysize or ivsize
778         * @param salt           8 byte salt
779         * @param password       password
780         * @param iterationCount iteration-count
781         * @param md             The message digest to use
782         * @return byte[] the derived key
783         */
784        private static byte[] pkcs12(int idByte, int n, byte[] salt,
785                                     byte[] password, int iterationCount,
786                                     MessageDigest md) {
787            int u = md.getDigestLength();
788            // sha1, md2, md5 all use 512 bits.  But future hashes might not.
789            int v = 512 / 8;
790            md.reset();
791            byte[] D = new byte[v];
792            byte[] dKey = new byte[n];
793            for (int i = 0; i != D.length; i++) {
794                D[i] = (byte) idByte;
795            }
796            byte[] S;
797            if ((salt != null) && (salt.length != 0)) {
798                S = new byte[v * ((salt.length + v - 1) / v)];
799                for (int i = 0; i != S.length; i++) {
800                    S[i] = salt[i % salt.length];
801                }
802            } else {
803                S = new byte[0];
804            }
805            byte[] P;
806            if ((password != null) && (password.length != 0)) {
807                P = new byte[v * ((password.length + v - 1) / v)];
808                for (int i = 0; i != P.length; i++) {
809                    P[i] = password[i % password.length];
810                }
811            } else {
812                P = new byte[0];
813            }
814            byte[] I = new byte[S.length + P.length];
815            System.arraycopy(S, 0, I, 0, S.length);
816            System.arraycopy(P, 0, I, S.length, P.length);
817            byte[] B = new byte[v];
818            int c = (n + u - 1) / u;
819            for (int i = 1; i <= c; i++) {
820                md.update(D);
821                byte[] result = md.digest(I);
822                for (int j = 1; j != iterationCount; j++) {
823                    result = md.digest(result);
824                }
825                for (int j = 0; j != B.length; j++) {
826                    B[j] = result[j % result.length];
827                }
828                for (int j = 0; j < (I.length / v); j++) {
829                    /*
830                         * add a + b + 1, returning the result in a. The a value is treated
831                         * as a BigInteger of length (b.length * 8) bits. The result is
832                         * modulo 2^b.length in case of overflow.
833                         */
834                    int aOff = j * v;
835                    int bLast = B.length - 1;
836                    int x = (B[bLast] & 0xff) + (I[aOff + bLast] & 0xff) + 1;
837                    I[aOff + bLast] = (byte) x;
838                    x >>>= 8;
839                    for (int k = B.length - 2; k >= 0; k--) {
840                        x += (B[k] & 0xff) + (I[aOff + k] & 0xff);
841                        I[aOff + k] = (byte) x;
842                        x >>>= 8;
843                    }
844                }
845                if (i == c) {
846                    System.arraycopy(result, 0, dKey, (i - 1) * u, dKey.length - ((i - 1) * u));
847                } else {
848                    System.arraycopy(result, 0, dKey, (i - 1) * u, result.length);
849                }
850            }
851            return dKey;
852        }
853    
854        public static DerivedKey deriveKeyV2(byte[] password, byte[] salt,
855                                             int iterations, int keySizeInBits,
856                                             int ivSizeInBits, Mac mac)
857            throws InvalidKeyException {
858            int keySize = keySizeInBits / 8;
859            int ivSize = ivSizeInBits / 8;
860    
861            // Because we're using an Hmac, we need to initialize with a SecretKey.
862            // HmacSHA1 doesn't need SecretKeySpec's 2nd parameter, hence the "N/A".
863            SecretKeySpec sk = new SecretKeySpec(password, "N/A");
864            mac.init(sk);
865            int macLength = mac.getMacLength();
866            int derivedKeyLength = keySize + ivSize;
867            int blocks = (derivedKeyLength + macLength - 1) / macLength;
868            byte[] blockIndex = new byte[4];
869            byte[] finalResult = new byte[blocks * macLength];
870            for (int i = 1; i <= blocks; i++) {
871                int offset = (i - 1) * macLength;
872                blockIndex[0] = (byte) (i >>> 24);
873                blockIndex[1] = (byte) (i >>> 16);
874                blockIndex[2] = (byte) (i >>> 8);
875                blockIndex[3] = (byte) i;
876                mac.reset();
877                mac.update(salt);
878                byte[] result = mac.doFinal(blockIndex);
879                System.arraycopy(result, 0, finalResult, offset, result.length);
880                for (int j = 1; j < iterations; j++) {
881                    mac.reset();
882                    result = mac.doFinal(result);
883                    for (int k = 0; k < result.length; k++) {
884                        finalResult[offset + k] ^= result[k];
885                    }
886                }
887            }
888            byte[] key = new byte[keySize];
889            byte[] iv = new byte[ivSize];
890            System.arraycopy(finalResult, 0, key, 0, key.length);
891            System.arraycopy(finalResult, key.length, iv, 0, iv.length);
892            return new DerivedKey(key, iv);
893        }
894    
895        public static byte[] formatAsPKCS8(byte[] privateKey, String oid,
896                                           ASN1Structure pkcs8) {
897            DERInteger derZero = new DERInteger(BigInteger.ZERO);
898            ASN1EncodableVector outterVec = new ASN1EncodableVector();
899            ASN1EncodableVector innerVec = new ASN1EncodableVector();
900            DEROctetString octetsToAppend;
901            try {
902                DERObjectIdentifier derOID = new DERObjectIdentifier(oid);
903                innerVec.add(derOID);
904                if (DSA_OID.equals(oid)) {
905                    if (pkcs8 == null) {
906                        try {
907                            pkcs8 = ASN1Util.analyze(privateKey);
908                        }
909                        catch (Exception e) {
910                            throw new RuntimeException("asn1 parse failure " + e);
911                        }
912                    }
913                    if (pkcs8.derIntegers == null || pkcs8.derIntegers.size() < 6) {
914                        throw new RuntimeException("invalid DSA key - can't find P, Q, G, X");
915                    }
916    
917                    DERInteger[] ints = new DERInteger[pkcs8.derIntegers.size()];
918                    pkcs8.derIntegers.toArray(ints);
919                    DERInteger p = ints[1];
920                    DERInteger q = ints[2];
921                    DERInteger g = ints[3];
922                    DERInteger x = ints[5];
923    
924                    byte[] encodedX = encode(x);
925                    octetsToAppend = new DEROctetString(encodedX);
926                    ASN1EncodableVector pqgVec = new ASN1EncodableVector();
927                    pqgVec.add(p);
928                    pqgVec.add(q);
929                    pqgVec.add(g);
930                    DERSequence pqg = new DERSequence(pqgVec);
931                    innerVec.add(pqg);
932                } else {
933                    innerVec.add(DERNull.INSTANCE);
934                    octetsToAppend = new DEROctetString(privateKey);
935                }
936    
937                DERSequence inner = new DERSequence(innerVec);
938                outterVec.add(derZero);
939                outterVec.add(inner);
940                outterVec.add(octetsToAppend);
941                DERSequence outter = new DERSequence(outterVec);
942                return encode(outter);
943            }
944            catch (IOException ioe) {
945                throw JavaImpl.newRuntimeException(ioe);
946            }
947        }
948    
949        private static boolean allZeroes(byte[] b) {
950            for (int i = 0; i < b.length; i++) {
951                if (b[i] != 0) {
952                    return false;
953                }
954            }
955            return true;
956        }
957    
958        public static byte[] encode(DEREncodable der) throws IOException {
959            ByteArrayOutputStream baos = new ByteArrayOutputStream(1024);
960            ASN1OutputStream out = new ASN1OutputStream(baos);
961            out.writeObject(der);
962            out.close();
963            return baos.toByteArray();
964        }
965    
966        public static void main(String[] args) throws Exception {
967            String password = "changeit";
968            if (args.length == 0) {
969                System.out.println("Usage1:  [password] [file:private-key]      Prints decrypted PKCS8 key (base64).");
970                System.out.println("Usage2:  [password] [file1] [file2] etc...  Checks that all private keys are equal.");
971                System.out.println("Usage2 assumes that all files can be decrypted with the same password.");
972            } else if (args.length == 1 || args.length == 2) {
973                FileInputStream in = new FileInputStream(args[args.length - 1]);
974                if (args.length == 2) {
975                    password = args[0];
976                }
977                byte[] bytes = Util.streamToBytes(in);
978                PKCS8Key key = new PKCS8Key(bytes, password.toCharArray());
979                PEMItem item = new PEMItem(key.getDecryptedBytes(), "PRIVATE KEY");
980                byte[] pem = PEMUtil.encode(Collections.singleton(item));
981                System.out.write(pem);
982            } else {
983                byte[] original = null;
984                File f = new File(args[0]);
985                int i = 0;
986                if (!f.exists()) {
987                    // File0 doesn't exist, so it must be a password!
988                    password = args[0];
989                    i++;
990                }
991                for (; i < args.length; i++) {
992                    FileInputStream in = new FileInputStream(args[i]);
993                    byte[] bytes = Util.streamToBytes(in);
994                    PKCS8Key key = null;
995                    try {
996                        key = new PKCS8Key(bytes, password.toCharArray());
997                    }
998                    catch (Exception e) {
999                        System.out.println(" FAILED! " + args[i] + " " + e);
1000                    }
1001                    if (key != null) {
1002                        byte[] decrypted = key.getDecryptedBytes();
1003                        int keySize = key.getKeySize();
1004                        String keySizeStr = "" + keySize;
1005                        if (keySize < 10) {
1006                            keySizeStr = "  " + keySizeStr;
1007                        } else if (keySize < 100) {
1008                            keySizeStr = " " + keySizeStr;
1009                        }
1010                        StringBuffer buf = new StringBuffer(key.getTransformation());
1011                        int maxLen = "Blowfish/CBC/PKCS5Padding".length();
1012                        for (int j = buf.length(); j < maxLen; j++) {
1013                            buf.append(' ');
1014                        }
1015                        String transform = buf.toString();
1016                        String type = key.isDSA() ? "DSA" : "RSA";
1017    
1018                        if (original == null) {
1019                            original = decrypted;
1020                            System.out.println("   SUCCESS    \t" + type + "\t" + transform + "\t" + keySizeStr + "\t" + args[i]);
1021                        } else {
1022                            boolean identical = Arrays.equals(original, decrypted);
1023                            if (!identical) {
1024                                System.out.println("***FAILURE*** \t" + type + "\t" + transform + "\t" + keySizeStr + "\t" + args[i]);
1025                            } else {
1026                                System.out.println("   SUCCESS    \t" + type + "\t" + transform + "\t" + keySizeStr + "\t" + args[i]);
1027                            }
1028                        }
1029                    }
1030                }
1031            }
1032        }
1033    
1034    }