|
|
package authorkey_test
|
|
|
|
|
|
import (
|
|
|
"bytes"
|
|
|
"fmt"
|
|
|
"strings"
|
|
|
"testing"
|
|
|
|
|
|
"g.activething.com/go/DateKeys/authorkey"
|
|
|
"g.activething.com/go/DateKeys/codec/bech32"
|
|
|
"g.activething.com/go/DateKeys/internal/ed25519strict"
|
|
|
)
|
|
|
|
|
|
func TestStrings(t *testing.T) {
|
|
|
seed := bytes.Repeat([]byte{7}, 32)
|
|
|
k, err := authorkey.NewFromSeed(seed)
|
|
|
if err != nil {
|
|
|
t.Fatal(err)
|
|
|
}
|
|
|
pub, err := authorkey.PublicString(k.Public())
|
|
|
if err != nil {
|
|
|
t.Fatal(err)
|
|
|
}
|
|
|
secret := k.Secret()
|
|
|
if len(pub) != authorkey.PublicLength || !strings.HasPrefix(pub, "dkauthor1") {
|
|
|
t.Errorf("public %q", pub)
|
|
|
}
|
|
|
if len(secret) != authorkey.SecretLength || !strings.HasPrefix(secret, "DKAUTHOR-SECRET-KEY-1") {
|
|
|
t.Errorf("secret %q", secret)
|
|
|
}
|
|
|
back, err := authorkey.ParseSecret(secret)
|
|
|
if err != nil || !bytes.Equal(back.Public(), k.Public()) {
|
|
|
t.Fatalf("ParseSecret: %v", err)
|
|
|
}
|
|
|
a, err := authorkey.ParsePublic(pub)
|
|
|
if err != nil || !bytes.Equal(a, k.Public()) {
|
|
|
t.Fatalf("ParsePublic: %v", err)
|
|
|
}
|
|
|
msg := []byte("datekeys:dkc3:author-signature:v1")
|
|
|
if !ed25519strict.Verify(a, msg, k.Sign(msg)) {
|
|
|
t.Error("the signature does not verify")
|
|
|
}
|
|
|
// Printing a key, or an options struct that holds one, never shows the
|
|
|
// secret.
|
|
|
for _, s := range []string{fmt.Sprint(k), fmt.Sprintf("%v %+v %#v %s", k, k, k, k), fmt.Sprintf("%+v", struct{ K *authorkey.Key }{k})} {
|
|
|
if strings.Contains(s, secret[len("DKAUTHOR-SECRET-KEY-1"):]) {
|
|
|
t.Fatalf("the secret key is printed: %s", s)
|
|
|
}
|
|
|
}
|
|
|
k.Clear()
|
|
|
if !bytes.Equal(k.Public(), make([]byte, 32)) {
|
|
|
t.Error("Clear leaves the key")
|
|
|
}
|
|
|
}
|
|
|
|
|
|
func TestParseRejects(t *testing.T) {
|
|
|
k, _ := authorkey.Generate()
|
|
|
pub, _ := authorkey.PublicString(k.Public())
|
|
|
secret := k.Secret()
|
|
|
short, _ := bech32.Encode("dkauthor", make([]byte, 31))
|
|
|
other, _ := bech32.Encode("dkauthoz", k.Public())
|
|
|
last := "q"
|
|
|
if pub[66] == 'q' {
|
|
|
last = "p"
|
|
|
}
|
|
|
identity := make([]byte, 32)
|
|
|
identity[0] = 1
|
|
|
small, _ := authorkey.PublicString(identity)
|
|
|
// y = 2 is canonical, and (y² − 1)/(d·y² + 1) is not a square: no point.
|
|
|
two := make([]byte, 32)
|
|
|
two[0] = 2
|
|
|
offCurve, _ := authorkey.PublicString(two)
|
|
|
for _, c := range []struct{ s, want string }{
|
|
|
{strings.ToUpper(pub), "lower case"},
|
|
|
{pub[:66] + last, "checksum"},
|
|
|
{short, "characters"},
|
|
|
{other, "is not a public key"},
|
|
|
{small, "small order"},
|
|
|
{offCurve, "not a point of the curve"},
|
|
|
} {
|
|
|
if _, err := authorkey.ParsePublic(c.s); err == nil || !strings.Contains(err.Error(), c.want) {
|
|
|
t.Errorf("ParsePublic(%q) = %v, want %q", c.s, err, c.want)
|
|
|
}
|
|
|
}
|
|
|
for _, c := range []struct{ s, want string }{
|
|
|
{strings.ToLower(secret), "upper case"},
|
|
|
{secret[:78], "characters"},
|
|
|
{"DKAUTHOR-SECRET-KEY-1" + strings.Repeat("Q", 58), "checksum"},
|
|
|
} {
|
|
|
if _, err := authorkey.ParseSecret(c.s); err == nil || !strings.Contains(err.Error(), c.want) {
|
|
|
t.Errorf("ParseSecret(%q) = %v, want %q", c.s, err, c.want)
|
|
|
}
|
|
|
}
|
|
|
if _, err := authorkey.PublicString(make([]byte, 31)); err == nil {
|
|
|
t.Error("PublicString takes 31 bytes")
|
|
|
}
|
|
|
if _, err := authorkey.NewFromSeed(make([]byte, 31)); err == nil {
|
|
|
t.Error("NewFromSeed takes 31 bytes")
|
|
|
}
|
|
|
}
|
|
|
|
|
|
func TestFiles(t *testing.T) {
|
|
|
k, _ := authorkey.Generate()
|
|
|
var enc bytes.Buffer
|
|
|
if err := authorkey.Encrypt(&enc, k, "contraseña larga"); err != nil {
|
|
|
t.Fatal(err)
|
|
|
}
|
|
|
if !bytes.HasPrefix(enc.Bytes(), []byte("age-encryption.org/v1\n-> scrypt ")) || !bytes.Contains(enc.Bytes(), []byte(" 16\n")) {
|
|
|
t.Errorf("not age scrypt with a work factor of 16: %q", enc.Bytes()[:60])
|
|
|
}
|
|
|
got, err := authorkey.Read(bytes.NewReader(enc.Bytes()), "contraseña larga")
|
|
|
if err != nil || !bytes.Equal(got.Public(), k.Public()) {
|
|
|
t.Fatalf("Read: %v", err)
|
|
|
}
|
|
|
if _, err := authorkey.Read(bytes.NewReader(enc.Bytes()), "otra"); err == nil {
|
|
|
t.Error("another passphrase opens the file")
|
|
|
}
|
|
|
if _, err := authorkey.Read(bytes.NewReader(enc.Bytes()), ""); err == nil || !strings.Contains(err.Error(), "needs its passphrase") {
|
|
|
t.Errorf("no passphrase: %v", err)
|
|
|
}
|
|
|
if err := authorkey.Encrypt(&enc, k, ""); err == nil {
|
|
|
t.Error("Encrypt takes an empty passphrase")
|
|
|
}
|
|
|
plain := authorkey.Marshal(k)
|
|
|
if got, err := authorkey.Read(bytes.NewReader(plain), ""); err != nil || !bytes.Equal(got.Public(), k.Public()) {
|
|
|
t.Fatalf("Read of a plain file: %v", err)
|
|
|
}
|
|
|
for _, c := range []struct{ file, want string }{
|
|
|
{"# nothing\n\n", "no secret key"},
|
|
|
{string(plain) + k.String() + "\n", "one secret key"},
|
|
|
{"age1notakey\n", "characters"},
|
|
|
{strings.Repeat("#", 64<<10+1), "more than"},
|
|
|
} {
|
|
|
if _, err := authorkey.Read(strings.NewReader(c.file), ""); err == nil || !strings.Contains(err.Error(), c.want) {
|
|
|
t.Errorf("Read(%.20q) = %v, want %q", c.file, err, c.want)
|
|
|
}
|
|
|
}
|
|
|
}
|