checker-email-keys/checker/collect_test.go

219 lines
6.6 KiB
Go

package checker
import (
"encoding/base64"
"encoding/hex"
"strings"
"testing"
"github.com/miekg/dns"
)
func TestOwnerHashHex(t *testing.T) {
// RFC 7929 worked example: the SHA-256 of "hugh" truncated to 28
// bytes, hex-encoded.
got := ownerHashHex("hugh")
if len(got) != 56 {
t.Fatalf("len = %d, want 56", len(got))
}
if got != strings.ToLower(got) {
t.Errorf("expected lowercase hex, got %q", got)
}
// Stable across calls.
if ownerHashHex("hugh") != got {
t.Error("ownerHashHex is not deterministic")
}
// Different inputs ⇒ different output.
if ownerHashHex("alice") == got {
t.Error("collisions across distinct inputs")
}
}
func TestExtractOwnerPrefix(t *testing.T) {
cases := []struct {
owner, prefix, want string
}{
{"abc123._openpgpkey.example.com.", "_openpgpkey", "abc123"},
{"ABC123._OPENPGPKEY.example.com", "_openpgpkey", "abc123"},
{"abc123._smimecert.example.com.", "_smimecert", "abc123"},
{"example.com.", "_openpgpkey", ""},
{"_openpgpkey.example.com.", "_openpgpkey", ""}, // no leading hash label
{"", "_openpgpkey", ""},
}
for _, c := range cases {
got := extractOwnerPrefix(c.owner, c.prefix, "")
if got != c.want {
t.Errorf("extractOwnerPrefix(%q,%q) = %q, want %q", c.owner, c.prefix, got, c.want)
}
}
}
func TestFirstNonEmpty(t *testing.T) {
if got := firstNonEmpty("", " ", "x", "y"); got != "x" {
t.Errorf("got %q, want x", got)
}
if got := firstNonEmpty("", "", ""); got != "" {
t.Errorf("got %q, want empty", got)
}
}
func TestKindForServiceType(t *testing.T) {
cases := map[string]string{
ServiceOpenPGP: KindOpenPGPKey,
ServiceSMimeCert: KindSMIMEA,
"abstract.Other": "",
"": "",
}
for in, want := range cases {
if got := kindForServiceType(in); got != want {
t.Errorf("kindForServiceType(%q) = %q, want %q", in, got, want)
}
}
}
func TestComputeOwner(t *testing.T) {
body := serviceBody{Username: "alice"}
exp, rec := computeOwner(body, OpenPGPKeyPrefix, "example.com")
wantPrefix := ownerHashHex("alice") + "._openpgpkey.example.com."
if exp != wantPrefix {
t.Errorf("expected = %q, want %q", exp, wantPrefix)
}
if rec != "" {
t.Errorf("recorded = %q, want empty", rec)
}
// With a record carrying its own owner.
body.OpenPGP = &dns.OPENPGPKEY{Hdr: dns.RR_Header{Name: "abc._openpgpkey.example.com."}}
_, rec = computeOwner(body, OpenPGPKeyPrefix, "example.com")
if rec != "abc._openpgpkey.example.com." {
t.Errorf("recorded = %q", rec)
}
// Empty username yields empty expected owner.
exp, _ = computeOwner(serviceBody{}, OpenPGPKeyPrefix, "example.com")
if exp != "" {
t.Errorf("expected = %q, want empty", exp)
}
}
func TestAnyOpenPGPMatches(t *testing.T) {
ref := &dns.OPENPGPKEY{PublicKey: "AAAA"}
rrs := []dns.RR{
&dns.OPENPGPKEY{PublicKey: "BBBB"},
&dns.OPENPGPKEY{PublicKey: " AAAA "}, // trims whitespace
}
if !anyOpenPGPMatches(rrs, ref) {
t.Error("expected match")
}
if anyOpenPGPMatches([]dns.RR{&dns.OPENPGPKEY{PublicKey: "ZZZZ"}}, ref) {
t.Error("unexpected match")
}
// Non-OPENPGPKEY RRs are skipped silently.
if anyOpenPGPMatches([]dns.RR{&dns.A{}}, ref) {
t.Error("non-OPENPGPKEY RR matched")
}
}
func TestAnySMIMEAMatches(t *testing.T) {
ref := &dns.SMIMEA{Usage: 3, Selector: 0, MatchingType: 0, Certificate: "DEADBEEF"}
rrs := []dns.RR{
&dns.SMIMEA{Usage: 3, Selector: 0, MatchingType: 0, Certificate: "deadbeef"},
}
if !anySMIMEAMatches(rrs, ref) {
t.Error("expected case-insensitive match")
}
rrs = []dns.RR{&dns.SMIMEA{Usage: 1, Selector: 0, MatchingType: 0, Certificate: "deadbeef"}}
if anySMIMEAMatches(rrs, ref) {
t.Error("usage mismatch should not match")
}
}
func TestAnalyzeOpenPGP_NoRecord(t *testing.T) {
got := analyzeOpenPGP(serviceBody{})
if got == nil || got.ParseError == "" {
t.Fatalf("expected ParseError, got %+v", got)
}
}
func TestAnalyzeOpenPGP_BadBase64(t *testing.T) {
body := serviceBody{OpenPGP: &dns.OPENPGPKEY{PublicKey: "!!! not base64 !!!"}}
got := analyzeOpenPGP(body)
if !strings.Contains(got.ParseError, "invalid base64") {
t.Errorf("ParseError = %q", got.ParseError)
}
}
func TestAnalyzeOpenPGP_OversizePayload(t *testing.T) {
// A base64 payload whose decoded size would exceed the cap.
raw := make([]byte, maxKeyMaterialBytes+1024)
body := serviceBody{OpenPGP: &dns.OPENPGPKEY{PublicKey: base64.StdEncoding.EncodeToString(raw)}}
got := analyzeOpenPGP(body)
if !strings.Contains(got.ParseError, "exceeds") {
t.Errorf("expected size-limit ParseError, got %q", got.ParseError)
}
// And we never tried to actually parse it as a keyring.
if got.EntityCount != 0 {
t.Errorf("EntityCount = %d, want 0", got.EntityCount)
}
}
func TestAnalyzeOpenPGP_GarbageBytes(t *testing.T) {
// Valid base64, but not a valid OpenPGP packet stream.
body := serviceBody{OpenPGP: &dns.OPENPGPKEY{PublicKey: base64.StdEncoding.EncodeToString([]byte("not a key"))}}
got := analyzeOpenPGP(body)
if got.ParseError == "" {
t.Error("expected ParseError for garbage payload")
}
if got.RawSize == 0 {
t.Error("RawSize should be set even on parse failure")
}
}
func TestAnalyzeSMIMEA_NoRecord(t *testing.T) {
got := analyzeSMIMEA(serviceBody{})
if got == nil || got.ParseError == "" {
t.Fatalf("expected ParseError, got %+v", got)
}
}
func TestAnalyzeSMIMEA_DigestOnly(t *testing.T) {
body := serviceBody{SMIMEA: &dns.SMIMEA{Usage: 3, Selector: 0, MatchingType: 1, Certificate: "abcd"}}
got := analyzeSMIMEA(body)
if got.ParseError != "" {
t.Errorf("digest-only should not error: %q", got.ParseError)
}
if got.Certificate != nil || got.PublicKey != nil {
t.Error("digest-only should not populate Certificate/PublicKey")
}
if got.HashHex != "abcd" {
t.Errorf("HashHex = %q", got.HashHex)
}
}
func TestAnalyzeSMIMEA_BadHex(t *testing.T) {
body := serviceBody{SMIMEA: &dns.SMIMEA{Usage: 3, Selector: 0, MatchingType: 0, Certificate: "ZZZZ"}}
got := analyzeSMIMEA(body)
if got.ParseError == "" {
t.Error("expected ParseError for invalid hex")
}
}
func TestAnalyzeSMIMEA_OversizePayload(t *testing.T) {
huge := strings.Repeat("ab", maxKeyMaterialBytes+1024)
body := serviceBody{SMIMEA: &dns.SMIMEA{Usage: 3, Selector: 0, MatchingType: 0, Certificate: huge}}
got := analyzeSMIMEA(body)
if !strings.Contains(got.ParseError, "exceeds") {
t.Errorf("expected size-limit ParseError, got %q", got.ParseError)
}
}
func TestAnalyzeSMIMEA_NotACertificate(t *testing.T) {
body := serviceBody{SMIMEA: &dns.SMIMEA{
Usage: 3, Selector: 0, MatchingType: 0,
Certificate: hex.EncodeToString([]byte("not a DER cert")),
}}
got := analyzeSMIMEA(body)
if got.ParseError == "" {
t.Error("expected ParseError for non-cert bytes")
}
}