Releases: a-sit-plus/signum
3.21.0 / Supreme 0.13.0
- 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
- Kotlinx serialization 1.11.0 fixing CBOR number decoding
3.20.0 / Supreme 0.12.0
- 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
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
policyQualifiersRejectedflag, 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)
- 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.
- 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
CertificateChainValidatorcoordinating the full validation pipelinevalidate()method returnsCertificateValidationResultwhich contains root policy node, leaf certificate and list ofValidatorFailure- Validation fails softly, by returning
ValidatorFailurefor every exception thrown during validation
- Modular validator design with pluggable components:
PolicyValidator– enforces certificate policies and policy constraintsBasicConstraintsValidator– validates basicConstraintsNameConstraintsValidator– enforces permitted/excluded name constraints across the chainChainValidator- validates signatures and name chainingKeyUsageValidator- validates KeyUsage extensionsTimeValidityValidator- checks certificate time validity and that each certificate was issued within the validity period of its issuerTrustAnchorValidator- checks if any certificate from the chain is trustedKeyIdentifierValidator- validates Subject and Authority key identifiersCertValidityValidator- checks whether the certificate is constructed correctly, since some components are decoded too leniently
- RFC Compliance:
- 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:
X509CertificateExtensionis now a base class- Enables polymorphic decoding/encoding of extension types
BasicConstraintsExtensionCertificatePoliciesExtensionInhibitAnyPolicyExtensionKeyUsageExtensionNameConstraintsExtensionandGeneralSubtreePolicyConstraintsExtensionPolicyMappingsExtensionAuthorityKeyIdentifierExtensionExtendedKeyUsageExtensionSubjectKeyIdentifier
- If decoding a dedicated extension fails, an
InvalidCertificateExtensionis returned, containing the original extension’s properties and the cause of the decoding failure.
- Refactored
AlternativeNamesfor SAN/IAN extraction- Removed detailed parsing of individual name types; now delegates decoding to
GeneralName - Introduced dedicated
GeneralNameclasses:DNSNameEDIPartyNameIPAddressNameOtherNameRegisteredIDNameRFC822NameUriNameX400AddressNameX500Name
- Removed detailed parsing of individual name types; now delegates decoding to
- Add
constrains()method inGeneralNameOptioninterface 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 normalizationRelativeDistinguishedName.fromString()- parse multi-attribute RDNsAttributeTypeAndValue.fromString()/.toRfc2253String- handle known attributes and canonicalize values
- Fix a glaring JWS bug that caused an err...
3.19.1 / Supreme 0.11.1
- Bouncy Castle 1.83 (non-forced version)
3.19.0 / Supreme 0.11.0
- Fix a glaring JWS bug that caused an error whenever trying to get the digest of a JWS signature algorithm
- Add
EnumerableandEnumerationinterfaces to support the pattern in sealed types where the companion object providesentriescontaining all possible instances- Classes and interfaces refactored to use
Setasentriesto follow the standardized pattern:Asn1Element.TagCoseAlgorithmand its nested implementationsJsonWebAlgorithmJwsAlgorithmand its nested implementationsDataIntegrityAlgorithmMessageAuthenticationCodeSignatureAlgorithmRSAPaddingSymmetricEncryptionAlgorithmand its nested implementations
- Classes and interfaces refactored to use
- Fix project setup on non-macOS build hosts
- COSE: Add identifiers
-9,-51,-53for fully-specified algorithms for ECDSA, see RFC 9864 - Fix Android project setup
- Limit Keystore operations with
limitedParallelismfromkotlinx.coroutines
3.18.2 / Supreme 0.10.2
- 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
Fix Android target pulling in JDK-21-specifics (KT-71375)
3.18.0 / Supreme 0.10.0
- 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
CoseKeyinCoseKeySerializer - Fix
prettyPrintfor structures
Asn1Stringrevamp- Deprecated
Asn1Primitive.asAsn1String(), moved decoding logic intoAsn1String.doDecode() - Added specific decodeTo functions in
Asn1Primitivefor 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.rawValueproperty 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
- Deprecated
- Add COSE_Mac0 support with
CoseMacclass - Introduce
ProtectedCoseHeaderSerializerfor serialization/deserialization protected header - Replace raw-byte protected header and its ByteArray extension serialization with
CoseHeaderusing 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 throughgradle.properties/local.propertiesor as env variable) - Auto-setup unsupported JVM toolchains
3.17.0 (Supreme 0.9.0)
- 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 aX509SignatureAlgorithm- Instances of
X509SignatureAlgorithmrepresent algorithms that are known to Signum - Test
.isSupported()or.requireSupported()(with contract smart-cast support)
- Instances of
X509CertificateandPkcs10CertificationRequestnow useX509SignatureAlgorithmDescriptionto represent a non-validated signature algorithm- Refactor
X509CertificateandTbsCertificateto store the raw signature asAsn1Primitiveand the raw public key asAsn1Sequenceenabling support for certificates with unsupported signature algorithms- Use the new KmmResult-returning
decodedSignatureanddecodedPublicKeymembers to replacepublicKeyandsignature, respectively. - The old
publicKeyandsignatureare being deprecated.
- Use the new KmmResult-returning
- Refactor
Pkcs10CertificationRequestto store the raw signature asAsn1Primitiveenabling unsupported signature algorithms- Use the new KmmResult-returning
decodedSignatureanddecodedPublicKey, respectively.
- Use the new KmmResult-returning
- Introduce
- Add structured iterator-based decoding of
Asn1Structure.Asn1Structurenow implementsIterable<Asn1Element>:- Deprecate child accessors in
Asn1Structurewith deprecation level ERROR:nextChild()nextChildOrNull()hasMoreChildren()peek()
- Add inner
Iteratorfor 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
- Add
decodeAs()for decoding ASN.1 structures via iterator-based lambda, moved trailing data check fromdecodeFromTlv()todecodeAs() - Refactor
doDecode()implementations inAsn1Structuresubclasses to use the newdecodeAs()iterator-based API instead of deprecated child access methods.
- Deprecate child accessors in
- Add
SpecializedSymmetricEncryptionAlgorithm- This allows
randomKey()etc to operate on COSE/JWE algorithms
- This allows
- Move constants of
KnownOIDsinto a discrete moduleindispensable-oidsas extensions on theKnownOIDsobject- → update your imports!
- ASN.1 polishing:
- rename
Asn1Element.lengthproperty toAsn1Element.contentLength(and add a delegate with the old name and deprecation annotation to the new property) - Add missing
Asn1.Realshorthand to the ASN.1 builder - Add
Asn1Nullconstant - Add human-readable ASN.1 element
prettyPrint()method - Make
Asn1OctetStringinterface sealed
- rename
- Strippable
KnownOIDs- Move
KnownOIDsinto a discrete moduleindispensable-oids
- Move
- OID descriptions:
KnownOIDsnow implementsMutableMap<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
KnownOIDsshipped with theindispensable-oidsmodule come with a description. To actually add them to all known descriptions, callKnownOIDs.describeAll()once.
- Deprecate
serialize()anddeserialize()methods in COSE+ JOSE data classes - Clean up some function signatures:
SymmetricKey.toJsonWebKeynow returnsKmmResultSymmetricEncryptionAlgorithm.toJweKwAlgorithmnow returnsKmmResultSymmetricEncryptionAlgorithm.toJweEncryptionAlgorithmremoved
- In
JwsHeaderadd propertyvcTypeMetadatawith keyvctm, 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