From 9aa3c14bf233424c9d5a8eaad8a383a4cf932068 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard Date: Sun, 6 Jul 2025 15:44:07 +0400 Subject: [PATCH] feat: fips inside support (checkpoint) --- backend/Dockerfile.dev.fips | 22 +- .../e2e-test/routes/v2/service-token.spec.ts | 24 +- backend/e2e-test/routes/v3/secrets.spec.ts | 7 +- backend/e2e-test/vitest-environment-knex.ts | 7 + backend/package-lock.json | 15 + backend/package.json | 2 + backend/src/@types/fastify-zod.d.ts | 2 + .../20250210101840_webhook-to-kms.ts | 6 +- ...250210101841_dynamic-secret-root-to-kms.ts | 4 +- .../20250210101841_secret-rotation-to-kms.ts | 4 +- .../20250210101842_identity-k8-auth-to-kms.ts | 10 +- ...0250210101842_identity-oidc-auth-to-kms.ts | 8 +- .../20250210101845_directory-config-to-kms.ts | 35 +- .../db/migrations/20250705074703_fips-mode.ts | 23 + backend/src/db/schemas/super-admin.ts | 4 +- backend/src/db/seed-data.ts | 75 ++- backend/src/db/seeds/3-project.ts | 10 +- backend/src/db/seeds/5-machine-identity.ts | 7 +- .../ee/routes/est/certificate-est-router.ts | 4 +- .../audit-log-stream-service.ts | 13 +- .../ee/services/audit-log/audit-log-queue.ts | 6 +- .../dynamic-secret/providers/aws-iam.ts | 4 +- .../dynamic-secret/providers/sql-database.ts | 14 +- .../dynamic-secret/providers/vertica.ts | 14 +- .../external-kms/providers/aws-kms.ts | 5 +- .../ee/services/gateway/gateway-service.ts | 31 +- backend/src/ee/services/group/group-fns.ts | 15 +- backend/src/ee/services/kmip/kmip-service.ts | 31 +- .../secret-approval-request-service.ts | 12 +- .../secret-replication-service.ts | 12 +- .../secret-rotation-v2/shared/utils/index.ts | 14 +- .../secret-rotation-queue.ts | 19 +- .../secret-rotation-service.ts | 8 +- .../secret-scanning-service.ts | 3 +- .../secret-snapshot-service.ts | 18 +- .../ssh/ssh-certificate-authority-fns.ts | 2 +- backend/src/lib/axios/digest-auth.ts | 17 +- backend/src/lib/config/env.ts | 3 +- backend/src/lib/crypto/cache.ts | 10 +- backend/src/lib/crypto/cipher/cipher.ts | 18 +- backend/src/lib/crypto/cryptography.ts | 630 ++++++++++++++++++ backend/src/lib/crypto/encryption.ts | 215 +----- backend/src/lib/crypto/index.ts | 16 +- backend/src/lib/crypto/secret-encryption.ts | 14 +- backend/src/lib/crypto/sign/signing.ts | 34 +- backend/src/lib/crypto/signing.ts | 8 +- backend/src/lib/crypto/srp.ts | 43 +- backend/src/lib/files/files.ts | 2 +- backend/src/lib/gateway/gateway.ts | 9 +- backend/src/lib/red-lock/index.ts | 6 +- backend/src/lib/turn/credentials.ts | 4 +- backend/src/main.ts | 6 + backend/src/queue/queue-service.ts | 14 +- backend/src/server/app.ts | 26 +- backend/src/server/routes/index.ts | 5 +- backend/src/server/routes/v1/admin-router.ts | 3 + .../v1/identity-tls-cert-auth-router.ts | 7 +- .../src/services/api-key/api-key-service.ts | 9 +- .../app-connection/app-connection-fns.ts | 4 +- .../app-connection/app-connection-service.ts | 4 +- .../app-connection/aws/aws-connection-fns.ts | 4 +- .../services/auth-token/auth-token-service.ts | 8 +- .../src/services/auth/auth-login-service.ts | 10 +- .../services/auth/auth-password-service.ts | 17 +- .../src/services/auth/auth-signup-service.ts | 15 +- .../acme/acme-certificate-authority-fns.ts | 7 +- .../certificate-authority-fns.ts | 30 +- .../certificate-authority-queue.ts | 14 +- .../internal-certificate-authority-fns.ts | 10 +- .../internal-certificate-authority-service.ts | 14 +- .../certificate-template-service.ts | 6 +- .../services/certificate/certificate-fns.ts | 7 +- .../certificate/certificate-service.ts | 10 +- .../external-migration-fns.ts | 14 +- .../external-migration-queue.ts | 4 +- .../external-migration-service.ts | 10 +- .../group-project/group-project-service.ts | 12 +- .../identity-tls-cert-auth-service.ts | 7 +- .../identity-ua/identity-ua-service.ts | 11 +- .../integration-auth-service.ts | 107 ++- .../integration-delete-secret.ts | 8 +- .../integration-sync-secret.ts | 6 +- backend/src/services/kms/kms-service.ts | 20 +- .../services/org-admin/org-admin-service.ts | 4 +- backend/src/services/org/org-service.ts | 26 +- .../services/project-bot/project-bot-fns.ts | 30 +- .../project-bot/project-bot-service.ts | 10 +- backend/src/services/project/project-fns.ts | 24 +- backend/src/services/project/project-queue.ts | 116 ++-- .../src/services/project/project-service.ts | 8 +- .../secret-sharing/secret-sharing-service.ts | 13 +- backend/src/services/secret/secret-fns.ts | 84 ++- backend/src/services/secret/secret-queue.ts | 95 ++- backend/src/services/secret/secret-service.ts | 119 +++- .../service-token/service-token-service.ts | 8 +- .../super-admin/super-admin-service.ts | 12 +- .../services/telemetry/telemetry-service.ts | 6 +- backend/src/services/totp/totp-fns.ts | 2 +- backend/src/services/user/user-service.ts | 5 +- backend/src/services/webhook/webhook-fns.ts | 6 +- docker-compose.dev.yml | 2 +- frontend/src/components/auth/UserInfoStep.tsx | 23 +- .../utilities/cryptography/crypto.ts | 147 ++-- frontend/src/hooks/api/admin/types.ts | 1 + frontend/src/hooks/api/cmeks/mutations.tsx | 3 +- .../api/secretApprovalRequest/queries.tsx | 79 --- frontend/src/hooks/api/users/index.tsx | 1 - frontend/src/hooks/api/users/mutation.tsx | 43 +- frontend/src/hooks/api/users/types.ts | 10 - frontend/src/lib/crypto/index.ts | 16 +- .../src/pages/admin/SignUpPage/SignUpPage.tsx | 15 +- .../SignUpInvitePage/SignUpInvitePage.tsx | 12 +- .../UserInfoSSOStep/UserInfoSSOStep.tsx | 13 +- .../CaCertificatesTable.tsx | 2 +- .../components/CmekDecryptModal.tsx | 2 +- .../components/CmekVerifyModal.tsx | 2 +- .../IntegrationsListPage.utils.tsx | 29 +- .../ProjectGeneralTab/ProjectGeneralTab.tsx | 6 +- .../RebuildSecretIndicesSection.tsx | 93 --- package-lock.json | 26 + package.json | 2 + 121 files changed, 1766 insertions(+), 1277 deletions(-) create mode 100644 backend/src/db/migrations/20250705074703_fips-mode.ts create mode 100644 backend/src/lib/crypto/cryptography.ts delete mode 100644 frontend/src/pages/secret-manager/SettingsPage/components/RebuildSecretIndicesSection/RebuildSecretIndicesSection.tsx diff --git a/backend/Dockerfile.dev.fips b/backend/Dockerfile.dev.fips index 0afb330e56..a777623242 100644 --- a/backend/Dockerfile.dev.fips +++ b/backend/Dockerfile.dev.fips @@ -1,11 +1,6 @@ FROM node:20-slim -# ? Setup a test SoftHSM module. In production a real HSM is used. - -ARG SOFTHSM2_VERSION=2.5.0 - -ENV SOFTHSM2_VERSION=${SOFTHSM2_VERSION} \ - SOFTHSM2_SOURCES=/tmp/softhsm2 +RUN echo "RUNNING FIPS BUILD" # Install build dependencies including python3 (required for pkcs11js and partially TDS driver) RUN apt-get update && apt-get install -y \ @@ -34,24 +29,10 @@ RUN apt-get install -y \ RUN printf "[FreeTDS]\nDescription = FreeTDS Driver\nDriver = /usr/lib/x86_64-linux-gnu/odbc/libtdsodbc.so\nSetup = /usr/lib/x86_64-linux-gnu/odbc/libtdsodbc.so\nFileUsage = 1\n" > /etc/odbcinst.ini -# Build and install SoftHSM2 -RUN git clone https://github.com/opendnssec/SoftHSMv2.git ${SOFTHSM2_SOURCES} -WORKDIR ${SOFTHSM2_SOURCES} - -RUN git checkout ${SOFTHSM2_VERSION} -b ${SOFTHSM2_VERSION} \ - && sh autogen.sh \ - && ./configure --prefix=/usr/local --disable-gost \ - && make \ - && make install - WORKDIR /root -RUN rm -fr ${SOFTHSM2_SOURCES} # Install pkcs11-tool RUN apt-get install -y opensc - -RUN mkdir -p /etc/softhsm2/tokens && \ - softhsm2-util --init-token --slot 0 --label "auth-app" --pin 1234 --so-pin 0000 WORKDIR /openssl-build RUN wget https://www.openssl.org/source/openssl-3.1.2.tar.gz \ @@ -81,5 +62,6 @@ ENV HOST=0.0.0.0 ENV OPENSSL_CONF=/app/nodejs.cnf ENV OPENSSL_MODULES=/usr/local/lib/ossl-modules ENV NODE_OPTIONS=--force-fips +ENV FIPS_ENABLED=true CMD ["npm", "run", "dev:docker"] diff --git a/backend/e2e-test/routes/v2/service-token.spec.ts b/backend/e2e-test/routes/v2/service-token.spec.ts index 6cc8f6e34f..13aaabcd37 100644 --- a/backend/e2e-test/routes/v2/service-token.spec.ts +++ b/backend/e2e-test/routes/v2/service-token.spec.ts @@ -1,8 +1,6 @@ -import crypto from "node:crypto"; - import { SecretType, TSecrets } from "@app/db/schemas"; import { decryptSecret, encryptSecret, getUserPrivateKey, seedData1 } from "@app/db/seed-data"; -import { decryptAsymmetric, decryptSymmetric128BitHexKeyUTF8, encryptSymmetric128BitHexKeyUTF8 } from "@app/lib/crypto"; +import { SymmetricKeySize } from "@app/lib/crypto"; const createServiceToken = async ( scopes: { environment: string; secretPath: string }[], @@ -26,15 +24,21 @@ const createServiceToken = async ( }); const { user: userInfo } = JSON.parse(userInfoRes.payload); const privateKey = await getUserPrivateKey(seedData1.password, userInfo); - const projectKey = decryptAsymmetric({ + const projectKey = testCryptoProvider.encryption().asymmetric().decrypt({ ciphertext: projectKeyEnc.encryptedKey, nonce: projectKeyEnc.nonce, publicKey: projectKeyEnc.sender.publicKey, privateKey }); - const randomBytes = crypto.randomBytes(16).toString("hex"); - const { ciphertext, iv, tag } = encryptSymmetric128BitHexKeyUTF8(projectKey, randomBytes); + const randomBytes = testCryptoProvider.randomBytes(16).toString("hex"); + + const { ciphertext, iv, tag } = testCryptoProvider.encryption().encryptSymmetric({ + plaintext: projectKey, + key: randomBytes, + keySize: SymmetricKeySize.Bits128 + }); + const serviceTokenRes = await testServer.inject({ method: "POST", url: "/api/v2/service-token", @@ -153,11 +157,13 @@ describe("Service token secret ops", async () => { expect(serviceTokenInfoRes.statusCode).toBe(200); const serviceTokenInfo = serviceTokenInfoRes.json(); const serviceTokenParts = serviceToken.split("."); - projectKey = decryptSymmetric128BitHexKeyUTF8({ + + projectKey = testCryptoProvider.encryption().decryptSymmetric({ key: serviceTokenParts[3], tag: serviceTokenInfo.tag, ciphertext: serviceTokenInfo.encryptedKey, - iv: serviceTokenInfo.iv + iv: serviceTokenInfo.iv, + keySize: SymmetricKeySize.Bits128 }); // create a deep folder @@ -551,7 +557,7 @@ describe("Service token fail cases", async () => { type: SecretType.Shared, secretPath: "/", // doesn't matter project key because this will fail before that due to read only access - ...encryptSecret(crypto.randomBytes(16).toString("hex"), "NEW", "value", "") + ...encryptSecret(testCryptoProvider.randomBytes(16).toString("hex"), "NEW", "value", "") }, headers: { authorization: `Bearer ${serviceToken}` diff --git a/backend/e2e-test/routes/v3/secrets.spec.ts b/backend/e2e-test/routes/v3/secrets.spec.ts index c035692ed3..c99c506847 100644 --- a/backend/e2e-test/routes/v3/secrets.spec.ts +++ b/backend/e2e-test/routes/v3/secrets.spec.ts @@ -1,6 +1,5 @@ import { SecretType, TSecrets } from "@app/db/schemas"; import { decryptSecret, encryptSecret, getUserPrivateKey, seedData1 } from "@app/db/seed-data"; -import { decryptAsymmetric, encryptAsymmetric } from "@app/lib/crypto"; import { AuthMode } from "@app/services/auth/auth-type"; const createSecret = async (dto: { @@ -173,7 +172,7 @@ describe("Secret V3 Router", async () => { }); const { user: userInfo } = JSON.parse(userInfoRes.payload); const privateKey = await getUserPrivateKey(seedData1.password, userInfo); - projectKey = decryptAsymmetric({ + projectKey = testCryptoProvider.encryption().asymmetric().decrypt({ ciphertext: projectKeyEncryptionDetails.encryptedKey, nonce: projectKeyEncryptionDetails.nonce, publicKey: projectKeyEncryptionDetails.sender.publicKey, @@ -669,7 +668,7 @@ describe.each([{ auth: AuthMode.JWT }, { auth: AuthMode.IDENTITY_ACCESS_TOKEN }] const { user: userInfo } = JSON.parse(userInfoRes.payload); const privateKey = await getUserPrivateKey(seedData1.password, userInfo); - const projectKey = decryptAsymmetric({ + const projectKey = testCryptoProvider.encryption().asymmetric().decrypt({ ciphertext: projectKeyEnc.encryptedKey, nonce: projectKeyEnc.nonce, publicKey: projectKeyEnc.sender.publicKey, @@ -685,7 +684,7 @@ describe.each([{ auth: AuthMode.JWT }, { auth: AuthMode.IDENTITY_ACCESS_TOKEN }] }); expect(projectBotRes.statusCode).toEqual(200); const projectBot = JSON.parse(projectBotRes.payload).bot; - const botKey = encryptAsymmetric(projectKey, projectBot.publicKey, privateKey); + const botKey = testCryptoProvider.encryption().asymmetric().encrypt(projectKey, projectBot.publicKey, privateKey); // set bot as active const setBotActive = await testServer.inject({ diff --git a/backend/e2e-test/vitest-environment-knex.ts b/backend/e2e-test/vitest-environment-knex.ts index 92cf86e66a..6e1f0ff49a 100644 --- a/backend/e2e-test/vitest-environment-knex.ts +++ b/backend/e2e-test/vitest-environment-knex.ts @@ -17,6 +17,8 @@ import { queueServiceFactory } from "@app/queue"; import { keyStoreFactory } from "@app/keystore/keystore"; import { initializeHsmModule } from "@app/ee/services/hsm/hsm-fns"; import { buildRedisFromConfig } from "@app/lib/config/redis"; +import { superAdminDALFactory } from "@app/services/super-admin/super-admin-dal"; +import { crypto } from "@app/lib/crypto/cryptography"; dotenv.config({ path: path.join(__dirname, "../../.env.test"), debug: true }); export default { @@ -29,6 +31,8 @@ export default { dbConnectionUri: envConfig.DB_CONNECTION_URI, dbRootCert: envConfig.DB_ROOT_CERT }); + const superAdminDAL = superAdminDALFactory(db); + await crypto.initialize(superAdminDAL); const redis = buildRedisFromConfig(envConfig); await redis.flushdb("SYNC"); @@ -68,6 +72,7 @@ export default { queue, keyStore, hsmModule: hsmModule.getModule(), + superAdminDAL, redis, envConfig }); @@ -75,6 +80,8 @@ export default { // @ts-expect-error type globalThis.testServer = server; // @ts-expect-error type + globalThis.testCryptoProvider = crypto; + // @ts-expect-error type globalThis.jwtAuthToken = jwt.sign( { authTokenType: AuthTokenType.ACCESS_TOKEN, diff --git a/backend/package-lock.json b/backend/package-lock.json index b90e448cb5..aa2bc915fc 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -69,6 +69,7 @@ "cassandra-driver": "^4.7.2", "connect-redis": "^7.1.1", "cron": "^3.1.7", + "crypto-js": "^4.2.0", "dd-trace": "^5.40.0", "dotenv": "^16.4.1", "fastify": "^4.28.1", @@ -138,6 +139,7 @@ "@babel/preset-env": "^7.18.10", "@babel/preset-react": "^7.24.7", "@types/bcrypt": "^5.0.2", + "@types/crypto-js": "^4.2.2", "@types/jmespath": "^0.15.2", "@types/jsonwebtoken": "^9.0.5", "@types/jsrp": "^0.2.6", @@ -12492,6 +12494,13 @@ "@types/node": "*" } }, + "node_modules/@types/crypto-js": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@types/crypto-js/-/crypto-js-4.2.2.tgz", + "integrity": "sha512-sDOLlVbHhXpAUAL0YHDUUwDZf3iN4Bwi4W6a0W0b+QcAezUbRtH4FVb+9J4h+XFPW7l/gQ9F8qC7P+Ec4k8QVQ==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/debug": { "version": "4.1.12", "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", @@ -15772,6 +15781,12 @@ "node": ">= 8" } }, + "node_modules/crypto-js": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz", + "integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==", + "license": "MIT" + }, "node_modules/crypto-randomuuid": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/crypto-randomuuid/-/crypto-randomuuid-1.0.0.tgz", diff --git a/backend/package.json b/backend/package.json index 128de7bd6c..8adc2bbef7 100644 --- a/backend/package.json +++ b/backend/package.json @@ -85,6 +85,7 @@ "@babel/preset-env": "^7.18.10", "@babel/preset-react": "^7.24.7", "@types/bcrypt": "^5.0.2", + "@types/crypto-js": "^4.2.2", "@types/jmespath": "^0.15.2", "@types/jsonwebtoken": "^9.0.5", "@types/jsrp": "^0.2.6", @@ -188,6 +189,7 @@ "cassandra-driver": "^4.7.2", "connect-redis": "^7.1.1", "cron": "^3.1.7", + "crypto-js": "^4.2.0", "dd-trace": "^5.40.0", "dotenv": "^16.4.1", "fastify": "^4.28.1", diff --git a/backend/src/@types/fastify-zod.d.ts b/backend/src/@types/fastify-zod.d.ts index 440e3393f1..366046e9c9 100644 --- a/backend/src/@types/fastify-zod.d.ts +++ b/backend/src/@types/fastify-zod.d.ts @@ -1,5 +1,6 @@ import { FastifyInstance, RawReplyDefaultExpression, RawRequestDefaultExpression, RawServerDefault } from "fastify"; +import { TCryptographyFactory } from "@app/lib/crypto/cryptography"; import { CustomLogger } from "@app/lib/logger/logger"; import { ZodTypeProvider } from "@app/server/plugins/fastify-zod"; @@ -14,5 +15,6 @@ declare global { // used only for testing const testServer: FastifyZodProvider; + const testCryptoProvider: TCryptographyFactory; const jwtAuthToken: string; } diff --git a/backend/src/db/migrations/20250210101840_webhook-to-kms.ts b/backend/src/db/migrations/20250210101840_webhook-to-kms.ts index a2d8563889..ff2503b324 100644 --- a/backend/src/db/migrations/20250210101840_webhook-to-kms.ts +++ b/backend/src/db/migrations/20250210101840_webhook-to-kms.ts @@ -1,7 +1,7 @@ import { Knex } from "knex"; import { inMemoryKeyStore } from "@app/keystore/memory"; -import { infisicalSymmetricDecrypt } from "@app/lib/crypto/encryption"; +import { crypto } from "@app/lib/crypto/cryptography"; import { initLogger } from "@app/lib/logger"; import { KmsDataKey } from "@app/services/kms/kms-types"; @@ -65,7 +65,7 @@ export async function up(knex: Knex): Promise { let encryptedSecretKey = null; if (el.encryptedSecretKey && el.iv && el.tag && el.keyEncoding) { - const decyptedSecretKey = infisicalSymmetricDecrypt({ + const decyptedSecretKey = crypto.encryption().decryptWithRootEncryptionKey({ keyEncoding: el.keyEncoding as SecretKeyEncoding, iv: el.iv, tag: el.tag, @@ -78,7 +78,7 @@ export async function up(knex: Knex): Promise { const decryptedUrl = el.urlIV && el.urlTag && el.urlCipherText && el.keyEncoding - ? infisicalSymmetricDecrypt({ + ? crypto.encryption().decryptWithRootEncryptionKey({ keyEncoding: el.keyEncoding as SecretKeyEncoding, iv: el.urlIV, tag: el.urlTag, diff --git a/backend/src/db/migrations/20250210101841_dynamic-secret-root-to-kms.ts b/backend/src/db/migrations/20250210101841_dynamic-secret-root-to-kms.ts index dde1e71889..e01fbb1db9 100644 --- a/backend/src/db/migrations/20250210101841_dynamic-secret-root-to-kms.ts +++ b/backend/src/db/migrations/20250210101841_dynamic-secret-root-to-kms.ts @@ -1,7 +1,7 @@ import { Knex } from "knex"; import { inMemoryKeyStore } from "@app/keystore/memory"; -import { infisicalSymmetricDecrypt } from "@app/lib/crypto/encryption"; +import { crypto } from "@app/lib/crypto/cryptography"; import { selectAllTableCols } from "@app/lib/knex"; import { initLogger } from "@app/lib/logger"; import { KmsDataKey } from "@app/services/kms/kms-types"; @@ -60,7 +60,7 @@ export async function up(knex: Knex): Promise { // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore This will be removed in next cycle so ignore the ts missing error el.inputIV && el.inputTag && el.inputCiphertext && el.keyEncoding - ? infisicalSymmetricDecrypt({ + ? crypto.encryption().decryptWithRootEncryptionKey({ // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore This will be removed in next cycle so ignore the ts missing error keyEncoding: el.keyEncoding as SecretKeyEncoding, diff --git a/backend/src/db/migrations/20250210101841_secret-rotation-to-kms.ts b/backend/src/db/migrations/20250210101841_secret-rotation-to-kms.ts index e11ef926e9..661d92f142 100644 --- a/backend/src/db/migrations/20250210101841_secret-rotation-to-kms.ts +++ b/backend/src/db/migrations/20250210101841_secret-rotation-to-kms.ts @@ -1,7 +1,7 @@ import { Knex } from "knex"; import { inMemoryKeyStore } from "@app/keystore/memory"; -import { infisicalSymmetricDecrypt } from "@app/lib/crypto/encryption"; +import { crypto } from "@app/lib/crypto/cryptography"; import { selectAllTableCols } from "@app/lib/knex"; import { initLogger } from "@app/lib/logger"; import { KmsDataKey } from "@app/services/kms/kms-types"; @@ -53,7 +53,7 @@ export async function up(knex: Knex): Promise { // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore This will be removed in next cycle so ignore the ts missing error el.encryptedDataTag && el.encryptedDataIV && el.encryptedData && el.keyEncoding - ? infisicalSymmetricDecrypt({ + ? crypto.encryption().decryptWithRootEncryptionKey({ // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore This will be removed in next cycle so ignore the ts missing error keyEncoding: el.keyEncoding as SecretKeyEncoding, diff --git a/backend/src/db/migrations/20250210101842_identity-k8-auth-to-kms.ts b/backend/src/db/migrations/20250210101842_identity-k8-auth-to-kms.ts index 934dce5e83..36c70c7040 100644 --- a/backend/src/db/migrations/20250210101842_identity-k8-auth-to-kms.ts +++ b/backend/src/db/migrations/20250210101842_identity-k8-auth-to-kms.ts @@ -1,7 +1,7 @@ import { Knex } from "knex"; import { inMemoryKeyStore } from "@app/keystore/memory"; -import { decryptSymmetric, infisicalSymmetricDecrypt } from "@app/lib/crypto/encryption"; +import { crypto, SymmetricKeySize } from "@app/lib/crypto/cryptography"; import { selectAllTableCols } from "@app/lib/knex"; import { initLogger } from "@app/lib/logger"; import { KmsDataKey } from "@app/services/kms/kms-types"; @@ -99,7 +99,7 @@ const reencryptIdentityK8sAuth = async (knex: Knex) => { orgEncryptionRingBuffer.push(orgId, orgKmsService); } - const key = infisicalSymmetricDecrypt({ + const key = crypto.encryption().decryptWithRootEncryptionKey({ ciphertext: encryptedSymmetricKey, iv: symmetricKeyIV, tag: symmetricKeyTag, @@ -110,8 +110,9 @@ const reencryptIdentityK8sAuth = async (knex: Knex) => { // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore This will be removed in next cycle so ignore the ts missing error el.encryptedTokenReviewerJwt && el.tokenReviewerJwtIV && el.tokenReviewerJwtTag - ? decryptSymmetric({ + ? crypto.encryption().decryptSymmetric({ key, + keySize: SymmetricKeySize.Bits256, // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore This will be removed in next cycle so ignore the ts missing error iv: el.tokenReviewerJwtIV, @@ -128,8 +129,9 @@ const reencryptIdentityK8sAuth = async (knex: Knex) => { // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore This will be removed in next cycle so ignore the ts missing error el.encryptedCaCert && el.caCertIV && el.caCertTag - ? decryptSymmetric({ + ? crypto.encryption().decryptSymmetric({ key, + keySize: SymmetricKeySize.Bits256, // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore This will be removed in next cycle so ignore the ts missing error iv: el.caCertIV, diff --git a/backend/src/db/migrations/20250210101842_identity-oidc-auth-to-kms.ts b/backend/src/db/migrations/20250210101842_identity-oidc-auth-to-kms.ts index 011585bdaa..c0496f8234 100644 --- a/backend/src/db/migrations/20250210101842_identity-oidc-auth-to-kms.ts +++ b/backend/src/db/migrations/20250210101842_identity-oidc-auth-to-kms.ts @@ -1,7 +1,7 @@ import { Knex } from "knex"; import { inMemoryKeyStore } from "@app/keystore/memory"; -import { decryptSymmetric, infisicalSymmetricDecrypt } from "@app/lib/crypto/encryption"; +import { crypto, SymmetricKeySize } from "@app/lib/crypto/cryptography"; import { selectAllTableCols } from "@app/lib/knex"; import { initLogger } from "@app/lib/logger"; import { KmsDataKey } from "@app/services/kms/kms-types"; @@ -71,7 +71,8 @@ const reencryptIdentityOidcAuth = async (knex: Knex) => { ); orgEncryptionRingBuffer.push(orgId, orgKmsService); } - const key = infisicalSymmetricDecrypt({ + + const key = crypto.encryption().decryptWithRootEncryptionKey({ ciphertext: encryptedSymmetricKey, iv: symmetricKeyIV, tag: symmetricKeyTag, @@ -82,8 +83,9 @@ const reencryptIdentityOidcAuth = async (knex: Knex) => { // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore This will be removed in next cycle so ignore the ts missing error el.encryptedCaCert && el.caCertIV && el.caCertTag - ? decryptSymmetric({ + ? crypto.encryption().decryptSymmetric({ key, + keySize: SymmetricKeySize.Bits256, // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore This will be removed in next cycle so ignore the ts missing error iv: el.caCertIV, diff --git a/backend/src/db/migrations/20250210101845_directory-config-to-kms.ts b/backend/src/db/migrations/20250210101845_directory-config-to-kms.ts index f5107b3017..b07fe77878 100644 --- a/backend/src/db/migrations/20250210101845_directory-config-to-kms.ts +++ b/backend/src/db/migrations/20250210101845_directory-config-to-kms.ts @@ -1,7 +1,7 @@ import { Knex } from "knex"; import { inMemoryKeyStore } from "@app/keystore/memory"; -import { decryptSymmetric, infisicalSymmetricDecrypt } from "@app/lib/crypto/encryption"; +import { crypto, SymmetricKeySize } from "@app/lib/crypto/cryptography"; import { selectAllTableCols } from "@app/lib/knex"; import { initLogger } from "@app/lib/logger"; import { KmsDataKey } from "@app/services/kms/kms-types"; @@ -58,7 +58,8 @@ const reencryptSamlConfig = async (knex: Knex) => { ); orgEncryptionRingBuffer.push(el.orgId, orgKmsService); } - const key = infisicalSymmetricDecrypt({ + + const key = crypto.encryption().decryptWithRootEncryptionKey({ ciphertext: encryptedSymmetricKey, iv: symmetricKeyIV, tag: symmetricKeyTag, @@ -69,8 +70,9 @@ const reencryptSamlConfig = async (knex: Knex) => { // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore This will be removed in next cycle so ignore the ts missing error el.encryptedEntryPoint && el.entryPointIV && el.entryPointTag - ? decryptSymmetric({ + ? crypto.encryption().decryptSymmetric({ key, + keySize: SymmetricKeySize.Bits256, // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore This will be removed in next cycle so ignore the ts missing error iv: el.entryPointIV, @@ -87,8 +89,9 @@ const reencryptSamlConfig = async (knex: Knex) => { // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore This will be removed in next cycle so ignore the ts missing error el.encryptedIssuer && el.issuerIV && el.issuerTag - ? decryptSymmetric({ + ? crypto.encryption().decryptSymmetric({ key, + keySize: SymmetricKeySize.Bits256, // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore This will be removed in next cycle so ignore the ts missing error iv: el.issuerIV, @@ -105,8 +108,9 @@ const reencryptSamlConfig = async (knex: Knex) => { // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore This will be removed in next cycle so ignore the ts missing error el.encryptedCert && el.certIV && el.certTag - ? decryptSymmetric({ + ? crypto.encryption().decryptSymmetric({ key, + keySize: SymmetricKeySize.Bits256, // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore This will be removed in next cycle so ignore the ts missing error iv: el.certIV, @@ -216,7 +220,8 @@ const reencryptLdapConfig = async (knex: Knex) => { ); orgEncryptionRingBuffer.push(el.orgId, orgKmsService); } - const key = infisicalSymmetricDecrypt({ + + const key = crypto.encryption().decryptWithRootEncryptionKey({ ciphertext: encryptedSymmetricKey, iv: symmetricKeyIV, tag: symmetricKeyTag, @@ -227,8 +232,9 @@ const reencryptLdapConfig = async (knex: Knex) => { // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore This will be removed in next cycle so ignore the ts missing error el.encryptedBindDN && el.bindDNIV && el.bindDNTag - ? decryptSymmetric({ + ? crypto.encryption().decryptSymmetric({ key, + keySize: SymmetricKeySize.Bits256, // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore This will be removed in next cycle so ignore the ts missing error iv: el.bindDNIV, @@ -245,8 +251,9 @@ const reencryptLdapConfig = async (knex: Knex) => { // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore This will be removed in next cycle so ignore the ts missing error el.encryptedBindPass && el.bindPassIV && el.bindPassTag - ? decryptSymmetric({ + ? crypto.encryption().decryptSymmetric({ key, + keySize: SymmetricKeySize.Bits256, // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore This will be removed in next cycle so ignore the ts missing error iv: el.bindPassIV, @@ -263,8 +270,9 @@ const reencryptLdapConfig = async (knex: Knex) => { // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore This will be removed in next cycle so ignore the ts missing error el.encryptedCACert && el.caCertIV && el.caCertTag - ? decryptSymmetric({ + ? crypto.encryption().decryptSymmetric({ key, + keySize: SymmetricKeySize.Bits256, // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore This will be removed in next cycle so ignore the ts missing error iv: el.caCertIV, @@ -368,7 +376,8 @@ const reencryptOidcConfig = async (knex: Knex) => { ); orgEncryptionRingBuffer.push(el.orgId, orgKmsService); } - const key = infisicalSymmetricDecrypt({ + + const key = crypto.encryption().decryptWithRootEncryptionKey({ ciphertext: encryptedSymmetricKey, iv: symmetricKeyIV, tag: symmetricKeyTag, @@ -379,8 +388,9 @@ const reencryptOidcConfig = async (knex: Knex) => { // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore This will be removed in next cycle so ignore the ts missing error el.encryptedClientId && el.clientIdIV && el.clientIdTag - ? decryptSymmetric({ + ? crypto.encryption().decryptSymmetric({ key, + keySize: SymmetricKeySize.Bits256, // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore This will be removed in next cycle so ignore the ts missing error iv: el.clientIdIV, @@ -397,8 +407,9 @@ const reencryptOidcConfig = async (knex: Knex) => { // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore This will be removed in next cycle so ignore the ts missing error el.encryptedClientSecret && el.clientSecretIV && el.clientSecretTag - ? decryptSymmetric({ + ? crypto.encryption().decryptSymmetric({ key, + keySize: SymmetricKeySize.Bits256, // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore This will be removed in next cycle so ignore the ts missing error iv: el.clientSecretIV, diff --git a/backend/src/db/migrations/20250705074703_fips-mode.ts b/backend/src/db/migrations/20250705074703_fips-mode.ts new file mode 100644 index 0000000000..45d23bf6f5 --- /dev/null +++ b/backend/src/db/migrations/20250705074703_fips-mode.ts @@ -0,0 +1,23 @@ +import { Knex } from "knex"; + +import { TableName } from "../schemas"; + +export async function up(knex: Knex): Promise { + const hasFipsModeColumn = await knex.schema.hasColumn(TableName.SuperAdmin, "fipsEnabled"); + + if (!hasFipsModeColumn) { + await knex.schema.alterTable(TableName.SuperAdmin, (table) => { + table.boolean("fipsEnabled").notNullable().defaultTo(false); + }); + } +} + +export async function down(knex: Knex): Promise { + const hasFipsModeColumn = await knex.schema.hasColumn(TableName.SuperAdmin, "fipsEnabled"); + + if (hasFipsModeColumn) { + await knex.schema.alterTable(TableName.SuperAdmin, (table) => { + table.dropColumn("fipsEnabled"); + }); + } +} diff --git a/backend/src/db/schemas/super-admin.ts b/backend/src/db/schemas/super-admin.ts index de4975b201..9df8cc0db9 100644 --- a/backend/src/db/schemas/super-admin.ts +++ b/backend/src/db/schemas/super-admin.ts @@ -34,7 +34,9 @@ export const SuperAdminSchema = z.object({ encryptedGitHubAppConnectionClientSecret: zodBuffer.nullable().optional(), encryptedGitHubAppConnectionSlug: zodBuffer.nullable().optional(), encryptedGitHubAppConnectionId: zodBuffer.nullable().optional(), - encryptedGitHubAppConnectionPrivateKey: zodBuffer.nullable().optional() + encryptedGitHubAppConnectionPrivateKey: zodBuffer.nullable().optional(), + encryptedEnvOverrides: zodBuffer.nullable().optional(), + fipsEnabled: z.boolean().default(false) }); export type TSuperAdmin = z.infer; diff --git a/backend/src/db/seed-data.ts b/backend/src/db/seed-data.ts index 47ef15d907..2822a785b2 100644 --- a/backend/src/db/seed-data.ts +++ b/backend/src/db/seed-data.ts @@ -1,18 +1,8 @@ /* eslint-disable import/no-mutable-exports */ -import crypto from "node:crypto"; - import argon2, { argon2id } from "argon2"; import jsrp from "jsrp"; -import nacl from "tweetnacl"; -import { encodeBase64 } from "tweetnacl-util"; -import { - decryptAsymmetric, - // decryptAsymmetric, - decryptSymmetric128BitHexKeyUTF8, - encryptAsymmetric, - encryptSymmetric128BitHexKeyUTF8 -} from "@app/lib/crypto"; +import { crypto, SymmetricKeySize } from "@app/lib/crypto/cryptography"; import { TSecrets, TUserEncryptionKeys } from "./schemas"; @@ -62,11 +52,7 @@ export const seedData1 = { }; export const generateUserSrpKeys = async (password: string) => { - const pair = nacl.box.keyPair(); - const secretKeyUint8Array = pair.secretKey; - const publicKeyUint8Array = pair.publicKey; - const privateKey = encodeBase64(secretKeyUint8Array); - const publicKey = encodeBase64(publicKeyUint8Array); + const { publicKey, privateKey } = crypto.encryption().asymmetric().generateKeyPair(); // eslint-disable-next-line const client = new jsrp.client(); @@ -98,7 +84,11 @@ export const generateUserSrpKeys = async (password: string) => { ciphertext: encryptedPrivateKey, iv: encryptedPrivateKeyIV, tag: encryptedPrivateKeyTag - } = encryptSymmetric128BitHexKeyUTF8(privateKey, key); + } = crypto.encryption().encryptSymmetric({ + plaintext: privateKey, + key, + keySize: SymmetricKeySize.Bits128 + }); // create the protected key by encrypting the symmetric key // [key] with the derived key @@ -106,7 +96,9 @@ export const generateUserSrpKeys = async (password: string) => { ciphertext: protectedKey, iv: protectedKeyIV, tag: protectedKeyTag - } = encryptSymmetric128BitHexKeyUTF8(key.toString("hex"), derivedKey); + } = crypto + .encryption() + .encryptSymmetric({ plaintext: key.toString("hex"), key: derivedKey, keySize: SymmetricKeySize.Bits128 }); return { protectedKey, @@ -133,30 +125,32 @@ export const getUserPrivateKey = async (password: string, user: TUserEncryptionK }); if (!derivedKey) throw new Error("Failed to derive key from password"); - const key = decryptSymmetric128BitHexKeyUTF8({ + const key = crypto.encryption().decryptSymmetric({ ciphertext: user.protectedKey as string, iv: user.protectedKeyIV as string, tag: user.protectedKeyTag as string, - key: derivedKey + key: derivedKey, + keySize: SymmetricKeySize.Bits128 }); - const privateKey = decryptSymmetric128BitHexKeyUTF8({ + const privateKey = crypto.encryption().decryptSymmetric({ ciphertext: user.encryptedPrivateKey, iv: user.iv, tag: user.tag, - key: Buffer.from(key, "hex") + key: Buffer.from(key, "hex"), + keySize: SymmetricKeySize.Bits128 }); return privateKey; }; export const buildUserProjectKey = (privateKey: string, publickey: string) => { const randomBytes = crypto.randomBytes(16).toString("hex"); - const { nonce, ciphertext } = encryptAsymmetric(randomBytes, publickey, privateKey); + const { nonce, ciphertext } = crypto.encryption().asymmetric().encrypt(randomBytes, publickey, privateKey); return { nonce, ciphertext }; }; export const getUserProjectKey = async (privateKey: string, ciphertext: string, nonce: string, publicKey: string) => { - return decryptAsymmetric({ + return crypto.encryption().asymmetric().decrypt({ ciphertext, nonce, publicKey, @@ -170,21 +164,33 @@ export const encryptSecret = (encKey: string, key: string, value?: string, comme ciphertext: secretKeyCiphertext, iv: secretKeyIV, tag: secretKeyTag - } = encryptSymmetric128BitHexKeyUTF8(key, encKey); + } = crypto.encryption().encryptSymmetric({ + plaintext: key, + key: encKey, + keySize: SymmetricKeySize.Bits128 + }); // encrypt value const { ciphertext: secretValueCiphertext, iv: secretValueIV, tag: secretValueTag - } = encryptSymmetric128BitHexKeyUTF8(value ?? "", encKey); + } = crypto.encryption().encryptSymmetric({ + plaintext: value ?? "", + key: encKey, + keySize: SymmetricKeySize.Bits128 + }); // encrypt comment const { ciphertext: secretCommentCiphertext, iv: secretCommentIV, tag: secretCommentTag - } = encryptSymmetric128BitHexKeyUTF8(comment ?? "", encKey); + } = crypto.encryption().encryptSymmetric({ + plaintext: comment ?? "", + key: encKey, + keySize: SymmetricKeySize.Bits128 + }); return { secretKeyCiphertext, @@ -200,27 +206,30 @@ export const encryptSecret = (encKey: string, key: string, value?: string, comme }; export const decryptSecret = (decryptKey: string, encSecret: TSecrets) => { - const secretKey = decryptSymmetric128BitHexKeyUTF8({ + const secretKey = crypto.encryption().decryptSymmetric({ key: decryptKey, ciphertext: encSecret.secretKeyCiphertext, tag: encSecret.secretKeyTag, - iv: encSecret.secretKeyIV + iv: encSecret.secretKeyIV, + keySize: SymmetricKeySize.Bits128 }); - const secretValue = decryptSymmetric128BitHexKeyUTF8({ + const secretValue = crypto.encryption().decryptSymmetric({ key: decryptKey, ciphertext: encSecret.secretValueCiphertext, tag: encSecret.secretValueTag, - iv: encSecret.secretValueIV + iv: encSecret.secretValueIV, + keySize: SymmetricKeySize.Bits128 }); const secretComment = encSecret.secretCommentIV && encSecret.secretCommentTag && encSecret.secretCommentCiphertext - ? decryptSymmetric128BitHexKeyUTF8({ + ? crypto.encryption().decryptSymmetric({ key: decryptKey, ciphertext: encSecret.secretCommentCiphertext, tag: encSecret.secretCommentTag, - iv: encSecret.secretCommentIV + iv: encSecret.secretCommentIV, + keySize: SymmetricKeySize.Bits128 }) : ""; diff --git a/backend/src/db/seeds/3-project.ts b/backend/src/db/seeds/3-project.ts index b6c80bb63f..91a309aa64 100644 --- a/backend/src/db/seeds/3-project.ts +++ b/backend/src/db/seeds/3-project.ts @@ -1,8 +1,6 @@ -import crypto from "node:crypto"; - import { Knex } from "knex"; -import { encryptSymmetric128BitHexKeyUTF8 } from "@app/lib/crypto"; +import { crypto, SymmetricKeySize } from "@app/lib/crypto/cryptography"; import { ProjectMembershipRole, ProjectType, SecretEncryptionAlgo, SecretKeyEncoding, TableName } from "../schemas"; import { buildUserProjectKey, getUserPrivateKey, seedData1 } from "../seed-data"; @@ -72,7 +70,11 @@ export async function seed(knex: Knex): Promise { const encKey = process.env.ENCRYPTION_KEY; if (!encKey) throw new Error("Missing ENCRYPTION_KEY"); const salt = crypto.randomBytes(16).toString("base64"); - const secretBlindIndex = encryptSymmetric128BitHexKeyUTF8(salt, encKey); + const secretBlindIndex = crypto.encryption().encryptSymmetric({ + plaintext: salt, + key: encKey, + keySize: SymmetricKeySize.Bits128 + }); // insert secret blind index for project await knex(TableName.SecretBlindIndex).insert({ projectId: project.id, diff --git a/backend/src/db/seeds/5-machine-identity.ts b/backend/src/db/seeds/5-machine-identity.ts index 3798d4bf32..391f785ecb 100644 --- a/backend/src/db/seeds/5-machine-identity.ts +++ b/backend/src/db/seeds/5-machine-identity.ts @@ -1,6 +1,7 @@ -import bcrypt from "bcrypt"; import { Knex } from "knex"; +import { crypto } from "@app/lib/crypto/cryptography"; + import { IdentityAuthMethod, OrgMembershipRole, ProjectMembershipRole, TableName } from "../schemas"; import { seedData1 } from "../seed-data"; @@ -54,7 +55,9 @@ export async function seed(knex: Knex): Promise { } ]) .returning("*"); - const clientSecretHash = await bcrypt.hash(seedData1.machineIdentity.clientCredentials.secret, 10); + + const clientSecretHash = await crypto.hashing().createHash(seedData1.machineIdentity.clientCredentials.secret, 10); + await knex(TableName.IdentityUaClientSecret).insert([ { identityUAId: identityUa[0].id, diff --git a/backend/src/ee/routes/est/certificate-est-router.ts b/backend/src/ee/routes/est/certificate-est-router.ts index e67d037eaf..33ebe910da 100644 --- a/backend/src/ee/routes/est/certificate-est-router.ts +++ b/backend/src/ee/routes/est/certificate-est-router.ts @@ -1,7 +1,7 @@ -import bcrypt from "bcrypt"; import { z } from "zod"; import { getConfig } from "@app/lib/config/env"; +import { crypto } from "@app/lib/crypto/cryptography"; import { BadRequestError, UnauthorizedError } from "@app/lib/errors"; import { readLimit, writeLimit } from "@app/server/config/rateLimiter"; @@ -85,7 +85,7 @@ export const registerCertificateEstRouter = async (server: FastifyZodProvider) = }); } - const isPasswordValid = await bcrypt.compare(password, estConfig.hashedPassphrase); + const isPasswordValid = await crypto.hashing().compareHash(password, estConfig.hashedPassphrase); if (!isPasswordValid) { throw new UnauthorizedError({ message: "Invalid credentials" diff --git a/backend/src/ee/services/audit-log-stream/audit-log-stream-service.ts b/backend/src/ee/services/audit-log-stream/audit-log-stream-service.ts index 65e49bdea4..2c7086e485 100644 --- a/backend/src/ee/services/audit-log-stream/audit-log-stream-service.ts +++ b/backend/src/ee/services/audit-log-stream/audit-log-stream-service.ts @@ -4,7 +4,7 @@ import { RawAxiosRequestHeaders } from "axios"; import { SecretKeyEncoding } from "@app/db/schemas"; import { getConfig } from "@app/lib/config/env"; import { request } from "@app/lib/config/request"; -import { infisicalSymmetricDecrypt, infisicalSymmetricEncypt } from "@app/lib/crypto/encryption"; +import { crypto } from "@app/lib/crypto/cryptography"; import { BadRequestError, NotFoundError, UnauthorizedError } from "@app/lib/errors"; import { blockLocalAndPrivateIpAddresses } from "@app/lib/validator"; @@ -86,7 +86,10 @@ export const auditLogStreamServiceFactory = ({ .catch((err) => { throw new BadRequestError({ message: `Failed to connect with upstream source: ${(err as Error)?.message}` }); }); - const encryptedHeaders = headers ? infisicalSymmetricEncypt(JSON.stringify(headers)) : undefined; + + const encryptedHeaders = headers + ? crypto.encryption().encryptWithRootEncryptionKey(JSON.stringify(headers)) + : undefined; const logStream = await auditLogStreamDAL.create({ orgId: actorOrgId, url, @@ -152,7 +155,9 @@ export const auditLogStreamServiceFactory = ({ throw new Error(`Failed to connect with the source ${(err as Error)?.message}`); }); - const encryptedHeaders = headers ? infisicalSymmetricEncypt(JSON.stringify(headers)) : undefined; + const encryptedHeaders = headers + ? crypto.encryption().encryptWithRootEncryptionKey(JSON.stringify(headers)) + : undefined; const updatedLogStream = await auditLogStreamDAL.updateById(id, { url, ...(encryptedHeaders @@ -205,7 +210,7 @@ export const auditLogStreamServiceFactory = ({ const headers = logStream?.encryptedHeadersCiphertext && logStream?.encryptedHeadersIV && logStream?.encryptedHeadersTag ? (JSON.parse( - infisicalSymmetricDecrypt({ + crypto.encryption().decryptWithRootEncryptionKey({ tag: logStream.encryptedHeadersTag, iv: logStream.encryptedHeadersIV, ciphertext: logStream.encryptedHeadersCiphertext, diff --git a/backend/src/ee/services/audit-log/audit-log-queue.ts b/backend/src/ee/services/audit-log/audit-log-queue.ts index 5cd1f507e7..09d6668892 100644 --- a/backend/src/ee/services/audit-log/audit-log-queue.ts +++ b/backend/src/ee/services/audit-log/audit-log-queue.ts @@ -3,7 +3,7 @@ import { AxiosError, RawAxiosRequestHeaders } from "axios"; import { SecretKeyEncoding } from "@app/db/schemas"; import { getConfig } from "@app/lib/config/env"; import { request } from "@app/lib/config/request"; -import { infisicalSymmetricDecrypt } from "@app/lib/crypto/encryption"; +import { crypto } from "@app/lib/crypto/cryptography"; import { logger } from "@app/lib/logger"; import { QueueJobs, QueueName, TQueueServiceFactory } from "@app/queue"; import { TProjectDALFactory } from "@app/services/project/project-dal"; @@ -114,7 +114,7 @@ export const auditLogQueueServiceFactory = async ({ const streamHeaders = encryptedHeadersIV && encryptedHeadersCiphertext && encryptedHeadersTag ? (JSON.parse( - infisicalSymmetricDecrypt({ + crypto.encryption().decryptWithRootEncryptionKey({ keyEncoding: encryptedHeadersKeyEncoding as SecretKeyEncoding, iv: encryptedHeadersIV, tag: encryptedHeadersTag, @@ -216,7 +216,7 @@ export const auditLogQueueServiceFactory = async ({ const streamHeaders = encryptedHeadersIV && encryptedHeadersCiphertext && encryptedHeadersTag ? (JSON.parse( - infisicalSymmetricDecrypt({ + crypto.encryption().decryptWithRootEncryptionKey({ keyEncoding: encryptedHeadersKeyEncoding as SecretKeyEncoding, iv: encryptedHeadersIV, tag: encryptedHeadersTag, diff --git a/backend/src/ee/services/dynamic-secret/providers/aws-iam.ts b/backend/src/ee/services/dynamic-secret/providers/aws-iam.ts index ec75bb2e41..cb8da4d130 100644 --- a/backend/src/ee/services/dynamic-secret/providers/aws-iam.ts +++ b/backend/src/ee/services/dynamic-secret/providers/aws-iam.ts @@ -17,10 +17,10 @@ import { RemoveUserFromGroupCommand } from "@aws-sdk/client-iam"; import { AssumeRoleCommand, STSClient } from "@aws-sdk/client-sts"; -import { randomUUID } from "crypto"; import { z } from "zod"; import { getConfig } from "@app/lib/config/env"; +import { crypto } from "@app/lib/crypto/cryptography"; import { BadRequestError } from "@app/lib/errors"; import { alphaNumericNanoId } from "@app/lib/nanoid"; @@ -60,7 +60,7 @@ export const AwsIamProvider = (): TDynamicProviderFns => { const command = new AssumeRoleCommand({ RoleArn: providerInputs.roleArn, - RoleSessionName: `infisical-dynamic-secret-${randomUUID()}`, + RoleSessionName: `infisical-dynamic-secret-${crypto.rawCrypto.randomUUID()}`, DurationSeconds: 900, // 15 mins ExternalId: projectId }); diff --git a/backend/src/ee/services/dynamic-secret/providers/sql-database.ts b/backend/src/ee/services/dynamic-secret/providers/sql-database.ts index d3217be379..c86e3aff5e 100644 --- a/backend/src/ee/services/dynamic-secret/providers/sql-database.ts +++ b/backend/src/ee/services/dynamic-secret/providers/sql-database.ts @@ -1,8 +1,8 @@ -import { randomInt } from "crypto"; import handlebars from "handlebars"; import knex from "knex"; import { z } from "zod"; +import { crypto } from "@app/lib/crypto/cryptography"; import { GatewayProxyProtocol, withGatewayProxy } from "@app/lib/gateway"; import { alphaNumericNanoId } from "@app/lib/nanoid"; import { validateHandlebarTemplate } from "@app/lib/template/validate-handlebars"; @@ -50,7 +50,7 @@ const generatePassword = (provider: SqlProviders, requirements?: PasswordRequire parts.push( ...Array(required.lowercase) .fill(0) - .map(() => chars.lowercase[randomInt(chars.lowercase.length)]) + .map(() => chars.lowercase[crypto.randomInt(chars.lowercase.length)]) ); } @@ -58,7 +58,7 @@ const generatePassword = (provider: SqlProviders, requirements?: PasswordRequire parts.push( ...Array(required.uppercase) .fill(0) - .map(() => chars.uppercase[randomInt(chars.uppercase.length)]) + .map(() => chars.uppercase[crypto.randomInt(chars.uppercase.length)]) ); } @@ -66,7 +66,7 @@ const generatePassword = (provider: SqlProviders, requirements?: PasswordRequire parts.push( ...Array(required.digits) .fill(0) - .map(() => chars.digits[randomInt(chars.digits.length)]) + .map(() => chars.digits[crypto.randomInt(chars.digits.length)]) ); } @@ -74,7 +74,7 @@ const generatePassword = (provider: SqlProviders, requirements?: PasswordRequire parts.push( ...Array(required.symbols) .fill(0) - .map(() => chars.symbols[randomInt(chars.symbols.length)]) + .map(() => chars.symbols[crypto.randomInt(chars.symbols.length)]) ); } @@ -89,12 +89,12 @@ const generatePassword = (provider: SqlProviders, requirements?: PasswordRequire parts.push( ...Array(remainingLength) .fill(0) - .map(() => allowedChars[randomInt(allowedChars.length)]) + .map(() => allowedChars[crypto.randomInt(allowedChars.length)]) ); // shuffle the array to mix up the characters for (let i = parts.length - 1; i > 0; i -= 1) { - const j = randomInt(i + 1); + const j = crypto.randomInt(i + 1); [parts[i], parts[j]] = [parts[j], parts[i]]; } diff --git a/backend/src/ee/services/dynamic-secret/providers/vertica.ts b/backend/src/ee/services/dynamic-secret/providers/vertica.ts index e361ab3297..0e60cddb38 100644 --- a/backend/src/ee/services/dynamic-secret/providers/vertica.ts +++ b/backend/src/ee/services/dynamic-secret/providers/vertica.ts @@ -1,8 +1,8 @@ -import { randomInt } from "crypto"; import handlebars from "handlebars"; import knex, { Knex } from "knex"; import { z } from "zod"; +import { crypto } from "@app/lib/crypto/cryptography"; import { BadRequestError } from "@app/lib/errors"; import { GatewayProxyProtocol, withGatewayProxy } from "@app/lib/gateway"; import { logger } from "@app/lib/logger"; @@ -64,7 +64,7 @@ const generatePassword = (requirements?: PasswordRequirements) => { parts.push( ...Array(required.lowercase) .fill(0) - .map(() => chars.lowercase[randomInt(chars.lowercase.length)]) + .map(() => chars.lowercase[crypto.randomInt(chars.lowercase.length)]) ); } @@ -72,7 +72,7 @@ const generatePassword = (requirements?: PasswordRequirements) => { parts.push( ...Array(required.uppercase) .fill(0) - .map(() => chars.uppercase[randomInt(chars.uppercase.length)]) + .map(() => chars.uppercase[crypto.randomInt(chars.uppercase.length)]) ); } @@ -80,7 +80,7 @@ const generatePassword = (requirements?: PasswordRequirements) => { parts.push( ...Array(required.digits) .fill(0) - .map(() => chars.digits[randomInt(chars.digits.length)]) + .map(() => chars.digits[crypto.randomInt(chars.digits.length)]) ); } @@ -88,7 +88,7 @@ const generatePassword = (requirements?: PasswordRequirements) => { parts.push( ...Array(required.symbols) .fill(0) - .map(() => chars.symbols[randomInt(chars.symbols.length)]) + .map(() => chars.symbols[crypto.randomInt(chars.symbols.length)]) ); } @@ -103,12 +103,12 @@ const generatePassword = (requirements?: PasswordRequirements) => { parts.push( ...Array(remainingLength) .fill(0) - .map(() => allowedChars[randomInt(allowedChars.length)]) + .map(() => allowedChars[crypto.randomInt(allowedChars.length)]) ); // shuffle the array to mix up the characters for (let i = parts.length - 1; i > 0; i -= 1) { - const j = randomInt(i + 1); + const j = crypto.randomInt(i + 1); [parts[i], parts[j]] = [parts[j], parts[i]]; } diff --git a/backend/src/ee/services/external-kms/providers/aws-kms.ts b/backend/src/ee/services/external-kms/providers/aws-kms.ts index 2bda9c75e7..2aba6db8a7 100644 --- a/backend/src/ee/services/external-kms/providers/aws-kms.ts +++ b/backend/src/ee/services/external-kms/providers/aws-kms.ts @@ -1,6 +1,7 @@ import { CreateKeyCommand, DecryptCommand, DescribeKeyCommand, EncryptCommand, KMSClient } from "@aws-sdk/client-kms"; import { AssumeRoleCommand, STSClient } from "@aws-sdk/client-sts"; -import { randomUUID } from "crypto"; + +import { crypto } from "@app/lib/crypto/cryptography"; import { ExternalKmsAwsSchema, KmsAwsCredentialType, TExternalKmsAwsSchema, TExternalKmsProviderFns } from "./model"; @@ -12,7 +13,7 @@ const getAwsKmsClient = async (providerInputs: TExternalKmsAwsSchema) => { }); const command = new AssumeRoleCommand({ RoleArn: awsCredential.assumeRoleArn, - RoleSessionName: `infisical-kms-${randomUUID()}`, + RoleSessionName: `infisical-kms-${crypto.rawCrypto.randomUUID()}`, DurationSeconds: 900, // 15mins ExternalId: awsCredential.externalId }); diff --git a/backend/src/ee/services/gateway/gateway-service.ts b/backend/src/ee/services/gateway/gateway-service.ts index ffef3e007b..118bff9bde 100644 --- a/backend/src/ee/services/gateway/gateway-service.ts +++ b/backend/src/ee/services/gateway/gateway-service.ts @@ -1,11 +1,10 @@ -import crypto from "node:crypto"; - import { ForbiddenError } from "@casl/ability"; import * as x509 from "@peculiar/x509"; import { z } from "zod"; import { KeyStorePrefixes, PgSqlLock, TKeyStoreFactory } from "@app/keystore/keystore"; import { getConfig } from "@app/lib/config/env"; +import { crypto } from "@app/lib/crypto/cryptography"; import { BadRequestError, NotFoundError } from "@app/lib/errors"; import { pingGatewayAndVerify } from "@app/lib/gateway"; import { alphaNumericNanoId } from "@app/lib/nanoid"; @@ -149,9 +148,9 @@ export const gatewayServiceFactory = ({ const alg = keyAlgorithmToAlgCfg(CertKeyAlgorithm.RSA_2048); // generate root CA - const rootCaKeys = await crypto.subtle.generateKey(alg, true, ["sign", "verify"]); + const rootCaKeys = await crypto.rawCrypto.subtle.generateKey(alg, true, ["sign", "verify"]); const rootCaSerialNumber = createSerialNumber(); - const rootCaSkObj = crypto.KeyObject.from(rootCaKeys.privateKey); + const rootCaSkObj = crypto.rawCrypto.KeyObject.from(rootCaKeys.privateKey); const rootCaIssuedAt = new Date(); const rootCaKeyAlgorithm = CertKeyAlgorithm.RSA_2048; const rootCaExpiration = new Date(new Date().setFullYear(2045)); @@ -173,8 +172,8 @@ export const gatewayServiceFactory = ({ const clientCaSerialNumber = createSerialNumber(); const clientCaIssuedAt = new Date(); const clientCaExpiration = new Date(new Date().setFullYear(2045)); - const clientCaKeys = await crypto.subtle.generateKey(alg, true, ["sign", "verify"]); - const clientCaSkObj = crypto.KeyObject.from(clientCaKeys.privateKey); + const clientCaKeys = await crypto.rawCrypto.subtle.generateKey(alg, true, ["sign", "verify"]); + const clientCaSkObj = crypto.rawCrypto.KeyObject.from(clientCaKeys.privateKey); const clientCaCert = await x509.X509CertificateGenerator.create({ serialNumber: clientCaSerialNumber, @@ -200,7 +199,7 @@ export const gatewayServiceFactory = ({ ] }); - const clientKeys = await crypto.subtle.generateKey(alg, true, ["sign", "verify"]); + const clientKeys = await crypto.rawCrypto.subtle.generateKey(alg, true, ["sign", "verify"]); const clientCertSerialNumber = createSerialNumber(); const clientCert = await x509.X509CertificateGenerator.create({ serialNumber: clientCertSerialNumber, @@ -226,14 +225,14 @@ export const gatewayServiceFactory = ({ new x509.ExtendedKeyUsageExtension([x509.ExtendedKeyUsage[CertExtendedKeyUsage.CLIENT_AUTH]], true) ] }); - const clientSkObj = crypto.KeyObject.from(clientKeys.privateKey); + const clientSkObj = crypto.rawCrypto.KeyObject.from(clientKeys.privateKey); // generate gateway ca const gatewayCaSerialNumber = createSerialNumber(); const gatewayCaIssuedAt = new Date(); const gatewayCaExpiration = new Date(new Date().setFullYear(2045)); - const gatewayCaKeys = await crypto.subtle.generateKey(alg, true, ["sign", "verify"]); - const gatewayCaSkObj = crypto.KeyObject.from(gatewayCaKeys.privateKey); + const gatewayCaKeys = await crypto.rawCrypto.subtle.generateKey(alg, true, ["sign", "verify"]); + const gatewayCaSkObj = crypto.rawCrypto.KeyObject.from(gatewayCaKeys.privateKey); const gatewayCaCert = await x509.X509CertificateGenerator.create({ serialNumber: gatewayCaSerialNumber, subject: `O=${identityOrg},CN=Gateway CA`, @@ -326,7 +325,7 @@ export const gatewayServiceFactory = ({ ); const gatewayCaAlg = keyAlgorithmToAlgCfg(orgGatewayConfig.rootCaKeyAlgorithm as CertKeyAlgorithm); - const gatewayCaSkObj = crypto.createPrivateKey({ + const gatewayCaSkObj = crypto.rawCrypto.createPrivateKey({ key: orgKmsDecryptor({ cipherTextBlob: orgGatewayConfig.encryptedGatewayCaPrivateKey }), format: "der", type: "pkcs8" @@ -337,7 +336,7 @@ export const gatewayServiceFactory = ({ }) ); - const gatewayCaPrivateKey = await crypto.subtle.importKey( + const gatewayCaPrivateKey = await crypto.rawCrypto.subtle.importKey( "pkcs8", gatewayCaSkObj.export({ format: "der", type: "pkcs8" }), gatewayCaAlg, @@ -346,7 +345,7 @@ export const gatewayServiceFactory = ({ ); const alg = keyAlgorithmToAlgCfg(CertKeyAlgorithm.RSA_2048); - const gatewayKeys = await crypto.subtle.generateKey(alg, true, ["sign", "verify"]); + const gatewayKeys = await crypto.rawCrypto.subtle.generateKey(alg, true, ["sign", "verify"]); const certIssuedAt = new Date(); // then need to periodically init const certExpireAt = new Date(new Date().setMonth(new Date().getMonth() + 1)); @@ -367,7 +366,7 @@ export const gatewayServiceFactory = ({ ]; const serialNumber = createSerialNumber(); - const privateKey = crypto.KeyObject.from(gatewayKeys.privateKey); + const privateKey = crypto.rawCrypto.KeyObject.from(gatewayKeys.privateKey); const gatewayCertificate = await x509.X509CertificateGenerator.create({ serialNumber, subject: `CN=${identityId},O=${identityOrg},OU=Gateway`, @@ -454,7 +453,7 @@ export const gatewayServiceFactory = ({ }) ); - const privateKey = crypto + const privateKey = crypto.rawCrypto .createPrivateKey({ key: orgKmsDecryptor({ cipherTextBlob: orgGatewayConfig.encryptedClientPrivateKey }), format: "der", @@ -588,7 +587,7 @@ export const gatewayServiceFactory = ({ }) ); - const clientSkObj = crypto.createPrivateKey({ + const clientSkObj = crypto.rawCrypto.createPrivateKey({ key: orgKmsDecryptor({ cipherTextBlob: orgGatewayConfig.encryptedClientPrivateKey }), format: "der", type: "pkcs8" diff --git a/backend/src/ee/services/group/group-fns.ts b/backend/src/ee/services/group/group-fns.ts index 72d052b298..af52d2f214 100644 --- a/backend/src/ee/services/group/group-fns.ts +++ b/backend/src/ee/services/group/group-fns.ts @@ -1,7 +1,7 @@ import { Knex } from "knex"; import { SecretKeyEncoding, TableName, TUsers } from "@app/db/schemas"; -import { decryptAsymmetric, encryptAsymmetric, infisicalSymmetricDecrypt } from "@app/lib/crypto/encryption"; +import { crypto } from "@app/lib/crypto/cryptography"; import { BadRequestError, ForbiddenRequestError, NotFoundError, ScimRequestError } from "@app/lib/errors"; import { @@ -94,14 +94,14 @@ const addAcceptedUsersToGroup = async ({ }); } - const botPrivateKey = infisicalSymmetricDecrypt({ + const botPrivateKey = crypto.encryption().decryptWithRootEncryptionKey({ keyEncoding: bot.keyEncoding as SecretKeyEncoding, iv: bot.iv, tag: bot.tag, ciphertext: bot.encryptedPrivateKey }); - const plaintextProjectKey = decryptAsymmetric({ + const plaintextProjectKey = crypto.encryption().asymmetric().decrypt({ ciphertext: ghostUserLatestKey.encryptedKey, nonce: ghostUserLatestKey.nonce, publicKey: ghostUserLatestKey.sender.publicKey, @@ -109,11 +109,10 @@ const addAcceptedUsersToGroup = async ({ }); const projectKeysToAdd = usersToAddProjectKeyFor.map((user) => { - const { ciphertext: encryptedKey, nonce } = encryptAsymmetric( - plaintextProjectKey, - user.publicKey, - botPrivateKey - ); + const { ciphertext: encryptedKey, nonce } = crypto + .encryption() + .asymmetric() + .encrypt(plaintextProjectKey, user.publicKey, botPrivateKey); return { encryptedKey, nonce, diff --git a/backend/src/ee/services/kmip/kmip-service.ts b/backend/src/ee/services/kmip/kmip-service.ts index f8c52fe560..618e679737 100644 --- a/backend/src/ee/services/kmip/kmip-service.ts +++ b/backend/src/ee/services/kmip/kmip-service.ts @@ -1,8 +1,8 @@ import { ForbiddenError } from "@casl/ability"; import * as x509 from "@peculiar/x509"; -import crypto, { KeyObject } from "crypto"; import { ActionProjectType } from "@app/db/schemas"; +import { crypto } from "@app/lib/crypto/cryptography"; import { BadRequestError, InternalServerError, NotFoundError } from "@app/lib/errors"; import { isValidIp } from "@app/lib/ip"; import { ms } from "@app/lib/ms"; @@ -299,7 +299,7 @@ export const kmipServiceFactory = ({ } const alg = keyAlgorithmToAlgCfg(keyAlgorithm); - const leafKeys = await crypto.subtle.generateKey(alg, true, ["sign", "verify"]); + const leafKeys = await crypto.rawCrypto.subtle.generateKey(alg, true, ["sign", "verify"]); const extensions: x509.Extension[] = [ new x509.BasicConstraintsExtension(false), @@ -318,13 +318,13 @@ export const kmipServiceFactory = ({ const caAlg = keyAlgorithmToAlgCfg(kmipConfig.caKeyAlgorithm as CertKeyAlgorithm); - const caSkObj = crypto.createPrivateKey({ + const caSkObj = crypto.rawCrypto.createPrivateKey({ key: decryptor({ cipherTextBlob: kmipConfig.encryptedClientIntermediateCaPrivateKey }), format: "der", type: "pkcs8" }); - const caPrivateKey = await crypto.subtle.importKey( + const caPrivateKey = await crypto.rawCrypto.subtle.importKey( "pkcs8", caSkObj.export({ format: "der", type: "pkcs8" }), caAlg, @@ -345,7 +345,7 @@ export const kmipServiceFactory = ({ extensions }); - const skLeafObj = KeyObject.from(leafKeys.privateKey); + const skLeafObj = crypto.rawCrypto.KeyObject.from(leafKeys.privateKey); const rootCaCert = new x509.X509Certificate(decryptor({ cipherTextBlob: kmipConfig.encryptedRootCaCertificate })); const serverIntermediateCaCert = new x509.X509Certificate( @@ -424,8 +424,8 @@ export const kmipServiceFactory = ({ // generate root CA const rootCaSerialNumber = createSerialNumber(); - const rootCaKeys = await crypto.subtle.generateKey(alg, true, ["sign", "verify"]); - const rootCaSkObj = KeyObject.from(rootCaKeys.privateKey); + const rootCaKeys = await crypto.rawCrypto.subtle.generateKey(alg, true, ["sign", "verify"]); + const rootCaSkObj = crypto.rawCrypto.KeyObject.from(rootCaKeys.privateKey); const rootCaIssuedAt = new Date(); const rootCaExpiration = new Date(new Date().setFullYear(new Date().getFullYear() + 20)); @@ -447,8 +447,8 @@ export const kmipServiceFactory = ({ const serverIntermediateCaSerialNumber = createSerialNumber(); const serverIntermediateCaIssuedAt = new Date(); const serverIntermediateCaExpiration = new Date(new Date().setFullYear(new Date().getFullYear() + 10)); - const serverIntermediateCaKeys = await crypto.subtle.generateKey(alg, true, ["sign", "verify"]); - const serverIntermediateCaSkObj = KeyObject.from(serverIntermediateCaKeys.privateKey); + const serverIntermediateCaKeys = await crypto.rawCrypto.subtle.generateKey(alg, true, ["sign", "verify"]); + const serverIntermediateCaSkObj = crypto.rawCrypto.KeyObject.from(serverIntermediateCaKeys.privateKey); const serverIntermediateCaCert = await x509.X509CertificateGenerator.create({ serialNumber: serverIntermediateCaSerialNumber, @@ -478,8 +478,8 @@ export const kmipServiceFactory = ({ const clientIntermediateCaSerialNumber = createSerialNumber(); const clientIntermediateCaIssuedAt = new Date(); const clientIntermediateCaExpiration = new Date(new Date().setFullYear(new Date().getFullYear() + 10)); - const clientIntermediateCaKeys = await crypto.subtle.generateKey(alg, true, ["sign", "verify"]); - const clientIntermediateCaSkObj = KeyObject.from(clientIntermediateCaKeys.privateKey); + const clientIntermediateCaKeys = await crypto.rawCrypto.subtle.generateKey(alg, true, ["sign", "verify"]); + const clientIntermediateCaSkObj = crypto.rawCrypto.KeyObject.from(clientIntermediateCaKeys.privateKey); const clientIntermediateCaCert = await x509.X509CertificateGenerator.create({ serialNumber: clientIntermediateCaSerialNumber, @@ -644,7 +644,8 @@ export const kmipServiceFactory = ({ } const alg = keyAlgorithmToAlgCfg(keyAlgorithm); - const leafKeys = await crypto.subtle.generateKey(alg, true, ["sign", "verify"]); + + const leafKeys = await crypto.rawCrypto.subtle.generateKey(alg, true, ["sign", "verify"]); const extensions: x509.Extension[] = [ new x509.BasicConstraintsExtension(false), @@ -692,13 +693,13 @@ export const kmipServiceFactory = ({ cipherTextBlob: kmipOrgConfig.encryptedServerIntermediateCaChain }).toString("utf-8"); - const caSkObj = crypto.createPrivateKey({ + const caSkObj = crypto.rawCrypto.createPrivateKey({ key: decryptor({ cipherTextBlob: kmipOrgConfig.encryptedServerIntermediateCaPrivateKey }), format: "der", type: "pkcs8" }); - const caPrivateKey = await crypto.subtle.importKey( + const caPrivateKey = await crypto.rawCrypto.subtle.importKey( "pkcs8", caSkObj.export({ format: "der", type: "pkcs8" }), caAlg, @@ -719,7 +720,7 @@ export const kmipServiceFactory = ({ extensions }); - const skLeafObj = KeyObject.from(leafKeys.privateKey); + const skLeafObj = crypto.rawCrypto.KeyObject.from(leafKeys.privateKey); const certificateChain = `${caCertObj.toString("pem")}\n${decryptedCaCertChain}`.trim(); await kmipOrgServerCertificateDAL.create({ diff --git a/backend/src/ee/services/secret-approval-request/secret-approval-request-service.ts b/backend/src/ee/services/secret-approval-request/secret-approval-request-service.ts index 49f3361115..130cd184f2 100644 --- a/backend/src/ee/services/secret-approval-request/secret-approval-request-service.ts +++ b/backend/src/ee/services/secret-approval-request/secret-approval-request-service.ts @@ -12,7 +12,7 @@ import { TSecretApprovalRequestsSecretsV2Insert } from "@app/db/schemas"; import { getConfig } from "@app/lib/config/env"; -import { decryptSymmetric128BitHexKeyUTF8 } from "@app/lib/crypto"; +import { crypto, SymmetricKeySize } from "@app/lib/crypto/cryptography"; import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors"; import { groupBy, pick, unique } from "@app/lib/fn"; import { setKnexStringValue } from "@app/lib/knex"; @@ -819,11 +819,12 @@ export const secretApprovalRequestServiceFactory = ({ type: SecretType.Shared, references: botKey ? getAllNestedSecretReferences( - decryptSymmetric128BitHexKeyUTF8({ + crypto.encryption().decryptSymmetric({ ciphertext: el.secretValueCiphertext, iv: el.secretValueIV, tag: el.secretValueTag, - key: botKey + key: botKey, + keySize: SymmetricKeySize.Bits128 }) ) : undefined @@ -864,11 +865,12 @@ export const secretApprovalRequestServiceFactory = ({ ]), references: botKey ? getAllNestedSecretReferences( - decryptSymmetric128BitHexKeyUTF8({ + crypto.encryption().decryptSymmetric({ ciphertext: el.secretValueCiphertext, iv: el.secretValueIV, tag: el.secretValueTag, - key: botKey + key: botKey, + keySize: SymmetricKeySize.Bits128 }) ) : undefined diff --git a/backend/src/ee/services/secret-replication/secret-replication-service.ts b/backend/src/ee/services/secret-replication/secret-replication-service.ts index 628f8e310c..3aa3a1f2ad 100644 --- a/backend/src/ee/services/secret-replication/secret-replication-service.ts +++ b/backend/src/ee/services/secret-replication/secret-replication-service.ts @@ -3,7 +3,7 @@ import { TSecretApprovalPolicyServiceFactory } from "@app/ee/services/secret-app import { TSecretApprovalRequestDALFactory } from "@app/ee/services/secret-approval-request/secret-approval-request-dal"; import { TSecretApprovalRequestSecretDALFactory } from "@app/ee/services/secret-approval-request/secret-approval-request-secret-dal"; import { KeyStorePrefixes, TKeyStoreFactory } from "@app/keystore/keystore"; -import { decryptSymmetric128BitHexKeyUTF8 } from "@app/lib/crypto"; +import { crypto, SymmetricKeySize } from "@app/lib/crypto/cryptography"; import { NotFoundError } from "@app/lib/errors"; import { groupBy, unique } from "@app/lib/fn"; import { logger } from "@app/lib/logger"; @@ -100,18 +100,20 @@ const getReplicationKeyLockPrefix = (projectId: string, environmentSlug: string, export const getReplicationFolderName = (importId: string) => `${ReservedFolders.SecretReplication}${importId}`; const getDecryptedKeyValue = (key: string, secret: TSecrets) => { - const secretKey = decryptSymmetric128BitHexKeyUTF8({ + const secretKey = crypto.encryption().decryptSymmetric({ ciphertext: secret.secretKeyCiphertext, iv: secret.secretKeyIV, tag: secret.secretKeyTag, - key + key, + keySize: SymmetricKeySize.Bits128 }); - const secretValue = decryptSymmetric128BitHexKeyUTF8({ + const secretValue = crypto.encryption().decryptSymmetric({ ciphertext: secret.secretValueCiphertext, iv: secret.secretValueIV, tag: secret.secretValueTag, - key + key, + keySize: SymmetricKeySize.Bits128 }); return { key: secretKey, value: secretValue }; }; diff --git a/backend/src/ee/services/secret-rotation-v2/shared/utils/index.ts b/backend/src/ee/services/secret-rotation-v2/shared/utils/index.ts index 9b2eb78394..ef58687a1e 100644 --- a/backend/src/ee/services/secret-rotation-v2/shared/utils/index.ts +++ b/backend/src/ee/services/secret-rotation-v2/shared/utils/index.ts @@ -1,4 +1,4 @@ -import { randomInt } from "crypto"; +import { crypto } from "@app/lib/crypto/cryptography"; type TPasswordRequirements = { length: number; @@ -39,7 +39,7 @@ export const generatePassword = (passwordRequirements?: TPasswordRequirements) = parts.push( ...Array(required.lowercase) .fill(0) - .map(() => chars.lowercase[randomInt(chars.lowercase.length)]) + .map(() => chars.lowercase[crypto.randomInt(chars.lowercase.length)]) ); } @@ -47,7 +47,7 @@ export const generatePassword = (passwordRequirements?: TPasswordRequirements) = parts.push( ...Array(required.uppercase) .fill(0) - .map(() => chars.uppercase[randomInt(chars.uppercase.length)]) + .map(() => chars.uppercase[crypto.randomInt(chars.uppercase.length)]) ); } @@ -55,7 +55,7 @@ export const generatePassword = (passwordRequirements?: TPasswordRequirements) = parts.push( ...Array(required.digits) .fill(0) - .map(() => chars.digits[randomInt(chars.digits.length)]) + .map(() => chars.digits[crypto.randomInt(chars.digits.length)]) ); } @@ -63,7 +63,7 @@ export const generatePassword = (passwordRequirements?: TPasswordRequirements) = parts.push( ...Array(required.symbols) .fill(0) - .map(() => chars.symbols[randomInt(chars.symbols.length)]) + .map(() => chars.symbols[crypto.randomInt(chars.symbols.length)]) ); } @@ -78,12 +78,12 @@ export const generatePassword = (passwordRequirements?: TPasswordRequirements) = parts.push( ...Array(remainingLength) .fill(0) - .map(() => allowedChars[randomInt(allowedChars.length)]) + .map(() => allowedChars[crypto.randomInt(allowedChars.length)]) ); // shuffle the array to mix up the characters for (let i = parts.length - 1; i > 0; i -= 1) { - const j = randomInt(i + 1); + const j = crypto.randomInt(i + 1); [parts[i], parts[j]] = [parts[j], parts[i]]; } diff --git a/backend/src/ee/services/secret-rotation/secret-rotation-queue/secret-rotation-queue.ts b/backend/src/ee/services/secret-rotation/secret-rotation-queue/secret-rotation-queue.ts index d792ac6e65..4ce32e3b2d 100644 --- a/backend/src/ee/services/secret-rotation/secret-rotation-queue/secret-rotation-queue.ts +++ b/backend/src/ee/services/secret-rotation/secret-rotation-queue/secret-rotation-queue.ts @@ -7,7 +7,7 @@ import { import { SecretType } from "@app/db/schemas"; import { getConfig } from "@app/lib/config/env"; -import { encryptSymmetric128BitHexKeyUTF8 } from "@app/lib/crypto/encryption"; +import { crypto, SymmetricKeySize } from "@app/lib/crypto/cryptography"; import { daysToMillisecond, secondsToMillis } from "@app/lib/dates"; import { NotFoundError } from "@app/lib/errors"; import { logger } from "@app/lib/logger"; @@ -117,6 +117,7 @@ export const secretRotationQueueFactory = ({ queue.start(QueueName.SecretRotation, async (job) => { const { rotationId } = job.data; const appCfg = getConfig(); + logger.info(`secretRotationQueue.process: [rotationDocument=${rotationId}]`); const secretRotation = await secretRotationDAL.findById(rotationId); const rotationProvider = rotationTemplates.find(({ name }) => name === secretRotation?.provider); @@ -365,15 +366,19 @@ export const secretRotationQueueFactory = ({ throw new NotFoundError({ message: `Project bot not found for project with ID '${secretRotation.projectId}'` }); + const encryptedSecrets = rotationOutputs.map(({ key: outputKey, secretId }) => ({ secretId, - value: encryptSymmetric128BitHexKeyUTF8( - typeof newCredential.outputs[outputKey] === "object" - ? JSON.stringify(newCredential.outputs[outputKey]) - : String(newCredential.outputs[outputKey]), - botKey - ) + value: crypto.encryption().encryptSymmetric({ + plaintext: + typeof newCredential.outputs[outputKey] === "object" + ? JSON.stringify(newCredential.outputs[outputKey]) + : String(newCredential.outputs[outputKey]), + key: botKey, + keySize: SymmetricKeySize.Bits128 + }) })); + // map the final values to output keys in the board await secretRotationDAL.transaction(async (tx) => { await secretRotationDAL.updateById( diff --git a/backend/src/ee/services/secret-rotation/secret-rotation-service.ts b/backend/src/ee/services/secret-rotation/secret-rotation-service.ts index 1099e17a72..ee840121af 100644 --- a/backend/src/ee/services/secret-rotation/secret-rotation-service.ts +++ b/backend/src/ee/services/secret-rotation/secret-rotation-service.ts @@ -2,7 +2,7 @@ import { ForbiddenError, subject } from "@casl/ability"; import Ajv from "ajv"; import { ActionProjectType, ProjectVersion, TableName } from "@app/db/schemas"; -import { decryptSymmetric128BitHexKeyUTF8 } from "@app/lib/crypto/encryption"; +import { crypto, SymmetricKeySize } from "@app/lib/crypto/cryptography"; import { BadRequestError, NotFoundError } from "@app/lib/errors"; import { TProjectPermission } from "@app/lib/types"; import { TKmsServiceFactory } from "@app/services/kms/kms-service"; @@ -230,6 +230,7 @@ export const secretRotationServiceFactory = ({ if (!botKey) throw new NotFoundError({ message: `Project bot not found for project with ID '${projectId}'` }); const docs = await secretRotationDAL.find({ projectId }); + return docs.map((el) => ({ ...el, outputs: el.outputs.map((output) => ({ @@ -237,11 +238,12 @@ export const secretRotationServiceFactory = ({ secret: { id: output.secret.id, version: output.secret.version, - secretKey: decryptSymmetric128BitHexKeyUTF8({ + secretKey: crypto.encryption().decryptSymmetric({ ciphertext: output.secret.secretKeyCiphertext, iv: output.secret.secretKeyIV, tag: output.secret.secretKeyTag, - key: botKey + key: botKey, + keySize: SymmetricKeySize.Bits128 }) } })) diff --git a/backend/src/ee/services/secret-scanning/secret-scanning-service.ts b/backend/src/ee/services/secret-scanning/secret-scanning-service.ts index a65bb9e68d..85a3cd5f2d 100644 --- a/backend/src/ee/services/secret-scanning/secret-scanning-service.ts +++ b/backend/src/ee/services/secret-scanning/secret-scanning-service.ts @@ -1,5 +1,3 @@ -import crypto from "node:crypto"; - import { ForbiddenError } from "@casl/ability"; import { WebhookEventMap } from "@octokit/webhooks-types"; import { ProbotOctokit } from "probot"; @@ -7,6 +5,7 @@ import { ProbotOctokit } from "probot"; import { OrgPermissionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types"; import { getConfig } from "@app/lib/config/env"; +import { crypto } from "@app/lib/crypto/cryptography"; import { NotFoundError } from "@app/lib/errors"; import { TGitAppDALFactory } from "./git-app-dal"; diff --git a/backend/src/ee/services/secret-snapshot/secret-snapshot-service.ts b/backend/src/ee/services/secret-snapshot/secret-snapshot-service.ts index c9f6dac9da..d8c8c0ead3 100644 --- a/backend/src/ee/services/secret-snapshot/secret-snapshot-service.ts +++ b/backend/src/ee/services/secret-snapshot/secret-snapshot-service.ts @@ -3,7 +3,7 @@ import { ForbiddenError } from "@casl/ability"; import { ActionProjectType, TableName, TSecretTagJunctionInsert, TSecretV2TagJunctionInsert } from "@app/db/schemas"; -import { decryptSymmetric128BitHexKeyUTF8 } from "@app/lib/crypto"; +import { crypto, SymmetricKeySize } from "@app/lib/crypto/cryptography"; import { InternalServerError, NotFoundError } from "@app/lib/errors"; import { groupBy } from "@app/lib/fn"; import { logger } from "@app/lib/logger"; @@ -236,14 +236,16 @@ export const secretSnapshotServiceFactory = ({ const { botKey } = await projectBotService.getBotKey(snapshot.projectId); if (!botKey) throw new NotFoundError({ message: `Project bot key not found for project with ID '${snapshot.projectId}'` }); + snapshotDetails = { ...encryptedSnapshotDetails, secretVersions: encryptedSnapshotDetails.secretVersions.map((el) => { - const secretKey = decryptSymmetric128BitHexKeyUTF8({ + const secretKey = crypto.encryption().decryptSymmetric({ ciphertext: el.secretKeyCiphertext, iv: el.secretKeyIV, tag: el.secretKeyTag, - key: botKey + key: botKey, + keySize: SymmetricKeySize.Bits128 }); const canReadValue = hasSecretReadValueOrDescribePermission( @@ -260,11 +262,12 @@ export const secretSnapshotServiceFactory = ({ let secretValue = ""; if (canReadValue) { - secretValue = decryptSymmetric128BitHexKeyUTF8({ + secretValue = crypto.encryption().decryptSymmetric({ ciphertext: el.secretValueCiphertext, iv: el.secretValueIV, tag: el.secretValueTag, - key: botKey + key: botKey, + keySize: SymmetricKeySize.Bits128 }); } else { secretValue = INFISICAL_SECRET_VALUE_HIDDEN_MASK; @@ -277,11 +280,12 @@ export const secretSnapshotServiceFactory = ({ secretValue, secretComment: el.secretCommentTag && el.secretCommentIV && el.secretCommentCiphertext - ? decryptSymmetric128BitHexKeyUTF8({ + ? crypto.encryption().decryptSymmetric({ ciphertext: el.secretCommentCiphertext, iv: el.secretCommentIV, tag: el.secretCommentTag, - key: botKey + key: botKey, + keySize: SymmetricKeySize.Bits128 }) : "" }; diff --git a/backend/src/ee/services/ssh/ssh-certificate-authority-fns.ts b/backend/src/ee/services/ssh/ssh-certificate-authority-fns.ts index 60c966fcde..21da4bc163 100644 --- a/backend/src/ee/services/ssh/ssh-certificate-authority-fns.ts +++ b/backend/src/ee/services/ssh/ssh-certificate-authority-fns.ts @@ -1,5 +1,4 @@ import { execFile } from "child_process"; -import crypto from "crypto"; import { promises as fs } from "fs"; import { Knex } from "knex"; import os from "os"; @@ -9,6 +8,7 @@ import { promisify } from "util"; import { TSshCertificateTemplates } from "@app/db/schemas"; import { SshCertKeyAlgorithm } from "@app/ee/services/ssh-certificate/ssh-certificate-types"; +import { crypto } from "@app/lib/crypto/cryptography"; import { BadRequestError } from "@app/lib/errors"; import { ms } from "@app/lib/ms"; import { CharacterType, characterValidator } from "@app/lib/validator/validate-string"; diff --git a/backend/src/lib/axios/digest-auth.ts b/backend/src/lib/axios/digest-auth.ts index 449c471fdd..eeaba83b24 100644 --- a/backend/src/lib/axios/digest-auth.ts +++ b/backend/src/lib/axios/digest-auth.ts @@ -1,7 +1,7 @@ -import crypto from "node:crypto"; - import { AxiosError, AxiosInstance, AxiosRequestConfig } from "axios"; +import { crypto, DigestType } from "../crypto/cryptography"; + export const createDigestAuthRequestInterceptor = ( axiosInstance: AxiosInstance, username: string, @@ -30,18 +30,13 @@ export const createDigestAuthRequestInterceptor = ( const cnonce = crypto.randomBytes(24).toString("hex"); const realm = authDetails.find((el) => el[0].toLowerCase().indexOf("realm") > -1)?.[1]?.replaceAll('"', "") || ""; const nonce = authDetails.find((el) => el[0].toLowerCase().indexOf("nonce") > -1)?.[1]?.replaceAll('"', "") || ""; - const ha1 = crypto.createHash("md5").update(`${username}:${realm}:${password}`).digest("hex"); + const ha1 = crypto.hashing().md5(`${username}:${realm}:${password}`, DigestType.Hex); const path = opts.url; - const ha2 = crypto - .createHash("md5") - .update(`${opts.method ?? "GET"}:${path}`) - .digest("hex"); + const ha2 = crypto.hashing().md5(`${opts.method ?? "GET"}:${path}`, DigestType.Hex); + + const response = crypto.hashing().md5(`${ha1}:${nonce}:${nonceCount}:${cnonce}:auth:${ha2}`, DigestType.Hex); - const response = crypto - .createHash("md5") - .update(`${ha1}:${nonce}:${nonceCount}:${cnonce}:auth:${ha2}`) - .digest("hex"); const authorization = `Digest username="${username}",realm="${realm}",nonce="${nonce}",uri="${path}",qop="auth",algorithm="MD5",response="${response}",nc="${nonceCount}",cnonce="${cnonce}"`; if (opts.headers) { diff --git a/backend/src/lib/config/env.ts b/backend/src/lib/config/env.ts index 8db5c16c4d..205ae3ed57 100644 --- a/backend/src/lib/config/env.ts +++ b/backend/src/lib/config/env.ts @@ -63,7 +63,7 @@ const envSchema = z DB_PASSWORD: zpStr(z.string().describe("Postgres database password").optional()), DB_NAME: zpStr(z.string().describe("Postgres database name").optional()), DB_READ_REPLICAS: zpStr(z.string().describe("Postgres read replicas").optional()), - BCRYPT_SALT_ROUND: z.number().default(12), + BCRYPT_SALT_ROUND: z.number().optional(), // note(daniel): this is deprecated, use SALT_ROUNDS instead. only keeping this for backwards compatibility. NODE_ENV: z.enum(["development", "test", "production"]).default("production"), SALT_ROUNDS: z.coerce.number().default(10), INITIAL_ORGANIZATION_NAME: zpStr(z.string().optional()), @@ -306,6 +306,7 @@ const envSchema = z ) .transform((data) => ({ ...data, + SALT_ROUNDS: data.SALT_ROUNDS || data.BCRYPT_SALT_ROUND || 12, DB_READ_REPLICAS: data.DB_READ_REPLICAS ? databaseReadReplicaSchema.parse(JSON.parse(data.DB_READ_REPLICAS)) : undefined, diff --git a/backend/src/lib/crypto/cache.ts b/backend/src/lib/crypto/cache.ts index 9f36d360ba..e74a01d8c2 100644 --- a/backend/src/lib/crypto/cache.ts +++ b/backend/src/lib/crypto/cache.ts @@ -1,10 +1,4 @@ -import crypto from "node:crypto"; +import { crypto } from "@app/lib/crypto/cryptography"; export const generateCacheKeyFromData = (data: unknown) => - crypto - .createHash("md5") - .update(JSON.stringify(data)) - .digest("base64") - .replace(/\+/g, "-") - .replace(/\//g, "_") - .replace(/=/g, ""); + crypto.rawCrypto.createHash("sha256").update(JSON.stringify(data)).digest("base64"); diff --git a/backend/src/lib/crypto/cipher/cipher.ts b/backend/src/lib/crypto/cipher/cipher.ts index 718c8ad5ee..e193c8c3fc 100644 --- a/backend/src/lib/crypto/cipher/cipher.ts +++ b/backend/src/lib/crypto/cipher/cipher.ts @@ -1,24 +1,16 @@ -import crypto from "crypto"; +import { crypto } from "@app/lib/crypto/cryptography"; import { SymmetricKeyAlgorithm, TSymmetricEncryptionFns } from "./types"; -const getIvLength = () => { - return 12; -}; - -const getTagLength = () => { - return 16; -}; +const IV_LENGTH = 12; +const TAG_LENGTH = 16; export const symmetricCipherService = ( type: SymmetricKeyAlgorithm.AES_GCM_128 | SymmetricKeyAlgorithm.AES_GCM_256 ): TSymmetricEncryptionFns => { - const IV_LENGTH = getIvLength(); - const TAG_LENGTH = getTagLength(); - const encrypt = (text: Buffer, key: Buffer) => { const iv = crypto.randomBytes(IV_LENGTH); - const cipher = crypto.createCipheriv(type, key, iv); + const cipher = crypto.rawCrypto.createCipheriv(type, key, iv); let encrypted = cipher.update(text); encrypted = Buffer.concat([encrypted, cipher.final()]); @@ -37,7 +29,7 @@ export const symmetricCipherService = ( const tag = ciphertextBlob.subarray(-TAG_LENGTH); const encrypted = ciphertextBlob.subarray(IV_LENGTH, -TAG_LENGTH); - const decipher = crypto.createDecipheriv(type, key, iv); + const decipher = crypto.rawCrypto.createDecipheriv(type, key, iv); decipher.setAuthTag(tag); const decrypted = Buffer.concat([decipher.update(encrypted), decipher.final()]); diff --git a/backend/src/lib/crypto/cryptography.ts b/backend/src/lib/crypto/cryptography.ts new file mode 100644 index 0000000000..8486837cb4 --- /dev/null +++ b/backend/src/lib/crypto/cryptography.ts @@ -0,0 +1,630 @@ +// NOTE: DO NOT USE crypto-js ANYWHERE EXCEPT THIS FILE. +// We use crypto-js purely to get around our native node crypto FIPS restrictions in FIPS mode. +import crypto, { subtle } from "node:crypto"; + +import bcrypt from "bcrypt"; +import cryptoJs from "crypto-js"; +import nacl from "tweetnacl"; +import naclUtils from "tweetnacl-util"; + +import { SecretEncryptionAlgo, SecretKeyEncoding } from "@app/db/schemas"; +import { TSuperAdminDALFactory } from "@app/services/super-admin/super-admin-dal"; +import { ADMIN_CONFIG_DB_UUID } from "@app/services/super-admin/super-admin-service"; + +import { getConfig } from "../config/env"; +import { logger } from "../logger"; + +enum DigestType { + Hex = "hex", + Base64 = "base64" +} + +export enum SymmetricKeySize { + Bits128 = "128-bits", + Bits256 = "256-bits" +} + +type TDecryptSymmetricInput = + | { + ciphertext: string; + iv: string; + tag: string; + key: string | Buffer; // can be hex encoded or buffer + keySize: SymmetricKeySize.Bits128; + } + | { + ciphertext: string; + iv: string; + tag: string; + key: string; // must be base64 encoded + keySize: SymmetricKeySize.Bits256; + }; + +type TEncryptSymmetricInput = + | { + plaintext: string; + key: string; + keySize: SymmetricKeySize.Bits256; + } + | { + plaintext: string; + key: string | Buffer; + keySize: SymmetricKeySize.Bits128; + }; + +type TDecryptAsymmetricInput = { + ciphertext: string; + nonce: string; + publicKey: string; + privateKey: string; +}; + +const IV_BYTES_SIZE = 12; +const BLOCK_SIZE_BYTES_16 = 16; + +const hasherFipsValidated = () => { + const $hashPassword = (password: string, salt: string, iterations: number, keyLength: number) => { + return new Promise((resolve, reject) => { + crypto.pbkdf2(password, salt, iterations, keyLength, "sha256", (err, derivedKey) => { + if (err) { + return reject(err); + } + resolve(derivedKey.toString("hex")); + }); + }); + }; + + const $validatePassword = ( + inputPassword: string, + storedHash: string, + salt: string, + iterations: number, + keyLength: number + ) => { + return $hashPassword(inputPassword, salt, iterations, keyLength).then((hash) => hash === storedHash); + }; + + const hash = async (password: string, saltRounds: number) => { + const salt = crypto.randomBytes(16).toString("hex"); + const derivedKey = await $hashPassword(password, salt, saltRounds, 32); + return `$infisical$${saltRounds}$${salt}$${derivedKey}`; + }; + + const compare = async (password: string, hashedPassword: string) => { + if (!hashedPassword.startsWith("$infisical$")) { + throw new Error("Invalid hash format"); + } + + const [, , iterations, salt, storedHash] = hashedPassword.split("$"); + return $validatePassword(password, storedHash, salt, Number(iterations), 32); + }; + + return { + hash, + compare + }; +}; + +const generateAsymmetricKeyPairFipsValidated = () => { + const { publicKey, privateKey } = crypto.generateKeyPairSync("x25519"); + + return { + publicKey: publicKey.export({ type: "spki", format: "der" }).toString("base64"), + privateKey: privateKey.export({ type: "pkcs8", format: "der" }).toString("base64") + }; +}; + +const encryptAsymmetricFipsValidated = (data: string, publicKey: string, privateKey: string) => { + const pubKeyObj = crypto.createPublicKey({ + key: Buffer.from(publicKey, "base64"), + type: "spki", + format: "der" + }); + + const privKeyObj = crypto.createPrivateKey({ + key: Buffer.from(privateKey, "base64"), + type: "pkcs8", + format: "der" + }); + + // Generate shared secret using X25519 + const sharedSecret = crypto.diffieHellman({ + privateKey: privKeyObj, + publicKey: pubKeyObj + }); + + // Generate 24-byte nonce (same as NaCl) + const nonce = crypto.randomBytes(24); + + // Derive 32-byte key from shared secret + const key = crypto.createHash("sha256").update(sharedSecret).digest(); + + // Use first 12 bytes of nonce as IV for AES-GCM + const iv = nonce.subarray(0, 12); + + // Encrypt with AES-256-GCM + const cipher = crypto.createCipheriv("aes-256-gcm", key, iv); + + const ciphertext = cipher.update(data, "utf8"); + cipher.final(); + + const authTag = cipher.getAuthTag(); + + // Combine ciphertext and auth tag + const combined = Buffer.concat([ciphertext, authTag]); + + return { + ciphertext: combined.toString("base64"), + nonce: nonce.toString("base64") + }; +}; + +const decryptAsymmetricFipsValidated = ({ + ciphertext, + nonce, + publicKey, + privateKey +}: { + ciphertext: string; + nonce: string; + publicKey: string; + privateKey: string; +}) => { + // Convert base64 keys back to key objects + const pubKeyObj = crypto.createPublicKey({ + key: Buffer.from(publicKey, "base64"), + type: "spki", + format: "der" + }); + + const privKeyObj = crypto.createPrivateKey({ + key: Buffer.from(privateKey, "base64"), + type: "pkcs8", + format: "der" + }); + + // Generate same shared secret + const sharedSecret = crypto.diffieHellman({ + privateKey: privKeyObj, + publicKey: pubKeyObj + }); + + const nonceBuffer = Buffer.from(nonce, "base64"); + const combinedBuffer = Buffer.from(ciphertext, "base64"); + + // Split ciphertext and auth tag (last 16 bytes for GCM) + const actualCiphertext = combinedBuffer.subarray(0, -16); + const authTag = combinedBuffer.subarray(-16); + + // Derive same 32-byte key + const key = crypto.createHash("sha256").update(sharedSecret).digest(); + + // Use first 12 bytes of nonce as IV + const iv = nonceBuffer.subarray(0, 12); + + // Decrypt + const decipher = crypto.createDecipheriv("aes-256-gcm", key, iv); + decipher.setAuthTag(authTag); + + const plaintext = decipher.update(actualCiphertext); + + try { + const final = decipher.final(); + return Buffer.concat([plaintext, final]).toString("utf8"); + } catch (error) { + throw new Error("Invalid ciphertext or keys"); + } +}; + +const generateAsymmetricKeyPairNoFipsValidation = () => { + const pair = nacl.box.keyPair(); + + return { + publicKey: naclUtils.encodeBase64(pair.publicKey), + privateKey: naclUtils.encodeBase64(pair.secretKey) + }; +}; + +export const encryptAsymmetricNoFipsValidation = (plaintext: string, publicKey: string, privateKey: string) => { + const nonce = nacl.randomBytes(24); + const ciphertext = nacl.box( + naclUtils.decodeUTF8(plaintext), + nonce, + naclUtils.decodeBase64(publicKey), + naclUtils.decodeBase64(privateKey) + ); + + return { + ciphertext: naclUtils.encodeBase64(ciphertext), + nonce: naclUtils.encodeBase64(nonce) + }; +}; + +const decryptAsymmetricNoFipsValidation = ({ ciphertext, nonce, publicKey, privateKey }: TDecryptAsymmetricInput) => { + const plaintext: Uint8Array | null = nacl.box.open( + naclUtils.decodeBase64(ciphertext), + naclUtils.decodeBase64(nonce), + naclUtils.decodeBase64(publicKey), + naclUtils.decodeBase64(privateKey) + ); + + if (plaintext == null) throw Error("Invalid ciphertext or keys"); + + return naclUtils.encodeUTF8(plaintext); +}; + +export const generateAsymmetricKeyPair = () => { + const pair = nacl.box.keyPair(); + + return { + publicKey: naclUtils.encodeBase64(pair.publicKey), + privateKey: naclUtils.encodeBase64(pair.secretKey) + }; +}; + +export const computeMd5 = (message: string, digest: DigestType = DigestType.Hex) => { + let encoder; + switch (digest) { + case DigestType.Hex: + encoder = cryptoJs.enc.Hex; + break; + case DigestType.Base64: + encoder = cryptoJs.enc.Base64; + break; + default: + throw new Error(`Invalid digest type: ${digest as string}`); + } + + return cryptoJs.MD5(message).toString(encoder); +}; + +const cryptographyFactory = () => { + // placeholder for now + let $fipsEnabled = false; + let $isInitialized = false; + + const $checkIsInitialized = () => { + if (!$isInitialized) { + throw new Error("Internal cryptography module is not initialized"); + } + }; + + const isFipsModeEnabled = () => { + $checkIsInitialized(); + return $fipsEnabled; + }; + const $setFipsModeEnabled = (enabled: boolean) => { + $fipsEnabled = enabled; + $isInitialized = true; + }; + + const initialize = async (superAdminDAL: TSuperAdminDALFactory) => { + if ($isInitialized) { + return isFipsModeEnabled(); + } + + if (process.env.FIPS_ENABLED !== "true") { + logger.info("[FIPS]: Instance is running in non-FIPS mode."); + return false; + } + + const serverCfg = await superAdminDAL.findById(ADMIN_CONFIG_DB_UUID).catch(() => null); + + // if fips mode is enabled, we need to check if the deployment is a new deployment or an old one. + if (serverCfg) { + if (serverCfg.fipsEnabled) { + logger.info("[FIPS]: Instance is configured for FIPS mode of operation. Continuing startup with FIPS enabled."); + $setFipsModeEnabled(true); + return true; + } + logger.info("[FIPS]: Instance age predates FIPS mode inception date. Continuing without FIPS."); + $setFipsModeEnabled(false); + return false; + } + + logger.info("[FIPS]: First time initializing cryptography module on a new deployment. FIPS mode is enabled."); + + // TODO(daniel): check if it's an enterprise deployment + + // if there is no server cfg, and FIPS_MODE is `true`, its a fresh FIPS deployment. We need to set the fipsEnabled to true. + $setFipsModeEnabled(true); + return true; + }; + + const encryption = () => { + $checkIsInitialized(); + + const asymmetric = () => { + const generateKeyPair = () => { + if (isFipsModeEnabled()) { + logger.info("[FIPS]: Generating asymmetric key pair. FIPS mode is enabled."); + logger.info("[FIPS]: Generating asymmetric key pair. FIPS mode is enabled."); + logger.info("[FIPS]: Generating asymmetric key pair. FIPS mode is enabled."); + logger.info("[FIPS]: Generating asymmetric key pair. FIPS mode is enabled."); + logger.info("[FIPS]: Generating asymmetric key pair. FIPS mode is enabled."); + return generateAsymmetricKeyPairFipsValidated(); + } + logger.info("[FIPS]: Generating asymmetric key pair. FIPS mode is DISABLED."); + logger.info("[FIPS]: Generating asymmetric key pair. FIPS mode is DISABLED."); + logger.info("[FIPS]: Generating asymmetric key pair. FIPS mode is DISABLED."); + logger.info("[FIPS]: Generating asymmetric key pair. FIPS mode is DISABLED."); + logger.info("[FIPS]: Generating asymmetric key pair. FIPS mode is DISABLED."); + return generateAsymmetricKeyPairNoFipsValidation(); + }; + + const encrypt = (data: string, publicKey: string, privateKey: string) => { + if (isFipsModeEnabled()) { + logger.info("[FIPS]: Encrypting asymmetric data. FIPS mode is enabled."); + logger.info("[FIPS]: Encrypting asymmetric data. FIPS mode is enabled."); + logger.info("[FIPS]: Encrypting asymmetric data. FIPS mode is enabled."); + logger.info("[FIPS]: Encrypting asymmetric data. FIPS mode is enabled."); + logger.info("[FIPS]: Encrypting asymmetric data. FIPS mode is enabled."); + return encryptAsymmetricFipsValidated(data, publicKey, privateKey); + } + logger.info("[FIPS]: Encrypting asymmetric data. FIPS mode is DISABLED."); + logger.info("[FIPS]: Encrypting asymmetric data. FIPS mode is DISABLED."); + logger.info("[FIPS]: Encrypting asymmetric data. FIPS mode is DISABLED."); + logger.info("[FIPS]: Encrypting asymmetric data. FIPS mode is DISABLED."); + logger.info("[FIPS]: Encrypting asymmetric data. FIPS mode is DISABLED."); + return encryptAsymmetricNoFipsValidation(data, publicKey, privateKey); + }; + + const decrypt = ({ ciphertext, nonce, publicKey, privateKey }: TDecryptAsymmetricInput) => { + if (isFipsModeEnabled()) { + logger.info("[FIPS]: Decrypting asymmetric data. FIPS mode is enabled."); + logger.info("[FIPS]: Decrypting asymmetric data. FIPS mode is enabled."); + logger.info("[FIPS]: Decrypting asymmetric data. FIPS mode is enabled."); + logger.info("[FIPS]: Decrypting asymmetric data. FIPS mode is enabled."); + logger.info("[FIPS]: Decrypting asymmetric data. FIPS mode is enabled."); + return decryptAsymmetricFipsValidated({ ciphertext, nonce, publicKey, privateKey }); + } + logger.info("[FIPS]: Decrypting asymmetric data. FIPS mode is DISABLED."); + logger.info("[FIPS]: Decrypting asymmetric data. FIPS mode is DISABLED."); + logger.info("[FIPS]: Decrypting asymmetric data. FIPS mode is DISABLED."); + logger.info("[FIPS]: Decrypting asymmetric data. FIPS mode is DISABLED."); + logger.info("[FIPS]: Decrypting asymmetric data. FIPS mode is DISABLED."); + return decryptAsymmetricNoFipsValidation({ ciphertext, nonce, publicKey, privateKey }); + }; + + return { + generateKeyPair, + encrypt, + decrypt + }; + }; + + const decryptSymmetric = ({ ciphertext, iv, tag, key, keySize }: TDecryptSymmetricInput): string => { + let decipher; + + if (keySize === SymmetricKeySize.Bits128) { + // Not ideal: 128-bit hex key (32 chars) gets interpreted as 32 UTF-8 bytes (256 bits) + // This works but reduces effective key entropy from 256 to 128 bits + // Note: Never use this for FIPS mode of operation. + decipher = crypto.createDecipheriv(SecretEncryptionAlgo.AES_256_GCM, key, Buffer.from(iv, "base64")); + } else { + const secretKey = crypto.createSecretKey(key, "base64"); + decipher = crypto.createDecipheriv(SecretEncryptionAlgo.AES_256_GCM, secretKey, Buffer.from(iv, "base64")); + } + + decipher.setAuthTag(Buffer.from(tag, "base64")); + let cleartext = decipher.update(ciphertext, "base64", "utf8"); + cleartext += decipher.final("utf8"); + + return cleartext; + }; + + const encryptSymmetric = ({ plaintext, key, keySize }: TEncryptSymmetricInput) => { + let iv; + let cipher; + + if (keySize === SymmetricKeySize.Bits128) { + iv = crypto.randomBytes(BLOCK_SIZE_BYTES_16); + cipher = crypto.createCipheriv(SecretEncryptionAlgo.AES_256_GCM, key, iv); + } else { + iv = crypto.randomBytes(IV_BYTES_SIZE); + cipher = crypto.createCipheriv(SecretEncryptionAlgo.AES_256_GCM, crypto.createSecretKey(key, "base64"), iv); + } + + let ciphertext = cipher.update(plaintext, "utf8", "base64"); + ciphertext += cipher.final("base64"); + + return { + ciphertext, + iv: iv.toString("base64"), + tag: cipher.getAuthTag().toString("base64") + }; + }; + + const encryptWithRootEncryptionKey = (data: string) => { + const appCfg = getConfig(); + const rootEncryptionKey = appCfg.ROOT_ENCRYPTION_KEY; + const encryptionKey = appCfg.ENCRYPTION_KEY; + if (rootEncryptionKey) { + const { iv, tag, ciphertext } = encryptSymmetric({ + plaintext: data, + key: rootEncryptionKey, + keySize: SymmetricKeySize.Bits256 + }); + return { + iv, + tag, + ciphertext, + algorithm: SecretEncryptionAlgo.AES_256_GCM, + encoding: SecretKeyEncoding.BASE64 + }; + } + if (encryptionKey) { + const { iv, tag, ciphertext } = encryptSymmetric({ + plaintext: data, + key: encryptionKey, + keySize: SymmetricKeySize.Bits128 + }); + return { + iv, + tag, + ciphertext, + algorithm: SecretEncryptionAlgo.AES_256_GCM, + encoding: SecretKeyEncoding.UTF8 + }; + } + throw new Error("Missing both encryption keys"); + }; + + const decryptWithRootEncryptionKey = ({ + keyEncoding, + ciphertext, + tag, + iv + }: Omit & { + keyEncoding: SecretKeyEncoding; + }) => { + logger.info( + `[FIPS]: decryptWithRootEncryptionKey -> Decrypting symmetric data. FIPS mode is: ${isFipsModeEnabled()}` + ); + logger.info( + `[FIPS]: decryptWithRootEncryptionKey -> Decrypting symmetric data. FIPS mode is: ${isFipsModeEnabled()}` + ); + logger.info( + `[FIPS]: decryptWithRootEncryptionKey -> Decrypting symmetric data. FIPS mode is: ${isFipsModeEnabled()}` + ); + logger.info( + `[FIPS]: decryptWithRootEncryptionKey -> Decrypting symmetric data. FIPS mode is: ${isFipsModeEnabled()}` + ); + logger.info( + `[FIPS]: decryptWithRootEncryptionKey -> Decrypting symmetric data. FIPS mode is: ${isFipsModeEnabled()}` + ); + const appCfg = getConfig(); + // the or gate is used used in migration + const rootEncryptionKey = appCfg?.ROOT_ENCRYPTION_KEY || process.env.ROOT_ENCRYPTION_KEY; + const encryptionKey = appCfg?.ENCRYPTION_KEY || process.env.ENCRYPTION_KEY; + if (rootEncryptionKey && keyEncoding === SecretKeyEncoding.BASE64) { + const data = decryptSymmetric({ + key: rootEncryptionKey, + iv, + tag, + ciphertext, + keySize: SymmetricKeySize.Bits256 + }); + return data as T; + } + if (encryptionKey && keyEncoding === SecretKeyEncoding.UTF8) { + const data = decryptSymmetric({ key: encryptionKey, iv, tag, ciphertext, keySize: SymmetricKeySize.Bits128 }); + return data as T; + } + throw new Error("Missing both encryption keys"); + }; + + return { + asymmetric, + encryptWithRootEncryptionKey, + decryptWithRootEncryptionKey, + encryptSymmetric, + decryptSymmetric + }; + }; + + const hashing = () => { + $checkIsInitialized(); + // mark this function as deprecated + /** + * @deprecated Do not use MD5 unless you absolutely have to. It is considered an unsafe hashing algorithm, and should only be used if absolutely necessary. + */ + const md5 = (message: string, digest: DigestType = DigestType.Hex) => { + logger.info(`[FIPS]: md5 -> Hashing message. FIPS mode is: ${isFipsModeEnabled()}`); + logger.info(`[FIPS]: md5 -> Hashing message. FIPS mode is: ${isFipsModeEnabled()}`); + logger.info(`[FIPS]: md5 -> Hashing message. FIPS mode is: ${isFipsModeEnabled()}`); + logger.info(`[FIPS]: md5 -> Hashing message. FIPS mode is: ${isFipsModeEnabled()}`); + logger.info(`[FIPS]: md5 -> Hashing message. FIPS mode is: ${isFipsModeEnabled()}`); + // If FIPS is enabled and we need MD5, we use the crypto-js implementation. + // Avoid this at all costs unless strictly necessary, like for mongo atlas digest auth. + if (isFipsModeEnabled()) { + return computeMd5(message, digest); + } + return crypto.createHash("md5").update(message).digest(digest); + }; + + const createHash = async (password: string, saltRounds: number) => { + logger.info(`[FIPS]: createHash -> Hashing password. FIPS mode is: ${isFipsModeEnabled()}`); + logger.info(`[FIPS]: createHash -> Hashing password. FIPS mode is: ${isFipsModeEnabled()}`); + logger.info(`[FIPS]: createHash -> Hashing password. FIPS mode is: ${isFipsModeEnabled()}`); + logger.info(`[FIPS]: createHash -> Hashing password. FIPS mode is: ${isFipsModeEnabled()}`); + logger.info(`[FIPS]: createHash -> Hashing password. FIPS mode is: ${isFipsModeEnabled()}`); + logger.info(`[FIPS]: createHash -> Hashing password. FIPS mode is: ${isFipsModeEnabled()}`); + if (isFipsModeEnabled()) { + const hasher = hasherFipsValidated(); + + // For the salt when using pkdf2, we do salt rounds * 100.000. + // The reason for this is because pbkdf2 is not as compute intense as bcrypt, making it faster to brute-force. + // From my testing, doing salt rounds * 100.000 brings the computational power required to roughly the same as bcrypt. + // OWASP recommends a minimum of 600.000 iterations for pbkdf2. + // Ref: https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html#pbkdf2 + const hash = await hasher.hash(password, saltRounds * 100_000); + return hash; + } + const hash = await bcrypt.hash(password, saltRounds); + return hash; + }; + + const compareHash = async (password: string, hash: string) => { + logger.info(`[FIPS]: compareHash -> Comparing password. FIPS mode is: ${isFipsModeEnabled()}`); + logger.info(`[FIPS]: compareHash -> Comparing password. FIPS mode is: ${isFipsModeEnabled()}`); + logger.info(`[FIPS]: compareHash -> Comparing password. FIPS mode is: ${isFipsModeEnabled()}`); + logger.info(`[FIPS]: compareHash -> Comparing password. FIPS mode is: ${isFipsModeEnabled()}`); + logger.info(`[FIPS]: compareHash -> Comparing password. FIPS mode is: ${isFipsModeEnabled()}`); + logger.info(`[FIPS]: compareHash -> Comparing password. FIPS mode is: ${isFipsModeEnabled()}`); + if (isFipsModeEnabled()) { + const isValid = await hasherFipsValidated().compare(password, hash); + return isValid; + } + const isValid = await bcrypt.compare(password, hash); + return isValid; + }; + + return { + md5, + createHash, + compareHash + }; + }; + + return { + initialize, + isFipsModeEnabled, + hashing, + encryption, + randomBytes: crypto.randomBytes, + randomInt: crypto.randomInt, + rawCrypto: { + createHash: crypto.createHash, + createHmac: crypto.createHmac, + sign: crypto.sign, + verify: crypto.verify, + createSign: crypto.createSign, + createVerify: crypto.createVerify, + generateKeyPair: crypto.generateKeyPair, + createCipheriv: crypto.createCipheriv, + createDecipheriv: crypto.createDecipheriv, + createPublicKey: crypto.createPublicKey, + createPrivateKey: crypto.createPrivateKey, + getRandomValues: crypto.getRandomValues, + randomUUID: crypto.randomUUID, + subtle: { + // eslint-disable-next-line @typescript-eslint/unbound-method + generateKey: subtle.generateKey, + // eslint-disable-next-line @typescript-eslint/unbound-method + importKey: subtle.importKey, + // eslint-disable-next-line @typescript-eslint/unbound-method + exportKey: subtle.exportKey + }, + constants: crypto.constants, + X509Certificate: crypto.X509Certificate, + KeyObject: crypto.KeyObject + } + }; +}; + +const factoryInstance = cryptographyFactory(); + +export type TCryptographyFactory = ReturnType; + +export { factoryInstance as crypto, DigestType }; diff --git a/backend/src/lib/crypto/encryption.ts b/backend/src/lib/crypto/encryption.ts index f495681f14..d11c4ac974 100644 --- a/backend/src/lib/crypto/encryption.ts +++ b/backend/src/lib/crypto/encryption.ts @@ -1,133 +1,10 @@ -import crypto from "node:crypto"; - import argon2 from "argon2"; -import nacl from "tweetnacl"; -import naclUtils from "tweetnacl-util"; -import { SecretEncryptionAlgo, SecretKeyEncoding } from "@app/db/schemas"; +import { SecretKeyEncoding } from "@app/db/schemas"; -import { getConfig } from "../config/env"; +import { crypto, SymmetricKeySize } from "./cryptography"; -export const decodeBase64 = (s: string) => naclUtils.decodeBase64(s); -export const encodeBase64 = (u: Uint8Array) => naclUtils.encodeBase64(u); - -export const randomSecureBytes = (length = 32) => crypto.randomBytes(length); - -export type TDecryptSymmetricInput = { - ciphertext: string; - iv: string; - tag: string; - key: string; -}; -export const IV_BYTES_SIZE = 12; -export const BLOCK_SIZE_BYTES_16 = 16; - -export const decryptSymmetric = ({ ciphertext, iv, tag, key }: TDecryptSymmetricInput): string => { - const secretKey = crypto.createSecretKey(key, "base64"); - - const decipher = crypto.createDecipheriv(SecretEncryptionAlgo.AES_256_GCM, secretKey, Buffer.from(iv, "base64")); - decipher.setAuthTag(Buffer.from(tag, "base64")); - let cleartext = decipher.update(ciphertext, "base64", "utf8"); - cleartext += decipher.final("utf8"); - - return cleartext; -}; - -export const encryptSymmetric = (plaintext: string, key: string) => { - const iv = crypto.randomBytes(IV_BYTES_SIZE); - - const secretKey = crypto.createSecretKey(key, "base64"); - const cipher = crypto.createCipheriv(SecretEncryptionAlgo.AES_256_GCM, secretKey, iv); - - let ciphertext = cipher.update(plaintext, "utf8", "base64"); - ciphertext += cipher.final("base64"); - - return { - ciphertext, - iv: iv.toString("base64"), - tag: cipher.getAuthTag().toString("base64") - }; -}; - -export const encryptSymmetric128BitHexKeyUTF8 = (plaintext: string, key: string | Buffer) => { - const iv = crypto.randomBytes(BLOCK_SIZE_BYTES_16); - const cipher = crypto.createCipheriv(SecretEncryptionAlgo.AES_256_GCM, key, iv); - - let ciphertext = cipher.update(plaintext, "utf8", "base64"); - ciphertext += cipher.final("base64"); - - return { - ciphertext, - iv: iv.toString("base64"), - tag: cipher.getAuthTag().toString("base64") - }; -}; - -export const decryptSymmetric128BitHexKeyUTF8 = ({ - ciphertext, - iv, - tag, - key -}: Omit & { key: string | Buffer }): string => { - const decipher = crypto.createDecipheriv(SecretEncryptionAlgo.AES_256_GCM, key, Buffer.from(iv, "base64")); - - decipher.setAuthTag(Buffer.from(tag, "base64")); - - let cleartext = decipher.update(ciphertext, "base64", "utf8"); - cleartext += decipher.final("utf8"); - - return cleartext; -}; - -export const encryptAsymmetric = (plaintext: string, publicKey: string, privateKey: string) => { - const nonce = nacl.randomBytes(24); - const ciphertext = nacl.box( - naclUtils.decodeUTF8(plaintext), - nonce, - naclUtils.decodeBase64(publicKey), - naclUtils.decodeBase64(privateKey) - ); - - return { - ciphertext: naclUtils.encodeBase64(ciphertext), - nonce: naclUtils.encodeBase64(nonce) - }; -}; - -export type TDecryptAsymmetricInput = { - ciphertext: string; - nonce: string; - publicKey: string; - privateKey: string; -}; - -export const decryptAsymmetric = ({ ciphertext, nonce, publicKey, privateKey }: TDecryptAsymmetricInput) => { - const plaintext: Uint8Array | null = nacl.box.open( - naclUtils.decodeBase64(ciphertext), - naclUtils.decodeBase64(nonce), - naclUtils.decodeBase64(publicKey), - naclUtils.decodeBase64(privateKey) - ); - - if (plaintext == null) throw Error("Invalid ciphertext or keys"); - - return naclUtils.encodeUTF8(plaintext); -}; - -export const generateSymmetricKey = (size = 32) => crypto.randomBytes(size).toString("base64"); - -export const generateHash = (value: string | Buffer) => crypto.createHash("sha256").update(value).digest("hex"); - -export const generateAsymmetricKeyPair = () => { - const pair = nacl.box.keyPair(); - - return { - publicKey: naclUtils.encodeBase64(pair.publicKey), - privateKey: naclUtils.encodeBase64(pair.secretKey) - }; -}; - -export type TGenSecretBlindIndex = { +type TBuildSecretBlindIndexDTO = { secretName: string; keyEncoding: SecretKeyEncoding; rootEncryptionKey?: string; @@ -137,6 +14,10 @@ export type TGenSecretBlindIndex = { ciphertext: string; }; +/** + * + * @deprecated `buildSecretBlindIndexFromName` is no longer used for newer projects. It remains a relic from V1 secrets which is still supported on very old projects. + */ export const buildSecretBlindIndexFromName = async ({ secretName, ciphertext, @@ -145,13 +26,17 @@ export const buildSecretBlindIndexFromName = async ({ tag, encryptionKey, rootEncryptionKey -}: TGenSecretBlindIndex) => { +}: TBuildSecretBlindIndexDTO) => { if (!encryptionKey && !rootEncryptionKey) throw new Error("Missing secret blind index key"); let salt = ""; if (rootEncryptionKey && keyEncoding === SecretKeyEncoding.BASE64) { - salt = decryptSymmetric({ iv, ciphertext, key: rootEncryptionKey, tag }); + salt = crypto + .encryption() + .decryptSymmetric({ iv, ciphertext, key: rootEncryptionKey, tag, keySize: SymmetricKeySize.Bits256 }); } else if (encryptionKey && keyEncoding === SecretKeyEncoding.UTF8) { - salt = decryptSymmetric128BitHexKeyUTF8({ iv, ciphertext, key: encryptionKey, tag }); + salt = crypto + .encryption() + .decryptSymmetric({ iv, ciphertext, key: encryptionKey, tag, keySize: SymmetricKeySize.Bits128 }); } if (!salt) throw new Error("Missing secret blind index key"); @@ -167,75 +52,3 @@ export const buildSecretBlindIndexFromName = async ({ return secretBlindIndex.toString("base64"); }; - -export const createSecretBlindIndex = (rootEncryptionKey?: string, encryptionKey?: string) => { - if (!encryptionKey && !rootEncryptionKey) throw new Error("Atleast one encryption key needed"); - const salt = crypto.randomBytes(16).toString("base64"); - if (rootEncryptionKey) { - const data = encryptSymmetric(salt, rootEncryptionKey); - return { - ...data, - algorithm: SecretEncryptionAlgo.AES_256_GCM, - keyEncoding: SecretKeyEncoding.BASE64 - }; - } - if (encryptionKey) { - const data = encryptSymmetric128BitHexKeyUTF8(salt, encryptionKey); - return { - ...data, - algorithm: SecretEncryptionAlgo.AES_256_GCM, - keyEncoding: SecretKeyEncoding.UTF8 - }; - } - throw new Error("Failed to generate blind index due to encryption key missing"); -}; - -export const infisicalSymmetricEncypt = (data: string) => { - const appCfg = getConfig(); - const rootEncryptionKey = appCfg.ROOT_ENCRYPTION_KEY; - const encryptionKey = appCfg.ENCRYPTION_KEY; - if (rootEncryptionKey) { - const { iv, tag, ciphertext } = encryptSymmetric(data, rootEncryptionKey); - return { - iv, - tag, - ciphertext, - algorithm: SecretEncryptionAlgo.AES_256_GCM, - encoding: SecretKeyEncoding.BASE64 - }; - } - if (encryptionKey) { - const { iv, tag, ciphertext } = encryptSymmetric128BitHexKeyUTF8(data, encryptionKey); - return { - iv, - tag, - ciphertext, - algorithm: SecretEncryptionAlgo.AES_256_GCM, - encoding: SecretKeyEncoding.UTF8 - }; - } - throw new Error("Missing both encryption keys"); -}; - -export const infisicalSymmetricDecrypt = ({ - keyEncoding, - ciphertext, - tag, - iv -}: Omit & { - keyEncoding: SecretKeyEncoding; -}) => { - const appCfg = getConfig(); - // the or gate is used used in migration - const rootEncryptionKey = appCfg?.ROOT_ENCRYPTION_KEY || process.env.ROOT_ENCRYPTION_KEY; - const encryptionKey = appCfg?.ENCRYPTION_KEY || process.env.ENCRYPTION_KEY; - if (rootEncryptionKey && keyEncoding === SecretKeyEncoding.BASE64) { - const data = decryptSymmetric({ key: rootEncryptionKey, iv, tag, ciphertext }); - return data as T; - } - if (encryptionKey && keyEncoding === SecretKeyEncoding.UTF8) { - const data = decryptSymmetric128BitHexKeyUTF8({ key: encryptionKey, iv, tag, ciphertext }); - return data as T; - } - throw new Error("Missing both encryption keys"); -}; diff --git a/backend/src/lib/crypto/index.ts b/backend/src/lib/crypto/index.ts index cc6acfb805..aac797a3e3 100644 --- a/backend/src/lib/crypto/index.ts +++ b/backend/src/lib/crypto/index.ts @@ -1,17 +1,5 @@ -export { - buildSecretBlindIndexFromName, - createSecretBlindIndex, - decodeBase64, - decryptAsymmetric, - decryptSymmetric, - decryptSymmetric128BitHexKeyUTF8, - encodeBase64, - encryptAsymmetric, - encryptSymmetric, - encryptSymmetric128BitHexKeyUTF8, - generateAsymmetricKeyPair, - randomSecureBytes -} from "./encryption"; +export { crypto, SymmetricKeySize } from "./cryptography"; +export { buildSecretBlindIndexFromName } from "./encryption"; export { decryptIntegrationAuths, decryptSecretApprovals, diff --git a/backend/src/lib/crypto/secret-encryption.ts b/backend/src/lib/crypto/secret-encryption.ts index 2e04925608..7355d4bd69 100644 --- a/backend/src/lib/crypto/secret-encryption.ts +++ b/backend/src/lib/crypto/secret-encryption.ts @@ -1,4 +1,4 @@ -import crypto from "crypto"; +import nodeCrypto from "crypto"; import { z } from "zod"; import { @@ -12,7 +12,7 @@ import { TSecrets, TSecretVersions } from "../../db/schemas"; -import { decryptAsymmetric } from "./encryption"; +import { crypto } from "./cryptography"; const DecryptedValuesSchema = z.object({ id: z.string(), @@ -68,7 +68,7 @@ const decryptCipher = ({ tag: string; key: string | Buffer; }) => { - const decipher = crypto.createDecipheriv("aes-256-gcm", key, Buffer.from(iv, "base64")); + const decipher = nodeCrypto.createDecipheriv("aes-256-gcm", key, Buffer.from(iv, "base64")); decipher.setAuthTag(Buffer.from(tag, "base64")); let cleartext = decipher.update(ciphertext, "base64", "utf8"); @@ -91,7 +91,7 @@ const getDecryptedValues = (data: Array<{ ciphertext: string; iv: string; tag: s return results; }; export const decryptSecrets = (encryptedSecrets: TSecrets[], privateKey: string, latestKey: TLatestKey) => { - const key = decryptAsymmetric({ + const key = crypto.encryption().asymmetric().decrypt({ ciphertext: latestKey.encryptedKey, nonce: latestKey.nonce, publicKey: latestKey.sender.publicKey, @@ -143,7 +143,7 @@ export const decryptSecretVersions = ( privateKey: string, latestKey: TLatestKey ) => { - const key = decryptAsymmetric({ + const key = crypto.encryption().asymmetric().decrypt({ ciphertext: latestKey.encryptedKey, nonce: latestKey.nonce, publicKey: latestKey.sender.publicKey, @@ -195,7 +195,7 @@ export const decryptSecretApprovals = ( privateKey: string, latestKey: TLatestKey ) => { - const key = decryptAsymmetric({ + const key = crypto.encryption().asymmetric().decrypt({ ciphertext: latestKey.encryptedKey, nonce: latestKey.nonce, publicKey: latestKey.sender.publicKey, @@ -247,7 +247,7 @@ export const decryptIntegrationAuths = ( privateKey: string, latestKey: TLatestKey ) => { - const key = decryptAsymmetric({ + const key = crypto.encryption().asymmetric().decrypt({ ciphertext: latestKey.encryptedKey, nonce: latestKey.nonce, publicKey: latestKey.sender.publicKey, diff --git a/backend/src/lib/crypto/sign/signing.ts b/backend/src/lib/crypto/sign/signing.ts index 66f36dc0f8..1864d4be57 100644 --- a/backend/src/lib/crypto/sign/signing.ts +++ b/backend/src/lib/crypto/sign/signing.ts @@ -1,9 +1,9 @@ import { execFile } from "child_process"; -import crypto from "crypto"; import fs from "fs/promises"; import path from "path"; import { promisify } from "util"; +import { crypto } from "@app/lib/crypto/cryptography"; import { BadRequestError } from "@app/lib/errors"; import { cleanTemporaryDirectory, createTemporaryDirectory, writeToTemporaryFile } from "@app/lib/files"; import { logger } from "@app/lib/logger"; @@ -43,19 +43,19 @@ export const signingService = (algorithm: AsymmetricKeyAlgorithm): TAsymmetricSi case SigningAlgorithm.RSASSA_PSS_SHA_512: return { hashAlgorithm: SupportedHashAlgorithm.SHA512, - padding: crypto.constants.RSA_PKCS1_PSS_PADDING, + padding: crypto.rawCrypto.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, + padding: crypto.rawCrypto.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, + padding: crypto.rawCrypto.constants.RSA_PKCS1_PSS_PADDING, saltLength: SHA384_DIGEST_LENGTH }; @@ -63,17 +63,17 @@ export const signingService = (algorithm: AsymmetricKeyAlgorithm): TAsymmetricSi case SigningAlgorithm.RSASSA_PKCS1_V1_5_SHA_512: return { hashAlgorithm: SupportedHashAlgorithm.SHA512, - padding: crypto.constants.RSA_PKCS1_PADDING + padding: crypto.rawCrypto.constants.RSA_PKCS1_PADDING }; case SigningAlgorithm.RSASSA_PKCS1_V1_5_SHA_384: return { hashAlgorithm: SupportedHashAlgorithm.SHA384, - padding: crypto.constants.RSA_PKCS1_PADDING + padding: crypto.rawCrypto.constants.RSA_PKCS1_PADDING }; case SigningAlgorithm.RSASSA_PKCS1_V1_5_SHA_256: return { hashAlgorithm: SupportedHashAlgorithm.SHA256, - padding: crypto.constants.RSA_PKCS1_PADDING + padding: crypto.rawCrypto.constants.RSA_PKCS1_PADDING }; // ECDSA @@ -389,7 +389,7 @@ export const signingService = (algorithm: AsymmetricKeyAlgorithm): TAsymmetricSi return signature; } - const privateKeyObject = crypto.createPrivateKey({ + const privateKeyObject = crypto.rawCrypto.createPrivateKey({ key: privateKey, format: "pem", type: "pkcs8" @@ -397,7 +397,7 @@ export const signingService = (algorithm: AsymmetricKeyAlgorithm): TAsymmetricSi // For RSA signatures if (signingAlgorithm.startsWith("RSA")) { - const signer = crypto.createSign(hashAlgorithm); + const signer = crypto.rawCrypto.createSign(hashAlgorithm); signer.update(data); return signer.sign({ @@ -408,7 +408,7 @@ export const signingService = (algorithm: AsymmetricKeyAlgorithm): TAsymmetricSi } if (signingAlgorithm.startsWith("ECDSA")) { // For ECDSA signatures - const signer = crypto.createSign(hashAlgorithm); + const signer = crypto.rawCrypto.createSign(hashAlgorithm); signer.update(data); return signer.sign({ key: privateKeyObject, @@ -452,7 +452,7 @@ export const signingService = (algorithm: AsymmetricKeyAlgorithm): TAsymmetricSi return signatureValid; } - const publicKeyObject = crypto.createPublicKey({ + const publicKeyObject = crypto.rawCrypto.createPublicKey({ key: publicKey, format: "der", type: "spki" @@ -460,7 +460,7 @@ export const signingService = (algorithm: AsymmetricKeyAlgorithm): TAsymmetricSi // For RSA signatures if (signingAlgorithm.startsWith("RSA")) { - const verifier = crypto.createVerify(hashAlgorithm); + const verifier = crypto.rawCrypto.createVerify(hashAlgorithm); verifier.update(data); return verifier.verify( @@ -474,7 +474,7 @@ export const signingService = (algorithm: AsymmetricKeyAlgorithm): TAsymmetricSi } // For ECDSA signatures if (signingAlgorithm.startsWith("ECDSA")) { - const verifier = crypto.createVerify(hashAlgorithm); + const verifier = crypto.rawCrypto.createVerify(hashAlgorithm); verifier.update(data); return verifier.verify( { @@ -499,7 +499,7 @@ export const signingService = (algorithm: AsymmetricKeyAlgorithm): TAsymmetricSi const generateAsymmetricPrivateKey = async () => { const { privateKey } = await new Promise<{ privateKey: string }>((resolve, reject) => { if (algorithm.startsWith("RSA")) { - crypto.generateKeyPair( + crypto.rawCrypto.generateKeyPair( "rsa", { modulusLength: Number(algorithm.split("_")[1]), @@ -517,7 +517,7 @@ export const signingService = (algorithm: AsymmetricKeyAlgorithm): TAsymmetricSi } else { const { full: namedCurve } = $getEcCurveName(algorithm); - crypto.generateKeyPair( + crypto.rawCrypto.generateKeyPair( "ec", { namedCurve, @@ -541,13 +541,13 @@ export const signingService = (algorithm: AsymmetricKeyAlgorithm): TAsymmetricSi }; const getPublicKeyFromPrivateKey = (privateKey: Buffer) => { - const privateKeyObj = crypto.createPrivateKey({ + const privateKeyObj = crypto.rawCrypto.createPrivateKey({ key: privateKey, format: "pem", type: "pkcs8" }); - const publicKey = crypto.createPublicKey(privateKeyObj).export({ + const publicKey = crypto.rawCrypto.createPublicKey(privateKeyObj).export({ type: "spki", format: "der" }); diff --git a/backend/src/lib/crypto/signing.ts b/backend/src/lib/crypto/signing.ts index 36c8587153..a97fe80205 100644 --- a/backend/src/lib/crypto/signing.ts +++ b/backend/src/lib/crypto/signing.ts @@ -1,9 +1,11 @@ -import crypto, { KeyObject } from "crypto"; +import { KeyObject } from "crypto"; import fs from "fs/promises"; import path from "path"; +import { crypto } from "./cryptography"; + export const verifySignature = (data: string, signature: Buffer, publicKey: KeyObject) => { - const verify = crypto.createVerify("SHA256"); + const verify = crypto.rawCrypto.createVerify("SHA256"); verify.update(data); verify.end(); return verify.verify(publicKey, signature); @@ -12,7 +14,7 @@ export const verifySignature = (data: string, signature: Buffer, publicKey: KeyO export const verifyOfflineLicense = async (licenseContents: string, signature: string) => { const publicKeyPem = await fs.readFile(path.join(__dirname, "license_public_key.pem"), "utf8"); - const publicKey = crypto.createPublicKey({ + const publicKey = crypto.rawCrypto.createPublicKey({ key: publicKeyPem, format: "pem", type: "pkcs1" diff --git a/backend/src/lib/crypto/srp.ts b/backend/src/lib/crypto/srp.ts index e6afd0f99d..5c204c0338 100644 --- a/backend/src/lib/crypto/srp.ts +++ b/backend/src/lib/crypto/srp.ts @@ -1,13 +1,10 @@ import argon2 from "argon2"; -import crypto from "crypto"; import jsrp from "jsrp"; -import nacl from "tweetnacl"; -import tweetnacl from "tweetnacl-util"; import { TUserEncryptionKeys } from "@app/db/schemas"; import { UserEncryption } from "@app/services/user/user-types"; -import { decryptSymmetric128BitHexKeyUTF8, encryptAsymmetric, encryptSymmetric } from "./encryption"; +import { crypto, SymmetricKeySize } from "./cryptography"; export const generateSrpServerKey = async (salt: string, verifier: string) => { // eslint-disable-next-line new-cap @@ -42,11 +39,10 @@ export const generateUserSrpKeys = async ( password: string, customKeys?: { publicKey: string; privateKey: string } ) => { - const pair = nacl.box.keyPair(); - const secretKeyUint8Array = pair.secretKey; - const publicKeyUint8Array = pair.publicKey; - const privateKey = customKeys?.privateKey || tweetnacl.encodeBase64(secretKeyUint8Array); - const publicKey = customKeys?.publicKey || tweetnacl.encodeBase64(publicKeyUint8Array); + const pair = crypto.encryption().asymmetric().generateKeyPair(); + + const privateKey = customKeys?.privateKey || pair.privateKey; + const publicKey = customKeys?.publicKey || pair.publicKey; // eslint-disable-next-line const client = new jsrp.client(); @@ -78,7 +74,11 @@ export const generateUserSrpKeys = async ( ciphertext: encryptedPrivateKey, iv: encryptedPrivateKeyIV, tag: encryptedPrivateKeyTag - } = encryptSymmetric(privateKey, key.toString("base64")); + } = crypto.encryption().encryptSymmetric({ + plaintext: privateKey, + key: key.toString("base64"), + keySize: SymmetricKeySize.Bits256 + }); // create the protected key by encrypting the symmetric key // [key] with the derived key @@ -86,7 +86,11 @@ export const generateUserSrpKeys = async ( ciphertext: protectedKey, iv: protectedKeyIV, tag: protectedKeyTag - } = encryptSymmetric(key.toString("hex"), derivedKey.toString("base64")); + } = crypto.encryption().encryptSymmetric({ + plaintext: key.toString("hex"), + key: derivedKey.toString("base64"), + keySize: SymmetricKeySize.Bits256 + }); return { protectedKey, @@ -117,11 +121,12 @@ export const getUserPrivateKey = async ( > ) => { if (user.encryptionVersion === UserEncryption.V1) { - return decryptSymmetric128BitHexKeyUTF8({ + return crypto.encryption().decryptSymmetric({ ciphertext: user.encryptedPrivateKey, iv: user.iv, tag: user.tag, - key: password.slice(0, 32).padStart(32 + (password.slice(0, 32).length - new Blob([password]).size), "0") + key: password.slice(0, 32).padStart(32 + (password.slice(0, 32).length - new Blob([password]).size), "0"), + keySize: SymmetricKeySize.Bits128 }); } if ( @@ -140,18 +145,20 @@ export const getUserPrivateKey = async ( raw: true }); if (!derivedKey) throw new Error("Failed to derive key from password"); - const key = decryptSymmetric128BitHexKeyUTF8({ + const key = crypto.encryption().decryptSymmetric({ ciphertext: user.protectedKey, iv: user.protectedKeyIV, tag: user.protectedKeyTag, - key: derivedKey + key: derivedKey, + keySize: SymmetricKeySize.Bits128 }); - const privateKey = decryptSymmetric128BitHexKeyUTF8({ + const privateKey = crypto.encryption().decryptSymmetric({ ciphertext: user.encryptedPrivateKey, iv: user.iv, tag: user.tag, - key: Buffer.from(key, "hex") + key: Buffer.from(key, "hex"), + keySize: SymmetricKeySize.Bits128 }); return privateKey; } @@ -160,6 +167,6 @@ export const getUserPrivateKey = async ( export const buildUserProjectKey = async (privateKey: string, publickey: string) => { const randomBytes = crypto.randomBytes(16).toString("hex"); - const { nonce, ciphertext } = encryptAsymmetric(randomBytes, publickey, privateKey); + const { nonce, ciphertext } = crypto.encryption().asymmetric().encrypt(randomBytes, publickey, privateKey); return { nonce, ciphertext }; }; diff --git a/backend/src/lib/files/files.ts b/backend/src/lib/files/files.ts index 063d71d09a..68c8acd604 100644 --- a/backend/src/lib/files/files.ts +++ b/backend/src/lib/files/files.ts @@ -1,8 +1,8 @@ -import crypto from "crypto"; import fs from "fs/promises"; import os from "os"; import path from "path"; +import { crypto } from "@app/lib/crypto/cryptography"; import { logger } from "@app/lib/logger"; const baseDir = path.join(os.tmpdir(), "infisical"); diff --git a/backend/src/lib/gateway/gateway.ts b/backend/src/lib/gateway/gateway.ts index 46481a0496..711fee4a0c 100644 --- a/backend/src/lib/gateway/gateway.ts +++ b/backend/src/lib/gateway/gateway.ts @@ -1,11 +1,12 @@ /* eslint-disable no-await-in-loop */ -import crypto from "node:crypto"; import net from "node:net"; import quicDefault, * as quicModule from "@infisical/quic"; import axios from "axios"; import https from "https"; +import { crypto } from "@app/lib/crypto/cryptography"; + import { BadRequestError } from "../errors"; import { logger } from "../logger"; import { @@ -48,8 +49,8 @@ const createQuicConnection = async ( verifyPeer: true, verifyCallback: async (certs) => { if (!certs || certs.length === 0) return quic.native.CryptoError.CertificateRequired; - const serverCertificate = new crypto.X509Certificate(Buffer.from(certs[0])); - const caCertificate = new crypto.X509Certificate(tlsOptions.ca); + const serverCertificate = new crypto.rawCrypto.X509Certificate(Buffer.from(certs[0])); + const caCertificate = new crypto.rawCrypto.X509Certificate(tlsOptions.ca); const isValidServerCertificate = serverCertificate.verify(caCertificate.publicKey); if (!isValidServerCertificate) return quic.native.CryptoError.BadCertificate; @@ -72,7 +73,7 @@ const createQuicConnection = async ( crypto: { ops: { randomBytes: async (data) => { - crypto.getRandomValues(new Uint8Array(data)); + crypto.rawCrypto.getRandomValues(new Uint8Array(data)); } } } diff --git a/backend/src/lib/red-lock/index.ts b/backend/src/lib/red-lock/index.ts index e1cc4f5872..64a48fd0cf 100644 --- a/backend/src/lib/red-lock/index.ts +++ b/backend/src/lib/red-lock/index.ts @@ -1,7 +1,7 @@ /* eslint-disable */ // Source code credits: https://github.com/mike-marcacci/node-redlock // Taken to avoid external dependency -import { randomBytes, createHash } from "crypto"; +import { crypto } from "@app/lib/crypto/cryptography"; import { EventEmitter } from "events"; // AbortController became available as a global in node version 16. Once version @@ -251,14 +251,14 @@ export class Redlock extends EventEmitter { * Generate a sha1 hash compatible with redis evalsha. */ private _hash(value: string): string { - return createHash("sha1").update(value).digest("hex"); + return crypto.rawCrypto.createHash("sha1").update(value).digest("hex"); } /** * Generate a cryptographically random string. */ private _random(): string { - return randomBytes(16).toString("hex"); + return crypto.randomBytes(16).toString("hex"); } /** diff --git a/backend/src/lib/turn/credentials.ts b/backend/src/lib/turn/credentials.ts index 37dcaa78bd..a41624b06f 100644 --- a/backend/src/lib/turn/credentials.ts +++ b/backend/src/lib/turn/credentials.ts @@ -1,11 +1,11 @@ -import crypto from "node:crypto"; +import { crypto } from "@app/lib/crypto/cryptography"; const TURN_TOKEN_TTL = 24 * 60 * 60 * 1000; // 24 hours in milliseconds export const getTurnCredentials = (id: string, authSecret: string, ttl = TURN_TOKEN_TTL) => { const timestamp = Math.floor((Date.now() + ttl) / 1000); const username = `${timestamp}:${id}`; - const hmac = crypto.createHmac("sha1", authSecret); + const hmac = crypto.rawCrypto.createHmac("sha1", authSecret); hmac.update(username); const password = hmac.digest("base64"); diff --git a/backend/src/main.ts b/backend/src/main.ts index d141b62d51..15dd0322b5 100644 --- a/backend/src/main.ts +++ b/backend/src/main.ts @@ -9,12 +9,14 @@ import { initAuditLogDbConnection, initDbConnection } from "./db"; import { keyStoreFactory } from "./keystore/keystore"; import { formatSmtpConfig, initEnvConfig } from "./lib/config/env"; import { buildRedisFromConfig } from "./lib/config/redis"; +import { crypto } from "./lib/crypto/cryptography"; import { removeTemporaryBaseDirectory } from "./lib/files"; import { initLogger } from "./lib/logger"; import { queueServiceFactory } from "./queue"; import { main } from "./server/app"; import { bootstrapCheck } from "./server/boot-strap-check"; import { smtpServiceFactory } from "./services/smtp/smtp-service"; +import { superAdminDALFactory } from "./services/super-admin/super-admin-dal"; dotenv.config(); @@ -33,6 +35,9 @@ const run = async () => { })) }); + const superAdminDAL = superAdminDALFactory(db); + await crypto.initialize(superAdminDAL); + const auditLogDb = envConfig.AUDIT_LOGS_DB_CONNECTION_URI ? initAuditLogDbConnection({ dbConnectionUri: envConfig.AUDIT_LOGS_DB_CONNECTION_URI, @@ -60,6 +65,7 @@ const run = async () => { const server = await main({ db, auditLogDb, + superAdminDAL, hsmModule: hsmModule.getModule(), smtp, logger, diff --git a/backend/src/queue/queue-service.ts b/backend/src/queue/queue-service.ts index a8d0d10a62..da841f1ccf 100644 --- a/backend/src/queue/queue-service.ts +++ b/backend/src/queue/queue-service.ts @@ -1,4 +1,13 @@ -import { Job, JobsOptions, Queue, QueueOptions, RepeatOptions, Worker, WorkerListener } from "bullmq"; +import { + Job, + JobsOptions, + Queue, + QueueOptions, + RepeatOptions, + Worker, + WorkerListener, + AdvancedRepeatOptions +} from "bullmq"; import PgBoss, { WorkOptions } from "pg-boss"; import { SecretEncryptionAlgo, SecretKeyEncoding } from "@app/db/schemas"; @@ -438,6 +447,9 @@ export const queueServiceFactory = ( queueContainer[name] = new Queue(name as string, { ...queueSettings, + settings: { + repeatKeyHashAlgorithm: "sha256" + }, connection }); diff --git a/backend/src/server/app.ts b/backend/src/server/app.ts index 3f5c477ef5..321a3656e8 100644 --- a/backend/src/server/app.ts +++ b/backend/src/server/app.ts @@ -22,6 +22,7 @@ import { CustomLogger } from "@app/lib/logger/logger"; import { alphaNumericNanoId } from "@app/lib/nanoid"; import { TQueueServiceFactory } from "@app/queue"; import { TSmtpService } from "@app/services/smtp/smtp-service"; +import { TSuperAdminDALFactory } from "@app/services/super-admin/super-admin-dal"; import { globalRateLimiterCfg } from "./config/rateLimiter"; import { addErrorsToResponseSchemas } from "./plugins/add-errors-to-response-schemas"; @@ -44,10 +45,22 @@ type TMain = { hsmModule: HsmModule; redis: Redis; envConfig: TEnvConfig; + superAdminDAL: TSuperAdminDALFactory; }; // Run the server! -export const main = async ({ db, hsmModule, auditLogDb, smtp, logger, queue, keyStore, redis, envConfig }: TMain) => { +export const main = async ({ + db, + hsmModule, + auditLogDb, + smtp, + logger, + queue, + keyStore, + redis, + envConfig, + superAdminDAL +}: TMain) => { const appCfg = getConfig(); const server = fastify({ @@ -128,7 +141,16 @@ export const main = async ({ db, hsmModule, auditLogDb, smtp, logger, queue, key }) }); - await server.register(registerRoutes, { smtp, queue, db, auditLogDb, keyStore, hsmModule, envConfig }); + await server.register(registerRoutes, { + smtp, + queue, + db, + auditLogDb, + keyStore, + hsmModule, + envConfig, + superAdminDAL + }); await server.register(registerServeUI, { standaloneMode: appCfg.STANDALONE_MODE || IS_PACKAGED, diff --git a/backend/src/server/routes/index.ts b/backend/src/server/routes/index.ts index aeef89838c..8aeeffdd3d 100644 --- a/backend/src/server/routes/index.ts +++ b/backend/src/server/routes/index.ts @@ -278,7 +278,7 @@ import { slackIntegrationDALFactory } from "@app/services/slack/slack-integratio import { slackServiceFactory } from "@app/services/slack/slack-service"; import { TSmtpService } from "@app/services/smtp/smtp-service"; import { invalidateCacheQueueFactory } from "@app/services/super-admin/invalidate-cache-queue"; -import { superAdminDALFactory } from "@app/services/super-admin/super-admin-dal"; +import { TSuperAdminDALFactory } from "@app/services/super-admin/super-admin-dal"; import { getServerCfg, superAdminServiceFactory } from "@app/services/super-admin/super-admin-service"; import { telemetryDALFactory } from "@app/services/telemetry/telemetry-dal"; import { telemetryQueueServiceFactory } from "@app/services/telemetry/telemetry-queue"; @@ -310,6 +310,7 @@ export const registerRoutes = async ( server: FastifyZodProvider, { auditLogDb, + superAdminDAL, db, hsmModule, smtp: smtpService, @@ -318,6 +319,7 @@ export const registerRoutes = async ( envConfig }: { auditLogDb?: Knex; + superAdminDAL: TSuperAdminDALFactory; db: Knex; hsmModule: HsmModule; smtp: TSmtpService; @@ -341,7 +343,6 @@ export const registerRoutes = async ( const orgBotDAL = orgBotDALFactory(db); const incidentContactDAL = incidentContactDALFactory(db); const orgRoleDAL = orgRoleDALFactory(db); - const superAdminDAL = superAdminDALFactory(db); const rateLimitDAL = rateLimitDALFactory(db); const apiKeyDAL = apiKeyDALFactory(db); diff --git a/backend/src/server/routes/v1/admin-router.ts b/backend/src/server/routes/v1/admin-router.ts index a21587db1e..8715a348fc 100644 --- a/backend/src/server/routes/v1/admin-router.ts +++ b/backend/src/server/routes/v1/admin-router.ts @@ -9,6 +9,7 @@ import { UsersSchema } from "@app/db/schemas"; import { getConfig } from "@app/lib/config/env"; +import { crypto } from "@app/lib/crypto/cryptography"; import { BadRequestError } from "@app/lib/errors"; import { invalidateCacheLimit, readLimit, writeLimit } from "@app/server/config/rateLimiter"; import { getTelemetryDistinctId } from "@app/server/lib/telemetry"; @@ -56,9 +57,11 @@ export const registerAdminRouter = async (server: FastifyZodProvider) => { handler: async () => { const config = await getServerCfg(); const serverEnvs = getConfig(); + return { config: { ...config, + fipsEnabled: crypto.isFipsModeEnabled(), isMigrationModeOn: serverEnvs.MAINTENANCE_MODE, isSecretScanningDisabled: serverEnvs.DISABLE_SECRET_SCANNING } diff --git a/backend/src/server/routes/v1/identity-tls-cert-auth-router.ts b/backend/src/server/routes/v1/identity-tls-cert-auth-router.ts index 40060ad40a..cecd7f2db2 100644 --- a/backend/src/server/routes/v1/identity-tls-cert-auth-router.ts +++ b/backend/src/server/routes/v1/identity-tls-cert-auth-router.ts @@ -1,11 +1,10 @@ -import crypto from "node:crypto"; - import { z } from "zod"; import { IdentityTlsCertAuthsSchema } from "@app/db/schemas"; import { EventType } from "@app/ee/services/audit-log/audit-log-types"; import { ApiDocsTags, TLS_CERT_AUTH } from "@app/lib/api-docs"; import { getConfig } from "@app/lib/config/env"; +import { crypto } from "@app/lib/crypto/cryptography"; import { BadRequestError } from "@app/lib/errors"; import { readLimit, writeLimit } from "@app/server/config/rateLimiter"; import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; @@ -28,9 +27,9 @@ const validateCaCertificate = (caCert: string) => { if (!caCert) return true; try { // eslint-disable-next-line no-new - new crypto.X509Certificate(caCert); + new crypto.rawCrypto.X509Certificate(caCert); return true; - } catch (err) { + } catch { return false; } }; diff --git a/backend/src/services/api-key/api-key-service.ts b/backend/src/services/api-key/api-key-service.ts index 96fb90026e..b928bbd6de 100644 --- a/backend/src/services/api-key/api-key-service.ts +++ b/backend/src/services/api-key/api-key-service.ts @@ -1,9 +1,6 @@ -import crypto from "node:crypto"; - -import bcrypt from "bcrypt"; - import { TApiKeys } from "@app/db/schemas/api-keys"; import { getConfig } from "@app/lib/config/env"; +import { crypto } from "@app/lib/crypto/cryptography"; import { NotFoundError, UnauthorizedError } from "@app/lib/errors"; import { TUserDALFactory } from "../user/user-dal"; @@ -27,7 +24,7 @@ export const apiKeyServiceFactory = ({ apiKeyDAL, userDAL }: TApiKeyServiceFacto const createApiKey = async (userId: string, name: string, expiresIn: number) => { const appCfg = getConfig(); const secret = crypto.randomBytes(16).toString("hex"); - const secretHash = await bcrypt.hash(secret, appCfg.SALT_ROUNDS); + const secretHash = await crypto.hashing().createHash(secret, appCfg.SALT_ROUNDS); const expiresAt = new Date(); expiresAt.setSeconds(expiresAt.getSeconds() + expiresIn); @@ -59,7 +56,7 @@ export const apiKeyServiceFactory = ({ apiKeyDAL, userDAL }: TApiKeyServiceFacto throw new UnauthorizedError(); } - const isMatch = await bcrypt.compare(TOKEN_SECRET, apiKey.secretHash); + const isMatch = await crypto.hashing().compareHash(TOKEN_SECRET, apiKey.secretHash); if (!isMatch) throw new UnauthorizedError(); await apiKeyDAL.updateById(apiKey.id, { lastUsed: new Date() }); const user = await userDAL.findById(apiKey.userId); diff --git a/backend/src/services/app-connection/app-connection-fns.ts b/backend/src/services/app-connection/app-connection-fns.ts index 78f6b99b5f..2a4d593323 100644 --- a/backend/src/services/app-connection/app-connection-fns.ts +++ b/backend/src/services/app-connection/app-connection-fns.ts @@ -6,7 +6,7 @@ import { } from "@app/ee/services/app-connections/oci"; import { getOracleDBConnectionListItem, OracleDBConnectionMethod } from "@app/ee/services/app-connections/oracledb"; import { TLicenseServiceFactory } from "@app/ee/services/license/license-service"; -import { generateHash } from "@app/lib/crypto/encryption"; +import { crypto } from "@app/lib/crypto/cryptography"; import { BadRequestError } from "@app/lib/errors"; import { APP_CONNECTION_NAME_MAP, APP_CONNECTION_PLAN_MAP } from "@app/services/app-connection/app-connection-maps"; import { @@ -290,7 +290,7 @@ export const decryptAppConnection = async ( orgId: appConnection.orgId, kmsService }), - credentialsHash: generateHash(appConnection.encryptedCredentials) + credentialsHash: crypto.rawCrypto.createHash("sha256").update(appConnection.encryptedCredentials).digest("hex") } as TAppConnection; }; diff --git a/backend/src/services/app-connection/app-connection-service.ts b/backend/src/services/app-connection/app-connection-service.ts index bcadd96364..487a40e10b 100644 --- a/backend/src/services/app-connection/app-connection-service.ts +++ b/backend/src/services/app-connection/app-connection-service.ts @@ -6,7 +6,7 @@ import { ValidateOracleDBConnectionCredentialsSchema } from "@app/ee/services/ap import { TLicenseServiceFactory } from "@app/ee/services/license/license-service"; import { OrgPermissionAppConnectionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types"; -import { generateHash } from "@app/lib/crypto/encryption"; +import { crypto } from "@app/lib/crypto/cryptography"; import { DatabaseErrorCode } from "@app/lib/error-codes"; import { BadRequestError, DatabaseError, NotFoundError } from "@app/lib/errors"; import { DiscriminativePick, OrgServiceActor } from "@app/lib/types"; @@ -272,7 +272,7 @@ export const appConnectionServiceFactory = ({ return { ...connection, - credentialsHash: generateHash(connection.encryptedCredentials), + credentialsHash: crypto.rawCrypto.createHash("sha256").update(connection.encryptedCredentials).digest("hex"), credentials: validatedCredentials } as TAppConnection; } catch (err) { diff --git a/backend/src/services/app-connection/aws/aws-connection-fns.ts b/backend/src/services/app-connection/aws/aws-connection-fns.ts index 28660173bb..2bcf759377 100644 --- a/backend/src/services/app-connection/aws/aws-connection-fns.ts +++ b/backend/src/services/app-connection/aws/aws-connection-fns.ts @@ -1,9 +1,9 @@ import { AssumeRoleCommand, STSClient } from "@aws-sdk/client-sts"; import AWS from "aws-sdk"; import { AxiosError } from "axios"; -import { randomUUID } from "crypto"; import { getConfig } from "@app/lib/config/env"; +import { crypto } from "@app/lib/crypto/cryptography"; import { BadRequestError, InternalServerError } from "@app/lib/errors"; import { logger } from "@app/lib/logger"; import { AppConnection, AWSRegion } from "@app/services/app-connection/app-connection-enums"; @@ -46,7 +46,7 @@ export const getAwsConnectionConfig = async (appConnection: TAwsConnectionConfig const command = new AssumeRoleCommand({ RoleArn: credentials.roleArn, - RoleSessionName: `infisical-app-connection-${randomUUID()}`, + RoleSessionName: `infisical-app-connection-${crypto.rawCrypto.randomUUID()}`, DurationSeconds: 900, // 15 mins ExternalId: orgId }); diff --git a/backend/src/services/auth-token/auth-token-service.ts b/backend/src/services/auth-token/auth-token-service.ts index f264643403..5070952485 100644 --- a/backend/src/services/auth-token/auth-token-service.ts +++ b/backend/src/services/auth-token/auth-token-service.ts @@ -1,11 +1,9 @@ -import crypto from "node:crypto"; - -import bcrypt from "bcrypt"; import jwt from "jsonwebtoken"; import { Knex } from "knex"; import { TAuthTokens, TAuthTokenSessions } from "@app/db/schemas"; import { getConfig } from "@app/lib/config/env"; +import { crypto } from "@app/lib/crypto/cryptography"; import { ForbiddenRequestError, NotFoundError, UnauthorizedError } from "@app/lib/errors"; import { TOrgMembershipDALFactory } from "@app/services/org-membership/org-membership-dal"; @@ -81,7 +79,7 @@ export const tokenServiceFactory = ({ tokenDAL, userDAL, orgMembershipDAL }: TAu const createTokenForUser = async ({ type, userId, orgId }: TCreateTokenForUserDTO) => { const { token, ...tkCfg } = getTokenConfig(type); const appCfg = getConfig(); - const tokenHash = await bcrypt.hash(token, appCfg.SALT_ROUNDS); + const tokenHash = await crypto.hashing().createHash(token, appCfg.SALT_ROUNDS); await tokenDAL.transaction(async (tx) => { await tokenDAL.delete({ userId, type, orgId: orgId || null }, tx); const newToken = await tokenDAL.create( @@ -115,7 +113,7 @@ export const tokenServiceFactory = ({ tokenDAL, userDAL, orgMembershipDAL }: TAu throw new Error("Token expired. Please try again"); } - const isValidToken = await bcrypt.compare(code, token.tokenHash); + const isValidToken = await crypto.hashing().compareHash(code, token.tokenHash); if (!isValidToken) { if (token?.triesLeft) { if (token.triesLeft === 1) { diff --git a/backend/src/services/auth/auth-login-service.ts b/backend/src/services/auth/auth-login-service.ts index 689228941e..a70fa5cc28 100644 --- a/backend/src/services/auth/auth-login-service.ts +++ b/backend/src/services/auth/auth-login-service.ts @@ -1,4 +1,3 @@ -import bcrypt from "bcrypt"; import jwt from "jsonwebtoken"; import { Knex } from "knex"; @@ -8,7 +7,7 @@ import { isAuthMethodSaml } from "@app/ee/services/permission/permission-fns"; import { getConfig } from "@app/lib/config/env"; import { request } from "@app/lib/config/request"; import { generateSrpServerKey, srpCheckClientProof } from "@app/lib/crypto"; -import { infisicalSymmetricEncypt } from "@app/lib/crypto/encryption"; +import { crypto } from "@app/lib/crypto/cryptography"; import { getUserPrivateKey } from "@app/lib/crypto/srp"; import { BadRequestError, DatabaseError, ForbiddenRequestError, UnauthorizedError } from "@app/lib/errors"; import { getMinExpiresIn, removeTrailingSlash } from "@app/lib/fn"; @@ -336,8 +335,11 @@ export const authLoginServiceFactory = ({ ); return ""; }); - const hashedPassword = await bcrypt.hash(password, cfg.BCRYPT_SALT_ROUND); - const { iv, tag, ciphertext, encoding } = infisicalSymmetricEncypt(privateKey); + + const hashedPassword = await crypto.hashing().createHash(password, cfg.SALT_ROUNDS); + + const { iv, tag, ciphertext, encoding } = crypto.encryption().encryptWithRootEncryptionKey(privateKey); + await userDAL.updateUserEncryptionByUserId(userEnc.userId, { serverPrivateKey: null, clientPublicKey: null, diff --git a/backend/src/services/auth/auth-password-service.ts b/backend/src/services/auth/auth-password-service.ts index 5e2f8c7b33..bd8a9a23de 100644 --- a/backend/src/services/auth/auth-password-service.ts +++ b/backend/src/services/auth/auth-password-service.ts @@ -1,10 +1,9 @@ -import bcrypt from "bcrypt"; import jwt from "jsonwebtoken"; import { SecretEncryptionAlgo, SecretKeyEncoding } from "@app/db/schemas"; import { getConfig } from "@app/lib/config/env"; import { generateSrpServerKey, srpCheckClientProof } from "@app/lib/crypto"; -import { infisicalSymmetricDecrypt, infisicalSymmetricEncypt } from "@app/lib/crypto/encryption"; +import { crypto } from "@app/lib/crypto/cryptography"; import { generateUserSrpKeys } from "@app/lib/crypto/srp"; import { BadRequestError } from "@app/lib/errors"; import { logger } from "@app/lib/logger"; @@ -95,7 +94,7 @@ export const authPaswordServiceFactory = ({ if (!isValidClientProof) throw new Error("Failed to authenticate. Try again?"); const appCfg = getConfig(); - const hashedPassword = await bcrypt.hash(password, appCfg.BCRYPT_SALT_ROUND); + const hashedPassword = await crypto.hashing().createHash(password, appCfg.SALT_ROUNDS); await userDAL.updateUserEncryptionByUserId(userId, { encryptionVersion: 2, protectedKey, @@ -208,13 +207,13 @@ export const authPaswordServiceFactory = ({ throw new BadRequestError({ message: "Current password is required." }); } - const isValid = await bcrypt.compare(oldPassword, user.hashedPassword); + const isValid = await crypto.hashing().compareHash(oldPassword, user.hashedPassword); if (!isValid) { throw new BadRequestError({ message: "Incorrect current password." }); } } - const newHashedPassword = await bcrypt.hash(newPassword, cfg.BCRYPT_SALT_ROUND); + const newHashedPassword = await crypto.hashing().createHash(newPassword, cfg.SALT_ROUNDS); // we need to get the original private key first for v2 let privateKey: string; @@ -225,7 +224,7 @@ export const authPaswordServiceFactory = ({ user.serverEncryptedPrivateKeyEncoding && user.encryptionVersion === UserEncryption.V2 ) { - privateKey = infisicalSymmetricDecrypt({ + privateKey = crypto.encryption().decryptWithRootEncryptionKey({ iv: user.serverEncryptedPrivateKeyIV, tag: user.serverEncryptedPrivateKeyTag, ciphertext: user.serverEncryptedPrivateKey, @@ -243,7 +242,7 @@ export const authPaswordServiceFactory = ({ privateKey }); - const { tag, iv, ciphertext, encoding } = infisicalSymmetricEncypt(privateKey); + const { tag, iv, ciphertext, encoding } = crypto.encryption().encryptWithRootEncryptionKey(privateKey); await userDAL.updateUserEncryptionByUserId(userId, { hashedPassword: newHashedPassword, @@ -285,7 +284,7 @@ export const authPaswordServiceFactory = ({ }: TResetPasswordViaBackupKeyDTO) => { const cfg = getConfig(); - const hashedPassword = await bcrypt.hash(password, cfg.BCRYPT_SALT_ROUND); + const hashedPassword = await crypto.hashing().createHash(password, cfg.SALT_ROUNDS); await userDAL.updateUserEncryptionByUserId(userId, { encryptionVersion: 2, @@ -461,7 +460,7 @@ export const authPaswordServiceFactory = ({ const cfg = getConfig(); - const hashedPassword = await bcrypt.hash(password, cfg.BCRYPT_SALT_ROUND); + const hashedPassword = await crypto.hashing().createHash(password, cfg.SALT_ROUNDS); await userDAL.updateUserEncryptionByUserId( actor.id, diff --git a/backend/src/services/auth/auth-signup-service.ts b/backend/src/services/auth/auth-signup-service.ts index 7e11f25cb3..48f5a434ee 100644 --- a/backend/src/services/auth/auth-signup-service.ts +++ b/backend/src/services/auth/auth-signup-service.ts @@ -1,4 +1,3 @@ -import bcrypt from "bcrypt"; import jwt from "jsonwebtoken"; import { OrgMembershipStatus, SecretKeyEncoding, TableName } from "@app/db/schemas"; @@ -7,7 +6,7 @@ import { TUserGroupMembershipDALFactory } from "@app/ee/services/group/user-grou import { TLicenseServiceFactory } from "@app/ee/services/license/license-service"; import { isAuthMethodSaml } from "@app/ee/services/permission/permission-fns"; import { getConfig } from "@app/lib/config/env"; -import { infisicalSymmetricDecrypt, infisicalSymmetricEncypt } from "@app/lib/crypto/encryption"; +import { crypto } from "@app/lib/crypto/cryptography"; import { generateUserSrpKeys, getUserPrivateKey } from "@app/lib/crypto/srp"; import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors"; import { getMinExpiresIn } from "@app/lib/fn"; @@ -193,7 +192,7 @@ export const authSignupServiceFactory = ({ validateSignUpAuthorization(authorization, user.id); } - const hashedPassword = await bcrypt.hash(password, appCfg.BCRYPT_SALT_ROUND); + const hashedPassword = await crypto.hashing().createHash(password, appCfg.SALT_ROUNDS); const privateKey = await getUserPrivateKey(password, { salt, protectedKey, @@ -204,7 +203,7 @@ export const authSignupServiceFactory = ({ tag: encryptedPrivateKeyTag, encryptionVersion: UserEncryption.V2 }); - const { tag, encoding, ciphertext, iv } = infisicalSymmetricEncypt(privateKey); + const { tag, encoding, ciphertext, iv } = crypto.encryption().encryptWithRootEncryptionKey(privateKey); const updateduser = await authDAL.transaction(async (tx) => { const us = await userDAL.updateById(user.id, { firstName, lastName, isAccepted: true }, tx); if (!us) throw new Error("User not found"); @@ -225,7 +224,7 @@ export const authSignupServiceFactory = ({ systemGeneratedUserEncryptionKey.serverEncryptedPrivateKeyEncoding ) { // get server generated password - const serverGeneratedPassword = infisicalSymmetricDecrypt({ + const serverGeneratedPassword = crypto.encryption().decryptWithRootEncryptionKey({ iv: systemGeneratedUserEncryptionKey.serverEncryptedPrivateKeyIV, tag: systemGeneratedUserEncryptionKey.serverEncryptedPrivateKeyTag, ciphertext: systemGeneratedUserEncryptionKey.serverEncryptedPrivateKey, @@ -436,7 +435,7 @@ export const authSignupServiceFactory = ({ }); const appCfg = getConfig(); - const hashedPassword = await bcrypt.hash(password, appCfg.BCRYPT_SALT_ROUND); + const hashedPassword = await crypto.hashing().createHash(password, appCfg.SALT_ROUNDS); const privateKey = await getUserPrivateKey(password, { salt, protectedKey, @@ -447,7 +446,7 @@ export const authSignupServiceFactory = ({ tag: encryptedPrivateKeyTag, encryptionVersion: 2 }); - const { tag, encoding, ciphertext, iv } = infisicalSymmetricEncypt(privateKey); + const { tag, encoding, ciphertext, iv } = crypto.encryption().encryptWithRootEncryptionKey(privateKey); const updateduser = await authDAL.transaction(async (tx) => { const us = await userDAL.updateById(user.id, { firstName, lastName, isAccepted: true }, tx); if (!us) throw new Error("User not found"); @@ -464,7 +463,7 @@ export const authSignupServiceFactory = ({ systemGeneratedUserEncryptionKey.serverEncryptedPrivateKeyEncoding ) { // get server generated password - const serverGeneratedPassword = infisicalSymmetricDecrypt({ + const serverGeneratedPassword = crypto.encryption().decryptWithRootEncryptionKey({ iv: systemGeneratedUserEncryptionKey.serverEncryptedPrivateKeyIV, tag: systemGeneratedUserEncryptionKey.serverEncryptedPrivateKeyTag, ciphertext: systemGeneratedUserEncryptionKey.serverEncryptedPrivateKey, diff --git a/backend/src/services/certificate-authority/acme/acme-certificate-authority-fns.ts b/backend/src/services/certificate-authority/acme/acme-certificate-authority-fns.ts index 8e0372953d..75f95c38af 100644 --- a/backend/src/services/certificate-authority/acme/acme-certificate-authority-fns.ts +++ b/backend/src/services/certificate-authority/acme/acme-certificate-authority-fns.ts @@ -1,9 +1,9 @@ import { ChangeResourceRecordSetsCommand, Route53Client } from "@aws-sdk/client-route-53"; import * as x509 from "@peculiar/x509"; import acme from "acme-client"; -import { KeyObject } from "crypto"; import { TableName } from "@app/db/schemas"; +import { crypto } from "@app/lib/crypto/cryptography"; import { BadRequestError, NotFoundError } from "@app/lib/errors"; import { OrgServiceActor } from "@app/lib/types"; import { blockLocalAndPrivateIpAddresses } from "@app/lib/validator"; @@ -404,8 +404,9 @@ export const AcmeCertificateAuthorityFns = ({ }); const alg = keyAlgorithmToAlgCfg(CertKeyAlgorithm.RSA_2048); - const leafKeys = await crypto.subtle.generateKey(alg, true, ["sign", "verify"]); - const skLeafObj = KeyObject.from(leafKeys.privateKey); + + const leafKeys = await crypto.rawCrypto.subtle.generateKey(alg, true, ["sign", "verify"]); + const skLeafObj = crypto.rawCrypto.KeyObject.from(leafKeys.privateKey); const skLeaf = skLeafObj.export({ format: "pem", type: "pkcs8" }) as string; const [, certificateCsr] = await acme.crypto.createCsr( diff --git a/backend/src/services/certificate-authority/certificate-authority-fns.ts b/backend/src/services/certificate-authority/certificate-authority-fns.ts index 02be765652..456d81d93a 100644 --- a/backend/src/services/certificate-authority/certificate-authority-fns.ts +++ b/backend/src/services/certificate-authority/certificate-authority-fns.ts @@ -1,6 +1,6 @@ import * as x509 from "@peculiar/x509"; -import crypto from "crypto"; +import { crypto } from "@app/lib/crypto/cryptography"; import { NotFoundError } from "@app/lib/errors"; import { getProjectKmsCertificateKeyId } from "@app/services/project/project-fns"; @@ -133,8 +133,8 @@ export const getCaCredentials = async ({ }); const alg = keyAlgorithmToAlgCfg(ca.internalCa.keyAlgorithm as CertKeyAlgorithm); - const skObj = crypto.createPrivateKey({ key: decryptedPrivateKey, format: "der", type: "pkcs8" }); - const caPrivateKey = await crypto.subtle.importKey( + const skObj = crypto.rawCrypto.createPrivateKey({ key: decryptedPrivateKey, format: "der", type: "pkcs8" }); + const caPrivateKey = await crypto.rawCrypto.subtle.importKey( "pkcs8", skObj.export({ format: "der", type: "pkcs8" }), alg, @@ -142,10 +142,14 @@ export const getCaCredentials = async ({ ["sign"] ); - const pkObj = crypto.createPublicKey(skObj); - const caPublicKey = await crypto.subtle.importKey("spki", pkObj.export({ format: "der", type: "spki" }), alg, true, [ - "verify" - ]); + const pkObj = crypto.rawCrypto.createPublicKey(skObj); + const caPublicKey = await crypto.rawCrypto.subtle.importKey( + "spki", + pkObj.export({ format: "der", type: "spki" }), + alg, + true, + ["verify"] + ); return { caSecret, @@ -277,10 +281,14 @@ export const rebuildCaCrl = async ({ cipherTextBlob: caSecret.encryptedPrivateKey }); - const skObj = crypto.createPrivateKey({ key: privateKey, format: "der", type: "pkcs8" }); - const sk = await crypto.subtle.importKey("pkcs8", skObj.export({ format: "der", type: "pkcs8" }), alg, true, [ - "sign" - ]); + const skObj = crypto.rawCrypto.createPrivateKey({ key: privateKey, format: "der", type: "pkcs8" }); + const sk = await crypto.rawCrypto.subtle.importKey( + "pkcs8", + skObj.export({ format: "der", type: "pkcs8" }), + alg, + true, + ["sign"] + ); const revokedCerts = await certificateDAL.find({ caId: ca.id, diff --git a/backend/src/services/certificate-authority/certificate-authority-queue.ts b/backend/src/services/certificate-authority/certificate-authority-queue.ts index 74970bf0c0..32471a0e31 100644 --- a/backend/src/services/certificate-authority/certificate-authority-queue.ts +++ b/backend/src/services/certificate-authority/certificate-authority-queue.ts @@ -1,8 +1,8 @@ import * as x509 from "@peculiar/x509"; -import crypto from "crypto"; import { KeyStorePrefixes, TKeyStoreFactory } from "@app/keystore/keystore"; import { getConfig } from "@app/lib/config/env"; +import { crypto } from "@app/lib/crypto/cryptography"; import { daysToMillisecond, secondsToMillis } from "@app/lib/dates"; import { BadRequestError, NotFoundError } from "@app/lib/errors"; import { logger } from "@app/lib/logger"; @@ -198,10 +198,14 @@ export const certificateAuthorityQueueFactory = ({ cipherTextBlob: caSecret.encryptedPrivateKey }); - const skObj = crypto.createPrivateKey({ key: privateKey, format: "der", type: "pkcs8" }); - const sk = await crypto.subtle.importKey("pkcs8", skObj.export({ format: "der", type: "pkcs8" }), alg, true, [ - "sign" - ]); + const skObj = crypto.rawCrypto.createPrivateKey({ key: privateKey, format: "der", type: "pkcs8" }); + const sk = await crypto.rawCrypto.subtle.importKey( + "pkcs8", + skObj.export({ format: "der", type: "pkcs8" }), + alg, + true, + ["sign"] + ); const revokedCerts = await certificateDAL.find({ caId: ca.id, diff --git a/backend/src/services/certificate-authority/internal/internal-certificate-authority-fns.ts b/backend/src/services/certificate-authority/internal/internal-certificate-authority-fns.ts index def2e2bedd..13084c5a87 100644 --- a/backend/src/services/certificate-authority/internal/internal-certificate-authority-fns.ts +++ b/backend/src/services/certificate-authority/internal/internal-certificate-authority-fns.ts @@ -1,12 +1,12 @@ /* eslint-disable no-bitwise */ import * as x509 from "@peculiar/x509"; -import { KeyObject } from "crypto"; import RE2 from "re2"; import { z } from "zod"; import { TCertificateTemplates, TPkiSubscribers } from "@app/db/schemas"; import { TCertificateAuthorityCrlDALFactory } from "@app/ee/services/certificate-authority-crl/certificate-authority-crl-dal"; import { getConfig } from "@app/lib/config/env"; +import { crypto } from "@app/lib/crypto/cryptography"; import { BadRequestError } from "@app/lib/errors"; import { ms } from "@app/lib/ms"; import { isFQDN } from "@app/lib/validator/validate-url"; @@ -99,7 +99,7 @@ export const InternalCertificateAuthorityFns = ({ } const alg = keyAlgorithmToAlgCfg(ca.internalCa.keyAlgorithm as CertKeyAlgorithm); - const leafKeys = await crypto.subtle.generateKey(alg, true, ["sign", "verify"]); + const leafKeys = await crypto.rawCrypto.subtle.generateKey(alg, true, ["sign", "verify"]); const csrObj = await x509.Pkcs10CertificateRequestGenerator.create({ name: `CN=${subscriber.commonName}`, @@ -184,7 +184,7 @@ export const InternalCertificateAuthorityFns = ({ extensions }); - const skLeafObj = KeyObject.from(leafKeys.privateKey); + const skLeafObj = crypto.rawCrypto.KeyObject.from(leafKeys.privateKey); const skLeaf = skLeafObj.export({ format: "pem", type: "pkcs8" }) as string; const kmsEncryptor = await kmsService.encryptWithKmsKey({ @@ -331,7 +331,7 @@ export const InternalCertificateAuthorityFns = ({ }); const alg = keyAlgorithmToAlgCfg(ca.internalCa.keyAlgorithm as CertKeyAlgorithm); - const leafKeys = await crypto.subtle.generateKey(alg, true, ["sign", "verify"]); + const leafKeys = await crypto.rawCrypto.subtle.generateKey(alg, true, ["sign", "verify"]); const csrObj = await x509.Pkcs10CertificateRequestGenerator.create({ name: `CN=${commonName}`, @@ -450,7 +450,7 @@ export const InternalCertificateAuthorityFns = ({ extensions }); - const skLeafObj = KeyObject.from(leafKeys.privateKey); + const skLeafObj = crypto.rawCrypto.KeyObject.from(leafKeys.privateKey); const skLeaf = skLeafObj.export({ format: "pem", type: "pkcs8" }) as string; const kmsEncryptor = await kmsService.encryptWithKmsKey({ diff --git a/backend/src/services/certificate-authority/internal/internal-certificate-authority-service.ts b/backend/src/services/certificate-authority/internal/internal-certificate-authority-service.ts index 98d9491f7e..cbc32c55a5 100644 --- a/backend/src/services/certificate-authority/internal/internal-certificate-authority-service.ts +++ b/backend/src/services/certificate-authority/internal/internal-certificate-authority-service.ts @@ -2,7 +2,6 @@ import { ForbiddenError, subject } from "@casl/ability"; import * as x509 from "@peculiar/x509"; import slugify from "@sindresorhus/slugify"; -import crypto, { KeyObject } from "crypto"; import { z } from "zod"; import { @@ -21,6 +20,7 @@ import { } from "@app/ee/services/permission/project-permission"; import { extractX509CertFromChain } from "@app/lib/certificates/extract-certificate"; import { getConfig } from "@app/lib/config/env"; +import { crypto } from "@app/lib/crypto/cryptography"; import { BadRequestError, NotFoundError } from "@app/lib/errors"; import { ms } from "@app/lib/ms"; import { alphaNumericNanoId } from "@app/lib/nanoid"; @@ -189,7 +189,7 @@ export const internalCertificateAuthorityServiceFactory = ({ }); const alg = keyAlgorithmToAlgCfg(keyAlgorithm); - const keys = await crypto.subtle.generateKey(alg, true, ["sign", "verify"]); + const keys = await crypto.rawCrypto.subtle.generateKey(alg, true, ["sign", "verify"]); const newCa = await certificateAuthorityDAL.transaction(async (tx) => { const notBeforeDate = notBefore ? new Date(notBefore) : new Date(); @@ -243,8 +243,8 @@ export const internalCertificateAuthorityServiceFactory = ({ kmsId: certificateManagerKmsId }); - // // https://nodejs.org/api/crypto.html#static-method-keyobjectfromkey - const skObj = KeyObject.from(keys.privateKey); + // https://nodejs.org/api/crypto.html#static-method-keyobjectfromkey + const skObj = crypto.rawCrypto.KeyObject.from(keys.privateKey); const { cipherTextBlob: encryptedPrivateKey } = await kmsEncryptor({ plainText: skObj.export({ @@ -1129,7 +1129,7 @@ export const internalCertificateAuthorityServiceFactory = ({ kmsService }); - const isCaAndCertPublicKeySame = Buffer.from(await crypto.subtle.exportKey("spki", caPublicKey)).equals( + const isCaAndCertPublicKeySame = Buffer.from(await crypto.rawCrypto.subtle.exportKey("spki", caPublicKey)).equals( Buffer.from(certObj.publicKey.rawData) ); @@ -1293,7 +1293,7 @@ export const internalCertificateAuthorityServiceFactory = ({ } const alg = keyAlgorithmToAlgCfg(ca.internalCa.keyAlgorithm as CertKeyAlgorithm); - const leafKeys = await crypto.subtle.generateKey(alg, true, ["sign", "verify"]); + const leafKeys = await crypto.rawCrypto.subtle.generateKey(alg, true, ["sign", "verify"]); const csrObj = await x509.Pkcs10CertificateRequestGenerator.create({ name: `CN=${commonName}`, @@ -1440,7 +1440,7 @@ export const internalCertificateAuthorityServiceFactory = ({ extensions }); - const skLeafObj = KeyObject.from(leafKeys.privateKey); + const skLeafObj = crypto.rawCrypto.KeyObject.from(leafKeys.privateKey); const skLeaf = skLeafObj.export({ format: "pem", type: "pkcs8" }) as string; const kmsEncryptor = await kmsService.encryptWithKmsKey({ diff --git a/backend/src/services/certificate-template/certificate-template-service.ts b/backend/src/services/certificate-template/certificate-template-service.ts index 4fa4e82831..20c061bf70 100644 --- a/backend/src/services/certificate-template/certificate-template-service.ts +++ b/backend/src/services/certificate-template/certificate-template-service.ts @@ -1,6 +1,5 @@ import { ForbiddenError, subject } from "@casl/ability"; import * as x509 from "@peculiar/x509"; -import bcrypt from "bcrypt"; import { ActionProjectType, TCertificateTemplateEstConfigsUpdate } from "@app/db/schemas"; import { TLicenseServiceFactory } from "@app/ee/services/license/license-service"; @@ -11,6 +10,7 @@ import { } from "@app/ee/services/permission/project-permission"; import { extractX509CertFromChain } from "@app/lib/certificates/extract-certificate"; import { getConfig } from "@app/lib/config/env"; +import { crypto } from "@app/lib/crypto/cryptography"; import { BadRequestError, NotFoundError } from "@app/lib/errors"; import { isCertChainValid } from "../certificate/certificate-fns"; @@ -313,7 +313,7 @@ export const certificateTemplateServiceFactory = ({ encryptedCaChain = cipherTextBlob; } - const hashedPassphrase = await bcrypt.hash(passphrase, appCfg.SALT_ROUNDS); + const hashedPassphrase = await crypto.hashing().createHash(passphrase, appCfg.SALT_ROUNDS); const estConfig = await certificateTemplateEstConfigDAL.create({ certificateTemplateId, hashedPassphrase, @@ -410,7 +410,7 @@ export const certificateTemplateServiceFactory = ({ } if (passphrase) { - const hashedPassphrase = await bcrypt.hash(passphrase, appCfg.SALT_ROUNDS); + const hashedPassphrase = await crypto.hashing().createHash(passphrase, appCfg.SALT_ROUNDS); updatedData.hashedPassphrase = hashedPassphrase; } diff --git a/backend/src/services/certificate/certificate-fns.ts b/backend/src/services/certificate/certificate-fns.ts index ffdaec3b4b..652974584d 100644 --- a/backend/src/services/certificate/certificate-fns.ts +++ b/backend/src/services/certificate/certificate-fns.ts @@ -1,8 +1,7 @@ -import crypto from "node:crypto"; - import * as x509 from "@peculiar/x509"; import RE2 from "re2"; +import { crypto } from "@app/lib/crypto/cryptography"; import { BadRequestError, NotFoundError } from "@app/lib/errors"; import { getProjectKmsCertificateKeyId } from "../project/project-fns"; @@ -87,10 +86,10 @@ export const getCertificateCredentials = async ({ }); try { - const skObj = crypto.createPrivateKey({ key: decryptedPrivateKey, format: "pem", type: "pkcs8" }); + const skObj = crypto.rawCrypto.createPrivateKey({ key: decryptedPrivateKey, format: "pem", type: "pkcs8" }); const certPrivateKey = skObj.export({ format: "pem", type: "pkcs8" }).toString(); - const pkObj = crypto.createPublicKey(skObj); + const pkObj = crypto.rawCrypto.createPublicKey(skObj); const certPublicKey = pkObj.export({ format: "pem", type: "spki" }).toString(); return { diff --git a/backend/src/services/certificate/certificate-service.ts b/backend/src/services/certificate/certificate-service.ts index 5558ccd651..36842f3240 100644 --- a/backend/src/services/certificate/certificate-service.ts +++ b/backend/src/services/certificate/certificate-service.ts @@ -1,6 +1,5 @@ import { ForbiddenError } from "@casl/ability"; import * as x509 from "@peculiar/x509"; -import { createPrivateKey, createPublicKey, sign, verify } from "crypto"; import { ActionProjectType, ProjectType } from "@app/db/schemas"; import { TCertificateAuthorityCrlDALFactory } from "@app/ee/services/certificate-authority-crl/certificate-authority-crl-dal"; @@ -9,6 +8,7 @@ import { ProjectPermissionCertificateActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission"; +import { crypto } from "@app/lib/crypto/cryptography"; import { BadRequestError, NotFoundError } from "@app/lib/errors"; import { TCertificateBodyDALFactory } from "@app/services/certificate/certificate-body-dal"; import { TCertificateDALFactory } from "@app/services/certificate/certificate-dal"; @@ -391,16 +391,16 @@ export const certificateServiceFactory = ({ // Verify private key matches the certificate let privateKey; try { - privateKey = createPrivateKey(privateKeyPem); + privateKey = crypto.rawCrypto.createPrivateKey(privateKeyPem); } catch (err) { throw new BadRequestError({ message: "Invalid private key format" }); } try { const message = Buffer.from(Buffer.alloc(32)); - const publicKey = createPublicKey(certificatePem); - const signature = sign(null, message, privateKey); - const isValid = verify(null, message, publicKey, signature); + const publicKey = crypto.rawCrypto.createPublicKey(certificatePem); + const signature = crypto.rawCrypto.sign(null, message, privateKey); + const isValid = crypto.rawCrypto.verify(null, message, publicKey, signature); if (!isValid) { throw new BadRequestError({ message: "Private key does not match certificate" }); diff --git a/backend/src/services/external-migration/external-migration-fns.ts b/backend/src/services/external-migration/external-migration-fns.ts index 018d7bd43e..08ee02a2bf 100644 --- a/backend/src/services/external-migration/external-migration-fns.ts +++ b/backend/src/services/external-migration/external-migration-fns.ts @@ -1,10 +1,10 @@ import slugify from "@sindresorhus/slugify"; -import { randomUUID } from "crypto"; import sjcl from "sjcl"; import tweetnacl from "tweetnacl"; import tweetnaclUtil from "tweetnacl-util"; import { SecretType, TSecretFolders } from "@app/db/schemas"; +import { crypto } from "@app/lib/crypto/cryptography"; import { BadRequestError, NotFoundError } from "@app/lib/errors"; import { chunkArray } from "@app/lib/fn"; import { logger } from "@app/lib/logger"; @@ -228,7 +228,7 @@ export const parseEnvKeyDataFn = async (decryptedJson: string): Promise { + if (crypto.isFipsModeEnabled()) { + throw new BadRequestError({ message: "EnvKey migration is not supported when running in FIPS mode." }); + } + const { membership } = await permissionService.getOrgPermission( actor, actorId, @@ -52,7 +56,7 @@ export const externalMigrationServiceFactory = ({ actorAuthMethod }); - const encrypted = infisicalSymmetricEncypt(stringifiedJson); + const encrypted = crypto.encryption().encryptWithRootEncryptionKey(stringifiedJson); await externalMigrationQueue.startImport({ actorEmail: user.email!, diff --git a/backend/src/services/group-project/group-project-service.ts b/backend/src/services/group-project/group-project-service.ts index 47d8950ccc..29a7787be4 100644 --- a/backend/src/services/group-project/group-project-service.ts +++ b/backend/src/services/group-project/group-project-service.ts @@ -8,8 +8,7 @@ import { } from "@app/ee/services/permission/permission-fns"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types"; import { ProjectPermissionGroupActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission"; -import { decryptAsymmetric, encryptAsymmetric } from "@app/lib/crypto"; -import { infisicalSymmetricDecrypt } from "@app/lib/crypto/encryption"; +import { crypto } from "@app/lib/crypto/cryptography"; import { BadRequestError, NotFoundError, PermissionBoundaryError } from "@app/lib/errors"; import { groupBy } from "@app/lib/fn"; import { ms } from "@app/lib/ms"; @@ -214,14 +213,14 @@ export const groupProjectServiceFactory = ({ }); } - const botPrivateKey = infisicalSymmetricDecrypt({ + const botPrivateKey = crypto.encryption().decryptWithRootEncryptionKey({ keyEncoding: bot.keyEncoding as SecretKeyEncoding, iv: bot.iv, tag: bot.tag, ciphertext: bot.encryptedPrivateKey }); - const plaintextProjectKey = decryptAsymmetric({ + const plaintextProjectKey = crypto.encryption().asymmetric().decrypt({ ciphertext: ghostUserLatestKey.encryptedKey, nonce: ghostUserLatestKey.nonce, publicKey: ghostUserLatestKey.sender.publicKey, @@ -229,7 +228,10 @@ export const groupProjectServiceFactory = ({ }); const projectKeyData = groupMembers.map(({ user: { publicKey, id } }) => { - const { ciphertext: encryptedKey, nonce } = encryptAsymmetric(plaintextProjectKey, publicKey, botPrivateKey); + const { ciphertext: encryptedKey, nonce } = crypto + .encryption() + .asymmetric() + .encrypt(plaintextProjectKey, publicKey, botPrivateKey); return { encryptedKey, diff --git a/backend/src/services/identity-tls-cert-auth/identity-tls-cert-auth-service.ts b/backend/src/services/identity-tls-cert-auth/identity-tls-cert-auth-service.ts index 11dd312ad1..e2c2e4cb8a 100644 --- a/backend/src/services/identity-tls-cert-auth/identity-tls-cert-auth-service.ts +++ b/backend/src/services/identity-tls-cert-auth/identity-tls-cert-auth-service.ts @@ -1,5 +1,3 @@ -import crypto from "node:crypto"; - import { ForbiddenError } from "@casl/ability"; import jwt from "jsonwebtoken"; @@ -13,6 +11,7 @@ import { import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types"; import { extractX509CertFromChain } from "@app/lib/certificates/extract-certificate"; import { getConfig } from "@app/lib/config/env"; +import { crypto } from "@app/lib/crypto/cryptography"; import { BadRequestError, NotFoundError, PermissionBoundaryError, UnauthorizedError } from "@app/lib/errors"; import { extractIPDetails, isValidIpOrCidr } from "@app/lib/ip"; @@ -87,8 +86,8 @@ export const identityTlsCertAuthServiceFactory = ({ throw new BadRequestError({ message: "Missing client certificate" }); } - const clientCertificateX509 = new crypto.X509Certificate(leafCertificate); - const caCertificateX509 = new crypto.X509Certificate(caCertificate); + const clientCertificateX509 = new crypto.rawCrypto.X509Certificate(leafCertificate); + const caCertificateX509 = new crypto.rawCrypto.X509Certificate(caCertificate); const isValidCertificate = clientCertificateX509.verify(caCertificateX509.publicKey); if (!isValidCertificate) diff --git a/backend/src/services/identity-ua/identity-ua-service.ts b/backend/src/services/identity-ua/identity-ua-service.ts index eaae0150b5..c159f73649 100644 --- a/backend/src/services/identity-ua/identity-ua-service.ts +++ b/backend/src/services/identity-ua/identity-ua-service.ts @@ -1,7 +1,4 @@ -import crypto from "node:crypto"; - import { ForbiddenError } from "@casl/ability"; -import bcrypt from "bcrypt"; import jwt from "jsonwebtoken"; import { IdentityAuthMethod } from "@app/db/schemas"; @@ -13,6 +10,7 @@ import { } from "@app/ee/services/permission/permission-fns"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types"; import { getConfig } from "@app/lib/config/env"; +import { crypto } from "@app/lib/crypto/cryptography"; import { BadRequestError, NotFoundError, PermissionBoundaryError, UnauthorizedError } from "@app/lib/errors"; import { checkIPAgainstBlocklist, extractIPDetails, isValidIpOrCidr, TIp } from "@app/lib/ip"; @@ -76,7 +74,8 @@ export const identityUaServiceFactory = ({ let validClientSecretInfo: (typeof clientSecrtInfo)[0] | null = null; for await (const info of clientSecrtInfo) { - const isMatch = await bcrypt.compare(clientSecret, info.clientSecretHash); + const isMatch = await crypto.hashing().compareHash(clientSecret, info.clientSecretHash); + if (isMatch) { validClientSecretInfo = info; break; @@ -250,7 +249,7 @@ export const identityUaServiceFactory = ({ const doc = await identityUaDAL.create( { identityId: identityMembershipOrg.identityId, - clientId: crypto.randomUUID(), + clientId: crypto.rawCrypto.randomUUID(), clientSecretTrustedIps: JSON.stringify(reformattedClientSecretTrustedIps), accessTokenMaxTTL, accessTokenTTL, @@ -494,7 +493,7 @@ export const identityUaServiceFactory = ({ const appCfg = getConfig(); const clientSecret = crypto.randomBytes(32).toString("hex"); - const clientSecretHash = await bcrypt.hash(clientSecret, appCfg.SALT_ROUNDS); + const clientSecretHash = await crypto.hashing().createHash(clientSecret, appCfg.SALT_ROUNDS); const identityUaAuth = await identityUaDAL.findOne({ identityId: identityMembershipOrg.identityId }); if (!identityUaAuth) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` }); diff --git a/backend/src/services/integration-auth/integration-auth-service.ts b/backend/src/services/integration-auth/integration-auth-service.ts index 2517c040cd..ad8d435267 100644 --- a/backend/src/services/integration-auth/integration-auth-service.ts +++ b/backend/src/services/integration-auth/integration-auth-service.ts @@ -15,7 +15,7 @@ import { TPermissionServiceFactory } from "@app/ee/services/permission/permissio import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission"; import { getConfig } from "@app/lib/config/env"; import { request } from "@app/lib/config/request"; -import { decryptSymmetric128BitHexKeyUTF8, encryptSymmetric128BitHexKeyUTF8 } from "@app/lib/crypto"; +import { crypto, SymmetricKeySize } from "@app/lib/crypto/cryptography"; import { BadRequestError, InternalServerError, NotFoundError } from "@app/lib/errors"; import { groupBy } from "@app/lib/fn"; import { logger } from "@app/lib/logger"; @@ -228,13 +228,22 @@ export const integrationAuthServiceFactory = ({ } else { if (!botKey) throw new NotFoundError({ message: `Project bot key for project with ID '${projectId}' not found` }); if (tokenExchange.refreshToken) { - const refreshEncToken = encryptSymmetric128BitHexKeyUTF8(tokenExchange.refreshToken, botKey); + const refreshEncToken = crypto.encryption().encryptSymmetric({ + plaintext: tokenExchange.refreshToken, + key: botKey, + keySize: SymmetricKeySize.Bits128 + }); + updateDoc.refreshIV = refreshEncToken.iv; updateDoc.refreshTag = refreshEncToken.tag; updateDoc.refreshCiphertext = refreshEncToken.ciphertext; } if (tokenExchange.accessToken) { - const accessEncToken = encryptSymmetric128BitHexKeyUTF8(tokenExchange.accessToken, botKey); + const accessEncToken = crypto.encryption().encryptSymmetric({ + plaintext: tokenExchange.accessToken, + key: botKey, + keySize: SymmetricKeySize.Bits128 + }); updateDoc.accessIV = accessEncToken.iv; updateDoc.accessTag = accessEncToken.tag; updateDoc.accessCiphertext = accessEncToken.ciphertext; @@ -357,11 +366,19 @@ export const integrationAuthServiceFactory = ({ url, updateDoc.metadata as Record ); - const refreshEncToken = encryptSymmetric128BitHexKeyUTF8(tokenDetails.refreshToken, botKey); + const refreshEncToken = crypto.encryption().encryptSymmetric({ + plaintext: tokenDetails.refreshToken, + key: botKey, + keySize: SymmetricKeySize.Bits128 + }); updateDoc.refreshIV = refreshEncToken.iv; updateDoc.refreshTag = refreshEncToken.tag; updateDoc.refreshCiphertext = refreshEncToken.ciphertext; - const accessEncToken = encryptSymmetric128BitHexKeyUTF8(tokenDetails.accessToken, botKey); + const accessEncToken = crypto.encryption().encryptSymmetric({ + plaintext: tokenDetails.accessToken, + key: botKey, + keySize: SymmetricKeySize.Bits128 + }); updateDoc.accessIV = accessEncToken.iv; updateDoc.accessTag = accessEncToken.tag; updateDoc.accessCiphertext = accessEncToken.ciphertext; @@ -371,19 +388,31 @@ export const integrationAuthServiceFactory = ({ if (!refreshToken && (accessId || accessToken || awsAssumeIamRoleArn)) { if (accessToken) { - const accessEncToken = encryptSymmetric128BitHexKeyUTF8(accessToken, botKey); + const accessEncToken = crypto.encryption().encryptSymmetric({ + plaintext: accessToken, + key: botKey, + keySize: SymmetricKeySize.Bits128 + }); updateDoc.accessIV = accessEncToken.iv; updateDoc.accessTag = accessEncToken.tag; updateDoc.accessCiphertext = accessEncToken.ciphertext; } if (accessId) { - const accessEncToken = encryptSymmetric128BitHexKeyUTF8(accessId, botKey); + const accessEncToken = crypto.encryption().encryptSymmetric({ + plaintext: accessId, + key: botKey, + keySize: SymmetricKeySize.Bits128 + }); updateDoc.accessIdIV = accessEncToken.iv; updateDoc.accessIdTag = accessEncToken.tag; updateDoc.accessIdCiphertext = accessEncToken.ciphertext; } if (awsAssumeIamRoleArn) { - const awsAssumeIamRoleArnEnc = encryptSymmetric128BitHexKeyUTF8(awsAssumeIamRoleArn, botKey); + const awsAssumeIamRoleArnEnc = crypto.encryption().encryptSymmetric({ + plaintext: awsAssumeIamRoleArn, + key: botKey, + keySize: SymmetricKeySize.Bits128 + }); updateDoc.awsAssumeIamRoleArnCipherText = awsAssumeIamRoleArnEnc.ciphertext; updateDoc.awsAssumeIamRoleArnIV = awsAssumeIamRoleArnEnc.iv; updateDoc.awsAssumeIamRoleArnTag = awsAssumeIamRoleArnEnc.tag; @@ -499,11 +528,21 @@ export const integrationAuthServiceFactory = ({ url, updateDoc.metadata as Record ); - const refreshEncToken = encryptSymmetric128BitHexKeyUTF8(tokenDetails.refreshToken, botKey); + const refreshEncToken = crypto.encryption().encryptSymmetric({ + plaintext: tokenDetails.refreshToken, + key: botKey, + keySize: SymmetricKeySize.Bits128 + }); updateDoc.refreshIV = refreshEncToken.iv; updateDoc.refreshTag = refreshEncToken.tag; updateDoc.refreshCiphertext = refreshEncToken.ciphertext; - const accessEncToken = encryptSymmetric128BitHexKeyUTF8(tokenDetails.accessToken, botKey); + + const accessEncToken = crypto.encryption().encryptSymmetric({ + plaintext: tokenDetails.accessToken, + key: botKey, + keySize: SymmetricKeySize.Bits128 + }); + updateDoc.accessIV = accessEncToken.iv; updateDoc.accessTag = accessEncToken.tag; updateDoc.accessCiphertext = accessEncToken.ciphertext; @@ -513,19 +552,32 @@ export const integrationAuthServiceFactory = ({ if (!refreshToken && (accessId || accessToken || awsAssumeIamRoleArn)) { if (accessToken) { - const accessEncToken = encryptSymmetric128BitHexKeyUTF8(accessToken, botKey); + const accessEncToken = crypto.encryption().encryptSymmetric({ + plaintext: accessToken, + key: botKey, + keySize: SymmetricKeySize.Bits128 + }); updateDoc.accessIV = accessEncToken.iv; updateDoc.accessTag = accessEncToken.tag; updateDoc.accessCiphertext = accessEncToken.ciphertext; } if (accessId) { - const accessEncToken = encryptSymmetric128BitHexKeyUTF8(accessId, botKey); + const accessEncToken = crypto.encryption().encryptSymmetric({ + plaintext: accessId, + key: botKey, + keySize: SymmetricKeySize.Bits128 + }); updateDoc.accessIdIV = accessEncToken.iv; updateDoc.accessIdTag = accessEncToken.tag; updateDoc.accessIdCiphertext = accessEncToken.ciphertext; } if (awsAssumeIamRoleArn) { - const awsAssumeIamRoleArnEnc = encryptSymmetric128BitHexKeyUTF8(awsAssumeIamRoleArn, botKey); + const awsAssumeIamRoleArnEnc = crypto.encryption().encryptSymmetric({ + plaintext: awsAssumeIamRoleArn, + key: botKey, + keySize: SymmetricKeySize.Bits128 + }); + updateDoc.awsAssumeIamRoleArnCipherText = awsAssumeIamRoleArnEnc.ciphertext; updateDoc.awsAssumeIamRoleArnIV = awsAssumeIamRoleArnEnc.iv; updateDoc.awsAssumeIamRoleArnTag = awsAssumeIamRoleArnEnc.tag; @@ -608,20 +660,22 @@ export const integrationAuthServiceFactory = ({ } else { if (!botKey) throw new NotFoundError({ message: "Project bot key not found" }); if (integrationAuth.accessTag && integrationAuth.accessIV && integrationAuth.accessCiphertext) { - accessToken = decryptSymmetric128BitHexKeyUTF8({ + accessToken = crypto.encryption().decryptSymmetric({ ciphertext: integrationAuth.accessCiphertext, iv: integrationAuth.accessIV, tag: integrationAuth.accessTag, - key: botKey + key: botKey, + keySize: SymmetricKeySize.Bits128 }); } if (integrationAuth.refreshCiphertext && integrationAuth.refreshIV && integrationAuth.refreshTag) { - const refreshToken = decryptSymmetric128BitHexKeyUTF8({ + const refreshToken = crypto.encryption().decryptSymmetric({ key: botKey, ciphertext: integrationAuth.refreshCiphertext, iv: integrationAuth.refreshIV, - tag: integrationAuth.refreshTag + tag: integrationAuth.refreshTag, + keySize: SymmetricKeySize.Bits128 }); if (integrationAuth.accessExpiresAt && integrationAuth.accessExpiresAt < new Date()) { @@ -632,8 +686,18 @@ export const integrationAuthServiceFactory = ({ integrationAuth?.url, integrationAuth.metadata as Record ); - const refreshEncToken = encryptSymmetric128BitHexKeyUTF8(tokenDetails.refreshToken, botKey); - const accessEncToken = encryptSymmetric128BitHexKeyUTF8(tokenDetails.accessToken, botKey); + + const refreshEncToken = crypto.encryption().encryptSymmetric({ + plaintext: tokenDetails.refreshToken, + key: botKey, + keySize: SymmetricKeySize.Bits128 + }); + + const accessEncToken = crypto.encryption().encryptSymmetric({ + plaintext: tokenDetails.accessToken, + key: botKey, + keySize: SymmetricKeySize.Bits128 + }); accessToken = tokenDetails.accessToken; await integrationAuthDAL.updateById(integrationAuth.id, { refreshIV: refreshEncToken.iv, @@ -649,11 +713,12 @@ export const integrationAuthServiceFactory = ({ if (!accessToken) throw new BadRequestError({ message: "Missing access token" }); if (integrationAuth.accessIdTag && integrationAuth.accessIdIV && integrationAuth.accessIdCiphertext) { - accessId = decryptSymmetric128BitHexKeyUTF8({ + accessId = crypto.encryption().decryptSymmetric({ key: botKey, ciphertext: integrationAuth.accessIdCiphertext, iv: integrationAuth.accessIdIV, - tag: integrationAuth.accessIdTag + tag: integrationAuth.accessIdTag, + keySize: SymmetricKeySize.Bits128 }); } } diff --git a/backend/src/services/integration-auth/integration-delete-secret.ts b/backend/src/services/integration-auth/integration-delete-secret.ts index f77becb020..a4923e347b 100644 --- a/backend/src/services/integration-auth/integration-delete-secret.ts +++ b/backend/src/services/integration-auth/integration-delete-secret.ts @@ -5,7 +5,7 @@ import { Octokit } from "@octokit/rest"; import { TIntegrationAuths, TIntegrations } from "@app/db/schemas"; import { getConfig } from "@app/lib/config/env"; -import { decryptSymmetric128BitHexKeyUTF8 } from "@app/lib/crypto"; +import { crypto, SymmetricKeySize } from "@app/lib/crypto/cryptography"; import { BadRequestError, NotFoundError } from "@app/lib/errors"; import { logger } from "@app/lib/logger"; @@ -109,12 +109,14 @@ const getIntegrationSecretsV1 = async ( // process secrets in current folder const secrets = await secretDAL.findByFolderId(dto.folderId); + secrets.forEach((secret) => { - const secretKey = decryptSymmetric128BitHexKeyUTF8({ + const secretKey = crypto.encryption().decryptSymmetric({ ciphertext: secret.secretKeyCiphertext, iv: secret.secretKeyIV, tag: secret.secretKeyTag, - key: dto.key + key: dto.key, + keySize: SymmetricKeySize.Bits128 }); content[secretKey] = true; diff --git a/backend/src/services/integration-auth/integration-sync-secret.ts b/backend/src/services/integration-auth/integration-sync-secret.ts index 989a5a88ca..0723136937 100644 --- a/backend/src/services/integration-auth/integration-sync-secret.ts +++ b/backend/src/services/integration-auth/integration-sync-secret.ts @@ -23,7 +23,6 @@ import { createAppAuth } from "@octokit/auth-app"; import { Octokit } from "@octokit/rest"; import AWS, { AWSError } from "aws-sdk"; import { AxiosError } from "axios"; -import { randomUUID } from "crypto"; import https from "https"; import sodium from "libsodium-wrappers"; import isEqual from "lodash.isequal"; @@ -33,6 +32,7 @@ import { z } from "zod"; import { SecretType, TIntegrationAuths, TIntegrations } from "@app/db/schemas"; import { getConfig } from "@app/lib/config/env"; import { request } from "@app/lib/config/request"; +import { crypto } from "@app/lib/crypto/cryptography"; import { BadRequestError, InternalServerError } from "@app/lib/errors"; import { logger } from "@app/lib/logger"; import { TCreateManySecretsRawFn, TUpdateManySecretsRawFn } from "@app/services/secret/secret-types"; @@ -806,7 +806,7 @@ const syncSecretsAWSParameterStore = async ({ }); const command = new AssumeRoleCommand({ RoleArn: awsAssumeRoleArn, - RoleSessionName: `infisical-parameter-store-${randomUUID()}`, + RoleSessionName: `infisical-parameter-store-${crypto.rawCrypto.randomUUID()}`, DurationSeconds: 900, // 15mins ExternalId: projectId }); @@ -1126,7 +1126,7 @@ const syncSecretsAWSSecretManager = async ({ }); const command = new AssumeRoleCommand({ RoleArn: awsAssumeRoleArn, - RoleSessionName: `infisical-sm-${randomUUID()}`, + RoleSessionName: `infisical-sm-${crypto.rawCrypto.randomUUID()}`, DurationSeconds: 900, // 15mins ExternalId: projectId }); diff --git a/backend/src/services/kms/kms-service.ts b/backend/src/services/kms/kms-service.ts index 196c183566..850635f5dd 100644 --- a/backend/src/services/kms/kms-service.ts +++ b/backend/src/services/kms/kms-service.ts @@ -14,9 +14,8 @@ import { import { THsmServiceFactory } from "@app/ee/services/hsm/hsm-service"; import { KeyStorePrefixes, PgSqlLock, TKeyStoreFactory } from "@app/keystore/keystore"; import { TEnvConfig } from "@app/lib/config/env"; -import { randomSecureBytes } from "@app/lib/crypto"; import { symmetricCipherService, SymmetricKeyAlgorithm } from "@app/lib/crypto/cipher"; -import { generateHash } from "@app/lib/crypto/encryption"; +import { crypto } from "@app/lib/crypto/cryptography"; import { AsymmetricKeyAlgorithm, signingService } from "@app/lib/crypto/sign"; import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors"; import { logger } from "@app/lib/logger"; @@ -101,7 +100,7 @@ export const kmsServiceFactory = ({ let kmsKeyMaterial: Buffer | null = null; if (keyUsage === KmsKeyUsage.ENCRYPT_DECRYPT) { - kmsKeyMaterial = randomSecureBytes( + kmsKeyMaterial = crypto.randomBytes( getByteLengthForSymmetricEncryptionAlgorithm(encryptionAlgorithm as SymmetricKeyAlgorithm) ); } else if (keyUsage === KmsKeyUsage.SIGN_VERIFY) { @@ -618,7 +617,7 @@ export const kmsServiceFactory = ({ return; } - const dataKey = randomSecureBytes(); + const dataKey = crypto.randomBytes(32); const kmsEncryptor = await encryptWithKmsKey( { kmsId: kmsKeyId @@ -761,7 +760,7 @@ export const kmsServiceFactory = ({ return; } - const dataKey = randomSecureBytes(); + const dataKey = crypto.randomBytes(32); const kmsEncryptor = await encryptWithKmsKey( { kmsId: kmsKeyId @@ -994,7 +993,7 @@ export const kmsServiceFactory = ({ "base64" )}`; - const verificationHash = generateHash(secretManagerBackup); + const verificationHash = crypto.rawCrypto.createHash("sha256").update(secretManagerBackup).digest("hex"); secretManagerBackup = `${secretManagerBackup}.${verificationHash}`; return { @@ -1011,7 +1010,12 @@ export const kmsServiceFactory = ({ } const [, backupProjectId, , backupKmsKeyId, backupBase64EncryptedDataKey, backupHash] = backup.split("."); - const computedHash = generateHash(backup.substring(0, backup.lastIndexOf("."))); + + const computedHash = crypto.rawCrypto + .createHash("sha256") + .update(backup.substring(0, backup.lastIndexOf("."))) + .digest("hex"); + if (computedHash !== backupHash) { throw new BadRequestError({ message: "Invalid backup" @@ -1075,7 +1079,7 @@ export const kmsServiceFactory = ({ if (existingRootConfig) return existingRootConfig; logger.info("KMS: Generating new ROOT Key"); - const newRootKey = randomSecureBytes(32); + const newRootKey = crypto.randomBytes(32); const encryptedRootKey = await $encryptRootKey(newRootKey, RootKeyEncryptionStrategy.Software).catch((err) => { logger.error({ hsmEnabled: hsmService.isActive() }, "KMS: Failed to encrypt ROOT Key"); throw err; diff --git a/backend/src/services/org-admin/org-admin-service.ts b/backend/src/services/org-admin/org-admin-service.ts index cb161c7e58..81afeedea3 100644 --- a/backend/src/services/org-admin/org-admin-service.ts +++ b/backend/src/services/org-admin/org-admin-service.ts @@ -3,7 +3,7 @@ import { ForbiddenError } from "@casl/ability"; import { ProjectMembershipRole, ProjectVersion, SecretKeyEncoding } from "@app/db/schemas"; import { OrgPermissionAdminConsoleAction, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types"; -import { infisicalSymmetricDecrypt } from "@app/lib/crypto/encryption"; +import { crypto } from "@app/lib/crypto/cryptography"; import { BadRequestError, NotFoundError } from "@app/lib/errors"; import { TProjectDALFactory } from "../project/project-dal"; @@ -144,7 +144,7 @@ export const orgAdminServiceFactory = ({ }); } - const botPrivateKey = infisicalSymmetricDecrypt({ + const botPrivateKey = crypto.encryption().decryptWithRootEncryptionKey({ keyEncoding: bot.keyEncoding as SecretKeyEncoding, iv: bot.iv, tag: bot.tag, diff --git a/backend/src/services/org/org-service.ts b/backend/src/services/org/org-service.ts index 1215b7c003..a746128038 100644 --- a/backend/src/services/org/org-service.ts +++ b/backend/src/services/org/org-service.ts @@ -1,6 +1,5 @@ import { ForbiddenError } from "@casl/ability"; import slugify from "@sindresorhus/slugify"; -import crypto from "crypto"; import jwt from "jsonwebtoken"; import { Knex } from "knex"; @@ -34,8 +33,7 @@ import { ProjectPermissionMemberActions, ProjectPermissionSub } from "@app/ee/se import { TProjectUserAdditionalPrivilegeDALFactory } from "@app/ee/services/project-user-additional-privilege/project-user-additional-privilege-dal"; import { TSamlConfigDALFactory } from "@app/ee/services/saml-config/saml-config-dal"; import { getConfig } from "@app/lib/config/env"; -import { generateAsymmetricKeyPair } from "@app/lib/crypto"; -import { generateSymmetricKey, infisicalSymmetricDecrypt, infisicalSymmetricEncypt } from "@app/lib/crypto/encryption"; +import { crypto } from "@app/lib/crypto/cryptography"; import { generateUserSrpKeys } from "@app/lib/crypto/srp"; import { BadRequestError, @@ -251,6 +249,7 @@ export const orgServiceFactory = ({ const addGhostUser = async (orgId: string, tx?: Knex) => { const email = `sudo-${alphaNumericNanoId(16)}-${orgId}@infisical.com`; // We add a nanoid because the email is unique. And we have to create a new ghost user each time, so we can have access to the private key. + const password = crypto.randomBytes(128).toString("hex"); const user = await userDAL.create( @@ -492,22 +491,22 @@ export const orgServiceFactory = ({ orgName: string; userEmail?: string | null; }) => { - const { privateKey, publicKey } = generateAsymmetricKeyPair(); - const key = generateSymmetricKey(); + const { privateKey, publicKey } = crypto.encryption().asymmetric().generateKeyPair(); + const key = crypto.randomBytes(32).toString("base64"); const { ciphertext: encryptedPrivateKey, iv: privateKeyIV, tag: privateKeyTag, encoding: privateKeyKeyEncoding, algorithm: privateKeyAlgorithm - } = infisicalSymmetricEncypt(privateKey); + } = crypto.encryption().encryptWithRootEncryptionKey(privateKey); const { ciphertext: encryptedSymmetricKey, iv: symmetricKeyIV, tag: symmetricKeyTag, encoding: symmetricKeyKeyEncoding, algorithm: symmetricKeyAlgorithm - } = infisicalSymmetricEncypt(key); + } = crypto.encryption().encryptWithRootEncryptionKey(key); const customerId = await licenseService.generateOrgCustomerId(orgName, userEmail); const organization = await orgDAL.transaction(async (tx) => { @@ -825,6 +824,7 @@ export const orgServiceFactory = ({ const mailsForOrgInvitation: { email: string; userId: string; firstName: string; lastName: string }[] = []; const mailsForProjectInvitation: { email: string[]; projectName: string }[] = []; const newProjectMemberships: TProjectMemberships[] = []; + await orgDAL.transaction(async (tx) => { const users: Pick[] = []; @@ -864,7 +864,9 @@ export const orgServiceFactory = ({ // Then when user sign in (as login is not possible as isAccepted is false) we rencrypt the private key with the user password if (!inviteeUser || (inviteeUser && !inviteeUser?.isAccepted && !existingEncrytionKey)) { const serverGeneratedPassword = crypto.randomBytes(32).toString("hex"); - const { tag, encoding, ciphertext, iv } = infisicalSymmetricEncypt(serverGeneratedPassword); + const { tag, encoding, ciphertext, iv } = crypto + .encryption() + .encryptWithRootEncryptionKey(serverGeneratedPassword); const encKeys = await generateUserSrpKeys(inviteeEmail, serverGeneratedPassword); await userDAL.createUserEncryption( { @@ -1091,9 +1093,9 @@ export const orgServiceFactory = ({ tx ); - const { iv, tag, ciphertext, encoding, algorithm } = infisicalSymmetricEncypt( - newGhostUser.keys.plainPrivateKey - ); + const { iv, tag, ciphertext, encoding, algorithm } = crypto + .encryption() + .encryptWithRootEncryptionKey(newGhostUser.keys.plainPrivateKey); if (autoGeneratedBot) { await projectBotDAL.updateById( autoGeneratedBot.id, @@ -1131,7 +1133,7 @@ export const orgServiceFactory = ({ }); } - const botPrivateKey = infisicalSymmetricDecrypt({ + const botPrivateKey = crypto.encryption().decryptWithRootEncryptionKey({ keyEncoding: bot.keyEncoding as SecretKeyEncoding, iv: bot.iv, tag: bot.tag, diff --git a/backend/src/services/project-bot/project-bot-fns.ts b/backend/src/services/project-bot/project-bot-fns.ts index a8e507bc75..42db24d5b2 100644 --- a/backend/src/services/project-bot/project-bot-fns.ts +++ b/backend/src/services/project-bot/project-bot-fns.ts @@ -1,24 +1,19 @@ import { SecretKeyEncoding } from "@app/db/schemas"; -import { - decryptAsymmetric, - encryptAsymmetric, - generateAsymmetricKeyPair, - infisicalSymmetricDecrypt, - infisicalSymmetricEncypt -} from "@app/lib/crypto/encryption"; +import { crypto } from "@app/lib/crypto/cryptography"; import { NotFoundError } from "@app/lib/errors"; import { TProjectBotDALFactory } from "@app/services/project-bot/project-bot-dal"; import { TProjectDALFactory } from "../project/project-dal"; import { TGetPrivateKeyDTO } from "./project-bot-types"; -export const getBotPrivateKey = ({ bot }: TGetPrivateKeyDTO) => - infisicalSymmetricDecrypt({ +export const getBotPrivateKey = ({ bot }: TGetPrivateKeyDTO) => { + return crypto.encryption().decryptWithRootEncryptionKey({ keyEncoding: bot.keyEncoding as SecretKeyEncoding, iv: bot.iv, tag: bot.tag, ciphertext: bot.encryptedPrivateKey }); +}; export const getBotKeyFnFactory = ( projectBotDAL: TProjectBotDALFactory, @@ -51,22 +46,27 @@ export const getBotKeyFnFactory = ( projectV1Keys.serverEncryptedPrivateKeyTag && projectV1Keys.serverEncryptedPrivateKeyEncoding ) { - userPrivateKey = infisicalSymmetricDecrypt({ + userPrivateKey = crypto.encryption().decryptWithRootEncryptionKey({ iv: projectV1Keys.serverEncryptedPrivateKeyIV, tag: projectV1Keys.serverEncryptedPrivateKeyTag, ciphertext: projectV1Keys.serverEncryptedPrivateKey, keyEncoding: projectV1Keys.serverEncryptedPrivateKeyEncoding as SecretKeyEncoding }); } - const workspaceKey = decryptAsymmetric({ + const workspaceKey = crypto.encryption().asymmetric().decrypt({ ciphertext: projectV1Keys.projectEncryptedKey, nonce: projectV1Keys.projectKeyNonce, publicKey: projectV1Keys.senderPublicKey, privateKey: userPrivateKey }); - const botKey = generateAsymmetricKeyPair(); - const { iv, tag, ciphertext, encoding, algorithm } = infisicalSymmetricEncypt(botKey.privateKey); - const encryptedWorkspaceKey = encryptAsymmetric(workspaceKey, botKey.publicKey, userPrivateKey); + const botKey = crypto.encryption().asymmetric().generateKeyPair(); + const { iv, tag, ciphertext, encoding, algorithm } = crypto + .encryption() + .encryptWithRootEncryptionKey(botKey.privateKey); + const encryptedWorkspaceKey = crypto + .encryption() + .asymmetric() + .encrypt(workspaceKey, botKey.publicKey, userPrivateKey); let botId; if (!bot) { @@ -105,7 +105,7 @@ export const getBotKeyFnFactory = ( } const botPrivateKey = getBotPrivateKey({ bot }); - const botKey = decryptAsymmetric({ + const botKey = crypto.encryption().asymmetric().decrypt({ ciphertext: bot.encryptedProjectKey, privateKey: botPrivateKey, nonce: bot.encryptedProjectKeyNonce, diff --git a/backend/src/services/project-bot/project-bot-service.ts b/backend/src/services/project-bot/project-bot-service.ts index 7dc5b058e0..a5a7d47b33 100644 --- a/backend/src/services/project-bot/project-bot-service.ts +++ b/backend/src/services/project-bot/project-bot-service.ts @@ -3,8 +3,7 @@ import { ForbiddenError } from "@casl/ability"; import { ActionProjectType, ProjectVersion } from "@app/db/schemas"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types"; import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission"; -import { generateAsymmetricKeyPair } from "@app/lib/crypto"; -import { infisicalSymmetricEncypt } from "@app/lib/crypto/encryption"; +import { crypto } from "@app/lib/crypto/cryptography"; import { BadRequestError, NotFoundError } from "@app/lib/errors"; import { TProjectDALFactory } from "../project/project-dal"; @@ -55,9 +54,12 @@ export const projectBotServiceFactory = ({ const doc = await projectBotDAL.findOne({ projectId }, tx); if (doc) return doc; - const keys = privateKey && publicKey ? { privateKey, publicKey } : generateAsymmetricKeyPair(); + const keys = + privateKey && publicKey ? { privateKey, publicKey } : crypto.encryption().asymmetric().generateKeyPair(); - const { iv, tag, ciphertext, encoding, algorithm } = infisicalSymmetricEncypt(keys.privateKey); + const { iv, tag, ciphertext, encoding, algorithm } = crypto + .encryption() + .encryptWithRootEncryptionKey(keys.privateKey); const project = await projectDAL.findById(projectId, tx); diff --git a/backend/src/services/project/project-fns.ts b/backend/src/services/project/project-fns.ts index 08652e3486..306cba50a5 100644 --- a/backend/src/services/project/project-fns.ts +++ b/backend/src/services/project/project-fns.ts @@ -1,10 +1,8 @@ -import crypto from "crypto"; - import { ProjectVersion, TProjects } from "@app/db/schemas"; import { createSshCaHelper } from "@app/ee/services/ssh/ssh-certificate-authority-fns"; import { SshCaKeySource } from "@app/ee/services/ssh/ssh-certificate-authority-types"; import { SshCertKeyAlgorithm } from "@app/ee/services/ssh-certificate/ssh-certificate-types"; -import { decryptAsymmetric, encryptAsymmetric } from "@app/lib/crypto"; +import { crypto } from "@app/lib/crypto/cryptography"; import { NotFoundError } from "@app/lib/errors"; import { TKmsServiceFactory } from "@app/services/kms/kms-service"; import { TProjectDALFactory } from "@app/services/project/project-dal"; @@ -12,7 +10,7 @@ import { TProjectDALFactory } from "@app/services/project/project-dal"; import { AddUserToWsDTO, TBootstrapSshProjectDTO } from "./project-types"; export const assignWorkspaceKeysToMembers = ({ members, decryptKey, userPrivateKey }: AddUserToWsDTO) => { - const plaintextProjectKey = decryptAsymmetric({ + const plaintextProjectKey = crypto.encryption().asymmetric().decrypt({ ciphertext: decryptKey.encryptedKey, nonce: decryptKey.nonce, publicKey: decryptKey.sender.publicKey, @@ -20,11 +18,10 @@ export const assignWorkspaceKeysToMembers = ({ members, decryptKey, userPrivateK }); const newWsMembers = members.map(({ orgMembershipId, userPublicKey }) => { - const { ciphertext: inviteeCipherText, nonce: inviteeNonce } = encryptAsymmetric( - plaintextProjectKey, - userPublicKey, - userPrivateKey - ); + const { ciphertext: inviteeCipherText, nonce: inviteeNonce } = crypto + .encryption() + .asymmetric() + .encrypt(plaintextProjectKey, userPublicKey, userPrivateKey); return { orgMembershipId, @@ -47,11 +44,10 @@ export const createProjectKey = ({ publicKey, privateKey, plainProjectKey }: TCr const randomBytes = plainProjectKey || crypto.randomBytes(16).toString("hex"); // 4. Encrypt the project key with the users key pair. - const { ciphertext: encryptedProjectKey, nonce: encryptedProjectKeyIv } = encryptAsymmetric( - randomBytes, - publicKey, - privateKey - ); + const { ciphertext: encryptedProjectKey, nonce: encryptedProjectKeyIv } = crypto + .encryption() + .asymmetric() + .encrypt(randomBytes, publicKey, privateKey); return { key: encryptedProjectKey, iv: encryptedProjectKeyIv }; }; diff --git a/backend/src/services/project/project-queue.ts b/backend/src/services/project/project-queue.ts index e845ebd35a..ed6241bc77 100644 --- a/backend/src/services/project/project-queue.ts +++ b/backend/src/services/project/project-queue.ts @@ -21,14 +21,10 @@ import { decryptIntegrationAuths, decryptSecretApprovals, decryptSecrets, - decryptSecretVersions + decryptSecretVersions, + SymmetricKeySize } from "@app/lib/crypto"; -import { - decryptAsymmetric, - encryptSymmetric128BitHexKeyUTF8, - infisicalSymmetricDecrypt, - infisicalSymmetricEncypt -} from "@app/lib/crypto/encryption"; +import { crypto } from "@app/lib/crypto/cryptography"; import { logger } from "@app/lib/logger"; import { QueueJobs, QueueName, TQueueJobTypes, TQueueServiceFactory } from "@app/queue"; @@ -118,17 +114,14 @@ export const projectQueueFactory = ({ await projectDAL.setProjectUpgradeStatus(data.projectId, ProjectUpgradeStatus.InProgress); // Set the status to in progress. This is important to prevent multiple upgrades at the same time. - // eslint-disable-next-line no-promise-executor-return - // await new Promise((resolve) => setTimeout(resolve, 50_000)); - - const userPrivateKey = infisicalSymmetricDecrypt({ + const userPrivateKey = crypto.encryption().decryptWithRootEncryptionKey({ keyEncoding: data.encryptedPrivateKey.keyEncoding, ciphertext: data.encryptedPrivateKey.encryptedKey, iv: data.encryptedPrivateKey.encryptedKeyIv, tag: data.encryptedPrivateKey.encryptedKeyTag }); - const decryptedPlainProjectKey = decryptAsymmetric({ + const decryptedPlainProjectKey = crypto.encryption().asymmetric().decrypt({ ciphertext: oldProjectKey.encryptedKey, nonce: oldProjectKey.nonce, publicKey: oldProjectKey.sender.publicKey, @@ -321,7 +314,9 @@ export const projectQueueFactory = ({ await projectKeyDAL.insertMany(newProjectMembers, tx); // Encrypt the bot private key (which is the same as the ghost user) - const { iv, tag, ciphertext, encoding, algorithm } = infisicalSymmetricEncypt(ghostUser.keys.plainPrivateKey); + const { iv, tag, ciphertext, encoding, algorithm } = crypto + .encryption() + .encryptWithRootEncryptionKey(ghostUser.keys.plainPrivateKey); // 5. Create a bot for the project const newBot = await projectBotDAL.create( @@ -342,14 +337,14 @@ export const projectQueueFactory = ({ tx ); - const botPrivateKey = infisicalSymmetricDecrypt({ + const botPrivateKey = crypto.encryption().decryptWithRootEncryptionKey({ keyEncoding: newBot.keyEncoding as SecretKeyEncoding, iv: newBot.iv, tag: newBot.tag, ciphertext: newBot.encryptedPrivateKey }); - const botKey = decryptAsymmetric({ + const botKey = crypto.encryption().asymmetric().decrypt({ ciphertext: newBot.encryptedProjectKey!, privateKey: botPrivateKey, nonce: newBot.encryptedProjectKeyNonce!, @@ -361,12 +356,23 @@ export const projectQueueFactory = ({ const updatedSecretApprovals: TSecretApprovalRequestsSecrets[] = []; const updatedIntegrationAuths: TIntegrationAuths[] = []; for (const rawSecret of decryptedSecrets) { - const secretKeyEncrypted = encryptSymmetric128BitHexKeyUTF8(rawSecret.decrypted.secretKey, botKey); - const secretValueEncrypted = encryptSymmetric128BitHexKeyUTF8(rawSecret.decrypted.secretValue || "", botKey); - const secretCommentEncrypted = encryptSymmetric128BitHexKeyUTF8( - rawSecret.decrypted.secretComment || "", - botKey - ); + const secretKeyEncrypted = crypto.encryption().encryptSymmetric({ + plaintext: rawSecret.decrypted.secretKey, + key: botKey, + keySize: SymmetricKeySize.Bits128 + }); + + const secretValueEncrypted = crypto.encryption().encryptSymmetric({ + plaintext: rawSecret.decrypted.secretValue || "", + key: botKey, + keySize: SymmetricKeySize.Bits128 + }); + + const secretCommentEncrypted = crypto.encryption().encryptSymmetric({ + plaintext: rawSecret.decrypted.secretComment || "", + key: botKey, + keySize: SymmetricKeySize.Bits128 + }); const payload: TSecrets = { ...rawSecret.original, @@ -393,15 +399,23 @@ export const projectQueueFactory = ({ } for (const rawSecretVersion of decryptedSecretVersions) { - const secretKeyEncrypted = encryptSymmetric128BitHexKeyUTF8(rawSecretVersion.decrypted.secretKey, botKey); - const secretValueEncrypted = encryptSymmetric128BitHexKeyUTF8( - rawSecretVersion.decrypted.secretValue || "", - botKey - ); - const secretCommentEncrypted = encryptSymmetric128BitHexKeyUTF8( - rawSecretVersion.decrypted.secretComment || "", - botKey - ); + const secretKeyEncrypted = crypto.encryption().encryptSymmetric({ + plaintext: rawSecretVersion.decrypted.secretKey, + key: botKey, + keySize: SymmetricKeySize.Bits128 + }); + + const secretValueEncrypted = crypto.encryption().encryptSymmetric({ + plaintext: rawSecretVersion.decrypted.secretValue || "", + key: botKey, + keySize: SymmetricKeySize.Bits128 + }); + + const secretCommentEncrypted = crypto.encryption().encryptSymmetric({ + plaintext: rawSecretVersion.decrypted.secretComment || "", + key: botKey, + keySize: SymmetricKeySize.Bits128 + }); const payload: TSecretVersions = { ...rawSecretVersion.original, @@ -428,15 +442,21 @@ export const projectQueueFactory = ({ } for (const rawSecretApproval of decryptedApprovalSecrets) { - const secretKeyEncrypted = encryptSymmetric128BitHexKeyUTF8(rawSecretApproval.decrypted.secretKey, botKey); - const secretValueEncrypted = encryptSymmetric128BitHexKeyUTF8( - rawSecretApproval.decrypted.secretValue || "", - botKey - ); - const secretCommentEncrypted = encryptSymmetric128BitHexKeyUTF8( - rawSecretApproval.decrypted.secretComment || "", - botKey - ); + const secretKeyEncrypted = crypto.encryption().encryptSymmetric({ + plaintext: rawSecretApproval.decrypted.secretKey, + key: botKey, + keySize: SymmetricKeySize.Bits128 + }); + const secretValueEncrypted = crypto.encryption().encryptSymmetric({ + plaintext: rawSecretApproval.decrypted.secretValue || "", + key: botKey, + keySize: SymmetricKeySize.Bits128 + }); + const secretCommentEncrypted = crypto.encryption().encryptSymmetric({ + plaintext: rawSecretApproval.decrypted.secretComment || "", + key: botKey, + keySize: SymmetricKeySize.Bits128 + }); const payload: TSecretApprovalRequestsSecrets = { ...rawSecretApproval.original, @@ -463,9 +483,21 @@ export const projectQueueFactory = ({ } for (const integrationAuth of decryptedIntegrationAuths) { - const access = encryptSymmetric128BitHexKeyUTF8(integrationAuth.decrypted.access, botKey); - const accessId = encryptSymmetric128BitHexKeyUTF8(integrationAuth.decrypted.accessId, botKey); - const refresh = encryptSymmetric128BitHexKeyUTF8(integrationAuth.decrypted.refresh, botKey); + const access = crypto.encryption().encryptSymmetric({ + plaintext: integrationAuth.decrypted.access, + key: botKey, + keySize: SymmetricKeySize.Bits128 + }); + const accessId = crypto.encryption().encryptSymmetric({ + plaintext: integrationAuth.decrypted.accessId, + key: botKey, + keySize: SymmetricKeySize.Bits128 + }); + const refresh = crypto.encryption().encryptSymmetric({ + plaintext: integrationAuth.decrypted.refresh, + key: botKey, + keySize: SymmetricKeySize.Bits128 + }); const payload: TIntegrationAuths = { ...integrationAuth.original, diff --git a/backend/src/services/project/project-service.ts b/backend/src/services/project/project-service.ts index 29774fcc8b..b649d8e4cf 100644 --- a/backend/src/services/project/project-service.ts +++ b/backend/src/services/project/project-service.ts @@ -34,7 +34,7 @@ import { TSshHostDALFactory } from "@app/ee/services/ssh-host/ssh-host-dal"; import { TSshHostGroupDALFactory } from "@app/ee/services/ssh-host-group/ssh-host-group-dal"; import { PgSqlLock, TKeyStoreFactory } from "@app/keystore/keystore"; import { getConfig } from "@app/lib/config/env"; -import { infisicalSymmetricEncypt } from "@app/lib/crypto/encryption"; +import { crypto } from "@app/lib/crypto/cryptography"; import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors"; import { groupBy } from "@app/lib/fn"; import { alphaNumericNanoId } from "@app/lib/nanoid"; @@ -392,7 +392,9 @@ export const projectServiceFactory = ({ tx ); - const { iv, tag, ciphertext, encoding, algorithm } = infisicalSymmetricEncypt(ghostUser.keys.plainPrivateKey); + const { iv, tag, ciphertext, encoding, algorithm } = crypto + .encryption() + .encryptWithRootEncryptionKey(ghostUser.keys.plainPrivateKey); // 5. Create & a bot for the project await projectBotDAL.create( @@ -853,7 +855,7 @@ export const projectServiceFactory = ({ }); } - const encryptedPrivateKey = infisicalSymmetricEncypt(userPrivateKey); + const encryptedPrivateKey = crypto.encryption().encryptWithRootEncryptionKey(userPrivateKey); await projectQueue.upgradeProject({ projectId, diff --git a/backend/src/services/secret-sharing/secret-sharing-service.ts b/backend/src/services/secret-sharing/secret-sharing-service.ts index e879d56f11..4cbfcdc7f3 100644 --- a/backend/src/services/secret-sharing/secret-sharing-service.ts +++ b/backend/src/services/secret-sharing/secret-sharing-service.ts @@ -1,10 +1,7 @@ -import crypto from "node:crypto"; - -import bcrypt from "bcrypt"; - import { TSecretSharing } from "@app/db/schemas"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types"; import { getConfig } from "@app/lib/config/env"; +import { crypto } from "@app/lib/crypto/cryptography"; import { BadRequestError, ForbiddenRequestError, NotFoundError, UnauthorizedError } from "@app/lib/errors"; import { logger } from "@app/lib/logger"; import { SecretSharingAccessType } from "@app/lib/types"; @@ -136,7 +133,7 @@ export const secretSharingServiceFactory = ({ const encryptedSecret = encryptWithRoot(Buffer.from(secretValue)); const id = crypto.randomBytes(32).toString("hex"); - const hashedPassword = password ? await bcrypt.hash(password, 10) : null; + const hashedPassword = password ? await crypto.hashing().createHash(password, appCfg.SALT_ROUNDS) : null; const newSharedSecret = await secretSharingDAL.create({ identifier: id, @@ -386,8 +383,10 @@ export const secretSharingServiceFactory = ({ const encryptWithRoot = kmsService.encryptWithRootKey(); const encryptedSecret = encryptWithRoot(Buffer.from(secretValue)); + const appCfg = getConfig(); + const id = crypto.randomBytes(32).toString("hex"); - const hashedPassword = password ? await bcrypt.hash(password, 10) : null; + const hashedPassword = password ? await crypto.hashing().createHash(password, appCfg.SALT_ROUNDS) : null; const newSharedSecret = await secretSharingDAL.create({ identifier: id, @@ -529,7 +528,7 @@ export const secretSharingServiceFactory = ({ const hasProvidedPassword = Boolean(password); if (isPasswordProtected) { if (hasProvidedPassword) { - const isMatch = await bcrypt.compare(password as string, sharedSecret.password as string); + const isMatch = await crypto.hashing().compareHash(password as string, sharedSecret.password as string); if (!isMatch) throw new UnauthorizedError({ message: "Invalid credentials" }); } else { return { isPasswordProtected }; diff --git a/backend/src/services/secret/secret-fns.ts b/backend/src/services/secret/secret-fns.ts index 847ef17df5..6f6358779e 100644 --- a/backend/src/services/secret/secret-fns.ts +++ b/backend/src/services/secret/secret-fns.ts @@ -16,11 +16,8 @@ import { hasSecretReadValueOrDescribePermission } from "@app/ee/services/permiss import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types"; import { ProjectPermissionSecretActions } from "@app/ee/services/permission/project-permission"; import { getConfig } from "@app/lib/config/env"; -import { - buildSecretBlindIndexFromName, - decryptSymmetric128BitHexKeyUTF8, - encryptSymmetric128BitHexKeyUTF8 -} from "@app/lib/crypto"; +import { buildSecretBlindIndexFromName } from "@app/lib/crypto"; +import { crypto, SymmetricKeySize } from "@app/lib/crypto/cryptography"; import { daysToMillisecond, secondsToMillis } from "@app/lib/dates"; import { BadRequestError, NotFoundError } from "@app/lib/errors"; import { groupBy, unique } from "@app/lib/fn"; @@ -239,17 +236,19 @@ export const interpolateSecrets = ({ projectId, secretEncKey, secretDAL, folderD const secrets = await secretDAL.findByFolderId(folder.id); const decryptedSec = secrets.reduce>((prev, secret) => { - const decryptedSecretKey = decryptSymmetric128BitHexKeyUTF8({ + const decryptedSecretKey = crypto.encryption().decryptSymmetric({ ciphertext: secret.secretKeyCiphertext, iv: secret.secretKeyIV, tag: secret.secretKeyTag, - key: secretEncKey + key: secretEncKey, + keySize: SymmetricKeySize.Bits128 }); - const decryptedSecretValue = decryptSymmetric128BitHexKeyUTF8({ + const decryptedSecretValue = crypto.encryption().decryptSymmetric({ ciphertext: secret.secretValueCiphertext, iv: secret.secretValueIV, tag: secret.secretValueTag, - key: secretEncKey + key: secretEncKey, + keySize: SymmetricKeySize.Bits128 }); // eslint-disable-next-line @@ -366,30 +365,33 @@ export const decryptSecretRaw = ( }, key: string ) => { - const secretKey = decryptSymmetric128BitHexKeyUTF8({ + const secretKey = crypto.encryption().decryptSymmetric({ ciphertext: secret.secretKeyCiphertext, iv: secret.secretKeyIV, tag: secret.secretKeyTag, - key + key, + keySize: SymmetricKeySize.Bits128 }); const secretValue = !secret.secretValueHidden - ? decryptSymmetric128BitHexKeyUTF8({ + ? crypto.encryption().decryptSymmetric({ ciphertext: secret.secretValueCiphertext, iv: secret.secretValueIV, tag: secret.secretValueTag, - key + key, + keySize: SymmetricKeySize.Bits128 }) : INFISICAL_SECRET_VALUE_HIDDEN_MASK; let secretComment = ""; if (secret.secretCommentCiphertext && secret.secretCommentIV && secret.secretCommentTag) { - secretComment = decryptSymmetric128BitHexKeyUTF8({ + secretComment = crypto.encryption().decryptSymmetric({ ciphertext: secret.secretCommentCiphertext, iv: secret.secretCommentIV, tag: secret.secretCommentTag, - key + key, + keySize: SymmetricKeySize.Bits128 }); } @@ -877,11 +879,24 @@ export const createManySecretsRawFnFactory = ({ message: `Project bot not found for project with ID '${projectId}'. Please upgrade your project.`, name: "bot_not_found_error" }); + const inputSecrets = secrets.map((secret) => { - const secretKeyEncrypted = encryptSymmetric128BitHexKeyUTF8(secret.secretName, botKey); - const secretValueEncrypted = encryptSymmetric128BitHexKeyUTF8(secret.secretValue || "", botKey); + const secretKeyEncrypted = crypto.encryption().encryptSymmetric({ + plaintext: secret.secretName, + key: botKey, + keySize: SymmetricKeySize.Bits128 + }); + const secretValueEncrypted = crypto.encryption().encryptSymmetric({ + plaintext: secret.secretValue || "", + key: botKey, + keySize: SymmetricKeySize.Bits128 + }); const secretReferences = getAllNestedSecretReferences(secret.secretValue || ""); - const secretCommentEncrypted = encryptSymmetric128BitHexKeyUTF8(secret.secretComment || "", botKey); + const secretCommentEncrypted = crypto.encryption().encryptSymmetric({ + plaintext: secret.secretComment || "", + key: botKey, + keySize: SymmetricKeySize.Bits128 + }); return { type: secret.type, @@ -1065,10 +1080,22 @@ export const updateManySecretsRawFnFactory = ({ throw new BadRequestError({ message: "New secret name cannot be empty" }); } - const secretKeyEncrypted = encryptSymmetric128BitHexKeyUTF8(secret.secretName, botKey); - const secretValueEncrypted = encryptSymmetric128BitHexKeyUTF8(secret.secretValue || "", botKey); + const secretKeyEncrypted = crypto.encryption().encryptSymmetric({ + plaintext: secret.secretName, + key: botKey, + keySize: SymmetricKeySize.Bits128 + }); + const secretValueEncrypted = crypto.encryption().encryptSymmetric({ + plaintext: secret.secretValue || "", + key: botKey, + keySize: SymmetricKeySize.Bits128 + }); const secretReferences = getAllNestedSecretReferences(secret.secretValue || ""); - const secretCommentEncrypted = encryptSymmetric128BitHexKeyUTF8(secret.secretComment || "", botKey); + const secretCommentEncrypted = crypto.encryption().encryptSymmetric({ + plaintext: secret.secretComment || "", + key: botKey, + keySize: SymmetricKeySize.Bits128 + }); return { type: secret.type, @@ -1151,28 +1178,31 @@ export const decryptSecretWithBot = ( >, key: string ) => { - const secretKey = decryptSymmetric128BitHexKeyUTF8({ + const secretKey = crypto.encryption().decryptSymmetric({ ciphertext: secret.secretKeyCiphertext, iv: secret.secretKeyIV, tag: secret.secretKeyTag, - key + key, + keySize: SymmetricKeySize.Bits128 }); - const secretValue = decryptSymmetric128BitHexKeyUTF8({ + const secretValue = crypto.encryption().decryptSymmetric({ ciphertext: secret.secretValueCiphertext, iv: secret.secretValueIV, tag: secret.secretValueTag, - key + key, + keySize: SymmetricKeySize.Bits128 }); let secretComment = ""; if (secret.secretCommentCiphertext && secret.secretCommentIV && secret.secretCommentTag) { - secretComment = decryptSymmetric128BitHexKeyUTF8({ + secretComment = crypto.encryption().decryptSymmetric({ ciphertext: secret.secretCommentCiphertext, iv: secret.secretCommentIV, tag: secret.secretCommentTag, - key + key, + keySize: SymmetricKeySize.Bits128 }); } diff --git a/backend/src/services/secret/secret-queue.ts b/backend/src/services/secret/secret-queue.ts index 25b6e65729..731eb6951d 100644 --- a/backend/src/services/secret/secret-queue.ts +++ b/backend/src/services/secret/secret-queue.ts @@ -18,8 +18,7 @@ import { TSnapshotDALFactory } from "@app/ee/services/secret-snapshot/snapshot-d import { TSnapshotSecretV2DALFactory } from "@app/ee/services/secret-snapshot/snapshot-secret-v2-dal"; import { KeyStorePrefixes, KeyStoreTtls, TKeyStoreFactory } from "@app/keystore/keystore"; import { getConfig } from "@app/lib/config/env"; -import { decryptSymmetric128BitHexKeyUTF8 } from "@app/lib/crypto"; -import { infisicalSymmetricEncypt } from "@app/lib/crypto/encryption"; +import { crypto, SymmetricKeySize } from "@app/lib/crypto/cryptography"; import { daysToMillisecond, secondsToMillis } from "@app/lib/dates"; import { BadRequestError, NotFoundError } from "@app/lib/errors"; import { getTimeDifferenceInSeconds, groupBy, isSamePath, unique } from "@app/lib/fn"; @@ -504,18 +503,20 @@ export const secretQueueFactory = ({ const secrets = await secretDAL.findByFolderId(dto.folderId); await Promise.allSettled( secrets.map(async (secret) => { - const secretKey = decryptSymmetric128BitHexKeyUTF8({ + const secretKey = crypto.encryption().decryptSymmetric({ ciphertext: secret.secretKeyCiphertext, iv: secret.secretKeyIV, tag: secret.secretKeyTag, - key: dto.key + key: dto.key, + keySize: SymmetricKeySize.Bits128 }); - const secretValue = decryptSymmetric128BitHexKeyUTF8({ + const secretValue = crypto.encryption().decryptSymmetric({ ciphertext: secret.secretValueCiphertext, iv: secret.secretValueIV, tag: secret.secretValueTag, - key: dto.key + key: dto.key, + keySize: SymmetricKeySize.Bits128 }); const expandedSecretValue = await expandSecretReferences({ environment: dto.environment, @@ -527,11 +528,12 @@ export const secretQueueFactory = ({ content[secretKey] = { value: expandedSecretValue || "" }; if (secret.secretCommentCiphertext && secret.secretCommentIV && secret.secretCommentTag) { - const commentValue = decryptSymmetric128BitHexKeyUTF8({ + const commentValue = crypto.encryption().decryptSymmetric({ ciphertext: secret.secretCommentCiphertext, iv: secret.secretCommentIV, tag: secret.secretCommentTag, - key: dto.key + key: dto.key, + keySize: SymmetricKeySize.Bits128 }); content[secretKey].comment = commentValue; } @@ -952,11 +954,12 @@ export const secretQueueFactory = ({ integrationAuth.awsAssumeIamRoleArnIV && integrationAuth.awsAssumeIamRoleArnCipherText ) { - awsAssumeRoleArn = decryptSymmetric128BitHexKeyUTF8({ + awsAssumeRoleArn = crypto.encryption().decryptSymmetric({ ciphertext: integrationAuth.awsAssumeIamRoleArnCipherText, iv: integrationAuth.awsAssumeIamRoleArnIV, tag: integrationAuth.awsAssumeIamRoleArnTag, - key: botKey as string + key: botKey as string, + keySize: SymmetricKeySize.Bits128 }); } @@ -1236,7 +1239,9 @@ export const secretQueueFactory = ({ }, tx ); - const { iv, tag, ciphertext, encoding, algorithm } = infisicalSymmetricEncypt(ghostUser.keys.plainPrivateKey); + const { iv, tag, ciphertext, encoding, algorithm } = crypto + .encryption() + .encryptWithRootEncryptionKey(ghostUser.keys.plainPrivateKey); await projectBotDAL.updateById( bot.id, { @@ -1267,27 +1272,31 @@ export const secretQueueFactory = ({ secretId: string; references: { environment: string; secretPath: string; secretKey: string }[]; }[] = []; + await secretV2BridgeDAL.batchInsert( projectV1Secrets.map((el) => { - const key = decryptSymmetric128BitHexKeyUTF8({ + const key = crypto.encryption().decryptSymmetric({ ciphertext: el.secretKeyCiphertext, iv: el.secretKeyIV, tag: el.secretKeyTag, - key: botKey + key: botKey, + keySize: SymmetricKeySize.Bits128 }); - const value = decryptSymmetric128BitHexKeyUTF8({ + const value = crypto.encryption().decryptSymmetric({ ciphertext: el.secretValueCiphertext, iv: el.secretValueIV, tag: el.secretValueTag, - key: botKey + key: botKey, + keySize: SymmetricKeySize.Bits128 }); const comment = el.secretCommentCiphertext && el.secretCommentTag && el.secretCommentIV - ? decryptSymmetric128BitHexKeyUTF8({ + ? crypto.encryption().decryptSymmetric({ ciphertext: el.secretCommentCiphertext, iv: el.secretCommentIV, tag: el.secretCommentTag, - key: botKey + key: botKey, + keySize: SymmetricKeySize.Bits128 }) : ""; const encryptedValue = secretManagerEncryptor({ plainText: Buffer.from(value) }).cipherTextBlob; @@ -1325,6 +1334,7 @@ export const secretQueueFactory = ({ const projectV3SecretVersionsGroupById: Record = {}; const projectV3SecretVersionTags: { secret_versions_v2Id: string; secret_tagsId: string }[] = []; const projectV3SnapshotSecrets: Omit[] = []; + snapshots.forEach(({ secretVersions = [], ...snapshot }) => { secretVersions.forEach((el) => { projectV3SnapshotSecrets.push({ @@ -1336,25 +1346,28 @@ export const secretQueueFactory = ({ }); if (projectV3SecretVersionsGroupById[el.id]) return; - const key = decryptSymmetric128BitHexKeyUTF8({ + const key = crypto.encryption().decryptSymmetric({ ciphertext: el.secretKeyCiphertext, iv: el.secretKeyIV, tag: el.secretKeyTag, - key: botKey + key: botKey, + keySize: SymmetricKeySize.Bits128 }); - const value = decryptSymmetric128BitHexKeyUTF8({ + const value = crypto.encryption().decryptSymmetric({ ciphertext: el.secretValueCiphertext, iv: el.secretValueIV, tag: el.secretValueTag, - key: botKey + key: botKey, + keySize: SymmetricKeySize.Bits128 }); const comment = el.secretCommentCiphertext && el.secretCommentTag && el.secretCommentIV - ? decryptSymmetric128BitHexKeyUTF8({ + ? crypto.encryption().decryptSymmetric({ ciphertext: el.secretCommentCiphertext, iv: el.secretCommentIV, tag: el.secretCommentTag, - key: botKey + key: botKey, + keySize: SymmetricKeySize.Bits128 }) : ""; const encryptedValue = secretManagerEncryptor({ plainText: Buffer.from(value) }).cipherTextBlob; @@ -1395,25 +1408,28 @@ export const secretQueueFactory = ({ ); Object.values(latestSecretVersionByFolder).forEach((el) => { if (projectV3SecretVersionsGroupById[el.id]) return; - const key = decryptSymmetric128BitHexKeyUTF8({ + const key = crypto.encryption().decryptSymmetric({ ciphertext: el.secretKeyCiphertext, iv: el.secretKeyIV, tag: el.secretKeyTag, - key: botKey + key: botKey, + keySize: SymmetricKeySize.Bits128 }); - const value = decryptSymmetric128BitHexKeyUTF8({ + const value = crypto.encryption().decryptSymmetric({ ciphertext: el.secretValueCiphertext, iv: el.secretValueIV, tag: el.secretValueTag, - key: botKey + key: botKey, + keySize: SymmetricKeySize.Bits128 }); const comment = el.secretCommentCiphertext && el.secretCommentTag && el.secretCommentIV - ? decryptSymmetric128BitHexKeyUTF8({ + ? crypto.encryption().decryptSymmetric({ ciphertext: el.secretCommentCiphertext, iv: el.secretCommentIV, tag: el.secretCommentTag, - key: botKey + key: botKey, + keySize: SymmetricKeySize.Bits128 }) : ""; const encryptedValue = secretManagerEncryptor({ plainText: Buffer.from(value) }).cipherTextBlob; @@ -1475,42 +1491,47 @@ export const secretQueueFactory = ({ * */ // eslint-disable-next-line no-await-in-loop const projectV1IntegrationAuths = await integrationAuthDAL.find({ projectId }, { tx }); + await integrationAuthDAL.upsert( projectV1IntegrationAuths.map((el) => { const accessToken = el.accessIV && el.accessTag && el.accessCiphertext - ? decryptSymmetric128BitHexKeyUTF8({ + ? crypto.encryption().decryptSymmetric({ ciphertext: el.accessCiphertext, iv: el.accessIV, tag: el.accessTag, - key: botKey + key: botKey, + keySize: SymmetricKeySize.Bits128 }) : undefined; const accessId = el.accessIdIV && el.accessIdTag && el.accessIdCiphertext - ? decryptSymmetric128BitHexKeyUTF8({ + ? crypto.encryption().decryptSymmetric({ ciphertext: el.accessIdCiphertext, iv: el.accessIdIV, tag: el.accessIdTag, - key: botKey + key: botKey, + keySize: SymmetricKeySize.Bits128 }) : undefined; const refreshToken = el.refreshIV && el.refreshTag && el.refreshCiphertext - ? decryptSymmetric128BitHexKeyUTF8({ + ? crypto.encryption().decryptSymmetric({ ciphertext: el.refreshCiphertext, iv: el.refreshIV, tag: el.refreshTag, - key: botKey + key: botKey, + keySize: SymmetricKeySize.Bits128 }) : undefined; const awsAssumeRoleArn = el.awsAssumeIamRoleArnCipherText && el.awsAssumeIamRoleArnIV && el.awsAssumeIamRoleArnTag - ? decryptSymmetric128BitHexKeyUTF8({ + ? crypto.encryption().decryptSymmetric({ ciphertext: el.awsAssumeIamRoleArnCipherText, iv: el.awsAssumeIamRoleArnIV, tag: el.awsAssumeIamRoleArnTag, - key: botKey + key: botKey, + keySize: SymmetricKeySize.Bits128 }) : undefined; diff --git a/backend/src/services/secret/secret-service.ts b/backend/src/services/secret/secret-service.ts index 159bee8e3f..5fa58ce904 100644 --- a/backend/src/services/secret/secret-service.ts +++ b/backend/src/services/secret/secret-service.ts @@ -29,11 +29,8 @@ import { TSecretApprovalRequestSecretDALFactory } from "@app/ee/services/secret- import { TSecretApprovalRequestServiceFactory } from "@app/ee/services/secret-approval-request/secret-approval-request-service"; import { TSecretSnapshotServiceFactory } from "@app/ee/services/secret-snapshot/secret-snapshot-service"; import { getConfig } from "@app/lib/config/env"; -import { - buildSecretBlindIndexFromName, - decryptSymmetric128BitHexKeyUTF8, - encryptSymmetric128BitHexKeyUTF8 -} from "@app/lib/crypto"; +import { buildSecretBlindIndexFromName, SymmetricKeySize } from "@app/lib/crypto"; +import { crypto } from "@app/lib/crypto/cryptography"; import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors"; import { groupBy, pick } from "@app/lib/fn"; import { logger } from "@app/lib/logger"; @@ -162,11 +159,12 @@ export const secretServiceFactory = ({ return (el: { ciphertext?: string; iv: string; tag: string }) => projectBot?.botKey ? getAllNestedSecretReferences( - decryptSymmetric128BitHexKeyUTF8({ + crypto.encryption().decryptSymmetric({ ciphertext: el.ciphertext || "", iv: el.iv, tag: el.tag, - key: projectBot.botKey + key: projectBot.botKey, + keySize: SymmetricKeySize.Bits128 }) ) : undefined; @@ -1705,9 +1703,22 @@ export const secretServiceFactory = ({ message: `Project bot for project with ID '${projectId}' not found. Please upgrade your project.`, name: "bot_not_found_error" }); - const secretKeyEncrypted = encryptSymmetric128BitHexKeyUTF8(secretName, botKey); - const secretValueEncrypted = encryptSymmetric128BitHexKeyUTF8(secretValue || "", botKey); - const secretCommentEncrypted = encryptSymmetric128BitHexKeyUTF8(secretComment || "", botKey); + + const secretKeyEncrypted = crypto.encryption().encryptSymmetric({ + plaintext: secretName, + key: botKey, + keySize: SymmetricKeySize.Bits128 + }); + const secretValueEncrypted = crypto.encryption().encryptSymmetric({ + plaintext: secretValue || "", + key: botKey, + keySize: SymmetricKeySize.Bits128 + }); + const secretCommentEncrypted = crypto.encryption().encryptSymmetric({ + plaintext: secretComment || "", + key: botKey, + keySize: SymmetricKeySize.Bits128 + }); if (policy) { const approval = await secretApprovalRequestService.generateSecretApprovalRequest({ policy, @@ -1873,9 +1884,22 @@ export const secretServiceFactory = ({ name: "bot_not_found_error" }); - const secretValueEncrypted = encryptSymmetric128BitHexKeyUTF8(secretValue || "", botKey); - const secretCommentEncrypted = encryptSymmetric128BitHexKeyUTF8(secretComment || "", botKey); - const secretKeyEncrypted = encryptSymmetric128BitHexKeyUTF8(newSecretName || secretName, botKey); + const secretValueEncrypted = crypto.encryption().encryptSymmetric({ + plaintext: secretValue || "", + key: botKey, + keySize: SymmetricKeySize.Bits128 + }); + const secretCommentEncrypted = crypto.encryption().encryptSymmetric({ + plaintext: secretComment || "", + key: botKey, + keySize: SymmetricKeySize.Bits128 + }); + + const secretKeyEncrypted = crypto.encryption().encryptSymmetric({ + plaintext: newSecretName || secretName, + key: botKey, + keySize: SymmetricKeySize.Bits128 + }); if (policy) { const approval = await secretApprovalRequestService.generateSecretApprovalRequest({ @@ -2119,11 +2143,24 @@ export const secretServiceFactory = ({ message: `Project bot for project with ID '${projectId}' not found. Please upgrade your project.`, name: "bot_not_found_error" }); + const sanitizedSecrets = inputSecrets.map( ({ secretComment, secretKey, metadata, tagIds, secretValue, skipMultilineEncoding }) => { - const secretKeyEncrypted = encryptSymmetric128BitHexKeyUTF8(secretKey, botKey); - const secretValueEncrypted = encryptSymmetric128BitHexKeyUTF8(secretValue || "", botKey); - const secretCommentEncrypted = encryptSymmetric128BitHexKeyUTF8(secretComment || "", botKey); + const secretKeyEncrypted = crypto.encryption().encryptSymmetric({ + plaintext: secretKey, + key: botKey, + keySize: SymmetricKeySize.Bits128 + }); + const secretValueEncrypted = crypto.encryption().encryptSymmetric({ + plaintext: secretValue || "", + key: botKey, + keySize: SymmetricKeySize.Bits128 + }); + const secretCommentEncrypted = crypto.encryption().encryptSymmetric({ + plaintext: secretComment || "", + key: botKey, + keySize: SymmetricKeySize.Bits128 + }); return { secretName: secretKey, skipMultilineEncoding, @@ -2263,6 +2300,7 @@ export const secretServiceFactory = ({ message: `Project bot for project with ID '${projectId}' not found. Please upgrade your project.`, name: "bot_not_found_error" }); + const sanitizedSecrets = inputSecrets.map( ({ secretComment, @@ -2274,9 +2312,21 @@ export const secretServiceFactory = ({ secretReminderNote, secretReminderRepeatDays }) => { - const secretKeyEncrypted = encryptSymmetric128BitHexKeyUTF8(newSecretName || secretKey, botKey); - const secretValueEncrypted = encryptSymmetric128BitHexKeyUTF8(secretValue || "", botKey); - const secretCommentEncrypted = encryptSymmetric128BitHexKeyUTF8(secretComment || "", botKey); + const secretKeyEncrypted = crypto.encryption().encryptSymmetric({ + plaintext: newSecretName || secretKey, + key: botKey, + keySize: SymmetricKeySize.Bits128 + }); + const secretValueEncrypted = crypto.encryption().encryptSymmetric({ + plaintext: secretValue || "", + key: botKey, + keySize: SymmetricKeySize.Bits128 + }); + const secretCommentEncrypted = crypto.encryption().encryptSymmetric({ + plaintext: secretComment || "", + key: botKey, + keySize: SymmetricKeySize.Bits128 + }); return { secretName: secretKey, newSecretName, @@ -2486,12 +2536,14 @@ export const secretServiceFactory = ({ limit, sort: [["createdAt", "desc"]] }); + return secretVersions.map((el) => { - const secretKey = decryptSymmetric128BitHexKeyUTF8({ + const secretKey = crypto.encryption().decryptSymmetric({ ciphertext: secret.secretKeyCiphertext, iv: secret.secretKeyIV, tag: secret.secretKeyTag, - key: botKey + key: botKey, + keySize: SymmetricKeySize.Bits128 }); const secretValueHidden = !hasSecretReadValueOrDescribePermission( @@ -2811,11 +2863,12 @@ export const secretServiceFactory = ({ secrets.map(({ id, secretValueCiphertext, secretValueIV, secretValueTag }) => ({ secretId: id, references: getAllNestedSecretReferences( - decryptSymmetric128BitHexKeyUTF8({ + crypto.encryption().decryptSymmetric({ ciphertext: secretValueCiphertext, iv: secretValueIV, tag: secretValueTag, - key: botKey + key: botKey, + keySize: SymmetricKeySize.Bits128 }) ) })), @@ -2916,11 +2969,12 @@ export const secretServiceFactory = ({ const destinationActions = [ProjectPermissionSecretActions.Create, ProjectPermissionSecretActions.Edit] as const; const decryptedSourceSecrets = sourceSecrets.map((secret) => { - const secretKey = decryptSymmetric128BitHexKeyUTF8({ + const secretKey = crypto.encryption().decryptSymmetric({ ciphertext: secret.secretKeyCiphertext, iv: secret.secretKeyIV, tag: secret.secretKeyTag, - key: botKey + key: botKey, + keySize: SymmetricKeySize.Bits128 }); for (const destinationAction of destinationActions) { @@ -2956,11 +3010,12 @@ export const secretServiceFactory = ({ return { ...secret, secretKey, - secretValue: decryptSymmetric128BitHexKeyUTF8({ + secretValue: crypto.encryption().decryptSymmetric({ ciphertext: secret.secretValueCiphertext, iv: secret.secretValueIV, tag: secret.secretValueTag, - key: botKey + key: botKey, + keySize: SymmetricKeySize.Bits128 }) }; }); @@ -2981,17 +3036,19 @@ export const secretServiceFactory = ({ const decryptedDestinationSecrets = destinationSecretsFromDB.map((secret) => { return { ...secret, - secretKey: decryptSymmetric128BitHexKeyUTF8({ + secretKey: crypto.encryption().decryptSymmetric({ ciphertext: secret.secretKeyCiphertext, iv: secret.secretKeyIV, tag: secret.secretKeyTag, - key: botKey + key: botKey, + keySize: SymmetricKeySize.Bits128 }), - secretValue: decryptSymmetric128BitHexKeyUTF8({ + secretValue: crypto.encryption().decryptSymmetric({ ciphertext: secret.secretValueCiphertext, iv: secret.secretValueIV, tag: secret.secretValueTag, - key: botKey + key: botKey, + keySize: SymmetricKeySize.Bits128 }) }; }); diff --git a/backend/src/services/service-token/service-token-service.ts b/backend/src/services/service-token/service-token-service.ts index d68b48d78b..030276d161 100644 --- a/backend/src/services/service-token/service-token-service.ts +++ b/backend/src/services/service-token/service-token-service.ts @@ -1,7 +1,4 @@ -import crypto from "node:crypto"; - import { ForbiddenError, subject } from "@casl/ability"; -import bcrypt from "bcrypt"; import { ActionProjectType } from "@app/db/schemas"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types"; @@ -11,6 +8,7 @@ import { ProjectPermissionSub } from "@app/ee/services/permission/project-permission"; import { getConfig } from "@app/lib/config/env"; +import { crypto } from "@app/lib/crypto/cryptography"; import { ForbiddenRequestError, NotFoundError, UnauthorizedError } from "@app/lib/errors"; import { logger } from "@app/lib/logger"; @@ -89,7 +87,7 @@ export const serviceTokenServiceFactory = ({ throw new NotFoundError({ message: `One or more selected environments not found` }); const secret = crypto.randomBytes(16).toString("hex"); - const secretHash = await bcrypt.hash(secret, appCfg.SALT_ROUNDS); + const secretHash = await crypto.hashing().createHash(secret, appCfg.SALT_ROUNDS); let expiresAt: Date | null = null; if (expiresIn) { expiresAt = new Date(); @@ -182,7 +180,7 @@ export const serviceTokenServiceFactory = ({ throw new ForbiddenRequestError({ message: "Service token has expired" }); } - const isMatch = await bcrypt.compare(tokenSecret, serviceToken.secretHash); + const isMatch = await crypto.hashing().compareHash(tokenSecret, serviceToken.secretHash); if (!isMatch) throw new UnauthorizedError({ message: "Invalid service token" }); await accessTokenQueue.updateServiceTokenStatus(serviceToken.id); diff --git a/backend/src/services/super-admin/super-admin-service.ts b/backend/src/services/super-admin/super-admin-service.ts index 8a0d4dd148..5c1b8f6f64 100644 --- a/backend/src/services/super-admin/super-admin-service.ts +++ b/backend/src/services/super-admin/super-admin-service.ts @@ -1,4 +1,3 @@ -import bcrypt from "bcrypt"; import { CronJob } from "cron"; import jwt from "jsonwebtoken"; @@ -6,7 +5,7 @@ import { IdentityAuthMethod, OrgMembershipRole, TSuperAdmin, TSuperAdminUpdate } import { TLicenseServiceFactory } from "@app/ee/services/license/license-service"; import { PgSqlLock, TKeyStoreFactory } from "@app/keystore/keystore"; import { getConfig } from "@app/lib/config/env"; -import { infisicalSymmetricEncypt } from "@app/lib/crypto/encryption"; +import { crypto } from "@app/lib/crypto/cryptography"; import { generateUserSrpKeys, getUserPrivateKey } from "@app/lib/crypto/srp"; import { BadRequestError, NotFoundError } from "@app/lib/errors"; import { logger } from "@app/lib/logger"; @@ -157,6 +156,7 @@ export const superAdminServiceFactory = ({ const newCfg = await serverCfgDAL.create({ // @ts-expect-error id is kept as fixed for idempotence and to avoid race condition id: ADMIN_CONFIG_DB_UUID, + fipsEnabled: crypto.isFipsModeEnabled(), initialized: false, allowSignUp: true, defaultAuthOrgId: null @@ -435,8 +435,10 @@ export const superAdminServiceFactory = ({ iv: encryptedPrivateKeyIV, tag: encryptedPrivateKeyTag }); - const hashedPassword = await bcrypt.hash(password, appCfg.BCRYPT_SALT_ROUND); - const { iv, tag, ciphertext, encoding } = infisicalSymmetricEncypt(privateKey); + + const hashedPassword = await crypto.hashing().createHash(password, appCfg.SALT_ROUNDS); + + const { iv, tag, ciphertext, encoding } = crypto.encryption().encryptWithRootEncryptionKey(privateKey); const userInfo = await userDAL.transaction(async (tx) => { const newUser = await userDAL.create( { @@ -522,7 +524,7 @@ export const superAdminServiceFactory = ({ }, tx ); - const { tag, encoding, ciphertext, iv } = infisicalSymmetricEncypt(password); + const { tag, encoding, ciphertext, iv } = crypto.encryption().encryptWithRootEncryptionKey(password); const encKeys = await generateUserSrpKeys(sanitizedEmail, password); const userEnc = await userDAL.createUserEncryption( diff --git a/backend/src/services/telemetry/telemetry-service.ts b/backend/src/services/telemetry/telemetry-service.ts index eaab5bec68..0c70a1d914 100644 --- a/backend/src/services/telemetry/telemetry-service.ts +++ b/backend/src/services/telemetry/telemetry-service.ts @@ -1,4 +1,3 @@ -import { createHash, randomUUID } from "crypto"; import { PostHog } from "posthog-node"; import { TLicenseServiceFactory } from "@app/ee/services/license/license-service"; @@ -6,6 +5,7 @@ import { InstanceType } from "@app/ee/services/license/license-types"; import { TKeyStoreFactory } from "@app/keystore/keystore"; import { getConfig } from "@app/lib/config/env"; import { request } from "@app/lib/config/request"; +import { crypto } from "@app/lib/crypto/cryptography"; import { logger } from "@app/lib/logger"; import { PostHogEventTypes, TPostHogEvent, TSecretModifiedEvent } from "./telemetry-types"; @@ -42,7 +42,7 @@ export type TTelemetryServiceFactoryDep = { const getBucketForDistinctId = (distinctId: string): string => { // Use SHA-256 hash for consistent distribution - const hash = createHash("sha256").update(distinctId).digest("hex"); + const hash = crypto.rawCrypto.createHash("sha256").update(distinctId).digest("hex"); // Take first 8 characters and convert to number for better distribution const hashNumber = parseInt(hash.substring(0, 8), 16); @@ -53,7 +53,7 @@ const getBucketForDistinctId = (distinctId: string): string => { export const createTelemetryEventKey = (event: string, distinctId: string): string => { const bucketId = getBucketForDistinctId(distinctId); - return `telemetry-event-${event}-${bucketId}-${distinctId}-${randomUUID()}`; + return `telemetry-event-${event}-${bucketId}-${distinctId}-${crypto.rawCrypto.randomUUID()}`; }; export const telemetryServiceFactory = ({ keyStore, licenseService }: TTelemetryServiceFactoryDep) => { diff --git a/backend/src/services/totp/totp-fns.ts b/backend/src/services/totp/totp-fns.ts index 9e9aae52c5..acd40e02de 100644 --- a/backend/src/services/totp/totp-fns.ts +++ b/backend/src/services/totp/totp-fns.ts @@ -1,3 +1,3 @@ -import crypto from "node:crypto"; +import { crypto } from "@app/lib/crypto/cryptography"; export const generateRecoveryCode = () => String(crypto.randomInt(10 ** 7, 10 ** 8 - 1)); diff --git a/backend/src/services/user/user-service.ts b/backend/src/services/user/user-service.ts index 07d55f787e..d4e392be1e 100644 --- a/backend/src/services/user/user-service.ts +++ b/backend/src/services/user/user-service.ts @@ -3,7 +3,7 @@ import { ForbiddenError } from "@casl/ability"; import { SecretKeyEncoding } from "@app/db/schemas"; import { OrgPermissionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types"; -import { infisicalSymmetricDecrypt } from "@app/lib/crypto/encryption"; +import { crypto } from "@app/lib/crypto/cryptography"; import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors"; import { logger } from "@app/lib/logger"; import { TAuthTokenServiceFactory } from "@app/services/auth-token/auth-token-service"; @@ -217,7 +217,8 @@ export const userServiceFactory = ({ if (!user?.serverEncryptedPrivateKey || !user.serverEncryptedPrivateKeyIV || !user.serverEncryptedPrivateKeyTag) { throw new NotFoundError({ message: `Private key for user with ID '${userId}' not found` }); } - const privateKey = infisicalSymmetricDecrypt({ + + const privateKey = crypto.encryption().decryptWithRootEncryptionKey({ ciphertext: user.serverEncryptedPrivateKey, tag: user.serverEncryptedPrivateKeyTag, iv: user.serverEncryptedPrivateKeyIV, diff --git a/backend/src/services/webhook/webhook-fns.ts b/backend/src/services/webhook/webhook-fns.ts index d5fc9f5b86..9aaf9b525b 100644 --- a/backend/src/services/webhook/webhook-fns.ts +++ b/backend/src/services/webhook/webhook-fns.ts @@ -1,11 +1,10 @@ -import crypto from "node:crypto"; - import { AxiosError } from "axios"; import picomatch from "picomatch"; import { TWebhooks } from "@app/db/schemas"; import { EventType, TAuditLogServiceFactory, WebhookTriggeredEvent } from "@app/ee/services/audit-log/audit-log-types"; import { request } from "@app/lib/config/request"; +import { crypto } from "@app/lib/crypto/cryptography"; import { NotFoundError } from "@app/lib/errors"; import { logger } from "@app/lib/logger"; import { ActorType } from "@app/services/auth/auth-type"; @@ -41,9 +40,8 @@ export const triggerWebhookRequest = async ( const headers: Record = {}; const payload = { ...data, timestamp: Date.now() }; const { secretKey, url } = decryptWebhookDetails(webhook, decryptor); - if (secretKey) { - const webhookSign = crypto.createHmac("sha256", secretKey).update(JSON.stringify(payload)).digest("hex"); + const webhookSign = crypto.rawCrypto.createHmac("sha256", secretKey).update(JSON.stringify(payload)).digest("hex"); headers["x-infisical-signature"] = `t=${payload.timestamp};${webhookSign}`; } diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index 209c6e62ee..00dc19a46b 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -60,7 +60,7 @@ services: container_name: infisical-dev-api build: context: ./backend - dockerfile: Dockerfile.dev + dockerfile: Dockerfile.dev.fips depends_on: db: condition: service_started diff --git a/frontend/src/components/auth/UserInfoStep.tsx b/frontend/src/components/auth/UserInfoStep.tsx index 582c2a850a..2a1f8dca1d 100644 --- a/frontend/src/components/auth/UserInfoStep.tsx +++ b/frontend/src/components/auth/UserInfoStep.tsx @@ -5,9 +5,8 @@ import { useTranslation } from "react-i18next"; import { faXmark } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import jsrp from "jsrp"; -import nacl from "tweetnacl"; -import { encodeBase64 } from "tweetnacl-util"; +import { useServerConfig } from "@app/context"; import { initProjectHelper } from "@app/helpers/project"; import { completeAccountSignup, useSelectOrganization } from "@app/hooks/api/auth/queries"; import { fetchOrganizations } from "@app/hooks/api/organization/queries"; @@ -16,7 +15,7 @@ import { onRequestError } from "@app/hooks/api/reactQuery"; import InputField from "../basic/InputField"; import checkPassword from "../utilities/checks/password/checkPassword"; import Aes256Gcm from "../utilities/cryptography/aes-256-gcm"; -import { deriveArgonKey } from "../utilities/cryptography/crypto"; +import { deriveArgonKey, generateKeyPair } from "../utilities/cryptography/crypto"; import { saveTokenToLocalStorage } from "../utilities/saveTokenToLocalStorage"; import SecurityClient from "../utilities/SecurityClient"; import { Button, Input } from "../v2"; @@ -77,6 +76,7 @@ export default function UserInfoStep({ }: UserInfoStepProps): JSX.Element { const [nameError, setNameError] = useState(false); const [organizationNameError, setOrganizationNameError] = useState(false); + const { config } = useServerConfig(); const [errors, setErrors] = useState({}); @@ -109,12 +109,9 @@ export default function UserInfoStep({ if (!errorCheck) { // Generate a random pair of a public and a private key - const pair = nacl.box.keyPair(); - const secretKeyUint8Array = pair.secretKey; - const publicKeyUint8Array = pair.publicKey; - const privateKey = encodeBase64(secretKeyUint8Array); - const publicKey = encodeBase64(publicKeyUint8Array); - localStorage.setItem("PRIVATE_KEY", privateKey); + const pair = await generateKeyPair(config.fipsEnabled); + + localStorage.setItem("PRIVATE_KEY", pair.privateKey); client.init( { @@ -145,7 +142,7 @@ export default function UserInfoStep({ iv: encryptedPrivateKeyIV, tag: encryptedPrivateKeyTag } = Aes256Gcm.encrypt({ - text: privateKey, + text: pair.privateKey, secret: key }); @@ -168,7 +165,7 @@ export default function UserInfoStep({ protectedKey, protectedKeyIV, protectedKeyTag, - publicKey, + publicKey: pair.publicKey, encryptedPrivateKey, encryptedPrivateKeyIV, encryptedPrivateKeyTag, @@ -189,11 +186,11 @@ export default function UserInfoStep({ } saveTokenToLocalStorage({ - publicKey, + publicKey: pair.publicKey, encryptedPrivateKey, iv: encryptedPrivateKeyIV, tag: encryptedPrivateKeyTag, - privateKey + privateKey: pair.privateKey }); const userOrgs = await fetchOrganizations(); diff --git a/frontend/src/components/utilities/cryptography/crypto.ts b/frontend/src/components/utilities/cryptography/crypto.ts index 82bac0ad55..57c239e449 100644 --- a/frontend/src/components/utilities/cryptography/crypto.ts +++ b/frontend/src/components/utilities/cryptography/crypto.ts @@ -2,48 +2,55 @@ // eslint-disable-next-line import argon2 from "argon2-browser/dist/argon2-bundled.min.js"; import nacl from "tweetnacl"; -import { decodeBase64, decodeUTF8, encodeBase64, encodeUTF8 } from "tweetnacl-util"; - +import { encodeBase64 as naclEncodeBase64 } from "tweetnacl-util"; import aes from "./aes-256-gcm"; -/** - * Return new base64, NaCl, public-private key pair. - * @returns {Object} obj - * @returns {String} obj.publicKey - base64, NaCl, public key - * @returns {String} obj.privateKey - base64, NaCl, private key - */ -const generateKeyPair = () => { +const encodeBase64 = (uint8Array: Uint8Array) => btoa(String.fromCharCode(...uint8Array)); +const decodeBase64 = (base64String: string) => + new Uint8Array([...atob(base64String)].map((c) => c.charCodeAt(0))); + +const generateKeyPair = async (fipsEnabled: boolean) => { + if (fipsEnabled) { + if (!crypto || !crypto.subtle) { + throw new Error("Web Crypto API not available"); + } + + // browser version of how the key format we expect on the backend for asymmetric encryption + const result = await crypto.subtle.generateKey( + { + name: "X25519" + }, + true, // extractable + ["deriveKey", "deriveBits"] + ); + + // Type guard + if (!("publicKey" in result)) { + throw new Error("Expected CryptoKeyPair but got CryptoKey"); + } + + const keyPair = result as CryptoKeyPair; + const publicKeyBytes = await crypto.subtle.exportKey("spki", keyPair.publicKey); + const privateKeyBytes = await crypto.subtle.exportKey("pkcs8", keyPair.privateKey); + + return { + publicKey: btoa(String.fromCharCode(...new Uint8Array(publicKeyBytes))), + privateKey: btoa(String.fromCharCode(...new Uint8Array(privateKeyBytes))) + }; + } + const pair = nacl.box.keyPair(); + const secretKeyUint8Array = pair.secretKey; + const publicKeyUint8Array = pair.publicKey; + const privateKey = naclEncodeBase64(secretKeyUint8Array); + const publicKey = naclEncodeBase64(publicKeyUint8Array); return { - publicKey: encodeBase64(pair.publicKey), - privateKey: encodeBase64(pair.secretKey) + publicKey, + privateKey }; }; -type EncryptAsymmetricProps = { - plaintext: string; - publicKey: string; - privateKey: string; -}; - -/** - * Verify that private key [privateKey] is the one that corresponds to - * the public key [publicKey] - * @param {Object} - * @param {String} - base64-encoded Nacl private key - * @param {String} - base64-encoded Nacl public key - */ -const verifyPrivateKey = ({ privateKey, publicKey }: { privateKey: string; publicKey: string }) => { - const derivedPublicKey = encodeBase64( - nacl.box.keyPair.fromSecretKey(decodeBase64(privateKey)).publicKey - ); - - if (derivedPublicKey !== publicKey) { - throw new Error("Failed to verify private key"); - } -}; - /** * Derive a key from password [password] and salt [salt] using Argon2id * @param {Object} obj @@ -88,71 +95,6 @@ const deriveArgonKey = async ({ return derivedKey; }; -/** - * Return assymmetrically encrypted [plaintext] using [publicKey] where - * [publicKey] likely belongs to the recipient. - * @param {Object} obj - * @param {String} obj.plaintext - plaintext to encrypt - * @param {String} obj.publicKey - public key of the recipient - * @param {String} obj.privateKey - private key of the sender (current user) - * @returns {Object} obj - * @returns {String} ciphertext - base64-encoded ciphertext - * @returns {String} nonce - base64-encoded nonce - */ -const encryptAssymmetric = ({ - plaintext, - publicKey, - privateKey -}: EncryptAsymmetricProps): { - ciphertext: string; - nonce: string; -} => { - const nonce = nacl.randomBytes(24); - const ciphertext = nacl.box( - decodeUTF8(plaintext), - nonce, - decodeBase64(publicKey), - decodeBase64(privateKey) - ); - - return { - ciphertext: encodeBase64(ciphertext), - nonce: encodeBase64(nonce) - }; -}; - -type DecryptAsymmetricProps = { - ciphertext: string; - nonce: string; - publicKey: string; - privateKey: string; -}; - -/** - * Return assymmetrically decrypted [ciphertext] using [privateKey] where - * [privateKey] likely belongs to the recipient. - * @param {Object} obj - * @param {String} obj.ciphertext - ciphertext to decrypt - * @param {String} obj.nonce - nonce - * @param {String} obj.publicKey - base64-encoded public key of the sender - * @param {String} obj.privateKey - base64-encoded private key of the receiver (current user) - */ -const decryptAssymmetric = ({ - ciphertext, - nonce, - publicKey, - privateKey -}: DecryptAsymmetricProps): string => { - const plaintext = nacl.box.open( - decodeBase64(ciphertext), - decodeBase64(nonce), - decodeBase64(publicKey), - decodeBase64(privateKey) - ); - - return encodeUTF8(plaintext!); -}; - type EncryptSymmetricProps = { plaintext: string; key: string; @@ -226,11 +168,10 @@ const decryptSymmetric = ({ ciphertext, iv, tag, key }: DecryptSymmetricProps): }; export { - decryptAssymmetric, + decodeBase64, decryptSymmetric, deriveArgonKey, - encryptAssymmetric, + encodeBase64, encryptSymmetric, - generateKeyPair, - verifyPrivateKey + generateKeyPair }; diff --git a/frontend/src/hooks/api/admin/types.ts b/frontend/src/hooks/api/admin/types.ts index c5d92b9da2..c1aa1f5394 100644 --- a/frontend/src/hooks/api/admin/types.ts +++ b/frontend/src/hooks/api/admin/types.ts @@ -48,6 +48,7 @@ export type TServerConfig = { authConsentContent?: string; pageFrameContent?: string; invalidatingCache: boolean; + fipsEnabled: boolean; }; export type TUpdateServerConfigDTO = { diff --git a/frontend/src/hooks/api/cmeks/mutations.tsx b/frontend/src/hooks/api/cmeks/mutations.tsx index b806e2b440..4b6248d696 100644 --- a/frontend/src/hooks/api/cmeks/mutations.tsx +++ b/frontend/src/hooks/api/cmeks/mutations.tsx @@ -1,6 +1,6 @@ import { useMutation, useQueryClient } from "@tanstack/react-query"; -import { encodeBase64 } from "tweetnacl-util"; +import { encodeBase64 } from "@app/components/utilities/cryptography/crypto"; import { apiRequest } from "@app/config/request"; import { cmekKeys } from "@app/hooks/api/cmeks/queries"; import { @@ -66,6 +66,7 @@ export const useDeleteCmek = () => { export const useCmekEncrypt = () => { return useMutation({ mutationFn: async ({ keyId, plaintext, isBase64Encoded }: TCmekEncrypt) => { + console.log("CALLING CMEK ENCRYPT"); const { data } = await apiRequest.post( `/api/v1/kms/keys/${keyId}/encrypt`, { diff --git a/frontend/src/hooks/api/secretApprovalRequest/queries.tsx b/frontend/src/hooks/api/secretApprovalRequest/queries.tsx index e96dcf34f6..6ad68bb939 100644 --- a/frontend/src/hooks/api/secretApprovalRequest/queries.tsx +++ b/frontend/src/hooks/api/secretApprovalRequest/queries.tsx @@ -1,15 +1,9 @@ /* eslint-disable no-param-reassign */ import { useQuery, UseQueryOptions } from "@tanstack/react-query"; -import { - decryptAssymmetric, - decryptSymmetric -} from "@app/components/utilities/cryptography/crypto"; import { apiRequest } from "@app/config/request"; import { TReactQueryOptions } from "@app/types/reactQuery"; -import { UserWsKeyPair } from "../keys/types"; -import { EncryptedSecret, SecretType, SecretV3RawSanitized } from "../secrets/types"; import { TGetSecretApprovalRequestCount, TGetSecretApprovalRequestDetails, @@ -40,79 +34,6 @@ export const secretApprovalRequestKeys = { ] }; -export const decryptSecrets = ( - encryptedSecrets: EncryptedSecret[], - decryptFileKey: UserWsKeyPair -) => { - const PRIVATE_KEY = localStorage.getItem("PRIVATE_KEY") as string; - const key = decryptAssymmetric({ - ciphertext: decryptFileKey.encryptedKey, - nonce: decryptFileKey.nonce, - publicKey: decryptFileKey.sender.publicKey, - privateKey: PRIVATE_KEY - }); - - const personalSecrets: Record = {}; - const secrets: SecretV3RawSanitized[] = []; - encryptedSecrets.forEach((encSecret) => { - const secretKey = decryptSymmetric({ - ciphertext: encSecret.secretKeyCiphertext, - iv: encSecret.secretKeyIV, - tag: encSecret.secretKeyTag, - key - }); - - const secretValue = decryptSymmetric({ - ciphertext: encSecret.secretValueCiphertext, - iv: encSecret.secretValueIV, - tag: encSecret.secretValueTag, - key - }); - - const secretComment = decryptSymmetric({ - ciphertext: encSecret.secretCommentCiphertext, - iv: encSecret.secretCommentIV, - tag: encSecret.secretCommentTag, - key - }); - - const decryptedSecret: SecretV3RawSanitized = { - id: encSecret.id, - env: encSecret.environment, - key: secretKey, - secretValueHidden: encSecret.secretValueHidden, - value: secretValue, - tags: encSecret.tags, - comment: secretComment, - reminderRepeatDays: encSecret.secretReminderRepeatDays, - reminderNote: encSecret.secretReminderNote, - createdAt: encSecret.createdAt, - updatedAt: encSecret.updatedAt, - version: encSecret.version, - skipMultilineEncoding: encSecret.skipMultilineEncoding - }; - - if (encSecret.type === SecretType.Personal) { - personalSecrets[decryptedSecret.key] = { - id: encSecret.id, - value: secretValue - }; - } else { - secrets.push(decryptedSecret); - } - }); - - secrets.forEach((sec) => { - if (personalSecrets?.[sec.key]) { - sec.idOverride = personalSecrets[sec.key].id; - sec.valueOverride = personalSecrets[sec.key].value; - sec.overrideAction = "modified"; - } - }); - - return secrets; -}; - const fetchSecretApprovalRequestList = async ({ workspaceId, environment, diff --git a/frontend/src/hooks/api/users/index.tsx b/frontend/src/hooks/api/users/index.tsx index 715c3532d8..d20a15bc81 100644 --- a/frontend/src/hooks/api/users/index.tsx +++ b/frontend/src/hooks/api/users/index.tsx @@ -1,5 +1,4 @@ export { - useAddUserToWsE2EE, useAddUserToWsNonE2EE, useRemoveMyDuplicateAccounts, useRevokeMySessionById, diff --git a/frontend/src/hooks/api/users/mutation.tsx b/frontend/src/hooks/api/users/mutation.tsx index ee274ab318..84bd05e074 100644 --- a/frontend/src/hooks/api/users/mutation.tsx +++ b/frontend/src/hooks/api/users/mutation.tsx @@ -1,51 +1,10 @@ import { useMutation, useQueryClient } from "@tanstack/react-query"; -import { - decryptAssymmetric, - encryptAssymmetric -} from "@app/components/utilities/cryptography/crypto"; import { apiRequest } from "@app/config/request"; import { workspaceKeys } from "../workspace"; import { userKeys } from "./query-keys"; -import { AddUserToWsDTOE2EE, AddUserToWsDTONonE2EE } from "./types"; - -export const useAddUserToWsE2EE = () => { - const queryClient = useQueryClient(); - - return useMutation({ - mutationFn: async ({ workspaceId, members, decryptKey, userPrivateKey }) => { - // assymmetrically decrypt symmetric key with local private key - const key = decryptAssymmetric({ - ciphertext: decryptKey.encryptedKey, - nonce: decryptKey.nonce, - publicKey: decryptKey.sender.publicKey, - privateKey: userPrivateKey - }); - - const newWsMembers = members.map(({ orgMembershipId, userPublicKey }) => { - const { ciphertext: inviteeCipherText, nonce: inviteeNonce } = encryptAssymmetric({ - plaintext: key, - publicKey: userPublicKey, - privateKey: userPrivateKey - }); - - return { - orgMembershipId, - workspaceEncryptedKey: inviteeCipherText, - workspaceEncryptedNonce: inviteeNonce - }; - }); - const { data } = await apiRequest.post(`/api/v1/workspace/${workspaceId}/memberships`, { - members: newWsMembers - }); - return data; - }, - onSuccess: (_, { workspaceId }) => { - queryClient.invalidateQueries({ queryKey: workspaceKeys.getWorkspaceUsers(workspaceId) }); - } - }); -}; +import { AddUserToWsDTONonE2EE } from "./types"; export const useAddUserToWsNonE2EE = () => { const queryClient = useQueryClient(); diff --git a/frontend/src/hooks/api/users/types.ts b/frontend/src/hooks/api/users/types.ts index 319fb723d8..544acc225a 100644 --- a/frontend/src/hooks/api/users/types.ts +++ b/frontend/src/hooks/api/users/types.ts @@ -129,16 +129,6 @@ export type TWorkspaceUser = { deniedPermissions: any[]; }; -export type AddUserToWsDTOE2EE = { - workspaceId: string; - decryptKey: UserWsKeyPair; - userPrivateKey: string; - members: { - orgMembershipId: string; - userPublicKey: string; - }[]; -}; - export type AddUserToWsDTONonE2EE = { projectId: string; usernames: string[]; diff --git a/frontend/src/lib/crypto/index.ts b/frontend/src/lib/crypto/index.ts index d80dc086c2..d0d6cd5dd2 100644 --- a/frontend/src/lib/crypto/index.ts +++ b/frontend/src/lib/crypto/index.ts @@ -1,11 +1,9 @@ import crypto from "crypto"; import jsrp from "jsrp"; -import nacl from "tweetnacl"; -import { encodeBase64 } from "tweetnacl-util"; import Aes256Gcm from "@app/components/utilities/cryptography/aes-256-gcm"; -import { deriveArgonKey } from "@app/components/utilities/cryptography/crypto"; +import { deriveArgonKey, generateKeyPair } from "@app/components/utilities/cryptography/crypto"; import { issueBackupPrivateKey, srp1 } from "@app/hooks/api/auth/queries"; export const generateUserBackupKey = async (email: string, password: string) => { @@ -54,15 +52,15 @@ export const generateUserBackupKey = async (email: string, password: string) => return generatedKey; }; -export const generateUserPassKey = async (email: string, password: string) => { +export const generateUserPassKey = async ( + email: string, + password: string, + fipsEnabled: boolean +) => { // eslint-disable-next-line new-cap const client = new jsrp.client(); - const pair = nacl.box.keyPair(); - const secretKeyUint8Array = pair.secretKey; - const publicKeyUint8Array = pair.publicKey; - const privateKey = encodeBase64(secretKeyUint8Array); - const publicKey = encodeBase64(publicKeyUint8Array); + const { publicKey, privateKey } = await generateKeyPair(fipsEnabled); await new Promise((resolve) => { client.init({ username: email, password }, () => resolve(null)); diff --git a/frontend/src/pages/admin/SignUpPage/SignUpPage.tsx b/frontend/src/pages/admin/SignUpPage/SignUpPage.tsx index 97b363558d..537da25d06 100644 --- a/frontend/src/pages/admin/SignUpPage/SignUpPage.tsx +++ b/frontend/src/pages/admin/SignUpPage/SignUpPage.tsx @@ -49,7 +49,20 @@ export const SignUpPage = () => { // avoid multi submission if (isSubmitting) return; try { - const { privateKey, ...userPass } = await generateUserPassKey(email, password); + console.log( + "Creating admin user...", + JSON.stringify({ + email, + password, + fipsEnabled: config.fipsEnabled + }) + ); + + const { privateKey, ...userPass } = await generateUserPassKey( + email, + password, + config.fipsEnabled + ); const res = await createAdminUser({ email, password, diff --git a/frontend/src/pages/auth/SignUpInvitePage/SignUpInvitePage.tsx b/frontend/src/pages/auth/SignUpInvitePage/SignUpInvitePage.tsx index fe27e5d7d2..62806a804a 100644 --- a/frontend/src/pages/auth/SignUpInvitePage/SignUpInvitePage.tsx +++ b/frontend/src/pages/auth/SignUpInvitePage/SignUpInvitePage.tsx @@ -8,17 +8,16 @@ import { faXmark } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { Link, useNavigate, useSearch } from "@tanstack/react-router"; import jsrp from "jsrp"; -import nacl from "tweetnacl"; -import { encodeBase64 } from "tweetnacl-util"; import { Mfa } from "@app/components/auth/Mfa"; import InputField from "@app/components/basic/InputField"; import checkPassword from "@app/components/utilities/checks/password/checkPassword"; import Aes256Gcm from "@app/components/utilities/cryptography/aes-256-gcm"; -import { deriveArgonKey } from "@app/components/utilities/cryptography/crypto"; +import { deriveArgonKey, generateKeyPair } from "@app/components/utilities/cryptography/crypto"; import { saveTokenToLocalStorage } from "@app/components/utilities/saveTokenToLocalStorage"; import SecurityClient from "@app/components/utilities/SecurityClient"; import { Button } from "@app/components/v2"; +import { useServerConfig } from "@app/context"; import { useToggle } from "@app/hooks"; import { completeAccountSignupInvite, @@ -69,6 +68,7 @@ export const SignupInvitePage = () => { const metadata = queryParams.get("metadata") || undefined; const { mutateAsync: selectOrganization } = useSelectOrganization(); + const { config } = useServerConfig(); const loggedIn = isLoggedIn(); @@ -96,11 +96,7 @@ export const SignupInvitePage = () => { if (!errorCheck) { // Generate a random pair of a public and a private key - const pair = nacl.box.keyPair(); - const secretKeyUint8Array = pair.secretKey; - const publicKeyUint8Array = pair.publicKey; - const privateKey = encodeBase64(secretKeyUint8Array); - const publicKey = encodeBase64(publicKeyUint8Array); + const { publicKey, privateKey } = await generateKeyPair(config.fipsEnabled); localStorage.setItem("PRIVATE_KEY", privateKey); diff --git a/frontend/src/pages/auth/SignUpSsoPage/components/UserInfoSSOStep/UserInfoSSOStep.tsx b/frontend/src/pages/auth/SignUpSsoPage/components/UserInfoSSOStep/UserInfoSSOStep.tsx index 2f2ade8e74..30c149ec01 100644 --- a/frontend/src/pages/auth/SignUpSsoPage/components/UserInfoSSOStep/UserInfoSSOStep.tsx +++ b/frontend/src/pages/auth/SignUpSsoPage/components/UserInfoSSOStep/UserInfoSSOStep.tsx @@ -4,15 +4,14 @@ import { useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; import { useNavigate } from "@tanstack/react-router"; import jsrp from "jsrp"; -import nacl from "tweetnacl"; -import { encodeBase64 } from "tweetnacl-util"; import { Mfa } from "@app/components/auth/Mfa"; import Aes256Gcm from "@app/components/utilities/cryptography/aes-256-gcm"; -import { deriveArgonKey } from "@app/components/utilities/cryptography/crypto"; +import { deriveArgonKey, generateKeyPair } from "@app/components/utilities/cryptography/crypto"; import { saveTokenToLocalStorage } from "@app/components/utilities/saveTokenToLocalStorage"; import SecurityClient from "@app/components/utilities/SecurityClient"; import { Button, Input } from "@app/components/v2"; +import { useServerConfig } from "@app/context"; import { initProjectHelper } from "@app/helpers/project"; import { useToggle } from "@app/hooks"; import { completeAccountSignup, useSelectOrganization } from "@app/hooks/api/auth/queries"; @@ -66,6 +65,7 @@ export const UserInfoSSOStep = ({ const { mutateAsync: selectOrganization } = useSelectOrganization(); const [mfaSuccessCallback, setMfaSuccessCallback] = useState<() => void>(() => {}); const navigate = useNavigate(); + const { config } = useServerConfig(); useEffect(() => { const randomPassword = crypto.randomBytes(32).toString("hex"); @@ -95,12 +95,7 @@ export const UserInfoSSOStep = ({ if (!errorCheck) { // Generate a random pair of a public and a private key - const pair = nacl.box.keyPair(); - const secretKeyUint8Array = pair.secretKey; - const publicKeyUint8Array = pair.publicKey; - const privateKey = encodeBase64(secretKeyUint8Array); - const publicKey = encodeBase64(publicKeyUint8Array); - localStorage.setItem("PRIVATE_KEY", privateKey); + const { publicKey, privateKey } = await generateKeyPair(config.fipsEnabled); client.init( { diff --git a/frontend/src/pages/cert-manager/CertAuthDetailsByIDPage/components/CaCertificatesSection/CaCertificatesTable.tsx b/frontend/src/pages/cert-manager/CertAuthDetailsByIDPage/components/CaCertificatesSection/CaCertificatesTable.tsx index e0d7c17409..e9f65c4f6d 100644 --- a/frontend/src/pages/cert-manager/CertAuthDetailsByIDPage/components/CaCertificatesSection/CaCertificatesTable.tsx +++ b/frontend/src/pages/cert-manager/CertAuthDetailsByIDPage/components/CaCertificatesSection/CaCertificatesTable.tsx @@ -51,7 +51,7 @@ export const CaCertificatesTable = ({ caId }: Props) => { {isPending && } {!isPending && - caCerts?.map((caCert, index) => { + caCerts?.map?.((caCert, index) => { const isLastItem = index === caCerts.length - 1; const caCertObj = new x509.X509Certificate(caCert.certificate); return ( diff --git a/frontend/src/pages/kms/OverviewPage/components/CmekDecryptModal.tsx b/frontend/src/pages/kms/OverviewPage/components/CmekDecryptModal.tsx index a413249056..9d6c2f2b01 100644 --- a/frontend/src/pages/kms/OverviewPage/components/CmekDecryptModal.tsx +++ b/frontend/src/pages/kms/OverviewPage/components/CmekDecryptModal.tsx @@ -3,10 +3,10 @@ import { useForm } from "react-hook-form"; import { faCheckCircle, faCopy, faInfoCircle, faLockOpen } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { zodResolver } from "@hookform/resolvers/zod"; -import { decodeBase64 } from "tweetnacl-util"; import { z } from "zod"; import { createNotification } from "@app/components/notifications"; +import { decodeBase64 } from "@app/components/utilities/cryptography/crypto"; import { Button, FormControl, diff --git a/frontend/src/pages/kms/OverviewPage/components/CmekVerifyModal.tsx b/frontend/src/pages/kms/OverviewPage/components/CmekVerifyModal.tsx index 95832fbb7b..4525b8a4a2 100644 --- a/frontend/src/pages/kms/OverviewPage/components/CmekVerifyModal.tsx +++ b/frontend/src/pages/kms/OverviewPage/components/CmekVerifyModal.tsx @@ -2,10 +2,10 @@ import { Controller, useForm } from "react-hook-form"; import { faFileSignature, faInfoCircle } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { zodResolver } from "@hookform/resolvers/zod"; -import { decodeBase64 } from "tweetnacl-util"; import { z } from "zod"; import { createNotification } from "@app/components/notifications"; +import { decodeBase64 } from "@app/components/utilities/cryptography/crypto"; import { Badge, Button, diff --git a/frontend/src/pages/secret-manager/IntegrationsListPage/IntegrationsListPage.utils.tsx b/frontend/src/pages/secret-manager/IntegrationsListPage/IntegrationsListPage.utils.tsx index 03f0bf4021..0b55214382 100644 --- a/frontend/src/pages/secret-manager/IntegrationsListPage/IntegrationsListPage.utils.tsx +++ b/frontend/src/pages/secret-manager/IntegrationsListPage/IntegrationsListPage.utils.tsx @@ -3,35 +3,8 @@ import crypto from "crypto"; import { NavigateFn } from "@tanstack/react-router"; import { createNotification } from "@app/components/notifications"; -import { - decryptAssymmetric, - encryptAssymmetric -} from "@app/components/utilities/cryptography/crypto"; import { localStorageService } from "@app/helpers/localStorage"; -import { TCloudIntegration, UserWsKeyPair } from "@app/hooks/api/types"; - -export const generateBotKey = (botPublicKey: string, latestKey: UserWsKeyPair) => { - const PRIVATE_KEY = localStorage.getItem("PRIVATE_KEY"); - - if (!PRIVATE_KEY) { - throw new Error("Private Key missing"); - } - - const WORKSPACE_KEY = decryptAssymmetric({ - ciphertext: latestKey.encryptedKey, - nonce: latestKey.nonce, - publicKey: latestKey.sender.publicKey, - privateKey: PRIVATE_KEY - }); - - const { ciphertext, nonce } = encryptAssymmetric({ - plaintext: WORKSPACE_KEY, - publicKey: botPublicKey, - privateKey: PRIVATE_KEY - }); - - return { encryptedKey: ciphertext, nonce }; -}; +import { TCloudIntegration } from "@app/hooks/api/types"; export const createIntegrationMissingEnvVarsNotification = ( slug: string, diff --git a/frontend/src/pages/secret-manager/SettingsPage/components/ProjectGeneralTab/ProjectGeneralTab.tsx b/frontend/src/pages/secret-manager/SettingsPage/components/ProjectGeneralTab/ProjectGeneralTab.tsx index 2843300e8f..7cc0e227e0 100644 --- a/frontend/src/pages/secret-manager/SettingsPage/components/ProjectGeneralTab/ProjectGeneralTab.tsx +++ b/frontend/src/pages/secret-manager/SettingsPage/components/ProjectGeneralTab/ProjectGeneralTab.tsx @@ -1,6 +1,6 @@ import { ProjectOverviewChangeSection } from "@app/components/project/ProjectOverviewChangeSection"; import { useWorkspace } from "@app/context"; -import { ProjectType, ProjectVersion } from "@app/hooks/api/workspace/types"; +import { ProjectType } from "@app/hooks/api/workspace/types"; import { AuditLogsRetentionSection } from "../AuditLogsRetentionSection"; import { AutoCapitalizationSection } from "../AutoCapitalizationSection"; @@ -9,7 +9,6 @@ import { DeleteProjectProtection } from "../DeleteProjectProtection"; import { DeleteProjectSection } from "../DeleteProjectSection"; import { EnvironmentSection } from "../EnvironmentSection"; import { PointInTimeVersionLimitSection } from "../PointInTimeVersionLimitSection"; -import { RebuildSecretIndicesSection } from "../RebuildSecretIndicesSection/RebuildSecretIndicesSection"; import { SecretSharingSection } from "../SecretSharingSection"; import { SecretSnapshotsLegacySection } from "../SecretSnapshotsLegacySection"; import { SecretTagsSection } from "../SecretTagsSection"; @@ -29,9 +28,6 @@ export const ProjectGeneralTab = () => { {isSecretManager && } {isSecretManager && } - {currentWorkspace?.version !== ProjectVersion.V3 && isSecretManager && ( - - )} diff --git a/frontend/src/pages/secret-manager/SettingsPage/components/RebuildSecretIndicesSection/RebuildSecretIndicesSection.tsx b/frontend/src/pages/secret-manager/SettingsPage/components/RebuildSecretIndicesSection/RebuildSecretIndicesSection.tsx deleted file mode 100644 index c32695abb3..0000000000 --- a/frontend/src/pages/secret-manager/SettingsPage/components/RebuildSecretIndicesSection/RebuildSecretIndicesSection.tsx +++ /dev/null @@ -1,93 +0,0 @@ -import { createNotification } from "@app/components/notifications"; -import { - decryptAssymmetric, - decryptSymmetric -} from "@app/components/utilities/cryptography/crypto"; -import { Button } from "@app/components/v2"; -import { useProjectPermission, useWorkspace } from "@app/context"; -import { useToggle } from "@app/hooks"; -import { useGetUserWsKey, useNameWorkspaceSecrets } from "@app/hooks/api"; -import { ProjectMembershipRole } from "@app/hooks/api/roles/types"; -import { fetchWorkspaceSecrets } from "@app/hooks/api/workspace/queries"; - -export const RebuildSecretIndicesSection = () => { - const { currentWorkspace } = useWorkspace(); - const { membership } = useProjectPermission(); - const nameWorkspaceSecrets = useNameWorkspaceSecrets(); - - const [isIndexing, setIsIndexing] = useToggle(); - const { data: decryptFileKey } = useGetUserWsKey(currentWorkspace.id); - - if (!currentWorkspace) return null; - - const onRebuildIndices = async () => { - if (!currentWorkspace?.id) return; - setIsIndexing.on(); - try { - const encryptedSecrets = await fetchWorkspaceSecrets(currentWorkspace.id); - - if (!currentWorkspace || !decryptFileKey) { - return; - } - - const key = decryptAssymmetric({ - ciphertext: decryptFileKey.encryptedKey, - nonce: decryptFileKey.nonce, - publicKey: decryptFileKey.sender.publicKey, - privateKey: localStorage.getItem("PRIVATE_KEY") as string - }); - - const secretsToUpdate = encryptedSecrets.map((encryptedSecret) => { - const secretName = decryptSymmetric({ - ciphertext: encryptedSecret.secretKeyCiphertext, - iv: encryptedSecret.secretKeyIV, - tag: encryptedSecret.secretKeyTag, - key - }); - - return { - secretName, - secretId: encryptedSecret.id - }; - }); - await nameWorkspaceSecrets.mutateAsync({ - workspaceId: currentWorkspace.id, - secretsToUpdate - }); - - createNotification({ - text: "Successfully rebuilt secret indices", - type: "success" - }); - } catch (err) { - console.log(err); - } finally { - setIsIndexing.off(); - } - }; - - const isAdmin = membership.roles.includes(ProjectMembershipRole.Admin); - - if (!isAdmin) { - return null; - } - - return ( -
-
-

Rebuild Secret Indices

-
-

- This will rebuild indices of all secrets in the project. -

- -
- ); -}; diff --git a/package-lock.json b/package-lock.json index 4d72220afd..ce284b4315 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,9 +8,11 @@ "license": "ISC", "dependencies": { "@radix-ui/react-radio-group": "^1.1.3", + "crypto-js": "^4.2.0", "secrets.js-grempe": "^2.0.0" }, "devDependencies": { + "@types/crypto-js": "^4.2.2", "@types/uuid": "^9.0.7", "eslint": "^8.57.1", "husky": "^8.0.3" @@ -480,6 +482,13 @@ } } }, + "node_modules/@types/crypto-js": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@types/crypto-js/-/crypto-js-4.2.2.tgz", + "integrity": "sha512-sDOLlVbHhXpAUAL0YHDUUwDZf3iN4Bwi4W6a0W0b+QcAezUbRtH4FVb+9J4h+XFPW7l/gQ9F8qC7P+Ec4k8QVQ==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/uuid": { "version": "9.0.7", "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.7.tgz", @@ -645,6 +654,12 @@ "node": ">= 8" } }, + "node_modules/crypto-js": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz", + "integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==", + "license": "MIT" + }, "node_modules/debug": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", @@ -1836,6 +1851,12 @@ "@radix-ui/react-use-layout-effect": "1.0.1" } }, + "@types/crypto-js": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@types/crypto-js/-/crypto-js-4.2.2.tgz", + "integrity": "sha512-sDOLlVbHhXpAUAL0YHDUUwDZf3iN4Bwi4W6a0W0b+QcAezUbRtH4FVb+9J4h+XFPW7l/gQ9F8qC7P+Ec4k8QVQ==", + "dev": true + }, "@types/uuid": { "version": "9.0.7", "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.7.tgz", @@ -1958,6 +1979,11 @@ "which": "^2.0.1" } }, + "crypto-js": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz", + "integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==" + }, "debug": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", diff --git a/package.json b/package.json index db15de3fb7..f30fda33dc 100644 --- a/package.json +++ b/package.json @@ -19,12 +19,14 @@ ] }, "devDependencies": { + "@types/crypto-js": "^4.2.2", "@types/uuid": "^9.0.7", "eslint": "^8.57.1", "husky": "^8.0.3" }, "dependencies": { "@radix-ui/react-radio-group": "^1.1.3", + "crypto-js": "^4.2.0", "secrets.js-grempe": "^2.0.0" } }