001    /* EncryptedPrivateKeyInfo.java -- As in PKCS #8.
002       Copyright (C) 2004  Free Software Foundation, Inc.
003    
004    This file is part of GNU Classpath.
005    
006    GNU Classpath is free software; you can redistribute it and/or modify
007    it under the terms of the GNU General Public License as published by
008    the Free Software Foundation; either version 2, or (at your option)
009    any later version.
010    
011    GNU Classpath is distributed in the hope that it will be useful, but
012    WITHOUT ANY WARRANTY; without even the implied warranty of
013    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014    General Public License for more details.
015    
016    You should have received a copy of the GNU General Public License
017    along with GNU Classpath; see the file COPYING.  If not, write to the
018    Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
019    02110-1301 USA.
020    
021    Linking this library statically or dynamically with other modules is
022    making a combined work based on this library.  Thus, the terms and
023    conditions of the GNU General Public License cover the whole
024    combination.
025    
026    As a special exception, the copyright holders of this library give you
027    permission to link this library with independent modules to produce an
028    executable, regardless of the license terms of these independent
029    modules, and to copy and distribute the resulting executable under
030    terms of your choice, provided that you also meet, for each linked
031    independent module, the terms and conditions of the license of that
032    module.  An independent module is a module which is not derived from
033    or based on this library.  If you modify this library, you may extend
034    this exception to your version of the library, but you are not
035    obligated to do so.  If you do not wish to do so, delete this
036    exception statement from your version. */
037    
038    
039    package javax.crypto;
040    
041    import gnu.java.security.OID;
042    import gnu.java.security.der.DER;
043    import gnu.java.security.der.DERReader;
044    import gnu.java.security.der.DERValue;
045    
046    import java.io.IOException;
047    import java.security.AlgorithmParameters;
048    import java.security.NoSuchAlgorithmException;
049    import java.security.spec.InvalidKeySpecException;
050    import java.security.spec.PKCS8EncodedKeySpec;
051    import java.util.ArrayList;
052    import java.util.List;
053    
054    /**
055     * An implementation of the <code>EncryptedPrivateKeyInfo</code> ASN.1
056     * type as specified in <a
057     * href="http://www.rsasecurity.com/rsalabs/pkcs/pkcs-8/">PKCS #8 -
058     * Private-Key Information Syntax Standard</a>.
059     *
060     * <p>The ASN.1 type <code>EncryptedPrivateKeyInfo</code> is:
061     *
062     * <blockquote>
063     * <pre>EncryptedPrivateKeyInfo ::= SEQUENCE {
064     *   encryptionAlgorithm EncryptionAlgorithmIdentifier,
065     *   encryptedData EncryptedData }
066     *
067     * EncryptionAlgorithmIdentifier ::= AlgorithmIdentifier
068     *
069     * EncrytpedData ::= OCTET STRING
070     *
071     * AlgorithmIdentifier ::= SEQUENCE {
072     *   algorithm  OBJECT IDENTIFIER,
073     *   parameters ANY DEFINED BY algorithm OPTIONAL }</pre>
074     * </blockquote>
075     *
076     * @author Casey Marshall (csm@gnu.org)
077     * @since 1.4
078     * @see java.security.spec.PKCS8EncodedKeySpec
079     */
080    public class EncryptedPrivateKeyInfo
081    {
082    
083      // Fields.
084      // ------------------------------------------------------------------------
085    
086      /** The encrypted data. */
087      private byte[] encryptedData;
088    
089      /** The encoded, encrypted key. */
090      private byte[] encoded;
091    
092      /** The OID of the encryption algorithm. */
093      private OID algOid;
094    
095      /** The encryption algorithm name. */
096      private String algName;
097    
098      /** The encryption algorithm's parameters. */
099      private AlgorithmParameters params;
100    
101      /** The encoded ASN.1 algorithm parameters. */
102      private byte[] encodedParams;
103    
104      // Constructors.
105      // ------------------------------------------------------------------------
106    
107      /**
108       * Create a new <code>EncryptedPrivateKeyInfo</code> object from raw
109       * encrypted data and the parameters used for encryption.
110       *
111       * <p>The <code>encryptedData</code> array is cloned.
112       *
113       * @param params        The encryption algorithm parameters.
114       * @param encryptedData The encrypted key data.
115       * @throws java.lang.IllegalArgumentException If the
116       *         <code>encryptedData</code> array is empty (zero-length).
117       * @throws java.security.NoSuchAlgorithmException If the algorithm
118       *         specified in the parameters is not supported.
119       * @throws java.lang.NullPointerException If <code>encryptedData</code>
120       *         is null.
121       */
122      public EncryptedPrivateKeyInfo(AlgorithmParameters params,
123                                     byte[] encryptedData)
124        throws IllegalArgumentException, NoSuchAlgorithmException
125      {
126        if (encryptedData.length == 0)
127          {
128            throw new IllegalArgumentException("0-length encryptedData");
129          }
130        this.params = params;
131        algName = params.getAlgorithm ();
132        algOid = getOid (algName);
133        this.encryptedData = (byte[]) encryptedData.clone();
134      }
135    
136      /**
137       * Create a new <code>EncryptedPrivateKeyInfo</code> from an encoded
138       * representation, parsing the ASN.1 sequence.
139       *
140       * @param encoded The encoded info.
141       * @throws java.io.IOException If parsing the encoded data fails.
142       * @throws java.lang.NullPointerException If <code>encoded</code> is
143       *         null.
144       */
145      public EncryptedPrivateKeyInfo(byte[] encoded)
146        throws IOException
147      {
148        this.encoded = (byte[]) encoded.clone();
149        decode();
150      }
151    
152      /**
153       * Create a new <code>EncryptedPrivateKeyInfo</code> from the cipher
154       * name and the encrytpedData.
155       *
156       * <p>The <code>encryptedData</code> array is cloned.
157       *
158       * @param algName       The name of the algorithm (as an object identifier).
159       * @param encryptedData The encrypted key data.
160       * @throws java.lang.IllegalArgumentException If the
161       *         <code>encryptedData</code> array is empty (zero-length).
162       * @throws java.security.NoSuchAlgorithmException If algName is not
163       *         the name of a supported algorithm.
164       * @throws java.lang.NullPointerException If <code>encryptedData</code>
165       *         is null.
166       */
167      public EncryptedPrivateKeyInfo(String algName, byte[] encryptedData)
168        throws IllegalArgumentException, NoSuchAlgorithmException,
169               NullPointerException
170      {
171        if (encryptedData.length == 0)
172          {
173            throw new IllegalArgumentException("0-length encryptedData");
174          }
175        this.algName = algName.toString (); // do NP check
176        this.algOid = getOid (algName);
177        this.encryptedData = (byte[]) encryptedData.clone();
178      }
179    
180      /**
181       * Return the OID for the given cipher name.
182       *
183       * @param str The string.
184       * @throws NoSuchAlgorithmException If the OID is not known.
185       */
186      private static OID getOid (final String str)
187        throws NoSuchAlgorithmException
188      {
189        if (str.equalsIgnoreCase ("DSA"))
190          {
191            return new OID ("1.2.840.10040.4.3");
192          }
193        // FIXME add more
194    
195        try
196          {
197            return new OID (str);
198          }
199        catch (Throwable t)
200          {
201          }
202        throw new NoSuchAlgorithmException ("cannot determine OID for '" + str + "'");
203      }
204    
205      // Instance methods.
206      // ------------------------------------------------------------------------
207    
208      /**
209       * Return the name of the cipher used to encrypt this key.
210       *
211       * @return The algorithm name.
212       */
213      public String getAlgName()
214      {
215        return algOid.toString();
216      }
217    
218      public AlgorithmParameters getAlgParameters()
219      {
220        if (params == null && encodedParams != null)
221          {
222            try
223              {
224                params = AlgorithmParameters.getInstance(getAlgName());
225                params.init(encodedParams);
226              }
227            catch (NoSuchAlgorithmException ignore)
228              {
229                // FIXME throw exception?
230              }
231            catch (IOException ignore)
232              {
233              }
234          }
235        return params;
236      }
237    
238      public synchronized byte[] getEncoded() throws IOException
239      {
240        if (encoded == null) encode();
241        return (byte[]) encoded.clone();
242      }
243    
244      public byte[] getEncryptedData()
245      {
246        return encryptedData;
247      }
248    
249      public PKCS8EncodedKeySpec getKeySpec(Cipher cipher)
250        throws InvalidKeySpecException
251      {
252        try
253          {
254            return new PKCS8EncodedKeySpec(cipher.doFinal(encryptedData));
255          }
256        catch (Exception x)
257          {
258            throw new InvalidKeySpecException(x.toString());
259          }
260      }
261    
262      // Own methods.
263      // -------------------------------------------------------------------------
264    
265      private void decode() throws IOException
266      {
267        DERReader der = new DERReader(encoded);
268        DERValue val = der.read();
269        if (val.getTag() != DER.SEQUENCE)
270          throw new IOException("malformed EncryptedPrivateKeyInfo");
271        val = der.read();
272        if (val.getTag() != DER.SEQUENCE)
273          throw new IOException("malformed AlgorithmIdentifier");
274        int algpLen = val.getLength();
275        DERValue oid = der.read();
276        if (oid.getTag() != DER.OBJECT_IDENTIFIER)
277          throw new IOException("malformed AlgorithmIdentifier");
278        algOid = (OID) oid.getValue();
279        if (algpLen == 0)
280          {
281            val = der.read();
282            if (val.getTag() != 0)
283              {
284                encodedParams = val.getEncoded();
285                der.read();
286              }
287          }
288        else if (oid.getEncodedLength() < val.getLength())
289          {
290            val = der.read();
291            encodedParams = val.getEncoded();
292          }
293        val = der.read();
294        if (val.getTag() != DER.OCTET_STRING)
295          throw new IOException("malformed AlgorithmIdentifier");
296        encryptedData = (byte[]) val.getValue();
297      }
298    
299      private void encode() throws IOException
300      {
301        List algId = new ArrayList(2);
302        algId.add(new DERValue(DER.OBJECT_IDENTIFIER, algOid));
303        getAlgParameters();
304        if (params != null)
305          {
306            algId.add (DERReader.read (params.getEncoded()));
307          }
308        else
309          {
310            algId.add (new DERValue (DER.NULL, null));
311          }
312        List epki = new ArrayList(2);
313        epki.add(new DERValue(DER.CONSTRUCTED|DER.SEQUENCE, algId));
314        epki.add(new DERValue(DER.OCTET_STRING, encryptedData));
315        encoded = new DERValue(DER.CONSTRUCTED|DER.SEQUENCE, epki).getEncoded();
316      }
317    }