aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorÖzgür Kesim <oec@codeblau.de>2021-10-18 14:15:01 +0200
committerÖzgür Kesim <oec@codeblau.de>2021-10-18 14:15:01 +0200
commit002a229c5df5b025e6e35b7acc72aa28ba9ae46c (patch)
treee60d758664c624396c769106a3badbbe94e7a3c1
parent9ce706684c88e1b168d9636c42f6258bd7f0cece (diff)
milestone 1: parsing of input.json seems to work
-rw-r--r--cmd/taler-auditor-offline-signing/main.go298
-rw-r--r--go.mod2
-rw-r--r--go.sum2
3 files changed, 263 insertions, 39 deletions
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 AbsoluteTime struct {
+ // TODO: en-/decode "never"
+ TMs uint64 `json:"t_ms"`
+}
+
+type RelativeTime struct {
+ // TODO: en-/decode "forever"
+ DMs uint64 `json:"d_ms"`
}
-type EncodedRSAPublicKey []byte
+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"`
}
+// 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 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 *EncodedRSAPublicKey) Decode() (p rsa.PublicKey, e error) {
+func (ep *RSAPublicKey) 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 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=