Skip to content

feat(helper): Add hardware token ECDH decryption support#356

Open
83noit wants to merge 2 commits intoProtonMail:v2from
83noit:feat/hardware-token-ecdh
Open

feat(helper): Add hardware token ECDH decryption support#356
83noit wants to merge 2 commits intoProtonMail:v2from
83noit:feat/hardware-token-ecdh

Conversation

@83noit
Copy link
Copy Markdown

@83noit 83noit commented Apr 1, 2026

Summary

Adds helper functions to support ECDH decryption when the private key
operation (Decaps) is performed by an external hardware token such as a
YubiKey or OpenPGP smartcard.

  • GetEncryptedKeyFieldsFromMessage — extracts key ID, algorithm, and
    raw MPI fields from the first PKESK packet, so the ephemeral point
    can be sent to a hardware token
  • GetEncryptedMPI1FromMessage — gomobile-compatible wrapper returning
    only the ephemeral point (gomobile cannot bind functions with more
    than 2 return values)
  • DecryptMessageWithECDHSharedSecret — completes ECDH decryption
    given a pre-computed shared secret from an external Decaps operation,
    performing KDF (RFC 6637 §8) and AES key unwrap (RFC 3394) to
    recover the session key

No existing behaviour is changed. All new functions are additive.

Motivation

OpenPGP smartcards support ECDH via PSO:DECIPHER, which takes the
ephemeral point and returns the raw shared secret without exposing
the private scalar. The current gopenpgp API requires a full private
key for decryption.

These helpers enable the split workflow:

  1. Extract ephemeral point from the message (GetEncryptedKeyFieldsFromMessage / GetEncryptedMPI1FromMessage)
  2. Send to hardware token for Decaps
  3. Complete KDF + unwrap + decrypt (DecryptMessageWithECDHSharedSecret)

This follows the existing helper pattern of composing lower-level
go-crypto operations into gomobile-compatible convenience functions.

Depends on ProtonMail/go-crypto#307 adding GetEncryptedMPI1/2,
GetECDHOid, and ecdh.DecryptWithSharedSecret.

Relates to #174.

Test plan

  • TestGetEncryptedKeyFieldsFromMessage — RSA message field extraction
  • TestDecryptMessageWithECDHSharedSecret — full ECDH roundtrip:
    encrypt → extract MPI1 → manual Decaps → DecryptMessageWithECDHSharedSecret
  • Full test suite passes (go test ./...)

83noit added 2 commits April 1, 2026 18:08
Add two new helper functions for integrating OpenPGP hardware tokens
(YubiKey, smartcards) with gopenpgp:

GetEncryptedKeyFieldsFromMessage extracts the key ID, algorithm, and
raw encrypted MPI fields from the first PKESK packet in a PGP message.
This allows callers to send the ephemeral point (MPI1) to a hardware
token for the ECDH Decaps operation.

DecryptMessageWithECDHSharedSecret completes ECDH decryption given a
pre-computed shared secret from an external Decaps operation. It
performs the KDF (RFC 6637 §8) and AES key unwrap (RFC 3394) to
recover the session key, then decrypts the message. The private key
is never needed.

Depends on go-crypto additions: EncryptedKey.GetEncryptedMPI1/2,
PublicKey.GetECDHOid, and ecdh.DecryptWithSharedSecret.

Relates to ProtonMail#174.
Adds a gomobile-compatible wrapper around GetEncryptedKeyFieldsFromMessage
that returns only the first MPI field. gomobile cannot bind functions with
more than 2 return values, so this single-purpose wrapper is needed for
iOS integration.
@83noit
Copy link
Copy Markdown
Author

83noit commented Apr 1, 2026

Note on v2 vs v3: This PR targets v2 deliberately — the helper package doesn't exist in main (v3), which has moved to a builder API in crypto/. The gomobile binding use case relies on the flat function signatures that the helper package provides, and a hardware token hook in v3 would be a more architectural change (likely a callback/delegate on DecryptionHandleBuilder).

@83noit
Copy link
Copy Markdown
Author

83noit commented Apr 2, 2026

Note: This PR depends on ProtonMail/go-crypto#307, which has been reworked based on reviewer feedback to use an interface-based approach (ecdh.Decapsulator) instead of exposing raw MPI getters.

Once the go-crypto PR is merged, I'll rework this PR to match the new design — the gopenpgp surface will be simpler since the library now handles KDF/unwrap internally. Please hold off on reviewing until then.

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.

1 participant