Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix DKIM-Signature header flowing, it is not valid to arbitrarily sli… #27

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
232 changes: 232 additions & 0 deletions dkim/dkim.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,241 @@
package dkim

import (
"bytes"
"strings"
"time"
)

var now = time.Now

const headerFieldName = "DKIM-Signature"

type DKIMTag interface {
Reset()
GetString(limit int) (chars string, ok bool)
GetRemaining() string
Done() bool
}

type DKIMTagBase64 struct {
TagLen int
TagAndValue string
Idx int
}

func (t *DKIMTagBase64) Reset() {
t.Idx = 0
}

func (t *DKIMTagBase64) NextBreak(idx int, max int) int {
if idx == 0 {
return t.TagLen
} else if idx == t.TagLen {
return t.TagLen+1
} else {
end := len(t.TagAndValue)
if max < end {
return max
} else {
return end
}
}
}

func (t *DKIMTagBase64) GetString(limit int) (chars string, ok bool) {
end_max := len(t.TagAndValue)
if end_max - t.Idx <= limit {
chars = t.TagAndValue[t.Idx:]
t.Idx = end_max
ok = true
return
}
if t.Idx + limit < end_max {
end_max = t.Idx + limit
}
end := t.Idx
for end < end_max {
idx := t.NextBreak(end, end_max)
if idx <= end_max {
end = idx
} else {
break
}
}
if t.Idx < end {
chars = t.TagAndValue[t.Idx:end]
t.Idx = end
ok = true
}
return
}

func (t *DKIMTagBase64) GetRemaining() string {
chars := t.TagAndValue[t.Idx:]
t.Idx = len(t.TagAndValue)
return chars
}

func (t *DKIMTagBase64) Done() bool {
return t.Idx == len(t.TagAndValue)
}

type DKIMTagDelim struct {
TagLen int
TagAndValue string
Delimiter string
Idx int
}

func (t *DKIMTagDelim) Reset() {
t.Idx = 0
}

func (t *DKIMTagDelim) NextBreak(idx int) int {
if idx == 0 {
return t.TagLen
} else if idx == t.TagLen {
return t.TagLen+1
} else {
if t.Delimiter == "" {
return len(t.TagAndValue)
} else {
i := strings.Index(t.TagAndValue[idx:], t.Delimiter)
if i == -1 {
return len(t.TagAndValue)
} else {
if i == 0 {
return idx + len(t.Delimiter)
} else {
return i + idx
}
}
}
}
}

func (t *DKIMTagDelim) GetString(limit int) (chars string, ok bool) {
end_max := len(t.TagAndValue)
if end_max - t.Idx <= limit {
chars = t.TagAndValue[t.Idx:]
t.Idx = end_max
ok = true
return
}
if t.Idx + limit < end_max {
end_max = t.Idx + limit
}
end := t.Idx
for end < end_max {
idx := t.NextBreak(end)
if idx <= end_max {
end = idx
} else {
break
}
}
if t.Idx < end {
chars = t.TagAndValue[t.Idx:end]
t.Idx = end
ok = true
}
return
}

func (t *DKIMTagDelim) GetRemaining() string {
chars := t.TagAndValue[t.Idx:]
t.Idx = len(t.TagAndValue)
return chars
}

func (t *DKIMTagDelim) Done() bool {
return t.Idx == len(t.TagAndValue)
}

func NewDKIMTagPlain(tag string, value string) DKIMTag {
dtag := &DKIMTagDelim{
TagLen: len(tag),
TagAndValue: tag+"="+value,
}
return dtag
}

func NewDKIMTagDelim(tag string, values []string, delimiter string) DKIMTag {
var sbuf bytes.Buffer
sbuf.WriteString(tag)
sbuf.WriteString("=")
for idx, value := range values {
if idx > 0 {
sbuf.WriteString(delimiter)
}
sbuf.WriteString(value)
}
dtag := &DKIMTagDelim{
TagLen: len(tag),
TagAndValue: sbuf.String(),
Delimiter: delimiter,
}
return dtag
}

func NewDKIMTagBase64(tag string, value string) DKIMTag {
dtag := &DKIMTagBase64{
TagLen: len(tag),
TagAndValue: tag+"="+value,
}
return dtag
}

type DKIMSignature struct {
Buf bytes.Buffer
LineLen int
}

func (sig *DKIMSignature) AddTag(tag DKIMTag) {
tag.Reset()
for ! tag.Done() {
max_chars := 80 - sig.LineLen - 1 - 2 // allow for CRLF and also the semi-colon
if max_chars <= 0 {
sig.Buf.WriteString("\r\n ")
sig.LineLen = 1
continue
}
s, ok := tag.GetString(max_chars)
if !ok {
if sig.LineLen > 1 {
sig.Buf.WriteString("\r\n ")
sig.LineLen = 1
continue
} else {
// we can't break the line, we are forced to just put it in
s = tag.GetRemaining()
}
}
sig.Buf.WriteString(s)
sig.LineLen += len(s)
if tag.Done() {
sig.Buf.WriteString(";")
sig.LineLen += 1
}
}
}

func (sig *DKIMSignature) AddPlainTag(tag string, value string) {
sig.AddTag(NewDKIMTagPlain(tag, value))
}

func (sig *DKIMSignature) AddDelimTag(tag string, values []string, delimiter string) {
sig.AddTag(NewDKIMTagDelim(tag, values, delimiter))
}

func (sig *DKIMSignature) AddBase64Tag(tag string, value string) {
sig.AddTag(NewDKIMTagBase64(tag, value))
}

func NewDKIMSignature() *DKIMSignature {
sig := DKIMSignature{}
sig.Buf.WriteString(headerFieldName)
sig.Buf.WriteString(": ")
sig.LineLen = len(headerFieldName)+2
return &sig
}
152 changes: 152 additions & 0 deletions dkim/dkim_signature_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
package dkim

import "testing"

func TestNewDKIMTagPlain(t *testing.T) {
t1 := NewDKIMTagPlain("a", "123456")
if t1.Done() {
t.Errorf("tag Done after init")
}
s := t1.GetRemaining()
if s != "a=123456" {
t.Errorf("GetRemaining failed after init")
}
if ! t1.Done() {
t.Errorf("!Done after GetRemaining")
}
t1.Reset()
var ok bool
s, ok = t1.GetString(3)
if !ok {
t.Errorf("failed to break after =")
}
if s != "a=" {
t.Errorf("failed to get name=")
}
s, ok = t1.GetString(8)
if ! ok {
t.Errorf("!ok for entire values")
}
if s != "123456" {
t.Errorf("s bad for GetString")
}
if ! t1.Done() {
t.Errorf("!Done after GetString for all")
}
t1.Reset()
s, ok = t1.GetString(2)
if !ok {
t.Errorf("GetString <name>= failed")
}
if s != "a=" {
t.Errorf("GetString <name>= resulted in %v", s)
}
if t1.Done() {
t.Errorf("Done after partial")
}
s, ok = t1.GetString(3)
if ok {
t.Errorf("GetString returned partial value")
}
s, ok = t1.GetString(6)
if !ok {
t.Errorf("GetString did not return value")
}
if s != "123456" {
t.Errorf("GetString returned wrong partial %v", s)
}
if ! t1.Done() {
t.Errorf("!Done after getting last bit")
}
t1.Reset()
s, ok = t1.GetString(1)
if ! ok {
t.Errorf("GetString <tagname> failed")
}
if s != "a" {
t.Errorf("GetString <tagname> incorrect %v", s)
}
s, ok = t1.GetString(4)
if !ok {
t.Errorf("GetString failed getting partial")
}
if s != "=" {
t.Errorf("GetString != =")
}
s, ok = t1.GetString(6)
if !ok {
t.Errorf("GetString remaining failed")
}
if s != "123456" {
t.Errorf("GetString remaining failed: %v", s)
}
if ! t1.Done() {
t.Errorf("Not done after partial")
}

t2 := NewDKIMTagPlain("ab", "123456")
s, ok = t2.GetString(1)
if ok {
t.Errorf("incorrectly got part of name: %v", s)
}
}

func TestNewDKIMTagDelim(t *testing.T) {
dt := NewDKIMTagDelim("h", []string{"To", "From", "Subject", "Date", "Message-ID",
"MIME-Version", "Content-Type", "Content-Transfer-Encoding"}, ":")
s, ok := dt.GetString(1)
if !ok {
t.Errorf("failed to get tag-name")
}
if s != "h" {
t.Errorf("tag-name is wrong: %v", s)
}
s, ok = dt.GetString(2)
if !ok {
t.Errorf("failed to get =")
}
if s != "=" {
t.Errorf("'=' != %v", s)
}
s, ok = dt.GetString(2)
if !ok {
t.Errorf("did not get To")
}
if s != "To" {
t.Errorf("'%v' != To", s)
}
s, ok = dt.GetString(2)
if !ok {
t.Errorf("failed to get ;")
}
if s != ":" {
t.Errorf("':' != %v", s)
}
}

func TestNewDKIMTagBase64(t *testing.T) {
dt := NewDKIMTagBase64("bh", "7Xgui0yFAxLMluvjaRLRKJPgrOpPtHSIYy/BndZ2zLg=")
s, ok := dt.GetString(1)
if ok {
t.Errorf("got partial tag-name")
}
s, ok = dt.GetString(3)
if !ok {
t.Errorf("failed to get name=")
return
}
if s != "bh=" {
t.Errorf("'%v' != bh=", s)
}
s, ok = dt.GetString(5)
if !ok {
t.Errorf("failed to get b64 data")
}
if s != "7Xgui" {
t.Errorf("'%v' != 7Xgui", s)
}
s = dt.GetRemaining()
if s != "0yFAxLMluvjaRLRKJPgrOpPtHSIYy/BndZ2zLg=" {
t.Errorf("remaining is incorrect")
}
}
Loading