Service providers¶
A service provider is an application that signs its users in through the identity
provider of the tenant. It exists here because the deployment declares it, keyed by
its entity ID as a client of OpenID Connect is keyed by its client_id. No request registers one and no page
edits one: a peer that may obtain an assertion about anybody is a decision of whoever runs the deployment.
Declaring one¶
beffroi:
saml:
identity_provider: true
service_providers:
'https://sp.example.com/saml/metadata':
metadata: 'https://sp.example.com/saml/metadata'
attributes:
'urn:oid:0.9.2342.19200300.100.1.1':
claim: preferred_username
friendly_name: uid
'urn:oid:0.9.2342.19200300.100.1.3':
claim: email
friendly_name: mail
'urn:oid:2.16.840.1.113730.3.1.241':
claim: name
friendly_name: displayName
| Option | Default | What it decides |
|---|---|---|
| The key | The entity ID of the service provider, compared exactly with the one its metadata carry: at most 1024 characters, no whitespace | |
metadata |
required | Where its metadata are read from: an https URL, or the absolute path of a file of the deployment. Nothing else, a plain http URL included |
metadata_signers |
none | The certificates the metadata are signed by, each the base64 of its DER as an X509Certificate carries it, sixteen at most. Named, the metadata are verified under them; left empty, they are trusted by the channel they come through |
attributes |
none | The claims sent to this service provider, keyed by the name of the attribute each one is sent as, 256 at most |
encrypt_assertions |
false |
Its assertions are encrypted for a key of its metadata |
signs_authn_requests |
true |
It signs its authentication requests |
A declaration is refused while the identity provider of the tenant is not enabled, and a declaration that does not hold together fails the build of the deployment rather than its first login: when the container is compiled for every rule, and right after it, in the same build, for the claims the attributes name. A declaration that holds an environment variable, whose value only the running deployment knows, is held to the same rules at its first use.
Its metadata¶
The declaration says where the metadata are; what they say is read there, and nowhere else. They are where the assertion consumer services of the service provider are, its single logout services, and the keys its requests are verified with: the declaration names none of them, so a service provider cannot be sent an assertion anywhere its own metadata do not list.
Read before the first login. A service provider whose metadata have never been accepted is refused, so a declaration is followed by a reading:
bin/console beffroi:saml:service-provider:refresh
bin/console beffroi:saml:service-provider:refresh 'https://sp.example.com/saml/metadata'
Without an argument it reads every service provider the configuration declares. Each one is reported as registered, registered by another process meanwhile, refreshed, unchanged, refused or failed, and a refusal names the rule it broke and never a value of the document. A timed step does the same every hour, for the service providers and the upstream identity providers alike, and writes a refusal to the log as an error: the refused document comes back at every hour while the accepted one goes on serving, which is something an operator wants to be alerted on.
How they are fetched. A URL is fetched with one GET, no redirect followed, ten seconds at most, and any
status but 2xx refused. A file or a body is refused as soon as it passes one mebibyte, which no metadata
the loader accepts reaches.
What is refused, at the first reading as at every refresh, the accepted document being kept each time:
- a document whose entity ID is not the declared one, or that holds no
md:SPSSODescriptorof SAML 2.0; - a document past its
validUntil, or past thevalidUntilof its service provider role; - an assertion consumer service or a single logout service that is not
httpson a host, with no userinfo; - metadata with no key for encryption, for a declaration that asks for encrypted assertions;
- metadata with no key for signing, for a service provider whose requests need a signature;
- a document read under pinned signers that carries no
validUntil, or whosevalidUntilis earlier than the one of the document already accepted.
Endpoints on other bindings than HTTP-Redirect and HTTP-POST are dropped, an assertion consumer service on anything but HTTP-POST too, since no other binding the provider sends over can carry a response, and the other role descriptors are skipped, so that the metadata of a real deployment load.
Signed metadata never go back. An older document signed by the same key verifies as well as the newer
one, so under pinned signers a document older than the accepted one is refused, which is the rollback the
signature cannot catch by itself. Metadata trusted by their channel follow the channel instead: the next
reading replaces the accepted document whatever its validUntil, so that a document planted while somebody
else held the host gives way at the first refresh after the host is the service provider's again.
Only a person goes back on it. Forgetting the accepted document is a command run by hand, never a step of the schedule nor a request, and the next reading then registers the service provider as a first one:
An entity ID for which nothing is accepted is answered so, and the command fails, so a mistyped entity ID is never taken for a forgotten one. A declaration removed from the configuration leaves its accepted document where it is, read by nothing, until an operator forgets it.
Sealed where it sits. The document is kept as the bytes that were read and sealed under the data key of
the tenant, since an md:ContactPerson names people.
The attributes it receives¶
An assertion carries one Attribute per mapping whose claim the person holds, under the name, the name
format and the friendly name the mapping gives, and nothing else. A claim may be sent under several names.
| Key | Default | What it is |
|---|---|---|
claim |
required | The name of the claim, as OpenID Connect names it (OIDC Core §5.1). The claims are the ones the provider serves to OpenID Connect, so a claim a deployment adds can be mapped the day its class is wired |
name_format |
uri |
The name format of the attribute (SAML Core §8.2): uri, under which the name is an absolute URI, as the SAML V2.0 interoperability profiles ask; basic; or unspecified |
friendly_name |
none | A name a person reads (SAML Core §2.7.3.1), which nothing compares |
A value is the text of the claim: a string as it is, an integer in decimal, a boolean as true or false,
and a list as one value per member. A value holding a character XML 1.0 cannot carry, a control character
other than the tab and the line breaks or bytes that are not UTF-8, is not sent, and neither is a claim that
is an object, such as address. The refusal goes to the log, naming the service provider, the claim and the
rule but never the value, and the sign-on goes on without that attribute: an attribute is the person's
statement to that service provider, and one cut short or rewritten on the way would be a statement they
never made.
The signature a request needs¶
An AuthnRequest needs a signature, in its XML or over the query string of HTTP-Redirect, unless both the
service provider and its metadata say it does not sign. Whichever of the two says it signs wins:
signs_authn_requests |
AuthnRequestsSigned of its metadata |
An unsigned request |
|---|---|---|
true, the default |
anything | refused |
false |
true |
refused |
false |
false or absent |
accepted |
Nothing else decides it, neither the WantAuthnRequestsSigned the identity provider announces nor any
setting of the tenant or of the deployment. A signature a request does carry is verified whatever the rule
says, under the keys of the metadata and never under a key the message names.
signs_authn_requests: false exists for the service providers that cannot sign, Microsoft Entra
ID federating its guests among them. A logout request is another matter: it is
always signed, whatever the declaration says of the sign-on.
The encryption it asks for¶
encrypt_assertions: true has every assertion of that service provider sent as a saml:EncryptedAssertion,
never in clear. The declaration decides whether the assertions are encrypted; the metadata only decide which
key they are encrypted for, which is the first certificate for encryption they list, read again when the
response is written so that a key a refresh put in place meanwhile is the one used. A service provider that
does not ask gets its assertions in clear, whatever keys its metadata hold.
| What | Value |
|---|---|
| The content | http://www.w3.org/2009/xmlenc11#aes256-gcm, a fresh key and IV for every assertion |
| The key | http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p, whose mask is computed with SHA-1: it is what PHP wraps a key under and what SimpleSAMLphp unwraps, and the hash inside OAEP rests on no collision resistance |
| The order | The assertion is signed, then encrypted with its signature inside, then the response is signed over the ciphertext (SAML Core §6.2) |
The md:EncryptionMethod elements of the metadata are not read, so no scheme is negotiated and none is
downgraded: a service provider that cannot decrypt AES-GCM gets nothing it can read rather than an assertion
in CBC. A request of a service provider that asks for encryption while its metadata hold no key for it is
refused before anybody signs in, and nothing is sent in clear.