diff --git a/internal/format/format.go b/internal/format/format.go index 94925d9..a91064a 100644 --- a/internal/format/format.go +++ b/internal/format/format.go @@ -258,7 +258,7 @@ func Parse(input io.Reader) (*Header, io.Reader, error) { return nil, nil, errorf("malformed closing line: %q", line) } h.MAC, err = DecodeString(args[0]) - if err != nil { + if err != nil || len(h.MAC) != 32 { return nil, nil, errorf("malformed closing line %q: %v", line, err) } break diff --git a/internal/testkit/testkit.go b/internal/testkit/testkit.go index b2410ae..60e0823 100644 --- a/internal/testkit/testkit.go +++ b/internal/testkit/testkit.go @@ -106,16 +106,22 @@ func (f *TestFile) Body(body []byte) { } } -func (f *TestFile) Stanza(args []string, body []byte) { - f.ArgsLine(args...) - f.Body(body) -} - func (f *TestFile) AEADBody(key, body []byte) { aead, _ := chacha20poly1305.New(key) f.Body(aead.Seal(nil, make([]byte, chacha20poly1305.NonceSize), body, nil)) } +func x25519(scalar, point []byte) []byte { + secret, err := curve25519.X25519(scalar, point) + if err != nil { + if err.Error() == "bad input point: low order point" { + return make([]byte, 32) + } + panic(err) + } + return secret +} + func (f *TestFile) X25519(identity []byte) { f.X25519RecordIdentity(identity) f.X25519NoRecordIdentity(identity) @@ -127,11 +133,16 @@ func (f *TestFile) X25519RecordIdentity(identity []byte) { } func (f *TestFile) X25519NoRecordIdentity(identity []byte) { - recipient, _ := curve25519.X25519(identity, curve25519.Basepoint) - ephemeral := f.Rand(32) - share, _ := curve25519.X25519(ephemeral, curve25519.Basepoint) + share := x25519(f.Rand(32), curve25519.Basepoint) + f.X25519Stanza(share, identity) +} + +func (f *TestFile) X25519Stanza(share, identity []byte) { + recipient := x25519(identity, curve25519.Basepoint) f.ArgsLine("X25519", b64(share)) - secret, _ := curve25519.X25519(ephemeral, recipient) + // This would be ordinarily done as [ephemeral]recipient rather than + // [identity]share, but for some tests we don't have the dlog of share. + secret := x25519(identity, share) key := make([]byte, 32) hkdf.New(sha256.New, secret, append(share, recipient...), []byte("age-encryption.org/v1/X25519")).Read(key) @@ -150,8 +161,11 @@ func (f *TestFile) ScryptRecordPassphrase(passphrase string) { func (f *TestFile) ScryptNoRecordPassphrase(passphrase string, workFactor int) { salt := f.Rand(16) f.ArgsLine("scrypt", b64(salt), strconv.Itoa(workFactor)) - key, _ := scrypt.Key([]byte(passphrase), append([]byte("age-encryption.org/v1/scrypt"), salt...), + key, err := scrypt.Key([]byte(passphrase), append([]byte("age-encryption.org/v1/scrypt"), salt...), 1< X25519 " + base64.RawStdEncoding.EncodeToString(append(share, 0x00))) + f.TextLine(body) + f.HMAC() + f.Payload("age") + f.ExpectHeaderFailure() + f.Comment("a trailing zero is missing from the X25519 share") + f.Generate() +} diff --git a/tests/x25519_low_order.go b/tests/x25519_low_order.go index 2e465bb..b27db75 100644 --- a/tests/x25519_low_order.go +++ b/tests/x25519_low_order.go @@ -6,14 +6,7 @@ package main -import ( - "crypto/sha256" - "encoding/base64" - - "filippo.io/age/internal/testkit" - "golang.org/x/crypto/curve25519" - "golang.org/x/crypto/hkdf" -) +import "filippo.io/age/internal/testkit" func main() { f := testkit.NewTestFile() @@ -24,12 +17,7 @@ func main() { share := []byte{0x5f, 0x9c, 0x95, 0xbc, 0xa3, 0x50, 0x8c, 0x24, 0xb1, 0xd0, 0xb1, 0x55, 0x9c, 0x83, 0xef, 0x5b, 0x04, 0x44, 0x5c, 0xc4, 0x58, 0x1c, 0x8e, 0x86, 0xd8, 0x22, 0x4e, 0xdd, 0xd0, 0x9f, 0x11, 0xd7} - f.ArgsLine("X25519", base64.RawStdEncoding.EncodeToString(share)) - secret := make([]byte, curve25519.PointSize) - key := make([]byte, 32) - hkdf.New(sha256.New, secret, append(share, testkit.TestX25519Recipient...), - []byte("age-encryption.org/v1/X25519")).Read(key) - f.AEADBody(key, testkit.TestFileKey) + f.X25519Stanza(share, testkit.TestX25519Identity) f.HMAC() f.Payload("age") f.ExpectHeaderFailure() diff --git a/tests/x25519_lowercase.go b/tests/x25519_lowercase.go new file mode 100644 index 0000000..838690c --- /dev/null +++ b/tests/x25519_lowercase.go @@ -0,0 +1,27 @@ +// Copyright 2022 The age Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build ignore + +package main + +import ( + "strings" + + "filippo.io/age/internal/testkit" +) + +func main() { + f := testkit.NewTestFile() + f.VersionLine("v1") + f.X25519(testkit.TestX25519Recipient) + body, args := f.UnreadLine(), f.UnreadLine() + f.TextLine(strings.Replace(args, "X25519", "x25519", -1)) + f.TextLine(body) + f.HMAC() + f.Payload("age") + f.ExpectHeaderFailure() + f.Comment("the first argument in the X25519 stanza is lowercase") + f.Generate() +} diff --git a/tests/x25519_short_share.go b/tests/x25519_short_share.go new file mode 100644 index 0000000..9210a7a --- /dev/null +++ b/tests/x25519_short_share.go @@ -0,0 +1,30 @@ +// Copyright 2022 The age Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build ignore + +package main + +import ( + "encoding/base64" + "encoding/hex" + + "filippo.io/age/internal/testkit" +) + +func main() { + f := testkit.NewTestFile() + f.VersionLine("v1") + share, _ := hex.DecodeString("97ba38a135fd5f9137fca3836bfec24340ab03d7ca316b26f482636334a52600") + f.X25519RecordIdentity(testkit.TestX25519Identity) + f.X25519Stanza(share, testkit.TestX25519Identity) + body, _ := f.UnreadLine(), f.UnreadLine() + f.TextLine("-> X25519 " + base64.RawStdEncoding.EncodeToString(share[:31])) + f.TextLine(body) + f.HMAC() + f.Payload("age") + f.ExpectHeaderFailure() + f.Comment("a trailing zero is missing from the X25519 share") + f.Generate() +}