diff options
author | Vivien Kraus <vivien@planete-kraus.eu> | 2020-12-06 20:06:32 +0100 |
---|---|---|
committer | Vivien Kraus <vivien@planete-kraus.eu> | 2021-05-11 00:38:50 +0200 |
commit | 5ff953aaa42250bfeae9cff59988dafcb6fe8185 (patch) | |
tree | 7b247d5abdc0c8b551b6aa9e2607d75b3e3252d7 | |
parent | f2c75420d982cd44ba67278b8ce01fb73438c865 (diff) |
Implement the resource server verification code
-rw-r--r-- | doc/manual.html | 54 | ||||
-rw-r--r-- | po/POTFILES.in | 1 | ||||
-rw-r--r-- | po/fr.po | 11 | ||||
-rw-r--r-- | po/webid-oidc.pot | 7 | ||||
-rw-r--r-- | src/scm/webid-oidc/Makefile.am | 6 | ||||
-rw-r--r-- | src/scm/webid-oidc/resource-server.scm | 85 | ||||
-rw-r--r-- | tests/Makefile.am | 3 | ||||
-rw-r--r-- | tests/resource-server.scm | 84 |
8 files changed, 242 insertions, 9 deletions
diff --git a/doc/manual.html b/doc/manual.html index 51d524b..d010685 100644 --- a/doc/manual.html +++ b/doc/manual.html @@ -981,6 +981,60 @@ generator. You can safely delete this directory, but you need to restart the program to actually change the seed. </p> + <h1>Running a Resource Server</h1> + <p> + A Solid server is the server that manages your data. It needs to + check that the proofs of possession are correct, and the + possessed key is signed by the identity provider. + </p> + <h2>The authenticator</h2> + <p> + In <emph>(webid-oidc jws)</emph>, the following function + gives a simple API for a web server: + </p> + <info:deffn type="function" + name="make-authenticator" + arguments="jti-list [#server-uri] [#current-time] [#http-get]"> + <p> + Create an authenticator, i.e. a function that takes a request + and request body and returns the webid of the authenticated + user, or <pre>#f</pre> if it is not authenticated. + </p> + <p> + To prevent replay attacks, each request is signed by the + client with a different unique padding value. If such a value + has already been seen, then the request must fail. + </p> + <p> + The authenticator expects the client to demonstrate the + possession of a key that the identity provider knows. So the + client creates a DPoP proof, targetted to a specific URI. In + order to check that the URI is correct, the authenticator + needs the public URI of the service. + </p> + <p> + The JTIs are checked within a small time frame. By default, + the system time will be used. Otherwise, you can customize the + <pre>current-time</pre> optional keyword argument, to pass a + thunk returning a time from <emph>(srfi srfi-19)</emph>. + </p> + <p> + You may want to customize the <info:var>http-get</info:var> + optional keyword argument to pass a function to replace + <pre>http-get</pre> from <emph>(http client)</emph>. This + function takes an URI and optional <pre>#:headers</pre> + arguments, makes the request, and return two values: the + response, and the response body. + </p> + <p> + This function, in + <emph>(webid-oidc resource-server)</emph>, returns a web + request handler, taking the request and request body, and + returning the subject of the access token. If an error + happens, it is thrown; the function always returns a valid + URI. + </p> + </info:deffn> <h1 type="appendix">GNU Free Documentation License</h1> <info:gfdl /> diff --git a/po/POTFILES.in b/po/POTFILES.in index ed66784..97cf2cf 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -9,3 +9,4 @@ src/hash/libwebidoidc-hash.c src/scm/webid-oidc/errors.scm src/scm/webid-oidc/identity-provider.scm src/scm/webid-oidc/authorization-page-unsafe.scm +src/scm/webid-oidc/resource-server.scm
\ No newline at end of file @@ -2,7 +2,7 @@ msgid "" msgstr "" "Project-Id-Version: webid-oidc 0.0.0\n" "Report-Msgid-Bugs-To: vivien@planete-kraus.eu\n" -"POT-Creation-Date: 2021-05-10 22:58+0200\n" +"POT-Creation-Date: 2021-05-10 22:59+0200\n" "PO-Revision-Date: 2021-05-10 14:31+0200\n" "Last-Translator: Vivien Kraus <vivien@planete-kraus.eu>\n" "Language-Team: French <vivien@planete-kraus.eu>\n" @@ -1087,6 +1087,11 @@ msgstr "" "<a href=~s>~a</a> peut maintenant s'identifier en votre nom. Vous devez " "toujours ajuster ses permissions." +#: src/scm/webid-oidc/resource-server.scm:68 +#, scheme-format +msgid "~a: authentication failure: ~a\n" +msgstr "~a : échec d’authentificationn : ~a\n" + #, scheme-format #~ msgid "" #~ "~a is neither an identity provider (because ~a) nor a webid (because ~a)" @@ -1116,10 +1121,6 @@ msgstr "" #~ msgid ", " #~ msgstr ", " -#, scheme-format -#~ msgid "~a: authentication failure: ~a\n" -#~ msgstr "~a : échec d’authentificationn : ~a\n" - #~ msgid "command-line|help" #~ msgstr "aide" diff --git a/po/webid-oidc.pot b/po/webid-oidc.pot index 2aafbac..87f9f50 100644 --- a/po/webid-oidc.pot +++ b/po/webid-oidc.pot @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: webid-oidc SNAPSHOT\n" "Report-Msgid-Bugs-To: vivien@planete-kraus.eu\n" -"POT-Creation-Date: 2021-05-10 22:58+0200\n" +"POT-Creation-Date: 2021-05-10 22:59+0200\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Language-Team: LANGUAGE <LL@li.org>\n" @@ -990,3 +990,8 @@ msgid "" "<a href=~s>~a</a> can now log in on your behalf. You still need to adjust " "permissions." msgstr "" + +#: src/scm/webid-oidc/resource-server.scm:68 +#, scheme-format +msgid "~a: authentication failure: ~a\n" +msgstr "" diff --git a/src/scm/webid-oidc/Makefile.am b/src/scm/webid-oidc/Makefile.am index 42c65b6..061af17 100644 --- a/src/scm/webid-oidc/Makefile.am +++ b/src/scm/webid-oidc/Makefile.am @@ -19,7 +19,8 @@ dist_webidoidcmod_DATA += \ %reldir%/authorization-endpoint.scm \ %reldir%/token-endpoint.scm \ %reldir%/identity-provider.scm \ - %reldir%/provider-confirmation.scm + %reldir%/provider-confirmation.scm \ + %reldir%/resource-server.scm webidoidcgo_DATA += \ %reldir%/errors.go \ @@ -42,6 +43,7 @@ webidoidcgo_DATA += \ %reldir%/authorization-endpoint.go \ %reldir%/token-endpoint.go \ %reldir%/identity-provider.go \ - %reldir%/provider-confirmation.go + %reldir%/provider-confirmation.go \ + %reldir%/resource-server.go EXTRA_DIST += %reldir%/ChangeLog diff --git a/src/scm/webid-oidc/resource-server.scm b/src/scm/webid-oidc/resource-server.scm new file mode 100644 index 0000000..25523ac --- /dev/null +++ b/src/scm/webid-oidc/resource-server.scm @@ -0,0 +1,85 @@ +(define-module (webid-oidc resource-server) + #:use-module (webid-oidc errors) + #:use-module (webid-oidc oidc-configuration) + #:use-module (webid-oidc provider-confirmation) + #:use-module (webid-oidc jwk) + #:use-module (webid-oidc dpop-proof) + #:use-module ((webid-oidc config) #:prefix cfg:) + #:use-module (webid-oidc jti) + #:use-module (webid-oidc access-token) + #:use-module (web request) + #:use-module (web response) + #:use-module (web uri) + #:use-module (web server) + #:use-module (ice-9 optargs) + #:use-module (ice-9 receive) + #:use-module (ice-9 i18n) + #:use-module (ice-9 getopt-long) + #:use-module (ice-9 suspendable-ports) + #:use-module (sxml simple) + #:use-module (srfi srfi-19)) + +(define (G_ text) + (let ((out (gettext text))) + (if (string=? out text) + ;; No translation, disambiguate + (car (reverse (string-split text #\|))) + out))) + +(define*-public (make-authenticator jti-list + #:key + (server-uri #f) + (current-time current-time) + (http-get http-get)) + (unless (and server-uri (uri? server-uri)) + (error "You need to pass #:server-uri URI where URI is the public URI of the server, as a (web uri).")) + (lambda (request request-body) + (let ((headers (request-headers request)) + (uri (request-uri request)) + (method (request-method request)) + (current-time + (let ((t current-time)) + (when (thunk? t) + (set! t (t))) + (when (integer? t) + (set! t (make-time time-utc 0 t))) + (when (time? t) + (set! t (time-utc->date t))) + t))) + (let ((authz (assoc-ref headers 'authorization)) + (dpop (assoc-ref headers 'dpop)) + (full-uri (build-uri (uri-scheme server-uri) + #:userinfo (uri-userinfo server-uri) + #:host (uri-host server-uri) + #:port (uri-port server-uri) + #:path (string-append + "/" + (encode-and-join-uri-path + (append + (split-and-decode-uri-path (uri-path server-uri)) + (split-and-decode-uri-path + (uri-path uri)))))))) + (and authz dpop + (eq? (car authz) 'dpop) + (with-exception-handler + (lambda (error) + (format (current-error-port) + (G_ "~a: authentication failure: ~a\n") + (date->string current-time) + (error->str error)) + #f) + (lambda () + (let* ((access-token + (access-token-decode + (symbol->string (cadr authz)) + #:http-get http-get)) + (cnf/jkt (access-token-cnf/jkt access-token)) + (dpop-proof + (dpop-proof-decode + current-time jti-list method full-uri + dpop cnf/jkt))) + (let ((subject (access-token-webid access-token)) + (issuer (access-token-iss access-token))) + (confirm-provider subject issuer #:http-get http-get) + subject))) + #:unwind? #t)))))) diff --git a/tests/Makefile.am b/tests/Makefile.am index ba64f00..0ea5390 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -36,7 +36,8 @@ TESTS = %reldir%/load-library.scm \ %reldir%/authorization-endpoint-submit-form.scm \ %reldir%/token-endpoint-issue.scm \ %reldir%/token-endpoint-refresh.scm \ - %reldir%/provider-confirmation.scm + %reldir%/provider-confirmation.scm \ + %reldir%/resource-server.scm EXTRA_DIST += $(TESTS) %reldir%/ChangeLog diff --git a/tests/resource-server.scm b/tests/resource-server.scm new file mode 100644 index 0000000..52a08b7 --- /dev/null +++ b/tests/resource-server.scm @@ -0,0 +1,84 @@ +(use-modules (webid-oidc provider-confirmation) + (webid-oidc jti) + (webid-oidc jwk) + (webid-oidc jws) + (webid-oidc oidc-configuration) + (webid-oidc access-token) + (webid-oidc dpop-proof) + (webid-oidc resource-server) + (webid-oidc testing) + (web uri) + (web request) + (srfi srfi-19) + (web response) + (ice-9 optargs) + (ice-9 receive)) + +(with-test-environment + "resource-server" + (lambda () + (define jti (make-jti-list)) + (define client-key (generate-key #:n-size 2048)) + (define idp-key (generate-key #:n-size 2048)) + (define jwks (make-jwks (list idp-key))) + (define jwks-uri (string->uri "https://identity.provider/keys")) + (define oidc-config + (make-oidc-configuration + jwks-uri + (string->uri "https://identity.provider/authorize") + (string->uri "https://identity.provider/token"))) + (define oidc-config-uri + (string->uri + "https://identity.provider/.well-known/openid-configuration")) + (define subject (string->uri "https://identity.provider/subject#me")) + (define* (http-get uri #:key (headers '())) + (define exp (time-utc->date (make-time time-utc 0 3600))) + (cond ((equal? uri oidc-config-uri) + (serve-oidc-configuration exp oidc-config)) + ((equal? uri jwks-uri) + (serve-jwks exp jwks)) + (else (exit 1)))) + (define access-token + (issue-access-token + idp-key + #:alg 'RS256 + #:webid subject + #:iss "https://identity.provider" + #:iat 10 + #:exp 3610 + #:client-key client-key + #:client-id "https://client")) + (define uri (string->uri "https://resource.server/resource")) + (define server-uri (string->uri "https://resource.server/")) + (define method 'GET) + (define dpop-proof + (issue-dpop-proof + client-key + #:alg 'RS256 + #:htm method + #:htu uri + #:iat (time-utc->date (make-time time-utc 0 15)))) + (define rq + (call-with-input-string + (format #f "GET /resource HTTP/1.1\r\n\ +Host: resource.server\r\n\ +User-Agent: Test Suite\r\n\ +Upgrade-Insecure-Requests: 1\r\n\ +Cache-Control: max-age=0\r\n\ +Authorization: DPoP ~a\r\n\ +DPoP: ~a\r\n\r\n" + access-token + dpop-proof) + read-request)) + (define rq-body "") + (define authenticator + (make-authenticator + jti + #:server-uri server-uri + #:current-time (lambda () (make-time time-utc 0 20)) + #:http-get http-get)) + (define parsed (authenticator rq rq-body)) + (unless (uri? parsed) + (exit 2)) + (unless (equal? parsed subject) + (exit 3)))) |