1 /*
2  * Copyright (c) 1998, 2019, Oracle and/or its affiliates. All rights reserved.
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * This code is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License version 2 only, as
7  * published by the Free Software Foundation.  Oracle designates this
8  * particular file as subject to the "Classpath" exception as provided
9  * by Oracle in the LICENSE file that accompanied this code.
10  *
11  * This code is distributed in the hope that it will be useful, but WITHOUT
12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14  * version 2 for more details (a copy is included in the LICENSE file that
15  * accompanied this code).
16  *
17  * You should have received a copy of the GNU General Public License version
18  * 2 along with this work; if not, write to the Free Software Foundation,
19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20  *
21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22  * or visit www.oracle.com if you need additional information or have any
23  * questions.
24  */
25 
26 package com.sun.crypto.provider;
27 
28 import java.io.IOException;
29 import java.security.Key;
30 import java.security.PrivateKey;
31 import java.security.Provider;
32 import java.security.KeyFactory;
33 import java.security.MessageDigest;
34 import java.security.GeneralSecurityException;
35 import java.security.NoSuchAlgorithmException;
36 import java.security.UnrecoverableKeyException;
37 import java.security.AlgorithmParameters;
38 import java.security.spec.InvalidParameterSpecException;
39 import java.security.spec.PKCS8EncodedKeySpec;
40 import java.util.Arrays;
41 
42 import javax.crypto.Cipher;
43 import javax.crypto.CipherSpi;
44 import javax.crypto.SecretKey;
45 import javax.crypto.SealedObject;
46 import javax.crypto.spec.*;
47 import javax.security.auth.DestroyFailedException;
48 
49 import sun.security.x509.AlgorithmId;
50 import sun.security.util.ObjectIdentifier;
51 import sun.security.util.KnownOIDs;
52 import sun.security.util.SecurityProperties;
53 
54 /**
55  * This class implements a protection mechanism for private keys. In JCE, we
56  * use a stronger protection mechanism than in the JDK, because we can use
57  * the <code>Cipher</code> class.
58  * Private keys are protected using the JCE mechanism, and are recovered using
59  * either the JDK or JCE mechanism, depending on how the key has been
60  * protected. This allows us to parse Sun's keystore implementation that ships
61  * with JDK 1.2.
62  *
63  * @author Jan Luehe
64  *
65  *
66  * @see JceKeyStore
67  */
68 
69 final class KeyProtector {
70 
71     private static final int MAX_ITERATION_COUNT = 5000000;
72     private static final int MIN_ITERATION_COUNT = 10000;
73     private static final int DEFAULT_ITERATION_COUNT = 200000;
74     private static final int SALT_LEN = 20; // the salt length
75     private static final int DIGEST_LEN = 20;
76     private static final int ITERATION_COUNT;
77 
78     // the password used for protecting/recovering keys passed through this
79     // key protector
80     private char[] password;
81 
82     /**
83      * {@systemProperty jdk.jceks.iterationCount} property indicating the
84      * number of iterations for password-based encryption (PBE) in JCEKS
85      * keystores. Values in the range 10000 to 5000000 are considered valid.
86      * If the value is out of this range, or is not a number, or is
87      * unspecified; a default of 200000 is used.
88      */
89     static {
90         int iterationCount = DEFAULT_ITERATION_COUNT;
91         String ic = SecurityProperties.privilegedGetOverridable(
92                 "jdk.jceks.iterationCount");
93         if (ic != null && !ic.isEmpty()) {
94             try {
95                 iterationCount = Integer.parseInt(ic);
96                 if (iterationCount < MIN_ITERATION_COUNT ||
97                         iterationCount > MAX_ITERATION_COUNT) {
98                     iterationCount = DEFAULT_ITERATION_COUNT;
99                 }
100             } catch (NumberFormatException e) {}
101         }
102         ITERATION_COUNT = iterationCount;
103     }
104 
KeyProtector(char[] password)105     KeyProtector(char[] password) {
106         if (password == null) {
107            throw new IllegalArgumentException("password can't be null");
108         }
109         this.password = password;
110     }
111 
112     /**
113      * Protects the given cleartext private key, using the password provided at
114      * construction time.
115      */
protect(PrivateKey key)116     byte[] protect(PrivateKey key)
117         throws Exception
118     {
119         // create a random salt (8 bytes)
120         byte[] salt = new byte[8];
121         SunJCE.getRandom().nextBytes(salt);
122 
123         // create PBE parameters from salt and iteration count
124         PBEParameterSpec pbeSpec = new PBEParameterSpec(salt, ITERATION_COUNT);
125 
126         // create PBE key from password
127         PBEKeySpec pbeKeySpec = new PBEKeySpec(this.password);
128         SecretKey sKey = null;
129         PBEWithMD5AndTripleDESCipher cipher;
130         try {
131             sKey = new PBEKey(pbeKeySpec, "PBEWithMD5AndTripleDES", false);
132             // encrypt private key
133             cipher = new PBEWithMD5AndTripleDESCipher();
134             cipher.engineInit(Cipher.ENCRYPT_MODE, sKey, pbeSpec, null);
135         } finally {
136             pbeKeySpec.clearPassword();
137             if (sKey != null) sKey.destroy();
138         }
139         byte[] plain = key.getEncoded();
140         byte[] encrKey = cipher.engineDoFinal(plain, 0, plain.length);
141         Arrays.fill(plain, (byte) 0x00);
142 
143         // wrap encrypted private key in EncryptedPrivateKeyInfo
144         // (as defined in PKCS#8)
145         AlgorithmParameters pbeParams =
146             AlgorithmParameters.getInstance("PBE", SunJCE.getInstance());
147         pbeParams.init(pbeSpec);
148 
149         AlgorithmId encrAlg = new AlgorithmId
150             (ObjectIdentifier.of(KnownOIDs.JAVASOFT_JCEKeyProtector),
151              pbeParams);
152         return new EncryptedPrivateKeyInfo(encrAlg,encrKey).getEncoded();
153     }
154 
155     /*
156      * Recovers the cleartext version of the given key (in protected format),
157      * using the password provided at construction time.
158      */
recover(EncryptedPrivateKeyInfo encrInfo)159     Key recover(EncryptedPrivateKeyInfo encrInfo)
160         throws UnrecoverableKeyException, NoSuchAlgorithmException
161     {
162         byte[] plain = null;
163         SecretKey sKey = null;
164         try {
165             String encrAlg = encrInfo.getAlgorithm().getOID().toString();
166             if (!encrAlg.equals(KnownOIDs.JAVASOFT_JCEKeyProtector.value())
167                 && !encrAlg.equals(KnownOIDs.JAVASOFT_JDKKeyProtector.value())) {
168                 throw new UnrecoverableKeyException("Unsupported encryption "
169                                                     + "algorithm");
170             }
171 
172             if (encrAlg.equals(KnownOIDs.JAVASOFT_JDKKeyProtector.value())) {
173                 // JDK 1.2 style recovery
174                 plain = recover(encrInfo.getEncryptedData());
175             } else {
176                 byte[] encodedParams =
177                     encrInfo.getAlgorithm().getEncodedParams();
178 
179                 // parse the PBE parameters into the corresponding spec
180                 AlgorithmParameters pbeParams =
181                     AlgorithmParameters.getInstance("PBE");
182                 pbeParams.init(encodedParams);
183                 PBEParameterSpec pbeSpec =
184                         pbeParams.getParameterSpec(PBEParameterSpec.class);
185                 if (pbeSpec.getIterationCount() > MAX_ITERATION_COUNT) {
186                     throw new IOException("PBE iteration count too large");
187                 }
188 
189                 // create PBE key from password
190                 PBEKeySpec pbeKeySpec = new PBEKeySpec(this.password);
191                 sKey = new PBEKey(pbeKeySpec, "PBEWithMD5AndTripleDES", false);
192                 pbeKeySpec.clearPassword();
193 
194                 // decrypt private key
195                 PBEWithMD5AndTripleDESCipher cipher;
196                 cipher = new PBEWithMD5AndTripleDESCipher();
197                 cipher.engineInit(Cipher.DECRYPT_MODE, sKey, pbeSpec, null);
198                 plain=cipher.engineDoFinal(encrInfo.getEncryptedData(), 0,
199                                            encrInfo.getEncryptedData().length);
200             }
201 
202             // determine the private-key algorithm, and parse private key
203             // using the appropriate key factory
204             String oidName = new AlgorithmId
205                 (new PrivateKeyInfo(plain).getAlgorithm().getOID()).getName();
206             KeyFactory kFac = KeyFactory.getInstance(oidName);
207             return kFac.generatePrivate(new PKCS8EncodedKeySpec(plain));
208         } catch (NoSuchAlgorithmException ex) {
209             // Note: this catch needed to be here because of the
210             // later catch of GeneralSecurityException
211             throw ex;
212         } catch (IOException ioe) {
213             throw new UnrecoverableKeyException(ioe.getMessage());
214         } catch (GeneralSecurityException gse) {
215             throw new UnrecoverableKeyException(gse.getMessage());
216         } finally {
217             if (plain != null) Arrays.fill(plain, (byte) 0x00);
218             if (sKey != null) {
219                 try {
220                     sKey.destroy();
221                 } catch (DestroyFailedException e) {
222                     //shouldn't happen
223                 }
224             }
225         }
226     }
227 
228     /*
229      * Recovers the cleartext version of the given key (in protected format),
230      * using the password provided at construction time. This method implements
231      * the recovery algorithm used by Sun's keystore implementation in
232      * JDK 1.2.
233      */
recover(byte[] protectedKey)234     private byte[] recover(byte[] protectedKey)
235         throws UnrecoverableKeyException, NoSuchAlgorithmException
236     {
237         int i, j;
238         byte[] digest;
239         int numRounds;
240         int xorOffset; // offset in xorKey where next digest will be stored
241         int encrKeyLen; // the length of the encrpyted key
242 
243         MessageDigest md = MessageDigest.getInstance("SHA");
244 
245         // Get the salt associated with this key (the first SALT_LEN bytes of
246         // <code>protectedKey</code>)
247         byte[] salt = new byte[SALT_LEN];
248         System.arraycopy(protectedKey, 0, salt, 0, SALT_LEN);
249 
250         // Determine the number of digest rounds
251         encrKeyLen = protectedKey.length - SALT_LEN - DIGEST_LEN;
252         numRounds = encrKeyLen / DIGEST_LEN;
253         if ((encrKeyLen % DIGEST_LEN) != 0)
254             numRounds++;
255 
256         // Get the encrypted key portion and store it in "encrKey"
257         byte[] encrKey = new byte[encrKeyLen];
258         System.arraycopy(protectedKey, SALT_LEN, encrKey, 0, encrKeyLen);
259 
260         // Set up the byte array which will be XORed with "encrKey"
261         byte[] xorKey = new byte[encrKey.length];
262 
263         // Convert password to byte array, so that it can be digested
264         byte[] passwdBytes = new byte[password.length * 2];
265         for (i=0, j=0; i<password.length; i++) {
266             passwdBytes[j++] = (byte)(password[i] >> 8);
267             passwdBytes[j++] = (byte)password[i];
268         }
269 
270         // Compute the digests, and store them in "xorKey"
271         for (i = 0, xorOffset = 0, digest = salt;
272              i < numRounds;
273              i++, xorOffset += DIGEST_LEN) {
274             md.update(passwdBytes);
275             md.update(digest);
276             digest = md.digest();
277             md.reset();
278             // Copy the digest into "xorKey"
279             if (i < numRounds - 1) {
280                 System.arraycopy(digest, 0, xorKey, xorOffset,
281                                  digest.length);
282             } else {
283                 System.arraycopy(digest, 0, xorKey, xorOffset,
284                                  xorKey.length - xorOffset);
285             }
286         }
287 
288         // XOR "encrKey" with "xorKey", and store the result in "plainKey"
289         byte[] plainKey = new byte[encrKey.length];
290         for (i = 0; i < plainKey.length; i++) {
291             plainKey[i] = (byte)(encrKey[i] ^ xorKey[i]);
292         }
293 
294         // Check the integrity of the recovered key by concatenating it with
295         // the password, digesting the concatenation, and comparing the
296         // result of the digest operation with the digest provided at the end
297         // of <code>protectedKey</code>. If the two digest values are
298         // different, throw an exception.
299         md.update(passwdBytes);
300         Arrays.fill(passwdBytes, (byte)0x00);
301         passwdBytes = null;
302         md.update(plainKey);
303         digest = md.digest();
304         md.reset();
305         for (i = 0; i < digest.length; i++) {
306             if (digest[i] != protectedKey[SALT_LEN + encrKeyLen + i]) {
307                 throw new UnrecoverableKeyException("Cannot recover key");
308             }
309         }
310         return plainKey;
311     }
312 
313     /**
314      * Seals the given cleartext key, using the password provided at
315      * construction time
316      */
seal(Key key)317     SealedObject seal(Key key)
318         throws Exception
319     {
320         // create a random salt (8 bytes)
321         byte[] salt = new byte[8];
322         SunJCE.getRandom().nextBytes(salt);
323 
324         // create PBE parameters from salt and iteration count
325         PBEParameterSpec pbeSpec = new PBEParameterSpec(salt, ITERATION_COUNT);
326 
327         // create PBE key from password
328         PBEKeySpec pbeKeySpec = new PBEKeySpec(this.password);
329         SecretKey sKey = null;
330         Cipher cipher;
331         try {
332             sKey = new PBEKey(pbeKeySpec, "PBEWithMD5AndTripleDES", false);
333             pbeKeySpec.clearPassword();
334 
335             // seal key
336             PBEWithMD5AndTripleDESCipher cipherSpi;
337             cipherSpi = new PBEWithMD5AndTripleDESCipher();
338             cipher = new CipherForKeyProtector(cipherSpi, SunJCE.getInstance(),
339                                                "PBEWithMD5AndTripleDES");
340             cipher.init(Cipher.ENCRYPT_MODE, sKey, pbeSpec);
341         } finally {
342             if (sKey != null) sKey.destroy();
343         }
344         return new SealedObjectForKeyProtector(key, cipher);
345     }
346 
347     /**
348      * Unseals the sealed key.
349      *
350      * @param maxLength Maximum possible length of so.
351      *                  If bigger, must be illegal.
352      */
unseal(SealedObject so, int maxLength)353     Key unseal(SealedObject so, int maxLength)
354         throws NoSuchAlgorithmException, UnrecoverableKeyException {
355         SecretKey sKey = null;
356         try {
357             // create PBE key from password
358             PBEKeySpec pbeKeySpec = new PBEKeySpec(this.password);
359             sKey = new PBEKey(pbeKeySpec,
360                     "PBEWithMD5AndTripleDES", false);
361             pbeKeySpec.clearPassword();
362 
363             SealedObjectForKeyProtector soForKeyProtector = null;
364             if (!(so instanceof SealedObjectForKeyProtector)) {
365                 soForKeyProtector = new SealedObjectForKeyProtector(so);
366             } else {
367                 soForKeyProtector = (SealedObjectForKeyProtector)so;
368             }
369             AlgorithmParameters params = soForKeyProtector.getParameters();
370             if (params == null) {
371                 throw new UnrecoverableKeyException("Cannot get " +
372                                                     "algorithm parameters");
373             }
374             PBEParameterSpec pbeSpec;
375             try {
376                 pbeSpec = params.getParameterSpec(PBEParameterSpec.class);
377             } catch (InvalidParameterSpecException ipse) {
378                 throw new IOException("Invalid PBE algorithm parameters");
379             }
380             if (pbeSpec.getIterationCount() > MAX_ITERATION_COUNT) {
381                 throw new IOException("PBE iteration count too large");
382             }
383             PBEWithMD5AndTripleDESCipher cipherSpi;
384             cipherSpi = new PBEWithMD5AndTripleDESCipher();
385             Cipher cipher = new CipherForKeyProtector(cipherSpi,
386                                                       SunJCE.getInstance(),
387                                                       "PBEWithMD5AndTripleDES");
388             cipher.init(Cipher.DECRYPT_MODE, sKey, params);
389             return soForKeyProtector.getKey(cipher, maxLength);
390         } catch (NoSuchAlgorithmException ex) {
391             // Note: this catch needed to be here because of the
392             // later catch of GeneralSecurityException
393             throw ex;
394         } catch (IOException ioe) {
395             throw new UnrecoverableKeyException(ioe.getMessage());
396         } catch (ClassNotFoundException cnfe) {
397             throw new UnrecoverableKeyException(cnfe.getMessage());
398         } catch (GeneralSecurityException gse) {
399             throw new UnrecoverableKeyException(gse.getMessage());
400         } finally {
401             if (sKey != null) {
402                 try {
403                     sKey.destroy();
404                 } catch (DestroyFailedException e) {
405                     //shouldn't happen
406                 }
407             }
408         }
409     }
410 }
411 
412 
413 final class CipherForKeyProtector extends javax.crypto.Cipher {
414     /**
415      * Creates a Cipher object.
416      *
417      * @param cipherSpi the delegate
418      * @param provider the provider
419      * @param transformation the transformation
420      */
CipherForKeyProtector(CipherSpi cipherSpi, Provider provider, String transformation)421     protected CipherForKeyProtector(CipherSpi cipherSpi,
422                                     Provider provider,
423                                     String transformation) {
424         super(cipherSpi, provider, transformation);
425     }
426 }
427