cleanup key validation;
This commit is contained in:
Родитель
d2e390d6ba
Коммит
39cad35573
|
@ -124,3 +124,4 @@ testdata/issuer.jwks.public.json
|
|||
|
||||
# compiled js files
|
||||
js/*
|
||||
testdata/missing_kid_key.json
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
"istextorbinary": "^5.12.0",
|
||||
"jimp": "^0.16.1",
|
||||
"jpeg-js": "^0.4.2",
|
||||
"json-beautify": "^1.1.1",
|
||||
"jsqr": "^1.3.1",
|
||||
"node-jose": "^2.0.0",
|
||||
"pako": "^2.0.3",
|
||||
|
@ -36,10 +37,12 @@
|
|||
"@types/node-jose": "^1.1.5",
|
||||
"@types/pako": "^1.0.1",
|
||||
"@types/pngjs": "^6.0.0",
|
||||
"@types/qrcode": "^1.4.0",
|
||||
"@typescript-eslint/eslint-plugin": "^4.15.2",
|
||||
"@typescript-eslint/parser": "^4.15.2",
|
||||
"eslint": "^7.20.0",
|
||||
"jest": "^26.6.3",
|
||||
"qrcode": "^1.4.4",
|
||||
"ts-jest": "^26.5.2",
|
||||
"ts-node": "^9.1.1",
|
||||
"typescript": "^4.2.2"
|
||||
|
@ -1631,6 +1634,15 @@
|
|||
"integrity": "sha512-i99hy7Ki19EqVOl77WplDrvgNugHnsSjECVR/wUrzw2TJXz1zlUfT2ngGckR6xN7yFYaijsMAqPkOLx9HgUqHg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/qrcode": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/qrcode/-/qrcode-1.4.0.tgz",
|
||||
"integrity": "sha512-BwDnCjdZKVOyy6+SPJ4ph+0DAftZGn5JFCY/MhetdEQ8yF6+YndhJWlfdBP8vtMe0w5/FH29Xi6bnEwVWkU1LQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/raf": {
|
||||
"version": "3.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/raf/-/raf-3.4.0.tgz",
|
||||
|
@ -2400,6 +2412,22 @@
|
|||
"ieee754": "^1.1.13"
|
||||
}
|
||||
},
|
||||
"node_modules/buffer-alloc": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/buffer-alloc/-/buffer-alloc-1.2.0.tgz",
|
||||
"integrity": "sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"buffer-alloc-unsafe": "^1.1.0",
|
||||
"buffer-fill": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/buffer-alloc-unsafe": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz",
|
||||
"integrity": "sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/buffer-equal": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/buffer-equal/-/buffer-equal-0.0.1.tgz",
|
||||
|
@ -2408,6 +2436,12 @@
|
|||
"node": ">=0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/buffer-fill": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/buffer-fill/-/buffer-fill-1.0.0.tgz",
|
||||
"integrity": "sha1-+PeLdniYiO858gXNY39o5wISKyw=",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/buffer-from": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz",
|
||||
|
@ -3091,6 +3125,12 @@
|
|||
"node": ">= 10.14.2"
|
||||
}
|
||||
},
|
||||
"node_modules/dijkstrajs": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/dijkstrajs/-/dijkstrajs-1.0.1.tgz",
|
||||
"integrity": "sha1-082BIh4+pAdCz83lVtTpnpjdxxs=",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/dir-glob": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz",
|
||||
|
@ -5628,6 +5668,14 @@
|
|||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/json-beautify": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/json-beautify/-/json-beautify-1.1.1.tgz",
|
||||
"integrity": "sha512-17j+Hk2lado0xqKtUcyAjK0AtoHnPSIgktWRsEXgdFQFG9UnaGw6CHa0J7xsvulxRpFl6CrkDFHght1p5ZJc4A==",
|
||||
"bin": {
|
||||
"json-beautify": "bin/json-beautify"
|
||||
}
|
||||
},
|
||||
"node_modules/json-buffer": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
|
||||
|
@ -6856,6 +6904,218 @@
|
|||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/qrcode": {
|
||||
"version": "1.4.4",
|
||||
"resolved": "https://registry.npmjs.org/qrcode/-/qrcode-1.4.4.tgz",
|
||||
"integrity": "sha512-oLzEC5+NKFou9P0bMj5+v6Z40evexeE29Z9cummZXZ9QXyMr3lphkURzxjXgPJC5azpxcshoDWV1xE46z+/c3Q==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"buffer": "^5.4.3",
|
||||
"buffer-alloc": "^1.2.0",
|
||||
"buffer-from": "^1.1.1",
|
||||
"dijkstrajs": "^1.0.1",
|
||||
"isarray": "^2.0.1",
|
||||
"pngjs": "^3.3.0",
|
||||
"yargs": "^13.2.4"
|
||||
},
|
||||
"bin": {
|
||||
"qrcode": "bin/qrcode"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/qrcode/node_modules/ansi-regex": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
|
||||
"integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/qrcode/node_modules/ansi-styles": {
|
||||
"version": "3.2.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
|
||||
"integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"color-convert": "^1.9.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/qrcode/node_modules/cliui": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz",
|
||||
"integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"string-width": "^3.1.0",
|
||||
"strip-ansi": "^5.2.0",
|
||||
"wrap-ansi": "^5.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/qrcode/node_modules/color-convert": {
|
||||
"version": "1.9.3",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
|
||||
"integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"color-name": "1.1.3"
|
||||
}
|
||||
},
|
||||
"node_modules/qrcode/node_modules/color-name": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
|
||||
"integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/qrcode/node_modules/emoji-regex": {
|
||||
"version": "7.0.3",
|
||||
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz",
|
||||
"integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/qrcode/node_modules/find-up": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz",
|
||||
"integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"locate-path": "^3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/qrcode/node_modules/is-fullwidth-code-point": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
|
||||
"integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/qrcode/node_modules/isarray": {
|
||||
"version": "2.0.5",
|
||||
"resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz",
|
||||
"integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/qrcode/node_modules/locate-path": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz",
|
||||
"integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"p-locate": "^3.0.0",
|
||||
"path-exists": "^3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/qrcode/node_modules/p-locate": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz",
|
||||
"integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"p-limit": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/qrcode/node_modules/path-exists": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz",
|
||||
"integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/qrcode/node_modules/pngjs": {
|
||||
"version": "3.4.0",
|
||||
"resolved": "https://registry.npmjs.org/pngjs/-/pngjs-3.4.0.tgz",
|
||||
"integrity": "sha512-NCrCHhWmnQklfH4MtJMRjZ2a8c80qXeMlQMv2uVp9ISJMTt562SbGd6n2oq0PaPgKm7Z6pL9E2UlLIhC+SHL3w==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/qrcode/node_modules/string-width": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz",
|
||||
"integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"emoji-regex": "^7.0.1",
|
||||
"is-fullwidth-code-point": "^2.0.0",
|
||||
"strip-ansi": "^5.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/qrcode/node_modules/strip-ansi": {
|
||||
"version": "5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
|
||||
"integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"ansi-regex": "^4.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/qrcode/node_modules/wrap-ansi": {
|
||||
"version": "5.1.0",
|
||||
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz",
|
||||
"integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"ansi-styles": "^3.2.0",
|
||||
"string-width": "^3.0.0",
|
||||
"strip-ansi": "^5.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/qrcode/node_modules/yargs": {
|
||||
"version": "13.3.2",
|
||||
"resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz",
|
||||
"integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"cliui": "^5.0.0",
|
||||
"find-up": "^3.0.0",
|
||||
"get-caller-file": "^2.0.1",
|
||||
"require-directory": "^2.1.1",
|
||||
"require-main-filename": "^2.0.0",
|
||||
"set-blocking": "^2.0.0",
|
||||
"string-width": "^3.0.0",
|
||||
"which-module": "^2.0.0",
|
||||
"y18n": "^4.0.0",
|
||||
"yargs-parser": "^13.1.2"
|
||||
}
|
||||
},
|
||||
"node_modules/qrcode/node_modules/yargs-parser": {
|
||||
"version": "13.1.2",
|
||||
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz",
|
||||
"integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"camelcase": "^5.0.0",
|
||||
"decamelize": "^1.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/qs": {
|
||||
"version": "6.5.2",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz",
|
||||
|
@ -10674,6 +10934,15 @@
|
|||
"integrity": "sha512-i99hy7Ki19EqVOl77WplDrvgNugHnsSjECVR/wUrzw2TJXz1zlUfT2ngGckR6xN7yFYaijsMAqPkOLx9HgUqHg==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/qrcode": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/qrcode/-/qrcode-1.4.0.tgz",
|
||||
"integrity": "sha512-BwDnCjdZKVOyy6+SPJ4ph+0DAftZGn5JFCY/MhetdEQ8yF6+YndhJWlfdBP8vtMe0w5/FH29Xi6bnEwVWkU1LQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"@types/raf": {
|
||||
"version": "3.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/raf/-/raf-3.4.0.tgz",
|
||||
|
@ -11212,11 +11481,33 @@
|
|||
"ieee754": "^1.1.13"
|
||||
}
|
||||
},
|
||||
"buffer-alloc": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/buffer-alloc/-/buffer-alloc-1.2.0.tgz",
|
||||
"integrity": "sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"buffer-alloc-unsafe": "^1.1.0",
|
||||
"buffer-fill": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"buffer-alloc-unsafe": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz",
|
||||
"integrity": "sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg==",
|
||||
"dev": true
|
||||
},
|
||||
"buffer-equal": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/buffer-equal/-/buffer-equal-0.0.1.tgz",
|
||||
"integrity": "sha1-kbx0sR6kBbyRa8aqkI+q+ltKrEs="
|
||||
},
|
||||
"buffer-fill": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/buffer-fill/-/buffer-fill-1.0.0.tgz",
|
||||
"integrity": "sha1-+PeLdniYiO858gXNY39o5wISKyw=",
|
||||
"dev": true
|
||||
},
|
||||
"buffer-from": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz",
|
||||
|
@ -11746,6 +12037,12 @@
|
|||
"integrity": "sha512-Mv/TDa3nZ9sbc5soK+OoA74BsS3mL37yixCvUAQkiuA4Wz6YtwP/K47n2rv2ovzHZvoiQeA5FTQOschKkEwB0Q==",
|
||||
"dev": true
|
||||
},
|
||||
"dijkstrajs": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/dijkstrajs/-/dijkstrajs-1.0.1.tgz",
|
||||
"integrity": "sha1-082BIh4+pAdCz83lVtTpnpjdxxs=",
|
||||
"dev": true
|
||||
},
|
||||
"dir-glob": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz",
|
||||
|
@ -13722,6 +14019,11 @@
|
|||
"integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==",
|
||||
"dev": true
|
||||
},
|
||||
"json-beautify": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/json-beautify/-/json-beautify-1.1.1.tgz",
|
||||
"integrity": "sha512-17j+Hk2lado0xqKtUcyAjK0AtoHnPSIgktWRsEXgdFQFG9UnaGw6CHa0J7xsvulxRpFl6CrkDFHght1p5ZJc4A=="
|
||||
},
|
||||
"json-buffer": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
|
||||
|
@ -14701,6 +15003,181 @@
|
|||
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
|
||||
"integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A=="
|
||||
},
|
||||
"qrcode": {
|
||||
"version": "1.4.4",
|
||||
"resolved": "https://registry.npmjs.org/qrcode/-/qrcode-1.4.4.tgz",
|
||||
"integrity": "sha512-oLzEC5+NKFou9P0bMj5+v6Z40evexeE29Z9cummZXZ9QXyMr3lphkURzxjXgPJC5azpxcshoDWV1xE46z+/c3Q==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"buffer": "^5.4.3",
|
||||
"buffer-alloc": "^1.2.0",
|
||||
"buffer-from": "^1.1.1",
|
||||
"dijkstrajs": "^1.0.1",
|
||||
"isarray": "^2.0.1",
|
||||
"pngjs": "^3.3.0",
|
||||
"yargs": "^13.2.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"ansi-regex": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
|
||||
"integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==",
|
||||
"dev": true
|
||||
},
|
||||
"ansi-styles": {
|
||||
"version": "3.2.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
|
||||
"integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"color-convert": "^1.9.0"
|
||||
}
|
||||
},
|
||||
"cliui": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz",
|
||||
"integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"string-width": "^3.1.0",
|
||||
"strip-ansi": "^5.2.0",
|
||||
"wrap-ansi": "^5.1.0"
|
||||
}
|
||||
},
|
||||
"color-convert": {
|
||||
"version": "1.9.3",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
|
||||
"integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"color-name": "1.1.3"
|
||||
}
|
||||
},
|
||||
"color-name": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
|
||||
"integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
|
||||
"dev": true
|
||||
},
|
||||
"emoji-regex": {
|
||||
"version": "7.0.3",
|
||||
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz",
|
||||
"integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==",
|
||||
"dev": true
|
||||
},
|
||||
"find-up": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz",
|
||||
"integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"locate-path": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"is-fullwidth-code-point": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
|
||||
"integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=",
|
||||
"dev": true
|
||||
},
|
||||
"isarray": {
|
||||
"version": "2.0.5",
|
||||
"resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz",
|
||||
"integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==",
|
||||
"dev": true
|
||||
},
|
||||
"locate-path": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz",
|
||||
"integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"p-locate": "^3.0.0",
|
||||
"path-exists": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"p-locate": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz",
|
||||
"integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"p-limit": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"path-exists": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz",
|
||||
"integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=",
|
||||
"dev": true
|
||||
},
|
||||
"pngjs": {
|
||||
"version": "3.4.0",
|
||||
"resolved": "https://registry.npmjs.org/pngjs/-/pngjs-3.4.0.tgz",
|
||||
"integrity": "sha512-NCrCHhWmnQklfH4MtJMRjZ2a8c80qXeMlQMv2uVp9ISJMTt562SbGd6n2oq0PaPgKm7Z6pL9E2UlLIhC+SHL3w==",
|
||||
"dev": true
|
||||
},
|
||||
"string-width": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz",
|
||||
"integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"emoji-regex": "^7.0.1",
|
||||
"is-fullwidth-code-point": "^2.0.0",
|
||||
"strip-ansi": "^5.1.0"
|
||||
}
|
||||
},
|
||||
"strip-ansi": {
|
||||
"version": "5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
|
||||
"integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ansi-regex": "^4.1.0"
|
||||
}
|
||||
},
|
||||
"wrap-ansi": {
|
||||
"version": "5.1.0",
|
||||
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz",
|
||||
"integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ansi-styles": "^3.2.0",
|
||||
"string-width": "^3.0.0",
|
||||
"strip-ansi": "^5.0.0"
|
||||
}
|
||||
},
|
||||
"yargs": {
|
||||
"version": "13.3.2",
|
||||
"resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz",
|
||||
"integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"cliui": "^5.0.0",
|
||||
"find-up": "^3.0.0",
|
||||
"get-caller-file": "^2.0.1",
|
||||
"require-directory": "^2.1.1",
|
||||
"require-main-filename": "^2.0.0",
|
||||
"set-blocking": "^2.0.0",
|
||||
"string-width": "^3.0.0",
|
||||
"which-module": "^2.0.0",
|
||||
"y18n": "^4.0.0",
|
||||
"yargs-parser": "^13.1.2"
|
||||
}
|
||||
},
|
||||
"yargs-parser": {
|
||||
"version": "13.1.2",
|
||||
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz",
|
||||
"integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"camelcase": "^5.0.0",
|
||||
"decamelize": "^1.2.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"qs": {
|
||||
"version": "6.5.2",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz",
|
||||
|
|
|
@ -29,6 +29,7 @@
|
|||
"istextorbinary": "^5.12.0",
|
||||
"jimp": "^0.16.1",
|
||||
"jpeg-js": "^0.4.2",
|
||||
"json-beautify": "^1.1.1",
|
||||
"jsqr": "^1.3.1",
|
||||
"node-jose": "^2.0.0",
|
||||
"pako": "^2.0.3",
|
||||
|
@ -43,10 +44,12 @@
|
|||
"@types/node-jose": "^1.1.5",
|
||||
"@types/pako": "^1.0.1",
|
||||
"@types/pngjs": "^6.0.0",
|
||||
"@types/qrcode": "^1.4.0",
|
||||
"@typescript-eslint/eslint-plugin": "^4.15.2",
|
||||
"@typescript-eslint/parser": "^4.15.2",
|
||||
"eslint": "^7.20.0",
|
||||
"jest": "^26.6.3",
|
||||
"qrcode": "^1.4.4",
|
||||
"ts-jest": "^26.5.2",
|
||||
"ts-node": "^9.1.1",
|
||||
"typescript": "^4.2.2"
|
||||
|
|
|
@ -29,6 +29,7 @@ export enum ErrorCode {
|
|||
INVALID_WRONG_USE,
|
||||
INVALID_MISSING_KID,
|
||||
INVALID_WRONG_KID,
|
||||
INVALID_SCHEMA,
|
||||
INVALID_SCHEMA,
|
||||
INVALID_PRIVATE,
|
||||
INVALID_UNKNOWN
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ import stream from 'stream';
|
|||
import {promisify} from 'util';
|
||||
import {JWK} from 'node-jose';
|
||||
import got from 'got';
|
||||
import { svgToQRImage } from './image';
|
||||
|
||||
|
||||
const outPath = 'testdata';
|
||||
|
@ -76,6 +77,7 @@ async function fetchExamples(outdir: string) : Promise<void> {
|
|||
const issuerPrivateKeyUrl = 'https://raw.githubusercontent.com/smart-on-fhir/health-cards/main/generate-examples/src/config/issuer.jwks.private.json';
|
||||
const issuerPublicKeyFileName = 'issuer.jwks.public.json';
|
||||
|
||||
|
||||
async function fetchKeys(outdir: string) : Promise<void> {
|
||||
|
||||
const filePath = path.join(outdir, issuerPublicKeyFileName);
|
||||
|
@ -92,10 +94,23 @@ async function fetchKeys(outdir: string) : Promise<void> {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
// for each .svg file, generate a png, jpg, and bmp QR image
|
||||
async function generateImagesFromSvg(dir: string) {
|
||||
const files = fs.readdirSync(dir);
|
||||
for (let i = 0; i < files.length; i++) {
|
||||
const file = path.join(dir, files[i]);
|
||||
if (path.extname(file) === '.svg') {
|
||||
await svgToQRImage(file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// We have to wrap these calls in an async function for ES5 support
|
||||
// Typescript error: Top-level 'await' expressions are only allowed when the 'module' option is set to 'esnext'
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
(async () => {
|
||||
void (async () => {
|
||||
await fetchExamples(outPath);
|
||||
await fetchKeys(outPath);
|
||||
await generateImagesFromSvg(outPath);
|
||||
})();
|
||||
|
|
|
@ -7,7 +7,7 @@ import { ErrorCode } from './error';
|
|||
import fhirBundleSchema from '../schema/fhir-bundle-schema.json';
|
||||
import Log from './logger';
|
||||
import { ValidationResult } from './validate';
|
||||
|
||||
import beautify from 'json-beautify'
|
||||
|
||||
export const schema = fhirBundleSchema;
|
||||
|
||||
|
@ -44,8 +44,18 @@ export function validate(fhirBundleText: string): ValidationResult {
|
|||
|
||||
|
||||
output.info("Fhir Bundle Contents:");
|
||||
output.info(JSON.stringify(fhirBundle, null, 2));
|
||||
output.info(beautify(fhirBundle, null as unknown as Array<string>, 3, 100));
|
||||
|
||||
|
||||
return { result: fhirBundle, log: output };
|
||||
}
|
||||
|
||||
|
||||
// payload .vc.credentialSubject.fhirBundle is created:
|
||||
// without Resource.id elements
|
||||
// without Resource.meta elements
|
||||
// without Resource.text elements
|
||||
// without CodeableConcept.text elements
|
||||
// without Coding.display elements
|
||||
// with Bundle.entry.fullUrl populated with short resource-scheme URIs (e.g., {"fullUrl": "resource:0})
|
||||
// with Reference.reference populated with short resource-scheme URIs (e.g., {"patient": {"reference": "resource:0"}})
|
|
@ -4,8 +4,7 @@
|
|||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import jose, { JWK } from 'node-jose';
|
||||
import svg2img from 'svg2img';
|
||||
import Jimp from 'jimp';
|
||||
|
||||
|
||||
interface KeyGenerationArgs {
|
||||
kty: string;
|
||||
|
@ -47,47 +46,3 @@ generateAndStoreKey('wrong_use_key.json', { kty: 'EC', size: 'P-256', props: { a
|
|||
generateAndStoreKey('wrong_alg_key.json', { kty: 'EC', size: 'P-256', props: { alg: 'ES256K', crv: 'P-256', use: 'sig' } });
|
||||
generateAndStoreKey('wrong_kty_key.json', { kty: 'RSA', size: 2048 });
|
||||
generateAndStoreKey('missing_kid_key.json', { kty: 'EC', size: 'P-256', props: { alg: 'ES256', crv: 'P-256', use: 'sig' } });
|
||||
|
||||
|
||||
function svgToImage(filePath: string): Promise<unknown> {
|
||||
|
||||
const baseFileName = filePath.slice(0, filePath.lastIndexOf('.'));
|
||||
|
||||
return new
|
||||
Promise<Buffer>((resolve, reject) => {
|
||||
svg2img(filePath, { width: 600, height: 600 },
|
||||
(error: unknown, buffer: Buffer) => {
|
||||
error ? reject("Could not create image from svg") : resolve(buffer);
|
||||
});
|
||||
})
|
||||
.then((buffer) => {
|
||||
fs.writeFileSync(baseFileName + '.png', buffer);
|
||||
return Jimp.read(baseFileName + '.png');
|
||||
})
|
||||
.then(png => {
|
||||
return Promise.all([
|
||||
png.write(baseFileName + '.bmp'),
|
||||
png.grayscale().quality(100).write(baseFileName + '.jpg')
|
||||
]);
|
||||
})
|
||||
.catch(err => { console.error(err); });
|
||||
}
|
||||
|
||||
|
||||
async function generateImagesFromSvg(dir: string) {
|
||||
|
||||
const files = fs.readdirSync(dir);
|
||||
|
||||
for (let i = 0; i < files.length; i++) {
|
||||
const file = path.join(dir, files[i]);
|
||||
if (path.extname(file) === '.svg') {
|
||||
await svgToImage(file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// TODO: generate files with missing algs, once omit is implemented
|
||||
|
||||
void generateImagesFromSvg(outdir);
|
||||
|
|
56
src/image.ts
56
src/image.ts
|
@ -9,6 +9,8 @@ import { FileInfo } from './file';
|
|||
import * as qr from './qr';
|
||||
import { PNG } from 'pngjs';
|
||||
import fs from 'fs';
|
||||
import Jimp from 'jimp';
|
||||
import { toFile, QRCodeSegment } from 'qrcode';
|
||||
|
||||
|
||||
export async function validate(images: FileInfo[]): Promise<{ result: JWS | undefined, log: Log }> {
|
||||
|
@ -19,11 +21,11 @@ export async function validate(images: FileInfo[]): Promise<{ result: JWS | unde
|
|||
'QR image');
|
||||
|
||||
|
||||
const shcStrings : SHC[] = [];
|
||||
const shcStrings: SHC[] = [];
|
||||
|
||||
for (let i = 0; i < images.length; i++) {
|
||||
const shc = await decode(images[i], log);
|
||||
if(shc === undefined) return {result: undefined, log: log};
|
||||
if (shc === undefined) return { result: undefined, log: log };
|
||||
shcStrings.push(shc);
|
||||
log.info(images[i].name + " decoded");
|
||||
log.debug(images[i].name + ' = ' + shc);
|
||||
|
@ -42,7 +44,7 @@ async function decode(fileInfo: FileInfo, log: Log): Promise<string | undefined>
|
|||
|
||||
let svgBuffer;
|
||||
|
||||
switch (fileInfo.fileType) {
|
||||
switch (fileInfo.fileType) {
|
||||
|
||||
case 'svg':
|
||||
svgBuffer = await svgToImageBuffer(fileInfo.buffer.toString(), log);
|
||||
|
@ -93,7 +95,7 @@ function decodeQrBuffer(fileInfo: FileInfo, log: Log): string | undefined {
|
|||
//const png = PNG.sync.read(image);
|
||||
const data = fileInfo.image;
|
||||
|
||||
if(!data) {
|
||||
if (!data) {
|
||||
log.fatal('Could not read image data from : ' + fileInfo.name);
|
||||
return undefined;
|
||||
}
|
||||
|
@ -107,4 +109,48 @@ function decodeQrBuffer(fileInfo: FileInfo, log: Log): string | undefined {
|
|||
}
|
||||
|
||||
return code.data;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export function svgToQRImage(filePath: string): Promise<unknown> {
|
||||
|
||||
const baseFileName = filePath.slice(0, filePath.lastIndexOf('.'));
|
||||
|
||||
return new
|
||||
Promise<Buffer>((resolve, reject) => {
|
||||
svg2img(filePath, { width: 600, height: 600 },
|
||||
(error: unknown, buffer: Buffer) => {
|
||||
error ? reject("Could not create image from svg") : resolve(buffer);
|
||||
});
|
||||
})
|
||||
.then((buffer) => {
|
||||
fs.writeFileSync(baseFileName + '.png', buffer);
|
||||
return Jimp.read(baseFileName + '.png');
|
||||
})
|
||||
.then(png => {
|
||||
return Promise.all([
|
||||
png.write(baseFileName + '.bmp'),
|
||||
png.grayscale().quality(100).write(baseFileName + '.jpg')
|
||||
]);
|
||||
})
|
||||
.catch(err => { console.error(err); });
|
||||
}
|
||||
|
||||
|
||||
export async function dataToQRImage(path: string, data: QRCodeSegment[]) : Promise<void> {
|
||||
|
||||
await toFile(path, data, { type: 'png', errorCorrectionLevel: 'low' })
|
||||
.catch((error) => {
|
||||
throw error;
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
|
||||
// const exampleQrCodes: string[] = await Promise.all(
|
||||
// qrSet.map((qrSegments): Promise<string> => new Promise((resolve, reject) =>
|
||||
// QrCode.toString(qrSegments, { type: 'svg', errorCorrectionLevel: 'low' }, function (err: any, result: string) {
|
||||
// if (err) return reject(err);
|
||||
// resolve(result as string);
|
||||
// })
|
||||
// )));
|
|
@ -8,10 +8,11 @@ import * as jwsPayload from './jws-payload';
|
|||
import * as keys from './keys';
|
||||
import pako from 'pako';
|
||||
import got from 'got';
|
||||
import jose, { JWK } from 'node-jose';
|
||||
import jose from 'node-jose';
|
||||
import path from 'path';
|
||||
import Log from './logger';
|
||||
import { ValidationResult } from './validate';
|
||||
import { verifyHealthCardIssuerKey } from './shcKeyValidator';
|
||||
|
||||
|
||||
//const MAX_JWS_LENGTH = 1195;
|
||||
|
@ -103,18 +104,15 @@ export async function validate(jws: JWS): Promise<ValidationResult> {
|
|||
}
|
||||
|
||||
|
||||
async function downloadKey(keyPath: string, log: Log): Promise<JWK.Key[] | undefined> {
|
||||
async function downloadKey(keyPath: string, log: Log): Promise<keys.KeySet | undefined> {
|
||||
|
||||
log.info("Retrieving issuer key from " + keyPath);
|
||||
|
||||
return await got(keyPath).json<{ keys: unknown[] }>()
|
||||
return await got(keyPath).json<keys.KeySet>()
|
||||
// TODO: split up download/parsing to provide finer-grainded error message
|
||||
.then(async keysObj => {
|
||||
log.debug("Downloaded issuer key : " + JSON.stringify(keysObj, null, 2));
|
||||
return [
|
||||
await keys.store.add(JSON.stringify(keysObj.keys[0]), 'json'),
|
||||
await keys.store.add(JSON.stringify(keysObj.keys[1]), 'json')
|
||||
];
|
||||
.then(async keySet => {
|
||||
log.debug("Downloaded issuer key(s) : ");
|
||||
return (await verifyHealthCardIssuerKey(keySet, log)).result as (keys.KeySet | undefined);
|
||||
})
|
||||
.catch(() => {
|
||||
log.error("Can't parse downloaded issuer keys as a key set",
|
||||
|
|
28
src/keys.ts
28
src/keys.ts
|
@ -4,6 +4,10 @@
|
|||
import { JWK } from "node-jose";
|
||||
import * as utils from './utils';
|
||||
|
||||
export type KeySet = {
|
||||
keys : JWK.Key[]
|
||||
}
|
||||
|
||||
export let store = JWK.createKeyStore();
|
||||
|
||||
export async function initKeyStoreFromFile(filePath: string): Promise<JWK.KeyStore> {
|
||||
|
@ -15,3 +19,27 @@ export async function initKeyStoreFromFile(filePath: string): Promise<JWK.KeySto
|
|||
return store;
|
||||
}
|
||||
|
||||
// export function importKeySet(keySet : keySet, log : Log) : void {
|
||||
|
||||
|
||||
|
||||
|
||||
// // failures will be recorded in the log. we can continue processing.
|
||||
// validateSchema(keySetSchema, keySet, log);
|
||||
|
||||
|
||||
// for (let i = 0; i < keySet.keys.length; i++) {
|
||||
|
||||
// let key: JWK.Key = keySet.keys[i];
|
||||
|
||||
// log.info('Validating key : ' + key.kid || i.toString());
|
||||
|
||||
// try {
|
||||
// key = await keyStore.add(key);
|
||||
// } catch (error) {
|
||||
// log.error('Error adding key to keyStore : ' + (error as Error).message, ErrorCode.INVALID_UNKNOWN);
|
||||
// return new ValidationResult(undefined, log);
|
||||
// }
|
||||
|
||||
// }
|
||||
|
||||
|
|
|
@ -7,6 +7,8 @@ import metaSchema from 'ajv/lib/refs/json-schema-draft-06.json';
|
|||
import fhirSchema from '../schema/fhir-definitions-schema.json';
|
||||
import Ajv, { AnySchemaObject } from "ajv";
|
||||
import { AnyValidateFunction } from 'ajv/dist/core';
|
||||
import { JWK } from 'node-jose';
|
||||
import { KeySet } from './keys';
|
||||
// http://json-schema.org/
|
||||
// https://github.com/ajv-validator/ajv
|
||||
|
||||
|
@ -15,7 +17,7 @@ import { AnyValidateFunction } from 'ajv/dist/core';
|
|||
const schemaCache: Record<string, AnyValidateFunction> = {};
|
||||
|
||||
|
||||
export function validateSchema(schema: AnySchemaObject | AnySchemaObject[], data: FhirBundle | JWS | JWSPayload | HealthCard, log: Log): boolean {
|
||||
export function validateSchema(schema: AnySchemaObject | AnySchemaObject[], data: FhirBundle | JWS | JWSPayload | HealthCard | KeySet, log: Log): boolean {
|
||||
|
||||
// by default, the validator will stop at the first failure. 'allErrors' allows it to keep going.
|
||||
const schemaId = (schema as { [key: string]: string })["$id"];
|
||||
|
|
|
@ -9,13 +9,9 @@ import * as validator from './validate';
|
|||
import { LogLevels } from './logger';
|
||||
import { getFileData } from './file';
|
||||
import { ErrorCode } from './error';
|
||||
import * as keys from './keys';
|
||||
import * as utils from './utils'
|
||||
import npmpackage from '../package.json';
|
||||
|
||||
|
||||
// function collect(value: string, previous: string[]) {
|
||||
// return previous.concat([value]);
|
||||
// }
|
||||
import { KeySet } from './keys';
|
||||
|
||||
/**
|
||||
* Defines the program
|
||||
|
@ -23,16 +19,18 @@ import npmpackage from '../package.json';
|
|||
* -h/--help auto-generated
|
||||
*/
|
||||
const loglevelChoices = ['debug', 'info', 'warning', 'error', 'fatal'];
|
||||
const artifactTypes = ['fhirbundle', 'jwspayload', 'jws', 'healthcard', 'qrnumeric', 'qr', 'jwkset'];
|
||||
const program = new Command();
|
||||
program.version(npmpackage.version, '-v, --version', 'display specification and tool version');
|
||||
program.requiredOption('-p, --path <path>', 'path of the file(s) to validate. Can be repeated for the qr and qrnumeric types, to provide multiple file chunks',
|
||||
(p: string, paths: string[]) => paths.concat([p]), []);
|
||||
program.addOption(new Option('-t, --type <type>', 'type of file to validate').choices(['fhirbundle', 'jwspayload', 'jws', 'healthcard', 'qrnumeric', 'qr', 'jwkset'])); // TODO: populate this from the validate enum.
|
||||
program.addOption(new Option('-t, --type <type>', 'type of file to validate').choices(artifactTypes)); // TODO: populate this from the validate enum.
|
||||
program.addOption(new Option('-l, --loglevel <loglevel>', 'set the minimum log level').choices(loglevelChoices).default('warning'));
|
||||
program.option('-o, --logout <path>', 'output path for log (if not specified log will be printed on console)');
|
||||
program.option('-k, --jwkset <key>', 'path to trusted issuer key set');
|
||||
program.parse(process.argv);
|
||||
|
||||
|
||||
export interface CliOptions {
|
||||
path: string[];
|
||||
type: validator.ValidationType;
|
||||
|
@ -42,83 +40,122 @@ export interface CliOptions {
|
|||
}
|
||||
|
||||
|
||||
//const log = new Log('main');
|
||||
function exit(message: string, exitCode: ErrorCode = 0): void {
|
||||
process.exitCode = exitCode;
|
||||
console.log(message);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Processes the program options and launches validation
|
||||
*/
|
||||
async function processOptions() {
|
||||
const options = program.opts() as CliOptions;
|
||||
let logFilePathIsValid = false;
|
||||
async function processOptions(options: CliOptions) {
|
||||
|
||||
// verify that the directory of the logfile exists
|
||||
|
||||
// map the --loglevel option to the Log.LogLevel enum
|
||||
const level = loglevelChoices.indexOf(options.loglevel) as LogLevels;
|
||||
|
||||
|
||||
// verify that the directory of the logfile exists, if provided
|
||||
if (options.logout) {
|
||||
const logDir = path.dirname(path.resolve(options.logout));
|
||||
if (!fs.existsSync(logDir)) {
|
||||
console.log('Cannot create log file at: ' + logDir);
|
||||
process.exitCode = ErrorCode.LOG_PATH_NOT_FOUND;
|
||||
return;
|
||||
return exit('Log file directory does not exist : ' + logDir, ErrorCode.LOG_PATH_NOT_FOUND);
|
||||
}
|
||||
logFilePathIsValid = true;
|
||||
}
|
||||
|
||||
if (options.path.length > 0 && options.type) {
|
||||
if (options.path.length > 1 && !(options.type === 'qr' || options.type === 'qrnumeric')) {
|
||||
console.log("Only the qr and qrnumeric types can have multiple path options")
|
||||
return; // TODO: add unit test
|
||||
}
|
||||
// read the file to validate
|
||||
const fileData = [];
|
||||
for (const path of options.path) {
|
||||
try {
|
||||
fileData.push(await getFileData(path));
|
||||
} catch (error) {
|
||||
console.log((error as Error).message);
|
||||
process.exitCode = ErrorCode.DATA_FILE_NOT_FOUND;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// if we have a key option, parse is and add it to the global key store
|
||||
if (options.jwkset) {
|
||||
// creates a new keyStore from a JSON key set file
|
||||
// const keyStore: JWK.KeyStore = await createKeyStoreFromFile(options.key);
|
||||
await keys.initKeyStoreFromFile(options.jwkset);
|
||||
//log.debug('keyStore');
|
||||
}
|
||||
|
||||
if (options.type === 'jwkset') {
|
||||
// validate a key file
|
||||
await validator.validateKey(fileData[0].buffer);
|
||||
} else {
|
||||
|
||||
// validate a health card
|
||||
const output = await validator.validateCard(fileData, options.type);
|
||||
|
||||
process.exitCode = output.log.exitCode;
|
||||
|
||||
const level = loglevelChoices.indexOf(options.loglevel) as LogLevels;
|
||||
|
||||
// append to the specified logfile
|
||||
if (logFilePathIsValid) {
|
||||
output.log.toFile(options.logout, options, true);
|
||||
} else {
|
||||
console.log(output.log.toString(level));
|
||||
}
|
||||
}
|
||||
|
||||
console.log("Validation completed ");
|
||||
|
||||
} else {
|
||||
console.log("Invalid option, missing 'path' or 'type'");
|
||||
// requires both --path and --type properties
|
||||
if (options.path.length === 0 || !options.type) {
|
||||
console.log("Invalid option, missing '--path' or '--type'");
|
||||
console.log(options);
|
||||
program.help();
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// only 'qr' and 'qrnumeric' --type supports multiple --path arguments
|
||||
if (options.path.length > 1 && !(options.type === 'qr') && !(options.type === 'qrnumeric')) {
|
||||
return exit("Only the 'qr' and 'qrnumeric' types can have multiple --path options");
|
||||
}
|
||||
|
||||
|
||||
// read the data file(s) to validate
|
||||
const fileData = [];
|
||||
for (let i = 0; i < options.path.length; i++) {
|
||||
const path = options.path[i];
|
||||
try {
|
||||
fileData.push(await getFileData(path));
|
||||
} catch (error) {
|
||||
return exit((error as Error).message, ErrorCode.DATA_FILE_NOT_FOUND);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// cannot provide a key file to both --path and --jwkset
|
||||
if (options.jwkset && options.type === 'jwkset') {
|
||||
return exit("Cannot pass a key file to both --path and --jwkset");
|
||||
}
|
||||
|
||||
|
||||
// if we have a key option, validate is and add it to the global key store
|
||||
if (options.jwkset) {
|
||||
|
||||
let keys;
|
||||
|
||||
try {
|
||||
keys = utils.loadJSONFromFile<KeySet>(options.jwkset);
|
||||
} catch (error) {
|
||||
return exit((error as Error).message, ErrorCode.DATA_FILE_NOT_FOUND);
|
||||
}
|
||||
|
||||
// validate the key/keyset
|
||||
const output = await validator.validateKey(keys);
|
||||
process.exitCode = output.log.exitCode;
|
||||
|
||||
|
||||
// if a logfile is specified, append to the specified logfile
|
||||
options.logout ?
|
||||
output.log.toFile(options.logout, options, true) :
|
||||
console.log(output.log.toString(level));
|
||||
}
|
||||
|
||||
|
||||
// validate the specified key-set
|
||||
if (options.type === 'jwkset') {
|
||||
|
||||
const keys = JSON.parse(fileData[0].buffer.toString('utf-8')) as KeySet;
|
||||
|
||||
// validate the key/keyset
|
||||
const output = await validator.validateKey(keys);
|
||||
process.exitCode = output.log.exitCode;
|
||||
|
||||
// if a logfile is specified, append to the specified logfile
|
||||
options.logout ?
|
||||
output.log.toFile(options.logout, options, true) :
|
||||
console.log(output.log.toString(level));
|
||||
}
|
||||
|
||||
|
||||
// validate the specified artifact ('fhirbundle', 'jwspayload', 'jws', 'healthcard', 'qrnumeric', 'qr')
|
||||
if (options.type !== 'jwkset') {
|
||||
|
||||
// validate a health card
|
||||
const output = await validator.validateCard(fileData, options.type);
|
||||
process.exitCode = output.log.exitCode;
|
||||
|
||||
// if a logfile is specified, append to the specified logfile
|
||||
options.logout ?
|
||||
output.log.toFile(options.logout, options, true) :
|
||||
console.log(output.log.toString(level));
|
||||
}
|
||||
|
||||
console.log("Validation completed");
|
||||
}
|
||||
|
||||
|
||||
// start the program
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
(async () => {
|
||||
await processOptions();
|
||||
// es5 compat requires await not be at the top level
|
||||
void (async () => {
|
||||
await processOptions(program.opts() as CliOptions);
|
||||
})();
|
||||
|
|
|
@ -4,62 +4,89 @@
|
|||
import Log from './logger';
|
||||
import jose, { JWK } from 'node-jose';
|
||||
import { ErrorCode } from './error';
|
||||
import { ValidationResult } from './validate';
|
||||
import { validateSchema } from './schema';
|
||||
import keySetSchema from '../schema/keyset-schema.json';
|
||||
import { KeySet, store } from './keys';
|
||||
|
||||
|
||||
export class shcKeyValidator {
|
||||
export async function verifyHealthCardIssuerKey(keySet: KeySet, log = new Log('Validate Key-Set')): Promise<ValidationResult> {
|
||||
|
||||
async verifyHealthCardIssuerKey(jwk: string | Buffer): Promise<Log> {
|
||||
|
||||
const log: Log = new Log('IssuerKey');
|
||||
const keyStore = JWK.createKeyStore();
|
||||
// check that keySet is valid
|
||||
if (!(keySet instanceof Object) || !keySet.keys || !(keySet.keys instanceof Array)) {
|
||||
log.fatal("keySet not valid. Expect {keys : JWK.Key[]}", ErrorCode.INVALID_SCHEMA);
|
||||
return new ValidationResult(undefined, log);
|
||||
}
|
||||
|
||||
return keyStore.add(jwk)
|
||||
// failures will be recorded in the log. we can continue processing.
|
||||
validateSchema(keySetSchema, keySet, log);
|
||||
|
||||
.then(async key => {
|
||||
// check that key type is 'EC'
|
||||
if (!key.kty) {
|
||||
log.error("'kty' missing in issuer key", ErrorCode.INVALID_MISSING_KTY);
|
||||
} else if (key.kty !== 'EC') {
|
||||
log.error("wrong key type in issuer key. expected: 'EC', actual: " + key.kty, ErrorCode.INVALID_WRONG_KTY);
|
||||
}
|
||||
|
||||
// check that EC curve is 'ES256'
|
||||
if (!key.alg) {
|
||||
log.error("'alg' missing in issuer key", ErrorCode.INVALID_MISSING_ALG);
|
||||
} else if (key.alg !== 'ES256') {
|
||||
log.error("wrong algorithm in issuer key. expected: 'ES256', actual: " + key.alg, ErrorCode.INVALID_WRONG_ALG);
|
||||
}
|
||||
for (let i = 0; i < keySet.keys.length; i++) {
|
||||
|
||||
// check that usage is 'sig'
|
||||
if (!key.use) {
|
||||
log.error("'use' missing in issuer key", ErrorCode.INVALID_MISSING_USE);
|
||||
} else if (key.use !== 'sig') {
|
||||
log.error("wrong usage in issuer key. expected: 'sig', actual: " + key.use, ErrorCode.INVALID_WRONG_USE);
|
||||
}
|
||||
let key: JWK.Key = keySet.keys[i];
|
||||
|
||||
// check that kid is properly generated
|
||||
if (!key.kid) {
|
||||
log.error("'kid' missing in issuer key", ErrorCode.INVALID_MISSING_KID);
|
||||
} else {
|
||||
await key.thumbprint('SHA-256')
|
||||
.then(tpDigest => {
|
||||
const thumbprint = jose.util.base64url.encode(tpDigest);
|
||||
if (key.kid !== thumbprint) {
|
||||
log.error("'kid' does not match thumbprint in issuer key. expected: "
|
||||
+ thumbprint + ", actual: " + key.kid, ErrorCode.INVALID_WRONG_KID);
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
log.error("Failed to calculate issuer key thumbprint : " + (err as Error).message, ErrorCode.INVALID_UNKNOWN);
|
||||
});
|
||||
}
|
||||
const keyName = 'key[' + (key.kid || i.toString()) + ']';
|
||||
|
||||
return log;
|
||||
})
|
||||
.catch(err => {
|
||||
log.error("Failed to parse issuer key : " + (err as Error).message, ErrorCode.INVALID_SCHEMA);
|
||||
return log;
|
||||
});
|
||||
log.info('Validating key : ' + keyName);
|
||||
log.debug("Key " + i.toString() + ":");
|
||||
log.debug(JSON.stringify(key, null, 3));
|
||||
|
||||
try {
|
||||
key = await store.add(key);
|
||||
} catch (error) {
|
||||
log.error('Error adding key to keyStore : ' + (error as Error).message, ErrorCode.INVALID_UNKNOWN);
|
||||
return new ValidationResult(undefined, log);
|
||||
}
|
||||
|
||||
|
||||
// check that kid is properly generated
|
||||
if (!key.kid) {
|
||||
log.error(keyName + ': ' + "'kid' missing in issuer key", ErrorCode.INVALID_MISSING_KID);
|
||||
} else {
|
||||
|
||||
await key.thumbprint('SHA-256')
|
||||
.then(tpDigest => {
|
||||
const thumbprint = jose.util.base64url.encode(tpDigest);
|
||||
if (key.kid !== thumbprint) {
|
||||
log.error(keyName + ': ' + "'kid' does not match thumbprint in issuer key. expected: "
|
||||
+ thumbprint + ", actual: " + key.kid, ErrorCode.INVALID_WRONG_KID);
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
log.error(keyName + ': ' + "Failed to calculate issuer key thumbprint : " + (err as Error).message, ErrorCode.INVALID_UNKNOWN);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// check that key type is 'EC'
|
||||
if (!key.kty) {
|
||||
log.error(keyName + ': ' + "'kty' missing in issuer key", ErrorCode.INVALID_MISSING_KTY);
|
||||
} else if (key.kty !== 'EC') {
|
||||
log.error(keyName + ': ' + "wrong key type in issuer key. expected: 'EC', actual: " + key.kty, ErrorCode.INVALID_WRONG_KTY);
|
||||
}
|
||||
|
||||
// check that EC curve is 'ES256'
|
||||
if (!key.alg) {
|
||||
log.error(keyName + ': ' + "'alg' missing in issuer key", ErrorCode.INVALID_MISSING_ALG);
|
||||
} else if (key.alg !== 'ES256') {
|
||||
log.warn(keyName + ': ' + "wrong algorithm in issuer key. expected: 'ES256', actual: " + key.alg, ErrorCode.INVALID_WRONG_ALG);
|
||||
}
|
||||
|
||||
// check that usage is 'sig'
|
||||
if (!key.use) {
|
||||
log.error(keyName + ': ' + "'use' missing in issuer key", ErrorCode.INVALID_MISSING_USE);
|
||||
} else if (key.use !== 'sig') {
|
||||
log.warn(keyName + ': ' + "wrong usage in issuer key. expected: 'sig', actual: " + key.use, ErrorCode.INVALID_WRONG_USE);
|
||||
}
|
||||
|
||||
// check for private key material
|
||||
if ((key as (JWK.Key & { d: string })).d) {
|
||||
log.error(keyName + ': ' + "key contains private key material.", ErrorCode.INVALID_PRIVATE);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return new ValidationResult(keySet, log);
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
// Licensed under the MIT license.
|
||||
|
||||
import Log from './logger';
|
||||
import { shcKeyValidator } from './shcKeyValidator';
|
||||
import { verifyHealthCardIssuerKey } from './shcKeyValidator';
|
||||
import { FileInfo } from './file';
|
||||
import { ErrorCode } from './error';
|
||||
import * as healthCard from './healthCard';
|
||||
|
@ -11,40 +11,29 @@ import * as jwsPayload from './jws-payload';
|
|||
import * as fhirBundle from './fhirBundle';
|
||||
import * as qr from './qr';
|
||||
import * as image from './image';
|
||||
|
||||
|
||||
|
||||
|
||||
/** Validate the issuer key */
|
||||
export async function validateKey(key: Buffer): Promise<void> {
|
||||
|
||||
const log = new Log('Validate Key');
|
||||
|
||||
log.debug('Validating key : ' + key.toString('utf-8'));
|
||||
|
||||
const keyValidator = new shcKeyValidator();
|
||||
|
||||
return keyValidator
|
||||
.verifyHealthCardIssuerKey(key)
|
||||
.then(() => { return Promise.resolve(); })
|
||||
.catch(err => {
|
||||
log.error("Error validating issuer key : " + (err as Error).message);
|
||||
return Promise.reject();
|
||||
});
|
||||
}
|
||||
//import { JWK } from 'node-jose';
|
||||
import { KeySet } from './keys';
|
||||
|
||||
|
||||
export type ValidationType = "qr" | "qrnumeric" | "healthcard" | "jws" | "jwspayload" | "fhirbundle" | "jwkset";
|
||||
|
||||
|
||||
|
||||
|
||||
export class ValidationResult {
|
||||
constructor(
|
||||
public result: HealthCard | JWS | JWSPayload | FhirBundle | undefined,
|
||||
public result: HealthCard | JWS | JWSPayload | FhirBundle | KeySet | undefined,
|
||||
public log: Log
|
||||
) { }
|
||||
}
|
||||
|
||||
|
||||
/** Validate the issuer key */
|
||||
export async function validateKey(keySet: KeySet): Promise<ValidationResult> {
|
||||
return await verifyHealthCardIssuerKey(keySet);
|
||||
}
|
||||
|
||||
|
||||
/** Validates SMART Health Card */
|
||||
export async function validateCard(fileData: FileInfo[], type: ValidationType): Promise<ValidationResult> {
|
||||
|
||||
|
@ -57,7 +46,7 @@ export async function validateCard(fileData: FileInfo[], type: ValidationType):
|
|||
break;
|
||||
|
||||
case "qrnumeric":
|
||||
result = await qr.validate(fileData.map((fi)=>fi.buffer.toString('utf-8')));
|
||||
result = await qr.validate(fileData.map((fi) => fi.buffer.toString('utf-8')));
|
||||
break;
|
||||
|
||||
case "healthcard":
|
||||
|
|
|
@ -9,6 +9,7 @@ import { getFileData } from '../src/file';
|
|||
import { ErrorCode } from '../src/error';
|
||||
import { LogLevels } from '../src/logger';
|
||||
|
||||
|
||||
const testdataDir = './testdata/';
|
||||
|
||||
|
||||
|
@ -119,3 +120,13 @@ test("Cards: invalid signature", async () => {
|
|||
expect(results).toHaveLength(1);
|
||||
expect(results[0].code).toBe(ErrorCode.JWS_VERIFICATION_ERROR);
|
||||
});
|
||||
|
||||
|
||||
|
||||
// test("Cards: rubbish QR code", async () => {
|
||||
// const shc = fs.readFileSync(path.join(testdataDir, 'example-00-f-qr-code-numeric-value-0.txt'));
|
||||
// const qrFile = 'foo.png';
|
||||
// await dataToQRImage(path.join(testdataDir, qrFile), [{ 'data': shc, 'mode': 'numeric'}]);
|
||||
// const result = await testCard(qrFile, "qr");
|
||||
// expect(result).toHaveLength(1);
|
||||
// });
|
||||
|
|
|
@ -87,7 +87,7 @@ function testCliCommand(command: string): number {
|
|||
}
|
||||
|
||||
// Valid calls to examples
|
||||
test("Cards: valid 00 health card", () => expect(testCliCommand('node . --path testdata/example-00-e-file.smart-health-card --type healthcard --loglevel info')).toBe(0));
|
||||
test("Cards: valid 00 health card", () => expect(testCliCommand('node . --path testdata/example-00-e-file.smart-health-card --type healthcard --loglevel info --jwkset testdata/issuer.jwks.public.json')).toBe(0));
|
||||
test("Cards: valid 00 jws", () => expect(testCliCommand('node . --path testdata/example-00-d-jws.txt --type jws --loglevel info')).toBe(0));
|
||||
test("Cards: valid 00 jws-payload", () => expect(testCliCommand('node . --path testdata/example-00-c-jws-payload-minified.json --type jwspayload --loglevel info')).toBe(0));
|
||||
test("Cards: valid 00 fhirBundle", () => expect(testCliCommand('node . --path testdata/example-00-a-fhirBundle.json --type fhirbundle --loglevel info')).toBe(0));
|
||||
|
|
|
@ -1,17 +1,18 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
import fs from 'fs';
|
||||
|
||||
import path from 'path';
|
||||
import { ErrorCode } from '../src/error';
|
||||
import { shcKeyValidator } from '../src/shcKeyValidator';
|
||||
import { LogLevels } from '../src/logger';
|
||||
import { verifyHealthCardIssuerKey } from '../src/shcKeyValidator';
|
||||
import * as utils from '../src/utils';
|
||||
const testdataDir = './testdata/';
|
||||
|
||||
async function testKey(fileName: string): Promise<ErrorCode[]> {
|
||||
const filePath = path.join(testdataDir, fileName);
|
||||
const keyValidator = new shcKeyValidator();
|
||||
const log = (await keyValidator.verifyHealthCardIssuerKey(fs.readFileSync(filePath))).log;
|
||||
return log.map(item => item.code);
|
||||
const result = (await verifyHealthCardIssuerKey({keys : [utils.loadJSONFromFile(filePath)]}));
|
||||
return result.log.flatten(LogLevels.WARNING).map(item => item.code);
|
||||
}
|
||||
|
||||
test("Keys: valid", async () => {
|
||||
|
|
Загрузка…
Ссылка в новой задаче