internal/age: surface format.Recipient as type Stanza

This commit is contained in:
Filippo Valsorda
2020-06-27 19:44:26 -04:00
parent c9a35c0727
commit 9b83d948f5
10 changed files with 46 additions and 34 deletions

View File

@@ -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)

View File

@@ -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

View File

@@ -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)

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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)

View File

@@ -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
}

View File

@@ -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

View File

@@ -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)