Skip to content

age-piv-p256 recipient stanza#31

Closed
remko wants to merge 1 commit intoC2SP:mainfrom
remko:age-piv-p256
Closed

age-piv-p256 recipient stanza#31
remko wants to merge 1 commit intoC2SP:mainfrom
remko:age-piv-p256

Conversation

@remko
Copy link
Copy Markdown

@remko remko commented May 10, 2023

This is an initial draft of a specification for the age piv-p256 recipient stanza, as used by age-plugin-yubikey and age-plugin-se.

Comment on lines +52 to +64
2. The second argument is the base-64 encdoded tag of the recipient,
consisting of the first 4 bytes of the SHA-256 hash of the
recipient:

tag = SHA-256(recipient)[0..4]
Copy link
Copy Markdown
Author

@remko remko May 10, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was wondering if the tag should be salted, to hide relations between stanzas (in different files) for the same recipient and/or anonymize the recipient

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm what do you have in mind?

Maybe x = CSPRNG(4); tag = x || HMAC(recipient, x)[0..4] so that only someone who knows the recipient can link files?

That is indeed a nice step forward in privacy! Might be worth making a backwards-incompatible change. @str4d, what do you think?

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's pretty common to just use the x-coordinate, similar to X25519

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's pretty common to just use the x-coordinate, similar to X25519

For the shared ECDH output yes, but the input is usually either a compressed or an uncompressed point, no? x-coordinate-only ECDH like in X25519 is very uncommon on P-256. (I recently surveyed a bunch of ECDH and ECIES implementations while designing the crypto/ecdh.)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Which spec uses a SEC1-encoded point as the input?

TLS 1.3 (by way of IEEE1363) uses the x-coordinate, for example:

https://datatracker.ietf.org/doc/html/rfc8446#section-7.4.2

7.4.2. Elliptic Curve Diffie-Hellman

For secp256r1, secp384r1, and secp521r1, ECDH calculations (including
parameter and key generation as well as the shared secret
calculation) are performed according to [IEEE1363] using the
ECKAS-DH1 scheme with the identity map as the key derivation function
(KDF), so that the shared secret is the x-coordinate of the ECDH
shared secret elliptic curve point represented as an octet string.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My bad, I was originally trying to reply to this comment

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe x = CSPRNG(4); tag = x || HMAC(recipient, x)[0..4] so that only someone who knows the recipient can link files?

That's indeed what I had in mind, except I was thinking of putting x in a separate stanza argument instead of concatenating it (to avoid a custom split)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason to prefer this construction instead of HMAC(recipient, ephemeral share)[0..4]?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IANAC, but sounds nice and simple to me.

age-piv-p256.md Outdated
Comment on lines +92 to +93
If the shared secret is all 0x00 bytes, the identity implementation MUST
abort.
Copy link
Copy Markdown
Member

@FiloSottile FiloSottile Aug 25, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
If the shared secret is all 0x00 bytes, the identity implementation MUST
abort.
If the shared secret is the point at infinity, the identity implementation
MUST abort.

The infinity is going to be represented differently by different libraries, and often not as a long string of 0x00.

salt = ephemeral share || recipient
info = "piv-p256"
shared secret = P256(ephemeral secret, recipient)
wrap key = HKDF-SHA-256(ikm = shared secret, salt, info)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we make other backward-incompatible changes, we should swap salt and info here. HKDF is... very theoretically documented, and this is a mistake I made in the original design. It doesn't have practical consequences, and I think one can prove the security of the current design, but it makes applying more sophisticated proofs annoying.

the recipient implementation as follows:

ephemeral secret = read(CSPRNG, 32)
ephemeral share = P256(ephemeral secret, basepoint)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"P256" is not a well-defined ECDH operation the way X25519 is.

We should define it somewhere as scalar multiplication over P-256, with SEC 1 point decoding and encoding. (Assuming that's what we are doing? But then why is the third argument 32 bytes?)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My comments about using the x-coordinate for ECDH with P-256 (and really all the NIST P-curves) were supposed to be in reply to this comment. I guess I must've clicked the wrong one in my previous replies.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Replying to #31 (comment).

The ECDH output is the x-coordinate. The ECDH input (that is, the third stanza argument here) is 0x04 || x || y, which is SEC 1.

https://datatracker.ietf.org/doc/html/rfc8446#section-4.2.8.2

Comment on lines +52 to +64
2. The second argument is the base-64 encdoded tag of the recipient,
consisting of the first 4 bytes of the SHA-256 hash of the
recipient:

tag = SHA-256(recipient)[0..4]
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm what do you have in mind?

Maybe x = CSPRNG(4); tag = x || HMAC(recipient, x)[0..4] so that only someone who knows the recipient can link files?

That is indeed a nice step forward in privacy! Might be worth making a backwards-incompatible change. @str4d, what do you think?


## Recipient stanza

A piv-p256 recipient stanza has three arguments.
Copy link
Copy Markdown
Member

@FiloSottile FiloSottile Aug 25, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we make other changes, we should drop "piv" from the name.

Maybe tagged-p256? Or hw-p256? Or just p256?

I assume @Foxboron could use it for age-plugin-tpm, too.

I wonder if we should standardize the recipient (not identity) format, and add native support to age(1) so that one doesn't need the plugin to encrypt to PIV/TPM/SE/any other P-256 identity.

/cc @str4d

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can probably use this, I wasn't aware of this PR work when I originally wrote age-plugin-tpm.

Copy link
Copy Markdown
Author

@remko remko Aug 26, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Native support sounds neat.

  • You don't need to install a plugin to encrypt to these plugins. For encrypting to a different person, it feels odd that you need to install age-plugin-se even if you don't have an Apple device. It also avoids the plugin discovery problem where you need to explain which plugin people have to download to encrypt a message to you.
  • For age-plugin-se, I could drop all cross-platform support, since the parts that don't involve encryption are only useful on macOS. In my case, it would for example avoid all the trouble I have because my plugin is written in Swift, which doesn't support musl-based Linux systems. I can imagine there are other hardware encryption plugins that don't make sense on all platforms.
  • It hides information about which type of token corresponds to a certain recipient
  • If there's a backwards-incompatible change now, only half of the existing state machines need to be updated

@Foxboron
Copy link
Copy Markdown

How can we move this forward? It would be nice to have a generic implementation on the age side of things :)

I'm contemplating implementing the spec as is into age-plugin-tpm but if the naming isn't settled that would maybe be a bit annoying?

@remko
Copy link
Copy Markdown
Author

remko commented Feb 16, 2024

How can we move this forward? It would be nice to have a generic implementation on the age side of things :)

I was hoping @str4d could give some input on the document and the change suggestions, before I started incorporating the changes (and editorial fixes).

The following backwards-incompatible changes for the recipient stanza format are on the table:

One possible way forward is to keep piv-p256 as-is, use the current version of this document (with the suggested editorial changes) as a historical description of the spec, and incorporate the changes into a different recipient format under a different recipient name (tagged-p256, hw-p256, p256), which new implementations should use.

Then there is the suggestion to standardise the recipient format and implement it in age. Age could decide to (only) implement the new recipient stanza as described above, and existing implementations then don't need to bother with implementing the encryption side of the new recipient stanza, and just need to switch to generating the new recipient when supported in age.

@Foxboron
Copy link
Copy Markdown

I think it would make sense to leave the current piv-p256 as-is and maybe just standardize this as p256?

@FiloSottile
Copy link
Copy Markdown
Member

Thank you for getting this started. I think it's becoming clear that a standard tagged P-256 recipient type would be valuable across multiple plugins. I propose we

  1. change the tag to HMAC(recipient, ephemeral share)[0..4];
  2. swap HKDF salt and info;
  3. name the new stanza p256-tag;
  4. also specify a p256tag recipient plugin type age1p256tag1...;
  5. implement native support for p256tag recipients in age(1) and maybe rage(1);
  6. migrate plugins to the new stanza and recipient, but have them support decrypting the old one stanza ~forever.

Plugins might choose to provide a recipient-only age-plugin-p256tag binary for compatibility with older versions of age.

How does that sound @str4d, @Foxboron, @remko?

@Foxboron
Copy link
Copy Markdown

I think this sounds great!

@FiloSottile
Copy link
Copy Markdown
Member

Actually, do we need a new spec for this? Why not add a section to c2sp.org/age?

@Foxboron
Copy link
Copy Markdown

Considering we have two other "native recipient types" I don't think a separate standard is required really. It might be nicer to ensure the age spec doesn't become super long?

@remko
Copy link
Copy Markdown
Author

remko commented Jun 23, 2024

Actually, do we need a new spec for this? Why not add a section to c2sp.org/age?

Works for me. How do you suggest to proceed with this spec: It can be released as a 'historical' spec for documenting existing behavior of plugins, with a note forwarding to the new recipient type and recommend to implement that. Or is it not worth the trouble (and the risk of people implementing the wrong spec), and we drop it altogether, and not document anything?

@remko
Copy link
Copy Markdown
Author

remko commented Jul 1, 2024

I propose we

FYI, the current HEAD of age-plugin-se (brew install --HEAD age-plugin-se) has an implementation of this proposal, behind a hidden --recipient-type=p256tag flag.

@Foxboron
Copy link
Copy Markdown

@FiloSottile are you going to propose the addition to the age spec or would it be useful if someone else finds the time to draft it?

@FiloSottile
Copy link
Copy Markdown
Member

@FiloSottile are you going to propose the addition to the age spec or would it be useful if someone else finds the time to draft it?

If anyone finds the time to make a PR against the age spec, that would be awesome!

@FiloSottile
Copy link
Copy Markdown
Member

How do you suggest to proceed with this spec: It can be released as a 'historical' spec for documenting existing behavior of plugins, with a note forwarding to the new recipient type and recommend to implement that. Or is it not worth the trouble (and the risk of people implementing the wrong spec), and we drop it altogether, and not document anything?

That's a good question, it would be nice for future-proofing to have the spec somewhere I guess. @C2SP/stewards how do you feel about "historical" specs?

@remko
Copy link
Copy Markdown
Author

remko commented Aug 22, 2024

If anyone finds the time to make a PR against the age spec, that would be awesome!

The PR can be found here: #96

FiloSottile added a commit that referenced this pull request Aug 16, 2025
Based on a previous proposal by @remko, with the tag design suggested by
@erincandescent.

Closes #96
Closes #31
FiloSottile added a commit that referenced this pull request Aug 16, 2025
Based on a previous proposal by @remko, with the tag design suggested by
@erincandescent.

Closes #96
Closes #31

Co-authored-by: Jack Grigg <thestr4d@gmail.com>
FiloSottile added a commit that referenced this pull request Aug 17, 2025
Based on a previous proposal by @remko, with the tag design suggested by
@erincandescent.

Closes #96
Closes #31

Co-authored-by: Jack Grigg <thestr4d@gmail.com>
FiloSottile added a commit that referenced this pull request Nov 16, 2025
Based on a previous proposal by @remko, with the tag design suggested by
@erincandescent.

Closes #96
Closes #31

Co-authored-by: Jack Grigg <thestr4d@gmail.com>
FiloSottile added a commit that referenced this pull request Dec 7, 2025
Based on a previous proposal by @remko, with the tag design suggested by
@erincandescent.

Closes #96
Closes #31

Co-authored-by: Jack Grigg <thestr4d@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants