1/* 2 * Licensed to the Apache Software Foundation (ASF) under one or more 3 * contributor license agreements. See the NOTICE file distributed with 4 * this work for additional information regarding copyright ownership. 5 * The ASF licenses this file to You under the Apache License, Version 2.0 6 * (the "License"); you may not use this file except in compliance with 7 * the License. You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18package javax.crypto; 19 20import java.io.IOException; 21import java.security.AlgorithmParameters; 22import java.security.InvalidAlgorithmParameterException; 23import java.security.InvalidKeyException; 24import java.security.Key; 25import java.security.NoSuchAlgorithmException; 26import java.security.NoSuchProviderException; 27import java.security.Provider; 28import java.security.spec.InvalidKeySpecException; 29import java.security.spec.PKCS8EncodedKeySpec; 30import org.apache.harmony.security.asn1.ASN1Any; 31import org.apache.harmony.security.asn1.ASN1Implicit; 32import org.apache.harmony.security.asn1.ASN1Integer; 33import org.apache.harmony.security.asn1.ASN1OctetString; 34import org.apache.harmony.security.asn1.ASN1Sequence; 35import org.apache.harmony.security.asn1.ASN1SetOf; 36import org.apache.harmony.security.asn1.ASN1Type; 37import org.apache.harmony.security.utils.AlgNameMapper; 38import org.apache.harmony.security.x509.AlgorithmIdentifier; 39 40 41/** 42 * This class implements the {@code EncryptedPrivateKeyInfo} ASN.1 type as 43 * specified in <a href="http://www.ietf.org/rfc/rfc5208.txt">PKCS 44 * #8 - Private-Key Information Syntax Standard</a>. 45 * <p> 46 * The definition of ASN.1 is as follows: 47 * <dl> 48 * EncryptedPrivateKeyInfo ::= SEQUENCE { 49 * <dd>encryptionAlgorithm AlgorithmIdentifier,</dd> 50 * <dd>encryptedData OCTET STRING }</dd> 51 * </dl> 52 * <dl> 53 * AlgorithmIdentifier ::= SEQUENCE { 54 * <dd>algorithm OBJECT IDENTIFIER,</dd> 55 * <dd>parameters ANY DEFINED BY algorithm OPTIONAL }</dd> 56 * </dl> 57 */ 58public class EncryptedPrivateKeyInfo { 59 // Encryption algorithm name 60 private String algName; 61 // Encryption algorithm parameters 62 private final AlgorithmParameters algParameters; 63 // Encrypted private key data 64 private final byte[] encryptedData; 65 // Encryption algorithm OID 66 private String oid; 67 // This EncryptedPrivateKeyInfo ASN.1 DER encoding 68 private volatile byte[] encoded; 69 70 /** 71 * Creates an {@code EncryptedPrivateKeyInfo} instance from its encoded 72 * representation by parsing it. 73 * 74 * @param encoded 75 * the encoded representation of this object 76 * @throws IOException 77 * if parsing the encoded representation fails. 78 * @throws NullPointerException 79 * if {@code encoded} is {@code null}. 80 */ 81 public EncryptedPrivateKeyInfo(byte[] encoded) throws IOException { 82 if (encoded == null) { 83 throw new NullPointerException("encoded == null"); 84 } 85 this.encoded = new byte[encoded.length]; 86 System.arraycopy(encoded, 0, this.encoded, 0, encoded.length); 87 Object[] values; 88 89 values = (Object[])asn1.decode(encoded); 90 91 AlgorithmIdentifier aId = (AlgorithmIdentifier) values[0]; 92 93 algName = aId.getAlgorithm(); 94 // algName == oid now 95 boolean mappingExists = mapAlgName(); 96 // algName == name from map oid->name if mapping exists, or 97 // algName == oid if mapping does not exist 98 99 AlgorithmParameters aParams = null; 100 byte[] params = aId.getParameters(); 101 if (params != null && !isNullValue(params)) { 102 try { 103 aParams = AlgorithmParameters.getInstance(algName); 104 aParams.init(aId.getParameters()); 105 if (!mappingExists) { 106 algName = aParams.getAlgorithm(); 107 } 108 } catch (NoSuchAlgorithmException e) { 109 } 110 } 111 algParameters = aParams; 112 113 encryptedData = (byte[]) values[1]; 114 } 115 116 private static boolean isNullValue(byte[] toCheck) { 117 return toCheck[0] == 5 && toCheck[1] == 0; 118 } 119 120 /** 121 * Creates an {@code EncryptedPrivateKeyInfo} instance from an algorithm 122 * name and its encrypted data. 123 * 124 * @param encryptionAlgorithmName 125 * the name of an algorithm. 126 * @param encryptedData 127 * the encrypted data. 128 * @throws NoSuchAlgorithmException 129 * if the {@code encrAlgName} is not a supported algorithm. 130 * @throws NullPointerException 131 * if {@code encrAlgName} or {@code encryptedData} is {@code 132 * null}. 133 * @throws IllegalArgumentException 134 * if {@code encryptedData} is empty. 135 */ 136 public EncryptedPrivateKeyInfo(String encryptionAlgorithmName, byte[] encryptedData) 137 throws NoSuchAlgorithmException { 138 if (encryptionAlgorithmName == null) { 139 throw new NullPointerException("encryptionAlgorithmName == null"); 140 } 141 this.algName = encryptionAlgorithmName; 142 if (!mapAlgName()) { 143 throw new NoSuchAlgorithmException("Unsupported algorithm: " + this.algName); 144 } 145 if (encryptedData == null) { 146 throw new NullPointerException("encryptedData == null"); 147 } 148 if (encryptedData.length == 0) { 149 throw new IllegalArgumentException("encryptedData.length == 0"); 150 } 151 this.encryptedData = new byte[encryptedData.length]; 152 System.arraycopy(encryptedData, 0, 153 this.encryptedData, 0, encryptedData.length); 154 this.algParameters = null; 155 } 156 157 /** 158 * Creates an {@code EncryptedPrivateKeyInfo} instance from the 159 * encryption algorithm parameters an its encrypted data. 160 * 161 * @param algParams 162 * the encryption algorithm parameters. 163 * @param encryptedData 164 * the encrypted data. 165 * @throws NoSuchAlgorithmException 166 * if the algorithm name of the specified {@code algParams} 167 * parameter is not supported. 168 * @throws NullPointerException 169 * if {@code algParams} or {@code encryptedData} is 170 * {@code null}. 171 */ 172 public EncryptedPrivateKeyInfo(AlgorithmParameters algParams, byte[] encryptedData) 173 throws NoSuchAlgorithmException { 174 if (algParams == null) { 175 throw new NullPointerException("algParams == null"); 176 } 177 this.algParameters = algParams; 178 if (encryptedData == null) { 179 throw new NullPointerException("encryptedData == null"); 180 } 181 if (encryptedData.length == 0) { 182 throw new IllegalArgumentException("encryptedData.length == 0"); 183 } 184 this.encryptedData = new byte[encryptedData.length]; 185 System.arraycopy(encryptedData, 0, 186 this.encryptedData, 0, encryptedData.length); 187 this.algName = this.algParameters.getAlgorithm(); 188 if (!mapAlgName()) { 189 throw new NoSuchAlgorithmException("Unsupported algorithm: " + this.algName); 190 } 191 } 192 193 /** 194 * Returns the name of the encryption algorithm. 195 * 196 * @return the name of the encryption algorithm. 197 */ 198 public String getAlgName() { 199 return algName; 200 } 201 202 /** 203 * Returns the parameters used by the encryption algorithm. 204 * 205 * @return the parameters used by the encryption algorithm. 206 */ 207 public AlgorithmParameters getAlgParameters() { 208 return algParameters; 209 } 210 211 /** 212 * Returns the encrypted data of this key. 213 * 214 * @return the encrypted data of this key, each time this method is called a 215 * new array is returned. 216 */ 217 public byte[] getEncryptedData() { 218 byte[] ret = new byte[encryptedData.length]; 219 System.arraycopy(encryptedData, 0, ret, 0, encryptedData.length); 220 return ret; 221 } 222 223 /** 224 * Returns the {@code PKCS8EncodedKeySpec} object extracted from the 225 * encrypted data. 226 * <p> 227 * The cipher must be initialize in either {@code Cipher.DECRYPT_MODE} or 228 * {@code Cipher.UNWRAP_MODE} with the same parameters and key used for 229 * encrypting this. 230 * 231 * @param cipher 232 * the cipher initialized for decrypting the encrypted data. 233 * @return the extracted {@code PKCS8EncodedKeySpec}. 234 * @throws InvalidKeySpecException 235 * if the specified cipher is not suited to decrypt the 236 * encrypted data. 237 * @throws NullPointerException 238 * if {@code cipher} is {@code null}. 239 */ 240 public PKCS8EncodedKeySpec getKeySpec(Cipher cipher) 241 throws InvalidKeySpecException { 242 if (cipher == null) { 243 throw new NullPointerException("cipher == null"); 244 } 245 try { 246 byte[] decryptedData = cipher.doFinal(encryptedData); 247 try { 248 ASN1PrivateKeyInfo.verify(decryptedData); 249 } catch (IOException e1) { 250 throw new InvalidKeySpecException("Decrypted data does not represent valid PKCS#8 PrivateKeyInfo"); 251 } 252 return new PKCS8EncodedKeySpec(decryptedData); 253 } catch (IllegalStateException e) { 254 throw new InvalidKeySpecException(e.getMessage()); 255 } catch (IllegalBlockSizeException e) { 256 throw new InvalidKeySpecException(e.getMessage()); 257 } catch (BadPaddingException e) { 258 throw new InvalidKeySpecException(e.getMessage()); 259 } 260 } 261 262 /** 263 * Returns the {@code PKCS8EncodedKeySpec} object extracted from the 264 * encrypted data. 265 * 266 * @param decryptKey 267 * the key to decrypt the encrypted data with. 268 * @return the extracted {@code PKCS8EncodedKeySpec}. 269 * @throws NoSuchAlgorithmException 270 * if no usable cipher can be found to decrypt the encrypted 271 * data. 272 * @throws InvalidKeyException 273 * if {@code decryptKey} is not usable to decrypt the encrypted 274 * data. 275 * @throws NullPointerException 276 * if {@code decryptKey} is {@code null}. 277 */ 278 public PKCS8EncodedKeySpec getKeySpec(Key decryptKey) throws NoSuchAlgorithmException, 279 InvalidKeyException { 280 if (decryptKey == null) { 281 throw new NullPointerException("decryptKey == null"); 282 } 283 try { 284 Cipher cipher = Cipher.getInstance(algName); 285 if (algParameters == null) { 286 cipher.init(Cipher.DECRYPT_MODE, decryptKey); 287 } else { 288 cipher.init(Cipher.DECRYPT_MODE, decryptKey, algParameters); 289 } 290 byte[] decryptedData = cipher.doFinal(encryptedData); 291 try { 292 ASN1PrivateKeyInfo.verify(decryptedData); 293 } catch (IOException e1) { 294 throw invalidKey(); 295 } 296 return new PKCS8EncodedKeySpec(decryptedData); 297 } catch (NoSuchPaddingException e) { 298 throw new NoSuchAlgorithmException(e.getMessage()); 299 } catch (InvalidAlgorithmParameterException e) { 300 throw new NoSuchAlgorithmException(e.getMessage()); 301 } catch (IllegalStateException e) { 302 throw new InvalidKeyException(e.getMessage()); 303 } catch (IllegalBlockSizeException e) { 304 throw new InvalidKeyException(e.getMessage()); 305 } catch (BadPaddingException e) { 306 throw new InvalidKeyException(e.getMessage()); 307 } 308 } 309 310 /** 311 * Returns the {@code PKCS8EncodedKeySpec} object extracted from the 312 * encrypted data. 313 * 314 * @param decryptKey 315 * the key to decrypt the encrypted data with. 316 * @param providerName 317 * the name of a provider whose cipher implementation should be 318 * used. 319 * @return the extracted {@code PKCS8EncodedKeySpec}. 320 * @throws NoSuchProviderException 321 * if no provider with {@code providerName} can be found. 322 * @throws NoSuchAlgorithmException 323 * if no usable cipher can be found to decrypt the encrypted 324 * data. 325 * @throws InvalidKeyException 326 * if {@code decryptKey} is not usable to decrypt the encrypted 327 * data. 328 * @throws NullPointerException 329 * if {@code decryptKey} or {@code providerName} is {@code null} 330 * . 331 */ 332 public PKCS8EncodedKeySpec getKeySpec(Key decryptKey, String providerName) 333 throws NoSuchProviderException, 334 NoSuchAlgorithmException, 335 InvalidKeyException { 336 if (decryptKey == null) { 337 throw new NullPointerException("decryptKey == null"); 338 } 339 if (providerName == null) { 340 throw new NullPointerException("providerName == null"); 341 } 342 try { 343 Cipher cipher = Cipher.getInstance(algName, providerName); 344 if (algParameters == null) { 345 cipher.init(Cipher.DECRYPT_MODE, decryptKey); 346 } else { 347 cipher.init(Cipher.DECRYPT_MODE, decryptKey, algParameters); 348 } 349 byte[] decryptedData = cipher.doFinal(encryptedData); 350 try { 351 ASN1PrivateKeyInfo.verify(decryptedData); 352 } catch (IOException e1) { 353 throw invalidKey(); 354 } 355 return new PKCS8EncodedKeySpec(decryptedData); 356 } catch (NoSuchPaddingException e) { 357 throw new NoSuchAlgorithmException(e.getMessage()); 358 } catch (InvalidAlgorithmParameterException e) { 359 throw new NoSuchAlgorithmException(e.getMessage()); 360 } catch (IllegalStateException e) { 361 throw new InvalidKeyException(e.getMessage()); 362 } catch (IllegalBlockSizeException e) { 363 throw new InvalidKeyException(e.getMessage()); 364 } catch (BadPaddingException e) { 365 throw new InvalidKeyException(e.getMessage()); 366 } 367 } 368 369 /** 370 * Returns the {@code PKCS8EncodedKeySpec} object extracted from the 371 * encrypted data. 372 * 373 * @param decryptKey 374 * the key to decrypt the encrypted data with. 375 * @param provider 376 * the provider whose cipher implementation should be used. 377 * @return the extracted {@code PKCS8EncodedKeySpec}. 378 * @throws NoSuchAlgorithmException 379 * if no usable cipher can be found to decrypt the encrypted 380 * data. 381 * @throws InvalidKeyException 382 * if {@code decryptKey} is not usable to decrypt the encrypted 383 * data. 384 * @throws NullPointerException 385 * if {@code decryptKey} or {@code provider} is {@code null}. 386 */ 387 public PKCS8EncodedKeySpec getKeySpec(Key decryptKey, Provider provider) 388 throws NoSuchAlgorithmException, 389 InvalidKeyException { 390 if (decryptKey == null) { 391 throw new NullPointerException("decryptKey == null"); 392 } 393 if (provider == null) { 394 throw new NullPointerException("provider == null"); 395 } 396 try { 397 Cipher cipher = Cipher.getInstance(algName, provider); 398 if (algParameters == null) { 399 cipher.init(Cipher.DECRYPT_MODE, decryptKey); 400 } else { 401 cipher.init(Cipher.DECRYPT_MODE, decryptKey, algParameters); 402 } 403 byte[] decryptedData = cipher.doFinal(encryptedData); 404 try { 405 ASN1PrivateKeyInfo.verify(decryptedData); 406 } catch (IOException e1) { 407 throw invalidKey(); 408 } 409 return new PKCS8EncodedKeySpec(decryptedData); 410 } catch (NoSuchPaddingException e) { 411 throw new NoSuchAlgorithmException(e.getMessage()); 412 } catch (InvalidAlgorithmParameterException e) { 413 throw new NoSuchAlgorithmException(e.getMessage()); 414 } catch (IllegalStateException e) { 415 throw new InvalidKeyException(e.getMessage()); 416 } catch (IllegalBlockSizeException e) { 417 throw new InvalidKeyException(e.getMessage()); 418 } catch (BadPaddingException e) { 419 throw new InvalidKeyException(e.getMessage()); 420 } 421 } 422 423 private InvalidKeyException invalidKey() throws InvalidKeyException { 424 throw new InvalidKeyException("Decrypted data does not represent valid PKCS#8 PrivateKeyInfo"); 425 } 426 427 /** 428 * Returns the ASN.1 encoded representation of this object. 429 * 430 * @return the ASN.1 encoded representation of this object. 431 * @throws IOException 432 * if encoding this object fails. 433 */ 434 public byte[] getEncoded() throws IOException { 435 if (encoded == null) { 436 // Generate ASN.1 encoding: 437 encoded = asn1.encode(this); 438 } 439 byte[] ret = new byte[encoded.length]; 440 System.arraycopy(encoded, 0, ret, 0, encoded.length); 441 return ret; 442 } 443 444 // Performs all needed alg name mappings. 445 // Returns 'true' if mapping available 'false' otherwise 446 private boolean mapAlgName() { 447 if (AlgNameMapper.isOID(this.algName)) { 448 // OID provided to the ctor 449 // get rid of possible leading "OID." 450 this.oid = AlgNameMapper.normalize(this.algName); 451 // try to find mapping OID->algName 452 this.algName = AlgNameMapper.map2AlgName(this.oid); 453 // if there is no mapping OID->algName 454 // set OID as algName 455 if (this.algName == null) { 456 this.algName = this.oid; 457 } 458 } else { 459 String stdName = AlgNameMapper.getStandardName(this.algName); 460 // Alg name provided to the ctor 461 // try to find mapping algName->OID or 462 // (algName->stdAlgName)->OID 463 this.oid = AlgNameMapper.map2OID(this.algName); 464 if (this.oid == null) { 465 if (stdName == null) { 466 // no above mappings available 467 return false; 468 } 469 this.oid = AlgNameMapper.map2OID(stdName); 470 if (this.oid == null) { 471 return false; 472 } 473 this.algName = stdName; 474 } else if (stdName != null) { 475 this.algName = stdName; 476 } 477 } 478 return true; 479 } 480 481 // 482 // EncryptedPrivateKeyInfo DER encoder/decoder. 483 // EncryptedPrivateKeyInfo ASN.1 definition 484 // (as defined in PKCS #8: Private-Key Information Syntax Standard 485 // http://www.ietf.org/rfc/rfc2313.txt) 486 // 487 // EncryptedPrivateKeyInfo ::= SEQUENCE { 488 // encryptionAlgorithm AlgorithmIdentifier, 489 // encryptedData OCTET STRING } 490 // 491 492 private static final byte[] nullParam = new byte[] { 5, 0 }; 493 494 private static final ASN1Sequence asn1 = new ASN1Sequence(new ASN1Type[] { 495 AlgorithmIdentifier.ASN1, ASN1OctetString.getInstance() }) { 496 497 @Override 498 protected void getValues(Object object, Object[] values) { 499 500 EncryptedPrivateKeyInfo epki = (EncryptedPrivateKeyInfo) object; 501 502 try { 503 byte[] algParmsEncoded = (epki.algParameters == null) ? nullParam 504 : epki.algParameters.getEncoded(); 505 values[0] = new AlgorithmIdentifier(epki.oid, algParmsEncoded); 506 values[1] = epki.encryptedData; 507 } catch (IOException e) { 508 throw new RuntimeException(e.getMessage()); 509 } 510 } 511 }; 512 513 // PrivateKeyInfo DER decoder. 514 // PrivateKeyInfo ASN.1 definition 515 // (as defined in PKCS #8: Private-Key Information Syntax Standard 516 // http://www.ietf.org/rfc/rfc2313.txt) 517 // 518 // 519 // PrivateKeyInfo ::= SEQUENCE { 520 // version Version, 521 // privateKeyAlgorithm PrivateKeyAlgorithmIdentifier, 522 // privateKey PrivateKey, 523 // attributes [0] IMPLICIT Attributes OPTIONAL } 524 // 525 // Version ::= INTEGER 526 // 527 // PrivateKeyAlgorithmIdentifier ::= AlgorithmIdentifier 528 // 529 // PrivateKey ::= OCTET STRING 530 // 531 // Attributes ::= SET OF Attribute 532 533 private static final ASN1SetOf ASN1Attributes = new ASN1SetOf(ASN1Any.getInstance()); 534 535 private static final ASN1Sequence ASN1PrivateKeyInfo = new ASN1Sequence( 536 new ASN1Type[] { ASN1Integer.getInstance(), AlgorithmIdentifier.ASN1, 537 ASN1OctetString.getInstance(), 538 new ASN1Implicit(0, ASN1Attributes) }) { 539 { 540 setOptional(3); //attributes are optional 541 } 542 }; 543} 544