From 281782d5937ec67de800924c499526a36eb512dd Mon Sep 17 00:00:00 2001 From: Wil Wade Date: Thu, 10 Oct 2024 17:14:23 -0400 Subject: [PATCH] Fix CAIP-122 verification (#193) # Problem Various issues found when testing the CAIP-122 signature verification # Solution 1. Fixed a few SIWA references 2. Fixed the signature verification to allow for the hashed version in addition to the pure string version. 3. `publicKey` should be `Sr` not `SR` (verification doesn't care however) 4. Added additional test generated from FA code 5. Match address encoding before comparing for the CAIP-122 payload 6. Fix address extraction 7. Fix expired parsing --- README.md | 2 +- docs/book.toml | 2 +- docs/src/DataStructures/Request.md | 2 +- docs/src/DataStructures/RequestUrl.md | 2 +- docs/src/DataStructures/Response-LoginOnly.md | 2 +- .../DataStructures/Response-NewProvider.md | 2 +- docs/src/DataStructures/Response-NewUser.md | 2 +- docs/src/DataStructures/SignedRequest.md | 2 +- libraries/js/package-lock.json | 8 +- libraries/js/package.json | 4 +- .../js/src/mocks/generate-data-structures.ts | 2 +- libraries/js/src/mocks/index.ts | 2 +- libraries/js/src/payloads.test.ts | 37 ++++++++ libraries/js/src/payloads.ts | 92 ++++++++++++++----- libraries/js/src/request.test.ts | 4 +- libraries/js/src/request.ts | 2 +- libraries/js/src/response.test.ts | 7 ++ libraries/js/src/types/general.ts | 2 +- 18 files changed, 132 insertions(+), 44 deletions(-) diff --git a/README.md b/README.md index bc684f95..dc7c91b5 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ Looking for SIWF v1 resources? [Go to the `v1` branch](https://github.com/Projec ## 💻 Prerequisites -Using Sign In With Access requires: +Using Sign In With Frequency requires: - Frequency Provider setup - Frequency Node RPC access diff --git a/docs/book.toml b/docs/book.toml index 89dec928..fa099c8e 100644 --- a/docs/book.toml +++ b/docs/book.toml @@ -3,7 +3,7 @@ authors = ["Project Liberty Labs"] language = "en" multilingual = false src = "src" -title = "Sign In With Access" +title = "Sign In With Frequency" [preprocessor.local] command = "node preprocessor.mjs" diff --git a/docs/src/DataStructures/Request.md b/docs/src/DataStructures/Request.md index f1e49700..0591dcbc 100644 --- a/docs/src/DataStructures/Request.md +++ b/docs/src/DataStructures/Request.md @@ -1,6 +1,6 @@ ```json { - "signedRequest": "eyJyZXF1ZXN0ZWRTaWduYXR1cmVzIjp7InB1YmxpY0tleSI6eyJlbmNvZGVkVmFsdWUiOiJmNmNMNHdxMUhVTngxMVRjdmRBQk5mOVVOWFhveUg0N21WVXdUNTl0elNGUlc4eURIIiwiZW5jb2RpbmciOiJiYXNlNTgiLCJmb3JtYXQiOiJzczU4IiwidHlwZSI6IlNSMjU1MTkifSwic2lnbmF0dXJlIjp7ImFsZ28iOiJTUjI1NTE5IiwiZW5jb2RpbmciOiJiYXNlMTYiLCJlbmNvZGVkVmFsdWUiOiIweGU2NjBiZjA4MTQxNzI3OGE4YzYxOTkwZGM3N2JjZTZjOTQxZWU3ZjM0ZDMwZGRiYjVjYzdjMzk2NmE0MDhjMDA3YmU1MGYyNjBkMzc2Y2I3ZjAyYzRiOGI2ZmFjZTkzOTJkZjZhNzE4MTYzZmJmZDI2YTNkNWQwYzA4NzYwNDgwIn0sInBheWxvYWQiOnsiY2FsbGJhY2siOiJodHRwOi8vbG9jYWxob3N0OjMwMDAiLCJwZXJtaXNzaW9ucyI6WzUsNyw4LDksMTBdfX0sInJlcXVlc3RlZENyZWRlbnRpYWxzIjpbeyJ0eXBlIjoiVmVyaWZpZWRHcmFwaEtleUNyZWRlbnRpYWwiLCJoYXNoIjpbImJjaXFtZHZteGQ1NHp2ZTVraWZ5Y2dzZHRvYWhzNWVjZjRoYWwydHMzZWV4a2dvY3ljNW9jYTJ5Il19LHsiYW55T2YiOlt7InR5cGUiOiJWZXJpZmllZEVtYWlsQWRkcmVzc0NyZWRlbnRpYWwiLCJoYXNoIjpbImJjaXFlNHFvY3poZnRpY2k0ZHpmdmZiZWw3Zm80aDRzcjVncmNvM29vdnd5azZ5NHluZjQ0dHNpIl19LHsidHlwZSI6IlZlcmlmaWVkUGhvbmVOdW1iZXJDcmVkZW50aWFsIiwiaGFzaCI6WyJiY2lxanNwbmJ3cGMzd2p4NGZld2NlazVkYXlzZGpwYmY1eGppbXo1d251NXVqN2UzdnUydXducSJdfV19XX0", + "signedRequest": "eyJyZXF1ZXN0ZWRTaWduYXR1cmVzIjp7InB1YmxpY0tleSI6eyJlbmNvZGVkVmFsdWUiOiJmNmNMNHdxMUhVTngxMVRjdmRBQk5mOVVOWFhveUg0N21WVXdUNTl0elNGUlc4eURIIiwiZW5jb2RpbmciOiJiYXNlNTgiLCJmb3JtYXQiOiJzczU4IiwidHlwZSI6IlNyMjU1MTkifSwic2lnbmF0dXJlIjp7ImFsZ28iOiJTUjI1NTE5IiwiZW5jb2RpbmciOiJiYXNlMTYiLCJlbmNvZGVkVmFsdWUiOiIweDNlMTdhYzM3Yzk3ZWE3M2E3YzM1ZjBjYTJkZTcxYmY3MmE5NjlkYjhiNjQyYzU3ZTI2N2Q4N2Q1OTA3ZGM4MzVmYTJjODI4MTdlODA2YTQ5NGIyY2E5Y2U5MjJmNDM1NDY4M2U4YzAxMzY5NTNlMGZlNWExODJkMzU0NjQ2Yzg4In0sInBheWxvYWQiOnsiY2FsbGJhY2siOiJodHRwOi8vbG9jYWxob3N0OjMwMDAiLCJwZXJtaXNzaW9ucyI6WzUsNyw4LDksMTBdfX0sInJlcXVlc3RlZENyZWRlbnRpYWxzIjpbeyJ0eXBlIjoiVmVyaWZpZWRHcmFwaEtleUNyZWRlbnRpYWwiLCJoYXNoIjpbImJjaXFtZHZteGQ1NHp2ZTVraWZ5Y2dzZHRvYWhzNWVjZjRoYWwydHMzZWV4a2dvY3ljNW9jYTJ5Il19LHsiYW55T2YiOlt7InR5cGUiOiJWZXJpZmllZEVtYWlsQWRkcmVzc0NyZWRlbnRpYWwiLCJoYXNoIjpbImJjaXFlNHFvY3poZnRpY2k0ZHpmdmZiZWw3Zm80aDRzcjVncmNvM29vdnd5azZ5NHluZjQ0dHNpIl19LHsidHlwZSI6IlZlcmlmaWVkUGhvbmVOdW1iZXJDcmVkZW50aWFsIiwiaGFzaCI6WyJiY2lxanNwbmJ3cGMzd2p4NGZld2NlazVkYXlzZGpwYmY1eGppbXo1d251NXVqN2UzdnUydXducSJdfV19XX0", "mode": "dark" } ``` diff --git a/docs/src/DataStructures/RequestUrl.md b/docs/src/DataStructures/RequestUrl.md index 56150bb2..08c3ae68 100644 --- a/docs/src/DataStructures/RequestUrl.md +++ b/docs/src/DataStructures/RequestUrl.md @@ -1,3 +1,3 @@ ```json -"https://testnet.frequencyaccess.com/siwa/start?signedRequest=eyJyZXF1ZXN0ZWRTaWduYXR1cmVzIjp7InB1YmxpY0tleSI6eyJlbmNvZGVkVmFsdWUiOiJmNmNMNHdxMUhVTngxMVRjdmRBQk5mOVVOWFhveUg0N21WVXdUNTl0elNGUlc4eURIIiwiZW5jb2RpbmciOiJiYXNlNTgiLCJmb3JtYXQiOiJzczU4IiwidHlwZSI6IlNSMjU1MTkifSwic2lnbmF0dXJlIjp7ImFsZ28iOiJTUjI1NTE5IiwiZW5jb2RpbmciOiJiYXNlMTYiLCJlbmNvZGVkVmFsdWUiOiIweGU2NjBiZjA4MTQxNzI3OGE4YzYxOTkwZGM3N2JjZTZjOTQxZWU3ZjM0ZDMwZGRiYjVjYzdjMzk2NmE0MDhjMDA3YmU1MGYyNjBkMzc2Y2I3ZjAyYzRiOGI2ZmFjZTkzOTJkZjZhNzE4MTYzZmJmZDI2YTNkNWQwYzA4NzYwNDgwIn0sInBheWxvYWQiOnsiY2FsbGJhY2siOiJodHRwOi8vbG9jYWxob3N0OjMwMDAiLCJwZXJtaXNzaW9ucyI6WzUsNyw4LDksMTBdfX0sInJlcXVlc3RlZENyZWRlbnRpYWxzIjpbeyJ0eXBlIjoiVmVyaWZpZWRHcmFwaEtleUNyZWRlbnRpYWwiLCJoYXNoIjpbImJjaXFtZHZteGQ1NHp2ZTVraWZ5Y2dzZHRvYWhzNWVjZjRoYWwydHMzZWV4a2dvY3ljNW9jYTJ5Il19LHsiYW55T2YiOlt7InR5cGUiOiJWZXJpZmllZEVtYWlsQWRkcmVzc0NyZWRlbnRpYWwiLCJoYXNoIjpbImJjaXFlNHFvY3poZnRpY2k0ZHpmdmZiZWw3Zm80aDRzcjVncmNvM29vdnd5azZ5NHluZjQ0dHNpIl19LHsidHlwZSI6IlZlcmlmaWVkUGhvbmVOdW1iZXJDcmVkZW50aWFsIiwiaGFzaCI6WyJiY2lxanNwbmJ3cGMzd2p4NGZld2NlazVkYXlzZGpwYmY1eGppbXo1d251NXVqN2UzdnUydXducSJdfV19XX0&mode=dark" +"https://testnet.frequencyaccess.com/siwa/start?signedRequest=eyJyZXF1ZXN0ZWRTaWduYXR1cmVzIjp7InB1YmxpY0tleSI6eyJlbmNvZGVkVmFsdWUiOiJmNmNMNHdxMUhVTngxMVRjdmRBQk5mOVVOWFhveUg0N21WVXdUNTl0elNGUlc4eURIIiwiZW5jb2RpbmciOiJiYXNlNTgiLCJmb3JtYXQiOiJzczU4IiwidHlwZSI6IlNyMjU1MTkifSwic2lnbmF0dXJlIjp7ImFsZ28iOiJTUjI1NTE5IiwiZW5jb2RpbmciOiJiYXNlMTYiLCJlbmNvZGVkVmFsdWUiOiIweDNlMTdhYzM3Yzk3ZWE3M2E3YzM1ZjBjYTJkZTcxYmY3MmE5NjlkYjhiNjQyYzU3ZTI2N2Q4N2Q1OTA3ZGM4MzVmYTJjODI4MTdlODA2YTQ5NGIyY2E5Y2U5MjJmNDM1NDY4M2U4YzAxMzY5NTNlMGZlNWExODJkMzU0NjQ2Yzg4In0sInBheWxvYWQiOnsiY2FsbGJhY2siOiJodHRwOi8vbG9jYWxob3N0OjMwMDAiLCJwZXJtaXNzaW9ucyI6WzUsNyw4LDksMTBdfX0sInJlcXVlc3RlZENyZWRlbnRpYWxzIjpbeyJ0eXBlIjoiVmVyaWZpZWRHcmFwaEtleUNyZWRlbnRpYWwiLCJoYXNoIjpbImJjaXFtZHZteGQ1NHp2ZTVraWZ5Y2dzZHRvYWhzNWVjZjRoYWwydHMzZWV4a2dvY3ljNW9jYTJ5Il19LHsiYW55T2YiOlt7InR5cGUiOiJWZXJpZmllZEVtYWlsQWRkcmVzc0NyZWRlbnRpYWwiLCJoYXNoIjpbImJjaXFlNHFvY3poZnRpY2k0ZHpmdmZiZWw3Zm80aDRzcjVncmNvM29vdnd5azZ5NHluZjQ0dHNpIl19LHsidHlwZSI6IlZlcmlmaWVkUGhvbmVOdW1iZXJDcmVkZW50aWFsIiwiaGFzaCI6WyJiY2lxanNwbmJ3cGMzd2p4NGZld2NlazVkYXlzZGpwYmY1eGppbXo1d251NXVqN2UzdnUydXducSJdfV19XX0&mode=dark" ``` diff --git a/docs/src/DataStructures/Response-LoginOnly.md b/docs/src/DataStructures/Response-LoginOnly.md index 5b201954..3101f4a2 100644 --- a/docs/src/DataStructures/Response-LoginOnly.md +++ b/docs/src/DataStructures/Response-LoginOnly.md @@ -4,7 +4,7 @@ "encodedValue": "f6akufkq9Lex6rT8RCEDRuoZQRgo5pWiRzeo81nmKNGWGNJdJ", "encoding": "base58", "format": "ss58", - "type": "SR25519" + "type": "Sr25519" }, "payloads": [ { diff --git a/docs/src/DataStructures/Response-NewProvider.md b/docs/src/DataStructures/Response-NewProvider.md index 180bc020..29ac4e5f 100644 --- a/docs/src/DataStructures/Response-NewProvider.md +++ b/docs/src/DataStructures/Response-NewProvider.md @@ -4,7 +4,7 @@ "encodedValue": "f6akufkq9Lex6rT8RCEDRuoZQRgo5pWiRzeo81nmKNGWGNJdJ", "encoding": "base58", "format": "ss58", - "type": "SR25519" + "type": "Sr25519" }, "payloads": [ { diff --git a/docs/src/DataStructures/Response-NewUser.md b/docs/src/DataStructures/Response-NewUser.md index 972bd937..cb4fd630 100644 --- a/docs/src/DataStructures/Response-NewUser.md +++ b/docs/src/DataStructures/Response-NewUser.md @@ -4,7 +4,7 @@ "encodedValue": "f6akufkq9Lex6rT8RCEDRuoZQRgo5pWiRzeo81nmKNGWGNJdJ", "encoding": "base58", "format": "ss58", - "type": "SR25519" + "type": "Sr25519" }, "payloads": [ { diff --git a/docs/src/DataStructures/SignedRequest.md b/docs/src/DataStructures/SignedRequest.md index c3057299..54eba6ab 100644 --- a/docs/src/DataStructures/SignedRequest.md +++ b/docs/src/DataStructures/SignedRequest.md @@ -5,7 +5,7 @@ "encodedValue": "f6cL4wq1HUNx11TcvdABNf9UNXXoyH47mVUwT59tzSFRW8yDH", "encoding": "base58", "format": "ss58", - "type": "SR25519" + "type": "Sr25519" }, "signature": { "algo": "SR25519", diff --git a/libraries/js/package-lock.json b/libraries/js/package-lock.json index 4c2146b3..4fa0cd75 100644 --- a/libraries/js/package-lock.json +++ b/libraries/js/package-lock.json @@ -34,7 +34,7 @@ "eslint": "^9.12.0", "prettier": "3.3.3", "tsup": "^8.3.0", - "typescript": "^5.6.2", + "typescript": "^5.6.3", "typescript-eslint": "^8.8.1", "vitest": "^2.1.2" }, @@ -18848,9 +18848,9 @@ } }, "node_modules/typescript": { - "version": "5.6.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.2.tgz", - "integrity": "sha512-NW8ByodCSNCwZeghjN3o+JX5OFH0Ojg6sadjEKY4huZ52TqbJTJnDo5+Tw98lSy63NZvi4n+ez5m2u5d4PkZyw==", + "version": "5.6.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz", + "integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==", "devOptional": true, "license": "Apache-2.0", "bin": { diff --git a/libraries/js/package.json b/libraries/js/package.json index 899f6f94..c8c43374 100644 --- a/libraries/js/package.json +++ b/libraries/js/package.json @@ -1,7 +1,7 @@ { "name": "@projectlibertylabs/siwf", "version": "0.0.0", - "description": "Sign In With Access Utility Library", + "description": "Sign In With Frequency Utility Library", "main": "src/index.ts", "type": "module", "scripts": { @@ -38,7 +38,7 @@ "eslint": "^9.12.0", "prettier": "3.3.3", "tsup": "^8.3.0", - "typescript": "^5.6.2", + "typescript": "^5.6.3", "typescript-eslint": "^8.8.1", "vitest": "^2.1.2" }, diff --git a/libraries/js/src/mocks/generate-data-structures.ts b/libraries/js/src/mocks/generate-data-structures.ts index ec3701d0..e84da926 100644 --- a/libraries/js/src/mocks/generate-data-structures.ts +++ b/libraries/js/src/mocks/generate-data-structures.ts @@ -29,7 +29,7 @@ function exampleSignedRequest(): SiwfSignedRequest { encodedValue: 'f6cL4wq1HUNx11TcvdABNf9UNXXoyH47mVUwT59tzSFRW8yDH', encoding: 'base58', format: 'ss58', - type: 'SR25519', + type: 'Sr25519', }, signature: { algo: 'SR25519', diff --git a/libraries/js/src/mocks/index.ts b/libraries/js/src/mocks/index.ts index e6682905..7ddb38ce 100644 --- a/libraries/js/src/mocks/index.ts +++ b/libraries/js/src/mocks/index.ts @@ -14,7 +14,7 @@ export const ExampleUserPublicKey: SiwfPublicKey = { encodedValue: ExampleUserKey.public, encoding: 'base58', format: 'ss58', - type: 'SR25519', + type: 'Sr25519', }; // NOTICE: These mocks ALSO generate the `docs/DataStructure/[].md` files. Take care changing them diff --git a/libraries/js/src/payloads.test.ts b/libraries/js/src/payloads.test.ts index cd40b03c..755dd7c8 100644 --- a/libraries/js/src/payloads.test.ts +++ b/libraries/js/src/payloads.test.ts @@ -20,6 +20,43 @@ beforeAll(async () => { describe('validatePayloads', () => { describe('Login Related Payloads', () => { + it('Can verify Login Payload generated from FA using hash', async () => { + await expect( + validatePayloads( + { + userPublicKey: { + encodedValue: '5HYHZ8e8kyLEBuEbsFa2bwKYbVSMgaUhymfRVgH7CuM4VCHv', + encoding: 'base58', + format: 'ss58', + type: 'Sr25519', + }, + payloads: [ + { + signature: { + algo: 'SR25519', + encoding: 'base16', + encodedValue: + '0xa6da6fa47076fbf7b0aac57a246041a777c867d15571dad1fdd014f4f0477a7ca5a92f5c952a740791b4e360b2320e94a3f20e733e821ac4242b1de72fbc6a80', + }, + type: 'login', + payload: { + message: `localhost wants you to sign in with your Frequency account: +frequency:dev:5HYHZ8e8kyLEBuEbsFa2bwKYbVSMgaUhymfRVgH7CuM4VCHv + +URI: http://localhost:3030/login/callback +Version: 1 +Nonce: d83eba8e-05a3-4c9a-9901-a976e67278b6 +Chain ID: frequency:dev +Issued At: 2024-10-10T18:40:37.344099626Z`, + }, + }, + ], + }, + 'localhost' + ) + ).resolves.toBeUndefined(); + }); + it('Can verify a Generated Login Payload', async () => { await expect( validatePayloads( diff --git a/libraries/js/src/payloads.ts b/libraries/js/src/payloads.ts index 42445630..f762081f 100644 --- a/libraries/js/src/payloads.ts +++ b/libraries/js/src/payloads.ts @@ -1,4 +1,5 @@ -import { signatureVerify, cryptoWaitReady } from '@polkadot/util-crypto'; +import { signatureVerify, cryptoWaitReady, decodeAddress, blake2AsU8a } from '@polkadot/util-crypto'; +import { hexToU8a, stringToU8a, u8aToHex, u8aWrapBytes } from '@polkadot/util'; import { isPayloadAddProvider, isPayloadClaimHandle, @@ -20,14 +21,17 @@ interface SiwxMessage { nonce: string; expired: boolean; issuedAt: Date; - expirationTime: Date; + expirationTime?: Date; uri: string; } function parseMessage(message: string): SiwxMessage { const msgSplit = message.split('\n'); const domain = (msgSplit[0] || '').split(' ')[0] || ''; - const address = msgSplit[1] || ''; + + const addressLines = (msgSplit[1] || '').split(':'); + const address = addressLines[addressLines.length - 1] || ''; + const nonceLine = msgSplit.find((x) => x.startsWith('Nonce: ')); const nonce = nonceLine ? nonceLine.replace('Nonce: ', '') : ''; @@ -36,8 +40,8 @@ function parseMessage(message: string): SiwxMessage { const expiredLine = msgSplit.find((x) => x.startsWith('Expiration Time: ')); const expiredString = expiredLine ? expiredLine.replace('Expiration Time: ', '') : ''; - const expirationTime = new Date(expiredString); - const expired = +expirationTime < Date.now(); + const expirationTime = expiredString ? new Date(expiredString) : undefined; + const expired = expirationTime ? +expirationTime < Date.now() : false; const issuedLine = msgSplit.find((x) => x.startsWith('Issued At: ')); const issuedString = issuedLine ? issuedLine.replace('Issued At: ', '') : ''; @@ -54,6 +58,36 @@ function parseMessage(message: string): SiwxMessage { }; } +// SIWA is switching away from this, but we should still support it for now +function verifySignatureHashMaybeWrapped(publicKey: string, signature: string, message: Uint8Array): boolean { + const unwrappedSigned = message.length > 256 ? blake2AsU8a(message) : message; + + const unwrappedVerifyResult = signatureVerify(unwrappedSigned, signature, publicKey); + if (unwrappedVerifyResult.isValid) { + return true; + } + + // Support both wrapped and unwrapped signatures + const wrappedSignedBytes = u8aWrapBytes(message); + const wrappedSigned = wrappedSignedBytes.length > 256 ? blake2AsU8a(wrappedSignedBytes) : wrappedSignedBytes; + const wrappedVerifyResult = signatureVerify(wrappedSigned, signature, publicKey); + + return wrappedVerifyResult.isValid; +} + +function verifySignatureMaybeWrapped(publicKey: string, signature: string, message: Uint8Array): boolean { + const unwrappedVerifyResult = signatureVerify(message, signature, publicKey); + if (unwrappedVerifyResult.isValid) { + return true; + } + + // Support both wrapped and unwrapped signatures + const wrappedSignedBytes = u8aWrapBytes(message); + const wrappedVerifyResult = signatureVerify(wrappedSignedBytes, signature, publicKey); + + return wrappedVerifyResult.isValid || verifySignatureHashMaybeWrapped(publicKey, signature, message); +} + function expect(test: boolean, errorMessage: string) { if (!test) throw new Error(errorMessage); } @@ -64,31 +98,41 @@ function validateLoginPayload( loginMsgDomain: string ): void { // Check that the userPublicKey signed the message - const signedMessage = payload.payload.message; - const verifyResult = signatureVerify(signedMessage, payload.signature.encodedValue, userPublicKey.encodedValue); - - expect(verifyResult.isValid, 'Login message signature failed'); + expect( + verifySignatureMaybeWrapped( + userPublicKey.encodedValue, + payload.signature.encodedValue, + stringToU8a(payload.payload.message) + ), + 'Login message signature failed' + ); // Validate the message contents - const msg = parseMessage(signedMessage); + const msg = parseMessage(payload.payload.message); expect( msg.domain === loginMsgDomain, `Message does not match expected domain. Message: ${msg.domain} Expected: ${loginMsgDomain}` ); - expect( - msg.address === userPublicKey.encodedValue, - `Message does not match expected user public key value. Message: ${msg.address}` - ); + // Match address encoding before comparing + // decodeAddress will throw if it cannot decode meaning bad address + try { + const msgAddr = decodeAddress(msg.address); + const userAddr = decodeAddress(userPublicKey.encodedValue); + // Hex for easy comparison + expect(u8aToHex(msgAddr) === u8aToHex(userAddr), 'Address mismatch'); + } catch (_e) { + throw new Error( + `Invalid address or message does not match bytes of expected user public key value. Message: ${msg.address} User: ${userPublicKey.encodedValue}` + ); + } - expect( - !msg.expired, - `Message does not match expected user public key value. Message: ${msg.expirationTime.toISOString()}` - ); + if (msg.expirationTime) { + expect(!msg.expired, `Message has expired. Message: ${msg.expirationTime.toISOString()}`); + } } -function validateSignature(key: string, signature: string, message: string) { - const verifyResult = signatureVerify(message, signature, key); - expect(verifyResult.isValid, 'Payload signature failed'); +function validateExtrinsicPayloadSignature(key: string, signature: string, message: string) { + expect(verifySignatureMaybeWrapped(key, signature, hexToU8a(message)), 'Payload signature failed'); } export async function validatePayloads(response: SiwfResponse, loginMsgDomain: string): Promise { @@ -99,19 +143,19 @@ export async function validatePayloads(response: SiwfResponse, loginMsgDomain: s case isPayloadLogin(payload): return validateLoginPayload(payload, response.userPublicKey, loginMsgDomain); case isPayloadAddProvider(payload): - return validateSignature( + return validateExtrinsicPayloadSignature( response.userPublicKey.encodedValue, payload.signature.encodedValue, serializeAddProviderPayloadHex(payload.payload) ); case isPayloadClaimHandle(payload): - return validateSignature( + return validateExtrinsicPayloadSignature( response.userPublicKey.encodedValue, payload.signature.encodedValue, serializeClaimHandlePayloadHex(payload.payload) ); case isPayloadItemActions(payload): - return validateSignature( + return validateExtrinsicPayloadSignature( response.userPublicKey.encodedValue, payload.signature.encodedValue, serializeItemActionsPayloadHex(payload.payload) diff --git a/libraries/js/src/request.test.ts b/libraries/js/src/request.test.ts index 09713d0d..29ba44b1 100644 --- a/libraries/js/src/request.test.ts +++ b/libraries/js/src/request.test.ts @@ -27,7 +27,7 @@ describe('request', () => { encodedValue: 'f6cL4wq1HUNx11TcvdABNf9UNXXoyH47mVUwT59tzSFRW8yDH', encoding: 'base58', format: 'ss58', - type: 'SR25519', + type: 'Sr25519', }, signature: { algo: 'SR25519', @@ -96,7 +96,7 @@ describe('request', () => { encodedValue: 'f6cL4wq1HUNx11TcvdABNf9UNXXoyH47mVUwT59tzSFRW8yDH', encoding: 'base58', format: 'ss58', - type: 'SR25519', + type: 'Sr25519', }, }, }); diff --git a/libraries/js/src/request.ts b/libraries/js/src/request.ts index 2fab1de4..691fdd35 100644 --- a/libraries/js/src/request.ts +++ b/libraries/js/src/request.ts @@ -107,7 +107,7 @@ export function buildSignedRequest( encodedValue: encodeAddress(signerPublicKey, 90), encoding: 'base58', format: 'ss58', - type: 'SR25519', + type: 'Sr25519', }, signature: { algo: 'SR25519', diff --git a/libraries/js/src/response.test.ts b/libraries/js/src/response.test.ts index 57d21988..58cd0082 100644 --- a/libraries/js/src/response.test.ts +++ b/libraries/js/src/response.test.ts @@ -84,4 +84,11 @@ describe('validateSiwfResponse', () => { 'Response failed to correctly parse or invalid content: {"foo":"bad"}' ); }); + + it('throws on a bad domain', async () => { + const example = await ExampleLogin(); + await expect(validateSiwfResponse(base64url(JSON.stringify(example)), 'bad.example.xyz')).to.rejects.toThrowError( + 'Message does not match expected domain. Message: localhost Expected: bad.example.xyz' + ); + }); }); diff --git a/libraries/js/src/types/general.ts b/libraries/js/src/types/general.ts index 45e45dd4..c387823d 100644 --- a/libraries/js/src/types/general.ts +++ b/libraries/js/src/types/general.ts @@ -9,7 +9,7 @@ export interface SiwfPublicKey { encodedValue: string; encoding: 'base58'; format: 'ss58'; - type: 'SR25519'; + type: 'Sr25519'; } // eslint-disable-next-line @typescript-eslint/no-explicit-any