mirror of
https://github.com/FiloSottile/age.git
synced 2026-01-05 03:43:57 +00:00
internal/age: add some docs and polish API
This commit is contained in:
@@ -38,8 +38,8 @@ func main() {
|
||||
|
||||
if fi, err := out.Stat(); err == nil {
|
||||
if fi.Mode().IsRegular() && fi.Mode().Perm()&0004 != 0 {
|
||||
fmt.Fprintf(os.Stderr, "Warning: writing to a world-readable file.\n")
|
||||
fmt.Fprintf(os.Stderr, "Consider setting the umask to 066 and trying again.\n")
|
||||
fmt.Fprintf(os.Stderr, "Warning: writing to a world-readable file.\n"+
|
||||
"Consider setting the umask to 066 and trying again.\n")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -84,7 +84,7 @@ func sshFingerprint(pk ssh.PublicKey) string {
|
||||
return format.EncodeToString(h[:4])
|
||||
}
|
||||
|
||||
func (i *EncryptedSSHIdentity) Matches(block *format.Recipient) error {
|
||||
func (i *EncryptedSSHIdentity) Match(block *format.Recipient) error {
|
||||
if block.Type != i.Type() {
|
||||
return age.ErrIncorrectIdentity
|
||||
}
|
||||
|
||||
@@ -18,23 +18,42 @@ import (
|
||||
"filippo.io/age/internal/stream"
|
||||
)
|
||||
|
||||
// An Identity is a private key or other value that can decrypt an opaque file
|
||||
// key from a recipient stanza.
|
||||
//
|
||||
// Unwrap must return ErrIncorrectIdentity for recipient blocks that don't match
|
||||
// the identity, any other error might be considered fatal.
|
||||
type Identity interface {
|
||||
Type() string
|
||||
Unwrap(block *format.Recipient) (fileKey []byte, err error)
|
||||
}
|
||||
|
||||
// IdentityMatcher can be optionally implemented by an Identity that can
|
||||
// communicate whether it can decrypt a recipient stanza without decrypting it.
|
||||
//
|
||||
// If an Identity implements IdentityMatcher, its Unwrap method will only be
|
||||
// invoked on blocks for which Match returned nil. Match must return
|
||||
// ErrIncorrectIdentity for recipient blocks that don't match the identity, any
|
||||
// other error might be considered fatal.
|
||||
type IdentityMatcher interface {
|
||||
Identity
|
||||
Matches(block *format.Recipient) error
|
||||
Match(block *format.Recipient) error
|
||||
}
|
||||
|
||||
var ErrIncorrectIdentity = errors.New("incorrect identity for recipient block")
|
||||
|
||||
// A Recipient is a public key or other value that can encrypt an opaque file
|
||||
// key to a recipient stanza.
|
||||
type Recipient interface {
|
||||
Type() string
|
||||
Wrap(fileKey []byte) (*format.Recipient, error)
|
||||
}
|
||||
|
||||
// Encrypt returns a WriteCloser. Writes to the returned value are encrypted and
|
||||
// written to dst as an age file. Every recipient will be able to decrypt the file.
|
||||
//
|
||||
// The caller must call Close on the returned value when done for the last chunk
|
||||
// to be encrypted and flushed to dst.
|
||||
func Encrypt(dst io.Writer, recipients ...Recipient) (io.WriteCloser, error) {
|
||||
if len(recipients) == 0 {
|
||||
return nil, errors.New("no recipients specified")
|
||||
@@ -77,6 +96,8 @@ func Encrypt(dst io.Writer, recipients ...Recipient) (io.WriteCloser, error) {
|
||||
return stream.NewWriter(streamKey(fileKey, nonce), dst)
|
||||
}
|
||||
|
||||
// Decrypt returns a Reader reading the decrypted plaintext of the age file read
|
||||
// from src. All identities will be tried until one successfully decrypts the file.
|
||||
func Decrypt(src io.Reader, identities ...Identity) (io.Reader, error) {
|
||||
if len(identities) == 0 {
|
||||
return nil, errors.New("no identities specified")
|
||||
@@ -102,7 +123,7 @@ RecipientsLoop:
|
||||
}
|
||||
|
||||
if i, ok := i.(IdentityMatcher); ok {
|
||||
err := i.Matches(r)
|
||||
err := i.Match(r)
|
||||
if err != nil {
|
||||
if err == ErrIncorrectIdentity {
|
||||
continue
|
||||
|
||||
@@ -19,6 +19,14 @@ import (
|
||||
|
||||
const scryptLabel = "age-encryption.org/v1/scrypt"
|
||||
|
||||
// ScryptRecipient is a password-based recipient.
|
||||
//
|
||||
// If a ScryptRecipient is used, it must be the only recipient for the file: it
|
||||
// can't be mixed with other recipient types and can't be used multiple times
|
||||
// for the same file.
|
||||
//
|
||||
// Its use is not recommended for automated systems, which should prefer
|
||||
// X25519Recipient.
|
||||
type ScryptRecipient struct {
|
||||
password []byte
|
||||
workFactor int
|
||||
@@ -28,6 +36,7 @@ var _ Recipient = &ScryptRecipient{}
|
||||
|
||||
func (*ScryptRecipient) Type() string { return "scrypt" }
|
||||
|
||||
// NewScryptRecipient returns a new ScryptRecipient with the provided password.
|
||||
func NewScryptRecipient(password string) (*ScryptRecipient, error) {
|
||||
if len(password) == 0 {
|
||||
return nil, errors.New("passphrase can't be empty")
|
||||
@@ -42,6 +51,8 @@ func NewScryptRecipient(password string) (*ScryptRecipient, error) {
|
||||
|
||||
// SetWorkFactor sets the scrypt work factor to 2^logN.
|
||||
// It must be called before Wrap.
|
||||
//
|
||||
// If SetWorkFactor is not called, a reasonable default is used.
|
||||
func (r *ScryptRecipient) SetWorkFactor(logN int) {
|
||||
if logN > 30 || logN < 1 {
|
||||
panic("age: SetWorkFactor called with illegal value")
|
||||
@@ -76,6 +87,7 @@ func (r *ScryptRecipient) Wrap(fileKey []byte) (*format.Recipient, error) {
|
||||
return l, nil
|
||||
}
|
||||
|
||||
// ScryptIdentity is a password-based identity.
|
||||
type ScryptIdentity struct {
|
||||
password []byte
|
||||
maxWorkFactor int
|
||||
@@ -85,6 +97,7 @@ var _ Identity = &ScryptIdentity{}
|
||||
|
||||
func (*ScryptIdentity) Type() string { return "scrypt" }
|
||||
|
||||
// NewScryptIdentity returns a new ScryptIdentity with the provided password.
|
||||
func NewScryptIdentity(password string) (*ScryptIdentity, error) {
|
||||
if len(password) == 0 {
|
||||
return nil, errors.New("passphrase can't be empty")
|
||||
@@ -96,8 +109,12 @@ func NewScryptIdentity(password string) (*ScryptIdentity, error) {
|
||||
return i, nil
|
||||
}
|
||||
|
||||
// SetWorkFactor sets the maximum accepted scrypt work factor to 2^logN.
|
||||
// SetMaxWorkFactor sets the maximum accepted scrypt work factor to 2^logN.
|
||||
// It must be called before Unwrap.
|
||||
//
|
||||
// This caps the amount of work that Decrypt might have to do to process
|
||||
// received files. If SetMaxWorkFactor is not called, a fairly high default is
|
||||
// used, which might not be suitable for systems processing untrusted files.
|
||||
func (i *ScryptIdentity) SetMaxWorkFactor(logN int) {
|
||||
if logN > 30 || logN < 1 {
|
||||
panic("age: SetMaxWorkFactor called with illegal value")
|
||||
|
||||
@@ -23,6 +23,7 @@ import (
|
||||
|
||||
const x25519Label = "age-encryption.org/v1/X25519"
|
||||
|
||||
// X25519Recipient is the standard age public key, based on a Curve25519 point.
|
||||
type X25519Recipient struct {
|
||||
theirPublicKey []byte
|
||||
}
|
||||
@@ -31,6 +32,7 @@ var _ Recipient = &X25519Recipient{}
|
||||
|
||||
func (*X25519Recipient) Type() string { return "X25519" }
|
||||
|
||||
// NewX25519Recipient returns a new X25519Recipient from a raw Curve25519 point.
|
||||
func NewX25519Recipient(publicKey []byte) (*X25519Recipient, error) {
|
||||
if len(publicKey) != curve25519.PointSize {
|
||||
return nil, errors.New("invalid X25519 public key")
|
||||
@@ -42,6 +44,8 @@ func NewX25519Recipient(publicKey []byte) (*X25519Recipient, error) {
|
||||
return r, nil
|
||||
}
|
||||
|
||||
// ParseX25519Recipient returns a new X25519Recipient from a Bech32 public key
|
||||
// encoding with the "age1" prefix.
|
||||
func ParseX25519Recipient(s string) (*X25519Recipient, error) {
|
||||
t, k, err := bech32.Decode(s)
|
||||
if err != nil {
|
||||
@@ -95,11 +99,13 @@ func (r *X25519Recipient) Wrap(fileKey []byte) (*format.Recipient, error) {
|
||||
return l, nil
|
||||
}
|
||||
|
||||
// String returns the Bech32 public key encoding of r.
|
||||
func (r *X25519Recipient) String() string {
|
||||
s, _ := bech32.Encode("age", r.theirPublicKey)
|
||||
return s
|
||||
}
|
||||
|
||||
// X25519Identity is the standard age private key, based on a Curve25519 scalar.
|
||||
type X25519Identity struct {
|
||||
secretKey, ourPublicKey []byte
|
||||
}
|
||||
@@ -108,6 +114,7 @@ var _ Identity = &X25519Identity{}
|
||||
|
||||
func (*X25519Identity) Type() string { return "X25519" }
|
||||
|
||||
// NewX25519Identity returns a new X25519Identity from a raw Curve25519 scalar.
|
||||
func NewX25519Identity(secretKey []byte) (*X25519Identity, error) {
|
||||
if len(secretKey) != curve25519.ScalarSize {
|
||||
return nil, errors.New("invalid X25519 secret key")
|
||||
@@ -120,6 +127,7 @@ func NewX25519Identity(secretKey []byte) (*X25519Identity, error) {
|
||||
return i, nil
|
||||
}
|
||||
|
||||
// GenerateX25519Identity generates a fresh X25519Identity.
|
||||
func GenerateX25519Identity() (*X25519Identity, error) {
|
||||
secretKey := make([]byte, curve25519.ScalarSize)
|
||||
if _, err := rand.Read(secretKey); err != nil {
|
||||
@@ -128,6 +136,8 @@ func GenerateX25519Identity() (*X25519Identity, error) {
|
||||
return NewX25519Identity(secretKey)
|
||||
}
|
||||
|
||||
// ParseX25519Identity returns a new X25519Recipient from a Bech32 private key
|
||||
// encoding with the "AGE-SECRET-KEY-1" prefix.
|
||||
func ParseX25519Identity(s string) (*X25519Identity, error) {
|
||||
t, k, err := bech32.Decode(s)
|
||||
if err != nil {
|
||||
@@ -179,12 +189,14 @@ func (i *X25519Identity) Unwrap(block *format.Recipient) ([]byte, error) {
|
||||
return fileKey, nil
|
||||
}
|
||||
|
||||
// Recipient returns the public X25519Recipient value corresponding to i.
|
||||
func (i *X25519Identity) Recipient() *X25519Recipient {
|
||||
r := &X25519Recipient{}
|
||||
r.theirPublicKey = i.ourPublicKey
|
||||
return r
|
||||
}
|
||||
|
||||
// String returns the Bech32 private key encoding of i.
|
||||
func (i *X25519Identity) String() string {
|
||||
s, _ := bech32.Encode("AGE-SECRET-KEY-", i.secretKey)
|
||||
return strings.ToUpper(s)
|
||||
|
||||
@@ -106,7 +106,7 @@ func (r *Reader) readChunk() (last bool, err error) {
|
||||
out, err = r.a.Open(outBuf, r.nonce[:], in, nil)
|
||||
}
|
||||
if err != nil {
|
||||
return false, err
|
||||
return false, errors.New("failed to decrypt and authenticate payload chunk")
|
||||
}
|
||||
|
||||
incNonce(&r.nonce)
|
||||
|
||||
Reference in New Issue
Block a user