\input texinfo @c -*-texinfo-*- @comment $Id@w{$} @documentlanguage en @comment %**start of header @include version.texi @settitle Demanding Interoperability to Strengthen the Free (Libre) Web: Introducing Disfluid @syncodeindex pg cp @syncodeindex fn cp @syncodeindex vr cp @syncodeindex tp cp @comment %**end of header @copying This is the manual of disfluid (version @value{VERSION}, @value{UPDATED}), an implementation of the Solid authentication protocol for guile, client and server. Copyright @copyright{} 2020, 2021 Vivien Kraus @quotation Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.3 or any later version published by the Free Software Foundation; with no Invariant Sections, with no Front-Cover Texts, and with no Back-Cover Texts. A copy of the license is included in the section entitled ``GNU Free Documentation License'' @end quotation @end copying @dircategory Software libraries @direntry * disfluid: (disfluid)Interoperability on the web @end direntry @titlepage @title Demanding Interoperability to Strengthen the Free (Libre) Web: Introducing Disfluid @subtitle for version @value{VERSION}, @value{UPDATED} @author Vivien Kraus (@email{vivien@@planete-kraus.eu}) @page @vskip 0pt plus 1fill @insertcopying @end titlepage @contents @ifnottex @node Top @top Disfluid @end ifnottex @menu * Decentralized Authentication on the Web:: * Invoking disfluid:: * Running disfluid with GNU Guix:: * The Json Web Token:: * Caching on server side:: * Content negociation:: * Running an Identity Provider:: * Running a Resource Server:: * Running a client:: * Exceptional conditions:: * GNU Free Documentation License:: * Index:: @end menu @node Decentralized Authentication on the Web @chapter Decentralized Authentication on the Web Authentication on the web is currently handled in the following way: anyone can install a server that will authenticate users on the web. The problem is interoperability. If a client (an application) wants to authenticate a user, it has to be approved by the authentication server. In other words, if @var{useful-program} wants to authenticate @var{MegaCorp} users, then @var{useful-program} has to register to @var{MegaCorp} first, and get approved. This goes against the principle of permission-less innovation, which is at the heart of the web. In the decentralized authentication web, the best attempt so far is that of ActivityPub. All servers are interoperable with respect to authentication: if user A emits an activity, it is forwarded by A's server to its recipients, and A's server is responsible for A's identity. The problem with that approach is that the data is tied to the application. It is not possible to use another application to process the data differently, or to use multiple data sources, in an interoperable way (without the ActivityPub server knowing). This means that on Activitypub, microblogging applications will not present different activities correctly. This also means that it is difficult to write a free replacement to a non-free application program, because it would need to manage the data. In the Solid ecosystem, there is a clear distinction between servers and applications. An application is free to read data from all places at the same time, using a permission-less authentication system. Since the applications do not need to store data, the cost of having users is neglectible, so users do not need prior approval before using them (making captchas and the like a thing of the past). Servers do not have a say in which applications the user uses. The authentication used is a slight modification of the well-established OpenID Connect. It is intended to work in a web browser, but this package demonstrates that it also works without a web browser. @node Invoking disfluid @chapter Invoking disfluid The @samp{disfluid} program provides different modes of operations: @table @samp @item reverse-proxy Run an authenticating reverse proxy. With this command, you specify a backend server. When an authenticated user makes a request, you receive an additional header containing the user’s identity. @item identity-provider Run the identity provider only. @item client-service The client applications must serve some resources: namely, the client manifest and the redirect URI. @item server Run both an identity provider and a resource server. @end table The server is configured with command-line arguments, and environment variables. @menu * General options:: * General server configuration:: * Configuration for the resource server:: * Configuration for the identity provider:: * Configuration for the client service:: @end menu @node General options @section General options The server will respond to @samp{-h} and @samp{-v} commands, to get the help output and the version information. The server output (command-line, logs) are localized for the system administrator. You can control it with the @samp{LANG} environment variable. So if your locale is not English, you can have the same commands as in this manual by running with @code{LANG=C}. The programs respect the @samp{XDG_DATA_HOME} and @samp{XDG_CACHE_HOME} to store persistent data and disposable data. The cache directory can be deleted at any time. If one of these variables is not set, its value is computed from the @samp{HOME} environment variable. @node General server configuration @section General server configuration All servers are published under the Affero GPL, which means that the service provider needs to publish all changes made to the program to users over the network. The @samp{disfluid} command provides a @samp{--complete-corresponding-source} option so that the system administrator can specify a means to download the source. The servers will add a @samp{Source:} header in each response, containing the value of this configuration option. The servers can be configured to redirect output and errors to a log file and an error file, with the @samp{--log-file} and @samp{--error-file} options. The server will listen to port 8080 by default, but this may be configured with @samp{--port}. Since the servers do not support TLS, and they only support HTTP/1.1, they are intended to run behind a reverse proxy (even for the authenticating reverse proxy). Finally, the servers are required to know their public name. This is configured with the @samp{--server-name} option. The server will make requests on the world-wide web, for instance to download client manifests. The requests can be redirected with XML Catalog, by setting the @samp{XML_CATALOG_FILES} to a space-separated list of URIs (can be @code{file:} URIs). The requests cannot be directed to the file system. @node Configuration for the resource server @section Configuration for the resource server The reverse proxy sets an identity header to authenticated requests. By default, it is @samp{XXX-Agent}, but it can be configured with @samp{--header}. The reverse proxy is configured to contact a backend URI with @samp{--backend-uri}. This backend URI should not be directly exposed, because a malicious user could set the identity header. @node Configuration for the identity provider @section Configuration for the identity provider The identity provider can only handle one user. If you want to handle multiple users, it is highly advised to use a different host name for each user, in case the server is accessed from a web browser. You can set the identity of the user with @samp{--subject}, and write the user’s password in a file. Pass the file name with @samp{--encrypted-password-file}. You can pass the encrypted password directly with @samp{--encrypted-password}, but the encrypted password will be public. The encrypted password format is defined by the crypt function in the C library. For glibc, it looks like this: @code{$@var{N}$@var{salt}$@var{hash}}, where @var{N} is the algorithm identifier, @var{salt} is the password salt annd @var{hash} is its hash. The server uses a key, which is not the same thing as the TLS certificate of the server (remember, the servers don’t support TLS). It is in the JWK format. You set its file name with @samp{--key-file}. If the key file does not exist, it will be generated. Finally, the public openid configuration requires you to set the JWKS URI (@samp{--jwks-uri}), authorization endpoint URI (@samp{--authorization-endpoint-uri}) and token endpoint URI (@samp{--token-endpoint-uri}). The identity provider will publish the full URIs, but will respond to their path, regardless of the host. @node Configuration for the client service @section Configuration for the client service The client will serve a stupid page for the redirect URI that will only display the authorization code. The redirect URI is set with @samp{--redirect-uri}. The client ID is set with @samp{--client-id}. This is the URI under which the client registrationn is served. Finally, you can set some cosmetic options, but since it can confuse the user, they are hidden by default by the identity provider. @table @samp @item --client-name set the name of the application. @item --client-uri set an URI where to find more information about the client. @end table @node Running disfluid with GNU Guix @chapter Running disfluid with GNU Guix The channel at @url{https://labo.planete-kraus.eu/webid-oidc-channel.git} can be used with guix. It defines the package at the latest commit, and a service definition in @emph{(vkraus services disfluid)}. @defvr {service type} disfluid-service-type This service runs a bunch of disfluid servers with the @emph{disfluid} system user, each with a unique name. The value it takes is an alist of service configurations: the keys are unique names (to differenciate the generated shepherd services), and the values are configuration records for an issuer, reverse proxy, server, or client service. @end defvr @deftp {configuration record} [@var{disfluid}] @var{complete-corresponding-source} @var{issuer} @var{key-file} @var{subject} @var{encrypted-password-file} @var{jwks-uri} @var{authorization-endpoint-uri} @var{token-endpoint-uri} @var{port} [@var{extra-options}] The configuration for the identity provider. The optional @var{disfluid} argument is the package containing the binary to run, if you want to apply some patches, and @var{extra-options} is an empty list by default. @end deftp @deftp {configuration record} [@var{disfluid}] @var{complete-corresponding-source} @var{port} @var{inbound-uri} @var{outbound-uri} @var{header} [@var{extra-options}] This record configures an authenticating reverse proxy. @end deftp @deftp {configuration record} [@var{disfluid}] @var{complete-corresponding-source} @var{client-id} @var{redirect-uri} [@var{client-name}] [@var{client-uri}] @var{port} [@var{extra-options}] This record configures a server to serve public application pages. @end deftp @deftp {configuration record} [@var{disfluid}] @var{complete-corresponding-source} @var{server-name} @var{key-file} @var{subject} @var{encrypted-password-file} @var{jwks-uri} @var{authorization-endpoint-uri} @var{token-endpoint-uri} @var{port} [@var{extra-options}] 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{#:alg} @var{#:webid} @var{#:iss} @var{#:sub} @var{#:aud} @var{#:validity} Create an ID token that is valid for @var{#:validity} seconds, 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{#alg} @var{#webid} @var{#iss} @var{#:validity} @var{[#client-key} @var{|} @var{#cnf/jkt]} @var{#client-id} 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{#alg} @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 Some functions require a key, or a key pair, to operate. The @emph{(webid-oidc jwk)} module provides you with everything required to manage keys. @deftp {Class} () This is the base class for a private key. You need it to issue signatures. @end deftp @deftp {Class} () This is the base class for a public key. You need it to check signatures. @end deftp @deftp {Class} () @var{public-key} @var{private-key} A key pair contains a @var{public-key} and a matching @var{private-key}. You use this form for keys you own. @end deftp @deftp {Class} () (@code{}) This key pair contains matching RSA keys. @end deftp @deftp {Class} () (@code{}) @var{crv} This key pair contains matching elliptic curve keys. @var{crv} is a symbol identifiying the curve. @end deftp @deftp {Class} () @var{d} @var{p} @var{q} @var{dp} @var{dq} @var{qi} @deftpx {Class} () @var{n} @var{e} @deftpx {Class} () @var{crv} @var{z} @deftpx {Class} () @var{crv} @var{x} @var{y} All fields are strings, base64 encoding the parameters, except @var{crv}, which is a symbol. @end deftp @deftp {Class} () @var{keys} An identity provider may use different keys that are in validity to sign different access tokens. The JWKS encapsulates many public @var{keys}. @end deftp @deftypefn {Generic method} public-key (@var{key} @code{}) @deftypefnx {Generic method} public-key (@var{key} @code{}) Return the public part of @var{key}, which may either be a key pair or a public key. @end deftypefn @deftypefn {Generic method} private-key (@var{key} @code{}) @deftypefnx {Generic method} private-key (@var{key} @code{}) Return the private part of @var{key}. @end deftypefn @deftypefn {Generic method} rsa-d (@var{key} @code{}) @deftypefnx {Generic method} rsa-d (@var{key} @code{}) @deftypefnx {Generic method} rsa-p (@var{key} @code{}) @deftypefnx {Generic method} rsa-p (@var{key} @code{}) @deftypefnx {Generic method} rsa-q (@var{key} @code{}) @deftypefnx {Generic method} rsa-q (@var{key} @code{}) @deftypefnx {Generic method} rsa-dp (@var{key} @code{}) @deftypefnx {Generic method} rsa-dp (@var{key} @code{}) @deftypefnx {Generic method} rsa-dq (@var{key} @code{}) @deftypefnx {Generic method} rsa-dq (@var{key} @code{}) @deftypefnx {Generic method} rsa-qi (@var{key} @code{}) @deftypefnx {Generic method} rsa-qi (@var{key} @code{}) @deftypefnx {Generic method} rsa-n (@var{key} @code{}) @deftypefnx {Generic method} rsa-n (@var{key} @code{}) @deftypefnx {Generic method} rsa-e (@var{key} @code{}) @deftypefnx {Generic method} rsa-e (@var{key} @code{}) @deftypefnx {Generic method} ec-crv (@var{key} @code{}) @deftypefnx {Generic method} ec-crv (@var{key} @code{}) @deftypefnx {Generic method} ec-crv (@var{key} @code{}) @deftypefnx {Generic method} ec-x (@var{key} @code{}) @deftypefnx {Generic method} ec-x (@var{key} @code{}) @deftypefnx {Generic method} ec-y (@var{key} @code{}) @deftypefnx {Generic method} ec-y (@var{key} @code{}) @deftypefnx {Generic method} ec-z (@var{key} @code{}) @deftypefnx {Generic method} ec-z (@var{key} @code{}) Key parameter getters. @end deftypefn @deftypefn {Generic method} keys (@var{jwks} @code{}) Return all the public keys used by @var{jwks}. @end deftypefn @deftypefn {Generic method} check-key (@var{key} @code{}) @deftypefnx {Generic method} check-key (@var{key} @code{}) Check that the @var{key} parameters are consistent. @end deftypefn When exchanging keys, maybe you will have them in the form of a JWK: an alist from symbols to strings, as a representation for a JSON object. @deftypefn {Generic method} key->jwk (@var{key} @code{}) @deftypefnx {Generic method} key->jwk (@var{key} @code{}) Return an alist with known parameter names for JSON. @end deftypefn @deffn function jwk->key @var{jwk} Parse @var{jwk} as a key or a key pair. @end deffn @deftypefn {Generic method} kty (@var{key} @code{}) @deftypefnx {Generic method} kty (@var{key} @code{}) @deftypefnx {Generic method} kty (@var{key} @code{}) @deftypefnx {Generic method} kty (@var{key} @code{}) @deftypefnx {Generic method} kty (@var{key} @code{}) @deftypefnx {Generic method} kty (@var{key} @code{}) Return @code{'RSA} for RSA keys, or @code{'EC} for elliptic curve keys. @end deftypefn @deftypefn {Generic method} jkt (@var{key} @code{}) @deftypefnx {Generic method} jkt (@var{key} @code{}) Hash the @var{key} parameters in a reproducible order to get the hash of a key. @end deftypefn @deffn function generate-key @var{[#:n-size]} @var{[#:e-size]} @var{[#:e=\"AQAB\"]} @var{[#:crv]} Generate a new key pair. @end deffn @deftypefn {Generic method} serve (@var{jwks} @code{}) @var{expiration-date} Return a response and response body for serving @var{jwks}. Client-side caching is very much necessary for a JWKS, so pass @var{expiration-date} as a SRFI-19 date to define a maximum date for caching. It should be in the future, for instance in 1 hour. @end deftypefn @deffn {function} get-jwks @var{uri} [#:@var{http-request}] Download a JWKS on the web at @var{uri}. Use @var{http-request}, with the same interface as that of @emph{(web client)}, to actually get the JWKS. @end deffn @deftp {Exception type} ¬-a-jwk If the key parameters are incorrect, this exception is raised. @end deftp @deftp {Exception type} ¬-a-jwks If the JWKS cannot be downloaded, or is incorrect, this exception is raised. @end deftp @node Caching on server side @chapter Caching on server side Both the identity provider and the resource server need to cache things. The identity provider will cache application webids, and the resource server will cache the identity provider keys, for instance. The solution is to use a file-system cache. Every response (except those that have a cache-control policy of no-store) are stored to a sub-directory of @emph{XDG_CACHE_HOME}. Each store has a 5% chance of triggering a cleanup of the cache. When a cleanup occurs, each cached response has a 5% chance of being dropped, including responses that are indicated as valid. This way, a malicious cache response that has a maliciously long validity will not stay too long in the cache. A log line will indicate which items are dropped. The @emph{(webid-oidc cache)} module exports two functions to deal with the cache. @deffn function clean-cache @var{[#percents]} Drop @var{percents}% of the cache right now. @end deffn @deffn function with-cache @var{[#http-get]} Return a function acting as @emph{http-get} from @emph{(web client)} (takes an URI as the first parameter, and an optional @var{#:headers} set, and returns 2 values, the response and its body). The cache will be read and written in the @samp{web-cache} subdirectory of the cache home. To check the time window validity, the @var{current-date} parameter is used. The back-end function, @var{http-get}, defaults to that of @emph{(web client)}. @end deffn @deffn parameter cache-home This parameters sets the cache directory. By default, it is @emph{XDG_CACHE_HOME}. @end deffn @node Content negociation @chapter Content negociation There are a number of different available syntaxes for RDF, some being simple and human readable like @emph{turtle}, and others more adapted to the JavaScript ecosystem like @emph{json-ld}. To help clients both from and outside of the JS ecosystem, the server needs to perform @dfn{content negociation}, i.e. convert from one content-type to another. @deffn {function from @code{(webid-oidc serve)}} convert @var{client-accepts} @var{server-name} @var{path} @var{content-type} @var{content} Convert the resource representation under @var{path} on @var{server-name}, which has a given @var{content-type} and @var{content}, to a content-type that the @var{client accepts}. Return 2 values: @enumerate @item the accepted content-type; @item the content in the given content-type. @end enumerate Currently, the only conversions are from and to @emph{Turtle} and @emph{N-Quads}. @end deffn @node Running an Identity Provider @chapter Running an Identity Provider This project is packaged with a barebones identity provider. It has an authorization endpoint and a token endpoint (and it serves its public keys), but it is only intended for one specific person. You can start it by invoking the @code{webid-oidc} program with the @code{issuer} command, with the following options: @table @asis @item @code{-h}, or @code{--help} prints a summary of options and exit. @item @code{-v}, or @code{--version} prints the version of the program and exits. @item @code{-n @var{URI}}, or @code{--server-name=@var{URI}} sets the global server name of the identity provider. It should have an empty path. @item @code{-k @var{FILE.jwk}}, or @code{--key-file=@var{FILE.jwk}} sets the file name where to read or generate a key for the identity provider. This file should be JSON, containing the representation of a JWK key pair. @item @code{-s @var{WEBID}}, or @code{--subject=@var{WEBID}} sets the webid of the only user of the identity provider. This is an URI, pointing to a RDF node corresponding to the user’s profile. @item @code{-w @var{PASSWORD}}, or @code{--password=@var{PASSWORD}} sets the password that the user must enter to authorize an application. @item @code{-j @var{URI}}, or @code{--jwks-uri=@var{URI}} tells the server that requests to @var{URI} should be responded with the public key used to sign the tokens. @item @code{-a @var{URI}}, or @code{--authorization-endpoint-uri=@var{URI}} tells the server that requests to @var{URI} should be treated as authorization requests. @item @code{-t @var{URI}}, or @code{--token-endpoint-uri=@var{URI}} tells the server that requests to @var{URI} should be treated as token negociation requests. @item @code{-p @var{PORT}}, or @code{--port=@var{PORT}} change the port number used by the server. By default, it is set to 8080. @item @code{-l @var{FILE.log}}, or @code{--log-file=@var{FILE.log}} let the server dump all its output to @var{FILE.log}. Since I don’t know how to deal with syslog, this is the only way to keep logs with a shepherd service. @item @code{-e @var{FILE.err}}, or @code{--error-file=@var{FILE.err}} let the server dump all its errors to @var{FILE.err}. @end table The program is sensitive to the environment variables. The most important one is @emph{LANG}, which influences how the program is internationalized to the server administrator (the pages served to the user use the user agent’s locale). This changes the long form of the options, and the language in the log files. The @emph{XDG_DATA_HOME} should point to some place where the program will store refresh tokens, under the @code{webid-oidc} directory. For a system service, you might want to define that environment to @code{/var/lib}, for instance. The @emph{XDG_CACHE_HOME} should point to a directory where to store the seed of the random number generator (under a @code{webid-oidc} directory, again). Changing the seed only happens when a program starts to require the random number generator. You can safely delete this directory, but you need to restart the program to actually change the seed. @node Running a Resource Server @chapter Running a Resource Server @menu * The authenticator:: * The full server:: * Resources stored on the server:: @end menu A Solid server is the server that manages your data. It needs to check that the proofs of possession are correct, and the possessed key is signed by the identity provider. @node The authenticator @section The authenticator In @emph{(webid-oidc resource-server)}, the following function gives a simple API for a web server: @deffn function make-authenticator @var{jti-list} @var{[#server-uri]} @var{[#current-time]} @var{[#http-get]} Create an authenticator, i.e. a function that takes a request and request body and returns the webid of the authenticated user, or @code{#f} if it is not authenticated. To prevent replay attacks, each request is signed by the client with a different unique padding value. If such a value has already been seen, then the request must fail. The authenticator expects the client to demonstrate the possession of a key that the identity provider knows. So the client creates a DPoP proof, targetted to a specific URI. In order to check that the URI is correct, the authenticator needs the public URI of the service. The JTIs are checked within a small time frame. By default, the system time will be used. Otherwise, you can customize the @code{current-time} optional keyword argument, to pass a thunk returning a time from @emph{(srfi srfi-19)}. You may want to customize the @var{http-get} optional keyword argument to pass a function to replace @code{http-get} from @emph{(http client)}. This function takes an URI and optional @code{#:headers} arguments, makes the request, and return two values: the response, and the response body. This function, in @emph{(webid-oidc resource-server)}, returns a web request handler, taking the request and request body, and returning the subject of the access token. If an error happens, it is thrown; the function always returns a valid URI. @end deffn @node The full server @section The full server @deffn {function from @emph{(webid-oidc resource-server)}} make-server @var{[#:server-uri]} @var{[#:owner]} @var{[#:authenticator]} @var{[#:current-time]} @var{[#:http-get]} Return a server handler, a function taking 2 values, a request and a request body, and returning 2 values, the response and response body. The optional @var{[#:authenticator]} argument defaults to the webid-oidc authenticator, @var{[#:current-time]} defaults to a thunk returning the system time and @var{[#:http-get]} to the web client from @emph{(web client)}. @end deffn @node Resources stored on the server @section Resources stored on the server To store and serve resources, the server has two distinct mechanisms. A @dfn{content} is a read-only possible value for a resource, indexed by etags, and a @dfn{path} is a mutable value that indicates the etag of the resource, and of the auxiliary resources (description and ACL). With this separation, it is possible to atomically delete a resource and all associated auxiliary resources, by unlinking the corresponding @emph{path}. It is also possible to mutate separately the ACL and the resource itself without writing a copy for both. The @emph{content} API is contained in the @code{(webid-oidc server resource content)} module. @deffn function with-session @var{f} Call @var{f} with 5 arguments: @itemize @item a function to get the content-type of a given etag; @item a function to list the paths contained within the resource; @item a function to load the content of a given etag; @item a function to create a new content; @item a function to remove a content from the file system. It is still possible to query it with the first 3 functions, but new sessions will not see it. @end itemize Since the contents are read-only, it is possible to cache the value of the content in memory. This is why @var{f} should run within a session with memoization. Resources only store @emph{static} content, because the membership triples for containers is considered dynamic and not included in the representation. The first 3 functions as well as the last one are called with an etag, and the function to create a content is called with the content-type, list of contained paths, and (static) content. The contents are searched in the @emph{server/content} subdirectory of @var{data-home}. @end deffn @deffn parameter data-home Defines the directory where to store persistent data. Defaults to @emph{XDG_DATA_HOME}. @end deffn The @emph{path} API is defined in @code{(webid-oidc server resource path)}. @deffn function read-path @var{path} Read the resource at @var{path}, and return 2 values: @enumerate @item the ETag of the main resource; @item an alist where keys are auxiliary resource type URIs (the type is from @code{(web uri)}), and the values are ETags of the corresponding resource. @end enumerate If the resource is not found, raise an exception with type @code{&path-not-found}, and maybe @code{&uri-slash-semantics-error} if a resource with a different ending-in-slash exists. This function is safe to call when the path is being modified, either by another thread, process or else, as the returned values will always be consistent. However, once the function returns, an updating process may have deleted the returned ETags. If this is the case, then you must call this function again to read the updated path. @end deffn @deffn function update-path @var{path} @var{f} @var{content-type} @var{contained} @var{static-content} @var{create} @var{delete} [@var{#:create-intermediate-containers?}=@code{#f}] Read @var{path}, call @var{f} with two values: the ETag and the auxiliary ETags (as returned by @var{read-path}), and update the path accordingly. If @var{path} does not exist, then the first argument is @code{#f} and the second one is the empty list. If @var{f} returns @code{#f}, then the resource is deleted. If @var{f} returns an ETag as the first returned value and an alist of auxiliary resource ETags as the second value, then the resource is updated. The last functions are from the content API. Since creating or deleting children requires updating the parent, we need them. Some operations should create the intermediate containers for a given path, this is the case for the @code{PUT} HTTP verb. For @code{POST}, the parent should exist. The @var{#:create-intermediate-containers?} switch lets you change the behavior. In any case, it is an error to delete a non-empty container. The update is atomic, meaning that at any point in time the file is fully written out. Concurrent access to the same resource is performed by locking the lock file named @var{X}/.lock, where @var{X} is the first character of the base64-url sha-256 hash of the path. @strong{The lock file is not meant to be removed} when the resource is unlocked. It should be locked with @code{flock} instead. @strong{Like other forms of lock-based synchronization, this function is not composable}. This means that you cannot call this function within @var{f}, otherwise a deadlock may ensue. If the resource is created or deleted, then the parent resource is updated as well. To avoid deadlocks with other processes, please follow the following rules: lock the path, then lock the parent path, then update the parent, then unlock the parent, and finally unlock the child path. @end deffn The Web Access Control specification defines an RDF vocabulary to check whether a given user is allowed to perform some operations. The @code{(webid-oidc server resource wac)} helps you do that. @deffn function wac-get-modes @var{server-name} @var{path} @var{user} @var{[#:http-get]} Return the list of modes that are allowed for @var{user} accessing @var{path}. The @var{server-name} URI is required to find the relevant triples in the ACL. If @var{user} is unauthenticated, pass @code{#f}. Please note that in any case, the data owner should have all rights whatsoever, bypassing WAC. Otherwise, it is possible to steal control away from the data owner. @end deffn @deffn function check-acl-can-read @var{server-name} @var{path} @var{owner} @var{user} @var{[#:http-get]} @deffnx function check-acl-can-write @var{server-name} @var{path} @var{owner} @var{user} @var{[#:http-get]} @deffnx function check-acl-can-append @var{server-name} @var{path} @var{owner} @var{user} @var{[#:http-get]} @deffnx function check-acl-can-control @var{server-name} @var{path} @var{owner} @var{user} @var{[#:http-get]} Assert that the resource at @var{path} on @var{server-name} is owned by @var{owner}, and check that @var{user} has the proper authorization. Otherwise, raise an exception of type @code{&forbidden}. @end deffn @node Running a client @chapter Running a client The job of the client is to use accounts to fetch private resources on the web. The @emph{(webid-oidc client)} defines the @code{} class. @deftp {Class} @var{client-id} @var{key-pair} @var{redirect-uri} In OIDC, a client is an application that does not hold the resources. It may in fact be a network server available on the web, or a program that you run on your machine. Being a network server or not is irrelevant. The @code{} class is designed with immutability in mind. You can create a client with the @code{make} generic method, using these keywords to initialize values: @table @code @item #:client-id to set the public client identifier (this endpoint should be available on the world-wide web), as a string representing an URI or an URI from @code{(web uri)}; @item #:key-pair to use a specific key pair. If not set, a new key pair will be generated; @item #:redirect-uri to set the redirect URI that the application controls. It may just be a page showing the authorization code, with instructions on how to paste this code into the application. It should match one of the authorized redirect URIs in the client identifier endpoint. If you want to set a state parameter for the redirection, you can do it by setting the guile parameter @code{authorization-state}. @end table @end deftp @deftypefn {Generic method} uri client-id (@var{client} @code{}) @deftypefnx {Generic method} {key pair} client-key-pair (@var{client} @code{}) @deftypefnx {Generic method} uri client-redirect-uri (@var{client} @code{}) Slot accessors for @var{client}. @end deftypefn @defvr {Parameter} client Define this parameter to set the client to use to access private data. @end defvr To access private data, you must identify yourself. The @emph{(webid-oidc client accounts)} module lets you define accounts. @deftp {Class} @var{subject} @var{issuer} @var{id-token} @var{access-token} @var{refresh-token} @var{key-pair} Encapsulate an account. @var{subject} is your webid, while @var{issuer} is a host name. @var{id-token} is the @emph{decoded} OIDC ID token, i.e. a pair of @code{(header . payload)}, because we don’t need to show it to any other party, so its authenticity needs not be demonstrated. However, @var{access-token} is an @emph{encoded} access token (into a string), because we don’t need to worry about its internals on client side. There are different ways to initialize an account. First, you can save all parameters to some form of storage, and restore it by using the associated keyword arguments at construction time: @table @code @item #:subject @item #:issuer @item #:id-token @item #:access-token @item #:refresh-token @item #:key-pair @end table If you want to make a new account, you would ask the user for an identity provider, and pass it with @code{#:issuer} as the only initialized value. The constructor will log you in, using the @code{authorization-process} and @code{anonymous-http-request} function parameters. If you want to refresh an access token, you would also set @code{#:refresh-token}. In any case, when you don’t specify a value, it’s as if you passed @code{#f}. @end deftp @defvr {Parameter} authorization-process This function is called when an explicit user authorization is required, for instance because there is no refresh token and the access token expired. The function takes an URI as argument, with an additional @code{#:issuer} keyword argument containing the issuer. In this function, you should ask the user to browse this URI so that your application gets the authorization code. @end defvr @defvr {Parameter} anonymous-http-request This function is used as a back-end for private resource access, and to query the server configuration. It defaults to @code{http-request} from @emph{(web client)}. @end defvr @deftypefn {Generic method} uri subject (@var{account} @code{}) @deftypefnx {Generic method} set-subject (@var{account} @code{}) (@var{uri} {string or URI}) @deftypefnx {Generic method} uri issuer (@var{account} @code{}) @deftypefnx {Generic method} set-issuer (@var{account} @code{}) (@var{uri} {string or URI}) @deftypefnx {Generic method} {optional decoded ID token} id-token (@var{account} @code{}) @deftypefnx {Generic method} set-id-token (@var{account} @code{}) (@var{id-token} {optional ID token}) @deftypefnx {Generic method} {optional encoded access token} access-token (@var{account} @code{}) @deftypefnx {Generic method} set-access-token (@var{account} @code{}) (@var{access-token} {optional access token}) @deftypefnx {Generic method} {optional } refresh-token (@var{account} @code{}) @deftypefnx {Generic method} set-refresh-token (@var{account} @code{}) (@var{refresh-token} {optional }) @deftypefnx {Generic method} {key pair} key-pair (@var{account} @code{}) @deftypefnx {Generic method} set-key-pair (@var{account} @code{}) (@var{key-pair} {optional key pair}) Slot accessors and functional setters for @var{account}. @end deftypefn If you intend to run a public network server as a client application, you may have multiple different users, but you should not let any user use any account. If this is the case, you can either store the accounts on the user agent storage (for instance, as a cookie), or store all of them on the server. If you choose to store the accounts on the user agent, at least use a new key pair for each of them. If you want to store the user database on the server side, be aware that no entity other than yourself will check that your user abides by any term of service, so it is possible that a single user makes a lot of accounts to annoy you and fill your hard drive with key pairs. If your application does not let random people to use it, you might want to use @emph{protected accounts}, to help you check that the users cannot impersonate each other. @deftp {Class} (@code{}) @var{username} @var{encrypted-password} This superclass of @code{} is protected by a username and password. It is constructed with the initializer keywords @code{#:username} and @code{#:encrypted-password}. @end deftp @deftypefn {Generic method} username (@var{protected-account} @code{}) @deftypefnx {Generic method} set-username (@var{protected-account} @code{}) (@var{username} ) @deftypefnx {Generic method} encrypted-password (@var{protected-account} @code{}) @deftypefnx {Generic method} set-encrypted-password (@var{protected-account} @code{}) (@var{encrypted-password} ) Slot accessors and functional setters for @var{protected-account}. @end deftypefn @deftypefn {Generic method} invalidate-access-token (@var{account} @code{}) Indicate that the access token in @var{account} cannot be used. Before using @var{account} again, you will need to refresh the access token. This function does not mutate @var{account}. @end deftypefn @deftypefn {Generic method} invalidate-refresh-token (@var{account} @code{}) Indicate that the refresh token has been revoked for @var{account}. This is usually an indication that the user don’t want your application to access her private data. This function does not mutate @var{account}. @end deftypefn @deftypefn {Generic method} refresh (@var{account} @code{}) Refresh the access token. @end deftypefn @deftp {Exception type} &authorization-code-required @var{uri} If the login process requires the user to send an authorization code, an exception of this type will be raised, with an implicit invitation for the user to browse @var{uri} and follow the instructions. The instructions will be handled by the @var{redirect-uri} in the @code{login} function. If your client is a traditional web application, the user will be redirected to this URI with an authorization code. If your client is a native application, then maybe that redirection URI should display the authorization code and invite the user to paste it in the appropriate place in the application. When an exception of this type is raised during the @code{login} function, it is continuable, meaning that the login function will resume. You need to create an exception handler for an exception of this type, look up the @var{uri}, direct the user to browse it, get the authorization code back, and @emph{return} the authorization code @emph{from the exception handler}. @end deftp @deffn function make-authorization-code-required @var{uri} @deffnx function authorization-code-required? @var{error} @deffnx function authorization-code-required-uri @var{error} Constructor, predicate, and accessor for the @code{&authorization-code-required} exception type. @end deffn @deftp {Exception type} &refresh-token-expired The refresh token can be used to still perform requests on behalf of the user when perse is offline. However, if the refresh token expires while the user is offline, it is not possible to log in again, because it requires a new authorization code. So, it is not possible to recover from this error, and the refresh token is immediately discarded. @end deftp @deffn function make-refresh-token-expired @deffnx function refresh-token-expired? @var{error} Constructor and predicate for the @code{&refresh-token-expired} exception type. @end deffn @deffn function invalidate-access-token @var{account} Discard the access token for @var{account}. It is not saved in the user database yet. This is roughly equivalent to log out. @end deffn @deffn function invalidate-refresh-token @var{account} Discard the refresh token for @var{account}. You still need to save the @var{account}. @end deffn @deftp {Exception type} &token-request-failed @var{response} @var{response-body} If the token endpoint is unable to deliver an identity token and an access token, this exception is raised with the identity provider @var{response} and @var{response body}. This exception cannot be continued. @end deftp @deffn function make-token-request-failed @var{response response-body} @deffnx function token-request-failed? @var{error} @deffnx function token-request-response @var{error} @deffnx function token-request-response-body @var{error} Constructor, predicate, and accessors for the @code{&token-request-failed} exception type. @end deffn The @emph{(webid-oidc client)} module provides the most useful function for a client. @deffn function request @var{account} @var{uri} . @var{args} Perform a request on behalf of @var{account}, with the current value of the @var{client} parameter as the client, using as a backend the current value of @var{anonymous-http-request}. @end deffn Finally, to implement your application, there needs to be a public endpoint for the resource server to check that you are not impersonating another application. This endpoint can be served by any web server, but a convenience procedure is made available here: @deffn function serve-application @var{id} @var{redirect-uri} @var{[#client-name]} @var{[#client-uri]} Return a handler for web requests to serve the application manifest and the redirection to transmit the authorization code. You should set the @var{client-name} to your application name and @var{client-uri} to point to where to a presentation of your application. @end deffn @node Exceptional conditions @chapter Exceptional conditions The library will raise an exception whenever something fishy occurs. For instance, if a signature is invalid, or the expiration date has passed. When the client is responsible for an error, such as presenting an invalid access token, a compound exception is raised. It is sometimes useful for the user to understand what happened, because it could indicate a problem in a part of the web they need to change. For instance, if the access token cannot be decoded because the identity provider is down, then maybe informing the user of that fact is useful. However, presenting too much information is a security risk. For instance, if the system administrator also runs a private server on the same machine, and a malicious client tries to pretend that this private server is an identity provider, then the public server will try to query the private server. If an error happens and the public server displays some information to the client, then a part of the information comes from the private server. Thus, a balance needs to be found so that not too much is revealed. The module @emph{(webid-oidc errors)} defines an exception type that indicates a message that is safe to display to the user. @deftp {Exception type} &message-for-the-user @var{message} Indicate that @var{message} can be safely displayed to the user. It is an XHTML paragraph (or equivalent), presented as SXML. @end deftp @deffn function make-message-for-the-user @var{message} @deffnx user-message @var{exception} Constructor and accessor for the @code{&message-for-the-user} exception type. @end deffn @node GNU Free Documentation License @appendix GNU Free Documentation License @include fdl.texi @node Index @unnumbered Index @printindex cp @bye