mirror of
https://github.com/hashicorp/vault.git
synced 2026-05-28 04:10:44 -04:00
Add support for missing attributes in PKI UI (#18953)
* Add additional OIDs for extKeyUsage Signed-off-by: Alexander Scheel <alex.scheel@hashicorp.com> * Allow ignoring AIA info on issuers Signed-off-by: Alexander Scheel <alex.scheel@hashicorp.com> * Tell users which extension OIDs are not allowed Signed-off-by: Alexander Scheel <alex.scheel@hashicorp.com> * Add commentary on cross-signing failure modes Signed-off-by: Alexander Scheel <alex.scheel@hashicorp.com> * Add parsing of keyUsage Signed-off-by: Alexander Scheel <alex.scheel@hashicorp.com> * Remove ext_key_usage parsing - doesn't exist on API Signed-off-by: Alexander Scheel <alex.scheel@hashicorp.com> * Add support for parsing ip_sans attribute Signed-off-by: Alexander Scheel <alex.scheel@hashicorp.com> * Use Uint8Array directly for key_usage parsing Signed-off-by: Alexander Scheel <alex.scheel@hashicorp.com> * Add error on unknown key usage values Signed-off-by: Alexander Scheel <alex.scheel@hashicorp.com> * Fix typing of IPv6 SANs, verficiation of keyUsages Signed-off-by: Alexander Scheel <alex.scheel@hashicorp.com> * Correctly format ip addresses Signed-off-by: Alexander Scheel <alex.scheel@hashicorp.com> * add ip_sans to details page * fix typo * update tests * alphabetize attrs * hold off on ip compression * rename model attrs * parse other_names * is that illegal * add parenthesis to labels * update tests to account for other_sans --------- Signed-off-by: Alexander Scheel <alex.scheel@hashicorp.com> Co-authored-by: clairebontempo@gmail.com <clairebontempo@gmail.com> Co-authored-by: claire bontempo <68122737+hellobontempo@users.noreply.github.com>
This commit is contained in:
parent
399baabd61
commit
e061eae584
10 changed files with 207 additions and 45 deletions
|
|
@ -49,8 +49,9 @@ export default class PkiCertificateBaseModel extends Model {
|
|||
// Parsed from cert in serializer
|
||||
@attr('number', { formatDate: true }) notValidAfter;
|
||||
@attr('number', { formatDate: true }) notValidBefore;
|
||||
@attr('string') uriSans;
|
||||
@attr('string') altNames;
|
||||
@attr('string', { label: 'URI Subject Alternative Names (URI SANs)' }) uriSans;
|
||||
@attr('string', { label: 'IP Subject Alternative Names (IP SANs)' }) ipSans;
|
||||
@attr('string', { label: 'Subject Alternative Names (SANs)' }) altNames;
|
||||
@attr('string') signatureBits;
|
||||
|
||||
// For importing
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ const issuerUrls = ['issuingCertificates', 'crlDistributionPoints', 'ocspServers
|
|||
'serialNumber',
|
||||
'keyId',
|
||||
'uriSans',
|
||||
'ipSans',
|
||||
'notValidBefore',
|
||||
'notValidAfter',
|
||||
],
|
||||
|
|
@ -38,11 +39,6 @@ export default class PkiIssuerModel extends PkiCertificateBaseModel {
|
|||
})
|
||||
keyId;
|
||||
|
||||
@attr({
|
||||
label: 'URI Subject Alternative Names (URI SANs)',
|
||||
})
|
||||
uriSans;
|
||||
|
||||
@attr('string') issuerName;
|
||||
|
||||
@attr({
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ export const SUBJECT_OIDs = {
|
|||
};
|
||||
|
||||
export const EXTENSION_OIDs = {
|
||||
key_usage: '2.5.29.15',
|
||||
key_usage: '2.5.29.15', // contains keyUsage values (KEY_USAGE_BITS below)
|
||||
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
|
||||
|
|
@ -21,12 +21,29 @@ export const EXTENSION_OIDs = {
|
|||
|
||||
// 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',
|
||||
// These two extensions are controlled by the parent authority.
|
||||
authority_key_identifier: '2.5.29.35',
|
||||
authority_access_info: '1.3.6.1.5.5.7.1.1',
|
||||
// This extension is based off the key material of the new issuer, which
|
||||
// will automatically match the existing issuer's key material.
|
||||
subject_key_identifier: '2.5.29.14',
|
||||
};
|
||||
|
||||
export const KEY_USAGE_BITS = [
|
||||
'DigitalSignature',
|
||||
'ContentCommitment',
|
||||
'KeyEncipherment',
|
||||
'DataEncipherment',
|
||||
'KeyAgreement',
|
||||
'CertSign',
|
||||
'CRLSign',
|
||||
'EncipherOnly',
|
||||
'DecipherOnly',
|
||||
];
|
||||
|
||||
// SubjectAltName/GeneralName types (scroll up to page 38 -> https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.7 )
|
||||
export const SAN_TYPES = {
|
||||
other_sans: 0, // <OID>;UTF8:<value>
|
||||
alt_names: 2, // dNSName
|
||||
uri_sans: 6, // uniformResourceIdentifier
|
||||
ip_sans: 7, // iPAddress - OCTET STRING
|
||||
|
|
|
|||
|
|
@ -4,10 +4,11 @@ import { Certificate } from 'pkijs';
|
|||
import { differenceInHours, getUnixTime } from 'date-fns';
|
||||
import {
|
||||
EXTENSION_OIDs,
|
||||
SUBJECT_OIDs,
|
||||
IGNORED_OIDs,
|
||||
KEY_USAGE_BITS,
|
||||
SAN_TYPES,
|
||||
SIGNATURE_ALGORITHM_OIDs,
|
||||
SUBJECT_OIDs,
|
||||
} from './parse-pki-cert-oids';
|
||||
|
||||
/*
|
||||
|
|
@ -87,8 +88,6 @@ export function formatValues(subject, extension) {
|
|||
}
|
||||
}
|
||||
|
||||
// TODO remove this deletion when key_usage is parsed, update test
|
||||
delete extValues.key_usage;
|
||||
return {
|
||||
...subjValues,
|
||||
...extValues,
|
||||
|
|
@ -114,9 +113,13 @@ 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 isUnexpectedSubjectOid = (rdn) => !Object.values(SUBJECT_OIDs).includes(rdn.type);
|
||||
if (subject.any(isUnexpectedSubjectOid)) {
|
||||
const unknown = subject.filter(isUnexpectedSubjectOid).map((rdn) => rdn.type);
|
||||
errors.push(new Error('certificate contains unsupported subject OIDs: ' + unknown.join(', ')));
|
||||
}
|
||||
|
||||
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
|
||||
|
|
@ -134,8 +137,10 @@ export function parseExtensions(extensions) {
|
|||
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'));
|
||||
const isUnknownExtension = (ext) => !allowedOids.includes(ext.extnID);
|
||||
if (extensions.any(isUnknownExtension)) {
|
||||
const unknown = extensions.filter(isUnknownExtension).map((ext) => ext.extnID);
|
||||
errors.push(new Error('certificate contains unsupported extension OIDs: ' + unknown.join(', ')));
|
||||
}
|
||||
|
||||
// make each extension its own key/value pair
|
||||
|
|
@ -149,7 +154,7 @@ export function parseExtensions(extensions) {
|
|||
const supportedNames = Object.keys(SAN_TYPES);
|
||||
const sans = values.subject_alt_name?.altNames;
|
||||
if (!sans) {
|
||||
errors.push(new Error('certificate contains unsupported subjectAltName values'));
|
||||
errors.push(new Error('certificate contains an unsupported subjectAltName construction'));
|
||||
} else if (sans.any((san) => !supportedTypes.includes(san.type))) {
|
||||
// pass along error that unsupported values exist
|
||||
errors.push(new Error('subjectAltName contains unsupported types'));
|
||||
|
|
@ -202,11 +207,108 @@ export function parseExtensions(extensions) {
|
|||
}
|
||||
|
||||
if (values.ip_sans) {
|
||||
// TODO parse octet string for IP addresses
|
||||
const parsed_ips = [];
|
||||
for (const ip_san of values.ip_sans) {
|
||||
const unused = ip_san.valueBlock.unusedBits;
|
||||
if (unused !== undefined && unused !== null && unused !== 0) {
|
||||
errors.push(new Error('unsupported ip_san value: non-zero unused bits in encoding'));
|
||||
continue;
|
||||
}
|
||||
|
||||
const ip = new Uint8Array(ip_san.valueBlock.valueHex);
|
||||
|
||||
// Length of the IP determines the type: 4 bytes for IPv4, 16 bytes for
|
||||
// IPv6.
|
||||
if (ip.length === 4) {
|
||||
const ip_addr = ip.join('.');
|
||||
parsed_ips.push(ip_addr);
|
||||
} else if (ip.length === 16) {
|
||||
const src = new Array(...ip);
|
||||
const hex = src.map((value) => '0' + new Number(value).toString(16));
|
||||
const trimmed = hex.map((value) => value.slice(value.length - 2, 3));
|
||||
// add a colon after every other number (those with an odd index)
|
||||
let ip_addr = trimmed.map((value, index) => (index % 2 === 0 ? value : value + ':')).join('');
|
||||
// Remove trailing :, if any.
|
||||
ip_addr = ip_addr.slice(-1) === ':' ? ip_addr.slice(0, -1) : ip_addr;
|
||||
parsed_ips.push(ip_addr);
|
||||
} else {
|
||||
errors.push(
|
||||
new Error(
|
||||
'unsupported ip_san value: unknown IP address size (should be 4 or 16 bytes, was ' +
|
||||
parseInt(ip.length / 2) +
|
||||
')'
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
values.ip_sans = parsed_ips;
|
||||
}
|
||||
|
||||
if (values.key_usage) {
|
||||
// TODO parse key_usage
|
||||
// KeyUsage is a big-endian bit-packed enum. Unused right-most bits are
|
||||
// truncated. So, a KeyUsage with CertSign+CRLSign would be "000001100",
|
||||
// with the right two bits truncated, and packed into an 8-bit, one-byte
|
||||
// string ("00000011"), introducing a leading zero. unused indicates that
|
||||
// this bit can be discard, shifting our result over by one, to go back
|
||||
// to its original form (minus trailing zeros).
|
||||
//
|
||||
// We can thus take our enumeration (KEY_USAGE_BITS), check whether the
|
||||
// bits are asserted, and push in our pretty names as appropriate.
|
||||
const unused = values.key_usage.valueBlock.unusedBits;
|
||||
const keyUsage = new Uint8Array(values.key_usage.valueBlock.valueHex);
|
||||
|
||||
const computedKeyUsages = [];
|
||||
for (const enumIndex in KEY_USAGE_BITS) {
|
||||
// May span two bytes.
|
||||
const byteIndex = parseInt(enumIndex / 8);
|
||||
const bitIndex = parseInt(enumIndex % 8);
|
||||
const enumName = KEY_USAGE_BITS[enumIndex];
|
||||
const mask = 1 << (8 - bitIndex); // Big endian.
|
||||
if (byteIndex >= keyUsage.length) {
|
||||
// DecipherOnly is rare and would push into a second byte, but we
|
||||
// don't have one so exit.
|
||||
break;
|
||||
}
|
||||
|
||||
let enumByte = keyUsage[byteIndex];
|
||||
const needsAdjust = byteIndex + 1 === keyUsage.length && unused > 0;
|
||||
if (needsAdjust) {
|
||||
enumByte = parseInt(enumByte << unused);
|
||||
}
|
||||
|
||||
const isSet = (mask & enumByte) === mask;
|
||||
if (isSet) {
|
||||
computedKeyUsages.push(enumName);
|
||||
}
|
||||
}
|
||||
|
||||
// Vault currently doesn't allow setting key_usage during issuer
|
||||
// generation, but will allow it if it comes in via an externally
|
||||
// generated CSR. Validate that key_usage matches expectations and
|
||||
// prune accordingly.
|
||||
const expectedUsages = ['CertSign', 'CRLSign'];
|
||||
const isUnexpectedKeyUsage = (ext) => !expectedUsages.includes(ext);
|
||||
|
||||
if (computedKeyUsages.any(isUnexpectedKeyUsage)) {
|
||||
const unknown = computedKeyUsages.filter(isUnexpectedKeyUsage);
|
||||
errors.push(new Error('unsupported key usage value on issuer certificate: ' + unknown.join(', ')));
|
||||
}
|
||||
|
||||
values.key_usage = computedKeyUsages;
|
||||
}
|
||||
|
||||
if (values.other_sans) {
|
||||
// We need to parse these into their server-side values.
|
||||
const parsed_sans = [];
|
||||
for (const san of values.other_sans) {
|
||||
let [objectId, constructed] = san.valueBlock.value;
|
||||
objectId = objectId.toJSON().valueBlock.value;
|
||||
constructed = constructed.valueBlock.value[0].toJSON(); // can I just grab the first element here?
|
||||
const { blockName } = constructed;
|
||||
const value = constructed.valueBlock.value;
|
||||
parsed_sans.push(`${objectId};${blockName.replace('String', '')}:${value}`);
|
||||
}
|
||||
values.other_sans = parsed_sans;
|
||||
}
|
||||
|
||||
delete values.subject_alt_name;
|
||||
|
|
@ -220,8 +322,8 @@ export function parseExtensions(extensions) {
|
|||
"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
|
||||
"key_usage": ['CertSign', 'CRLSign'],
|
||||
"ip_sans": ['192.158.1.38', '1234:fd2:5621:1:89::4500'],
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@
|
|||
<DocLink class="has-font-weight-normal" @path="/vault/docs/secrets/pki/rotation-primitives#notes-on-manual_chain">
|
||||
update the manual_chain<Icon @name="docs-link" />
|
||||
</DocLink>
|
||||
for each of your new certificates listed below,w and the existing issuer.
|
||||
for each of your new certificates listed below, and the existing issuer.
|
||||
</p>
|
||||
<div class="is-flex-start">
|
||||
{{#each this.inputFields as |column|}}
|
||||
|
|
|
|||
|
|
@ -99,6 +99,8 @@ export default class PkiIssuerCrossSign extends Component {
|
|||
@action
|
||||
async crossSignIntermediate(intMount, intName, newCrossSignedIssuer) {
|
||||
// 1. Fetch issuer we want to sign
|
||||
//
|
||||
// What/Recovery: any failure is early enough that you can bail safely/normally.
|
||||
const existingIssuer = await this.store.queryRecord('pki/issuer', {
|
||||
backend: intMount,
|
||||
id: intName,
|
||||
|
|
@ -115,6 +117,8 @@ export default class PkiIssuerCrossSign extends Component {
|
|||
}
|
||||
|
||||
// 2. Create the new CSR
|
||||
//
|
||||
// What/Recovery: any failure is early enough that you can bail safely/normally.
|
||||
const newCsr = await this.store
|
||||
.createRecord('pki/action', {
|
||||
keyRef: existingIssuer.keyId,
|
||||
|
|
@ -127,7 +131,10 @@ export default class PkiIssuerCrossSign extends Component {
|
|||
})
|
||||
.then(({ csr }) => csr);
|
||||
|
||||
// 3. Sign newCSR with correct parent to create cross-signed cert
|
||||
// 3. Sign newCSR with correct parent to create cross-signed cert, "issuing"
|
||||
// an intermediate certificate.
|
||||
//
|
||||
// What/Recovery: any failure is early enough that you can bail safely/normally.
|
||||
const signedCaChain = await this.store
|
||||
.createRecord('pki/action', {
|
||||
csr: newCsr,
|
||||
|
|
@ -144,6 +151,35 @@ export default class PkiIssuerCrossSign extends Component {
|
|||
.then(({ caChain }) => caChain.join('\n'));
|
||||
|
||||
// 4. Import the newly cross-signed cert to become an issuer
|
||||
//
|
||||
// What/Recovery:
|
||||
//
|
||||
// 1. Permission issue -> give the cert (`signedCaChain`) to the user,
|
||||
// let them import & name. (Issue you have is that you already issued
|
||||
// it (step 3) and so "undo" would mean revoking the cert, which
|
||||
// you might not have permissions to do either).
|
||||
//
|
||||
// 2. CRL rebuilding fails ("the CRL" in error message). Server returns
|
||||
// an error, we wanted the CRL rebuilt -- but the issuer was still
|
||||
// imported anyways. Only way to detect would be to do a list issuers
|
||||
// before and after. Recovery would be on the operator in this case;
|
||||
// reproduce the error and let them deal with it.
|
||||
//
|
||||
// End result: user should solve this issue, but we shouldn't undo anything
|
||||
// either.
|
||||
//
|
||||
// -> For 1 though, make sure to give the `signedCaChain` in the
|
||||
// error message for them.
|
||||
// -> For 2, you could list before and after to find the id of the
|
||||
// new issuer(s) so they can name them and fix any issues with
|
||||
// them.
|
||||
//
|
||||
// If its not a permissions error _and_ you did two lists, not finding
|
||||
// a new issuer...
|
||||
//
|
||||
// -> Unknown error. Could give them `signedCaChain` and serial of
|
||||
// the newly issued intermediate CA, so that they can do recovery
|
||||
// as they'd like.
|
||||
const issuerId = await this.store
|
||||
.createRecord('pki/issuer', { pemBundle: signedCaChain })
|
||||
.save({ adapterOptions: { import: true, mount: intMount } })
|
||||
|
|
@ -155,9 +191,15 @@ export default class PkiIssuerCrossSign extends Component {
|
|||
});
|
||||
|
||||
// 5. Fetch issuer imported above by issuer_id, name and save
|
||||
// Recovery: cosmetic issue; can let the user deal with it. Usually
|
||||
// fails because the name is in use.
|
||||
// Pre-fix: list all issuers, check the desired name isn't either
|
||||
// an existing issuer_id or an issuer_name.
|
||||
const crossSignedIssuer = await this.store.queryRecord('pki/issuer', { backend: intMount, id: issuerId });
|
||||
crossSignedIssuer.issuerName = newCrossSignedIssuer;
|
||||
crossSignedIssuer.save({ adapterOptions: { mount: intMount } });
|
||||
await crossSignedIssuer.save({ adapterOptions: { mount: intMount } });
|
||||
|
||||
// 6. Return the data to our caller.
|
||||
return {
|
||||
intermediateIssuer: existingIssuer,
|
||||
newCrossSignedIssuer: crossSignedIssuer,
|
||||
|
|
|
|||
|
|
@ -427,7 +427,7 @@ module('Acceptance | pki workflow', function (hooks) {
|
|||
assert.dom(SELECTORS.issuerDetails.title).hasText('View issuer certificate');
|
||||
assert
|
||||
.dom(`${SELECTORS.issuerDetails.defaultGroup} ${SELECTORS.issuerDetails.row}`)
|
||||
.exists({ count: 9 }, 'Renders 9 info table items under default group');
|
||||
.exists({ count: 10 }, 'Renders 10 info table items under default group');
|
||||
assert
|
||||
.dom(`${SELECTORS.issuerDetails.urlsGroup} ${SELECTORS.issuerDetails.row}`)
|
||||
.exists({ count: 3 }, 'Renders 4 info table items under URLs group');
|
||||
|
|
|
|||
|
|
@ -127,7 +127,7 @@ Q4raFvQrZth3Cz/X5yPTtQL78oBYrmHzoQKDFJ2z
|
|||
|
||||
// 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-----`;
|
||||
export const loadedCert = `-----BEGIN CERTIFICATE-----\nMIIE7TCCA9WgAwIBAgIULcrWXSz3/kG81EgBo0A4Zt+ZgkYwDQYJKoZIhvcNAQEL\nBQAwga0xDzANBgNVBAYTBkZyYW5jZTESMBAGA1UECBMJQ2hhbXBhZ25lMQ4wDAYD\nVQQHEwVQYXJpczETMBEGA1UECRMKMjM0IHNlc2FtZTEPMA0GA1UEERMGMTIzNDU2\nMQ8wDQYDVQQKEwZXaWRnZXQxEDAOBgNVBAsTB0ZpbmFuY2UxGDAWBgNVBAMTD2Nv\nbW1vbi1uYW1lLmNvbTETMBEGA1UEBRMKY2VyZWFsMTI5MjAeFw0yMzAyMDMxNzI3\nMzNaFw0yMzAzMDcxNzI4MDNaMIGtMQ8wDQYDVQQGEwZGcmFuY2UxEjAQBgNVBAgT\nCUNoYW1wYWduZTEOMAwGA1UEBxMFUGFyaXMxEzARBgNVBAkTCjIzNCBzZXNhbWUx\nDzANBgNVBBETBjEyMzQ1NjEPMA0GA1UEChMGV2lkZ2V0MRAwDgYDVQQLEwdGaW5h\nbmNlMRgwFgYDVQQDEw9jb21tb24tbmFtZS5jb20xEzARBgNVBAUTCmNlcmVhbDEy\nOTIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC8NO7LXHp28SzOqmQv\nns4fGogKydEklWG4JEN3pM+k9nTyEgA8DFhtSLvcqF0cqhEydw4FVU+LEUGySUer\nmM4VNl9qglFBgmYE8TNgWkUw9ZP6MNgx13I8zXTXOIDj0iwXks02x8451oPbqqdq\nXsCc4vSP7BPwQOjc0C56c54zyRC1zFm9jlh+As0QinuYcjFjVabCku6JSYc4kunh\nz7derU9cURUxB5/ja9zC7jGS8tg4XUWdUkbj1O/krEWfjQx9Kj8aEU1gFfAvW/Bd\nIqgAlHATYN6i8HDmAmdGty9zLht9wUgnAtVh3lK3939h/rI0qCLV6N/RjCC7csnz\n9I67AgMBAAGjggEBMIH+MA4GA1UdDwEB/wQEAwIBBjASBgNVHRMBAf8ECDAGAQH/\nAgERMB0GA1UdDgQWBBSSdKle0wMGy0jvPmcoDanGduhLqzAfBgNVHSMEGDAWgBSS\ndKle0wMGy0jvPmcoDanGduhLqzAuBgNVHR4BAf8EJDAioCAwDoIMZG5zbmFtZTEu\nY29tMA6CDGRzbm5hbWUyLmNvbTBoBgNVHREEYTBfoB0GCCsBBAEFCQIGoBEMD3Nv\nbWUtdXRmLXN0cmluZ4IIYWx0bmFtZTGCCGFsdG5hbWUyhwTAngEmhxASNA/SViEA\nAQCJAAAAAEUAhgh0ZXN0dXJpMYYIdGVzdHVyaTIwDQYJKoZIhvcNAQELBQADggEB\nAAQukDwIg01QLQK2MQqjePNZlJleKLMK9LiabyGqc7u4bgmX3gYIrH7uopvO5cIv\nvqxcVBATQ6ez29t5MagzDu1+vnwE8fQhRoe0sp5TRLiGSlBJf53+0Wb3vbaOT0Fx\n/FFK0f2wHqYv3h/CTxu8YxDY4DwCRTPJ2KfTvT85BXtTUlzKIp1ytALSKcz0Owoe\neQPtQUdi8UHef8uHuWbk7DftMXojXbCqtHQdS3Rrl9zyc+Ds67flb5hKEseQZRgw\ntPtAIxhjSfZPTjl/3aasCBikESdeS8IOxIXL1bGun0xWnIBBc9uRe8hpdPjZj7Eh\nIt7ucIzFep0DLWCeQrAHeqo=\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
|
||||
|
|
|
|||
|
|
@ -337,7 +337,7 @@ module('Integration | Component | pki issuer cross sign', function (hooks) {
|
|||
assert
|
||||
.dom('[data-test-alert-banner="alert"] .alert-banner-message-body')
|
||||
.hasText(
|
||||
'certificate contains unsupported subject OIDs, certificate contains unsupported extension OIDs, subjectAltName contains unsupported types '
|
||||
'certificate contains unsupported subject OIDs: 1.2.840.113549.1.9.1, certificate contains unsupported extension OIDs: 2.5.29.37'
|
||||
);
|
||||
|
||||
for (const field of FIELDS) {
|
||||
|
|
|
|||
|
|
@ -41,14 +41,16 @@ module('Integration | Util | parse pki certificate', function (hooks) {
|
|||
can_parse: true,
|
||||
common_name: 'common-name.com',
|
||||
country: 'France',
|
||||
other_sans: '1.3.1.4.1.5.9.2.6;UTF8:some-utf-string',
|
||||
exclude_cn_from_sans: true,
|
||||
expiry_date: {},
|
||||
ip_sans: 'OCTET STRING : C09E0126', // when parsed, should be 192.158.1.38
|
||||
ip_sans: '192.158.1.38, 1234:0fd2:5621:0001:0089:0000:0000:4500',
|
||||
key_usage: 'CertSign, CRLSign',
|
||||
issue_date: {},
|
||||
locality: 'Paris',
|
||||
max_path_length: 17,
|
||||
not_valid_after: 1989622380,
|
||||
not_valid_before: 1674262350,
|
||||
not_valid_after: 1678210083,
|
||||
not_valid_before: 1675445253,
|
||||
organization: 'Widget',
|
||||
ou: 'Finance',
|
||||
parsing_errors: [],
|
||||
|
|
@ -56,13 +58,13 @@ module('Integration | Util | parse pki certificate', function (hooks) {
|
|||
postal_code: '123456',
|
||||
province: 'Champagne',
|
||||
serial_number: 'cereal1292',
|
||||
signature_bits: '512',
|
||||
signature_bits: '256',
|
||||
street_address: '234 sesame',
|
||||
ttl: '87600h',
|
||||
ttl: '768h',
|
||||
uri_sans: 'testuri1, testuri2',
|
||||
use_pss: false,
|
||||
},
|
||||
'it contains expected attrs, cn is excluded from alt_names (exclude_cn_from_sans: true)'
|
||||
'it contains expected attrs, cn is excluded from alt_names (exclude_cn_from_sans: true) and ipV6 is compressed correctly'
|
||||
);
|
||||
assert.ok(
|
||||
isSameDay(
|
||||
|
|
@ -97,14 +99,13 @@ module('Integration | Util | parse pki certificate', function (hooks) {
|
|||
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: [{}, {}, {}],
|
||||
ip_sans: '192.158.1.38',
|
||||
parsing_errors: [{}, {}],
|
||||
uri_sans: 'uriSupported',
|
||||
},
|
||||
'supported values are present when unsupported values exist'
|
||||
|
|
@ -112,9 +113,8 @@ module('Integration | Util | parse pki certificate', function (hooks) {
|
|||
assert.propEqual(
|
||||
parsingErrors,
|
||||
[
|
||||
'certificate contains unsupported subject OIDs',
|
||||
'certificate contains unsupported extension OIDs',
|
||||
'subjectAltName contains unsupported types',
|
||||
'certificate contains unsupported subject OIDs: 1.2.840.113549.1.9.1',
|
||||
'certificate contains unsupported extension OIDs: 2.5.29.37',
|
||||
],
|
||||
'it contains expected error messages'
|
||||
);
|
||||
|
|
@ -169,7 +169,7 @@ module('Integration | Util | parse pki certificate', function (hooks) {
|
|||
const unsupportedSubj = parseSubject(this.parsableUnsupportedCert.subject.typesAndValues);
|
||||
assert.propEqual(
|
||||
this.getErrorMessages(unsupportedSubj.subjErrors),
|
||||
['certificate contains unsupported subject OIDs'],
|
||||
['certificate contains unsupported subject OIDs: 1.2.840.113549.1.9.1'],
|
||||
'it returns subject errors'
|
||||
);
|
||||
assert.ok(
|
||||
|
|
@ -179,7 +179,7 @@ module('Integration | Util | parse pki certificate', function (hooks) {
|
|||
});
|
||||
|
||||
test('the helper parseExtensions returns object with correct key/value pairs', async function (assert) {
|
||||
assert.expect(9);
|
||||
assert.expect(11);
|
||||
// assert supported extensions return correct type
|
||||
const supportedExtensions = parseExtensions(this.parsableLoadedCert.extensions);
|
||||
let { extValues, extErrors } = supportedExtensions;
|
||||
|
|
@ -188,7 +188,7 @@ module('Integration | Util | parse pki certificate', function (hooks) {
|
|||
}
|
||||
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.propEqual(extValues.key_usage, ['CertSign', 'CRLSign'], 'parses key_usage');
|
||||
assert.strictEqual(extErrors.length, 0, 'no extension errors');
|
||||
|
||||
// assert unsupported extensions return errors
|
||||
|
|
@ -196,7 +196,7 @@ module('Integration | Util | parse pki certificate', function (hooks) {
|
|||
({ extValues, extErrors } = unsupportedExt);
|
||||
assert.propEqual(
|
||||
this.getErrorMessages(extErrors),
|
||||
['certificate contains unsupported extension OIDs', 'subjectAltName contains unsupported types'],
|
||||
['certificate contains unsupported extension OIDs: 2.5.29.37'],
|
||||
'it returns extension errors'
|
||||
);
|
||||
assert.ok(
|
||||
|
|
@ -214,7 +214,7 @@ module('Integration | Util | parse pki certificate', function (hooks) {
|
|||
formatValues(supportedSubj, supportedExtensions),
|
||||
{
|
||||
alt_names: 'altname1, altname2',
|
||||
ip_sans: 'OCTET STRING : C09E0126', // when parsed, should be 192.158.1.38
|
||||
ip_sans: '192.158.1.38, 1234:0fd2:5621:0001:0089:0000:0000:4500',
|
||||
permitted_dns_domains: 'dnsname1.com, dsnname2.com',
|
||||
uri_sans: 'testuri1, testuri2',
|
||||
parsing_errors: [],
|
||||
|
|
@ -236,6 +236,7 @@ module('Integration | Util | parse pki certificate', function (hooks) {
|
|||
exclude_cn_from_sans: false,
|
||||
expiry_date: {},
|
||||
issue_date: {},
|
||||
key_usage: null,
|
||||
locality: null,
|
||||
max_path_length: 10,
|
||||
not_valid_after: 1989876490,
|
||||
|
|
@ -256,7 +257,10 @@ module('Integration | Util | parse pki certificate', function (hooks) {
|
|||
const parsingErrors = this.getErrorMessages(parsedCert.parsing_errors);
|
||||
assert.propEqual(
|
||||
parsingErrors,
|
||||
['certificate contains unsupported subject OIDs', 'certificate contains unsupported extension OIDs'],
|
||||
[
|
||||
'certificate contains unsupported subject OIDs: 1.2.840.113549.1.9.1',
|
||||
'certificate contains unsupported extension OIDs: 2.5.29.37',
|
||||
],
|
||||
'it returns correct errors'
|
||||
);
|
||||
assert.propEqual(
|
||||
|
|
|
|||
Loading…
Reference in a new issue