2
|
1 |
/*
|
|
2 |
* Copyright 1998-2007 Sun Microsystems, Inc. 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. Sun designates this
|
|
8 |
* particular file as subject to the "Classpath" exception as provided
|
|
9 |
* by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
|
|
22 |
* CA 95054 USA or visit www.sun.com if you need additional information or
|
|
23 |
* have any questions.
|
|
24 |
*/
|
|
25 |
|
|
26 |
package com.sun.crypto.provider;
|
|
27 |
|
|
28 |
import java.io.UnsupportedEncodingException;
|
|
29 |
import java.io.IOException;
|
|
30 |
import java.io.Serializable;
|
|
31 |
import java.io.ByteArrayInputStream;
|
|
32 |
import java.io.ByteArrayOutputStream;
|
|
33 |
import java.io.ObjectInputStream;
|
|
34 |
import java.io.ObjectOutputStream;
|
|
35 |
import java.io.ObjectInputStream.GetField;
|
|
36 |
import java.security.Security;
|
|
37 |
import java.security.Key;
|
|
38 |
import java.security.PrivateKey;
|
|
39 |
import java.security.Provider;
|
|
40 |
import java.security.KeyFactory;
|
|
41 |
import java.security.MessageDigest;
|
|
42 |
import java.security.GeneralSecurityException;
|
|
43 |
import java.security.NoSuchAlgorithmException;
|
|
44 |
import java.security.NoSuchProviderException;
|
|
45 |
import java.security.SecureRandom;
|
|
46 |
import java.security.UnrecoverableKeyException;
|
|
47 |
import java.security.InvalidParameterException;
|
|
48 |
import java.security.InvalidAlgorithmParameterException;
|
|
49 |
import java.security.InvalidKeyException;
|
|
50 |
import java.security.AlgorithmParameters;
|
|
51 |
import java.security.spec.InvalidParameterSpecException;
|
|
52 |
import java.security.spec.InvalidKeySpecException;
|
|
53 |
import java.security.spec.PKCS8EncodedKeySpec;
|
|
54 |
|
|
55 |
import javax.crypto.Cipher;
|
|
56 |
import javax.crypto.CipherSpi;
|
|
57 |
import javax.crypto.SecretKey;
|
|
58 |
import javax.crypto.NoSuchPaddingException;
|
|
59 |
import javax.crypto.IllegalBlockSizeException;
|
|
60 |
import javax.crypto.BadPaddingException;
|
|
61 |
import javax.crypto.SealedObject;
|
|
62 |
import javax.crypto.spec.*;
|
|
63 |
import sun.security.x509.AlgorithmId;
|
|
64 |
import sun.security.util.ObjectIdentifier;
|
|
65 |
|
|
66 |
/**
|
|
67 |
* This class implements a protection mechanism for private keys. In JCE, we
|
|
68 |
* use a stronger protection mechanism than in the JDK, because we can use
|
|
69 |
* the <code>Cipher</code> class.
|
|
70 |
* Private keys are protected using the JCE mechanism, and are recovered using
|
|
71 |
* either the JDK or JCE mechanism, depending on how the key has been
|
|
72 |
* protected. This allows us to parse Sun's keystore implementation that ships
|
|
73 |
* with JDK 1.2.
|
|
74 |
*
|
|
75 |
* @author Jan Luehe
|
|
76 |
*
|
|
77 |
*
|
|
78 |
* @see JceKeyStore
|
|
79 |
*/
|
|
80 |
|
|
81 |
final class KeyProtector {
|
|
82 |
|
|
83 |
// defined by SunSoft (SKI project)
|
|
84 |
private static final String PBE_WITH_MD5_AND_DES3_CBC_OID
|
|
85 |
= "1.3.6.1.4.1.42.2.19.1";
|
|
86 |
|
|
87 |
// JavaSoft proprietary key-protection algorithm (used to protect private
|
|
88 |
// keys in the keystore implementation that comes with JDK 1.2)
|
|
89 |
private static final String KEY_PROTECTOR_OID = "1.3.6.1.4.1.42.2.17.1.1";
|
|
90 |
|
|
91 |
private static final int SALT_LEN = 20; // the salt length
|
|
92 |
private static final int DIGEST_LEN = 20;
|
|
93 |
|
|
94 |
// the password used for protecting/recovering keys passed through this
|
|
95 |
// key protector
|
|
96 |
private char[] password;
|
|
97 |
|
|
98 |
private static final Provider PROV = Security.getProvider("SunJCE");
|
|
99 |
|
|
100 |
KeyProtector(char[] password) {
|
|
101 |
if (password == null) {
|
|
102 |
throw new IllegalArgumentException("password can't be null");
|
|
103 |
}
|
|
104 |
this.password = password;
|
|
105 |
}
|
|
106 |
|
|
107 |
/**
|
|
108 |
* Protects the given cleartext private key, using the password provided at
|
|
109 |
* construction time.
|
|
110 |
*/
|
|
111 |
byte[] protect(PrivateKey key)
|
|
112 |
throws Exception
|
|
113 |
{
|
|
114 |
// create a random salt (8 bytes)
|
|
115 |
byte[] salt = new byte[8];
|
|
116 |
SunJCE.RANDOM.nextBytes(salt);
|
|
117 |
|
|
118 |
// create PBE parameters from salt and iteration count
|
|
119 |
PBEParameterSpec pbeSpec = new PBEParameterSpec(salt, 20);
|
|
120 |
|
|
121 |
// create PBE key from password
|
|
122 |
PBEKeySpec pbeKeySpec = new PBEKeySpec(this.password);
|
|
123 |
SecretKey sKey = new PBEKey(pbeKeySpec, "PBEWithMD5AndTripleDES");
|
|
124 |
pbeKeySpec.clearPassword();
|
|
125 |
|
|
126 |
// encrypt private key
|
|
127 |
PBEWithMD5AndTripleDESCipher cipher;
|
|
128 |
cipher = new PBEWithMD5AndTripleDESCipher();
|
|
129 |
cipher.engineInit(Cipher.ENCRYPT_MODE, sKey, pbeSpec, null);
|
|
130 |
byte[] plain = (byte[])key.getEncoded();
|
|
131 |
byte[] encrKey = cipher.engineDoFinal(plain, 0, plain.length);
|
|
132 |
|
|
133 |
// wrap encrypted private key in EncryptedPrivateKeyInfo
|
|
134 |
// (as defined in PKCS#8)
|
|
135 |
AlgorithmParameters pbeParams =
|
|
136 |
AlgorithmParameters.getInstance("PBE", PROV);
|
|
137 |
pbeParams.init(pbeSpec);
|
|
138 |
|
|
139 |
AlgorithmId encrAlg = new AlgorithmId
|
|
140 |
(new ObjectIdentifier(PBE_WITH_MD5_AND_DES3_CBC_OID), pbeParams);
|
|
141 |
return new EncryptedPrivateKeyInfo(encrAlg,encrKey).getEncoded();
|
|
142 |
}
|
|
143 |
|
|
144 |
/*
|
|
145 |
* Recovers the cleartext version of the given key (in protected format),
|
|
146 |
* using the password provided at construction time.
|
|
147 |
*/
|
|
148 |
Key recover(EncryptedPrivateKeyInfo encrInfo)
|
|
149 |
throws UnrecoverableKeyException, NoSuchAlgorithmException
|
|
150 |
{
|
|
151 |
byte[] plain;
|
|
152 |
|
|
153 |
try {
|
|
154 |
String encrAlg = encrInfo.getAlgorithm().getOID().toString();
|
|
155 |
if (!encrAlg.equals(PBE_WITH_MD5_AND_DES3_CBC_OID)
|
|
156 |
&& !encrAlg.equals(KEY_PROTECTOR_OID)) {
|
|
157 |
throw new UnrecoverableKeyException("Unsupported encryption "
|
|
158 |
+ "algorithm");
|
|
159 |
}
|
|
160 |
|
|
161 |
if (encrAlg.equals(KEY_PROTECTOR_OID)) {
|
|
162 |
// JDK 1.2 style recovery
|
|
163 |
plain = recover(encrInfo.getEncryptedData());
|
|
164 |
} else {
|
|
165 |
byte[] encodedParams =
|
|
166 |
encrInfo.getAlgorithm().getEncodedParams();
|
|
167 |
|
|
168 |
// parse the PBE parameters into the corresponding spec
|
|
169 |
AlgorithmParameters pbeParams =
|
|
170 |
AlgorithmParameters.getInstance("PBE");
|
|
171 |
pbeParams.init(encodedParams);
|
|
172 |
PBEParameterSpec pbeSpec = (PBEParameterSpec)
|
|
173 |
pbeParams.getParameterSpec(PBEParameterSpec.class);
|
|
174 |
|
|
175 |
// create PBE key from password
|
|
176 |
PBEKeySpec pbeKeySpec = new PBEKeySpec(this.password);
|
|
177 |
SecretKey sKey =
|
|
178 |
new PBEKey(pbeKeySpec, "PBEWithMD5AndTripleDES");
|
|
179 |
pbeKeySpec.clearPassword();
|
|
180 |
|
|
181 |
// decrypt private key
|
|
182 |
PBEWithMD5AndTripleDESCipher cipher;
|
|
183 |
cipher = new PBEWithMD5AndTripleDESCipher();
|
|
184 |
cipher.engineInit(Cipher.DECRYPT_MODE, sKey, pbeSpec, null);
|
|
185 |
plain=cipher.engineDoFinal(encrInfo.getEncryptedData(), 0,
|
|
186 |
encrInfo.getEncryptedData().length);
|
|
187 |
}
|
|
188 |
|
|
189 |
// determine the private-key algorithm, and parse private key
|
|
190 |
// using the appropriate key factory
|
|
191 |
String oidName = new AlgorithmId
|
|
192 |
(new PrivateKeyInfo(plain).getAlgorithm().getOID()).getName();
|
|
193 |
KeyFactory kFac = KeyFactory.getInstance(oidName);
|
|
194 |
return kFac.generatePrivate(new PKCS8EncodedKeySpec(plain));
|
|
195 |
|
|
196 |
} catch (NoSuchAlgorithmException ex) {
|
|
197 |
// Note: this catch needed to be here because of the
|
|
198 |
// later catch of GeneralSecurityException
|
|
199 |
throw ex;
|
|
200 |
} catch (IOException ioe) {
|
|
201 |
throw new UnrecoverableKeyException(ioe.getMessage());
|
|
202 |
} catch (GeneralSecurityException gse) {
|
|
203 |
throw new UnrecoverableKeyException(gse.getMessage());
|
|
204 |
}
|
|
205 |
}
|
|
206 |
|
|
207 |
/*
|
|
208 |
* Recovers the cleartext version of the given key (in protected format),
|
|
209 |
* using the password provided at construction time. This method implements
|
|
210 |
* the recovery algorithm used by Sun's keystore implementation in
|
|
211 |
* JDK 1.2.
|
|
212 |
*/
|
|
213 |
private byte[] recover(byte[] protectedKey)
|
|
214 |
throws UnrecoverableKeyException, NoSuchAlgorithmException
|
|
215 |
{
|
|
216 |
int i, j;
|
|
217 |
byte[] digest;
|
|
218 |
int numRounds;
|
|
219 |
int xorOffset; // offset in xorKey where next digest will be stored
|
|
220 |
int encrKeyLen; // the length of the encrpyted key
|
|
221 |
|
|
222 |
MessageDigest md = MessageDigest.getInstance("SHA");
|
|
223 |
|
|
224 |
// Get the salt associated with this key (the first SALT_LEN bytes of
|
|
225 |
// <code>protectedKey</code>)
|
|
226 |
byte[] salt = new byte[SALT_LEN];
|
|
227 |
System.arraycopy(protectedKey, 0, salt, 0, SALT_LEN);
|
|
228 |
|
|
229 |
// Determine the number of digest rounds
|
|
230 |
encrKeyLen = protectedKey.length - SALT_LEN - DIGEST_LEN;
|
|
231 |
numRounds = encrKeyLen / DIGEST_LEN;
|
|
232 |
if ((encrKeyLen % DIGEST_LEN) != 0)
|
|
233 |
numRounds++;
|
|
234 |
|
|
235 |
// Get the encrypted key portion and store it in "encrKey"
|
|
236 |
byte[] encrKey = new byte[encrKeyLen];
|
|
237 |
System.arraycopy(protectedKey, SALT_LEN, encrKey, 0, encrKeyLen);
|
|
238 |
|
|
239 |
// Set up the byte array which will be XORed with "encrKey"
|
|
240 |
byte[] xorKey = new byte[encrKey.length];
|
|
241 |
|
|
242 |
// Convert password to byte array, so that it can be digested
|
|
243 |
byte[] passwdBytes = new byte[password.length * 2];
|
|
244 |
for (i=0, j=0; i<password.length; i++) {
|
|
245 |
passwdBytes[j++] = (byte)(password[i] >> 8);
|
|
246 |
passwdBytes[j++] = (byte)password[i];
|
|
247 |
}
|
|
248 |
|
|
249 |
// Compute the digests, and store them in "xorKey"
|
|
250 |
for (i = 0, xorOffset = 0, digest = salt;
|
|
251 |
i < numRounds;
|
|
252 |
i++, xorOffset += DIGEST_LEN) {
|
|
253 |
md.update(passwdBytes);
|
|
254 |
md.update(digest);
|
|
255 |
digest = md.digest();
|
|
256 |
md.reset();
|
|
257 |
// Copy the digest into "xorKey"
|
|
258 |
if (i < numRounds - 1) {
|
|
259 |
System.arraycopy(digest, 0, xorKey, xorOffset,
|
|
260 |
digest.length);
|
|
261 |
} else {
|
|
262 |
System.arraycopy(digest, 0, xorKey, xorOffset,
|
|
263 |
xorKey.length - xorOffset);
|
|
264 |
}
|
|
265 |
}
|
|
266 |
|
|
267 |
// XOR "encrKey" with "xorKey", and store the result in "plainKey"
|
|
268 |
byte[] plainKey = new byte[encrKey.length];
|
|
269 |
for (i = 0; i < plainKey.length; i++) {
|
|
270 |
plainKey[i] = (byte)(encrKey[i] ^ xorKey[i]);
|
|
271 |
}
|
|
272 |
|
|
273 |
// Check the integrity of the recovered key by concatenating it with
|
|
274 |
// the password, digesting the concatenation, and comparing the
|
|
275 |
// result of the digest operation with the digest provided at the end
|
|
276 |
// of <code>protectedKey</code>. If the two digest values are
|
|
277 |
// different, throw an exception.
|
|
278 |
md.update(passwdBytes);
|
|
279 |
java.util.Arrays.fill(passwdBytes, (byte)0x00);
|
|
280 |
passwdBytes = null;
|
|
281 |
md.update(plainKey);
|
|
282 |
digest = md.digest();
|
|
283 |
md.reset();
|
|
284 |
for (i = 0; i < digest.length; i++) {
|
|
285 |
if (digest[i] != protectedKey[SALT_LEN + encrKeyLen + i]) {
|
|
286 |
throw new UnrecoverableKeyException("Cannot recover key");
|
|
287 |
}
|
|
288 |
}
|
|
289 |
return plainKey;
|
|
290 |
}
|
|
291 |
|
|
292 |
/**
|
|
293 |
* Seals the given cleartext key, using the password provided at
|
|
294 |
* construction time
|
|
295 |
*/
|
|
296 |
SealedObject seal(Key key)
|
|
297 |
throws Exception
|
|
298 |
{
|
|
299 |
// create a random salt (8 bytes)
|
|
300 |
byte[] salt = new byte[8];
|
|
301 |
SunJCE.RANDOM.nextBytes(salt);
|
|
302 |
|
|
303 |
// create PBE parameters from salt and iteration count
|
|
304 |
PBEParameterSpec pbeSpec = new PBEParameterSpec(salt, 20);
|
|
305 |
|
|
306 |
// create PBE key from password
|
|
307 |
PBEKeySpec pbeKeySpec = new PBEKeySpec(this.password);
|
|
308 |
SecretKey sKey = new PBEKey(pbeKeySpec, "PBEWithMD5AndTripleDES");
|
|
309 |
pbeKeySpec.clearPassword();
|
|
310 |
|
|
311 |
// seal key
|
|
312 |
Cipher cipher;
|
|
313 |
|
|
314 |
PBEWithMD5AndTripleDESCipher cipherSpi;
|
|
315 |
cipherSpi = new PBEWithMD5AndTripleDESCipher();
|
|
316 |
cipher = new CipherForKeyProtector(cipherSpi, PROV,
|
|
317 |
"PBEWithMD5AndTripleDES");
|
|
318 |
cipher.init(Cipher.ENCRYPT_MODE, sKey, pbeSpec);
|
|
319 |
return new SealedObjectForKeyProtector(key, cipher);
|
|
320 |
}
|
|
321 |
|
|
322 |
/**
|
|
323 |
* Unseals the sealed key.
|
|
324 |
*/
|
|
325 |
Key unseal(SealedObject so)
|
|
326 |
throws NoSuchAlgorithmException, UnrecoverableKeyException
|
|
327 |
{
|
|
328 |
try {
|
|
329 |
// create PBE key from password
|
|
330 |
PBEKeySpec pbeKeySpec = new PBEKeySpec(this.password);
|
|
331 |
SecretKey skey = new PBEKey(pbeKeySpec, "PBEWithMD5AndTripleDES");
|
|
332 |
pbeKeySpec.clearPassword();
|
|
333 |
|
|
334 |
SealedObjectForKeyProtector soForKeyProtector = null;
|
|
335 |
if (!(so instanceof SealedObjectForKeyProtector)) {
|
|
336 |
soForKeyProtector = new SealedObjectForKeyProtector(so);
|
|
337 |
} else {
|
|
338 |
soForKeyProtector = (SealedObjectForKeyProtector)so;
|
|
339 |
}
|
|
340 |
AlgorithmParameters params = soForKeyProtector.getParameters();
|
|
341 |
if (params == null) {
|
|
342 |
throw new UnrecoverableKeyException("Cannot get " +
|
|
343 |
"algorithm parameters");
|
|
344 |
}
|
|
345 |
PBEWithMD5AndTripleDESCipher cipherSpi;
|
|
346 |
cipherSpi = new PBEWithMD5AndTripleDESCipher();
|
|
347 |
Cipher cipher = new CipherForKeyProtector(cipherSpi, PROV,
|
|
348 |
"PBEWithMD5AndTripleDES");
|
|
349 |
cipher.init(Cipher.DECRYPT_MODE, skey, params);
|
|
350 |
return (Key)soForKeyProtector.getObject(cipher);
|
|
351 |
} catch (NoSuchAlgorithmException ex) {
|
|
352 |
// Note: this catch needed to be here because of the
|
|
353 |
// later catch of GeneralSecurityException
|
|
354 |
throw ex;
|
|
355 |
} catch (IOException ioe) {
|
|
356 |
throw new UnrecoverableKeyException(ioe.getMessage());
|
|
357 |
} catch (ClassNotFoundException cnfe) {
|
|
358 |
throw new UnrecoverableKeyException(cnfe.getMessage());
|
|
359 |
} catch (GeneralSecurityException gse) {
|
|
360 |
throw new UnrecoverableKeyException(gse.getMessage());
|
|
361 |
}
|
|
362 |
}
|
|
363 |
}
|
|
364 |
|
|
365 |
|
|
366 |
final class CipherForKeyProtector extends javax.crypto.Cipher {
|
|
367 |
/**
|
|
368 |
* Creates a Cipher object.
|
|
369 |
*
|
|
370 |
* @param cipherSpi the delegate
|
|
371 |
* @param provider the provider
|
|
372 |
* @param transformation the transformation
|
|
373 |
*/
|
|
374 |
protected CipherForKeyProtector(CipherSpi cipherSpi,
|
|
375 |
Provider provider,
|
|
376 |
String transformation) {
|
|
377 |
super(cipherSpi, provider, transformation);
|
|
378 |
}
|
|
379 |
}
|
|
380 |
|
|
381 |
final class SealedObjectForKeyProtector extends javax.crypto.SealedObject {
|
|
382 |
|
|
383 |
static final long serialVersionUID = -3650226485480866989L;
|
|
384 |
|
|
385 |
SealedObjectForKeyProtector(Serializable object, Cipher c)
|
|
386 |
throws IOException, IllegalBlockSizeException {
|
|
387 |
super(object, c);
|
|
388 |
}
|
|
389 |
|
|
390 |
SealedObjectForKeyProtector(SealedObject so) {
|
|
391 |
super(so);
|
|
392 |
}
|
|
393 |
|
|
394 |
AlgorithmParameters getParameters() {
|
|
395 |
AlgorithmParameters params = null;
|
|
396 |
if (super.encodedParams != null) {
|
|
397 |
try {
|
|
398 |
params = AlgorithmParameters.getInstance("PBE", "SunJCE");
|
|
399 |
params.init(super.encodedParams);
|
|
400 |
} catch (NoSuchProviderException nspe) {
|
|
401 |
// eat.
|
|
402 |
} catch (NoSuchAlgorithmException nsae) {
|
|
403 |
//eat.
|
|
404 |
} catch (IOException ioe) {
|
|
405 |
//eat.
|
|
406 |
}
|
|
407 |
}
|
|
408 |
return params;
|
|
409 |
}
|
|
410 |
}
|