Issue 719: ssh fingerprint and comparison support

This commit is contained in:
Adrian Cole 2011-10-13 01:49:27 -07:00
parent 0e9d3666ad
commit a1d1f0131f
6 changed files with 301 additions and 94 deletions

View File

@ -98,15 +98,15 @@ public class CryptoStreams {
} }
/** /**
* Computes and returns the MAC value for a supplied input stream. The mac object is reset when * Computes and returns the MAC value for a supplied input stream. The mac
* this method returns successfully. * object is reset when this method returns successfully.
* *
* @param supplier * @param supplier
* the input stream factory * the input stream factory
* @param mac * @param mac
* the mac object * the mac object
* @return the result of {@link Mac#doFinal()} after updating the mac object with all of the * @return the result of {@link Mac#doFinal()} after updating the mac object
* bytes in the stream and encoding in Base64 * with all of the bytes in the stream and encoding in Base64
* @throws IOException * @throws IOException
* if an I/O error occurs * 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 * Computes and returns the Digest value for a supplied input stream. The
* when this method returns successfully. * digest object is reset when this method returns successfully.
* *
* @param supplier * @param supplier
* the input stream factory * the input stream factory
* @param md * @param md
* the digest object * the digest object
* @return the result of {@link MessageDigest#digest()} after updating the digest object with all * @return the result of {@link MessageDigest#digest()} after updating the
* of the bytes in the stream * digest object with all of the bytes in the stream
* @throws IOException * @throws IOException
* if an I/O error occurs * if an I/O error occurs
*/ */
public static byte[] digest(InputSupplier<? extends InputStream> supplier, final MessageDigest md) public static byte[] digest(InputSupplier<? extends InputStream> supplier, final MessageDigest md)
throws IOException { throws IOException {
return com.google.common.io.ByteStreams.readBytes(supplier, new ByteProcessor<byte[]>() { return com.google.common.io.ByteStreams.readBytes(supplier, new ByteProcessor<byte[]>() {
public boolean processBytes(byte[] buf, int off, int len) { public boolean processBytes(byte[] buf, int off, int len) {
md.update(buf, off, 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 * Computes and returns the MD5 value for a supplied input stream. A digest
* disposed of at runtime, consider using {@link #digest} to be more efficient. * object is created and disposed of at runtime, consider using
* {@link #digest} to be more efficient.
* *
* @param supplier * @param supplier
* the input stream factory * the input stream factory
* *
* @return the result of {@link MessageDigest#digest()} after updating the md5 object with all of * @return the result of {@link MessageDigest#digest()} after updating the
* the bytes in the stream * md5 object with all of the bytes in the stream
* @throws IOException * @throws IOException
* if an I/O error occurs * if an I/O error occurs
*/ */
@ -162,35 +163,40 @@ public class CryptoStreams {
} }
} }
public static byte[] md5(byte[] in) throws IOException { public static byte[] md5(byte[] in) {
return md5(ByteStreams.newInputStreamSupplier(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 * Computes and returns the MAC value for a supplied input stream. The mac
* this method returns successfully. * object is reset when this method returns successfully.
* *
* @param supplier * @param supplier
* the input stream factory * the input stream factory
* @param mac * @param mac
* the mac object * the mac object
* @return the result of {@link Mac#doFinal()} after updating the mac object with all of the * @return the result of {@link Mac#doFinal()} after updating the mac object
* bytes in the stream * with all of the bytes in the stream
* @throws IOException * @throws IOException
* if an I/O error occurs * if an I/O error occurs
*/ */
public static byte[] mac(InputSupplier<? extends InputStream> supplier, final Mac mac) throws IOException { public static byte[] mac(InputSupplier<? extends InputStream> supplier, final Mac mac) throws IOException {
return com.google.common.io.ByteStreams.readBytes(checkNotNull(supplier, "supplier"), return com.google.common.io.ByteStreams.readBytes(checkNotNull(supplier, "supplier"),
new ByteProcessor<byte[]>() { new ByteProcessor<byte[]>() {
public boolean processBytes(byte[] buf, int off, int len) { public boolean processBytes(byte[] buf, int off, int len) {
mac.update(buf, off, len); mac.update(buf, off, len);
return true; return true;
} }
public byte[] getResult() { public byte[] getResult() {
return mac.doFinal(); return mac.doFinal();
} }
}); });
} }
/** /**
@ -206,20 +212,21 @@ public class CryptoStreams {
public static String base64Encode(InputSupplier<? extends InputStream> supplier) throws IOException { public static String base64Encode(InputSupplier<? extends InputStream> supplier) throws IOException {
final ByteArrayOutputStream out = new ByteArrayOutputStream(); final ByteArrayOutputStream out = new ByteArrayOutputStream();
return com.google.common.io.ByteStreams.readBytes(InputSuppliers.base64Encoder(supplier), return com.google.common.io.ByteStreams.readBytes(InputSuppliers.base64Encoder(supplier),
new ByteProcessor<String>() { new ByteProcessor<String>() {
public boolean processBytes(byte[] buf, int off, int len) { public boolean processBytes(byte[] buf, int off, int len) {
out.write(buf, off, len); out.write(buf, off, len);
return true; return true;
} }
public String getResult() { public String getResult() {
return new String(out.toByteArray(), Charsets.UTF_8); 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 * @param supplier
* the input stream factory * the input stream factory
@ -231,21 +238,21 @@ public class CryptoStreams {
public static byte[] base64Decode(InputSupplier<? extends InputStream> supplier) throws IOException { public static byte[] base64Decode(InputSupplier<? extends InputStream> supplier) throws IOException {
final ByteArrayOutputStream out = new ByteArrayOutputStream(); final ByteArrayOutputStream out = new ByteArrayOutputStream();
return com.google.common.io.ByteStreams.readBytes(InputSuppliers.base64Decoder(supplier), return com.google.common.io.ByteStreams.readBytes(InputSuppliers.base64Decoder(supplier),
new ByteProcessor<byte[]>() { new ByteProcessor<byte[]>() {
public boolean processBytes(byte[] buf, int off, int len) { public boolean processBytes(byte[] buf, int off, int len) {
out.write(buf, off, len); out.write(buf, off, len);
return true; return true;
} }
public byte[] getResult() { public byte[] getResult() {
return out.toByteArray(); return out.toByteArray();
} }
}); });
} }
final static byte[] HEX_CHAR_TABLE = { (byte) '0', (byte) '1', (byte) '2', (byte) '3', (byte) '4', (byte) '5', 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) '6', (byte) '7', (byte) '8', (byte) '9', (byte) 'a', (byte) 'b', (byte) 'c', (byte) 'd', (byte) 'e',
(byte) 'f' }; (byte) 'f' };
/** /**
* Computes and returns the hex value for a supplied input stream. * 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 * @param supplier
* the input stream factory * the input stream factory

View File

@ -166,8 +166,13 @@ public class Pems {
* private key in pem encoded format. * private key in pem encoded format.
* @see Pems#privateKeySpec(InputSupplier) * @see Pems#privateKeySpec(InputSupplier)
*/ */
public static KeySpec privateKeySpec(String pem) throws IOException { public static KeySpec privateKeySpec(String pem) {
return privateKeySpec(InputSuppliers.of(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 IOException
* @throws CertificateEncodingException * @throws CertificateEncodingException
*/ */
// TODO: understand why pem isn't passing SshKeysTest.testCanGenerate where
// keys are checked to match.
public static String pem(PrivateKey key) { public static String pem(PrivateKey key) {
String marker = key instanceof RSAPrivateCrtKey ? PRIVATE_PKCS1_MARKER : PRIVATE_PKCS8_MARKER; String marker = key instanceof RSAPrivateCrtKey ? PRIVATE_PKCS1_MARKER : PRIVATE_PKCS8_MARKER;
return pem(key instanceof RSAPrivateCrtKey ? getEncoded(RSAPrivateCrtKey.class.cast(key)) : key.getEncoded(), return pem(key instanceof RSAPrivateCrtKey ? getEncoded(RSAPrivateCrtKey.class.cast(key)) : key.getEncoded(),

View File

@ -18,23 +18,46 @@
*/ */
package org.jclouds.crypto; 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.math.BigInteger;
import java.security.KeyPair; import java.security.KeyPair;
import java.security.KeyPairGenerator; import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.interfaces.RSAPrivateKey; import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey; 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 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.annotations.Beta;
import com.google.common.base.Joiner;
import com.google.common.base.Splitter;
import com.google.common.base.Throwables; import com.google.common.base.Throwables;
import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableMap.Builder; import com.google.common.collect.ImmutableMap.Builder;
import com.google.common.io.ByteArrayDataOutput; import com.google.common.collect.Iterables;
import com.google.common.io.ByteStreams; import com.google.common.io.InputSupplier;
/** /**
* Creates OpenSSH RSA keypairs * Utilities for ssh key pairs
* *
* @author Adrian Cole * @author Adrian Cole
* @see <a href= * @see <a href=
@ -43,9 +66,57 @@ import com.google.common.io.ByteStreams;
*/ */
@Beta @Beta
public class SshKeys { public class SshKeys {
// All data type encoding is defined in the section #5 of RFC #4251. string
// and mpint (multiple precision integer) types are encoded this way : /**
private static final byte[] sshrsa = new byte[] { 0, 0, 0, 7, 's', 's', 'h', '-', 'r', 's', 'a' }; * Executes {@link Pems#publicKeySpecFromOpenSSH(InputSupplier)} on the
* string which was OpenSSH Base64 Encoded {@code id_rsa.pub}
*
* @param idRsaPub
* formatted {@code ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAB...}
* @see Pems#publicKeySpecFromOpenSSH(InputSupplier)
*/
public static RSAPublicKeySpec publicKeySpecFromOpenSSH(String idRsaPub) throws IOException {
return publicKeySpecFromOpenSSH(InputSuppliers.of(idRsaPub));
}
/**
* Returns {@link RSAPublicKeySpec} which was OpenSSH Base64 Encoded
* {@code id_rsa.pub}
*
* @param supplier
* the input stream factory, formatted
* {@code ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAB...}
*
* @return the {@link RSAPublicKeySpec} which was OpenSSH Base64 Encoded
* {@code id_rsa.pub}
* @throws IOException
* if an I/O error occurs
*/
public static RSAPublicKeySpec publicKeySpecFromOpenSSH(InputSupplier<? extends InputStream> supplier)
throws IOException {
InputStream stream = supplier.getInput();
Iterable<String> 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() * @see Crypto#rsaKeyPairGenerator()
*/ */
public static KeyPair generateRsaKeyPair(KeyPairGenerator generator) { public static KeyPair generateRsaKeyPair(KeyPairGenerator generator) {
generator.initialize(2048); SecureRandom rand = new SecureRandom();
generator.initialize(2048, rand);
return generator.genKeyPair(); return generator.genKeyPair();
} }
@ -67,7 +139,7 @@ public class SshKeys {
try { try {
return generate(KeyPairGenerator.getInstance("RSA")); return generate(KeyPairGenerator.getInstance("RSA"));
} catch (NoSuchAlgorithmException e) { } catch (NoSuchAlgorithmException e) {
Throwables.propagate(e); propagate(e);
return null; return null;
} }
} }
@ -81,31 +153,91 @@ public class SshKeys {
} }
public static String encodeAsOpenSSH(RSAPublicKey key) { public static String encodeAsOpenSSH(RSAPublicKey key) {
ByteArrayDataOutput out = ByteStreams.newDataOutput(); byte[] keyBlob = keyBlob(key.getPublicExponent(), key.getModulus());
/* encode the "ssh-rsa" string */ return "ssh-rsa " + base64(keyBlob);
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());
} }
public static String encodeAsPem(RSAPrivateKey key) { 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)); * @param privateKeyPEM
out.write((byte) ((value >>> 16) & 0xff)); * RSA private key in PEM format
out.write((byte) ((value >>> 8) & 0xff)); * @param publicKeyOpenSSH
out.write((byte) (value & 0xff)); * 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 <a
* href="http://tools.ietf.org/html/draft-friedl-secsh-fingerprint-00"
* >spec</a>
*
* @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);
}
} }

View File

@ -18,48 +18,80 @@
*/ */
package org.jclouds.crypto; 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 static org.testng.Assert.assertEquals;
import java.io.IOException; import java.io.IOException;
import java.security.KeyFactory; import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import java.security.interfaces.RSAPrivateCrtKey;
import java.security.interfaces.RSAPublicKey; import java.security.interfaces.RSAPublicKey;
import java.security.spec.InvalidKeySpecException; import java.security.spec.InvalidKeySpecException;
import java.security.spec.RSAPrivateCrtKeySpec;
import java.security.spec.RSAPublicKeySpec;
import java.util.Map; import java.util.Map;
import org.jclouds.io.Payloads; import org.jclouds.io.Payloads;
import org.jclouds.util.Strings2;
import org.testng.annotations.Test; import org.testng.annotations.Test;
/** /**
* *
* @author Adrian Cole * @author Adrian Cole
*/ */
@Test(groups = "unit", singleThreaded = true) @Test(groups = "unit", singleThreaded = true, testName = "SshKeysTest")
public class SshKeysTest { public class SshKeysTest {
public static final String SSH_PUBLIC_KEY = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDJvZkkmoabQopH7yd9Ak2xJ34X21c0xXsJ85xbqOyqzwRmCJVHT2EPUhg6PhiozSok42WDKDjFFZ7B1IbtBM+PWUmlUCJ1r2xfLb6TPJqBkDUCbQ5lxupvmGh4gOBxf54Nrv2zS7QOiaNx870QqG8csHP7Mz7dCo9GQ9XydhNt+z4eNXPM5ToPUHRdHf4g9lmXYCdazZ3SqGdK0dwNS+dFVDQ/jzZjA31WBx45m2k/PQMIoQnlPHlJO5vyTT/O39UAwdBp8tK5Awt3azhku45mm03/+4CxObGKt6p3fvP7xlN0FsnwsSkn6IJe5J+blpSHuLDqjG9OhjYAvnf6MrZR"; String expectedFingerprint = "2b:a9:62:95:5b:8b:1d:61:e0:92:f7:03:10:e9:db:d9";
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-----";
@Test @Test
public void testCanGenerate() { public void testCanReadRsaAndCompareFingerprintOnPublicRSAKey() throws IOException {
Map<String, String> map = SshKeys.generate(); String pubKey = Strings2.toStringAndClose(getClass().getResourceAsStream("/test.pub"));
assert map.get("public").startsWith("ssh-rsa ") : map; RSAPublicKeySpec key = SshKeys.publicKeySpecFromOpenSSH(pubKey);
assert map.get("private").startsWith("-----BEGIN RSA PRIVATE KEY-----") : map; String fingerPrint = fingerprint(key.getPublicExponent(), key.getModulus());
assertEquals(fingerPrint, expectedFingerprint);
} }
@Test @Test
public void testEncodeAsPem() throws IOException, InvalidKeySpecException, NoSuchAlgorithmException { public void testCanReadRsaAndCompareFingerprintOnPrivateRSAKey() throws IOException {
String encoded = SshKeys.encodeAsPem((RSAPrivateCrtKey) KeyFactory.getInstance("RSA").generatePrivate( String privKey = Strings2.toStringAndClose(getClass().getResourceAsStream("/test"));
Pems.privateKeySpec(Payloads.newStringPayload(PemsTest.PRIVATE_KEY)))); RSAPrivateCrtKeySpec key = (RSAPrivateCrtKeySpec) Pems.privateKeySpec(privKey);
assertEquals(encoded.replace("\n", "\\n").trim(), SSH_PRIVATE_KEY); 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<String, String> 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 @Test
public void testEncodeAsOpenSSH() throws IOException, InvalidKeySpecException, NoSuchAlgorithmException { public void testEncodeAsOpenSSH() throws IOException, InvalidKeySpecException, NoSuchAlgorithmException {
String encoded = SshKeys.encodeAsOpenSSH((RSAPublicKey) KeyFactory.getInstance("RSA").generatePublic( String encoded = SshKeys.encodeAsOpenSSH((RSAPublicKey) KeyFactory.getInstance("RSA").generatePublic(
Pems.publicKeySpec(Payloads.newStringPayload(PemsTest.PUBLIC_KEY)))); SshKeys.publicKeySpecFromOpenSSH(Payloads.newPayload(getClass().getResourceAsStream("/test.pub")))));
assertEquals(encoded, SSH_PUBLIC_KEY); assertEquals(encoded, Strings2.toStringAndClose(getClass().getResourceAsStream("/test.pub")).trim());
} }
} }

View File

@ -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-----

View File

@ -0,0 +1 @@
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCcm8DjTHg4r72dVhNLQ33XpUyMLr+ph78i4NR3LqF1bXDP0g4xNLcI/GUTQq6g07X8zs7vIWyjoitqBPFSQ2onaZQ6pXQF/QISRQgrN5hEZ+nH5Aw+isdstBeOMWKdYrCJtm6/qWq2+rByyuNbtulazP3H7SqoozSjRSGNQyFNGpmhjGgTbNQftYDwlFq0T9tCSO/+dYF8j79bNIOEmfsCMiqXQ13hD5vGiEgkRm7zIPDUfpOl3ubDzebpRgGTh5kfv2vd3Z665AxQoi6fItvDu80knyphMlC41giIm5YqfPOPG4lR+6aF06p+NKhvOeECNMtRsD9u1kKJD9NqxXhx