From 21ac516248246d17eb074534d3e795c1436d584d Mon Sep 17 00:00:00 2001 From: CPlusSharp Date: Sun, 14 Apr 2024 16:11:11 +0200 Subject: [PATCH] ECDSA JSON Web Signature format used e.g. by JWT --- src/core/operations/ECDSASign.mjs | 7 +++ .../operations/ECDSASignatureConversion.mjs | 33 ++++++++++++-- src/core/operations/ECDSAVerify.mjs | 28 ++++++++++-- tests/operations/tests/ECDSA.mjs | 45 +++++++++++++++++++ 4 files changed, 105 insertions(+), 8 deletions(-) diff --git a/src/core/operations/ECDSASign.mjs b/src/core/operations/ECDSASign.mjs index 0278eeeb..7b8f57f1 100644 --- a/src/core/operations/ECDSASign.mjs +++ b/src/core/operations/ECDSASign.mjs @@ -6,6 +6,8 @@ import Operation from "../Operation.mjs"; import OperationError from "../errors/OperationError.mjs"; +import { fromHex } from "../lib/Hex.mjs"; +import { toBase64 } from "../lib/Base64.mjs"; import r from "jsrsasign"; /** @@ -48,6 +50,7 @@ class ECDSASign extends Operation { value: [ "ASN.1 HEX", "P1363 HEX", + "JSON Web Signature", "Raw JSON" ] } @@ -86,6 +89,10 @@ class ECDSASign extends Operation { case "P1363 HEX": result = r.KJUR.crypto.ECDSA.asn1SigToConcatSig(signatureASN1Hex); break; + case "JSON Web Signature": + result = r.KJUR.crypto.ECDSA.asn1SigToConcatSig(signatureASN1Hex); + result = toBase64(fromHex(result), "A-Za-z0-9-_"); // base64url + break; case "Raw JSON": { const signatureRS = r.KJUR.crypto.ECDSA.parseSigHexInHexRS(signatureASN1Hex); result = JSON.stringify(signatureRS); diff --git a/src/core/operations/ECDSASignatureConversion.mjs b/src/core/operations/ECDSASignatureConversion.mjs index 49c5ef53..3f6c6bfb 100644 --- a/src/core/operations/ECDSASignatureConversion.mjs +++ b/src/core/operations/ECDSASignatureConversion.mjs @@ -6,6 +6,8 @@ import Operation from "../Operation.mjs"; import OperationError from "../errors/OperationError.mjs"; +import { fromBase64, toBase64 } from "../lib/Base64.mjs"; +import { fromHex, toHexFast } from "../lib/Hex.mjs"; import r from "jsrsasign"; /** @@ -33,6 +35,7 @@ class ECDSASignatureConversion extends Operation { "Auto", "ASN.1 HEX", "P1363 HEX", + "JSON Web Signature", "Raw JSON" ] }, @@ -42,6 +45,7 @@ class ECDSASignatureConversion extends Operation { value: [ "ASN.1 HEX", "P1363 HEX", + "JSON Web Signature", "Raw JSON" ] } @@ -69,22 +73,39 @@ class ECDSASignatureConversion extends Operation { } if (inputFormat === "Auto") { - if (input.substring(0, 2) === "30" && r.ASN1HEX.isASN1HEX(input)) { - inputFormat = "ASN.1 HEX"; - } else { - inputFormat = "P1363 HEX"; + const hexRegex = /^[a-f\d]{2,}$/gi; + if (hexRegex.test(input)) { + if (input.substring(0, 2) === "30" && r.ASN1HEX.isASN1HEX(input)) { + inputFormat = "ASN.1 HEX"; + } else { + inputFormat = "P1363 HEX"; + } } } + let inputBase64; + if (inputFormat === "Auto") { + try { + inputBase64 = fromBase64(input, "A-Za-z0-9-_", false); + inputFormat = "JSON Web Signature"; + } catch {} + } + // convert input to ASN.1 hex let signatureASN1Hex; switch (inputFormat) { + case "Auto": + throw new OperationError("Signature format could not be detected"); case "ASN.1 HEX": signatureASN1Hex = input; break; case "P1363 HEX": signatureASN1Hex = r.KJUR.crypto.ECDSA.concatSigToASN1Sig(input); break; + case "JSON Web Signature": + if (!inputBase64) inputBase64 = fromBase64(input, "A-Za-z0-9-_"); + signatureASN1Hex = r.KJUR.crypto.ECDSA.concatSigToASN1Sig(toHexFast(inputBase64)); + break; case "Raw JSON": { if (!inputJson) inputJson = JSON.parse(input); if (!inputJson.r) { @@ -107,6 +128,10 @@ class ECDSASignatureConversion extends Operation { case "P1363 HEX": result = r.KJUR.crypto.ECDSA.asn1SigToConcatSig(signatureASN1Hex); break; + case "JSON Web Signature": + result = r.KJUR.crypto.ECDSA.asn1SigToConcatSig(signatureASN1Hex); + result = toBase64(fromHex(result), "A-Za-z0-9-_"); // base64url + break; case "Raw JSON": { const signatureRS = r.KJUR.crypto.ECDSA.parseSigHexInHexRS(signatureASN1Hex); result = JSON.stringify(signatureRS); diff --git a/src/core/operations/ECDSAVerify.mjs b/src/core/operations/ECDSAVerify.mjs index 2cd38398..7e46e867 100644 --- a/src/core/operations/ECDSAVerify.mjs +++ b/src/core/operations/ECDSAVerify.mjs @@ -6,6 +6,8 @@ import Operation from "../Operation.mjs"; import OperationError from "../errors/OperationError.mjs"; +import { fromBase64 } from "../lib/Base64.mjs"; +import { toHexFast } from "../lib/Hex.mjs"; import r from "jsrsasign"; /** @@ -33,6 +35,7 @@ class ECDSAVerify extends Operation { "Auto", "ASN.1 HEX", "P1363 HEX", + "JSON Web Signature", "Raw JSON" ] }, @@ -85,22 +88,39 @@ class ECDSAVerify extends Operation { } if (inputFormat === "Auto") { - if (input.substring(0, 2) === "30" && r.ASN1HEX.isASN1HEX(input)) { - inputFormat = "ASN.1 HEX"; - } else { - inputFormat = "P1363 HEX"; + const hexRegex = /^[a-f\d]{2,}$/gi; + if (hexRegex.test(input)) { + if (input.substring(0, 2) === "30" && r.ASN1HEX.isASN1HEX(input)) { + inputFormat = "ASN.1 HEX"; + } else { + inputFormat = "P1363 HEX"; + } } } + let inputBase64; + if (inputFormat === "Auto") { + try { + inputBase64 = fromBase64(input, "A-Za-z0-9-_", false); + inputFormat = "JSON Web Signature"; + } catch {} + } + // convert to ASN.1 signature let signatureASN1Hex; switch (inputFormat) { + case "Auto": + throw new OperationError("Signature format could not be detected"); case "ASN.1 HEX": signatureASN1Hex = input; break; case "P1363 HEX": signatureASN1Hex = r.KJUR.crypto.ECDSA.concatSigToASN1Sig(input); break; + case "JSON Web Signature": + if (!inputBase64) inputBase64 = fromBase64(input, "A-Za-z0-9-_"); + signatureASN1Hex = r.KJUR.crypto.ECDSA.concatSigToASN1Sig(toHexFast(inputBase64)); + break; case "Raw JSON": { if (!inputJson) inputJson = JSON.parse(input); if (!inputJson.r) { diff --git a/tests/operations/tests/ECDSA.mjs b/tests/operations/tests/ECDSA.mjs index f0d4305a..560afc5c 100644 --- a/tests/operations/tests/ECDSA.mjs +++ b/tests/operations/tests/ECDSA.mjs @@ -31,6 +31,7 @@ gusgcAE8H6810fkJ8ZmTNiCCa6sLgR2vD1VNh2diirWgKPH4PVMKav5e6Q== sha256: { asn1: "3046022100e06905608a2fa7dbda9e284c2a7959dfb68fb527a5f003b2d7975ff135145127022100b6baa253793334f8b93ea1dd622bc600124d8090babd807efe3f77b8b324388d", p1363: "e06905608a2fa7dbda9e284c2a7959dfb68fb527a5f003b2d7975ff135145127b6baa253793334f8b93ea1dd622bc600124d8090babd807efe3f77b8b324388d", + jws: "4GkFYIovp9vanihMKnlZ37aPtSel8AOy15df8TUUUSe2uqJTeTM0-Lk-od1iK8YAEk2AkLq9gH7-P3e4syQ4jQ", json: `{"r":"00e06905608a2fa7dbda9e284c2a7959dfb68fb527a5f003b2d7975ff135145127","s":"00b6baa253793334f8b93ea1dd622bc600124d8090babd807efe3f77b8b324388d"}` } } @@ -260,6 +261,17 @@ TestRegister.addTests([ } ] }, + { + name: "ECDSA Verify: P-256 with SHA256 (JWS signature)", + input: P256.signature.sha256.jws, + expectedOutput: "Verified OK", + recipeConfig: [ + { + "op": "ECDSA Verify", + "args": ["Auto", "SHA-256", P256.publicKey, ASCII_TEXT] + } + ] + }, { name: "ECDSA Verify: P-256 with SHA256 (JSON signature)", input: P256.signature.sha256.json, @@ -339,6 +351,17 @@ TestRegister.addTests([ } ] }, + { + name: "ECDSA Signature Conversion: ASN.1 To JWS", + input: P256.signature.sha256.asn1, + expectedOutput: P256.signature.sha256.jws, + recipeConfig: [ + { + "op": "ECDSA Signature Conversion", + "args": ["Auto", "JSON Web Signature"] + } + ] + }, { name: "ECDSA Signature Conversion: ASN.1 To JSON", input: P256.signature.sha256.asn1, @@ -372,6 +395,17 @@ TestRegister.addTests([ } ] }, + { + name: "ECDSA Signature Conversion: P1363 To JWS", + input: P256.signature.sha256.p1363, + expectedOutput: P256.signature.sha256.jws, + recipeConfig: [ + { + "op": "ECDSA Signature Conversion", + "args": ["Auto", "JSON Web Signature"] + } + ] + }, { name: "ECDSA Signature Conversion: P1363 To JSON", input: P256.signature.sha256.p1363, @@ -405,6 +439,17 @@ TestRegister.addTests([ } ] }, + { + name: "ECDSA Signature Conversion: JSON To JWS", + input: P256.signature.sha256.json, + expectedOutput: P256.signature.sha256.jws, + recipeConfig: [ + { + "op": "ECDSA Signature Conversion", + "args": ["Auto", "JSON Web Signature"] + } + ] + }, { name: "ECDSA Signature Conversion: JSON To JSON", input: P256.signature.sha256.json,