// 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"
"Last-Credit-Serial": "int",
"Last-Debit-Serial": "int",
"Maturity-Date": "date",
- "Nonce": "text",
+ "Nonce": "id",
"Notes": "text",
"References": "ids",
"Serial": "int",
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
}
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 {
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())
}
}
// 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
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
}
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
}
}
// 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
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
}
}
}
// 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
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)