From d36ca103f3cb1779b55e164328841d51bf24c3ce Mon Sep 17 00:00:00 2001 From: Ignasi Barrera Date: Sun, 9 Dec 2012 00:08:18 +0100 Subject: [PATCH] Fixed decoding of some PKCS1 encoded public keys --- .../crypto/pem/PKCS1EncodedPublicKeySpec.java | 39 +++++++++++++---- .../java/org/jclouds/crypto/PemsTest.java | 43 +++++++++++++++++-- 2 files changed, 69 insertions(+), 13 deletions(-) diff --git a/core/src/main/java/org/jclouds/crypto/pem/PKCS1EncodedPublicKeySpec.java b/core/src/main/java/org/jclouds/crypto/pem/PKCS1EncodedPublicKeySpec.java index a07537870a..bb49caf124 100644 --- a/core/src/main/java/org/jclouds/crypto/pem/PKCS1EncodedPublicKeySpec.java +++ b/core/src/main/java/org/jclouds/crypto/pem/PKCS1EncodedPublicKeySpec.java @@ -46,12 +46,14 @@ package org.jclouds.crypto.pem; import java.io.IOException; import java.security.spec.RSAPublicKeySpec; -import net.oauth.signature.pem.PKCS1EncodedKeySpec; +import org.bouncycastle.asn1.ASN1Object; +import org.bouncycastle.asn1.ASN1Sequence; +import org.bouncycastle.asn1.x509.RSAPublicKeyStructure; +import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo; /** * PKCS#1 encoded public key spec. * - * * @author Adrian Cole */ public class PKCS1EncodedPublicKeySpec { @@ -65,7 +67,7 @@ public class PKCS1EncodedPublicKeySpec { * DER encoded octet stream * @throws IOException */ - public PKCS1EncodedPublicKeySpec(byte[] keyBytes) throws IOException { + public PKCS1EncodedPublicKeySpec(final byte[] keyBytes) throws IOException { decode(keyBytes); } @@ -79,12 +81,31 @@ public class PKCS1EncodedPublicKeySpec { } /** - * get the modulus and public exponent by reusing {@link PKCS1EncodedKeySpec} + * Decode PKCS#1 encoded private key into RSAPrivateCrtKeySpec. + *

+ * Keys here can be in two different formats. They can have the algorithm + * encoded, or they can have only the modulus and the public exponent. + *

+ * The latter is not a valid PEM encoded file, but it is a valid DER encoded + * RSA key, so this method should also support it. + * + * @param keyBytes + * Encoded PKCS#1 rsa key. */ - private void decode(byte[] keyBytes) throws IOException { - PKCS1EncodedKeySpec privateSpec = new PKCS1EncodedKeySpec(keyBytes); - - keySpec = new RSAPublicKeySpec(privateSpec.getKeySpec().getModulus(), privateSpec.getKeySpec() - .getPublicExponent()); + private void decode(final byte[] keyBytes) throws IOException { + RSAPublicKeyStructure pks = null; + ASN1Sequence seq = (ASN1Sequence) ASN1Object.fromByteArray(keyBytes); + try { + // Try to parse the public key normally. If the algorithm is not + // present in the encoded key, an IllegalArgumentException will be + // raised. + SubjectPublicKeyInfo info = new SubjectPublicKeyInfo(seq); + pks = new RSAPublicKeyStructure((ASN1Sequence) info.getPublicKey()); + } catch (IllegalArgumentException ex) { + // If the algorithm is not found in the encoded key, try to extract + // just the modulus and the public exponent to build the public key. + pks = new RSAPublicKeyStructure(seq); + } + keySpec = new RSAPublicKeySpec(pks.getModulus(), pks.getPublicExponent()); } } diff --git a/core/src/test/java/org/jclouds/crypto/PemsTest.java b/core/src/test/java/org/jclouds/crypto/PemsTest.java index d20f881b07..b55a7d2755 100644 --- a/core/src/test/java/org/jclouds/crypto/PemsTest.java +++ b/core/src/test/java/org/jclouds/crypto/PemsTest.java @@ -19,6 +19,8 @@ package org.jclouds.crypto; import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertNotEquals; +import static org.testng.Assert.assertTrue; import java.io.IOException; import java.security.KeyFactory; @@ -27,6 +29,8 @@ import java.security.cert.CertificateException; import java.security.cert.CertificateFactory; import java.security.interfaces.RSAPrivateCrtKey; import java.security.spec.InvalidKeySpecException; +import java.security.spec.KeySpec; +import java.security.spec.RSAPublicKeySpec; import org.jclouds.io.Payloads; import org.testng.annotations.Test; @@ -42,6 +46,10 @@ public class PemsTest { public static final String PUBLIC_KEY = "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyb2ZJJqGm0KKR+8nfQJN\nsSd+F9tXNMV7CfOcW6jsqs8EZgiVR09hD1IYOj4YqM0qJONlgyg4xRWewdSG7QTP\nj1lJpVAida9sXy2+kzyagZA1Am0OZcbqb5hoeIDgcX+eDa79s0u0DomjcfO9EKhv\nHLBz+zM+3QqPRkPV8nYTbfs+HjVzzOU6D1B0XR3+IPZZl2AnWs2d0qhnStHcDUvn\nRVQ0P482YwN9VgceOZtpPz0DCKEJ5Tx5STub8k0/zt/VAMHQafLSuQMLd2s4ZLuO\nZptN//uAsTmxireqd37z+8ZTdBbJ8LEpJ+iCXuSfm5aUh7iw6oxvToY2AL53+jK2\nUQIDAQAB\n-----END PUBLIC KEY-----\n"; + private static final String PUBLIC_KEY_PKCS1 = "-----BEGIN RSA PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAp0ytgXbPzqJwOOixn7bT\na6VAiNvVIOn+yDPoWbyEfc0li93BHIwv01KW/mn55IXnSbMw86rdxisvwPHFfb7U\nRuKuTzME6yrphBiancmNjushZZeBWb8jqJhnFIKbaaOqew0LZSyG9ycYODB/HDK/\npWTV4Bd1OtLHBNFrnIf+r3HOjJsa4rmKWXgSQIQO7be/iRHysApV9tfVH8lo1ETn\nA08JTrQwDgo9St9YNbydb5V0CiLiQsOaIbY09buUK9lXthh/rrRVbGbSwQM6OYdX\nIEZTN2BFvQ0p5pH8AiTwFqb0ICO46a0SjfGcXNjC/QfHljAPY3T5xyIOODM8afHC\nnwIDAQAB\n-----END RSA PUBLIC KEY-----\n"; + + private static final String PUBLIC_KEY_PKCS1_RAW = "-----BEGIN RSA PUBLIC KEY-----\nMIIBCgKCAQEAp0ytgXbPzqJwOOixn7bTa6VAiNvVIOn+yDPoWbyEfc0li93BHIwv\n01KW/mn55IXnSbMw86rdxisvwPHFfb7URuKuTzME6yrphBiancmNjushZZeBWb8j\nqJhnFIKbaaOqew0LZSyG9ycYODB/HDK/pWTV4Bd1OtLHBNFrnIf+r3HOjJsa4rmK\nWXgSQIQO7be/iRHysApV9tfVH8lo1ETnA08JTrQwDgo9St9YNbydb5V0CiLiQsOa\nIbY09buUK9lXthh/rrRVbGbSwQM6OYdXIEZTN2BFvQ0p5pH8AiTwFqb0ICO46a0S\njfGcXNjC/QfHljAPY3T5xyIOODM8afHCnwIDAQAB\n-----END RSA PUBLIC KEY-----\n"; + private static final String CERTIFICATE = "-----BEGIN CERTIFICATE-----\nMIIClzCCAgCgAwIBAgIBATANBgkqhkiG9w0BAQUFADCBnjELMAkGA1UEBhMCVVMx\nEzARBgNVBAgMCldhc2hpbmd0b24xEDAOBgNVBAcMB1NlYXR0bGUxFjAUBgNVBAoM\nDU9wc2NvZGUsIEluYy4xHDAaBgNVBAsME0NlcnRpZmljYXRlIFNlcnZpY2UxMjAw\nBgNVBAMMKW9wc2NvZGUuY29tL2VtYWlsQWRkcmVzcz1hdXRoQG9wc2NvZGUuY29t\nMB4XDTEwMDczMDIwNDEzMFoXDTIwMDcyNzIwNDEzMFowADCCASIwDQYJKoZIhvcN\nAQEBBQADggEPADCCAQoCggEBAMm9mSSahptCikfvJ30CTbEnfhfbVzTFewnznFuo\n7KrPBGYIlUdPYQ9SGDo+GKjNKiTjZYMoOMUVnsHUhu0Ez49ZSaVQInWvbF8tvpM8\nmoGQNQJtDmXG6m+YaHiA4HF/ng2u/bNLtA6Jo3HzvRCobxywc/szPt0Kj0ZD1fJ2\nE237Ph41c8zlOg9QdF0d/iD2WZdgJ1rNndKoZ0rR3A1L50VUND+PNmMDfVYHHjmb\naT89AwihCeU8eUk7m/JNP87f1QDB0Gny0rkDC3drOGS7jmabTf/7gLE5sYq3qnd+\n8/vGU3QWyfCxKSfogl7kn5uWlIe4sOqMb06GNgC+d/oytlECAwEAATANBgkqhkiG\n9w0BAQUFAAOBgQBftzSZxstWw60GqRTDNN/F2GnrdtnKBoXzHww3r6jtGEylYq20\n5KfKpEx+sPX0gyZuYJiXC2CkEjImAluWKcdN9ZF6VD541sheAjbiaU7q7ZsztTxF\nWUH2tCvHeDXYKPKek3QzL7bYpUhLnCN/XxEv6ibeMDwtI7f5qpk2Aspzcw==\n-----END CERTIFICATE-----\n"; @Test @@ -67,7 +75,7 @@ public class PemsTest { @Test public void testPrivateKeySpecPem() throws IOException, InvalidKeySpecException, NoSuchAlgorithmException { RSAPrivateCrtKey key = (RSAPrivateCrtKey) KeyFactory.getInstance("RSA").generatePrivate( - Pems.privateKeySpec(Payloads.newStringPayload(PRIVATE_KEY))); + Pems.privateKeySpec(Payloads.newStringPayload(PRIVATE_KEY))); String encoded = Pems.pem(key); assertEquals(encoded, PRIVATE_KEY.replaceAll("\n", "\n").trim()); } @@ -75,14 +83,41 @@ public class PemsTest { @Test public void testRSAPublicKeySpecPem() throws IOException, InvalidKeySpecException, NoSuchAlgorithmException { String encoded = Pems.pem(KeyFactory.getInstance("RSA").generatePublic( - Pems.publicKeySpec(Payloads.newStringPayload(PUBLIC_KEY)))); + Pems.publicKeySpec(Payloads.newStringPayload(PUBLIC_KEY)))); assertEquals(encoded, PUBLIC_KEY.replaceAll("PUBLIC", "RSA PUBLIC").replaceAll("\n", "\n").trim()); } + @Test + public void testRSAPKCS1PublicKeySpecPem() throws IOException, InvalidKeySpecException, NoSuchAlgorithmException { + String encoded = Pems.pem(KeyFactory.getInstance("RSA").generatePublic( + Pems.publicKeySpec(Payloads.newStringPayload(PUBLIC_KEY_PKCS1)))); + assertEquals(encoded, PUBLIC_KEY_PKCS1.replaceAll("\n", "\n").trim()); + } + + @Test + public void testRSAPKCS1RawPublicKeySpecPem() throws IOException, InvalidKeySpecException, NoSuchAlgorithmException { + KeySpec spec = Pems.publicKeySpec(Payloads.newStringPayload(PUBLIC_KEY_PKCS1_RAW)); + String encoded = Pems.pem(KeyFactory.getInstance("RSA").generatePublic(spec)); + KeySpec generatedSpec = Pems.publicKeySpec(encoded); + + assertTrue(spec instanceof RSAPublicKeySpec); + assertTrue(generatedSpec instanceof RSAPublicKeySpec); + + // The encoded is different because the generatePublic method adds the + // algorithm to the key. + assertNotEquals(encoded, PUBLIC_KEY_PKCS1_RAW.replaceAll("\n", "\n").trim()); + // Verify that the modulus and public exponent of the encoded key are the + // same than the ones in the original key + assertEquals(RSAPublicKeySpec.class.cast(spec).getModulus(), RSAPublicKeySpec.class.cast(generatedSpec) + .getModulus()); + assertEquals(RSAPublicKeySpec.class.cast(spec).getPublicExponent(), RSAPublicKeySpec.class.cast(generatedSpec) + .getPublicExponent()); + } + @Test public void testX509CertificatePem() throws IOException, CertificateException { - String encoded = Pems.pem(Pems.x509Certificate(Payloads.newStringPayload(CERTIFICATE), CertificateFactory - .getInstance("X.509"))); + String encoded = Pems.pem(Pems.x509Certificate(Payloads.newStringPayload(CERTIFICATE), + CertificateFactory.getInstance("X.509"))); assertEquals(encoded, CERTIFICATE.replaceAll("\n", "\n").trim()); }