This commit is contained in:
Fang-Pen Lin
2025-11-04 16:05:54 -08:00
parent 7faac62c8a
commit 4791b16b00
2 changed files with 28 additions and 20 deletions

View File

@@ -6,6 +6,10 @@ import { AcmeConnectionError, AcmeDnsFailureError, AcmeIncorrectResponseError }
import { AcmeAuthStatus, AcmeChallengeStatus, AcmeChallengeType } from "./pki-acme-schemas";
import { TPkiAcmeChallengeServiceFactory } from "./pki-acme-types";
type FetchError = Error & {
code?: string;
};
type TPkiAcmeChallengeServiceFactoryDep = {
acmeChallengeDAL: Pick<
TPkiAcmeChallengeDALFactory,
@@ -69,27 +73,33 @@ export const pkiAcmeChallengeServiceFactory = ({
throw new AcmeIncorrectResponseError({ message: "ACME challenge response is not correct" });
}
await acmeChallengeDAL.markAsValidCascadeById(challengeId, tx);
} catch (error) {
} catch (exp) {
// TODO: we should retry the challenge validation a few times, but let's keep it simple for now
await acmeChallengeDAL.markAsInvalidCascadeById(challengeId, tx);
// Properly type and inspect the error
if (error instanceof TypeError && error.message.includes("fetch failed")) {
const cause = error.cause;
const errors = cause instanceof AggregateError ? cause.errors : cause instanceof Error ? [cause] : [];
if (exp instanceof TypeError && exp.message.includes("fetch failed")) {
const { cause } = exp;
let errors: Error[] = [];
if (cause instanceof AggregateError) {
errors = cause.errors;
} else if (cause instanceof Error) {
errors = [cause];
}
for (const err of errors) {
// TODO: handle multiple errors, return a compound error instead of just the first error
if (err?.code === "ECONNREFUSED" || err?.message?.includes("ECONNREFUSED")) {
const fetchError = err as FetchError;
if (fetchError.code === "ECONNREFUSED" || fetchError.message.includes("ECONNREFUSED")) {
return new AcmeConnectionError({ message: "Connection refused" });
} else if (err?.code === "ENOTFOUND" || err?.message?.includes("ENOTFOUND")) {
} else if (fetchError.code === "ENOTFOUND" || fetchError.message.includes("ENOTFOUND")) {
return new AcmeDnsFailureError({ message: "Hostname could not be resolved (DNS failure)" });
}
}
} else if (error instanceof Error) {
logger.error(error, "Error validating ACME challenge response");
} else if (exp instanceof Error) {
logger.error(exp, "Error validating ACME challenge response");
} else {
logger.error(error, "Unknown error validating ACME challenge response");
logger.error(exp, "Unknown error validating ACME challenge response");
}
return error;
return exp;
}
});
if (error) {

View File

@@ -129,7 +129,7 @@ export const pkiAcmeServiceFactory = ({
};
const validateJwsPayload = async <
TSchema extends z.ZodSchema<any> | undefined = undefined,
TSchema extends z.ZodSchema<unknown> | undefined = undefined,
T = TSchema extends z.ZodSchema<infer R> ? R : string
>({
url,
@@ -149,7 +149,8 @@ export const pkiAcmeServiceFactory = ({
throw new AcmeMalformedError({ detail: "Protected header is required" });
}
const jwk = await getJWK(protectedHeader);
return await importJWK(jwk, protectedHeader.alg);
const key = await importJWK(jwk, protectedHeader.alg);
return key;
});
} catch (error) {
if (error instanceof AcmeError) {
@@ -186,7 +187,7 @@ export const pkiAcmeServiceFactory = ({
const payload = schema ? schema.parse(JSON.parse(textPayload)) : textPayload;
return {
protectedHeader,
payload
payload: payload as T
};
} catch (error) {
if (error instanceof AcmeError) {
@@ -200,14 +201,14 @@ export const pkiAcmeServiceFactory = ({
}
};
const validateNewAccountJwsPayload = async ({
const validateNewAccountJwsPayload = ({
url,
rawJwsPayload
}: {
url: URL;
rawJwsPayload: TRawJwsPayload;
}): Promise<TJwsPayload<TCreateAcmeAccountPayload>> => {
return await validateJwsPayload({
return validateJwsPayload({
url,
rawJwsPayload,
getJWK: async (protectedHeader) => {
@@ -355,11 +356,8 @@ export const pkiAcmeServiceFactory = ({
const eabSecret = await kmsDecryptor({ cipherTextBlob: profile.acmeConfig!.encryptedEabSecret! });
const { eabPayload, eabProtectedHeader } = await (async () => {
try {
const { payload: eabPayload, protectedHeader: eabProtectedHeader } = await flattenedVerify(
externalAccountBinding,
eabSecret
);
return { eabPayload, eabProtectedHeader };
const result = await flattenedVerify(externalAccountBinding, eabSecret);
return { eabPayload: result.payload, eabProtectedHeader: result.protectedHeader };
} catch (error) {
if (error instanceof errors.JWSSignatureVerificationFailed) {
throw new AcmeMalformedError({ detail: "Invalid external account binding JWS signature" });