Add option to use pfx or p12 for TLS connections

Add-pfxp12-to-tls-options
Dave Conway-Jones 2024-10-09 16:49:27 +01:00
parent 83acc4836b
commit c3d4421592
No known key found for this signature in database
GPG Key ID: 1DDB0E91A28C2643
4 changed files with 111 additions and 21 deletions

View File

@ -86,7 +86,7 @@
"@node-rs/bcrypt": "1.10.4"
},
"devDependencies": {
"dompurify": "2.4.1",
"dompurify": "3.1.7",
"grunt": "1.6.1",
"grunt-chmod": "~1.1.1",
"grunt-cli": "~1.4.3",

View File

@ -20,6 +20,13 @@
<label for="node-config-input-uselocalfiles" style="width: 70%;"><span data-i18n="tls.label.use-local-files"></label>
</div>
<div class="form-row">
<label style="width: 120px;" for="node-config-input-certType"><i class="fa fa-bars"></i> <span data-i18n="tls.label.certtype"></label>
<select id="node-config-input-certType">
<option value="files" data-i18n="tls.label.files"></option>
<option value="pfx" data-i18n="tls.label.pfx"></option>
</select>
</div>
<div class="form-row" id="node-tls-conf-cer">
<label style="width: 120px;"><i class="fa fa-file-text-o"></i> <span data-i18n="tls.label.cert"></span></label>
<span class="tls-config-input-data">
<label class="red-ui-button" for="node-config-input-certfile"><i class="fa fa-upload"></i> <span data-i18n="tls.label.upload"></span></label>
@ -31,7 +38,7 @@
<input type="hidden" id="node-config-input-certdata">
<input class="hide tls-config-input-path" style="width: calc(100% - 170px);" type="text" id="node-config-input-cert" data-i18n="[placeholder]tls.placeholder.cert">
</div>
<div class="form-row">
<div class="form-row" id="node-tls-conf-key">
<label style="width: 120px;" for="node-config-input-key"><i class="fa fa-file-text-o"></i> <span data-i18n="tls.label.key"></span></label>
<span class="tls-config-input-data">
<label class="red-ui-button" for="node-config-input-keyfile"><i class="fa fa-upload"></i> <span data-i18n="tls.label.upload"></span></label>
@ -43,11 +50,23 @@
<input type="hidden" id="node-config-input-keydata">
<input class="hide tls-config-input-path" style="width: calc(100% - 170px);" type="text" id="node-config-input-key" data-i18n="[placeholder]tls.placeholder.key">
</div>
<div class="form-row">
<label style="width: 100px; margin-left: 20px;" for="node-config-input-passphrase"> <span data-i18n="tls.label.passphrase"></span></label>
<input type="password" style="width: calc(100% - 170px);" id="node-config-input-passphrase" data-i18n="[placeholder]tls.placeholder.passphrase">
<div class="form-row" id="node-tls-conf-p12">
<label style="width: 120px;" for="node-config-input-p12"><i class="fa fa-file-text-o"></i> <span data-i18n="tls.label.p12"></span></label>
<span class="tls-config-input-data">
<label class="red-ui-button" for="node-config-input-p12file"><i class="fa fa-upload"></i> <span data-i18n="tls.label.upload"></span></label>
<input class="hide" type="file" id="node-config-input-p12file">
<span id="tls-config-p12name" style="width: calc(100% - 280px); overflow: hidden; line-height:34px; height:34px; text-overflow: ellipsis; white-space: nowrap; display: inline-block; vertical-align: middle;"> </span>
<button type="button" class="red-ui-button red-ui-button-small" id="tls-config-button-p12-clear" style="margin-left: 10px"><i class="fa fa-times"></i></button>
</span>
<input type="hidden" id="node-config-input-p12name">
<input type="hidden" id="node-config-input-p12data">
<input class="hide tls-config-input-path" style="width: calc(100% - 170px);" type="text" id="node-config-input-p12" data-i18n="[placeholder]tls.placeholder.p12">
</div>
<div class="form-row">
<label style="width: 120px;" for="node-config-input-passphrase"><i class="fa fa-key"></i> <span data-i18n="tls.label.passphrase"></span></label>
<input type="password" style="width: calc(100% - 170px);" id="node-config-input-passphrase" data-i18n="[placeholder]tls.placeholder.passphrase">
</div>
<div class="form-row" id="node-tls-conf-ca">
<label style="width: 120px;" for="node-config-input-ca"><i class="fa fa-file-text-o"></i> <span data-i18n="tls.label.ca"></span></label>
<span class="tls-config-input-data">
<label class="red-ui-button" for="node-config-input-cafile"><i class="fa fa-upload"></i> <span data-i18n="tls.label.upload"></span></label>
@ -83,6 +102,7 @@
category: 'config',
defaults: {
name: {value:""},
certType: {value:"files"},
cert: {value:"", validate: function(v,opt) {
var currentKey = $("#node-config-input-key").val();
if (currentKey === undefined) {
@ -103,6 +123,8 @@
}
return RED._("node-red:tls.error.invalid-key");
}},
p12: {value:""},
p12name: {value:""},
ca: {value:""},
certname: {value:""},
keyname: {value:""},
@ -115,6 +137,7 @@
certdata: {type:"text"},
keydata: {type:"text"},
cadata: {type:"text"},
p12data: {type:"text"},
passphrase: {type:"password"}
},
label: function() {
@ -124,6 +147,21 @@
return this.name?"node_label_italic":"";
},
oneditprepare: function() {
$("#node-config-input-certType").on('change',function() {
if ($("#node-config-input-certType").val() === "pfx") {
$("#node-tls-conf-cer").hide();
$("#node-tls-conf-key").hide();
$("#node-tls-conf-ca").hide();
$("#node-tls-conf-p12").show();
}
else {
$("#node-tls-conf-cer").show();
$("#node-tls-conf-key").show();
$("#node-tls-conf-ca").show();
$("#node-tls-conf-p12").hide();
}
});
function updateFileUpload() {
if ($("#node-config-input-uselocalfiles").is(':checked')) {
$(".tls-config-input-path").show();
@ -145,9 +183,19 @@
reader.onload = function(event) {
$("#tls-config-"+property+"name").text(filename);
$(filenameInputId).val(filename);
$(dataInputId).val(event.target.result);
if (property === "p12") {
$(dataInputId).val(btoa(String.fromCharCode(...new Uint8Array(event.target.result))))
}
else {
$(dataInputId).val(event.target.result);
}
}
if (property === "p12") {
reader.readAsArrayBuffer(file);
}
else {
reader.readAsText(file,"UTF-8");
}
reader.readAsText(file,"UTF-8");
}
$("#node-config-input-certfile" ).on("change", function() {
saveFile("cert", this.files[0]);
@ -158,6 +206,9 @@
$("#node-config-input-cafile" ).on("change", function() {
saveFile("ca", this.files[0]);
});
$("#node-config-input-p12file" ).on("change", function() {
saveFile("p12", this.files[0]);
});
function clearNameData(prop) {
$("#tls-config-"+prop+"name").text("");
@ -173,6 +224,9 @@
$("#tls-config-button-ca-clear").on("click", function() {
clearNameData("ca");
});
$("#tls-config-button-p12-clear").on("click", function() {
clearNameData("p12");
});
if (RED.settings.tlsConfigDisableLocalFiles) {
$("#node-config-row-uselocalfiles").hide();
@ -180,12 +234,13 @@
$("#node-config-row-uselocalfiles").show();
}
// in case paths were set from old TLS config
if(this.cert || this.key || this.ca) {
if (this.cert || this.key || this.ca || this.p12) {
$("#node-config-input-uselocalfiles").prop('checked',true);
}
$("#tls-config-certname").text(this.certname);
$("#tls-config-keyname").text(this.keyname);
$("#tls-config-caname").text(this.caname);
$("#tls-config-p12name").text(this.p12name);
updateFileUpload();
},
oneditsave: function() {
@ -193,10 +248,13 @@
clearNameData("ca");
clearNameData("cert");
clearNameData("key");
} else {
clearNameData("p12");
}
else {
$("#node-config-input-ca").val("");
$("#node-config-input-cert").val("");
$("#node-config-input-key").val("");
$("#node-config-input-p12").val("");
}
}
});

View File

@ -25,6 +25,8 @@ module.exports = function(RED) {
var certPath = n.cert.trim();
var keyPath = n.key.trim();
var caPath = n.ca.trim();
var p12Path = n.p12.trim();
this.certType = n.certType || "files";
this.servername = (n.servername||"").trim();
this.alpnprotocol = (n.alpnprotocol||"").trim();
@ -46,18 +48,31 @@ module.exports = function(RED) {
if (caPath) {
this.ca = fs.readFileSync(caPath);
}
} catch(err) {
}
catch(err) {
this.valid = false;
this.error(err.toString());
return;
}
} else {
}
else if (p12Path.length > 0) {
try {
this.pfx = fs.readFileSync(p12Path);
}
catch(err) {
this.valid = false;
this.error(err.toString());
return;
}
}
else {
if (this.credentials) {
var certData = this.credentials.certdata || "";
var keyData = this.credentials.keydata || "";
var caData = this.credentials.cadata || "";
var p12Data = this.credentials.p12data || "";
if ((certData.length > 0) !== (keyData.length > 0)) {
if ((certData.length > 0) !== (keyData.length > 0) && p12Data.length === 0) {
this.valid = false;
this.error(RED._("tls.error.missing-file"));
return;
@ -72,6 +87,9 @@ module.exports = function(RED) {
if (caData) {
this.ca = caData;
}
if (p12Data) {
this.pfx = Buffer.from(p12Data, 'base64');
}
}
}
}
@ -80,6 +98,7 @@ module.exports = function(RED) {
certdata: {type:"text"},
keydata: {type:"text"},
cadata: {type:"text"},
p12data: {type:"text"},
passphrase: {type:"password"}
},
settings: {
@ -92,14 +111,21 @@ module.exports = function(RED) {
TLSConfig.prototype.addTLSOptions = function(opts) {
if (this.valid) {
if (this.key) {
opts.key = this.key;
if (this.certType === "files") {
if (this.key) {
opts.key = this.key;
}
if (this.cert) {
opts.cert = this.cert;
}
if (this.ca) {
opts.ca = this.ca;
}
}
if (this.cert) {
opts.cert = this.cert;
}
if (this.ca) {
opts.ca = this.ca;
else {
if (this.pfx) {
opts.pfx = this.pfx;
}
}
if (this.credentials && this.credentials.passphrase) {
opts.passphrase = this.credentials.passphrase;

View File

@ -204,19 +204,25 @@
"ca": "CA Certificate",
"verify-server-cert": "Verify server certificate",
"servername": "Server Name",
"alpnprotocol": "ALPN Protocol"
"alpnprotocol": "ALPN Protocol",
"certtype": "Cert Type",
"files": "Individual files",
"p12": "pfx or p12",
"pfx": "pfx or p12 file"
},
"placeholder": {
"cert": "path to certificate (PEM format)",
"key": "path to private key (PEM format)",
"p12": "path to .pfx or .p12 (PKCS12 format)",
"ca": "path to CA certificate (PEM format)",
"passphrase": "private key passphrase (optional)",
"passphrase": "password or passphrase (optional)",
"servername": "for use with SNI",
"alpnprotocol": "for use with ALPN"
},
"error": {
"missing-file": "No certificate/key file provided",
"invalid-cert": "Certificate not specified",
"invalid-p12": "pfx/p12 not specified",
"invalid-key": "Private key not specified"
}
},