From 944e6d36100b42e1a538b4db7c4252d2bdb65e91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Depierrepont=20aka=20Toorop?= Date: Tue, 12 May 2015 18:21:41 +0200 Subject: [PATCH] pub key representation parsing 2/.. --- dkim.go | 5 +++- errors.go | 23 ++++++++++++++- pubKeyRep.go | 82 ++++++++++++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 106 insertions(+), 4 deletions(-) diff --git a/dkim.go b/dkim.go index a1724d7..55aa1a8 100644 --- a/dkim.go +++ b/dkim.go @@ -243,7 +243,10 @@ func Verify(email *[]byte) (VerifyOutput, error) { } // we do not set quesry method because if it's other validation failed earlier - pubKey, err := newPubKeyFromDnsTxt(dkimHeader.Selector, dkimHeader.Domain) + pubKey, verifyOutputOnError, err := newPubKeyFromDnsTxt(dkimHeader.Selector, dkimHeader.Domain) + if err != nil { + return verifyOutputOnError, err + } println(pubKey) return SUCCESS, nil diff --git a/errors.go b/errors.go index 466296a..19eadf5 100644 --- a/errors.go +++ b/errors.go @@ -53,9 +53,30 @@ var ( // Version not supported ErrDkimVersionNotsupported = errors.New("incompatible version") - // Query method unsopported + // Query method unsupported errQueryMethodNotsupported = errors.New("query method not supported") // ErrVerifyBodyHash when body hash doesn't verify ErrVerifyBodyHash = errors.New("body hash did not verify") + + // ErrVerifyNoKeyForSignature + ErrVerifyNoKeyForSignature = errors.New("no key for verify") + + // ErrVerifyKeyUnavailable when service (dns) is anavailable + ErrVerifyKeyUnavailable = errors.New("key unavailable") + + // ErrVerifyTagVMustBeTheFirst if present the v tag must be the firts in the record + ErrVerifyTagVMustBeTheFirst = errors.New("pub key syntax error: v tag must be the first") + + // ErrVerifyVersionMusBeDkim1 if présent flag v (version) must be DKIM1 + ErrVerifyVersionMusBeDkim1 = errors.New("flag v must be set to DKIM1") + + // ErrVerifyBadKeyType bad type for pub key (only rsa is accepted) + ErrVerifyBadKeyType = errors.New("bad type for key type") + + // ErrVerifyRevokedKey key(s) for this selector is revoked (p is empty) + ErrVerifyRevokedKey = errors.New("revoked key") + + // ErrVerifyBadKey when we can't parse pubkey + ErrVerifyBadKey = errors.New("unable to parse pub key") ) diff --git a/pubKeyRep.go b/pubKeyRep.go index 84ebb4e..c8f1e78 100644 --- a/pubKeyRep.go +++ b/pubKeyRep.go @@ -2,8 +2,11 @@ package dkim import ( "crypto/rsa" + "crypto/x509" + "encoding/base64" "fmt" "net" + "strings" ) // pubKeyRep represents a parsed version of public key record @@ -18,9 +21,84 @@ type pubKeyRep struct { FlagIMustBeD bool // flag i } -func newPubKeyFromDnsTxt(selector, domain string) (*pubKeyRep, error) { +func newPubKeyFromDnsTxt(selector, domain string) (*pubKeyRep, VerifyOutput, error) { txt, err := net.LookupTXT(selector + "._domainkey." + domain) + if err != nil { + if strings.HasSuffix(err.Error(), "no such host") { + return nil, PERMFAIL, ErrVerifyNoKeyForSignature + } else { + return nil, TEMPFAIL, ErrVerifyKeyUnavailable + } + } + + // empty record + if len(txt) == 0 { + return nil, PERMFAIL, ErrVerifyNoKeyForSignature + } + + pkr := new(pubKeyRep) + pkr.Version = "DKIM1" + pkr.HashAlgo = []string{"sha1", "sha256"} + pkr.KeyType = "rsa" + + // parsing, we keep the first record + // TODO: if there is multiple record + + p := strings.Split(txt[0], ";") + for i, data := range p { + keyVal := strings.SplitN(data, "=", 2) + switch strings.ToLower(strings.TrimSpace(keyVal[0])) { + case "v": + // RFC: is this tag is specified it MUST be the first in the record + if i != 0 { + return nil, PERMFAIL, ErrVerifyTagVMustBeTheFirst + } + pkr.Version = strings.TrimSpace(keyVal[1]) + if pkr.Version != "DKIM1" { + return nil, PERMFAIL, ErrVerifyVersionMusBeDkim1 + } + case "h": + p := strings.Split(strings.ToLower(keyVal[1]), ":") + pkr.HashAlgo = []string{} + for _, h := range p { + h = strings.TrimSpace(h) + if h == "sha1" || h == "sha256" { + pkr.HashAlgo = append(pkr.HashAlgo, h) + } + } + // if empty switch back to default + if len(pkr.HashAlgo) == 0 { + pkr.HashAlgo = []string{"sha1", "sha256"} + } + case "k": + if strings.ToLower(strings.TrimSpace(keyVal[1])) != "rsa" { + return nil, PERMFAIL, ErrVerifyBadKeyType + } + case "n": + pkr.Note = strings.TrimSpace(keyVal[1]) + case "p": + rawkey := strings.TrimSpace(keyVal[1]) + if rawkey == "" { + return nil, PERMFAIL, ErrVerifyRevokedKey + } + // x509.ParsePKIXPublicKey(Dkim.PublicKey.PublicKey) + un64, err := base64.StdEncoding.DecodeString(rawkey) + if err != nil { + return nil, PERMFAIL, ErrVerifyBadKey + } + pk, err := x509.ParsePKIXPublicKey(un64) + pkr.PubKey = *pk.(*rsa.PublicKey) + // HERE + case "s": + case "t": + + } + + } + + // TODO: If no pubkey + fmt.Println(txt, err) - return nil, nil + return nil, SUCCESS, nil }