51Degrees Pipeline for PHP

fiftyone\pipeline\did\DidClient Class Reference

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.

  1. 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()}).
  2. Verifies a 51Did's signature offline against that key ({
    See also
    DidClient::verifySignature()}).
  3. Verifies a 51Did's signature through the cloud's verify endpoint ({
    See also
    DidClient::verify()}).
  4. Redeems a sealed creator context result on the server, with the licence key, and returns a typed {
    See also
    RedeemResult} ({
    DidClient::redeem()}).
    Creating a 51Did is not part of this client. Creation is the cloud json endpoint, 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$resourceKeyThe page's resource key, public by nature.
string | null$licenceKeyThe account's licence key, server side only, needed to redeem where the account holds licence keys.
string | null$endpointThe API base including /api/v4/. When null the FOD_CLOUD_API_URL environment 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$transportAn 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_contents with a stream context.
Parameters
callable | null$clockA clock for tests returning the current Unix timestamp. The default is time().
Exceptions
InvalidArgumentExceptionwhen 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
CloudExceptionwhen the key endpoint answers other than 200.
RuntimeExceptionwhen 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
CloudExceptionwhen the key endpoint answers other than 200.
RuntimeExceptionwhen 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$fodIdThe identifier the server knows independently, as a {
See also
FodId} or a string in either alphabet.
Parameters
string$resultThe sealed result exactly as the verify endpoint returned it to the browser.
string$challengeThe 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
InvalidArgumentExceptionwhen 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.
NotSupportedExceptionwhen the host does not offer the creator context (404).
CloudExceptionfor any other status.
RuntimeExceptionwhen 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, 51did and owid, so the request works with hosts that read either one. Hosts that recognise both prefer 51did and keep owid as a compatibility alias.
Returns
bool True for { "valid": true }, false for { "valid": false }.
Exceptions
InvalidArgumentExceptionwhen 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.
CloudExceptionfor any other status.
RuntimeExceptionwhen 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.

  1. The envelope version must be 3.
  2. 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.
  3. 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.
  4. 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.
  5. 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
    CloudExceptionwhen the key endpoint answers other than 200.
    RuntimeExceptionwhen the cloud cannot be reached.
    SwanCommunity\Owid\OwidException when a published key is not a valid public key.

◆ 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
CloudExceptionwhen the key endpoint answers other than 200.
RuntimeExceptionwhen 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.