Detail and message are mostly the same for acme error, use just one to avoid conufsing

This commit is contained in:
Fang-Pen Lin
2025-11-12 09:12:14 -08:00
parent 159eb3138d
commit f11c4084ac
6 changed files with 144 additions and 206 deletions
@@ -16,13 +16,13 @@ Feature: Account
Given I have an ACME cert profile as "acme_profile" Given I have an ACME cert profile as "acme_profile"
When I have an ACME client connecting to {BASE_URL}/api/v1/pki/acme/profiles/{acme_profile.id}/directory When I have an ACME client connecting to {BASE_URL}/api/v1/pki/acme/profiles/{acme_profile.id}/directory
Then I register a new ACME account with email fangpen@infisical.com and EAB key id "<eab_kid>" with secret "<eab_secret>" as acme_account Then I register a new ACME account with email fangpen@infisical.com and EAB key id "<eab_kid>" with secret "<eab_secret>" as acme_account
Then the value error with jq ".type" should be equal to "urn:ietf:params:acme:error:malformed" Then the value error with jq ".type" should be equal to "<error_type>"
Then the value error with jq ".detail" should be equal to "Invalid external account binding JWS signature" Then the value error with jq ".detail" should be equal to "<error_msg>"
Examples: Bad Credentials Examples: Bad Credentials
| eab_kid | eab_secret | | eab_kid | eab_secret | error_type | error_msg |
| bad | Cg== | | bad | Cg== | urn:ietf:params:acme:error:externalAccountRequired | fixme |
| bad | Cg== | | bad | Cg== | urn:ietf:params:acme:error:externalAccountRequired | fixme |
| {acme_profile.eab_kid} | Cg== | | {acme_profile.eab_kid} | Cg== | urn:ietf:params:acme:error:externalAccountRequired | fixme |
| {acme_profile.eab_kid} | YmFkLXNjcmV0Cg== | | {acme_profile.eab_kid} | YmFkLXNjcmV0Cg== | urn:ietf:params:acme:error:externalAccountRequired | fixme |
| 4bc7959c-fe2d-4447-ae91-0cd893667af6 | {acme_profile.eab_secret} | | 4bc7959c-fe2d-4447-ae91-0cd893667af6 | {acme_profile.eab_secret} | urn:ietf:params:acme:error:externalAccountRequired | fixme |
+2 -2
View File
@@ -251,8 +251,8 @@ def step_impl(context: Context, email: str, kid: str, secret: str, account_var:
) )
try: try:
context.vars[account_var] = acme_client.new_account(registration) context.vars[account_var] = acme_client.new_account(registration)
except Exception: except Exception as exp:
context.vars["error"] = acme_client.new_account(registration) context.vars["error"] = exp
@then("I register a new ACME account with email {email} without EAB") @then("I register a new ACME account with email {email} without EAB")
@@ -35,15 +35,14 @@ export enum AcmeErrorType {
export interface IAcmeError { export interface IAcmeError {
type: AcmeErrorType; type: AcmeErrorType;
detail: string; message: string;
status: number; status: number;
subproblems?: Array<{ type: string; detail: string; identifier?: { type: string; value: string } }>; subproblems?: Array<{ type: string; detail: string; identifier?: { type: string; value: string } }>;
} }
export class AcmeError extends Error implements IAcmeError { export class AcmeError extends Error implements IAcmeError {
type: AcmeErrorType; type: AcmeErrorType;
message: string;
detail: string;
status: number; status: number;
@@ -53,22 +52,20 @@ export class AcmeError extends Error implements IAcmeError {
constructor({ constructor({
type, type,
detail, message,
status, status,
subproblems, subproblems,
error, error
message
}: { }: {
type: AcmeErrorType; type: AcmeErrorType;
detail: string; message: string;
status: number; status: number;
subproblems?: Array<{ type: string; detail: string; identifier?: { type: string; value: string } }>; subproblems?: Array<{ type: string; detail: string; identifier?: { type: string; value: string } }>;
error?: unknown; error?: unknown;
message?: string;
}) { }) {
super(message || detail); super(message);
this.type = type; this.type = type;
this.detail = detail; this.message = message;
this.status = status; this.status = status;
this.subproblems = subproblems; this.subproblems = subproblems;
this.error = error; this.error = error;
@@ -78,7 +75,7 @@ export class AcmeError extends Error implements IAcmeError {
toAcmeResponse(): IAcmeError { toAcmeResponse(): IAcmeError {
return { return {
type: this.type, type: this.type,
detail: this.detail, message: this.message,
status: this.status, status: this.status,
subproblems: this.subproblems subproblems: this.subproblems
}; };
@@ -90,20 +87,17 @@ export class AcmeError extends Error implements IAcmeError {
*/ */
export class AcmeMalformedError extends AcmeError { export class AcmeMalformedError extends AcmeError {
constructor({ constructor({
detail = "The request message was malformed", message = "The request message was malformed",
error, error
message
}: { }: {
detail?: string;
error?: unknown;
message?: string; message?: string;
error?: unknown;
} = {}) { } = {}) {
super({ super({
type: AcmeErrorType.Malformed, type: AcmeErrorType.Malformed,
detail, message,
status: 400, status: 400,
error, error
message
}); });
this.name = "AcmeMalformedError"; this.name = "AcmeMalformedError";
} }
@@ -114,20 +108,17 @@ export class AcmeMalformedError extends AcmeError {
*/ */
export class AcmeUnauthorizedError extends AcmeError { export class AcmeUnauthorizedError extends AcmeError {
constructor({ constructor({
detail = "The client lacks sufficient authorization", message = "The client lacks sufficient authorization",
error, error
message
}: { }: {
detail?: string;
error?: unknown;
message?: string; message?: string;
error?: unknown;
} = {}) { } = {}) {
super({ super({
type: AcmeErrorType.Unauthorized, type: AcmeErrorType.Unauthorized,
detail, message,
status: 403, status: 403,
error, error
message
}); });
this.name = "AcmeUnauthorizedError"; this.name = "AcmeUnauthorizedError";
} }
@@ -139,20 +130,17 @@ export class AcmeUnauthorizedError extends AcmeError {
*/ */
export class AcmeAccountDoesNotExistError extends AcmeError { export class AcmeAccountDoesNotExistError extends AcmeError {
constructor({ constructor({
detail = "The request specified an account that does not exist", message = "The request specified an account that does not exist",
error, error
message
}: { }: {
detail?: string;
error?: unknown;
message?: string; message?: string;
error?: unknown;
} = {}) { } = {}) {
super({ super({
type: AcmeErrorType.AccountDoesNotExist, type: AcmeErrorType.AccountDoesNotExist,
detail, message,
status: 400, status: 400,
error, error
message
}); });
this.name = "AcmeAccountDoesNotExistError"; this.name = "AcmeAccountDoesNotExistError";
} }
@@ -163,20 +151,17 @@ export class AcmeAccountDoesNotExistError extends AcmeError {
*/ */
export class AcmeBadNonceError extends AcmeError { export class AcmeBadNonceError extends AcmeError {
constructor({ constructor({
detail = "The client sent an unacceptable anti-replay nonce", message = "The client sent an unacceptable anti-replay nonce",
error, error
message
}: { }: {
detail?: string;
error?: unknown;
message?: string; message?: string;
error?: unknown;
} = {}) { } = {}) {
super({ super({
type: AcmeErrorType.BadNonce, type: AcmeErrorType.BadNonce,
detail, message,
status: 400, status: 400,
error, error
message
}); });
this.name = "AcmeBadNonceError"; this.name = "AcmeBadNonceError";
} }
@@ -187,20 +172,17 @@ export class AcmeBadNonceError extends AcmeError {
*/ */
export class AcmeBadSignatureAlgorithmError extends AcmeError { export class AcmeBadSignatureAlgorithmError extends AcmeError {
constructor({ constructor({
detail = "The signature algorithm is invalid", message = "The signature algorithm is invalid",
error, error
message
}: { }: {
detail?: string;
error?: unknown;
message?: string; message?: string;
error?: unknown;
} = {}) { } = {}) {
super({ super({
type: AcmeErrorType.BadSignatureAlgorithm, type: AcmeErrorType.BadSignatureAlgorithm,
detail, message,
status: 401, status: 401,
error, error
message
}); });
this.name = "AcmeBadSignatureAlgorithmError"; this.name = "AcmeBadSignatureAlgorithmError";
} }
@@ -211,20 +193,17 @@ export class AcmeBadSignatureAlgorithmError extends AcmeError {
*/ */
export class AcmeBadPublicKeyError extends AcmeError { export class AcmeBadPublicKeyError extends AcmeError {
constructor({ constructor({
detail = "The public key is not acceptable", message = "The public key is not acceptable",
error, error
message
}: { }: {
detail?: string;
error?: unknown;
message?: string; message?: string;
error?: unknown;
} = {}) { } = {}) {
super({ super({
type: AcmeErrorType.BadPublicKey, type: AcmeErrorType.BadPublicKey,
detail, message,
status: 400, status: 400,
error, error
message
}); });
this.name = "AcmeBadPublicKeyError"; this.name = "AcmeBadPublicKeyError";
} }
@@ -235,20 +214,17 @@ export class AcmeBadPublicKeyError extends AcmeError {
*/ */
export class AcmeBadCsrError extends AcmeError { export class AcmeBadCsrError extends AcmeError {
constructor({ constructor({
detail = "The CSR is unacceptable", message = "The CSR is unacceptable",
error, error
message
}: { }: {
detail?: string;
error?: unknown;
message?: string; message?: string;
error?: unknown;
} = {}) { } = {}) {
super({ super({
type: AcmeErrorType.BadCsr, type: AcmeErrorType.BadCsr,
detail, message,
status: 400, status: 400,
error, error
message
}); });
this.name = "AcmeBadCsrError"; this.name = "AcmeBadCsrError";
} }
@@ -260,20 +236,17 @@ export class AcmeBadCsrError extends AcmeError {
*/ */
export class AcmeBadRevocationReasonError extends AcmeError { export class AcmeBadRevocationReasonError extends AcmeError {
constructor({ constructor({
detail = "The revocation reason provided is not allowed", message = "The revocation reason provided is not allowed",
error, error
message
}: { }: {
detail?: string;
error?: unknown;
message?: string; message?: string;
error?: unknown;
} = {}) { } = {}) {
super({ super({
type: AcmeErrorType.BadRevocationReason, type: AcmeErrorType.BadRevocationReason,
detail, message,
status: 400, status: 400,
error, error
message
}); });
this.name = "AcmeBadRevocationReasonError"; this.name = "AcmeBadRevocationReasonError";
} }
@@ -284,20 +257,17 @@ export class AcmeBadRevocationReasonError extends AcmeError {
*/ */
export class AcmeRateLimitedError extends AcmeError { export class AcmeRateLimitedError extends AcmeError {
constructor({ constructor({
detail = "The client has exceeded a rate limit", message = "The client has exceeded a rate limit",
error, error
message
}: { }: {
detail?: string;
error?: unknown;
message?: string; message?: string;
error?: unknown;
} = {}) { } = {}) {
super({ super({
type: AcmeErrorType.RateLimited, type: AcmeErrorType.RateLimited,
detail, message,
status: 429, status: 429,
error, error
message
}); });
this.name = "AcmeRateLimitedError"; this.name = "AcmeRateLimitedError";
} }
@@ -309,23 +279,20 @@ export class AcmeRateLimitedError extends AcmeError {
*/ */
export class AcmeRejectedIdentifierError extends AcmeError { export class AcmeRejectedIdentifierError extends AcmeError {
constructor({ constructor({
detail = "The server will not issue certificates for the identifier", message = "The server will not issue certificates for the identifier",
subproblems, subproblems,
error, error
message
}: { }: {
detail?: string; message?: string;
subproblems?: Array<{ type: string; detail: string; identifier?: { type: string; value: string } }>; subproblems?: Array<{ type: string; detail: string; identifier?: { type: string; value: string } }>;
error?: unknown; error?: unknown;
message?: string;
} = {}) { } = {}) {
super({ super({
type: AcmeErrorType.RejectedIdentifier, type: AcmeErrorType.RejectedIdentifier,
detail, message,
status: 400, status: 400,
subproblems, subproblems,
error, error
message
}); });
this.name = "AcmeRejectedIdentifierError"; this.name = "AcmeRejectedIdentifierError";
} }
@@ -336,20 +303,17 @@ export class AcmeRejectedIdentifierError extends AcmeError {
*/ */
export class AcmeServerInternalError extends AcmeError { export class AcmeServerInternalError extends AcmeError {
constructor({ constructor({
detail = "An internal error occurred", message = "An internal error occurred",
error, error
message
}: { }: {
detail?: string;
error?: unknown;
message?: string; message?: string;
error?: unknown;
} = {}) { } = {}) {
super({ super({
type: AcmeErrorType.ServerInternal, type: AcmeErrorType.ServerInternal,
detail, message,
status: 500, status: 500,
error, error
message
}); });
this.name = "AcmeServerInternalError"; this.name = "AcmeServerInternalError";
} }
@@ -360,20 +324,17 @@ export class AcmeServerInternalError extends AcmeError {
*/ */
export class AcmeUnsupportedContactError extends AcmeError { export class AcmeUnsupportedContactError extends AcmeError {
constructor({ constructor({
detail = "A contact URL is of an unsupported type", message = "A contact URL is of an unsupported type",
error, error
message
}: { }: {
detail?: string;
error?: unknown;
message?: string; message?: string;
error?: unknown;
} = {}) { } = {}) {
super({ super({
type: AcmeErrorType.UnsupportedContact, type: AcmeErrorType.UnsupportedContact,
detail, message,
status: 400, status: 400,
error, error
message
}); });
this.name = "AcmeUnsupportedContactError"; this.name = "AcmeUnsupportedContactError";
} }
@@ -385,20 +346,17 @@ export class AcmeUnsupportedContactError extends AcmeError {
*/ */
export class AcmeUnsupportedIdentifierError extends AcmeError { export class AcmeUnsupportedIdentifierError extends AcmeError {
constructor({ constructor({
detail = "An identifier is of an unsupported type", message = "An identifier is of an unsupported type",
error, error
message
}: { }: {
detail?: string;
error?: unknown;
message?: string; message?: string;
error?: unknown;
} = {}) { } = {}) {
super({ super({
type: AcmeErrorType.UnsupportedIdentifier, type: AcmeErrorType.UnsupportedIdentifier,
detail, message,
status: 400, status: 400,
error, error
message
}); });
this.name = "AcmeUnsupportedIdentifierError"; this.name = "AcmeUnsupportedIdentifierError";
} }
@@ -412,22 +370,19 @@ export class AcmeUserActionRequiredError extends AcmeError {
instance?: string; instance?: string;
constructor({ constructor({
detail = "Visit the instance URL and take actions specified there", message = "Visit the instance URL and take actions specified there",
instance, instance,
error, error
message
}: { }: {
detail?: string; message?: string;
instance?: string; instance?: string;
error?: unknown; error?: unknown;
message?: string;
} = {}) { } = {}) {
super({ super({
type: AcmeErrorType.UserActionRequired, type: AcmeErrorType.UserActionRequired,
detail, message,
status: 403, status: 403,
error, error
message
}); });
this.instance = instance; this.instance = instance;
this.name = "AcmeUserActionRequiredError"; this.name = "AcmeUserActionRequiredError";
@@ -446,20 +401,17 @@ export class AcmeUserActionRequiredError extends AcmeError {
*/ */
export class AcmeIncorrectResponseError extends AcmeError { export class AcmeIncorrectResponseError extends AcmeError {
constructor({ constructor({
detail = "The response is incorrect", message = "The response is incorrect",
error, error
message
}: { }: {
detail?: string;
error?: unknown;
message?: string; message?: string;
error?: unknown;
} = {}) { } = {}) {
super({ super({
type: AcmeErrorType.IncorrectResponse, type: AcmeErrorType.IncorrectResponse,
detail, message,
status: 400, status: 400,
error, error
message
}); });
this.name = "AcmeIncorrectResponseError"; this.name = "AcmeIncorrectResponseError";
} }
@@ -470,20 +422,17 @@ export class AcmeIncorrectResponseError extends AcmeError {
*/ */
export class AcmeConnectionError extends AcmeError { export class AcmeConnectionError extends AcmeError {
constructor({ constructor({
detail = "A connection error occurred", message = "A connection error occurred",
error, error
message
}: { }: {
detail?: string;
error?: unknown;
message?: string; message?: string;
error?: unknown;
} = {}) { } = {}) {
super({ super({
type: AcmeErrorType.Connection, type: AcmeErrorType.Connection,
detail, message,
status: 400, status: 400,
error, error
message
}); });
this.name = "AcmeConnectionError"; this.name = "AcmeConnectionError";
} }
@@ -491,20 +440,17 @@ export class AcmeConnectionError extends AcmeError {
export class AcmeDnsFailureError extends AcmeError { export class AcmeDnsFailureError extends AcmeError {
constructor({ constructor({
detail = "Hostname could not be resolved (DNS failure)", message = "Hostname could not be resolved (DNS failure)",
error, error
message
}: { }: {
detail?: string;
error?: unknown;
message?: string; message?: string;
error?: unknown;
} = {}) { } = {}) {
super({ super({
type: AcmeErrorType.DNS, type: AcmeErrorType.DNS,
detail, message,
status: 400, status: 400,
error, error
message
}); });
this.name = "AcmeDnsFailureError"; this.name = "AcmeDnsFailureError";
} }
@@ -512,20 +458,17 @@ export class AcmeDnsFailureError extends AcmeError {
export class AcmeOrderNotReadyError extends AcmeError { export class AcmeOrderNotReadyError extends AcmeError {
constructor({ constructor({
detail = "The order is not ready", message = "The order is not ready",
error, error
message
}: { }: {
detail?: string;
error?: unknown;
message?: string; message?: string;
error?: unknown;
} = {}) { } = {}) {
super({ super({
type: AcmeErrorType.OrderNotReady, type: AcmeErrorType.OrderNotReady,
detail, message,
status: 403, status: 403,
error, error
message
}); });
this.name = "AcmeOrderNotReadyError"; this.name = "AcmeOrderNotReadyError";
} }
@@ -533,20 +476,17 @@ export class AcmeOrderNotReadyError extends AcmeError {
export class AcmeBadCSRError extends AcmeError { export class AcmeBadCSRError extends AcmeError {
constructor({ constructor({
detail = "The CSR is unacceptable", message = "The CSR is unacceptable",
error, error
message
}: { }: {
detail?: string;
error?: unknown;
message?: string; message?: string;
error?: unknown;
} = {}) { } = {}) {
super({ super({
type: AcmeErrorType.BadCsr, type: AcmeErrorType.BadCsr,
detail, message,
status: 400, status: 400,
error, error
message
}); });
this.name = "AcmeBadCSRError"; this.name = "AcmeBadCSRError";
} }
@@ -554,20 +494,17 @@ export class AcmeBadCSRError extends AcmeError {
export class AcmeExternalAccountRequiredError extends AcmeError { export class AcmeExternalAccountRequiredError extends AcmeError {
constructor({ constructor({
detail = "External account binding is required", message = "External account binding is required",
error, error
message
}: { }: {
detail?: string;
error?: unknown;
message?: string; message?: string;
error?: unknown;
} = {}) { } = {}) {
super({ super({
type: AcmeErrorType.ExternalAccountRequired, type: AcmeErrorType.ExternalAccountRequired,
detail, message,
status: 400, status: 400,
error, error
message
}); });
this.name = "AcmeExternalAccountRequiredError"; this.name = "AcmeExternalAccountRequiredError";
} }
@@ -13,7 +13,7 @@ export const buildUrl = (profileId: string, path: string): string => {
export const extractAccountIdFromKid = (kid: string, profileId: string): string => { export const extractAccountIdFromKid = (kid: string, profileId: string): string => {
const kidPrefix = buildUrl(profileId, "/accounts/"); const kidPrefix = buildUrl(profileId, "/accounts/");
if (!kid.startsWith(kidPrefix)) { if (!kid.startsWith(kidPrefix)) {
throw new AcmeMalformedError({ detail: "KID must start with the profile account URL" }); throw new AcmeMalformedError({ message: "KID must start with the profile account URL" });
} }
return z.string().uuid().parse(kid.slice(kidPrefix.length)); return z.string().uuid().parse(kid.slice(kidPrefix.length));
}; };
@@ -148,7 +148,7 @@ export const pkiAcmeServiceFactory = ({
try { try {
result = await flattenedVerify(rawJwsPayload, async (protectedHeader: JWSHeaderParameters | undefined) => { result = await flattenedVerify(rawJwsPayload, async (protectedHeader: JWSHeaderParameters | undefined) => {
if (protectedHeader === undefined) { if (protectedHeader === undefined) {
throw new AcmeMalformedError({ detail: "Protected header is required" }); throw new AcmeMalformedError({ message: "Protected header is required" });
} }
const jwk = await getJWK(protectedHeader); const jwk = await getJWK(protectedHeader);
const key = await importJWK(jwk, protectedHeader.alg); const key = await importJWK(jwk, protectedHeader.alg);
@@ -159,28 +159,28 @@ export const pkiAcmeServiceFactory = ({
throw error; throw error;
} }
if (error instanceof ZodError) { if (error instanceof ZodError) {
throw new AcmeMalformedError({ detail: `Invalid JWS payload: ${error.message}` }); throw new AcmeMalformedError({ message: `Invalid JWS payload: ${error.message}` });
} }
if (error instanceof errors.JWSSignatureVerificationFailed) { if (error instanceof errors.JWSSignatureVerificationFailed) {
throw new AcmeBadPublicKeyError({ detail: "Invalid JWS payload" }); throw new AcmeBadPublicKeyError({ message: "Invalid JWS payload" });
} }
logger.error(error, "Unexpected error while verifying JWS payload"); logger.error(error, "Unexpected error while verifying JWS payload");
throw new AcmeServerInternalError({ detail: "Failed to verify JWS payload" }); throw new AcmeServerInternalError({ message: "Failed to verify JWS payload" });
} }
const { protectedHeader: rawProtectedHeader, payload: rawPayload } = result; const { protectedHeader: rawProtectedHeader, payload: rawPayload } = result;
try { try {
const protectedHeader = ProtectedHeaderSchema.parse(rawProtectedHeader); const protectedHeader = ProtectedHeaderSchema.parse(rawProtectedHeader);
// Validate the URL // Validate the URL
if (new URL(protectedHeader.url).href !== url.href) { if (new URL(protectedHeader.url).href !== url.href) {
throw new AcmeUnauthorizedError({ detail: "URL mismatch in the protected header" }); throw new AcmeUnauthorizedError({ message: "URL mismatch in the protected header" });
} }
// Consume the nonce // Consume the nonce
if (!protectedHeader.nonce) { if (!protectedHeader.nonce) {
throw new AcmeMalformedError({ detail: "Nonce is required in the protected header" }); throw new AcmeMalformedError({ message: "Nonce is required in the protected header" });
} }
const deleted = await keyStore.deleteItem(KeyStorePrefixes.PkiAcmeNonce(protectedHeader.nonce)); const deleted = await keyStore.deleteItem(KeyStorePrefixes.PkiAcmeNonce(protectedHeader.nonce));
if (deleted !== 1) { if (deleted !== 1) {
throw new AcmeBadNonceError({ detail: "Invalid nonce" }); throw new AcmeBadNonceError({ message: "Invalid nonce" });
} }
// Parse the payload // Parse the payload
@@ -196,10 +196,10 @@ export const pkiAcmeServiceFactory = ({
throw error; throw error;
} }
if (error instanceof ZodError) { if (error instanceof ZodError) {
throw new AcmeMalformedError({ detail: `Invalid JWS payload: ${error.message}` }); throw new AcmeMalformedError({ message: `Invalid JWS payload: ${error.message}` });
} }
logger.error(error, "Unexpected error while parsing JWS payload"); logger.error(error, "Unexpected error while parsing JWS payload");
throw new AcmeMalformedError({ detail: "Failed to verify JWS payload" }); throw new AcmeMalformedError({ message: "Failed to verify JWS payload" });
} }
}; };
@@ -215,7 +215,7 @@ export const pkiAcmeServiceFactory = ({
rawJwsPayload, rawJwsPayload,
getJWK: async (protectedHeader) => { getJWK: async (protectedHeader) => {
if (!protectedHeader.jwk) { if (!protectedHeader.jwk) {
throw new AcmeMalformedError({ detail: "JWK is required in the protected header" }); throw new AcmeMalformedError({ message: "JWK is required in the protected header" });
} }
return protectedHeader.jwk as unknown as JsonWebKey; return protectedHeader.jwk as unknown as JsonWebKey;
}, },
@@ -246,7 +246,7 @@ export const pkiAcmeServiceFactory = ({
rawJwsPayload, rawJwsPayload,
getJWK: async (protectedHeader) => { getJWK: async (protectedHeader) => {
if (!protectedHeader.kid) { if (!protectedHeader.kid) {
throw new AcmeMalformedError({ detail: "KID is required in the protected header" }); throw new AcmeMalformedError({ message: "KID is required in the protected header" });
} }
const accountId = extractAccountIdFromKid(protectedHeader.kid, profileId); const accountId = extractAccountIdFromKid(protectedHeader.kid, profileId);
if (expectedAccountId && accountId !== expectedAccountId) { if (expectedAccountId && accountId !== expectedAccountId) {
@@ -257,7 +257,7 @@ export const pkiAcmeServiceFactory = ({
throw new AcmeAccountDoesNotExistError({ message: "ACME account not found" }); throw new AcmeAccountDoesNotExistError({ message: "ACME account not found" });
} }
if (account.alg !== protectedHeader.alg) { if (account.alg !== protectedHeader.alg) {
throw new AcmeMalformedError({ detail: "ACME account algorithm mismatch" }); throw new AcmeMalformedError({ message: "ACME account algorithm mismatch" });
} }
return account.publicKey as JsonWebKey; return account.publicKey as JsonWebKey;
}, },
@@ -344,7 +344,7 @@ export const pkiAcmeServiceFactory = ({
}): Promise<TAcmeResponse<TCreateAcmeAccountResponse>> => { }): Promise<TAcmeResponse<TCreateAcmeAccountResponse>> => {
const profile = await validateAcmeProfile(profileId); const profile = await validateAcmeProfile(profileId);
if (!externalAccountBinding) { if (!externalAccountBinding) {
throw new AcmeExternalAccountRequiredError({ detail: "External account binding is required" }); throw new AcmeExternalAccountRequiredError({ message: "External account binding is required" });
} }
const publicKeyThumbprint = await calculateJwkThumbprint(jwk, "sha256"); const publicKeyThumbprint = await calculateJwkThumbprint(jwk, "sha256");
@@ -363,26 +363,28 @@ export const pkiAcmeServiceFactory = ({
return { eabPayload: result.payload, eabProtectedHeader: result.protectedHeader }; return { eabPayload: result.payload, eabProtectedHeader: result.protectedHeader };
} catch (error) { } catch (error) {
if (error instanceof errors.JWSSignatureVerificationFailed) { if (error instanceof errors.JWSSignatureVerificationFailed) {
throw new AcmeMalformedError({ detail: "Invalid external account binding JWS signature" }); throw new AcmeExternalAccountRequiredError({ message: "Invalid external account binding JWS signature" });
} }
logger.error(error, "Unexpected error while verifying EAB JWS signature"); logger.error(error, "Unexpected error while verifying EAB JWS signature");
throw new AcmeServerInternalError({ detail: "Failed to verify EAB JWS signature" }); throw new AcmeServerInternalError({ message: "Failed to verify EAB JWS signature" });
} }
})(); })();
const { alg: eabAlg, kid: eabKid } = eabProtectedHeader!; const { alg: eabAlg, kid: eabKid } = eabProtectedHeader!;
if (!["HS256", "HS384", "HS512"].includes(eabAlg!)) { if (!["HS256", "HS384", "HS512"].includes(eabAlg!)) {
throw new AcmeMalformedError({ detail: "Invalid algorithm for external account binding JWS payload" }); throw new AcmeExternalAccountRequiredError({
message: "Invalid algorithm for external account binding JWS payload"
});
} }
// Make sure the KID in the EAB payload matches the profile ID // Make sure the KID in the EAB payload matches the profile ID
if (eabKid !== profile.id) { if (eabKid !== profile.id) {
throw new UnauthorizedError({ message: "External account binding KID mismatch" }); throw new AcmeExternalAccountRequiredError({ message: "External account binding KID mismatch" });
} }
// Make sure the URL matches the expected URL // Make sure the URL matches the expected URL
const url = eabProtectedHeader!.url!; const url = eabProtectedHeader!.url!;
if (url !== buildUrl(profile.id, "/new-account")) { if (url !== buildUrl(profile.id, "/new-account")) {
throw new UnauthorizedError({ message: "External account binding URL mismatch" }); throw new AcmeExternalAccountRequiredError({ message: "External account binding URL mismatch" });
} }
// Make sure the JWK in the EAB payload matches the one provided in the outer JWS payload // Make sure the JWK in the EAB payload matches the one provided in the outer JWS payload
@@ -497,10 +499,10 @@ export const pkiAcmeServiceFactory = ({
const authorizations: TPkiAcmeAuths[] = await Promise.all( const authorizations: TPkiAcmeAuths[] = await Promise.all(
payload.identifiers.map(async (identifier) => { payload.identifiers.map(async (identifier) => {
if (identifier.type !== AcmeIdentifierType.DNS) { if (identifier.type !== AcmeIdentifierType.DNS) {
throw new AcmeUnsupportedIdentifierError({ detail: "Only DNS identifiers are supported" }); throw new AcmeUnsupportedIdentifierError({ message: "Only DNS identifiers are supported" });
} }
if (isPrivateIp(identifier.value)) { if (isPrivateIp(identifier.value)) {
throw new AcmeUnsupportedIdentifierError({ detail: "Private IP addresses are not allowed" }); throw new AcmeUnsupportedIdentifierError({ message: "Private IP addresses are not allowed" });
} }
const auth = await acmeAuthDAL.create( const auth = await acmeAuthDAL.create(
{ {
@@ -647,9 +649,9 @@ export const pkiAcmeServiceFactory = ({
logger.error(exp, "Failed to sign certificate"); logger.error(exp, "Failed to sign certificate");
// TODO: audit log the error // TODO: audit log the error
if (exp instanceof BadRequestError) { if (exp instanceof BadRequestError) {
errorToReturn = new AcmeBadCSRError({ detail: `Invalid CSR: ${exp.message}` }); errorToReturn = new AcmeBadCSRError({ message: `Invalid CSR: ${exp.message}` });
} else { } else {
errorToReturn = new AcmeServerInternalError({ detail: "Failed to sign certificate with internal error" }); errorToReturn = new AcmeServerInternalError({ message: "Failed to sign certificate with internal error" });
} }
} }
return { return {
+1 -2
View File
@@ -252,8 +252,7 @@ export const fastifyErrHandler = fastifyPlugin(async (server: FastifyZodProvider
error: error.name, error: error.name,
status: error.status, status: error.status,
type: `urn:ietf:params:acme:error:${error.type}`, type: `urn:ietf:params:acme:error:${error.type}`,
detail: error.detail, detail: error.message
message: error.message
// TODO: add subproblems if they exist // TODO: add subproblems if they exist
}); });
} else { } else {