diff --git a/depaware.txt b/depaware.txt index 13786da..8e711f5 100644 --- a/depaware.txt +++ b/depaware.txt @@ -4,14 +4,16 @@ github.com/redsift/dkim dependencies: (generated by github.com/tailscale/depawar golang.org/x/crypto/cryptobyte/asn1 from crypto/ecdsa+ golang.org/x/net/dns/dnsmessage from net D golang.org/x/net/route from net - bufio from crypto/rand+ + bufio from github.com/redsift/dkim+ bytes from bufio+ + cmp from encoding/json+ context from net - crypto from crypto/ecdsa+ - crypto/aes from crypto/ecdsa+ + crypto from crypto/ecdh+ + crypto/aes from crypto/x509 crypto/cipher from crypto/aes+ crypto/des from crypto/x509 crypto/dsa from crypto/x509 + crypto/ecdh from crypto/ecdsa+ crypto/ecdsa from crypto/x509 crypto/ed25519 from crypto/x509+ crypto/elliptic from crypto/ecdsa+ @@ -20,44 +22,50 @@ github.com/redsift/dkim dependencies: (generated by github.com/tailscale/depawar crypto/rsa from crypto/x509+ crypto/sha1 from crypto/x509 crypto/sha256 from crypto/x509 + crypto/sha3 from crypto/internal/fips140hash crypto/sha512 from crypto/ecdsa+ - crypto/subtle from crypto/aes+ + crypto/subtle from crypto/cipher+ crypto/x509 from github.com/redsift/dkim crypto/x509/pkix from crypto/x509 - embed from crypto/elliptic encoding from encoding/json encoding/asn1 from crypto/x509+ encoding/base64 from encoding/json+ - encoding/binary from crypto/aes+ + encoding/binary from encoding/base64 encoding/hex from crypto/x509+ encoding/json from github.com/redsift/dkim encoding/pem from crypto/x509 errors from bufio+ - fmt from crypto/x509+ + fmt from crypto/rsa+ hash from crypto+ io from bufio+ - io/fs from crypto/rand+ - math from crypto/rsa+ + io/fs from crypto/x509+ + iter from bytes+ + maps from crypto/x509+ + math from crypto/internal/fips140/aes/gcm+ math/big from crypto/dsa+ - math/bits from crypto/ed25519/internal/edwards25519/field+ + math/bits from bytes+ math/rand from math/big + math/rand/v2 from crypto/ecdsa+ net from crypto/x509+ - net/netip from net + net/netip from crypto/x509+ net/textproto from github.com/redsift/dkim net/url from crypto/x509 - os from crypto/rand+ - path from io/fs + os from crypto/internal/sysrand+ + path from io/fs+ L path/filepath from crypto/x509 reflect from crypto/x509+ regexp from github.com/redsift/dkim regexp/syntax from regexp - sort from encoding/asn1+ + slices from encoding/asn1+ + sort from regexp/syntax strconv from crypto+ strings from bufio+ sync from context+ sync/atomic from context+ - syscall from crypto/rand+ + syscall from crypto/internal/sysrand+ time from context+ unicode from bytes+ unicode/utf16 from crypto/x509+ unicode/utf8 from bufio+ + unique from net/netip + weak from unique diff --git a/dkim.go b/dkim.go index ba5b209..3247726 100644 --- a/dkim.go +++ b/dkim.go @@ -10,6 +10,7 @@ import ( "encoding/base64" "encoding/json" "errors" + "fmt" "hash" "io" "net" @@ -19,6 +20,8 @@ import ( "time" ) +const MinRSAKeyLen = 1024 + // Result holds all details about result of DKIM signature verification type Result struct { Order int `json:"order"` @@ -173,10 +176,20 @@ var ( ErrTestingMode = errors.New("domain is testing DKIM") ErrKeyRevoked = errors.New("key revoked") errUnsupportedCanonicalization = errors.New("bad canonicalization") + ErrEmptyRecord = errors.New("empty record") + ErrUnsupportedVersion = errors.New("unsupported version") + ErrUnsupportedAlgorithm = errors.New("unsupported algorithm") + ErrDecodeBase64 = errors.New("decode base64") + ErrParsePublicKey = errors.New("parse public key") + ErrUnsupportedServices = errors.New("unsupported services") + ErrMissedPTag = errors.New("missing p-tag") + ErrWeakRSAKey = errors.New("weak RSA key") + ErrInvalidED25519Key = errors.New("invalid ed25519 key") ) const ( expEmptyKey = "empty key" + expEmptyRecord = "empty record" expUnsupportedVersion = "unsupported version" expUnsupportedAlgorithm = "unsupported algorithm" expUnsupportedServices = "no supported services listed" @@ -659,9 +672,9 @@ func _DNSTxtPublicKeyQuery(s *Signature) (*PublicKey, error) { return nil, ErrKeyUnavailable } - key, err := ParsePublicKey(strings.Join(records, "")) - if err != nil { - return nil, err + key, errs := ParsePublicKey(strings.Join(records, "")) + if len(errs) > 0 { + return nil, errors.Join(errs...) } return key, nil @@ -683,43 +696,35 @@ func mapMatches(re *regexp.Regexp, s string, f func(g []string) string) []string return a } -// ParsePublicKey parses textual representation of the key -// See https://tools.ietf.org/html/rfc6376#section-3.6.1 for details -func ParsePublicKey(s string) (*PublicKey, error) { - unacceptableKey := func(t, v string, s string) (*PublicKey, error) { - return nil, &VerificationError{ - Source: KeyError, - Tag: t, - Value: v, - Err: ErrUnacceptableKey, - Explanation: s, - } - } - +// ParsePublicKey parses a textual representation of a DKIM public key record +// according to RFC 6376 section 3.6.1. See https://tools.ietf.org/html/rfc6376#section-3.6.1 +// for the details. It returns a PublicKey struct and a slice +// of errors representing all validation errors encountered during parsing. +// +// The PublicKey is returned even if parsing encountered errors. Errors returned +// are encapsulated within the VerificationError struct, providing detailed +// information about the specific issue, including the affected tag and its value. +// +// The returned PublicKey may not be fully valid or usable if critical errors exist. +// Callers must examine the slice of VerificationError to determine if the key +// is usable or has been revoked. Common verification errors include: +// +// - ErrEmptyRecord: The input record is empty. +// - ErrUnsupportedVersion: The DKIM version is not supported. +// - ErrUnsupportedAlgorithm: Unsupported hash or key algorithm specified. +// - ErrDecodeBase64: Invalid base64 data in public key record. +// - ErrWeakRSAKey: RSA key length is below security threshold. +// - ErrInvalidED25519Key: Provided ed25519 key data is invalid. +// - ErrMissedPTag: Missing required "p=" public key data tag. +// - ErrUnsupportedServices: Unsupported services listed. +func ParsePublicKey(s string) (*PublicKey, []error) { + s = strings.TrimSpace(s) if s == "" { - return unacceptableKey("", "", expEmptyKey) - } - const ( - fData uint64 = 1 << iota - ) - required := fData - missedTags := func() string { - symbols := []string{"v", "a", "b", "bh", "d", "h", "s"} - var w bytes.Buffer - w.WriteString("no required tags found (") - for f, i, d := fData, 0, false; f <= fData; f, i = f<<1, i+1 { - if (required & f) == 0 { - continue - } - if d { - w.WriteString(", ") - } - w.WriteString(symbols[i]) - d = true - } - w.WriteByte(')') - return w.String() + return nil, []error{verificationError(ErrEmptyRecord, "", "", expEmptyRecord)} } + + var errs []error + required := false k := &PublicKey{Revoked: true, Raw: s} for _, m := range reTagValueList.FindAllStringSubmatch(s, -1) { // m := ["t=v" "t" "v"] @@ -727,7 +732,7 @@ func ParsePublicKey(s string) (*PublicKey, error) { switch key { case "v": // Version of the DKIM key record if value != "DKIM1" { - return unacceptableKey("v", value, expUnsupportedVersion) + errs = append(errs, verificationError(ErrUnsupportedVersion, "v", value, expUnsupportedVersion)) } k.Version = value case "h": // Acceptable Hash algorithms @@ -741,14 +746,14 @@ func ParsePublicKey(s string) (*PublicKey, error) { return s }) if !acceptable { - return unacceptableKey("h", value, expUnsupportedAlgorithm) + errs = append(errs, verificationError(ErrUnsupportedAlgorithm, "h", value, expUnsupportedAlgorithm)) } case "k": // Key type k.KeyType = value if value != "rsa" && value != "ed25519" { // Unrecognized key types MUST be ignored. // https://tools.ietf.org/html/rfc6376#page-27 - return unacceptableKey("k", value, expUnsupportedAlgorithm) + errs = append(errs, verificationError(ErrUnsupportedAlgorithm, "k", value, expUnsupportedAlgorithm)) } case "n": // Notes that might be of interest to a human k.Notes = value @@ -756,34 +761,14 @@ func ParsePublicKey(s string) (*PublicKey, error) { // An empty value means that this public key has been revoked. // The syntax and semantics of this tag value before being // encoded in base64 are defined by the "k=" tag. + required = true if value != "" { - // INFORMATIVE NOTE: A base64string is permitted to include - // whitespace (FWS) at arbitrary places; however, any CRLFs must - // be followed by at least one WSP character. Implementers and - // administrators are cautioned to ensure that selector TXT RRs - // conform to this specification. - b, err := base64.StdEncoding.DecodeString(reReduceFWS.ReplaceAllString(value, "")) - if err != nil { - return unacceptableKey("p", value, err.Error()) - } - k.Data = b - if k.KeyType == "ed25519" { - k.Revoked = false - return k, nil - } - i, err := x509.ParsePKIXPublicKey(b) + var err error + k.Data, k.Key, k.Revoked, err = validatePublicKey(k.KeyType, value) if err != nil { - return unacceptableKey("p", value, err.Error()) - } - pkey, ok := i.(*rsa.PublicKey) - if !ok { - // should not happen - return unacceptableKey("p", value, "internal error") + errs = append(errs, err) } - k.Key = pkey - k.Revoked = false } - required &^= fData case "s": // Service Type acceptable := false k.Services = mapMatches(reKeySTag, value, func(m []string) string { @@ -797,7 +782,7 @@ func ParsePublicKey(s string) (*PublicKey, error) { return m[1] }) if !acceptable { - return unacceptableKey("s", value, expUnsupportedServices) + errs = append(errs, verificationError(ErrUnsupportedServices, "s", value, expUnsupportedServices)) } case "t": // Flags k.Flags = mapMatches(reKeyXTag, value, func(m []string) string { @@ -812,11 +797,56 @@ func ParsePublicKey(s string) (*PublicKey, error) { }) } } - if required != 0 { - return unacceptableKey("", "", missedTags()) + if !required { + errs = append(errs, verificationError(ErrMissedPTag, "p", "", "no required tag p found")) } - return k, nil + return k, errs +} + +func validatePublicKey(keyType, src string) ([]byte, *rsa.PublicKey, bool, error) { + // INFORMATIVE NOTE: A base64string is permitted to include + // whitespace (FWS) at arbitrary places; however, any CRLFs must + // be followed by at least one WSP character. Implementers and + // administrators are cautioned to ensure that selector TXT RRs + // conform to this specification. + b, err := base64.StdEncoding.DecodeString(reReduceFWS.ReplaceAllString(src, "")) + if err != nil { + return nil, nil, true, verificationError(ErrDecodeBase64, "p", src, err.Error()) + } + + if keyType == "ed25519" { + if l := len(b); l != ed25519.PublicKeySize { + return b, nil, true, verificationError(ErrInvalidED25519Key, "p", src, "ed25519: bad public key length: "+strconv.Itoa(l)) + } + + return b, nil, false, nil + } + + i, err := x509.ParsePKIXPublicKey(b) + if err != nil { + return b, nil, true, verificationError(ErrParsePublicKey, "p", src, err.Error()) + } + pkey, ok := i.(*rsa.PublicKey) + if !ok { + // should not happen + return b, nil, true, verificationError(ErrParsePublicKey, "p", src, "internal error") + } + if pkey.N.BitLen() < MinRSAKeyLen { + return b, nil, true, verificationError(ErrWeakRSAKey, "p", src, fmt.Sprintf("RSA key too short (%d bits)", pkey.N.BitLen())) + } + + return b, pkey, false, nil +} + +func verificationError(err error, t, v string, s string) error { + return &VerificationError{ + Source: KeyError, + Tag: t, + Value: v, + Err: err, + Explanation: s, + } } func (s *Signature) verifyBodyHash(rs io.ReadSeeker) (ResultCode, error) { diff --git a/dkim_test.go b/dkim_test.go index e028576..8578de7 100644 --- a/dkim_test.go +++ b/dkim_test.go @@ -1,6 +1,7 @@ package dkim import ( + "errors" "fmt" "io" "os" @@ -11,6 +12,7 @@ import ( "time" "github.com/google/go-cmp/cmp" + "github.com/stretchr/testify/assert" ) type cacheEntry struct { @@ -38,7 +40,7 @@ func CachedPublicKeyQuery(s *Signature) (*PublicKey, error) { return c.k, c.e } k, e := ParsePublicKey(c.s) - entry := &cacheEntry{k: k, e: e} + entry := &cacheEntry{k: k, e: errors.Join(e...)} cache[n] = entry return entry.k, nil } @@ -76,7 +78,7 @@ func TestDnsTxtPublicKeyQuery(t *testing.T) { {"highgrade", sigs["highgrade"], mustKey("highgrade", "guerrillamail.com"), nil}, // skipped as dns record no longer contains public key //{"20161025", sigs["20161025"], mustKey("20161025", "1e100.net"), nil}, - {"temperror", sigs["temperror"], nil, ErrKeyUnavailable}, + {"temperror", sigs["temperror"], &PublicKey{Raw: "v=DKIM1; p=", Version: "DKIM1", Revoked: true}, nil}, } const wantTest = -1 @@ -98,63 +100,174 @@ func TestDnsTxtPublicKeyQuery(t *testing.T) { } func TestParsePublicKey(t *testing.T) { - unacceptableKey := func(t, v string, s string) error { - return &VerificationError{ - Source: KeyError, - Tag: t, - Value: v, - Err: ErrUnacceptableKey, - Explanation: s, - } - } tests := []struct { name string raw string wantKey *PublicKey - wantErr error + wantErr []error }{ - {"empty", - "", nil, unacceptableKey("", "", expEmptyKey)}, - {"wrong_version", - "v=1", nil, unacceptableKey("v", "1", expUnsupportedVersion)}, - {"no data", - "v=DKIM1", nil, unacceptableKey("", "", "no required tags found (v)")}, - {"wrong algorithm", - "h=md5", nil, unacceptableKey("h", "md5", expUnsupportedAlgorithm)}, - {"wrong type", - "k=des", nil, unacceptableKey("k", "des", expUnsupportedAlgorithm)}, - {"revoked", - "p=", &PublicKey{Raw: "p=", Revoked: true}, nil}, - {"wrong data", - "p==", nil, unacceptableKey("p", "=", "illegal base64 data at input byte 0")}, - {"not a key", - "p=MAMA", nil, unacceptableKey("p", "MAMA", "asn1: syntax error: data truncated")}, - {"revoked testing strict", - "p=; t=y:\n\ts", &PublicKey{Raw: "p=; t=y:\n\ts", Flags: []string{"y", "s"}, Revoked: true, Testing: true, Strict: true}, nil}, - {"x-teleport", - "p=; s=*:email:x-teleport", &PublicKey{Raw: "p=; s=*:email:x-teleport", Revoked: true, Services: []string{"*", "email", "x-teleport"}}, nil}, - {"no services listed", - "p=; s=x-teleport", nil, unacceptableKey("s", "x-teleport", expUnsupportedServices)}, - {"key with ws", `k=rsa; p= NDM1NWE0Nm - IxOWQzNDhkYzJmNTdjM DQ2ZjhlZjYzZDQ1Mzh lYmI5MzYwMDBm - M2M5ZWU5NTRhMjc0NjBkZDg2NSAgLQo=`, nil, unacceptableKey("p", `NDM1NWE0Nm - IxOWQzNDhkYzJmNTdjM DQ2ZjhlZjYzZDQ1Mzh lYmI5MzYwMDBm - M2M5ZWU5NTRhMjc0NjBkZDg2NSAgLQo=`, "asn1: structure error: tags don't match (16 vs {class:0 tag:20 length:51 isCompound:true}) {optional:false explicit:false application:false private:false defaultValue: tag: stringType:0 timeType:0 set:false omitEmpty:false} publicKeyInfo @2")}, - } - const wantTest = 11 + { + "empty", + "", + nil, + []error{verificationError(ErrEmptyRecord, "", "", expEmptyRecord)}, + }, + { + "wrong_version", + "v=1", + &PublicKey{Raw: "v=1", Version: "1", Revoked: true}, + []error{ + verificationError(ErrUnsupportedVersion, "v", "1", expUnsupportedVersion), + verificationError(ErrMissedPTag, "p", "", "no required tag p found"), + }, + }, + { + "no data", + "v=DKIM1", + &PublicKey{Raw: "v=DKIM1", Version: "DKIM1", Revoked: true}, + []error{verificationError(ErrMissedPTag, "p", "", "no required tag p found")}, + }, + { + "wrong algorithm", + "h=md5", + &PublicKey{Raw: "h=md5", Algorithms: []string{"rsa-md5"}, Revoked: true}, + []error{ + verificationError(ErrUnsupportedAlgorithm, "h", "md5", expUnsupportedAlgorithm), + verificationError(ErrMissedPTag, "p", "", "no required tag p found"), + }, + }, + { + "wrong type", + "k=des", + &PublicKey{Raw: "k=des", KeyType: "des", Revoked: true}, + []error{ + verificationError(ErrUnsupportedAlgorithm, "k", "des", expUnsupportedAlgorithm), + verificationError(ErrMissedPTag, "p", "", "no required tag p found"), + }, + }, + { + "revoked", + "p=", + &PublicKey{Raw: "p=", Revoked: true}, + nil, + }, + { + "wrong data", + "p==", + &PublicKey{Raw: "p==", Revoked: true}, + []error{ + verificationError(ErrDecodeBase64, "p", "=", "illegal base64 data at input byte 0"), + }, + }, + { + "not a key", + "p=MAMA", + &PublicKey{Raw: "p=MAMA", Data: []byte{0x30, 0x03, 0x00}, Revoked: true}, + []error{ + verificationError(ErrParsePublicKey, "p", "MAMA", "asn1: syntax error: data truncated"), + }, + }, + { + "revoked testing strict", + "p=; t=y:\n\ts", + &PublicKey{Raw: "p=; t=y:\n\ts", Flags: []string{"y", "s"}, Revoked: true, Testing: true, Strict: true}, + nil, + }, + { + "x-teleport", + "p=; s=*:email:x-teleport", + &PublicKey{Raw: "p=; s=*:email:x-teleport", Revoked: true, Services: []string{"*", "email", "x-teleport"}}, + nil, + }, + { + "no services listed", + "p=; s=x-teleport", + &PublicKey{Raw: "p=; s=x-teleport", Services: []string{"x-teleport"}, Revoked: true}, + []error{verificationError(ErrUnsupportedServices, "s", "x-teleport", expUnsupportedServices)}, + }, + { + "key with ws", + `k=rsa; p= NDM1NWE0Nm\n IxOWQzNDhkYzJmNTdjM DQ2ZjhlZjYzZDQ1Mzh lYmI5MzYwMDBm \n M2M5ZWU5NTRhMjc0NjBkZDg2NSAgLQo=`, + &PublicKey{Raw: "k=rsa; p= NDM1NWE0Nm\\n IxOWQzNDhkYzJmNTdjM DQ2ZjhlZjYzZDQ1Mzh lYmI5MzYwMDBm \\n M2M5ZWU5NTRhMjc0NjBkZDg2NSAgLQo=", Version: "", KeyType: "rsa", Revoked: true}, + []error{ + verificationError(ErrDecodeBase64, "p", `NDM1NWE0Nm\n IxOWQzNDhkYzJmNTdjM DQ2ZjhlZjYzZDQ1Mzh lYmI5MzYwMDBm \n M2M5ZWU5NTRhMjc0NjBkZDg2NSAgLQo=`, "illegal base64 data at input byte 10"), + }, + }, + { + "512", + "v=DKIM1; k=rsa; p=MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAMPekl+uAfmcdIuyNUybIsdPyzK6mFOk5s87lOXFWKs//btaTCJRSjG1kijuGZZEZluxlX/qR1xB4G/ep/hYTZkCAwEAAQ==", + &PublicKey{ + Raw: "v=DKIM1; k=rsa; p=MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAMPekl+uAfmcdIuyNUybIsdPyzK6mFOk5s87lOXFWKs//btaTCJRSjG1kijuGZZEZluxlX/qR1xB4G/ep/hYTZkCAwEAAQ==", + Version: "DKIM1", + KeyType: "rsa", + Revoked: true, + Data: []uint8{0x30, 0x5c, 0x30, 0xd, 0x6, 0x9, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0xd, 0x1, 0x1, 0x1, 0x5, 0x0, 0x3, 0x4b, 0x0, 0x30, 0x48, 0x2, 0x41, 0x0, 0xc3, 0xde, 0x92, 0x5f, 0xae, 0x1, 0xf9, 0x9c, 0x74, 0x8b, 0xb2, 0x35, 0x4c, 0x9b, 0x22, 0xc7, 0x4f, 0xcb, 0x32, 0xba, 0x98, 0x53, 0xa4, 0xe6, 0xcf, 0x3b, 0x94, 0xe5, 0xc5, 0x58, 0xab, 0x3f, 0xfd, 0xbb, 0x5a, 0x4c, 0x22, 0x51, 0x4a, 0x31, 0xb5, 0x92, 0x28, 0xee, 0x19, 0x96, 0x44, 0x66, 0x5b, 0xb1, 0x95, 0x7f, 0xea, 0x47, 0x5c, 0x41, 0xe0, 0x6f, 0xde, 0xa7, 0xf8, 0x58, 0x4d, 0x99, 0x2, 0x3, 0x1, 0x0, 0x1}, + }, + []error{ + verificationError(ErrWeakRSAKey, "p", `MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAMPekl+uAfmcdIuyNUybIsdPyzK6mFOk5s87lOXFWKs//btaTCJRSjG1kijuGZZEZluxlX/qR1xB4G/ep/hYTZkCAwEAAQ==`, "RSA key too short (512 bits)"), + }, + }, + { + "no-p", + "v=DKIM1;", + &PublicKey{Raw: "v=DKIM1;", Version: "DKIM1", Revoked: true}, + []error{verificationError(ErrMissedPTag, "p", "", "no required tag p found")}, + }, + { + "no-dkim-record", + " ", + nil, + []error{verificationError(ErrEmptyRecord, "", "", "empty record")}, + }, + { + "invalid-base64", + "v=DKIM1; k=rsa; p=invalid-base64$$$===", + &PublicKey{Raw: "v=DKIM1; k=rsa; p=invalid-base64$$$===", Version: "DKIM1", KeyType: "rsa", Revoked: true}, + []error{verificationError(ErrDecodeBase64, "p", "invalid-base64$$$===", "illegal base64 data at input byte 7")}, + }, + { + "invalid-algorithm", + "v=DKIM1; k=unsupported; p=", + &PublicKey{Raw: "v=DKIM1; k=unsupported; p=", Version: "DKIM1", KeyType: "unsupported", Revoked: true}, + []error{verificationError(ErrUnsupportedAlgorithm, "k", "unsupported", "unsupported algorithm")}, + }, + { + "invalid-version", + "v=DKIM99; k=rsa; p=", + &PublicKey{Raw: "v=DKIM99; k=rsa; p=", Version: "DKIM99", KeyType: "rsa", Revoked: true}, + []error{verificationError(ErrUnsupportedVersion, "v", "DKIM99", "unsupported version")}, + }, + { + "invalid-service", + "v=DKIM1; s=unsupported; k=rsa; p=", + &PublicKey{Raw: "v=DKIM1; s=unsupported; k=rsa; p=", Version: "DKIM1", KeyType: "rsa", Services: []string{"unsupported"}, Revoked: true}, + []error{verificationError(ErrUnsupportedServices, "s", "unsupported", "no supported services listed")}, + }, + { + "revoked", + "v=DKIM1; p=", + &PublicKey{Raw: "v=DKIM1; p=", Version: "DKIM1", Revoked: true}, + nil, + }, + { + "invalid-ed25519", + "v=DKIM1; k=ed25519; p=aW52YWxpZCByc2EK", + &PublicKey{Raw: "v=DKIM1; k=ed25519; p=aW52YWxpZCByc2EK", Version: "DKIM1", KeyType: "ed25519", Revoked: true, Data: []uint8{0x69, 0x6e, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x20, 0x72, 0x73, 0x61, 0xa}}, + []error{verificationError(ErrInvalidED25519Key, "p", "aW52YWxpZCByc2EK", "ed25519: bad public key length: 12")}, + }, + { + "invalid-rsa", + "v=DKIM1; k=rsa; p=aW52YWxpZCByc2EK", + &PublicKey{Raw: "v=DKIM1; k=rsa; p=aW52YWxpZCByc2EK", Version: "DKIM1", KeyType: "rsa", Revoked: true, Data: []uint8{0x69, 0x6e, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x20, 0x72, 0x73, 0x61, 0xa}}, + []error{verificationError(ErrParsePublicKey, "p", "aW52YWxpZCByc2EK", "asn1: structure error: tags don't match (16 vs {class:1 tag:9 length:110 isCompound:true}) {optional:false explicit:false application:false private:false defaultValue: tag: stringType:0 timeType:0 set:false omitEmpty:false} publicKeyInfo @2")}, + }, + } + for testNo, test := range tests { - //noinspection GoBoolExpressions - if wantTest > -1 && wantTest != testNo { - continue - } t.Run(fmt.Sprintf("%d_%s", testNo, test.name), func(t *testing.T) { - key, err := ParsePublicKey(test.raw) - if !reflect.DeepEqual(err, test.wantErr) { - t.Errorf("ParsePublicKey()\n\t err=\"%v\"\n\twantErr=\"%v\"", err, test.wantErr) - } - if !reflect.DeepEqual(key, test.wantKey) { - t.Errorf("ParsePublicKey()\n\t key=\"%#v\"\n\twantKey=\"%#v\"", key, test.wantKey) - } + key, errs := ParsePublicKey(test.raw) + assert.Equal(t, test.wantErr, errs) + assert.Equal(t, test.wantKey, key) }) } } diff --git a/go.mod b/go.mod index 10d84b6..015830b 100644 --- a/go.mod +++ b/go.mod @@ -1,22 +1,30 @@ module github.com/redsift/dkim -go 1.18 +go 1.24 require ( - github.com/google/go-cmp v0.5.8 - github.com/tailscale/depaware v0.0.0-20210622194025-720c4b409502 + github.com/google/go-cmp v0.6.0 + github.com/stretchr/testify v1.4.0 gopkg.in/yaml.v2 v2.4.0 - honnef.co/go/tools v0.3.3 - mvdan.cc/gofumpt v0.4.0 ) require ( - github.com/BurntSushi/toml v0.4.1 // indirect + github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c // indirect + github.com/davecgh/go-spew v1.1.0 // indirect github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e // indirect - golang.org/x/exp/typeparams v0.0.0-20220218215828-6cf2b201936e // indirect - golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect - golang.org/x/sync v0.0.0-20220819030929-7fc1605a5dde // indirect - golang.org/x/sys v0.0.0-20220829200755-d48e67d00261 // indirect - golang.org/x/tools v0.1.12 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/tailscale/depaware v0.0.0-20250112153213-b748de04d81b // indirect + golang.org/x/exp/typeparams v0.0.0-20231108232855-2478ac86f678 // indirect + golang.org/x/mod v0.23.0 // indirect + golang.org/x/sync v0.11.0 // indirect + golang.org/x/tools v0.30.0 // indirect gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect + honnef.co/go/tools v0.6.1 // indirect + mvdan.cc/gofumpt v0.7.0 // indirect +) + +tool ( + github.com/tailscale/depaware/depaware + honnef.co/go/tools/cmd/staticcheck + mvdan.cc/gofumpt ) diff --git a/go.sum b/go.sum index f12b67d..17586a3 100644 --- a/go.sum +++ b/go.sum @@ -1,50 +1,57 @@ -github.com/BurntSushi/toml v0.4.1 h1:GaI7EiDXDRfa8VshkTj7Fym7ha+y8/XxIgD2okUIjLw= -github.com/BurntSushi/toml v0.4.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c h1:pxW6RcqyfI9/kWtOwnv/G+AzdKuy2ZrqINhenH4HyNs= +github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= -github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= -github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/go-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7eI= +github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/pkg/diff v0.0.0-20200914180035-5b29258ca4f7/go.mod h1:zO8QMzTeZd5cpnIkz/Gn6iK0jDfGicM1nynOkkPIl28= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e h1:aoZm08cpOy4WuID//EZDgcC4zIxODThtZNPirFr42+A= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= +github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= +github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/tailscale/depaware v0.0.0-20210622194025-720c4b409502 h1:34icjjmqJ2HPjrSuJYEkdZ+0ItmGQAQ75cRHIiftIyE= -github.com/tailscale/depaware v0.0.0-20210622194025-720c4b409502/go.mod h1:p9lPsd+cx33L3H9nNoecRRxPssFKUwwI50I3pZ0yT+8= +github.com/tailscale/depaware v0.0.0-20250112153213-b748de04d81b h1:ewWb4cA+YO9/3X+v5UhdV+eKFsNBOPcGRh39Glshx/4= +github.com/tailscale/depaware v0.0.0-20250112153213-b748de04d81b/go.mod h1:p9lPsd+cx33L3H9nNoecRRxPssFKUwwI50I3pZ0yT+8= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/exp/typeparams v0.0.0-20220218215828-6cf2b201936e h1:qyrTQ++p1afMkO4DPEeLGq/3oTsdlvdH4vqZUBWzUKM= -golang.org/x/exp/typeparams v0.0.0-20220218215828-6cf2b201936e/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= +golang.org/x/exp/typeparams v0.0.0-20231108232855-2478ac86f678 h1:1P7xPZEwZMoBoz0Yze5Nx2/4pxj6nw9ZqHWXqP0iRgQ= +golang.org/x/exp/typeparams v0.0.0-20231108232855-2478ac86f678/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s= -golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.23.0 h1:Zb7khfcRGKk+kqfxFaP5tZqCnDZMjC5VtUBs87Hr6QM= +golang.org/x/mod v0.23.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220819030929-7fc1605a5dde h1:ejfdSekXMDxDLbRrJMwUk6KnSLZ2McaUCVcIKM+N6jc= -golang.org/x/sync v0.0.0-20220819030929-7fc1605a5dde/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w= +golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20220829200755-d48e67d00261 h1:v6hYoSR9T5oet+pMXwUWkbiVqx/63mlHjefrHmxwfeY= -golang.org/x/sys v0.0.0-20220829200755-d48e67d00261/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= +golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20201211185031-d93e913c1a58/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU= -golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.30.0 h1:BgcpHewrV5AUp2G9MebG4XPFI1E2W41zU1SaqVA9vJY= +golang.org/x/tools v0.30.0/go.mod h1:c347cR/OJfw5TI+GfX7RUPNMdDRRbjvYTS0jPyvsVtY= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -54,7 +61,7 @@ gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= -honnef.co/go/tools v0.3.3 h1:oDx7VAwstgpYpb3wv0oxiZlxY+foCpRAwY7Vk6XpAgA= -honnef.co/go/tools v0.3.3/go.mod h1:jzwdWgg7Jdq75wlfblQxO4neNaFFSvgc1tD5Wv8U0Yw= -mvdan.cc/gofumpt v0.4.0 h1:JVf4NN1mIpHogBj7ABpgOyZc65/UUOkKQFkoURsz4MM= -mvdan.cc/gofumpt v0.4.0/go.mod h1:PljLOHDeZqgS8opHRKLzp2It2VBuSdteAgqUfzMTxlQ= +honnef.co/go/tools v0.6.1 h1:R094WgE8K4JirYjBaOpz/AvTyUu/3wbmAoskKN/pxTI= +honnef.co/go/tools v0.6.1/go.mod h1:3puzxxljPCe8RGJX7BIy1plGbxEOZni5mR2aXe3/uk4= +mvdan.cc/gofumpt v0.7.0 h1:bg91ttqXmi9y2xawvkuMXyvAA/1ZGJqYAEGjXuP0JXU= +mvdan.cc/gofumpt v0.7.0/go.mod h1:txVFJy/Sc/mvaycET54pV8SW8gWxTlUuGHVEcncmNUo= diff --git a/internal/tooldeps/toolsdeps.go b/internal/tooldeps/toolsdeps.go deleted file mode 100644 index 18f7e94..0000000 --- a/internal/tooldeps/toolsdeps.go +++ /dev/null @@ -1,9 +0,0 @@ -//go:build for_go_mod_tidy_only - -package tooldeps - -import ( - _ "github.com/tailscale/depaware/depaware" - _ "honnef.co/go/tools/cmd/staticcheck" - _ "mvdan.cc/gofumpt" -)