Detailed Description
Client for every manipulation of a 51Did a server needs against the 51Degrees cloud, so server code never hand-writes HTTP or key handling.
- Fetches the signing public keys, holds them, and picks the key in force when a given 51Did was created ({
- See also
- DidClient::publicKeys()} and {
- DidClient::publicKeyFor()}).
- Verifies a 51Did's signature offline against that key ({
- See also
- DidClient::verifySignature()}).
- Verifies a 51Did's signature through the cloud's verify endpoint ({
- See also
- DidClient::verify()}).
- Redeems a sealed creator context result on the server, with the licence key, and returns a typed {
- See also
- RedeemResult} ({
- DidClient::redeem()}).
jsonendpoint, and a page creates from the browser because the identifier describes the browser's own connection. The verify-context and verify-full endpoints are browser calls for the same reason.
Credentials never appear in a URL. The key list and verify calls are GETs with the resource key in the route (id/key/{resource} and id/verify/{resource}). Redeem is a POST to id/redeem whose form body carries the resource key beside the licence key, because a query string is written to access logs and the cloud's POST route takes the resource key from the form rather than the path.
The cloud publishes a key only once its period has started or is about to, and each entry carries endsAt, the next key's start. The client verifies offline until a 51Did is dated within the boundary tolerance of the newest entry's end, or of its start where the cloud sent no end, and then fetches the entries from the newest start held, at most once a minute. A key may be replaced before its endsAt, and the client picks up the replacement on the first signature that fails with the keys held, or at the next daily fetch of the whole list.
The key list cache is per instance. PHP runs one request per process state, so under a long-running application server the cache lives for the life of the instance, while under the built-in php -S server each request starts afresh and fetches the keys again.
Public Member Functions | |
| __construct (string $resourceKey, ?string $licenceKey=null, ?string $endpoint=null, ?callable $transport=null, ?callable $clock=null) | |
| getEndpoint () | |
| The API base, ending in one slash. | |
| getResourceKey () | |
| The resource key the client was built with. | |
| hasLicenceKey () | |
| Whether the client was given a licence key. | |
| publicKeys () | |
| The signing public keys held, oldest start first, fetched on first use and then answered from the cache. | |
| publicKeyFor (FodId $fodId) | |
| The key in force when the identifier was created, being the entry whose start is latest on or before the identifier's date, or null when there is no such entry or that entry had ended by then. | |
| verifySignature (FodId $fodId) | |
| Verifies the identifier's signature offline against the published keys, mirroring the check the cloud's verify endpoint makes. | |
| verifySignatureDetailed (FodId $fodId) | |
| The same check as {. | |
| verify (FodId|string $fodId) | |
| Verifies the identifier's signature through the cloud's verify endpoint, which needs no licence key and counts as one use. | |
| redeem (FodId|string $fodId, string $result, string $challenge) | |
| Redeems a sealed creator context result against the identifier, on the server, with the licence key. | |
Data Fields | |
| const | DEFAULT_ENDPOINT = 'https://cloud.51degrees.com/api/v4/' |
| The cloud API base used when no endpoint is given or set. | |
| const | ENDPOINT_VARIABLE = 'FOD_CLOUD_API_URL' |
| The environment variable read for the API base when the constructor argument is absent, the same one the cloud request engine honours. | |
| const | PACKAGE_NAME = '51degrees/fiftyone.pipeline.did' |
| The Composer package name, sent in the User-Agent. | |
| const | KEY_LIST_MAX_AGE_SECONDS = 24 * 60 * 60 |
| How old the key list may be before the whole list is fetched again. | |
Constructor & Destructor Documentation
◆ __construct()
| fiftyone\pipeline\did\DidClient::__construct | ( | string | $resourceKey, |
| ?string | $licenceKey = null, | ||
| ?string | $endpoint = null, | ||
| ?callable | $transport = null, | ||
| ?callable | $clock = null ) |
- Parameters
-
string $resourceKey The page's resource key, public by nature. string | null $licenceKey The account's licence key, server side only, needed to redeem where the account holds licence keys. string | null $endpoint The API base including /api/v4/. When null theFOD_CLOUD_API_URLenvironment variable is read, then {
- See also
- DidClient::DEFAULT_ENDPOINT}. A value with or without a trailing slash is normalised to end in exactly one.
- Parameters
-
callable | null $transport An HTTP transport for tests, called as $transport(string $method, string $url, string[] $headers, string $body): array{status: int, body: string}and throwing a {
- See also
- RuntimeException} on a transport failure. The default uses
file_get_contentswith a stream context.
- Parameters
-
callable | null $clock A clock for tests returning the current Unix timestamp. The default is time().
- Exceptions
-
InvalidArgumentException when the resource key is empty.
Member Function Documentation
◆ hasLicenceKey()
| fiftyone\pipeline\did\DidClient::hasLicenceKey | ( | ) |
Whether the client was given a licence key.
A licence key is needed only by {
- See also
- DidClient::redeem()}, so a client without one still reads keys and verifies signatures.
◆ publicKeyFor()
| fiftyone\pipeline\did\DidClient::publicKeyFor | ( | FodId | $fodId | ) |
The key in force when the identifier was created, being the entry whose start is latest on or before the identifier's date, or null when there is no such entry or that entry had ended by then.
The whole list is fetched again before answering when it is more than a day old. The entries from the newest start held are fetched first, at most once a minute, when the date is within the boundary tolerance of the end of the keys held, being the newest entry's end or, where the cloud sent none, its start. Otherwise the answer comes from the keys held.
- Exceptions
-
CloudException when the key endpoint answers other than 200. RuntimeException when the cloud cannot be reached.
◆ publicKeys()
| fiftyone\pipeline\did\DidClient::publicKeys | ( | ) |
The signing public keys held, oldest start first, fetched on first use and then answered from the cache.
The cloud publishes a key only once its period has started or is about to, so the newest entry is normally the key in force. Each later fetch is merged in without dropping older entries, because 51Dids made long ago verify against them.
- Returns
- PublicKey[]
- Exceptions
-
CloudException when the key endpoint answers other than 200. RuntimeException when the cloud cannot be reached, the answer is not a key list, or any entry in the list is malformed.
◆ redeem()
| fiftyone\pipeline\did\DidClient::redeem | ( | FodId|string | $fodId, |
| string | $result, | ||
| string | $challenge ) |
Redeems a sealed creator context result against the identifier, on the server, with the licence key.
Sends POST {endpoint}id/redeem with a form body of resource, 51did, result, challenge and license (omitted when the client holds no licence key). Counts as one use, the second of the two a browser-based context check costs.
- Parameters
-
FodId | string $fodId The identifier the server knows independently, as a {
- See also
- FodId} or a string in either alphabet.
- Parameters
-
string $result The sealed result exactly as the verify endpoint returned it to the browser. string $challenge The single-use challenge given to the verify endpoint, or an empty string where none was.
- Returns
- RedeemResult For a 200, and for a 503 where the context is {
- See also
- ContextOutcome::Unconfirmed} and the caller may retry.
- Exceptions
-
InvalidArgumentException when a string value is far longer than any identifier or is not a 51Did, either of which is refused before the request with the message naming which, or when the cloud answered 400, carrying the cloud's message. NotSupportedException when the host does not offer the creator context (404). CloudException for any other status. RuntimeException when the cloud cannot be reached.
◆ verify()
| fiftyone\pipeline\did\DidClient::verify | ( | FodId|string | $fodId | ) |
Verifies the identifier's signature through the cloud's verify endpoint, which needs no licence key and counts as one use.
A string is read first with {
- See also
- FodId::tryFromBase64()} and refused when it is not a 51Did, so a malformed value costs no use, and is otherwise sent as given, in either base64 alphabet. A {
-
FodId} is sent in the URL-safe form. The identifier goes under both parameter names,
51didandowid, so the request works with hosts that read either one. Hosts that recognise both prefer51didand keepowidas a compatibility alias.
- Returns
- bool True for
{ "valid": true }, false for{ "valid": false }.
- Exceptions
-
InvalidArgumentException when a string value is far longer than any identifier or is not a 51Did, either of which is refused before the request with the message naming which, or when the cloud answered 400, carrying the cloud's message. CloudException for any other status. RuntimeException when the cloud cannot be reached.
◆ verifySignature()
| fiftyone\pipeline\did\DidClient::verifySignature | ( | FodId | $fodId | ) |
Verifies the identifier's signature offline against the published keys, mirroring the check the cloud's verify endpoint makes.
- The envelope version must be 3.
- The payload must be at least the base length for its type, being the 5 header bytes plus a 32 byte match key, or 16 for a Random identifier. Anything beyond the base is a creator context section and is accepted, since the signature covers the whole payload.
- The candidate keys are the entry in force at the identifier's date, plus the neighbouring entry either side of a nearby key boundary where those differ. They are tried in that order and the first that verifies answers true. Every earlier key is never tried, because one leaked key from any past period could then sign identifiers dated today.
- When none verifies, this call fetched nothing, and a key held starts on or before the date, the entries from the newest such key's start are fetched, at most once a minute, and the candidates tried once more, because that key may have been replaced before its scheduled end.
- No candidate, meaning no key held was in force within the boundary tolerance of the date, answers false. {
- See also
- DidClient::verifySignatureDetailed()} says which of the five cases it was, and {
- DidClient::publicKeyFor()} returning null says the same for this one case.
- Exceptions
-
CloudException when the key endpoint answers other than 200. RuntimeException when the cloud cannot be reached.
◆ verifySignatureDetailed()
| fiftyone\pipeline\did\DidClient::verifySignatureDetailed | ( | FodId | $fodId | ) |
The same check as {.
- See also
- DidClient::verifySignature()}, answering which of the five things happened rather than only whether the identifier is genuine.
Only {
- See also
- SignatureCheck::Verified} says the signature was examined against a key and matched, and only {
- SignatureCheck::Invalid} says it was examined and did not. The other three say the check never happened, because the envelope version is not one this package issues, or the payload is too short to hold a complete identifier, or the published schedule covers no key for the identifier's date. A caller that treats those three as forged reports its own outage as an attack, which is why they are told apart here.
The outcome names are the same in every 51Did package, so they can be logged or carried between services.
- Exceptions
-
CloudException when the key endpoint answers other than 200. RuntimeException when the cloud cannot be reached.
SwanCommunity\Owid\OwidException when a published key is not a valid public key.
Field Documentation
◆ KEY_LIST_MAX_AGE_SECONDS
| const fiftyone\pipeline\did\DidClient::KEY_LIST_MAX_AGE_SECONDS = 24 * 60 * 60 |
How old the key list may be before the whole list is fetched again.
This bounds how long a key replaced before its scheduled end can still be trusted offline.
