github a-sit-plus/signum PKIX-PREVIEW
PKIX Preview

pre-release18 hours ago

3.20.0-PREVIEW / 0.20.0-PREVIEW

This is a preview release to gather feedback for the newly introduced PKIX cert path validation API. It introduces some breaking changes, so please refer to the full changelog below!

Using it in your projects

Add the following maven repo: https://raw.githubusercontent.com/a-sit-plus/signum/refs/heads/mvn/repo and use versions 3.20.0-PREVIEW/ 0.20.0-PREVIEW respectively.

This gets you the following:

X.509 Certificate validation

Provides a flexible framework for X.509 certificate chain validation, supporting both:

  • RFC 5280-compliant validation
  • Custom validator pipelines for application-specific needs.
  • No revocation checks
  • Only linear certificate chains, no complex graphs yet

Remember: this is a preview, o use with care!

Validation never throws; instead it returns a structured CertificateValidationResult describing all encountered issues.

The validation system is built around the CertificateValidator interface. Each validator:

  • Receives a certificate being processed
  • Removes supported critical extensions
  • Throws an exception when it cannot validate (caught and recorded by the framework)

Unsupported / Not Yet Implemented:

  • Revocation checking: OCSP and CRL checks are not yet supported
  • Partial cross-validation support. Our validation logic assumes a linear certificate chain and does not perform graph-based path discovery, meaning it cannot search for or create alternative certification paths.:
    • Multiple trust anchors can be supplied and checked (implemented)
    • Validation succeeds if any certificate in the chain is issued by a trust anchor (implemented)
    • If the trust anchor matches an intermediate certificate, the TA’s public key is verified against the certificate before it (implemented)
    • Full cross-validation (exploring alternative chains through intermediates) (not implemented)

Use CertificateValidationContext to configure RFC 5280 validation:

val context = CertificateValidationContext(
  date = Clock.System.now(),
  explicitPolicyRequired = false,
  policyMappingInhibited = false,
  anyPolicyInhibited = false,
  policyQualifiersRejected = false,
  initialPolicies = emptySet(),
  trustAnchors = setOf(TrustAnchor.Certificate(myRootCert)),
  expectedEku = setOf(KnownOIDs.id_kp_serverAuth)
)

This lets you specify:

  • Validation time
  • Whether explicit policy is required
  • Whether anyPolicy or policy mapping is allowed
  • policyQualifiersRejected flag, which indicates should certificates that include policy qualifiers in a certificate policies extension that is marked critical be rejected
  • Expected EKUs
  • Which trust anchors are acceptable

Trust anchors

A TrustAnchor represents the starting point of trust for certificate path validation.
It defines the cryptographic identity against which the certificate chain is ultimately verified.

A trust anchor can be represented in two ways:

  • Certificate (full X.509 certificate)
  • PublicKey (bare public key with an optional subject name and name constraints)
  1. Certificate based Trust anchor:
  • TrustAnchor.Certificate(cert: X509Certificate)
    This is the standard RFC 5280 trust model:
  • Trust is a self-signed or intermediate CA certificate.
  • The certificate’s subject name becomes the trust anchor’s principal.
  • Name constraints (if present) propagate into path validation.
  • The public key is extracted from the certificate.
  1. Public Key based Trust anchor:
  • TrustAnchor.PublicKey(publicKey: CryptoPublicKey, principal: X500Name?, nameConstraints: NameConstraintsExtension? = null)

  • There is also a convenience constructor (An unnamed trust anchor has no distinguishing subject name. Use only when a raw key truly makes sense):

@HazardousMaterials
constructor(publicKey: CryptoPublicKey)

RFC 5280 validation:

val root: X509Certificate = TODO("Trusted root")
val intermediate: X509Certificate = TODO()
val leaf: X509Certificate = TODO("Certificate to validate")

val context = CertificateValidationContext(
  trustAnchors = setOf(TrustAnchor.Certificate(root)),
)
// Build chain leaf → intermediates
val chain: CertificateChain = listOf(leaf, intermediate, root)

val result = chain.validate(context)

println("Chain valid? ${result.isValid}")

if (!result.isValid) {
  println("Failures:")
  result.validatorFailures.forEach {
    println(" - [${it.validatorName}] ${it.errorMessage}")
  }
}

If no trust anchors are explicitly specified, the validation will fall back to using the system trust store.
If allowIncludedTrustAnchor is set to true in the validation context, validation will try to match root with one of the trust anchors and the if match is found root will be omitted from the chain during the validation.

Custom validation:

val intermediate: X509Certificate = TODO()
val leaf: X509Certificate = TODO("Certificate to validate")

val chain: CertificateChain = listOf(leaf, intermediate)

// It is optional, falls back to the default if missing
val context = CertificateValidationContext(
  date = Clock.System.now()
)

// TODO: Replace with your own custom validator factory
val myValidatorFactory = ValidatorFactory { context -> 
    // Here you can define your own validators for this chain 
    // e.g. use rfc5280 factory to create mutable list and remove unnecessary  validators  
  val validators = ValidatorFactory.RFC5280.run { chain.generate(context) }
  validators.removeAll { it is CertValidityValidator || it is KeyIdentifierValidator || it is TimeValidityValidator }
  validators
}

val result = chain.validate(
  validatorFactory = myValidatorFactory,
  context = context
)

println("Chain valid? ${result.isValid}")

Changelog

  • Introduce full X.509 certificate validation support
    • RFC Compliance:
      • Implements RFC 5280 path validation rules, including policy processing, name constraints, key usage, and basic constraints
    • Unsupported / Not Yet Implemented:
      • Revocation checking: OCSP and CRL checks are not yet supported
      • Partial cross-validation support:
        • Multiple trust anchors can be supplied and checked (implemented)
        • Validation succeeds if any certificate in the chain is issued by a trust anchor (implemented)
        • If the trust anchor matches an intermediate certificate, the TA’s public key is verified against the certificate before it (implemented)
        • Full cross-validation (exploring alternative chains through intermediates) (not implemented)
    • Added core CertificateChainValidator coordinating the full validation pipeline
      • validate() method returns CertificateValidationResult which contains root policy node, leaf certificate and list of ValidatorFailure
      • Validation fails softly, by returning ValidatorFailure for every exception thrown during validation
    • Modular validator design with pluggable components:
      • PolicyValidator – enforces certificate policies and policy constraints
      • BasicConstraintsValidator – validates basicConstraints
      • NameConstraintsValidator – enforces permitted/excluded name constraints across the chain
      • ChainValidator - validates signatures and name chaining
      • KeyUsageValidator - validates KeyUsage extensions
      • TimeValidityValidator - checks certificate time validity and that each certificate was issued within the validity period of its issuer
      • TrustAnchorValidator - checks if any certificate from the chain is trusted
      • KeyIdentifierValidator - validates Subject and Authority key identifiers
      • CertValidityValidator - checks whether the certificate is constructed correctly, since some components are decoded too leniently
  • Make more provider functions suspending
    • digest calculation
    • MAC calculation
    • private key export
    • signature verification
    • ephemeral key creation
    • signer creation
    • verifier creation
    • hash to curve
  • Introduced dedicated X509 extension classes:
    • X509CertificateExtension is now a base class
    • Enables polymorphic decoding/encoding of extension types
      • BasicConstraintsExtension
      • CertificatePoliciesExtension
      • InhibitAnyPolicyExtension
      • KeyUsageExtension
      • NameConstraintsExtension and GeneralSubtree
      • PolicyConstraintsExtension
      • PolicyMappingsExtension
      • AuthorityKeyIdentifierExtension
      • ExtendedKeyUsageExtension
      • SubjectKeyIdentifier
    • If decoding a dedicated extension fails, an InvalidCertificateExtension is returned, containing the original extension’s properties and the cause of the decoding failure.
  • Refactored AlternativeNames for SAN/IAN extraction
    • Removed detailed parsing of individual name types; now delegates decoding to GeneralName
    • Introduced dedicated GeneralName classes:
      • DNSName
      • EDIPartyName
      • IPAddressName
      • OtherName
      • RegisteredIDName
      • RFC822Name
      • UriName
      • X400AddressName
      • X500Name
  • Add constrains() method in GeneralNameOption interface to perform constraint checking between General Names, this method is intended for use in NameConstraints check during certificate chain validation
  • Add full RFC 2253 support for Distinguished Names (X500Name, RDN, AttributeTypeAndValue):
    • X500Name.fromString() / .toRfc2253String() - parse and serialize complete DNs with escaping and normalization
    • RelativeDistinguishedName.fromString() - parse multi-attribute RDNs
    • AttributeTypeAndValue.fromString() / .toRfc2253String - handle known attributes and canonicalize values
  • Fix a glaring JWS bug that caused an error whenever trying to get the digest of a JWS signature algorithm
  • Add Enumerable and Enumeration interfaces to support the pattern in sealed types where the companion object provides entries containing all possible instances
    • Classes and interfaces refactored to use Set as entries to follow the standardized pattern:
      • Asn1Element.Tag
      • CoseAlgorithm and its nested implementations
      • JsonWebAlgorithm
      • JwsAlgorithm and its nested implementations
      • DataIntegrityAlgorithm
      • MessageAuthenticationCode
      • SignatureAlgorithm
      • RSAPadding
      • SymmetricEncryptionAlgorithm and its nested implementations

Don't miss a new signum release

NewReleases is sending notifications on new releases.