From 92cc175eb6f19b27ed7ba075eed307fa2b51307b Mon Sep 17 00:00:00 2001 From: claire bontempo <68122737+hellobontempo@users.noreply.github.com> Date: Mon, 23 Jan 2023 16:49:16 -0800 Subject: [PATCH] ui: add params to pki parser (#18760) * refactor parser to pull serial number from subject * refactor pki parser * uninstall pvtutils * remove hideFormSection as attr * remove hideFormSection as attr * add string-list * test removing issueDate * update tests * final answer - make number types * change to unix time - since valueOf() is typically used internally * add algo mapping * add comment to complete in followon * add attrs to pki parser * add conditional operands so parser continues when values dont exist * add error handling WIP * finish tests, add error handling * revert to helper * move helper to util * add parseSubject test * finish tests * move certs to pki helper file * wrap parsing functions in try...catch --- ui/app/adapters/pki-ca-certificate.js | 4 +- ui/app/helpers/parse-pki-cert.js | 70 ----- ui/app/models/pki/certificate.js | 10 - ui/app/models/pki/certificate/base.js | 5 +- ui/app/models/pki/issuer.js | 6 +- ui/app/models/pki/role.js | 18 +- ui/app/serializers/pki/cert.js | 4 +- ui/app/serializers/pki/certificate/base.js | 7 +- ui/app/serializers/pki/issuer.js | 2 +- ui/app/styles/components/form-section.scss | 3 +- ui/app/utils/parse-pki-cert-oids.js | 55 ++++ ui/app/utils/parse-pki-cert.js | 241 ++++++++++++++++ ui/lib/core/addon/components/form-field.hbs | 1 - ui/lib/core/addon/components/string-list.hbs | 2 +- ui/lib/core/addon/components/string-list.js | 1 - .../components/page/pki-issuer-details.hbs | 2 +- ui/lib/pki/addon/components/pki-key-usage.hbs | 2 +- ui/package.json | 3 +- ui/tests/helpers/pki/values.js | 11 + .../pki/page/pki-certificate-details-test.js | 2 +- .../components/pki/pki-role-form-test.js | 2 +- .../integration/utils/parse-pki-cert-test.js | 268 ++++++++++++++++++ ui/yarn.lock | 9 +- 23 files changed, 600 insertions(+), 128 deletions(-) delete mode 100644 ui/app/helpers/parse-pki-cert.js delete mode 100644 ui/app/models/pki/certificate.js create mode 100644 ui/app/utils/parse-pki-cert-oids.js create mode 100644 ui/app/utils/parse-pki-cert.js create mode 100644 ui/tests/integration/utils/parse-pki-cert-test.js diff --git a/ui/app/adapters/pki-ca-certificate.js b/ui/app/adapters/pki-ca-certificate.js index b07fab16ec..db681f644d 100644 --- a/ui/app/adapters/pki-ca-certificate.js +++ b/ui/app/adapters/pki-ca-certificate.js @@ -1,4 +1,4 @@ -import { parsePkiCert } from '../helpers/parse-pki-cert'; +import { parsePkiCert } from 'vault/utils/parse-pki-cert'; import ApplicationAdapter from './application'; export default ApplicationAdapter.extend({ @@ -49,7 +49,7 @@ export default ApplicationAdapter.extend({ response.modelName = type.modelName; // only parse if certificate is attached to response if (response.data && response.data.certificate) { - const caCertMetadata = parsePkiCert([response.data]); + const caCertMetadata = parsePkiCert(response.data); const transformedResponse = { ...response, ...caCertMetadata }; store.pushPayload(type.modelName, transformedResponse); } else { diff --git a/ui/app/helpers/parse-pki-cert.js b/ui/app/helpers/parse-pki-cert.js deleted file mode 100644 index b049e95b6a..0000000000 --- a/ui/app/helpers/parse-pki-cert.js +++ /dev/null @@ -1,70 +0,0 @@ -import { helper } from '@ember/component/helper'; -import * as asn1js from 'asn1js'; -import { fromBase64, stringToArrayBuffer } from 'pvutils'; -import { Convert } from 'pvtsutils'; -import { Certificate } from 'pkijs'; - -export function parseCertificate(certificateContent) { - let cert; - try { - const cert_base64 = certificateContent.replace(/(-----(BEGIN|END) CERTIFICATE-----|\n)/g, ''); - const cert_der = fromBase64(cert_base64); - const cert_asn1 = asn1js.fromBER(stringToArrayBuffer(cert_der)); - cert = new Certificate({ schema: cert_asn1.result }); - } catch (error) { - console.debug('DEBUG: Parsing Certificate', error); // eslint-disable-line - return { - can_parse: false, - }; - } - // 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; - const serialNumber = Convert.ToHex(cert.serialNumber.valueBlock.valueHex) - .match(/.{1,2}/g) - .join(':'); - return { - can_parse: true, - common_name: commonName, - serial_number: serialNumber, - expiry_date: expiryDate, - issue_date: issueDate, - not_valid_after: expiryDate.valueOf(), - not_valid_before: issueDate.valueOf(), - }; -} - -export function parsePkiCert([model]) { - // model has to be the responseJSON from PKI serializer - // return if no certificate or if the "certificate" is actually a CRL - if (!model.certificate || model.certificate.includes('BEGIN X509 CRL')) { - return; - } - return parseCertificate(model.certificate); -} - -export default helper(parsePkiCert); diff --git a/ui/app/models/pki/certificate.js b/ui/app/models/pki/certificate.js deleted file mode 100644 index 33546aa893..0000000000 --- a/ui/app/models/pki/certificate.js +++ /dev/null @@ -1,10 +0,0 @@ -import Model, { attr } from '@ember-data/model'; - -export default class PkiCertificateModel extends Model { - @attr('string', { readOnly: true }) backend; - @attr('string') commonName; - @attr('string') issueDate; - @attr('string') serialNumber; - @attr('string') notAfter; - @attr('string') notBeforeDuration; -} diff --git a/ui/app/models/pki/certificate/base.js b/ui/app/models/pki/certificate/base.js index 3453167e8c..395f5b8078 100644 --- a/ui/app/models/pki/certificate/base.js +++ b/ui/app/models/pki/certificate/base.js @@ -15,7 +15,6 @@ const certDisplayFields = [ 'certificate', 'commonName', 'revocationTime', - 'issueDate', 'serialNumber', 'notValidBefore', 'notValidAfter', @@ -48,9 +47,11 @@ export default class PkiCertificateBaseModel extends Model { @attr('string') serialNumber; // Parsed from cert in serializer - @attr('number', { formatDate: true }) issueDate; @attr('number', { formatDate: true }) notValidAfter; @attr('number', { formatDate: true }) notValidBefore; + @attr('string') uriSans; + @attr('string') altNames; + @attr('string') signatureBits; // For importing @attr('string') pemBundle; diff --git a/ui/app/models/pki/issuer.js b/ui/app/models/pki/issuer.js index 814e3a043c..d7902ea761 100644 --- a/ui/app/models/pki/issuer.js +++ b/ui/app/models/pki/issuer.js @@ -13,10 +13,10 @@ const issuerUrls = ['issuingCertificates', 'crlDistributionPoints', 'ocspServers 'caChain', 'commonName', 'issuerName', - 'notValidBefore', 'serialNumber', 'keyId', 'uriSans', + 'notValidBefore', 'notValidAfter', ], }, @@ -34,10 +34,6 @@ export default class PkiIssuerModel extends PkiCertificateBaseModel { @attr('string') issuerId; @attr('string', { displayType: 'masked' }) certificate; @attr('string', { displayType: 'masked', label: 'CA Chain' }) caChain; - @attr('date', { - label: 'Issue date', - }) - notValidBefore; @attr('string', { label: 'Default key ID', diff --git a/ui/app/models/pki/role.js b/ui/app/models/pki/role.js index 8030faefc0..29a40d7b59 100644 --- a/ui/app/models/pki/role.js +++ b/ui/app/models/pki/role.js @@ -156,7 +156,6 @@ export default class PkiRoleModel extends Model { label: 'Allowed domains', subText: 'Specifies the domains this role is allowed to issue certificates for. Add one item per row.', editType: 'stringArray', - hideFormSection: true, }) allowedDomains; @@ -194,7 +193,6 @@ export default class PkiRoleModel extends Model { label: 'Policy identifiers', subText: 'A comma-separated string or list of policy object identifiers (OIDs). Add one per row. ', editType: 'stringArray', - hideFormSection: true, }) policyIdentifiers; /* End of overriding Policy identifier options */ @@ -213,7 +211,6 @@ export default class PkiRoleModel extends Model { subText: 'Defines allowed URI Subject Alternative Names. Add one item per row', editType: 'stringArray', docLink: '/docs/concepts/policies', - hideFormSection: true, }) allowedUriSans; @@ -229,7 +226,6 @@ export default class PkiRoleModel extends Model { label: 'Other SANs', subText: 'Defines allowed custom OID/UTF8-string SANs. Add one item per row.', editType: 'stringArray', - hideFormSection: true, }) allowedOtherSans; /* End of overriding SAN options */ @@ -240,7 +236,6 @@ export default class PkiRoleModel extends Model { subText: 'A list of allowed serial numbers to be requested during certificate issuance. Shell-style globbing is supported. If empty, custom-specified serial numbers will be forbidden.', editType: 'stringArray', - hideFormSection: true, }) allowedSerialNumbers; @@ -271,7 +266,6 @@ export default class PkiRoleModel extends Model { label: 'Organization Units (OU)', subText: 'A list of allowed serial numbers to be requested during certificate issuance. Shell-style globbing is supported. If empty, custom-specified serial numbers will be forbidden.', - hideFormSection: true, }) ou; @@ -293,12 +287,12 @@ export default class PkiRoleModel extends Model { }) extKeyUsageOids; - @attr({ hideFormSection: true }) organization; - @attr({ hideFormSection: true }) country; - @attr({ hideFormSection: true }) locality; - @attr({ hideFormSection: true }) province; - @attr({ hideFormSection: true }) streetAddress; - @attr({ hideFormSection: true }) postalCode; + @attr('string') organization; + @attr('string') country; + @attr('string') locality; + @attr('string') province; + @attr('string') streetAddress; + @attr('string') postalCode; /* End of overriding Additional subject field options */ /* CAPABILITIES diff --git a/ui/app/serializers/pki/cert.js b/ui/app/serializers/pki/cert.js index 451e511ce0..4e2f4e9346 100644 --- a/ui/app/serializers/pki/cert.js +++ b/ui/app/serializers/pki/cert.js @@ -2,7 +2,7 @@ import RESTSerializer from '@ember-data/serializer/rest'; import { isNone, isBlank } from '@ember/utils'; import { assign } from '@ember/polyfills'; import { decamelize } from '@ember/string'; -import { parsePkiCert } from '../../helpers/parse-pki-cert'; +import { parsePkiCert } from 'vault/utils/parse-pki-cert'; export default RESTSerializer.extend({ keyForAttribute: function (attr) { @@ -45,7 +45,7 @@ export default RESTSerializer.extend({ let transformedPayload, certMetadata; // hits cert/list endpoint first which returns an array of keys, only want to parse if response contains certificates if (!Array.isArray(responseJSON)) { - certMetadata = parsePkiCert([responseJSON]); + certMetadata = parsePkiCert(responseJSON); transformedPayload = { [modelName]: { ...certMetadata, ...responseJSON } }; } else { transformedPayload = { [modelName]: responseJSON }; diff --git a/ui/app/serializers/pki/certificate/base.js b/ui/app/serializers/pki/certificate/base.js index 30e904ae7d..2da71e6d23 100644 --- a/ui/app/serializers/pki/certificate/base.js +++ b/ui/app/serializers/pki/certificate/base.js @@ -1,4 +1,4 @@ -import { parseCertificate } from 'vault/helpers/parse-pki-cert'; +import { parseCertificate } from 'vault/utils/parse-pki-cert'; import ApplicationSerializer from '../../application'; export default class PkiCertificateBaseSerializer extends ApplicationSerializer { @@ -12,11 +12,6 @@ export default class PkiCertificateBaseSerializer extends ApplicationSerializer if (payload.data.certificate) { // Parse certificate back from the API and add to payload const parsedCert = parseCertificate(payload.data.certificate); - // convert issueDate to same format as other date values - // this can be moved into the parseCertificate helper once the old pki implementation is removed - if (parsedCert.issue_date) { - parsedCert.issue_date = parsedCert.issue_date.valueOf(); - } const json = super.normalizeResponse( store, primaryModelClass, diff --git a/ui/app/serializers/pki/issuer.js b/ui/app/serializers/pki/issuer.js index b8e0bff99d..286e19add3 100644 --- a/ui/app/serializers/pki/issuer.js +++ b/ui/app/serializers/pki/issuer.js @@ -1,4 +1,4 @@ -import { parseCertificate } from 'vault/helpers/parse-pki-cert'; +import { parseCertificate } from 'vault/utils/parse-pki-cert'; import ApplicationSerializer from '../application'; export default class PkiIssuerSerializer extends ApplicationSerializer { diff --git a/ui/app/styles/components/form-section.scss b/ui/app/styles/components/form-section.scss index a6bbe524c2..3d6a94a403 100644 --- a/ui/app/styles/components/form-section.scss +++ b/ui/app/styles/components/form-section.scss @@ -7,7 +7,8 @@ } } -.field:first-child .form-section { +.field:first-child .form-section, +.box > .field > .field.form-section.string-list { padding: 0; box-shadow: none; } diff --git a/ui/app/utils/parse-pki-cert-oids.js b/ui/app/utils/parse-pki-cert-oids.js new file mode 100644 index 0000000000..e7bc91c2cd --- /dev/null +++ b/ui/app/utils/parse-pki-cert-oids.js @@ -0,0 +1,55 @@ +//* lookup OIDs: http://oid-info.com/basic-search.htm + +export const SUBJECT_OIDs = { + common_name: '2.5.4.3', + serial_number: '2.5.4.5', + ou: '2.5.4.11', + organization: '2.5.4.10', + country: '2.5.4.6', + locality: '2.5.4.7', + province: '2.5.4.8', + street_address: '2.5.4.9', + postal_code: '2.5.4.17', +}; + +export const EXTENSION_OIDs = { + key_usage: '2.5.29.15', + subject_alt_name: '2.5.29.17', // contains SAN_TYPES below + basic_constraints: '2.5.29.19', // contains max_path_length + name_constraints: '2.5.29.30', // contains permitted_dns_domains +}; + +// these are allowed ext oids, but not parsed and passed to cross-signed certs +export const IGNORED_OIDs = { + subject_key_identifier: '2.5.29.14', + authority_key_identifier: '2.5.29.35', +}; + +// SubjectAltName/GeneralName types (scroll up to page 38 -> https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.7 ) +export const SAN_TYPES = { + alt_names: 2, // dNSName + uri_sans: 6, // uniformResourceIdentifier + ip_sans: 7, // iPAddress - OCTET STRING +}; + +export const SIGNATURE_ALGORITHM_OIDs = { + '1.2.840.113549.1.1.2': '0', // MD2-RSA + '1.2.840.113549.1.1.4': '0', // MD5-RSA + '1.2.840.113549.1.1.5': '0', // SHA1-RSA + '1.2.840.113549.1.1.11': '256', // SHA256-RSA + '1.2.840.113549.1.1.12': '384', // SHA384-RSA + '1.2.840.113549.1.1.13': '512', // SHA512-RSA + '1.2.840.113549.1.1.10': { + // RSA-PSS have additional OIDs that need to be mapped + '2.16.840.1.101.3.4.2.1': '256', // SHA-256 + '2.16.840.1.101.3.4.2.2': '384', // SHA-384 + '2.16.840.1.101.3.4.2.3': '512', // SHA-512 + }, + '1.2.840.10040.4.3': '0', // DSA-SHA1 + '2.16.840.1.101.3.4.3.2': '256', // DSA-SHA256 + '1.2.840.10045.4.1': '0', // ECDSA-SHA1 + '1.2.840.10045.4.3.2': '256', // ECDSA-SHA256 + '1.2.840.10045.4.3.3': '384', // ECDSA-SHA384 + '1.2.840.10045.4.3.4': '512', // ECDSA-SHA512 + '1.3.101.112': '0', // Ed25519 +}; diff --git a/ui/app/utils/parse-pki-cert.js b/ui/app/utils/parse-pki-cert.js new file mode 100644 index 0000000000..51f6aac50d --- /dev/null +++ b/ui/app/utils/parse-pki-cert.js @@ -0,0 +1,241 @@ +import * as asn1js from 'asn1js'; +import { fromBase64, stringToArrayBuffer } from 'pvutils'; +import { Certificate } from 'pkijs'; +import { differenceInHours, getUnixTime } from 'date-fns'; +import { + EXTENSION_OIDs, + SUBJECT_OIDs, + IGNORED_OIDs, + SAN_TYPES, + SIGNATURE_ALGORITHM_OIDs, +} from './parse-pki-cert-oids'; + +/* + It may be helpful to visualize a certificate's SEQUENCE structure alongside this parsing file. + You can do so by decoding a certificate here: https://lapo.it/asn1js/# + + A certificate is encoded in ASN.1 data - a SEQUENCE is how you define structures in ASN.1. + GeneralNames, Extension, AlgorithmIdentifier are all examples of SEQUENCEs + + * Error handling: +{ can_parse: false } -> returned if the external library cannot convert the certificate +{ parsing_errors: [] } -> returned if the certificate was converted, but there's ANY problem parsing certificate details. + This means we cannot cross-sign in the UI and prompt the user to do so manually using the CLI. + */ + +export function parseCertificate(certificateContent) { + let cert; + try { + const cert_base64 = certificateContent.replace(/(-----(BEGIN|END) CERTIFICATE-----|\n)/g, ''); + const cert_der = fromBase64(cert_base64); + const cert_asn1 = asn1js.fromBER(stringToArrayBuffer(cert_der)); + cert = new Certificate({ schema: cert_asn1.result }); + } catch (error) { + console.debug('DEBUG: Converting Certificate', error); // eslint-disable-line + return { can_parse: false }; + } + + let parsedCertificateValues; + try { + const subjectValues = parseSubject(cert?.subject?.typesAndValues); + const extensionValues = parseExtensions(cert?.extensions); + const [signature_bits, use_pss] = mapSignatureBits(cert?.signatureAlgorithm); + const formattedValues = formatValues(subjectValues, extensionValues); + parsedCertificateValues = { ...formattedValues, signature_bits, use_pss }; + } catch (error) { + console.debug('DEBUG: Parsing Certificate', error); // eslint-disable-line + parsedCertificateValues = { parsing_errors: [new Error('error parsing certificate values')] }; + } + + const expiryDate = cert?.notAfter?.value; + const issueDate = cert?.notBefore?.value; + const ttl = `${differenceInHours(expiryDate, issueDate)}h`; + + return { + ...parsedCertificateValues, + can_parse: true, + expiry_date: expiryDate, // remove along with old PKI work + issue_date: issueDate, // remove along with old PKI work + not_valid_after: getUnixTime(expiryDate), + not_valid_before: getUnixTime(issueDate), + ttl, + }; +} + +export function parsePkiCert(model) { + // model has to be the responseJSON from PKI serializer + // return if no certificate or if the "certificate" is actually a CRL + if (!model.certificate || model.certificate.includes('BEGIN X509 CRL')) { + return; + } + return parseCertificate(model.certificate); +} + +export function formatValues(subject, extension) { + if (!subject || !extension) { + return { parsing_errors: [new Error('error formatting certificate values')] }; + } + const { subjValues, subjErrors } = subject; + const { extValues, extErrors } = extension; + const parsing_errors = [...subjErrors, ...extErrors]; + const exclude_cn_from_sans = + extValues.alt_names?.length > 0 && !extValues.alt_names?.includes(subjValues?.common_name) ? true : false; + // now that we've finished parsing data, join all extension arrays + for (const ext in extValues) { + if (Array.isArray(extValues[ext])) { + extValues[ext] = extValues[ext].length !== 0 ? extValues[ext].join(', ') : null; + } + } + + // TODO remove this deletion when key_usage is parsed, update test + delete extValues.key_usage; + return { + ...subjValues, + ...extValues, + parsing_errors, + exclude_cn_from_sans, + }; +} + +//* PARSING HELPERS +/* + We wish to get each SUBJECT_OIDs (see utils/parse-pki-cert-oids.js) 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: + + 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. +*/ +export function parseSubject(subject) { + if (!subject) return null; + const values = {}; + const errors = []; + if (subject.any((rdn) => !Object.values(SUBJECT_OIDs).includes(rdn.type))) { + errors.push(new Error('certificate contains unsupported subject OIDs')); + } + const returnValues = (OID) => { + const values = subject.filter((rdn) => rdn?.type === OID).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 and return a string array + return values ? (values?.length ? values[0] : null) : null; + }; + Object.keys(SUBJECT_OIDs).forEach((key) => (values[key] = returnValues(SUBJECT_OIDs[key]))); + return { subjValues: values, subjErrors: errors }; +} + +export function parseExtensions(extensions) { + if (!extensions) return null; + const values = {}; + const errors = []; + const allowedOids = Object.values({ ...EXTENSION_OIDs, ...IGNORED_OIDs }); + if (extensions.any((ext) => !allowedOids.includes(ext.extnID))) { + errors.push(new Error('certificate contains unsupported extension OIDs')); + } + + // make each extension its own key/value pair + for (const attrName in EXTENSION_OIDs) { + values[attrName] = extensions.find((ext) => ext.extnID === EXTENSION_OIDs[attrName])?.parsedValue; + } + + if (values.subject_alt_name) { + // we only support SANs of type 2 (altNames), 6 (uri) and 7 (ipAddress) + const supportedTypes = Object.values(SAN_TYPES); + const supportedNames = Object.keys(SAN_TYPES); + const sans = values.subject_alt_name?.altNames; + if (!sans) { + errors.push(new Error('certificate contains unsupported subjectAltName values')); + } else if (sans.any((san) => !supportedTypes.includes(san.type))) { + // pass along error that unsupported values exist + errors.push(new Error('subjectAltName contains unsupported types')); + // still check and parse any supported values + if (sans.any((san) => supportedTypes.includes(san.type))) { + supportedNames.forEach((attrName) => { + values[attrName] = sans + .filter((gn) => gn.type === Number(SAN_TYPES[attrName])) + .map((gn) => gn.value); + }); + } + } else if (sans.every((san) => supportedTypes.includes(san.type))) { + supportedNames.forEach((attrName) => { + values[attrName] = sans.filter((gn) => gn.type === Number(SAN_TYPES[attrName])).map((gn) => gn.value); + }); + } else { + errors.push(new Error('unsupported subjectAltName values')); + } + } + + // permitted_dns_domains + if (values.name_constraints) { + // we only support Name Constraints of dnsName (type 2), this value lives in the permittedSubtree of the Name Constraints sequence + // permittedSubtrees contain an array of subtree objects, each object has a 'base' key and EITHER a 'minimum' or 'maximum' key + // GeneralSubtree { "base": { "type": 2, "value": "dnsname1.com" }, minimum: 0 } + const nameConstraints = values.name_constraints; + if (Object.keys(nameConstraints).includes('excludedSubtrees')) { + errors.push(new Error('nameConstraints contains excludedSubtrees')); + } else if (nameConstraints.permittedSubtrees.any((subtree) => subtree.minimum !== 0)) { + errors.push(new Error('nameConstraints permittedSubtree contains non-zero minimums')); + } else if (nameConstraints.permittedSubtrees.any((subtree) => subtree.maximum)) { + errors.push(new Error('nameConstraints permittedSubtree contains maximum')); + } else if (nameConstraints.permittedSubtrees.any((subtree) => subtree.base.type !== 2)) { + errors.push(new Error('nameConstraints permittedSubtree can only contain dnsName (type 2)')); + // still check and parse any supported values + if (nameConstraints.permittedSubtrees.any((subtree) => subtree.base.type === 2)) { + values.permitted_dns_domains = nameConstraints.permittedSubtrees + .filter((gn) => gn.base.type === 2) + .map((gn) => gn.base.value); + } + } else if (nameConstraints.permittedSubtrees.every((subtree) => subtree.base.type === 2)) { + values.permitted_dns_domains = nameConstraints.permittedSubtrees.map((gn) => gn.base.value); + } else { + errors.push(new Error('unsupported nameConstraints values')); + } + } + + if (values.basic_constraints) { + values.max_path_length = values.basic_constraints?.pathLenConstraint; + } + + if (values.ip_sans) { + // TODO parse octet string for IP addresses + } + + if (values.key_usage) { + // TODO parse key_usage + } + + delete values.subject_alt_name; + delete values.basic_constraints; + delete values.name_constraints; + return { extValues: values, extErrors: errors }; + /* + values is an object with keys from EXTENSION_OIDs and SAN_TYPES + values = { + "alt_names": string[], + "uri_sans": string[], + "permitted_dns_domains": string[], + "max_path_length": int, + "key_usage": BitString, <- to-be-parsed + "ip_sans": OctetString[], <- currently array of OctetStrings to-be-parsed + } + */ +} + +function mapSignatureBits(sigAlgo) { + const { algorithmId } = sigAlgo; + + // use_pss is true, additional OIDs need to be mapped + if (algorithmId === '1.2.840.113549.1.1.10') { + // object identifier for PSS is very nested + const objId = sigAlgo.algorithmParams?.valueBlock?.value[0]?.valueBlock?.value[0]?.valueBlock?.value[0] + .toString() + .split(' : ')[1]; + return [SIGNATURE_ALGORITHM_OIDs[algorithmId][objId], true]; + } + return [SIGNATURE_ALGORITHM_OIDs[algorithmId], false]; +} diff --git a/ui/lib/core/addon/components/form-field.hbs b/ui/lib/core/addon/components/form-field.hbs index 72cfcd5d9b..3bdfe14949 100644 --- a/ui/lib/core/addon/components/form-field.hbs +++ b/ui/lib/core/addon/components/form-field.hbs @@ -215,7 +215,6 @@ @onChange={{this.setAndBroadcast}} @attrName={{@attr.name}} @subText={{@attr.options.subText}} - @hideFormSection={{@attr.options.hideFormSection}} /> {{else if (eq @attr.options.sensitive true)}} {{! Masked Input }} diff --git a/ui/lib/core/addon/components/string-list.hbs b/ui/lib/core/addon/components/string-list.hbs index 095bb047bf..b27d8386ba 100644 --- a/ui/lib/core/addon/components/string-list.hbs +++ b/ui/lib/core/addon/components/string-list.hbs @@ -1,5 +1,5 @@
{{/if}} diff --git a/ui/lib/pki/addon/components/pki-key-usage.hbs b/ui/lib/pki/addon/components/pki-key-usage.hbs index c17de8cfde..77bd9a1a24 100644 --- a/ui/lib/pki/addon/components/pki-key-usage.hbs +++ b/ui/lib/pki/addon/components/pki-key-usage.hbs @@ -19,6 +19,7 @@ />