diff --git a/common/src/main/java/org/keycloak/common/crypto/PemUtilsProvider.java b/common/src/main/java/org/keycloak/common/crypto/PemUtilsProvider.java index 14f14627b67..0af1aeb8c24 100755 --- a/common/src/main/java/org/keycloak/common/crypto/PemUtilsProvider.java +++ b/common/src/main/java/org/keycloak/common/crypto/PemUtilsProvider.java @@ -40,9 +40,6 @@ import org.keycloak.common.util.PemException; */ public abstract class PemUtilsProvider { - public static final String BEGIN_CERT = "-----BEGIN CERTIFICATE-----"; - public static final String END_CERT = "-----END CERTIFICATE-----"; - /** * Decode a X509 Certificate from a PEM string @@ -104,18 +101,7 @@ public abstract class PemUtilsProvider { * @return * @throws Exception */ - public PrivateKey decodePrivateKey(String pem) { - if (pem == null) { - return null; - } - - try { - byte[] der = pemToDer(pem); - return DerUtils.decodePrivateKey(der); - } catch (Exception e) { - throw new PemException(e); - } - } + public abstract PrivateKey decodePrivateKey(String pem); /** diff --git a/common/src/main/java/org/keycloak/common/util/PemUtils.java b/common/src/main/java/org/keycloak/common/util/PemUtils.java index 00291504621..955f509bdf0 100755 --- a/common/src/main/java/org/keycloak/common/util/PemUtils.java +++ b/common/src/main/java/org/keycloak/common/util/PemUtils.java @@ -37,6 +37,11 @@ public class PemUtils { public static final String BEGIN_CERT = "-----BEGIN CERTIFICATE-----"; public static final String END_CERT = "-----END CERTIFICATE-----"; + public static final String BEGIN_PRIVATE_KEY = "-----BEGIN PRIVATE KEY-----"; + public static final String END_PRIVATE_KEY = "-----END PRIVATE KEY-----"; + public static final String BEGIN_RSA_PRIVATE_KEY = "-----BEGIN RSA PRIVATE KEY-----"; + public static final String END_RSA_PRIVATE_KEY = "-----END RSA PRIVATE KEY-----"; + /** * Decode a X509 Certificate from a PEM string * @@ -112,6 +117,20 @@ public class PemUtils { return CryptoIntegration.getProvider().getPemUtils().removeBeginEnd(pem); } + public static String addPrivateKeyBeginEnd(String privateKeyPem) { + return new StringBuilder(PemUtils.BEGIN_PRIVATE_KEY + "\n") + .append(privateKeyPem) + .append("\n" + PemUtils.END_PRIVATE_KEY) + .toString(); + } + + public static String addRsaPrivateKeyBeginEnd(String privateKeyPem) { + return new StringBuilder(PemUtils.BEGIN_RSA_PRIVATE_KEY + "\n") + .append(privateKeyPem) + .append("\n" + PemUtils.END_RSA_PRIVATE_KEY) + .toString(); + } + public static String generateThumbprint(String[] certChain, String encoding) throws NoSuchAlgorithmException{ return CryptoIntegration.getProvider().getPemUtils().generateThumbprint(certChain, encoding); } diff --git a/core/src/test/java/org/keycloak/KeyPairVerifierTest.java b/core/src/test/java/org/keycloak/KeyPairVerifierTest.java index 1b3216b7d11..4108b632f94 100644 --- a/core/src/test/java/org/keycloak/KeyPairVerifierTest.java +++ b/core/src/test/java/org/keycloak/KeyPairVerifierTest.java @@ -50,13 +50,65 @@ public abstract class KeyPairVerifierTest { + "LMD9RZHcsIdfSnG7xVNBQZpf4ZCSFO3RbIH7b//+kn8TxQudptd9SkXba65prBM2\n" + "kh8IbDNBAoGAVsKvkruH7RK7CimDSWcdAKvHARqkjs/PoeKEEY8Yu6zf0Z9TQM5l\n" + "uC9EwBamYcSusWRcdcz+9HYG58XFnmXq+3EUuFbJ+Ljb8YWBgePjSHDoS/6+/+zq\n" + "B1b5uQp/jYFbYQl50UPRPTF+ul1eQoy7F43Ngj3/5cDRarFZe3ZTzZo=\n" + "-----END RSA PRIVATE KEY-----"; + String publicKey2048 = "-----BEGIN PUBLIC KEY-----\n" + "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4V3MpOnuKsdBbR1UzNjK\n" + "9o5meEMQ4s5Vpykhv1DpqTilKOiEH7VQ/XtjNxw0yjnFBilCnpK6yN9mDEHbBEza\n" + "RjtdrgVhkIejiaXFBP5MBhUQ5l9u8E3IZC3E8pwDjVF0Z9u0R4lGeUg2k6O+NKum\n" + "qIvxoLCTuG0zf53bctGsRd57LuFipgCkNyxvscOhulsbEMYrLwlb5bMGgx9v+RCn\n" + "wvunNEb7RK+5pzP+iH1MRejRsX+U7h9zHRn2gQhIl7SzG9GXebuPWr4KKwfMHWy0\n" + "PEuQrsfWRXm9/dTEavbfNkv5E53zWXjWyf93ezkVhBX0YoXmf6UO7PAlvsrjno3T\n" + "uwIDAQAB\n" + "-----END PUBLIC KEY-----"; @Test - public void verify() throws Exception { + public void verifyWithPrivateKeysInTraditionalRSAFormat() throws Exception { + verifyImpl(this.privateKey1, this.privateKey2048); + } + + @Test + public void verifyWithPrivateKeysInPKCS8Format() throws Exception { + String privateKey1 = "MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAKtWsK5O0CtuBpnM" + + "vWG+HTG0vmZzujQ2o9WdheQu+BzCILcGMsbDW0YQaglpcO5JpGWWhubnckGGPHfd" + + "Q2/7nP9QwbiTK0FbGF41UqcvoaCqU1psxoV88s8IXyQCAqeyLv00yj6foqdJjxh5" + + "SZ5z+na+M7Y2OxIBVxYRAxWEnfUvAgMBAAECgYB+Y7yBWHIHF2qXGYi6CVvPxtyN" + + "BuFcktHYShLyeBNeY3VujYv3QzSZQpJ1zuoXXQuARMHOovyNiVAhu357pMfx9wSk" + + "oKNSXKrQx/+9Vt9lI1pXJxjXedPOjbuI/JZAcrk0u4nOfXG/HGtR5cjoDZYWkYQE" + + "tsePCnHlZAb0D7axwQJBAO92f00Tvkc9NU/EGqwR3bPXRMqSX0JnG7XRBvLeJBCZ" + + "YsQn0s2bLdpy8qsTeAyJg1ZvrEc8qIio5HVqzsvbhpMCQQC3K9A6UK+vmQCNWqsQ" + + "pdqWPRPN7CPB67FzSmyS8CtMjY6jTvSHrkamggotz2N/5QDr1xG2q7A/3dpkq1bT" + + "pTx1AkAXZjjiSz+Yrn57IOqKTeSgIjTypoLwdirbBWXsbZCQnqxsBogu1y8P3ZOg" + + "6/IbJ4TR+W+YNnExiW9pmdpDSVxJAkEAplTq6YmLf/F4RuQmox94tyUPbtcYQWg9" + + "42uZ3HSrXQDOng18kBj5nwpHJAJHYEQb6g2K0E5n5hcX0oKkfdx2YQJAcSKAmFiD" + + "7KQ6+vVqJlQwVPvYdTSOeZB7YVV6S4b4slS3ZObsa0yNMWgal/QnCtW5k3f185gC" + + "Wj6dOLGB5btfxg=="; + + String privateKey2048 = "MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDhXcyk6e4qx0Ft" + + "HVTM2Mr2jmZ4QxDizlWnKSG/UOmpOKUo6IQftVD9e2M3HDTKOcUGKUKekrrI32YM" + + "QdsETNpGO12uBWGQh6OJpcUE/kwGFRDmX27wTchkLcTynAONUXRn27RHiUZ5SDaT" + + "o740q6aoi/GgsJO4bTN/ndty0axF3nsu4WKmAKQ3LG+xw6G6WxsQxisvCVvlswaD" + + "H2/5EKfC+6c0RvtEr7mnM/6IfUxF6NGxf5TuH3MdGfaBCEiXtLMb0Zd5u49avgor" + + "B8wdbLQ8S5Cux9ZFeb391MRq9t82S/kTnfNZeNbJ/3d7ORWEFfRiheZ/pQ7s8CW+" + + "yuOejdO7AgMBAAECggEBALmIIA5wK0t6aGls6UAPBeA+0SsWg1NE7IzGNusqsIJI" + + "iOeJrCPygC9+IerfxLHrJ0FwPFERmMX/7CIRIT6ECnohK3k1IuH6WG7cUrtOosWr" + + "GBOf41PfpSab63STbfUsZrmNzPfLkoIMKioXdmIkIfrF4vEYDTSaafgYu+3loX6O" + + "I7zQgIaziJSD30iheFzm79VTSEHknvwGKdaKeQIAG4E2QMuAipz0Ggfgvkw7HfMO" + + "rOYd996r37ZXhfs2IPlDKLJa0AFpCkQhjmRHjxFOejrE3eG8bjz8PCQ7aAAFItD8" + + "4l3ce6m/jCWaZJzXGj3cJpXjiGraLYaxTWKbp3fENbkCgYEA8J+S8+SqvzzGD7wK" + + "7cb/cYWlSxDRUSZ77x0iNcxMkdrXcrvFpGEYcJWDhrygcn8/+81LC8/JHvWJFfhy" + + "yqQpJqmu8mTy/FtTnf26eYdYqR9QevLBCXOrg65c6M528gss5Oy7f/6Tq8AgTpJk" + + "mIOZ/Z4bGL1BubmuXETeHcdEAp8CgYEA78SiAdXzouaclMlvHWE/ch9EeTSpqJKP" + + "fmWOUDP7e/oY38pJRgJZO2nYaNEgpjepDwjuX49VMWDdJjtw+rYL1MT7rGuiJaRR" + + "3YmV08thLGlakU1iWjvT1LOYuq4OGj5/AkKcDGjEqCGxclqvPtNF83IWoNexxLqh" + + "Au6tT0/mVWUCgYEAmHVC8u1Lkme7RnTqp8WSTCdVl75MIZK0q8hVyKhtS2zRXYzD" + + "qWcryQmykEgrkOA3dh+ZER7SW59PAHCuqt5ghHK2ujZkDqj+zffZku7CqkWBBKWS" + + "0Z5Mad6sV4WZr7qM829bTbnLbuMIlUAEJO4dP6hRmtcvMbIIW8X2xf9fhBkCgYEA" + + "gJqnivSHSckIE4Y34zpWHZBH2fs1RQXXkaRHQR2gtk7fKKoHw1VfJ08OlKoXKRCR" + + "zU6tDPSEbYfXFrqrTs52ahl+JG1W+3m3r2wswP1Fkdywh19KcbvFU0FBml/hkJIU" + + "7dFsgftv//6SfxPFC52m131KRdtrrmmsEzaSHwhsM0ECgYBWwq+Su4ftErsKKYNJ" + + "Zx0Aq8cBGqSOz8+h4oQRjxi7rN/Rn1NAzmW4L0TAFqZhxK6xZFx1zP70dgbnxcWe" + + "Zer7cRS4Vsn4uNvxhYGB4+NIcOhL/r7/7OoHVvm5Cn+NgVthCXnRQ9E9MX66XV5C" + + "jLsXjc2CPf/lwNFqsVl7dlPNmg=="; + + verifyImpl(privateKey1, privateKey2048); + } + + protected void verifyImpl(String privateKey1, String privateKey2048) throws Exception { KeyPairVerifier.verify(privateKey1, publicKey1); KeyPairVerifier.verify(privateKey2048, publicKey2048); diff --git a/core/src/test/java/org/keycloak/util/PemUtilsTest.java b/core/src/test/java/org/keycloak/util/PemUtilsTest.java new file mode 100644 index 00000000000..1daf36b2446 --- /dev/null +++ b/core/src/test/java/org/keycloak/util/PemUtilsTest.java @@ -0,0 +1,136 @@ +package org.keycloak.util; + +import java.security.KeyPair; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.cert.Certificate; +import java.security.cert.X509Certificate; + +import org.junit.Assert; +import org.junit.ClassRule; +import org.junit.Test; +import org.keycloak.common.util.CertificateUtils; +import org.keycloak.common.util.KeyUtils; +import org.keycloak.common.util.PemUtils; +import org.keycloak.rule.CryptoInitRule; + +import static org.junit.Assert.assertEquals; + +public abstract class PemUtilsTest { + + @ClassRule + public static CryptoInitRule cryptoInitRule = new CryptoInitRule(); + + @Test + public void testGenerateThumbprintSha1() throws NoSuchAlgorithmException { + String[] test = new String[] {"abcdefg"}; + String encoded = org.keycloak.common.util.PemUtils.generateThumbprint(test, "SHA-1"); + assertEquals(27, encoded.length()); + } + + @Test + public void testGenerateThumbprintSha256() throws NoSuchAlgorithmException { + String[] test = new String[] {"abcdefg"}; + String encoded = org.keycloak.common.util.PemUtils.generateThumbprint(test, "SHA-256"); + assertEquals(43, encoded.length()); + } + + @Test + public void testEncodeAndDecodeGeneratedObjects() { + KeyPair keyPair = KeyUtils.generateRsaKeyPair(2048); + Certificate certificate = CertificateUtils.generateV1SelfSignedCertificate(keyPair, "FooBar"); + + // Test encoding/decoding private key + String encodedPrivateKey = PemUtils.encodeKey(keyPair.getPrivate()); + PrivateKey decodedPrivateKey = PemUtils.decodePrivateKey(encodedPrivateKey); + assertEquals(decodedPrivateKey, keyPair.getPrivate()); + + // Test encoding/decoding public key + String encodedPublicKey = PemUtils.encodeKey(keyPair.getPublic()); + PublicKey decodedPublicKey = PemUtils.decodePublicKey(encodedPublicKey); + assertEquals(decodedPublicKey, keyPair.getPublic()); + + // Test encoding/decoding certificate + String encodedCertificate = PemUtils.encodeCertificate(certificate); + Certificate decodedCertificate = PemUtils.decodeCertificate(encodedCertificate); + assertEquals(decodedCertificate, certificate); + } + + @Test + public void testDecodeObjectsInPEMFormat() { + String privateKey1 = "MIICXAIBAAKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQABAoGAfmO8gVhyBxdqlxmIuglbz8bcjQbhXJLR2EoS8ngTXmN1bo2L90M0mUKSdc7qF10LgETBzqL8jYlQIbt+e6TH8fcEpKCjUlyq0Mf/vVbfZSNaVycY13nTzo27iPyWQHK5NLuJzn1xvxxrUeXI6A2WFpGEBLbHjwpx5WQG9A+2scECQQDvdn9NE75HPTVPxBqsEd2z10TKkl9CZxu10Qby3iQQmWLEJ9LNmy3acvKrE3gMiYNWb6xHPKiIqOR1as7L24aTAkEAtyvQOlCvr5kAjVqrEKXalj0Tzewjweuxc0pskvArTI2Oo070h65GpoIKLc9jf+UA69cRtquwP93aZKtW06U8dQJAF2Y44ks/mK5+eyDqik3koCI08qaC8HYq2wVl7G2QkJ6sbAaILtcvD92ToOvyGyeE0flvmDZxMYlvaZnaQ0lcSQJBAKZU6umJi3/xeEbkJqMfeLclD27XGEFoPeNrmdx0q10Azp4NfJAY+Z8KRyQCR2BEG+oNitBOZ+YXF9KCpH3cdmECQHEigJhYg+ykOvr1aiZUMFT72HU0jnmQe2FVekuG+LJUt2Tm7GtMjTFoGpf0JwrVuZN39fOYAlo+nTixgeW7X8Y="; + String publicKey1 = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB"; + + String cert1 = "MIICnTCCAYUCBgFPPLDaTzANBgkqhkiG9w0BAQsFADASMRAwDgYDVQQDDAdjbGllbnQxMB4XDTE1MDgxNzE3MjI0N1oXDTI1MDgxNzE3MjQyN1owEjEQMA4GA1UEAwwHY2xpZW50MTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAIUjjgv+V3s96O+Za9002Lp/trtGuHBeaeVL9dFKMKzO2MPqdRmHB4PqNlDdd28Rwf5Xn6iWdFpyUKOnI/yXDLhdcuFpR0sMNK/C9Lt+hSpPFLuzDqgtPgDotlMxiHIWDOZ7g9/gPYNXbNvjv8nSiyqoguoCQiiafW90bPHsiVLdP7ZIUwCcfi1qQm7FhxRJ1NiW5dvUkuCnnWEf0XR+Wzc5eC9EgB0taLFiPsSEIlWMm5xlahYyXkPdNOqZjiRnrTWm5Y4uk8ZcsD/KbPTf/7t7cQXipVaswgjdYi1kK2/zRwOhg1QwWFX/qmvdd+fLxV0R6VqRDhn7Qep2cxwMxLsCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAKE6OA46sf20bz8LZPoiNsqRwBUDkaMGXfnob7s/hJZIIwDEx0IAQ3uKsG7q9wb+aA6s+v7S340zb2k3IxuhFaHaZpAd4CyR5cn1FHylbzoZ7rI/3ASqHDqpljdJaFqPH+m7nZWtyDvtZf+gkZ8OjsndwsSBK1d/jMZPp29qYbl1+XfO7RCp/jDqro/R3saYFaIFiEZPeKn1hUJn6BO48vxH1xspSu9FmlvDOEAOz4AuM58z4zRMP49GcFdCWr1wkonJUHaSptJaQwmBwLFUkCbE5I1ixGMb7mjEud6Y5jhfzJiZMo2U8RfcjNbrN0diZl3jB6LQIwESnhYSghaTjNQ=="; + String cert2 = "MIICnTCCAYUCBgFPPQDGxTANBgkqhkiG9w0BAQsFADASMRAwDgYDVQQDDAdjbGllbnQxMB4XDTE1MDgxNzE4NTAwNVoXDTI1MDgxNzE4NTE0NVowEjEQMA4GA1UEAwwHY2xpZW50MTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMMw3PaBffWxgS2PYSDDBp6As+cNvv9kt2C4f/RDAGmvSIHPFev9kuQiKs3Oaws3ZsV4JG3qHEuYgnh9W4vfe3DwNwtD1bjL5FYBhPBFTw0lAQECYxaBHnkjHwUKp957FqdSPPICm3LjmTcEdlH+9dpp9xHCMbbiNiWDzWI1xSxC8Fs2d0hwz1sd+Q4QeTBPIBWcPM+ICZtNG5MN+ORfayu4X+Me5d0tXG2fQO//rAevk1i5IFjKZuOjTwyKB5SJIY4b8QTeg0g/50IU7Ht00Pxw6CK02dHS+FvXHasZlD3ckomqCDjStTBWdhJo5dST0CbOqalkkpLlCCbGA1yEQRsCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAUIMeJ+EAo8eNpCG/nXImacjrKakbFnZYBGD/gqeTGaZynkX+jgBSructTHR83zSH+yELEhsAy+3BfK4EEihp+PEcRnK2fASVkHste8AQ7rlzC+HGGirlwrVhWCdizNUCGK80DE537IZ7nmZw6LFG9P5/Q2MvCsOCYjRUvMkukq6TdXBXR9tETwZ+0gpSfsOxjj0ZF7ftTRUSzx4rFfcbM9fRNdVizdOuKGc8HJPA5lLOxV6CyaYIvi3y5RlQI1OHeS34lE4w9CNPRFa/vdxXvN7ClyzA0HMFNWxBN7pC/Ht/FbhSvaAagJBHg+vCrcY5C26Oli7lAglf/zZrwUPs0w=="; + + // RSA key in the "traditional" PKCS1 format + String privateKey2 = "-----BEGIN RSA PRIVATE KEY-----\n" + + "MIICXAIBAAKBgQCjcGqTkOq0CR3rTx0ZSQSIdTrDrFAYl29611xN8aVgMQIWtDB/\n" + + "lD0W5TpKPuU9iaiG/sSn/VYt6EzN7Sr332jj7cyl2WrrHI6ujRswNy4HojMuqtfa\n" + + "b5FFDpRmCuvl35fge18OvoQTJELhhJ1EvJ5KUeZiuJ3u3YyMnxxXzLuKbQIDAQAB\n" + + "AoGAPrNDz7TKtaLBvaIuMaMXgBopHyQd3jFKbT/tg2Fu5kYm3PrnmCoQfZYXFKCo\n" + + "ZUFIS/G1FBVWWGpD/MQ9tbYZkKpwuH+t2rGndMnLXiTC296/s9uix7gsjnT4Naci\n" + + "5N6EN9pVUBwQmGrYUTHFc58ThtelSiPARX7LSU2ibtJSv8ECQQDWBRrrAYmbCUN7\n" + + "ra0DFT6SppaDtvvuKtb+mUeKbg0B8U4y4wCIK5GH8EyQSwUWcXnNBO05rlUPbifs\n" + + "DLv/u82lAkEAw39sTJ0KmJJyaChqvqAJ8guulKlgucQJ0Et9ppZyet9iVwNKX/aW\n" + + "9UlwGBMQdafQ36nd1QMEA8AbAw4D+hw/KQJBANJbHDUGQtk2hrSmZNoV5HXB9Uiq\n" + + "7v4N71k5ER8XwgM5yVGs2tX8dMM3RhnBEtQXXs9LW1uJZSOQcv7JGXNnhN0CQBZe\n" + + "nzrJAWxh3XtznHtBfsHWelyCYRIAj4rpCHCmaGUM6IjCVKFUawOYKp5mmAyObkUZ\n" + + "f8ue87emJLEdynC1CLkCQHduNjP1hemAGWrd6v8BHhE3kKtcK6KHsPvJR5dOfzbd\n" + + "HAqVePERhISfN6cwZt5p8B3/JUwSR8el66DF7Jm57BM=\n" + + "-----END RSA PRIVATE KEY-----"; + + testPrivateKeyEncodeDecode(privateKey1); + testPublicKeyEncodeDecode(publicKey1); + testPrivateKeyEncodeDecode(PemUtils.removeBeginEnd(privateKey2).replace("\n", "")); + testCertificateEncodeDecode(cert1); + testCertificateEncodeDecode(cert2); + } + + @Test + public void testPrivateKeyInPKCS8Format() { + String privateKeyPkcs8 = "-----BEGIN PRIVATE KEY-----\n" + + "MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAKNwapOQ6rQJHetP\n" + + "HRlJBIh1OsOsUBiXb3rXXE3xpWAxAha0MH+UPRblOko+5T2JqIb+xKf9Vi3oTM3t\n" + + "KvffaOPtzKXZauscjq6NGzA3LgeiMy6q19pvkUUOlGYK6+Xfl+B7Xw6+hBMkQuGE\n" + + "nUS8nkpR5mK4ne7djIyfHFfMu4ptAgMBAAECgYA+s0PPtMq1osG9oi4xoxeAGikf\n" + + "JB3eMUptP+2DYW7mRibc+ueYKhB9lhcUoKhlQUhL8bUUFVZYakP8xD21thmQqnC4\n" + + "f63asad0ycteJMLb3r+z26LHuCyOdPg1pyLk3oQ32lVQHBCYathRMcVznxOG16VK\n" + + "I8BFfstJTaJu0lK/wQJBANYFGusBiZsJQ3utrQMVPpKmloO2++4q1v6ZR4puDQHx\n" + + "TjLjAIgrkYfwTJBLBRZxec0E7TmuVQ9uJ+wMu/+7zaUCQQDDf2xMnQqYknJoKGq+\n" + + "oAnyC66UqWC5xAnQS32mlnJ632JXA0pf9pb1SXAYExB1p9Dfqd3VAwQDwBsDDgP6\n" + + "HD8pAkEA0lscNQZC2TaGtKZk2hXkdcH1SKru/g3vWTkRHxfCAznJUaza1fx0wzdG\n" + + "GcES1Bdez0tbW4llI5By/skZc2eE3QJAFl6fOskBbGHde3Oce0F+wdZ6XIJhEgCP\n" + + "iukIcKZoZQzoiMJUoVRrA5gqnmaYDI5uRRl/y57zt6YksR3KcLUIuQJAd242M/WF\n" + + "6YAZat3q/wEeETeQq1wrooew+8lHl05/Nt0cCpV48RGEhJ83pzBm3mnwHf8lTBJH\n" + + "x6XroMXsmbnsEw==\n" + + "-----END PRIVATE KEY-----"; + + PrivateKey decodedPrivateKey1 = PemUtils.decodePrivateKey(privateKeyPkcs8); + + // Assert it works also when the "begin/end" section is removed + String pk = PemUtils.removeBeginEnd(privateKeyPkcs8).replace("\n", ""); + PrivateKey decodedPrivateKey2 = PemUtils.decodePrivateKey(pk); + Assert.assertEquals(decodedPrivateKey1, decodedPrivateKey2); + } + + private void testPrivateKeyEncodeDecode(String origPrivateKeyPem) { + PrivateKey decodedPrivateKey = PemUtils.decodePrivateKey(origPrivateKeyPem); + String encodedPrivateKey = PemUtils.encodeKey(decodedPrivateKey); + assertEquals(origPrivateKeyPem, encodedPrivateKey); + } + + private void testPublicKeyEncodeDecode(String origPublicKeyPem) { + PublicKey decodedPublicKey = PemUtils.decodePublicKey(origPublicKeyPem); + String encodedPublicKey = PemUtils.encodeKey(decodedPublicKey); + assertEquals(origPublicKeyPem, encodedPublicKey); + } + + private void testCertificateEncodeDecode(String origCertPem) { + X509Certificate decodedCert = PemUtils.decodeCertificate(origCertPem); + String encodedCert = PemUtils.encodeCertificate(decodedCert); + assertEquals(origCertPem, encodedCert); + } +} diff --git a/crypto/default/src/main/java/org/keycloak/crypto/def/BCPemUtilsProvider.java b/crypto/default/src/main/java/org/keycloak/crypto/def/BCPemUtilsProvider.java index 599682fe829..82e88c410a6 100755 --- a/crypto/default/src/main/java/org/keycloak/crypto/def/BCPemUtilsProvider.java +++ b/crypto/default/src/main/java/org/keycloak/crypto/def/BCPemUtilsProvider.java @@ -18,10 +18,12 @@ package org.keycloak.crypto.def; import org.bouncycastle.openssl.jcajce.JcaPEMWriter; +import org.keycloak.common.util.DerUtils; import org.keycloak.common.util.PemException; import org.keycloak.common.crypto.PemUtilsProvider; import java.io.StringWriter; +import java.security.PrivateKey; /** * Encodes Key or Certificates to PEM format string @@ -57,4 +59,18 @@ public class BCPemUtilsProvider extends PemUtilsProvider { } } + @Override + public PrivateKey decodePrivateKey(String pem) { + if (pem == null) { + return null; + } + + try { + byte[] der = pemToDer(pem); + return DerUtils.decodePrivateKey(der); + } catch (Exception e) { + throw new PemException(e); + } + } + } diff --git a/crypto/default/src/test/java/org/keycloak/crypto/def/test/PemUtilsBCTest.java b/crypto/default/src/test/java/org/keycloak/crypto/def/test/PemUtilsBCTest.java index 0d5d42fddfa..1e414de720a 100644 --- a/crypto/default/src/test/java/org/keycloak/crypto/def/test/PemUtilsBCTest.java +++ b/crypto/default/src/test/java/org/keycloak/crypto/def/test/PemUtilsBCTest.java @@ -2,14 +2,10 @@ package org.keycloak.crypto.def.test; import org.junit.Assume; import org.junit.Before; -import org.junit.Test; import org.keycloak.common.util.Environment; +import org.keycloak.util.PemUtilsTest; -import java.security.NoSuchAlgorithmException; - -import static org.junit.Assert.assertEquals; - -public class PemUtilsBCTest { +public class PemUtilsBCTest extends PemUtilsTest { @Before public void before() { @@ -17,18 +13,5 @@ public class PemUtilsBCTest { Assume.assumeFalse("Java is in FIPS mode. Skipping the test.", Environment.isJavaInFipsMode()); } - @Test - public void testGenerateThumbprintSha1() throws NoSuchAlgorithmException { - String[] test = new String[] {"abcdefg"}; - String encoded = org.keycloak.common.util.PemUtils.generateThumbprint(test, "SHA-1"); - assertEquals(27, encoded.length()); - } - - @Test - public void testGenerateThumbprintSha256() throws NoSuchAlgorithmException { - String[] test = new String[] {"abcdefg"}; - String encoded = org.keycloak.common.util.PemUtils.generateThumbprint(test, "SHA-256"); - assertEquals(43, encoded.length()); - } } diff --git a/crypto/fips1402/src/main/java/org/keycloak/crypto/fips/BCFIPSPemUtilsProvider.java b/crypto/fips1402/src/main/java/org/keycloak/crypto/fips/BCFIPSPemUtilsProvider.java index c4da4dc4bcf..b0b065b95a7 100755 --- a/crypto/fips1402/src/main/java/org/keycloak/crypto/fips/BCFIPSPemUtilsProvider.java +++ b/crypto/fips1402/src/main/java/org/keycloak/crypto/fips/BCFIPSPemUtilsProvider.java @@ -17,11 +17,23 @@ package org.keycloak.crypto.fips; +import org.bouncycastle.asn1.pkcs.PrivateKeyInfo; +import org.bouncycastle.openssl.PEMKeyPair; +import org.bouncycastle.openssl.PEMParser; +import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter; import org.bouncycastle.openssl.jcajce.JcaPEMWriter; +import org.keycloak.common.util.BouncyIntegration; +import org.keycloak.common.util.DerUtils; import org.keycloak.common.util.PemException; import org.keycloak.common.crypto.PemUtilsProvider; +import org.keycloak.common.util.PemUtils; +import java.io.IOException; +import java.io.StringReader; import java.io.StringWriter; +import java.security.KeyFactory; +import java.security.PrivateKey; +import java.security.spec.PKCS8EncodedKeySpec; /** * Encodes Key or Certificates to PEM format string @@ -57,4 +69,52 @@ public class BCFIPSPemUtilsProvider extends PemUtilsProvider { } } + @Override + public PrivateKey decodePrivateKey(String pem) { + if (pem == null) { + return null; + } + + try { + boolean beginEndAvailable = pem.startsWith("-----BEGIN"); + Object parsedPk; + if (beginEndAvailable) { // No fallback needed as BC should know the format of the key (based on the phrase like BEGIN PRIVATE KEY, BEGIN RSA PRIVATE KEY, BEGIN EC PRIVATE KEY etc) + parsedPk = readPrivateKeyObject(pem); + } else { + try { + // Case for the PEM in traditional format + String rsaPem = PemUtils.addRsaPrivateKeyBeginEnd(pem); + parsedPk = readPrivateKeyObject(rsaPem); + } catch (IOException ioe) { + // Case for generic PKCS#8 represented keys + pem = PemUtils.addPrivateKeyBeginEnd(pem); + parsedPk = readPrivateKeyObject(pem); + } + } + + PrivateKeyInfo privateKeyInfo; + if (parsedPk instanceof PEMKeyPair) { + // Usually for keys of known format (For example when PEM starts with "BEGIN RSA PRIVATE KEY") + PEMKeyPair pemKeyPair = (PEMKeyPair)parsedPk; + privateKeyInfo = pemKeyPair.getPrivateKeyInfo(); + } else if (parsedPk instanceof PrivateKeyInfo) { + // Usually for PKCS#8 formatted keys of unknown type ("BEGIN PRIVATE KEY") + privateKeyInfo = (PrivateKeyInfo) parsedPk; + } else { + throw new IllegalStateException("Unknown type returned by PEMParser when parsing private key: " + parsedPk.getClass()); + } + + return new JcaPEMKeyConverter() + .setProvider(BouncyIntegration.PROVIDER) + .getPrivateKey(privateKeyInfo); + } catch (Exception e) { + throw new PemException(e); + } + } + + private Object readPrivateKeyObject(String pemWithBeginEnd) throws IOException { + PEMParser parser = new PEMParser(new StringReader(pemWithBeginEnd)); + return parser.readObject(); + } + } diff --git a/crypto/fips1402/src/test/java/org/keycloak/crypto/fips/test/FIPS1402KeyPairVerifierTest.java b/crypto/fips1402/src/test/java/org/keycloak/crypto/fips/test/FIPS1402KeyPairVerifierTest.java index 7bb1367f285..e80a51fe57e 100644 --- a/crypto/fips1402/src/test/java/org/keycloak/crypto/fips/test/FIPS1402KeyPairVerifierTest.java +++ b/crypto/fips1402/src/test/java/org/keycloak/crypto/fips/test/FIPS1402KeyPairVerifierTest.java @@ -12,55 +12,6 @@ import org.keycloak.common.util.Environment; */ public class FIPS1402KeyPairVerifierTest extends KeyPairVerifierTest { - @Before - public void initPrivKeys() { - - // The parent private key is not supported in FIPS-140-2, using a PKCS#8 formatted key - privateKey1 = "MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAKtWsK5O0CtuBpnM" + - "vWG+HTG0vmZzujQ2o9WdheQu+BzCILcGMsbDW0YQaglpcO5JpGWWhubnckGGPHfd" + - "Q2/7nP9QwbiTK0FbGF41UqcvoaCqU1psxoV88s8IXyQCAqeyLv00yj6foqdJjxh5" + - "SZ5z+na+M7Y2OxIBVxYRAxWEnfUvAgMBAAECgYB+Y7yBWHIHF2qXGYi6CVvPxtyN" + - "BuFcktHYShLyeBNeY3VujYv3QzSZQpJ1zuoXXQuARMHOovyNiVAhu357pMfx9wSk" + - "oKNSXKrQx/+9Vt9lI1pXJxjXedPOjbuI/JZAcrk0u4nOfXG/HGtR5cjoDZYWkYQE" + - "tsePCnHlZAb0D7axwQJBAO92f00Tvkc9NU/EGqwR3bPXRMqSX0JnG7XRBvLeJBCZ" + - "YsQn0s2bLdpy8qsTeAyJg1ZvrEc8qIio5HVqzsvbhpMCQQC3K9A6UK+vmQCNWqsQ" + - "pdqWPRPN7CPB67FzSmyS8CtMjY6jTvSHrkamggotz2N/5QDr1xG2q7A/3dpkq1bT" + - "pTx1AkAXZjjiSz+Yrn57IOqKTeSgIjTypoLwdirbBWXsbZCQnqxsBogu1y8P3ZOg" + - "6/IbJ4TR+W+YNnExiW9pmdpDSVxJAkEAplTq6YmLf/F4RuQmox94tyUPbtcYQWg9" + - "42uZ3HSrXQDOng18kBj5nwpHJAJHYEQb6g2K0E5n5hcX0oKkfdx2YQJAcSKAmFiD" + - "7KQ6+vVqJlQwVPvYdTSOeZB7YVV6S4b4slS3ZObsa0yNMWgal/QnCtW5k3f185gC" + - "Wj6dOLGB5btfxg=="; - - // The parent private key is not supported in FIPS-140-2, using a PKCS#8 formatted key - privateKey2048 = "MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDhXcyk6e4qx0Ft" + - "HVTM2Mr2jmZ4QxDizlWnKSG/UOmpOKUo6IQftVD9e2M3HDTKOcUGKUKekrrI32YM" + - "QdsETNpGO12uBWGQh6OJpcUE/kwGFRDmX27wTchkLcTynAONUXRn27RHiUZ5SDaT" + - "o740q6aoi/GgsJO4bTN/ndty0axF3nsu4WKmAKQ3LG+xw6G6WxsQxisvCVvlswaD" + - "H2/5EKfC+6c0RvtEr7mnM/6IfUxF6NGxf5TuH3MdGfaBCEiXtLMb0Zd5u49avgor" + - "B8wdbLQ8S5Cux9ZFeb391MRq9t82S/kTnfNZeNbJ/3d7ORWEFfRiheZ/pQ7s8CW+" + - "yuOejdO7AgMBAAECggEBALmIIA5wK0t6aGls6UAPBeA+0SsWg1NE7IzGNusqsIJI" + - "iOeJrCPygC9+IerfxLHrJ0FwPFERmMX/7CIRIT6ECnohK3k1IuH6WG7cUrtOosWr" + - "GBOf41PfpSab63STbfUsZrmNzPfLkoIMKioXdmIkIfrF4vEYDTSaafgYu+3loX6O" + - "I7zQgIaziJSD30iheFzm79VTSEHknvwGKdaKeQIAG4E2QMuAipz0Ggfgvkw7HfMO" + - "rOYd996r37ZXhfs2IPlDKLJa0AFpCkQhjmRHjxFOejrE3eG8bjz8PCQ7aAAFItD8" + - "4l3ce6m/jCWaZJzXGj3cJpXjiGraLYaxTWKbp3fENbkCgYEA8J+S8+SqvzzGD7wK" + - "7cb/cYWlSxDRUSZ77x0iNcxMkdrXcrvFpGEYcJWDhrygcn8/+81LC8/JHvWJFfhy" + - "yqQpJqmu8mTy/FtTnf26eYdYqR9QevLBCXOrg65c6M528gss5Oy7f/6Tq8AgTpJk" + - "mIOZ/Z4bGL1BubmuXETeHcdEAp8CgYEA78SiAdXzouaclMlvHWE/ch9EeTSpqJKP" + - "fmWOUDP7e/oY38pJRgJZO2nYaNEgpjepDwjuX49VMWDdJjtw+rYL1MT7rGuiJaRR" + - "3YmV08thLGlakU1iWjvT1LOYuq4OGj5/AkKcDGjEqCGxclqvPtNF83IWoNexxLqh" + - "Au6tT0/mVWUCgYEAmHVC8u1Lkme7RnTqp8WSTCdVl75MIZK0q8hVyKhtS2zRXYzD" + - "qWcryQmykEgrkOA3dh+ZER7SW59PAHCuqt5ghHK2ujZkDqj+zffZku7CqkWBBKWS" + - "0Z5Mad6sV4WZr7qM829bTbnLbuMIlUAEJO4dP6hRmtcvMbIIW8X2xf9fhBkCgYEA" + - "gJqnivSHSckIE4Y34zpWHZBH2fs1RQXXkaRHQR2gtk7fKKoHw1VfJ08OlKoXKRCR" + - "zU6tDPSEbYfXFrqrTs52ahl+JG1W+3m3r2wswP1Fkdywh19KcbvFU0FBml/hkJIU" + - "7dFsgftv//6SfxPFC52m131KRdtrrmmsEzaSHwhsM0ECgYBWwq+Su4ftErsKKYNJ" + - "Zx0Aq8cBGqSOz8+h4oQRjxi7rN/Rn1NAzmW4L0TAFqZhxK6xZFx1zP70dgbnxcWe" + - "Zer7cRS4Vsn4uNvxhYGB4+NIcOhL/r7/7OoHVvm5Cn+NgVthCXnRQ9E9MX66XV5C" + - "jLsXjc2CPf/lwNFqsVl7dlPNmg=="; - - } - @Before public void before() { // Run this test just if java is in FIPS mode diff --git a/crypto/fips1402/src/test/java/org/keycloak/crypto/fips/test/PemUtilsBCFIPSTest.java b/crypto/fips1402/src/test/java/org/keycloak/crypto/fips/test/PemUtilsBCFIPSTest.java index 68f57026d27..b5977171a3c 100644 --- a/crypto/fips1402/src/test/java/org/keycloak/crypto/fips/test/PemUtilsBCFIPSTest.java +++ b/crypto/fips1402/src/test/java/org/keycloak/crypto/fips/test/PemUtilsBCFIPSTest.java @@ -2,34 +2,15 @@ package org.keycloak.crypto.fips.test; import org.junit.Assume; import org.junit.Before; -import org.junit.Test; import org.keycloak.common.util.Environment; -import org.keycloak.common.util.PemUtils; +import org.keycloak.util.PemUtilsTest; -import java.security.NoSuchAlgorithmException; - -import static org.junit.Assert.assertEquals; - -public class PemUtilsBCFIPSTest { +public class PemUtilsBCFIPSTest extends PemUtilsTest { @Before public void before() { // Run this test just if java is in FIPS mode Assume.assumeTrue("Java is not in FIPS mode. Skipping the test.", Environment.isJavaInFipsMode()); } - - @Test - public void testGenerateThumbprintSha1() throws NoSuchAlgorithmException { - String[] test = new String[] {"abcdefg"}; - String encoded = PemUtils.generateThumbprint(test, "SHA-1"); - assertEquals(27, encoded.length()); - } - - @Test - public void testGenerateThumbprintSha256() throws NoSuchAlgorithmException { - String[] test = new String[] {"abcdefg"}; - String encoded = PemUtils.generateThumbprint(test, "SHA-256"); - assertEquals(43, encoded.length()); - } } diff --git a/docs/fips.md b/docs/fips.md index 8a817054a19..ac3f4f2b0b9 100644 --- a/docs/fips.md +++ b/docs/fips.md @@ -37,8 +37,8 @@ cd keycloak-999-SNAPSHOT/bin ./kc.sh start-dev ``` -NOTE: Right now, server should start, and I am able to create admin user on `http://localhost:8080`, but I am not able to finish -login to the admin console. However the Keycloak uses bouncycastle-fips libraries and the `CryptoIntegration` uses `FIPS1402Provider`. More fixes are required to have Keycloak server working... +NOTE: Right now, server should start, and you should be able to use `http://localhost:8080` and login to admin console etc. +Keycloak will now use bouncycastle-fips libraries and the `CryptoIntegration` will use `FIPS1402Provider`. Run the tests in the FIPS environment -------------------------------------