diff --git a/lib/fetchUnl.mjs b/lib/fetchUnl.mjs index 94eccc9..1c6b0b8 100644 --- a/lib/fetchUnl.mjs +++ b/lib/fetchUnl.mjs @@ -10,126 +10,132 @@ import address from 'ripple-address-codec' const fetchUnl = (url, master_public_key) => { return new Promise(async (resolve, reject) => { - const codec = { address, } - const assert = (c, m) => { - if (!c) reject("Invalid manifest: " + (m ? m : "")); - } - - const parse_manifest = buf => { - let man = {} - let upto = 0 - - let verify_fields = [Buffer.from('MAN\x00', 'utf-8')]; - let last_signing = 0; - - // sequence number - assert(buf[upto++] == 0x24, "Missing Sequence Number") - man['Sequence'] = (buf[upto] << 24) + (buf[upto+1] << 16) + (buf[upto+2] << 8) + buf[upto+3] - upto += 4 - - // public key - assert(buf[upto++] == 0x71, "Missing Public Key") // type 7 = VL, 1 = PublicKey - assert(buf[upto++] == 33, "Missing Public Key size") // one byte size - man['PublicKey'] = buf.slice(upto, upto + 33).toString('hex') - upto += 33 - - // signing public key - assert(buf[upto++] == 0x73, "Missing Signing Public Key") // type 7 = VL, 3 = SigningPubKey - assert(buf[upto++] == 33, "Missing Signing Public Key size") // one byte size - man['SigningPubKey'] = buf.slice(upto, upto + 33).toString('hex') - upto += 33 - - // signature - verify_fields.push(buf.slice(last_signing, upto)) - assert(buf[upto++] == 0x76, "Missing Signature") // type 7 = VL, 6 = Signature - let signature_size = buf[upto++]; - man['Signature'] = buf.slice(upto, upto + signature_size).toString('hex') - upto += signature_size - last_signing = upto - - // domain field | optional - if (buf[upto] == 0x77) { - upto++ - let domain_size = buf[upto++] - man['Domain'] = buf.slice(upto, upto + domain_size).toString('utf-8') - upto += domain_size + try { + const codec = { address, } + const assert = (c, m) => { + if (!c) reject("Invalid manifest: " + (m ? m : "")); } - // master signature - verify_fields.push(buf.slice(last_signing, upto)) - assert(buf[upto++] == 0x70, "Missing Master Signature lead byte") // type 7 = VL, 0 = uncommon field - assert(buf[upto++] == 0x12, "Missing Master Signature follow byte") // un field = 0x12 master signature - let master_size = buf[upto++]; - man['MasterSignature'] = buf.slice(upto, upto + master_size).toString('hex') - upto += master_size - last_signing = upto // here in case more fields ever added below + const parse_manifest = buf => { + let man = {} + let upto = 0 - assert(upto == buf.length, "Extra bytes after end of manifest") + let verify_fields = [Buffer.from('MAN\x00', 'utf-8')]; + let last_signing = 0; - // for signature verification - man.without_signing_fields = Buffer.concat(verify_fields) - return man; - } + // sequence number + assert(buf[upto++] == 0x24, "Missing Sequence Number") + man['Sequence'] = (buf[upto] << 24) + (buf[upto+1] << 16) + (buf[upto+2] << 8) + buf[upto+3] + upto += 4 - const unlData = await fetch(url) - const json = await unlData.json() + // public key + assert(buf[upto++] == 0x71, "Missing Public Key") // type 7 = VL, 1 = PublicKey + assert(buf[upto++] == 33, "Missing Public Key size") // one byte size + man['PublicKey'] = buf.slice(upto, upto + 33).toString('hex') + upto += 33 - // initial json validation - assert(json.public_key !== undefined, "public key missing from vl") - assert(json.signature !== undefined, "signature missing from vl") - assert(json.version !== undefined, "version missing from vl") - assert(json.manifest !== undefined, "manifest missing from vl") - assert(json.blob !== undefined, "blob missing from vl") - assert(json.version == 1, "vl version != 1") + // signing public key + assert(buf[upto++] == 0x73, "Missing Signing Public Key") // type 7 = VL, 3 = SigningPubKey + assert(buf[upto++] == 33, "Missing Signing Public Key size") // one byte size + man['SigningPubKey'] = buf.slice(upto, upto + 33).toString('hex') + upto += 33 - // check key is recognised - if (master_public_key) - assert(json.public_key.toUpperCase() == master_public_key.toUpperCase(), - "Provided VL key does not match") - else - master_public_key = json.public_key.toUpperCase() + // signature + verify_fields.push(buf.slice(last_signing, upto)) + assert(buf[upto++] == 0x76, "Missing Signature") // type 7 = VL, 6 = Signature + let signature_size = buf[upto++]; + man['Signature'] = buf.slice(upto, upto + signature_size).toString('hex') + upto += signature_size + last_signing = upto - // parse blob - let blob = Buffer.from(json.blob, 'base64') + // domain field | optional + if (buf[upto] == 0x77) { + upto++ + let domain_size = buf[upto++] + man['Domain'] = buf.slice(upto, upto + domain_size).toString('utf-8') + upto += domain_size + } - // parse manifest - const manifest = parse_manifest(Buffer.from(json.manifest, 'base64')) + // master signature + verify_fields.push(buf.slice(last_signing, upto)) + assert(buf[upto++] == 0x70, "Missing Master Signature lead byte") // type 7 = VL, 0 = uncommon field + assert(buf[upto++] == 0x12, "Missing Master Signature follow byte") // un field = 0x12 master signature + let master_size = buf[upto++]; + man['MasterSignature'] = buf.slice(upto, upto + master_size).toString('hex') + upto += master_size + last_signing = upto // here in case more fields ever added below - // verify manifest signature and payload signature - const master_key = ed25519.keyFromPublic(master_public_key.slice(2), 'hex') - assert(master_key.verify(manifest.without_signing_fields, manifest.MasterSignature), - "Master signature in master manifest does not match vl key") - let signing_key = ed25519.keyFromPublic(manifest.SigningPubKey.slice(2), 'hex') - assert(signing_key.verify(blob.toString('hex'), json.signature), - "Payload signature in mantifest failed verification") - blob = JSON.parse(blob) + assert(upto == buf.length, "Extra bytes after end of manifest") - assert(blob.validators !== undefined, "validators missing from blob") + // for signature verification + man.without_signing_fields = Buffer.concat(verify_fields) + return man; + } - // parse manifests inside blob (actual validator list) - let unl = {} - for (let idx in blob.validators) { - assert(blob.validators[idx].manifest !== undefined, - "validators list in blob contains invalid entry (missing manifest)") - assert(blob.validators[idx].validation_public_key !== undefined, - "validators list in blob contains invalid entry (missing validation public key)") - - let manifest = parse_manifest(Buffer.from(blob.validators[idx].manifest, 'base64')) + const unlData = await fetch(url) + const json = await unlData.json() - // verify signature - signing_key = ed25519.keyFromPublic(blob.validators[idx].validation_public_key.slice(2), 'hex') + // initial json validation + assert(json.public_key !== undefined, "public key missing from vl") + assert(json.signature !== undefined, "signature missing from vl") + assert(json.version !== undefined, "version missing from vl") + assert(json.manifest !== undefined, "manifest missing from vl") + assert(json.blob !== undefined, "blob missing from vl") + assert(json.version == 1, "vl version != 1") - assert(signing_key.verify(manifest.without_signing_fields, manifest.MasterSignature), - "Validation manifest " + idx + " signature verification failed") + // check key is recognised + if (master_public_key) + assert(json.public_key.toUpperCase() == master_public_key.toUpperCase(), + "Provided VL key does not match") + else + master_public_key = json.public_key.toUpperCase() - blob.validators[idx].validation_public_key = Buffer.from(blob.validators[idx].validation_public_key, 'hex') - blob.validators[idx].manifest = manifest + // parse blob + let blob = Buffer.from(json.blob, 'base64') + + // parse manifest + const manifest = parse_manifest(Buffer.from(json.manifest, 'base64')) + + // verify manifest signature and payload signature + const master_key = ed25519.keyFromPublic(master_public_key.slice(2), 'hex') + assert(master_key.verify(manifest.without_signing_fields, manifest.MasterSignature), + "Master signature in master manifest does not match vl key") + let signing_key = ed25519.keyFromPublic(manifest.SigningPubKey.slice(2), 'hex') + assert(signing_key.verify(blob.toString('hex'), json.signature), + "Payload signature in mantifest failed verification") + blob = JSON.parse(blob) + + assert(blob.validators !== undefined, "validators missing from blob") + + // parse manifests inside blob (actual validator list) + let unl = {} + for (let idx in blob.validators) { + assert(blob.validators[idx].manifest !== undefined, + "validators list in blob contains invalid entry (missing manifest)") + assert(blob.validators[idx].validation_public_key !== undefined, + "validators list in blob contains invalid entry (missing validation public key)") - let nodepub = codec.address.encodeNodePublic(Buffer.from(manifest.SigningPubKey, 'hex')) - unl[nodepub] = manifest.SigningPubKey + let manifest = parse_manifest(Buffer.from(blob.validators[idx].manifest, 'base64')) + + // verify signature + signing_key = ed25519.keyFromPublic(blob.validators[idx].validation_public_key.slice(2), 'hex') + + assert(signing_key.verify(manifest.without_signing_fields, manifest.MasterSignature), + "Validation manifest " + idx + " signature verification failed") + + blob.validators[idx].validation_public_key = Buffer.from(blob.validators[idx].validation_public_key, 'hex') + blob.validators[idx].manifest = manifest + + let nodepub = codec.address.encodeNodePublic(Buffer.from(manifest.SigningPubKey, 'hex')) + unl[nodepub] = manifest.SigningPubKey + } + + resolve({unl: {...unl}, vl: json}) + } catch (e) { + console.log('Error fetching UNL', e) } - resolve({unl: {...unl}, vl: json}) + return resolve({}) }) } diff --git a/lib/unlData.mjs b/lib/unlData.mjs index ed2e2dc..7cc17c0 100644 --- a/lib/unlData.mjs +++ b/lib/unlData.mjs @@ -2,7 +2,7 @@ import { fetchUnl } from './fetchUnl.mjs' import 'dotenv/config' import assert from 'assert' -const unlCacheTimeSec = 120 +const unlCacheTimeSec = 60 class UNL { data = {} @@ -35,14 +35,25 @@ class UNL { this.fetching = true try { + // Always register, if fatal: waits too. Give it time to recover. + this.updated = new Date() + const unl = await fetchUnl(process.env.UNLURL, process.env.UNLKEY) + + if (!unl?.unl) { + if (this.hosts.length < 1) { + // This was the initial call, error out + console.log('Cannot fetch UNL at this time on first call, fatal error!') + process.exit(1) + } + throw Error('Cannot fetch UNL at this time') + } const unlHosts = Object.keys(unl.unl) console.log('Fetched UNL', process.env.UNLURL, 'found validators', unlHosts.length) if (unlHosts.length > 1) { this.data = unl this.hosts = unlHosts - this.updated = new Date() } } catch (e) { this.fetching = false