Home | History | Annotate | Download | only in crypto
      1 /*
      2  * Copyright (c) 2001, 2011, 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      */
     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 = 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      */
    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 = 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      */
    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 = 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      */
    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      */
    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      */
    224     public byte[] getEncryptedData() {
    225         return 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      */
    246     public PKCS8EncodedKeySpec getKeySpec(Cipher cipher)
    247         throws InvalidKeySpecException {
    248         byte[] encoded = null;
    249         try {
    250             encoded = cipher.doFinal(encryptedData);
    251             checkPKCS8Encoding(encoded);
    252         } catch (GeneralSecurityException |
    253                  IOException |
    254                  IllegalStateException ex) {
    255             throw new InvalidKeySpecException(
    256                     "Cannot retrieve the PKCS8EncodedKeySpec", ex);
    257         }
    258         return new PKCS8EncodedKeySpec(encoded);
    259     }
    260 
    261     private PKCS8EncodedKeySpec getKeySpecImpl(Key decryptKey,
    262         Provider provider) throws NoSuchAlgorithmException,
    263         InvalidKeyException {
    264         byte[] encoded = null;
    265         Cipher c;
    266         try {
    267             if (provider == null) {
    268                 // use the most preferred one
    269                 c = Cipher.getInstance(algid.getName());
    270             } else {
    271                 c = Cipher.getInstance(algid.getName(), provider);
    272             }
    273             c.init(Cipher.DECRYPT_MODE, decryptKey, algid.getParameters());
    274             encoded = c.doFinal(encryptedData);
    275             checkPKCS8Encoding(encoded);
    276         } catch (NoSuchAlgorithmException nsae) {
    277             // rethrow
    278             throw nsae;
    279         } catch (GeneralSecurityException | IOException ex) {
    280             throw new InvalidKeyException(
    281                     "Cannot retrieve the PKCS8EncodedKeySpec", ex);
    282         }
    283         return new PKCS8EncodedKeySpec(encoded);
    284     }
    285 
    286     /**
    287      * Extract the enclosed PKCS8EncodedKeySpec object from the
    288      * encrypted data and return it.
    289      * @param decryptKey key used for decrypting the encrypted data.
    290      * @return the PKCS8EncodedKeySpec object.
    291      * @exception NullPointerException if <code>decryptKey</code>
    292      * is null.
    293      * @exception NoSuchAlgorithmException if cannot find appropriate
    294      * cipher to decrypt the encrypted data.
    295      * @exception InvalidKeyException if <code>decryptKey</code>
    296      * cannot be used to decrypt the encrypted data or the decryption
    297      * result is not a valid PKCS8KeySpec.
    298      *
    299      * @since 1.5
    300      */
    301     public PKCS8EncodedKeySpec getKeySpec(Key decryptKey)
    302         throws NoSuchAlgorithmException, InvalidKeyException {
    303         if (decryptKey == null) {
    304             throw new NullPointerException("decryptKey is null");
    305         }
    306         return getKeySpecImpl(decryptKey, null);
    307     }
    308 
    309     /**
    310      * Extract the enclosed PKCS8EncodedKeySpec object from the
    311      * encrypted data and return it.
    312      * @param decryptKey key used for decrypting the encrypted data.
    313      * @param providerName the name of provider whose Cipher
    314      * implementation will be used.
    315      * @return the PKCS8EncodedKeySpec object.
    316      * @exception NullPointerException if <code>decryptKey</code>
    317      * or <code>providerName</code> is null.
    318      * @exception NoSuchProviderException if no provider
    319      * <code>providerName</code> is registered.
    320      * @exception NoSuchAlgorithmException if cannot find appropriate
    321      * cipher to decrypt the encrypted data.
    322      * @exception InvalidKeyException if <code>decryptKey</code>
    323      * cannot be used to decrypt the encrypted data or the decryption
    324      * result is not a valid PKCS8KeySpec.
    325      *
    326      * @since 1.5
    327      */
    328     public PKCS8EncodedKeySpec getKeySpec(Key decryptKey,
    329         String providerName) throws NoSuchProviderException,
    330         NoSuchAlgorithmException, InvalidKeyException {
    331         if (decryptKey == null) {
    332             throw new NullPointerException("decryptKey is null");
    333         }
    334         if (providerName == null) {
    335             throw new NullPointerException("provider is null");
    336         }
    337         Provider provider = Security.getProvider(providerName);
    338         if (provider == null) {
    339             throw new NoSuchProviderException("provider " +
    340                 providerName + " not found");
    341         }
    342         return getKeySpecImpl(decryptKey, provider);
    343     }
    344 
    345     /**
    346      * Extract the enclosed PKCS8EncodedKeySpec object from the
    347      * encrypted data and return it.
    348      * @param decryptKey key used for decrypting the encrypted data.
    349      * @param provider the name of provider whose Cipher implementation
    350      * will be used.
    351      * @return the PKCS8EncodedKeySpec object.
    352      * @exception NullPointerException if <code>decryptKey</code>
    353      * or <code>provider</code> is null.
    354      * @exception NoSuchAlgorithmException if cannot find appropriate
    355      * cipher to decrypt the encrypted data in <code>provider</code>.
    356      * @exception InvalidKeyException if <code>decryptKey</code>
    357      * cannot be used to decrypt the encrypted data or the decryption
    358      * result is not a valid PKCS8KeySpec.
    359      *
    360      * @since 1.5
    361      */
    362     public PKCS8EncodedKeySpec getKeySpec(Key decryptKey,
    363         Provider provider) throws NoSuchAlgorithmException,
    364         InvalidKeyException {
    365         if (decryptKey == null) {
    366             throw new NullPointerException("decryptKey is null");
    367         }
    368         if (provider == null) {
    369             throw new NullPointerException("provider is null");
    370         }
    371         return getKeySpecImpl(decryptKey, provider);
    372     }
    373 
    374     /**
    375      * Returns the ASN.1 encoding of this object.
    376      * @return the ASN.1 encoding. Returns a new array
    377      * each time this method is called.
    378      * @exception IOException if error occurs when constructing its
    379      * ASN.1 encoding.
    380      */
    381     public byte[] getEncoded() throws IOException {
    382         if (this.encoded == null) {
    383             DerOutputStream out = new DerOutputStream();
    384             DerOutputStream tmp = new DerOutputStream();
    385 
    386             // encode encryption algorithm
    387             algid.encode(tmp);
    388 
    389             // encode encrypted data
    390             tmp.putOctetString(encryptedData);
    391 
    392             // wrap everything into a SEQUENCE
    393             out.write(DerValue.tag_Sequence, tmp);
    394             this.encoded = out.toByteArray();
    395         }
    396         return this.encoded.clone();
    397     }
    398 
    399     private static void checkTag(DerValue val, byte tag, String valName)
    400         throws IOException {
    401         if (val.getTag() != tag) {
    402             throw new IOException("invalid key encoding - wrong tag for " +
    403                                   valName);
    404         }
    405     }
    406 
    407     @SuppressWarnings("fallthrough")
    408     private static void checkPKCS8Encoding(byte[] encodedKey)
    409         throws IOException {
    410         DerInputStream in = new DerInputStream(encodedKey);
    411         DerValue[] values = in.getSequence(3);
    412 
    413         switch (values.length) {
    414         case 4:
    415             checkTag(values[3], DerValue.TAG_CONTEXT, "attributes");
    416             /* fall through */
    417         case 3:
    418             checkTag(values[0], DerValue.tag_Integer, "version");
    419             DerInputStream algid = values[1].toDerInputStream();
    420             algid.getOID();
    421             if (algid.available() != 0) {
    422                 algid.getDerValue();
    423             }
    424             checkTag(values[2], DerValue.tag_OctetString, "privateKey");
    425             break;
    426         default:
    427             throw new IOException("invalid key encoding");
    428         }
    429     }
    430 }
    431