auditor/cmd/taler-auditor-offline-signing/main.go

410 lines
9.5 KiB
Go

/*
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/binary"
"encoding/json"
"fmt"
"log"
"math/big"
"os"
"regexp"
"strconv"
)
type Input 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 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 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 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
}
func (ep *EdDSAPublicKey) MarshalJSON() ([]byte, error) {
enc, e := crockfordEncode([]byte(*ep))
if e != nil {
return nil, e
}
buf := make([]byte, len(enc)+2)
buf[0] = '"'
buf[len(buf)-1] = '"'
copy(buf[1:], enc)
return buf, nil
}
const (
CURRENCY_LEN = 12
CURRENCY_LEN_STR = "12"
)
type Amount struct {
Value uint64 `json:"value"`
Fraction uint32 `json:"fraction"`
Currency string `json:"currency"`
}
// 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 []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
}
// Copy of GNUNET_STRINGS_data_to_string from gnunet/src/util/strings.c
func crockfordEncode(in []byte) ([]byte, error) {
const encTable = "0123456789ABCDEFGHJKMNPQRSTVWXYZ"
var (
out []byte
rpos, bits, vbit int
)
for rpos < len(in) || vbit > 0 {
if rpos < len(in) && vbit < 5 {
bits = (bits << 8) | int(in[rpos]) // eat 8 more bits
rpos++
vbit += 8
}
if vbit < 5 {
bits <<= (5 - vbit) // zero-padding
if vbit != (len(in)*8)%5 {
return nil, fmt.Errorf("vbit (%d) != (len(in)*8)%%5 (%d)", vbit, (len(in)*8)%5)
}
vbit = 5
}
vbit -= 5
out = append(out, encTable[(bits>>vbit)&31])
}
return out, nil
}
func (es *EdDSASignature) UnmarshalJSON(in []byte) (e error) {
var buf []byte
// Decode crockford.base32, the Taler flavour
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
}
func (es *EdDSASignature) MarshalJSON() (b []byte, e error) {
var buf = make([]byte, len(es.R)+len(es.S))
copy(buf, es.R)
copy(buf[len(es.R):], es.S)
enc, err := crockfordEncode(buf)
if err != nil {
return nil, err
}
b = make([]byte, len(enc)+2)
b[0] = '"'
b[len(b)-1] = '"'
copy(b[1:], enc)
return b, 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, the Taler flavour
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 {
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) {
return fmt.Errorf("byte array has wrong size according to encoded length's for modulus and public exponent")
}
// 3. parse RSA public key
// Consult _gcry_mpi_set_buffer from libgcrypt-1.9.4/mpi/mpicoder.c
buf = buf[4:]
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() {
return fmt.Errorf("public exponent is not int64")
}
ep.E = int(ex.Int64())
return nil
}
func (ep *RSAPublicKey) MarshalJSON() (b []byte, e error) {
nb := ep.N.Bytes()
eb := big.NewInt(int64(ep.E)).Bytes()
if len(nb) > 2<<16-1 || len(eb) > 2<<16-1 {
return nil, fmt.Errorf("values too large")
}
buf := make([]byte, 4+len(nb)+len(eb))
binary.BigEndian.PutUint16(buf, uint16(len(nb)))
binary.BigEndian.PutUint16(buf[2:], uint16(len(eb)))
copy(buf[4:], nb)
copy(buf[4+len(nb):], eb)
enc, err := crockfordEncode(buf)
if err != nil {
return nil, err
}
b = make([]byte, len(enc)+2)
b[0] = '"'
b[len(b)-1] = '"'
copy(b[1:], enc)
return b, nil
}
type Output []SignOperation
type SignOperation struct {
Operation string `json:"operation"`
Arguments struct {
HDenumPub RSAPublicKey `json:"h_denum_pub"`
AuditorSig EdDSASignature `json:"auditor_sig"`
} `json:"arguments"`
}
func main() {
input := new(Input)
dec := json.NewDecoder(os.Stdin)
e := dec.Decode(input)
if e != nil {
log.Fatal(e)
}
enc := json.NewEncoder(os.Stdout)
enc.SetIndent("Input:", " ")
e = enc.Encode(input)
if e != nil {
log.Fatal(e)
}
}