age,cmd/age,cmd/age-keygen: add post-quantum hybrid keys

This commit is contained in:
Filippo Valsorda
2025-11-17 12:32:50 +01:00
committed by Filippo Valsorda
parent 6ece9e45ee
commit c6fcb5300c
20 changed files with 720 additions and 91 deletions

View File

@@ -16,10 +16,10 @@ import (
//
// This is the same syntax as the private key files accepted by the CLI, except
// the CLI also accepts SSH private keys, which are not recommended for the
// average application.
// average application, and plugins, which involve invoking external programs.
//
// Currently, all returned values are of type *X25519Identity, but different
// types might be returned in the future.
// Currently, all returned values are of type *[X25519Identity] or
// *[HybridIdentity], but different types might be returned in the future.
func ParseIdentities(f io.Reader) ([]Identity, error) {
const privateKeySizeLimit = 1 << 24 // 16 MiB
var ids []Identity
@@ -31,7 +31,7 @@ func ParseIdentities(f io.Reader) ([]Identity, error) {
if strings.HasPrefix(line, "#") || line == "" {
continue
}
i, err := ParseX25519Identity(line)
i, err := parseIdentity(line)
if err != nil {
return nil, fmt.Errorf("error at line %d: %v", n, err)
}
@@ -46,15 +46,27 @@ func ParseIdentities(f io.Reader) ([]Identity, error) {
return ids, nil
}
func parseIdentity(arg string) (Identity, error) {
switch {
case strings.HasPrefix(arg, "AGE-SECRET-KEY-1"):
return ParseX25519Identity(arg)
case strings.HasPrefix(arg, "AGE-SECRET-KEY-PQ-1"):
return ParseHybridIdentity(arg)
default:
return nil, fmt.Errorf("unknown identity type: %q", arg)
}
}
// ParseRecipients parses a file with one or more public key encodings, one per
// line. Empty lines and lines starting with "#" are ignored.
//
// This is the same syntax as the recipients files accepted by the CLI, except
// the CLI also accepts SSH recipients, which are not recommended for the
// average application.
// average application, tagged recipients, which have different privacy
// properties, and plugins, which involve invoking external programs.
//
// Currently, all returned values are of type *X25519Recipient, but different
// types might be returned in the future.
// Currently, all returned values are of type *[X25519Recipient] or
// *[HybridRecipient] but different types might be returned in the future.
func ParseRecipients(f io.Reader) ([]Recipient, error) {
const recipientFileSizeLimit = 1 << 24 // 16 MiB
var recs []Recipient
@@ -66,7 +78,7 @@ func ParseRecipients(f io.Reader) ([]Recipient, error) {
if strings.HasPrefix(line, "#") || line == "" {
continue
}
r, err := ParseX25519Recipient(line)
r, err := parseRecipient(line)
if err != nil {
// Hide the error since it might unintentionally leak the contents
// of confidential files.
@@ -82,3 +94,14 @@ func ParseRecipients(f io.Reader) ([]Recipient, error) {
}
return recs, nil
}
func parseRecipient(arg string) (Recipient, error) {
switch {
case strings.HasPrefix(arg, "age1pq1"):
return ParseHybridRecipient(arg)
case strings.HasPrefix(arg, "age1"):
return ParseX25519Recipient(arg)
default:
return nil, fmt.Errorf("unknown recipient type: %q", arg)
}
}