1package org.bouncycastle.openssl;
2
3import java.io.BufferedWriter;
4import java.io.IOException;
5import java.io.Writer;
6import java.math.BigInteger;
7import java.security.Key;
8import java.security.KeyPair;
9import java.security.PrivateKey;
10import java.security.PublicKey;
11import java.security.SecureRandom;
12import java.security.cert.CRLException;
13import java.security.cert.CertificateEncodingException;
14import java.security.cert.X509CRL;
15import java.security.cert.X509Certificate;
16import java.security.interfaces.DSAParams;
17import java.security.interfaces.DSAPrivateKey;
18import java.security.interfaces.RSAPrivateCrtKey;
19import java.security.interfaces.RSAPrivateKey;
20
21import org.bouncycastle.asn1.ASN1EncodableVector;
22import org.bouncycastle.asn1.ASN1Object;
23import org.bouncycastle.asn1.ASN1Sequence;
24import org.bouncycastle.asn1.DERInteger;
25import org.bouncycastle.asn1.DERSequence;
26import org.bouncycastle.asn1.cms.ContentInfo;
27import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
28import org.bouncycastle.asn1.pkcs.RSAPrivateKeyStructure;
29import org.bouncycastle.asn1.x509.DSAParameter;
30import org.bouncycastle.jce.PKCS10CertificationRequest;
31import org.bouncycastle.util.Strings;
32import org.bouncycastle.util.encoders.Base64;
33import org.bouncycastle.util.encoders.Hex;
34import org.bouncycastle.x509.X509AttributeCertificate;
35import org.bouncycastle.x509.X509V2AttributeCertificate;
36
37/**
38 * General purpose writer for OpenSSL PEM objects.
39 */
40public class PEMWriter
41    extends BufferedWriter
42{
43    private String provider;
44
45    /**
46     * Base constructor.
47     *
48     * @param out output stream to use.
49     */
50    public PEMWriter(Writer out)
51    {
52        this(out, "BC");
53    }
54
55    public PEMWriter(
56        Writer  out,
57        String  provider)
58    {
59        super(out);
60
61        this.provider = provider;
62    }
63
64    private void writeHexEncoded(byte[] bytes)
65        throws IOException
66    {
67        bytes = Hex.encode(bytes);
68
69        for (int i = 0; i != bytes.length; i++)
70        {
71            this.write((char)bytes[i]);
72        }
73    }
74
75    private void writeEncoded(byte[] bytes)
76        throws IOException
77    {
78        char[]  buf = new char[64];
79
80        bytes = Base64.encode(bytes);
81
82        for (int i = 0; i < bytes.length; i += buf.length)
83        {
84            int index = 0;
85
86            while (index != buf.length)
87            {
88                if ((i + index) >= bytes.length)
89                {
90                    break;
91                }
92                buf[index] = (char)bytes[i + index];
93                index++;
94            }
95            this.write(buf, 0, index);
96            this.newLine();
97        }
98    }
99
100    public void writeObject(
101        Object  o)
102        throws IOException
103    {
104        String  type;
105        byte[]  encoding;
106
107        if (o instanceof X509Certificate)
108        {
109            type = "CERTIFICATE";
110            try
111            {
112                encoding = ((X509Certificate)o).getEncoded();
113            }
114            catch (CertificateEncodingException e)
115            {
116                throw new IOException("Cannot encode object: " + e.toString());
117            }
118        }
119        else if (o instanceof X509CRL)
120        {
121            type = "X509 CRL";
122            try
123            {
124                encoding = ((X509CRL)o).getEncoded();
125            }
126            catch (CRLException e)
127            {
128                throw new IOException("Cannot encode object: " + e.toString());
129            }
130        }
131        else if (o instanceof KeyPair)
132        {
133            writeObject(((KeyPair)o).getPrivate());
134            return;
135        }
136        else if (o instanceof PrivateKey)
137        {
138            PrivateKeyInfo info = new PrivateKeyInfo(
139                (ASN1Sequence) ASN1Object.fromByteArray(((Key)o).getEncoded()));
140
141            if (o instanceof RSAPrivateKey)
142            {
143                type = "RSA PRIVATE KEY";
144
145                encoding = info.getPrivateKey().getEncoded();
146            }
147            else if (o instanceof DSAPrivateKey)
148            {
149                type = "DSA PRIVATE KEY";
150
151                DSAParameter        p = DSAParameter.getInstance(info.getAlgorithmId().getParameters());
152                ASN1EncodableVector v = new ASN1EncodableVector();
153
154                v.add(new DERInteger(0));
155                v.add(new DERInteger(p.getP()));
156                v.add(new DERInteger(p.getQ()));
157                v.add(new DERInteger(p.getG()));
158
159                BigInteger x = ((DSAPrivateKey)o).getX();
160                BigInteger y = p.getG().modPow(x, p.getP());
161
162                v.add(new DERInteger(y));
163                v.add(new DERInteger(x));
164
165                encoding = new DERSequence(v).getEncoded();
166            }
167            else if (((PrivateKey)o).getAlgorithm().equals("ECDSA"))
168            {
169                type = "EC PRIVATE KEY";
170
171                encoding = info.getPrivateKey().getEncoded();
172            }
173            else
174            {
175                throw new IOException("Cannot identify private key");
176            }
177        }
178        else if (o instanceof PublicKey)
179        {
180            type = "PUBLIC KEY";
181
182            encoding = ((PublicKey)o).getEncoded();
183        }
184        else if (o instanceof X509AttributeCertificate)
185        {
186            type = "ATTRIBUTE CERTIFICATE";
187            encoding = ((X509V2AttributeCertificate)o).getEncoded();
188        }
189        else if (o instanceof PKCS10CertificationRequest)
190        {
191            type = "CERTIFICATE REQUEST";
192            encoding = ((PKCS10CertificationRequest)o).getEncoded();
193        }
194        else if (o instanceof ContentInfo)
195        {
196            type = "PKCS7";
197            encoding = ((ContentInfo)o).getEncoded();
198        }
199        else
200        {
201            throw new IOException("unknown object passed - can't encode.");
202        }
203
204        writeHeader(type);
205        writeEncoded(encoding);
206        writeFooter(type);
207    }
208
209    public void writeObject(
210        Object       obj,
211        String       algorithm,
212        char[]       password,
213        SecureRandom random)
214        throws IOException
215    {
216        if (obj instanceof KeyPair)
217        {
218            writeObject(((KeyPair)obj).getPrivate());
219            return;
220        }
221
222        String type = null;
223        byte[] keyData = null;
224
225        if (obj instanceof RSAPrivateCrtKey)
226        {
227            type = "RSA PRIVATE KEY";
228
229            RSAPrivateCrtKey k = (RSAPrivateCrtKey)obj;
230
231            RSAPrivateKeyStructure keyStruct = new RSAPrivateKeyStructure(
232                k.getModulus(),
233                k.getPublicExponent(),
234                k.getPrivateExponent(),
235                k.getPrimeP(),
236                k.getPrimeQ(),
237                k.getPrimeExponentP(),
238                k.getPrimeExponentQ(),
239                k.getCrtCoefficient());
240
241            // convert to bytearray
242            keyData = keyStruct.getEncoded();
243        }
244        else if (obj instanceof DSAPrivateKey)
245        {
246            type = "DSA PRIVATE KEY";
247
248            DSAPrivateKey       k = (DSAPrivateKey)obj;
249            DSAParams           p = k.getParams();
250            ASN1EncodableVector v = new ASN1EncodableVector();
251
252            v.add(new DERInteger(0));
253            v.add(new DERInteger(p.getP()));
254            v.add(new DERInteger(p.getQ()));
255            v.add(new DERInteger(p.getG()));
256
257            BigInteger x = k.getX();
258            BigInteger y = p.getG().modPow(x, p.getP());
259
260            v.add(new DERInteger(y));
261            v.add(new DERInteger(x));
262
263            keyData = new DERSequence(v).getEncoded();
264        }
265        else if (obj instanceof PrivateKey && "ECDSA".equals(((PrivateKey)obj).getAlgorithm()))
266        {
267            type = "EC PRIVATE KEY";
268
269            PrivateKeyInfo      privInfo = PrivateKeyInfo.getInstance(ASN1Object.fromByteArray(((PrivateKey)obj).getEncoded()));
270
271            keyData = privInfo.getPrivateKey().getEncoded();
272        }
273
274        if (type == null || keyData == null)
275        {
276            // TODO Support other types?
277            throw new IllegalArgumentException("Object type not supported: " + obj.getClass().getName());
278        }
279
280
281        String dekAlgName = Strings.toUpperCase(algorithm);
282
283        // Note: For backward compatibility
284        if (dekAlgName.equals("DESEDE"))
285        {
286            dekAlgName = "DES-EDE3-CBC";
287        }
288
289        int ivLength = dekAlgName.startsWith("AES-") ? 16 : 8;
290
291        byte[] iv = new byte[ivLength];
292        random.nextBytes(iv);
293
294        byte[] encData = PEMUtilities.crypt(true, provider, keyData, password, dekAlgName, iv);
295
296
297        // write the data
298        writeHeader(type);
299        this.write("Proc-Type: 4,ENCRYPTED");
300        this.newLine();
301        this.write("DEK-Info: " + dekAlgName + ",");
302        this.writeHexEncoded(iv);
303        this.newLine();
304        this.newLine();
305        this.writeEncoded(encData);
306        writeFooter(type);
307    }
308
309    private void writeHeader(
310        String type)
311        throws IOException
312    {
313        this.write("-----BEGIN " + type + "-----");
314        this.newLine();
315    }
316
317    private void writeFooter(
318        String type)
319        throws IOException
320    {
321        this.write("-----END " + type + "-----");
322        this.newLine();
323    }
324}
325