summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorVivien Kraus <vivien@planete-kraus.eu>2021-09-20 11:25:29 +0200
committerVivien Kraus <vivien@planete-kraus.eu>2021-09-21 22:28:51 +0200
commite910b3ba2ded990a5193f7ea0cfad525332e4171 (patch)
treeb04e74e7c06e0a0fde5edd7ac0b8773db94cd515
parentdcd329af1ec765ca0fac97ef2dc18a3177d34083 (diff)
JWS: use GOOPS
-rw-r--r--doc/disfluid.texi965
-rw-r--r--po/disfluid.pot620
-rw-r--r--po/fr.po951
-rw-r--r--src/scm/webid-oidc/access-token.scm410
-rw-r--r--src/scm/webid-oidc/authorization-code.scm255
-rw-r--r--src/scm/webid-oidc/authorization-endpoint.scm2
-rw-r--r--src/scm/webid-oidc/client.scm11
-rw-r--r--src/scm/webid-oidc/client/accounts.scm26
-rw-r--r--src/scm/webid-oidc/dpop-proof.scm475
-rw-r--r--src/scm/webid-oidc/example-app.scm1
-rw-r--r--src/scm/webid-oidc/jws.scm481
-rw-r--r--src/scm/webid-oidc/oidc-id-token.scm361
-rw-r--r--src/scm/webid-oidc/resource-server.scm28
-rw-r--r--src/scm/webid-oidc/token-endpoint.scm45
-rw-r--r--tests/Makefile.am3
-rw-r--r--tests/authorization-endpoint-submit-form.scm6
-rw-r--r--tests/dpop-proof-iat-in-future.scm17
-rw-r--r--tests/dpop-proof-iat-too-late.scm23
-rw-r--r--tests/dpop-proof-invalid-ath.scm34
-rw-r--r--tests/dpop-proof-no-ath.scm19
-rw-r--r--tests/dpop-proof-no-explicit-exp.scm86
-rw-r--r--tests/dpop-proof-no-explicit-iat.scm83
-rw-r--r--tests/dpop-proof-replay.scm23
-rw-r--r--tests/dpop-proof-valid-ath.scm35
-rw-r--r--tests/dpop-proof-valid.scm11
-rw-r--r--tests/dpop-proof-wrong-htm.scm17
-rw-r--r--tests/dpop-proof-wrong-htu.scm17
-rw-r--r--tests/dpop-proof-wrong-key.scm17
-rw-r--r--tests/jws.scm70
-rw-r--r--tests/resource-server.scm24
-rw-r--r--tests/token-endpoint-issue.scm55
-rw-r--r--tests/token-endpoint-refresh.scm63
32 files changed, 2720 insertions, 2514 deletions
diff --git a/doc/disfluid.texi b/doc/disfluid.texi
index 2cfe60c..0047379 100644
--- a/doc/disfluid.texi
+++ b/doc/disfluid.texi
@@ -60,6 +60,7 @@ is tracked in the Guix channel
* Decentralized Authentication on the Web::
* Invoking disfluid::
* Running disfluid with GNU Guix::
+* Managing keys::
* The Json Web Token::
* Caching on server side::
* Content negociation::
@@ -280,264 +281,8 @@ This record configures a server to serve public application pages.
The configuration for the full server.
@end deftp
-@node The Json Web Token
-@chapter The Json Web Token
-
-The Json Web Token, or @dfn{JWT}, is a terse representation of a pair
-of JSON objects: the @dfn{header}, and the @dfn{payload}. The JWT can
-be @dfn{encoded} as a Json Web Signature (@dfn{JWS}), in which case
-the header is encoded to base64 with the URL alphabet, and without
-padding characters, the payload is also encoded to base64, and the
-concatenation of the encoding of the header, a dot, and the encoding
-of the payload is signed with some cryptography algorithm. In the
-following, we will only be interested by public-key cryptography. The
-concatenation of header, dot, payload, dot and signature in base64 is
-the encoding of the JWT.
-
-Decoded JWT are represented as a pair. The car of the pair is the
-header, and the cdr is the payload. Both the header and the payload
-use the JSON representation from srfi-180: objects are alists of
-@strong{symbols} to values, arrays are vectors. It is unfortunate that
-guile-json has a slightly different representation, where alist keys
-are @emph{strings}, but we hope that in the future SRFI-180 will be
-more closely respected.
-
-@menu
-* The ID token::
-* The access token::
-* The DPoP proof::
-* Generic JWTs::
-* Public-key cryptography::
-@end menu
-
-@node The ID token
-@section The ID token
-
-The ID token is a special JWT that the application keeps for
-itself. It is signed by the identity provider, and contains the
-following claims:
-
-@table @emph
-@item webid
-the URI of the user’s webid;
-@item iss
-the URI of the identity provider (issuer);
-@item sub
-the username (the webid-oidc issuer puts the webid again here, but it
-could be any string);
-@item aud
-the ID of the client application that is intended to receive the ID
-token;
-@item nonce
-some random data to change the signature;
-@item exp
-an UTC time (in seconds) for when the token expires;
-@item iat
-the time when it was issued.
-@end table
-
-There are functions to work with ID tokens in
-@emph{(webid-oidc oidc-id-token)}.
-
-@deffn function id-token? @var{object}
-Check that @var{object} is a decoded ID token.
-@end deffn
-
-The following helper functions convert URIs to the URIs from
-@emph{(web uri)} and times to @emph{(srfi srfi-19)} dates.
-
-@deffn function id-token-webid @var{token}
-@deffnx function id-token-iss @var{token}
-@deffnx function id-token-sub @var{token}
-@deffnx function id-token-aud @var{token}
-@deffnx function id-token-nonce @var{token}
-@deffnx function id-token-exp @var{token}
-@deffnx function id-token-iat @var{token}
-Get the suitable field from the payload of @var{token}.
-@end deffn
-
-ID tokens can be signed and encoded as a string, or decoded.
-
-@deffn function id-token-decode @var{token} @var{[#http-get]}
-Decode @var{token}, as a string, into a decoded token. The signature
-verification will need to fetch the oidc configuration of the claimed
-issuer, and check the signature against the published keys. The
-@code{http-get} optional keyword argument can set a different
-implementation of @code{http-get} from @emph{(web client)}. Return
-@code{#f} if it failed, or the decoded token otherwise.
-@end deffn
-
-@deffn function id-token-encode @var{token} @var{key}
-Encode @var{token} and sign it with the issuer’s @var{key}.
-@end deffn
-
-@deffn function issue-id-token @var{issuer-key} @var{#:webid} @var{#:iss} @var{#:sub} @var{#:aud} @var{#:validity}
-Create an ID token that is valid for @var{#:validity} seconds, and
-sign and encode it with @var{issuer-key}.
-@end deffn
-
-@node The access token
-@section The access token
-
-The access token is obtained by the client through a token request,
-and is presented to the server on each authenticated request. It is
-signed by the identity provider, and it contains enough information so
-that the server knows who the user is and who the agent is, and most
-importantly the fingerprint of the key that the client should use in a
-DPoP proof.
-
-The API is defined in @emph{(webid-oidc access-token)}.
-
-@deffn function access-token? @var{object}
-Check that @var{object} is a decoded access token.
-@end deffn
-
-There are field getters for the access token:
-
-@deffn function access-token-webid @var{token}
-@deffnx function access-token-iss @var{token}
-@deffnx function access-token-aud @var{token}
-@deffnx function access-token-exp @var{token}
-@deffnx function access-token-iat @var{token}
-@deffnx function access-token-cnf/jkt @var{token}
-@deffnx function access-token-client-id @var{token}
-Get the suitable field from the payload of @var{token}.
-@end deffn
-
-Access tokens can be signed and encoded as a string, or decoded.
-
-@deffn function access-token-decode @var{token} @var{[#http-get]}
-Decode @var{token}, as a string, into a decoded token. As with the ID
-token, the signature verification will need to fetch the oidc
-configuration of the claimed issuer, and check the signature against
-the published keys. The @code{http-get} optional keyword argument can
-set a different implementation of @code{http-get} from
-@emph{(web client)}, for instance to re-use the what has been obtained
-by the ID token validation. Return the decoded access token, or raise
-an exception.
-@end deffn
-
-@deffn function issue-access-token @var{issuer-key} #:@var{webid} #:@var{iss} #:@var{client-id} #:@var{validity} [#:@var{[client-key} | #:@var{cnf/jkt}]
-Create an access token for @var{#:validity} seconds, and encode it
-with @var{issuer-key}. You can either set the @code{#:cnf/jkt} keyword
-argument with the fingerprint of the client key, or set
-@code{#:client-key} directly, in which case the fingerprint will be
-computed for you.
-@end deffn
-
-@node The DPoP proof
-@section The DPoP proof
-
-This is a special JWT, that is signed by a key controlled by the
-application. The access token certifies that the key used to sign the
-proof is approved by the identity provider.
-
-@deffn function dpop-proof? @var{proof}
-Check that the @var{proof} is a decoded DPoP proof. The validity of
-the proof is not checked by this function.
-@end deffn
-
-@deffn function dpop-proof-alg @var{proof}
-@deffnx function dpop-proof-jwk @var{proof}
-@deffnx function dpop-proof-jti @var{proof}
-@deffnx function dpop-proof-htm @var{proof}
-@deffnx function dpop-proof-htu @var{proof}
-@deffnx function dpop-proof-iat @var{proof}
-@deffnx function dpop-proof-ath @var{proof}
-Get the corresponding field of the proof.
-@end deffn
-
-@deffn function dpop-proof-decode @var{method} @var{uri} @var{str} @var{cnf/check} @var{[#:access-token]}
-Check and decode a DPoP proof encoded as @var{str}.
-
-In order to prevent replay attacks, each proof has a unique random
-string that is remembered globally until its expiration date is
-reached.
-
-The proof is limited to the scope of one @var{uri} and one
-@var{method} (@code{'GET}, @code{'POST} and so on).
-
-The key that is used to sign the proof should be confirmed by the
-identity provider. To this end, the @var{cnf/check} function is called
-with the fingerprint of the key. The function should check that the
-fingerprint is OK (return a boolean).
-
-Finally, when the DPoP proof is tied to an access token (so, for all
-uses except requesting an access token or a refresh token), it must be
-bound to an @var{access-token}.
-@end deffn
-
-The DPoP proof algorithm is sensitive to the current time, because the
-proofs have a limited time validity. By default, the time is the
-system time when the proof is decoded.
-
-@deffn parameter current-date
-This parameter overrides the current time.
-
-It is a thunk returning a date, so you need to put two parenthesis to
-get the time. However, you can set it to a date, a time, a number of
-seconds, or a thunk returning any of these.
-
-@example
- (use-module ((webid-oidc parameters) #:prefix p:))
- ;; This is the current date:
- ((p:current-date))
- ;; You can override it with a thunk, or a fixed date:
- (parameterize ((p:current-date 0))
- ;; Jan 1st 1970
- ((p:current-date)))
-@end example
-@end deffn
-
-@deffn function dpop-proof-encode @var{proof} @var{key}
-Encode the proof and sign it with @var{key}. To generate valid proofs,
-@var{key} should be the private key corresponding to the @code{jwk}
-field of the proof.
-@end deffn
-
-@deffn function issue-dpop-proof @var{client-key} #:@var{htm} #:@var{htu} {[#:@var{access-token}=#f]}
-Create a proof, sign it and encode it with
-@var{client-key}. @var{client-key} should contain both the private and
-public key, because the public part is written in the proof and the
-private part is used to sign it. For most uses, the DPoP proof should
-be encoded for a specific access token. Only token requests should
-omit the @samp{access-token} field.
-
-The @samp{iat} field of the DPoP proof is read from the
-@var{current-date} parameter.
-@end deffn
-
-@node Generic JWTs
-@section Generic JWTs
-
-You can parse generic JWTs signed with JWS with the following
-functions from @emph{(webid-oidc jws)}.
-
-@deffn function jws? @var{jwt}
-Check that @var{jwt} is a decoded JWT signed with JWS.
-@end deffn
-
-@deffn function jws-alg @var{jwt}
-Get the algorithm used to sign @var{jwt}.
-@end deffn
-
-@deffn function jws-decode @var{str} @var{lookup-keys}
-Check and decode a JWT signed with JWS and encoded as @var{str}.
-
-Since the decoding and signature verification happen at the same time
-(for user friendliness), the @var{lookup-keys} function is used. It is
-passed as arguments the decoded JWT (but the signature is not checked
-yet), and it should return a public key, a public key set or a list of
-public keys. If the key lookup failed, this function should raise an
-exception.
-@end deffn
-
-@deffn function jws-encode @var{jwt} @var{key}
-Encode the JWT and sign it with @var{key}.
-@end deffn
-
-@node Public-key cryptography
-@section Public-key cryptography
+@node Managing keys
+@chapter Managing keys
Some functions require a key, or a key pair, to operate. The
@emph{(webid-oidc jwk)} module provides you with everything required
@@ -713,6 +458,710 @@ If the JWKS cannot be downloaded, or is incorrect, this exception is
raised.
@end deftp
+@node The Json Web Token
+@chapter The Json Web Token
+
+The Json Web Token, or @dfn{JWT}, is a terse representation of a pair
+of JSON objects: the @dfn{header}, and the @dfn{payload}. The JWT can
+be @dfn{encoded} as a Json Web Signature (@dfn{JWS}), in which case
+the header is encoded to base64 with the URL alphabet, and without
+padding characters, the payload is also encoded to base64, and the
+concatenation of the encoding of the header, a dot, and the encoding
+of the payload is signed with some cryptography algorithm. In the
+following, we will only be interested by public-key cryptography. The
+concatenation of header, dot, payload, dot and signature in base64 is
+the encoding of the JWT.
+
+@menu
+* Tokens::
+* Tokens issued by an OIDC provider::
+* Date verification for tokens::
+* Single-use tokens::
+* ID tokens::
+* Access tokens::
+* DPoP proofs::
+* Authorization codes::
+@end menu
+
+@node Tokens
+@section Tokens
+
+The @emph{(webid-oidc jws)} implements some functionality for tokens.
+
+@deftp {Class} <token> () @var{alg}
+The base class for all tokens. It only knows the signature
+@var{alg}orithm. You can construct one in different ways:
+@itemize
+@item
+the @code{#:@var{alg}} construct keyword supports a string or a
+keyword as a value, containing a valid JWA identifier, such as
+@code{RS256};
+@item
+the @code{#:@var{signing-key}} keyword defines the key that will serve
+to sign the token. The signature algorithm is set to the default of
+@var{signing-key};
+@item
+the @code{#:@var{jwt-header}} and @code{#:@var{jwt-payload}} keywords
+let you pass two alists, following the JSON representation from
+srfi-180: objects are alists of @strong{symbols} to values, arrays are
+vectors.
+@end itemize
+@end deftp
+
+@deftp {Exception type} &invalid-jws
+This exception is raised when a JWT cannot be parsed or constructed as
+a JWS.
+@end deftp
+
+@deffn {function} make-invalid-jws
+Construct an exception of type @code{&invalid-jws}.
+@end deffn
+
+@deffn {function} invalid-jws? @var{exception}
+Check whether @var{exception} was raised because of an invalid JWS.
+@end deffn
+
+There are multiple things you can do with a token.
+
+@deffn {Generic} alg @var{token}
+Return the signature algorithm used for @var{token}, as a symbol.
+@end deffn
+
+@deffn {Generic} token->jwt @var{token}
+Return two alists, following the JSON representation from srfi-180:
+one for the header, and then one for the payload.
+@end deffn
+
+@deffn {Generic} lookup-keys @var{token} @var{args}
+Return the set of keys that could be used to sign @var{token}, as a
+public key, a list of keys, or a JWKS. @var{args} is a list of keyword
+arguments for specific implementations.
+@end deffn
+
+@deffn {Generic} verify @var{token} @var{args}
+Suppose that the @var{token} signature has been checked, perform some
+additional verifications. This function should raise exceptions to
+signal an invalid token.
+@end deffn
+
+@deffn {function} decode @var{expected-token-class} @var{encoded} . @var{args}
+Parse @var{encoded} as a token from the @var{expected-token-class},
+check its signature against the key obtained by @code{(lookup-keys
+@var{token} @var{args})} where @var{token} is the parsed token, and
+perform additional verifications with @code{(verify @var{token}
+@var{args})}.
+@end deffn
+
+@deffn {function} encode @var{token} @var{key}
+Encode and sign @var{token} with @var{key}, returning a string.
+@end deffn
+
+@deffn {function} issue @var{token-class} @var{issuer-key} . @var{args}
+Construct a token of @var{token-class} and @var{args} and sign it with
+@var{issuer-key}. Since we know the key to sign it, it is not
+necessary to pass either @code{#:signing-key} nor @code{#:alg} to the
+constructor.
+@end deffn
+
+@node Tokens issued by an OIDC provider
+@section Tokens issued by an OIDC provider
+OIDC tokens are those signed by an OIDC identity provider. This kind
+of token knows its issuer, and getting the keys to check the token
+signature is done by OIDC discovery.
+
+@deftp {Class} <oidc-token> (<token>) @var{iss}
+The base class for tokens which are issued by an identity provider. It
+knows the issuer (@var{iss}, an uri from @emph{(web uri)}), and can
+query it to check the token signature.
+
+Similarly to the base token type, you can construct one by specifying
+its arguments, or create one from a pair of alists.
+@itemize
+@item
+@code{#:@var{alg}} or @code{#:@var{signing-key}} is required to
+construct the base token;
+@item
+@code{#:@var{iss}} specifies the issuer.
+@end itemize
+
+The main point of this class is to provide a method for the
+@code{lookup-keys} generic. This method accepts one keyword argument,
+@code{#:@var{http-request}}, a function that behaves like the web
+client in @emph{(web client)}. You can set this value as a keyword
+argument in the @code{decode} function.
+@end deftp
+
+@deffn {Generic} iss @var{token}
+Return the issuer of @var{token}, as an URI.
+@end deffn
+
+@deftp {Exception type} &cannot-query-identity-provider @var{identity-provider}
+This exception is raised when the OIDC discovery
+fails. @var{identity-provider} is an URI.
+@end deftp
+
+@deffn {function} make-cannot-query-identity-provider @var{identity-provider}
+Construct an exception of type @code{&cannot-query-identity-provider}.
+@end deffn
+
+@deffn {function} cannot-query-identity-provider? @var{exception}
+Check whether @var{exception} was raised because an identity provider
+could not be queried.
+@end deffn
+
+@deffn {function} cannot-query-identity-provider-value @var{exception}
+Return the faulty identity provider for @var{exception}.
+@end deffn
+
+@node Date verification for tokens
+@section Date verification for tokens
+Different kinds of tokens have a requirement for a limited time window
+for which the signature should be valid.
+
+@deftp {Class} <time-bound-token> (<token>) @var{iat} @var{exp}
+The base class for tokens which are issued for a limited time
+window. It knows the issuance date (@var{iat}, a date from
+@emph{(srfi srfi-19)}), and the expiration date (@var{iat}, a date
+from @emph{(srfi srfi-19)}).
+
+Similarly to the base token type, you can construct one by specifying
+its arguments, or create one from a pair of alists.
+@itemize
+@item
+@code{#:@var{alg}} or @code{#:@var{signing-key}} is required to
+construct the base token;
+@item
+@code{#:@var{iat}} specifies the issuance date. It defaults to the
+current date;
+@item
+@code{#:@var{exp}} specifies the expiration date. If it is not set,
+the value will be computed from @var{iat} and @var{validity};
+@item
+@code{#:@var{validity}} is used when the expiration date is not known
+in advance. It is a number of seconds. For a DPoP proof, the value
+should be around 30 seconds. For an access token, a good value is in
+the ballpark of 3600 seconds (an hour). Defaults to 3600 seconds, but
+be aware that for single-use tokens, this value will be ignored and
+replaced with a much shorter time.
+@end itemize
+
+The main point of this class is to provide a stricter token validation
+function. You can customize the current date by passing
+@code{#:@var{current-date} ...} as keyword arguments to
+@code{decode}. @code{...} would be replaced with a time or date.
+@end deftp
+
+@deffn {Generic} default-validity @var{token}
+Return the default validity as a number of seconds to construct
+@var{token}, or @code{#f} if an explicit @code{#:validity} is
+required.
+@end deffn
+
+@deffn {Generic} has-explicit-exp? @var{token}
+Check whether we should trust the JWT exp field when constructing
+@var{token}. DPoP proofs should not be able to fill our cache with
+infinitely-valid proofs, so it is disabled for DPoP proofs.
+@end deffn
+
+@deffn {Generic} iat @var{token}
+Return the signature date of @var{token}, as a srfi-19 date.
+@end deffn
+
+@deffn {Generic} exp @var{token}
+Return the expiration date of @var{token}, as a srfi-19 date.
+@end deffn
+
+@deftp {Exception type} &signed-in-future @var{signature-date} @var{current-date}
+@deftpx {Exception type} &expired @var{expiration-date} @var{current-date}
+An exception of type @code{&signed-in-future} is raised when the
+current date is before the alleged signature date. Since the signing
+entity and the verifier entity may not be on the same system, the
+clocks may be slightly out of synchronization, so a margin of 5
+seconds is usually accepted.
+
+An exception of type @code{&expired} indicates that the signature is
+no longer valid.
+@end deftp
+
+@deffn {function} make-signed-in-future @var{signature-date} @var{current-date}
+@deffnx {function} make-expired @var{expiration-date} @var{current-date}
+Constructors for the @code{&signed-in-future} and @code{&expired}
+exception types.
+@end deffn
+
+@deffn {function} signed-in-future? @var{exception}
+@deffnx {function} expired? @var{exception}
+Check whether @var{exception} was raised because of a date mismatch.
+@end deffn
+
+@deffn {function} error-signature-date @var{exception}
+@deffnx {function} error-expiration-date @var{exception}
+@deffnx {function} error-current-date @var{exception}
+If @var{exception} was raised because of a date mismatch, return the
+signature, expiration or current date.
+@end deffn
+
+@node Single-use tokens
+@section Single-use tokens
+To prevent replay attacks, you might want to assign an unique
+identifier to each token of some kind. If you have an expiration date,
+you could remember that this identifier has been seen, and forget
+about it as soon as the token expires. For this to work, you would
+need an expiration date for your single-use token: this is why we only
+support it for time-bound tokens, and the validity is reduced down to
+2 minutes.
+
+@deftp {Class} <single-use-token> (<time-bound-token>) @var{nonce}
+The base class for tokens which are intended to be decoded only
+once. The unique identifier string @var{nonce} will be remembered as
+long as the program is running and the token is not expired.
+
+Similarly to the base token type, you can construct one by specifying
+its arguments, or create one from a pair of alists.
+@itemize
+@item
+@code{#:@var{alg}} or @code{#:@var{signing-key}} is required to
+construct the base token;
+@item
+@code{#:@var{iat}} and @code{#:@var{exp}} or @code{#:@var{validity}}
+is required to construct the time-bound token;
+@item
+@code{#:@var{nonce}} specifies the unique identifier. It defaults to a
+random string of base64 data encoding 96 bits of entropy.
+@item
+@end itemize
+
+The main point of this class is to provide an even stricter token
+validation function, that can only be run once for a given token (with
+reasonable limits: if the program is killed, it won’t remember the
+tokens from before). You can customize the current date by passing
+@code{#:@var{current-date} ...} as keyword arguments to @code{decode},
+just as you do for regular time-bound tokens. @code{...} would be
+replaced with a time or date.
+@end deftp
+
+@deffn {Generic} nonce-field-name @var{token}
+When constructing @var{token} from an existing JWT, this method gives
+the field name in the JWT payload that represents the nonce. DPoP
+proofs use @code{'jti}, so they override this value.
+@end deffn
+
+@deffn {Generic} nonce @var{token}
+Return the unique identifier of @var{token}, as a string.
+@end deffn
+
+@deftp {Exception type} &nonce-found @var{nonce}
+If a token with the same nonce has already been decoded during its
+life time, this exception is raised with the duplicated @var{nonce}.
+@end deftp
+
+@deffn {function} make-nonce-found @var{nonce}
+Construct an exception of type @code{&nonce-found}.
+@end deffn
+
+@deffn {function} nonce-found? @var{exception}
+Check whether @var{exception} was raised because a single-use token
+was already parsed.
+@end deffn
+
+@deffn {function} nonce-found-nonce @var{exception}
+Return the faulty nonce in @var{exception}.
+@end deffn
+
+@node ID tokens
+@section ID tokens
+
+The @emph{(webid-oidc oidc-id-token)} module contains a definition for
+the OIDC ID token.
+
+@deftp {Class} <id-token> (<single-use-token> <oidc-token>) @var{webid} @var{sub} @var{aud}
+The ID token is issued by an identity provider, and is intended to be
+used by the client only. It gives information about the user
+identified by a @var{webid}, an URI from @emph{(web uri)}, and the
+client ID, @var{aud}, an URI too. Since the client should not
+communicate this token, it is reasonable to think that the client will
+deccode the token as soon as it gets it, and then forget the now
+useless signature. This is why this token is considered
+single-use. The @var{sub} field should store a username as a string,
+but if it is missing, the webid (as a string) will be used.
+
+To construct an ID token, you would either need
+@code{#:@var{jwt-header}} and @code{#:@var{jwt-payload}}, as for any
+token, or a combination of parameters:
+
+@itemize
+@item
+@code{#:@var{alg}} or @code{#:@var{signing-key}}, to initialize a JWT;
+@item
+@code{#:@var{iat}} and @code{#:@var{exp}} or @code{#:@var{validity}},
+because it is issued for a limited time window (around an hour);
+@item
+@code{#:@var{nonce}} to define its identifier (defaults to a random
+one);
+@item
+@code{#:@var{iss}}, the issuer URI, because it is an OIDC token;
+@item
+@code{#:@var{webid}}, an URI identifying the user;
+@item
+@code{#:@var{sub}}, a string that defaults to the webid;
+@item
+@code{#:@var{aud}}, an URI identifying the application.
+@end itemize
+@end deftp
+
+@deffn {Generic} webid @var{token}
+Return the user identifier in @var{token}, as an URI.
+@end deffn
+
+@deffn {Generic} sub @var{token}
+Return the username in @var{token}, as a string.
+@end deffn
+
+@deffn {Generic} aud @var{token}
+Return the client identifier in @var{token}, as an URI.
+@end deffn
+
+@deftp {Exception type} &invalid-id-token
+This exception is raised when the ID token is invalid.
+@end deftp
+
+@deffn {function} make-invalid-id-token
+Construct an exception of type @code{&invalid-id-token}.
+@end deffn
+
+@deffn {function} invalid-id-token? @var{exception}
+Check whether @var{exception} was raised because of an invalid ID
+token.
+@end deffn
+
+@node Access tokens
+@section Access tokens
+
+The @emph{(webid-oidc access-token)} module contains a definition for
+the OIDC access token.
+
+@deftp {Class} <access-token> (<time-bound-token> <oidc-token>) @var{webid} @var{aud} @var{client-id} @var{cnf/jkt}
+The access token is issued by an identity provider for a client, and
+is intended to be used by the resource servers. It indicates that the
+agent possessing a key hashed to @var{cnf/jkt} (a string) is
+identified by @var{client-id} (an URI) and is authorized to act on
+behalf of the user identified by @var{webid} (an URI). For
+compatibility, @var{aud} should be set to the literal string
+@code{"solid"}. The agent demonstrates that it owns this key by
+issuing a DPoP proof alongside the access token.
+
+To construct an access token, you would either need
+@code{#:@var{jwt-header}} and @code{#:@var{jwt-payload}}, as for any
+token, or a combination of parameters:
+
+@itemize
+@item
+@code{#:@var{alg}} or @code{#:@var{signing-key}}, to initialize a JWT;
+@item
+@code{#:@var{iat}} and @code{#:@var{exp}} or @code{#:@var{validity}},
+because it is issued for a limited time window (around an hour);
+@item
+@code{#:@var{iss}}, the issuer URI, because it is an OIDC token;
+@item
+@code{#:@var{webid}}, an URI identifying the user;
+@item
+@code{#:@var{client-id}}, an URI identifying the client;
+@item
+@code{#:@var{cnf/jkt}}, the hash of a public key whose private key is
+owned by the client, or @code{#:@var{client-key}}, the client key
+itself;
+@item
+@code{#:@var{aud}}, literally @code{"solid"},
+optional, defaults to the correct value.
+@end itemize
+
+Since the same access token is presented on each request, it is not
+single-use.
+@end deftp
+
+@deffn {Generic} webid @var{token}
+Return the user identifier in @var{token}, as an URI.
+@end deffn
+
+@deffn {Generic} client-id @var{token}
+Return the client identifier in @var{token}, as an URI.
+@end deffn
+
+@deffn {Generic} cnf/jkt @var{token}
+Return the hash of the client key, as a string.
+@end deffn
+
+@deffn {Generic} aud @var{token}
+Return @code{"solid"}.
+@end deffn
+
+@deftp {Exception type} &invalid-access-token
+This exception is raised when the access token is invalid.
+@end deftp
+
+@deffn {function} make-invalid-access-token
+Construct an exception of type @code{&invalid-access-token}.
+@end deffn
+
+@deffn {function} invalid-access-token? @var{exception}
+Check whether @var{exception} was raised because of an invalid access
+token.
+@end deffn
+
+@node DPoP proofs
+@section DPoP proofs
+
+The @emph{(webid-oidc dpop-proof)} module contains a definition for
+the DPoP proof token.
+
+@deftp {Class} <dpop-proof> (<single-use-token>) @var{typ} @var{jwk} @var{htm} @var{htu} @var{ath}
+The DPoP proof is a token that is issued by the client, and presented
+to the resource server along with an access token. It is only valid
+for one request, and for one use. So, it should have a very short
+validity frame, for instance 30 seconds, and should only be valid for
+a specific request method @var{htm} and a specific request URI
+@var{htu}, down to the path, but ignoring the query and fragment.
+
+The DPoP proof is the proof of possession of @var{jwk}, a public
+key. It should always have a @var{typ} field set to @code{"dpop+jwt"}.
+
+To construct a DPoP proof, you would either need
+@code{#:@var{jwt-header}} and @code{#:@var{jwt-payload}}, as for any
+token, or a combination of parameters:
+
+@itemize
+@item
+@code{#:@var{alg}} or @code{#:@var{signing-key}}, to initialize a JWT;
+@item
+@code{#:@var{iat}} and @code{#:@var{exp}} or @code{#:@var{validity}},
+because it is issued for a limited time window (around 30 seconds);
+@item
+@code{#:@var{nonce}}, because it is single-use;
+@item
+@code{#:@var{jwk}}, the public key whose possession we demonstrate by
+signing the proof;
+@item
+@code{#:@var{htm}}, the HTTP method used (as a symbol);
+@item
+@code{#:@var{htu}}, the HTTP URI used (as an URI);
+@item
+@code{#:@var{ath}}, the hash of the access token that goes with this
+proof, or @code{#:@var{access-token}}, the encoded access token
+itself, if the proof goes with an access token. Otherwise, pass
+@code{#f}. Defaults to @code{#f};
+@item
+@code{#:@var{typ}}, literally @code{"dpop+jwt"},
+optional, defaults to the correct value.
+@end itemize
+
+This token class makes a stricter verification function. It requires
+you to set as a keyword argument in @code{decode} the following
+parameters:
+
+@table @code
+@item #:@var{access-token}
+set the access token that should go with the proof, defaults to
+@code{#f} (no access token);
+@item #:@var{method}
+set the method used for the proof;
+@item #:@var{uri}
+set the URI used for the proof;
+@item #:@var{cnf/check}
+set the expected hash of the key used by the DPoP proof, or a function
+taking a public key hash. If this is a function, it should raise an
+exception if the hash is invalid, because its return value is ignored.
+@end table
+@end deftp
+
+@deffn {Generic} jwk @var{proof}
+Return the public key whose possession @var{proof} demonstrates.
+@end deffn
+
+@deffn {Generic} htm @var{proof}
+Return the HTTP method in @var{proof}, as a symbol.
+@end deffn
+
+@deffn {Generic} htu @var{proof}
+Return the HTTP URI in @var{proof}, as an URI.
+@end deffn
+
+@deffn {Generic} ath @var{proof}
+Return the hash of the access token that should go with @var{proof},
+or @code{#f} if @var{proof} is not used with an access token.
+@end deffn
+
+@deffn {Generic} typ @var{proof}
+Return @code{"dpop+jwt"}.
+@end deffn
+
+@deftp {Exception type} &invalid-dpop-proof
+This exception is raised when the DPoP proof is invalid.
+@end deftp
+
+@deffn {function} make-invalid-dpop-proof
+Construct an exception of type @code{&invalid-dpop-proof}.
+@end deffn
+
+@deffn {function} invalid-dpop-proof? @var{exception}
+Check whether @var{exception} was raised because of an invalid DPoP
+proof.
+@end deffn
+
+@deftp {Exception type} &dpop-method-mismatch @var{advertised} @var{actual}
+This exception is raised when the @var{advertised} method is not what
+is @var{actual}ly used in the request (both symbols).
+@end deftp
+
+@deffn {function} make-dpop-method-mismatch @var{advertised} @var{actual}
+Construct an exception of type @code{&dpop-method-mismatch}.
+@end deffn
+
+@deffn {function} dpop-method-mismatch? @var{exception}
+Check whether @var{exception} was raised because of a difference
+between the advertised and actual HTTP methods used.
+@end deffn
+
+@deffn {function} dpop-method-mismatch-advertised @var{exception}
+In case of a DPoP method mismatch causing @var{exception}, return the
+method used in the proof signature.
+@end deffn
+
+@deffn {function} dpop-method-mismatch-actual @var{exception}
+In case of a DPoP method mismatch causing @var{exception}, return the
+method that the server received.
+@end deffn
+
+@deftp {Exception type} &dpop-uri-mismatch @var{advertised} @var{actual}
+This exception is raised when the @var{advertised} URI is not what is
+@var{actual}ly used in the request (both URIs).
+@end deftp
+
+@deffn {function} make-dpop-uri-mismatch @var{advertised} @var{actual}
+Construct an exception of type @code{&dpop-uri-mismatch}.
+@end deffn
+
+@deffn {function} dpop-uri-mismatch? @var{exception}
+Check whether @var{exception} was raised because of a difference
+between the advertised and actual HTTP URIs used.
+@end deffn
+
+@deffn {function} dpop-uri-mismatch-advertised @var{exception}
+In case of a DPoP URI mismatch causing @var{exception}, return the
+URI used in the proof signature.
+@end deffn
+
+@deffn {function} dpop-uri-mismatch-actual @var{exception}
+In case of a DPoP URI mismatch causing @var{exception}, return the URI
+that the server received.
+@end deffn
+
+@deftp {Exception type} &dpop-invalid-ath @var{hash} @var{access-token}
+This exception is raised when the DPoP proof is intended for use along
+with an access token identified by @var{hash}, but is actually used
+along with @var{access-token}.
+@end deftp
+
+@deffn {function} make-dpop-invalid-ath @var{hash} @var{access-token}
+Construct an exception of type @code{&dpop-invalid-ath}.
+@end deffn
+
+@deffn {function} dpop-invalid-ath? @var{exception}
+Check whether @var{exception} was raised because the DPoP proof was
+not used with the correct access token.
+@end deffn
+
+@deffn {function} dpop-invalid-ath-hash @var{exception}
+In case of a DPoP presented with the wrong access token, causing
+@var{exception}, return the hash of the intended access token.
+@end deffn
+
+@deffn {function} dpop-invalid-ath-access-token @var{exception}
+In case of a DPoP presented with the wrong access token, causing
+@var{exception}, return the actual access token.
+@end deffn
+
+@deftp {Exception type} &dpop-unconfirmed-key
+This exception is raised when the DPoP proof does not demonstrate the
+possession of the correct key.
+@end deftp
+
+@deffn {function} make-dpop-unconfirmed-key
+Construct an exception of type @code{&dpop-unconfirmed-key}.
+@end deffn
+
+@deffn {function} dpop-unconfirmed-key? @var{exception}
+Check whether @var{exception} was raised because the DPoP proof
+demonstrated the possession of an incorrect key.
+@end deffn
+
+@node Authorization codes
+@section Authorization codes
+@emph{(webid-oidc authorization-code)} defines an authorization code
+type.
+
+@deftp {Class} <authorization-code> (<single-use-token>) @var{webid} @var{client-id}
+While it is not necessary that an authorization code is a JWT, it is
+easier to implement that way. It is an authorization for
+@var{client-id}, an URI identifying a client, to access the data of
+the user identified by @var{webid}, an URI too. It should only be
+valid for a limited amount of time, and used once only.
+
+
+The DPoP proof is a token that is issued by the client, and presented
+to the resource server along with an access token. It is only valid
+for one request, and for one use. So, it should have a very short
+validity frame, for instance 30 seconds, and should only be valid for
+a specific request method @var{htm} and a specific request URI
+@var{htu}, down to the path, but ignoring the query and fragment.
+
+The DPoP proof is the proof of possession of @var{jwk}, a public
+key. It should always have a @var{typ} field set to @code{"dpop+jwt"}.
+
+To construct an authorization code, you would either need
+@code{#:@var{jwt-header}} and @code{#:@var{jwt-payload}}, as for any
+token, or a combination of parameters:
+
+@itemize
+@item
+@code{#:@var{alg}} or @code{#:@var{signing-key}}, to initialize a JWT;
+@item
+@code{#:@var{iat}} and @code{#:@var{exp}} or @code{#:@var{validity}},
+because it is issued for a limited time window (around 30 seconds);
+@item
+@code{#:@var{nonce}}, because it is single-use;
+@item
+@code{#:@var{webid}}, the user identifier;
+@item
+@code{#:@var{client-id}}, the client identifier.
+@end itemize
+
+The authorization code is signed and verified by the same entity. So,
+the key lookup function is tuned to always return the issuer key. You
+need to set it as the @code{#:@var{issuer-key}} keyword argument of
+the @code{decode} function.
+@end deftp
+
+@deffn {Generic} webid @var{token}
+Return the user identifier in @var{token}, as an URI.
+@end deffn
+
+@deffn {Generic} client-id @var{token}
+Return the client identifier in @var{token}, as an URI.
+@end deffn
+
+@deftp {Exception type} &invalid-authorization-code
+This exception is raised when the authorization ccode is invalid.
+@end deftp
+
+@deffn {function} make-invalid-authorization-code
+Construct an exception of type @code{&invalid-authorization-code}.
+@end deffn
+
+@deffn {function} invalid-authorization-code? @var{exception}
+Check whether @var{exception} was raised because of an invalid
+authorization code.
+@end deffn
+
@node Caching on server side
@chapter Caching on server side
diff --git a/po/disfluid.pot b/po/disfluid.pot
index 6f2cad2..67b4c36 100644
--- a/po/disfluid.pot
+++ b/po/disfluid.pot
@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: disfluid SNAPSHOT\n"
"Report-Msgid-Bugs-To: vivien@planete-kraus.eu\n"
-"POT-Creation-Date: 2021-09-17 23:19+0200\n"
+"POT-Creation-Date: 2021-09-21 22:28+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"
@@ -122,183 +122,66 @@ msgid ""
"webid_oidc_random_init first.\n"
msgstr ""
-#: src/scm/webid-oidc/access-token.scm:72
+#: src/scm/webid-oidc/access-token.scm:73
#, scheme-format
-msgid "this is not an access token, because it is not even a JWS: ~a"
+msgid "invalid access token: ~a"
msgstr ""
-#: src/scm/webid-oidc/access-token.scm:74
-msgid "this is not an access token, because it is not even a JWS"
+#: src/scm/webid-oidc/access-token.scm:75
+msgid "invalid access token"
msgstr ""
-#: src/scm/webid-oidc/access-token.scm:77 src/scm/webid-oidc/dpop-proof.scm:101
-#, scheme-format
-msgid "this is not an access token: ~a"
-msgstr ""
-
-#: src/scm/webid-oidc/access-token.scm:79 src/scm/webid-oidc/dpop-proof.scm:103
-msgid "this is not an access token"
-msgstr ""
-
-#: src/scm/webid-oidc/access-token.scm:101
-#: src/scm/webid-oidc/authorization-code.scm:89
-#: src/scm/webid-oidc/oidc-id-token.scm:97
-#, scheme-format
-msgid "the payload is missing ~s"
+#: src/scm/webid-oidc/access-token.scm:117
+#: src/scm/webid-oidc/authorization-code.scm:93
+#: src/scm/webid-oidc/oidc-id-token.scm:100
+msgid "#:webid should be an URI"
msgstr ""
-#: src/scm/webid-oidc/access-token.scm:123
-#: src/scm/webid-oidc/authorization-code.scm:105
-#: src/scm/webid-oidc/oidc-id-token.scm:118
-#, scheme-format
-msgid "the \"webid\" field should be an URI, ~s is given"
-msgstr ""
-
-#: src/scm/webid-oidc/access-token.scm:130
-#: src/scm/webid-oidc/oidc-id-token.scm:125
-#, scheme-format
-msgid "the \"iss\" field should be an URI, ~s is given"
+#: src/scm/webid-oidc/access-token.scm:122
+msgid "#:client-id should be an URI"
msgstr ""
-#: src/scm/webid-oidc/access-token.scm:135
-#, scheme-format
-msgid "the \"aud\" field should be set to \"solid\", ~s is given"
+#: src/scm/webid-oidc/access-token.scm:127
+msgid "#:cnf/jkt should be a string"
msgstr ""
-#: src/scm/webid-oidc/access-token.scm:142
-#: src/scm/webid-oidc/oidc-id-token.scm:153
-#, scheme-format
-msgid "the \"iat\" field should be a timestamp, ~s is given"
-msgstr ""
-
-#: src/scm/webid-oidc/access-token.scm:149
-#: src/scm/webid-oidc/authorization-code.scm:126
-#: src/scm/webid-oidc/oidc-id-token.scm:160
-#, scheme-format
-msgid "the \"exp\" field should be a timestamp, ~s is given"
-msgstr ""
-
-#: src/scm/webid-oidc/access-token.scm:158
-msgid "the \"cnf\" / \"jkt\" field is missing"
-msgstr ""
-
-#: src/scm/webid-oidc/access-token.scm:166
-#, scheme-format
-msgid "the \"cnf\" / \"jkt\" field should be a string, ~s is given"
-msgstr ""
-
-#: src/scm/webid-oidc/access-token.scm:171
-#, scheme-format
-msgid "the \"cnf\" field should be an object, ~s is given"
+#: src/scm/webid-oidc/access-token.scm:132
+msgid "#:aud should be exactly \"solid\""
msgstr ""
-#: src/scm/webid-oidc/access-token.scm:178
-#: src/scm/webid-oidc/authorization-code.scm:112
-#, scheme-format
-msgid "the \"client_id\" field should be an URI, ~s is given"
-msgstr ""
-
-#: src/scm/webid-oidc/access-token.scm:239
-#, scheme-format
-msgid "the access token is invalid: ~a"
-msgstr ""
-
-#: src/scm/webid-oidc/access-token.scm:241
-msgid "the access token is invalid"
-msgstr ""
-
-#: src/scm/webid-oidc/access-token.scm:257
-#: src/scm/webid-oidc/oidc-id-token.scm:237
-#, scheme-format
-msgid "I cannot query the identity provider configuration: ~a"
-msgstr ""
-
-#: src/scm/webid-oidc/access-token.scm:259
-#: src/scm/webid-oidc/oidc-id-token.scm:239
-msgid "I cannot query the identity provider configuratioon"
-msgstr ""
-
-#: src/scm/webid-oidc/access-token.scm:276
-#, scheme-format
-msgid "I cannot query the identity provider public keys: ~a"
-msgstr ""
-
-#: src/scm/webid-oidc/access-token.scm:278
-msgid "I cannot query the identity provider public keys"
-msgstr ""
-
-#: src/scm/webid-oidc/access-token.scm:294
-#, scheme-format
-msgid "the access token is signed in the future, ~a, relative to current ~a"
-msgstr ""
-
-#: src/scm/webid-oidc/access-token.scm:303
-#, scheme-format
-msgid "the access token expired ~a, which is in the past (from ~a)"
-msgstr ""
-
-#: src/scm/webid-oidc/access-token.scm:317
-#, scheme-format
-msgid "cannot encode the access token: ~a"
-msgstr ""
-
-#: src/scm/webid-oidc/access-token.scm:319
-msgid "cannot encode the access token"
-msgstr ""
-
-#: src/scm/webid-oidc/authorization-code.scm:64
-#, scheme-format
-msgid "this is not an authorization code, because it is not even a JWS: ~a"
-msgstr ""
-
-#: src/scm/webid-oidc/authorization-code.scm:66
-msgid "this is not an authorization code, because it is not even a JWS"
+#: src/scm/webid-oidc/access-token.scm:150
+msgid ""
+"when making an access token either its required fields (#:alg, #:webid, #:"
+"iss, #:aud, #:client-id, #:cnf/jkt, #:iat and #:exp) or (#:jwt-header and #:"
+"jwt-payload) should be passed"
msgstr ""
#: src/scm/webid-oidc/authorization-code.scm:69
#, scheme-format
-msgid "this is not an authorization code: ~a"
+msgid "invalid authorization code: ~a"
msgstr ""
#: src/scm/webid-oidc/authorization-code.scm:71
-msgid "this is not an authorization code"
-msgstr ""
-
-#: src/scm/webid-oidc/authorization-code.scm:119
-#, scheme-format
-msgid "the \"jti\" field should be a string, ~s is given"
-msgstr ""
-
-#: src/scm/webid-oidc/authorization-code.scm:171
-#, scheme-format
-msgid "the authorization code is invalid: ~a"
-msgstr ""
-
-#: src/scm/webid-oidc/authorization-code.scm:173
-msgid "the authorization code is invalid"
-msgstr ""
-
-#: src/scm/webid-oidc/authorization-code.scm:187
-#, scheme-format
-msgid "the authorization expired ~a, which is in the past (from ~a)"
+msgid "invalid authorization code"
msgstr ""
-#: src/scm/webid-oidc/authorization-code.scm:203
-#, scheme-format
-msgid "cannot encode the authorization code: ~a"
+#: src/scm/webid-oidc/authorization-code.scm:98
+msgid "#:client-id should be a string"
msgstr ""
-#: src/scm/webid-oidc/authorization-code.scm:205
-msgid "cannot encode the authorization code"
+#: src/scm/webid-oidc/authorization-code.scm:112
+msgid ""
+"when making an authorization code either its required fields (#:webid and #:"
+"client-id) or (#:jwt-header and #:jwt-payload) should be passed"
msgstr ""
#: src/scm/webid-oidc/authorization-page-unsafe.scm:52
#: src/scm/webid-oidc/hello-world.scm:40 src/scm/webid-oidc/hello-world.scm:167
#: src/scm/webid-oidc/hello-world.scm:187
#: src/scm/webid-oidc/identity-provider.scm:139
-#: src/scm/webid-oidc/token-endpoint.scm:111
-#: src/scm/webid-oidc/token-endpoint.scm:137
-#: src/scm/webid-oidc/token-endpoint.scm:164
+#: src/scm/webid-oidc/token-endpoint.scm:113
+#: src/scm/webid-oidc/token-endpoint.scm:139
+#: src/scm/webid-oidc/token-endpoint.scm:166
msgid "xml-lang|en"
msgstr ""
@@ -332,8 +215,8 @@ msgid "Allow"
msgstr ""
#: src/scm/webid-oidc/authorization-page-unsafe.scm:95
-#: src/scm/webid-oidc/token-endpoint.scm:129
-#: src/scm/webid-oidc/token-endpoint.scm:156
+#: src/scm/webid-oidc/token-endpoint.scm:131
+#: src/scm/webid-oidc/token-endpoint.scm:158
msgid "reason-phrase|Bad Request"
msgstr ""
@@ -358,7 +241,7 @@ msgid "The application you are trying to authorize behaved unexpectedly."
msgstr ""
#: src/scm/webid-oidc/authorization-page-unsafe.scm:126
-#: src/scm/webid-oidc/resource-server.scm:299
+#: src/scm/webid-oidc/resource-server.scm:311
msgid "reason-phrase|Found"
msgstr ""
@@ -477,78 +360,78 @@ msgstr ""
msgid "the client manifest is dereferenced from ~s, but it pretends to be ~s"
msgstr ""
-#: src/scm/webid-oidc/client/accounts.scm:282
+#: src/scm/webid-oidc/client/accounts.scm:285
msgid "The refresh token has expired."
msgstr ""
-#: src/scm/webid-oidc/client/accounts.scm:289
+#: src/scm/webid-oidc/client/accounts.scm:292
#, scheme-format
msgid "The token request failed with code ~s (~s)."
msgstr ""
-#: src/scm/webid-oidc/client/accounts.scm:298
+#: src/scm/webid-oidc/client/accounts.scm:301
msgid "The token response did not set the content type."
msgstr ""
-#: src/scm/webid-oidc/client/accounts.scm:306
+#: src/scm/webid-oidc/client/accounts.scm:309
msgid "The token endpoint did not respond in UTF-8."
msgstr ""
-#: src/scm/webid-oidc/client/accounts.scm:318
+#: src/scm/webid-oidc/client/accounts.scm:321
#, scheme-format
msgid "The token response has content-type ~s, not application/json."
msgstr ""
-#: src/scm/webid-oidc/client/accounts.scm:328
+#: src/scm/webid-oidc/client/accounts.scm:331
msgid "The token response is not valid JSON."
msgstr ""
-#: src/scm/webid-oidc/client/accounts.scm:342
+#: src/scm/webid-oidc/client/accounts.scm:345
#, scheme-format
msgid "The token response did not include an ID token: ~s"
msgstr ""
-#: src/scm/webid-oidc/client/accounts.scm:350
+#: src/scm/webid-oidc/client/accounts.scm:353
#, scheme-format
msgid "The token response did not include an access token: ~s\n"
msgstr ""
-#: src/scm/webid-oidc/client/accounts.scm:361
+#: src/scm/webid-oidc/client/accounts.scm:364
#, scheme-format
msgid "the ID token signature is invalid: ~a"
msgstr ""
-#: src/scm/webid-oidc/client/accounts.scm:363
+#: src/scm/webid-oidc/client/accounts.scm:366
msgid "the ID token signature is invalid"
msgstr ""
-#: src/scm/webid-oidc/client/accounts.scm:381
+#: src/scm/webid-oidc/client/accounts.scm:383
#, scheme-format
msgid "the ID token delivered by the identity provider for ~s has ~s as webid"
msgstr ""
-#: src/scm/webid-oidc/client/accounts.scm:391
+#: src/scm/webid-oidc/client/accounts.scm:393
#, scheme-format
msgid "The ID token delivered by the identity provider ~s is for issuer ~s."
msgstr ""
-#: src/scm/webid-oidc/client/accounts.scm:406
+#: src/scm/webid-oidc/client/accounts.scm:408
msgid "The issuer is required."
msgstr ""
-#: src/scm/webid-oidc/client/accounts.scm:411
+#: src/scm/webid-oidc/client/accounts.scm:413
msgid "The optional subject and required issuer should be strings or URI."
msgstr ""
-#: src/scm/webid-oidc/client/accounts.scm:447
+#: src/scm/webid-oidc/client/accounts.scm:449
msgid "Cannot check the username and/or password."
msgstr ""
-#: src/scm/webid-oidc/client/accounts.scm:457
+#: src/scm/webid-oidc/client/accounts.scm:459
msgid "The subject should be a string or URI."
msgstr ""
-#: src/scm/webid-oidc/client/accounts.scm:471
+#: src/scm/webid-oidc/client/accounts.scm:473
msgid "The issuer should be a string or URI."
msgstr ""
@@ -582,217 +465,161 @@ msgstr ""
msgid "Hello, world!"
msgstr ""
-#: src/scm/webid-oidc/dpop-proof.scm:96
-#, scheme-format
-msgid "this is not a DPoP proof, because it is not even a JWS: ~a"
-msgstr ""
-
-#: src/scm/webid-oidc/dpop-proof.scm:98
-msgid "this is not a DPoP proof, because it is not even a JWS"
-msgstr ""
-
-#: src/scm/webid-oidc/dpop-proof.scm:129
-#, scheme-format
-msgid "the DPoP proof is missing ~s"
-msgstr ""
-
-#: src/scm/webid-oidc/dpop-proof.scm:152
-#, scheme-format
-msgid "the \"jti\" field should be a string, not ~s"
-msgstr ""
-
-#: src/scm/webid-oidc/dpop-proof.scm:159
+#: src/scm/webid-oidc/dpop-proof.scm:110
#, scheme-format
-msgid "the \"htm\" field should be a string, not ~s"
-msgstr ""
-
-#: src/scm/webid-oidc/dpop-proof.scm:166
-#, scheme-format
-msgid "the \"htu\" field should be an URI, not ~s"
+msgid "the DPoP proof is signed for ~s, but it is issued to ~s"
msgstr ""
-#: src/scm/webid-oidc/dpop-proof.scm:173
+#: src/scm/webid-oidc/dpop-proof.scm:155
#, scheme-format
-msgid "the \"iat\" field should be a timestamp, not ~s"
+msgid "invalid DPoP proof: ~a"
msgstr ""
-#: src/scm/webid-oidc/dpop-proof.scm:180
-#, scheme-format
-msgid "the \"ath\" field should be an encoded JWT, not ~s"
+#: src/scm/webid-oidc/dpop-proof.scm:157
+msgid "invalid DPoP proof token"
msgstr ""
#: src/scm/webid-oidc/dpop-proof.scm:189
-#, scheme-format
-msgid "the \"alg\" field should be a string, not ~s"
+msgid "#:typ should be exactly \"dpop+jwt\""
msgstr ""
#: src/scm/webid-oidc/dpop-proof.scm:194
-#, scheme-format
-msgid "the \"typ\" field should be \"dpop+jwt\", not ~s"
-msgstr ""
-
-#: src/scm/webid-oidc/dpop-proof.scm:200
-msgid "the \"jwk\" field should not contain the private key"
-msgstr ""
-
-#: src/scm/webid-oidc/dpop-proof.scm:202
-#, scheme-format
-msgid "the \"jwk\" field should be a valid public key, not ~s"
+msgid "#:jwk should be a public key"
msgstr ""
-#: src/scm/webid-oidc/dpop-proof.scm:281
-#, scheme-format
-msgid "the DPoP proof is signed for ~s, but it is issued to ~s"
-msgstr ""
-
-#: src/scm/webid-oidc/dpop-proof.scm:312
-#, scheme-format
-msgid "the DPoP proof cannot be decoded: ~a"
+#: src/scm/webid-oidc/dpop-proof.scm:199
+msgid "#:htm should be a symbol"
msgstr ""
-#: src/scm/webid-oidc/dpop-proof.scm:314
-msgid "the DPoP proof cannot be decoded"
+#: src/scm/webid-oidc/dpop-proof.scm:205
+msgid "when present, #:ath should be a string"
msgstr ""
-#: src/scm/webid-oidc/dpop-proof.scm:324
-#, scheme-format
-msgid "the DPoP proof is signed for access through ~s, but it is used with ~s"
-msgstr ""
-
-#: src/scm/webid-oidc/dpop-proof.scm:338
-#, scheme-format
+#: src/scm/webid-oidc/dpop-proof.scm:226
msgid ""
-"the DPoP proof is signed in the future, ~a, relative to the current date, ~a"
+"when making a DPoP proof, either its required fields (#:typ, #:jwk, #:htm "
+"and #:htu) or (#:jwt-header and #:jwt-payload) should be passed"
msgstr ""
-#: src/scm/webid-oidc/dpop-proof.scm:347
+#: src/scm/webid-oidc/dpop-proof.scm:259
#, scheme-format
-msgid "the DPoP proof is too old, it was signed ~a and now it is ~a"
+msgid "the DPoP proof is signed for access through ~s, but it is used with ~s"
msgstr ""
-#: src/scm/webid-oidc/dpop-proof.scm:359
+#: src/scm/webid-oidc/dpop-proof.scm:269
#, scheme-format
msgid ""
"the DPoP proof should go along with an access token hashed to ~s, not ~s"
msgstr ""
-#: src/scm/webid-oidc/dpop-proof.scm:368 src/scm/webid-oidc/dpop-proof.scm:379
+#: src/scm/webid-oidc/dpop-proof.scm:277 src/scm/webid-oidc/dpop-proof.scm:284
msgid "the DPoP proof is signed with the wrong key"
msgstr ""
-#: src/scm/webid-oidc/dpop-proof.scm:377
+#: src/scm/webid-oidc/dpop-proof.scm:282
#, scheme-format
msgid "the DPoP proof is signed with the wrong key: ~a"
msgstr ""
-#: src/scm/webid-oidc/dpop-proof.scm:388
+#: src/scm/webid-oidc/dpop-proof.scm:293
msgid "the cnf/check function returned #f"
msgstr ""
-#: src/scm/webid-oidc/dpop-proof.scm:399
-#, scheme-format
-msgid "cannot encode a DPoP proof: ~a"
-msgstr ""
-
-#: src/scm/webid-oidc/dpop-proof.scm:401
-msgid "cannot encode a DPoP proof"
-msgstr ""
-
-#: src/scm/webid-oidc/example-app.scm:97
+#: src/scm/webid-oidc/example-app.scm:96
#, scheme-format
msgid "~a (issued by ~a): no interaction required"
msgstr ""
-#: src/scm/webid-oidc/example-app.scm:100
+#: src/scm/webid-oidc/example-app.scm:99
#, scheme-format
msgid "~a (issued by ~a): offline but accessible"
msgstr ""
-#: src/scm/webid-oidc/example-app.scm:103
+#: src/scm/webid-oidc/example-app.scm:102
#, scheme-format
msgid "~a (issued by ~a): online"
msgstr ""
-#: src/scm/webid-oidc/example-app.scm:106
+#: src/scm/webid-oidc/example-app.scm:105
#, scheme-format
msgid "~a (issued by ~a): inaccessible"
msgstr ""
-#: src/scm/webid-oidc/example-app.scm:119
+#: src/scm/webid-oidc/example-app.scm:118
#, scheme-format
msgid "Your choice ~a does not exist.\n"
msgstr ""
-#: src/scm/webid-oidc/example-app.scm:137
+#: src/scm/webid-oidc/example-app.scm:136
msgid "Your choice is not a valid URI.\n"
msgstr ""
-#: src/scm/webid-oidc/example-app.scm:146
+#: src/scm/webid-oidc/example-app.scm:145
msgid "This is not a valid HTTP method.\n"
msgstr ""
-#: src/scm/webid-oidc/example-app.scm:162
+#: src/scm/webid-oidc/example-app.scm:161
msgid "This is not a valid value for this header.\n"
msgstr ""
-#: src/scm/webid-oidc/example-app.scm:200
+#: src/scm/webid-oidc/example-app.scm:199
msgid "Nothing to undo.\n"
msgstr ""
-#: src/scm/webid-oidc/example-app.scm:212
+#: src/scm/webid-oidc/example-app.scm:211
msgid "Nothing to redo.\n"
msgstr ""
-#: src/scm/webid-oidc/example-app.scm:272
+#: src/scm/webid-oidc/example-app.scm:271
msgid "Example app command|add-account"
msgstr ""
-#: src/scm/webid-oidc/example-app.scm:274
+#: src/scm/webid-oidc/example-app.scm:273
msgid "Example app command|choose-account"
msgstr ""
-#: src/scm/webid-oidc/example-app.scm:276
+#: src/scm/webid-oidc/example-app.scm:275
msgid "Example app command|set-uri"
msgstr ""
-#: src/scm/webid-oidc/example-app.scm:278
+#: src/scm/webid-oidc/example-app.scm:277
msgid "Example app command|set-method"
msgstr ""
-#: src/scm/webid-oidc/example-app.scm:280
+#: src/scm/webid-oidc/example-app.scm:279
msgid "Example app command|view-headers"
msgstr ""
-#: src/scm/webid-oidc/example-app.scm:282
+#: src/scm/webid-oidc/example-app.scm:281
msgid "Example app command|clear-headers"
msgstr ""
-#: src/scm/webid-oidc/example-app.scm:284
+#: src/scm/webid-oidc/example-app.scm:283
msgid "Example app command|add-header"
msgstr ""
-#: src/scm/webid-oidc/example-app.scm:286
+#: src/scm/webid-oidc/example-app.scm:285
msgid "Example app command|ok"
msgstr ""
-#: src/scm/webid-oidc/example-app.scm:288
+#: src/scm/webid-oidc/example-app.scm:287
msgid "Example app command|undo"
msgstr ""
-#: src/scm/webid-oidc/example-app.scm:290
+#: src/scm/webid-oidc/example-app.scm:289
msgid "Example app command|redo"
msgstr ""
-#: src/scm/webid-oidc/example-app.scm:300
+#: src/scm/webid-oidc/example-app.scm:299
#, scheme-format
msgid "To log in on ~a, please visit: ~a\n"
msgstr ""
-#: src/scm/webid-oidc/example-app.scm:303
+#: src/scm/webid-oidc/example-app.scm:302
msgid "Then, paste the authorization code you get:\n"
msgstr ""
-#: src/scm/webid-oidc/example-app.scm:321
+#: src/scm/webid-oidc/example-app.scm:320
#, scheme-format
msgid ""
"Account: ~a\n"
@@ -812,87 +639,87 @@ msgid ""
"\n"
msgstr ""
-#: src/scm/webid-oidc/example-app.scm:340
+#: src/scm/webid-oidc/example-app.scm:339
msgid "Account:|unset"
msgstr ""
-#: src/scm/webid-oidc/example-app.scm:344
+#: src/scm/webid-oidc/example-app.scm:343
msgid "URI:|unset"
msgstr ""
-#: src/scm/webid-oidc/example-app.scm:348
+#: src/scm/webid-oidc/example-app.scm:347
msgid "Method:|unset"
msgstr ""
-#: src/scm/webid-oidc/example-app.scm:351
+#: src/scm/webid-oidc/example-app.scm:350
msgid "Headers:|none"
msgstr ""
-#: src/scm/webid-oidc/example-app.scm:355
+#: src/scm/webid-oidc/example-app.scm:354
msgid "list separator|, "
msgstr ""
-#: src/scm/webid-oidc/example-app.scm:365
+#: src/scm/webid-oidc/example-app.scm:364
#, scheme-format
msgid "You can undo your last command with \"~a\".\n"
msgstr ""
-#: src/scm/webid-oidc/example-app.scm:367
+#: src/scm/webid-oidc/example-app.scm:366
#, scheme-format
msgid "You can re-apply your last undone command with \"~a\".\n"
msgstr ""
-#: src/scm/webid-oidc/example-app.scm:368
+#: src/scm/webid-oidc/example-app.scm:367
msgid "Readline prompt|Command: "
msgstr ""
-#: src/scm/webid-oidc/example-app.scm:375
+#: src/scm/webid-oidc/example-app.scm:374
#, scheme-format
msgid "An error happened: ~a.\n"
msgstr ""
-#: src/scm/webid-oidc/example-app.scm:387
+#: src/scm/webid-oidc/example-app.scm:386
msgid "Please enter your identity provider: "
msgstr ""
-#: src/scm/webid-oidc/example-app.scm:393
+#: src/scm/webid-oidc/example-app.scm:392
msgid ""
"You don’t have other accounts available. Please add one with \"add-account"
"\".\n"
msgstr ""
-#: src/scm/webid-oidc/example-app.scm:399
+#: src/scm/webid-oidc/example-app.scm:398
#, scheme-format
msgid "- ~a: ~a\n"
msgstr ""
-#: src/scm/webid-oidc/example-app.scm:407
+#: src/scm/webid-oidc/example-app.scm:406
#, scheme-format
msgid "[1-~a] "
msgstr ""
-#: src/scm/webid-oidc/example-app.scm:415
+#: src/scm/webid-oidc/example-app.scm:414
msgid "Visit this URI: "
msgstr ""
-#: src/scm/webid-oidc/example-app.scm:421
+#: src/scm/webid-oidc/example-app.scm:420
msgid "Use this HTTP method [GET]: "
msgstr ""
-#: src/scm/webid-oidc/example-app.scm:437
+#: src/scm/webid-oidc/example-app.scm:436
msgid "Which header? "
msgstr ""
-#: src/scm/webid-oidc/example-app.scm:440
+#: src/scm/webid-oidc/example-app.scm:439
#, scheme-format
msgid "Which header value for ~a? "
msgstr ""
-#: src/scm/webid-oidc/example-app.scm:463
+#: src/scm/webid-oidc/example-app.scm:462
msgid "Please define an account and the URI.\n"
msgstr ""
-#: src/scm/webid-oidc/example-app.scm:470
+#: src/scm/webid-oidc/example-app.scm:469
msgid "I don’t know that command.\n"
msgstr ""
@@ -1002,7 +829,7 @@ msgid "The port should be a number between 0 and 65535.\n"
msgstr ""
#: src/scm/webid-oidc/hello-world.scm:159
-#: src/scm/webid-oidc/resource-server.scm:320
+#: src/scm/webid-oidc/resource-server.scm:332
msgid "reason-phrase|Unauthorized"
msgstr ""
@@ -1015,7 +842,7 @@ msgid "<p>This page requires authentication with Solid.</p>"
msgstr ""
#: src/scm/webid-oidc/hello-world.scm:179
-#: src/scm/webid-oidc/resource-server.scm:328
+#: src/scm/webid-oidc/resource-server.scm:340
msgid "reason-phrase|Method Not Allowed"
msgstr ""
@@ -1097,84 +924,123 @@ msgstr ""
msgid "invalid content-type: ~s"
msgstr ""
-#: src/scm/webid-oidc/jws.scm:73
+#: src/scm/webid-oidc/jws.scm:127
#, scheme-format
-msgid "the JWS is invalid: ~a"
+msgid "unsupported JWS algorithm: ~s"
msgstr ""
-#: src/scm/webid-oidc/jws.scm:75
-msgid "the JWS is invalid"
+#: src/scm/webid-oidc/jws.scm:137
+msgid ""
+"when making a token either #:alg or (#:jwt-header and #:jwt-payload) should "
+"be passed"
msgstr ""
-#: src/scm/webid-oidc/jws.scm:94
-msgid "the JWS header does not have an \"alg\" field"
+#: src/scm/webid-oidc/jws.scm:202
+msgid "#:iat should be a date"
msgstr ""
-#: src/scm/webid-oidc/jws.scm:102
-msgid "invalid JSON object as payload"
+#: src/scm/webid-oidc/jws.scm:207
+msgid "#:exp should be a date"
msgstr ""
-#: src/scm/webid-oidc/jws.scm:111
-#, scheme-format
-msgid "invalid signature algorithm: ~s"
+#: src/scm/webid-oidc/jws.scm:217
+msgid ""
+"when making a time-bound token, either its required fields (#:iat, and "
+"either #:exp or #:validity) or (#:jwt-header and #:jwt-payload) should be "
+"passed"
msgstr ""
-#: src/scm/webid-oidc/jws.scm:115
-#, scheme-format
-msgid "invalid \"alg\" value: ~s"
+#: src/scm/webid-oidc/jws.scm:245
+msgid "#:iss should be an URI"
msgstr ""
-#: src/scm/webid-oidc/jws.scm:120
-msgid "invalid JSON object as header"
+#: src/scm/webid-oidc/jws.scm:256
+msgid ""
+"when making an OIDC token, either its required #:iss field or (#:jwt-header "
+"and #:jwt-payload) should be passed"
msgstr ""
-#: src/scm/webid-oidc/jws.scm:122
-msgid "this is not a pair"
+#: src/scm/webid-oidc/jws.scm:300
+msgid "#:nonce should be a string"
msgstr ""
-#: src/scm/webid-oidc/jws.scm:139
+#: src/scm/webid-oidc/jws.scm:309
+msgid ""
+"when making a single-use token, either its required #:nonce field or (#:jwt-"
+"header and #:jwt-payload) should be passed"
+msgstr ""
+
+#: src/scm/webid-oidc/jws.scm:354
msgid "the encoded JWS is not in 3 parts"
msgstr ""
-#: src/scm/webid-oidc/jws.scm:150
+#: src/scm/webid-oidc/jws.scm:365
#, scheme-format
msgid ""
"the encoded JWS header or payload is not a JSON object encoded in base64: ~a"
msgstr ""
-#: src/scm/webid-oidc/jws.scm:152
+#: src/scm/webid-oidc/jws.scm:367
msgid ""
"the encoded JWS header or payload is not a JSON object encoded in base64"
msgstr ""
-#: src/scm/webid-oidc/jws.scm:211
+#: src/scm/webid-oidc/jws.scm:426
msgid "the JWS is not signed by any of the expected set of public keys"
msgstr ""
-#: src/scm/webid-oidc/jws.scm:222
+#: src/scm/webid-oidc/jws.scm:437
#, scheme-format
msgid "while verifying the JWS signature: ~a"
msgstr ""
-#: src/scm/webid-oidc/jws.scm:224
+#: src/scm/webid-oidc/jws.scm:439
msgid "an unexpected error happened while verifying a JWS"
msgstr ""
-#: src/scm/webid-oidc/jws.scm:253
+#: src/scm/webid-oidc/jws.scm:478
+#, scheme-format
+msgid "I cannot query the identity provider configuration: ~a"
+msgstr ""
+
+#: src/scm/webid-oidc/jws.scm:480
+msgid "I cannot query the identity provider configuration"
+msgstr ""
+
+#: src/scm/webid-oidc/jws.scm:501
+#, scheme-format
+msgid "I cannot query the JWKS URI of the identity provider: ~a"
+msgstr ""
+
+#: src/scm/webid-oidc/jws.scm:503
+msgid "I cannot query the JWKS URI of the identity provider"
+msgstr ""
+
+#: src/scm/webid-oidc/jws.scm:531
+#, scheme-format
+msgid "the token is signed in the future, ~a, relative to current ~a"
+msgstr ""
+
+#: src/scm/webid-oidc/jws.scm:540
+#, scheme-format
+msgid "the token expired ~a, which is in the past (from ~a)"
+msgstr ""
+
+#: src/scm/webid-oidc/jws.scm:563
#, scheme-format
msgid "cannot decode a JWS: ~a"
msgstr ""
-#: src/scm/webid-oidc/jws.scm:255
+#: src/scm/webid-oidc/jws.scm:565
msgid "cannot decode a JWS"
msgstr ""
-#: src/scm/webid-oidc/jws.scm:272
+#: src/scm/webid-oidc/jws.scm:583
#, scheme-format
msgid "cannot encode a JWS: ~a"
msgstr ""
-#: src/scm/webid-oidc/jws.scm:274
+#: src/scm/webid-oidc/jws.scm:585
msgid "cannot encode a JWS"
msgstr ""
@@ -1239,78 +1105,28 @@ msgstr ""
msgid "unexpected content-type: ~s"
msgstr ""
-#: src/scm/webid-oidc/oidc-id-token.scm:68
-#, scheme-format
-msgid "this is not an ID token, because it is not even a JWS: ~a"
-msgstr ""
-
-#: src/scm/webid-oidc/oidc-id-token.scm:71
-msgid "this is not an ID token, because it is not even a JWS"
-msgstr ""
-
-#: src/scm/webid-oidc/oidc-id-token.scm:73
-#, scheme-format
-msgid "this is not an ID token: ~a"
-msgstr ""
-
-#: src/scm/webid-oidc/oidc-id-token.scm:76
-msgid "this is not an ID token"
-msgstr ""
-
-#: src/scm/webid-oidc/oidc-id-token.scm:132
-#, scheme-format
-msgid "the \"sub\" field should be a string, ~s is given"
-msgstr ""
-
-#: src/scm/webid-oidc/oidc-id-token.scm:139
-#, scheme-format
-msgid "the \"aud\" field should be an URI, ~s is given"
-msgstr ""
-
-#: src/scm/webid-oidc/oidc-id-token.scm:146
-#, scheme-format
-msgid "the \"nonce\" field should be a string, ~s is given"
-msgstr ""
-
-#: src/scm/webid-oidc/oidc-id-token.scm:166
-msgid "the payload should be a JSON object"
-msgstr ""
-
-#: src/scm/webid-oidc/oidc-id-token.scm:219
-#, scheme-format
-msgid "the ID token is invalid: ~a"
-msgstr ""
-
-#: src/scm/webid-oidc/oidc-id-token.scm:221
-msgid "the ID token is invalid"
-msgstr ""
-
-#: src/scm/webid-oidc/oidc-id-token.scm:259
+#: src/scm/webid-oidc/oidc-id-token.scm:72
#, scheme-format
-msgid "I cannot query the JWKS URI of the identity provider: ~a"
+msgid "invalid OIDC ID token: ~a"
msgstr ""
-#: src/scm/webid-oidc/oidc-id-token.scm:261
-msgid "I cannot query the JWKS URI of the identity provider"
+#: src/scm/webid-oidc/oidc-id-token.scm:74
+msgid "invalid OIDC id token"
msgstr ""
-#: src/scm/webid-oidc/oidc-id-token.scm:272
-#, scheme-format
-msgid "the ID token is signed in the future, ~a, relative to current ~a"
+#: src/scm/webid-oidc/oidc-id-token.scm:105
+msgid "#:sub should be a string"
msgstr ""
-#: src/scm/webid-oidc/oidc-id-token.scm:281
-#, scheme-format
-msgid "the ID token expired ~a, which is in the past (from ~a)"
+#: src/scm/webid-oidc/oidc-id-token.scm:110
+msgid "#:aud should be a string"
msgstr ""
-#: src/scm/webid-oidc/oidc-id-token.scm:295
-#, scheme-format
-msgid "cannot encode the ID token: ~a"
-msgstr ""
-
-#: src/scm/webid-oidc/oidc-id-token.scm:297
-msgid "cannot encode the ID token"
+#: src/scm/webid-oidc/oidc-id-token.scm:126
+msgid ""
+"when making an ID token either its required fields (#:alg, #:webid, #:iss, #:"
+"sub, #:aud, #:iat and #:exp) or (#:jwt-header and #:jwt-payload) should be "
+"passed"
msgstr ""
#: src/scm/webid-oidc/program.scm:57
@@ -2050,47 +1866,47 @@ msgstr ""
msgid "~a: authentication failure\n"
msgstr ""
-#: src/scm/webid-oidc/resource-server.scm:160
-#: src/scm/webid-oidc/resource-server.scm:351
+#: src/scm/webid-oidc/resource-server.scm:172
+#: src/scm/webid-oidc/resource-server.scm:363
msgid "reason-phrase|Precondition Failed"
msgstr ""
-#: src/scm/webid-oidc/resource-server.scm:175
+#: src/scm/webid-oidc/resource-server.scm:187
msgid "reason-phrase|Not Modified"
msgstr ""
-#: src/scm/webid-oidc/resource-server.scm:191
+#: src/scm/webid-oidc/resource-server.scm:203
msgid "The owner is not defined."
msgstr ""
-#: src/scm/webid-oidc/resource-server.scm:263
+#: src/scm/webid-oidc/resource-server.scm:275
msgid "reason-phrase|Created"
msgstr ""
-#: src/scm/webid-oidc/resource-server.scm:288
+#: src/scm/webid-oidc/resource-server.scm:300
#, scheme-format
msgid "~a: ignoring a group that cannot be fetched: ~a\n"
msgstr ""
-#: src/scm/webid-oidc/resource-server.scm:292
+#: src/scm/webid-oidc/resource-server.scm:304
#, scheme-format
msgid "~a: ignoring a group that cannot be fetched\n"
msgstr ""
-#: src/scm/webid-oidc/resource-server.scm:316
-#: src/scm/webid-oidc/token-endpoint.scm:103
+#: src/scm/webid-oidc/resource-server.scm:328
+#: src/scm/webid-oidc/token-endpoint.scm:105
msgid "reason-phrase|Forbidden"
msgstr ""
-#: src/scm/webid-oidc/resource-server.scm:337
+#: src/scm/webid-oidc/resource-server.scm:349
msgid "reason-phrase|Conflict"
msgstr ""
-#: src/scm/webid-oidc/resource-server.scm:344
+#: src/scm/webid-oidc/resource-server.scm:356
msgid "reason-phrase|Unsupported Media Type"
msgstr ""
-#: src/scm/webid-oidc/resource-server.scm:358
+#: src/scm/webid-oidc/resource-server.scm:370
msgid "reason-phrase|Not Acceptable"
msgstr ""
@@ -2169,46 +1985,46 @@ msgstr ""
msgid "an error happened while updating file ~s"
msgstr ""
-#: src/scm/webid-oidc/token-endpoint.scm:91
+#: src/scm/webid-oidc/token-endpoint.scm:93
#, scheme-format
msgid "while handling web failure for the token endpoint: ~a"
msgstr ""
-#: src/scm/webid-oidc/token-endpoint.scm:93
+#: src/scm/webid-oidc/token-endpoint.scm:95
msgid "an error happened during the token endpoint failure handling"
msgstr ""
-#: src/scm/webid-oidc/token-endpoint.scm:222
+#: src/scm/webid-oidc/token-endpoint.scm:225
msgid "missing grant type"
msgstr ""
-#: src/scm/webid-oidc/token-endpoint.scm:226
+#: src/scm/webid-oidc/token-endpoint.scm:229
msgid "<p>You did not specify a grant_type for this request.</p>"
msgstr ""
-#: src/scm/webid-oidc/token-endpoint.scm:240
+#: src/scm/webid-oidc/token-endpoint.scm:243
msgid "missing authorization code"
msgstr ""
-#: src/scm/webid-oidc/token-endpoint.scm:244
+#: src/scm/webid-oidc/token-endpoint.scm:247
msgid ""
"<p>You want to grant an authorization code, but you did not set one.</p>"
msgstr ""
-#: src/scm/webid-oidc/token-endpoint.scm:258
+#: src/scm/webid-oidc/token-endpoint.scm:268
msgid "missing refresh token"
msgstr ""
-#: src/scm/webid-oidc/token-endpoint.scm:262
+#: src/scm/webid-oidc/token-endpoint.scm:272
msgid "<p>You want to grant a refresh token, but you did not set one.</p>"
msgstr ""
-#: src/scm/webid-oidc/token-endpoint.scm:275
+#: src/scm/webid-oidc/token-endpoint.scm:285
#, scheme-format
msgid "unsupported grant type: ~s"
msgstr ""
-#: src/scm/webid-oidc/token-endpoint.scm:280
+#: src/scm/webid-oidc/token-endpoint.scm:290
#, scheme-format
msgid ""
"<p>You want to use <pre>~s</pre> as a grant type, but this is not supported."
diff --git a/po/fr.po b/po/fr.po
index fa1a500..8767117 100644
--- a/po/fr.po
+++ b/po/fr.po
@@ -2,8 +2,8 @@ msgid ""
msgstr ""
"Project-Id-Version: webid-oidc 0.0.0\n"
"Report-Msgid-Bugs-To: vivien@planete-kraus.eu\n"
-"POT-Creation-Date: 2021-09-17 23:19+0200\n"
-"PO-Revision-Date: 2021-09-17 18:38+0200\n"
+"POT-Creation-Date: 2021-09-21 22:28+0200\n"
+"PO-Revision-Date: 2021-09-21 22:28+0200\n"
"Last-Translator: Vivien Kraus <vivien@planete-kraus.eu>\n"
"Language-Team: French <vivien@planete-kraus.eu>\n"
"Language: fr\n"
@@ -126,190 +126,71 @@ msgstr ""
"Le module aléatoire n'a pas été initialisé. Veuillez appeler "
"webid_oidc_random_init d'abort.\n"
-#: src/scm/webid-oidc/access-token.scm:72
+#: src/scm/webid-oidc/access-token.scm:73
#, scheme-format
-msgid "this is not an access token, because it is not even a JWS: ~a"
-msgstr "ce n’est pas un jeton d’accès, parce que ce n’est même pas un JWS : ~a"
+msgid "invalid access token: ~a"
+msgstr "jeton d’accès invalide : ~a"
-#: src/scm/webid-oidc/access-token.scm:74
-msgid "this is not an access token, because it is not even a JWS"
-msgstr "ce n’est pas un jeton d’accès, parce que ce n’est même pas un JWS"
+#: src/scm/webid-oidc/access-token.scm:75
+msgid "invalid access token"
+msgstr "jeton d’accès invalide"
-#: src/scm/webid-oidc/access-token.scm:77 src/scm/webid-oidc/dpop-proof.scm:101
-#, scheme-format
-msgid "this is not an access token: ~a"
-msgstr "ce n’est pas un jeton d’accès : ~a"
+#: src/scm/webid-oidc/access-token.scm:117
+#: src/scm/webid-oidc/authorization-code.scm:93
+#: src/scm/webid-oidc/oidc-id-token.scm:100
+msgid "#:webid should be an URI"
+msgstr "#:webid doit être une URI"
-#: src/scm/webid-oidc/access-token.scm:79 src/scm/webid-oidc/dpop-proof.scm:103
-msgid "this is not an access token"
-msgstr "ce n’est pas un jeton d’accès"
+#: src/scm/webid-oidc/access-token.scm:122
+msgid "#:client-id should be an URI"
+msgstr "#:client-id doit être une URI"
-#: src/scm/webid-oidc/access-token.scm:101
-#: src/scm/webid-oidc/authorization-code.scm:89
-#: src/scm/webid-oidc/oidc-id-token.scm:97
-#, scheme-format
-msgid "the payload is missing ~s"
-msgstr "il manque ~s à la charge utile"
+#: src/scm/webid-oidc/access-token.scm:127
+msgid "#:cnf/jkt should be a string"
+msgstr "#:cnf/jkt doit être une chaîne de caractères"
-#: src/scm/webid-oidc/access-token.scm:123
-#: src/scm/webid-oidc/authorization-code.scm:105
-#: src/scm/webid-oidc/oidc-id-token.scm:118
-#, scheme-format
-msgid "the \"webid\" field should be an URI, ~s is given"
-msgstr "le champ « webid » doit être une URI, pas ~s"
-
-#: src/scm/webid-oidc/access-token.scm:130
-#: src/scm/webid-oidc/oidc-id-token.scm:125
-#, scheme-format
-msgid "the \"iss\" field should be an URI, ~s is given"
-msgstr "le champ « iss » doit être une URI, pas ~s"
-
-#: src/scm/webid-oidc/access-token.scm:135
-#, scheme-format
-msgid "the \"aud\" field should be set to \"solid\", ~s is given"
-msgstr "le champ « aud » doit être « solid », pas ~s"
-
-#: src/scm/webid-oidc/access-token.scm:142
-#: src/scm/webid-oidc/oidc-id-token.scm:153
-#, scheme-format
-msgid "the \"iat\" field should be a timestamp, ~s is given"
-msgstr "le champ « iat » doit être un horodatage, pas ~s"
+#: src/scm/webid-oidc/access-token.scm:132
+msgid "#:aud should be exactly \"solid\""
+msgstr "#:aud doit être exactement « solid »"
-#: src/scm/webid-oidc/access-token.scm:149
-#: src/scm/webid-oidc/authorization-code.scm:126
-#: src/scm/webid-oidc/oidc-id-token.scm:160
-#, scheme-format
-msgid "the \"exp\" field should be a timestamp, ~s is given"
-msgstr "le champ « exp » doit être un horodatage, pas ~s"
-
-#: src/scm/webid-oidc/access-token.scm:158
-msgid "the \"cnf\" / \"jkt\" field is missing"
-msgstr "le champ « cnf » / « jkt » est manquant"
-
-#: src/scm/webid-oidc/access-token.scm:166
-#, scheme-format
-msgid "the \"cnf\" / \"jkt\" field should be a string, ~s is given"
-msgstr "le champ « cnf » / « jkt » doit être une chaîne de caractères, pas ~s"
-
-#: src/scm/webid-oidc/access-token.scm:171
-#, scheme-format
-msgid "the \"cnf\" field should be an object, ~s is given"
-msgstr "le champ « cnf » doit être un objet JSON, pas ~s"
-
-#: src/scm/webid-oidc/access-token.scm:178
-#: src/scm/webid-oidc/authorization-code.scm:112
-#, scheme-format
-msgid "the \"client_id\" field should be an URI, ~s is given"
-msgstr "le champ « client_id » doit être une URI, pas ~s"
-
-#: src/scm/webid-oidc/access-token.scm:239
-#, scheme-format
-msgid "the access token is invalid: ~a"
-msgstr "le jeton d’accès est invalide : ~a"
-
-#: src/scm/webid-oidc/access-token.scm:241
-msgid "the access token is invalid"
-msgstr "le jeton d’accès est invalide"
-
-#: src/scm/webid-oidc/access-token.scm:257
-#: src/scm/webid-oidc/oidc-id-token.scm:237
-#, scheme-format
-msgid "I cannot query the identity provider configuration: ~a"
-msgstr ""
-"je ne peux pas requêter la configuration du fournisseur d’identité : ~a"
-
-#: src/scm/webid-oidc/access-token.scm:259
-#: src/scm/webid-oidc/oidc-id-token.scm:239
-msgid "I cannot query the identity provider configuratioon"
-msgstr "je ne peux pas requêter la configurration du fournisseur d’identité"
-
-#: src/scm/webid-oidc/access-token.scm:276
-#, scheme-format
-msgid "I cannot query the identity provider public keys: ~a"
-msgstr ""
-"je ne peux pas requêter les clés publiques du fournisseur d’identité : ~a"
-
-#: src/scm/webid-oidc/access-token.scm:278
-msgid "I cannot query the identity provider public keys"
-msgstr "je ne peux pas requêter les clés publiques du fournisseur d’identité"
-
-#: src/scm/webid-oidc/access-token.scm:294
-#, scheme-format
-msgid "the access token is signed in the future, ~a, relative to current ~a"
-msgstr ""
-"le jeton d’accès est signé dans le futur, le ~a, par rapport à la date "
-"courante, le ~a"
-
-#: src/scm/webid-oidc/access-token.scm:303
-#, scheme-format
-msgid "the access token expired ~a, which is in the past (from ~a)"
-msgstr "le jeton d’accès a expiré le ~a, qui est dans le passé (de ~a)"
-
-#: src/scm/webid-oidc/access-token.scm:317
-#, scheme-format
-msgid "cannot encode the access token: ~a"
-msgstr "impossible d’encoder le jeton d’accès : ~a"
-
-#: src/scm/webid-oidc/access-token.scm:319
-msgid "cannot encode the access token"
-msgstr "impossible d’encoder le jeton d’accès"
-
-#: src/scm/webid-oidc/authorization-code.scm:64
-#, scheme-format
-msgid "this is not an authorization code, because it is not even a JWS: ~a"
-msgstr ""
-"ce n’est pas un code d’autorisation, parce que ce n’est même pas un JWS : ~a"
-
-#: src/scm/webid-oidc/authorization-code.scm:66
-msgid "this is not an authorization code, because it is not even a JWS"
+#: src/scm/webid-oidc/access-token.scm:150
+msgid ""
+"when making an access token either its required fields (#:alg, #:webid, #:"
+"iss, #:aud, #:client-id, #:cnf/jkt, #:iat and #:exp) or (#:jwt-header and #:"
+"jwt-payload) should be passed"
msgstr ""
-"ce n’est pas un code d’autorisation, parce que ce n’est même pas un JWS"
+"lors de la création d’un jeton d’accès, il faut passer soit les champs "
+"nécessaires (#:alg, #:webid, #:iss, #:aud, #:client-id, #:cnf/jkt, #:iat et "
+"#:exp) soit (#:jwt-header et #:jwt-payload)"
#: src/scm/webid-oidc/authorization-code.scm:69
#, scheme-format
-msgid "this is not an authorization code: ~a"
-msgstr "ce n’est pas un code d’autorisation : ~a"
+msgid "invalid authorization code: ~a"
+msgstr "jeton d’autorisation invalide : ~a"
#: src/scm/webid-oidc/authorization-code.scm:71
-msgid "this is not an authorization code"
-msgstr "ce n’est pas un code d’autorisation"
-
-#: src/scm/webid-oidc/authorization-code.scm:119
-#, scheme-format
-msgid "the \"jti\" field should be a string, ~s is given"
-msgstr "le champ « jti » doit être une chaîne de caractères, pas ~s"
-
-#: src/scm/webid-oidc/authorization-code.scm:171
-#, scheme-format
-msgid "the authorization code is invalid: ~a"
-msgstr "le code d’autorisation est invalide : ~a"
+msgid "invalid authorization code"
+msgstr "jeton d’autorisation invalide"
-#: src/scm/webid-oidc/authorization-code.scm:173
-msgid "the authorization code is invalid"
-msgstr "le code d’autorisation est invalide"
+#: src/scm/webid-oidc/authorization-code.scm:98
+msgid "#:client-id should be a string"
+msgstr "#:client-id doit être une chaîne de caractères"
-#: src/scm/webid-oidc/authorization-code.scm:187
-#, scheme-format
-msgid "the authorization expired ~a, which is in the past (from ~a)"
+#: src/scm/webid-oidc/authorization-code.scm:112
+msgid ""
+"when making an authorization code either its required fields (#:webid and #:"
+"client-id) or (#:jwt-header and #:jwt-payload) should be passed"
msgstr ""
-"le code d’autorisation a expiré le ~a, qui est dans le passé (depuis ~a)"
-
-#: src/scm/webid-oidc/authorization-code.scm:203
-#, scheme-format
-msgid "cannot encode the authorization code: ~a"
-msgstr "impossible d’encoder le code d’autorisation : ~a"
-
-#: src/scm/webid-oidc/authorization-code.scm:205
-msgid "cannot encode the authorization code"
-msgstr "impossible d’encoder le code d’autorisation"
+"lors de la création d’un code d’autorisation, il faut soit passer les champs "
+"requis (#:webid et #:client-id), soit (#:jwt-header et #:jwt-payload)"
#: src/scm/webid-oidc/authorization-page-unsafe.scm:52
#: src/scm/webid-oidc/hello-world.scm:40 src/scm/webid-oidc/hello-world.scm:167
#: src/scm/webid-oidc/hello-world.scm:187
#: src/scm/webid-oidc/identity-provider.scm:139
-#: src/scm/webid-oidc/token-endpoint.scm:111
-#: src/scm/webid-oidc/token-endpoint.scm:137
-#: src/scm/webid-oidc/token-endpoint.scm:164
+#: src/scm/webid-oidc/token-endpoint.scm:113
+#: src/scm/webid-oidc/token-endpoint.scm:139
+#: src/scm/webid-oidc/token-endpoint.scm:166
msgid "xml-lang|en"
msgstr "fr"
@@ -343,8 +224,8 @@ msgid "Allow"
msgstr "Autoriser"
#: src/scm/webid-oidc/authorization-page-unsafe.scm:95
-#: src/scm/webid-oidc/token-endpoint.scm:129
-#: src/scm/webid-oidc/token-endpoint.scm:156
+#: src/scm/webid-oidc/token-endpoint.scm:131
+#: src/scm/webid-oidc/token-endpoint.scm:158
msgid "reason-phrase|Bad Request"
msgstr "Requête Invalide"
@@ -370,7 +251,7 @@ msgstr ""
"L’application que vous essayez d’autoriser se comporte de façon inattendue."
#: src/scm/webid-oidc/authorization-page-unsafe.scm:126
-#: src/scm/webid-oidc/resource-server.scm:299
+#: src/scm/webid-oidc/resource-server.scm:311
msgid "reason-phrase|Found"
msgstr "Trouvé"
@@ -497,83 +378,83 @@ msgstr "impossible de télécharger le manifeste client ~s"
msgid "the client manifest is dereferenced from ~s, but it pretends to be ~s"
msgstr "le manifeste client est déréférencé depuis ~s, mais il prétend être ~s"
-#: src/scm/webid-oidc/client/accounts.scm:282
+#: src/scm/webid-oidc/client/accounts.scm:285
msgid "The refresh token has expired."
msgstr "le jeton de rafraîchissement a expiré."
-#: src/scm/webid-oidc/client/accounts.scm:289
+#: src/scm/webid-oidc/client/accounts.scm:292
#, scheme-format
msgid "The token request failed with code ~s (~s)."
msgstr "La requête de jeton a échoué avec un code ~s (~s)."
-#: src/scm/webid-oidc/client/accounts.scm:298
+#: src/scm/webid-oidc/client/accounts.scm:301
msgid "The token response did not set the content type."
msgstr "Le jeton de réponse n’a pas défini de type de contenu."
-#: src/scm/webid-oidc/client/accounts.scm:306
+#: src/scm/webid-oidc/client/accounts.scm:309
msgid "The token endpoint did not respond in UTF-8."
msgstr "Le terminal de jetonn n’a pas répondu en UTF-8."
-#: src/scm/webid-oidc/client/accounts.scm:318
+#: src/scm/webid-oidc/client/accounts.scm:321
#, scheme-format
msgid "The token response has content-type ~s, not application/json."
msgstr "La réponse de jeton a un type de contenu ~s, pas application/json."
-#: src/scm/webid-oidc/client/accounts.scm:328
+#: src/scm/webid-oidc/client/accounts.scm:331
msgid "The token response is not valid JSON."
msgstr "La réponse de jeton n’est pas un JSON valide."
-#: src/scm/webid-oidc/client/accounts.scm:342
+#: src/scm/webid-oidc/client/accounts.scm:345
#, scheme-format
msgid "The token response did not include an ID token: ~s"
msgstr "La réponse de jeton n’a pas inclus de jeton d’ID : ~s"
-#: src/scm/webid-oidc/client/accounts.scm:350
+#: src/scm/webid-oidc/client/accounts.scm:353
#, scheme-format
msgid "The token response did not include an access token: ~s\n"
msgstr "La réponse de jeton n’a pas inclus de jeton d’accès : ~s\n"
-#: src/scm/webid-oidc/client/accounts.scm:361
+#: src/scm/webid-oidc/client/accounts.scm:364
#, scheme-format
msgid "the ID token signature is invalid: ~a"
msgstr "la signature du jeton d’ID est invalide : ~a"
-#: src/scm/webid-oidc/client/accounts.scm:363
+#: src/scm/webid-oidc/client/accounts.scm:366
msgid "the ID token signature is invalid"
msgstr "la signature du jeton d’ID est invalide"
-#: src/scm/webid-oidc/client/accounts.scm:381
+#: src/scm/webid-oidc/client/accounts.scm:383
#, scheme-format
msgid "the ID token delivered by the identity provider for ~s has ~s as webid"
msgstr ""
"le jeton d’ID délivré par le fournisseur d’identité pour ~s a ~s pour webid"
-#: src/scm/webid-oidc/client/accounts.scm:391
+#: src/scm/webid-oidc/client/accounts.scm:393
#, scheme-format
msgid "The ID token delivered by the identity provider ~s is for issuer ~s."
msgstr ""
"Le jeton d’ID délivré par le fournisseur d’identité ~s est pour l’émetteur "
"~s."
-#: src/scm/webid-oidc/client/accounts.scm:406
+#: src/scm/webid-oidc/client/accounts.scm:408
msgid "The issuer is required."
msgstr "L’émetteur est requis."
-#: src/scm/webid-oidc/client/accounts.scm:411
+#: src/scm/webid-oidc/client/accounts.scm:413
msgid "The optional subject and required issuer should be strings or URI."
msgstr ""
"Le sujet optionnel et émetteur doivent être des chaînes de caractère ou des "
"URIs."
-#: src/scm/webid-oidc/client/accounts.scm:447
+#: src/scm/webid-oidc/client/accounts.scm:449
msgid "Cannot check the username and/or password."
msgstr "Impossible de vérifier le nom d’utilisateur et/ou le mot de passe."
-#: src/scm/webid-oidc/client/accounts.scm:457
+#: src/scm/webid-oidc/client/accounts.scm:459
msgid "The subject should be a string or URI."
msgstr "Le sujet doit être une chaîne de caractères ou une URI."
-#: src/scm/webid-oidc/client/accounts.scm:471
+#: src/scm/webid-oidc/client/accounts.scm:473
msgid "The issuer should be a string or URI."
msgstr "L’émetteur doit être une chaîne de caractères ou une URI."
@@ -611,101 +492,52 @@ msgstr "Bonjour, le monde !\n"
msgid "Hello, world!"
msgstr "Bonjour, le monde !"
-#: src/scm/webid-oidc/dpop-proof.scm:96
-#, scheme-format
-msgid "this is not a DPoP proof, because it is not even a JWS: ~a"
-msgstr "ce n’est pas une preuve DPoP, parce que ce n’est même pas un JWS : ~a"
-
-#: src/scm/webid-oidc/dpop-proof.scm:98
-msgid "this is not a DPoP proof, because it is not even a JWS"
-msgstr "ce n’est pas une preuve DPoP, parce que ce n’est même pas un JWS"
-
-#: src/scm/webid-oidc/dpop-proof.scm:129
+#: src/scm/webid-oidc/dpop-proof.scm:110
#, scheme-format
-msgid "the DPoP proof is missing ~s"
-msgstr "il manque ~s à la preuve DPoP"
-
-#: src/scm/webid-oidc/dpop-proof.scm:152
-#, scheme-format
-msgid "the \"jti\" field should be a string, not ~s"
-msgstr "le champ « jti » doit être une chaîne de caractères, pas ~s"
-
-#: src/scm/webid-oidc/dpop-proof.scm:159
-#, scheme-format
-msgid "the \"htm\" field should be a string, not ~s"
-msgstr "le champ « htm » doit être une chaîne de caractères, pas ~s"
-
-#: src/scm/webid-oidc/dpop-proof.scm:166
-#, scheme-format
-msgid "the \"htu\" field should be an URI, not ~s"
-msgstr "le champ « htu » doit être une URI, pas ~s"
+msgid "the DPoP proof is signed for ~s, but it is issued to ~s"
+msgstr "la preuve DPoP est signée pour ~s, mais elle est émise pour ~s"
-#: src/scm/webid-oidc/dpop-proof.scm:173
+#: src/scm/webid-oidc/dpop-proof.scm:155
#, scheme-format
-msgid "the \"iat\" field should be a timestamp, not ~s"
-msgstr "le champ « iat » doit être un horodatage, pas ~s"
+msgid "invalid DPoP proof: ~a"
+msgstr "preuve DPoP invalide : ~a"
-#: src/scm/webid-oidc/dpop-proof.scm:180
-#, scheme-format
-msgid "the \"ath\" field should be an encoded JWT, not ~s"
-msgstr "le champ « ath » doit être un JWT encodé, pas ~s"
+#: src/scm/webid-oidc/dpop-proof.scm:157
+msgid "invalid DPoP proof token"
+msgstr "jeton de preuve DPoP invalide"
#: src/scm/webid-oidc/dpop-proof.scm:189
-#, scheme-format
-msgid "the \"alg\" field should be a string, not ~s"
-msgstr "le champ « alg » doit être une chaîne de caractères, pas ~s"
+msgid "#:typ should be exactly \"dpop+jwt\""
+msgstr "#:typ doit être exactement « dpop+jwt »"
#: src/scm/webid-oidc/dpop-proof.scm:194
-#, scheme-format
-msgid "the \"typ\" field should be \"dpop+jwt\", not ~s"
-msgstr "le champ « typ » doit être « dpop+jwt », pas ~s"
+msgid "#:jwk should be a public key"
+msgstr "#:jwk doit être une clé publique"
-#: src/scm/webid-oidc/dpop-proof.scm:200
-msgid "the \"jwk\" field should not contain the private key"
-msgstr "le champ « jwk » ne doit pas contenir la clé privée"
+#: src/scm/webid-oidc/dpop-proof.scm:199
+msgid "#:htm should be a symbol"
+msgstr "#:htm doit être un symbole"
-#: src/scm/webid-oidc/dpop-proof.scm:202
-#, scheme-format
-msgid "the \"jwk\" field should be a valid public key, not ~s"
-msgstr "le champ « jwk » doit être unen clé publique valide, pas ~s"
+#: src/scm/webid-oidc/dpop-proof.scm:205
+msgid "when present, #:ath should be a string"
+msgstr "si présent, #:ath doit être une chaîne de caractères"
-#: src/scm/webid-oidc/dpop-proof.scm:281
-#, scheme-format
-msgid "the DPoP proof is signed for ~s, but it is issued to ~s"
-msgstr "la preuve DPoP est signée pour ~s, mais elle est émise pour ~s"
-
-#: src/scm/webid-oidc/dpop-proof.scm:312
-#, scheme-format
-msgid "the DPoP proof cannot be decoded: ~a"
-msgstr "impossible de décoder la preuve DPoP : ~a"
-
-#: src/scm/webid-oidc/dpop-proof.scm:314
-msgid "the DPoP proof cannot be decoded"
-msgstr "impossible de décoder la preuve DPoP"
+#: src/scm/webid-oidc/dpop-proof.scm:226
+msgid ""
+"when making a DPoP proof, either its required fields (#:typ, #:jwk, #:htm "
+"and #:htu) or (#:jwt-header and #:jwt-payload) should be passed"
+msgstr ""
+"lors de la création d’une preuve DPoP, il faut passer soit les champs requis "
+"(#:typ, #:jwk, #:htm et #:htu) soit (#:jwt-header et #:jwt-payload)"
-#: src/scm/webid-oidc/dpop-proof.scm:324
+#: src/scm/webid-oidc/dpop-proof.scm:259
#, scheme-format
msgid "the DPoP proof is signed for access through ~s, but it is used with ~s"
msgstr ""
"la preuve DPoP est signée pour un accès avec ~s, mais elle est utilisée avec "
"~s"
-#: src/scm/webid-oidc/dpop-proof.scm:338
-#, scheme-format
-msgid ""
-"the DPoP proof is signed in the future, ~a, relative to the current date, ~a"
-msgstr ""
-"la preuve DPoP est signée dans le futur, le ~a, par rapport à la date "
-"courante, ~a"
-
-#: src/scm/webid-oidc/dpop-proof.scm:347
-#, scheme-format
-msgid "the DPoP proof is too old, it was signed ~a and now it is ~a"
-msgstr ""
-"la preuve DPoP est trop vieille, elle a été signée le ~a et nous sommes "
-"maintenant le ~a"
-
-#: src/scm/webid-oidc/dpop-proof.scm:359
+#: src/scm/webid-oidc/dpop-proof.scm:269
#, scheme-format
msgid ""
"the DPoP proof should go along with an access token hashed to ~s, not ~s"
@@ -713,123 +545,114 @@ msgstr ""
"la preuve DPoP devrait être accompagnée d’un jeton d’accès de condensat ~s, "
"pas ~s"
-#: src/scm/webid-oidc/dpop-proof.scm:368 src/scm/webid-oidc/dpop-proof.scm:379
+#: src/scm/webid-oidc/dpop-proof.scm:277 src/scm/webid-oidc/dpop-proof.scm:284
msgid "the DPoP proof is signed with the wrong key"
msgstr "la preuve DPoP est signée avec la mauvaise clé"
-#: src/scm/webid-oidc/dpop-proof.scm:377
+#: src/scm/webid-oidc/dpop-proof.scm:282
#, scheme-format
msgid "the DPoP proof is signed with the wrong key: ~a"
msgstr "la preuve DPoP est signée avec la mauvaise clé : ~a"
-#: src/scm/webid-oidc/dpop-proof.scm:388
+#: src/scm/webid-oidc/dpop-proof.scm:293
msgid "the cnf/check function returned #f"
msgstr "la fonction cnf/check a retourné #f"
-#: src/scm/webid-oidc/dpop-proof.scm:399
-#, scheme-format
-msgid "cannot encode a DPoP proof: ~a"
-msgstr "impossible d’encoder la preuve DPoP : ~a"
-
-#: src/scm/webid-oidc/dpop-proof.scm:401
-msgid "cannot encode a DPoP proof"
-msgstr "impossible d’encoder la preuve DPoP"
-
-#: src/scm/webid-oidc/example-app.scm:97
+#: src/scm/webid-oidc/example-app.scm:96
#, scheme-format
msgid "~a (issued by ~a): no interaction required"
msgstr "~a (émis par ~a) : aucune interaction nécessaire"
-#: src/scm/webid-oidc/example-app.scm:100
+#: src/scm/webid-oidc/example-app.scm:99
#, scheme-format
msgid "~a (issued by ~a): offline but accessible"
msgstr "~a (émis par ~a) : hors ligne mais accessible"
-#: src/scm/webid-oidc/example-app.scm:103
+#: src/scm/webid-oidc/example-app.scm:102
#, scheme-format
msgid "~a (issued by ~a): online"
msgstr "~a (émis par ~a) : en ligne"
-#: src/scm/webid-oidc/example-app.scm:106
+#: src/scm/webid-oidc/example-app.scm:105
#, scheme-format
msgid "~a (issued by ~a): inaccessible"
msgstr "~a (émis par ~a) : inaccessible"
-#: src/scm/webid-oidc/example-app.scm:119
+#: src/scm/webid-oidc/example-app.scm:118
#, scheme-format
msgid "Your choice ~a does not exist.\n"
msgstr "Votre choix, ~a, n’existe pas.\n"
-#: src/scm/webid-oidc/example-app.scm:137
+#: src/scm/webid-oidc/example-app.scm:136
msgid "Your choice is not a valid URI.\n"
msgstr "Votre choix doit être une URI valide.\n"
-#: src/scm/webid-oidc/example-app.scm:146
+#: src/scm/webid-oidc/example-app.scm:145
msgid "This is not a valid HTTP method.\n"
msgstr "ce n’est pas une méthode HTTP valide.\n"
-#: src/scm/webid-oidc/example-app.scm:162
+#: src/scm/webid-oidc/example-app.scm:161
msgid "This is not a valid value for this header.\n"
msgstr "Ce n’est pas une valeur valide pour cet en-tête.\n"
-#: src/scm/webid-oidc/example-app.scm:200
+#: src/scm/webid-oidc/example-app.scm:199
msgid "Nothing to undo.\n"
msgstr "Rien à annuler.\n"
-#: src/scm/webid-oidc/example-app.scm:212
+#: src/scm/webid-oidc/example-app.scm:211
msgid "Nothing to redo.\n"
msgstr "Rien à refaire.\n"
-#: src/scm/webid-oidc/example-app.scm:272
+#: src/scm/webid-oidc/example-app.scm:271
msgid "Example app command|add-account"
msgstr "ajouter-compte"
-#: src/scm/webid-oidc/example-app.scm:274
+#: src/scm/webid-oidc/example-app.scm:273
msgid "Example app command|choose-account"
msgstr "choisir-compte"
-#: src/scm/webid-oidc/example-app.scm:276
+#: src/scm/webid-oidc/example-app.scm:275
msgid "Example app command|set-uri"
msgstr "définir-uri"
-#: src/scm/webid-oidc/example-app.scm:278
+#: src/scm/webid-oidc/example-app.scm:277
msgid "Example app command|set-method"
msgstr "définir-méthode"
-#: src/scm/webid-oidc/example-app.scm:280
+#: src/scm/webid-oidc/example-app.scm:279
msgid "Example app command|view-headers"
msgstr "voir-en-têtes"
-#: src/scm/webid-oidc/example-app.scm:282
+#: src/scm/webid-oidc/example-app.scm:281
msgid "Example app command|clear-headers"
msgstr "effacer-en-têtes"
-#: src/scm/webid-oidc/example-app.scm:284
+#: src/scm/webid-oidc/example-app.scm:283
msgid "Example app command|add-header"
msgstr "ajouter-en-tête"
-#: src/scm/webid-oidc/example-app.scm:286
+#: src/scm/webid-oidc/example-app.scm:285
msgid "Example app command|ok"
msgstr "ok"
-#: src/scm/webid-oidc/example-app.scm:288
+#: src/scm/webid-oidc/example-app.scm:287
msgid "Example app command|undo"
msgstr "annuler"
-#: src/scm/webid-oidc/example-app.scm:290
+#: src/scm/webid-oidc/example-app.scm:289
msgid "Example app command|redo"
msgstr "refaire"
-#: src/scm/webid-oidc/example-app.scm:300
+#: src/scm/webid-oidc/example-app.scm:299
#, scheme-format
msgid "To log in on ~a, please visit: ~a\n"
msgstr "Pour vous connecte avec ~a, veuillez visiter : ~a\n"
-#: src/scm/webid-oidc/example-app.scm:303
+#: src/scm/webid-oidc/example-app.scm:302
msgid "Then, paste the authorization code you get:\n"
msgstr "Ensuite, veuillez coller votre code d’autorisation :\n"
-#: src/scm/webid-oidc/example-app.scm:321
+#: src/scm/webid-oidc/example-app.scm:320
#, scheme-format
msgid ""
"Account: ~a\n"
@@ -864,50 +687,50 @@ msgstr ""
" - ~a : effectuer la requête.\n"
"\n"
-#: src/scm/webid-oidc/example-app.scm:340
+#: src/scm/webid-oidc/example-app.scm:339
msgid "Account:|unset"
msgstr "non défini"
-#: src/scm/webid-oidc/example-app.scm:344
+#: src/scm/webid-oidc/example-app.scm:343
msgid "URI:|unset"
msgstr "non défini"
-#: src/scm/webid-oidc/example-app.scm:348
+#: src/scm/webid-oidc/example-app.scm:347
msgid "Method:|unset"
msgstr "non définie"
-#: src/scm/webid-oidc/example-app.scm:351
+#: src/scm/webid-oidc/example-app.scm:350
msgid "Headers:|none"
msgstr "aucun"
-#: src/scm/webid-oidc/example-app.scm:355
+#: src/scm/webid-oidc/example-app.scm:354
msgid "list separator|, "
msgstr ", "
-#: src/scm/webid-oidc/example-app.scm:365
+#: src/scm/webid-oidc/example-app.scm:364
#, scheme-format
msgid "You can undo your last command with \"~a\".\n"
msgstr "Vous pouvez annuler votre dernière commande avec « ~a ».\n"
-#: src/scm/webid-oidc/example-app.scm:367
+#: src/scm/webid-oidc/example-app.scm:366
#, scheme-format
msgid "You can re-apply your last undone command with \"~a\".\n"
msgstr "Vous pouvez refaire votre dernière commande annulée avec « ~a ».\n"
-#: src/scm/webid-oidc/example-app.scm:368
+#: src/scm/webid-oidc/example-app.scm:367
msgid "Readline prompt|Command: "
msgstr "Commande : "
-#: src/scm/webid-oidc/example-app.scm:375
+#: src/scm/webid-oidc/example-app.scm:374
#, scheme-format
msgid "An error happened: ~a.\n"
msgstr "Une erreur est survenue : ~a.\n"
-#: src/scm/webid-oidc/example-app.scm:387
+#: src/scm/webid-oidc/example-app.scm:386
msgid "Please enter your identity provider: "
msgstr "Veuillez entrer votre fournisseur d’identité : "
-#: src/scm/webid-oidc/example-app.scm:393
+#: src/scm/webid-oidc/example-app.scm:392
msgid ""
"You don’t have other accounts available. Please add one with \"add-account"
"\".\n"
@@ -915,38 +738,38 @@ msgstr ""
"Vous n’avez pas d’autre compte disponible. Veuillez en ajouter un avec "
"« ajouter-compte ».\n"
-#: src/scm/webid-oidc/example-app.scm:399
+#: src/scm/webid-oidc/example-app.scm:398
#, scheme-format
msgid "- ~a: ~a\n"
msgstr "- ~a : ~a\n"
-#: src/scm/webid-oidc/example-app.scm:407
+#: src/scm/webid-oidc/example-app.scm:406
#, scheme-format
msgid "[1-~a] "
msgstr "[1-~a] "
-#: src/scm/webid-oidc/example-app.scm:415
+#: src/scm/webid-oidc/example-app.scm:414
msgid "Visit this URI: "
msgstr "Naviguer cette URI : "
-#: src/scm/webid-oidc/example-app.scm:421
+#: src/scm/webid-oidc/example-app.scm:420
msgid "Use this HTTP method [GET]: "
msgstr "Utiliser cette méthode HTTP [GET] : "
-#: src/scm/webid-oidc/example-app.scm:437
+#: src/scm/webid-oidc/example-app.scm:436
msgid "Which header? "
msgstr "Quel en-tête ? "
-#: src/scm/webid-oidc/example-app.scm:440
+#: src/scm/webid-oidc/example-app.scm:439
#, scheme-format
msgid "Which header value for ~a? "
msgstr "Quelle valeur pour l’en-tête ~a ? "
-#: src/scm/webid-oidc/example-app.scm:463
+#: src/scm/webid-oidc/example-app.scm:462
msgid "Please define an account and the URI.\n"
msgstr "Veuillez définir un compte et une URI.\n"
-#: src/scm/webid-oidc/example-app.scm:470
+#: src/scm/webid-oidc/example-app.scm:469
msgid "I don’t know that command.\n"
msgstr "Je ne connais pas cette commande.\n"
@@ -1083,7 +906,7 @@ msgid "The port should be a number between 0 and 65535.\n"
msgstr "Le port doit être un nombre entre 0 et 65535.\n"
#: src/scm/webid-oidc/hello-world.scm:159
-#: src/scm/webid-oidc/resource-server.scm:320
+#: src/scm/webid-oidc/resource-server.scm:332
msgid "reason-phrase|Unauthorized"
msgstr "Non Autorisé"
@@ -1096,7 +919,7 @@ msgid "<p>This page requires authentication with Solid.</p>"
msgstr "<p>Cette page requiert une authentification avec Solid.</p>"
#: src/scm/webid-oidc/hello-world.scm:179
-#: src/scm/webid-oidc/resource-server.scm:328
+#: src/scm/webid-oidc/resource-server.scm:340
msgid "reason-phrase|Method Not Allowed"
msgstr "Méthode Non Autorisée"
@@ -1184,46 +1007,66 @@ msgstr "type de contenu manquant"
msgid "invalid content-type: ~s"
msgstr "type de contenu invalide : ~s"
-#: src/scm/webid-oidc/jws.scm:73
+#: src/scm/webid-oidc/jws.scm:127
#, scheme-format
-msgid "the JWS is invalid: ~a"
-msgstr "le JWS est invalide : ~a"
+msgid "unsupported JWS algorithm: ~s"
+msgstr "algorithme JWS non supporté : ~s"
-#: src/scm/webid-oidc/jws.scm:75
-msgid "the JWS is invalid"
-msgstr "le JWS est invalide"
+#: src/scm/webid-oidc/jws.scm:137
+msgid ""
+"when making a token either #:alg or (#:jwt-header and #:jwt-payload) should "
+"be passed"
+msgstr ""
+"lors de la création d’un jeton il faut passer soit #:alg soit (#:jwt-header "
+"et #:jwt-payload)"
-#: src/scm/webid-oidc/jws.scm:94
-msgid "the JWS header does not have an \"alg\" field"
-msgstr "l’en-tête JWS n’a pas de champ « alg »"
+#: src/scm/webid-oidc/jws.scm:202
+msgid "#:iat should be a date"
+msgstr "#:iat doit être une date"
-#: src/scm/webid-oidc/jws.scm:102
-msgid "invalid JSON object as payload"
-msgstr "objet JSON invalide comme charge utile"
+#: src/scm/webid-oidc/jws.scm:207
+msgid "#:exp should be a date"
+msgstr "#:exp doit être une date"
-#: src/scm/webid-oidc/jws.scm:111
-#, scheme-format
-msgid "invalid signature algorithm: ~s"
-msgstr "algorithme de signature invalide : ~s"
+#: src/scm/webid-oidc/jws.scm:217
+msgid ""
+"when making a time-bound token, either its required fields (#:iat, and "
+"either #:exp or #:validity) or (#:jwt-header and #:jwt-payload) should be "
+"passed"
+msgstr ""
+"lors de la création d’un jeton limité dans le temps, il faut passer soit ses "
+"champs requis (#:iat et soit #:exp soit #:validity) soit (#:jwt-header et #:"
+"jwt-payload)"
-#: src/scm/webid-oidc/jws.scm:115
-#, scheme-format
-msgid "invalid \"alg\" value: ~s"
-msgstr "valeur « alg » invalide : ~s"
+#: src/scm/webid-oidc/jws.scm:245
+msgid "#:iss should be an URI"
+msgstr "#:iss doit être une URI"
-#: src/scm/webid-oidc/jws.scm:120
-msgid "invalid JSON object as header"
-msgstr "objet JSON d’en-tête invalide"
+#: src/scm/webid-oidc/jws.scm:256
+msgid ""
+"when making an OIDC token, either its required #:iss field or (#:jwt-header "
+"and #:jwt-payload) should be passed"
+msgstr ""
+"lors de la création d’un jeton OIDC, il faut passer soit le champs requis #:"
+"iss soit (#:jwt-header et #:jwt-payload)"
-#: src/scm/webid-oidc/jws.scm:122
-msgid "this is not a pair"
-msgstr "ce n’est pas une paire"
+#: src/scm/webid-oidc/jws.scm:300
+msgid "#:nonce should be a string"
+msgstr "#:nonce doit être une chaîne de caractères"
-#: src/scm/webid-oidc/jws.scm:139
+#: src/scm/webid-oidc/jws.scm:309
+msgid ""
+"when making a single-use token, either its required #:nonce field or (#:jwt-"
+"header and #:jwt-payload) should be passed"
+msgstr ""
+"lors de la création d’un jeton à usage unique, il faut soit passer le champs "
+"requis #:nonce soit (#:jwt-header et #:jwt-payload)"
+
+#: src/scm/webid-oidc/jws.scm:354
msgid "the encoded JWS is not in 3 parts"
msgstr "le JWS encodé n’est pas en 3 parties"
-#: src/scm/webid-oidc/jws.scm:150
+#: src/scm/webid-oidc/jws.scm:365
#, scheme-format
msgid ""
"the encoded JWS header or payload is not a JSON object encoded in base64: ~a"
@@ -1231,41 +1074,71 @@ msgstr ""
"l’en-tête ou la charge utile du JWS encodé n’est pas un objet JSON encodé en "
"base64 : ~a"
-#: src/scm/webid-oidc/jws.scm:152
+#: src/scm/webid-oidc/jws.scm:367
msgid ""
"the encoded JWS header or payload is not a JSON object encoded in base64"
msgstr ""
"l’en-tête ou la charge utile du JWS encodé n’est pas un objet JSON encodé en "
"base64"
-#: src/scm/webid-oidc/jws.scm:211
+#: src/scm/webid-oidc/jws.scm:426
msgid "the JWS is not signed by any of the expected set of public keys"
msgstr "le JWS n’est signé par aucune des clés attendues"
-#: src/scm/webid-oidc/jws.scm:222
+#: src/scm/webid-oidc/jws.scm:437
#, scheme-format
msgid "while verifying the JWS signature: ~a"
msgstr "en vérifiant la signature du JWS : ~a"
-#: src/scm/webid-oidc/jws.scm:224
+#: src/scm/webid-oidc/jws.scm:439
msgid "an unexpected error happened while verifying a JWS"
msgstr "une erreur inattendue est survenue pendant la vérification d’un JWS"
-#: src/scm/webid-oidc/jws.scm:253
+#: src/scm/webid-oidc/jws.scm:478
+#, scheme-format
+msgid "I cannot query the identity provider configuration: ~a"
+msgstr ""
+"je ne peux pas requêter la configuration du fournisseur d’identité : ~a"
+
+#: src/scm/webid-oidc/jws.scm:480
+msgid "I cannot query the identity provider configuration"
+msgstr "je ne peux pas requêter la configuration du fournisseur d’identité"
+
+#: src/scm/webid-oidc/jws.scm:501
+#, scheme-format
+msgid "I cannot query the JWKS URI of the identity provider: ~a"
+msgstr "je ne peux pas requêter l’URI de JWKS du fournisseur d’identité : ~a"
+
+#: src/scm/webid-oidc/jws.scm:503
+msgid "I cannot query the JWKS URI of the identity provider"
+msgstr "impossible de requêter l’URI de JWKS du fournisseur d’identité"
+
+#: src/scm/webid-oidc/jws.scm:531
+#, scheme-format
+msgid "the token is signed in the future, ~a, relative to current ~a"
+msgstr ""
+"le jeton est signé dans le futur, ~a, par rapport à la date courante, ~a"
+
+#: src/scm/webid-oidc/jws.scm:540
+#, scheme-format
+msgid "the token expired ~a, which is in the past (from ~a)"
+msgstr "le jeton a expiré le ~a, qui est dans le passé (depuis ~a)"
+
+#: src/scm/webid-oidc/jws.scm:563
#, scheme-format
msgid "cannot decode a JWS: ~a"
msgstr "impossible de décoder un JWS : ~a"
-#: src/scm/webid-oidc/jws.scm:255
+#: src/scm/webid-oidc/jws.scm:565
msgid "cannot decode a JWS"
msgstr "impossible de décoder un JWS"
-#: src/scm/webid-oidc/jws.scm:272
+#: src/scm/webid-oidc/jws.scm:583
#, scheme-format
msgid "cannot encode a JWS: ~a"
msgstr "impossible d’encoder un JWS : ~a"
-#: src/scm/webid-oidc/jws.scm:274
+#: src/scm/webid-oidc/jws.scm:585
msgid "cannot encode a JWS"
msgstr "impossible d’encoder un JWS"
@@ -1330,80 +1203,32 @@ msgstr "il n’y a pas de type de contenu"
msgid "unexpected content-type: ~s"
msgstr "type de contenu inattendu : ~s"
-#: src/scm/webid-oidc/oidc-id-token.scm:68
-#, scheme-format
-msgid "this is not an ID token, because it is not even a JWS: ~a"
-msgstr "ce n’est pas un jeton d’ID, parce que ce n’est même pas un JWS : ~a"
-
-#: src/scm/webid-oidc/oidc-id-token.scm:71
-msgid "this is not an ID token, because it is not even a JWS"
-msgstr "ce n’est pas un jeton d’ID, parce que ce n’est même pas un JWS"
-
-#: src/scm/webid-oidc/oidc-id-token.scm:73
-#, scheme-format
-msgid "this is not an ID token: ~a"
-msgstr "ce n’est pas un jeton d’ID : ~a"
-
-#: src/scm/webid-oidc/oidc-id-token.scm:76
-msgid "this is not an ID token"
-msgstr "ce n’est pas un jeton d’ID"
-
-#: src/scm/webid-oidc/oidc-id-token.scm:132
-#, scheme-format
-msgid "the \"sub\" field should be a string, ~s is given"
-msgstr "le champ « sub » doit être une chaîne de caractères, pas ~s"
-
-#: src/scm/webid-oidc/oidc-id-token.scm:139
-#, scheme-format
-msgid "the \"aud\" field should be an URI, ~s is given"
-msgstr "le champ « aud » doit être une URI, pas ~s"
-
-#: src/scm/webid-oidc/oidc-id-token.scm:146
-#, scheme-format
-msgid "the \"nonce\" field should be a string, ~s is given"
-msgstr "le champ « nonce » doit être une chaîne de caractères, pas ~s"
-
-#: src/scm/webid-oidc/oidc-id-token.scm:166
-msgid "the payload should be a JSON object"
-msgstr "la charge utile doit être un objet JSON"
-
-#: src/scm/webid-oidc/oidc-id-token.scm:219
+#: src/scm/webid-oidc/oidc-id-token.scm:72
#, scheme-format
-msgid "the ID token is invalid: ~a"
-msgstr "le jeton d’ID est invalide : ~a"
+msgid "invalid OIDC ID token: ~a"
+msgstr "jeton d’identité OIDC invalide : ~a"
-#: src/scm/webid-oidc/oidc-id-token.scm:221
-msgid "the ID token is invalid"
-msgstr "le jeton d’ID est invalide"
+#: src/scm/webid-oidc/oidc-id-token.scm:74
+msgid "invalid OIDC id token"
+msgstr "jeton d’identité OIDC invalide"
-#: src/scm/webid-oidc/oidc-id-token.scm:259
-#, scheme-format
-msgid "I cannot query the JWKS URI of the identity provider: ~a"
-msgstr "je ne peux pas requêter l’URI de JWKS du fournisseur d’identité : ~a"
+#: src/scm/webid-oidc/oidc-id-token.scm:105
+msgid "#:sub should be a string"
+msgstr "#:sub doit être une chaîne de caractères"
-#: src/scm/webid-oidc/oidc-id-token.scm:261
-msgid "I cannot query the JWKS URI of the identity provider"
-msgstr "impossible de requêter l’URI de JWKS du fournisseur d’identité"
+#: src/scm/webid-oidc/oidc-id-token.scm:110
+msgid "#:aud should be a string"
+msgstr "#:aud doit être une chaîne de caractères"
-#: src/scm/webid-oidc/oidc-id-token.scm:272
-#, scheme-format
-msgid "the ID token is signed in the future, ~a, relative to current ~a"
+#: src/scm/webid-oidc/oidc-id-token.scm:126
+msgid ""
+"when making an ID token either its required fields (#:alg, #:webid, #:iss, #:"
+"sub, #:aud, #:iat and #:exp) or (#:jwt-header and #:jwt-payload) should be "
+"passed"
msgstr ""
-"le jeton d’ID est signé dans le futur, ~a, par rapport à la date courante, ~a"
-
-#: src/scm/webid-oidc/oidc-id-token.scm:281
-#, scheme-format
-msgid "the ID token expired ~a, which is in the past (from ~a)"
-msgstr "le jeton d’ID a expiré le ~a, qui est dans le passé (depuis ~a)"
-
-#: src/scm/webid-oidc/oidc-id-token.scm:295
-#, scheme-format
-msgid "cannot encode the ID token: ~a"
-msgstr "impossible d’encoder le jeton d’ID : ~a"
-
-#: src/scm/webid-oidc/oidc-id-token.scm:297
-msgid "cannot encode the ID token"
-msgstr "impossible d’encoder le jeton d’ID"
+"lors de la création d’un jeton d’identité il faut soit passer ses champs "
+"requis (#:alg, #:webid, #:iss, #:sub, #:aud, #:iat et #:exp) soit (#:jwt-"
+"header et #:jwt-payload)"
#: src/scm/webid-oidc/program.scm:57
#, scheme-format
@@ -2387,47 +2212,47 @@ msgstr "~a : échec d’authentificationn : ~a\n"
msgid "~a: authentication failure\n"
msgstr "~a : échec d’authentification\n"
-#: src/scm/webid-oidc/resource-server.scm:160
-#: src/scm/webid-oidc/resource-server.scm:351
+#: src/scm/webid-oidc/resource-server.scm:172
+#: src/scm/webid-oidc/resource-server.scm:363
msgid "reason-phrase|Precondition Failed"
msgstr "Échec de Précondition"
-#: src/scm/webid-oidc/resource-server.scm:175
+#: src/scm/webid-oidc/resource-server.scm:187
msgid "reason-phrase|Not Modified"
msgstr "Non Modifié"
-#: src/scm/webid-oidc/resource-server.scm:191
+#: src/scm/webid-oidc/resource-server.scm:203
msgid "The owner is not defined."
msgstr "Le propriétaire n’est pas défini."
-#: src/scm/webid-oidc/resource-server.scm:263
+#: src/scm/webid-oidc/resource-server.scm:275
msgid "reason-phrase|Created"
msgstr "Créé"
-#: src/scm/webid-oidc/resource-server.scm:288
+#: src/scm/webid-oidc/resource-server.scm:300
#, scheme-format
msgid "~a: ignoring a group that cannot be fetched: ~a\n"
msgstr "~a : j’ignore un groupe qui n’a pas pu être téléchargé : ~a\n"
-#: src/scm/webid-oidc/resource-server.scm:292
+#: src/scm/webid-oidc/resource-server.scm:304
#, scheme-format
msgid "~a: ignoring a group that cannot be fetched\n"
msgstr "~a : j’ignore un groupe qui ne peut pas être téléchargé\n"
-#: src/scm/webid-oidc/resource-server.scm:316
-#: src/scm/webid-oidc/token-endpoint.scm:103
+#: src/scm/webid-oidc/resource-server.scm:328
+#: src/scm/webid-oidc/token-endpoint.scm:105
msgid "reason-phrase|Forbidden"
msgstr "Interdit"
-#: src/scm/webid-oidc/resource-server.scm:337
+#: src/scm/webid-oidc/resource-server.scm:349
msgid "reason-phrase|Conflict"
msgstr "Conflit"
-#: src/scm/webid-oidc/resource-server.scm:344
+#: src/scm/webid-oidc/resource-server.scm:356
msgid "reason-phrase|Unsupported Media Type"
msgstr "Type de Média Non Supporté"
-#: src/scm/webid-oidc/resource-server.scm:358
+#: src/scm/webid-oidc/resource-server.scm:370
msgid "reason-phrase|Not Acceptable"
msgstr "Inacceptable"
@@ -2506,51 +2331,51 @@ msgstr "pendant la mise à jour du fichier ~s : ~a"
msgid "an error happened while updating file ~s"
msgstr "une erreur est survenue pendant la mise à jour du fichier ~s"
-#: src/scm/webid-oidc/token-endpoint.scm:91
+#: src/scm/webid-oidc/token-endpoint.scm:93
#, scheme-format
msgid "while handling web failure for the token endpoint: ~a"
msgstr "lors de la gestion d’un échec web pour le terminal de jeton : ~a"
-#: src/scm/webid-oidc/token-endpoint.scm:93
+#: src/scm/webid-oidc/token-endpoint.scm:95
msgid "an error happened during the token endpoint failure handling"
msgstr ""
"une erreur est survenue pendant la gestion d’un échec du terminal de jeton"
-#: src/scm/webid-oidc/token-endpoint.scm:222
+#: src/scm/webid-oidc/token-endpoint.scm:225
msgid "missing grant type"
msgstr "type d’offre manquant"
-#: src/scm/webid-oidc/token-endpoint.scm:226
+#: src/scm/webid-oidc/token-endpoint.scm:229
msgid "<p>You did not specify a grant_type for this request.</p>"
msgstr "<p>Vous n’avez pas spécifié de grant_type pour cette requête.</p>"
-#: src/scm/webid-oidc/token-endpoint.scm:240
+#: src/scm/webid-oidc/token-endpoint.scm:243
msgid "missing authorization code"
msgstr "code d’autorisation manquant"
-#: src/scm/webid-oidc/token-endpoint.scm:244
+#: src/scm/webid-oidc/token-endpoint.scm:247
msgid ""
"<p>You want to grant an authorization code, but you did not set one.</p>"
msgstr ""
"<p>Vous voulez offrir un code d’autorisation, mais vous n’en avez pas défini."
"</p>"
-#: src/scm/webid-oidc/token-endpoint.scm:258
+#: src/scm/webid-oidc/token-endpoint.scm:268
msgid "missing refresh token"
msgstr "jeton de rafraîchissement manquant"
-#: src/scm/webid-oidc/token-endpoint.scm:262
+#: src/scm/webid-oidc/token-endpoint.scm:272
msgid "<p>You want to grant a refresh token, but you did not set one.</p>"
msgstr ""
"<p>Vous voulez offrir un jeton de rafraîchissement, mais vous n’en avez pas "
"défini.</p>"
-#: src/scm/webid-oidc/token-endpoint.scm:275
+#: src/scm/webid-oidc/token-endpoint.scm:285
#, scheme-format
msgid "unsupported grant type: ~s"
msgstr "type d’offre non supporté : ~s"
-#: src/scm/webid-oidc/token-endpoint.scm:280
+#: src/scm/webid-oidc/token-endpoint.scm:290
#, scheme-format
msgid ""
"<p>You want to use <pre>~s</pre> as a grant type, but this is not supported."
@@ -2560,6 +2385,244 @@ msgstr ""
"supporté.</p>"
#, scheme-format
+#~ msgid "this is not an access token, because it is not even a JWS: ~a"
+#~ msgstr ""
+#~ "ce n’est pas un jeton d’accès, parce que ce n’est même pas un JWS : ~a"
+
+#~ msgid "this is not an access token, because it is not even a JWS"
+#~ msgstr "ce n’est pas un jeton d’accès, parce que ce n’est même pas un JWS"
+
+#, scheme-format
+#~ msgid "the payload is missing ~s"
+#~ msgstr "il manque ~s à la charge utile"
+
+#, scheme-format
+#~ msgid "the \"webid\" field should be an URI, ~s is given"
+#~ msgstr "le champ « webid » doit être une URI, pas ~s"
+
+#, scheme-format
+#~ msgid "the \"iss\" field should be an URI, ~s is given"
+#~ msgstr "le champ « iss » doit être une URI, pas ~s"
+
+#, scheme-format
+#~ msgid "the \"aud\" field should be set to \"solid\", ~s is given"
+#~ msgstr "le champ « aud » doit être « solid », pas ~s"
+
+#, scheme-format
+#~ msgid "the \"iat\" field should be a timestamp, ~s is given"
+#~ msgstr "le champ « iat » doit être un horodatage, pas ~s"
+
+#, scheme-format
+#~ msgid "the \"exp\" field should be a timestamp, ~s is given"
+#~ msgstr "le champ « exp » doit être un horodatage, pas ~s"
+
+#~ msgid "the \"cnf\" / \"jkt\" field is missing"
+#~ msgstr "le champ « cnf » / « jkt » est manquant"
+
+#, scheme-format
+#~ msgid "the \"cnf\" / \"jkt\" field should be a string, ~s is given"
+#~ msgstr ""
+#~ "le champ « cnf » / « jkt » doit être une chaîne de caractères, pas ~s"
+
+#, scheme-format
+#~ msgid "the \"cnf\" field should be an object, ~s is given"
+#~ msgstr "le champ « cnf » doit être un objet JSON, pas ~s"
+
+#, scheme-format
+#~ msgid "the access token is invalid: ~a"
+#~ msgstr "le jeton d’accès est invalide : ~a"
+
+#~ msgid "the access token is invalid"
+#~ msgstr "le jeton d’accès est invalide"
+
+#, scheme-format
+#~ msgid "I cannot query the identity provider public keys: ~a"
+#~ msgstr ""
+#~ "je ne peux pas requêter les clés publiques du fournisseur d’identité : ~a"
+
+#~ msgid "I cannot query the identity provider public keys"
+#~ msgstr ""
+#~ "je ne peux pas requêter les clés publiques du fournisseur d’identité"
+
+#, scheme-format
+#~ msgid "the access token is signed in the future, ~a, relative to current ~a"
+#~ msgstr ""
+#~ "le jeton d’accès est signé dans le futur, le ~a, par rapport à la date "
+#~ "courante, le ~a"
+
+#, scheme-format
+#~ msgid "the access token expired ~a, which is in the past (from ~a)"
+#~ msgstr "le jeton d’accès a expiré le ~a, qui est dans le passé (de ~a)"
+
+#, scheme-format
+#~ msgid "cannot encode the access token: ~a"
+#~ msgstr "impossible d’encoder le jeton d’accès : ~a"
+
+#~ msgid "cannot encode the access token"
+#~ msgstr "impossible d’encoder le jeton d’accès"
+
+#, scheme-format
+#~ msgid "this is not an authorization code, because it is not even a JWS: ~a"
+#~ msgstr ""
+#~ "ce n’est pas un code d’autorisation, parce que ce n’est même pas un JWS : "
+#~ "~a"
+
+#~ msgid "this is not an authorization code, because it is not even a JWS"
+#~ msgstr ""
+#~ "ce n’est pas un code d’autorisation, parce que ce n’est même pas un JWS"
+
+#, scheme-format
+#~ msgid "this is not an authorization code: ~a"
+#~ msgstr "ce n’est pas un code d’autorisation : ~a"
+
+#~ msgid "this is not an authorization code"
+#~ msgstr "ce n’est pas un code d’autorisation"
+
+#, scheme-format
+#~ msgid "the \"jti\" field should be a string, ~s is given"
+#~ msgstr "le champ « jti » doit être une chaîne de caractères, pas ~s"
+
+#, scheme-format
+#~ msgid "the authorization code is invalid: ~a"
+#~ msgstr "le code d’autorisation est invalide : ~a"
+
+#~ msgid "the authorization code is invalid"
+#~ msgstr "le code d’autorisation est invalide"
+
+#, scheme-format
+#~ msgid "the authorization expired ~a, which is in the past (from ~a)"
+#~ msgstr ""
+#~ "le code d’autorisation a expiré le ~a, qui est dans le passé (depuis ~a)"
+
+#, scheme-format
+#~ msgid "cannot encode the authorization code: ~a"
+#~ msgstr "impossible d’encoder le code d’autorisation : ~a"
+
+#~ msgid "cannot encode the authorization code"
+#~ msgstr "impossible d’encoder le code d’autorisation"
+
+#, scheme-format
+#~ msgid "this is not a DPoP proof, because it is not even a JWS: ~a"
+#~ msgstr ""
+#~ "ce n’est pas une preuve DPoP, parce que ce n’est même pas un JWS : ~a"
+
+#~ msgid "this is not a DPoP proof, because it is not even a JWS"
+#~ msgstr "ce n’est pas une preuve DPoP, parce que ce n’est même pas un JWS"
+
+#, scheme-format
+#~ msgid "the DPoP proof is missing ~s"
+#~ msgstr "il manque ~s à la preuve DPoP"
+
+#, scheme-format
+#~ msgid "the \"htm\" field should be a string, not ~s"
+#~ msgstr "le champ « htm » doit être une chaîne de caractères, pas ~s"
+
+#, scheme-format
+#~ msgid "the \"iat\" field should be a timestamp, not ~s"
+#~ msgstr "le champ « iat » doit être un horodatage, pas ~s"
+
+#, scheme-format
+#~ msgid "the \"ath\" field should be an encoded JWT, not ~s"
+#~ msgstr "le champ « ath » doit être un JWT encodé, pas ~s"
+
+#, scheme-format
+#~ msgid "the \"alg\" field should be a string, not ~s"
+#~ msgstr "le champ « alg » doit être une chaîne de caractères, pas ~s"
+
+#~ msgid "the \"jwk\" field should not contain the private key"
+#~ msgstr "le champ « jwk » ne doit pas contenir la clé privée"
+
+#, scheme-format
+#~ msgid "the DPoP proof cannot be decoded: ~a"
+#~ msgstr "impossible de décoder la preuve DPoP : ~a"
+
+#~ msgid "the DPoP proof cannot be decoded"
+#~ msgstr "impossible de décoder la preuve DPoP"
+
+#, scheme-format
+#~ msgid ""
+#~ "the DPoP proof is signed in the future, ~a, relative to the current date, "
+#~ "~a"
+#~ msgstr ""
+#~ "la preuve DPoP est signée dans le futur, le ~a, par rapport à la date "
+#~ "courante, ~a"
+
+#, scheme-format
+#~ msgid "the DPoP proof is too old, it was signed ~a and now it is ~a"
+#~ msgstr ""
+#~ "la preuve DPoP est trop vieille, elle a été signée le ~a et nous sommes "
+#~ "maintenant le ~a"
+
+#~ msgid "cannot encode a DPoP proof"
+#~ msgstr "impossible d’encoder la preuve DPoP"
+
+#, scheme-format
+#~ msgid "the JWS is invalid: ~a"
+#~ msgstr "le JWS est invalide : ~a"
+
+#~ msgid "the JWS is invalid"
+#~ msgstr "le JWS est invalide"
+
+#~ msgid "the JWS header does not have an \"alg\" field"
+#~ msgstr "l’en-tête JWS n’a pas de champ « alg »"
+
+#~ msgid "invalid JSON object as payload"
+#~ msgstr "objet JSON invalide comme charge utile"
+
+#, scheme-format
+#~ msgid "invalid signature algorithm: ~s"
+#~ msgstr "algorithme de signature invalide : ~s"
+
+#, scheme-format
+#~ msgid "invalid \"alg\" value: ~s"
+#~ msgstr "valeur « alg » invalide : ~s"
+
+#~ msgid "invalid JSON object as header"
+#~ msgstr "objet JSON d’en-tête invalide"
+
+#~ msgid "this is not a pair"
+#~ msgstr "ce n’est pas une paire"
+
+#, scheme-format
+#~ msgid "this is not an ID token, because it is not even a JWS: ~a"
+#~ msgstr "ce n’est pas un jeton d’ID, parce que ce n’est même pas un JWS : ~a"
+
+#~ msgid "this is not an ID token, because it is not even a JWS"
+#~ msgstr "ce n’est pas un jeton d’ID, parce que ce n’est même pas un JWS"
+
+#, scheme-format
+#~ msgid "this is not an ID token: ~a"
+#~ msgstr "ce n’est pas un jeton d’ID : ~a"
+
+#~ msgid "this is not an ID token"
+#~ msgstr "ce n’est pas un jeton d’ID"
+
+#, scheme-format
+#~ msgid "the \"sub\" field should be a string, ~s is given"
+#~ msgstr "le champ « sub » doit être une chaîne de caractères, pas ~s"
+
+#, scheme-format
+#~ msgid "the \"aud\" field should be an URI, ~s is given"
+#~ msgstr "le champ « aud » doit être une URI, pas ~s"
+
+#~ msgid "the payload should be a JSON object"
+#~ msgstr "la charge utile doit être un objet JSON"
+
+#, scheme-format
+#~ msgid "the ID token is invalid: ~a"
+#~ msgstr "le jeton d’ID est invalide : ~a"
+
+#~ msgid "the ID token is invalid"
+#~ msgstr "le jeton d’ID est invalide"
+
+#, scheme-format
+#~ msgid "cannot encode the ID token: ~a"
+#~ msgstr "impossible d’encoder le jeton d’ID : ~a"
+
+#~ msgid "cannot encode the ID token"
+#~ msgstr "impossible d’encoder le jeton d’ID"
+
+#, scheme-format
#~ msgid "unknown key type ~s"
#~ msgstr "type de clé inconnu ~s"
diff --git a/src/scm/webid-oidc/access-token.scm b/src/scm/webid-oidc/access-token.scm
index 7e67270..7c23126 100644
--- a/src/scm/webid-oidc/access-token.scm
+++ b/src/scm/webid-oidc/access-token.scm
@@ -29,7 +29,17 @@
#:use-module (srfi srfi-19)
#:use-module (srfi srfi-26)
#:use-module (ice-9 exceptions)
+ #:use-module (ice-9 receive)
+ #:use-module (oop goops)
#:declarative? #t
+ #:re-export
+ (
+ alg iat exp iss
+ token->jwt
+ decode
+ encode
+ issue
+ )
#:export
(
@@ -37,21 +47,7 @@
make-invalid-access-token
invalid-access-token?
- the-access-token
- access-token?
-
- access-token-alg
-
- access-token-webid
- access-token-iss
- access-token-aud
- access-token-iat
- access-token-exp
- access-token-client-id
- access-token-cnf/jkt
-
- access-token-decode
- issue-access-token
+ <access-token> webid aud client-id cnf/jkt
))
(define-exception-type
@@ -60,289 +56,109 @@
make-invalid-access-token
invalid-access-token?)
-;; The order is meaningful in this module, the-access-token reorders
-;; them.
-(define (the-access-token x)
- (with-exception-handler
- (lambda (error)
- (let ((final-message
- (cond
- ((invalid-jws? error)
- (if (exception-with-message? error)
- (format #f (G_ "this is not an access token, because it is not even a JWS: ~a")
- (exception-message error))
- (format #f (G_ "this is not an access token, because it is not even a JWS"))))
- (else
- (if (exception-with-message? error)
- (format #f (G_ "this is not an access token: ~a")
- (exception-message error))
- (format #f (G_ "this is not an access token")))))))
- (raise-exception
- (make-exception
- (make-invalid-access-token)
- (make-exception-with-message final-message)
- error))))
- (lambda ()
- (match (the-jws x)
- ((header . payload)
- (let examine-payload ((payload payload)
- (webid #f)
- (iss #f)
- (aud #f)
- (iat #f)
- (exp #f)
- (cnf #f)
- (client-id #f)
- (other-fields '()))
- (match payload
- (()
- (unless (and webid iss aud iat exp cnf client-id)
- ;; Missing some things
- (fail (format #f (G_ "the payload is missing ~s")
- `(,@(if webid '() '("webid"))
- ,@(if iss '() '("iss"))
- ,@(if aud '() '("aud"))
- ,@(if iat '() '("iat"))
- ,@(if exp '() '("exp"))
- ,@(if cnf '() '("cnf"))
- ,@(if client-id '() '("client_id"))))))
- `(,header
- . ((webid . ,(uri->string webid))
- (iss . ,(uri->string iss))
- (aud . "solid")
- (iat . ,(time-second (date->time-utc iat)))
- (exp . ,(time-second (date->time-utc exp)))
- (client_id . ,(uri->string client-id))
- (cnf . ,cnf)
- ,@(reverse other-fields))))
- ((('webid . (? string? (= string->uri (? uri? webid-given)))) payload ...)
- (examine-payload payload
- (or webid webid-given)
- iss aud iat exp cnf client-id other-fields))
- ((('webid . infringing) payload ...)
- (fail (format #f (G_ "the \"webid\" field should be an URI, ~s is given")
- infringing)))
- ((('iss . (? string? (= string->uri (? uri? iss-given)))) payload ...)
- (examine-payload payload webid
- (or iss iss-given)
- aud iat exp cnf client-id other-fields))
- ((('iss . infringing) payload ...)
- (fail (format #f (G_ "the \"iss\" field should be an URI, ~s is given")
- infringing)))
- ((('aud . "solid") payload ...)
- (examine-payload payload webid iss #t iat exp cnf client-id other-fields))
- ((('aud . infringing) payload ...)
- (fail (format #f (G_ "the \"aud\" field should be set to \"solid\", ~s is given")
- infringing)))
- ((('iat . (? (cute >= <> 0) (? integer? iat-given))) payload ...)
- (examine-payload payload webid iss aud
- (or iat (time-utc->date (make-time time-utc 0 iat-given)))
- exp cnf client-id other-fields))
- ((('iat . infringing) payload ...)
- (fail (format #f (G_ "the \"iat\" field should be a timestamp, ~s is given")
- infringing)))
- ((('exp . (? (cute >= <> 0) (? integer? exp-given))) payload ...)
- (examine-payload payload webid iss aud iat
- (or exp (time-utc->date (make-time time-utc 0 exp-given)))
- cnf client-id other-fields))
- ((('exp . infringing) payload ...)
- (fail (format #f (G_ "the \"exp\" field should be a timestamp, ~s is given")
- infringing)))
- ((('cnf . cnf) payload ...)
- (let examine-cnf ((data cnf)
- (jkt #f)
- (other-cnf-fields '()))
- (match data
- (()
- (unless jkt
- (fail (format #f (G_ "the \"cnf\" / \"jkt\" field is missing"))))
- (examine-payload payload webid iss aud iat exp
- `((jkt . ,jkt)
- ,@(reverse other-cnf-fields))
- client-id other-fields))
- ((('jkt . (? string? jkt-given)) data ...)
- (examine-cnf data (or jkt jkt-given other-cnf-fields) other-cnf-fields))
- ((('jkt . infringing) _ ...)
- (fail (format #f (G_ "the \"cnf\" / \"jkt\" field should be a string, ~s is given")
- infringing)))
- ((field data ...)
- (examine-cnf data jkt `(,field ,@other-cnf-fields)))
- (data
- (fail (format #f (G_ "the \"cnf\" field should be an object, ~s is given")
- data))))))
- ((('client_id . (? string? (= string->uri (? uri? client-id-given)))) payload ...)
- (examine-payload payload webid iss aud iat exp cnf
- (or client-id client-id-given)
- other-fields))
- ((('client_id . infringing) payload ...)
- (fail (format #f (G_ "the \"client_id\" field should be an URI, ~s is given")
- infringing)))
- ((field payload ...)
- (examine-payload payload webid iss aud iat exp cnf client-id
- `(,field ,@other-fields))))))
- (else
- (scm-error 'wrong-type-arg "the-access-token"
- "expected a pair of lists"
- '()
- (list x)))))))
-
-(define (access-token? x)
- (false-if-exception (the-access-token x)))
-
-(define (access-token-alg code)
- (match (the-access-token code)
- ((header . _)
- (string->symbol (assq-ref header 'alg)))))
-
-(define (access-token-webid code)
- (match (the-access-token code)
- ((_ . payload)
- (string->uri (assq-ref payload 'webid)))))
-
-(define (access-token-iss code)
- (match (the-access-token code)
- ((_ . payload)
- (string->uri (assq-ref payload 'iss)))))
+(define-class <access-token> (<time-bound-token> <oidc-token>)
+ (webid #:init-keyword #:webid #:accessor webid)
+ (aud #:init-keyword #:aud #:accessor aud)
+ (client-id #:init-keyword #:client-id #:accessor client-id)
+ (cnf/jkt #:init-keyword #:cnf/jkt #:accessor cnf/jkt))
-(define (access-token-aud code)
- (match (the-access-token code)
- ((_ . payload)
- (assq-ref payload 'aud))))
-
-(define (access-token-iat code)
- (match (the-access-token code)
- ((_ . payload)
- (time-utc->date
- (make-time time-utc 0 (assq-ref payload 'iat))))))
-
-(define (access-token-exp code)
- (match (the-access-token code)
- ((_ . payload)
- (time-utc->date
- (make-time time-utc 0 (assq-ref payload 'exp))))))
-
-(define (access-token-client-id code)
- (match (the-access-token code)
- ((_ . payload)
- (string->uri (assq-ref payload 'client-id)))))
-
-(define (access-token-cnf/jkt code)
- (match (the-access-token code)
- ((_ . payload)
- (assq-ref (assq-ref payload 'cnf) 'jkt))))
-
-(define* (access-token-decode str #:key (http-get http-get))
+(define-method (initialize (token <access-token>) initargs)
(with-exception-handler
(lambda (error)
- (let ((final-message
- (if (exception-with-message? error)
- (format #f (G_ "the access token is invalid: ~a")
- (exception-message error))
- (format #f (G_ "the access token is invalid")))))
- (raise-exception
- (make-exception
- (make-invalid-access-token)
- (make-exception-with-message final-message)
- error))))
+ (raise-exception
+ (make-exception
+ (make-invalid-access-token)
+ (make-exception-with-message
+ (if (exception-with-message? error)
+ (format #f (G_ "invalid access token: ~a")
+ (exception-message error))
+ (G_ "invalid access token")))
+ error)))
(lambda ()
- (jws-decode
- str
- (lambda (token)
- (let* ((iss (access-token-iss token))
- (cfg
- (with-exception-handler
- (lambda (error)
- (let ((final-message
- (if (exception-with-message? error)
- (format #f (G_ "I cannot query the identity provider configuration: ~a")
- (exception-message error))
- (format #f (G_ "I cannot query the identity provider configuratioon")))))
- (raise-exception
- (make-exception
- (make-cannot-query-identity-provider iss)
- (make-exception-with-message final-message)
- error))))
- (lambda ()
- (get-oidc-configuration
- (uri-host iss)
- #:userinfo (uri-userinfo iss)
- #:port (uri-port iss)
- #:http-get http-get))))
- (jwks
- (with-exception-handler
- (lambda (error)
- (let ((final-message
- (if (exception-with-message? error)
- (format #f (G_ "I cannot query the identity provider public keys: ~a")
- (exception-message error))
- (format #f (G_ "I cannot query the identity provider public keys")))))
- (raise-exception
- (make-exception
- (make-cannot-query-identity-provider iss)
- (make-exception-with-message final-message)
- error))))
- (lambda ()
- (oidc-configuration-jwks cfg #:http-get http-get)))))
- (let ((iat (access-token-iat token))
- (exp (access-token-exp token))
- (current-date ((p:current-date))))
- (let ((iat-s (time-second (date->time-utc iat)))
- (exp-s (time-second (date->time-utc exp)))
- (current-s (time-second (date->time-utc current-date))))
- (when (>= iat-s (+ current-s 5))
- (let ((final-message
- (format #f (G_ "the access token is signed in the future, ~a, relative to current ~a")
- (date->string iat)
- (date->string current-date))))
- (raise-exception
- (make-exception
- (make-signed-in-future iat current-date)
- (make-exception-with-message final-message)))))
- (when (>= current-s exp-s)
- (let ((final-message
- (format #f (G_ "the access token expired ~a, which is in the past (from ~a)")
- (date->string exp)
- (date->string current-date))))
- (raise-exception
- (make-exception
- (make-expired exp current-date)
- (make-exception-with-message final-message)))))))
- jwks))))))
-
-(define (access-token-encode access-token key)
- (with-exception-handler
- (lambda (error)
- (let ((final-message
- (if (exception-with-message? error)
- (format #f (G_ "cannot encode the access token: ~a")
- (exception-message error))
- (format #f (G_ "cannot encode the access token")))))
- (raise-exception
- (make-exception-with-message final-message))))
- (lambda ()
- (jws-encode access-token key))))
-
-(define* (issue-access-token
- issuer-key
- #:key
- (webid #f)
- (iss #f)
- (validity 3600)
- (client-key #f)
- (cnf/jkt #f)
- (client-id #f))
- (when client-key
- (set! cnf/jkt (jkt client-key)))
- (let* ((iat (time-second (date->time-utc ((p:current-date)))))
- (exp (+ iat validity)))
- (jws-encode
- (the-access-token
- `(((alg . ,(symbol->string (alg issuer-key))))
- . ((webid . ,(uri->string webid))
- (iss . ,(uri->string iss))
- (aud . "solid")
- (iat . ,iat)
- (exp . ,exp)
- (cnf . ((jkt . ,cnf/jkt)))
- (client_id . ,(uri->string client-id)))))
- issuer-key)))
+ (next-method)
+ (let-keywords
+ initargs #t
+ ((webid #f)
+ (aud "solid")
+ (client-id #f)
+ (cnf/jkt #f)
+ (client-key #f)
+ (jwt-header #f)
+ (jwt-payload #f))
+ (let do-initialize ((webid webid)
+ (aud aud)
+ (client-id client-id)
+ (cnf/jkt cnf/jkt)
+ (client-key client-key)
+ (jwt-header jwt-header)
+ (jwt-payload jwt-payload))
+ (cond
+ ((string? webid)
+ (do-initialize (string->uri webid)
+ aud
+ client-id
+ cnf/jkt
+ client-key
+ jwt-header
+ jwt-payload))
+ ((string? client-id)
+ (do-initialize webid
+ aud
+ (string->uri client-id)
+ cnf/jkt
+ client-key
+ jwt-header
+ jwt-payload))
+ ((and (not cnf/jkt) client-key)
+ (do-initialize webid aud client-id (jkt client-key) #f jwt-header jwt-payload))
+ ((and webid client-id cnf/jkt)
+ (unless (uri? webid)
+ (scm-error 'wrong-type-arg "make"
+ (G_ "#:webid should be an URI")
+ '()
+ (list webid)))
+ (unless (uri? client-id)
+ (scm-error 'wrong-type-arg "make"
+ (G_ "#:client-id should be an URI")
+ '()
+ (list client-id)))
+ (unless (string? cnf/jkt)
+ (scm-error 'wrong-type-arg "make"
+ (G_ "#:cnf/jkt should be a string")
+ '()
+ (list cnf/jkt)))
+ (unless (equal? aud "solid")
+ (scm-error 'wrong-type-arg "make"
+ (G_ "#:aud should be exactly \"solid\"")
+ '()
+ (list aud)))
+ (slot-set! token 'webid webid)
+ (slot-set! token 'aud aud)
+ (slot-set! token 'client-id client-id)
+ (slot-set! token 'cnf/jkt cnf/jkt))
+ ((and jwt-header jwt-payload)
+ (do-initialize (assq-ref jwt-payload 'webid)
+ (assq-ref jwt-payload 'aud)
+ (assq-ref jwt-payload 'client_id)
+ (assq-ref (assq-ref jwt-payload 'cnf) 'jkt)
+ #f #f #f))
+ (else
+ (raise-exception
+ (make-exception
+ (make-invalid-jws)
+ (make-exception-with-message
+ (G_ "when making an access token either its required fields (#:alg, #:webid, #:iss, #:aud, #:client-id, #:cnf/jkt, #:iat and #:exp) or (#:jwt-header and #:jwt-payload) should be passed")))))))))))
+
+(define-method (token->jwt (token <access-token>))
+ (receive (base-header base-payload)
+ (next-method)
+ (values
+ base-header
+ `((webid . ,(uri->string (webid token)))
+ (iss . ,(uri->string (iss token)))
+ (aud . ,(aud token))
+ (client_id . ,(uri->string (client-id token)))
+ (cnf . ((jkt . ,(cnf/jkt token))))
+ (iat . ,(time-second (date->time-utc (iat token))))
+ (exp . ,(time-second (date->time-utc (exp token))))
+ ,@base-payload))))
diff --git a/src/scm/webid-oidc/authorization-code.scm b/src/scm/webid-oidc/authorization-code.scm
index 1481b2c..13b7ac4 100644
--- a/src/scm/webid-oidc/authorization-code.scm
+++ b/src/scm/webid-oidc/authorization-code.scm
@@ -26,7 +26,18 @@
#:use-module (webid-oidc web-i18n)
#:use-module (ice-9 match)
#:use-module (ice-9 exceptions)
+ #:use-module (ice-9 receive)
+ #:use-module (ice-9 optargs)
+ #:use-module (oop goops)
#:declarative? #t
+ #:re-export
+ (
+ alg iat exp nonce
+ token->jwt
+ decode
+ encode
+ issue
+ )
#:export
(
@@ -34,18 +45,7 @@
make-invalid-authorization-code
invalid-authorization-code?
- the-authorization-code
- authorization-code?
-
- authorization-code-alg
-
- authorization-code-webid
- authorization-code-client-id
- authorization-code-jti
- authorization-code-exp
-
- authorization-code-decode
- issue-authorization-code
+ <authorization-code> webid client-id
))
(define-exception-type
@@ -54,171 +54,74 @@
make-invalid-authorization-code
invalid-authorization-code?)
-(define (the-authorization-code x)
- (with-exception-handler
- (lambda (error)
- (let ((final-message
- (cond
- ((invalid-jws? error)
- (if (exception-with-message? error)
- (format #f (G_ "this is not an authorization code, because it is not even a JWS: ~a")
- (exception-message error))
- (format #f (G_ "this is not an authorization code, because it is not even a JWS"))))
- (else
- (if (exception-with-message? error)
- (format #f (G_ "this is not an authorization code: ~a")
- (exception-message error))
- (format #f (G_ "this is not an authorization code")))))))
- (raise-exception
- (make-exception
- (make-invalid-authorization-code)
- (make-exception-with-message final-message)
- error))))
- (lambda ()
- (match (the-jws x)
- ((header . payload)
- (let examine-payload ((payload payload)
- (webid #f)
- (client-id #f)
- (jti #f)
- (exp #f)
- (other-fields '()))
- (match payload
- (()
- (unless (and webid client-id jti exp)
- (fail (format #f (G_ "the payload is missing ~s")
- `(,@(if webid '() '("webid"))
- ,@(if client-id '() '("client_id"))
- ,@(if jti '() '("jti"))
- ,@(if exp '() '("exp"))))))
- `(,header
- . ((webid . ,(uri->string webid))
- (client_id . ,(uri->string client-id))
- (jti . ,jti)
- (exp . ,(time-second (date->time-utc exp)))
- ,@(reverse other-fields))))
- ((('webid . (? string? (= string->uri (? uri? webid-given)))) payload ...)
- (examine-payload payload
- (or webid webid-given)
- client-id jti exp other-fields))
- ((('webid . infringing) payload ...)
- (fail (format #f (G_ "the \"webid\" field should be an URI, ~s is given")
- infringing)))
- ((('client_id . (? string? (= string->uri (? uri? client-id-given)))) payload ...)
- (examine-payload payload webid
- (or client-id client-id-given)
- jti exp other-fields))
- ((('client_id . infringing) payload ...)
- (fail (format #f (G_ "the \"client_id\" field should be an URI, ~s is given")
- infringing)))
- ((('jti . (? string? jti-given)) payload ...)
- (examine-payload payload webid client-id
- (or jti jti-given)
- exp other-fields))
- ((('jti . invalid) payload ...)
- (fail (format #f (G_ "the \"jti\" field should be a string, ~s is given")
- invalid)))
- ((('exp . (? (lambda (x) (and (integer? x) (>= x 0))) exp-given)) payload ...)
- (examine-payload payload webid client-id jti
- (or exp (time-utc->date (make-time time-utc 0 exp-given)))
- other-fields))
- ((('exp . infringing) payload ...)
- (fail (format #f (G_ "the \"exp\" field should be a timestamp, ~s is given")
- infringing)))
- ((field payload ...)
- (examine-payload payload webid client-id jti exp `(,field ,@other-fields))))))
- (else
- (scm-error 'wrong-type-arg "the-authorization-code"
- "expected a pair of lists"
- '()
- (list x)))))))
-
-(define (authorization-code? x)
- (false-if-exception (the-authorization-code x)))
+(define-class <authorization-code> (<single-use-token>)
+ (webid #:init-keyword #:webid #:accessor webid)
+ (client-id #:init-keyword #:client-id #:accessor client-id))
-(define (authorization-code-alg x)
- (match (the-authorization-code x)
- ((header . _)
- (string->symbol (assq-ref header 'alg)))))
-
-(define (authorization-code-webid x)
- (match (the-authorization-code x)
- ((_ . payload)
- (string->uri (assq-ref payload 'webid)))))
-
-(define (authorization-code-client-id x)
- (match (the-authorization-code x)
- ((_ . payload)
- (string->uri (assq-ref payload 'client_id)))))
-
-(define (authorization-code-jti x)
- (match (the-authorization-code x)
- ((_ . payload)
- (assq-ref payload 'jti))))
-
-(define (authorization-code-exp x)
- (match (the-authorization-code x)
- ((_ . payload)
- (time-utc->date (make-time time-utc 0 (assq-ref payload 'exp))))))
-
-(define (authorization-code-decode str jwk)
- (parameterize ((p:current-date
- (time-second (date->time-utc ((p:current-date))))))
- (with-exception-handler
- (lambda (error)
- (let ((final-message
- (if (exception-with-message? error)
- (format #f (G_ "the authorization code is invalid: ~a")
- (exception-message error))
- (format #f (G_ "the authorization code is invalid")))))
- (raise-exception
- (make-exception
- (make-invalid-authorization-code)
- (make-exception-with-message final-message)
- error))))
- (lambda ()
- (let ((code (the-authorization-code (jws-decode str (lambda (x) jwk)))))
- (let ((exp (authorization-code-exp code))
- (current-date ((p:current-date))))
- (let ((exp-s (time-second (date->time-utc exp)))
- (current-s (time-second (date->time-utc current-date))))
- (when (>= current-s exp-s)
- (let ((final-message
- (format #f (G_ "the authorization expired ~a, which is in the past (from ~a)")
- (date->string exp)
- (date->string current-date))))
- (raise-exception
- (make-exception
- (make-expired exp current-date)
- (make-exception-with-message final-message)))))
- (jti-check (authorization-code-jti code)
- (- exp-s current-s))
- code)))))))
-
-(define (authorization-code-encode authorization-code key)
+(define-method (initialize (token <authorization-code>) initargs)
(with-exception-handler
(lambda (error)
- (let ((final-message
- (if (exception-with-message? error)
- (format #f (G_ "cannot encode the authorization code: ~a")
- (exception-message error))
- (format #f (G_ "cannot encode the authorization code")))))
- (raise-exception
- (make-exception-with-message final-message))))
+ (raise-exception
+ (make-exception
+ (make-invalid-authorization-code)
+ (make-exception-with-message
+ (if (exception-with-message? error)
+ (format #f (G_ "invalid authorization code: ~a")
+ (exception-message error))
+ (G_ "invalid authorization code")))
+ error)))
(lambda ()
- (jws-encode authorization-code key))))
-
-(define* (issue-authorization-code issuer-key
- #:key
- (validity 120)
- webid
- client-id)
- (let* ((iat (time-second (date->time-utc ((p:current-date)))))
- (exp (+ iat validity)))
- (authorization-code-encode
- `(((alg . ,(symbol->string (alg issuer-key))))
- . ((webid . ,(uri->string webid))
- (client_id . ,(uri->string client-id))
- (exp . ,exp)
- (jti . ,(stubs:random 12))))
- issuer-key)))
+ (next-method)
+ (let-keywords
+ initargs #t
+ ((webid #f)
+ (client-id #f)
+ (jwt-header #f)
+ (jwt-payload #f))
+ (let do-initialize ((webid webid)
+ (client-id client-id)
+ (jwt-header jwt-header)
+ (jwt-payload jwt-payload))
+ (cond
+ ((string? webid)
+ (do-initialize (string->uri webid) client-id jwt-header jwt-payload))
+ ((string? client-id)
+ (do-initialize webid (string->uri client-id) jwt-header jwt-payload))
+ ((and webid client-id)
+ (unless (uri? webid)
+ (scm-error 'wrong-type-arg "make"
+ (G_ "#:webid should be an URI")
+ '()
+ (list webid)))
+ (unless (uri? client-id)
+ (scm-error 'wrong-type-arg "make"
+ (G_ "#:client-id should be a string")
+ '()
+ (list client-id)))
+ (slot-set! token 'webid webid)
+ (slot-set! token 'client-id client-id))
+ ((and jwt-header jwt-payload)
+ (do-initialize (assq-ref jwt-payload 'webid)
+ (assq-ref jwt-payload 'client_id)
+ #f #f))
+ (else
+ (raise-exception
+ (make-exception
+ (make-invalid-jws)
+ (make-exception-with-message
+ (G_ "when making an authorization code either its required fields (#:webid and #:client-id) or (#:jwt-header and #:jwt-payload) should be passed")))))))))))
+
+(define-method (token->jwt (token <authorization-code>))
+ (receive (base-header base-payload)
+ (next-method)
+ (values
+ base-header
+ `((webid . ,(uri->string (webid token)))
+ (client_id . ,(uri->string (client-id token)))
+ ,@base-payload))))
+
+(define-method (lookup-keys (token <authorization-code>) args)
+ (let-keywords
+ args #f
+ ((issuer-key #f))
+ issuer-key))
diff --git a/src/scm/webid-oidc/authorization-endpoint.scm b/src/scm/webid-oidc/authorization-endpoint.scm
index cf45a9c..4f171a2 100644
--- a/src/scm/webid-oidc/authorization-endpoint.scm
+++ b/src/scm/webid-oidc/authorization-endpoint.scm
@@ -106,7 +106,7 @@
(lambda (error)
(error-application locale error))
(lambda ()
- (let ((code (issue-authorization-code
+ (let ((code (issue <authorization-code>
jwk
#:webid subject
#:client-id client-id))
diff --git a/src/scm/webid-oidc/client.scm b/src/scm/webid-oidc/client.scm
index 5b6b0ef..006c86a 100644
--- a/src/scm/webid-oidc/client.scm
+++ b/src/scm/webid-oidc/client.scm
@@ -137,11 +137,12 @@
(let* ((access-token (account:access-token account))
(dpop-proof
(let ((key-pair (account:key-pair account)))
- (issue-dpop-proof
- key-pair
- #:htm method
- #:htu uri
- #:access-token access-token))))
+ (issue <dpop-proof>
+ key-pair
+ #:jwk (public-key key-pair)
+ #:htm method
+ #:htu uri
+ #:access-token access-token))))
(let ((all-headers
`((dpop . ,dpop-proof)
(authorization . (dpop . ,access-token))
diff --git a/src/scm/webid-oidc/client/accounts.scm b/src/scm/webid-oidc/client/accounts.scm
index ddb592a..3591b52 100644
--- a/src/scm/webid-oidc/client/accounts.scm
+++ b/src/scm/webid-oidc/client/accounts.scm
@@ -27,12 +27,13 @@
#:use-module (srfi srfi-19)
#:use-module (webid-oidc errors)
#:use-module (webid-oidc web-i18n)
+ #:use-module (webid-oidc jws)
#:use-module ((webid-oidc parameters) #:prefix p:)
#:use-module ((webid-oidc stubs) #:prefix stubs:)
#:use-module ((webid-oidc oidc-id-token) #:prefix id:)
#:use-module ((webid-oidc oidc-configuration) #:prefix cfg:)
#:use-module ((webid-oidc jwk) #:prefix jwk:)
- #:use-module ((webid-oidc dpop-proof) #:prefix dpop:)
+ #:use-module (webid-oidc dpop-proof)
#:use-module ((webid-oidc client client) #:prefix client:)
#:use-module (web uri)
#:use-module (web response)
@@ -87,6 +88,7 @@
#:declarative? #t)
(define <jwk:key-pair> jwk:<key-pair>)
+(define <id:id-token> id:<id-token>)
;; This exception is continuable! Continue with the authorization
;; code.
@@ -253,10 +255,11 @@
(unless key-pair
(set! key-pair (client:client-key-pair client)))
(let ((dpop-proof
- (dpop:issue-dpop-proof
- key-pair
- #:htm 'POST
- #:htu token-endpoint)))
+ (issue <dpop-proof>
+ key-pair
+ #:jwk (jwk:public-key key-pair)
+ #:htm 'POST
+ #:htu token-endpoint)))
(receive (response response-body)
((anonymous-http-request) token-endpoint
#:method 'POST
@@ -368,25 +371,24 @@
decoding-error))))
(lambda ()
(set! id-token
- (id:id-token-decode id-token
- #:http-get
- (http-request->http-get (anonymous-http-request))))))
+ (decode <id:id-token> id-token
+ #:http-request (anonymous-http-request)))))
;; We are not interested in the ID token
;; signature anymore, because it won’t be
;; transmitted to other parties and we know that
;; it is valid.
(when (and subject
- (not (equal? subject (id:id-token-webid id-token))))
+ (not (equal? subject (id:webid id-token))))
(let ((final-message
(format #f (G_ "the ID token delivered by the identity provider for ~s has ~s as webid")
(uri->string subject)
- (id:id-token-webid id-token))))
+ (id:webid id-token))))
(raise-exception
(make-exception
(make-token-request-failed response response-body)
(make-exception-with-message final-message)))))
- (set! subject (id:id-token-webid id-token))
- (when (not (equal? issuer (id:id-token-iss id-token)))
+ (set! subject (id:webid id-token))
+ (when (not (equal? issuer (iss id-token)))
(let ((final-message
(format #f (G_ "The ID token delivered by the identity provider ~s is for issuer ~s.")
(uri->string issuer)
diff --git a/src/scm/webid-oidc/dpop-proof.scm b/src/scm/webid-oidc/dpop-proof.scm
index 8c66f68..318ebb8 100644
--- a/src/scm/webid-oidc/dpop-proof.scm
+++ b/src/scm/webid-oidc/dpop-proof.scm
@@ -26,10 +26,19 @@
#:use-module (ice-9 optargs)
#:use-module (ice-9 exceptions)
#:use-module (ice-9 match)
+ #:use-module (ice-9 receive)
#:use-module (srfi srfi-19)
#:use-module (srfi srfi-26)
#:use-module (oop goops)
#:declarative? #t
+ #:re-export
+ (
+ alg iat exp nonce (nonce . jti)
+ token->jwt
+ decode
+ encode
+ issue
+ )
#:export
(
@@ -37,19 +46,6 @@
make-invalid-dpop-proof
invalid-dpop-proof?
- the-dpop-proof
- dpop-proof?
-
- dpop-proof-alg
- dpop-proof-typ
- dpop-proof-jwk
-
- dpop-proof-jti
- dpop-proof-htm
- dpop-proof-htu
- dpop-proof-iat
- dpop-proof-ath
-
&dpop-method-mismatch
make-dpop-method-mismatch
dpop-method-mismatch?
@@ -72,8 +68,7 @@
make-dpop-unconfirmed-key
dpop-unconfirmed-key?
- dpop-proof-decode
- issue-dpop-proof
+ <dpop-proof> typ jwk htm htu ath
))
(define-exception-type
@@ -82,172 +77,6 @@
make-invalid-dpop-proof
invalid-dpop-proof?)
-(define (parse-jwk data)
- (false-if-exception
- (jwk->key data)))
-
-(define (the-dpop-proof x)
- (with-exception-handler
- (lambda (error)
- (let ((final-message
- (cond
- ((invalid-jws? error)
- (if (exception-with-message? error)
- (format #f (G_ "this is not a DPoP proof, because it is not even a JWS: ~a")
- (exception-message error))
- (format #f (G_ "this is not a DPoP proof, because it is not even a JWS"))))
- (else
- (if (exception-with-message? error)
- (format #f (G_ "this is not an access token: ~a")
- (exception-message error))
- (format #f (G_ "this is not an access token")))))))
- (raise-exception
- (make-exception
- (make-invalid-dpop-proof)
- (make-exception-with-message final-message)
- error))))
- (lambda ()
- (match (the-jws x)
- ((header . payload)
- (let examine-header ((header header)
- (alg #f)
- (typ #f)
- (jwk #f)
- (other-header-fields '()))
- (match header
- (()
- (let examine-payload ((payload payload)
- (jti #f)
- (htm #f)
- (htu #f)
- (iat #f)
- (ath #f)
- (other-payload-fields '()))
- (match payload
- (()
- (unless (and alg typ jwk jti htm htu iat)
- (fail (format #f (G_ "the DPoP proof is missing ~s")
- `(,@(if alg '() '("alg"))
- ,@(if typ '() '("typ"))
- ,@(if jwk '() '("jwk"))
- ,@(if jti '() '("jti"))
- ,@(if htm '() '("htm"))
- ,@(if htu '() '("htu"))
- ,@(if iat '() '("iat"))))))
- `(((alg . ,(symbol->string alg))
- (typ . "dpop+jwt")
- (jwk . ,(key->jwk (public-key jwk)))
- ,@other-header-fields)
- . ((jti . ,jti)
- (htm . ,(symbol->string htm))
- (htu . ,(uri->string htu))
- (iat . ,(time-second (date->time-utc iat)))
- ,@(if ath `((ath . ,ath)) '())
- ,@other-payload-fields)))
- ((('jti . (? string? given-jti)) payload ...)
- (examine-payload payload
- (or jti given-jti) htm htu iat ath
- other-payload-fields))
- ((('jti . incorrect) payload ...)
- (fail (format #f (G_ "the \"jti\" field should be a string, not ~s")
- incorrect)))
- ((('htm . (? string? given-htm)) payload ...)
- (examine-payload payload jti
- (or htm (string->symbol given-htm))
- htu iat ath other-payload-fields))
- ((('htm . incorrect) payload ...)
- (fail (format #f (G_ "the \"htm\" field should be a string, not ~s")
- incorrect)))
- ((('htu . (? string? (= string->uri (? uri? given-htu)))) payload ...)
- (examine-payload payload jti htm
- (or htu given-htu)
- iat ath other-payload-fields))
- ((('htu . incorrect) payload ...)
- (fail (format #f (G_ "the \"htu\" field should be an URI, not ~s")
- incorrect)))
- ((('iat . (? (cute >= <> 0) (? integer? given-iat))) payload ...)
- (examine-payload payload jti htm htu
- (or iat (time-utc->date (make-time time-utc 0 given-iat)))
- ath other-payload-fields))
- ((('iat . incorrect) payload ...)
- (fail (format #f (G_ "the \"iat\" field should be a timestamp, not ~s")
- incorrect)))
- ((('ath . (? string? given-ath)) payload ...)
- (examine-payload payload jti htm htu iat
- (or ath given-ath)
- other-payload-fields))
- ((('ath . incorrect) payload ...)
- (fail (format #f (G_ "the \"ath\" field should be an encoded JWT, not ~s")
- incorrect)))
- ((field payload ...)
- (examine-payload payload jti htm htu iat ath
- `(,field ,@other-payload-fields))))))
- ((('alg . (? string? given-alg)) header ...)
- (examine-header header (or alg (string->symbol given-alg))
- typ jwk other-header-fields))
- ((('alg . incorrect) header ...)
- (fail (format #f (G_ "the \"alg\" field should be a string, not ~s")
- incorrect)))
- ((('typ . "dpop+jwt") header ...)
- (examine-header header alg #t jwk other-header-fields))
- ((('typ . incorrect) header ...)
- (fail (format #f (G_ "the \"typ\" field should be \"dpop+jwt\", not ~s")
- incorrect)))
- ((('jwk . (= parse-jwk (? (cute is-a? <> <public-key>) given-jwk))) header ...)
- (examine-header header alg typ (or jwk given-jwk)
- other-header-fields))
- ((('jwk . (= parse-jwk (? (cute is-a? <> <key-pair>) given-jwk))) header ...)
- (fail (format #f (G_ "the \"jwk\" field should not contain the private key"))))
- ((('jwk . incorrect) header ...)
- (fail (format #f (G_ "the \"jwk\" field should be a valid public key, not ~s")
- incorrect)))
- ((field header ...)
- (examine-header header alg typ jwk `(,field ,@other-header-fields))))))))))
-
-(define (dpop-proof? x)
- (false-if-exception (the-dpop-proof x)))
-
-(define (dpop-proof-alg proof)
- (match (the-dpop-proof proof)
- ((header . _)
- (symbol->string (assq-ref header 'alg)))))
-
-(define (dpop-proof-typ proof)
- (match (the-dpop-proof proof)
- ((header . _)
- (assq-ref header 'typ))))
-
-(define (dpop-proof-jwk proof)
- (match (the-dpop-proof proof)
- ((header . _)
- (jwk->key (assq-ref header 'jwk)))))
-
-(define (dpop-proof-jti proof)
- (match (the-dpop-proof proof)
- ((_ . payload)
- (assq-ref payload 'jti))))
-
-(define (dpop-proof-htm proof)
- (match (the-dpop-proof proof)
- ((_ . payload)
- (string->symbol (assq-ref payload 'htm)))))
-
-(define (dpop-proof-htu proof)
- (match (the-dpop-proof proof)
- ((_ . payload)
- (string->uri (assq-ref payload 'htu)))))
-
-(define (dpop-proof-iat proof)
- (match (the-dpop-proof proof)
- ((_ . payload)
- (time-utc->date
- (make-time time-utc 0 (assq-ref payload 'iat))))))
-
-(define (dpop-proof-ath proof)
- (match (the-dpop-proof proof)
- ((_ . payload)
- (assq-ref payload 'ath))))
-
(define-exception-type
&dpop-method-mismatch
&external-error
@@ -299,129 +128,169 @@
make-dpop-unconfirmed-key
dpop-unconfirmed-key?)
-(define* (dpop-proof-decode method uri str cnf/check
- #:key
- (access-token #f))
- (let* ((current-date ((p:current-date)))
- (current-time
- (time-second (date->time-utc current-date))))
- (with-exception-handler
- (lambda (error)
- (let ((final-message
- (if (exception-with-message? error)
- (format #f (G_ "the DPoP proof cannot be decoded: ~a")
- (exception-message error))
- (format #f (G_ "the DPoP proof cannot be decoded")))))
- (raise-exception
- (make-exception
- (make-invalid-dpop-proof)
- (make-exception-with-message final-message)
- error))))
- (lambda ()
- (let ((decoded (the-dpop-proof (jws-decode str dpop-proof-jwk))))
- (unless (eq? method (dpop-proof-htm decoded))
- (let ((final-message
- (format #f (G_ "the DPoP proof is signed for access through ~s, but it is used with ~s")
- (dpop-proof-htm decoded) method)))
- (raise-exception
- (make-exception
- (make-dpop-method-mismatch (dpop-proof-htm decoded) method)
- (make-exception-with-message final-message)))))
- (uris-compatible (dpop-proof-htu decoded)
- (if (string? uri)
- (string->uri uri)
- uri))
- (let ((iat (dpop-proof-iat decoded)))
- (let ((iat-s (time-second (date->time-utc iat))))
- (unless (>= current-time (- iat-s 5))
- (let ((final-message
- (format #f (G_ "the DPoP proof is signed in the future, ~a, relative to the current date, ~a")
- (date->string iat)
- (date->string current-date))))
- (raise-exception
- (make-exception
- (make-signed-in-future iat current-date)
- (make-exception-with-message final-message)))))
- (unless (<= current-time (+ iat-s 120)) ;; valid for 2 minutes
- (let ((final-message
- (format #f (G_ "the DPoP proof is too old, it was signed ~a and now it is ~a")
- (date->string iat)
- (date->string current-date))))
- (raise-exception
- (make-exception
- (make-expired (time-utc->date (make-time time-utc 0 (+ iat-s 120)))
- current-date)
- (make-exception-with-message final-message)))))))
- (when access-token
- (let ((h (stubs:hash 'SHA-256 access-token)))
- (unless (equal? (dpop-proof-ath decoded) h)
- (let ((final-message
- (format #f (G_ "the DPoP proof should go along with an access token hashed to ~s, not ~s")
- (dpop-proof-ath decoded) access-token)))
- (raise-exception
- (make-exception
- (make-dpop-invalid-ath (dpop-proof-ath decoded) access-token)
- (make-exception-with-message final-message)))))))
- (if (string? cnf/check)
- (unless (equal? cnf/check (jkt (dpop-proof-jwk decoded)))
- (let ((final-message
- (format #f (G_ "the DPoP proof is signed with the wrong key"))))
- (raise-exception
- (make-exception
- (make-dpop-unconfirmed-key)
- (make-exception-with-message final-message)))))
- (with-exception-handler
- (lambda (error)
- (let ((final-message
- (if (exception-with-message? error)
- (format #f (G_ "the DPoP proof is signed with the wrong key: ~a")
- (exception-message error))
- (format #f (G_ "the DPoP proof is signed with the wrong key")))))
- (raise-exception
- (make-exception
- (make-dpop-unconfirmed-key)
- (make-exception-with-message final-message)
- error))))
- (lambda ()
- (unless (cnf/check (jkt (dpop-proof-jwk decoded)))
- ;; You should throw an error instead!
- (fail (G_ "the cnf/check function returned #f"))))))
- (parameterize ((p:current-date current-date))
- ;; jti-check should use the same date.
- (jti-check (dpop-proof-jti decoded) 120))
- decoded)))))
+(define-class <dpop-proof> (<single-use-token>)
+ (typ #:init-keyword #:typ #:accessor typ)
+ (jwk #:init-keyword #:jwk #:accessor jwk)
+ (htm #:init-keyword #:htm #:accessor htm)
+ (htu #:init-keyword #:htu #:accessor htu)
+ (ath #:init-keyword #:ath #:accessor ath))
+
+(define-method (default-validity (proof <dpop-proof>))
+ 30)
+
+(define-method (has-explicit-exp? (proof <dpop-proof>))
+ #f)
-(define (dpop-proof-encode dpop-proof key)
+(define-method (nonce-field-name (proof <dpop-proof>))
+ 'jti)
+
+(define-method (initialize (token <dpop-proof>) initargs)
(with-exception-handler
(lambda (error)
- (let ((final-message
- (if (exception-with-message? error)
- (format #f (G_ "cannot encode a DPoP proof: ~a")
- (exception-message error))
- (format #f (G_ "cannot encode a DPoP proof")))))
- (raise-exception
- (make-exception
- (make-exception-with-message final-message)
- error))))
+ (raise-exception
+ (make-exception
+ (make-invalid-dpop-proof)
+ (make-exception-with-message
+ (if (exception-with-message? error)
+ (format #f (G_ "invalid DPoP proof: ~a")
+ (exception-message error))
+ (G_ "invalid DPoP proof token")))
+ error)))
(lambda ()
- (jws-encode dpop-proof key))))
-
-(define* (issue-dpop-proof
- client-key
- #:key
- (htm #f)
- (htu #f)
- (access-token #f))
- (dpop-proof-encode
- (the-dpop-proof
- `(((alg . ,(symbol->string (alg client-key)))
- (typ . "dpop+jwt")
- (jwk . ,(key->jwk (public-key client-key))))
- . ((jti . ,(stubs:random 12))
- (htm . ,(symbol->string htm))
- (htu . ,(uri->string htu))
- (iat . ,(time-second (date->time-utc ((p:current-date)))))
- ,@(if access-token
- `((ath . ,(stubs:hash 'SHA-256 access-token)))
- '()))))
- client-key))
+ (next-method)
+ (let-keywords
+ initargs #t
+ ((typ "dpop+jwt")
+ (jwk #f)
+ (htm #f)
+ (htu #f)
+ (ath #f)
+ (access-token #f)
+ (jwt-header #f)
+ (jwt-payload #f))
+ (let do-initialize ((typ typ)
+ (jwk jwk)
+ (htm htm)
+ (htu htu)
+ (ath ath)
+ (access-token access-token)
+ (jwt-header jwt-header)
+ (jwt-payload jwt-payload))
+ (cond
+ ((string? htu)
+ (do-initialize typ jwk htm (string->uri htu) ath access-token jwt-header jwt-payload))
+ ((string? htm)
+ (do-initialize typ jwk (string->symbol htm) htu ath access-token jwt-header jwt-payload))
+ ((and (not ath) access-token)
+ (do-initialize typ jwk htm htu (stubs:hash 'SHA-256 access-token) #f jwt-header jwt-payload))
+ ((and typ jwk htm htu)
+ (unless (equal? typ "dpop+jwt")
+ (scm-error 'wrong-type-arg "make"
+ (G_ "#:typ should be exactly \"dpop+jwt\"")
+ '()
+ (list typ)))
+ (unless (is-a? jwk <public-key>)
+ (scm-error 'wrong-type-arg "make"
+ (G_ "#:jwk should be a public key")
+ '()
+ (list jwk)))
+ (unless (symbol? htm)
+ (scm-error 'wrong-type-arg "make"
+ (G_ "#:htm should be a symbol")
+ '()
+ (list htm)))
+ (when ath
+ (unless (string? ath)
+ (scm-error 'wrong-type-arg "make"
+ (G_ "when present, #:ath should be a string")
+ '()
+ (list ath))))
+ (slot-set! token 'typ typ)
+ (slot-set! token 'jwk jwk)
+ (slot-set! token 'htm htm)
+ (slot-set! token 'htu htu)
+ (slot-set! token 'ath ath))
+ ((and jwt-header jwt-payload)
+ (do-initialize
+ (assq-ref jwt-header 'typ)
+ (jwk->key (assq-ref jwt-header 'jwk))
+ (assq-ref jwt-payload 'htm)
+ (assq-ref jwt-payload 'htu)
+ (assq-ref jwt-payload 'ath)
+ #f #f #f))
+ (else
+ (raise-exception
+ (make-exception
+ (make-invalid-jws)
+ (make-exception-with-message
+ (G_ "when making a DPoP proof, either its required fields (#:typ, #:jwk, #:htm and #:htu) or (#:jwt-header and #:jwt-payload) should be passed")))))))))))
+
+(define-method (token->jwt (token <dpop-proof>))
+ ;; exp should be implicit, and nonce should be replaced by jti
+ (receive (base-header base-payload) (next-method)
+ (values
+ `((typ . ,(typ token))
+ (jwk . ,(key->jwk (jwk token)))
+ ,@base-header)
+ `((htm . ,(symbol->string (htm token)))
+ (htu . ,(uri->string (htu token)))
+ ,@(let ((ath (ath token)))
+ (if ath
+ `((ath . ,ath))
+ '()))
+ ,@base-payload))))
+
+(define-method (verify (token <dpop-proof>) args)
+ (next-method)
+ (let-keywords
+ args #t
+ ((access-token #f)
+ (method #f)
+ (uri #f)
+ (cnf/check #f))
+ (begin
+ (when (string? uri)
+ (set! uri (string->uri uri)))
+ (unless (eq? (htm token) method)
+ (raise-exception
+ (make-exception
+ (make-dpop-method-mismatch (htm token) method)
+ (make-exception-with-message
+ (format #f (G_ "the DPoP proof is signed for access through ~s, but it is used with ~s")
+ (htm token) method)))))
+ (uris-compatible (htu token) uri)
+ (when access-token
+ (let ((h (stubs:hash 'SHA-256 access-token)))
+ (unless (equal? (ath token) h)
+ (raise-exception
+ (make-exception
+ (make-dpop-invalid-ath (ath token) access-token)
+ (make-exception-with-message
+ (format #f (G_ "the DPoP proof should go along with an access token hashed to ~s, not ~s")
+ (ath token) access-token)))))))
+ (if (string? cnf/check)
+ (unless (equal? cnf/check (jkt (jwk token)))
+ (raise-exception
+ (make-exception
+ (make-dpop-unconfirmed-key)
+ (make-exception-with-message
+ (format #f (G_ "the DPoP proof is signed with the wrong key"))))))
+ (with-exception-handler
+ (lambda (error)
+ (let ((final-message
+ (if (exception-with-message? error)
+ (format #f (G_ "the DPoP proof is signed with the wrong key: ~a")
+ (exception-message error))
+ (format #f (G_ "the DPoP proof is signed with the wrong key")))))
+ (raise-exception
+ (make-exception
+ (make-dpop-unconfirmed-key)
+ (make-exception-with-message final-message)
+ error))))
+ (lambda ()
+ (unless (cnf/check (jkt (jwk token)))
+ ;; You should throw an error instead!
+ (fail (G_ "the cnf/check function returned #f")))))))))
+
+(define-method (lookup-keys (token <dpop-proof>) args)
+ (jwk token))
diff --git a/src/scm/webid-oidc/example-app.scm b/src/scm/webid-oidc/example-app.scm
index c293d69..67d959f 100644
--- a/src/scm/webid-oidc/example-app.scm
+++ b/src/scm/webid-oidc/example-app.scm
@@ -18,7 +18,6 @@
#:use-module ((webid-oidc client) #:prefix client:)
#:use-module ((webid-oidc client accounts) #:prefix account:)
#:use-module ((webid-oidc cache) #:prefix cache:)
- #:use-module (webid-oidc dpop-proof)
#:use-module (webid-oidc web-i18n)
#:use-module ((webid-oidc stubs) #:prefix stubs:)
#:use-module ((webid-oidc refresh-token) #:prefix refresh:)
diff --git a/src/scm/webid-oidc/jws.scm b/src/scm/webid-oidc/jws.scm
index 3e5e50b..af83c90 100644
--- a/src/scm/webid-oidc/jws.scm
+++ b/src/scm/webid-oidc/jws.scm
@@ -18,13 +18,30 @@
#:use-module (webid-oidc jwk)
#:use-module (webid-oidc errors)
#:use-module (webid-oidc web-i18n)
+ #:use-module (webid-oidc jti)
+ #:use-module (webid-oidc oidc-configuration)
#:use-module ((webid-oidc stubs) #:prefix stubs:)
+ #:use-module ((webid-oidc parameters) #:prefix p:)
#:use-module (rnrs bytevectors)
+ #:use-module (srfi srfi-19)
#:use-module (ice-9 receive)
#:use-module (ice-9 exceptions)
#:use-module (ice-9 match)
+ #:use-module (ice-9 optargs)
+ #:use-module (web uri)
#:use-module (oop goops)
#:declarative? #t
+ #:re-export
+ (
+ (&jti-found . &nonce-found)
+ (make-jti-found . make-nonce-found)
+ (jti-found? . nonce-found?)
+ (jti-found-jti . nonce-found-nonce)
+ )
+ #:replace
+ (
+ exp ;; This is a function in guile
+ )
#:export
(
@@ -32,10 +49,16 @@
make-invalid-jws
invalid-jws?
- the-jws
- jws?
+ <token>
+
+ <time-bound-token> iat default-validity has-explicit-exp?
+ nonce-field-name ;; DPoP proofs use 'jti instead of 'nonce
+
+ <oidc-token> iss
- jws-alg
+ <single-use-token> nonce
+
+ token->jwt
&cannot-query-identity-provider
make-cannot-query-identity-provider
@@ -54,8 +77,11 @@
error-expiration-date
;; error-current-date works for that one too
- jws-decode
- jws-encode
+ lookup-keys
+ verify
+ decode
+ encode
+ issue
))
@@ -65,70 +91,259 @@
make-invalid-jws
invalid-jws?)
-(define (the-jws x)
- (with-exception-handler
- (lambda (error)
- (let ((final-message
- (if (exception-with-message? error)
- (format #f (G_ "the JWS is invalid: ~a")
- (exception-message error))
- (format #f (G_ "the JWS is invalid")))))
+(define-class <token> ()
+ (alg #:init-keyword #:alg #:accessor alg))
+
+(define (key-alg key)
+ (alg key))
+
+(define-method (initialize (token <token>) initargs)
+ (next-method)
+ (let-keywords
+ initargs #t
+ ((alg #f)
+ (signing-key #f)
+ (jwt-header #f)
+ (jwt-payload #f))
+ (let do-initialize ((alg alg)
+ (signing-key signing-key)
+ (jwt-header jwt-header)
+ (jwt-payload jwt-payload))
+ (cond
+ ((string? alg)
+ (do-initialize (string->symbol alg) signing-key jwt-header jwt-payload))
+ (alg
+ (case alg
+ ((HS256 HS384 HS512
+ RS256 RS384 RS512
+ ES256 ES384 ES512
+ PS256 PS384 PS512)
+ (slot-set! token 'alg alg))
+ (else
(raise-exception
(make-exception
(make-invalid-jws)
- (make-exception-with-message final-message)
- error))))
- (lambda ()
- (match x
- ((header . payload)
- (let examine-header ((header header)
- (alg #f)
- (other-header-fields '()))
- (match header
- (()
- (let examine-payload ((payload payload)
- (other-payload-fields '()))
- (match payload
- (()
- (unless alg
- (fail (format #f (G_ "the JWS header does not have an \"alg\" field"))))
- `(((alg . ,(symbol->string alg))
- ,@(reverse other-header-fields))
- . ,(reverse other-payload-fields)))
- ((((? symbol? key) . value) payload ...)
- (examine-payload payload
- `((,key . ,value) ,@other-payload-fields)))
- (else
- (fail (format #f (G_ "invalid JSON object as payload")))))))
- ((('alg . (? string? given-alg)) header ...)
- (case (string->symbol given-alg)
- ((HS256 HS384 HS512
- RS256 RS384 RS512
- ES256 ES384 ES512
- PS256 PS384 PS512)
- #t)
- (else
- (fail (format #f (G_ "invalid signature algorithm: ~s") given-alg))))
- (examine-header header (or alg (string->symbol given-alg))
- other-header-fields))
- ((('alg . invalid) header ...)
- (fail (format #f (G_ "invalid \"alg\" value: ~s") invalid)))
- ((((? symbol? key) . value) header ...)
- (examine-header header alg
- `((,key . ,value) ,@other-header-fields)))
- (else
- (fail (format #f (G_ "invalid JSON object as header")))))))
- (else
- (fail (format #f (G_ "this is not a pair"))))))))
-
-(define (jws? x)
- (false-if-exception
- (the-jws x)))
-
-(define (jws-alg jws)
- (match (the-jws jws)
- ((header . _)
- (string->symbol (assq-ref header 'alg)))))
+ (make-exception-with-message
+ (format #f (G_ "unsupported JWS algorithm: ~s") alg)))))))
+ (signing-key
+ (do-initialize (key-alg signing-key) #f jwt-payload jwt-header))
+ ((and jwt-header jwt-payload)
+ (do-initialize (assq-ref jwt-header 'alg) #f #f #f))
+ (else
+ (raise-exception
+ (make-exception
+ (make-invalid-jws)
+ (make-exception-with-message
+ (G_ "when making a token either #:alg or (#:jwt-header and #:jwt-payload) should be passed")))))))))
+
+(define-class <generic-with-default> (<generic>)
+ ;; neutral is the list of values that are returned when there are no
+ ;; next methods.
+ (neutral #:init-keyword #:neutral))
+
+(define-method (no-next-method (generic <generic-with-default>) args)
+ (apply values (slot-ref generic 'neutral)))
+
+(define-method (no-applicable-method (generic <generic-with-default>) args)
+ (apply values (slot-ref generic 'neutral)))
+
+(define-class <time-bound-token> (<token>)
+ (iat #:init-keyword #:iat #:accessor iat)
+ (exp #:init-keyword #:exp #:accessor exp))
+
+(define default-validity
+ (make <generic-with-default>
+ #:name 'default-validity
+ #:neutral (list #f)))
+
+(define-method (has-explicit-exp? (token <time-bound-token>))
+ ;; Change it to #f when the token should not have an explicit
+ ;; expiration date, such as DPoP proofs
+ #t)
+
+(define-method (initialize (token <time-bound-token>) initargs)
+ (next-method)
+ (let-keywords
+ initargs #t
+ ((iat ((p:current-date)))
+ (exp #f)
+ (validity (default-validity token))
+ (jwt-header #f)
+ (jwt-payload #f))
+ (let do-initialize ((iat iat)
+ (exp exp)
+ (validity validity)
+ (jwt-header jwt-header)
+ (jwt-payload jwt-payload))
+ (cond
+ ((integer? iat)
+ (do-initialize (make-time time-utc 0 iat) exp validity jwt-header jwt-payload))
+ ((time? iat)
+ (do-initialize (time-utc->date iat) exp validity jwt-header jwt-payload))
+ ((and (not exp) (date? iat) (integer? validity))
+ (do-initialize iat
+ (+ (time-second (date->time-utc iat))
+ validity)
+ validity
+ jwt-header
+ jwt-payload))
+ ((integer? exp)
+ (do-initialize iat (make-time time-utc 0 exp) validity jwt-header jwt-payload))
+ ((time? exp)
+ (do-initialize iat (time-utc->date exp) validity jwt-header jwt-payload))
+ ((and jwt-header jwt-payload)
+ (do-initialize (assq-ref jwt-payload 'iat)
+ (and (has-explicit-exp? token)
+ (assq-ref jwt-payload 'exp))
+ validity #f #f))
+ ((and iat exp)
+ (unless (date? iat)
+ (scm-error 'wrong-type-arg "make"
+ (G_ "#:iat should be a date")
+ '()
+ (list iat)))
+ (unless (date? exp)
+ (scm-error 'wrong-type-arg "make"
+ (G_ "#:exp should be a date")
+ '()
+ (list exp)))
+ (slot-set! token 'iat iat)
+ (slot-set! token 'exp exp))
+ (else
+ (raise-exception
+ (make-exception
+ (make-invalid-jws)
+ (make-exception-with-message
+ (G_ "when making a time-bound token, either its required fields (#:iat, and either #:exp or #:validity) or (#:jwt-header and #:jwt-payload) should be passed")))))))))
+
+(define-class <oidc-token> (<token>)
+ (iss #:init-keyword #:iss #:accessor iss))
+
+(define-method (default-validity (token <oidc-token>))
+ (let ((next (next-method))
+ (mine 3600))
+ (if (and next (< next mine))
+ next
+ mine)))
+
+(define-method (initialize (token <oidc-token>) initargs)
+ (next-method)
+ (let-keywords
+ initargs #t
+ ((iss #f)
+ (jwt-header #f)
+ (jwt-payload #f))
+ (let do-initialize ((iss iss)
+ (jwt-header jwt-header)
+ (jwt-payload jwt-payload))
+ (cond
+ ((string? iss)
+ (do-initialize (string->uri iss) jwt-header jwt-payload))
+ (iss
+ (unless (uri? iss)
+ (scm-error 'wrong-type-arg "make"
+ (G_ "#:iss should be an URI")
+ '()
+ (list iss)))
+ (slot-set! token 'iss iss))
+ ((and jwt-header jwt-payload)
+ (do-initialize (assq-ref jwt-payload 'iss) #f #f))
+ (else
+ (raise-exception
+ (make-exception
+ (make-invalid-jws)
+ (make-exception-with-message
+ (G_ "when making an OIDC token, either its required #:iss field or (#:jwt-header and #:jwt-payload) should be passed")))))))))
+
+(define-class <single-use-token> (<time-bound-token>)
+ (nonce #:init-keyword #:nonce #:accessor nonce))
+
+(define-method (default-validity (token <single-use-token>))
+ (let ((next (next-method))
+ (mine 120))
+ (if (and next (< next mine))
+ next
+ mine)))
+
+(define nonce-field-name
+ (make <generic-with-default>
+ #:name 'nonce-field-name
+ #:neutral (list 'nonce)))
+
+(define-method (nonce-field-name (token <top>))
+ ;; Without this method, this is an infinite loop.
+ (next-method))
+
+(define-method (initialize (token <single-use-token>) initargs)
+ (next-method)
+ (let-keywords
+ initargs #t
+ ((nonce (stubs:random 12))
+ (jwt-header #f)
+ (jwt-payload #f))
+ ;; The maximum validity is 2 minutes
+ (let ((iat (time-second (date->time-utc (iat token))))
+ (exp (time-second (date->time-utc (exp token)))))
+ (let ((validity (- exp iat)))
+ (when (> validity 120)
+ (let ((true-exp (+ iat 120)))
+ (slot-set! token 'exp (time-utc->date (make-time time-utc 0 true-exp)))))))
+ (let do-initialize ((nonce nonce)
+ (jwt-header jwt-header)
+ (jwt-payload jwt-payload))
+ (cond
+ ((and jwt-header jwt-payload)
+ (do-initialize (assq-ref jwt-payload (nonce-field-name token)) #f #f))
+ (nonce
+ (unless (string? nonce)
+ (scm-error 'wrong-type-arg "make"
+ (G_ "#:nonce should be a string")
+ '()
+ (list nonce)))
+ (slot-set! token 'nonce nonce))
+ (else
+ (raise-exception
+ (make-exception
+ (make-invalid-jws)
+ (make-exception-with-message
+ (G_ "when making a single-use token, either its required #:nonce field or (#:jwt-header and #:jwt-payload) should be passed")))))))))
+
+(define token->jwt
+ (make <generic-with-default>
+ #:name 'token->jwt
+ #:neutral (list '() '())))
+
+(define-method (token->jwt (token <token>))
+ (receive (base-header base-payload)
+ (next-method)
+ (values
+ `((alg . ,(symbol->string (alg token)))
+ ,@base-header)
+ base-payload)))
+
+(define-method (token->jwt (token <time-bound-token>))
+ (receive (base-header base-payload)
+ (next-method)
+ (values base-header
+ `((iat . ,(time-second (date->time-utc (iat token))))
+ ,@(if (has-explicit-exp? token)
+ `((exp . ,(time-second (date->time-utc (exp token)))))
+ '())
+ ,@base-payload))))
+
+(define-method (token->jwt (token <single-use-token>))
+ (receive (base-header base-payload)
+ (next-method)
+ (values base-header
+ `((,(nonce-field-name token) . ,(nonce token))
+ ,@base-payload))))
+
+(define-method (token->jwt (token <oidc-token>))
+ (receive (base-header base-payload)
+ (next-method)
+ (values base-header
+ `((iss . ,(uri->string (iss token)))
+ ,@base-payload))))
(define (split-in-3-parts string separator)
(match (string-split string separator)
@@ -193,14 +408,14 @@
(error-current-date (apply make-exception sub-exceptions)))
(else #f)))
-(define (parse str verify)
+(define (parse token-class str verify-signature)
(receive (header payload signature)
(split-in-3-parts str #\.)
(let ((base (string-append header "." payload))
(header (base64-decode-json header))
(payload (base64-decode-json payload)))
- (let ((ret `(,header . ,payload)))
- (verify ret base signature)
+ (let ((ret (make token-class #:jwt-header header #:jwt-payload payload)))
+ (verify-signature ret base signature)
ret))))
(define (verify-any alg keys payload signature)
@@ -245,7 +460,102 @@
(define-method (keys (keys <list>))
(map public-key keys))
-(define (jws-decode str lookup-keys)
+(define lookup-keys
+ (make <generic-with-default>
+ #:name 'lookup-keys
+ #:neutral (list '())))
+
+(define-method (lookup-keys (token <oidc-token>) args)
+ (let-keywords
+ args #f
+ ((http-request http-request))
+ (let ((iss (iss token)))
+ (let ((cfg
+ (with-exception-handler
+ (lambda (error)
+ (let ((final-message
+ (if (exception-with-message? error)
+ (format #f (G_ "I cannot query the identity provider configuration: ~a")
+ (exception-message error))
+ (format #f (G_ "I cannot query the identity provider configuration")))))
+ (raise-exception
+ (make-exception
+ (make-cannot-query-identity-provider iss)
+ (make-exception-with-message final-message)
+ error))))
+ (lambda ()
+ (get-oidc-configuration
+ (uri-host iss)
+ #:userinfo (uri-userinfo iss)
+ #:port (uri-port iss)
+ #:http-get
+ (lambda* (uri . args)
+ (apply http-request uri #:method 'GET args)))))))
+ (with-exception-handler
+ (lambda (error)
+ (raise-exception
+ (make-exception
+ (make-cannot-query-identity-provider iss)
+ (make-exception-with-message
+ (if (exception-with-message? error)
+ (format #f (G_ "I cannot query the JWKS URI of the identity provider: ~a")
+ (exception-message error))
+ (format #f (G_ "I cannot query the JWKS URI of the identity provider")))))))
+ (lambda ()
+ (append
+ (keys (next-method))
+ (keys
+ (oidc-configuration-jwks
+ cfg
+ #:http-get
+ (lambda* (uri . args)
+ (apply http-request uri #:method 'GET args)))))))))))
+
+(define verify
+ (make <generic-with-default>
+ #:name 'verify
+ #:neutral (list #t)))
+
+(define-method (verify (token <time-bound-token>) args)
+ (next-method)
+ (let-keywords
+ args #t
+ ((current-date ((p:current-date))))
+ (let ((iat (iat token))
+ (exp (exp token)))
+ (let ((iat-s (time-second (date->time-utc iat)))
+ (exp-s (time-second (date->time-utc exp)))
+ (current-s (time-second (date->time-utc current-date))))
+ (when (>= iat-s (+ current-s 5))
+ (let ((final-message
+ (format #f (G_ "the token is signed in the future, ~a, relative to current ~a")
+ (date->string iat)
+ (date->string current-date))))
+ (raise-exception
+ (make-exception
+ (make-signed-in-future iat current-date)
+ (make-exception-with-message final-message)))))
+ (when (>= current-s exp-s)
+ (let ((final-message
+ (format #f (G_ "the token expired ~a, which is in the past (from ~a)")
+ (date->string exp)
+ (date->string current-date))))
+ (raise-exception
+ (make-exception
+ (make-expired exp current-date)
+ (make-exception-with-message final-message)))))))))
+
+(define-method (verify (token <single-use-token>) args)
+ (next-method)
+ (let-keywords
+ args #t
+ ((current-date ((p:current-date))))
+ (let ((exp (exp token)))
+ (let ((exp-s (time-second (date->time-utc exp)))
+ (current-s (time-second (date->time-utc current-date))))
+ (jti-check (nonce token) (- exp-s current-s))))))
+
+(define* (decode token-class str . args)
(with-exception-handler
(lambda (error)
(let ((final-message
@@ -259,12 +569,13 @@
(make-exception-with-message final-message)
error))))
(lambda ()
- (parse str
- (lambda (jws payload signature)
- (let ((k (keys (lookup-keys jws))))
- (verify-any (jws-alg jws) k payload signature)))))))
+ (parse token-class str
+ (lambda (token payload signature)
+ (let ((k (keys (lookup-keys token args))))
+ (verify-any (alg token) k payload signature))
+ (verify token args))))))
-(define (jws-encode jws key)
+(define (encode token key)
(with-exception-handler
(lambda (error)
(let ((final-message
@@ -278,12 +589,14 @@
(make-exception-with-message final-message)
error))))
(lambda ()
- (match jws
- ((header . payload)
- (let ((header (stubs:scm->json-string header))
- (payload (stubs:scm->json-string payload)))
- (let ((header (stubs:base64-encode header))
- (payload (stubs:base64-encode payload)))
- (let ((payload (string-append header "." payload)))
- (let ((signature (stubs:sign (jws-alg jws) (key->jwk key) payload)))
- (string-append payload "." signature))))))))))
+ (receive (header payload) (token->jwt token)
+ (let ((header (stubs:scm->json-string header))
+ (payload (stubs:scm->json-string payload)))
+ (let ((header (stubs:base64-encode header))
+ (payload (stubs:base64-encode payload)))
+ (let ((payload (string-append header "." payload)))
+ (let ((signature (stubs:sign (alg token) (key->jwk key) payload)))
+ (string-append payload "." signature)))))))))
+
+(define* (issue token-class issuer-key . args)
+ (encode (apply make token-class #:signing-key issuer-key args) issuer-key))
diff --git a/src/scm/webid-oidc/oidc-id-token.scm b/src/scm/webid-oidc/oidc-id-token.scm
index abef88d..1d96a47 100644
--- a/src/scm/webid-oidc/oidc-id-token.scm
+++ b/src/scm/webid-oidc/oidc-id-token.scm
@@ -28,28 +28,26 @@
#:use-module (ice-9 optargs)
#:use-module (ice-9 exceptions)
#:use-module (ice-9 match)
+ #:use-module (ice-9 receive)
#:use-module (srfi srfi-19)
+ #:use-module (oop goops)
+ #:duplicates (merge-generics)
#:declarative? #t
+ #:re-export
+ (
+ alg iat iss nonce
+ token->jwt
+ decode
+ encode
+ issue
+ )
#:export
(
&invalid-id-token
make-invalid-id-token
invalid-id-token?
- the-id-token
- id-token?
-
- id-token-alg
- id-token-webid
- id-token-iss
- id-token-sub
- id-token-aud
- id-token-nonce
- id-token-iat
- id-token-exp
-
- id-token-decode
- issue-id-token
+ <id-token> webid sub aud
))
(define-exception-type
@@ -58,268 +56,81 @@
make-invalid-id-token
invalid-id-token?)
-(define (the-id-token x)
- (with-exception-handler
- (lambda (error)
- (let ((final-message
- (cond
- ((and (invalid-jws? error)
- (exception-with-message? error))
- (format #f (G_ "this is not an ID token, because it is not even a JWS: ~a")
- (exception-message error)))
- ((invalid-jws? error)
- (format #f (G_ "this is not an ID token, because it is not even a JWS")))
- ((exception-with-message? error)
- (format #f (G_ "this is not an ID token: ~a")
- (exception-message error)))
- (else
- (format #f (G_ "this is not an ID token"))))))
- (raise-exception
- (make-exception
- (make-invalid-id-token)
- (make-exception-with-message final-message)
- error))))
- (lambda ()
- (match (the-jws x)
- ((header . payload)
- (let examine-payload ((payload payload)
- (webid #f)
- (iss #f)
- (sub #f)
- (aud #f)
- (nonce #f)
- (iat #f)
- (exp #f)
- (other-fields '()))
- (match payload
- (()
- (unless (and webid iss sub aud nonce iat exp)
- (fail (format #f (G_ "the payload is missing ~s")
- `(,@(if webid '() '("webid"))
- ,@(if iss '() '("iss"))
- ,@(if sub '() '("sub"))
- ,@(if aud '() '("aud"))
- ,@(if nonce '() '("nonce"))
- ,@(if iat '() '("iat"))
- ,@(if exp '() '("exp"))))))
- `(,header
- . ((webid . ,(uri->string webid))
- (iss . ,(uri->string iss))
- (sub . ,sub)
- (aud . ,(uri->string aud))
- (nonce . ,nonce)
- (iat . ,(time-second (date->time-utc iat)))
- (exp . ,(time-second (date->time-utc exp))))))
- ((('webid . (? string? (= string->uri (? uri? webid-given)))) payload ...)
- (examine-payload payload
- (or webid webid-given)
- iss sub aud nonce iat exp other-fields))
- ((('webid . invalid) payload ...)
- (fail (format #f (G_ "the \"webid\" field should be an URI, ~s is given")
- invalid)))
- ((('iss . (? string? (= string->uri (? uri? iss-given)))) payload ...)
- (examine-payload payload webid
- (or iss iss-given)
- sub aud nonce iat exp other-fields))
- ((('iss . invalid) payload ...)
- (fail (format #f (G_ "the \"iss\" field should be an URI, ~s is given")
- invalid)))
- ((('sub . (? string? sub-given)) payload ...)
- (examine-payload payload webid iss
- (or sub sub-given)
- aud nonce iat exp other-fields))
- ((('sub . invalid) payload ...)
- (fail (format #f (G_ "the \"sub\" field should be a string, ~s is given")
- invalid)))
- ((('aud . (? string? (= string->uri (? uri? aud-given)))) payload ...)
- (examine-payload payload webid iss sub
- (or aud aud-given)
- nonce iat exp other-fields))
- ((('aud . invalid) payload ...)
- (fail (format #f (G_ "the \"aud\" field should be an URI, ~s is given")
- invalid)))
- ((('nonce . (? string? nonce-given)) payload ...)
- (examine-payload payload webid iss sub aud
- (or nonce nonce-given)
- iat exp other-fields))
- ((('nonce . invalid) payload ...)
- (fail (format #f (G_ "the \"nonce\" field should be a string, ~s is given")
- invalid)))
- ((('iat . (? (lambda (x) (>= x 0)) (? integer? iat-given))) payload ...)
- (examine-payload payload webid iss sub aud nonce
- (or iat (time-utc->date (make-time time-utc 0 iat-given)))
- exp other-fields))
- ((('iat . invalid) payload ...)
- (fail (format #f (G_ "the \"iat\" field should be a timestamp, ~s is given")
- invalid)))
- ((('exp . (? (lambda (x) (>= x 0)) (? integer? exp-given))) payload ...)
- (examine-payload payload webid iss sub aud nonce iat
- (or exp (time-utc->date (make-time time-utc 0 exp-given)))
- other-fields))
- ((('exp . invalid) payload ...)
- (fail (format #f (G_ "the \"exp\" field should be a timestamp, ~s is given")
- invalid)))
- ((field payload ...)
- (examine-payload payload webid iss sub aud nonce iat exp
- `(,field ,@other-fields)))
- (else
- (fail (format #f (G_ "the payload should be a JSON object")))))))))))
-
-(define (id-token? x)
- (false-if-exception
- (the-id-token x)))
-
-(define (id-token-alg code)
- (match (the-id-token code)
- ((header . _)
- (string->symbol (assq-ref header 'alg)))))
-
-(define (id-token-webid code)
- (match (the-id-token code)
- ((_ . payload)
- (string->uri (assq-ref payload 'webid)))))
-
-(define (id-token-iss code)
- (match (the-id-token code)
- ((_ . payload)
- (string->uri (assq-ref payload 'iss)))))
+(define-class <id-token> (<single-use-token> <oidc-token>)
+ (webid #:init-keyword #:webid #:accessor webid)
+ (sub #:init-keyword #:sub #:accessor sub)
+ (aud #:init-keyword #:aud #:accessor aud))
-(define (id-token-sub code)
- (match (the-id-token code)
- ((_ . payload)
- (assq-ref payload 'sub))))
-
-(define (id-token-aud code)
- (match (the-id-token code)
- ((_ . payload)
- (string->uri (assq-ref payload 'aud)))))
-
-(define (id-token-nonce code)
- (match (the-id-token code)
- ((_ . payload)
- (assq-ref payload 'nonce))))
-
-(define (id-token-iat code)
- (match (the-id-token code)
- ((_ . payload)
- (time-utc->date
- (make-time time-utc 0 (assq-ref payload 'iat))))))
-
-(define (id-token-exp code)
- (match (the-id-token code)
- ((_ . payload)
- (time-utc->date
- (make-time time-utc 0 (assq-ref payload 'exp))))))
-
-(define* (id-token-decode str #:key (http-get http-get))
+(define-method (initialize (token <id-token>) initargs)
(with-exception-handler
(lambda (error)
- (let ((final-message
- (if (exception-with-message? error)
- (format #f (G_ "the ID token is invalid: ~a")
- (exception-message error))
- (format #f (G_ "the ID token is invalid")))))
- (raise-exception
- (make-exception
- (make-invalid-id-token)
- (make-exception-with-message final-message)
- error))))
+ (raise-exception
+ (make-exception
+ (make-invalid-id-token)
+ (make-exception-with-message
+ (if (exception-with-message? error)
+ (format #f (G_ "invalid OIDC ID token: ~a")
+ (exception-message error))
+ (G_ "invalid OIDC id token")))
+ error)))
(lambda ()
- (jws-decode
- str
- (lambda (token)
- (let ((iss (id-token-iss token)))
- (let* ((cfg
- (with-exception-handler
- (lambda (error)
- (let ((final-message
- (if (exception-with-message? error)
- (format #f (G_ "I cannot query the identity provider configuration: ~a")
- (exception-message error))
- (format #f (G_ "I cannot query the identity provider configuratioon")))))
- (raise-exception
- (make-exception
- (make-cannot-query-identity-provider iss)
- (make-exception-with-message final-message)
- error))))
- (lambda ()
- (get-oidc-configuration
- (uri-host iss)
- #:userinfo (uri-userinfo iss)
- #:port (uri-port iss)
- #:http-get http-get))))
- (jwks
- (with-exception-handler
- (lambda (error)
- (raise-exception
- (make-exception
- (make-cannot-query-identity-provider iss)
- (make-exception-with-message
- (if (exception-with-message? error)
- (format #f (G_ "I cannot query the JWKS URI of the identity provider: ~a")
- (exception-message error))
- (format #f (G_ "I cannot query the JWKS URI of the identity provider")))))))
- (lambda ()
- (oidc-configuration-jwks cfg #:http-get http-get)))))
- (let ((iat (id-token-iat token))
- (exp (id-token-exp token))
- (current-date ((p:current-date))))
- (let ((iat-s (time-second (date->time-utc iat)))
- (exp-s (time-second (date->time-utc exp)))
- (current-s (time-second (date->time-utc current-date))))
- (when (>= iat-s (+ current-s 5))
- (let ((final-message
- (format #f (G_ "the ID token is signed in the future, ~a, relative to current ~a")
- (date->string iat)
- (date->string current-date))))
- (raise-exception
- (make-exception
- (make-signed-in-future iat current-date)
- (make-exception-with-message final-message)))))
- (when (>= current-s exp-s)
- (let ((final-message
- (format #f (G_ "the ID token expired ~a, which is in the past (from ~a)")
- (date->string exp)
- (date->string current-date))))
- (raise-exception
- (make-exception
- (make-expired exp current-date)
- (make-exception-with-message final-message)))))))
- jwks)))))))
-
-(define (id-token-encode id-token key)
- (with-exception-handler
- (lambda (error)
- (let ((final-message
- (if (exception-with-message? error)
- (format #f (G_ "cannot encode the ID token: ~a")
- (exception-message error))
- (format #f (G_ "cannot encode the ID token")))))
- (raise-exception
- (make-exception-with-message final-message))))
- (lambda ()
- (jws-encode id-token key))))
-
-(define* (issue-id-token
- issuer-key
- #:key
- (webid #f)
- (iss #f)
- (sub #f)
- (aud #f)
- (validity 3600))
- (unless sub
- (set! sub (uri->string webid)))
- (let* ((iat (time-second (date->time-utc ((p:current-date)))))
- (exp (+ iat validity)))
- (jws-encode
- (the-id-token
- `(((alg . ,(symbol->string (alg issuer-key))))
- . ((webid . ,(uri->string webid))
- (iss . ,(uri->string iss))
- (sub . ,sub)
- (aud . ,(uri->string aud))
- (nonce . ,(stubs:random 12))
- (iat . ,iat)
- (exp . ,exp))))
- issuer-key)))
+ (next-method)
+ (let-keywords
+ initargs #t
+ ((webid #f)
+ (sub #f)
+ (aud #f)
+ (jwt-header #f)
+ (jwt-payload #f))
+ (let do-initialize ((webid webid)
+ (sub sub)
+ (aud aud)
+ (jwt-header jwt-header)
+ (jwt-payload jwt-payload))
+ (cond
+ ((string? webid)
+ (do-initialize (string->uri webid) sub aud jwt-header jwt-payload))
+ ((and (not sub) (uri? webid))
+ (do-initialize webid (uri->string webid) aud jwt-header jwt-payload))
+ ((string? aud)
+ (do-initialize webid sub (string->uri aud) jwt-header jwt-payload))
+ ((and webid sub)
+ (unless (uri? webid)
+ (scm-error 'wrong-type-arg "make"
+ (G_ "#:webid should be an URI")
+ '()
+ (list webid)))
+ (unless (string? sub)
+ (scm-error 'wrong-type-arg "make"
+ (G_ "#:sub should be a string")
+ '()
+ (list sub)))
+ (unless (uri? aud)
+ (scm-error 'wrong-type-arg "make"
+ (G_ "#:aud should be a string")
+ '()
+ (list aud)))
+ (slot-set! token 'webid webid)
+ (slot-set! token 'sub sub)
+ (slot-set! token 'aud aud))
+ ((and jwt-header jwt-payload)
+ (do-initialize (assq-ref jwt-payload 'webid)
+ (assq-ref jwt-payload 'sub)
+ (assq-ref jwt-payload 'aud)
+ #f #f))
+ (else
+ (raise-exception
+ (make-exception
+ (make-invalid-jws)
+ (make-exception-with-message
+ (G_ "when making an ID token either its required fields (#:alg, #:webid, #:iss, #:sub, #:aud, #:iat and #:exp) or (#:jwt-header and #:jwt-payload) should be passed")))))))))))
+
+(define-method (token->jwt (token <id-token>))
+ (receive (base-header base-payload)
+ (next-method)
+ (values
+ base-header
+ `((webid . ,(uri->string (webid token)))
+ (sub . ,(sub token))
+ (aud . ,(uri->string (aud token)))
+ ,@base-payload))))
diff --git a/src/scm/webid-oidc/resource-server.scm b/src/scm/webid-oidc/resource-server.scm
index 551b72d..99291b0 100644
--- a/src/scm/webid-oidc/resource-server.scm
+++ b/src/scm/webid-oidc/resource-server.scm
@@ -107,15 +107,27 @@
(('dpop . (? string? string-value))
string-value)))
(access-token
- (access-token-decode lit-access-token
- #:http-get http-get))
- (cnf/jkt (access-token-cnf/jkt access-token))
+ (decode <access-token> lit-access-token
+ #:http-request
+ (lambda* (uri . args)
+ (let without-method ((remaining-args args)
+ (kept-args '()))
+ (match remaining-args
+ (() (apply http-get uri (reverse kept-args)))
+ ((#:method 'GET remaining-args ...)
+ (without-method remaining-args kept-args))
+ (((? keyword? key) value remaining-args ...)
+ (without-method remaining-args
+ `(,value ,key ,@kept-args))))))))
+ (cnf/jkt (cnf/jkt access-token))
(dpop-proof
- (dpop-proof-decode
- method full-uri
- dpop cnf/jkt #:access-token lit-access-token)))
- (let ((subject (access-token-webid access-token))
- (issuer (access-token-iss access-token)))
+ (decode <dpop-proof> dpop
+ #:method method
+ #:uri full-uri
+ #:cnf/check cnf/jkt
+ #:access-token lit-access-token)))
+ (let ((subject (webid access-token))
+ (issuer (iss access-token)))
(confirm-provider subject issuer #:http-get http-get)
subject)))
#:unwind? #t)))))))
diff --git a/src/scm/webid-oidc/token-endpoint.scm b/src/scm/webid-oidc/token-endpoint.scm
index 81f8e48..292df4d 100644
--- a/src/scm/webid-oidc/token-endpoint.scm
+++ b/src/scm/webid-oidc/token-endpoint.scm
@@ -38,6 +38,8 @@
#:use-module (rnrs bytevectors)
#:use-module (sxml simple)
#:use-module (sxml match)
+ #:use-module (oop goops)
+ #:duplicates (merge-generics)
#:declarative? #t
#:export
(
@@ -177,7 +179,7 @@
port)))))))
thunk))))
-(define (make-token-endpoint token-endpoint-uri iss jwk validity)
+(define (make-token-endpoint token-endpoint-uri iss issuer-key validity)
(lambda (request request-body)
(when (bytevector? request-body)
(set! request-body (utf8->string request-body)))
@@ -213,10 +215,11 @@
#:path (uri-path (request-uri request))
#:query (uri-query (request-uri request)))))
(let ((grant-type (assoc-ref form-args "grant_type"))
- (dpop (dpop-proof-decode
- method uri
- (assq-ref (request-headers request) 'dpop)
- (lambda (jkt) #t))))
+ (dpop (decode <dpop-proof> (assq-ref (request-headers request) 'dpop)
+ #:method method
+ #:uri uri
+ #:cnf/check
+ (lambda (jkt) #t))))
(unless (and grant-type (string? grant-type))
(let ((final-message
(format #f (G_ "missing grant type")))
@@ -248,9 +251,16 @@
(make-no-authorization-code)
(make-exception-with-message final-message)
(make-message-for-the-user final-user-message)))))
- (authorization-code-decode str jwk))))
- (values (authorization-code-webid code)
- (authorization-code-client-id code))))
+ (with-exception-handler
+ (lambda (error)
+ (raise-exception
+ (make-exception
+ (make-invalid-authorization-code)
+ error)))
+ (lambda ()
+ (decode <authorization-code> str
+ #:issuer-key issuer-key))))))
+ (values (webid code) (client-id code))))
((refresh_token)
(let ((refresh-token (assoc-ref form-args "refresh_token")))
(unless refresh-token
@@ -268,7 +278,7 @@
(make-message-for-the-user final-user-message)))))
(refresh:with-refresh-token
refresh-token
- (dpop-proof-jwk dpop)
+ (jwk dpop)
values)))
(else
(let ((final-message
@@ -288,26 +298,23 @@
(let* ((iat (time-second (date->time-utc current-time)))
(exp (+ iat validity)))
(let ((id-token
- (issue-id-token
- jwk
+ (issue <id-token>
+ issuer-key
#:webid webid
- #:sub (uri->string webid)
#:iss iss
- #:aud client-id
- #:validity 3600))
+ #:aud client-id))
(access-token
- (issue-access-token
- jwk
+ (issue <access-token>
+ issuer-key
#:webid webid
#:iss iss
- #:validity 3600
- #:client-key (dpop-proof-jwk dpop)
+ #:client-key (jwk dpop)
#:client-id client-id))
(refresh-token
(if (equal? grant-type "refresh_token")
(assoc-ref form-args "refresh_token")
(refresh:issue-refresh-token webid client-id
- (jkt (dpop-proof-jwk dpop))))))
+ (jkt (jwk dpop))))))
(values
(build-response #:headers '((content-type application/json)
(cache-control (no-cache no-store)))
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 251b6b0..99c834d 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -29,7 +29,6 @@ TESTS = %reldir%/load-library.scm \
%reldir%/jkt.scm \
%reldir%/verify.scm \
%reldir%/verification-failed.scm \
- %reldir%/jws.scm \
%reldir%/cache-valid.scm \
%reldir%/cache-revalidate.scm \
%reldir%/oidc-configuration.scm \
@@ -43,6 +42,8 @@ TESTS = %reldir%/load-library.scm \
%reldir%/dpop-proof-replay.scm \
%reldir%/dpop-proof-no-ath.scm \
%reldir%/dpop-proof-invalid-ath.scm \
+ %reldir%/dpop-proof-no-explicit-exp.scm \
+ %reldir%/dpop-proof-no-explicit-iat.scm \
%reldir%/client-manifest-public.scm \
%reldir%/client-manifest.scm \
%reldir%/client-manifest-fraudulent.scm \
diff --git a/tests/authorization-endpoint-submit-form.scm b/tests/authorization-endpoint-submit-form.scm
index 37059fe..2fc7197 100644
--- a/tests/authorization-endpoint-submit-form.scm
+++ b/tests/authorization-endpoint-submit-form.scm
@@ -107,8 +107,8 @@
(exit 9))
(let ((parsed
(parameterize ((p:current-date 60))
- (authorization-code-decode
- (car (assoc-ref args "code"))
- key))))
+ (decode <authorization-code>
+ (car (assoc-ref args "code"))
+ #:issuer-key key))))
(unless parsed
(exit 10)))))))))
diff --git a/tests/dpop-proof-iat-in-future.scm b/tests/dpop-proof-iat-in-future.scm
index f212643..7e6a3b1 100644
--- a/tests/dpop-proof-iat-in-future.scm
+++ b/tests/dpop-proof-iat-in-future.scm
@@ -32,10 +32,11 @@
(define cnf (jkt jwk))
(define proof
(parameterize ((p:current-date 10))
- (issue-dpop-proof
- jwk
- #:htm 'GET
- #:htu (string->uri "https://example.com/res#frag"))))
+ (issue <dpop-proof>
+ jwk
+ #:jwk (public-key jwk)
+ #:htm 'GET
+ #:htu (string->uri "https://example.com/res#frag"))))
(with-exception-handler
(lambda (error)
(unless (and (signed-in-future? error)
@@ -46,10 +47,10 @@
(raise-exception error)))
(lambda ()
(parameterize ((p:current-date 0))
- (dpop-proof-decode 'GET
- (string->uri "https://example.com/res?query")
- proof
- cnf))
+ (decode <dpop-proof> proof
+ #:method 'GET
+ #:uri (string->uri "https://example.com/res?query")
+ #:cnf/check cnf))
(exit 2))
#:unwind? #t
#:unwind-for-type &signed-in-future)))
diff --git a/tests/dpop-proof-iat-too-late.scm b/tests/dpop-proof-iat-too-late.scm
index 149e814..8019d1d 100644
--- a/tests/dpop-proof-iat-too-late.scm
+++ b/tests/dpop-proof-iat-too-late.scm
@@ -32,24 +32,25 @@
(define cnf (jkt jwk))
(define proof
(parameterize ((p:current-date 0))
- (issue-dpop-proof
- jwk
- #:htm 'GET
- #:htu (string->uri "https://example.com/res#frag"))))
+ (issue <dpop-proof>
+ jwk
+ #:jwk (public-key jwk)
+ #:htm 'GET
+ #:htu (string->uri "https://example.com/res#frag"))))
(with-exception-handler
(lambda (error)
(unless (and (expired? error)
(eqv? (time-second (date->time-utc (error-expiration-date error)))
- 120)
+ 30)
(eqv? (time-second (date->time-utc (error-current-date error)))
- 600))
+ 60))
(raise-exception error)))
(lambda ()
- (parameterize ((p:current-date 600))
- (dpop-proof-decode 'GET
- (string->uri "https://example.com/res?query")
- proof
- cnf))
+ (parameterize ((p:current-date 60))
+ (decode <dpop-proof> proof
+ #:method 'GET
+ #:uri (string->uri "https://example.com/res?query")
+ #:cnf/check cnf))
(exit 2))
#:unwind? #t
#:unwind-for-type &expired)))
diff --git a/tests/dpop-proof-invalid-ath.scm b/tests/dpop-proof-invalid-ath.scm
index a82cf47..8c33e77 100644
--- a/tests/dpop-proof-invalid-ath.scm
+++ b/tests/dpop-proof-invalid-ath.scm
@@ -33,20 +33,20 @@
(define cnf (jkt jwk))
(define access-token
(parameterize ((p:current-date 10))
- (issue-access-token
- idp-key
- #:webid (string->uri "https://data.provider/subject")
- #:iss (string->uri "https://identity.provider")
- #:validity 3600
- #:client-key jwk
- #:client-id (string->uri "https://client"))))
+ (issue <access-token>
+ idp-key
+ #:webid (string->uri "https://data.provider/subject")
+ #:iss (string->uri "https://identity.provider")
+ #:client-key jwk
+ #:client-id (string->uri "https://client"))))
(define proof
(parameterize ((p:current-date 0))
- (issue-dpop-proof
- jwk
- #:htm 'GET
- #:htu (string->uri "https://example.com/res?query")
- #:access-token "aaaaaaaaaaaaaaa")))
+ (issue <dpop-proof>
+ jwk
+ #:jwk (public-key jwk)
+ #:htm 'GET
+ #:htu (string->uri "https://example.com/res?query")
+ #:access-token "aaaaaaaaaaaaaaa")))
(with-exception-handler
(lambda (error)
(unless (and (dpop-invalid-ath? error)
@@ -57,11 +57,11 @@
(raise-exception error)))
(lambda ()
(parameterize ((p:current-date 10))
- (dpop-proof-decode 'GET
- (string->uri "https://example.com/res?query")
- proof
- cnf
- #:access-token access-token))
+ (decode <dpop-proof> proof
+ #:method 'GET
+ #:uri (string->uri "https://example.com/res?query")
+ #:cnf/check cnf
+ #:access-token access-token))
(exit 2))
#:unwind? #t
#:unwind-for-type &dpop-invalid-ath)))
diff --git a/tests/dpop-proof-no-ath.scm b/tests/dpop-proof-no-ath.scm
index ec37836..60c9cee 100644
--- a/tests/dpop-proof-no-ath.scm
+++ b/tests/dpop-proof-no-ath.scm
@@ -31,10 +31,11 @@
(define cnf (jkt jwk))
(define proof
(parameterize ((p:current-date 0))
- (issue-dpop-proof
- jwk
- #:htm 'GET
- #:htu (string->uri "https://example.com/res?query"))))
+ (issue <dpop-proof>
+ jwk
+ #:jwk (public-key jwk)
+ #:htm 'GET
+ #:htu (string->uri "https://example.com/res?query"))))
(with-exception-handler
(lambda (error)
(unless (and (dpop-invalid-ath? error)
@@ -45,11 +46,11 @@
(raise-exception error)))
(lambda ()
(parameterize ((p:current-date 10))
- (dpop-proof-decode 'GET
- (string->uri "https://example.com/res?query")
- proof
- cnf
- #:access-token "aaa"))
+ (decode <dpop-proof> proof
+ #:method 'GET
+ #:uri (string->uri "https://example.com/res?query")
+ #:cnf/check cnf
+ #:access-token "aaa"))
(exit 2))
#:unwind? #t
#:unwind-for-type &dpop-invalid-ath)))
diff --git a/tests/dpop-proof-no-explicit-exp.scm b/tests/dpop-proof-no-explicit-exp.scm
new file mode 100644
index 0000000..c485cac
--- /dev/null
+++ b/tests/dpop-proof-no-explicit-exp.scm
@@ -0,0 +1,86 @@
+;; disfluid, implementation of the Solid specification
+;; Copyright (C) 2021 Vivien Kraus
+
+;; This program is free software: you can redistribute it and/or modify
+;; it under the terms of the GNU Affero General Public License as
+;; published by the Free Software Foundation, either version 3 of the
+;; License, or (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU Affero General Public License for more details.
+
+;; You should have received a copy of the GNU Affero General Public License
+;; along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+(use-modules (webid-oidc dpop-proof)
+ (webid-oidc access-token)
+ (webid-oidc jwk)
+ (webid-oidc jws)
+ (webid-oidc testing)
+ (webid-oidc errors)
+ ((webid-oidc stubs) #:prefix stubs:)
+ ((webid-oidc parameters) #:prefix p:)
+ (web uri)
+ (srfi srfi-19)
+ (web response)
+ (ice-9 receive)
+ (oop goops))
+
+(define-class <dpop-proof-with-exp> (<dpop-proof>))
+
+(define malicious-jwt-created? #f)
+
+(define-method (token->jwt (token <dpop-proof-with-exp>))
+ (set! malicious-jwt-created? #t)
+ (receive (header payload) (next-method)
+ (values header
+ `((exp . ,(time-second (date->time-utc (exp token))))
+ ,@payload))))
+
+(with-test-environment
+ "dpop-proof-no-explicit-exp"
+ (lambda ()
+ (define jwk (generate-key #:n-size 2048))
+ (define idp-key (generate-key #:n-size 2048))
+ (define cnf (jkt jwk))
+ (define access-token
+ (parameterize ((p:current-date 0))
+ (issue <access-token>
+ idp-key
+ #:webid (string->uri "https://data.provider/subject")
+ #:iss (string->uri "https://identity.provider")
+ #:client-key jwk
+ #:client-id (string->uri "https://client"))))
+ (define proof
+ (parameterize ((p:current-date 0))
+ (issue <dpop-proof-with-exp>
+ jwk
+ #:jwk (public-key jwk)
+ #:htm 'GET
+ #:htu (string->uri "https://example.com/res?query")
+ #:validity 3600 ;; Obviously too long: the decoder
+ ;; should ignore this value and make it
+ ;; obsolete after 120 seconds.
+ #:access-token access-token)))
+ (unless malicious-jwt-created?
+ (exit 1))
+ (with-exception-handler
+ (lambda (error)
+ (unless (and (expired? error)
+ (eqv? (time-second (date->time-utc (error-expiration-date error)))
+ 30)
+ (eqv? (time-second (date->time-utc (error-current-date error)))
+ 60))
+ (raise-exception error)))
+ (lambda ()
+ (parameterize ((p:current-date 60))
+ (decode <dpop-proof> proof
+ #:method 'GET
+ #:uri (string->uri "https://example.com/res?query")
+ #:cnf/check cnf
+ #:access-token access-token))
+ (exit 2))
+ #:unwind? #t
+ #:unwind-for-type &expired)))
diff --git a/tests/dpop-proof-no-explicit-iat.scm b/tests/dpop-proof-no-explicit-iat.scm
new file mode 100644
index 0000000..671dfa0
--- /dev/null
+++ b/tests/dpop-proof-no-explicit-iat.scm
@@ -0,0 +1,83 @@
+;; disfluid, implementation of the Solid specification
+;; Copyright (C) 2021 Vivien Kraus
+
+;; This program is free software: you can redistribute it and/or modify
+;; it under the terms of the GNU Affero General Public License as
+;; published by the Free Software Foundation, either version 3 of the
+;; License, or (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU Affero General Public License for more details.
+
+;; You should have received a copy of the GNU Affero General Public License
+;; along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+(use-modules (webid-oidc dpop-proof)
+ (webid-oidc access-token)
+ (webid-oidc jwk)
+ (webid-oidc jws)
+ (webid-oidc testing)
+ (webid-oidc errors)
+ ((webid-oidc stubs) #:prefix stubs:)
+ ((webid-oidc parameters) #:prefix p:)
+ (web uri)
+ (srfi srfi-19)
+ (web response)
+ (ice-9 receive)
+ (ice-9 match)
+ (oop goops))
+
+(define-class <dpop-proof-without-iat> (<dpop-proof>))
+
+(define malicious-jwt-created? #f)
+
+(define-method (token->jwt (token <dpop-proof-without-iat>))
+ (set! malicious-jwt-created? #t)
+ ;; Omit the iat field; check that we don’t provide a default
+ (receive (header payload) (next-method)
+ (values header
+ (filter (match-lambda
+ (('iat . _) #f)
+ (else #t))
+ payload))))
+
+(with-test-environment
+ "dpop-proof-no-explicit-iat"
+ (lambda ()
+ (define jwk (generate-key #:n-size 2048))
+ (define idp-key (generate-key #:n-size 2048))
+ (define cnf (jkt jwk))
+ (define access-token
+ (parameterize ((p:current-date 10))
+ (issue <access-token>
+ idp-key
+ #:webid (string->uri "https://data.provider/subject")
+ #:iss (string->uri "https://identity.provider")
+ #:client-key jwk
+ #:client-id (string->uri "https://client"))))
+ (define proof
+ (parameterize ((p:current-date 0))
+ (issue <dpop-proof-without-iat>
+ jwk
+ #:jwk (public-key jwk)
+ #:htm 'GET
+ #:htu (string->uri "https://example.com/res?query")
+ #:access-token access-token)))
+ (unless malicious-jwt-created?
+ (exit 1))
+ (with-exception-handler
+ (lambda (error)
+ (unless (invalid-jws? error) ;; iat should not be missing
+ (exit 2)))
+ (lambda ()
+ (parameterize ((p:current-date 180))
+ (decode <dpop-proof> proof
+ #:method 'GET
+ #:uri (string->uri "https://example.com/res?query")
+ #:cnf/check cnf
+ #:access-token access-token))
+ (exit 3))
+ #:unwind? #t
+ #:unwind-for-type &invalid-jws)))
diff --git a/tests/dpop-proof-replay.scm b/tests/dpop-proof-replay.scm
index 19e6a30..5720d93 100644
--- a/tests/dpop-proof-replay.scm
+++ b/tests/dpop-proof-replay.scm
@@ -31,23 +31,24 @@
(define cnf (jkt jwk))
(define proof
(parameterize ((p:current-date 0))
- (issue-dpop-proof
- jwk
- #:htm 'GET
- #:htu (string->uri "https://example.com/res#frag"))))
- (define (decode)
+ (issue <dpop-proof>
+ jwk
+ #:jwk (public-key jwk)
+ #:htm 'GET
+ #:htu (string->uri "https://example.com/res#frag"))))
+ (define (do-decode)
(parameterize ((p:current-date 10))
- (dpop-proof-decode 'GET
- (string->uri "https://example.com/res?query")
- proof
- cnf)))
- (define decoded-once (decode))
+ (decode <dpop-proof> proof
+ #:method 'GET
+ #:uri (string->uri "https://example.com/res?query")
+ #:cnf/check cnf)))
+ (define decoded-once (do-decode))
(with-exception-handler
(lambda (error)
(unless (jti-found? error)
(raise-exception error)))
(lambda ()
- (decode)
+ (do-decode)
(exit 2))
#:unwind? #t
#:unwind-for-type &jti-found)))
diff --git a/tests/dpop-proof-valid-ath.scm b/tests/dpop-proof-valid-ath.scm
index 2a27e88..afcc9cd 100644
--- a/tests/dpop-proof-valid-ath.scm
+++ b/tests/dpop-proof-valid-ath.scm
@@ -31,26 +31,27 @@
(define cnf (jkt jwk))
(define access-token
(parameterize ((p:current-date 10))
- (issue-access-token
- idp-key
- #:webid (string->uri "https://data.provider/subject")
- #:iss (string->uri "https://identity.provider")
- #:validity 3600
- #:client-key jwk
- #:client-id (string->uri "https://client"))))
+ (issue <access-token>
+ idp-key
+ #:webid "https://data.provider/subject"
+ #:iss "https://identity.provider"
+ #:client-key jwk
+ #:client-id "https://client")))
(define proof
(parameterize ((p:current-date 0))
- (issue-dpop-proof
- jwk
- #:htm 'GET
- #:htu (string->uri "https://example.com/res#frag")
- #:access-token access-token)))
+ (issue <dpop-proof>
+ jwk
+ #:jwk (public-key jwk)
+ #:htm 'GET
+ #:htu "https://example.com/res#frag"
+ #:access-token access-token)))
(define decoded
(parameterize ((p:current-date 10))
- (dpop-proof-decode 'GET
- (string->uri "https://example.com/res?query")
- proof
- cnf
- #:access-token access-token)))
+ (decode <dpop-proof>
+ proof
+ #:method 'GET
+ #:uri (string->uri "https://example.com/res?query")
+ #:cnf/check cnf
+ #:access-token access-token)))
(unless decoded
(exit 1))))
diff --git a/tests/dpop-proof-valid.scm b/tests/dpop-proof-valid.scm
index 71ef602..1ef50d4 100644
--- a/tests/dpop-proof-valid.scm
+++ b/tests/dpop-proof-valid.scm
@@ -30,15 +30,16 @@
(define cnf (jkt jwk))
(define proof
(parameterize ((p:current-date 0))
- (issue-dpop-proof
+ (issue <dpop-proof>
jwk
+ #:jwk (public-key jwk)
#:htm 'GET
#:htu (string->uri "https://example.com/res#frag"))))
(define decoded
(parameterize ((p:current-date 10))
- (dpop-proof-decode 'GET
- (string->uri "https://example.com/res?query")
- proof
- cnf)))
+ (decode <dpop-proof> proof
+ #:method 'GET
+ #:uri (string->uri "https://example.com/res?query")
+ #:cnf/check cnf)))
(unless decoded
(exit 1))))
diff --git a/tests/dpop-proof-wrong-htm.scm b/tests/dpop-proof-wrong-htm.scm
index 1e94f72..b59dc9a 100644
--- a/tests/dpop-proof-wrong-htm.scm
+++ b/tests/dpop-proof-wrong-htm.scm
@@ -31,10 +31,11 @@
(define cnf (jkt jwk))
(define proof
(parameterize ((p:current-date 0))
- (issue-dpop-proof
- jwk
- #:htm 'POST
- #:htu (string->uri "https://example.com/res#frag"))))
+ (issue <dpop-proof>
+ jwk
+ #:jwk (public-key jwk)
+ #:htm 'POST
+ #:htu (string->uri "https://example.com/res#frag"))))
(with-exception-handler
(lambda (error)
(unless (and (dpop-method-mismatch? error)
@@ -45,10 +46,10 @@
(raise-exception error)))
(lambda ()
(parameterize ((p:current-date 10))
- (dpop-proof-decode 'GET
- (string->uri "https://example.com/res?query")
- proof
- cnf))
+ (decode <dpop-proof> proof
+ #:method 'GET
+ #:uri (string->uri "https://example.com/res?query")
+ #:cnf/jkt cnf))
(exit 2))
#:unwind? #t
#:unwind-for-type &dpop-method-mismatch)))
diff --git a/tests/dpop-proof-wrong-htu.scm b/tests/dpop-proof-wrong-htu.scm
index 299060e..68303d9 100644
--- a/tests/dpop-proof-wrong-htu.scm
+++ b/tests/dpop-proof-wrong-htu.scm
@@ -31,10 +31,11 @@
(define cnf (jkt jwk))
(define proof
(parameterize ((p:current-date 0))
- (issue-dpop-proof
- jwk
- #:htm 'GET
- #:htu (string->uri "https://example.com/other-res#frag"))))
+ (issue <dpop-proof>
+ jwk
+ #:jwk (public-key jwk)
+ #:htm 'GET
+ #:htu (string->uri "https://example.com/other-res#frag"))))
(with-exception-handler
(lambda (error)
(unless (and (dpop-uri-mismatch? error)
@@ -45,10 +46,10 @@
(raise-exception error)))
(lambda ()
(parameterize ((p:current-date 10))
- (dpop-proof-decode 'GET
- (string->uri "https://example.com/res?query")
- proof
- cnf))
+ (decode <dpop-proof> proof
+ #:method 'GET
+ #:uri (string->uri "https://example.com/res?query")
+ #:cnf/jkt cnf))
(exit 2))
#:unwind? #t
#:unwind-for-type &dpop-uri-mismatch)))
diff --git a/tests/dpop-proof-wrong-key.scm b/tests/dpop-proof-wrong-key.scm
index 1f3d033..cb5d4e5 100644
--- a/tests/dpop-proof-wrong-key.scm
+++ b/tests/dpop-proof-wrong-key.scm
@@ -31,20 +31,21 @@
(define cnf (jkt (generate-key #:n-size 2048)))
(define proof
(parameterize ((p:current-date 0))
- (issue-dpop-proof
- jwk
- #:htm 'GET
- #:htu (string->uri "https://example.com/res#frag"))))
+ (issue <dpop-proof>
+ jwk
+ #:jwk (public-key jwk)
+ #:htm 'GET
+ #:htu (string->uri "https://example.com/res#frag"))))
(with-exception-handler
(lambda (error)
(unless (dpop-unconfirmed-key? error)
(raise-exception error)))
(lambda ()
(parameterize ((p:current-date 10))
- (dpop-proof-decode 'GET
- (string->uri "https://example.com/res?query")
- proof
- cnf))
+ (decode <dpop-proof> proof
+ #:method 'GET
+ #:uri (string->uri "https://example.com/res?query")
+ #:cnf/check cnf))
(exit 2))
#:unwind? #t
#:unwind-for-type &dpop-unconfirmed-key)))
diff --git a/tests/jws.scm b/tests/jws.scm
deleted file mode 100644
index a5c9330..0000000
--- a/tests/jws.scm
+++ /dev/null
@@ -1,70 +0,0 @@
-;; disfluid, implementation of the Solid specification
-;; Copyright (C) 2020, 2021 Vivien Kraus
-
-;; This program is free software: you can redistribute it and/or modify
-;; it under the terms of the GNU Affero General Public License as
-;; published by the Free Software Foundation, either version 3 of the
-;; License, or (at your option) any later version.
-
-;; This program is distributed in the hope that it will be useful,
-;; but WITHOUT ANY WARRANTY; without even the implied warranty of
-;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-;; GNU Affero General Public License for more details.
-
-;; You should have received a copy of the GNU Affero General Public License
-;; along with this program. If not, see <https://www.gnu.org/licenses/>.
-
-(use-modules (webid-oidc stubs)
- (webid-oidc jwk)
- (webid-oidc jws)
- (webid-oidc testing))
-
-(with-test-environment
- "jws"
- (lambda ()
- (let* ((key
- (jwk->key
- (json-string->scm "{\"kty\":\"RSA\",\"e\":\"AQAB\",\"kid\":\"db7cdbbf-0ca3-48da-abf6-8f34002a4651\",\"n\":\"nzyis1ZjfNB0bBgKFMSvvkTtwlvBsaJq7S5wA-kzeVOVpVWwkWdVha4s38XM_pa_yr47av7-z3VTmvDRyAHcaT92whREFpLv9cj5lTeJSibyr_Mrm_YtjCZVWgaOYIhwrXwKLqPr_11inWsAkfIytvHWTxZYEcXLgAXFuUuaS3uF9gEiNQwzGTU1v0FqkqTBr4B8nW3HCN47XUu0t8Y0e-lf4s4OxQawWD79J9_5d3Ry0vbV3Am1FtGJiJvOwRsIfVChDpYStTcHTCMqtvWbV6L11BWkpzGXSW4Hv43qa-GSYOD2QU68Mb59oSk2OB-BtOLpJofmbGEGgvmwyCI9Mw\"}")))
- (other-key (generate-key #:n-size 2048))
- (encoded "eyJhbGciOiJQUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0.hZnl5amPk_I3tb4O-Otci_5XZdVWhPlFyVRvcqSwnDo_srcysDvhhKOD01DigPK1lJvTSTolyUgKGtpLqMfRDXQlekRsF4XhAjYZTmcynf-C-6wO5EI4wYewLNKFGGJzHAknMgotJFjDi_NCVSjHsW3a10nTao1lB82FRS305T226Q0VqNVJVWhE4G0JQvi2TssRtCxYTqzXVt22iDKkXeZJARZ1paXHGV5Kd1CljcZtkNZYIGcwnj65gvuCwohbkIxAnhZMJXCLaVvHqv9l-AAUV7esZvkQR1IpwBAiDQJh4qxPjFGylyXrHMqh5NlT_pWL2ZoULWTg_TJjMO9TuQ")
- (expected-alg "PS256")
- (expected-typ "JWT")
- (expected-sub "1234567890")
- (expected-name "John Doe")
- (expected-admin #t)
- (expected-iat 1516239022)
- (parsed (jws-decode encoded (lambda (jws)
- (and (jws? jws)
- key))))
- (parsed-header (car parsed))
- (parsed-payload (cdr parsed))
- (alg (jws-alg parsed))
- (typ (assq-ref parsed-header 'typ))
- (sub (assq-ref parsed-payload 'sub))
- (name (assq-ref parsed-payload 'name))
- (admin (assq-ref parsed-payload 'admin))
- (iat (assq-ref parsed-payload 'iat))
- (re-encoded (jws-encode parsed other-key))
- (re-parsed (jws-decode re-encoded (lambda (jws) other-key)))
- (re-parsed-header (car re-parsed))
- (re-parsed-payload (cdr re-parsed))
- (re-alg (jws-alg re-parsed))
- (re-typ (assq-ref re-parsed-header 'typ))
- (re-sub (assq-ref re-parsed-payload 'sub))
- (re-name (assq-ref re-parsed-payload 'name))
- (re-admin (assq-ref re-parsed-payload 'admin))
- (re-iat (assq-ref re-parsed-payload 'iat)))
- (unless (and (equal? alg expected-alg)
- (equal? re-alg expected-alg)
- (equal? typ expected-typ)
- (equal? re-typ expected-typ)
- (equal? sub expected-sub)
- (equal? re-sub expected-sub)
- (equal? name expected-name)
- (equal? re-name expected-name)
- (equal? admin expected-admin)
- (equal? re-admin expected-admin)
- (equal? iat expected-iat)
- (equal? re-iat expected-iat))
- (format (current-error-port)
- "The JWS test failed.")))))
diff --git a/tests/resource-server.scm b/tests/resource-server.scm
index 02b7e46..a8032b1 100644
--- a/tests/resource-server.scm
+++ b/tests/resource-server.scm
@@ -57,23 +57,23 @@
(else (exit 1))))
(define access-token
(parameterize ((p:current-date 10))
- (issue-access-token
- idp-key
- #:webid subject
- #:iss (string->uri "https://identity.provider")
- #:validity 3600
- #:client-key client-key
- #:client-id (string->uri "https://client"))))
+ (issue <access-token>
+ idp-key
+ #:webid subject
+ #:iss (string->uri "https://identity.provider")
+ #:client-key client-key
+ #:client-id (string->uri "https://client"))))
(define uri (string->uri "https://resource.server/resource"))
(define server-uri (string->uri "https://resource.server/"))
(define method 'GET)
(define dpop-proof
(parameterize ((p:current-date 15))
- (issue-dpop-proof
- client-key
- #:htm method
- #:htu uri
- #:access-token access-token)))
+ (issue <dpop-proof>
+ client-key
+ #:jwk (public-key client-key)
+ #:htm method
+ #:htu uri
+ #:access-token access-token)))
(define rq
(call-with-input-string
(format #f "GET /resource HTTP/1.1\r\n\
diff --git a/tests/token-endpoint-issue.scm b/tests/token-endpoint-issue.scm
index c80658c..0815c30 100644
--- a/tests/token-endpoint-issue.scm
+++ b/tests/token-endpoint-issue.scm
@@ -43,11 +43,10 @@
(define validity 3600)
(define authz
(parameterize ((p:current-date 0))
- (issue-authorization-code
- key
- #:validity 120
- #:webid subject
- #:client-id client)))
+ (issue <authorization-code>
+ key
+ #:webid subject
+ #:client-id client)))
(define endpoint
(make-token-endpoint
(string->uri "https://token-endpoint-issue.scm/token")
@@ -56,11 +55,12 @@
;; The code is fake!
(let ((dpop
(parameterize ((p:current-date 0))
- (issue-dpop-proof
- client-key
- #:htm 'POST
- #:htu (string->uri
- "https://token-endpoint-issue.scm/token")))))
+ (issue <dpop-proof>
+ client-key
+ #:jwk (public-key client-key)
+ #:htm 'POST
+ #:htu (string->uri
+ "https://token-endpoint-issue.scm/token")))))
(parameterize ((p:current-date 0))
(endpoint
(build-request (string->uri
@@ -75,11 +75,12 @@
(receive (response response-body . _)
(let ((dpop
(parameterize ((p:current-date 10))
- (issue-dpop-proof
- client-key
- #:htm 'POST
- #:htu (string->uri
- "https://token-endpoint-issue.scm/token")))))
+ (issue <dpop-proof>
+ client-key
+ #:jwk (public-key client-key)
+ #:htm 'POST
+ #:htu (string->uri
+ "https://token-endpoint-issue.scm/token")))))
(parameterize ((p:current-date 10))
(endpoint
(build-request (string->uri
@@ -101,11 +102,29 @@
(exit 6))
(unless refresh-token-enc
(exit 7))
- (let ((access-token (jws-decode access-token-enc
- (lambda (h) key))))
+ (let ((access-token
+ (parameterize ((p:current-date 20))
+ (decode <access-token> access-token-enc
+ #:http-request
+ (lambda* (uri . args)
+ (cond
+ ((equal? uri (string->uri "https://issuer.token-endpoint-issue.scm/.well-known/openid-configuration"))
+ (values (build-response #:headers '((content-type application/json)))
+ "{
+ \"jwks_uri\": \"https://token-endpoint-issue.scm/keys\",
+ \"token_endpoint\": \"https://token-endpoint-issue.scm/token\",
+ \"authorization_endpoint\": \"https://token-endpoint-issue.scm/authorize\",
+ \"solid_oidc_supported\": \"https://solidproject.org/TR/solid-oidc\"
+}"))
+ ((equal? uri (string->uri "https://token-endpoint-issue.scm/keys"))
+ (values (build-response #:headers '((content-type application/json)))
+ (stubs:scm->json-string `((keys . ,(list->vector (list (key->jwk key))))))))
+ (else
+ (format (current-error-port) "Unknown URI: ~s\n" (uri->string uri))
+ (exit 11))))))))
(unless access-token
(exit 8))
- (let ((access-token-cnf/jkt (access-token-cnf/jkt access-token)))
+ (let ((access-token-cnf/jkt (cnf/jkt access-token)))
(unless access-token-cnf/jkt
(exit 9))
(unless (string=? access-token-cnf/jkt (jkt client-key))
diff --git a/tests/token-endpoint-refresh.scm b/tests/token-endpoint-refresh.scm
index f14d648..f0174b8 100644
--- a/tests/token-endpoint-refresh.scm
+++ b/tests/token-endpoint-refresh.scm
@@ -19,6 +19,7 @@
(webid-oidc refresh-token)
(webid-oidc dpop-proof)
(webid-oidc jwk)
+ (webid-oidc access-token)
(webid-oidc jws)
(webid-oidc jti)
(webid-oidc testing)
@@ -50,11 +51,12 @@
;; The refresh token is fake!
(let ((dpop
(parameterize ((p:current-date 0))
- (issue-dpop-proof
- client-key
- #:htm 'POST
- #:htu (string->uri
- "https://token-endpoint-issue.scm/token")))))
+ (issue <dpop-proof>
+ client-key
+ #:jwk (public-key client-key)
+ #:htm 'POST
+ #:htu (string->uri
+ "https://token-endpoint-issue.scm/token")))))
(parameterize ((p:current-date 0))
(endpoint
(build-request (string->uri
@@ -69,11 +71,12 @@
(receive (response response-body user error)
(let ((dpop
(parameterize ((p:current-date 10))
- (issue-dpop-proof
- client-key
- #:htm 'POST
- #:htu (string->uri
- "https://token-endpoint-issue.scm/token")))))
+ (issue <dpop-proof>
+ client-key
+ #:jwk (public-key client-key)
+ #:htm 'POST
+ #:htu (string->uri
+ "https://token-endpoint-issue.scm/token")))))
(parameterize ((p:current-date 10))
(endpoint
(build-request (string->uri
@@ -94,17 +97,31 @@
(exit 6))
(unless refresh-token-enc
(exit 7))
- (let ((access-token (jws-decode access-token-enc
- (lambda (h) key))))
+ (let ((access-token
+ (parameterize ((p:current-date 20))
+ (decode <access-token> access-token-enc
+ #:http-request
+ (lambda* (uri . args)
+ (cond
+ ((equal? uri (string->uri "https://issuer.token-endpoint-issue.scm/.well-known/openid-configuration"))
+ (values (build-response #:headers '((content-type application/json)))
+ "{
+ \"jwks_uri\": \"https://token-endpoint-issue.scm/keys\",
+ \"token_endpoint\": \"https://token-endpoint-issue.scm/token\",
+ \"authorization_endpoint\": \"https://token-endpoint-issue.scm/authorize\",
+ \"solid_oidc_supported\": \"https://solidproject.org/TR/solid-oidc\"
+}"))
+ ((equal? uri (string->uri "https://token-endpoint-issue.scm/keys"))
+ (values (build-response #:headers '((content-type application/json)))
+ (stubs:scm->json-string `((keys . ,(list->vector (list (key->jwk key))))))))
+ (else
+ (exit 8))))))))
(unless access-token
- (exit 8))
- (let ((access-token-cnf (assq-ref access-token 'cnf)))
- (unless access-token-cnf
- (exit 9))
- (let ((access-token-cnf/jkt (assq-ref access-token-cnf 'jkt)))
- (unless access-token-cnf/jkt
- (exit 10))
- (unless (string=? access-token-cnf/jkt (jkt client-key))
- (exit 11))))
- (unless (string=? refresh-token-enc refresh-code)
- (exit 12)))))))))
+ (exit 9))
+ (let ((access-token-cnf/jkt (cnf/jkt access-token)))
+ (unless access-token-cnf/jkt
+ (exit 10))
+ (unless (string=? access-token-cnf/jkt (jkt client-key))
+ (exit 11))))
+ (unless (string=? refresh-token-enc refresh-code)
+ (exit 12))))))))