Home | History | Annotate | Download | only in crypto
      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 
     18 package javax.crypto;
     19 
     20 import java.io.IOException;
     21 import java.security.AlgorithmParameters;
     22 import java.security.InvalidAlgorithmParameterException;
     23 import java.security.InvalidKeyException;
     24 import java.security.Key;
     25 import java.security.NoSuchAlgorithmException;
     26 import java.security.NoSuchProviderException;
     27 import java.security.Provider;
     28 import java.security.spec.InvalidKeySpecException;
     29 import java.security.spec.PKCS8EncodedKeySpec;
     30 import org.apache.harmony.security.asn1.ASN1Any;
     31 import org.apache.harmony.security.asn1.ASN1Implicit;
     32 import org.apache.harmony.security.asn1.ASN1Integer;
     33 import org.apache.harmony.security.asn1.ASN1OctetString;
     34 import org.apache.harmony.security.asn1.ASN1Sequence;
     35 import org.apache.harmony.security.asn1.ASN1SetOf;
     36 import org.apache.harmony.security.asn1.ASN1Type;
     37 import org.apache.harmony.security.utils.AlgNameMapper;
     38 import 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  */
     58 public 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