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 TEiF0ypqr+bpvcqXNyCVJpL7OuwPdVwPL7KQEbFDOCc +EmECAEcKN+n/Vs9SbWiV+Hu0r+E8R77DdWYyd83nw7U +-- stanza + +--- lpxzkyQGe/sA7F1yh4c6KVZV7//jANm5lYefTToioXs +îÏbÇΑ´3'NhÔòùL·L[þ÷¾ªRÈð¼™,ƒ1ûf \ No newline at end of file diff --git a/testdata/testkit/stanza_base64_padding b/testdata/testkit/stanza_base64_padding new file mode 100644 index 0000000..18ad2d2 --- /dev/null +++ b/testdata/testkit/stanza_base64_padding @@ -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 \ No newline at end of file diff --git a/testdata/testkit/stanza_empty_argument b/testdata/testkit/stanza_empty_argument new file mode 100644 index 0000000..cb9ef0b --- /dev/null +++ b/testdata/testkit/stanza_empty_argument @@ -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 \ No newline at end of file diff --git a/testdata/testkit/stanza_empty_last_line b/testdata/testkit/stanza_empty_last_line index 285f0d8..30a6c8f 100644 --- a/testdata/testkit/stanza_empty_last_line +++ b/testdata/testkit/stanza_empty_last_line @@ -8,6 +8,7 @@ age-encryption.org/v1 EmECAEcKN+n/Vs9SbWiV+Hu0r+E8R77DdWYyd83nw7U -> stanza QUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFB +QUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFB ---- +3PC416gxa7Mk7WxpX0kb6DVfSuCun0niGre+G4bZhE +--- cb4SqtunSJzXKDGjqeYxuva9Be80QXEDKDn2aKBaCsw îÏbÇΑ´3'NhÔòùL·L[þ÷¾ªRÈð¼™,ƒ1ûf \ No newline at end of file diff --git a/testdata/testkit/stanza_invalid_character b/testdata/testkit/stanza_invalid_character new file mode 100644 index 0000000..d5c8b43 --- /dev/null +++ b/testdata/testkit/stanza_invalid_character @@ -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 \ No newline at end of file diff --git a/testdata/testkit/stanza_long_line b/testdata/testkit/stanza_long_line new file mode 100644 index 0000000..5435260 --- /dev/null +++ b/testdata/testkit/stanza_long_line @@ -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 \ No newline at end of file diff --git a/testdata/testkit/stanza_no_arguments b/testdata/testkit/stanza_no_arguments new file mode 100644 index 0000000..a667ad9 --- /dev/null +++ b/testdata/testkit/stanza_no_arguments @@ -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 \ No newline at end of file diff --git a/testdata/testkit/stanza_not_canonical b/testdata/testkit/stanza_not_canonical new file mode 100644 index 0000000..f7794aa --- /dev/null +++ b/testdata/testkit/stanza_not_canonical @@ -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 \ No newline at end of file diff --git a/testdata/testkit/stanza_spurious_cr b/testdata/testkit/stanza_spurious_cr new file mode 100644 index 0000000..b85c2ce --- /dev/null +++ b/testdata/testkit/stanza_spurious_cr @@ -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 \ No newline at end of file diff --git a/testdata/testkit/version_unsupported b/testdata/testkit/version_unsupported new file mode 100644 index 0000000..e7a7ddf --- /dev/null +++ b/testdata/testkit/version_unsupported @@ -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 \ No newline at end of file diff --git a/testdata/testkit/x25519_bad_tag b/testdata/testkit/x25519_bad_tag new file mode 100644 index 0000000..9a08745 --- /dev/null +++ b/testdata/testkit/x25519_bad_tag @@ -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 \ No newline at end of file diff --git a/testdata/testkit/x25519_grease b/testdata/testkit/x25519_grease new file mode 100644 index 0000000..aa212d9 --- /dev/null +++ b/testdata/testkit/x25519_grease @@ -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 \ No newline at end of file diff --git a/testdata/testkit/long_file_key b/testdata/testkit/x25519_long_file_key similarity index 100% rename from testdata/testkit/long_file_key rename to testdata/testkit/x25519_long_file_key diff --git a/testdata/testkit/x25519_long_share b/testdata/testkit/x25519_long_share new file mode 100644 index 0000000..a2f3a04 --- /dev/null +++ b/testdata/testkit/x25519_long_share @@ -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 \ No newline at end of file diff --git a/testdata/testkit/x25519_lowercase b/testdata/testkit/x25519_lowercase new file mode 100644 index 0000000..853d408 --- /dev/null +++ b/testdata/testkit/x25519_lowercase @@ -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 \ No newline at end of file diff --git a/testdata/testkit/x25519_short_share b/testdata/testkit/x25519_short_share new file mode 100644 index 0000000..7feb27e --- /dev/null +++ b/testdata/testkit/x25519_short_share @@ -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ŠóÎ| \ No newline at end of file diff --git a/testkit_test.go b/testkit_test.go index 578efcf..3a44b48 100644 --- a/testkit_test.go +++ b/testkit_test.go @@ -75,10 +75,9 @@ func TestVectors(t *testing.T) { func testVector(t *testing.T, test []byte) { var ( - expectHeaderFailure bool - expectPayloadFailure bool - payloadHash *[32]byte - identities []age.Identity + expect string + payloadHash *[32]byte + identities []age.Identity ) for { @@ -95,13 +94,13 @@ func testVector(t *testing.T, test []byte) { case "expect": switch value { case "success": + case "HMAC failure": case "header failure": - expectHeaderFailure = true case "payload failure": - expectPayloadFailure = true default: t.Fatal("invalid test file: unknown expect value:", value) } + expect = value case "payload": h, err := hex.DecodeString(value) if err != nil { @@ -130,24 +129,30 @@ func testVector(t *testing.T, test []byte) { } r, err := age.Decrypt(bytes.NewReader(test), identities...) - if err != nil { - if expectHeaderFailure { + if err != nil && strings.HasSuffix(err.Error(), "bad header MAC") { + if expect == "HMAC failure" { t.Log(err) return } - t.Fatal("unexpected header error:", err) - } else if expectHeaderFailure { - t.Fatal("expected header error") + t.Fatalf("expected %s, got HMAC error", expect) + } else if err != nil { + 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) if err != nil { - if expectPayloadFailure { + if expect == "payload failure" { t.Log(err) return } - t.Fatal("unexpected payload error:", err) - } else if expectPayloadFailure { - t.Fatal("expected payload error") + t.Fatalf("expected %s, got: %v", expect, err) + } else if expect != "success" { + t.Fatalf("expected %s, got success", expect) } if sha256.Sum256(out) != *payloadHash { t.Error("payload hash mismatch") diff --git a/tests/crlf.go b/tests/header_crlf.go similarity index 100% rename from tests/crlf.go rename to tests/header_crlf.go diff --git a/tests/hmac_bad.go b/tests/hmac_bad.go index a8556f0..b6a7eba 100644 --- a/tests/hmac_bad.go +++ b/tests/hmac_bad.go @@ -16,6 +16,6 @@ func main() { f.HMAC() f.FileKey(testkit.TestFileKey) f.Payload("age") - f.ExpectHeaderFailure() + f.ExpectHMACFailure() f.Generate() } diff --git a/tests/long_file_key_scrypt.go b/tests/scrypt_long_file_key.go similarity index 100% rename from tests/long_file_key_scrypt.go rename to tests/scrypt_long_file_key.go diff --git a/tests/stanza_bad_start.go b/tests/stanza_bad_start.go new file mode 100644 index 0000000..3e473c2 --- /dev/null +++ b/tests/stanza_bad_start.go @@ -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() +} diff --git a/tests/stanza_base64_padding.go b/tests/stanza_base64_padding.go new file mode 100644 index 0000000..0c81221 --- /dev/null +++ b/tests/stanza_base64_padding.go @@ -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() +} diff --git a/tests/stanza_empty_argument.go b/tests/stanza_empty_argument.go new file mode 100644 index 0000000..60b707f --- /dev/null +++ b/tests/stanza_empty_argument.go @@ -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() +} diff --git a/tests/stanza_empty_last_line.go b/tests/stanza_empty_last_line.go index 519be1e..8ed8f78 100644 --- a/tests/stanza_empty_last_line.go +++ b/tests/stanza_empty_last_line.go @@ -17,7 +17,7 @@ func main() { f.VersionLine("v1") f.X25519(testkit.TestX25519Recipient) f.ArgsLine("stanza") - f.Body(bytes.Repeat([]byte("A"), 48)) + f.Body(bytes.Repeat([]byte("A"), 48*2)) f.HMAC() f.Payload("age") f.Generate() diff --git a/tests/stanza_invalid_character.go b/tests/stanza_invalid_character.go new file mode 100644 index 0000000..bd9c7e3 --- /dev/null +++ b/tests/stanza_invalid_character.go @@ -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() +} diff --git a/tests/stanza_long_line.go b/tests/stanza_long_line.go new file mode 100644 index 0000000..5eb7d5c --- /dev/null +++ b/tests/stanza_long_line.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) + 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() +} diff --git a/tests/stanza_no_arguments.go b/tests/stanza_no_arguments.go new file mode 100644 index 0000000..ecba530 --- /dev/null +++ b/tests/stanza_no_arguments.go @@ -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() +} diff --git a/tests/stanza_not_canonical.go b/tests/stanza_not_canonical.go new file mode 100644 index 0000000..88935e7 --- /dev/null +++ b/tests/stanza_not_canonical.go @@ -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() +} diff --git a/tests/stanza_spurious_cr.go b/tests/stanza_spurious_cr.go new file mode 100644 index 0000000..b898616 --- /dev/null +++ b/tests/stanza_spurious_cr.go @@ -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() +} diff --git a/tests/version_unsupported.go b/tests/version_unsupported.go new file mode 100644 index 0000000..e1fc997 --- /dev/null +++ b/tests/version_unsupported.go @@ -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() +} diff --git a/tests/x25519_bad_tag.go b/tests/x25519_bad_tag.go new file mode 100644 index 0000000..e56cd7c --- /dev/null +++ b/tests/x25519_bad_tag.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 ( + "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() +} diff --git a/tests/x25519_grease.go b/tests/x25519_grease.go new file mode 100644 index 0000000..98f7148 --- /dev/null +++ b/tests/x25519_grease.go @@ -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() +} diff --git a/tests/x25519_identity.go b/tests/x25519_identity.go index be8e531..f1b86e6 100644 --- a/tests/x25519_identity.go +++ b/tests/x25519_identity.go @@ -6,26 +6,14 @@ 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() f.VersionLine("v1") f.X25519RecordIdentity(testkit.TestX25519Identity) - share := make([]byte, curve25519.PointSize) - 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) + share := make([]byte, 32) + f.X25519Stanza(share, testkit.TestX25519Identity) f.HMAC() f.Payload("age") f.ExpectHeaderFailure() diff --git a/tests/long_file_key.go b/tests/x25519_long_file_key.go similarity index 100% rename from tests/long_file_key.go rename to tests/x25519_long_file_key.go diff --git a/tests/x25519_long_share.go b/tests/x25519_long_share.go new file mode 100644 index 0000000..eca52d4 --- /dev/null +++ b/tests/x25519_long_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" + + "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() +} 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() +}