Skip to content

Commit

Permalink
feat: CBOR support + other tests (#12)
Browse files Browse the repository at this point in the history
* feat: working Msgpack

* CBOR

* feat: tests against malicious prepended bytes
  • Loading branch information
HashMapsData2Value authored Mar 8, 2024
1 parent 9226750 commit 32bce82
Show file tree
Hide file tree
Showing 3 changed files with 257 additions and 54 deletions.
9 changes: 4 additions & 5 deletions lib/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,13 @@ dependencies {
// https://mvnrepository.com/artifact/org.jetbrains.kotlinx
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.3")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-core:1.6.3")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-cbor:1.6.3")
// https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind
// https://mvnrepository.com/artifact/com.fasterxml.jackson.core
implementation("com.fasterxml.jackson.core:jackson-databind:2.16.1")

// https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-core
implementation("com.fasterxml.jackson.core:jackson-core:2.16.1")
implementation("com.fasterxml.jackson.core:jackson-databind:2.16.1")
implementation("org.msgpack:jackson-dataformat-msgpack:0.9.8")
implementation("com.fasterxml.jackson.dataformat:jackson-dataformat-cbor:2.16.1")




// Use the Kotlin JUnit 5 integration.
Expand Down
103 changes: 55 additions & 48 deletions lib/src/main/kotlin/bip32ed25519/Bip32Ed25519.kt
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import com.algorand.algosdk.transaction.SignedTransaction
import com.algorand.algosdk.transaction.Transaction
import com.algorand.algosdk.util.*
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.dataformat.cbor.databind.CBORMapper
import com.goterl.lazysodium.LazySodiumJava
import com.goterl.lazysodium.SodiumJava
import com.goterl.lazysodium.utils.LibraryLoader
Expand Down Expand Up @@ -53,6 +54,51 @@ data class SignMetadata(val encoding: Encoding, val schema: JSONSchema)

class Bip32Ed25519(private var seed: ByteArray) {
companion object {
val prefixes =
listOf(
"appID",
"arc",
"aB",
"aD",
"aO",
"aP",
"aS",
"AS",
"B256",
"BH",
"BR",
"CR",
"GE",
"KP",
"MA",
"MB",
"MX",
"NIC",
"NIR",
"NIV",
"NPR",
"OT1",
"OT2",
"PF",
"PL",
"Program",
"ProgData",
"PS",
"PK",
"SD",
"SpecialAddr",
"STIB",
"spc",
"spm",
"spp",
"sps",
"spv",
"TE",
"TG",
"TL",
"TX",
"VO"
)

// Load it once statically and use it for the lifetime of the application
val lazySodium: LazySodiumJava =
Expand Down Expand Up @@ -100,13 +146,18 @@ class Bip32Ed25519(private var seed: ByteArray) {
fun validateData(message: ByteArray, metadata: SignMetadata): Boolean {
// Check for Algorand tags
if (hasAlgorandTags(message)) {
return false
throw DataValidationException("Data contains Algorand tags")
}

val decoded: ByteArray =
when (metadata.encoding) {
Encoding.BASE64 -> Base64.getDecoder().decode(message)
// Encoding.CBOR ->
Encoding.CBOR ->
ObjectMapper()
.writeValueAsString(
CBORMapper().readValue(message, Map::class.java)
)
.toByteArray()
Encoding.MSGPACK ->
ObjectMapper()
.writeValueAsString(
Expand All @@ -120,7 +171,7 @@ class Bip32Ed25519(private var seed: ByteArray) {

// Check after decoding too
if (hasAlgorandTags(decoded)) {
return false
throw DataValidationException("Data contains Algorand tags")
}

// Validate with schema
Expand All @@ -134,51 +185,7 @@ class Bip32Ed25519(private var seed: ByteArray) {
fun hasAlgorandTags(message: ByteArray): Boolean {
// Prefixes taken from go-algorand node software code
// https://github.com/algorand/go-algorand/blob/master/protocol/hash.go
val prefixes =
listOf(
"appID",
"arc",
"aB",
"aD",
"aO",
"aP",
"aS",
"AS",
"B256",
"BH",
"BR",
"CR",
"GE",
"KP",
"MA",
"MB",
"MX",
"NIC",
"NIR",
"NIV",
"NPR",
"OT1",
"OT2",
"PF",
"PL",
"Program",
"ProgData",
"PS",
"PK",
"SD",
"SpecialAddr",
"STIB",
"spc",
"spm",
"spp",
"sps",
"spv",
"TE",
"TG",
"TL",
"TX",
"VO"
)

val messageString = String(message)
return prefixes.any { messageString.startsWith(it) }
}
Expand Down
199 changes: 198 additions & 1 deletion lib/src/test/kotlin/bip32ed25519/Bip32Ed25519Test.kt
Original file line number Diff line number Diff line change
Expand Up @@ -673,6 +673,86 @@ class Bip32Ed25519Test {
"validation succeeded, despite message not in line with schema"
}
}

@Test
fun validateCBORTest() {
// {"text":"Hello, World!"} --> A164746578746D48656C6C6F2C20576F726C6421
val cborData = "A164746578746D48656C6C6F2C20576F726C6421"
val message = helperHexStringToByteArray(cborData)
val jsonSchema =
"""
{
"type": "object",
"properties": {
"text": {
"type": "string"
}
},
"required": ["text"]
}
""".trimIndent()
val msgSchema = JSONSchema.parse(jsonSchema)
val metadata = SignMetadata(Encoding.CBOR, msgSchema)
val valid = Bip32Ed25519.validateData(message, metadata)
assert(valid) { "validation failed, message not in line with schema" }
}

@Test
fun validateCBORFailedTest() {
// {"text":"Hello, World!"} --> A164746578746D48656C6C6F2C20576F726C6421
val cborData = "A164746578746D48656C6C6F2C20576F726C6421"
val message = helperHexStringToByteArray(cborData)
val jsonSchema =
"""
{
"type": "object",
"properties": {
"text": {
"type": "integer"
}
},
"required": ["text"]
}
""".trimIndent()
val msgSchema = JSONSchema.parse(jsonSchema)
val metadata = SignMetadata(Encoding.CBOR, msgSchema)
val valid = Bip32Ed25519.validateData(message, metadata)
assert(!valid) { "validation failed, message not in line with schema" }
}

@Test
fun passThroughIllegalPrependFailedTest() {
val message = """{"text":"Hello, World!"}""".toByteArray()
val jsonSchema =
"""
{
"type": "object",
"properties": {
"text": {
"type": "string"
}
},
"required": ["text"]
}
""".trimIndent()
val metadata = SignMetadata(Encoding.NONE, JSONSchema.parse(jsonSchema))
val valid = Bip32Ed25519.validateData(message, metadata)
assert(valid) { "validation failed, message not in line with schema" }

try {
for (prefix in Bip32Ed25519.prefixes) {
Bip32Ed25519.validateData(
prefix.toByteArray() + message,
metadata
)
assert(false) {
"Illegal prepend unexpectedly did not throw error!"
}
}
} catch (e: DataValidationException) {
assert(true) { "Wrong exception was thrown" }
}
}
}

@TestInstance(TestInstance.Lifecycle.PER_CLASS)
Expand Down Expand Up @@ -872,6 +952,123 @@ class Bip32Ed25519Test {
}
}
}

@Test
fun signAuthChallengeCBORTest() {
// Corresponds to {"0": 255, "1": 1032, ..., "31": 27}
val CBORData =
"B820613018FF613118676132181A613318DE61340761351856613618376137185F613818C5613918B362313018F962313118FC62313218E862313318FC62313418B06231351827623136187062313718836231381834623139183F62323018D4623231183A62323218E262323318596232341840623235185E62323617623237185B6232381880623239188F623330187B623331181B"

val data = helperHexStringToByteArray(CBORData)
val pk = c.keyGen(KeyContext.Address, 0u, 0u, 0u)

val authSchema =
JSONSchema.parseFile("src/test/resources/auth.request.json")
val metadata = SignMetadata(Encoding.CBOR, authSchema)

val signature = c.signData(KeyContext.Address, 0u, 0u, 0u, data, metadata)

val isValid = c.verifyWithPublicKey(signature, data, pk)
assert(isValid) { "signature is not valid" }

val pk2 = c.keyGen(KeyContext.Address, 0u, 0u, 1u)
assert(!c.verifyWithPublicKey(signature, data, pk2)) {
"signature is unexpectedly valid"
}
}

@Test
fun signAuthChallengeCBORFailedTest() {
// Corresponds to {"0": 256, "1": 1032, ..., "31": 27} - 256 is too large

val CBORData =
"B820613018FF613118676132181A613318DE61340761351856613618376137185F613818C5613918B362313018F962313118FC62313218E862313318FC62313418B06231351827623136187062313718836231381834623139183F62323018D4623231183A62323218E262323318596232341840623235185E62323617623237185B6232381880623239188F623330187B623331181B"

val data = helperHexStringToByteArray(CBORData)

val authSchema =
JSONSchema.parseFile("src/test/resources/auth.request.json")
val metadata = SignMetadata(Encoding.CBOR, authSchema)

try {
c.signData(KeyContext.Address, 0u, 0u, 0u, data, metadata)
// If we get past this line, the test failed
throw (IllegalArgumentException(
"signData func did not throw DataValidationExcept despite bad message"
))
} catch (e: Exception) {
assert(e is DataValidationException) {
"signData did not throw an DataValidationException"
}
}
}

@Test
fun signIllegalPrependMsgFailedTest() {
val message = """{"text":"Hello, World!"}""".toByteArray()
val jsonSchema =
"""
{
"type": "object",
"properties": {
"text": {
"type": "string"
}
},
"required": ["text"]
}
""".trimIndent()
val metadata = SignMetadata(Encoding.NONE, JSONSchema.parse(jsonSchema))

val pk = c.keyGen(KeyContext.Address, 0u, 0u, 0u)

val signature =
c.signData(
KeyContext.Address,
0u,
0u,
0u,
message,
metadata
)

val isValid = c.verifyWithPublicKey(signature, message, pk)
assert(isValid) { "signature is not valid" }

val pk2 = c.keyGen(KeyContext.Address, 0u, 0u, 1u)
assert(!c.verifyWithPublicKey(signature, message, pk2)) {
"signature is unexpectedly valid"
}

try {
for (prefix in Bip32Ed25519.prefixes) {
c.signData(
KeyContext.Address,
0u,
0u,
0u,
prefix.toByteArray() + message,
metadata
)
assert(false) {
"Illegal prepend unexpectedly did not throw error!"
}
}
} catch (e: DataValidationException) {
assert(true) { "Wrong exception was thrown" }
}
try {
c.signData(KeyContext.Address, 0u, 0u, 0u, data, metadata)
// If we get past this line, the test failed
throw (IllegalArgumentException(
"signData func did not throw DataValidationExcept despite bad message"
))
} catch (e: Exception) {
assert(e is DataValidationException) {
"signData did not throw an DataValidationException"
}
}
}
}

@TestInstance(TestInstance.Lifecycle.PER_CLASS)
Expand Down Expand Up @@ -1064,4 +1261,4 @@ class Bip32Ed25519Test {
}
}
}
}
}

0 comments on commit 32bce82

Please sign in to comment.