Remove bouncycastle dependency from keycloak-services (#13489)

Closes #12857


Co-authored-by: mposolda <mposolda@gmail.com>
This commit is contained in:
David Anderson 2022-08-22 08:43:59 -05:00 committed by GitHub
parent fb978de0d8
commit ce1331f550
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
44 changed files with 1765 additions and 606 deletions

View file

@ -27,7 +27,6 @@ import org.keycloak.adapters.rotation.HardcodedPublicKeyLocator;
import org.keycloak.adapters.rotation.JWKPublicKeyLocator;
import org.keycloak.common.crypto.CryptoIntegration;
import org.keycloak.common.enums.SslRequired;
import org.keycloak.common.util.BouncyIntegration;
import org.keycloak.common.util.PemUtils;
import org.keycloak.enums.TokenStore;
import org.keycloak.representations.adapters.config.AdapterConfig;

View file

@ -17,10 +17,14 @@
package org.keycloak.common.crypto;
import java.io.IOException;
import java.math.BigInteger;
import java.security.GeneralSecurityException;
import java.security.KeyPair;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;
import java.util.Date;
import java.util.List;
/**
* The Class CertificateUtils provides utility functions for generation of V1 and V3 {@link java.security.cert.X509Certificate}
@ -28,6 +32,8 @@ import java.security.cert.X509Certificate;
*/
public interface CertificateUtilsProvider {
public static final String CRL_DISTRIBUTION_POINTS_OID = "2.5.29.31";
/**
* Generates version 3 {@link java.security.cert.X509Certificate}.
*
@ -56,5 +62,15 @@ public interface CertificateUtilsProvider {
public X509Certificate generateV1SelfSignedCertificate(KeyPair caKeyPair, String subject);
public X509Certificate generateV1SelfSignedCertificate(KeyPair caKeyPair, String subject, BigInteger serialNumber);
public List<String> getCertificatePolicyList(X509Certificate cert) throws GeneralSecurityException;
public List<String> getCRLDistributionPoints(X509Certificate cert) throws IOException;
public X509Certificate createServicesTestCertificate(String dn,
Date startDate,
Date expiryDate,
KeyPair keyPair,
String... certificatePolicyOid);
}

View file

@ -11,6 +11,9 @@ public class CryptoConstants {
public static final String RSA_OAEP = "RSA-OAEP";
public static final String RSA_OAEP_256 = "RSA-OAEP-256";
// Constant for the OCSP provider
// public static final String OCSP = "OCSP";
/** Name of Java security provider used with non-fips BouncyCastle. Should be used in non-FIPS environment */
public static final String BC_PROVIDER_ID = "BC";

View file

@ -42,6 +42,13 @@ public interface CryptoProvider {
*/
PemUtilsProvider getPemUtils();
<T> T getOCSPProver(Class<T> clazz);
public UserIdentityExtractorProvider getIdentityExtractorProvider();
public ECDSACryptoProvider getEcdsaCryptoProvider();
/**
* Create the param spec for the EC curve

View file

@ -0,0 +1,13 @@
package org.keycloak.common.crypto;
import java.io.IOException;
public interface ECDSACryptoProvider {
public byte[] concatenatedRSToASN1DER(final byte[] signature, int signLength) throws IOException;
public byte[] asn1derToConcatenatedRS(final byte[] derEncodedSignatureValue, int signLength) throws IOException;
}

View file

@ -0,0 +1,45 @@
/*
* Copyright 2016 Analytical Graphics, 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.common.crypto;
import java.io.ByteArrayInputStream;
import java.security.cert.CertificateParsingException;
import java.security.cert.X509Certificate;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Optional;
import java.util.function.Function;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.keycloak.common.util.PemUtils;
/**
* @author <a href="mailto:pnalyvayko@agi.com">Peter Nalyvayko</a>
* @version $Revision: 1 $
* @date 7/30/2016
*/
public interface UserIdentityExtractor {
public Object extractUserIdentity(X509Certificate[] certs);
}

View file

@ -0,0 +1,147 @@
/*
* Copyright 2016 Analytical Graphics, 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.common.crypto;
import java.security.Principal;
import java.security.cert.X509Certificate;
import java.util.Optional;
import java.util.function.Function;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.jboss.logging.Logger;
import org.keycloak.common.util.PemUtils;
/**
* @author <a href="mailto:pnalyvayko@agi.com">Peter Nalyvayko</a>
* @version $Revision: 1 $
* @date 7/30/2016
*/
public abstract class UserIdentityExtractorProvider {
private static final Logger logger = Logger.getLogger(UserIdentityExtractorProvider.class);
public abstract class SubjectAltNameExtractor implements UserIdentityExtractor {
}
public abstract class X500NameRDNExtractor implements UserIdentityExtractor {
}
protected class OrExtractor implements UserIdentityExtractor {
UserIdentityExtractor extractor;
UserIdentityExtractor other;
OrExtractor(UserIdentityExtractor extractor, UserIdentityExtractor other) {
this.extractor = extractor;
this.other = other;
if (this.extractor == null)
throw new IllegalArgumentException("extractor is null");
if (this.other == null)
throw new IllegalArgumentException("other is null");
}
@Override
public Object extractUserIdentity(X509Certificate[] certs) {
Object result = this.extractor.extractUserIdentity(certs);
if (result == null)
result = this.other.extractUserIdentity(certs);
return result;
}
}
public class PatternMatcher implements UserIdentityExtractor {
private final String _pattern;
private final Function<X509Certificate[],String> _f;
PatternMatcher(String pattern, Function<X509Certificate[],String> valueToMatch) {
_pattern = pattern;
_f = valueToMatch;
}
@Override
public Object extractUserIdentity(X509Certificate[] certs) {
String value = Optional.ofNullable(_f.apply(certs)).orElseThrow(IllegalArgumentException::new);
Pattern r = Pattern.compile(_pattern, Pattern.CASE_INSENSITIVE);
Matcher m = r.matcher(value);
if (!m.find()) {
logger.debugf("[PatternMatcher:extract] No matches were found for input \"%s\", pattern=\"%s\"", value, _pattern);
return null;
}
if (m.groupCount() != 1) {
logger.debugf("[PatternMatcher:extract] Match produced more than a single group for input \"%s\", pattern=\"%s\"", value, _pattern);
return null;
}
return m.group(1);
}
}
public class OrBuilder {
UserIdentityExtractor extractor;
UserIdentityExtractor other;
OrBuilder(UserIdentityExtractor extractor) {
this.extractor = extractor;
}
public UserIdentityExtractor or(UserIdentityExtractor other) {
return new OrExtractor(extractor, other);
}
}
public OrBuilder either(UserIdentityExtractor extractor) {
return new OrBuilder(extractor);
}
public UserIdentityExtractor getCertificatePemIdentityExtractor() {
return new UserIdentityExtractor() {
@Override
public Object extractUserIdentity(X509Certificate[] certs) {
if (certs == null || certs.length == 0) {
throw new IllegalArgumentException();
}
String pem = PemUtils.encodeCertificate(certs[0]);
logger.debugf("Using PEM certificate \"%s\" as user identity.", pem);
return pem;
}
};
}
public UserIdentityExtractor getPatternIdentityExtractor(String pattern,
Function<X509Certificate[],String> valueToMatch) {
return new PatternMatcher(pattern, valueToMatch);
}
public abstract UserIdentityExtractor getX500NameExtractor(String identifier, Function<X509Certificate[],Principal> x500Name);
/**
* Obtains the subjectAltName given a <code>generalName</code>.
*
* @param generalName an integer representing the general name. See {@link X509Certificate#getSubjectAlternativeNames()}
* @return the value from the subjectAltName extension
*/
public abstract SubjectAltNameExtractor getSubjectAltNameExtractor(int generalName);
}

View file

@ -41,7 +41,19 @@
<scope>test</scope>
<type>test-jar</type>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-server-spi</artifactId>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-server-spi-private</artifactId>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>

View file

@ -17,31 +17,54 @@
package org.keycloak.crypto.def;
import org.bouncycastle.asn1.ASN1InputStream;
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.asn1.DERIA5String;
import org.bouncycastle.asn1.DEROctetString;
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.asn1.x509.BasicConstraints;
import org.bouncycastle.asn1.x509.CRLDistPoint;
import org.bouncycastle.asn1.x509.CertificatePolicies;
import org.bouncycastle.asn1.x509.DistributionPoint;
import org.bouncycastle.asn1.x509.DistributionPointName;
import org.bouncycastle.asn1.x509.ExtendedKeyUsage;
import org.bouncycastle.asn1.x509.Extension;
import org.bouncycastle.asn1.x509.Extensions;
import org.bouncycastle.asn1.x509.GeneralName;
import org.bouncycastle.asn1.x509.GeneralNames;
import org.bouncycastle.asn1.x509.KeyPurposeId;
import org.bouncycastle.asn1.x509.KeyUsage;
import org.bouncycastle.asn1.x509.PolicyInformation;
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
import org.bouncycastle.cert.CertIOException;
import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.cert.X509v1CertificateBuilder;
import org.bouncycastle.cert.X509v3CertificateBuilder;
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
import org.bouncycastle.cert.jcajce.JcaX509CertificateHolder;
import org.bouncycastle.cert.jcajce.JcaX509ExtensionUtils;
import org.bouncycastle.operator.ContentSigner;
import org.bouncycastle.operator.OperatorCreationException;
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
import org.keycloak.common.util.BouncyIntegration;
import org.keycloak.common.crypto.CertificateUtilsProvider;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.security.GeneralSecurityException;
import java.security.KeyPair;
import java.security.PrivateKey;
import java.security.SecureRandom;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
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}
@ -166,4 +189,133 @@ public class BCCertificateUtilsProvider implements CertificateUtilsProvider {
throw new RuntimeException("Could not create content signer.", e);
}
}
@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");
CertificatePolicies policies = CertificatePolicies.fromExtensions(certExtensions);
if (policies == null)
throw new GeneralSecurityException("Certificate Policy validation was expected, but no certificate policy extensions were found");
List<String> policyList = new LinkedList<>();
Arrays.stream(policies.getPolicyInformation()).forEach(p -> policyList.add(p.getPolicyIdentifier().toString().toLowerCase()));
return policyList;
}
/**
* 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
*/
public List<String> getCRLDistributionPoints(X509Certificate cert) throws IOException {
byte[] data = cert.getExtensionValue(CRL_DISTRIBUTION_POINTS_OID);
if (data == null) {
return Collections.emptyList();
}
List<String> distributionPointUrls = new LinkedList<>();
DEROctetString octetString;
try (ASN1InputStream crldpExtensionInputStream = new ASN1InputStream(new ByteArrayInputStream(data))) {
octetString = (DEROctetString)crldpExtensionInputStream.readObject();
}
byte[] octets = octetString.getOctets();
CRLDistPoint crlDP;
try (ASN1InputStream crldpInputStream = new ASN1InputStream(new ByteArrayInputStream(octets))) {
crlDP = CRLDistPoint.getInstance(crldpInputStream.readObject());
}
for (DistributionPoint dp : crlDP.getDistributionPoints()) {
DistributionPointName dpn = dp.getDistributionPoint();
if (dpn != null && dpn.getType() == DistributionPointName.FULL_NAME) {
GeneralName[] names = GeneralNames.getInstance(dpn.getName()).getNames();
for (GeneralName gn : names) {
if (gn.getTagNo() == GeneralName.uniformResourceIdentifier) {
String url = DERIA5String.getInstance(gn.getName()).getString();
distributionPointUrls.add(url);
}
}
}
}
return distributionPointUrls;
}
public X509Certificate createServicesTestCertificate(String dn,
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()));
BigInteger serialNumber = new BigInteger(130, new SecureRandom());
// Build the certificate
X509v3CertificateBuilder certGen = new X509v3CertificateBuilder(issuerDN, serialNumber, startDate, expiryDate,
subjectDN, subjPubKeyInfo);
if (certificatePolicyOid != null)
{
try
{
for (Extension certExtension: certPolicyExtensions(certificatePolicyOid))
certGen.addExtension(certExtension);
} catch (CertIOException e) {
throw new IllegalStateException(e);
}
}
// Sign the cert with the private key
try {
ContentSigner contentSigner = new JcaContentSignerBuilder("SHA256withRSA")
.setProvider(BouncyIntegration.PROVIDER)
.build(keyPair.getPrivate());
X509Certificate x509Certificate = new JcaX509CertificateConverter()
.setProvider(BouncyIntegration.PROVIDER)
.getCertificate(certGen.build(contentSigner));
return x509Certificate;
} catch (CertificateException | OperatorCreationException e) {
throw new IllegalStateException(e);
}
}
private List<Extension> certPolicyExtensions(String... certificatePolicyOid) {
List<Extension> certificatePolicies = new LinkedList<>();
if (certificatePolicyOid != null && certificatePolicyOid.length > 0)
{
List<PolicyInformation> policyInfoList = new LinkedList<>();
for (String oid: certificatePolicyOid)
{
policyInfoList.add(new PolicyInformation(new ASN1ObjectIdentifier(oid)));
}
CertificatePolicies policies = new CertificatePolicies(policyInfoList.toArray(new PolicyInformation[0]));
try {
boolean isCritical = false;
Extension extension = new Extension(Extension.certificatePolicies, isCritical, policies.getEncoded());
certificatePolicies.add(extension);
} catch (IOException e) {
throw new IllegalStateException(e);
}
}
return certificatePolicies;
}
}

View file

@ -0,0 +1,64 @@
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.keycloak.common.crypto.ECDSACryptoProvider;
public class BCECDSACryptoProvider implements ECDSACryptoProvider {
@Override
public byte[] concatenatedRSToASN1DER(final byte[] signature, int signLength) throws IOException {
int len = signLength / 2;
int arraySize = len + 1;
byte[] r = new byte[arraySize];
byte[] s = new byte[arraySize];
System.arraycopy(signature, 0, r, 1, len);
System.arraycopy(signature, len, s, 1, len);
BigInteger rBigInteger = new BigInteger(r);
BigInteger sBigInteger = new BigInteger(s);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
DERSequenceGenerator seqGen = new DERSequenceGenerator(bos);
seqGen.addObject(new ASN1Integer(rBigInteger.toByteArray()));
seqGen.addObject(new ASN1Integer(sBigInteger.toByteArray()));
seqGen.close();
bos.close();
return bos.toByteArray();
}
@Override
public byte[] asn1derToConcatenatedRS(final byte[] derEncodedSignatureValue, int signLength) throws IOException {
int len = signLength / 2;
ASN1InputStream asn1InputStream = new ASN1InputStream(derEncodedSignatureValue);
ASN1Primitive asn1Primitive = asn1InputStream.readObject();
asn1InputStream.close();
ASN1Sequence asn1Sequence = (ASN1Sequence.getInstance(asn1Primitive));
ASN1Integer rASN1 = (ASN1Integer) asn1Sequence.getObjectAt(0);
ASN1Integer sASN1 = (ASN1Integer) asn1Sequence.getObjectAt(1);
X9IntegerConverter x9IntegerConverter = new X9IntegerConverter();
byte[] r = x9IntegerConverter.integerToBytes(rASN1.getValue(), len);
byte[] s = x9IntegerConverter.integerToBytes(sASN1.getValue(), len);
byte[] concatenatedSignatureValue = new byte[signLength];
System.arraycopy(r, 0, concatenatedSignatureValue, 0, len);
System.arraycopy(s, 0, concatenatedSignatureValue, len, len);
return concatenatedSignatureValue;
}
}

View file

@ -16,23 +16,14 @@
*
*/
package org.keycloak.authentication.authenticators.x509;
import org.apache.http.HttpHeaders;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.util.EntityUtils;
package org.keycloak.crypto.def;
import org.bouncycastle.asn1.ASN1InputStream;
import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.asn1.DERIA5String;
import org.bouncycastle.asn1.DEROctetString;
import org.bouncycastle.asn1.ocsp.OCSPObjectIdentifiers;
import org.bouncycastle.asn1.oiw.OIWObjectIdentifiers;
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
import org.bouncycastle.asn1.x509.CRLReason;
import org.bouncycastle.asn1.x509.Extension;
import org.bouncycastle.asn1.x509.Extensions;
import org.bouncycastle.asn1.x509.SubjectKeyIdentifier;
@ -44,17 +35,26 @@ import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
import org.bouncycastle.cert.jcajce.JcaX509CertificateHolder;
import org.bouncycastle.cert.jcajce.JcaX509ExtensionUtils;
import org.bouncycastle.cert.ocsp.*;
import org.bouncycastle.cert.ocsp.BasicOCSPResp;
import org.bouncycastle.cert.ocsp.CertificateID;
import org.bouncycastle.cert.ocsp.CertificateStatus;
import org.bouncycastle.cert.ocsp.OCSPException;
import org.bouncycastle.cert.ocsp.OCSPReq;
import org.bouncycastle.cert.ocsp.OCSPReqBuilder;
import org.bouncycastle.cert.ocsp.OCSPResp;
import org.bouncycastle.cert.ocsp.RevokedStatus;
import org.bouncycastle.cert.ocsp.SingleResp;
import org.bouncycastle.cert.ocsp.UnknownStatus;
import org.bouncycastle.cert.ocsp.jcajce.JcaCertificateID;
import org.bouncycastle.operator.ContentVerifierProvider;
import org.bouncycastle.operator.DigestCalculator;
import org.bouncycastle.operator.DigestCalculatorProvider;
import org.bouncycastle.operator.OperatorCreationException;
import org.bouncycastle.operator.bc.BcDigestCalculatorProvider;
import org.bouncycastle.operator.jcajce.JcaContentVerifierProviderBuilder;
import org.bouncycastle.operator.jcajce.JcaDigestCalculatorProviderBuilder;
import org.keycloak.common.util.BouncyIntegration;
import org.keycloak.connections.httpclient.HttpClientProvider;
import org.keycloak.models.KeycloakSession;
import org.keycloak.utils.OCSPProvider;
import java.io.IOException;
import java.math.BigInteger;
@ -63,12 +63,24 @@ import java.security.GeneralSecurityException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.SecureRandom;
import java.security.cert.*;
import java.util.*;
import java.security.cert.CRLReason;
import java.security.cert.CertPath;
import java.security.cert.CertPathValidatorException;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateExpiredException;
import java.security.cert.CertificateNotYetValidException;
import java.security.cert.CertificateParsingException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.impl.client.CloseableHttpClient;
/**
* @author <a href="mailto:brat000012001@gmail.com">Peter Nalyvayko</a>
@ -76,115 +88,13 @@ import org.apache.http.impl.client.CloseableHttpClient;
* @since 10/29/2016
*/
public final class OCSPUtils {
public class BCOCSPProvider extends OCSPProvider {
private final static Logger logger = Logger.getLogger(""+OCSPUtils.class);
private final static Logger logger = Logger.getLogger(BCOCSPProvider.class.getName());
private static int OCSP_CONNECT_TIMEOUT = 10000; // 10 sec
private static final int TIME_SKEW = 900000;
public enum RevocationStatus {
GOOD,
REVOKED,
UNKNOWN
}
public interface OCSPRevocationStatus {
RevocationStatus getRevocationStatus();
Date getRevocationTime();
CRLReason getRevocationReason();
}
/**
* Requests certificate revocation status using OCSP.
* @param cert the certificate to be checked
* @param issuerCertificate The issuer certificate
* @param responderURI an address of OCSP responder. Overrides any OCSP responder URIs stored in certificate's AIA extension
* @param date
* @param responderCert a certificate that OCSP responder uses to sign OCSP responses
* @return revocation status
*/
public static OCSPRevocationStatus check(KeycloakSession session, X509Certificate cert, X509Certificate issuerCertificate, URI responderURI, X509Certificate responderCert, Date date) throws CertPathValidatorException {
if (cert == null)
throw new IllegalArgumentException("cert cannot be null");
if (issuerCertificate == null)
throw new IllegalArgumentException("issuerCertificate cannot be null");
if (responderURI == null)
throw new IllegalArgumentException("responderURI cannot be null");
return check(session, cert, issuerCertificate, Collections.singletonList(responderURI), responderCert, date);
}
/**
* Requests certificate revocation status using OCSP. The OCSP responder URI
* is obtained from the certificate's AIA extension.
* @param cert the certificate to be checked
* @param issuerCertificate The issuer certificate
* @param date
* @return revocation status
*/
public static OCSPRevocationStatus check(KeycloakSession session, X509Certificate cert, X509Certificate issuerCertificate, Date date, X509Certificate responderCert) throws CertPathValidatorException {
List<String> responderURIs = null;
try {
responderURIs = getResponderURIs(cert);
} catch (CertificateEncodingException e) {
logger.log(Level.FINE, "CertificateEncodingException: {0}", e.getMessage());
throw new CertPathValidatorException(e.getMessage(), e);
}
if (responderURIs.size() == 0) {
logger.log(Level.INFO, "No OCSP responders in the specified certificate");
throw new CertPathValidatorException("No OCSP Responder URI in certificate");
}
List<URI> uris = new LinkedList<>();
for (String value : responderURIs) {
try {
URI responderURI = URI.create(value);
uris.add(responderURI);
} catch (IllegalArgumentException ex) {
logger.log(Level.FINE, "Malformed responder URI {0}", value);
}
}
return check(session, cert, issuerCertificate, Collections.unmodifiableList(uris), responderCert, date);
}
/**
* Requests certificate revocation status using OCSP. The OCSP responder URI
* is obtained from the certificate's AIA extension.
* @param cert the certificate to be checked
* @param issuerCertificate The issuer certificate
* @return revocation status
*/
public static OCSPRevocationStatus check(KeycloakSession session, X509Certificate cert, X509Certificate issuerCertificate) throws CertPathValidatorException {
return check(session, cert, issuerCertificate, null, null);
}
private static OCSPResp getResponse(KeycloakSession session, OCSPReq ocspReq, URI responderUri) throws IOException {
CloseableHttpClient httpClient = session.getProvider(HttpClientProvider.class).getHttpClient();
HttpPost post = new HttpPost(responderUri);
post.setHeader(HttpHeaders.CONTENT_TYPE, "application/ocsp-request");
final RequestConfig params = RequestConfig.custom()
.setConnectTimeout(OCSP_CONNECT_TIMEOUT)
.setSocketTimeout(OCSP_CONNECT_TIMEOUT)
.build();
post.setConfig(params);
post.setEntity(new ByteArrayEntity(ocspReq.getEncoded()));
//Get Response
try (CloseableHttpResponse response = httpClient.execute(post)) {
try {
if (response.getStatusLine().getStatusCode() / 100 != 2) {
String errorMessage = String.format("Connection error, unable to obtain certificate revocation status using OCSP responder \"%s\", code \"%d\"",
responderUri.toString(), response.getStatusLine().getStatusCode());
throw new IOException(errorMessage);
}
byte[] data = EntityUtils.toByteArray(response.getEntity());
return new OCSPResp(data);
} finally {
EntityUtils.consumeQuietly(response.getEntity());
}
}
protected OCSPResp getResponse(KeycloakSession session, OCSPReq ocspReq, URI responderUri) throws IOException {
byte[] data = getEncodedOCSPResponse(session, ocspReq.getEncoded(), responderUri);
return new OCSPResp(data);
}
/**
@ -197,12 +107,15 @@ public final class OCSPUtils {
* @return a revocation status
* @throws CertPathValidatorException
*/
private static OCSPRevocationStatus check(KeycloakSession session, X509Certificate cert, X509Certificate issuerCertificate, List<URI> responderURIs, X509Certificate responderCert, Date date) throws CertPathValidatorException {
@Override
protected OCSPRevocationStatus check(KeycloakSession session, X509Certificate cert, X509Certificate issuerCertificate, List<URI> responderURIs, X509Certificate responderCert, Date date) throws CertPathValidatorException {
if (responderURIs == null || responderURIs.size() == 0)
throw new IllegalArgumentException("Need at least one responder");
try {
DigestCalculator digCalc = new BcDigestCalculatorProvider()
.get(new AlgorithmIdentifier(OIWObjectIdentifiers.idSHA1));
DigestCalculatorProvider dcp = new JcaDigestCalculatorProviderBuilder().build();
DigestCalculator digCalc = dcp.get(CertificateID.HASH_SHA1);
JcaCertificateID certificateID = new JcaCertificateID(digCalc, issuerCertificate, cert.getSerialNumber());
@ -251,13 +164,14 @@ public final class OCSPUtils {
throw new CertPathValidatorException("OCSP check failed", e);
}
}
catch(CertificateNotYetValidException | CertificateExpiredException | OperatorCreationException | OCSPException | CertificateEncodingException | NoSuchAlgorithmException | NoSuchProviderException e) {
catch(CertificateNotYetValidException | CertificateExpiredException | OperatorCreationException | OCSPException |
CertificateEncodingException | NoSuchAlgorithmException | NoSuchProviderException e) {
logger.log(Level.FINE, e.getMessage());
throw new CertPathValidatorException(e.getMessage(), e);
}
}
private static OCSPRevocationStatus processBasicOCSPResponse(X509Certificate issuerCertificate, X509Certificate responderCertificate, Date date, JcaCertificateID certificateID, BigInteger nounce, BasicOCSPResp basicOcspResponse)
private OCSPRevocationStatus processBasicOCSPResponse(X509Certificate issuerCertificate, X509Certificate responderCertificate, Date date, JcaCertificateID certificateID, BigInteger nounce, BasicOCSPResp basicOcspResponse)
throws OCSPException, NoSuchProviderException, NoSuchAlgorithmException, CertificateNotYetValidException, CertificateExpiredException, CertPathValidatorException {
SingleResp expectedResponse = null;
for (SingleResp singleResponse : basicOcspResponse.getResponses()) {
@ -275,7 +189,7 @@ public final class OCSPUtils {
}
}
private static boolean compareCertIDs(JcaCertificateID idLeft, CertificateID idRight) {
private boolean compareCertIDs(JcaCertificateID idLeft, CertificateID idRight) {
if (idLeft == idRight)
return true;
if (idLeft == null || idRight == null)
@ -286,7 +200,7 @@ public final class OCSPUtils {
idLeft.getSerialNumber().equals(idRight.getSerialNumber());
}
private static void verifyResponse(BasicOCSPResp basicOcspResponse, X509Certificate issuerCertificate, X509Certificate responderCertificate, byte[] requestNonce, Date date) throws NoSuchProviderException, NoSuchAlgorithmException, CertificateNotYetValidException, CertificateExpiredException, CertPathValidatorException {
private void verifyResponse(BasicOCSPResp basicOcspResponse, X509Certificate issuerCertificate, X509Certificate responderCertificate, byte[] requestNonce, Date date) throws NoSuchProviderException, NoSuchAlgorithmException, CertificateNotYetValidException, CertificateExpiredException, CertPathValidatorException {
List<X509CertificateHolder> certs = new ArrayList<>(Arrays.asList(basicOcspResponse.getCerts()));
X509Certificate signingCert = null;
@ -444,7 +358,7 @@ public final class OCSPUtils {
}
}
private static boolean verifySignature(BasicOCSPResp basicOcspResponse, X509Certificate cert) {
private boolean verifySignature(BasicOCSPResp basicOcspResponse, X509Certificate cert) {
try {
ContentVerifierProvider contentVerifier = new JcaContentVerifierProviderBuilder()
.setProvider(BouncyIntegration.PROVIDER).build(cert.getPublicKey());
@ -457,29 +371,10 @@ public final class OCSPUtils {
return false;
}
private static OCSPRevocationStatus unknownStatus() {
return new OCSPRevocationStatus() {
@Override
public RevocationStatus getRevocationStatus() {
return RevocationStatus.UNKNOWN;
}
@Override
public Date getRevocationTime() {
return new Date(System.currentTimeMillis());
}
@Override
public CRLReason getRevocationReason() {
return CRLReason.lookup(CRLReason.unspecified);
}
};
}
private static OCSPRevocationStatus singleResponseToRevocationStatus(final SingleResp singleResponse) throws CertPathValidatorException {
private OCSPRevocationStatus singleResponseToRevocationStatus(final SingleResp singleResponse) throws CertPathValidatorException {
final CertificateStatus certStatus = singleResponse.getCertStatus();
int revocationReason = CRLReason.unspecified;
CRLReason revocationReason = CRLReason.UNSPECIFIED;
Date revocationTime = null;
RevocationStatus status = RevocationStatus.UNKNOWN;
if (certStatus == CertificateStatus.GOOD) {
@ -489,7 +384,7 @@ public final class OCSPUtils {
revocationTime = revoked.getRevocationTime();
status = RevocationStatus.REVOKED;
if (revoked.hasRevocationReason()) {
revocationReason = revoked.getRevocationReason();
revocationReason = CRLReason.values()[revoked.getRevocationReason()];
}
} else if (certStatus instanceof UnknownStatus) {
status = RevocationStatus.UNKNOWN;
@ -499,7 +394,7 @@ public final class OCSPUtils {
final RevocationStatus finalStatus = status;
final Date finalRevocationTime = revocationTime;
final int finalRevocationReason = revocationReason;
final CRLReason finalRevocationReason = revocationReason;
return new OCSPRevocationStatus() {
@Override
public RevocationStatus getRevocationStatus() {
@ -513,7 +408,7 @@ public final class OCSPUtils {
@Override
public CRLReason getRevocationReason() {
return CRLReason.lookup(finalRevocationReason);
return finalRevocationReason;
}
};
}
@ -526,7 +421,8 @@ public final class OCSPUtils {
* @return a list of available responder URIs.
* @throws CertificateEncodingException
*/
private static List<String> getResponderURIs(X509Certificate cert) throws CertificateEncodingException {
@Override
protected List<String> getResponderURIs(X509Certificate cert) throws CertificateEncodingException {
LinkedList<String> responderURIs = new LinkedList<>();
JcaX509CertificateHolder holder = new JcaX509CertificateHolder(cert);

View file

@ -16,9 +16,8 @@
*
*/
package org.keycloak.authentication.authenticators.x509;
package org.keycloak.crypto.def;
import freemarker.template.utility.NullArgumentException;
import org.bouncycastle.asn1.ASN1Encodable;
import org.bouncycastle.asn1.ASN1InputStream;
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
@ -27,20 +26,20 @@ import org.bouncycastle.asn1.ASN1TaggedObject;
import org.bouncycastle.asn1.DERUTF8String;
import org.bouncycastle.asn1.x500.RDN;
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.asn1.x500.style.BCStyle;
import org.bouncycastle.asn1.x500.style.IETFUtils;
import org.keycloak.common.util.PemUtils;
import org.keycloak.services.ServicesLogger;
import org.jboss.logging.Logger;
import org.keycloak.common.crypto.UserIdentityExtractor;
import org.keycloak.common.crypto.UserIdentityExtractorProvider;
import java.io.ByteArrayInputStream;
import java.security.Principal;
import java.security.cert.CertificateParsingException;
import java.security.cert.X509Certificate;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Optional;
import java.util.function.Function;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* @author <a href="mailto:pnalyvayko@agi.com">Peter Nalyvayko</a>
@ -48,41 +47,17 @@ import java.util.regex.Pattern;
* @date 7/30/2016
*/
public abstract class UserIdentityExtractor {
public class BCUserIdentityExtractorProvider extends UserIdentityExtractorProvider {
private static final ServicesLogger logger = ServicesLogger.LOGGER;
private static final Logger logger = Logger.getLogger(BCUserIdentityExtractorProvider.class.getName());
public abstract Object extractUserIdentity(X509Certificate[] certs);
static class OrExtractor extends UserIdentityExtractor {
UserIdentityExtractor extractor;
UserIdentityExtractor other;
OrExtractor(UserIdentityExtractor extractor, UserIdentityExtractor other) {
this.extractor = extractor;
this.other = other;
if (this.extractor == null)
throw new NullArgumentException("extractor");
if (this.other == null)
throw new NullArgumentException("other");
}
@Override
public Object extractUserIdentity(X509Certificate[] certs) {
Object result = this.extractor.extractUserIdentity(certs);
if (result == null)
result = this.other.extractUserIdentity(certs);
return result;
}
}
static class X500NameRDNExtractor extends UserIdentityExtractor {
class X500NameRDNExtractorBCProvider extends X500NameRDNExtractor {
private ASN1ObjectIdentifier x500NameStyle;
Function<X509Certificate[],X500Name> x500Name;
X500NameRDNExtractor(ASN1ObjectIdentifier x500NameStyle, Function<X509Certificate[],X500Name> x500Name) {
this.x500NameStyle = x500NameStyle;
Function<X509Certificate[],Principal> x500Name;
public X500NameRDNExtractorBCProvider(String attrName, Function<X509Certificate[], Principal> x500Name) {
this.x500NameStyle = BCStyle.INSTANCE.attrNameToOID(attrName);
this.x500Name = x500Name;
}
@ -92,7 +67,7 @@ public abstract class UserIdentityExtractor {
if (certs == null || certs.length == 0)
throw new IllegalArgumentException();
X500Name name = x500Name.apply(certs);
X500Name name = new X500Name(x500Name.apply(certs).getName());
if (name != null) {
RDN[] rnds = name.getRDNs(x500NameStyle);
if (rnds != null && rnds.length > 0) {
@ -107,7 +82,7 @@ public abstract class UserIdentityExtractor {
/**
* Extracts the subject identifier from the subjectAltName extension.
*/
static class SubjectAltNameExtractor extends UserIdentityExtractor {
class SubjectAltNameExtractorBCProvider extends SubjectAltNameExtractor {
// User Principal Name. Used typically by Microsoft in certificates for Smart Card Login
private static final String UPN_OID = "1.3.6.1.4.1.311.20.2.3";
@ -119,7 +94,7 @@ public abstract class UserIdentityExtractor {
*
* @param generalName an integer representing the general name. See {@link X509Certificate#getSubjectAlternativeNames()}
*/
SubjectAltNameExtractor(int generalName) {
SubjectAltNameExtractorBCProvider(int generalName) {
this.generalName = generalName;
}
@ -213,83 +188,14 @@ public abstract class UserIdentityExtractor {
}
}
static class PatternMatcher extends UserIdentityExtractor {
private final String _pattern;
private final Function<X509Certificate[],String> _f;
PatternMatcher(String pattern, Function<X509Certificate[],String> valueToMatch) {
_pattern = pattern;
_f = valueToMatch;
}
@Override
public Object extractUserIdentity(X509Certificate[] certs) {
String value = Optional.ofNullable(_f.apply(certs)).orElseThrow(IllegalArgumentException::new);
Pattern r = Pattern.compile(_pattern, Pattern.CASE_INSENSITIVE);
Matcher m = r.matcher(value);
if (!m.find()) {
logger.debugf("[PatternMatcher:extract] No matches were found for input \"%s\", pattern=\"%s\"", value, _pattern);
return null;
}
if (m.groupCount() != 1) {
logger.debugf("[PatternMatcher:extract] Match produced more than a single group for input \"%s\", pattern=\"%s\"", value, _pattern);
return null;
}
return m.group(1);
}
@Override
public UserIdentityExtractor getX500NameExtractor(String identifier, Function<X509Certificate[], Principal> x500Name) {
return new X500NameRDNExtractorBCProvider(identifier, x500Name);
}
static class OrBuilder {
UserIdentityExtractor extractor;
UserIdentityExtractor other;
OrBuilder(UserIdentityExtractor extractor) {
this.extractor = extractor;
}
public UserIdentityExtractor or(UserIdentityExtractor other) {
return new OrExtractor(extractor, other);
}
@Override
public SubjectAltNameExtractor getSubjectAltNameExtractor(int generalName) {
return new SubjectAltNameExtractorBCProvider(generalName);
}
public static UserIdentityExtractor getPatternIdentityExtractor(String pattern,
Function<X509Certificate[],String> func) {
return new PatternMatcher(pattern, func);
}
public static UserIdentityExtractor getX500NameExtractor(ASN1ObjectIdentifier identifier, Function<X509Certificate[],X500Name> x500Name) {
return new X500NameRDNExtractor(identifier, x500Name);
}
/**
* Obtains the subjectAltName given a <code>generalName</code>.
*
* @param generalName an integer representing the general name. See {@link X509Certificate#getSubjectAlternativeNames()}
* @return the value from the subjectAltName extension
*/
public static SubjectAltNameExtractor getSubjectAltNameExtractor(int generalName) {
return new SubjectAltNameExtractor(generalName);
}
public static OrBuilder either(UserIdentityExtractor extractor) {
return new OrBuilder(extractor);
}
public static UserIdentityExtractor getCertificatePemIdentityExtractor(X509AuthenticatorConfigModel config) {
return new UserIdentityExtractor() {
@Override
public Object extractUserIdentity(X509Certificate[] certs) {
if (certs == null || certs.length == 0) {
throw new IllegalArgumentException();
}
String pem = PemUtils.encodeCertificate(certs[0]);
logger.debugf("Using PEM certificate \"%s\" as user identity.", pem);
return pem;
}
};
}
}

View file

@ -12,8 +12,10 @@ import org.bouncycastle.jce.spec.ECNamedCurveSpec;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.keycloak.common.crypto.CryptoProvider;
import org.keycloak.common.crypto.CryptoConstants;
import org.keycloak.common.crypto.ECDSACryptoProvider;
import org.keycloak.common.crypto.CertificateUtilsProvider;
import org.keycloak.common.crypto.PemUtilsProvider;
import org.keycloak.common.crypto.UserIdentityExtractorProvider;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
@ -67,4 +69,20 @@ public class DefaultCryptoProvider implements CryptoProvider {
return new ECNamedCurveSpec("prime256v1", spec.getCurve(), spec.getG(), spec.getN());
}
@Override
public UserIdentityExtractorProvider getIdentityExtractorProvider() {
return new BCUserIdentityExtractorProvider();
}
@Override
public ECDSACryptoProvider getEcdsaCryptoProvider() {
return new BCECDSACryptoProvider();
}
@Override
public <T> T getOCSPProver(Class<T> clazz) {
return clazz.cast(new BCOCSPProvider());
}
}

View file

@ -41,6 +41,19 @@
<scope>test</scope>
<type>test-jar</type>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-server-spi</artifactId>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-server-spi-private</artifactId>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>

View file

@ -17,30 +17,54 @@
package org.keycloak.crypto.fips;
import org.bouncycastle.asn1.ASN1InputStream;
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.asn1.DERIA5String;
import org.bouncycastle.asn1.DEROctetString;
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.asn1.x509.BasicConstraints;
import org.bouncycastle.asn1.x509.CRLDistPoint;
import org.bouncycastle.asn1.x509.CertificatePolicies;
import org.bouncycastle.asn1.x509.DistributionPoint;
import org.bouncycastle.asn1.x509.DistributionPointName;
import org.bouncycastle.asn1.x509.ExtendedKeyUsage;
import org.bouncycastle.asn1.x509.Extension;
import org.bouncycastle.asn1.x509.Extensions;
import org.bouncycastle.asn1.x509.GeneralName;
import org.bouncycastle.asn1.x509.GeneralNames;
import org.bouncycastle.asn1.x509.KeyPurposeId;
import org.bouncycastle.asn1.x509.KeyUsage;
import org.bouncycastle.asn1.x509.PolicyInformation;
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
import org.bouncycastle.cert.CertIOException;
import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.cert.X509v1CertificateBuilder;
import org.bouncycastle.cert.X509v3CertificateBuilder;
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
import org.bouncycastle.cert.jcajce.JcaX509CertificateHolder;
import org.bouncycastle.cert.jcajce.JcaX509ExtensionUtils;
import org.bouncycastle.operator.ContentSigner;
import org.bouncycastle.operator.OperatorCreationException;
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
import org.keycloak.common.util.BouncyIntegration;
import org.keycloak.common.crypto.CertificateUtilsProvider;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.security.GeneralSecurityException;
import java.security.KeyPair;
import java.security.PrivateKey;
import java.security.SecureRandom;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
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}
@ -165,4 +189,133 @@ public class BCFIPSCertificateUtilsProvider implements CertificateUtilsProvider{
throw new RuntimeException("Could not create content signer.", e);
}
}
@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");
CertificatePolicies policies = CertificatePolicies.fromExtensions(certExtensions);
if (policies == null)
throw new GeneralSecurityException("Certificate Policy validation was expected, but no certificate policy extensions were found");
List<String> policyList = new LinkedList<>();
Arrays.stream(policies.getPolicyInformation()).forEach(p -> policyList.add(p.getPolicyIdentifier().toString().toLowerCase()));
return policyList;
}
/**
* 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
*/
public List<String> getCRLDistributionPoints(X509Certificate cert) throws IOException {
byte[] data = cert.getExtensionValue(CRL_DISTRIBUTION_POINTS_OID);
if (data == null) {
return Collections.emptyList();
}
List<String> distributionPointUrls = new LinkedList<>();
DEROctetString octetString;
try (ASN1InputStream crldpExtensionInputStream = new ASN1InputStream(new ByteArrayInputStream(data))) {
octetString = (DEROctetString)crldpExtensionInputStream.readObject();
}
byte[] octets = octetString.getOctets();
CRLDistPoint crlDP;
try (ASN1InputStream crldpInputStream = new ASN1InputStream(new ByteArrayInputStream(octets))) {
crlDP = CRLDistPoint.getInstance(crldpInputStream.readObject());
}
for (DistributionPoint dp : crlDP.getDistributionPoints()) {
DistributionPointName dpn = dp.getDistributionPoint();
if (dpn != null && dpn.getType() == DistributionPointName.FULL_NAME) {
GeneralName[] names = GeneralNames.getInstance(dpn.getName()).getNames();
for (GeneralName gn : names) {
if (gn.getTagNo() == GeneralName.uniformResourceIdentifier) {
String url = DERIA5String.getInstance(gn.getName()).getString();
distributionPointUrls.add(url);
}
}
}
}
return distributionPointUrls;
}
public X509Certificate createServicesTestCertificate(String dn,
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()));
BigInteger serialNumber = new BigInteger(130, new SecureRandom());
// Build the certificate
X509v3CertificateBuilder certGen = new X509v3CertificateBuilder(issuerDN, serialNumber, startDate, expiryDate,
subjectDN, subjPubKeyInfo);
if (certificatePolicyOid != null)
{
try
{
for (Extension certExtension: certPolicyExtensions(certificatePolicyOid))
certGen.addExtension(certExtension);
} catch (CertIOException e) {
throw new IllegalStateException(e);
}
}
// Sign the cert with the private key
try {
ContentSigner contentSigner = new JcaContentSignerBuilder("SHA256withRSA")
.setProvider(BouncyIntegration.PROVIDER)
.build(keyPair.getPrivate());
X509Certificate x509Certificate = new JcaX509CertificateConverter()
.setProvider(BouncyIntegration.PROVIDER)
.getCertificate(certGen.build(contentSigner));
return x509Certificate;
} catch (CertificateException | OperatorCreationException e) {
throw new IllegalStateException(e);
}
}
private List<Extension> certPolicyExtensions(String... certificatePolicyOid) {
List<Extension> certificatePolicies = new LinkedList<>();
if (certificatePolicyOid != null && certificatePolicyOid.length > 0)
{
List<PolicyInformation> policyInfoList = new LinkedList<>();
for (String oid: certificatePolicyOid)
{
policyInfoList.add(new PolicyInformation(new ASN1ObjectIdentifier(oid)));
}
CertificatePolicies policies = new CertificatePolicies(policyInfoList.toArray(new PolicyInformation[0]));
try {
boolean isCritical = false;
Extension extension = new Extension(Extension.certificatePolicies, isCritical, policies.getEncoded());
certificatePolicies.add(extension);
} catch (IOException e) {
throw new IllegalStateException(e);
}
}
return certificatePolicies;
}
}

View file

@ -0,0 +1,64 @@
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.keycloak.common.crypto.ECDSACryptoProvider;
public class BCFIPSECDSACryptoProvider implements ECDSACryptoProvider {
@Override
public byte[] concatenatedRSToASN1DER(final byte[] signature, int signLength) throws IOException {
int len = signLength / 2;
int arraySize = len + 1;
byte[] r = new byte[arraySize];
byte[] s = new byte[arraySize];
System.arraycopy(signature, 0, r, 1, len);
System.arraycopy(signature, len, s, 1, len);
BigInteger rBigInteger = new BigInteger(r);
BigInteger sBigInteger = new BigInteger(s);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
DERSequenceGenerator seqGen = new DERSequenceGenerator(bos);
seqGen.addObject(new ASN1Integer(rBigInteger.toByteArray()));
seqGen.addObject(new ASN1Integer(sBigInteger.toByteArray()));
seqGen.close();
bos.close();
return bos.toByteArray();
}
@Override
public byte[] asn1derToConcatenatedRS(final byte[] derEncodedSignatureValue, int signLength) throws IOException {
int len = signLength / 2;
ASN1InputStream asn1InputStream = new ASN1InputStream(derEncodedSignatureValue);
ASN1Primitive asn1Primitive = asn1InputStream.readObject();
asn1InputStream.close();
ASN1Sequence asn1Sequence = (ASN1Sequence.getInstance(asn1Primitive));
ASN1Integer rASN1 = (ASN1Integer) asn1Sequence.getObjectAt(0);
ASN1Integer sASN1 = (ASN1Integer) asn1Sequence.getObjectAt(1);
X9IntegerConverter x9IntegerConverter = new X9IntegerConverter();
byte[] r = x9IntegerConverter.integerToBytes(rASN1.getValue(), len);
byte[] s = x9IntegerConverter.integerToBytes(sASN1.getValue(), len);
byte[] concatenatedSignatureValue = new byte[signLength];
System.arraycopy(r, 0, concatenatedSignatureValue, 0, len);
System.arraycopy(s, 0, concatenatedSignatureValue, len, len);
return concatenatedSignatureValue;
}
}

View file

@ -0,0 +1,449 @@
/*
* Copyright 2016 Analytical Graphics, 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;
import org.bouncycastle.asn1.ASN1InputStream;
import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.asn1.DERIA5String;
import org.bouncycastle.asn1.DEROctetString;
import org.bouncycastle.asn1.ocsp.OCSPObjectIdentifiers;
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.asn1.x509.Extension;
import org.bouncycastle.asn1.x509.Extensions;
import org.bouncycastle.asn1.x509.SubjectKeyIdentifier;
import org.bouncycastle.asn1.x509.KeyPurposeId;
import org.bouncycastle.asn1.x509.AuthorityInformationAccess;
import org.bouncycastle.asn1.x509.AccessDescription;
import org.bouncycastle.asn1.x509.GeneralName;
import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
import org.bouncycastle.cert.jcajce.JcaX509CertificateHolder;
import org.bouncycastle.cert.jcajce.JcaX509ExtensionUtils;
import org.bouncycastle.cert.ocsp.BasicOCSPResp;
import org.bouncycastle.cert.ocsp.CertificateID;
import org.bouncycastle.cert.ocsp.CertificateStatus;
import org.bouncycastle.cert.ocsp.OCSPException;
import org.bouncycastle.cert.ocsp.OCSPReq;
import org.bouncycastle.cert.ocsp.OCSPReqBuilder;
import org.bouncycastle.cert.ocsp.OCSPResp;
import org.bouncycastle.cert.ocsp.RevokedStatus;
import org.bouncycastle.cert.ocsp.SingleResp;
import org.bouncycastle.cert.ocsp.UnknownStatus;
import org.bouncycastle.cert.ocsp.jcajce.JcaCertificateID;
import org.bouncycastle.operator.ContentVerifierProvider;
import org.bouncycastle.operator.DigestCalculator;
import org.bouncycastle.operator.DigestCalculatorProvider;
import org.bouncycastle.operator.OperatorCreationException;
import org.bouncycastle.operator.jcajce.JcaContentVerifierProviderBuilder;
import org.bouncycastle.operator.jcajce.JcaDigestCalculatorProviderBuilder;
import org.keycloak.common.util.BouncyIntegration;
import org.keycloak.models.KeycloakSession;
import org.keycloak.utils.OCSPProvider;
import java.io.IOException;
import java.math.BigInteger;
import java.net.URI;
import java.security.GeneralSecurityException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.SecureRandom;
import java.security.cert.CRLReason;
import java.security.cert.CertPath;
import java.security.cert.CertPathValidatorException;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateExpiredException;
import java.security.cert.CertificateNotYetValidException;
import java.security.cert.CertificateParsingException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* @author <a href="mailto:brat000012001@gmail.com">Peter Nalyvayko</a>
* @version $Revision: 1 $
* @since 10/29/2016
*/
public class BCFIPSOCSPProvider extends OCSPProvider {
private final static Logger logger = Logger.getLogger(BCFIPSOCSPProvider.class.getName());
protected OCSPResp getResponse(KeycloakSession session, OCSPReq ocspReq, URI responderUri) throws IOException, InterruptedException {
byte[] data = getEncodedOCSPResponse(session, ocspReq.getEncoded(), responderUri);
return new OCSPResp(data);
}
/**
* Requests certificate revocation status using OCSP.
* @param cert the certificate to be checked
* @param issuerCertificate the issuer certificate
* @param responderURIs the OCSP responder URIs
* @param responderCert the OCSP responder certificate
* @param date if null, the current time is used.
* @return a revocation status
* @throws CertPathValidatorException
*/
@Override
protected OCSPRevocationStatus check(KeycloakSession session, X509Certificate cert, X509Certificate issuerCertificate, List<URI> responderURIs, X509Certificate responderCert, Date date) throws CertPathValidatorException {
if (responderURIs == null || responderURIs.size() == 0)
throw new IllegalArgumentException("Need at least one responder");
try {
DigestCalculatorProvider dcp = new JcaDigestCalculatorProviderBuilder().build();
DigestCalculator digCalc = dcp.get(CertificateID.HASH_SHA1);
JcaCertificateID certificateID = new JcaCertificateID(digCalc, issuerCertificate, cert.getSerialNumber());
// Create a nounce extension to protect against replay attacks
SecureRandom random = SecureRandom.getInstance("SHA1PRNG");
BigInteger nounce = BigInteger.valueOf(Math.abs(random.nextInt()));
DEROctetString derString = new DEROctetString(nounce.toByteArray());
Extension nounceExtension = new Extension(OCSPObjectIdentifiers.id_pkix_ocsp_nonce, false, derString);
Extensions extensions = new Extensions(nounceExtension);
OCSPReq ocspReq = new OCSPReqBuilder().addRequest(certificateID, extensions).build();
URI responderURI = responderURIs.get(0);
logger.log(Level.INFO, "OCSP Responder {0}", responderURI);
try {
OCSPResp resp = getResponse(session, ocspReq, responderURI);
logger.log(Level.FINE, "Received a response from OCSP responder {0}, the response status is {1}", new Object[]{responderURI, resp.getStatus()});
switch (resp.getStatus()) {
case OCSPResp.SUCCESSFUL:
if (resp.getResponseObject() instanceof BasicOCSPResp) {
return processBasicOCSPResponse(issuerCertificate, responderCert, date, certificateID, nounce, (BasicOCSPResp)resp.getResponseObject());
} else {
throw new CertPathValidatorException("OCSP responder returned an invalid or unknown OCSP response.");
}
case OCSPResp.INTERNAL_ERROR:
case OCSPResp.TRY_LATER:
throw new CertPathValidatorException("Internal error/try later. OCSP response error: " + resp.getStatus(), (Throwable) null, (CertPath) null, -1, CertPathValidatorException.BasicReason.UNDETERMINED_REVOCATION_STATUS);
case OCSPResp.SIG_REQUIRED:
throw new CertPathValidatorException("Invalid or missing signature. OCSP response error: " + resp.getStatus(), (Throwable) null, (CertPath) null, -1, CertPathValidatorException.BasicReason.INVALID_SIGNATURE);
case OCSPResp.UNAUTHORIZED:
throw new CertPathValidatorException("Unauthorized request. OCSP response error: " + resp.getStatus(), (Throwable) null, (CertPath) null, -1, CertPathValidatorException.BasicReason.UNSPECIFIED);
case OCSPResp.MALFORMED_REQUEST:
default:
throw new CertPathValidatorException("OCSP request is malformed. OCSP response error: " + resp.getStatus(), (Throwable) null, (CertPath) null, -1, CertPathValidatorException.BasicReason.UNSPECIFIED);
}
}
catch(IOException | InterruptedException e) {
logger.log(Level.FINE, "OCSP Responder \"{0}\" failed to return a valid OCSP response\n{1}",
new Object[] {responderURI, e.getMessage()});
throw new CertPathValidatorException("OCSP check failed", e);
}
}
catch(CertificateNotYetValidException | CertificateExpiredException | OperatorCreationException | OCSPException |
CertificateEncodingException | NoSuchAlgorithmException | NoSuchProviderException e) {
logger.log(Level.FINE, e.getMessage());
throw new CertPathValidatorException(e.getMessage(), e);
}
}
private OCSPRevocationStatus processBasicOCSPResponse(X509Certificate issuerCertificate, X509Certificate responderCertificate, Date date, JcaCertificateID certificateID, BigInteger nounce, BasicOCSPResp basicOcspResponse)
throws OCSPException, NoSuchProviderException, NoSuchAlgorithmException, CertificateNotYetValidException, CertificateExpiredException, CertPathValidatorException {
SingleResp expectedResponse = null;
for (SingleResp singleResponse : basicOcspResponse.getResponses()) {
if (compareCertIDs(certificateID, singleResponse.getCertID())) {
expectedResponse = singleResponse;
break;
}
}
if (expectedResponse != null) {
verifyResponse(basicOcspResponse, issuerCertificate, responderCertificate, nounce.toByteArray(), date);
return singleResponseToRevocationStatus(expectedResponse);
} else {
throw new CertPathValidatorException("OCSP response does not include a response for a certificate supplied in the OCSP request");
}
}
private boolean compareCertIDs(JcaCertificateID idLeft, CertificateID idRight) {
if (idLeft == idRight)
return true;
if (idLeft == null || idRight == null)
return false;
return Arrays.equals(idLeft.getIssuerKeyHash(), idRight.getIssuerKeyHash()) &&
Arrays.equals(idLeft.getIssuerNameHash(), idRight.getIssuerNameHash()) &&
idLeft.getSerialNumber().equals(idRight.getSerialNumber());
}
private void verifyResponse(BasicOCSPResp basicOcspResponse, X509Certificate issuerCertificate, X509Certificate responderCertificate, byte[] requestNonce, Date date) throws NoSuchProviderException, NoSuchAlgorithmException, CertificateNotYetValidException, CertificateExpiredException, CertPathValidatorException {
List<X509CertificateHolder> certs = new ArrayList<>(Arrays.asList(basicOcspResponse.getCerts()));
X509Certificate signingCert = null;
try {
certs.add(new JcaX509CertificateHolder(issuerCertificate));
if (responderCertificate != null) {
certs.add(new JcaX509CertificateHolder(responderCertificate));
}
} catch (CertificateEncodingException e) {
e.printStackTrace();
}
if (certs.size() > 0) {
X500Name responderName = basicOcspResponse.getResponderId().toASN1Primitive().getName();
byte[] responderKey = basicOcspResponse.getResponderId().toASN1Primitive().getKeyHash();
if (responderName != null) {
logger.log(Level.INFO, "Responder Name: {0}", responderName.toString());
for (X509CertificateHolder certHolder : certs) {
try {
X509Certificate tempCert = new JcaX509CertificateConverter()
.setProvider(BouncyIntegration.PROVIDER).getCertificate(certHolder);
X500Name respName = new X500Name(tempCert.getSubjectX500Principal().getName());
if (responderName.equals(respName)) {
signingCert = tempCert;
logger.log(Level.INFO, "Found a certificate whose principal \"{0}\" matches the responder name \"{1}\"",
new Object[] {tempCert.getSubjectDN().getName(), responderName.toString()});
break;
}
} catch (CertificateException e) {
logger.log(Level.FINE, e.getMessage());
}
}
} else if (responderKey != null) {
SubjectKeyIdentifier responderSubjectKey = new SubjectKeyIdentifier(responderKey);
logger.log(Level.INFO, "Responder Key: {0}", Arrays.toString(responderKey));
for (X509CertificateHolder certHolder : certs) {
try {
X509Certificate tempCert = new JcaX509CertificateConverter()
.setProvider(BouncyIntegration.PROVIDER).getCertificate(certHolder);
SubjectKeyIdentifier subjectKeyIdentifier = null;
if (certHolder.getExtensions() != null) {
subjectKeyIdentifier = SubjectKeyIdentifier.fromExtensions(certHolder.getExtensions());
}
if (subjectKeyIdentifier != null) {
logger.log(Level.INFO, "Certificate: {0}\nSubject Key Id: {1}",
new Object[] {tempCert.getSubjectDN().getName(), Arrays.toString(subjectKeyIdentifier.getKeyIdentifier())});
}
if (subjectKeyIdentifier != null && responderSubjectKey.equals(subjectKeyIdentifier)) {
signingCert = tempCert;
logger.log(Level.INFO, "Found a signer certificate \"{0}\" with the subject key extension value matching the responder key",
signingCert.getSubjectDN().getName());
break;
}
subjectKeyIdentifier = new JcaX509ExtensionUtils().createSubjectKeyIdentifier(tempCert.getPublicKey());
if (responderSubjectKey.equals(subjectKeyIdentifier)) {
signingCert = tempCert;
logger.log(Level.INFO, "Found a certificate \"{0}\" with the subject key matching the OCSP responder key", signingCert.getSubjectDN().getName());
break;
}
} catch (CertificateException e) {
logger.log(Level.FINE, e.getMessage());
}
}
}
}
if (signingCert != null) {
if (signingCert.equals(issuerCertificate)) {
logger.log(Level.INFO, "OCSP response is signed by the target''s Issuing CA");
} else if (responderCertificate != null && signingCert.equals(responderCertificate)) {
// https://www.ietf.org/rfc/rfc2560.txt
// 2.6 OCSP Signature Authority Delegation
// - The responder certificate is issued to the responder by CA
logger.log(Level.INFO, "OCSP response is signed by an authorized responder certificate");
} else {
// 4.2.2.2 Authorized Responders
// 3. Includes a value of id-ad-ocspSigning in an ExtendedKeyUsage
// extension and is issued by the CA that issued the certificate in
// question."
if (!signingCert.getIssuerX500Principal().equals(issuerCertificate.getSubjectX500Principal())) {
logger.log(Level.INFO, "Signer certificate''s Issuer: {0}\nIssuer certificate''s Subject: {1}",
new Object[] {signingCert.getIssuerX500Principal().getName(), issuerCertificate.getSubjectX500Principal().getName()});
throw new CertPathValidatorException("Responder\'s certificate is not authorized to sign OCSP responses");
}
try {
List<String> purposes = signingCert.getExtendedKeyUsage();
if (purposes == null || !purposes.contains(KeyPurposeId.id_kp_OCSPSigning.getId())) {
logger.log(Level.INFO, "OCSPSigning extended usage is not set");
throw new CertPathValidatorException("Responder\'s certificate not valid for signing OCSP responses");
}
} catch (CertificateParsingException e) {
logger.log(Level.FINE, "Failed to get certificate''s extended key usage extension\n{0}", e.getMessage());
}
if (date == null) {
signingCert.checkValidity();
} else {
signingCert.checkValidity(date);
}
try {
Extension noOCSPCheck = new JcaX509CertificateHolder(signingCert).getExtension(OCSPObjectIdentifiers.id_pkix_ocsp_nocheck);
// TODO If the extension is present, the OCSP client can trust the
// responder's certificate for the lifetime of the certificate.
logger.log(Level.INFO, "OCSP no-check extension is {0} present", noOCSPCheck == null ? "not" : "");
} catch (CertificateEncodingException e) {
logger.log(Level.FINE, "Certificate encoding exception: {0}", e.getMessage());
}
try {
signingCert.verify(issuerCertificate.getPublicKey());
logger.log(Level.INFO, "OCSP response is signed by an Authorized Responder");
} catch (GeneralSecurityException ex) {
signingCert = null;
}
}
}
if (signingCert == null) {
throw new CertPathValidatorException("Unable to verify OCSP Response\'s signature");
} else {
if (!verifySignature(basicOcspResponse, signingCert)) {
throw new CertPathValidatorException("Error verifying OCSP Response\'s signature");
} else {
Extension responseNonce = basicOcspResponse.getExtension(OCSPObjectIdentifiers.id_pkix_ocsp_nonce);
if (responseNonce != null && requestNonce != null && !Arrays.equals(requestNonce, responseNonce.getExtnValue().getOctets())) {
throw new CertPathValidatorException("Nonces do not match.");
} else {
// See Sun's OCSP implementation.
// https://www.ietf.org/rfc/rfc2560.txt, if nextUpdate is not set,
// the responder is indicating that newer update is avilable all the time
long current = date == null ? System.currentTimeMillis() : date.getTime();
Date stop = new Date(current + (long) TIME_SKEW);
Date start = new Date(current - (long) TIME_SKEW);
Iterator<SingleResp> iter = Arrays.asList(basicOcspResponse.getResponses()).iterator();
SingleResp singleRes = null;
do {
if (!iter.hasNext()) {
return;
}
singleRes = iter.next();
}
while (!stop.before(singleRes.getThisUpdate()) &&
!start.after(singleRes.getNextUpdate() != null ? singleRes.getNextUpdate() : singleRes.getThisUpdate()));
throw new CertPathValidatorException("Response is unreliable: its validity interval is out-of-date");
}
}
}
}
private boolean verifySignature(BasicOCSPResp basicOcspResponse, X509Certificate cert) {
try {
ContentVerifierProvider contentVerifier = new JcaContentVerifierProviderBuilder()
.setProvider(BouncyIntegration.PROVIDER).build(cert.getPublicKey());
return basicOcspResponse.isSignatureValid(contentVerifier);
} catch (OperatorCreationException e) {
logger.log(Level.FINE, "Unable to construct OCSP content signature verifier\n{0}", e.getMessage());
} catch (OCSPException e) {
logger.log(Level.FINE, "Unable to validate OCSP response signature\n{0}", e.getMessage());
}
return false;
}
private OCSPRevocationStatus singleResponseToRevocationStatus(final SingleResp singleResponse) throws CertPathValidatorException {
final CertificateStatus certStatus = singleResponse.getCertStatus();
CRLReason revocationReason = CRLReason.UNSPECIFIED;
Date revocationTime = null;
RevocationStatus status = RevocationStatus.UNKNOWN;
if (certStatus == CertificateStatus.GOOD) {
status = RevocationStatus.GOOD;
} else if (certStatus instanceof RevokedStatus) {
RevokedStatus revoked = (RevokedStatus)certStatus;
revocationTime = revoked.getRevocationTime();
status = RevocationStatus.REVOKED;
if (revoked.hasRevocationReason()) {
revocationReason = CRLReason.values()[revoked.getRevocationReason()];
}
} else if (certStatus instanceof UnknownStatus) {
status = RevocationStatus.UNKNOWN;
} else {
throw new CertPathValidatorException("Unrecognized revocation status received from OCSP.");
}
final RevocationStatus finalStatus = status;
final Date finalRevocationTime = revocationTime;
final CRLReason finalRevocationReason = revocationReason;
return new OCSPRevocationStatus() {
@Override
public RevocationStatus getRevocationStatus() {
return finalStatus;
}
@Override
public Date getRevocationTime() {
return finalRevocationTime;
}
@Override
public CRLReason getRevocationReason() {
return finalRevocationReason;
}
};
}
/**
* Extracts OCSP responder URI from X509 AIA v3 extension, if available. There can be
* multiple responder URIs encoded in the certificate.
* @param cert
* @return a list of available responder URIs.
* @throws CertificateEncodingException
*/
@Override
protected List<String> getResponderURIs(X509Certificate cert) throws CertificateEncodingException {
LinkedList<String> responderURIs = new LinkedList<>();
JcaX509CertificateHolder holder = new JcaX509CertificateHolder(cert);
Extension aia = holder.getExtension(Extension.authorityInfoAccess);
if (aia != null) {
try {
ASN1InputStream in = new ASN1InputStream(aia.getExtnValue().getOctetStream());
ASN1Sequence seq = (ASN1Sequence)in.readObject();
AuthorityInformationAccess authorityInfoAccess = AuthorityInformationAccess.getInstance(seq);
for (AccessDescription ad : authorityInfoAccess.getAccessDescriptions()) {
if (ad.getAccessMethod().equals(AccessDescription.id_ad_ocsp)) {
// See https://www.ietf.org/rfc/rfc2560.txt, 3.1 Certificate Content
if (ad.getAccessLocation().getTagNo() == GeneralName.uniformResourceIdentifier) {
DERIA5String value = DERIA5String.getInstance(ad.getAccessLocation().getName());
responderURIs.add(value.getString());
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
return responderURIs;
}
}

View file

@ -0,0 +1,201 @@
/*
* Copyright 2016 Analytical Graphics, 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;
import org.bouncycastle.asn1.ASN1Encodable;
import org.bouncycastle.asn1.ASN1InputStream;
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.asn1.ASN1TaggedObject;
import org.bouncycastle.asn1.DERUTF8String;
import org.bouncycastle.asn1.x500.RDN;
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.asn1.x500.style.BCStyle;
import org.bouncycastle.asn1.x500.style.IETFUtils;
import org.jboss.logging.Logger;
import org.keycloak.common.crypto.UserIdentityExtractor;
import org.keycloak.common.crypto.UserIdentityExtractorProvider;
import java.io.ByteArrayInputStream;
import java.security.Principal;
import java.security.cert.CertificateParsingException;
import java.security.cert.X509Certificate;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.function.Function;
/**
* @author <a href="mailto:pnalyvayko@agi.com">Peter Nalyvayko</a>
* @version $Revision: 1 $
* @date 7/30/2016
*/
public class BCFIPSUserIdentityExtractorProvider extends UserIdentityExtractorProvider {
private static final Logger logger = Logger.getLogger(BCFIPSUserIdentityExtractorProvider.class.getName());
class X500NameRDNExtractorBCProvider extends X500NameRDNExtractor {
private ASN1ObjectIdentifier x500NameStyle;
Function<X509Certificate[],Principal> x500Name;
public X500NameRDNExtractorBCProvider(String attrName, Function<X509Certificate[], Principal> x500Name) {
this.x500NameStyle = BCStyle.INSTANCE.attrNameToOID(attrName);
this.x500Name = x500Name;
}
@Override
public Object extractUserIdentity(X509Certificate[] certs) {
if (certs == null || certs.length == 0)
throw new IllegalArgumentException();
X500Name name = new X500Name(x500Name.apply(certs).getName());
if (name != null) {
RDN[] rnds = name.getRDNs(x500NameStyle);
if (rnds != null && rnds.length > 0) {
RDN cn = rnds[0];
return IETFUtils.valueToString(cn.getFirst().getValue());
}
}
return null;
}
}
/**
* Extracts the subject identifier from the subjectAltName extension.
*/
class SubjectAltNameExtractorBCProvider extends SubjectAltNameExtractor {
// User Principal Name. Used typically by Microsoft in certificates for Smart Card Login
private static final String UPN_OID = "1.3.6.1.4.1.311.20.2.3";
private final int generalName;
/**
* Creates a new instance
*
* @param generalName an integer representing the general name. See {@link X509Certificate#getSubjectAlternativeNames()}
*/
SubjectAltNameExtractorBCProvider(int generalName) {
this.generalName = generalName;
}
@Override
public Object extractUserIdentity(X509Certificate[] certs) {
if (certs == null || certs.length == 0) {
throw new IllegalArgumentException();
}
try {
Collection<List<?>> subjectAlternativeNames = certs[0].getSubjectAlternativeNames();
if (subjectAlternativeNames == null) {
return null;
}
Iterator<List<?>> iterator = subjectAlternativeNames.iterator();
boolean foundUpn = false;
String tempOtherName = null;
String tempOid = null;
while (iterator.hasNext() && !foundUpn) {
List<?> next = iterator.next();
if (Integer.class.cast(next.get(0)) == generalName) {
// We will try to find UPN_OID among the subjectAltNames of type 'otherName' . Just if not found, we will fallback to the other type
for (int i = 1 ; i<next.size() ; i++) {
Object obj = next.get(i);
// We have Subject Alternative Name of other type than 'otherName' . Just return it directly
if (generalName != 0) {
logger.tracef("Extracted identity '%s' from Subject Alternative Name of type '%d'", obj, generalName);
return obj;
}
byte[] otherNameBytes = (byte[]) obj;
try {
ASN1InputStream asn1Stream = new ASN1InputStream(new ByteArrayInputStream(otherNameBytes));
ASN1Encodable asn1otherName = asn1Stream.readObject();
asn1otherName = unwrap(asn1otherName);
ASN1Sequence asn1Sequence = ASN1Sequence.getInstance(asn1otherName);
if (asn1Sequence != null) {
ASN1Encodable encodedOid = asn1Sequence.getObjectAt(0);
ASN1ObjectIdentifier oid = ASN1ObjectIdentifier.getInstance(unwrap(encodedOid));
tempOid = oid.getId();
ASN1Encodable principalNameEncoded = asn1Sequence.getObjectAt(1);
DERUTF8String principalName = DERUTF8String.getInstance(unwrap(principalNameEncoded));
tempOtherName = principalName.getString();
// We found UPN among the 'otherName' principal. We don't need to look other
if (UPN_OID.equals(tempOid)) {
foundUpn = true;
break;
}
}
} catch (Exception e) {
logger.error("Failed to parse subjectAltName", e);
}
}
}
}
logger.tracef("Parsed otherName from subjectAltName. OID: '%s', Principal: '%s'", tempOid, tempOtherName);
return tempOtherName;
} catch (CertificateParsingException cause) {
logger.errorf(cause, "Failed to obtain identity from subjectAltName extension");
}
return null;
}
private ASN1Encodable unwrap(ASN1Encodable encodable) {
while (encodable instanceof ASN1TaggedObject) {
ASN1TaggedObject taggedObj = (ASN1TaggedObject) encodable;
encodable = taggedObj.getObject();
}
return encodable;
}
}
@Override
public UserIdentityExtractor getX500NameExtractor(String identifier, Function<X509Certificate[], Principal> x500Name) {
return new X500NameRDNExtractorBCProvider(identifier, x500Name);
}
@Override
public SubjectAltNameExtractor getSubjectAltNameExtractor(int generalName) {
return new SubjectAltNameExtractorBCProvider(generalName);
}
}

View file

@ -18,9 +18,11 @@ import org.bouncycastle.crypto.fips.FipsSHS;
import org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider;
import org.bouncycastle.math.ec.ECCurve;
import org.keycloak.common.crypto.CryptoProvider;
import org.keycloak.common.crypto.ECDSACryptoProvider;
import org.keycloak.common.crypto.CryptoConstants;
import org.keycloak.common.crypto.CertificateUtilsProvider;
import org.keycloak.common.crypto.PemUtilsProvider;
import org.keycloak.common.crypto.UserIdentityExtractorProvider;
/**
@ -100,4 +102,20 @@ public class FIPS1402Provider implements CryptoProvider {
ECPoint point = new ECPoint( params.getG().getXCoord().toBigInteger(), params.getG().getYCoord().toBigInteger());
return new ECParameterSpec( c,point, params.getN(), params.getH().intValue());
}
@Override
public UserIdentityExtractorProvider getIdentityExtractorProvider() {
return new BCFIPSUserIdentityExtractorProvider();
}
@Override
public ECDSACryptoProvider getEcdsaCryptoProvider() {
return new BCFIPSECDSACryptoProvider();
}
@Override
public <T> T getOCSPProver(Class<T> clazz) {
return clazz.cast(new BCFIPSOCSPProvider());
}
}

View file

@ -31,7 +31,7 @@
<packaging>pom</packaging>
<modules>
<module>fips1402</module>
<module>default</module>
<module>fips1402</module>
</modules>
</project>

View file

@ -27,8 +27,11 @@
<module name="com.fasterxml.jackson.jaxrs.jackson-jaxrs-json-provider"/>
<module name="org.keycloak.keycloak-common"/>
<module name="org.keycloak.keycloak-core"/>
<module name="org.keycloak.keycloak-server-spi"/>
<module name="org.keycloak.keycloak-server-spi-private"/>
<module name="org.bouncycastle" />
<module name="org.jboss.logging"/>
<module name="org.apache.httpcomponents"/>
<module name="javax.api"/>
<module name="javax.activation.api"/>
<module name="sun.jdk" optional="true" />

View file

@ -37,7 +37,6 @@
<module name="org.liquibase" services="import"/>
<module name="org.javassist"/>
<module name="org.hibernate" services="import"/>
<module name="org.bouncycastle" />
<module name="javax.api"/>
<module name="com.fasterxml.jackson.core.jackson-core"/>
<module name="com.fasterxml.jackson.core.jackson-annotations"/>

View file

@ -60,7 +60,6 @@
<module name="com.google.zxing.core"/>
<module name="com.google.zxing.javase"/>
<module name="org.jboss.logging"/>
<module name="org.bouncycastle" />
<module name="javax.api"/>
<module name="javax.activation.api"/>
<module name="javax.json.api"/>

View file

@ -66,7 +66,6 @@
<module name="com.google.zxing.core"/>
<module name="com.google.zxing.javase"/>
<module name="org.jboss.logging"/>
<module name="org.bouncycastle" />
<module name="javax.api"/>
<module name="javax.activation.api"/>
<module name="javax.json.api"/>

View file

@ -27,8 +27,11 @@
<module name="com.fasterxml.jackson.jaxrs.jackson-jaxrs-json-provider"/>
<module name="org.keycloak.keycloak-common"/>
<module name="org.keycloak.keycloak-core"/>
<module name="org.keycloak.keycloak-server-spi"/>
<module name="org.keycloak.keycloak-server-spi-private"/>
<module name="org.bouncycastle" />
<module name="org.jboss.logging"/>
<module name="org.apache.httpcomponents"/>
<module name="javax.api"/>
<module name="javax.activation.api"/>
<module name="sun.jdk" optional="true" />

View file

@ -37,7 +37,6 @@
<module name="org.liquibase" services="import"/>
<module name="org.javassist"/>
<module name="org.hibernate" services="import"/>
<module name="org.bouncycastle" />
<module name="javax.api"/>
<module name="com.fasterxml.jackson.core.jackson-core"/>
<module name="com.fasterxml.jackson.core.jackson-annotations"/>

View file

@ -60,7 +60,6 @@
<module name="com.google.zxing.core"/>
<module name="com.google.zxing.javase"/>
<module name="org.jboss.logging"/>
<module name="org.bouncycastle" />
<module name="javax.api"/>
<module name="javax.activation.api"/>
<module name="javax.json.api"/>

View file

@ -74,7 +74,6 @@
<module name="com.google.zxing.core"/>
<module name="com.google.zxing.javase"/>
<module name="org.jboss.logging"/>
<module name="org.bouncycastle" />
<module name="javax.api"/>
<module name="javax.activation.api"/>
<module name="javax.json.api"/>

View file

@ -42,10 +42,6 @@
</properties>
<dependencies>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-core</artifactId>

View file

@ -12,10 +12,6 @@
<description/>
<dependencies>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-core</artifactId>

View file

@ -12,10 +12,6 @@
<description/>
<dependencies>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-core</artifactId>

View file

@ -44,10 +44,6 @@
</build>
<dependencies>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-core</artifactId>

View file

@ -59,7 +59,7 @@
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>

View file

@ -0,0 +1,213 @@
/*
* Copyright 2016 Analytical Graphics, 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.utils;
import java.io.IOException;
import java.net.URI;
import java.security.cert.CRLReason;
import java.security.cert.CertPathValidatorException;
import java.security.cert.CertificateEncodingException;
import java.security.cert.X509Certificate;
import java.util.Collections;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
import org.apache.http.HttpHeaders;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.util.EntityUtils;
import org.jboss.logging.Logger;
import org.jboss.logging.Logger.Level;
import org.keycloak.connections.httpclient.HttpClientProvider;
import org.keycloak.models.KeycloakSession;
/**
* @author <a href="mailto:brat000012001@gmail.com">Peter Nalyvayko</a>
* @version $Revision: 1 $
* @since 10/29/2016
*/
public abstract class OCSPProvider {
private final static Logger logger = Logger.getLogger(OCSPProvider.class);
protected static int OCSP_CONNECT_TIMEOUT = 10000; // 10 sec
protected static final int TIME_SKEW = 900000;
public enum RevocationStatus {
GOOD,
REVOKED,
UNKNOWN
}
public interface OCSPRevocationStatus {
RevocationStatus getRevocationStatus();
Date getRevocationTime();
CRLReason getRevocationReason();
}
/**
* Requests certificate revocation status using OCSP.
* @param session Keycloak session
* @param cert the certificate to be checked
* @param issuerCertificate The issuer certificate
* @param responderURI an address of OCSP responder. Overrides any OCSP responder URIs stored in certificate's AIA extension
* @param date
* @param responderCert a certificate that OCSP responder uses to sign OCSP responses
* @return revocation status
*/
public OCSPRevocationStatus check(KeycloakSession session, X509Certificate cert, X509Certificate issuerCertificate, URI responderURI, X509Certificate responderCert, Date date) throws CertPathValidatorException {
if (cert == null)
throw new IllegalArgumentException("cert cannot be null");
if (issuerCertificate == null)
throw new IllegalArgumentException("issuerCertificate cannot be null");
if (responderURI == null)
throw new IllegalArgumentException("responderURI cannot be null");
return check(session, cert, issuerCertificate, Collections.singletonList(responderURI), responderCert, date);
}
/**
* Requests certificate revocation status using OCSP. The OCSP responder URI
* is obtained from the certificate's AIA extension.
* @param session Keycloak session
* @param cert the certificate to be checked
* @param issuerCertificate The issuer certificate
* @param date
* @return revocation status
*/
public OCSPRevocationStatus check(KeycloakSession session, X509Certificate cert, X509Certificate issuerCertificate, Date date, X509Certificate responderCert) throws CertPathValidatorException {
List<String> responderURIs = null;
try {
responderURIs = getResponderURIs(cert);
} catch (CertificateEncodingException e) {
logger.log(Level.DEBUG, "CertificateEncodingException: {0}", e);
throw new CertPathValidatorException(e.getMessage(), e);
}
if (responderURIs.size() == 0) {
logger.log(Level.INFO, "No OCSP responders in the specified certificate");
throw new CertPathValidatorException("No OCSP Responder URI in certificate");
}
List<URI> uris = new LinkedList<>();
for (String value : responderURIs) {
try {
URI responderURI = URI.create(value);
uris.add(responderURI);
} catch (IllegalArgumentException ex) {
logger.log(Level.DEBUG, "Malformed responder URI {0}", value, ex);
}
}
return check(session, cert, issuerCertificate, Collections.unmodifiableList(uris), responderCert, date);
}
protected byte[] getEncodedOCSPResponse(KeycloakSession session, byte[] encodedOCSPReq, URI responderUri) throws IOException {
CloseableHttpClient httpClient = session.getProvider(HttpClientProvider.class).getHttpClient();
HttpPost post = new HttpPost(responderUri);
post.setHeader(HttpHeaders.CONTENT_TYPE, "application/ocsp-request");
final RequestConfig params = RequestConfig.custom()
.setConnectTimeout(OCSP_CONNECT_TIMEOUT)
.setSocketTimeout(OCSP_CONNECT_TIMEOUT)
.build();
post.setConfig(params);
post.setEntity(new ByteArrayEntity(encodedOCSPReq));
//Get Response
try (CloseableHttpResponse response = httpClient.execute(post)) {
try {
if (response.getStatusLine().getStatusCode() != 200) {
String errorMessage = String.format("Connection error, unable to obtain certificate revocation status using OCSP responder \"%s\", code \"%d\"",
responderUri.toString(), response.getStatusLine().getStatusCode());
throw new IOException(errorMessage);
}
byte[] data = EntityUtils.toByteArray(response.getEntity());
return data;
} finally {
EntityUtils.consumeQuietly(response.getEntity());
}
}
}
/**
* Requests certificate revocation status using OCSP. The OCSP responder URI
* is obtained from the certificate's AIA extension.
* @param session Keycloak session
* @param cert the certificate to be checked
* @param issuerCertificate The issuer certificate
* @return revocation status
*/
public OCSPRevocationStatus check(KeycloakSession session, X509Certificate cert, X509Certificate issuerCertificate) throws CertPathValidatorException {
return check(session, cert, issuerCertificate, null, null);
}
/**
* Requests certificate revocation status using OCSP.
* @param session Keycloak session
* @param cert the certificate to be checked
* @param issuerCertificate the issuer certificate
* @param responderURIs the OCSP responder URIs
* @param responderCert the OCSP responder certificate
* @param date if null, the current time is used.
* @return a revocation status
* @throws CertPathValidatorException
*/
protected abstract OCSPRevocationStatus check(KeycloakSession session, X509Certificate cert,
X509Certificate issuerCertificate, List<URI> responderURIs, X509Certificate responderCert, Date date)
throws CertPathValidatorException;
protected static OCSPRevocationStatus unknownStatus() {
return new OCSPRevocationStatus() {
@Override
public RevocationStatus getRevocationStatus() {
return RevocationStatus.UNKNOWN;
}
@Override
public Date getRevocationTime() {
return new Date(System.currentTimeMillis());
}
@Override
public CRLReason getRevocationReason() {
return CRLReason.UNSPECIFIED;
}
};
}
/**
* Extracts OCSP responder URI from X509 AIA v3 extension, if available. There can be
* multiple responder URIs encoded in the certificate.
* @param cert
* @return a list of available responder URIs.
* @throws CertificateEncodingException
*/
protected abstract List<String> getResponderURIs(X509Certificate cert) throws CertificateEncodingException;
}

View file

@ -35,14 +35,6 @@
</properties>
<dependencies>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcpkix-jdk15on</artifactId>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-core</artifactId>

View file

@ -19,6 +19,7 @@
package org.keycloak.authentication.authenticators.x509;
import java.security.GeneralSecurityException;
import java.security.Principal;
import java.security.cert.CertificateEncodingException;
import java.security.cert.X509Certificate;
import java.util.function.Function;
@ -26,12 +27,13 @@ import java.util.function.Function;
import javax.security.auth.x500.X500Principal;
import javax.ws.rs.core.Response;
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.asn1.x500.style.BCStyle;
import org.bouncycastle.cert.jcajce.JcaX509CertificateHolder;
import org.bouncycastle.util.encoders.Hex;
import org.apache.commons.codec.binary.Hex;
import org.keycloak.authentication.AuthenticationFlowContext;
import org.keycloak.authentication.Authenticator;
import org.keycloak.common.crypto.CryptoIntegration;
import org.keycloak.common.crypto.UserIdentityExtractor;
import org.keycloak.common.crypto.UserIdentityExtractorProvider;
import org.keycloak.events.Details;
import org.keycloak.forms.login.LoginFormsProvider;
import org.keycloak.jose.jws.crypto.HashUtils;
@ -134,27 +136,13 @@ public abstract class AbstractX509ClientCertificateAuthenticator implements Auth
protected static class UserIdentityExtractorBuilder {
private static final Function<X509Certificate[],X500Name> subject = certs -> {
try {
return new JcaX509CertificateHolder(certs[0]).getSubject();
} catch (CertificateEncodingException e) {
logger.warn("Unable to get certificate Subject", e);
}
return null;
};
private static final Function<X509Certificate[],X500Name> issuer = certs -> {
try {
return new JcaX509CertificateHolder(certs[0]).getIssuer();
} catch (CertificateEncodingException e) {
logger.warn("Unable to get certificate Issuer", e);
}
return null;
private static final Function<X509Certificate[],Principal> subject = certs -> {
return certs[0].getSubjectX500Principal();
};
private static Function<X509Certificate[], String> getSerialnumberFunc(X509AuthenticatorConfigModel config) {
return config.isSerialnumberHex() ?
certs -> Hex.toHexString(certs[0].getSerialNumber().toByteArray()) :
certs -> Hex.encodeHexString(certs[0].getSerialNumber().toByteArray()) :
certs -> certs[0].getSerialNumber().toString();
}
@ -171,24 +159,28 @@ public abstract class AbstractX509ClientCertificateAuthenticator implements Auth
UserIdentityExtractor extractor = null;
Function<X509Certificate[], String> func = null;
UserIdentityExtractorProvider userIdExtractor = CryptoIntegration.getProvider().getIdentityExtractorProvider();
logger.debug("UID Source: " + userIdentitySource);
logger.debug("UID Extractor: " + userIdExtractor.getClass().getName());
switch(userIdentitySource) {
case SUBJECTDN:
func = config.isCanonicalDnEnabled() ?
certs -> certs[0].getSubjectX500Principal().getName(X500Principal.CANONICAL) :
certs -> certs[0].getSubjectDN().getName();
extractor = UserIdentityExtractor.getPatternIdentityExtractor(pattern, func);
extractor = userIdExtractor.getPatternIdentityExtractor(pattern, func);
break;
case ISSUERDN:
extractor = UserIdentityExtractor.getPatternIdentityExtractor(pattern, getIssuerDNFunc(config));
extractor = userIdExtractor.getPatternIdentityExtractor(pattern, getIssuerDNFunc(config));
break;
case SERIALNUMBER:
extractor = UserIdentityExtractor.getPatternIdentityExtractor(DEFAULT_MATCH_ALL_EXPRESSION, getSerialnumberFunc(config));
extractor = userIdExtractor.getPatternIdentityExtractor(DEFAULT_MATCH_ALL_EXPRESSION, getSerialnumberFunc(config));
break;
case SHA256_THUMBPRINT:
extractor = UserIdentityExtractor.getPatternIdentityExtractor(DEFAULT_MATCH_ALL_EXPRESSION, certs -> {
extractor = userIdExtractor.getPatternIdentityExtractor(DEFAULT_MATCH_ALL_EXPRESSION, certs -> {
try {
return Hex.toHexString(HashUtils.hash(JavaAlgorithm.SHA256, certs[0].getEncoded()));
return Hex.encodeHexString(HashUtils.hash(JavaAlgorithm.SHA256, certs[0].getEncoded()));
} catch (CertificateEncodingException | HashException e) {
logger.warn("Unable to get certificate's thumbprint", e);
}
@ -197,24 +189,24 @@ public abstract class AbstractX509ClientCertificateAuthenticator implements Auth
break;
case SERIALNUMBER_ISSUERDN:
func = certs -> getSerialnumberFunc(config).apply(certs) + Constants.CFG_DELIMITER + getIssuerDNFunc(config).apply(certs);
extractor = UserIdentityExtractor.getPatternIdentityExtractor(DEFAULT_MATCH_ALL_EXPRESSION, func);
extractor = userIdExtractor.getPatternIdentityExtractor(DEFAULT_MATCH_ALL_EXPRESSION, func);
break;
case SUBJECTDN_CN:
extractor = UserIdentityExtractor.getX500NameExtractor(BCStyle.CN, subject);
extractor = userIdExtractor.getX500NameExtractor("CN", subject);
break;
case SUBJECTDN_EMAIL:
extractor = UserIdentityExtractor
.either(UserIdentityExtractor.getX500NameExtractor(BCStyle.EmailAddress, subject))
.or(UserIdentityExtractor.getX500NameExtractor(BCStyle.E, subject));
extractor = userIdExtractor
.either(userIdExtractor.getX500NameExtractor("EmailAddress", subject))
.or(userIdExtractor.getX500NameExtractor("E", subject));
break;
case SUBJECTALTNAME_EMAIL:
extractor = UserIdentityExtractor.getSubjectAltNameExtractor(1);
extractor = userIdExtractor.getSubjectAltNameExtractor(1);
break;
case SUBJECTALTNAME_OTHERNAME:
extractor = UserIdentityExtractor.getSubjectAltNameExtractor(0);
extractor = userIdExtractor.getSubjectAltNameExtractor(0);
break;
case CERTIFICATE_PEM:
extractor = UserIdentityExtractor.getCertificatePemIdentityExtractor(config);
extractor = userIdExtractor.getCertificatePemIdentityExtractor();
break;
default:
logger.warnf("[UserIdentityExtractorBuilder:fromConfig] Unknown or unsupported user identity source: \"%s\"", userIdentitySource.getName());

View file

@ -18,9 +18,10 @@
package org.keycloak.authentication.authenticators.x509;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.keycloak.common.crypto.CryptoConstants;
import org.keycloak.common.crypto.CryptoIntegration;
import org.keycloak.utils.OCSPProvider;
import org.keycloak.common.util.BouncyIntegration;
import org.keycloak.common.util.Time;
import org.keycloak.connections.httpclient.HttpClientProvider;
@ -49,7 +50,6 @@ import java.net.URI;
import java.net.URISyntaxException;
import java.security.GeneralSecurityException;
import java.security.cert.CertPathBuilder;
import java.security.cert.CertPathBuilderException;
import java.security.cert.CertPathValidatorException;
import java.security.cert.CertStore;
import java.security.cert.CertificateException;
@ -77,10 +77,6 @@ import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.util.EntityUtils;
import org.bouncycastle.asn1.x509.Extensions;
import org.bouncycastle.asn1.x509.CertificatePolicies;
import org.bouncycastle.cert.jcajce.JcaX509CertificateHolder;
import static org.keycloak.authentication.authenticators.x509.AbstractX509ClientCertificateAuthenticator.CERTIFICATE_POLICY_MODE_ANY;
/**
@ -165,7 +161,7 @@ public class CertificateValidator {
* @param issuerCertificate The issuer certificate
* @return revocation status
*/
public abstract OCSPUtils.OCSPRevocationStatus check(X509Certificate cert, X509Certificate issuerCertificate) throws CertPathValidatorException;
public abstract OCSPProvider.OCSPRevocationStatus check(X509Certificate cert, X509Certificate issuerCertificate) throws CertPathValidatorException;
}
public abstract static class CRLLoaderImpl {
@ -190,9 +186,9 @@ public class CertificateValidator {
}
@Override
public OCSPUtils.OCSPRevocationStatus check(X509Certificate cert, X509Certificate issuerCertificate) throws CertPathValidatorException {
public OCSPProvider.OCSPRevocationStatus check(X509Certificate cert, X509Certificate issuerCertificate) throws CertPathValidatorException {
OCSPUtils.OCSPRevocationStatus ocspRevocationStatus = null;
OCSPProvider.OCSPRevocationStatus ocspRevocationStatus = null;
if (responderUri == null || responderUri.trim().length() == 0) {
// Obtains revocation status of a certificate using OCSP and assuming
// most common defaults. If responderUri is not specified,
@ -203,7 +199,8 @@ public class CertificateValidator {
// 1) signed by the issuer certificate,
// 2) Includes the value of OCSPsigning in ExtendedKeyUsage v3 extension
// 3) Certificate is valid at the time
ocspRevocationStatus = OCSPUtils.check(session, cert, issuerCertificate);
OCSPProvider ocspProvider = CryptoIntegration.getProvider().getOCSPProver(OCSPProvider.class);
ocspRevocationStatus = ocspProvider.check(session, cert, issuerCertificate);
}
else {
URI uri;
@ -219,7 +216,8 @@ public class CertificateValidator {
// OCSP responder's certificate is assumed to be the issuer's certificate
// certificate.
// responderUri overrides the contents (if any) of the certificate's AIA extension
ocspRevocationStatus = OCSPUtils.check(session, cert, issuerCertificate, uri, responderCert, null);
OCSPProvider ocspProvider = CryptoIntegration.getProvider().getOCSPProver(OCSPProvider.class);
ocspRevocationStatus = ocspProvider.check(session, cert, issuerCertificate, uri, responderCert, null);
}
return ocspRevocationStatus;
}
@ -454,7 +452,7 @@ public class CertificateValidator {
}
boolean isCritical = false;
Set critSet = certs[0].getCriticalExtensionOIDs();
Set<String> critSet = certs[0].getCriticalExtensionOIDs();
if (critSet != null) {
isCritical = critSet.contains("2.5.29.15");
}
@ -491,7 +489,7 @@ public class CertificateValidator {
}
boolean isCritical = false;
Set critSet = certs[0].getCriticalExtensionOIDs();
Set<String> critSet = certs[0].getCriticalExtensionOIDs();
if (critSet != null) {
isCritical = critSet.contains("2.5.29.37");
}
@ -510,23 +508,14 @@ public class CertificateValidator {
}
}
private static void validatePolicy(X509Certificate[] certs, List<String> expectedPolicies, String policyCheckMode) throws GeneralSecurityException {
if (expectedPolicies == null || expectedPolicies.size() == 0) {
logger.debug("Certificate Policy validation is not enabled.");
return;
}
Extensions certExtensions = new JcaX509CertificateHolder(certs[0]).getExtensions();
if (certExtensions == null)
throw new GeneralSecurityException("Certificate Policy validation was expected, but no certificate extensions were found");
CertificatePolicies policies = CertificatePolicies.fromExtensions(certExtensions);
if (policies == null)
throw new GeneralSecurityException("Certificate Policy validation was expected, but no certificate policy extensions were found");
List<String> policyList = new LinkedList<>();
Arrays.stream(policies.getPolicyInformation()).forEach(p -> policyList.add(p.getPolicyIdentifier().toString().toLowerCase()));
List<String> policyList = CryptoIntegration.getProvider().getCertificateUtils().getCertificatePolicyList(certs[0]);
logger.debugf("Certificate policies found: %s", String.join(",", policyList));
@ -710,7 +699,7 @@ public class CertificateValidator {
}
try {
OCSPUtils.OCSPRevocationStatus rs = ocspChecker.check(cert, issuer);
OCSPProvider.OCSPRevocationStatus rs = ocspChecker.check(cert, issuer);
if (rs == null) {
if (_ocspFailOpen)
@ -719,13 +708,13 @@ public class CertificateValidator {
throw new GeneralSecurityException("Unable to check client revocation status using OCSP");
}
if (rs.getRevocationStatus() == OCSPUtils.RevocationStatus.UNKNOWN) {
if (rs.getRevocationStatus() == OCSPProvider.RevocationStatus.UNKNOWN) {
if (_ocspFailOpen)
logger.warnf("Unable to determine certificate's revocation status - continuing certificate authentication because of fail-open OCSP configuration setting");
else
throw new GeneralSecurityException("Unable to determine certificate's revocation status.");
}
else if (rs.getRevocationStatus() == OCSPUtils.RevocationStatus.REVOKED) {
else if (rs.getRevocationStatus() == OCSPProvider.RevocationStatus.REVOKED) {
StringBuilder sb = new StringBuilder();
sb.append("Certificate's been revoked.");
@ -755,7 +744,7 @@ public class CertificateValidator {
private static List<String> getCRLDistributionPoints(X509Certificate cert) {
try {
return CRLUtils.getCRLDistributionPoints(cert);
return CryptoIntegration.getProvider().getCertificateUtils().getCRLDistributionPoints(cert);
}
catch(IOException e) {
logger.error(e.getMessage());

View file

@ -1,17 +1,10 @@
package org.keycloak.crypto;
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.keycloak.common.VerificationException;
import org.keycloak.common.crypto.CryptoIntegration;
import org.keycloak.models.KeycloakSession;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.math.BigInteger;
public class ECDSASignatureProvider implements SignatureProvider {
@ -51,46 +44,11 @@ public class ECDSASignatureProvider implements SignatureProvider {
}
public static byte[] concatenatedRSToASN1DER(final byte[] signature, int signLength) throws IOException {
int len = signLength / 2;
int arraySize = len + 1;
byte[] r = new byte[arraySize];
byte[] s = new byte[arraySize];
System.arraycopy(signature, 0, r, 1, len);
System.arraycopy(signature, len, s, 1, len);
BigInteger rBigInteger = new BigInteger(r);
BigInteger sBigInteger = new BigInteger(s);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
DERSequenceGenerator seqGen = new DERSequenceGenerator(bos);
seqGen.addObject(new ASN1Integer(rBigInteger.toByteArray()));
seqGen.addObject(new ASN1Integer(sBigInteger.toByteArray()));
seqGen.close();
bos.close();
return bos.toByteArray();
return CryptoIntegration.getProvider().getEcdsaCryptoProvider().concatenatedRSToASN1DER(signature, signLength);
}
public static byte[] asn1derToConcatenatedRS(final byte[] derEncodedSignatureValue, int signLength) throws IOException {
int len = signLength / 2;
ASN1InputStream asn1InputStream = new ASN1InputStream(derEncodedSignatureValue);
ASN1Primitive asn1Primitive = asn1InputStream.readObject();
asn1InputStream.close();
ASN1Sequence asn1Sequence = (ASN1Sequence.getInstance(asn1Primitive));
ASN1Integer rASN1 = (ASN1Integer) asn1Sequence.getObjectAt(0);
ASN1Integer sASN1 = (ASN1Integer) asn1Sequence.getObjectAt(1);
X9IntegerConverter x9IntegerConverter = new X9IntegerConverter();
byte[] r = x9IntegerConverter.integerToBytes(rASN1.getValue(), len);
byte[] s = x9IntegerConverter.integerToBytes(sASN1.getValue(), len);
byte[] concatenatedSignatureValue = new byte[signLength];
System.arraycopy(r, 0, concatenatedSignatureValue, 0, len);
System.arraycopy(s, 0, concatenatedSignatureValue, len, len);
return concatenatedSignatureValue;
return CryptoIntegration.getProvider().getEcdsaCryptoProvider().asn1derToConcatenatedRS(derEncodedSignatureValue, signLength);
}
public enum ECDSA {

View file

@ -1,10 +1,12 @@
package org.keycloak.protocol.docker.installation.compose;
import org.keycloak.common.util.BouncyIntegration;
import org.keycloak.common.util.CertificateUtils;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.PrivateKey;
import java.security.SecureRandom;
import java.security.cert.Certificate;
@ -24,7 +26,7 @@ public class DockerComposeCertsDirectory {
final KeyPairGenerator keyGen;
try {
keyGen = KeyPairGenerator.getInstance("RSA");
keyGen = KeyPairGenerator.getInstance("RSA", BouncyIntegration.PROVIDER);
keyGen.initialize(2048, new SecureRandom());
final KeyPair keypair = keyGen.generateKeyPair();
@ -35,10 +37,7 @@ public class DockerComposeCertsDirectory {
localhostKeyFile = new AbstractMap.SimpleImmutableEntry<>(registryKeyFilename, DockerCertFileUtils.formatPrivateKeyContents(privateKey).getBytes());
idpTrustChainFile = new AbstractMap.SimpleEntry<>(idpCertTrustChainFilename, DockerCertFileUtils.formatCrtFileContents(realmCert).getBytes());
} catch (final NoSuchAlgorithmException e) {
// TODO throw error here descritively
throw new RuntimeException(e);
} catch (final CertificateEncodingException e) {
} catch (NoSuchAlgorithmException | NoSuchProviderException | CertificateEncodingException e) {
// TODO throw error here descritively
throw new RuntimeException(e);
}

View file

@ -17,29 +17,16 @@
package org.keycloak.utils;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.security.cert.X509CRL;
import java.security.cert.X509Certificate;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import javax.security.auth.x500.X500Principal;
import org.bouncycastle.asn1.ASN1InputStream;
import org.bouncycastle.asn1.DERIA5String;
import org.bouncycastle.asn1.DEROctetString;
import org.bouncycastle.asn1.x509.CRLDistPoint;
import org.bouncycastle.asn1.x509.DistributionPoint;
import org.bouncycastle.asn1.x509.DistributionPointName;
import org.bouncycastle.asn1.x509.GeneralName;
import org.bouncycastle.asn1.x509.GeneralNames;
import org.jboss.logging.Logger;
import org.keycloak.common.util.BouncyIntegration;
import org.keycloak.models.KeycloakSession;
@ -55,49 +42,6 @@ public final class CRLUtils {
private static final Logger log = Logger.getLogger(CRLUtils.class);
private static final String CRL_DISTRIBUTION_POINTS_OID = "2.5.29.31";
/**
* 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
*/
public static List<String> getCRLDistributionPoints(X509Certificate cert) throws IOException {
byte[] data = cert.getExtensionValue(CRL_DISTRIBUTION_POINTS_OID);
if (data == null) {
return Collections.emptyList();
}
List<String> distributionPointUrls = new LinkedList<>();
DEROctetString octetString;
try (ASN1InputStream crldpExtensionInputStream = new ASN1InputStream(new ByteArrayInputStream(data))) {
octetString = (DEROctetString)crldpExtensionInputStream.readObject();
}
byte[] octets = octetString.getOctets();
CRLDistPoint crlDP;
try (ASN1InputStream crldpInputStream = new ASN1InputStream(new ByteArrayInputStream(octets))) {
crlDP = CRLDistPoint.getInstance(crldpInputStream.readObject());
}
for (DistributionPoint dp : crlDP.getDistributionPoints()) {
DistributionPointName dpn = dp.getDistributionPoint();
if (dpn != null && dpn.getType() == DistributionPointName.FULL_NAME) {
GeneralName[] names = GeneralNames.getInstance(dpn.getName()).getNames();
for (GeneralName gn : names) {
if (gn.getTagNo() == GeneralName.uniformResourceIdentifier) {
String url = DERIA5String.getInstance(gn.getName()).getString();
distributionPointUrls.add(url);
}
}
}
}
return distributionPointUrls;
}
/**
* Check the signature on CRL and check if 1st certificate from the chain ((The actual certificate from the client)) is valid and not available on CRL.

View file

@ -9,6 +9,8 @@ import java.security.cert.X509Certificate;
import org.junit.ClassRule;
import org.keycloak.rule.CryptoInitRule;
import org.junit.Test;
import org.keycloak.common.crypto.CryptoIntegration;
import org.keycloak.common.crypto.UserIdentityExtractor;
import org.keycloak.common.util.PemUtils;
import org.keycloak.common.util.StreamUtil;
@ -23,8 +25,8 @@ public class CertificatePemIdentityExtractorTest {
X509Certificate x509Certificate = PemUtils.decodeCertificate(StreamUtil.readString(is, Charset.defaultCharset()));
String certificatePem = PemUtils.encodeCertificate(x509Certificate);
X509AuthenticatorConfigModel config = new X509AuthenticatorConfigModel();
UserIdentityExtractor extractor = UserIdentityExtractor.getCertificatePemIdentityExtractor(config);
//X509AuthenticatorConfigModel config = new X509AuthenticatorConfigModel();
UserIdentityExtractor extractor = CryptoIntegration.getProvider().getIdentityExtractorProvider().getCertificatePemIdentityExtractor();
String userIdentity = (String) extractor.extractUserIdentity(new X509Certificate[]{x509Certificate});

View file

@ -1,35 +1,19 @@
package org.keycloak.authentication.authenticators.x509;
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.asn1.x509.CertificatePolicies;
import org.bouncycastle.asn1.x509.Extension;
import org.bouncycastle.asn1.x509.PolicyInformation;
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
import org.bouncycastle.cert.CertIOException;
import org.bouncycastle.cert.X509v3CertificateBuilder;
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.operator.ContentSigner;
import org.bouncycastle.operator.OperatorCreationException;
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
import org.hamcrest.MatcherAssert;
import org.hamcrest.Matchers;
import org.junit.Assert;
import org.junit.ClassRule;
import org.junit.Test;
import org.keycloak.common.crypto.CryptoIntegration;
import org.keycloak.common.util.BouncyIntegration;
import org.keycloak.rule.CryptoInitRule;
import java.io.IOException;
import java.math.BigInteger;
import java.security.GeneralSecurityException;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.SecureRandom;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
import static org.keycloak.authentication.authenticators.x509.AbstractX509ClientCertificateAuthenticator.CERTIFICATE_POLICY_MODE_ALL;
import static org.keycloak.authentication.authenticators.x509.AbstractX509ClientCertificateAuthenticator.CERTIFICATE_POLICY_MODE_ANY;
@ -42,19 +26,20 @@ import static org.keycloak.authentication.authenticators.x509.AbstractX509Client
*/
public class CertificateValidatorTest {
private static final BouncyCastleProvider BOUNCY_CASTLE_PROVIDER = new BouncyCastleProvider();
@ClassRule
public static CryptoInitRule cryptoInitRule = new CryptoInitRule();
/**
* will validate that the certificate validation succeeds if the certificate is currently valid
*/
@Test
public void testValidityOfCertificatesSuccess() throws GeneralSecurityException {
KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA");
KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA", BouncyIntegration.PROVIDER);
kpg.initialize(512);
KeyPair keyPair = kpg.generateKeyPair();
X509Certificate certificate =
createCertificate("CN=keycloak-test", new Date(),
new Date(System.currentTimeMillis() + 1000L * 60), keyPair, null);
X509Certificate certificate = CryptoIntegration.getProvider().getCertificateUtils()
.createServicesTestCertificate("CN=keycloak-test", new Date(),
new Date(System.currentTimeMillis() + 1000L * 60), keyPair);
CertificateValidator.CertificateValidatorBuilder builder =
new CertificateValidator.CertificateValidatorBuilder();
@ -75,12 +60,12 @@ public class CertificateValidatorTest {
*/
@Test
public void testValidityOfCertificatesNotValidYet() throws GeneralSecurityException {
KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA");
KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA", BouncyIntegration.PROVIDER);
kpg.initialize(512);
KeyPair keyPair = kpg.generateKeyPair();
X509Certificate certificate =
createCertificate("CN=keycloak-test", new Date(System.currentTimeMillis() + 1000L * 60),
new Date(System.currentTimeMillis() + 1000L * 60), keyPair, null);
X509Certificate certificate = CryptoIntegration.getProvider().getCertificateUtils()
.createServicesTestCertificate("CN=keycloak-test", new Date(System.currentTimeMillis() + 1000L * 60),
new Date(System.currentTimeMillis() + 1000L * 60), keyPair);
CertificateValidator.CertificateValidatorBuilder builder =
new CertificateValidator.CertificateValidatorBuilder();
@ -102,12 +87,13 @@ public class CertificateValidatorTest {
*/
@Test
public void testValidityOfCertificatesHasExpired() throws GeneralSecurityException {
KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA");
KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA", BouncyIntegration.PROVIDER);
kpg.initialize(512);
KeyPair keyPair = kpg.generateKeyPair();
X509Certificate certificate =
createCertificate("CN=keycloak-test", new Date(System.currentTimeMillis() - 1000L * 60 * 2),
new Date(System.currentTimeMillis() - 1000L * 60), keyPair, null);
X509Certificate certificate = CryptoIntegration.getProvider().getCertificateUtils()
.createServicesTestCertificate("CN=keycloak-test",
new Date(System.currentTimeMillis() - 1000L * 60 * 2),
new Date(System.currentTimeMillis() - 1000L * 60), keyPair);
CertificateValidator.CertificateValidatorBuilder builder =
new CertificateValidator.CertificateValidatorBuilder();
@ -157,7 +143,7 @@ public class CertificateValidatorTest {
*/
@Test(expected = GeneralSecurityException.class)
public void testCertificatePolicyModeAllOneRequestedAndNotPresent() throws GeneralSecurityException {
testCertificatePolicyValidation("1.3.76.16.2.1", CERTIFICATE_POLICY_MODE_ALL, null);
testCertificatePolicyValidation("1.3.76.16.2.1", CERTIFICATE_POLICY_MODE_ALL);
}
/**
@ -265,7 +251,7 @@ public class CertificateValidatorTest {
*/
@Test(expected = GeneralSecurityException.class)
public void testCertificatePolicyModeAnyOneRequestedAndNotPresent() throws GeneralSecurityException {
testCertificatePolicyValidation("1.3.76.16.2.1", CERTIFICATE_POLICY_MODE_ANY, null);
testCertificatePolicyValidation("1.3.76.16.2.1", CERTIFICATE_POLICY_MODE_ANY);
}
/**
@ -344,35 +330,15 @@ public class CertificateValidatorTest {
private void testCertificatePolicyValidation(String expectedPolicy, String mode, String... certificatePolicyOid)
throws GeneralSecurityException
{
List<Extension> certificatePolicies = null;
if (certificatePolicyOid != null && certificatePolicyOid.length > 0)
{
certificatePolicies = new LinkedList<>();
List<PolicyInformation> policyInfoList = new LinkedList<>();
for (String oid: certificatePolicyOid)
{
policyInfoList.add(new PolicyInformation(new ASN1ObjectIdentifier(oid)));
}
CertificatePolicies policies = new CertificatePolicies(policyInfoList.toArray(new PolicyInformation[0]));
try {
boolean isCritical = false;
Extension extension = new Extension(Extension.certificatePolicies, isCritical, policies.getEncoded());
certificatePolicies.add(extension);
} catch (IOException e) {
throw new IllegalStateException(e);
}
}
KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA");
KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA", BouncyIntegration.PROVIDER);
kpg.initialize(512);
KeyPair keyPair = kpg.generateKeyPair();
X509Certificate certificate =
createCertificate("CN=keycloak-test", new Date(System.currentTimeMillis() - 1000L * 60 * 2),
new Date(System.currentTimeMillis() - 1000L * 60), keyPair, certificatePolicies);
X509Certificate certificate = CryptoIntegration.getProvider().getCertificateUtils()
.createServicesTestCertificate("CN=keycloak-test",
new Date(System.currentTimeMillis() - 1000L * 60 * 2),
new Date(System.currentTimeMillis() - 1000L * 60), keyPair, certificatePolicyOid);
CertificateValidator.CertificateValidatorBuilder builder =
new CertificateValidator.CertificateValidatorBuilder();
@ -385,57 +351,4 @@ public class CertificateValidatorTest {
validator.validatePolicy();
}
/**
* will create a self-signed certificate
*
* @param dn the DN of the subject and issuer
* @param startDate startdate of the validity of the created certificate
* @param expiryDate expiration date of the created certificate
* @param keyPair the keypair that is used to create the certificate
* @param extensions optional list of extensions to include in the certificate
* @return a X509-Certificate in version 3
*/
public X509Certificate createCertificate(String dn,
Date startDate,
Date expiryDate,
KeyPair keyPair,
List<Extension> extensions) {
// Cert data
X500Name subjectDN = new X500Name(dn);
X500Name issuerDN = new X500Name(dn);
SubjectPublicKeyInfo subjPubKeyInfo = SubjectPublicKeyInfo.getInstance(
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);
if (extensions != null)
{
try
{
for (Extension certExtension: extensions)
certGen.addExtension(certExtension);
} catch (CertIOException e) {
throw new IllegalStateException(e);
}
}
// Sign the cert with the private key
try {
ContentSigner contentSigner = new JcaContentSignerBuilder("SHA256withRSA")
.setProvider(BOUNCY_CASTLE_PROVIDER)
.build(keyPair.getPrivate());
X509Certificate x509Certificate = new JcaX509CertificateConverter()
.setProvider(BOUNCY_CASTLE_PROVIDER)
.getCertificate(certGen.build(contentSigner));
return x509Certificate;
} catch (CertificateException | OperatorCreationException e) {
throw new IllegalStateException(e);
}
}
}

View file

@ -23,6 +23,8 @@ import java.security.cert.X509Certificate;
import org.junit.Assert;
import org.junit.ClassRule;
import org.junit.Test;
import org.keycloak.common.crypto.CryptoIntegration;
import org.keycloak.common.crypto.UserIdentityExtractor;
import org.keycloak.common.util.PemUtils;
import org.keycloak.common.util.StreamUtil;
import org.keycloak.rule.CryptoInitRule;
@ -37,7 +39,7 @@ public class SubjectAltNameIdentityExtractorTest {
@Test
public void testX509SubjectAltName_otherName() throws Exception {
UserIdentityExtractor extractor = UserIdentityExtractor.getSubjectAltNameExtractor(0);
UserIdentityExtractor extractor = CryptoIntegration.getProvider().getIdentityExtractorProvider().getSubjectAltNameExtractor(0);
X509Certificate cert = getCertificate();
@ -48,7 +50,7 @@ public class SubjectAltNameIdentityExtractorTest {
@Test
public void testX509SubjectAltName_email() throws Exception {
UserIdentityExtractor extractor = UserIdentityExtractor.getSubjectAltNameExtractor(1);
UserIdentityExtractor extractor = CryptoIntegration.getProvider().getIdentityExtractorProvider().getSubjectAltNameExtractor(1);
X509Certificate cert = getCertificate();

View file

@ -16,7 +16,6 @@ import java.net.URL;
import java.nio.charset.Charset;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.SecureRandom;
import java.security.cert.Certificate;
@ -31,10 +30,10 @@ import javax.ws.rs.core.Response;
import org.apache.commons.io.FileUtils;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.ClassRule;
import org.junit.Ignore;
import org.junit.Test;
import org.keycloak.common.util.BouncyIntegration;
import org.keycloak.common.util.CertificateUtils;
import org.keycloak.common.util.PemUtils;
import org.keycloak.protocol.docker.installation.DockerComposeYamlInstallationProvider;
@ -47,19 +46,15 @@ public class DockerComposeYamlInstallationProviderTest {
DockerComposeYamlInstallationProvider installationProvider;
static Certificate certificate;
@BeforeClass
public static void setUp_beforeClass() throws NoSuchAlgorithmException {
@Before
public void setUp() throws Exception {
final KeyPairGenerator keyGen;
keyGen = KeyPairGenerator.getInstance("RSA");
keyGen = KeyPairGenerator.getInstance("RSA", BouncyIntegration.PROVIDER);
keyGen.initialize(2048, new SecureRandom());
final KeyPair keypair = keyGen.generateKeyPair();
certificate = CertificateUtils.generateV1SelfSignedCertificate(keypair, "test-realm");
}
@Before
public void setUp() {
installationProvider = new DockerComposeYamlInstallationProvider();
}