You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
166 lines
7.5 KiB
166 lines
7.5 KiB
package main
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"regexp"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"g.activething.com/go/DateKeys/profile"
|
|
)
|
|
|
|
// Spec v0.11, §29.9, §29.12: author keygen, encrypt -sign and decrypt
|
|
// -expect-author, with the passphrase in a file and in the standard input.
|
|
func TestAuthorSignRoundTrip(t *testing.T) {
|
|
dir := t.TempDir()
|
|
in := filepath.Join(dir, "carta.txt")
|
|
os.WriteFile(in, []byte("firmada"), 0o600)
|
|
pass := filepath.Join(dir, "pass.txt")
|
|
os.WriteFile(pass, []byte("una contraseña larga\r\n"), 0o600)
|
|
p := profile.Quicknet()
|
|
unlock := time.Unix(p.GenesisTime+999*3, 0).UTC() // round 1000
|
|
genesis := time.Unix(p.GenesisTime, 0)
|
|
|
|
keyFile := filepath.Join(dir, "autor.key")
|
|
pub, stderr, err := cli(t, genesis, "author", "keygen", "-out", keyFile, "-pass-file", pass)
|
|
if err != nil || !strings.HasPrefix(pub, "dkauthor1") || !strings.Contains(stderr, "keep it") {
|
|
t.Fatalf("keygen: %q %v %s", pub, err, stderr)
|
|
}
|
|
pub = strings.TrimSpace(pub)
|
|
if b, _ := os.ReadFile(keyFile); !strings.HasPrefix(string(b), "age-encryption.org/v1") {
|
|
t.Error("the key file is not encrypted")
|
|
}
|
|
if _, _, err := cli(t, genesis, "author", "keygen", "-out", keyFile, "-pass-file", pass); err == nil {
|
|
t.Error("overwrote a key")
|
|
}
|
|
if _, _, err := cli(t, genesis, "author", "keygen", "-out", filepath.Join(dir, "x.key")); err == nil {
|
|
t.Error("wrote a key without a passphrase and without -plain")
|
|
}
|
|
if got, _, err := cli(t, genesis, "author", "public", "-key", keyFile, "-pass-file", pass); err != nil || strings.TrimSpace(got) != pub {
|
|
t.Errorf("public: %q %v", got, err)
|
|
}
|
|
if _, _, err := cli(t, genesis, "author", "public", "-key", keyFile); err == nil || !strings.Contains(err.Error(), "-pass-file") {
|
|
t.Errorf("public of an encrypted key without its passphrase: %v", err)
|
|
}
|
|
other := filepath.Join(dir, "otra.key")
|
|
otherPub, _, err := cli(t, genesis, "author", "keygen", "-out", other, "-plain")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
otherPub = strings.TrimSpace(otherPub)
|
|
|
|
dkc := filepath.Join(dir, "c.dkc")
|
|
_, stderr, err = cli(t, genesis, "encrypt", "-at", unlock.Format(time.RFC3339), "-in", in, "-out", dkc, "-sign", keyFile, "-sign-pass-file", pass)
|
|
if err != nil {
|
|
t.Fatalf("%v\n%s", err, stderr)
|
|
}
|
|
// Spec §62.1 rule 20: the key and the code of AUTHOR_MESSAGE, before the
|
|
// signature.
|
|
if code := regexp.MustCompile(`AUTHOR_MESSAGE code ([0-9a-f]{4}-[0-9a-f]{4})\n`).FindStringSubmatch(stderr); code == nil || !strings.Contains(stderr, "Signing with the author key "+pub+"\n") {
|
|
t.Errorf("encrypt -sign does not show the key and the code:\n%s", stderr)
|
|
}
|
|
// The passphrase from the standard input.
|
|
stdin = strings.NewReader("una contraseña larga\n")
|
|
t.Cleanup(func() { stdin = os.Stdin })
|
|
dkc2 := filepath.Join(dir, "c2.dkc")
|
|
if _, stderr, err := cli(t, genesis, "encrypt", "-at", unlock.Format(time.RFC3339), "-in", in, "-out", dkc2, "-sign", keyFile, "-sign-pass-file", "-", "-large-area"); err != nil {
|
|
t.Fatalf("%v\n%s", err, stderr)
|
|
}
|
|
|
|
// The expected key is not a saved one: the line is F4, with the whole key.
|
|
// A capsule that is not signed with it writes nothing.
|
|
for i, tc := range []struct {
|
|
file, expect, want string
|
|
fails bool
|
|
}{
|
|
{dkc, "", "Firmado con la clave " + pub, false},
|
|
{dkc, pub, "Firmado con la clave " + pub, false},
|
|
{dkc2, pub, "Firmado con la clave " + pub, false},
|
|
{dkc, otherPub, "Firmado con la clave " + pub, true},
|
|
} {
|
|
out := filepath.Join(dir, "out"+string(rune('a'+i)))
|
|
args := []string{"decrypt", "-in", tc.file, "-out", out, "-relay", relay(t)}
|
|
if tc.expect != "" {
|
|
args = append(args, "-expect-author", tc.expect)
|
|
}
|
|
stdout, _, err := cli(t, later, args...)
|
|
if (err != nil) != tc.fails || !strings.Contains(joined(stdout), tc.want) {
|
|
t.Errorf("case %d: %v\n%s", i, err, stdout)
|
|
}
|
|
if strings.Contains(stdout, "guardaste") {
|
|
t.Errorf("case %d: the expected key shown as a saved one:\n%s", i, stdout)
|
|
}
|
|
if tc.fails {
|
|
if err == nil || !strings.Contains(err.Error(), "not signed with the expected key") || !strings.Contains(err.Error(), "nothing was written") {
|
|
t.Errorf("case %d: %v", i, err)
|
|
}
|
|
if _, err := os.Stat(out); !os.IsNotExist(err) {
|
|
t.Errorf("case %d: %s was created: %v", i, out, err)
|
|
}
|
|
}
|
|
}
|
|
// An unsigned capsule does not meet -expect-author, and writes nothing.
|
|
plain := filepath.Join(dir, "plain.dkc")
|
|
if _, _, err := cli(t, genesis, "encrypt", "-at", unlock.Format(time.RFC3339), "-in", in, "-out", plain); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
stdout, _, err := cli(t, later, "decrypt", "-in", plain, "-out", filepath.Join(dir, "outz"), "-relay", relay(t), "-expect-author", pub)
|
|
if err == nil || !strings.Contains(stdout, "Sin firma de autor.") {
|
|
t.Errorf("an unsigned capsule met -expect-author: %v\n%s", err, stdout)
|
|
}
|
|
if _, err := os.Stat(filepath.Join(dir, "outz")); !os.IsNotExist(err) {
|
|
t.Errorf("the files of an unsigned capsule were written: %v", err)
|
|
}
|
|
if _, _, err := cli(t, later, "decrypt", "-in", plain, "-out", filepath.Join(dir, "outy"), "-relay", relay(t), "-expect-author", "dkauthor1x"); err == nil {
|
|
t.Error("a malformed -expect-author was accepted")
|
|
}
|
|
}
|
|
|
|
// Spec v0.11 §24.1: -note puts the public note in clear, inspect shows it
|
|
// before the date with its warning, and decrypt shows it as text of the
|
|
// creator after the date.
|
|
func TestPublicNoteCLI(t *testing.T) {
|
|
dir := t.TempDir()
|
|
in := filepath.Join(dir, "carta.txt")
|
|
os.WriteFile(in, []byte("con nota"), 0o600)
|
|
p := profile.Quicknet()
|
|
unlock := time.Unix(p.GenesisTime+999*3, 0).UTC() // round 1000
|
|
genesis := time.Unix(p.GenesisTime, 0)
|
|
dkc := filepath.Join(dir, "n.dkc")
|
|
if _, stderr, err := cli(t, genesis, "encrypt", "-at", unlock.Format(time.RFC3339), "-in", in, "-out", dkc, "-note", "Cartas del viaje a Lisboa"); err != nil {
|
|
t.Fatalf("%v\n%s", err, stderr)
|
|
}
|
|
if _, _, err := cli(t, genesis, "encrypt", "-at", unlock.Format(time.RFC3339), "-in", in, "-out", filepath.Join(dir, "bad.dkc"), "-note", "dos\nlíneas"); err == nil {
|
|
t.Error("a note of two lines was written")
|
|
}
|
|
out, _, err := cli(t, genesis, "inspect", "-in", dkc)
|
|
if err != nil || !strings.Contains(out, "┌ "+noteTitle+"\n│ Cartas del viaje a Lisboa\n└\n Nadie puede comprobar antes de la fecha quién creó la cápsula ni si va firmada.") {
|
|
t.Errorf("inspect: %v\n%s", err, out)
|
|
}
|
|
js, _, err := cli(t, genesis, "inspect", "-in", dkc, "-json")
|
|
if err != nil || !strings.Contains(js, `"public_note": "Cartas del viaje a Lisboa"`) {
|
|
t.Errorf("inspect -json: %v\n%s", err, js)
|
|
}
|
|
shown, _, err := cli(t, later, "decrypt", "-in", dkc, "-out", filepath.Join(dir, "out"), "-relay", relay(t))
|
|
if err != nil || !strings.Contains(shown, "┌ "+noteTitle+"\n│ Cartas del viaje a Lisboa\n└\n") {
|
|
t.Errorf("decrypt: %v\n%s", err, shown)
|
|
}
|
|
}
|
|
|
|
// Spec v0.11 §29.7: under a valid seal, an mtime later than the seal is shown
|
|
// as an inconsistency. format3_sealed has a file dated 2026, sealed in 2023.
|
|
func TestMTimeAfterSeal(t *testing.T) {
|
|
out := filepath.Join(t.TempDir(), "out")
|
|
shown, _, err := cli(t, later, "decrypt", "-in", filepath.Join(fixtures, "format3_sealed.dkc"), "-out", out, "-relay", relay(t))
|
|
if err != nil || !strings.Contains(joined(shown), "aviso: la fecha de modificación de un fichero es posterior al sello (2023-08-23T15:09:27Z)") {
|
|
t.Errorf("%v\n%s", err, shown)
|
|
}
|
|
clean := filepath.Join(t.TempDir(), "out")
|
|
shown, _, err = cli(t, later, "decrypt", "-in", filepath.Join(fixtures, "format3_single.dkc"), "-out", clean, "-relay", relay(t))
|
|
if err != nil || strings.Contains(shown, "no es coherente") {
|
|
t.Errorf("an unsealed capsule: %v\n%s", err, shown)
|
|
}
|
|
}
|