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