219 lines
6.6 KiB
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")
|
|
}
|
|
}
|