taldir

Directory service to resolve wallet mailboxes by messenger addresses
Log | Files | Refs | Submodules | README | LICENSE

commit f01dddf4da0689dc612b6a8b7cda332e0d2a3568
parent 6fd9c1815caefdf8d66dfe03d7e5430e73db6eb3
Author: Martin Schanzenbach <schanzen@gnunet.org>
Date:   Wed,  6 Jul 2022 14:20:14 +0200

forgot to add files; update base32; fix solution calculation

Diffstat:
Mtaldir.go | 66++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------
Atemplates/validation_landing.html | 17+++++++++++++++++
Mtest.sh | 12+++++++-----
Autil/base32.go | 127+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 207 insertions(+), 15 deletions(-)

diff --git a/taldir.go b/taldir.go @@ -3,16 +3,18 @@ package main import ( "os" "os/exec" + "bufio" "time" "fmt" "log" + "flag" "net/http" "html/template" "encoding/json" "github.com/gorilla/mux" "gorm.io/gorm" - "encoding/base32" "encoding/base64" + "taler.net/taldir/util" "math/rand" "crypto/sha512" "gorm.io/driver/postgres" @@ -101,7 +103,7 @@ type Validation struct { Method string `json:"method"` Duration int64 `json:"duration"` Inbox string `json:"inbox_url"` - Solution string `json:"solution"` + Code string `json:"activation_code"` PublicKey string `json:"public_key"` } @@ -184,11 +186,10 @@ func saltHAddress(h_address string) string { if "" == salt { salt = cfg.Section("taldir").Key("salt").MustString("ChangeMe") } - fmt.Println("Using salt " + salt) h := sha512.New() h.Write([]byte(h_address)) h.Write([]byte(salt)) - return base32.StdEncoding.EncodeToString(h.Sum(nil)) + return util.EncodeBinaryToString(h.Sum(nil)) } // Called by the registrant to validate the registration request. The reference ID was @@ -217,7 +218,8 @@ func validationRequest(w http.ResponseWriter, r *http.Request){ w.WriteHeader(http.StatusNotFound) return } - if confirm.Solution != validation.Solution { + expectedSolution := generateSolution(validation.PublicKey, validation.Code) + if confirm.Solution != expectedSolution { // FIXME how TF do we rate limit here?? w.WriteHeader(http.StatusForbidden) return @@ -237,7 +239,6 @@ func validationRequest(w http.ResponseWriter, r *http.Request){ err = db.First(&entry, "hs_address = ?", entry.HsAddress).Error if err == nil { db.Save(&entry) - return } else { err = db.Create(&entry).Error if err != nil { @@ -256,7 +257,7 @@ func generateToken() string { if err != nil { panic(err) } - return base32.StdEncoding.EncodeToString(randBytes) + return util.EncodeBinaryToString(randBytes) } func registerRequest(w http.ResponseWriter, r *http.Request){ @@ -289,7 +290,7 @@ func registerRequest(w http.ResponseWriter, r *http.Request){ } h := sha512.New() h.Write([]byte(req.Address)) - validation.HAddress = base32.StdEncoding.EncodeToString(h.Sum(nil)) + validation.HAddress = util.EncodeBinaryToString(h.Sum(nil)) // We first try if there is already an entry for this address which // is still valid and the duration is not extended. hs_address := saltHAddress(validation.HAddress) @@ -325,7 +326,7 @@ func registerRequest(w http.ResponseWriter, r *http.Request){ w.WriteHeader(202) return } else { - validation.Solution = generateToken() + validation.Code = generateToken() validation.Inbox = req.Inbox validation.Duration = req.Duration validation.PublicKey = req.PublicKey @@ -346,7 +347,7 @@ func registerRequest(w http.ResponseWriter, r *http.Request){ return } command := cfg.Section("taldir-" + vars["method"]).Key("command").String() - out, err := exec.Command(command, req.Address, validation.Solution).Output() + out, err := exec.Command(command, req.Address, validation.Code).Output() if err != nil { log.Fatal(err) db.Delete(&validation) @@ -403,6 +404,20 @@ func validationPage(w http.ResponseWriter, r *http.Request) { return } +// Generates a solution from a code and pubkey +func generateSolution(pubkeyEncoded string, code string) string { + pubkey, err := util.DecodeStringToBinary(pubkeyEncoded, 36) + if err != nil { + fmt.Println("error decoding pubkey:", err) + return "" + } + h := sha512.New() + h.Write([]byte(code)) + h.Write(pubkey) + return util.EncodeBinaryToString(h.Sum(nil)) +} + + func handleRequests() { myRouter := mux.NewRouter().StrictSlash(true) @@ -435,6 +450,19 @@ func main() { if cfg.Section("taldir").Key("production").MustBool(false) { fmt.Println("Production mode enabled") } + var solveFlag = flag.Bool("s", false, "Provide a solution for the code/pubkey") + var codeFlag = flag.String("c", "", "Activation code") + var pubkeyFlag = flag.String("p", "", "Public key") + var dropFlag = flag.Bool("D", false, "Drop all data in table (DANGEROUS!)") + flag.Parse() + if *solveFlag { + if len(*codeFlag) == 0 || len(*pubkeyFlag) == 0 { + fmt.Println("You need to provide an activation code and a public key to generate a solution") + os.Exit(1) + } + fmt.Println(generateSolution(*pubkeyFlag, *codeFlag)) + os.Exit(0) + } validators = make(map[string]bool) for _, a := range strings.Split(cfg.Section("taldir").Key("validators").String(), " ") { validators[a] = true @@ -456,6 +484,24 @@ func main() { if err := db.AutoMigrate(&Validation{}); err != nil { panic(err) } + if *dropFlag { + fmt.Println("Really delete all data in database? [y/N]:") + reader := bufio.NewReader(os.Stdin) + char, _, err := reader.ReadRune() + + if err == nil { + fmt.Println(char) + if char == 'y' { + fmt.Println("Deleting entries...") + db.Where("1 = 1").Delete(&Entry{}) + fmt.Println("Deleting validations...") + db.Where("1 = 1").Delete(&Validation{}) + } + os.Exit(0) + } + os.Exit(1) + } + validationTpl, err = template.ParseFiles("templates/validation_landing.html") if err != nil { fmt.Println(err) diff --git a/templates/validation_landing.html b/templates/validation_landing.html @@ -0,0 +1,17 @@ +<!DOCTYPE html> +<html lang="en"> + <head> + <!-- Required meta tags --> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> + <title>Validation Landing Page</title> + </head> + <body> + <div class="container"> + <h1>Scan this QR code with your Taler Wallet to complete your registration.</h1> + <a href="{{.WalletLink}}"> + <img src="{{.QRCode}}"/> + </a> + </div> + </body> +</html> diff --git a/test.sh b/test.sh @@ -1,13 +1,15 @@ #!/bin/bash # New request -curl -v localhost:11000/register/test --data '{"address": "abc@test", "public_key": "pkey", "inbox_url": "myinbox@xyz", "duration": 23}' +PUBKEY="000G006XE97PTWV3B7AJNCRQZA6BF26HPV3XZ07293FMY7KD4181946A90" +curl -v localhost:11000/register/test --data "{\"address\": \"abc@test\", \"public_key\": \"${PUBKEY}\", \"inbox_url\": \"myinbox@xyz\", \"duration\": 23}" # Read validation code from tempfile CODE=`cat validation_code` -H_ADDRESS=`echo -n abc@test | openssl dgst -binary -sha512 | base32 -w0` +H_ADDRESS=`echo -n abc@test | openssl dgst -binary -sha512 | gnunet-base32` echo "Code: $CODE; Address: $H_ADDRESS" # Validate -echo localhost:11000/register/$H_ADDRESS/$CODE -exit -curl -v localhost:11000/$H_ADDRESS --data "{\"solution\": \"${CODE}\"}" +# echo localhost:11000/register/$H_ADDRESS/$CODE +SOLUTION=$(./taldir -s -c ${CODE} -p ${PUBKEY}) +echo "Solution: $SOLUTION" +curl -v localhost:11000/$H_ADDRESS --data "{\"solution\": \"${SOLUTION}\"}" # Get mapping curl -v localhost:11000/$H_ADDRESS diff --git a/util/base32.go b/util/base32.go @@ -0,0 +1,127 @@ +// This file is part of gnunet-go, a GNUnet-implementation in Golang. +// Copyright (C) 2019-2022 Bernd Fix >Y< +// +// gnunet-go is free software: you can redistribute it and/or modify it +// under the terms of the GNU Affero General Public License as published +// by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// gnunet-go is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// +// SPDX-License-Identifier: AGPL3.0-or-later + +package util + +import ( + "errors" + "strings" +) + +//------------------------------------------------------------------------ +// Base32 conversion between binary data and string representation +//------------------------------------------------------------------------ +// +// A binary array of size m is viewed as a consecutive stream of bits +// from left to right. Bytes are ordered with ascending address, while +// bits (in a byte) are ordered MSB to LSB. + +// For encoding the stream is partitioned into 5-bit chunks; the last chunk +// is right-padded with 0's if 8*m is not divisible by 5. Each chunk (value +// between 0 and 31) is encoded into a character; the mapping for encoding +// is the same as in [https://www.crockford.com/wrmg/base32.html]. +// +// For decoding each character is converted to a 5-bit chunk based on the +// encoder mapping (with one addition: the character 'U' maps to the value +// 27). The chunks are concatenated to produce the bit stream to be stored +// in the output array. + +// character set used for encoding/decoding +const xlate = "0123456789ABCDEFGHJKMNPQRSTVWXYZ" + +var ( + // ErrInvalidEncoding signals an invalid encoding + ErrInvalidEncoding = errors.New("Invalid encoding") + // ErrBufferTooSmall signalsa too small buffer for decoding + ErrBufferTooSmall = errors.New("Buffer to small") +) + +// EncodeBinaryToString encodes a byte array into a string. +func EncodeBinaryToString(data []byte) string { + size, pos, bits, n := len(data), 0, 0, 0 + out := "" + for { + if n < 5 { + if pos < size { + bits = (bits << 8) | (int(data[pos]) & 0xFF) + pos++ + n += 8 + } else if n > 0 { + bits <<= uint(5 - n) + n = 5 + } else { + break + } + } + out += string(xlate[(bits>>uint(n-5))&0x1F]) + n -= 5 + } + return out +} + +// DecodeStringToBinary decodes a string into a byte array. +// The function expects the size of the output buffer to be sepcified as an +// argument ('num'); the function returns an error if the buffer is overrun +// or if an invalid character is found in the encoded string. If the decoded +// bit stream is smaller than the output buffer, it is padded with 0's. +func DecodeStringToBinary(s string, num int) ([]byte, error) { + size := len(s) + out := make([]byte, num) + rpos, wpos, n, bits := 0, 0, 0, 0 + for { + if n < 8 { + if rpos < size { + c := rune(s[rpos]) + rpos++ + v := strings.IndexRune(xlate, c) + if v == -1 { + switch c { + case 'O': + v = 0 + case 'I', 'L': + v = 1 + case 'U': + v = 27 + default: + return nil, ErrInvalidEncoding + } + } + bits = (bits << 5) | (v & 0x1F) + n += 5 + } else { + if wpos < num { + out[wpos] = byte(bits & ((1 << uint(n+1)) - 1)) + wpos++ + for i := wpos; i < num; i++ { + out[i] = 0 + } + } + break + } + } else { + if wpos < num { + out[wpos] = byte((bits >> uint(n-8)) & 0xFF) + wpos++ + n -= 8 + } else { + return nil, ErrBufferTooSmall + } + } + } + return out, nil +}