fix(kms-signing): requested changes

This commit is contained in:
Daniel Hougaard
2025-04-10 19:55:59 +04:00
parent ac0f4aa8bd
commit 894633143d
2 changed files with 105 additions and 90 deletions
+100 -56
View File
@@ -24,10 +24,17 @@ enum SupportedHashAlgorithm {
SHA512 = "sha512" SHA512 = "sha512"
} }
const COMMAND_TIMEOUT = 15_000;
const SHA256_DIGEST_LENGTH = 32; const SHA256_DIGEST_LENGTH = 32;
const SHA384_DIGEST_LENGTH = 48; const SHA384_DIGEST_LENGTH = 48;
const SHA512_DIGEST_LENGTH = 64; const SHA512_DIGEST_LENGTH = 64;
const makeTempDir = async (name: string) => {
const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), `${name}-${crypto.randomBytes(16).toString("hex")}-`));
return tempDir;
};
/** /**
* Service for cryptographic signing and verification operations using asymmetric keys * Service for cryptographic signing and verification operations using asymmetric keys
* *
@@ -117,52 +124,106 @@ export const signingService = (algorithm: AsymmetricKeyAlgorithm): TAsymmetricSi
}; };
const $signRsaDigest = async (digest: Buffer, privateKey: Buffer, hashAlgorithm: SupportedHashAlgorithm) => { const $signRsaDigest = async (digest: Buffer, privateKey: Buffer, hashAlgorithm: SupportedHashAlgorithm) => {
const script = `openssl pkeyutl -sign -in <(base64 -d <<< "$DIGEST_B64") -inkey <(base64 -d <<< "$KEY_B64") -pkeyopt digest:"$HASH_ALG" | base64`; const tempDir = await makeTempDir("kms-rsa-sign");
const result = await execFileAsync("bash", ["-c", script], { const digestPath = path.join(tempDir, "digest.bin");
encoding: "utf8", const keyPath = path.join(tempDir, "private_key.pem");
maxBuffer: 10 * 1024 * 1024, const sigPath = path.join(tempDir, "signature.bin");
env: {
DIGEST_B64: digest.toString("base64"),
KEY_B64: privateKey.toString("base64"),
HASH_ALG: hashAlgorithm
}
});
if (result.stderr) { try {
throw new Error(result.stderr); await fs.writeFile(digestPath, digest, { mode: 0o600 });
} await fs.writeFile(keyPath, privateKey, { mode: 0o600 });
if (!result.stdout) { const { stderr } = await execFileAsync(
throw new Error( "openssl",
"No signature was created. Make sure you are using an appropriate signing algorithm that uses the same hashing algorithm as the one used to create the digest." [
"pkeyutl",
"-sign",
"-in",
digestPath,
"-inkey",
keyPath,
"-pkeyopt",
`digest:${hashAlgorithm}`,
"-out",
sigPath
],
{
maxBuffer: 10 * 1024 * 1024,
timeout: COMMAND_TIMEOUT
}
); );
}
return Buffer.from(result.stdout.trim(), "base64"); if (stderr) {
logger.error(stderr, "KMS: Failed to sign RSA digest");
throw new BadRequestError({
message: "Failed to sign RSA digest due to signing error"
});
}
const signature = await fs.readFile(sigPath);
if (!signature) {
throw new BadRequestError({
message:
"No signature was created. Make sure you are using an appropriate signing algorithm that uses the same hashing algorithm as the one used to create the digest."
});
}
return signature;
} finally {
await fs.rm(tempDir, { recursive: true, force: true }).catch(() => {});
}
}; };
const $signEccDigest = async (digest: Buffer, privateKey: Buffer, hashAlgorithm: SupportedHashAlgorithm) => { const $signEccDigest = async (digest: Buffer, privateKey: Buffer, hashAlgorithm: SupportedHashAlgorithm) => {
const script = `openssl pkeyutl -sign -in <(base64 -d <<< "$DIGEST_B64") -inkey <(base64 -d <<< "$KEY_B64") -pkeyopt digest:"$HASH_ALG" | base64`; const tempDir = await makeTempDir("ecc-sign");
const digestPath = path.join(tempDir, "digest.bin");
const keyPath = path.join(tempDir, "private_key.pem");
const sigPath = path.join(tempDir, "signature.bin");
const result = await execFileAsync("bash", ["-c", script], { try {
encoding: "utf8", await fs.writeFile(digestPath, digest);
maxBuffer: 10 * 1024 * 1024, await fs.writeFile(keyPath, privateKey);
env: {
DIGEST_B64: digest.toString("base64"), const { stderr } = await execFileAsync(
KEY_B64: privateKey.toString("base64"), "openssl",
HASH_ALG: hashAlgorithm [
"pkeyutl",
"-sign",
"-in",
digestPath,
"-inkey",
keyPath,
"-pkeyopt",
`digest:${hashAlgorithm}`,
"-out",
sigPath
],
{
maxBuffer: 10 * 1024 * 1024,
timeout: COMMAND_TIMEOUT
}
);
if (stderr) {
logger.error(stderr, "KMS: Failed to sign ECC digest");
throw new BadRequestError({
message: "Failed to sign ECC digest due to signing error"
});
} }
});
if (result.stderr) { const signature = await fs.readFile(sigPath);
throw new Error(result.stderr);
if (!signature) {
throw new BadRequestError({
message:
"No signature was created. Make sure you are using an appropriate signing algorithm that uses the same hashing algorithm as the one used to create the digest."
});
}
return signature;
} finally {
await fs.rm(tempDir, { recursive: true, force: true }).catch(() => {});
} }
if (!result.stdout) {
throw new Error("No signature was created. Make sure you are using an appropriate ECC key and hash algorithm.");
}
return Buffer.from(result.stdout.trim(), "base64");
}; };
const $verifyEccDigest = async ( const $verifyEccDigest = async (
@@ -171,25 +232,16 @@ export const signingService = (algorithm: AsymmetricKeyAlgorithm): TAsymmetricSi
publicKey: Buffer, publicKey: Buffer,
hashAlgorithm: SupportedHashAlgorithm hashAlgorithm: SupportedHashAlgorithm
) => { ) => {
const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "ecc-signature-verification-")); const tempDir = await makeTempDir("ecc-signature-verification");
const pubKeyFile = path.join(tempDir, "public-key.pem"); const pubKeyFile = path.join(tempDir, "public-key.pem");
const sigFile = path.join(tempDir, "signature.sig"); const sigFile = path.join(tempDir, "signature.sig");
const digestFile = path.join(tempDir, "digest.bin"); const digestFile = path.join(tempDir, "digest.bin");
try { try {
// Write the necessary files
await fs.writeFile(pubKeyFile, publicKey, { mode: 0o600 }); await fs.writeFile(pubKeyFile, publicKey, { mode: 0o600 });
await fs.writeFile(sigFile, signature, { mode: 0o600 }); await fs.writeFile(sigFile, signature, { mode: 0o600 });
await fs.writeFile(digestFile, digest, { mode: 0o600 }); await fs.writeFile(digestFile, digest, { mode: 0o600 });
} catch {
await fs.rm(tempDir, { recursive: true, force: true }).catch(() => {});
throw new BadRequestError({
message: "Failed to verify ECC signature due to internal error."
});
}
try {
// Execute OpenSSL verification command
await execFileAsync( await execFileAsync(
"openssl", "openssl",
[ [
@@ -205,10 +257,9 @@ export const signingService = (algorithm: AsymmetricKeyAlgorithm): TAsymmetricSi
"-pkeyopt", "-pkeyopt",
`digest:${hashAlgorithm}` `digest:${hashAlgorithm}`
], ],
{ timeout: 15_000 } { timeout: COMMAND_TIMEOUT }
); );
// If we get here, verification succeeded
return true; return true;
} catch (error) { } catch (error) {
const err = error as { stderr: string }; const err = error as { stderr: string };
@@ -231,7 +282,7 @@ export const signingService = (algorithm: AsymmetricKeyAlgorithm): TAsymmetricSi
publicKey: Buffer, publicKey: Buffer,
hashAlgorithm: SupportedHashAlgorithm hashAlgorithm: SupportedHashAlgorithm
) => { ) => {
const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "kms-signature-verification-")); const tempDir = await makeTempDir("kms-signature-verification");
const publicKeyFile = path.join(tempDir, "public-key.pub"); const publicKeyFile = path.join(tempDir, "public-key.pub");
const signatureFile = path.join(tempDir, "signature.sig"); const signatureFile = path.join(tempDir, "signature.sig");
const digestFile = path.join(tempDir, "digest.bin"); const digestFile = path.join(tempDir, "digest.bin");
@@ -240,14 +291,7 @@ export const signingService = (algorithm: AsymmetricKeyAlgorithm): TAsymmetricSi
await fs.writeFile(publicKeyFile, publicKey, { mode: 0o600 }); await fs.writeFile(publicKeyFile, publicKey, { mode: 0o600 });
await fs.writeFile(signatureFile, signature, { mode: 0o600 }); await fs.writeFile(signatureFile, signature, { mode: 0o600 });
await fs.writeFile(digestFile, digest, { mode: 0o600 }); await fs.writeFile(digestFile, digest, { mode: 0o600 });
} catch {
await fs.rm(tempDir, { recursive: true, force: true }).catch(() => {});
throw new BadRequestError({
message: "Failed to verify RSA signature due to internal error."
});
}
try {
await execFileAsync( await execFileAsync(
"openssl", "openssl",
[ [
@@ -263,7 +307,7 @@ export const signingService = (algorithm: AsymmetricKeyAlgorithm): TAsymmetricSi
"-pkeyopt", "-pkeyopt",
`digest:${hashAlgorithm}` `digest:${hashAlgorithm}`
], ],
{ timeout: 15_000 } { timeout: COMMAND_TIMEOUT }
); );
// it'll throw if the verification was not successful // it'll throw if the verification was not successful
+5 -34
View File
@@ -412,7 +412,8 @@ export const registerCmekRouter = async (server: FastifyZodProvider) => {
rateLimit: readLimit rateLimit: readLimit
}, },
schema: { schema: {
description: "Get the public key for a KMS key that is used for signing and verifying data.", description:
"Get the public key for a KMS key that is used for signing and verifying data. This endpoint is only available for asymmetric keys.",
params: z.object({ params: z.object({
keyId: z.string().uuid().describe(KMS.GET_PUBLIC_KEY.keyId) keyId: z.string().uuid().describe(KMS.GET_PUBLIC_KEY.keyId)
}), }),
@@ -501,17 +502,7 @@ export const registerCmekRouter = async (server: FastifyZodProvider) => {
body: z.object({ body: z.object({
signingAlgorithm: z.nativeEnum(SigningAlgorithm), signingAlgorithm: z.nativeEnum(SigningAlgorithm),
isDigest: z.boolean().optional().default(false).describe(KMS.SIGN.isDigest), isDigest: z.boolean().optional().default(false).describe(KMS.SIGN.isDigest),
data: z data: base64Schema.describe(KMS.SIGN.data)
.string()
.superRefine((data, ctx) => {
if (!isBase64(data)) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: "data must be base64 encoded"
});
}
})
.describe(KMS.SIGN.data)
}), }),
response: { response: {
200: z.object({ 200: z.object({
@@ -563,28 +554,8 @@ export const registerCmekRouter = async (server: FastifyZodProvider) => {
}), }),
body: z.object({ body: z.object({
isDigest: z.boolean().optional().default(false).describe(KMS.VERIFY.isDigest), isDigest: z.boolean().optional().default(false).describe(KMS.VERIFY.isDigest),
data: z data: base64Schema.describe(KMS.VERIFY.data),
.string() signature: base64Schema.describe(KMS.VERIFY.signature),
.superRefine((data, ctx) => {
if (!isBase64(data)) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: "data must be base64 encoded"
});
}
})
.describe(KMS.VERIFY.data),
signature: z
.string()
.superRefine((data, ctx) => {
if (!isBase64(data)) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: "signature must be base64 encoded"
});
}
})
.describe(KMS.VERIFY.signature),
signingAlgorithm: z.nativeEnum(SigningAlgorithm) signingAlgorithm: z.nativeEnum(SigningAlgorithm)
}), }),
response: { response: {