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