mirror of
https://github.com/vacp2p/nim-libp2p.git
synced 2026-01-09 14:28:11 -05:00
feat(certificate): add date verification (#1299)
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
/**
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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".}
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user