1 /* 2 * Copyright (c) 2001, 2007, 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 javax.crypto; 27 28 import java.io.*; 29 import java.security.*; 30 import java.security.spec.*; 31 import sun.security.x509.AlgorithmId; 32 import sun.security.util.DerValue; 33 import sun.security.util.DerInputStream; 34 import sun.security.util.DerOutputStream; 35 36 /** 37 * This class implements the <code>EncryptedPrivateKeyInfo</code> type 38 * as defined in PKCS #8. 39 * <p>Its ASN.1 definition is as follows: 40 * 41 * <pre> 42 * EncryptedPrivateKeyInfo ::= SEQUENCE { 43 * encryptionAlgorithm AlgorithmIdentifier, 44 * encryptedData OCTET STRING } 45 * 46 * AlgorithmIdentifier ::= SEQUENCE { 47 * algorithm OBJECT IDENTIFIER, 48 * parameters ANY DEFINED BY algorithm OPTIONAL } 49 * </pre> 50 * 51 * @author Valerie Peng 52 * 53 * @see java.security.spec.PKCS8EncodedKeySpec 54 * 55 * @since 1.4 56 */ 57 58 public class EncryptedPrivateKeyInfo { 59 60 // the "encryptionAlgorithm" field 61 private AlgorithmId algid; 62 63 // the "encryptedData" field 64 private byte[] encryptedData; 65 66 // the ASN.1 encoded contents of this class 67 private byte[] encoded = null; 68 69 /** 70 * Constructs (i.e., parses) an <code>EncryptedPrivateKeyInfo</code> from 71 * its ASN.1 encoding. 72 * @param encoded the ASN.1 encoding of this object. The contents of 73 * the array are copied to protect against subsequent modification. 74 * @exception NullPointerException if the <code>encoded</code> is null. 75 * @exception IOException if error occurs when parsing the ASN.1 encoding. 76 */ EncryptedPrivateKeyInfo(byte[] encoded)77 public EncryptedPrivateKeyInfo(byte[] encoded) 78 throws IOException { 79 if (encoded == null) { 80 throw new NullPointerException("the encoded parameter " + 81 "must be non-null"); 82 } 83 this.encoded = (byte[])encoded.clone(); 84 DerValue val = new DerValue(this.encoded); 85 86 DerValue[] seq = new DerValue[2]; 87 88 seq[0] = val.data.getDerValue(); 89 seq[1] = val.data.getDerValue(); 90 91 if (val.data.available() != 0) { 92 throw new IOException("overrun, bytes = " + val.data.available()); 93 } 94 95 this.algid = AlgorithmId.parse(seq[0]); 96 if (seq[0].data.available() != 0) { 97 throw new IOException("encryptionAlgorithm field overrun"); 98 } 99 100 this.encryptedData = seq[1].getOctetString(); 101 if (seq[1].data.available() != 0) { 102 throw new IOException("encryptedData field overrun"); 103 } 104 } 105 106 /** 107 * Constructs an <code>EncryptedPrivateKeyInfo</code> from the 108 * encryption algorithm name and the encrypted data. 109 * 110 * <p>Note: This constructor will use null as the value of the 111 * algorithm parameters. If the encryption algorithm has 112 * parameters whose value is not null, a different constructor, 113 * e.g. EncryptedPrivateKeyInfo(AlgorithmParameters, byte[]), 114 * should be used. 115 * 116 * @param algName encryption algorithm name. See Appendix A in the 117 * <a href= 118 * "{@docRoot}openjdk-redirect.html?v=8&path=/technotes/guides/security/crypto/CryptoSpec.html#AppA"> 119 * Java Cryptography Architecture Reference Guide</a> 120 * for information about standard Cipher algorithm names. 121 * @param encryptedData encrypted data. The contents of 122 * <code>encrypedData</code> are copied to protect against subsequent 123 * modification when constructing this object. 124 * @exception NullPointerException if <code>algName</code> or 125 * <code>encryptedData</code> is null. 126 * @exception IllegalArgumentException if <code>encryptedData</code> 127 * is empty, i.e. 0-length. 128 * @exception NoSuchAlgorithmException if the specified algName is 129 * not supported. 130 */ EncryptedPrivateKeyInfo(String algName, byte[] encryptedData)131 public EncryptedPrivateKeyInfo(String algName, byte[] encryptedData) 132 throws NoSuchAlgorithmException { 133 134 if (algName == null) 135 throw new NullPointerException("the algName parameter " + 136 "must be non-null"); 137 this.algid = AlgorithmId.get(algName); 138 139 if (encryptedData == null) { 140 throw new NullPointerException("the encryptedData " + 141 "parameter must be non-null"); 142 } else if (encryptedData.length == 0) { 143 throw new IllegalArgumentException("the encryptedData " + 144 "parameter must not be empty"); 145 } else { 146 this.encryptedData = (byte[])encryptedData.clone(); 147 } 148 // delay the generation of ASN.1 encoding until 149 // getEncoded() is called 150 this.encoded = null; 151 } 152 153 /** 154 * Constructs an <code>EncryptedPrivateKeyInfo</code> from the 155 * encryption algorithm parameters and the encrypted data. 156 * 157 * @param algParams the algorithm parameters for the encryption 158 * algorithm. <code>algParams.getEncoded()</code> should return 159 * the ASN.1 encoded bytes of the <code>parameters</code> field 160 * of the <code>AlgorithmIdentifer</code> component of the 161 * <code>EncryptedPrivateKeyInfo</code> type. 162 * @param encryptedData encrypted data. The contents of 163 * <code>encrypedData</code> are copied to protect against 164 * subsequent modification when constructing this object. 165 * @exception NullPointerException if <code>algParams</code> or 166 * <code>encryptedData</code> is null. 167 * @exception IllegalArgumentException if <code>encryptedData</code> 168 * is empty, i.e. 0-length. 169 * @exception NoSuchAlgorithmException if the specified algName of 170 * the specified <code>algParams</code> parameter is not supported. 171 */ EncryptedPrivateKeyInfo(AlgorithmParameters algParams, byte[] encryptedData)172 public EncryptedPrivateKeyInfo(AlgorithmParameters algParams, 173 byte[] encryptedData) throws NoSuchAlgorithmException { 174 175 if (algParams == null) { 176 throw new NullPointerException("algParams must be non-null"); 177 } 178 this.algid = AlgorithmId.get(algParams); 179 180 if (encryptedData == null) { 181 throw new NullPointerException("encryptedData must be non-null"); 182 } else if (encryptedData.length == 0) { 183 throw new IllegalArgumentException("the encryptedData " + 184 "parameter must not be empty"); 185 } else { 186 this.encryptedData = (byte[])encryptedData.clone(); 187 } 188 189 // delay the generation of ASN.1 encoding until 190 // getEncoded() is called 191 this.encoded = null; 192 } 193 194 195 /** 196 * Returns the encryption algorithm. 197 * <p>Note: Standard name is returned instead of the specified one 198 * in the constructor when such mapping is available. 199 * See Appendix A in the 200 * <a href= 201 * "{@docRoot}openjdk-redirect.html?v=8&path=/technotes/guides/security/crypto/CryptoSpec.html#AppA"> 202 * Java Cryptography Architecture Reference Guide</a> 203 * for information about standard Cipher algorithm names. 204 * 205 * @return the encryption algorithm name. 206 */ getAlgName()207 public String getAlgName() { 208 return this.algid.getName(); 209 } 210 211 /** 212 * Returns the algorithm parameters used by the encryption algorithm. 213 * @return the algorithm parameters. 214 */ getAlgParameters()215 public AlgorithmParameters getAlgParameters() { 216 return this.algid.getParameters(); 217 } 218 219 /** 220 * Returns the encrypted data. 221 * @return the encrypted data. Returns a new array 222 * each time this method is called. 223 */ getEncryptedData()224 public byte[] getEncryptedData() { 225 return (byte[])this.encryptedData.clone(); 226 } 227 228 /** 229 * Extract the enclosed PKCS8EncodedKeySpec object from the 230 * encrypted data and return it. 231 * <br>Note: In order to successfully retrieve the enclosed 232 * PKCS8EncodedKeySpec object, <code>cipher</code> needs 233 * to be initialized to either Cipher.DECRYPT_MODE or 234 * Cipher.UNWRAP_MODE, with the same key and parameters used 235 * for generating the encrypted data. 236 * 237 * @param cipher the initialized cipher object which will be 238 * used for decrypting the encrypted data. 239 * @return the PKCS8EncodedKeySpec object. 240 * @exception NullPointerException if <code>cipher</code> 241 * is null. 242 * @exception InvalidKeySpecException if the given cipher is 243 * inappropriate for the encrypted data or the encrypted 244 * data is corrupted and cannot be decrypted. 245 */ getKeySpec(Cipher cipher)246 public PKCS8EncodedKeySpec getKeySpec(Cipher cipher) 247 throws InvalidKeySpecException { 248 byte[] encoded = null; 249 try { 250 encoded = cipher.doFinal((byte[])encryptedData); 251 checkPKCS8Encoding(encoded); 252 } catch (GeneralSecurityException gse) { 253 InvalidKeySpecException ikse = new 254 InvalidKeySpecException( 255 "Cannot retrieve the PKCS8EncodedKeySpec"); 256 ikse.initCause(gse); 257 throw ikse; 258 } catch (IOException ioe) { 259 InvalidKeySpecException ikse = new 260 InvalidKeySpecException( 261 "Cannot retrieve the PKCS8EncodedKeySpec"); 262 ikse.initCause(ioe); 263 throw ikse; 264 } catch (IllegalStateException ise) { 265 InvalidKeySpecException ikse = new 266 InvalidKeySpecException( 267 "Cannot retrieve the PKCS8EncodedKeySpec"); 268 ikse.initCause(ise); 269 throw ikse; 270 } 271 return new PKCS8EncodedKeySpec(encoded); 272 } 273 getKeySpecImpl(Key decryptKey, Provider provider)274 private PKCS8EncodedKeySpec getKeySpecImpl(Key decryptKey, 275 Provider provider) throws NoSuchAlgorithmException, 276 InvalidKeyException { 277 byte[] encoded = null; 278 Cipher c; 279 try { 280 if (provider == null) { 281 // use the most preferred one 282 c = Cipher.getInstance(algid.getName()); 283 } else { 284 c = Cipher.getInstance(algid.getName(), provider); 285 } 286 c.init(Cipher.DECRYPT_MODE, decryptKey, algid.getParameters()); 287 encoded = c.doFinal(encryptedData); 288 checkPKCS8Encoding(encoded); 289 } catch (NoSuchAlgorithmException nsae) { 290 // rethrow 291 throw nsae; 292 } catch (GeneralSecurityException gse) { 293 InvalidKeyException ike = new InvalidKeyException 294 ("Cannot retrieve the PKCS8EncodedKeySpec"); 295 ike.initCause(gse); 296 throw ike; 297 } catch (IOException ioe) { 298 InvalidKeyException ike = new InvalidKeyException 299 ("Cannot retrieve the PKCS8EncodedKeySpec"); 300 ike.initCause(ioe); 301 throw ike; 302 } 303 return new PKCS8EncodedKeySpec(encoded); 304 } 305 306 /** 307 * Extract the enclosed PKCS8EncodedKeySpec object from the 308 * encrypted data and return it. 309 * @param decryptKey key used for decrypting the encrypted data. 310 * @return the PKCS8EncodedKeySpec object. 311 * @exception NullPointerException if <code>decryptKey</code> 312 * is null. 313 * @exception NoSuchAlgorithmException if cannot find appropriate 314 * cipher to decrypt the encrypted data. 315 * @exception InvalidKeyException if <code>decryptKey</code> 316 * cannot be used to decrypt the encrypted data or the decryption 317 * result is not a valid PKCS8KeySpec. 318 * 319 * @since 1.5 320 */ getKeySpec(Key decryptKey)321 public PKCS8EncodedKeySpec getKeySpec(Key decryptKey) 322 throws NoSuchAlgorithmException, InvalidKeyException { 323 if (decryptKey == null) { 324 throw new NullPointerException("decryptKey is null"); 325 } 326 return getKeySpecImpl(decryptKey, null); 327 } 328 329 /** 330 * Extract the enclosed PKCS8EncodedKeySpec object from the 331 * encrypted data and return it. 332 * @param decryptKey key used for decrypting the encrypted data. 333 * @param providerName the name of provider whose Cipher 334 * implementation will be used. 335 * @return the PKCS8EncodedKeySpec object. 336 * @exception NullPointerException if <code>decryptKey</code> 337 * or <code>providerName</code> is null. 338 * @exception NoSuchProviderException if no provider 339 * <code>providerName</code> is registered. 340 * @exception NoSuchAlgorithmException if cannot find appropriate 341 * cipher to decrypt the encrypted data. 342 * @exception InvalidKeyException if <code>decryptKey</code> 343 * cannot be used to decrypt the encrypted data or the decryption 344 * result is not a valid PKCS8KeySpec. 345 * 346 * @since 1.5 347 */ getKeySpec(Key decryptKey, String providerName)348 public PKCS8EncodedKeySpec getKeySpec(Key decryptKey, 349 String providerName) throws NoSuchProviderException, 350 NoSuchAlgorithmException, InvalidKeyException { 351 if (decryptKey == null) { 352 throw new NullPointerException("decryptKey is null"); 353 } 354 if (providerName == null) { 355 throw new NullPointerException("provider is null"); 356 } 357 Provider provider = Security.getProvider(providerName); 358 if (provider == null) { 359 throw new NoSuchProviderException("provider " + 360 providerName + " not found"); 361 } 362 return getKeySpecImpl(decryptKey, provider); 363 } 364 365 /** 366 * Extract the enclosed PKCS8EncodedKeySpec object from the 367 * encrypted data and return it. 368 * @param decryptKey key used for decrypting the encrypted data. 369 * @param provider the name of provider whose Cipher implementation 370 * will be used. 371 * @return the PKCS8EncodedKeySpec object. 372 * @exception NullPointerException if <code>decryptKey</code> 373 * or <code>provider</code> is null. 374 * @exception NoSuchAlgorithmException if cannot find appropriate 375 * cipher to decrypt the encrypted data in <code>provider</code>. 376 * @exception InvalidKeyException if <code>decryptKey</code> 377 * cannot be used to decrypt the encrypted data or the decryption 378 * result is not a valid PKCS8KeySpec. 379 * 380 * @since 1.5 381 */ getKeySpec(Key decryptKey, Provider provider)382 public PKCS8EncodedKeySpec getKeySpec(Key decryptKey, 383 Provider provider) throws NoSuchAlgorithmException, 384 InvalidKeyException { 385 if (decryptKey == null) { 386 throw new NullPointerException("decryptKey is null"); 387 } 388 if (provider == null) { 389 throw new NullPointerException("provider is null"); 390 } 391 return getKeySpecImpl(decryptKey, provider); 392 } 393 394 /** 395 * Returns the ASN.1 encoding of this object. 396 * @return the ASN.1 encoding. Returns a new array 397 * each time this method is called. 398 * @exception IOException if error occurs when constructing its 399 * ASN.1 encoding. 400 */ getEncoded()401 public byte[] getEncoded() throws IOException { 402 if (this.encoded == null) { 403 DerOutputStream out = new DerOutputStream(); 404 DerOutputStream tmp = new DerOutputStream(); 405 406 // encode encryption algorithm 407 algid.encode(tmp); 408 409 // encode encrypted data 410 tmp.putOctetString(encryptedData); 411 412 // wrap everything into a SEQUENCE 413 out.write(DerValue.tag_Sequence, tmp); 414 this.encoded = out.toByteArray(); 415 } 416 return (byte[])this.encoded.clone(); 417 } 418 checkTag(DerValue val, byte tag, String valName)419 private static void checkTag(DerValue val, byte tag, String valName) 420 throws IOException { 421 if (val.getTag() != tag) { 422 throw new IOException("invalid key encoding - wrong tag for " + 423 valName); 424 } 425 } 426 checkPKCS8Encoding(byte[] encodedKey)427 private static void checkPKCS8Encoding(byte[] encodedKey) 428 throws IOException { 429 DerInputStream in = new DerInputStream(encodedKey); 430 DerValue[] values = in.getSequence(3); 431 432 switch (values.length) { 433 case 4: 434 checkTag(values[3], DerValue.TAG_CONTEXT, "attributes"); 435 case 3: 436 checkTag(values[0], DerValue.tag_Integer, "version"); 437 DerInputStream algid = values[1].toDerInputStream(); 438 algid.getOID(); 439 if (algid.available() != 0) { 440 algid.getDerValue(); 441 } 442 checkTag(values[2], DerValue.tag_OctetString, "privateKey"); 443 break; 444 default: 445 throw new IOException("invalid key encoding"); 446 } 447 } 448 } 449