You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
It would also be helpful to guide on what DNS records need to be set and other such setups in order to get the keyserver to be auto-detected, and which clients/gpg versions support which setup.
It might be possible to do this url rewriting in web server as well. decode the request to email address and then rewrite the url to search by email address with an option to return raw data (probably in binary since wkd expects binary data - at least the example url returned binary data).
I manged to get the hash generated, but unable to pass the value back from util.js to public-key.js. Need to figure out doing this with proper async/await.
diff --git a/src/lib/util.js b/src/lib/util.js
index b871fba..271f0aa 100644
--- a/src/lib/util.js
+++ b/src/lib/util.js
@@ -87,6 +87,53 @@ exports.normalizeEmail = function(email) {
return email;
};
+exports.genWKDHash = function(email, wkdhash = '') {
+ const [localPart, domain] = email.split('@');
+ crypto.subtle.digest({
+ name: "SHA-1",
+ },
+ new TextEncoder().encode(localPart.toLowerCase())
+ )
+ .then(function(hash){
+ const localPartBase32 = encodeZBase32(new Uint8Array(hash));
+ wkdhash = localPartBase32 + '@' + domain;
+ })
+ .catch(function(err){
+ console.error(err);
+ });
+ console.log(wkdhash);
+ return wkdhash;
+}
+
+function encodeZBase32(data) {
+ if (data.length === 0) {
+ return "";
+ }
+ const ALPHABET = "ybndrfg8ejkmcpqxot1uwisza345h769";
+ const SHIFT = 5;
+ const MASK = 31;
+ let buffer = data[0];
+ let index = 1;
+ let bitsLeft = 8;
+ let result = '';
+ while (bitsLeft > 0 || index < data.length) {
+ if (bitsLeft < SHIFT) {
+ if (index < data.length) {
+ buffer <<= 8;
+ buffer |= data[index++] & 0xff;
+ bitsLeft += 8;
+ } else {
+ const pad = SHIFT - bitsLeft;
+ buffer <<= pad;
+ bitsLeft += pad;
+ }
+ }
+ bitsLeft -= SHIFT;
+ result += ALPHABET[MASK & (buffer >> bitsLeft)];
+ }
+ return result;
+}
+
/**
* Generate a cryptographically secure random hex string. If no length is
* provided a 32 char hex string will be generated by default.
diff --git a/src/modules/public-key.js b/src/modules/public-key.js
index bb0b2b1..0fe68f6 100644
--- a/src/modules/public-key.js
+++ b/src/modules/public-key.js
@@ -160,6 +160,10 @@ class PublicKey {
if (userId.notify && userId.notify === true) {
// generate nonce for verification
userId.nonce = util.random();
+ // generate nonce for verification
+ console.log(util.genWKDHash(userId.email));
+ userId.wkdhash = util.genWKDHash(userId.email,userId.wkdhash);
+console.log("wkdhash" + userId.wkdhash);
await this._email.send({template: tpl.verifyKey, userId, keyId, origin, publicKeyArmored: userId.publicKeyArmored, i18n});
}
}
In the above, crypto.subtle.digest( function starts a new branch of execution (asynchronous) while the main branch continues from that line and jumps to return wkdhash;. At this point in time, wkdhash has not changed, and will be an empty string.
What needs to be done is to return the promise of crypto.subtle.digest itself, like this:
exports.genWKDHash = function(email, wkdhash = '') {
const [localPart, domain] = email.split('@');
return crypto.subtle.digest({
name: "SHA-1",
},
new TextEncoder().encode(localPart.toLowerCase())
)
.then(function(hash){
const localPartBase32 = encodeZBase32(new Uint8Array(hash));
wkdhash = localPartBase32 + '@' + domain;
return wkdhash; // this is what the promise resolves to if there's no error
})
.catch(function(err){
console.error(err);
});
}
And then when you call it, you've to wait for the promise to resolve, like this:
This hashing operation seems to be destructive in that we cannot get the email address back from the hash. Therefore your latter comment about having to store the hash in the database seems right. Meaning for existing keys, there'll have to be a separate function to calculate the hashes and save to DB. And for new keys being uploaded, the hash can be calculated along with fingerprint, etc.
You could add a wkd endpoint like the hkp endpoint:
'use strict';
const Boom = require('@hapi/boom');
const util = require('../lib/util');
const openpgp = require('openpgp');
/**
* An implementation of Web Key Directory
* See https://wiki.gnupg.org/WKD
*/
class WKD {
/**
* Create an instance of the WKD server
* @param {Object} publicKey - an instance of the public key service
*/
constructor(publicKey) {
this._publicKey = publicKey;
}
/**
* Public key lookup via http GET
* @param {Object} request - hapi request object
* @param {Object} h - hapi response toolkit
*/
async lookup(request, h) {
const hash = request.params.hash;
// implement lookup
return h.response(body).type('text/plain');
}
}
}
exports.plugin = {
name: 'WKD',
async register(server, options) {
const wkd = new WKD(server.app.publicKey);
const routeOptions = {
bind: wkd,
cors: options.server.cors === 'true',
security: options.server.security === 'true',
ext: {
onPreResponse: {
method({response}, h) {
if (!response.isBoom) {
return h.continue;
}
return h.response(response.message).code(response.output.statusCode).type('text/plain');
}
}
}
};
server.route({
method: 'GET',
path: '/.well-known/openpgpkey/hu/{hash}',
handler: wkd.lookup,
options: routeOptions
});
}
};
But of course, implementing the lookup requires the hash to be saved.
@asdofindia I tried adding src/route/wkd.js and visiting the .well-known/openpgpkey/hu/g4rf118d16sbj3k77d5txywpm879pszg which resulted in a 404. I think we should try to get a key not found/another debug message printed to get started before implementing the lookup part.
/pks/lookup?op=get&search=g4rf118d16sbj3k77d5txywpm879pszg%40puri.sm&options=wkd is what is working correctly now, what is expected is /.well-known/openpgpkey/hu/g4rf118d16sbj3k77d5txywpm879pszg
So we have two options to do this mapping,
Add a new route (previous try did not work)
Rewrite the url at web server
Domain name will need to dynamically selected based on the request url since the local part of hash is not unique.
Though gpg --locate-keys praveen@puri.sm seems to be just waiting, no error nor timeout for a long time.
$ gpg --verbose --locate-keys praveen@puri.sm
gpg: enabled compatibility flags:
gpg: using pgp trust model
gpg: error retrieving 'praveen@puri.sm' via Local: No public key
$ gpg --verbose --locate-keys praveen@puri.sm
gpg: enabled compatibility flags:
gpg: using pgp trust model
gpg: error retrieving 'praveen@puri.sm' via Local: No public key
gpg: error retrieving 'praveen@puri.sm' via WKD: No data
gpg: error reading key: No data
Earlier it was waiting for terminal input before overwriting the .gpg file, I changed it to check for the file before running gpg --dearmor but still no luck with gpg client.
I'm trying to see if we can do this entirely in caddy/web server without generating and storing the wkd hash since we now know the localPart is passed as a query parameter which can be used to search by email address.
@pravi thank you very much for your guide. I'm trying to make it work on Nginx, but the Mailvelope Browser Plugin still can't find my PGP Key. But the gpg-wks-client does:
gpg-wks-client: public key for'test@example.com' found via WKD
gpg-wks-client: fingerprint: XXX
gpg-wks-client: user-id: XXX <test@example.com>
gpg-wks-client: created: Sat 08 Feb 2025 03:47:35 PM UTC
gpg-wks-client: addr-spec: test@example.com
this is the nginx config for this:
# Serve the OpenPGP policy file (if needed)
location = /.well-known/openpgpkey/example.com/policy {
default_type text/plain;return 200 "protocol-version 5";
}
# Rewrite and proxy requests for the key lookup endpoint
location ^~ /.well-known/openpgpkey/example.com/hu/ {
# Optionally, check if the 'l' query parameter is providedif ($arg_l = "") {
return 400 "Missing 'l' query parameter";
}
# Rewrite the URL so that the key lookup is forwarded as:# /pks/lookup?op=get&search=<l>@example.com&options=mr
rewrite ^/\.well-known/openpgpkey/example\.com/hu/.*$ /pks/lookup?op=get&search=$arg_l%40example.com&options=mr break;# Reverse proxy to your backend keyserver
proxy_pass http://10.16.66.10:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;# (Add any other necessary proxy headers or settings)
}
the Mailvelope Browser Plugin states this in the browser console
WKD: Did not find key (Errors are expected): Error: WKD: Failed to parse result for 'test@example.com': Error during parsing. This message / key probably does not conform to a valid OpenPGP format.
Activity
strausmann commentedon Feb 20, 2023
i just stumbled across this question, too.
pravi commentedon May 28, 2024
There is a python script to generate .well-known/openpgpkey/hu/ from a keyring - https://hg.intevation.de/gnupg/wkd-tools/file/tip/generate-openpgpkey-hu, but we should probably use the existing database and map the wkd requests to hkps search requests.
pravi commentedon May 28, 2024
The format that WKD expects,
https://intevation.de/.well-known/openpgpkey/hu/it5sewh54rxz33fwmr8u6dy4bbz8itz4 is the direct method URL for "bernhard.reiter@intevation.de".
and mailvelope keyserver's format is,
https://keys.puri.sm/pks/lookup?op=get&search=praveen%40debian.org (though this gives a html page and not raw key, so we will need to add another option to get raw keys).
It might be possible to do this url rewriting in web server as well. decode the request to email address and then rewrite the url to search by email address with an option to return raw data (probably in binary since wkd expects binary data - at least the example url returned binary data).
pravi commentedon May 28, 2024
if we can get the hash generation logic figured out, then it can be simple I think.
Update: we can already pass options=mr https://keys.puri.sm/pks/lookup?op=get&search=praveen@puri.sm&options=mr
pravi commentedon May 29, 2024
https://gist.github.com/kafene/0a6e259996862d35845784e6e5dbfc79 this is a useful resource. It shows how to generate the wkd-hash with gpg command itself. We need to find a way to do the same in Javascript.
pravi commentedon May 29, 2024
https://github.com/openpgpjs/wkd-client/blob/master/src/wkd.js#L50 this has js code for generating the hash.
pravi commentedon May 29, 2024
Based on wkd-client code, I was able to generate this hash using this code snippet.
pravi commentedon May 30, 2024
I manged to get the hash generated, but unable to pass the value back from util.js to public-key.js. Need to figure out doing this with proper async/await.
asdofindia commentedon May 30, 2024
@pravi
In the above,
crypto.subtle.digest(function starts a new branch of execution (asynchronous) while the main branch continues from that line and jumps toreturn wkdhash;. At this point in time, wkdhash has not changed, and will be an empty string.What needs to be done is to return the promise of crypto.subtle.digest itself, like this:
And then when you call it, you've to wait for the promise to resolve, like this:
This hashing operation seems to be destructive in that we cannot get the email address back from the hash. Therefore your latter comment about having to store the hash in the database seems right. Meaning for existing keys, there'll have to be a separate function to calculate the hashes and save to DB. And for new keys being uploaded, the hash can be calculated along with fingerprint, etc.
You could add a wkd endpoint like the hkp endpoint:
But of course, implementing the lookup requires the hash to be saved.
pravi commentedon May 31, 2024
@asdofindia thanks, let me try this.
Thanks also to Ananthu CV who also helped making this a promise.
pravi commentedon May 31, 2024
@asdofindia I tried adding src/route/wkd.js and visiting the .well-known/openpgpkey/hu/g4rf118d16sbj3k77d5txywpm879pszg which resulted in a 404. I think we should try to get a key not found/another debug message printed to get started before implementing the lookup part.
pravi commentedon Jun 1, 2024
With the following code,
I'm able to get the raw ascii armored key when looking for /pks/lookup?op=get&search=g4rf118d16sbj3k77d5txywpm879pszg%40puri.sm&options=wkd
Now we just need to either add a wkd route or a url rewriting scheme in web server that will redirect to this url from the wkd direct url.
pravi commentedon Jun 1, 2024
/pks/lookup?op=get&search=g4rf118d16sbj3k77d5txywpm879pszg%40puri.sm&options=wkdis what is working correctly now, what is expected is/.well-known/openpgpkey/hu/g4rf118d16sbj3k77d5txywpm879pszgSo we have two options to do this mapping,
Domain name will need to dynamically selected based on the request url since the local part of hash is not unique.
pravi commentedon Jun 1, 2024
with this in CaddyFile, I can get the key from https://openpgpkey.puri.sm/.well-known/openpgpkey/puri.sm/hu/g4rf118d16sbj3k77d5txywpm879pszg (this is a restricted url for now).
pravi commentedon Jun 1, 2024
Though
gpg --locate-keys praveen@puri.smseems to be just waiting, no error nor timeout for a long time.5 remaining items
pravi commentedon Jun 1, 2024
I could not find a way to dearmor with openpgp.js so I used gpg command to dearmor
But even then gpg fails to get the key,
wiktor-k commentedon Jun 2, 2024
Hmm... I'd advise to use the Web Key Directory checker but I just sunsetted it last month! 😬
GnuPG may cache the result and return error even if you fixed everything. I'd suggest creating the WKD URL manually and visiting it with
curl -i -sSL.I can do more thorough checks but only on Monday.
pravi commentedon Jun 2, 2024
I have removed the firewall to allow access to everyone and this is the curl output.
pravi commentedon Jun 2, 2024
pravi commentedon Jun 2, 2024
Earlier it was waiting for terminal input before overwriting the .gpg file, I changed it to check for the file before running gpg --dearmor but still no luck with gpg client.
pravi commentedon Jun 2, 2024
Using https://sequoia-pgp.org/tools/wkd/
This gave,
Update: this is probably broken since the same error is shown for bernhard.reiter@intevation.de which has a working WKD implementation.
wiktor-k commentedon Jun 2, 2024
Well, Sequoia's WKD checker really uses mine and since I shut down mine it's not working 🤷♂️
asdofindia commentedon Jun 2, 2024
@pravi
❯ gpg-wks-client --print-wkd-url praveen@puri.sm https://openpgpkey.puri.sm/.well-known/openpgpkey/puri.sm/hu/g4rf118d16sbj3k77d5txywpm879pszg?l=praveenIt seems like
?l=praveenis (?) expected in the latest spec: https://datatracker.ietf.org/doc/draft-koch-openpgp-webkey-service/Anyhow, gpg seems to be looking there which in this case returns Not found.
pravi commentedon Jun 2, 2024
@asdofindia yay! That was the really missing secret sauce!
After adding
rewrite * {path}?to strip the query parameter before the previous rewrite rule, gpg now finds the keys correctly!.pravi commentedon Jun 2, 2024
All the WKD documentation/wiki pages and tutorials will need to be updated.
pravi commentedon Jun 2, 2024
#146 is ready for review (I don't have a lot of experience with coding, so I'm sure there can be many improvements).
pravi commentedon Jun 2, 2024
I'm trying to see if we can do this entirely in caddy/web server without generating and storing the wkd hash since we now know the localPart is passed as a query parameter which can be used to search by email address.
I'm trying this,
But {query} is "l=praveen" so we need a way to strip out "l=" also from that.
I tried
uri replace ?l= ?search=but it is still passing l=praveen.pravi commentedon Jun 2, 2024
With just a small change, I can get the keys without storing the wkd hash.
pravi commentedon Jun 2, 2024
#147 submitted as a simpler method for wkd support.
strickes commentedon Feb 9, 2025
@pravi thank you very much for your guide. I'm trying to make it work on Nginx, but the Mailvelope Browser Plugin still can't find my PGP Key. But the
gpg-wks-clientdoes:this is the nginx config for this:
the Mailvelope Browser Plugin states this in the browser console