diff --git a/.github/dependabot.yml b/.github/dependabot.yml
index 4e1a74f0..932890c0 100644
--- a/.github/dependabot.yml
+++ b/.github/dependabot.yml
@@ -39,8 +39,6 @@ updates:
versions: [ '>=2.0.0' ]
- dependency-name: 'jimp'
versions: [ '1.6.1' ]
- - dependency-name: 'webpack-dev-server'
- versions: [ '>=5.1.0' ]
groups:
#
# Grouping so we don't get a seperate PR for every patch version.
diff --git a/.github/workflows/pull_requests.yml b/.github/workflows/pull_requests.yml
index f72f187b..01f23d17 100644
--- a/.github/workflows/pull_requests.yml
+++ b/.github/workflows/pull_requests.yml
@@ -61,7 +61,7 @@ jobs:
- name: Set up Docker Buildx
if: success()
- uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0
+ uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0
- name: Set up QEMU
uses: docker/setup-qemu-action@ce360397dd3f832beb865e1373c09c0e9f86d70a # v4.0.0
diff --git a/.github/workflows/releases.yml b/.github/workflows/releases.yml
index ea66b7c6..e826d4b1 100644
--- a/.github/workflows/releases.yml
+++ b/.github/workflows/releases.yml
@@ -61,14 +61,14 @@ jobs:
xvfb-run --server-args="-screen 0 1200x800x24" npx grunt testui
- name: Set up Docker Buildx
- uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0
+ uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0
- name: Set up QEMU
uses: docker/setup-qemu-action@ce360397dd3f832beb865e1373c09c0e9f86d70a # v4.0.0
- name: Image Metadata
id: image-metadata
- uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6.0.0
+ uses: docker/metadata-action@80c7e94dd9b9319bd5eb7a0e0fe9291e23a2a2e9 # v6.1.0
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
@@ -77,7 +77,7 @@ jobs:
type=semver,pattern={{version}}
- name: Log in to GHCR
- uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
+ uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0
with:
registry: ${{ env.REGISTRY }}
username: ${{ env.REGISTRY_USER }}
diff --git a/package-lock.json b/package-lock.json
index f2f6b10c..f47a79a6 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -39,7 +39,7 @@
"d3": "7.9.0",
"d3-hexbin": "^0.2.2",
"diff": "^5.2.2",
- "dompurify": "^3.4.3",
+ "dompurify": "^3.4.5",
"es6-promisify": "^7.0.0",
"escodegen": "^2.1.0",
"esprima": "^4.0.1",
@@ -85,7 +85,7 @@
"path": "^0.12.7",
"popper.js": "^1.16.1",
"process": "^0.11.10",
- "protobufjs": "^7.6.0",
+ "protobufjs": "^7.6.1",
"punycode.js": "^2.3.1",
"qr-image": "^3.2.0",
"reflect-metadata": "^0.2.2",
@@ -123,7 +123,7 @@
"autoprefixer": "^10.5.0",
"babel-loader": "^10.1.1",
"base64-loader": "^1.0.0",
- "chromedriver": "^148.0.3",
+ "chromedriver": "^148.0.4",
"cli-progress": "^3.12.0",
"colors": "^1.4.0",
"compression-webpack-plugin": "^12.0.0",
@@ -150,7 +150,7 @@
"mini-css-extract-plugin": "2.10.2",
"modify-source-webpack-plugin": "^4.1.0",
"nightwatch": "^3.15.0",
- "postcss": "^8.5.14",
+ "postcss": "^8.5.15",
"postcss-css-variables": "^0.19.0",
"postcss-import": "^16.1.1",
"postcss-loader": "^8.2.1",
@@ -159,7 +159,7 @@
"terser": "^5.46.2",
"webpack": "^5.107.1",
"webpack-bundle-analyzer": "^5.3.0",
- "webpack-dev-server": "^5.0.4",
+ "webpack-dev-server": "^5.2.4",
"webpack-node-externals": "^3.0.0",
"worker-loader": "^3.0.8"
},
@@ -4435,9 +4435,9 @@
"license": "BSD-3-Clause"
},
"node_modules/@protobufjs/eventemitter": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz",
- "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==",
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.1.tgz",
+ "integrity": "sha512-vW1GmwMZNnL+gMRaovlh9yZX74kc+TTU3FObkkurpMaRtBfLP3ldjS9KQWlwZgraRE0+dheEEoAxdzcJQ8eXZg==",
"license": "BSD-3-Clause"
},
"node_modules/@protobufjs/fetch": {
@@ -6835,9 +6835,9 @@
}
},
"node_modules/chromedriver": {
- "version": "148.0.3",
- "resolved": "https://registry.npmjs.org/chromedriver/-/chromedriver-148.0.3.tgz",
- "integrity": "sha512-efk4NIkjjRr3Zy0C8Trj8jpMGgDY+ZA6kVpYFerH3M4tM4fguKCi81PguWwmHVx8FZdMmhFYczb1PaPvJqUkZQ==",
+ "version": "148.0.4",
+ "resolved": "https://registry.npmjs.org/chromedriver/-/chromedriver-148.0.4.tgz",
+ "integrity": "sha512-3UyptFDG4YF1Pyv3fzn95s1CN4K3zCpHSmE6g+6J4f2u9KxxOYzrwN2GApVyM2z02hlbSqzo9Ajn2hMi7LnvCw==",
"dev": true,
"hasInstallScript": true,
"license": "Apache-2.0",
@@ -8774,9 +8774,9 @@
}
},
"node_modules/dompurify": {
- "version": "3.4.3",
- "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.3.tgz",
- "integrity": "sha512-VVwJidIJcp1hpg2OMXML3ZVRPYSZiq4aX7qBh83BSIpOaRDqI+qxhXjjIWnpzkOXhmp0L81lnoME1mnCc9H48A==",
+ "version": "3.4.5",
+ "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.5.tgz",
+ "integrity": "sha512-OrwIBKsdNSVEeubdJ1HBv/wNENRM9ytAVCv7YXt//A3vPdVMNuACRqK9mXCGCBW2ln7BT/A4X0jXHo2Gu89miA==",
"license": "(MPL-2.0 OR Apache-2.0)",
"optionalDependencies": {
"@types/trusted-types": "^2.0.7"
@@ -13645,9 +13645,9 @@
"license": "ISC"
},
"node_modules/nanoid": {
- "version": "3.3.11",
- "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
- "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
+ "version": "3.3.12",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz",
+ "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==",
"dev": true,
"funding": [
{
@@ -15009,9 +15009,9 @@
}
},
"node_modules/postcss": {
- "version": "8.5.14",
- "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.14.tgz",
- "integrity": "sha512-SoSL4+OSEtR99LHFZQiJLkT59C5B1amGO1NzTwj7TT1qCUgUO6hxOvzkOYxD+vMrXBM3XJIKzokoERdqQq/Zmg==",
+ "version": "8.5.15",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz",
+ "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==",
"dev": true,
"funding": [
{
@@ -15029,7 +15029,7 @@
],
"license": "MIT",
"dependencies": {
- "nanoid": "^3.3.11",
+ "nanoid": "^3.3.12",
"picocolors": "^1.1.1",
"source-map-js": "^1.2.1"
},
@@ -15278,16 +15278,16 @@
"license": "MIT"
},
"node_modules/protobufjs": {
- "version": "7.6.0",
- "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.6.0.tgz",
- "integrity": "sha512-LtESOsMPTZgyYtwxhvdgdjGL0HmXEaRA/hVD6sol4zA60hVXXXP/SGmxnqDbgGE8gy7pYex7cym+5vYPcmaXBQ==",
+ "version": "7.6.1",
+ "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.6.1.tgz",
+ "integrity": "sha512-4K0myLaWL5EteuSAro91EGFgcfVgxb64Jx+7oDAY6GOkXD4M69yuSEljNcInGVCA5sOPxmZ/EqDLj2x0Q0+Ygg==",
"hasInstallScript": true,
"license": "BSD-3-Clause",
"dependencies": {
"@protobufjs/aspromise": "^1.1.2",
"@protobufjs/base64": "^1.1.2",
"@protobufjs/codegen": "^2.0.5",
- "@protobufjs/eventemitter": "^1.1.0",
+ "@protobufjs/eventemitter": "^1.1.1",
"@protobufjs/fetch": "^1.1.1",
"@protobufjs/float": "^1.0.2",
"@protobufjs/inquire": "^1.1.2",
@@ -18348,9 +18348,9 @@
}
},
"node_modules/webpack-dev-server": {
- "version": "5.2.3",
- "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-5.2.3.tgz",
- "integrity": "sha512-9Gyu2F7+bg4Vv+pjbovuYDhHX+mqdqITykfzdM9UyKqKHlsE5aAjRhR+oOEfXW5vBeu8tarzlJFIZva4ZjAdrQ==",
+ "version": "5.2.4",
+ "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-5.2.4.tgz",
+ "integrity": "sha512-GqDPGZN9bRqKBTkp4aWkobDDHMsrXKoGSdOH56smIri8qR0JG8gfL8/v/f/OZR3/OKXjG8uwJbFVhKm/FNU/UA==",
"dev": true,
"license": "MIT",
"dependencies": {
diff --git a/package.json b/package.json
index 93026310..ac15dd32 100644
--- a/package.json
+++ b/package.json
@@ -53,7 +53,7 @@
"autoprefixer": "^10.5.0",
"babel-loader": "^10.1.1",
"base64-loader": "^1.0.0",
- "chromedriver": "^148.0.3",
+ "chromedriver": "^148.0.4",
"cli-progress": "^3.12.0",
"colors": "^1.4.0",
"compression-webpack-plugin": "^12.0.0",
@@ -80,7 +80,7 @@
"mini-css-extract-plugin": "2.10.2",
"modify-source-webpack-plugin": "^4.1.0",
"nightwatch": "^3.15.0",
- "postcss": "^8.5.14",
+ "postcss": "^8.5.15",
"postcss-css-variables": "^0.19.0",
"postcss-import": "^16.1.1",
"postcss-loader": "^8.2.1",
@@ -89,7 +89,7 @@
"terser": "^5.46.2",
"webpack": "^5.107.1",
"webpack-bundle-analyzer": "^5.3.0",
- "webpack-dev-server": "^5.0.4",
+ "webpack-dev-server": "^5.2.4",
"webpack-node-externals": "^3.0.0",
"worker-loader": "^3.0.8"
},
@@ -123,7 +123,7 @@
"d3": "7.9.0",
"d3-hexbin": "^0.2.2",
"diff": "^5.2.2",
- "dompurify": "^3.4.3",
+ "dompurify": "^3.4.5",
"es6-promisify": "^7.0.0",
"escodegen": "^2.1.0",
"esprima": "^4.0.1",
@@ -169,7 +169,7 @@
"path": "^0.12.7",
"popper.js": "^1.16.1",
"process": "^0.11.10",
- "protobufjs": "^7.6.0",
+ "protobufjs": "^7.6.1",
"punycode.js": "^2.3.1",
"qr-image": "^3.2.0",
"reflect-metadata": "^0.2.2",
diff --git a/src/core/config/Categories.json b/src/core/config/Categories.json
index 5fcba297..3404dc6d 100644
--- a/src/core/config/Categories.json
+++ b/src/core/config/Categories.json
@@ -188,6 +188,7 @@
"Generate PGP Key Pair",
"PGP Encrypt",
"PGP Decrypt",
+ "PGP Sign",
"PGP Verify",
"PGP Encrypt and Sign",
"PGP Decrypt and Verify",
diff --git a/src/core/operations/GeneratePGPKeyPair.mjs b/src/core/operations/GeneratePGPKeyPair.mjs
index a26b5cc8..e3fd6742 100644
--- a/src/core/operations/GeneratePGPKeyPair.mjs
+++ b/src/core/operations/GeneratePGPKeyPair.mjs
@@ -12,6 +12,7 @@ import { getSubkeySize, ASP } from "../lib/PGP.mjs";
import { cryptNotice } from "../lib/Crypt.mjs";
import * as es6promisify from "es6-promisify";
const promisify = es6promisify.default ? es6promisify.default.promisify : es6promisify.promisify;
+const KEY_FLAGS = kbpgp.const.openpgp.key_flags;
/**
@@ -73,11 +74,11 @@ class GeneratePGPKeyPair extends Operation {
if (name) userIdentifier += name;
if (email) userIdentifier += ` <${email}>`;
- let flags = kbpgp.const.openpgp.certify_keys;
- flags |= kbpgp.const.openpgp.sign_data;
- flags |= kbpgp.const.openpgp.auth;
- flags |= kbpgp.const.openpgp.encrypt_comm;
- flags |= kbpgp.const.openpgp.encrypt_storage;
+ let flags = KEY_FLAGS.certify_keys;
+ flags |= KEY_FLAGS.sign_data;
+ flags |= KEY_FLAGS.auth;
+ flags |= KEY_FLAGS.encrypt_comm;
+ flags |= KEY_FLAGS.encrypt_storage;
const keyGenerationOptions = {
userid: userIdentifier,
@@ -89,11 +90,11 @@ class GeneratePGPKeyPair extends Operation {
},
subkeys: [{
"nbits": getSubkeySize(keySize),
- "flags": kbpgp.const.openpgp.sign_data,
+ "flags": KEY_FLAGS.sign_data,
"expire_in": 86400 * 365 * 8
}, {
"nbits": getSubkeySize(keySize),
- "flags": kbpgp.const.openpgp.encrypt_comm | kbpgp.const.openpgp.encrypt_storage,
+ "flags": KEY_FLAGS.encrypt_comm | KEY_FLAGS.encrypt_storage,
"expire_in": 86400 * 365 * 2
}],
asp: ASP
diff --git a/src/core/operations/PGPSign.mjs b/src/core/operations/PGPSign.mjs
new file mode 100644
index 00000000..e3063d89
--- /dev/null
+++ b/src/core/operations/PGPSign.mjs
@@ -0,0 +1,83 @@
+/**
+ * @author GCHQDeveloper581
+ * @copyright Crown Copyright 2026
+ * @license Apache-2.0
+ */
+
+import Operation from "../Operation.mjs";
+import kbpgp from "kbpgp";
+import { ASP, importPrivateKey } from "../lib/PGP.mjs";
+import OperationError from "../errors/OperationError.mjs";
+import * as es6promisify from "es6-promisify";
+const promisify = es6promisify.default ? es6promisify.default.promisify : es6promisify.promisify;
+
+/**
+ * PGP Sign operation
+ */
+class PGPSign extends Operation {
+
+ /**
+ * PGPSign constructor
+ */
+ constructor() {
+ super();
+
+ this.name = "PGP Sign";
+ this.module = "PGP";
+ this.description = [
+ "Input: the message you want to sign",
+ "
",
+ "Arguments: the ASCII-armoured PGP private key of the sender.",
+ "
",
+ "Pretty Good Privacy is an encryption standard (OpenPGP) used for encrypting, decrypting, and signing messages.",
+ "
",
+ "This function uses the Keybase implementation of PGP.",
+ ].join("\n");
+ this.infoURL = "https://wikipedia.org/wiki/Pretty_Good_Privacy"; // Usually a Wikipedia link. Remember to remove localisation (i.e. https://wikipedia.org/etc rather than https://en.wikipedia.org/etc)
+ this.inputType = "string";
+ this.outputType = "string";
+ this.args = [
+ {
+ "name": "Private key of signer",
+ "type": "text",
+ "value": ""
+ },
+ {
+ "name": "Private key passphrase (optional)",
+ "type": "string",
+ "value": ""
+ }
+ ];
+ }
+
+ /**
+ * @param {string} input
+ * @param {Object[]} args
+ * @returns {string}
+ *
+ * @throws {OperationError} if failed private key import or failed encryption
+ */
+ async run(input, args) {
+ const message = input,
+ [privateKey, passphrase] = args;
+ let signedMessage;
+
+ if (!privateKey) throw new OperationError("Enter the private key of the signer.");
+ const privKey = await importPrivateKey(privateKey, passphrase);
+
+ try {
+ signedMessage = await promisify(kbpgp.box)({
+ "msg": message,
+ "sign_with": privKey,
+ "asp": ASP
+ });
+ } catch (err) {
+ throw new OperationError(`Couldn't sign message: ${err}`);
+ }
+
+ return signedMessage;
+ }
+
+}
+
+export default PGPSign;
diff --git a/tests/node/index.mjs b/tests/node/index.mjs
index f872f8f4..52670d48 100644
--- a/tests/node/index.mjs
+++ b/tests/node/index.mjs
@@ -18,6 +18,7 @@ import {
import TestRegister from "../lib/TestRegister.mjs";
import "./tests/nodeApi.mjs";
import "./tests/operations.mjs";
+import "./tests/PGP.mjs";
import "./tests/File.mjs";
import "./tests/Dish.mjs";
import "./tests/NodeDish.mjs";
diff --git a/tests/node/tests/PGP.mjs b/tests/node/tests/PGP.mjs
new file mode 100644
index 00000000..2a695ea3
--- /dev/null
+++ b/tests/node/tests/PGP.mjs
@@ -0,0 +1,69 @@
+/**
+ * PGP node tests.
+ *
+ * @author C85297 [95289555+C85297@users.noreply.github.com]
+ * @copyright Crown Copyright 2026
+ * @license Apache-2.0
+ */
+
+import assert from "assert";
+import kbpgp from "kbpgp";
+import * as es6promisify from "es6-promisify";
+
+import TestRegister from "../../lib/TestRegister.mjs";
+import it from "../assertionHandler.mjs";
+import GeneratePGPKeyPair from "../../../src/core/operations/GeneratePGPKeyPair.mjs";
+
+const promisify = es6promisify.default ? es6promisify.default.promisify : es6promisify.promisify;
+
+const PUBLIC_KEY_BLOCK = /-----BEGIN PGP PUBLIC KEY BLOCK-----[\s\S]*-----END PGP PUBLIC KEY BLOCK-----/;
+
+/**
+ * Generate a PGP key pair and import the generated public key.
+ *
+ * @param {string} keyType
+ * @returns {Promise