mirror of
https://github.com/FiloSottile/age.git
synced 2026-05-02 08:05:50 +00:00
internal/age: surface format.Recipient as type Stanza
This commit is contained in:
@@ -11,7 +11,6 @@ import (
|
||||
"os"
|
||||
|
||||
"filippo.io/age/internal/age"
|
||||
"filippo.io/age/internal/format"
|
||||
"golang.org/x/crypto/ssh/terminal"
|
||||
)
|
||||
|
||||
@@ -25,7 +24,7 @@ func (i *LazyScryptIdentity) Type() string {
|
||||
return "scrypt"
|
||||
}
|
||||
|
||||
func (i *LazyScryptIdentity) Unwrap(block *format.Recipient) (fileKey []byte, err error) {
|
||||
func (i *LazyScryptIdentity) Unwrap(block *age.Stanza) (fileKey []byte, err error) {
|
||||
pass, err := i.Passphrase()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not read passphrase: %v", err)
|
||||
|
||||
@@ -25,7 +25,7 @@ import (
|
||||
// the identity, any other error might be considered fatal.
|
||||
type Identity interface {
|
||||
Type() string
|
||||
Unwrap(block *format.Recipient) (fileKey []byte, err error)
|
||||
Unwrap(block *Stanza) (fileKey []byte, err error)
|
||||
}
|
||||
|
||||
// IdentityMatcher can be optionally implemented by an Identity that can
|
||||
@@ -37,7 +37,7 @@ type Identity interface {
|
||||
// other error might be considered fatal.
|
||||
type IdentityMatcher interface {
|
||||
Identity
|
||||
Match(block *format.Recipient) error
|
||||
Match(block *Stanza) error
|
||||
}
|
||||
|
||||
var ErrIncorrectIdentity = errors.New("incorrect identity for recipient block")
|
||||
@@ -46,7 +46,15 @@ var ErrIncorrectIdentity = errors.New("incorrect identity for recipient block")
|
||||
// key to a recipient stanza.
|
||||
type Recipient interface {
|
||||
Type() string
|
||||
Wrap(fileKey []byte) (*format.Recipient, error)
|
||||
Wrap(fileKey []byte) (*Stanza, error)
|
||||
}
|
||||
|
||||
// A Stanza is a section of the age header that encapsulates the file key as
|
||||
// encrypted to a specific recipient.
|
||||
type Stanza struct {
|
||||
Type string
|
||||
Args []string
|
||||
Body []byte
|
||||
}
|
||||
|
||||
// Encrypt returns a WriteCloser. Writes to the returned value are encrypted and
|
||||
@@ -74,7 +82,7 @@ func Encrypt(dst io.Writer, recipients ...Recipient) (io.WriteCloser, error) {
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to wrap key for recipient #%d: %v", i, err)
|
||||
}
|
||||
hdr.Recipients = append(hdr.Recipients, block)
|
||||
hdr.Recipients = append(hdr.Recipients, (*format.Stanza)(block))
|
||||
}
|
||||
if mac, err := headerMAC(fileKey, hdr); err != nil {
|
||||
return nil, fmt.Errorf("failed to compute header MAC: %v", err)
|
||||
@@ -123,7 +131,7 @@ RecipientsLoop:
|
||||
}
|
||||
|
||||
if i, ok := i.(IdentityMatcher); ok {
|
||||
err := i.Match(r)
|
||||
err := i.Match((*Stanza)(r))
|
||||
if err != nil {
|
||||
if err == ErrIncorrectIdentity {
|
||||
continue
|
||||
@@ -132,7 +140,7 @@ RecipientsLoop:
|
||||
}
|
||||
}
|
||||
|
||||
fileKey, err = i.Unwrap(r)
|
||||
fileKey, err = i.Unwrap((*Stanza)(r))
|
||||
if err != nil {
|
||||
if err == ErrIncorrectIdentity {
|
||||
// TODO: we should collect these errors and return them as an
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"filippo.io/age/internal/age"
|
||||
"filippo.io/age/internal/format"
|
||||
"golang.org/x/crypto/curve25519"
|
||||
)
|
||||
|
||||
@@ -55,7 +56,7 @@ func TestX25519RoundTrip(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
b := &bytes.Buffer{}
|
||||
block.Marshal(b)
|
||||
(*format.Stanza)(block).Marshal(b)
|
||||
t.Logf("%s", b.Bytes())
|
||||
|
||||
out, err := i.Unwrap(block)
|
||||
@@ -94,7 +95,7 @@ func TestScryptRoundTrip(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
b := &bytes.Buffer{}
|
||||
block.Marshal(b)
|
||||
(*format.Stanza)(block).Marshal(b)
|
||||
t.Logf("%s", b.Bytes())
|
||||
|
||||
out, err := i.Unwrap(block)
|
||||
|
||||
@@ -60,14 +60,14 @@ func (r *ScryptRecipient) SetWorkFactor(logN int) {
|
||||
r.workFactor = logN
|
||||
}
|
||||
|
||||
func (r *ScryptRecipient) Wrap(fileKey []byte) (*format.Recipient, error) {
|
||||
func (r *ScryptRecipient) Wrap(fileKey []byte) (*Stanza, error) {
|
||||
salt := make([]byte, 16)
|
||||
if _, err := rand.Read(salt[:]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
logN := r.workFactor
|
||||
l := &format.Recipient{
|
||||
l := &Stanza{
|
||||
Type: "scrypt",
|
||||
Args: []string{format.EncodeToString(salt), strconv.Itoa(logN)},
|
||||
}
|
||||
@@ -122,7 +122,7 @@ func (i *ScryptIdentity) SetMaxWorkFactor(logN int) {
|
||||
i.maxWorkFactor = logN
|
||||
}
|
||||
|
||||
func (i *ScryptIdentity) Unwrap(block *format.Recipient) ([]byte, error) {
|
||||
func (i *ScryptIdentity) Unwrap(block *Stanza) ([]byte, error) {
|
||||
if block.Type != "scrypt" {
|
||||
return nil, ErrIncorrectIdentity
|
||||
}
|
||||
|
||||
@@ -61,7 +61,7 @@ func ParseX25519Recipient(s string) (*X25519Recipient, error) {
|
||||
return r, nil
|
||||
}
|
||||
|
||||
func (r *X25519Recipient) Wrap(fileKey []byte) (*format.Recipient, error) {
|
||||
func (r *X25519Recipient) Wrap(fileKey []byte) (*Stanza, error) {
|
||||
ephemeral := make([]byte, curve25519.ScalarSize)
|
||||
if _, err := rand.Read(ephemeral); err != nil {
|
||||
return nil, err
|
||||
@@ -76,7 +76,7 @@ func (r *X25519Recipient) Wrap(fileKey []byte) (*format.Recipient, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
l := &format.Recipient{
|
||||
l := &Stanza{
|
||||
Type: "X25519",
|
||||
Args: []string{format.EncodeToString(ourPublicKey)},
|
||||
}
|
||||
@@ -153,7 +153,7 @@ func ParseX25519Identity(s string) (*X25519Identity, error) {
|
||||
return r, nil
|
||||
}
|
||||
|
||||
func (i *X25519Identity) Unwrap(block *format.Recipient) ([]byte, error) {
|
||||
func (i *X25519Identity) Unwrap(block *Stanza) ([]byte, error) {
|
||||
if block.Type != "X25519" {
|
||||
return nil, ErrIncorrectIdentity
|
||||
}
|
||||
|
||||
@@ -67,8 +67,8 @@ func NewRSARecipient(pk ssh.PublicKey) (*RSARecipient, error) {
|
||||
return r, nil
|
||||
}
|
||||
|
||||
func (r *RSARecipient) Wrap(fileKey []byte) (*format.Recipient, error) {
|
||||
l := &format.Recipient{
|
||||
func (r *RSARecipient) Wrap(fileKey []byte) (*age.Stanza, error) {
|
||||
l := &age.Stanza{
|
||||
Type: "ssh-rsa",
|
||||
Args: []string{sshFingerprint(r.sshKey)},
|
||||
}
|
||||
@@ -103,7 +103,7 @@ func NewRSAIdentity(key *rsa.PrivateKey) (*RSAIdentity, error) {
|
||||
return i, nil
|
||||
}
|
||||
|
||||
func (i *RSAIdentity) Unwrap(block *format.Recipient) ([]byte, error) {
|
||||
func (i *RSAIdentity) Unwrap(block *age.Stanza) ([]byte, error) {
|
||||
if block.Type != "ssh-rsa" {
|
||||
return nil, age.ErrIncorrectIdentity
|
||||
}
|
||||
@@ -207,7 +207,7 @@ func ed25519PublicKeyToCurve25519(pk ed25519.PublicKey) []byte {
|
||||
|
||||
const ed25519Label = "age-encryption.org/v1/ssh-ed25519"
|
||||
|
||||
func (r *Ed25519Recipient) Wrap(fileKey []byte) (*format.Recipient, error) {
|
||||
func (r *Ed25519Recipient) Wrap(fileKey []byte) (*age.Stanza, error) {
|
||||
ephemeral := make([]byte, curve25519.ScalarSize)
|
||||
if _, err := rand.Read(ephemeral); err != nil {
|
||||
return nil, err
|
||||
@@ -229,7 +229,7 @@ func (r *Ed25519Recipient) Wrap(fileKey []byte) (*format.Recipient, error) {
|
||||
}
|
||||
sharedSecret, _ = curve25519.X25519(tweak, sharedSecret)
|
||||
|
||||
l := &format.Recipient{
|
||||
l := &age.Stanza{
|
||||
Type: "ssh-ed25519",
|
||||
Args: []string{sshFingerprint(r.sshKey),
|
||||
format.EncodeToString(ourPublicKey[:])},
|
||||
@@ -298,7 +298,7 @@ func ed25519PrivateKeyToCurve25519(pk ed25519.PrivateKey) []byte {
|
||||
return out[:curve25519.ScalarSize]
|
||||
}
|
||||
|
||||
func (i *Ed25519Identity) Unwrap(block *format.Recipient) ([]byte, error) {
|
||||
func (i *Ed25519Identity) Unwrap(block *age.Stanza) ([]byte, error) {
|
||||
if block.Type != "ssh-ed25519" {
|
||||
return nil, age.ErrIncorrectIdentity
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"filippo.io/age/internal/agessh"
|
||||
"filippo.io/age/internal/format"
|
||||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
@@ -49,7 +50,7 @@ func TestSSHRSARoundTrip(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
b := &bytes.Buffer{}
|
||||
block.Marshal(b)
|
||||
(*format.Stanza)(block).Marshal(b)
|
||||
t.Logf("%s", b.Bytes())
|
||||
|
||||
out, err := i.Unwrap(block)
|
||||
@@ -94,7 +95,7 @@ func TestSSHEd25519RoundTrip(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
b := &bytes.Buffer{}
|
||||
block.Marshal(b)
|
||||
(*format.Stanza)(block).Marshal(b)
|
||||
t.Logf("%s", b.Bytes())
|
||||
|
||||
out, err := i.Unwrap(block)
|
||||
|
||||
@@ -12,7 +12,6 @@ import (
|
||||
"fmt"
|
||||
|
||||
"filippo.io/age/internal/age"
|
||||
"filippo.io/age/internal/format"
|
||||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
@@ -65,7 +64,7 @@ func (i *EncryptedSSHIdentity) Type() string {
|
||||
// Unwrap implements age.Identity. If the private key is still encrypted, it
|
||||
// will request the passphrase. The decrypted private key will be cached after
|
||||
// the first successful invocation.
|
||||
func (i *EncryptedSSHIdentity) Unwrap(block *format.Recipient) (fileKey []byte, err error) {
|
||||
func (i *EncryptedSSHIdentity) Unwrap(block *age.Stanza) (fileKey []byte, err error) {
|
||||
if i.decrypted != nil {
|
||||
return i.decrypted.Unwrap(block)
|
||||
}
|
||||
@@ -99,7 +98,7 @@ func (i *EncryptedSSHIdentity) Unwrap(block *format.Recipient) (fileKey []byte,
|
||||
|
||||
// Match implements age.IdentityMatcher without decrypting the private key, to
|
||||
// ensure the passphrase is only obtained if necessary.
|
||||
func (i *EncryptedSSHIdentity) Match(block *format.Recipient) error {
|
||||
func (i *EncryptedSSHIdentity) Match(block *age.Stanza) error {
|
||||
if block.Type != i.Type() {
|
||||
return age.ErrIncorrectIdentity
|
||||
}
|
||||
|
||||
@@ -21,8 +21,10 @@ import (
|
||||
"filippo.io/age/internal/format"
|
||||
)
|
||||
|
||||
const Header = "-----BEGIN AGE ENCRYPTED FILE-----"
|
||||
const Footer = "-----END AGE ENCRYPTED FILE-----"
|
||||
const (
|
||||
Header = "-----BEGIN AGE ENCRYPTED FILE-----"
|
||||
Footer = "-----END AGE ENCRYPTED FILE-----"
|
||||
)
|
||||
|
||||
type armoredWriter struct {
|
||||
started, closed bool
|
||||
|
||||
@@ -18,11 +18,13 @@ import (
|
||||
)
|
||||
|
||||
type Header struct {
|
||||
Recipients []*Recipient
|
||||
Recipients []*Stanza
|
||||
MAC []byte
|
||||
}
|
||||
|
||||
type Recipient struct {
|
||||
// Stanza is assignable to age.Stanza, and if this package is made public,
|
||||
// age.Stanza can be made a type alias of this type.
|
||||
type Stanza struct {
|
||||
Type string
|
||||
Args []string
|
||||
Body []byte
|
||||
@@ -83,7 +85,7 @@ const intro = "age-encryption.org/v1\n"
|
||||
var recipientPrefix = []byte("->")
|
||||
var footerPrefix = []byte("---")
|
||||
|
||||
func (r *Recipient) Marshal(w io.Writer) error {
|
||||
func (r *Stanza) Marshal(w io.Writer) error {
|
||||
if _, err := w.Write(recipientPrefix); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -155,7 +157,7 @@ func Parse(input io.Reader) (*Header, io.Reader, error) {
|
||||
return nil, nil, errorf("unexpected intro: %q", line)
|
||||
}
|
||||
|
||||
var r *Recipient
|
||||
var r *Stanza
|
||||
for {
|
||||
line, err := rr.ReadBytes('\n')
|
||||
if err != nil {
|
||||
@@ -174,7 +176,7 @@ func Parse(input io.Reader) (*Header, io.Reader, error) {
|
||||
break
|
||||
|
||||
} else if bytes.HasPrefix(line, recipientPrefix) {
|
||||
r = &Recipient{}
|
||||
r = &Stanza{}
|
||||
prefix, args := splitArgs(line)
|
||||
if prefix != string(recipientPrefix) || len(args) < 1 {
|
||||
return nil, nil, errorf("malformed recipient: %q", line)
|
||||
|
||||
Reference in New Issue
Block a user