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
26package javax.crypto;
27
28import java.io.*;
29import java.security.*;
30import java.security.spec.*;
31import sun.security.x509.AlgorithmId;
32import sun.security.util.DerValue;
33import sun.security.util.DerInputStream;
34import 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
58public 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