diff options
author | Guilhem Moulin <guilhem@fripost.org> | 2021-02-22 20:32:33 +0100 |
---|---|---|
committer | Guilhem Moulin <guilhem@fripost.org> | 2021-02-22 22:36:59 +0100 |
commit | 045d169339c5b973f0924269e6ca485e48de3668 (patch) | |
tree | 2e159653533e2a4a89360404e7bfa4f59d9d7bee | |
parent | 87fa9468a26c1902423839473049cd3325098c1a (diff) |
lacme-accountd: Refuse to sign JWS with an invalid Protected Header.
“The JWS Protected Header is a JSON object” — RFC 7515 sec. 2.
“The JWS Protected Header MUST include the following fields:
- "alg"
- "nonce"
- "url"
- either "jwk" or "kid"”
— RFC 8555 sec. 6.2.
-rw-r--r-- | Changelog | 1 | ||||
-rwxr-xr-x | lacme-accountd | 13 | ||||
-rw-r--r-- | tests/accountd-validate | 36 |
3 files changed, 50 insertions, 0 deletions
@@ -1,6 +1,7 @@ lacme (0.8.1) upstream; + lacme-accountd: improve log messages. + + lacme-accountd: refuse to sign JWS with an invalid Protected Header. - lacme: in the [accountd] config, let lacme-accountd(1) do the %-expansion for 'config', not lacme(8) when building the command. diff --git a/lacme-accountd b/lacme-accountd index 68d0f39..5478cc2 100755 --- a/lacme-accountd +++ b/lacme-accountd @@ -256,6 +256,19 @@ sub conn($$$) { } my $req = "header=base64url($header) playload=base64url($payload)"; + + eval { $header = JSON::->new->decode($header); }; + if ($@ or # couldn't decode (parse error) + # RFC 7515: not a JSON object + !defined($header) or ref($header) ne "HASH" or + # RFC 8555 sec. 6.2: the protected Header MUST include all these fields + grep !defined, @$header{qw/alg nonce url/} or + # RFC 8555 sec. 6.2: the protected header MUST include any of these fields + !grep defined, @$header{qw/jwk kid/}) { + info("[$id] NOSIGN [invalid JWS Protected Header] ", $req); + last; + } + my $sig = $SIGN->($data) // panic(); logmsg(noquiet => "[$id] SIGNED ", $req); $out->printflush( encode_base64url($sig), "\r\n" ) or warn "print: $!"; diff --git a/tests/accountd-validate b/tests/accountd-validate new file mode 100644 index 0000000..d4be5ee --- /dev/null +++ b/tests/accountd-validate @@ -0,0 +1,36 @@ +# JWS Signing Input (RFC 7515) validation + +# missing or empty protected header +printf "\\r\\n" | lacme-accountd --stdio 2>"$STDERR" +grepstderr -Fq "] NOSIGN [malformed JWS Protected Header]" +printf ".foo\\r\\n" | lacme-accountd --stdio 2>"$STDERR" +grepstderr -Fq "] NOSIGN [malformed JWS Protected Header]" + +# invalid base64url-encoded protected header +printf "foo/bar.baz\\r\\n" | lacme-accountd --stdio 2>"$STDERR" +grepstderr -Fq "] NOSIGN [malformed JWS Protected Header]" + +# missing payload +printf "foo\\r\\n" | lacme-accountd --stdio 2>"$STDERR" +grepstderr -Fq "] NOSIGN [malformed JWS Payload]" + +# invalid base64url-encoded payload +printf "foo.bar/baz\\r\\n" | lacme-accountd --stdio 2>"$STDERR" +grepstderr -Fq "] NOSIGN [malformed JWS Payload]" + +# invalid JWS Protected Header: not a JSON object; missing fields "alg", +# "nonce", "url", or either "jwk" or "kid" +for s in "null" "\"str\"" "{}" "{\"alg\":\"\",\"nonce\":\"\",\"url\":\"\"}" "{\"jwk\":{}}"; do + s="$(printf "%s" "$s" | base64 -w0 | sed "s/=*$//" | tr "+/" "-_")" + printf "%s.\\r\\n" "$s" | lacme-accountd --stdio 2>"$STDERR" + grepstderr -F "] NOSIGN [invalid JWS Protected Header]" +done + +# valid JWS Protected Header and Payload +h="{\"alg\":\"\",\"nonce\":\"\",\"url\":\"\",\"jwk\":{}}" +s="$(printf "%s" "$h" | base64 -w0 | sed "s/=*$//" | tr "+/" "-_")" +p="$(printf "%s" "JWS Payload" | base64 -w0 | sed "s/=*$//" | tr "+/" "-_")" +printf "%s.%s\\r\\n" "$s" "$p" | lacme-accountd --stdio 2>"$STDERR" +grepstderr -F "] SIGNED header=base64url($h) playload=base64url(JWS Payload)" + +# vim: set filetype=sh : |