commit 78e11e7fef48ca7258e0c2d1ca0104cee6f9d0a5
parent 8db0d0b6d4bca8e06026ef77cbd589cf83a1ce9e
Author: Martin Schanzenbach <schanzen@gnunet.org>
Date: Thu, 6 Nov 2025 15:35:12 +0100
allow deletion of messages (remotely)
Diffstat:
3 files changed, 51 insertions(+), 12 deletions(-)
diff --git a/packages/taler-util/src/http-client/mailbox.ts b/packages/taler-util/src/http-client/mailbox.ts
@@ -42,6 +42,8 @@ import {
sha512,
decodeCrock,
codecForEmptyObject,
+ MailboxConfiguration,
+ eddsaGetPublic,
} from "@gnu-taler/taler-util";
import {
HttpRequestLibrary,
@@ -55,6 +57,11 @@ export type TalerMailboxInstanceErrorsByMethod<
prop extends keyof TalerMailboxInstanceHttpClient,
> = FailCasesByMethod<TalerMailboxInstanceHttpClient, prop>;
+export interface MailboxMessagesResponseRaw {
+ messages: Uint8Array;
+ etag: string;
+}
+
/**
* Protocol version spoken with the service.
*
@@ -160,7 +167,7 @@ export class TalerMailboxInstanceHttpClient {
async getMessages(args: {
hMailbox: string;
}): Promise<
- | OperationOk<Uint8Array>
+ | OperationOk<MailboxMessagesResponseRaw>
| OperationOk<void>
| OperationFail<HttpStatusCode.TooManyRequests>
>{
@@ -175,7 +182,9 @@ export class TalerMailboxInstanceHttpClient {
switch (resp.status) {
case HttpStatusCode.Ok: {
const uintar = await resp.bytes() as Uint8Array;
- return opFixedSuccess(uintar);
+ const etag = resp.headers.get("etag")
+ const index = etag? etag : "0";
+ return opFixedSuccess({messages: uintar, etag: index});
}
case HttpStatusCode.NoContent: {
return opEmptySuccess();
@@ -192,19 +201,23 @@ export class TalerMailboxInstanceHttpClient {
* https://docs.taler.net/core/api-mailbox.html#delete--$ADDRESS
*/
async deleteMessages(args: {
- mailbox: string;
+ mailboxConf: MailboxConfiguration;
+ matchIf: string;
body: MailboxMessageDeletionRequest;
}): Promise<
| OperationOk<void>
| OperationFail<HttpStatusCode.Forbidden>
| OperationFail<HttpStatusCode.NotFound>
>{
- const { mailbox, body } = args;
- const url = new URL(`${mailbox}`, this.baseUrl);
+ const { mailboxConf, matchIf: etag, body } = args;
+ const mailboxPubkeyString = encodeCrock(eddsaGetPublic(decodeCrock(mailboxConf.privateKey)));
+ const url = new URL(`private/${mailboxPubkeyString.toUpperCase()}`, this.baseUrl);
+
const resp = await this.httpLib.fetch(url.href, {
- method: "DELETE",
+ method: "POST",
body: body,
+ headers: { "If-Match" : etag },
cancellationToken: this.cancellationToken,
});
diff --git a/packages/taler-util/src/types-taler-mailbox.ts b/packages/taler-util/src/types-taler-mailbox.ts
@@ -173,6 +173,6 @@ export interface MailboxMessageDeletionRequest {
// Signature by the mailbox's private key affirming
// the deletion of the messages, of purpuse
// TALER_SIGNATURE_WALLET_MAILBOX_DELETE_MESSAGES.
- wallet_sig: EddsaSignatureString;
+ signature: EddsaSignatureString;
}
diff --git a/packages/taler-wallet-core/src/mailbox.ts b/packages/taler-wallet-core/src/mailbox.ts
@@ -48,6 +48,7 @@ import {
eddsaSign,
Duration,
AbsoluteTime,
+ eddsaVerify,
} from "@gnu-taler/taler-util";
import {
WalletExecutionContext,
@@ -250,19 +251,20 @@ export async function refreshMailbox(
case "ok":
const hpkeSk: Uint8Array = decodeCrock(mailboxConf.privateEncryptionKey);
if (res.body) {
+ const messages = res.body.messages;
const now = TalerProtocolTimestamp.now();
- if ((res.body.byteLength % message_size) !== 0) {
- throw Error(`mailbox messages response not a multiple of message size! (${res.body.byteLength} % ${message_size} != 0)`);
+ if ((messages.byteLength % message_size) !== 0) {
+ throw Error(`mailbox messages response not a multiple of message size! (${messages.byteLength} % ${message_size} != 0)`);
}
// FIXME: if we have reached the maximum number of
// messages that the service returns at a time,
// we probably have to call again until no more messages to
// download.
- const numMessages = res.body.byteLength / message_size;
+ const numMessages = messages.byteLength / message_size;
const records: MailboxMessageRecord[] = [];
for (let i = 0; i < numMessages; i++) {
const offset = i * message_size;
- const msg: Uint8Array = res.body.slice(offset,
+ const msg: Uint8Array = messages.slice(offset,
offset + message_size);
const header = new Uint8Array(msg.slice(0, 4));
const ct = new Uint8Array(msg.slice(4));
@@ -282,9 +284,33 @@ export async function refreshMailbox(
records.push(newMessage);
await addMailboxMessage(wex, {message: newMessage});
}
+ // Message header: 8 byte + 64 byte SHA512 digest to sign
+ // We hash all messages
+ const messageHeader = new ArrayBuffer(8);
+ const vMsg = new DataView(messageHeader);
+ const digest = sha512(new Uint8Array(messages));
+ vMsg.setUint32(0, 8 + 64);
+ // FIXME: Where is gana?
+ vMsg.setUint32(4, 1223);
+ const msgToSign: Uint8Array = new Uint8Array([...(new Uint8Array(messageHeader)), ...digest]);
+ const privateSigningKey: Uint8Array = decodeCrock(mailboxConf.privateKey);
+ const signature = eddsaSign(msgToSign, privateSigningKey);
+ const mailboxPubkeyString = encodeCrock(eddsaGetPublic(decodeCrock(mailboxConf.privateKey)));
+ if (!eddsaVerify(msgToSign, signature, decodeCrock(mailboxPubkeyString))) {
+ logger.error("Signature invalid!");
+ }
+ succeedOrThrow(await mailboxClient.deleteMessages({
+ mailboxConf: mailboxConf,
+ matchIf: res.body.etag,
+ body: {
+ count: numMessages,
+ checksum: encodeCrock(digest),
+ signature: encodeCrock(signature),
+ }
+ }));
return records;
}
- throw Error("mailbox messages response empty!");
+ return []; // No new messages;
default:
throw Error("unexpected mailbox messages response empty");
}