Latest commit

History

106 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

HTTP Message Signer (RFC 9421)

A PHP 8.1+ library for signing and verifying HTTP messages (requests or responses) per RFC 9421.

At the time of writing, this was the closest thing to a reference implementation of RFC9421 that could be found for the PHP platform and one of only a handful of implementations with the full range of support for Structured-Fields and signing algorithms specified in that document.

Supports:

  • PSR-7 HTTP message requests/responses
  • Automatically verify body digest (content-digest header) -- if present
  • Algorithm support:
    • 'RS256' (JWT)
    • 'rsa-v1_5-sha256' (RFC9421)
    • 'RS384' (JWT)
    • 'rsa-v1_5-sha384'
    • 'RS512' (JWT)
    • 'rsa-v1_5-sha512' (RFC9421)
    • 'rsa-pss-sha512' (RFC9421)
    • 'EdDSA' (JWT)
    • 'Ed25519' (openssl)
    • 'ed25519' (RFC9421)
    • 'HS256' (JWT)
    • 'hmac-sha256' (RFC9421)
    • 'HS384' (JWT)
    • 'hmac-sha384'
    • 'HS512' (JWT)
    • 'hmac-sha512'
    • 'ES256' (JWT)
    • 'ecdsa-p256-sha256' (RFC9421)
    • 'ES384' (JWT)
    • 'ecdsa-p384-sha384' (RFC9421)
    • 'ES512' (JWT)
    • 'ecdsa-p512-sha512'

Note

Please report issues. Thanks. Tested on PHP 8.4, should run fine on 8.1+

Installation

composer require arduent/http-message-signer

Notes

An instance of a PSR-7 MessageInterface is passed to the sign and verify functions. This can be a RequestInterface or a ResponseInterface. Typically, this will be a RequestInterface. If your web framework does not supply a pre-populated PSR7-compatible request interface, you can quickly generate one using

useGuzzleHttp\Psr7\ServerRequest;
$request = ServerRequest::fromGlobals();

This would typically be used to verify a message.

If your project uses URL rewriting (such as Apache's 'mod_rewrite'), you may have difficulties verifying some request parameters using a PSR7 request generated using ServerRequest::fromGlobals() as shown here. In that case, you might wish instead to generate a minimal PSR7 Request Message which is populated from the original request URI and which is not affected by URL re-writing:

useGuzzleHttp\Psr7\Request;
// Generate PSR7 request from current HTTP request, which is NOT// affected by the use of Apache mod-rewrite or equivalent.functioncreateRequest(string$baseurl)
{
/** * $baseurl for your site e.g. 'https://example.com' */if ($_SERVER['REQUEST_METHOD'] == 'POST') {
$input = file_get_contents('php://input');
}
$headers = [];
if (isset($_SERVER['CONTENT_TYPE'])) {
$headers['content-type'] = $_SERVER['CONTENT_TYPE'];
}
if (isset($_SERVER['CONTENT_LENGTH'])) {
$headers['content-length'] = $_SERVER['CONTENT_LENGTH'];
}
foreach ($_SERVERas$k => $v) {
if (str_starts_with($k, 'HTTP_')) {
$field = str_replace('_', '-', strtolower(substr($k, 5)));
$headers[$field] = $v;
}
}
returnnewRequest(
$_SERVER['REQUEST_METHOD'],
$baseurl . $_SERVER['REQUEST_URI']),
$headers,
$input ?? null
);
}

To sign a message, install the composer package guzzlehttp/psr7 (or any other PSR7 compliant interface) and create an instance of Request or Response as appropriate.

Usage

useHttpSignature\HttpMessageSigner;
useHttpSignature\UnProcessableSignatureException;
useGuzzleHttp\Psr7\Request;
$request = newRequest(
'GET',
'https://api.example.com/resource?bat&baz=3',
[
'Host' => 'api.example.com',
'Date' => gmdate('D, d M Y H:i:s T'),
...additional headers
]
);
$signer = (newHttpMessageSigner())
->setPrivateKey($privateKey) // only needed for signing
->setPublicKey($publicKey) // only needed for verifying
->setKeyId('https://example.com/dave#rsaKey') // required when signing
->setAlgorithm('rsa-v1_5-sha256') // typically required when signing
->setCreated(time()) // recommended
->setExpires(time() + 300) // optional, enforced
->setNonce('xJJ9;ro.3*kidney`') // optional one-time token, uniqueness SHOULD be checked/enforced by the calling application
->setTag('fediverse') // optional app profile name
->setSignatureId('sig1') // optional, default is sig1
try { $request = $signer->signRequest('("@method" "@path" "host" "date")', $request);
}
catch (UnProcessableSignatureException $exception) {
$whatHappened = $exception->getMessage();
}
try { $isValid = $signer->verifyRequest($request);
} catch (UnProcessableSignatureException$exception) {
$isValid = false;
$whatHappened = $exception->getMessage();
}

See full examples in /tests.

Structured Fields

RFC9421 makes heavy use of HTTP Structured Fields (RFC8941/RFC9651).

The signRequest() method takes a structured InnerList of components to sign. These may be headers or derived fields. The string will look something like the following (where ... represents additional components):

'("header1" "header2" "@method" ...)'

and may include modifier parameters. These are represented as

'("@query-param";name="foo" "header2";sf "header3" ...)'

Field names beginning with '@' are components derived from the HTTP request but may not be represented in the headers. Please review RFC9421 for precise definitions.

Using the 'sf' parameter on a component will treat a signature component as a Structured Field when normalising the string.

However, parsing arbitrary Structured Fields by adding the 'sf' parameter is likely to fail unless you know what type it is. A built-in table contains the type definition for a number of known stuctured header types. This list is probably incomplete. A method addStructuredFieldTypes() is available to add the type information so it can be successfully parsed. This takes an array with key of the lowercase header name and a value; which is one of 'list', 'innerlist', 'parameters, 'dictionary', 'item'. If the header name is in the list and the 'sf' modifier is used, the header will be parsed as the Structured Field type indicated.

If a Structured Field is declared as type 'dictionary'; it is suitable for use with the RFC9421 key parameter. Using this parameter will fail if the Structured Field type is unknown or has not been registered.

The signRequest() and verifyRequest() methods both use an instance of MessageInterface. In nearly all cases, this will be the RequestInterface. However, when signing responses, the default will be the ResponseInterface, and if components are required from the RequestInterface, the :req parameter must be added to the field definition.

To sign or verify an HTTP Response, use a ResponseInterface as the provided $interface, and provide the RequestInterface in $originalRequest. This is optional but will allow the req modifier to work correctly when signing Responses.

Known issues

Currently not implemented is the special handling of the cookie and set-cookie headers when using the sf modifier. For further information please see https://httpwg.org/http-extensions/draft-ietf-httpbis-retrofit.html and https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis-20 (or later). It is planned to implement this once RFC6265bis is finalised as a new RFC.

Currently, PEM keys are supported as per the RFC examples. JWT/JWK keys are not yet fully supported. A number of encryption libraries are being used to obtain coverage of the entire suite of supported algorithms under PHP, and their key format support varies dramatically.

JWT/JWK algorithm identifiers are permitted for any of the supported algorithms. For instance, 'RS256' and 'rsa-v1_5-sha256' are inter-changeable, depending on your application requirements.

Pull requests welcome.

License

BSD 3-Clause

About

RFC 9421 signer and verifier class in PHP. BSD licensed

Resources

Stars

1 star

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Latest commit

History

106 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

HTTP Message Signer (RFC 9421)

A PHP 8.1+ library for signing and verifying HTTP messages (requests or responses) per RFC 9421.

At the time of writing, this was the closest thing to a reference implementation of RFC9421 that could be found for the PHP platform and one of only a handful of implementations with the full range of support for Structured-Fields and signing algorithms specified in that document.

Supports:

  • PSR-7 HTTP message requests/responses
  • Automatically verify body digest (content-digest header) -- if present
  • Algorithm support:
    • 'RS256' (JWT)
    • 'rsa-v1_5-sha256' (RFC9421)
    • 'RS384' (JWT)
    • 'rsa-v1_5-sha384'
    • 'RS512' (JWT)
    • 'rsa-v1_5-sha512' (RFC9421)
    • 'rsa-pss-sha512' (RFC9421)
    • 'EdDSA' (JWT)
    • 'Ed25519' (openssl)
    • 'ed25519' (RFC9421)
    • 'HS256' (JWT)
    • 'hmac-sha256' (RFC9421)
    • 'HS384' (JWT)
    • 'hmac-sha384'
    • 'HS512' (JWT)
    • 'hmac-sha512'
    • 'ES256' (JWT)
    • 'ecdsa-p256-sha256' (RFC9421)
    • 'ES384' (JWT)
    • 'ecdsa-p384-sha384' (RFC9421)
    • 'ES512' (JWT)
    • 'ecdsa-p512-sha512'

Note

Please report issues. Thanks. Tested on PHP 8.4, should run fine on 8.1+

Installation

composer require arduent/http-message-signer

Notes

An instance of a PSR-7 MessageInterface is passed to the sign and verify functions. This can be a RequestInterface or a ResponseInterface. Typically, this will be a RequestInterface. If your web framework does not supply a pre-populated PSR7-compatible request interface, you can quickly generate one using

useGuzzleHttp\Psr7\ServerRequest;
$request = ServerRequest::fromGlobals();

This would typically be used to verify a message.

If your project uses URL rewriting (such as Apache's 'mod_rewrite'), you may have difficulties verifying some request parameters using a PSR7 request generated using ServerRequest::fromGlobals() as shown here. In that case, you might wish instead to generate a minimal PSR7 Request Message which is populated from the original request URI and which is not affected by URL re-writing:

useGuzzleHttp\Psr7\Request;
// Generate PSR7 request from current HTTP request, which is NOT// affected by the use of Apache mod-rewrite or equivalent.functioncreateRequest(string$baseurl)
{
/** * $baseurl for your site e.g. 'https://example.com' */if ($_SERVER['REQUEST_METHOD'] == 'POST') {
$input = file_get_contents('php://input');
}
$headers = [];
if (isset($_SERVER['CONTENT_TYPE'])) {
$headers['content-type'] = $_SERVER['CONTENT_TYPE'];
}
if (isset($_SERVER['CONTENT_LENGTH'])) {
$headers['content-length'] = $_SERVER['CONTENT_LENGTH'];
}
foreach ($_SERVERas$k => $v) {
if (str_starts_with($k, 'HTTP_')) {
$field = str_replace('_', '-', strtolower(substr($k, 5)));
$headers[$field] = $v;
}
}
returnnewRequest(
$_SERVER['REQUEST_METHOD'],
$baseurl . $_SERVER['REQUEST_URI']),
$headers,
$input ?? null
);
}

To sign a message, install the composer package guzzlehttp/psr7 (or any other PSR7 compliant interface) and create an instance of Request or Response as appropriate.

Usage

useHttpSignature\HttpMessageSigner;
useHttpSignature\UnProcessableSignatureException;
useGuzzleHttp\Psr7\Request;
$request = newRequest(
'GET',
'https://api.example.com/resource?bat&baz=3',
[
'Host' => 'api.example.com',
'Date' => gmdate('D, d M Y H:i:s T'),
...additional headers
]
);
$signer = (newHttpMessageSigner())
->setPrivateKey($privateKey) // only needed for signing
->setPublicKey($publicKey) // only needed for verifying
->setKeyId('https://example.com/dave#rsaKey') // required when signing
->setAlgorithm('rsa-v1_5-sha256') // typically required when signing
->setCreated(time()) // recommended
->setExpires(time() + 300) // optional, enforced
->setNonce('xJJ9;ro.3*kidney`') // optional one-time token, uniqueness SHOULD be checked/enforced by the calling application
->setTag('fediverse') // optional app profile name
->setSignatureId('sig1') // optional, default is sig1
try { $request = $signer->signRequest('("@method" "@path" "host" "date")', $request);
}
catch (UnProcessableSignatureException $exception) {
$whatHappened = $exception->getMessage();
}
try { $isValid = $signer->verifyRequest($request);
} catch (UnProcessableSignatureException$exception) {
$isValid = false;
$whatHappened = $exception->getMessage();
}

See full examples in /tests.

Structured Fields

RFC9421 makes heavy use of HTTP Structured Fields (RFC8941/RFC9651).

The signRequest() method takes a structured InnerList of components to sign. These may be headers or derived fields. The string will look something like the following (where ... represents additional components):

'("header1" "header2" "@method" ...)'

and may include modifier parameters. These are represented as

'("@query-param";name="foo" "header2";sf "header3" ...)'

Field names beginning with '@' are components derived from the HTTP request but may not be represented in the headers. Please review RFC9421 for precise definitions.

Using the 'sf' parameter on a component will treat a signature component as a Structured Field when normalising the string.

However, parsing arbitrary Structured Fields by adding the 'sf' parameter is likely to fail unless you know what type it is. A built-in table contains the type definition for a number of known stuctured header types. This list is probably incomplete. A method addStructuredFieldTypes() is available to add the type information so it can be successfully parsed. This takes an array with key of the lowercase header name and a value; which is one of 'list', 'innerlist', 'parameters, 'dictionary', 'item'. If the header name is in the list and the 'sf' modifier is used, the header will be parsed as the Structured Field type indicated.

If a Structured Field is declared as type 'dictionary'; it is suitable for use with the RFC9421 key parameter. Using this parameter will fail if the Structured Field type is unknown or has not been registered.

The signRequest() and verifyRequest() methods both use an instance of MessageInterface. In nearly all cases, this will be the RequestInterface. However, when signing responses, the default will be the ResponseInterface, and if components are required from the RequestInterface, the :req parameter must be added to the field definition.

To sign or verify an HTTP Response, use a ResponseInterface as the provided $interface, and provide the RequestInterface in $originalRequest. This is optional but will allow the req modifier to work correctly when signing Responses.

Known issues

Currently not implemented is the special handling of the cookie and set-cookie headers when using the sf modifier. For further information please see https://httpwg.org/http-extensions/draft-ietf-httpbis-retrofit.html and https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis-20 (or later). It is planned to implement this once RFC6265bis is finalised as a new RFC.

Currently, PEM keys are supported as per the RFC examples. JWT/JWK keys are not yet fully supported. A number of encryption libraries are being used to obtain coverage of the entire suite of supported algorithms under PHP, and their key format support varies dramatically.

JWT/JWK algorithm identifiers are permitted for any of the supported algorithms. For instance, 'RS256' and 'rsa-v1_5-sha256' are inter-changeable, depending on your application requirements.

Pull requests welcome.

License

BSD 3-Clause

About

RFC 9421 signer and verifier class in PHP. BSD licensed

Resources

Stars

1 star

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Latest commit

History

106 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

HTTP Message Signer (RFC 9421)

A PHP 8.1+ library for signing and verifying HTTP messages (requests or responses) per RFC 9421.

At the time of writing, this was the closest thing to a reference implementation of RFC9421 that could be found for the PHP platform and one of only a handful of implementations with the full range of support for Structured-Fields and signing algorithms specified in that document.

Supports:

  • PSR-7 HTTP message requests/responses
  • Automatically verify body digest (content-digest header) -- if present
  • Algorithm support:
    • 'RS256' (JWT)
    • 'rsa-v1_5-sha256' (RFC9421)
    • 'RS384' (JWT)
    • 'rsa-v1_5-sha384'
    • 'RS512' (JWT)
    • 'rsa-v1_5-sha512' (RFC9421)
    • 'rsa-pss-sha512' (RFC9421)
    • 'EdDSA' (JWT)
    • 'Ed25519' (openssl)
    • 'ed25519' (RFC9421)
    • 'HS256' (JWT)
    • 'hmac-sha256' (RFC9421)
    • 'HS384' (JWT)
    • 'hmac-sha384'
    • 'HS512' (JWT)
    • 'hmac-sha512'
    • 'ES256' (JWT)
    • 'ecdsa-p256-sha256' (RFC9421)
    • 'ES384' (JWT)
    • 'ecdsa-p384-sha384' (RFC9421)
    • 'ES512' (JWT)
    • 'ecdsa-p512-sha512'

Note

Please report issues. Thanks. Tested on PHP 8.4, should run fine on 8.1+

Installation

composer require arduent/http-message-signer

Notes

An instance of a PSR-7 MessageInterface is passed to the sign and verify functions. This can be a RequestInterface or a ResponseInterface. Typically, this will be a RequestInterface. If your web framework does not supply a pre-populated PSR7-compatible request interface, you can quickly generate one using

useGuzzleHttp\Psr7\ServerRequest;
$request = ServerRequest::fromGlobals();

This would typically be used to verify a message.

If your project uses URL rewriting (such as Apache's 'mod_rewrite'), you may have difficulties verifying some request parameters using a PSR7 request generated using ServerRequest::fromGlobals() as shown here. In that case, you might wish instead to generate a minimal PSR7 Request Message which is populated from the original request URI and which is not affected by URL re-writing:

useGuzzleHttp\Psr7\Request;
// Generate PSR7 request from current HTTP request, which is NOT// affected by the use of Apache mod-rewrite or equivalent.functioncreateRequest(string$baseurl)
{
/** * $baseurl for your site e.g. 'https://example.com' */if ($_SERVER['REQUEST_METHOD'] == 'POST') {
$input = file_get_contents('php://input');
}
$headers = [];
if (isset($_SERVER['CONTENT_TYPE'])) {
$headers['content-type'] = $_SERVER['CONTENT_TYPE'];
}
if (isset($_SERVER['CONTENT_LENGTH'])) {
$headers['content-length'] = $_SERVER['CONTENT_LENGTH'];
}
foreach ($_SERVERas$k => $v) {
if (str_starts_with($k, 'HTTP_')) {
$field = str_replace('_', '-', strtolower(substr($k, 5)));
$headers[$field] = $v;
}
}
returnnewRequest(
$_SERVER['REQUEST_METHOD'],
$baseurl . $_SERVER['REQUEST_URI']),
$headers,
$input ?? null
);
}

To sign a message, install the composer package guzzlehttp/psr7 (or any other PSR7 compliant interface) and create an instance of Request or Response as appropriate.

Usage

useHttpSignature\HttpMessageSigner;
useHttpSignature\UnProcessableSignatureException;
useGuzzleHttp\Psr7\Request;
$request = newRequest(
'GET',
'https://api.example.com/resource?bat&baz=3',
[
'Host' => 'api.example.com',
'Date' => gmdate('D, d M Y H:i:s T'),
...additional headers
]
);
$signer = (newHttpMessageSigner())
->setPrivateKey($privateKey) // only needed for signing
->setPublicKey($publicKey) // only needed for verifying
->setKeyId('https://example.com/dave#rsaKey') // required when signing
->setAlgorithm('rsa-v1_5-sha256') // typically required when signing
->setCreated(time()) // recommended
->setExpires(time() + 300) // optional, enforced
->setNonce('xJJ9;ro.3*kidney`') // optional one-time token, uniqueness SHOULD be checked/enforced by the calling application
->setTag('fediverse') // optional app profile name
->setSignatureId('sig1') // optional, default is sig1
try { $request = $signer->signRequest('("@method" "@path" "host" "date")', $request);
}
catch (UnProcessableSignatureException $exception) {
$whatHappened = $exception->getMessage();
}
try { $isValid = $signer->verifyRequest($request);
} catch (UnProcessableSignatureException$exception) {
$isValid = false;
$whatHappened = $exception->getMessage();
}

See full examples in /tests.

Structured Fields

RFC9421 makes heavy use of HTTP Structured Fields (RFC8941/RFC9651).

The signRequest() method takes a structured InnerList of components to sign. These may be headers or derived fields. The string will look something like the following (where ... represents additional components):

'("header1" "header2" "@method" ...)'

and may include modifier parameters. These are represented as

'("@query-param";name="foo" "header2";sf "header3" ...)'

Field names beginning with '@' are components derived from the HTTP request but may not be represented in the headers. Please review RFC9421 for precise definitions.

Using the 'sf' parameter on a component will treat a signature component as a Structured Field when normalising the string.

However, parsing arbitrary Structured Fields by adding the 'sf' parameter is likely to fail unless you know what type it is. A built-in table contains the type definition for a number of known stuctured header types. This list is probably incomplete. A method addStructuredFieldTypes() is available to add the type information so it can be successfully parsed. This takes an array with key of the lowercase header name and a value; which is one of 'list', 'innerlist', 'parameters, 'dictionary', 'item'. If the header name is in the list and the 'sf' modifier is used, the header will be parsed as the Structured Field type indicated.

If a Structured Field is declared as type 'dictionary'; it is suitable for use with the RFC9421 key parameter. Using this parameter will fail if the Structured Field type is unknown or has not been registered.

The signRequest() and verifyRequest() methods both use an instance of MessageInterface. In nearly all cases, this will be the RequestInterface. However, when signing responses, the default will be the ResponseInterface, and if components are required from the RequestInterface, the :req parameter must be added to the field definition.

To sign or verify an HTTP Response, use a ResponseInterface as the provided $interface, and provide the RequestInterface in $originalRequest. This is optional but will allow the req modifier to work correctly when signing Responses.

Known issues

Currently not implemented is the special handling of the cookie and set-cookie headers when using the sf modifier. For further information please see https://httpwg.org/http-extensions/draft-ietf-httpbis-retrofit.html and https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis-20 (or later). It is planned to implement this once RFC6265bis is finalised as a new RFC.

Currently, PEM keys are supported as per the RFC examples. JWT/JWK keys are not yet fully supported. A number of encryption libraries are being used to obtain coverage of the entire suite of supported algorithms under PHP, and their key format support varies dramatically.

JWT/JWK algorithm identifiers are permitted for any of the supported algorithms. For instance, 'RS256' and 'rsa-v1_5-sha256' are inter-changeable, depending on your application requirements.

Pull requests welcome.

License

BSD 3-Clause

About

RFC 9421 signer and verifier class in PHP. BSD licensed

Resources

Stars

1 star

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Latest commit

History

106 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

HTTP Message Signer (RFC 9421)

A PHP 8.1+ library for signing and verifying HTTP messages (requests or responses) per RFC 9421.

At the time of writing, this was the closest thing to a reference implementation of RFC9421 that could be found for the PHP platform and one of only a handful of implementations with the full range of support for Structured-Fields and signing algorithms specified in that document.

Supports:

  • PSR-7 HTTP message requests/responses
  • Automatically verify body digest (content-digest header) -- if present
  • Algorithm support:
    • 'RS256' (JWT)
    • 'rsa-v1_5-sha256' (RFC9421)
    • 'RS384' (JWT)
    • 'rsa-v1_5-sha384'
    • 'RS512' (JWT)
    • 'rsa-v1_5-sha512' (RFC9421)
    • 'rsa-pss-sha512' (RFC9421)
    • 'EdDSA' (JWT)
    • 'Ed25519' (openssl)
    • 'ed25519' (RFC9421)
    • 'HS256' (JWT)
    • 'hmac-sha256' (RFC9421)
    • 'HS384' (JWT)
    • 'hmac-sha384'
    • 'HS512' (JWT)
    • 'hmac-sha512'
    • 'ES256' (JWT)
    • 'ecdsa-p256-sha256' (RFC9421)
    • 'ES384' (JWT)
    • 'ecdsa-p384-sha384' (RFC9421)
    • 'ES512' (JWT)
    • 'ecdsa-p512-sha512'

Note

Please report issues. Thanks. Tested on PHP 8.4, should run fine on 8.1+

Installation

composer require arduent/http-message-signer

Notes

An instance of a PSR-7 MessageInterface is passed to the sign and verify functions. This can be a RequestInterface or a ResponseInterface. Typically, this will be a RequestInterface. If your web framework does not supply a pre-populated PSR7-compatible request interface, you can quickly generate one using

useGuzzleHttp\Psr7\ServerRequest;
$request = ServerRequest::fromGlobals();

This would typically be used to verify a message.

If your project uses URL rewriting (such as Apache's 'mod_rewrite'), you may have difficulties verifying some request parameters using a PSR7 request generated using ServerRequest::fromGlobals() as shown here. In that case, you might wish instead to generate a minimal PSR7 Request Message which is populated from the original request URI and which is not affected by URL re-writing:

useGuzzleHttp\Psr7\Request;
// Generate PSR7 request from current HTTP request, which is NOT// affected by the use of Apache mod-rewrite or equivalent.functioncreateRequest(string$baseurl)
{
/** * $baseurl for your site e.g. 'https://example.com' */if ($_SERVER['REQUEST_METHOD'] == 'POST') {
$input = file_get_contents('php://input');
}
$headers = [];
if (isset($_SERVER['CONTENT_TYPE'])) {
$headers['content-type'] = $_SERVER['CONTENT_TYPE'];
}
if (isset($_SERVER['CONTENT_LENGTH'])) {
$headers['content-length'] = $_SERVER['CONTENT_LENGTH'];
}
foreach ($_SERVERas$k => $v) {
if (str_starts_with($k, 'HTTP_')) {
$field = str_replace('_', '-', strtolower(substr($k, 5)));
$headers[$field] = $v;
}
}
returnnewRequest(
$_SERVER['REQUEST_METHOD'],
$baseurl . $_SERVER['REQUEST_URI']),
$headers,
$input ?? null
);
}

To sign a message, install the composer package guzzlehttp/psr7 (or any other PSR7 compliant interface) and create an instance of Request or Response as appropriate.

Usage

useHttpSignature\HttpMessageSigner;
useHttpSignature\UnProcessableSignatureException;
useGuzzleHttp\Psr7\Request;
$request = newRequest(
'GET',
'https://api.example.com/resource?bat&baz=3',
[
'Host' => 'api.example.com',
'Date' => gmdate('D, d M Y H:i:s T'),
...additional headers
]
);
$signer = (newHttpMessageSigner())
->setPrivateKey($privateKey) // only needed for signing
->setPublicKey($publicKey) // only needed for verifying
->setKeyId('https://example.com/dave#rsaKey') // required when signing
->setAlgorithm('rsa-v1_5-sha256') // typically required when signing
->setCreated(time()) // recommended
->setExpires(time() + 300) // optional, enforced
->setNonce('xJJ9;ro.3*kidney`') // optional one-time token, uniqueness SHOULD be checked/enforced by the calling application
->setTag('fediverse') // optional app profile name
->setSignatureId('sig1') // optional, default is sig1
try { $request = $signer->signRequest('("@method" "@path" "host" "date")', $request);
}
catch (UnProcessableSignatureException $exception) {
$whatHappened = $exception->getMessage();
}
try { $isValid = $signer->verifyRequest($request);
} catch (UnProcessableSignatureException$exception) {
$isValid = false;
$whatHappened = $exception->getMessage();
}

See full examples in /tests.

Structured Fields

RFC9421 makes heavy use of HTTP Structured Fields (RFC8941/RFC9651).

The signRequest() method takes a structured InnerList of components to sign. These may be headers or derived fields. The string will look something like the following (where ... represents additional components):

'("header1" "header2" "@method" ...)'

and may include modifier parameters. These are represented as

'("@query-param";name="foo" "header2";sf "header3" ...)'

Field names beginning with '@' are components derived from the HTTP request but may not be represented in the headers. Please review RFC9421 for precise definitions.

Using the 'sf' parameter on a component will treat a signature component as a Structured Field when normalising the string.

However, parsing arbitrary Structured Fields by adding the 'sf' parameter is likely to fail unless you know what type it is. A built-in table contains the type definition for a number of known stuctured header types. This list is probably incomplete. A method addStructuredFieldTypes() is available to add the type information so it can be successfully parsed. This takes an array with key of the lowercase header name and a value; which is one of 'list', 'innerlist', 'parameters, 'dictionary', 'item'. If the header name is in the list and the 'sf' modifier is used, the header will be parsed as the Structured Field type indicated.

If a Structured Field is declared as type 'dictionary'; it is suitable for use with the RFC9421 key parameter. Using this parameter will fail if the Structured Field type is unknown or has not been registered.

The signRequest() and verifyRequest() methods both use an instance of MessageInterface. In nearly all cases, this will be the RequestInterface. However, when signing responses, the default will be the ResponseInterface, and if components are required from the RequestInterface, the :req parameter must be added to the field definition.

To sign or verify an HTTP Response, use a ResponseInterface as the provided $interface, and provide the RequestInterface in $originalRequest. This is optional but will allow the req modifier to work correctly when signing Responses.

Known issues

Currently not implemented is the special handling of the cookie and set-cookie headers when using the sf modifier. For further information please see https://httpwg.org/http-extensions/draft-ietf-httpbis-retrofit.html and https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis-20 (or later). It is planned to implement this once RFC6265bis is finalised as a new RFC.

Currently, PEM keys are supported as per the RFC examples. JWT/JWK keys are not yet fully supported. A number of encryption libraries are being used to obtain coverage of the entire suite of supported algorithms under PHP, and their key format support varies dramatically.

JWT/JWK algorithm identifiers are permitted for any of the supported algorithms. For instance, 'RS256' and 'rsa-v1_5-sha256' are inter-changeable, depending on your application requirements.

Pull requests welcome.

License

BSD 3-Clause

About

RFC 9421 signer and verifier class in PHP. BSD licensed

Resources

Stars

1 star

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Latest commit

History

106 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

HTTP Message Signer (RFC 9421)

A PHP 8.1+ library for signing and verifying HTTP messages (requests or responses) per RFC 9421.

At the time of writing, this was the closest thing to a reference implementation of RFC9421 that could be found for the PHP platform and one of only a handful of implementations with the full range of support for Structured-Fields and signing algorithms specified in that document.

Supports:

  • PSR-7 HTTP message requests/responses
  • Automatically verify body digest (content-digest header) -- if present
  • Algorithm support:
    • 'RS256' (JWT)
    • 'rsa-v1_5-sha256' (RFC9421)
    • 'RS384' (JWT)
    • 'rsa-v1_5-sha384'
    • 'RS512' (JWT)
    • 'rsa-v1_5-sha512' (RFC9421)
    • 'rsa-pss-sha512' (RFC9421)
    • 'EdDSA' (JWT)
    • 'Ed25519' (openssl)
    • 'ed25519' (RFC9421)
    • 'HS256' (JWT)
    • 'hmac-sha256' (RFC9421)
    • 'HS384' (JWT)
    • 'hmac-sha384'
    • 'HS512' (JWT)
    • 'hmac-sha512'
    • 'ES256' (JWT)
    • 'ecdsa-p256-sha256' (RFC9421)
    • 'ES384' (JWT)
    • 'ecdsa-p384-sha384' (RFC9421)
    • 'ES512' (JWT)
    • 'ecdsa-p512-sha512'

Note

Please report issues. Thanks. Tested on PHP 8.4, should run fine on 8.1+

Installation

composer require arduent/http-message-signer

Notes

An instance of a PSR-7 MessageInterface is passed to the sign and verify functions. This can be a RequestInterface or a ResponseInterface. Typically, this will be a RequestInterface. If your web framework does not supply a pre-populated PSR7-compatible request interface, you can quickly generate one using

useGuzzleHttp\Psr7\ServerRequest;
$request = ServerRequest::fromGlobals();

This would typically be used to verify a message.

If your project uses URL rewriting (such as Apache's 'mod_rewrite'), you may have difficulties verifying some request parameters using a PSR7 request generated using ServerRequest::fromGlobals() as shown here. In that case, you might wish instead to generate a minimal PSR7 Request Message which is populated from the original request URI and which is not affected by URL re-writing:

useGuzzleHttp\Psr7\Request;
// Generate PSR7 request from current HTTP request, which is NOT// affected by the use of Apache mod-rewrite or equivalent.functioncreateRequest(string$baseurl)
{
/** * $baseurl for your site e.g. 'https://example.com' */if ($_SERVER['REQUEST_METHOD'] == 'POST') {
$input = file_get_contents('php://input');
}
$headers = [];
if (isset($_SERVER['CONTENT_TYPE'])) {
$headers['content-type'] = $_SERVER['CONTENT_TYPE'];
}
if (isset($_SERVER['CONTENT_LENGTH'])) {
$headers['content-length'] = $_SERVER['CONTENT_LENGTH'];
}
foreach ($_SERVERas$k => $v) {
if (str_starts_with($k, 'HTTP_')) {
$field = str_replace('_', '-', strtolower(substr($k, 5)));
$headers[$field] = $v;
}
}
returnnewRequest(
$_SERVER['REQUEST_METHOD'],
$baseurl . $_SERVER['REQUEST_URI']),
$headers,
$input ?? null
);
}

To sign a message, install the composer package guzzlehttp/psr7 (or any other PSR7 compliant interface) and create an instance of Request or Response as appropriate.

Usage

useHttpSignature\HttpMessageSigner;
useHttpSignature\UnProcessableSignatureException;
useGuzzleHttp\Psr7\Request;
$request = newRequest(
'GET',
'https://api.example.com/resource?bat&baz=3',
[
'Host' => 'api.example.com',
'Date' => gmdate('D, d M Y H:i:s T'),
...additional headers
]
);
$signer = (newHttpMessageSigner())
->setPrivateKey($privateKey) // only needed for signing
->setPublicKey($publicKey) // only needed for verifying
->setKeyId('https://example.com/dave#rsaKey') // required when signing
->setAlgorithm('rsa-v1_5-sha256') // typically required when signing
->setCreated(time()) // recommended
->setExpires(time() + 300) // optional, enforced
->setNonce('xJJ9;ro.3*kidney`') // optional one-time token, uniqueness SHOULD be checked/enforced by the calling application
->setTag('fediverse') // optional app profile name
->setSignatureId('sig1') // optional, default is sig1
try { $request = $signer->signRequest('("@method" "@path" "host" "date")', $request);
}
catch (UnProcessableSignatureException $exception) {
$whatHappened = $exception->getMessage();
}
try { $isValid = $signer->verifyRequest($request);
} catch (UnProcessableSignatureException$exception) {
$isValid = false;
$whatHappened = $exception->getMessage();
}

See full examples in /tests.

Structured Fields

RFC9421 makes heavy use of HTTP Structured Fields (RFC8941/RFC9651).

The signRequest() method takes a structured InnerList of components to sign. These may be headers or derived fields. The string will look something like the following (where ... represents additional components):

'("header1" "header2" "@method" ...)'

and may include modifier parameters. These are represented as

'("@query-param";name="foo" "header2";sf "header3" ...)'

Field names beginning with '@' are components derived from the HTTP request but may not be represented in the headers. Please review RFC9421 for precise definitions.

Using the 'sf' parameter on a component will treat a signature component as a Structured Field when normalising the string.

However, parsing arbitrary Structured Fields by adding the 'sf' parameter is likely to fail unless you know what type it is. A built-in table contains the type definition for a number of known stuctured header types. This list is probably incomplete. A method addStructuredFieldTypes() is available to add the type information so it can be successfully parsed. This takes an array with key of the lowercase header name and a value; which is one of 'list', 'innerlist', 'parameters, 'dictionary', 'item'. If the header name is in the list and the 'sf' modifier is used, the header will be parsed as the Structured Field type indicated.

If a Structured Field is declared as type 'dictionary'; it is suitable for use with the RFC9421 key parameter. Using this parameter will fail if the Structured Field type is unknown or has not been registered.

The signRequest() and verifyRequest() methods both use an instance of MessageInterface. In nearly all cases, this will be the RequestInterface. However, when signing responses, the default will be the ResponseInterface, and if components are required from the RequestInterface, the :req parameter must be added to the field definition.

To sign or verify an HTTP Response, use a ResponseInterface as the provided $interface, and provide the RequestInterface in $originalRequest. This is optional but will allow the req modifier to work correctly when signing Responses.

Known issues

Currently not implemented is the special handling of the cookie and set-cookie headers when using the sf modifier. For further information please see https://httpwg.org/http-extensions/draft-ietf-httpbis-retrofit.html and https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis-20 (or later). It is planned to implement this once RFC6265bis is finalised as a new RFC.

Currently, PEM keys are supported as per the RFC examples. JWT/JWK keys are not yet fully supported. A number of encryption libraries are being used to obtain coverage of the entire suite of supported algorithms under PHP, and their key format support varies dramatically.

JWT/JWK algorithm identifiers are permitted for any of the supported algorithms. For instance, 'RS256' and 'rsa-v1_5-sha256' are inter-changeable, depending on your application requirements.

Pull requests welcome.

License

BSD 3-Clause

About

RFC 9421 signer and verifier class in PHP. BSD licensed

Resources

Stars

1 star

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Latest commit

History

106 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

HTTP Message Signer (RFC 9421)

A PHP 8.1+ library for signing and verifying HTTP messages (requests or responses) per RFC 9421.

At the time of writing, this was the closest thing to a reference implementation of RFC9421 that could be found for the PHP platform and one of only a handful of implementations with the full range of support for Structured-Fields and signing algorithms specified in that document.

Supports:

  • PSR-7 HTTP message requests/responses
  • Automatically verify body digest (content-digest header) -- if present
  • Algorithm support:
    • 'RS256' (JWT)
    • 'rsa-v1_5-sha256' (RFC9421)
    • 'RS384' (JWT)
    • 'rsa-v1_5-sha384'
    • 'RS512' (JWT)
    • 'rsa-v1_5-sha512' (RFC9421)
    • 'rsa-pss-sha512' (RFC9421)
    • 'EdDSA' (JWT)
    • 'Ed25519' (openssl)
    • 'ed25519' (RFC9421)
    • 'HS256' (JWT)
    • 'hmac-sha256' (RFC9421)
    • 'HS384' (JWT)
    • 'hmac-sha384'
    • 'HS512' (JWT)
    • 'hmac-sha512'
    • 'ES256' (JWT)
    • 'ecdsa-p256-sha256' (RFC9421)
    • 'ES384' (JWT)
    • 'ecdsa-p384-sha384' (RFC9421)
    • 'ES512' (JWT)
    • 'ecdsa-p512-sha512'

Note

Please report issues. Thanks. Tested on PHP 8.4, should run fine on 8.1+

Installation

composer require arduent/http-message-signer

Notes

An instance of a PSR-7 MessageInterface is passed to the sign and verify functions. This can be a RequestInterface or a ResponseInterface. Typically, this will be a RequestInterface. If your web framework does not supply a pre-populated PSR7-compatible request interface, you can quickly generate one using

useGuzzleHttp\Psr7\ServerRequest;
$request = ServerRequest::fromGlobals();

This would typically be used to verify a message.

If your project uses URL rewriting (such as Apache's 'mod_rewrite'), you may have difficulties verifying some request parameters using a PSR7 request generated using ServerRequest::fromGlobals() as shown here. In that case, you might wish instead to generate a minimal PSR7 Request Message which is populated from the original request URI and which is not affected by URL re-writing:

useGuzzleHttp\Psr7\Request;
// Generate PSR7 request from current HTTP request, which is NOT// affected by the use of Apache mod-rewrite or equivalent.functioncreateRequest(string$baseurl)
{
/** * $baseurl for your site e.g. 'https://example.com' */if ($_SERVER['REQUEST_METHOD'] == 'POST') {
$input = file_get_contents('php://input');
}
$headers = [];
if (isset($_SERVER['CONTENT_TYPE'])) {
$headers['content-type'] = $_SERVER['CONTENT_TYPE'];
}
if (isset($_SERVER['CONTENT_LENGTH'])) {
$headers['content-length'] = $_SERVER['CONTENT_LENGTH'];
}
foreach ($_SERVERas$k => $v) {
if (str_starts_with($k, 'HTTP_')) {
$field = str_replace('_', '-', strtolower(substr($k, 5)));
$headers[$field] = $v;
}
}
returnnewRequest(
$_SERVER['REQUEST_METHOD'],
$baseurl . $_SERVER['REQUEST_URI']),
$headers,
$input ?? null
);
}

To sign a message, install the composer package guzzlehttp/psr7 (or any other PSR7 compliant interface) and create an instance of Request or Response as appropriate.

Usage

useHttpSignature\HttpMessageSigner;
useHttpSignature\UnProcessableSignatureException;
useGuzzleHttp\Psr7\Request;
$request = newRequest(
'GET',
'https://api.example.com/resource?bat&baz=3',
[
'Host' => 'api.example.com',
'Date' => gmdate('D, d M Y H:i:s T'),
...additional headers
]
);
$signer = (newHttpMessageSigner())
->setPrivateKey($privateKey) // only needed for signing
->setPublicKey($publicKey) // only needed for verifying
->setKeyId('https://example.com/dave#rsaKey') // required when signing
->setAlgorithm('rsa-v1_5-sha256') // typically required when signing
->setCreated(time()) // recommended
->setExpires(time() + 300) // optional, enforced
->setNonce('xJJ9;ro.3*kidney`') // optional one-time token, uniqueness SHOULD be checked/enforced by the calling application
->setTag('fediverse') // optional app profile name
->setSignatureId('sig1') // optional, default is sig1
try { $request = $signer->signRequest('("@method" "@path" "host" "date")', $request);
}
catch (UnProcessableSignatureException $exception) {
$whatHappened = $exception->getMessage();
}
try { $isValid = $signer->verifyRequest($request);
} catch (UnProcessableSignatureException$exception) {
$isValid = false;
$whatHappened = $exception->getMessage();
}

See full examples in /tests.

Structured Fields

RFC9421 makes heavy use of HTTP Structured Fields (RFC8941/RFC9651).

The signRequest() method takes a structured InnerList of components to sign. These may be headers or derived fields. The string will look something like the following (where ... represents additional components):

'("header1" "header2" "@method" ...)'

and may include modifier parameters. These are represented as

'("@query-param";name="foo" "header2";sf "header3" ...)'

Field names beginning with '@' are components derived from the HTTP request but may not be represented in the headers. Please review RFC9421 for precise definitions.

Using the 'sf' parameter on a component will treat a signature component as a Structured Field when normalising the string.

However, parsing arbitrary Structured Fields by adding the 'sf' parameter is likely to fail unless you know what type it is. A built-in table contains the type definition for a number of known stuctured header types. This list is probably incomplete. A method addStructuredFieldTypes() is available to add the type information so it can be successfully parsed. This takes an array with key of the lowercase header name and a value; which is one of 'list', 'innerlist', 'parameters, 'dictionary', 'item'. If the header name is in the list and the 'sf' modifier is used, the header will be parsed as the Structured Field type indicated.

If a Structured Field is declared as type 'dictionary'; it is suitable for use with the RFC9421 key parameter. Using this parameter will fail if the Structured Field type is unknown or has not been registered.

The signRequest() and verifyRequest() methods both use an instance of MessageInterface. In nearly all cases, this will be the RequestInterface. However, when signing responses, the default will be the ResponseInterface, and if components are required from the RequestInterface, the :req parameter must be added to the field definition.

To sign or verify an HTTP Response, use a ResponseInterface as the provided $interface, and provide the RequestInterface in $originalRequest. This is optional but will allow the req modifier to work correctly when signing Responses.

Known issues

Currently not implemented is the special handling of the cookie and set-cookie headers when using the sf modifier. For further information please see https://httpwg.org/http-extensions/draft-ietf-httpbis-retrofit.html and https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis-20 (or later). It is planned to implement this once RFC6265bis is finalised as a new RFC.

Currently, PEM keys are supported as per the RFC examples. JWT/JWK keys are not yet fully supported. A number of encryption libraries are being used to obtain coverage of the entire suite of supported algorithms under PHP, and their key format support varies dramatically.

JWT/JWK algorithm identifiers are permitted for any of the supported algorithms. For instance, 'RS256' and 'rsa-v1_5-sha256' are inter-changeable, depending on your application requirements.

Pull requests welcome.

License

BSD 3-Clause

About

RFC 9421 signer and verifier class in PHP. BSD licensed

Resources

Stars

1 star

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Latest commit

History

106 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

HTTP Message Signer (RFC 9421)

A PHP 8.1+ library for signing and verifying HTTP messages (requests or responses) per RFC 9421.

At the time of writing, this was the closest thing to a reference implementation of RFC9421 that could be found for the PHP platform and one of only a handful of implementations with the full range of support for Structured-Fields and signing algorithms specified in that document.

Supports:

  • PSR-7 HTTP message requests/responses
  • Automatically verify body digest (content-digest header) -- if present
  • Algorithm support:
    • 'RS256' (JWT)
    • 'rsa-v1_5-sha256' (RFC9421)
    • 'RS384' (JWT)
    • 'rsa-v1_5-sha384'
    • 'RS512' (JWT)
    • 'rsa-v1_5-sha512' (RFC9421)
    • 'rsa-pss-sha512' (RFC9421)
    • 'EdDSA' (JWT)
    • 'Ed25519' (openssl)
    • 'ed25519' (RFC9421)
    • 'HS256' (JWT)
    • 'hmac-sha256' (RFC9421)
    • 'HS384' (JWT)
    • 'hmac-sha384'
    • 'HS512' (JWT)
    • 'hmac-sha512'
    • 'ES256' (JWT)
    • 'ecdsa-p256-sha256' (RFC9421)
    • 'ES384' (JWT)
    • 'ecdsa-p384-sha384' (RFC9421)
    • 'ES512' (JWT)
    • 'ecdsa-p512-sha512'

Note

Please report issues. Thanks. Tested on PHP 8.4, should run fine on 8.1+

Installation

composer require arduent/http-message-signer

Notes

An instance of a PSR-7 MessageInterface is passed to the sign and verify functions. This can be a RequestInterface or a ResponseInterface. Typically, this will be a RequestInterface. If your web framework does not supply a pre-populated PSR7-compatible request interface, you can quickly generate one using

useGuzzleHttp\Psr7\ServerRequest;
$request = ServerRequest::fromGlobals();

This would typically be used to verify a message.

If your project uses URL rewriting (such as Apache's 'mod_rewrite'), you may have difficulties verifying some request parameters using a PSR7 request generated using ServerRequest::fromGlobals() as shown here. In that case, you might wish instead to generate a minimal PSR7 Request Message which is populated from the original request URI and which is not affected by URL re-writing:

useGuzzleHttp\Psr7\Request;
// Generate PSR7 request from current HTTP request, which is NOT// affected by the use of Apache mod-rewrite or equivalent.functioncreateRequest(string$baseurl)
{
/** * $baseurl for your site e.g. 'https://example.com' */if ($_SERVER['REQUEST_METHOD'] == 'POST') {
$input = file_get_contents('php://input');
}
$headers = [];
if (isset($_SERVER['CONTENT_TYPE'])) {
$headers['content-type'] = $_SERVER['CONTENT_TYPE'];
}
if (isset($_SERVER['CONTENT_LENGTH'])) {
$headers['content-length'] = $_SERVER['CONTENT_LENGTH'];
}
foreach ($_SERVERas$k => $v) {
if (str_starts_with($k, 'HTTP_')) {
$field = str_replace('_', '-', strtolower(substr($k, 5)));
$headers[$field] = $v;
}
}
returnnewRequest(
$_SERVER['REQUEST_METHOD'],
$baseurl . $_SERVER['REQUEST_URI']),
$headers,
$input ?? null
);
}

To sign a message, install the composer package guzzlehttp/psr7 (or any other PSR7 compliant interface) and create an instance of Request or Response as appropriate.

Usage

useHttpSignature\HttpMessageSigner;
useHttpSignature\UnProcessableSignatureException;
useGuzzleHttp\Psr7\Request;
$request = newRequest(
'GET',
'https://api.example.com/resource?bat&baz=3',
[
'Host' => 'api.example.com',
'Date' => gmdate('D, d M Y H:i:s T'),
...additional headers
]
);
$signer = (newHttpMessageSigner())
->setPrivateKey($privateKey) // only needed for signing
->setPublicKey($publicKey) // only needed for verifying
->setKeyId('https://example.com/dave#rsaKey') // required when signing
->setAlgorithm('rsa-v1_5-sha256') // typically required when signing
->setCreated(time()) // recommended
->setExpires(time() + 300) // optional, enforced
->setNonce('xJJ9;ro.3*kidney`') // optional one-time token, uniqueness SHOULD be checked/enforced by the calling application
->setTag('fediverse') // optional app profile name
->setSignatureId('sig1') // optional, default is sig1
try { $request = $signer->signRequest('("@method" "@path" "host" "date")', $request);
}
catch (UnProcessableSignatureException $exception) {
$whatHappened = $exception->getMessage();
}
try { $isValid = $signer->verifyRequest($request);
} catch (UnProcessableSignatureException$exception) {
$isValid = false;
$whatHappened = $exception->getMessage();
}

See full examples in /tests.

Structured Fields

RFC9421 makes heavy use of HTTP Structured Fields (RFC8941/RFC9651).

The signRequest() method takes a structured InnerList of components to sign. These may be headers or derived fields. The string will look something like the following (where ... represents additional components):

'("header1" "header2" "@method" ...)'

and may include modifier parameters. These are represented as

'("@query-param";name="foo" "header2";sf "header3" ...)'

Field names beginning with '@' are components derived from the HTTP request but may not be represented in the headers. Please review RFC9421 for precise definitions.

Using the 'sf' parameter on a component will treat a signature component as a Structured Field when normalising the string.

However, parsing arbitrary Structured Fields by adding the 'sf' parameter is likely to fail unless you know what type it is. A built-in table contains the type definition for a number of known stuctured header types. This list is probably incomplete. A method addStructuredFieldTypes() is available to add the type information so it can be successfully parsed. This takes an array with key of the lowercase header name and a value; which is one of 'list', 'innerlist', 'parameters, 'dictionary', 'item'. If the header name is in the list and the 'sf' modifier is used, the header will be parsed as the Structured Field type indicated.

If a Structured Field is declared as type 'dictionary'; it is suitable for use with the RFC9421 key parameter. Using this parameter will fail if the Structured Field type is unknown or has not been registered.

The signRequest() and verifyRequest() methods both use an instance of MessageInterface. In nearly all cases, this will be the RequestInterface. However, when signing responses, the default will be the ResponseInterface, and if components are required from the RequestInterface, the :req parameter must be added to the field definition.

To sign or verify an HTTP Response, use a ResponseInterface as the provided $interface, and provide the RequestInterface in $originalRequest. This is optional but will allow the req modifier to work correctly when signing Responses.

Known issues

Currently not implemented is the special handling of the cookie and set-cookie headers when using the sf modifier. For further information please see https://httpwg.org/http-extensions/draft-ietf-httpbis-retrofit.html and https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis-20 (or later). It is planned to implement this once RFC6265bis is finalised as a new RFC.

Currently, PEM keys are supported as per the RFC examples. JWT/JWK keys are not yet fully supported. A number of encryption libraries are being used to obtain coverage of the entire suite of supported algorithms under PHP, and their key format support varies dramatically.

JWT/JWK algorithm identifiers are permitted for any of the supported algorithms. For instance, 'RS256' and 'rsa-v1_5-sha256' are inter-changeable, depending on your application requirements.

Pull requests welcome.

License

BSD 3-Clause

About

RFC 9421 signer and verifier class in PHP. BSD licensed

Resources

Stars

1 star

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Latest commit

History

106 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

HTTP Message Signer (RFC 9421)

A PHP 8.1+ library for signing and verifying HTTP messages (requests or responses) per RFC 9421.

At the time of writing, this was the closest thing to a reference implementation of RFC9421 that could be found for the PHP platform and one of only a handful of implementations with the full range of support for Structured-Fields and signing algorithms specified in that document.

Supports:

  • PSR-7 HTTP message requests/responses
  • Automatically verify body digest (content-digest header) -- if present
  • Algorithm support:
    • 'RS256' (JWT)
    • 'rsa-v1_5-sha256' (RFC9421)
    • 'RS384' (JWT)
    • 'rsa-v1_5-sha384'
    • 'RS512' (JWT)
    • 'rsa-v1_5-sha512' (RFC9421)
    • 'rsa-pss-sha512' (RFC9421)
    • 'EdDSA' (JWT)
    • 'Ed25519' (openssl)
    • 'ed25519' (RFC9421)
    • 'HS256' (JWT)
    • 'hmac-sha256' (RFC9421)
    • 'HS384' (JWT)
    • 'hmac-sha384'
    • 'HS512' (JWT)
    • 'hmac-sha512'
    • 'ES256' (JWT)
    • 'ecdsa-p256-sha256' (RFC9421)
    • 'ES384' (JWT)
    • 'ecdsa-p384-sha384' (RFC9421)
    • 'ES512' (JWT)
    • 'ecdsa-p512-sha512'

Note

Please report issues. Thanks. Tested on PHP 8.4, should run fine on 8.1+

Installation

composer require arduent/http-message-signer

Notes

An instance of a PSR-7 MessageInterface is passed to the sign and verify functions. This can be a RequestInterface or a ResponseInterface. Typically, this will be a RequestInterface. If your web framework does not supply a pre-populated PSR7-compatible request interface, you can quickly generate one using

useGuzzleHttp\Psr7\ServerRequest;
$request = ServerRequest::fromGlobals();

This would typically be used to verify a message.

If your project uses URL rewriting (such as Apache's 'mod_rewrite'), you may have difficulties verifying some request parameters using a PSR7 request generated using ServerRequest::fromGlobals() as shown here. In that case, you might wish instead to generate a minimal PSR7 Request Message which is populated from the original request URI and which is not affected by URL re-writing:

useGuzzleHttp\Psr7\Request;
// Generate PSR7 request from current HTTP request, which is NOT// affected by the use of Apache mod-rewrite or equivalent.functioncreateRequest(string$baseurl)
{
/** * $baseurl for your site e.g. 'https://example.com' */if ($_SERVER['REQUEST_METHOD'] == 'POST') {
$input = file_get_contents('php://input');
}
$headers = [];
if (isset($_SERVER['CONTENT_TYPE'])) {
$headers['content-type'] = $_SERVER['CONTENT_TYPE'];
}
if (isset($_SERVER['CONTENT_LENGTH'])) {
$headers['content-length'] = $_SERVER['CONTENT_LENGTH'];
}
foreach ($_SERVERas$k => $v) {
if (str_starts_with($k, 'HTTP_')) {
$field = str_replace('_', '-', strtolower(substr($k, 5)));
$headers[$field] = $v;
}
}
returnnewRequest(
$_SERVER['REQUEST_METHOD'],
$baseurl . $_SERVER['REQUEST_URI']),
$headers,
$input ?? null
);
}

To sign a message, install the composer package guzzlehttp/psr7 (or any other PSR7 compliant interface) and create an instance of Request or Response as appropriate.

Usage

useHttpSignature\HttpMessageSigner;
useHttpSignature\UnProcessableSignatureException;
useGuzzleHttp\Psr7\Request;
$request = newRequest(
'GET',
'https://api.example.com/resource?bat&baz=3',
[
'Host' => 'api.example.com',
'Date' => gmdate('D, d M Y H:i:s T'),
...additional headers
]
);
$signer = (newHttpMessageSigner())
->setPrivateKey($privateKey) // only needed for signing
->setPublicKey($publicKey) // only needed for verifying
->setKeyId('https://example.com/dave#rsaKey') // required when signing
->setAlgorithm('rsa-v1_5-sha256') // typically required when signing
->setCreated(time()) // recommended
->setExpires(time() + 300) // optional, enforced
->setNonce('xJJ9;ro.3*kidney`') // optional one-time token, uniqueness SHOULD be checked/enforced by the calling application
->setTag('fediverse') // optional app profile name
->setSignatureId('sig1') // optional, default is sig1
try { $request = $signer->signRequest('("@method" "@path" "host" "date")', $request);
}
catch (UnProcessableSignatureException $exception) {
$whatHappened = $exception->getMessage();
}
try { $isValid = $signer->verifyRequest($request);
} catch (UnProcessableSignatureException$exception) {
$isValid = false;
$whatHappened = $exception->getMessage();
}

See full examples in /tests.

Structured Fields

RFC9421 makes heavy use of HTTP Structured Fields (RFC8941/RFC9651).

The signRequest() method takes a structured InnerList of components to sign. These may be headers or derived fields. The string will look something like the following (where ... represents additional components):

'("header1" "header2" "@method" ...)'

and may include modifier parameters. These are represented as

'("@query-param";name="foo" "header2";sf "header3" ...)'

Field names beginning with '@' are components derived from the HTTP request but may not be represented in the headers. Please review RFC9421 for precise definitions.

Using the 'sf' parameter on a component will treat a signature component as a Structured Field when normalising the string.

However, parsing arbitrary Structured Fields by adding the 'sf' parameter is likely to fail unless you know what type it is. A built-in table contains the type definition for a number of known stuctured header types. This list is probably incomplete. A method addStructuredFieldTypes() is available to add the type information so it can be successfully parsed. This takes an array with key of the lowercase header name and a value; which is one of 'list', 'innerlist', 'parameters, 'dictionary', 'item'. If the header name is in the list and the 'sf' modifier is used, the header will be parsed as the Structured Field type indicated.

If a Structured Field is declared as type 'dictionary'; it is suitable for use with the RFC9421 key parameter. Using this parameter will fail if the Structured Field type is unknown or has not been registered.

The signRequest() and verifyRequest() methods both use an instance of MessageInterface. In nearly all cases, this will be the RequestInterface. However, when signing responses, the default will be the ResponseInterface, and if components are required from the RequestInterface, the :req parameter must be added to the field definition.

To sign or verify an HTTP Response, use a ResponseInterface as the provided $interface, and provide the RequestInterface in $originalRequest. This is optional but will allow the req modifier to work correctly when signing Responses.

Known issues

Currently not implemented is the special handling of the cookie and set-cookie headers when using the sf modifier. For further information please see https://httpwg.org/http-extensions/draft-ietf-httpbis-retrofit.html and https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis-20 (or later). It is planned to implement this once RFC6265bis is finalised as a new RFC.

Currently, PEM keys are supported as per the RFC examples. JWT/JWK keys are not yet fully supported. A number of encryption libraries are being used to obtain coverage of the entire suite of supported algorithms under PHP, and their key format support varies dramatically.

JWT/JWK algorithm identifiers are permitted for any of the supported algorithms. For instance, 'RS256' and 'rsa-v1_5-sha256' are inter-changeable, depending on your application requirements.

Pull requests welcome.

License

BSD 3-Clause

About

RFC 9421 signer and verifier class in PHP. BSD licensed

Resources

Stars

1 star

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages