diff --git a/gradle.properties b/gradle.properties index 91334602..ec2f67fe 100644 --- a/gradle.properties +++ b/gradle.properties @@ -2,8 +2,8 @@ kotlin.code.style=official kotlin.js.compiler=ir org.gradle.jvmargs=-Xmx4g -Dfile.encoding=UTF-8 -artifactVersion=3.10.0 -supremeVersion=0.5.0 +artifactVersion=3.10.1 +supremeVersion=0.5.1 # This is not a well-defined property, the ASP convention plugin respects it, though jdk.version=17 diff --git a/indispensable-asn1/src/commonMain/kotlin/at/asitplus/signum/indispensable/asn1/Asn1BitString.kt b/indispensable-asn1/src/commonMain/kotlin/at/asitplus/signum/indispensable/asn1/Asn1BitString.kt index 87459838..95167f25 100644 --- a/indispensable-asn1/src/commonMain/kotlin/at/asitplus/signum/indispensable/asn1/Asn1BitString.kt +++ b/indispensable-asn1/src/commonMain/kotlin/at/asitplus/signum/indispensable/asn1/Asn1BitString.kt @@ -41,6 +41,8 @@ class Asn1BitString private constructor( */ constructor(source: BitSet) : this(fromBitSet(source)) + constructor(source: ByteArray) : this(Pair(0x00.toByte(), source)) + /** * Transforms [rawBytes] and wraps into a [BitSet]. The last [numPaddingBits] bits are ignored. * This is a deep copy and mirrors the bits in every byte to match diff --git a/indispensable-asn1/src/commonMain/kotlin/at/asitplus/signum/indispensable/asn1/Asn1Elements.kt b/indispensable-asn1/src/commonMain/kotlin/at/asitplus/signum/indispensable/asn1/Asn1Elements.kt index 03c145dd..0cde13da 100644 --- a/indispensable-asn1/src/commonMain/kotlin/at/asitplus/signum/indispensable/asn1/Asn1Elements.kt +++ b/indispensable-asn1/src/commonMain/kotlin/at/asitplus/signum/indispensable/asn1/Asn1Elements.kt @@ -755,7 +755,7 @@ class Asn1SetOf @Throws(Asn1Exception::class) internal constructor(children: Lis }) /** - * ASN.1 primitive. Hold o children, but [content] under [tag] + * ASN.1 primitive. Holds no children, but [content] under [tag] */ open class Asn1Primitive( tag: Tag, diff --git a/indispensable-asn1/src/jvmTest/kotlin/at/asitplus/signum/indispensable/asn1/Asn1ParserTest.kt b/indispensable-asn1/src/jvmTest/kotlin/at/asitplus/signum/indispensable/asn1/Asn1ParserTest.kt index 58ff58a5..ba3b763d 100644 --- a/indispensable-asn1/src/jvmTest/kotlin/at/asitplus/signum/indispensable/asn1/Asn1ParserTest.kt +++ b/indispensable-asn1/src/jvmTest/kotlin/at/asitplus/signum/indispensable/asn1/Asn1ParserTest.kt @@ -35,8 +35,9 @@ class Asn1ParserTest : FreeSpec({ } var byteIterator = rest repeat(9) { - Asn1Element.parseFirst(byteIterator) - .let { (elem, residue) -> byteIterator = residue;elem } shouldBe childIterator.next() + val (elem, residue) = Asn1Element.parseFirst(byteIterator) + elem shouldBe childIterator.next() + byteIterator = residue } Asn1Element.parseAll(rawChildren) shouldBe seq.children diff --git a/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/CryptoSignature.kt b/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/CryptoSignature.kt index 9e013361..3734cbab 100644 --- a/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/CryptoSignature.kt +++ b/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/CryptoSignature.kt @@ -6,6 +6,7 @@ import at.asitplus.signum.indispensable.io.Base64Strict import at.asitplus.signum.indispensable.misc.ensureSize import at.asitplus.signum.indispensable.misc.BitLength import at.asitplus.signum.indispensable.misc.max +import at.asitplus.signum.indispensable.misc.orLazy import at.asitplus.signum.indispensable.pki.X509Certificate import com.ionspin.kotlin.bignum.integer.BigInteger import com.ionspin.kotlin.bignum.integer.Sign @@ -27,7 +28,6 @@ import kotlinx.serialization.encoding.Encoder @Serializable(with = CryptoSignature.CryptoSignatureSerializer::class) sealed interface CryptoSignature : Asn1Encodable { - val signature: Asn1Element /** @@ -58,12 +58,7 @@ sealed interface CryptoSignature : Asn1Encodable { */ sealed interface NotRawByteEncodable : CryptoSignature - - fun encodeToTlvBitString(): Asn1Element - - override fun encodeToTlv(): Asn1Element = signature - - val humanReadableString: String get() = "${this::class.simpleName ?: "CryptoSignature"}(signature=${signature.prettyPrint()})" + val humanReadableString: String get() = "${this::class.simpleName ?: "CryptoSignature"}(signature=${encodeToTlv().prettyPrint()})" object CryptoSignatureSerializer : KSerializer { @@ -93,9 +88,7 @@ sealed interface CryptoSignature : Asn1Encodable { require(s.isPositive) { "s must be positive" } } - override val signature: Asn1Element = Asn1.Sequence { +r.encodeToAsn1Primitive(); +s.encodeToAsn1Primitive() } - - override fun encodeToTlvBitString(): Asn1Element = encodeToDer().encodeToAsn1BitStringPrimitive() + override fun encodeToTlv() = Asn1.Sequence { +r.encodeToAsn1Primitive(); +s.encodeToAsn1Primitive() } /** * Two signatures are considered equal if `r` and `s` are equal. @@ -209,11 +202,6 @@ sealed interface CryptoSignature : Asn1Encodable { return fromRawBytes(input) } - @Throws(Asn1Exception::class) - fun decodeFromTlvBitString(src: Asn1Primitive): EC.IndefiniteLength = runRethrowing { - decodeFromDer(src.asAsn1BitString().rawBytes) - } - override fun doDecode(src: Asn1Element): EC.IndefiniteLength { src as Asn1Sequence val r = (src.nextChild() as Asn1Primitive).decodeToBigInteger() @@ -229,12 +217,21 @@ sealed interface CryptoSignature : Asn1Encodable { } - class RSAorHMAC(input: ByteArray) : CryptoSignature, RawByteEncodable { + class RSAorHMAC private constructor (rawBytes: ByteArray?, x509Element: Asn1Primitive?) : CryptoSignature, RawByteEncodable { + constructor(rawBytes: ByteArray) : this(rawBytes, null) + constructor(x509Element: Asn1Primitive) : this(null, x509Element) - override val signature: Asn1Element = Asn1Primitive(Asn1Element.Tag.BIT_STRING, input) + /** the signature encoded as an ASN.1 BIT STRING */ + val signature: Asn1Primitive by x509Element orLazy { + Asn1BitString(rawByteArray).encodeToTlv() + } - override val rawByteArray by lazy { (signature as Asn1Primitive).decode(Asn1Element.Tag.BIT_STRING) { it } } - override fun encodeToTlvBitString(): Asn1Element = this.encodeToTlv() + override fun encodeToTlv() = signature + + /** the raw bytes of the signature value */ + override val rawByteArray by rawBytes orLazy { + signature.asAsn1BitString().rawBytes + } override fun hashCode(): Int = signature.hashCode() @@ -249,10 +246,11 @@ sealed interface CryptoSignature : Asn1Encodable { return signature == other.signature } - companion object { + companion object : Asn1Decodable { @Throws(Asn1Exception::class) - fun decodeFromTlvBitString(src: Asn1Primitive): RSAorHMAC = runRethrowing { - decodeFromTlv(src) as RSAorHMAC + override fun doDecode(src: Asn1Element): RSAorHMAC { + src as Asn1Primitive + return RSAorHMAC(src) } } } @@ -261,8 +259,8 @@ sealed interface CryptoSignature : Asn1Encodable { @Throws(Asn1Exception::class) override fun doDecode(src: Asn1Element): CryptoSignature = runRethrowing { when (src.tag) { - Asn1Element.Tag.BIT_STRING -> RSAorHMAC((src as Asn1Primitive).decode(Asn1Element.Tag.BIT_STRING) { it }) - Asn1Element.Tag.SEQUENCE -> EC.decodeFromTlv(src as Asn1Sequence) + Asn1Element.Tag.BIT_STRING -> RSAorHMAC.decodeFromTlv(src) + Asn1Element.Tag.SEQUENCE -> EC.decodeFromTlv(src) else -> throw Asn1Exception("Unknown Signature Format") } diff --git a/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/misc/Utils.kt b/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/misc/Utils.kt index b5399509..09b743fc 100644 --- a/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/misc/Utils.kt +++ b/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/misc/Utils.kt @@ -12,4 +12,7 @@ fun ByteArray.ensureSize(size: Int): ByteArray = (this.size - size).let { toDrop } @Suppress("NOTHING_TO_INLINE") -inline fun ByteArray.ensureSize(size: UInt) = ensureSize(size.toInt()) \ No newline at end of file +inline fun ByteArray.ensureSize(size: UInt) = ensureSize(size.toInt()) + +internal infix fun T?.orLazy(block: ()->T) = + if (this != null) lazyOf(this) else lazy(block) diff --git a/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/pki/X509Certificate.kt b/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/pki/X509Certificate.kt index 84babe46..0e417247 100644 --- a/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/pki/X509Certificate.kt +++ b/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/pki/X509Certificate.kt @@ -6,7 +6,6 @@ import at.asitplus.signum.indispensable.CryptoSignature import at.asitplus.signum.indispensable.X509SignatureAlgorithm import at.asitplus.signum.indispensable.asn1.* import at.asitplus.signum.indispensable.asn1.encoding.* -import at.asitplus.signum.indispensable.asn1.BitSet import at.asitplus.signum.indispensable.io.ByteArrayBase64Serializer import at.asitplus.signum.indispensable.pki.AlternativeNames.Companion.findIssuerAltNames import at.asitplus.signum.indispensable.pki.AlternativeNames.Companion.findSubjectAltNames @@ -15,13 +14,8 @@ import at.asitplus.signum.indispensable.pki.TbsCertificate.Companion.Tags.ISSUER import at.asitplus.signum.indispensable.pki.TbsCertificate.Companion.Tags.SUBJECT_UID import io.matthewnelson.encoding.base64.Base64 import io.matthewnelson.encoding.core.Decoder.Companion.decodeToByteArray -import kotlinx.serialization.KSerializer import kotlinx.serialization.Serializable import kotlinx.serialization.Transient -import kotlinx.serialization.descriptors.PrimitiveKind -import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor -import kotlinx.serialization.encoding.Decoder -import kotlinx.serialization.encoding.Encoder /** * Very simple implementation of the meat of an X.509 Certificate: @@ -36,7 +30,7 @@ constructor( val signatureAlgorithm: X509SignatureAlgorithm, val issuerName: List, val validFrom: Asn1Time, - val validUntil: Asn1Time, + val validUntil: Asn1Time, val subjectName: List, val publicKey: CryptoPublicKey, val issuerUniqueID: BitSet? = null, @@ -178,7 +172,8 @@ constructor( } else null } val extensions = if (src.hasMoreChildren()) { - ((src.nextChild() as Asn1ExplicitlyTagged).verifyTag(EXTENSIONS.tagValue).single() as Asn1Sequence).children.map { + ((src.nextChild() as Asn1ExplicitlyTagged).verifyTag(EXTENSIONS.tagValue) + .single() as Asn1Sequence).children.map { X509CertificateExtension.decodeFromTlv(it as Asn1Sequence) } } else null @@ -210,6 +205,28 @@ constructor( } } +/** + * Signature encoded as per X.509: + * - RSA remains a bit string + * - EC is DER-encoded then wrapped in a bit string + */ +val CryptoSignature.x509Encoded + get() = when (this) { + is CryptoSignature.EC -> encodeToDer().encodeToAsn1BitStringPrimitive() + is CryptoSignature.RSAorHMAC -> encodeToTlv() + } + +/** + * Decode a X.509-encoded signature + * - RSA is encoded as a bit string + * - EC is DER-encoded then wrapped in a bit string + */ +fun CryptoSignature.Companion.fromX509Encoded(alg: X509SignatureAlgorithm, it: Asn1Primitive) = + when (alg.isEc) { + true -> CryptoSignature.EC.decodeFromDer(it.asAsn1BitString().rawBytes) + false -> CryptoSignature.RSAorHMAC.decodeFromTlv(it) + } + /** * Very simple implementation of an X.509 Certificate */ @@ -224,7 +241,7 @@ data class X509Certificate @Throws(IllegalArgumentException::class) constructor( override fun encodeToTlv() = Asn1.Sequence { +tbsCertificate +signatureAlgorithm - +signature.encodeToTlvBitString() + +signature.x509Encoded } override fun equals(other: Any?): Boolean { @@ -255,10 +272,7 @@ data class X509Certificate @Throws(IllegalArgumentException::class) constructor( override fun doDecode(src: Asn1Sequence): X509Certificate = runRethrowing { val tbs = TbsCertificate.decodeFromTlv(src.nextChild() as Asn1Sequence) val sigAlg = X509SignatureAlgorithm.decodeFromTlv(src.nextChild() as Asn1Sequence) - val signature = when { - sigAlg.isEc -> CryptoSignature.EC.decodeFromTlvBitString(src.nextChild() as Asn1Primitive) - else -> CryptoSignature.RSAorHMAC.decodeFromTlvBitString(src.nextChild() as Asn1Primitive) - } + val signature = CryptoSignature.fromX509Encoded(sigAlg, src.nextChild() as Asn1Primitive) if (src.hasMoreChildren()) throw Asn1StructuralException("Superfluous structure in Certificate Structure") return X509Certificate(tbs, sigAlg, signature) } diff --git a/indispensable/src/jvmTest/kotlin/at/asitplus/signum/ecmath/ECMathTest.kt b/indispensable/src/jvmTest/kotlin/at/asitplus/signum/ecmath/ECMathTest.kt index 2098e5b2..d02a4f39 100644 --- a/indispensable/src/jvmTest/kotlin/at/asitplus/signum/ecmath/ECMathTest.kt +++ b/indispensable/src/jvmTest/kotlin/at/asitplus/signum/ecmath/ECMathTest.kt @@ -38,9 +38,11 @@ class ECMathTest : FreeSpec({ } "Addition: group axioms" - { withData(ECCurve.entries) { curve -> - withData(generateSequence { - Triple(curve.randomPoint(), curve.randomPoint(), curve.randomPoint()) - }.take(50)) { (a, b, c) -> + withData(nameFn = { (a, b, c) -> "(a=$a, b=$b, c=$c)" }, + generateSequence { + Triple(curve.randomPoint(), curve.randomPoint(), curve.randomPoint()) + }.take(50) + ) { (a, b, c) -> a + b shouldBe b + a (a + b) + c shouldBe a + (b + c) a + (-a) shouldBe curve.IDENTITY diff --git a/indispensable/src/jvmTest/kotlin/at/asitplus/signum/indispensable/SignatureCodecTest.kt b/indispensable/src/jvmTest/kotlin/at/asitplus/signum/indispensable/SignatureCodecTest.kt new file mode 100644 index 00000000..d018fa5f --- /dev/null +++ b/indispensable/src/jvmTest/kotlin/at/asitplus/signum/indispensable/SignatureCodecTest.kt @@ -0,0 +1,113 @@ +package at.asitplus.signum.indispensable + +import at.asitplus.signum.indispensable.pki.getContentSigner +import io.kotest.core.spec.style.FreeSpec +import io.kotest.datatest.withData +import io.kotest.matchers.shouldBe +import org.bouncycastle.asn1.ASN1Sequence +import org.bouncycastle.asn1.DLSequence +import org.bouncycastle.asn1.x500.X500Name +import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo +import org.bouncycastle.cert.X509v3CertificateBuilder +import org.bouncycastle.jce.provider.BouncyCastleProvider +import org.bouncycastle.operator.ContentSigner +import java.math.BigInteger +import java.security.KeyPair +import java.security.KeyPairGenerator +import java.security.Security +import java.security.Signature +import java.security.spec.ECGenParameterSpec +import java.time.Instant +import java.util.* +import kotlin.math.absoluteValue +import kotlin.random.Random +import kotlin.time.Duration.Companion.days + +@OptIn(ExperimentalStdlibApi::class) +class SignatureCodecTest : FreeSpec({ + + "EC" - { + val curve = "secp256r1" + val digest = "SHA256" + val data = Random.nextBytes(256) + + val preGen = List(500) { + KeyPairGenerator.getInstance("EC").also { + it.initialize(ECGenParameterSpec(curve)) + }.generateKeyPair() + } + withData(nameFn = { CryptoPublicKey.fromJcaPublicKey(it.public).getOrThrow().didEncoded }, preGen) { keys -> + val sig = Signature.getInstance("${digest}withECDSA").run { + initSign(keys.private) + update(data) + sign() + } + + CryptoSignature.EC.parseFromJca(sig).jcaSignatureBytes shouldBe sig + CryptoSignature.parseFromJca( + sig, + SignatureAlgorithm.ECDSA(Digest.valueOf(digest), ECCurve.byJcaName(curve)) + ).jcaSignatureBytes shouldBe sig + + Signature.getInstance("${digest}withECDSAinP1363Format").run { + initVerify(keys.public) + update(data) + verify(CryptoSignature.EC.parseFromJca(sig).encodeToDer()) + } + + } + } + + "RSA" - { + Security.addProvider(BouncyCastleProvider()) + + val digest = ("SHA256") + + val preGen = List(500) { KeyPairGenerator.getInstance("RSA").apply { initialize(512) }.generateKeyPair() } + withData(nameFn = { CryptoPublicKey.fromJcaPublicKey(it.public).getOrThrow().didEncoded }, preGen) { keys -> + val data = Random.nextBytes(256) + val sig = Signature.getInstance("${digest}withRSA").run { + initSign(keys.private) + update(data) + sign() + } + + CryptoSignature.RSAorHMAC.parseFromJca(sig).jcaSignatureBytes shouldBe sig + CryptoSignature.parseFromJca( + sig, + SignatureAlgorithm.RSA(Digest.valueOf(digest), RSAPadding.PKCS1) + ).jcaSignatureBytes shouldBe sig + + // create certificate with bouncycastle + val notBeforeDate = Date.from(Instant.now()) + val notAfterDate = Date.from(Instant.now().plusSeconds(30.days.inWholeSeconds)) + val serialNumber: BigInteger = BigInteger.valueOf(Random.nextLong().absoluteValue) + val commonName = "DefaultCryptoService" + val issuer = X500Name("CN=$commonName") + val builder = X509v3CertificateBuilder( + /* issuer = */ issuer, + /* serial = */ serialNumber, + /* notBefore = */ notBeforeDate, + /* notAfter = */ notAfterDate, + /* subject = */ issuer, + /* publicKeyInfo = */ SubjectPublicKeyInfo.getInstance(keys.public.encoded) + ) + val signatureAlgorithm = X509SignatureAlgorithm.RS256 + val contentSigner: ContentSigner = signatureAlgorithm.getContentSigner(keys.private) + val certificateHolder = builder.build(contentSigner) + certificateHolder.signature + val bcSig = + (ASN1Sequence.fromByteArray(certificateHolder.encoded) as DLSequence).elementAt(2) + .toASN1Primitive().encoded + CryptoSignature.RSAorHMAC.parseFromJca(certificateHolder.signature).encodeToDer() shouldBe bcSig + CryptoSignature.parseFromJca( + certificateHolder.signature, + SignatureAlgorithm.RSA(Digest.valueOf(digest), RSAPadding.PKCS1) + ).encodeToDer() shouldBe bcSig + + } + } + + +}) + diff --git a/supreme/src/commonTest/kotlin/at/asitplus/signum/supreme/sign/EphemeralSignerCommonTests.kt b/supreme/src/commonTest/kotlin/at/asitplus/signum/supreme/sign/EphemeralSignerCommonTests.kt index 20ded8b1..4c108d7f 100644 --- a/supreme/src/commonTest/kotlin/at/asitplus/signum/supreme/sign/EphemeralSignerCommonTests.kt +++ b/supreme/src/commonTest/kotlin/at/asitplus/signum/supreme/sign/EphemeralSignerCommonTests.kt @@ -6,6 +6,9 @@ import at.asitplus.signum.indispensable.asn1.Asn1String import at.asitplus.signum.indispensable.asn1.Asn1Time import at.asitplus.signum.indispensable.asn1.KnownOIDs import at.asitplus.signum.indispensable.pki.* +import at.asitplus.signum.supreme.dsl.DSL +import at.asitplus.signum.supreme.os.PlatformSigningKeyConfigurationBase +import at.asitplus.signum.supreme.os.SignerConfiguration import at.asitplus.signum.supreme.sign import at.asitplus.signum.supreme.signature import at.asitplus.signum.supreme.succeed @@ -22,28 +25,80 @@ import kotlinx.datetime.Clock import kotlin.random.Random import kotlin.time.Duration.Companion.days +interface SignatureTestSuite { + val isPreHashed: Boolean + fun configure(it: SigningKeyConfiguration) + fun configure(it: SignerConfiguration) +} +data class ECDSATestSuite(val curve: ECCurve, val digest: Digest, override val isPreHashed: Boolean): SignatureTestSuite { + override fun toString() = "ECDSA/$curve/$digest${if (isPreHashed) "/pre" else ""}" + override fun configure(it: SigningKeyConfiguration) { + it.ec { + this.curve = this@ECDSATestSuite.curve + this.digests = setOf(this@ECDSATestSuite.digest) + } + if (it is PlatformSigningKeyConfigurationBase<*>) { + it.signer { this@ECDSATestSuite.configure(this@signer) } + } + } + override fun configure(it: SignerConfiguration) { + it.ec { + this.digest = this@ECDSATestSuite.digest + } + } +} +data class RSATestSuite(val padding: RSAPadding, val digest: Digest, val keySize: Int, override val isPreHashed: Boolean): SignatureTestSuite { + override fun toString() = "RSA/$digest/$padding/${keySize}bit${if (isPreHashed) "/pre" else ""}" + override fun configure(it: SigningKeyConfiguration) { + it.rsa { + this.digests = setOf(this@RSATestSuite.digest) + this.paddings = setOf(this@RSATestSuite.padding) + this.bits = this@RSATestSuite.keySize + } + if (it is PlatformSigningKeyConfigurationBase<*>) { + it.signer { this@RSATestSuite.configure(this@signer) } + } + } + override fun configure(it: SignerConfiguration) { + it.rsa { + this.digest = this@RSATestSuite.digest + this.padding = this@RSATestSuite.padding + } + } +} +object TestSuites { + val ALL get() = ECDSA + RSA + val ECDSA get() = sequence { + ECCurve.entries.forEach { curve -> + Digest.entries.forEach { digest -> + yield(ECDSATestSuite(curve, digest, false)) + yield(ECDSATestSuite(curve, digest, true)) + } + } + } + val RSA get() = sequence { + RSAPadding.entries.forEach { padding -> + Digest.entries.forEach { digest -> + when { + digest == Digest.SHA512 && padding == RSAPadding.PSS + -> listOf(2048, 3072, 4096) + digest == Digest.SHA384 || digest == Digest.SHA512 || padding == RSAPadding.PSS + -> listOf(1024,2048,3072,4096) + else + -> listOf(512, 1024, 2048, 3072, 4096) + }.forEach { keySize -> + yield(RSATestSuite(padding, digest, keySize, false)) + yield(RSATestSuite(padding, digest, keySize, true)) + } + } + } + } +} + class EphemeralSignerCommonTests : FreeSpec({ "Functional" - { "RSA" - { - withData( - nameFn = { (pad, dig, bits, pre) -> "$dig/$pad/${bits}bit${if (pre) "/pre" else ""}" }, - sequence { - RSAPadding.entries.forEach { padding -> - Digest.entries.forEach { digest -> - when { - digest == Digest.SHA512 && padding == RSAPadding.PSS - -> listOf(2048, 3072, 4096) - digest == Digest.SHA384 || digest == Digest.SHA512 || padding == RSAPadding.PSS - -> listOf(1024,2048,3072,4096) - else - -> listOf(512, 1024, 2048, 3072, 4096) - }.forEach { keySize -> - yield(Quadruple(padding, digest, keySize, false)) - yield(Quadruple(padding, digest, keySize, true)) - } - } - } - }) { (padding, digest, keySize, preHashed) -> + withData(TestSuites.RSA) { (padding, digest, keySize, preHashed) -> val data = Random.Default.nextBytes(64) val signer: Signer val signature = try { @@ -68,16 +123,7 @@ class EphemeralSignerCommonTests : FreeSpec({ } } "ECDSA" - { - withData( - nameFn = { (crv, dig, pre) -> "$crv/$dig${if (pre) "/pre" else ""}" }, - sequence { - ECCurve.entries.forEach { curve -> - Digest.entries.forEach { digest -> - yield(Triple(curve, digest, false)) - yield(Triple(curve, digest, true)) - } - } - }) { (crv, digest, preHashed) -> + withData(TestSuites.ECDSA) { (crv, digest, preHashed) -> val signer = Signer.Ephemeral { ec { curve = crv; digests = setOf(digest) } }.getOrThrow() signer.signatureAlgorithm.shouldBeInstanceOf().let { @@ -142,31 +188,10 @@ class EphemeralSignerCommonTests : FreeSpec({ "Cert signing" - { "RSA" - { - withData( - nameFn = { (pad, dig, bits, pre) -> "$dig/$pad/${bits}bit${if (pre) "/pre" else ""}" }, - sequence { - RSAPadding.entries.forEach { padding -> - Digest.entries.forEach { digest -> - when { - digest == Digest.SHA512 && padding == RSAPadding.PSS - -> listOf(2048, 3072, 4096) - - digest == Digest.SHA384 || digest == Digest.SHA512 || padding == RSAPadding.PSS - -> listOf(1024, 2048, 3072, 4096) - - else - -> listOf(512, 1024, 2048, 3072, 4096) - }.forEach { keySize -> - yield(Quadruple(padding, digest, keySize, false)) - yield(Quadruple(padding, digest, keySize, true)) - } - } - } - }) { (padding, digest, keySize, preHashed) -> + withData(TestSuites.RSA) { (padding, digest, keySize, preHashed) -> val data = Random.Default.nextBytes(64) val signer: Signer - try { signer = Signer.Ephemeral { rsa { @@ -226,16 +251,7 @@ class EphemeralSignerCommonTests : FreeSpec({ } "ECDSA" - { - withData( - nameFn = { (crv, dig, pre) -> "$crv/$dig${if (pre) "/pre" else ""}" }, - sequence { - ECCurve.entries.forEach { curve -> - Digest.entries.filterNot { it == Digest.SHA1 }.forEach { digest -> - yield(Triple(curve, digest, false)) - yield(Triple(curve, digest, true)) - } - } - }) { (crv, digest, preHashed) -> + withData(TestSuites.ECDSA.filter { it.digest != Digest.SHA1 }) { (crv, digest, _) -> val signer = Signer.Ephemeral { ec { curve = crv; digests = setOf(digest) } }.getOrThrow() signer.signatureAlgorithm.shouldBeInstanceOf().let { diff --git a/supreme/src/jvmTest/kotlin/at/asitplus/signum/supreme/os/JKSProviderTest.kt b/supreme/src/jvmTest/kotlin/at/asitplus/signum/supreme/os/JKSProviderTest.kt index 0977f3ba..36eec5d6 100644 --- a/supreme/src/jvmTest/kotlin/at/asitplus/signum/supreme/os/JKSProviderTest.kt +++ b/supreme/src/jvmTest/kotlin/at/asitplus/signum/supreme/os/JKSProviderTest.kt @@ -1,10 +1,12 @@ package at.asitplus.signum.supreme.os -import at.asitplus.signum.supreme.sign.makeVerifier -import at.asitplus.signum.supreme.sign.verify +import at.asitplus.signum.indispensable.* +import at.asitplus.signum.supreme.UnsupportedCryptoException +import at.asitplus.signum.supreme.sign.* import at.asitplus.signum.supreme.signature import at.asitplus.signum.supreme.succeed import io.kotest.core.spec.style.FreeSpec +import io.kotest.datatest.withData import io.kotest.matchers.should import io.kotest.matchers.shouldBe import io.kotest.matchers.shouldNot @@ -70,4 +72,39 @@ class JKSProviderTest : FreeSpec({ ks1.getSignerForKey(alias) shouldNot succeed } finally { Files.deleteIfExists(tempfile) } } + "Certificate encoding" - { + withData(TestSuites.ALL) { test -> + val alias = Random.azstring(16) + val ks = JKSProvider().getOrThrow() + val signer = ks.createSigningKey(alias) { + test.configure(this) + }.getOrThrow() + + val data = SignatureInput(Random.nextBytes(1200)).let { + if (test.isPreHashed) it.convertTo(signer.signatureAlgorithm.preHashedSignatureFormat).getOrThrow() + else it + } + val signature = try { + signer.sign(data).signature + } catch (x: UnsupportedOperationException) { + return@withData + } + CryptoSignature.parseFromJca(signature.jcaSignatureBytes, signer.signatureAlgorithm) shouldBe signature + when (signer.signatureAlgorithm) { + is SignatureAlgorithm.RSA, is SignatureAlgorithm.HMAC -> + CryptoSignature.RSAorHMAC.parseFromJca(signature.jcaSignatureBytes) shouldBe signature + is SignatureAlgorithm.ECDSA -> + CryptoSignature.EC.parseFromJca(signature.jcaSignatureBytes) shouldBe signature + } + + signer.signatureAlgorithm.let { + if (test.isPreHashed) it.getJCASignatureInstancePreHashed() + else it.getJCASignatureInstance() + }.getOrThrow().let { sig -> + sig.initVerify(signer.publicKey.getJcaPublicKey().getOrThrow()) + data.data.forEach(sig::update) + sig.verify(signature.jcaSignatureBytes) shouldBe true + } + } + } })