Add API data sanitization

This commit is contained in:
Yarmo Mackenbach 2021-10-15 22:23:29 +02:00
parent d89d970a15
commit 5b5ccbb590
No known key found for this signature in database
GPG key ID: 37367F4AF4087AD1
4 changed files with 229 additions and 20 deletions

View file

@ -29,17 +29,149 @@ more information on this, and how to apply and follow the GNU AGPL, see <https:/
*/ */
const router = require('express').Router() const router = require('express').Router()
const { check, validationResult } = require('express-validator') const { check, validationResult } = require('express-validator')
const Ajv = require("ajv")
const ajv = new Ajv({coerceTypes: true})
const kx = require('../../server') const kx = require('../../server')
const apiProfileSchema = {
type: "object",
properties: {
keyData: {
type: "object",
properties: {
fingerprint: {
type: "string"
},
users: {
type: "array",
items: {
type: "object",
properties: {
userData: {
type: "object",
properties: {
id: { type: "string" },
name: { type: "string" },
email: { type: "string" },
comment: { type: "string" },
isPrimary: { type: "boolean" },
isRevoked: { type: "boolean" },
}
},
claims: {
type: "array",
items: {
type: "object",
properties: {
claimVersion: { type: "integer" },
uri: { type: "string" },
fingerprint: { type: "string" },
status: { type: "string" },
matches: {
type: "array",
items: {
type: "object",
properties: {
serviceProvider: {
type: "object",
properties: {
type: { type: "string" },
name: { type: "string" },
}
},
match: {
type: "object",
properties: {
regularExpression: { type: "object" },
isAmbiguous: { type: "boolean" },
}
},
profile: {
type: "object",
properties: {
display: { type: "string" },
uri: { type: "string" },
qr: { type: "string" },
}
},
proof: {
type: "object",
properties: {
uri: { type: "string" },
request: {
type: "object",
properties: {
fetcher: { type: "string" },
access: { type: "string" },
format: { type: "string" },
data: { type: "object" },
}
},
}
},
claim: {
type: "object",
properties: {
format: { type: "string" },
relation: { type: "string" },
path: {
type: "array",
items: {
type: "string"
}
},
}
},
}
}
},
verification: {
type: "object"
},
}
}
},
}
}
},
primaryUserIndex: {
type: "integer"
},
key: {
type: "object",
properties: {
data: { type: "object" },
fetchMethod: { type: "string" },
uri: { type: "string" },
}
},
},
},
extra: {
type: "object",
properties: {
avatarURL: { type: "string" },
}
},
errors: {
type: "array"
},
},
required: ["keyData", "extra", "errors"],
additionalProperties: false
}
const apiProfileValidate = ajv.compile(apiProfileSchema)
const doVerification = async (data) => { const doVerification = async (data) => {
let promises = [] let promises = []
let results = [] let results = []
for (let iUser = 0; iUser < data.keyData.users.length; iUser++) { for (let iUser = 0; iUser < data.keyData.users.length; iUser++) {
const user = data.keyData.users[iUser]; const user = data.keyData.users[iUser]
for (let iClaim = 0; iClaim < user.claims.length; iClaim++) { for (let iClaim = 0; iClaim < user.claims.length; iClaim++) {
const claim = user.claims[iClaim]; const claim = user.claims[iClaim]
promises.push( promises.push(
new Promise(async (resolve, reject) => { new Promise(async (resolve, reject) => {
@ -59,6 +191,34 @@ const doVerification = async (data) => {
return data return data
} }
const sanitize = (data) => {
let results = []
const dataClone = JSON.parse(JSON.stringify(data))
for (let iUser = 0; iUser < dataClone.keyData.users.length; iUser++) {
const user = dataClone.keyData.users[iUser]
for (let iClaim = 0; iClaim < user.claims.length; iClaim++) {
const claim = user.claims[iClaim]
// TODO Fix upstream
if (!claim.verification) {
claim.verification = {}
}
data.keyData.users[iUser].claims[iClaim] = claim
}
}
const valid = apiProfileValidate(data)
if (!valid) {
throw new Error(`Profile data sanitization error`)
}
return data
}
router.get('/profile/fetch', router.get('/profile/fetch',
check('query').exists(), check('query').exists(),
check('protocol').optional().toLowerCase().isIn(["hkp", "wkd"]), check('protocol').optional().toLowerCase().isIn(["hkp", "wkd"]),
@ -100,7 +260,21 @@ router.get('/profile/fetch',
data = await doVerification(data) data = await doVerification(data)
} }
res.send(data) try {
// Sanitize JSON
data = sanitize(data);
} catch (error) {
data.keyData = {}
data.extra = {}
data.errors = [error.message]
}
let statusCode = 200
if (data.errors.length > 0) {
statusCode = 500
}
res.status(500).send(data)
} }
) )
@ -116,7 +290,21 @@ router.get('/profile/verify',
// Do verification // Do verification
data = await doVerification(req.query.data) data = await doVerification(req.query.data)
res.send(data) try {
// Sanitize JSON
data = sanitize(data);
} catch (error) {
data.keyData = {}
data.extra = {}
data.errors = [error.message]
}
let statusCode = 200
if (data.errors.length > 0) {
statusCode = 500
}
res.status(500).send(data)
} }
) )

View file

@ -4,6 +4,7 @@
"description": "A modern, secure and privacy-friendly platform to establish your decentralized online identity", "description": "A modern, secure and privacy-friendly platform to establish your decentralized online identity",
"main": "index.js", "main": "index.js",
"dependencies": { "dependencies": {
"ajv": "^8.6.3",
"bent": "^7.3.12", "bent": "^7.3.12",
"body-parser": "^1.19.0", "body-parser": "^1.19.0",
"dialog-polyfill": "^0.5.6", "dialog-polyfill": "^0.5.6",

View file

@ -37,7 +37,7 @@ const generateWKDProfile = async (id) => {
let keyData = await doip.keys.process(key.publicKey) let keyData = await doip.keys.process(key.publicKey)
keyData.key.fetchMethod = 'wkd' keyData.key.fetchMethod = 'wkd'
keyData.key.uri = key.fetchURL keyData.key.uri = key.fetchURL
keyData.key.data = null keyData.key.data = {}
keyData = processKeyData(keyData) keyData = processKeyData(keyData)
return { return {
@ -49,9 +49,9 @@ const generateWKDProfile = async (id) => {
}) })
.catch(err => { .catch(err => {
return { return {
key: null, key: {},
keyData: null, keyData: {},
extra: null, extra: {},
errors: [err.message] errors: [err.message]
} }
}) })
@ -63,7 +63,7 @@ const generateHKPProfile = async (id, keyserverDomain) => {
let keyData = await doip.keys.process(key.publicKey) let keyData = await doip.keys.process(key.publicKey)
keyData.key.fetchMethod = 'hkp' keyData.key.fetchMethod = 'hkp'
keyData.key.uri = key.fetchURL keyData.key.uri = key.fetchURL
keyData.key.data = null keyData.key.data = {}
keyData = processKeyData(keyData) keyData = processKeyData(keyData)
return { return {
@ -75,9 +75,9 @@ const generateHKPProfile = async (id, keyserverDomain) => {
}) })
.catch(err => { .catch(err => {
return { return {
key: null, key: {},
keyData: null, keyData: {},
extra: null, extra: {},
errors: [err.message] errors: [err.message]
} }
}) })
@ -88,7 +88,7 @@ const generateSignatureProfile = async (signature) => {
.then(async key => { .then(async key => {
let keyData = key.keyData let keyData = key.keyData
delete key.keyData delete key.keyData
keyData.key.data = null keyData.key.data = {}
keyData = processKeyData(keyData) keyData = processKeyData(keyData)
return { return {
@ -100,9 +100,9 @@ const generateSignatureProfile = async (signature) => {
}) })
.catch(err => { .catch(err => {
return { return {
key: null, key: {},
keyData: null, keyData: {},
extra: null, extra: {},
errors: [err.message] errors: [err.message]
} }
}) })
@ -114,7 +114,7 @@ const generateKeybaseProfile = async (username, fingerprint) => {
let keyData = await doip.keys.process(key.publicKey) let keyData = await doip.keys.process(key.publicKey)
keyData.key.fetchMethod = 'hkp' keyData.key.fetchMethod = 'hkp'
keyData.key.uri = key.fetchURL keyData.key.uri = key.fetchURL
keyData.key.data = null keyData.key.data = {}
keyData = processKeyData(keyData) keyData = processKeyData(keyData)
return { return {
@ -126,9 +126,9 @@ const generateKeybaseProfile = async (username, fingerprint) => {
}) })
.catch(err => { .catch(err => {
return { return {
key: null, key: {},
keyData: null, keyData: {},
extra: null, extra: {},
errors: [err.message] errors: [err.message]
} }
}) })

View file

@ -645,6 +645,16 @@ ajv@^6.12.3:
json-schema-traverse "^0.4.1" json-schema-traverse "^0.4.1"
uri-js "^4.2.2" uri-js "^4.2.2"
ajv@^8.6.3:
version "8.6.3"
resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.6.3.tgz#11a66527761dc3e9a3845ea775d2d3c0414e8764"
integrity sha512-SMJOdDP6LqTkD0Uq8qLi+gMwSt0imXLSV080qFVwJCpH9U6Mb+SUGHAXM0KNbcBPguytWyvFxcHgMLe2D2XSpw==
dependencies:
fast-deep-equal "^3.1.1"
json-schema-traverse "^1.0.0"
require-from-string "^2.0.2"
uri-js "^4.2.2"
ansi-align@^3.0.0: ansi-align@^3.0.0:
version "3.0.0" version "3.0.0"
resolved "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.0.tgz" resolved "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.0.tgz"
@ -2666,6 +2676,11 @@ json-schema-traverse@^0.4.1:
resolved "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz" resolved "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz"
integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==
json-schema-traverse@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz#ae7bcb3656ab77a73ba5c49bf654f38e6b6860e2"
integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==
json-schema@0.2.3: json-schema@0.2.3:
version "0.2.3" version "0.2.3"
resolved "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz" resolved "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz"
@ -3706,6 +3721,11 @@ require-directory@^2.1.1:
resolved "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz" resolved "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz"
integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I= integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I=
require-from-string@^2.0.2:
version "2.0.2"
resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909"
integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==
require-main-filename@^2.0.0: require-main-filename@^2.0.0:
version "2.0.0" version "2.0.0"
resolved "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz" resolved "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz"