Skip to content

Commit 7272e7b

Browse files
committed
Fix DER signature decoding when r or s < 32 bytes; pad and slice
`webauthn.decode_asn1_der_signature()` could return NULL when either INTEGER in the ASN.1 DER (r or s) was shorter than 32 bytes. The old logic sliced the last 32 bytes using a negative start when `len1/len2` < 32, yielding no rows. We now left-pad each INTEGER with zero bytes and then take a 32-byte tail: * `decode(repeat('00',32),'hex') || integer*_zeropadded` * `substring(... from len*+1 for 32)` This preserves the query structure and fixes short-length cases while remaining immutable and constant-time friendly. Other minor changes: * Use `CROSS JOIN LATERAL` in place of `JOIN LATERAL ... ON TRUE`. * Convert to SQL-body function with `BEGIN ATOMIC ... END`. Bump extension to 1.5 and add upgrade script. Files: * `FUNCTIONS/decode_asn1_der_signature.sql` — apply fix and SQL-body form. * `1.4--1.5.sql`, `webauthn--1.4--1.5.sql` — upgrade script. * `webauthn--1.5.sql` — regenerated extension script. * `webauthn.control` — `default_version` → 1.5. * `Makefile` — update DATA/targets and clean rules.
1 parent 4fdad79 commit 7272e7b

File tree

6 files changed

+651
-32
lines changed

6 files changed

+651
-32
lines changed

1.4--1.5.sql

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
CREATE OR REPLACE FUNCTION webauthn.decode_asn1_der_signature(asn1der bytea)
2+
RETURNS bytea
3+
IMMUTABLE
4+
BEGIN ATOMIC
5+
SELECT integer1||integer2
6+
FROM get_byte(asn1der,3) AS Q1(len1)
7+
CROSS JOIN LATERAL get_byte(asn1der,5+len1) AS Q2(len2)
8+
CROSS JOIN LATERAL substring(asn1der from 5 for len1) AS Q3(integer1_zeropadded)
9+
CROSS JOIN LATERAL substring(decode(repeat('00',32),'hex')||integer1_zeropadded from len1+1 for 32) AS Q4(integer1)
10+
CROSS JOIN LATERAL substring(asn1der from 5+len1+2 for len2) AS Q5(integer2_zeropadded)
11+
CROSS JOIN LATERAL substring(decode(repeat('00',32),'hex')||integer2_zeropadded from len2+1 for 32) AS Q6(integer2)
12+
WHERE get_byte(asn1der,0) = 48 /* 0x30 SEQUENCE */
13+
AND get_byte(asn1der,1) = length(asn1der)-2
14+
AND get_byte(asn1der,2) = 2 /* 0x02 INTEGER */
15+
AND get_byte(asn1der,5+len1-1) = 2 /* 0x02 INTEGER */
16+
AND length(integer1||integer2) = 64;
17+
END;
Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,17 @@
11
CREATE OR REPLACE FUNCTION webauthn.decode_asn1_der_signature(asn1der bytea)
22
RETURNS bytea
33
IMMUTABLE
4-
LANGUAGE sql
5-
AS $$
4+
BEGIN ATOMIC
65
SELECT integer1||integer2
76
FROM get_byte(asn1der,3) AS Q1(len1)
8-
JOIN LATERAL get_byte(asn1der,5+len1) AS Q2(len2) ON TRUE
9-
JOIN LATERAL substring(asn1der from 5 for len1) AS Q3(integer1_zeropadded) ON TRUE
10-
JOIN LATERAL substring(integer1_zeropadded from length(integer1_zeropadded)-31 for 32) AS Q4(integer1) ON TRUE
11-
JOIN LATERAL substring(asn1der from 5+len1+2 for len2) AS Q5(integer2_zeropadded) ON TRUE
12-
JOIN LATERAL substring(integer2_zeropadded from length(integer2_zeropadded)-31 for 32) AS Q6(integer2) ON TRUE
7+
CROSS JOIN LATERAL get_byte(asn1der,5+len1) AS Q2(len2)
8+
CROSS JOIN LATERAL substring(asn1der from 5 for len1) AS Q3(integer1_zeropadded)
9+
CROSS JOIN LATERAL substring(decode(repeat('00',32),'hex')||integer1_zeropadded from len1+1 for 32) AS Q4(integer1)
10+
CROSS JOIN LATERAL substring(asn1der from 5+len1+2 for len2) AS Q5(integer2_zeropadded)
11+
CROSS JOIN LATERAL substring(decode(repeat('00',32),'hex')||integer2_zeropadded from len2+1 for 32) AS Q6(integer2)
1312
WHERE get_byte(asn1der,0) = 48 /* 0x30 SEQUENCE */
1413
AND get_byte(asn1der,1) = length(asn1der)-2
1514
AND get_byte(asn1der,2) = 2 /* 0x02 INTEGER */
1615
AND get_byte(asn1der,5+len1-1) = 2 /* 0x02 INTEGER */
17-
AND length(integer1||integer2) = 64
18-
$$;
16+
AND length(integer1||integer2) = 64;
17+
END;

Makefile

Lines changed: 24 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@ DATA = \
88
webauthn--1.2--1.3.sql \
99
webauthn--1.3.sql \
1010
webauthn--1.3--1.4.sql \
11-
webauthn--1.4.sql
11+
webauthn--1.4.sql \
12+
webauthn--1.4--1.5.sql \
13+
webauthn--1.5.sql
1214

1315
REGRESS = ok \
1416
ok_user_handle \
@@ -24,49 +26,49 @@ REGRESS = ok \
2426
error_replay_attack \
2527
error_hijack_attack
2628

27-
EXTRA_CLEAN = webauthn--1.4.sql webauthn--1.3--1.4.sql
29+
EXTRA_CLEAN = webauthn--1.5.sql webauthn--1.4--1.5.sql
2830

2931
PG_CONFIG = pg_config
3032
PGXS := $(shell $(PG_CONFIG) --pgxs)
3133
include $(PGXS)
3234

33-
all: webauthn--1.4.sql webauthn--1.3--1.4.sql
35+
all: webauthn--1.5.sql webauthn--1.4--1.5.sql
3436

3537
SQL_SRC = \
36-
complain_header.sql \
38+
complain_header.sql \
3739
FUNCTIONS/raise_error.sql \
3840
ENUMS/credential_type.sql \
3941
ENUMS/user_verification_requirement.sql \
4042
ENUMS/attestation_conveyance_preference.sql \
41-
FUNCTIONS/base64url_decode.sql \
42-
FUNCTIONS/base64url_encode.sql \
43-
FUNCTIONS/cose_ecdha_to_pkcs.sql \
44-
FUNCTIONS/decode_asn1_der_signature.sql \
45-
FUNCTIONS/from_utf8.sql \
46-
FUNCTIONS/parse_authenticator_data.sql \
47-
FUNCTIONS/parse_attestation_object.sql \
48-
TABLES/credential_challenges.sql \
43+
FUNCTIONS/base64url_decode.sql \
44+
FUNCTIONS/base64url_encode.sql \
45+
FUNCTIONS/cose_ecdha_to_pkcs.sql \
46+
FUNCTIONS/decode_asn1_der_signature.sql \
47+
FUNCTIONS/from_utf8.sql \
48+
FUNCTIONS/parse_authenticator_data.sql \
49+
FUNCTIONS/parse_attestation_object.sql \
50+
TABLES/credential_challenges.sql \
4951
FUNCTIONS/credential_challenge_user_verification.sql \
5052
FUNCTIONS/credential_challenge_expiration.sql \
51-
TABLES/credentials.sql \
52-
TABLES/assertion_challenges.sql \
53+
TABLES/credentials.sql \
54+
TABLES/assertion_challenges.sql \
5355
FUNCTIONS/assertion_challenge_user_verification.sql \
5456
FUNCTIONS/assertion_challenge_expiration.sql \
5557
FUNCTIONS/credential_public_key.sql \
56-
TABLES/assertions.sql \
58+
TABLES/assertions.sql \
5759
FUNCTIONS/get_credential_creation_options.sql \
58-
FUNCTIONS/init_credential.sql \
59-
FUNCTIONS/store_credential.sql \
60-
FUNCTIONS/get_credentials.sql \
61-
FUNCTIONS/verify_assertion.sql \
60+
FUNCTIONS/init_credential.sql \
61+
FUNCTIONS/store_credential.sql \
62+
FUNCTIONS/get_credentials.sql \
63+
FUNCTIONS/verify_assertion.sql \
6264
FUNCTIONS/generate_test.sql
6365

64-
webauthn--1.4.sql: $(SQL_SRC)
66+
webauthn--1.5.sql: $(SQL_SRC)
6567
cat $^ > $@
6668

6769
SQL_SRC = \
6870
complain_header.sql \
69-
1.3--1.4.sql
71+
1.4--1.5.sql
7072

71-
webauthn--1.3--1.4.sql: $(SQL_SRC)
73+
webauthn--1.4--1.5.sql: $(SQL_SRC)
7274
cat $^ > $@

webauthn--1.4--1.5.sql

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
-- complain if script is sourced in psql, rather than via CREATE EXTENSION
2+
\echo Use "CREATE EXTENSION webauthn" to load this file. \quit
3+
CREATE OR REPLACE FUNCTION webauthn.decode_asn1_der_signature(asn1der bytea)
4+
RETURNS bytea
5+
IMMUTABLE
6+
BEGIN ATOMIC
7+
SELECT integer1||integer2
8+
FROM get_byte(asn1der,3) AS Q1(len1)
9+
CROSS JOIN LATERAL get_byte(asn1der,5+len1) AS Q2(len2)
10+
CROSS JOIN LATERAL substring(asn1der from 5 for len1) AS Q3(integer1_zeropadded)
11+
CROSS JOIN LATERAL substring(decode(repeat('00',32),'hex')||integer1_zeropadded from len1+1 for 32) AS Q4(integer1)
12+
CROSS JOIN LATERAL substring(asn1der from 5+len1+2 for len2) AS Q5(integer2_zeropadded)
13+
CROSS JOIN LATERAL substring(decode(repeat('00',32),'hex')||integer2_zeropadded from len2+1 for 32) AS Q6(integer2)
14+
WHERE get_byte(asn1der,0) = 48 /* 0x30 SEQUENCE */
15+
AND get_byte(asn1der,1) = length(asn1der)-2
16+
AND get_byte(asn1der,2) = 2 /* 0x02 INTEGER */
17+
AND get_byte(asn1der,5+len1-1) = 2 /* 0x02 INTEGER */
18+
AND length(integer1||integer2) = 64;
19+
END;

0 commit comments

Comments
 (0)