internal/format: wrap body at 56 columns

This commit is contained in:
Filippo Valsorda
2019-10-13 17:24:21 -04:00
parent 2d009c8eaf
commit 11fc3e293a
5 changed files with 69 additions and 51 deletions

View File

@@ -19,13 +19,10 @@ import (
) )
func TestX25519RoundTrip(t *testing.T) { func TestX25519RoundTrip(t *testing.T) {
var secretKey, publicKey, fileKey [32]byte var secretKey, publicKey [32]byte
if _, err := rand.Read(secretKey[:]); err != nil { if _, err := rand.Read(secretKey[:]); err != nil {
t.Fatal(err) t.Fatal(err)
} }
if _, err := rand.Read(fileKey[:]); err != nil {
t.Fatal(err)
}
curve25519.ScalarBaseMult(&publicKey, &secretKey) curve25519.ScalarBaseMult(&publicKey, &secretKey)
r, err := age.NewX25519Recipient(publicKey[:]) r, err := age.NewX25519Recipient(publicKey[:])
@@ -41,11 +38,17 @@ func TestX25519RoundTrip(t *testing.T) {
t.Errorf("invalid Type values: %v, %v", r.Type(), i.Type()) t.Errorf("invalid Type values: %v, %v", r.Type(), i.Type())
} }
fileKey := make([]byte, 16)
if _, err := rand.Read(fileKey[:]); err != nil {
t.Fatal(err)
}
block, err := r.Wrap(fileKey[:]) block, err := r.Wrap(fileKey[:])
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
t.Logf("%#v", block) b := &bytes.Buffer{}
block.Marshal(b)
t.Logf("%s", b.Bytes())
out, err := i.Unwrap(block) out, err := i.Unwrap(block)
if err != nil { if err != nil {
@@ -82,7 +85,9 @@ func TestScryptRoundTrip(t *testing.T) {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
t.Logf("%#v", block) b := &bytes.Buffer{}
block.Marshal(b)
t.Logf("%s", b.Bytes())
out, err := i.Unwrap(block) out, err := i.Unwrap(block)
if err != nil { if err != nil {
@@ -125,7 +130,9 @@ func TestSSHRSARoundTrip(t *testing.T) {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
t.Logf("%#v", block) b := &bytes.Buffer{}
block.Marshal(b)
t.Logf("%s", b.Bytes())
out, err := i.Unwrap(block) out, err := i.Unwrap(block)
if err != nil { if err != nil {
@@ -168,7 +175,9 @@ func TestSSHEd25519RoundTrip(t *testing.T) {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
t.Logf("%#v", block) b := &bytes.Buffer{}
block.Marshal(b)
t.Logf("%s", b.Bytes())
out, err := i.Unwrap(block) out, err := i.Unwrap(block)
if err != nil { if err != nil {

View File

@@ -68,7 +68,7 @@ func (r *ScryptRecipient) Wrap(fileKey []byte) (*format.Recipient, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
l.Body = []byte(format.EncodeToString(wrappedKey) + "\n") l.Body = wrappedKey
return l, nil return l, nil
} }
@@ -126,17 +126,13 @@ func (i *ScryptIdentity) Unwrap(block *format.Recipient) ([]byte, error) {
if logN <= 0 { if logN <= 0 {
return nil, fmt.Errorf("invalid scrypt work factor: %v", logN) return nil, fmt.Errorf("invalid scrypt work factor: %v", logN)
} }
wrappedKey, err := format.DecodeString(string(block.Body))
if err != nil {
return nil, fmt.Errorf("failed to parse scrypt recipient: %v", err)
}
k, err := scrypt.Key(i.password, salt, 1<<logN, 8, 1, chacha20poly1305.KeySize) k, err := scrypt.Key(i.password, salt, 1<<logN, 8, 1, chacha20poly1305.KeySize)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to generate scrypt hash: %v", err) return nil, fmt.Errorf("failed to generate scrypt hash: %v", err)
} }
fileKey, err := aeadDecrypt(k, wrappedKey) fileKey, err := aeadDecrypt(k, block.Body)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to decrypt file key: %v", err) return nil, fmt.Errorf("failed to decrypt file key: %v", err)
} }

View File

@@ -71,7 +71,7 @@ func (r *SSHRSARecipient) Wrap(fileKey []byte) (*format.Recipient, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
l.Body = []byte(format.EncodeToString(wrappedKey) + "\n") l.Body = wrappedKey
return l, nil return l, nil
} }
@@ -110,10 +110,6 @@ func (i *SSHRSAIdentity) Unwrap(block *format.Recipient) ([]byte, error) {
if len(hash) != 4 { if len(hash) != 4 {
return nil, errors.New("invalid ssh-rsa recipient block") return nil, errors.New("invalid ssh-rsa recipient block")
} }
wrappedKey, err := format.DecodeString(string(block.Body))
if err != nil {
return nil, fmt.Errorf("failed to parse ssh-rsa recipient: %v", err)
}
h := sha256.New() h := sha256.New()
h.Write(i.sshKey.Marshal()) h.Write(i.sshKey.Marshal())
@@ -123,7 +119,7 @@ func (i *SSHRSAIdentity) Unwrap(block *format.Recipient) ([]byte, error) {
} }
fileKey, err := rsa.DecryptOAEP(sha256.New(), rand.Reader, i.k, fileKey, err := rsa.DecryptOAEP(sha256.New(), rand.Reader, i.k,
wrappedKey, []byte(oaepLabel)) block.Body, []byte(oaepLabel))
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to decrypt file key: %v", err) return nil, fmt.Errorf("failed to decrypt file key: %v", err)
} }
@@ -254,7 +250,7 @@ func (r *SSHEd25519Recipient) Wrap(fileKey []byte) (*format.Recipient, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
l.Body = []byte(format.EncodeToString(wrappedKey) + "\n") l.Body = wrappedKey
return l, nil return l, nil
} }
@@ -327,10 +323,6 @@ func (i *SSHEd25519Identity) Unwrap(block *format.Recipient) ([]byte, error) {
if len(publicKey) != 32 { if len(publicKey) != 32 {
return nil, errors.New("invalid ssh-ed25519 recipient block") return nil, errors.New("invalid ssh-ed25519 recipient block")
} }
wrappedKey, err := format.DecodeString(string(block.Body))
if err != nil {
return nil, fmt.Errorf("failed to parse ssh-ed25519 recipient: %v", err)
}
sH := sha256.New() sH := sha256.New()
sH.Write(i.sshKey.Marshal()) sH.Write(i.sshKey.Marshal())
@@ -357,7 +349,7 @@ func (i *SSHEd25519Identity) Unwrap(block *format.Recipient) ([]byte, error) {
return nil, err return nil, err
} }
fileKey, err := aeadDecrypt(wrappingKey, wrappedKey) fileKey, err := aeadDecrypt(wrappingKey, block.Body)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to decrypt file key: %v", err) return nil, fmt.Errorf("failed to decrypt file key: %v", err)
} }

View File

@@ -83,7 +83,7 @@ func (r *X25519Recipient) Wrap(fileKey []byte) (*format.Recipient, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
l.Body = []byte(format.EncodeToString(wrappedKey) + "\n") l.Body = wrappedKey
return l, nil return l, nil
} }
@@ -140,10 +140,6 @@ func (i *X25519Identity) Unwrap(block *format.Recipient) ([]byte, error) {
if len(publicKey) != 32 { if len(publicKey) != 32 {
return nil, errors.New("invalid X25519 recipient block") return nil, errors.New("invalid X25519 recipient block")
} }
wrappedKey, err := format.DecodeString(string(block.Body))
if err != nil {
return nil, fmt.Errorf("failed to parse X25519 recipient: %v", err)
}
var sharedSecret, theirPublicKey [32]byte var sharedSecret, theirPublicKey [32]byte
copy(theirPublicKey[:], publicKey) copy(theirPublicKey[:], publicKey)
@@ -158,7 +154,7 @@ func (i *X25519Identity) Unwrap(block *format.Recipient) ([]byte, error) {
return nil, err return nil, err
} }
fileKey, err := aeadDecrypt(wrappingKey, wrappedKey) fileKey, err := aeadDecrypt(wrappingKey, block.Body)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to decrypt file key: %v", err) return nil, fmt.Errorf("failed to decrypt file key: %v", err)
} }

View File

@@ -30,39 +30,53 @@ type Recipient struct {
var b64 = base64.RawURLEncoding.Strict() var b64 = base64.RawURLEncoding.Strict()
func DecodeString(s string) ([]byte, error) { func DecodeString(s string) ([]byte, error) {
// CR and LF are ignored by DecodeString. LF is handled by the parser, // CR and LF are ignored by DecodeString, but we don't want any malleability.
// but CR can introduce malleability. if strings.ContainsAny(s, "\n\r") {
if strings.Contains(s, "\r") { return nil, errors.New(`unexpected newline character`)
return nil, errors.New(`invalid character: \r`)
} }
return b64.DecodeString(s) return b64.DecodeString(s)
} }
var EncodeToString = b64.EncodeToString // TODO: wrap lines var EncodeToString = b64.EncodeToString
const bytesPerLine = 56 / 4 * 3 // 56 columns of Base64
const intro = "This is a file encrypted with age-tool.com, version 1\n" const intro = "This is a file encrypted with age-tool.com, version 1\n"
var recipientPrefix = []byte("->") var recipientPrefix = []byte("->")
var footerPrefix = []byte("---") var footerPrefix = []byte("---")
func (r *Recipient) Marshal(w io.Writer) error {
if _, err := w.Write(recipientPrefix); err != nil {
return err
}
for _, a := range append([]string{r.Type}, r.Args...) {
if _, err := io.WriteString(w, " "+a); err != nil {
return err
}
}
if _, err := io.WriteString(w, "\n"); err != nil {
return err
}
for i := 0; i < len(r.Body); i += bytesPerLine {
n := bytesPerLine
if n > len(r.Body)-i {
n = len(r.Body) - i
}
s := EncodeToString(r.Body[i : i+n])
if _, err := io.WriteString(w, s+"\n"); err != nil {
return err
}
}
return nil
}
func (h *Header) MarshalWithoutMAC(w io.Writer) error { func (h *Header) MarshalWithoutMAC(w io.Writer) error {
if _, err := io.WriteString(w, intro); err != nil { if _, err := io.WriteString(w, intro); err != nil {
return err return err
} }
for _, r := range h.Recipients { for _, r := range h.Recipients {
if _, err := w.Write(recipientPrefix); err != nil { if err := r.Marshal(w); err != nil {
return err
}
for _, a := range append([]string{r.Type}, r.Args...) {
if _, err := io.WriteString(w, " "+a); err != nil {
return err
}
}
if _, err := io.WriteString(w, "\n"); err != nil {
return err
}
// TODO: check that Body ends with a newline.
if _, err := w.Write(r.Body); err != nil {
return err return err
} }
} }
@@ -132,7 +146,18 @@ func Parse(input io.Reader) (*Header, io.Reader, error) {
h.Recipients = append(h.Recipients, r) h.Recipients = append(h.Recipients, r)
} else if r != nil { } else if r != nil {
r.Body = append(r.Body, line...) b, err := DecodeString(strings.TrimSuffix(string(line), "\n"))
if err != nil {
return nil, nil, errorf("malformed body line %q: %v", line, err)
}
if len(b) > bytesPerLine {
return nil, nil, errorf("malformed body line %q: too long", line)
}
r.Body = append(r.Body, b...)
if len(b) < bytesPerLine {
// Only the last line of a body can be short.
r = nil
}
} else { } else {
return nil, nil, errorf("unexpected line: %q", line) return nil, nil, errorf("unexpected line: %q", line)