Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// SPDX-FileCopyrightText: Copyright (c) A-SIT Plus GmbH
// SPDX-License-Identifier: Apache-2.0

package at.asitplus.awesn1.crypto

import at.asitplus.awesn1.Asn1Integer
import at.asitplus.awesn1.serialization.DER
import at.asitplus.awesn1.serialization.decodeFromDer
import kotlinx.serialization.Serializable
import kotlinx.serialization.encodeToByteArray

@Serializable
data class EcdsaSigValue(val r: Asn1Integer, val s: Asn1Integer) {
Comment thread
JesusMcCloud marked this conversation as resolved.
fun toX509SignatureValue() = X509SignatureValue(DER.encodeToByteArray(this))
companion object {
fun X509SignatureValue.toEcdsaSigValue() = DER.decodeFromDer<EcdsaSigValue>(rawBytes)
}
}
Comment thread
JesusMcCloud marked this conversation as resolved.

Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,15 @@

package at.asitplus.awesn1.crypto

import at.asitplus.awesn1.Asn1Exception
import at.asitplus.awesn1.Asn1Integer
import at.asitplus.awesn1.ObjectIdentifier
import at.asitplus.awesn1.encoding.Asn1
import at.asitplus.awesn1.runRethrowing
import at.asitplus.awesn1.serialization.Asn1Tag
import at.asitplus.awesn1.serialization.DER
import at.asitplus.awesn1.serialization.ExplicitlyTagged
import at.asitplus.awesn1.serialization.decodeFromTlv
import at.asitplus.awesn1.serialization.getValue
import at.asitplus.awesn1.toInt
import kotlinx.serialization.Serializable
Expand Down Expand Up @@ -117,6 +121,23 @@ data class RsaSsaPssParams internal constructor(

val SHA1_IDENTIFIER = X509AlgorithmIdentifier(SHA1_OID, listOf(Asn1.Null()))
val MGF1_SHA1_IDENTIFIER = X509AlgorithmIdentifier(MGF1_OID, listOf(SHA1_IDENTIFIER.element))

/**
* Asserts that this identifier uses the `id-RSASSA-PSS` OID,
* then parses [parameters] as RSASSA-PSS parameters.
*
* This helper models [RFC 4055, section 3.1](https://www.rfc-editor.org/rfc/rfc4055.html#section-3.1).
*
* @throws Asn1Exception if this algorithm is RSA_SSA_PSS has no parameters, or the parameter element is
* not a valid `RSASSA-PSS-params` SEQUENCE.
*/
val X509AlgorithmIdentifier.rsaSsaPssParams: RsaSsaPssParams get() = runRethrowing {
require(oid == RSA_SSA_PSS_OID)
DER.decodeFromTlv<RsaSsaPssParams>(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hardcoding DER was cursed from the get-go. Given that this does not have to live inside X509AlgorithmIdentifier any more, we can reshape it from a getter to something else entirely, that properly deserializes

parameters?.asSequence() ?: throw Asn1Exception("RSASSA-PSS AlgorithmIdentifier has no parameters")
)
}

}

override fun equals(other: Any?): Boolean {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,35 +87,6 @@ value class X509AlgorithmIdentifier(override val element: Asn1Sequence) : Identi
else -> throw Asn1Exception("AlgorithmIdentifier has ${element.children.size} children")
}

/**
* Parses [parameters] as RSASSA-PSS parameters if this identifier uses the `id-RSASSA-PSS` OID.
*
* This helper models [RFC 4055, section 3.1](https://www.rfc-editor.org/rfc/rfc4055.html#section-3.1) without
* making [X509AlgorithmIdentifier] itself enforce algorithm-specific parameter schemas during generic DER parsing.
*
* @return `null` if this algorithm is nor RSA_SSA_PSS
*
* @throws Asn1Exception if this algorithm is RSA_SSA_PSS has no parameters, or the parameter element is
* not a valid `RSASSA-PSS-params` SEQUENCE.
*
* From Swift/Objective-C use the throwing `rsaSsaPssParams()` accessor (exported as a static
* `rsaSsaPssParams(_:)`, since value classes are not bridged as Objective-C types).
*/
@OptIn(ExperimentalObjCRefinement::class)
@Suppress("WRONG_ANNOTATION_TARGET_WITH_USE_SITE_TARGET")
@get:Throws(Asn1Exception::class)
@HiddenFromObjC
@get:HiddenFromObjC
val rsaSsaPssParams: RsaSsaPssParams?
get() = runWrappingAs(a = ::Asn1Exception) {
if (oid != RsaSsaPssParams.RSA_SSA_PSS_OID) {
return null
}
DER.decodeFromTlv<RsaSsaPssParams>(
parameters?.asSequence() ?: throw Asn1Exception("RSASSA-PSS AlgorithmIdentifier has no parameters")
)
}

override fun toString(): String {
return catchingUnwrapped {
"AlgorithmIdentifier(" +
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,21 +49,4 @@ value class X509SignatureValue(val rawBitString: Asn1BitString): WrappedEncodabl
constructor(rawBytes: ByteArray) : this(Asn1BitString(rawBytes))

val rawBytes: ByteArray get() = rawBitString.bitCarryingBytes

// runRethrowing: a malformed ECDSA-Sig-Value (fewer than two children, or non-positive integers) would
// otherwise leak NoSuchElementException/ClassCastException instead of a catchable Asn1Exception.
@Throws(Asn1Exception::class)
fun decodeRS(): Pair<Asn1Integer.Positive, Asn1Integer.Positive> = runRethrowing {
Asn1Element.parse(rawBytes).asSequence().decodeAs {
next().asPrimitive().decodeToAsn1Integer() as Asn1Integer.Positive to
next().asPrimitive().decodeToAsn1Integer() as Asn1Integer.Positive
}
}

companion object {
fun fromRS(r: Asn1Integer.Positive, s: Asn1Integer.Positive) =
X509SignatureValue(Asn1.Sequence { +r; +s }.derEncoded)
}
}

fun X509SignatureValue.decodeRsOrNull() = catchingUnwrapped { decodeRS() }.getOrNull()
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ private fun randomBitStringSignatureValue(random: Random) =
X509SignatureValue(randomBytes(random))

private fun randomEcdsaSignatureValue(random: Random) =
X509SignatureValue.fromRS(positiveAsn1Integer(random), positiveAsn1Integer(random))
EcdsaSigValue(positiveAsn1Integer(random), positiveAsn1Integer(random)).toX509SignatureValue()

private fun randomEcPrivateKey(random: Random) = Sec1EcPrivateKeyInfo(
privateKey = randomBytes(random, 32),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package at.asitplus.awesn1.crypto

import at.asitplus.awesn1.Asn1Integer
import at.asitplus.awesn1.crypto.EcdsaSigValue.Companion.toEcdsaSigValue
import at.asitplus.awesn1.serialization.DER
import at.asitplus.awesn1.serialization.decodeFromDer
import at.asitplus.testballoon.matrix.matrixSuite
import io.kotest.matchers.shouldBe
import kotlin.io.encoding.Base64

val EcdsaSigValueTest by matrixSuite {
"Test" {
val sigValue = Base64.UrlSafe.decode("A0kAMEYCIQCnXiAKLwJP0uXBKtTmJccBu" +
"yddhFFVTz-J0DNHBi21lgIhAI1SUoIYXXqdZMrKox4_HBTEmuxvG9sloAoDH5rfsyd4")
.let { DER.decodeFromDer<X509SignatureValue>(it) }
sigValue.toEcdsaSigValue().let {
it.r.toString() shouldBe "75702550467927847687504835259358957068594330833158195591194084637648375100822"
it.s.toString() shouldBe "63921562560111841846899158949481573510824775155107323853077162713843342976888"
}
}
}
Loading