afbe31c3fe99166554c3a49ff5225718b728ed52
[epoint] / document / document.go
1 package document
2
3 // An epoint document is a clear signed utf-8 text of key-value pairs
4 // according to OpenPGP RFC 4880. The body contains a content-type
5 // MIME header so it can be used in OpenPGP/MIME RFC 3156 emails with
6 // the signature detached. The format of the key-value pairs are
7 // similar to MIME header fields.
8 //
9 // Example:
10 //
11 // -----BEGIN PGP SIGNED MESSAGE-----
12 // Hash: SHA1
13 //
14 // Content-Type: text/plain.epoint.cert; charset=utf-8
15 //
16 // Key: Value1
17 // Another-Key: Value2
18 // -----BEGIN PGP SIGNATURE-----
19 // pgp signature
20 // -----END PGP SIGNATURE-----
21
22 import (
23         "bytes"
24         "encoding/hex"
25         "fmt"
26         "reflect"
27         "strconv"
28         "strings"
29         "time"
30 )
31
32 const ClearSignedHeader = "-----BEGIN PGP SIGNED MESSAGE-----\n"
33
34 // (non-standard) MIME subtype for epoint documents, see RFC 2045 and RFC 2046
35 var ContentType = map[string]string{
36         "cert":  "text/plain.epoint.cert; charset=utf-8",
37         "draft": "text/plain.epoint.draft; charset=utf-8",
38 }
39
40 // OpenPGP signed cleartext document representation
41 type ClearSigned struct {
42         Hash string
43         // Signed text (no dash escape, no trailing space)
44         Body []byte
45         // Armored detached text signature of the Body
46         ArmoredSignature []byte
47 }
48
49 type Field struct {
50         Key   string
51         Value string
52 }
53
54 // Draft document represents an obligation transfer order
55 type Draft struct {
56         Drawer       string `marshal:"id"` // ID of the payer (signer of the document)
57         Beneficiary  string `marshal:"id"` // ID of the payee
58         Amount       int64  // amount transfered
59         Denomination string
60         IssueDate    int64 `marshal:"date" key:"Issue-Date"`
61         // Draft is bounced before this date
62         MaturityDate int64  `marshal:"date" key:"Maturity-Date"`
63         Notes        string // Arbitrary text notes of the drawer
64         Nonce        string // unique number
65         Server       string `marshal:"id"` // ID of the server
66         Drawee       string `marshal:"id"` // ID of the obligation issuer
67         // useful if more strict date of issue information is needed
68         //References []string
69 }
70
71 // Obligation certificate after a transfer
72 // References previous certificate (if any)
73 // and the transfer related other documents
74 type Cert struct {
75         Holder           string `marshal:"id"` // ID of the creditor
76         Serial           uint32 // serial number, number of certs of the holder
77         Date             int64  `marshal:"date"` // date of issue
78         Balance          int64  // current obligation value
79         Denomination     string
80         Issuer           string   `marshal:"id"`                 // ID of the obligation issuer (drawee?)
81         LastDebitSerial  uint32   `key:"Last-Debit-Serial"`      // serial of the last draft cert or 0
82         LastCreditSerial uint32   `key:"Last-Credit-Serial"`     // serial of the last credit cert or 0
83         LastCert         string   `marshal:"id" key:"Last-Cert"` // ID of the previous cert if any
84         Difference       int64    // difference from previous balance
85         Draft            string   `marshal:"id"`                   // draft ID related to the transfer
86         Drawer           string   `marshal:"id"`                   // ID of the drawer in the transaction
87         DrawerSerial     uint32   `key:"Drawer-Serial"`            // serial of the drawer's related debit cert
88         DrawerCert       string   `marshal:"id" key:"Drawer-Cert"` // ID of the drawer's related debit cert
89         References       []string `marshal:"idlist"`               // cert IDs for timestamping the system
90 }
91
92 func DecodeClearSigned(s []byte) (c *ClearSigned, err error) {
93         hash, body, sig := split(s)
94         if len(sig) == 0 {
95                 err = fmt.Errorf("DecodeClearSigned could parse the signed document")
96                 return
97         }
98         c = &ClearSigned{string(hash), trimspace(dashunesc(body)), sig}
99         return
100 }
101
102 func EncodeClearSigned(c *ClearSigned) (data []byte, err error) {
103         s := ClearSignedHeader
104         if c.Hash != "" {
105                 s += "Hash: " + c.Hash + "\n"
106         }
107         // TODO: check if space was trimmed from body before signature
108         s += "\n"
109         s += string(dashesc(c.Body))
110         s += "\n"
111         s += string(c.ArmoredSignature)
112         data = []byte(s)
113         return
114 }
115
116 func ParseFields(s []byte) (fields []Field, rest []byte, err error) {
117         rest = s
118         for len(rest) > 0 {
119                 var line []byte
120                 line, rest = getLine(rest)
121                 // empty line after the parsed fields (consumed)
122                 if len(line) == 0 {
123                         break
124                 }
125                 i := bytes.Index(line, []byte(": "))
126                 if i < 0 {
127                         err = fmt.Errorf("ParseFields: missing ': '\n")
128                         break
129                 }
130                 fields = append(fields, Field{string(line[:i]), string(line[i+2:])})
131         }
132         return
133 }
134
135 func ParseBody(s []byte) (doctype string, fields []Field, err error) {
136         // parse content type header first
137         fs, s, err := ParseFields(s)
138         if err != nil {
139                 return
140         }
141         if len(fs) != 1 || fs[0].Key != "Content-Type" {
142                 return "", nil, fmt.Errorf("ParseBody: single Content-Type header was expected\n")
143         }
144         ctype := fs[0].Value
145         for k, v := range ContentType {
146                 if ctype == v {
147                         doctype = k
148                         break
149                 }
150         }
151         if doctype == "" {
152                 return "", nil, fmt.Errorf("ParseBody: unknown Content-Type: %s", ctype)
153         }
154         fields, s, err = ParseFields(s)
155         if err == nil && len(s) > 0 {
156                 err = fmt.Errorf("ParseBody: extra data after fields: %q", s)
157         }
158         return
159 }
160
161 func parse(s []byte, doctype string) (v interface{}, err error) {
162         t, fields, err := ParseBody(s)
163         if err != nil {
164                 return
165         }
166         if doctype != t && doctype != "" {
167                 err = fmt.Errorf("parse: expected doctype %s; got %s", doctype, t)
168                 return
169         }
170         switch t {
171         case "draft":
172                 v = new(Draft)
173         case "cert":
174                 v = new(Cert)
175         default:
176                 err = fmt.Errorf("parse: unkown doc type: %s", t)
177                 return
178         }
179         err = unmarshal(fields, v)
180         return
181 }
182
183 // TODO: limit errors
184 func render(v interface{}) ([]byte, error) {
185         doctype := ""
186         switch v.(type) {
187         case *Draft:
188                 doctype = "draft"
189         case *Cert:
190                 doctype = "cert"
191         default:
192                 panic("reder: unknown type")
193         }
194         s := "Content-Type: " + ContentType[doctype] + "\n\n"
195         fields := marshal(v)
196         for _, f := range fields {
197                 s += f.Key + ": " + f.Value + "\n"
198         }
199         return []byte(s), nil
200 }
201
202 func ParseDraft(s []byte) (draft *Draft, err error) {
203         v, err := parse(s, "draft")
204         if err != nil {
205                 return
206         }
207         draft = v.(*Draft)
208         return
209 }
210
211 func RenderDraft(draft *Draft) ([]byte, error) {
212         return render(draft)
213 }
214
215 func ParseCert(s []byte) (cert *Cert, err error) {
216         v, err := parse(s, "cert")
217         if err != nil {
218                 return nil, err
219         }
220         cert = v.(*Cert)
221         return
222 }
223
224 func RenderCert(cert *Cert) ([]byte, error) {
225         return render(cert)
226 }
227
228 func formatId(s string) string {
229         return fmt.Sprintf("%040X", s)
230 }
231
232 func parseId(s string) (string, error) {
233         dst := make([]byte, 20)
234         if len(s) != 40 {
235                 return "", fmt.Errorf("parseId: expected 40 characters; got %d", len(s))
236         }
237         _, err := hex.Decode(dst, []byte(s))
238         return string(dst), err
239 }
240
241 func quoteValue(s string) string {
242         return s
243 }
244
245 func unquoteValue(s string) (string, error) {
246         return s, nil
247 }
248
249 func formatDate(i int64) string {
250         return time.SecondsToUTC(i).Format(time.RFC3339)
251 }
252
253 func parseDate(s string) (int64, error) {
254         t, err := time.Parse(time.RFC3339, s)
255         if err != nil {
256                 return 0, err
257         }
258         return t.Seconds(), nil
259 }
260
261 func marshal(iv interface{}) []Field {
262         v := reflect.ValueOf(iv)
263         if v.Kind() != reflect.Ptr || v.IsNil() || v.Elem().Kind() != reflect.Struct {
264                 panic("unmarshal: input is not a pointer to struct")
265         }
266         v = v.Elem()
267         t := v.Type()
268         n := v.NumField()
269         fields := []Field{}
270         for i := 0; i < n; i++ {
271                 ft := t.Field(i)
272                 fv := v.Field(i)
273                 m := ft.Tag.Get("marshal")
274                 k := ft.Tag.Get("key")
275                 if k == "" {
276                         k = ft.Name
277                 }
278                 val := ""
279                 switch fv.Kind() {
280                 case reflect.String:
281                         switch m {
282                         case "id":
283                                 val = formatId(fv.String())
284                         case "":
285                                 val = quoteValue(fv.String())
286                         default:
287                                 panic("bad string field tag")
288                         }
289                 case reflect.Int, reflect.Int32, reflect.Int64:
290                         switch m {
291                         case "date":
292                                 val = formatDate(fv.Int())
293                         case "":
294                                 val = strconv.Itoa64(fv.Int())
295                         default:
296                                 panic("bad int field tag")
297                         }
298                 case reflect.Uint, reflect.Uint32, reflect.Uint64:
299                         switch m {
300                         case "":
301                                 val = strconv.Uitoa64(fv.Uint())
302                         default:
303                                 panic("bad uint field tag")
304                         }
305                 case reflect.Slice:
306                         switch m {
307                         case "idlist":
308                                 if fv.Type().Elem().Kind() != reflect.String {
309                                         panic("only string slice is supported")
310                                 }
311                                 k := fv.Len()
312                                 for j := 0; j < k; j++ {
313                                         if j > 0 {
314                                                 val += " "
315                                         }
316                                         val += formatId(fv.Index(j).String())
317                                 }
318                         default:
319                                 panic("bad slice field tag")
320                         }
321                 default:
322                         panic("bad field type")
323                 }
324                 fields = append(fields, Field{k, val})
325         }
326         return fields
327 }
328
329 func unmarshal(fields []Field, iv interface{}) error {
330         v := reflect.ValueOf(iv)
331         if v.Kind() != reflect.Ptr || v.IsNil() || v.Elem().Kind() != reflect.Struct {
332                 panic("unmarshal: input is not a pointer to struct")
333         }
334         v = v.Elem()
335         t := v.Type()
336         n := v.NumField()
337         if len(fields) != n {
338                 return fmt.Errorf("unmarshal: %s has %d fields, got %d\n", t.Name(), n, len(fields))
339         }
340         for i := 0; i < n; i++ {
341                 ft := t.Field(i)
342                 fv := v.Field(i)
343                 m := ft.Tag.Get("marshal")
344                 k := ft.Tag.Get("key")
345                 if k == "" {
346                         k = ft.Name
347                 }
348                 if fields[i].Key != k {
349                         return fmt.Errorf("unmarshal: field %d of %s (%s) is missing\n", i, t.Name(), k)
350                 }
351                 s := fields[i].Value
352                 var err error
353                 switch fv.Kind() {
354                 case reflect.String:
355                         var val string
356                         switch m {
357                         case "id":
358                                 val, err = parseId(s)
359                         case "":
360                                 val, err = unquoteValue(s)
361                         default:
362                                 panic("bad string field tag")
363                         }
364                         fv.SetString(val)
365                 case reflect.Int, reflect.Int32, reflect.Int64:
366                         var val int64
367                         switch m {
368                         case "date":
369                                 val, err = parseDate(s)
370                         case "":
371                                 val, err = strconv.Atoi64(s)
372                         default:
373                                 panic("bad int field tag")
374                         }
375                         fv.SetInt(val)
376                 case reflect.Uint, reflect.Uint32, reflect.Uint64:
377                         var val uint64
378                         switch m {
379                         case "":
380                                 val, err = strconv.Atoui64(s)
381                         default:
382                                 panic("bad uint field tag")
383                         }
384                         fv.SetUint(val)
385                 case reflect.Slice:
386                         var val []string
387                         switch m {
388                         case "idlist":
389                                 if fv.Type().Elem().Kind() != reflect.String {
390                                         panic("only string slice is supported")
391                                 }
392                                 ids := strings.Split(s, " ")
393                                 val = make([]string, len(ids))
394                                 for j := range val {
395                                         val[j], err = parseId(ids[j])
396                                         if err != nil {
397                                                 return err
398                                         }
399                                 }
400                         default:
401                                 panic("bad slice field tag")
402                         }
403                         fv.Set(reflect.ValueOf(val))
404                 default:
405                         panic("bad field type")
406                 }
407                 if err != nil {
408                         return err
409                 }
410         }
411         return nil
412 }
413
414 func getLine(data []byte) (line, rest []byte) {
415         i := bytes.Index(data, []byte{'\n'})
416         j := i + 1
417         if i < 0 {
418                 i = len(data)
419                 j = i
420         } else if i > 0 && data[i-1] == '\r' {
421                 i--
422         }
423         return data[:i], data[j:]
424 }
425
426 func trimspace(s []byte) []byte {
427         a := bytes.Split(s, []byte("\n"))
428         for i := range a {
429                 a[i] = bytes.TrimRight(a[i], " \t\r")
430         }
431         return bytes.Join(a, []byte("\n"))
432 }
433
434 func dashesc(s []byte) []byte {
435         r := bytes.Replace(s, []byte("\n-"), []byte("\n- -"), -1)
436         if len(r) > 0 && r[0] == '-' {
437                 r = append([]byte("- "), r...)
438         }
439         return r
440 }
441
442 func dashunesc(s []byte) []byte {
443         r := bytes.Replace(s, []byte("\n- "), []byte("\n"), -1)
444         if len(r) >= 2 && r[0] == '-' && r[1] == ' ' {
445                 r = r[2:]
446         }
447         return r
448 }
449
450 // RFC 4880 is unclear about multiple Hash header semantics, section 7. says
451 //  "One or more "Hash" Armor Headers"
452 // then
453 //  "If more than one message digest is used in the signature, the "Hash"
454 //   armor header contains a comma-delimited list of used message digests."
455 // in section 6.2.
456 //  "there is no limit to the length of Armor Headers.  Care should
457 //   be taken that the Armor Headers are short enough to survive
458 //   transport.  One way to do this is to repeat an Armor Header key
459 //   multiple times with different values for each so that no one line is
460 //   overly long."
461 // we accept a single Hash header with a list of hash algorithms for now
462 // but use the one specified by the signature
463
464 func split(s []byte) (hash, body, sig []byte) {
465         if !bytes.HasPrefix(s, []byte(ClearSignedHeader)) {
466                 return
467         }
468         s = s[len(ClearSignedHeader):]
469         // only allow a single Hash: header
470         if bytes.HasPrefix(s, []byte("Hash: ")) {
471                 s = s[len("Hash: "):]
472                 hash, s = getLine(s)
473         }
474         // skip empty line
475         empty, s := getLine(s)
476         if len(empty) != 0 {
477                 return
478         }
479         i := bytes.Index(s, []byte("\n-----BEGIN"))
480         if i < 0 {
481                 return
482         }
483         body, sig = s[:i], s[i+1:]
484         if i > 0 && body[i-1] == '\r' {
485                 body = body[:i-1]
486         }
487         return
488 }