com.henricook.tls.x509.ocsp.OCSP.scala Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of cryptoutils_2.12 Show documentation
Show all versions of cryptoutils_2.12 Show documentation
Cryptoutils for Scala 2.12 and 2.13 - Forked from Karasiq
The newest version!
package com.henricook.tls.x509.ocsp
import java.net.URL
import java.security.SecureRandom
import java.time.Instant
import java.util.Date
import com.henricook.tls.TLS
import com.henricook.tls.internal.BCConversions._
import com.henricook.tls.internal.TLSUtils
import com.henricook.tls.x509.X509Utils
import org.apache.commons.io.IOUtils
import org.bouncycastle.asn1.DEROctetString
import org.bouncycastle.asn1.ocsp.OCSPObjectIdentifiers
import org.bouncycastle.asn1.x509.{CRLReason, ExtensionsGenerator, KeyUsage}
import org.bouncycastle.cert.X509CertificateHolder
import org.bouncycastle.cert.ocsp._
import org.bouncycastle.cert.ocsp.jcajce.JcaBasicOCSPRespBuilder
import org.bouncycastle.operator.DigestCalculator
import org.bouncycastle.operator.jcajce.JcaDigestCalculatorProviderBuilder
import org.bouncycastle.util.encoders.Base64
/**
* Online Certificate Status Protocol (OCSP) utility
* @see [[https://en.wikipedia.org/wiki/Online_Certificate_Status_Protocol]]
*/
object OCSP {
/**
* OCSP certificate status
* @param id Certificate ID
* @param status Certificate status
*/
case class Status(
id: CertificateID,
status: CertificateStatus = Status.good
) {
def isRevoked: Boolean =
status.ne(null) && status.isInstanceOf[RevokedStatus]
}
object Status {
/**
* OCSP good status
* @return Good status
*/
val good: CertificateStatus = CertificateStatus.GOOD
/**
* OCSP revoked status
* @param reason Revocation reason
* @param date Revocation date
* @return Revoked status
*/
def revoked(
reason: Int = CRLReason.unspecified,
date: Instant = Instant.now()
): CertificateStatus = {
new RevokedStatus(Date.from(date), reason)
}
def wrap(responses: Array[SingleResp]): Seq[Status] = responses.map { resp ⇒
Status(resp.getCertID, resp.getCertStatus)
}
}
private val digestCalculator: DigestCalculator =
new JcaDigestCalculatorProviderBuilder()
.setProvider(TLSUtils.provider)
.build()
.get(CertificateID.HASH_SHA1)
private val secureRandom = SecureRandom.getInstanceStrong
/**
* OCSP certificate ID
* @param issuer Issuer certificate
* @param serial Certificate serial number
* @return OCSP certificate ID
*/
def id(issuer: TLS.Certificate, serial: BigInt): CertificateID = {
new CertificateID(
digestCalculator,
new X509CertificateHolder(issuer),
serial.underlying()
)
}
/**
* Creates signed OCSP request
* @param signer Signer credentials
* @param ids Certificate IDs
* @return Signed OCSP request
*/
def signedRequest(
signer: TLS.CertificateKey,
ids: CertificateID*
): OCSPReq = {
val builder =
ids.foldLeft(new OCSPReqBuilder())((builder, id) ⇒ builder.addRequest(id))
builder.setRequestorName(signer.certificate.getSubject)
val extGen = new ExtensionsGenerator()
val nonce: DEROctetString = {
val bytes = new Array[Byte](16)
secureRandom.nextBytes(bytes)
new DEROctetString(bytes)
}
extGen.addExtension(OCSPObjectIdentifiers.id_pkix_ocsp_nonce, false, nonce)
builder.setRequestExtensions(extGen.generate())
builder.build(
X509Utils.contentSigner(signer.key.getPrivate.toPrivateKey),
signer.certificateChain.getCertificateList
.map(new X509CertificateHolder(_))
)
}
/**
* Creates unsigned OCSP request
* @param ids Certificate IDs
* @return OCSP request
*/
def request(ids: CertificateID*): OCSPReq = {
val builder =
ids.foldLeft(new OCSPReqBuilder())((builder, id) ⇒ builder.addRequest(id))
builder.build()
}
/**
* Basic OCSP response
* @param signer Signer credentials
* @param statuses Certificate statuses list
* @return OCSP response
*/
def response(signer: TLS.CertificateKey, statuses: Status*): BasicOCSPResp = {
val builder = statuses.foldLeft[BasicOCSPRespBuilder](
new JcaBasicOCSPRespBuilder(
signer.key.getPublic.toPublicKey,
digestCalculator
)
) {
case (b, Status(id, status)) ⇒
b.addResponse(id, status)
}
builder.build(
X509Utils.contentSigner(signer.key.getPrivate.toPrivateKey),
signer.certificateChain.getCertificateList
.map(new X509CertificateHolder(_)),
new Date()
)
}
private def loadUrl(url: String, request: OCSPReq): OCSPResp =
concurrent.blocking {
val encoded = Base64.toBase64String(request.getEncoded)
val ocspUrl =
new URL(if (url.endsWith("/")) url + encoded else url + "/" + encoded)
val inputStream = ocspUrl.openStream()
try {
new OCSPResp(inputStream)
} finally {
IOUtils.closeQuietly(inputStream)
}
}
/**
* Ensures that OCSP response signature is valid
* @param r OCSP response
* @param issuer OCSP issuer
* @return Is signature valid
*/
def verify(r: BasicOCSPResp, issuer: TLS.Certificate): Boolean = {
X509Utils.isKeyUsageAllowed(issuer, KeyUsage.cRLSign) &&
r.isSignatureValid(X509Utils.contentVerifierProvider(issuer))
}
/**
* Ensures that OCSP request signature is valid
* @param r OCSP request
* @param issuer OCSP issuer
* @return Is signature valid
*/
def verify(r: OCSPReq, issuer: TLS.Certificate): Boolean = {
X509Utils.isKeyUsageAllowed(issuer, KeyUsage.digitalSignature) &&
r.isSignatureValid(X509Utils.contentVerifierProvider(issuer))
}
/**
* Tries to load OCSP response from URL
* @param ocsp OCSP URL
* @param issuer OCSP issuer
* @param request OCSP request
* @return OCSP response or [[scala.None None]]
*/
def fromUrl(
ocsp: String,
issuer: TLS.Certificate,
request: OCSPReq
): Option[BasicOCSPResp] = {
val response = loadUrl(ocsp, request)
assert(
response.getStatus == OCSPResp.SUCCESSFUL,
s"OCSP error: ${response.getStatus}"
)
response.getResponseObject match {
case r: BasicOCSPResp if verify(r, issuer) ⇒
Some(r)
case _ ⇒
None
}
}
/**
* Checks certificate online status
* @param cert Certificate
* @param issuer Issuer
* @return Certificate OCSP status
*/
def getStatus(
cert: TLS.Certificate,
issuer: TLS.Certificate
): Option[Status] = X509Utils.getOcspUrl(cert).flatMap { ocsp ⇒
val certId = id(issuer, BigInt(cert.getSerialNumber.getValue))
fromUrl(ocsp, issuer, request(certId)).toSeq
.flatMap(_.getResponses)
.find(_.getCertID == certId)
.map(r ⇒ Status(r.getCertID, r.getCertStatus))
}
}