From 6f779f9e6bed3132fa54532eee83c04ddca2645f Mon Sep 17 00:00:00 2001 From: zzstoatzz Date: Sun, 24 May 2026 23:00:51 -0500 Subject: [PATCH] Accept standard JOSE ECDSA signatures --- src/internal/jose.zig | 42 ++++++++++++++++++++++++++++++++---------- 1 file changed, 32 insertions(+), 10 deletions(-) diff --git a/src/internal/jose.zig b/src/internal/jose.zig index 40d1a78..1fbf434 100644 --- a/src/internal/jose.zig +++ b/src/internal/jose.zig @@ -2,23 +2,25 @@ const std = @import("std"); const zat = @import("zat"); pub fn verifyEcdsaJwtSignature(allocator: std.mem.Allocator, alg: []const u8, signing_input: []const u8, signature: []const u8, public_key: []const u8) !void { + _ = allocator; if (std.mem.eql(u8, alg, "ES256")) { - zat.jwt.verifyP256(signing_input, signature, public_key) catch { - const normalized = try normalizeEcdsaHighS(allocator, signature, p256_order); - defer allocator.free(normalized); - try zat.jwt.verifyP256(signing_input, normalized, public_key); - }; + try verifyRawEcdsa(std.crypto.sign.ecdsa.EcdsaP256Sha256, signing_input, signature, public_key); } else if (std.mem.eql(u8, alg, "ES256K")) { - zat.jwt.verifySecp256k1(signing_input, signature, public_key) catch { - const normalized = try normalizeEcdsaHighS(allocator, signature, secp256k1_order); - defer allocator.free(normalized); - try zat.jwt.verifySecp256k1(signing_input, normalized, public_key); - }; + try verifyRawEcdsa(std.crypto.sign.ecdsa.EcdsaSecp256k1Sha256, signing_input, signature, public_key); } else { return error.UnsupportedAlgorithm; } } +fn verifyRawEcdsa(comptime Scheme: type, message: []const u8, signature: []const u8, public_key_raw: []const u8) !void { + if (signature.len != 64) return error.InvalidSignature; + if (public_key_raw.len != 33) return error.InvalidPublicKey; + + const sig = Scheme.Signature.fromBytes(signature[0..64].*); + const public_key = Scheme.PublicKey.fromSec1(public_key_raw) catch return error.InvalidPublicKey; + sig.verify(message, public_key) catch return error.SignatureVerificationFailed; +} + const p256_order: [32]u8 = .{ 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, @@ -81,3 +83,23 @@ test "JOSE ECDSA verifier normalizes high-S P-256 signatures" { const pk = kp.public_key.toCompressedSec1(); try verifyEcdsaJwtSignature(std.testing.allocator, "ES256", message, &high_s_sig, &pk); } + +test "JOSE ECDSA verifier accepts high-S secp256k1 signatures" { + const Scheme = std.crypto.sign.ecdsa.EcdsaSecp256k1Sha256; + const sk_bytes = [_]u8{ + 0x20, 0x1f, 0x1e, 0x1d, 0x1c, 0x1b, 0x1a, 0x19, + 0x18, 0x17, 0x16, 0x15, 0x14, 0x13, 0x12, 0x11, + 0x10, 0x0f, 0x0e, 0x0d, 0x0c, 0x0b, 0x0a, 0x09, + 0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, + }; + const message = "oauth client assertion"; + const sig = try zat.jwt.signSecp256k1(message, &sk_bytes); + + var high_s_sig = sig.bytes; + subtractBigEndian(high_s_sig[32..64], &secp256k1_order, sig.bytes[32..64]); + + const sk = try Scheme.SecretKey.fromBytes(sk_bytes); + const kp = try Scheme.KeyPair.fromSecretKey(sk); + const pk = kp.public_key.toCompressedSec1(); + try verifyEcdsaJwtSignature(std.testing.allocator, "ES256K", message, &high_s_sig, &pk); +} -- 2.51.2