From 002a229c5df5b025e6e35b7acc72aa28ba9ae46c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96zg=C3=BCr=20Kesim?= Date: Mon, 18 Oct 2021 14:15:01 +0200 Subject: [PATCH] milestone 1: parsing of input.json seems to work --- cmd/taler-auditor-offline-signing/main.go | 300 +++++++++++++++++++--- go.mod | 2 + go.sum | 2 + 3 files changed, 264 insertions(+), 40 deletions(-) create mode 100644 go.sum diff --git a/cmd/taler-auditor-offline-signing/main.go b/cmd/taler-auditor-offline-signing/main.go index f9050e1..bbab9fb 100644 --- a/cmd/taler-auditor-offline-signing/main.go +++ b/cmd/taler-auditor-offline-signing/main.go @@ -1,35 +1,94 @@ +/* +taler-auditor-offline will become a standalone offline signing tool for GNU +Taler. It is an implementation in Go of +https://git.taler.net/exchange.git/tree/src/exchange-tools/taler-auditor-offline.c +in order to simplify portability (to ARM f.e.). + +TODOs: + - implement sign operation + - implement JSON-encoding of + - hashes + - keys + - signatures + - factor out types and helper functions to own package codeblau.de/taler + - implement full functionality from origin + +*/ package main import ( + "bytes" + "crypto/ed25519" "crypto/rsa" - "encoding/base32" "encoding/binary" + "encoding/json" "fmt" + "log" "math/big" + "os" + "regexp" + "strconv" + + "github.com/davecgh/go-spew/spew" ) -type Keys struct { - Denoms []DenomKey `json:"denoms"` - MasterPublicKey MasterPublicKey `json:"master_public_key"` +type Data struct { + Operation string `json:"operation"` + Arguments struct { + Version string `json:"version"` + Currency string `json:"currency"` + MasterPublicKey EdDSAPublicKey `json:"master_public_key"` + ReserveClosingDelay RelativeTime `json:"reserve_closing_delay"` + Signkeys []SignKey `json:"signkeys"` + Denoms []DenomKey `json:"denoms"` + // Recoup []??? `json:"recoup"` + } `json:"arguments"` } -type MasterPublicKey [256 / 8]byte +type SignKey struct { + MasterSig EdDSASignature `json:"master_sig"` + StampStart AbsoluteTime `json:"stamp_start"` + StampExpire AbsoluteTime `json:"stamp_expire"` + StampEnd AbsoluteTime `json:"stamp_end"` + Key EdDSAPublicKey `json:"key"` +} type DenomKey struct { - DenomPub EncodedRSAPublicKey `json:"denom_pub"` - Value Amount `json:"value"` - FeeWithdraw Amount `json:"fee_withdraw"` - FeeDeposit Amount `json:"fee_deposit"` - FeeRefresh Amount `json:"fee_refresh"` - FeeRefund Amount `json:"fee_refund"` - StampStart AbsoluteTime `json:"stamp_start"` - StampExpireWithdraw AbsoluteTime `json:"stamp_expire_withdraw"` - StampExpireDeposit AbsoluteTime `json:"stamp_expire_deposit"` - StampExpireLegal AbsoluteTime `json:"stamp_expire_legal"` - MasterSig EdDSASignature `json:"master_sig"` + DenomPub RSAPublicKey `json:"denom_pub"` + Value Amount `json:"value"` + FeeWithdraw Amount `json:"fee_withdraw"` + FeeDeposit Amount `json:"fee_deposit"` + FeeRefresh Amount `json:"fee_refresh"` + FeeRefund Amount `json:"fee_refund"` + StampStart AbsoluteTime `json:"stamp_start"` + StampExpireWithdraw AbsoluteTime `json:"stamp_expire_withdraw"` + StampExpireDeposit AbsoluteTime `json:"stamp_expire_deposit"` + StampExpireLegal AbsoluteTime `json:"stamp_expire_legal"` + MasterSig EdDSASignature `json:"master_sig"` } -type EncodedRSAPublicKey []byte +type AbsoluteTime struct { + // TODO: en-/decode "never" + TMs uint64 `json:"t_ms"` +} + +type RelativeTime struct { + // TODO: en-/decode "forever" + DMs uint64 `json:"d_ms"` +} + +type EdDSAPublicKey ed25519.PublicKey + +func (ep *EdDSAPublicKey) UnmarshalJSON(in []byte) (e error) { + var buf []byte + // decode crockford.base32 + if buf, e = crockfordDecode(bytes.Trim(in, `"`)); e != nil { + return fmt.Errorf("couldn't decode EncodedRSAPublicKey as crockford.base32: %v (%v)", e, string(in)) + } + + *ep = EdDSAPublicKey(buf) + return nil +} const ( CURRENCY_LEN = 12 @@ -37,55 +96,216 @@ const ( ) type Amount struct { - Value uint64 `json:"value"` - Fraction uint32 `json:"fraction"` - Currency [CURRENCY_LEN]byte `json:"currency"` + Value uint64 `json:"value"` + Fraction uint32 `json:"fraction"` + Currency string `json:"currency"` } -type AbsoluteTime uint64 +// Amount comes in as something like "CHF:0.32" +// see https://docs.taler.net/core/api-common.html#amounts +var nonAllowedCharsRX = regexp.MustCompile("[^a-zA-Z]") + +func (a *Amount) UnmarshalJSON(in []byte) (e error) { + // split and parse Currency + parts := bytes.Split(bytes.Trim(in, `"`), []byte(":")) + if len(parts) != 2 { + return fmt.Errorf("invalid amount sequence") + } else if len(parts[0]) >= CURRENCY_LEN || nonAllowedCharsRX.Match(parts[0]) { + return fmt.Errorf("invalid currency") + } + a.Currency = string(parts[0]) + + // split and parse Value + parts = bytes.Split(parts[1], []byte(".")) + if len(parts) > 2 || len(parts[0]) == 0 { + return fmt.Errorf("invalid decimal value") + } else if a.Value, e = strconv.ParseUint(string(parts[0]), 10, 64); e != nil { + return e + } else if a.Value > 2<<52 { + return fmt.Errorf("decimal value too large") + } + + // parse Fraction + if len(parts) == 2 { + if len(parts[1]) == 0 || len(parts[1]) > 8 { + return fmt.Errorf("invalid fraction") + } else if v, e := strconv.ParseUint(string(parts[1]), 10, 32); e != nil { + return e + } else { + a.Fraction = uint32(v) + } + } + + return nil +} type EdDSASignature struct { - R [256 / 8]byte `json:"r"` - S [256 / 8]byte `json:"s"` + R []byte `json:"r"` + S []byte `json:"s"` } -// following gnunet/src/json/json_helper.c and gnunet/src/util/crypto_rsa.c -func (ep *EncodedRSAPublicKey) Decode() (p rsa.PublicKey, e error) { +// copy of GNUNET_STRINGS_string_to_data from gnunet/src/util/strings.c +func crockfordDecode(enc []byte) ([]byte, error) { + if len(enc) == 0 { + return nil, nil + } + + out_size := (len(enc) * 5) / 8 + if out_size > 2<<24 { + return nil, fmt.Errorf("input too large") + } + + var ( + out = make([]byte, out_size) + encoded_len = out_size * 8 + rpos = len(enc) + vbit, bits, ret, shift int + ) + + if encoded_len%5 > 0 { + vbit = encoded_len % 5 // padding! !? + shift = 5 - vbit + rpos-- + ret = getValue(enc[rpos]) + bits = ret >> shift + } else { + vbit = 5 + shift = 0 + rpos-- + ret = getValue(enc[rpos]) + bits = ret + } + + if ((encoded_len + shift) / 5) != len(enc) { + return nil, fmt.Errorf("encoding length mismatch: %d vs %d (%v)", (encoded_len+shift)/5, len(enc), enc) + } else if ret == -1 { + return nil, fmt.Errorf("invalid character? getValue returned -1") + } + + for wpos := out_size; wpos > 0; { + if 0 == rpos { + return nil, fmt.Errorf("incomplete encoding") + } + rpos-- + ret = getValue(enc[rpos]) << vbit + bits = ret | bits + if -1 == ret { + return nil, fmt.Errorf("incorrect encoding") + } + vbit += 5 + if vbit >= 8 { + wpos-- + out[wpos] = byte(bits) + bits >>= 8 + vbit -= 8 + } + } + + if 0 != rpos || 0 != vbit { + return nil, fmt.Errorf("incorrectly ended") + } + + return out, nil +} + +func getValue(a byte) int { + // exluded letters + switch a { + case '0', 'o': + a = '0' + case 'i', 'I', 'l', 'L': + a = '1' + case 'u', 'U': + a = 'V' + } + + if a >= '0' && a <= '9' { + return int(a - '0') + } + if a >= 'a' && a <= 'z' { + a = a - 'a' + 'A' + } + + dec := 0 + if a >= 'A' && a <= 'Z' { + if 'I' < a { + dec++ + } + if 'L' < a { + dec++ + } + if 'O' < a { + dec++ + } + if 'U' < a { + dec++ + } + return int(a) - 'A' + 10 - dec + } + + return -1 +} + +func (es *EdDSASignature) UnmarshalJSON(in []byte) (e error) { var buf []byte - // 1. decode base32 - _, e = base32.StdEncoding.Decode(buf, *ep) - if e != nil { - return + // 1. decode crockford.base32 + if buf, e = crockfordDecode(bytes.Trim(in, `"`)); e != nil { + return fmt.Errorf("couldn't decode EdDSASignature as crockford.base32: %v (%v)", e, string(in)) + } else if len(buf) != 64 { + return fmt.Errorf("unknown data as signature: %v", buf) + } + + es.R = buf[0:32] + es.S = buf[32:64] + + return nil +} + +type RSAPublicKey rsa.PublicKey + +// following gnunet/src/json/json_helper.c and gnunet/src/util/crypto_rsa.c +func (ep *RSAPublicKey) UnmarshalJSON(in []byte) (e error) { + var buf []byte + + // 1. decode crockford.base32 + if buf, e = crockfordDecode(bytes.Trim(in, `"`)); e != nil { + return fmt.Errorf("couldn't decode EncodedRSAPublicKey as crockford.base32: %v (%v)", e, string(in)) } // 2. parse header if len(buf) < 4 { - e = fmt.Errorf("byte array too small for RSA public key header") - return + return fmt.Errorf("byte array too small for RSA public key header") } modulus_length, public_exponent_length := binary.BigEndian.Uint16(buf[0:]), binary.BigEndian.Uint16(buf[2:]) if len(buf[4:]) != int(modulus_length)+int(public_exponent_length) { - e = fmt.Errorf("byte array has wrong size according to encoded length's for modulus and public exponent") - return + return fmt.Errorf("byte array has wrong size according to encoded length's for modulus and public exponent") } // 3. parse RSA public key - // BUG! This is most likely wrong. // Consult _gcry_mpi_set_buffer from libgcrypt-1.9.4/mpi/mpicoder.c buf = buf[4:] - p.N = big.NewInt(0).SetBytes(buf[:modulus_length]) + ep.N = big.NewInt(0).SetBytes(buf[:modulus_length]) buf = buf[modulus_length:] ex := big.NewInt(0).SetBytes(buf[:public_exponent_length]) // binary.BigEndian.Uint64 instead? if !ex.IsInt64() { - e = fmt.Errorf("public exponent is not int64") - return + return fmt.Errorf("public exponent is not int64") } - p.E = int(ex.Int64()) + ep.E = int(ex.Int64()) - return p, e + return nil } func main() { - panic("nothing implemented yet.") + data := new(Data) + dec := json.NewDecoder(os.Stdin) + e := dec.Decode(data) + if e != nil { + log.Fatal(e) + } + + spew.Dump(data) + enc := json.NewEncoder(os.Stdout) + enc.SetIndent("Data:", " ") + enc.Encode(data) } diff --git a/go.mod b/go.mod index 66c8161..25da996 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,5 @@ module codeblau.de/taler go 1.17 + +require github.com/davecgh/go-spew v1.1.1 // indirect diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..b5e2922 --- /dev/null +++ b/go.sum @@ -0,0 +1,2 @@ +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=