diff --git a/internal/testkit/testkit.go b/internal/testkit/testkit.go index ad7fc43..f6bb52b 100644 --- a/internal/testkit/testkit.go +++ b/internal/testkit/testkit.go @@ -63,6 +63,7 @@ type TestFile struct { comment string identities []string passphrases []string + armor bool } func NewTestFile() *TestFile { @@ -87,7 +88,7 @@ func (f *TestFile) TextLine(s string) { func (f *TestFile) UnreadLine() string { buf := bytes.TrimSuffix(f.Buf.Bytes(), []byte("\n")) - idx := bytes.LastIndex(buf[:len(buf)-1], []byte("\n")) + 1 + idx := bytes.LastIndex(buf, []byte("\n")) + 1 f.Buf.Reset() f.Buf.Write(buf[:idx]) return string(buf[idx:]) @@ -122,6 +123,16 @@ func (f *TestFile) Body(body []byte) { } } +func (f *TestFile) Base64Padding() { + line := f.UnreadLine() + paddingLen := 4 - len(line)%4 + if paddingLen == 4 { + paddingLen = 0 + } + padding := strings.Repeat("=", paddingLen) + f.TextLine(line + padding) +} + func (f *TestFile) AEADBody(key, body []byte) { aead, _ := chacha20poly1305.New(key) f.Body(aead.Seal(nil, make([]byte, chacha20poly1305.NonceSize), body, nil)) @@ -231,6 +242,11 @@ func (f *TestFile) ExpectHeaderFailure() { f.expect = "header failure" } +func (f *TestFile) ExpectArmorFailure() { + f.armor = true + f.expect = "armor failure" +} + func (f *TestFile) ExpectPayloadFailure() { f.expect = "payload failure" f.payload.Reset() @@ -255,6 +271,22 @@ func (f *TestFile) Comment(c string) { f.comment = c } +func (f *TestFile) BeginArmor(t string) { + f.armor = true + f.TextLine("-----BEGIN " + t + "-----") +} + +func (f *TestFile) EndArmor(t string) { + f.armor = true + f.TextLine("-----END " + t + "-----") +} + +func (f *TestFile) Bytes() []byte { + out := make([]byte, f.Buf.Len()) + copy(out, f.Buf.Bytes()) + return out +} + func (f *TestFile) Generate() { fmt.Printf("expect: %s\n", f.expect) if f.expect == "success" || f.expect == "payload failure" { @@ -267,6 +299,9 @@ func (f *TestFile) Generate() { for _, p := range f.passphrases { fmt.Printf("passphrase: %s\n", p) } + if f.armor { + fmt.Printf("armored: yes\n") + } if f.comment != "" { fmt.Printf("comment: %s\n", f.comment) } diff --git a/testdata/testkit/armor b/testdata/testkit/armor new file mode 100644 index 0000000..7ebd0db Binary files /dev/null and b/testdata/testkit/armor differ diff --git a/testdata/testkit/armor_crlf b/testdata/testkit/armor_crlf new file mode 100644 index 0000000..b474ce1 Binary files /dev/null and b/testdata/testkit/armor_crlf differ diff --git a/testdata/testkit/armor_empty_line_begin b/testdata/testkit/armor_empty_line_begin new file mode 100644 index 0000000..5900cc5 Binary files /dev/null and b/testdata/testkit/armor_empty_line_begin differ diff --git a/testdata/testkit/armor_empty_line_end b/testdata/testkit/armor_empty_line_end new file mode 100644 index 0000000..0a6975f Binary files /dev/null and b/testdata/testkit/armor_empty_line_end differ diff --git a/testdata/testkit/armor_eol_between_padding b/testdata/testkit/armor_eol_between_padding new file mode 100644 index 0000000..f69d0cf Binary files /dev/null and b/testdata/testkit/armor_eol_between_padding differ diff --git a/testdata/testkit/armor_full_last_line b/testdata/testkit/armor_full_last_line new file mode 100644 index 0000000..54439f0 Binary files /dev/null and b/testdata/testkit/armor_full_last_line differ diff --git a/testdata/testkit/armor_garbage_encoded b/testdata/testkit/armor_garbage_encoded new file mode 100644 index 0000000..8f95ca3 Binary files /dev/null and b/testdata/testkit/armor_garbage_encoded differ diff --git a/testdata/testkit/armor_garbage_leading b/testdata/testkit/armor_garbage_leading new file mode 100644 index 0000000..f65c11a Binary files /dev/null and b/testdata/testkit/armor_garbage_leading differ diff --git a/testdata/testkit/armor_garbage_trailing b/testdata/testkit/armor_garbage_trailing new file mode 100644 index 0000000..67715ac Binary files /dev/null and b/testdata/testkit/armor_garbage_trailing differ diff --git a/testdata/testkit/armor_header_crlf b/testdata/testkit/armor_header_crlf new file mode 100644 index 0000000..ce52d31 Binary files /dev/null and b/testdata/testkit/armor_header_crlf differ diff --git a/testdata/testkit/armor_headers b/testdata/testkit/armor_headers new file mode 100644 index 0000000..3ca00d2 Binary files /dev/null and b/testdata/testkit/armor_headers differ diff --git a/testdata/testkit/armor_invalid_character_header b/testdata/testkit/armor_invalid_character_header new file mode 100644 index 0000000..599d1cd Binary files /dev/null and b/testdata/testkit/armor_invalid_character_header differ diff --git a/testdata/testkit/armor_invalid_character_payload b/testdata/testkit/armor_invalid_character_payload new file mode 100644 index 0000000..634b922 Binary files /dev/null and b/testdata/testkit/armor_invalid_character_payload differ diff --git a/testdata/testkit/armor_long_line b/testdata/testkit/armor_long_line new file mode 100644 index 0000000..7bafc0e Binary files /dev/null and b/testdata/testkit/armor_long_line differ diff --git a/testdata/testkit/armor_lowercase b/testdata/testkit/armor_lowercase new file mode 100644 index 0000000..3ab173d Binary files /dev/null and b/testdata/testkit/armor_lowercase differ diff --git a/testdata/testkit/armor_no_end_line b/testdata/testkit/armor_no_end_line new file mode 100644 index 0000000..d4a2bce Binary files /dev/null and b/testdata/testkit/armor_no_end_line differ diff --git a/testdata/testkit/armor_no_eol b/testdata/testkit/armor_no_eol new file mode 100644 index 0000000..49630cb Binary files /dev/null and b/testdata/testkit/armor_no_eol differ diff --git a/testdata/testkit/armor_no_match b/testdata/testkit/armor_no_match new file mode 100644 index 0000000..f694a45 Binary files /dev/null and b/testdata/testkit/armor_no_match differ diff --git a/testdata/testkit/armor_no_padding b/testdata/testkit/armor_no_padding new file mode 100644 index 0000000..737ed67 Binary files /dev/null and b/testdata/testkit/armor_no_padding differ diff --git a/testdata/testkit/armor_not_canonical b/testdata/testkit/armor_not_canonical new file mode 100644 index 0000000..a6c6f56 Binary files /dev/null and b/testdata/testkit/armor_not_canonical differ diff --git a/testdata/testkit/armor_pgp_checksum b/testdata/testkit/armor_pgp_checksum new file mode 100644 index 0000000..c245ba2 Binary files /dev/null and b/testdata/testkit/armor_pgp_checksum differ diff --git a/testdata/testkit/armor_short_line b/testdata/testkit/armor_short_line new file mode 100644 index 0000000..a92d523 Binary files /dev/null and b/testdata/testkit/armor_short_line differ diff --git a/testdata/testkit/armor_whitespace_begin b/testdata/testkit/armor_whitespace_begin new file mode 100644 index 0000000..91c70ab Binary files /dev/null and b/testdata/testkit/armor_whitespace_begin differ diff --git a/testdata/testkit/armor_whitespace_end b/testdata/testkit/armor_whitespace_end new file mode 100644 index 0000000..22bfb39 Binary files /dev/null and b/testdata/testkit/armor_whitespace_end differ diff --git a/testdata/testkit/armor_whitespace_eol b/testdata/testkit/armor_whitespace_eol new file mode 100644 index 0000000..d0a5904 Binary files /dev/null and b/testdata/testkit/armor_whitespace_eol differ diff --git a/testdata/testkit/armor_whitespace_last_line b/testdata/testkit/armor_whitespace_last_line new file mode 100644 index 0000000..f925d3a Binary files /dev/null and b/testdata/testkit/armor_whitespace_last_line differ diff --git a/testdata/testkit/armor_whitespace_line_start b/testdata/testkit/armor_whitespace_line_start new file mode 100644 index 0000000..6093e97 Binary files /dev/null and b/testdata/testkit/armor_whitespace_line_start differ diff --git a/testdata/testkit/armor_whitespace_outside b/testdata/testkit/armor_whitespace_outside new file mode 100644 index 0000000..76b891a Binary files /dev/null and b/testdata/testkit/armor_whitespace_outside differ diff --git a/testdata/testkit/armor_wrong_type b/testdata/testkit/armor_wrong_type new file mode 100644 index 0000000..28f6d16 Binary files /dev/null and b/testdata/testkit/armor_wrong_type differ diff --git a/testkit_test.go b/testkit_test.go index 166e15e..a354fd9 100644 --- a/testkit_test.go +++ b/testkit_test.go @@ -11,6 +11,7 @@ import ( "bytes" "crypto/sha256" "encoding/hex" + "errors" "flag" "io" "log" @@ -21,6 +22,7 @@ import ( "testing" "filippo.io/age" + "filippo.io/age/armor" ) //go:generate go test -generate -run ^$ @@ -81,6 +83,7 @@ func testVector(t *testing.T, test []byte) { expect string payloadHash *[32]byte identities []age.Identity + armored bool ) for { @@ -99,6 +102,7 @@ func testVector(t *testing.T, test []byte) { case "success": case "HMAC failure": case "header failure": + case "armor failure": case "payload failure": case "no match": default: @@ -123,6 +127,8 @@ func testVector(t *testing.T, test []byte) { t.Fatal(err) } identities = append(identities, i) + case "armored": + armored = true case "file key": // Ignored. case "comment": @@ -132,13 +138,23 @@ func testVector(t *testing.T, test []byte) { } } - r, err := age.Decrypt(bytes.NewReader(test), identities...) + var in io.Reader = bytes.NewReader(test) + if armored { + in = armor.NewReader(in) + } + r, err := age.Decrypt(in, identities...) if err != nil && strings.HasSuffix(err.Error(), "bad header MAC") { if expect == "HMAC failure" { t.Log(err) return } t.Fatalf("expected %s, got HMAC error", expect) + } else if e := new(armor.Error); errors.As(err, &e) { + if expect == "armor failure" { + t.Log(err) + return + } + t.Fatalf("expected %s, got: %v", expect, err) } else if _, ok := err.(*age.NoIdentityMatchError); ok { if expect == "no match" { t.Log(err) @@ -151,19 +167,24 @@ func testVector(t *testing.T, test []byte) { return } t.Fatalf("expected %s, got: %v", expect, err) - } else if expect != "success" && expect != "payload failure" { + } else if expect != "success" && expect != "payload failure" && + expect != "armor failure" { t.Fatalf("expected %s, got success", expect) } out, err := io.ReadAll(r) - if err != nil { - if expect == "payload failure" { - t.Log(err) - if payloadHash != nil && sha256.Sum256(out) != *payloadHash { - t.Error("partial payload hash mismatch") - } - return - } + if err != nil && expect == "success" { t.Fatalf("expected %s, got: %v", expect, err) + } else if err != nil { + t.Log(err) + if expect == "armor failure" { + if e := new(armor.Error); !errors.As(err, &e) { + t.Errorf("expected armor.Error, got %T", err) + } + } + if payloadHash != nil && sha256.Sum256(out) != *payloadHash { + t.Error("partial payload hash mismatch") + } + return } else if expect != "success" { t.Fatalf("expected %s, got success", expect) } diff --git a/tests/armor.go b/tests/armor.go new file mode 100644 index 0000000..1c00ead --- /dev/null +++ b/tests/armor.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 +// +build ignore + +package main + +import "filippo.io/age/internal/testkit" + +func main() { + f := testkit.NewTestFile() + f.VersionLine("v1") + f.X25519(testkit.TestX25519Recipient) + f.HMAC() + f.Payload("age") + file := f.Bytes() + f.Buf.Reset() + f.BeginArmor("AGE ENCRYPTED FILE") + f.Body(file) + f.Base64Padding() + f.EndArmor("AGE ENCRYPTED FILE") + f.Generate() +} diff --git a/tests/armor_crlf.go b/tests/armor_crlf.go new file mode 100644 index 0000000..8b2f569 --- /dev/null +++ b/tests/armor_crlf.go @@ -0,0 +1,33 @@ +// 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 +// +build ignore + +package main + +import ( + "bytes" + + "filippo.io/age/internal/testkit" +) + +func main() { + f := testkit.NewTestFile() + f.VersionLine("v1") + f.X25519(testkit.TestX25519Recipient) + f.HMAC() + f.Payload("age") + file := f.Bytes() + f.Buf.Reset() + f.BeginArmor("AGE ENCRYPTED FILE") + f.Body(file) + f.Base64Padding() + f.EndArmor("AGE ENCRYPTED FILE") + armored := f.Bytes() + f.Buf.Reset() + f.Buf.Write(bytes.Replace(armored, []byte("\n"), []byte("\r\n"), -1)) + f.Comment("CRLF is allowed as a end of line for armored files") + f.Generate() +} diff --git a/tests/armor_empty_line_begin.go b/tests/armor_empty_line_begin.go new file mode 100644 index 0000000..75dfff2 --- /dev/null +++ b/tests/armor_empty_line_begin.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 +// +build ignore + +package main + +import "filippo.io/age/internal/testkit" + +func main() { + f := testkit.NewTestFile() + f.VersionLine("v1") + f.X25519(testkit.TestX25519Recipient) + f.HMAC() + f.Payload("age") + file := f.Bytes() + f.Buf.Reset() + f.BeginArmor("AGE ENCRYPTED FILE") + f.TextLine("") + f.Body(file) + f.Base64Padding() + f.EndArmor("AGE ENCRYPTED FILE") + f.ExpectArmorFailure() + f.Generate() +} diff --git a/tests/armor_empty_line_end.go b/tests/armor_empty_line_end.go new file mode 100644 index 0000000..68ff502 --- /dev/null +++ b/tests/armor_empty_line_end.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 +// +build ignore + +package main + +import "filippo.io/age/internal/testkit" + +func main() { + f := testkit.NewTestFile() + f.VersionLine("v1") + f.X25519(testkit.TestX25519Recipient) + f.HMAC() + f.Payload("age") + file := f.Bytes() + f.Buf.Reset() + f.BeginArmor("AGE ENCRYPTED FILE") + f.Body(file) + f.Base64Padding() + f.TextLine("") + f.EndArmor("AGE ENCRYPTED FILE") + f.ExpectArmorFailure() + f.Generate() +} diff --git a/tests/armor_eol_between_padding.go b/tests/armor_eol_between_padding.go new file mode 100644 index 0000000..eaef051 --- /dev/null +++ b/tests/armor_eol_between_padding.go @@ -0,0 +1,39 @@ +// 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 +// +build ignore + +package main + +import ( + "strings" + + "filippo.io/age/internal/testkit" +) + +// See base64finl in RFC 7468. +// ; ...AB= = is not good, but is valid + +func main() { + f := testkit.NewTestFile() + f.VersionLine("v1") + f.X25519(testkit.TestX25519Recipient) + f.HMAC() + f.Payload("age12") + file := f.Bytes() + f.Buf.Reset() + f.BeginArmor("AGE ENCRYPTED FILE") + f.Body(file) + f.Base64Padding() + line := f.UnreadLine() + if !strings.Contains(line, "==") { + panic("need two padding characters") + } + line = strings.Replace(line, "==", "=\n=", 1) + f.TextLine(line) + f.EndArmor("AGE ENCRYPTED FILE") + f.ExpectArmorFailure() + f.Generate() +} diff --git a/tests/armor_full_last_line.go b/tests/armor_full_last_line.go new file mode 100644 index 0000000..be76f87 --- /dev/null +++ b/tests/armor_full_last_line.go @@ -0,0 +1,29 @@ +// 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 +// +build ignore + +package main + +import "filippo.io/age/internal/testkit" + +func main() { + f := testkit.NewTestFile() + f.VersionLine("v1") + f.X25519(testkit.TestX25519Recipient) + f.HMAC() + f.Payload("age age age age age age age age age age ") + file := f.Bytes() + f.Buf.Reset() + f.BeginArmor("AGE ENCRYPTED FILE") + if len(file)%48 != 0 { + println(len(file) % 48) + panic("last line is not full") + } + f.Body(file) + f.UnreadLine() // Body leaves an empty line, PEM doesn't. + f.EndArmor("AGE ENCRYPTED FILE") + f.Generate() +} diff --git a/tests/armor_garbage_encoded.go b/tests/armor_garbage_encoded.go new file mode 100644 index 0000000..1898582 --- /dev/null +++ b/tests/armor_garbage_encoded.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 +// +build ignore + +package main + +import "filippo.io/age/internal/testkit" + +func main() { + f := testkit.NewTestFile() + f.FileKey(testkit.LargeTestFileKey) + f.VersionLine("v1") + f.X25519(testkit.TestX25519Identity) + f.HMAC() + f.Nonce(testkit.LargeTestNonce) + f.PayloadChunkFinal(testkit.LargeTestFirstChunk) + f.Buf.Write(f.Rand(20)) + f.ExpectPartialPayload(64 * 1024) + file := f.Bytes() + f.Buf.Reset() + f.BeginArmor("AGE ENCRYPTED FILE") + f.Body(file) + f.Base64Padding() + f.EndArmor("AGE ENCRYPTED FILE") + f.Comment("there is trailing garbage encoded after the final chunk") + f.Generate() +} diff --git a/tests/armor_garbage_leading.go b/tests/armor_garbage_leading.go new file mode 100644 index 0000000..a700abc --- /dev/null +++ b/tests/armor_garbage_leading.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 +// +build ignore + +package main + +import "filippo.io/age/internal/testkit" + +func main() { + f := testkit.NewTestFile() + f.VersionLine("v1") + f.X25519(testkit.TestX25519Recipient) + f.HMAC() + f.Payload("age") + file := f.Bytes() + f.Buf.Reset() + f.TextLine("garbage") + f.BeginArmor("AGE ENCRYPTED FILE") + f.Body(file) + f.Base64Padding() + f.EndArmor("AGE ENCRYPTED FILE") + f.ExpectArmorFailure() + f.Generate() +} diff --git a/tests/armor_garbage_trailing.go b/tests/armor_garbage_trailing.go new file mode 100644 index 0000000..5126143 --- /dev/null +++ b/tests/armor_garbage_trailing.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 +// +build ignore + +package main + +import "filippo.io/age/internal/testkit" + +func main() { + f := testkit.NewTestFile() + f.VersionLine("v1") + f.X25519(testkit.TestX25519Recipient) + f.HMAC() + f.Payload("age") + file := f.Bytes() + f.Buf.Reset() + f.BeginArmor("AGE ENCRYPTED FILE") + f.Body(file) + f.Base64Padding() + f.EndArmor("AGE ENCRYPTED FILE") + f.TextLine("garbage") + f.ExpectArmorFailure() + f.Generate() +} diff --git a/tests/armor_header_crlf.go b/tests/armor_header_crlf.go new file mode 100644 index 0000000..5a90246 --- /dev/null +++ b/tests/armor_header_crlf.go @@ -0,0 +1,35 @@ +// 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 +// +build ignore + +package main + +import ( + "bytes" + + "filippo.io/age/internal/testkit" +) + +func main() { + f := testkit.NewTestFile() + f.VersionLine("v1") + f.X25519(testkit.TestX25519Identity) + hdr := f.Buf.Bytes() + f.Buf.Reset() + f.Buf.Write(bytes.Replace(hdr, []byte("\n"), []byte("\r\n"), -1)) + f.HMAC() + f.Buf.WriteString(f.UnreadLine() + "\r\n") + f.Payload("age") + f.ExpectHeaderFailure() + f.Comment("lines in the header end with CRLF instead of LF") + file := f.Bytes() + f.Buf.Reset() + f.BeginArmor("AGE ENCRYPTED FILE") + f.Body(file) + f.Base64Padding() + f.EndArmor("AGE ENCRYPTED FILE") + f.Generate() +} diff --git a/tests/armor_headers.go b/tests/armor_headers.go new file mode 100644 index 0000000..7cfd549 --- /dev/null +++ b/tests/armor_headers.go @@ -0,0 +1,29 @@ +// 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 +// +build ignore + +package main + +import "filippo.io/age/internal/testkit" + +func main() { + f := testkit.NewTestFile() + f.VersionLine("v1") + f.X25519(testkit.TestX25519Recipient) + f.HMAC() + f.Payload("age") + file := f.Bytes() + f.Buf.Reset() + f.BeginArmor("AGE ENCRYPTED FILE") + f.TextLine("Headers: are") + f.TextLine("Not: allowed") + f.TextLine("") + f.Body(file) + f.Base64Padding() + f.EndArmor("AGE ENCRYPTED FILE") + f.ExpectArmorFailure() + f.Generate() +} diff --git a/tests/armor_invalid_character_header.go b/tests/armor_invalid_character_header.go new file mode 100644 index 0000000..cfc7d72 --- /dev/null +++ b/tests/armor_invalid_character_header.go @@ -0,0 +1,34 @@ +// 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 +// +build ignore + +package main + +import ( + "strings" + + "filippo.io/age/internal/testkit" +) + +func main() { + f := testkit.NewTestFile() + f.VersionLine("v1") + f.X25519(testkit.TestX25519Recipient) + f.HMAC() + f.Payload("age") + file := f.Bytes() + f.Buf.Reset() + f.BeginArmor("AGE ENCRYPTED FILE") + f.Body(file) + f.Base64Padding() + begin, rest, _ := strings.Cut(string(f.Bytes()), "\n") + f.Buf.Reset() + f.TextLine(begin) + f.Buf.WriteString(rest[:4] + "*" + rest[5:]) + f.EndArmor("AGE ENCRYPTED FILE") + f.ExpectArmorFailure() + f.Generate() +} diff --git a/tests/armor_invalid_character_payload.go b/tests/armor_invalid_character_payload.go new file mode 100644 index 0000000..0738b5f --- /dev/null +++ b/tests/armor_invalid_character_payload.go @@ -0,0 +1,28 @@ +// 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 +// +build ignore + +package main + +import "filippo.io/age/internal/testkit" + +func main() { + f := testkit.NewTestFile() + f.VersionLine("v1") + f.X25519(testkit.TestX25519Recipient) + f.HMAC() + f.Payload("age") + file := f.Bytes() + f.Buf.Reset() + f.BeginArmor("AGE ENCRYPTED FILE") + f.Body(file) + f.Base64Padding() + line := f.UnreadLine() + f.TextLine("*" + line[1:]) + f.EndArmor("AGE ENCRYPTED FILE") + f.ExpectArmorFailure() + f.Generate() +} diff --git a/tests/armor_long_line.go b/tests/armor_long_line.go new file mode 100644 index 0000000..aae9f94 --- /dev/null +++ b/tests/armor_long_line.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 +// +build ignore + +package main + +import ( + "encoding/base64" + + "filippo.io/age/internal/testkit" +) + +func main() { + f := testkit.NewTestFile() + f.VersionLine("v1") + f.X25519(testkit.TestX25519Recipient) + f.HMAC() + f.Payload("age") + file := f.Bytes() + f.Buf.Reset() + f.BeginArmor("AGE ENCRYPTED FILE") + f.TextLine(base64.StdEncoding.EncodeToString(file)) + f.Base64Padding() + f.EndArmor("AGE ENCRYPTED FILE") + f.ExpectArmorFailure() + f.Generate() +} diff --git a/tests/armor_lowercase.go b/tests/armor_lowercase.go new file mode 100644 index 0000000..cb823b4 --- /dev/null +++ b/tests/armor_lowercase.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 +// +build ignore + +package main + +import "filippo.io/age/internal/testkit" + +func main() { + f := testkit.NewTestFile() + f.VersionLine("v1") + f.X25519(testkit.TestX25519Recipient) + f.HMAC() + f.Payload("age") + file := f.Bytes() + f.Buf.Reset() + f.BeginArmor("age ENCRYPTED FILE") + f.Body(file) + f.Base64Padding() + f.EndArmor("age ENCRYPTED FILE") + f.ExpectArmorFailure() + f.Generate() +} diff --git a/tests/armor_no_end_line.go b/tests/armor_no_end_line.go new file mode 100644 index 0000000..46b8f03 --- /dev/null +++ b/tests/armor_no_end_line.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 +// +build ignore + +package main + +import "filippo.io/age/internal/testkit" + +func main() { + f := testkit.NewTestFile() + f.VersionLine("v1") + f.X25519(testkit.TestX25519Recipient) + f.HMAC() + f.Payload("age") + file := f.Bytes() + f.Buf.Reset() + f.BeginArmor("AGE ENCRYPTED FILE") + f.Body(file) + f.Base64Padding() + f.ExpectArmorFailure() + f.Generate() +} diff --git a/tests/armor_no_eol.go b/tests/armor_no_eol.go new file mode 100644 index 0000000..e9b673d --- /dev/null +++ b/tests/armor_no_eol.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 +// +build ignore + +package main + +import "filippo.io/age/internal/testkit" + +func main() { + f := testkit.NewTestFile() + f.VersionLine("v1") + f.X25519(testkit.TestX25519Recipient) + f.HMAC() + f.Payload("age") + file := f.Bytes() + f.Buf.Reset() + f.BeginArmor("AGE ENCRYPTED FILE") + f.Body(file) + f.Base64Padding() + f.Buf.WriteString("-----END AGE ENCRYPTED FILE-----") + f.Comment("there is no end of line at the end of the file") + f.Generate() +} diff --git a/tests/armor_no_match.go b/tests/armor_no_match.go new file mode 100644 index 0000000..130956d --- /dev/null +++ b/tests/armor_no_match.go @@ -0,0 +1,28 @@ +// 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 +// +build ignore + +package main + +import "filippo.io/age/internal/testkit" + +func main() { + f := testkit.NewTestFile() + f.VersionLine("v1") + identity := f.Rand(32) + f.X25519RecordIdentity(identity) + f.X25519NoRecordIdentity(testkit.TestX25519Recipient) + f.HMAC() + f.Payload("age") + f.ExpectNoMatch() + file := f.Bytes() + f.Buf.Reset() + f.BeginArmor("AGE ENCRYPTED FILE") + f.Body(file) + f.Base64Padding() + f.EndArmor("AGE ENCRYPTED FILE") + f.Generate() +} diff --git a/tests/armor_no_padding.go b/tests/armor_no_padding.go new file mode 100644 index 0000000..789efb9 --- /dev/null +++ b/tests/armor_no_padding.go @@ -0,0 +1,29 @@ +// 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 +// +build ignore + +package main + +import "filippo.io/age/internal/testkit" + +func main() { + f := testkit.NewTestFile() + f.VersionLine("v1") + f.X25519(testkit.TestX25519Recipient) + f.HMAC() + f.Payload("age") + file := f.Bytes() + f.Buf.Reset() + f.BeginArmor("AGE ENCRYPTED FILE") + if len(file)%3 == 0 { + panic("no need for padding") + } + f.Body(file) + f.EndArmor("AGE ENCRYPTED FILE") + f.ExpectArmorFailure() + f.Comment("missing base64 padding") + f.Generate() +} diff --git a/tests/armor_not_canonical.go b/tests/armor_not_canonical.go new file mode 100644 index 0000000..92efb83 --- /dev/null +++ b/tests/armor_not_canonical.go @@ -0,0 +1,28 @@ +// 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 +// +build ignore + +package main + +import "filippo.io/age/internal/testkit" + +func main() { + f := testkit.NewTestFile() + f.VersionLine("v1") + f.X25519(testkit.TestX25519Recipient) + f.HMAC() + f.Payload("age") + file := f.Bytes() + f.Buf.Reset() + f.BeginArmor("AGE ENCRYPTED FILE") + f.Body(file) + f.TextLine(testkit.NotCanonicalBase64(f.UnreadLine())) + f.Base64Padding() + f.EndArmor("AGE ENCRYPTED FILE") + f.ExpectArmorFailure() + f.Comment("base64 is not canonical") + f.Generate() +} diff --git a/tests/armor_pgp_checksum.go b/tests/armor_pgp_checksum.go new file mode 100644 index 0000000..4172f09 --- /dev/null +++ b/tests/armor_pgp_checksum.go @@ -0,0 +1,29 @@ +// 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 +// +build ignore + +package main + +import ( + "filippo.io/age/internal/testkit" + "golang.org/x/crypto/openpgp/armor" +) + +func main() { + f := testkit.NewTestFile() + f.VersionLine("v1") + f.X25519(testkit.TestX25519Recipient) + f.HMAC() + f.Payload("age") + file := f.Bytes() + f.Buf.Reset() + w, _ := armor.Encode(&f.Buf, "AGE ENCRYPTED FILE", nil) + w.Write(file) + w.Close() + f.Buf.WriteString("\n") + f.ExpectArmorFailure() + f.Generate() +} diff --git a/tests/armor_short_line.go b/tests/armor_short_line.go new file mode 100644 index 0000000..7a4df09 --- /dev/null +++ b/tests/armor_short_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 +// +build ignore + +package main + +import "filippo.io/age/internal/testkit" + +func main() { + f := testkit.NewTestFile() + f.VersionLine("v1") + f.X25519(testkit.TestX25519Recipient) + f.HMAC() + f.Payload("age") + file := f.Bytes() + f.Buf.Reset() + f.BeginArmor("AGE ENCRYPTED FILE") + f.Body(file[:12]) + f.Body(file[12:]) + f.Base64Padding() + f.EndArmor("AGE ENCRYPTED FILE") + f.ExpectArmorFailure() + f.Generate() +} diff --git a/tests/armor_whitespace_begin.go b/tests/armor_whitespace_begin.go new file mode 100644 index 0000000..bf34a9a --- /dev/null +++ b/tests/armor_whitespace_begin.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 +// +build ignore + +package main + +import "filippo.io/age/internal/testkit" + +func main() { + f := testkit.NewTestFile() + f.VersionLine("v1") + f.X25519(testkit.TestX25519Recipient) + f.HMAC() + f.Payload("age") + file := f.Bytes() + f.Buf.Reset() + f.TextLine("----- BEGIN AGE ENCRYPTED FILE -----") + f.Body(file) + f.Base64Padding() + f.EndArmor("AGE ENCRYPTED FILE") + f.ExpectArmorFailure() + f.Generate() +} diff --git a/tests/armor_whitespace_end.go b/tests/armor_whitespace_end.go new file mode 100644 index 0000000..c85bac1 --- /dev/null +++ b/tests/armor_whitespace_end.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 +// +build ignore + +package main + +import "filippo.io/age/internal/testkit" + +func main() { + f := testkit.NewTestFile() + f.VersionLine("v1") + f.X25519(testkit.TestX25519Recipient) + f.HMAC() + f.Payload("age") + file := f.Bytes() + f.Buf.Reset() + f.BeginArmor("AGE ENCRYPTED FILE") + f.Body(file) + f.Base64Padding() + f.TextLine("----- END AGE ENCRYPTED FILE -----") + f.ExpectArmorFailure() + f.Generate() +} diff --git a/tests/armor_whitespace_eol.go b/tests/armor_whitespace_eol.go new file mode 100644 index 0000000..32419d8 --- /dev/null +++ b/tests/armor_whitespace_eol.go @@ -0,0 +1,29 @@ +// 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 +// +build ignore + +package main + +import "filippo.io/age/internal/testkit" + +func main() { + f := testkit.NewTestFile() + f.VersionLine("v1") + f.X25519(testkit.TestX25519Recipient) + f.HMAC() + f.Payload("age") + file := f.Bytes() + f.Buf.Reset() + f.BeginArmor("AGE ENCRYPTED FILE") + f.Body(file) + f.Base64Padding() + line2, line1 := f.UnreadLine(), f.UnreadLine() + f.TextLine(line1 + " ") + f.TextLine(line2) + f.EndArmor("AGE ENCRYPTED FILE") + f.ExpectArmorFailure() + f.Generate() +} diff --git a/tests/armor_whitespace_last_line.go b/tests/armor_whitespace_last_line.go new file mode 100644 index 0000000..e287f0a --- /dev/null +++ b/tests/armor_whitespace_last_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 +// +build ignore + +package main + +import "filippo.io/age/internal/testkit" + +func main() { + f := testkit.NewTestFile() + f.VersionLine("v1") + f.X25519(testkit.TestX25519Recipient) + f.HMAC() + f.Payload("age") + file := f.Bytes() + f.Buf.Reset() + f.BeginArmor("AGE ENCRYPTED FILE") + f.Body(file) + f.Base64Padding() + f.TextLine(f.UnreadLine() + " ") + f.EndArmor("AGE ENCRYPTED FILE") + f.ExpectArmorFailure() + f.Generate() +} diff --git a/tests/armor_whitespace_line_start.go b/tests/armor_whitespace_line_start.go new file mode 100644 index 0000000..cb17c9e --- /dev/null +++ b/tests/armor_whitespace_line_start.go @@ -0,0 +1,29 @@ +// 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 +// +build ignore + +package main + +import "filippo.io/age/internal/testkit" + +func main() { + f := testkit.NewTestFile() + f.VersionLine("v1") + f.X25519(testkit.TestX25519Recipient) + f.HMAC() + f.Payload("age") + file := f.Bytes() + f.Buf.Reset() + f.BeginArmor("AGE ENCRYPTED FILE") + f.Body(file) + f.Base64Padding() + line2, line1 := f.UnreadLine(), f.UnreadLine() + f.TextLine(" " + line1) + f.TextLine(line2) + f.EndArmor("AGE ENCRYPTED FILE") + f.ExpectArmorFailure() + f.Generate() +} diff --git a/tests/armor_whitespace_outside.go b/tests/armor_whitespace_outside.go new file mode 100644 index 0000000..516c5fa --- /dev/null +++ b/tests/armor_whitespace_outside.go @@ -0,0 +1,28 @@ +// 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 +// +build ignore + +package main + +import "filippo.io/age/internal/testkit" + +func main() { + f := testkit.NewTestFile() + f.VersionLine("v1") + f.X25519(testkit.TestX25519Recipient) + f.HMAC() + f.Payload("age") + file := f.Bytes() + f.Buf.Reset() + f.Buf.Write([]byte("\n\r \t\n")) + f.BeginArmor("AGE ENCRYPTED FILE") + f.Body(file) + f.Base64Padding() + f.EndArmor("AGE ENCRYPTED FILE") + f.Buf.Write([]byte("\n\r \t\n")) + f.Comment("whitespace is allowed before and after armored files") + f.Generate() +} diff --git a/tests/armor_wrong_type.go b/tests/armor_wrong_type.go new file mode 100644 index 0000000..de8521d --- /dev/null +++ b/tests/armor_wrong_type.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 +// +build ignore + +package main + +import "filippo.io/age/internal/testkit" + +func main() { + f := testkit.NewTestFile() + f.VersionLine("v1") + f.X25519(testkit.TestX25519Recipient) + f.HMAC() + f.Payload("age") + file := f.Bytes() + f.Buf.Reset() + f.BeginArmor("AGE ENCRYPTED MESSAGE") + f.Body(file) + f.Base64Padding() + f.EndArmor("AGE ENCRYPTED MESSAGE") + f.ExpectArmorFailure() + f.Generate() +}