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
+95 -51
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");
const sigPath = path.join(tempDir, "signature.bin");
try {
await fs.writeFile(digestPath, digest, { mode: 0o600 });
await fs.writeFile(keyPath, privateKey, { mode: 0o600 });
const { stderr } = await execFileAsync(
"openssl",
[
"pkeyutl",
"-sign",
"-in",
digestPath,
"-inkey",
keyPath,
"-pkeyopt",
`digest:${hashAlgorithm}`,
"-out",
sigPath
],
{
maxBuffer: 10 * 1024 * 1024, maxBuffer: 10 * 1024 * 1024,
env: { timeout: COMMAND_TIMEOUT
DIGEST_B64: digest.toString("base64"),
KEY_B64: privateKey.toString("base64"),
HASH_ALG: hashAlgorithm
} }
});
if (result.stderr) {
throw new Error(result.stderr);
}
if (!result.stdout) {
throw new Error(
"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."
); );
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 Buffer.from(result.stdout.trim(), "base64"); 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);
await fs.writeFile(keyPath, privateKey);
const { stderr } = await execFileAsync(
"openssl",
[
"pkeyutl",
"-sign",
"-in",
digestPath,
"-inkey",
keyPath,
"-pkeyopt",
`digest:${hashAlgorithm}`,
"-out",
sigPath
],
{
maxBuffer: 10 * 1024 * 1024, maxBuffer: 10 * 1024 * 1024,
env: { timeout: COMMAND_TIMEOUT
DIGEST_B64: digest.toString("base64"),
KEY_B64: privateKey.toString("base64"),
HASH_ALG: hashAlgorithm
} }
);
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) {
throw new Error(result.stderr);
} }
if (!result.stdout) { const signature = await fs.readFile(sigPath);
throw new Error("No signature was created. Make sure you are using an appropriate ECC key and hash algorithm.");
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 Buffer.from(result.stdout.trim(), "base64"); return signature;
} finally {
await fs.rm(tempDir, { recursive: true, force: true }).catch(() => {});
}
}; };
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: {