Skip to content

Commit eca80c2

Browse files
brianmcgeeMa27
andcommitted
feat: add age plugin support
Signed-off-by: Brian McGee <brian@bmcgee.ie> Co-authored-by: Maximilian Bosch <maximilian@mbosch.me> Signed-off-by: Brian McGee <brian@bmcgee.ie>
1 parent 8019097 commit eca80c2

File tree

3 files changed

+70
-10
lines changed

3 files changed

+70
-10
lines changed

age/keysource.go

Lines changed: 66 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
package age
22

33
import (
4+
"bufio"
45
"bytes"
56
"errors"
7+
"filippo.io/age/plugin"
8+
"filippo.io/age/tui"
69
"fmt"
710
"io"
811
"os"
@@ -60,7 +63,7 @@ type MasterKey struct {
6063
parsedIdentities []age.Identity
6164
// parsedRecipient contains a parsed age public key.
6265
// It is used to lazy-load the Recipient at-most once.
63-
parsedRecipient *age.X25519Recipient
66+
parsedRecipient age.Recipient
6467
}
6568

6669
// MasterKeysFromRecipients takes a comma-separated list of Bech32-encoded
@@ -126,6 +129,7 @@ func (i ParsedIdentities) ApplyToMasterKey(key *MasterKey) {
126129

127130
// Encrypt takes a SOPS data key, encrypts it with the Recipient, and stores
128131
// the result in the EncryptedKey field.
132+
129133
func (key *MasterKey) Encrypt(dataKey []byte) error {
130134
if key.parsedRecipient == nil {
131135
parsedRecipient, err := parseRecipient(key.Recipient)
@@ -284,7 +288,12 @@ func (key *MasterKey) loadIdentities() (ParsedIdentities, error) {
284288

285289
var identities ParsedIdentities
286290
for n, r := range readers {
287-
ids, err := age.ParseIdentities(r)
291+
buf := new(strings.Builder)
292+
_, err := io.Copy(buf, r)
293+
if err != nil {
294+
return nil, fmt.Errorf("failed to read '%s' age identities: %w", n, err)
295+
}
296+
ids, err := parseIdentities(buf.String())
288297
if err != nil {
289298
return nil, fmt.Errorf("failed to parse '%s' age identities: %w", n, err)
290299
}
@@ -295,12 +304,21 @@ func (key *MasterKey) loadIdentities() (ParsedIdentities, error) {
295304

296305
// parseRecipient attempts to parse a string containing an encoded age public
297306
// key.
298-
func parseRecipient(recipient string) (*age.X25519Recipient, error) {
299-
parsedRecipient, err := age.ParseX25519Recipient(recipient)
300-
if err != nil {
301-
return nil, fmt.Errorf("failed to parse input as Bech32-encoded age public key: %w", err)
307+
func parseRecipient(recipient string) (age.Recipient, error) {
308+
switch {
309+
case strings.HasPrefix(recipient, "age1") && strings.Count(recipient, "1") > 1:
310+
parsedRecipient, err := plugin.NewRecipient(recipient, tui.PluginTerminalUI)
311+
if err != nil {
312+
return nil, fmt.Errorf("failed to parse input as age key from age plugin: %w", err)
313+
}
314+
return parsedRecipient, nil
315+
default:
316+
parsedRecipient, err := age.ParseX25519Recipient(recipient)
317+
if err != nil {
318+
return nil, fmt.Errorf("failed to parse input as Bech32-encoded age public key: %w", err)
319+
}
320+
return parsedRecipient, nil
302321
}
303-
return parsedRecipient, nil
304322
}
305323

306324
// parseIdentities attempts to parse the string set of encoded age identities.
@@ -309,11 +327,51 @@ func parseRecipient(recipient string) (*age.X25519Recipient, error) {
309327
func parseIdentities(identity ...string) (ParsedIdentities, error) {
310328
var identities []age.Identity
311329
for _, i := range identity {
312-
parsed, err := age.ParseIdentities(strings.NewReader(i))
330+
parsed, err := _parseIdentities(strings.NewReader(i))
313331
if err != nil {
314332
return nil, err
315333
}
316334
identities = append(identities, parsed...)
317335
}
318336
return identities, nil
319337
}
338+
339+
func parseIdentity(s string) (age.Identity, error) {
340+
switch {
341+
case strings.HasPrefix(s, "AGE-PLUGIN-"):
342+
return plugin.NewIdentity(s, tui.PluginTerminalUI)
343+
case strings.HasPrefix(s, "AGE-SECRET-KEY-1"):
344+
return age.ParseX25519Identity(s)
345+
default:
346+
return nil, fmt.Errorf("unknown identity type")
347+
}
348+
}
349+
350+
// parseIdentities is like age.ParseIdentities, but supports plugin identities.
351+
func _parseIdentities(f io.Reader) (ParsedIdentities, error) {
352+
const privateKeySizeLimit = 1 << 24 // 16 MiB
353+
var ids []age.Identity
354+
scanner := bufio.NewScanner(io.LimitReader(f, privateKeySizeLimit))
355+
var n int
356+
for scanner.Scan() {
357+
n++
358+
line := scanner.Text()
359+
if strings.HasPrefix(line, "#") || line == "" {
360+
continue
361+
}
362+
363+
i, err := parseIdentity(line)
364+
if err != nil {
365+
return nil, fmt.Errorf("error at line %d: %v", n, err)
366+
}
367+
ids = append(ids, i)
368+
369+
}
370+
if err := scanner.Err(); err != nil {
371+
return nil, fmt.Errorf("failed to read secret keys file: %v", err)
372+
}
373+
if len(ids) == 0 {
374+
return nil, fmt.Errorf("no secret keys found")
375+
}
376+
return ids, nil
377+
}

go.mod

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,3 +147,5 @@ require (
147147
google.golang.org/genproto/googleapis/api v0.0.0-20241118233622-e639e219e697 // indirect
148148
gopkg.in/yaml.v2 v2.4.0 // indirect
149149
)
150+
151+
replace filippo.io/age => github.com/brianmcgee/age v0.0.0-20241218154423-266c0940916d

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,6 @@ cloud.google.com/go/trace v1.11.2 h1:4ZmaBdL8Ng/ajrgKqY5jfvzqMXbrDcBsUGXOT9aqTtI
2727
cloud.google.com/go/trace v1.11.2/go.mod h1:bn7OwXd4pd5rFuAnTrzBuoZ4ax2XQeG3qNgYmfCy0Io=
2828
dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s=
2929
dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
30-
filippo.io/age v1.2.1 h1:X0TZjehAZylOIj4DubWYU1vWQxv9bJpo+Uu2/LGhi1o=
31-
filippo.io/age v1.2.1/go.mod h1:JL9ew2lTN+Pyft4RiNGguFfOpewKwSHm5ayKD/A4004=
3230
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
3331
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
3432
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.16.0 h1:JZg6HRh6W6U4OLl6lk7BZ7BLisIzM9dG1R50zUk9C/M=
@@ -107,6 +105,8 @@ github.com/aws/smithy-go v1.22.1 h1:/HPHZQ0g7f4eUeK6HKglFz8uwVfZKgoI25rb/J+dnro=
107105
github.com/aws/smithy-go v1.22.1/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg=
108106
github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ=
109107
github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
108+
github.com/brianmcgee/age v0.0.0-20241218154423-266c0940916d h1:6z3SW9YQdT4SVX/qT43NxZvOxek9ZgYbY4R7ACzBPvE=
109+
github.com/brianmcgee/age v0.0.0-20241218154423-266c0940916d/go.mod h1:JL9ew2lTN+Pyft4RiNGguFfOpewKwSHm5ayKD/A4004=
110110
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
111111
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
112112
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=

0 commit comments

Comments
 (0)