use keyring in verify, add beneficiary to debitcerts
[epoint] / document / document.go
1 // Package document implements epoint document parsing and creation.
2 //
3 // An epoint document is an OpenPGP (RFC 4880) clear signed
4 // utf-8 text of key-value pairs.
5 // The body contains a content-type MIME header so the document
6 // can be used in OpenPGP/MIME (RFC 3156) emails.
7 // The format of the key-value pairs are similar to MIME header
8 // fields: keys and values are separated by ": ", repeated keys
9 // are not allowed, long values can be split before a space.
10 //
11 // Example:
12 //
13 // -----BEGIN PGP SIGNED MESSAGE-----
14 // Hash: SHA1
15 //
16 // Content-Type: text/plain.epoint.type; charset=utf-8
17 //
18 // Key: Value1
19 // Another-Key: Value2
20 // Last-Key: Long
21 //  value that spans
22 //  multiple lines
23 // -----BEGIN PGP SIGNATURE-----
24 //
25 // pgp signature
26 // -----END PGP SIGNATURE-----
27 package document
28
29 // TODO: error wrapper (so reporting to user or creating bounce cert is simple)
30 // TODO: optional fields: exact semantics ("" vs "-" vs nil)
31 // TODO: trailing space handling in ParseFields
32 // TODO: fields of notice (last notice, serial, failure notice,..)
33 // TODO: limits and cert type specific input validation
34
35 import (
36         "bytes"
37         "crypto"
38         "crypto/openpgp"
39         "crypto/openpgp/armor"
40         "crypto/openpgp/packet"
41         "crypto/sha1"
42         "encoding/hex"
43         "fmt"
44         "reflect"
45         "strconv"
46         "strings"
47         "time"
48 )
49
50 // limits
51 const (
52         MaxFields             = 20
53         MaxLineLength         = 160  // 1 sha512 + 1 key (without \n)
54         MaxValueLength        = 1300 // 20 sha256 space separated (without \n)
55         MaxNonceLength        = 20
56         MaxDenominationLength = 100
57 )
58
59 const ClearSignedHeader = "-----BEGIN PGP SIGNED MESSAGE-----"
60
61 // MIME type for epoint documents, see RFC 4288
62 var ContentType = map[string]string{
63         "Draft":      "text/vnd.epoint.draft; charset=utf-8",
64         "Notice":     "text/vnd.epoint.notice; charset=utf-8",
65         "DebitCert":  "text/vnd.epoint.debit; charset=utf-8",
66         "CreditCert": "text/vnd.epoint.credit; charset=utf-8",
67         "BounceCert": "text/vnd.epoint.bounce; charset=utf-8",
68 }
69
70 // OpenPGP signed cleartext document representation
71 type Signed struct {
72         // Sign and CleanSigned sets Hash for FormatSigned
73         // TODO: CreationDate
74         Hash string
75         // Signed text (no dash escape, no trailing space, \n new lines)
76         Body []byte
77         // Armored detached text signature of the Body
78         Signature []byte
79 }
80
81 // parsed epoint document
82 type Document struct {
83         Type   string
84         Fields map[string]string
85         Order  []string
86 }
87
88 var fieldtype = map[string]string{
89         "Amount":             "int",
90         "Authorized-By":      "id",
91         "Balance":            "int",
92         "Beneficiary":        "id",
93         "Date":               "date",
94         "Debit-Cert":         "id",
95         "Denomination":       "text",
96         "Difference":         "int",
97         "Draft":              "id",
98         "Drawer":             "id",
99         "Expiry-Date":        "date",
100         "Holder":             "id",
101         "Issuer":             "id",
102         "Last-Cert":          "id",
103         "Last-Credit-Serial": "int",
104         "Last-Debit-Serial":  "int",
105         "Maturity-Date":      "date",
106         "Nonce":              "text",
107         "Notes":              "text",
108         "References":         "ids",
109         "Serial":             "int",
110 }
111
112 var fieldname = map[string]string{
113         "AuthorizedBy":     "Authorized-By",
114         "DebitCert":        "Debit-Cert",
115         "ExpiryDate":       "Expiry-Date",
116         "LastCert":         "Last-Cert",
117         "LastCreditSerial": "Last-Credit-Serial",
118         "LastDebitSerial":  "Last-Debit-Serial",
119         "MaturityDate":     "Maturity-Date",
120 }
121
122 type Draft struct {
123         Drawer       string
124         Beneficiary  string
125         Amount       int64
126         Denomination string
127         Issuer       string
128         AuthorizedBy string
129         MaturityDate *int64  // optional
130         ExpiryDate   *int64  // optional
131         Nonce        *string // optional
132         Notes        *string // optional
133 }
134
135 type Notice struct {
136         Date         int64
137         AuthorizedBy string
138         Notes        *string  // optional
139         References   []string // may be empty (startup notice)
140 }
141
142 type DebitCert struct {
143         Holder           string
144         Serial           int64
145         Balance          int64
146         Denomination     string
147         Issuer           string
148         Date             int64
149         Difference       int64
150         Draft            string
151         Beneficiary      string
152         AuthorizedBy     string
153         Notes            *string // optional
154         LastDebitSerial  int64   // 0 if none
155         LastCreditSerial int64   // 0 if none
156         LastCert         *string // ? if serial == 1
157         References       []string
158 }
159
160 type CreditCert struct {
161         Holder           string
162         Serial           int64
163         Balance          int64
164         Denomination     string
165         Issuer           string
166         Date             int64
167         Difference       int64
168         Draft            string
169         Drawer           string
170         DebitCert        string
171         AuthorizedBy     string
172         Notes            *string // optional
173         LastDebitSerial  int64   // 0 if none
174         LastCreditSerial int64   // 0 if none
175         LastCert         *string // ? if serial == 1
176         References       []string
177 }
178
179 type BounceCert struct {
180         Drawer       string
181         Draft        string
182         LastCert     *string // optional
183         Balance      int64   // 0 if none
184         Date         int64
185         AuthorizedBy string
186         Notes        *string // optional
187         References   []string
188 }
189
190 // sha1 sum of the (cleaned) document as uppercase hex string
191 func Id(d []byte) string {
192         h := sha1.New()
193         h.Write(d)
194         return fmt.Sprintf("%040X", h.Sum())
195 }
196
197 // parse an epoint document without checking the signature and format details
198 func Parse(s []byte) (iv interface{}, c *Signed, err error) {
199         c, err = ParseSigned(s)
200         if err != nil {
201                 return
202         }
203         doc, err := ParseDocument(c.Body)
204         if err != nil {
205                 return
206         }
207         iv, err = ParseStruct(doc)
208         return
209 }
210
211 // format and sign an epoint document
212 func Format(iv interface{}, key *openpgp.Entity) (s []byte, err error) {
213         doc, err := FormatStruct(iv)
214         if err != nil {
215                 return
216         }
217         body, err := FormatDocument(doc)
218         if err != nil {
219                 return
220         }
221         c, err := Sign(body, key)
222         if err != nil {
223                 return
224         }
225         return FormatSigned(c)
226 }
227
228 // verify an epoint document, return the cleaned version as well
229 func Verify(c *Signed, key openpgp.KeyRing) (cleaned []byte, err error) {
230         err = CleanSigned(c)
231         if err != nil {
232                 return
233         }
234         err = VerifyCleaned(c, key)
235         if err != nil {
236                 return
237         }
238         return FormatSigned(c)
239 }
240
241 // verify signature of body with given key
242 func VerifyCleaned(c *Signed, key openpgp.KeyRing) (err error) {
243         msg := bytes.NewBuffer(c.Body)
244         sig := bytes.NewBuffer(c.Signature)
245         _, err = openpgp.CheckArmoredDetachedSignature(key, msg, sig)
246         return
247 }
248
249 // sign body with given secret key
250 func Sign(body []byte, key *openpgp.Entity) (c *Signed, err error) {
251         c = new(Signed)
252         c.Hash = "SHA256"
253         c.Body = body
254         w := new(bytes.Buffer)
255         err = openpgp.ArmoredDetachSignText(w, key, bytes.NewBuffer(c.Body))
256         c.Signature = w.Bytes()
257         return
258 }
259
260 // split a clear signed document into body and armored signature
261 func ParseSigned(s []byte) (c *Signed, err error) {
262         // look for clear signed header
263         for !bytes.HasPrefix(s, []byte(ClearSignedHeader)) {
264                 _, s = getLine(s)
265                 if len(s) == 0 {
266                         err = fmt.Errorf("ParseSigned: clear signed header is missing")
267                         return
268                 }
269         }
270         s = s[len(ClearSignedHeader):]
271         // end of line after the header
272         empty, s := getLine(s)
273         if len(empty) != 0 {
274                 err = fmt.Errorf("ParseSigned: bad clear signed header")
275                 return
276         }
277         // skip all hash headers, section 7.
278         for bytes.HasPrefix(s, []byte("Hash: ")) {
279                 _, s = getLine(s)
280         }
281         // skip empty line
282         empty, s = getLine(s)
283         if len(empty) != 0 {
284                 err = fmt.Errorf("ParseSigned: expected an empty line after armor headers")
285                 return
286         }
287         lines := [][]byte{}
288         for !bytes.HasPrefix(s, []byte("-----BEGIN")) {
289                 var line []byte
290                 line, s = getLine(s)
291                 // dash unescape, section 7.1.
292                 if bytes.HasPrefix(line, []byte("- ")) {
293                         line = line[2:]
294                 }
295                 // empty values are not supported: "Key: \n"
296                 lines = append(lines, bytes.TrimRight(line, " \t"))
297         }
298         c = new(Signed)
299         // last line is not closed by \n
300         c.Body = bytes.Join(lines, []byte("\n"))
301         // signature is just the rest of the input data
302         c.Signature = s
303         return
304 }
305
306 // clean up, check and reencode signature
307 // used on drafts before calculating the signed document hash
308 func CleanSigned(c *Signed) (err error) {
309         b, err := armor.Decode(bytes.NewBuffer(c.Signature))
310         if err != nil {
311                 return
312         }
313         if b.Type != openpgp.SignatureType {
314                 err = fmt.Errorf("CleanSigned: invalid armored signature type")
315                 return
316         }
317         p, err := packet.Read(b.Body)
318         if err != nil {
319                 return
320         }
321         sig, ok := p.(*packet.Signature)
322         if !ok {
323                 err = fmt.Errorf("CleanSigned: invalid signature packet")
324                 return
325         }
326         // section 5.2.3
327         if sig.SigType != packet.SigTypeText {
328                 err = fmt.Errorf("CleanSigned: expected text signature")
329                 return
330         }
331         switch sig.Hash {
332         case crypto.SHA1:
333                 c.Hash = "SHA1"
334         case crypto.SHA256:
335                 c.Hash = "SHA256"
336         default:
337                 err = fmt.Errorf("CleanSigned: expected SHA1 or SHA256 signature hash")
338                 return
339         }
340         // TODO: check CreationTime and other subpackets
341         if sig.SigLifetimeSecs != nil && *sig.SigLifetimeSecs != 0 {
342                 err = fmt.Errorf("CleanSigned: signature must not expire")
343                 return
344         }
345         out := new(bytes.Buffer)
346         w, err := armor.Encode(out, openpgp.SignatureType, nil)
347         if err != nil {
348                 return
349         }
350         err = sig.Serialize(w)
351         if err != nil {
352                 return
353         }
354         err = w.Close()
355         if err != nil {
356                 return
357         }
358         c.Signature = out.Bytes()
359         return
360 }
361
362 // create clear signed document
363 func FormatSigned(c *Signed) (data []byte, err error) {
364         s := ClearSignedHeader + "\n"
365         if c.Hash != "" {
366                 s += "Hash: " + c.Hash + "\n"
367         }
368         s += "\n"
369         s += string(c.Body)
370         s += "\n"
371         s += string(c.Signature)
372         data = []byte(s)
373         return
374 }
375
376 // parse type and fields of a document body
377 func ParseDocument(body []byte) (doc *Document, err error) {
378         // parse content type header first
379         fields, s, err := ParseFields(body)
380         if err != nil {
381                 return
382         }
383         ctype, ok := fields["Content-Type"]
384         if len(fields) != 1 || !ok {
385                 return nil, fmt.Errorf("ParseBody: expected a single Content-Type header field")
386         }
387         doc = new(Document)
388         for k, v := range ContentType {
389                 if ctype == v {
390                         doc.Type = k
391                         break
392                 }
393         }
394         if doc.Type == "" {
395                 return nil, fmt.Errorf("ParseBody: unknown Content-Type: %s", ctype)
396         }
397         // TODO: doc.Order
398         doc.Fields, s, err = ParseFields(s)
399         if err == nil && len(s) > 0 {
400                 err = fmt.Errorf("ParseBody: extra data after fields: %q", s)
401         }
402         return
403 }
404
405 // create document body
406 func FormatDocument(doc *Document) (body []byte, err error) {
407         ctype, ok := ContentType[doc.Type]
408         if !ok {
409                 err = fmt.Errorf("FormatDocument: unknown document type: %s", doc.Type)
410                 return
411         }
412         s := "Content-Type: " + ctype + "\n\n"
413         for _, k := range doc.Order {
414                 s += k + ": " + doc.Fields[k] + "\n"
415         }
416         return []byte(s), nil
417 }
418
419 // parse doc fields into a struct according to the document type
420 func ParseStruct(doc *Document) (iv interface{}, err error) {
421         switch doc.Type {
422         case "Draft":
423                 iv = new(Draft)
424         case "Notice":
425                 iv = new(Notice)
426         case "DebitCert":
427                 iv = new(DebitCert)
428         case "CreditCert":
429                 iv = new(CreditCert)
430         case "BounceCert":
431                 iv = new(BounceCert)
432         default:
433                 err = fmt.Errorf("ParseStruct: unkown doc type: %s", doc.Type)
434                 return
435         }
436         seen := make(map[string]bool)
437         v := reflect.ValueOf(iv).Elem()
438         t := v.Type()
439         n := v.NumField()
440         nokey := 0
441         for i := 0; i < n; i++ {
442                 ft := t.Field(i)
443                 fv := v.Field(i)
444                 key := fieldname[ft.Name]
445                 if key == "" {
446                         key = ft.Name
447                 }
448                 seen[key] = true
449                 s, ok := doc.Fields[key]
450                 if !ok {
451                         if fv.Kind() == reflect.Ptr {
452                                 // missing optional key: leave the pointer as nil
453                                 nokey++
454                                 continue
455                         }
456                         return nil, fmt.Errorf("ParseStruct: field %s of %s is missing\n", key, t.Name())
457                 }
458                 if fv.Kind() == reflect.Ptr {
459                         if s == "" || s == "-" {
460                                 // TODO
461                                 // empty optional key: same as missing
462                                 continue
463                         }
464                         fv.Set(reflect.New(fv.Type().Elem()))
465                         fv = fv.Elem()
466                 }
467                 switch fieldtype[key] {
468                 case "id":
469                         var val string
470                         val, err = parseId(s)
471                         fv.SetString(val)
472                 case "text":
473                         var val string
474                         val, err = parseString(s)
475                         fv.SetString(val)
476                 case "int":
477                         var val int64
478                         val, err = strconv.Atoi64(s)
479                         fv.SetInt(val)
480                 case "date":
481                         var val int64
482                         val, err = parseDate(s)
483                         fv.SetInt(val)
484                 case "ids":
485                         // TODO: empty slice?
486                         ids := strings.Split(s, " ")
487                         val := make([]string, len(ids))
488                         for j, id := range ids {
489                                 val[j], err = parseId(id)
490                                 if err != nil {
491                                         return
492                                 }
493                         }
494                         fv.Set(reflect.ValueOf(val))
495                 default:
496                         panic("bad field type " + key + " " + fieldtype[key])
497                 }
498                 if err != nil {
499                         return
500                 }
501         }
502         if len(doc.Fields)+nokey != n {
503                 for k := range doc.Fields {
504                         if !seen[k] {
505                                 err = fmt.Errorf("ParseStruct: unknown field %s in %s", k, t.Name())
506                                 return
507                         }
508                 }
509         }
510         return
511 }
512
513 // turn a struct into a document
514 func FormatStruct(iv interface{}) (doc *Document, err error) {
515         v := reflect.ValueOf(iv)
516         if v.Kind() != reflect.Ptr || v.IsNil() || v.Elem().Kind() != reflect.Struct {
517                 panic("input is not a pointer to struct")
518         }
519         v = v.Elem()
520         t := v.Type()
521         n := v.NumField()
522         doc = new(Document)
523         doc.Type = t.Name()
524         doc.Fields = make(map[string]string)
525         for i := 0; i < n; i++ {
526                 ft := t.Field(i)
527                 fv := v.Field(i)
528                 key := fieldname[ft.Name]
529                 if key == "" {
530                         key = ft.Name
531                 }
532                 val := ""
533                 if fv.Kind() == reflect.Ptr {
534                         if fv.IsNil() {
535                                 // keep empty optional fields but mark them
536                                 val = "-"
537                                 goto setval
538                         }
539                         fv = fv.Elem()
540                 }
541                 switch fieldtype[key] {
542                 case "id":
543                         val = formatId(fv.String())
544                 case "text":
545                         val = formatString(fv.String())
546                 case "int":
547                         val = strconv.Itoa64(fv.Int())
548                 case "date":
549                         val = formatDate(fv.Int())
550                 case "ids":
551                         k := fv.Len()
552                         for j := 0; j < k; j++ {
553                                 if j > 0 {
554                                         val += "\n "
555                                 }
556                                 val += formatId(fv.Index(j).String())
557                         }
558                 default:
559                         panic("bad field type " + key + " " + fieldtype[key])
560                 }
561         setval:
562                 doc.Fields[key] = val
563                 doc.Order = append(doc.Order, key)
564         }
565         return
566 }
567
568 func ParseFields(s []byte) (fields map[string]string, rest []byte, err error) {
569         rest = s
570         fields = make(map[string]string)
571         key := ""
572         // \n is optional after the last field and an extra \n is allowed as well
573         for len(rest) > 0 {
574                 var line []byte
575                 line, rest = getLine(rest)
576                 // empty line after the last field is consumed
577                 if len(line) == 0 {
578                         break
579                 }
580                 if line[0] == ' ' && key != "" {
581                         // "Key: v1\n v2\n" is equivalent to "Key: v1 v2\n"
582                         fields[key] += string(line)
583                         continue
584                 }
585                 if line[0] < 'A' || line[0] > 'Z' {
586                         err = fmt.Errorf("ParseFields: field name must start with an upper-case ascii letter")
587                         return
588                 }
589                 i := bytes.IndexByte(line, ':')
590                 if i < 0 {
591                         err = fmt.Errorf("ParseFields: missing ':'")
592                         return
593                 }
594                 key = string(line[:i])
595                 if _, ok := fields[key]; ok {
596                         err = fmt.Errorf("ParseFields: repeated fields are not allowed")
597                         return
598                 }
599                 fields[key] = string(line[i+1:])
600         }
601         for key, v := range fields {
602                 // either a single space follows ':' or the value is empty
603                 // good: "Key:\n", "Key:\n value\n", "Key: value\n", "Key: v1\n v2\n"
604                 // bad: "Key:value\n", "Key: \nvalue\n"
605                 // bad but not checked here: "Key: \n", "Key: value \n", "Key:\n \n value\n"
606                 if len(v) == 0 {
607                         continue
608                 }
609                 if v[0] != ' ' {
610                         err = fmt.Errorf("ParseFields: ':' is not followed by ' '")
611                         return
612                 }
613                 fields[key] = v[1:]
614         }
615         return
616 }
617
618 // TODO: limit errors
619
620 func parseId(s string) (string, error) {
621         // check if hex decodable
622         // TODO: length check
623         dst := make([]byte, len(s)/2)
624         _, err := hex.Decode(dst, []byte(s))
625         return s, err
626 }
627
628 func formatId(s string) string {
629         return s
630 }
631
632 func parseString(s string) (string, error) {
633         if len(s) > MaxValueLength {
634                 return "", fmt.Errorf("parseString: length limit is exceeded")
635         }
636         return s, nil
637 }
638
639 func formatString(s string) string {
640         return s
641 }
642
643 func parseDate(s string) (int64, error) {
644         // TODO: fractional seconds?
645         t, err := time.Parse(time.RFC3339, s)
646         if err != nil {
647                 return 0, err
648         }
649         return t.Seconds(), nil
650 }
651
652 func formatDate(i int64) string {
653         return time.SecondsToUTC(i).Format(time.RFC3339)
654 }
655
656 func getLine(data []byte) (line, rest []byte) {
657         i := bytes.IndexByte(data, '\n')
658         j := i + 1
659         if i < 0 {
660                 i = len(data)
661                 j = i
662         } else if i > 0 && data[i-1] == '\r' {
663                 i--
664         }
665         return data[:i], data[j:]
666 }