feat(certificate): add date verification (#1299)

This commit is contained in:
vladopajic
2025-03-25 11:50:25 +01:00
committed by GitHub
parent efe453df87
commit 024ec51f66
5 changed files with 101 additions and 8 deletions

View File

@@ -215,6 +215,7 @@ int init_cert_buffer(cert_buffer **buffer, const unsigned char *src_data,
cert_error_t cert_generate(cert_context_t ctx, cert_key_t key,
cert_buffer **out, cert_buffer *signature,
cert_buffer *ident_pubk, const char *cn,
const char *validFrom, const char *validTo,
cert_format_t format) {
X509 *x509 = NULL;
BIO *bio = NULL;
@@ -317,8 +318,8 @@ cert_error_t cert_generate(cert_context_t ctx, cert_key_t key,
ret_code = CERT_ERROR_AS1_TIME_GEN;
goto cleanup;
}
if (!ASN1_TIME_set_string(start_time, "19750101000000Z") ||
!ASN1_TIME_set_string(end_time, "40960101000000Z")) {
if (!ASN1_TIME_set_string(start_time, validFrom) ||
!ASN1_TIME_set_string(end_time, validTo)) {
ret_code = CERT_ERROR_VALIDITY_PERIOD;
goto cleanup;
}

View File

@@ -129,6 +129,8 @@ cert_error_t cert_serialize_pubk(cert_key_t key, cert_buffer **out,
* @param signature buffer that contains a signature
* @param ident_pubk buffer that contains the bytes of an identity pubk
* @param common_name Common name to use for the certificate subject/issuer
* @param validFrom Date from which certificate is issued
* @param validTo Date to which certificate is issued
* @param format Certificate format
*
* @return CERT_SUCCESS on successful execution, an error code otherwise
@@ -136,6 +138,7 @@ cert_error_t cert_serialize_pubk(cert_key_t key, cert_buffer **out,
cert_error_t cert_generate(cert_context_t ctx, cert_key_t key,
cert_buffer **out, cert_buffer *signature,
cert_buffer *ident_pubk, const char *cn,
const char *validFrom, const char *validTo,
cert_format_t format);
/**

View File

@@ -9,6 +9,8 @@
import std/[sequtils, exitprocs]
import strutils
import times
import stew/byteutils
import chronicles
import ../../crypto/crypto
@@ -38,6 +40,8 @@ type
P2pCertificate* = object
extension*: P2pExtension
pubKeyDer: seq[byte]
validFrom: Time
validTo: Time
type EncodingFormat* = enum
DER
@@ -104,6 +108,16 @@ func makeIssuerDN(identityKeyPair: KeyPair): string {.inline.} =
return issuerDN
proc makeASN1Time(time: Time): cstring {.inline.} =
let str =
try:
let f = initTimeFormat("yyyyMMddhhmmss")
format(time.utc(), f)
except TimeFormatParseError:
raiseAssert "time format is const and checked with test"
return (str & "Z").cstring
proc makeExtValues(
identityKeypair: KeyPair, certKey: cert_key_t
): tuple[signature: cert_buffer, pubkey: cert_buffer] {.
@@ -156,7 +170,10 @@ proc makeExtValues(
return (signature.toCertBuffer(), pubKeyBytes.toCertBuffer())
proc generate*(
identityKeyPair: KeyPair, encodingFormat: EncodingFormat = EncodingFormat.DER
identityKeyPair: KeyPair,
validFrom: Time = fromUnix(157813200),
validTo: Time = fromUnix(67090165200),
encodingFormat: EncodingFormat = EncodingFormat.DER,
): tuple[raw: seq[byte], privateKey: seq[byte]] {.
raises: [
KeyGenerationError, IdentitySigningError, IdentityPubKeySerializationError,
@@ -189,11 +206,14 @@ proc generate*(
let issuerDN = makeIssuerDN(identityKeyPair)
let libp2pExtension = makeExtValues(identityKeyPair, certKey)
let validFromAsn1 = makeASN1Time(validFrom)
let validToAsn1 = makeASN1Time(validTo)
var certificate: ptr cert_buffer = nil
ret = cert_generate(
cert_ctx, certKey, certificate.addr, libp2pExtension.signature.unsafeAddr,
libp2pExtension.pubkey.unsafeAddr, issuerDN.cstring, encodingFormat.cert_format_t,
libp2pExtension.pubkey.unsafeAddr, issuerDN.cstring, validFromAsn1, validToAsn1,
encodingFormat.cert_format_t,
)
if ret != CERT_SUCCESS:
raise
@@ -213,6 +233,15 @@ proc generate*(
# Return the Serialized Certificate and Private Key
return (outputCertificate, outputPrivateKey)
proc parseCertTime*(certTime: string): Time {.raises: [TimeParseError].} =
var timeNoZone = certTime[0 ..^ 5] # removes GMT part
# days with 1 digit have additional space -> strip it
timeNoZone = timeNoZone.replace(" ", " ")
const certTimeFormat = "MMM d hh:mm:ss yyyy"
const f = initTimeFormat(certTimeFormat)
return parse(timeNoZone, f, utc()).toTime()
proc parse*(
certificateDer: seq[byte]
): P2pCertificate {.raises: [CertificateParsingError].} =
@@ -239,11 +268,22 @@ proc parse*(
CertificateParsingError, "Failed to parse certificate, error code: " & $ret
)
var validFrom, validTo: Time
try:
validFrom = parseCertTime($certParsed.valid_from)
validTo = parseCertTime($certParsed.valid_to)
except TimeParseError as e:
raise newException(
CertificateParsingError, "Failed to parse certificate validity time, " & $e.msg
)
P2pCertificate(
extension: P2pExtension(
signature: certParsed.signature.toSeq(), publicKey: certParsed.ident_pubk.toSeq()
),
pubKeyDer: certParsed.cert_pbuk.toSeq(),
validFrom: validFrom,
validTo: validTo,
)
proc verify*(self: P2pCertificate): bool =
@@ -255,11 +295,12 @@ proc verify*(self: P2pCertificate): bool =
## Returns:
## `true` if certificate is valid.
let currentTime = now().utc().toTime()
if not (currentTime >= self.validFrom and currentTime < self.validTo):
return false
var sig: Signature
var key: PublicKey
# TODO: validate dates
if sig.init(self.extension.signature) and key.init(self.extension.publicKey):
let msg = makeSignatureMessage(self.pubKeyDer)
return sig.verify(msg, key)

View File

@@ -57,6 +57,8 @@ proc cert_generate*(
signature: ptr cert_buffer,
ident_pubk: ptr cert_buffer,
cn: cstring,
validFrom: cstring,
validTo: cstring,
format: cert_format_t,
): cert_error_t {.cdecl, importc: "cert_generate".}

View File

@@ -1,5 +1,6 @@
import unittest2
import times
import ../../../libp2p/transports/tls/certificate
import ../../../libp2p/crypto/crypto
import ../../../libp2p/peerid
@@ -12,13 +13,38 @@ suite "Certificate roundtrip tests":
let keypair = KeyPair.random(scheme, rng[]).tryGet()
let peerId = PeerId.init(keypair.pubkey).tryGet()
let certTuple = generate(keypair, EncodingFormat.DER)
let certTuple = generate(keypair)
let cert = parse(certTuple.raw)
check peerId == cert.peerId()
check cert.publicKey().scheme == scheme
check cert.verify()
test "gnerate with invalid validity time":
var rng = newRng()
let keypair = KeyPair.random(Ed25519, rng[]).tryGet()
# past
var validFrom = (now() - 3.days).toTime()
var validTo = (now() - 1.days).toTime()
var certTuple = generate(keypair, validFrom, validTo)
var cert = parse(certTuple.raw)
check not cert.verify()
# future
validFrom = (now() + 1.days).toTime()
validTo = (now() + 3.days).toTime()
certTuple = generate(keypair, validFrom, validTo)
cert = parse(certTuple.raw)
check not cert.verify()
# twisted from-to
validFrom = (now() + 3.days).toTime()
validTo = (now() - 3.days).toTime()
certTuple = generate(keypair, validFrom, validTo)
cert = parse(certTuple.raw)
check not cert.verify()
## Test vectors are equivalents to https://github.com/libp2p/specs/blob/master/tls/tls.md#test-vectors.
## Since certificates in those don't have Issuer and Subject, they are empty,
## they are not successfully parsed by parse(...) because those rules are enforced by Mbed TLS.
@@ -70,3 +96,23 @@ suite "Test vectors":
# should not verify
check not cert.verify()
test "Expired certificate":
let certBytesHex =
"30820214308201BBA003020102021412A974B8DE545B54729BF8393EFFFB00AEF69FB5300A06082A8648CE3D04030230423140303E06035504030C37434E3D313244334B6F6F574A7A564657566869746861656A395172374E6A4A4642626A44447942475351737146317361734342635841483022180F32303234313232343038303030345A180F32303235303232343038303030345A30423140303E06035504030C37434E3D313244334B6F6F574A7A564657566869746861656A395172374E6A4A4642626A44447942475351737146317361734342635841483059301306072A8648CE3D020106082A8648CE3D03010703420004C1DB5D2F5D4697386B723993D5499DB50E80E3F970135381B25FDBCA0660797F79FCE818EDDEFF6D27F56C6505F3F3F439E5D78F355293A5215718FA91DA0263A3818A3081873078060A2B0601040183A25A0101046A30680424080112208850FF8CF0E751088411ECF49A34DDBB50EEE0584F1B8CA6F9BBC93752FD6D4A04401ACF48FCFC7CA932C316E21DC986B366531D158E1499194D8601AE9FEF65D6E41198D4FC14B5AEA6BC67B06D28AFA13B759477048FF887CC26C0F197FB227B0F300B0603551D0F0101FF0401A0300A06082A8648CE3D040302034700304402206B1C01813E0A3CF0777D564A8090386B324660E703F6120E7C387DF23F94323B0220206E6BCC1213EF2A15E01B16F30DF45653B84AE6CEA87271A9301E055DB65FCB"
let cert = parse(fromHex(certBytesHex))
# should have valid key
check $cert.peerId() == "12D3KooWJzVFWVhithaej9Qr7NjJFBbjDDyBGSQsqF1sasCBcXAH"
check cert.publicKey().scheme == PKScheme.Ed25519
# should not verify
check not cert.verify()
suite "utilities test":
test "parseCertTime":
var dt = parseCertTime("Mar 19 11:54:31 2025 GMT")
check 1742385271 == dt.toUnix()
dt = parseCertTime("Jan 1 00:00:00 1975 GMT")
check 157766400 == dt.toUnix()