Skip to content

constructing WKD urls  #314

@tomholub

Description

@tomholub

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:

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

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions