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