/* * Copyright 2017 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.testsuite.x509; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.math.BigInteger; import java.nio.ByteBuffer; import java.security.GeneralSecurityException; import java.security.cert.Certificate; import java.security.cert.CertificateFactory; import java.util.Date; import java.util.Map; import com.google.common.collect.ImmutableMap; import org.bouncycastle.asn1.nist.NISTObjectIdentifiers; import org.bouncycastle.asn1.ocsp.OCSPObjectIdentifiers; import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers; 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.SubjectPublicKeyInfo; import org.bouncycastle.cert.X509CertificateHolder; import org.bouncycastle.cert.ocsp.BasicOCSPRespBuilder; import org.bouncycastle.cert.ocsp.CertificateID; import org.bouncycastle.cert.ocsp.CertificateStatus; import org.bouncycastle.cert.ocsp.OCSPReq; import org.bouncycastle.cert.ocsp.OCSPResp; import org.bouncycastle.cert.ocsp.OCSPRespBuilder; import org.bouncycastle.cert.ocsp.Req; import org.bouncycastle.cert.ocsp.RespID; import org.bouncycastle.cert.ocsp.RevokedStatus; import org.bouncycastle.crypto.params.AsymmetricKeyParameter; import org.bouncycastle.crypto.util.PrivateKeyFactory; import org.bouncycastle.crypto.util.PublicKeyFactory; import org.bouncycastle.crypto.util.SubjectPublicKeyInfoFactory; import org.bouncycastle.openssl.PEMKeyPair; import org.bouncycastle.openssl.PEMParser; import org.bouncycastle.operator.ContentSigner; import org.bouncycastle.operator.DigestCalculator; import org.bouncycastle.operator.OperatorCreationException; import org.bouncycastle.operator.bc.BcRSAContentSignerBuilder; import org.bouncycastle.operator.jcajce.JcaDigestCalculatorProviderBuilder; import io.undertow.io.Sender; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.util.HeaderMap; import io.undertow.util.Headers; final class OcspHandler implements HttpHandler { private static final String OCSP_RESPONDER_CERT_PATH = "/client-auth-test/intermediate-ca.crt"; private static final String OCSP_RESPONDER_KEYPAIR_PATH = "/client-auth-test/intermediate-ca.key"; // add any certificates that the OCSP responder needs to know about in the tests here private static final Map<BigInteger, CertificateStatus> REVOKED_CERTIFICATES_STATUS = ImmutableMap .of(BigInteger.valueOf(4096), new RevokedStatus(new Date(1472169600000L), CRLReason.unspecified)); private final SubjectPublicKeyInfo subjectPublicKeyInfo; private final X509CertificateHolder[] chain; private final AsymmetricKeyParameter privateKey; OcspHandler() throws OperatorCreationException, GeneralSecurityException, IOException { final Certificate certificate = CertificateFactory.getInstance("X509") .generateCertificate(X509OCSPResponderTest.class.getResourceAsStream(OCSP_RESPONDER_CERT_PATH)); chain = new X509CertificateHolder[] {new X509CertificateHolder(certificate.getEncoded())}; final AsymmetricKeyParameter publicKey = PublicKeyFactory.createKey(certificate.getPublicKey().getEncoded()); subjectPublicKeyInfo = SubjectPublicKeyInfoFactory.createSubjectPublicKeyInfo(publicKey); final InputStream keyPairStream = X509OCSPResponderTest.class.getResourceAsStream(OCSP_RESPONDER_KEYPAIR_PATH); try (final PEMParser keyPairReader = new PEMParser(new InputStreamReader(keyPairStream))) { final PEMKeyPair keyPairPem = (PEMKeyPair) keyPairReader.readObject(); privateKey = PrivateKeyFactory.createKey(keyPairPem.getPrivateKeyInfo()); } } @Override public void handleRequest(final HttpServerExchange exchange) throws Exception { if (exchange.isInIoThread()) { exchange.dispatch(this); return; } final byte[] buffy = new byte[16384]; try (InputStream requestStream = exchange.getInputStream()) { requestStream.read(buffy); } final OCSPReq request = new OCSPReq(buffy); final Req[] requested = request.getRequestList(); final Extension nonce = request.getExtension(OCSPObjectIdentifiers.id_pkix_ocsp_nonce); final DigestCalculator sha1Calculator = new JcaDigestCalculatorProviderBuilder().build() .get(AlgorithmIdentifier.getInstance(RespID.HASH_SHA1)); final BasicOCSPRespBuilder responseBuilder = new BasicOCSPRespBuilder(subjectPublicKeyInfo, sha1Calculator); if (nonce != null) { responseBuilder.setResponseExtensions(new Extensions(nonce)); } for (final Req req : requested) { final CertificateID certId = req.getCertID(); final BigInteger certificateSerialNumber = certId.getSerialNumber(); responseBuilder.addResponse(certId, REVOKED_CERTIFICATES_STATUS.get(certificateSerialNumber)); } final ContentSigner contentSigner = new BcRSAContentSignerBuilder( new AlgorithmIdentifier(PKCSObjectIdentifiers.sha256WithRSAEncryption), new AlgorithmIdentifier(NISTObjectIdentifiers.id_sha256)).build(privateKey); final OCSPResp response = new OCSPRespBuilder().build(OCSPResp.SUCCESSFUL, responseBuilder.build(contentSigner, chain, new Date())); final byte[] responseBytes = response.getEncoded(); final HeaderMap responseHeaders = exchange.getResponseHeaders(); responseHeaders.put(Headers.CONTENT_TYPE, "application/ocsp-response"); final Sender responseSender = exchange.getResponseSender(); responseSender.send(ByteBuffer.wrap(responseBytes)); exchange.endExchange(); } }