package ch.ethz.ssh2.crypto;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import net.rim.device.api.crypto.AESKey;
import net.rim.device.api.crypto.BlockDecryptorEngine;
import net.rim.device.api.crypto.CBCDecryptorEngine;
import net.rim.device.api.crypto.CryptoException;
import net.rim.device.api.crypto.CryptoInteger;
import net.rim.device.api.crypto.CryptoTokenException;
import net.rim.device.api.crypto.CryptoUnsupportedOperationException;
import net.rim.device.api.crypto.DESKey;
import net.rim.device.api.crypto.DSACryptoSystem;
import net.rim.device.api.crypto.DSAKeyPair;
import net.rim.device.api.crypto.DSAPrivateKey;
import net.rim.device.api.crypto.DSAPublicKey;
import net.rim.device.api.crypto.DecryptorFactory;
import net.rim.device.api.crypto.InitializationVector;
import net.rim.device.api.crypto.InvalidKeyException;
import net.rim.device.api.crypto.KeyPair;
import net.rim.device.api.crypto.MD5Digest;
import net.rim.device.api.crypto.NoSuchAlgorithmException;
import net.rim.device.api.crypto.RSACryptoSystem;
import net.rim.device.api.crypto.RSAKeyPair;
import net.rim.device.api.crypto.RSAPrivateKey;
import net.rim.device.api.crypto.RSAPublicKey;
import net.rim.device.api.crypto.SymmetricKey;
import net.rim.device.api.crypto.TripleDESKey;
import net.rim.device.api.io.Base64InputStream;
import net.rim.device.api.io.LineReader;
import org.bbssh.util.Tools;
/**
* PEM Support.
*
* @author Christian Plattner, plattner@inf.ethz.ch
* @version $Id: PEMDecoder.java,v 1.7 2006/02/02 09:11:03 cplattne Exp $
*/
public class PEMDecoder {
private static final int PEM_RSA_PRIVATE_KEY = 1;
private static final int PEM_DSA_PRIVATE_KEY = 2;
private static byte[] generateKeyFromPasswordSaltWithMD5(byte[] password, byte[] salt,
int keyLen)
throws IOException {
if (salt.length < 8) {
throw new IllegalArgumentException(
"Salt needs to be at least 8 bytes for key generation.");
}
MD5Digest md5 = new MD5Digest();
byte[] key = new byte[keyLen];
byte[] tmp = new byte[md5.getDigestLength()];
while (true) {
md5.update(password, 0, password.length);
md5.update(salt, 0, 8); // ARGH we only use the first 8 bytes of the salt in this step.
// This took me two hours until I got AES-xxx running.
int copy = (keyLen < tmp.length) ? keyLen : tmp.length;
tmp = md5.getDigest();
System.arraycopy(tmp, 0, key, key.length - keyLen, copy);
keyLen -= copy;
if (keyLen == 0) {
return key;
}
md5.update(tmp, 0, tmp.length);
}
}
private static byte[] removePadding(byte[] buff, int blockSize) throws IOException {
/* Removes RFC 1423/PKCS #7 padding */
int rfc_1423_padding = buff[buff.length - 1] & 0xff;
if ((rfc_1423_padding < 1) || (rfc_1423_padding > blockSize)) {
throw new IOException(
"Decrypted PEM has wrong padding, did you specify the correct password?");
}
for (int i = 2; i <= rfc_1423_padding; i++) {
if (buff[buff.length - i] != rfc_1423_padding) {
throw new IOException(
"Decrypted PEM has wrong padding, did you specify the correct password?");
}
}
byte[] tmp = new byte[buff.length - rfc_1423_padding];
System.arraycopy(buff, 0, tmp, 0, buff.length - rfc_1423_padding);
return tmp;
}
public static final PEMStructure parsePEM(byte[] pem) throws IOException {
PEMStructure ps = new PEMStructure();
String line = null;
LineReader reader = new LineReader(new ByteArrayInputStream(pem));
String endLine = null;
while (true) {
line = new String(reader.readLine());
// if (line.length() == 0) {
// throw new IOException("Invalid PEM structure, '-----BEGIN...' missing");
// }
line = line.trim();
if (line.startsWith("-----BEGIN DSA PRIVATE KEY-----")) {
endLine = "-----END DSA PRIVATE KEY-----";
ps.pemType = PEM_DSA_PRIVATE_KEY;
break;
}
if (line.startsWith("-----BEGIN RSA PRIVATE KEY-----")) {
endLine = "-----END RSA PRIVATE KEY-----";
ps.pemType = PEM_RSA_PRIVATE_KEY;
break;
}
}
while (true) {
line = new String(reader.readLine());
// if (line == null) {
// throw new IOException("Invalid PEM structure, " + endLine + " missing");
// }
line = line.trim();
int sem_idx = line.indexOf(':');
if (sem_idx == -1) {
break;
}
String name = line.substring(0, sem_idx + 1);
String value = line.substring(sem_idx + 1);
String values[] = Tools.splitString(value, ',');
for (int i = 0; i < values.length; i++) {
values[i] = values[i].trim();
}
// Proc-Type: 4,ENCRYPTED
// DEK-Info: DES-EDE3-CBC,579B6BE3E5C60483
if ("Proc-Type:".equals(name)) {
ps.procType = values;
continue;
}
if ("DEK-Info:".equals(name)) {
ps.dekInfo = values;
continue;
}
/* Ignore line */
}
StringBuffer keyData = new StringBuffer();
while (true) {
if (line == null) {
throw new IOException("Invalid PEM structure, " + endLine + " missing");
}
line = line.trim();
if (line.startsWith(endLine)) {
break;
}
keyData.append(line);
line = new String(reader.readLine());
}
char[] pem_chars = new char[keyData.length()];
keyData.getChars(0, pem_chars.length, pem_chars, 0);
ps.data = Base64InputStream.decode(keyData.toString()); // pem_chars);
if (ps.data.length == 0) {
throw new IOException("Invalid PEM structure, no data available");
}
return ps;
}
public static final void decryptPEM(PEMStructure ps, byte[] pw) throws
IOException, CryptoTokenException, CryptoUnsupportedOperationException {
try {
if (!isPEMEncrypted(ps)) {
return;
}
if (pw == null || pw.length == 0) {
return;
}
if (ps.dekInfo == null) {
throw new IOException("Broken PEM, no mode and salt given, but encryption enabled");
}
if (ps.dekInfo.length != 2) {
throw new IOException("Broken PEM, DEK-Info is incomplete!");
}
BlockDecryptorEngine bc;
BlockDecryptorEngine bcmain;
String algo = ps.dekInfo[0];
byte[] salt = Tools.getHexStringAsBytes(ps.dekInfo[1]);
SymmetricKey key;
// NOte that the key is generated here, based on password + salt (as provided in the key file)
if (algo.equals("DES-EDE3-CBC")) {
key = new TripleDESKey(generateKeyFromPasswordSaltWithMD5(pw, salt, 24));
} else if (algo.equals("DES-CBC")) {
key = new DESKey(generateKeyFromPasswordSaltWithMD5(pw, salt, 8));
} else if (algo.equals("AES-128-CBC")) {
key = new AESKey(generateKeyFromPasswordSaltWithMD5(pw, salt, 16));
} else if (algo.equals("AES-192-CBC")) {
key = new AESKey(generateKeyFromPasswordSaltWithMD5(pw, salt, 24));
} else if (algo.equals("AES-256-CBC")) {
key = new AESKey(generateKeyFromPasswordSaltWithMD5(pw, salt, 32));
} else {
throw new IOException("Cannot decrypt PEM structure, unknown cipher " + algo);
}
bcmain = DecryptorFactory.getBlockDecryptorEngine(key);
bc =
new CBCDecryptorEngine(bcmain,
new InitializationVector(salt));
int blockSize = bc.getBlockLength();
if ((ps.data.length % blockSize) != 0) {
throw new IOException("Invalid PEM structure, size of encrypted block is not a multiple of " + bc.
getBlockLength());
}
byte[] dz = new byte[ps.data.length];
for (int i = 0; i < ps.data.length / blockSize; i++) {
bc.decrypt(ps.data, i * blockSize, dz, i * blockSize);
}
/* Now check and remove RFC 1423/PKCS #7 padding */
dz = removePadding(dz, blockSize);
ps.data = dz;
ps.dekInfo = null;
ps.procType = null;
} catch (NoSuchAlgorithmException ex) {
throw new IOException(ex.getMessage());
} catch (CryptoException ex) {
throw new IOException(ex.getMessage());
}
}
public static final boolean isPEMEncrypted(PEMStructure ps) throws IOException {
if (ps.procType == null) {
return false;
}
if (ps.procType.length != 2) {
throw new IOException("Unknown Proc-Type field.");
}
if ("4".equals(ps.procType[0]) == false) {
throw new IOException("Unknown Proc-Type field (" + ps.procType[0] + ")");
}
if ("ENCRYPTED".equals(ps.procType[1])) {
return true;
}
return false;
}
public static RSAKeyPair decodeRSA(SimpleDERReader dr) throws IOException, InvalidKeyException {
CryptoInteger version = dr.readCryptoInt();
if (version.compareTo(0) != 0 && version.compareTo(1) != 0) {
throw new IOException("Wrong version (" + version.toString() + ") in RSA PRIVATE KEY DER stream.");
}
CryptoInteger n = dr.readCryptoInt();
CryptoInteger e = dr.readCryptoInt();
CryptoInteger d = dr.readCryptoInt();
try {
byte[] nb = n.toByteArray();
RSACryptoSystem cs = new RSACryptoSystem(nb.length * 8);
RSAPrivateKey prk = new RSAPrivateKey(cs, e.toByteArray(), d.toByteArray(), nb);
RSAPublicKey pk = new RSAPublicKey(cs, e.toByteArray(), n.toByteArray());
return new RSAKeyPair(pk, prk);
} catch (InvalidKeyException ex) {
throw ex;
} catch (Throwable ex) {
throw new IOException("Unable to create key-pair"); // " + ex.toString()); ;
}
}
public static DSAKeyPair decodeDSA(SimpleDERReader dr) throws IOException, InvalidKeyException {
CryptoInteger version = dr.readCryptoInt();
if (version.compareTo(0) != 0) {
throw new IOException("Wrong version (" + version + ") in DSA PRIVATE KEY DER stream.");
}
CryptoInteger p = dr.readCryptoInt();
CryptoInteger q = dr.readCryptoInt();
CryptoInteger g = dr.readCryptoInt();
CryptoInteger y = dr.readCryptoInt();
CryptoInteger x = dr.readCryptoInt();
if (dr.available() != 0) {
throw new IOException("Padding in DSA PRIVATE KEY DER stream.");
}
DSACryptoSystem cs;
try {
cs = new DSACryptoSystem(p.toByteArray(), q.toByteArray(), g.toByteArray());
byte[] privk = x.toByteArray();
byte[] pubk = y.toByteArray();
DSAPublicKey pubKey = new DSAPublicKey(cs, pubk);
DSAPrivateKey privKey = new DSAPrivateKey(cs, privk);
return new DSAKeyPair(pubKey, privKey);
} catch (InvalidKeyException ex) {
throw ex;
} catch (Throwable e) {
throw new IOException("Unable to create key-pair: " + e.toString());
}
}
public static KeyPair decode(byte[] pem, String password) throws IOException, InvalidKeyException {
// @todo this must b e converted to parse into BB Crypto version of RSAPrivateKey.
PEMStructure ps = parsePEM(pem);
if (isPEMEncrypted(ps)) {
try {
if (password == null || password.length() == 0) {
throw new IOException("PEM is encrypted, but no password was specified");
}
decryptPEM(ps, password.getBytes());
} catch (CryptoTokenException ex) {
throw new IOException(ex.getMessage());
} catch (CryptoUnsupportedOperationException ex) {
throw new IOException(ex.getMessage());
}
}
SimpleDERReader dr = new SimpleDERReader(ps.data);
byte[] seq = dr.readSequenceAsByteArray();
if (dr.available() != 0) {
throw new IOException("Padding in PRIVATE KEY DER stream.");
}
dr.resetInput(seq);
if (ps.pemType == PEM_DSA_PRIVATE_KEY) {
return decodeDSA(dr);
}
if (ps.pemType == PEM_RSA_PRIVATE_KEY) {
return decodeRSA(dr);
}
throw new IOException("Unrecognized key type.");
}
}