-
Notifications
You must be signed in to change notification settings - Fork 11
Closed
Description
part of #275
We'll need a way to construct WKD URLs with an email address as an input. Signature:
class Wkd {
static fun constructUrl(email: string, mode: ADVANCED | DIRECT): string
}This will follow the following spec: https://tools.ietf.org/html/draft-koch-openpgp-webkey-service-11 - this uses z-base-32 which is different from base-32, and may need to be implemented by hand (unless you can find a library somewhere for ObjC, Swift or C).
A few test cases:
recipient.hello@example.comDIRECT-> https://example.com/.well-known/openpgpkey/hu/1sbjrcaf8m3zckmmuej93nx61yn1sttg?l=recipient.hellorecipient.hello@example.comADVANCED-> https://openpgpkey.example.com/.well-known/openpgpkey/example.com/hu/1sbjrcaf8m3zckmmuej93nx61yn1sttg?l=recipient.helloUPPER@EXAMPLE.COMDIRECT->https://example.com/.well-known/openpgpkey/hu/awhcnhf7a4ax8qha5u1rwymkfaswmjz8?l=UPPERUPPER@EXAMPLE.COMADVANCED-> https://openpgpkey.example.com/.well-known/openpgpkey/example.com/hu/awhcnhf7a4ax8qha5u1rwymkfaswmjz8?l=UPPER
Existing implementation - typescript (we used a library for z-base32)
const parts = email.split('@');
if (parts.length !== 2) {
return null;
}
const [user, recipientDomain] = parts;
if (!user || !recipientDomain) {
return null;
}
const directDomain = recipientDomain.toLowerCase();
const advancedDomainPrefix = (directDomain === 'localhost') ? '' : 'openpgpkey.';
const hu = opgp.util.encodeZBase32(await opgp.crypto.hash.digest(opgp.enums.hash.sha1, Buf.fromUtfStr(user.toLowerCase())));
const directHost = (typeof this.port === 'undefined') ? directDomain : `${directDomain}:${this.port}`;
const advancedHost = `${advancedDomainPrefix}${directHost}`;
const userPart = `hu/${hu}?l=${encodeURIComponent(user)}`;
const advancedUrl = `https://${advancedHost}/.well-known/openpgpkey/${directDomain}`;
const directUrl = `https://${directHost}/.well-known/openpgpkey`;
// ...Existing implementation - python, where we had to implement zbase32 ourselves:
class Wkd(PubkeySource):
# https://datatracker.ietf.org/doc/draft-koch-openpgp-webkey-service/?include_text=1
# https://www.sektioneins.de/en/blog/18-11-23-gnupg-wkd.html
# https://metacode.biz/openpgp/web-key-directory
__TIMEOUT = (3, 3)
def find_pubkey(self, email:str=None):
user, domain = self._email_as_user_and_private_domain(email)
if user is None or domain is None:
return None
hu = self.__sha1_zbase32(user)
url_advanced = f'https://openpgpkey.{domain}/.well-known/openpgpkey/{domain}/hu/{hu}?l={user}'
url_direct = f'https://{domain}/.well-known/openpgpkey/hu/{hu}?l={user}'
# ...
def __sha1_zbase32(self, user: str):
def chunk_string(string, length):
return (string[0 + i:length + i] for i in range(0, len(string), length))
def chars_from_int(int32):
result = []
shift = 27
for _ in range(4):
result.append("ybndrfg8ejkmcpqxot1uwisza345h769"[(int32 >> shift) & 31])
shift = shift - 5
return result
hashed_bytes = hashlib.sha1(user.encode()).digest()
bits_string = bin(int.from_bytes(hashed_bytes, byteorder="big"))[2:]
chunks_of_20_bits = chunk_string(bits_string, 20)
offset_chunks_of_20_bits = [chunk + '000000000000' for chunk in chunks_of_20_bits]
ints = [int(bits, 2) for bits in offset_chunks_of_20_bits]
groups_of_4_chars = [chars_from_int(int32) for int32 in ints]
return "".join(sum(groups_of_4_chars, []))This is a pre-requisite for #296
Reactions are currently unavailable