All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.karasiq.tls.x509.ocsp.OCSP.scala Maven / Gradle / Ivy

The newest version!
package com.karasiq.tls.x509.ocsp

import java.net.URL
import java.security.SecureRandom
import java.time.Instant
import java.util.Date

import com.karasiq.tls.TLS
import com.karasiq.tls.internal.BCConversions._
import com.karasiq.tls.internal.TLSUtils
import com.karasiq.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))
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy