Validating certificates via Node-Red
Update: We have created a simple, custom S1SEVEN node that can significantly simplify this workflow, see how to use and install it here. This article, while more complex to set up, shows more implementation details and can be extended or customized later. For most use cases, we recommend installing and using our custom nodes.
Introduction
Normally interacting with an API requires some development work, but using Node-Red can greatly simplify this process.
Some things that can be done via the API include notarizing a certificate, validating a certificate (checking if it has already been notarized), getting the hash of a certificate, etc. The simplest is certificate validation, as it doesn’t require authentication.
This article will explain each step of the flows required to verify a certificate. Instructions on how to import these pre-made flows will be made available near the end of the article under the heading Importing pre-made flows.
Getting started
A simple flow for verifying a certificate
As you can see in the image above, only four steps are needed. Inject the certificate, send an HTTP request, parse the response as JSON and print the result.
An HTTP request is a request that is sent to a server, in this case, S1SEVEN’s servers. Each request receives a response, in this case, the response is set to msg.payload.
Uploading a certificate
To simplify the process of injecting a certificate, we have added another flow that allows the user to upload a JSON certificate via the UI, as you can see in the following screenshot:
Upload JSON certificate via the UI
Flow that allows upload
As you can see in the image above, this flow uses an upload node which is responsible for adding the upload section to the UI. When the user uploads a file via the UI, msg.payload is set to the contents of the file in binary format. This is then passed to the json node, which parses the contents as an object we can interact with.
msg.payload is the property that is read by default by many nodes.
The Allow access to certificate globally is a function node containing a small amount of code, global.set(‘certificate’, msg.payload);that sets the contents of msg.payload as a property on a globally accessible object, meaning that it can be accessed from any other flow.
Just as msg is an object that flows through a particular flow, global is an object that can be accessed from any flow.
Then the green debug node prints out the contents of msg.payload, which in this case would be a JSON certificate in object form.
Verifying the certificate
Now that we have uploaded a certificate and made it accessible to other flows by storing it as a property on the global object, we can switch back to our verification flow:
Verifying a certificate
In this flow, the blue Verify Certificate node is used to start the process. If you open it by double-clicking the node, you will see that it sets msg.payload to a timestamp. This is the default behavior of an inject node.
Notice that we have added a Get certificate node to this flow. This is to simplify the flow and allows us to retrieve the uploaded certificate. Our Get certificate node contains a little more code than its corresponding function node in the upload flow, but it’s pretty straightforward to understand.
const certificate = global.get('certificate');
if (certificate) {
msg.payload = certificate;
return msg;
} else {
node.warn('No certificate found, please upload a JSON
certificate via the UI');
}
On the first line, we retrieve the certificate that has been saved at global.certificate and store it in a variable called certificate.
A variable is a bit like a box in that any value can be stored in it, or it can be empty.
Then, on line two, we check if certificate contains a value. If it does, we store that value in msg.payload. The line return msg; sends the msg object to the next node.
If certificate does not contain a value, we use node.warn to print a message to the debug sidebar, asking the user to upload a JSON certificate.
The next node in our flow is the http request node. This node is used to send a request to S1SEVEN’s servers. You can double-click on the node to see the options:
HTTP request node
We are primarily interested in the first two settings, Method and URL. The Method we want to set to POST, as this is a type of request that allows us to send a file along with the request. In this case, we want to send the certificate.
When Method is set to POST, the contents of msg.payload are sent along with the request.
URL is pretty self-explanatory; it contains the URL we want to send the request to. In this case, the URL is set to https://app.s1seven.dev/api/certificates/verify?mode=test.
As we mentioned above, every HTTP request receives a response. msg.payload is set to the response value, which is then passed into the json node to be parsed into an object we can interact with.
That object is then passed to the green debug node, where its contents are printed in the debug panel to the right. Here is a sample of what that object can contain:
Sample response object
When validating a certificate, we are primarily interested in the first property, isValid. isValid will either be true if the certificate is valid and has been notarized or false if the certificate has not been notarized.
The hash object contains extra data about the certificate hash, including its hash value, the algorithm used, and the encoding.
So simply put, all we have to do is check whether isValid is true or false, and we can verify whether a certificate has been notarized or not!
Importing pre-made flows
To simplify the process, instead of replicating our flows, our pre-made flows can be easily imported into Node-Red and modified if necessary.
Click on the hamburger menu (the three horizontal lines) at the top right of the Node-Red toolbar (beside the Deploy button) and click import:
The import option in the menu
Then choose Clipboard, and copy-paste the code from further down in this page into the box, then click the redImport button:
Importing a flow
The pre-made flows will automatically be imported, and you can begin using them immediately!
Here is the flow for allowing JSON certificates to be uploaded via the UI:
[
{
"id": "3064fedc7d32c913",
"type": "tab",
"label": "JSON certificate input via UI",
"disabled": false,
"info": "",
"env": []
},
{
"id": "b32669d638cb2d0e",
"type": "debug",
"z": "3064fedc7d32c913",
"name": "",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "payload",
"targetType": "msg",
"statusVal": "",
"statusType": "auto",
"x": 790,
"y": 100,
"wires": []
},
{
"id": "4498c50d358dd7ee",
"type": "function",
"z": "3064fedc7d32c913",
"name": "Allow access to certificate globally",
"func": "global.set('certificate', msg.payload);\nreturn msg;",
"outputs": 1,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 540,
"y": 100,
"wires": [
[
"b32669d638cb2d0e"
]
]
},
{
"id": "02e44634367bb213",
"type": "json",
"z": "3064fedc7d32c913",
"name": "",
"property": "payload",
"action": "",
"pretty": false,
"x": 310,
"y": 100,
"wires": [
[
"4498c50d358dd7ee"
]
]
},
{
"id": "5873882101bad2f8",
"type": "ui_upload",
"z": "3064fedc7d32c913",
"group": "06fcee6ae387d692",
"title": "upload",
"name": "",
"order": 3,
"width": 0,
"height": 5,
"chunk": 256,
"transfer": "binary",
"x": 170,
"y": 100,
"wires": [
[
"02e44634367bb213"
]
]
},
{
"id": "06fcee6ae387d692",
"type": "ui_group",
"name": "API input",
"tab": "998ca7fbf9a4874e",
"order": 1,
"disp": true,
"width": "10",
"collapse": false,
"className": ""
},
{
"id": "998ca7fbf9a4874e",
"type": "ui_tab",
"name": "API input",
"icon": "dashboard",
"disabled": false,
"hidden": false
}
]
Here is the flow for validating a certificate via an HTTP request:
[
{
"id": "26f6e289b3fab452",
"type": "tab",
"label": "Validate Certificate",
"disabled": false,
"info": "",
"env": []
},
{
"id": "8e060cd8e0287093",
"type": "inject",
"z": "26f6e289b3fab452",
"name": "Verify Certificate",
"props": [
{
"p": "payload"
}
],
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": 0.1,
"topic": "",
"payload": "",
"payloadType": "date",
"x": 220,
"y": 160,
"wires": [
[
"eb3589bc5a1eb08c"
]
]
},
{
"id": "05e799dddf96b541",
"type": "http request",
"z": "26f6e289b3fab452",
"name": "",
"method": "POST",
"ret": "txt",
"paytoqs": "ignore",
"url": "https://app.s1seven.ovh/api/certificates/verify?mode=test",
"tls": "",
"persist": false,
"proxy": "",
"authType": "",
"senderr": false,
"x": 610,
"y": 160,
"wires": [
[
"ecc3ac7d13f75bf0"
]
]
},
{
"id": "314196406ec27dd6",
"type": "debug",
"z": "26f6e289b3fab452",
"name": "",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "true",
"targetType": "full",
"statusVal": "",
"statusType": "auto",
"x": 910,
"y": 160,
"wires": []
},
{
"id": "ecc3ac7d13f75bf0",
"type": "json",
"z": "26f6e289b3fab452",
"name": "",
"property": "payload",
"action": "",
"pretty": false,
"x": 770,
"y": 160,
"wires": [
[
"314196406ec27dd6"
]
]
},
{
"id": "eb3589bc5a1eb08c",
"type": "function",
"z": "26f6e289b3fab452",
"name": "Get certificate",
"func": "const certificate = global.get('certificate');\nif (certificate) {\n msg.payload = certificate;\n return msg;\n} else {\n node.warn('No certificate found, please upload a JSON certificate via the UI');\n}\n",
"outputs": 1,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 420,
"y": 160,
"wires": [
[
"05e799dddf96b541"
]
]
}
]