1 2 package ch.ethz.ssh2.crypto; 3 4 import java.io.BufferedReader ; 5 import java.io.CharArrayReader ; 6 import java.io.IOException ; 7 import java.math.BigInteger ; 8 9 import ch.ethz.ssh2.crypto.cipher.AES; 10 import ch.ethz.ssh2.crypto.cipher.BlockCipher; 11 import ch.ethz.ssh2.crypto.cipher.CBCMode; 12 import ch.ethz.ssh2.crypto.cipher.DES; 13 import ch.ethz.ssh2.crypto.cipher.DESede; 14 import ch.ethz.ssh2.crypto.digest.MD5; 15 import ch.ethz.ssh2.signature.DSAPrivateKey; 16 import ch.ethz.ssh2.signature.RSAPrivateKey; 17 18 24 public class PEMDecoder 25 { 26 private static final int PEM_RSA_PRIVATE_KEY = 1; 27 private static final int PEM_DSA_PRIVATE_KEY = 2; 28 29 private static final int hexToInt(char c) 30 { 31 if ((c >= 'a') && (c <= 'f')) 32 { 33 return (c - 'a') + 10; 34 } 35 36 if ((c >= 'A') && (c <= 'F')) 37 { 38 return (c - 'A') + 10; 39 } 40 41 if ((c >= '0') && (c <= '9')) 42 { 43 return (c - '0'); 44 } 45 46 throw new IllegalArgumentException ("Need hex char"); 47 } 48 49 private static byte[] hexToByteArray(String hex) 50 { 51 if (hex == null) 52 throw new IllegalArgumentException ("null argument"); 53 54 if ((hex.length() % 2) != 0) 55 throw new IllegalArgumentException ("Uneven string length in hex encoding."); 56 57 byte decoded[] = new byte[hex.length() / 2]; 58 59 for (int i = 0; i < decoded.length; i++) 60 { 61 int hi = hexToInt(hex.charAt(i * 2)); 62 int lo = hexToInt(hex.charAt((i * 2) + 1)); 63 64 decoded[i] = (byte) (hi * 16 + lo); 65 } 66 67 return decoded; 68 } 69 70 private static byte[] generateKeyFromPasswordSaltWithMD5(byte[] password, byte[] salt, int keyLen) 71 throws IOException  72 { 73 if (salt.length < 8) 74 throw new IllegalArgumentException ("Salt needs to be at least 8 bytes for key generation."); 75 76 MD5 md5 = new MD5(); 77 78 byte[] key = new byte[keyLen]; 79 byte[] tmp = new byte[md5.getDigestLength()]; 80 81 while (true) 82 { 83 md5.update(password, 0, password.length); 84 md5.update(salt, 0, 8); 87 int copy = (keyLen < tmp.length) ? keyLen : tmp.length; 88 89 md5.digest(tmp, 0); 90 91 System.arraycopy(tmp, 0, key, key.length - keyLen, copy); 92 93 keyLen -= copy; 94 95 if (keyLen == 0) 96 return key; 97 98 md5.update(tmp, 0, tmp.length); 99 } 100 } 101 102 private static byte[] removePadding(byte[] buff, int blockSize) throws IOException  103 { 104 105 106 int rfc_1423_padding = buff[buff.length - 1] & 0xff; 107 108 if ((rfc_1423_padding < 1) || (rfc_1423_padding > blockSize)) 109 throw new IOException ("Decrypted PEM has wrong padding, did you specify the correct password?"); 110 111 for (int i = 2; i <= rfc_1423_padding; i++) 112 { 113 if (buff[buff.length - i] != rfc_1423_padding) 114 throw new IOException ("Decrypted PEM has wrong padding, did you specify the correct password?"); 115 } 116 117 byte[] tmp = new byte[buff.length - rfc_1423_padding]; 118 System.arraycopy(buff, 0, tmp, 0, buff.length - rfc_1423_padding); 119 return tmp; 120 } 121 122 private static final PEMStructure parsePEM(char[] pem) throws IOException  123 { 124 PEMStructure ps = new PEMStructure(); 125 126 String line = null; 127 128 BufferedReader br = new BufferedReader (new CharArrayReader (pem)); 129 130 String endLine = null; 131 132 while (true) 133 { 134 line = br.readLine(); 135 136 if (line == null) 137 throw new IOException ("Invalid PEM structure, '-----BEGIN...' missing"); 138 139 line = line.trim(); 140 141 if (line.startsWith("-----BEGIN DSA PRIVATE KEY-----")) 142 { 143 endLine = "-----END DSA PRIVATE KEY-----"; 144 ps.pemType = PEM_DSA_PRIVATE_KEY; 145 break; 146 } 147 148 if (line.startsWith("-----BEGIN RSA PRIVATE KEY-----")) 149 { 150 endLine = "-----END RSA PRIVATE KEY-----"; 151 ps.pemType = PEM_RSA_PRIVATE_KEY; 152 break; 153 } 154 } 155 156 while (true) 157 { 158 line = br.readLine(); 159 160 if (line == null) 161 throw new IOException ("Invalid PEM structure, " + endLine + " missing"); 162 163 line = line.trim(); 164 165 int sem_idx = line.indexOf(':'); 166 167 if (sem_idx == -1) 168 break; 169 170 String name = line.substring(0, sem_idx + 1); 171 String value = line.substring(sem_idx + 1); 172 173 String values[] = value.split(","); 174 175 for (int i = 0; i < values.length; i++) 176 values[i] = values[i].trim(); 177 178 181 if ("Proc-Type:".equals(name)) 182 { 183 ps.procType = values; 184 continue; 185 } 186 187 if ("DEK-Info:".equals(name)) 188 { 189 ps.dekInfo = values; 190 continue; 191 } 192 193 } 194 195 StringBuffer keyData = new StringBuffer (); 196 197 while (true) 198 { 199 if (line == null) 200 throw new IOException ("Invalid PEM structure, " + endLine + " missing"); 201 202 line = line.trim(); 203 204 if (line.startsWith(endLine)) 205 break; 206 207 keyData.append(line); 208 209 line = br.readLine(); 210 } 211 212 char[] pem_chars = new char[keyData.length()]; 213 keyData.getChars(0, pem_chars.length, pem_chars, 0); 214 215 ps.data = Base64.decode(pem_chars); 216 217 if (ps.data.length == 0) 218 throw new IOException ("Invalid PEM structure, no data available"); 219 220 return ps; 221 } 222 223 private static final void decryptPEM(PEMStructure ps, byte[] pw) throws IOException  224 { 225 if (ps.dekInfo == null) 226 throw new IOException ("Broken PEM, no mode and salt given, but encryption enabled"); 227 228 if (ps.dekInfo.length != 2) 229 throw new IOException ("Broken PEM, DEK-Info is incomplete!"); 230 231 String algo = ps.dekInfo[0]; 232 byte[] salt = hexToByteArray(ps.dekInfo[1]); 233 234 BlockCipher bc = null; 235 236 if (algo.equals("DES-EDE3-CBC")) 237 { 238 DESede des3 = new DESede(); 239 des3.init(false, generateKeyFromPasswordSaltWithMD5(pw, salt, 24)); 240 bc = new CBCMode(des3, salt, false); 241 } 242 else if (algo.equals("DES-CBC")) 243 { 244 DES des = new DES(); 245 des.init(false, generateKeyFromPasswordSaltWithMD5(pw, salt, 8)); 246 bc = new CBCMode(des, salt, false); 247 } 248 else if (algo.equals("AES-128-CBC")) 249 { 250 AES aes = new AES(); 251 aes.init(false, generateKeyFromPasswordSaltWithMD5(pw, salt, 16)); 252 bc = new CBCMode(aes, salt, false); 253 } 254 else if (algo.equals("AES-192-CBC")) 255 { 256 AES aes = new AES(); 257 aes.init(false, generateKeyFromPasswordSaltWithMD5(pw, salt, 24)); 258 bc = new CBCMode(aes, salt, false); 259 } 260 else if (algo.equals("AES-256-CBC")) 261 { 262 AES aes = new AES(); 263 aes.init(false, generateKeyFromPasswordSaltWithMD5(pw, salt, 32)); 264 bc = new CBCMode(aes, salt, false); 265 } 266 else 267 { 268 throw new IOException ("Cannot decrypt PEM structure, unknown cipher " + algo); 269 } 270 271 if ((ps.data.length % bc.getBlockSize()) != 0) 272 throw new IOException ("Invalid PEM structure, size of encrypted block is not a multiple of " 273 + bc.getBlockSize()); 274 275 276 277 byte[] dz = new byte[ps.data.length]; 278 279 for (int i = 0; i < ps.data.length / bc.getBlockSize(); i++) 280 { 281 bc.transformBlock(ps.data, i * bc.getBlockSize(), dz, i * bc.getBlockSize()); 282 } 283 284 285 286 dz = removePadding(dz, bc.getBlockSize()); 287 288 ps.data = dz; 289 ps.dekInfo = null; 290 ps.procType = null; 291 } 292 293 public static final boolean isPEMEncrypted(PEMStructure ps) throws IOException  294 { 295 if (ps.procType == null) 296 return false; 297 298 if (ps.procType.length != 2) 299 throw new IOException ("Unknown Proc-Type field."); 300 301 if ("4".equals(ps.procType[0]) == false) 302 throw new IOException ("Unknown Proc-Type field (" + ps.procType[0] + ")"); 303 304 if ("ENCRYPTED".equals(ps.procType[1])) 305 return true; 306 307 return false; 308 } 309 310 public static Object decode(char[] pem, String password) throws IOException  311 { 312 PEMStructure ps = parsePEM(pem); 313 314 if (isPEMEncrypted(ps)) 315 { 316 if (password == null) 317 throw new IOException ("PEM is encrypted, but no password was specified"); 318 319 decryptPEM(ps, password.getBytes()); 320 } 321 322 if (ps.pemType == PEM_DSA_PRIVATE_KEY) 323 { 324 SimpleDERReader dr = new SimpleDERReader(ps.data); 325 326 byte[] seq = dr.readSequenceAsByteArray(); 327 328 if (dr.available() != 0) 329 throw new IOException ("Padding in DSA PRIVATE KEY DER stream."); 330 331 dr.resetInput(seq); 332 333 BigInteger version = dr.readInt(); 334 335 if (version.compareTo(BigInteger.ZERO) != 0) 336 throw new IOException ("Wrong version (" + version + ") in DSA PRIVATE KEY DER stream."); 337 338 BigInteger p = dr.readInt(); 339 BigInteger q = dr.readInt(); 340 BigInteger g = dr.readInt(); 341 BigInteger y = dr.readInt(); 342 BigInteger x = dr.readInt(); 343 344 if (dr.available() != 0) 345 throw new IOException ("Padding in DSA PRIVATE KEY DER stream."); 346 347 return new DSAPrivateKey(p, q, g, y, x); 348 } 349 350 if (ps.pemType == PEM_RSA_PRIVATE_KEY) 351 { 352 SimpleDERReader dr = new SimpleDERReader(ps.data); 353 354 byte[] seq = dr.readSequenceAsByteArray(); 355 356 if (dr.available() != 0) 357 throw new IOException ("Padding in RSA PRIVATE KEY DER stream."); 358 359 dr.resetInput(seq); 360 361 BigInteger version = dr.readInt(); 362 363 if ((version.compareTo(BigInteger.ZERO) != 0) && (version.compareTo(BigInteger.ONE) != 0)) 364 throw new IOException ("Wrong version (" + version + ") in RSA PRIVATE KEY DER stream."); 365 366 BigInteger n = dr.readInt(); 367 BigInteger e = dr.readInt(); 368 BigInteger d = dr.readInt(); 369 370 return new RSAPrivateKey(d, e, n); 371 } 372 373 throw new IOException ("PEM problem: it is of unknown type"); 374 } 375 376 } 377 | Popular Tags |