From de158f906b1f7d1e7f7cca572f513d17c82954df Mon Sep 17 00:00:00 2001 From: Filippo Valsorda Date: Sun, 7 Dec 2025 20:32:06 +0100 Subject: [PATCH] cmd/age-plugin-tag,cmd/age-plugin-tagpq: new backward compatibility plugins --- cmd/age-plugin-tag/plugin-tag.go | 32 +++++++++++++++++++++++++++ cmd/age-plugin-tagpq/plugin-tagpq.go | 33 ++++++++++++++++++++++++++++ doc/age.1.ronn | 14 ++++++++++++ 3 files changed, 79 insertions(+) create mode 100644 cmd/age-plugin-tag/plugin-tag.go create mode 100644 cmd/age-plugin-tagpq/plugin-tagpq.go diff --git a/cmd/age-plugin-tag/plugin-tag.go b/cmd/age-plugin-tag/plugin-tag.go new file mode 100644 index 0000000..a3c5269 --- /dev/null +++ b/cmd/age-plugin-tag/plugin-tag.go @@ -0,0 +1,32 @@ +package main + +import ( + "flag" + "fmt" + "log" + "os" + + "filippo.io/age" + "filippo.io/age/plugin" + "filippo.io/age/tag" +) + +const usage = `age-plugin-tag is an age plugin for P-256 tagged recipients. These are supported +natively by age v1.3.0 and later, but this plugin can be placed in $PATH to add +support to any version and implementation of age that supports plugins. + +Usually, tagged recipients are the public side of private keys held in hardware, +where the identity side is handled by a different plugin.` + +func main() { + flag.Usage = func() { fmt.Fprintf(os.Stderr, "%s\n", usage) } + + p, err := plugin.New("tag") + if err != nil { + log.Fatal(err) + } + p.HandleRecipient(func(b []byte) (age.Recipient, error) { + return tag.NewClassicRecipient(b) + }) + os.Exit(p.Main()) +} diff --git a/cmd/age-plugin-tagpq/plugin-tagpq.go b/cmd/age-plugin-tagpq/plugin-tagpq.go new file mode 100644 index 0000000..8577f65 --- /dev/null +++ b/cmd/age-plugin-tagpq/plugin-tagpq.go @@ -0,0 +1,33 @@ +package main + +import ( + "flag" + "fmt" + "log" + "os" + + "filippo.io/age" + "filippo.io/age/plugin" + "filippo.io/age/tag" +) + +const usage = `age-plugin-tagpq is an age plugin for ML-KEM-768 + P-256 post-quantum hybrid +tagged recipients. These are supported natively by age v1.3.0 and later, but +this plugin can be placed in $PATH to add support to any version and +implementation of age that supports plugins. + +Usually, tagged recipients are the public side of private keys held in hardware, +where the identity side is handled by a different plugin.` + +func main() { + flag.Usage = func() { fmt.Fprintf(os.Stderr, "%s\n", usage) } + + p, err := plugin.New("tagpq") + if err != nil { + log.Fatal(err) + } + p.HandleRecipient(func(b []byte) (age.Recipient, error) { + return tag.NewHybridRecipient(b) + }) + os.Exit(p.Main()) +} diff --git a/doc/age.1.ronn b/doc/age.1.ronn index 64d0c4b..ef76785 100644 --- a/doc/age.1.ronn +++ b/doc/age.1.ronn @@ -237,6 +237,20 @@ instruct the user to perform encryption with the `-e`/`--encrypt` and doesn't make sense (such as a password-encryption plugin) may instruct the user to use the `-j` flag. +#### Tagged recipients + +`age` can natively encrypt to recipients starting with `age1tag1` (using P-256 +ECDH) or `age1tagpq1` (using the ML-KEM-768 + P-256 post-quantum hybrid). These +are intended to be the public side of private keys held in hardware. + +They are directly supported to avoid the need to install the plugin, which may +be platform-specific, on the encrypting side. + +The tag reduces privacy, by allowing an observer to correlate files with a +recipient (but not files amongst them without knowledge of the recipient), +but this is also a desirable property for hardware keys that require user +interaction for each decryption operation. + ## EXIT STATUS `age` will exit 0 if and only if encryption or decryption are successful for the