Skip to content

Releases: a-sit-plus/signum

3.21.0 / Supreme 0.13.0

14 Apr 04:31

Choose a tag to compare

  • Drop Apple X64 targets
  • Move InstantLongSerializer from indispensable-josef/indispensable-cosef to indispensable
  • More lenient boolean decoding
  • Preserve cursed CSR and cert encodings

3.20.1 / Supreme 0.12.1

10 Apr 18:07

Choose a tag to compare

3.20.0 / Supreme 0.12.0

24 Mar 19:57

Choose a tag to compare

  • Fix ASN.1 SET ordering and Tag comparator
  • Add AES.ECB_NOPADDING
  • Fatal crash if decline FaceID permission and cancel Passcode input
  • Fix ASN.1 REAL parsing causing a crash on WASM
  • fix build setup on non-macOS hosts
  • Dependency Updates:
    • Kotlin 2.3.20
    • Kotlinx serialization 1.10.0
    • Kotlinx IO 0.9.0

PKIX Preview

18 Dec 16:21
ab67268

Choose a tag to compare

PKIX Preview Pre-release
Pre-release

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 err...
Read more

3.19.1 / Supreme 0.11.1

13 Dec 18:37

Choose a tag to compare

  • Bouncy Castle 1.83 (non-forced version)

3.19.0 / Supreme 0.11.0

10 Dec 17:27

Choose a tag to compare

  • 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
  • Fix project setup on non-macOS build hosts
  • COSE: Add identifiers -9, -51, -53 for fully-specified algorithms for ECDSA, see RFC 9864
  • Fix Android project setup
  • Limit Keystore operations with limitedParallelism from kotlinx.coroutines

3.18.2 / Supreme 0.10.2

29 Oct 18:14

Choose a tag to compare

  • Fixes
    • Fix memory management on iOS
  • Dependency Updates:
    • Kotlin 2.2.21
    • Kotlinx.io 0.8.0
    • crypto-rand 0.6.0
  • Build Setup:
    • Remove Swift-Klib and massively cleanup Swift Interop

3.18.1 / Supreme 0.10.1

26 Oct 20:53

Choose a tag to compare

Fix Android target pulling in JDK-21-specifics (KT-71375)

3.18.0 / Supreme 0.10.0

22 Oct 21:45

Choose a tag to compare

  • Fixes:
    • Fix memleak on iOS targets
    • Add stricter length checks to be more resilient towards adversarial inputs
    • Fix RSA signing on Android
    • Correct serialization logic for symmetric CoseKey in CoseKeySerializer
    • Fix prettyPrint for structures
  • Asn1String revamp
    • Deprecated Asn1Primitive.asAsn1String(), moved decoding logic into Asn1String.doDecode()
    • Added specific decodeTo functions in Asn1Primitive for every string type that follow usual pattern for decoding primitive, when dealing with implicit tags caller should call function for decoding specific string type. If we do something like 'Asn1String.decodeFromTlv()` we expect explicit tag.
    • Added Asn1String.rawValue property for storing raw ByteArray of the string, used in checks for several charsets and good for reencoding Asn1String, it will be encoded as it was before decoding
  • Add COSE_Mac0 support with CoseMac class
  • Introduce ProtectedCoseHeaderSerializer for serialization/deserialization protected header
  • Replace raw-byte protected header and its ByteArray extension serialization with CoseHeader using the new serializer
  • Allow overriding randomness source for symmetric key generation
    • requires HazMat opt in
    • provides only secure random (insecure/test/faux RNGs need to be implemented by consumers)
  • Extend properties in JweHeader
  • Strictly enforce RFC3394 key wrapping on Android
  • Build Setup:
    • Kotlin 2.2.20
    • TestBalloon-powered tests
    • New KMP-AGP Plugin
    • Raise Android minSDK to 26 (Android 8.0 Oreo)
    • Make it possible to disable all apple targets by setting Gradle property disableAppleTargets=true (either through gradle.properties/local.properties or as env variable)
    • Auto-setup unsupported JVM toolchains

3.17.0 (Supreme 0.9.0)

22 Jul 22:30

Choose a tag to compare

  • KDF Support
    • PBKDF2
    • HKDF
    • scrypt
  • RSA encryption using in-memory keys (no hardware-backed key management yet)
  • X.509 Revamp
    • Introduce X509SignatureAlgorithmDescription, which is the OID + params pair that identifies a X509SignatureAlgorithm
      • Instances of X509SignatureAlgorithm represent algorithms that are known to Signum
      • Test .isSupported() or .requireSupported() (with contract smart-cast support)
    • X509Certificate and Pkcs10CertificationRequest now use X509SignatureAlgorithmDescription to represent a non-validated signature algorithm
    • Refactor X509Certificate and TbsCertificate to store the raw signature as Asn1Primitive and the raw public key as Asn1Sequence enabling support for certificates with unsupported signature algorithms
      • Use the new KmmResult-returning decodedSignature and decodedPublicKey members to replace publicKey and signature, respectively.
      • The old publicKey and signature are being deprecated.
    • Refactor Pkcs10CertificationRequest to store the raw signature as Asn1Primitive enabling unsupported signature algorithms
      • Use the new KmmResult-returning decodedSignature and decodedPublicKey, respectively.
  • Add structured iterator-based decoding of Asn1Structure. Asn1Structure now implements Iterable<Asn1Element>:
    • Deprecate child accessors in Asn1Structure with deprecation level ERROR:
      • nextChild()
      • nextChildOrNull()
      • hasMoreChildren()
      • peek()
    • Add inner Iterator for child accesses
      • Add Iterator.reversed() method for getting a new iterator from an existing one, but with reversed direction, keeping the current index
      • Add Asn1Structure.reverseIterator() to get a reversed iterator right away, to iterate over all child elements in reverse.
    • Add decodeAs() for decoding ASN.1 structures via iterator-based lambda, moved trailing data check from decodeFromTlv() to decodeAs()
    • Refactor doDecode() implementations in Asn1Structure subclasses to use the new decodeAs() iterator-based API instead of deprecated child access methods.
  • Add SpecializedSymmetricEncryptionAlgorithm
    • This allows randomKey() etc to operate on COSE/JWE algorithms
  • Move constants of KnownOIDs into a discrete module indispensable-oids as extensions on the KnownOIDs object
    • → update your imports!
  • ASN.1 polishing:
    • rename Asn1Element.length property to Asn1Element.contentLength (and add a delegate with the old name and deprecation annotation to the new property)
    • Add missing Asn1.Real shorthand to the ASN.1 builder
    • Add Asn1Null constant
    • Add human-readable ASN.1 element prettyPrint() method
    • Make Asn1OctetString interface sealed
  • Strippable KnownOIDs
    • Move KnownOIDs into a discrete module indispensable-oids
  • OID descriptions:
    • KnownOIDs now implements MutableMap<ObjectIdentifier, String> to store and look up descriptions of Object Identifiers
    • OIDs can hence be described using KnownOIDs[theExpressionistsOid] = "Edvard Munch"
    • OID descriptions are exposed in accordance with the map interface: KnownOIDs[theExpressionistsOid] will yield "Edvard Munch" if this description was added prior.
    • All OIDs present in KnownOIDs shipped with the indispensable-oids module come with a description. To actually add them to all known descriptions, call KnownOIDs.describeAll() once.
  • Deprecate serialize() and deserialize() methods in COSE+ JOSE data classes
  • Clean up some function signatures:
    • SymmetricKey.toJsonWebKey now returns KmmResult
    • SymmetricEncryptionAlgorithm.toJweKwAlgorithm now returns KmmResult
    • SymmetricEncryptionAlgorithm.toJweEncryptionAlgorithm removed
  • In JwsHeader add property vcTypeMetadata with key vctm, see SD-JWT VC
  • Dependency Updates:
    • Kotlin 2.2.0
    • AGP 8.10.0
    • kotlincrypto:secure-random:0.3.2 -> kotlincrypto.random:crypto-rand:0.5.0
      • This fixes key generation in WASM/JS
    • kotlinx.io 0.7.0
    • Update to kotlinx.datetime 0.7.1.
      • This moves Instant and Clock to stdlib
      • (but introduces typealiases for easier migration)
      • Also forces serialization 1.9.0
    • Update to latest conventions plugin:
      • Bouncy Castle 1.81!!
      • Serialization 1.9.0
      • Coroutines 1.10.2
      • Ktor 3.2.2
      • Kotest 6.0.0.M5