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