From 47ba1033e11d5548f13179d9abee2b2a8a2b54d8 Mon Sep 17 00:00:00 2001 From: Kenny Root Date: Fri, 12 Apr 2013 23:13:55 -0700 Subject: Partial support for importing EC keys --- .../java/com/trilead/ssh2/crypto/PEMDecoder.java | 81 +++++++++++++++++++++- .../com/trilead/ssh2/crypto/SimpleDERReader.java | 75 +++++++++++++++++++- .../trilead/ssh2/signature/ECDSASHA2Verify.java | 17 +++++ 3 files changed, 167 insertions(+), 6 deletions(-) diff --git a/lib/src/main/java/com/trilead/ssh2/crypto/PEMDecoder.java b/lib/src/main/java/com/trilead/ssh2/crypto/PEMDecoder.java index bb14464..430307e 100644 --- a/lib/src/main/java/com/trilead/ssh2/crypto/PEMDecoder.java +++ b/lib/src/main/java/com/trilead/ssh2/crypto/PEMDecoder.java @@ -14,6 +14,10 @@ import java.security.PrivateKey; import java.security.PublicKey; import java.security.spec.DSAPrivateKeySpec; import java.security.spec.DSAPublicKeySpec; +import java.security.spec.ECParameterSpec; +import java.security.spec.ECPoint; +import java.security.spec.ECPrivateKeySpec; +import java.security.spec.ECPublicKeySpec; import java.security.spec.InvalidKeySpecException; import java.security.spec.RSAPrivateCrtKeySpec; import java.security.spec.RSAPrivateKeySpec; @@ -24,6 +28,7 @@ import com.trilead.ssh2.crypto.cipher.BlockCipher; import com.trilead.ssh2.crypto.cipher.CBCMode; import com.trilead.ssh2.crypto.cipher.DES; import com.trilead.ssh2.crypto.cipher.DESede; +import com.trilead.ssh2.signature.ECDSASHA2Verify; /** * PEM Support. @@ -35,6 +40,7 @@ public class PEMDecoder { public static final int PEM_RSA_PRIVATE_KEY = 1; public static final int PEM_DSA_PRIVATE_KEY = 2; + public static final int PEM_EC_PRIVATE_KEY = 3; private static final int hexToInt(char c) { @@ -173,6 +179,12 @@ public class PEMDecoder ps.pemType = PEM_RSA_PRIVATE_KEY; break; } + + if (line.startsWith("-----BEGIN EC PRIVATE KEY-----")) { + endLine = "-----END EC PRIVATE KEY-----"; + ps.pemType = PEM_EC_PRIVATE_KEY; + break; + } } while (true) @@ -420,9 +432,72 @@ public class PEMDecoder PublicKey pubKey; PrivateKey privKey; try { - KeyFactory kf = KeyFactory.getInstance("RSA"); - pubKey = kf.generatePublic(pubSpec); - privKey = kf.generatePrivate(privSpec); + KeyFactory kf = KeyFactory.getInstance("RSA"); + pubKey = kf.generatePublic(pubSpec); + privKey = kf.generatePrivate(privSpec); + } catch (NoSuchAlgorithmException ex) { + IOException ioex = new IOException(); + ioex.initCause(ex); + throw ioex; + } catch (InvalidKeySpecException ex) { + IOException ioex = new IOException("invalid keyspec"); + ioex.initCause(ex); + throw ioex; + } + + return new KeyPair(pubKey, privKey); + } + + if (ps.pemType == PEM_EC_PRIVATE_KEY) { + SimpleDERReader dr = new SimpleDERReader(ps.data); + + byte[] seq = dr.readSequenceAsByteArray(); + + if (dr.available() != 0) + throw new IOException("Padding in EC PRIVATE KEY DER stream."); + + dr.resetInput(seq); + + BigInteger version = dr.readInt(); + + if ((version.compareTo(BigInteger.ONE) != 0)) + throw new IOException("Wrong version (" + version + ") in EC PRIVATE KEY DER stream."); + + byte[] privateBytes = dr.readOctetString(); + + String curveOid = null; + byte[] publicBytes = null; + while (dr.available() > 0) { + int type = dr.readConstructedType(); + SimpleDERReader cr = dr.readConstructed(); + switch (type) { + case 0: + curveOid = cr.readOid(); + break; + case 1: + publicBytes = cr.readOctetString(); + break; + } + } + + ECParameterSpec params = ECDSASHA2Verify.getCurveForOID(curveOid); + if (params == null) + throw new IOException("invalid OID"); + + BigInteger s = new BigInteger(privateBytes); + byte[] publicBytesSlice = new byte[publicBytes.length - 1]; + System.arraycopy(publicBytes, 1, publicBytesSlice, 0, publicBytesSlice.length); + ECPoint w = ECDSASHA2Verify.decodeECPoint(publicBytesSlice, params.getCurve()); + + ECPrivateKeySpec privSpec = new ECPrivateKeySpec(s, params); + ECPublicKeySpec pubSpec = new ECPublicKeySpec(w, params); + + PublicKey pubKey; + PrivateKey privKey; + try { + KeyFactory kf = KeyFactory.getInstance("EC"); + pubKey = kf.generatePublic(pubSpec); + privKey = kf.generatePrivate(privSpec); } catch (NoSuchAlgorithmException ex) { IOException ioex = new IOException(); ioex.initCause(ex); diff --git a/lib/src/main/java/com/trilead/ssh2/crypto/SimpleDERReader.java b/lib/src/main/java/com/trilead/ssh2/crypto/SimpleDERReader.java index 9f626ad..ff8112a 100644 --- a/lib/src/main/java/com/trilead/ssh2/crypto/SimpleDERReader.java +++ b/lib/src/main/java/com/trilead/ssh2/crypto/SimpleDERReader.java @@ -12,6 +12,8 @@ import java.math.BigInteger; */ public class SimpleDERReader { + private static final int CONSTRUCTED = 0x20; + byte[] buffer; int pos; int count; @@ -123,6 +125,30 @@ public class SimpleDERReader return bi; } + public int readConstructedType() throws IOException { + int type = readByte() & 0xff; + + if ((type & CONSTRUCTED) != CONSTRUCTED) + throw new IOException("Expected constructed type, but was " + type); + + return type & 0x1f; + } + + public SimpleDERReader readConstructed() throws IOException + { + int len = readLength(); + + if ((len < 0) || len > available()) + throw new IOException("Illegal len in DER object (" + len + ")"); + + SimpleDERReader cr = new SimpleDERReader(buffer, pos, len); + + pos += len; + count -= len; + + return cr; + } + public byte[] readSequenceAsByteArray() throws IOException { int type = readByte() & 0xff; @@ -139,12 +165,55 @@ public class SimpleDERReader return b; } - + + public String readOid() throws IOException + { + int type = readByte() & 0xff; + + if (type != 0x06) + throw new IOException("Expected DER OID, but found type " + type); + + int len = readLength(); + + if ((len < 1) || len > available()) + throw new IOException("Illegal len in DER object (" + len + ")"); + + byte[] b = readBytes(len); + + long value = 0; + + StringBuilder sb = new StringBuilder(64); + switch(b[0] / 40) { + case 0: + sb.append('0'); + break; + case 1: + sb.append('1'); + b[0] -= 40; + break; + default: + sb.append('2'); + b[0] -= 80; + break; + } + + for (int i = 0; i < len; i++) { + value = (value << 7) + (b[i] & 0x7F); + if ((b[i] & 0x80) == 0) { + sb.append('.'); + sb.append(value); + value = 0; + } + } + + return sb.toString(); + } + public byte[] readOctetString() throws IOException { int type = readByte() & 0xff; - - if (type != 0x04) + + if (type != 0x04 && type != 0x03) throw new IOException("Expected DER Octetstring, but found type " + type); int len = readLength(); diff --git a/lib/src/main/java/com/trilead/ssh2/signature/ECDSASHA2Verify.java b/lib/src/main/java/com/trilead/ssh2/signature/ECDSASHA2Verify.java index 7b4f6af..49028e6 100644 --- a/lib/src/main/java/com/trilead/ssh2/signature/ECDSASHA2Verify.java +++ b/lib/src/main/java/com/trilead/ssh2/signature/ECDSASHA2Verify.java @@ -38,8 +38,11 @@ public class ECDSASHA2Verify { public static final String ECDSA_SHA2_PREFIX = "ecdsa-sha2-"; private static final String NISTP256 = "nistp256"; + private static final String NISTP256_OID = "1.2.840.10045.3.1.7"; private static final String NISTP384 = "nistp384"; + private static final String NISTP384_OID = "1.3.132.0.34"; private static final String NISTP521 = "nistp521"; + private static final String NISTP521_OID = "1.3.132.0.35"; private static final Map CURVES = new TreeMap(); static { @@ -55,6 +58,13 @@ public class ECDSASHA2Verify { CURVE_SIZES.put(521, NISTP521); } + private static final Map CURVE_OIDS = new TreeMap(); + static { + CURVE_OIDS.put(NISTP256_OID, NISTP256); + CURVE_OIDS.put(NISTP384_OID, NISTP256); + CURVE_OIDS.put(NISTP521_OID, NISTP256); + } + public static int[] getCurveSizes() { int[] keys = new int[CURVE_SIZES.size()]; int i = 0; @@ -156,6 +166,13 @@ public class ECDSASHA2Verify { return params.getCurve().getField().getFieldSize(); } + public static ECParameterSpec getCurveForOID(String oid) { + String name = CURVE_OIDS.get(oid); + if (name == null) + return null; + return CURVES.get(name); + } + public static byte[] decodeSSHECDSASignature(byte[] sig) throws IOException { byte[] rsArray = null; -- cgit v1.2.3