diff --git a/core/src/main/java/org/jclouds/crypto/CryptoStreams.java b/core/src/main/java/org/jclouds/crypto/CryptoStreams.java index 5760cd6fef..4130857725 100644 --- a/core/src/main/java/org/jclouds/crypto/CryptoStreams.java +++ b/core/src/main/java/org/jclouds/crypto/CryptoStreams.java @@ -98,15 +98,15 @@ public class CryptoStreams { } /** - * Computes and returns the MAC value for a supplied input stream. The mac object is reset when - * this method returns successfully. + * Computes and returns the MAC value for a supplied input stream. The mac + * object is reset when this method returns successfully. * * @param supplier * the input stream factory * @param mac * the mac object - * @return the result of {@link Mac#doFinal()} after updating the mac object with all of the - * bytes in the stream and encoding in Base64 + * @return the result of {@link Mac#doFinal()} after updating the mac object + * with all of the bytes in the stream and encoding in Base64 * @throws IOException * if an I/O error occurs */ @@ -115,20 +115,20 @@ public class CryptoStreams { } /** - * Computes and returns the Digest value for a supplied input stream. The digest object is reset - * when this method returns successfully. + * Computes and returns the Digest value for a supplied input stream. The + * digest object is reset when this method returns successfully. * * @param supplier * the input stream factory * @param md * the digest object - * @return the result of {@link MessageDigest#digest()} after updating the digest object with all - * of the bytes in the stream + * @return the result of {@link MessageDigest#digest()} after updating the + * digest object with all of the bytes in the stream * @throws IOException * if an I/O error occurs */ public static byte[] digest(InputSupplier supplier, final MessageDigest md) - throws IOException { + throws IOException { return com.google.common.io.ByteStreams.readBytes(supplier, new ByteProcessor() { public boolean processBytes(byte[] buf, int off, int len) { md.update(buf, off, len); @@ -142,14 +142,15 @@ public class CryptoStreams { } /** - * Computes and returns the MD5 value for a supplied input stream. A digest object is created and - * disposed of at runtime, consider using {@link #digest} to be more efficient. + * Computes and returns the MD5 value for a supplied input stream. A digest + * object is created and disposed of at runtime, consider using + * {@link #digest} to be more efficient. * * @param supplier * the input stream factory * - * @return the result of {@link MessageDigest#digest()} after updating the md5 object with all of - * the bytes in the stream + * @return the result of {@link MessageDigest#digest()} after updating the + * md5 object with all of the bytes in the stream * @throws IOException * if an I/O error occurs */ @@ -162,35 +163,40 @@ public class CryptoStreams { } } - public static byte[] md5(byte[] in) throws IOException { - return md5(ByteStreams.newInputStreamSupplier(in)); + public static byte[] md5(byte[] in) { + try { + return md5(ByteStreams.newInputStreamSupplier(in)); + } catch (IOException e) { + Throwables.propagate(e); + return null; + } } /** - * Computes and returns the MAC value for a supplied input stream. The mac object is reset when - * this method returns successfully. + * Computes and returns the MAC value for a supplied input stream. The mac + * object is reset when this method returns successfully. * * @param supplier * the input stream factory * @param mac * the mac object - * @return the result of {@link Mac#doFinal()} after updating the mac object with all of the - * bytes in the stream + * @return the result of {@link Mac#doFinal()} after updating the mac object + * with all of the bytes in the stream * @throws IOException * if an I/O error occurs */ public static byte[] mac(InputSupplier supplier, final Mac mac) throws IOException { return com.google.common.io.ByteStreams.readBytes(checkNotNull(supplier, "supplier"), - new ByteProcessor() { - public boolean processBytes(byte[] buf, int off, int len) { - mac.update(buf, off, len); - return true; - } + new ByteProcessor() { + public boolean processBytes(byte[] buf, int off, int len) { + mac.update(buf, off, len); + return true; + } - public byte[] getResult() { - return mac.doFinal(); - } - }); + public byte[] getResult() { + return mac.doFinal(); + } + }); } /** @@ -206,20 +212,21 @@ public class CryptoStreams { public static String base64Encode(InputSupplier supplier) throws IOException { final ByteArrayOutputStream out = new ByteArrayOutputStream(); return com.google.common.io.ByteStreams.readBytes(InputSuppliers.base64Encoder(supplier), - new ByteProcessor() { - public boolean processBytes(byte[] buf, int off, int len) { - out.write(buf, off, len); - return true; - } + new ByteProcessor() { + public boolean processBytes(byte[] buf, int off, int len) { + out.write(buf, off, len); + return true; + } - public String getResult() { - return new String(out.toByteArray(), Charsets.UTF_8); - } - }); + public String getResult() { + return new String(out.toByteArray(), Charsets.UTF_8); + } + }); } /** - * Computes and returns the unencoded value for an input stream which is encoded in Base64. + * Computes and returns the unencoded value for an input stream which is + * encoded in Base64. * * @param supplier * the input stream factory @@ -231,21 +238,21 @@ public class CryptoStreams { public static byte[] base64Decode(InputSupplier supplier) throws IOException { final ByteArrayOutputStream out = new ByteArrayOutputStream(); return com.google.common.io.ByteStreams.readBytes(InputSuppliers.base64Decoder(supplier), - new ByteProcessor() { - public boolean processBytes(byte[] buf, int off, int len) { - out.write(buf, off, len); - return true; - } + new ByteProcessor() { + public boolean processBytes(byte[] buf, int off, int len) { + out.write(buf, off, len); + return true; + } - public byte[] getResult() { - return out.toByteArray(); - } - }); + public byte[] getResult() { + return out.toByteArray(); + } + }); } final static byte[] HEX_CHAR_TABLE = { (byte) '0', (byte) '1', (byte) '2', (byte) '3', (byte) '4', (byte) '5', - (byte) '6', (byte) '7', (byte) '8', (byte) '9', (byte) 'a', (byte) 'b', (byte) 'c', (byte) 'd', (byte) 'e', - (byte) 'f' }; + (byte) '6', (byte) '7', (byte) '8', (byte) '9', (byte) 'a', (byte) 'b', (byte) 'c', (byte) 'd', (byte) 'e', + (byte) 'f' }; /** * Computes and returns the hex value for a supplied input stream. @@ -281,7 +288,8 @@ public class CryptoStreams { } /** - * Computes and returns the unencoded value for an input stream which is encoded in hex. + * Computes and returns the unencoded value for an input stream which is + * encoded in hex. * * @param supplier * the input stream factory diff --git a/core/src/main/java/org/jclouds/crypto/Pems.java b/core/src/main/java/org/jclouds/crypto/Pems.java index df3c0ec1fa..1ce08bfec8 100644 --- a/core/src/main/java/org/jclouds/crypto/Pems.java +++ b/core/src/main/java/org/jclouds/crypto/Pems.java @@ -166,8 +166,13 @@ public class Pems { * private key in pem encoded format. * @see Pems#privateKeySpec(InputSupplier) */ - public static KeySpec privateKeySpec(String pem) throws IOException { - return privateKeySpec(InputSuppliers.of(pem)); + public static KeySpec privateKeySpec(String pem) { + try { + return privateKeySpec(InputSuppliers.of(pem)); + } catch (IOException e) { + Throwables.propagate(e); + return null; + } } /** @@ -302,6 +307,8 @@ public class Pems { * @throws IOException * @throws CertificateEncodingException */ + // TODO: understand why pem isn't passing SshKeysTest.testCanGenerate where + // keys are checked to match. public static String pem(PrivateKey key) { String marker = key instanceof RSAPrivateCrtKey ? PRIVATE_PKCS1_MARKER : PRIVATE_PKCS8_MARKER; return pem(key instanceof RSAPrivateCrtKey ? getEncoded(RSAPrivateCrtKey.class.cast(key)) : key.getEncoded(), diff --git a/core/src/main/java/org/jclouds/crypto/SshKeys.java b/core/src/main/java/org/jclouds/crypto/SshKeys.java index a33a789f18..ba10c4af71 100644 --- a/core/src/main/java/org/jclouds/crypto/SshKeys.java +++ b/core/src/main/java/org/jclouds/crypto/SshKeys.java @@ -18,23 +18,46 @@ */ package org.jclouds.crypto; +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Throwables.propagate; +import static org.jclouds.crypto.CryptoStreams.base64; +import static org.jclouds.crypto.CryptoStreams.hex; +import static org.jclouds.crypto.CryptoStreams.md5; +import static org.jclouds.crypto.Pems.privateKeySpec; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.StringWriter; import java.math.BigInteger; import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; import java.security.interfaces.RSAPrivateKey; import java.security.interfaces.RSAPublicKey; +import java.security.spec.KeySpec; +import java.security.spec.RSAPrivateCrtKeySpec; +import java.security.spec.RSAPublicKeySpec; import java.util.Map; +import org.bouncycastle.openssl.PEMWriter; +import org.jclouds.encryption.internal.Base64; +import org.jclouds.io.InputSuppliers; +import org.jclouds.util.Strings2; + import com.google.common.annotations.Beta; +import com.google.common.base.Joiner; +import com.google.common.base.Splitter; import com.google.common.base.Throwables; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap.Builder; -import com.google.common.io.ByteArrayDataOutput; -import com.google.common.io.ByteStreams; +import com.google.common.collect.Iterables; +import com.google.common.io.InputSupplier; /** - * Creates OpenSSH RSA keypairs + * Utilities for ssh key pairs * * @author Adrian Cole * @see supplier) + throws IOException { + InputStream stream = supplier.getInput(); + Iterable parts = Splitter.on(' ').split(Strings2.toStringAndClose(stream)); + checkArgument(Iterables.size(parts) >= 2 && "ssh-rsa".equals(Iterables.get(parts, 0)), + "bad format, should be: ssh-rsa AAAAB3..."); + stream = new ByteArrayInputStream(Base64.decode(Iterables.get(parts, 1))); + String marker = new String(readLengthFirst(stream)); + checkArgument("ssh-rsa".equals(marker), "looking for marker ssh-rsa but got %s", marker); + BigInteger publicExponent = new BigInteger(readLengthFirst(stream)); + BigInteger modulus = new BigInteger(readLengthFirst(stream)); + return new RSAPublicKeySpec(modulus, publicExponent); + } + + // http://www.ietf.org/rfc/rfc4253.txt + static byte[] readLengthFirst(InputStream in) throws IOException { + int byte1 = in.read(); + int byte2 = in.read(); + int byte3 = in.read(); + int byte4 = in.read(); + int length = ((byte1 << 24) + (byte2 << 16) + (byte3 << 8) + (byte4 << 0)); + byte[] val = new byte[length]; + in.read(val, 0, length); + return val; + } /** * @@ -55,7 +126,8 @@ public class SshKeys { * @see Crypto#rsaKeyPairGenerator() */ public static KeyPair generateRsaKeyPair(KeyPairGenerator generator) { - generator.initialize(2048); + SecureRandom rand = new SecureRandom(); + generator.initialize(2048, rand); return generator.genKeyPair(); } @@ -67,7 +139,7 @@ public class SshKeys { try { return generate(KeyPairGenerator.getInstance("RSA")); } catch (NoSuchAlgorithmException e) { - Throwables.propagate(e); + propagate(e); return null; } } @@ -81,31 +153,91 @@ public class SshKeys { } public static String encodeAsOpenSSH(RSAPublicKey key) { - ByteArrayDataOutput out = ByteStreams.newDataOutput(); - /* encode the "ssh-rsa" string */ - out.write(sshrsa); - /* Encode the public exponent */ - BigInteger e = key.getPublicExponent(); - byte[] data = e.toByteArray(); - encodeUint32(data.length, out); - out.write(data); - /* Encode the modulus */ - BigInteger m = key.getModulus(); - data = m.toByteArray(); - encodeUint32(data.length, out); - out.write(data); - return "ssh-rsa " + CryptoStreams.base64(out.toByteArray()); + byte[] keyBlob = keyBlob(key.getPublicExponent(), key.getModulus()); + return "ssh-rsa " + base64(keyBlob); } public static String encodeAsPem(RSAPrivateKey key) { - return Pems.pem(key.getEncoded(), Pems.PRIVATE_PKCS1_MARKER, 64); + StringWriter stringWriter = new StringWriter(); + PEMWriter pemFormatWriter = new PEMWriter(stringWriter); + try { + pemFormatWriter.writeObject(key); + pemFormatWriter.close(); + } catch (IOException e) { + Throwables.propagate(e); + } + return stringWriter.toString(); + // TODO: understand why pem isn't passing testCanGenerate where keys are + // checked to match. + // return pem(key.getEncoded(), PRIVATE_PKCS1_MARKER, 64); } - public static void encodeUint32(int value, ByteArrayDataOutput out) { - out.write((byte) ((value >>> 24) & 0xff)); - out.write((byte) ((value >>> 16) & 0xff)); - out.write((byte) ((value >>> 8) & 0xff)); - out.write((byte) (value & 0xff)); + /** + * @param privateKeyPEM + * RSA private key in PEM format + * @param publicKeyOpenSSH + * RSA public key in OpenSSH format + * @return true if the keypair are matches + */ + public static boolean privateKeyMatchesPublicKey(String privateKeyPEM, String publicKeyOpenSSH) { + try { + KeySpec privateKeySpec = privateKeySpec(privateKeyPEM); + checkArgument(privateKeySpec instanceof RSAPrivateCrtKeySpec, + "incorrect format expected RSAPrivateCrtKeySpec was %s", privateKeySpec); + return privateKeyMatchesPublicKey(RSAPrivateCrtKeySpec.class.cast(privateKeySpec), + publicKeySpecFromOpenSSH(publicKeyOpenSSH)); + } catch (IOException e) { + propagate(e); + return false; + } } + /** + * @return true if the keypair are matches + */ + public static boolean privateKeyMatchesPublicKey(RSAPrivateCrtKeySpec privateKey, RSAPublicKeySpec publicKey) { + return privateKey.getPublicExponent().equals(publicKey.getPublicExponent()) + && privateKey.getModulus().equals(publicKey.getModulus()); + } + + /** + * Create a fingerprint per the following spec + * + * @param publicExponent + * @param modulus + * + * @return hex fingerprint ex. + * {@code 2b:a9:62:95:5b:8b:1d:61:e0:92:f7:03:10:e9:db:d9} + */ + public static String fingerprint(BigInteger publicExponent, BigInteger modulus) { + byte[] keyBlob = keyBlob(publicExponent, modulus); + return Joiner.on(":").join(Splitter.fixedLength(2).split(hex(md5(keyBlob)))); + } + + public static byte[] keyBlob(BigInteger publicExponent, BigInteger modulus) { + try { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + writeLengthFirst("ssh-rsa".getBytes(), out); + writeLengthFirst(publicExponent.toByteArray(), out); + writeLengthFirst(modulus.toByteArray(), out); + return out.toByteArray(); + } catch (IOException e) { + propagate(e); + return null; + } + } + + // http://www.ietf.org/rfc/rfc4253.txt + static void writeLengthFirst(byte[] array, ByteArrayOutputStream out) throws IOException { + out.write((array.length >>> 24) & 0xFF); + out.write((array.length >>> 16) & 0xFF); + out.write((array.length >>> 8) & 0xFF); + out.write((array.length >>> 0) & 0xFF); + if (array.length == 1 && array[0] == (byte) 0x00) + out.write(new byte[0]); + else + out.write(array); + } } diff --git a/core/src/test/java/org/jclouds/crypto/SshKeysTest.java b/core/src/test/java/org/jclouds/crypto/SshKeysTest.java index e9a4415a53..f88d4cc004 100644 --- a/core/src/test/java/org/jclouds/crypto/SshKeysTest.java +++ b/core/src/test/java/org/jclouds/crypto/SshKeysTest.java @@ -18,48 +18,80 @@ */ package org.jclouds.crypto; +import static org.jclouds.crypto.SshKeys.fingerprint; +import static org.jclouds.crypto.SshKeys.generate; +import static org.jclouds.crypto.SshKeys.privateKeyMatchesPublicKey; +import static org.jclouds.crypto.SshKeys.publicKeySpecFromOpenSSH; import static org.testng.Assert.assertEquals; import java.io.IOException; import java.security.KeyFactory; import java.security.NoSuchAlgorithmException; -import java.security.interfaces.RSAPrivateCrtKey; import java.security.interfaces.RSAPublicKey; import java.security.spec.InvalidKeySpecException; +import java.security.spec.RSAPrivateCrtKeySpec; +import java.security.spec.RSAPublicKeySpec; import java.util.Map; import org.jclouds.io.Payloads; +import org.jclouds.util.Strings2; import org.testng.annotations.Test; /** * * @author Adrian Cole */ -@Test(groups = "unit", singleThreaded = true) +@Test(groups = "unit", singleThreaded = true, testName = "SshKeysTest") public class SshKeysTest { - public static final String SSH_PUBLIC_KEY = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDJvZkkmoabQopH7yd9Ak2xJ34X21c0xXsJ85xbqOyqzwRmCJVHT2EPUhg6PhiozSok42WDKDjFFZ7B1IbtBM+PWUmlUCJ1r2xfLb6TPJqBkDUCbQ5lxupvmGh4gOBxf54Nrv2zS7QOiaNx870QqG8csHP7Mz7dCo9GQ9XydhNt+z4eNXPM5ToPUHRdHf4g9lmXYCdazZ3SqGdK0dwNS+dFVDQ/jzZjA31WBx45m2k/PQMIoQnlPHlJO5vyTT/O39UAwdBp8tK5Awt3azhku45mm03/+4CxObGKt6p3fvP7xlN0FsnwsSkn6IJe5J+blpSHuLDqjG9OhjYAvnf6MrZR"; - public static final String SSH_PRIVATE_KEY = "-----BEGIN RSA PRIVATE KEY-----\\nMIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDJvZkkmoabQopH\\n7yd9Ak2xJ34X21c0xXsJ85xbqOyqzwRmCJVHT2EPUhg6PhiozSok42WDKDjFFZ7B\\n1IbtBM+PWUmlUCJ1r2xfLb6TPJqBkDUCbQ5lxupvmGh4gOBxf54Nrv2zS7QOiaNx\\n870QqG8csHP7Mz7dCo9GQ9XydhNt+z4eNXPM5ToPUHRdHf4g9lmXYCdazZ3SqGdK\\n0dwNS+dFVDQ/jzZjA31WBx45m2k/PQMIoQnlPHlJO5vyTT/O39UAwdBp8tK5Awt3\\nazhku45mm03/+4CxObGKt6p3fvP7xlN0FsnwsSkn6IJe5J+blpSHuLDqjG9OhjYA\\nvnf6MrZRAgMBAAECggEBAMDzwHeL/FafS9cFXEVqYJih5y42MbBdeRLJl7DrXoD4\\nQ4K7jtuHhpO6t0VtgvRgVoC1pa/OVo3Z4eANv4cO5N58Tb35aRwaTpKyE+aLPlPR\\nc4IAgJbDrBJUOQeYbBLiNm9sAWbtbyfAaT1iHGDEWJGeCzAlkWik4ugXlZeza13y\\nC4hg2yUymju27nSIWcEfAo7Zmw8CTy32edLGUNq8qu7KYSaocKvf522Kjl5p6cly\\nnd+OhcFA4pSTeKkgjb1dEHw2p4JVX/srHCpySo4ERtHAFyVyTIhsP5GICAq856U5\\nGMSIT1Hja+6q/FEDqT2R32ju62VfQBS2kSfeBlzot6kCgYEA/1p6Xljvn6wrYklQ\\n046E7xp7jlx2yKWnbIjRf5QFpK5Czb53I+mSkQcDF3imaZK+f/xz1OURmGv6phsA\\nFdU+UOdzGki7DYCNuTuz9LhdpIqxGqlcvnvOP9A6ouRGTz45VX2rqUx5A2ou0SPW\\n7ok++JfvRGj4VC1G2005ht90/98CgYEAykBeNHnChXmxRv5ESD28S5ijxFLI5qvG\\nooUIhdVQ2ws7H7bb4Arj4JClu55Y3uOi2uVgPq5Pqy9aLcAALuSJjRQj9+NU9EJS\\nLy1WhBQTRNpTlUshNpg4H5b9sFoMTgz3nrTU24NY73RtoDl19TjiK7O9rTasYlcr\\n36dLejyeT88CgYEAs4vp2OcN7ibABobolyhx3jGvyOTI/MJFm7IEJIFvCmEhRctz\\nuEOms+TLTridwkPVQObAh2Rd39+kySDZCYD8JSTosQWMyKyoeiM5oIv2BBkk+Es3\\nlBQ3bHU8lYaOzW9CHxOTHSJRQI5rxtA9c1H7fg5OxbpNSdrgJJkDJwt+F98CgYEA\\niCBWx58EK+5CQXQ15SGYMJFl+Gd3zLnlEdHUcK+ooiWm/6uFxf/ObIEu616iljJE\\nlGw6ITYVbTSLz6sg9G7hndDmfJvHvDc/NX2gc3lHltoT07IjgqllbO2lhiK1kXrs\\n1ycC9VQscc69UlAacph8scliaskXsYDWiMwC4x0VuMUCgYA/FSHHAXLyBqoFqEpQ\\nRmfgXzUcU/mKEaGRPhAvoAU87Z74mEgaO5Hbv7zmFwWpD4cdW0WybzVv9r5Hs/hS\\n4JGBzx4KPFAygnVeXiftVuM9scycg3RHK907hmlYNZicI7TbUlM5RQ4ngn/LTjXo\\nG+TDTPn3Luw1fvAMEEcdsr9cJw==\\n-----END RSA PRIVATE KEY-----"; + String expectedFingerprint = "2b:a9:62:95:5b:8b:1d:61:e0:92:f7:03:10:e9:db:d9"; @Test - public void testCanGenerate() { - Map map = SshKeys.generate(); - assert map.get("public").startsWith("ssh-rsa ") : map; - assert map.get("private").startsWith("-----BEGIN RSA PRIVATE KEY-----") : map; + public void testCanReadRsaAndCompareFingerprintOnPublicRSAKey() throws IOException { + String pubKey = Strings2.toStringAndClose(getClass().getResourceAsStream("/test.pub")); + RSAPublicKeySpec key = SshKeys.publicKeySpecFromOpenSSH(pubKey); + String fingerPrint = fingerprint(key.getPublicExponent(), key.getModulus()); + assertEquals(fingerPrint, expectedFingerprint); } @Test - public void testEncodeAsPem() throws IOException, InvalidKeySpecException, NoSuchAlgorithmException { - String encoded = SshKeys.encodeAsPem((RSAPrivateCrtKey) KeyFactory.getInstance("RSA").generatePrivate( - Pems.privateKeySpec(Payloads.newStringPayload(PemsTest.PRIVATE_KEY)))); - assertEquals(encoded.replace("\n", "\\n").trim(), SSH_PRIVATE_KEY); + public void testCanReadRsaAndCompareFingerprintOnPrivateRSAKey() throws IOException { + String privKey = Strings2.toStringAndClose(getClass().getResourceAsStream("/test")); + RSAPrivateCrtKeySpec key = (RSAPrivateCrtKeySpec) Pems.privateKeySpec(privKey); + String fingerPrint = fingerprint(key.getPublicExponent(), key.getModulus()); + assertEquals(fingerPrint, expectedFingerprint); + } + + @Test + public void testPrivateKeyMatchesPublicKeyTyped() throws IOException { + String privKey = Strings2.toStringAndClose(getClass().getResourceAsStream("/test")); + RSAPrivateCrtKeySpec privateKey = (RSAPrivateCrtKeySpec) Pems.privateKeySpec(privKey); + String pubKey = Strings2.toStringAndClose(getClass().getResourceAsStream("/test.pub")); + RSAPublicKeySpec publicKey = publicKeySpecFromOpenSSH(pubKey); + assert privateKeyMatchesPublicKey(privateKey, publicKey); + } + + @Test + public void testPrivateKeyMatchesPublicKeyString() throws IOException { + String privKey = Strings2.toStringAndClose(getClass().getResourceAsStream("/test")); + String pubKey = Strings2.toStringAndClose(getClass().getResourceAsStream("/test.pub")); + assert privateKeyMatchesPublicKey(privKey, pubKey); + } + + @Test + public void testCanGenerate() { + Map map = generate(); + assert map.get("public").startsWith("ssh-rsa ") : map; + assert map.get("private").startsWith("-----BEGIN RSA PRIVATE KEY-----") : map; + assert privateKeyMatchesPublicKey(map.get("private"), map.get("public")) : map; + } @Test public void testEncodeAsOpenSSH() throws IOException, InvalidKeySpecException, NoSuchAlgorithmException { String encoded = SshKeys.encodeAsOpenSSH((RSAPublicKey) KeyFactory.getInstance("RSA").generatePublic( - Pems.publicKeySpec(Payloads.newStringPayload(PemsTest.PUBLIC_KEY)))); - assertEquals(encoded, SSH_PUBLIC_KEY); + SshKeys.publicKeySpecFromOpenSSH(Payloads.newPayload(getClass().getResourceAsStream("/test.pub"))))); + assertEquals(encoded, Strings2.toStringAndClose(getClass().getResourceAsStream("/test.pub")).trim()); } } diff --git a/core/src/test/resources/test b/core/src/test/resources/test new file mode 100644 index 0000000000..5de8a47c8a --- /dev/null +++ b/core/src/test/resources/test @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEogIBAAKCAQEAnJvA40x4OK+9nVYTS0N916VMjC6/qYe/IuDUdy6hdW1wz9IO +MTS3CPxlE0KuoNO1/M7O7yFso6IragTxUkNqJ2mUOqV0Bf0CEkUIKzeYRGfpx+QM +PorHbLQXjjFinWKwibZuv6lqtvqwcsrjW7bpWsz9x+0qqKM0o0UhjUMhTRqZoYxo +E2zUH7WA8JRatE/bQkjv/nWBfI+/WzSDhJn7AjIql0Nd4Q+bxohIJEZu8yDw1H6T +pd7mw83m6UYBk4eZH79r3d2euuQMUKIunyLbw7vNJJ8qYTJQuNYIiJuWKnzzjxuJ +UfumhdOqfjSobznhAjTLUbA/btZCiQ/TasV4cQIDAQABAoIBAEeOn1b8ZN455qDS +aKR2JTT4cX6ICckznnEYW9xNMTcPl4FN0HBJTuzLLn/bcyFHOxtVf5YiJpqqCb46 +ne1hokp54mHdoaLu1Rh19GKS139CH77XA4U8Mh0IOM8e35lcM5/o/LeUeI89Aoyh +CbupWvzDN543TsuZLv7/InKCXt/0dXhAQpq3UiBT63EITQbyom5fSPnMzqM3F8jD +E9ZqkX4JsnTPC7FQDIpPCaKjG9YCZqoljz+1ssli3mN66V/JKefcCiVoubalmmT2 +dpvmRtFaKvhAmkWYakYybYg8aDi3YygAHSU1bzxlY4TNiQgPdnTTDAPyeqqVrE1D +Chi+18UCgYEAzlk7c+tFwxZ3ryycOe0loFudUNE5rviHhPgbOHoSTooXh0Hq1Vrb +2ic+4FbRpoPHLpcLM9LX+arezUdeFBZ8qunjUG6MbUhAeAm/3cfMk+nZg3Skpg8+ +C1D3hxGX4qdhURHvc2QUH7VIUWbucvPgtL8pt1z5Su/EE1Cb2XVsvu8CgYEAwkqZ +4vTZxI0XqJo6BfUnKGJEDC8xeWr10ELPdXLTCuNDpLSYNedQAxZi9XzvbnbWZ/MF +Z7IWkzzyAjsX0gpI56cxxtas/chxUboBlUo6ZW8QcPDcU2sKJi318wzElqqvRMNM +InfLf8nuPC9hyhe49/lFBBSZJeIo396DuqnTPp8CgYBO4NVVLm5wcLo3gDoH+psT +fXHZXuFJ/T7wmVbuc9tjom30CkKWZDD+Z1olr4pcuKr/KEXj/YkJq0OX/Nv9mcr2 +GooGSPvtGl1qhW+Oe728HPxEv+XghJsXAFBelV8WCR2uO8jotyzqIgYO9+XWk1sm +PJzZtvSkrJqrN3kb20NCiQKBgDDVP0hj8jgMnl2qJdtJesYTrLbDRdQWpiHqKOqE +Kbca1+2V1ov1z453GfhJpoRFKi6GTl15zWLEdq9I2vvXyesvgrtPSbufnZvE/JDh +TzwfZip832O4C5z9AExOcTrNO7A0xfYD1goQXuiRoCqDO+JXrJkR9EwpQ8zAyKsp +9AZRAoGAGq3TYpmlI5oucEURHKsHOrIBirHFD+RaXMynxzgwkRnt6Z5Mg10I7Ddr +LiGK8/IrF8bg1F7weLVmj93zjvhQTh5yvb1jwVdFGXM2rbR7/P7F6n2f7xM4+lmv +Tq7E9Sv8UVuraAwJihlKCuBtpZM1t2JhcuNjXAZngj7R9j5HIZg= +-----END RSA PRIVATE KEY----- diff --git a/core/src/test/resources/test.pub b/core/src/test/resources/test.pub new file mode 100644 index 0000000000..2219cf9d55 --- /dev/null +++ b/core/src/test/resources/test.pub @@ -0,0 +1 @@ +ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCcm8DjTHg4r72dVhNLQ33XpUyMLr+ph78i4NR3LqF1bXDP0g4xNLcI/GUTQq6g07X8zs7vIWyjoitqBPFSQ2onaZQ6pXQF/QISRQgrN5hEZ+nH5Aw+isdstBeOMWKdYrCJtm6/qWq2+rByyuNbtulazP3H7SqoozSjRSGNQyFNGpmhjGgTbNQftYDwlFq0T9tCSO/+dYF8j79bNIOEmfsCMiqXQ13hD5vGiEgkRm7zIPDUfpOl3ubDzebpRgGTh5kfv2vd3Z665AxQoi6fItvDu80knyphMlC41giIm5YqfPOPG4lR+6aF06p+NKhvOeECNMtRsD9u1kKJD9NqxXhx