milestone 1: parsing of input.json seems to work
This commit is contained in:
parent
9ce706684c
commit
002a229c5d
@ -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)
|
||||
}
|
||||
|
2
go.mod
2
go.mod
@ -1,3 +1,5 @@
|
||||
module codeblau.de/taler
|
||||
|
||||
go 1.17
|
||||
|
||||
require github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
|
Loading…
Reference in New Issue
Block a user