X-Git-Url: http://nsz.repo.hu/git/?a=blobdiff_plain;f=document%2Fdocument.go;h=0c8b0f4a0a94d1219f70f2ddf5b8906572cb1c46;hb=34d06eb5c368465927c780c21d6deac1c7f847b8;hp=e2a08599c0316cf18a9e8388f2479d278b971888;hpb=90b0c664371e5c26560412ae004e0c5e01a77bca;p=epoint diff --git a/document/document.go b/document/document.go index e2a0859..0c8b0f4 100644 --- a/document/document.go +++ b/document/document.go @@ -31,6 +31,9 @@ package document // TODO: trailing space handling in ParseFields // TODO: fields of notice (last notice, serial, failure notice,..) // TODO: limits and cert type specific input validation +// TODO: fix Cert mess +// TODO: nonce is id, id is even number of hex digits (require only drawer.nonce to be uniq) +// TODO: denom, issuer from key (key representation: armor?) import ( "bytes" @@ -103,7 +106,7 @@ var fieldtype = map[string]string{ "Last-Credit-Serial": "int", "Last-Debit-Serial": "int", "Maturity-Date": "date", - "Nonce": "text", + "Nonce": "id", "Notes": "text", "References": "ids", "Serial": "int", @@ -126,9 +129,9 @@ type Draft struct { Denomination string Issuer string AuthorizedBy string - MaturityDate *int64 // optional - ExpiryDate *int64 // optional - Nonce *string // optional + MaturityDate *int64 // optional + ExpiryDate *int64 // optional + Nonce string Notes *string // optional } @@ -139,41 +142,32 @@ type Notice struct { References []string // may be empty (startup notice) } -type DebitCert struct { +type Cert struct { Holder string Serial int64 Balance int64 Denomination string Issuer string Date int64 - Difference int64 - Draft string - Beneficiary string AuthorizedBy string Notes *string // optional LastDebitSerial int64 // 0 if none LastCreditSerial int64 // 0 if none - LastCert *string // ? if serial == 1 + LastCert *string // nil if serial == 1 References []string + Difference int64 + Draft string +} + +type DebitCert struct { + Cert + Beneficiary string } type CreditCert struct { - Holder string - Serial int64 - Balance int64 - Denomination string - Issuer string - Date int64 - Difference int64 - Draft string + Cert Drawer string DebitCert string - AuthorizedBy string - Notes *string // optional - LastDebitSerial int64 // 0 if none - LastCreditSerial int64 // 0 if none - LastCert *string // ? if serial == 1 - References []string } type BounceCert struct { @@ -187,10 +181,32 @@ type BounceCert struct { References []string } -// sha1 sum of the (cleaned) document as uppercase hex string -func Id(d []byte) string { +func ToCert(v interface{}) (cert *Cert, err error) { + cert = new(Cert) + switch x := v.(type) { + case *DebitCert: + cert = &x.Cert + case *CreditCert: + cert = &x.Cert + default: + err = fmt.Errorf("ToCert: only debit or credit document can be converted to cert") + } + return +} + +func cleanBody(s []byte) []byte { + nl := []byte{'\n'} + a := bytes.Split(s, nl) + for i := range a { + a[i] = bytes.TrimRight(a[i], " \t") + } + return bytes.Join(a, nl) +} + +// sha1 sum of the (cleaned) document body as uppercase hex string +func Id(c *Signed) string { h := sha1.New() - h.Write(d) + h.Write(c.Body) return fmt.Sprintf("%040X", h.Sum()) } @@ -209,7 +225,7 @@ func Parse(s []byte) (iv interface{}, c *Signed, err error) { } // format and sign an epoint document -func Format(iv interface{}, key *openpgp.Entity) (s []byte, err error) { +func Format(iv interface{}, key *openpgp.Entity) (s []byte, c *Signed, err error) { doc, err := FormatStruct(iv) if err != nil { return @@ -218,31 +234,21 @@ func Format(iv interface{}, key *openpgp.Entity) (s []byte, err error) { if err != nil { return } - c, err := Sign(body, key) + c, err = Sign(body, key) if err != nil { return } - return FormatSigned(c) + s, err = FormatSigned(c) + return } // verify an epoint document, return the cleaned version as well -func Verify(c *Signed, key openpgp.KeyRing) (cleaned []byte, err error) { - err = CleanSigned(c) - if err != nil { - return - } - err = VerifyCleaned(c, key) - if err != nil { - return - } - return FormatSigned(c) -} - -// verify signature of body with given key -func VerifyCleaned(c *Signed, key openpgp.KeyRing) (err error) { +func Verify(c *Signed, key openpgp.KeyRing) (err error) { msg := bytes.NewBuffer(c.Body) sig := bytes.NewBuffer(c.Signature) - _, err = openpgp.CheckArmoredDetachedSignature(key, msg, sig) + // TODO: verify signature + _, _ = msg, sig + // _, err = openpgp.CheckArmoredDetachedSignature(key, msg, sig) return } @@ -250,9 +256,14 @@ func VerifyCleaned(c *Signed, key openpgp.KeyRing) (err error) { func Sign(body []byte, key *openpgp.Entity) (c *Signed, err error) { c = new(Signed) c.Hash = "SHA256" - c.Body = body + c.Body = cleanBody(body) w := new(bytes.Buffer) err = openpgp.ArmoredDetachSignText(w, key, bytes.NewBuffer(c.Body)) + if err != nil { + return + } + // close armored document with a \n + _, _ = w.Write([]byte{'\n'}) c.Signature = w.Bytes() return } @@ -417,44 +428,29 @@ func FormatDocument(doc *Document) (body []byte, err error) { } // parse doc fields into a struct according to the document type -func ParseStruct(doc *Document) (iv interface{}, err error) { - switch doc.Type { - case "Draft": - iv = new(Draft) - case "Notice": - iv = new(Notice) - case "DebitCert": - iv = new(DebitCert) - case "CreditCert": - iv = new(CreditCert) - case "BounceCert": - iv = new(BounceCert) - default: - err = fmt.Errorf("ParseStruct: unkown doc type: %s", doc.Type) - return - } - seen := make(map[string]bool) - v := reflect.ValueOf(iv).Elem() +func parseStruct(v reflect.Value, fields map[string]string, seen map[string]bool) (err error) { t := v.Type() n := v.NumField() - nokey := 0 - for i := 0; i < n; i++ { + for i := 0; i < n && err == nil; i++ { ft := t.Field(i) fv := v.Field(i) + if ft.Anonymous && fv.Kind() == reflect.Struct { + err = parseStruct(fv, fields, seen) + continue + } key := fieldname[ft.Name] if key == "" { key = ft.Name } - seen[key] = true - s, ok := doc.Fields[key] + s, ok := fields[key] if !ok { if fv.Kind() == reflect.Ptr { // missing optional key: leave the pointer as nil - nokey++ continue } - return nil, fmt.Errorf("ParseStruct: field %s of %s is missing\n", key, t.Name()) + return fmt.Errorf("ParseStruct: field %s of %s is missing\n", key, t.Name()) } + seen[key] = true if fv.Kind() == reflect.Ptr { if s == "" || s == "-" { // TODO @@ -495,14 +491,35 @@ func ParseStruct(doc *Document) (iv interface{}, err error) { default: panic("bad field type " + key + " " + fieldtype[key]) } - if err != nil { - return - } } - if len(doc.Fields)+nokey != n { - for k := range doc.Fields { - if !seen[k] { - err = fmt.Errorf("ParseStruct: unknown field %s in %s", k, t.Name()) + return +} + +func ParseStruct(doc *Document) (iv interface{}, err error) { + switch doc.Type { + case "Draft": + iv = new(Draft) + case "Notice": + iv = new(Notice) + case "DebitCert": + iv = new(DebitCert) + case "CreditCert": + iv = new(CreditCert) + case "BounceCert": + iv = new(BounceCert) + default: + err = fmt.Errorf("ParseStruct: unkown doc type: %s", doc.Type) + return + } + seen := make(map[string]bool) + err = parseStruct(reflect.ValueOf(iv).Elem(), doc.Fields, seen) + if err != nil { + return + } + if len(doc.Fields) != len(seen) { + for f := range doc.Fields { + if !seen[f] { + err = fmt.Errorf("ParseStruct: unknown field %s in %s", f, doc.Type) return } } @@ -511,20 +528,19 @@ func ParseStruct(doc *Document) (iv interface{}, err error) { } // turn a struct into a document -func FormatStruct(iv interface{}) (doc *Document, err error) { - v := reflect.ValueOf(iv) - if v.Kind() != reflect.Ptr || v.IsNil() || v.Elem().Kind() != reflect.Struct { - panic("input is not a pointer to struct") - } - v = v.Elem() +func formatStruct(v reflect.Value, doc *Document) (err error) { t := v.Type() n := v.NumField() - doc = new(Document) - doc.Type = t.Name() - doc.Fields = make(map[string]string) for i := 0; i < n; i++ { ft := t.Field(i) fv := v.Field(i) + if ft.Anonymous && fv.Kind() == reflect.Struct { + err = formatStruct(fv, doc) + if err != nil { + return + } + continue + } key := fieldname[ft.Name] if key == "" { key = ft.Name @@ -565,6 +581,19 @@ func FormatStruct(iv interface{}) (doc *Document, err error) { return } +// turn a struct into a document +func FormatStruct(iv interface{}) (doc *Document, err error) { + v := reflect.ValueOf(iv) + if v.Kind() != reflect.Ptr || v.IsNil() || v.Elem().Kind() != reflect.Struct { + panic("input is not a pointer to struct") + } + doc = new(Document) + doc.Type = v.Elem().Type().Name() + doc.Fields = make(map[string]string) + err = formatStruct(v.Elem(), doc) + return +} + func ParseFields(s []byte) (fields map[string]string, rest []byte, err error) { rest = s fields = make(map[string]string)