-
Notifications
You must be signed in to change notification settings - Fork 82
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #227 from zkemail/feat/archiver-dkim
Add an option to resolve DNS from ZKEmail Archive when HTTP DNS fails
- Loading branch information
Showing
6 changed files
with
265 additions
and
120 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
import { CustomError } from "../lib/mailauth/tools"; | ||
|
||
const ZKEMAIL_DNS_ARCHIVER_API = "https://archive.prove.email/api/key"; | ||
|
||
export async function resolveDNSFromZKEmailArchive(name: string, type: string) { | ||
if (type !== "TXT") { | ||
throw new Error(`ZK Email Archive only supports TXT records - got ${type}`); | ||
} | ||
|
||
// Get domain from full dns record name - $selector._domainkey.$domain.com | ||
const domain = name.split(".").slice(-2).join("."); | ||
const selector = name.split(".")[0]; | ||
|
||
const queryUrl = new URL(ZKEMAIL_DNS_ARCHIVER_API); | ||
queryUrl.searchParams.set("domain", domain); | ||
|
||
const resp = await fetch(queryUrl); | ||
const data = await resp.json(); | ||
|
||
const dkimRecord = data.find((record: any) => record.selector === selector); | ||
|
||
if (!dkimRecord) { | ||
throw new CustomError( | ||
`DKIM record not found for domain ${domain} and selector ${selector} in ZK Email Archive.`, | ||
"ENODATA" | ||
); | ||
} | ||
|
||
return [dkimRecord.value]; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,131 @@ | ||
import { CustomError } from "../lib/mailauth/tools"; | ||
|
||
// DoH servers list | ||
export enum DoHServer { | ||
// Google Public DNS | ||
Google = "https://dns.google/resolve", | ||
// Cloudflare DNS | ||
Cloudflare = "https://cloudflare-dns.com/dns-query", | ||
} | ||
|
||
/** | ||
* DNS over HTTPS (DoH) resolver | ||
* | ||
* @export | ||
* @class DoH | ||
*/ | ||
export class DoH { | ||
// DNS response codes | ||
static DoHStatusNoError = 0; | ||
// DNS RR types | ||
static DoHTypeTXT = 16; | ||
|
||
/** | ||
* Resolve DKIM public key from DNS | ||
* | ||
* @static | ||
* @param {string} name DKIM record name (e.g. 20230601._domainkey.gmail.com) | ||
* @param {string} DNSServer DNS over HTTPS API URL | ||
* @return {*} {(Promise<string | null>)} DKIM public key or null if not found | ||
* @memberof DoH | ||
*/ | ||
public static async resolveDKIMPublicKey( | ||
name: string, | ||
DNSServer: string | ||
): Promise<string | null> { | ||
if (!DNSServer.startsWith("https://")) { | ||
DNSServer = "https://" + DNSServer; | ||
} | ||
if (DNSServer.endsWith("/")) { | ||
DNSServer = DNSServer.slice(0, -1); | ||
} | ||
const resp = await fetch( | ||
DNSServer + | ||
"?" + | ||
new URLSearchParams({ | ||
name: name, | ||
// DKIM public key record type is TXT | ||
type: DoH.DoHTypeTXT.toString(), | ||
}), | ||
{ | ||
headers: { | ||
accept: "application/dns-json", | ||
}, | ||
} | ||
); | ||
if (resp.status === 200) { | ||
const out = await resp.json(); | ||
if ( | ||
typeof out === "object" && | ||
out !== null && | ||
"Status" in out && | ||
"Answer" in out | ||
) { | ||
const resp = out as DoHResponse; | ||
if (resp.Status === DoH.DoHStatusNoError && resp.Answer.length > 0) { | ||
for (const ans of resp.Answer) { | ||
if (ans.type === DoH.DoHTypeTXT) { | ||
let DKIMRecord = ans.data; | ||
/* | ||
Remove all double quotes | ||
Some DNS providers wrap TXT records in double quotes, | ||
and others like Cloudflare may include them. According to | ||
TXT (potentially multi-line) and DKIM (Base64 data) standards, | ||
we can directly remove all double quotes from the DKIM public key. | ||
*/ | ||
DKIMRecord = DKIMRecord.replace(/"/g, ""); | ||
return DKIMRecord; | ||
} | ||
} | ||
} | ||
} | ||
} | ||
return null; | ||
} | ||
} | ||
|
||
interface DoHResponse { | ||
Status: number; // NOERROR - Standard DNS response code (32 bit integer). | ||
TC: boolean; // Whether the response is truncated | ||
AD: boolean; // Whether all response data was validated with DNSSEC | ||
CD: boolean; // Whether the client asked to disable DNSSEC | ||
Question: Question[]; | ||
Answer: Answer[]; | ||
Comment: string; | ||
} | ||
|
||
interface Question { | ||
name: string; // FQDN with trailing dot | ||
type: number; // A - Standard DNS RR type. 5:CNAME, 16:TXT | ||
} | ||
|
||
interface Answer { | ||
name: string; // Always matches name in the Question section | ||
type: number; // A - Standard DNS RR type. 5:CNAME, 16:TXT | ||
TTL: number; // Record's time-to-live in seconds | ||
data: string; // Record data | ||
} | ||
|
||
export async function resolveDNSHTTP(name: string, type: string) { | ||
if (type !== "TXT") { | ||
throw new Error(`DNS over HTTP: Only type TXT is supported, got ${type}`); | ||
} | ||
const googleResult = await DoH.resolveDKIMPublicKey(name, DoHServer.Google); | ||
if (!googleResult) { | ||
throw new CustomError("No DKIM record found in Google", "ENODATA"); | ||
} | ||
|
||
const cloudflareResult = await DoH.resolveDKIMPublicKey( | ||
name, | ||
DoHServer.Cloudflare | ||
); | ||
|
||
// Log an error if there is a mismatch in the result | ||
if (googleResult !== cloudflareResult) { | ||
console.error( | ||
"DKIM record mismatch between Google and Cloudflare! Using Google result." | ||
); | ||
} | ||
|
||
return [googleResult]; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.