diff --git a/internal/testkit/testkit.go b/internal/testkit/testkit.go index fd37579..b2410ae 100644 --- a/internal/testkit/testkit.go +++ b/internal/testkit/testkit.go @@ -30,6 +30,16 @@ var _, TestX25519Identity, _ = bech32.Decode( var TestX25519Recipient, _ = curve25519.X25519(TestX25519Identity, curve25519.Basepoint) +func NotCanonicalBase64(s string) string { + // Assuming there are spare zero bits at the end of the encoded bitstring, + // the character immediately after in the alphabet compared to the last one + // in the encoding will only flip the last bit to one, making the string a + // non-canonical encoding of the same value. + alphabet := "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" + idx := strings.IndexByte(alphabet, s[len(s)-1]) + return s[:len(s)-1] + string(alphabet[idx+1]) +} + type TestFile struct { Buf bytes.Buffer Rand func(n int) []byte @@ -64,6 +74,14 @@ func (f *TestFile) TextLine(s string) { f.Buf.WriteString("\n") } +func (f *TestFile) UnreadLine() string { + buf := bytes.TrimSuffix(f.Buf.Bytes(), []byte("\n")) + idx := bytes.LastIndex(buf[:len(buf)-1], []byte("\n")) + 1 + f.Buf.Reset() + f.Buf.Write(buf[:idx]) + return string(buf[idx:]) +} + func (f *TestFile) VersionLine(v string) { f.TextLine("age-encryption.org/" + v) } diff --git a/testdata/testkit/crlf b/testdata/testkit/crlf new file mode 100644 index 0000000..69cafae Binary files /dev/null and b/testdata/testkit/crlf differ diff --git a/testdata/testkit/hmac_bad b/testdata/testkit/hmac_bad index dd37b49..59168ce 100644 Binary files a/testdata/testkit/hmac_bad and b/testdata/testkit/hmac_bad differ diff --git a/testdata/testkit/hmac_extra_space b/testdata/testkit/hmac_extra_space new file mode 100644 index 0000000..f3bfd1e Binary files /dev/null and b/testdata/testkit/hmac_extra_space differ diff --git a/testdata/testkit/hmac_garbage b/testdata/testkit/hmac_garbage new file mode 100644 index 0000000..29e54ab Binary files /dev/null and b/testdata/testkit/hmac_garbage differ diff --git a/testdata/testkit/hmac_missing b/testdata/testkit/hmac_missing new file mode 100644 index 0000000..6ecea2a Binary files /dev/null and b/testdata/testkit/hmac_missing differ diff --git a/testdata/testkit/hmac_no_space b/testdata/testkit/hmac_no_space new file mode 100644 index 0000000..9f1d80d Binary files /dev/null and b/testdata/testkit/hmac_no_space differ diff --git a/testdata/testkit/hmac_not_canonical b/testdata/testkit/hmac_not_canonical new file mode 100644 index 0000000..fd18cda Binary files /dev/null and b/testdata/testkit/hmac_not_canonical differ diff --git a/testdata/testkit/hmac_trailing_space b/testdata/testkit/hmac_trailing_space new file mode 100644 index 0000000..2e216bd Binary files /dev/null and b/testdata/testkit/hmac_trailing_space differ diff --git a/testdata/testkit/hmac_truncated b/testdata/testkit/hmac_truncated new file mode 100644 index 0000000..e7f7196 Binary files /dev/null and b/testdata/testkit/hmac_truncated differ diff --git a/testdata/testkit/x25519_extra_argument b/testdata/testkit/x25519_extra_argument new file mode 100644 index 0000000..198389b Binary files /dev/null and b/testdata/testkit/x25519_extra_argument differ diff --git a/testdata/testkit/x25519_low_order b/testdata/testkit/x25519_low_order index 84e9fa3..f528a0d 100644 Binary files a/testdata/testkit/x25519_low_order and b/testdata/testkit/x25519_low_order differ diff --git a/testdata/testkit/x25519_not_canonical_body b/testdata/testkit/x25519_not_canonical_body new file mode 100644 index 0000000..11138db Binary files /dev/null and b/testdata/testkit/x25519_not_canonical_body differ diff --git a/testdata/testkit/x25519_not_canonical_share b/testdata/testkit/x25519_not_canonical_share new file mode 100644 index 0000000..0b2a08d Binary files /dev/null and b/testdata/testkit/x25519_not_canonical_share differ diff --git a/tests/crlf.go b/tests/crlf.go new file mode 100644 index 0000000..721626b --- /dev/null +++ b/tests/crlf.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 + +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()) + f.Buf.WriteString("\r\n") + f.Payload("age") + f.ExpectHeaderFailure() + f.Comment("lines in the header end with CRLF instead of LF") + f.Generate() +} diff --git a/tests/hmac_bad.go b/tests/hmac_bad.go index 15c2854..a8556f0 100644 --- a/tests/hmac_bad.go +++ b/tests/hmac_bad.go @@ -12,7 +12,7 @@ func main() { f := testkit.NewTestFile() f.VersionLine("v1") f.X25519(testkit.TestX25519Identity) - f.FileKey(f.Rand(16)) + f.FileKey(make([]byte, 16)) f.HMAC() f.FileKey(testkit.TestFileKey) f.Payload("age") diff --git a/tests/hmac_extra_space.go b/tests/hmac_extra_space.go new file mode 100644 index 0000000..6d88871 --- /dev/null +++ b/tests/hmac_extra_space.go @@ -0,0 +1,24 @@ +// 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.TestX25519Identity) + f.HMAC() + f.TextLine(strings.Replace(f.UnreadLine(), "--- ", "--- ", -1)) + f.Payload("age") + f.ExpectHeaderFailure() + f.Generate() +} diff --git a/tests/hmac_garbage.go b/tests/hmac_garbage.go new file mode 100644 index 0000000..04e4df5 --- /dev/null +++ b/tests/hmac_garbage.go @@ -0,0 +1,20 @@ +// 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.TestX25519Identity) + f.HMAC() + f.TextLine(f.UnreadLine() + "AAA") + f.Payload("age") + f.ExpectHeaderFailure() + f.Generate() +} diff --git a/tests/hmac_missing.go b/tests/hmac_missing.go new file mode 100644 index 0000000..b094de8 --- /dev/null +++ b/tests/hmac_missing.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("v1") + f.X25519(testkit.TestX25519Identity) + f.HMACLine(nil) + f.Payload("age") + f.ExpectHeaderFailure() + f.Generate() +} diff --git a/tests/hmac_no_space.go b/tests/hmac_no_space.go new file mode 100644 index 0000000..d15c9df --- /dev/null +++ b/tests/hmac_no_space.go @@ -0,0 +1,24 @@ +// 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.TestX25519Identity) + f.HMAC() + f.TextLine(strings.Replace(f.UnreadLine(), "--- ", "---", -1)) + f.Payload("age") + f.ExpectHeaderFailure() + f.Generate() +} diff --git a/tests/hmac_not_canonical.go b/tests/hmac_not_canonical.go new file mode 100644 index 0000000..0f6fc06 --- /dev/null +++ b/tests/hmac_not_canonical.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.TestX25519Identity) + f.HMAC() + f.TextLine(testkit.NotCanonicalBase64(f.UnreadLine())) + f.Payload("age") + f.ExpectHeaderFailure() + f.Comment("the base64 encoding of the HMAC is not canonical") + f.Generate() +} diff --git a/tests/hmac_trailing_space.go b/tests/hmac_trailing_space.go new file mode 100644 index 0000000..02395d7 --- /dev/null +++ b/tests/hmac_trailing_space.go @@ -0,0 +1,20 @@ +// 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.TestX25519Identity) + f.HMAC() + f.TextLine(f.UnreadLine() + " ") + f.Payload("age") + f.ExpectHeaderFailure() + f.Generate() +} diff --git a/tests/hmac_truncated.go b/tests/hmac_truncated.go new file mode 100644 index 0000000..9f3a009 --- /dev/null +++ b/tests/hmac_truncated.go @@ -0,0 +1,20 @@ +// 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.TestX25519Identity) + f.HMAC() + f.TextLine(f.UnreadLine()[:len("--- 1234")]) + f.Payload("age") + f.ExpectHeaderFailure() + f.Generate() +} diff --git a/tests/x25519_extra_argument.go b/tests/x25519_extra_argument.go new file mode 100644 index 0000000..2974b0e --- /dev/null +++ b/tests/x25519_extra_argument.go @@ -0,0 +1,23 @@ +// 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) + body, args := f.UnreadLine(), f.UnreadLine() + f.TextLine(args + " 1234") + f.TextLine(body) + f.HMAC() + f.Payload("age") + f.ExpectHeaderFailure() + f.Comment("the base64 encoding of the share is not canonical") + f.Generate() +} diff --git a/tests/x25519_low_order.go b/tests/x25519_low_order.go index fbc374a..2e465bb 100644 --- a/tests/x25519_low_order.go +++ b/tests/x25519_low_order.go @@ -33,6 +33,7 @@ func main() { f.HMAC() f.Payload("age") f.ExpectHeaderFailure() - f.Comment("the X25519 share is a low-order point, so the shared secret is the disallowed all-zero value") + f.Comment("the X25519 share is a low-order point, so the shared secret" + + "is the disallowed all-zero value") f.Generate() } diff --git a/tests/x25519_not_canonical_body.go b/tests/x25519_not_canonical_body.go new file mode 100644 index 0000000..ee49f96 --- /dev/null +++ b/tests/x25519_not_canonical_body.go @@ -0,0 +1,23 @@ +// 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) + body, args := f.UnreadLine(), f.UnreadLine() + f.TextLine(args) + f.TextLine(testkit.NotCanonicalBase64(body)) + f.HMAC() + f.Payload("age") + f.ExpectHeaderFailure() + f.Comment("the base64 encoding of the share is not canonical") + f.Generate() +} diff --git a/tests/x25519_not_canonical_share.go b/tests/x25519_not_canonical_share.go new file mode 100644 index 0000000..d9696ca --- /dev/null +++ b/tests/x25519_not_canonical_share.go @@ -0,0 +1,23 @@ +// 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) + body, args := f.UnreadLine(), f.UnreadLine() + f.TextLine(testkit.NotCanonicalBase64(args)) + f.TextLine(body) + f.HMAC() + f.Payload("age") + f.ExpectHeaderFailure() + f.Comment("the base64 encoding of the share is not canonical") + f.Generate() +}