tests: finish parsing and X25519 tests, distinguish HMAC errors

If the implementation re-encodes the header before checking the HMAC,
that would mask malleability issues: the HMAC check would fail because
the tests HMAC'd the original header, but an attacker could also produce
the right HMAC. Instead of duplicating every parsing tests (with the
original and re-encoded HMAC), we make the test framework distinguish
HMAC errors, which ensures bad encodings are recognized as such and not
bypassable HMAC errors.
This commit is contained in:
Filippo Valsorda
2022-06-18 13:47:00 +02:00
parent f8a121dd87
commit eaa4e03cfe
43 changed files with 557 additions and 59 deletions

View File

@@ -258,7 +258,7 @@ func Parse(input io.Reader) (*Header, io.Reader, error) {
return nil, nil, errorf("malformed closing line: %q", line) return nil, nil, errorf("malformed closing line: %q", line)
} }
h.MAC, err = DecodeString(args[0]) 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) return nil, nil, errorf("malformed closing line %q: %v", line, err)
} }
break break

View File

@@ -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) { func (f *TestFile) AEADBody(key, body []byte) {
aead, _ := chacha20poly1305.New(key) aead, _ := chacha20poly1305.New(key)
f.Body(aead.Seal(nil, make([]byte, chacha20poly1305.NonceSize), body, nil)) 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) { func (f *TestFile) X25519(identity []byte) {
f.X25519RecordIdentity(identity) f.X25519RecordIdentity(identity)
f.X25519NoRecordIdentity(identity) f.X25519NoRecordIdentity(identity)
@@ -127,11 +133,16 @@ func (f *TestFile) X25519RecordIdentity(identity []byte) {
} }
func (f *TestFile) X25519NoRecordIdentity(identity []byte) { func (f *TestFile) X25519NoRecordIdentity(identity []byte) {
recipient, _ := curve25519.X25519(identity, curve25519.Basepoint) share := x25519(f.Rand(32), curve25519.Basepoint)
ephemeral := f.Rand(32) f.X25519Stanza(share, identity)
share, _ := curve25519.X25519(ephemeral, curve25519.Basepoint) }
func (f *TestFile) X25519Stanza(share, identity []byte) {
recipient := x25519(identity, curve25519.Basepoint)
f.ArgsLine("X25519", b64(share)) 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) key := make([]byte, 32)
hkdf.New(sha256.New, secret, append(share, recipient...), hkdf.New(sha256.New, secret, append(share, recipient...),
[]byte("age-encryption.org/v1/X25519")).Read(key) []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) { func (f *TestFile) ScryptNoRecordPassphrase(passphrase string, workFactor int) {
salt := f.Rand(16) salt := f.Rand(16)
f.ArgsLine("scrypt", b64(salt), strconv.Itoa(workFactor)) 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<<workFactor, 8, 1, 32) 1<<workFactor, 8, 1, 32)
if err != nil {
panic(err)
}
f.AEADBody(key, f.fileKey) f.AEADBody(key, f.fileKey)
} }
@@ -201,6 +215,10 @@ func (f *TestFile) ExpectPayloadFailure() {
f.expect = "payload failure" f.expect = "payload failure"
} }
func (f *TestFile) ExpectHMACFailure() {
f.expect = "HMAC failure"
}
func (f *TestFile) Comment(c string) { func (f *TestFile) Comment(c string) {
f.comment = c f.comment = c
} }

View File

@@ -1,4 +1,4 @@
expect: header failure expect: HMAC failure
file key: 59454c4c4f57205355424d4152494e45 file key: 59454c4c4f57205355424d4152494e45
identity: AGE-SECRET-KEY-1EGTZVFFV20835NWYV6270LXYVK2VKNX2MMDKWYKLMGR48UAWX40Q2P2LM0 identity: AGE-SECRET-KEY-1EGTZVFFV20835NWYV6270LXYVK2VKNX2MMDKWYKLMGR48UAWX40Q2P2LM0

11
testdata/testkit/stanza_bad_start vendored Normal file
View File

@@ -0,0 +1,11 @@
expect: header failure
file key: 59454c4c4f57205355424d4152494e45
identity: AGE-SECRET-KEY-1XMWWC06LY3EE5RYTXM9MFLAZ2U56JJJ36S0MYPDRWSVLUL66MV4QX3S7F6
age-encryption.org/v1
-> X25519 TEiF0ypqr+bpvcqXNyCVJpL7OuwPdVwPL7KQEbFDOCc
EmECAEcKN+n/Vs9SbWiV+Hu0r+E8R77DdWYyd83nw7U
-- stanza
--- lpxzkyQGe/sA7F1yh4c6KVZV7//jANm5lYefTToioXs
îÏbÇδ3'NhÔòùL·L[þ÷¾ªRÈð¼™,ƒ1ûf

12
testdata/testkit/stanza_base64_padding vendored Normal file
View File

@@ -0,0 +1,12 @@
expect: header failure
file key: 59454c4c4f57205355424d4152494e45
identity: AGE-SECRET-KEY-1XMWWC06LY3EE5RYTXM9MFLAZ2U56JJJ36S0MYPDRWSVLUL66MV4QX3S7F6
age-encryption.org/v1
-> X25519 TEiF0ypqr+bpvcqXNyCVJpL7OuwPdVwPL7KQEbFDOCc
EmECAEcKN+n/Vs9SbWiV+Hu0r+E8R77DdWYyd83nw7U
-> stanza
QUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFB
QUE=
--- OtG7IuNHaf2SHZuowmxg/fhbhtz0/DI5g5OGd7WH7S0
îÏbÇδ3'NhÔòùL·L[þ÷¾ªRÈð¼™,ƒ1ûf

11
testdata/testkit/stanza_empty_argument vendored Normal file
View File

@@ -0,0 +1,11 @@
expect: header failure
file key: 59454c4c4f57205355424d4152494e45
identity: AGE-SECRET-KEY-1XMWWC06LY3EE5RYTXM9MFLAZ2U56JJJ36S0MYPDRWSVLUL66MV4QX3S7F6
age-encryption.org/v1
-> X25519 TEiF0ypqr+bpvcqXNyCVJpL7OuwPdVwPL7KQEbFDOCc
EmECAEcKN+n/Vs9SbWiV+Hu0r+E8R77DdWYyd83nw7U
-> stanza argument
--- bosBxVRBzKF9emyxQ9BERq7+D5JKU+lvbEsL8UHJ/SA
îÏbÇδ3'NhÔòùL·L[þ÷¾ªRÈð¼™,ƒ1ûf

View File

@@ -8,6 +8,7 @@ age-encryption.org/v1
EmECAEcKN+n/Vs9SbWiV+Hu0r+E8R77DdWYyd83nw7U EmECAEcKN+n/Vs9SbWiV+Hu0r+E8R77DdWYyd83nw7U
-> stanza -> stanza
QUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFB QUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFB
QUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFB
--- +3PC416gxa7Mk7WxpX0kb6DVfSuCun0niGre+G4bZhE --- cb4SqtunSJzXKDGjqeYxuva9Be80QXEDKDn2aKBaCsw
îÏbÇδ3'NhÔòùL·L[þ÷¾ªRÈð¼™,ƒ1ûf îÏbÇδ3'NhÔòùL·L[þ÷¾ªRÈð¼™,ƒ1ûf

View File

@@ -0,0 +1,11 @@
expect: header failure
file key: 59454c4c4f57205355424d4152494e45
identity: AGE-SECRET-KEY-1XMWWC06LY3EE5RYTXM9MFLAZ2U56JJJ36S0MYPDRWSVLUL66MV4QX3S7F6
age-encryption.org/v1
-> X25519 TEiF0ypqr+bpvcqXNyCVJpL7OuwPdVwPL7KQEbFDOCc
EmECAEcKN+n/Vs9SbWiV+Hu0r+E8R77DdWYyd83nw7U
-> stanza è
--- sTIB/0Fc74rhpjC4RAxoR3E01eVTTnWruaD+c5QWjKI
îÏbÇδ3'NhÔòùL·L[þ÷¾ªRÈð¼™,ƒ1ûf

13
testdata/testkit/stanza_long_line vendored Normal file
View File

@@ -0,0 +1,13 @@
expect: header failure
file key: 59454c4c4f57205355424d4152494e45
identity: AGE-SECRET-KEY-1XMWWC06LY3EE5RYTXM9MFLAZ2U56JJJ36S0MYPDRWSVLUL66MV4QX3S7F6
comment: a body line is longer than 64 columns
age-encryption.org/v1
-> X25519 TEiF0ypqr+bpvcqXNyCVJpL7OuwPdVwPL7KQEbFDOCc
EmECAEcKN+n/Vs9SbWiV+Hu0r+E8R77DdWYyd83nw7U
-> stanza
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
--- tnRUR2vmmU92czsjnioF5ujgXUetUhzUoQPPGT9wmug
îÏbÇδ3'NhÔòùL·L[þ÷¾ªRÈð¼™,ƒ1ûf

11
testdata/testkit/stanza_no_arguments vendored Normal file
View File

@@ -0,0 +1,11 @@
expect: header failure
file key: 59454c4c4f57205355424d4152494e45
identity: AGE-SECRET-KEY-1XMWWC06LY3EE5RYTXM9MFLAZ2U56JJJ36S0MYPDRWSVLUL66MV4QX3S7F6
age-encryption.org/v1
-> X25519 TEiF0ypqr+bpvcqXNyCVJpL7OuwPdVwPL7KQEbFDOCc
EmECAEcKN+n/Vs9SbWiV+Hu0r+E8R77DdWYyd83nw7U
->
--- B0qjnUjVajTa8I4Uia49g1c4DMQQN6u9m9QOSS1HLks
îÏbÇδ3'NhÔòùL·L[þ÷¾ªRÈð¼™,ƒ1ûf

12
testdata/testkit/stanza_not_canonical vendored Normal file
View File

@@ -0,0 +1,12 @@
expect: header failure
file key: 59454c4c4f57205355424d4152494e45
identity: AGE-SECRET-KEY-1XMWWC06LY3EE5RYTXM9MFLAZ2U56JJJ36S0MYPDRWSVLUL66MV4QX3S7F6
age-encryption.org/v1
-> X25519 TEiF0ypqr+bpvcqXNyCVJpL7OuwPdVwPL7KQEbFDOCc
EmECAEcKN+n/Vs9SbWiV+Hu0r+E8R77DdWYyd83nw7U
-> stanza
QUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFB
QUF
--- nQM2VCzmNLPrUurNWN+SW9wVp/9uTMQ/6CTUM7l8c84
îÏbÇδ3'NhÔòùL·L[þ÷¾ªRÈð¼™,ƒ1ûf

11
testdata/testkit/stanza_spurious_cr vendored Normal file
View File

@@ -0,0 +1,11 @@
expect: header failure
file key: 59454c4c4f57205355424d4152494e45
identity: AGE-SECRET-KEY-1XMWWC06LY3EE5RYTXM9MFLAZ2U56JJJ36S0MYPDRWSVLUL66MV4QX3S7F6
age-encryption.org/v1
-> X25519 TEiF0ypqr+bpvcqXNyCVJpL7OuwPdVwPL7KQEbFDOCc
EmECAEcKN+n/Vs9SbWiV+Hu0r+E8R77DdWYyd83nw7U
-> stanza
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
--- MZaFAh8ldzU0F88NJjLx5yd7fnd57XS5COowmgvQtXQ
îÏbÇδ3'NhÔòùL·L[þ÷¾ªRÈð¼™,ƒ1ûf

9
testdata/testkit/version_unsupported vendored Normal file
View File

@@ -0,0 +1,9 @@
expect: header failure
file key: 59454c4c4f57205355424d4152494e45
identity: AGE-SECRET-KEY-1XMWWC06LY3EE5RYTXM9MFLAZ2U56JJJ36S0MYPDRWSVLUL66MV4QX3S7F6
age-encryption.org/v1234
-> X25519 TEiF0ypqr+bpvcqXNyCVJpL7OuwPdVwPL7KQEbFDOCc
EmECAEcKN+n/Vs9SbWiV+Hu0r+E8R77DdWYyd83nw7U
--- 38AL8Mr4VwmS6CNbM4bc7u3WwGBDqsMTRHOuYJ9ckqs
îÏbÇδ3'NhÔòùL·L[þ÷¾ªRÈð¼™,ƒ1ûf

10
testdata/testkit/x25519_bad_tag vendored Normal file
View File

@@ -0,0 +1,10 @@
expect: header failure
file key: 59454c4c4f57205355424d4152494e45
identity: AGE-SECRET-KEY-1XMWWC06LY3EE5RYTXM9MFLAZ2U56JJJ36S0MYPDRWSVLUL66MV4QX3S7F6
comment: the ChaCha20Poly1305 authentication tag on the body of the X25519 stanza is wrong
age-encryption.org/v1
-> X25519 TEiF0ypqr+bpvcqXNyCVJpL7OuwPdVwPL7KQEbFDOCc
EmECAEcKN+n/Vs9SbWiV+Hu0r+E8R77DdWYyd83nw0o
--- tG0k9bg4iIuBdMWb13n7FFYDzoBbtsLppNLhbh22aKg
îÏbÇδ3'NhÔòùL·L[þ÷¾ªRÈð¼™,ƒ1ûf

14
testdata/testkit/x25519_grease vendored Normal file
View File

@@ -0,0 +1,14 @@
expect: success
payload: 013f54400c82da08037759ada907a8b864e97de81c088a182062c4b5622fd2ab
file key: 59454c4c4f57205355424d4152494e45
identity: AGE-SECRET-KEY-1XMWWC06LY3EE5RYTXM9MFLAZ2U56JJJ36S0MYPDRWSVLUL66MV4QX3S7F6
age-encryption.org/v1
-> grease
-> X25519 TEiF0ypqr+bpvcqXNyCVJpL7OuwPdVwPL7KQEbFDOCc
EmECAEcKN+n/Vs9SbWiV+Hu0r+E8R77DdWYyd83nw7U
-> grease
--- 7NLrfbRUZt6qK0pdtARUf59dHwo12ReldjJKjMlbE3I
îÏbÇδ3'NhÔòùL·L[þ÷¾ªRÈð¼™,ƒ1ûf

10
testdata/testkit/x25519_long_share vendored Normal file
View File

@@ -0,0 +1,10 @@
expect: header failure
file key: 59454c4c4f57205355424d4152494e45
identity: AGE-SECRET-KEY-1EGTZVFFV20835NWYV6270LXYVK2VKNX2MMDKWYKLMGR48UAWX40Q2P2LM0
comment: a trailing zero is missing from the X25519 share
age-encryption.org/v1
-> X25519 TEiF0ypqr+bpvcqXNyCVJpL7OuwPdVwPL7KQEbFDOCcA
hjabGXwSLQ9c3S6Lw2i+S2Tu2fiwQHHslbBN6B41FLE
--- QbEwdWirchS37UUOPh7uVddRiOaWjFwRUpaQ4Q+Z1RE
îÏbÇδ3'NhÔòùL·L[þ÷¾ªRÈð¼™,ƒ1ûf

10
testdata/testkit/x25519_lowercase vendored Normal file
View File

@@ -0,0 +1,10 @@
expect: header failure
file key: 59454c4c4f57205355424d4152494e45
identity: AGE-SECRET-KEY-1XMWWC06LY3EE5RYTXM9MFLAZ2U56JJJ36S0MYPDRWSVLUL66MV4QX3S7F6
comment: the first argument in the X25519 stanza is lowercase
age-encryption.org/v1
-> x25519 TEiF0ypqr+bpvcqXNyCVJpL7OuwPdVwPL7KQEbFDOCc
EmECAEcKN+n/Vs9SbWiV+Hu0r+E8R77DdWYyd83nw7U
--- SwXKO3dXLh9l5QiSgMWgPhCkwstT8oB4jLDv7aBgC+c
îÏbÇδ3'NhÔòùL·L[þ÷¾ªRÈð¼™,ƒ1ûf

10
testdata/testkit/x25519_short_share vendored Normal file
View File

@@ -0,0 +1,10 @@
expect: header failure
file key: 59454c4c4f57205355424d4152494e45
identity: AGE-SECRET-KEY-1EGTZVFFV20835NWYV6270LXYVK2VKNX2MMDKWYKLMGR48UAWX40Q2P2LM0
comment: a trailing zero is missing from the X25519 share
age-encryption.org/v1
-> X25519 l7o4oTX9X5E3/KODa/7CQ0CrA9fKMWsm9IJjYzSlJg
yUGP5aPob6YJ+vzRfBtDT9D1K/wmyheZE/Xl/mDSKA4
--- Zn1/VRtHpD93HtIXSv1S++POXeKcQF7w1+hpXhMiAbk
¬]?7åPqÓ¦ F—¹ •Â÷õÛ®è zŒ(rŠóÎ|

View File

@@ -75,10 +75,9 @@ func TestVectors(t *testing.T) {
func testVector(t *testing.T, test []byte) { func testVector(t *testing.T, test []byte) {
var ( var (
expectHeaderFailure bool expect string
expectPayloadFailure bool payloadHash *[32]byte
payloadHash *[32]byte identities []age.Identity
identities []age.Identity
) )
for { for {
@@ -95,13 +94,13 @@ func testVector(t *testing.T, test []byte) {
case "expect": case "expect":
switch value { switch value {
case "success": case "success":
case "HMAC failure":
case "header failure": case "header failure":
expectHeaderFailure = true
case "payload failure": case "payload failure":
expectPayloadFailure = true
default: default:
t.Fatal("invalid test file: unknown expect value:", value) t.Fatal("invalid test file: unknown expect value:", value)
} }
expect = value
case "payload": case "payload":
h, err := hex.DecodeString(value) h, err := hex.DecodeString(value)
if err != nil { if err != nil {
@@ -130,24 +129,30 @@ func testVector(t *testing.T, test []byte) {
} }
r, err := age.Decrypt(bytes.NewReader(test), identities...) r, err := age.Decrypt(bytes.NewReader(test), identities...)
if err != nil { if err != nil && strings.HasSuffix(err.Error(), "bad header MAC") {
if expectHeaderFailure { if expect == "HMAC failure" {
t.Log(err) t.Log(err)
return return
} }
t.Fatal("unexpected header error:", err) t.Fatalf("expected %s, got HMAC error", expect)
} else if expectHeaderFailure { } else if err != nil {
t.Fatal("expected header error") if expect == "header failure" {
t.Log(err)
return
}
t.Fatalf("expected %s, got: %v", expect, err)
} else if expect != "success" && expect != "payload failure" {
t.Fatalf("expected %s, got success", expect)
} }
out, err := io.ReadAll(r) out, err := io.ReadAll(r)
if err != nil { if err != nil {
if expectPayloadFailure { if expect == "payload failure" {
t.Log(err) t.Log(err)
return return
} }
t.Fatal("unexpected payload error:", err) t.Fatalf("expected %s, got: %v", expect, err)
} else if expectPayloadFailure { } else if expect != "success" {
t.Fatal("expected payload error") t.Fatalf("expected %s, got success", expect)
} }
if sha256.Sum256(out) != *payloadHash { if sha256.Sum256(out) != *payloadHash {
t.Error("payload hash mismatch") t.Error("payload hash mismatch")

View File

@@ -16,6 +16,6 @@ func main() {
f.HMAC() f.HMAC()
f.FileKey(testkit.TestFileKey) f.FileKey(testkit.TestFileKey)
f.Payload("age") f.Payload("age")
f.ExpectHeaderFailure() f.ExpectHMACFailure()
f.Generate() f.Generate()
} }

21
tests/stanza_bad_start.go Normal file
View File

@@ -0,0 +1,21 @@
// 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 "filippo.io/age/internal/testkit"
func main() {
f := testkit.NewTestFile()
f.VersionLine("v1")
f.X25519(testkit.TestX25519Recipient)
f.TextLine("-- stanza")
f.Body([]byte(""))
f.HMAC()
f.Payload("age")
f.ExpectHeaderFailure()
f.Generate()
}

View File

@@ -0,0 +1,26 @@
// 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 (
"bytes"
"filippo.io/age/internal/testkit"
)
func main() {
f := testkit.NewTestFile()
f.VersionLine("v1")
f.X25519(testkit.TestX25519Recipient)
f.ArgsLine("stanza")
f.Body(bytes.Repeat([]byte("A"), 50))
f.TextLine(f.UnreadLine() + "=")
f.HMAC()
f.Payload("age")
f.ExpectHeaderFailure()
f.Generate()
}

View File

@@ -0,0 +1,21 @@
// 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 "filippo.io/age/internal/testkit"
func main() {
f := testkit.NewTestFile()
f.VersionLine("v1")
f.X25519(testkit.TestX25519Recipient)
f.ArgsLine("stanza", "", "argument")
f.Body([]byte(""))
f.HMAC()
f.Payload("age")
f.ExpectHeaderFailure()
f.Generate()
}

View File

@@ -17,7 +17,7 @@ func main() {
f.VersionLine("v1") f.VersionLine("v1")
f.X25519(testkit.TestX25519Recipient) f.X25519(testkit.TestX25519Recipient)
f.ArgsLine("stanza") f.ArgsLine("stanza")
f.Body(bytes.Repeat([]byte("A"), 48)) f.Body(bytes.Repeat([]byte("A"), 48*2))
f.HMAC() f.HMAC()
f.Payload("age") f.Payload("age")
f.Generate() f.Generate()

View File

@@ -0,0 +1,21 @@
// 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 "filippo.io/age/internal/testkit"
func main() {
f := testkit.NewTestFile()
f.VersionLine("v1")
f.X25519(testkit.TestX25519Recipient)
f.ArgsLine("stanza", "è")
f.Body([]byte(""))
f.HMAC()
f.Payload("age")
f.ExpectHeaderFailure()
f.Generate()
}

27
tests/stanza_long_line.go Normal file
View File

@@ -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)
f.ArgsLine("stanza")
f.TextLine(strings.Repeat("A", 68))
f.TextLine("")
f.HMAC()
f.Payload("age")
f.ExpectHeaderFailure()
f.Comment("a body line is longer than 64 columns")
f.Generate()
}

View File

@@ -0,0 +1,21 @@
// 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 "filippo.io/age/internal/testkit"
func main() {
f := testkit.NewTestFile()
f.VersionLine("v1")
f.X25519(testkit.TestX25519Recipient)
f.ArgsLine()
f.Body([]byte(""))
f.HMAC()
f.Payload("age")
f.ExpectHeaderFailure()
f.Generate()
}

View File

@@ -0,0 +1,26 @@
// 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 (
"bytes"
"filippo.io/age/internal/testkit"
)
func main() {
f := testkit.NewTestFile()
f.VersionLine("v1")
f.X25519(testkit.TestX25519Recipient)
f.ArgsLine("stanza")
f.Body(bytes.Repeat([]byte("A"), 50))
f.TextLine(testkit.NotCanonicalBase64(f.UnreadLine()))
f.HMAC()
f.Payload("age")
f.ExpectHeaderFailure()
f.Generate()
}

View File

@@ -0,0 +1,25 @@
// 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)
f.ArgsLine("stanza")
f.TextLine(strings.Repeat("A", 32) + "\r" + strings.Repeat("A", 31))
f.HMAC()
f.Payload("age")
f.ExpectHeaderFailure()
f.Generate()
}

View File

@@ -0,0 +1,19 @@
// 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 "filippo.io/age/internal/testkit"
func main() {
f := testkit.NewTestFile()
f.VersionLine("v1234")
f.X25519(testkit.TestX25519Recipient)
f.HMAC()
f.Payload("age")
f.ExpectHeaderFailure()
f.Generate()
}

27
tests/x25519_bad_tag.go Normal file
View File

@@ -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 (
"encoding/base64"
"filippo.io/age/internal/testkit"
)
func main() {
f := testkit.NewTestFile()
f.VersionLine("v1")
f.X25519(testkit.TestX25519Recipient)
body, _ := base64.RawStdEncoding.DecodeString(f.UnreadLine())
body[len(body)-1] ^= 0xff
f.TextLine(base64.RawStdEncoding.EncodeToString(body))
f.HMAC()
f.Payload("age")
f.ExpectHeaderFailure()
f.Comment("the ChaCha20Poly1305 authentication tag on the body of the X25519 stanza is wrong")
f.Generate()
}

22
tests/x25519_grease.go Normal file
View File

@@ -0,0 +1,22 @@
// 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 "filippo.io/age/internal/testkit"
func main() {
f := testkit.NewTestFile()
f.VersionLine("v1")
f.ArgsLine("grease")
f.Body(nil)
f.X25519(testkit.TestX25519Recipient)
f.ArgsLine("grease")
f.Body(nil)
f.HMAC()
f.Payload("age")
f.Generate()
}

View File

@@ -6,26 +6,14 @@
package main package main
import ( import "filippo.io/age/internal/testkit"
"crypto/sha256"
"encoding/base64"
"filippo.io/age/internal/testkit"
"golang.org/x/crypto/curve25519"
"golang.org/x/crypto/hkdf"
)
func main() { func main() {
f := testkit.NewTestFile() f := testkit.NewTestFile()
f.VersionLine("v1") f.VersionLine("v1")
f.X25519RecordIdentity(testkit.TestX25519Identity) f.X25519RecordIdentity(testkit.TestX25519Identity)
share := make([]byte, curve25519.PointSize) share := make([]byte, 32)
f.ArgsLine("X25519", base64.RawStdEncoding.EncodeToString(share)) f.X25519Stanza(share, testkit.TestX25519Identity)
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.HMAC() f.HMAC()
f.Payload("age") f.Payload("age")
f.ExpectHeaderFailure() f.ExpectHeaderFailure()

View File

@@ -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"
"filippo.io/age/internal/testkit"
"golang.org/x/crypto/curve25519"
)
func main() {
f := testkit.NewTestFile()
f.VersionLine("v1")
share, _ := curve25519.X25519(f.Rand(32), curve25519.Basepoint)
f.X25519RecordIdentity(testkit.TestX25519Identity)
f.X25519Stanza(share, testkit.TestX25519Identity)
body, _ := f.UnreadLine(), f.UnreadLine()
f.TextLine("-> 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()
}

View File

@@ -6,14 +6,7 @@
package main package main
import ( import "filippo.io/age/internal/testkit"
"crypto/sha256"
"encoding/base64"
"filippo.io/age/internal/testkit"
"golang.org/x/crypto/curve25519"
"golang.org/x/crypto/hkdf"
)
func main() { func main() {
f := testkit.NewTestFile() f := testkit.NewTestFile()
@@ -24,12 +17,7 @@ func main() {
share := []byte{0x5f, 0x9c, 0x95, 0xbc, 0xa3, 0x50, 0x8c, 0x24, 0xb1, 0xd0, share := []byte{0x5f, 0x9c, 0x95, 0xbc, 0xa3, 0x50, 0x8c, 0x24, 0xb1, 0xd0,
0xb1, 0x55, 0x9c, 0x83, 0xef, 0x5b, 0x04, 0x44, 0x5c, 0xc4, 0x58, 0x1c, 0xb1, 0x55, 0x9c, 0x83, 0xef, 0x5b, 0x04, 0x44, 0x5c, 0xc4, 0x58, 0x1c,
0x8e, 0x86, 0xd8, 0x22, 0x4e, 0xdd, 0xd0, 0x9f, 0x11, 0xd7} 0x8e, 0x86, 0xd8, 0x22, 0x4e, 0xdd, 0xd0, 0x9f, 0x11, 0xd7}
f.ArgsLine("X25519", base64.RawStdEncoding.EncodeToString(share)) f.X25519Stanza(share, testkit.TestX25519Identity)
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.HMAC() f.HMAC()
f.Payload("age") f.Payload("age")
f.ExpectHeaderFailure() f.ExpectHeaderFailure()

27
tests/x25519_lowercase.go Normal file
View File

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

View File

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