Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ This repo is part of the app-server Zowe Component, and the change logs here may

## 3.2.0
- Bugfix: App-server /server/environment endpoint was missing the "agent" object, causing the Desktop to choose an indirect route to accessing ZSS. This fix improves latency and high availability behavior of ZSS APIs in the Desktop. (#588)
- Enhancement: When not using a SAF keyring, the app-server can now use PKCS12 keystores so that the PEM option for Zowe certificates no longer needs to be specified. (#596)
- Enhancement: Added utility certificateChecker.js which can use NodeJS to determine if a keystore of PKCS12, PEM, or SAF keyring is valid for use in Zowe (#597)
- Bugfix: Reduce minimum memory consumption. A bug in the library "axios" caused abnormally high memory utilization every startup, and has been removed. The app-servers functionality has not been altered from this change. ([#600](https://github.com/zowe/zlux-server-framework/pull/600))
- Bugfix: When eureka registration experienced a network failure, troubleshooting information was not available. The property `components.app-server.node.mediationLayer.traceTls` now exists for troubleshooting TLS issues. (#592)
Expand Down
53 changes: 12 additions & 41 deletions lib/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -248,49 +248,20 @@ module.exports.readFilesToArray = function(fileList, type, pass) {
try {
let extension = filePath.split('.').pop();
let content = fs.readFileSync(filePath);
if(extension == 'p12' || extension == 'pfx'){
let p12Der = forge.util.decode64(content.toString('base64'));
let p12Asn1 = forge.asn1.fromDer(p12Der);
let p12;
try {
p12 = forge.pkcs12.pkcs12FromAsn1(p12Asn1, pass || "password");
} catch (e1) {
loggers.bootstrapLogger.warn("ZWED0173W", e1.message);
p12 = forge.pkcs12.pkcs12FromAsn1(p12Asn1, "");
}
const certData = p12.getBags({bagType: forge.pki.oids.certBag})[forge.pki.oids.certBag];
const keyData = p12.getBags({bagType: forge.pki.oids.pkcs8ShroudedKeyBag})[forge.pki.oids.pkcs8ShroudedKeyBag];
if(certData != undefined && type != 1 && type != 3){ //CRLs not currently supported by node forge
for(let i = 0; i < certData.length; i++){
let certObj = certData[i];
contentArray.push(Buffer.from(forge.pki.certificateToPem(certObj.cert), 'utf8'));
}
}
if(keyData != undefined && type == 1){
for(let i = 0; i < keyData.length; i++){
const rsaPrivateKey = forge.pki.privateKeyToAsn1(keyData[i].key);
const privateKeyInfo = forge.pki.wrapRsaPrivateKey(rsaPrivateKey);
let privateKeyPem = forge.pki.privateKeyInfoToPem(privateKeyInfo);
var buf = Buffer.from(privateKeyPem, 'utf8');
contentArray.push(buf);
}
}
} else {
if(!content.toString().includes('-----BEGIN')){
let der = forge.util.decode64(content.toString('base64'));
let derAsn1 = forge.asn1.fromDer(der);
if(type == 1){
let privateKeyInfo = forge.pki.wrapRsaPrivateKey(derAsn1);
let privateKeyPem = forge.pki.privateKeyInfoToPem(privateKeyInfo);
contentArray.push(Buffer.from(privateKeyPem, 'utf8'));
} else {
let asn1Cert = forge.pki.certificateFromAsn1(derAsn1);
let pem = forge.pki.certificateToPem(asn1Cert);
contentArray.push(Buffer.from(pem, 'utf8'));
}
if(!content.toString().includes('-----BEGIN')){
let der = forge.util.decode64(content.toString('base64'));
let derAsn1 = forge.asn1.fromDer(der);
if(type == 1){
let privateKeyInfo = forge.pki.wrapRsaPrivateKey(derAsn1);
let privateKeyPem = forge.pki.privateKeyInfoToPem(privateKeyInfo);
contentArray.push(Buffer.from(privateKeyPem, 'utf8'));
} else {
contentArray.push(content);
let asn1Cert = forge.pki.certificateFromAsn1(derAsn1);
let pem = forge.pki.certificateToPem(asn1Cert);
contentArray.push(Buffer.from(pem, 'utf8'));
}
} else {
contentArray.push(content);
}
} catch (e) {
loggers.bootstrapLogger.warn("ZWED0052W", filePath, e.message); //loggers.bootstrapLogger.warn('Error when reading file='+filePath+'. Error='+e.message);
Expand Down
140 changes: 109 additions & 31 deletions lib/webserver.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ const contentLogger = util.loggers.contentLogger;
const childLogger = util.loggers.childLogger;
const networkLogger = util.loggers.network;

const forge = require('node-forge');

const os = require('os');
let keyring_js;
try {
Expand Down Expand Up @@ -130,8 +132,76 @@ function splitCryptoLocationsByType(locations) {
return locationsByType;
}

function loadPkcs12(location, pass) {
const p12Content = fs.readFileSync(location, 'binary');
const p12Asn1 = forge.asn1.fromDer(p12Content);
const p12 = forge.pkcs12.pkcs12FromAsn1(p12Asn1, false, pass);
return p12;
}

function getP12Content(p12, type, alias) {
//An array is expected, but just one object should be in it if we're looking for key/cert instead of CA.
let value = [];

for (let i = 0; i < p12.safeContents.length; i++) {
let safeContents = p12.safeContents[i];
bootstrapLogger.debug(`iterating through pkcs12 safe contents ${i}`);
for (let j = 0; j < safeContents.safeBags.length; j++) {
bootstrapLogger.debug(`iterating through pkcs12 bag ${j}`);
let bag = safeContents.safeBags[j];
if (type == CRYPTO_CONTENT_CERT && bag.type === forge.pki.oids.certBag) {
if (bag.attributes.friendlyName == alias) {
value.push(forge.pki.certificateToPem(bag.cert));
bootstrapLogger.debug(`Found pkcs12 certificate`);
return value;
}
} else if (type == CRYPTO_CONTENT_KEY && bag.type === forge.pki.oids.keyBag) {
if (bag.attributes.friendlyName == alias) {
value.push(forge.pki.privateKeyToPem(bag.key));
bootstrapLogger.debug(`Found pkcs12 key`);
return value;
}
} else if (type == CRYPTO_CONTENT_KEY && bag.type === forge.pki.oids.pkcs8ShroudedKeyBag) {
if (bag.attributes.friendlyName == alias) {
value.push(forge.pki.privateKeyToPem(bag.key));
bootstrapLogger.debug(`Found pkcs12 key`);
return value;
}
} else if (type == CRYPTO_CONTENT_CA && bag.type === forge.pki.oids.certBag && bag.cert?.extensions) {
let isCa = false;
for (let k = 0; k < bag.cert.extensions.length; k++) {
let extension = bag.cert.extensions[k];
bootstrapLogger.debug(`Potential CA extension ${k} = `,extension);
//2.5.29.19
if (extension.id == forge.pki.oids.basicConstraints) {
isCa = extension.cA;
break;
}
/*
// 2.5.29.15 has 'keyCertSign: true' on a CA, i guess?
if (extension.id == forge.pki.oids.keyUsage) {
isCa = extension.keyCertSign;
}
*/
}
if (isCa) {
value.push(forge.pki.certificateToPem(bag.cert));
bootstrapLogger.debug(`Found CA with name `+bag.attributes.friendlyName);
}
} else if (type == CRYPTO_CONTENT_CRL && bag.type === forge.pki.oids.crlBag) {
bootstrapLogger.warn(`Certificate Revocation List requested and found, but unhandled`);
return [];
} else {
bootstrapLogger.debug(`Ignoring PKCS12 object type=${bag.type}`);
}
}
}

return value;
}

// safkeyring://
function loadPem(locations, type, keyrings, pass) {
function loadPem(locations, type, keyringCache, p12Cache, pass, alias) {
const locationsByType = splitCryptoLocationsByType(locations);
let content = [];
const types = Object.keys(locationsByType);
Expand All @@ -152,13 +222,13 @@ function loadPem(locations, type, keyrings, pass) {
const {userId, keyringName, label} = parseSafKeyringAddress(safRingAddress);
if (userId && keyringName && label) {
const cachedKey = 'safkeyring://'+safRingAddress;
let keyringData = keyrings[cachedKey];
let keyringData = keyringCache[cachedKey];
const attribute = getAttributeNameForCryptoType('safkeyring', type);
try {
if (!keyringData) {
bootstrapLogger.debug(`Cache not found for ${cachedKey}`);
keyringData = keyring_js.getPemEncodedData(userId, keyringName, label);
keyrings[cachedKey] = keyringData;
keyringCache[cachedKey] = keyringData;
}
if (keyringData) {
if (keyringData[attribute]) {
Expand All @@ -185,42 +255,49 @@ function loadPem(locations, type, keyrings, pass) {
//Cannot load SAF keyring due to missing keyring_js library');
bootstrapLogger.warn('ZWED0150E');
}

const files = locationsByType['file'];
if (files) {
//workaround for a bug outside zlux: seems that some yaml files may come in with strings with trailing ',', just strip it.
content = util.readFilesToArray(files.map(file => file.charAt(file.length-1)==',' ? file.substring(0, file.length-1) : file),
type, pass).concat(content);
if (files && (files.length > 0)) {
const pemFiles = files.filter(name => !name.endsWith('.p12') && !name.endsWith('.pfx'));
const p12Files = files.filter(name => name.endsWith('.p12') || name.endsWith('.pfx'));
for (let i = 0; i < p12Files.length; i++) {
let p12 = p12Cache[p12Files[i]];
if (!p12) {
p12 = loadPkcs12(p12Files[i], pass);
p12Cache[p12Files[i]] = p12;
}

content = getP12Content(p12, type, alias);

// It does not make sense to load multiple cert/keys, does it?
if ((content.length >= 1) && ((type == CRYPTO_CONTENT_CERT) || (type == CRYPTO_CONTENT_KEY))) { break; }
}
if (pemFiles && pemFiles.length > 0) {
bootstrapLogger.warn(`PEM is insecure storage for TLS key material, consider using PKCS12 or SAF keyrings instead.`);
//workaround for a bug outside zlux: seems that some yaml files may come in with strings with trailing ',', just strip it.
content = util.readFilesToArray(pemFiles.map(file => file.charAt(file.length-1)==',' ? file.substring(0, file.length-1) : file),
type, pass).concat(content);
}
}
return {content, keyrings};
return {content, keyringCache, p12Cache};
}

function readTlsOptionsFromConfig(nodeConfig, httpsOptions, pass) {
function readTlsOptionsFromConfig(nodeConfig, httpsOptions, keystorePass, truststorePass, alias) {
//in case keys and certs can be read from the same keyring, store them here for later retrieval
let keyrings = {};
if (nodeConfig.https.pfx) {
try {
httpsOptions.pfx = fs.readFileSync(nodeConfig.https.pfx);
bootstrapLogger.info('ZWED0071I', nodeConfig.https.pfx); //bootstrapLogger.info('Using PFX: '+ nodeConfig.https.pfx);
} catch (e) {
bootstrapLogger.warn('ZWED0070W', e.message); //bootstrapLogger.warn('Error when reading PFX. Server cannot continue. Error='
//+ e.message);
process.exit(constants.EXIT_PFX_READ);
throw e;
}
} else {
if (nodeConfig.https.certificates) {
httpsOptions.cert = loadPem(nodeConfig.https.certificates, CRYPTO_CONTENT_CERT, keyrings, pass).content;
bootstrapLogger.info('ZWED0072I', nodeConfig.https.certificates); //bootstrapLogger.info('Using Certificate: ' + nodeConfig.https.certificates);
}
if (nodeConfig.https.keys) {
httpsOptions.key = loadPem(nodeConfig.https.keys, CRYPTO_CONTENT_KEY, keyrings, pass).content;
}
let keyringCache = {};
let p12Cache = {};
if (nodeConfig.https.certificates) {
httpsOptions.cert = loadPem(nodeConfig.https.certificates, CRYPTO_CONTENT_CERT, keyringCache, p12Cache, keystorePass, alias).content;
bootstrapLogger.info('ZWED0072I', nodeConfig.https.certificates); //bootstrapLogger.info('Using Certificate: ' + nodeConfig.https.certificates);
}
if (nodeConfig.https.keys) {
httpsOptions.key = loadPem(nodeConfig.https.keys, CRYPTO_CONTENT_KEY, keyringCache, p12Cache, keystorePass, alias).content;
}
if (nodeConfig.https.certificateAuthorities) {
httpsOptions.ca = loadPem(nodeConfig.https.certificateAuthorities, CRYPTO_CONTENT_CA, keyrings, pass).content;
httpsOptions.ca = loadPem(nodeConfig.https.certificateAuthorities, CRYPTO_CONTENT_CA, keyringCache, p12Cache, truststorePass).content;
}
if (nodeConfig.https.certificateRevocationLists) {
httpsOptions.crl = loadPem(nodeConfig.https.certificateRevocationLists, CRYPTO_CONTENT_CRL, keyrings, pass).content;
httpsOptions.crl = loadPem(nodeConfig.https.certificateRevocationLists, CRYPTO_CONTENT_CRL, keyringCache, p12Cache, keystorePass).content;
}
}

Expand Down Expand Up @@ -397,7 +474,8 @@ WebServer.prototype = {
this.httpsOptions.enableTrace = true;
}
bootstrapLogger.debug('TLS trace:', this.httpsOptions.enableTrace ? 'enabled' : 'disabled');
readTlsOptionsFromConfig(nodeConfig, this.httpsOptions, zoweConfig.zowe?.certificate?.keystore?.password);

readTlsOptionsFromConfig(nodeConfig, this.httpsOptions, zoweConfig.zowe?.certificate?.keystore?.password, zoweConfig.zowe?.certificate?.truststore?.password, zoweConfig.zowe?.certificate?.keystore?.alias);
}
},

Expand Down
Loading