DPoP reference implementation
A small Python package that does the DPoP part of the authentication of the
Profit Basetool Exchange API: it keeps the installation’s P-256 key where the platform keeps keys,
and it builds the proof JWT for each request. It uses nothing but the Python standard library
(3.11 or later); the platform’s own crypto is reached through ctypes. It is licensed under the
MIT licence, so it can be copied into a client of any licence.
It is a reference, not a client: it sends no request, stores no token and runs no device login.
Usage #
from dpop_reference import (
NonceCache, asks_for_nonce, authorization_header, build_proof, open_installation_key,
)
key = open_installation_key("ExampleClient DPoP", key_file="~/.config/example-client/dpop.pem")
print(key.thumbprint())
nonces = NonceCache()
url = "https://ingest.profit-base.online/exchange/v1/me/blueprints?cursor=f1.7.42"
headers = {
"Authorization": authorization_header(access_token),
"DPoP": build_proof(key, "GET", url, access_token=access_token, nonce=nonces.get(url)),
}
After every answer, call nonces.update(url, answer_headers). When
asks_for_nonce(status, answer_headers) is true, build a new proof with nonces.get(url) and
send the request once more. At the token endpoint, build the proof without access_token.
| Function | What it does |
|---|---|
open_installation_key(name, key_file=...) |
Opens the installation’s key, creating it on first use. |
delete_installation_key(name, key_file=...) |
Deletes it, for a disconnect or INSTALLATION_REVOKED. |
build_proof(key, method, url, access_token=, nonce=) |
Builds and signs one proof. |
normalize_htu(url) |
The htu of a URL: lower-case scheme and host, no default port, no query or fragment. |
jwk_thumbprint(jwk) |
The RFC 7638 thumbprint — the installation’s identity. |
access_token_hash(token) |
The ath claim. |
NonceCache, asks_for_nonce(...) |
The latest DPoP-Nonce per server, and whether an answer is a nonce challenge. |
Platforms #
Windows (dpop_reference.cng). The key is a persisted, non-exportable CNG key, created
through ncrypt.dll under the name you pass:
- in the TPM through the Microsoft Platform Crypto Provider when it is present and supports P-256, otherwise
- in the Microsoft Software Key Storage Provider, with an export policy of 0.
The private key never enters the process: NCryptSignHash signs in the provider and already
returns the 64-byte r || s form. On the software provider, code running as the same Windows user
can still ask Windows to sign; only the TPM resists an administrator.
Linux (dpop_reference.openssl). The key is generated by OpenSSL 3’s libcrypto
(libcrypto.so.3) and stored as a PKCS #8 PEM file, created exclusively with mode 0600 in a
directory created with mode 0700; a file that others can read is refused. OpenSSL’s DER
signature is converted to r || s. The client security requirements prefer the Secret Service;
a file is the fallback, and a client that uses it shows the member that it does.
Other systems. macOS is not covered: its system libcrypto must not be loaded. Set
DPOP_REFERENCE_LIBCRYPTO to the path of an OpenSSL 3 libcrypto to use the file backend anyway;
the same variable overrides the library search everywhere.
Tests #
From the repository root:
python -m unittest discover -s docs/exchange/dpop-reference
The tests need no network. They check base64url without padding, the RFC 7638 thumbprint example,
the jkt and ath examples of RFC 9449, htu normalisation, the DER conversion and the proof’s
header and claims. For each backend available on the machine they create a key, sign, and verify
the signature twice: with the backend itself, and with an independent pure-Python P-256 verifier
that is checked against RFC 6979’s test vector. A backend the machine lacks — CNG outside Windows,
the TPM without one, OpenSSL without a libcrypto 3 — is skipped, not failed. The CNG tests create
keys under random names and delete them again.
Files #
| File | Content |
|---|---|
dpop_reference/jose.py |
base64url, thumbprint, ath, htu, the DER and JWS signature forms |
dpop_reference/proof.py |
the proof, the Authorization value, the nonce cache |
dpop_reference/cng.py |
the Windows CNG backend |
dpop_reference/openssl.py |
the OpenSSL 3 backend |
dpop_reference/keystore.py |
the platform’s choice of backend |
tests/ |
the tests and the independent verifier |