taldir

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

commit f5e140b41c1697f4b013dda1f1c1f7a3caec17fa
parent 41a3a5ecc97b942c463a8b8539ffd6930d3fc9fe
Author: Martin Schanzenbach <mschanzenbach@posteo.de>
Date:   Tue, 19 Apr 2022 14:34:20 +0200

Cleanup code. Hash identities

Diffstat:
Mconfig.json | 3++-
Mgo.mod | 1+
Mtaldir.go | 127+++++++++++++++++++++++++++++++++++++++++++++++++++----------------------------
3 files changed, 85 insertions(+), 46 deletions(-)

diff --git a/config.json b/config.json @@ -2,5 +2,6 @@ "production": false, "db_backend": "sqlite", "validators": ["email","phone"], - "email_sender": "taldir@taler.net" + "email_sender": "taldir@taler.net", + "host": "https://taldir.net/" } diff --git a/go.mod b/go.mod @@ -6,6 +6,7 @@ require ( github.com/gorilla/mux v1.8.0 // indirect github.com/jinzhu/now v1.1.5 // indirect github.com/mattn/go-sqlite3 v1.14.12 // indirect + golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 // indirect gorm.io/driver/sqlite v1.3.1 // indirect gorm.io/gorm v1.23.4 // indirect ) diff --git a/taldir.go b/taldir.go @@ -12,41 +12,51 @@ import ( "encoding/base32" "math/rand" "net/smtp" + "golang.org/x/crypto/argon2" + "crypto/sha256" ) -type Validator interface { - TriggerValidation() string -} - type Configuration struct { Production bool DbBackend string `json:"db_backend"` Validators []string EmailSender string `json:"email_sender"` + Salt string `json:"salt"` + Host string `json:"host"` } +// A mappind entry from the identity key hash to a wallet key +// The identity key hash is argon2(sha256(identity)) where identity is +// one of the identity key types supported (e.g. email) type Entry struct { gorm.Model - IdentityKey string `json:"identity_key"` - IdentityKeyType string `json:"identity_key_type"` + IdentityKeyHash string `json:"identity_key_hash"` TalerWalletKey string `json:"taler_wallet_key"` - Status string `json:"status"` } +// A validation is created when a registration for an entry is initiated. +// The validation stores the identity key (sha256(identity)) the secret +// validation reference. The validation reference is sent to the identity +// depending on the out-of-band chennel defined through the identity key type. type Validation struct { gorm.Model IdentityKey string `json:"identity_key"` - IdentityKeyType string `json:"type"` + IdentityKeyType string `json:"identity_key_type"` ValidationReference string `json:"reference"` + TalerWalletKey string `json:"taler_wallet_key"` } -var Entries []Entry - +// The main DB handle var db *gorm.DB +// Our configuration from the config.json var config Configuration +// Map of supported validators as defined in the configuration +var validators map[string]bool + +// Send an email for email identities func sendEmail(recipient string, ref Validation) { from := config.EmailSender @@ -57,9 +67,9 @@ func sendEmail(recipient string, ref Validation) { smtpHost := "localhost" smtpPort := "587" - message := []byte("Please click here to validate your Taldir identity: " + ref.ValidationReference) + message := fmt.Sprintf("Please click here to validate your Taldir identity: %s%s", config.Host, ref.ValidationReference) - err := smtp.SendMail(smtpHost+":"+smtpPort, nil, from, to, message) + err := smtp.SendMail(smtpHost+":"+smtpPort, nil, from, to, []byte(message)) if err != nil { fmt.Println(err) return @@ -70,7 +80,8 @@ func sendEmail(recipient string, ref Validation) { func returnSingleEntry(w http.ResponseWriter, r *http.Request){ vars := mux.Vars(r) var entry Entry - var err = db.First(&entry, "identity_key = ?", vars["identity_key"]).Error + identityKeyHash := hashIdentityKey(vars["identity_key"]) + var err = db.First(&entry, "identity_key_hash = ?", identityKeyHash).Error if err == nil { w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(entry) @@ -79,23 +90,41 @@ func returnSingleEntry(w http.ResponseWriter, r *http.Request){ w.WriteHeader(http.StatusNotFound) } +func hashIdentityKey(idkey string) string { + salt := make([]byte, len(config.Salt)) + return base32.StdEncoding.EncodeToString(argon2.IDKey([]byte(idkey), salt, 1, 64*1024, 4, 32)) +} + func validateSingleEntry(w http.ResponseWriter, r *http.Request){ vars := mux.Vars(r) var entry Entry - var ref Validation + var validation Validation //TODO actually validate - var err = db.First(&ref, "validation_reference = ?", vars["reference"]).Error + var err = db.First(&validation, "validation_reference = ?", vars["reference"]).Error if err != nil { w.WriteHeader(http.StatusNotFound) } - err = db.First(&entry, "identity_key = ?", entry.IdentityKey).Error + err = db.First(&validation, "identity_key = ?", validation.IdentityKey).Error if err != nil { w.WriteHeader(http.StatusNotFound) } - err = db.Model(&entry).Update("status", "validated").Error + err = db.Delete(&validation).Error if err != nil { w.WriteHeader(http.StatusInternalServerError) } + entry.IdentityKeyHash = hashIdentityKey(validation.IdentityKey) + entry.TalerWalletKey = validation.TalerWalletKey + err = db.First(&entry, "identity_key_hash = ?", entry.IdentityKeyHash).Error + if err == nil { + w.WriteHeader(http.StatusConflict) + return + } + err = db.Create(&entry).Error + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + return + } + w.WriteHeader(http.StatusCreated) } func generateToken() string { @@ -107,45 +136,54 @@ func generateToken() string { return base32.StdEncoding.EncodeToString(randBytes) } -func addSingleEntry(w http.ResponseWriter, r *http.Request){ - var entry Entry +func addPendingValidation(w http.ResponseWriter, r *http.Request){ + vars := mux.Vars(r) + var validation Validation if r.Body == nil { http.Error(w, "No request body", 400) return } - err := json.NewDecoder(r.Body).Decode(&entry) + err := json.NewDecoder(r.Body).Decode(&validation) if err != nil { http.Error(w, err.Error(), 400) return } - ref := Validation{IdentityKey: entry.IdentityKey, ValidationReference: generateToken() } - fmt.Println("Got ID key:", entry.IdentityKey) - err = db.First(&entry, "identity_key = ?", entry.IdentityKey).Error - if err == nil { - w.WriteHeader(http.StatusConflict) + fmt.Println(validators) + fmt.Println(validation) + if !validators[validation.IdentityKeyType] { + http.Error(w, "Identity key type not supported.", 400) return } - entry.Status = "unvalidated" - err = db.Create(&entry).Error - if err != nil { - w.WriteHeader(http.StatusInternalServerError) + // TODO make sure sha256(vars["identity"]) == validation.IdentityKey + h := sha256.New() + h.Write([]byte(vars["identity"])) + identityKey := base32.StdEncoding.EncodeToString(h.Sum(nil)) + if (identityKey != validation.IdentityKey) { + fmt.Printf("Identity key hash %s does not match identity %s\n", identityKey, validation.IdentityKey) + w.WriteHeader(http.StatusBadRequest) + return + } + validation.ValidationReference = generateToken() + fmt.Println("Got ID key:", validation.IdentityKey) + err = db.First(&validation, "identity_key = ?", validation.IdentityKey).Error + if err == nil { + w.WriteHeader(http.StatusConflict) return } - err = db.Create(&ref).Error + err = db.Create(&validation).Error if err != nil { w.WriteHeader(http.StatusInternalServerError) return } - fmt.Println("Validation reference created:", ref) - sendEmail(ref.IdentityKey, ref) - json.NewEncoder(w).Encode(entry) + fmt.Println("Pending validation created:", validation) + sendEmail(vars["identity"], validation) } func handleRequests() { myRouter := mux.NewRouter().StrictSlash(true) - myRouter.HandleFunc("/directory/{id}", returnSingleEntry).Methods("GET") - myRouter.HandleFunc("/validation/{reference}", validateSingleEntry).Methods("POST") - myRouter.HandleFunc("/directory/", addSingleEntry).Methods("POST") + myRouter.HandleFunc("/directory/{identity_key}", returnSingleEntry).Methods("GET") + myRouter.HandleFunc("/validation/{reference}", validateSingleEntry).Methods("GET") + myRouter.HandleFunc("/register/{identity}", addPendingValidation).Methods("POST") log.Fatal(http.ListenAndServe(":10000", myRouter)) } @@ -158,7 +196,14 @@ func main() { if err != nil { fmt.Println("error:", err) } - fmt.Println("Configuration:", config) + if config.Production { + fmt.Println("Production mode enabled") + } + validators = make(map[string]bool) + fmt.Println("Enabled validators:", config.Validators) + for _, a := range config.Validators { + validators[a] = true + } _db, err := gorm.Open(sqlite.Open("./taldir.db"), &gorm.Config{}) if err != nil { panic(err) @@ -170,13 +215,5 @@ func main() { if err := db.AutoMigrate(&Validation{}); err != nil { panic(err) } - var entry Entry; - db.Create(&Entry{IdentityKey: "jdoe@example.com", IdentityKeyType: "email", TalerWalletKey: "OIU123"}) - db.Create(&Entry{IdentityKey: "+12345678", IdentityKeyType: "phone", TalerWalletKey: "OIU123"}) handleRequests() - db.First(&entry, "identity_key = ?", "jdoe@example.com") - db.Delete(&entry) - db.First(&entry, "identity_key = ?", "+12345678") - db.Delete(&entry) - }