diff options
Diffstat (limited to 'luni/src/main/java/javax/crypto/EncryptedPrivateKeyInfo.java')
-rw-r--r-- | luni/src/main/java/javax/crypto/EncryptedPrivateKeyInfo.java | 551 |
1 files changed, 551 insertions, 0 deletions
diff --git a/luni/src/main/java/javax/crypto/EncryptedPrivateKeyInfo.java b/luni/src/main/java/javax/crypto/EncryptedPrivateKeyInfo.java new file mode 100644 index 0000000..2d0fd25 --- /dev/null +++ b/luni/src/main/java/javax/crypto/EncryptedPrivateKeyInfo.java @@ -0,0 +1,551 @@ +/* + * 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.crypto.internal.nls.Messages; +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(Messages.getString("crypto.22")); //$NON-NLS-1$ + } + 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(Messages.getString("crypto.23")); //$NON-NLS-1$ + } + this.algName = encrAlgName; + if (!mapAlgName()) { + throw new NoSuchAlgorithmException(Messages.getString("crypto.24", this.algName)); //$NON-NLS-1$ + } + if (encryptedData == null) { + throw new NullPointerException( + Messages.getString("crypto.25")); //$NON-NLS-1$ + } + if (encryptedData.length == 0) { + throw new IllegalArgumentException(Messages.getString("crypto.26")); //$NON-NLS-1$ + } + 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(Messages.getString("crypto.27")); //$NON-NLS-1$ + } + this.algParameters = algParams; + if (encryptedData == null) { + throw new NullPointerException( + Messages.getString("crypto.25")); //$NON-NLS-1$ + } + if (encryptedData.length == 0) { + throw new IllegalArgumentException(Messages.getString("crypto.26")); //$NON-NLS-1$ + } + 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(Messages.getString("crypto.24", this.algName)); //$NON-NLS-1$ + } + } + + /** + * 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(Messages.getString("crypto.28")); //$NON-NLS-1$ + } + try { + byte[] decryptedData = cipher.doFinal(encryptedData); + try { + ASN1PrivateKeyInfo.verify(decryptedData); + } catch (IOException e1) { + throw new InvalidKeySpecException( + Messages.getString("crypto.29")); //$NON-NLS-1$ + } + 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(Messages.getString("crypto.2A")); //$NON-NLS-1$ + } + 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 new InvalidKeyException( + Messages.getString("crypto.29")); //$NON-NLS-1$ + } + 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(Messages.getString("crypto.2A")); //$NON-NLS-1$ + } + if (providerName == null) { + throw new NullPointerException( + Messages.getString("crypto.2B")); //$NON-NLS-1$ + } + 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 new InvalidKeyException( + Messages.getString("crypto.29")); //$NON-NLS-1$ + } + 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(Messages.getString("crypto.2A")); //$NON-NLS-1$ + } + if (provider == null) { + throw new NullPointerException(Messages.getString("crypto.2C")); //$NON-NLS-1$ + } + 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 new InvalidKeyException( + Messages.getString("crypto.29")); //$NON-NLS-1$ + } + 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 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 + } + }; +} |