From 7c21cc65bbec67720cf8852a961d39f90f717853 Mon Sep 17 00:00:00 2001 From: Zachary Newman Date: Wed, 12 Jul 2023 23:27:12 -0400 Subject: [PATCH] Add GQ proofs-of-authentication. Proof of concept for https://github.com/sigstore/fulcio/issues/1071 . To test: ``` $ go run main.go serve --port 5555 --ca ephemeralca --ct-log-url="" $ ./cosign sign-blob \ ~/git/cosign --fulcio-url http://localhost:5555 \ --insecure-skip-verify \ --tlog-upload=false \ --output-certificate crt.pem.b64 \ --output-signature sig \ --yes \ /dev/null $ step certificate inspect --format json <(base64 -d crt.pem.b64) Certificate: Data: Version: 3 (0x2) Serial Number: 591924787950286913218593311682257250005470899824 (0x67aed352de3c3eb6c935b5b93f64624b80576e70) Signature Algorithm: ECDSA-SHA256 Issuer: C=USA,ST=WA,L=Kirkland,STREET=767 6th St S,POSTALCODE=98033,O=sigstore Validity Not Before: Jul 13 03:02:12 2023 UTC Not After : Jul 13 03:12:12 2023 UTC Subject: Subject Public Key Info: Public Key Algorithm: ECDSA Public-Key: (256 bit) X: 5d:00:66:db:8e:01:2d:7a:51:26:b6:96:65:ad:fd: 66:28:db:c4:10:fd:6c:7e:b7:74:be:7e:38:c4:e8: bc:4d Y: df:ad:e0:e4:fa:94:d9:36:81:0e:96:77:31:13:bc: 13:ea:04:69:4c:4e:a0:62:1b:98:8c:1c:d3:f0:13: 3d:31 Curve: P-256 X509v3 extensions: X509v3 Key Usage: critical Digital Signature X509v3 Extended Key Usage: Code Signing X509v3 Subject Key Identifier: 22:47:23:46:A6:C4:8A:FE:DD:1E:E3:A0:39:B6:98:5B:13:61:83:B4 X509v3 Authority Key Identifier: keyid:93:54:F8:AF:1B:DC:2B:C2:71:FB:8F:E7:70:5F:08:86:14:AA:3B:79 X509v3 Subject Alternative Name: critical email:zjn@chainguard.dev Sigstore OIDC Issuer: https://accounts.google.com 1.3.6.1.4.1.57264.1.8: ..https://accounts.google.com 1.3.9901: ....eyJhbGciOiJSUzI1NiIsImtpZCI6ImEzNDUzNjE0YzVkOThhYThiNzQyYjJiYTVhZTFjNTY2NzFmYjgyYWYifQ.eyJpc3MiOiJodHRwczovL29hdXRoMi5zaWdzdG9yZS5kZXYvYXV0aCIsInN1YiI6IkNoVXhNVGd5TnpFek1qSTFNVFV6TmpFeE1EazJNekVTSDJoMGRIQnpPaVV5UmlVeVJtRmpZMjkxYm5SekxtZHZiMmRzWlM1amIyMCIsImF1ZCI6InNpZ3N0b3JlIiwiZXhwIjoxNjg5MjE3MzkyLCJpYXQiOjE2ODkyMTczMzIsIm5vbmNlIjoiMlNWMkdOdDRtV2t6STE1Wk5FZ0dTU3FMbHVCIiwiYXRfaGFzaCI6InZET1lZbnRlX0c4RkNGOFgxSnlqMHciLCJjX2hhc2giOiJJQkNJRmRLVUl3dnpxX290TjZBREZ3IiwiZW1haWwiOiJ6am5AY2hhaW5ndWFyZC5kZXYiLCJlbWFpbF92ZXJpZmllZCI6dHJ1ZSwiZmVkZXJhdGVkX2NsYWltcyI6eyJjb25uZWN0b3JfaWQiOiJodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20iLCJ1c2VyX2lkIjoiMTE4MjcxMzIyNTE1MzYxMTA5NjMxIn19 1.3.9902: ....{"Data":{"Round1":{"Pub":{"N":28070169755581386667437819450594913842446058566312248362488431550452327180771668694566958982231153239223207750514843228889849138467821049164767718727244839495922422496304381549328654085511887755075971204801723207114576081583911873129739320162443294174496792029835924675740072553464008368490661565630606061935141754560520595862703723981716412325623767556613166179401496695829458751124848584649668298938416237727401130044300603750914600510582900320509286362001468253982253031417502341212077586248942527869541614414254482042263206306154518897469365521680684942548823841919122471527179903649562711489277652019751462647739,"V":65537,"X":986236757547332986472011617696226561292849812918563355472727826767720188564083584387121625107510786855734801053524719833194566624465665316622563244215340671405971599343902468620306327831715457360719532421388780770165778156818229863337344187575566725786793391480600129482653072861971002459947277805295727097226389568776499707662505334062639449916265137796823793276300221537201727072401742985542559596685092673521228140822200236743113743661549252453726123450722876929538747702356573783116197523966334991563351853851212597377279504828784737035057630632270649616902422974306504680292018366321657348804190525202425699},"Msg1":{"T":2351076426232626148752483123315923449891310622328956880091101853373383247112012179424044324821006974774293124100582355517074320400151805903752206752519359906866266355131814824142359891140408021169969026923603493083607859590541997601652785145556988411763394573993032192436328983449099723590584914539545744196468848429652503889874096862905913537056029653464052940400576470910836246301669489448525151467864234919761978720209173001593225591389889308142322072838320986145158317496860440927831452769413779581145703178021032034600819119398863254219505712692893599595889886210602693471576404593924841607524137029909992803963}},"D":83,"Msg2":{"T":18959599301938102359557306258727591685947268304912402802229111659306878630842974344671290933616860374219837425749602925125131463212045780964350522234550666883161207109290182944695295226265223633341170476623254934623340647216243823514191972880710976273840382028518009858687372256758231291478829745548537842205646651604955581491737928734515931900834419145338861082397683997577789545907136662845403488217686102159618500589437050379925691843568643952251864205297536417828150834590850305495292849471744452792814804441968742070796386634978604153177013805125605023745430986337734931444997274902771795258148120953054587602138}}} Signature Algorithm: ECDSA-SHA256 30:45:02:21:00:eb:bc:81:81:fa:2d:c2:d5:15:04:58:15:d7: fa:97:9f:15:5a:ca:59:2f:e4:7f:6f:80:28:24:79:81:4b:48: c9:02:20:02:52:c1:e3:ea:16:ef:41:5c:a6:6d:54:1f:15:b5: 5f:30:ae:12:0f:58:df:87:04:c3:e8:6d:79:5d:e8:03:6d ``` In particular, look for the `1.3.9001` and `1.3.9002` extensions above, which now embed (respectively) a JWT token header/body (no signature!) and a GQ proof-of-knowledge-of-signature. This change includes: - Reimplementing RSA signature validation for JWTs. - Turning RSA signatures for JWTs into GQ proofs-of-knowledge-of-signatures. Involves implementing GQ scheme manually, along with a probably-insecure implementation of the Fiat-Shamir transform. - Adding the JWT and GQ proof into the certificate. This is crammed in awkardly, doing inefficient things like re-fetching the JWKs (and hard-coding the JWK URL for the issuer used for testing). --- go.mod | 8 ++ go.sum | 31 +++++++ pkg/certificate/extensions.go | 27 ++++++ pkg/challenges/challenges.go | 6 +- pkg/identity/email/issuer.go | 2 +- pkg/identity/email/principal.go | 69 ++++++++++++-- pkg/poa/gq.go | 157 ++++++++++++++++++++++++++++++++ pkg/poa/gq_test.go | 54 +++++++++++ pkg/poa/oidc.go | 72 +++++++++++++++ pkg/poa/oidc_test.go | 50 ++++++++++ pkg/poa/rsa.go | 54 +++++++++++ 11 files changed, 520 insertions(+), 10 deletions(-) create mode 100644 pkg/poa/gq.go create mode 100644 pkg/poa/gq_test.go create mode 100644 pkg/poa/oidc.go create mode 100644 pkg/poa/oidc_test.go create mode 100644 pkg/poa/rsa.go diff --git a/go.mod b/go.mod index 79f03de8d..8e93a3540 100644 --- a/go.mod +++ b/go.mod @@ -79,9 +79,11 @@ require ( github.com/cenkalti/backoff/v4 v4.2.1 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/common-nighthawk/go-figure v0.0.0-20210622060536-734e95fb86be // indirect + github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect github.com/go-jose/go-jose/v3 v3.0.0 // indirect github.com/go-logr/logr v1.2.4 // indirect github.com/go-logr/stdr v1.2.2 // indirect + github.com/goccy/go-json v0.10.2 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang-jwt/jwt/v4 v4.5.0 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect @@ -106,6 +108,12 @@ require ( github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/kylelemons/godebug v1.1.0 // indirect + github.com/lestrrat-go/backoff/v2 v2.0.8 // indirect + github.com/lestrrat-go/blackmagic v1.0.1 // indirect + github.com/lestrrat-go/httpcc v1.0.1 // indirect + github.com/lestrrat-go/iter v1.0.2 // indirect + github.com/lestrrat-go/jwx v1.2.26 // indirect + github.com/lestrrat-go/option v1.0.1 // indirect github.com/letsencrypt/boulder v0.0.0-20221109233200-85aa52084eaf // indirect github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect github.com/miekg/pkcs11 v1.1.1 // indirect diff --git a/go.sum b/go.sum index 360c5f865..b0c7298e4 100644 --- a/go.sum +++ b/go.sum @@ -142,6 +142,9 @@ github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46t github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/decred/dcrd/crypto/blake256 v1.0.1/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 h1:8UrgZ3GkP4i/CLijOJx79Yu+etlyjdBU4sfcs2WYQMs= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0= github.com/dnaeon/go-vcr v1.2.0 h1:zHCHvJYTMh1N7xnV7zf1m1GPBF9Ad0Jk/whtQ1663qI= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= @@ -177,6 +180,8 @@ github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/me github.com/go-test/deep v1.1.0 h1:WOcxcdHcvdgThNXjw0t76K42FXTU7HpNQWHpA2HHNlg= github.com/goadesign/goa v2.2.5+incompatible h1:SLgzk0V+QfFs7MVz9sbDHelbTDI9B/d4W7Hl5udTynY= github.com/goadesign/goa v2.2.5+incompatible/go.mod h1:d/9lpuZBK7HFi/7O0oXfwvdoIl+nx2bwKqctZe/lQao= +github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= +github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= @@ -330,6 +335,19 @@ github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/lestrrat-go/backoff/v2 v2.0.8 h1:oNb5E5isby2kiro9AgdHLv5N5tint1AnDVVf2E2un5A= +github.com/lestrrat-go/backoff/v2 v2.0.8/go.mod h1:rHP/q/r9aT27n24JQLa7JhSQZCKBBOiM/uP402WwN8Y= +github.com/lestrrat-go/blackmagic v1.0.1 h1:lS5Zts+5HIC/8og6cGHb0uCcNCa3OUt1ygh3Qz2Fe80= +github.com/lestrrat-go/blackmagic v1.0.1/go.mod h1:UrEqBzIR2U6CnzVyUtfM6oZNMt/7O7Vohk2J0OGSAtU= +github.com/lestrrat-go/httpcc v1.0.1 h1:ydWCStUeJLkpYyjLDHihupbn2tYmZ7m22BGkcvZZrIE= +github.com/lestrrat-go/httpcc v1.0.1/go.mod h1:qiltp3Mt56+55GPVCbTdM9MlqhvzyuL6W/NMDA8vA5E= +github.com/lestrrat-go/iter v1.0.2 h1:gMXo1q4c2pHmC3dn8LzRhJfP1ceCbgSiT9lUydIzltI= +github.com/lestrrat-go/iter v1.0.2/go.mod h1:Momfcq3AnRlRjI5b5O8/G5/BvpzrhoFTZcn06fEOPt4= +github.com/lestrrat-go/jwx v1.2.26 h1:4iFo8FPRZGDYe1t19mQP0zTRqA7n8HnJ5lkIiDvJcB0= +github.com/lestrrat-go/jwx v1.2.26/go.mod h1:MaiCdGbn3/cckbOFSCluJlJMmp9dmZm5hDuIkx8ftpQ= +github.com/lestrrat-go/option v1.0.0/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= +github.com/lestrrat-go/option v1.0.1 h1:oAzP2fvZGQKWkvHa1/SAcFolBEca1oN+mQ7eooNBEYU= +github.com/lestrrat-go/option v1.0.1/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= github.com/letsencrypt/boulder v0.0.0-20221109233200-85aa52084eaf h1:ndns1qx/5dL43g16EQkPV/i8+b3l5bYQwLeoSBe7tS8= github.com/letsencrypt/boulder v0.0.0-20221109233200-85aa52084eaf/go.mod h1:aGkAgvWY/IUcVFfuly53REpfv5edu25oij+qHRFaraA= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= @@ -438,6 +456,7 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8= github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= github.com/thales-e-security/pool v0.0.2 h1:RAPs4q2EbWsTit6tpzuvTFlgFRJ3S8Evf5gtvVDbmPg= @@ -511,6 +530,7 @@ golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220314234659-1baeb1ce4c0b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0= golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA= golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -547,6 +567,7 @@ golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -584,6 +605,8 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.12.0 h1:cfawfvKITfUsFCeJIHJrbSxpeu/E81khclypR0GVT50= golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -608,6 +631,7 @@ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -653,11 +677,15 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA= golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.10.0 h1:3R7pNqamzBraeqj/Tj8qt1aQ2HpmlC+Cx/qL/7hn4/c= golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -670,6 +698,8 @@ golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4= golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -729,6 +759,7 @@ golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.8.0 h1:vSDcovVPld282ceKgDimkRSC8kpaH1dgyc9UMzlt84Y= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/pkg/certificate/extensions.go b/pkg/certificate/extensions.go index 53d4d6626..890db09d4 100644 --- a/pkg/certificate/extensions.go +++ b/pkg/certificate/extensions.go @@ -52,6 +52,10 @@ var ( OIDBuildConfigDigest = asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 19} OIDBuildTrigger = asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 20} OIDRunInvocationURI = asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 21} + + // Experimental + OIDJWTNoSig = asn1.ObjectIdentifier{1, 3, 9901} + OIDGQProof = asn1.ObjectIdentifier{1, 3, 9902} ) // Extensions contains all custom x509 extensions defined by Fulcio @@ -128,6 +132,9 @@ type Extensions struct { // Run Invocation URL to uniquely identify the build execution. RunInvocationURI string // 1.3.6.1.4.1.57264.1.21 + + JWTNoSig string // 1.3.9901 + GQProof string // 1.3.9902 } func (e Extensions) Render() ([]pkix.Extension, error) { @@ -320,6 +327,26 @@ func (e Extensions) Render() ([]pkix.Extension, error) { Value: val, }) } + if e.JWTNoSig != "" { + val, err := asn1.MarshalWithParams(e.JWTNoSig, "utf8") + if err != nil { + return nil, err + } + exts = append(exts, pkix.Extension{ + Id: OIDJWTNoSig, + Value: val, + }) + } + if e.GQProof != "" { + val, err := asn1.MarshalWithParams(e.GQProof, "utf8") + if err != nil { + return nil, err + } + exts = append(exts, pkix.Extension{ + Id: OIDGQProof, + Value: val, + }) + } return exts, nil } diff --git a/pkg/challenges/challenges.go b/pkg/challenges/challenges.go index dda3298ff..cbc60806d 100644 --- a/pkg/challenges/challenges.go +++ b/pkg/challenges/challenges.go @@ -27,7 +27,7 @@ import ( "github.com/sigstore/fulcio/pkg/config" "github.com/sigstore/fulcio/pkg/identity" "github.com/sigstore/fulcio/pkg/identity/buildkite" - "github.com/sigstore/fulcio/pkg/identity/email" + // "github.com/sigstore/fulcio/pkg/identity/email" "github.com/sigstore/fulcio/pkg/identity/github" "github.com/sigstore/fulcio/pkg/identity/gitlabcom" "github.com/sigstore/fulcio/pkg/identity/kubernetes" @@ -63,8 +63,8 @@ func PrincipalFromIDToken(ctx context.Context, tok *oidc.IDToken) (identity.Prin principal, err = buildkite.JobPrincipalFromIDToken(ctx, tok) case config.IssuerTypeGitLabPipeline: principal, err = gitlabcom.JobPrincipalFromIDToken(ctx, tok) - case config.IssuerTypeEmail: - principal, err = email.PrincipalFromIDToken(ctx, tok) + // case config.IssuerTypeEmail: + // principal, err = email.PrincipalFromIDToken(ctx, tok) case config.IssuerTypeSpiffe: principal, err = spiffe.PrincipalFromIDToken(ctx, tok) case config.IssuerTypeGithubWorkflow: diff --git a/pkg/identity/email/issuer.go b/pkg/identity/email/issuer.go index 38c44d721..c0a1661d9 100644 --- a/pkg/identity/email/issuer.go +++ b/pkg/identity/email/issuer.go @@ -34,5 +34,5 @@ func (e *emailIssuer) Authenticate(ctx context.Context, token string) (identity. if err != nil { return nil, err } - return PrincipalFromIDToken(ctx, idtoken) + return PrincipalFromIDToken(ctx, idtoken, token) } diff --git a/pkg/identity/email/principal.go b/pkg/identity/email/principal.go index 33854057c..497229f39 100644 --- a/pkg/identity/email/principal.go +++ b/pkg/identity/email/principal.go @@ -16,24 +16,32 @@ package email import ( "context" + "crypto/rsa" "crypto/x509" + "encoding/json" + + // "encoding/json" "errors" "fmt" + "math/big" "github.com/asaskevich/govalidator" "github.com/coreos/go-oidc/v3/oidc" + "github.com/lestrrat-go/jwx/jwk" "github.com/sigstore/fulcio/pkg/certificate" "github.com/sigstore/fulcio/pkg/config" "github.com/sigstore/fulcio/pkg/identity" "github.com/sigstore/fulcio/pkg/oauthflow" + "github.com/sigstore/fulcio/pkg/poa" ) type principal struct { address string issuer string + rawJWT string } -func PrincipalFromIDToken(ctx context.Context, token *oidc.IDToken) (identity.Principal, error) { +func PrincipalFromIDToken(ctx context.Context, token *oidc.IDToken, rawJWT string) (identity.Principal, error) { emailAddress, emailVerified, err := oauthflow.EmailFromIDToken(token) if err != nil { return nil, err @@ -59,6 +67,7 @@ func PrincipalFromIDToken(ctx context.Context, token *oidc.IDToken) (identity.Pr return principal{ issuer: issuer, address: emailAddress, + rawJWT: rawJWT, }, nil } @@ -66,13 +75,61 @@ func (p principal) Name(_ context.Context) string { return p.address } -func (p principal) Embed(_ context.Context, cert *x509.Certificate) error { +type jwtHeader struct { + Alg string // `json`:"alg"` + Kid string // `json`:"kid"` +} + +func (p principal) Embed(ctx context.Context, cert *x509.Certificate) error { cert.EmailAddresses = []string{p.address} - var err error - cert.ExtraExtensions, err = certificate.Extensions{ - Issuer: p.issuer, - }.Render() + jwt := poa.ParseJWT(p.rawJWT) + + var header jwtHeader + err := json.Unmarshal(jwt.Header, &header) + if err != nil { + return err + } + + // TODO: use the cached JWKs. There's no easy access so we just re-request here. + set, err := jwk.Fetch(ctx, "https://oauth2.sigstore.dev/auth/keys") + if err != nil { + return err + } + var matchingKey jwk.Key + found := false + for it := set.Iterate(ctx); it.Next(ctx); { + pair := it.Pair() + key := pair.Value.(jwk.Key) + + if key.KeyID() == header.Kid { + found = true + matchingKey = key + } + } + + ext := certificate.Extensions{ + Issuer: p.issuer, + JWTNoSig: string(jwt.SignedPart()), + } + if found { + // Base64urlUInt-encoded + rsaPubJwk := matchingKey.(jwk.RSAPublicKey) + e := new(big.Int).SetBytes(rsaPubJwk.E()) + n := new(big.Int).SetBytes(rsaPubJwk.N()) + rsaPub := rsa.PublicKey{n, int(e.Int64())} + if !jwt.Verify(&rsaPub) { + return fmt.Errorf("uh oh") + } + + proof := jwt.GQProve(&rsaPub) + proofJson, jsonErr := json.Marshal(proof) + if jsonErr != nil { + return jsonErr + } + ext.GQProof = string(proofJson) + } + cert.ExtraExtensions, err = ext.Render() if err != nil { return err } diff --git a/pkg/poa/gq.go b/pkg/poa/gq.go new file mode 100644 index 000000000..722f0ae02 --- /dev/null +++ b/pkg/poa/gq.go @@ -0,0 +1,157 @@ +package poa + +import ( + "crypto/rand" + "fmt" + "io" + "math/big" + + "golang.org/x/crypto/sha3" +) + +// Public knowledge: +// - RSA modulus n +// - public RSA exponent v +// - preimage X +// +// Secret knowledge for prover: 𝐴 +// A, such that A^v = X mod N +// +// +// r <-$ Z_N^* +// T <- r^v mod n +// T -> +// d <-$ {0, .. v-1} +// d <- +// t <- A^d r mod n +// t -> +// t^v =? X^d T mod n + +type FiatShamirHashable interface { + FSHash() *io.Reader +} + +type GQPublic struct { + N *big.Int // modulus + V int // exponent + X *big.Int // message +} + +type GQPrivate struct { + A *big.Int // signature +} + +func checkInput(pub GQPublic, priv GQPrivate) { + if !CheckRSA(pub.N, pub.V, pub.X, priv.A) { + panic("uh oh") + } +} + +type aux1 struct { + r *big.Int +} + +type message1 struct { + T *big.Int +} + +func round1prover(rng io.Reader, pub GQPublic, priv GQPrivate) (aux1, message1) { + // r <-$ Z_N^* + var r *big.Int + for { + // Sample from Z_N^* + r, _ = rand.Int(rng, pub.N) + if new(big.Int).GCD(nil, nil, r, pub.N).Cmp(big.NewInt(1)) == 0 { + break + } + } + // T <- r^v mod n + T := new(big.Int).Exp(r, big.NewInt(int64(pub.V)), pub.N) + return aux1{r}, message1{T} +} + +type round1info struct { + Pub GQPublic + Msg1 message1 +} + +func (r *round1info) FSHash() io.Reader { + rng := sha3.NewShake256() + rng.Write([]byte(fmt.Sprintf("gq1:%v:%v:%v:%v", r.Pub.N, r.Pub.V, r.Pub.X, r.Msg1.T))) + return rng +} + +func round1verifier(rng io.Reader, info round1info) *big.Int { + // d <-$ {0, .. v-1} + d, _ := rand.Int(rng, big.NewInt(int64(info.Pub.V))) + return d +} + +type message2 struct { + T *big.Int +} + +func round2prover(pub GQPublic, priv GQPrivate, aux aux1, d *big.Int) message2 { + // t <- A^d r mod n + t := new(big.Int).Exp(priv.A, d, pub.N) + t.Mul(t, aux.r) + t.Mod(t, pub.N) + return message2{t} +} + +type round2info struct { + Round1 round1info + D *big.Int + Msg2 message2 +} + +func (r *round2info) FSHash() io.Reader { + xof := sha3.NewShake256() + xof.Write([]byte(fmt.Sprintf("gq2:%v:%v", r.Round1.FSHash(), r.Msg2.T))) + return xof +} + +func round2verifier(info round2info) bool { + // t^v =? X^d T mod n + t := info.Msg2.T + pub := info.Round1.Pub + T := info.Round1.Msg1.T + d := info.D + lhs := new(big.Int).Exp(t, big.NewInt(int64(pub.V)), pub.N) + rhs := new(big.Int).Exp(pub.X, d, pub.N) + rhs.Mul(rhs, T) + rhs.Mod(rhs, pub.N) + return (lhs.Cmp(rhs) == 0) +} + +type GQProof struct { + Data round2info +} + +func GQProve(pub GQPublic, priv GQPrivate) GQProof { + aux1, msg1 := round1prover(rand.Reader, pub, priv) + round1 := round1info{pub, msg1} + d := round1verifier(round1.FSHash(), round1) + + msg2 := round2prover(pub, priv, aux1, d) + return GQProof{round2info{round1, d, msg2}} +} + +func GQVerify(pub GQPublic, proof GQProof) bool { + // Correct input. + if !(proof.Data.Round1.Pub.N.Cmp(pub.N) == 0 && + proof.Data.Round1.Pub.V == pub.V && + proof.Data.Round1.Pub.X.Cmp(pub.X) == 0) { + return false + } + + // Take round1 from prover. + + // Correct first response from verifier. + d := round1verifier(proof.Data.Round1.FSHash(), proof.Data.Round1) + if !(d.Cmp(proof.Data.D) == 0) { + return false + } + + return round2verifier(proof.Data) +} diff --git a/pkg/poa/gq_test.go b/pkg/poa/gq_test.go new file mode 100644 index 000000000..8975df2c5 --- /dev/null +++ b/pkg/poa/gq_test.go @@ -0,0 +1,54 @@ +package poa + +import ( + "crypto/rand" + "encoding/json" + "math/big" + "testing" +) + +func Setup() (GQPublic, GQPrivate) { + n := big.NewInt(17 * 7) + v := 3 + A := big.NewInt(8) + X := new(big.Int).Exp(A, big.NewInt(int64(v)), n) + if !CheckRSA(n, v, X, A) { + panic("bad rsa") + } + pub := GQPublic{n, v, X} + priv := GQPrivate{A} + return pub, priv +} + +func TestGQInteractive(t *testing.T) { + rng := rand.Reader + + pub, priv := Setup() + checkInput(pub, priv) + + // Round 1. + aux1, msg1 := round1prover(rng, pub, priv) + round1 := round1info{pub, msg1} + d := round1verifier(rng, round1) + + // Round 2 + msg2 := round2prover(pub, priv, aux1, d) + if !round2verifier(round2info{round1, d, msg2}) { + t.Error("uh oh wuh oh") + } +} + +func TestGQNonInteractive(t *testing.T) { + pub, priv := Setup() + checkInput(pub, priv) + + proof := GQProve(pub, priv) + + proofJson, _ := json.Marshal(proof) + proofAfterRoundTrip := GQProof{} + _ = json.Unmarshal(proofJson, &proofAfterRoundTrip) + + if !GQVerify(pub, proofAfterRoundTrip) { + t.Error("GQ verification failed.") + } +} diff --git a/pkg/poa/oidc.go b/pkg/poa/oidc.go new file mode 100644 index 000000000..589a6918e --- /dev/null +++ b/pkg/poa/oidc.go @@ -0,0 +1,72 @@ +package poa + +import ( + "crypto/rsa" + "encoding/base64" + "strings" +) + +var encoding = base64.RawURLEncoding + +type JWT struct { + Header []byte + Body []byte + Signature []byte + raw string +} + +func decode(s string) []byte { + decoded := make([]byte, encoding.DecodedLen(len(s))) + encoding.Decode(decoded, []byte(s)) + return decoded +} + +func ParseJWT(jwt string) JWT { + parts := strings.SplitN(jwt, ".", 3) + return JWT{ + Header: decode(parts[0]), + Body: decode(parts[1]), + Signature: decode(parts[2]), + raw: jwt, + } +} + +func (j *JWT) SignedPart() []byte { + return []byte(j.raw[:strings.LastIndexByte(j.raw, '.')]) +} + +func (j *JWT) Verify(pub *rsa.PublicKey) bool { + return VerifyPKCS1v15SHA256(pub, j.SignedPart(), j.Signature) +} + +func (j *JWT) StripSignature() JWTNoSignature { + return JWTNoSignature{j.Header, j.Body, string(j.SignedPart())} +} + +func (j *JWT) GQProve(pub *rsa.PublicKey) GQProof { + N, E, m := PreparePKCS1v15SHA256ForRSACheck(pub, j.SignedPart()) + gqPub := GQPublic{N, E, m} + gqPriv := GQPrivate{ParseSig(j.Signature, N)} + return GQProve(gqPub, gqPriv) +} + +type JWTNoSignature struct { + Header []byte + Body []byte + raw string +} + +func ParseJWTNoSignature(jwt string) JWTNoSignature { + parts := strings.SplitN(jwt, ".", 2) + return JWTNoSignature{ + Header: decode(parts[0]), + Body: decode(parts[1]), + raw: jwt, + } +} + +func (j *JWTNoSignature) GQVerify(pub *rsa.PublicKey, proof GQProof) bool { + N, E, m := PreparePKCS1v15SHA256ForRSACheck(pub, []byte(j.raw)) + gqPub := GQPublic{N, E, m} + return GQVerify(gqPub, proof) +} diff --git a/pkg/poa/oidc_test.go b/pkg/poa/oidc_test.go new file mode 100644 index 000000000..58fb13a40 --- /dev/null +++ b/pkg/poa/oidc_test.go @@ -0,0 +1,50 @@ +package poa + +import ( + "crypto/rsa" + "crypto/x509" + "encoding/pem" + "testing" +) + +// Example from https://jwt.io/ +const ExampleJWT = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0.NHVaYe26MbtOYhSKkoKYdFVomg4i8ZJd8_-RU8VNbftc4TSMb4bXP3l3YlNWACwyXPGffz5aXHc6lty1Y2t4SWRqGteragsVdZufDn5BlnJl9pdR_kdVFUsra2rWKEofkZeIC4yWytE58sMIihvo9H1ScmmVwBcQP6XETqYd0aSHp1gOa9RdUPDvoXQ5oqygTqVtxaDr6wUFKrKItgBMzWIdNZ6y7O9E0DhEPTbE9rfBo6KTFsHAZnMg4k68CDp2woYIaXbmYTWcvbzIuHO7_37GT79XdIwkm95QJ7hYC9RiwrV7mesbY4PAahERJawntho0my942XheVLmGwLMBkQ" +const PubKey = `-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAu1SU1LfVLPHCozMxH2Mo +4lgOEePzNm0tRgeLezV6ffAt0gunVTLw7onLRnrq0/IzW7yWR7QkrmBL7jTKEn5u ++qKhbwKfBstIs+bMY2Zkp18gnTxKLxoS2tFczGkPLPgizskuemMghRniWaoLcyeh +kd3qqGElvW/VDL5AaWTg0nLVkjRo9z+40RQzuVaE8AkAFmxZzow3x+VJYKdjykkJ +0iT9wCS0DRTXu269V264Vf/3jvredZiKRkgwlL9xNAwxXFg0x/XFw005UWVRIkdg +cKWTjpBP2dPwVZ4WWC+9aGVd+Gyn1o0CLelf4rEjGoXbAAEgAqeGUxrcIlbjXfbc +mwIDAQAB +-----END PUBLIC KEY-----` + +func parseKey(t *testing.T, keyPem string) *rsa.PublicKey { + skDer, _ := pem.Decode([]byte(keyPem)) + key, err := x509.ParsePKIXPublicKey(skDer.Bytes) + if err != nil { + t.Fatal("Couldn't parse public key.") + } + return key.(*rsa.PublicKey) +} + +func TestRSAValidation(t *testing.T) { + rsaKey := parseKey(t, PubKey) + jwt := ParseJWT(ExampleJWT) + if !jwt.Verify(rsaKey) { + t.Fatal("RSA validation failed.") + } +} + +func TestOIDCGQ(t *testing.T) { + rsaKey := parseKey(t, PubKey) + jwt := ParseJWT(ExampleJWT) + if !jwt.Verify(rsaKey) { + t.Fatal("sig no validate") + } + proof := jwt.GQProve(rsaKey) + jwtNoSig := jwt.StripSignature() + if !jwtNoSig.GQVerify(rsaKey, proof) { + t.Error("gq proof no validate") + } +} diff --git a/pkg/poa/rsa.go b/pkg/poa/rsa.go new file mode 100644 index 000000000..8a516a294 --- /dev/null +++ b/pkg/poa/rsa.go @@ -0,0 +1,54 @@ +package poa + +import ( + "crypto/rsa" + "crypto/sha256" + "encoding/binary" + "math/big" +) + +// Check A^v = X mod N. +func CheckRSA(n *big.Int, v int, X, A *big.Int) bool { + return new(big.Int).Exp(A, big.NewInt(int64(v)), n).Cmp(X) == 0 +} + +func IntToBytes(i int) []byte { + b := make([]byte, 8) + binary.BigEndian.PutUint64(b, uint64(i)) + for len(b) > 1 && b[0] == 0 { + b = b[1:] + } + return b +} + +var prefix = []byte{0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x05, 0x00, 0x04, 0x20} + +func ParseSig(sigBytes []byte, N *big.Int) *big.Int { + return new(big.Int).SetBytes(sigBytes) +} + +func formatMsg(k int, msg []byte, N *big.Int) *big.Int { + hasher := sha256.New() + tLen := len(prefix) + hasher.Size() + // EM = 0x00 || 0x01 || PS || 0x00 || T + em := make([]byte, k) + em[1] = 1 + for i := 2; i < k-tLen-1; i++ { + em[i] = 0xff + } + copy(em[k-tLen:k-hasher.Size()], prefix) + hasher.Write([]byte(msg)) + copy(em[k-hasher.Size():k], hasher.Sum(nil)) + return new(big.Int).SetBytes(em) +} + +func VerifyPKCS1v15SHA256(pub *rsa.PublicKey, msg, sigBytes []byte) bool { + sig := ParseSig(sigBytes, pub.N) + m := formatMsg(pub.Size(), msg, pub.N) + return CheckRSA(pub.N, pub.E, m, sig) +} + +func PreparePKCS1v15SHA256ForRSACheck(pub *rsa.PublicKey, msg []byte) (*big.Int, int, *big.Int) { + m := formatMsg(pub.Size(), msg, pub.N) + return pub.N, pub.E, m +}