package io.fathom.cloud.openstack.client.identity; import io.fathom.cloud.openstack.client.identity.model.V2AuthRequest; import java.security.InvalidKeyException; import java.security.Key; import java.security.KeyPair; import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; import java.security.PublicKey; import java.security.cert.X509Certificate; import java.util.Arrays; import javax.crypto.BadPaddingException; import javax.crypto.Cipher; import javax.crypto.IllegalBlockSizeException; import javax.crypto.NoSuchPaddingException; import javax.security.auth.x500.X500Principal; import com.fathomdb.crypto.CertificateAndKey; import com.fathomdb.crypto.SimpleCertificateAndKey; import com.fathomdb.crypto.bouncycastle.SimpleCertificateAuthority; import com.google.common.base.Charsets; import com.google.common.base.Strings; import com.google.common.io.BaseEncoding; import com.google.common.primitives.Bytes; public class ChallengeResponses { private static final byte[] PREFIX_V1_BYTES = "openstack-rsa-v1".getBytes(Charsets.UTF_8); public static boolean hasPrefix(byte[] data) { if (data.length < PREFIX_V1_BYTES.length) { return false; } byte[] head = new byte[PREFIX_V1_BYTES.length]; System.arraycopy(data, 0, head, 0, PREFIX_V1_BYTES.length); return Arrays.equals(head, PREFIX_V1_BYTES); } public static byte[] getPayload(byte[] data) { if (!hasPrefix(data)) { throw new IllegalArgumentException(); } byte[] tail = new byte[data.length - PREFIX_V1_BYTES.length]; System.arraycopy(data, PREFIX_V1_BYTES.length, tail, 0, tail.length); return tail; } public static byte[] addHeader(byte[] ciphertext) { return Bytes.concat(PREFIX_V1_BYTES, ciphertext); } public static V2AuthRequest.ChallengeResponse respondToAuthChallenge(CertificateAndKey certificateAndKey, String challenge) { if (Strings.isNullOrEmpty(challenge)) { throw new IllegalStateException("Did not receieve challenge when authenticating"); } byte[] challengeBytes = BaseEncoding.base64().decode(challenge); // The prefix acts as a version if (!hasPrefix(challengeBytes)) { throw new IllegalStateException("Challenge was not in a recognized format"); } byte[] ciphertext = getPayload(challengeBytes); byte[] plaintext = decrypt(certificateAndKey.getPrivateKey(), ciphertext); // The prefix must also be in the plaintext if (!hasPrefix(plaintext)) { throw new IllegalStateException("Challenge was not valid (bad decrypted result)"); } V2AuthRequest.ChallengeResponse challengeResponse = new V2AuthRequest.ChallengeResponse(); challengeResponse.challenge = challenge; challengeResponse.response = BaseEncoding.base64().encode(plaintext); return challengeResponse; } public static V2AuthRequest.ChallengeResponse respondToRegistrationChallenge(CertificateAndKey certificateAndKey, String challenge) { if (Strings.isNullOrEmpty(challenge)) { throw new IllegalStateException("Did not receieve challenge when authenticating"); } byte[] challengeBytes = BaseEncoding.base64().decode(challenge); // The prefix acts as a version if (!hasPrefix(challengeBytes)) { throw new IllegalStateException("Challenge was not in a recognized format"); } // The payload is no longer encrypted, because the server doesn't get // the public key behind a firewall byte[] payload = getPayload(challengeBytes); // The prefix must also be in the payload if (!hasPrefix(payload)) { throw new IllegalStateException("Challenge was not valid"); } byte[] plaintext = encrypt(certificateAndKey.getPublicKey(), payload); V2AuthRequest.ChallengeResponse challengeResponse = new V2AuthRequest.ChallengeResponse(); challengeResponse.challenge = challenge; challengeResponse.response = BaseEncoding.base64().encode(plaintext); return challengeResponse; } public static byte[] encrypt(Key key, byte[] plaintext) { Cipher cipher = getCipher(key); try { cipher.init(Cipher.ENCRYPT_MODE, key); } catch (InvalidKeyException e) { throw new IllegalArgumentException("Invalid key", e); } byte[] ciphertext; try { ciphertext = cipher.doFinal(plaintext); } catch (IllegalBlockSizeException e) { throw new IllegalArgumentException("Error in encryption", e); } catch (BadPaddingException e) { throw new IllegalArgumentException("Error in encryption", e); } return ciphertext; } private static Cipher getCipher(Key key) { Cipher cipher; String algorithm = "RSA"; try { cipher = Cipher.getInstance(algorithm); } catch (NoSuchAlgorithmException e) { throw new IllegalArgumentException("Error loading crypto provider", e); } catch (NoSuchPaddingException e) { throw new IllegalArgumentException("Error loading crypto provider", e); } return cipher; } static byte[] decrypt(Key key, byte[] ciphertext) { Cipher cipher = getCipher(key); try { cipher.init(Cipher.DECRYPT_MODE, key); } catch (InvalidKeyException e) { throw new IllegalArgumentException("Invalid key", e); } byte[] plaintext; try { plaintext = cipher.doFinal(ciphertext); } catch (IllegalBlockSizeException e) { throw new IllegalArgumentException("Error in encryption", e); } catch (BadPaddingException e) { throw new IllegalArgumentException("Error in encryption", e); } return plaintext; } public static CertificateAndKey createSelfSigned(X500Principal principal, KeyPair keypair) { X500Principal subject = principal; PublicKey subjectPublicKey = keypair.getPublic(); X500Principal issuer = principal; PrivateKey issuerPrivateKey = keypair.getPrivate(); X509Certificate certificate = SimpleCertificateAuthority.signAsCa(subject, subjectPublicKey, issuer, issuerPrivateKey); X509Certificate[] certificateChain = new X509Certificate[1]; certificateChain[0] = certificate; return new SimpleCertificateAndKey(certificateChain, keypair.getPrivate()); } }