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