1/*
2 * Copyright (c) 1997, 2017, 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
26package sun.security.provider;
27
28import java.io.IOException;
29import java.io.UnsupportedEncodingException;
30import java.security.Key;
31import java.security.KeyStoreException;
32import java.security.MessageDigest;
33import java.security.NoSuchAlgorithmException;
34import java.security.SecureRandom;
35import java.security.UnrecoverableKeyException;
36import java.util.*;
37
38import jdk.internal.ref.CleanerFactory;
39import sun.security.pkcs.PKCS8Key;
40import sun.security.pkcs.EncryptedPrivateKeyInfo;
41import sun.security.x509.AlgorithmId;
42import sun.security.util.ObjectIdentifier;
43import sun.security.util.DerValue;
44
45/**
46 * This is an implementation of a Sun proprietary, exportable algorithm
47 * intended for use when protecting (or recovering the cleartext version of)
48 * sensitive keys.
49 * This algorithm is not intended as a general purpose cipher.
50 *
51 * This is how the algorithm works for key protection:
52 *
53 * p - user password
54 * s - random salt
55 * X - xor key
56 * P - to-be-protected key
57 * Y - protected key
58 * R - what gets stored in the keystore
59 *
60 * Step 1:
61 * Take the user's password, append a random salt (of fixed size) to it,
62 * and hash it: d1 = digest(p, s)
63 * Store d1 in X.
64 *
65 * Step 2:
66 * Take the user's password, append the digest result from the previous step,
67 * and hash it: dn = digest(p, dn-1).
68 * Store dn in X (append it to the previously stored digests).
69 * Repeat this step until the length of X matches the length of the private key
70 * P.
71 *
72 * Step 3:
73 * XOR X and P, and store the result in Y: Y = X XOR P.
74 *
75 * Step 4:
76 * Store s, Y, and digest(p, P) in the result buffer R:
77 * R = s + Y + digest(p, P), where "+" denotes concatenation.
78 * (NOTE: digest(p, P) is stored in the result buffer, so that when the key is
79 * recovered, we can check if the recovered key indeed matches the original
80 * key.) R is stored in the keystore.
81 *
82 * The protected key is recovered as follows:
83 *
84 * Step1 and Step2 are the same as above, except that the salt is not randomly
85 * generated, but taken from the result R of step 4 (the first length(s)
86 * bytes).
87 *
88 * Step 3 (XOR operation) yields the plaintext key.
89 *
90 * Then concatenate the password with the recovered key, and compare with the
91 * last length(digest(p, P)) bytes of R. If they match, the recovered key is
92 * indeed the same key as the original key.
93 *
94 * @author Jan Luehe
95 *
96 *
97 * @see java.security.KeyStore
98 * @see JavaKeyStore
99 * @see KeyTool
100 *
101 * @since 1.2
102 */
103
104final class KeyProtector {
105
106    private static final int SALT_LEN = 20; // the salt length
107    private static final String DIGEST_ALG = "SHA";
108    private static final int DIGEST_LEN = 20;
109
110    // defined by JavaSoft
111    private static final String KEY_PROTECTOR_OID = "1.3.6.1.4.1.42.2.17.1.1";
112
113    // The password used for protecting/recovering keys passed through this
114    // key protector. We store it as a byte array, so that we can digest it.
115    private byte[] passwdBytes;
116
117    private MessageDigest md;
118
119
120    /**
121     * Creates an instance of this class, and initializes it with the given
122     * password.
123     *
124     * <p>The password is expected to be in printable ASCII.
125     * Normal rules for good password selection apply: at least
126     * seven characters, mixed case, with punctuation encouraged.
127     * Phrases or words which are easily guessed, for example by
128     * being found in dictionaries, are bad.
129     */
130    public KeyProtector(char[] password)
131        throws NoSuchAlgorithmException
132    {
133        int i, j;
134
135        if (password == null) {
136           throw new IllegalArgumentException("password can't be null");
137        }
138        md = MessageDigest.getInstance(DIGEST_ALG);
139        // Convert password to byte array, so that it can be digested
140        passwdBytes = new byte[password.length * 2];
141        for (i=0, j=0; i<password.length; i++) {
142            passwdBytes[j++] = (byte)(password[i] >> 8);
143            passwdBytes[j++] = (byte)password[i];
144        }
145        // Use the cleaner to zero the password when no longer referenced
146        final byte[] k = this.passwdBytes;
147        CleanerFactory.cleaner().register(this,
148                () -> java.util.Arrays.fill(k, (byte)0x00));
149    }
150
151    /*
152     * Protects the given plaintext key, using the password provided at
153     * construction time.
154     */
155    public byte[] protect(Key key) throws KeyStoreException
156    {
157        int i;
158        int numRounds;
159        byte[] digest;
160        int xorOffset; // offset in xorKey where next digest will be stored
161        int encrKeyOffset = 0;
162
163        if (key == null) {
164            throw new IllegalArgumentException("plaintext key can't be null");
165        }
166
167        if (!"PKCS#8".equalsIgnoreCase(key.getFormat())) {
168            throw new KeyStoreException(
169                "Cannot get key bytes, not PKCS#8 encoded");
170        }
171
172        byte[] plainKey = key.getEncoded();
173        if (plainKey == null) {
174            throw new KeyStoreException(
175                "Cannot get key bytes, encoding not supported");
176        }
177
178        // Determine the number of digest rounds
179        numRounds = plainKey.length / DIGEST_LEN;
180        if ((plainKey.length % DIGEST_LEN) != 0)
181            numRounds++;
182
183        // Create a random salt
184        byte[] salt = new byte[SALT_LEN];
185        SecureRandom random = new SecureRandom();
186        random.nextBytes(salt);
187
188        // Set up the byte array which will be XORed with "plainKey"
189        byte[] xorKey = new byte[plainKey.length];
190
191        // Compute the digests, and store them in "xorKey"
192        for (i = 0, xorOffset = 0, digest = salt;
193             i < numRounds;
194             i++, xorOffset += DIGEST_LEN) {
195            md.update(passwdBytes);
196            md.update(digest);
197            digest = md.digest();
198            md.reset();
199            // Copy the digest into "xorKey"
200            if (i < numRounds - 1) {
201                System.arraycopy(digest, 0, xorKey, xorOffset,
202                                 digest.length);
203            } else {
204                System.arraycopy(digest, 0, xorKey, xorOffset,
205                                 xorKey.length - xorOffset);
206            }
207        }
208
209        // XOR "plainKey" with "xorKey", and store the result in "tmpKey"
210        byte[] tmpKey = new byte[plainKey.length];
211        for (i = 0; i < tmpKey.length; i++) {
212            tmpKey[i] = (byte)(plainKey[i] ^ xorKey[i]);
213        }
214
215        // Store salt and "tmpKey" in "encrKey"
216        byte[] encrKey = new byte[salt.length + tmpKey.length + DIGEST_LEN];
217        System.arraycopy(salt, 0, encrKey, encrKeyOffset, salt.length);
218        encrKeyOffset += salt.length;
219        System.arraycopy(tmpKey, 0, encrKey, encrKeyOffset, tmpKey.length);
220        encrKeyOffset += tmpKey.length;
221
222        // Append digest(password, plainKey) as an integrity check to "encrKey"
223        md.update(passwdBytes);
224        Arrays.fill(passwdBytes, (byte)0x00);
225        passwdBytes = null;
226        md.update(plainKey);
227        digest = md.digest();
228        md.reset();
229        System.arraycopy(digest, 0, encrKey, encrKeyOffset, digest.length);
230
231        // wrap the protected private key in a PKCS#8-style
232        // EncryptedPrivateKeyInfo, and returns its encoding
233        AlgorithmId encrAlg;
234        try {
235            encrAlg = new AlgorithmId(new ObjectIdentifier(KEY_PROTECTOR_OID));
236            return new EncryptedPrivateKeyInfo(encrAlg,encrKey).getEncoded();
237        } catch (IOException ioe) {
238            throw new KeyStoreException(ioe.getMessage());
239        }
240    }
241
242    /*
243     * Recovers the plaintext version of the given key (in protected format),
244     * using the password provided at construction time.
245     */
246    public Key recover(EncryptedPrivateKeyInfo encrInfo)
247        throws UnrecoverableKeyException
248    {
249        int i;
250        byte[] digest;
251        int numRounds;
252        int xorOffset; // offset in xorKey where next digest will be stored
253        int encrKeyLen; // the length of the encrpyted key
254
255        // do we support the algorithm?
256        AlgorithmId encrAlg = encrInfo.getAlgorithm();
257        if (!(encrAlg.getOID().toString().equals(KEY_PROTECTOR_OID))) {
258            throw new UnrecoverableKeyException("Unsupported key protection "
259                                                + "algorithm");
260        }
261
262        byte[] protectedKey = encrInfo.getEncryptedData();
263
264        /*
265         * Get the salt associated with this key (the first SALT_LEN bytes of
266         * <code>protectedKey</code>)
267         */
268        byte[] salt = new byte[SALT_LEN];
269        System.arraycopy(protectedKey, 0, salt, 0, SALT_LEN);
270
271        // Determine the number of digest rounds
272        encrKeyLen = protectedKey.length - SALT_LEN - DIGEST_LEN;
273        numRounds = encrKeyLen / DIGEST_LEN;
274        if ((encrKeyLen % DIGEST_LEN) != 0) numRounds++;
275
276        // Get the encrypted key portion and store it in "encrKey"
277        byte[] encrKey = new byte[encrKeyLen];
278        System.arraycopy(protectedKey, SALT_LEN, encrKey, 0, encrKeyLen);
279
280        // Set up the byte array which will be XORed with "encrKey"
281        byte[] xorKey = new byte[encrKey.length];
282
283        // Compute the digests, and store them in "xorKey"
284        for (i = 0, xorOffset = 0, digest = salt;
285             i < numRounds;
286             i++, xorOffset += DIGEST_LEN) {
287            md.update(passwdBytes);
288            md.update(digest);
289            digest = md.digest();
290            md.reset();
291            // Copy the digest into "xorKey"
292            if (i < numRounds - 1) {
293                System.arraycopy(digest, 0, xorKey, xorOffset,
294                                 digest.length);
295            } else {
296                System.arraycopy(digest, 0, xorKey, xorOffset,
297                                 xorKey.length - xorOffset);
298            }
299        }
300
301        // XOR "encrKey" with "xorKey", and store the result in "plainKey"
302        byte[] plainKey = new byte[encrKey.length];
303        for (i = 0; i < plainKey.length; i++) {
304            plainKey[i] = (byte)(encrKey[i] ^ xorKey[i]);
305        }
306
307        /*
308         * Check the integrity of the recovered key by concatenating it with
309         * the password, digesting the concatenation, and comparing the
310         * result of the digest operation with the digest provided at the end
311         * of <code>protectedKey</code>. If the two digest values are
312         * different, throw an exception.
313         */
314        md.update(passwdBytes);
315        Arrays.fill(passwdBytes, (byte)0x00);
316        passwdBytes = null;
317        md.update(plainKey);
318        digest = md.digest();
319        md.reset();
320        for (i = 0; i < digest.length; i++) {
321            if (digest[i] != protectedKey[SALT_LEN + encrKeyLen + i]) {
322                throw new UnrecoverableKeyException("Cannot recover key");
323            }
324        }
325
326        // The parseKey() method of PKCS8Key parses the key
327        // algorithm and instantiates the appropriate key factory,
328        // which in turn parses the key material.
329        try {
330            return PKCS8Key.parseKey(new DerValue(plainKey));
331        } catch (IOException ioe) {
332            throw new UnrecoverableKeyException(ioe.getMessage());
333        }
334    }
335}
336