From bbba963ef8f85884811b108d361628fde639fbe2 Mon Sep 17 00:00:00 2001 From: Tyler Brockmeyer Date: Wed, 7 Aug 2024 10:11:16 -0500 Subject: [PATCH] fix mac creation, encryption modifiers, simplify walkAndDecrypt and decryptScalar, improve error output --- .gitignore | 1 + config.json | 64 ++++ package-lock.json | 553 ++++++++++++++++++++++++++++++-- package.json | 3 +- src/decode.ts | 158 ++++----- test/integration/decode.test.ts | 139 ++++++++ 6 files changed, 805 insertions(+), 113 deletions(-) create mode 100644 config.json create mode 100644 test/integration/decode.test.ts diff --git a/.gitignore b/.gitignore index fd4b647..f2c258d 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ npm-debug.log .DS_Store build secure.enc.json +/coverage \ No newline at end of file diff --git a/config.json b/config.json new file mode 100644 index 0000000..c5cc62d --- /dev/null +++ b/config.json @@ -0,0 +1,64 @@ +{ + "signingKey": "ENC[AES256_GCM,data:rHspVwwjrWPV0M0=,iv:wGXW91GOGflFeBY2YD30AKa++fXOPkC6YaRW3Xr3ytc=,tag:DmxZ1yCaTH507Us440BUMQ==,type:str]", + "asurionIdApi": [ + { + "url": "https://api.stage.asurionid.dev", + "tokenUri": "https://stage.asurionid.dev/oauth2/token", + "scope": "account:read account-profile:read", + "credentials": { + "username": "edsapi-api", + "password": "ENC[AES256_GCM,data:t2d7/MhOkWyKXHfz/WtCnBUqvxZYz270mklnER7U6qdF2QBG0wgV2w==,iv:TbvMXVOAptuefVsNbiewaVvVFSOQQXIDCWDsgoVUxkA=,tag:G5tTeXtcQhw2iw7FMYabxA==,type:str]" + } + } + ], + "eapi": [ + { + "tokenUri": "https://edpapi-npr-us-east-1.auth.us-east-1.amazoncognito.com/oauth2/token", + "scope": "customeragreementsapi/get customerclaimsapi/get customerapi/get customersegmentationapi/get att/get multi/get verizon/get", + "credentials": { + "username": "6gt6hkjh6co16c5vvf89r9jc3", + "password": "ENC[AES256_GCM,data:g/4jmFs+YNK0bjA/7/0qNqf5YfHT7XYTA2H6rWefRbZWT2lKFuxS4tXfZBoOXRxjH2j1,iv:xsq4wxdZ5c/tQn2HBI0WJi2T4nndtkaq2KBeV+IXlEY=,tag:VbxkMpvkhourFzNPE1a/8Q==,type:str]" + } + } + ], + "eds_att": { + "user": "eds_customer_eapi_ro", + "password": "ENC[AES256_GCM,data:PRXyJZ0MYXFUy3O9Vn44o+VsNkOnqzc1LwfYfAm/FtQ=,iv:Vj/bbDqMgcOmXyiA3JnzHqfCCadmB/Sd0hgp1OSuRZo=,tag:f++Form/UhWXLp+/X8eTLw==,type:str]", + "database": "customer" + }, + "eds_verizon": { + "user": "eds_customer_eapi_ro", + "password": "ENC[AES256_GCM,data:Avr4BqC/4l0uzqvEumjwwModwG2jAjlTEegOv7LA1/8=,iv:qBW5yiDKsD/c0VUu+Mp3hHaeoxscBJwI70ba29UaXA8=,tag:PErbIWcCitG8VVrTVy9HyA==,type:str]", + "database": "customer" + }, + "eds_multitenant": { + "user": "eds_customer_eapi_ro", + "password": "ENC[AES256_GCM,data:maytAikTxc1NG5yXcnjOj4WaQNjrP4BKoaQshTRMWTM=,iv:lhLhrm4ZUCtBSCIeEzI85rUbv5sZL7LoXaErduSRrs4=,tag:0XUsagrEx3mNXw5MAA9Zjg==,type:str]", + "database": "customer" + }, + "sops": { + "kms": [ + { + "arn": "arn:aws:kms:us-east-1:807093730725:key/mrk-15dd169b6bc94f68bcc6a903a51fdf36", + "created_at": "2022-05-19T18:53:27Z", + "enc": "AQICAHgHlRJ620cAlBZqYND/ifqINNW1hA7gHLDisqIRq3iCKwHpXAy/9rcS01IOkK71GtFVAAAAfjB8BgkqhkiG9w0BBwagbzBtAgEAMGgGCSqGSIb3DQEHATAeBglghkgBZQMEAS4wEQQMuVl20vE9eo8tXWUGAgEQgDs82KCy0eb199IeyBmg2CqUg/FG2CH/WoADG8AeureZWnr6WG1psLj9p33OaQbq0nsOtaL6y6sFxDMtHw==", + "aws_profile": "" + }, + { + "arn": "arn:aws:kms:us-east-2:807093730725:key/mrk-15dd169b6bc94f68bcc6a903a51fdf36", + "created_at": "2022-05-19T18:53:27Z", + "enc": "AQICAHgHlRJ620cAlBZqYND/ifqINNW1hA7gHLDisqIRq3iCKwFlnJYPQy3NTy6U0u7SU5YWAAAAfjB8BgkqhkiG9w0BBwagbzBtAgEAMGgGCSqGSIb3DQEHATAeBglghkgBZQMEAS4wEQQMBb+FUa2FazKvqiPaAgEQgDsuU6ndZdaZ7GlX8DY+B0BDKFGYGsc+6quro+GhYnBEVBYr+9VckmoOsguIRRzH9IWJjc6hiQI+qhPn8g==", + "aws_profile": "" + } + ], + "gcp_kms": null, + "azure_kv": null, + "hc_vault": null, + "age": null, + "lastmodified": "2024-08-06T17:51:49Z", + "mac": "ENC[AES256_GCM,data:jei4Ex7+vsYKewtvB1RQgkqXfbHEVhq+khDR39mp3pTOtQBTv1NzW32m4bf+goztdr//AQMsx+LUuu8Tlyuubq+v1FrH4RfrwDvGWizu51EVVsUY+YdVhaaauBQe/uJEEBRuJKeP4D1lvFo5/SKgOHssLbz+yWC6NTw7tv9MP6Y=,iv:Gea0PF9tmfY9plBPqOUiivTJqPONqkfY1jIYv1EtlF4=,tag:OAXXd9uZdC73uYLfcwFvrQ==,type:str]", + "pgp": null, + "encrypted_regex": "(password|[kK]ey)", + "version": "3.9.0" + } +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 2af01fe..179f892 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,6 +16,7 @@ "@semantic-release/changelog": "^6.0.3", "@semantic-release/git": "^10.0.1", "@types/node": "^20.4.1", + "c8": "^10.1.2", "eslint": "^8.44.0", "eslint-config-airbnb-typescript": "^17.0.0", "eslint-config-prettier": "^8.1.0", @@ -709,6 +710,12 @@ "node": ">=4" } }, + "node_modules/@bcoe/v8-coverage": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "dev": true + }, "node_modules/@colors/colors": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", @@ -821,6 +828,111 @@ "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", "dev": true }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true + }, + "node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/@jridgewell/resolve-uri": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", @@ -1074,6 +1186,16 @@ "@octokit/openapi-types": "^18.0.0" } }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "optional": true, + "engines": { + "node": ">=14" + } + }, "node_modules/@pkgr/utils": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/@pkgr/utils/-/utils-2.4.2.tgz", @@ -2096,6 +2218,12 @@ "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", "dev": true }, + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", + "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", + "dev": true + }, "node_modules/@types/json-schema": { "version": "7.0.12", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.12.tgz", @@ -2673,6 +2801,130 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/c8": { + "version": "10.1.2", + "resolved": "https://registry.npmjs.org/c8/-/c8-10.1.2.tgz", + "integrity": "sha512-Qr6rj76eSshu5CgRYvktW0uM0CFY0yi4Fd5D0duDXO6sYinyopmftUiJVuzBQxQcwQLor7JWDVRP+dUfCmzgJw==", + "dev": true, + "dependencies": { + "@bcoe/v8-coverage": "^0.2.3", + "@istanbuljs/schema": "^0.1.3", + "find-up": "^5.0.0", + "foreground-child": "^3.1.1", + "istanbul-lib-coverage": "^3.2.0", + "istanbul-lib-report": "^3.0.1", + "istanbul-reports": "^3.1.6", + "test-exclude": "^7.0.1", + "v8-to-istanbul": "^9.0.0", + "yargs": "^17.7.2", + "yargs-parser": "^21.1.1" + }, + "bin": { + "c8": "bin/c8.js" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "monocart-coverage-reports": "^2" + }, + "peerDependenciesMeta": { + "monocart-coverage-reports": { + "optional": true + } + } + }, + "node_modules/c8/node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/c8/node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/c8/node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/c8/node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/c8/node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/c8/node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/c8/node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/call-bind": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", @@ -2783,7 +3035,6 @@ "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", "dev": true, - "peer": true, "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", @@ -2913,6 +3164,12 @@ "node": ">=14" } }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true + }, "node_modules/core-util-is": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", @@ -3303,6 +3560,18 @@ "readable-stream": "^2.0.2" } }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, "node_modules/env-ci": { "version": "9.1.1", "resolved": "https://registry.npmjs.org/env-ci/-/env-ci-9.1.1.tgz", @@ -3538,7 +3807,6 @@ "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", "dev": true, - "peer": true, "engines": { "node": ">=6" } @@ -4227,6 +4495,34 @@ "is-callable": "^1.1.3" } }, + "node_modules/foreground-child": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.2.1.tgz", + "integrity": "sha512-PXUUyLqrR2XCWICfv6ukppP96sdFwWbNEnfEMt7jNsISjMsvaLNinAHNDYyvkyU+SZG2BTSbT5NjG+vZslfGTA==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.0", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/foreground-child/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/from2": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz", @@ -4296,7 +4592,6 @@ "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", "dev": true, - "peer": true, "engines": { "node": "6.* || 8.* || >= 10.*" } @@ -4615,6 +4910,12 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true + }, "node_modules/http-proxy-agent": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.0.tgz", @@ -4879,7 +5180,6 @@ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", "dev": true, - "peer": true, "engines": { "node": ">=8" } @@ -5173,6 +5473,57 @@ "node": ">=10.13" } }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-reports": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz", + "integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==", + "dev": true, + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, "node_modules/java-properties": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/java-properties/-/java-properties-1.0.2.tgz", @@ -5433,6 +5784,21 @@ "node": ">=12" } }, + "node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/make-error": { "version": "1.3.6", "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", @@ -5784,6 +6150,15 @@ "node": ">= 6" } }, + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, "node_modules/modify-values": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/modify-values/-/modify-values-1.0.1.tgz", @@ -9576,6 +9951,12 @@ "node": ">=6" } }, + "node_modules/package-json-from-dist": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.0.tgz", + "integrity": "sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw==", + "dev": true + }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -9641,6 +10022,28 @@ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", "dev": true }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true + }, "node_modules/path-type": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", @@ -10087,7 +10490,6 @@ "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", "dev": true, - "peer": true, "engines": { "node": ">=0.10.0" } @@ -10460,7 +10862,6 @@ "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dev": true, - "peer": true, "dependencies": { "lru-cache": "^6.0.0" }, @@ -10505,7 +10906,6 @@ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", "dev": true, - "peer": true, "dependencies": { "yallist": "^4.0.0" }, @@ -10787,7 +11187,6 @@ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, - "peer": true, "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -10797,12 +11196,20 @@ "node": ">=8" } }, - "node_modules/string-width/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, - "peer": true + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } }, "node_modules/string.prototype.trim": { "version": "1.2.7", @@ -10861,6 +11268,19 @@ "node": ">=8" } }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/strip-bom": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", @@ -11018,6 +11438,64 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/test-exclude": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-7.0.1.tgz", + "integrity": "sha512-pFYqmTw68LXVjeWJMST4+borgQP2AyMNbg1BpZh9LbyhUeNkeaPF9gzfPGUAnSMV3qPYdWUwDIjjCLiSDOl7vg==", + "dev": true, + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^10.4.1", + "minimatch": "^9.0.4" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/test-exclude/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/test-exclude/node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "dev": true, + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/test-exclude/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/text-extensions": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/text-extensions/-/text-extensions-1.9.0.tgz", @@ -11347,6 +11825,30 @@ "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", "dev": true }, + "node_modules/v8-to-istanbul": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", + "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.12", + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^2.0.0" + }, + "engines": { + "node": ">=10.12.0" + } + }, + "node_modules/v8-to-istanbul/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, "node_modules/validate-npm-package-license": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", @@ -11439,7 +11941,24 @@ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "dev": true, - "peer": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", @@ -11473,7 +11992,6 @@ "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", "dev": true, - "peer": true, "engines": { "node": ">=10" } @@ -11482,15 +12000,13 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true, - "peer": true + "dev": true }, "node_modules/yargs": { "version": "17.7.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", "dev": true, - "peer": true, "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", @@ -11519,7 +12035,6 @@ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", "dev": true, - "peer": true, "engines": { "node": ">=12" } diff --git a/package.json b/package.json index 9d38cee..127637f 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ "secure" ], "scripts": { - "test": "true", + "test": "c8 node --require ts-node/register --test test/integration/*.test.ts", "lint": "eslint src --ext .ts", "build": "tsc" }, @@ -48,6 +48,7 @@ "@semantic-release/changelog": "^6.0.3", "@semantic-release/git": "^10.0.1", "@types/node": "^20.4.1", + "c8": "^10.1.2", "eslint": "^8.44.0", "eslint-config-airbnb-typescript": "^17.0.0", "eslint-config-prettier": "^8.1.0", diff --git a/src/decode.ts b/src/decode.ts index e54fd8f..9888177 100644 --- a/src/decode.ts +++ b/src/decode.ts @@ -40,15 +40,19 @@ export interface EncodedTree { [property: string]: Json | undefined; } -type EncryptionModifier = (key: string) => boolean; -const checkEncryptedSuffix = (modifier: string) => (key: string) => - !key.endsWith(modifier); -const checkUnencryptedSuffix = (modifier: string) => (key: string) => - key.endsWith(modifier); -const checkUnencryptedRegex = (modifier: string) => (key: string) => - new RegExp(modifier).test(key); -const checkEncryptedRegex = (modifier: string) => (key: string) => - !new RegExp(modifier).test(key); +type EncryptionModifier = (path: string[]) => boolean; +const checkEncryptedSuffix = (modifier: string) => (path: string[]) => + path.some((key) => key.endsWith(modifier)); +const checkUnencryptedSuffix = (modifier: string) => (path: string[]) => + !path.some((key) => key.endsWith(modifier)); +const checkUnencryptedRegex = (modifier: string) => { + const re = new RegExp(modifier); + return (path: string[]) => !path.some((key) => re.test(key)); +}; +const checkEncryptedRegex = (modifier: string) => { + const re = new RegExp(modifier); + return (path: string[]) => path.some((key) => re.test(key)); +}; /** * Read the given file from the FileSytem and return the decoded data @@ -88,7 +92,7 @@ export async function decrypt(tree: JsonObject) { const key = await getKey(tree); - const encryptionModifier: EncryptionModifier = getEncryptionModifier(sops); + const shouldBeEncrypted: EncryptionModifier = getEncryptionModifier(sops); if (key === null) { throw new SopsError("missing key"); @@ -96,20 +100,10 @@ export async function decrypt(tree: JsonObject) { const digest = crypto.createHash("sha512"); - const result = walkAndDecrypt( - tree, - key, - "", - digest, - true, - false, - encryptionModifier, - ); + const result = walkAndDecrypt(tree, key, "", digest, [], shouldBeEncrypted); if (sops.mac) { - const hash = String( - decryptScalar(sops.mac, key, sops.lastmodified, null, false), - ); + const hash = String(decryptScalar(sops.mac, key, sops.lastmodified)); if (hash.toUpperCase() !== digest.digest("hex").toUpperCase()) { throw new Error("Hash mismatch"); @@ -154,20 +148,10 @@ function getEncryptionModifier( * Decrypt a single value, update the digest if provided */ export function decryptScalar( - value: string | boolean | number, - key: Buffer, + value: string, + key: Uint8Array, aad: string, - digest: crypto.Hash | null, - unencrypted: boolean, ): string | boolean | number { - if (unencrypted || typeof value !== "string") { - if (digest) { - digest.update(toBytes(value)); - } - - return value; - } - const valre = value.match( /^ENC\[AES256_GCM,data:(.+),iv:(.+),tag:(.+),type:(.+)\]/, ); @@ -188,10 +172,6 @@ export function decryptScalar( const cleartext = decryptor.update(encValue, undefined, "utf8") + decryptor.final("utf8"); - if (digest) { - digest.update(cleartext); - } - switch (valtype) { case "bytes": return cleartext; @@ -209,60 +189,49 @@ export function decryptScalar( } function walkAndDecrypt( - tree: EncodedTree, - key: Buffer, + value: Json, + key: Uint8Array, aad: string, digest: crypto.Hash, - isRoot: boolean, - unencrypted: boolean, - encryptionModifier: EncryptionModifier, + path: string[], + shouldBeEncrypted: EncryptionModifier, ): unknown { - const doValue = ( - value: Json, - caad: string, - unencrypted_branch: boolean, - ): unknown => { - if (Array.isArray(value)) { - return value.map((vv) => doValue(vv, caad, unencrypted_branch)); - } - if (typeof value === "object") { - return walkAndDecrypt( - value as EncodedTree, - key, - caad, - digest, - false, - unencrypted_branch, - encryptionModifier, - ); - } - return decryptScalar(value, key, caad, digest, unencrypted_branch); - }; - - const result: { [key: string]: any } = {}; - - Object.entries(tree).forEach(([k, value]) => { - if ((k === "sops" && isRoot) || value === undefined) { - // The top level 'sops' node is ignored since it's the internal configuration - return; - } - - result[k] = doValue( - value, - `${aad}${k}:`, - unencrypted || encryptionModifier(k), + if (value === null) { + return value; + } + if (Array.isArray(value)) { + return value.map((innerValue) => + walkAndDecrypt(innerValue, key, aad, digest, path, shouldBeEncrypted), ); - }); - - return result; -} + } + if (typeof value === "object") { + return Object.fromEntries(Object.entries(value) + .filter(([k, v]) => v !== undefined && (path.length > 0 || k !== "sops")) + .map(([k, innerValue]) => [ + k, + walkAndDecrypt( + innerValue, + key, + `${aad}${k}:`, + digest, + [...path, k], + shouldBeEncrypted, + ) + ])); + } + const plaintext = typeof value === 'string' && shouldBeEncrypted(path) + ? decryptScalar(value, key, aad) + : value; + digest.update(toBytes(plaintext)); + return plaintext; +}; /** * Get the key from the 'sops.kms' node of the tree * * @param tree */ -async function getKey(tree: EncodedTree): Promise { +async function getKey(tree: EncodedTree): Promise { const { sops } = tree as { sops?: SopsMetadata }; if (!sops || !sops.kms) { @@ -275,15 +244,15 @@ async function getKey(tree: EncodedTree): Promise { return null; } + const errors: string[] = []; + // eslint-disable-next-line no-restricted-syntax for (const entry of kmsTree) { - if (!entry.enc || !entry.arn) { - // Invalid format for a KMS node - // eslint-disable-next-line no-continue - continue; - } - try { + if (!entry.enc || !entry.arn) { + throw new SopsError(`Invalid format for KMS node: ${JSON.stringify(entry)}`); + } + // eslint-disable-next-line no-await-in-loop const kms = await getAwsSessionForEntry(entry); @@ -295,16 +264,19 @@ async function getKey(tree: EncodedTree): Promise { const response = await kms.send(command); - if (!response.Plaintext || !(response.Plaintext instanceof Buffer)) { - throw new SopsError("Invalid response"); + if (!response.Plaintext || !(response.Plaintext instanceof Uint8Array)) { + throw new SopsError("Invalid plaintext in KMS response"); } return response.Plaintext; - } catch (err) { - // log it + } catch(error) { + const [errorType, errorText] = error instanceof Error ? [error.name, error.message] : ['UnknownError', JSON.stringify(error)]; + errors.push(`${entry.arn} - ${errorType}: ${errorText}`); } } - + if (errors.length > 0) { + throw new SopsError(`Failed to get key: \n ${errors.join("\n ")}`); + } return null; } diff --git a/test/integration/decode.test.ts b/test/integration/decode.test.ts new file mode 100644 index 0000000..e5ec24e --- /dev/null +++ b/test/integration/decode.test.ts @@ -0,0 +1,139 @@ +import { deepEqual } from "assert"; +import { execFile } from "child_process"; +import { randomUUID } from "crypto"; +import * as test from "node:test"; +import { tmpdir } from "os"; +import * as path from "path"; +import { decodeFile } from '../../src/decode'; +import { rm, writeFile } from "fs/promises"; + +const mustEnv = (name: string) => { + const value = process.env[name]; + if (!value) { + throw new Error(`missing required environment variable: '${name}'`); + } + return value; +} + +type SopsArgs = { + data: Record; + encryptionMethod: { + kms?: string[], + }, + keyEncryptionBasis: { + unencrypted_suffix?: string; + encrypted_suffix?: string; + unencrypted_regex?: string; + encrypted_regex?: string; + } +}; + +const createEncryptedFile = async ({ + data, + encryptionMethod, + keyEncryptionBasis, +}: SopsArgs) => { + const filepath = path.join(tmpdir(), `${randomUUID()}.json`); + const opts: string[] = []; + if (encryptionMethod.kms) { + opts.push(...encryptionMethod.kms.flatMap(key => ['-k', key])); + } + if (keyEncryptionBasis.unencrypted_suffix) { + opts.push('--unencrypted-suffix', keyEncryptionBasis.unencrypted_suffix); + } + if (keyEncryptionBasis.encrypted_suffix) { + opts.push('--encrypted-suffix', keyEncryptionBasis.encrypted_suffix); + } + if (keyEncryptionBasis.unencrypted_regex) { + opts.push('--unencrypted-regex', keyEncryptionBasis.unencrypted_regex); + } + if (keyEncryptionBasis.encrypted_regex) { + opts.push('--encrypted-regex', keyEncryptionBasis.encrypted_regex); + } + await writeFile(filepath, JSON.stringify(data)); + const child = execFile('sops', [...opts, '-e', '-i', filepath]); + await new Promise((res, rej) => child.on('exit', (code) => code === 0 ? res(null) : rej(code))); + test.after(async () => rm(filepath)); + return filepath; +}; + +test.test('decodeFile', {concurrency: true, timeout: 1000}, async (t) => { + const simpleArray = ['value', false, 21, null]; + const simpleObject = { + myString: 'string', + myNumber: 42, + myBool: true, + myNull: null, + }; + const strings = { + myString: 'string', + myString_plain: 'string', + myString_encrypted: 'string', + }; + const bools = { + myBool: true, + myBool_plain: true, + myBool_encrypted: true, + }; + const numbers = { + myNumber: 42, + myNumber_plain: 42, + myNumber_encrypted: 42, + }; + const nulls = { + myNull: null, + myNull_plain: null, + myNull_encrypted: null, + }; + const arrays = { + myArray: simpleArray, + myArray_plain: simpleArray, + myArray_encrypted: simpleArray, + }; + const objects = { + myObject: simpleObject, + myObject_plain: simpleObject, + myObject_encrypted: simpleObject, + }; + + const encryptionMethods: (SopsArgs['encryptionMethod'])[] = [ + { kms: [mustEnv('KMS_KEY_ARN')] }, + ]; + const keyEncryptionBases: (SopsArgs['keyEncryptionBasis'])[] = [ + { unencrypted_suffix: '_plain' }, + { encrypted_suffix: '_encrypted' }, + { unencrypted_regex: '_plain$' }, + { encrypted_regex: '_encrypted$' }, + ]; + const dataOptions: Record> = { + strings, + bools, + numbers, + nulls, + arrays, + objects, + }; + + await Promise.all([ + t.test('key encryption bases (arbitrarily choosing encryption method)', async (t) => { + const encryptionMethod = encryptionMethods[0]; + await Promise.all(keyEncryptionBases.map(keyEncryptionBasis => t.test(`using ${JSON.stringify(keyEncryptionBasis)}`, async (t) => { + await Promise.all(Object.entries(dataOptions).map(async ([name, data]) => t.test(`with ${name} data`, async (t) => { + const filepath = await createEncryptedFile({ data, encryptionMethod, keyEncryptionBasis }); + const decoded = await decodeFile(filepath); + deepEqual(decoded, data); + }))); + }))); + }), + t.test('encryption methods (arbitrarily choosing key encryption basis)', async (t) => { + const keyEncryptionBasis = keyEncryptionBases[0]; + await Promise.all(encryptionMethods.map(encryptionMethod => t.test(`using ${Object.keys(encryptionMethod)[0]} encryption`, async (t) => { + await Promise.all(Object.entries(dataOptions).map(async ([name, data]) => t.test(`with ${name} data`, async (t) => { + const filepath = await createEncryptedFile({ data, encryptionMethod, keyEncryptionBasis }); + const decoded = await decodeFile(filepath); + deepEqual(decoded, data); + }))); + }))); + }), + ]); +});