summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorMatthew Miller <matthew@millerti.me>2020-07-02 18:32:34 -0700
committerMatthew Miller <matthew@millerti.me>2020-07-02 18:32:34 -0700
commit4c8255ab3d7e74dbfa767a3da375ddabf642ab99 (patch)
treebc56799288826f79160ab983dcd9a5121c488498
parent5c9824af8e938356509e4932f56aacc5cbd429d1 (diff)
Add hash check for downloaded statements
-rw-r--r--packages/server/src/metadata/metadataService.ts28
1 files changed, 25 insertions, 3 deletions
diff --git a/packages/server/src/metadata/metadataService.ts b/packages/server/src/metadata/metadataService.ts
index 0bb9a2e..276d6a1 100644
--- a/packages/server/src/metadata/metadataService.ts
+++ b/packages/server/src/metadata/metadataService.ts
@@ -3,6 +3,7 @@ import { Base64URLString } from '@simplewebauthn/typescript-types';
import fetch from 'node-fetch';
import { ENV_VARS } from '../helpers/constants';
+import toHash from '../helpers/toHash';
import parseJWT from './parseJWT';
@@ -10,6 +11,7 @@ const { ENABLE_MDS, MDS_TOC_URL, MDS_API_TOKEN } = ENV_VARS;
type CachedAAGUID = {
url: string;
+ hash: string;
statement?: MetadataStatement;
};
@@ -22,6 +24,7 @@ type CachedAAGUID = {
class MetadataService {
private cache: { [aaguid: string]: CachedAAGUID } = {};
private nextUpdate: Date = new Date(0);
+ private tocAlg = '';
/**
* Prepare the service to handle live data, or prepared data.
@@ -35,7 +38,7 @@ class MetadataService {
} else {
if (statements?.length) {
statements.forEach(statement => {
- this.cache[statement.aaguid] = { url: '', statement };
+ this.cache[statement.aaguid] = { url: '', hash: '', statement };
});
}
}
@@ -48,6 +51,10 @@ class MetadataService {
* as per the `nextUpdate` property in the initial TOC download.
*/
async getStatement(aaguid: string): Promise<MetadataStatement | undefined> {
+ if (!aaguid) {
+ return;
+ }
+
if (ENABLE_MDS) {
const now = new Date();
if (now > this.nextUpdate) {
@@ -66,8 +73,18 @@ class MetadataService {
const resp = await fetch(`${cached.url}?token=${MDS_API_TOKEN}`);
const data = await resp.text();
const statement: MetadataStatement = JSON.parse(base64url.decode(data));
- // Update the cached entry with the latest statement
- cached.statement = statement;
+
+ const hashAlg = this.tocAlg === 'ES256' ? 'SHA256' : undefined;
+ const calculatedHash = base64url.encode(toHash(data, hashAlg));
+
+ if (calculatedHash === cached.hash) {
+ // Update the cached entry with the latest statement
+ cached.statement = statement;
+ } else {
+ // From FIDO MDS docs: "Ignore the downloaded metadata statement if the hash value doesn't
+ // match."
+ cached.statement = undefined;
+ }
}
return cached.statement;
@@ -83,6 +100,7 @@ class MetadataService {
// Break apart the JWT we get back
const parsedJWT = parseJWT<MDSJWTTOCHeader, MDSJWTTOCPayload>(data);
+ const header = parsedJWT[0];
const payload = parsedJWT[1];
// Convert the nextUpdate property into a Date so we can detemrine when to redownload
@@ -94,6 +112,9 @@ class MetadataService {
parseInt(day, 10),
);
+ // Store the header `alg` so we know what to use when verifying metadata statement hashes
+ this.tocAlg = header.alg;
+
// Prepare the in-memory cache of statements.
for (const entry of payload.entries) {
// Only cache entries with an `aaguid`
@@ -101,6 +122,7 @@ class MetadataService {
const _entry = entry as TOCAAGUIDEntry;
const cached: CachedAAGUID = {
url: entry.url,
+ hash: entry.hash,
};
this.cache[_entry.aaguid] = cached;