mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-08 11:27:47 +00:00
fix: fixed local verification & added digest support
This commit is contained in:
+2
-1
@@ -8,7 +8,8 @@ RUN apt-get update && apt-get install -y \
|
|||||||
python3 \
|
python3 \
|
||||||
make \
|
make \
|
||||||
g++ \
|
g++ \
|
||||||
openssh-client
|
openssh-client \
|
||||||
|
openssl
|
||||||
|
|
||||||
# Install dependencies for TDS driver (required for SAP ASE dynamic secrets)
|
# Install dependencies for TDS driver (required for SAP ASE dynamic secrets)
|
||||||
RUN apt-get install -y \
|
RUN apt-get install -y \
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ RUN apt-get update && apt-get install -y \
|
|||||||
make \
|
make \
|
||||||
g++ \
|
g++ \
|
||||||
openssh-client \
|
openssh-client \
|
||||||
|
openssl \
|
||||||
curl \
|
curl \
|
||||||
pkg-config
|
pkg-config
|
||||||
|
|
||||||
|
|||||||
@@ -1637,12 +1637,16 @@ export const KMS = {
|
|||||||
|
|
||||||
SIGN: {
|
SIGN: {
|
||||||
keyId: "The ID of the key to sign the data with.",
|
keyId: "The ID of the key to sign the data with.",
|
||||||
data: "The data in string format to be signed (base64 encoded)."
|
data: "The data in string format to be signed (base64 encoded).",
|
||||||
|
isDigest:
|
||||||
|
"Whether the data is already digested or not. Please be aware that if you are passing a digest the algorithm used to create the digest must match the signing algorithm used to sign the digest.",
|
||||||
|
signingAlgorithm: "The algorithm to use when performing cryptographic operations with the key."
|
||||||
},
|
},
|
||||||
VERIFY: {
|
VERIFY: {
|
||||||
keyId: "The ID of the key to verify the data with.",
|
keyId: "The ID of the key to verify the data with.",
|
||||||
data: "The data in string format to be verified (base64 encoded). For data larger than 4096 bytes you must first create a digest of the data and then pass the digest in the data parameter.",
|
data: "The data in string format to be verified (base64 encoded). For data larger than 4096 bytes you must first create a digest of the data and then pass the digest in the data parameter.",
|
||||||
signature: "The signature to be verified (base64 encoded)."
|
signature: "The signature to be verified (base64 encoded).",
|
||||||
|
isDigest: "Whether the data is already digested or not."
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,17 +1,29 @@
|
|||||||
|
import { execFile } from "child_process";
|
||||||
import crypto from "crypto";
|
import crypto from "crypto";
|
||||||
|
import fs from "fs/promises";
|
||||||
|
import os from "os";
|
||||||
|
import path from "path";
|
||||||
|
import { promisify } from "util";
|
||||||
|
|
||||||
import { BadRequestError } from "@app/lib/errors";
|
import { BadRequestError } from "@app/lib/errors";
|
||||||
import { logger } from "@app/lib/logger";
|
import { logger } from "@app/lib/logger";
|
||||||
|
|
||||||
import { AsymmetricKeyAlgorithm, SigningAlgorithm, TAsymmetricSignVerifyFns } from "./types";
|
import { AsymmetricKeyAlgorithm, SigningAlgorithm, TAsymmetricSignVerifyFns } from "./types";
|
||||||
|
|
||||||
// Map of signing algorithms to their parameters
|
const execFileAsync = promisify(execFile);
|
||||||
|
|
||||||
interface SigningParams {
|
interface SigningParams {
|
||||||
hashAlgorithm: string;
|
hashAlgorithm: SupportedHashAlgorithm;
|
||||||
padding?: number; // Will use crypto.constants values
|
padding?: number;
|
||||||
saltLength?: number;
|
saltLength?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum SupportedHashAlgorithm {
|
||||||
|
SHA256 = "sha256",
|
||||||
|
SHA384 = "sha384",
|
||||||
|
SHA512 = "sha512"
|
||||||
|
}
|
||||||
|
|
||||||
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;
|
||||||
@@ -19,76 +31,78 @@ const SHA512_DIGEST_LENGTH = 64;
|
|||||||
/**
|
/**
|
||||||
* Service for cryptographic signing and verification operations using asymmetric keys
|
* Service for cryptographic signing and verification operations using asymmetric keys
|
||||||
*
|
*
|
||||||
* @param algorithm The signing algorithm to use
|
* @param algorithm The key algorithm itself. The signing algorithm is supplied in the individual sign/verify functions.
|
||||||
* @returns Object with sign and verify functions
|
* @returns Object with sign and verify functions
|
||||||
*/
|
*/
|
||||||
export const signingService = (algorithm: AsymmetricKeyAlgorithm): TAsymmetricSignVerifyFns => {
|
export const signingService = (algorithm: AsymmetricKeyAlgorithm): TAsymmetricSignVerifyFns => {
|
||||||
const $getSigningParams = (signingAlgorithm: SigningAlgorithm): SigningParams => {
|
const $getSigningParams = (signingAlgorithm: SigningAlgorithm): SigningParams => {
|
||||||
switch (signingAlgorithm) {
|
switch (signingAlgorithm) {
|
||||||
// RSA PSS
|
// RSA PSS
|
||||||
|
case SigningAlgorithm.RSASSA_PSS_SHA_512:
|
||||||
|
return {
|
||||||
|
hashAlgorithm: SupportedHashAlgorithm.SHA512,
|
||||||
|
padding: crypto.constants.RSA_PKCS1_PSS_PADDING,
|
||||||
|
saltLength: SHA512_DIGEST_LENGTH
|
||||||
|
};
|
||||||
case SigningAlgorithm.RSASSA_PSS_SHA_256:
|
case SigningAlgorithm.RSASSA_PSS_SHA_256:
|
||||||
return {
|
return {
|
||||||
hashAlgorithm: "sha256",
|
hashAlgorithm: SupportedHashAlgorithm.SHA256,
|
||||||
padding: crypto.constants.RSA_PKCS1_PSS_PADDING,
|
padding: crypto.constants.RSA_PKCS1_PSS_PADDING,
|
||||||
saltLength: SHA256_DIGEST_LENGTH
|
saltLength: SHA256_DIGEST_LENGTH
|
||||||
};
|
};
|
||||||
case SigningAlgorithm.RSASSA_PSS_SHA_384:
|
case SigningAlgorithm.RSASSA_PSS_SHA_384:
|
||||||
return {
|
return {
|
||||||
hashAlgorithm: "sha384",
|
hashAlgorithm: SupportedHashAlgorithm.SHA384,
|
||||||
padding: crypto.constants.RSA_PKCS1_PSS_PADDING,
|
padding: crypto.constants.RSA_PKCS1_PSS_PADDING,
|
||||||
saltLength: SHA384_DIGEST_LENGTH
|
saltLength: SHA384_DIGEST_LENGTH
|
||||||
};
|
};
|
||||||
case SigningAlgorithm.RSASSA_PSS_SHA_512:
|
|
||||||
return {
|
|
||||||
hashAlgorithm: "sha512",
|
|
||||||
padding: crypto.constants.RSA_PKCS1_PSS_PADDING,
|
|
||||||
saltLength: SHA512_DIGEST_LENGTH
|
|
||||||
};
|
|
||||||
|
|
||||||
// RSA PKCS#1 v1.5
|
// RSA PKCS#1 v1.5
|
||||||
case SigningAlgorithm.RSASSA_PKCS1_V1_5_SHA_256:
|
case SigningAlgorithm.RSASSA_PKCS1_V1_5_SHA_512:
|
||||||
return {
|
return {
|
||||||
hashAlgorithm: "sha256",
|
hashAlgorithm: SupportedHashAlgorithm.SHA512,
|
||||||
padding: crypto.constants.RSA_PKCS1_PADDING
|
padding: crypto.constants.RSA_PKCS1_PADDING
|
||||||
};
|
};
|
||||||
case SigningAlgorithm.RSASSA_PKCS1_V1_5_SHA_384:
|
case SigningAlgorithm.RSASSA_PKCS1_V1_5_SHA_384:
|
||||||
return {
|
return {
|
||||||
hashAlgorithm: "sha384",
|
hashAlgorithm: SupportedHashAlgorithm.SHA384,
|
||||||
padding: crypto.constants.RSA_PKCS1_PADDING
|
padding: crypto.constants.RSA_PKCS1_PADDING
|
||||||
};
|
};
|
||||||
case SigningAlgorithm.RSASSA_PKCS1_V1_5_SHA_512:
|
case SigningAlgorithm.RSASSA_PKCS1_V1_5_SHA_256:
|
||||||
return {
|
return {
|
||||||
hashAlgorithm: "sha512",
|
hashAlgorithm: SupportedHashAlgorithm.SHA256,
|
||||||
padding: crypto.constants.RSA_PKCS1_PADDING
|
padding: crypto.constants.RSA_PKCS1_PADDING
|
||||||
};
|
};
|
||||||
|
|
||||||
// ECDSA
|
// ECDSA
|
||||||
case SigningAlgorithm.ECDSA_SHA_256:
|
case SigningAlgorithm.ECDSA_SHA_256:
|
||||||
return { hashAlgorithm: "sha256" };
|
return { hashAlgorithm: SupportedHashAlgorithm.SHA256 };
|
||||||
case SigningAlgorithm.ECDSA_SHA_384:
|
case SigningAlgorithm.ECDSA_SHA_384:
|
||||||
return { hashAlgorithm: "sha384" };
|
return { hashAlgorithm: SupportedHashAlgorithm.SHA384 };
|
||||||
case SigningAlgorithm.ECDSA_SHA_512:
|
case SigningAlgorithm.ECDSA_SHA_512:
|
||||||
return { hashAlgorithm: "sha512" };
|
return { hashAlgorithm: SupportedHashAlgorithm.SHA512 };
|
||||||
|
|
||||||
default:
|
default:
|
||||||
throw new Error(`Unsupported signing algorithm: ${signingAlgorithm as string}`);
|
throw new Error(`Unsupported signing algorithm: ${signingAlgorithm as string}`);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// For ECC key generation, nodejs has some strange and hardly documented curve naming conventions
|
const $getEcCurveName = (keyAlgorithm: AsymmetricKeyAlgorithm): { full: string; short: string } => {
|
||||||
const $getEcCurveName = (keyAlgorithm: AsymmetricKeyAlgorithm): string => {
|
|
||||||
// We will support more in the future
|
// We will support more in the future
|
||||||
switch (keyAlgorithm) {
|
switch (keyAlgorithm) {
|
||||||
case AsymmetricKeyAlgorithm.ECC_NIST_P256:
|
case AsymmetricKeyAlgorithm.ECC_NIST_P256:
|
||||||
return "prime256v1";
|
return {
|
||||||
|
full: "prime256v1",
|
||||||
|
short: "p256"
|
||||||
|
};
|
||||||
default:
|
default:
|
||||||
throw new Error(`Unsupported EC curve: ${keyAlgorithm}`);
|
throw new Error(`Unsupported EC curve: ${keyAlgorithm}`);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const $validateAlgorithmWithKeyType = (signingAlgorithm: SigningAlgorithm) => {
|
const $validateAlgorithmWithKeyType = (signingAlgorithm: SigningAlgorithm) => {
|
||||||
const isRsaKey = algorithm.startsWith("rsa");
|
const isRsaKey = algorithm.startsWith("RSA");
|
||||||
const isEccKey = algorithm.startsWith("ecc");
|
const isEccKey = algorithm.startsWith("ECC");
|
||||||
|
|
||||||
const isRsaAlgorithm = signingAlgorithm.startsWith("RSASSA");
|
const isRsaAlgorithm = signingAlgorithm.startsWith("RSASSA");
|
||||||
const isEccAlgorithm = signingAlgorithm.startsWith("ECDSA");
|
const isEccAlgorithm = signingAlgorithm.startsWith("ECDSA");
|
||||||
@@ -102,13 +116,329 @@ export const signingService = (algorithm: AsymmetricKeyAlgorithm): TAsymmetricSi
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
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 result = await execFileAsync("bash", ["-c", script], {
|
||||||
|
encoding: "utf8",
|
||||||
|
maxBuffer: 10 * 1024 * 1024,
|
||||||
|
env: {
|
||||||
|
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 appropiate signing algorithm that uses the same hashing algorithm as the one used to create the digest."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Buffer.from(result.stdout.trim(), "base64");
|
||||||
|
};
|
||||||
|
|
||||||
|
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 result = await execFileAsync("bash", ["-c", script], {
|
||||||
|
encoding: "utf8",
|
||||||
|
maxBuffer: 10 * 1024 * 1024,
|
||||||
|
env: {
|
||||||
|
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 ECC key and hash algorithm.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return Buffer.from(result.stdout.trim(), "base64");
|
||||||
|
};
|
||||||
|
|
||||||
|
const $verifyEccDigest = async (
|
||||||
|
digest: Buffer,
|
||||||
|
signature: Buffer,
|
||||||
|
publicKey: Buffer,
|
||||||
|
hashAlgorithm: SupportedHashAlgorithm
|
||||||
|
) => {
|
||||||
|
const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "ecc-signature-verification-"));
|
||||||
|
const pubKeyFile = path.join(tempDir, "public-key.pem");
|
||||||
|
const sigFile = path.join(tempDir, "signature.sig");
|
||||||
|
const digestFile = path.join(tempDir, "digest.bin");
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Write the necessary files
|
||||||
|
await fs.writeFile(pubKeyFile, publicKey, { mode: 0o600 });
|
||||||
|
await fs.writeFile(sigFile, signature, { 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(
|
||||||
|
"openssl",
|
||||||
|
[
|
||||||
|
"pkeyutl",
|
||||||
|
"-verify",
|
||||||
|
"-in",
|
||||||
|
digestFile,
|
||||||
|
"-inkey",
|
||||||
|
pubKeyFile,
|
||||||
|
"-pubin", // Important for EC public keys
|
||||||
|
"-sigfile",
|
||||||
|
sigFile,
|
||||||
|
"-pkeyopt",
|
||||||
|
`digest:${hashAlgorithm}`
|
||||||
|
],
|
||||||
|
{ timeout: 15_000 }
|
||||||
|
);
|
||||||
|
|
||||||
|
// If we get here, verification succeeded
|
||||||
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
const err = error as { stderr: string };
|
||||||
|
|
||||||
|
if (
|
||||||
|
!err?.stderr?.toLowerCase()?.includes("signature verification failure") &&
|
||||||
|
!err?.stderr?.toLowerCase()?.includes("bad signature")
|
||||||
|
) {
|
||||||
|
logger.error(error, "KMS: Failed to verify ECC signature");
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
} finally {
|
||||||
|
await fs.rm(tempDir, { recursive: true, force: true }).catch(() => {});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const $verifyRsaDigest = async (
|
||||||
|
digest: Buffer,
|
||||||
|
signature: Buffer,
|
||||||
|
publicKey: Buffer,
|
||||||
|
hashAlgorithm: SupportedHashAlgorithm
|
||||||
|
) => {
|
||||||
|
const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "kms-signature-verification-"));
|
||||||
|
const publicKeyFile = path.join(tempDir, "public-key.pub");
|
||||||
|
const signatureFile = path.join(tempDir, "signature.sig");
|
||||||
|
const digestFile = path.join(tempDir, "digest.bin");
|
||||||
|
|
||||||
|
try {
|
||||||
|
await fs.writeFile(publicKeyFile, publicKey, { mode: 0o600 });
|
||||||
|
await fs.writeFile(signatureFile, signature, { 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(
|
||||||
|
"openssl",
|
||||||
|
[
|
||||||
|
"pkeyutl",
|
||||||
|
"-verify",
|
||||||
|
"-in",
|
||||||
|
digestFile,
|
||||||
|
"-inkey",
|
||||||
|
publicKeyFile,
|
||||||
|
"-pubin",
|
||||||
|
"-sigfile",
|
||||||
|
signatureFile,
|
||||||
|
"-pkeyopt",
|
||||||
|
`digest:${hashAlgorithm}`
|
||||||
|
],
|
||||||
|
{ timeout: 15_000 }
|
||||||
|
);
|
||||||
|
|
||||||
|
// it'll throw if the verification was not successful
|
||||||
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
const err = error as { stdout: string };
|
||||||
|
|
||||||
|
if (!err?.stdout?.toLowerCase()?.includes("signature verification failure")) {
|
||||||
|
logger.error(error, "KMS: Failed to verify signature");
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
} finally {
|
||||||
|
await fs.rm(tempDir, { recursive: true, force: true }).catch(() => {});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const verifyDigestFunctionsMap: Record<
|
||||||
|
AsymmetricKeyAlgorithm,
|
||||||
|
(data: Buffer, signature: Buffer, publicKey: Buffer, hashAlgorithm: SupportedHashAlgorithm) => Promise<boolean>
|
||||||
|
> = {
|
||||||
|
[AsymmetricKeyAlgorithm.ECC_NIST_P256]: $verifyEccDigest,
|
||||||
|
[AsymmetricKeyAlgorithm.RSA_4096]: $verifyRsaDigest
|
||||||
|
};
|
||||||
|
|
||||||
|
const signDigestFunctionsMap: Record<
|
||||||
|
AsymmetricKeyAlgorithm,
|
||||||
|
(data: Buffer, privateKey: Buffer, hashAlgorithm: SupportedHashAlgorithm) => Promise<Buffer>
|
||||||
|
> = {
|
||||||
|
[AsymmetricKeyAlgorithm.ECC_NIST_P256]: $signEccDigest,
|
||||||
|
[AsymmetricKeyAlgorithm.RSA_4096]: $signRsaDigest
|
||||||
|
};
|
||||||
|
|
||||||
|
const sign = async (
|
||||||
|
data: Buffer,
|
||||||
|
privateKey: Buffer,
|
||||||
|
signingAlgorithm: SigningAlgorithm,
|
||||||
|
isDigest: boolean
|
||||||
|
): Promise<Buffer> => {
|
||||||
|
$validateAlgorithmWithKeyType(signingAlgorithm);
|
||||||
|
|
||||||
|
const { hashAlgorithm, padding, saltLength } = $getSigningParams(signingAlgorithm);
|
||||||
|
|
||||||
|
if (isDigest) {
|
||||||
|
if (signingAlgorithm.startsWith("RSASSA_PSS")) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: "RSA PSS does not support digested input"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const signFunction = signDigestFunctionsMap[algorithm];
|
||||||
|
|
||||||
|
if (!signFunction) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: `Digested input is not supported for key algorithm ${algorithm}`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const signature = await signFunction(data, privateKey, hashAlgorithm);
|
||||||
|
return signature;
|
||||||
|
}
|
||||||
|
|
||||||
|
const privateKeyObject = crypto.createPrivateKey({
|
||||||
|
key: privateKey,
|
||||||
|
format: "pem",
|
||||||
|
type: "pkcs8"
|
||||||
|
});
|
||||||
|
|
||||||
|
// For RSA signatures
|
||||||
|
if (signingAlgorithm.startsWith("RSA")) {
|
||||||
|
const signer = crypto.createSign(hashAlgorithm);
|
||||||
|
signer.update(data);
|
||||||
|
|
||||||
|
return signer.sign({
|
||||||
|
key: privateKeyObject,
|
||||||
|
padding,
|
||||||
|
...(signingAlgorithm.includes("PSS") ? { saltLength } : {})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (signingAlgorithm.startsWith("ECDSA")) {
|
||||||
|
// For ECDSA signatures
|
||||||
|
const signer = crypto.createSign(hashAlgorithm);
|
||||||
|
signer.update(data);
|
||||||
|
return signer.sign({
|
||||||
|
key: privateKeyObject,
|
||||||
|
dsaEncoding: "der"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: `Signing algorithm ${signingAlgorithm} not implemented`
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const verify = async (
|
||||||
|
data: Buffer,
|
||||||
|
signature: Buffer,
|
||||||
|
publicKey: Buffer,
|
||||||
|
signingAlgorithm: SigningAlgorithm,
|
||||||
|
isDigest: boolean
|
||||||
|
): Promise<boolean> => {
|
||||||
|
try {
|
||||||
|
$validateAlgorithmWithKeyType(signingAlgorithm);
|
||||||
|
|
||||||
|
const { hashAlgorithm, padding, saltLength } = $getSigningParams(signingAlgorithm);
|
||||||
|
|
||||||
|
if (isDigest) {
|
||||||
|
if (signingAlgorithm.startsWith("RSASSA_PSS")) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: "RSA PSS does not support digested input"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const verifyFunction = verifyDigestFunctionsMap[algorithm];
|
||||||
|
|
||||||
|
if (!verifyFunction) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: `Digested input is not supported for key algorithm ${algorithm}`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const signatureValid = await verifyFunction(data, signature, publicKey, hashAlgorithm);
|
||||||
|
|
||||||
|
return signatureValid;
|
||||||
|
}
|
||||||
|
|
||||||
|
const publicKeyObject = crypto.createPublicKey({
|
||||||
|
key: publicKey,
|
||||||
|
format: "der",
|
||||||
|
type: "spki"
|
||||||
|
});
|
||||||
|
|
||||||
|
// For RSA signatures
|
||||||
|
if (signingAlgorithm.startsWith("RSA")) {
|
||||||
|
const verifier = crypto.createVerify(hashAlgorithm);
|
||||||
|
verifier.update(data);
|
||||||
|
|
||||||
|
return verifier.verify(
|
||||||
|
{
|
||||||
|
key: publicKeyObject,
|
||||||
|
padding,
|
||||||
|
...(signingAlgorithm.includes("PSS") ? { saltLength } : {})
|
||||||
|
},
|
||||||
|
signature
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// For ECDSA signatures
|
||||||
|
if (signingAlgorithm.startsWith("ECDSA")) {
|
||||||
|
const verifier = crypto.createVerify(hashAlgorithm);
|
||||||
|
verifier.update(data);
|
||||||
|
return verifier.verify(
|
||||||
|
{
|
||||||
|
key: publicKeyObject,
|
||||||
|
dsaEncoding: "der"
|
||||||
|
},
|
||||||
|
signature
|
||||||
|
);
|
||||||
|
}
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: `Verification for algorithm ${signingAlgorithm} not implemented`
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof BadRequestError) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
logger.error(error, "KMS: Failed to verify signature");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const generateAsymmetricPrivateKey = async () => {
|
const generateAsymmetricPrivateKey = async () => {
|
||||||
const { privateKey } = await new Promise<{ privateKey: string }>((resolve, reject) => {
|
const { privateKey } = await new Promise<{ privateKey: string }>((resolve, reject) => {
|
||||||
if (algorithm.startsWith("rsa")) {
|
if (algorithm.startsWith("RSA")) {
|
||||||
crypto.generateKeyPair(
|
crypto.generateKeyPair(
|
||||||
"rsa",
|
"rsa",
|
||||||
{
|
{
|
||||||
modulusLength: Number(algorithm.split("-")[1]),
|
modulusLength: Number(algorithm.split("_")[1]),
|
||||||
publicKeyEncoding: { type: "spki", format: "pem" },
|
publicKeyEncoding: { type: "spki", format: "pem" },
|
||||||
privateKeyEncoding: { type: "pkcs8", format: "pem" }
|
privateKeyEncoding: { type: "pkcs8", format: "pem" }
|
||||||
},
|
},
|
||||||
@@ -121,7 +451,7 @@ export const signingService = (algorithm: AsymmetricKeyAlgorithm): TAsymmetricSi
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
const namedCurve = $getEcCurveName(algorithm);
|
const { full: namedCurve } = $getEcCurveName(algorithm);
|
||||||
|
|
||||||
crypto.generateKeyPair(
|
crypto.generateKeyPair(
|
||||||
"ec",
|
"ec",
|
||||||
@@ -147,25 +477,6 @@ export const signingService = (algorithm: AsymmetricKeyAlgorithm): TAsymmetricSi
|
|||||||
};
|
};
|
||||||
|
|
||||||
const getPublicKeyFromPrivateKey = (privateKey: Buffer) => {
|
const getPublicKeyFromPrivateKey = (privateKey: Buffer) => {
|
||||||
if (algorithm.startsWith("rsa")) {
|
|
||||||
// For RSA keys in PEM format
|
|
||||||
const privateKeyObj = crypto.createPrivateKey({
|
|
||||||
key: privateKey,
|
|
||||||
format: "pem",
|
|
||||||
type: "pkcs8"
|
|
||||||
});
|
|
||||||
|
|
||||||
const publicKey = crypto.createPublicKey(privateKeyObj).export({
|
|
||||||
type: "spki",
|
|
||||||
format: "pem"
|
|
||||||
});
|
|
||||||
|
|
||||||
if (Buffer.isBuffer(publicKey)) {
|
|
||||||
return publicKey;
|
|
||||||
}
|
|
||||||
return Buffer.from(publicKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
const privateKeyObj = crypto.createPrivateKey({
|
const privateKeyObj = crypto.createPrivateKey({
|
||||||
key: privateKey,
|
key: privateKey,
|
||||||
format: "pem",
|
format: "pem",
|
||||||
@@ -174,109 +485,10 @@ export const signingService = (algorithm: AsymmetricKeyAlgorithm): TAsymmetricSi
|
|||||||
|
|
||||||
const publicKey = crypto.createPublicKey(privateKeyObj).export({
|
const publicKey = crypto.createPublicKey(privateKeyObj).export({
|
||||||
type: "spki",
|
type: "spki",
|
||||||
format: "pem"
|
format: "der"
|
||||||
});
|
});
|
||||||
|
|
||||||
if (Buffer.isBuffer(publicKey)) {
|
return publicKey;
|
||||||
return publicKey;
|
|
||||||
}
|
|
||||||
return Buffer.from(publicKey);
|
|
||||||
};
|
|
||||||
|
|
||||||
const sign = (data: Buffer, privateKey: Buffer, signingAlgorithm: SigningAlgorithm): Buffer => {
|
|
||||||
$validateAlgorithmWithKeyType(signingAlgorithm);
|
|
||||||
|
|
||||||
const { hashAlgorithm, padding, saltLength } = $getSigningParams(signingAlgorithm);
|
|
||||||
|
|
||||||
const privateKeyObject = crypto.createPrivateKey({
|
|
||||||
key: privateKey,
|
|
||||||
format: "pem",
|
|
||||||
type: "pkcs8"
|
|
||||||
});
|
|
||||||
|
|
||||||
// For RSA signatures
|
|
||||||
if (signingAlgorithm.startsWith("RSASSA")) {
|
|
||||||
const signer = crypto.createSign(hashAlgorithm);
|
|
||||||
signer.update(data);
|
|
||||||
|
|
||||||
if (signingAlgorithm.includes("PSS")) {
|
|
||||||
// For PSS padding
|
|
||||||
return signer.sign({
|
|
||||||
key: privateKeyObject,
|
|
||||||
padding,
|
|
||||||
saltLength
|
|
||||||
});
|
|
||||||
}
|
|
||||||
// For PKCS1 v1.5 padding
|
|
||||||
return signer.sign({
|
|
||||||
key: privateKeyObject,
|
|
||||||
padding
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (signingAlgorithm.startsWith("ECDSA")) {
|
|
||||||
// For ECDSA signatures
|
|
||||||
const signer = crypto.createSign(hashAlgorithm);
|
|
||||||
signer.update(data);
|
|
||||||
return signer.sign({
|
|
||||||
key: privateKeyObject,
|
|
||||||
dsaEncoding: "ieee-p1363" // Based on AWS KMS implementation, where ECDSA signatures follow the ANSI X9.62-2005 format, which is equivalent to the IEEE-P1363 format
|
|
||||||
});
|
|
||||||
}
|
|
||||||
throw new BadRequestError({
|
|
||||||
message: `Signing algorithm ${signingAlgorithm} not implemented`
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const verify = (data: Buffer, signature: Buffer, publicKey: Buffer, signingAlgorithm: SigningAlgorithm): boolean => {
|
|
||||||
try {
|
|
||||||
$validateAlgorithmWithKeyType(signingAlgorithm);
|
|
||||||
|
|
||||||
const { hashAlgorithm, padding, saltLength } = $getSigningParams(signingAlgorithm);
|
|
||||||
|
|
||||||
// For RSA signatures
|
|
||||||
if (signingAlgorithm.startsWith("RSASSA")) {
|
|
||||||
const verifier = crypto.createVerify(hashAlgorithm);
|
|
||||||
verifier.update(data);
|
|
||||||
|
|
||||||
if (signingAlgorithm.includes("PSS")) {
|
|
||||||
// For PSS padding
|
|
||||||
return verifier.verify(
|
|
||||||
{
|
|
||||||
key: publicKey.toString(),
|
|
||||||
padding,
|
|
||||||
saltLength
|
|
||||||
},
|
|
||||||
signature
|
|
||||||
);
|
|
||||||
}
|
|
||||||
// For PKCS1 v1.5 padding
|
|
||||||
return verifier.verify(
|
|
||||||
{
|
|
||||||
key: publicKey.toString(),
|
|
||||||
padding
|
|
||||||
},
|
|
||||||
signature
|
|
||||||
);
|
|
||||||
}
|
|
||||||
// For ECDSA signatures
|
|
||||||
if (signingAlgorithm.startsWith("ECDSA")) {
|
|
||||||
const verifier = crypto.createVerify(hashAlgorithm);
|
|
||||||
verifier.update(data);
|
|
||||||
return verifier.verify(
|
|
||||||
{
|
|
||||||
key: publicKey.toString(),
|
|
||||||
dsaEncoding: "ieee-p1363"
|
|
||||||
},
|
|
||||||
signature
|
|
||||||
);
|
|
||||||
}
|
|
||||||
throw new BadRequestError({
|
|
||||||
message: `Verification for algorithm ${signingAlgorithm} not implemented`
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
logger.error(error, "KMS: Failed to verify signature");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -1,16 +1,22 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
export type TAsymmetricSignVerifyFns = {
|
export type TAsymmetricSignVerifyFns = {
|
||||||
sign: (data: Buffer, key: Buffer, signingAlgorithm: SigningAlgorithm) => Buffer;
|
sign: (data: Buffer, key: Buffer, signingAlgorithm: SigningAlgorithm, isDigest: boolean) => Promise<Buffer>;
|
||||||
verify: (data: Buffer, signature: Buffer, key: Buffer, signingAlgorithm: SigningAlgorithm) => boolean;
|
verify: (
|
||||||
|
data: Buffer,
|
||||||
|
signature: Buffer,
|
||||||
|
key: Buffer,
|
||||||
|
signingAlgorithm: SigningAlgorithm,
|
||||||
|
isDigest: boolean
|
||||||
|
) => Promise<boolean>;
|
||||||
generateAsymmetricPrivateKey: () => Promise<Buffer>;
|
generateAsymmetricPrivateKey: () => Promise<Buffer>;
|
||||||
getPublicKeyFromPrivateKey: (privateKey: Buffer) => Buffer;
|
getPublicKeyFromPrivateKey: (privateKey: Buffer) => Buffer;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Supported asymmetric key types
|
// Supported asymmetric key types
|
||||||
export enum AsymmetricKeyAlgorithm {
|
export enum AsymmetricKeyAlgorithm {
|
||||||
RSA_4096 = "rsa-4096",
|
RSA_4096 = "RSA_4096",
|
||||||
ECC_NIST_P256 = "ecc-nist-p256"
|
ECC_NIST_P256 = "ECC_NIST_P256"
|
||||||
}
|
}
|
||||||
|
|
||||||
export const AsymmetricKeyAlgorithmEnum = z.enum(
|
export const AsymmetricKeyAlgorithmEnum = z.enum(
|
||||||
|
|||||||
@@ -321,7 +321,7 @@ export const registerCmekRouter = async (server: FastifyZodProvider) => {
|
|||||||
rateLimit: readLimit
|
rateLimit: readLimit
|
||||||
},
|
},
|
||||||
schema: {
|
schema: {
|
||||||
description: "Get KMS key by Name",
|
description: "Get KMS key by name",
|
||||||
params: z.object({
|
params: z.object({
|
||||||
keyName: slugSchema({ field: "Key name" }).describe(KMS.GET_KEY_BY_NAME.keyName)
|
keyName: slugSchema({ field: "Key name" }).describe(KMS.GET_KEY_BY_NAME.keyName)
|
||||||
}),
|
}),
|
||||||
@@ -500,6 +500,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),
|
||||||
data: z
|
data: z
|
||||||
.string()
|
.string()
|
||||||
.superRefine((data, ctx) => {
|
.superRefine((data, ctx) => {
|
||||||
@@ -524,12 +525,12 @@ export const registerCmekRouter = async (server: FastifyZodProvider) => {
|
|||||||
handler: async (req) => {
|
handler: async (req) => {
|
||||||
const {
|
const {
|
||||||
params: { keyId: inputKeyId },
|
params: { keyId: inputKeyId },
|
||||||
body: { data, signingAlgorithm },
|
body: { data, signingAlgorithm, isDigest },
|
||||||
permission
|
permission
|
||||||
} = req;
|
} = req;
|
||||||
|
|
||||||
const { projectId, ...result } = await server.services.cmek.cmekSign(
|
const { projectId, ...result } = await server.services.cmek.cmekSign(
|
||||||
{ keyId: inputKeyId, data, signingAlgorithm },
|
{ keyId: inputKeyId, data, signingAlgorithm, isDigest },
|
||||||
permission
|
permission
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -561,6 +562,7 @@ export const registerCmekRouter = async (server: FastifyZodProvider) => {
|
|||||||
keyId: z.string().uuid().describe(KMS.VERIFY.keyId)
|
keyId: z.string().uuid().describe(KMS.VERIFY.keyId)
|
||||||
}),
|
}),
|
||||||
body: z.object({
|
body: z.object({
|
||||||
|
isDigest: z.boolean().optional().default(false).describe(KMS.VERIFY.isDigest),
|
||||||
data: z
|
data: z
|
||||||
.string()
|
.string()
|
||||||
.superRefine((data, ctx) => {
|
.superRefine((data, ctx) => {
|
||||||
@@ -597,12 +599,12 @@ export const registerCmekRouter = async (server: FastifyZodProvider) => {
|
|||||||
handler: async (req) => {
|
handler: async (req) => {
|
||||||
const {
|
const {
|
||||||
params: { keyId },
|
params: { keyId },
|
||||||
body: { data, signature, signingAlgorithm },
|
body: { data, signature, signingAlgorithm, isDigest },
|
||||||
permission
|
permission
|
||||||
} = req;
|
} = req;
|
||||||
|
|
||||||
const { projectId, ...result } = await server.services.cmek.cmekVerify(
|
const { projectId, ...result } = await server.services.cmek.cmekVerify(
|
||||||
{ keyId, data, signature, signingAlgorithm },
|
{ keyId, data, signature, signingAlgorithm, isDigest },
|
||||||
permission
|
permission
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -301,13 +301,10 @@ export const cmekServiceFactory = ({ kmsService, kmsDAL, permissionService, proj
|
|||||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionCmekActions.Read, ProjectPermissionSub.Cmek);
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionCmekActions.Read, ProjectPermissionSub.Cmek);
|
||||||
|
|
||||||
const publicKey = await kmsService.getPublicKey({ kmsId: keyId });
|
const publicKey = await kmsService.getPublicKey({ kmsId: keyId });
|
||||||
|
return { publicKey: publicKey.toString("base64"), projectId: key.projectId };
|
||||||
const base64EncodedPublicKey = publicKey.toString("base64");
|
|
||||||
|
|
||||||
return { publicKey: base64EncodedPublicKey, projectId: key.projectId };
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const cmekSign = async ({ keyId, data, signingAlgorithm }: TCmekSignDTO, actor: OrgServiceActor) => {
|
const cmekSign = async ({ keyId, data, signingAlgorithm, isDigest }: TCmekSignDTO, actor: OrgServiceActor) => {
|
||||||
const key = await kmsDAL.findCmekById(keyId);
|
const key = await kmsDAL.findCmekById(keyId);
|
||||||
|
|
||||||
if (!key) throw new NotFoundError({ message: `Key with ID "${keyId}" not found` });
|
if (!key) throw new NotFoundError({ message: `Key with ID "${keyId}" not found` });
|
||||||
@@ -329,7 +326,7 @@ export const cmekServiceFactory = ({ kmsService, kmsDAL, permissionService, proj
|
|||||||
|
|
||||||
const sign = await kmsService.signWithKmsKey({ kmsId: keyId });
|
const sign = await kmsService.signWithKmsKey({ kmsId: keyId });
|
||||||
|
|
||||||
const { signature, algorithm } = await sign({ data: Buffer.from(data, "base64"), signingAlgorithm });
|
const { signature, algorithm } = await sign({ data: Buffer.from(data, "base64"), signingAlgorithm, isDigest });
|
||||||
|
|
||||||
return {
|
return {
|
||||||
signature: signature.toString("base64"),
|
signature: signature.toString("base64"),
|
||||||
@@ -339,7 +336,10 @@ export const cmekServiceFactory = ({ kmsService, kmsDAL, permissionService, proj
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const cmekVerify = async ({ keyId, data, signature, signingAlgorithm }: TCmekVerifyDTO, actor: OrgServiceActor) => {
|
const cmekVerify = async (
|
||||||
|
{ keyId, data, signature, signingAlgorithm, isDigest }: TCmekVerifyDTO,
|
||||||
|
actor: OrgServiceActor
|
||||||
|
) => {
|
||||||
const key = await kmsDAL.findCmekById(keyId);
|
const key = await kmsDAL.findCmekById(keyId);
|
||||||
|
|
||||||
if (!key) throw new NotFoundError({ message: `Key with ID "${keyId}" not found` });
|
if (!key) throw new NotFoundError({ message: `Key with ID "${keyId}" not found` });
|
||||||
@@ -362,6 +362,7 @@ export const cmekServiceFactory = ({ kmsService, kmsDAL, permissionService, proj
|
|||||||
const verify = await kmsService.verifyWithKmsKey({ kmsId: keyId, signingAlgorithm });
|
const verify = await kmsService.verifyWithKmsKey({ kmsId: keyId, signingAlgorithm });
|
||||||
|
|
||||||
const { signatureValid, algorithm } = await verify({
|
const { signatureValid, algorithm } = await verify({
|
||||||
|
isDigest,
|
||||||
data: Buffer.from(data, "base64"),
|
data: Buffer.from(data, "base64"),
|
||||||
signature: Buffer.from(signature, "base64")
|
signature: Buffer.from(signature, "base64")
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -57,6 +57,7 @@ export type TCmekSignDTO = {
|
|||||||
keyId: string;
|
keyId: string;
|
||||||
data: string;
|
data: string;
|
||||||
signingAlgorithm: SigningAlgorithm;
|
signingAlgorithm: SigningAlgorithm;
|
||||||
|
isDigest: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TCmekVerifyDTO = {
|
export type TCmekVerifyDTO = {
|
||||||
@@ -64,4 +65,5 @@ export type TCmekVerifyDTO = {
|
|||||||
data: string;
|
data: string;
|
||||||
signature: string;
|
signature: string;
|
||||||
signingAlgorithm: SigningAlgorithm;
|
signingAlgorithm: SigningAlgorithm;
|
||||||
|
isDigest: boolean;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
import crypto from "node:crypto";
|
|
||||||
|
|
||||||
import slugify from "@sindresorhus/slugify";
|
import slugify from "@sindresorhus/slugify";
|
||||||
import { Knex } from "knex";
|
import { Knex } from "knex";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
@@ -452,18 +450,7 @@ export const kmsServiceFactory = ({
|
|||||||
const keyCipher = symmetricCipherService(SymmetricKeyAlgorithm.AES_GCM_256);
|
const keyCipher = symmetricCipherService(SymmetricKeyAlgorithm.AES_GCM_256);
|
||||||
const kmsKey = keyCipher.decrypt(kmsDoc.internalKms?.encryptedKey as Buffer, ROOT_ENCRYPTION_KEY);
|
const kmsKey = keyCipher.decrypt(kmsDoc.internalKms?.encryptedKey as Buffer, ROOT_ENCRYPTION_KEY);
|
||||||
|
|
||||||
const publicKeyBuffer = signingService(encryptionAlgorithm).getPublicKeyFromPrivateKey(kmsKey);
|
return signingService(encryptionAlgorithm).getPublicKeyFromPrivateKey(kmsKey);
|
||||||
|
|
||||||
return (
|
|
||||||
crypto
|
|
||||||
.createPublicKey({
|
|
||||||
key: publicKeyBuffer,
|
|
||||||
format: "pem",
|
|
||||||
type: "spki"
|
|
||||||
})
|
|
||||||
// We return as a DER encoded X.509 certificate (https://datatracker.ietf.org/doc/html/rfc5280)
|
|
||||||
.export({ type: "spki", format: "der" })
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const signWithKmsKey = async ({ kmsId }: Pick<TSignWithKmsDTO, "kmsId">) => {
|
const signWithKmsKey = async ({ kmsId }: Pick<TSignWithKmsDTO, "kmsId">) => {
|
||||||
@@ -479,9 +466,13 @@ export const kmsServiceFactory = ({
|
|||||||
|
|
||||||
const keyCipher = symmetricCipherService(SymmetricKeyAlgorithm.AES_GCM_256);
|
const keyCipher = symmetricCipherService(SymmetricKeyAlgorithm.AES_GCM_256);
|
||||||
const { sign } = signingService(encryptionAlgorithm);
|
const { sign } = signingService(encryptionAlgorithm);
|
||||||
return ({ data, signingAlgorithm }: Pick<TSignWithKmsDTO, "data" | "signingAlgorithm">) => {
|
return async ({
|
||||||
|
data,
|
||||||
|
signingAlgorithm,
|
||||||
|
isDigest
|
||||||
|
}: Pick<TSignWithKmsDTO, "data" | "signingAlgorithm" | "isDigest">) => {
|
||||||
const kmsKey = keyCipher.decrypt(kmsDoc.internalKms?.encryptedKey as Buffer, ROOT_ENCRYPTION_KEY);
|
const kmsKey = keyCipher.decrypt(kmsDoc.internalKms?.encryptedKey as Buffer, ROOT_ENCRYPTION_KEY);
|
||||||
const signature = sign(data, kmsKey, signingAlgorithm);
|
const signature = await sign(data, kmsKey, signingAlgorithm, isDigest);
|
||||||
|
|
||||||
return Promise.resolve({ signature, algorithm: signingAlgorithm });
|
return Promise.resolve({ signature, algorithm: signingAlgorithm });
|
||||||
};
|
};
|
||||||
@@ -503,11 +494,11 @@ export const kmsServiceFactory = ({
|
|||||||
|
|
||||||
const keyCipher = symmetricCipherService(SymmetricKeyAlgorithm.AES_GCM_256);
|
const keyCipher = symmetricCipherService(SymmetricKeyAlgorithm.AES_GCM_256);
|
||||||
const { verify, getPublicKeyFromPrivateKey } = signingService(encryptionAlgorithm);
|
const { verify, getPublicKeyFromPrivateKey } = signingService(encryptionAlgorithm);
|
||||||
return ({ data, signature }: Pick<TVerifyWithKmsDTO, "data" | "signature">) => {
|
return async ({ data, signature, isDigest }: Pick<TVerifyWithKmsDTO, "data" | "signature" | "isDigest">) => {
|
||||||
const kmsKey = keyCipher.decrypt(kmsDoc.internalKms?.encryptedKey as Buffer, ROOT_ENCRYPTION_KEY);
|
const kmsKey = keyCipher.decrypt(kmsDoc.internalKms?.encryptedKey as Buffer, ROOT_ENCRYPTION_KEY);
|
||||||
|
|
||||||
const publicKey = getPublicKeyFromPrivateKey(kmsKey);
|
const publicKey = getPublicKeyFromPrivateKey(kmsKey);
|
||||||
const signatureValid = verify(data, signature, publicKey, signingAlgorithm);
|
const signatureValid = await verify(data, signature, publicKey, signingAlgorithm, isDigest);
|
||||||
return Promise.resolve({ signatureValid, algorithm: signingAlgorithm });
|
return Promise.resolve({ signatureValid, algorithm: signingAlgorithm });
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ export type TSignWithKmsDTO = {
|
|||||||
kmsId: string;
|
kmsId: string;
|
||||||
data: Buffer;
|
data: Buffer;
|
||||||
signingAlgorithm: SigningAlgorithm;
|
signingAlgorithm: SigningAlgorithm;
|
||||||
|
isDigest: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TVerifyWithKmsDTO = {
|
export type TVerifyWithKmsDTO = {
|
||||||
@@ -59,6 +60,7 @@ export type TVerifyWithKmsDTO = {
|
|||||||
data: Buffer;
|
data: Buffer;
|
||||||
signature: Buffer;
|
signature: Buffer;
|
||||||
signingAlgorithm: SigningAlgorithm;
|
signingAlgorithm: SigningAlgorithm;
|
||||||
|
isDigest: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TEncryptionWithKeyDTO = {
|
export type TEncryptionWithKeyDTO = {
|
||||||
|
|||||||
@@ -81,8 +81,8 @@ export enum CmekOrderBy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export enum AsymmetricKeyAlgorithm {
|
export enum AsymmetricKeyAlgorithm {
|
||||||
RSA_4096 = "rsa-4096",
|
RSA_4096 = "RSA_4096",
|
||||||
ECC_NIST_P256 = "ecc-nist-p256"
|
ECC_NIST_P256 = "ECC_NIST_P256"
|
||||||
}
|
}
|
||||||
|
|
||||||
// Supported symmetric encrypt/decrypt algorithms
|
// Supported symmetric encrypt/decrypt algorithms
|
||||||
|
|||||||
Reference in New Issue
Block a user