closes #26936 Signed-off-by: Stefan Wiedemann <wistefan@googlemail.com>
This commit is contained in:
parent
018914d7fd
commit
aa6b102e3d
13 changed files with 546 additions and 117 deletions
|
@ -1,13 +1,14 @@
|
|||
package org.keycloak.common.crypto;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.security.interfaces.ECPrivateKey;
|
||||
import java.security.interfaces.ECPublicKey;
|
||||
|
||||
public interface ECDSACryptoProvider {
|
||||
|
||||
|
||||
|
||||
public byte[] concatenatedRSToASN1DER(final byte[] signature, int signLength) throws IOException;
|
||||
|
||||
public byte[] asn1derToConcatenatedRS(final byte[] derEncodedSignatureValue, int signLength) throws IOException;
|
||||
|
||||
|
||||
public ECPublicKey getPublicFromPrivate(ECPrivateKey ecPrivateKey);
|
||||
}
|
||||
|
|
|
@ -67,7 +67,7 @@ import java.util.LinkedList;
|
|||
import java.util.List;
|
||||
|
||||
/**
|
||||
* The Class CertificateUtils provides utility functions for generation of V1 and V3 {@link java.security.cert.X509Certificate}
|
||||
* The Class CertificateUtils provides utility functions for generation of V1 and V3 {@link X509Certificate}
|
||||
*
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @author <a href="mailto:giriraj.sharma27@gmail.com">Giriraj Sharma</a>
|
||||
|
@ -76,19 +76,17 @@ import java.util.List;
|
|||
public class BCCertificateUtilsProvider implements CertificateUtilsProvider {
|
||||
|
||||
/**
|
||||
* Generates version 3 {@link java.security.cert.X509Certificate}.
|
||||
* Generates version 3 {@link X509Certificate}.
|
||||
*
|
||||
* @param keyPair the key pair
|
||||
* @param keyPair the key pair
|
||||
* @param caPrivateKey the CA private key
|
||||
* @param caCert the CA certificate
|
||||
* @param subject the subject name
|
||||
*
|
||||
* @param caCert the CA certificate
|
||||
* @param subject the subject name
|
||||
* @return the x509 certificate
|
||||
*
|
||||
* @throws Exception the exception
|
||||
*/
|
||||
public X509Certificate generateV3Certificate(KeyPair keyPair, PrivateKey caPrivateKey, X509Certificate caCert,
|
||||
String subject) throws Exception {
|
||||
String subject) throws Exception {
|
||||
try {
|
||||
X500Name subjectDN = new X500Name("CN=" + subject);
|
||||
|
||||
|
@ -141,13 +139,11 @@ public class BCCertificateUtilsProvider implements CertificateUtilsProvider {
|
|||
}
|
||||
|
||||
/**
|
||||
* Generate version 1 self signed {@link java.security.cert.X509Certificate}..
|
||||
* Generate version 1 self signed {@link X509Certificate}..
|
||||
*
|
||||
* @param caKeyPair the CA key pair
|
||||
* @param subject the subject name
|
||||
*
|
||||
* @param subject the subject name
|
||||
* @return the x509 certificate
|
||||
*
|
||||
* @throws Exception the exception
|
||||
*/
|
||||
public X509Certificate generateV1SelfSignedCertificate(KeyPair caKeyPair, String subject) {
|
||||
|
@ -174,16 +170,30 @@ public class BCCertificateUtilsProvider implements CertificateUtilsProvider {
|
|||
}
|
||||
|
||||
/**
|
||||
* Creates the content signer for generation of Version 1 {@link java.security.cert.X509Certificate}.
|
||||
* Creates the content signer for generation of Version 1 {@link X509Certificate}.
|
||||
*
|
||||
* @param privateKey the private key
|
||||
*
|
||||
* @return the content signer
|
||||
*/
|
||||
private ContentSigner createSigner(PrivateKey privateKey) {
|
||||
try {
|
||||
JcaContentSignerBuilder signerBuilder = new JcaContentSignerBuilder("SHA256WithRSAEncryption")
|
||||
.setProvider(BouncyIntegration.PROVIDER);
|
||||
JcaContentSignerBuilder signerBuilder;
|
||||
switch (privateKey.getAlgorithm()) {
|
||||
case "RSA": {
|
||||
signerBuilder = new JcaContentSignerBuilder("SHA256WithRSAEncryption")
|
||||
.setProvider(BouncyIntegration.PROVIDER);
|
||||
break;
|
||||
}
|
||||
case "ECDSA": {
|
||||
signerBuilder = new JcaContentSignerBuilder("SHA256WithECDSA")
|
||||
.setProvider(BouncyIntegration.PROVIDER);
|
||||
break;
|
||||
|
||||
}
|
||||
default: {
|
||||
throw new RuntimeException(String.format("Keytype %s is not supported.", privateKey.getAlgorithm()));
|
||||
}
|
||||
}
|
||||
return signerBuilder.build(privateKey);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Could not create content signer.", e);
|
||||
|
@ -192,7 +202,7 @@ public class BCCertificateUtilsProvider implements CertificateUtilsProvider {
|
|||
|
||||
@Override
|
||||
public List<String> getCertificatePolicyList(X509Certificate cert) throws GeneralSecurityException {
|
||||
|
||||
|
||||
Extensions certExtensions = new JcaX509CertificateHolder(cert).getExtensions();
|
||||
if (certExtensions == null)
|
||||
throw new GeneralSecurityException("Certificate Policy validation was expected, but no certificate extensions were found");
|
||||
|
@ -212,6 +222,7 @@ public class BCCertificateUtilsProvider implements CertificateUtilsProvider {
|
|||
/**
|
||||
* Retrieves a list of CRL distribution points from CRLDP v3 certificate extension
|
||||
* See <a href="www.nakov.com/blog/2009/12/01/x509-certificate-validation-in-java-build-and-verify-cchain-and-verify-clr-with-bouncy-castle/">CRL validation</a>
|
||||
*
|
||||
* @param cert
|
||||
* @return
|
||||
* @throws IOException
|
||||
|
@ -225,7 +236,7 @@ public class BCCertificateUtilsProvider implements CertificateUtilsProvider {
|
|||
List<String> distributionPointUrls = new LinkedList<>();
|
||||
DEROctetString octetString;
|
||||
try (ASN1InputStream crldpExtensionInputStream = new ASN1InputStream(new ByteArrayInputStream(data))) {
|
||||
octetString = (DEROctetString)crldpExtensionInputStream.readObject();
|
||||
octetString = (DEROctetString) crldpExtensionInputStream.readObject();
|
||||
}
|
||||
byte[] octets = octetString.getOctets();
|
||||
|
||||
|
@ -251,28 +262,26 @@ public class BCCertificateUtilsProvider implements CertificateUtilsProvider {
|
|||
}
|
||||
|
||||
public X509Certificate createServicesTestCertificate(String dn,
|
||||
Date startDate,
|
||||
Date expiryDate,
|
||||
KeyPair keyPair,
|
||||
String... certificatePolicyOid) {
|
||||
Date startDate,
|
||||
Date expiryDate,
|
||||
KeyPair keyPair,
|
||||
String... certificatePolicyOid) {
|
||||
// Cert data
|
||||
X500Name subjectDN = new X500Name(dn);
|
||||
X500Name issuerDN = new X500Name(dn);
|
||||
|
||||
SubjectPublicKeyInfo subjPubKeyInfo = SubjectPublicKeyInfo.getInstance(
|
||||
ASN1Sequence.getInstance(keyPair.getPublic().getEncoded()));
|
||||
ASN1Sequence.getInstance(keyPair.getPublic().getEncoded()));
|
||||
|
||||
BigInteger serialNumber = new BigInteger(130, new SecureRandom());
|
||||
|
||||
// Build the certificate
|
||||
X509v3CertificateBuilder certGen = new X509v3CertificateBuilder(issuerDN, serialNumber, startDate, expiryDate,
|
||||
subjectDN, subjPubKeyInfo);
|
||||
subjectDN, subjPubKeyInfo);
|
||||
|
||||
if (certificatePolicyOid != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
for (Extension certExtension: certPolicyExtensions(certificatePolicyOid))
|
||||
if (certificatePolicyOid != null) {
|
||||
try {
|
||||
for (Extension certExtension : certPolicyExtensions(certificatePolicyOid))
|
||||
certGen.addExtension(certExtension);
|
||||
} catch (CertIOException e) {
|
||||
throw new IllegalStateException(e);
|
||||
|
@ -282,11 +291,11 @@ public class BCCertificateUtilsProvider implements CertificateUtilsProvider {
|
|||
// Sign the cert with the private key
|
||||
try {
|
||||
ContentSigner contentSigner = new JcaContentSignerBuilder("SHA256withRSA")
|
||||
.setProvider(BouncyIntegration.PROVIDER)
|
||||
.build(keyPair.getPrivate());
|
||||
.setProvider(BouncyIntegration.PROVIDER)
|
||||
.build(keyPair.getPrivate());
|
||||
X509Certificate x509Certificate = new JcaX509CertificateConverter()
|
||||
.setProvider(BouncyIntegration.PROVIDER)
|
||||
.getCertificate(certGen.build(contentSigner));
|
||||
.setProvider(BouncyIntegration.PROVIDER)
|
||||
.getCertificate(certGen.build(contentSigner));
|
||||
|
||||
return x509Certificate;
|
||||
} catch (CertificateException | OperatorCreationException e) {
|
||||
|
@ -297,11 +306,9 @@ public class BCCertificateUtilsProvider implements CertificateUtilsProvider {
|
|||
private List<Extension> certPolicyExtensions(String... certificatePolicyOid) {
|
||||
List<Extension> certificatePolicies = new LinkedList<>();
|
||||
|
||||
if (certificatePolicyOid != null && certificatePolicyOid.length > 0)
|
||||
{
|
||||
if (certificatePolicyOid != null && certificatePolicyOid.length > 0) {
|
||||
List<PolicyInformation> policyInfoList = new LinkedList<>();
|
||||
for (String oid: certificatePolicyOid)
|
||||
{
|
||||
for (String oid : certificatePolicyOid) {
|
||||
policyInfoList.add(new PolicyInformation(new ASN1ObjectIdentifier(oid)));
|
||||
}
|
||||
|
||||
|
|
|
@ -1,17 +1,26 @@
|
|||
package org.keycloak.crypto.def;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.math.BigInteger;
|
||||
|
||||
import org.bouncycastle.asn1.ASN1InputStream;
|
||||
import org.bouncycastle.asn1.ASN1Integer;
|
||||
import org.bouncycastle.asn1.ASN1Primitive;
|
||||
import org.bouncycastle.asn1.ASN1Sequence;
|
||||
import org.bouncycastle.asn1.DERSequenceGenerator;
|
||||
import org.bouncycastle.asn1.x9.X9IntegerConverter;
|
||||
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey;
|
||||
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||
import org.bouncycastle.jce.spec.ECPublicKeySpec;
|
||||
import org.bouncycastle.math.ec.ECPoint;
|
||||
import org.keycloak.common.crypto.ECDSACryptoProvider;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.math.BigInteger;
|
||||
import java.security.KeyFactory;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.interfaces.ECPrivateKey;
|
||||
import java.security.interfaces.ECPublicKey;
|
||||
import java.security.spec.InvalidKeySpecException;
|
||||
|
||||
public class BCECDSACryptoProvider implements ECDSACryptoProvider {
|
||||
|
||||
|
||||
|
@ -60,5 +69,22 @@ public class BCECDSACryptoProvider implements ECDSACryptoProvider {
|
|||
return concatenatedSignatureValue;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public ECPublicKey getPublicFromPrivate(ECPrivateKey ecPrivateKey) {
|
||||
try {
|
||||
BCECPrivateKey bcecPrivateKey = new BCECPrivateKey(ecPrivateKey, BouncyCastleProvider.CONFIGURATION);
|
||||
|
||||
ECPoint q = bcecPrivateKey.getParameters().getG().multiply(bcecPrivateKey.getD());
|
||||
|
||||
ECPublicKeySpec publicKeySpec = new ECPublicKeySpec(q, bcecPrivateKey.getParameters());
|
||||
KeyFactory keyFactory = KeyFactory.getInstance("EC");
|
||||
return (ECPublicKey) keyFactory.generatePublic(publicKeySpec);
|
||||
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new RuntimeException("Key algorithm not supported.", e);
|
||||
} catch (InvalidKeySpecException e) {
|
||||
throw new RuntimeException("Received an invalid key spec.", e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,81 @@
|
|||
/*
|
||||
* Copyright 2023 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed 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.keycloak.crypto.def.test;
|
||||
|
||||
import org.junit.ClassRule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.Parameterized;
|
||||
import org.keycloak.common.crypto.CryptoIntegration;
|
||||
import org.keycloak.crypto.def.BCECDSACryptoProvider;
|
||||
import org.keycloak.rule.CryptoInitRule;
|
||||
|
||||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.KeyPair;
|
||||
import java.security.KeyPairGenerator;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.NoSuchProviderException;
|
||||
import java.security.interfaces.ECPrivateKey;
|
||||
import java.security.spec.ECGenParameterSpec;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
@RunWith(Parameterized.class)
|
||||
public class BCECDSACryptoProviderTest {
|
||||
|
||||
|
||||
@ClassRule
|
||||
public static CryptoInitRule cryptoInitRule = new CryptoInitRule();
|
||||
|
||||
@Parameterized.Parameters
|
||||
public static Collection<Object[]> data() {
|
||||
return Arrays.asList(new Object[][]{
|
||||
{"secp256r1"}, {"secp384r1"}, {"secp521r1"}
|
||||
});
|
||||
}
|
||||
|
||||
private String curve;
|
||||
|
||||
public BCECDSACryptoProviderTest(String curve) {
|
||||
this.curve = curve;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getPublicFromPrivate() {
|
||||
KeyPair testKey = generateECKey(curve);
|
||||
|
||||
BCECDSACryptoProvider bcecdsaCryptoProvider = new BCECDSACryptoProvider();
|
||||
assertEquals("The derived key should be equal to the originally generated one.",
|
||||
testKey.getPublic(),
|
||||
bcecdsaCryptoProvider.getPublicFromPrivate((ECPrivateKey) testKey.getPrivate()));
|
||||
|
||||
}
|
||||
|
||||
public static KeyPair generateECKey(String curve) {
|
||||
|
||||
try {
|
||||
KeyPairGenerator kpg = CryptoIntegration.getProvider().getKeyPairGen("ECDSA");
|
||||
ECGenParameterSpec parameterSpec = new ECGenParameterSpec(curve);
|
||||
kpg.initialize(parameterSpec);
|
||||
return kpg.generateKeyPair();
|
||||
} catch (NoSuchAlgorithmException | NoSuchProviderException | InvalidAlgorithmParameterException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -18,6 +18,8 @@ package org.keycloak.crypto.elytron;
|
|||
|
||||
import java.io.IOException;
|
||||
import java.math.BigInteger;
|
||||
import java.security.interfaces.ECPrivateKey;
|
||||
import java.security.interfaces.ECPublicKey;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.common.crypto.ECDSACryptoProvider;
|
||||
|
@ -51,7 +53,7 @@ public class ElytronECDSACryptoProvider implements ECDSACryptoProvider {
|
|||
seq.endSequence();
|
||||
|
||||
return seq.getEncoded();
|
||||
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -60,8 +62,8 @@ public class ElytronECDSACryptoProvider implements ECDSACryptoProvider {
|
|||
|
||||
DERDecoder der = new DERDecoder(derEncodedSignatureValue);
|
||||
der.startSequence();
|
||||
byte[] r = convertToBytes(der.decodeInteger(),len);
|
||||
byte[] s = convertToBytes(der.decodeInteger(),len);
|
||||
byte[] r = convertToBytes(der.decodeInteger(), len);
|
||||
byte[] s = convertToBytes(der.decodeInteger(), len);
|
||||
der.endSequence();
|
||||
byte[] concatenatedSignatureValue = new byte[signLength];
|
||||
|
||||
|
@ -71,13 +73,18 @@ public class ElytronECDSACryptoProvider implements ECDSACryptoProvider {
|
|||
return concatenatedSignatureValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ECPublicKey getPublicFromPrivate(ECPrivateKey ecPrivateKey) {
|
||||
throw new UnsupportedOperationException("Elytron Crypto Provider currently does not support extraction of EC Public Keys.");
|
||||
}
|
||||
|
||||
// If byte array length doesn't match expected length, copy to new
|
||||
// byte array of the expected length
|
||||
private byte[] convertToBytes(BigInteger decodeInteger, int len) {
|
||||
|
||||
byte[] bytes = decodeInteger.toByteArray();
|
||||
|
||||
if(len < bytes.length) {
|
||||
if (len < bytes.length) {
|
||||
log.debug("Decoded integer byte length greater than expected.");
|
||||
byte[] t = new byte[len];
|
||||
System.arraycopy(bytes, bytes.length - len, t, 0, len);
|
||||
|
|
|
@ -67,7 +67,7 @@ import java.util.LinkedList;
|
|||
import java.util.List;
|
||||
|
||||
/**
|
||||
* The Class CertificateUtils provides utility functions for generation of V1 and V3 {@link java.security.cert.X509Certificate}
|
||||
* The Class CertificateUtils provides utility functions for generation of V1 and V3 {@link X509Certificate}
|
||||
*
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @author <a href="mailto:giriraj.sharma27@gmail.com">Giriraj Sharma</a>
|
||||
|
@ -76,7 +76,7 @@ import java.util.List;
|
|||
public class BCFIPSCertificateUtilsProvider implements CertificateUtilsProvider{
|
||||
|
||||
/**
|
||||
* Generates version 3 {@link java.security.cert.X509Certificate}.
|
||||
* Generates version 3 {@link X509Certificate}.
|
||||
*
|
||||
* @param keyPair the key pair
|
||||
* @param caPrivateKey the CA private key
|
||||
|
@ -141,7 +141,7 @@ public class BCFIPSCertificateUtilsProvider implements CertificateUtilsProvider{
|
|||
}
|
||||
|
||||
/**
|
||||
* Generate version 1 self signed {@link java.security.cert.X509Certificate}..
|
||||
* Generate version 1 self signed {@link X509Certificate}..
|
||||
*
|
||||
* @param caKeyPair the CA key pair
|
||||
* @param subject the subject name
|
||||
|
@ -174,7 +174,7 @@ public class BCFIPSCertificateUtilsProvider implements CertificateUtilsProvider{
|
|||
}
|
||||
|
||||
/**
|
||||
* Creates the content signer for generation of Version 1 {@link java.security.cert.X509Certificate}.
|
||||
* Creates the content signer for generation of Version 1 {@link X509Certificate}.
|
||||
*
|
||||
* @param privateKey the private key
|
||||
*
|
||||
|
@ -182,8 +182,23 @@ public class BCFIPSCertificateUtilsProvider implements CertificateUtilsProvider{
|
|||
*/
|
||||
private ContentSigner createSigner(PrivateKey privateKey) {
|
||||
try {
|
||||
JcaContentSignerBuilder signerBuilder = new JcaContentSignerBuilder("SHA256WithRSAEncryption")
|
||||
.setProvider(BouncyIntegration.PROVIDER);
|
||||
JcaContentSignerBuilder signerBuilder;
|
||||
switch (privateKey.getAlgorithm()) {
|
||||
case "RSA": {
|
||||
signerBuilder = new JcaContentSignerBuilder("SHA256WithRSAEncryption")
|
||||
.setProvider(BouncyIntegration.PROVIDER);
|
||||
break;
|
||||
}
|
||||
case "EC": {
|
||||
signerBuilder = new JcaContentSignerBuilder("SHA256WithECDSA")
|
||||
.setProvider(BouncyIntegration.PROVIDER);
|
||||
break;
|
||||
|
||||
}
|
||||
default: {
|
||||
throw new RuntimeException(String.format("Keytype %s is not supported.", privateKey.getAlgorithm()));
|
||||
}
|
||||
}
|
||||
return signerBuilder.build(privateKey);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Could not create content signer.", e);
|
||||
|
|
|
@ -1,17 +1,26 @@
|
|||
package org.keycloak.crypto.fips;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.math.BigInteger;
|
||||
|
||||
import org.bouncycastle.asn1.ASN1InputStream;
|
||||
import org.bouncycastle.asn1.ASN1Integer;
|
||||
import org.bouncycastle.asn1.ASN1Primitive;
|
||||
import org.bouncycastle.asn1.ASN1Sequence;
|
||||
import org.bouncycastle.asn1.DERSequenceGenerator;
|
||||
import org.bouncycastle.asn1.x9.X9IntegerConverter;
|
||||
import org.bouncycastle.jcajce.spec.ECDomainParameterSpec;
|
||||
import org.bouncycastle.math.ec.ECPoint;
|
||||
import org.keycloak.common.crypto.ECDSACryptoProvider;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.math.BigInteger;
|
||||
import java.security.KeyFactory;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.interfaces.ECPrivateKey;
|
||||
import java.security.interfaces.ECPublicKey;
|
||||
import java.security.spec.ECParameterSpec;
|
||||
import java.security.spec.ECPublicKeySpec;
|
||||
import java.security.spec.InvalidKeySpecException;
|
||||
|
||||
public class BCFIPSECDSACryptoProvider implements ECDSACryptoProvider {
|
||||
|
||||
|
||||
|
@ -60,5 +69,27 @@ public class BCFIPSECDSACryptoProvider implements ECDSACryptoProvider {
|
|||
return concatenatedSignatureValue;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public ECPublicKey getPublicFromPrivate(ECPrivateKey ecPrivateKey) {
|
||||
try {
|
||||
ECParameterSpec parameterSpec = ecPrivateKey.getParams();
|
||||
ECDomainParameterSpec domainParameterSpec = new ECDomainParameterSpec(parameterSpec);
|
||||
|
||||
ECPoint q = domainParameterSpec.getDomainParameters().getG().multiply(ecPrivateKey.getS()).normalize();
|
||||
ECPublicKeySpec ecPublicKeySpec = new ECPublicKeySpec(
|
||||
new java.security.spec.ECPoint(
|
||||
q.getAffineXCoord().toBigInteger(),
|
||||
q.getAffineYCoord().toBigInteger()),
|
||||
domainParameterSpec);
|
||||
|
||||
KeyFactory keyFactory = KeyFactory.getInstance("EC");
|
||||
return (ECPublicKey) keyFactory.generatePublic(ecPublicKeySpec);
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new RuntimeException("Key algorithm not supported.", e);
|
||||
} catch (InvalidKeySpecException e) {
|
||||
throw new RuntimeException("Received an invalid key spec.", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,85 @@
|
|||
/*
|
||||
* Copyright 2023 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed 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.keycloak.crypto.fips.test;
|
||||
|
||||
import org.junit.ClassRule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.Parameterized;
|
||||
import org.keycloak.common.crypto.CryptoIntegration;
|
||||
import org.keycloak.crypto.Algorithm;
|
||||
import org.keycloak.crypto.fips.BCFIPSECDSACryptoProvider;
|
||||
import org.keycloak.keys.AbstractEcdsaKeyProviderFactory;
|
||||
import org.keycloak.rule.CryptoInitRule;
|
||||
|
||||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.KeyPair;
|
||||
import java.security.KeyPairGenerator;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.NoSuchProviderException;
|
||||
import java.security.interfaces.ECPrivateKey;
|
||||
import java.security.interfaces.ECPublicKey;
|
||||
import java.security.spec.ECGenParameterSpec;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
@RunWith(Parameterized.class)
|
||||
public class BCFIPSECDSACryptoProviderTest {
|
||||
|
||||
@ClassRule
|
||||
public static CryptoInitRule cryptoInitRule = new CryptoInitRule();
|
||||
|
||||
@Parameterized.Parameters
|
||||
public static Collection<Object[]> data() {
|
||||
return Arrays.asList(new Object[][]{
|
||||
{Algorithm.ES256}, {Algorithm.ES384}, {Algorithm.ES512}
|
||||
});
|
||||
}
|
||||
|
||||
private String algorithm;
|
||||
|
||||
public BCFIPSECDSACryptoProviderTest(String algorithm) {
|
||||
this.algorithm = algorithm;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getPublicFromPrivate() {
|
||||
KeyPair testKey = generateECKey(algorithm);
|
||||
|
||||
BCFIPSECDSACryptoProvider bcfipsecdsaCryptoProvider = new BCFIPSECDSACryptoProvider();
|
||||
ECPublicKey derivedKey = bcfipsecdsaCryptoProvider.getPublicFromPrivate((ECPrivateKey) testKey.getPrivate());
|
||||
assertEquals("The derived key should be equal to the originally generated one.",
|
||||
testKey.getPublic(),
|
||||
derivedKey);
|
||||
}
|
||||
|
||||
public static KeyPair generateECKey(String algorithm) {
|
||||
|
||||
try {
|
||||
KeyPairGenerator kpg = CryptoIntegration.getProvider().getKeyPairGen("ECDSA");
|
||||
String domainParamNistRep = AbstractEcdsaKeyProviderFactory.convertAlgorithmToECDomainParmNistRep(algorithm);
|
||||
String curve = AbstractEcdsaKeyProviderFactory.convertECDomainParmNistRepToSecRep(domainParamNistRep);
|
||||
ECGenParameterSpec parameterSpec = new ECGenParameterSpec(curve);
|
||||
kpg.initialize(parameterSpec);
|
||||
return kpg.generateKeyPair();
|
||||
} catch (NoSuchAlgorithmException | NoSuchProviderException | InvalidAlgorithmParameterException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -17,12 +17,17 @@
|
|||
|
||||
package org.keycloak.keys;
|
||||
|
||||
import org.keycloak.common.crypto.CryptoIntegration;
|
||||
import org.keycloak.common.util.CertificateUtils;
|
||||
import org.keycloak.common.util.KeyUtils;
|
||||
import org.keycloak.common.util.KeystoreUtil;
|
||||
import org.keycloak.component.ComponentModel;
|
||||
import org.keycloak.crypto.Algorithm;
|
||||
import org.keycloak.crypto.KeyStatus;
|
||||
import org.keycloak.crypto.KeyType;
|
||||
import org.keycloak.crypto.KeyUse;
|
||||
import org.keycloak.crypto.KeyWrapper;
|
||||
import org.keycloak.jose.jwe.JWEConstants;
|
||||
import org.keycloak.models.RealmModel;
|
||||
|
||||
import java.io.FileInputStream;
|
||||
|
@ -43,6 +48,7 @@ import java.security.cert.CertificateFactory;
|
|||
import java.security.cert.PKIXParameters;
|
||||
import java.security.cert.TrustAnchor;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.security.interfaces.ECPrivateKey;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
|
@ -50,39 +56,49 @@ import java.util.List;
|
|||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public class JavaKeystoreKeyProvider extends AbstractRsaKeyProvider {
|
||||
public class JavaKeystoreKeyProvider implements KeyProvider {
|
||||
|
||||
private final KeyStatus status;
|
||||
|
||||
private final ComponentModel model;
|
||||
|
||||
private final KeyWrapper key;
|
||||
|
||||
private final String algorithm;
|
||||
|
||||
public JavaKeystoreKeyProvider(RealmModel realm, ComponentModel model) {
|
||||
super(realm, model);
|
||||
this.model = model;
|
||||
this.status = KeyStatus.from(model.get(Attributes.ACTIVE_KEY, true), model.get(Attributes.ENABLED_KEY, true));
|
||||
|
||||
String defaultAlgorithmKey = KeyUse.ENC.name().equals(model.get(Attributes.KEY_USE)) ? JWEConstants.RSA_OAEP : Algorithm.RS256;
|
||||
this.algorithm = model.get(Attributes.ALGORITHM_KEY, defaultAlgorithmKey);
|
||||
|
||||
if (model.hasNote(KeyWrapper.class.getName())) {
|
||||
key = model.getNote(KeyWrapper.class.getName());
|
||||
} else {
|
||||
key = loadKey(realm, model);
|
||||
model.setNote(KeyWrapper.class.getName(), key);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected KeyWrapper loadKey(RealmModel realm, ComponentModel model) {
|
||||
String keystorePath = model.get(JavaKeystoreKeyProviderFactory.KEYSTORE_KEY);
|
||||
try (FileInputStream is = new FileInputStream(keystorePath)) {
|
||||
// Use "JKS" as default type for backwards compatibility
|
||||
String keystoreType = KeystoreUtil.getKeystoreType(model.get(JavaKeystoreKeyProviderFactory.KEYSTORE_TYPE_KEY), keystorePath, "JKS");
|
||||
KeyStore keyStore = KeyStore.getInstance(keystoreType);
|
||||
keyStore.load(is, model.get(JavaKeystoreKeyProviderFactory.KEYSTORE_PASSWORD_KEY).toCharArray());
|
||||
|
||||
KeyStore keyStore = loadKeyStore(is, keystorePath);
|
||||
String keyAlias = model.get(JavaKeystoreKeyProviderFactory.KEY_ALIAS_KEY);
|
||||
PrivateKey privateKey = (PrivateKey) keyStore.getKey(keyAlias, model.get(JavaKeystoreKeyProviderFactory.KEY_PASSWORD_KEY).toCharArray());
|
||||
PublicKey publicKey = KeyUtils.extractPublicKey(privateKey);
|
||||
|
||||
KeyPair keyPair = new KeyPair(publicKey, privateKey);
|
||||
|
||||
X509Certificate certificate = (X509Certificate) keyStore.getCertificate(keyAlias);
|
||||
if (certificate == null) {
|
||||
certificate = CertificateUtils.generateV1SelfSignedCertificate(keyPair, realm.getName());
|
||||
}
|
||||
|
||||
KeyUse keyUse = KeyUse.valueOf(model.get(Attributes.KEY_USE, KeyUse.SIG.getSpecName()).toUpperCase());
|
||||
|
||||
return createKeyWrapper(keyPair, certificate, loadCertificateChain(keyStore, keyAlias), keyUse);
|
||||
return switch (algorithm) {
|
||||
case Algorithm.PS256, Algorithm.PS384, Algorithm.PS512, Algorithm.RS256, Algorithm.RS384, Algorithm.RS512 ->
|
||||
loadRSAKey(realm, model, keyStore, keyAlias);
|
||||
case Algorithm.ES256, Algorithm.ES384, Algorithm.ES512 -> loadECKey(realm, model, keyStore, keyAlias);
|
||||
default ->
|
||||
throw new RuntimeException(String.format("Keys for algorithm %s are not supported.", algorithm));
|
||||
};
|
||||
} catch (KeyStoreException kse) {
|
||||
throw new RuntimeException("KeyStore error on server. " + kse.getMessage(), kse);
|
||||
} catch (FileNotFoundException fnfe) {
|
||||
|
@ -100,6 +116,44 @@ public class JavaKeystoreKeyProvider extends AbstractRsaKeyProvider {
|
|||
}
|
||||
}
|
||||
|
||||
private KeyStore loadKeyStore(FileInputStream inputStream, String keystorePath) throws KeyStoreException, CertificateException, IOException, NoSuchAlgorithmException {
|
||||
// Use "JKS" as default type for backwards compatibility
|
||||
String keystoreType = KeystoreUtil.getKeystoreType(model.get(JavaKeystoreKeyProviderFactory.KEYSTORE_TYPE_KEY), keystorePath, "JKS");
|
||||
KeyStore keyStore = KeyStore.getInstance(keystoreType);
|
||||
keyStore.load(inputStream, model.get(JavaKeystoreKeyProviderFactory.KEYSTORE_PASSWORD_KEY).toCharArray());
|
||||
return keyStore;
|
||||
}
|
||||
|
||||
|
||||
private KeyWrapper loadECKey(RealmModel realm, ComponentModel model, KeyStore keyStore, String keyAlias) throws GeneralSecurityException {
|
||||
ECPrivateKey privateKey = (ECPrivateKey) keyStore.getKey(keyAlias, model.get(JavaKeystoreKeyProviderFactory.KEY_PASSWORD_KEY).toCharArray());
|
||||
String curve = AbstractEcdsaKeyProviderFactory.convertECDomainParmNistRepToSecRep(AbstractEcdsaKeyProviderFactory.convertAlgorithmToECDomainParmNistRep(algorithm));
|
||||
|
||||
PublicKey publicKey = CryptoIntegration.getProvider().getEcdsaCryptoProvider().getPublicFromPrivate(privateKey);
|
||||
|
||||
KeyPair keyPair = new KeyPair(publicKey, privateKey);
|
||||
|
||||
return createKeyWrapper(keyPair, getCertificate(keyStore, keyPair, keyAlias, realm.getName()), loadCertificateChain(keyStore, keyAlias), KeyType.EC);
|
||||
|
||||
}
|
||||
|
||||
private X509Certificate getCertificate(KeyStore keyStore, KeyPair keyPair, String keyAlias, String realmName) throws KeyStoreException {
|
||||
X509Certificate certificate = (X509Certificate) keyStore.getCertificate(keyAlias);
|
||||
if (certificate == null) {
|
||||
certificate = CertificateUtils.generateV1SelfSignedCertificate(keyPair, realmName);
|
||||
}
|
||||
return certificate;
|
||||
}
|
||||
|
||||
private KeyWrapper loadRSAKey(RealmModel realm, ComponentModel model, KeyStore keyStore, String keyAlias) throws GeneralSecurityException {
|
||||
PrivateKey privateKey = (PrivateKey) keyStore.getKey(keyAlias, model.get(JavaKeystoreKeyProviderFactory.KEY_PASSWORD_KEY).toCharArray());
|
||||
PublicKey publicKey = KeyUtils.extractPublicKey(privateKey);
|
||||
|
||||
KeyPair keyPair = new KeyPair(publicKey, privateKey);
|
||||
|
||||
return createKeyWrapper(keyPair, getCertificate(keyStore, keyPair, keyAlias, realm.getName()), loadCertificateChain(keyStore, keyAlias), KeyType.RSA);
|
||||
}
|
||||
|
||||
private List<X509Certificate> loadCertificateChain(KeyStore keyStore, String keyAlias) throws GeneralSecurityException {
|
||||
List<X509Certificate> chain = Optional.ofNullable(keyStore.getCertificateChain(keyAlias))
|
||||
.map(certificates -> Arrays.stream(certificates)
|
||||
|
@ -112,6 +166,34 @@ public class JavaKeystoreKeyProvider extends AbstractRsaKeyProvider {
|
|||
return chain;
|
||||
}
|
||||
|
||||
private KeyWrapper createKeyWrapper(KeyPair keyPair, X509Certificate certificate, List<X509Certificate> certificateChain, String type) {
|
||||
KeyUse keyUse = KeyUse.valueOf(model.get(Attributes.KEY_USE, KeyUse.SIG.getSpecName()).toUpperCase());
|
||||
|
||||
KeyWrapper key = new KeyWrapper();
|
||||
|
||||
key.setProviderId(model.getId());
|
||||
key.setProviderPriority(model.get("priority", 0L));
|
||||
|
||||
key.setKid(model.get(Attributes.KID_KEY) != null ? model.get(Attributes.KID_KEY) : KeyUtils.createKeyId(keyPair.getPublic()));
|
||||
key.setUse(keyUse);
|
||||
key.setType(type);
|
||||
key.setAlgorithm(algorithm);
|
||||
key.setStatus(status);
|
||||
key.setPrivateKey(keyPair.getPrivate());
|
||||
key.setPublicKey(keyPair.getPublic());
|
||||
key.setCertificate(certificate);
|
||||
|
||||
if (!certificateChain.isEmpty()) {
|
||||
if (certificate != null && !certificate.equals(certificateChain.get(0))) {
|
||||
// just in case the chain does not contain the end-user certificate
|
||||
certificateChain.add(0, certificate);
|
||||
}
|
||||
key.setCertificateChain(certificateChain);
|
||||
}
|
||||
|
||||
return key;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Validates the giving certificate chain represented by {@code certificates}. If the list of certificates is empty
|
||||
* or does not have at least 2 certificates (end-user certificate plus intermediary/root CAs) this method does nothing.
|
||||
|
@ -140,4 +222,10 @@ public class JavaKeystoreKeyProvider extends AbstractRsaKeyProvider {
|
|||
|
||||
validator.validate(certPath, params);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Stream<KeyWrapper> getKeysStream() {
|
||||
return Stream.of(key);
|
||||
}
|
||||
}
|
|
@ -23,12 +23,15 @@ import org.keycloak.common.crypto.CryptoIntegration;
|
|||
import org.keycloak.common.util.KeystoreUtil;
|
||||
import org.keycloak.component.ComponentModel;
|
||||
import org.keycloak.component.ComponentValidationException;
|
||||
import org.keycloak.crypto.Algorithm;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.provider.ConfigurationValidationHelper;
|
||||
import org.keycloak.provider.ProviderConfigProperty;
|
||||
import org.keycloak.provider.ProviderConfigurationBuilder;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static org.keycloak.provider.ProviderConfigProperty.LIST_TYPE;
|
||||
import static org.keycloak.provider.ProviderConfigProperty.STRING_TYPE;
|
||||
|
@ -36,7 +39,7 @@ import static org.keycloak.provider.ProviderConfigProperty.STRING_TYPE;
|
|||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public class JavaKeystoreKeyProviderFactory extends AbstractRsaKeyProviderFactory {
|
||||
public class JavaKeystoreKeyProviderFactory implements KeyProviderFactory {
|
||||
private static final Logger logger = Logger.getLogger(JavaKeystoreKeyProviderFactory.class);
|
||||
|
||||
public static final String ID = "java-keystore";
|
||||
|
@ -62,6 +65,7 @@ public class JavaKeystoreKeyProviderFactory extends AbstractRsaKeyProviderFactor
|
|||
|
||||
private List<ProviderConfigProperty> configProperties;
|
||||
|
||||
|
||||
@Override
|
||||
public void init(Config.Scope config) {
|
||||
String[] supportedKeystoreTypes = CryptoIntegration.getProvider().getSupportedKeyStoreTypes()
|
||||
|
@ -71,7 +75,11 @@ public class JavaKeystoreKeyProviderFactory extends AbstractRsaKeyProviderFactor
|
|||
"Keystore type. This parameter is not mandatory. If omitted, the type will be detected from keystore file or default keystore type will be used", LIST_TYPE,
|
||||
supportedKeystoreTypes.length > 0 ? supportedKeystoreTypes[0] : null, supportedKeystoreTypes);
|
||||
|
||||
configProperties = AbstractRsaKeyProviderFactory.configurationBuilder()
|
||||
configProperties = ProviderConfigurationBuilder.create()
|
||||
.property(Attributes.PRIORITY_PROPERTY)
|
||||
.property(Attributes.ENABLED_PROPERTY)
|
||||
.property(Attributes.ACTIVE_PROPERTY)
|
||||
.property(mergedAlgorithmProperties())
|
||||
.property(KEYSTORE_PROPERTY)
|
||||
.property(KEYSTORE_PASSWORD_PROPERTY)
|
||||
.property(keystoreTypeProperty)
|
||||
|
@ -88,9 +96,11 @@ public class JavaKeystoreKeyProviderFactory extends AbstractRsaKeyProviderFactor
|
|||
|
||||
@Override
|
||||
public void validateConfiguration(KeycloakSession session, RealmModel realm, ComponentModel model) throws ComponentValidationException {
|
||||
super.validateConfiguration(session, realm, model);
|
||||
|
||||
ConfigurationValidationHelper.check(model)
|
||||
.checkLong(Attributes.PRIORITY_PROPERTY, false)
|
||||
.checkBoolean(Attributes.ENABLED_PROPERTY, false)
|
||||
.checkBoolean(Attributes.ACTIVE_PROPERTY, false)
|
||||
.checkSingle(KEYSTORE_PROPERTY, true)
|
||||
.checkSingle(KEYSTORE_PASSWORD_PROPERTY, true)
|
||||
.checkSingle(keystoreTypeProperty, false)
|
||||
|
@ -105,6 +115,14 @@ public class JavaKeystoreKeyProviderFactory extends AbstractRsaKeyProviderFactor
|
|||
}
|
||||
}
|
||||
|
||||
// merge the algorithms supported for RSA and EC keys and provide them as one configuration property
|
||||
private static ProviderConfigProperty mergedAlgorithmProperties() {
|
||||
List<String> ecAlgorithms = List.of(Algorithm.ES256, Algorithm.ES384, Algorithm.ES512);
|
||||
List<String> algorithms = Stream.concat(Attributes.RS_ALGORITHM_PROPERTY.getOptions().stream(), ecAlgorithms.stream()).toList();
|
||||
return new ProviderConfigProperty(Attributes.ALGORITHM_KEY, "Algorithm", "Intended algorithm for the key", LIST_TYPE, algorithms.toArray());
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHelpText() {
|
||||
return HELP_TEXT;
|
||||
|
|
|
@ -1,22 +1,27 @@
|
|||
package org.keycloak.testsuite.util;
|
||||
|
||||
import jakarta.ws.rs.core.Response;
|
||||
import org.keycloak.admin.client.resource.RealmResource;
|
||||
import org.keycloak.common.crypto.CryptoIntegration;
|
||||
import org.keycloak.common.util.MultivaluedHashMap;
|
||||
import org.keycloak.crypto.KeyStatus;
|
||||
import org.keycloak.crypto.KeyType;
|
||||
import org.keycloak.crypto.KeyUse;
|
||||
import org.keycloak.keys.AbstractEcdsaKeyProviderFactory;
|
||||
import org.keycloak.keys.KeyProvider;
|
||||
import org.keycloak.representations.idm.ComponentRepresentation;
|
||||
import org.keycloak.representations.idm.KeysMetadataRepresentation;
|
||||
import org.keycloak.testsuite.admin.ApiUtil;
|
||||
|
||||
import jakarta.ws.rs.core.Response;
|
||||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.KeyFactory;
|
||||
import java.security.KeyPair;
|
||||
import java.security.KeyPairGenerator;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.NoSuchProviderException;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.PublicKey;
|
||||
import java.security.spec.ECGenParameterSpec;
|
||||
import java.security.spec.InvalidKeySpecException;
|
||||
import java.security.spec.PKCS8EncodedKeySpec;
|
||||
import java.security.spec.X509EncodedKeySpec;
|
||||
|
@ -31,6 +36,20 @@ import static org.junit.Assert.fail;
|
|||
* @author mhajas
|
||||
*/
|
||||
public class KeyUtils {
|
||||
public static KeyPair generateECKey(String algorithm) {
|
||||
|
||||
try {
|
||||
KeyPairGenerator kpg = CryptoIntegration.getProvider().getKeyPairGen("ECDSA");
|
||||
String domainParamNistRep = AbstractEcdsaKeyProviderFactory.convertAlgorithmToECDomainParmNistRep(algorithm);
|
||||
String curve = AbstractEcdsaKeyProviderFactory.convertECDomainParmNistRepToSecRep(domainParamNistRep);
|
||||
ECGenParameterSpec parameterSpec = new ECGenParameterSpec(curve);
|
||||
kpg.initialize(parameterSpec);
|
||||
return kpg.generateKeyPair();
|
||||
} catch (NoSuchAlgorithmException | NoSuchProviderException | InvalidAlgorithmParameterException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static PublicKey publicKeyFromString(String key) {
|
||||
try {
|
||||
|
@ -106,6 +125,7 @@ public class KeyUtils {
|
|||
public static AutoCloseable generateNewRealmKey(RealmResource realm, KeyUse keyUse, String algorithm) {
|
||||
return generateNewRealmKey(realm, keyUse, algorithm, "100");
|
||||
}
|
||||
|
||||
/**
|
||||
* @return key sizes, which are expected to be supported by Keycloak server for {@link org.keycloak.keys.GeneratedRsaKeyProviderFactory} and {@link org.keycloak.keys.GeneratedRsaEncKeyProviderFactory}.
|
||||
*/
|
||||
|
|
|
@ -19,15 +19,6 @@
|
|||
|
||||
package org.keycloak.testsuite.util;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.security.KeyPair;
|
||||
import java.security.KeyStore;
|
||||
import java.security.cert.Certificate;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.Arrays;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.junit.Assume;
|
||||
import org.junit.rules.TemporaryFolder;
|
||||
import org.keycloak.common.crypto.CryptoIntegration;
|
||||
|
@ -37,6 +28,15 @@ import org.keycloak.common.util.KeystoreUtil;
|
|||
import org.keycloak.common.util.PemUtils;
|
||||
import org.keycloak.representations.idm.CertificateRepresentation;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.security.KeyPair;
|
||||
import java.security.KeyStore;
|
||||
import java.security.cert.Certificate;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.Arrays;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
/**
|
||||
|
@ -63,16 +63,15 @@ public class KeystoreUtils {
|
|||
.anyMatch(type -> type.equals(keystoreType.toString())));
|
||||
}
|
||||
|
||||
public static KeystoreInfo generateKeystore(TemporaryFolder folder, KeystoreUtil.KeystoreFormat keystoreType, String subject, String keystorePassword, String keyPassword) throws Exception {
|
||||
public static KeystoreInfo generateKeystore(TemporaryFolder folder, KeystoreUtil.KeystoreFormat keystoreType, String subject, String keystorePassword, String keyPassword, KeyPair keyPair) throws Exception {
|
||||
String fileName = "keystore." + keystoreType.getPrimaryExtension();
|
||||
|
||||
KeyPair keyPair = KeyUtils.generateRsaKeyPair(2048);
|
||||
X509Certificate certificate = CertificateUtils.generateV1SelfSignedCertificate(keyPair, subject);
|
||||
|
||||
KeyStore keyStore = CryptoIntegration.getProvider().getKeyStore(keystoreType);
|
||||
keyStore.load(null, null);
|
||||
|
||||
Certificate[] chain = {certificate};
|
||||
Certificate[] chain = {certificate};
|
||||
keyStore.setKeyEntry(subject, keyPair.getPrivate(), keyPassword.trim().toCharArray(), chain);
|
||||
|
||||
File file = folder.newFile(fileName);
|
||||
|
@ -85,6 +84,10 @@ public class KeystoreUtils {
|
|||
return new KeystoreInfo(certRep, file);
|
||||
}
|
||||
|
||||
public static KeystoreInfo generateKeystore(TemporaryFolder folder, KeystoreUtil.KeystoreFormat keystoreType, String subject, String keystorePassword, String keyPassword) throws Exception {
|
||||
return generateKeystore(folder, keystoreType, subject, keystorePassword, keyPassword, KeyUtils.generateRsaKeyPair(2048));
|
||||
}
|
||||
|
||||
public static class KeystoreInfo {
|
||||
private final CertificateRepresentation certificateInfo;
|
||||
private final File keystoreFile;
|
||||
|
|
|
@ -17,12 +17,15 @@
|
|||
|
||||
package org.keycloak.testsuite.keys;
|
||||
|
||||
import jakarta.ws.rs.core.Response;
|
||||
import org.jboss.arquillian.graphene.page.Page;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.TemporaryFolder;
|
||||
import org.keycloak.common.util.KeystoreUtil;
|
||||
import org.keycloak.common.util.MultivaluedHashMap;
|
||||
import org.keycloak.common.util.PemUtils;
|
||||
import org.keycloak.crypto.Algorithm;
|
||||
import org.keycloak.jose.jws.AlgorithmType;
|
||||
import org.keycloak.keys.JavaKeystoreKeyProviderFactory;
|
||||
import org.keycloak.keys.KeyProvider;
|
||||
|
@ -36,13 +39,14 @@ import org.keycloak.testsuite.AssertEvents;
|
|||
import org.keycloak.testsuite.admin.ApiUtil;
|
||||
import org.keycloak.testsuite.pages.AppPage;
|
||||
import org.keycloak.testsuite.pages.LoginPage;
|
||||
import org.keycloak.testsuite.util.KeyUtils;
|
||||
import org.keycloak.testsuite.util.KeystoreUtils;
|
||||
|
||||
import jakarta.ws.rs.core.Response;
|
||||
import java.security.PublicKey;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
import static org.keycloak.common.util.KeystoreUtil.KeystoreFormat.PKCS12;
|
||||
import static org.keycloak.testsuite.admin.AbstractAdminTest.loadJson;
|
||||
|
@ -64,6 +68,7 @@ public class JavaKeystoreKeyProviderTest extends AbstractKeycloakTest {
|
|||
@Page
|
||||
protected LoginPage loginPage;
|
||||
private KeystoreUtils.KeystoreInfo generatedKeystore;
|
||||
private String keyAlgorithm;
|
||||
|
||||
@Override
|
||||
public void addTestRealms(List<RealmRepresentation> testRealms) {
|
||||
|
@ -72,34 +77,49 @@ public class JavaKeystoreKeyProviderTest extends AbstractKeycloakTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void createJks() throws Exception {
|
||||
createSuccess(KeystoreUtil.KeystoreFormat.JKS);
|
||||
public void createJksRSA() throws Exception {
|
||||
createSuccess(KeystoreUtil.KeystoreFormat.JKS, AlgorithmType.RSA);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createPkcs12() throws Exception {
|
||||
createSuccess(PKCS12);
|
||||
public void createPkcs12RSA() throws Exception {
|
||||
createSuccess(PKCS12, AlgorithmType.RSA);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createBcfks() throws Exception {
|
||||
createSuccess(KeystoreUtil.KeystoreFormat.BCFKS);
|
||||
public void createBcfksRSA() throws Exception {
|
||||
createSuccess(KeystoreUtil.KeystoreFormat.BCFKS, AlgorithmType.RSA);
|
||||
}
|
||||
|
||||
private void createSuccess(KeystoreUtil.KeystoreFormat keystoreType) throws Exception {
|
||||
@Test
|
||||
public void createJksECDSA() throws Exception {
|
||||
createSuccess(KeystoreUtil.KeystoreFormat.JKS, AlgorithmType.ECDSA);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createPkcs12ECDSA() throws Exception {
|
||||
createSuccess(KeystoreUtil.KeystoreFormat.PKCS12, AlgorithmType.ECDSA);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createBcfksECDSA() throws Exception {
|
||||
createSuccess(KeystoreUtil.KeystoreFormat.BCFKS, AlgorithmType.ECDSA);
|
||||
}
|
||||
|
||||
private void createSuccess(KeystoreUtil.KeystoreFormat keystoreType, AlgorithmType algorithmType) throws Exception {
|
||||
KeystoreUtils.assumeKeystoreTypeSupported(keystoreType);
|
||||
generateKeystore(keystoreType);
|
||||
generateKeystore(keystoreType, algorithmType);
|
||||
|
||||
long priority = System.currentTimeMillis();
|
||||
|
||||
ComponentRepresentation rep = createRep("valid", priority);
|
||||
ComponentRepresentation rep = createRep("valid", priority, keyAlgorithm);
|
||||
|
||||
Response response = adminClient.realm("test").components().add(rep);
|
||||
String id = ApiUtil.getCreatedId(response);
|
||||
getCleanup().addComponentId(id);
|
||||
|
||||
ComponentRepresentation createdRep = adminClient.realm("test").components().component(id).toRepresentation();
|
||||
assertEquals(5, createdRep.getConfig().size());
|
||||
assertEquals(6, createdRep.getConfig().size());
|
||||
assertEquals(Long.toString(priority), createdRep.getConfig().getFirst("priority"));
|
||||
assertEquals(ComponentRepresentation.SECRET_VALUE, createdRep.getConfig().getFirst("keystorePassword"));
|
||||
assertEquals(ComponentRepresentation.SECRET_VALUE, createdRep.getConfig().getFirst("keyPassword"));
|
||||
|
@ -109,16 +129,29 @@ public class JavaKeystoreKeyProviderTest extends AbstractKeycloakTest {
|
|||
KeysMetadataRepresentation.KeyMetadataRepresentation key = keys.getKeys().get(0);
|
||||
|
||||
assertEquals(id, key.getProviderId());
|
||||
assertEquals(AlgorithmType.RSA.name(), key.getType());
|
||||
switch (algorithmType) {
|
||||
case RSA: {
|
||||
assertEquals(algorithmType.name(), key.getType());
|
||||
PublicKey exp = PemUtils.decodePublicKey(generatedKeystore.getCertificateInfo().getPublicKey(), "RSA");
|
||||
PublicKey got = PemUtils.decodePublicKey(key.getPublicKey(), "RSA");
|
||||
assertEquals(exp, got);
|
||||
break;
|
||||
}
|
||||
case ECDSA:
|
||||
assertEquals("EC", key.getType());
|
||||
PublicKey exp = PemUtils.decodePublicKey(generatedKeystore.getCertificateInfo().getPublicKey(), "EC");
|
||||
PublicKey got = PemUtils.decodePublicKey(key.getPublicKey(), "EC");
|
||||
assertEquals(exp, got);
|
||||
}
|
||||
|
||||
assertEquals(priority, key.getProviderPriority());
|
||||
assertEquals(generatedKeystore.getCertificateInfo().getPublicKey(), key.getPublicKey());
|
||||
assertEquals(generatedKeystore.getCertificateInfo().getCertificate(), key.getCertificate());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void invalidKeystore() throws Exception {
|
||||
generateKeystore(KeystoreUtils.getPreferredKeystoreType());
|
||||
ComponentRepresentation rep = createRep("valid", System.currentTimeMillis());
|
||||
ComponentRepresentation rep = createRep("valid", System.currentTimeMillis(), keyAlgorithm);
|
||||
rep.getConfig().putSingle("keystore", "/nosuchfile");
|
||||
|
||||
Response response = adminClient.realm("test").components().add(rep);
|
||||
|
@ -128,7 +161,7 @@ public class JavaKeystoreKeyProviderTest extends AbstractKeycloakTest {
|
|||
@Test
|
||||
public void invalidKeystorePassword() throws Exception {
|
||||
generateKeystore(KeystoreUtils.getPreferredKeystoreType());
|
||||
ComponentRepresentation rep = createRep("valid", System.currentTimeMillis());
|
||||
ComponentRepresentation rep = createRep("valid", System.currentTimeMillis(), keyAlgorithm);
|
||||
rep.getConfig().putSingle("keystore", "invalid");
|
||||
|
||||
Response response = adminClient.realm("test").components().add(rep);
|
||||
|
@ -138,7 +171,7 @@ public class JavaKeystoreKeyProviderTest extends AbstractKeycloakTest {
|
|||
@Test
|
||||
public void invalidKeyAlias() throws Exception {
|
||||
generateKeystore(KeystoreUtils.getPreferredKeystoreType());
|
||||
ComponentRepresentation rep = createRep("valid", System.currentTimeMillis());
|
||||
ComponentRepresentation rep = createRep("valid", System.currentTimeMillis(), keyAlgorithm);
|
||||
rep.getConfig().putSingle("keyAlias", "invalid");
|
||||
|
||||
Response response = adminClient.realm("test").components().add(rep);
|
||||
|
@ -158,7 +191,7 @@ public class JavaKeystoreKeyProviderTest extends AbstractKeycloakTest {
|
|||
log.infof("Fallback to keystore type '%s' for the invalidKeyPassword() test", keystoreType);
|
||||
}
|
||||
generateKeystore(keystoreType);
|
||||
ComponentRepresentation rep = createRep("valid", System.currentTimeMillis());
|
||||
ComponentRepresentation rep = createRep("valid", System.currentTimeMillis(), keyAlgorithm);
|
||||
rep.getConfig().putSingle("keyPassword", "invalid");
|
||||
|
||||
Response response = adminClient.realm("test").components().add(rep);
|
||||
|
@ -176,7 +209,7 @@ public class JavaKeystoreKeyProviderTest extends AbstractKeycloakTest {
|
|||
response.close();
|
||||
}
|
||||
|
||||
protected ComponentRepresentation createRep(String name, long priority) {
|
||||
protected ComponentRepresentation createRep(String name, long priority, String algorithm) {
|
||||
ComponentRepresentation rep = new ComponentRepresentation();
|
||||
rep.setName(name);
|
||||
rep.setParentId(adminClient.realm("test").toRepresentation().getId());
|
||||
|
@ -188,11 +221,25 @@ public class JavaKeystoreKeyProviderTest extends AbstractKeycloakTest {
|
|||
rep.getConfig().putSingle("keystorePassword", "password");
|
||||
rep.getConfig().putSingle("keyAlias", "selfsigned");
|
||||
rep.getConfig().putSingle("keyPassword", "password");
|
||||
rep.getConfig().putSingle("algorithm", algorithm);
|
||||
return rep;
|
||||
}
|
||||
|
||||
private void generateKeystore(KeystoreUtil.KeystoreFormat keystoreType) throws Exception {
|
||||
this.generatedKeystore = KeystoreUtils.generateKeystore(folder, keystoreType, "selfsigned", "password", "password");
|
||||
generateKeystore(keystoreType, AlgorithmType.RSA);
|
||||
}
|
||||
|
||||
private void generateKeystore(KeystoreUtil.KeystoreFormat keystoreType, AlgorithmType algorithmType) throws Exception {
|
||||
switch (algorithmType) {
|
||||
case RSA: {
|
||||
this.generatedKeystore = KeystoreUtils.generateKeystore(folder, keystoreType, "selfsigned", "password", "password");
|
||||
this.keyAlgorithm = Algorithm.RS256;
|
||||
return;
|
||||
}
|
||||
case ECDSA:
|
||||
this.generatedKeystore = KeystoreUtils.generateKeystore(folder, keystoreType, "selfsigned", "password", "password", KeyUtils.generateECKey(Algorithm.ES256));
|
||||
this.keyAlgorithm = Algorithm.ES256;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue