taler-mailbox

Service for asynchronous wallet-to-wallet payment messages
Log | Files | Refs | Submodules | README | LICENSE

commit 2077f42893ef52129bde0cc1d8d6721c6a25ec67
parent 7959f0dc744553d32170b47959da8912b0b886f0
Author: Martin Schanzenbach <schanzen@gnunet.org>
Date:   Wed,  5 Nov 2025 13:07:55 +0100

Add key directory and tests

Diffstat:
Mcmd/mailbox-server/main.go | 2+-
Mcmd/mailbox-server/main_test.go | 89++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------
Minternal/gana/taler_error_codes.go | 182++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----
Minternal/gana/taler_signatures.go | 6++++++
Mpkg/rest/mailbox.go | 140+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
5 files changed, 388 insertions(+), 31 deletions(-)

diff --git a/cmd/mailbox-server/main.go b/cmd/mailbox-server/main.go @@ -38,7 +38,7 @@ var ( ltversion string version string mailboxdatahome string - mailboxconfdir string + mailboxconfdir string verbose bool ) diff --git a/cmd/mailbox-server/main_test.go b/cmd/mailbox-server/main_test.go @@ -13,6 +13,7 @@ import ( "os" "strings" "testing" + "time" "github.com/schanzen/taler-go/pkg/merchant" talerutil "github.com/schanzen/taler-go/pkg/util" @@ -25,9 +26,9 @@ import ( var a mailbox.Mailbox -var testWalletAlicePriv ed25519.PrivateKey -var testWalletAlice ed25519.PublicKey -var testWalletAliceString string +var testAliceSigningKeyPriv ed25519.PrivateKey +var testAliceSigningKey ed25519.PublicKey +var testAliceHashedSigningKeyString string const merchantConfigResponse = `{ "currency": "KUDOS", @@ -112,10 +113,10 @@ func TestMain(m *testing.M) { Db: db, Merchant: merch, Ini: cfg}) - testWalletAlice, testWalletAlicePriv, _ = ed25519.GenerateKey(nil) + testAliceSigningKey, testAliceSigningKeyPriv, _ = ed25519.GenerateKey(nil) h := sha512.New() - h.Write(testWalletAlice) - testWalletAliceString = util.Base32CrockfordEncode(h.Sum(nil)) + h.Write(testAliceSigningKey) + testAliceHashedSigningKeyString = util.Base32CrockfordEncode(h.Sum(nil)) a.Merchant = merchant.NewMerchant(merchServer.URL, "") @@ -127,7 +128,7 @@ func TestMain(m *testing.M) { func TestEmptyMailbox(t *testing.T) { a.Db.Where("1 = 1").Delete(&mailbox.InboxEntry{}) - req, _ := http.NewRequest("GET", "/"+testWalletAliceString, nil) + req, _ := http.NewRequest("GET", "/"+testAliceHashedSigningKeyString, nil) response := executeRequest(req) checkResponseCode(t, http.StatusNoContent, response.Code) @@ -141,7 +142,7 @@ func TestEmptyMailbox(t *testing.T) { func TestPostMessage(t *testing.T) { testMessage := make([]byte, 256) a.Db.Where("1 = 1").Delete(&mailbox.InboxEntry{}) - req, _ := http.NewRequest("POST", "/"+testWalletAliceString, bytes.NewReader(testMessage)) + req, _ := http.NewRequest("POST", "/"+testAliceHashedSigningKeyString, bytes.NewReader(testMessage)) response := executeRequest(req) checkResponseCode(t, http.StatusNoContent, response.Code) @@ -152,6 +153,60 @@ func TestPostMessage(t *testing.T) { } } +func TestGetKeysEmpty(t *testing.T) { + req, _ := http.NewRequest("GET", "/keys/"+testAliceHashedSigningKeyString, nil) + response := executeRequest(req) + checkResponseCode(t, http.StatusNotFound, response.Code) +} + +func TestPostKeys(t *testing.T) { + var msg mailbox.KeyUpdateRequest + // Dummy pubkey + encKey := make([]byte, 32) + aliceSigningKey := util.Base32CrockfordEncode(testAliceSigningKey) + msg.Keys.EncryptionKey = util.Base32CrockfordEncode(encKey) + msg.Keys.EncryptionKeyType = "X25519" + msg.Keys.Expiration = uint64(time.Now().UnixMicro()) + msg.Keys.SigningKey = aliceSigningKey + msg.Keys.SigningKeyType = "EdDSA" + expNbo := make([]byte, 8) + binary.BigEndian.PutUint64(expNbo, msg.Keys.Expiration) + h := sha512.New() + h.Write([]byte(msg.Keys.EncryptionKeyType)) + h.Write(encKey) + h.Write(expNbo) + var signed_msg [64 + 4 + 4]byte + size := signed_msg[0:4] + binary.BigEndian.PutUint32(size, 64+4+4) + purp := signed_msg[4:8] + binary.BigEndian.PutUint32(purp, gana.TALER_SIGNATURE_PURPOSE_MAILBOX_KEYS_UPDATE) + copy(signed_msg[8:], h.Sum(nil)) + sig := ed25519.Sign(testAliceSigningKeyPriv, signed_msg[0:]) + if !ed25519.Verify(testAliceSigningKey, signed_msg[0:], sig) { + t.Errorf("Signature invalid!") + } + msg.Signature = util.Base32CrockfordEncode(sig) + jsonMsg, _ := json.Marshal(msg) + req, _ := http.NewRequest("POST", "/keys/"+aliceSigningKey, bytes.NewReader(jsonMsg)) + response := executeRequest(req) + checkResponseCode(t, http.StatusNoContent, response.Code) + req, _ = http.NewRequest("GET", "/keys/"+testAliceHashedSigningKeyString, nil) + response = executeRequest(req) + checkResponseCode(t, http.StatusOK, response.Code) + body := response.Body.String() + if body == "" { + t.Errorf("Expected response, Got %s", body) + } + var respMsg mailbox.MailboxMessageKeys + err := json.NewDecoder(response.Body).Decode(&respMsg) + if err != nil { + fmt.Printf("Error %s\n", err) + } + if respMsg != msg.Keys { + fmt.Printf("Keys mismatch! %v %v\n", respMsg, msg.Keys) + } +} + func TestPostMessagePaid(t *testing.T) { testMessage := make([]byte, 256) @@ -162,14 +217,14 @@ func TestPostMessagePaid(t *testing.T) { } a.MessageFee = fee a.Db.Where("1 = 1").Delete(&mailbox.InboxEntry{}) - req, _ := http.NewRequest("POST", "/"+testWalletAliceString, bytes.NewReader(testMessage)) + req, _ := http.NewRequest("POST", "/"+testAliceHashedSigningKeyString, bytes.NewReader(testMessage)) response := executeRequest(req) checkResponseCode(t, http.StatusPaymentRequired, response.Code) // TODO check QR / payto response - req, _ = http.NewRequest("POST", "/"+testWalletAliceString, bytes.NewReader(testMessage)) + req, _ = http.NewRequest("POST", "/"+testAliceHashedSigningKeyString, bytes.NewReader(testMessage)) req.Header.Add("PaidIndicator", "yes") response = executeRequest(req) @@ -192,7 +247,7 @@ func TestPostThenDeleteMessage(t *testing.T) { for i := 0; i < int(numMessagesToPost); i++ { testMessage := testMessages[i*256 : (i+1)*256] - req, _ := http.NewRequest("POST", "/"+testWalletAliceString, bytes.NewReader(testMessage)) + req, _ := http.NewRequest("POST", "/"+testAliceHashedSigningKeyString, bytes.NewReader(testMessage)) response := executeRequest(req) checkResponseCode(t, http.StatusNoContent, response.Code) @@ -203,7 +258,7 @@ func TestPostThenDeleteMessage(t *testing.T) { } } - req, _ := http.NewRequest("GET", "/"+testWalletAliceString, nil) + req, _ := http.NewRequest("GET", "/"+testAliceHashedSigningKeyString, nil) response := executeRequest(req) checkResponseCode(t, http.StatusOK, response.Code) @@ -230,16 +285,16 @@ func TestPostThenDeleteMessage(t *testing.T) { binary.BigEndian.PutUint32(purp, gana.TALER_SIGNATURE_PURPOSE_MAILBOX_MESSAGES_DELETE) checksum := h.Sum(nil) copy(signed_msg[8:], checksum) - sig := ed25519.Sign(testWalletAlicePriv, signed_msg[0:]) - if !ed25519.Verify(testWalletAlice, signed_msg[0:], sig) { + sig := ed25519.Sign(testAliceSigningKeyPriv, signed_msg[0:]) + if !ed25519.Verify(testAliceSigningKey, signed_msg[0:], sig) { t.Errorf("Signature invalid!") } - deletionReq.WalletSig = util.Base32CrockfordEncode(sig) + deletionReq.Signature = util.Base32CrockfordEncode(sig) deletionReq.Count = int(a.MessageResponseLimit) deletionReq.Checksum = util.Base32CrockfordEncode(checksum) reqJson, _ := json.Marshal(deletionReq) - hAddress := util.Base32CrockfordEncode(testWalletAlice) + hAddress := util.Base32CrockfordEncode(testAliceSigningKey) req, _ = http.NewRequest("DELETE", "/"+hAddress, bytes.NewBuffer(reqJson)) req.Header.Add("If-Match", etag) response = executeRequest(req) @@ -251,7 +306,7 @@ func TestPostThenDeleteMessage(t *testing.T) { t.Errorf("Expected empty response, Got %s", body) } - req, _ = http.NewRequest("GET", "/"+testWalletAliceString, nil) + req, _ = http.NewRequest("GET", "/"+testAliceHashedSigningKeyString, nil) response = executeRequest(req) checkResponseCode(t, http.StatusOK, response.Code) diff --git a/internal/gana/taler_error_codes.go b/internal/gana/taler_error_codes.go @@ -387,6 +387,14 @@ const ( /** + * The requested feature is not implemented by the server. The system administrator of the server may try to update the software or build it with other options to enable the feature. + * Returned with an HTTP status code of #MHD_HTTP_NOT_IMPLEMENTED (501). + * (A value of 0 indicates that the error is generated client-side). + */ + GENERIC_FEATURE_NOT_IMPLEMENTED = 76 + + + /** * Exchange is badly configured and thus cannot operate. * Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500). * (A value of 0 indicates that the error is generated client-side). @@ -923,8 +931,8 @@ const ( /** - * The batch withdraw included a planchet that was already withdrawn. This is not allowed. - * Returned with an HTTP status code of #MHD_HTTP_CONFLICT (409). + * The withdraw operation included the same planchet more than once. This is not allowed. + * Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400). * (A value of 0 indicates that the error is generated client-side). */ EXCHANGE_WITHDRAW_IDEMPOTENT_PLANCHET = 1175 @@ -1531,6 +1539,14 @@ const ( /** + * The signatures from the master exchange public key are missing, thus the exchange cannot currently sign its API responses. The exchange operator must use taler-exchange-offline to sign the current key material. + * Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500). + * (A value of 0 indicates that the error is generated client-side). + */ + EXCHANGE_SIGNKEY_HELPER_OFFLINE_MISSING = 1753 + + + /** * The purse expiration time is in the past at the time of its creation. * Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400). * (A value of 0 indicates that the error is generated client-side). @@ -2539,6 +2555,38 @@ const ( /** + * The public signing key given in the exchange response is not in the current keys response. It is possible that the operation will succeed later after the merchant has downloaded an updated keys response. + * Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0). + * (A value of 0 indicates that the error is generated client-side). + */ + MERCHANT_EXCHANGE_SIGN_PUB_UNKNOWN = 2029 + + + /** + * The merchant backend does not support the requested feature. + * Returned with an HTTP status code of #MHD_HTTP_NOT_IMPLEMENTED (501). + * (A value of 0 indicates that the error is generated client-side). + */ + MERCHANT_GENERIC_FEATURE_NOT_AVAILABLE = 2030 + + + /** + * This operation requires multi-factor authorization and the respective instance does not have a sufficient number of factors that could be validated configured. You need to ask the system administrator to perform this operation. + * Returned with an HTTP status code of #MHD_HTTP_FORBIDDEN (403). + * (A value of 0 indicates that the error is generated client-side). + */ + MERCHANT_GENERIC_MFA_MISSING = 2031 + + + /** + * A donation authority (Donau) provided an invalid response. This should be analyzed by the administrator. Trying again later may help. + * Returned with an HTTP status code of #MHD_HTTP_BAD_GATEWAY (502). + * (A value of 0 indicates that the error is generated client-side). + */ + MERCHANT_GENERIC_DONAU_INVALID_RESPONSE = 2032 + + + /** * The exchange failed to provide a valid answer to the tracking request, thus those details are not in the response. * Returned with an HTTP status code of #MHD_HTTP_OK (200). * (A value of 0 indicates that the error is generated client-side). @@ -2587,6 +2635,62 @@ const ( /** + * The provided TAN code is invalid for this challenge. + * Returned with an HTTP status code of #MHD_HTTP_CONFLICT (409). + * (A value of 0 indicates that the error is generated client-side). + */ + MERCHANT_TAN_CHALLENGE_FAILED = 2125 + + + /** + * The backend is not aware of the specified MFA challenge. + * Returned with an HTTP status code of #MHD_HTTP_NOT_FOUND (404). + * (A value of 0 indicates that the error is generated client-side). + */ + MERCHANT_TAN_CHALLENGE_UNKNOWN = 2126 + + + /** + * There have been too many attempts to solve the challenge. A new TAN must be requested. + * Returned with an HTTP status code of #MHD_HTTP_TOO_MANY_REQUESTS (429). + * (A value of 0 indicates that the error is generated client-side). + */ + MERCHANT_TAN_TOO_MANY_ATTEMPTS = 2127 + + + /** + * The backend failed to launch a helper process required for the multi-factor authentication step. The backend operator should check the logs and fix the Taler merchant backend configuration. + * Returned with an HTTP status code of #MHD_HTTP_BAD_GATEWAY (502). + * (A value of 0 indicates that the error is generated client-side). + */ + MERCHANT_TAN_MFA_HELPER_EXEC_FAILED = 2128 + + + /** + * The challenge was already solved. Thus, we refuse to send it again. + * Returned with an HTTP status code of #MHD_HTTP_GONE (410). + * (A value of 0 indicates that the error is generated client-side). + */ + MERCHANT_TAN_CHALLENGE_SOLVED = 2129 + + + /** + * It is too early to request another transmission of the challenge. The client should wait and see if they received the previous challenge. + * Returned with an HTTP status code of #MHD_HTTP_TOO_MANY_REQUESTS (429). + * (A value of 0 indicates that the error is generated client-side). + */ + MERCHANT_TAN_TOO_EARLY = 2130 + + + /** + * There have been too many attempts to solve MFA. The client may attempt again in the future. + * Returned with an HTTP status code of #MHD_HTTP_FORBIDDEN (403). + * (A value of 0 indicates that the error is generated client-side). + */ + MERCHANT_MFA_FORBIDDEN = 2131 + + + /** * The exchange responded saying that funds were insufficient (for example, due to double-spending). * Returned with an HTTP status code of #MHD_HTTP_CONFLICT (409). * (A value of 0 indicates that the error is generated client-side). @@ -2859,6 +2963,14 @@ const ( /** + * The donation amount provided in the BKPS does not match the amount of the order choice. + * Returned with an HTTP status code of #MHD_HTTP_CONFLICT (409). + * (A value of 0 indicates that the error is generated client-side). + */ + MERCHANT_POST_ORDERS_ID_PAY_DONATION_AMOUNT_MISMATCH = 2185 + + + /** * The contract hash does not match the given order ID. * Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400). * (A value of 0 indicates that the error is generated client-side). @@ -3307,7 +3419,7 @@ const ( /** - * The backend was previously informed about a wire transfer with the same ID but a different amount. Multiple wire transfers with the same ID are not allowed. If the new amount is correct, the old transfer should first be deleted. + * The backend could not persist the wire transfer due to the state of the backend. This usually means that the bank account specified is not known to the backend for this instance. * Returned with an HTTP status code of #MHD_HTTP_CONFLICT (409). * (A value of 0 indicates that the error is generated client-side). */ @@ -3315,6 +3427,14 @@ const ( /** + * The target bank account given by the exchange is not (or no longer) known at the merchant instance. + * Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0). + * (A value of 0 indicates that the error is generated client-side). + */ + MERCHANT_EXCHANGE_TRANSFERS_TARGET_ACCOUNT_UNKNOWN = 2558 + + + /** * The amount transferred differs between what was submitted and what the exchange claimed. * Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0). * (A value of 0 indicates that the error is generated client-side). @@ -4043,6 +4163,30 @@ const ( /** + * A non-admin user has tried to set their conversion rate class + * Returned with an HTTP status code of #MHD_HTTP_CONFLICT (409). + * (A value of 0 indicates that the error is generated client-side). + */ + BANK_NON_ADMIN_SET_CONVERSION_RATE_CLASS = 5155 + + + /** + * The referenced conversion rate class was not found + * Returned with an HTTP status code of #MHD_HTTP_CONFLICT (409). + * (A value of 0 indicates that the error is generated client-side). + */ + BANK_CONVERSION_RATE_CLASS_UNKNOWN = 5156 + + + /** + * The client tried to use an already taken name. + * Returned with an HTTP status code of #MHD_HTTP_CONFLICT (409). + * (A value of 0 indicates that the error is generated client-side). + */ + BANK_NAME_REUSE = 5157 + + + /** * The sync service failed find the account in its database. * Returned with an HTTP status code of #MHD_HTTP_NOT_FOUND (404). * (A value of 0 indicates that the error is generated client-side). @@ -4243,14 +4387,6 @@ const ( /** - * The exchange does not know about the reserve (yet), and thus withdrawal can't progress. - * Returned with an HTTP status code of #MHD_HTTP_NOT_FOUND (404). - * (A value of 0 indicates that the error is generated client-side). - */ - WALLET_EXCHANGE_WITHDRAW_RESERVE_UNKNOWN_AT_EXCHANGE = 7010 - - - /** * The wallet core service is not available. * Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0). * (A value of 0 indicates that the error is generated client-side). @@ -4539,6 +4675,22 @@ const ( /** + * A transaction could not be processed due to an unrecoverable protocol violation. + * Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0). + * (A value of 0 indicates that the error is generated client-side). + */ + WALLET_TRANSACTION_PROTOCOL_VIOLATION = 7047 + + + /** + * A parameter in the request is malformed or missing. + * Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0). + * (A value of 0 indicates that the error is generated client-side). + */ + WALLET_CORE_API_BAD_REQUEST = 7048 + + + /** * We encountered a timeout with our payment backend. * Returned with an HTTP status code of #MHD_HTTP_GATEWAY_TIMEOUT (504). * (A value of 0 indicates that the error is generated client-side). @@ -5155,6 +5307,14 @@ const ( /** + * A charity with the same public key is already registered. + * Returned with an HTTP status code of #MHD_HTTP_NOT_FOUND (404). + * (A value of 0 indicates that the error is generated client-side). + */ + DONAU_CHARITY_PUB_EXISTS = 8618 + + + /** * A generic error happened in the LibEuFin nexus. See the enclose details JSON for more information. * Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0). * (A value of 0 indicates that the error is generated client-side). diff --git a/internal/gana/taler_signatures.go b/internal/gana/taler_signatures.go @@ -411,6 +411,12 @@ const ( /** + * Signature over new key set in key update (GNU Taler) + */ + TALER_SIGNATURE_PURPOSE_MAILBOX_KEYS_UPDATE = 1224 + + + /** * Signature on a denomination key announcement. (GNU Taler) */ TALER_SIGNATURE_PURPOSE_SM_RSA_DENOMINATION_KEY = 1250 diff --git a/pkg/rest/mailbox.go b/pkg/rest/mailbox.go @@ -158,7 +158,7 @@ type MessageDeletionRequest struct { // Signature by the mailbox's private key affirming // the deletion of the messages, of purpose // TALER_SIGNATURE_WALLET_MAILBOX_DELETE_MESSAGES. - WalletSig string `json:"wallet_sig"` + Signature string `json:"signature"` } // MailboxRateLimitedResponse is the JSON response when a rate limit is hit @@ -315,6 +315,137 @@ func (m *Mailbox) sendMessageResponse(w http.ResponseWriter, r *http.Request) { } } +type MailboxMessageKeys struct { + // ORM + gorm.Model `json:"-"` + + // ORM helper hash of signing key + HashedSigningKey string `json:"-"` + + // The mailbox signing key. + // Note that $H_MAILBOX == H(singingKey). + // Note also how this key cannot be updated + // as it identifies the mailbox. + SigningKey string `json:"signingKey"` + + // Type of key. + // Optional, as currently only + // EdDSA keys are supported. + SigningKeyType string `json:"signingKeyType"` + + // The mailbox encryption key. + // This is an HPKE public key + // in the X25519 format for use + // in a X25519-DHKEM (RFC 9180). + // Base32 crockford-encoded. + EncryptionKey string `json:"encryptionKey"` + + // Type of key. + // Optional, as currently only + // X25519 keys are supported. + EncryptionKeyType string `json:"encryptionKeyType"` + + // Expiration of this mapping. + Expiration uint64 `json:"expiration"` +} + +type KeyUpdateRequest struct { + // ORM + gorm.Model `json:"-"` + + // Keys to add/update for a mailbox. + Keys MailboxMessageKeys `json:"keys"` + + // Signature by the mailbox's signing key affirming + // the update of keys, of purpuse + // TALER_SIGNATURE_WALLET_MAILBOX_KEYS_UPDATE. + // The signature is created over the SHA-512 hash + // of (encryptionKeyType||encryptionKey||expiration) + Signature string `json:"signature"` +} + +func (m *Mailbox) getKeysResponse(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + var keyEntry MailboxMessageKeys + err := m.Db.First(&keyEntry, "hashed_signing_key = ?", vars["h_mailbox"]).Error + if err != nil { + w.WriteHeader(http.StatusNotFound) + return + } + response, _ := json.Marshal(keyEntry) + w.Write(response) +} + +func (m *Mailbox) updateKeysResponse(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + var msg KeyUpdateRequest + if r.Body == nil { + http.Error(w, "No request body", http.StatusBadRequest) + return + } + err := json.NewDecoder(r.Body).Decode(&msg) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + return + } + if msg.Keys.SigningKey != vars["mailbox"] { + w.WriteHeader(http.StatusBadRequest) + return + } + pkey, err := util.Base32CrockfordDecode(vars["mailbox"], 32) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + return + } + pk := ed25519.PublicKey(pkey) + sig, err := util.Base32CrockfordDecode(msg.Signature, 64) + if nil != err { + w.WriteHeader(http.StatusBadRequest) + return + } + encPk, err := util.Base32CrockfordDecode(msg.Keys.EncryptionKey, 32) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + return + } + var expNbo [8]byte + var signed_msg [72]byte + binary.BigEndian.PutUint64(expNbo[:], msg.Keys.Expiration) + size := signed_msg[0:4] + binary.BigEndian.PutUint32(size, 64+4+4) + purp := signed_msg[4:8] + binary.BigEndian.PutUint32(purp, gana.TALER_SIGNATURE_PURPOSE_MAILBOX_KEYS_UPDATE) + h := sha512.New() + h.Write([]byte(msg.Keys.EncryptionKeyType)) // Currently always X25519 + h.Write(encPk) + h.Write(expNbo[:]) + copy(signed_msg[8:], h.Sum(nil)) + if !ed25519.Verify(pk, signed_msg[0:], sig) { + w.WriteHeader(http.StatusForbidden) + return + } + var keyEntry MailboxMessageKeys + keyEntry.SigningKeyType = msg.Keys.SigningKeyType + keyEntry.SigningKey = msg.Keys.SigningKey + keyEntry.EncryptionKeyType = msg.Keys.EncryptionKeyType + keyEntry.EncryptionKey = msg.Keys.EncryptionKey + keyEntry.Expiration = msg.Keys.Expiration + hAddr := sha512.New() + hAddr.Write(pk) + keyEntry.HashedSigningKey = util.Base32CrockfordEncode(hAddr.Sum(nil)) + err = m.Db.First(&keyEntry, "signing_key = ?", msg.Keys.SigningKey).Error + if nil != err { + m.Db.Create(&keyEntry) + } else { + err = m.Db.Save(&keyEntry).Error + if nil != err { + w.WriteHeader(http.StatusInternalServerError) + return + } + } + w.WriteHeader(http.StatusNoContent) +} + func (m *Mailbox) deleteMessagesResponse(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) var msg MessageDeletionRequest @@ -353,7 +484,7 @@ func (m *Mailbox) deleteMessagesResponse(w http.ResponseWriter, r *http.Request) return } pk := ed25519.PublicKey(pkey) - sig, err := util.Base32CrockfordDecode(msg.WalletSig, 64) + sig, err := util.Base32CrockfordDecode(msg.Signature, 64) if nil != err { w.WriteHeader(http.StatusBadRequest) return @@ -443,6 +574,8 @@ func (m *Mailbox) setupHandlers() { m.Router.HandleFunc("/{h_mailbox}", m.sendMessageResponse).Methods("POST") m.Router.HandleFunc("/{h_mailbox}", m.getMessagesResponse).Methods("GET") m.Router.HandleFunc("/{mailbox}", m.deleteMessagesResponse).Methods("DELETE") + m.Router.HandleFunc("/keys/{h_mailbox}", m.getKeysResponse).Methods("GET") + m.Router.HandleFunc("/keys/{mailbox}", m.updateKeysResponse).Methods("POST") } func (m *Mailbox) Logf(loglevel LogLevel, fmt string, args ...any) { @@ -480,6 +613,9 @@ func (m *Mailbox) Initialize(cfg MailboxConfig) { if err := m.Db.AutoMigrate(&InboxEntry{}); err != nil { panic(err) } + if err := m.Db.AutoMigrate(&MailboxMessageKeys{}); err != nil { + panic(err) + } m.Merchant = cfg.Merchant m.setupHandlers()