From 04b614e1ad315ee52c6eeca804ee419ae2f825c3 Mon Sep 17 00:00:00 2001 From: kalsprite Date: Mon, 22 Jun 2026 22:41:42 -0700 Subject: [PATCH] update ecdsa to use asn1 --- core/crypto/ecdsa/ecdsa_asn1.odin | 184 +++++------------------------- 1 file changed, 26 insertions(+), 158 deletions(-) diff --git a/core/crypto/ecdsa/ecdsa_asn1.odin b/core/crypto/ecdsa/ecdsa_asn1.odin index 74c9d65e6..0bad6a263 100644 --- a/core/crypto/ecdsa/ecdsa_asn1.odin +++ b/core/crypto/ecdsa/ecdsa_asn1.odin @@ -1,17 +1,13 @@ package ecdsa +import "core:encoding/asn1" import secec "core:crypto/_weierstrass" -// ASN.1 format ECDSA signatures are`SEQUENCE { r INTEGER, s INTEGER }` -// this implements enough to generate/parse signatures. Eventually when -// we have a full ASN.1 DER library, these routines will be removed. +// ASN.1 ECDSA signatures are `SEQUENCE { r INTEGER, s INTEGER }`. These thin +// wrappers over core:encoding/asn1 generate/parse that structure the DER +// minimal-encoding rules. -@(private="file") -TAG_SEQUENCE :: 0x30 -@(private="file") -TAG_INTEGER :: 0x02 - -@(private,require_results) +@(private, require_results) generate_asn1_sig :: proc(r, s: ^$T, allocator := context.allocator) -> []byte { when T == secec.Scalar_p256r1 { SC_SZ :: secec.SC_SIZE_P256R1 @@ -21,163 +17,35 @@ generate_asn1_sig :: proc(r, s: ^$T, allocator := context.allocator) -> []byte { #panic("crypto/ecdsa: invalid curve") } - INT_TLP :: 3 // tag, tength, (optional) leading zero-byte - encode_uint :: proc(b: []byte) -> []byte { - b := b + r_buf, s_buf: [SC_SZ]byte = ---, --- + secec.sc_bytes(r_buf[:], r) + secec.sc_bytes(s_buf[:], s) - // DER requires minimal encoding. - off := INT_TLP - for v in b[off:] { - if v != 0 { - break - } - off += 1 - } - // If the sign big is set, add a leading zero. - if b[off] & 0x80 == 0x80 { - off -= 1 - b[off] = 0 - } - - // Encode the length (up to 127 octets, adequate for ECDSA). - l := len(b[off:]) - off -= 1 - b[off] = byte(l) - - // Encode the tag - off -= 1 - b[off] = TAG_INTEGER - - return b[off:] + sig, err := asn1.marshal( + asn1.sequence({asn1.integer_unsigned(r_buf[:]), asn1.integer_unsigned(s_buf[:])}), + allocator, + ) + if err != .None { + return nil } - - r_buf, s_buf: [INT_TLP+SC_SZ]byte = ---, --- - secec.sc_bytes(r_buf[INT_TLP:], r) - secec.sc_bytes(s_buf[INT_TLP:], s) - - r_bytes, s_bytes := encode_uint(r_buf[:]), encode_uint(s_buf[:]) - seq_len := len(r_bytes) + len(s_bytes) - - // WARNING: If secp521r1 support is added, this needs to support - // long-form length encoding. - ensure(seq_len <= 127, "BUG: crypto/ecdsa: signature length too large") - b := make([]byte, seq_len + 2, allocator) - b[0] = TAG_SEQUENCE - b[1] = byte(seq_len) - copy(b[2:], r_bytes) - copy(b[2+len(r_bytes):], s_bytes) - - return b + return sig } -@(private,require_results) +@(private, require_results) parse_asn1_sig :: proc(sig: []byte) -> (r, s: []byte, ok: bool) { - read_seq :: proc(b: []byte) -> (v: []byte, rest: []byte, ok: bool) { - b_len := len(b) - if b_len < 3 { - return nil, nil, false - } - if b[0] != TAG_SEQUENCE { - return nil, nil, false - } - seq_len, off: int - if b[1] & 0x80 == 0x80 { - if b[1] != 0x81 || b_len < 4 { // 2-length octets is sufficient for ecdsa. - return nil, nil, false - } - if b[2] & 0x80 == 0x80 || b[3] & 0x80 == 80 { - return nil, nil, false - } - seq_len = int(b[2]) * 127 + int(b[3]) - off = 4 - } else { - seq_len = int(b[1]) - off = 2 - } - if b_len - off < seq_len { - return nil, nil, false - } - return b[off:off+seq_len], b[off+seq_len:], true - } - - read_int :: proc(b: []byte) -> (v: []byte, rest: []byte, ok: bool) { - b_len := len(b) - if b_len < 3 { - return nil, nil, false - } - if b[0] != TAG_INTEGER { - return nil, nil, false - } - v_len := int(b[1]) - if v_len > 0x80 || b_len - 2 < v_len { // 127-bytes max. - return nil, nil, false - } - - return b[2:2+v_len], b[2+v_len:], true - } - - // SEQUENCE - seq_bytes, rest: []byte - seq_bytes, rest, ok = read_seq(sig) - if !ok { - return nil, nil, false - } - if len(rest) != 0 { + cur: asn1.Cursor + asn1.cursor_init(&cur, sig) + seq, e0 := asn1.read_sequence(&cur) + if e0 != .None || asn1.done(&cur) != .None { return nil, nil, false } - // INTEGER (r) - r, rest, ok = read_int(seq_bytes) - if !ok { + // r and s are unsigned; read_unsigned_integer_bytes validates the INTEGER + // and strips the DER sign octet, returning the magnitude as a view of sig. + rb, e1 := asn1.read_unsigned_integer_bytes(&seq) + sb, e2 := asn1.read_unsigned_integer_bytes(&seq) + if e1 != .None || e2 != .None || asn1.done(&seq) != .None { return nil, nil, false } - - // INTEGER (s) - s, rest, ok = read_int(rest) - if !ok { - return nil, nil, false - } - if len(rest) != 0 { - return nil, nil, false - } - - // DER requires a leading 0 if and only if (⟺) the sign bit of the leading byte - // is set to distinguish between positive and negative integers, - // and the minimal length representation. `r` and `s` are always - // going to be unsigned, so we validate malformed DER and strip - // the leading 0 as needed. - fixup_der_uint :: proc(b: []byte) -> ([]byte, bool) { - switch len(b) { - case 0: - // 0 length is invalid - return nil, false - case 1: - // Missing leading zero - if b[0] & 0x80 == 0x80 { - return nil, false - } - case: - if b[0] == 0 { - // Sign bit not set - if b[1] & 0x80 != 0x80 { - return nil, false - } - return b[1:], true - } else if b[0] & 0x80 == 0x80 { - // Missing leading zero - return nil, false - } - } - - return b, true - } - - if r, ok = fixup_der_uint(r); !ok { - return nil, nil, false - } - if s, ok = fixup_der_uint(s); !ok { - return nil, nil, false - } - - return r, s, true + return rb, sb, true }