mirror of
https://github.com/hashicorp/vault.git
synced 2025-08-15 19:17:02 +02:00
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
This commit is contained in:
parent
3ded1388cb
commit
92cc175eb6
@ -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 {
|
||||
|
@ -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);
|
@ -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;
|
||||
}
|
@ -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;
|
||||
|
@ -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',
|
||||
|
@ -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
|
||||
|
@ -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 };
|
||||
|
@ -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,
|
||||
|
@ -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 {
|
||||
|
@ -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;
|
||||
}
|
||||
|
55
ui/app/utils/parse-pki-cert-oids.js
Normal file
55
ui/app/utils/parse-pki-cert-oids.js
Normal file
@ -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
|
||||
};
|
241
ui/app/utils/parse-pki-cert.js
Normal file
241
ui/app/utils/parse-pki-cert.js
Normal file
@ -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];
|
||||
}
|
@ -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 }}
|
||||
|
@ -1,5 +1,5 @@
|
||||
<div
|
||||
class={{concat "field string-list" (if @hideFormSection "" " form-section")}}
|
||||
class="field string-list form-section"
|
||||
data-test-component="string-list"
|
||||
{{did-insert this.autoSize}}
|
||||
{{did-update this.autoSizeUpdate}}
|
||||
|
@ -20,7 +20,6 @@ import { next } from '@ember/runloop';
|
||||
* @param {string} type=array - Optional type for inputValue.
|
||||
* @param {string} attrName - We use this to check the type so we can modify the tooltip content.
|
||||
* @param {string} subText - Text below the label.
|
||||
* @param {boolean} hideFormSection - If true do not add form-section class on surrounding div.
|
||||
*/
|
||||
|
||||
export default class StringList extends Component {
|
||||
|
@ -79,7 +79,7 @@
|
||||
<InfoTableRow
|
||||
@label={{or attr.options.label (humanize (dasherize attr.name))}}
|
||||
@value={{get @issuer attr.name}}
|
||||
@formatDate={{if (eq attr.type "date") "MMM d yyyy HH:mm:ss a zzzz"}}
|
||||
@formatDate={{if attr.options.formatDate "MMM d yyyy HH:mm:ss a zzzz"}}
|
||||
@alwaysRender={{true}}
|
||||
/>
|
||||
{{/if}}
|
||||
|
@ -19,6 +19,7 @@
|
||||
/>
|
||||
<div class="has-top-margin-xxl">
|
||||
<StringList
|
||||
class="is-box-shadowless"
|
||||
data-test-input="extKeyUsageOids"
|
||||
@label="Extended key usage oids"
|
||||
@inputValue={{get @model "extKeyUsageOids"}}
|
||||
@ -26,6 +27,5 @@
|
||||
@attrName="extKeyUsageOids"
|
||||
@subText="A list of extended key usage oids. Add one item per row."
|
||||
@showHelpText={{false}}
|
||||
@hideFormSection={{true}}
|
||||
/>
|
||||
</div>
|
@ -256,7 +256,6 @@
|
||||
"highlight.js": "^10.4.1",
|
||||
"js-yaml": "^3.13.1",
|
||||
"lodash": "^4.17.13",
|
||||
"node-notifier": "^8.0.1",
|
||||
"pvtsutils": "^1.3.2"
|
||||
"node-notifier": "^8.0.1"
|
||||
}
|
||||
}
|
||||
|
@ -124,3 +124,14 @@ UmYDODRN4qh9xYruKJ8i89iMGQfbdcq78dCC4JwBIx3bysC8oF4lqbTYoYNVTnAi
|
||||
LVqvLdHycEOMlqV0ecq8uMLhPVBalCmIlKdWNQFpXB0TQCsn95rCCdi7ZTsYk5zv
|
||||
Q4raFvQrZth3Cz/X5yPTtQL78oBYrmHzoQKDFJ2z
|
||||
-----END CERTIFICATE-----`;
|
||||
|
||||
// for parse-pki-cert tests:
|
||||
// certificate contains all allowable params
|
||||
export const loadedCert = `-----BEGIN CERTIFICATE-----\nMIIFFTCCA/2gAwIBAgIULIZoZjgoLLQeYd/I0EQgdUegragwDQYJKoZIhvcNAQEN\nBQAwgdoxDzANBgNVBAYTBkZyYW5jZTESMBAGA1UECBMJQ2hhbXBhZ25lMQ4wDAYD\nVQQHEwVQYXJpczETMBEGA1UECRMKMjM0IHNlc2FtZTEPMA0GA1UEERMGMTIzNDU2\nMSQwDQYDVQQKEwZXaWRnZXQwEwYDVQQKEwxJbmNvcnBvcmF0ZWQxKDAOBgNVBAsT\nB0ZpbmFuY2UwFgYDVQQLEw9IdW1hbiBSZXNvdXJjZXMxGDAWBgNVBAMTD2NvbW1v\nbi1uYW1lLmNvbTETMBEGA1UEBRMKY2VyZWFsMTI5MjAeFw0yMzAxMjEwMDUyMzBa\nFw0zMzAxMTgwMDUzMDBaMIHaMQ8wDQYDVQQGEwZGcmFuY2UxEjAQBgNVBAgTCUNo\nYW1wYWduZTEOMAwGA1UEBxMFUGFyaXMxEzARBgNVBAkTCjIzNCBzZXNhbWUxDzAN\nBgNVBBETBjEyMzQ1NjEkMA0GA1UEChMGV2lkZ2V0MBMGA1UEChMMSW5jb3Jwb3Jh\ndGVkMSgwDgYDVQQLEwdGaW5hbmNlMBYGA1UECxMPSHVtYW4gUmVzb3VyY2VzMRgw\nFgYDVQQDEw9jb21tb24tbmFtZS5jb20xEzARBgNVBAUTCmNlcmVhbDEyOTIwggEi\nMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDZRug7meAek7/LvKyPqVL0L9hO\n3RQrvotWAGxCUp7gEPVxVBuVH97hwfABazikQQGhXQVeISrwaX7zI945fd3dGx3R\n3iDPrGp3A8KXsaS70luMg6WyIQJ5GM21GIGchACXiIKv+Ln0++0wivFyMw8sA4V2\nbQyZHOsN5puoYqhEFyypw0E3yiyvBW7KuDrkOLzuVSCa1WdYCnpg7O1v/ViM6dIk\no83CH1p1MtQ6ZPgBfB4V6JPAm4R3zhoG0Geg3FziCXm+F2qyfbICyTQLoXXB0YD9\nE5D4jnsGwRvSLIdadxfqZCN740JOHIIZopQLhJDHNjQjTcuqtW8EhC1UJzIjAgMB\nAAGjgdAwgc0wDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8CAREwHQYD\nVR0OBBYEFAsrMoFu6tt1pybxx9ln6w5QK/2tMB8GA1UdIwQYMBaAFAsrMoFu6tt1\npybxx9ln6w5QK/2tMDcGA1UdEQQwMC6CCGFsdG5hbWUxgghhbHRuYW1lMocEwJ4B\nJoYIdGVzdHVyaTGGCHRlc3R1cmkyMC4GA1UdHgEB/wQkMCKgIDAOggxkbnNuYW1l\nMS5jb20wDoIMZHNubmFtZTIuY29tMA0GCSqGSIb3DQEBDQUAA4IBAQCLIQ/AEVME\n5F9N5kqT0PdJ7PgjCHraWnEa25TH7RxH5mh6BakuUkJr5TFnytDU6TwkVfixgT9j\nT6O+BdB6ILv1u3ECGBQNObq1HtO0NM/Q1IZewEUNIjDVfdXFIxHLLlyxoGiCV/PS\nm/QHHX6K7EezAIdw4OvvO5lfjOzPZ6vaWEab1BCCPgxaWOqQ4U6MX3NzLiP5VqTs\npMFoLJ0yG1yMkW0pr8d1NkqDoZI1JW/DGrQEdYg182ckHogjmjydVE0B00yCzGHh\nOYqj7AHqjkpa9DMZMH22reuiSGNun7o2jEQ9iRt79UEpqkIap3aohsypeqgYCMGf\n6V/JEhjKPzap\n-----END CERTIFICATE-----`;
|
||||
// use_pss = true
|
||||
export const pssTrueCert = `-----BEGIN CERTIFICATE-----\nMIIDqTCCAl2gAwIBAgIUVY2PTRZl1t/fjfyEwrG4HvGjYekwQQYJKoZIhvcNAQEK\nMDSgDzANBglghkgBZQMEAgEFAKEcMBoGCSqGSIb3DQEBCDANBglghkgBZQMEAgEF\nAKIDAgEgMBoxGDAWBgNVBAMTD2NvbW1vbi1uYW1lLmNvbTAeFw0yMzAxMjEwMTA3\nNDBaFw0yMzAyMjIwMTA4MTBaMBoxGDAWBgNVBAMTD2NvbW1vbi1uYW1lLmNvbTCC\nASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANlG6DuZ4B6Tv8u8rI+pUvQv\n2E7dFCu+i1YAbEJSnuAQ9XFUG5Uf3uHB8AFrOKRBAaFdBV4hKvBpfvMj3jl93d0b\nHdHeIM+sancDwpexpLvSW4yDpbIhAnkYzbUYgZyEAJeIgq/4ufT77TCK8XIzDywD\nhXZtDJkc6w3mm6hiqEQXLKnDQTfKLK8Fbsq4OuQ4vO5VIJrVZ1gKemDs7W/9WIzp\n0iSjzcIfWnUy1Dpk+AF8HhXok8CbhHfOGgbQZ6DcXOIJeb4XarJ9sgLJNAuhdcHR\ngP0TkPiOewbBG9Ish1p3F+pkI3vjQk4cghmilAuEkMc2NCNNy6q1bwSELVQnMiMC\nAwEAAaN/MH0wDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0O\nBBYEFAsrMoFu6tt1pybxx9ln6w5QK/2tMB8GA1UdIwQYMBaAFAsrMoFu6tt1pybx\nx9ln6w5QK/2tMBoGA1UdEQQTMBGCD2NvbW1vbi1uYW1lLmNvbTBBBgkqhkiG9w0B\nAQowNKAPMA0GCWCGSAFlAwQCAQUAoRwwGgYJKoZIhvcNAQEIMA0GCWCGSAFlAwQC\nAQUAogMCASADggEBAFh+PMwEmxaZR6OtfB0Uvw2vA7Oodmm3W0bYjQlEz8U+Q+JZ\ncIPa4VnRy1QALmKbPCbRApA/gcWzIwtzo1JhLtcDINg2Tl0nj4WvgpIvj0/lQNMq\nmwP7G/K4PyJTv3+y5XwVfepZAZITB0w5Sg5dLC6HP8AGVIaeb3hGNHYvPlE+pbT+\njL0xxzFjOorWoy5fxbWoVyVv9iZ4j0zRnbkYHIi3d8g56VV6Rbyw4WJt6p87lmQ8\n0wbiJTtuew/0Rpuc3PEcR9XfB5ct8bvaGGTSTwh6JQ33ohKKAKjbBNmhBDSP1thQ\n2mTkms/mbDRaTiQKHZx25TmOlLN5Ea1TSS0K6yw=\n-----END CERTIFICATE-----`;
|
||||
// only has common name
|
||||
export const skeletonCert = `-----BEGIN CERTIFICATE-----\nMIIDQTCCAimgAwIBAgIUVQy58VgdVpAK9c8SfS31idSv6FUwDQYJKoZIhvcNAQEL\nBQAwGjEYMBYGA1UEAxMPY29tbW9uLW5hbWUuY29tMB4XDTIzMDEyMTAxMjAyOVoX\nDTIzMDIyMjAxMjA1OVowGjEYMBYGA1UEAxMPY29tbW9uLW5hbWUuY29tMIIBIjAN\nBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2UboO5ngHpO/y7ysj6lS9C/YTt0U\nK76LVgBsQlKe4BD1cVQblR/e4cHwAWs4pEEBoV0FXiEq8Gl+8yPeOX3d3Rsd0d4g\nz6xqdwPCl7Gku9JbjIOlsiECeRjNtRiBnIQAl4iCr/i59PvtMIrxcjMPLAOFdm0M\nmRzrDeabqGKoRBcsqcNBN8osrwVuyrg65Di87lUgmtVnWAp6YOztb/1YjOnSJKPN\nwh9adTLUOmT4AXweFeiTwJuEd84aBtBnoNxc4gl5vhdqsn2yAsk0C6F1wdGA/ROQ\n+I57BsEb0iyHWncX6mQje+NCThyCGaKUC4SQxzY0I03LqrVvBIQtVCcyIwIDAQAB\no38wfTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU\nCysygW7q23WnJvHH2WfrDlAr/a0wHwYDVR0jBBgwFoAUCysygW7q23WnJvHH2Wfr\nDlAr/a0wGgYDVR0RBBMwEYIPY29tbW9uLW5hbWUuY29tMA0GCSqGSIb3DQEBCwUA\nA4IBAQDPco+FIHXczf0HTwFAmIVu4HKaeIwDsVPxoUqqWEix8AyCsB5uqpKZasby\nedlrdBohM4dnoV+VmV0de04y95sdo3Ot60hm/czLog3tHg4o7AmfA7saS+5hCL1M\nCJWqoJHRFo0hOWJHpLJRWz5DqRZWspASoVozLOYyjRD+tNBjO5hK4FtaG6eri38t\nOpTt7sdInVODlntpNuuCVprPpHGj4kPOcViQULoFQq5fwyadpdjqSXmEGlt0to5Y\nMbTb4Jhj0HywgO53BUUmMzzY9idXh/8A7ThrM5LtqhxaYHLVhyeo+5e0mgiXKp+n\nQ8Uh4TNNTCvOUlAHycZNaxYTlEPn\n-----END CERTIFICATE-----`;
|
||||
// contains unsupported subject and extension OIDs
|
||||
export const unsupportedOids = `-----BEGIN CERTIFICATE-----\nMIIEjDCCA3SgAwIBAgIUD4EeORgh/i+ZZFOk8KsGKQPWsoIwDQYJKoZIhvcNAQEL\nBQAwgZIxMTAvBgNVBAMMKGZhbmN5LWNlcnQtdW5zdXBwb3J0ZWQtc3Viai1hbmQt\nZXh0LW9pZHMxCzAJBgNVBAYTAlVTMQ8wDQYDVQQIDAZLYW5zYXMxDzANBgNVBAcM\nBlRvcGVrYTESMBAGA1UECgwJQWNtZSwgSW5jMRowGAYJKoZIhvcNAQkBFgtmb29A\nYmFyLmNvbTAeFw0yMzAxMjMxODQ3MjNaFw0zMzAxMjAxODQ3MjNaMIGSMTEwLwYD\nVQQDDChmYW5jeS1jZXJ0LXVuc3VwcG9ydGVkLXN1YmotYW5kLWV4dC1vaWRzMQsw\nCQYDVQQGEwJVUzEPMA0GA1UECAwGS2Fuc2FzMQ8wDQYDVQQHDAZUb3Bla2ExEjAQ\nBgNVBAoMCUFjbWUsIEluYzEaMBgGCSqGSIb3DQEJARYLZm9vQGJhci5jb20wggEi\nMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDyYH5qS7krfZ2tA5uZsY2qXbTb\ntGNG1BsyDhZ/qqVlQybjDsHJZwNUbpfhBcCLaKyAwH1R9n54NOOOn6bYgfKWTgy3\nL7224YDAqYe7Y/GPjgI2MRvRfn6t2xzQxtJ0l0k8LeyNcwhiqYLQyOOfDdc127fm\nW40r2nmhLpH0i9e2I/YP1HQ+ldVgVBqeUTntgVSBfrQF56v9mAcvvHEa5sdHqmX4\nJ2lhWTnx9jqb7NZxCem76BlX1Gt5TpP3Ym2ZFVQI9fuPK4O8JVhk1KBCmIgR3Ft+\nPpFUs/c41EMunKJNzveYrInSDScaC6voIJpK23nMAiM1HckLfUUc/4UojD+VAgMB\nAAGjgdcwgdQwHQYDVR0OBBYEFH7tt4enejKTZtYjUKUUx6PXyzlgMB8GA1UdIwQY\nMBaAFH7tt4enejKTZtYjUKUUx6PXyzlgMA4GA1UdDwEB/wQEAwIFoDAgBgNVHSUB\nAf8EFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwEgYDVR0TAQH/BAgwBgEB/wIBCjBM\nBgNVHREERTBDhwTAngEmhgx1cmlTdXBwb3J0ZWSCEWRucy1OYW1lU3VwcG9ydGVk\noBoGAyoDBKATDBFleGFtcGxlIG90aGVybmFtZTANBgkqhkiG9w0BAQsFAAOCAQEA\nP6ckVJgbcJue+MK3RVDuG+Mh7dl89ynC7NwpQFRjLVZQuoMHZT/dcLlVeFejVXu5\nR+IPLmQU6NV7JAmy4zGap8awf12QTy3g410ecrSF94WWlu8bPoekfUnnP+kfzLPH\nCUAkRKxWDSRKX5C8cMMxacVBBaBIayuusLcHkHmxLLDw34PFzyz61gtZOJq7JYnD\nhU9YsNh6bCDmnBDBsDMOI7h8lBRQwTiWVoSD9YNVvFiY29YvFbJQGdh+pmBtf7E+\n1B/0t5NbvqlQSbhMM0QgYFhuCxr3BGNob7kRjgW4i+oh+Nc5ptA5q70QMaYudqRS\nd8SYWhRdxmH3qcHNPcR1iw==\n-----END CERTIFICATE-----`;
|
||||
export const certWithoutCN = `-----BEGIN CERTIFICATE-----\nMIIDUDCCAjigAwIBAgIUEUpM5i7XMd/imZkR9XvonMaqPyYwDQYJKoZIhvcNAQEL\nBQAwHDEaMBgGCSqGSIb3DQEJARYLZm9vQGJhci5jb20wHhcNMjMwMTIzMjMyODEw\nWhcNMzMwMTIwMjMyODEwWjAcMRowGAYJKoZIhvcNAQkBFgtmb29AYmFyLmNvbTCC\nASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAPGSdeqLICZcoUzxk88F8Tp+\nVNI+mS74L8pHyb9ZNZfeXPo0E9L5pi+KKI7rkxAtBGUecG1ENSxDDK9p6XZhWHSU\nZ6bdjOsjcIlfiM+1hhtDclIVxIDnz2Jt1/Vmnm8DXwdwVATWiFLTnfm288deNwsT\npl0ehAR3BadkZvteC6t+giEw/4qm1/FP53GEBOQeUWJDZRvtL37rdx4joFv3cR4w\nV0dukOjc5AGXtIOorO145OSZj8s7RsW3pfGcFUcOg7/flDxfK1UqFflQa7veLvKa\nWE/fOMyB/711QjSkTuQ5Rw3Rf9Fr2pqVJQgElTIW1SKaX5EJTB9mtGB34UqUXtsC\nAwEAAaOBiTCBhjAdBgNVHQ4EFgQUyhFP/fm+798mErPD5VQvEaAZQrswHwYDVR0j\nBBgwFoAUyhFP/fm+798mErPD5VQvEaAZQrswDgYDVR0PAQH/BAQDAgWgMCAGA1Ud\nJQEB/wQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjASBgNVHRMBAf8ECDAGAQH/AgEK\nMA0GCSqGSIb3DQEBCwUAA4IBAQCishzVkhuSAtqxgsZdYzBs3GpakGIio5zReW27\n6dk96hYCbbe4K3DtcFbRD1B8t6aTJlHxkFRaOWErSXu9WP3fUhIDNRE64Qsrg1zk\n3Km430qBlorXmTp6xhYHQfY5bn5rT2YY7AmaYIlIFxRhod43i5GDbBP+e+d/vTqR\nv1AJflYofeR4LeATP64B6a4R+QQVoxI43+pyH3ka+nRHwJBR9h8SMtJoqBy7x9pl\nYlBDa8lSn05doA3+e03VIzitvBBWI4oX1XB0tShSLk6YJXayIwe0ZNVvfYLIRKCp\nb4DUwChYzG/FwFSssUAqzVFhu3i+uU3Z47bsLVm0R5m7hLiZ\n-----END CERTIFICATE-----`;
|
||||
|
@ -65,7 +65,7 @@ module('Integration | Component | pki | Page::PkiCertificateDetails', function (
|
||||
|
||||
assert
|
||||
.dom('[data-test-component="info-table-row"]')
|
||||
.exists({ count: 6 }, 'Correct number of fields render when certificate has not been revoked');
|
||||
.exists({ count: 5 }, 'Correct number of fields render when certificate has not been revoked');
|
||||
assert
|
||||
.dom('[data-test-value-div="Certificate"] [data-test-masked-input]')
|
||||
.exists('Masked input renders for certificate');
|
||||
|
@ -107,7 +107,7 @@ module('Integration | Component | pki-role-form', function (hooks) {
|
||||
const groupBoxHeight = find('[data-test-toggle-div="Key usage"]').clientHeight;
|
||||
assert.strictEqual(
|
||||
groupBoxHeight,
|
||||
518,
|
||||
567,
|
||||
'renders the correct height of the box element if the component is rending as a flexbox'
|
||||
);
|
||||
await click(SELECTORS.roleCreateButton);
|
||||
|
268
ui/tests/integration/utils/parse-pki-cert-test.js
Normal file
268
ui/tests/integration/utils/parse-pki-cert-test.js
Normal file
@ -0,0 +1,268 @@
|
||||
import { module, test } from 'qunit';
|
||||
import { setupTest } from 'ember-qunit';
|
||||
import { parseCertificate, parseExtensions, parseSubject, formatValues } from 'vault/utils/parse-pki-cert';
|
||||
import * as asn1js from 'asn1js';
|
||||
import { fromBase64, stringToArrayBuffer } from 'pvutils';
|
||||
import { Certificate } from 'pkijs';
|
||||
import { addHours, fromUnixTime, isSameDay } from 'date-fns';
|
||||
import errorMessage from 'vault/utils/error-message';
|
||||
import { SAN_TYPES } from 'vault/utils/parse-pki-cert-oids';
|
||||
import {
|
||||
certWithoutCN,
|
||||
loadedCert,
|
||||
pssTrueCert,
|
||||
skeletonCert,
|
||||
unsupportedOids,
|
||||
} from 'vault/tests/helpers/pki/values';
|
||||
|
||||
module('Integration | Util | parse pki certificate', function (hooks) {
|
||||
setupTest(hooks);
|
||||
|
||||
hooks.beforeEach(function () {
|
||||
this.getErrorMessages = (certErrors) => certErrors.map((error) => errorMessage(error));
|
||||
this.certSchema = (cert) => {
|
||||
const cert_base64 = cert.replace(/(-----(BEGIN|END) CERTIFICATE-----|\n)/g, '');
|
||||
const cert_der = fromBase64(cert_base64);
|
||||
const cert_asn1 = asn1js.fromBER(stringToArrayBuffer(cert_der));
|
||||
return new Certificate({ schema: cert_asn1.result });
|
||||
};
|
||||
this.parsableLoadedCert = this.certSchema(loadedCert);
|
||||
this.parsableUnsupportedCert = this.certSchema(unsupportedOids);
|
||||
});
|
||||
|
||||
test('it parses a certificate with supported values', async function (assert) {
|
||||
assert.expect(2);
|
||||
// certificate contains all allowable params
|
||||
const parsedCert = parseCertificate(loadedCert);
|
||||
assert.propEqual(
|
||||
parsedCert,
|
||||
{
|
||||
alt_names: 'altname1, altname2',
|
||||
can_parse: true,
|
||||
common_name: 'common-name.com',
|
||||
country: 'France',
|
||||
exclude_cn_from_sans: true,
|
||||
expiry_date: {},
|
||||
ip_sans: 'OCTET STRING : C09E0126', // when parsed, should be 192.158.1.38
|
||||
issue_date: {},
|
||||
locality: 'Paris',
|
||||
max_path_length: 17,
|
||||
not_valid_after: 1989622380,
|
||||
not_valid_before: 1674262350,
|
||||
organization: 'Widget',
|
||||
ou: 'Finance',
|
||||
parsing_errors: [],
|
||||
permitted_dns_domains: 'dnsname1.com, dsnname2.com',
|
||||
postal_code: '123456',
|
||||
province: 'Champagne',
|
||||
serial_number: 'cereal1292',
|
||||
signature_bits: '512',
|
||||
street_address: '234 sesame',
|
||||
ttl: '87600h',
|
||||
uri_sans: 'testuri1, testuri2',
|
||||
use_pss: false,
|
||||
},
|
||||
'it contains expected attrs, cn is excluded from alt_names (exclude_cn_from_sans: true)'
|
||||
);
|
||||
assert.ok(
|
||||
isSameDay(
|
||||
addHours(fromUnixTime(parsedCert.not_valid_before), Number(parsedCert.ttl.split('h')[0])),
|
||||
fromUnixTime(parsedCert.not_valid_after),
|
||||
'ttl value is correct'
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
test('it parses a certificate with use_pass=true and exclude_cn_from_sans=false', async function (assert) {
|
||||
assert.expect(2);
|
||||
const parsedPssCert = parseCertificate(pssTrueCert);
|
||||
assert.propContains(
|
||||
parsedPssCert,
|
||||
{ signature_bits: '256', ttl: '768h', use_pss: true },
|
||||
'returns signature_bits value and use_pss is true'
|
||||
);
|
||||
assert.propContains(
|
||||
parsedPssCert,
|
||||
{
|
||||
alt_names: 'common-name.com',
|
||||
can_parse: true,
|
||||
common_name: 'common-name.com',
|
||||
exclude_cn_from_sans: false,
|
||||
},
|
||||
'common name is included in alt_names'
|
||||
);
|
||||
});
|
||||
|
||||
test('it returns parsing_errors when certificate has unsupported values', async function (assert) {
|
||||
assert.expect(2);
|
||||
const parsedCert = parseCertificate(unsupportedOids); // contains unsupported subject and extension OIDs
|
||||
const parsingErrors = this.getErrorMessages(parsedCert.parsing_errors);
|
||||
|
||||
assert.propContains(
|
||||
parsedCert,
|
||||
{
|
||||
alt_names: 'dns-NameSupported',
|
||||
common_name: 'fancy-cert-unsupported-subj-and-ext-oids',
|
||||
ip_sans: 'OCTET STRING : C09E0126', // when parsed, should be 192.158.1.38
|
||||
parsing_errors: [{}, {}, {}],
|
||||
uri_sans: 'uriSupported',
|
||||
},
|
||||
'supported values are present when unsupported values exist'
|
||||
);
|
||||
assert.propEqual(
|
||||
parsingErrors,
|
||||
[
|
||||
'certificate contains unsupported subject OIDs',
|
||||
'certificate contains unsupported extension OIDs',
|
||||
'subjectAltName contains unsupported types',
|
||||
],
|
||||
'it contains expected error messages'
|
||||
);
|
||||
});
|
||||
|
||||
test('it returns attr with a null value if nonexistent', async function (assert) {
|
||||
assert.expect(1);
|
||||
const onlyHasCommonName = parseCertificate(skeletonCert);
|
||||
assert.propContains(
|
||||
onlyHasCommonName,
|
||||
{
|
||||
alt_names: 'common-name.com',
|
||||
common_name: 'common-name.com',
|
||||
country: null,
|
||||
ip_sans: null,
|
||||
locality: null,
|
||||
max_path_length: undefined,
|
||||
organization: null,
|
||||
ou: null,
|
||||
postal_code: null,
|
||||
province: null,
|
||||
serial_number: null,
|
||||
street_address: null,
|
||||
uri_sans: null,
|
||||
},
|
||||
'it contains expected attrs'
|
||||
);
|
||||
});
|
||||
|
||||
test('the helper parseSubject returns object with correct key/value pairs', async function (assert) {
|
||||
assert.expect(3);
|
||||
const supportedSubj = parseSubject(this.parsableLoadedCert.subject.typesAndValues);
|
||||
assert.propEqual(
|
||||
supportedSubj,
|
||||
{
|
||||
subjErrors: [],
|
||||
subjValues: {
|
||||
common_name: 'common-name.com',
|
||||
country: 'France',
|
||||
locality: 'Paris',
|
||||
organization: 'Widget',
|
||||
ou: 'Finance',
|
||||
postal_code: '123456',
|
||||
province: 'Champagne',
|
||||
serial_number: 'cereal1292',
|
||||
street_address: '234 sesame',
|
||||
},
|
||||
},
|
||||
'it returns supported subject values'
|
||||
);
|
||||
|
||||
const unsupportedSubj = parseSubject(this.parsableUnsupportedCert.subject.typesAndValues);
|
||||
assert.propEqual(
|
||||
this.getErrorMessages(unsupportedSubj.subjErrors),
|
||||
['certificate contains unsupported subject OIDs'],
|
||||
'it returns subject errors'
|
||||
);
|
||||
assert.ok(
|
||||
unsupportedSubj.subjErrors.every((e) => e instanceof Error),
|
||||
'subjErrors contain error objects'
|
||||
);
|
||||
});
|
||||
|
||||
test('the helper parseExtensions returns object with correct key/value pairs', async function (assert) {
|
||||
assert.expect(9);
|
||||
// assert supported extensions return correct type
|
||||
const supportedExtensions = parseExtensions(this.parsableLoadedCert.extensions);
|
||||
let { extValues, extErrors } = supportedExtensions;
|
||||
for (const keyName in SAN_TYPES) {
|
||||
assert.ok(Array.isArray(extValues[keyName]), `${keyName} is an array`);
|
||||
}
|
||||
assert.ok(Array.isArray(extValues.permitted_dns_domains), 'permitted_dns_domains is an array');
|
||||
assert.ok(Number.isInteger(extValues.max_path_length), 'max_path_length is an integer');
|
||||
// TODO add assertion for key_usage
|
||||
assert.strictEqual(extErrors.length, 0, 'no extension errors');
|
||||
|
||||
// assert unsupported extensions return errors
|
||||
const unsupportedExt = parseExtensions(this.parsableUnsupportedCert.extensions);
|
||||
({ extValues, extErrors } = unsupportedExt);
|
||||
assert.propEqual(
|
||||
this.getErrorMessages(extErrors),
|
||||
['certificate contains unsupported extension OIDs', 'subjectAltName contains unsupported types'],
|
||||
'it returns extension errors'
|
||||
);
|
||||
assert.ok(
|
||||
extErrors.every((e) => e instanceof Error),
|
||||
'subjErrors contain error objects'
|
||||
);
|
||||
assert.ok(Number.isInteger(extValues.max_path_length), 'max_path_length is an integer');
|
||||
});
|
||||
|
||||
test('the helper formatValues returns object with correct types', async function (assert) {
|
||||
assert.expect(1);
|
||||
const supportedSubj = parseSubject(this.parsableLoadedCert.subject.typesAndValues);
|
||||
const supportedExtensions = parseExtensions(this.parsableLoadedCert.extensions);
|
||||
assert.propContains(
|
||||
formatValues(supportedSubj, supportedExtensions),
|
||||
{
|
||||
alt_names: 'altname1, altname2',
|
||||
ip_sans: 'OCTET STRING : C09E0126', // when parsed, should be 192.158.1.38
|
||||
permitted_dns_domains: 'dnsname1.com, dsnname2.com',
|
||||
uri_sans: 'testuri1, testuri2',
|
||||
parsing_errors: [],
|
||||
exclude_cn_from_sans: true,
|
||||
},
|
||||
`values for ${Object.keys(SAN_TYPES).join(', ')} are comma separated strings (and no longer arrays)`
|
||||
);
|
||||
});
|
||||
|
||||
test('it fails silently when passed null', async function (assert) {
|
||||
assert.expect(3);
|
||||
const parsedCert = parseCertificate(certWithoutCN);
|
||||
assert.propEqual(
|
||||
parsedCert,
|
||||
{
|
||||
can_parse: true,
|
||||
common_name: null,
|
||||
country: null,
|
||||
exclude_cn_from_sans: false,
|
||||
expiry_date: {},
|
||||
issue_date: {},
|
||||
locality: null,
|
||||
max_path_length: 10,
|
||||
not_valid_after: 1989876490,
|
||||
not_valid_before: 1674516490,
|
||||
organization: null,
|
||||
ou: null,
|
||||
parsing_errors: [{}, {}],
|
||||
postal_code: null,
|
||||
province: null,
|
||||
serial_number: null,
|
||||
signature_bits: '256',
|
||||
street_address: null,
|
||||
ttl: '87600h',
|
||||
use_pss: false,
|
||||
},
|
||||
'it parses a cert without CN'
|
||||
);
|
||||
const parsingErrors = this.getErrorMessages(parsedCert.parsing_errors);
|
||||
assert.propEqual(
|
||||
parsingErrors,
|
||||
['certificate contains unsupported subject OIDs', 'certificate contains unsupported extension OIDs'],
|
||||
'it returns correct errors'
|
||||
);
|
||||
assert.propEqual(
|
||||
formatValues(null, null),
|
||||
{ parsing_errors: [Error('error parsing certificate')] },
|
||||
'it returns error if unable to format values'
|
||||
);
|
||||
});
|
||||
});
|
@ -15994,13 +15994,6 @@ punycode@^2.1.0, punycode@^2.1.1:
|
||||
resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec"
|
||||
integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==
|
||||
|
||||
pvtsutils@^1.3.2:
|
||||
version "1.3.2"
|
||||
resolved "https://registry.yarnpkg.com/pvtsutils/-/pvtsutils-1.3.2.tgz#9f8570d132cdd3c27ab7d51a2799239bf8d8d5de"
|
||||
integrity sha512-+Ipe2iNUyrZz+8K/2IOo+kKikdtfhRKzNpQbruF2URmqPtoqAs8g3xS7TJvFF2GcPXjh7DkqMnpVveRFq4PgEQ==
|
||||
dependencies:
|
||||
tslib "^2.4.0"
|
||||
|
||||
pvutils@^1.0.17, pvutils@latest:
|
||||
version "1.0.17"
|
||||
resolved "https://registry.yarnpkg.com/pvutils/-/pvutils-1.0.17.tgz#ade3c74dfe7178944fe44806626bd2e249d996bf"
|
||||
@ -18225,7 +18218,7 @@ tslib@^2.0.3:
|
||||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.2.0.tgz#fb2c475977e35e241311ede2693cee1ec6698f5c"
|
||||
integrity sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w==
|
||||
|
||||
tslib@^2.1.0, tslib@^2.4.0:
|
||||
tslib@^2.1.0:
|
||||
version "2.4.1"
|
||||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.4.1.tgz#0d0bfbaac2880b91e22df0768e55be9753a5b17e"
|
||||
integrity sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==
|
||||
|
Loading…
Reference in New Issue
Block a user