mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-14 21:51:56 +00:00
Merge pull request #3357 from Infisical/daniel/kms-sign-verify
feat(kms): sign & verify data
This commit is contained in:
@@ -1672,7 +1672,8 @@ export const KMS = {
|
||||
projectId: "The ID of the project to create the key in.",
|
||||
name: "The name of the key to be created. Must be slug-friendly.",
|
||||
description: "An optional description of the key.",
|
||||
encryptionAlgorithm: "The algorithm to use when performing cryptographic operations with the key."
|
||||
encryptionAlgorithm: "The algorithm to use when performing cryptographic operations with the key.",
|
||||
type: "The type of key to be created, either encrypt-decrypt or sign-verify, based on your intended use for the key."
|
||||
},
|
||||
UPDATE_KEY: {
|
||||
keyId: "The ID of the key to be updated.",
|
||||
@@ -1705,6 +1706,28 @@ export const KMS = {
|
||||
DECRYPT: {
|
||||
keyId: "The ID of the key to decrypt the data with.",
|
||||
ciphertext: "The ciphertext to be decrypted (base64 encoded)."
|
||||
},
|
||||
|
||||
LIST_SIGNING_ALGORITHMS: {
|
||||
keyId: "The ID of the key to list the signing algorithms for. The key must be for signing and verifying."
|
||||
},
|
||||
|
||||
GET_PUBLIC_KEY: {
|
||||
keyId: "The ID of the key to get the public key for. The key must be for signing and verifying."
|
||||
},
|
||||
|
||||
SIGN: {
|
||||
keyId: "The ID of the key to sign the data with.",
|
||||
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: {
|
||||
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.",
|
||||
signature: "The signature to be verified (base64 encoded).",
|
||||
isDigest: "Whether the data is already digested or not."
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import crypto from "crypto";
|
||||
|
||||
import { SymmetricEncryption, TSymmetricEncryptionFns } from "./types";
|
||||
import { SymmetricKeyAlgorithm, TSymmetricEncryptionFns } from "./types";
|
||||
|
||||
const getIvLength = () => {
|
||||
return 12;
|
||||
@@ -10,7 +10,9 @@ const getTagLength = () => {
|
||||
return 16;
|
||||
};
|
||||
|
||||
export const symmetricCipherService = (type: SymmetricEncryption): TSymmetricEncryptionFns => {
|
||||
export const symmetricCipherService = (
|
||||
type: SymmetricKeyAlgorithm.AES_GCM_128 | SymmetricKeyAlgorithm.AES_GCM_256
|
||||
): TSymmetricEncryptionFns => {
|
||||
const IV_LENGTH = getIvLength();
|
||||
const TAG_LENGTH = getTagLength();
|
||||
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
export { symmetricCipherService } from "./cipher";
|
||||
export { SymmetricEncryption } from "./types";
|
||||
export { AllowedEncryptionKeyAlgorithms, SymmetricKeyAlgorithm } from "./types";
|
||||
|
||||
@@ -1,7 +1,18 @@
|
||||
export enum SymmetricEncryption {
|
||||
import { z } from "zod";
|
||||
|
||||
import { AsymmetricKeyAlgorithm } from "../sign/types";
|
||||
|
||||
// Supported symmetric encrypt/decrypt algorithms
|
||||
export enum SymmetricKeyAlgorithm {
|
||||
AES_GCM_256 = "aes-256-gcm",
|
||||
AES_GCM_128 = "aes-128-gcm"
|
||||
}
|
||||
export const SymmetricKeyAlgorithmEnum = z.enum(Object.values(SymmetricKeyAlgorithm) as [string, ...string[]]).options;
|
||||
|
||||
export const AllowedEncryptionKeyAlgorithms = z.enum([
|
||||
...Object.values(SymmetricKeyAlgorithm),
|
||||
...Object.values(AsymmetricKeyAlgorithm)
|
||||
] as [string, ...string[]]).options;
|
||||
|
||||
export type TSymmetricEncryptionFns = {
|
||||
encrypt: (text: Buffer, key: Buffer) => Buffer;
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
export { signingService } from "./signing";
|
||||
export { AsymmetricKeyAlgorithm, SigningAlgorithm } from "./types";
|
||||
@@ -0,0 +1,539 @@
|
||||
import { execFile } from "child_process";
|
||||
import crypto from "crypto";
|
||||
import fs from "fs/promises";
|
||||
import path from "path";
|
||||
import { promisify } from "util";
|
||||
|
||||
import { BadRequestError } from "@app/lib/errors";
|
||||
import { cleanTemporaryDirectory, createTemporaryDirectory, writeToTemporaryFile } from "@app/lib/files";
|
||||
import { logger } from "@app/lib/logger";
|
||||
|
||||
import { AsymmetricKeyAlgorithm, SigningAlgorithm, TAsymmetricSignVerifyFns } from "./types";
|
||||
|
||||
const execFileAsync = promisify(execFile);
|
||||
|
||||
interface SigningParams {
|
||||
hashAlgorithm: SupportedHashAlgorithm;
|
||||
padding?: number;
|
||||
saltLength?: number;
|
||||
}
|
||||
|
||||
enum SupportedHashAlgorithm {
|
||||
SHA256 = "sha256",
|
||||
SHA384 = "sha384",
|
||||
SHA512 = "sha512"
|
||||
}
|
||||
|
||||
const COMMAND_TIMEOUT = 15_000;
|
||||
|
||||
const SHA256_DIGEST_LENGTH = 32;
|
||||
const SHA384_DIGEST_LENGTH = 48;
|
||||
const SHA512_DIGEST_LENGTH = 64;
|
||||
|
||||
/**
|
||||
* Service for cryptographic signing and verification operations using asymmetric keys
|
||||
*
|
||||
* @param algorithm The key algorithm itself. The signing algorithm is supplied in the individual sign/verify functions.
|
||||
* @returns Object with sign and verify functions
|
||||
*/
|
||||
export const signingService = (algorithm: AsymmetricKeyAlgorithm): TAsymmetricSignVerifyFns => {
|
||||
const $getSigningParams = (signingAlgorithm: SigningAlgorithm): SigningParams => {
|
||||
switch (signingAlgorithm) {
|
||||
// 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:
|
||||
return {
|
||||
hashAlgorithm: SupportedHashAlgorithm.SHA256,
|
||||
padding: crypto.constants.RSA_PKCS1_PSS_PADDING,
|
||||
saltLength: SHA256_DIGEST_LENGTH
|
||||
};
|
||||
case SigningAlgorithm.RSASSA_PSS_SHA_384:
|
||||
return {
|
||||
hashAlgorithm: SupportedHashAlgorithm.SHA384,
|
||||
padding: crypto.constants.RSA_PKCS1_PSS_PADDING,
|
||||
saltLength: SHA384_DIGEST_LENGTH
|
||||
};
|
||||
|
||||
// RSA PKCS#1 v1.5
|
||||
case SigningAlgorithm.RSASSA_PKCS1_V1_5_SHA_512:
|
||||
return {
|
||||
hashAlgorithm: SupportedHashAlgorithm.SHA512,
|
||||
padding: crypto.constants.RSA_PKCS1_PADDING
|
||||
};
|
||||
case SigningAlgorithm.RSASSA_PKCS1_V1_5_SHA_384:
|
||||
return {
|
||||
hashAlgorithm: SupportedHashAlgorithm.SHA384,
|
||||
padding: crypto.constants.RSA_PKCS1_PADDING
|
||||
};
|
||||
case SigningAlgorithm.RSASSA_PKCS1_V1_5_SHA_256:
|
||||
return {
|
||||
hashAlgorithm: SupportedHashAlgorithm.SHA256,
|
||||
padding: crypto.constants.RSA_PKCS1_PADDING
|
||||
};
|
||||
|
||||
// ECDSA
|
||||
case SigningAlgorithm.ECDSA_SHA_256:
|
||||
return { hashAlgorithm: SupportedHashAlgorithm.SHA256 };
|
||||
case SigningAlgorithm.ECDSA_SHA_384:
|
||||
return { hashAlgorithm: SupportedHashAlgorithm.SHA384 };
|
||||
case SigningAlgorithm.ECDSA_SHA_512:
|
||||
return { hashAlgorithm: SupportedHashAlgorithm.SHA512 };
|
||||
|
||||
default:
|
||||
throw new Error(`Unsupported signing algorithm: ${signingAlgorithm as string}`);
|
||||
}
|
||||
};
|
||||
|
||||
const $getEcCurveName = (keyAlgorithm: AsymmetricKeyAlgorithm): { full: string; short: string } => {
|
||||
// We will support more in the future
|
||||
switch (keyAlgorithm) {
|
||||
case AsymmetricKeyAlgorithm.ECC_NIST_P256:
|
||||
return {
|
||||
full: "prime256v1",
|
||||
short: "p256"
|
||||
};
|
||||
default:
|
||||
throw new Error(`Unsupported EC curve: ${keyAlgorithm}`);
|
||||
}
|
||||
};
|
||||
|
||||
const $validateAlgorithmWithKeyType = (signingAlgorithm: SigningAlgorithm) => {
|
||||
const isRsaKey = algorithm.startsWith("RSA");
|
||||
const isEccKey = algorithm.startsWith("ECC");
|
||||
|
||||
const isRsaAlgorithm = signingAlgorithm.startsWith("RSASSA");
|
||||
const isEccAlgorithm = signingAlgorithm.startsWith("ECDSA");
|
||||
|
||||
if (isRsaKey && !isRsaAlgorithm) {
|
||||
throw new BadRequestError({ message: `KMS RSA key cannot be used with ${signingAlgorithm}` });
|
||||
}
|
||||
|
||||
if (isEccKey && !isEccAlgorithm) {
|
||||
throw new BadRequestError({ message: `KMS ECC key cannot be used with ${signingAlgorithm}` });
|
||||
}
|
||||
};
|
||||
|
||||
const $signRsaDigest = async (digest: Buffer, privateKey: Buffer, hashAlgorithm: SupportedHashAlgorithm) => {
|
||||
const tempDir = await createTemporaryDirectory("kms-rsa-sign");
|
||||
const digestPath = path.join(tempDir, "digest.bin");
|
||||
const sigPath = path.join(tempDir, "signature.bin");
|
||||
const keyPath = path.join(tempDir, "key.pem");
|
||||
|
||||
try {
|
||||
await writeToTemporaryFile(digestPath, digest);
|
||||
await writeToTemporaryFile(keyPath, privateKey);
|
||||
|
||||
const { stderr } = await execFileAsync(
|
||||
"openssl",
|
||||
[
|
||||
"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 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 cleanTemporaryDirectory(tempDir);
|
||||
}
|
||||
};
|
||||
|
||||
const $signEccDigest = async (digest: Buffer, privateKey: Buffer, hashAlgorithm: SupportedHashAlgorithm) => {
|
||||
const tempDir = await createTemporaryDirectory("ecc-sign");
|
||||
const digestPath = path.join(tempDir, "digest.bin");
|
||||
const keyPath = path.join(tempDir, "key.pem");
|
||||
const sigPath = path.join(tempDir, "signature.bin");
|
||||
|
||||
try {
|
||||
await writeToTemporaryFile(digestPath, digest);
|
||||
await writeToTemporaryFile(keyPath, privateKey);
|
||||
|
||||
const { stderr } = await execFileAsync(
|
||||
"openssl",
|
||||
[
|
||||
"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"
|
||||
});
|
||||
}
|
||||
|
||||
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 cleanTemporaryDirectory(tempDir);
|
||||
}
|
||||
};
|
||||
|
||||
const $verifyEccDigest = async (
|
||||
digest: Buffer,
|
||||
signature: Buffer,
|
||||
publicKey: Buffer,
|
||||
hashAlgorithm: SupportedHashAlgorithm
|
||||
) => {
|
||||
const tempDir = await createTemporaryDirectory("ecc-signature-verification");
|
||||
const publicKeyFile = path.join(tempDir, "public-key.pem");
|
||||
const sigFile = path.join(tempDir, "signature.sig");
|
||||
const digestFile = path.join(tempDir, "digest.bin");
|
||||
|
||||
try {
|
||||
await writeToTemporaryFile(publicKeyFile, publicKey);
|
||||
await writeToTemporaryFile(sigFile, signature);
|
||||
await writeToTemporaryFile(digestFile, digest);
|
||||
|
||||
await execFileAsync(
|
||||
"openssl",
|
||||
[
|
||||
"pkeyutl",
|
||||
"-verify",
|
||||
"-in",
|
||||
digestFile,
|
||||
"-inkey",
|
||||
publicKeyFile,
|
||||
"-pubin", // Important for EC public keys
|
||||
"-sigfile",
|
||||
sigFile,
|
||||
"-pkeyopt",
|
||||
`digest:${hashAlgorithm}`
|
||||
],
|
||||
{ timeout: COMMAND_TIMEOUT }
|
||||
);
|
||||
|
||||
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 cleanTemporaryDirectory(tempDir);
|
||||
}
|
||||
};
|
||||
|
||||
const $verifyRsaDigest = async (
|
||||
digest: Buffer,
|
||||
signature: Buffer,
|
||||
publicKey: Buffer,
|
||||
hashAlgorithm: SupportedHashAlgorithm
|
||||
) => {
|
||||
const tempDir = await createTemporaryDirectory("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 writeToTemporaryFile(publicKeyFile, publicKey);
|
||||
await writeToTemporaryFile(signatureFile, signature);
|
||||
await writeToTemporaryFile(digestFile, digest);
|
||||
|
||||
await execFileAsync(
|
||||
"openssl",
|
||||
[
|
||||
"pkeyutl",
|
||||
"-verify",
|
||||
"-in",
|
||||
digestFile,
|
||||
"-inkey",
|
||||
publicKeyFile,
|
||||
"-pubin",
|
||||
"-sigfile",
|
||||
signatureFile,
|
||||
"-pkeyopt",
|
||||
`digest:${hashAlgorithm}`
|
||||
],
|
||||
{ timeout: COMMAND_TIMEOUT }
|
||||
);
|
||||
|
||||
// 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 cleanTemporaryDirectory(tempDir);
|
||||
}
|
||||
};
|
||||
|
||||
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 { privateKey } = await new Promise<{ privateKey: string }>((resolve, reject) => {
|
||||
if (algorithm.startsWith("RSA")) {
|
||||
crypto.generateKeyPair(
|
||||
"rsa",
|
||||
{
|
||||
modulusLength: Number(algorithm.split("_")[1]),
|
||||
publicKeyEncoding: { type: "spki", format: "pem" },
|
||||
privateKeyEncoding: { type: "pkcs8", format: "pem" }
|
||||
},
|
||||
(err, _, pk) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
resolve({ privateKey: pk });
|
||||
}
|
||||
}
|
||||
);
|
||||
} else {
|
||||
const { full: namedCurve } = $getEcCurveName(algorithm);
|
||||
|
||||
crypto.generateKeyPair(
|
||||
"ec",
|
||||
{
|
||||
namedCurve,
|
||||
publicKeyEncoding: { type: "spki", format: "pem" },
|
||||
privateKeyEncoding: { type: "pkcs8", format: "pem" }
|
||||
},
|
||||
(err, _, pk) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
resolve({
|
||||
privateKey: pk
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
return Buffer.from(privateKey);
|
||||
};
|
||||
|
||||
const getPublicKeyFromPrivateKey = (privateKey: Buffer) => {
|
||||
const privateKeyObj = crypto.createPrivateKey({
|
||||
key: privateKey,
|
||||
format: "pem",
|
||||
type: "pkcs8"
|
||||
});
|
||||
|
||||
const publicKey = crypto.createPublicKey(privateKeyObj).export({
|
||||
type: "spki",
|
||||
format: "der"
|
||||
});
|
||||
|
||||
return publicKey;
|
||||
};
|
||||
|
||||
return {
|
||||
sign,
|
||||
verify,
|
||||
generateAsymmetricPrivateKey,
|
||||
getPublicKeyFromPrivateKey
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,45 @@
|
||||
import { z } from "zod";
|
||||
|
||||
export type TAsymmetricSignVerifyFns = {
|
||||
sign: (data: Buffer, key: Buffer, signingAlgorithm: SigningAlgorithm, isDigest: boolean) => Promise<Buffer>;
|
||||
verify: (
|
||||
data: Buffer,
|
||||
signature: Buffer,
|
||||
key: Buffer,
|
||||
signingAlgorithm: SigningAlgorithm,
|
||||
isDigest: boolean
|
||||
) => Promise<boolean>;
|
||||
generateAsymmetricPrivateKey: () => Promise<Buffer>;
|
||||
getPublicKeyFromPrivateKey: (privateKey: Buffer) => Buffer;
|
||||
};
|
||||
|
||||
// Supported asymmetric key types
|
||||
export enum AsymmetricKeyAlgorithm {
|
||||
RSA_4096 = "RSA_4096",
|
||||
ECC_NIST_P256 = "ECC_NIST_P256"
|
||||
}
|
||||
|
||||
export const AsymmetricKeyAlgorithmEnum = z.enum(
|
||||
Object.values(AsymmetricKeyAlgorithm) as [string, ...string[]]
|
||||
).options;
|
||||
|
||||
export enum SigningAlgorithm {
|
||||
// RSA PSS algorithms
|
||||
// These are NOT deterministic and include randomness.
|
||||
// This means that the output signature is different each time for the same input.
|
||||
RSASSA_PSS_SHA_512 = "RSASSA_PSS_SHA_512",
|
||||
RSASSA_PSS_SHA_384 = "RSASSA_PSS_SHA_384",
|
||||
RSASSA_PSS_SHA_256 = "RSASSA_PSS_SHA_256",
|
||||
|
||||
// RSA PKCS#1 v1.5 algorithms
|
||||
// These are deterministic and the output is the same each time for the same input.
|
||||
RSASSA_PKCS1_V1_5_SHA_512 = "RSASSA_PKCS1_V1_5_SHA_512",
|
||||
RSASSA_PKCS1_V1_5_SHA_384 = "RSASSA_PKCS1_V1_5_SHA_384",
|
||||
RSASSA_PKCS1_V1_5_SHA_256 = "RSASSA_PKCS1_V1_5_SHA_256",
|
||||
|
||||
// ECDSA algorithms
|
||||
// None of these are deterministic and include randomness like RSA PSS.
|
||||
ECDSA_SHA_512 = "ECDSA_SHA_512",
|
||||
ECDSA_SHA_384 = "ECDSA_SHA_384",
|
||||
ECDSA_SHA_256 = "ECDSA_SHA_256"
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
import crypto from "crypto";
|
||||
import fs from "fs/promises";
|
||||
import os from "os";
|
||||
import path from "path";
|
||||
|
||||
import { logger } from "@app/lib/logger";
|
||||
|
||||
const baseDir = path.join(os.tmpdir(), "infisical");
|
||||
const randomPath = () => `${crypto.randomBytes(32).toString("hex")}`;
|
||||
|
||||
export const createTemporaryDirectory = async (name: string) => {
|
||||
const tempDirPath = path.join(baseDir, `${name}-${randomPath()}`);
|
||||
await fs.mkdir(tempDirPath, { recursive: true });
|
||||
|
||||
return tempDirPath;
|
||||
};
|
||||
|
||||
export const removeTemporaryBaseDirectory = async () => {
|
||||
await fs.rm(baseDir, { force: true, recursive: true }).catch((err) => {
|
||||
logger.error(err, `Failed to remove temporary base directory [path=${baseDir}]`);
|
||||
});
|
||||
};
|
||||
|
||||
export const cleanTemporaryDirectory = async (dirPath: string) => {
|
||||
await fs.rm(dirPath, { recursive: true, force: true }).catch((err) => {
|
||||
logger.error(err, `Failed to cleanup temporary directory [path=${dirPath}]`);
|
||||
});
|
||||
};
|
||||
|
||||
export const writeToTemporaryFile = async (tempDirPath: string, data: string | Buffer) => {
|
||||
await fs.writeFile(tempDirPath, data, { mode: 0o600 }).catch((err) => {
|
||||
logger.error(err, `Failed to write to temporary file [path=${tempDirPath}]`);
|
||||
throw err;
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
export * from "./files";
|
||||
Reference in New Issue
Block a user