├── .github └── workflows │ └── editor.yml ├── .gitignore ├── .scalafmt.conf ├── CONTRIBUTING.md ├── Makefile ├── README.md ├── draft-dijkhuis-cfrg-hdkeys.md ├── feedback-poa.md ├── feedback.md ├── prototype.demo.lisp └── prototype.lisp /.github/workflows/editor.yml: -------------------------------------------------------------------------------- 1 | name: Editor 2 | 3 | on: 4 | push: 5 | paths-ignore: 6 | - media/deployment.svg 7 | - Makefile 8 | - README.md 9 | - feedback.md 10 | - prototype.worksheet.sc 11 | - .gitignore 12 | pull_request: 13 | paths-ignore: 14 | - media/deployment.svg 15 | - Makefile 16 | - README.md 17 | - feedback.md 18 | - prototype.worksheet.sc 19 | - .gitignore 20 | 21 | permissions: 22 | contents: write 23 | 24 | jobs: 25 | build: 26 | name: Editor’s Copy 27 | runs-on: ubuntu-latest 28 | steps: 29 | - uses: actions/checkout@v3 30 | - id: setup 31 | run: date -u "+date=%FT%T" >> $GITHUB_OUTPUT 32 | - uses: actions/cache@v3 33 | with: 34 | path: | 35 | .refcache 36 | .venv 37 | .gems 38 | node_modules 39 | .targets.mk 40 | key: i-d-${{ steps.setup.outputs.date }} 41 | restore-keys: i-d- 42 | - name: Build 43 | uses: martinthomson/i-d-template@v1 44 | with: 45 | token: ${{ github.token }} 46 | - name: Publish 47 | uses: martinthomson/i-d-template@v1 48 | if: ${{ github.event_name == 'push' }} 49 | with: 50 | make: gh-pages 51 | token: ${{ github.token }} 52 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .bsp/ 3 | .metals/ 4 | .scala-build/ 5 | .vscode/ 6 | -------------------------------------------------------------------------------- /.scalafmt.conf: -------------------------------------------------------------------------------- 1 | version = "3.7.15" 2 | runner.dialect = scala3 3 | maxColumn = 100 4 | newlines.source=fold 5 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | This repository relates to activities in the Internet Engineering Task Force 4 | ([IETF](https://www.ietf.org/)). All material in this repository is considered 5 | Contributions to the IETF Standards Process, as defined in the intellectual 6 | property policies of IETF currently designated as 7 | [BCP 78](https://www.rfc-editor.org/info/bcp78), 8 | [BCP 79](https://www.rfc-editor.org/info/bcp79) and the 9 | [IETF Trust Legal Provisions (TLP) Relating to IETF Documents](http://trustee.ietf.org/trust-legal-provisions.html). 10 | 11 | Any edit, commit, pull request, issue, comment or other change made to this 12 | repository constitutes Contributions to the IETF Standards Process 13 | (https://www.ietf.org/). 14 | 15 | You agree to comply with all applicable IETF policies and procedures, including, 16 | BCP 78, 79, the TLP, and the TLP rules regarding code components (e.g. being 17 | subject to a Simplified BSD License) in Contributions. 18 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | LIBDIR := lib 2 | include $(LIBDIR)/main.mk 3 | 4 | $(LIBDIR)/main.mk: 5 | ifneq (,$(shell grep "path *= *$(LIBDIR)" .gitmodules 2>/dev/null)) 6 | git submodule sync 7 | git submodule update $(CLONE_ARGS) --init 8 | else 9 | git clone -q --depth 10 $(CLONE_ARGS) \ 10 | -b main https://github.com/martinthomson/i-d-template $(LIBDIR) 11 | endif 12 | 13 | demo: 14 | sbcl --non-interactive \ 15 | --eval '(ql:quickload "ironclad")' \ 16 | --load prototype.lisp \ 17 | --load prototype.demo.lisp 18 | 19 | repl: 20 | rlwrap sbcl \ 21 | --eval '(ql:quickload "ironclad")' \ 22 | --load prototype.lisp \ 23 | --eval "(use-package 'prototype)" 24 | 25 | hdk.pdf: 26 | mkdir -p build 27 | cp -r media build 28 | echo \ 29 | "" \ 30 | "Hierarchical Deterministic Keys for the European Digital Identity Wallet" \ 31 | "" \ 32 | > build/hdk.html 33 | npx -p @mermaid-js/mermaid-cli mmdc -i draft-dijkhuis-cfrg-hdkeys.md -o build/keys.md -e svg -t neutral -w 400 34 | cat README.md | \ 35 | sed -e "s/# Hierarchical Deterministic Keys for the European Digital Identity Wallet/# Introduction to Hierarchical Deterministic Keys/g" | \ 36 | sed -e "s/keys.md/#hierarchical-deterministic-keys/g" | \ 37 | sed -e "s/prototype.worksheet.sc/https:\/\/github.com\/sander\/hierarchical-deterministic-keys\/blob\/main\/prototype.worksheet.sc/g" | \ 38 | sed -e "s/feedback.md/#feedback-to-enable-hierarchical-deterministic-keys-in-the-wallet-toolbox/g" | \ 39 | pandoc \ 40 | --from=gfm \ 41 | --to=html \ 42 | >> build/hdk.html 43 | pandoc \ 44 | --from=gfm \ 45 | --to=html \ 46 | build/keys.md \ 47 | >> build/hdk.html 48 | cat feedback.md | \ 49 | sed -e 's/Hierarchical Deterministic Keys for the European Digital Identity Wallet/Introduction to Hierarchical Deterministic Keys/g' | \ 50 | sed -e 's/README.md/#introduction-to-hierarchical-deterministic-keys/g' | \ 51 | sed -e "s/keys.md/#hierarchical-deterministic-keys/g" | \ 52 | pandoc \ 53 | --from=gfm \ 54 | --to=html \ 55 | >> build/hdk.html 56 | cd build && \ 57 | cat hdk.html | \ 58 | sed -e "s//
<\/colgroup>/g" | \ 59 | sed -e "s/

Note<\/p>/

Note<\/b><\/p>/g" | \ 60 | pandoc \ 61 | --from=html \ 62 | --pdf-engine=xelatex \ 63 | --toc \ 64 | --columns=10 \ 65 | --variable title="Hierarchical Deterministic Keys" \ 66 | --variable subtitle="for the European Digital Identity Wallet" \ 67 | --variable date="Version 0.2.0-SNAPSHOT\\\\\vspace{2cm}\href{https://github.com/sander/hierarchical-deterministic-keys}{github.com/sander/hierarchical-deterministic-keys}" \ 68 | --variable colorlinks=true \ 69 | --variable papersize=a4 \ 70 | --variable geometry="margin=2cm" \ 71 | --variable numbersections=true \ 72 | --variable documentclass=report \ 73 | -o hdk.pdf 74 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Hierarchical Deterministic Keys for the European Digital Identity Wallet 2 | 3 | ## Background 4 | 5 | The [EU Digital Identity Regulation](https://eur-lex.europa.eu/eli/reg/2024/1183/oj) requires secure cryptography in wallet solutions. The regulatory requirements bring several implementation challenges: 6 | 7 | 1. How might an issuer protect document authenticity? 8 | 2. How might an issuer prevent tracking based on document authenticity signatures? 9 | 3. How might a wallet solution enable binding documents to a WSCD with a high level of assurance? 10 | 4. How might a wallet solution enable relying parties to verify WSCD binding? 11 | 5. How might a wallet solution blind verification keys for each attestation? 12 | 6. How might a wallet solution prove possession of blinded keys? 13 | 7. How might a wallet solution create qualified electronic signatures or seals? 14 | 15 | The European Commission and Member States are developing a Wallet Toolbox to enable interoperable solutions to challenges such as these. This Toolbox includes the [Architecture and Reference Framework](https://eu-digital-identity-wallet.github.io/eudi-doc-architecture-and-reference-framework/latest/arf/) (ARF). The Large Scale Pilots are implementing and testing the wallet to generate feedback on this Toolbox. 16 | 17 | In this repository, Pilot participants contribute to concrete interoperable solutions based on the ideas of Hierarchical Deterministic Keys (HDKs) and blinded key proof of possession. This approach is introduced in the Analysis of selective disclosure and zero-knowledge proofs ([ETSI TR 119476 version 1.2.1](https://www.etsi.org/deliver/etsi_tr/119400_119499/119476/01.02.01_60/tr_119476v010201p.pdf)). The Pilot participants aim to evaluate various options, present an appropriate solution, and develop a common specification to enable testing interoperability. 18 | 19 | > [!NOTE] 20 | > This information is shared by participants of the [Digital Credentials for Europe (DC4EU) Consortium](https://www.dc4eu.eu), the [EU Digital Identity Wallet Consortium (EWC)](https://eudiwalletconsortium.org), and the [Potential Consortium](https://www.digital-identity-wallet.eu). Views and opinions expressed are those of the authors only and do not necessarily reflect those of all consortium members. 21 | 22 | ## Position 23 | 24 | The participants share the following position. 25 | 26 | As of today, ARF 1.4 provides no complete and interoperable solution for key management, that can be industrially deployed at scale and used across the whole ecosystem. 27 | 28 | HDK is a viable solution to some EU Digital Identity Wallet key management challenges. It enables the management of an unlimited amount of keys using a single secret. It also allows the use of existing secure cryptographic devices with common algorithms. 29 | 30 | HDK is applicable to specific credential schemes, including one-time-use attestations and BBS#. 31 | 32 | We want to start a dialogue with the European Commission about the proposed solution in this document, and how to include this in the Toolbox. We have specific feedback on the ARF high-level requirements to make sure that the EU Digital Identity implementation addresses the key management challenges in an interoperable and scalable way. This should provide a solid basis for legislation in the Implementing Acts. 33 | 34 | Expert participants from DC4EU: 35 | 36 | - John Bradley (Yubico) 37 | - Leif Johansson (Sunet) 38 | - Nikos Voutsinas (GUnet) 39 | 40 | Expert participants from Potential: 41 | 42 | - Antoine Dumanois (Orange) 43 | - Sander Dijkhuis (Cleverbase) 44 | - Zeff Sherriff (Bundesdruckerei) 45 | 46 | ## Contents 47 | 48 | To address challenges 5 and 6, this repository contains a freely accessible, unencumbered specification of **[Hierarchical Deterministic Keys](draft-dijkhuis-cfrg-hdkeys.md)**. This enables an EU Digital Identity Wallet deployment that distributes key management efficiently: 49 | 50 | To illustrate and validate the specifications, this repository contains a **[Prototype implementation](prototype.lisp)** and **[demo](prototype.demo.lisp)** to run with [Common Lisp](https://lisp-lang.org/learn/getting-started/) using `make demo` and `make repl`. 51 | 52 | To inform further standardisation and legislation, this repository contains **[Feedback to enable Hierarchical Deterministic Keys in the Wallet Toolbox](feedback.md)**. It also contains **[Feedback to resolve HDK and PoA issues in the ARF](feedback-poa.md)**. 53 | 54 | The repository does not contain details about the implementation of HDK for key management in credential schemes, such as one-time-use document schemes for relying party unlinkability and weak issuer unlinkability, or [BBS#](https://github.com/user-attachments/files/15905230/BBS_Sharp_Short_TR.pdf) for full unlinkability. Credential schemes have not currently been analysed by the working group. When such analysis is carried out, it might result in changes to the specification. For example, delegated key generation only seems to have use cases for batch one-time-use document issuance, and not for BBS#. 55 | 56 | ## Contributing 57 | 58 | See the [contributor agreement](CONTRIBUTING.md). 59 | 60 | Feedback and other input is easiest to discuss in [GitHub issues](https://github.com/sander/hierarchical-deterministic-keys/issues). 61 | 62 | To enable reuse, new contributions to the technical reports not intended for standardisation must be provided under either [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/) or [CC0 1.0](https://creativecommons.org/publicdomain/zero/1.0/). 63 | -------------------------------------------------------------------------------- /draft-dijkhuis-cfrg-hdkeys.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Hierarchical Deterministic Keys 3 | abbrev: HDK 4 | category: info 5 | docname: draft-dijkhuis-cfrg-hdkeys-latest 6 | submissiontype: independent 7 | v: 3 8 | area: IRTF 9 | workgroup: Crypto Forum 10 | keyword: 11 | - KDF 12 | venue: 13 | github: sander/hierarchical-deterministic-keys 14 | author: 15 | - fullname: Sander Dijkhuis 16 | role: editor 17 | initials: S. Q. 18 | surname: Dijkhuis 19 | organization: Cleverbase 20 | email: mail@sanderdijkhuis.nl 21 | contributor: 22 | - fullname: Micha Kraus 23 | ipr: trust200902 24 | normative: 25 | FIPS180-4: 26 | title: Secure Hash Standard (SHS) 27 | target: https://csrc.nist.gov/pubs/fips/180-4/upd1/final 28 | seriesinfo: 29 | FIPS: 180-4 30 | DOI: 10.6028/NIST.FIPS.180-4 31 | author: 32 | - organization: National Institute of Standards and Technology (NIST) 33 | date: 2012-06 34 | ISO18013-5: 35 | title: "Personal identification - ISO-compliant driving licence - Part 5: Mobile driving licence (mDL) application" 36 | target: https://www.iso.org/standard/69084.html 37 | seriesinfo: 38 | ISO/IEC: 18013-5:2021 39 | author: 40 | - organization: ISO/IEC 41 | date: 2019-09 42 | RFC4648: 43 | RFC5234: 44 | RFC8017: 45 | RFC9180: 46 | RFC9380: 47 | RFC9497: 48 | SEC2: 49 | title: "SEC 2: Recommended Elliptic Curve Domain Parameters, Version 2.0" 50 | target: https://www.secg.org/sec2-v2.pdf 51 | seriesinfo: 52 | SEC: 2 Version 2.0 53 | author: 54 | - organization: Certicom Research 55 | date: 2010-01 56 | TR03111: 57 | title: Elliptic Curve Cryptography 58 | target: https://www.bsi.bund.de/EN/Themen/Unternehmen-und-Organisationen/Standards-und-Zertifizierung/Technische-Richtlinien/TR-nach-Thema-sortiert/tr03111/tr-03111.html 59 | seriesinfo: 60 | BSI: TR-03111 Version 2.10 61 | author: 62 | - organization: Federal Office for Information Security (BSI) 63 | date: 2018-06 64 | informative: 65 | BIP32: 66 | title: Hierarchical Deterministic Wallets 67 | target: https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki 68 | seriesinfo: 69 | BIP: 32 70 | author: 71 | - name: Pieter Wuille 72 | date: 2021-02 73 | draft-OpenID4VCI: 74 | title: OpenID for Verifiable Credential Issuance, draft 13 75 | target: https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0.html 76 | author: 77 | - name: T. Lodderstedt 78 | - name: K. Yasuda 79 | - name: T. Looker 80 | date: 2024-02-08 81 | EU2015-1502: 82 | title: Commission Implementing Regulation (EU) 2015/1502 of 8 September 2015 on setting out minimum technical specifications and procedures for assurance levels for electronic identification means 83 | target: https://eur-lex.europa.eu/legal-content/TXT/?uri=CELEX%3A32015R1502 84 | author: 85 | - organization: European Commission 86 | seriesinfo: 87 | (EU): 2015/1502 88 | date: 2025-09 89 | EU2024-1183: 90 | title: Amending Regulation (EU) No 910/2014 as regards establishing the European Digital Identity Framework 91 | target: https://data.europa.eu/eli/reg/2024/1183/oj 92 | author: 93 | - organization: The European Parliament and the Council of the European Union 94 | seriesinfo: 95 | (EU): 2024/1183 96 | date: 2024-04 97 | I-D.draft-bradleylundberg-cfrg-arkg-02: 98 | I-D.draft-irtf-cfrg-signature-key-blinding-07: 99 | RFC7800: 100 | RFC8235: 101 | Verheul2024: 102 | title: Attestation Proof of Association – provability that attestation keys are bound to the same hardware and person 103 | target: https://eprint.iacr.org/2024/1444 104 | author: 105 | - name: E. Verheul 106 | date: 2024-09-18 107 | Wilson2023: 108 | title: Post-Quantum Account Recovery for Passwordless Authentication. Master’s thesis 109 | target: https://hdl.handle.net/10012/19316 110 | author: 111 | - name: Spencer MacLaren Wilson 112 | date: 2023-04-24 113 | 114 | --- abstract 115 | 116 | Hierarchical Deterministic Keys enables managing large sets of keys bound to a secure cryptographic device that protects a single key. This enables the development of secure digital identity wallets providing many one-time-use public keys. Some instantiations can be implemented in such a way that the secure cryptographic device does not need to support key blinding, enabling the use of devices that already are widely deployed. 117 | 118 | --- middle 119 | 120 | # Introduction 121 | 122 | This document specifies the algorithms to apply Hierarchical Deterministic Keys (HDKeys). The purpose of an HDK architecture is to manage large sets of keys bound to a secure cryptographic device that protects a single key. This enables the development of secure digital identity wallets providing many one-time-use public keys. 123 | 124 | The core idea has been introduced in [BIP32] to create multiple cryptocurrency addresses in a manageable way. The present document extends the idea towards devices commonly used for digital wallets, and towards common interaction patterns for document issuance and authentication. 125 | 126 | To store many HDKeys, only a seed string needs to be stored confidentially, associated with a device private key. Each HDK is then deterministically defined by a path of indices, optionally alternated by key handles provided by another party. Such a path can efficiently be stored and requires less confidentiality than the seed. 127 | 128 | To prove possession of many HDKeys, the secure cryptographic device only needs to perform common cryptographic operations on a single private key. The HDK acts as a blinding factor that enables blinding the device public key. In several instantiations, such as those [using ECDH shared secrets](#using-ecdh-shared-secrets) and those [using EC-SDSA signatures](#using-ec-sdsa-signatures), the secure cryptographic device does not need to support key blinding natively, and the application can pre-process the input or post-process the output from the device to compute the blinded device authentication data. This enables the application of HDK on devices that are already deployed without native support for HDK. 129 | 130 | This document provides a specification of the generic HDK function, generic HDK instantiations, and fully specified concrete HDK instantiations. 131 | 132 | An HDK instantiation is expected to be applied in a solution deployed as (wallet) units. One unit can have multiple HDK instantiations, for example to manage multiple identities or multiple cryptographic algorithms or key protection mechanisms. 133 | 134 | This document represents the consensus of the authors, based on working group input and feedback. It is not a standard. It does not include security or privacy proofs. 135 | 136 | ## Conventions and definitions 137 | 138 | {::boilerplate bcp14-tagged} 139 | 140 | ## Notation and terminology 141 | 142 | The following notation is used throughout the document. 143 | 144 | General terms: 145 | 146 | - `I2OSP(n, w)`: Convert non-negative integer `n` to a `w`-length, big-endian byte string, as described in [RFC8017]. 147 | 148 | Terms specific to HDK: 149 | 150 | - HDK context `ctx`: A byte string derived from a public key and an index, used to enforce domain separation in HDK-based key derivation. 151 | - Key encapsulation mechanism (KEM): A cryptographic scheme used in remote HDK derivation to securely exchange a shared secret. 152 | - HDK Salt: A `Ns`-byte value used to introduce entropy into the HDK derivation. Without knowledge of the salt, the derived keys appear unrelated. 153 | - Blind Key `bk`: A scalar that propagates the entropy from the salt to the deterministic key derivation. 154 | - Blinding factor `bf`: A scalar applied to a private-public key pair to produce a blinded version. Obtained by (repeatedly) applying a blind key with application (HDK) context. 155 | - Blinded Private Key `sk_b`: A private key transformed using a blinding factor. 156 | - Blinded Public Key `pk_b`: A public key derived from another public key with a blinding factor to ensure that the derived key appears unrelated to any other key. 157 | - HDK Key Alias Format: A structured identifier representing HDK-derived keys. 158 | 159 | Algorithmic and cryptographic notation: 160 | 161 | - `DeriveBlindKey(ikm)`: Generates a blind key from the input key material `ikm`. Ensures uniform distribution of outputs, and propagates any entropy from `ikm`. 162 | - `DeriveBlindingFactor(bk, ctx)`: Derives a blinding factor from a blind key for a given context. Blinding factors are unlinkable if either the blind key or the context is unknown. 163 | - `BlindPrivateKey(sk, bf)`: Blinds a private key `sk` with the blinding factor `bf` to generate the private key `sk_b`. 164 | - `BlindPublicKey(pk, bf)`: Blinds a public key `pk` with the blinding factor `bf` to generate the public key `pk_b`. 165 | - `Combine(s1, s2)`: Computes a new blinding factor by combining two scalar values s1 and s2. Supports multi-stage derivations and key composability in HDK. 166 | - `SerializePublicKey(pk)`: Encodes the public key `pk` into a canonical byte string representation. 167 | 168 | Security and implementation specific notation: 169 | 170 | - Secure Cryptographic Device (WSCD): A module that securely stores and processes cryptographic keys and provides the hardware root of trust for HDK-based key derivation. 171 | - Trust Evidence: Information providing ownership or authenticity of a derived key. 172 | - Wallet Secure Cryptographic Application (WSCA): A secure application within a WSCD that exposes cryptographic functions used in key derivation. 173 | 174 | # The Hierarchical Deterministic Key function 175 | 176 | An HDK instantiation enables local key derivation to create many key pairs from a single seed value. It enables remote parties to generate key handles from which both parties can derive more key pairs asynchronously. Additionally, an HDK instantiation enables securely proving possession of the private keys, such as required in [RFC7800], either in a centralised or in a distributed manner. 177 | 178 | Solutions MAY omit application of the remote functionality. In this case, a unit can only derive keys locally. 179 | 180 | ## Introductory examples 181 | 182 | ### Local deterministic key derivation 183 | 184 | The following example illustrates the use of local key derivation. An HDK tree is associated with a device key pair and initiated using confidential static data: a `seed` value, which is a byte array containing sufficient entropy. Now tree nodes are constructed as follows. 185 | 186 | ~~~ 187 | +----+ +--+ 188 | Confidential static data: |seed| |pk| 189 | +-+--+ +--+ 190 | v 191 | +----+ +----+ 192 | Level 0 HDKeys: |hdk0| |hdk1| 193 | +-+--+ +----+ 194 | v 195 | +-----+ +-----+ +-----+ 196 | Level 1 HDKeys: |hdk00| |hdk01| |hdk02| 197 | +-----+ ++---++ +-----+ 198 | v v 199 | +------+ +------+ 200 | Level 2 HDKeys at hdk01: |hdk000| |hdk001| ... 201 | +------+ +------+ 202 | ~~~ 203 | 204 | The unit computes a Level 0 HDK at the root node using a deterministic function, taking the device public key `pk` and the `seed` as input: `(pk0, salt0, bf0) = hdk0 = HDK(0, pk, seed)`. The HDK consists of a first blinded public key `pk0`, a first byte string `salt0` to derive next-level keys, and a first blinding factor `bf0`. Using `bf0` and the device key pair, the unit can compute blinded private keys and proofs of possession. 205 | 206 | The unit computes any Level `n > 0` HDK from any other HDK `(pk, salt, bf)` using the same deterministic function: `(pk', salt', bf') = hdk' = HDK(index, pk, salt, bf)`. The function takes as input the `index` starting at 0, an the previous-level HDK. The function returns a new HDK as output, which can be used in the same way as the root HDK. 207 | 208 | ### Remote deterministic key derivation 209 | 210 | Instead of local derivation, an HDK salt can also be derived using a key handle that is generated remotely. Using the derived salt, the local and remote parties can derive the same new HDKeys. The remote party can use these to derive public keys. The local party can use these to derive associated private keys for proof of possession. 211 | 212 | This approach is similar to Asynchronous Remote Key Generation (ARKG) [I-D.draft-bradleylundberg-cfrg-arkg-02] when considered at a single level. However, ARKG does not enable distributed proof of possession with deterministic hierarchies. Such hierarchies can be used for example to enable remote parties to derive keys from previously derived keys. Secure cryptographic devices that support ARKG may therefore not support all features of HDK. 213 | 214 | To enable remote derivation of child HDKeys, the unit uses the parent HDK to derive the parent public key and a second public key for key encapsulation. The issuer returns a key handle, using which both parties can derive a sequence of child HDKeys. Key encapsulation prevents other parties from discovering a link between the public keys of the parent and the children, even if the other party knows the parent HDK or can eavesdrop communications. 215 | 216 | Locally derived parents can have remotely derived children. Remotely derived parents can have locally derived children. 217 | 218 | ### Blinded proof of possession 219 | 220 | The next concept to illustrate is blinded proof of possession. This enables a unit to prove possession of a (device) private key without disclosing the directly associated public key. This way, solutions can avoid linkability across readers of a digital document that is released with proof of possession. 221 | 222 | In this example, a document is issued with binding to a public key `pk'`, which is a blinding public key `pk` blinded with the blinding factor `bf` in some HDK `hdk = (pk', salt, bf)`. The unit can present the document with a proof of possession of the corresponding blinded private key, which is the blinding private key `sk` blinded with `bf`. The unit applies some authentication function `device_data = authenticate(sk, reader_data, bf)` to the blinding private key, reader-provided data and the blinding factor. The unit can subsequently use the output `device_data` to prove possession to the reader using common algorithms. 223 | 224 | ~~~ 225 | +------------------+ +--------+ 226 | | +--+ +---+ | | | 227 | | Unit |sk| |hdk| | | Reader | 228 | | +--+ +---+ | | | 229 | +---+--------------+ +----+---+ 230 | | | 231 | | | 232 | | 1. Request and | 233 | | reader_data | 234 | | <------------------ | 235 | | | 236 | +---+-------------+ | 237 | | 2. authenticate | | 238 | +---+-------------+ | 239 | | | 240 | | 3. Proof with | 241 | | device_data | 242 | | ------------------> | 243 | | | 244 | | +-----------+ | 245 | | | Document | | 246 | | | | | 247 | | | +---+ | | 248 | | | |pk'| | | 249 | | | +---+ | | 250 | | | | | 251 | +-----------+ 252 | ~~~ 253 | 254 | The reader does not need to be aware that an HDK function or key blinding was used, since for common algorithms, the blinded public key and the proof are indistinguishable from non-blinded keys and proofs. 255 | 256 | When applied on HDK level `n`, the blinding private key `sk` is the device private key blinded with a combination of `n` blinding factors. These can either be combined within the secure cryptographic device, by subsequent computation of the blinded private key starting with the device private key, or outside of the secure cryptographic device, by combining the blinding factors outside of the secure cryptographic device. 257 | 258 | Blinding methods can be constructed such that the secure cryptographic device does not need to be designed for key blinding. In such cases, the computation of `device_data` is distributed between two parties: the secure cryptographic device using common cryptographic operations, and the unit component invoking these operations. Some blinded proof of possession algorithms can only be centralised. 259 | 260 | ## Instantiation parameters 261 | 262 | The parameters of an HDK instantiation are: 263 | 264 | - `Ns`: The amount of bytes of a salt value with sufficient entropy. 265 | - `H`: A cryptographic hash function. 266 | - H(msg): Outputs `Ns` bytes. 267 | - `BL`: A key blinding scheme [Wilson2023] with opaque blinding factors and algebraic properties, consisting of the functions: 268 | - DeriveBlindKey(ikm): Outputs a blind key `bk` based on input keying material `ikm`. 269 | - BlindPublicKey(pk, bk, ctx): Outputs the result public key `pk'` of blinding public key `pk` with blind key `bk` and application context byte string `ctx`. 270 | - BlindPrivateKey(sk, bk, ctx): Outputs the result private key `sk'` of blinding private key `sk` with blind key `bk` and application context byte string `ctx`. The result `sk'` is such that if `pk` is the public key for `sk`, then `(sk', pk')` forms a key pair for `pk' = BlindPublicKey(pk, bk, ctx)`. 271 | - Combine(k1, k2): Outputs a blinding factor `bf` given input keys `k1` and `k2` which are either private keys or blinding factors, with the following associative property. For all input keys `k1`, `k2`, `k3`: 272 | 273 | ~~~ 274 | Combine(Combine(k1, k2), k3) == Combine(k1, Combine(k2, k3)) 275 | ~~~ 276 | - DeriveBlindingFactor(bk, ctx): Outputs a blinding factor `bf` based on a blind key `bk` and an application context byte string `ctx`, such that for all private keys `sk`: 277 | 278 | ~~~ 279 | BlindPrivateKey(sk, bk, ctx) == Combine(sk, bf) 280 | ~~~ 281 | - SerializePublicKey(pk): Outputs a canonical byte string serialisation of public key `pk`. 282 | - `KEM`: A key encapsulation mechanism [RFC9180], consisting of the functions: 283 | - DeriveKeyPair(ikm): Outputs a key encapsulation key pair `(sk, pk)`. 284 | - Encap(pk): Outputs `(k, c)` consisting of a shared secret `k` and a ciphertext `c`, taking key encapsulation public key `pk`. 285 | - Decap(c, sk): Outputs shared secret `k`, taking ciphertext `c` and key encapsulation private key `sk`. 286 | 287 | An HDK instantiation MUST specify the instantiation of each of the above functions and values. 288 | 289 | Note that by design of BL, when a document is issued using HDK, the reader does not need to know that HDK was applied: the public key will look like any other public key used for proofs of possession. 290 | 291 | An HDK implementation MAY leave BlindPrivateKey implicit in cases where the blinding method is constructed in a distributed way. In those cases, the secure cryptographic device holding the private key does not need to support key blinding, and the value of the blinded private key is never available during computation. 292 | 293 | ## The HDK context 294 | 295 | A local unit or remote party creates an HDK context from an index. 296 | 297 | ~~~ 298 | Inputs: 299 | - pk, a public key to be blinded. 300 | - index, an integer between 0 and 2^32-1 (inclusive). 301 | 302 | Outputs: 303 | - ctx, an application context byte string. 304 | 305 | def CreateContext(pk, index): 306 | ctx = SerializePublicKey(pk) || I2OSP(index, 4) 307 | return ctx 308 | ~~~ 309 | 310 | This context byte string is used as input for DeriveBlindingFactor, BlindPrivateKey, BlindPublicKey, and [DeriveSalt](#the-hdk-salt). 311 | 312 | ## The HDK salt 313 | 314 | A local unit or remote party derives a next-level HDK salt from within an HDK context. 315 | 316 | ~~~ 317 | Inputs: 318 | - salt, a string of Ns bytes. 319 | - ctx, an HDK context byte string. 320 | 321 | Outputs: 322 | - salt', the next salt for HDK derivation. 323 | 324 | def DeriveSalt(salt, ctx): 325 | salt' = H(salt || ctx) 326 | return salt' 327 | ~~~ 328 | 329 | Salt values are used as input for DeriveBlindKey, DeriveKeyPair, and DeriveSalt. 330 | 331 | Salt values, including the original seed value, MUST NOT be reused outside of HDK. 332 | 333 | ## The HDK function 334 | 335 | A local unit or a remote party deterministically computes an HDK from an index, a parent public key, a salt, and an optional parent blinding factor. The salt can be an initial seed value of `Ns` bytes or it can be taken from another parent HDK. The secure generation of the seed is out of scope for this specification. 336 | 337 | ~~~ 338 | Inputs: 339 | - index, an integer between 0 and 2^32-1 (inclusive). 340 | - pk, a public key to be blinded. 341 | - salt, a string of Ns bytes. 342 | - bf, a blinding factor to combine with, if any, Nil otherwise. 343 | - skD, a private key to be blinded, if known, Nil otherwise. 344 | 345 | Outputs: 346 | - pk', the blinded public key at the provided index. 347 | - salt', the salt for HDK derivation at the provided index. 348 | - bf', the blinding factor at the provided index. 349 | - bk, the current blind key. 350 | - ctx, the current key blinding application context byte string. 351 | - sk', the blinded private key. 352 | 353 | def HDK(index, pk, salt, bf = Nil, skD = Nil): 354 | ctx = CreateContext(pk, index) 355 | salt' = DeriveSalt(salt, ctx) 356 | 357 | bk = DeriveBlindKey(salt) 358 | pk' = BlindPublicKey(pk, bk, ctx) 359 | sk' = if skD == Nil: Nil 360 | elif bf == Nil: BlindPrivateKey(skD, bk, ctx) 361 | else : BlindPrivateKey(Combine(skD, bf), bk, ctx) 362 | bf' = if bf == Nil: DeriveBlindingFactor(bk, ctx) 363 | else : Combine(bf, DeriveBlindingFactor(bk, ctx)) 364 | 365 | return ((pk', salt', bf'), (bk, ctx), sk') 366 | ~~~ 367 | 368 | A unit MUST NOT persist a blinded private key. Instead, if persistence is needed, a unit can persist either the blinding factor of each HDK, or a path consisting of the seed salt, indices and key handles. In both cases, the application of Combine in the HDK function enables reconstruction of the blinding factor with respect to the original private key, enabling application of for example BlindPrivateKey. 369 | 370 | If the unit uses the blinded private key directly, the unit MUST use it within the secure cryptographic device protecting the device private key. 371 | 372 | If the unit uses the blinded private key directly, the unit MUST ensure the secure cryptographic device deletes it securely from memory after usage. 373 | 374 | When presenting multiple documents, a reader can require a proof that multiple keys are associated to a single device. Several protocols for a cryptographic proof of association are possible, such as [Verheul2024]. For example, a unit could prove in a zero-knowledge protocol knowledge of the association between two elliptic curve keys `B1 = [bf1]D` and `B2 = [bf2]D`, where `bf1` and `bf2` are multiplicative blinding factors for a common blinding public key `D`. In this protocol, the association is known by the discrete logarithm of `B2 = [bf2/bf1]B1` with respect to generator `B1`. The unit can apply Combine to obtain values to compute this association. 375 | 376 | ## The local HDK procedure 377 | 378 | This is a procedure executed locally by a unit. 379 | 380 | To begin, the unit securely generates a `seed` salt of `Ns` bytes and a device key pair: 381 | 382 | ~~~ 383 | seed = random(Ns) # specification of random out of scope 384 | (skD, pkD) = GenerateKeyPair() 385 | ~~~ 386 | 387 | The unit MUST generate `skD` within a secure cryptographic device. 388 | 389 | Whenever the unit requires the HDK with some `index` at level 0, the unit computes: 390 | 391 | ~~~ 392 | ((pk, salt, bf), (bk, ctx), sk) = HDK(index, pkD, seed, Nil, sk) 393 | ~~~ 394 | 395 | Now the unit can use the blinded key pair `(sk, pk)` or derive child HDKeys. 396 | 397 | Whenever the unit requires the HDK with some `index` at level `n > 0` based on a parent HDK `(pk, salt, bf)` with blinded key pair `(sk, pk)` at level `n`, the unit computes: 398 | 399 | ~~~ 400 | ((pk', salt', bf'), (bk, ctx), sk') = HDK(index, pk, salt, bf, sk) 401 | ~~~ 402 | 403 | Now the unit can use the blinded key pair `(sk', pk')` or derive child HDKeys. 404 | 405 | Note that providing `sk` is optional. Alternatively, the unit can use the returned `bk` and `ctx` with the parent `bf` separately in a key blinding scheme, for example using: 406 | 407 | ~~~ 408 | sk' = BlindPrivateKey(Combine(sk, bf), bk, ctx) 409 | ~~~ 410 | 411 | ## The remote HDK protocol 412 | 413 | This is a protocol between a local unit and a remote issuer. 414 | 415 | As a prerequisite, the unit possesses a `salt` of `Ns` bytes associated with a parent key pair `(sk, pk)` with blinding factor `bf` (potentially `Nil`) generated using the local HDK procedure. 416 | 417 | ~~~ 418 | # 1. Unit computes: 419 | (skR, pkR) = DeriveKeyPair(salt) 420 | 421 | # 2. Unit shares with issuer: (pk, pkR) 422 | 423 | # 3. Issuer computes: 424 | (salt_kem, kh) = Encap(pkR) 425 | 426 | # 4. Issuer shares with unit: kh 427 | 428 | # Subsequently, for any index known to both parties: 429 | 430 | # 5. Issuer computes: 431 | ((pk', salt', bf'), _, _) = HDK(index, pk, salt_kem) 432 | 433 | # 6. Issuer shares with unit: pkA = pk' 434 | 435 | # 7. Unit verifies integrity: 436 | salt_kem = Decap(kh, skR) 437 | ((pk', salt', bf'), (bk, ctx), _) = HDK(index, pk, salt_kem, bf) 438 | pk' == pkA 439 | 440 | # 8. Unit computes: 441 | sk' = BlindPrivateKey(Combine(sk, bf), bk, ctx) # optional 442 | ~~~ 443 | 444 | After step 7, the unit can use the value of `salt'` to derive next-level HDKeys. 445 | 446 | Step 4 MAY be postponed to be combined with step 6. Steps 5 to 8 MAY be combined in concurrent execution for multiple indices. 447 | 448 | ## The HDK key alias format 449 | 450 | An HDK can be represented canonically using the following string format, in augmented Backus-Naur form (ABNF) [RFC5234] and applying non-padded base64url encoding [RFC4648] for key handles: 451 | 452 | ~~~ 453 | hdk-key-alias = origin-alias "/" path 454 | 455 | ; The origin-alias is an opaque identifier for a device 456 | ; key pair, the associated HDK instantiation, and the seed. 457 | origin-alias = 1*255no-slash 458 | 459 | ; The hdk-path identifies the indices and key handles to 460 | ; apply from left to right. 461 | hdk-path = hdk-index *("/" hdk-sub-path) 462 | 463 | hdk-sub-path = *(hdk-edge "/") hdk-index 464 | hdk-edge = ("#" hdk-key-handle) / hdk-index 465 | 466 | ; The index is to be parsed to an integer between 0 and 467 | ; 2^32-1 (inclusive) and used as input to CreateContext. 468 | hdk-index = non-zero-digit 0*9DIGIT 469 | 470 | ; The key handle is to be decoded from 471 | hdk-key-handle = 1*base64url-char 472 | 473 | no-slash = %x21-%x2E / %x30-%x7E ; ASCII printable, no "/" 474 | non-zero-digit = %31-39 475 | base64url-char = ALPHA / DIGIT / "-" / "_" 476 | ~~~ 477 | 478 | A unit MAY use the HDK key alias format to represent keys internally. 479 | 480 | A unit MUST NOT directly include the device private key in the `origin-alias`. 481 | 482 | A unit MUST NOT directly include the seed in the `origin-alias`. 483 | 484 | When taking input in the HDK key alias format: 485 | 486 | - a unit MAY pose further limitations on the value of `origin-alias`; 487 | - a unit MUST limit either the amount of `hdk-edge` instances or the total length of the `hdk-key-alias`; 488 | - a unit MUST verify that the byte strings represented by `hdk-key-handle` has the size of ciphertext in `KEM`. 489 | 490 | Example key handles: 491 | 492 | ~~~ 493 | my_pid_key/0 494 | 495 | my_pid_key/12345 496 | 497 | my_pid_key/0/iS2ipkvGCDI0-Lps25Ex2KdjTfGRmIBjGEHkjBCPoQg/3 498 | 499 | ; newline for printing purposes not in the actual hdk-path 500 | second_key/123/45/itnCVhZ-DYZDaUqofDNhHEbNc9XOrdnLL9B-9dVZ 501 | tTg/6789/3JVRsML8NvUnCx1CvzpZrHSn4TkSUpGgn8r-X_RiQ1Y/3 502 | ~~~ 503 | 504 | # Generic HDK instantiations 505 | 506 | ## Using digital signatures 507 | 508 | Instantiations of HDK using digital signatures require: 509 | 510 | - `DSA`: A digital signature algorithm, consisting of the functions: 511 | - GenerateKeyPair(): Outputs a new key pair `(sk, pk)` consisting of private key `sk` and public key `pk`. 512 | - Sign(sk, msg): Outputs the signature created using private signing key `sk` over byte string `msg`. 513 | - Verify(signature, pk, msg): Outputs whether `signature` is a signature over `msg` using public verification key `pk`. 514 | 515 | Using these constructs, an example proof of possession protocol is: 516 | 517 | ~~~ 518 | # 1. Unit shares with reader: pk 519 | 520 | # 2. Reader computes: 521 | nonce = generate_random_nonce() # out of scope for this spec 522 | 523 | # 3. Reader shares with unit: nonce 524 | 525 | # 4. Unit computes: 526 | msg = create_message(pk, nonce) # out of scope for this spec 527 | signature = Sign(sk, msg) 528 | 529 | # 5. Reader computes: 530 | msg = create_message(pk, nonce) # out of scope for this spec 531 | Verify(signature, pk, msg) 532 | ~~~ 533 | 534 | Instantiations of HDK using digital signatures provide: 535 | 536 | - `BL`: A cryptographic construct that extends `DSA` as specified in [I-D.draft-irtf-cfrg-signature-key-blinding-07], implementing the interface from [Instantiation parameters](#instantiation-parameters), as well as: 537 | - BlindKeySign(sk, bk, ctx, msg): Outputs the result of signing a message `msg` using the private key `sk` with the private blind key `bk` and application context byte string `ctx` such that for key pair `(sk, pk)`: 538 | 539 | ~~~ 540 | Verify( BlindKeySign(sk, bk, ctx, msg), 541 | BlindPublicKey(pk, bk, ctx)) == 1 542 | ~~~ 543 | 544 | By design of `BL`, the same proof of possession protocol can be used with blinded key pairs and BlindKeySign, in such a way that the reader does not recognise that key blinding was used. 545 | 546 | In the default implementation, BlindKeySign requires support from the secure cryptographic device protecting `sk`: 547 | 548 | ~~~ 549 | def BlindKeySign(sk, bk, ctx, msg): 550 | sk' = BlindPrivateKey(sk, bk, ctx) 551 | signature = Sign(sk', msg) 552 | return signature 553 | ~~~ 554 | 555 | In some cases, BlindKeySign can be implemented in an alternative, distributed way. An example will be provided for [using EC-SDSA signatures](#using-ec-sdsa-signatures). 556 | 557 | Applications MUST bind the message to be signed to the blinded public key. This mitigates attacks based on signature malleability. Several proof of possession protocols require including document data in the message, which includes the blinded public key indeed. 558 | 559 | ## Using prime-order groups 560 | 561 | Instantiations of HDK using prime-order groups require: 562 | 563 | - `G`: A prime-order group as defined in [RFC9497] with elements of type Element and scalars of type Scalar, consisting of the functions: 564 | - RandomScalar(): Outputs a random Scalar `k`. 565 | - Add(A, B): Outputs the sum between Elements `A` and `B`. 566 | - ScalarMult(A, k): Outputs the scalar multiplication between Element `A` and Scalar `k`. 567 | - ScalarBaseMult(k): Outputs the scalar multiplication between the base Element and Scalar `k`. 568 | - Order(): Outputs the order of the base Element. 569 | - SerializeScalar(k): Outputs a byte string representing Scalar `k`.` 570 | - HashToScalar(msg): Outputs the result of deterministically mapping a byte string `msg` to an element in the scalar field of the prime order subgroup of `G`, using the `hash_to_field` function from a hash-to-curve suite [RFC9380]. 571 | 572 | Instantiations of HDK using prime-order groups provide: 573 | 574 | ~~~ 575 | def GenerateKeyPair(): 576 | sk = GenerateRandomScalar() 577 | pk = ScalarBaseMult(sk) 578 | return (sk, pk) 579 | 580 | def DeriveBlindKey(ikm): 581 | bk_scalar = HashToScalar(ikm) 582 | bk = SerializeScalar(bk_scalar) 583 | return bk 584 | 585 | def DeriveBlindingFactor(bk, ctx): 586 | msg = bk || 0x00 || ctx 587 | bf = HashToScalar(msg) 588 | return bf 589 | ~~~ 590 | 591 | Note that DeriveBlindKey and DeriveBlindingFactor are compatible with the definitions in [I-D.draft-irtf-cfrg-signature-key-blinding-07]. We illustrate also what would be needed instead for full compatibility with [I-D.draft-bradleylundberg-cfrg-arkg-02] below and when [Using elliptic curves](#using-elliptic-curves): 592 | 593 | ~~~ 594 | def DeriveBlindKey_ARKG(ikm): 595 | # There is no need for additional processing, 596 | # since bk is in ARKG as intermediate input 597 | # for a pseudo-random function only. 598 | bk = ikm 599 | return bk 600 | ~~~ 601 | 602 | ### Using additive blinding 603 | 604 | Instantiations of HDK using additive blinding use: 605 | 606 | - [prime-order groups](#using-prime-order-groups) 607 | 608 | Instantiations of HDK using additive blinding provide: 609 | 610 | ~~~ 611 | def BlindPublicKey(pk, bk, ctx): 612 | bf = DeriveBlindingFactor(bk, ctx) 613 | pk' = Add(pk, ScalarBaseMult(bf)) 614 | return pk 615 | 616 | def BlindPrivateKey(sk, bk, ctx): 617 | bf = DeriveBlindingFactor(bk, ctx) 618 | sk' = sk + bf mod Order() 619 | if sk' == 0: abort with an error 620 | return sk 621 | 622 | def Combine(bf1, bf2): 623 | bf = bf1 + bf2 mod Order() 624 | return bf 625 | ~~~ 626 | 627 | Note that all algorithms in [I-D.draft-bradleylundberg-cfrg-arkg-02] use additive blinding. 628 | 629 | ### Using multiplicative blinding 630 | 631 | Instantiations of HDK using multiplicative blinding use: 632 | 633 | - [prime-order groups](#using-prime-order-groups) 634 | 635 | Instantiations of HDK using multiplicative blinding provide: 636 | 637 | ~~~ 638 | def BlindPublicKey(pk, bk, ctx): 639 | bf = DeriveBlindingFactor(bk, ctx) 640 | pk' = ScalarMult(pk, bf) 641 | return pk 642 | 643 | def BlindPrivateKey(sk, bk, ctx): 644 | bf = DeriveBlindingFactor(bk, ctx) 645 | sk' = sk * bf mod Order() 646 | if sk' == 1: abort with an error 647 | return sk 648 | 649 | def Combine(bf1, bf2): 650 | bf = bf1 * bf2 mod Order() 651 | return bf 652 | ~~~ 653 | 654 | Note that all algorithms in [I-D.draft-irtf-cfrg-signature-key-blinding-07] use multiplicative blinding. 655 | 656 | ## Using elliptic curves 657 | 658 | Instantiations of HDK using elliptic curves use: 659 | 660 | - [prime-order groups](#using-prime-order-groups) 661 | 662 | Instantiations of HDK using elliptic curves require: 663 | 664 | - `DST`: A domain separation tag for use with HashToScalar. 665 | - `H2C`: A hash-to-curve suite [RFC9380]. 666 | 667 | Instantiations of HDK using elliptic curves provide: 668 | 669 | - `H`: `H` from `H2C`. 670 | - `Ns`: The output size of `H`. 671 | 672 | ~~~ 673 | def HashToScalar(msg): 674 | scalar = hash_to_field(msg, 1) with the parameters: 675 | DST: DST 676 | F: GF(Order()), the scalar field 677 | of the prime order subgroup of EC 678 | p: Order() 679 | m: 1 680 | L: as defined in H2C 681 | expand_message: as defined in H2C 682 | return scalar 683 | ~~~ 684 | 685 | We illustrate also what would be needed instead for full compatibility with [I-D.draft-bradleylundberg-cfrg-arkg-02] below: 686 | 687 | ~~~ 688 | def DeriveBlindingFactor_ARKG(bk, ctx): 689 | bf = HashToScalar_ARKG(msg, ctx) 690 | return bf 691 | 692 | def HashToScalar_ARKG(msg, info): 693 | scalar = hash_to_field(msg, 1) with the parameters: 694 | DST: DST || info 695 | F: GF(Order()), the scalar field 696 | of the prime order subgroup of EC 697 | p: Order() 698 | m: 1 699 | L: as defined in H2C 700 | expand_message: as defined in H2C 701 | return scalar 702 | ~~~ 703 | 704 | ### Using ECDH shared secrets 705 | 706 | Instantiations of HDK using ECDH shared secrets use: 707 | 708 | - [elliptic curves](#using-elliptic-curves) 709 | - [multiplicative blinding](#using-multiplicative-blinding) 710 | 711 | Instantiations of HDK using ECDH shared secrets provide: 712 | 713 | - `DH`: The Elliptic Curve Key Agreement Algorithm - Diffie-Hellman (ECKA-DH) [TR03111] with elliptic curve `G`, consisting of the functions: 714 | - CreateSharedSecret(skX, pkY): Outputs a shared secret byte string `Z_AB` representing the x-coordinate of the Element `ScalarMult(pkY, skX)`. 715 | 716 | Note that DH enables an alternative way of authenticating a key pair `(sk, pk)` without creation or verification of a signature: 717 | 718 | ~~~ 719 | # 1. Unit shares with reader: pk 720 | 721 | # 2. Reader computes: 722 | (skR, pkR) = GenerateKeyPair() 723 | 724 | # 3. Reader shares with unit: pkR 725 | 726 | # 4. Unit computes: 727 | Z_AB = CreateSharedSecret(sk, pkR) 728 | 729 | # 5. Reader computes: 730 | Z_AB = CreateSharedSecret(skR, pk) 731 | ~~~ 732 | 733 | Now with the shared secret `Z_AB`, the unit and the reader can compute a secret shared key. The unit can convince the reader that it possesses `sk` for example by sharing a message authentication code created using this key. The reader can verify this by recomputing the code using its value of `Z_AB`. This is for example used in ECDH-MAC authentication defined in [ISO18013-5]. 734 | 735 | In this example, step 1 can be postponed in the interactions between the unit and the reader if a trustworthy earlier commitment to `pk` is available, for example in a sealed document. 736 | 737 | Similarly, ECDH enables authentication of key pair `(sk', pk')` blinded from an original key pair `(sk, pk)` using a blind key `ctx` and application context byte string `ctx` such that: 738 | 739 | ~~~ 740 | bf = DeriveBlindingFactor(bk, ctx) 741 | sk' = BlindPrivateKey(sk, bk, ctx) 742 | = sk * bf mod Order() 743 | pk' = ScalarMult(pk, bf) 744 | ~~~ 745 | 746 | In this case, the computation in step 4 can be performed as such: 747 | 748 | ~~~ 749 | # 4. Unit computes: 750 | Z_AB = CreateSharedSecret(sk', pkR) 751 | = CreateSharedSecret(sk * bf mod Order(), pkR) 752 | = CreateSharedSecret(sk, ScalarMult(pkR, bf)) 753 | ~~~ 754 | 755 | Note that the value of `ScalarMult(pkR, bf)` does not need to be computed within the secure cryptographic device that protects `sk`. 756 | 757 | ### Using EC-SDSA signatures 758 | 759 | Instantiations of HDK using EC-SDSA (Schnorr) signatures use: 760 | 761 | - [additive blinding](#using-additive-blinding) 762 | - [digital signatures](#using-digital-signatures) 763 | - [elliptic curves](#using-elliptic-curves) 764 | 765 | Instantiations of HDK using EC-SDSA signatures provide: 766 | 767 | - `DSA`: An EC-SDSA digital signature algorithm [TR03111], representing signatures as pairs `(c, s)`. 768 | 769 | Note that in this case, the following definition is equivalent to the [original definition of BlindKeySign](#using-digital-signatures): 770 | 771 | ~~~ 772 | def BlindKeySign(sk, bk, ctx, msg): 773 | # Compute signature within the secure cryptographic device. 774 | (c, s) = Sign(sk, msg) 775 | 776 | # Post-process the signature outside of this device. 777 | bf = DeriveBlindingFactor(bk, ctx) 778 | s' = s + c * bf mod Order() 779 | 780 | signature = (c, s') 781 | return signature 782 | ~~~ 783 | 784 | ### Using P-256 785 | 786 | Instantiations of HDK using P-256 use: 787 | 788 | - [elliptic curves](#using-elliptic-curves) 789 | 790 | Instantiations of HDK using P-256 provide: 791 | 792 | - `G`: The NIST curve `secp256r1` (P-256) [SEC2]. 793 | - `H2C`: P256_XMD:SHA-256_SSWU_RO_ [RFC9380], which uses SHA-256 [FIPS180-4] as `H`. 794 | - `KEM`: DHKEM(P-256, HKDF-SHA256) [RFC9180]. 795 | 796 | # Concrete HDK instantiations 797 | 798 | The RECOMMENDED instantiation is the HDK-ECDH-P256. This avoids the risk of having the holder unknowingly producing a potentially non-repudiable signature over reader-provided data. Secure cryptographic devices that enable a high level of assurance typically support managing ECDH keys with the P-256 elliptic curve. 799 | 800 | ## HDK-ECDH-P256 801 | 802 | The HDK-ECDH-P256 instantiation of HDK uses: 803 | 804 | - [P-256](#using-p-256) 805 | - [ECDH shared secrets](#using-ecdh-shared-secrets) 806 | 807 | The HDK-ECDH-P256 instantiation defines: 808 | 809 | - `DST`: `"ECDH Key Blind"` 810 | 811 | ## HDK-ECDSA-P256add 812 | 813 | The HDK-ECDSA-P256add instantiation of HDK uses: 814 | 815 | - [digital signatures](#using-digital-signatures) 816 | - [P-256](#using-p-256) 817 | - [additive blinding](#using-additive-blinding) 818 | 819 | The HDK-ECDSA-P256add instantiation of HDK defines: 820 | 821 | - `DST`: `"ARKG-BL-EC.ARKG-P256ADD-ECDH"` for interoperability with [I-D.draft-bradleylundberg-cfrg-arkg-02]. 822 | - `DSA`: ECDSA [TR03111] with curve `G`. 823 | 824 | ## HDK-ECDSA-P256mul 825 | 826 | The HDK-ECDSA-P256mul instantiation of HDK uses: 827 | 828 | - [digital signatures](#using-digital-signatures) 829 | - [P-256](#using-p-256) 830 | - [multiplicative blinding](#using-multiplicative-blinding) 831 | 832 | The HDK-ECDSA-P256mul instantiation of HDK defines: 833 | 834 | - `DST`: `"ECDSA Key Blind"` for interoperability with [I-D.draft-irtf-cfrg-signature-key-blinding-07]. 835 | - `DSA`: ECDSA [TR03111] with curve `G`. 836 | 837 | ## HDK-ECSDSA-P256 838 | 839 | The HDK-ECSDSA-P256 instantiation of HDK uses: 840 | 841 | - [EC-SDSA signatures](#using-ec-sdsa-signatures) 842 | - [P-256](#using-p-256) 843 | 844 | The HDK-ECSDSA-P256 instantiation of HDK defines: 845 | 846 | - `DST`: `"EC-SDSA Key Blind"` 847 | 848 | # Application considerations 849 | 850 | ## Secure cryptographic device 851 | 852 | The HDK approach assumes that the holder controls a secure cryptographic device that protects the device key pair `(sk_device, pk_device)`. The device key is under sole control of the holder. 853 | 854 | In the context of [EU2024-1183], this device is typically called a Wallet Secure Cryptographic Device (WSCD), running a personalised Wallet Secure Cryptographic Application (WSCA) that exposes a Secure Cryptographic Interface (SCI) to a Wallet Instance (WI) running on a User Device (UD). The WSCD is certified to protect access to the device private key with high attack potential resistance to achieve high level of assurance authentication as per [EU2015-1502]. This typically means that the key is associated with a strong possession factor and with a rate-limited Personal Identification Number (PIN) check as a knowledge factor, and the verification of both factors actively involve the WSCD. 855 | 856 | An example deployment of HDK in this context is illustrated below. 857 | 858 | ~~~ 859 | +---------------------+ +----------------------+ 860 | |Issuer infrastructure| |User Device (UD) | 861 | | | | | 862 | |+-------------------+|OpenID4VCI|+--------------------+| 863 | ||Issuer service |<----------++Wallet Instance (WI)|| 864 | || || |++-------------------+| 865 | ||Optionally an || +-+--------------------+ 866 | ||ARKG subordinate || |Secure 867 | ||party || |Cryptographic 868 | |+-------------------+| |Interface (SCI) 869 | +---------------------+ +v-------------------+ 870 | |Wallet Secure | 871 | |Cryptographic | 872 | Internal Manages |Application (WSCA) | 873 | registry <-----------+ | 874 | |Optionally an | 875 | |ARKG delegating | 876 | |party | 877 | ++-------------------+ 878 | |Uses 879 | +v-------------------+ 880 | Protects |Wallet secure | 881 | Device keys <-----------+cryptographic | 882 | |device (WSCD) | 883 | +--------------------+ 884 | ~~~ 885 | 886 | The WSCA could be a single program or could be deployed in a distributed architecture, as illustrated below. 887 | 888 | ~~~ 889 | +--------------+ 890 | |User device | 891 | |+------------+| 892 | ||WI || 893 | |++-----------+| 894 | | |SCI | 895 | |+v-----------+| 896 | ||WSCA agent || 897 | |++-----------+| 898 | +-+------------+ 899 | |WSCA protocol 900 | +v-----------+ 901 | |WSCA service| 902 | +------------+ 903 | ~~~ 904 | 905 | In the case of a distributed WSCA, the UD contains a local component, here called WSCA agent, accessing an external and possibly remote WSCA service from one or more components over a WSCA protocol. For example, the WSCA agent may be a local web API client and the WSCA service may be provided at a remote web API server. In such cases, typically the WSCA service receives a high-assurance security evaluation, while the WSCA agent is assessed to not be able to compromise the system's security guarantees. 906 | 907 | The internal registry can be managed by the WSCA agent, by the WSCA service, or by the combination. When the user device is a natural person’s mobile phone, WSCA agent management could provide better confidentiality protection against compromised WSCA service providers. When the user device is a cloud server used by a legal person, and the legal person deploys its own WSCD, WSCA service management could provide better confidentiality protection against compromised Wallet Instance cloud providers. 908 | 909 | In a distributed WSCA architecture, the WSCA could internally apply distributed key generation. A description of this is out of scope for the current document. 910 | 911 | The solution proposal discussed herein works in all any WSCD architecture that supports the required cryptographic primitives: 912 | 913 | - In the case of HDK-ECDH-P256 (see [HDK-ECDH-P256](#hdk-ecdh-p256)): 914 | - P-256 ECDH key pair generation 915 | - P-256 ECDH key agreement 916 | - In the case of HDK-ECDSA-P256mul (see [HDK-ECDSA-P256mul](#hdk-ecdsa-p256mul)): 917 | - P-256 ECDSA blinding key pair generation 918 | - P-256 ECDSA blinded signature creation 919 | - In the case of HDK-ECSDSA-P256 (see [HDK-ECSDSA-P256](#hdk-ecsdsa-p256)): 920 | - P-256 EC-SDSA key pair generation 921 | - P-256 EC-SDSA signature creation 922 | 923 | The other HDK operations can be performed in a WSCA or WSCA agent running on any UD, including hostile ones with limited sandboxing capabilities, such as in a smartphone's rich execution environment or in a personal computer web browser. 924 | 925 | ## Trust evidence 926 | 927 | Some issuers could require evidence from a solution provider of the security of the holder's cryptographic device. This evidence can in the context of [EU2024-1183] be divided into initial "Wallet Trust Evidence" and related "Issuer Trust Evidence". Each is a protected document that contains a trust evidence public key associated with a private key that is protected in the secure cryptographic device. With HDK, these public keys are specified as follows. 928 | 929 | ### Wallet Trust Evidence 930 | 931 | The Wallet Trust Evidence public key is the first level 0 HDK public key. To achieve reader unlinkability, the wallet SHOULD limit access to a trusted person identification document provider only. 932 | 933 | To prevent association across identities, the solution provider MUST before issuing Wallet Trust Evidence ensure that (a) a newly generated device key pair is used and (b) the wallet follows the protocol so that the HDK output is bound to exactly this key. For (a), the solution provider could rely on freshness of a key attestation and ensure that each device public key is attested only once. For (b), the wallet could proof knowledge of the blinding factor `bf` with a Schnorr non-interactive zero-knowledge proof [RFC8235] with base point `pk_device`. This would ensure that the root blinding key `bf` is not shared with the solution provider to reduce the risk of the solution provider unblinding future derived keys. 934 | 935 | ### Issuer Trust Evidence 936 | 937 | The Issuer Trust Evidence public key can be any other HDK public key. The solution provider MUST verify that the wallet knows the associated private key before issuing Issuer Trust Evidence. The solution provider MUST ensure that `sk_device` is under sole control of the unit holder. To achieve reader unlinkability, the unit MUST limit access of Issuer Trust Evidence to a single issuer. Subsequent issuers within the same HDK tree do not need to receive any Issuer Trust Evidence, since they can derive equally secure keys by applying the remote HDK protocol to presented keys attested by trusted (other) issuers. 938 | 939 | ## Applying HDK in OpenID for Verifiable Credential Issuance 940 | 941 | In [draft-OpenID4VCI], the following terminology applies: 942 | 943 | | OpenID4VCI | HDK | 944 | | ----------------- | ----------------- | 945 | | Credential | document | 946 | | Credential Issuer | issuer | 947 | | Verifier | reader | 948 | | Wallet | unit | 949 | 950 | HDK enables unit and issuers cooperatively to establish the cryptographic key material that issued documents will be bound to. 951 | 952 | For the remote HDK protocol, HDK proposes an update to the OpenID4VCI endpoints. This proposal is under discussion in [openid/OpenID4VCI#359](https://github.com/openid/OpenID4VCI/issues/359). In the update, the unit shares a key encapsulation public key with the issuer, and the issuer returns a key handle. Then documents can be re-issued, potentially in batches, using synchronised indices. Alternatively, re-issued documents can have their own key handles. 953 | 954 | ## Applying HDK with ARKG 955 | 956 | This section illustrates how an Asynchronous Remote Key Generation (ARKG) instance can be constructed using the interfaces from the current document. It is not fully compatible with [I-D.draft-bradleylundberg-cfrg-arkg-02] due to subtle differences, such as those in [Using prime-order groups](#using-prime-order-groups) and [Using elliptic curves](#using-elliptic-curves). 957 | 958 | ~~~ 959 | def DeriveSeed(ikm, (skD, bf), pk): 960 | (skR, pkR) = DeriveKeyPair(ikm) 961 | skA = (skR, (skD, bf, pk)) 962 | pkA = (pkR, pk) 963 | return (skA, pkA) 964 | 965 | def DerivePublicKey((pkR, pk), index): 966 | (salt_kem, kh) = Encap(pkR) 967 | 968 | bk = DeriveBlindKey(salt_kem) 969 | ctx = CreateContext(pk, index) 970 | pk' = BlindPublicKey(pk, bk, ctx) 971 | 972 | return (pk', kh) 973 | 974 | def DerivePrivateKey((skR, (skD, bf, pk)), (pk', kh), index): 975 | salt_kem = Decap(kh, skR) 976 | 977 | bk = DeriveBlindKey(salt_kem) 978 | ctx = CreateContext(pk, index) 979 | pkE = BlindPublicKey(pk, bk, ctx) 980 | 981 | if pk' != pkE: abort with an error 982 | 983 | sk = Combine(skD, bf) 984 | sk' = BlindPrivateKey(sk, bk, ctx) 985 | 986 | return sk' 987 | ~~~ 988 | 989 | This enables the [remote HDK protocol](#the-remote-hdk-protocol) to be performed as such, given an `index` known to both parties: 990 | 991 | ~~~ 992 | # 1. Unit computes: 993 | (skA, pkA) = DeriveSeed(salt, (skD, bf), pk) 994 | 995 | # 2. Unit shares with issuer: pkA 996 | 997 | # 3. Issuer computes: 998 | (pk', kh) = DerivePublicKey(pkA, index) 999 | 1000 | # 4. Issuer shares with unit: (pk', kh) 1001 | 1002 | # 5. Unit verifies integrity and computes the private key: 1003 | sk' = DerivePrivateKey(skA, (pk', kh), index) 1004 | ~~~ 1005 | 1006 | For using a single `kh` with multiple values of `index`, the DerivePublicKey needs to be refactored to be able to reuse the Encap output. 1007 | 1008 | # Security considerations 1009 | 1010 | ## Confidentiality of key handles 1011 | 1012 | The key handles MUST be considered confidential, since they provide knowledge about the blinding factors. Compromise of this knowledge could introduce undesired linkability. In HDK, both the holder and the issuer know the key handle during issuance. 1013 | 1014 | In an alternative to HDK, the holder independently generates blinded key pairs and proofs of association, providing the issuer with zero knowledge about the blinding factors. However, this moves the problem: the proofs of association would now need to be considered confidential. 1015 | 1016 | --- back 1017 | 1018 | # Acknowledgements 1019 | {:numbered="false"} 1020 | 1021 | This design is based on ideas introduced to the EU Digital Identity domain by Peter Lee Altmann. 1022 | 1023 | Helpful feedback came from Emil Lundberg, John Bradley and Remco Schaar. 1024 | -------------------------------------------------------------------------------- /feedback-poa.md: -------------------------------------------------------------------------------- 1 | # Feedback to resolve HDK and PoA issues in the ARF 2 | 3 | **Editor:** Sander Dijkhuis (Cleverbase) 4 | 5 | **License:** [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/) 6 | 7 | ## Context 8 | 9 | The binding of attestation (PID, QEAA, PuB-EAA, EAA) keys to critical assets in certified WSCDs, and the ability to prove this binding is critical for security of EUDI wallets. Relying party unlinkability is critical to its privacy. Several experts are proposing solutions that could work for the first wallet deployments. 10 | 11 | The Commission and standardisation organisations are looking into two documents related to these subjects: 12 | 13 | - [Attestation Proof of Association](https://eprint.iacr.org/2024/1444) (PoA) 14 | - [Hierarchical Deterministic Keys](README.md) (HDK) 15 | 16 | The first is prepared in collaboration between experts from Member State. The second is prepared in an informal collaboration between experts from public and private sector participants in Large Scale Pilots. Both PoA and HDK propose concrete short-term considerations in two areas: 17 | 18 | - implementation of the wallet solution internally 19 | - interoperability between wallet units and other entities 20 | 21 | Both of these types are relevant to security and interface requirements in law and standards. This document analyses the common ground and contentious issues and proposes next steps. 22 | 23 | ## Implementation considerations 24 | 25 | ### Common grounds on implementation 26 | 27 | #### Verifiable association of public keys to a single attested WSCD 28 | 29 | Both docs suggest applications of key blinding for unlinkability between presentations, based on a single WSCD key pair. At least one trusted issuer should receive trust evidence issued by the wallet provider, which attests this WSCD key pair, potentially with blinding. This is an important requirement to protect against unauthorised use of attestations. 30 | 31 | To follow up: 32 | 33 | - Ensure the ARF and implementing acts continue to contain this requirement. 34 | 35 | #### Distributed WSCA deployment 36 | 37 | Both docs suggest that a WSCA can be distributed across the user device and an external, possibly remote WSCD. They apply threshold signatures or multiple-ECDH schemes, performing the critical security functions in an off-the-shelf WSCD. This may be the only way to meet the regulation deadlines, since developing and certifying new WSCDs will take too much time in practice. 38 | 39 | Related ARF issue: [#283](https://github.com/eu-digital-identity-wallet/eudi-doc-architecture-and-reference-framework/issues/283). Method to resolve the issue: 40 | 41 | - Update the ARF and implementing act text to acknowledge that the WSCA does not need to be hosted on the WSCD, but can for example be distributed. 42 | 43 | ### Contentious issues on implementation 44 | 45 | #### Patent risks to wallet providers 46 | 47 | In practice, EUDI wallets would use either ECDSA or ECDH-MAC, which are widely supported by off-the-shelf WSCDs and can be applied in a distributed WSCA deployment. Several patent claims seem to apply to the new and innovative ECDSA option. While HDK could be extended to support ECDSA, it is left out of the doc to leave any commercial interests out of the informal cross-LSP working group. If ECDSA becomes the de-facto standard, this would require all wallet providers using off-the-shelf WSCDs to deal with these patents. Experts so far do not see similar risk with ECDH-MAC. 48 | 49 | Related ARF issue: [#286](https://github.com/eu-digital-identity-wallet/eudi-doc-architecture-and-reference-framework/issues/286). Options to resolve this issue: 50 | 51 | - Investigate the patents and potentially negotiate licensing conditions at the EU-level. 52 | - Mandate the use of at least ECDH-MAC to enable at least one open standards-based route. 53 | - Leave the risk to the individual wallet providers. 54 | 55 | ## Interoperability considerations 56 | 57 | ### Common grounds on interoperability 58 | 59 | #### Cryptographic proofs of association to relying parties 60 | 61 | Both approaches enable the construction of a cryptographic proof of association based on zero-knowledge proofs. This enables proving association between attestations towards relying parties. The method is complementary to other methods for proof of association. These should co-exist since parties are also working on WSCAs that cannot create the zero-knowledge proof but will have other certified ways to assure relying parties of association. One question that is not yet addressed explicitly is whether the proof of association may be non-repudiable, or if designated verifier protocols are needed for plausible deniability. 62 | 63 | To follow up: 64 | 65 | - Require attestation protocols to provide sufficient flexiblity to support zero-knowledge proof of association. 66 | - with an advisory group, analyse pros and cons of having non-repudiable versus plausibly deniable proof of association. 67 | 68 | ### Contentious issues on interoperability 69 | 70 | #### Proof of association requirements to attestation providers 71 | 72 | Both PoA and HDK convince attestation providers that a newly generated key is associated with a previously attested key. But the methods are different: 73 | 74 | - PoA provides the same zero-knowledge proof as to relying parties. 75 | - HDK applies key share agreement with the wallet unit, which directly proves to the attestation provider association with a previously presented key. 76 | 77 | There is a tradeoff to make. Arguably, HDK is simpler and enables other features such as delegated key share generation. However, it is not in zero-knowledge, which at least theoretically reduces confidentiality. So far, no concrete abuse scenarios are known. 78 | 79 | Options to resolve the issue: 80 | 81 | - Leave the ARF and implementing acts open towards the method of proving association. 82 | - With an advisory group, analyse the pros and cons of each approach and select one. 83 | 84 | #### Delegated key share generation 85 | 86 | By applying key share agreement, HDK can delegate the generation of additional association key shares to the attestation provider. This enables more efficient batch re-issuance. For example, the provider could periodically issue a new batch, without wallet unit interaction, while still being sure that the new public keys are bound to the same WSCD. In the PoA approach, this would generate periodic interaction with the wallet unit, or the wallet unit should pre-generate a high number of keys and proofs of association. 87 | 88 | Related ARF issue: [#284](https://github.com/eu-digital-identity-wallet/eudi-doc-architecture-and-reference-framework/issues/284). Options to resolve the issue: 89 | 90 | - Update the ARF and implementing acts to enable delegated key share generation. 91 | - With an advisory group, analyse concrete re-issuance scenarios and decide whether the pros outweigh the cons. 92 | 93 | #### Use of implicit trust evidence 94 | 95 | One driver for development of HDK is to enable attestation providers to rely on PID presentation not only for identity verification, but also for assurance of WSCD binding. If the attestation provider can generate a new public key based on a previously presented PID key, they do not need additional trust evidence. This could simplify the process for non-PID issuers and reduce dependence on wallet provider availability. The current ARF and implementing act texts instead make the use of trust evidence mandatory, and the PoA document focuses on methods to apply this trust evidence in combination with proof of association. 96 | 97 | Related ARF issues: [#285](https://github.com/eu-digital-identity-wallet/eudi-doc-architecture-and-reference-framework/issues/285), [#286](https://github.com/eu-digital-identity-wallet/eudi-doc-architecture-and-reference-framework/issues/286). Options to resolve the issue: 98 | 99 | - Update the ARF and implementing acts to enable the use of implicit trust evidence. 100 | - With an advisory group, analyse pros and cons of each approach to the verify-PID-then-issue-EAA use case. 101 | - Leave explicit trust evidence mandatory, even when it is not needed for attestation issuers. 102 | 103 | ## Proposed next steps 104 | 105 | 1. Discuss these issues with the Commission and ARF authors for shared understanding, and triage if some may have already been resolved. 106 | 2. Organise an ad-hoc technical advisory group to analyse the remaining issues. 107 | 3. Present the results to an expert group for Member State feedback. 108 | 4. Record the conclusions and any remaining open issues in the ARF project on GitHub. 109 | -------------------------------------------------------------------------------- /feedback.md: -------------------------------------------------------------------------------- 1 | # Feedback to enable Hierarchical Deterministic Keys in the Wallet Toolbox 2 | 3 | **Version:** 0.2.0-SNAPSHOT 4 | 5 | **Editor:** Sander Dijkhuis (Cleverbase) 6 | 7 | **License:** [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/) 8 | 9 | **Discussion:** [eu-digital-identity-wallet/eudi-doc-architecture-and-reference-framework#282](https://github.com/eu-digital-identity-wallet/eudi-doc-architecture-and-reference-framework/discussions/282) 10 | 11 | ## Context 12 | 13 | For a general introduction, see [Hierarchical Deterministic Keys for the European Digital Identity Wallet](README.md). In the current document, the authors develop and share structured feedback on one part of the Wallet Toolbox: the [Architecture and Reference Framework](https://eu-digital-identity-wallet.github.io/eudi-doc-architecture-and-reference-framework/latest/arf/) (ARF). The purpose of this feedback is to enable implementation of [Hierarchical Deterministc Keys](draft-dijkhuis-cfrg-hierarchical-deterministic-keys.md) (HDKs). 14 | 15 | By enabling Hierarchical Deterministic Keys, we aim for interoperability with a concrete and desirable cryptographic architecture in the context of person identification data and some (qualified) electronic attestations of attributes. We do not suggest to mandate the application of this cryptographic architecture for all digital identity documents. Instead, we aim to address two risks to the ARF and subsequently the implementing acts: the risk of accidentally disabling desirable technical solutions, and the risk of accidentally requiring undesirable technical solutions. 16 | 17 | > [!NOTE] 18 | > This feedback document is a first version, and requires detailed exchanges to understand each other’s detailed points of view. This information is shared by participants of the [Digital Credentials for Europe (DC4EU) Consortium](https://www.dc4eu.eu), the [EU Digital Identity Wallet Consortium (EWC)](https://eudiwalletconsortium.org), and the [Potential Consortium](https://www.digital-identity-wallet.eu). Views and opinions expressed are those of the authors only and do not necessarily reflect those of all consortium members. 19 | 20 | ## Feedback on the high level requirements 21 | 22 | The original requirement texts are copied from ARF 1.4.0. 23 | 24 | ### Topic 9 - Wallet Trust Evidence 25 | 26 | This topic currently specifies a very specific technical solution. To be applicable to HDKs, the requirements need to be either at a higher level, or more open to different technical solutions. The feedback below proposes more open requirements in Topic 9. 27 | 28 | |Index|Proposed change|Rationale| 29 | |--|--|--| 30 | |WTE_*|Split WTE requirements into WTE requirements and Issuer Trust Evidence (ITE) requirements. Limit the WTE audience to authorised Person Identification Data (PID) Providers. Make requesting ITE optional to other providers.|Splitting requirements makes explicit the associated security and privacy conditions. HDK splits the WTE and ITE solutions as well. Splitting the requirements does not preclude solutions other than HDK from applying a single solution to create both WTE and ITE.| 31 | |WTE_01–09|None.|N/A| 32 | |WTE_10|Modify: “A WSCA SHALL generate a new key pair for a new WTE on request of the Wallet Provider via the Wallet Instance. The WSCA Wallet Instance SHALL register a unique identifier of the new key as a WTE key in an internal registry. The WSCA Wallet Instance SHALL register the WTE key as an independent (i.e., non-associated) key in an internal registry.”

Add: “A Wallet Instance SHALL generate a new key pair for a new ITE on request of the Wallet Provider, with the same WSCA-enforced access controls (see WTE_02) as a valid WTE key. The Wallet Instance SHALL register the new key in an internal registry. The Wallet Instance SHALL register the ITE key as associated with the WTE in an internal registry.”|This change is essential to the HDK architecture, where the WSCA is responsible only for device key pairs, and the other keys are managed as HDKs within the Wallet Instance. This change does not preclude solutions other than HDK from having the Wallet Instance delegate this functionality to a WSCA.| 33 | |WTE_10–11|None.|N/A| 34 | |WTE_13|Modify: “During PID or attestation issuance, a WSCA Wallet Instance SHALL generate a new key pair for a new PID or attestation, on request of the PID Provider or Attestation Provider via the Wallet Instance. The attestation key MUST be protected using the same WSCA-enforced access controls (see WTE_02) as a valid WTE key. The Wallet Instance MAY delegate key generation to the PID Provider or Attestation Provider. The PID Provider or Attestation Provider SHALL indicate a single WTE public key (see WTE_10) with which the new PID or attestation key must be associated, along with data identifying the method to be used for association. This indication can either be direct, by providing the WTE public key value, or indirect, by providing a public key value that has been associated with the WTE key previously. In the latter case, the WSCA SHALL look up the associated WTE key in its internal registry.
The WSCA Wallet Instance SHALL register the new key in an internal registry as an attestation key. The WSCA Wallet Instance SHALL register the association between the new private key and the WTE private key in an internal registry.”|In HDK, the Wallet Provider, PID Provider, or Attestation Provider may asynchronously, remotely generate batches of single-use keys. These keys are under sole control of the User by association to a WTE key. Other proposed modifications are required to remove non-essential implementation details and thereby to enable HDK as in WTE_10.| 35 | |WTE_14|Modify: “During PID or attestation issuance, a WSCA Wallet Instance SHALL prove possession of the private key corresponding to the new PID or attestation public key, or, in the case of delegated key generation, by proving possession of the key indicated in WTE_13, on request of the PID Provider or Attestation Provider via the Wallet Instance, for example by signing a challenge with that private key. Note that by design, this proof of possession implies that the WSCA has authenticated the user.”|In HDK, a single proof of possession to the PID or (Q)EAA Provider is sufficient to enable multiple unique one-time-use keys.| 36 | |WTE_15–16|None.|N/A| 37 | |WTE_17|Modify: “During PID or attestation issuance, a WSCA Wallet Instance SHALL prove possession of the private key corresponding to a WTE public key on request of a PID Provider or Attestation Provider via the Wallet Instance, for example by signing a challenge with that private key.”|With HDK, there is no need for roles other than PID Providers to learn wallet metadata. Therefore, disclosing an ITE is no requirement for issuance. Users may choose to disclose ITE data as part of releasing attributes, which is sufficiently covered by other requirements.| 38 | |WTE_18|Modify: “During PID or attestation issuance, a WSCA Wallet Instance SHOULD generate a proof of association for two or more public keys whenever two or more public keys are disclosed, if and only if the corresponding private keys are protected by the same WSCA and the WSCA Wallet Instance has internally registered an association between these private keys.”|In HDK, typically only one public key is provided for issuance. In such cases, a proof of association is not applicable.| 39 | |WTE_19|Modify: “During PID or attestation issuance, the PID Provider or Attestation Provider SHALL verify that:
• The WSCA Wallet Instance described in the WTE, if any, or ITE, if any, received from the Wallet Instance has proven possession of the private key corresponding to the public key in the WTE or ITE (see WTE_17),
•The WSCA has proven possession of Wallet Instance possesses the new PID or attestation private key (see WTE_14)
In addition, the PID Provider or Attestation Provider SHOULD verify that:
• The WSCA has proven association (see WTE_18) between protects with the same access controls the new PID or attestation public key and the public key requested by the PID Provider or Attestation Provider according to WTE_15 or WTE_16.”|In HDK, the PID Provider or Attestation Provider can apply HDK instead of a proof of association to verify equivalent WSCA protection between two keys.| 40 | |WTE_20|Modify: “During PID or attestation issuance, a Wallet Instance SHALL provide the PID Provider or Attestation Provider with the WTE describing the properties of the WSCA that generated the new PID or attestation private key and protects it.”|In HDK, only PID Providers require WTE, and Attestation Providers do not require WTE or ITE. This modification does not preclude other technical solutions from still providing ITE to Attestation Providers over the same interface.| 41 | |WTE_21–22|None.|N/A| 42 | |WTE_23|Modify: “The common OpenID4VCI protocol defined in requirement ISSU_01 SHALL enable a Wallet Instance to transfer a WTE to a PID Provider or Attestation Provider.”|See WTE_20.| 43 | |WTE_24|Modify: “A Wallet Instance SHALL release a WTE only to a PID Provider or Attestation Provider, and not to a Attestation Provider or Relying Party or any other party.”

Add: “A Wallet Instance SHALL release a ITE only to an Attestation Provider, and not to a Relying Party or any other party.”|See WTE_20.| 44 | |WTE_25–26|None.|N/A| 45 | |WTE_27|Add: “The common OpenID4VCI protocol SHALL enable a PID Provider or Attestation Provider to indicate in the Token Response:
• in the case of delegated key generation, data enabling the Wallet Instance to prove possession of the private key associated with the new PID or attestation key”|This is essential to enable application of HDK to batch issuance.| 46 | |WTE_28–30|None.|N/A| 47 | |WTE_31|A WSCA Wallet Instance SHALL register each newly generated key pair as either a WTE key or an attestation key, depending on information provided by the Wallet Instance in the key generation request. The internal registry used by the WSCA Wallet Instance for this purpose SHALL be protected against modification by external parties.|See WTE_10.| 48 | |WTE_32|None.|N/A| 49 | |WTE_33|Modify: “A WSCA Wallet Instance SHALL associate each newly generated attestation key with a WTE key indicated by the Wallet Instance. The WSCA Wallet Instance SHALL record the association between these keys in an internal registry, which SHALL be protected against modification by external parties.”|In HDK, the Wallet Instance maintains the associations.| 50 | |WTE_34|Drop.|This is an implementation detail only to some technical solutions.| 51 | |WTE_35|A WSCA Wallet Instance SHALL consider two keys to be associated if they are associated to a common WTE key.|See WTE_10.| 52 | |WTE_36|Modify: “A WSCA Wallet Instance SHOULD be able to generate a proof of association for two or more public keys. The WSCA Wallet Instance SHALL generate such a proof if and only if the corresponding private keys are protected by that a single WSCA, and the WSCA Wallet Instance has internally registered an association between these private keys.”|In HDK, the Wallet Instance manages such associations. This does not preclude solutions other than HDK to delegate this to the WSCA.| 53 | 54 | ### Topic 18 - Relying Party handling EUDI Wallet attribute combined presentation 55 | 56 | With HDK, document readers do not need the proof of association, as they may instead rely on attributes attested by issuers. Since the issuers apply HDK, they can for example ensure binding to PID. 57 | 58 | To enable HDK, the following changes to Topic 18 are needed. 59 | 60 | |Index|Proposed change|Rationale| 61 | |--|--|--| 62 | |ACP_01–03|None.|N/A| 63 | |ACP_04|Drop or modify: “If (as a result of ACP_03) a Wallet Instance determines it must release multiple attestations to a Relying Party in a combined presentation of attributes, it SHALL requestMAY generate a proof of association between the public keys of these attestations from the WSC.”|Proof of association is not necessary in the case of attribute-based binding. It could introduce disproportional complications. For example, depending on the proof mechanism, this could produce a potentially non-repudiable proof that a certain combination of documents was revealed. Also, by disclosing public keys related to blinding scalars, it will be more difficult for solutions to guarantee unconditional privacy.| 64 | |ACP_05|Drop.|It is up to the User whether to authorise sharing a proof of association with another party.| 65 | |ACP_06–08|None.|N/A| 66 | |ACP_09|Drop.|With HDK, proof of association is not essential.| 67 | -------------------------------------------------------------------------------- /prototype.demo.lisp: -------------------------------------------------------------------------------- 1 | (defpackage #:demo (:use #:common-lisp #:prototype)) 2 | (in-package #:demo) 3 | 4 | (defvar *wallet* (make-unit +hdk-ecdh-p256+)) 5 | (defvar *evidence* (activate *wallet*)) 6 | 7 | ;; Present wallet trust evidence to the PID provider 8 | (let* ((reader (make-reader +ecdh-p256+)) 9 | (device-data (prove-possession *wallet* *evidence* (pk reader)))) 10 | (assert (verify reader *evidence* device-data))) 11 | 12 | ;; Request issuance using remote key derivation 13 | (defvar *pk-kem* (request *wallet* *evidence*)) 14 | 15 | ;; Create a key handle and issue a first batch of PID 16 | (defvar *kh*) 17 | (defvar *pid*) 18 | (multiple-value-bind (salt kh) (encap (kem *wallet*) *pk-kem*) 19 | (setf *kh* kh) 20 | (setf *pid* (loop for i in '(0 1 2 3) 21 | collect (make-document (hdk *wallet*) *evidence* salt i)))) 22 | 23 | ;; Accept the first batch of PID, using synchronised indices 24 | ;; (synchronisation is implicit: easy upon first batch) 25 | (loop for i in '(0 1 2 3) 26 | for doc in *pid* 27 | do (accept *wallet* *evidence* *kh* i doc)) 28 | 29 | ;; Present PID to various readers 30 | (loop for doc in *pid* do 31 | (let* ((reader (make-reader +ecdh-p256+)) 32 | (device-data (prove-possession *wallet* doc (pk reader)))) 33 | (assert (verify reader doc device-data)))) 34 | 35 | (format t "Demo finished~%") 36 | -------------------------------------------------------------------------------- /prototype.lisp: -------------------------------------------------------------------------------- 1 | (defpackage #:prototype 2 | (:export #:kem #:encap 3 | #:hdk 4 | #:make-unit #:activate #:prove-possession #:request #:accept 5 | #:make-reader #:pk #:verify 6 | #:make-document 7 | #:+hdk-ecdh-p256+ #:+ecdh-p256+) 8 | (:use #:common-lisp) 9 | (:import-from #:crypto 10 | #:+secp256r1-l+ #:+secp256r1-g+ #:EC-Scalar-Mult #:EC-Point-Equal)) 11 | 12 | (in-package #:prototype) 13 | 14 | (defun concat (&rest bs) (apply #'concatenate 15 | '(vector (unsigned-byte 8)) bs)) 16 | (defun i2osp (i n) (crypto:integer-to-octets i :n-bits (* n 8))) 17 | (defun os2ip (os) (crypto:octets-to-integer os)) 18 | (defun strxor (s1 s2) (map 'crypto::simple-octet-vector #'logxor s1 s2)) 19 | (defun ascii (s) (crypto:ascii-string-to-byte-array s)) 20 | (defun read-bytes (&rest hex-strings) 21 | (read-from-string (apply #'concatenate 'string "#x" hex-strings))) 22 | 23 | (defclass ec () ((id :reader id :initarg :id) 24 | (n :reader order :initarg :n) 25 | (g :reader base :initarg :g))) 26 | (defclass ec-kg (ec) ()) 27 | 28 | (defclass h () ((id :reader id :initarg :id))) 29 | (defclass h2c () ((ec :reader ec :initarg :ec) 30 | (h :reader hash :initarg :h) 31 | (dst :reader dst :initarg :dst))) 32 | 33 | (defclass dh () ((n-dh :reader output-length :initarg :n-dh))) 34 | (defclass ec-dh (dh) ((ec :reader ec :initarg :ec))) 35 | 36 | (defclass bl () ((id :reader id :initarg :id))) 37 | (defclass ec-bl (bl) ((ec :reader ec :initarg :ec) 38 | (h :reader hash :initarg :h))) 39 | (defclass ec-bl-mul (ec-bl) ()) 40 | (defclass ec-bl-mul-dh (ec-bl-mul) ((ec-dh :reader ec-dh :initarg :ec-dh))) 41 | 42 | (defclass hmac () ((id :reader id :initarg :id))) 43 | 44 | (defclass hkdf () ((hmac :reader hmac :initarg :hmac))) 45 | 46 | (defclass hdk () ((id :reader id :initarg :id) 47 | (bl :reader bl :initarg :bl) 48 | (kem :reader kem :initarg :kem) 49 | (n-s :reader seed-length :initarg :n-s) 50 | (h :reader hash :initarg :h))) 51 | 52 | (defclass kem () ((n-secret :reader secret-length :initarg :n-secret) 53 | (n-sk :reader private-key-length :initarg :n-sk) 54 | (id :reader id :initarg :id) 55 | (bitmask :reader bitmask :initarg :bitmask))) 56 | (defclass dhkem (kem) ((dh :reader dh :initarg :dh) 57 | (hkdf :reader hkdf :initarg :hkdf))) 58 | (defclass ec-dhkem (dhkem) ((ec :reader ec :initarg :ec))) 59 | 60 | (defmethod h ((h h) &rest bs) 61 | (loop with hash = (crypto:make-digest (id h)) 62 | for b in bs do (crypto:update-digest hash b) 63 | finally (return (crypto:produce-digest hash)))) 64 | (defmethod expand-message-xmd ((h2c h2c) msg dst len) 65 | (loop with dst = (concat dst (i2osp (length dst) 1)) 66 | with b = (make-array len :fill-pointer 0) 67 | with b0 = (h (hash h2c) (i2osp 0 64) msg (i2osp len 2) (i2osp 0 1) dst) 68 | for i from 1 upto (ceiling (/ len 32)) 69 | for bi = (h (hash h2c) b0 (i2osp 1 1) dst) 70 | then (h (hash h2c) (strxor b0 bi) (i2osp i 1) dst) 71 | do (loop for j across bi do (vector-push j b)) 72 | finally (return (coerce b 'crypto::simple-octet-vector)))) 73 | (defmethod hash-to-field ((h2c h2c) &rest msg) 74 | (mod (os2ip (expand-message-xmd h2c 75 | (apply #'concat msg) (dst h2c) 48)) 76 | (order (ec h2c)))) 77 | 78 | (defmethod random-scalar ((ec ec)) (1+ (crypto:strong-random (1- (order ec))))) 79 | (defmethod scalar-mult ((ec ec) el k) (crypto:ec-scalar-mult el k)) 80 | (defmethod scalar-base-mult ((ec ec) k) (scalar-mult ec +secp256r1-g+ k)) 81 | 82 | (defmethod generate-key-pair ((ec ec-kg)) 83 | (let ((sk (random-scalar ec))) (values sk (scalar-base-mult ec sk)))) 84 | (defmethod serialize-public-key ((ec ec-kg) pk) 85 | (concat (i2osp (getf (crypto:ec-destructure-point pk) :x) 32) 86 | (i2osp (getf (crypto:ec-destructure-point pk) :y) 32))) 87 | (defmethod deserialize-public-key ((ec ec-kg) b) 88 | (crypto:ec-make-point (id ec) :x (os2ip (subseq b 0 32)) 89 | :y (os2ip (subseq b 32)))) 90 | 91 | (defmethod create-shared-secret ((dh ec-dh) sk-x pk-y) 92 | (i2osp (getf (crypto:ec-destructure-point (scalar-mult (ec dh) pk-y sk-x)) :x) 93 | (output-length dh))) 94 | 95 | (defmethod derive-blind-key ((bl ec-bl) ikm) 96 | (let ((h2c (make-instance 'h2c :ec (ec bl) :h (hash bl) :dst (id bl)))) 97 | (i2osp (hash-to-field h2c ikm) 32))) 98 | (defmethod derive-blinding-factor ((bl ec-bl) bk ctx) 99 | (let ((h2c (make-instance 'h2c :ec (ec bl) :h (hash bl) :dst (id bl)))) 100 | (hash-to-field h2c bk '(#x00) ctx))) 101 | (defmethod combine ((bl ec-bl-mul) bf1 bf2) 102 | (mod (* bf1 bf2) (order (ec bl)))) 103 | (defmethod blind-public-key ((bl ec-bl-mul) pk-s bk ctx) 104 | (scalar-mult (ec bl) pk-s (derive-blinding-factor bl bk ctx))) 105 | (defmethod blind-dh ((bl ec-bl-mul-dh) sk-x bf pk-y) 106 | (create-shared-secret (ec-dh bl) sk-x (scalar-mult (ec bl) pk-y bf))) 107 | 108 | (defmethod create-context ((hdk hdk) index) (concat (id hdk) (i2osp index 4))) 109 | (defmethod derive-salt ((hdk hdk) salt ctx) (h (hash hdk) (id hdk) salt ctx)) 110 | (defmethod hdk-apply ((hdk hdk) index pk salt &optional bf) 111 | (let* ((bk (derive-blind-key (bl hdk) salt)) 112 | (ctx (create-context hdk index))) 113 | (values (blind-public-key (bl hdk) pk bk ctx) 114 | (derive-salt hdk salt ctx) 115 | (let ((bf2 (derive-blinding-factor (bl hdk) bk ctx))) 116 | (if bf (combine (bl hdk) bf bf2) bf2))))) 117 | 118 | (defmethod mac ((hmac hmac) key &rest bs) 119 | (loop with mac = (crypto:make-mac :hmac key (id hmac)) 120 | for b in bs do (crypto:update-mac mac b) 121 | finally (return (crypto:produce-mac mac)))) 122 | 123 | (defmethod extract ((hkdf hkdf) salt ikm) (mac (hmac hkdf) salt ikm)) 124 | (defmethod expand ((hkdf hkdf) prk info len) 125 | (loop with tb = (make-array len :fill-pointer 0) 126 | for i from 1 upto (ceiling (/ len 32)) 127 | for ti = (mac (hmac hkdf) prk (concat info (i2osp i 1))) 128 | then (mac (hmac hkdf) prk (concat ti info (i2osp i 1))) 129 | do (loop for j across ti do (vector-push j tb)) 130 | finally (return (coerce tb '(vector (unsigned-byte 8)))))) 131 | 132 | (defmethod labeled-extract ((kem dhkem) salt label ikm) 133 | (extract (hkdf kem) salt (concat (ascii "HPKE-v1") (id kem) 134 | (ascii label) ikm))) 135 | (defmethod labeled-expand ((kem dhkem) prk label info length) 136 | (expand (hkdf kem) 137 | prk 138 | (concat (i2osp length 2) (ascii "HPKE-v1") (id kem) 139 | (ascii label) info) 140 | length)) 141 | (defmethod extract-and-expand ((kem kem) dh kem-context) 142 | (let ((eae-prk (labeled-extract kem (ascii "") "eae_prk" dh))) 143 | (labeled-expand 144 | kem eae-prk "shared_secret" kem-context (secret-length kem)))) 145 | (defmethod derive-key-pair ((kem ec-dhkem) ikm) 146 | (loop with dkp-prk = (labeled-extract kem (ascii "") "dkp_prk" ikm) 147 | for counter from 0 upto 254 148 | for bytes = (labeled-expand kem dkp-prk "candidate" (i2osp counter 1) 149 | (private-key-length kem)) 150 | for sk = (progn 151 | (setf (aref bytes 0) (logand (aref bytes 0) (bitmask kem))) 152 | (os2ip bytes)) 153 | when (not (= sk 0)) return (values sk (scalar-base-mult (ec kem) sk)))) 154 | (defmethod encap ((kem ec-dhkem) pk-r) 155 | (multiple-value-bind (sk-e pk-e) (generate-key-pair (ec kem)) 156 | (let* ((dh (create-shared-secret (dh kem) sk-e pk-r)) 157 | (enc (serialize-public-key (ec kem) pk-e)) 158 | (pk-rm (serialize-public-key (ec kem) pk-r)) 159 | (kem-context (concat enc pk-rm)) 160 | (shared-secret (extract-and-expand kem dh kem-context))) 161 | (values shared-secret enc)))) 162 | (defmethod decap ((kem ec-dhkem) enc sk-r) 163 | (let* ((pk-e (deserialize-public-key (ec kem) enc)) 164 | (dh (create-shared-secret (dh kem) sk-r pk-e)) 165 | (pk-rm (serialize-public-key (ec kem) 166 | (scalar-base-mult (ec kem) sk-r))) 167 | (kem-context (concat enc pk-rm)) 168 | (shared-secret (extract-and-expand kem dh kem-context))) 169 | shared-secret)) 170 | 171 | (defconstant +sha256+ 172 | (make-instance 'h :id :sha256)) 173 | (defconstant +p256+ 174 | (make-instance 'ec-kg :n +secp256r1-l+ :g +secp256r1-g+ :id :secp256r1)) 175 | (defconstant +ecdh-p256+ 176 | (make-instance 'ec-dh :n-dh 32 :ec +p256+)) 177 | (defconstant +bl-ecdh-p256+ 178 | (make-instance 'ec-bl-mul-dh :id (ascii "ECDH Multiplicative Key Blind") 179 | :ec +p256+ 180 | :ec-dh +ecdh-p256+ 181 | :h +sha256+)) 182 | (defconstant +hmac-sha256+ 183 | (make-instance 'hmac :id :sha256)) 184 | (defconstant +hkdf-sha256+ 185 | (make-instance 'hkdf :hmac +hmac-sha256+)) 186 | (defconstant +dhkem-p256-hkdf-sha256+ 187 | (make-instance 'ec-dhkem :id (concat (ascii "KEM") (i2osp #x0010 2)) 188 | :n-secret 32 189 | :n-sk 32 190 | :bitmask #xff 191 | :dh +ecdh-p256+ 192 | :hkdf +hkdf-sha256+ 193 | :ec +p256+)) 194 | (defconstant +hdk-ecdh-p256+ 195 | (make-instance 'hdk :id (ascii "HDK-ECDH-P256-v1") 196 | :bl +bl-ecdh-p256+ 197 | :kem +dhkem-p256-hkdf-sha256+ 198 | :n-s 32 199 | :h +sha256+)) 200 | 201 | (defmethod fold ((hdk hdk) path pk salt &optional bf) 202 | (cond ((null path) (values pk bf salt)) 203 | ((typep (car path) 'number) 204 | (multiple-value-bind (pk salt bf) 205 | (hdk-apply hdk (car path) pk salt bf) 206 | (fold hdk (cdr path) pk salt bf))) 207 | (t (let* ((sk-r (derive-key-pair (kem hdk) salt)) 208 | (salt (decap (kem hdk) (car path) sk-r))) 209 | (fold hdk (cdr path) pk salt bf))))) 210 | 211 | (defclass document () ((pk :reader pk :initarg :pk))) 212 | (defun make-document (hdk doc salt index) 213 | (make-instance 'document :pk (hdk-apply hdk index (pk doc) salt))) 214 | 215 | (defclass app () 216 | ((hdk :reader hdk :initarg :hdk) 217 | (device :reader device :initarg :device) 218 | (seed :reader seed :initarg :seed))) 219 | (defun make-app (hdk) 220 | (make-instance 'app 221 | :hdk hdk 222 | :device (multiple-value-list (generate-key-pair (ec (bl hdk)))) 223 | :seed (crypto:random-data (seed-length hdk)))) 224 | (defun sk-device (app) (car (device app))) 225 | (defun pk-device (app) (cadr (device app))) 226 | (defun fold-hdk (app hdk) (fold (hdk app) hdk (pk-device app) (seed app))) 227 | (defun get-key-info (app hdk) 228 | (let ((pk (fold-hdk app hdk))) 229 | (values pk '(:agree-key) (make-instance 'document :pk pk)))) 230 | (defmethod create-shared-secret (app hdk reader-pk) 231 | (blind-dh (bl (hdk app)) 232 | (sk-device app) 233 | (nth-value 1 (fold-hdk app hdk)) 234 | reader-pk)) 235 | (defun delegate-key-creation (app hdk) 236 | (derive-key-pair (kem (hdk app)) 237 | (nth-value 2 (fold-hdk app hdk)))) 238 | (defun accept-key (app hdk kh index pk-expected) 239 | (let* ((salt (decap (kem (hdk app)) kh (delegate-key-creation app hdk))) 240 | (pk (hdk-apply (hdk app) index (get-key-info app hdk) salt))) 241 | (assert (crypto:ec-point-equal pk-expected pk))) 242 | (append hdk (list kh index))) 243 | 244 | (defconstant +hdk-root+ '(0)) 245 | (defclass unit () 246 | ((app :reader app :initarg :app) 247 | (index :reader index :initform (make-hash-table :weakness :key)))) 248 | (defmacro unit-hdk (unit doc) (list 'gethash doc (list 'index unit))) 249 | (defun make-unit (hdk) (make-instance 'unit :app (make-app hdk))) 250 | (defun activate (unit) 251 | (multiple-value-bind (pk purposes doc) (get-key-info (app unit) +hdk-root+) 252 | (declare (ignore pk purposes)) 253 | (setf (unit-hdk unit doc) +hdk-root+) 254 | doc)) 255 | (defun prove-possession (unit doc reader-data) 256 | (create-shared-secret (app unit) (unit-hdk unit doc) reader-data)) 257 | (defun request (unit doc-parent) 258 | (nth-value 1 (delegate-key-creation (app unit) (unit-hdk unit doc-parent)))) 259 | (defun accept (unit doc-parent kh index doc) 260 | (let* ((hdk (unit-hdk unit doc-parent)) 261 | (app (app unit))) 262 | (setf (unit-hdk unit doc) (accept-key app hdk kh index (pk doc))))) 263 | (defmethod hdk ((unit unit)) (hdk (app unit))) 264 | (defmethod kem ((unit unit)) (kem (hdk unit))) 265 | 266 | (defclass reader () 267 | ((sk :reader sk :initarg :sk) 268 | (dh :reader dh :initarg :dh))) 269 | (defun make-reader (ec-dh) 270 | (make-instance 'reader :sk (random-scalar (ec ec-dh)) 271 | :dh ec-dh)) 272 | (defun verify (reader doc device-data) 273 | (= (os2ip device-data) 274 | (os2ip (create-shared-secret (dh reader) (sk reader) (pk doc))))) 275 | (defmethod pk ((reader reader)) 276 | (scalar-base-mult (ec (dh reader)) (sk reader))) 277 | 278 | (loop with vectors = 279 | `(("" 280 | "QUUX-V01-CS02-with-expander-SHA256-128" #x20 281 | ,(read-bytes 282 | "68a985b87eb6b46952128911f2a4412bbc302a9d759667f87f7a21d803f07235")) 283 | ("abc" 284 | "QUUX-V01-CS02-with-expander-SHA256-128" #x20 285 | ,(read-bytes 286 | "d8ccab23b5985ccea865c6c97b6e5b8350e794e603b4b97902f53a8a0d605615")) 287 | ("" 288 | "QUUX-V01-CS02-with-expander-SHA256-128" #x80 289 | ,(read-bytes 290 | "af84c27ccfd45d41914fdff5df25293e221afc53d8ad2ac06d5e3e29485dadbe" 291 | "e0d121587713a3e0dd4d5e69e93eb7cd4f5df4cd103e188cf60cb02edc3edf18" 292 | "eda8576c412b18ffb658e3dd6ec849469b979d444cf7b26911a08e63cf31f9dc" 293 | "c541708d3491184472c2c29bb749d4286b004ceb5ee6b9a7fa5b646c993f0ced" 294 | ))) 295 | for (msg dst len result) in vectors 296 | do (assert 297 | (= (let ((h2c (make-instance 'h2c :ec +p256+ 298 | :h +sha256+ 299 | :dst dst))) 300 | (os2ip (expand-message-xmd h2c (ASCII msg) (ASCII dst) len)) 301 | result)))) 302 | 303 | (assert 304 | (let* ((prk 305 | (extract +hkdf-sha256+ 306 | (i2osp #x000102030405060708090a0b0c 13) 307 | (i2osp #x0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b 22))) 308 | (okm (expand +hkdf-sha256+ prk (i2osp #xf0f1f2f3f4f5f6f7f8f9 10) 42))) 309 | (and 310 | (= (os2ip prk) 311 | #x077709362c2e32df0ddc3f0dc47bba6390b6c73bb50f9c3122ec844ad7c2b3e5) 312 | (= (os2ip okm) 313 | (read-bytes 314 | "3cb25f25faacd57a90434f64d0362f2a2d2d0a90cf1a5a4c5db02d56ecc4c5bf34" 315 | "007208d5b887185865"))))) 316 | 317 | (assert 318 | (= (derive-key-pair +dhkem-p256-hkdf-sha256+ 319 | (i2osp 320 | #x4270e54ffd08d79d5928020af4686d8f6b7d35dbe470265f1f5aa22816ce860e 32)) 321 | #x4995788ef4b9d6132b249ce59a77281493eb39af373d236a1fe415cb0c2d7beb)) 322 | 323 | (assert (let ((kem +dhkem-p256-hkdf-sha256+)) 324 | (multiple-value-bind (sk pk) (derive-key-pair kem (i2osp #x01 4)) 325 | (multiple-value-bind (k c) (encap kem pk) 326 | (= (os2ip k) (os2ip (decap kem c sk))))))) 327 | 328 | (let* ((bl +bl-ecdh-p256+) 329 | (ikm #(1 2 3)) 330 | (bk (derive-blind-key bl ikm)) 331 | (ctx #(4 5 6)) 332 | (bf (derive-blinding-factor bl bk ctx))) 333 | (multiple-value-bind (sk-x pk-x) (generate-key-pair (ec bl)) 334 | (multiple-value-bind (sk-y pk-y) (generate-key-pair (ec bl)) 335 | (assert (= (os2ip (blind-dh bl sk-x bf pk-y)) 336 | (let ((pk-blinded (blind-public-key bl pk-x bk ctx))) 337 | (os2ip (create-shared-secret 338 | (ec-dh bl) sk-y pk-blinded)))))))) 339 | 340 | (let* ((bl +bl-ecdh-p256+) 341 | (ikm #(1 2 3)) 342 | (bk (derive-blind-key bl ikm)) 343 | (ctx1 #(4 5 6)) 344 | (ctx2 #(7 8 9)) 345 | (bf1 (derive-blinding-factor bl bk ctx1)) 346 | (bf2 (derive-blinding-factor bl bk ctx2))) 347 | (multiple-value-bind (sk pk) (generate-key-pair (ec bl)) 348 | (assert (= (os2ip 349 | (create-shared-secret 350 | (ec-dh bl) 1 351 | (blind-public-key bl 352 | (blind-public-key bl pk bk ctx1) 353 | bk ctx2))) 354 | (os2ip 355 | (blind-dh 356 | bl sk 357 | (combine bl bf1 bf2) 358 | +secp256r1-g+)))))) 359 | 360 | (let* ((app (make-app +hdk-ecdh-p256+)) 361 | (hdk +hdk-root+) 362 | (pk-bl (get-key-info app hdk)) 363 | (pk-kem (nth-value 1 (delegate-key-creation app hdk)))) 364 | (multiple-value-bind (salt kh) (encap (kem (hdk app)) pk-kem) 365 | (let* ((bk (derive-blind-key (bl (hdk app)) salt)) 366 | (index 42) 367 | (ctx (create-context (hdk app) index)) 368 | (pk-expected (blind-public-key (bl (hdk app)) pk-bl bk ctx))) 369 | (accept-key app hdk kh index pk-expected)))) 370 | 371 | (let* ((unit (make-unit +hdk-ecdh-p256+)) 372 | (doc (activate unit))) 373 | (let* ((reader (make-reader +ecdh-p256+)) 374 | (device-data (prove-possession unit doc (pk reader)))) 375 | (assert (verify reader doc device-data))) 376 | (let ((pk-kem (request unit doc))) 377 | (multiple-value-bind (salt kh) (encap (kem (hdk (app unit))) pk-kem) 378 | (let* ((range '(0 1 2 3 4 5 6 7 8)) 379 | (docs (loop for i in range 380 | collect (make-document (hdk (app unit)) doc salt i)))) 381 | (loop for i in range for d in docs do (accept unit doc kh i d)) 382 | (assert (= 9 (length docs))) 383 | (loop for doc in docs do 384 | (let* ((reader (make-reader +ecdh-p256+)) 385 | (device-data (prove-possession unit doc (pk reader)))) 386 | (assert (verify reader doc device-data)))))))) 387 | 388 | (format t "Tests ran successfully~%") 389 | --------------------------------------------------------------------------------