diff --git a/apis/cloudstack/src/main/java/org/jclouds/cloudstack/domain/EncryptedPasswordAndPrivateKey.java b/apis/cloudstack/src/main/java/org/jclouds/cloudstack/domain/EncryptedPasswordAndPrivateKey.java new file mode 100644 index 0000000000..51b3a116af --- /dev/null +++ b/apis/cloudstack/src/main/java/org/jclouds/cloudstack/domain/EncryptedPasswordAndPrivateKey.java @@ -0,0 +1,103 @@ +/** + * Licensed to jclouds, Inc. (jclouds) under one or more + * contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. jclouds licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jclouds.cloudstack.domain; + +/** + * @author Andrei Savu + */ +public class EncryptedPasswordAndPrivateKey { + + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + private String encryptedPassword; + private String privateKey; + + public Builder encryptedPassword(String encryptedPassword) { + this.encryptedPassword = encryptedPassword; + return this; + } + + public Builder encryptedPassword(EncryptedPassword password) { + return encryptedPassword(password.getEncryptedPassword()); + } + + public Builder privateKey(String privateKey) { + this.privateKey = privateKey; + return this; + } + + public EncryptedPasswordAndPrivateKey build() { + return new EncryptedPasswordAndPrivateKey(encryptedPassword, privateKey); + } + } + + private final String encryptedPassword; + private final String privateKey; + + public EncryptedPasswordAndPrivateKey(String encryptedPassword, String privateKey) { + this.encryptedPassword = encryptedPassword; + this.privateKey = privateKey; + } + + /** + * @return the encrypted password String representation + */ + public String getEncryptedPassword() { + return encryptedPassword; + } + + /** + * @return get the string representation of the private key + */ + public String getPrivateKey() { + return privateKey; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + EncryptedPasswordAndPrivateKey that = (EncryptedPasswordAndPrivateKey) o; + + if (encryptedPassword != null ? !encryptedPassword.equals(that.encryptedPassword) : that.encryptedPassword != null) + return false; + if (privateKey != null ? !privateKey.equals(that.privateKey) : that.privateKey != null) return false; + + return true; + } + + @Override + public int hashCode() { + int result = encryptedPassword != null ? encryptedPassword.hashCode() : 0; + result = 31 * result + (privateKey != null ? privateKey.hashCode() : 0); + return result; + } + + @Override + public String toString() { + return "EncryptedPasswordAndPrivateKey{" + + "encryptedPassword='" + encryptedPassword + '\'' + + ", privateKey='" + privateKey + '\'' + + '}'; + } +} diff --git a/apis/cloudstack/src/main/java/org/jclouds/cloudstack/functions/WindowsLoginCredentialsFromEncryptedData.java b/apis/cloudstack/src/main/java/org/jclouds/cloudstack/functions/WindowsLoginCredentialsFromEncryptedData.java new file mode 100644 index 0000000000..eb618740c6 --- /dev/null +++ b/apis/cloudstack/src/main/java/org/jclouds/cloudstack/functions/WindowsLoginCredentialsFromEncryptedData.java @@ -0,0 +1,61 @@ +package org.jclouds.cloudstack.functions; + +import com.google.common.base.Function; +import com.google.common.base.Throwables; +import com.google.inject.Inject; +import com.google.inject.Singleton; +import org.jclouds.cloudstack.domain.EncryptedPasswordAndPrivateKey; +import org.jclouds.crypto.Crypto; +import org.jclouds.crypto.Pems; +import org.jclouds.domain.LoginCredentials; +import org.jclouds.encryption.internal.Base64; +import org.jclouds.javax.annotation.Nullable; + +import javax.crypto.Cipher; +import java.nio.charset.Charset; +import java.security.KeyFactory; +import java.security.PrivateKey; +import java.security.spec.KeySpec; + +/** + * Given an encrypted Windows Administrator password and the decryption key, return a LoginCredentials instance. + * + * @author Richard Downer, Andrei Savu + */ +@Singleton +public class WindowsLoginCredentialsFromEncryptedData implements Function { + + private final Crypto crypto; + + @Inject + public WindowsLoginCredentialsFromEncryptedData(Crypto crypto) { + this.crypto = crypto; + } + + @Override + public LoginCredentials apply(@Nullable EncryptedPasswordAndPrivateKey dataAndKey) { + if (dataAndKey == null) + return null; + + try { + KeySpec keySpec = Pems.privateKeySpec(dataAndKey.getPrivateKey()); + KeyFactory kf = crypto.rsaKeyFactory(); + PrivateKey privKey = kf.generatePrivate(keySpec); + + Cipher cipher = crypto.cipher("RSA/NONE/PKCS1Padding"); + cipher.init(Cipher.DECRYPT_MODE, privKey); + byte[] cipherText = Base64.decode(dataAndKey.getEncryptedPassword()); + byte[] plainText = cipher.doFinal(cipherText); + String password = new String(plainText, Charset.forName("ASCII")); + + return LoginCredentials.builder() + .user("Administrator") + .password(password) + .noPrivateKey() + .build(); + + } catch (Exception e) { + throw Throwables.propagate(e); + } + } +} \ No newline at end of file diff --git a/apis/cloudstack/src/test/java/org/jclouds/cloudstack/compute/CloudStackExperimentLiveTest.java b/apis/cloudstack/src/test/java/org/jclouds/cloudstack/compute/CloudStackExperimentLiveTest.java index c69674eedc..fcd3aafa00 100644 --- a/apis/cloudstack/src/test/java/org/jclouds/cloudstack/compute/CloudStackExperimentLiveTest.java +++ b/apis/cloudstack/src/test/java/org/jclouds/cloudstack/compute/CloudStackExperimentLiveTest.java @@ -20,19 +20,24 @@ package org.jclouds.cloudstack.compute; import org.jclouds.cloudstack.compute.options.CloudStackTemplateOptions; import org.jclouds.cloudstack.domain.EncryptedPassword; +import org.jclouds.cloudstack.domain.EncryptedPasswordAndPrivateKey; import org.jclouds.cloudstack.domain.Network; import org.jclouds.cloudstack.domain.SshKeyPair; import org.jclouds.cloudstack.domain.TrafficType; import org.jclouds.cloudstack.features.BaseCloudStackClientLiveTest; +import org.jclouds.cloudstack.functions.WindowsLoginCredentialsFromEncryptedData; import org.jclouds.cloudstack.options.ListNetworksOptions; import org.jclouds.compute.RunNodesException; import org.jclouds.compute.domain.NodeMetadata; import org.jclouds.compute.domain.Template; import org.jclouds.compute.predicates.NodePredicates; -import org.testng.Assert; +import org.jclouds.crypto.Crypto; +import org.jclouds.encryption.bouncycastle.BouncyCastleCrypto; import org.testng.annotations.Test; import java.net.URI; +import java.security.NoSuchAlgorithmException; +import java.security.cert.CertificateException; import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; @@ -43,9 +48,9 @@ import static com.google.common.collect.Iterables.getOnlyElement; import static com.google.common.collect.Sets.newTreeSet; import static org.jclouds.cloudstack.options.CreateNetworkOptions.Builder.vlan; import static org.jclouds.cloudstack.options.ListNetworkOfferingsOptions.Builder.specifyVLAN; +import static org.testng.Assert.assertEquals; /** - * * @author Adrian Cole */ @Test(groups = "live", testName = "CloudStackExperimentLiveTest") @@ -66,7 +71,7 @@ public class CloudStackExperimentLiveTest extends BaseCloudStackClientLiveTest { // Warning: the vlan id is not set in the response - using an workaround URI broadcastUri = URI.create("vlan://" + vlanId); - for(Network net : networks) { + for (Network net : networks) { if (broadcastUri.equals(net.getBroadcastURI())) { long jobId = domainAdminContext.getApi().getNetworkClient().deleteNetwork(net.getId()); adminJobComplete.apply(jobId); @@ -98,14 +103,14 @@ public class CloudStackExperimentLiveTest extends BaseCloudStackClientLiveTest { // find a network offering that supports vlans in our zone long offeringId = get( - context.getApi().getOfferingClient().listNetworkOfferings(specifyVLAN(true).zoneId(zoneId)), 0).getId(); + context.getApi().getOfferingClient().listNetworkOfferings(specifyVLAN(true).zoneId(zoneId)), 0).getId(); // create an arbitrary network network = domainAdminContext.getApi() - .getNetworkClient() + .getNetworkClient() // startIP/endIP/netmask/gateway must be specified together - .createNetworkInZone(zoneId, offeringId, group, group, - vlan(vlanId).startIP("192.168.1.2").netmask("255.255.255.0").gateway("192.168.1.1")); + .createNetworkInZone(zoneId, offeringId, group, group, + vlan(vlanId).startIP("192.168.1.2").netmask("255.255.255.0").gateway("192.168.1.1")); // set options to specify this network id template.getOptions().as(CloudStackTemplateOptions.class).networkId(network.getId()); @@ -127,7 +132,8 @@ public class CloudStackExperimentLiveTest extends BaseCloudStackClientLiveTest { } @Test - public void testCreateWindowsMachineWithKeyPairAndCheckIfTheGeneratedPasswordIsEncrypted() throws RunNodesException { + public void testCreateWindowsMachineWithKeyPairAndCheckIfTheGeneratedPasswordIsEncrypted() + throws RunNodesException, NoSuchAlgorithmException, CertificateException { // final Map sshKey = SshKeys.generate(); // final String publicKey = sshKey.get("public"); @@ -147,12 +153,16 @@ public class CloudStackExperimentLiveTest extends BaseCloudStackClientLiveTest { try { node = getOnlyElement(computeContext.getComputeService() .createNodesInGroup(group, 1, template)); - + EncryptedPassword password = client.getVirtualMachineClient() .getEncryptedPasswordForVirtualMachine(Long.parseLong(node.getId())); - Assert.fail("Private key:" + keyPair.getPrivateKey() + "\nPassword: " + password.getEncryptedPassword()); - // TODO: decrypt the password + Crypto crypto = new BouncyCastleCrypto(); + WindowsLoginCredentialsFromEncryptedData passwordDecrypt = new WindowsLoginCredentialsFromEncryptedData(crypto); + + assertEquals(passwordDecrypt.apply(EncryptedPasswordAndPrivateKey.builder() + .encryptedPassword(password).privateKey(keyPair.getPrivateKey()).build()) + .getPassword(), "bX7vvptvw"); } finally { if (node != null) { diff --git a/apis/cloudstack/src/test/java/org/jclouds/cloudstack/features/VirtualMachineClientExpectTest.java b/apis/cloudstack/src/test/java/org/jclouds/cloudstack/features/VirtualMachineClientExpectTest.java index b12c15df2a..cb25ff5787 100644 --- a/apis/cloudstack/src/test/java/org/jclouds/cloudstack/features/VirtualMachineClientExpectTest.java +++ b/apis/cloudstack/src/test/java/org/jclouds/cloudstack/features/VirtualMachineClientExpectTest.java @@ -21,11 +21,17 @@ package org.jclouds.cloudstack.features; import com.google.common.collect.ImmutableMultimap; import org.jclouds.cloudstack.CloudStackContext; import org.jclouds.cloudstack.domain.EncryptedPassword; +import org.jclouds.cloudstack.domain.EncryptedPasswordAndPrivateKey; +import org.jclouds.cloudstack.functions.WindowsLoginCredentialsFromEncryptedData; +import org.jclouds.crypto.Crypto; +import org.jclouds.encryption.bouncycastle.BouncyCastleCrypto; import org.jclouds.http.HttpRequest; import org.jclouds.http.HttpResponse; import org.testng.annotations.Test; import java.net.URI; +import java.security.NoSuchAlgorithmException; +import java.security.cert.CertificateException; import static org.testng.Assert.assertEquals; @@ -37,7 +43,7 @@ import static org.testng.Assert.assertEquals; @Test(groups = "unit", testName = "VirtualMachineClientExpectTest") public class VirtualMachineClientExpectTest extends BaseCloudStackRestClientExpectTest { - public void testGetPasswordForVirtualMachineWhenResponseIs2xx() { + public void testGetPasswordForVirtualMachineWhenResponseIs2xx() throws NoSuchAlgorithmException, CertificateException { String privateKey = "-----BEGIN RSA PRIVATE KEY-----\n" + "MIICXgIBAAKBgQDnaPKhTNgw7qPJVp3qsT+7XhhAbip25a0AnUgq8Fb9LPcZk00p\n" + "jm+m4JrKmDWKZWrHMNBhCNHMzvV9KrAXUMzL4s7mdEicbxTKratTYoyJM7a87bcZ\n" + @@ -77,7 +83,11 @@ public class VirtualMachineClientExpectTest extends BaseCloudStackRestClientExpe assertEquals(actual, expected); - // TODO: decrypt the returned password using the private key + Crypto crypto = new BouncyCastleCrypto(); + WindowsLoginCredentialsFromEncryptedData passwordDecrypt = new WindowsLoginCredentialsFromEncryptedData(crypto); + + assertEquals(passwordDecrypt.apply(EncryptedPasswordAndPrivateKey.builder() + .encryptedPassword(actual).privateKey(privateKey).build()).getPassword(), "bX7vvptvw"); } @Override