package main import ( "os" "path/filepath" "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") if _, stderr, err := cli(t, genesis, "encrypt", "-at", unlock.Format(time.RFC3339), "-in", in, "-out", dkc, "-sign", keyFile, "-sign-pass-file", pass); err != nil { t.Fatalf("%v\n%s", err, 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) } for i, tc := range []struct { file, expect, want string fails bool }{ {dkc, "", "Firmado con la clave " + pub, false}, {dkc, pub, "Firmado con la clave que guardaste como -expect-author.", false}, {dkc2, pub, "Firmado con la clave que guardaste como -expect-author.", false}, {dkc, otherPub, "Firmado con la clave " + pub, true}, } { args := []string{"decrypt", "-in", tc.file, "-out", filepath.Join(dir, "out"+string(rune('a'+i))), "-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(stdout, tc.want) { t.Errorf("case %d: %v\n%s", i, err, stdout) } if tc.fails && (err == nil || !strings.Contains(err.Error(), "not signed with the expected key")) { t.Errorf("case %d: %v", i, err) } } // An unsigned capsule does not meet -expect-author. 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) } if _, _, err := cli(t, later, "decrypt", "-in", plain, "-out", filepath.Join(dir, "outz"), "-relay", relay(t), "-expect-author", pub); err == nil { t.Error("an unsigned capsule met -expect-author") } 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) } }