From dcdfaa888934339b416a4d9fe38f36f17446144a Mon Sep 17 00:00:00 2001 From: Alexander Scheel Date: Fri, 4 Feb 2022 12:52:28 -0500 Subject: [PATCH] Switch from node-forge to PKI.js (#13894) * Switch parse-pki-cert from node-forge to PKI.js This replaces the implementation of parse-pki-cert to use PKI.js rather than node-forge for two reasons: - PKI.js uses Web Crypto rather than maintaining a built-in implementation of several algorithms. - node-forge presently lacks support for ECDSA and Ed25519 certificates. Related: #13680 Signed-off-by: Alexander Scheel * Add dependency on PKI.js $ yarn add -D asn1js pvutils pkijs Signed-off-by: Alexander Scheel * Remove dependency on node-forge $ yarn remove node-forge Signed-off-by: Alexander Scheel * Add changelog entry Signed-off-by: Alexander Scheel --- changelog/13894.txt | 3 +++ ui/app/helpers/parse-pki-cert.js | 43 ++++++++++++++++++++++++++------ ui/package.json | 4 ++- ui/yarn.lock | 31 +++++++++++++++++++---- 4 files changed, 68 insertions(+), 13 deletions(-) create mode 100644 changelog/13894.txt diff --git a/changelog/13894.txt b/changelog/13894.txt new file mode 100644 index 000000000..2ada1d513 --- /dev/null +++ b/changelog/13894.txt @@ -0,0 +1,3 @@ +```release-note:improvement +ui: Add support for ECDSA and Ed25519 certificate views +``` diff --git a/ui/app/helpers/parse-pki-cert.js b/ui/app/helpers/parse-pki-cert.js index 56a897fe6..709c83c5f 100644 --- a/ui/app/helpers/parse-pki-cert.js +++ b/ui/app/helpers/parse-pki-cert.js @@ -1,5 +1,7 @@ import { helper } from '@ember/component/helper'; -import { pki } from 'node-forge'; +import * as asn1js from 'asn1js'; +import { fromBase64, stringToArrayBuffer } from 'pvutils'; +import { Certificate } from 'pkijs'; export function parsePkiCert([model]) { // model has to be the responseJSON from PKI serializer @@ -7,18 +9,45 @@ export function parsePkiCert([model]) { return; } let cert; - // node-forge cannot parse EC (elliptical curve) certs - // set canParse to false if unable to convert a Forge cert from PEM try { - cert = pki.certificateFromPem(model.certificate); + let cert_base64 = model.certificate.replace(/(-----(BEGIN|END) CERTIFICATE-----|\n)/g, ''); + let cert_der = fromBase64(cert_base64); + let cert_asn1 = asn1js.fromBER(stringToArrayBuffer(cert_der)); + cert = new Certificate({ schema: cert_asn1.result }); } catch (error) { + console.log('Error parsing certificate:', error, model.certificate); return { can_parse: false, }; } - const commonName = cert?.subject.getField('CN') ? cert.subject.getField('CN').value : null; - const expiryDate = cert?.validity.notAfter; - const issueDate = cert?.validity.notBefore; + + // We wish to get the CN element out of this certificate's subject. A + // subject is a list of RDNs, where each RDN is a (type, value) tuple + // and where a type is an OID. The OID for CN can be found here: + // + // http://oid-info.com/get/2.5.4.3 + // https://datatracker.ietf.org/doc/html/rfc5280#page-112 + // + // Each value is then encoded as another ASN.1 object; in the case of a + // CommonName field, this is usually a PrintableString, BMPString, or a + // UTF8String. Regardless of encoding, it should be present in the + // valueBlock's value field if it is renderable. + const commonNameOID = '2.5.4.3'; + const commonNames = cert?.subject?.typesAndValues + .filter((rdn) => rdn?.type === commonNameOID) + .map((rdn) => rdn?.value?.valueBlock?.value); + + // Theoretically, there might be multiple (or no) CommonNames -- but Vault + // presently refuses to issue certificates without CommonNames in most + // cases. For now, return the first CommonName we find. Alternatively, we + // might update our callers to handle multiple, or join them using some + // separator like ','. + const commonName = commonNames ? (commonNames.length ? commonNames[0] : null) : null; + + // Date instances are stored in the value field as the notAfter/notBefore + // field themselves are Time values. + const expiryDate = cert?.notAfter?.value; + const issueDate = cert?.notBefore?.value; return { can_parse: true, common_name: commonName, diff --git a/ui/package.json b/ui/package.json index a8b2f5180..effea7e90 100644 --- a/ui/package.json +++ b/ui/package.json @@ -67,6 +67,7 @@ "@icholy/duration": "^5.1.0", "@storybook/cli": "^6.3.10", "@storybook/ember-cli-storybook": "^0.4.0", + "asn1js": "^2.2.0", "autosize": "^4.0.0", "babel-eslint": "^10.1.0", "babel-plugin-inline-json-import": "^0.3.2", @@ -156,13 +157,14 @@ "jsondiffpatch": "^0.4.1", "jsonlint": "^1.6.3", "loader.js": "^4.7.0", - "node-forge": "^0.10.0", "node-sass": "^4.10.0", "normalize.css": "4.1.1", "npm-run-all": "^4.1.5", + "pkijs": "^2.2.2", "pretender": "^3.4.3", "prettier": "^2.2.1", "prettier-eslint-cli": "^5.0.0", + "pvutils": "^1.0.17", "qunit": "^2.14.1", "qunit-dom": "^1.6.0", "route-recognizer": "^0.3.4", diff --git a/ui/yarn.lock b/ui/yarn.lock index 963ba661a..5613cda18 100644 --- a/ui/yarn.lock +++ b/ui/yarn.lock @@ -5087,6 +5087,13 @@ asn1@~0.2.3: dependencies: safer-buffer "~2.1.0" +asn1js@^2.1.1, asn1js@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/asn1js/-/asn1js-2.2.0.tgz#d890fcdda86b8a005693df14a986bfb2c2069c57" + integrity sha512-oagLNqpfNv7CvmyMoexMDNyVDSiq1rya0AEUgcLlNHdHgNl6U/hi8xY370n5y+ZIFEXOx0J4B1qF2NDjMRxklA== + dependencies: + pvutils latest + assert-never@^1.1.0, assert-never@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/assert-never/-/assert-never-1.2.1.tgz#11f0e363bf146205fb08193b5c7b90f4d1cf44fe" @@ -7353,6 +7360,11 @@ bytes@3.1.0: resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.0.tgz#f6cf7933a360e0588fa9fde85651cdc7f805d1f6" integrity sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg== +bytestreamjs@^1.0.29: + version "1.0.29" + resolved "https://registry.yarnpkg.com/bytestreamjs/-/bytestreamjs-1.0.29.tgz#691f8ee8e5a150c61b925993a3eec0911ed17f1d" + integrity sha512-Mri3yqoo9YvdaSvD5OYl4Rdu9zCBJInW/Ez31sdlNY4ikMy//EvTTmidfLcs0e+NBvKVEpPzYvJAesjgMdjnZg== + cacache@^12.0.2: version "12.0.4" resolved "https://registry.yarnpkg.com/cacache/-/cacache-12.0.4.tgz#668bcbd105aeb5f1d92fe25570ec9525c8faa40c" @@ -16125,11 +16137,6 @@ node-fetch@^2.6.1: resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052" integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw== -node-forge@^0.10.0: - version "0.10.0" - resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.10.0.tgz#32dea2afb3e9926f02ee5ce8794902691a676bf3" - integrity sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA== - node-gyp@^3.8.0: version "3.8.0" resolved "https://registry.yarnpkg.com/node-gyp/-/node-gyp-3.8.0.tgz#540304261c330e80d0d5edce253a68cb3964218c" @@ -17142,6 +17149,15 @@ pkg-up@^2.0.0: dependencies: find-up "^2.1.0" +pkijs@^2.2.2: + version "2.2.2" + resolved "https://registry.yarnpkg.com/pkijs/-/pkijs-2.2.2.tgz#d0780379a2fb80c892c3ead4bcfb42675b67a982" + integrity sha512-xRTEW9LgUeHBe5hRCC4EHvLbO6o3L/t8uEk8cTaSMVEjEcy2G5qJ/bY2ndvrGgZlKDThnTrM4Xlwu8Qpyr6mOg== + dependencies: + asn1js "^2.1.1" + bytestreamjs "^1.0.29" + pvutils "^1.0.17" + please-upgrade-node@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/please-upgrade-node/-/please-upgrade-node-3.2.0.tgz#aeddd3f994c933e4ad98b99d9a556efa0e2fe942" @@ -17615,6 +17631,11 @@ puppeteer-core@^2.1.1: rimraf "^2.6.1" ws "^6.1.0" +pvutils@^1.0.17, pvutils@latest: + version "1.0.17" + resolved "https://registry.yarnpkg.com/pvutils/-/pvutils-1.0.17.tgz#ade3c74dfe7178944fe44806626bd2e249d996bf" + integrity sha512-wLHYUQxWaXVQvKnwIDWFVKDJku9XDCvyhhxoq8dc5MFdIlRenyPI9eSfEtcvgHgD7FlvCyGAlWgOzRnZD99GZQ== + q@^1.1.2: version "1.5.1" resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7"