From 7e5a6e68cf634fe424116c0a4f98f1f0811d9c2a Mon Sep 17 00:00:00 2001 From: Adrian Cole Date: Thu, 13 Oct 2011 23:57:46 -0700 Subject: [PATCH] Issue 719: add sha1 support to SshKeys --- ...rityGroupsAsNeededAndReturnRunOptions.java | 37 ++-- .../java/org/jclouds/ec2/domain/KeyPair.java | 73 ++++++-- .../xml/DescribeKeyPairsResponseHandler.java | 32 ++-- .../ec2/xml/KeyPairResponseHandler.java | 34 ++-- ...GroupsAsNeededAndReturnRunOptionsTest.java | 107 +++++++---- .../DescribeKeyPairsResponseHandlerTest.java | 9 +- .../ec2/xml/KeyPairResponseHandlerTest.java | 62 ++++--- .../ec2/src/test/resources/create_keypair.xml | 51 +++--- .../org/jclouds/crypto/CryptoStreams.java | 31 ++++ .../main/java/org/jclouds/crypto/Pems.java | 2 +- .../main/java/org/jclouds/crypto/SshKeys.java | 168 ++++++++++++++---- .../java/org/jclouds/crypto/SshKeysTest.java | 31 +++- .../java/org/jclouds/sshj/SshjSshClient.java | 46 ++--- ...GroupsAsNeededAndReturnRunOptionsTest.java | 24 +-- 14 files changed, 483 insertions(+), 224 deletions(-) diff --git a/apis/ec2/src/main/java/org/jclouds/ec2/compute/strategy/CreateKeyPairAndSecurityGroupsAsNeededAndReturnRunOptions.java b/apis/ec2/src/main/java/org/jclouds/ec2/compute/strategy/CreateKeyPairAndSecurityGroupsAsNeededAndReturnRunOptions.java index 896918d25f..15d21a2bb0 100644 --- a/apis/ec2/src/main/java/org/jclouds/ec2/compute/strategy/CreateKeyPairAndSecurityGroupsAsNeededAndReturnRunOptions.java +++ b/apis/ec2/src/main/java/org/jclouds/ec2/compute/strategy/CreateKeyPairAndSecurityGroupsAsNeededAndReturnRunOptions.java @@ -30,6 +30,7 @@ import javax.inject.Singleton; import org.jclouds.compute.domain.Template; import org.jclouds.compute.options.TemplateOptions; +import static org.jclouds.crypto.SshKeys.*; import org.jclouds.ec2.compute.domain.RegionAndName; import org.jclouds.ec2.compute.domain.RegionNameAndIngressRules; import org.jclouds.ec2.compute.options.EC2TemplateOptions; @@ -58,8 +59,8 @@ public class CreateKeyPairAndSecurityGroupsAsNeededAndReturnRunOptions { @Inject public CreateKeyPairAndSecurityGroupsAsNeededAndReturnRunOptions(Cache credentialsMap, - @Named("SECURITY") Cache securityGroupMap, - Provider optionsProvider) { + @Named("SECURITY") Cache securityGroupMap, + Provider optionsProvider) { this.credentialsMap = checkNotNull(credentialsMap, "credentialsMap"); this.securityGroupMap = checkNotNull(securityGroupMap, "securityGroupMap"); this.optionsProvider = checkNotNull(optionsProvider, "optionsProvider"); @@ -83,10 +84,10 @@ public class CreateKeyPairAndSecurityGroupsAsNeededAndReturnRunOptions { instanceOptions.withUserData(userData); Set blockDeviceMappings = EC2TemplateOptions.class.cast(template.getOptions()) - .getBlockDeviceMappings(); + .getBlockDeviceMappings(); if (blockDeviceMappings.size() > 0) { checkState("ebs".equals(template.getImage().getUserMetadata().get("rootDeviceType")), - "BlockDeviceMapping only available on ebs boot"); + "BlockDeviceMapping only available on ebs boot"); instanceOptions.withBlockDeviceMappings(blockDeviceMappings); } } @@ -102,30 +103,32 @@ public class CreateKeyPairAndSecurityGroupsAsNeededAndReturnRunOptions { public String createNewKeyPairUnlessUserSpecifiedOtherwise(String region, String group, TemplateOptions options) { String keyPairName = null; boolean shouldAutomaticallyCreateKeyPair = true; - + if (options instanceof EC2TemplateOptions) { keyPairName = EC2TemplateOptions.class.cast(options).getKeyPair(); if (keyPairName == null) shouldAutomaticallyCreateKeyPair = EC2TemplateOptions.class.cast(options) - .shouldAutomaticallyCreateKeyPair(); + .shouldAutomaticallyCreateKeyPair(); } - + if (keyPairName == null && shouldAutomaticallyCreateKeyPair) { keyPairName = createOrImportKeyPair(region, group, options); } else if (keyPairName != null) { if (options.getOverridingCredentials() != null && options.getOverridingCredentials().credential != null) { - KeyPair keyPair = KeyPair.builder().region(region).keyName(keyPairName).keyFingerprint("//TODO") - .keyMaterial(options.getOverridingCredentials().credential).build(); - + String pem = options.getOverridingCredentials().credential; + KeyPair keyPair = KeyPair.builder().region(region).keyName(keyPairName).fingerprint( + fingerprintPrivateKey(pem)).sha1OfPrivateKey(sha1PrivateKey(pem)).keyMaterial(pem).build(); RegionAndName key = new RegionAndName(region, keyPairName); credentialsMap.asMap().put(key, keyPair); } } - + if (options.getRunScript() != null) { RegionAndName regionAndName = new RegionAndName(region, keyPairName); - String message = String.format("no private key configured for: %s; please use options.overrideLoginCredentialWith(rsa_private_text)", - regionAndName); + String message = String + .format( + "no private key configured for: %s; please use options.overrideLoginCredentialWith(rsa_private_text)", + regionAndName); // test to see if this is in cache. try { credentialsMap.getUnchecked(regionAndName); @@ -152,14 +155,14 @@ public class CreateKeyPairAndSecurityGroupsAsNeededAndReturnRunOptions { groups.add(markerGroup); RegionNameAndIngressRules regionNameAndIngessRulesForMarkerGroup; - + if (userSpecifiedTheirOwnGroups(options)) { regionNameAndIngessRulesForMarkerGroup = new RegionNameAndIngressRules(region, markerGroup, new int[] {}, - false); + false); groups.addAll(EC2TemplateOptions.class.cast(options).getGroups()); } else { - regionNameAndIngessRulesForMarkerGroup = new RegionNameAndIngressRules(region, markerGroup, - options.getInboundPorts(), true); + regionNameAndIngessRulesForMarkerGroup = new RegionNameAndIngressRules(region, markerGroup, options + .getInboundPorts(), true); } // this will create if not yet exists. securityGroupMap.getUnchecked(regionNameAndIngessRulesForMarkerGroup); diff --git a/apis/ec2/src/main/java/org/jclouds/ec2/domain/KeyPair.java b/apis/ec2/src/main/java/org/jclouds/ec2/domain/KeyPair.java index 42f48f8610..0038838c16 100644 --- a/apis/ec2/src/main/java/org/jclouds/ec2/domain/KeyPair.java +++ b/apis/ec2/src/main/java/org/jclouds/ec2/domain/KeyPair.java @@ -20,6 +20,7 @@ package org.jclouds.ec2.domain; import static com.google.common.base.Preconditions.checkNotNull; +import org.jclouds.crypto.SshKeys; import org.jclouds.javax.annotation.Nullable; /** @@ -32,8 +33,8 @@ import org.jclouds.javax.annotation.Nullable; public class KeyPair implements Comparable { @Override public String toString() { - return "[region=" + region + ", keyName=" + keyName + ", keyFingerprint=" + keyFingerprint + ", keyMaterial?=" - + (keyMaterial != null) + "]"; + return "[region=" + region + ", keyName=" + keyName + ", fingerprint=" + fingerprint + ", sha1OfPrivateKey=" + + sha1OfPrivateKey + ", keyMaterial?=" + (keyMaterial != null) + "]"; } public static Builder builder() { @@ -43,7 +44,8 @@ public class KeyPair implements Comparable { public static class Builder { private String region; private String keyName; - private String keyFingerprint; + private String fingerprint; + private String sha1OfPrivateKey; private String keyMaterial; public Builder region(String region) { @@ -56,8 +58,8 @@ public class KeyPair implements Comparable { return this; } - public Builder keyFingerprint(String keyFingerprint) { - this.keyFingerprint = keyFingerprint; + public Builder sha1OfPrivateKey(String sha1OfPrivateKey) { + this.sha1OfPrivateKey = sha1OfPrivateKey; return this; } @@ -66,27 +68,38 @@ public class KeyPair implements Comparable { return this; } + public Builder fingerprint(String fingerprint) { + this.fingerprint = fingerprint; + return this; + } + public KeyPair build() { - return new KeyPair(region, keyName, keyFingerprint, keyMaterial); + if (fingerprint == null && keyMaterial != null) + fingerprint(SshKeys.fingerprintPrivateKey(keyMaterial)); + return new KeyPair(region, keyName, sha1OfPrivateKey, keyMaterial, fingerprint); } public static Builder fromKeyPair(KeyPair in) { - return new Builder().region(in.getRegion()).keyName(in.getKeyName()).keyFingerprint(in.getKeyFingerprint()) - .keyMaterial(in.getKeyMaterial()); + return new Builder().region(in.getRegion()).keyName(in.getKeyName()).sha1OfPrivateKey(in.getKeyFingerprint()) + .keyMaterial(in.getKeyMaterial()); } } private final String region; private final String keyName; - private final String keyFingerprint; + private final String sha1OfPrivateKey; @Nullable private final String keyMaterial; + @Nullable + private final String fingerprint; - public KeyPair(String region, String keyName, String keyFingerprint, @Nullable String keyMaterial) { + public KeyPair(String region, String keyName, String sha1OfPrivateKey, @Nullable String keyMaterial, + @Nullable String fingerprint) { this.region = checkNotNull(region, "region"); this.keyName = checkNotNull(keyName, "keyName"); - this.keyFingerprint = checkNotNull(keyFingerprint, "keyFingerprint"); + this.sha1OfPrivateKey = checkNotNull(sha1OfPrivateKey, "sha1OfPrivateKey"); this.keyMaterial = keyMaterial;// nullable on list + this.fingerprint = fingerprint;// nullable on list } /** @@ -104,10 +117,30 @@ public class KeyPair implements Comparable { } /** - * A SHA-1 digest of the DER encoded private key. + * @see #getSha1OfPrivateKey */ + @Deprecated public String getKeyFingerprint() { - return keyFingerprint; + return sha1OfPrivateKey; + } + + /** + * A SHA-1 digest of the DER encoded private key. + * + * @see SshKeys#sha1 + */ + public String getSha1OfPrivateKey() { + return sha1OfPrivateKey; + } + + /** + * fingerprint per the following spec + * + * @see SshKeys#fingerprint + */ + public String getFingerprint() { + return fingerprint; } /** @@ -128,10 +161,11 @@ public class KeyPair implements Comparable { public int hashCode() { final int prime = 31; int result = 1; - result = prime * result + ((keyFingerprint == null) ? 0 : keyFingerprint.hashCode()); + result = prime * result + ((fingerprint == null) ? 0 : fingerprint.hashCode()); result = prime * result + ((keyMaterial == null) ? 0 : keyMaterial.hashCode()); result = prime * result + ((keyName == null) ? 0 : keyName.hashCode()); result = prime * result + ((region == null) ? 0 : region.hashCode()); + result = prime * result + ((sha1OfPrivateKey == null) ? 0 : sha1OfPrivateKey.hashCode()); return result; } @@ -144,10 +178,10 @@ public class KeyPair implements Comparable { if (getClass() != obj.getClass()) return false; KeyPair other = (KeyPair) obj; - if (keyFingerprint == null) { - if (other.keyFingerprint != null) + if (fingerprint == null) { + if (other.fingerprint != null) return false; - } else if (!keyFingerprint.equals(other.keyFingerprint)) + } else if (!fingerprint.equals(other.fingerprint)) return false; if (keyMaterial == null) { if (other.keyMaterial != null) @@ -164,6 +198,11 @@ public class KeyPair implements Comparable { return false; } else if (!region.equals(other.region)) return false; + if (sha1OfPrivateKey == null) { + if (other.sha1OfPrivateKey != null) + return false; + } else if (!sha1OfPrivateKey.equals(other.sha1OfPrivateKey)) + return false; return true; } diff --git a/apis/ec2/src/main/java/org/jclouds/ec2/xml/DescribeKeyPairsResponseHandler.java b/apis/ec2/src/main/java/org/jclouds/ec2/xml/DescribeKeyPairsResponseHandler.java index 18cacd3237..a4ab1ac359 100644 --- a/apis/ec2/src/main/java/org/jclouds/ec2/xml/DescribeKeyPairsResponseHandler.java +++ b/apis/ec2/src/main/java/org/jclouds/ec2/xml/DescribeKeyPairsResponseHandler.java @@ -18,12 +18,15 @@ */ package org.jclouds.ec2.xml; +import static org.jclouds.util.SaxUtils.currentOrNull; + import java.util.Set; import javax.inject.Inject; import org.jclouds.aws.util.AWSUtils; import org.jclouds.ec2.domain.KeyPair; +import org.jclouds.ec2.domain.KeyPair.Builder; import org.jclouds.http.functions.ParseSax; import org.jclouds.location.Region; @@ -36,16 +39,18 @@ import com.google.common.collect.Sets; * /> * @author Adrian Cole */ -public class DescribeKeyPairsResponseHandler extends - ParseSax.HandlerForGeneratedRequestWithResult> { +public class DescribeKeyPairsResponseHandler extends ParseSax.HandlerForGeneratedRequestWithResult> { + private final String defaultRegion; + private Builder builder; + @Inject - @Region - String defaultRegion; + public DescribeKeyPairsResponseHandler(@Region String defaultRegion) { + this.defaultRegion = defaultRegion; + builder = KeyPair.builder().region(defaultRegion); + } private StringBuilder currentText = new StringBuilder(); private Set keyPairs = Sets.newLinkedHashSet(); - private String keyFingerprint; - private String keyName; public Set getResult() { return keyPairs; @@ -54,16 +59,19 @@ public class DescribeKeyPairsResponseHandler extends public void endElement(String uri, String name, String qName) { if (qName.equals("keyFingerprint")) { - this.keyFingerprint = currentText.toString().trim(); + builder.sha1OfPrivateKey(currentOrNull(currentText)); } else if (qName.equals("item")) { String region = AWSUtils.findRegionInArgsOrNull(getRequest()); - if (region == null) - region = defaultRegion; - keyPairs.add(new KeyPair(region, keyName, keyFingerprint, null)); + if (region != null) + builder.region(region); + try { + keyPairs.add(builder.build()); + } finally { + builder = KeyPair.builder().region(defaultRegion); + } } else if (qName.equals("keyName")) { - this.keyName = currentText.toString().trim(); + builder.keyName(currentOrNull(currentText)); } - currentText = new StringBuilder(); } diff --git a/apis/ec2/src/main/java/org/jclouds/ec2/xml/KeyPairResponseHandler.java b/apis/ec2/src/main/java/org/jclouds/ec2/xml/KeyPairResponseHandler.java index 43b161235f..de44b37da9 100644 --- a/apis/ec2/src/main/java/org/jclouds/ec2/xml/KeyPairResponseHandler.java +++ b/apis/ec2/src/main/java/org/jclouds/ec2/xml/KeyPairResponseHandler.java @@ -18,10 +18,13 @@ */ package org.jclouds.ec2.xml; +import static org.jclouds.util.SaxUtils.currentOrNull; + import javax.inject.Inject; import org.jclouds.aws.util.AWSUtils; import org.jclouds.ec2.domain.KeyPair; +import org.jclouds.ec2.domain.KeyPair.Builder; import org.jclouds.http.functions.ParseSax; import org.jclouds.location.Region; @@ -33,31 +36,36 @@ import org.jclouds.location.Region; * @author Adrian Cole */ public class KeyPairResponseHandler extends ParseSax.HandlerForGeneratedRequestWithResult { + private final String defaultRegion; + private Builder builder; + @Inject - @Region - String defaultRegion; + public KeyPairResponseHandler(@Region String defaultRegion) { + this.defaultRegion = defaultRegion; + builder = KeyPair.builder().region(defaultRegion); + } + private StringBuilder currentText = new StringBuilder(); - private String keyFingerprint; - private String keyMaterial; - private String keyName; public KeyPair getResult() { String region = AWSUtils.findRegionInArgsOrNull(getRequest()); - if (region == null) - region = defaultRegion; - return new KeyPair(region, keyName, keyFingerprint, keyMaterial); + if (region != null) + builder.region(region); + try { + return builder.build(); + } finally { + builder = KeyPair.builder().region(defaultRegion); + } } public void endElement(String uri, String name, String qName) { - if (qName.equals("keyFingerprint")) { - this.keyFingerprint = currentText.toString().trim(); + builder.sha1OfPrivateKey(currentOrNull(currentText)); } else if (qName.equals("keyMaterial")) { - this.keyMaterial = currentText.toString().trim(); + builder.keyMaterial(currentOrNull(currentText)); } else if (qName.equals("keyName")) { - this.keyName = currentText.toString().trim(); + builder.keyName(currentOrNull(currentText)); } - currentText = new StringBuilder(); } diff --git a/apis/ec2/src/test/java/org/jclouds/ec2/compute/strategy/CreateKeyPairAndSecurityGroupsAsNeededAndReturnRunOptionsTest.java b/apis/ec2/src/test/java/org/jclouds/ec2/compute/strategy/CreateKeyPairAndSecurityGroupsAsNeededAndReturnRunOptionsTest.java index 8a7f90c9fe..502491bc43 100644 --- a/apis/ec2/src/test/java/org/jclouds/ec2/compute/strategy/CreateKeyPairAndSecurityGroupsAsNeededAndReturnRunOptionsTest.java +++ b/apis/ec2/src/test/java/org/jclouds/ec2/compute/strategy/CreateKeyPairAndSecurityGroupsAsNeededAndReturnRunOptionsTest.java @@ -57,6 +57,34 @@ import com.google.common.collect.ImmutableSet; @Test(groups = "unit", singleThreaded = true, testName = "CreateKeyPairAndSecurityGroupsAsNeededAndReturnRunOptionsTest") public class CreateKeyPairAndSecurityGroupsAsNeededAndReturnRunOptionsTest { + public static final Credentials CREDENTIALS = new Credentials(null, "-----BEGIN RSA PRIVATE KEY-----\n" + + "MIIEowIBAAKCAQEA0CbFlhSdbMdad2ux2BVqk6Ut5fLKb0CdbqubGcEBfwsSz9Rp4Ile76P90MpV\n" + + "W1BGKL5V4MO+flG6dZnRWPVmgrNVyDTmEsALiMGjfEwbACEZ1A8C6mPa36wWO7MlxuyMjg8OczTB\n" + + "EXnHNDpxE5a6KowJtzFlmgjHk2Y+Q42UIqPx47lQUv5bdMDCnfNNomSzTVRjOZLUkDja+ybCKdux\n" + + "gqTsuInhuBRMx+wxff8Z43ECdJV6UPoXK3der1dlZunxGCFkCeYq0kCX7FZ7PV35X744jqhD8P+7\n" + + "y5prO4W+M3DWgChUx0OlbDbSHtDVlcfdbj/+4AKYKU6rQOqh+4DPDQIDAQABAoIBAHjQuEiXKJSV\n" + + "1U2RZcVtENInws9AL/2I/Jfa5Qh6vTqXG9EjklywfzkK72x7tDVvD3ngmAoAs5WwLFDL+fXvYhOk\n" + + "sbql8ZCahVdYRWME7XsSu2IZYHDZipXe1XzLS7b9X8uos5Ns4E8bZuNKtI1RJDdD1vPMqRNR2z0T\n" + + "0Dn3eC7t+t+t7PWaK5AXu2ot7DoOeG1QhqJbwd5pMkIn2ydBILytgmDk/2P3EtJGePIJIeQBicmw\n" + + "Z0KrJFa/K2cC8AtmMJUoZMo+mh1yemDbDLCZW30PjFHbZtcszS2cydAgq/HDFkZynvZG0zhbx/To\n" + + "jzcNza1AyypYwOwb2/9/ulXZp0UCgYEA+QFgWDfYLH2zwjU5b6e0UbIyd/X/yRZ+L8lOEBd0Bbu8\n" + + "qO3txaDbwi7o2mG7pJENHJ3u62CHjgTGDNW9V9Q8eNoGtj3uHvMvi7FdDEK8B6izdZyR7hmZmQ/5\n" + + "MIldelyiGZlz1KBSoy4FsCpA7hV7cI6H6x+Im24NxG90/wd/EgMCgYEA1f+cUyUisIO3yKOCf0hQ\n" + + "aL289q2//F2cbvBxtki6I8JzTg1H3oTO2WVrXQeCA3a/yiuRUatgGH4mxrpCF6byVJyqrEWAj4kU\n" + + "uTbhMgIYhLGoaF1e+vMirCRXUXox0i5X976ASzHn64V9JSd1B+UbKfpcFTYYnChmrRDzmhKN1a8C\n" + + "gYBTvIHAyO7ab18/BRUOllAOVSWhr8lXv0eqHEEzKh/rOaoFCRY3qpOcZpgJsGogumK1Z+sLnoeX\n" + + "W8WaVVp6KbY4UeGF8aedItyvVnLbB6ohzTqkZ4Wvk05S6cs75kXYO0SL5U3NiCiiFXz2NA9nwTOk\n" + + "s1nD2PPgiQ76Kx0mEkhKLwKBgFhHEJqv+AZu37Kx2NRe5WS/2KK9/DPD/hM5tv7mM3sq7Nvm2J3v\n" + + "lVDS6J5AyZ5aLzXcER9qncKcz6wtC7SsFs1Wr4VPSoBroRPikrVJbgnXK8yZr+O/xq7Scv7WdJTq\n" + + "rzkw6cWbObvLnltkUn/GQBVqBPBvF2nbtLdyBbuqKb5bAoGBAI1+aoJnvXEXxT4UHrMkQcY0eXRz\n" + + "3UdbzJmtjMW9CR6l9s11mV6PcZP4qnODp3nd6a+lPeL3wVYQ47DsTJ/Bx5dI17zA5mU57n6mV0a3\n" + + "DbSoPKSdaKTQdo2THnVE9P9sPKZWueAcsE4Yw/qcTjoxrtUnAH/AXN250v0tkKIOvMhu\n" + + "-----END RSA PRIVATE KEY-----"); + + public static final KeyPair KEYPAIR = KeyPair.builder().region(Region.AP_SOUTHEAST_1).keyName("myKeyPair") + .sha1OfPrivateKey("13:36:74:b9:56:bb:07:96:c0:19:ab:00:7f:9f:06:d2:16:a0:45:32").fingerprint( + "60:15:d1:f1:d9:e2:3c:e2:ee:a9:64:6a:42:a7:34:0c").keyMaterial(CREDENTIALS.credential).build(); + private static final Provider OPTIONS_PROVIDER = new javax.inject.Provider() { @Override @@ -69,7 +97,7 @@ public class CreateKeyPairAndSecurityGroupsAsNeededAndReturnRunOptionsTest { public void testExecuteWithDefaultOptionsEC2() throws SecurityException, NoSuchMethodException { // setup constants String region = Region.AP_SOUTHEAST_1; - String tag = "tag"; + String group = "group"; Hardware size = EC2HardwareBuilder.m1_small32().build(); String systemGeneratedKeyPairName = "systemGeneratedKeyPair"; String generatedGroup = "group"; @@ -95,9 +123,9 @@ public class CreateKeyPairAndSecurityGroupsAsNeededAndReturnRunOptionsTest { expect(template.getHardware()).andReturn(size).atLeastOnce(); expect(template.getOptions()).andReturn(options).atLeastOnce(); expect(options.getBlockDeviceMappings()).andReturn(ImmutableSet. of()).atLeastOnce(); - expect(strategy.createNewKeyPairUnlessUserSpecifiedOtherwise(region, tag, options)).andReturn( + expect(strategy.createNewKeyPairUnlessUserSpecifiedOtherwise(region, group, options)).andReturn( systemGeneratedKeyPairName); - expect(strategy.getSecurityGroupsForTagAndOptions(region, tag, options)).andReturn(generatedGroups); + expect(strategy.getSecurityGroupsForTagAndOptions(region, group, options)).andReturn(generatedGroups); expect(options.getUserData()).andReturn(null); // replay mocks @@ -106,7 +134,7 @@ public class CreateKeyPairAndSecurityGroupsAsNeededAndReturnRunOptionsTest { replay(strategy); // run - RunInstancesOptions customize = strategy.execute(region, tag, template); + RunInstancesOptions customize = strategy.execute(region, group, template); assertEquals(customize.buildQueryParameters(), ImmutableMultimap. of()); assertEquals(customize.buildFormParameters().entries(), ImmutableMultimap. of("InstanceType", size.getProviderId(), "SecurityGroup.1", generatedGroup, "KeyName", systemGeneratedKeyPairName) @@ -124,7 +152,7 @@ public class CreateKeyPairAndSecurityGroupsAsNeededAndReturnRunOptionsTest { public void testExecuteWithUserData() throws SecurityException, NoSuchMethodException { // setup constants String region = Region.AP_SOUTHEAST_1; - String tag = "tag"; + String group = "group"; Hardware size = EC2HardwareBuilder.m1_small32().build(); String systemGeneratedKeyPairName = "systemGeneratedKeyPair"; String generatedGroup = "group"; @@ -150,9 +178,9 @@ public class CreateKeyPairAndSecurityGroupsAsNeededAndReturnRunOptionsTest { expect(template.getHardware()).andReturn(size).atLeastOnce(); expect(template.getOptions()).andReturn(options).atLeastOnce(); expect(options.getBlockDeviceMappings()).andReturn(ImmutableSet. of()).atLeastOnce(); - expect(strategy.createNewKeyPairUnlessUserSpecifiedOtherwise(region, tag, options)).andReturn( + expect(strategy.createNewKeyPairUnlessUserSpecifiedOtherwise(region, group, options)).andReturn( systemGeneratedKeyPairName); - expect(strategy.getSecurityGroupsForTagAndOptions(region, tag, options)).andReturn(generatedGroups); + expect(strategy.getSecurityGroupsForTagAndOptions(region, group, options)).andReturn(generatedGroups); expect(options.getUserData()).andReturn("hello".getBytes()); // replay mocks @@ -161,7 +189,7 @@ public class CreateKeyPairAndSecurityGroupsAsNeededAndReturnRunOptionsTest { replay(strategy); // run - RunInstancesOptions customize = strategy.execute(region, tag, template); + RunInstancesOptions customize = strategy.execute(region, group, template); assertEquals(customize.buildQueryParameters(), ImmutableMultimap. of()); assertEquals(customize.buildFormParameters().entries(), ImmutableMultimap. of("InstanceType", size.getProviderId(), "SecurityGroup.1", "group", "KeyName", systemGeneratedKeyPairName, "UserData", @@ -179,7 +207,7 @@ public class CreateKeyPairAndSecurityGroupsAsNeededAndReturnRunOptionsTest { public void testCreateNewKeyPairUnlessUserSpecifiedOtherwise_reusesKeyWhenToldTo() { // setup constants String region = Region.AP_SOUTHEAST_1; - String tag = "tag"; + String group = "group"; String userSuppliedKeyPair = "myKeyPair"; // create mocks @@ -198,7 +226,7 @@ public class CreateKeyPairAndSecurityGroupsAsNeededAndReturnRunOptionsTest { replayStrategy(strategy); // run - assertEquals(strategy.createNewKeyPairUnlessUserSpecifiedOtherwise(region, tag, options), userSuppliedKeyPair); + assertEquals(strategy.createNewKeyPairUnlessUserSpecifiedOtherwise(region, group, options), userSuppliedKeyPair); // verify mocks verify(options); @@ -210,7 +238,7 @@ public class CreateKeyPairAndSecurityGroupsAsNeededAndReturnRunOptionsTest { public void testCreateNewKeyPairUnlessUserSpecifiedOtherwise_reusesKeyWhenToldToWithRunScriptButNoCredentials() { // setup constants String region = Region.AP_SOUTHEAST_1; - String tag = "tag"; + String group = "group"; String userSuppliedKeyPair = "myKeyPair"; // create mocks @@ -232,7 +260,7 @@ public class CreateKeyPairAndSecurityGroupsAsNeededAndReturnRunOptionsTest { replayStrategy(strategy); // run - assertEquals(strategy.createNewKeyPairUnlessUserSpecifiedOtherwise(region, tag, options), userSuppliedKeyPair); + assertEquals(strategy.createNewKeyPairUnlessUserSpecifiedOtherwise(region, group, options), userSuppliedKeyPair); // verify mocks verify(options); @@ -243,7 +271,7 @@ public class CreateKeyPairAndSecurityGroupsAsNeededAndReturnRunOptionsTest { public void testCreateNewKeyPairUnlessUserSpecifiedOtherwise_reusesKeyWhenToldToWithRunScriptAndCredentialsAlreadyInMap() { // setup constants String region = Region.AP_SOUTHEAST_1; - String tag = "tag"; + String group = "group"; String userSuppliedKeyPair = "myKeyPair"; // create mocks @@ -264,7 +292,7 @@ public class CreateKeyPairAndSecurityGroupsAsNeededAndReturnRunOptionsTest { replayStrategy(strategy); // run - assertEquals(strategy.createNewKeyPairUnlessUserSpecifiedOtherwise(region, tag, options), userSuppliedKeyPair); + assertEquals(strategy.createNewKeyPairUnlessUserSpecifiedOtherwise(region, group, options), userSuppliedKeyPair); // verify mocks verify(options); @@ -276,7 +304,7 @@ public class CreateKeyPairAndSecurityGroupsAsNeededAndReturnRunOptionsTest { public void testCreateNewKeyPairUnlessUserSpecifiedOtherwise_reusesKeyWhenToldToWithRunScriptAndCredentialsSpecified() { // setup constants String region = Region.AP_SOUTHEAST_1; - String tag = "tag"; + String group = "group"; String userSuppliedKeyPair = "myKeyPair"; // create mocks @@ -287,11 +315,11 @@ public class CreateKeyPairAndSecurityGroupsAsNeededAndReturnRunOptionsTest { // setup expectations expect(options.getKeyPair()).andReturn(userSuppliedKeyPair); - expect(options.getOverridingCredentials()).andReturn(new Credentials(null, "MyRsa")).atLeastOnce(); + expect(options.getOverridingCredentials()).andReturn(CREDENTIALS).atLeastOnce(); expect(strategy.credentialsMap.asMap()).andReturn(backing); - expect( - backing.put(new RegionAndName(region, userSuppliedKeyPair), KeyPair.builder().region(region).keyName( - userSuppliedKeyPair).keyFingerprint("//TODO").keyMaterial("MyRsa").build())).andReturn(null); + + // Notice that the fingerprint and sha1 generated + expect(backing.put(new RegionAndName(region, userSuppliedKeyPair), KEYPAIR)).andReturn(null); expect(options.getRunScript()).andReturn(Statements.exec("echo foo")); expect(strategy.credentialsMap.getUnchecked(new RegionAndName(region, userSuppliedKeyPair))).andReturn(keyPair); @@ -302,7 +330,7 @@ public class CreateKeyPairAndSecurityGroupsAsNeededAndReturnRunOptionsTest { replayStrategy(strategy); // run - assertEquals(strategy.createNewKeyPairUnlessUserSpecifiedOtherwise(region, tag, options), userSuppliedKeyPair); + assertEquals(strategy.createNewKeyPairUnlessUserSpecifiedOtherwise(region, group, options), userSuppliedKeyPair); // verify mocks verify(options); @@ -315,7 +343,7 @@ public class CreateKeyPairAndSecurityGroupsAsNeededAndReturnRunOptionsTest { throws ExecutionException { // setup constants String region = Region.AP_SOUTHEAST_1; - String tag = "tag"; + String group = "group"; String userSuppliedKeyPair = null; boolean shouldAutomaticallyCreateKeyPair = true; String systemGeneratedKeyPairName = "systemGeneratedKeyPair"; @@ -329,7 +357,7 @@ public class CreateKeyPairAndSecurityGroupsAsNeededAndReturnRunOptionsTest { expect(options.getKeyPair()).andReturn(userSuppliedKeyPair); expect(options.shouldAutomaticallyCreateKeyPair()).andReturn(shouldAutomaticallyCreateKeyPair); expect(keyPair.getKeyName()).andReturn(systemGeneratedKeyPairName).atLeastOnce(); - expect(strategy.credentialsMap.getUnchecked(new RegionAndName(region, tag))).andReturn(keyPair); + expect(strategy.credentialsMap.getUnchecked(new RegionAndName(region, group))).andReturn(keyPair); expect(options.getRunScript()).andReturn(null); // replay mocks @@ -338,7 +366,7 @@ public class CreateKeyPairAndSecurityGroupsAsNeededAndReturnRunOptionsTest { replayStrategy(strategy); // run - assertEquals(strategy.createNewKeyPairUnlessUserSpecifiedOtherwise(region, tag, options), + assertEquals(strategy.createNewKeyPairUnlessUserSpecifiedOtherwise(region, group, options), systemGeneratedKeyPairName); // verify mocks @@ -350,7 +378,7 @@ public class CreateKeyPairAndSecurityGroupsAsNeededAndReturnRunOptionsTest { public void testCreateNewKeyPairUnlessUserSpecifiedOtherwise_doesntCreateAKeyPairAndReturnsNullWhenToldNotTo() { // setup constants String region = Region.AP_SOUTHEAST_1; - String tag = "tag"; + String group = "group"; String userSuppliedKeyPair = null; boolean shouldAutomaticallyCreateKeyPair = false; // here's the important // part! @@ -371,7 +399,7 @@ public class CreateKeyPairAndSecurityGroupsAsNeededAndReturnRunOptionsTest { replayStrategy(strategy); // run - assertEquals(strategy.createNewKeyPairUnlessUserSpecifiedOtherwise(region, tag, options), null); + assertEquals(strategy.createNewKeyPairUnlessUserSpecifiedOtherwise(region, group, options), null); // verify mocks verify(options); @@ -383,8 +411,8 @@ public class CreateKeyPairAndSecurityGroupsAsNeededAndReturnRunOptionsTest { throws ExecutionException { // setup constants String region = Region.AP_SOUTHEAST_1; - String tag = "tag"; - String generatedMarkerGroup = "jclouds#tag#" + Region.AP_SOUTHEAST_1; + String group = "group"; + String generatedMarkerGroup = "jclouds#group#" + Region.AP_SOUTHEAST_1; Set groupIds = ImmutableSet. of(); int[] ports = new int[] {}; boolean shouldAuthorizeSelf = true; @@ -399,14 +427,14 @@ public class CreateKeyPairAndSecurityGroupsAsNeededAndReturnRunOptionsTest { expect(options.getInboundPorts()).andReturn(ports).atLeastOnce(); RegionNameAndIngressRules regionNameAndIngressRules = new RegionNameAndIngressRules(region, generatedMarkerGroup, ports, shouldAuthorizeSelf); - expect(strategy.securityGroupMap.getUnchecked(regionNameAndIngressRules)).andReturn(tag); + expect(strategy.securityGroupMap.getUnchecked(regionNameAndIngressRules)).andReturn(group); // replay mocks replay(options); replayStrategy(strategy); // run - assertEquals(strategy.getSecurityGroupsForTagAndOptions(region, tag, options), returnVal); + assertEquals(strategy.getSecurityGroupsForTagAndOptions(region, group, options), returnVal); // verify mocks verify(options); @@ -417,8 +445,8 @@ public class CreateKeyPairAndSecurityGroupsAsNeededAndReturnRunOptionsTest { throws ExecutionException { // setup constants String region = Region.AP_SOUTHEAST_1; - String tag = "tag"; - String generatedMarkerGroup = "jclouds#tag#" + Region.AP_SOUTHEAST_1; + String group = "group"; + String generatedMarkerGroup = "jclouds#group#" + Region.AP_SOUTHEAST_1; Set groupIds = ImmutableSet. of(); int[] ports = new int[] { 22, 80 }; boolean shouldAuthorizeSelf = true; @@ -440,7 +468,7 @@ public class CreateKeyPairAndSecurityGroupsAsNeededAndReturnRunOptionsTest { replayStrategy(strategy); // run - assertEquals(strategy.getSecurityGroupsForTagAndOptions(region, tag, options), returnVal); + assertEquals(strategy.getSecurityGroupsForTagAndOptions(region, group, options), returnVal); // verify mocks verify(options); @@ -451,8 +479,8 @@ public class CreateKeyPairAndSecurityGroupsAsNeededAndReturnRunOptionsTest { throws ExecutionException { // setup constants String region = Region.AP_SOUTHEAST_1; - String tag = "tag"; - String generatedMarkerGroup = "jclouds#tag#" + Region.AP_SOUTHEAST_1; + String group = "group"; + String generatedMarkerGroup = "jclouds#group#" + Region.AP_SOUTHEAST_1; Set groupIds = ImmutableSet. of(); int[] ports = new int[] {}; boolean shouldAuthorizeSelf = true; @@ -474,7 +502,7 @@ public class CreateKeyPairAndSecurityGroupsAsNeededAndReturnRunOptionsTest { replayStrategy(strategy); // run - assertEquals(strategy.getSecurityGroupsForTagAndOptions(region, tag, options), returnVal); + assertEquals(strategy.getSecurityGroupsForTagAndOptions(region, group, options), returnVal); // verify mocks verify(options); @@ -484,8 +512,8 @@ public class CreateKeyPairAndSecurityGroupsAsNeededAndReturnRunOptionsTest { public void testGetSecurityGroupsForTagAndOptions_reusesGroupByDefaultWhenNoPortsAreSpecifiedWhenDoesExistAndAcceptsUserSuppliedGroups() { // setup constants String region = Region.AP_SOUTHEAST_1; - String tag = "tag"; - String generatedMarkerGroup = "jclouds#tag#" + Region.AP_SOUTHEAST_1; + String group = "group"; + String generatedMarkerGroup = "jclouds#group#" + Region.AP_SOUTHEAST_1; Set groupIds = ImmutableSet. of("group1", "group2"); int[] ports = new int[] {}; boolean shouldAuthorizeSelf = true; @@ -501,14 +529,15 @@ public class CreateKeyPairAndSecurityGroupsAsNeededAndReturnRunOptionsTest { RegionNameAndIngressRules regionNameAndIngressRules = new RegionNameAndIngressRules(region, generatedMarkerGroup, ports, shouldAuthorizeSelf); - expect(strategy.securityGroupMap.getUnchecked(regionNameAndIngressRules)).andReturn(groupExisted ? "tag" : null); + expect(strategy.securityGroupMap.getUnchecked(regionNameAndIngressRules)) + .andReturn(groupExisted ? "group" : null); // replay mocks replay(options); replayStrategy(strategy); // run - assertEquals(strategy.getSecurityGroupsForTagAndOptions(region, tag, options), returnVal); + assertEquals(strategy.getSecurityGroupsForTagAndOptions(region, group, options), returnVal); // verify mocks verify(options); diff --git a/apis/ec2/src/test/java/org/jclouds/ec2/xml/DescribeKeyPairsResponseHandlerTest.java b/apis/ec2/src/test/java/org/jclouds/ec2/xml/DescribeKeyPairsResponseHandlerTest.java index 55dde87af9..d569f2b733 100644 --- a/apis/ec2/src/test/java/org/jclouds/ec2/xml/DescribeKeyPairsResponseHandlerTest.java +++ b/apis/ec2/src/test/java/org/jclouds/ec2/xml/DescribeKeyPairsResponseHandlerTest.java @@ -39,7 +39,7 @@ import com.google.common.collect.ImmutableSet; * * @author Adrian Cole */ -//NOTE:without testName, this will not call @Before* and fail w/NPE during surefire +// NOTE:without testName, this will not call @Before* and fail w/NPE during surefire @Test(groups = "unit", testName = "DescribeKeyPairsResponseHandlerTest") public class DescribeKeyPairsResponseHandlerTest extends BaseEC2HandlerTest { public void testApplyInputStream() { @@ -47,10 +47,9 @@ public class DescribeKeyPairsResponseHandlerTest extends BaseEC2HandlerTest { InputStream is = getClass().getResourceAsStream("/describe_keypairs.xml"); Set expected = ImmutableSet.of(new KeyPair(defaultRegion, "gsg-keypair", - "1f:51:ae:28:bf:89:e9:d8:1f:25:5d:37:2d:7d:b8:ca:9f:f5:f1:6f", null)); + "1f:51:ae:28:bf:89:e9:d8:1f:25:5d:37:2d:7d:b8:ca:9f:f5:f1:6f", null, null)); - DescribeKeyPairsResponseHandler handler = injector - .getInstance(DescribeKeyPairsResponseHandler.class); + DescribeKeyPairsResponseHandler handler = injector.getInstance(DescribeKeyPairsResponseHandler.class); addDefaultRegionToHandler(handler); Set result = factory.create(handler).parse(is); assertEquals(result, expected); @@ -58,7 +57,7 @@ public class DescribeKeyPairsResponseHandlerTest extends BaseEC2HandlerTest { private void addDefaultRegionToHandler(ParseSax.HandlerWithResult handler) { GeneratedHttpRequest request = createMock(GeneratedHttpRequest.class); - expect(request.getArgs()).andReturn(ImmutableList.of()); + expect(request.getArgs()).andReturn(ImmutableList. of()); replay(request); handler.setContext(request); } diff --git a/apis/ec2/src/test/java/org/jclouds/ec2/xml/KeyPairResponseHandlerTest.java b/apis/ec2/src/test/java/org/jclouds/ec2/xml/KeyPairResponseHandlerTest.java index 22688b38d5..bf29fef2ba 100644 --- a/apis/ec2/src/test/java/org/jclouds/ec2/xml/KeyPairResponseHandlerTest.java +++ b/apis/ec2/src/test/java/org/jclouds/ec2/xml/KeyPairResponseHandlerTest.java @@ -23,8 +23,10 @@ import static org.easymock.classextension.EasyMock.createMock; import static org.easymock.classextension.EasyMock.replay; import static org.testng.Assert.assertEquals; +import java.io.IOException; import java.io.InputStream; +import org.jclouds.crypto.SshKeys; import org.jclouds.ec2.domain.KeyPair; import org.jclouds.http.functions.ParseSax; import org.jclouds.rest.internal.GeneratedHttpRequest; @@ -37,51 +39,53 @@ import com.google.common.collect.ImmutableList; * * @author Adrian Cole */ -//NOTE:without testName, this will not call @Before* and fail w/NPE during surefire +// NOTE:without testName, this will not call @Before* and fail w/NPE during surefire @Test(groups = "unit", testName = "KeyPairResponseHandlerTest") public class KeyPairResponseHandlerTest extends BaseEC2HandlerTest { - public void testApplyInputStream() { + public void testApplyInputStream() throws IOException { InputStream is = getClass().getResourceAsStream("/create_keypair.xml"); - KeyPair expected = new KeyPair( - defaultRegion, - "gsg-keypair", - "1f:51:ae:28:bf:89:e9:d8:1f:25:5d:37:2d:7d:b8:ca:9f:f5:f1:6f", + KeyPair expected = KeyPair.builder().region(defaultRegion).keyName("adriancole-ec21").sha1OfPrivateKey( + "13:36:74:b9:56:bb:07:96:c0:19:ab:00:7f:9f:06:d2:16:a0:45:32").keyMaterial( "-----BEGIN RSA PRIVATE KEY-----\n" - + "MIIEoQIBAAKCAQBuLFg5ujHrtm1jnutSuoO8Xe56LlT+HM8v/xkaa39EstM3/aFxTHgElQiJLChp\n" - + "HungXQ29VTc8rc1bW0lkdi23OH5eqkMHGhvEwqa0HWASUMll4o3o/IX+0f2UcPoKCOVUR+jx71Sg\n" - + "5AU52EQfanIn3ZQ8lFW7Edp5a3q4DhjGlUKToHVbicL5E+g45zfB95wIyywWZfeW/UUF3LpGZyq/\n" - + "ebIUlq1qTbHkLbCC2r7RTn8vpQWp47BGVYGtGSBMpTRP5hnbzzuqj3itkiLHjU39S2sJCJ0TrJx5\n" - + "i8BygR4s3mHKBj8l+ePQxG1kGbF6R4yg6sECmXn17MRQVXODNHZbAgMBAAECggEAY1tsiUsIwDl5\n" - + "91CXirkYGuVfLyLflXenxfI50mDFms/mumTqloHO7tr0oriHDR5K7wMcY/YY5YkcXNo7mvUVD1pM\n" - + "ZNUJs7rw9gZRTrf7LylaJ58kOcyajw8TsC4e4LPbFaHwS1d6K8rXh64o6WgW4SrsB6ICmr1kGQI7\n" - + "3wcfgt5ecIu4TZf0OE9IHjn+2eRlsrjBdeORi7KiUNC/pAG23I6MdDOFEQRcCSigCj+4/mciFUSA\n" - + "SWS4dMbrpb9FNSIcf9dcLxVM7/6KxgJNfZc9XWzUw77Jg8x92Zd0fVhHOux5IZC+UvSKWB4dyfcI\n" - + "tE8C3p9bbU9VGyY5vLCAiIb4qQKBgQDLiO24GXrIkswF32YtBBMuVgLGCwU9h9HlO9mKAc2m8Cm1\n" - + "jUE5IpzRjTedc9I2qiIMUTwtgnw42auSCzbUeYMURPtDqyQ7p6AjMujp9EPemcSVOK9vXYL0Ptco\n" - + "xW9MC0dtV6iPkCN7gOqiZXPRKaFbWADp16p8UAIvS/a5XXk5jwKBgQCKkpHi2EISh1uRkhxljyWC\n" - + "iDCiK6JBRsMvpLbc0v5dKwP5alo1fmdR5PJaV2qvZSj5CYNpMAy1/EDNTY5OSIJU+0KFmQbyhsbm\n" - + "rdLNLDL4+TcnT7c62/aH01ohYaf/VCbRhtLlBfqGoQc7+sAc8vmKkesnF7CqCEKDyF/dhrxYdQKB\n" - + "gC0iZzzNAapayz1+JcVTwwEid6j9JqNXbBc+Z2YwMi+T0Fv/P/hwkX/ypeOXnIUcw0Ih/YtGBVAC\n" - + "DQbsz7LcY1HqXiHKYNWNvXgwwO+oiChjxvEkSdsTTIfnK4VSCvU9BxDbQHjdiNDJbL6oar92UN7V\n" - + "rBYvChJZF7LvUH4YmVpHAoGAbZ2X7XvoeEO+uZ58/BGKOIGHByHBDiXtzMhdJr15HTYjxK7OgTZm\n" - + "gK+8zp4L9IbvLGDMJO8vft32XPEWuvI8twCzFH+CsWLQADZMZKSsBasOZ/h1FwhdMgCMcY+Qlzd4\n" - + "JZKjTSu3i7vhvx6RzdSedXEMNTZWN4qlIx3kR5aHcukCgYA9T+Zrvm1F0seQPbLknn7EqhXIjBaT\n" - + "P8TTvW/6bdPi23ExzxZn7KOdrfclYRph1LHMpAONv/x2xALIf91UB+v5ohy1oDoasL0gij1houRe\n" - + "2ERKKdwz0ZL9SWq6VTdhr/5G994CK72fy5WhyERbDjUIdHaK3M849JJuf8cSrvSb4g==\n" - + "-----END RSA PRIVATE KEY-----"); + + "MIIEowIBAAKCAQEA0CbFlhSdbMdad2ux2BVqk6Ut5fLKb0CdbqubGcEBfwsSz9Rp4Ile76P90MpV\n" + + "W1BGKL5V4MO+flG6dZnRWPVmgrNVyDTmEsALiMGjfEwbACEZ1A8C6mPa36wWO7MlxuyMjg8OczTB\n" + + "EXnHNDpxE5a6KowJtzFlmgjHk2Y+Q42UIqPx47lQUv5bdMDCnfNNomSzTVRjOZLUkDja+ybCKdux\n" + + "gqTsuInhuBRMx+wxff8Z43ECdJV6UPoXK3der1dlZunxGCFkCeYq0kCX7FZ7PV35X744jqhD8P+7\n" + + "y5prO4W+M3DWgChUx0OlbDbSHtDVlcfdbj/+4AKYKU6rQOqh+4DPDQIDAQABAoIBAHjQuEiXKJSV\n" + + "1U2RZcVtENInws9AL/2I/Jfa5Qh6vTqXG9EjklywfzkK72x7tDVvD3ngmAoAs5WwLFDL+fXvYhOk\n" + + "sbql8ZCahVdYRWME7XsSu2IZYHDZipXe1XzLS7b9X8uos5Ns4E8bZuNKtI1RJDdD1vPMqRNR2z0T\n" + + "0Dn3eC7t+t+t7PWaK5AXu2ot7DoOeG1QhqJbwd5pMkIn2ydBILytgmDk/2P3EtJGePIJIeQBicmw\n" + + "Z0KrJFa/K2cC8AtmMJUoZMo+mh1yemDbDLCZW30PjFHbZtcszS2cydAgq/HDFkZynvZG0zhbx/To\n" + + "jzcNza1AyypYwOwb2/9/ulXZp0UCgYEA+QFgWDfYLH2zwjU5b6e0UbIyd/X/yRZ+L8lOEBd0Bbu8\n" + + "qO3txaDbwi7o2mG7pJENHJ3u62CHjgTGDNW9V9Q8eNoGtj3uHvMvi7FdDEK8B6izdZyR7hmZmQ/5\n" + + "MIldelyiGZlz1KBSoy4FsCpA7hV7cI6H6x+Im24NxG90/wd/EgMCgYEA1f+cUyUisIO3yKOCf0hQ\n" + + "aL289q2//F2cbvBxtki6I8JzTg1H3oTO2WVrXQeCA3a/yiuRUatgGH4mxrpCF6byVJyqrEWAj4kU\n" + + "uTbhMgIYhLGoaF1e+vMirCRXUXox0i5X976ASzHn64V9JSd1B+UbKfpcFTYYnChmrRDzmhKN1a8C\n" + + "gYBTvIHAyO7ab18/BRUOllAOVSWhr8lXv0eqHEEzKh/rOaoFCRY3qpOcZpgJsGogumK1Z+sLnoeX\n" + + "W8WaVVp6KbY4UeGF8aedItyvVnLbB6ohzTqkZ4Wvk05S6cs75kXYO0SL5U3NiCiiFXz2NA9nwTOk\n" + + "s1nD2PPgiQ76Kx0mEkhKLwKBgFhHEJqv+AZu37Kx2NRe5WS/2KK9/DPD/hM5tv7mM3sq7Nvm2J3v\n" + + "lVDS6J5AyZ5aLzXcER9qncKcz6wtC7SsFs1Wr4VPSoBroRPikrVJbgnXK8yZr+O/xq7Scv7WdJTq\n" + + "rzkw6cWbObvLnltkUn/GQBVqBPBvF2nbtLdyBbuqKb5bAoGBAI1+aoJnvXEXxT4UHrMkQcY0eXRz\n" + + "3UdbzJmtjMW9CR6l9s11mV6PcZP4qnODp3nd6a+lPeL3wVYQ47DsTJ/Bx5dI17zA5mU57n6mV0a3\n" + + "DbSoPKSdaKTQdo2THnVE9P9sPKZWueAcsE4Yw/qcTjoxrtUnAH/AXN250v0tkKIOvMhu\n" + + "-----END RSA PRIVATE KEY-----").build(); KeyPairResponseHandler handler = injector.getInstance(KeyPairResponseHandler.class); addDefaultRegionToHandler(handler); KeyPair result = factory.create(handler).parse(is); assertEquals(result, expected); + + assert SshKeys.privateKeyHasSha1(result.getKeyMaterial(), result.getSha1OfPrivateKey()); + assert SshKeys.privateKeyHasFingerprint(result.getKeyMaterial(), result.getFingerprint()); + } private void addDefaultRegionToHandler(ParseSax.HandlerWithResult handler) { GeneratedHttpRequest request = createMock(GeneratedHttpRequest.class); - expect(request.getArgs()).andReturn(ImmutableList.of()).atLeastOnce(); + expect(request.getArgs()).andReturn(ImmutableList. of()).atLeastOnce(); replay(request); handler.setContext(request); } diff --git a/apis/ec2/src/test/resources/create_keypair.xml b/apis/ec2/src/test/resources/create_keypair.xml index 07ad014d74..9aa005b3c0 100644 --- a/apis/ec2/src/test/resources/create_keypair.xml +++ b/apis/ec2/src/test/resources/create_keypair.xml @@ -1,27 +1,28 @@ - - gsg-keypair - 1f:51:ae:28:bf:89:e9:d8:1f:25:5d:37:2d:7d:b8:ca:9f:f5:f1:6f - -----BEGIN RSA PRIVATE KEY----- -MIIEoQIBAAKCAQBuLFg5ujHrtm1jnutSuoO8Xe56LlT+HM8v/xkaa39EstM3/aFxTHgElQiJLChp -HungXQ29VTc8rc1bW0lkdi23OH5eqkMHGhvEwqa0HWASUMll4o3o/IX+0f2UcPoKCOVUR+jx71Sg -5AU52EQfanIn3ZQ8lFW7Edp5a3q4DhjGlUKToHVbicL5E+g45zfB95wIyywWZfeW/UUF3LpGZyq/ -ebIUlq1qTbHkLbCC2r7RTn8vpQWp47BGVYGtGSBMpTRP5hnbzzuqj3itkiLHjU39S2sJCJ0TrJx5 -i8BygR4s3mHKBj8l+ePQxG1kGbF6R4yg6sECmXn17MRQVXODNHZbAgMBAAECggEAY1tsiUsIwDl5 -91CXirkYGuVfLyLflXenxfI50mDFms/mumTqloHO7tr0oriHDR5K7wMcY/YY5YkcXNo7mvUVD1pM -ZNUJs7rw9gZRTrf7LylaJ58kOcyajw8TsC4e4LPbFaHwS1d6K8rXh64o6WgW4SrsB6ICmr1kGQI7 -3wcfgt5ecIu4TZf0OE9IHjn+2eRlsrjBdeORi7KiUNC/pAG23I6MdDOFEQRcCSigCj+4/mciFUSA -SWS4dMbrpb9FNSIcf9dcLxVM7/6KxgJNfZc9XWzUw77Jg8x92Zd0fVhHOux5IZC+UvSKWB4dyfcI -tE8C3p9bbU9VGyY5vLCAiIb4qQKBgQDLiO24GXrIkswF32YtBBMuVgLGCwU9h9HlO9mKAc2m8Cm1 -jUE5IpzRjTedc9I2qiIMUTwtgnw42auSCzbUeYMURPtDqyQ7p6AjMujp9EPemcSVOK9vXYL0Ptco -xW9MC0dtV6iPkCN7gOqiZXPRKaFbWADp16p8UAIvS/a5XXk5jwKBgQCKkpHi2EISh1uRkhxljyWC -iDCiK6JBRsMvpLbc0v5dKwP5alo1fmdR5PJaV2qvZSj5CYNpMAy1/EDNTY5OSIJU+0KFmQbyhsbm -rdLNLDL4+TcnT7c62/aH01ohYaf/VCbRhtLlBfqGoQc7+sAc8vmKkesnF7CqCEKDyF/dhrxYdQKB -gC0iZzzNAapayz1+JcVTwwEid6j9JqNXbBc+Z2YwMi+T0Fv/P/hwkX/ypeOXnIUcw0Ih/YtGBVAC -DQbsz7LcY1HqXiHKYNWNvXgwwO+oiChjxvEkSdsTTIfnK4VSCvU9BxDbQHjdiNDJbL6oar92UN7V -rBYvChJZF7LvUH4YmVpHAoGAbZ2X7XvoeEO+uZ58/BGKOIGHByHBDiXtzMhdJr15HTYjxK7OgTZm -gK+8zp4L9IbvLGDMJO8vft32XPEWuvI8twCzFH+CsWLQADZMZKSsBasOZ/h1FwhdMgCMcY+Qlzd4 -JZKjTSu3i7vhvx6RzdSedXEMNTZWN4qlIx3kR5aHcukCgYA9T+Zrvm1F0seQPbLknn7EqhXIjBaT -P8TTvW/6bdPi23ExzxZn7KOdrfclYRph1LHMpAONv/x2xALIf91UB+v5ohy1oDoasL0gij1houRe -2ERKKdwz0ZL9SWq6VTdhr/5G994CK72fy5WhyERbDjUIdHaK3M849JJuf8cSrvSb4g== + + bb5e3a3c-e254-454d-ba29-d607cdf6357a + adriancole-ec21 + 13:36:74:b9:56:bb:07:96:c0:19:ab:00:7f:9f:06:d2:16:a0:45:32 + -----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEA0CbFlhSdbMdad2ux2BVqk6Ut5fLKb0CdbqubGcEBfwsSz9Rp4Ile76P90MpV +W1BGKL5V4MO+flG6dZnRWPVmgrNVyDTmEsALiMGjfEwbACEZ1A8C6mPa36wWO7MlxuyMjg8OczTB +EXnHNDpxE5a6KowJtzFlmgjHk2Y+Q42UIqPx47lQUv5bdMDCnfNNomSzTVRjOZLUkDja+ybCKdux +gqTsuInhuBRMx+wxff8Z43ECdJV6UPoXK3der1dlZunxGCFkCeYq0kCX7FZ7PV35X744jqhD8P+7 +y5prO4W+M3DWgChUx0OlbDbSHtDVlcfdbj/+4AKYKU6rQOqh+4DPDQIDAQABAoIBAHjQuEiXKJSV +1U2RZcVtENInws9AL/2I/Jfa5Qh6vTqXG9EjklywfzkK72x7tDVvD3ngmAoAs5WwLFDL+fXvYhOk +sbql8ZCahVdYRWME7XsSu2IZYHDZipXe1XzLS7b9X8uos5Ns4E8bZuNKtI1RJDdD1vPMqRNR2z0T +0Dn3eC7t+t+t7PWaK5AXu2ot7DoOeG1QhqJbwd5pMkIn2ydBILytgmDk/2P3EtJGePIJIeQBicmw +Z0KrJFa/K2cC8AtmMJUoZMo+mh1yemDbDLCZW30PjFHbZtcszS2cydAgq/HDFkZynvZG0zhbx/To +jzcNza1AyypYwOwb2/9/ulXZp0UCgYEA+QFgWDfYLH2zwjU5b6e0UbIyd/X/yRZ+L8lOEBd0Bbu8 +qO3txaDbwi7o2mG7pJENHJ3u62CHjgTGDNW9V9Q8eNoGtj3uHvMvi7FdDEK8B6izdZyR7hmZmQ/5 +MIldelyiGZlz1KBSoy4FsCpA7hV7cI6H6x+Im24NxG90/wd/EgMCgYEA1f+cUyUisIO3yKOCf0hQ +aL289q2//F2cbvBxtki6I8JzTg1H3oTO2WVrXQeCA3a/yiuRUatgGH4mxrpCF6byVJyqrEWAj4kU +uTbhMgIYhLGoaF1e+vMirCRXUXox0i5X976ASzHn64V9JSd1B+UbKfpcFTYYnChmrRDzmhKN1a8C +gYBTvIHAyO7ab18/BRUOllAOVSWhr8lXv0eqHEEzKh/rOaoFCRY3qpOcZpgJsGogumK1Z+sLnoeX +W8WaVVp6KbY4UeGF8aedItyvVnLbB6ohzTqkZ4Wvk05S6cs75kXYO0SL5U3NiCiiFXz2NA9nwTOk +s1nD2PPgiQ76Kx0mEkhKLwKBgFhHEJqv+AZu37Kx2NRe5WS/2KK9/DPD/hM5tv7mM3sq7Nvm2J3v +lVDS6J5AyZ5aLzXcER9qncKcz6wtC7SsFs1Wr4VPSoBroRPikrVJbgnXK8yZr+O/xq7Scv7WdJTq +rzkw6cWbObvLnltkUn/GQBVqBPBvF2nbtLdyBbuqKb5bAoGBAI1+aoJnvXEXxT4UHrMkQcY0eXRz +3UdbzJmtjMW9CR6l9s11mV6PcZP4qnODp3nd6a+lPeL3wVYQ47DsTJ/Bx5dI17zA5mU57n6mV0a3 +DbSoPKSdaKTQdo2THnVE9P9sPKZWueAcsE4Yw/qcTjoxrtUnAH/AXN250v0tkKIOvMhu -----END RSA PRIVATE KEY----- \ No newline at end of file diff --git a/core/src/main/java/org/jclouds/crypto/CryptoStreams.java b/core/src/main/java/org/jclouds/crypto/CryptoStreams.java index 4130857725..ad3ae8e280 100644 --- a/core/src/main/java/org/jclouds/crypto/CryptoStreams.java +++ b/core/src/main/java/org/jclouds/crypto/CryptoStreams.java @@ -140,7 +140,38 @@ public class CryptoStreams { } }); } + + /** + * Computes and returns the SHA1 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 + * sha1 object with all of the bytes in the stream + * @throws IOException + * if an I/O error occurs + */ + public static byte[] sha1(InputSupplier supplier) throws IOException { + try { + return digest(supplier, MessageDigest.getInstance("SHA1")); + } catch (NoSuchAlgorithmException e) { + Throwables.propagate(e); + return null; + } + } + public static byte[] sha1(byte[] in) { + try { + return sha1(ByteStreams.newInputStreamSupplier(in)); + } catch (IOException e) { + Throwables.propagate(e); + return null; + } + } + /** * Computes and returns the MD5 value for a supplied input stream. A digest * object is created and disposed of at runtime, consider using diff --git a/core/src/main/java/org/jclouds/crypto/Pems.java b/core/src/main/java/org/jclouds/crypto/Pems.java index 1ce08bfec8..76b8bb6ae4 100644 --- a/core/src/main/java/org/jclouds/crypto/Pems.java +++ b/core/src/main/java/org/jclouds/crypto/Pems.java @@ -316,7 +316,7 @@ public class Pems { } // TODO find a way to do this without using bouncycastle - static byte[] getEncoded(RSAPrivateCrtKey key) { + public static byte[] getEncoded(RSAPrivateCrtKey key) { RSAPrivateKeyStructure keyStruct = new RSAPrivateKeyStructure(key.getModulus(), key.getPublicExponent(), key.getPrivateExponent(), key.getPrimeP(), key.getPrimeQ(), key.getPrimeExponentP(), key.getPrimeExponentQ(), key.getCrtCoefficient()); diff --git a/core/src/main/java/org/jclouds/crypto/SshKeys.java b/core/src/main/java/org/jclouds/crypto/SshKeys.java index ba10c4af71..03ab33c9e7 100644 --- a/core/src/main/java/org/jclouds/crypto/SshKeys.java +++ b/core/src/main/java/org/jclouds/crypto/SshKeys.java @@ -31,12 +31,14 @@ import java.io.IOException; import java.io.InputStream; import java.io.StringWriter; import java.math.BigInteger; +import java.security.KeyFactory; 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.InvalidKeySpecException; import java.security.spec.KeySpec; import java.security.spec.RSAPrivateCrtKeySpec; import java.security.spec.RSAPublicKeySpec; @@ -52,8 +54,8 @@ 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.collect.Iterables; +import com.google.common.collect.ImmutableMap.Builder; import com.google.common.io.InputSupplier; /** @@ -68,36 +70,38 @@ import com.google.common.io.InputSupplier; public class SshKeys { /** - * Executes {@link Pems#publicKeySpecFromOpenSSH(InputSupplier)} on the - * string which was OpenSSH Base64 Encoded {@code id_rsa.pub} + * 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)); + public static RSAPublicKeySpec publicKeySpecFromOpenSSH(String idRsaPub) { + try { + return publicKeySpecFromOpenSSH(InputSuppliers.of(idRsaPub)); + } catch (IOException e) { + propagate(e); + return null; + } } /** - * Returns {@link RSAPublicKeySpec} which was OpenSSH Base64 Encoded - * {@code id_rsa.pub} + * Returns {@link RSAPublicKeySpec} which was OpenSSH Base64 Encoded {@code id_rsa.pub} * * @param supplier - * the input stream factory, formatted - * {@code ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAB...} + * the input stream factory, formatted {@code ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAB...} * - * @return the {@link RSAPublicKeySpec} which was OpenSSH Base64 Encoded - * {@code id_rsa.pub} + * @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 supplier) - throws IOException { + 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..."); + "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); @@ -132,8 +136,7 @@ public class SshKeys { } /** - * return a "public" -> rsa public key, "private" -> its corresponding - * private key + * return a "public" -> rsa public key, "private" -> its corresponding private key */ public static Map generate() { try { @@ -177,39 +180,142 @@ public class SshKeys { * RSA private key in PEM format * @param publicKeyOpenSSH * RSA public key in OpenSSH format - * @return true if the keypair are matches + * @return true if the keypairs match */ public static boolean privateKeyMatchesPublicKey(String privateKeyPEM, String publicKeyOpenSSH) { - try { - KeySpec privateKeySpec = privateKeySpec(privateKeyPEM); - checkArgument(privateKeySpec instanceof RSAPrivateCrtKeySpec, + KeySpec privateKeySpec = privateKeySpec(privateKeyPEM); + checkArgument(privateKeySpec instanceof RSAPrivateCrtKeySpec, "incorrect format expected RSAPrivateCrtKeySpec was %s", privateKeySpec); - return privateKeyMatchesPublicKey(RSAPrivateCrtKeySpec.class.cast(privateKeySpec), + return privateKeyMatchesPublicKey(RSAPrivateCrtKeySpec.class.cast(privateKeySpec), publicKeySpecFromOpenSSH(publicKeyOpenSSH)); - } catch (IOException e) { - propagate(e); - return false; - } } /** - * @return true if the keypair are matches + * @return true if the keypairs match */ public static boolean privateKeyMatchesPublicKey(RSAPrivateCrtKeySpec privateKey, RSAPublicKeySpec publicKey) { return privateKey.getPublicExponent().equals(publicKey.getPublicExponent()) - && privateKey.getModulus().equals(publicKey.getModulus()); + && privateKey.getModulus().equals(publicKey.getModulus()); } /** - * Create a fingerprint per the following spec + * @return true if the keypair has the same fingerprint as supplied + */ + public static boolean privateKeyHasFingerprint(RSAPrivateCrtKeySpec privateKey, String fingerprint) { + return fingerprint(privateKey.getPublicExponent(), privateKey.getModulus()).equals(fingerprint); + } + + /** + * @param privateKeyPEM + * RSA private key in PEM format + * @param fingerprint + * ex. {@code 2b:a9:62:95:5b:8b:1d:61:e0:92:f7:03:10:e9:db:d9} + * @return true if the keypair has the same fingerprint as supplied + */ + public static boolean privateKeyHasFingerprint(String privateKeyPEM, String fingerprint) { + KeySpec privateKeySpec = privateKeySpec(privateKeyPEM); + checkArgument(privateKeySpec instanceof RSAPrivateCrtKeySpec, + "incorrect format expected RSAPrivateCrtKeySpec was %s", privateKeySpec); + return privateKeyHasFingerprint(RSAPrivateCrtKeySpec.class.cast(privateKeySpec), fingerprint); + } + + /** + * @param privateKeyPEM + * RSA private key in PEM format + * @return fingerprint ex. {@code 2b:a9:62:95:5b:8b:1d:61:e0:92:f7:03:10:e9:db:d9} + */ + public static String fingerprintPrivateKey(String privateKeyPEM) { + KeySpec privateKeySpec = privateKeySpec(privateKeyPEM); + checkArgument(privateKeySpec instanceof RSAPrivateCrtKeySpec, + "incorrect format expected RSAPrivateCrtKeySpec was %s", privateKeySpec); + RSAPrivateCrtKeySpec certKeySpec = RSAPrivateCrtKeySpec.class.cast(privateKeySpec); + return fingerprint(certKeySpec.getPublicExponent(), certKeySpec.getModulus()); + } + + /** + * @return true if the keypair has the same SHA1 fingerprint as supplied + */ + public static boolean privateKeyHasSha1(RSAPrivateCrtKeySpec privateKey, String fingerprint) { + return sha1(privateKey).equals(fingerprint); + } + + /** + * @param privateKeyPEM + * RSA private key in PEM format + * @param sha1HexColonDelimited + * ex. {@code 2b:a9:62:95:5b:8b:1d:61:e0:92:f7:03:10:e9:db:d9} + * @return true if the keypair has the same fingerprint as supplied + */ + public static boolean privateKeyHasSha1(String privateKeyPEM, String sha1HexColonDelimited) { + KeySpec privateKeySpec = privateKeySpec(privateKeyPEM); + checkArgument(privateKeySpec instanceof RSAPrivateCrtKeySpec, + "incorrect format expected RSAPrivateCrtKeySpec was %s", privateKeySpec); + return privateKeyHasSha1(RSAPrivateCrtKeySpec.class.cast(privateKeySpec), sha1HexColonDelimited); + } + + /** + * @param privateKeyPEM + * RSA private key in PEM format + * @return sha1HexColonDelimited ex. {@code 2b:a9:62:95:5b:8b:1d:61:e0:92:f7:03:10:e9:db:d9} + */ + public static String sha1PrivateKey(String privateKeyPEM) { + KeySpec privateKeySpec = privateKeySpec(privateKeyPEM); + checkArgument(privateKeySpec instanceof RSAPrivateCrtKeySpec, + "incorrect format expected RSAPrivateCrtKeySpec was %s", privateKeySpec); + RSAPrivateCrtKeySpec certKeySpec = RSAPrivateCrtKeySpec.class.cast(privateKeySpec); + return sha1(certKeySpec); + } + + /** + * Create a SHA-1 digest of the DER encoded private key. * * @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} + * @return hex sha1HexColonDelimited ex. {@code 2b:a9:62:95:5b:8b:1d:61:e0:92:f7:03:10:e9:db:d9} + */ + public static String sha1(RSAPrivateCrtKeySpec privateKey) { + try { + String sha1 = Joiner.on(":").join( + Splitter.fixedLength(2).split( + hex(CryptoStreams.sha1(KeyFactory.getInstance("RSA").generatePrivate(privateKey) + .getEncoded())))); + return sha1; + } catch (InvalidKeySpecException e) { + propagate(e); + return null; + } catch (NoSuchAlgorithmException e) { + propagate(e); + return null; + } + } + + /** + * @return true if the keypair has the same fingerprint as supplied + */ + public static boolean publicKeyHasFingerprint(RSAPublicKeySpec publicKey, String fingerprint) { + return fingerprint(publicKey.getPublicExponent(), publicKey.getModulus()).equals(fingerprint); + } + + /** + * @param publicKeyOpenSSH + * RSA public key in OpenSSH format + * @param fingerprint + * ex. {@code 2b:a9:62:95:5b:8b:1d:61:e0:92:f7:03:10:e9:db:d9} + * @return true if the keypair has the same fingerprint as supplied + */ + public static boolean publicKeyHasFingerprint(String publicKeyOpenSSH, String fingerprint) { + return publicKeyHasFingerprint(publicKeySpecFromOpenSSH(publicKeyOpenSSH), fingerprint); + } + + /** + * 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); diff --git a/core/src/test/java/org/jclouds/crypto/SshKeysTest.java b/core/src/test/java/org/jclouds/crypto/SshKeysTest.java index f88d4cc004..e027e954b8 100644 --- a/core/src/test/java/org/jclouds/crypto/SshKeysTest.java +++ b/core/src/test/java/org/jclouds/crypto/SshKeysTest.java @@ -20,7 +20,9 @@ package org.jclouds.crypto; import static org.jclouds.crypto.SshKeys.fingerprint; import static org.jclouds.crypto.SshKeys.generate; +import static org.jclouds.crypto.SshKeys.privateKeyHasFingerprint; import static org.jclouds.crypto.SshKeys.privateKeyMatchesPublicKey; +import static org.jclouds.crypto.SshKeys.privateKeyHasSha1; import static org.jclouds.crypto.SshKeys.publicKeySpecFromOpenSSH; import static org.testng.Assert.assertEquals; @@ -45,6 +47,7 @@ import org.testng.annotations.Test; public class SshKeysTest { String expectedFingerprint = "2b:a9:62:95:5b:8b:1d:61:e0:92:f7:03:10:e9:db:d9"; + String expectedSha1 = "c8:01:34:c0:3c:8c:91:ac:e1:da:cf:72:15:d7:f2:e5:99:5b:28:d4"; @Test public void testCanReadRsaAndCompareFingerprintOnPublicRSAKey() throws IOException { @@ -62,6 +65,32 @@ public class SshKeysTest { assertEquals(fingerPrint, expectedFingerprint); } + @Test + public void testPrivateKeyMatchesFingerprintTyped() throws IOException { + String privKey = Strings2.toStringAndClose(getClass().getResourceAsStream("/test")); + RSAPrivateCrtKeySpec privateKey = (RSAPrivateCrtKeySpec) Pems.privateKeySpec(privKey); + assert privateKeyHasFingerprint(privateKey, expectedFingerprint); + } + + @Test + public void testPrivateKeyMatchesFingerprintString() throws IOException { + String privKey = Strings2.toStringAndClose(getClass().getResourceAsStream("/test")); + assert privateKeyHasFingerprint(privKey, expectedFingerprint); + } + + @Test + public void testPrivateKeyMatchesSha1Typed() throws IOException { + String privKey = Strings2.toStringAndClose(getClass().getResourceAsStream("/test")); + RSAPrivateCrtKeySpec privateKey = (RSAPrivateCrtKeySpec) Pems.privateKeySpec(privKey); + assert privateKeyHasSha1(privateKey, expectedSha1); + } + + @Test + public void testPrivateKeyMatchesSha1String() throws IOException { + String privKey = Strings2.toStringAndClose(getClass().getResourceAsStream("/test")); + assert privateKeyHasSha1(privKey, expectedSha1); + } + @Test public void testPrivateKeyMatchesPublicKeyTyped() throws IOException { String privKey = Strings2.toStringAndClose(getClass().getResourceAsStream("/test")); @@ -90,7 +119,7 @@ public class SshKeysTest { @Test public void testEncodeAsOpenSSH() throws IOException, InvalidKeySpecException, NoSuchAlgorithmException { String encoded = SshKeys.encodeAsOpenSSH((RSAPublicKey) KeyFactory.getInstance("RSA").generatePublic( - SshKeys.publicKeySpecFromOpenSSH(Payloads.newPayload(getClass().getResourceAsStream("/test.pub"))))); + SshKeys.publicKeySpecFromOpenSSH(Payloads.newPayload(getClass().getResourceAsStream("/test.pub"))))); assertEquals(encoded, Strings2.toStringAndClose(getClass().getResourceAsStream("/test.pub")).trim()); } diff --git a/drivers/sshj/src/main/java/org/jclouds/sshj/SshjSshClient.java b/drivers/sshj/src/main/java/org/jclouds/sshj/SshjSshClient.java index 09731de7e7..7094fb954b 100644 --- a/drivers/sshj/src/main/java/org/jclouds/sshj/SshjSshClient.java +++ b/drivers/sshj/src/main/java/org/jclouds/sshj/SshjSshClient.java @@ -25,11 +25,11 @@ import static com.google.common.base.Predicates.instanceOf; import static com.google.common.base.Predicates.or; import static com.google.common.base.Throwables.getCausalChain; import static com.google.common.collect.Iterables.any; -import static org.jclouds.crypto.SshKeys.fingerprint; +import static org.jclouds.crypto.SshKeys.fingerprintPrivateKey; +import static org.jclouds.crypto.SshKeys.sha1PrivateKey; import java.io.IOException; import java.io.InputStream; -import java.security.spec.RSAPrivateCrtKeySpec; import java.util.concurrent.TimeUnit; import javax.annotation.PostConstruct; @@ -50,7 +50,6 @@ import net.schmizz.sshj.xfer.InMemorySourceFile; import org.apache.commons.io.input.ProxyInputStream; import org.jclouds.compute.domain.ExecResponse; -import org.jclouds.crypto.Pems; import org.jclouds.http.handlers.BackoffLimitedRetryHandler; import org.jclouds.io.Payload; import org.jclouds.io.Payloads; @@ -96,17 +95,17 @@ public class SshjSshClient implements SshClient { private final String username; private final String password; private final String toString; - + @Inject(optional = true) @Named("jclouds.ssh.max-retries") @VisibleForTesting int sshRetries = 5; - + @Inject(optional = true) @Named("jclouds.ssh.retry-auth") @VisibleForTesting boolean retryAuth; - + @Inject(optional = true) @Named("jclouds.ssh.retryable-messages") @VisibleForTesting @@ -116,7 +115,7 @@ public class SshjSshClient implements SshClient { @Named("jclouds.ssh.retry-predicate") // NOTE cannot retry io exceptions, as SSHException is a part of the chain private Predicate retryPredicate = or(instanceOf(ConnectionException.class), - instanceOf(TransportException.class)); + instanceOf(TransportException.class)); @Resource @Named("jclouds.ssh") @@ -129,7 +128,7 @@ public class SshjSshClient implements SshClient { private final BackoffLimitedRetryHandler backoffLimitedRetryHandler; public SshjSshClient(BackoffLimitedRetryHandler backoffLimitedRetryHandler, IPSocket socket, int timeout, - String username, String password, byte[] privateKey) { + String username, String password, byte[] privateKey) { this.host = checkNotNull(socket, "socket").getAddress(); checkArgument(socket.getPort() > 0, "ssh port must be greater then zero" + socket.getPort()); checkArgument(password != null || privateKey != null, "you must specify a password or a key"); @@ -140,11 +139,12 @@ public class SshjSshClient implements SshClient { this.password = password; this.privateKey = privateKey; if (privateKey == null) { - this.toString = String.format("%s@%s:%d", username, host, port); + this.toString = String.format("%s:password@%s:%d", username, host, port); } else { - RSAPrivateCrtKeySpec key = (RSAPrivateCrtKeySpec) Pems.privateKeySpec(new String(privateKey)); - String fingerPrint = fingerprint(key.getPublicExponent(), key.getModulus()); - this.toString = String.format("%s:[%s]@%s:%d", username, fingerPrint, host, port); + String fingerPrint = fingerprintPrivateKey(new String(privateKey)); + String sha1 = sha1PrivateKey(new String(privateKey)); + this.toString = String.format("%s:rsa[fingerprint(%s),sha1(%s)]@%s:%d", username, fingerPrint, sha1, host, + port); } } @@ -198,7 +198,7 @@ public class SshjSshClient implements SshClient { @Override public String toString() { - return String.format("SSHClient(%s)", SshjSshClient.this.toString()); + return String.format("SSHClient(timeout=%d)", timeoutMillis); } }; @@ -266,7 +266,7 @@ public class SshjSshClient implements SshClient { @Override public String toString() { - return "SFTPClient(" + SshjSshClient.this.toString() + ")"; + return "SFTPClient()"; } }; @@ -288,12 +288,12 @@ public class SshjSshClient implements SshClient { public Payload create() throws Exception { sftp = acquire(sftpConnection); return Payloads.newInputStreamPayload(new CloseFtpChannelOnCloseInputStream(sftp.getSFTPEngine().open(path) - .getInputStream(), sftp)); + .getInputStream(), sftp)); } @Override public String toString() { - return "Payload(" + SshjSshClient.this.toString() + ")[" + path + "]"; + return "Payload(path=[" + path + "])"; } }; @@ -351,7 +351,7 @@ public class SshjSshClient implements SshClient { @Override public String toString() { - return "Put(" + SshjSshClient.this.toString() + ")[" + path + "]"; + return "Put(path=[" + path + "])"; } }; @@ -362,8 +362,8 @@ public class SshjSshClient implements SshClient { @VisibleForTesting boolean shouldRetry(Exception from) { - Predicate predicate = retryAuth ? Predicates.or(retryPredicate, instanceOf(AuthorizationException.class)) - : retryPredicate; + Predicate predicate = retryAuth ? Predicates. or(retryPredicate, + instanceOf(AuthorizationException.class)) : retryPredicate; if (any(getCausalChain(from), predicate)) return true; if (!retryableMessages.equals("")) @@ -397,12 +397,12 @@ public class SshjSshClient implements SshClient { if (e instanceof UserAuthException) throw new AuthorizationException("(" + toString() + ") " + message, e); throw e instanceof SshException ? SshException.class.cast(e) : new SshException( - "(" + toString() + ") " + message, e); + "(" + toString() + ") " + message, e); } @Override public String toString() { - return toString ; + return toString; } @PreDestroy @@ -436,7 +436,7 @@ public class SshjSshClient implements SshClient { @Override public String toString() { - return "Session(" + SshjSshClient.this.toString() + ")"; + return "Session()"; } }; @@ -473,7 +473,7 @@ public class SshjSshClient implements SshClient { @Override public String toString() { - return "ExecResponse(" + SshjSshClient.this.toString() + ")[" + command + "]"; + return "ExecResponse(command=[" + command + "])"; } } diff --git a/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/compute/strategy/CreateKeyPairPlacementAndSecurityGroupsAsNeededAndReturnRunOptionsTest.java b/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/compute/strategy/CreateKeyPairPlacementAndSecurityGroupsAsNeededAndReturnRunOptionsTest.java index 76cd77facd..769b249944 100644 --- a/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/compute/strategy/CreateKeyPairPlacementAndSecurityGroupsAsNeededAndReturnRunOptionsTest.java +++ b/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/compute/strategy/CreateKeyPairPlacementAndSecurityGroupsAsNeededAndReturnRunOptionsTest.java @@ -23,6 +23,8 @@ import static org.easymock.classextension.EasyMock.createMock; import static org.easymock.classextension.EasyMock.replay; import static org.easymock.classextension.EasyMock.verify; import static org.jclouds.aws.ec2.compute.AWSEC2TemplateOptions.Builder.keyPair; +import static org.jclouds.ec2.compute.strategy.CreateKeyPairAndSecurityGroupsAsNeededAndReturnRunOptionsTest.CREDENTIALS; +import static org.jclouds.ec2.compute.strategy.CreateKeyPairAndSecurityGroupsAsNeededAndReturnRunOptionsTest.KEYPAIR; import static org.testng.Assert.assertEquals; import java.lang.reflect.Method; @@ -471,10 +473,8 @@ public class CreateKeyPairPlacementAndSecurityGroupsAsNeededAndReturnRunOptionsT expect(backing.containsKey(new RegionAndName(region, group))).andReturn(false); expect(options.getKeyPair()).andReturn(userSuppliedKeyPair); expect(options.getPublicKey()).andReturn(null).times(2); - expect(options.getOverridingCredentials()).andReturn(new Credentials(null, "MyRsa")).atLeastOnce(); - expect( - backing.put(new RegionAndName(region, userSuppliedKeyPair), KeyPair.builder().region(region).keyName( - userSuppliedKeyPair).keyFingerprint("//TODO").keyMaterial("MyRsa").build())).andReturn(null); + expect(options.getOverridingCredentials()).andReturn(CREDENTIALS).atLeastOnce(); + expect(backing.put(new RegionAndName(region, userSuppliedKeyPair), KEYPAIR)).andReturn(null); expect(options.getRunScript()).andReturn(Statements.exec("echo foo")); expect(strategy.credentialsMap.getUnchecked(new RegionAndName(region, userSuppliedKeyPair))).andReturn(keyPair); @@ -504,17 +504,19 @@ public class CreateKeyPairPlacementAndSecurityGroupsAsNeededAndReturnRunOptionsT // create mocks CreateKeyPairPlacementAndSecurityGroupsAsNeededAndReturnRunOptions strategy = setupStrategy(); AWSEC2TemplateOptions options = keyPair(group).authorizePublicKey("ssh-rsa").overrideCredentialsWith( - new Credentials("foo", "private")); - KeyPair keyPair = new KeyPair(region, group, "//TODO", null); + new Credentials("foo", CREDENTIALS.credential)); + KeyPair keyPair = new KeyPair(region, group, "//TODO", null, null); ConcurrentMap backing = createMock(ConcurrentMap.class); // setup expectations expect(strategy.credentialsMap.asMap()).andReturn(backing).atLeastOnce(); expect(backing.containsKey(new RegionAndName(region, group))).andReturn(false); - expect(strategy.importExistingKeyPair.apply(new RegionNameAndPublicKeyMaterial(region, group, "private"))) - .andReturn(keyPair); - expect(backing.put(new RegionAndName(region, group), keyPair.toBuilder().keyMaterial("private").build())) - .andReturn(null); + expect( + strategy.importExistingKeyPair.apply(new RegionNameAndPublicKeyMaterial(region, group, + CREDENTIALS.credential))).andReturn(keyPair); + expect( + backing.put(new RegionAndName(region, group), keyPair.toBuilder().keyMaterial(CREDENTIALS.credential) + .build())).andReturn(null); // replay mocks replay(backing); @@ -539,7 +541,7 @@ public class CreateKeyPairPlacementAndSecurityGroupsAsNeededAndReturnRunOptionsT // create mocks CreateKeyPairPlacementAndSecurityGroupsAsNeededAndReturnRunOptions strategy = setupStrategy(); - KeyPair keyPair = new KeyPair(region, "jclouds#" + group, "fingerprint", null); + KeyPair keyPair = new KeyPair(region, "jclouds#" + group, "fingerprint", null, null); ConcurrentMap backing = createMock(ConcurrentMap.class);