Title: Quick Trusted Response (QTR) Codes Specification
Version: 0.2
Status: Drafting
Date last updated: 23th October 2024
- Introduction
- Terminology
- QTR Parameter Format
- Operational Workflow
- Cryptographic Specifications
- Public Key Retrieval Methods
- Example (with Python proof of concepts)
- Error Handling and Timeouts
- Backward Compatibility
- Security Considerations
- Privacy and Ethical Considerations
- Future Extensions
- References
Quick Trusted Response (QTR) Codes enhance the security and trustworthiness of QR codes by introducing a standardised verification mechanism. This mechanism allows applications to authenticate the content of QR codes before any action is taken, mitigating risks associated with malicious QR codes. The QTR mechanism can be used outside of QR codes by browsers or applications processing links, implementation details for non-QR use-cases is out-of-scope for this document.
- QTR: Quick Trusted Response
- QTR Code: A QR code that includes mechanisms to verify signed information.
- JWT: JSON Web Token is a format encompassing a header, payload and signature.
- BIMI: Brand Indicators for Message Identification; used for displaying verified brand logos.
- Key ID (
kid
): Identifier for the public key used in signature verification. - Key Location: Method to retrieve the public key:
d
:{kid}._qtr
DNS recordw
:.well-known/jwks.json
s
:.well-known/qtr/{kid}.json
h
:X-QTR-P
header available in HEAD request to hostnameu
:X-QTR-P
header available in HEAD request to URL
The x-qtr
parameter utilises JWTs.
x-qtr={jwt: "header . payload . signature"}
Where the payload consists of a version and key location identifier: {"qtr": "1d"}
.
- version (1 or more numerical digits): QTR protocol version number (e.g.,
1
). - key_location (1 a-z character): Method to retrieve the public key.
- key_id (variable length): Identifier for the public key (optional for
h
andu
key locations). - signature (variable length): base64 URL safe encoded cryptographic signature.
x-qtr
regex:
(?i:x-qtr=)?(?P<header>[A-Za-z0-9_-]+)\.(?P<payload>[A-Za-z0-9_-]+)\.(?P<signature>[A-Za-z0-9_-]+)
Decoded payload regex:
(?:{"qtr":\s*")?(?P<version>\d+)(?P<key_location>[a-z])(?:"})?
1: x-qtr=eyJhbGciOiJFZERTQSIsImlzcyI6ImV4YW1wbGUuY29tIiwia2lkIjoiMTIzNCJ9.eyJxdHIiOiIxZCJ9.TVuX6dqmmVi-nF8YLo8GquM5MfsLqexcv4KXmGliNt--c2RT6b34sR2dQfD3O20OlhjpDRXAPLh3DAgZ0KClBw
- Header:
eyJhbGciOiJFZERTQSIs...
={"alg":"EdDSA","iss":"example.com","kid":"1234"}
- Signing algorithm:
EdDSA
- Issuer:
example.com
- Key ID:
1234
- Signing algorithm:
- Payload:
eyJxdHIiOiIxZCJ9
={"qtr": "1d"}
- Version:
1
- Key Location:
d
(DNS)
- Version:
- Signature:
TVuX6dqmmVi-nF8YLo8GquM5MfsLqexcv4KXmGliNt--c2RT6b34sR2dQfD3O20OlhjpDRXAPLh3DAgZ0KClBw
(Ed25519 signature in base64 URL safe format)
2: x-qtr=eyJhbGciOiJFZERTQSJ9.eyJxdHIiOiAiMWgifQ.TVuX6dqmmVi-nF8YLo8GquM5MfsLqexcv4KXmGliNt--c2RT6b34sR2dQfD3O20OlhjpDRXAPLh3DAgZ0KClBw
- Header:
eyJhbGciOiJFZERTQSJ9
={"alg":"EdDSA"}
- Signing algorithm:
EdDSA
- Issuer: null (use domain in URL)
- Key ID: null (public key in
X-QTR-P
response header)
- Signing algorithm:
- Payload:
eyJxdHIiOiIxZCJ9
={"qtr": "1d"}
- Version:
1
- Key Location:
h
(X-QTR-P
response header to hostname request)
- Version:
- Signature:
TVuX6dqmmVi-nF8YLo8GquM5MfsLqexcv4KXmGliNt--c2RT6b34sR2dQfD3O20OlhjpDRXAPLh3DAgZ0KClBw
(Ed25519 signature in base64 URL safe format)
x-qtrs
: (Optional) Specifies the short URL mechanism.- Example:
https://example.com?x-qtrs
- Example:
-
Data Preparation:
- Original data (e.g., URL, Wi-Fi credentials) is prepared.
-
Appending QTR Parameters:
- The
x-qtr
parameter is constructed and appended to the data, minus the signature and trailing.
x-qtr
should be the last parameter
- The
-
Signature Generation:
- The data is signed using a private key compliant with cryptographic specifications.
-
Appending QTR Signature:
- A
.
is added to thex-qtr
- Signature is appended to the
x-qtr
parameter to create the JWT.
- A
-
Minifying the QR Code:
- The
x-qtrs
parameter can be used to specify the shortening mechanism.
- The
-
Data Extraction:
- Application scans the QR code and extracts data and parameters.
-
Parameter Parsing:
- If anyting is set after the host (other than a trailing
/
), thenx-qtr
orx-qtrs
parameters are required for a possible verified link. - In other words, if the data is a URL with only a host (or a path of
/
) thenx-qtr
parameters are optional. - BIMI and valid VMC will still be required to be a verified link.
- If applicable, parse the
x-qtr
or thex-qtrs
parameter.
- If anyting is set after the host (other than a trailing
-
Handle Shortened URL:
- Check the domain for a valid BIMI record.
- Perform a GET request without following redirects.
- Continue verification using the
Location
response header. - Inform the user if domains differ.
-
Original Data Reconstruction
- If applicable, remove the signature from the
x-qtr
JWT and trailing.
to retrieve the pre-signed data. - The data must be trimmed of any trailing
&?#./
characters (regex[&\?#\.\/]+$
), for example:https://qtrco.de?
=>https://qtrco.de
https://qtrco.de?x-qtr=eyJh11.eyJh22.eyJh33&test=1
=>https://qtrco.de?x-qtr=eyJh11.eyJh22&test=1
tel:+441234567890#x-qtr=eyJh11.eyJh22.eyJh33
=>tel:+441234567890#x-qtr=eyJh11.eyJh22
- If applicable, remove the signature from the
-
Domain Determination
- Determine the domain from the JWT header
iss
value.
- Determine the domain from the JWT header
-
Public Key Retrieval
- Check cache for existing
key_id
. - Retrieve the public key using the specified
key_location
andkey_id
.
- Check cache for existing
-
Signature Verification
- Verify the signature using the retrieved public key.
-
BIMI Integration
- Check cache for logo and VMC.
- Retrieve and display the BIMI logo associated with the domain.
- First at
default._bimi.{domain}
- Then at
qtr._bimi.{domain}
- First at
-
User Feedback
- Inform the user of the verification result within 4 seconds.
- Ed25519-SHA256:
- Signature Algorithm: EdDSA using Ed25519 curve and SHA-256 hash.
-
Data to Sign:
- The original data plus the
x-qtr
orx-qtrs
parameters without any signature.
- The original data plus the
-
Process:
- Sign the data using the private key.
-
Signature Encoding:
- Encode the signature in URL safe base64 and append to the
x-qtr
parameter to form a JWT.
- Encode the signature in URL safe base64 and append to the
- Encoding:
- Public keys are JWKs either as JSON, or URL safe Base64 encoded JSON.
-
Query:
- TXT record at
{key_id}._qtr.{domain}
- Cascades upwards similar to BIMI DNS records.
- For example, the domain
third.second.first.example.com
will invoke the following lookups until thekey_id
is found:{key_id}._qtr.third.second.first.example.com
{key_id}._qtr.second.first.example.com
{key_id}._qtr.first.example.com
{key_id}._qtr.example.com
- For example, the domain
- TXT record at
-
Record Content:
- Contains the public key in Base64-encoded JWT format.
- Example:
eyJrdHkiOiJPS1AiLCJjcnYiOiJFZDI1NTE5IiwieCI6IkRnU2I1SGtzeHh1aTNEMUljaVFkYjMySXpWbExjUjc3VjJZUWk1b25fVTgifQ
-
URL:
https://{domain}/.well-known/jwks.json
-
Content:
- JSON Web Key Set containing public keys.
- Example:
{"keys":[{"kid":"1234","kty":"OKP","crv":"Ed25519","x":"DgSb5Hksxxui3D1IciQdb32IzVlLcR77V2YQi5on_U8"}]}
-
Key Identification:
- Use
key_id
(kid
) to select the correct key.
- Use
-
URL:
https://{domain}/.well-known/qtr/{key_id}.json
-
Content:
- JSON Web Key containing public key.
- Example:
{"kty":"OKP","crv":"Ed25519","x":"DgSb5Hksxxui3D1IciQdb32IzVlLcR77V2YQi5on_U8"}
-
X-QTR-P Header:
curl -X HEAD https://{domain}
- Where one key is used for the whole domain
- Example:
X-QTR-P: eyJrdHkiOiJPS1AiLCJjcnYiOiJFZDI1NTE5IiwieCI6IkRnU2I1SGtzeHh1aTNEMUljaVFkYjMySXpWbExjUjc3VjJZUWk1b25fVTgifQ
-
Content:
- Contains the public key in Base64-encoded JWT format.
-
X-QTR-P Header:
curl -X HEAD https://{domain}/{path}{querystrings}
- Where different keys are used for different URLs
- Remove any
x-qtr
parameters - Example:
X-QTR-P: eyJrdHkiOiJPS1AiLCJjcnYiOiJFZDI1NTE5IiwieCI6IkRnU2I1SGtzeHh1aTNEMUljaVFkYjMySXpWbExjUjc3VjJZUWk1b25fVTgifQ
-
Content:
- Contains the public key in Base64-encoded JWT format.
Using the following example Ed25519 key pair:
- Private key:
XdIlrwpzVw51QcI7SRQYcY8VMjKSrXtvtbxauvsC_tk
- Public key:
7kyURdPplV85hQ6BcVuvEbcBTMRhosOs5Jv5oGfu28k
The following steps generate a signed URL where a public key is set in the response header of a HEAD request to host (e.g. example.com
).
URL to sign: https://example.com/testing?test=abc123
Generate a JWT header with at least an algorithm (alg
) set (e.g. {"alg":"EdDSA"}
) and payload of {"qtr":"1h"}
.
Append the JWT (minus signature and with no trailing .
) to the URL: https://example.com/testing?test=abc123&x-qtr=eyJhbGciOiJFZERTQSJ9.eyJxdHIiOiAiMWgifQ
Sign the URL and append the signature: https://example.com/testing?test=abc123&x-qtr=eyJhbGciOiJFZERTQSJ9.eyJxdHIiOiAiMWgifQ.pLQJOGWHRL34IA8Lr3T4CHVrnw325AlOnedfRzjDpqiVguOoHpkHKkqouGF449gtcqWulSgexuCTTE9gbzsBDQ
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
import base64
import json
def base64url_encode(data):
return base64.urlsafe_b64encode(data).rstrip(b"=").decode("utf-8")
def base64url_decode(data):
padding = "=" * (-len(data) % 4)
return base64.urlsafe_b64decode(data + padding)
# Given data
private_key_b64url = "XdIlrwpzVw51QcI7SRQYcY8VMjKSrXtvtbxauvsC_tk"
private_key_bytes = base64.urlsafe_b64decode(private_key_b64url + "==")
private_key = Ed25519PrivateKey.from_private_bytes(private_key_bytes)
public_key = private_key.public_key()
# Original URL
original_url = "https://example.com/testing?test=abc123"
# Create JWT header and payload
header = {"alg": "EdDSA"}
payload = {"qtr": "1h"}
header_json = json.dumps(header, separators=(",", ":")).encode("utf-8")
payload_json = json.dumps(payload, separators=(",", ":")).encode("utf-8")
header_b64url = base64url_encode(header_json)
payload_b64url = base64url_encode(payload_json)
partial_jwt = f"{header_b64url}.{payload_b64url}"
# Message to sign is the URL with x-qtr containing the partial JWT
message_to_sign = f"{original_url}&x-qtr={partial_jwt}"
print("Unsigned URL:", message_to_sign)
# Sign the message
signature = private_key.sign(message_to_sign.encode("utf-8"))
signature_b64url = base64url_encode(signature)
# Append the signature to the JWT
full_jwt = f"{partial_jwt}.{signature_b64url}"
# Construct the final URL
final_url = f"{original_url}&x-qtr={full_jwt}"
print("Signed URL:", final_url)
# To verify the signature (optional)
# Extract the message to verify and the signature
signature_to_verify = base64url_decode(signature_b64url)
message_to_verify = message_to_sign.encode("utf-8")
try:
public_key.verify(signature_to_verify, message_to_verify)
print("Signature is valid.")
except Exception as e:
print("Signature is invalid:", str(e))
To verify a signed URL like: https://example.com/testing?test=abc123&x-qtr=eyJhbGciOiJFZERTQSJ9.eyJxdHIiOiAiMWgifQ.pLQJOGWHRL34IA8Lr3T4CHVrnw325AlOnedfRzjDpqiVguOoHpkHKkqouGF449gtcqWulSgexuCTTE9gbzsBDQ
Parse QTR JWT, to get version 1 and key location h
(in the x-qtr-p
response header from HEAD request to host).
curl --head -s https://example.com | grep "x-qtr-p:"
=>
x-qtr-p: eyJrdHkiOiJPS1AiLCJjcnYiOiAiRWQyNTUxOSIsIngiOiAiN2t5VVJkUHBsVjg1aFE2QmNWdXZFYmNCVE1SaG9zT3M1SnY1b0dmdTI4ayJ9
Decode the X-QTR-P
value to get the public key.
Remove the signature: https://example.com/testing?test=abc123&x-qtr=eyJhbGciOiJFZERTQSJ9.eyJxdHIiOiAiMWgifQ
Verify the signature using the public key from the JWK that was in the x-qtr-p
response header.
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey
import base64
# url = b"https://example.com/testing?test=abc123&x-qtr=eyJhbGciOiJFZERTQSJ9.eyJxdHIiOiAiMWgifQ.pLQJOGWHRL34IA8Lr3T4CHVrnw325AlOnedfRzjDpqiVguOoHpkHKkqouGF449gtcqWulSgexuCTTE9gbzsBDQ"
# Given data
public_key_b64url = "7kyURdPplV85hQ6BcVuvEbcBTMRhosOs5Jv5oGfu28k"
signature_b64url = "pLQJOGWHRL34IA8Lr3T4CHVrnw325AlOnedfRzjDpqiVguOoHpkHKkqouGF449gtcqWulSgexuCTTE9gbzsBDQ"
message = b"https://example.com/testing?test=abc123&x-qtr=eyJhbGciOiJFZERTQSJ9.eyJxdHIiOiAiMWgifQ"
# Decode the public key from Base64URL to bytes
public_key_bytes = base64.urlsafe_b64decode(public_key_b64url + "==")
# Load the public key
public_key = Ed25519PublicKey.from_public_bytes(public_key_bytes)
# Decode the signature from Base64URL to bytes
signature_bytes = base64.urlsafe_b64decode(signature_b64url + "==")
# Verify the signature
try:
public_key.verify(signature_bytes, message)
print("Signature is valid.")
except Exception as e:
print("Signature is invalid:", str(e))
-
Timeouts:
- Maximum acceptable latency for verification is 4 seconds.
-
Error Handling:
- If verification cannot be completed within the timeout, warn the user.
-
Error Codes:
- Implement error codes similar to email specifications (e.g., SMTP error codes).
-
Compatibility:
- QTR Codes are designed to be backward compatible with standard QR code readers.
-
Non-QTR Aware (Standard QR Reader) Applications:
- Applications that do not recognise
x-qtr
parameters will ignore them and process the data normally.
- Applications that do not recognise
-
Private Key Security:
- Private keys must be securely generated, stored, and managed.
-
Input Validation:
- Validate all inputs to prevent injection and other attacks.
-
Key Rotation:
- Implement key rotation policies where appropriate.
-
TLS Requirements:
- Use HTTPS for all network requests to ensure data integrity and confidentiality.
-
Compliance:
- Adhere to relevant industry standards and regulations (e.g., GDPR, PCI DSS).
-
Data Minimisation:
- Collect only data necessary for verification.
-
User Consent:
- Inform users about data processing activities.
-
Transparency:
- Be transparent about how data is used and stored.
-
Ethical Use:
- Ensure QTR Codes are not used to track or profile users without consent.
-
Quantum-Resistant Algorithms:
- Explore the adoption of quantum-resistant cryptographic algorithms.
-
Additional Data Types:
- Extend support to other data types as needed.
-
Internationalisation:
- Support internationalised domain names and multilingual data.
-
Offline Verification Enhancements:
- Improve caching mechanisms for better offline support.
- DKIM Specifications: RFC 6376
- BIMI Specifications: AuthIndicators Working Group
- JSON Web Token (JWT): RFC 7519
- JSON Web Key (JWK): RFC 7517
- Ed25519 Algorithm: RFC 8032