diff --git a/package.json b/package.json index 0be91ba..118c540 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@xtr-dev/rondevu-server", - "version": "0.2.3", + "version": "0.2.4", "description": "DNS-like WebRTC signaling server with username claiming and service discovery", "main": "dist/index.js", "scripts": { diff --git a/src/app.ts b/src/app.ts index 3239319..39fd680 100644 --- a/src/app.ts +++ b/src/app.ts @@ -3,7 +3,7 @@ import { cors } from 'hono/cors'; import { Storage } from './storage/types.ts'; import { Config } from './config.ts'; import { createAuthMiddleware, getAuthenticatedPeerId } from './middleware/auth.ts'; -import { generatePeerId, encryptPeerId, validateUsernameClaim, validateServiceFqn } from './crypto.ts'; +import { generatePeerId, encryptPeerId, validateUsernameClaim, validateServicePublish, validateServiceFqn } from './crypto.ts'; import type { Context } from 'hono'; /** @@ -207,7 +207,7 @@ export function createApp(storage: Storage, config: Config) { } // Verify signature matches username's public key - const signatureValidation = await validateUsernameClaim(username, usernameRecord.publicKey, signature, message); + const signatureValidation = await validateServicePublish(username, serviceFqn, usernameRecord.publicKey, signature, message); if (!signatureValidation.valid) { return c.json({ error: 'Invalid signature for username' }, 403); } diff --git a/src/crypto.ts b/src/crypto.ts index ed77a42..023d110 100644 --- a/src/crypto.ts +++ b/src/crypto.ts @@ -317,3 +317,46 @@ export async function validateUsernameClaim( return { valid: true }; } + +/** + * Validates a service publish signature + * Message format: publish:{username}:{serviceFqn}:{timestamp} + */ +export async function validateServicePublish( + username: string, + serviceFqn: string, + publicKey: string, + signature: string, + message: string +): Promise<{ valid: boolean; error?: string }> { + // Validate username format + const usernameCheck = validateUsername(username); + if (!usernameCheck.valid) { + return usernameCheck; + } + + // Parse message format: "publish:{username}:{serviceFqn}:{timestamp}" + const parts = message.split(':'); + if (parts.length !== 4 || parts[0] !== 'publish' || parts[1] !== username || parts[2] !== serviceFqn) { + return { valid: false, error: 'Invalid message format (expected: publish:{username}:{serviceFqn}:{timestamp})' }; + } + + const timestamp = parseInt(parts[3], 10); + if (isNaN(timestamp)) { + return { valid: false, error: 'Invalid timestamp in message' }; + } + + // Validate timestamp + const timestampCheck = validateTimestamp(timestamp); + if (!timestampCheck.valid) { + return timestampCheck; + } + + // Verify signature + const signatureValid = await verifyEd25519Signature(publicKey, signature, message); + if (!signatureValid) { + return { valid: false, error: 'Invalid signature' }; + } + + return { valid: true }; +}