Mercurial > 510Connectbot
comparison src/ch/ethz/ssh2/crypto/PEMDecoder.java @ 275:03ae56b26003 ganymed
start conversion from trilead to ganymed
author | Carl Byington <carl@five-ten-sg.com> |
---|---|
date | Fri, 18 Jul 2014 11:56:27 -0700 |
parents | 91a31873c42a |
children | 3a1deb1040f6 |
comparison
equal
deleted
inserted
replaced
274:82bd20cb0d1d | 275:03ae56b26003 |
---|---|
1 /* | 1 |
2 * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. | 2 package com.trilead.ssh2.crypto; |
3 * Please refer to the LICENSE.txt for licensing details. | |
4 */ | |
5 package ch.ethz.ssh2.crypto; | |
6 | 3 |
7 import java.io.BufferedReader; | 4 import java.io.BufferedReader; |
8 import java.io.CharArrayReader; | 5 import java.io.CharArrayReader; |
9 import java.io.IOException; | 6 import java.io.IOException; |
10 import java.math.BigInteger; | 7 import java.math.BigInteger; |
11 import java.security.DigestException; | 8 import java.security.DigestException; |
12 | 9 import java.security.KeyFactory; |
13 import ch.ethz.ssh2.crypto.cipher.AES; | 10 import java.security.KeyPair; |
14 import ch.ethz.ssh2.crypto.cipher.BlockCipher; | 11 import java.security.MessageDigest; |
15 import ch.ethz.ssh2.crypto.cipher.CBCMode; | 12 import java.security.NoSuchAlgorithmException; |
16 import ch.ethz.ssh2.crypto.cipher.DES; | 13 import java.security.PrivateKey; |
17 import ch.ethz.ssh2.crypto.cipher.DESede; | 14 import java.security.PublicKey; |
18 import ch.ethz.ssh2.crypto.digest.MD5; | 15 import java.security.spec.DSAPrivateKeySpec; |
19 import ch.ethz.ssh2.signature.DSAPrivateKey; | 16 import java.security.spec.DSAPublicKeySpec; |
20 import ch.ethz.ssh2.signature.RSAPrivateKey; | 17 import java.security.spec.ECParameterSpec; |
21 import ch.ethz.ssh2.util.StringEncoder; | 18 import java.security.spec.ECPoint; |
19 import java.security.spec.ECPrivateKeySpec; | |
20 import java.security.spec.ECPublicKeySpec; | |
21 import java.security.spec.InvalidKeySpecException; | |
22 import java.security.spec.KeySpec; | |
23 import java.security.spec.RSAPrivateCrtKeySpec; | |
24 import java.security.spec.RSAPrivateKeySpec; | |
25 import java.security.spec.RSAPublicKeySpec; | |
26 | |
27 import com.trilead.ssh2.crypto.cipher.AES; | |
28 import com.trilead.ssh2.crypto.cipher.BlockCipher; | |
29 import com.trilead.ssh2.crypto.cipher.CBCMode; | |
30 import com.trilead.ssh2.crypto.cipher.DES; | |
31 import com.trilead.ssh2.crypto.cipher.DESede; | |
32 import com.trilead.ssh2.signature.ECDSASHA2Verify; | |
22 | 33 |
23 /** | 34 /** |
24 * PEM Support. | 35 * PEM Support. |
25 * | 36 * |
26 * @author Christian Plattner | 37 * @author Christian Plattner, plattner@trilead.com |
27 * @version $Id: PEMDecoder.java 154 2014-04-28 11:45:02Z dkocher@sudo.ch $ | 38 * @version $Id: PEMDecoder.java,v 1.2 2008/04/01 12:38:09 cplattne Exp $ |
28 */ | 39 */ |
29 public class PEMDecoder | 40 public class PEMDecoder { |
30 { | 41 public static final int PEM_RSA_PRIVATE_KEY = 1; |
31 private static final int PEM_RSA_PRIVATE_KEY = 1; | 42 public static final int PEM_DSA_PRIVATE_KEY = 2; |
32 private static final int PEM_DSA_PRIVATE_KEY = 2; | 43 public static final int PEM_EC_PRIVATE_KEY = 3; |
33 | 44 |
34 private static int hexToInt(char c) | 45 private static final int hexToInt(char c) { |
35 { | 46 if ((c >= 'a') && (c <= 'f')) { |
36 if ((c >= 'a') && (c <= 'f')) | 47 return (c - 'a') + 10; |
37 { | 48 } |
38 return (c - 'a') + 10; | 49 |
39 } | 50 if ((c >= 'A') && (c <= 'F')) { |
40 | 51 return (c - 'A') + 10; |
41 if ((c >= 'A') && (c <= 'F')) | 52 } |
42 { | 53 |
43 return (c - 'A') + 10; | 54 if ((c >= '0') && (c <= '9')) { |
44 } | 55 return (c - '0'); |
45 | 56 } |
46 if ((c >= '0') && (c <= '9')) | 57 |
47 { | 58 throw new IllegalArgumentException("Need hex char"); |
48 return (c - '0'); | 59 } |
49 } | 60 |
50 | 61 private static byte[] hexToByteArray(String hex) { |
51 throw new IllegalArgumentException("Need hex char"); | 62 if (hex == null) |
52 } | 63 throw new IllegalArgumentException("null argument"); |
53 | 64 |
54 private static byte[] hexToByteArray(String hex) | 65 if ((hex.length() % 2) != 0) |
55 { | 66 throw new IllegalArgumentException("Uneven string length in hex encoding."); |
56 if (hex == null) | 67 |
57 throw new IllegalArgumentException("null argument"); | 68 byte decoded[] = new byte[hex.length() / 2]; |
58 | 69 |
59 if ((hex.length() % 2) != 0) | 70 for (int i = 0; i < decoded.length; i++) { |
60 throw new IllegalArgumentException("Uneven string length in hex encoding."); | 71 int hi = hexToInt(hex.charAt(i * 2)); |
61 | 72 int lo = hexToInt(hex.charAt((i * 2) + 1)); |
62 byte decoded[] = new byte[hex.length() / 2]; | 73 decoded[i] = (byte)(hi * 16 + lo); |
63 | 74 } |
64 for (int i = 0; i < decoded.length; i++) | 75 |
65 { | 76 return decoded; |
66 int hi = hexToInt(hex.charAt(i * 2)); | 77 } |
67 int lo = hexToInt(hex.charAt((i * 2) + 1)); | 78 |
68 | 79 private static byte[] generateKeyFromPasswordSaltWithMD5(byte[] password, byte[] salt, int keyLen) |
69 decoded[i] = (byte) (hi * 16 + lo); | 80 throws IOException { |
70 } | 81 if (salt.length < 8) |
71 | 82 throw new IllegalArgumentException("Salt needs to be at least 8 bytes for key generation."); |
72 return decoded; | 83 |
73 } | 84 MessageDigest md5; |
74 | 85 |
75 private static byte[] generateKeyFromPasswordSaltWithMD5(byte[] password, byte[] salt, int keyLen) | 86 try { |
76 throws IOException | 87 md5 = MessageDigest.getInstance("MD5"); |
77 { | 88 } |
78 if (salt.length < 8) | 89 catch (NoSuchAlgorithmException e) { |
79 throw new IllegalArgumentException("Salt needs to be at least 8 bytes for key generation."); | 90 throw new IllegalArgumentException("VM does not support MD5", e); |
80 | 91 } |
81 MD5 md5 = new MD5(); | 92 |
82 | 93 byte[] key = new byte[keyLen]; |
83 byte[] key = new byte[keyLen]; | 94 byte[] tmp = new byte[md5.getDigestLength()]; |
84 byte[] tmp = new byte[md5.getDigestLength()]; | 95 |
85 | 96 while (true) { |
86 while (true) | 97 md5.update(password, 0, password.length); |
87 { | 98 md5.update(salt, 0, 8); // ARGH we only use the first 8 bytes of the |
88 md5.update(password, 0, password.length); | 99 // salt in this step. |
89 md5.update(salt, 0, 8); // ARGH we only use the first 8 bytes of the salt in this step. | 100 // This took me two hours until I got AES-xxx running. |
90 // This took me two hours until I got AES-xxx running. | 101 int copy = (keyLen < tmp.length) ? keyLen : tmp.length; |
91 | |
92 int copy = (keyLen < tmp.length) ? keyLen : tmp.length; | |
93 | 102 |
94 try { | 103 try { |
95 md5.digest(tmp, 0); | 104 md5.digest(tmp, 0, tmp.length); |
96 } | 105 } |
97 catch(DigestException e) { | 106 catch (DigestException e) { |
98 throw new IOException(e); | 107 IOException ex = new IOException("could not digest password"); |
108 ex.initCause(e); | |
109 throw ex; | |
99 } | 110 } |
100 | 111 |
101 System.arraycopy(tmp, 0, key, key.length - keyLen, copy); | 112 System.arraycopy(tmp, 0, key, key.length - keyLen, copy); |
102 | 113 keyLen -= copy; |
103 keyLen -= copy; | 114 |
104 | 115 if (keyLen == 0) |
105 if (keyLen == 0) | 116 return key; |
106 return key; | 117 |
107 | 118 md5.update(tmp, 0, tmp.length); |
108 md5.update(tmp, 0, tmp.length); | 119 } |
109 } | 120 } |
110 } | 121 |
111 | 122 private static byte[] removePadding(byte[] buff, int blockSize) throws IOException { |
112 private static byte[] removePadding(byte[] buff, int blockSize) throws IOException | 123 /* Removes RFC 1423/PKCS #7 padding */ |
113 { | 124 int rfc_1423_padding = buff[buff.length - 1] & 0xff; |
114 /* Removes RFC 1423/PKCS #7 padding */ | 125 |
115 | 126 if ((rfc_1423_padding < 1) || (rfc_1423_padding > blockSize)) |
116 int rfc_1423_padding = buff[buff.length - 1] & 0xff; | 127 throw new IOException("Decrypted PEM has wrong padding, did you specify the correct password?"); |
117 | 128 |
118 if ((rfc_1423_padding < 1) || (rfc_1423_padding > blockSize)) | 129 for (int i = 2; i <= rfc_1423_padding; i++) { |
119 throw new PEMDecryptException("Decrypted PEM has wrong padding, did you specify the correct password?"); | 130 if (buff[buff.length - i] != rfc_1423_padding) |
120 | 131 throw new IOException("Decrypted PEM has wrong padding, did you specify the correct password?"); |
121 for (int i = 2; i <= rfc_1423_padding; i++) | 132 } |
122 { | 133 |
123 if (buff[buff.length - i] != rfc_1423_padding) | 134 byte[] tmp = new byte[buff.length - rfc_1423_padding]; |
124 throw new PEMDecryptException("Decrypted PEM has wrong padding, did you specify the correct password?"); | 135 System.arraycopy(buff, 0, tmp, 0, buff.length - rfc_1423_padding); |
125 } | 136 return tmp; |
126 | 137 } |
127 byte[] tmp = new byte[buff.length - rfc_1423_padding]; | 138 |
128 System.arraycopy(buff, 0, tmp, 0, buff.length - rfc_1423_padding); | 139 public static final PEMStructure parsePEM(char[] pem) throws IOException { |
129 return tmp; | 140 PEMStructure ps = new PEMStructure(); |
130 } | 141 String line = null; |
131 | 142 BufferedReader br = new BufferedReader(new CharArrayReader(pem)); |
132 private static PEMStructure parsePEM(char[] pem) throws IOException | 143 String endLine = null; |
133 { | 144 |
134 PEMStructure ps = new PEMStructure(); | 145 while (true) { |
135 | 146 line = br.readLine(); |
136 String line = null; | 147 |
137 | 148 if (line == null) |
138 BufferedReader br = new BufferedReader(new CharArrayReader(pem)); | 149 throw new IOException("Invalid PEM structure, '-----BEGIN...' missing"); |
139 | 150 |
140 String endLine = null; | 151 line = line.trim(); |
141 | 152 |
142 while (true) | 153 if (line.startsWith("-----BEGIN DSA PRIVATE KEY-----")) { |
143 { | 154 endLine = "-----END DSA PRIVATE KEY-----"; |
144 line = br.readLine(); | 155 ps.pemType = PEM_DSA_PRIVATE_KEY; |
145 | 156 break; |
146 if (line == null) | 157 } |
147 throw new IOException("Invalid PEM structure, '-----BEGIN...' missing"); | 158 |
148 | 159 if (line.startsWith("-----BEGIN RSA PRIVATE KEY-----")) { |
149 line = line.trim(); | 160 endLine = "-----END RSA PRIVATE KEY-----"; |
150 | 161 ps.pemType = PEM_RSA_PRIVATE_KEY; |
151 if (line.startsWith("-----BEGIN DSA PRIVATE KEY-----")) | 162 break; |
152 { | 163 } |
153 endLine = "-----END DSA PRIVATE KEY-----"; | 164 |
154 ps.pemType = PEM_DSA_PRIVATE_KEY; | 165 if (line.startsWith("-----BEGIN EC PRIVATE KEY-----")) { |
155 break; | 166 endLine = "-----END EC PRIVATE KEY-----"; |
156 } | 167 ps.pemType = PEM_EC_PRIVATE_KEY; |
157 | 168 break; |
158 if (line.startsWith("-----BEGIN RSA PRIVATE KEY-----")) | 169 } |
159 { | 170 } |
160 endLine = "-----END RSA PRIVATE KEY-----"; | 171 |
161 ps.pemType = PEM_RSA_PRIVATE_KEY; | 172 while (true) { |
162 break; | 173 line = br.readLine(); |
163 } | 174 |
164 } | 175 if (line == null) |
165 | 176 throw new IOException("Invalid PEM structure, " + endLine + " missing"); |
166 while (true) | 177 |
167 { | 178 line = line.trim(); |
168 line = br.readLine(); | 179 int sem_idx = line.indexOf(':'); |
169 | 180 |
170 if (line == null) | 181 if (sem_idx == -1) |
171 throw new IOException("Invalid PEM structure, " + endLine + " missing"); | 182 break; |
172 | 183 |
173 line = line.trim(); | 184 String name = line.substring(0, sem_idx + 1); |
174 | 185 String value = line.substring(sem_idx + 1); |
175 int sem_idx = line.indexOf(':'); | 186 String values[] = value.split(","); |
176 | 187 |
177 if (sem_idx == -1) | 188 for (int i = 0; i < values.length; i++) |
178 break; | 189 values[i] = values[i].trim(); |
179 | 190 |
180 String name = line.substring(0, sem_idx + 1); | 191 // Proc-Type: 4,ENCRYPTED |
181 String value = line.substring(sem_idx + 1); | 192 // DEK-Info: DES-EDE3-CBC,579B6BE3E5C60483 |
182 | 193 |
183 String values[] = value.split(","); | 194 if ("Proc-Type:".equals(name)) { |
184 | 195 ps.procType = values; |
185 for (int i = 0; i < values.length; i++) | 196 continue; |
186 values[i] = values[i].trim(); | 197 } |
187 | 198 |
188 // Proc-Type: 4,ENCRYPTED | 199 if ("DEK-Info:".equals(name)) { |
189 // DEK-Info: DES-EDE3-CBC,579B6BE3E5C60483 | 200 ps.dekInfo = values; |
190 | 201 continue; |
191 if ("Proc-Type:".equals(name)) | 202 } |
192 { | 203 |
193 ps.procType = values; | 204 /* Ignore line */ |
194 continue; | 205 } |
195 } | 206 |
196 | 207 StringBuffer keyData = new StringBuffer(); |
197 if ("DEK-Info:".equals(name)) | 208 |
198 { | 209 while (true) { |
199 ps.dekInfo = values; | 210 if (line == null) |
200 continue; | 211 throw new IOException("Invalid PEM structure, " + endLine + " missing"); |
201 } | 212 |
202 /* Ignore line */ | 213 line = line.trim(); |
203 } | 214 |
204 | 215 if (line.startsWith(endLine)) |
205 StringBuilder keyData = new StringBuilder(); | 216 break; |
206 | 217 |
207 while (true) | 218 keyData.append(line); |
208 { | 219 line = br.readLine(); |
209 if (line == null) | 220 } |
210 throw new IOException("Invalid PEM structure, " + endLine + " missing"); | 221 |
211 | 222 char[] pem_chars = new char[keyData.length()]; |
212 line = line.trim(); | 223 keyData.getChars(0, pem_chars.length, pem_chars, 0); |
213 | 224 ps.data = Base64.decode(pem_chars); |
214 if (line.startsWith(endLine)) | 225 |
215 break; | 226 if (ps.data.length == 0) |
216 | 227 throw new IOException("Invalid PEM structure, no data available"); |
217 keyData.append(line); | 228 |
218 | 229 return ps; |
219 line = br.readLine(); | 230 } |
220 } | 231 |
221 | 232 private static final void decryptPEM(PEMStructure ps, byte[] pw) throws IOException { |
222 char[] pem_chars = new char[keyData.length()]; | 233 if (ps.dekInfo == null) |
223 keyData.getChars(0, pem_chars.length, pem_chars, 0); | 234 throw new IOException("Broken PEM, no mode and salt given, but encryption enabled"); |
224 | 235 |
225 ps.data = Base64.decode(pem_chars); | 236 if (ps.dekInfo.length != 2) |
226 | 237 throw new IOException("Broken PEM, DEK-Info is incomplete!"); |
227 if (ps.data.length == 0) | 238 |
228 throw new IOException("Invalid PEM structure, no data available"); | 239 String algo = ps.dekInfo[0]; |
229 | 240 byte[] salt = hexToByteArray(ps.dekInfo[1]); |
230 return ps; | 241 BlockCipher bc = null; |
231 } | 242 |
232 | 243 if (algo.equals("DES-EDE3-CBC")) { |
233 private static void decryptPEM(PEMStructure ps, byte[] pw) throws IOException | 244 DESede des3 = new DESede(); |
234 { | 245 des3.init(false, generateKeyFromPasswordSaltWithMD5(pw, salt, 24)); |
235 if (ps.dekInfo == null) | 246 bc = new CBCMode(des3, salt, false); |
236 throw new IOException("Broken PEM, no mode and salt given, but encryption enabled"); | 247 } |
237 | 248 else if (algo.equals("DES-CBC")) { |
238 if (ps.dekInfo.length != 2) | 249 DES des = new DES(); |
239 throw new IOException("Broken PEM, DEK-Info is incomplete!"); | 250 des.init(false, generateKeyFromPasswordSaltWithMD5(pw, salt, 8)); |
240 | 251 bc = new CBCMode(des, salt, false); |
241 String algo = ps.dekInfo[0]; | 252 } |
242 byte[] salt = hexToByteArray(ps.dekInfo[1]); | 253 else if (algo.equals("AES-128-CBC")) { |
243 | 254 AES aes = new AES(); |
244 BlockCipher bc = null; | 255 aes.init(false, generateKeyFromPasswordSaltWithMD5(pw, salt, 16)); |
245 | 256 bc = new CBCMode(aes, salt, false); |
246 if (algo.equals("DES-EDE3-CBC")) | 257 } |
247 { | 258 else if (algo.equals("AES-192-CBC")) { |
248 DESede des3 = new DESede(); | 259 AES aes = new AES(); |
249 des3.init(false, generateKeyFromPasswordSaltWithMD5(pw, salt, 24)); | 260 aes.init(false, generateKeyFromPasswordSaltWithMD5(pw, salt, 24)); |
250 bc = new CBCMode(des3, salt, false); | 261 bc = new CBCMode(aes, salt, false); |
251 } | 262 } |
252 else if (algo.equals("DES-CBC")) | 263 else if (algo.equals("AES-256-CBC")) { |
253 { | 264 AES aes = new AES(); |
254 DES des = new DES(); | 265 aes.init(false, generateKeyFromPasswordSaltWithMD5(pw, salt, 32)); |
255 des.init(false, generateKeyFromPasswordSaltWithMD5(pw, salt, 8)); | 266 bc = new CBCMode(aes, salt, false); |
256 bc = new CBCMode(des, salt, false); | 267 } |
257 } | 268 else { |
258 else if (algo.equals("AES-128-CBC")) | 269 throw new IOException("Cannot decrypt PEM structure, unknown cipher " + algo); |
259 { | 270 } |
260 AES aes = new AES(); | 271 |
261 aes.init(false, generateKeyFromPasswordSaltWithMD5(pw, salt, 16)); | 272 if ((ps.data.length % bc.getBlockSize()) != 0) |
262 bc = new CBCMode(aes, salt, false); | 273 throw new IOException("Invalid PEM structure, size of encrypted block is not a multiple of " |
263 } | 274 + bc.getBlockSize()); |
264 else if (algo.equals("AES-192-CBC")) | 275 |
265 { | 276 /* Now decrypt the content */ |
266 AES aes = new AES(); | 277 byte[] dz = new byte[ps.data.length]; |
267 aes.init(false, generateKeyFromPasswordSaltWithMD5(pw, salt, 24)); | 278 |
268 bc = new CBCMode(aes, salt, false); | 279 for (int i = 0; i < ps.data.length / bc.getBlockSize(); i++) { |
269 } | 280 bc.transformBlock(ps.data, i * bc.getBlockSize(), dz, i * bc.getBlockSize()); |
270 else if (algo.equals("AES-256-CBC")) | 281 } |
271 { | 282 |
272 AES aes = new AES(); | 283 /* Now check and remove RFC 1423/PKCS #7 padding */ |
273 aes.init(false, generateKeyFromPasswordSaltWithMD5(pw, salt, 32)); | 284 dz = removePadding(dz, bc.getBlockSize()); |
274 bc = new CBCMode(aes, salt, false); | 285 ps.data = dz; |
275 } | 286 ps.dekInfo = null; |
276 else | 287 ps.procType = null; |
277 { | 288 } |
278 throw new IOException("Cannot decrypt PEM structure, unknown cipher " + algo); | 289 |
279 } | 290 public static final boolean isPEMEncrypted(PEMStructure ps) throws IOException { |
280 | 291 if (ps.procType == null) |
281 if ((ps.data.length % bc.getBlockSize()) != 0) | 292 return false; |
282 throw new IOException("Invalid PEM structure, size of encrypted block is not a multiple of " | 293 |
283 + bc.getBlockSize()); | 294 if (ps.procType.length != 2) |
284 | 295 throw new IOException("Unknown Proc-Type field."); |
285 /* Now decrypt the content */ | 296 |
286 | 297 if ("4".equals(ps.procType[0]) == false) |
287 byte[] dz = new byte[ps.data.length]; | 298 throw new IOException("Unknown Proc-Type field (" + ps.procType[0] + ")"); |
288 | 299 |
289 for (int i = 0; i < ps.data.length / bc.getBlockSize(); i++) | 300 if ("ENCRYPTED".equals(ps.procType[1])) |
290 { | 301 return true; |
291 bc.transformBlock(ps.data, i * bc.getBlockSize(), dz, i * bc.getBlockSize()); | 302 |
292 } | 303 return false; |
293 | 304 } |
294 /* Now check and remove RFC 1423/PKCS #7 padding */ | 305 |
295 | 306 public static KeyPair decode(char[] pem, String password) throws IOException { |
296 dz = removePadding(dz, bc.getBlockSize()); | 307 PEMStructure ps = parsePEM(pem); |
297 | 308 return decode(ps, password); |
298 ps.data = dz; | 309 } |
299 ps.dekInfo = null; | 310 |
300 ps.procType = null; | 311 public static KeyPair decode(PEMStructure ps, String password) throws IOException { |
301 } | 312 if (isPEMEncrypted(ps)) { |
302 | 313 if (password == null) |
303 public static final boolean isPEMEncrypted(PEMStructure ps) throws IOException | 314 throw new IOException("PEM is encrypted, but no password was specified"); |
304 { | 315 |
305 if (ps.procType == null) | 316 decryptPEM(ps, password.getBytes("ISO-8859-1")); |
306 return false; | 317 } |
307 | 318 |
308 if (ps.procType.length != 2) | 319 if (ps.pemType == PEM_DSA_PRIVATE_KEY) { |
309 throw new IOException("Unknown Proc-Type field."); | 320 SimpleDERReader dr = new SimpleDERReader(ps.data); |
310 | 321 byte[] seq = dr.readSequenceAsByteArray(); |
311 if ("4".equals(ps.procType[0]) == false) | 322 |
312 throw new IOException("Unknown Proc-Type field (" + ps.procType[0] + ")"); | 323 if (dr.available() != 0) |
313 | 324 throw new IOException("Padding in DSA PRIVATE KEY DER stream."); |
314 if ("ENCRYPTED".equals(ps.procType[1])) | 325 |
315 return true; | 326 dr.resetInput(seq); |
316 | 327 BigInteger version = dr.readInt(); |
317 return false; | 328 |
318 } | 329 if (version.compareTo(BigInteger.ZERO) != 0) |
319 | 330 throw new IOException("Wrong version (" + version + ") in DSA PRIVATE KEY DER stream."); |
320 public static final boolean isPEMEncrypted(char[] pem) throws IOException | 331 |
321 { | 332 BigInteger p = dr.readInt(); |
322 return isPEMEncrypted(parsePEM(pem)); | 333 BigInteger q = dr.readInt(); |
323 } | 334 BigInteger g = dr.readInt(); |
324 | 335 BigInteger y = dr.readInt(); |
325 public static Object decode(char[] pem, String password) throws IOException | 336 BigInteger x = dr.readInt(); |
326 { | 337 |
327 PEMStructure ps = parsePEM(pem); | 338 if (dr.available() != 0) |
328 | 339 throw new IOException("Padding in DSA PRIVATE KEY DER stream."); |
329 if (isPEMEncrypted(ps)) | 340 |
330 { | 341 DSAPrivateKeySpec privSpec = new DSAPrivateKeySpec(x, p, q, g); |
331 if (password == null) | 342 DSAPublicKeySpec pubSpec = new DSAPublicKeySpec(y, p, q, g); |
332 throw new IOException("PEM is encrypted, but no password was specified"); | 343 return generateKeyPair("DSA", privSpec, pubSpec); |
333 | 344 } |
334 decryptPEM(ps, StringEncoder.GetBytes(password)); | 345 |
335 } | 346 if (ps.pemType == PEM_RSA_PRIVATE_KEY) { |
336 | 347 SimpleDERReader dr = new SimpleDERReader(ps.data); |
337 if (ps.pemType == PEM_DSA_PRIVATE_KEY) | 348 byte[] seq = dr.readSequenceAsByteArray(); |
338 { | 349 |
339 SimpleDERReader dr = new SimpleDERReader(ps.data); | 350 if (dr.available() != 0) |
340 | 351 throw new IOException("Padding in RSA PRIVATE KEY DER stream."); |
341 byte[] seq = dr.readSequenceAsByteArray(); | 352 |
342 | 353 dr.resetInput(seq); |
343 if (dr.available() != 0) | 354 BigInteger version = dr.readInt(); |
344 throw new IOException("Padding in DSA PRIVATE KEY DER stream."); | 355 |
345 | 356 if ((version.compareTo(BigInteger.ZERO) != 0) && (version.compareTo(BigInteger.ONE) != 0)) |
346 dr.resetInput(seq); | 357 throw new IOException("Wrong version (" + version + ") in RSA PRIVATE KEY DER stream."); |
347 | 358 |
348 BigInteger version = dr.readInt(); | 359 BigInteger n = dr.readInt(); |
349 | 360 BigInteger e = dr.readInt(); |
350 if (version.compareTo(BigInteger.ZERO) != 0) | 361 BigInteger d = dr.readInt(); |
351 throw new IOException("Wrong version (" + version + ") in DSA PRIVATE KEY DER stream."); | 362 // TODO: is this right? |
352 | 363 BigInteger primeP = dr.readInt(); |
353 BigInteger p = dr.readInt(); | 364 BigInteger primeQ = dr.readInt(); |
354 BigInteger q = dr.readInt(); | 365 BigInteger expP = dr.readInt(); |
355 BigInteger g = dr.readInt(); | 366 BigInteger expQ = dr.readInt(); |
356 BigInteger y = dr.readInt(); | 367 BigInteger coeff = dr.readInt(); |
357 BigInteger x = dr.readInt(); | 368 RSAPrivateKeySpec privSpec = new RSAPrivateCrtKeySpec(n, e, d, primeP, primeQ, expP, expQ, coeff); |
358 | 369 RSAPublicKeySpec pubSpec = new RSAPublicKeySpec(n, e); |
359 if (dr.available() != 0) | 370 return generateKeyPair("RSA", privSpec, pubSpec); |
360 throw new IOException("Padding in DSA PRIVATE KEY DER stream."); | 371 } |
361 | 372 |
362 return new DSAPrivateKey(p, q, g, y, x); | 373 if (ps.pemType == PEM_EC_PRIVATE_KEY) { |
363 } | 374 SimpleDERReader dr = new SimpleDERReader(ps.data); |
364 | 375 byte[] seq = dr.readSequenceAsByteArray(); |
365 if (ps.pemType == PEM_RSA_PRIVATE_KEY) | 376 |
366 { | 377 if (dr.available() != 0) |
367 SimpleDERReader dr = new SimpleDERReader(ps.data); | 378 throw new IOException("Padding in EC PRIVATE KEY DER stream."); |
368 | 379 |
369 byte[] seq = dr.readSequenceAsByteArray(); | 380 dr.resetInput(seq); |
370 | 381 BigInteger version = dr.readInt(); |
371 if (dr.available() != 0) | 382 |
372 throw new IOException("Padding in RSA PRIVATE KEY DER stream."); | 383 if ((version.compareTo(BigInteger.ONE) != 0)) |
373 | 384 throw new IOException("Wrong version (" + version + ") in EC PRIVATE KEY DER stream."); |
374 dr.resetInput(seq); | 385 |
375 | 386 byte[] privateBytes = dr.readOctetString(); |
376 BigInteger version = dr.readInt(); | 387 String curveOid = null; |
377 | 388 byte[] publicBytes = null; |
378 if ((version.compareTo(BigInteger.ZERO) != 0) && (version.compareTo(BigInteger.ONE) != 0)) | 389 |
379 throw new IOException("Wrong version (" + version + ") in RSA PRIVATE KEY DER stream."); | 390 while (dr.available() > 0) { |
380 | 391 int type = dr.readConstructedType(); |
381 BigInteger n = dr.readInt(); | 392 SimpleDERReader cr = dr.readConstructed(); |
382 BigInteger e = dr.readInt(); | 393 |
383 BigInteger d = dr.readInt(); | 394 switch (type) { |
384 | 395 case 0: |
385 return new RSAPrivateKey(d, e, n); | 396 curveOid = cr.readOid(); |
386 } | 397 break; |
387 | 398 |
388 throw new IOException("PEM problem: it is of unknown type"); | 399 case 1: |
389 } | 400 publicBytes = cr.readOctetString(); |
390 | 401 break; |
402 } | |
403 } | |
404 | |
405 ECParameterSpec params = ECDSASHA2Verify.getCurveForOID(curveOid); | |
406 | |
407 if (params == null) | |
408 throw new IOException("invalid OID"); | |
409 | |
410 BigInteger s = new BigInteger(privateBytes); | |
411 byte[] publicBytesSlice = new byte[publicBytes.length - 1]; | |
412 System.arraycopy(publicBytes, 1, publicBytesSlice, 0, publicBytesSlice.length); | |
413 ECPoint w = ECDSASHA2Verify.decodeECPoint(publicBytesSlice, params.getCurve()); | |
414 ECPrivateKeySpec privSpec = new ECPrivateKeySpec(s, params); | |
415 ECPublicKeySpec pubSpec = new ECPublicKeySpec(w, params); | |
416 return generateKeyPair("EC", privSpec, pubSpec); | |
417 } | |
418 | |
419 throw new IOException("PEM problem: it is of unknown type"); | |
420 } | |
421 | |
422 /** | |
423 * Generate a {@code KeyPair} given an {@code algorithm} and {@code KeySpec}. | |
424 */ | |
425 private static KeyPair generateKeyPair(String algorithm, KeySpec privSpec, KeySpec pubSpec) | |
426 throws IOException { | |
427 try { | |
428 final KeyFactory kf = KeyFactory.getInstance(algorithm); | |
429 final PublicKey pubKey = kf.generatePublic(pubSpec); | |
430 final PrivateKey privKey = kf.generatePrivate(privSpec); | |
431 return new KeyPair(pubKey, privKey); | |
432 } | |
433 catch (NoSuchAlgorithmException ex) { | |
434 IOException ioex = new IOException(); | |
435 ioex.initCause(ex); | |
436 throw ioex; | |
437 } | |
438 catch (InvalidKeySpecException ex) { | |
439 IOException ioex = new IOException("invalid keyspec"); | |
440 ioex.initCause(ex); | |
441 throw ioex; | |
442 } | |
443 } | |
391 } | 444 } |