main.com.appmattus.certificatetransparency.internal.verifier.LogSignatureVerifier.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of certificatetransparency Show documentation
Show all versions of certificatetransparency Show documentation
Certificate transparency for Android and Java
The newest version!
/*
* Copyright 2021-2023 Appmattus Limited
* Copyright 2019 Babylon Partners Limited
*
* 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.
*
* Code derived from https://github.com/google/certificate-transparency-java
*
* File modified by Appmattus Limited
* See: https://github.com/appmattus/certificatetransparency/compare/e3d469df9be35bcbf0f564d32ca74af4e5ca4ae5...main
*/
package com.appmattus.certificatetransparency.internal.verifier
import com.appmattus.certificatetransparency.SctVerificationResult
import com.appmattus.certificatetransparency.internal.serialization.CTConstants
import com.appmattus.certificatetransparency.internal.serialization.CTConstants.LOG_ENTRY_TYPE_LENGTH
import com.appmattus.certificatetransparency.internal.serialization.CTConstants.MAX_CERTIFICATE_LENGTH
import com.appmattus.certificatetransparency.internal.serialization.CTConstants.MAX_EXTENSIONS_LENGTH
import com.appmattus.certificatetransparency.internal.serialization.CTConstants.TIMESTAMP_LENGTH
import com.appmattus.certificatetransparency.internal.serialization.CTConstants.VERSION_LENGTH
import com.appmattus.certificatetransparency.internal.serialization.writeUint
import com.appmattus.certificatetransparency.internal.serialization.writeVariableLength
import com.appmattus.certificatetransparency.internal.utils.Base64
import com.appmattus.certificatetransparency.internal.utils.asn1.x509.Certificate
import com.appmattus.certificatetransparency.internal.utils.asn1.x509.Extension
import com.appmattus.certificatetransparency.internal.utils.asn1.x509.Extensions
import com.appmattus.certificatetransparency.internal.utils.asn1.x509.TbsCertificate
import com.appmattus.certificatetransparency.internal.utils.hasEmbeddedSct
import com.appmattus.certificatetransparency.internal.utils.isPreCertificate
import com.appmattus.certificatetransparency.internal.utils.isPreCertificateSigningCert
import com.appmattus.certificatetransparency.internal.utils.issuerInformation
import com.appmattus.certificatetransparency.internal.utils.issuerInformationFromPreCertificate
import com.appmattus.certificatetransparency.internal.verifier.model.IssuerInformation
import com.appmattus.certificatetransparency.internal.verifier.model.SignedCertificateTimestamp
import com.appmattus.certificatetransparency.internal.verifier.model.Version
import com.appmattus.certificatetransparency.loglist.LogServer
import java.io.ByteArrayOutputStream
import java.io.IOException
import java.io.OutputStream
import java.security.InvalidKeyException
import java.security.NoSuchAlgorithmException
import java.security.Signature
import java.security.SignatureException
import java.security.cert.CertificateEncodingException
import java.security.cert.CertificateException
import java.security.cert.CertificateParsingException
import java.security.cert.X509Certificate
import java.time.Instant
/**
* Verifies signatures from a given CT Log.
*
* @constructor Creates a new LogSignatureVerifier which is associated with a single log.
* @property logServer information of the log this verifier is to be associated with.
*/
internal class LogSignatureVerifier(private val logServer: LogServer) : SignatureVerifier {
@Suppress("ReturnCount", "ComplexMethod")
override fun verifySignature(sct: SignedCertificateTimestamp, chain: List): SctVerificationResult {
// If the timestamp is in the future then we have to reject it
val now = Instant.now()
if (sct.timestamp > now) {
return SctVerificationResult.Invalid.FutureTimestamp(sct.timestamp, now)
}
if (logServer.validUntil != null && sct.timestamp > logServer.validUntil) {
return SctVerificationResult.Invalid.LogServerUntrusted(sct.timestamp, logServer.validUntil)
}
if (!logServer.id.contentEquals(sct.id.keyId)) {
return LogIdMismatch(Base64.toBase64String(sct.id.keyId), Base64.toBase64String(logServer.id))
}
val leafCert = chain[0]
if (!leafCert.isPreCertificate() && !leafCert.hasEmbeddedSct()) {
// When verifying final cert without embedded SCTs, we don't need the issuer but can verify directly
return try {
val toVerify = serializeSignedSctData(leafCert, sct)
verifySctSignatureOverBytes(sct, toVerify)
} catch (e: IOException) {
CertificateEncodingFailed(e)
} catch (e: CertificateEncodingException) {
CertificateEncodingFailed(e)
}
}
if (chain.size < 2) {
return NoIssuer
}
// PreCertificate or final certificate with embedded SCTs, we want the issuerInformation
val issuerCert = chain[1]
val isPreCertificateSigningCert = try {
issuerCert.isPreCertificateSigningCert()
} catch (e: CertificateParsingException) {
return CertificateParsingFailed(e)
}
val issuerInformation = if (!isPreCertificateSigningCert) {
// If signed by the real issuing CA
try {
issuerCert.issuerInformation()
} catch (e: NoSuchAlgorithmException) {
return UnsupportedSignatureAlgorithm("SHA-256", e)
}
} else {
// Must have at least 3 certificates when a pre-certificate is involved
@Suppress("MagicNumber")
if (chain.size < 3) {
return NoIssuerWithPreCert
}
try {
issuerCert.issuerInformationFromPreCertificate(chain[2])
} catch (e: CertificateEncodingException) {
return CertificateEncodingFailed(e)
} catch (e: NoSuchAlgorithmException) {
return UnsupportedSignatureAlgorithm("SHA-256", e)
} catch (e: IOException) {
return ASN1ParsingFailed(e)
}
}
return verifySCTOverPreCertificate(sct, leafCert, issuerInformation)
}
/**
* Verifies the CT Log's signature over the SCT and the PreCertificate, or a final certificate.
*
* @property sct SignedCertificateTimestamp received from the log.
* @property certificate the PreCertificate sent to the log for addition, or the final certificate
* with the embedded SCTs.
* @property issuerInfo Information on the issuer which will (or did) ultimately sign this
* PreCertificate. If the PreCertificate was signed using by a PreCertificate Signing Cert,
* the issuerInfo contains data on the final CA certificate used for signing.
* @return true if the SCT verifies, false otherwise.
*/
internal fun verifySCTOverPreCertificate(
sct: SignedCertificateTimestamp,
certificate: X509Certificate,
issuerInfo: IssuerInformation
): SctVerificationResult {
return try {
val preCertificateTBS = createTbsForVerification(certificate, issuerInfo)
val toVerify = serializeSignedSctDataForPreCertificate(
preCertificateTBS.bytes.toList().toByteArray(),
issuerInfo.keyHash,
sct
)
verifySctSignatureOverBytes(sct, toVerify)
} catch (e: IOException) {
CertificateEncodingFailed(e)
} catch (e: CertificateException) {
CertificateEncodingFailed(e)
}
}
/**
* @throws CertificateException Certificate error
* @throws IOException Error deleting extension
*/
private fun createTbsForVerification(preCertificate: X509Certificate, issuerInformation: IssuerInformation): TbsCertificate {
@Suppress("MagicNumber")
require(preCertificate.version >= 3)
// We have to use our own parsing code because Java's X509 certificate
// parsing discards the order of the extensions. The signature from SCT we're verifying
// is over the TBSCertificate in its original form, including the order of the extensions.
// Get the list of extensions, in its original order, minus the poison extension.
val parsedPreCertificate = Certificate.create(preCertificate.encoded)
// Make sure that we have the X509AuthorityKeyIdentifier of the real issuer if:
// The PreCertificate has this extension, AND:
// The PreCertificate was signed by a PreCertificate signing cert.
if (parsedPreCertificate.hasX509AuthorityKeyIdentifier() && issuerInformation.issuedByPreCertificateSigningCert) {
require(issuerInformation.x509authorityKeyIdentifier != null)
}
val orderedExtensions = getExtensionsWithoutPoisonAndSct(
parsedPreCertificate.tbsCertificate.extensions!!,
issuerInformation.x509authorityKeyIdentifier
)
val tbsPart = parsedPreCertificate.tbsCertificate
return tbsPart.copy(
issuer = issuerInformation.name ?: tbsPart.issuer,
extensions = Extensions.create(orderedExtensions)
)
}
private fun getExtensionsWithoutPoisonAndSct(
extensions: Extensions,
replacementX509authorityKeyIdentifier: Extension?
): List {
// Order is important, which is why a list is used.
return extensions.values
.mapNotNull {
when (it.objectIdentifier) {
// Do nothing - skip copying this extension
CTConstants.POISON_EXTENSION_OID, CTConstants.SCT_CERTIFICATE_OID -> null
// Use the real issuer's authority key identifier, since it's present.
X509_AUTHORITY_KEY_IDENTIFIER -> replacementX509authorityKeyIdentifier ?: it
else -> it
}
}
}
@Suppress("ComplexMethod")
private fun verifySctSignatureOverBytes(sct: SignedCertificateTimestamp, toVerify: ByteArray): SctVerificationResult {
val sigAlg = when (logServer.key.algorithm) {
"EC" -> "SHA256withECDSA"
"RSA" -> "SHA256withRSA"
else -> return UnsupportedSignatureAlgorithm(logServer.key.algorithm)
}
return try {
val result = Signature.getInstance(sigAlg).apply {
initVerify(logServer.key)
update(toVerify)
}.verify(sct.signature.signature)
val operator = logServer.operatorAt(sct.timestamp)
if (result) SctVerificationResult.Valid(sct, operator) else SctVerificationResult.Invalid.FailedVerification
} catch (e: SignatureException) {
SignatureNotValid(e)
} catch (e: InvalidKeyException) {
LogPublicKeyNotValid(e)
} catch (e: NoSuchAlgorithmException) {
UnsupportedSignatureAlgorithm(sigAlg, e)
}
}
private fun Certificate.hasX509AuthorityKeyIdentifier(): Boolean {
return tbsCertificate.extensions?.values?.any { it.objectIdentifier == X509_AUTHORITY_KEY_IDENTIFIER } ?: false
}
/**
* @throws IOException
* @throws CertificateEncodingException
*/
private fun serializeSignedSctData(certificate: X509Certificate, sct: SignedCertificateTimestamp): ByteArray {
return ByteArrayOutputStream().use {
it.serializeCommonSctFields(sct)
it.writeUint(X509_ENTRY, LOG_ENTRY_TYPE_LENGTH)
it.writeVariableLength(certificate.encoded, MAX_CERTIFICATE_LENGTH)
it.writeVariableLength(sct.extensions, MAX_EXTENSIONS_LENGTH)
it.toByteArray()
}
}
/**
* @throws IOException
*/
private fun serializeSignedSctDataForPreCertificate(
preCertBytes: ByteArray,
issuerKeyHash: ByteArray,
sct: SignedCertificateTimestamp
): ByteArray {
return ByteArrayOutputStream().use {
it.serializeCommonSctFields(sct)
it.writeUint(PRECERT_ENTRY, LOG_ENTRY_TYPE_LENGTH)
it.write(issuerKeyHash)
it.writeVariableLength(preCertBytes, MAX_CERTIFICATE_LENGTH)
it.writeVariableLength(sct.extensions, MAX_EXTENSIONS_LENGTH)
it.toByteArray()
}
}
/**
* @throws IOException
*/
private fun OutputStream.serializeCommonSctFields(sct: SignedCertificateTimestamp) {
require(sct.sctVersion == Version.V1) { "Can only serialize SCT v1 for now." }
writeUint(sct.sctVersion.number.toLong(), VERSION_LENGTH) // ct::V1
writeUint(0, 1) // ct::CERTIFICATE_TIMESTAMP
writeUint(sct.timestamp.toEpochMilli(), TIMESTAMP_LENGTH) // Timestamp
}
companion object {
private const val X509_AUTHORITY_KEY_IDENTIFIER = "2.5.29.35"
private const val X509_ENTRY = 0L
private const val PRECERT_ENTRY = 1L
}
}