ElytronOCSPProvider.java
/*
* Copyright 2017 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.crypto.elytron;
import java.io.IOException;
import java.net.URI;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CRLReason;
import java.security.cert.CertPathValidatorException;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Arrays;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
import javax.net.ssl.TrustManagerFactory;
import org.jboss.logging.Logger;
import org.keycloak.models.KeycloakSession;
import org.keycloak.utils.OCSPProvider;
import org.wildfly.security.asn1.ASN1;
import org.wildfly.security.asn1.DERDecoder;
import org.wildfly.security.ssl.X509RevocationTrustManager;
import org.wildfly.security.x500.X500;
/**
* @author <a href="mailto:david.anderson@redhat.com">David Anderson</a>
*/
public class ElytronOCSPProvider extends OCSPProvider {
private final static Logger logger = Logger.getLogger(ElytronOCSPProvider.class.getName());
/**
* 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 {
KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
trustStore.load(null,"pass".toCharArray());
trustStore.setCertificateEntry("trust", cert);
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
X509RevocationTrustManager trustMgr = X509RevocationTrustManager.builder()
.setOcspResponderCert(responderCert)
.setTrustStore(trustStore)
.setTrustManagerFactory(trustManagerFactory)
.build()
;
X509Certificate[] certs = { cert };
trustMgr.checkClientTrusted(certs, cert.getType());
} catch (NoSuchAlgorithmException | CertificateException | IOException | KeyStoreException e) {
logger.warn("OSCP Response check failed.", e);
return unknownStatus();
}
return new OCSPRevocationStatus() {
@Override
public RevocationStatus getRevocationStatus() {
return RevocationStatus.GOOD;
}
@Override
public Date getRevocationTime() {
return null;
}
@Override
public CRLReason getRevocationReason() {
return null;
}
};
}
/**
* 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<>();
byte[] authinfob = cert.getExtensionValue(X500.OID_PE_AUTHORITY_INFO_ACCESS);
DERDecoder der = new DERDecoder(authinfob);
der = new DERDecoder(der.decodeOctetString());
while ( der.hasNextElement() ) {
switch (der.peekType()) {
case ASN1.SEQUENCE_TYPE:
der.startSequence();
break;
case ASN1.OBJECT_IDENTIFIER_TYPE:
String oid = der.decodeObjectIdentifier();
if ("1.3.6.1.5.5.7.48.1".equals(oid)) {
byte[] uri = der.drainElementValue();
responderURIs.add(new String(uri));
}
break;
case ASN1.IA5_STRING_TYPE:
break;
case ASN1.UTF8_STRING_TYPE:
responderURIs.add(der.decodeUtf8String());
break;
case 0xa0:
der.decodeImplicit(0xa0);
byte[] edata = der.decodeOctetString();
while(!Character.isLetterOrDigit(edata[0])) {
edata = Arrays.copyOfRange(edata, 1, edata.length);
}
responderURIs.add(new String(edata));
break;
default:
der.skipElement();
}
}
return responderURIs;
}
}