@smartdcc/dccboxed-keystore
Version:
DCC Boxed server keystore exposed as json db.
211 lines • 7.75 kB
JavaScript
;
/*
* Created on Thu Aug 04 2022
*
* Copyright (c) 2022 Smart DCC Limited
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.prepareRequest = prepareRequest;
exports.parseUrl = parseUrl;
exports.resolveHeaders = resolveHeaders;
exports.search = search;
exports.query = query;
const fast_xml_parser_1 = require("fast-xml-parser");
const got_1 = __importDefault(require("got"));
const content_type_1 = require("content-type");
const crypto_1 = require("crypto");
const certificateMetadata_1 = require("./certificateMetadata");
function prepareRequest(name, body) {
const builder = new fast_xml_parser_1.XMLBuilder({ ignoreAttributes: false });
return builder.build({
'?xml': {
'@_version': '1.0',
'@_encoding': 'utf-8',
[name]: body,
},
});
}
/**
* Parses a URL-like string and returns a properly formatted URL string
*
* @param url_like - String that may be a partial or complete URL
* @returns Properly formatted URL string with protocol and port if needed
*
* If the input string:
* - Does not contain a colon: Prepends 'http://' and appends port 8083
* - Does not contain protocol: Prepends 'http://'
* - Is already a complete URL: Returns as-is after URL validation
*/
function parseUrl(url_like) {
if (!url_like.includes(':')) {
return `http://${url_like}:8083/`;
}
const fullUrl = url_like.includes('://') ? url_like : `http://${url_like}`;
return new URL(fullUrl).toString();
}
/**
* Resolves a Headers object into a wider object by evaluating any function values
* and resolving any promises.
*
* @param headers - The Headers object containing string values, functions or promises
* @param implHeaders - Optional existing headers object to merge with (e.g. got's headers)
* @returns Promise resolving to a headers record with all string values
*/
async function resolveHeaders(headers = {}, implHeaders = {}) {
const resolvedHeaders = { ...implHeaders };
for (const [key, value] of Object.entries(headers)) {
if (typeof value === 'function') {
const result = value();
if (result instanceof Promise) {
resolvedHeaders[key] = await result;
}
else {
resolvedHeaders[key] = result;
}
}
else {
resolvedHeaders[key] = value;
}
}
return resolvedHeaders;
}
/**
* queries the SMKI certificatesearch service. when entering the
* CertificateSubjectName or CertificateSubjectAltName parameters, ensure they
* follow the <code>a1-a2-a3-a4-a5-a6-a7-a8</code> format.
*
* @param sr
* @param boxedAddress
* @returns
*/
async function search(sr, boxedAddress, headers) {
const result = await (0, got_1.default)(`${parseUrl(boxedAddress)}services/certificatesearch`, {
method: 'post',
headers: await resolveHeaders(headers, {
'content-type': 'application/xml',
}),
searchParams: { apikey: 'u3bg9gt38htd0j2' },
body: prepareRequest('CertificateSearchRequest', sr.q),
timeout: {
lookup: 100,
connect: 500,
request: 4000,
},
throwHttpErrors: false,
});
const ct = result.headers['content-type'];
if (result.statusCode === 402) {
return [];
}
else if (result.statusCode === 401) {
throw new Error('invalid search parameters');
}
else if (result.statusCode !== 200 ||
!ct ||
(0, content_type_1.parse)(ct).type !== 'application/xml') {
throw new Error(`unknown error ${result.statusCode}: ${ct}`);
}
const parser = new fast_xml_parser_1.XMLParser({
ignoreAttributes: false,
parseAttributeValue: false,
parseTagValue: false,
});
const searchResult = parser.parse(result.body);
const resultListMaybe = searchResult?.CertificateSearchResponse?.Result;
if (!resultListMaybe) {
return [];
}
const serials = [];
let resultList;
if (!Array.isArray(resultListMaybe)) {
resultList = [resultListMaybe];
}
else {
resultList = resultListMaybe;
}
for (const e of resultList) {
if (e?.CertificateUsage === sr.CertificateUsage &&
typeof e?.CertificateSerial === 'string' &&
e?.CertificateStatus === sr.CertificateStatus) {
const srRole = sr;
if (typeof srRole.CertificateRole !== 'string' ||
srRole.CertificateRole === e?.CertificateRole) {
serials.push(e?.CertificateSerial);
}
}
}
const queryResults = await Promise.all(serials.map((serial) => query(serial, boxedAddress, headers)));
return queryResults.filter((qr) => qr !== null);
}
async function query(serial, boxedAddress, headers) {
const result = await (0, got_1.default)(`${parseUrl(boxedAddress)}services/retrievecertificate`, {
method: 'post',
headers: await resolveHeaders(headers, {
'content-type': 'application/xml',
}),
searchParams: { apikey: 'u3bg9gt38htd0j2' },
body: prepareRequest('CertificateDataRequest', {
CertificateSerial: serial,
}),
timeout: {
lookup: 100,
connect: 500,
request: 4000,
},
throwHttpErrors: false,
});
const ct = result.headers['content-type'];
if (result.statusCode === 402) {
return null;
}
else if (result.statusCode === 401) {
throw new Error('invalid search parameters');
}
else if (result.statusCode !== 200 ||
!ct ||
(0, content_type_1.parse)(ct).type !== 'application/xml') {
throw new Error(`unknown error ${result.statusCode}: ${ct}`);
}
const parser = new fast_xml_parser_1.XMLParser({
ignoreAttributes: false,
parseAttributeValue: false,
parseTagValue: false,
});
const certificateResult = parser.parse(result.body);
const certificateResponse = certificateResult?.CertificateDataResponse?.CertificateResponse;
if (typeof certificateResponse?.CertificateBody === 'string') {
const x509 = new crypto_1.X509Certificate(`-----BEGIN CERTIFICATE-----\n${certificateResponse?.CertificateBody}\n-----END CERTIFICATE-----`);
if (typeof certificateResponse?.CertificateSubjectName === 'string') {
/* org cert */
return {
meta: (0, certificateMetadata_1.buildOrgCertificateMetadata)(x509),
x509,
};
}
else if (typeof certificateResponse?.CertificateSubjectAltName === 'string') {
/* device cert */
return {
meta: (0, certificateMetadata_1.buildDeviceCertificateMetadata)(x509),
x509,
};
}
}
return null;
}
//# sourceMappingURL=certificateSearch.js.map