| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package javax.crypto; |
| |
| import java.io.IOException; |
| import java.security.AlgorithmParameters; |
| import java.security.InvalidAlgorithmParameterException; |
| import java.security.InvalidKeyException; |
| import java.security.Key; |
| import java.security.NoSuchAlgorithmException; |
| import java.security.NoSuchProviderException; |
| import java.security.Provider; |
| import java.security.spec.InvalidKeySpecException; |
| import java.security.spec.PKCS8EncodedKeySpec; |
| import org.apache.harmony.security.asn1.ASN1Any; |
| import org.apache.harmony.security.asn1.ASN1Implicit; |
| import org.apache.harmony.security.asn1.ASN1Integer; |
| import org.apache.harmony.security.asn1.ASN1OctetString; |
| import org.apache.harmony.security.asn1.ASN1Sequence; |
| import org.apache.harmony.security.asn1.ASN1SetOf; |
| import org.apache.harmony.security.asn1.ASN1Type; |
| import org.apache.harmony.security.utils.AlgNameMapper; |
| import org.apache.harmony.security.x509.AlgorithmIdentifier; |
| |
| |
| /** |
| * This class implements the {@code EncryptedPrivateKeyInfo} ASN.1 type as |
| * specified in <a href="http://www.ietf.org/rfc/rfc5208.txt">PKCS |
| * #8 - Private-Key Information Syntax Standard</a>. |
| * <p> |
| * The definition of ASN.1 is as follows: |
| * <dl> |
| * EncryptedPrivateKeyInfo ::= SEQUENCE { |
| * <dd>encryptionAlgorithm AlgorithmIdentifier,</dd> |
| * <dd>encryptedData OCTET STRING }</dd> |
| * </dl> |
| * <dl> |
| * AlgorithmIdentifier ::= SEQUENCE { |
| * <dd>algorithm OBJECT IDENTIFIER,</dd> |
| * <dd>parameters ANY DEFINED BY algorithm OPTIONAL }</dd> |
| * </dl> |
| */ |
| public class EncryptedPrivateKeyInfo { |
| // Encryption algorithm name |
| private String algName; |
| // Encryption algorithm parameters |
| private final AlgorithmParameters algParameters; |
| // Encrypted private key data |
| private final byte[] encryptedData; |
| // Encryption algorithm OID |
| private String oid; |
| // This EncryptedPrivateKeyInfo ASN.1 DER encoding |
| private volatile byte[] encoded; |
| |
| /** |
| * Creates an {@code EncryptedPrivateKeyInfo} instance from its encoded |
| * representation by parsing it. |
| * |
| * @param encoded |
| * the encoded representation of this object |
| * @throws IOException |
| * if parsing the encoded representation fails. |
| * @throws NullPointerException |
| * if {@code encoded} is {@code null}. |
| */ |
| public EncryptedPrivateKeyInfo(byte[] encoded) throws IOException { |
| if (encoded == null) { |
| throw new NullPointerException("encoded == null"); |
| } |
| this.encoded = new byte[encoded.length]; |
| System.arraycopy(encoded, 0, this.encoded, 0, encoded.length); |
| Object[] values; |
| |
| values = (Object[])asn1.decode(encoded); |
| |
| AlgorithmIdentifier aId = (AlgorithmIdentifier) values[0]; |
| |
| algName = aId.getAlgorithm(); |
| // algName == oid now |
| boolean mappingExists = mapAlgName(); |
| // algName == name from map oid->name if mapping exists, or |
| // algName == oid if mapping does not exist |
| |
| AlgorithmParameters aParams = null; |
| byte[] params = aId.getParameters(); |
| if (params != null && !isNullValue(params)) { |
| try { |
| aParams = AlgorithmParameters.getInstance(algName); |
| aParams.init(aId.getParameters()); |
| if (!mappingExists) { |
| algName = aParams.getAlgorithm(); |
| } |
| } catch (NoSuchAlgorithmException e) { |
| } |
| } |
| algParameters = aParams; |
| |
| encryptedData = (byte[]) values[1]; |
| } |
| |
| private static boolean isNullValue(byte[] toCheck) { |
| return toCheck[0] == 5 && toCheck[1] == 0; |
| } |
| |
| /** |
| * Creates an {@code EncryptedPrivateKeyInfo} instance from an algorithm |
| * name and its encrypted data. |
| * |
| * @param encrAlgName |
| * the name of an algorithm. |
| * @param encryptedData |
| * the encrypted data. |
| * @throws NoSuchAlgorithmException |
| * if the {@code encrAlgName} is not a supported algorithm. |
| * @throws NullPointerException |
| * if {@code encrAlgName} or {@code encryptedData} is {@code |
| * null}. |
| * @throws IllegalArgumentException |
| * if {@code encryptedData} is empty. |
| */ |
| public EncryptedPrivateKeyInfo(String encrAlgName, byte[] encryptedData) |
| throws NoSuchAlgorithmException { |
| if (encrAlgName == null) { |
| throw new NullPointerException("the algName parameter is null"); |
| } |
| this.algName = encrAlgName; |
| if (!mapAlgName()) { |
| throw new NoSuchAlgorithmException("Unsupported algorithm: " + this.algName); |
| } |
| if (encryptedData == null) { |
| throw new NullPointerException("encryptedData == null"); |
| } |
| if (encryptedData.length == 0) { |
| throw new IllegalArgumentException("encryptedData.length == 0"); |
| } |
| this.encryptedData = new byte[encryptedData.length]; |
| System.arraycopy(encryptedData, 0, |
| this.encryptedData, 0, encryptedData.length); |
| this.algParameters = null; |
| } |
| |
| /** |
| * Creates an {@code EncryptedPrivateKeyInfo} instance from the |
| * encryption algorithm parameters an its encrypted data. |
| * |
| * @param algParams |
| * the encryption algorithm parameters. |
| * @param encryptedData |
| * the encrypted data. |
| * @throws NoSuchAlgorithmException |
| * if the algorithm name of the specified {@code algParams} |
| * parameter is not supported. |
| * @throws NullPointerException |
| * if {@code algParams} or {@code encryptedData} is |
| * {@code null}. |
| */ |
| public EncryptedPrivateKeyInfo(AlgorithmParameters algParams, byte[] encryptedData) |
| throws NoSuchAlgorithmException { |
| if (algParams == null) { |
| throw new NullPointerException("algParams == null"); |
| } |
| this.algParameters = algParams; |
| if (encryptedData == null) { |
| throw new NullPointerException("encryptedData == null"); |
| } |
| if (encryptedData.length == 0) { |
| throw new IllegalArgumentException("encryptedData.length == 0"); |
| } |
| this.encryptedData = new byte[encryptedData.length]; |
| System.arraycopy(encryptedData, 0, |
| this.encryptedData, 0, encryptedData.length); |
| this.algName = this.algParameters.getAlgorithm(); |
| if (!mapAlgName()) { |
| throw new NoSuchAlgorithmException("Unsupported algorithm: " + this.algName); |
| } |
| } |
| |
| /** |
| * Returns the name of the encryption algorithm. |
| * |
| * @return the name of the encryption algorithm. |
| */ |
| public String getAlgName() { |
| return algName; |
| } |
| |
| /** |
| * Returns the parameters used by the encryption algorithm. |
| * |
| * @return the parameters used by the encryption algorithm. |
| */ |
| public AlgorithmParameters getAlgParameters() { |
| return algParameters; |
| } |
| |
| /** |
| * Returns the encrypted data of this key. |
| * |
| * @return the encrypted data of this key, each time this method is called a |
| * new array is returned. |
| */ |
| public byte[] getEncryptedData() { |
| byte[] ret = new byte[encryptedData.length]; |
| System.arraycopy(encryptedData, 0, ret, 0, encryptedData.length); |
| return ret; |
| } |
| |
| /** |
| * Returns the {@code PKCS8EncodedKeySpec} object extracted from the |
| * encrypted data. |
| * <p> |
| * The cipher must be initialize in either {@code Cipher.DECRYPT_MODE} or |
| * {@code Cipher.UNWRAP_MODE} with the same parameters and key used for |
| * encrypting this. |
| * |
| * @param cipher |
| * the cipher initialized for decrypting the encrypted data. |
| * @return the extracted {@code PKCS8EncodedKeySpec}. |
| * @throws InvalidKeySpecException |
| * if the specified cipher is not suited to decrypt the |
| * encrypted data. |
| * @throws NullPointerException |
| * if {@code cipher} is {@code null}. |
| */ |
| public PKCS8EncodedKeySpec getKeySpec(Cipher cipher) |
| throws InvalidKeySpecException { |
| if (cipher == null) { |
| throw new NullPointerException("cipher == null"); |
| } |
| try { |
| byte[] decryptedData = cipher.doFinal(encryptedData); |
| try { |
| ASN1PrivateKeyInfo.verify(decryptedData); |
| } catch (IOException e1) { |
| throw new InvalidKeySpecException("Decrypted data does not represent valid PKCS#8 PrivateKeyInfo"); |
| } |
| return new PKCS8EncodedKeySpec(decryptedData); |
| } catch (IllegalStateException e) { |
| throw new InvalidKeySpecException(e.getMessage()); |
| } catch (IllegalBlockSizeException e) { |
| throw new InvalidKeySpecException(e.getMessage()); |
| } catch (BadPaddingException e) { |
| throw new InvalidKeySpecException(e.getMessage()); |
| } |
| } |
| |
| /** |
| * Returns the {@code PKCS8EncodedKeySpec} object extracted from the |
| * encrypted data. |
| * |
| * @param decryptKey |
| * the key to decrypt the encrypted data with. |
| * @return the extracted {@code PKCS8EncodedKeySpec}. |
| * @throws NoSuchAlgorithmException |
| * if no usable cipher can be found to decrypt the encrypted |
| * data. |
| * @throws InvalidKeyException |
| * if {@code decryptKey} is not usable to decrypt the encrypted |
| * data. |
| * @throws NullPointerException |
| * if {@code decryptKey} is {@code null}. |
| */ |
| public PKCS8EncodedKeySpec getKeySpec(Key decryptKey) throws NoSuchAlgorithmException, |
| InvalidKeyException { |
| if (decryptKey == null) { |
| throw new NullPointerException("decryptKey == null"); |
| } |
| try { |
| Cipher cipher = Cipher.getInstance(algName); |
| if (algParameters == null) { |
| cipher.init(Cipher.DECRYPT_MODE, decryptKey); |
| } else { |
| cipher.init(Cipher.DECRYPT_MODE, decryptKey, algParameters); |
| } |
| byte[] decryptedData = cipher.doFinal(encryptedData); |
| try { |
| ASN1PrivateKeyInfo.verify(decryptedData); |
| } catch (IOException e1) { |
| throw invalidKey(); |
| } |
| return new PKCS8EncodedKeySpec(decryptedData); |
| } catch (NoSuchPaddingException e) { |
| throw new NoSuchAlgorithmException(e.getMessage()); |
| } catch (InvalidAlgorithmParameterException e) { |
| throw new NoSuchAlgorithmException(e.getMessage()); |
| } catch (IllegalStateException e) { |
| throw new InvalidKeyException(e.getMessage()); |
| } catch (IllegalBlockSizeException e) { |
| throw new InvalidKeyException(e.getMessage()); |
| } catch (BadPaddingException e) { |
| throw new InvalidKeyException(e.getMessage()); |
| } |
| } |
| |
| /** |
| * Returns the {@code PKCS8EncodedKeySpec} object extracted from the |
| * encrypted data. |
| * |
| * @param decryptKey |
| * the key to decrypt the encrypted data with. |
| * @param providerName |
| * the name of a provider whose cipher implementation should be |
| * used. |
| * @return the extracted {@code PKCS8EncodedKeySpec}. |
| * @throws NoSuchProviderException |
| * if no provider with {@code providerName} can be found. |
| * @throws NoSuchAlgorithmException |
| * if no usable cipher can be found to decrypt the encrypted |
| * data. |
| * @throws InvalidKeyException |
| * if {@code decryptKey} is not usable to decrypt the encrypted |
| * data. |
| * @throws NullPointerException |
| * if {@code decryptKey} or {@code providerName} is {@code null} |
| * . |
| */ |
| public PKCS8EncodedKeySpec getKeySpec(Key decryptKey, String providerName) |
| throws NoSuchProviderException, |
| NoSuchAlgorithmException, |
| InvalidKeyException { |
| if (decryptKey == null) { |
| throw new NullPointerException("decryptKey == null"); |
| } |
| if (providerName == null) { |
| throw new NullPointerException("providerName == null"); |
| } |
| try { |
| Cipher cipher = Cipher.getInstance(algName, providerName); |
| if (algParameters == null) { |
| cipher.init(Cipher.DECRYPT_MODE, decryptKey); |
| } else { |
| cipher.init(Cipher.DECRYPT_MODE, decryptKey, algParameters); |
| } |
| byte[] decryptedData = cipher.doFinal(encryptedData); |
| try { |
| ASN1PrivateKeyInfo.verify(decryptedData); |
| } catch (IOException e1) { |
| throw invalidKey(); |
| } |
| return new PKCS8EncodedKeySpec(decryptedData); |
| } catch (NoSuchPaddingException e) { |
| throw new NoSuchAlgorithmException(e.getMessage()); |
| } catch (InvalidAlgorithmParameterException e) { |
| throw new NoSuchAlgorithmException(e.getMessage()); |
| } catch (IllegalStateException e) { |
| throw new InvalidKeyException(e.getMessage()); |
| } catch (IllegalBlockSizeException e) { |
| throw new InvalidKeyException(e.getMessage()); |
| } catch (BadPaddingException e) { |
| throw new InvalidKeyException(e.getMessage()); |
| } |
| } |
| |
| /** |
| * Returns the {@code PKCS8EncodedKeySpec} object extracted from the |
| * encrypted data. |
| * |
| * @param decryptKey |
| * the key to decrypt the encrypted data with. |
| * @param provider |
| * the provider whose cipher implementation should be used. |
| * @return the extracted {@code PKCS8EncodedKeySpec}. |
| * @throws NoSuchAlgorithmException |
| * if no usable cipher can be found to decrypt the encrypted |
| * data. |
| * @throws InvalidKeyException |
| * if {@code decryptKey} is not usable to decrypt the encrypted |
| * data. |
| * @throws NullPointerException |
| * if {@code decryptKey} or {@code provider} is {@code null}. |
| */ |
| public PKCS8EncodedKeySpec getKeySpec(Key decryptKey, Provider provider) |
| throws NoSuchAlgorithmException, |
| InvalidKeyException { |
| if (decryptKey == null) { |
| throw new NullPointerException("decryptKey == null"); |
| } |
| if (provider == null) { |
| throw new NullPointerException("provider == null"); |
| } |
| try { |
| Cipher cipher = Cipher.getInstance(algName, provider); |
| if (algParameters == null) { |
| cipher.init(Cipher.DECRYPT_MODE, decryptKey); |
| } else { |
| cipher.init(Cipher.DECRYPT_MODE, decryptKey, algParameters); |
| } |
| byte[] decryptedData = cipher.doFinal(encryptedData); |
| try { |
| ASN1PrivateKeyInfo.verify(decryptedData); |
| } catch (IOException e1) { |
| throw invalidKey(); |
| } |
| return new PKCS8EncodedKeySpec(decryptedData); |
| } catch (NoSuchPaddingException e) { |
| throw new NoSuchAlgorithmException(e.getMessage()); |
| } catch (InvalidAlgorithmParameterException e) { |
| throw new NoSuchAlgorithmException(e.getMessage()); |
| } catch (IllegalStateException e) { |
| throw new InvalidKeyException(e.getMessage()); |
| } catch (IllegalBlockSizeException e) { |
| throw new InvalidKeyException(e.getMessage()); |
| } catch (BadPaddingException e) { |
| throw new InvalidKeyException(e.getMessage()); |
| } |
| } |
| |
| private InvalidKeyException invalidKey() throws InvalidKeyException { |
| throw new InvalidKeyException("Decrypted data does not represent valid PKCS#8 PrivateKeyInfo"); |
| } |
| |
| /** |
| * Returns the ASN.1 encoded representation of this object. |
| * |
| * @return the ASN.1 encoded representation of this object. |
| * @throws IOException |
| * if encoding this object fails. |
| */ |
| public byte[] getEncoded() throws IOException { |
| if (encoded == null) { |
| // Generate ASN.1 encoding: |
| encoded = asn1.encode(this); |
| } |
| byte[] ret = new byte[encoded.length]; |
| System.arraycopy(encoded, 0, ret, 0, encoded.length); |
| return ret; |
| } |
| |
| // Performs all needed alg name mappings. |
| // Returns 'true' if mapping available 'false' otherwise |
| private boolean mapAlgName() { |
| if (AlgNameMapper.isOID(this.algName)) { |
| // OID provided to the ctor |
| // get rid of possible leading "OID." |
| this.oid = AlgNameMapper.normalize(this.algName); |
| // try to find mapping OID->algName |
| this.algName = AlgNameMapper.map2AlgName(this.oid); |
| // if there is no mapping OID->algName |
| // set OID as algName |
| if (this.algName == null) { |
| this.algName = this.oid; |
| } |
| } else { |
| String stdName = AlgNameMapper.getStandardName(this.algName); |
| // Alg name provided to the ctor |
| // try to find mapping algName->OID or |
| // (algName->stdAlgName)->OID |
| this.oid = AlgNameMapper.map2OID(this.algName); |
| if (this.oid == null) { |
| if (stdName == null) { |
| // no above mappings available |
| return false; |
| } |
| this.oid = AlgNameMapper.map2OID(stdName); |
| if (this.oid == null) { |
| return false; |
| } |
| this.algName = stdName; |
| } else if (stdName != null) { |
| this.algName = stdName; |
| } |
| } |
| return true; |
| } |
| |
| // |
| // EncryptedPrivateKeyInfo DER encoder/decoder. |
| // EncryptedPrivateKeyInfo ASN.1 definition |
| // (as defined in PKCS #8: Private-Key Information Syntax Standard |
| // http://www.ietf.org/rfc/rfc2313.txt) |
| // |
| // EncryptedPrivateKeyInfo ::= SEQUENCE { |
| // encryptionAlgorithm AlgorithmIdentifier, |
| // encryptedData OCTET STRING } |
| // |
| |
| private static final byte[] nullParam = new byte[] { 5, 0 }; |
| |
| private static final ASN1Sequence asn1 = new ASN1Sequence(new ASN1Type[] { |
| AlgorithmIdentifier.ASN1, ASN1OctetString.getInstance() }) { |
| |
| @Override |
| protected void getValues(Object object, Object[] values) { |
| |
| EncryptedPrivateKeyInfo epki = (EncryptedPrivateKeyInfo) object; |
| |
| try { |
| byte[] algParmsEncoded = (epki.algParameters == null) ? nullParam |
| : epki.algParameters.getEncoded(); |
| values[0] = new AlgorithmIdentifier(epki.oid, algParmsEncoded); |
| values[1] = epki.encryptedData; |
| } catch (IOException e) { |
| throw new RuntimeException(e.getMessage()); |
| } |
| } |
| }; |
| |
| // PrivateKeyInfo DER decoder. |
| // PrivateKeyInfo ASN.1 definition |
| // (as defined in PKCS #8: Private-Key Information Syntax Standard |
| // http://www.ietf.org/rfc/rfc2313.txt) |
| // |
| // |
| // PrivateKeyInfo ::= SEQUENCE { |
| // version Version, |
| // privateKeyAlgorithm PrivateKeyAlgorithmIdentifier, |
| // privateKey PrivateKey, |
| // attributes [0] IMPLICIT Attributes OPTIONAL } |
| // |
| // Version ::= INTEGER |
| // |
| // PrivateKeyAlgorithmIdentifier ::= AlgorithmIdentifier |
| // |
| // PrivateKey ::= OCTET STRING |
| // |
| // Attributes ::= SET OF Attribute |
| |
| private static final ASN1SetOf ASN1Attributes = new ASN1SetOf(ASN1Any.getInstance()); |
| |
| private static final ASN1Sequence ASN1PrivateKeyInfo = new ASN1Sequence( |
| new ASN1Type[] { ASN1Integer.getInstance(), AlgorithmIdentifier.ASN1, |
| ASN1OctetString.getInstance(), |
| new ASN1Implicit(0, ASN1Attributes) }) { |
| { |
| setOptional(3); //attributes are optional |
| } |
| }; |
| } |