From 44c16cf75f681d527ea822f3f81021c90a55d17a Mon Sep 17 00:00:00 2001 From: Joe Bryan Date: Tue, 28 Aug 2018 18:31:49 -0700 Subject: [PATCH] 5. moves :acme JWK, JWS and tests out of app --- app/acme.hoon | 414 +------------------------------------------- lib/jose.hoon | 213 +++++++++++++++++++++++ tests/lib/jose.hoon | 204 ++++++++++++++++++++++ 3 files changed, 419 insertions(+), 412 deletions(-) create mode 100644 lib/jose.hoon create mode 100644 tests/lib/jose.hoon diff --git a/app/acme.hoon b/app/acme.hoon index 4fc4451ca..d25231071 100644 --- a/app/acme.hoon +++ b/app/acme.hoon @@ -1,9 +1,8 @@ /- asn1 -/+ base64, der, primitive-rsa, *pkcs, tester +/+ base64, der, primitive-rsa, *pkcs, *jose, tester =, eyre =* rsa primitive-rsa -:: -:::: %zuse additions +:: XX move these to %zuse and dedupe libs and tests :: |% :: +en-base64url: url-safe base64 encoding, without padding @@ -21,201 +20,7 @@ ++ en |=(a=@u `octs`[(met 3 a) (swp 3 a)]) ++ de |=(a=octs `@u`(rev 3 p.a q.a)) -- -:: +en-json-sort: json encoding with sorted object keys :: -:: to be included in %zuse, with sorting optional? -:: -++ en-json-sort :: XX rename - |^ |=([sor=$-(^ ?) val=json] (apex val sor "")) - :: :: ++apex:en-json:html - ++ apex - =, en-json:html - |= {val/json sor/$-(^ ?) rez/tape} - ^- tape - ?~ val (weld "null" rez) - ?- -.val - $a - :- '[' - =. rez [']' rez] - !. - ?~ p.val rez - |- - ?~ t.p.val ^$(val i.p.val) - ^$(val i.p.val, rez [',' $(p.val t.p.val)]) - :: - $b (weld ?:(p.val "true" "false") rez) - $n (weld (trip p.val) rez) - $s - :- '"' - =. rez ['"' rez] - =+ viz=(trip p.val) - !. - |- ^- tape - ?~ viz rez - =+ hed=(jesc i.viz) - ?: ?=({@ $~} hed) - [i.hed $(viz t.viz)] - (weld hed $(viz t.viz)) - :: - $o - :- '{' - =. rez ['}' rez] - =/ viz - %+ sort ~(tap by p.val) - |=((pair) (sor (head p) (head q))) - ?~ viz rez - !. - |- ^+ rez - ?~ t.viz ^$(val [%s p.i.viz], rez [':' ^$(val q.i.viz)]) - =. rez [',' $(viz t.viz)] - ^$(val [%s p.i.viz], rez [':' ^$(val q.i.viz)]) - == - -- -:: -:: %/lib/jose -:: -:: |jwk: json representations of cryptographic keys (rfc7517) -:: -:: Url-safe base64 encoding of key parameters in big-endian byte order. -:: RSA-only for now -:: -++ jwk - |% - :: |en:jwk: encoding of json cryptographic keys - :: - ++ en - => |% - :: +numb:en:jwk: base64-url encode big-endian number - :: - ++ numb (corl en-base64url en:octn) - -- - |% - :: +pass:en:jwk: json encode public key - :: - ++ pass - |= k=key:rsa - ^- json - [%o (my kty+s+'RSA' n+s+(numb n.pub.k) e+s+(numb e.pub.k) ~)] - :: +ring:en:jwk: json encode private key - :: - ++ ring - |= k=key:rsa - ^- json - ~| %rsa-need-ring - ?> ?=(^ sek.k) - :- %o %- my :~ - kty+s+'RSA' - n+s+(numb n.pub.k) - e+s+(numb e.pub.k) - d+s+(numb d.u.sek.k) - p+s+(numb p.u.sek.k) - q+s+(numb q.u.sek.k) - == - -- - :: |de:jwk: decoding of json cryptographic keys - :: - ++ de - =, dejs-soft:format - => |% - :: +numb:de:jwk: parse base64-url big-endian number - :: - ++ numb (cu (cork de-base64url (lift de:octn)) so) - -- - |% - :: +pass:de:jwk: decode json public key - :: - ++ pass - %+ ci - =/ a (unit @ux) - |= [kty=@t n=a e=a] - ^- (unit key:rsa) - =/ pub (both n e) - ?~(pub ~ `[u.pub ~]) - (ot kty+(su (jest 'RSA')) n+numb e+numb ~) - :: +ring:de:jwk: decode json private key - :: - ++ ring - %+ ci - =/ a (unit @ux) - |= [kty=@t n=a e=a d=a p=a q=a] - ^- (unit key:rsa) - =/ pub (both n e) - =/ sek :(both d p q) - ?:(|(?=(~ pub) ?=(~ sek)) ~ `[u.pub sek]) - (ot kty+(su (jest 'RSA')) n+numb e+numb d+numb p+numb q+numb ~) - -- - :: |thumb:jwk: "thumbprint" json-encoded key (rfc7638) - :: - ++ thumb - |% - :: +pass:thumb:jwk: thumbprint json-encoded public key - :: - ++ pass - |= k=key:rsa - (en-base64url 32 (shax (crip (en-json-sort aor (pass:en k))))) - :: +ring:thumb:jwk: thumbprint json-encoded private key - :: - ++ ring !! - -- - -- -:: |jws: json web signatures (rfc7515) -:: -:: Note: flattened signature form only. -:: -++ jws - |% - :: +sign:jws: sign json value - :: - ++ sign - |= [k=key:rsa pro=json lod=json] - |^ ^- json - =. pro header - =/ protect=cord (encode pro) - =/ payload=cord (encode lod) - :- %o %- my :~ - protected+s+protect - payload+s+payload - signature+s+(sign protect payload) - == - :: +header:sign:jws: set signature algorithm in header - :: - ++ header - ?> ?=([%o *] pro) - ^- json - [%o (~(put by p.pro) %alg s+'RS256')] - :: +encode:sign:jws: encode json for signing - :: - :: Alphabetically sort object keys, url-safe base64 encode - :: the serialized json. - :: - ++ encode - |= jon=json - %- en-base64url - %- as-octt:mimes:html - (en-json-sort aor jon) - :: +sign:sign:jws: compute signature - :: - :: Url-safe base64 encode in big-endian byte order. - :: - ++ sign - |= [protect=cord payload=cord] - =/ sig=@ud (~(sign rs256 k) (rap 3 ~[protect '.' payload])) - =/ len=@ud (met 3 n.pub.k) - (en-base64url len (rev 3 len sig)) - -- - :: +verify:jws: verify signature - :: - ++ verify !! - -- -:: +eor: explicit sort order comparator -:: -:: Lookup :a and :b in :lit, and pass their indices to :com. -:: -++ eor - |= [com=$-([@ @] ?) lit=(list)] - |= [a=* b=*] - ^- ? - (fall (bind (both (find ~[a] lit) (find ~[b] lit)) com) |) :: |grab: acme api response json reparsers :: ++ grab @@ -1079,7 +884,6 @@ %rule install:effect %fake fake %none none - %test test == ++ none ^+ this @@ -1240,219 +1044,5 @@ :: if initialized, defer :: ?.(=(act *acct) this init) -:: -++ test - =, tester:tester - =/ eny eny.bow - :: non-deterministic for now - :: 0vhu.gp79o.hi7at.smp8u.g5hhr.u3rff.st8ms.q4dc2.hv5ls.tp5cp.10qds. - :: h9bpt.vlmm7.lh375.f6u9n.krqv8.5jcml.cujkr.v1uqv.cjhe5.nplta - |^ =/ out=tang - ;: weld - test-jwk - test-jws - test-jws-2 - == - ?~(out this ((slog out) this)) - :: - ++ test-jwk - :: rfc7638 section 3.1 - =/ n - :~ '0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2' - 'aiAFbWhM78LhWx4cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCi' - 'FV4n3oknjhMstn64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65Y' - 'GjQR0_FDW2QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n' - '91CbOpbISD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_x' - 'BniIqbw0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw' - == - =/ jk=json - :- %o %- my :~ - kty+s+'RSA' - n+s+(rap 3 n) - e+s+'AQAB' - == - =/ k (need (pass:de:jwk jk)) - ;: weld - %- expect-eq !> - :- jk - (pass:en:jwk k) - %- expect-eq !> - :- 'NzbLsXh8uDCcd-6MNwXF4W_7noWXFZAfHkxZsRGC9Xs' - (pass:thumb:jwk k) - == - :: - ++ test-jws - :: rfc7515 appendix 2 - =/ pt=@t - %+ rap 3 - :~ '4BzEEOtIpmVdVEZNCqS7baC4crd0pqnRH_5IB3jw3bcxGn6QLvnEtfdUdi' - 'YrqBdss1l58BQ3KhooKeQTa9AB0Hw_Py5PJdTJNPY8cQn7ouZ2KKDcmnPG' - 'BY5t7yLc1QlQ5xHdwW1VhvKn-nXqhJTBgIPgtldC-KDV5z-y2XDwGUc' - == - =/ qt=@t - %+ rap 3 - :~ 'uQPEfgmVtjL0Uyyx88GZFF1fOunH3-7cepKmtH4pxhtCoHqpWmT8YAmZxa' - 'ewHgHAjLYsp1ZSe7zFYHj7C6ul7TjeLQeZD_YwD66t62wDmpe_HlB-TnBA' - '-njbglfIsRLtXlnDzQkv5dTltRJ11BKBBypeeF6689rjcJIDEz9RWdc' - == - =/ nt=@t - %+ rap 3 - :~ 'ofgWCuLjybRlzo0tZWJjNiuSfb4p4fAkd_wWJcyQoTbji9k0l8W26mPddx' - 'HmfHQp-Vaw-4qPCJrcS2mJPMEzP1Pt0Bm4d4QlL-yRT-SFd2lZS-pCgNMs' - 'D1W_YpRPEwOWvG6b32690r2jZ47soMZo9wGzjb_7OMg0LOL-bSf63kpaSH' - 'SXndS5z5rexMdbBYUsLA9e-KXBdQOS-UTo7WTBEMa2R2CapHg665xsmtdV' - 'MTBQY4uDZlxvb3qCo5ZwKh9kG4LT6_I5IhlJH7aGhyxXFvUK-DWNmoudF8' - 'NAco9_h9iaGNj8q2ethFkMLs91kzk2PAcDTW9gb54h4FRWyuXpoQ' - == - =/ dt=@t - %+ rap 3 - :~ 'Eq5xpGnNCivDflJsRQBXHx1hdR1k6Ulwe2JZD50LpXyWPEAeP88vLNO97I' - 'jlA7_GQ5sLKMgvfTeXZx9SE-7YwVol2NXOoAJe46sui395IW_GO-pWJ1O0' - 'BkTGoVEn2bKVRUCgu-GjBVaYLU6f3l9kJfFNS3E0QbVdxzubSu3Mkqzjkn' - '439X0M_V51gfpRLI9JYanrC4D4qAdGcopV_0ZHHzQlBjudU2QvXt4ehNYT' - 'CBr6XCLQUShb1juUO1ZdiYoFaFQT5Tw8bGUl_x_jTj3ccPDVZFD9pIuhLh' - 'BOneufuBiB4cS98l2SR_RQyGWSeWjnczT0QU91p1DhOVRuOopznQ' - == - =/ jk=json - :- %o %- my :~ - kty+s+'RSA' - n+s+nt - e+s+'AQAB' - d+s+dt - p+s+pt - q+s+qt - == - =/ k=key:rsa (need (ring:de:jwk jk)) - =/ hed=json o+(my alg+s+'RS256' ~) - =/ hedt=@t 'eyJhbGciOiJSUzI1NiJ9' - =/ lod=json - :- %o %- my :~ - iss+s+'joe' - exp+n+'1300819380' - ['http://example.com/is_root' %b &] - == - =/ lodt=@t - %+ rap 3 - :~ 'eyJpc3MiOiJqb2UiLCJleHAiOjEzMDA4MTkzODAsImh0dHA' - '6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ' - == - :: rfc example includes whitespace in json serialization - =/ lodt-ws=@t - %+ rap 3 - :~ 'eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQo' - 'gImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ' - == - =/ inp-ws=@t - (rap 3 [hedt '.' lodt-ws ~]) - =/ exp-ws=@t - %+ rap 3 - :~ 'cC4hiUPoj9Eetdgtv3hF80EGrhuB__dzERat0XF9g2VtQgr9PJbu3XOiZj5RZmh7' - 'AAuHIm4Bh-0Qc_lF5YKt_O8W2Fp5jujGbds9uJdbF9CUAr7t1dnZcAcQjbKBYNX4' - 'BAynRFdiuB--f_nZLgrnbyTyWzO75vRK5h6xBArLIARNPvkSjtQBMHlb1L07Qe7K' - '0GarZRmB_eSN9383LcOLn6_dO--xi12jzDwusC-eOkHWEsqtFZESc6BfI7noOPqv' - 'hJ1phCnvWh6IeYI2w9QOYEUipUTI8np6LbgGY9Fs98rqVt5AXLIhWkWywlVmtVrB' - 'p0igcN_IoypGlUPQGe77Rw' - == - =/ lod-order=(list @t) ['iss' 'exp' 'http://example.com/is_root' ~] - ?> ?=(^ sek.k) - ;: weld - %- expect-eq !> - [jk (ring:en:jwk k)] - %- expect-eq !> - [n.pub.k `@ux`(mul p.u.sek.k q.u.sek.k)] - %- expect-eq !> - :- d.u.sek.k - `@ux`(~(inv fo (elcm:rsa (dec p.u.sek.k) (dec q.u.sek.k))) e.pub.k) - %- expect-eq !> - :- hedt - (en-base64url (as-octt:mimes:html (en-json-sort aor hed))) - %- expect-eq !> - :- lodt - (en-base64url (as-octt:mimes:html (en-json-sort (eor lte lod-order) lod))) - %- expect-eq !> - :- exp-ws - (en-base64url (en:octn (~(sign rs256 k) inp-ws))) - == - :: - ++ test-jws-2 - :: captured from an in-the-wild failure - :: relevant sha-256 has a significant leading zero - :: which was not being captured in the asn.1 digest ... - =/ kpem=wain - :~ '-----BEGIN RSA PRIVATE KEY-----' - 'MIIEogIBAAKCAQEAkmWLu+9gyzCbrGAHTFE6Hs7CtVQofONmpnhmE7JQkmdS+aph' - 'WwZQfp9p6RU6vSoBaPXD96uqMXhvoOXz9/Ub5TRwLmQzfHZdksfU3pEZ8qFMikZU' - 'p5v+CyBnLq9YR0VXN+/JVatmYb1hhC1k101X9m+IU3DR3U+kyCZnXuOd10xVX05H' - '0pXl+nI25bZyMJFnz1Xfw1rTnhtU/w7bgCWYdMii5jLkl5zfoY2gulpPu7QeYa4K' - '3fTqklDNFK7kQQ1l4O3461fbSO0cnG4t8Vk3026ageA54+Qx8O8UDi8k18Z1NF+B' - 'pbPUZn55/InuZ8iGyHBZ4GRFIPG0iOdWM7gHCwIDAQABAoIBAAMQN/9SS6MJMULq' - 'CsXHxyl5sHtXa/BgWLHP+j2/FtRX++EkR0s+ln2FobZa+l5Q9m4Ljn5PbqSMAFfM' - 'Y6u0hNyj9om04oOl8bILl4Vcvqgp51oFvAEGOW15/o69+6bS3aBx7cqwfnsivInr' - 'nIXDvHcyey3kh9WCKNx3rxNVgfuTCkw0+K2qXkMTh2c3Iz2efR2f78qbNWQcBe1+' - 's83fABafxACYuXzfOYoO01GBCJnHrmXxJVePLXwxLkLeJHOQJQgPnagVbUH4kbUp' - 'OLd9h1dOVYKpyVaxbQiAH3U/ekOXCCv18a47/PQSbueolzSzMzwVPSZdf+88lzuq' - 'ZZyDXDECgYEAk5zt4cO7X+8IIeNXx8/2pztT9WmC1kqw4RtInoVXm62K1B0pPndW' - 'm0nMVFEDuSwdn61G5amlaOT0dTFHlMFydC9H+1L5PMK7d+6ArSeAtMWoUhz+jkcO' - 'B9KoMfZ9CtP2r5589zDGir8kaY8Fia5Z7TohpJDidmuumgDabl+qH+kCgYEA/eP6' - 'lIGVHF8EIrfewjLM+8i1RE/hzItOpegrwDUVeYfZlPM59xUyC9REdgvmnTssxPcL' - '2+EB11wvcImSPLuwN0kXUkh9qZUkr9hvYlikALNH1f8WhCJ0kT6pUeA7LbjU4/bM' - 'fsgcOh1POW2piIMERl1TuNRZg7JdKuCJKax3qtMCgYB2dxcifOc/0qIAMGgeX/Rf' - 'ueljp03tlPvnbPIW5oSs19X27YBQNY44Cj4F3Q7T6WfM4k9nuYKacEUQWIBODgJA' - '5EEsniaQcOfrFGoIjQ9qBMdVPxe8L6I+/P0nO96Wdg4gW12HNIniiAw8+x9Co75f' - '+KtPW0ekKj9yMQUcV4I9IQKBgE06bruDmzbRFDH3WjQaPc4M5E6OOfH9IgRHVh+W' - 'Rhz8nMu5HJWzBdEhVV3PCuwi1uBnAV112RiIOwnxXuFIejam7ggics8Fxe4TWPZC' - 'Xki0QBKxEElLLcgMlnaITZf/1AovxU5/Uk6/IZ0nZV1X9RHuS4w6U6xCsiJbwH1D' - 'r/bvAoGAV/Vx+Z2BD7QhmHofu98OMW6EGSjWMgOI4iXdcQ80Urz9akHkOM4KGojq' - 'UDobbxxkJt1K5Dzux+vnp1siiIkcLdVdtMzqo7KcKYWonMqZmppNqIFCXQHscCRD' - 'r6f1TIjlurYrazLAkRsmjE5uYM13/E1UdxplWSkdCbivIWqoqTM=' - '-----END RSA PRIVATE KEY-----' - == - =/ k=key:rsa - (need (ring:de:pem:pkcs1 kpem)) - =/ kid=@t - 'https://acme-staging-v02.api.letsencrypt.org/acme/acct/6336694' - =/ non=@t - 'a5Pwh6GcuqRSvHTQouW96XNg3iiMORMkBf_wSLOf0M4' - =/ url=purl - :- [sec=%.y por=~ hot=[%.y p=/org/letsencrypt/api/acme-staging-v02]] - :_ query=~ - :- ext=~ - %+ weld - /acme/challenge - /'efJn0ywfjIi3M7yT-6H8Mdq85R2LnI8XsTG3DaaY8Gc'/'138087558' - =/ protected-header=json - :- %o %- my :~ - nonce+s+non - url+s+(crip (en-purl:html url)) - kid+s+kid - == - =/ bod=json - [%o ~] - =/ exp=json - =/ payload=@t 'e30' - =/ protected=@t - %+ rap 3 - :~ 'eyJhbGci' - 'OiJSUzI1NiIsImtpZCI6Imh0dHBzOi8vYWNtZS1zdGFnaW5nLXYwMi5hcGkubGV0c2' - 'VuY3J5cHQub3JnL2FjbWUvYWNjdC82MzM2Njk0Iiwibm9uY2UiOiJhNVB3aDZHY3Vx' - 'UlN2SFRRb3VXOTZYTmczaWlNT1JNa0JmX3dTTE9mME00IiwidXJsIjoiaHR0cHM6Ly' - '9hY21lLXN0YWdpbmctdjAyLmFwaS5sZXRzZW5jcnlwdC5vcmcvYWNtZS9jaGFsbGVu' - 'Z2UvZWZKbjB5d2ZqSWkzTTd5VC02SDhNZHE4NVIyTG5JOFhzVEczRGFhWThHYy8xMz' - 'gwODc1NTgifQ' - == - =/ signature=@t - %+ rap 3 - :~ 'cukOS_KIWTolvORyJoIu5eejdLoFi6xpd06Y6nW565zFMKZi44BepsWIZXw4yxYjxs' - '8xFdoKOxtXhBS5BT0mbkHSUGokAPTUiF5b1wjm00ZiKRYwnIotizsLPzHAJKwhMlFs' - 'x6oAu25mmremBgnNtVD_cskQBbkTBgiTL6alrkrmwxlP2gSqyX6uEO-UCY71QB_xYj' - '4IOoX2k0jdXJevXDAJSUWfs5cZkm8Ug_q4GVTRWhZmFHMnMzonmCC4Ui7nDa9oKJH5' - 'Npyn74FCcqbz111AK-Aul1dNhz3ojE1VOk3eVjH69lSGsaMleYR5fi60Jdc5ZbpPPy' - 't-CZRp1F0k6w' - == - [%o (my payload+s+payload protected+s+protected signature+s+signature ~)] - %- expect-eq !> - :- exp - (sign:jws k protected-header bod) - -- -- diff --git a/lib/jose.hoon b/lib/jose.hoon new file mode 100644 index 000000000..bd970890f --- /dev/null +++ b/lib/jose.hoon @@ -0,0 +1,213 @@ +/+ base64, primitive-rsa, *pkcs +=* rsa primitive-rsa +|% +:: +en-base64url: url-safe base64 encoding, without padding +:: +++ en-base64url + ~(en base64 | &) +:: +de-base64url: url-safe base64 decoding, without padding +:: +++ de-base64url + ~(de base64 | &) +:: |octn: encode/decode unsigned atoms as big-endian octet stream +:: +++ octn + |% + ++ en |=(a=@u `octs`[(met 3 a) (swp 3 a)]) + ++ de |=(a=octs `@u`(rev 3 p.a q.a)) + -- +:: +eor: explicit sort order comparator +:: +:: Lookup :a and :b in :lit, and pass their indices to :com. +:: +++ eor + |= [com=$-([@ @] ?) lit=(list)] + |= [a=* b=*] + ^- ? + (fall (bind (both (find ~[a] lit) (find ~[b] lit)) com) |) +:: +en-json-sort: json encoding with sorted object keys +:: +:: XX move %zuse with sorting optional? +:: +++ en-json-sort :: XX rename + |^ |=([sor=$-(^ ?) val=json] (apex val sor "")) + :: :: ++apex:en-json:html + ++ apex + =, en-json:html + |= {val/json sor/$-(^ ?) rez/tape} + ^- tape + ?~ val (weld "null" rez) + ?- -.val + $a + :- '[' + =. rez [']' rez] + !. + ?~ p.val rez + |- + ?~ t.p.val ^$(val i.p.val) + ^$(val i.p.val, rez [',' $(p.val t.p.val)]) + :: + $b (weld ?:(p.val "true" "false") rez) + $n (weld (trip p.val) rez) + $s + :- '"' + =. rez ['"' rez] + =+ viz=(trip p.val) + !. + |- ^- tape + ?~ viz rez + =+ hed=(jesc i.viz) + ?: ?=({@ $~} hed) + [i.hed $(viz t.viz)] + (weld hed $(viz t.viz)) + :: + $o + :- '{' + =. rez ['}' rez] + =/ viz + %+ sort ~(tap by p.val) + |=((pair) (sor (head p) (head q))) + ?~ viz rez + !. + |- ^+ rez + ?~ t.viz ^$(val [%s p.i.viz], rez [':' ^$(val q.i.viz)]) + =. rez [',' $(viz t.viz)] + ^$(val [%s p.i.viz], rez [':' ^$(val q.i.viz)]) + == + -- +:: %/lib/jose +:: +:: |jwk: json representations of cryptographic keys (rfc7517) +:: +:: Url-safe base64 encoding of key parameters in big-endian byte order. +:: RSA-only for now +:: +++ jwk + |% + :: |en:jwk: encoding of json cryptographic keys + :: + ++ en + => |% + :: +numb:en:jwk: base64-url encode big-endian number + :: + ++ numb (corl en-base64url en:octn) + -- + |% + :: +pass:en:jwk: json encode public key + :: + ++ pass + |= k=key:rsa + ^- json + [%o (my kty+s+'RSA' n+s+(numb n.pub.k) e+s+(numb e.pub.k) ~)] + :: +ring:en:jwk: json encode private key + :: + ++ ring + |= k=key:rsa + ^- json + ~| %rsa-need-ring + ?> ?=(^ sek.k) + :- %o %- my :~ + kty+s+'RSA' + n+s+(numb n.pub.k) + e+s+(numb e.pub.k) + d+s+(numb d.u.sek.k) + p+s+(numb p.u.sek.k) + q+s+(numb q.u.sek.k) + == + -- + :: |de:jwk: decoding of json cryptographic keys + :: + ++ de + =, dejs-soft:format + => |% + :: +numb:de:jwk: parse base64-url big-endian number + :: + ++ numb (cu (cork de-base64url (lift de:octn)) so) + -- + |% + :: +pass:de:jwk: decode json public key + :: + ++ pass + %+ ci + =/ a (unit @ux) + |= [kty=@t n=a e=a] + ^- (unit key:rsa) + =/ pub (both n e) + ?~(pub ~ `[u.pub ~]) + (ot kty+(su (jest 'RSA')) n+numb e+numb ~) + :: +ring:de:jwk: decode json private key + :: + ++ ring + %+ ci + =/ a (unit @ux) + |= [kty=@t n=a e=a d=a p=a q=a] + ^- (unit key:rsa) + =/ pub (both n e) + =/ sek :(both d p q) + ?:(|(?=(~ pub) ?=(~ sek)) ~ `[u.pub sek]) + (ot kty+(su (jest 'RSA')) n+numb e+numb d+numb p+numb q+numb ~) + -- + :: |thumb:jwk: "thumbprint" json-encoded key (rfc7638) + :: + ++ thumb + |% + :: +pass:thumb:jwk: thumbprint json-encoded public key + :: + ++ pass + |= k=key:rsa + (en-base64url 32 (shax (crip (en-json-sort aor (pass:en k))))) + :: +ring:thumb:jwk: thumbprint json-encoded private key + :: + ++ ring !! + -- + -- +:: |jws: json web signatures (rfc7515) +:: +:: Note: flattened signature form only. +:: +++ jws + |% + :: +sign:jws: sign json value + :: + ++ sign + |= [k=key:rsa pro=json lod=json] + |^ ^- json + =. pro header + =/ protect=cord (encode pro) + =/ payload=cord (encode lod) + :- %o %- my :~ + protected+s+protect + payload+s+payload + signature+s+(sign protect payload) + == + :: +header:sign:jws: set signature algorithm in header + :: + ++ header + ?> ?=([%o *] pro) + ^- json + [%o (~(put by p.pro) %alg s+'RS256')] + :: +encode:sign:jws: encode json for signing + :: + :: Alphabetically sort object keys, url-safe base64 encode + :: the serialized json. + :: + ++ encode + |= jon=json + %- en-base64url + %- as-octt:mimes:html + (en-json-sort aor jon) + :: +sign:sign:jws: compute signature + :: + :: Url-safe base64 encode in big-endian byte order. + :: + ++ sign + |= [protect=cord payload=cord] + =/ sig=@ud (~(sign rs256 k) (rap 3 ~[protect '.' payload])) + =/ len=@ud (met 3 n.pub.k) + (en-base64url len (rev 3 len sig)) + -- + :: +verify:jws: verify signature + :: + ++ verify !! + -- +-- diff --git a/tests/lib/jose.hoon b/tests/lib/jose.hoon new file mode 100644 index 000000000..85fd3f35c --- /dev/null +++ b/tests/lib/jose.hoon @@ -0,0 +1,204 @@ +/- asn1 +/+ primitive-rsa, *pkcs, *jose, tester +=, eyre +=* rsa primitive-rsa +|_ _tester:tester +++ test-jwk + :: rfc7638 section 3.1 + =/ n + :~ '0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2' + 'aiAFbWhM78LhWx4cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCi' + 'FV4n3oknjhMstn64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65Y' + 'GjQR0_FDW2QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n' + '91CbOpbISD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_x' + 'BniIqbw0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw' + == + =/ jk=json + :- %o %- my :~ + kty+s+'RSA' + n+s+(rap 3 n) + e+s+'AQAB' + == + =/ k (need (pass:de:jwk jk)) + ;: weld + %- expect-eq !> + :- jk + (pass:en:jwk k) + %- expect-eq !> + :- 'NzbLsXh8uDCcd-6MNwXF4W_7noWXFZAfHkxZsRGC9Xs' + (pass:thumb:jwk k) + == +:: +++ test-jws + :: rfc7515 appendix 2 + =/ pt=@t + %+ rap 3 + :~ '4BzEEOtIpmVdVEZNCqS7baC4crd0pqnRH_5IB3jw3bcxGn6QLvnEtfdUdi' + 'YrqBdss1l58BQ3KhooKeQTa9AB0Hw_Py5PJdTJNPY8cQn7ouZ2KKDcmnPG' + 'BY5t7yLc1QlQ5xHdwW1VhvKn-nXqhJTBgIPgtldC-KDV5z-y2XDwGUc' + == + =/ qt=@t + %+ rap 3 + :~ 'uQPEfgmVtjL0Uyyx88GZFF1fOunH3-7cepKmtH4pxhtCoHqpWmT8YAmZxa' + 'ewHgHAjLYsp1ZSe7zFYHj7C6ul7TjeLQeZD_YwD66t62wDmpe_HlB-TnBA' + '-njbglfIsRLtXlnDzQkv5dTltRJ11BKBBypeeF6689rjcJIDEz9RWdc' + == + =/ nt=@t + %+ rap 3 + :~ 'ofgWCuLjybRlzo0tZWJjNiuSfb4p4fAkd_wWJcyQoTbji9k0l8W26mPddx' + 'HmfHQp-Vaw-4qPCJrcS2mJPMEzP1Pt0Bm4d4QlL-yRT-SFd2lZS-pCgNMs' + 'D1W_YpRPEwOWvG6b32690r2jZ47soMZo9wGzjb_7OMg0LOL-bSf63kpaSH' + 'SXndS5z5rexMdbBYUsLA9e-KXBdQOS-UTo7WTBEMa2R2CapHg665xsmtdV' + 'MTBQY4uDZlxvb3qCo5ZwKh9kG4LT6_I5IhlJH7aGhyxXFvUK-DWNmoudF8' + 'NAco9_h9iaGNj8q2ethFkMLs91kzk2PAcDTW9gb54h4FRWyuXpoQ' + == + =/ dt=@t + %+ rap 3 + :~ 'Eq5xpGnNCivDflJsRQBXHx1hdR1k6Ulwe2JZD50LpXyWPEAeP88vLNO97I' + 'jlA7_GQ5sLKMgvfTeXZx9SE-7YwVol2NXOoAJe46sui395IW_GO-pWJ1O0' + 'BkTGoVEn2bKVRUCgu-GjBVaYLU6f3l9kJfFNS3E0QbVdxzubSu3Mkqzjkn' + '439X0M_V51gfpRLI9JYanrC4D4qAdGcopV_0ZHHzQlBjudU2QvXt4ehNYT' + 'CBr6XCLQUShb1juUO1ZdiYoFaFQT5Tw8bGUl_x_jTj3ccPDVZFD9pIuhLh' + 'BOneufuBiB4cS98l2SR_RQyGWSeWjnczT0QU91p1DhOVRuOopznQ' + == + =/ jk=json + :- %o %- my :~ + kty+s+'RSA' + n+s+nt + e+s+'AQAB' + d+s+dt + p+s+pt + q+s+qt + == + =/ k=key:rsa (need (ring:de:jwk jk)) + =/ hed=json o+(my alg+s+'RS256' ~) + =/ hedt=@t 'eyJhbGciOiJSUzI1NiJ9' + =/ lod=json + :- %o %- my :~ + iss+s+'joe' + exp+n+'1300819380' + ['http://example.com/is_root' %b &] + == + =/ lodt=@t + %+ rap 3 + :~ 'eyJpc3MiOiJqb2UiLCJleHAiOjEzMDA4MTkzODAsImh0dHA' + '6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ' + == + :: rfc example includes whitespace in json serialization + =/ lodt-ws=@t + %+ rap 3 + :~ 'eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQo' + 'gImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ' + == + =/ inp-ws=@t + (rap 3 [hedt '.' lodt-ws ~]) + =/ exp-ws=@t + %+ rap 3 + :~ 'cC4hiUPoj9Eetdgtv3hF80EGrhuB__dzERat0XF9g2VtQgr9PJbu3XOiZj5RZmh7' + 'AAuHIm4Bh-0Qc_lF5YKt_O8W2Fp5jujGbds9uJdbF9CUAr7t1dnZcAcQjbKBYNX4' + 'BAynRFdiuB--f_nZLgrnbyTyWzO75vRK5h6xBArLIARNPvkSjtQBMHlb1L07Qe7K' + '0GarZRmB_eSN9383LcOLn6_dO--xi12jzDwusC-eOkHWEsqtFZESc6BfI7noOPqv' + 'hJ1phCnvWh6IeYI2w9QOYEUipUTI8np6LbgGY9Fs98rqVt5AXLIhWkWywlVmtVrB' + 'p0igcN_IoypGlUPQGe77Rw' + == + =/ lod-order=(list @t) ['iss' 'exp' 'http://example.com/is_root' ~] + ?> ?=(^ sek.k) + ;: weld + %- expect-eq !> + [jk (ring:en:jwk k)] + %- expect-eq !> + [n.pub.k `@ux`(mul p.u.sek.k q.u.sek.k)] + %- expect-eq !> + :- d.u.sek.k + `@ux`(~(inv fo (elcm:rsa (dec p.u.sek.k) (dec q.u.sek.k))) e.pub.k) + %- expect-eq !> + :- hedt + (en-base64url (as-octt:mimes:html (en-json-sort aor hed))) + %- expect-eq !> + :- lodt + (en-base64url (as-octt:mimes:html (en-json-sort (eor lte lod-order) lod))) + %- expect-eq !> + :- exp-ws + (en-base64url (en:octn (~(sign rs256 k) inp-ws))) + == +:: +++ test-jws-2 + :: captured from an in-the-wild failure + :: relevant sha-256 has a significant leading zero + :: which was not being captured in the asn.1 digest ... + =/ kpem=wain + :~ '-----BEGIN RSA PRIVATE KEY-----' + 'MIIEogIBAAKCAQEAkmWLu+9gyzCbrGAHTFE6Hs7CtVQofONmpnhmE7JQkmdS+aph' + 'WwZQfp9p6RU6vSoBaPXD96uqMXhvoOXz9/Ub5TRwLmQzfHZdksfU3pEZ8qFMikZU' + 'p5v+CyBnLq9YR0VXN+/JVatmYb1hhC1k101X9m+IU3DR3U+kyCZnXuOd10xVX05H' + '0pXl+nI25bZyMJFnz1Xfw1rTnhtU/w7bgCWYdMii5jLkl5zfoY2gulpPu7QeYa4K' + '3fTqklDNFK7kQQ1l4O3461fbSO0cnG4t8Vk3026ageA54+Qx8O8UDi8k18Z1NF+B' + 'pbPUZn55/InuZ8iGyHBZ4GRFIPG0iOdWM7gHCwIDAQABAoIBAAMQN/9SS6MJMULq' + 'CsXHxyl5sHtXa/BgWLHP+j2/FtRX++EkR0s+ln2FobZa+l5Q9m4Ljn5PbqSMAFfM' + 'Y6u0hNyj9om04oOl8bILl4Vcvqgp51oFvAEGOW15/o69+6bS3aBx7cqwfnsivInr' + 'nIXDvHcyey3kh9WCKNx3rxNVgfuTCkw0+K2qXkMTh2c3Iz2efR2f78qbNWQcBe1+' + 's83fABafxACYuXzfOYoO01GBCJnHrmXxJVePLXwxLkLeJHOQJQgPnagVbUH4kbUp' + 'OLd9h1dOVYKpyVaxbQiAH3U/ekOXCCv18a47/PQSbueolzSzMzwVPSZdf+88lzuq' + 'ZZyDXDECgYEAk5zt4cO7X+8IIeNXx8/2pztT9WmC1kqw4RtInoVXm62K1B0pPndW' + 'm0nMVFEDuSwdn61G5amlaOT0dTFHlMFydC9H+1L5PMK7d+6ArSeAtMWoUhz+jkcO' + 'B9KoMfZ9CtP2r5589zDGir8kaY8Fia5Z7TohpJDidmuumgDabl+qH+kCgYEA/eP6' + 'lIGVHF8EIrfewjLM+8i1RE/hzItOpegrwDUVeYfZlPM59xUyC9REdgvmnTssxPcL' + '2+EB11wvcImSPLuwN0kXUkh9qZUkr9hvYlikALNH1f8WhCJ0kT6pUeA7LbjU4/bM' + 'fsgcOh1POW2piIMERl1TuNRZg7JdKuCJKax3qtMCgYB2dxcifOc/0qIAMGgeX/Rf' + 'ueljp03tlPvnbPIW5oSs19X27YBQNY44Cj4F3Q7T6WfM4k9nuYKacEUQWIBODgJA' + '5EEsniaQcOfrFGoIjQ9qBMdVPxe8L6I+/P0nO96Wdg4gW12HNIniiAw8+x9Co75f' + '+KtPW0ekKj9yMQUcV4I9IQKBgE06bruDmzbRFDH3WjQaPc4M5E6OOfH9IgRHVh+W' + 'Rhz8nMu5HJWzBdEhVV3PCuwi1uBnAV112RiIOwnxXuFIejam7ggics8Fxe4TWPZC' + 'Xki0QBKxEElLLcgMlnaITZf/1AovxU5/Uk6/IZ0nZV1X9RHuS4w6U6xCsiJbwH1D' + 'r/bvAoGAV/Vx+Z2BD7QhmHofu98OMW6EGSjWMgOI4iXdcQ80Urz9akHkOM4KGojq' + 'UDobbxxkJt1K5Dzux+vnp1siiIkcLdVdtMzqo7KcKYWonMqZmppNqIFCXQHscCRD' + 'r6f1TIjlurYrazLAkRsmjE5uYM13/E1UdxplWSkdCbivIWqoqTM=' + '-----END RSA PRIVATE KEY-----' + == + =/ k=key:rsa + (need (ring:de:pem:pkcs1 kpem)) + =/ kid=@t + 'https://acme-staging-v02.api.letsencrypt.org/acme/acct/6336694' + =/ non=@t + 'a5Pwh6GcuqRSvHTQouW96XNg3iiMORMkBf_wSLOf0M4' + =/ url=purl + :- [sec=%.y por=~ hot=[%.y p=/org/letsencrypt/api/acme-staging-v02]] + :_ query=~ + :- ext=~ + %+ weld + /acme/challenge + /'efJn0ywfjIi3M7yT-6H8Mdq85R2LnI8XsTG3DaaY8Gc'/'138087558' + =/ protected-header=json + :- %o %- my :~ + nonce+s+non + url+s+(crip (en-purl:html url)) + kid+s+kid + == + =/ bod=json + [%o ~] + =/ exp=json + =/ payload=@t 'e30' + =/ protected=@t + %+ rap 3 + :~ 'eyJhbGci' + 'OiJSUzI1NiIsImtpZCI6Imh0dHBzOi8vYWNtZS1zdGFnaW5nLXYwMi5hcGkubGV0c2' + 'VuY3J5cHQub3JnL2FjbWUvYWNjdC82MzM2Njk0Iiwibm9uY2UiOiJhNVB3aDZHY3Vx' + 'UlN2SFRRb3VXOTZYTmczaWlNT1JNa0JmX3dTTE9mME00IiwidXJsIjoiaHR0cHM6Ly' + '9hY21lLXN0YWdpbmctdjAyLmFwaS5sZXRzZW5jcnlwdC5vcmcvYWNtZS9jaGFsbGVu' + 'Z2UvZWZKbjB5d2ZqSWkzTTd5VC02SDhNZHE4NVIyTG5JOFhzVEczRGFhWThHYy8xMz' + 'gwODc1NTgifQ' + == + =/ signature=@t + %+ rap 3 + :~ 'cukOS_KIWTolvORyJoIu5eejdLoFi6xpd06Y6nW565zFMKZi44BepsWIZXw4yxYjxs' + '8xFdoKOxtXhBS5BT0mbkHSUGokAPTUiF5b1wjm00ZiKRYwnIotizsLPzHAJKwhMlFs' + 'x6oAu25mmremBgnNtVD_cskQBbkTBgiTL6alrkrmwxlP2gSqyX6uEO-UCY71QB_xYj' + '4IOoX2k0jdXJevXDAJSUWfs5cZkm8Ug_q4GVTRWhZmFHMnMzonmCC4Ui7nDa9oKJH5' + 'Npyn74FCcqbz111AK-Aul1dNhz3ojE1VOk3eVjH69lSGsaMleYR5fi60Jdc5ZbpPPy' + 't-CZRp1F0k6w' + == + [%o (my payload+s+payload protected+s+protected signature+s+signature ~)] + %- expect-eq !> + :- exp + (sign:jws k protected-header bod) +--