From 9aa3c14bf233424c9d5a8eaad8a383a4cf932068 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard Date: Sun, 6 Jul 2025 15:44:07 +0400 Subject: [PATCH 01/27] 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 0afb330e5..a77762324 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 6cc8f6e34..13aaabcd3 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 c035692ed..c99c50684 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 92cf86e66..6e1f0ff49 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 b90e448cb..aa2bc915f 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 128de7bd6..8adc2bbef 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 440e3393f..366046e9c 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 a2d856388..ff2503b32 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 dde1e7188..e01fbb1db 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 e11ef926e..661d92f14 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 934dce5e8..36c70c704 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 011585bda..c0496f823 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 f5107b301..b07fe7787 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 000000000..45d23bf6f --- /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 de4975b20..9df8cc0db 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 47ef15d90..2822a785b 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 b6c80bb63..91a309aa6 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 3798d4bf3..391f785ec 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 e67d037ea..33ebe910d 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 65e49bdea..2c7086e48 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 5cd1f507e..09d666889 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 ec75bb2e4..cb8da4d13 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 d3217be37..c86e3aff5 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 e361ab329..0e60cddb3 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 2bda9c75e..2aba6db8a 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 ffef3e007..118bff9bd 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 72d052b29..af52d2f21 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 f8c52fe56..618e67973 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 49f336111..130cd184f 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 628f8e310..3aa3a1f2a 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 9b2eb7839..ef58687a1 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 d792ac6e6..4ce32e3b2 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 1099e17a7..ee840121a 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 a65bb9e68..85a3cd5f2 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 c9f6dac9d..d8c8c0ead 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 60c966fcd..21da4bc16 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 449c471fd..eeaba83b2 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 8db5c16c4..205ae3ed5 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 9f36d360b..e74a01d8c 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 718c8ad5e..e193c8c3f 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 000000000..8486837cb --- /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 f495681f1..d11c4ac97 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 cc6acfb80..aac797a3e 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 2e0492560..7355d4bd6 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 66f36dc0f..1864d4be5 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 36c858715..a97fe8020 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 e6afd0f99..5c204c033 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 063d71d09..68c8acd60 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 46481a049..711fee4a0 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 e1cc4f587..64a48fd0c 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 37dcaa78b..a41624b06 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 d141b62d5..15dd0322b 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 a8d0d10a6..da841f1cc 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 3f5c477ef..321a3656e 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 aeef89838..8aeeffdd3 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 a21587db1..8715a348f 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 40060ad40..cecd7f2db 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 96fb90026..b928bbd6d 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 78f6b99b5..2a4d59332 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 bcadd9636..487a40e10 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 28660173b..2bcf75937 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 f26464340..507095248 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 689228941..a70fa5cc2 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 5e2f8c7b3..bd8a9a23d 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 7e11f25cb..48f5a434e 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 8e0372953..75f95c38a 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 02be76565..456d81d93 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 74970bf0c..32471a0e3 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 def2e2bed..13084c5a8 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 98d9491f7..cbc32c55a 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 4fa4e8283..20c061bf7 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 ffdaec3b4..652974584 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 5558ccd65..36842f324 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 018d7bd43..08ee02a2b 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 47d8950cc..29a7787be 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 11dd312ad..e2c2e4cb8 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 eaae0150b..c159f7364 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 2517c040c..ad8d43526 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 f77becb02..a4923e347 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 989a5a88c..072313693 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 196c18356..850635f5d 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 cb161c7e5..81afeedea 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 1215b7c00..a74612803 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 a8e507bc7..42db24d5b 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 7dc5b058e..a5a7d47b3 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 08652e348..306cba50a 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 e845ebd35..ed6241bc7 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 29774fcc8..b649d8e4c 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 e879d56f1..4cbfcdc7f 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 847ef17df..6f6358779 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 25b6e6572..731eb6951 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 159bee8e3..5fa58ce90 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 d68b48d78..030276d16 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 8a0d4dd14..5c1b8f6f6 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 eaab5bec6..0c70a1d91 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 9e9aae52c..acd40e02d 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 07d55f787..d4e392be1 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 d5fc9f5b8..9aaf9b525 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 209c6e62e..00dc19a46 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 582c2a850..2a1f8dca1 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 82bac0ad5..57c239e44 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 c5d92b9da..c1aa1f539 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 b806e2b44..4b6248d69 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 e96dcf34f..6ad68bb93 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 715c3532d..d20a15bc8 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 ee274ab31..84bd05e07 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 319fb723d..544acc225 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 d80dc086c..d0d6cd5dd 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 97b363558..537da25d0 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 fe27e5d7d..62806a804 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 2f2ade8e7..30c149ec0 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 e0d7c1740..e9f65c4f6 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 a41324905..9d6c2f2b0 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 95832fbb7..4525b8a4a 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 03f0bf402..0b5521438 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 2843300e8..7cc0e227e 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 c32695abb..000000000 --- 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 4d72220af..ce284b431 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 db15de3fb..f30fda33d 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" } } From d4652e69cebfbb85d3b17165dcaaa8836694d2e8 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard Date: Mon, 7 Jul 2025 09:47:02 +0400 Subject: [PATCH 02/27] feat: fips inside (checkpoint) --- backend/e2e-test/vitest-environment-knex.ts | 25 +++-- backend/src/lib/config/env.ts | 19 +++- backend/src/lib/crypto/cipher/cipher.ts | 1 + backend/src/lib/crypto/cryptography.ts | 105 ++++++++----------- backend/src/lib/errors/index.ts | 12 +++ backend/src/lib/telemetry/instrumentation.ts | 28 ++--- backend/src/main.ts | 39 +++---- backend/src/server/plugins/error-handler.ts | 8 ++ 8 files changed, 131 insertions(+), 106 deletions(-) diff --git a/backend/e2e-test/vitest-environment-knex.ts b/backend/e2e-test/vitest-environment-knex.ts index 6e1f0ff49..2b4559022 100644 --- a/backend/e2e-test/vitest-environment-knex.ts +++ b/backend/e2e-test/vitest-environment-knex.ts @@ -26,15 +26,18 @@ export default { transformMode: "ssr", async setup() { const logger = initLogger(); - const envConfig = initEnvConfig(logger); + const { envCfg, updateRootEncryptionKey } = initEnvConfig(logger); const db = initDbConnection({ - dbConnectionUri: envConfig.DB_CONNECTION_URI, - dbRootCert: envConfig.DB_ROOT_CERT + dbConnectionUri: envCfg.DB_CONNECTION_URI, + dbRootCert: envCfg.DB_ROOT_CERT }); const superAdminDAL = superAdminDALFactory(db); - await crypto.initialize(superAdminDAL); + const fipsEnabled = await crypto.initialize(superAdminDAL); + if (fipsEnabled) { + updateRootEncryptionKey(envCfg.ENCRYPTION_KEY); + } - const redis = buildRedisFromConfig(envConfig); + const redis = buildRedisFromConfig(envCfg); await redis.flushdb("SYNC"); try { @@ -59,10 +62,10 @@ export default { }); const smtp = mockSmtpServer(); - const queue = queueServiceFactory(envConfig, { dbConnectionUrl: envConfig.DB_CONNECTION_URI }); - const keyStore = keyStoreFactory(envConfig); + const queue = queueServiceFactory(envCfg, { dbConnectionUrl: envCfg.DB_CONNECTION_URI }); + const keyStore = keyStoreFactory(envCfg); - const hsmModule = initializeHsmModule(envConfig); + const hsmModule = initializeHsmModule(envCfg); hsmModule.initialize(); const server = await main({ @@ -74,7 +77,7 @@ export default { hsmModule: hsmModule.getModule(), superAdminDAL, redis, - envConfig + envConfig: envCfg }); // @ts-expect-error type @@ -91,8 +94,8 @@ export default { organizationId: seedData1.organization.id, accessVersion: 1 }, - envConfig.AUTH_SECRET, - { expiresIn: envConfig.JWT_AUTH_LIFETIME } + envCfg.AUTH_SECRET, + { expiresIn: envCfg.JWT_AUTH_LIFETIME } ); } catch (error) { // eslint-disable-next-line diff --git a/backend/src/lib/config/env.ts b/backend/src/lib/config/env.ts index 205ae3ed5..818b3e254 100644 --- a/backend/src/lib/config/env.ts +++ b/backend/src/lib/config/env.ts @@ -353,8 +353,23 @@ export const initEnvConfig = (logger?: CustomLogger) => { process.exit(-1); } - envCfg = Object.freeze(parsedEnv.data); - return envCfg; + const updateRootEncryptionKey = (key?: string) => { + if (!key) { + throw new Error("Failed to update root encryption key. Key is unset."); + } + + const newEnvCfg = { + ...envCfg + }; + + newEnvCfg.ROOT_ENCRYPTION_KEY = key; + delete newEnvCfg.ENCRYPTION_KEY; + + envCfg = Object.freeze(newEnvCfg); + return envCfg; + }; + + return { envCfg, updateRootEncryptionKey }; }; export const formatSmtpConfig = () => { diff --git a/backend/src/lib/crypto/cipher/cipher.ts b/backend/src/lib/crypto/cipher/cipher.ts index e193c8c3f..ca5890f87 100644 --- a/backend/src/lib/crypto/cipher/cipher.ts +++ b/backend/src/lib/crypto/cipher/cipher.ts @@ -5,6 +5,7 @@ import { SymmetricKeyAlgorithm, TSymmetricEncryptionFns } from "./types"; const IV_LENGTH = 12; const TAG_LENGTH = 16; +// todo(daniel): Decide if we should move this into the cryptography module export const symmetricCipherService = ( type: SymmetricKeyAlgorithm.AES_GCM_128 | SymmetricKeyAlgorithm.AES_GCM_256 ): TSymmetricEncryptionFns => { diff --git a/backend/src/lib/crypto/cryptography.ts b/backend/src/lib/crypto/cryptography.ts index 8486837cb..036048c45 100644 --- a/backend/src/lib/crypto/cryptography.ts +++ b/backend/src/lib/crypto/cryptography.ts @@ -11,7 +11,9 @@ 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 { isBase64 } from "../base64"; import { getConfig } from "../config/env"; +import { CryptographyError } from "../errors"; import { logger } from "../logger"; enum DigestType { @@ -59,6 +61,8 @@ type TDecryptAsymmetricInput = { privateKey: string; }; +const bytesToBits = (bytes: number) => bytes * 8; + const IV_BYTES_SIZE = 12; const BLOCK_SIZE_BYTES_16 = 16; @@ -294,6 +298,33 @@ const cryptographyFactory = () => { return $fipsEnabled; }; const $setFipsModeEnabled = (enabled: boolean) => { + // If FIPS is enabled, we need to validate that the ENCRYPTION_KEY is in a base64 format, and is a 256-bit key. + if (enabled) { + const appCfg = getConfig(); + + if (appCfg.ENCRYPTION_KEY) { + // we need to validate that the ENCRYPTION_KEY is a base64 encoded 256-bit key + + if (!isBase64(appCfg.ENCRYPTION_KEY)) { + throw new CryptographyError({ + message: + "FIPS mode is enabled, but the ENCRYPTION_KEY environment variable is not a base64 encoded 256-bit key.\nYou can generate a 256-bit key using the following command: `openssl rand -base64 32`" + }); + } + + if (bytesToBits(Buffer.from(appCfg.ENCRYPTION_KEY, "base64").length) !== 256) { + throw new CryptographyError({ + message: + "FIPS mode is enabled, but the ENCRYPTION_KEY environment variable is not a 256-bit key.\nYou can generate a 256-bit key using the following command: `openssl rand -base64 32`" + }); + } + } else { + throw new CryptographyError({ + message: + "FIPS mode is enabled, but the ENCRYPTION_KEY environment variable is not set.\nYou can generate a 256-bit key using the following command: `openssl rand -base64 32`" + }); + } + } $fipsEnabled = enabled; $isInitialized = true; }; @@ -337,52 +368,22 @@ const cryptographyFactory = () => { 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 }); }; @@ -397,6 +398,12 @@ const cryptographyFactory = () => { let decipher; if (keySize === SymmetricKeySize.Bits128) { + if (isFipsModeEnabled()) { + throw new CryptographyError({ + message: "128-bit symmetric key is not supported in FIPS mode of operation." + }); + } + // 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. @@ -418,6 +425,12 @@ const cryptographyFactory = () => { let cipher; if (keySize === SymmetricKeySize.Bits128) { + if (isFipsModeEnabled()) { + throw new CryptographyError({ + message: "128-bit symmetric key is not supported in FIPS mode of operation." + }); + } + iv = crypto.randomBytes(BLOCK_SIZE_BYTES_16); cipher = crypto.createCipheriv(SecretEncryptionAlgo.AES_256_GCM, key, iv); } else { @@ -478,21 +491,6 @@ const cryptographyFactory = () => { }: 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; @@ -530,11 +528,6 @@ const cryptographyFactory = () => { * @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()) { @@ -544,12 +537,6 @@ const cryptographyFactory = () => { }; 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(); @@ -566,12 +553,6 @@ const cryptographyFactory = () => { }; 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; diff --git a/backend/src/lib/errors/index.ts b/backend/src/lib/errors/index.ts index a5df64caf..dab9d3278 100644 --- a/backend/src/lib/errors/index.ts +++ b/backend/src/lib/errors/index.ts @@ -171,3 +171,15 @@ export class OidcAuthError extends Error { this.error = error; } } + +export class CryptographyError extends Error { + name: string; + + error: unknown; + + constructor({ name, error, message }: { message?: string; name?: string; error?: unknown }) { + super(message || "Cryptographic operation failed"); + this.name = name || "CryptographyError"; + this.error = error; + } +} diff --git a/backend/src/lib/telemetry/instrumentation.ts b/backend/src/lib/telemetry/instrumentation.ts index faa7560d3..1cb35e143 100644 --- a/backend/src/lib/telemetry/instrumentation.ts +++ b/backend/src/lib/telemetry/instrumentation.ts @@ -75,27 +75,29 @@ const initTelemetryInstrumentation = ({ }; const setupTelemetry = () => { - const appCfg = initEnvConfig(); + const { envCfg } = initEnvConfig(); - if (appCfg.OTEL_TELEMETRY_COLLECTION_ENABLED) { + console.log("envCfg", envCfg); + + if (envCfg.OTEL_TELEMETRY_COLLECTION_ENABLED) { console.log("Initializing telemetry instrumentation"); initTelemetryInstrumentation({ - otlpURL: appCfg.OTEL_EXPORT_OTLP_ENDPOINT, - otlpUser: appCfg.OTEL_COLLECTOR_BASIC_AUTH_USERNAME, - otlpPassword: appCfg.OTEL_COLLECTOR_BASIC_AUTH_PASSWORD, - otlpPushInterval: appCfg.OTEL_OTLP_PUSH_INTERVAL, - exportType: appCfg.OTEL_EXPORT_TYPE + otlpURL: envCfg.OTEL_EXPORT_OTLP_ENDPOINT, + otlpUser: envCfg.OTEL_COLLECTOR_BASIC_AUTH_USERNAME, + otlpPassword: envCfg.OTEL_COLLECTOR_BASIC_AUTH_PASSWORD, + otlpPushInterval: envCfg.OTEL_OTLP_PUSH_INTERVAL, + exportType: envCfg.OTEL_EXPORT_TYPE }); } - if (appCfg.SHOULD_USE_DATADOG_TRACER) { + if (envCfg.SHOULD_USE_DATADOG_TRACER) { console.log("Initializing Datadog tracer"); tracer.init({ - profiling: appCfg.DATADOG_PROFILING_ENABLED, - version: appCfg.INFISICAL_PLATFORM_VERSION, - env: appCfg.DATADOG_ENV, - service: appCfg.DATADOG_SERVICE, - hostname: appCfg.DATADOG_HOSTNAME + profiling: envCfg.DATADOG_PROFILING_ENABLED, + version: envCfg.INFISICAL_PLATFORM_VERSION, + env: envCfg.DATADOG_ENV, + service: envCfg.DATADOG_SERVICE, + hostname: envCfg.DATADOG_HOSTNAME }); } }; diff --git a/backend/src/main.ts b/backend/src/main.ts index 15dd0322b..78cd05acf 100644 --- a/backend/src/main.ts +++ b/backend/src/main.ts @@ -22,26 +22,29 @@ dotenv.config(); const run = async () => { const logger = initLogger(); - const envConfig = initEnvConfig(logger); + const { envCfg, updateRootEncryptionKey } = initEnvConfig(logger); await removeTemporaryBaseDirectory(); const db = initDbConnection({ - dbConnectionUri: envConfig.DB_CONNECTION_URI, - dbRootCert: envConfig.DB_ROOT_CERT, - readReplicas: envConfig.DB_READ_REPLICAS?.map((el) => ({ + dbConnectionUri: envCfg.DB_CONNECTION_URI, + dbRootCert: envCfg.DB_ROOT_CERT, + readReplicas: envCfg.DB_READ_REPLICAS?.map((el) => ({ dbRootCert: el.DB_ROOT_CERT, dbConnectionUri: el.DB_CONNECTION_URI })) }); const superAdminDAL = superAdminDALFactory(db); - await crypto.initialize(superAdminDAL); + const fipsEnabled = await crypto.initialize(superAdminDAL); + if (fipsEnabled) { + updateRootEncryptionKey(envCfg.ENCRYPTION_KEY); + } - const auditLogDb = envConfig.AUDIT_LOGS_DB_CONNECTION_URI + const auditLogDb = envCfg.AUDIT_LOGS_DB_CONNECTION_URI ? initAuditLogDbConnection({ - dbConnectionUri: envConfig.AUDIT_LOGS_DB_CONNECTION_URI, - dbRootCert: envConfig.AUDIT_LOGS_DB_ROOT_CERT + dbConnectionUri: envCfg.AUDIT_LOGS_DB_CONNECTION_URI, + dbRootCert: envCfg.AUDIT_LOGS_DB_ROOT_CERT }) : undefined; @@ -49,17 +52,17 @@ const run = async () => { const smtp = smtpServiceFactory(formatSmtpConfig()); - const queue = queueServiceFactory(envConfig, { - dbConnectionUrl: envConfig.DB_CONNECTION_URI, - dbRootCert: envConfig.DB_ROOT_CERT + const queue = queueServiceFactory(envCfg, { + dbConnectionUrl: envCfg.DB_CONNECTION_URI, + dbRootCert: envCfg.DB_ROOT_CERT }); await queue.initialize(); - const keyStore = keyStoreFactory(envConfig); - const redis = buildRedisFromConfig(envConfig); + const keyStore = keyStoreFactory(envCfg); + const redis = buildRedisFromConfig(envCfg); - const hsmModule = initializeHsmModule(envConfig); + const hsmModule = initializeHsmModule(envCfg); hsmModule.initialize(); const server = await main({ @@ -72,7 +75,7 @@ const run = async () => { queue, keyStore, redis, - envConfig + envConfig: envCfg }); const bootstrap = await bootstrapCheck({ db }); @@ -96,7 +99,7 @@ const run = async () => { process.exit(0); }); - if (!envConfig.isDevelopmentMode) { + if (!envCfg.isDevelopmentMode) { process.on("uncaughtException", (error) => { logger.error(error, "CRITICAL ERROR: Uncaught Exception"); }); @@ -107,8 +110,8 @@ const run = async () => { } await server.listen({ - port: envConfig.PORT, - host: envConfig.HOST, + port: envCfg.PORT, + host: envCfg.HOST, listenTextResolver: (address) => { void bootstrap(); return address; diff --git a/backend/src/server/plugins/error-handler.ts b/backend/src/server/plugins/error-handler.ts index c8170a023..62df05eec 100644 --- a/backend/src/server/plugins/error-handler.ts +++ b/backend/src/server/plugins/error-handler.ts @@ -7,6 +7,7 @@ import { ZodError } from "zod"; import { getConfig } from "@app/lib/config/env"; import { BadRequestError, + CryptographyError, DatabaseError, ForbiddenRequestError, GatewayTimeoutError, @@ -147,6 +148,13 @@ export const fastifyErrHandler = fastifyPlugin(async (server: FastifyZodProvider message: error.message, error: error.name }); + } else if (error instanceof CryptographyError) { + void res.status(HttpStatusCodes.BadRequest).send({ + reqId: req.id, + statusCode: HttpStatusCodes.BadRequest, + message: error.message, + error: error.name + }); } else if (error instanceof jwt.JsonWebTokenError) { let errorMessage = error.message; From ce88b0cbb1bff62d16dfaf4e694123c689b11b05 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard Date: Mon, 7 Jul 2025 18:16:53 +0400 Subject: [PATCH 03/27] feat(fips): fips inside --- .infisicalignore | 1 + backend/e2e-test/vitest-environment-knex.ts | 15 +- .../20250210101840_webhook-to-kms.ts | 6 +- ...250210101841_dynamic-secret-root-to-kms.ts | 5 +- .../20250210101841_secret-rotation-to-kms.ts | 5 +- .../20250210101842_identity-k8-auth-to-kms.ts | 5 +- ...0250210101842_identity-oidc-auth-to-kms.ts | 5 +- .../20250210101845_directory-config-to-kms.ts | 10 +- ...50513081738_remove-gateway-project-link.ts | 4 +- backend/src/db/migrations/utils/env-config.ts | 20 ++- .../src/ee/services/license/license-fns.ts | 3 +- .../src/ee/services/license/license-types.ts | 1 + backend/src/lib/config/env.ts | 52 +++++- backend/src/lib/crypto/cryptography.ts | 148 +++++++++++++----- backend/src/lib/telemetry/instrumentation.ts | 6 +- backend/src/main.ts | 47 +++--- backend/src/server/routes/index.ts | 8 +- backend/src/services/kms/kms-service.ts | 1 + .../super-admin/super-admin-service.ts | 2 + 19 files changed, 244 insertions(+), 100 deletions(-) diff --git a/.infisicalignore b/.infisicalignore index d705c0d66..4c50aa7fa 100644 --- a/.infisicalignore +++ b/.infisicalignore @@ -46,3 +46,4 @@ cli/detect/config/gitleaks.toml:gcp-api-key:582 .github/workflows/helm-release-infisical-core.yml:generic-api-key:47 backend/src/services/smtp/smtp-service.ts:generic-api-key:79 frontend/src/components/secret-syncs/forms/SecretSyncDestinationFields/CloudflarePagesSyncFields.tsx:cloudflare-api-key:7 +docs/integrations/app-connections/zabbix.mdx:generic-api-key:91 diff --git a/backend/e2e-test/vitest-environment-knex.ts b/backend/e2e-test/vitest-environment-knex.ts index 2b4559022..74276001d 100644 --- a/backend/e2e-test/vitest-environment-knex.ts +++ b/backend/e2e-test/vitest-environment-knex.ts @@ -6,7 +6,7 @@ import jwt from "jsonwebtoken"; import path from "path"; import { seedData1 } from "@app/db/seed-data"; -import { initEnvConfig } from "@app/lib/config/env"; +import { getDatabaseCredentials, initEnvConfig } from "@app/lib/config/env"; import { initLogger } from "@app/lib/logger"; import { main } from "@app/server/app"; import { AuthMethod, AuthTokenType } from "@app/services/auth/auth-type"; @@ -26,16 +26,15 @@ export default { transformMode: "ssr", async setup() { const logger = initLogger(); - const { envCfg, updateRootEncryptionKey } = initEnvConfig(logger); + const databaseCredentials = getDatabaseCredentials(logger); + const db = initDbConnection({ - dbConnectionUri: envCfg.DB_CONNECTION_URI, - dbRootCert: envCfg.DB_ROOT_CERT + dbConnectionUri: databaseCredentials.dbConnectionUri, + dbRootCert: databaseCredentials.dbRootCert }); + const superAdminDAL = superAdminDALFactory(db); - const fipsEnabled = await crypto.initialize(superAdminDAL); - if (fipsEnabled) { - updateRootEncryptionKey(envCfg.ENCRYPTION_KEY); - } + const envCfg = await initEnvConfig(superAdminDAL, logger); const redis = buildRedisFromConfig(envCfg); await redis.flushdb("SYNC"); diff --git a/backend/src/db/migrations/20250210101840_webhook-to-kms.ts b/backend/src/db/migrations/20250210101840_webhook-to-kms.ts index ff2503b32..63f1874e3 100644 --- a/backend/src/db/migrations/20250210101840_webhook-to-kms.ts +++ b/backend/src/db/migrations/20250210101840_webhook-to-kms.ts @@ -4,6 +4,7 @@ import { inMemoryKeyStore } from "@app/keystore/memory"; import { crypto } from "@app/lib/crypto/cryptography"; import { initLogger } from "@app/lib/logger"; import { KmsDataKey } from "@app/services/kms/kms-types"; +import { superAdminDALFactory } from "@app/services/super-admin/super-admin-dal"; import { SecretKeyEncoding, TableName } from "../schemas"; import { getMigrationEnvConfig } from "./utils/env-config"; @@ -26,9 +27,12 @@ export async function up(knex: Knex): Promise { } initLogger(); - const envConfig = getMigrationEnvConfig(); + const superAdminDAL = superAdminDALFactory(knex); + const envConfig = await getMigrationEnvConfig(superAdminDAL); + const keyStore = inMemoryKeyStore(); const { kmsService } = await getMigrationEncryptionServices({ envConfig, keyStore, db: knex }); + const projectEncryptionRingBuffer = createCircularCache>>(25); const webhooks = await knex(TableName.Webhook) 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 e01fbb1db..ac0395111 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 @@ -5,6 +5,7 @@ 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"; +import { superAdminDALFactory } from "@app/services/super-admin/super-admin-dal"; import { SecretKeyEncoding, TableName } from "../schemas"; import { getMigrationEnvConfig } from "./utils/env-config"; @@ -29,7 +30,9 @@ export async function up(knex: Knex): Promise { } initLogger(); - const envConfig = getMigrationEnvConfig(); + const superAdminDAL = superAdminDALFactory(knex); + const envConfig = await getMigrationEnvConfig(superAdminDAL); + const keyStore = inMemoryKeyStore(); const { kmsService } = await getMigrationEncryptionServices({ envConfig, keyStore, db: knex }); const projectEncryptionRingBuffer = 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 661d92f14..b8e23e757 100644 --- a/backend/src/db/migrations/20250210101841_secret-rotation-to-kms.ts +++ b/backend/src/db/migrations/20250210101841_secret-rotation-to-kms.ts @@ -5,6 +5,7 @@ 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"; +import { superAdminDALFactory } from "@app/services/super-admin/super-admin-dal"; import { SecretKeyEncoding, TableName } from "../schemas"; import { getMigrationEnvConfig } from "./utils/env-config"; @@ -23,7 +24,9 @@ export async function up(knex: Knex): Promise { } initLogger(); - const envConfig = getMigrationEnvConfig(); + const superAdminDAL = superAdminDALFactory(knex); + const envConfig = await getMigrationEnvConfig(superAdminDAL); + const keyStore = inMemoryKeyStore(); const { kmsService } = await getMigrationEncryptionServices({ envConfig, keyStore, db: knex }); const projectEncryptionRingBuffer = 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 36c70c704..23a8c0e9b 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 @@ -5,6 +5,7 @@ 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"; +import { superAdminDALFactory } from "@app/services/super-admin/super-admin-dal"; import { SecretKeyEncoding, TableName, TOrgBots } from "../schemas"; import { getMigrationEnvConfig } from "./utils/env-config"; @@ -54,7 +55,9 @@ const reencryptIdentityK8sAuth = async (knex: Knex) => { } initLogger(); - const envConfig = getMigrationEnvConfig(); + const superAdminDAL = superAdminDALFactory(knex); + const envConfig = await getMigrationEnvConfig(superAdminDAL); + const keyStore = inMemoryKeyStore(); const { kmsService } = await getMigrationEncryptionServices({ envConfig, keyStore, db: knex }); const orgEncryptionRingBuffer = 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 c0496f823..25dfb69f1 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 @@ -5,6 +5,7 @@ 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"; +import { superAdminDALFactory } from "@app/services/super-admin/super-admin-dal"; import { SecretKeyEncoding, TableName, TOrgBots } from "../schemas"; import { getMigrationEnvConfig } from "./utils/env-config"; @@ -34,7 +35,9 @@ const reencryptIdentityOidcAuth = async (knex: Knex) => { } initLogger(); - const envConfig = getMigrationEnvConfig(); + const superAdminDAL = superAdminDALFactory(knex); + const envConfig = await getMigrationEnvConfig(superAdminDAL); + const keyStore = inMemoryKeyStore(); const { kmsService } = await getMigrationEncryptionServices({ envConfig, keyStore, db: knex }); const orgEncryptionRingBuffer = 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 b07fe7787..cd40318a4 100644 --- a/backend/src/db/migrations/20250210101845_directory-config-to-kms.ts +++ b/backend/src/db/migrations/20250210101845_directory-config-to-kms.ts @@ -10,6 +10,7 @@ import { SecretKeyEncoding, TableName } from "../schemas"; import { getMigrationEnvConfig } from "./utils/env-config"; import { createCircularCache } from "./utils/ring-buffer"; import { getMigrationEncryptionServices } from "./utils/services"; +import { superAdminDALFactory } from "@app/services/super-admin/super-admin-dal"; const BATCH_SIZE = 500; const reencryptSamlConfig = async (knex: Knex) => { @@ -27,7 +28,8 @@ const reencryptSamlConfig = async (knex: Knex) => { } initLogger(); - const envConfig = getMigrationEnvConfig(); + const superAdminDAL = superAdminDALFactory(knex); + const envConfig = await getMigrationEnvConfig(superAdminDAL); const keyStore = inMemoryKeyStore(); const { kmsService } = await getMigrationEncryptionServices({ envConfig, keyStore, db: knex }); const orgEncryptionRingBuffer = @@ -189,7 +191,8 @@ const reencryptLdapConfig = async (knex: Knex) => { } initLogger(); - const envConfig = getMigrationEnvConfig(); + const superAdminDAL = superAdminDALFactory(knex); + const envConfig = await getMigrationEnvConfig(superAdminDAL); const keyStore = inMemoryKeyStore(); const { kmsService } = await getMigrationEncryptionServices({ envConfig, keyStore, db: knex }); const orgEncryptionRingBuffer = @@ -345,7 +348,8 @@ const reencryptOidcConfig = async (knex: Knex) => { } initLogger(); - const envConfig = getMigrationEnvConfig(); + const superAdminDAL = superAdminDALFactory(knex); + const envConfig = await getMigrationEnvConfig(superAdminDAL); const keyStore = inMemoryKeyStore(); const { kmsService } = await getMigrationEncryptionServices({ envConfig, keyStore, db: knex }); const orgEncryptionRingBuffer = diff --git a/backend/src/db/migrations/20250513081738_remove-gateway-project-link.ts b/backend/src/db/migrations/20250513081738_remove-gateway-project-link.ts index 3b3c5322e..a0985471f 100644 --- a/backend/src/db/migrations/20250513081738_remove-gateway-project-link.ts +++ b/backend/src/db/migrations/20250513081738_remove-gateway-project-link.ts @@ -4,6 +4,7 @@ import { inMemoryKeyStore } from "@app/keystore/memory"; import { selectAllTableCols } from "@app/lib/knex"; import { initLogger } from "@app/lib/logger"; import { KmsDataKey } from "@app/services/kms/kms-types"; +import { superAdminDALFactory } from "@app/services/super-admin/super-admin-dal"; import { TableName } from "../schemas"; import { getMigrationEnvConfig } from "./utils/env-config"; @@ -39,7 +40,8 @@ export async function up(knex: Knex): Promise { ); initLogger(); - const envConfig = getMigrationEnvConfig(); + const superAdminDAL = superAdminDALFactory(knex); + const envConfig = await getMigrationEnvConfig(superAdminDAL); const keyStore = inMemoryKeyStore(); const { kmsService } = await getMigrationEncryptionServices({ envConfig, keyStore, db: knex }); diff --git a/backend/src/db/migrations/utils/env-config.ts b/backend/src/db/migrations/utils/env-config.ts index 8744308ab..14a6a2023 100644 --- a/backend/src/db/migrations/utils/env-config.ts +++ b/backend/src/db/migrations/utils/env-config.ts @@ -1,6 +1,8 @@ import { z } from "zod"; import { zpStr } from "@app/lib/zod"; +import { crypto } from "@app/lib/crypto/cryptography"; +import { TSuperAdminDALFactory } from "@app/services/super-admin/super-admin-dal"; const envSchema = z .object({ @@ -35,7 +37,7 @@ const envSchema = z export type TMigrationEnvConfig = z.infer; -export const getMigrationEnvConfig = () => { +export const getMigrationEnvConfig = async (superAdminDAL: TSuperAdminDALFactory) => { const parsedEnv = envSchema.safeParse(process.env); if (!parsedEnv.success) { // eslint-disable-next-line no-console @@ -49,5 +51,19 @@ export const getMigrationEnvConfig = () => { process.exit(-1); } - return Object.freeze(parsedEnv.data); + let envCfg = Object.freeze(parsedEnv.data); + + const fipsEnabled = await crypto.initialize(superAdminDAL); + + if (fipsEnabled) { + const newEnvCfg = { + ...envCfg, + ROOT_ENCRYPTION_KEY: envCfg.ENCRYPTION_KEY + }; + delete newEnvCfg.ENCRYPTION_KEY; + + envCfg = Object.freeze(newEnvCfg); + } + + return envCfg; }; diff --git a/backend/src/ee/services/license/license-fns.ts b/backend/src/ee/services/license/license-fns.ts index c2db3e6e7..2b4054e1e 100644 --- a/backend/src/ee/services/license/license-fns.ts +++ b/backend/src/ee/services/license/license-fns.ts @@ -58,7 +58,8 @@ export const getDefaultOnPremFeatures = (): TFeatureSet => ({ sshHostGroups: false, secretScanning: false, enterpriseSecretSyncs: false, - enterpriseAppConnections: false + enterpriseAppConnections: false, + fips: true }); export const setupLicenseRequestWithStore = ( diff --git a/backend/src/ee/services/license/license-types.ts b/backend/src/ee/services/license/license-types.ts index 2937ac265..a3412f574 100644 --- a/backend/src/ee/services/license/license-types.ts +++ b/backend/src/ee/services/license/license-types.ts @@ -75,6 +75,7 @@ export type TFeatureSet = { secretScanning: false; enterpriseSecretSyncs: false; enterpriseAppConnections: false; + fips: false; }; export type TOrgPlansTableDTO = { diff --git a/backend/src/lib/config/env.ts b/backend/src/lib/config/env.ts index 44ca5363f..8c9318d8b 100644 --- a/backend/src/lib/config/env.ts +++ b/backend/src/lib/config/env.ts @@ -1,6 +1,8 @@ import { z } from "zod"; +import { crypto } from "@app/lib/crypto/cryptography"; import { QueueWorkerProfile } from "@app/lib/types"; +import { TSuperAdminDALFactory } from "@app/services/super-admin/super-admin-dal"; import { BadRequestError } from "../errors"; import { removeTrailingSlash } from "../fn"; @@ -349,7 +351,7 @@ export const getConfig = () => envCfg; export const getOriginalConfig = () => originalEnvConfig; // cannot import singleton logger directly as it needs config to load various transport -export const initEnvConfig = (logger?: CustomLogger) => { +export const initEnvConfig = async (superAdminDAL?: TSuperAdminDALFactory, logger?: CustomLogger) => { const parsedEnv = envSchema.safeParse(process.env); if (!parsedEnv.success) { (logger ?? console).error("Invalid environment variables. Check the error below"); @@ -357,16 +359,50 @@ export const initEnvConfig = (logger?: CustomLogger) => { process.exit(-1); } - const config = Object.freeze(parsedEnv.data); - envCfg = config; + if (superAdminDAL) { + const fipsEnabled = await crypto.initialize(superAdminDAL); - if (!originalEnvConfig) { - originalEnvConfig = config; + if (fipsEnabled) { + const newEnvCfg = { + ...envCfg, + ROOT_ENCRYPTION_KEY: envCfg.ENCRYPTION_KEY + }; + delete newEnvCfg.ENCRYPTION_KEY; + + envCfg = Object.freeze(newEnvCfg); + } + } + + if (!envCfg) { + const config = Object.freeze(parsedEnv.data); + envCfg = config; + + if (!originalEnvConfig) { + originalEnvConfig = config; + } } return envCfg; }; +export const getDatabaseCredentials = (logger?: CustomLogger) => { + const parsedEnv = envSchema.safeParse(process.env); + if (!parsedEnv.success) { + (logger ?? console).error("Invalid environment variables. Check the error below"); + (logger ?? console).error(parsedEnv.error.issues); + process.exit(-1); + } + + return { + dbConnectionUri: envCfg.DB_CONNECTION_URI, + dbRootCert: envCfg.DB_ROOT_CERT, + readReplicas: envCfg.DB_READ_REPLICAS?.map((el) => ({ + dbRootCert: el.DB_ROOT_CERT, + dbConnectionUri: el.DB_CONNECTION_URI + })) + }; +}; + // A list of environment variables that can be overwritten export const overwriteSchema: { [key: string]: { @@ -462,7 +498,11 @@ export const overrideEnvConfig = (config: Record) => { const parsedResult = envSchema.safeParse(tempEnv); if (parsedResult.success) { - envCfg = Object.freeze(parsedResult.data); + envCfg = Object.freeze({ + ...parsedResult.data, + ENCRYPTION_KEY: envCfg.ENCRYPTION_KEY, + ROOT_ENCRYPTION_KEY: envCfg.ROOT_ENCRYPTION_KEY + }); } }; diff --git a/backend/src/lib/crypto/cryptography.ts b/backend/src/lib/crypto/cryptography.ts index 036048c45..3a44e5ce2 100644 --- a/backend/src/lib/crypto/cryptography.ts +++ b/backend/src/lib/crypto/cryptography.ts @@ -8,6 +8,7 @@ import nacl from "tweetnacl"; import naclUtils from "tweetnacl-util"; import { SecretEncryptionAlgo, SecretKeyEncoding } from "@app/db/schemas"; +import { TLicenseServiceFactory } from "@app/ee/services/license/license-service"; import { TSuperAdminDALFactory } from "@app/services/super-admin/super-admin-dal"; import { ADMIN_CONFIG_DB_UUID } from "@app/services/super-admin/super-admin-service"; @@ -67,40 +68,101 @@ 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) => { + const keySize = 32; + + // For the salt when using pkdf2, we do salt rounds^6. If the salt rounds are 10, this will result in 10^6 = 1.000.000 iterations. + // 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^6 brings the computational power required to a little more than bcrypt. + // OWASP recommends a minimum of 600.000 iterations for pbkdf2, so 1.000.000 is more than enough. + // Ref: https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html#pbkdf2 + const MIN_COST_FACTOR = 10; + const MAX_COST_FACTOR = 20; // Iterations scales polynomial (costFactor^6), so we need an upper bound + + const $calculateIterations = (costFactor: number) => { + return Math.round(costFactor ** 6); + }; + + const $hashPassword = (password: Buffer, salt: Buffer, 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")); + resolve(derivedKey); }); }); }; - const $validatePassword = ( - inputPassword: string, - storedHash: string, - salt: string, + const $validatePassword = async ( + inputPassword: Buffer, + storedHash: Buffer, + salt: Buffer, iterations: number, keyLength: number ) => { - return $hashPassword(inputPassword, salt, iterations, keyLength).then((hash) => hash === storedHash); + const computedHash = await $hashPassword(inputPassword, salt, iterations, keyLength); + + return crypto.timingSafeEqual(computedHash, 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 hash = async (password: string, costFactor: number) => { + // Strict input validation + if (typeof password !== "string" || password.length === 0) { + throw new CryptographyError({ + message: "Invalid input, password must be a non-empty string" + }); + } + + if (!Number.isInteger(costFactor)) { + throw new CryptographyError({ + message: "Invalid cost factor, must be an integer" + }); + } + + if (costFactor < MIN_COST_FACTOR || costFactor > MAX_COST_FACTOR) { + throw new CryptographyError({ + message: `Invalid cost factor, must be between ${MIN_COST_FACTOR} and ${MAX_COST_FACTOR}` + }); + } + + const iterations = $calculateIterations(costFactor); + + const salt = crypto.randomBytes(16); + const derivedKey = await $hashPassword(Buffer.from(password), salt, iterations, keySize); + + const combined = Buffer.concat([salt, derivedKey]); + return `$v1$${costFactor}$${combined.toString("base64")}`; // Store original costFactor! }; const compare = async (password: string, hashedPassword: string) => { - if (!hashedPassword.startsWith("$infisical$")) { - throw new Error("Invalid hash format"); - } + try { + if (!hashedPassword?.startsWith("$v1$")) return false; - const [, , iterations, salt, storedHash] = hashedPassword.split("$"); - return $validatePassword(password, storedHash, salt, Number(iterations), 32); + const parts = hashedPassword.split("$"); + if (parts.length !== 4) return false; + + const [, , storedCostFactor, combined] = parts; + + if ( + !Number.isInteger(Number(storedCostFactor)) || + Number(storedCostFactor) < MIN_COST_FACTOR || + Number(storedCostFactor) > MAX_COST_FACTOR + ) { + return false; + } + + const combinedBuffer = Buffer.from(combined, "base64"); + const salt = combinedBuffer.subarray(0, 16); + const storedHash = combinedBuffer.subarray(16); + + const iterations = $calculateIterations(Number(storedCostFactor)); + + const isMatch = await $validatePassword(Buffer.from(password), storedHash, salt, iterations, keySize); + + return isMatch; + } catch { + return false; + } }; return { @@ -216,7 +278,9 @@ const decryptAsymmetricFipsValidated = ({ const final = decipher.final(); return Buffer.concat([plaintext, final]).toString("utf8"); } catch (error) { - throw new Error("Invalid ciphertext or keys"); + throw new CryptographyError({ + message: "Invalid ciphertext or keys" + }); } }; @@ -276,7 +340,9 @@ export const computeMd5 = (message: string, digest: DigestType = DigestType.Hex) encoder = cryptoJs.enc.Base64; break; default: - throw new Error(`Invalid digest type: ${digest as string}`); + throw new CryptographyError({ + message: `Invalid digest type: ${digest as string}` + }); } return cryptoJs.MD5(message).toString(encoder); @@ -289,7 +355,9 @@ const cryptographyFactory = () => { const $checkIsInitialized = () => { if (!$isInitialized) { - throw new Error("Internal cryptography module is not initialized"); + throw new CryptographyError({ + message: "Internal cryptography module is not initialized" + }); } }; @@ -297,6 +365,15 @@ const cryptographyFactory = () => { $checkIsInitialized(); return $fipsEnabled; }; + + const verifyFipsLicense = (licenseService: Pick) => { + if (isFipsModeEnabled() && !licenseService.onPremFeatures?.fips) { + throw new CryptographyError({ + message: "FIPS mode is enabled but your license does not include FIPS support. Please contact support." + }); + } + }; + const $setFipsModeEnabled = (enabled: boolean) => { // If FIPS is enabled, we need to validate that the ENCRYPTION_KEY is in a base64 format, and is a 256-bit key. if (enabled) { @@ -305,6 +382,7 @@ const cryptographyFactory = () => { if (appCfg.ENCRYPTION_KEY) { // we need to validate that the ENCRYPTION_KEY is a base64 encoded 256-bit key + // note(daniel): for some reason this resolves as true for some hex-encoded strings. if (!isBase64(appCfg.ENCRYPTION_KEY)) { throw new CryptographyError({ message: @@ -398,15 +476,8 @@ const cryptographyFactory = () => { let decipher; if (keySize === SymmetricKeySize.Bits128) { - if (isFipsModeEnabled()) { - throw new CryptographyError({ - message: "128-bit symmetric key is not supported in FIPS mode of operation." - }); - } - // 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"); @@ -425,12 +496,6 @@ const cryptographyFactory = () => { let cipher; if (keySize === SymmetricKeySize.Bits128) { - if (isFipsModeEnabled()) { - throw new CryptographyError({ - message: "128-bit symmetric key is not supported in FIPS mode of operation." - }); - } - iv = crypto.randomBytes(BLOCK_SIZE_BYTES_16); cipher = crypto.createCipheriv(SecretEncryptionAlgo.AES_256_GCM, key, iv); } else { @@ -452,6 +517,7 @@ const cryptographyFactory = () => { const appCfg = getConfig(); const rootEncryptionKey = appCfg.ROOT_ENCRYPTION_KEY; const encryptionKey = appCfg.ENCRYPTION_KEY; + if (rootEncryptionKey) { const { iv, tag, ciphertext } = encryptSymmetric({ plaintext: data, @@ -480,7 +546,9 @@ const cryptographyFactory = () => { encoding: SecretKeyEncoding.UTF8 }; } - throw new Error("Missing both encryption keys"); + throw new CryptographyError({ + message: "Missing both encryption keys" + }); }; const decryptWithRootEncryptionKey = ({ @@ -509,7 +577,9 @@ const cryptographyFactory = () => { const data = decryptSymmetric({ key: encryptionKey, iv, tag, ciphertext, keySize: SymmetricKeySize.Bits128 }); return data as T; } - throw new Error("Missing both encryption keys"); + throw new CryptographyError({ + message: "Missing both encryption keys" + }); }; return { @@ -540,12 +610,7 @@ const cryptographyFactory = () => { 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); + const hash = await hasher.hash(password, saltRounds); return hash; } const hash = await bcrypt.hash(password, saltRounds); @@ -572,6 +637,7 @@ const cryptographyFactory = () => { initialize, isFipsModeEnabled, hashing, + verifyFipsLicense, encryption, randomBytes: crypto.randomBytes, randomInt: crypto.randomInt, diff --git a/backend/src/lib/telemetry/instrumentation.ts b/backend/src/lib/telemetry/instrumentation.ts index 1cb35e143..44c910816 100644 --- a/backend/src/lib/telemetry/instrumentation.ts +++ b/backend/src/lib/telemetry/instrumentation.ts @@ -74,10 +74,8 @@ const initTelemetryInstrumentation = ({ }); }; -const setupTelemetry = () => { - const { envCfg } = initEnvConfig(); - - console.log("envCfg", envCfg); +const setupTelemetry = async () => { + const envCfg = await initEnvConfig(); if (envCfg.OTEL_TELEMETRY_COLLECTION_ENABLED) { console.log("Initializing telemetry instrumentation"); diff --git a/backend/src/main.ts b/backend/src/main.ts index 78cd05acf..1f62f5a76 100644 --- a/backend/src/main.ts +++ b/backend/src/main.ts @@ -7,9 +7,8 @@ import { initializeHsmModule } from "@app/ee/services/hsm/hsm-fns"; import { runMigrations } from "./auto-start-migrations"; import { initAuditLogDbConnection, initDbConnection } from "./db"; import { keyStoreFactory } from "./keystore/keystore"; -import { formatSmtpConfig, initEnvConfig } from "./lib/config/env"; +import { formatSmtpConfig, getDatabaseCredentials, 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"; @@ -22,29 +21,23 @@ dotenv.config(); const run = async () => { const logger = initLogger(); - const { envCfg, updateRootEncryptionKey } = initEnvConfig(logger); - await removeTemporaryBaseDirectory(); + const databaseCredentials = getDatabaseCredentials(logger); + const db = initDbConnection({ - dbConnectionUri: envCfg.DB_CONNECTION_URI, - dbRootCert: envCfg.DB_ROOT_CERT, - readReplicas: envCfg.DB_READ_REPLICAS?.map((el) => ({ - dbRootCert: el.DB_ROOT_CERT, - dbConnectionUri: el.DB_CONNECTION_URI - })) + dbConnectionUri: databaseCredentials.dbConnectionUri, + dbRootCert: databaseCredentials.dbRootCert, + readReplicas: databaseCredentials.readReplicas }); const superAdminDAL = superAdminDALFactory(db); - const fipsEnabled = await crypto.initialize(superAdminDAL); - if (fipsEnabled) { - updateRootEncryptionKey(envCfg.ENCRYPTION_KEY); - } + const envConfig = await initEnvConfig(superAdminDAL, logger); - const auditLogDb = envCfg.AUDIT_LOGS_DB_CONNECTION_URI + const auditLogDb = envConfig.AUDIT_LOGS_DB_CONNECTION_URI ? initAuditLogDbConnection({ - dbConnectionUri: envCfg.AUDIT_LOGS_DB_CONNECTION_URI, - dbRootCert: envCfg.AUDIT_LOGS_DB_ROOT_CERT + dbConnectionUri: envConfig.AUDIT_LOGS_DB_CONNECTION_URI, + dbRootCert: envConfig.AUDIT_LOGS_DB_ROOT_CERT }) : undefined; @@ -52,17 +45,17 @@ const run = async () => { const smtp = smtpServiceFactory(formatSmtpConfig()); - const queue = queueServiceFactory(envCfg, { - dbConnectionUrl: envCfg.DB_CONNECTION_URI, - dbRootCert: envCfg.DB_ROOT_CERT + const queue = queueServiceFactory(envConfig, { + dbConnectionUrl: envConfig.DB_CONNECTION_URI, + dbRootCert: envConfig.DB_ROOT_CERT }); await queue.initialize(); - const keyStore = keyStoreFactory(envCfg); - const redis = buildRedisFromConfig(envCfg); + const keyStore = keyStoreFactory(envConfig); + const redis = buildRedisFromConfig(envConfig); - const hsmModule = initializeHsmModule(envCfg); + const hsmModule = initializeHsmModule(envConfig); hsmModule.initialize(); const server = await main({ @@ -75,7 +68,7 @@ const run = async () => { queue, keyStore, redis, - envConfig: envCfg + envConfig }); const bootstrap = await bootstrapCheck({ db }); @@ -99,7 +92,7 @@ const run = async () => { process.exit(0); }); - if (!envCfg.isDevelopmentMode) { + if (!envConfig.isDevelopmentMode) { process.on("uncaughtException", (error) => { logger.error(error, "CRITICAL ERROR: Uncaught Exception"); }); @@ -110,8 +103,8 @@ const run = async () => { } await server.listen({ - port: envCfg.PORT, - host: envCfg.HOST, + port: envConfig.PORT, + host: envConfig.HOST, listenTextResolver: (address) => { void bootstrap(); return address; diff --git a/backend/src/server/routes/index.ts b/backend/src/server/routes/index.ts index db7f0954b..e3a335deb 100644 --- a/backend/src/server/routes/index.ts +++ b/backend/src/server/routes/index.ts @@ -119,6 +119,7 @@ import { trustedIpDALFactory } from "@app/ee/services/trusted-ip/trusted-ip-dal" import { trustedIpServiceFactory } from "@app/ee/services/trusted-ip/trusted-ip-service"; import { TKeyStoreFactory } from "@app/keystore/keystore"; import { getConfig, TEnvConfig } from "@app/lib/config/env"; +import { crypto } from "@app/lib/crypto/cryptography"; import { logger } from "@app/lib/logger"; import { TQueueServiceFactory } from "@app/queue"; import { readLimit } from "@app/server/config/rateLimiter"; @@ -1903,11 +1904,14 @@ export const registerRoutes = async ( kmsService }); - await superAdminService.initServerCfg(); - // setup the communication with license key server await licenseService.init(); + // If FIPS is enabled, we check to ensure that the users license includes FIPS mode. + crypto.verifyFipsLicense(licenseService); + + await superAdminService.initServerCfg(); + // Start HSM service if it's configured/enabled. await hsmService.startService(); diff --git a/backend/src/services/kms/kms-service.ts b/backend/src/services/kms/kms-service.ts index 850635f5d..c4a26afd8 100644 --- a/backend/src/services/kms/kms-service.ts +++ b/backend/src/services/kms/kms-service.ts @@ -830,6 +830,7 @@ export const kmsServiceFactory = ({ const $getBasicEncryptionKey = () => { const encryptionKey = envConfig.ENCRYPTION_KEY || envConfig.ROOT_ENCRYPTION_KEY; + const isBase64 = !envConfig.ENCRYPTION_KEY; if (!encryptionKey) throw new Error( diff --git a/backend/src/services/super-admin/super-admin-service.ts b/backend/src/services/super-admin/super-admin-service.ts index 59db26147..70cf7d67f 100644 --- a/backend/src/services/super-admin/super-admin-service.ts +++ b/backend/src/services/super-admin/super-admin-service.ts @@ -277,6 +277,7 @@ export const superAdminServiceFactory = ({ const $syncEnvConfig = async () => { const config = await getEnvOverrides(); + overrideEnvConfig(config); }; @@ -483,6 +484,7 @@ export const superAdminServiceFactory = ({ userAgent }: TAdminSignUpDTO) => { const appCfg = getConfig(); + const sanitizedEmail = email.trim().toLowerCase(); const existingUser = await userDAL.findOne({ username: sanitizedEmail }); if (existingUser) throw new BadRequestError({ name: "Admin sign up", message: "User already exists" }); From 30e901c00c8ff087b48ef47c4187b37f0ec9d448 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard Date: Mon, 7 Jul 2025 21:56:07 +0400 Subject: [PATCH 04/27] feat(fips): fips inside, AWS patch-up and docker improvements --- Dockerfile.fips.standalone-infisical | 26 + backend/Dockerfile.dev.fips | 19 +- backend/{nodejs.cnf => nodejs.fips.cnf} | 0 backend/package-lock.json | 921 +++++++++++++++++- backend/package.json | 1 + .../providers/aws-elasticache.ts | 5 + .../dynamic-secret/providers/aws-iam.ts | 7 + .../external-kms/providers/aws-kms.ts | 9 +- .../src/ee/services/license/license-fns.ts | 2 +- .../secret-rotation-queue.ts | 3 + backend/src/lib/aws/hashing.ts | 57 ++ backend/src/lib/crypto/cryptography.ts | 8 +- .../app-connection/aws/aws-connection-fns.ts | 3 + .../acme/acme-certificate-authority-fns.ts | 3 + .../integration-sync-secret.ts | 3 + .../aws-secrets-manager-sync-fns.ts | 4 + .../components/EncryptionPageForm.tsx | 36 +- 17 files changed, 1048 insertions(+), 59 deletions(-) rename backend/{nodejs.cnf => nodejs.fips.cnf} (100%) create mode 100644 backend/src/lib/aws/hashing.ts diff --git a/Dockerfile.fips.standalone-infisical b/Dockerfile.fips.standalone-infisical index c799aaf23..8db60c7b3 100644 --- a/Dockerfile.fips.standalone-infisical +++ b/Dockerfile.fips.standalone-infisical @@ -73,6 +73,17 @@ RUN apt-get update && apt-get install -y \ # Configure ODBC RUN printf "[FreeTDS]\nDescription = FreeTDS Driver\nDriver = /usr/lib/x86_64-linux-gnu/odbc/libtdsodbc.so\nSetup = /usr/lib/x86_64-linux-gnu/odbc/libtdsS.so\nFileUsage = 1\n" > /etc/odbcinst.ini + +# Build and install FIPS validated OpenSSL +WORKDIR /openssl-build +RUN wget https://www.openssl.org/source/openssl-3.1.2.tar.gz \ + && tar -xf openssl-3.1.2.tar.gz \ + && cd openssl-3.1.2 \ + && ./Configure enable-fips \ + && make \ + && make install_fips + + COPY backend/package*.json ./ RUN npm ci --only-production @@ -103,6 +114,15 @@ RUN apt-get update && apt-get install -y \ # Configure ODBC RUN printf "[FreeTDS]\nDescription = FreeTDS Driver\nDriver = /usr/lib/x86_64-linux-gnu/odbc/libtdsodbc.so\nSetup = /usr/lib/x86_64-linux-gnu/odbc/libtdsS.so\nFileUsage = 1\n" > /etc/odbcinst.ini +# Build and install FIPS validated OpenSSL +WORKDIR /openssl-build +RUN wget https://www.openssl.org/source/openssl-3.1.2.tar.gz \ + && tar -xf openssl-3.1.2.tar.gz \ + && cd openssl-3.1.2 \ + && ./Configure enable-fips \ + && make \ + && make install_fips + COPY backend/package*.json ./ RUN npm ci --only-production @@ -173,6 +193,12 @@ ENV STANDALONE_MODE true ENV ChrystokiConfigurationPath=/usr/safenet/lunaclient/ ENV NODE_OPTIONS="--max-old-space-size=1024" +# FIPS mode of operation: +ENV OPENSSL_CONF=/backend/nodejs.fips.cnf +ENV OPENSSL_MODULES=/usr/local/lib/ossl-modules +ENV NODE_OPTIONS=--force-fips +ENV FIPS_ENABLED=true + WORKDIR /backend ENV TELEMETRY_ENABLED true diff --git a/backend/Dockerfile.dev.fips b/backend/Dockerfile.dev.fips index a77762324..f78ac1212 100644 --- a/backend/Dockerfile.dev.fips +++ b/backend/Dockerfile.dev.fips @@ -1,7 +1,5 @@ FROM node:20-slim -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 \ build-essential \ @@ -29,11 +27,26 @@ 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 +# Build and install FIPS validated OpenSSL WORKDIR /openssl-build RUN wget https://www.openssl.org/source/openssl-3.1.2.tar.gz \ && tar -xf openssl-3.1.2.tar.gz \ @@ -59,7 +72,7 @@ RUN npm install COPY . . ENV HOST=0.0.0.0 -ENV OPENSSL_CONF=/app/nodejs.cnf +ENV OPENSSL_CONF=/app/nodejs.fips.cnf ENV OPENSSL_MODULES=/usr/local/lib/ossl-modules ENV NODE_OPTIONS=--force-fips ENV FIPS_ENABLED=true diff --git a/backend/nodejs.cnf b/backend/nodejs.fips.cnf similarity index 100% rename from backend/nodejs.cnf rename to backend/nodejs.fips.cnf diff --git a/backend/package-lock.json b/backend/package-lock.json index aa2bc915f..d3533a3b4 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -138,6 +138,7 @@ "@babel/plugin-syntax-import-attributes": "^7.24.7", "@babel/preset-env": "^7.18.10", "@babel/preset-react": "^7.24.7", + "@smithy/types": "^4.3.1", "@types/bcrypt": "^5.0.2", "@types/crypto-js": "^4.2.2", "@types/jmespath": "^0.15.2", @@ -478,6 +479,18 @@ "node": ">=16.0.0" } }, + "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@smithy/types": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.7.2.tgz", + "integrity": "sha512-bNwBYYmN8Eh9RyjS1p2gW6MIhSO2rl7X9QeLM8iTdcGRP+eDiIWDt66c9IysCc22gefKszZv+ubV9qZc7hdESg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/@aws-sdk/client-elasticache": { "version": "3.637.0", "resolved": "https://registry.npmjs.org/@aws-sdk/client-elasticache/-/client-elasticache-3.637.0.tgz", @@ -820,6 +833,18 @@ "node": ">=16.0.0" } }, + "node_modules/@aws-sdk/client-elasticache/node_modules/@smithy/types": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.7.2.tgz", + "integrity": "sha512-bNwBYYmN8Eh9RyjS1p2gW6MIhSO2rl7X9QeLM8iTdcGRP+eDiIWDt66c9IysCc22gefKszZv+ubV9qZc7hdESg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/@aws-sdk/client-iam": { "version": "3.632.0", "resolved": "https://registry.npmjs.org/@aws-sdk/client-iam/-/client-iam-3.632.0.tgz", @@ -872,6 +897,18 @@ "node": ">=16.0.0" } }, + "node_modules/@aws-sdk/client-iam/node_modules/@smithy/types": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.7.2.tgz", + "integrity": "sha512-bNwBYYmN8Eh9RyjS1p2gW6MIhSO2rl7X9QeLM8iTdcGRP+eDiIWDt66c9IysCc22gefKszZv+ubV9qZc7hdESg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/@aws-sdk/client-kms": { "version": "3.632.0", "resolved": "https://registry.npmjs.org/@aws-sdk/client-kms/-/client-kms-3.632.0.tgz", @@ -923,6 +960,18 @@ "node": ">=16.0.0" } }, + "node_modules/@aws-sdk/client-kms/node_modules/@smithy/types": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.7.2.tgz", + "integrity": "sha512-bNwBYYmN8Eh9RyjS1p2gW6MIhSO2rl7X9QeLM8iTdcGRP+eDiIWDt66c9IysCc22gefKszZv+ubV9qZc7hdESg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/@aws-sdk/client-route-53": { "version": "3.810.0", "resolved": "https://registry.npmjs.org/@aws-sdk/client-route-53/-/client-route-53-3.810.0.tgz", @@ -1703,18 +1752,6 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-route-53/node_modules/@smithy/types": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.2.0.tgz", - "integrity": "sha512-7eMk09zQKCO+E/ivsjQv+fDlOupcFUCSC/L2YUPgwhvowVGWbPQHjEFcmjt7QQ4ra5lyowS92SV53Zc6XD4+fg==", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, "node_modules/@aws-sdk/client-route-53/node_modules/@smithy/url-parser": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.0.2.tgz", @@ -2484,6 +2521,18 @@ } } }, + "node_modules/@aws-sdk/client-s3/node_modules/@smithy/types": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.7.2.tgz", + "integrity": "sha512-bNwBYYmN8Eh9RyjS1p2gW6MIhSO2rl7X9QeLM8iTdcGRP+eDiIWDt66c9IysCc22gefKszZv+ubV9qZc7hdESg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/@aws-sdk/client-secrets-manager": { "version": "3.632.0", "resolved": "https://registry.npmjs.org/@aws-sdk/client-secrets-manager/-/client-secrets-manager-3.632.0.tgz", @@ -2536,6 +2585,18 @@ "node": ">=16.0.0" } }, + "node_modules/@aws-sdk/client-secrets-manager/node_modules/@smithy/types": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.7.2.tgz", + "integrity": "sha512-bNwBYYmN8Eh9RyjS1p2gW6MIhSO2rl7X9QeLM8iTdcGRP+eDiIWDt66c9IysCc22gefKszZv+ubV9qZc7hdESg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/@aws-sdk/client-sso": { "version": "3.632.0", "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.632.0.tgz", @@ -2636,6 +2697,30 @@ "@aws-sdk/client-sts": "^3.632.0" } }, + "node_modules/@aws-sdk/client-sso-oidc/node_modules/@smithy/types": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.7.2.tgz", + "integrity": "sha512-bNwBYYmN8Eh9RyjS1p2gW6MIhSO2rl7X9QeLM8iTdcGRP+eDiIWDt66c9IysCc22gefKszZv+ubV9qZc7hdESg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-sso/node_modules/@smithy/types": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.7.2.tgz", + "integrity": "sha512-bNwBYYmN8Eh9RyjS1p2gW6MIhSO2rl7X9QeLM8iTdcGRP+eDiIWDt66c9IysCc22gefKszZv+ubV9qZc7hdESg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/@aws-sdk/client-sts": { "version": "3.632.0", "resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.632.0.tgz", @@ -2686,6 +2771,18 @@ "node": ">=16.0.0" } }, + "node_modules/@aws-sdk/client-sts/node_modules/@smithy/types": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.7.2.tgz", + "integrity": "sha512-bNwBYYmN8Eh9RyjS1p2gW6MIhSO2rl7X9QeLM8iTdcGRP+eDiIWDt66c9IysCc22gefKszZv+ubV9qZc7hdESg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/@aws-sdk/core": { "version": "3.629.0", "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.629.0.tgz", @@ -2706,6 +2803,18 @@ "node": ">=16.0.0" } }, + "node_modules/@aws-sdk/core/node_modules/@smithy/types": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.7.2.tgz", + "integrity": "sha512-bNwBYYmN8Eh9RyjS1p2gW6MIhSO2rl7X9QeLM8iTdcGRP+eDiIWDt66c9IysCc22gefKszZv+ubV9qZc7hdESg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/@aws-sdk/credential-provider-env": { "version": "3.620.1", "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.620.1.tgz", @@ -2720,6 +2829,18 @@ "node": ">=16.0.0" } }, + "node_modules/@aws-sdk/credential-provider-env/node_modules/@smithy/types": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.7.2.tgz", + "integrity": "sha512-bNwBYYmN8Eh9RyjS1p2gW6MIhSO2rl7X9QeLM8iTdcGRP+eDiIWDt66c9IysCc22gefKszZv+ubV9qZc7hdESg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/@aws-sdk/credential-provider-http": { "version": "3.622.0", "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.622.0.tgz", @@ -2739,6 +2860,18 @@ "node": ">=16.0.0" } }, + "node_modules/@aws-sdk/credential-provider-http/node_modules/@smithy/types": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.7.2.tgz", + "integrity": "sha512-bNwBYYmN8Eh9RyjS1p2gW6MIhSO2rl7X9QeLM8iTdcGRP+eDiIWDt66c9IysCc22gefKszZv+ubV9qZc7hdESg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/@aws-sdk/credential-provider-ini": { "version": "3.632.0", "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.632.0.tgz", @@ -2763,6 +2896,18 @@ "@aws-sdk/client-sts": "^3.632.0" } }, + "node_modules/@aws-sdk/credential-provider-ini/node_modules/@smithy/types": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.7.2.tgz", + "integrity": "sha512-bNwBYYmN8Eh9RyjS1p2gW6MIhSO2rl7X9QeLM8iTdcGRP+eDiIWDt66c9IysCc22gefKszZv+ubV9qZc7hdESg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/@aws-sdk/credential-provider-node": { "version": "3.632.0", "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.632.0.tgz", @@ -2785,6 +2930,18 @@ "node": ">=16.0.0" } }, + "node_modules/@aws-sdk/credential-provider-node/node_modules/@smithy/types": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.7.2.tgz", + "integrity": "sha512-bNwBYYmN8Eh9RyjS1p2gW6MIhSO2rl7X9QeLM8iTdcGRP+eDiIWDt66c9IysCc22gefKszZv+ubV9qZc7hdESg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/@aws-sdk/credential-provider-process": { "version": "3.620.1", "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.620.1.tgz", @@ -2800,6 +2957,18 @@ "node": ">=16.0.0" } }, + "node_modules/@aws-sdk/credential-provider-process/node_modules/@smithy/types": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.7.2.tgz", + "integrity": "sha512-bNwBYYmN8Eh9RyjS1p2gW6MIhSO2rl7X9QeLM8iTdcGRP+eDiIWDt66c9IysCc22gefKszZv+ubV9qZc7hdESg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/@aws-sdk/credential-provider-sso": { "version": "3.632.0", "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.632.0.tgz", @@ -2817,6 +2986,18 @@ "node": ">=16.0.0" } }, + "node_modules/@aws-sdk/credential-provider-sso/node_modules/@smithy/types": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.7.2.tgz", + "integrity": "sha512-bNwBYYmN8Eh9RyjS1p2gW6MIhSO2rl7X9QeLM8iTdcGRP+eDiIWDt66c9IysCc22gefKszZv+ubV9qZc7hdESg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/@aws-sdk/credential-provider-web-identity": { "version": "3.621.0", "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.621.0.tgz", @@ -2834,6 +3015,18 @@ "@aws-sdk/client-sts": "^3.621.0" } }, + "node_modules/@aws-sdk/credential-provider-web-identity/node_modules/@smithy/types": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.7.2.tgz", + "integrity": "sha512-bNwBYYmN8Eh9RyjS1p2gW6MIhSO2rl7X9QeLM8iTdcGRP+eDiIWDt66c9IysCc22gefKszZv+ubV9qZc7hdESg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/@aws-sdk/middleware-bucket-endpoint": { "version": "3.679.0", "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-bucket-endpoint/-/middleware-bucket-endpoint-3.679.0.tgz", @@ -2865,6 +3058,18 @@ "node": ">=16.0.0" } }, + "node_modules/@aws-sdk/middleware-bucket-endpoint/node_modules/@smithy/types": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.7.2.tgz", + "integrity": "sha512-bNwBYYmN8Eh9RyjS1p2gW6MIhSO2rl7X9QeLM8iTdcGRP+eDiIWDt66c9IysCc22gefKszZv+ubV9qZc7hdESg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/@aws-sdk/middleware-expect-continue": { "version": "3.679.0", "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-expect-continue/-/middleware-expect-continue-3.679.0.tgz", @@ -2893,6 +3098,18 @@ "node": ">=16.0.0" } }, + "node_modules/@aws-sdk/middleware-expect-continue/node_modules/@smithy/types": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.7.2.tgz", + "integrity": "sha512-bNwBYYmN8Eh9RyjS1p2gW6MIhSO2rl7X9QeLM8iTdcGRP+eDiIWDt66c9IysCc22gefKszZv+ubV9qZc7hdESg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/@aws-sdk/middleware-flexible-checksums": { "version": "3.682.0", "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-flexible-checksums/-/middleware-flexible-checksums-3.682.0.tgz", @@ -2950,6 +3167,18 @@ "node": ">=16.0.0" } }, + "node_modules/@aws-sdk/middleware-flexible-checksums/node_modules/@smithy/types": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.7.2.tgz", + "integrity": "sha512-bNwBYYmN8Eh9RyjS1p2gW6MIhSO2rl7X9QeLM8iTdcGRP+eDiIWDt66c9IysCc22gefKszZv+ubV9qZc7hdESg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/@aws-sdk/middleware-host-header": { "version": "3.620.0", "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.620.0.tgz", @@ -2964,6 +3193,18 @@ "node": ">=16.0.0" } }, + "node_modules/@aws-sdk/middleware-host-header/node_modules/@smithy/types": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.7.2.tgz", + "integrity": "sha512-bNwBYYmN8Eh9RyjS1p2gW6MIhSO2rl7X9QeLM8iTdcGRP+eDiIWDt66c9IysCc22gefKszZv+ubV9qZc7hdESg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/@aws-sdk/middleware-location-constraint": { "version": "3.679.0", "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-location-constraint/-/middleware-location-constraint-3.679.0.tgz", @@ -2991,6 +3232,18 @@ "node": ">=16.0.0" } }, + "node_modules/@aws-sdk/middleware-location-constraint/node_modules/@smithy/types": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.7.2.tgz", + "integrity": "sha512-bNwBYYmN8Eh9RyjS1p2gW6MIhSO2rl7X9QeLM8iTdcGRP+eDiIWDt66c9IysCc22gefKszZv+ubV9qZc7hdESg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/@aws-sdk/middleware-logger": { "version": "3.609.0", "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.609.0.tgz", @@ -3004,6 +3257,18 @@ "node": ">=16.0.0" } }, + "node_modules/@aws-sdk/middleware-logger/node_modules/@smithy/types": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.7.2.tgz", + "integrity": "sha512-bNwBYYmN8Eh9RyjS1p2gW6MIhSO2rl7X9QeLM8iTdcGRP+eDiIWDt66c9IysCc22gefKszZv+ubV9qZc7hdESg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/@aws-sdk/middleware-recursion-detection": { "version": "3.620.0", "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.620.0.tgz", @@ -3018,6 +3283,18 @@ "node": ">=16.0.0" } }, + "node_modules/@aws-sdk/middleware-recursion-detection/node_modules/@smithy/types": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.7.2.tgz", + "integrity": "sha512-bNwBYYmN8Eh9RyjS1p2gW6MIhSO2rl7X9QeLM8iTdcGRP+eDiIWDt66c9IysCc22gefKszZv+ubV9qZc7hdESg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/@aws-sdk/middleware-sdk-route53": { "version": "3.804.0", "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-route53/-/middleware-sdk-route53-3.804.0.tgz", @@ -3045,18 +3322,6 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/middleware-sdk-route53/node_modules/@smithy/types": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.2.0.tgz", - "integrity": "sha512-7eMk09zQKCO+E/ivsjQv+fDlOupcFUCSC/L2YUPgwhvowVGWbPQHjEFcmjt7QQ4ra5lyowS92SV53Zc6XD4+fg==", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, "node_modules/@aws-sdk/middleware-sdk-s3": { "version": "3.682.0", "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.682.0.tgz", @@ -3117,6 +3382,18 @@ "node": ">=16.0.0" } }, + "node_modules/@aws-sdk/middleware-sdk-s3/node_modules/@smithy/types": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.7.2.tgz", + "integrity": "sha512-bNwBYYmN8Eh9RyjS1p2gW6MIhSO2rl7X9QeLM8iTdcGRP+eDiIWDt66c9IysCc22gefKszZv+ubV9qZc7hdESg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/@aws-sdk/middleware-ssec": { "version": "3.679.0", "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-ssec/-/middleware-ssec-3.679.0.tgz", @@ -3144,6 +3421,18 @@ "node": ">=16.0.0" } }, + "node_modules/@aws-sdk/middleware-ssec/node_modules/@smithy/types": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.7.2.tgz", + "integrity": "sha512-bNwBYYmN8Eh9RyjS1p2gW6MIhSO2rl7X9QeLM8iTdcGRP+eDiIWDt66c9IysCc22gefKszZv+ubV9qZc7hdESg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/@aws-sdk/middleware-user-agent": { "version": "3.632.0", "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.632.0.tgz", @@ -3159,6 +3448,18 @@ "node": ">=16.0.0" } }, + "node_modules/@aws-sdk/middleware-user-agent/node_modules/@smithy/types": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.7.2.tgz", + "integrity": "sha512-bNwBYYmN8Eh9RyjS1p2gW6MIhSO2rl7X9QeLM8iTdcGRP+eDiIWDt66c9IysCc22gefKszZv+ubV9qZc7hdESg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/@aws-sdk/nested-clients": { "version": "3.810.0", "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.810.0.tgz", @@ -3719,18 +4020,6 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/types": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.2.0.tgz", - "integrity": "sha512-7eMk09zQKCO+E/ivsjQv+fDlOupcFUCSC/L2YUPgwhvowVGWbPQHjEFcmjt7QQ4ra5lyowS92SV53Zc6XD4+fg==", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/url-parser": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.0.2.tgz", @@ -4049,6 +4338,18 @@ "node": ">=16.0.0" } }, + "node_modules/@aws-sdk/region-config-resolver/node_modules/@smithy/types": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.7.2.tgz", + "integrity": "sha512-bNwBYYmN8Eh9RyjS1p2gW6MIhSO2rl7X9QeLM8iTdcGRP+eDiIWDt66c9IysCc22gefKszZv+ubV9qZc7hdESg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/@aws-sdk/signature-v4-multi-region": { "version": "3.682.0", "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.682.0.tgz", @@ -4079,6 +4380,18 @@ "node": ">=16.0.0" } }, + "node_modules/@aws-sdk/signature-v4-multi-region/node_modules/@smithy/types": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.7.2.tgz", + "integrity": "sha512-bNwBYYmN8Eh9RyjS1p2gW6MIhSO2rl7X9QeLM8iTdcGRP+eDiIWDt66c9IysCc22gefKszZv+ubV9qZc7hdESg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/@aws-sdk/token-providers": { "version": "3.614.0", "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.614.0.tgz", @@ -4097,6 +4410,18 @@ "@aws-sdk/client-sso-oidc": "^3.614.0" } }, + "node_modules/@aws-sdk/token-providers/node_modules/@smithy/types": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.7.2.tgz", + "integrity": "sha512-bNwBYYmN8Eh9RyjS1p2gW6MIhSO2rl7X9QeLM8iTdcGRP+eDiIWDt66c9IysCc22gefKszZv+ubV9qZc7hdESg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/@aws-sdk/types": { "version": "3.609.0", "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.609.0.tgz", @@ -4109,6 +4434,18 @@ "node": ">=16.0.0" } }, + "node_modules/@aws-sdk/types/node_modules/@smithy/types": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.7.2.tgz", + "integrity": "sha512-bNwBYYmN8Eh9RyjS1p2gW6MIhSO2rl7X9QeLM8iTdcGRP+eDiIWDt66c9IysCc22gefKszZv+ubV9qZc7hdESg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/@aws-sdk/util-arn-parser": { "version": "3.679.0", "resolved": "https://registry.npmjs.org/@aws-sdk/util-arn-parser/-/util-arn-parser-3.679.0.tgz", @@ -4135,6 +4472,18 @@ "node": ">=16.0.0" } }, + "node_modules/@aws-sdk/util-endpoints/node_modules/@smithy/types": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.7.2.tgz", + "integrity": "sha512-bNwBYYmN8Eh9RyjS1p2gW6MIhSO2rl7X9QeLM8iTdcGRP+eDiIWDt66c9IysCc22gefKszZv+ubV9qZc7hdESg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/@aws-sdk/util-locate-window": { "version": "3.465.0", "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.465.0.tgz", @@ -4157,6 +4506,18 @@ "tslib": "^2.6.2" } }, + "node_modules/@aws-sdk/util-user-agent-browser/node_modules/@smithy/types": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.7.2.tgz", + "integrity": "sha512-bNwBYYmN8Eh9RyjS1p2gW6MIhSO2rl7X9QeLM8iTdcGRP+eDiIWDt66c9IysCc22gefKszZv+ubV9qZc7hdESg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/@aws-sdk/util-user-agent-node": { "version": "3.614.0", "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.614.0.tgz", @@ -4179,6 +4540,18 @@ } } }, + "node_modules/@aws-sdk/util-user-agent-node/node_modules/@smithy/types": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.7.2.tgz", + "integrity": "sha512-bNwBYYmN8Eh9RyjS1p2gW6MIhSO2rl7X9QeLM8iTdcGRP+eDiIWDt66c9IysCc22gefKszZv+ubV9qZc7hdESg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/@aws-sdk/xml-builder": { "version": "3.679.0", "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.679.0.tgz", @@ -4192,6 +4565,18 @@ "node": ">=16.0.0" } }, + "node_modules/@aws-sdk/xml-builder/node_modules/@smithy/types": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.7.2.tgz", + "integrity": "sha512-bNwBYYmN8Eh9RyjS1p2gW6MIhSO2rl7X9QeLM8iTdcGRP+eDiIWDt66c9IysCc22gefKszZv+ubV9qZc7hdESg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/@azure/abort-controller": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-1.1.0.tgz", @@ -11478,6 +11863,18 @@ "node": ">=16.0.0" } }, + "node_modules/@smithy/abort-controller/node_modules/@smithy/types": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.7.2.tgz", + "integrity": "sha512-bNwBYYmN8Eh9RyjS1p2gW6MIhSO2rl7X9QeLM8iTdcGRP+eDiIWDt66c9IysCc22gefKszZv+ubV9qZc7hdESg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/@smithy/chunked-blob-reader": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@smithy/chunked-blob-reader/-/chunked-blob-reader-4.0.0.tgz", @@ -11513,6 +11910,18 @@ "node": ">=16.0.0" } }, + "node_modules/@smithy/config-resolver/node_modules/@smithy/types": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.7.2.tgz", + "integrity": "sha512-bNwBYYmN8Eh9RyjS1p2gW6MIhSO2rl7X9QeLM8iTdcGRP+eDiIWDt66c9IysCc22gefKszZv+ubV9qZc7hdESg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/@smithy/core": { "version": "2.5.1", "resolved": "https://registry.npmjs.org/@smithy/core/-/core-2.5.1.tgz", @@ -11532,6 +11941,18 @@ "node": ">=16.0.0" } }, + "node_modules/@smithy/core/node_modules/@smithy/types": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.7.2.tgz", + "integrity": "sha512-bNwBYYmN8Eh9RyjS1p2gW6MIhSO2rl7X9QeLM8iTdcGRP+eDiIWDt66c9IysCc22gefKszZv+ubV9qZc7hdESg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/@smithy/credential-provider-imds": { "version": "3.2.5", "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-3.2.5.tgz", @@ -11548,6 +11969,18 @@ "node": ">=16.0.0" } }, + "node_modules/@smithy/credential-provider-imds/node_modules/@smithy/types": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.7.2.tgz", + "integrity": "sha512-bNwBYYmN8Eh9RyjS1p2gW6MIhSO2rl7X9QeLM8iTdcGRP+eDiIWDt66c9IysCc22gefKszZv+ubV9qZc7hdESg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/@smithy/eventstream-codec": { "version": "3.1.7", "resolved": "https://registry.npmjs.org/@smithy/eventstream-codec/-/eventstream-codec-3.1.7.tgz", @@ -11560,6 +11993,18 @@ "tslib": "^2.6.2" } }, + "node_modules/@smithy/eventstream-codec/node_modules/@smithy/types": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.7.2.tgz", + "integrity": "sha512-bNwBYYmN8Eh9RyjS1p2gW6MIhSO2rl7X9QeLM8iTdcGRP+eDiIWDt66c9IysCc22gefKszZv+ubV9qZc7hdESg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/@smithy/eventstream-serde-browser": { "version": "3.0.11", "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-browser/-/eventstream-serde-browser-3.0.11.tgz", @@ -11574,6 +12019,18 @@ "node": ">=16.0.0" } }, + "node_modules/@smithy/eventstream-serde-browser/node_modules/@smithy/types": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.7.2.tgz", + "integrity": "sha512-bNwBYYmN8Eh9RyjS1p2gW6MIhSO2rl7X9QeLM8iTdcGRP+eDiIWDt66c9IysCc22gefKszZv+ubV9qZc7hdESg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/@smithy/eventstream-serde-config-resolver": { "version": "3.0.8", "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-config-resolver/-/eventstream-serde-config-resolver-3.0.8.tgz", @@ -11587,6 +12044,18 @@ "node": ">=16.0.0" } }, + "node_modules/@smithy/eventstream-serde-config-resolver/node_modules/@smithy/types": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.7.2.tgz", + "integrity": "sha512-bNwBYYmN8Eh9RyjS1p2gW6MIhSO2rl7X9QeLM8iTdcGRP+eDiIWDt66c9IysCc22gefKszZv+ubV9qZc7hdESg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/@smithy/eventstream-serde-node": { "version": "3.0.10", "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-node/-/eventstream-serde-node-3.0.10.tgz", @@ -11601,6 +12070,18 @@ "node": ">=16.0.0" } }, + "node_modules/@smithy/eventstream-serde-node/node_modules/@smithy/types": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.7.2.tgz", + "integrity": "sha512-bNwBYYmN8Eh9RyjS1p2gW6MIhSO2rl7X9QeLM8iTdcGRP+eDiIWDt66c9IysCc22gefKszZv+ubV9qZc7hdESg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/@smithy/eventstream-serde-universal": { "version": "3.0.10", "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-universal/-/eventstream-serde-universal-3.0.10.tgz", @@ -11615,6 +12096,18 @@ "node": ">=16.0.0" } }, + "node_modules/@smithy/eventstream-serde-universal/node_modules/@smithy/types": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.7.2.tgz", + "integrity": "sha512-bNwBYYmN8Eh9RyjS1p2gW6MIhSO2rl7X9QeLM8iTdcGRP+eDiIWDt66c9IysCc22gefKszZv+ubV9qZc7hdESg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/@smithy/fetch-http-handler": { "version": "3.2.9", "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-3.2.9.tgz", @@ -11628,6 +12121,18 @@ "tslib": "^2.6.2" } }, + "node_modules/@smithy/fetch-http-handler/node_modules/@smithy/types": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.7.2.tgz", + "integrity": "sha512-bNwBYYmN8Eh9RyjS1p2gW6MIhSO2rl7X9QeLM8iTdcGRP+eDiIWDt66c9IysCc22gefKszZv+ubV9qZc7hdESg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/@smithy/hash-blob-browser": { "version": "3.1.7", "resolved": "https://registry.npmjs.org/@smithy/hash-blob-browser/-/hash-blob-browser-3.1.7.tgz", @@ -11640,6 +12145,18 @@ "tslib": "^2.6.2" } }, + "node_modules/@smithy/hash-blob-browser/node_modules/@smithy/types": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.7.2.tgz", + "integrity": "sha512-bNwBYYmN8Eh9RyjS1p2gW6MIhSO2rl7X9QeLM8iTdcGRP+eDiIWDt66c9IysCc22gefKszZv+ubV9qZc7hdESg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/@smithy/hash-node": { "version": "3.0.8", "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-3.0.8.tgz", @@ -11655,6 +12172,18 @@ "node": ">=16.0.0" } }, + "node_modules/@smithy/hash-node/node_modules/@smithy/types": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.7.2.tgz", + "integrity": "sha512-bNwBYYmN8Eh9RyjS1p2gW6MIhSO2rl7X9QeLM8iTdcGRP+eDiIWDt66c9IysCc22gefKszZv+ubV9qZc7hdESg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/@smithy/hash-stream-node": { "version": "3.1.7", "resolved": "https://registry.npmjs.org/@smithy/hash-stream-node/-/hash-stream-node-3.1.7.tgz", @@ -11669,6 +12198,18 @@ "node": ">=16.0.0" } }, + "node_modules/@smithy/hash-stream-node/node_modules/@smithy/types": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.7.2.tgz", + "integrity": "sha512-bNwBYYmN8Eh9RyjS1p2gW6MIhSO2rl7X9QeLM8iTdcGRP+eDiIWDt66c9IysCc22gefKszZv+ubV9qZc7hdESg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/@smithy/invalid-dependency": { "version": "3.0.8", "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-3.0.8.tgz", @@ -11679,6 +12220,18 @@ "tslib": "^2.6.2" } }, + "node_modules/@smithy/invalid-dependency/node_modules/@smithy/types": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.7.2.tgz", + "integrity": "sha512-bNwBYYmN8Eh9RyjS1p2gW6MIhSO2rl7X9QeLM8iTdcGRP+eDiIWDt66c9IysCc22gefKszZv+ubV9qZc7hdESg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/@smithy/is-array-buffer": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-3.0.0.tgz", @@ -11701,6 +12254,18 @@ "tslib": "^2.6.2" } }, + "node_modules/@smithy/md5-js/node_modules/@smithy/types": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.7.2.tgz", + "integrity": "sha512-bNwBYYmN8Eh9RyjS1p2gW6MIhSO2rl7X9QeLM8iTdcGRP+eDiIWDt66c9IysCc22gefKszZv+ubV9qZc7hdESg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/@smithy/middleware-content-length": { "version": "3.0.10", "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-3.0.10.tgz", @@ -11715,6 +12280,18 @@ "node": ">=16.0.0" } }, + "node_modules/@smithy/middleware-content-length/node_modules/@smithy/types": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.7.2.tgz", + "integrity": "sha512-bNwBYYmN8Eh9RyjS1p2gW6MIhSO2rl7X9QeLM8iTdcGRP+eDiIWDt66c9IysCc22gefKszZv+ubV9qZc7hdESg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/@smithy/middleware-endpoint": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-3.2.1.tgz", @@ -11734,6 +12311,18 @@ "node": ">=16.0.0" } }, + "node_modules/@smithy/middleware-endpoint/node_modules/@smithy/types": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.7.2.tgz", + "integrity": "sha512-bNwBYYmN8Eh9RyjS1p2gW6MIhSO2rl7X9QeLM8iTdcGRP+eDiIWDt66c9IysCc22gefKszZv+ubV9qZc7hdESg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/@smithy/middleware-retry": { "version": "3.0.25", "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-3.0.25.tgz", @@ -11754,6 +12343,18 @@ "node": ">=16.0.0" } }, + "node_modules/@smithy/middleware-retry/node_modules/@smithy/types": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.7.2.tgz", + "integrity": "sha512-bNwBYYmN8Eh9RyjS1p2gW6MIhSO2rl7X9QeLM8iTdcGRP+eDiIWDt66c9IysCc22gefKszZv+ubV9qZc7hdESg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/@smithy/middleware-serde": { "version": "3.0.8", "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-3.0.8.tgz", @@ -11767,6 +12368,18 @@ "node": ">=16.0.0" } }, + "node_modules/@smithy/middleware-serde/node_modules/@smithy/types": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.7.2.tgz", + "integrity": "sha512-bNwBYYmN8Eh9RyjS1p2gW6MIhSO2rl7X9QeLM8iTdcGRP+eDiIWDt66c9IysCc22gefKszZv+ubV9qZc7hdESg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/@smithy/middleware-stack": { "version": "3.0.8", "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-3.0.8.tgz", @@ -11780,6 +12393,18 @@ "node": ">=16.0.0" } }, + "node_modules/@smithy/middleware-stack/node_modules/@smithy/types": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.7.2.tgz", + "integrity": "sha512-bNwBYYmN8Eh9RyjS1p2gW6MIhSO2rl7X9QeLM8iTdcGRP+eDiIWDt66c9IysCc22gefKszZv+ubV9qZc7hdESg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/@smithy/node-config-provider": { "version": "3.1.9", "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-3.1.9.tgz", @@ -11795,6 +12420,18 @@ "node": ">=16.0.0" } }, + "node_modules/@smithy/node-config-provider/node_modules/@smithy/types": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.7.2.tgz", + "integrity": "sha512-bNwBYYmN8Eh9RyjS1p2gW6MIhSO2rl7X9QeLM8iTdcGRP+eDiIWDt66c9IysCc22gefKszZv+ubV9qZc7hdESg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/@smithy/node-http-handler": { "version": "3.2.5", "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-3.2.5.tgz", @@ -11811,6 +12448,18 @@ "node": ">=16.0.0" } }, + "node_modules/@smithy/node-http-handler/node_modules/@smithy/types": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.7.2.tgz", + "integrity": "sha512-bNwBYYmN8Eh9RyjS1p2gW6MIhSO2rl7X9QeLM8iTdcGRP+eDiIWDt66c9IysCc22gefKszZv+ubV9qZc7hdESg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/@smithy/property-provider": { "version": "3.1.8", "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-3.1.8.tgz", @@ -11824,6 +12473,18 @@ "node": ">=16.0.0" } }, + "node_modules/@smithy/property-provider/node_modules/@smithy/types": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.7.2.tgz", + "integrity": "sha512-bNwBYYmN8Eh9RyjS1p2gW6MIhSO2rl7X9QeLM8iTdcGRP+eDiIWDt66c9IysCc22gefKszZv+ubV9qZc7hdESg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/@smithy/protocol-http": { "version": "4.1.5", "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.5.tgz", @@ -11837,6 +12498,18 @@ "node": ">=16.0.0" } }, + "node_modules/@smithy/protocol-http/node_modules/@smithy/types": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.7.2.tgz", + "integrity": "sha512-bNwBYYmN8Eh9RyjS1p2gW6MIhSO2rl7X9QeLM8iTdcGRP+eDiIWDt66c9IysCc22gefKszZv+ubV9qZc7hdESg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/@smithy/querystring-builder": { "version": "3.0.8", "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-3.0.8.tgz", @@ -11851,6 +12524,18 @@ "node": ">=16.0.0" } }, + "node_modules/@smithy/querystring-builder/node_modules/@smithy/types": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.7.2.tgz", + "integrity": "sha512-bNwBYYmN8Eh9RyjS1p2gW6MIhSO2rl7X9QeLM8iTdcGRP+eDiIWDt66c9IysCc22gefKszZv+ubV9qZc7hdESg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/@smithy/querystring-parser": { "version": "3.0.8", "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-3.0.8.tgz", @@ -11864,6 +12549,18 @@ "node": ">=16.0.0" } }, + "node_modules/@smithy/querystring-parser/node_modules/@smithy/types": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.7.2.tgz", + "integrity": "sha512-bNwBYYmN8Eh9RyjS1p2gW6MIhSO2rl7X9QeLM8iTdcGRP+eDiIWDt66c9IysCc22gefKszZv+ubV9qZc7hdESg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/@smithy/service-error-classification": { "version": "3.0.8", "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-3.0.8.tgz", @@ -11876,6 +12573,18 @@ "node": ">=16.0.0" } }, + "node_modules/@smithy/service-error-classification/node_modules/@smithy/types": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.7.2.tgz", + "integrity": "sha512-bNwBYYmN8Eh9RyjS1p2gW6MIhSO2rl7X9QeLM8iTdcGRP+eDiIWDt66c9IysCc22gefKszZv+ubV9qZc7hdESg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/@smithy/shared-ini-file-loader": { "version": "3.1.9", "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-3.1.9.tgz", @@ -11889,6 +12598,18 @@ "node": ">=16.0.0" } }, + "node_modules/@smithy/shared-ini-file-loader/node_modules/@smithy/types": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.7.2.tgz", + "integrity": "sha512-bNwBYYmN8Eh9RyjS1p2gW6MIhSO2rl7X9QeLM8iTdcGRP+eDiIWDt66c9IysCc22gefKszZv+ubV9qZc7hdESg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/@smithy/signature-v4": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-4.2.1.tgz", @@ -11908,6 +12629,18 @@ "node": ">=16.0.0" } }, + "node_modules/@smithy/signature-v4/node_modules/@smithy/types": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.7.2.tgz", + "integrity": "sha512-bNwBYYmN8Eh9RyjS1p2gW6MIhSO2rl7X9QeLM8iTdcGRP+eDiIWDt66c9IysCc22gefKszZv+ubV9qZc7hdESg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/@smithy/smithy-client": { "version": "3.4.2", "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-3.4.2.tgz", @@ -11926,10 +12659,10 @@ "node": ">=16.0.0" } }, - "node_modules/@smithy/types": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.6.0.tgz", - "integrity": "sha512-8VXK/KzOHefoC65yRgCn5vG1cysPJjHnOVt9d0ybFQSmJgQj152vMn4EkYhGuaOmnnZvCPav/KnYyE6/KsNZ2w==", + "node_modules/@smithy/smithy-client/node_modules/@smithy/types": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.7.2.tgz", + "integrity": "sha512-bNwBYYmN8Eh9RyjS1p2gW6MIhSO2rl7X9QeLM8iTdcGRP+eDiIWDt66c9IysCc22gefKszZv+ubV9qZc7hdESg==", "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" @@ -11938,6 +12671,18 @@ "node": ">=16.0.0" } }, + "node_modules/@smithy/types": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.3.1.tgz", + "integrity": "sha512-UqKOQBL2x6+HWl3P+3QqFD4ncKq0I8Nuz9QItGv5WuKuMHuuwlhvqcZCoXGfc+P1QmfJE7VieykoYYmrOoFJxA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/@smithy/url-parser": { "version": "3.0.8", "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-3.0.8.tgz", @@ -11949,6 +12694,18 @@ "tslib": "^2.6.2" } }, + "node_modules/@smithy/url-parser/node_modules/@smithy/types": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.7.2.tgz", + "integrity": "sha512-bNwBYYmN8Eh9RyjS1p2gW6MIhSO2rl7X9QeLM8iTdcGRP+eDiIWDt66c9IysCc22gefKszZv+ubV9qZc7hdESg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/@smithy/util-base64": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-3.0.0.tgz", @@ -12020,6 +12777,18 @@ "node": ">= 10.0.0" } }, + "node_modules/@smithy/util-defaults-mode-browser/node_modules/@smithy/types": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.7.2.tgz", + "integrity": "sha512-bNwBYYmN8Eh9RyjS1p2gW6MIhSO2rl7X9QeLM8iTdcGRP+eDiIWDt66c9IysCc22gefKszZv+ubV9qZc7hdESg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/@smithy/util-defaults-mode-node": { "version": "3.0.25", "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-3.0.25.tgz", @@ -12038,6 +12807,18 @@ "node": ">= 10.0.0" } }, + "node_modules/@smithy/util-defaults-mode-node/node_modules/@smithy/types": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.7.2.tgz", + "integrity": "sha512-bNwBYYmN8Eh9RyjS1p2gW6MIhSO2rl7X9QeLM8iTdcGRP+eDiIWDt66c9IysCc22gefKszZv+ubV9qZc7hdESg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/@smithy/util-endpoints": { "version": "2.1.4", "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-2.1.4.tgz", @@ -12052,6 +12833,18 @@ "node": ">=16.0.0" } }, + "node_modules/@smithy/util-endpoints/node_modules/@smithy/types": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.7.2.tgz", + "integrity": "sha512-bNwBYYmN8Eh9RyjS1p2gW6MIhSO2rl7X9QeLM8iTdcGRP+eDiIWDt66c9IysCc22gefKszZv+ubV9qZc7hdESg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/@smithy/util-hex-encoding": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-3.0.0.tgz", @@ -12076,6 +12869,18 @@ "node": ">=16.0.0" } }, + "node_modules/@smithy/util-middleware/node_modules/@smithy/types": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.7.2.tgz", + "integrity": "sha512-bNwBYYmN8Eh9RyjS1p2gW6MIhSO2rl7X9QeLM8iTdcGRP+eDiIWDt66c9IysCc22gefKszZv+ubV9qZc7hdESg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/@smithy/util-retry": { "version": "3.0.8", "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-3.0.8.tgz", @@ -12090,6 +12895,18 @@ "node": ">=16.0.0" } }, + "node_modules/@smithy/util-retry/node_modules/@smithy/types": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.7.2.tgz", + "integrity": "sha512-bNwBYYmN8Eh9RyjS1p2gW6MIhSO2rl7X9QeLM8iTdcGRP+eDiIWDt66c9IysCc22gefKszZv+ubV9qZc7hdESg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/@smithy/util-stream": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-3.2.1.tgz", @@ -12122,6 +12939,18 @@ "tslib": "^2.6.2" } }, + "node_modules/@smithy/util-stream/node_modules/@smithy/types": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.7.2.tgz", + "integrity": "sha512-bNwBYYmN8Eh9RyjS1p2gW6MIhSO2rl7X9QeLM8iTdcGRP+eDiIWDt66c9IysCc22gefKszZv+ubV9qZc7hdESg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/@smithy/util-uri-escape": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-3.0.0.tgz", @@ -12159,6 +12988,18 @@ "node": ">=16.0.0" } }, + "node_modules/@smithy/util-waiter/node_modules/@smithy/types": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.7.2.tgz", + "integrity": "sha512-bNwBYYmN8Eh9RyjS1p2gW6MIhSO2rl7X9QeLM8iTdcGRP+eDiIWDt66c9IysCc22gefKszZv+ubV9qZc7hdESg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/@socket.io/component-emitter": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz", diff --git a/backend/package.json b/backend/package.json index 8adc2bbef..bd7eb6b12 100644 --- a/backend/package.json +++ b/backend/package.json @@ -84,6 +84,7 @@ "@babel/plugin-syntax-import-attributes": "^7.24.7", "@babel/preset-env": "^7.18.10", "@babel/preset-react": "^7.24.7", + "@smithy/types": "^4.3.1", "@types/bcrypt": "^5.0.2", "@types/crypto-js": "^4.2.2", "@types/jmespath": "^0.15.2", diff --git a/backend/src/ee/services/dynamic-secret/providers/aws-elasticache.ts b/backend/src/ee/services/dynamic-secret/providers/aws-elasticache.ts index 89371f1bd..b5d94112a 100644 --- a/backend/src/ee/services/dynamic-secret/providers/aws-elasticache.ts +++ b/backend/src/ee/services/dynamic-secret/providers/aws-elasticache.ts @@ -12,6 +12,8 @@ import handlebars from "handlebars"; import { customAlphabet } from "nanoid"; import { z } from "zod"; +import { CustomAWSHasher } from "@app/lib/aws/hashing"; +import { crypto } from "@app/lib/crypto"; import { BadRequestError } from "@app/lib/errors"; import { validateHandlebarTemplate } from "@app/lib/template/validate-handlebars"; @@ -39,8 +41,11 @@ type TDeleteElastiCacheUserInput = z.infer; const ElastiCacheUserManager = (credentials: TBasicAWSCredentials, region: string) => { const elastiCache = new ElastiCache({ region, + useFipsEndpoint: crypto.isFipsModeEnabled(), + sha256: CustomAWSHasher, credentials }); + const infisicalGroup = "infisical-managed-group-elasticache"; const ensureInfisicalGroupExists = async (clusterName: string) => { 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 cb8da4d13..e3be93aff 100644 --- a/backend/src/ee/services/dynamic-secret/providers/aws-iam.ts +++ b/backend/src/ee/services/dynamic-secret/providers/aws-iam.ts @@ -19,6 +19,7 @@ import { import { AssumeRoleCommand, STSClient } from "@aws-sdk/client-sts"; import { z } from "zod"; +import { CustomAWSHasher } from "@app/lib/aws/hashing"; import { getConfig } from "@app/lib/config/env"; import { crypto } from "@app/lib/crypto/cryptography"; import { BadRequestError } from "@app/lib/errors"; @@ -49,6 +50,8 @@ export const AwsIamProvider = (): TDynamicProviderFns => { if (providerInputs.method === AwsIamAuthType.AssumeRole) { const stsClient = new STSClient({ region: providerInputs.region, + useFipsEndpoint: crypto.isFipsModeEnabled(), + sha256: CustomAWSHasher, credentials: appCfg.DYNAMIC_SECRET_AWS_ACCESS_KEY_ID && appCfg.DYNAMIC_SECRET_AWS_SECRET_ACCESS_KEY ? { @@ -72,6 +75,8 @@ export const AwsIamProvider = (): TDynamicProviderFns => { } const client = new IAMClient({ region: providerInputs.region, + useFipsEndpoint: crypto.isFipsModeEnabled(), + sha256: CustomAWSHasher, credentials: { accessKeyId: assumeRes.Credentials?.AccessKeyId, secretAccessKey: assumeRes.Credentials?.SecretAccessKey, @@ -83,6 +88,8 @@ export const AwsIamProvider = (): TDynamicProviderFns => { const client = new IAMClient({ region: providerInputs.region, + useFipsEndpoint: crypto.isFipsModeEnabled(), + sha256: CustomAWSHasher, credentials: { accessKeyId: providerInputs.accessKey, secretAccessKey: providerInputs.secretAccessKey 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 2aba6db8a..f9812229f 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 { CustomAWSHasher } from "@app/lib/aws/hashing"; import { crypto } from "@app/lib/crypto/cryptography"; import { ExternalKmsAwsSchema, KmsAwsCredentialType, TExternalKmsAwsSchema, TExternalKmsProviderFns } from "./model"; @@ -9,7 +10,9 @@ const getAwsKmsClient = async (providerInputs: TExternalKmsAwsSchema) => { if (providerInputs.credential.type === KmsAwsCredentialType.AssumeRole) { const awsCredential = providerInputs.credential.data; const stsClient = new STSClient({ - region: providerInputs.awsRegion + region: providerInputs.awsRegion, + useFipsEndpoint: crypto.isFipsModeEnabled(), + sha256: CustomAWSHasher }); const command = new AssumeRoleCommand({ RoleArn: awsCredential.assumeRoleArn, @@ -23,6 +26,8 @@ const getAwsKmsClient = async (providerInputs: TExternalKmsAwsSchema) => { const kmsClient = new KMSClient({ region: providerInputs.awsRegion, + useFipsEndpoint: crypto.isFipsModeEnabled(), + sha256: CustomAWSHasher, credentials: { accessKeyId: response.Credentials.AccessKeyId, secretAccessKey: response.Credentials.SecretAccessKey, @@ -35,6 +40,8 @@ const getAwsKmsClient = async (providerInputs: TExternalKmsAwsSchema) => { const awsCredential = providerInputs.credential.data; const kmsClient = new KMSClient({ region: providerInputs.awsRegion, + useFipsEndpoint: crypto.isFipsModeEnabled(), + sha256: CustomAWSHasher, credentials: { accessKeyId: awsCredential.accessKey, secretAccessKey: awsCredential.secretKey diff --git a/backend/src/ee/services/license/license-fns.ts b/backend/src/ee/services/license/license-fns.ts index 2b4054e1e..1ffe69bb7 100644 --- a/backend/src/ee/services/license/license-fns.ts +++ b/backend/src/ee/services/license/license-fns.ts @@ -18,7 +18,7 @@ export const getDefaultOnPremFeatures = (): TFeatureSet => ({ environmentsUsed: 0, identityLimit: null, identitiesUsed: 0, - dynamicSecret: false, + dynamicSecret: true, secretVersioning: true, pitRecovery: false, ipAllowlisting: false, 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 4ce32e3b2..8a02d3cd3 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 @@ -6,6 +6,7 @@ import { } from "@aws-sdk/client-iam"; import { SecretType } from "@app/db/schemas"; +import { CustomAWSHasher } from "@app/lib/aws/hashing"; import { getConfig } from "@app/lib/config/env"; import { crypto, SymmetricKeySize } from "@app/lib/crypto/cryptography"; import { daysToMillisecond, secondsToMillis } from "@app/lib/dates"; @@ -226,6 +227,8 @@ export const secretRotationQueueFactory = ({ if (provider.template.type === TProviderFunctionTypes.AWS) { if (provider.template.client === TAwsProviderSystems.IAM) { const client = new IAMClient({ + useFipsEndpoint: crypto.isFipsModeEnabled(), + sha256: CustomAWSHasher, region: newCredential.inputs.manager_user_aws_region as string, credentials: { accessKeyId: newCredential.inputs.manager_user_access_key as string, diff --git a/backend/src/lib/aws/hashing.ts b/backend/src/lib/aws/hashing.ts new file mode 100644 index 000000000..5ad9d681d --- /dev/null +++ b/backend/src/lib/aws/hashing.ts @@ -0,0 +1,57 @@ +/* eslint-disable no-underscore-dangle */ +import type { SourceData } from "@smithy/types"; +import { Hash, Hmac } from "crypto"; + +import { crypto } from "@app/lib/crypto"; + +export class CustomAWSHasher { + public algorithmIdentifier: string = "sha256"; + + public secret: SourceData | undefined; + + public hash: Hash | Hmac | undefined; + + private _hash: Hash | Hmac | undefined; + + constructor(secret?: SourceData) { + this.secret = secret; + this.reset(); + } + + reset() { + if (this.secret) { + // Convert any secret type to Buffer + let secretBuffer = this.secret as Buffer; + if (this.secret instanceof ArrayBuffer) { + secretBuffer = Buffer.from(this.secret); + } else if (ArrayBuffer.isView && ArrayBuffer.isView(this.secret)) { + secretBuffer = Buffer.from(this.secret.buffer, this.secret.byteOffset, this.secret.byteLength); + } + this._hash = crypto.rawCrypto.createHmac(this.algorithmIdentifier, secretBuffer); + } else { + this._hash = crypto.rawCrypto.createHash(this.algorithmIdentifier); + } + return this; + } + + update(data: SourceData) { + // Handle all possible data types + let buffer: Buffer = data as Buffer; + if (typeof data === "string") { + buffer = Buffer.from(data, "utf8"); + } else if (data instanceof ArrayBuffer) { + buffer = Buffer.from(data); + } else if (ArrayBuffer.isView && ArrayBuffer.isView(data)) { + buffer = Buffer.from(data.buffer, data.byteOffset, data.byteLength); + } + + this._hash?.update(buffer); + return this; + } + + digest(): Promise { + const result = new Uint8Array(this._hash?.digest() || []); + this.reset(); + return Promise.resolve(result); + } +} diff --git a/backend/src/lib/crypto/cryptography.ts b/backend/src/lib/crypto/cryptography.ts index 3a44e5ce2..7e6fe2a80 100644 --- a/backend/src/lib/crypto/cryptography.ts +++ b/backend/src/lib/crypto/cryptography.ts @@ -193,13 +193,12 @@ const encryptAsymmetricFipsValidated = (data: string, publicKey: string, private format: "der" }); - // Generate shared secret using X25519 + // Generate shared secret using x25519 curve 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 @@ -209,7 +208,7 @@ const encryptAsymmetricFipsValidated = (data: string, publicKey: string, private const iv = nonce.subarray(0, 12); // Encrypt with AES-256-GCM - const cipher = crypto.createCipheriv("aes-256-gcm", key, iv); + const cipher = crypto.createCipheriv(SecretEncryptionAlgo.AES_256_GCM, key, iv); const ciphertext = cipher.update(data, "utf8"); cipher.final(); @@ -665,7 +664,8 @@ const cryptographyFactory = () => { }, constants: crypto.constants, X509Certificate: crypto.X509Certificate, - KeyObject: crypto.KeyObject + KeyObject: crypto.KeyObject, + Hash: crypto.Hash } }; }; 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 2bcf75937..c647dc2c2 100644 --- a/backend/src/services/app-connection/aws/aws-connection-fns.ts +++ b/backend/src/services/app-connection/aws/aws-connection-fns.ts @@ -2,6 +2,7 @@ import { AssumeRoleCommand, STSClient } from "@aws-sdk/client-sts"; import AWS from "aws-sdk"; import { AxiosError } from "axios"; +import { CustomAWSHasher } from "@app/lib/aws/hashing"; import { getConfig } from "@app/lib/config/env"; import { crypto } from "@app/lib/crypto/cryptography"; import { BadRequestError, InternalServerError } from "@app/lib/errors"; @@ -35,6 +36,8 @@ export const getAwsConnectionConfig = async (appConnection: TAwsConnectionConfig case AwsConnectionMethod.AssumeRole: { const client = new STSClient({ region, + useFipsEndpoint: crypto.isFipsModeEnabled(), + sha256: CustomAWSHasher, credentials: appCfg.INF_APP_CONNECTION_AWS_ACCESS_KEY_ID && appCfg.INF_APP_CONNECTION_AWS_SECRET_ACCESS_KEY ? { 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 75f95c38a..46a55821d 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 @@ -3,6 +3,7 @@ import * as x509 from "@peculiar/x509"; import acme from "acme-client"; import { TableName } from "@app/db/schemas"; +import { CustomAWSHasher } from "@app/lib/aws/hashing"; import { crypto } from "@app/lib/crypto/cryptography"; import { BadRequestError, NotFoundError } from "@app/lib/errors"; import { OrgServiceActor } from "@app/lib/types"; @@ -102,6 +103,8 @@ export const route53InsertTxtRecord = async ( ) => { const config = await getAwsConnectionConfig(connection, AWSRegion.US_WEST_1); // REGION is irrelevant because Route53 is global const route53Client = new Route53Client({ + sha256: CustomAWSHasher, + useFipsEndpoint: crypto.isFipsModeEnabled(), credentials: config.credentials!, region: config.region }); diff --git a/backend/src/services/integration-auth/integration-sync-secret.ts b/backend/src/services/integration-auth/integration-sync-secret.ts index 072313693..9845c3da0 100644 --- a/backend/src/services/integration-auth/integration-sync-secret.ts +++ b/backend/src/services/integration-auth/integration-sync-secret.ts @@ -30,6 +30,7 @@ import RE2 from "re2"; import { z } from "zod"; import { SecretType, TIntegrationAuths, TIntegrations } from "@app/db/schemas"; +import { CustomAWSHasher } from "@app/lib/aws/hashing"; import { getConfig } from "@app/lib/config/env"; import { request } from "@app/lib/config/request"; import { crypto } from "@app/lib/crypto/cryptography"; @@ -796,6 +797,8 @@ const syncSecretsAWSParameterStore = async ({ if (awsAssumeRoleArn) { const client = new STSClient({ region: integration.region as string, + useFipsEndpoint: crypto.isFipsModeEnabled(), + sha256: CustomAWSHasher, credentials: appCfg.CLIENT_ID_AWS_INTEGRATION && appCfg.CLIENT_SECRET_AWS_INTEGRATION ? { diff --git a/backend/src/services/secret-sync/aws-secrets-manager/aws-secrets-manager-sync-fns.ts b/backend/src/services/secret-sync/aws-secrets-manager/aws-secrets-manager-sync-fns.ts index a6415ac00..8e37cf277 100644 --- a/backend/src/services/secret-sync/aws-secrets-manager/aws-secrets-manager-sync-fns.ts +++ b/backend/src/services/secret-sync/aws-secrets-manager/aws-secrets-manager-sync-fns.ts @@ -24,6 +24,8 @@ import { Tag } from "aws-sdk/clients/secretsmanager"; +import { CustomAWSHasher } from "@app/lib/aws/hashing"; +import { crypto } from "@app/lib/crypto"; import { getAwsConnectionConfig } from "@app/services/app-connection/aws/aws-connection-fns"; import { AwsSecretsManagerSyncMappingBehavior } from "@app/services/secret-sync/aws-secrets-manager/aws-secrets-manager-sync-enums"; import { SecretSyncError } from "@app/services/secret-sync/secret-sync-errors"; @@ -46,6 +48,8 @@ const getSecretsManagerClient = async (secretSync: TAwsSecretsManagerSyncWithCre const secretsManagerClient = new SecretsManagerClient({ region: config.region, + useFipsEndpoint: crypto.isFipsModeEnabled(), + sha256: CustomAWSHasher, credentials: config.credentials! }); diff --git a/frontend/src/pages/admin/EncryptionPage/components/EncryptionPageForm.tsx b/frontend/src/pages/admin/EncryptionPage/components/EncryptionPageForm.tsx index 5db679c61..69d971dd5 100644 --- a/frontend/src/pages/admin/EncryptionPage/components/EncryptionPageForm.tsx +++ b/frontend/src/pages/admin/EncryptionPage/components/EncryptionPageForm.tsx @@ -1,12 +1,14 @@ import { useCallback } from "react"; import { Controller, useForm } from "react-hook-form"; +import { faInfoCircle } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { zodResolver } from "@hookform/resolvers/zod"; import { z } from "zod"; import { UpgradePlanModal } from "@app/components/license/UpgradePlanModal"; import { createNotification } from "@app/components/notifications"; -import { Button, FormControl, Select, SelectItem } from "@app/components/v2"; -import { useSubscription } from "@app/context"; +import { Badge, Button, FormControl, Select, SelectItem, Tooltip } from "@app/components/v2"; +import { useServerConfig, useSubscription } from "@app/context"; import { usePopUp } from "@app/hooks"; import { useGetServerRootKmsEncryptionDetails, @@ -29,6 +31,7 @@ export const EncryptionPageForm = () => { const { data: rootKmsDetails } = useGetServerRootKmsEncryptionDetails(); const { mutateAsync: updateEncryptionStrategy } = useUpdateServerEncryptionStrategy(); + const { config } = useServerConfig(); const { subscription } = useSubscription(); const { popUp, handlePopUpOpen, handlePopUpToggle } = usePopUp(["upgradePlan"] as const); @@ -117,14 +120,27 @@ export const EncryptionPageForm = () => { )} - +
+ + + {config.fipsEnabled && ( + +
+ + FIPS Mode: Enabled + + +
+
+ )} +
Date: Mon, 7 Jul 2025 21:58:46 +0400 Subject: [PATCH 05/27] fix: type checks --- backend/src/ee/services/license/license-fns.ts | 4 ++-- frontend/src/hooks/api/types.ts | 2 +- frontend/src/hooks/api/users/types.ts | 1 - 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/backend/src/ee/services/license/license-fns.ts b/backend/src/ee/services/license/license-fns.ts index 1ffe69bb7..5b755567b 100644 --- a/backend/src/ee/services/license/license-fns.ts +++ b/backend/src/ee/services/license/license-fns.ts @@ -18,7 +18,7 @@ export const getDefaultOnPremFeatures = (): TFeatureSet => ({ environmentsUsed: 0, identityLimit: null, identitiesUsed: 0, - dynamicSecret: true, + dynamicSecret: false, secretVersioning: true, pitRecovery: false, ipAllowlisting: false, @@ -59,7 +59,7 @@ export const getDefaultOnPremFeatures = (): TFeatureSet => ({ secretScanning: false, enterpriseSecretSyncs: false, enterpriseAppConnections: false, - fips: true + fips: false }); export const setupLicenseRequestWithStore = ( diff --git a/frontend/src/hooks/api/types.ts b/frontend/src/hooks/api/types.ts index 8f2acbbd7..8c29d4825 100644 --- a/frontend/src/hooks/api/types.ts +++ b/frontend/src/hooks/api/types.ts @@ -27,7 +27,7 @@ export * from "./secrets/types"; export type { CreateServiceTokenDTO, ServiceToken } from "./serviceTokens/types"; export type { SubscriptionPlan } from "./subscriptions/types"; export type { WsTag } from "./tags/types"; -export type { AddUserToWsDTOE2EE, OrgUser, TWorkspaceUser, User, UserEnc } from "./users/types"; +export type { OrgUser, TWorkspaceUser, User, UserEnc } from "./users/types"; export type { TWebhook } from "./webhooks/types"; export type { CreateEnvironmentDTO, diff --git a/frontend/src/hooks/api/users/types.ts b/frontend/src/hooks/api/users/types.ts index 544acc225..963ab100e 100644 --- a/frontend/src/hooks/api/users/types.ts +++ b/frontend/src/hooks/api/users/types.ts @@ -1,5 +1,4 @@ import { MfaMethod } from "../auth/types"; -import { UserWsKeyPair } from "../keys/types"; import { ProjectType, ProjectUserMembershipTemporaryMode } from "../workspace/types"; export enum AuthMethod { From 0662f62b016ecc21577078a2e5134c721fc53f96 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard Date: Mon, 7 Jul 2025 22:00:46 +0400 Subject: [PATCH 06/27] Update env.ts --- backend/src/lib/config/env.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/backend/src/lib/config/env.ts b/backend/src/lib/config/env.ts index 8c9318d8b..11b7ecedc 100644 --- a/backend/src/lib/config/env.ts +++ b/backend/src/lib/config/env.ts @@ -394,9 +394,9 @@ export const getDatabaseCredentials = (logger?: CustomLogger) => { } return { - dbConnectionUri: envCfg.DB_CONNECTION_URI, - dbRootCert: envCfg.DB_ROOT_CERT, - readReplicas: envCfg.DB_READ_REPLICAS?.map((el) => ({ + dbConnectionUri: parsedEnv.data.DB_CONNECTION_URI, + dbRootCert: parsedEnv.data.DB_ROOT_CERT, + readReplicas: parsedEnv.data.DB_READ_REPLICAS?.map((el) => ({ dbRootCert: el.DB_ROOT_CERT, dbConnectionUri: el.DB_CONNECTION_URI })) From ca18776932303924d53b09c5a3c0f5b15ed11746 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard Date: Mon, 7 Jul 2025 22:03:51 +0400 Subject: [PATCH 07/27] Update cryptography.ts --- backend/src/lib/crypto/cryptography.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/backend/src/lib/crypto/cryptography.ts b/backend/src/lib/crypto/cryptography.ts index 7e6fe2a80..2bbb9eadd 100644 --- a/backend/src/lib/crypto/cryptography.ts +++ b/backend/src/lib/crypto/cryptography.ts @@ -360,13 +360,15 @@ const cryptographyFactory = () => { } }; - const isFipsModeEnabled = () => { - $checkIsInitialized(); + const isFipsModeEnabled = (options: { skipInitializationCheck?: boolean } = {}) => { + if (!options?.skipInitializationCheck) { + $checkIsInitialized(); + } return $fipsEnabled; }; const verifyFipsLicense = (licenseService: Pick) => { - if (isFipsModeEnabled() && !licenseService.onPremFeatures?.fips) { + if (isFipsModeEnabled({ skipInitializationCheck: true }) && !licenseService.onPremFeatures?.fips) { throw new CryptographyError({ message: "FIPS mode is enabled but your license does not include FIPS support. Please contact support." }); From 6bfcc59486ec95149ba4139efc5e184083bbe43b Mon Sep 17 00:00:00 2001 From: Daniel Hougaard Date: Mon, 7 Jul 2025 22:14:55 +0400 Subject: [PATCH 08/27] fix: seeding fails --- backend/src/db/seeds/1-user.ts | 9 +++++++++ backend/src/lib/crypto/cryptography.ts | 1 + 2 files changed, 10 insertions(+) diff --git a/backend/src/db/seeds/1-user.ts b/backend/src/db/seeds/1-user.ts index 86cd2be34..5c6245382 100644 --- a/backend/src/db/seeds/1-user.ts +++ b/backend/src/db/seeds/1-user.ts @@ -1,5 +1,9 @@ import { Knex } from "knex"; +import { crypto } from "@app/lib/crypto"; +import { initLogger } from "@app/lib/logger"; +import { superAdminDALFactory } from "@app/services/super-admin/super-admin-dal"; + import { AuthMethod } from "../../services/auth/auth-type"; import { TableName } from "../schemas"; import { generateUserSrpKeys, seedData1 } from "../seed-data"; @@ -10,6 +14,11 @@ export async function seed(knex: Knex): Promise { await knex(TableName.UserEncryptionKey).del(); await knex(TableName.SuperAdmin).del(); + initLogger(); + + const superAdminDAL = superAdminDALFactory(knex); + await crypto.initialize(superAdminDAL); + await knex(TableName.SuperAdmin).insert([ // eslint-disable-next-line // @ts-ignore diff --git a/backend/src/lib/crypto/cryptography.ts b/backend/src/lib/crypto/cryptography.ts index 2bbb9eadd..4caee64fe 100644 --- a/backend/src/lib/crypto/cryptography.ts +++ b/backend/src/lib/crypto/cryptography.ts @@ -415,6 +415,7 @@ const cryptographyFactory = () => { if (process.env.FIPS_ENABLED !== "true") { logger.info("[FIPS]: Instance is running in non-FIPS mode."); + $setFipsModeEnabled(false); return false; } From 6dfe5854ea0d918adaeebec2bd9c4505cdf0cd58 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard Date: Tue, 8 Jul 2025 12:09:56 +0400 Subject: [PATCH 09/27] fix: tests failing --- .../e2e-test/routes/v2/service-token.spec.ts | 17 ++++++++++++----- backend/e2e-test/routes/v3/secrets.spec.ts | 12 +++++++++--- backend/e2e-test/vitest-environment-knex.ts | 5 +++-- backend/src/@types/fastify-zod.d.ts | 4 ++-- .../20250602155451_fix-secret-versions.ts | 4 +++- backend/src/lib/crypto/cryptography.ts | 1 - 6 files changed, 29 insertions(+), 14 deletions(-) diff --git a/backend/e2e-test/routes/v2/service-token.spec.ts b/backend/e2e-test/routes/v2/service-token.spec.ts index 13aaabcd3..72a9c4433 100644 --- a/backend/e2e-test/routes/v2/service-token.spec.ts +++ b/backend/e2e-test/routes/v2/service-token.spec.ts @@ -1,6 +1,9 @@ import { SecretType, TSecrets } from "@app/db/schemas"; import { decryptSecret, encryptSecret, getUserPrivateKey, seedData1 } from "@app/db/seed-data"; +import { initEnvConfig } from "@app/lib/config/env"; import { SymmetricKeySize } from "@app/lib/crypto"; +import { crypto } from "@app/lib/crypto/cryptography"; +import { initLogger, logger } from "@app/lib/logger"; const createServiceToken = async ( scopes: { environment: string; secretPath: string }[], @@ -24,16 +27,17 @@ const createServiceToken = async ( }); const { user: userInfo } = JSON.parse(userInfoRes.payload); const privateKey = await getUserPrivateKey(seedData1.password, userInfo); - const projectKey = testCryptoProvider.encryption().asymmetric().decrypt({ + + const projectKey = crypto.encryption().asymmetric().decrypt({ ciphertext: projectKeyEnc.encryptedKey, nonce: projectKeyEnc.nonce, publicKey: projectKeyEnc.sender.publicKey, privateKey }); - const randomBytes = testCryptoProvider.randomBytes(16).toString("hex"); + const randomBytes = crypto.randomBytes(16).toString("hex"); - const { ciphertext, iv, tag } = testCryptoProvider.encryption().encryptSymmetric({ + const { ciphertext, iv, tag } = crypto.encryption().encryptSymmetric({ plaintext: projectKey, key: randomBytes, keySize: SymmetricKeySize.Bits128 @@ -141,6 +145,9 @@ describe("Service token secret ops", async () => { let projectKey = ""; let folderId = ""; beforeAll(async () => { + initLogger(); + await initEnvConfig(testSuperAdminDAL, logger); + serviceToken = await createServiceToken( [{ secretPath: "/**", environment: seedData1.environment.slug }], ["read", "write"] @@ -158,7 +165,7 @@ describe("Service token secret ops", async () => { const serviceTokenInfo = serviceTokenInfoRes.json(); const serviceTokenParts = serviceToken.split("."); - projectKey = testCryptoProvider.encryption().decryptSymmetric({ + projectKey = crypto.encryption().decryptSymmetric({ key: serviceTokenParts[3], tag: serviceTokenInfo.tag, ciphertext: serviceTokenInfo.encryptedKey, @@ -557,7 +564,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(testCryptoProvider.randomBytes(16).toString("hex"), "NEW", "value", "") + ...encryptSecret(crypto.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 c99c50684..1e58c7f4a 100644 --- a/backend/e2e-test/routes/v3/secrets.spec.ts +++ b/backend/e2e-test/routes/v3/secrets.spec.ts @@ -1,5 +1,8 @@ import { SecretType, TSecrets } from "@app/db/schemas"; import { decryptSecret, encryptSecret, getUserPrivateKey, seedData1 } from "@app/db/seed-data"; +import { initEnvConfig } from "@app/lib/config/env"; +import { crypto } from "@app/lib/crypto/cryptography"; +import { initLogger, logger } from "@app/lib/logger"; import { AuthMode } from "@app/services/auth/auth-type"; const createSecret = async (dto: { @@ -154,6 +157,9 @@ describe("Secret V3 Router", async () => { let projectKey = ""; let folderId = ""; beforeAll(async () => { + initLogger(); + await initEnvConfig(testSuperAdminDAL, logger); + const projectKeyRes = await testServer.inject({ method: "GET", url: `/api/v2/workspace/${seedData1.project.id}/encrypted-key`, @@ -172,7 +178,7 @@ describe("Secret V3 Router", async () => { }); const { user: userInfo } = JSON.parse(userInfoRes.payload); const privateKey = await getUserPrivateKey(seedData1.password, userInfo); - projectKey = testCryptoProvider.encryption().asymmetric().decrypt({ + projectKey = crypto.encryption().asymmetric().decrypt({ ciphertext: projectKeyEncryptionDetails.encryptedKey, nonce: projectKeyEncryptionDetails.nonce, publicKey: projectKeyEncryptionDetails.sender.publicKey, @@ -668,7 +674,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 = testCryptoProvider.encryption().asymmetric().decrypt({ + const projectKey = crypto.encryption().asymmetric().decrypt({ ciphertext: projectKeyEnc.encryptedKey, nonce: projectKeyEnc.nonce, publicKey: projectKeyEnc.sender.publicKey, @@ -684,7 +690,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 = testCryptoProvider.encryption().asymmetric().encrypt(projectKey, projectBot.publicKey, privateKey); + const botKey = crypto.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 74276001d..289945f17 100644 --- a/backend/e2e-test/vitest-environment-knex.ts +++ b/backend/e2e-test/vitest-environment-knex.ts @@ -18,7 +18,6 @@ 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 { @@ -82,7 +81,7 @@ export default { // @ts-expect-error type globalThis.testServer = server; // @ts-expect-error type - globalThis.testCryptoProvider = crypto; + globalThis.testSuperAdminDAL = superAdminDAL; // @ts-expect-error type globalThis.jwtAuthToken = jwt.sign( { @@ -111,6 +110,8 @@ export default { // @ts-expect-error type delete globalThis.testServer; // @ts-expect-error type + delete globalThis.testSuperAdminDAL; + // @ts-expect-error type delete globalThis.jwtToken; // called after all tests with this env have been run await db.migrate.rollback( diff --git a/backend/src/@types/fastify-zod.d.ts b/backend/src/@types/fastify-zod.d.ts index 366046e9c..f0240d1a0 100644 --- a/backend/src/@types/fastify-zod.d.ts +++ b/backend/src/@types/fastify-zod.d.ts @@ -1,8 +1,8 @@ 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"; +import { TSuperAdminDALFactory } from "@app/services/super-admin/super-admin-dal"; declare global { type FastifyZodProvider = FastifyInstance< @@ -15,6 +15,6 @@ declare global { // used only for testing const testServer: FastifyZodProvider; - const testCryptoProvider: TCryptographyFactory; + const testSuperAdminDAL: TSuperAdminDALFactory; const jwtAuthToken: string; } diff --git a/backend/src/db/migrations/20250602155451_fix-secret-versions.ts b/backend/src/db/migrations/20250602155451_fix-secret-versions.ts index f525e85f3..94be8dad4 100644 --- a/backend/src/db/migrations/20250602155451_fix-secret-versions.ts +++ b/backend/src/db/migrations/20250602155451_fix-secret-versions.ts @@ -3,11 +3,12 @@ import { Knex } from "knex"; import { chunkArray } from "@app/lib/fn"; import { selectAllTableCols } from "@app/lib/knex"; -import { logger } from "@app/lib/logger"; +import { initLogger, logger } from "@app/lib/logger"; import { SecretType, TableName } from "../schemas"; export async function up(knex: Knex): Promise { + initLogger(); logger.info("Starting secret version fix migration"); // Get all shared secret IDs first to optimize versions query @@ -133,6 +134,7 @@ export async function up(knex: Knex): Promise { } export async function down(): Promise { + initLogger(); logger.info("Rollback not implemented for secret version fix migration"); // Note: Rolling back this migration would be complex and potentially destructive // as it would require tracking which version entries were added diff --git a/backend/src/lib/crypto/cryptography.ts b/backend/src/lib/crypto/cryptography.ts index 4caee64fe..d35991c49 100644 --- a/backend/src/lib/crypto/cryptography.ts +++ b/backend/src/lib/crypto/cryptography.ts @@ -348,7 +348,6 @@ export const computeMd5 = (message: string, digest: DigestType = DigestType.Hex) }; const cryptographyFactory = () => { - // placeholder for now let $fipsEnabled = false; let $isInitialized = false; From b87bb2b1d943cfec243fbc2b4499f98a72dee1a7 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard Date: Tue, 8 Jul 2025 12:10:43 +0400 Subject: [PATCH 10/27] Update queue-service.ts --- backend/src/queue/queue-service.ts | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/backend/src/queue/queue-service.ts b/backend/src/queue/queue-service.ts index da841f1cc..9f98a7347 100644 --- a/backend/src/queue/queue-service.ts +++ b/backend/src/queue/queue-service.ts @@ -1,13 +1,4 @@ -import { - Job, - JobsOptions, - Queue, - QueueOptions, - RepeatOptions, - Worker, - WorkerListener, - AdvancedRepeatOptions -} from "bullmq"; +import { Job, JobsOptions, Queue, QueueOptions, RepeatOptions, Worker, WorkerListener } from "bullmq"; import PgBoss, { WorkOptions } from "pg-boss"; import { SecretEncryptionAlgo, SecretKeyEncoding } from "@app/db/schemas"; From 2c50de28bd1c01118bd8e547ef757563cb61af4b Mon Sep 17 00:00:00 2001 From: Daniel Hougaard Date: Tue, 8 Jul 2025 18:28:43 +0400 Subject: [PATCH 11/27] feat(fips): fips validated JWT's --- .infisicalignore | 1 + backend/Dockerfile.dev.fips | 11 +- backend/e2e-test/vitest-environment-knex.ts | 4 +- .../assume-privilege-service.ts | 6 +- .../dynamic-secret/providers/github.ts | 3 +- .../ldap-config/ldap-config-service.ts | 4 +- .../ee/services/oidc/oidc-config-service.ts | 4 +- .../saml-config/saml-config-service.ts | 4 +- backend/src/ee/services/scim/scim-service.ts | 4 +- .../bitbucket-secret-scanning-service.ts | 5 +- .../crypto/cryptography/asymmetric-fips.ts | 124 +++++++ .../crypto.ts} | 325 +++--------------- .../src/lib/crypto/cryptography/hash-fips.ts | 107 ++++++ backend/src/lib/crypto/cryptography/index.ts | 9 + .../src/lib/crypto/cryptography/jwt-fips.ts | 289 ++++++++++++++++ backend/src/lib/crypto/cryptography/types.ts | 92 +++++ backend/src/lib/crypto/index.ts | 2 +- .../server/plugins/auth/inject-identity.ts | 5 +- backend/src/server/routes/v1/auth-router.ts | 4 +- backend/src/server/routes/v2/mfa-router.ts | 4 +- .../services/auth-token/auth-token-service.ts | 3 +- backend/src/services/auth/auth-fns.ts | 9 +- .../src/services/auth/auth-login-service.ts | 16 +- .../services/auth/auth-password-service.ts | 4 +- .../src/services/auth/auth-signup-service.ts | 12 +- .../identity-access-token-service.ts | 10 +- .../identity-alicloud-auth-service.ts | 4 +- .../identity-aws-auth-service.ts | 4 +- .../identity-azure-auth-fns.ts | 6 +- .../identity-azure-auth-service.ts | 4 +- .../identity-gcp-auth-fns.ts | 6 +- .../identity-gcp-auth-service.ts | 4 +- .../identity-jwt-auth-service.ts | 12 +- .../identity-kubernetes-auth-service.ts | 4 +- .../identity-ldap-auth-service.ts | 4 +- .../identity-oci-auth-service.ts | 4 +- .../identity-oidc-auth-service.ts | 10 +- .../identity-tls-cert-auth-service.ts | 3 +- .../identity-token-auth-service.ts | 4 +- .../identity-ua/identity-ua-service.ts | 3 +- .../integration-auth/integration-token.ts | 5 +- .../microsoft-teams/microsoft-teams-fns.ts | 8 +- backend/src/services/org/org-service.ts | 5 +- .../super-admin/super-admin-service.ts | 3 +- 44 files changed, 779 insertions(+), 375 deletions(-) create mode 100644 backend/src/lib/crypto/cryptography/asymmetric-fips.ts rename backend/src/lib/crypto/{cryptography.ts => cryptography/crypto.ts} (63%) create mode 100644 backend/src/lib/crypto/cryptography/hash-fips.ts create mode 100644 backend/src/lib/crypto/cryptography/index.ts create mode 100644 backend/src/lib/crypto/cryptography/jwt-fips.ts create mode 100644 backend/src/lib/crypto/cryptography/types.ts diff --git a/.infisicalignore b/.infisicalignore index 4c50aa7fa..06a8db5c2 100644 --- a/.infisicalignore +++ b/.infisicalignore @@ -47,3 +47,4 @@ cli/detect/config/gitleaks.toml:gcp-api-key:582 backend/src/services/smtp/smtp-service.ts:generic-api-key:79 frontend/src/components/secret-syncs/forms/SecretSyncDestinationFields/CloudflarePagesSyncFields.tsx:cloudflare-api-key:7 docs/integrations/app-connections/zabbix.mdx:generic-api-key:91 +docs/integrations/app-connections/bitbucket.mdx:generic-api-key:123 diff --git a/backend/Dockerfile.dev.fips b/backend/Dockerfile.dev.fips index 27c35f2aa..23f9db295 100644 --- a/backend/Dockerfile.dev.fips +++ b/backend/Dockerfile.dev.fips @@ -1,5 +1,12 @@ 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 + # Install build dependencies including python3 (required for pkcs11js and partially TDS driver) RUN apt-get update && apt-get install -y \ build-essential \ @@ -45,9 +52,7 @@ 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 - - -# Build and install FIPS validated OpenSSL + WORKDIR /openssl-build RUN wget https://www.openssl.org/source/openssl-3.1.2.tar.gz \ && tar -xf openssl-3.1.2.tar.gz \ diff --git a/backend/e2e-test/vitest-environment-knex.ts b/backend/e2e-test/vitest-environment-knex.ts index 289945f17..60e70d379 100644 --- a/backend/e2e-test/vitest-environment-knex.ts +++ b/backend/e2e-test/vitest-environment-knex.ts @@ -2,7 +2,7 @@ import "ts-node/register"; import dotenv from "dotenv"; -import jwt from "jsonwebtoken"; +import { crypto } from "@app/lib/crypto/cryptography"; import path from "path"; import { seedData1 } from "@app/db/seed-data"; @@ -83,7 +83,7 @@ export default { // @ts-expect-error type globalThis.testSuperAdminDAL = superAdminDAL; // @ts-expect-error type - globalThis.jwtAuthToken = jwt.sign( + globalThis.jwtAuthToken = crypto.jwt().sign( { authTokenType: AuthTokenType.ACCESS_TOKEN, userId: seedData1.id, diff --git a/backend/src/ee/services/assume-privilege/assume-privilege-service.ts b/backend/src/ee/services/assume-privilege/assume-privilege-service.ts index c1cfad082..a63b0e3be 100644 --- a/backend/src/ee/services/assume-privilege/assume-privilege-service.ts +++ b/backend/src/ee/services/assume-privilege/assume-privilege-service.ts @@ -1,7 +1,7 @@ import { ForbiddenError } from "@casl/ability"; -import jwt from "jsonwebtoken"; import { getConfig } from "@app/lib/config/env"; +import { crypto } from "@app/lib/crypto/cryptography"; import { ForbiddenRequestError, NotFoundError } from "@app/lib/errors"; import { ActorType } from "@app/services/auth/auth-type"; import { TProjectDALFactory } from "@app/services/project/project-dal"; @@ -62,7 +62,7 @@ export const assumePrivilegeServiceFactory = ({ }); const appCfg = getConfig(); - const assumePrivilegesToken = jwt.sign( + const assumePrivilegesToken = crypto.jwt().sign( { tokenVersionId, actorType: targetActorType, @@ -82,7 +82,7 @@ export const assumePrivilegeServiceFactory = ({ tokenVersionId ) => { const appCfg = getConfig(); - const decodedToken = jwt.verify(token, appCfg.AUTH_SECRET) as { + const decodedToken = crypto.jwt().verify(token, appCfg.AUTH_SECRET) as { tokenVersionId: string; projectId: string; requesterId: string; diff --git a/backend/src/ee/services/dynamic-secret/providers/github.ts b/backend/src/ee/services/dynamic-secret/providers/github.ts index 172041f6c..8bd1cccf4 100644 --- a/backend/src/ee/services/dynamic-secret/providers/github.ts +++ b/backend/src/ee/services/dynamic-secret/providers/github.ts @@ -1,6 +1,7 @@ import axios from "axios"; import jwt from "jsonwebtoken"; +import { crypto } from "@app/lib/crypto"; import { BadRequestError, InternalServerError } from "@app/lib/errors"; import { alphaNumericNanoId } from "@app/lib/nanoid"; import { IntegrationUrls } from "@app/services/integration-auth/integration-list"; @@ -40,7 +41,7 @@ export const GithubProvider = (): TDynamicProviderFns => { let appJwt: string; try { - appJwt = jwt.sign(jwtPayload, privateKey, { algorithm: "RS256" }); + appJwt = crypto.jwt().sign(jwtPayload, privateKey, { algorithm: "RS256" }); } catch (error) { let message = "Failed to sign JWT."; if (error instanceof jwt.JsonWebTokenError) { diff --git a/backend/src/ee/services/ldap-config/ldap-config-service.ts b/backend/src/ee/services/ldap-config/ldap-config-service.ts index 426ed132e..2a4f895c4 100644 --- a/backend/src/ee/services/ldap-config/ldap-config-service.ts +++ b/backend/src/ee/services/ldap-config/ldap-config-service.ts @@ -1,11 +1,11 @@ import { ForbiddenError } from "@casl/ability"; -import jwt from "jsonwebtoken"; import { OrgMembershipStatus, TableName, TLdapConfigsUpdate, TUsers } from "@app/db/schemas"; import { TGroupDALFactory } from "@app/ee/services/group/group-dal"; import { addUsersToGroupByUserIds, removeUsersFromGroupByUserIds } from "@app/ee/services/group/group-fns"; import { TUserGroupMembershipDALFactory } from "@app/ee/services/group/user-group-membership-dal"; import { getConfig } from "@app/lib/config/env"; +import { crypto } from "@app/lib/crypto"; import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors"; import { AuthMethod, AuthTokenType } from "@app/services/auth/auth-type"; import { TAuthTokenServiceFactory } from "@app/services/auth-token/auth-token-service"; @@ -536,7 +536,7 @@ export const ldapConfigServiceFactory = ({ const isUserCompleted = Boolean(user.isAccepted); const userEnc = await userDAL.findUserEncKeyByUserId(user.id); - const providerAuthToken = jwt.sign( + const providerAuthToken = crypto.jwt().sign( { authTokenType: AuthTokenType.PROVIDER_TOKEN, userId: user.id, diff --git a/backend/src/ee/services/oidc/oidc-config-service.ts b/backend/src/ee/services/oidc/oidc-config-service.ts index 1a3374035..ca6f67235 100644 --- a/backend/src/ee/services/oidc/oidc-config-service.ts +++ b/backend/src/ee/services/oidc/oidc-config-service.ts @@ -1,6 +1,5 @@ /* eslint-disable @typescript-eslint/no-unsafe-call */ import { ForbiddenError } from "@casl/ability"; -import jwt from "jsonwebtoken"; import { Issuer, Issuer as OpenIdIssuer, Strategy as OpenIdStrategy, TokenSet } from "openid-client"; import { OrgMembershipStatus, TableName, TUsers } from "@app/db/schemas"; @@ -13,6 +12,7 @@ import { TLicenseServiceFactory } from "@app/ee/services/license/license-service 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"; import { BadRequestError, ForbiddenRequestError, NotFoundError, OidcAuthError } from "@app/lib/errors"; import { OrgServiceActor } from "@app/lib/types"; import { ActorType, AuthMethod, AuthTokenType } from "@app/services/auth/auth-type"; @@ -406,7 +406,7 @@ export const oidcConfigServiceFactory = ({ const userEnc = await userDAL.findUserEncKeyByUserId(user.id); const isUserCompleted = Boolean(user.isAccepted); - const providerAuthToken = jwt.sign( + const providerAuthToken = crypto.jwt().sign( { authTokenType: AuthTokenType.PROVIDER_TOKEN, userId: user.id, diff --git a/backend/src/ee/services/saml-config/saml-config-service.ts b/backend/src/ee/services/saml-config/saml-config-service.ts index 5430b0afc..a89bc0346 100644 --- a/backend/src/ee/services/saml-config/saml-config-service.ts +++ b/backend/src/ee/services/saml-config/saml-config-service.ts @@ -1,8 +1,8 @@ import { ForbiddenError } from "@casl/ability"; -import jwt from "jsonwebtoken"; import { OrgMembershipStatus, TableName, TSamlConfigs, TSamlConfigsUpdate, TUsers } from "@app/db/schemas"; import { getConfig } from "@app/lib/config/env"; +import { crypto } from "@app/lib/crypto"; import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors"; import { AuthTokenType } from "@app/services/auth/auth-type"; import { TAuthTokenServiceFactory } from "@app/services/auth-token/auth-token-service"; @@ -419,7 +419,7 @@ export const samlConfigServiceFactory = ({ const isUserCompleted = Boolean(user.isAccepted); const userEnc = await userDAL.findUserEncKeyByUserId(user.id); - const providerAuthToken = jwt.sign( + const providerAuthToken = crypto.jwt().sign( { authTokenType: AuthTokenType.PROVIDER_TOKEN, userId: user.id, diff --git a/backend/src/ee/services/scim/scim-service.ts b/backend/src/ee/services/scim/scim-service.ts index 6c5465488..fc05cd2ce 100644 --- a/backend/src/ee/services/scim/scim-service.ts +++ b/backend/src/ee/services/scim/scim-service.ts @@ -1,6 +1,5 @@ import { ForbiddenError } from "@casl/ability"; import slugify from "@sindresorhus/slugify"; -import jwt from "jsonwebtoken"; import { scimPatch } from "scim-patch"; import { OrgMembershipRole, OrgMembershipStatus, TableName, TGroups, TOrgMemberships, TUsers } from "@app/db/schemas"; @@ -10,6 +9,7 @@ import { TUserGroupMembershipDALFactory } from "@app/ee/services/group/user-grou import { TScimDALFactory } from "@app/ee/services/scim/scim-dal"; import { getConfig } from "@app/lib/config/env"; import { BadRequestError, NotFoundError, ScimRequestError, UnauthorizedError } from "@app/lib/errors"; +import { crypto } from "@app/lib/crypto"; import { alphaNumericNanoId } from "@app/lib/nanoid"; import { AuthTokenType } from "@app/services/auth/auth-type"; import { TExternalGroupOrgRoleMappingDALFactory } from "@app/services/external-group-org-role-mapping/external-group-org-role-mapping-dal"; @@ -137,7 +137,7 @@ export const scimServiceFactory = ({ ttlDays }); - const scimToken = jwt.sign( + const scimToken = crypto.jwt().sign( { scimTokenId: scimTokenData.id, authTokenType: AuthTokenType.SCIM_TOKEN diff --git a/backend/src/ee/services/secret-scanning-v2/bitbucket/bitbucket-secret-scanning-service.ts b/backend/src/ee/services/secret-scanning-v2/bitbucket/bitbucket-secret-scanning-service.ts index c5a0aedd6..24b017d98 100644 --- a/backend/src/ee/services/secret-scanning-v2/bitbucket/bitbucket-secret-scanning-service.ts +++ b/backend/src/ee/services/secret-scanning-v2/bitbucket/bitbucket-secret-scanning-service.ts @@ -1,8 +1,7 @@ -import crypto from "crypto"; - import { TSecretScanningV2DALFactory } from "@app/ee/services/secret-scanning-v2/secret-scanning-v2-dal"; import { SecretScanningDataSource } from "@app/ee/services/secret-scanning-v2/secret-scanning-v2-enums"; import { TSecretScanningV2QueueServiceFactory } from "@app/ee/services/secret-scanning-v2/secret-scanning-v2-queue"; +import { crypto } from "@app/lib/crypto"; import { logger } from "@app/lib/logger"; import { TKmsServiceFactory } from "@app/services/kms/kms-service"; import { KmsDataKey } from "@app/services/kms/kms-types"; @@ -67,7 +66,7 @@ export const bitbucketSecretScanningService = ( const credentials = JSON.parse(decryptedCredentials.toString()) as TBitbucketDataSourceCredentials; - const hmac = crypto.createHmac("sha256", credentials.webhookSecret); + const hmac = crypto.rawCrypto.createHmac("sha256", credentials.webhookSecret); hmac.update(bodyString); const calculatedSignature = hmac.digest("hex"); diff --git a/backend/src/lib/crypto/cryptography/asymmetric-fips.ts b/backend/src/lib/crypto/cryptography/asymmetric-fips.ts new file mode 100644 index 000000000..9e1e12459 --- /dev/null +++ b/backend/src/lib/crypto/cryptography/asymmetric-fips.ts @@ -0,0 +1,124 @@ +import crypto from "crypto"; + +import { SecretEncryptionAlgo } from "@app/db/schemas"; +import { CryptographyError } from "@app/lib/errors"; + +export const asymmetricFipsValidated = () => { + const generateKeyPair = () => { + 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 encryptAsymmetric = (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 curve + const sharedSecret = crypto.diffieHellman({ + privateKey: privKeyObj, + publicKey: pubKeyObj + }); + + 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(SecretEncryptionAlgo.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 decryptAsymmetric = ({ + 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 CryptographyError({ + message: "Invalid ciphertext or keys" + }); + } + }; + + return { + generateKeyPair, + encryptAsymmetric, + decryptAsymmetric + }; +}; diff --git a/backend/src/lib/crypto/cryptography.ts b/backend/src/lib/crypto/cryptography/crypto.ts similarity index 63% rename from backend/src/lib/crypto/cryptography.ts rename to backend/src/lib/crypto/cryptography/crypto.ts index d35991c49..f5c2182e6 100644 --- a/backend/src/lib/crypto/cryptography.ts +++ b/backend/src/lib/crypto/cryptography/crypto.ts @@ -4,6 +4,7 @@ import crypto, { subtle } from "node:crypto"; import bcrypt from "bcrypt"; import cryptoJs from "crypto-js"; +import jwtDep from "jsonwebtoken"; import nacl from "tweetnacl"; import naclUtils from "tweetnacl-util"; @@ -12,277 +13,30 @@ import { TLicenseServiceFactory } from "@app/ee/services/license/license-service import { TSuperAdminDALFactory } from "@app/services/super-admin/super-admin-dal"; import { ADMIN_CONFIG_DB_UUID } from "@app/services/super-admin/super-admin-service"; -import { isBase64 } from "../base64"; -import { getConfig } from "../config/env"; -import { CryptographyError } from "../errors"; -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; -}; +import { isBase64 } from "../../base64"; +import { getConfig } from "../../config/env"; +import { CryptographyError } from "../../errors"; +import { logger } from "../../logger"; +import { asymmetricFipsValidated } from "./asymmetric-fips"; +import { hasherFipsValidated } from "./hash-fips"; +import { jwtFipsValidated } from "./jwt-fips"; +import { + DigestType, + JWTPayload, + JWTSecretOrKey, + JWTSignOptions, + JWTVerifyOptions, + SymmetricKeySize, + TDecryptAsymmetricInput, + TDecryptSymmetricInput, + TEncryptSymmetricInput +} from "./types"; const bytesToBits = (bytes: number) => bytes * 8; const IV_BYTES_SIZE = 12; const BLOCK_SIZE_BYTES_16 = 16; -const hasherFipsValidated = () => { - const keySize = 32; - - // For the salt when using pkdf2, we do salt rounds^6. If the salt rounds are 10, this will result in 10^6 = 1.000.000 iterations. - // 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^6 brings the computational power required to a little more than bcrypt. - // OWASP recommends a minimum of 600.000 iterations for pbkdf2, so 1.000.000 is more than enough. - // Ref: https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html#pbkdf2 - const MIN_COST_FACTOR = 10; - const MAX_COST_FACTOR = 20; // Iterations scales polynomial (costFactor^6), so we need an upper bound - - const $calculateIterations = (costFactor: number) => { - return Math.round(costFactor ** 6); - }; - - const $hashPassword = (password: Buffer, salt: Buffer, 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); - }); - }); - }; - - const $validatePassword = async ( - inputPassword: Buffer, - storedHash: Buffer, - salt: Buffer, - iterations: number, - keyLength: number - ) => { - const computedHash = await $hashPassword(inputPassword, salt, iterations, keyLength); - - return crypto.timingSafeEqual(computedHash, storedHash); - }; - - const hash = async (password: string, costFactor: number) => { - // Strict input validation - if (typeof password !== "string" || password.length === 0) { - throw new CryptographyError({ - message: "Invalid input, password must be a non-empty string" - }); - } - - if (!Number.isInteger(costFactor)) { - throw new CryptographyError({ - message: "Invalid cost factor, must be an integer" - }); - } - - if (costFactor < MIN_COST_FACTOR || costFactor > MAX_COST_FACTOR) { - throw new CryptographyError({ - message: `Invalid cost factor, must be between ${MIN_COST_FACTOR} and ${MAX_COST_FACTOR}` - }); - } - - const iterations = $calculateIterations(costFactor); - - const salt = crypto.randomBytes(16); - const derivedKey = await $hashPassword(Buffer.from(password), salt, iterations, keySize); - - const combined = Buffer.concat([salt, derivedKey]); - return `$v1$${costFactor}$${combined.toString("base64")}`; // Store original costFactor! - }; - - const compare = async (password: string, hashedPassword: string) => { - try { - if (!hashedPassword?.startsWith("$v1$")) return false; - - const parts = hashedPassword.split("$"); - if (parts.length !== 4) return false; - - const [, , storedCostFactor, combined] = parts; - - if ( - !Number.isInteger(Number(storedCostFactor)) || - Number(storedCostFactor) < MIN_COST_FACTOR || - Number(storedCostFactor) > MAX_COST_FACTOR - ) { - return false; - } - - const combinedBuffer = Buffer.from(combined, "base64"); - const salt = combinedBuffer.subarray(0, 16); - const storedHash = combinedBuffer.subarray(16); - - const iterations = $calculateIterations(Number(storedCostFactor)); - - const isMatch = await $validatePassword(Buffer.from(password), storedHash, salt, iterations, keySize); - - return isMatch; - } catch { - return false; - } - }; - - 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 curve - const sharedSecret = crypto.diffieHellman({ - privateKey: privKeyObj, - publicKey: pubKeyObj - }); - - 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(SecretEncryptionAlgo.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 CryptographyError({ - message: "Invalid ciphertext or keys" - }); - } -}; - const generateAsymmetricKeyPairNoFipsValidation = () => { const pair = nacl.box.keyPair(); @@ -447,21 +201,21 @@ const cryptographyFactory = () => { const asymmetric = () => { const generateKeyPair = () => { if (isFipsModeEnabled()) { - return generateAsymmetricKeyPairFipsValidated(); + return asymmetricFipsValidated().generateKeyPair(); } return generateAsymmetricKeyPairNoFipsValidation(); }; const encrypt = (data: string, publicKey: string, privateKey: string) => { if (isFipsModeEnabled()) { - return encryptAsymmetricFipsValidated(data, publicKey, privateKey); + return asymmetricFipsValidated().encryptAsymmetric(data, publicKey, privateKey); } return encryptAsymmetricNoFipsValidation(data, publicKey, privateKey); }; const decrypt = ({ ciphertext, nonce, publicKey, privateKey }: TDecryptAsymmetricInput) => { if (isFipsModeEnabled()) { - return decryptAsymmetricFipsValidated({ ciphertext, nonce, publicKey, privateKey }); + return asymmetricFipsValidated().decryptAsymmetric({ ciphertext, nonce, publicKey, privateKey }); } return decryptAsymmetricNoFipsValidation({ ciphertext, nonce, publicKey, privateKey }); }; @@ -633,13 +387,44 @@ const cryptographyFactory = () => { compareHash }; }; + const jwt = () => { + $checkIsInitialized(); + + const sign = (payload: JWTPayload, secretOrKey: JWTSecretOrKey, options: JWTSignOptions = {}) => { + if (isFipsModeEnabled()) { + return jwtFipsValidated().sign(payload, secretOrKey, options); + } + return jwtDep.sign(payload, secretOrKey, options); + }; + + const verify = (token: string, secretOrKey: JWTSecretOrKey, options: JWTVerifyOptions = {}) => { + if (isFipsModeEnabled()) { + return jwtFipsValidated().verify(token, secretOrKey, options); + } + return jwtDep.verify(token, secretOrKey, options); + }; + + const decode = (token: string, options: { complete?: boolean } = {}) => { + if (isFipsModeEnabled()) { + return jwtFipsValidated().decode(token, options); + } + return jwtDep.decode(token, options); + }; + + return { + sign, + verify, + decode + }; + }; return { initialize, isFipsModeEnabled, - hashing, verifyFipsLicense, + hashing, encryption, + jwt, randomBytes: crypto.randomBytes, randomInt: crypto.randomInt, rawCrypto: { @@ -674,6 +459,4 @@ const cryptographyFactory = () => { const factoryInstance = cryptographyFactory(); -export type TCryptographyFactory = ReturnType; - export { factoryInstance as crypto, DigestType }; diff --git a/backend/src/lib/crypto/cryptography/hash-fips.ts b/backend/src/lib/crypto/cryptography/hash-fips.ts new file mode 100644 index 000000000..d4ec5f727 --- /dev/null +++ b/backend/src/lib/crypto/cryptography/hash-fips.ts @@ -0,0 +1,107 @@ +import crypto from "crypto"; + +import { CryptographyError } from "@app/lib/errors"; + +export const hasherFipsValidated = () => { + const keySize = 32; + + // For the salt when using pkdf2, we do salt rounds^6. If the salt rounds are 10, this will result in 10^6 = 1.000.000 iterations. + // 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^6 brings the computational power required to a little more than bcrypt. + // OWASP recommends a minimum of 600.000 iterations for pbkdf2, so 1.000.000 is more than enough. + // Ref: https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html#pbkdf2 + const MIN_COST_FACTOR = 10; + const MAX_COST_FACTOR = 20; // Iterations scales polynomial (costFactor^6), so we need an upper bound + + const $calculateIterations = (costFactor: number) => { + return Math.round(costFactor ** 6); + }; + + const $hashPassword = (password: Buffer, salt: Buffer, 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); + }); + }); + }; + + const $validatePassword = async ( + inputPassword: Buffer, + storedHash: Buffer, + salt: Buffer, + iterations: number, + keyLength: number + ) => { + const computedHash = await $hashPassword(inputPassword, salt, iterations, keyLength); + + return crypto.timingSafeEqual(computedHash, storedHash); + }; + + const hash = async (password: string, costFactor: number) => { + // Strict input validation + if (typeof password !== "string" || password.length === 0) { + throw new CryptographyError({ + message: "Invalid input, password must be a non-empty string" + }); + } + + if (!Number.isInteger(costFactor)) { + throw new CryptographyError({ + message: "Invalid cost factor, must be an integer" + }); + } + + if (costFactor < MIN_COST_FACTOR || costFactor > MAX_COST_FACTOR) { + throw new CryptographyError({ + message: `Invalid cost factor, must be between ${MIN_COST_FACTOR} and ${MAX_COST_FACTOR}` + }); + } + + const iterations = $calculateIterations(costFactor); + + const salt = crypto.randomBytes(16); + const derivedKey = await $hashPassword(Buffer.from(password), salt, iterations, keySize); + + const combined = Buffer.concat([salt, derivedKey]); + return `$v1$${costFactor}$${combined.toString("base64")}`; // Store original costFactor! + }; + + const compare = async (password: string, hashedPassword: string) => { + try { + if (!hashedPassword?.startsWith("$v1$")) return false; + + const parts = hashedPassword.split("$"); + if (parts.length !== 4) return false; + + const [, , storedCostFactor, combined] = parts; + + if ( + !Number.isInteger(Number(storedCostFactor)) || + Number(storedCostFactor) < MIN_COST_FACTOR || + Number(storedCostFactor) > MAX_COST_FACTOR + ) { + return false; + } + + const combinedBuffer = Buffer.from(combined, "base64"); + const salt = combinedBuffer.subarray(0, 16); + const storedHash = combinedBuffer.subarray(16); + + const iterations = $calculateIterations(Number(storedCostFactor)); + + const isMatch = await $validatePassword(Buffer.from(password), storedHash, salt, iterations, keySize); + + return isMatch; + } catch { + return false; + } + }; + + return { + hash, + compare + }; +}; diff --git a/backend/src/lib/crypto/cryptography/index.ts b/backend/src/lib/crypto/cryptography/index.ts new file mode 100644 index 000000000..5edd5fb89 --- /dev/null +++ b/backend/src/lib/crypto/cryptography/index.ts @@ -0,0 +1,9 @@ +export { crypto } from "./crypto"; +export { + DigestType, + SymmetricKeySize, + TDecryptAsymmetricInput, + TDecryptSymmetricInput, + TEncryptedWithRootEncryptionKey, + TEncryptSymmetricInput +} from "./types"; diff --git a/backend/src/lib/crypto/cryptography/jwt-fips.ts b/backend/src/lib/crypto/cryptography/jwt-fips.ts new file mode 100644 index 000000000..42e5809c3 --- /dev/null +++ b/backend/src/lib/crypto/cryptography/jwt-fips.ts @@ -0,0 +1,289 @@ +import crypto from "crypto"; +import RE2 from "re2"; + +import { Algorithm, CompleteJWTPayload, JWTPayload, JWTSecretOrKey, JWTSignOptions, JWTVerifyOptions } from "./types"; + +export const jwtFipsValidated = () => { + const $base64urlEncode = (str: string) => Buffer.from(str).toString("base64url"); + const $isRSAAlgorithm = (algorithm: string) => algorithm.startsWith("RS"); + + const $parseTimeToSeconds = (timeStr: string | number) => { + if (typeof timeStr === "number") { + return timeStr; + } + + const match = new RE2(/^(\d+)([smhd])$/).exec(timeStr); + if (!match) { + throw new Error(`Invalid time format: ${timeStr}`); + } + + const value = parseInt(match[1], 10); + const unit = match[2]; + + switch (unit) { + case "s": + return value; + case "m": + return value * 60; + case "h": + return value * 60 * 60; + case "d": + return value * 60 * 60 * 24; + default: + throw new Error(`Unknown time unit: ${unit}`); + } + }; + + const $getHashAlgorithm = (algorithm: string) => { + switch (algorithm) { + case "HS256": + case "RS256": + return "sha256"; + case "HS384": + case "RS384": + return "sha384"; + case "HS512": + case "RS512": + return "sha512"; + default: + throw new Error(`Unsupported algorithm: ${algorithm}`); + } + }; + + /** + * Sign a JWT token + */ + const sign = (payload: JWTPayload, secretOrPrivateKey: JWTSecretOrKey, options: JWTSignOptions = {}) => { + const algorithm = options.algorithm || "HS256"; + + const now = Math.floor(Date.now() / 1000); + // Create header + const header = { + alg: algorithm, + typ: "JWT", + ...(options.keyid && { kid: options.keyid }) + }; + + // Create payload with timestamps + const finalPayload = { + ...payload, + iat: now, + ...(options.expiresIn !== undefined && { exp: now + $parseTimeToSeconds(options.expiresIn) }) + }; + + // Encode header and payload + const encodedHeader = $base64urlEncode(JSON.stringify(header)); + const encodedPayload = $base64urlEncode(JSON.stringify(finalPayload)); + + // Create signature + const data = `${encodedHeader}.${encodedPayload}`; + const hashAlgorithm = $getHashAlgorithm(algorithm); + let signature: string; + + if ($isRSAAlgorithm(algorithm)) { + let privateKey: crypto.KeyLike | { key: string | Buffer }; + + if (typeof secretOrPrivateKey === "string") { + try { + // Try to create a proper private key object + privateKey = crypto.createPrivateKey(secretOrPrivateKey); + } catch (error) { + throw new Error("Invalid JWT private key"); + } + } else { + privateKey = secretOrPrivateKey; + } + + const signatureBuffer = crypto.sign(hashAlgorithm, Buffer.from(data), privateKey); + signature = signatureBuffer.toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, ""); + } else { + // HMAC signing + if (typeof secretOrPrivateKey !== "string") { + throw new Error("HMAC algorithms require a string secret"); + } + signature = crypto + .createHmac(hashAlgorithm, secretOrPrivateKey) + .update(data) + .digest("base64") + .replace(/\+/g, "-") + .replace(/\//g, "_") + .replace(/=/g, ""); + } + + return `${data}.${signature}`; + }; + + const verify = (token: string, secretOrKey: JWTSecretOrKey, options: JWTVerifyOptions = {}) => { + const parts = token.split("."); + if (parts.length !== 3) { + throw new Error("Invalid JWT format"); + } + + const [encodedHeader, encodedPayload, signature] = parts; + + // Decode header + const headerJson = Buffer.from(encodedHeader, "base64").toString(); + const header = JSON.parse(headerJson) as { alg: string; typ: string; kid?: string }; + + if (!header.alg || header.typ !== "JWT") { + throw new Error("Invalid JWT header"); + } + + // Extract the actual key from different input types + let keyString: string; + if (Buffer.isBuffer(secretOrKey)) { + keyString = secretOrKey.toString(); + } else if (typeof secretOrKey === "object" && "key" in secretOrKey) { + keyString = Buffer.isBuffer(secretOrKey.key) ? secretOrKey.key.toString() : secretOrKey.key; + } else { + keyString = secretOrKey as string; + } + + // Verify signature + const data = `${encodedHeader}.${encodedPayload}`; + const hashAlgorithm = $getHashAlgorithm(header.alg); + let isValidSignature: boolean; + + if ($isRSAAlgorithm(header.alg)) { + // For RSA, handle both private and public keys + let verificationKey: crypto.KeyLike; + + // Clean up the key format + const cleanKey = keyString.replace(/\\n/g, "\n"); + + try { + // If it's a private key, extract the public key for verification + if (cleanKey.includes("PRIVATE KEY")) { + const privateKeyObj = crypto.createPrivateKey(cleanKey); + verificationKey = crypto.createPublicKey(privateKeyObj); + } else { + // It's already a public key + verificationKey = crypto.createPublicKey(cleanKey); + } + } catch (error) { + throw new Error("Invalid JWT signature"); + } + + // Convert base64url signature back to buffer + const signatureBuffer = Buffer.from( + signature.replace(/-/g, "+").replace(/_/g, "/") + "==".slice(0, (4 - (signature.length % 4)) % 4), + "base64" + ); + isValidSignature = crypto.verify(hashAlgorithm, Buffer.from(data), verificationKey, signatureBuffer); + } else { + // HMAC verification + const expectedSignature = crypto + .createHmac(hashAlgorithm, keyString) + .update(data) + .digest("base64") + .replace(/\+/g, "-") + .replace(/\//g, "_") + .replace(/=/g, ""); + isValidSignature = signature === expectedSignature; + } + + if (!isValidSignature) { + throw new Error("Invalid JWT signature"); + } + + // Decode payload + const payloadJson = Buffer.from(encodedPayload, "base64").toString(); + const payload = JSON.parse(payloadJson) as JWTPayload & { + aud?: string | string[]; + iss?: string; + sub?: string; + nbf?: number; + jti?: string; + }; + + const now = Math.floor(Date.now() / 1000); + const clockTolerance = options.clockTolerance || 0; + + // Check expiration + if (!options.ignoreExpiration && payload.exp && now - clockTolerance > payload.exp) { + throw new Error("JWT token has expired"); + } + + // Check not before + if (!options.ignoreNotBefore && payload.nbf && now + clockTolerance < payload.nbf) { + throw new Error("JWT not active"); + } + + // Check audience + if (options.audience) { + const audiences = Array.isArray(options.audience) ? options.audience : [options.audience]; + const tokenAudiences = Array.isArray(payload.aud) ? payload.aud : [payload.aud]; + const hasValidAudience = audiences.some((aud) => tokenAudiences.includes(aud)); + if (!hasValidAudience) { + throw new Error("JWT audience invalid"); + } + } + + // Check issuer + if (options.issuer) { + const issuers = Array.isArray(options.issuer) ? options.issuer : [options.issuer]; + if (!payload.iss || !issuers.includes(payload.iss)) { + throw new Error("JWT issuer invalid"); + } + } + + // Check subject + if (options.subject && payload.sub !== options.subject) { + throw new Error("JWT subject invalid"); + } + + // Check JWT ID + if (options.jwtid && payload.jti !== options.jwtid) { + throw new Error("JWT ID invalid"); + } + + // Check max age + if (options.maxAge && payload.iat) { + const maxAgeSeconds = typeof options.maxAge === "string" ? $parseTimeToSeconds(options.maxAge) : options.maxAge; + if (now - payload.iat > maxAgeSeconds) { + throw new Error("JWT max age exceeded"); + } + } + + // Check algorithms + if (options.algorithms && !options.algorithms.includes(header.alg as Algorithm)) { + throw new Error(`Algorithm not allowed: ${header.alg}`); + } + + return payload; + }; + + const decode = (token: string, options: { complete?: boolean } = {}): JWTPayload | CompleteJWTPayload => { + const parts = token.split("."); + if (parts.length !== 3) { + throw new Error("Invalid JWT format"); + } + + const [encodedHeader, encodedPayload] = parts; + + // Decode header + const headerJson = Buffer.from(encodedHeader, "base64").toString(); + const header = JSON.parse(headerJson) as Record; + + // Decode payload + const payloadJson = Buffer.from(encodedPayload, "base64").toString(); + const payload = JSON.parse(payloadJson) as Record; + + // Return complete token info or just payload + if (options.complete) { + return { + header, + payload, + signature: parts[2] + }; + } + + return payload as JWTPayload; + }; + + return { + sign, + verify, + decode + }; +}; diff --git a/backend/src/lib/crypto/cryptography/types.ts b/backend/src/lib/crypto/cryptography/types.ts new file mode 100644 index 000000000..e9622575e --- /dev/null +++ b/backend/src/lib/crypto/cryptography/types.ts @@ -0,0 +1,92 @@ +import { KeyObject } from "crypto"; + +import { SecretEncryptionAlgo, SecretKeyEncoding } from "@app/db/schemas"; + +export enum DigestType { + Hex = "hex", + Base64 = "base64" +} + +export enum SymmetricKeySize { + Bits128 = "128-bits", + Bits256 = "256-bits" +} + +export 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; + }; + +export type TEncryptSymmetricInput = + | { + plaintext: string; + key: string; + keySize: SymmetricKeySize.Bits256; + } + | { + plaintext: string; + key: string | Buffer; + keySize: SymmetricKeySize.Bits128; + }; + +export type TDecryptAsymmetricInput = { + ciphertext: string; + nonce: string; + publicKey: string; + privateKey: string; +}; + +export type TEncryptedWithRootEncryptionKey = { + iv: string; + tag: string; + ciphertext: string; + algorithm: SecretEncryptionAlgo; + encoding: SecretKeyEncoding; +}; + +export interface JWTPayload { + iat?: number; + exp?: number; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + [key: string]: any; +} + +export interface CompleteJWTPayload { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + header: any; + payload: JWTPayload; + signature: string; +} + +export type Algorithm = "HS256" | "HS384" | "HS512" | "RS256" | "RS384" | "RS512"; + +export interface JWTSignOptions { + algorithm?: Algorithm | undefined; + keyid?: string | undefined; + expiresIn?: string | number; +} + +export type JWTSecretOrKey = string | Buffer | KeyObject | { key: string | Buffer; passphrase: string }; + +export interface JWTVerifyOptions { + algorithms?: Algorithm[] | undefined; + audience?: string | string[]; + issuer?: string | string[]; + subject?: string; + ignoreExpiration?: boolean; + ignoreNotBefore?: boolean; + clockTolerance?: number; + maxAge?: string | number; + jwtid?: string; +} diff --git a/backend/src/lib/crypto/index.ts b/backend/src/lib/crypto/index.ts index aac797a3e..d67e75d0b 100644 --- a/backend/src/lib/crypto/index.ts +++ b/backend/src/lib/crypto/index.ts @@ -1,4 +1,4 @@ -export { crypto, SymmetricKeySize } from "./cryptography"; +export { crypto, SymmetricKeySize, TEncryptedWithRootEncryptionKey } from "./cryptography"; export { buildSecretBlindIndexFromName } from "./encryption"; export { decryptIntegrationAuths, diff --git a/backend/src/server/plugins/auth/inject-identity.ts b/backend/src/server/plugins/auth/inject-identity.ts index f065bfbed..98982e969 100644 --- a/backend/src/server/plugins/auth/inject-identity.ts +++ b/backend/src/server/plugins/auth/inject-identity.ts @@ -1,11 +1,12 @@ import { requestContext } from "@fastify/request-context"; import { FastifyRequest } from "fastify"; import fp from "fastify-plugin"; -import jwt, { JwtPayload } from "jsonwebtoken"; import { TServiceTokens, TUsers } from "@app/db/schemas"; import { TScimTokenJwtPayload } from "@app/ee/services/scim/scim-types"; import { getConfig } from "@app/lib/config/env"; +import { crypto } from "@app/lib/crypto"; +import { JWTPayload } from "@app/lib/crypto/cryptography/types"; import { BadRequestError } from "@app/lib/errors"; import { ActorType, AuthMethod, AuthMode, AuthModeJwtTokenPayload, AuthTokenType } from "@app/services/auth/auth-type"; import { TIdentityAccessTokenJwtPayload } from "@app/services/identity-access-token/identity-access-token-types"; @@ -72,7 +73,7 @@ const extractAuth = async (req: FastifyRequest, jwtSecret: string) => { } as const; } - const decodedToken = jwt.verify(authTokenValue, jwtSecret) as JwtPayload; + const decodedToken = crypto.jwt().verify(authTokenValue, jwtSecret) as JWTPayload; switch (decodedToken.authTokenType) { case AuthTokenType.ACCESS_TOKEN: diff --git a/backend/src/server/routes/v1/auth-router.ts b/backend/src/server/routes/v1/auth-router.ts index 7231ce85c..e7c06ff51 100644 --- a/backend/src/server/routes/v1/auth-router.ts +++ b/backend/src/server/routes/v1/auth-router.ts @@ -1,7 +1,7 @@ -import jwt from "jsonwebtoken"; import { z } from "zod"; import { getConfig } from "@app/lib/config/env"; +import { crypto } from "@app/lib/crypto"; import { getMinExpiresIn } from "@app/lib/fn"; import { authRateLimit, writeLimit } from "@app/server/config/rateLimiter"; import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; @@ -93,7 +93,7 @@ export const registerAuthRoutes = async (server: FastifyZodProvider) => { } } - const token = jwt.sign( + const token = crypto.jwt().sign( { authMethod: decodedToken.authMethod, authTokenType: AuthTokenType.ACCESS_TOKEN, diff --git a/backend/src/server/routes/v2/mfa-router.ts b/backend/src/server/routes/v2/mfa-router.ts index 6f28ec34c..bdb609432 100644 --- a/backend/src/server/routes/v2/mfa-router.ts +++ b/backend/src/server/routes/v2/mfa-router.ts @@ -1,7 +1,7 @@ -import jwt from "jsonwebtoken"; import { z } from "zod"; import { getConfig } from "@app/lib/config/env"; +import { crypto } from "@app/lib/crypto"; import { BadRequestError, NotFoundError } from "@app/lib/errors"; import { mfaRateLimit } from "@app/server/config/rateLimiter"; import { AuthModeMfaJwtTokenPayload, AuthTokenType, MfaMethod } from "@app/services/auth/auth-type"; @@ -23,7 +23,7 @@ export const registerMfaRouter = async (server: FastifyZodProvider) => { return res; } - const decodedToken = jwt.verify(token, cfg.AUTH_SECRET) as AuthModeMfaJwtTokenPayload; + const decodedToken = crypto.jwt().verify(token, cfg.AUTH_SECRET) as AuthModeMfaJwtTokenPayload; if (decodedToken.authTokenType !== AuthTokenType.MFA_TOKEN) throw new Error("Unauthorized access"); const user = await server.store.user.findById(decodedToken.userId); diff --git a/backend/src/services/auth-token/auth-token-service.ts b/backend/src/services/auth-token/auth-token-service.ts index 507095248..1a2f290ec 100644 --- a/backend/src/services/auth-token/auth-token-service.ts +++ b/backend/src/services/auth-token/auth-token-service.ts @@ -1,4 +1,3 @@ -import jwt from "jsonwebtoken"; import { Knex } from "knex"; import { TAuthTokens, TAuthTokenSessions } from "@app/db/schemas"; @@ -160,7 +159,7 @@ export const tokenServiceFactory = ({ tokenDAL, userDAL, orgMembershipDAL }: TAu message: "Failed to find refresh token" }); - const decodedToken = jwt.verify(refreshToken, appCfg.AUTH_SECRET) as AuthModeRefreshJwtTokenPayload; + const decodedToken = crypto.jwt().verify(refreshToken, appCfg.AUTH_SECRET) as AuthModeRefreshJwtTokenPayload; if (decodedToken.authTokenType !== AuthTokenType.REFRESH_TOKEN) throw new UnauthorizedError({ diff --git a/backend/src/services/auth/auth-fns.ts b/backend/src/services/auth/auth-fns.ts index ec6e0a303..b38275c8b 100644 --- a/backend/src/services/auth/auth-fns.ts +++ b/backend/src/services/auth/auth-fns.ts @@ -1,6 +1,5 @@ -import jwt from "jsonwebtoken"; - import { getConfig } from "@app/lib/config/env"; +import { crypto } from "@app/lib/crypto"; import { ForbiddenRequestError, UnauthorizedError } from "@app/lib/errors"; import { AuthModeProviderJwtTokenPayload, AuthModeProviderSignUpTokenPayload, AuthTokenType } from "./auth-type"; @@ -8,7 +7,7 @@ import { AuthModeProviderJwtTokenPayload, AuthModeProviderSignUpTokenPayload, Au export const validateProviderAuthToken = (providerToken: string, username?: string) => { if (!providerToken) throw new UnauthorizedError(); const appCfg = getConfig(); - const decodedToken = jwt.verify(providerToken, appCfg.AUTH_SECRET) as AuthModeProviderJwtTokenPayload; + const decodedToken = crypto.jwt().verify(providerToken, appCfg.AUTH_SECRET) as AuthModeProviderJwtTokenPayload; if (decodedToken.authTokenType !== AuthTokenType.PROVIDER_TOKEN) throw new UnauthorizedError(); @@ -38,7 +37,7 @@ export const validateSignUpAuthorization = (token: string, userId: string, valid }); } - const decodedToken = jwt.verify(AUTH_TOKEN_VALUE, appCfg.AUTH_SECRET) as AuthModeProviderSignUpTokenPayload; + const decodedToken = crypto.jwt().verify(AUTH_TOKEN_VALUE, appCfg.AUTH_SECRET) as AuthModeProviderSignUpTokenPayload; if (!validate) return decodedToken; if (decodedToken.authTokenType !== AuthTokenType.SIGNUP_TOKEN) throw new UnauthorizedError(); @@ -64,7 +63,7 @@ export const validatePasswordResetAuthorization = (token?: string) => { }); } - const decodedToken = jwt.verify(AUTH_TOKEN_VALUE, appCfg.AUTH_SECRET) as AuthModeProviderSignUpTokenPayload; + const decodedToken = crypto.jwt().verify(AUTH_TOKEN_VALUE, appCfg.AUTH_SECRET) as AuthModeProviderSignUpTokenPayload; if (decodedToken.authTokenType !== AuthTokenType.SIGNUP_TOKEN) { throw new UnauthorizedError({ diff --git a/backend/src/services/auth/auth-login-service.ts b/backend/src/services/auth/auth-login-service.ts index a70fa5cc2..8463562bc 100644 --- a/backend/src/services/auth/auth-login-service.ts +++ b/backend/src/services/auth/auth-login-service.ts @@ -1,4 +1,3 @@ -import jwt from "jsonwebtoken"; import { Knex } from "knex"; import { OrgMembershipRole, OrgMembershipStatus, TableName, TUsers, UserDeviceSchema } from "@app/db/schemas"; @@ -6,8 +5,7 @@ import { EventType, TAuditLogServiceFactory } from "@app/ee/services/audit-log/a 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 { crypto } from "@app/lib/crypto/cryptography"; +import { crypto, generateSrpServerKey, srpCheckClientProof } from "@app/lib/crypto"; import { getUserPrivateKey } from "@app/lib/crypto/srp"; import { BadRequestError, DatabaseError, ForbiddenRequestError, UnauthorizedError } from "@app/lib/errors"; import { getMinExpiresIn, removeTrailingSlash } from "@app/lib/fn"; @@ -156,7 +154,7 @@ export const authLoginServiceFactory = ({ } } - const accessToken = jwt.sign( + const accessToken = crypto.jwt().sign( { authMethod, authTokenType: AuthTokenType.ACCESS_TOKEN, @@ -171,7 +169,7 @@ export const authLoginServiceFactory = ({ { expiresIn: tokenSessionExpiresIn } ); - const refreshToken = jwt.sign( + const refreshToken = crypto.jwt().sign( { authMethod, authTokenType: AuthTokenType.REFRESH_TOKEN, @@ -390,7 +388,7 @@ export const authLoginServiceFactory = ({ authJwtToken = authJwtToken.replace("Bearer ", ""); // remove bearer from token // The decoded JWT token, which contains the auth method. - const decodedToken = jwt.verify(authJwtToken, cfg.AUTH_SECRET) as AuthModeJwtTokenPayload; + const decodedToken = crypto.jwt().verify(authJwtToken, cfg.AUTH_SECRET) as AuthModeJwtTokenPayload; if (!decodedToken.authMethod) throw new UnauthorizedError({ name: "Auth method not found on existing token" }); const user = await userDAL.findUserEncKeyByUserId(decodedToken.userId); @@ -415,7 +413,7 @@ export const authLoginServiceFactory = ({ if (shouldCheckMfa && (!decodedToken.isMfaVerified || decodedToken.mfaMethod !== mfaMethod)) { enforceUserLockStatus(Boolean(user.isLocked), user.temporaryLockDateEnd); - const mfaToken = jwt.sign( + const mfaToken = crypto.jwt().sign( { authMethod: decodedToken.authMethod, authTokenType: AuthTokenType.MFA_TOKEN, @@ -626,7 +624,7 @@ export const authLoginServiceFactory = ({ throw err; } - const decodedToken = jwt.verify(mfaJwtToken, getConfig().AUTH_SECRET) as AuthModeMfaJwtTokenPayload; + const decodedToken = crypto.jwt().verify(mfaJwtToken, getConfig().AUTH_SECRET) as AuthModeMfaJwtTokenPayload; const userEnc = await userDAL.findUserEncKeyByUserId(userId); if (!userEnc) throw new Error("Failed to authenticate user"); @@ -776,7 +774,7 @@ export const authLoginServiceFactory = ({ const userEnc = await userDAL.findUserEncKeyByUserId(user.id); const isUserCompleted = user.isAccepted; - const providerAuthToken = jwt.sign( + const providerAuthToken = crypto.jwt().sign( { authTokenType: AuthTokenType.PROVIDER_TOKEN, userId: user.id, diff --git a/backend/src/services/auth/auth-password-service.ts b/backend/src/services/auth/auth-password-service.ts index bd8a9a23d..39ab0a134 100644 --- a/backend/src/services/auth/auth-password-service.ts +++ b/backend/src/services/auth/auth-password-service.ts @@ -1,5 +1,3 @@ -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"; @@ -174,7 +172,7 @@ export const authPaswordServiceFactory = ({ code }); - const token = jwt.sign( + const token = crypto.jwt().sign( { authTokenType: AuthTokenType.SIGNUP_TOKEN, userId: user.id diff --git a/backend/src/services/auth/auth-signup-service.ts b/backend/src/services/auth/auth-signup-service.ts index 48f5a434e..c2c337292 100644 --- a/backend/src/services/auth/auth-signup-service.ts +++ b/backend/src/services/auth/auth-signup-service.ts @@ -1,5 +1,3 @@ -import jwt from "jsonwebtoken"; - import { OrgMembershipStatus, SecretKeyEncoding, TableName } from "@app/db/schemas"; import { convertPendingGroupAdditionsToGroupMemberships } from "@app/ee/services/group/group-fns"; import { TUserGroupMembershipDALFactory } from "@app/ee/services/group/user-group-membership-dal"; @@ -131,7 +129,7 @@ export const authSignupServiceFactory = ({ await userDAL.updateById(user.id, { isEmailVerified: true }); // generate jwt token this is a temporary token - const jwtToken = jwt.sign( + const jwtToken = crypto.jwt().sign( { authTokenType: AuthTokenType.SIGNUP_TOKEN, userId: user.id.toString() @@ -364,7 +362,7 @@ export const authSignupServiceFactory = ({ }); if (!tokenSession) throw new Error("Failed to create token"); - const accessToken = jwt.sign( + const accessToken = crypto.jwt().sign( { authMethod: authMethod || AuthMethod.EMAIL, authTokenType: AuthTokenType.ACCESS_TOKEN, @@ -377,7 +375,7 @@ export const authSignupServiceFactory = ({ { expiresIn: tokenSessionExpiresIn } ); - const refreshToken = jwt.sign( + const refreshToken = crypto.jwt().sign( { authMethod: authMethod || AuthMethod.EMAIL, authTokenType: AuthTokenType.REFRESH_TOKEN, @@ -551,7 +549,7 @@ export const authSignupServiceFactory = ({ }); if (!tokenSession) throw new Error("Failed to create token"); - const accessToken = jwt.sign( + const accessToken = crypto.jwt().sign( { authMethod: AuthMethod.EMAIL, authTokenType: AuthTokenType.ACCESS_TOKEN, @@ -563,7 +561,7 @@ export const authSignupServiceFactory = ({ { expiresIn: appCfg.JWT_SIGNUP_LIFETIME } ); - const refreshToken = jwt.sign( + const refreshToken = crypto.jwt().sign( { authMethod: AuthMethod.EMAIL, authTokenType: AuthTokenType.REFRESH_TOKEN, diff --git a/backend/src/services/identity-access-token/identity-access-token-service.ts b/backend/src/services/identity-access-token/identity-access-token-service.ts index 0937b9640..e9863f666 100644 --- a/backend/src/services/identity-access-token/identity-access-token-service.ts +++ b/backend/src/services/identity-access-token/identity-access-token-service.ts @@ -1,7 +1,7 @@ -import jwt, { JwtPayload } from "jsonwebtoken"; - import { IdentityAuthMethod, TableName, TIdentityAccessTokens } from "@app/db/schemas"; import { getConfig } from "@app/lib/config/env"; +import { crypto } from "@app/lib/crypto"; +import { JWTPayload } from "@app/lib/crypto/cryptography/types"; import { BadRequestError, UnauthorizedError } from "@app/lib/errors"; import { checkIPAgainstBlocklist, TIp } from "@app/lib/ip"; @@ -81,7 +81,7 @@ export const identityAccessTokenServiceFactory = ({ const renewAccessToken = async ({ accessToken }: TRenewAccessTokenDTO) => { const appCfg = getConfig(); - const decodedToken = jwt.verify(accessToken, appCfg.AUTH_SECRET) as TIdentityAccessTokenJwtPayload; + const decodedToken = crypto.jwt().verify(accessToken, appCfg.AUTH_SECRET) as TIdentityAccessTokenJwtPayload; if (decodedToken.authTokenType !== AuthTokenType.IDENTITY_ACCESS_TOKEN) { throw new BadRequestError({ message: "Only identity access tokens can be renewed" }); } @@ -145,7 +145,7 @@ export const identityAccessTokenServiceFactory = ({ expiresIn = undefined; } - const renewedToken = jwt.sign( + const renewedToken = crypto.jwt().sign( { identityId: decodedToken.identityId, clientSecretId: decodedToken.clientSecretId, @@ -162,7 +162,7 @@ export const identityAccessTokenServiceFactory = ({ const revokeAccessToken = async (accessToken: string) => { const appCfg = getConfig(); - const decodedToken = jwt.verify(accessToken, appCfg.AUTH_SECRET) as JwtPayload & { + const decodedToken = crypto.jwt().verify(accessToken, appCfg.AUTH_SECRET) as JWTPayload & { identityAccessTokenId: string; }; if (decodedToken.authTokenType !== AuthTokenType.IDENTITY_ACCESS_TOKEN) { diff --git a/backend/src/services/identity-alicloud-auth/identity-alicloud-auth-service.ts b/backend/src/services/identity-alicloud-auth/identity-alicloud-auth-service.ts index 77ae64121..819329a22 100644 --- a/backend/src/services/identity-alicloud-auth/identity-alicloud-auth-service.ts +++ b/backend/src/services/identity-alicloud-auth/identity-alicloud-auth-service.ts @@ -1,7 +1,6 @@ /* eslint-disable @typescript-eslint/no-unsafe-assignment */ import { ForbiddenError } from "@casl/ability"; import { AxiosError } from "axios"; -import jwt from "jsonwebtoken"; import { IdentityAuthMethod } from "@app/db/schemas"; import { TLicenseServiceFactory } from "@app/ee/services/license/license-service"; @@ -13,6 +12,7 @@ import { import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types"; import { getConfig } from "@app/lib/config/env"; import { request } from "@app/lib/config/request"; +import { crypto } from "@app/lib/crypto"; import { BadRequestError, NotFoundError, PermissionBoundaryError, UnauthorizedError } from "@app/lib/errors"; import { extractIPDetails, isValidIpOrCidr } from "@app/lib/ip"; import { logger } from "@app/lib/logger"; @@ -103,7 +103,7 @@ export const identityAliCloudAuthServiceFactory = ({ }); const appCfg = getConfig(); - const accessToken = jwt.sign( + const accessToken = crypto.jwt().sign( { identityId: identityAliCloudAuth.identityId, identityAccessTokenId: identityAccessToken.id, diff --git a/backend/src/services/identity-aws-auth/identity-aws-auth-service.ts b/backend/src/services/identity-aws-auth/identity-aws-auth-service.ts index b60366335..7c339f15e 100644 --- a/backend/src/services/identity-aws-auth/identity-aws-auth-service.ts +++ b/backend/src/services/identity-aws-auth/identity-aws-auth-service.ts @@ -1,7 +1,6 @@ /* eslint-disable @typescript-eslint/no-unsafe-assignment */ import { ForbiddenError } from "@casl/ability"; import axios from "axios"; -import jwt from "jsonwebtoken"; import RE2 from "re2"; import { IdentityAuthMethod } from "@app/db/schemas"; @@ -13,6 +12,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"; import { BadRequestError, NotFoundError, PermissionBoundaryError, UnauthorizedError } from "@app/lib/errors"; import { extractIPDetails, isValidIpOrCidr } from "@app/lib/ip"; @@ -168,7 +168,7 @@ export const identityAwsAuthServiceFactory = ({ }); const appCfg = getConfig(); - const accessToken = jwt.sign( + const accessToken = crypto.jwt().sign( { identityId: identityAwsAuth.identityId, identityAccessTokenId: identityAccessToken.id, diff --git a/backend/src/services/identity-azure-auth/identity-azure-auth-fns.ts b/backend/src/services/identity-azure-auth/identity-azure-auth-fns.ts index 741d7e63c..cb06f3d40 100644 --- a/backend/src/services/identity-azure-auth/identity-azure-auth-fns.ts +++ b/backend/src/services/identity-azure-auth/identity-azure-auth-fns.ts @@ -1,6 +1,6 @@ import axios from "axios"; -import jwt from "jsonwebtoken"; +import { crypto } from "@app/lib/crypto"; import { UnauthorizedError } from "@app/lib/errors"; import { TAzureAuthJwtPayload, TAzureJwksUriResponse, TDecodedAzureAuthJwt } from "./identity-azure-auth-types"; @@ -16,7 +16,7 @@ export const validateAzureIdentity = async ({ }) => { const jwksUri = `https://login.microsoftonline.com/${tenantId}/discovery/keys`; - const decodedJwt = jwt.decode(azureJwt, { complete: true }) as TDecodedAzureAuthJwt; + const decodedJwt = crypto.jwt().decode(azureJwt, { complete: true }) as TDecodedAzureAuthJwt; const { kid } = decodedJwt.header; @@ -35,7 +35,7 @@ export const validateAzureIdentity = async ({ resource = resource.slice(0, -1); } - return jwt.verify(azureJwt, publicKey, { + return crypto.jwt().verify(azureJwt, publicKey, { audience: resource, issuer: `https://sts.windows.net/${tenantId}/` }) as TAzureAuthJwtPayload; diff --git a/backend/src/services/identity-azure-auth/identity-azure-auth-service.ts b/backend/src/services/identity-azure-auth/identity-azure-auth-service.ts index 4c0d2164b..35103c8cf 100644 --- a/backend/src/services/identity-azure-auth/identity-azure-auth-service.ts +++ b/backend/src/services/identity-azure-auth/identity-azure-auth-service.ts @@ -1,5 +1,4 @@ import { ForbiddenError } from "@casl/ability"; -import jwt from "jsonwebtoken"; import { IdentityAuthMethod } from "@app/db/schemas"; import { TLicenseServiceFactory } from "@app/ee/services/license/license-service"; @@ -10,6 +9,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"; import { BadRequestError, NotFoundError, PermissionBoundaryError, UnauthorizedError } from "@app/lib/errors"; import { extractIPDetails, isValidIpOrCidr } from "@app/lib/ip"; @@ -96,7 +96,7 @@ export const identityAzureAuthServiceFactory = ({ }); const appCfg = getConfig(); - const accessToken = jwt.sign( + const accessToken = crypto.jwt().sign( { identityId: identityAzureAuth.identityId, identityAccessTokenId: identityAccessToken.id, diff --git a/backend/src/services/identity-gcp-auth/identity-gcp-auth-fns.ts b/backend/src/services/identity-gcp-auth/identity-gcp-auth-fns.ts index 05567d190..a089a24f6 100644 --- a/backend/src/services/identity-gcp-auth/identity-gcp-auth-fns.ts +++ b/backend/src/services/identity-gcp-auth/identity-gcp-auth-fns.ts @@ -1,7 +1,7 @@ import axios from "axios"; import { OAuth2Client } from "google-auth-library"; -import jwt from "jsonwebtoken"; +import { crypto } from "@app/lib/crypto"; import { UnauthorizedError } from "@app/lib/errors"; import { TDecodedGcpIamAuthJwt, TGcpIdTokenPayload } from "./identity-gcp-auth-types"; @@ -48,7 +48,7 @@ export const validateIamIdentity = async ({ identityId: string; jwt: string; }) => { - const decodedJwt = jwt.decode(serviceAccountJwt, { complete: true }) as TDecodedGcpIamAuthJwt; + const decodedJwt = crypto.jwt().decode(serviceAccountJwt, { complete: true }) as TDecodedGcpIamAuthJwt; const { sub, aud } = decodedJwt.payload; const { @@ -61,7 +61,7 @@ export const validateIamIdentity = async ({ const publicKey = data[decodedJwt.header.kid]; - jwt.verify(serviceAccountJwt, publicKey, { + crypto.jwt().verify(serviceAccountJwt, publicKey, { algorithms: ["RS256"] }); diff --git a/backend/src/services/identity-gcp-auth/identity-gcp-auth-service.ts b/backend/src/services/identity-gcp-auth/identity-gcp-auth-service.ts index 1e21d1d3e..b83697e52 100644 --- a/backend/src/services/identity-gcp-auth/identity-gcp-auth-service.ts +++ b/backend/src/services/identity-gcp-auth/identity-gcp-auth-service.ts @@ -1,5 +1,4 @@ import { ForbiddenError } from "@casl/ability"; -import jwt from "jsonwebtoken"; import { IdentityAuthMethod } from "@app/db/schemas"; import { TLicenseServiceFactory } from "@app/ee/services/license/license-service"; @@ -10,6 +9,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"; import { BadRequestError, NotFoundError, PermissionBoundaryError, UnauthorizedError } from "@app/lib/errors"; import { extractIPDetails, isValidIpOrCidr } from "@app/lib/ip"; @@ -135,7 +135,7 @@ export const identityGcpAuthServiceFactory = ({ }); const appCfg = getConfig(); - const accessToken = jwt.sign( + const accessToken = crypto.jwt().sign( { identityId: identityGcpAuth.identityId, identityAccessTokenId: identityAccessToken.id, diff --git a/backend/src/services/identity-jwt-auth/identity-jwt-auth-service.ts b/backend/src/services/identity-jwt-auth/identity-jwt-auth-service.ts index 7ebf75a85..546980f41 100644 --- a/backend/src/services/identity-jwt-auth/identity-jwt-auth-service.ts +++ b/backend/src/services/identity-jwt-auth/identity-jwt-auth-service.ts @@ -12,6 +12,8 @@ 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"; +import { CompleteJWTPayload } from "@app/lib/crypto/cryptography/types"; import { BadRequestError, ForbiddenRequestError, @@ -79,7 +81,7 @@ export const identityJwtAuthServiceFactory = ({ orgId: identityMembershipOrg.orgId }); - const decodedToken = jwt.decode(jwtValue, { complete: true }); + const decodedToken = crypto.jwt().decode(jwtValue, { complete: true }) as CompleteJWTPayload; if (!decodedToken) { throw new UnauthorizedError({ message: "Invalid JWT" @@ -106,11 +108,11 @@ export const identityJwtAuthServiceFactory = ({ }); } - const { kid } = decodedToken.header; + const { kid } = decodedToken.header as { kid: string }; const jwtSigningKey = await client.getSigningKey(kid); try { - tokenData = jwt.verify(jwtValue, jwtSigningKey.getPublicKey()) as Record; + tokenData = crypto.jwt().verify(jwtValue, jwtSigningKey.getPublicKey()) as Record; } catch (error) { if (error instanceof jwt.JsonWebTokenError) { throw new UnauthorizedError({ @@ -129,7 +131,7 @@ export const identityJwtAuthServiceFactory = ({ let isMatchAnyKey = false; for (const publicKey of decryptedPublicKeys) { try { - tokenData = jwt.verify(jwtValue, publicKey) as Record; + tokenData = crypto.jwt().verify(jwtValue, publicKey) as Record; isMatchAnyKey = true; } catch (error) { if (error instanceof jwt.JsonWebTokenError) { @@ -225,7 +227,7 @@ export const identityJwtAuthServiceFactory = ({ }); const appCfg = getConfig(); - const accessToken = jwt.sign( + const accessToken = crypto.jwt().sign( { identityId: identityJwtAuth.identityId, identityAccessTokenId: identityAccessToken.id, diff --git a/backend/src/services/identity-kubernetes-auth/identity-kubernetes-auth-service.ts b/backend/src/services/identity-kubernetes-auth/identity-kubernetes-auth-service.ts index 3ce28361f..41491ab2f 100644 --- a/backend/src/services/identity-kubernetes-auth/identity-kubernetes-auth-service.ts +++ b/backend/src/services/identity-kubernetes-auth/identity-kubernetes-auth-service.ts @@ -1,7 +1,6 @@ import { ForbiddenError } from "@casl/ability"; import axios, { AxiosError } from "axios"; import https from "https"; -import jwt from "jsonwebtoken"; import RE2 from "re2"; import { IdentityAuthMethod, TIdentityKubernetesAuthsUpdate } from "@app/db/schemas"; @@ -19,6 +18,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"; import { BadRequestError, NotFoundError, PermissionBoundaryError, UnauthorizedError } from "@app/lib/errors"; import { GatewayHttpProxyActions, GatewayProxyProtocol, withGatewayProxy } from "@app/lib/gateway"; import { extractIPDetails, isValidIpOrCidr } from "@app/lib/ip"; @@ -396,7 +396,7 @@ export const identityKubernetesAuthServiceFactory = ({ }); const appCfg = getConfig(); - const accessToken = jwt.sign( + const accessToken = crypto.jwt().sign( { identityId: identityKubernetesAuth.identityId, identityAccessTokenId: identityAccessToken.id, diff --git a/backend/src/services/identity-ldap-auth/identity-ldap-auth-service.ts b/backend/src/services/identity-ldap-auth/identity-ldap-auth-service.ts index 89a169b1a..399ef7da9 100644 --- a/backend/src/services/identity-ldap-auth/identity-ldap-auth-service.ts +++ b/backend/src/services/identity-ldap-auth/identity-ldap-auth-service.ts @@ -1,6 +1,5 @@ /* eslint-disable @typescript-eslint/no-unsafe-assignment */ import { ForbiddenError } from "@casl/ability"; -import jwt from "jsonwebtoken"; import { IdentityAuthMethod } from "@app/db/schemas"; import { testLDAPConfig } from "@app/ee/services/ldap-config/ldap-fns"; @@ -12,6 +11,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"; import { BadRequestError, NotFoundError, PermissionBoundaryError } from "@app/lib/errors"; import { extractIPDetails, isValidIpOrCidr } from "@app/lib/ip"; @@ -153,7 +153,7 @@ export const identityLdapAuthServiceFactory = ({ }); const appCfg = getConfig(); - const accessToken = jwt.sign( + const accessToken = crypto.jwt().sign( { identityId: identityLdapAuth.identityId, identityAccessTokenId: identityAccessToken.id, diff --git a/backend/src/services/identity-oci-auth/identity-oci-auth-service.ts b/backend/src/services/identity-oci-auth/identity-oci-auth-service.ts index 106c30486..3b6450e6e 100644 --- a/backend/src/services/identity-oci-auth/identity-oci-auth-service.ts +++ b/backend/src/services/identity-oci-auth/identity-oci-auth-service.ts @@ -1,7 +1,6 @@ /* eslint-disable @typescript-eslint/no-unsafe-assignment */ import { ForbiddenError } from "@casl/ability"; import { AxiosError } from "axios"; -import jwt from "jsonwebtoken"; import RE2 from "re2"; import { IdentityAuthMethod } from "@app/db/schemas"; @@ -14,6 +13,7 @@ import { import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types"; import { getConfig } from "@app/lib/config/env"; import { request } from "@app/lib/config/request"; +import { crypto } from "@app/lib/crypto"; import { BadRequestError, NotFoundError, PermissionBoundaryError, UnauthorizedError } from "@app/lib/errors"; import { extractIPDetails, isValidIpOrCidr } from "@app/lib/ip"; import { logger } from "@app/lib/logger"; @@ -107,7 +107,7 @@ export const identityOciAuthServiceFactory = ({ }); const appCfg = getConfig(); - const accessToken = jwt.sign( + const accessToken = crypto.jwt().sign( { identityId: identityOciAuth.identityId, identityAccessTokenId: identityAccessToken.id, diff --git a/backend/src/services/identity-oidc-auth/identity-oidc-auth-service.ts b/backend/src/services/identity-oidc-auth/identity-oidc-auth-service.ts index 211d00163..997ed6dfd 100644 --- a/backend/src/services/identity-oidc-auth/identity-oidc-auth-service.ts +++ b/backend/src/services/identity-oidc-auth/identity-oidc-auth-service.ts @@ -13,6 +13,8 @@ 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"; +import { CompleteJWTPayload } from "@app/lib/crypto/cryptography/types"; import { BadRequestError, ForbiddenRequestError, @@ -93,7 +95,7 @@ export const identityOidcAuthServiceFactory = ({ ); const jwksUri = discoveryDoc.jwks_uri; - const decodedToken = jwt.decode(oidcJwt, { complete: true }); + const decodedToken = crypto.jwt().decode(oidcJwt, { complete: true }) as CompleteJWTPayload; if (!decodedToken) { throw new UnauthorizedError({ message: "Invalid JWT" @@ -105,12 +107,12 @@ export const identityOidcAuthServiceFactory = ({ requestAgent: identityOidcAuth.oidcDiscoveryUrl.includes("https") ? requestAgent : undefined }); - const { kid } = decodedToken.header; + const { kid } = decodedToken.header as { kid: string }; const oidcSigningKey = await client.getSigningKey(kid); let tokenData: Record; try { - tokenData = jwt.verify(oidcJwt, oidcSigningKey.getPublicKey(), { + tokenData = crypto.jwt().verify(oidcJwt, oidcSigningKey.getPublicKey(), { issuer: identityOidcAuth.boundIssuer }) as Record; } catch (error) { @@ -193,7 +195,7 @@ export const identityOidcAuthServiceFactory = ({ }); const appCfg = getConfig(); - const accessToken = jwt.sign( + const accessToken = crypto.jwt().sign( { identityId: identityOidcAuth.identityId, identityAccessTokenId: identityAccessToken.id, 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 e2c2e4cb8..e47cc4c9a 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,4 @@ import { ForbiddenError } from "@casl/ability"; -import jwt from "jsonwebtoken"; import { IdentityAuthMethod } from "@app/db/schemas"; import { TLicenseServiceFactory } from "@app/ee/services/license/license-service"; @@ -135,7 +134,7 @@ export const identityTlsCertAuthServiceFactory = ({ }); const appCfg = getConfig(); - const accessToken = jwt.sign( + const accessToken = crypto.jwt().sign( { identityId: identityTlsCertAuth.identityId, identityAccessTokenId: identityAccessToken.id, diff --git a/backend/src/services/identity-token-auth/identity-token-auth-service.ts b/backend/src/services/identity-token-auth/identity-token-auth-service.ts index 8f1218045..82949090e 100644 --- a/backend/src/services/identity-token-auth/identity-token-auth-service.ts +++ b/backend/src/services/identity-token-auth/identity-token-auth-service.ts @@ -1,5 +1,4 @@ import { ForbiddenError } from "@casl/ability"; -import jwt from "jsonwebtoken"; import { IdentityAuthMethod, TableName } from "@app/db/schemas"; import { TLicenseServiceFactory } from "@app/ee/services/license/license-service"; @@ -10,6 +9,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"; import { BadRequestError, NotFoundError, PermissionBoundaryError } from "@app/lib/errors"; import { extractIPDetails, isValidIpOrCidr } from "@app/lib/ip"; @@ -362,7 +362,7 @@ export const identityTokenAuthServiceFactory = ({ }); const appCfg = getConfig(); - const accessToken = jwt.sign( + const accessToken = crypto.jwt().sign( { identityId: identityTokenAuth.identityId, identityAccessTokenId: identityAccessToken.id, diff --git a/backend/src/services/identity-ua/identity-ua-service.ts b/backend/src/services/identity-ua/identity-ua-service.ts index c159f7364..643f32047 100644 --- a/backend/src/services/identity-ua/identity-ua-service.ts +++ b/backend/src/services/identity-ua/identity-ua-service.ts @@ -1,5 +1,4 @@ import { ForbiddenError } from "@casl/ability"; -import jwt from "jsonwebtoken"; import { IdentityAuthMethod } from "@app/db/schemas"; import { TLicenseServiceFactory } from "@app/ee/services/license/license-service"; @@ -147,7 +146,7 @@ export const identityUaServiceFactory = ({ }); const appCfg = getConfig(); - const accessToken = jwt.sign( + const accessToken = crypto.jwt().sign( { identityId: identityUa.identityId, clientSecretId: validClientSecretInfo.id, diff --git a/backend/src/services/integration-auth/integration-token.ts b/backend/src/services/integration-auth/integration-token.ts index a15c9dd1f..1f7061076 100644 --- a/backend/src/services/integration-auth/integration-token.ts +++ b/backend/src/services/integration-auth/integration-token.ts @@ -1,7 +1,6 @@ -import jwt from "jsonwebtoken"; - import { getConfig } from "@app/lib/config/env"; import { request } from "@app/lib/config/request"; +import { crypto } from "@app/lib/crypto"; import { BadRequestError, ForbiddenRequestError, InternalServerError, NotFoundError } from "@app/lib/errors"; import { Integrations, IntegrationUrls } from "./integration-list"; @@ -718,7 +717,7 @@ const exchangeRefreshGCPSecretManager = async ({ exp: Math.floor(Date.now() / 1000) + 3600 }; - const token = jwt.sign(payload, serviceAccount.private_key, { algorithm: "RS256" }); + const token = crypto.jwt().sign(payload, serviceAccount.private_key, { algorithm: "RS256" }); const { data }: { data: ServiceAccountAccessTokenGCPSecretManagerResponse } = await request.post( IntegrationUrls.GCP_TOKEN_URL, diff --git a/backend/src/services/microsoft-teams/microsoft-teams-fns.ts b/backend/src/services/microsoft-teams/microsoft-teams-fns.ts index d477143a9..8734b8459 100644 --- a/backend/src/services/microsoft-teams/microsoft-teams-fns.ts +++ b/backend/src/services/microsoft-teams/microsoft-teams-fns.ts @@ -1,11 +1,11 @@ /* eslint-disable class-methods-use-this */ import axios from "axios"; import { TeamsActivityHandler, TurnContext } from "botbuilder"; -import jwt from "jsonwebtoken"; import { Knex } from "knex"; import { z } from "zod"; import { getConfig } from "@app/lib/config/env"; +import { crypto } from "@app/lib/crypto"; import { BadRequestError } from "@app/lib/errors"; import { logger } from "@app/lib/logger"; import { TNotification, TriggerFeature } from "@app/lib/workflow-integrations/types"; @@ -71,7 +71,7 @@ export const verifyTenantFromCode = async ( ); // Verify application token - const { tid: tenantIdFromApplicationAccessToken } = jwt.decode(applicationAccessToken) as { tid: string }; + const { tid: tenantIdFromApplicationAccessToken } = crypto.jwt().decode(applicationAccessToken) as { tid: string }; if (tenantIdFromApplicationAccessToken !== tenantId) { throw new BadRequestError({ @@ -80,7 +80,9 @@ export const verifyTenantFromCode = async ( } // Verify user authorization token - const { tid: tenantIdFromAuthorizationAccessToken } = jwt.decode(authorizationAccessToken) as { tid: string }; + const { tid: tenantIdFromAuthorizationAccessToken } = crypto.jwt().decode(authorizationAccessToken) as { + tid: string; + }; if (tenantIdFromAuthorizationAccessToken !== tenantId) { throw new BadRequestError({ diff --git a/backend/src/services/org/org-service.ts b/backend/src/services/org/org-service.ts index 4bd8d1715..eb712843a 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 jwt from "jsonwebtoken"; import { Knex } from "knex"; import { @@ -596,7 +595,7 @@ export const orgServiceFactory = ({ const cfg = getConfig(); const authToken = authorizationHeader.replace("Bearer ", ""); - const decodedToken = jwt.verify(authToken, cfg.AUTH_SECRET) as AuthModeJwtTokenPayload; + const decodedToken = crypto.jwt().verify(authToken, cfg.AUTH_SECRET) as AuthModeJwtTokenPayload; if (!decodedToken.authMethod) throw new UnauthorizedError({ name: "Auth method not found on existing token" }); const response = await orgDAL.transaction(async (tx) => { @@ -1304,7 +1303,7 @@ export const orgServiceFactory = ({ } const appCfg = getConfig(); - const token = jwt.sign( + const token = crypto.jwt().sign( { authTokenType: AuthTokenType.SIGNUP_TOKEN, userId: user.id diff --git a/backend/src/services/super-admin/super-admin-service.ts b/backend/src/services/super-admin/super-admin-service.ts index 70cf7d67f..0de4950f0 100644 --- a/backend/src/services/super-admin/super-admin-service.ts +++ b/backend/src/services/super-admin/super-admin-service.ts @@ -1,5 +1,4 @@ import { CronJob } from "cron"; -import jwt from "jsonwebtoken"; import { IdentityAuthMethod, OrgMembershipRole, TSuperAdmin, TSuperAdminUpdate } from "@app/db/schemas"; import { TLicenseServiceFactory } from "@app/ee/services/license/license-service"; @@ -670,7 +669,7 @@ export const superAdminServiceFactory = ({ tx ); - const generatedAccessToken = jwt.sign( + const generatedAccessToken = crypto.jwt().sign( { identityId: newIdentity.id, identityAccessTokenId: newToken.id, From e77911f574361b12b72b2e8c5125040b2ce99595 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard Date: Tue, 8 Jul 2025 20:40:57 +0400 Subject: [PATCH 12/27] fix: build fails and standalone docker fixes --- Dockerfile.fips.standalone-infisical | 39 +++++++++---------- backend/src/lib/crypto/cryptography/crypto.ts | 13 +------ backend/src/lib/crypto/cryptography/index.ts | 5 +-- backend/src/lib/crypto/index.ts | 2 +- backend/src/lib/telemetry/instrumentation.ts | 3 +- backend/src/queue/queue-service.ts | 3 ++ 6 files changed, 28 insertions(+), 37 deletions(-) diff --git a/Dockerfile.fips.standalone-infisical b/Dockerfile.fips.standalone-infisical index 667245cb6..c06347c7a 100644 --- a/Dockerfile.fips.standalone-infisical +++ b/Dockerfile.fips.standalone-infisical @@ -73,17 +73,6 @@ RUN apt-get update && apt-get install -y \ # Configure ODBC RUN printf "[FreeTDS]\nDescription = FreeTDS Driver\nDriver = /usr/lib/x86_64-linux-gnu/odbc/libtdsodbc.so\nSetup = /usr/lib/x86_64-linux-gnu/odbc/libtdsS.so\nFileUsage = 1\n" > /etc/odbcinst.ini - -# Build and install FIPS validated OpenSSL -WORKDIR /openssl-build -RUN wget https://www.openssl.org/source/openssl-3.1.2.tar.gz \ - && tar -xf openssl-3.1.2.tar.gz \ - && cd openssl-3.1.2 \ - && ./Configure enable-fips \ - && make \ - && make install_fips - - COPY backend/package*.json ./ RUN npm ci --only-production @@ -114,15 +103,6 @@ RUN apt-get update && apt-get install -y \ # Configure ODBC RUN printf "[FreeTDS]\nDescription = FreeTDS Driver\nDriver = /usr/lib/x86_64-linux-gnu/odbc/libtdsodbc.so\nSetup = /usr/lib/x86_64-linux-gnu/odbc/libtdsS.so\nFileUsage = 1\n" > /etc/odbcinst.ini -# Build and install FIPS validated OpenSSL -WORKDIR /openssl-build -RUN wget https://www.openssl.org/source/openssl-3.1.2.tar.gz \ - && tar -xf openssl-3.1.2.tar.gz \ - && cd openssl-3.1.2 \ - && ./Configure enable-fips \ - && make \ - && make install_fips - COPY backend/package*.json ./ RUN npm ci --only-production @@ -135,6 +115,12 @@ FROM base AS production # Install necessary packages including ODBC RUN apt-get update && apt-get install -y \ + build-essential \ + autoconf \ + automake \ + libtool \ + wget \ + libssl-dev \ ca-certificates \ curl \ git \ @@ -152,6 +138,15 @@ RUN apt-get update && apt-get install -y \ # Configure ODBC in production RUN printf "[FreeTDS]\nDescription = FreeTDS Driver\nDriver = /usr/lib/x86_64-linux-gnu/odbc/libtdsodbc.so\nSetup = /usr/lib/x86_64-linux-gnu/odbc/libtdsS.so\nFileUsage = 1\n" > /etc/odbcinst.ini + +WORKDIR /openssl-build +RUN wget https://www.openssl.org/source/openssl-3.1.2.tar.gz \ + && tar -xf openssl-3.1.2.tar.gz \ + && cd openssl-3.1.2 \ + && ./Configure enable-fips \ + && make \ + && make install_fips + # Install Infisical CLI RUN curl -1sLf 'https://artifacts-cli.infisical.com/setup.deb.sh' | bash \ && apt-get update && apt-get install -y infisical=0.41.89 \ @@ -198,14 +193,16 @@ ENV OPENSSL_CONF=/backend/nodejs.fips.cnf ENV OPENSSL_MODULES=/usr/local/lib/ossl-modules ENV NODE_OPTIONS=--force-fips ENV FIPS_ENABLED=true + WORKDIR /backend ENV TELEMETRY_ENABLED true EXPOSE 8080 +EXPOSE 80 EXPOSE 443 USER non-root-user -CMD ["./standalone-entrypoint.sh"] +CMD ["./standalone-entrypoint.sh"] \ No newline at end of file diff --git a/backend/src/lib/crypto/cryptography/crypto.ts b/backend/src/lib/crypto/cryptography/crypto.ts index f5c2182e6..e8111dfed 100644 --- a/backend/src/lib/crypto/cryptography/crypto.ts +++ b/backend/src/lib/crypto/cryptography/crypto.ts @@ -20,17 +20,8 @@ import { logger } from "../../logger"; import { asymmetricFipsValidated } from "./asymmetric-fips"; import { hasherFipsValidated } from "./hash-fips"; import { jwtFipsValidated } from "./jwt-fips"; -import { - DigestType, - JWTPayload, - JWTSecretOrKey, - JWTSignOptions, - JWTVerifyOptions, - SymmetricKeySize, - TDecryptAsymmetricInput, - TDecryptSymmetricInput, - TEncryptSymmetricInput -} from "./types"; +import type { TDecryptAsymmetricInput, TDecryptSymmetricInput, TEncryptSymmetricInput } from "./types"; +import { DigestType, JWTPayload, JWTSecretOrKey, JWTSignOptions, JWTVerifyOptions, SymmetricKeySize } from "./types"; const bytesToBits = (bytes: number) => bytes * 8; diff --git a/backend/src/lib/crypto/cryptography/index.ts b/backend/src/lib/crypto/cryptography/index.ts index 5edd5fb89..e1cc71548 100644 --- a/backend/src/lib/crypto/cryptography/index.ts +++ b/backend/src/lib/crypto/cryptography/index.ts @@ -1,9 +1,8 @@ export { crypto } from "./crypto"; -export { - DigestType, - SymmetricKeySize, +export type { TDecryptAsymmetricInput, TDecryptSymmetricInput, TEncryptedWithRootEncryptionKey, TEncryptSymmetricInput } from "./types"; +export { DigestType, SymmetricKeySize } from "./types"; diff --git a/backend/src/lib/crypto/index.ts b/backend/src/lib/crypto/index.ts index d67e75d0b..aac797a3e 100644 --- a/backend/src/lib/crypto/index.ts +++ b/backend/src/lib/crypto/index.ts @@ -1,4 +1,4 @@ -export { crypto, SymmetricKeySize, TEncryptedWithRootEncryptionKey } from "./cryptography"; +export { crypto, SymmetricKeySize } from "./cryptography"; export { buildSecretBlindIndexFromName } from "./encryption"; export { decryptIntegrationAuths, diff --git a/backend/src/lib/telemetry/instrumentation.ts b/backend/src/lib/telemetry/instrumentation.ts index 44c910816..1c9b29cc7 100644 --- a/backend/src/lib/telemetry/instrumentation.ts +++ b/backend/src/lib/telemetry/instrumentation.ts @@ -6,7 +6,6 @@ import { HttpInstrumentation } from "@opentelemetry/instrumentation-http"; import { Resource } from "@opentelemetry/resources"; import { AggregationTemporality, MeterProvider, PeriodicExportingMetricReader } from "@opentelemetry/sdk-metrics"; import { ATTR_SERVICE_NAME, ATTR_SERVICE_VERSION } from "@opentelemetry/semantic-conventions"; -import tracer from "dd-trace"; import dotenv from "dotenv"; import { initEnvConfig } from "../config/env"; @@ -89,6 +88,8 @@ const setupTelemetry = async () => { } if (envCfg.SHOULD_USE_DATADOG_TRACER) { + const tracer = await import("dd-trace"); + console.log("Initializing Datadog tracer"); tracer.init({ profiling: envCfg.DATADOG_PROFILING_ENABLED, diff --git a/backend/src/queue/queue-service.ts b/backend/src/queue/queue-service.ts index 9f98a7347..2761179a8 100644 --- a/backend/src/queue/queue-service.ts +++ b/backend/src/queue/queue-service.ts @@ -448,6 +448,9 @@ export const queueServiceFactory = ( if (appCfg.QUEUE_WORKERS_ENABLED && isQueueEnabled(name)) { workerContainer[name] = new Worker(name, jobFn, { ...queueSettings, + settings: { + repeatKeyHashAlgorithm: "sha256" + }, connection }); } From be924f23e6d0c4e158a455aff233a72f97b5b4f9 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard Date: Tue, 8 Jul 2025 22:21:29 +0400 Subject: [PATCH 13/27] minor fixes --- backend/src/lib/config/env.ts | 3 ++- package-lock.json | 26 -------------------------- package.json | 2 -- 3 files changed, 2 insertions(+), 29 deletions(-) diff --git a/backend/src/lib/config/env.ts b/backend/src/lib/config/env.ts index 11b7ecedc..d052f089a 100644 --- a/backend/src/lib/config/env.ts +++ b/backend/src/lib/config/env.ts @@ -364,9 +364,10 @@ export const initEnvConfig = async (superAdminDAL?: TSuperAdminDALFactory, logge if (fipsEnabled) { const newEnvCfg = { - ...envCfg, + ...parsedEnv.data, ROOT_ENCRYPTION_KEY: envCfg.ENCRYPTION_KEY }; + delete newEnvCfg.ENCRYPTION_KEY; envCfg = Object.freeze(newEnvCfg); diff --git a/package-lock.json b/package-lock.json index ce284b431..4d72220af 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,11 +8,9 @@ "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" @@ -482,13 +480,6 @@ } } }, - "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", @@ -654,12 +645,6 @@ "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", @@ -1851,12 +1836,6 @@ "@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", @@ -1979,11 +1958,6 @@ "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 f30fda33d..db15de3fb 100644 --- a/package.json +++ b/package.json @@ -19,14 +19,12 @@ ] }, "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" } } From c0b296b86b48cf2f65b45d7297a9e6b8a855e98d Mon Sep 17 00:00:00 2001 From: Daniel Hougaard Date: Wed, 9 Jul 2025 12:32:40 +0400 Subject: [PATCH 14/27] Update jwt-fips.ts --- .../src/lib/crypto/cryptography/jwt-fips.ts | 74 ++++++++++++++----- 1 file changed, 56 insertions(+), 18 deletions(-) diff --git a/backend/src/lib/crypto/cryptography/jwt-fips.ts b/backend/src/lib/crypto/cryptography/jwt-fips.ts index 42e5809c3..1b7ee3367 100644 --- a/backend/src/lib/crypto/cryptography/jwt-fips.ts +++ b/backend/src/lib/crypto/cryptography/jwt-fips.ts @@ -1,6 +1,8 @@ import crypto from "crypto"; import RE2 from "re2"; +import { CryptographyError } from "@app/lib/errors"; + import { Algorithm, CompleteJWTPayload, JWTPayload, JWTSecretOrKey, JWTSignOptions, JWTVerifyOptions } from "./types"; export const jwtFipsValidated = () => { @@ -14,7 +16,9 @@ export const jwtFipsValidated = () => { const match = new RE2(/^(\d+)([smhd])$/).exec(timeStr); if (!match) { - throw new Error(`Invalid time format: ${timeStr}`); + throw new CryptographyError({ + message: `Invalid JWT time format: ${timeStr}` + }); } const value = parseInt(match[1], 10); @@ -30,7 +34,9 @@ export const jwtFipsValidated = () => { case "d": return value * 60 * 60 * 24; default: - throw new Error(`Unknown time unit: ${unit}`); + throw new CryptographyError({ + message: `Unknown JWT time unit: ${unit}` + }); } }; @@ -46,7 +52,9 @@ export const jwtFipsValidated = () => { case "RS512": return "sha512"; default: - throw new Error(`Unsupported algorithm: ${algorithm}`); + throw new CryptographyError({ + message: `Unsupported JWT algorithm: ${algorithm}` + }); } }; @@ -88,7 +96,9 @@ export const jwtFipsValidated = () => { // Try to create a proper private key object privateKey = crypto.createPrivateKey(secretOrPrivateKey); } catch (error) { - throw new Error("Invalid JWT private key"); + throw new CryptographyError({ + message: "Invalid JWT private key" + }); } } else { privateKey = secretOrPrivateKey; @@ -99,7 +109,9 @@ export const jwtFipsValidated = () => { } else { // HMAC signing if (typeof secretOrPrivateKey !== "string") { - throw new Error("HMAC algorithms require a string secret"); + throw new CryptographyError({ + message: "HMAC algorithms require a string secret" + }); } signature = crypto .createHmac(hashAlgorithm, secretOrPrivateKey) @@ -116,7 +128,9 @@ export const jwtFipsValidated = () => { const verify = (token: string, secretOrKey: JWTSecretOrKey, options: JWTVerifyOptions = {}) => { const parts = token.split("."); if (parts.length !== 3) { - throw new Error("Invalid JWT format"); + throw new CryptographyError({ + message: "Invalid JWT format" + }); } const [encodedHeader, encodedPayload, signature] = parts; @@ -126,7 +140,9 @@ export const jwtFipsValidated = () => { const header = JSON.parse(headerJson) as { alg: string; typ: string; kid?: string }; if (!header.alg || header.typ !== "JWT") { - throw new Error("Invalid JWT header"); + throw new CryptographyError({ + message: "Invalid JWT header" + }); } // Extract the actual key from different input types @@ -161,7 +177,9 @@ export const jwtFipsValidated = () => { verificationKey = crypto.createPublicKey(cleanKey); } } catch (error) { - throw new Error("Invalid JWT signature"); + throw new CryptographyError({ + message: "Invalid JWT signature" + }); } // Convert base64url signature back to buffer @@ -183,7 +201,9 @@ export const jwtFipsValidated = () => { } if (!isValidSignature) { - throw new Error("Invalid JWT signature"); + throw new CryptographyError({ + message: "Invalid JWT signature" + }); } // Decode payload @@ -201,12 +221,16 @@ export const jwtFipsValidated = () => { // Check expiration if (!options.ignoreExpiration && payload.exp && now - clockTolerance > payload.exp) { - throw new Error("JWT token has expired"); + throw new CryptographyError({ + message: "JWT token has expired" + }); } // Check not before if (!options.ignoreNotBefore && payload.nbf && now + clockTolerance < payload.nbf) { - throw new Error("JWT not active"); + throw new CryptographyError({ + message: "JWT not active" + }); } // Check audience @@ -215,7 +239,9 @@ export const jwtFipsValidated = () => { const tokenAudiences = Array.isArray(payload.aud) ? payload.aud : [payload.aud]; const hasValidAudience = audiences.some((aud) => tokenAudiences.includes(aud)); if (!hasValidAudience) { - throw new Error("JWT audience invalid"); + throw new CryptographyError({ + message: "JWT audience invalid" + }); } } @@ -223,31 +249,41 @@ export const jwtFipsValidated = () => { if (options.issuer) { const issuers = Array.isArray(options.issuer) ? options.issuer : [options.issuer]; if (!payload.iss || !issuers.includes(payload.iss)) { - throw new Error("JWT issuer invalid"); + throw new CryptographyError({ + message: "JWT issuer invalid" + }); } } // Check subject if (options.subject && payload.sub !== options.subject) { - throw new Error("JWT subject invalid"); + throw new CryptographyError({ + message: "JWT subject invalid" + }); } // Check JWT ID if (options.jwtid && payload.jti !== options.jwtid) { - throw new Error("JWT ID invalid"); + throw new CryptographyError({ + message: "JWT ID invalid" + }); } // Check max age if (options.maxAge && payload.iat) { const maxAgeSeconds = typeof options.maxAge === "string" ? $parseTimeToSeconds(options.maxAge) : options.maxAge; if (now - payload.iat > maxAgeSeconds) { - throw new Error("JWT max age exceeded"); + throw new CryptographyError({ + message: "JWT max age exceeded" + }); } } // Check algorithms if (options.algorithms && !options.algorithms.includes(header.alg as Algorithm)) { - throw new Error(`Algorithm not allowed: ${header.alg}`); + throw new CryptographyError({ + message: `Algorithm not allowed: ${header.alg}` + }); } return payload; @@ -256,7 +292,9 @@ export const jwtFipsValidated = () => { const decode = (token: string, options: { complete?: boolean } = {}): JWTPayload | CompleteJWTPayload => { const parts = token.split("."); if (parts.length !== 3) { - throw new Error("Invalid JWT format"); + throw new CryptographyError({ + message: "Invalid JWT format" + }); } const [encodedHeader, encodedPayload] = parts; From 419db549eaa44acc5db74d3ca90cc60b868a379e Mon Sep 17 00:00:00 2001 From: Daniel Hougaard Date: Wed, 9 Jul 2025 13:45:59 +0400 Subject: [PATCH 15/27] fix: crypto errors and disable acme --- backend/src/lib/crypto/cryptography/crypto.ts | 9 +++----- backend/src/queue/queue-service.ts | 23 ++++++++++++++----- .../acme/acme-certificate-authority-fns.ts | 8 ++++++- 3 files changed, 27 insertions(+), 13 deletions(-) diff --git a/backend/src/lib/crypto/cryptography/crypto.ts b/backend/src/lib/crypto/cryptography/crypto.ts index e8111dfed..2fd747ded 100644 --- a/backend/src/lib/crypto/cryptography/crypto.ts +++ b/backend/src/lib/crypto/cryptography/crypto.ts @@ -433,12 +433,9 @@ const cryptographyFactory = () => { 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 + generateKey: subtle.generateKey.bind(subtle), + importKey: subtle.importKey.bind(subtle), + exportKey: subtle.exportKey.bind(subtle) }, constants: crypto.constants, X509Certificate: crypto.X509Certificate, diff --git a/backend/src/queue/queue-service.ts b/backend/src/queue/queue-service.ts index 2761179a8..9f4d1c67f 100644 --- a/backend/src/queue/queue-service.ts +++ b/backend/src/queue/queue-service.ts @@ -18,6 +18,7 @@ import { } from "@app/ee/services/secret-scanning-v2/secret-scanning-v2-types"; import { getConfig } from "@app/lib/config/env"; import { buildRedisFromConfig, TRedisConfigKeys } from "@app/lib/config/redis"; +import { crypto } from "@app/lib/crypto"; import { logger } from "@app/lib/logger"; import { QueueWorkerProfile } from "@app/lib/types"; import { CaType } from "@app/services/certificate-authority/certificate-authority-enums"; @@ -438,9 +439,14 @@ export const queueServiceFactory = ( queueContainer[name] = new Queue(name as string, { ...queueSettings, - settings: { - repeatKeyHashAlgorithm: "sha256" - }, + ...(crypto.isFipsModeEnabled() + ? { + settings: { + ...queueSettings?.settings, + repeatKeyHashAlgorithm: "sha256" + } + } + : {}), connection }); @@ -448,9 +454,14 @@ export const queueServiceFactory = ( if (appCfg.QUEUE_WORKERS_ENABLED && isQueueEnabled(name)) { workerContainer[name] = new Worker(name, jobFn, { ...queueSettings, - settings: { - repeatKeyHashAlgorithm: "sha256" - }, + ...(crypto.isFipsModeEnabled() + ? { + settings: { + ...queueSettings?.settings, + repeatKeyHashAlgorithm: "sha256" + } + } + : {}), connection }); } 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 46a55821d..d7893779c 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 @@ -5,7 +5,7 @@ import acme from "acme-client"; import { TableName } from "@app/db/schemas"; import { CustomAWSHasher } from "@app/lib/aws/hashing"; import { crypto } from "@app/lib/crypto/cryptography"; -import { BadRequestError, NotFoundError } from "@app/lib/errors"; +import { BadRequestError, CryptographyError, NotFoundError } from "@app/lib/errors"; import { OrgServiceActor } from "@app/lib/types"; import { blockLocalAndPrivateIpAddresses } from "@app/lib/validator"; import { TAppConnectionDALFactory } from "@app/services/app-connection/app-connection-dal"; @@ -190,6 +190,12 @@ export const AcmeCertificateAuthorityFns = ({ enableDirectIssuance: boolean; actor: OrgServiceActor; }) => { + if (crypto.isFipsModeEnabled()) { + throw new CryptographyError({ + message: "ACME is currently not supported in FIPS mode of operation." + }); + } + const { dnsAppConnectionId, directoryUrl, accountEmail, dnsProviderConfig } = configuration; const appConnection = await appConnectionDAL.findById(dnsAppConnectionId); From 3c4549e262c9098397f3e63e58820f0129071ffa Mon Sep 17 00:00:00 2001 From: Daniel Hougaard Date: Wed, 9 Jul 2025 23:33:18 +0400 Subject: [PATCH 16/27] feat(fips): requested changes & additional fixes --- Dockerfile.fips.standalone-infisical | 5 +- backend/Dockerfile.dev.fips | 2 +- backend/src/db/seed-data.ts | 2 +- backend/src/ee/services/kmip/kmip-service.ts | 6 + backend/src/lib/config/env.ts | 28 ++ backend/src/lib/crypto/cache.ts | 8 +- .../crypto/cryptography/asymmetric-fips.ts | 20 +- backend/src/lib/crypto/cryptography/crypto.ts | 37 +- .../src/lib/crypto/cryptography/jwt-fips.ts | 327 ------------------ backend/src/lib/crypto/cryptography/types.ts | 38 -- backend/src/lib/crypto/srp.ts | 2 +- backend/src/lib/telemetry/instrumentation.ts | 29 +- backend/src/main.ts | 2 + .../server/plugins/auth/inject-identity.ts | 4 +- .../identity-jwt-auth-service.ts | 3 +- .../identity-oidc-auth-service.ts | 3 +- backend/src/services/org/org-service.ts | 2 +- .../services/project-bot/project-bot-fns.ts | 2 +- .../project-bot/project-bot-service.ts | 2 +- 19 files changed, 91 insertions(+), 431 deletions(-) delete mode 100644 backend/src/lib/crypto/cryptography/jwt-fips.ts diff --git a/Dockerfile.fips.standalone-infisical b/Dockerfile.fips.standalone-infisical index c06347c7a..d2b2a2d87 100644 --- a/Dockerfile.fips.standalone-infisical +++ b/Dockerfile.fips.standalone-infisical @@ -200,9 +200,12 @@ WORKDIR /backend ENV TELEMETRY_ENABLED true EXPOSE 8080 -EXPOSE 80 EXPOSE 443 +# Remove telemetry. dd-trace uses BullMQ with MD5 hashing, which breaks when FIPS mode is enabled. +RUN grep -v 'import "./lib/telemetry/instrumentation.mjs";' dist/main.mjs > dist/main.mjs.tmp && \ + mv dist/main.mjs.tmp dist/main.mjs + USER non-root-user CMD ["./standalone-entrypoint.sh"] \ No newline at end of file diff --git a/backend/Dockerfile.dev.fips b/backend/Dockerfile.dev.fips index 23f9db295..977362e03 100644 --- a/backend/Dockerfile.dev.fips +++ b/backend/Dockerfile.dev.fips @@ -80,7 +80,7 @@ COPY . . ENV HOST=0.0.0.0 ENV OPENSSL_CONF=/app/nodejs.fips.cnf ENV OPENSSL_MODULES=/usr/local/lib/ossl-modules -ENV NODE_OPTIONS=--force-fips +# ENV NODE_OPTIONS=--force-fips # Note(Daniel): We can't set this on the node options because it may break for existing folks using the infisical/infisical-fips image. Instead we call crypto.setFips(true) at runtime. ENV FIPS_ENABLED=true CMD ["npm", "run", "dev:docker"] diff --git a/backend/src/db/seed-data.ts b/backend/src/db/seed-data.ts index 2822a785b..d923e45f1 100644 --- a/backend/src/db/seed-data.ts +++ b/backend/src/db/seed-data.ts @@ -52,7 +52,7 @@ export const seedData1 = { }; export const generateUserSrpKeys = async (password: string) => { - const { publicKey, privateKey } = crypto.encryption().asymmetric().generateKeyPair(); + const { publicKey, privateKey } = await crypto.encryption().asymmetric().generateKeyPair(); // eslint-disable-next-line const client = new jsrp.client(); diff --git a/backend/src/ee/services/kmip/kmip-service.ts b/backend/src/ee/services/kmip/kmip-service.ts index 44d970fa3..c09843120 100644 --- a/backend/src/ee/services/kmip/kmip-service.ts +++ b/backend/src/ee/services/kmip/kmip-service.ts @@ -67,6 +67,12 @@ export const kmipServiceFactory = ({ description, permissions }: TCreateKmipClientDTO) => { + if (crypto.isFipsModeEnabled()) { + throw new BadRequestError({ + message: "KMIP is currently not supported in FIPS mode of operation." + }); + } + const { permission } = await permissionService.getProjectPermission({ actor, actorId, diff --git a/backend/src/lib/config/env.ts b/backend/src/lib/config/env.ts index d052f089a..d7a934763 100644 --- a/backend/src/lib/config/env.ts +++ b/backend/src/lib/config/env.ts @@ -386,6 +386,34 @@ export const initEnvConfig = async (superAdminDAL?: TSuperAdminDALFactory, logge return envCfg; }; +export const getTelemetryConfig = () => { + const parsedEnv = envSchema.safeParse(process.env); + if (!parsedEnv.success) { + console.error("Invalid environment variables. Check the error below"); + console.error(parsedEnv.error.issues); + process.exit(-1); + } + + return { + useOtel: parsedEnv.data.OTEL_TELEMETRY_COLLECTION_ENABLED, + useDataDogTracer: parsedEnv.data.SHOULD_USE_DATADOG_TRACER, + OTEL: { + otlpURL: parsedEnv.data.OTEL_EXPORT_OTLP_ENDPOINT, + otlpUser: parsedEnv.data.OTEL_COLLECTOR_BASIC_AUTH_USERNAME, + otlpPassword: parsedEnv.data.OTEL_COLLECTOR_BASIC_AUTH_PASSWORD, + otlpPushInterval: parsedEnv.data.OTEL_OTLP_PUSH_INTERVAL, + exportType: parsedEnv.data.OTEL_EXPORT_TYPE + }, + TRACER: { + profiling: parsedEnv.data.DATADOG_PROFILING_ENABLED, + version: parsedEnv.data.INFISICAL_PLATFORM_VERSION, + env: parsedEnv.data.DATADOG_ENV, + service: parsedEnv.data.DATADOG_SERVICE, + hostname: parsedEnv.data.DATADOG_HOSTNAME + } + }; +}; + export const getDatabaseCredentials = (logger?: CustomLogger) => { const parsedEnv = envSchema.safeParse(process.env); if (!parsedEnv.success) { diff --git a/backend/src/lib/crypto/cache.ts b/backend/src/lib/crypto/cache.ts index e74a01d8c..6a050c8a7 100644 --- a/backend/src/lib/crypto/cache.ts +++ b/backend/src/lib/crypto/cache.ts @@ -1,4 +1,10 @@ import { crypto } from "@app/lib/crypto/cryptography"; export const generateCacheKeyFromData = (data: unknown) => - crypto.rawCrypto.createHash("sha256").update(JSON.stringify(data)).digest("base64"); + crypto.rawCrypto. + .createHash("sha256") + .update(JSON.stringify(data)) + .digest("base64") + .replace(/\+/g, "-") + .replace(/\//g, "_") + .replace(/=/g, ""); diff --git a/backend/src/lib/crypto/cryptography/asymmetric-fips.ts b/backend/src/lib/crypto/cryptography/asymmetric-fips.ts index 9e1e12459..da49f99fc 100644 --- a/backend/src/lib/crypto/cryptography/asymmetric-fips.ts +++ b/backend/src/lib/crypto/cryptography/asymmetric-fips.ts @@ -1,11 +1,25 @@ -import crypto from "crypto"; +import crypto, { KeyObject } from "crypto"; import { SecretEncryptionAlgo } from "@app/db/schemas"; import { CryptographyError } from "@app/lib/errors"; +import { logger } from "@app/lib/logger"; export const asymmetricFipsValidated = () => { - const generateKeyPair = () => { - const { publicKey, privateKey } = crypto.generateKeyPairSync("x25519"); + const generateKeyPair = async () => { + const { publicKey, privateKey } = await new Promise<{ publicKey: KeyObject; privateKey: KeyObject }>((resolve) => { + crypto.generateKeyPair("x25519", undefined, (err, pubKey, privKey) => { + if (err) { + logger.error(err, "FIPS generateKeyPair: Failed to generate key pair"); + throw new CryptographyError({ + message: "Failed to generate key pair" + }); + } + resolve({ + publicKey: pubKey, + privateKey: privKey + }); + }); + }); return { publicKey: publicKey.export({ type: "spki", format: "der" }).toString("base64"), diff --git a/backend/src/lib/crypto/cryptography/crypto.ts b/backend/src/lib/crypto/cryptography/crypto.ts index 2fd747ded..df525e708 100644 --- a/backend/src/lib/crypto/cryptography/crypto.ts +++ b/backend/src/lib/crypto/cryptography/crypto.ts @@ -19,9 +19,8 @@ import { CryptographyError } from "../../errors"; import { logger } from "../../logger"; import { asymmetricFipsValidated } from "./asymmetric-fips"; import { hasherFipsValidated } from "./hash-fips"; -import { jwtFipsValidated } from "./jwt-fips"; import type { TDecryptAsymmetricInput, TDecryptSymmetricInput, TEncryptSymmetricInput } from "./types"; -import { DigestType, JWTPayload, JWTSecretOrKey, JWTSignOptions, JWTVerifyOptions, SymmetricKeySize } from "./types"; +import { DigestType, SymmetricKeySize } from "./types"; const bytesToBits = (bytes: number) => bytes * 8; @@ -122,6 +121,8 @@ const cryptographyFactory = () => { const $setFipsModeEnabled = (enabled: boolean) => { // If FIPS is enabled, we need to validate that the ENCRYPTION_KEY is in a base64 format, and is a 256-bit key. if (enabled) { + crypto.setFips(true); + const appCfg = getConfig(); if (appCfg.ENCRYPTION_KEY) { @@ -190,9 +191,10 @@ const cryptographyFactory = () => { $checkIsInitialized(); const asymmetric = () => { - const generateKeyPair = () => { + const generateKeyPair = async () => { if (isFipsModeEnabled()) { - return asymmetricFipsValidated().generateKeyPair(); + const keyPair = await asymmetricFipsValidated().generateKeyPair(); + return keyPair; } return generateAsymmetricKeyPairNoFipsValidation(); }; @@ -381,31 +383,10 @@ const cryptographyFactory = () => { const jwt = () => { $checkIsInitialized(); - const sign = (payload: JWTPayload, secretOrKey: JWTSecretOrKey, options: JWTSignOptions = {}) => { - if (isFipsModeEnabled()) { - return jwtFipsValidated().sign(payload, secretOrKey, options); - } - return jwtDep.sign(payload, secretOrKey, options); - }; - - const verify = (token: string, secretOrKey: JWTSecretOrKey, options: JWTVerifyOptions = {}) => { - if (isFipsModeEnabled()) { - return jwtFipsValidated().verify(token, secretOrKey, options); - } - return jwtDep.verify(token, secretOrKey, options); - }; - - const decode = (token: string, options: { complete?: boolean } = {}) => { - if (isFipsModeEnabled()) { - return jwtFipsValidated().decode(token, options); - } - return jwtDep.decode(token, options); - }; - return { - sign, - verify, - decode + sign: jwtDep.sign, + verify: jwtDep.verify, + decode: jwtDep.decode }; }; diff --git a/backend/src/lib/crypto/cryptography/jwt-fips.ts b/backend/src/lib/crypto/cryptography/jwt-fips.ts deleted file mode 100644 index 1b7ee3367..000000000 --- a/backend/src/lib/crypto/cryptography/jwt-fips.ts +++ /dev/null @@ -1,327 +0,0 @@ -import crypto from "crypto"; -import RE2 from "re2"; - -import { CryptographyError } from "@app/lib/errors"; - -import { Algorithm, CompleteJWTPayload, JWTPayload, JWTSecretOrKey, JWTSignOptions, JWTVerifyOptions } from "./types"; - -export const jwtFipsValidated = () => { - const $base64urlEncode = (str: string) => Buffer.from(str).toString("base64url"); - const $isRSAAlgorithm = (algorithm: string) => algorithm.startsWith("RS"); - - const $parseTimeToSeconds = (timeStr: string | number) => { - if (typeof timeStr === "number") { - return timeStr; - } - - const match = new RE2(/^(\d+)([smhd])$/).exec(timeStr); - if (!match) { - throw new CryptographyError({ - message: `Invalid JWT time format: ${timeStr}` - }); - } - - const value = parseInt(match[1], 10); - const unit = match[2]; - - switch (unit) { - case "s": - return value; - case "m": - return value * 60; - case "h": - return value * 60 * 60; - case "d": - return value * 60 * 60 * 24; - default: - throw new CryptographyError({ - message: `Unknown JWT time unit: ${unit}` - }); - } - }; - - const $getHashAlgorithm = (algorithm: string) => { - switch (algorithm) { - case "HS256": - case "RS256": - return "sha256"; - case "HS384": - case "RS384": - return "sha384"; - case "HS512": - case "RS512": - return "sha512"; - default: - throw new CryptographyError({ - message: `Unsupported JWT algorithm: ${algorithm}` - }); - } - }; - - /** - * Sign a JWT token - */ - const sign = (payload: JWTPayload, secretOrPrivateKey: JWTSecretOrKey, options: JWTSignOptions = {}) => { - const algorithm = options.algorithm || "HS256"; - - const now = Math.floor(Date.now() / 1000); - // Create header - const header = { - alg: algorithm, - typ: "JWT", - ...(options.keyid && { kid: options.keyid }) - }; - - // Create payload with timestamps - const finalPayload = { - ...payload, - iat: now, - ...(options.expiresIn !== undefined && { exp: now + $parseTimeToSeconds(options.expiresIn) }) - }; - - // Encode header and payload - const encodedHeader = $base64urlEncode(JSON.stringify(header)); - const encodedPayload = $base64urlEncode(JSON.stringify(finalPayload)); - - // Create signature - const data = `${encodedHeader}.${encodedPayload}`; - const hashAlgorithm = $getHashAlgorithm(algorithm); - let signature: string; - - if ($isRSAAlgorithm(algorithm)) { - let privateKey: crypto.KeyLike | { key: string | Buffer }; - - if (typeof secretOrPrivateKey === "string") { - try { - // Try to create a proper private key object - privateKey = crypto.createPrivateKey(secretOrPrivateKey); - } catch (error) { - throw new CryptographyError({ - message: "Invalid JWT private key" - }); - } - } else { - privateKey = secretOrPrivateKey; - } - - const signatureBuffer = crypto.sign(hashAlgorithm, Buffer.from(data), privateKey); - signature = signatureBuffer.toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, ""); - } else { - // HMAC signing - if (typeof secretOrPrivateKey !== "string") { - throw new CryptographyError({ - message: "HMAC algorithms require a string secret" - }); - } - signature = crypto - .createHmac(hashAlgorithm, secretOrPrivateKey) - .update(data) - .digest("base64") - .replace(/\+/g, "-") - .replace(/\//g, "_") - .replace(/=/g, ""); - } - - return `${data}.${signature}`; - }; - - const verify = (token: string, secretOrKey: JWTSecretOrKey, options: JWTVerifyOptions = {}) => { - const parts = token.split("."); - if (parts.length !== 3) { - throw new CryptographyError({ - message: "Invalid JWT format" - }); - } - - const [encodedHeader, encodedPayload, signature] = parts; - - // Decode header - const headerJson = Buffer.from(encodedHeader, "base64").toString(); - const header = JSON.parse(headerJson) as { alg: string; typ: string; kid?: string }; - - if (!header.alg || header.typ !== "JWT") { - throw new CryptographyError({ - message: "Invalid JWT header" - }); - } - - // Extract the actual key from different input types - let keyString: string; - if (Buffer.isBuffer(secretOrKey)) { - keyString = secretOrKey.toString(); - } else if (typeof secretOrKey === "object" && "key" in secretOrKey) { - keyString = Buffer.isBuffer(secretOrKey.key) ? secretOrKey.key.toString() : secretOrKey.key; - } else { - keyString = secretOrKey as string; - } - - // Verify signature - const data = `${encodedHeader}.${encodedPayload}`; - const hashAlgorithm = $getHashAlgorithm(header.alg); - let isValidSignature: boolean; - - if ($isRSAAlgorithm(header.alg)) { - // For RSA, handle both private and public keys - let verificationKey: crypto.KeyLike; - - // Clean up the key format - const cleanKey = keyString.replace(/\\n/g, "\n"); - - try { - // If it's a private key, extract the public key for verification - if (cleanKey.includes("PRIVATE KEY")) { - const privateKeyObj = crypto.createPrivateKey(cleanKey); - verificationKey = crypto.createPublicKey(privateKeyObj); - } else { - // It's already a public key - verificationKey = crypto.createPublicKey(cleanKey); - } - } catch (error) { - throw new CryptographyError({ - message: "Invalid JWT signature" - }); - } - - // Convert base64url signature back to buffer - const signatureBuffer = Buffer.from( - signature.replace(/-/g, "+").replace(/_/g, "/") + "==".slice(0, (4 - (signature.length % 4)) % 4), - "base64" - ); - isValidSignature = crypto.verify(hashAlgorithm, Buffer.from(data), verificationKey, signatureBuffer); - } else { - // HMAC verification - const expectedSignature = crypto - .createHmac(hashAlgorithm, keyString) - .update(data) - .digest("base64") - .replace(/\+/g, "-") - .replace(/\//g, "_") - .replace(/=/g, ""); - isValidSignature = signature === expectedSignature; - } - - if (!isValidSignature) { - throw new CryptographyError({ - message: "Invalid JWT signature" - }); - } - - // Decode payload - const payloadJson = Buffer.from(encodedPayload, "base64").toString(); - const payload = JSON.parse(payloadJson) as JWTPayload & { - aud?: string | string[]; - iss?: string; - sub?: string; - nbf?: number; - jti?: string; - }; - - const now = Math.floor(Date.now() / 1000); - const clockTolerance = options.clockTolerance || 0; - - // Check expiration - if (!options.ignoreExpiration && payload.exp && now - clockTolerance > payload.exp) { - throw new CryptographyError({ - message: "JWT token has expired" - }); - } - - // Check not before - if (!options.ignoreNotBefore && payload.nbf && now + clockTolerance < payload.nbf) { - throw new CryptographyError({ - message: "JWT not active" - }); - } - - // Check audience - if (options.audience) { - const audiences = Array.isArray(options.audience) ? options.audience : [options.audience]; - const tokenAudiences = Array.isArray(payload.aud) ? payload.aud : [payload.aud]; - const hasValidAudience = audiences.some((aud) => tokenAudiences.includes(aud)); - if (!hasValidAudience) { - throw new CryptographyError({ - message: "JWT audience invalid" - }); - } - } - - // Check issuer - if (options.issuer) { - const issuers = Array.isArray(options.issuer) ? options.issuer : [options.issuer]; - if (!payload.iss || !issuers.includes(payload.iss)) { - throw new CryptographyError({ - message: "JWT issuer invalid" - }); - } - } - - // Check subject - if (options.subject && payload.sub !== options.subject) { - throw new CryptographyError({ - message: "JWT subject invalid" - }); - } - - // Check JWT ID - if (options.jwtid && payload.jti !== options.jwtid) { - throw new CryptographyError({ - message: "JWT ID invalid" - }); - } - - // Check max age - if (options.maxAge && payload.iat) { - const maxAgeSeconds = typeof options.maxAge === "string" ? $parseTimeToSeconds(options.maxAge) : options.maxAge; - if (now - payload.iat > maxAgeSeconds) { - throw new CryptographyError({ - message: "JWT max age exceeded" - }); - } - } - - // Check algorithms - if (options.algorithms && !options.algorithms.includes(header.alg as Algorithm)) { - throw new CryptographyError({ - message: `Algorithm not allowed: ${header.alg}` - }); - } - - return payload; - }; - - const decode = (token: string, options: { complete?: boolean } = {}): JWTPayload | CompleteJWTPayload => { - const parts = token.split("."); - if (parts.length !== 3) { - throw new CryptographyError({ - message: "Invalid JWT format" - }); - } - - const [encodedHeader, encodedPayload] = parts; - - // Decode header - const headerJson = Buffer.from(encodedHeader, "base64").toString(); - const header = JSON.parse(headerJson) as Record; - - // Decode payload - const payloadJson = Buffer.from(encodedPayload, "base64").toString(); - const payload = JSON.parse(payloadJson) as Record; - - // Return complete token info or just payload - if (options.complete) { - return { - header, - payload, - signature: parts[2] - }; - } - - return payload as JWTPayload; - }; - - return { - sign, - verify, - decode - }; -}; diff --git a/backend/src/lib/crypto/cryptography/types.ts b/backend/src/lib/crypto/cryptography/types.ts index e9622575e..eb2f6dd7e 100644 --- a/backend/src/lib/crypto/cryptography/types.ts +++ b/backend/src/lib/crypto/cryptography/types.ts @@ -1,5 +1,3 @@ -import { KeyObject } from "crypto"; - import { SecretEncryptionAlgo, SecretKeyEncoding } from "@app/db/schemas"; export enum DigestType { @@ -54,39 +52,3 @@ export type TEncryptedWithRootEncryptionKey = { algorithm: SecretEncryptionAlgo; encoding: SecretKeyEncoding; }; - -export interface JWTPayload { - iat?: number; - exp?: number; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - [key: string]: any; -} - -export interface CompleteJWTPayload { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - header: any; - payload: JWTPayload; - signature: string; -} - -export type Algorithm = "HS256" | "HS384" | "HS512" | "RS256" | "RS384" | "RS512"; - -export interface JWTSignOptions { - algorithm?: Algorithm | undefined; - keyid?: string | undefined; - expiresIn?: string | number; -} - -export type JWTSecretOrKey = string | Buffer | KeyObject | { key: string | Buffer; passphrase: string }; - -export interface JWTVerifyOptions { - algorithms?: Algorithm[] | undefined; - audience?: string | string[]; - issuer?: string | string[]; - subject?: string; - ignoreExpiration?: boolean; - ignoreNotBefore?: boolean; - clockTolerance?: number; - maxAge?: string | number; - jwtid?: string; -} diff --git a/backend/src/lib/crypto/srp.ts b/backend/src/lib/crypto/srp.ts index 5c204c033..6c4dbc5aa 100644 --- a/backend/src/lib/crypto/srp.ts +++ b/backend/src/lib/crypto/srp.ts @@ -39,7 +39,7 @@ export const generateUserSrpKeys = async ( password: string, customKeys?: { publicKey: string; privateKey: string } ) => { - const pair = crypto.encryption().asymmetric().generateKeyPair(); + const pair = await crypto.encryption().asymmetric().generateKeyPair(); const privateKey = customKeys?.privateKey || pair.privateKey; const publicKey = customKeys?.publicKey || pair.publicKey; diff --git a/backend/src/lib/telemetry/instrumentation.ts b/backend/src/lib/telemetry/instrumentation.ts index 1c9b29cc7..3bc6ea7fb 100644 --- a/backend/src/lib/telemetry/instrumentation.ts +++ b/backend/src/lib/telemetry/instrumentation.ts @@ -6,9 +6,10 @@ import { HttpInstrumentation } from "@opentelemetry/instrumentation-http"; import { Resource } from "@opentelemetry/resources"; import { AggregationTemporality, MeterProvider, PeriodicExportingMetricReader } from "@opentelemetry/sdk-metrics"; import { ATTR_SERVICE_NAME, ATTR_SERVICE_VERSION } from "@opentelemetry/semantic-conventions"; +import tracer from "dd-trace"; import dotenv from "dotenv"; -import { initEnvConfig } from "../config/env"; +import { getTelemetryConfig } from "../config/env"; dotenv.config(); @@ -73,31 +74,17 @@ const initTelemetryInstrumentation = ({ }); }; -const setupTelemetry = async () => { - const envCfg = await initEnvConfig(); +const setupTelemetry = () => { + const appCfg = getTelemetryConfig(); - if (envCfg.OTEL_TELEMETRY_COLLECTION_ENABLED) { + if (appCfg.useOtel) { console.log("Initializing telemetry instrumentation"); - initTelemetryInstrumentation({ - otlpURL: envCfg.OTEL_EXPORT_OTLP_ENDPOINT, - otlpUser: envCfg.OTEL_COLLECTOR_BASIC_AUTH_USERNAME, - otlpPassword: envCfg.OTEL_COLLECTOR_BASIC_AUTH_PASSWORD, - otlpPushInterval: envCfg.OTEL_OTLP_PUSH_INTERVAL, - exportType: envCfg.OTEL_EXPORT_TYPE - }); + initTelemetryInstrumentation({ ...appCfg.OTEL }); } - if (envCfg.SHOULD_USE_DATADOG_TRACER) { - const tracer = await import("dd-trace"); - + if (appCfg.useDataDogTracer) { console.log("Initializing Datadog tracer"); - tracer.init({ - profiling: envCfg.DATADOG_PROFILING_ENABLED, - version: envCfg.INFISICAL_PLATFORM_VERSION, - env: envCfg.DATADOG_ENV, - service: envCfg.DATADOG_SERVICE, - hostname: envCfg.DATADOG_HOSTNAME - }); + tracer.init({ ...appCfg.TRACER }); } }; diff --git a/backend/src/main.ts b/backend/src/main.ts index 1f62f5a76..8af47eb0b 100644 --- a/backend/src/main.ts +++ b/backend/src/main.ts @@ -1,3 +1,5 @@ +// Note(Daniel): Do not rename this import, as it is strictly removed from FIPS standalone builds to avoid FIPS mode issues. +// If you rename the import, update the Dockerfile.fips.standalone-infisical file as well. import "./lib/telemetry/instrumentation"; import dotenv from "dotenv"; diff --git a/backend/src/server/plugins/auth/inject-identity.ts b/backend/src/server/plugins/auth/inject-identity.ts index 98982e969..7b4f5d90c 100644 --- a/backend/src/server/plugins/auth/inject-identity.ts +++ b/backend/src/server/plugins/auth/inject-identity.ts @@ -1,12 +1,12 @@ import { requestContext } from "@fastify/request-context"; import { FastifyRequest } from "fastify"; import fp from "fastify-plugin"; +import type { JwtPayload } from "jsonwebtoken"; import { TServiceTokens, TUsers } from "@app/db/schemas"; import { TScimTokenJwtPayload } from "@app/ee/services/scim/scim-types"; import { getConfig } from "@app/lib/config/env"; import { crypto } from "@app/lib/crypto"; -import { JWTPayload } from "@app/lib/crypto/cryptography/types"; import { BadRequestError } from "@app/lib/errors"; import { ActorType, AuthMethod, AuthMode, AuthModeJwtTokenPayload, AuthTokenType } from "@app/services/auth/auth-type"; import { TIdentityAccessTokenJwtPayload } from "@app/services/identity-access-token/identity-access-token-types"; @@ -73,7 +73,7 @@ const extractAuth = async (req: FastifyRequest, jwtSecret: string) => { } as const; } - const decodedToken = crypto.jwt().verify(authTokenValue, jwtSecret) as JWTPayload; + const decodedToken = crypto.jwt().verify(authTokenValue, jwtSecret) as JwtPayload; switch (decodedToken.authTokenType) { case AuthTokenType.ACCESS_TOKEN: diff --git a/backend/src/services/identity-jwt-auth/identity-jwt-auth-service.ts b/backend/src/services/identity-jwt-auth/identity-jwt-auth-service.ts index 546980f41..35063ae70 100644 --- a/backend/src/services/identity-jwt-auth/identity-jwt-auth-service.ts +++ b/backend/src/services/identity-jwt-auth/identity-jwt-auth-service.ts @@ -13,7 +13,6 @@ import { import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types"; import { getConfig } from "@app/lib/config/env"; import { crypto } from "@app/lib/crypto"; -import { CompleteJWTPayload } from "@app/lib/crypto/cryptography/types"; import { BadRequestError, ForbiddenRequestError, @@ -81,7 +80,7 @@ export const identityJwtAuthServiceFactory = ({ orgId: identityMembershipOrg.orgId }); - const decodedToken = crypto.jwt().decode(jwtValue, { complete: true }) as CompleteJWTPayload; + const decodedToken = crypto.jwt().decode(jwtValue, { complete: true }); if (!decodedToken) { throw new UnauthorizedError({ message: "Invalid JWT" diff --git a/backend/src/services/identity-oidc-auth/identity-oidc-auth-service.ts b/backend/src/services/identity-oidc-auth/identity-oidc-auth-service.ts index 997ed6dfd..08d53a344 100644 --- a/backend/src/services/identity-oidc-auth/identity-oidc-auth-service.ts +++ b/backend/src/services/identity-oidc-auth/identity-oidc-auth-service.ts @@ -14,7 +14,6 @@ import { import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types"; import { getConfig } from "@app/lib/config/env"; import { crypto } from "@app/lib/crypto"; -import { CompleteJWTPayload } from "@app/lib/crypto/cryptography/types"; import { BadRequestError, ForbiddenRequestError, @@ -95,7 +94,7 @@ export const identityOidcAuthServiceFactory = ({ ); const jwksUri = discoveryDoc.jwks_uri; - const decodedToken = crypto.jwt().decode(oidcJwt, { complete: true }) as CompleteJWTPayload; + const decodedToken = crypto.jwt().decode(oidcJwt, { complete: true }); if (!decodedToken) { throw new UnauthorizedError({ message: "Invalid JWT" diff --git a/backend/src/services/org/org-service.ts b/backend/src/services/org/org-service.ts index eb712843a..94bf4aa4e 100644 --- a/backend/src/services/org/org-service.ts +++ b/backend/src/services/org/org-service.ts @@ -497,7 +497,7 @@ export const orgServiceFactory = ({ orgName: string; userEmail?: string | null; }) => { - const { privateKey, publicKey } = crypto.encryption().asymmetric().generateKeyPair(); + const { privateKey, publicKey } = await crypto.encryption().asymmetric().generateKeyPair(); const key = crypto.randomBytes(32).toString("base64"); const { ciphertext: encryptedPrivateKey, diff --git a/backend/src/services/project-bot/project-bot-fns.ts b/backend/src/services/project-bot/project-bot-fns.ts index 42db24d5b..daf6fa33e 100644 --- a/backend/src/services/project-bot/project-bot-fns.ts +++ b/backend/src/services/project-bot/project-bot-fns.ts @@ -59,7 +59,7 @@ export const getBotKeyFnFactory = ( publicKey: projectV1Keys.senderPublicKey, privateKey: userPrivateKey }); - const botKey = crypto.encryption().asymmetric().generateKeyPair(); + const botKey = await crypto.encryption().asymmetric().generateKeyPair(); const { iv, tag, ciphertext, encoding, algorithm } = crypto .encryption() .encryptWithRootEncryptionKey(botKey.privateKey); diff --git a/backend/src/services/project-bot/project-bot-service.ts b/backend/src/services/project-bot/project-bot-service.ts index 12e4ecbdc..ab2eb7b52 100644 --- a/backend/src/services/project-bot/project-bot-service.ts +++ b/backend/src/services/project-bot/project-bot-service.ts @@ -54,7 +54,7 @@ export const projectBotServiceFactory = ({ if (doc) return doc; const keys = - privateKey && publicKey ? { privateKey, publicKey } : crypto.encryption().asymmetric().generateKeyPair(); + privateKey && publicKey ? { privateKey, publicKey } : await crypto.encryption().asymmetric().generateKeyPair(); const { iv, tag, ciphertext, encoding, algorithm } = crypto .encryption() From 41787908dd80ddbe5203a3a7e6bb76b65818ab5f Mon Sep 17 00:00:00 2001 From: Daniel Hougaard Date: Wed, 9 Jul 2025 23:36:46 +0400 Subject: [PATCH 17/27] Update cache.ts --- backend/src/lib/crypto/cache.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/lib/crypto/cache.ts b/backend/src/lib/crypto/cache.ts index 6a050c8a7..7e341a59e 100644 --- a/backend/src/lib/crypto/cache.ts +++ b/backend/src/lib/crypto/cache.ts @@ -1,7 +1,7 @@ import { crypto } from "@app/lib/crypto/cryptography"; export const generateCacheKeyFromData = (data: unknown) => - crypto.rawCrypto. + crypto.rawCrypto .createHash("sha256") .update(JSON.stringify(data)) .digest("base64") From 6ce2438827c417f2a65a02e02ed3f9524b67ba93 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard Date: Wed, 9 Jul 2025 23:40:06 +0400 Subject: [PATCH 18/27] Update identity-access-token-service.ts --- .../identity-access-token/identity-access-token-service.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/backend/src/services/identity-access-token/identity-access-token-service.ts b/backend/src/services/identity-access-token/identity-access-token-service.ts index e9863f666..71f7ec365 100644 --- a/backend/src/services/identity-access-token/identity-access-token-service.ts +++ b/backend/src/services/identity-access-token/identity-access-token-service.ts @@ -1,7 +1,6 @@ import { IdentityAuthMethod, TableName, TIdentityAccessTokens } from "@app/db/schemas"; import { getConfig } from "@app/lib/config/env"; import { crypto } from "@app/lib/crypto"; -import { JWTPayload } from "@app/lib/crypto/cryptography/types"; import { BadRequestError, UnauthorizedError } from "@app/lib/errors"; import { checkIPAgainstBlocklist, TIp } from "@app/lib/ip"; @@ -162,7 +161,7 @@ export const identityAccessTokenServiceFactory = ({ const revokeAccessToken = async (accessToken: string) => { const appCfg = getConfig(); - const decodedToken = crypto.jwt().verify(accessToken, appCfg.AUTH_SECRET) as JWTPayload & { + const decodedToken = crypto.jwt().verify(accessToken, appCfg.AUTH_SECRET) as TIdentityAccessTokenJwtPayload & { identityAccessTokenId: string; }; if (decodedToken.authTokenType !== AuthTokenType.IDENTITY_ACCESS_TOKEN) { From 3d84de350af1aa667e07693137e53c1c0dd0edad Mon Sep 17 00:00:00 2001 From: Daniel Hougaard Date: Fri, 11 Jul 2025 13:08:09 +0400 Subject: [PATCH 19/27] requested changes --- frontend/src/hooks/api/cmeks/mutations.tsx | 1 - frontend/src/pages/admin/SignUpPage/SignUpPage.tsx | 9 --------- .../components/UserInfoSSOStep/UserInfoSSOStep.tsx | 1 + 3 files changed, 1 insertion(+), 10 deletions(-) diff --git a/frontend/src/hooks/api/cmeks/mutations.tsx b/frontend/src/hooks/api/cmeks/mutations.tsx index 4b6248d69..a32409dff 100644 --- a/frontend/src/hooks/api/cmeks/mutations.tsx +++ b/frontend/src/hooks/api/cmeks/mutations.tsx @@ -66,7 +66,6 @@ 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/pages/admin/SignUpPage/SignUpPage.tsx b/frontend/src/pages/admin/SignUpPage/SignUpPage.tsx index 537da25d0..2d6aeb9d6 100644 --- a/frontend/src/pages/admin/SignUpPage/SignUpPage.tsx +++ b/frontend/src/pages/admin/SignUpPage/SignUpPage.tsx @@ -49,15 +49,6 @@ export const SignUpPage = () => { // avoid multi submission if (isSubmitting) return; try { - console.log( - "Creating admin user...", - JSON.stringify({ - email, - password, - fipsEnabled: config.fipsEnabled - }) - ); - const { privateKey, ...userPass } = await generateUserPassKey( email, password, diff --git a/frontend/src/pages/auth/SignUpSsoPage/components/UserInfoSSOStep/UserInfoSSOStep.tsx b/frontend/src/pages/auth/SignUpSsoPage/components/UserInfoSSOStep/UserInfoSSOStep.tsx index 99a48f92d..85343e79e 100644 --- a/frontend/src/pages/auth/SignUpSsoPage/components/UserInfoSSOStep/UserInfoSSOStep.tsx +++ b/frontend/src/pages/auth/SignUpSsoPage/components/UserInfoSSOStep/UserInfoSSOStep.tsx @@ -95,6 +95,7 @@ export const UserInfoSSOStep = ({ if (!errorCheck) { // Generate a random pair of a public and a private key const { publicKey, privateKey } = await generateKeyPair(config.fipsEnabled); + localStorage.setItem("PRIVATE_KEY", privateKey); client.init( { From 4ac6a65cd5c6fdf4bd0115b850794c658f994aa7 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard Date: Fri, 11 Jul 2025 20:22:05 +0400 Subject: [PATCH 20/27] Update env.ts --- backend/src/lib/config/env.ts | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/backend/src/lib/config/env.ts b/backend/src/lib/config/env.ts index 372f190f6..986963e47 100644 --- a/backend/src/lib/config/env.ts +++ b/backend/src/lib/config/env.ts @@ -360,6 +360,13 @@ export const initEnvConfig = async (superAdminDAL?: TSuperAdminDALFactory, logge process.exit(-1); } + const config = Object.freeze(parsedEnv.data); + envCfg = config; + + if (!originalEnvConfig) { + originalEnvConfig = config; + } + if (superAdminDAL) { const fipsEnabled = await crypto.initialize(superAdminDAL); @@ -375,15 +382,6 @@ export const initEnvConfig = async (superAdminDAL?: TSuperAdminDALFactory, logge } } - if (!envCfg) { - const config = Object.freeze(parsedEnv.data); - envCfg = config; - - if (!originalEnvConfig) { - originalEnvConfig = config; - } - } - return envCfg; }; From 563ac32bf12872191a0999920dd24fe4ea5c465d Mon Sep 17 00:00:00 2001 From: Daniel Hougaard Date: Fri, 11 Jul 2025 22:09:54 +0400 Subject: [PATCH 21/27] chore: cleanup --- .infisicalignore | 1 + backend/src/db/migrations/utils/env-config.ts | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.infisicalignore b/.infisicalignore index 06a8db5c2..2ff4f5006 100644 --- a/.infisicalignore +++ b/.infisicalignore @@ -48,3 +48,4 @@ backend/src/services/smtp/smtp-service.ts:generic-api-key:79 frontend/src/components/secret-syncs/forms/SecretSyncDestinationFields/CloudflarePagesSyncFields.tsx:cloudflare-api-key:7 docs/integrations/app-connections/zabbix.mdx:generic-api-key:91 docs/integrations/app-connections/bitbucket.mdx:generic-api-key:123 +docs/integrations/app-connections/railway.mdx:generic-api-key:156 diff --git a/backend/src/db/migrations/utils/env-config.ts b/backend/src/db/migrations/utils/env-config.ts index 14a6a2023..7c872cc71 100644 --- a/backend/src/db/migrations/utils/env-config.ts +++ b/backend/src/db/migrations/utils/env-config.ts @@ -1,7 +1,7 @@ import { z } from "zod"; -import { zpStr } from "@app/lib/zod"; import { crypto } from "@app/lib/crypto/cryptography"; +import { zpStr } from "@app/lib/zod"; import { TSuperAdminDALFactory } from "@app/services/super-admin/super-admin-dal"; const envSchema = z From 8d8f690b6317fc6198909bfcc8add32bbb134e47 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard Date: Sun, 13 Jul 2025 18:52:53 +0400 Subject: [PATCH 22/27] requested changes --- backend/package-lock.json | 2 +- backend/package.json | 2 +- backend/src/db/migrations/utils/env-config.ts | 5 +++++ backend/src/lib/crypto/cryptography/asymmetric-fips.ts | 2 +- 4 files changed, 8 insertions(+), 3 deletions(-) diff --git a/backend/package-lock.json b/backend/package-lock.json index d3533a3b4..460721966 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -69,7 +69,7 @@ "cassandra-driver": "^4.7.2", "connect-redis": "^7.1.1", "cron": "^3.1.7", - "crypto-js": "^4.2.0", + "crypto-js": "4.2.0", "dd-trace": "^5.40.0", "dotenv": "^16.4.1", "fastify": "^4.28.1", diff --git a/backend/package.json b/backend/package.json index bd7eb6b12..2ba9aca52 100644 --- a/backend/package.json +++ b/backend/package.json @@ -190,7 +190,7 @@ "cassandra-driver": "^4.7.2", "connect-redis": "^7.1.1", "cron": "^3.1.7", - "crypto-js": "^4.2.0", + "crypto-js": "4.2.0", "dd-trace": "^5.40.0", "dotenv": "^16.4.1", "fastify": "^4.28.1", diff --git a/backend/src/db/migrations/utils/env-config.ts b/backend/src/db/migrations/utils/env-config.ts index 7c872cc71..debaea03f 100644 --- a/backend/src/db/migrations/utils/env-config.ts +++ b/backend/src/db/migrations/utils/env-config.ts @@ -55,6 +55,11 @@ export const getMigrationEnvConfig = async (superAdminDAL: TSuperAdminDALFactory const fipsEnabled = await crypto.initialize(superAdminDAL); + // Fix for 128-bit entropy encryption key expansion issue: + // In FIPS it is not ideal to expand a 128-bit key into 256-bit. We solved this issue in the past by creating the ROOT_ENCRYPTION_KEY. + // If FIPS mode is enabled, we set the value of ROOT_ENCRYPTION_KEY to the value of ENCRYPTION_KEY. + // ROOT_ENCRYPTION_KEY is expected to be a 256-bit base64-encoded key, unlike the 32-byte key of ENCRYPTION_KEY. + // When ROOT_ENCRYPTION_KEY is set, our cryptography will always use a 256-bit entropy encryption key. So for the sake of FIPS we should just roll over the value of ENCRYPTION_KEY to ROOT_ENCRYPTION_KEY. if (fipsEnabled) { const newEnvCfg = { ...envCfg, diff --git a/backend/src/lib/crypto/cryptography/asymmetric-fips.ts b/backend/src/lib/crypto/cryptography/asymmetric-fips.ts index da49f99fc..ad076b3c3 100644 --- a/backend/src/lib/crypto/cryptography/asymmetric-fips.ts +++ b/backend/src/lib/crypto/cryptography/asymmetric-fips.ts @@ -1,4 +1,4 @@ -import crypto, { KeyObject } from "crypto"; +import crypto, { KeyObject } from "node:crypto"; import { SecretEncryptionAlgo } from "@app/db/schemas"; import { CryptographyError } from "@app/lib/errors"; From de81c6f0c61fce4754d2fa003655925ac63338fb Mon Sep 17 00:00:00 2001 From: Daniel Hougaard Date: Sun, 13 Jul 2025 21:28:27 +0400 Subject: [PATCH 23/27] Update crypto.ts --- backend/src/lib/crypto/cryptography/crypto.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/backend/src/lib/crypto/cryptography/crypto.ts b/backend/src/lib/crypto/cryptography/crypto.ts index df525e708..05f17f750 100644 --- a/backend/src/lib/crypto/cryptography/crypto.ts +++ b/backend/src/lib/crypto/cryptography/crypto.ts @@ -1,5 +1,6 @@ // 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"; From dad5153f6142b4fd4db4e23107d67be6e0087fa5 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard Date: Sun, 13 Jul 2025 21:44:16 +0400 Subject: [PATCH 24/27] Update 20250711005900_github-app-connection-to-environments.ts --- .../20250711005900_github-app-connection-to-environments.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/backend/src/db/migrations/20250711005900_github-app-connection-to-environments.ts b/backend/src/db/migrations/20250711005900_github-app-connection-to-environments.ts index 14e9ddd65..548d6207a 100644 --- a/backend/src/db/migrations/20250711005900_github-app-connection-to-environments.ts +++ b/backend/src/db/migrations/20250711005900_github-app-connection-to-environments.ts @@ -2,6 +2,7 @@ import { Knex } from "knex"; import { inMemoryKeyStore } from "@app/keystore/memory"; import { selectAllTableCols } from "@app/lib/knex"; +import { superAdminDALFactory } from "@app/services/super-admin/super-admin-dal"; import { TableName } from "../schemas"; import { getMigrationEnvConfig } from "./utils/env-config"; @@ -12,7 +13,8 @@ export async function up(knex: Knex) { .select(selectAllTableCols(TableName.SuperAdmin)) .whereNotNull(`${TableName.SuperAdmin}.encryptedGitHubAppConnectionClientId`); - const envConfig = getMigrationEnvConfig(); + const superAdminDAL = superAdminDALFactory(knex); + const envConfig = await getMigrationEnvConfig(superAdminDAL); const keyStore = inMemoryKeyStore(); const { kmsService } = await getMigrationEncryptionServices({ envConfig, keyStore, db: knex }); From 260ef05644420946e7a3c72d93afd0a57adfa0ed Mon Sep 17 00:00:00 2001 From: Daniel Hougaard Date: Mon, 14 Jul 2025 21:48:13 +0400 Subject: [PATCH 25/27] feat(fips): requested changes (function renaming) --- .../e2e-test/routes/v2/service-token.spec.ts | 4 +- .../20250210101840_webhook-to-kms.ts | 30 ++- ...250210101841_dynamic-secret-root-to-kms.ts | 31 +-- .../20250210101841_secret-rotation-to-kms.ts | 31 +-- .../20250210101842_identity-k8-auth-to-kms.ts | 19 +- ...0250210101842_identity-oidc-auth-to-kms.ts | 17 +- .../20250210101845_directory-config-to-kms.ts | 63 ++--- backend/src/db/seed-data.ts | 73 +++--- backend/src/db/seeds/3-project.ts | 2 +- .../audit-log-stream-service.ts | 19 +- .../ee/services/audit-log/audit-log-queue.ts | 30 ++- backend/src/ee/services/group/group-fns.ts | 15 +- .../secret-approval-request-service.ts | 4 +- .../secret-replication-service.ts | 4 +- .../secret-rotation-queue.ts | 19 +- .../secret-rotation-service.ts | 2 +- .../secret-snapshot-service.ts | 6 +- backend/src/lib/crypto/cryptography/crypto.ts | 218 +++++++++--------- backend/src/lib/crypto/encryption.ts | 6 +- backend/src/lib/crypto/srp.ts | 62 +++-- .../src/services/auth/auth-login-service.ts | 5 +- .../services/auth/auth-password-service.ts | 17 +- .../src/services/auth/auth-signup-service.ts | 34 +-- .../external-migration-queue.ts | 2 +- .../external-migration-service.ts | 2 +- .../group-project/group-project-service.ts | 15 +- .../integration-auth-service.ts | 34 +-- .../integration-delete-secret.ts | 2 +- .../services/org-admin/org-admin-service.ts | 15 +- backend/src/services/org/org-service.ts | 21 +- .../services/project-bot/project-bot-fns.ts | 31 ++- .../project-bot/project-bot-service.ts | 1 + backend/src/services/project/project-queue.ts | 108 +++++---- .../src/services/project/project-service.ts | 3 +- backend/src/services/secret/secret-fns.ts | 72 +++--- backend/src/services/secret/secret-queue.ts | 50 ++-- backend/src/services/secret/secret-service.ts | 163 +++++++------ .../super-admin/super-admin-service.ts | 4 +- backend/src/services/user/user-service.ts | 15 +- 39 files changed, 718 insertions(+), 531 deletions(-) diff --git a/backend/e2e-test/routes/v2/service-token.spec.ts b/backend/e2e-test/routes/v2/service-token.spec.ts index 72a9c4433..025d9796f 100644 --- a/backend/e2e-test/routes/v2/service-token.spec.ts +++ b/backend/e2e-test/routes/v2/service-token.spec.ts @@ -37,7 +37,7 @@ const createServiceToken = async ( const randomBytes = crypto.randomBytes(16).toString("hex"); - const { ciphertext, iv, tag } = crypto.encryption().encryptSymmetric({ + const { ciphertext, iv, tag } = crypto.encryption().symmetric().encrypt({ plaintext: projectKey, key: randomBytes, keySize: SymmetricKeySize.Bits128 @@ -165,7 +165,7 @@ describe("Service token secret ops", async () => { const serviceTokenInfo = serviceTokenInfoRes.json(); const serviceTokenParts = serviceToken.split("."); - projectKey = crypto.encryption().decryptSymmetric({ + projectKey = crypto.encryption().symmetric().decrypt({ key: serviceTokenParts[3], tag: serviceTokenInfo.tag, ciphertext: serviceTokenInfo.encryptedKey, diff --git a/backend/src/db/migrations/20250210101840_webhook-to-kms.ts b/backend/src/db/migrations/20250210101840_webhook-to-kms.ts index 63f1874e3..09a346abb 100644 --- a/backend/src/db/migrations/20250210101840_webhook-to-kms.ts +++ b/backend/src/db/migrations/20250210101840_webhook-to-kms.ts @@ -69,12 +69,15 @@ export async function up(knex: Knex): Promise { let encryptedSecretKey = null; if (el.encryptedSecretKey && el.iv && el.tag && el.keyEncoding) { - const decyptedSecretKey = crypto.encryption().decryptWithRootEncryptionKey({ - keyEncoding: el.keyEncoding as SecretKeyEncoding, - iv: el.iv, - tag: el.tag, - ciphertext: el.encryptedSecretKey - }); + const decyptedSecretKey = crypto + .encryption() + .symmetric() + .decryptWithRootEncryptionKey({ + keyEncoding: el.keyEncoding as SecretKeyEncoding, + iv: el.iv, + tag: el.tag, + ciphertext: el.encryptedSecretKey + }); encryptedSecretKey = projectKmsService.encryptor({ plainText: Buffer.from(decyptedSecretKey, "utf8") }).cipherTextBlob; @@ -82,12 +85,15 @@ export async function up(knex: Knex): Promise { const decryptedUrl = el.urlIV && el.urlTag && el.urlCipherText && el.keyEncoding - ? crypto.encryption().decryptWithRootEncryptionKey({ - keyEncoding: el.keyEncoding as SecretKeyEncoding, - iv: el.urlIV, - tag: el.urlTag, - ciphertext: el.urlCipherText - }) + ? crypto + .encryption() + .symmetric() + .decryptWithRootEncryptionKey({ + keyEncoding: el.keyEncoding as SecretKeyEncoding, + iv: el.urlIV, + tag: el.urlTag, + ciphertext: el.urlCipherText + }) : null; const encryptedUrl = projectKmsService.encryptor({ 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 ac0395111..94e30a7b8 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 @@ -63,20 +63,23 @@ 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 - ? 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, - // 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.inputIV, - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore This will be removed in next cycle so ignore the ts missing error - tag: el.inputTag, - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore This will be removed in next cycle so ignore the ts missing error - ciphertext: el.inputCiphertext - }) + ? crypto + .encryption() + .symmetric() + .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, + // 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.inputIV, + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore This will be removed in next cycle so ignore the ts missing error + tag: el.inputTag, + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore This will be removed in next cycle so ignore the ts missing error + ciphertext: el.inputCiphertext + }) : ""; const encryptedInput = projectKmsService.encryptor({ 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 b8e23e757..bbda48dac 100644 --- a/backend/src/db/migrations/20250210101841_secret-rotation-to-kms.ts +++ b/backend/src/db/migrations/20250210101841_secret-rotation-to-kms.ts @@ -56,20 +56,23 @@ 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 - ? 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, - // 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.encryptedDataIV, - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore This will be removed in next cycle so ignore the ts missing error - tag: el.encryptedDataTag, - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore This will be removed in next cycle so ignore the ts missing error - ciphertext: el.encryptedData - }) + ? crypto + .encryption() + .symmetric() + .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, + // 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.encryptedDataIV, + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore This will be removed in next cycle so ignore the ts missing error + tag: el.encryptedDataTag, + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore This will be removed in next cycle so ignore the ts missing error + ciphertext: el.encryptedData + }) : ""; const encryptedRotationData = projectKmsService.encryptor({ 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 23a8c0e9b..a24bfdf0c 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 @@ -102,18 +102,21 @@ const reencryptIdentityK8sAuth = async (knex: Knex) => { orgEncryptionRingBuffer.push(orgId, orgKmsService); } - const key = crypto.encryption().decryptWithRootEncryptionKey({ - ciphertext: encryptedSymmetricKey, - iv: symmetricKeyIV, - tag: symmetricKeyTag, - keyEncoding: symmetricKeyKeyEncoding as SecretKeyEncoding - }); + const key = crypto + .encryption() + .symmetric() + .decryptWithRootEncryptionKey({ + ciphertext: encryptedSymmetricKey, + iv: symmetricKeyIV, + tag: symmetricKeyTag, + keyEncoding: symmetricKeyKeyEncoding as SecretKeyEncoding + }); const decryptedTokenReviewerJwt = // 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 - ? crypto.encryption().decryptSymmetric({ + ? crypto.encryption().symmetric().decrypt({ key, keySize: SymmetricKeySize.Bits256, // eslint-disable-next-line @typescript-eslint/ban-ts-comment @@ -132,7 +135,7 @@ 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 - ? crypto.encryption().decryptSymmetric({ + ? crypto.encryption().symmetric().decrypt({ key, keySize: SymmetricKeySize.Bits256, // eslint-disable-next-line @typescript-eslint/ban-ts-comment 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 25dfb69f1..25db615fa 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 @@ -75,18 +75,21 @@ const reencryptIdentityOidcAuth = async (knex: Knex) => { orgEncryptionRingBuffer.push(orgId, orgKmsService); } - const key = crypto.encryption().decryptWithRootEncryptionKey({ - ciphertext: encryptedSymmetricKey, - iv: symmetricKeyIV, - tag: symmetricKeyTag, - keyEncoding: symmetricKeyKeyEncoding as SecretKeyEncoding - }); + const key = crypto + .encryption() + .symmetric() + .decryptWithRootEncryptionKey({ + ciphertext: encryptedSymmetricKey, + iv: symmetricKeyIV, + tag: symmetricKeyTag, + keyEncoding: symmetricKeyKeyEncoding as SecretKeyEncoding + }); const decryptedCertificate = // 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 - ? crypto.encryption().decryptSymmetric({ + ? crypto.encryption().symmetric().decrypt({ key, keySize: SymmetricKeySize.Bits256, // eslint-disable-next-line @typescript-eslint/ban-ts-comment 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 cd40318a4..783693da6 100644 --- a/backend/src/db/migrations/20250210101845_directory-config-to-kms.ts +++ b/backend/src/db/migrations/20250210101845_directory-config-to-kms.ts @@ -5,12 +5,12 @@ 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"; +import { superAdminDALFactory } from "@app/services/super-admin/super-admin-dal"; import { SecretKeyEncoding, TableName } from "../schemas"; import { getMigrationEnvConfig } from "./utils/env-config"; import { createCircularCache } from "./utils/ring-buffer"; import { getMigrationEncryptionServices } from "./utils/services"; -import { superAdminDALFactory } from "@app/services/super-admin/super-admin-dal"; const BATCH_SIZE = 500; const reencryptSamlConfig = async (knex: Knex) => { @@ -61,18 +61,21 @@ const reencryptSamlConfig = async (knex: Knex) => { orgEncryptionRingBuffer.push(el.orgId, orgKmsService); } - const key = crypto.encryption().decryptWithRootEncryptionKey({ - ciphertext: encryptedSymmetricKey, - iv: symmetricKeyIV, - tag: symmetricKeyTag, - keyEncoding: symmetricKeyKeyEncoding as SecretKeyEncoding - }); + const key = crypto + .encryption() + .symmetric() + .decryptWithRootEncryptionKey({ + ciphertext: encryptedSymmetricKey, + iv: symmetricKeyIV, + tag: symmetricKeyTag, + keyEncoding: symmetricKeyKeyEncoding as SecretKeyEncoding + }); const decryptedEntryPoint = // 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 - ? crypto.encryption().decryptSymmetric({ + ? crypto.encryption().symmetric().decrypt({ key, keySize: SymmetricKeySize.Bits256, // eslint-disable-next-line @typescript-eslint/ban-ts-comment @@ -91,7 +94,7 @@ 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 - ? crypto.encryption().decryptSymmetric({ + ? crypto.encryption().symmetric().decrypt({ key, keySize: SymmetricKeySize.Bits256, // eslint-disable-next-line @typescript-eslint/ban-ts-comment @@ -110,7 +113,7 @@ 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 - ? crypto.encryption().decryptSymmetric({ + ? crypto.encryption().symmetric().decrypt({ key, keySize: SymmetricKeySize.Bits256, // eslint-disable-next-line @typescript-eslint/ban-ts-comment @@ -224,18 +227,21 @@ const reencryptLdapConfig = async (knex: Knex) => { orgEncryptionRingBuffer.push(el.orgId, orgKmsService); } - const key = crypto.encryption().decryptWithRootEncryptionKey({ - ciphertext: encryptedSymmetricKey, - iv: symmetricKeyIV, - tag: symmetricKeyTag, - keyEncoding: symmetricKeyKeyEncoding as SecretKeyEncoding - }); + const key = crypto + .encryption() + .symmetric() + .decryptWithRootEncryptionKey({ + ciphertext: encryptedSymmetricKey, + iv: symmetricKeyIV, + tag: symmetricKeyTag, + keyEncoding: symmetricKeyKeyEncoding as SecretKeyEncoding + }); const decryptedBindDN = // 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 - ? crypto.encryption().decryptSymmetric({ + ? crypto.encryption().symmetric().decrypt({ key, keySize: SymmetricKeySize.Bits256, // eslint-disable-next-line @typescript-eslint/ban-ts-comment @@ -254,7 +260,7 @@ 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 - ? crypto.encryption().decryptSymmetric({ + ? crypto.encryption().symmetric().decrypt({ key, keySize: SymmetricKeySize.Bits256, // eslint-disable-next-line @typescript-eslint/ban-ts-comment @@ -273,7 +279,7 @@ 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 - ? crypto.encryption().decryptSymmetric({ + ? crypto.encryption().symmetric().decrypt({ key, keySize: SymmetricKeySize.Bits256, // eslint-disable-next-line @typescript-eslint/ban-ts-comment @@ -381,18 +387,21 @@ const reencryptOidcConfig = async (knex: Knex) => { orgEncryptionRingBuffer.push(el.orgId, orgKmsService); } - const key = crypto.encryption().decryptWithRootEncryptionKey({ - ciphertext: encryptedSymmetricKey, - iv: symmetricKeyIV, - tag: symmetricKeyTag, - keyEncoding: symmetricKeyKeyEncoding as SecretKeyEncoding - }); + const key = crypto + .encryption() + .symmetric() + .decryptWithRootEncryptionKey({ + ciphertext: encryptedSymmetricKey, + iv: symmetricKeyIV, + tag: symmetricKeyTag, + keyEncoding: symmetricKeyKeyEncoding as SecretKeyEncoding + }); const decryptedClientId = // 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 - ? crypto.encryption().decryptSymmetric({ + ? crypto.encryption().symmetric().decrypt({ key, keySize: SymmetricKeySize.Bits256, // eslint-disable-next-line @typescript-eslint/ban-ts-comment @@ -411,7 +420,7 @@ 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 - ? crypto.encryption().decryptSymmetric({ + ? crypto.encryption().symmetric().decrypt({ key, keySize: SymmetricKeySize.Bits256, // eslint-disable-next-line @typescript-eslint/ban-ts-comment diff --git a/backend/src/db/seed-data.ts b/backend/src/db/seed-data.ts index d923e45f1..2aee85fb1 100644 --- a/backend/src/db/seed-data.ts +++ b/backend/src/db/seed-data.ts @@ -84,7 +84,7 @@ export const generateUserSrpKeys = async (password: string) => { ciphertext: encryptedPrivateKey, iv: encryptedPrivateKeyIV, tag: encryptedPrivateKeyTag - } = crypto.encryption().encryptSymmetric({ + } = crypto.encryption().symmetric().encrypt({ plaintext: privateKey, key, keySize: SymmetricKeySize.Bits128 @@ -98,7 +98,8 @@ export const generateUserSrpKeys = async (password: string) => { tag: protectedKeyTag } = crypto .encryption() - .encryptSymmetric({ plaintext: key.toString("hex"), key: derivedKey, keySize: SymmetricKeySize.Bits128 }); + .symmetric() + .encrypt({ plaintext: key.toString("hex"), key: derivedKey, keySize: SymmetricKeySize.Bits128 }); return { protectedKey, @@ -125,21 +126,27 @@ export const getUserPrivateKey = async (password: string, user: TUserEncryptionK }); if (!derivedKey) throw new Error("Failed to derive key from password"); - const key = crypto.encryption().decryptSymmetric({ - ciphertext: user.protectedKey as string, - iv: user.protectedKeyIV as string, - tag: user.protectedKeyTag as string, - key: derivedKey, - keySize: SymmetricKeySize.Bits128 - }); + const key = crypto + .encryption() + .symmetric() + .decrypt({ + ciphertext: user.protectedKey as string, + iv: user.protectedKeyIV as string, + tag: user.protectedKeyTag as string, + key: derivedKey, + keySize: SymmetricKeySize.Bits128 + }); - const privateKey = crypto.encryption().decryptSymmetric({ - ciphertext: user.encryptedPrivateKey, - iv: user.iv, - tag: user.tag, - key: Buffer.from(key, "hex"), - keySize: SymmetricKeySize.Bits128 - }); + const privateKey = crypto + .encryption() + .symmetric() + .decrypt({ + ciphertext: user.encryptedPrivateKey, + iv: user.iv, + tag: user.tag, + key: Buffer.from(key, "hex"), + keySize: SymmetricKeySize.Bits128 + }); return privateKey; }; @@ -164,7 +171,7 @@ export const encryptSecret = (encKey: string, key: string, value?: string, comme ciphertext: secretKeyCiphertext, iv: secretKeyIV, tag: secretKeyTag - } = crypto.encryption().encryptSymmetric({ + } = crypto.encryption().symmetric().encrypt({ plaintext: key, key: encKey, keySize: SymmetricKeySize.Bits128 @@ -175,22 +182,28 @@ export const encryptSecret = (encKey: string, key: string, value?: string, comme ciphertext: secretValueCiphertext, iv: secretValueIV, tag: secretValueTag - } = crypto.encryption().encryptSymmetric({ - plaintext: value ?? "", - key: encKey, - keySize: SymmetricKeySize.Bits128 - }); + } = crypto + .encryption() + .symmetric() + .encrypt({ + plaintext: value ?? "", + key: encKey, + keySize: SymmetricKeySize.Bits128 + }); // encrypt comment const { ciphertext: secretCommentCiphertext, iv: secretCommentIV, tag: secretCommentTag - } = crypto.encryption().encryptSymmetric({ - plaintext: comment ?? "", - key: encKey, - keySize: SymmetricKeySize.Bits128 - }); + } = crypto + .encryption() + .symmetric() + .encrypt({ + plaintext: comment ?? "", + key: encKey, + keySize: SymmetricKeySize.Bits128 + }); return { secretKeyCiphertext, @@ -206,7 +219,7 @@ export const encryptSecret = (encKey: string, key: string, value?: string, comme }; export const decryptSecret = (decryptKey: string, encSecret: TSecrets) => { - const secretKey = crypto.encryption().decryptSymmetric({ + const secretKey = crypto.encryption().symmetric().decrypt({ key: decryptKey, ciphertext: encSecret.secretKeyCiphertext, tag: encSecret.secretKeyTag, @@ -214,7 +227,7 @@ export const decryptSecret = (decryptKey: string, encSecret: TSecrets) => { keySize: SymmetricKeySize.Bits128 }); - const secretValue = crypto.encryption().decryptSymmetric({ + const secretValue = crypto.encryption().symmetric().decrypt({ key: decryptKey, ciphertext: encSecret.secretValueCiphertext, tag: encSecret.secretValueTag, @@ -224,7 +237,7 @@ export const decryptSecret = (decryptKey: string, encSecret: TSecrets) => { const secretComment = encSecret.secretCommentIV && encSecret.secretCommentTag && encSecret.secretCommentCiphertext - ? crypto.encryption().decryptSymmetric({ + ? crypto.encryption().symmetric().decrypt({ key: decryptKey, ciphertext: encSecret.secretCommentCiphertext, tag: encSecret.secretCommentTag, diff --git a/backend/src/db/seeds/3-project.ts b/backend/src/db/seeds/3-project.ts index 91a309aa6..26f96eafb 100644 --- a/backend/src/db/seeds/3-project.ts +++ b/backend/src/db/seeds/3-project.ts @@ -70,7 +70,7 @@ 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 = crypto.encryption().encryptSymmetric({ + const secretBlindIndex = crypto.encryption().symmetric().encrypt({ plaintext: salt, key: encKey, keySize: SymmetricKeySize.Bits128 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 2c7086e48..46d2782b3 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 @@ -88,7 +88,7 @@ export const auditLogStreamServiceFactory = ({ }); const encryptedHeaders = headers - ? crypto.encryption().encryptWithRootEncryptionKey(JSON.stringify(headers)) + ? crypto.encryption().symmetric().encryptWithRootEncryptionKey(JSON.stringify(headers)) : undefined; const logStream = await auditLogStreamDAL.create({ orgId: actorOrgId, @@ -156,7 +156,7 @@ export const auditLogStreamServiceFactory = ({ }); const encryptedHeaders = headers - ? crypto.encryption().encryptWithRootEncryptionKey(JSON.stringify(headers)) + ? crypto.encryption().symmetric().encryptWithRootEncryptionKey(JSON.stringify(headers)) : undefined; const updatedLogStream = await auditLogStreamDAL.updateById(id, { url, @@ -210,12 +210,15 @@ export const auditLogStreamServiceFactory = ({ const headers = logStream?.encryptedHeadersCiphertext && logStream?.encryptedHeadersIV && logStream?.encryptedHeadersTag ? (JSON.parse( - crypto.encryption().decryptWithRootEncryptionKey({ - tag: logStream.encryptedHeadersTag, - iv: logStream.encryptedHeadersIV, - ciphertext: logStream.encryptedHeadersCiphertext, - keyEncoding: logStream.encryptedHeadersKeyEncoding as SecretKeyEncoding - }) + crypto + .encryption() + .symmetric() + .decryptWithRootEncryptionKey({ + tag: logStream.encryptedHeadersTag, + iv: logStream.encryptedHeadersIV, + ciphertext: logStream.encryptedHeadersCiphertext, + keyEncoding: logStream.encryptedHeadersKeyEncoding as SecretKeyEncoding + }) ) as LogStreamHeaders[]) : undefined; 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 09d666889..be58c4c43 100644 --- a/backend/src/ee/services/audit-log/audit-log-queue.ts +++ b/backend/src/ee/services/audit-log/audit-log-queue.ts @@ -114,12 +114,15 @@ export const auditLogQueueServiceFactory = async ({ const streamHeaders = encryptedHeadersIV && encryptedHeadersCiphertext && encryptedHeadersTag ? (JSON.parse( - crypto.encryption().decryptWithRootEncryptionKey({ - keyEncoding: encryptedHeadersKeyEncoding as SecretKeyEncoding, - iv: encryptedHeadersIV, - tag: encryptedHeadersTag, - ciphertext: encryptedHeadersCiphertext - }) + crypto + .encryption() + .symmetric() + .decryptWithRootEncryptionKey({ + keyEncoding: encryptedHeadersKeyEncoding as SecretKeyEncoding, + iv: encryptedHeadersIV, + tag: encryptedHeadersTag, + ciphertext: encryptedHeadersCiphertext + }) ) as LogStreamHeaders[]) : []; @@ -216,12 +219,15 @@ export const auditLogQueueServiceFactory = async ({ const streamHeaders = encryptedHeadersIV && encryptedHeadersCiphertext && encryptedHeadersTag ? (JSON.parse( - crypto.encryption().decryptWithRootEncryptionKey({ - keyEncoding: encryptedHeadersKeyEncoding as SecretKeyEncoding, - iv: encryptedHeadersIV, - tag: encryptedHeadersTag, - ciphertext: encryptedHeadersCiphertext - }) + crypto + .encryption() + .symmetric() + .decryptWithRootEncryptionKey({ + keyEncoding: encryptedHeadersKeyEncoding as SecretKeyEncoding, + iv: encryptedHeadersIV, + tag: encryptedHeadersTag, + ciphertext: encryptedHeadersCiphertext + }) ) as LogStreamHeaders[]) : []; diff --git a/backend/src/ee/services/group/group-fns.ts b/backend/src/ee/services/group/group-fns.ts index af52d2f21..436b0b79e 100644 --- a/backend/src/ee/services/group/group-fns.ts +++ b/backend/src/ee/services/group/group-fns.ts @@ -94,12 +94,15 @@ const addAcceptedUsersToGroup = async ({ }); } - const botPrivateKey = crypto.encryption().decryptWithRootEncryptionKey({ - keyEncoding: bot.keyEncoding as SecretKeyEncoding, - iv: bot.iv, - tag: bot.tag, - ciphertext: bot.encryptedPrivateKey - }); + const botPrivateKey = crypto + .encryption() + .symmetric() + .decryptWithRootEncryptionKey({ + keyEncoding: bot.keyEncoding as SecretKeyEncoding, + iv: bot.iv, + tag: bot.tag, + ciphertext: bot.encryptedPrivateKey + }); const plaintextProjectKey = crypto.encryption().asymmetric().decrypt({ ciphertext: ghostUserLatestKey.encryptedKey, 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 9cac1dd43..2a755f9a6 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 @@ -820,7 +820,7 @@ export const secretApprovalRequestServiceFactory = ({ type: SecretType.Shared, references: botKey ? getAllNestedSecretReferences( - crypto.encryption().decryptSymmetric({ + crypto.encryption().symmetric().decrypt({ ciphertext: el.secretValueCiphertext, iv: el.secretValueIV, tag: el.secretValueTag, @@ -866,7 +866,7 @@ export const secretApprovalRequestServiceFactory = ({ ]), references: botKey ? getAllNestedSecretReferences( - crypto.encryption().decryptSymmetric({ + crypto.encryption().symmetric().decrypt({ ciphertext: el.secretValueCiphertext, iv: el.secretValueIV, tag: el.secretValueTag, 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 3aa3a1f2a..4a5558f46 100644 --- a/backend/src/ee/services/secret-replication/secret-replication-service.ts +++ b/backend/src/ee/services/secret-replication/secret-replication-service.ts @@ -100,7 +100,7 @@ const getReplicationKeyLockPrefix = (projectId: string, environmentSlug: string, export const getReplicationFolderName = (importId: string) => `${ReservedFolders.SecretReplication}${importId}`; const getDecryptedKeyValue = (key: string, secret: TSecrets) => { - const secretKey = crypto.encryption().decryptSymmetric({ + const secretKey = crypto.encryption().symmetric().decrypt({ ciphertext: secret.secretKeyCiphertext, iv: secret.secretKeyIV, tag: secret.secretKeyTag, @@ -108,7 +108,7 @@ const getDecryptedKeyValue = (key: string, secret: TSecrets) => { keySize: SymmetricKeySize.Bits128 }); - const secretValue = crypto.encryption().decryptSymmetric({ + const secretValue = crypto.encryption().symmetric().decrypt({ ciphertext: secret.secretValueCiphertext, iv: secret.secretValueIV, tag: secret.secretValueTag, 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 8a02d3cd3..1d5c1cedf 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 @@ -372,14 +372,17 @@ export const secretRotationQueueFactory = ({ const encryptedSecrets = rotationOutputs.map(({ key: outputKey, secretId }) => ({ secretId, - value: crypto.encryption().encryptSymmetric({ - plaintext: - typeof newCredential.outputs[outputKey] === "object" - ? JSON.stringify(newCredential.outputs[outputKey]) - : String(newCredential.outputs[outputKey]), - key: botKey, - keySize: SymmetricKeySize.Bits128 - }) + value: crypto + .encryption() + .symmetric() + .encrypt({ + 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 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 06aa306d9..53056e294 100644 --- a/backend/src/ee/services/secret-rotation/secret-rotation-service.ts +++ b/backend/src/ee/services/secret-rotation/secret-rotation-service.ts @@ -235,7 +235,7 @@ export const secretRotationServiceFactory = ({ secret: { id: output.secret.id, version: output.secret.version, - secretKey: crypto.encryption().decryptSymmetric({ + secretKey: crypto.encryption().symmetric().decrypt({ ciphertext: output.secret.secretKeyCiphertext, iv: output.secret.secretKeyIV, tag: output.secret.secretKeyTag, 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 c6f0c0353..a61b0d586 100644 --- a/backend/src/ee/services/secret-snapshot/secret-snapshot-service.ts +++ b/backend/src/ee/services/secret-snapshot/secret-snapshot-service.ts @@ -237,7 +237,7 @@ export const secretSnapshotServiceFactory = ({ snapshotDetails = { ...encryptedSnapshotDetails, secretVersions: encryptedSnapshotDetails.secretVersions.map((el) => { - const secretKey = crypto.encryption().decryptSymmetric({ + const secretKey = crypto.encryption().symmetric().decrypt({ ciphertext: el.secretKeyCiphertext, iv: el.secretKeyIV, tag: el.secretKeyTag, @@ -259,7 +259,7 @@ export const secretSnapshotServiceFactory = ({ let secretValue = ""; if (canReadValue) { - secretValue = crypto.encryption().decryptSymmetric({ + secretValue = crypto.encryption().symmetric().decrypt({ ciphertext: el.secretValueCiphertext, iv: el.secretValueIV, tag: el.secretValueTag, @@ -277,7 +277,7 @@ export const secretSnapshotServiceFactory = ({ secretValue, secretComment: el.secretCommentTag && el.secretCommentIV && el.secretCommentCiphertext - ? crypto.encryption().decryptSymmetric({ + ? crypto.encryption().symmetric().decrypt({ ciphertext: el.secretCommentCiphertext, iv: el.secretCommentIV, tag: el.secretCommentTag, diff --git a/backend/src/lib/crypto/cryptography/crypto.ts b/backend/src/lib/crypto/cryptography/crypto.ts index 05f17f750..30f00fa4b 100644 --- a/backend/src/lib/crypto/cryptography/crypto.ts +++ b/backend/src/lib/crypto/cryptography/crypto.ts @@ -221,122 +221,134 @@ const cryptographyFactory = () => { }; }; - const decryptSymmetric = ({ ciphertext, iv, tag, key, keySize }: TDecryptSymmetricInput): string => { - let decipher; + const symmetric = () => { + const decrypt = ({ 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 - 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")); - } + 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 + 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"); + decipher.setAuthTag(Buffer.from(tag, "base64")); + let cleartext = decipher.update(ciphertext, "base64", "utf8"); + cleartext += decipher.final("utf8"); - return cleartext; - }; + return cleartext; + }; - const encryptSymmetric = ({ plaintext, key, keySize }: TEncryptSymmetricInput) => { - let iv; - let cipher; + const encrypt = ({ 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); - } + 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"); + 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 } = encrypt({ + 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 } = encrypt({ + plaintext: data, + key: encryptionKey, + keySize: SymmetricKeySize.Bits128 + }); + return { + iv, + tag, + ciphertext, + algorithm: SecretEncryptionAlgo.AES_256_GCM, + encoding: SecretKeyEncoding.UTF8 + }; + } + throw new CryptographyError({ + message: "Missing both encryption keys" + }); + }; + + const decryptWithRootEncryptionKey = ({ + 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 = symmetric().decrypt({ + key: rootEncryptionKey, + iv, + tag, + ciphertext, + keySize: SymmetricKeySize.Bits256 + }); + return data as T; + } + if (encryptionKey && keyEncoding === SecretKeyEncoding.UTF8) { + const data = symmetric().decrypt({ + key: encryptionKey, + iv, + tag, + ciphertext, + keySize: SymmetricKeySize.Bits128 + }); + return data as T; + } + throw new CryptographyError({ + message: "Missing both encryption keys" + }); + }; return { - ciphertext, - iv: iv.toString("base64"), - tag: cipher.getAuthTag().toString("base64") + decrypt, + encrypt, + encryptWithRootEncryptionKey, + decryptWithRootEncryptionKey }; }; - 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 CryptographyError({ - message: "Missing both encryption keys" - }); - }; - - const decryptWithRootEncryptionKey = ({ - 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, - 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 CryptographyError({ - message: "Missing both encryption keys" - }); - }; - return { asymmetric, - encryptWithRootEncryptionKey, - decryptWithRootEncryptionKey, - encryptSymmetric, - decryptSymmetric + symmetric }; }; diff --git a/backend/src/lib/crypto/encryption.ts b/backend/src/lib/crypto/encryption.ts index d11c4ac97..7d2710207 100644 --- a/backend/src/lib/crypto/encryption.ts +++ b/backend/src/lib/crypto/encryption.ts @@ -32,11 +32,13 @@ export const buildSecretBlindIndexFromName = async ({ if (rootEncryptionKey && keyEncoding === SecretKeyEncoding.BASE64) { salt = crypto .encryption() - .decryptSymmetric({ iv, ciphertext, key: rootEncryptionKey, tag, keySize: SymmetricKeySize.Bits256 }); + .symmetric() + .decrypt({ iv, ciphertext, key: rootEncryptionKey, tag, keySize: SymmetricKeySize.Bits256 }); } else if (encryptionKey && keyEncoding === SecretKeyEncoding.UTF8) { salt = crypto .encryption() - .decryptSymmetric({ iv, ciphertext, key: encryptionKey, tag, keySize: SymmetricKeySize.Bits128 }); + .symmetric() + .decrypt({ iv, ciphertext, key: encryptionKey, tag, keySize: SymmetricKeySize.Bits128 }); } if (!salt) throw new Error("Missing secret blind index key"); diff --git a/backend/src/lib/crypto/srp.ts b/backend/src/lib/crypto/srp.ts index 6c4dbc5aa..3f403405e 100644 --- a/backend/src/lib/crypto/srp.ts +++ b/backend/src/lib/crypto/srp.ts @@ -74,11 +74,14 @@ export const generateUserSrpKeys = async ( ciphertext: encryptedPrivateKey, iv: encryptedPrivateKeyIV, tag: encryptedPrivateKeyTag - } = crypto.encryption().encryptSymmetric({ - plaintext: privateKey, - key: key.toString("base64"), - keySize: SymmetricKeySize.Bits256 - }); + } = crypto + .encryption() + .symmetric() + .encrypt({ + plaintext: privateKey, + key: key.toString("base64"), + keySize: SymmetricKeySize.Bits256 + }); // create the protected key by encrypting the symmetric key // [key] with the derived key @@ -86,11 +89,14 @@ export const generateUserSrpKeys = async ( ciphertext: protectedKey, iv: protectedKeyIV, tag: protectedKeyTag - } = crypto.encryption().encryptSymmetric({ - plaintext: key.toString("hex"), - key: derivedKey.toString("base64"), - keySize: SymmetricKeySize.Bits256 - }); + } = crypto + .encryption() + .symmetric() + .encrypt({ + plaintext: key.toString("hex"), + key: derivedKey.toString("base64"), + keySize: SymmetricKeySize.Bits256 + }); return { protectedKey, @@ -121,13 +127,16 @@ export const getUserPrivateKey = async ( > ) => { if (user.encryptionVersion === UserEncryption.V1) { - 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"), - keySize: SymmetricKeySize.Bits128 - }); + return crypto + .encryption() + .symmetric() + .decrypt({ + 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"), + keySize: SymmetricKeySize.Bits128 + }); } if ( user.encryptionVersion === UserEncryption.V2 && @@ -145,7 +154,7 @@ export const getUserPrivateKey = async ( raw: true }); if (!derivedKey) throw new Error("Failed to derive key from password"); - const key = crypto.encryption().decryptSymmetric({ + const key = crypto.encryption().symmetric().decrypt({ ciphertext: user.protectedKey, iv: user.protectedKeyIV, tag: user.protectedKeyTag, @@ -153,13 +162,16 @@ export const getUserPrivateKey = async ( keySize: SymmetricKeySize.Bits128 }); - const privateKey = crypto.encryption().decryptSymmetric({ - ciphertext: user.encryptedPrivateKey, - iv: user.iv, - tag: user.tag, - key: Buffer.from(key, "hex"), - keySize: SymmetricKeySize.Bits128 - }); + const privateKey = crypto + .encryption() + .symmetric() + .decrypt({ + ciphertext: user.encryptedPrivateKey, + iv: user.iv, + tag: user.tag, + key: Buffer.from(key, "hex"), + keySize: SymmetricKeySize.Bits128 + }); return privateKey; } throw new Error(`GetUserPrivateKey: Encryption version not found`); diff --git a/backend/src/services/auth/auth-login-service.ts b/backend/src/services/auth/auth-login-service.ts index 8463562bc..aea90fe11 100644 --- a/backend/src/services/auth/auth-login-service.ts +++ b/backend/src/services/auth/auth-login-service.ts @@ -336,7 +336,10 @@ export const authLoginServiceFactory = ({ const hashedPassword = await crypto.hashing().createHash(password, cfg.SALT_ROUNDS); - const { iv, tag, ciphertext, encoding } = crypto.encryption().encryptWithRootEncryptionKey(privateKey); + const { iv, tag, ciphertext, encoding } = crypto + .encryption() + .symmetric() + .encryptWithRootEncryptionKey(privateKey); await userDAL.updateUserEncryptionByUserId(userEnc.userId, { serverPrivateKey: null, diff --git a/backend/src/services/auth/auth-password-service.ts b/backend/src/services/auth/auth-password-service.ts index 39ab0a134..0dbdfaf79 100644 --- a/backend/src/services/auth/auth-password-service.ts +++ b/backend/src/services/auth/auth-password-service.ts @@ -222,12 +222,15 @@ export const authPaswordServiceFactory = ({ user.serverEncryptedPrivateKeyEncoding && user.encryptionVersion === UserEncryption.V2 ) { - privateKey = crypto.encryption().decryptWithRootEncryptionKey({ - iv: user.serverEncryptedPrivateKeyIV, - tag: user.serverEncryptedPrivateKeyTag, - ciphertext: user.serverEncryptedPrivateKey, - keyEncoding: user.serverEncryptedPrivateKeyEncoding as SecretKeyEncoding - }); + privateKey = crypto + .encryption() + .symmetric() + .decryptWithRootEncryptionKey({ + iv: user.serverEncryptedPrivateKeyIV, + tag: user.serverEncryptedPrivateKeyTag, + ciphertext: user.serverEncryptedPrivateKey, + keyEncoding: user.serverEncryptedPrivateKeyEncoding as SecretKeyEncoding + }); } else { throw new BadRequestError({ message: "Cannot reset password without current credentials or recovery method", @@ -240,7 +243,7 @@ export const authPaswordServiceFactory = ({ privateKey }); - const { tag, iv, ciphertext, encoding } = crypto.encryption().encryptWithRootEncryptionKey(privateKey); + const { tag, iv, ciphertext, encoding } = crypto.encryption().symmetric().encryptWithRootEncryptionKey(privateKey); await userDAL.updateUserEncryptionByUserId(userId, { hashedPassword: newHashedPassword, diff --git a/backend/src/services/auth/auth-signup-service.ts b/backend/src/services/auth/auth-signup-service.ts index c2c337292..96cf4121a 100644 --- a/backend/src/services/auth/auth-signup-service.ts +++ b/backend/src/services/auth/auth-signup-service.ts @@ -201,7 +201,7 @@ export const authSignupServiceFactory = ({ tag: encryptedPrivateKeyTag, encryptionVersion: UserEncryption.V2 }); - const { tag, encoding, ciphertext, iv } = crypto.encryption().encryptWithRootEncryptionKey(privateKey); + const { tag, encoding, ciphertext, iv } = crypto.encryption().symmetric().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"); @@ -222,12 +222,15 @@ export const authSignupServiceFactory = ({ systemGeneratedUserEncryptionKey.serverEncryptedPrivateKeyEncoding ) { // get server generated password - const serverGeneratedPassword = crypto.encryption().decryptWithRootEncryptionKey({ - iv: systemGeneratedUserEncryptionKey.serverEncryptedPrivateKeyIV, - tag: systemGeneratedUserEncryptionKey.serverEncryptedPrivateKeyTag, - ciphertext: systemGeneratedUserEncryptionKey.serverEncryptedPrivateKey, - keyEncoding: systemGeneratedUserEncryptionKey.serverEncryptedPrivateKeyEncoding as SecretKeyEncoding - }); + const serverGeneratedPassword = crypto + .encryption() + .symmetric() + .decryptWithRootEncryptionKey({ + iv: systemGeneratedUserEncryptionKey.serverEncryptedPrivateKeyIV, + tag: systemGeneratedUserEncryptionKey.serverEncryptedPrivateKeyTag, + ciphertext: systemGeneratedUserEncryptionKey.serverEncryptedPrivateKey, + keyEncoding: systemGeneratedUserEncryptionKey.serverEncryptedPrivateKeyEncoding as SecretKeyEncoding + }); const serverGeneratedPrivateKey = await getUserPrivateKey(serverGeneratedPassword, { ...systemGeneratedUserEncryptionKey }); @@ -444,7 +447,7 @@ export const authSignupServiceFactory = ({ tag: encryptedPrivateKeyTag, encryptionVersion: 2 }); - const { tag, encoding, ciphertext, iv } = crypto.encryption().encryptWithRootEncryptionKey(privateKey); + const { tag, encoding, ciphertext, iv } = crypto.encryption().symmetric().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"); @@ -461,12 +464,15 @@ export const authSignupServiceFactory = ({ systemGeneratedUserEncryptionKey.serverEncryptedPrivateKeyEncoding ) { // get server generated password - const serverGeneratedPassword = crypto.encryption().decryptWithRootEncryptionKey({ - iv: systemGeneratedUserEncryptionKey.serverEncryptedPrivateKeyIV, - tag: systemGeneratedUserEncryptionKey.serverEncryptedPrivateKeyTag, - ciphertext: systemGeneratedUserEncryptionKey.serverEncryptedPrivateKey, - keyEncoding: systemGeneratedUserEncryptionKey.serverEncryptedPrivateKeyEncoding as SecretKeyEncoding - }); + const serverGeneratedPassword = crypto + .encryption() + .symmetric() + .decryptWithRootEncryptionKey({ + iv: systemGeneratedUserEncryptionKey.serverEncryptedPrivateKeyIV, + tag: systemGeneratedUserEncryptionKey.serverEncryptedPrivateKeyTag, + ciphertext: systemGeneratedUserEncryptionKey.serverEncryptedPrivateKey, + keyEncoding: systemGeneratedUserEncryptionKey.serverEncryptedPrivateKeyEncoding as SecretKeyEncoding + }); const serverGeneratedPrivateKey = await getUserPrivateKey(serverGeneratedPassword, { ...systemGeneratedUserEncryptionKey }); diff --git a/backend/src/services/external-migration/external-migration-queue.ts b/backend/src/services/external-migration/external-migration-queue.ts index bf4aa5ad0..c4e6b43c5 100644 --- a/backend/src/services/external-migration/external-migration-queue.ts +++ b/backend/src/services/external-migration/external-migration-queue.ts @@ -98,7 +98,7 @@ export const externalMigrationQueueFactory = ({ template: SmtpTemplates.ExternalImportStarted }); - const decrypted = crypto.encryption().decryptWithRootEncryptionKey({ + const decrypted = crypto.encryption().symmetric().decryptWithRootEncryptionKey({ ciphertext: data.ciphertext, iv: data.iv, keyEncoding: data.encoding, diff --git a/backend/src/services/external-migration/external-migration-service.ts b/backend/src/services/external-migration/external-migration-service.ts index 67dbfa1ed..c310fd273 100644 --- a/backend/src/services/external-migration/external-migration-service.ts +++ b/backend/src/services/external-migration/external-migration-service.ts @@ -56,7 +56,7 @@ export const externalMigrationServiceFactory = ({ actorAuthMethod }); - const encrypted = crypto.encryption().encryptWithRootEncryptionKey(stringifiedJson); + const encrypted = crypto.encryption().symmetric().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 21b8efbb8..a04d8b19f 100644 --- a/backend/src/services/group-project/group-project-service.ts +++ b/backend/src/services/group-project/group-project-service.ts @@ -212,12 +212,15 @@ export const groupProjectServiceFactory = ({ }); } - const botPrivateKey = crypto.encryption().decryptWithRootEncryptionKey({ - keyEncoding: bot.keyEncoding as SecretKeyEncoding, - iv: bot.iv, - tag: bot.tag, - ciphertext: bot.encryptedPrivateKey - }); + const botPrivateKey = crypto + .encryption() + .symmetric() + .decryptWithRootEncryptionKey({ + keyEncoding: bot.keyEncoding as SecretKeyEncoding, + iv: bot.iv, + tag: bot.tag, + ciphertext: bot.encryptedPrivateKey + }); const plaintextProjectKey = crypto.encryption().asymmetric().decrypt({ ciphertext: ghostUserLatestKey.encryptedKey, diff --git a/backend/src/services/integration-auth/integration-auth-service.ts b/backend/src/services/integration-auth/integration-auth-service.ts index f49369bad..0729fcb5d 100644 --- a/backend/src/services/integration-auth/integration-auth-service.ts +++ b/backend/src/services/integration-auth/integration-auth-service.ts @@ -218,7 +218,7 @@ 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 = crypto.encryption().encryptSymmetric({ + const refreshEncToken = crypto.encryption().symmetric().encrypt({ plaintext: tokenExchange.refreshToken, key: botKey, keySize: SymmetricKeySize.Bits128 @@ -229,7 +229,7 @@ export const integrationAuthServiceFactory = ({ updateDoc.refreshCiphertext = refreshEncToken.ciphertext; } if (tokenExchange.accessToken) { - const accessEncToken = crypto.encryption().encryptSymmetric({ + const accessEncToken = crypto.encryption().symmetric().encrypt({ plaintext: tokenExchange.accessToken, key: botKey, keySize: SymmetricKeySize.Bits128 @@ -355,7 +355,7 @@ export const integrationAuthServiceFactory = ({ url, updateDoc.metadata as Record ); - const refreshEncToken = crypto.encryption().encryptSymmetric({ + const refreshEncToken = crypto.encryption().symmetric().encrypt({ plaintext: tokenDetails.refreshToken, key: botKey, keySize: SymmetricKeySize.Bits128 @@ -363,7 +363,7 @@ export const integrationAuthServiceFactory = ({ updateDoc.refreshIV = refreshEncToken.iv; updateDoc.refreshTag = refreshEncToken.tag; updateDoc.refreshCiphertext = refreshEncToken.ciphertext; - const accessEncToken = crypto.encryption().encryptSymmetric({ + const accessEncToken = crypto.encryption().symmetric().encrypt({ plaintext: tokenDetails.accessToken, key: botKey, keySize: SymmetricKeySize.Bits128 @@ -377,7 +377,7 @@ export const integrationAuthServiceFactory = ({ if (!refreshToken && (accessId || accessToken || awsAssumeIamRoleArn)) { if (accessToken) { - const accessEncToken = crypto.encryption().encryptSymmetric({ + const accessEncToken = crypto.encryption().symmetric().encrypt({ plaintext: accessToken, key: botKey, keySize: SymmetricKeySize.Bits128 @@ -387,7 +387,7 @@ export const integrationAuthServiceFactory = ({ updateDoc.accessCiphertext = accessEncToken.ciphertext; } if (accessId) { - const accessEncToken = crypto.encryption().encryptSymmetric({ + const accessEncToken = crypto.encryption().symmetric().encrypt({ plaintext: accessId, key: botKey, keySize: SymmetricKeySize.Bits128 @@ -397,7 +397,7 @@ export const integrationAuthServiceFactory = ({ updateDoc.accessIdCiphertext = accessEncToken.ciphertext; } if (awsAssumeIamRoleArn) { - const awsAssumeIamRoleArnEnc = crypto.encryption().encryptSymmetric({ + const awsAssumeIamRoleArnEnc = crypto.encryption().symmetric().encrypt({ plaintext: awsAssumeIamRoleArn, key: botKey, keySize: SymmetricKeySize.Bits128 @@ -516,7 +516,7 @@ export const integrationAuthServiceFactory = ({ url, updateDoc.metadata as Record ); - const refreshEncToken = crypto.encryption().encryptSymmetric({ + const refreshEncToken = crypto.encryption().symmetric().encrypt({ plaintext: tokenDetails.refreshToken, key: botKey, keySize: SymmetricKeySize.Bits128 @@ -525,7 +525,7 @@ export const integrationAuthServiceFactory = ({ updateDoc.refreshTag = refreshEncToken.tag; updateDoc.refreshCiphertext = refreshEncToken.ciphertext; - const accessEncToken = crypto.encryption().encryptSymmetric({ + const accessEncToken = crypto.encryption().symmetric().encrypt({ plaintext: tokenDetails.accessToken, key: botKey, keySize: SymmetricKeySize.Bits128 @@ -540,7 +540,7 @@ export const integrationAuthServiceFactory = ({ if (!refreshToken && (accessId || accessToken || awsAssumeIamRoleArn)) { if (accessToken) { - const accessEncToken = crypto.encryption().encryptSymmetric({ + const accessEncToken = crypto.encryption().symmetric().encrypt({ plaintext: accessToken, key: botKey, keySize: SymmetricKeySize.Bits128 @@ -550,7 +550,7 @@ export const integrationAuthServiceFactory = ({ updateDoc.accessCiphertext = accessEncToken.ciphertext; } if (accessId) { - const accessEncToken = crypto.encryption().encryptSymmetric({ + const accessEncToken = crypto.encryption().symmetric().encrypt({ plaintext: accessId, key: botKey, keySize: SymmetricKeySize.Bits128 @@ -560,7 +560,7 @@ export const integrationAuthServiceFactory = ({ updateDoc.accessIdCiphertext = accessEncToken.ciphertext; } if (awsAssumeIamRoleArn) { - const awsAssumeIamRoleArnEnc = crypto.encryption().encryptSymmetric({ + const awsAssumeIamRoleArnEnc = crypto.encryption().symmetric().encrypt({ plaintext: awsAssumeIamRoleArn, key: botKey, keySize: SymmetricKeySize.Bits128 @@ -648,7 +648,7 @@ export const integrationAuthServiceFactory = ({ } else { if (!botKey) throw new NotFoundError({ message: "Project bot key not found" }); if (integrationAuth.accessTag && integrationAuth.accessIV && integrationAuth.accessCiphertext) { - accessToken = crypto.encryption().decryptSymmetric({ + accessToken = crypto.encryption().symmetric().decrypt({ ciphertext: integrationAuth.accessCiphertext, iv: integrationAuth.accessIV, tag: integrationAuth.accessTag, @@ -658,7 +658,7 @@ export const integrationAuthServiceFactory = ({ } if (integrationAuth.refreshCiphertext && integrationAuth.refreshIV && integrationAuth.refreshTag) { - const refreshToken = crypto.encryption().decryptSymmetric({ + const refreshToken = crypto.encryption().symmetric().decrypt({ key: botKey, ciphertext: integrationAuth.refreshCiphertext, iv: integrationAuth.refreshIV, @@ -675,13 +675,13 @@ export const integrationAuthServiceFactory = ({ integrationAuth.metadata as Record ); - const refreshEncToken = crypto.encryption().encryptSymmetric({ + const refreshEncToken = crypto.encryption().symmetric().encrypt({ plaintext: tokenDetails.refreshToken, key: botKey, keySize: SymmetricKeySize.Bits128 }); - const accessEncToken = crypto.encryption().encryptSymmetric({ + const accessEncToken = crypto.encryption().symmetric().encrypt({ plaintext: tokenDetails.accessToken, key: botKey, keySize: SymmetricKeySize.Bits128 @@ -701,7 +701,7 @@ export const integrationAuthServiceFactory = ({ if (!accessToken) throw new BadRequestError({ message: "Missing access token" }); if (integrationAuth.accessIdTag && integrationAuth.accessIdIV && integrationAuth.accessIdCiphertext) { - accessId = crypto.encryption().decryptSymmetric({ + accessId = crypto.encryption().symmetric().decrypt({ key: botKey, ciphertext: integrationAuth.accessIdCiphertext, iv: integrationAuth.accessIdIV, diff --git a/backend/src/services/integration-auth/integration-delete-secret.ts b/backend/src/services/integration-auth/integration-delete-secret.ts index a4923e347..2efec7a47 100644 --- a/backend/src/services/integration-auth/integration-delete-secret.ts +++ b/backend/src/services/integration-auth/integration-delete-secret.ts @@ -111,7 +111,7 @@ const getIntegrationSecretsV1 = async ( const secrets = await secretDAL.findByFolderId(dto.folderId); secrets.forEach((secret) => { - const secretKey = crypto.encryption().decryptSymmetric({ + const secretKey = crypto.encryption().symmetric().decrypt({ ciphertext: secret.secretKeyCiphertext, iv: secret.secretKeyIV, tag: secret.secretKeyTag, diff --git a/backend/src/services/org-admin/org-admin-service.ts b/backend/src/services/org-admin/org-admin-service.ts index 81afeedea..6640412c3 100644 --- a/backend/src/services/org-admin/org-admin-service.ts +++ b/backend/src/services/org-admin/org-admin-service.ts @@ -144,12 +144,15 @@ export const orgAdminServiceFactory = ({ }); } - const botPrivateKey = crypto.encryption().decryptWithRootEncryptionKey({ - keyEncoding: bot.keyEncoding as SecretKeyEncoding, - iv: bot.iv, - tag: bot.tag, - ciphertext: bot.encryptedPrivateKey - }); + const botPrivateKey = crypto + .encryption() + .symmetric() + .decryptWithRootEncryptionKey({ + keyEncoding: bot.keyEncoding as SecretKeyEncoding, + iv: bot.iv, + tag: bot.tag, + ciphertext: bot.encryptedPrivateKey + }); const userEncryptionKey = await userDAL.findUserEncKeyByUserId(actorId); if (!userEncryptionKey) diff --git a/backend/src/services/org/org-service.ts b/backend/src/services/org/org-service.ts index 24a1682a3..cda1bc52f 100644 --- a/backend/src/services/org/org-service.ts +++ b/backend/src/services/org/org-service.ts @@ -508,14 +508,14 @@ export const orgServiceFactory = ({ tag: privateKeyTag, encoding: privateKeyKeyEncoding, algorithm: privateKeyAlgorithm - } = crypto.encryption().encryptWithRootEncryptionKey(privateKey); + } = crypto.encryption().symmetric().encryptWithRootEncryptionKey(privateKey); const { ciphertext: encryptedSymmetricKey, iv: symmetricKeyIV, tag: symmetricKeyTag, encoding: symmetricKeyKeyEncoding, algorithm: symmetricKeyAlgorithm - } = crypto.encryption().encryptWithRootEncryptionKey(key); + } = crypto.encryption().symmetric().encryptWithRootEncryptionKey(key); const customerId = await licenseService.generateOrgCustomerId(orgName, userEmail); const organization = await orgDAL.transaction(async (tx) => { @@ -879,6 +879,7 @@ export const orgServiceFactory = ({ const serverGeneratedPassword = crypto.randomBytes(32).toString("hex"); const { tag, encoding, ciphertext, iv } = crypto .encryption() + .symmetric() .encryptWithRootEncryptionKey(serverGeneratedPassword); const encKeys = await generateUserSrpKeys(inviteeEmail, serverGeneratedPassword); await userDAL.createUserEncryption( @@ -1099,6 +1100,7 @@ export const orgServiceFactory = ({ const { iv, tag, ciphertext, encoding, algorithm } = crypto .encryption() + .symmetric() .encryptWithRootEncryptionKey(newGhostUser.keys.plainPrivateKey); if (autoGeneratedBot) { await projectBotDAL.updateById( @@ -1137,12 +1139,15 @@ export const orgServiceFactory = ({ }); } - const botPrivateKey = crypto.encryption().decryptWithRootEncryptionKey({ - keyEncoding: bot.keyEncoding as SecretKeyEncoding, - iv: bot.iv, - tag: bot.tag, - ciphertext: bot.encryptedPrivateKey - }); + const botPrivateKey = crypto + .encryption() + .symmetric() + .decryptWithRootEncryptionKey({ + keyEncoding: bot.keyEncoding as SecretKeyEncoding, + iv: bot.iv, + tag: bot.tag, + ciphertext: bot.encryptedPrivateKey + }); const newWsMembers = assignWorkspaceKeysToMembers({ decryptKey: ghostUserLatestKey, diff --git a/backend/src/services/project-bot/project-bot-fns.ts b/backend/src/services/project-bot/project-bot-fns.ts index daf6fa33e..d26669b86 100644 --- a/backend/src/services/project-bot/project-bot-fns.ts +++ b/backend/src/services/project-bot/project-bot-fns.ts @@ -7,12 +7,15 @@ import { TProjectDALFactory } from "../project/project-dal"; import { TGetPrivateKeyDTO } from "./project-bot-types"; export const getBotPrivateKey = ({ bot }: TGetPrivateKeyDTO) => { - return crypto.encryption().decryptWithRootEncryptionKey({ - keyEncoding: bot.keyEncoding as SecretKeyEncoding, - iv: bot.iv, - tag: bot.tag, - ciphertext: bot.encryptedPrivateKey - }); + return crypto + .encryption() + .symmetric() + .decryptWithRootEncryptionKey({ + keyEncoding: bot.keyEncoding as SecretKeyEncoding, + iv: bot.iv, + tag: bot.tag, + ciphertext: bot.encryptedPrivateKey + }); }; export const getBotKeyFnFactory = ( @@ -46,12 +49,15 @@ export const getBotKeyFnFactory = ( projectV1Keys.serverEncryptedPrivateKeyTag && projectV1Keys.serverEncryptedPrivateKeyEncoding ) { - userPrivateKey = crypto.encryption().decryptWithRootEncryptionKey({ - iv: projectV1Keys.serverEncryptedPrivateKeyIV, - tag: projectV1Keys.serverEncryptedPrivateKeyTag, - ciphertext: projectV1Keys.serverEncryptedPrivateKey, - keyEncoding: projectV1Keys.serverEncryptedPrivateKeyEncoding as SecretKeyEncoding - }); + userPrivateKey = crypto + .encryption() + .symmetric() + .decryptWithRootEncryptionKey({ + iv: projectV1Keys.serverEncryptedPrivateKeyIV, + tag: projectV1Keys.serverEncryptedPrivateKeyTag, + ciphertext: projectV1Keys.serverEncryptedPrivateKey, + keyEncoding: projectV1Keys.serverEncryptedPrivateKeyEncoding as SecretKeyEncoding + }); } const workspaceKey = crypto.encryption().asymmetric().decrypt({ ciphertext: projectV1Keys.projectEncryptedKey, @@ -62,6 +68,7 @@ export const getBotKeyFnFactory = ( const botKey = await crypto.encryption().asymmetric().generateKeyPair(); const { iv, tag, ciphertext, encoding, algorithm } = crypto .encryption() + .symmetric() .encryptWithRootEncryptionKey(botKey.privateKey); const encryptedWorkspaceKey = crypto .encryption() diff --git a/backend/src/services/project-bot/project-bot-service.ts b/backend/src/services/project-bot/project-bot-service.ts index ab2eb7b52..76c40dff7 100644 --- a/backend/src/services/project-bot/project-bot-service.ts +++ b/backend/src/services/project-bot/project-bot-service.ts @@ -58,6 +58,7 @@ export const projectBotServiceFactory = ({ const { iv, tag, ciphertext, encoding, algorithm } = crypto .encryption() + .symmetric() .encryptWithRootEncryptionKey(keys.privateKey); const project = await projectDAL.findById(projectId, tx); diff --git a/backend/src/services/project/project-queue.ts b/backend/src/services/project/project-queue.ts index ed6241bc7..0d7c7dd55 100644 --- a/backend/src/services/project/project-queue.ts +++ b/backend/src/services/project/project-queue.ts @@ -114,7 +114,7 @@ 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. - const userPrivateKey = crypto.encryption().decryptWithRootEncryptionKey({ + const userPrivateKey = crypto.encryption().symmetric().decryptWithRootEncryptionKey({ keyEncoding: data.encryptedPrivateKey.keyEncoding, ciphertext: data.encryptedPrivateKey.encryptedKey, iv: data.encryptedPrivateKey.encryptedKeyIv, @@ -316,6 +316,7 @@ export const projectQueueFactory = ({ // Encrypt the bot private key (which is the same as the ghost user) const { iv, tag, ciphertext, encoding, algorithm } = crypto .encryption() + .symmetric() .encryptWithRootEncryptionKey(ghostUser.keys.plainPrivateKey); // 5. Create a bot for the project @@ -337,12 +338,15 @@ export const projectQueueFactory = ({ tx ); - const botPrivateKey = crypto.encryption().decryptWithRootEncryptionKey({ - keyEncoding: newBot.keyEncoding as SecretKeyEncoding, - iv: newBot.iv, - tag: newBot.tag, - ciphertext: newBot.encryptedPrivateKey - }); + const botPrivateKey = crypto + .encryption() + .symmetric() + .decryptWithRootEncryptionKey({ + keyEncoding: newBot.keyEncoding as SecretKeyEncoding, + iv: newBot.iv, + tag: newBot.tag, + ciphertext: newBot.encryptedPrivateKey + }); const botKey = crypto.encryption().asymmetric().decrypt({ ciphertext: newBot.encryptedProjectKey!, @@ -356,23 +360,29 @@ export const projectQueueFactory = ({ const updatedSecretApprovals: TSecretApprovalRequestsSecrets[] = []; const updatedIntegrationAuths: TIntegrationAuths[] = []; for (const rawSecret of decryptedSecrets) { - const secretKeyEncrypted = crypto.encryption().encryptSymmetric({ + const secretKeyEncrypted = crypto.encryption().symmetric().encrypt({ plaintext: rawSecret.decrypted.secretKey, key: botKey, keySize: SymmetricKeySize.Bits128 }); - const secretValueEncrypted = crypto.encryption().encryptSymmetric({ - plaintext: rawSecret.decrypted.secretValue || "", - key: botKey, - keySize: SymmetricKeySize.Bits128 - }); + const secretValueEncrypted = crypto + .encryption() + .symmetric() + .encrypt({ + plaintext: rawSecret.decrypted.secretValue || "", + key: botKey, + keySize: SymmetricKeySize.Bits128 + }); - const secretCommentEncrypted = crypto.encryption().encryptSymmetric({ - plaintext: rawSecret.decrypted.secretComment || "", - key: botKey, - keySize: SymmetricKeySize.Bits128 - }); + const secretCommentEncrypted = crypto + .encryption() + .symmetric() + .encrypt({ + plaintext: rawSecret.decrypted.secretComment || "", + key: botKey, + keySize: SymmetricKeySize.Bits128 + }); const payload: TSecrets = { ...rawSecret.original, @@ -399,23 +409,29 @@ export const projectQueueFactory = ({ } for (const rawSecretVersion of decryptedSecretVersions) { - const secretKeyEncrypted = crypto.encryption().encryptSymmetric({ + const secretKeyEncrypted = crypto.encryption().symmetric().encrypt({ plaintext: rawSecretVersion.decrypted.secretKey, key: botKey, keySize: SymmetricKeySize.Bits128 }); - const secretValueEncrypted = crypto.encryption().encryptSymmetric({ - plaintext: rawSecretVersion.decrypted.secretValue || "", - key: botKey, - keySize: SymmetricKeySize.Bits128 - }); + const secretValueEncrypted = crypto + .encryption() + .symmetric() + .encrypt({ + plaintext: rawSecretVersion.decrypted.secretValue || "", + key: botKey, + keySize: SymmetricKeySize.Bits128 + }); - const secretCommentEncrypted = crypto.encryption().encryptSymmetric({ - plaintext: rawSecretVersion.decrypted.secretComment || "", - key: botKey, - keySize: SymmetricKeySize.Bits128 - }); + const secretCommentEncrypted = crypto + .encryption() + .symmetric() + .encrypt({ + plaintext: rawSecretVersion.decrypted.secretComment || "", + key: botKey, + keySize: SymmetricKeySize.Bits128 + }); const payload: TSecretVersions = { ...rawSecretVersion.original, @@ -442,21 +458,27 @@ export const projectQueueFactory = ({ } for (const rawSecretApproval of decryptedApprovalSecrets) { - const secretKeyEncrypted = crypto.encryption().encryptSymmetric({ + const secretKeyEncrypted = crypto.encryption().symmetric().encrypt({ 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 secretValueEncrypted = crypto + .encryption() + .symmetric() + .encrypt({ + plaintext: rawSecretApproval.decrypted.secretValue || "", + key: botKey, + keySize: SymmetricKeySize.Bits128 + }); + const secretCommentEncrypted = crypto + .encryption() + .symmetric() + .encrypt({ + plaintext: rawSecretApproval.decrypted.secretComment || "", + key: botKey, + keySize: SymmetricKeySize.Bits128 + }); const payload: TSecretApprovalRequestsSecrets = { ...rawSecretApproval.original, @@ -483,17 +505,17 @@ export const projectQueueFactory = ({ } for (const integrationAuth of decryptedIntegrationAuths) { - const access = crypto.encryption().encryptSymmetric({ + const access = crypto.encryption().symmetric().encrypt({ plaintext: integrationAuth.decrypted.access, key: botKey, keySize: SymmetricKeySize.Bits128 }); - const accessId = crypto.encryption().encryptSymmetric({ + const accessId = crypto.encryption().symmetric().encrypt({ plaintext: integrationAuth.decrypted.accessId, key: botKey, keySize: SymmetricKeySize.Bits128 }); - const refresh = crypto.encryption().encryptSymmetric({ + const refresh = crypto.encryption().symmetric().encrypt({ plaintext: integrationAuth.decrypted.refresh, key: botKey, keySize: SymmetricKeySize.Bits128 diff --git a/backend/src/services/project/project-service.ts b/backend/src/services/project/project-service.ts index cff47ff29..de35c11f4 100644 --- a/backend/src/services/project/project-service.ts +++ b/backend/src/services/project/project-service.ts @@ -379,6 +379,7 @@ export const projectServiceFactory = ({ const { iv, tag, ciphertext, encoding, algorithm } = crypto .encryption() + .symmetric() .encryptWithRootEncryptionKey(ghostUser.keys.plainPrivateKey); // 5. Create & a bot for the project @@ -822,7 +823,7 @@ export const projectServiceFactory = ({ }); } - const encryptedPrivateKey = crypto.encryption().encryptWithRootEncryptionKey(userPrivateKey); + const encryptedPrivateKey = crypto.encryption().symmetric().encryptWithRootEncryptionKey(userPrivateKey); await projectQueue.upgradeProject({ projectId, diff --git a/backend/src/services/secret/secret-fns.ts b/backend/src/services/secret/secret-fns.ts index 937367d67..15a31a331 100644 --- a/backend/src/services/secret/secret-fns.ts +++ b/backend/src/services/secret/secret-fns.ts @@ -234,14 +234,14 @@ export const interpolateSecrets = ({ projectId, secretEncKey, secretDAL, folderD const secrets = await secretDAL.findByFolderId(folder.id); const decryptedSec = secrets.reduce>((prev, secret) => { - const decryptedSecretKey = crypto.encryption().decryptSymmetric({ + const decryptedSecretKey = crypto.encryption().symmetric().decrypt({ ciphertext: secret.secretKeyCiphertext, iv: secret.secretKeyIV, tag: secret.secretKeyTag, key: secretEncKey, keySize: SymmetricKeySize.Bits128 }); - const decryptedSecretValue = crypto.encryption().decryptSymmetric({ + const decryptedSecretValue = crypto.encryption().symmetric().decrypt({ ciphertext: secret.secretValueCiphertext, iv: secret.secretValueIV, tag: secret.secretValueTag, @@ -363,7 +363,7 @@ export const decryptSecretRaw = ( }, key: string ) => { - const secretKey = crypto.encryption().decryptSymmetric({ + const secretKey = crypto.encryption().symmetric().decrypt({ ciphertext: secret.secretKeyCiphertext, iv: secret.secretKeyIV, tag: secret.secretKeyTag, @@ -372,7 +372,7 @@ export const decryptSecretRaw = ( }); const secretValue = !secret.secretValueHidden - ? crypto.encryption().decryptSymmetric({ + ? crypto.encryption().symmetric().decrypt({ ciphertext: secret.secretValueCiphertext, iv: secret.secretValueIV, tag: secret.secretValueTag, @@ -384,7 +384,7 @@ export const decryptSecretRaw = ( let secretComment = ""; if (secret.secretCommentCiphertext && secret.secretCommentIV && secret.secretCommentTag) { - secretComment = crypto.encryption().decryptSymmetric({ + secretComment = crypto.encryption().symmetric().decrypt({ ciphertext: secret.secretCommentCiphertext, iv: secret.secretCommentIV, tag: secret.secretCommentTag, @@ -879,22 +879,28 @@ export const createManySecretsRawFnFactory = ({ }); const inputSecrets = secrets.map((secret) => { - const secretKeyEncrypted = crypto.encryption().encryptSymmetric({ + const secretKeyEncrypted = crypto.encryption().symmetric().encrypt({ plaintext: secret.secretName, key: botKey, keySize: SymmetricKeySize.Bits128 }); - const secretValueEncrypted = crypto.encryption().encryptSymmetric({ - plaintext: secret.secretValue || "", - key: botKey, - keySize: SymmetricKeySize.Bits128 - }); + const secretValueEncrypted = crypto + .encryption() + .symmetric() + .encrypt({ + plaintext: secret.secretValue || "", + key: botKey, + keySize: SymmetricKeySize.Bits128 + }); const secretReferences = getAllNestedSecretReferences(secret.secretValue || ""); - const secretCommentEncrypted = crypto.encryption().encryptSymmetric({ - plaintext: secret.secretComment || "", - key: botKey, - keySize: SymmetricKeySize.Bits128 - }); + const secretCommentEncrypted = crypto + .encryption() + .symmetric() + .encrypt({ + plaintext: secret.secretComment || "", + key: botKey, + keySize: SymmetricKeySize.Bits128 + }); return { type: secret.type, @@ -1078,22 +1084,28 @@ export const updateManySecretsRawFnFactory = ({ throw new BadRequestError({ message: "New secret name cannot be empty" }); } - const secretKeyEncrypted = crypto.encryption().encryptSymmetric({ + const secretKeyEncrypted = crypto.encryption().symmetric().encrypt({ plaintext: secret.secretName, key: botKey, keySize: SymmetricKeySize.Bits128 }); - const secretValueEncrypted = crypto.encryption().encryptSymmetric({ - plaintext: secret.secretValue || "", - key: botKey, - keySize: SymmetricKeySize.Bits128 - }); + const secretValueEncrypted = crypto + .encryption() + .symmetric() + .encrypt({ + plaintext: secret.secretValue || "", + key: botKey, + keySize: SymmetricKeySize.Bits128 + }); const secretReferences = getAllNestedSecretReferences(secret.secretValue || ""); - const secretCommentEncrypted = crypto.encryption().encryptSymmetric({ - plaintext: secret.secretComment || "", - key: botKey, - keySize: SymmetricKeySize.Bits128 - }); + const secretCommentEncrypted = crypto + .encryption() + .symmetric() + .encrypt({ + plaintext: secret.secretComment || "", + key: botKey, + keySize: SymmetricKeySize.Bits128 + }); return { type: secret.type, @@ -1176,7 +1188,7 @@ export const decryptSecretWithBot = ( >, key: string ) => { - const secretKey = crypto.encryption().decryptSymmetric({ + const secretKey = crypto.encryption().symmetric().decrypt({ ciphertext: secret.secretKeyCiphertext, iv: secret.secretKeyIV, tag: secret.secretKeyTag, @@ -1184,7 +1196,7 @@ export const decryptSecretWithBot = ( keySize: SymmetricKeySize.Bits128 }); - const secretValue = crypto.encryption().decryptSymmetric({ + const secretValue = crypto.encryption().symmetric().decrypt({ ciphertext: secret.secretValueCiphertext, iv: secret.secretValueIV, tag: secret.secretValueTag, @@ -1195,7 +1207,7 @@ export const decryptSecretWithBot = ( let secretComment = ""; if (secret.secretCommentCiphertext && secret.secretCommentIV && secret.secretCommentTag) { - secretComment = crypto.encryption().decryptSymmetric({ + secretComment = crypto.encryption().symmetric().decrypt({ ciphertext: secret.secretCommentCiphertext, iv: secret.secretCommentIV, tag: secret.secretCommentTag, diff --git a/backend/src/services/secret/secret-queue.ts b/backend/src/services/secret/secret-queue.ts index dbca65385..8fcaa6e34 100644 --- a/backend/src/services/secret/secret-queue.ts +++ b/backend/src/services/secret/secret-queue.ts @@ -503,7 +503,7 @@ export const secretQueueFactory = ({ const secrets = await secretDAL.findByFolderId(dto.folderId); await Promise.allSettled( secrets.map(async (secret) => { - const secretKey = crypto.encryption().decryptSymmetric({ + const secretKey = crypto.encryption().symmetric().decrypt({ ciphertext: secret.secretKeyCiphertext, iv: secret.secretKeyIV, tag: secret.secretKeyTag, @@ -511,7 +511,7 @@ export const secretQueueFactory = ({ keySize: SymmetricKeySize.Bits128 }); - const secretValue = crypto.encryption().decryptSymmetric({ + const secretValue = crypto.encryption().symmetric().decrypt({ ciphertext: secret.secretValueCiphertext, iv: secret.secretValueIV, tag: secret.secretValueTag, @@ -528,7 +528,7 @@ export const secretQueueFactory = ({ content[secretKey] = { value: expandedSecretValue || "" }; if (secret.secretCommentCiphertext && secret.secretCommentIV && secret.secretCommentTag) { - const commentValue = crypto.encryption().decryptSymmetric({ + const commentValue = crypto.encryption().symmetric().decrypt({ ciphertext: secret.secretCommentCiphertext, iv: secret.secretCommentIV, tag: secret.secretCommentTag, @@ -954,13 +954,16 @@ export const secretQueueFactory = ({ integrationAuth.awsAssumeIamRoleArnIV && integrationAuth.awsAssumeIamRoleArnCipherText ) { - awsAssumeRoleArn = crypto.encryption().decryptSymmetric({ - ciphertext: integrationAuth.awsAssumeIamRoleArnCipherText, - iv: integrationAuth.awsAssumeIamRoleArnIV, - tag: integrationAuth.awsAssumeIamRoleArnTag, - key: botKey as string, - keySize: SymmetricKeySize.Bits128 - }); + awsAssumeRoleArn = crypto + .encryption() + .symmetric() + .decrypt({ + ciphertext: integrationAuth.awsAssumeIamRoleArnCipherText, + iv: integrationAuth.awsAssumeIamRoleArnIV, + tag: integrationAuth.awsAssumeIamRoleArnTag, + key: botKey as string, + keySize: SymmetricKeySize.Bits128 + }); } const suffixedSecrets: typeof secrets = {}; @@ -1241,6 +1244,7 @@ export const secretQueueFactory = ({ ); const { iv, tag, ciphertext, encoding, algorithm } = crypto .encryption() + .symmetric() .encryptWithRootEncryptionKey(ghostUser.keys.plainPrivateKey); await projectBotDAL.updateById( bot.id, @@ -1275,14 +1279,14 @@ export const secretQueueFactory = ({ await secretV2BridgeDAL.batchInsert( projectV1Secrets.map((el) => { - const key = crypto.encryption().decryptSymmetric({ + const key = crypto.encryption().symmetric().decrypt({ ciphertext: el.secretKeyCiphertext, iv: el.secretKeyIV, tag: el.secretKeyTag, key: botKey, keySize: SymmetricKeySize.Bits128 }); - const value = crypto.encryption().decryptSymmetric({ + const value = crypto.encryption().symmetric().decrypt({ ciphertext: el.secretValueCiphertext, iv: el.secretValueIV, tag: el.secretValueTag, @@ -1291,7 +1295,7 @@ export const secretQueueFactory = ({ }); const comment = el.secretCommentCiphertext && el.secretCommentTag && el.secretCommentIV - ? crypto.encryption().decryptSymmetric({ + ? crypto.encryption().symmetric().decrypt({ ciphertext: el.secretCommentCiphertext, iv: el.secretCommentIV, tag: el.secretCommentTag, @@ -1346,14 +1350,14 @@ export const secretQueueFactory = ({ }); if (projectV3SecretVersionsGroupById[el.id]) return; - const key = crypto.encryption().decryptSymmetric({ + const key = crypto.encryption().symmetric().decrypt({ ciphertext: el.secretKeyCiphertext, iv: el.secretKeyIV, tag: el.secretKeyTag, key: botKey, keySize: SymmetricKeySize.Bits128 }); - const value = crypto.encryption().decryptSymmetric({ + const value = crypto.encryption().symmetric().decrypt({ ciphertext: el.secretValueCiphertext, iv: el.secretValueIV, tag: el.secretValueTag, @@ -1362,7 +1366,7 @@ export const secretQueueFactory = ({ }); const comment = el.secretCommentCiphertext && el.secretCommentTag && el.secretCommentIV - ? crypto.encryption().decryptSymmetric({ + ? crypto.encryption().symmetric().decrypt({ ciphertext: el.secretCommentCiphertext, iv: el.secretCommentIV, tag: el.secretCommentTag, @@ -1408,14 +1412,14 @@ export const secretQueueFactory = ({ ); Object.values(latestSecretVersionByFolder).forEach((el) => { if (projectV3SecretVersionsGroupById[el.id]) return; - const key = crypto.encryption().decryptSymmetric({ + const key = crypto.encryption().symmetric().decrypt({ ciphertext: el.secretKeyCiphertext, iv: el.secretKeyIV, tag: el.secretKeyTag, key: botKey, keySize: SymmetricKeySize.Bits128 }); - const value = crypto.encryption().decryptSymmetric({ + const value = crypto.encryption().symmetric().decrypt({ ciphertext: el.secretValueCiphertext, iv: el.secretValueIV, tag: el.secretValueTag, @@ -1424,7 +1428,7 @@ export const secretQueueFactory = ({ }); const comment = el.secretCommentCiphertext && el.secretCommentTag && el.secretCommentIV - ? crypto.encryption().decryptSymmetric({ + ? crypto.encryption().symmetric().decrypt({ ciphertext: el.secretCommentCiphertext, iv: el.secretCommentIV, tag: el.secretCommentTag, @@ -1496,7 +1500,7 @@ export const secretQueueFactory = ({ projectV1IntegrationAuths.map((el) => { const accessToken = el.accessIV && el.accessTag && el.accessCiphertext - ? crypto.encryption().decryptSymmetric({ + ? crypto.encryption().symmetric().decrypt({ ciphertext: el.accessCiphertext, iv: el.accessIV, tag: el.accessTag, @@ -1506,7 +1510,7 @@ export const secretQueueFactory = ({ : undefined; const accessId = el.accessIdIV && el.accessIdTag && el.accessIdCiphertext - ? crypto.encryption().decryptSymmetric({ + ? crypto.encryption().symmetric().decrypt({ ciphertext: el.accessIdCiphertext, iv: el.accessIdIV, tag: el.accessIdTag, @@ -1516,7 +1520,7 @@ export const secretQueueFactory = ({ : undefined; const refreshToken = el.refreshIV && el.refreshTag && el.refreshCiphertext - ? crypto.encryption().decryptSymmetric({ + ? crypto.encryption().symmetric().decrypt({ ciphertext: el.refreshCiphertext, iv: el.refreshIV, tag: el.refreshTag, @@ -1526,7 +1530,7 @@ export const secretQueueFactory = ({ : undefined; const awsAssumeRoleArn = el.awsAssumeIamRoleArnCipherText && el.awsAssumeIamRoleArnIV && el.awsAssumeIamRoleArnTag - ? crypto.encryption().decryptSymmetric({ + ? crypto.encryption().symmetric().decrypt({ ciphertext: el.awsAssumeIamRoleArnCipherText, iv: el.awsAssumeIamRoleArnIV, tag: el.awsAssumeIamRoleArnTag, diff --git a/backend/src/services/secret/secret-service.ts b/backend/src/services/secret/secret-service.ts index 83e586360..084423fa9 100644 --- a/backend/src/services/secret/secret-service.ts +++ b/backend/src/services/secret/secret-service.ts @@ -158,13 +158,16 @@ export const secretServiceFactory = ({ return (el: { ciphertext?: string; iv: string; tag: string }) => projectBot?.botKey ? getAllNestedSecretReferences( - crypto.encryption().decryptSymmetric({ - ciphertext: el.ciphertext || "", - iv: el.iv, - tag: el.tag, - key: projectBot.botKey, - keySize: SymmetricKeySize.Bits128 - }) + crypto + .encryption() + .symmetric() + .decrypt({ + ciphertext: el.ciphertext || "", + iv: el.iv, + tag: el.tag, + key: projectBot.botKey, + keySize: SymmetricKeySize.Bits128 + }) ) : undefined; }; @@ -1695,21 +1698,27 @@ export const secretServiceFactory = ({ name: "bot_not_found_error" }); - const secretKeyEncrypted = crypto.encryption().encryptSymmetric({ + const secretKeyEncrypted = crypto.encryption().symmetric().encrypt({ 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 - }); + const secretValueEncrypted = crypto + .encryption() + .symmetric() + .encrypt({ + plaintext: secretValue || "", + key: botKey, + keySize: SymmetricKeySize.Bits128 + }); + const secretCommentEncrypted = crypto + .encryption() + .symmetric() + .encrypt({ + plaintext: secretComment || "", + key: botKey, + keySize: SymmetricKeySize.Bits128 + }); if (policy) { const approval = await secretApprovalRequestService.generateSecretApprovalRequest({ policy, @@ -1875,22 +1884,31 @@ export const secretServiceFactory = ({ name: "bot_not_found_error" }); - 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 secretValueEncrypted = crypto + .encryption() + .symmetric() + .encrypt({ + plaintext: secretValue || "", + key: botKey, + keySize: SymmetricKeySize.Bits128 + }); + const secretCommentEncrypted = crypto + .encryption() + .symmetric() + .encrypt({ + plaintext: secretComment || "", + key: botKey, + keySize: SymmetricKeySize.Bits128 + }); - const secretKeyEncrypted = crypto.encryption().encryptSymmetric({ - plaintext: newSecretName || secretName, - key: botKey, - keySize: SymmetricKeySize.Bits128 - }); + const secretKeyEncrypted = crypto + .encryption() + .symmetric() + .encrypt({ + plaintext: newSecretName || secretName, + key: botKey, + keySize: SymmetricKeySize.Bits128 + }); if (policy) { const approval = await secretApprovalRequestService.generateSecretApprovalRequest({ @@ -2137,21 +2155,27 @@ export const secretServiceFactory = ({ const sanitizedSecrets = inputSecrets.map( ({ secretComment, secretKey, metadata, tagIds, secretValue, skipMultilineEncoding }) => { - const secretKeyEncrypted = crypto.encryption().encryptSymmetric({ + const secretKeyEncrypted = crypto.encryption().symmetric().encrypt({ 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 - }); + const secretValueEncrypted = crypto + .encryption() + .symmetric() + .encrypt({ + plaintext: secretValue || "", + key: botKey, + keySize: SymmetricKeySize.Bits128 + }); + const secretCommentEncrypted = crypto + .encryption() + .symmetric() + .encrypt({ + plaintext: secretComment || "", + key: botKey, + keySize: SymmetricKeySize.Bits128 + }); return { secretName: secretKey, skipMultilineEncoding, @@ -2303,21 +2327,30 @@ export const secretServiceFactory = ({ secretReminderNote, secretReminderRepeatDays }) => { - 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 - }); + const secretKeyEncrypted = crypto + .encryption() + .symmetric() + .encrypt({ + plaintext: newSecretName || secretKey, + key: botKey, + keySize: SymmetricKeySize.Bits128 + }); + const secretValueEncrypted = crypto + .encryption() + .symmetric() + .encrypt({ + plaintext: secretValue || "", + key: botKey, + keySize: SymmetricKeySize.Bits128 + }); + const secretCommentEncrypted = crypto + .encryption() + .symmetric() + .encrypt({ + plaintext: secretComment || "", + key: botKey, + keySize: SymmetricKeySize.Bits128 + }); return { secretName: secretKey, newSecretName, @@ -2528,7 +2561,7 @@ export const secretServiceFactory = ({ }); return secretVersions.map((el) => { - const secretKey = crypto.encryption().decryptSymmetric({ + const secretKey = crypto.encryption().symmetric().decrypt({ ciphertext: secret.secretKeyCiphertext, iv: secret.secretKeyIV, tag: secret.secretKeyTag, @@ -2850,7 +2883,7 @@ export const secretServiceFactory = ({ secrets.map(({ id, secretValueCiphertext, secretValueIV, secretValueTag }) => ({ secretId: id, references: getAllNestedSecretReferences( - crypto.encryption().decryptSymmetric({ + crypto.encryption().symmetric().decrypt({ ciphertext: secretValueCiphertext, iv: secretValueIV, tag: secretValueTag, @@ -2955,7 +2988,7 @@ export const secretServiceFactory = ({ const destinationActions = [ProjectPermissionSecretActions.Create, ProjectPermissionSecretActions.Edit] as const; const decryptedSourceSecrets = sourceSecrets.map((secret) => { - const secretKey = crypto.encryption().decryptSymmetric({ + const secretKey = crypto.encryption().symmetric().decrypt({ ciphertext: secret.secretKeyCiphertext, iv: secret.secretKeyIV, tag: secret.secretKeyTag, @@ -2996,7 +3029,7 @@ export const secretServiceFactory = ({ return { ...secret, secretKey, - secretValue: crypto.encryption().decryptSymmetric({ + secretValue: crypto.encryption().symmetric().decrypt({ ciphertext: secret.secretValueCiphertext, iv: secret.secretValueIV, tag: secret.secretValueTag, @@ -3022,14 +3055,14 @@ export const secretServiceFactory = ({ const decryptedDestinationSecrets = destinationSecretsFromDB.map((secret) => { return { ...secret, - secretKey: crypto.encryption().decryptSymmetric({ + secretKey: crypto.encryption().symmetric().decrypt({ ciphertext: secret.secretKeyCiphertext, iv: secret.secretKeyIV, tag: secret.secretKeyTag, key: botKey, keySize: SymmetricKeySize.Bits128 }), - secretValue: crypto.encryption().decryptSymmetric({ + secretValue: crypto.encryption().symmetric().decrypt({ ciphertext: secret.secretValueCiphertext, iv: secret.secretValueIV, tag: secret.secretValueTag, diff --git a/backend/src/services/super-admin/super-admin-service.ts b/backend/src/services/super-admin/super-admin-service.ts index 0de4950f0..ff47718f1 100644 --- a/backend/src/services/super-admin/super-admin-service.ts +++ b/backend/src/services/super-admin/super-admin-service.ts @@ -501,7 +501,7 @@ export const superAdminServiceFactory = ({ const hashedPassword = await crypto.hashing().createHash(password, appCfg.SALT_ROUNDS); - const { iv, tag, ciphertext, encoding } = crypto.encryption().encryptWithRootEncryptionKey(privateKey); + const { iv, tag, ciphertext, encoding } = crypto.encryption().symmetric().encryptWithRootEncryptionKey(privateKey); const userInfo = await userDAL.transaction(async (tx) => { const newUser = await userDAL.create( { @@ -587,7 +587,7 @@ export const superAdminServiceFactory = ({ }, tx ); - const { tag, encoding, ciphertext, iv } = crypto.encryption().encryptWithRootEncryptionKey(password); + const { tag, encoding, ciphertext, iv } = crypto.encryption().symmetric().encryptWithRootEncryptionKey(password); const encKeys = await generateUserSrpKeys(sanitizedEmail, password); const userEnc = await userDAL.createUserEncryption( diff --git a/backend/src/services/user/user-service.ts b/backend/src/services/user/user-service.ts index d4e392be1..b2258447e 100644 --- a/backend/src/services/user/user-service.ts +++ b/backend/src/services/user/user-service.ts @@ -218,12 +218,15 @@ export const userServiceFactory = ({ throw new NotFoundError({ message: `Private key for user with ID '${userId}' not found` }); } - const privateKey = crypto.encryption().decryptWithRootEncryptionKey({ - ciphertext: user.serverEncryptedPrivateKey, - tag: user.serverEncryptedPrivateKeyTag, - iv: user.serverEncryptedPrivateKeyIV, - keyEncoding: user.serverEncryptedPrivateKeyEncoding as SecretKeyEncoding - }); + const privateKey = crypto + .encryption() + .symmetric() + .decryptWithRootEncryptionKey({ + ciphertext: user.serverEncryptedPrivateKey, + tag: user.serverEncryptedPrivateKeyTag, + iv: user.serverEncryptedPrivateKeyIV, + keyEncoding: user.serverEncryptedPrivateKeyEncoding as SecretKeyEncoding + }); return privateKey; }; From 37014bf3f9b3ffa0a204108aa8f39b179e00b43a Mon Sep 17 00:00:00 2001 From: Daniel Hougaard Date: Tue, 15 Jul 2025 15:39:21 +0400 Subject: [PATCH 26/27] feat(fips): requested changes --- backend/package-lock.json | 2 +- backend/package.json | 2 +- .../20250602155451_fix-secret-versions.ts | 4 +-- .../dynamic-secret/providers/aws-iam.ts | 2 +- .../external-kms/providers/aws-kms.ts | 2 +- .../ee/services/gateway/gateway-service.ts | 28 ++++++++-------- backend/src/ee/services/kmip/kmip-service.ts | 28 ++++++++-------- backend/src/ee/services/scim/scim-service.ts | 2 +- .../bitbucket-secret-scanning-service.ts | 2 +- backend/src/lib/aws/hashing.ts | 4 +-- backend/src/lib/crypto/cache.ts | 2 +- backend/src/lib/crypto/cipher/cipher.ts | 4 +-- backend/src/lib/crypto/cryptography/crypto.ts | 4 +-- backend/src/lib/crypto/sign/signing.ts | 32 +++++++++---------- backend/src/lib/crypto/signing.ts | 4 +-- backend/src/lib/gateway/gateway.ts | 6 ++-- backend/src/lib/red-lock/index.ts | 2 +- backend/src/lib/turn/credentials.ts | 2 +- .../v1/identity-tls-cert-auth-router.ts | 2 +- .../app-connection/app-connection-fns.ts | 2 +- .../app-connection/app-connection-service.ts | 2 +- .../app-connection/aws/aws-connection-fns.ts | 2 +- .../acme/acme-certificate-authority-fns.ts | 4 +-- .../certificate-authority-fns.ts | 12 +++---- .../certificate-authority-queue.ts | 4 +-- .../internal-certificate-authority-fns.ts | 8 ++--- .../internal-certificate-authority-service.ts | 14 ++++---- .../services/certificate/certificate-fns.ts | 4 +-- .../certificate/certificate-service.ts | 8 ++--- .../external-migration-fns.ts | 12 +++---- .../identity-tls-cert-auth-service.ts | 4 +-- .../identity-ua/identity-ua-service.ts | 2 +- .../integration-sync-secret.ts | 4 +-- backend/src/services/kms/kms-service.ts | 4 +-- .../services/telemetry/telemetry-service.ts | 4 +-- backend/src/services/webhook/webhook-fns.ts | 5 ++- 36 files changed, 115 insertions(+), 114 deletions(-) diff --git a/backend/package-lock.json b/backend/package-lock.json index 460721966..408ff59f0 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -140,7 +140,7 @@ "@babel/preset-react": "^7.24.7", "@smithy/types": "^4.3.1", "@types/bcrypt": "^5.0.2", - "@types/crypto-js": "^4.2.2", + "@types/crypto-js": "4.2.2", "@types/jmespath": "^0.15.2", "@types/jsonwebtoken": "^9.0.5", "@types/jsrp": "^0.2.6", diff --git a/backend/package.json b/backend/package.json index 2ba9aca52..b1aedbedd 100644 --- a/backend/package.json +++ b/backend/package.json @@ -86,7 +86,7 @@ "@babel/preset-react": "^7.24.7", "@smithy/types": "^4.3.1", "@types/bcrypt": "^5.0.2", - "@types/crypto-js": "^4.2.2", + "@types/crypto-js": "4.2.2", "@types/jmespath": "^0.15.2", "@types/jsonwebtoken": "^9.0.5", "@types/jsrp": "^0.2.6", diff --git a/backend/src/db/migrations/20250602155451_fix-secret-versions.ts b/backend/src/db/migrations/20250602155451_fix-secret-versions.ts index 94be8dad4..f525e85f3 100644 --- a/backend/src/db/migrations/20250602155451_fix-secret-versions.ts +++ b/backend/src/db/migrations/20250602155451_fix-secret-versions.ts @@ -3,12 +3,11 @@ import { Knex } from "knex"; import { chunkArray } from "@app/lib/fn"; import { selectAllTableCols } from "@app/lib/knex"; -import { initLogger, logger } from "@app/lib/logger"; +import { logger } from "@app/lib/logger"; import { SecretType, TableName } from "../schemas"; export async function up(knex: Knex): Promise { - initLogger(); logger.info("Starting secret version fix migration"); // Get all shared secret IDs first to optimize versions query @@ -134,7 +133,6 @@ export async function up(knex: Knex): Promise { } export async function down(): Promise { - initLogger(); logger.info("Rollback not implemented for secret version fix migration"); // Note: Rolling back this migration would be complex and potentially destructive // as it would require tracking which version entries were added 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 377e58d33..7bb11b9ae 100644 --- a/backend/src/ee/services/dynamic-secret/providers/aws-iam.ts +++ b/backend/src/ee/services/dynamic-secret/providers/aws-iam.ts @@ -63,7 +63,7 @@ export const AwsIamProvider = (): TDynamicProviderFns => { const command = new AssumeRoleCommand({ RoleArn: providerInputs.roleArn, - RoleSessionName: `infisical-dynamic-secret-${crypto.rawCrypto.randomUUID()}`, + RoleSessionName: `infisical-dynamic-secret-${crypto.nativeCrypto.randomUUID()}`, DurationSeconds: 900, // 15 mins ExternalId: projectId }); 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 f9812229f..2c248992f 100644 --- a/backend/src/ee/services/external-kms/providers/aws-kms.ts +++ b/backend/src/ee/services/external-kms/providers/aws-kms.ts @@ -16,7 +16,7 @@ const getAwsKmsClient = async (providerInputs: TExternalKmsAwsSchema) => { }); const command = new AssumeRoleCommand({ RoleArn: awsCredential.assumeRoleArn, - RoleSessionName: `infisical-kms-${crypto.rawCrypto.randomUUID()}`, + RoleSessionName: `infisical-kms-${crypto.nativeCrypto.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 118bff9bd..762be864d 100644 --- a/backend/src/ee/services/gateway/gateway-service.ts +++ b/backend/src/ee/services/gateway/gateway-service.ts @@ -148,9 +148,9 @@ export const gatewayServiceFactory = ({ const alg = keyAlgorithmToAlgCfg(CertKeyAlgorithm.RSA_2048); // generate root CA - const rootCaKeys = await crypto.rawCrypto.subtle.generateKey(alg, true, ["sign", "verify"]); + const rootCaKeys = await crypto.nativeCrypto.subtle.generateKey(alg, true, ["sign", "verify"]); const rootCaSerialNumber = createSerialNumber(); - const rootCaSkObj = crypto.rawCrypto.KeyObject.from(rootCaKeys.privateKey); + const rootCaSkObj = crypto.nativeCrypto.KeyObject.from(rootCaKeys.privateKey); const rootCaIssuedAt = new Date(); const rootCaKeyAlgorithm = CertKeyAlgorithm.RSA_2048; const rootCaExpiration = new Date(new Date().setFullYear(2045)); @@ -172,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.rawCrypto.subtle.generateKey(alg, true, ["sign", "verify"]); - const clientCaSkObj = crypto.rawCrypto.KeyObject.from(clientCaKeys.privateKey); + const clientCaKeys = await crypto.nativeCrypto.subtle.generateKey(alg, true, ["sign", "verify"]); + const clientCaSkObj = crypto.nativeCrypto.KeyObject.from(clientCaKeys.privateKey); const clientCaCert = await x509.X509CertificateGenerator.create({ serialNumber: clientCaSerialNumber, @@ -199,7 +199,7 @@ export const gatewayServiceFactory = ({ ] }); - const clientKeys = await crypto.rawCrypto.subtle.generateKey(alg, true, ["sign", "verify"]); + const clientKeys = await crypto.nativeCrypto.subtle.generateKey(alg, true, ["sign", "verify"]); const clientCertSerialNumber = createSerialNumber(); const clientCert = await x509.X509CertificateGenerator.create({ serialNumber: clientCertSerialNumber, @@ -225,14 +225,14 @@ export const gatewayServiceFactory = ({ new x509.ExtendedKeyUsageExtension([x509.ExtendedKeyUsage[CertExtendedKeyUsage.CLIENT_AUTH]], true) ] }); - const clientSkObj = crypto.rawCrypto.KeyObject.from(clientKeys.privateKey); + const clientSkObj = crypto.nativeCrypto.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.rawCrypto.subtle.generateKey(alg, true, ["sign", "verify"]); - const gatewayCaSkObj = crypto.rawCrypto.KeyObject.from(gatewayCaKeys.privateKey); + const gatewayCaKeys = await crypto.nativeCrypto.subtle.generateKey(alg, true, ["sign", "verify"]); + const gatewayCaSkObj = crypto.nativeCrypto.KeyObject.from(gatewayCaKeys.privateKey); const gatewayCaCert = await x509.X509CertificateGenerator.create({ serialNumber: gatewayCaSerialNumber, subject: `O=${identityOrg},CN=Gateway CA`, @@ -325,7 +325,7 @@ export const gatewayServiceFactory = ({ ); const gatewayCaAlg = keyAlgorithmToAlgCfg(orgGatewayConfig.rootCaKeyAlgorithm as CertKeyAlgorithm); - const gatewayCaSkObj = crypto.rawCrypto.createPrivateKey({ + const gatewayCaSkObj = crypto.nativeCrypto.createPrivateKey({ key: orgKmsDecryptor({ cipherTextBlob: orgGatewayConfig.encryptedGatewayCaPrivateKey }), format: "der", type: "pkcs8" @@ -336,7 +336,7 @@ export const gatewayServiceFactory = ({ }) ); - const gatewayCaPrivateKey = await crypto.rawCrypto.subtle.importKey( + const gatewayCaPrivateKey = await crypto.nativeCrypto.subtle.importKey( "pkcs8", gatewayCaSkObj.export({ format: "der", type: "pkcs8" }), gatewayCaAlg, @@ -345,7 +345,7 @@ export const gatewayServiceFactory = ({ ); const alg = keyAlgorithmToAlgCfg(CertKeyAlgorithm.RSA_2048); - const gatewayKeys = await crypto.rawCrypto.subtle.generateKey(alg, true, ["sign", "verify"]); + const gatewayKeys = await crypto.nativeCrypto.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)); @@ -366,7 +366,7 @@ export const gatewayServiceFactory = ({ ]; const serialNumber = createSerialNumber(); - const privateKey = crypto.rawCrypto.KeyObject.from(gatewayKeys.privateKey); + const privateKey = crypto.nativeCrypto.KeyObject.from(gatewayKeys.privateKey); const gatewayCertificate = await x509.X509CertificateGenerator.create({ serialNumber, subject: `CN=${identityId},O=${identityOrg},OU=Gateway`, @@ -453,7 +453,7 @@ export const gatewayServiceFactory = ({ }) ); - const privateKey = crypto.rawCrypto + const privateKey = crypto.nativeCrypto .createPrivateKey({ key: orgKmsDecryptor({ cipherTextBlob: orgGatewayConfig.encryptedClientPrivateKey }), format: "der", @@ -587,7 +587,7 @@ export const gatewayServiceFactory = ({ }) ); - const clientSkObj = crypto.rawCrypto.createPrivateKey({ + const clientSkObj = crypto.nativeCrypto.createPrivateKey({ key: orgKmsDecryptor({ cipherTextBlob: orgGatewayConfig.encryptedClientPrivateKey }), format: "der", type: "pkcs8" diff --git a/backend/src/ee/services/kmip/kmip-service.ts b/backend/src/ee/services/kmip/kmip-service.ts index c09843120..992b31017 100644 --- a/backend/src/ee/services/kmip/kmip-service.ts +++ b/backend/src/ee/services/kmip/kmip-service.ts @@ -298,7 +298,7 @@ export const kmipServiceFactory = ({ } const alg = keyAlgorithmToAlgCfg(keyAlgorithm); - const leafKeys = await crypto.rawCrypto.subtle.generateKey(alg, true, ["sign", "verify"]); + const leafKeys = await crypto.nativeCrypto.subtle.generateKey(alg, true, ["sign", "verify"]); const extensions: x509.Extension[] = [ new x509.BasicConstraintsExtension(false), @@ -317,13 +317,13 @@ export const kmipServiceFactory = ({ const caAlg = keyAlgorithmToAlgCfg(kmipConfig.caKeyAlgorithm as CertKeyAlgorithm); - const caSkObj = crypto.rawCrypto.createPrivateKey({ + const caSkObj = crypto.nativeCrypto.createPrivateKey({ key: decryptor({ cipherTextBlob: kmipConfig.encryptedClientIntermediateCaPrivateKey }), format: "der", type: "pkcs8" }); - const caPrivateKey = await crypto.rawCrypto.subtle.importKey( + const caPrivateKey = await crypto.nativeCrypto.subtle.importKey( "pkcs8", caSkObj.export({ format: "der", type: "pkcs8" }), caAlg, @@ -344,7 +344,7 @@ export const kmipServiceFactory = ({ extensions }); - const skLeafObj = crypto.rawCrypto.KeyObject.from(leafKeys.privateKey); + const skLeafObj = crypto.nativeCrypto.KeyObject.from(leafKeys.privateKey); const rootCaCert = new x509.X509Certificate(decryptor({ cipherTextBlob: kmipConfig.encryptedRootCaCertificate })); const serverIntermediateCaCert = new x509.X509Certificate( @@ -423,8 +423,8 @@ export const kmipServiceFactory = ({ // generate root CA const rootCaSerialNumber = createSerialNumber(); - const rootCaKeys = await crypto.rawCrypto.subtle.generateKey(alg, true, ["sign", "verify"]); - const rootCaSkObj = crypto.rawCrypto.KeyObject.from(rootCaKeys.privateKey); + const rootCaKeys = await crypto.nativeCrypto.subtle.generateKey(alg, true, ["sign", "verify"]); + const rootCaSkObj = crypto.nativeCrypto.KeyObject.from(rootCaKeys.privateKey); const rootCaIssuedAt = new Date(); const rootCaExpiration = new Date(new Date().setFullYear(new Date().getFullYear() + 20)); @@ -446,8 +446,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.rawCrypto.subtle.generateKey(alg, true, ["sign", "verify"]); - const serverIntermediateCaSkObj = crypto.rawCrypto.KeyObject.from(serverIntermediateCaKeys.privateKey); + const serverIntermediateCaKeys = await crypto.nativeCrypto.subtle.generateKey(alg, true, ["sign", "verify"]); + const serverIntermediateCaSkObj = crypto.nativeCrypto.KeyObject.from(serverIntermediateCaKeys.privateKey); const serverIntermediateCaCert = await x509.X509CertificateGenerator.create({ serialNumber: serverIntermediateCaSerialNumber, @@ -477,8 +477,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.rawCrypto.subtle.generateKey(alg, true, ["sign", "verify"]); - const clientIntermediateCaSkObj = crypto.rawCrypto.KeyObject.from(clientIntermediateCaKeys.privateKey); + const clientIntermediateCaKeys = await crypto.nativeCrypto.subtle.generateKey(alg, true, ["sign", "verify"]); + const clientIntermediateCaSkObj = crypto.nativeCrypto.KeyObject.from(clientIntermediateCaKeys.privateKey); const clientIntermediateCaCert = await x509.X509CertificateGenerator.create({ serialNumber: clientIntermediateCaSerialNumber, @@ -644,7 +644,7 @@ export const kmipServiceFactory = ({ const alg = keyAlgorithmToAlgCfg(keyAlgorithm); - const leafKeys = await crypto.rawCrypto.subtle.generateKey(alg, true, ["sign", "verify"]); + const leafKeys = await crypto.nativeCrypto.subtle.generateKey(alg, true, ["sign", "verify"]); const extensions: x509.Extension[] = [ new x509.BasicConstraintsExtension(false), @@ -692,13 +692,13 @@ export const kmipServiceFactory = ({ cipherTextBlob: kmipOrgConfig.encryptedServerIntermediateCaChain }).toString("utf-8"); - const caSkObj = crypto.rawCrypto.createPrivateKey({ + const caSkObj = crypto.nativeCrypto.createPrivateKey({ key: decryptor({ cipherTextBlob: kmipOrgConfig.encryptedServerIntermediateCaPrivateKey }), format: "der", type: "pkcs8" }); - const caPrivateKey = await crypto.rawCrypto.subtle.importKey( + const caPrivateKey = await crypto.nativeCrypto.subtle.importKey( "pkcs8", caSkObj.export({ format: "der", type: "pkcs8" }), caAlg, @@ -719,7 +719,7 @@ export const kmipServiceFactory = ({ extensions }); - const skLeafObj = crypto.rawCrypto.KeyObject.from(leafKeys.privateKey); + const skLeafObj = crypto.nativeCrypto.KeyObject.from(leafKeys.privateKey); const certificateChain = `${caCertObj.toString("pem")}\n${decryptedCaCertChain}`.trim(); await kmipOrgServerCertificateDAL.create({ diff --git a/backend/src/ee/services/scim/scim-service.ts b/backend/src/ee/services/scim/scim-service.ts index fc05cd2ce..ecb900e53 100644 --- a/backend/src/ee/services/scim/scim-service.ts +++ b/backend/src/ee/services/scim/scim-service.ts @@ -8,8 +8,8 @@ import { addUsersToGroupByUserIds, removeUsersFromGroupByUserIds } from "@app/ee import { TUserGroupMembershipDALFactory } from "@app/ee/services/group/user-group-membership-dal"; import { TScimDALFactory } from "@app/ee/services/scim/scim-dal"; import { getConfig } from "@app/lib/config/env"; -import { BadRequestError, NotFoundError, ScimRequestError, UnauthorizedError } from "@app/lib/errors"; import { crypto } from "@app/lib/crypto"; +import { BadRequestError, NotFoundError, ScimRequestError, UnauthorizedError } from "@app/lib/errors"; import { alphaNumericNanoId } from "@app/lib/nanoid"; import { AuthTokenType } from "@app/services/auth/auth-type"; import { TExternalGroupOrgRoleMappingDALFactory } from "@app/services/external-group-org-role-mapping/external-group-org-role-mapping-dal"; diff --git a/backend/src/ee/services/secret-scanning-v2/bitbucket/bitbucket-secret-scanning-service.ts b/backend/src/ee/services/secret-scanning-v2/bitbucket/bitbucket-secret-scanning-service.ts index 24b017d98..258fb8ac0 100644 --- a/backend/src/ee/services/secret-scanning-v2/bitbucket/bitbucket-secret-scanning-service.ts +++ b/backend/src/ee/services/secret-scanning-v2/bitbucket/bitbucket-secret-scanning-service.ts @@ -66,7 +66,7 @@ export const bitbucketSecretScanningService = ( const credentials = JSON.parse(decryptedCredentials.toString()) as TBitbucketDataSourceCredentials; - const hmac = crypto.rawCrypto.createHmac("sha256", credentials.webhookSecret); + const hmac = crypto.nativeCrypto.createHmac("sha256", credentials.webhookSecret); hmac.update(bodyString); const calculatedSignature = hmac.digest("hex"); diff --git a/backend/src/lib/aws/hashing.ts b/backend/src/lib/aws/hashing.ts index 5ad9d681d..f30f05169 100644 --- a/backend/src/lib/aws/hashing.ts +++ b/backend/src/lib/aws/hashing.ts @@ -27,9 +27,9 @@ export class CustomAWSHasher { } else if (ArrayBuffer.isView && ArrayBuffer.isView(this.secret)) { secretBuffer = Buffer.from(this.secret.buffer, this.secret.byteOffset, this.secret.byteLength); } - this._hash = crypto.rawCrypto.createHmac(this.algorithmIdentifier, secretBuffer); + this._hash = crypto.nativeCrypto.createHmac(this.algorithmIdentifier, secretBuffer); } else { - this._hash = crypto.rawCrypto.createHash(this.algorithmIdentifier); + this._hash = crypto.nativeCrypto.createHash(this.algorithmIdentifier); } return this; } diff --git a/backend/src/lib/crypto/cache.ts b/backend/src/lib/crypto/cache.ts index 7e341a59e..9c6f76aac 100644 --- a/backend/src/lib/crypto/cache.ts +++ b/backend/src/lib/crypto/cache.ts @@ -1,7 +1,7 @@ import { crypto } from "@app/lib/crypto/cryptography"; export const generateCacheKeyFromData = (data: unknown) => - crypto.rawCrypto + crypto.nativeCrypto .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 ca5890f87..bdd6a0674 100644 --- a/backend/src/lib/crypto/cipher/cipher.ts +++ b/backend/src/lib/crypto/cipher/cipher.ts @@ -11,7 +11,7 @@ export const symmetricCipherService = ( ): TSymmetricEncryptionFns => { const encrypt = (text: Buffer, key: Buffer) => { const iv = crypto.randomBytes(IV_LENGTH); - const cipher = crypto.rawCrypto.createCipheriv(type, key, iv); + const cipher = crypto.nativeCrypto.createCipheriv(type, key, iv); let encrypted = cipher.update(text); encrypted = Buffer.concat([encrypted, cipher.final()]); @@ -30,7 +30,7 @@ export const symmetricCipherService = ( const tag = ciphertextBlob.subarray(-TAG_LENGTH); const encrypted = ciphertextBlob.subarray(IV_LENGTH, -TAG_LENGTH); - const decipher = crypto.rawCrypto.createDecipheriv(type, key, iv); + const decipher = crypto.nativeCrypto.createDecipheriv(type, key, iv); decipher.setAuthTag(tag); const decrypted = Buffer.concat([decipher.update(encrypted), decipher.final()]); diff --git a/backend/src/lib/crypto/cryptography/crypto.ts b/backend/src/lib/crypto/cryptography/crypto.ts index 30f00fa4b..c9f5a7bc7 100644 --- a/backend/src/lib/crypto/cryptography/crypto.ts +++ b/backend/src/lib/crypto/cryptography/crypto.ts @@ -160,7 +160,7 @@ const cryptographyFactory = () => { } if (process.env.FIPS_ENABLED !== "true") { - logger.info("[FIPS]: Instance is running in non-FIPS mode."); + logger.info("Cryptography module initialized in normal operation mode."); $setFipsModeEnabled(false); return false; } @@ -412,7 +412,7 @@ const cryptographyFactory = () => { jwt, randomBytes: crypto.randomBytes, randomInt: crypto.randomInt, - rawCrypto: { + nativeCrypto: { createHash: crypto.createHash, createHmac: crypto.createHmac, sign: crypto.sign, diff --git a/backend/src/lib/crypto/sign/signing.ts b/backend/src/lib/crypto/sign/signing.ts index 1864d4be5..474f200f8 100644 --- a/backend/src/lib/crypto/sign/signing.ts +++ b/backend/src/lib/crypto/sign/signing.ts @@ -43,19 +43,19 @@ export const signingService = (algorithm: AsymmetricKeyAlgorithm): TAsymmetricSi case SigningAlgorithm.RSASSA_PSS_SHA_512: return { hashAlgorithm: SupportedHashAlgorithm.SHA512, - padding: crypto.rawCrypto.constants.RSA_PKCS1_PSS_PADDING, + padding: crypto.nativeCrypto.constants.RSA_PKCS1_PSS_PADDING, saltLength: SHA512_DIGEST_LENGTH }; case SigningAlgorithm.RSASSA_PSS_SHA_256: return { hashAlgorithm: SupportedHashAlgorithm.SHA256, - padding: crypto.rawCrypto.constants.RSA_PKCS1_PSS_PADDING, + padding: crypto.nativeCrypto.constants.RSA_PKCS1_PSS_PADDING, saltLength: SHA256_DIGEST_LENGTH }; case SigningAlgorithm.RSASSA_PSS_SHA_384: return { hashAlgorithm: SupportedHashAlgorithm.SHA384, - padding: crypto.rawCrypto.constants.RSA_PKCS1_PSS_PADDING, + padding: crypto.nativeCrypto.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.rawCrypto.constants.RSA_PKCS1_PADDING + padding: crypto.nativeCrypto.constants.RSA_PKCS1_PADDING }; case SigningAlgorithm.RSASSA_PKCS1_V1_5_SHA_384: return { hashAlgorithm: SupportedHashAlgorithm.SHA384, - padding: crypto.rawCrypto.constants.RSA_PKCS1_PADDING + padding: crypto.nativeCrypto.constants.RSA_PKCS1_PADDING }; case SigningAlgorithm.RSASSA_PKCS1_V1_5_SHA_256: return { hashAlgorithm: SupportedHashAlgorithm.SHA256, - padding: crypto.rawCrypto.constants.RSA_PKCS1_PADDING + padding: crypto.nativeCrypto.constants.RSA_PKCS1_PADDING }; // ECDSA @@ -389,7 +389,7 @@ export const signingService = (algorithm: AsymmetricKeyAlgorithm): TAsymmetricSi return signature; } - const privateKeyObject = crypto.rawCrypto.createPrivateKey({ + const privateKeyObject = crypto.nativeCrypto.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.rawCrypto.createSign(hashAlgorithm); + const signer = crypto.nativeCrypto.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.rawCrypto.createSign(hashAlgorithm); + const signer = crypto.nativeCrypto.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.rawCrypto.createPublicKey({ + const publicKeyObject = crypto.nativeCrypto.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.rawCrypto.createVerify(hashAlgorithm); + const verifier = crypto.nativeCrypto.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.rawCrypto.createVerify(hashAlgorithm); + const verifier = crypto.nativeCrypto.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.rawCrypto.generateKeyPair( + crypto.nativeCrypto.generateKeyPair( "rsa", { modulusLength: Number(algorithm.split("_")[1]), @@ -517,7 +517,7 @@ export const signingService = (algorithm: AsymmetricKeyAlgorithm): TAsymmetricSi } else { const { full: namedCurve } = $getEcCurveName(algorithm); - crypto.rawCrypto.generateKeyPair( + crypto.nativeCrypto.generateKeyPair( "ec", { namedCurve, @@ -541,13 +541,13 @@ export const signingService = (algorithm: AsymmetricKeyAlgorithm): TAsymmetricSi }; const getPublicKeyFromPrivateKey = (privateKey: Buffer) => { - const privateKeyObj = crypto.rawCrypto.createPrivateKey({ + const privateKeyObj = crypto.nativeCrypto.createPrivateKey({ key: privateKey, format: "pem", type: "pkcs8" }); - const publicKey = crypto.rawCrypto.createPublicKey(privateKeyObj).export({ + const publicKey = crypto.nativeCrypto.createPublicKey(privateKeyObj).export({ type: "spki", format: "der" }); diff --git a/backend/src/lib/crypto/signing.ts b/backend/src/lib/crypto/signing.ts index a97fe8020..ea32ac9dc 100644 --- a/backend/src/lib/crypto/signing.ts +++ b/backend/src/lib/crypto/signing.ts @@ -5,7 +5,7 @@ import path from "path"; import { crypto } from "./cryptography"; export const verifySignature = (data: string, signature: Buffer, publicKey: KeyObject) => { - const verify = crypto.rawCrypto.createVerify("SHA256"); + const verify = crypto.nativeCrypto.createVerify("SHA256"); verify.update(data); verify.end(); return verify.verify(publicKey, signature); @@ -14,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.rawCrypto.createPublicKey({ + const publicKey = crypto.nativeCrypto.createPublicKey({ key: publicKeyPem, format: "pem", type: "pkcs1" diff --git a/backend/src/lib/gateway/gateway.ts b/backend/src/lib/gateway/gateway.ts index 711fee4a0..6bc6e204e 100644 --- a/backend/src/lib/gateway/gateway.ts +++ b/backend/src/lib/gateway/gateway.ts @@ -49,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.rawCrypto.X509Certificate(Buffer.from(certs[0])); - const caCertificate = new crypto.rawCrypto.X509Certificate(tlsOptions.ca); + const serverCertificate = new crypto.nativeCrypto.X509Certificate(Buffer.from(certs[0])); + const caCertificate = new crypto.nativeCrypto.X509Certificate(tlsOptions.ca); const isValidServerCertificate = serverCertificate.verify(caCertificate.publicKey); if (!isValidServerCertificate) return quic.native.CryptoError.BadCertificate; @@ -73,7 +73,7 @@ const createQuicConnection = async ( crypto: { ops: { randomBytes: async (data) => { - crypto.rawCrypto.getRandomValues(new Uint8Array(data)); + crypto.nativeCrypto.getRandomValues(new Uint8Array(data)); } } } diff --git a/backend/src/lib/red-lock/index.ts b/backend/src/lib/red-lock/index.ts index 64a48fd0c..7db17aa87 100644 --- a/backend/src/lib/red-lock/index.ts +++ b/backend/src/lib/red-lock/index.ts @@ -251,7 +251,7 @@ export class Redlock extends EventEmitter { * Generate a sha1 hash compatible with redis evalsha. */ private _hash(value: string): string { - return crypto.rawCrypto.createHash("sha1").update(value).digest("hex"); + return crypto.nativeCrypto.createHash("sha1").update(value).digest("hex"); } /** diff --git a/backend/src/lib/turn/credentials.ts b/backend/src/lib/turn/credentials.ts index a41624b06..34c30ac8f 100644 --- a/backend/src/lib/turn/credentials.ts +++ b/backend/src/lib/turn/credentials.ts @@ -5,7 +5,7 @@ export const getTurnCredentials = (id: string, authSecret: string, ttl = TURN_TO const timestamp = Math.floor((Date.now() + ttl) / 1000); const username = `${timestamp}:${id}`; - const hmac = crypto.rawCrypto.createHmac("sha1", authSecret); + const hmac = crypto.nativeCrypto.createHmac("sha1", authSecret); hmac.update(username); const password = hmac.digest("base64"); 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 cecd7f2db..0bb9e08ea 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 @@ -27,7 +27,7 @@ const validateCaCertificate = (caCert: string) => { if (!caCert) return true; try { // eslint-disable-next-line no-new - new crypto.rawCrypto.X509Certificate(caCert); + new crypto.nativeCrypto.X509Certificate(caCert); return true; } catch { return false; diff --git a/backend/src/services/app-connection/app-connection-fns.ts b/backend/src/services/app-connection/app-connection-fns.ts index 842931c79..9dadcc4e5 100644 --- a/backend/src/services/app-connection/app-connection-fns.ts +++ b/backend/src/services/app-connection/app-connection-fns.ts @@ -305,7 +305,7 @@ export const decryptAppConnection = async ( orgId: appConnection.orgId, kmsService }), - credentialsHash: crypto.rawCrypto.createHash("sha256").update(appConnection.encryptedCredentials).digest("hex") + credentialsHash: crypto.nativeCrypto.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 bc95c30e6..fdb861b95 100644 --- a/backend/src/services/app-connection/app-connection-service.ts +++ b/backend/src/services/app-connection/app-connection-service.ts @@ -281,7 +281,7 @@ export const appConnectionServiceFactory = ({ return { ...connection, - credentialsHash: crypto.rawCrypto.createHash("sha256").update(connection.encryptedCredentials).digest("hex"), + credentialsHash: crypto.nativeCrypto.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 c647dc2c2..82a53a1f5 100644 --- a/backend/src/services/app-connection/aws/aws-connection-fns.ts +++ b/backend/src/services/app-connection/aws/aws-connection-fns.ts @@ -49,7 +49,7 @@ export const getAwsConnectionConfig = async (appConnection: TAwsConnectionConfig const command = new AssumeRoleCommand({ RoleArn: credentials.roleArn, - RoleSessionName: `infisical-app-connection-${crypto.rawCrypto.randomUUID()}`, + RoleSessionName: `infisical-app-connection-${crypto.nativeCrypto.randomUUID()}`, DurationSeconds: 900, // 15 mins ExternalId: orgId }); 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 d7893779c..f6e77ac8e 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 @@ -414,8 +414,8 @@ export const AcmeCertificateAuthorityFns = ({ const alg = keyAlgorithmToAlgCfg(CertKeyAlgorithm.RSA_2048); - const leafKeys = await crypto.rawCrypto.subtle.generateKey(alg, true, ["sign", "verify"]); - const skLeafObj = crypto.rawCrypto.KeyObject.from(leafKeys.privateKey); + const leafKeys = await crypto.nativeCrypto.subtle.generateKey(alg, true, ["sign", "verify"]); + const skLeafObj = crypto.nativeCrypto.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 456d81d93..9991e462e 100644 --- a/backend/src/services/certificate-authority/certificate-authority-fns.ts +++ b/backend/src/services/certificate-authority/certificate-authority-fns.ts @@ -133,8 +133,8 @@ export const getCaCredentials = async ({ }); const alg = keyAlgorithmToAlgCfg(ca.internalCa.keyAlgorithm as CertKeyAlgorithm); - const skObj = crypto.rawCrypto.createPrivateKey({ key: decryptedPrivateKey, format: "der", type: "pkcs8" }); - const caPrivateKey = await crypto.rawCrypto.subtle.importKey( + const skObj = crypto.nativeCrypto.createPrivateKey({ key: decryptedPrivateKey, format: "der", type: "pkcs8" }); + const caPrivateKey = await crypto.nativeCrypto.subtle.importKey( "pkcs8", skObj.export({ format: "der", type: "pkcs8" }), alg, @@ -142,8 +142,8 @@ export const getCaCredentials = async ({ ["sign"] ); - const pkObj = crypto.rawCrypto.createPublicKey(skObj); - const caPublicKey = await crypto.rawCrypto.subtle.importKey( + const pkObj = crypto.nativeCrypto.createPublicKey(skObj); + const caPublicKey = await crypto.nativeCrypto.subtle.importKey( "spki", pkObj.export({ format: "der", type: "spki" }), alg, @@ -281,8 +281,8 @@ export const rebuildCaCrl = async ({ cipherTextBlob: caSecret.encryptedPrivateKey }); - const skObj = crypto.rawCrypto.createPrivateKey({ key: privateKey, format: "der", type: "pkcs8" }); - const sk = await crypto.rawCrypto.subtle.importKey( + const skObj = crypto.nativeCrypto.createPrivateKey({ key: privateKey, format: "der", type: "pkcs8" }); + const sk = await crypto.nativeCrypto.subtle.importKey( "pkcs8", skObj.export({ format: "der", type: "pkcs8" }), alg, diff --git a/backend/src/services/certificate-authority/certificate-authority-queue.ts b/backend/src/services/certificate-authority/certificate-authority-queue.ts index 32471a0e3..afe17ec5f 100644 --- a/backend/src/services/certificate-authority/certificate-authority-queue.ts +++ b/backend/src/services/certificate-authority/certificate-authority-queue.ts @@ -198,8 +198,8 @@ export const certificateAuthorityQueueFactory = ({ cipherTextBlob: caSecret.encryptedPrivateKey }); - const skObj = crypto.rawCrypto.createPrivateKey({ key: privateKey, format: "der", type: "pkcs8" }); - const sk = await crypto.rawCrypto.subtle.importKey( + const skObj = crypto.nativeCrypto.createPrivateKey({ key: privateKey, format: "der", type: "pkcs8" }); + const sk = await crypto.nativeCrypto.subtle.importKey( "pkcs8", skObj.export({ format: "der", type: "pkcs8" }), alg, 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 13084c5a8..80d2842fa 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 @@ -99,7 +99,7 @@ export const InternalCertificateAuthorityFns = ({ } const alg = keyAlgorithmToAlgCfg(ca.internalCa.keyAlgorithm as CertKeyAlgorithm); - const leafKeys = await crypto.rawCrypto.subtle.generateKey(alg, true, ["sign", "verify"]); + const leafKeys = await crypto.nativeCrypto.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 = crypto.rawCrypto.KeyObject.from(leafKeys.privateKey); + const skLeafObj = crypto.nativeCrypto.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.rawCrypto.subtle.generateKey(alg, true, ["sign", "verify"]); + const leafKeys = await crypto.nativeCrypto.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 = crypto.rawCrypto.KeyObject.from(leafKeys.privateKey); + const skLeafObj = crypto.nativeCrypto.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 f6370db61..6668c806c 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 @@ -171,7 +171,7 @@ export const internalCertificateAuthorityServiceFactory = ({ }); const alg = keyAlgorithmToAlgCfg(keyAlgorithm); - const keys = await crypto.rawCrypto.subtle.generateKey(alg, true, ["sign", "verify"]); + const keys = await crypto.nativeCrypto.subtle.generateKey(alg, true, ["sign", "verify"]); const newCa = await certificateAuthorityDAL.transaction(async (tx) => { const notBeforeDate = notBefore ? new Date(notBefore) : new Date(); @@ -226,7 +226,7 @@ export const internalCertificateAuthorityServiceFactory = ({ }); // https://nodejs.org/api/crypto.html#static-method-keyobjectfromkey - const skObj = crypto.rawCrypto.KeyObject.from(keys.privateKey); + const skObj = crypto.nativeCrypto.KeyObject.from(keys.privateKey); const { cipherTextBlob: encryptedPrivateKey } = await kmsEncryptor({ plainText: skObj.export({ @@ -1102,9 +1102,9 @@ export const internalCertificateAuthorityServiceFactory = ({ kmsService }); - const isCaAndCertPublicKeySame = Buffer.from(await crypto.rawCrypto.subtle.exportKey("spki", caPublicKey)).equals( - Buffer.from(certObj.publicKey.rawData) - ); + const isCaAndCertPublicKeySame = Buffer.from( + await crypto.nativeCrypto.subtle.exportKey("spki", caPublicKey) + ).equals(Buffer.from(certObj.publicKey.rawData)); if (!isCaAndCertPublicKeySame) { throw new BadRequestError({ message: "CA and certificate public key do not match" }); @@ -1265,7 +1265,7 @@ export const internalCertificateAuthorityServiceFactory = ({ } const alg = keyAlgorithmToAlgCfg(ca.internalCa.keyAlgorithm as CertKeyAlgorithm); - const leafKeys = await crypto.rawCrypto.subtle.generateKey(alg, true, ["sign", "verify"]); + const leafKeys = await crypto.nativeCrypto.subtle.generateKey(alg, true, ["sign", "verify"]); const csrObj = await x509.Pkcs10CertificateRequestGenerator.create({ name: `CN=${commonName}`, @@ -1412,7 +1412,7 @@ export const internalCertificateAuthorityServiceFactory = ({ extensions }); - const skLeafObj = crypto.rawCrypto.KeyObject.from(leafKeys.privateKey); + const skLeafObj = crypto.nativeCrypto.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/certificate-fns.ts b/backend/src/services/certificate/certificate-fns.ts index 652974584..eee220ce9 100644 --- a/backend/src/services/certificate/certificate-fns.ts +++ b/backend/src/services/certificate/certificate-fns.ts @@ -86,10 +86,10 @@ export const getCertificateCredentials = async ({ }); try { - const skObj = crypto.rawCrypto.createPrivateKey({ key: decryptedPrivateKey, format: "pem", type: "pkcs8" }); + const skObj = crypto.nativeCrypto.createPrivateKey({ key: decryptedPrivateKey, format: "pem", type: "pkcs8" }); const certPrivateKey = skObj.export({ format: "pem", type: "pkcs8" }).toString(); - const pkObj = crypto.rawCrypto.createPublicKey(skObj); + const pkObj = crypto.nativeCrypto.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 753ee0cda..541bddac7 100644 --- a/backend/src/services/certificate/certificate-service.ts +++ b/backend/src/services/certificate/certificate-service.ts @@ -373,16 +373,16 @@ export const certificateServiceFactory = ({ // Verify private key matches the certificate let privateKey; try { - privateKey = crypto.rawCrypto.createPrivateKey(privateKeyPem); + privateKey = crypto.nativeCrypto.createPrivateKey(privateKeyPem); } catch (err) { throw new BadRequestError({ message: "Invalid private key format" }); } try { const message = Buffer.from(Buffer.alloc(32)); - const publicKey = crypto.rawCrypto.createPublicKey(certificatePem); - const signature = crypto.rawCrypto.sign(null, message, privateKey); - const isValid = crypto.rawCrypto.verify(null, message, publicKey, signature); + const publicKey = crypto.nativeCrypto.createPublicKey(certificatePem); + const signature = crypto.nativeCrypto.sign(null, message, privateKey); + const isValid = crypto.nativeCrypto.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 08ee02a2b..8af22d858 100644 --- a/backend/src/services/external-migration/external-migration-fns.ts +++ b/backend/src/services/external-migration/external-migration-fns.ts @@ -228,7 +228,7 @@ export const parseEnvKeyDataFn = async (decryptedJson: string): Promise { // Use SHA-256 hash for consistent distribution - const hash = crypto.rawCrypto.createHash("sha256").update(distinctId).digest("hex"); + const hash = crypto.nativeCrypto.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}-${crypto.rawCrypto.randomUUID()}`; + return `telemetry-event-${event}-${bucketId}-${distinctId}-${crypto.nativeCrypto.randomUUID()}`; }; export const telemetryServiceFactory = ({ keyStore, licenseService }: TTelemetryServiceFactoryDep) => { diff --git a/backend/src/services/webhook/webhook-fns.ts b/backend/src/services/webhook/webhook-fns.ts index 9aaf9b525..d7e07ae28 100644 --- a/backend/src/services/webhook/webhook-fns.ts +++ b/backend/src/services/webhook/webhook-fns.ts @@ -41,7 +41,10 @@ export const triggerWebhookRequest = async ( const payload = { ...data, timestamp: Date.now() }; const { secretKey, url } = decryptWebhookDetails(webhook, decryptor); if (secretKey) { - const webhookSign = crypto.rawCrypto.createHmac("sha256", secretKey).update(JSON.stringify(payload)).digest("hex"); + const webhookSign = crypto.nativeCrypto + .createHmac("sha256", secretKey) + .update(JSON.stringify(payload)) + .digest("hex"); headers["x-infisical-signature"] = `t=${payload.timestamp};${webhookSign}`; } From b621225706e9130533f46cc691d980554f21260d Mon Sep 17 00:00:00 2001 From: Daniel Hougaard Date: Tue, 15 Jul 2025 17:44:17 +0400 Subject: [PATCH 27/27] fix: remove md5 support in fips --- backend/package-lock.json | 15 ----------- backend/package.json | 2 -- .../dynamic-secret/dynamic-secret-service.ts | 7 +++++ backend/src/lib/crypto/cryptography/crypto.ts | 27 +++---------------- 4 files changed, 11 insertions(+), 40 deletions(-) diff --git a/backend/package-lock.json b/backend/package-lock.json index 408ff59f0..a5c106540 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -69,7 +69,6 @@ "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", @@ -140,7 +139,6 @@ "@babel/preset-react": "^7.24.7", "@smithy/types": "^4.3.1", "@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", @@ -13335,13 +13333,6 @@ "@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", @@ -16622,12 +16613,6 @@ "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 b1aedbedd..bd355dd22 100644 --- a/backend/package.json +++ b/backend/package.json @@ -86,7 +86,6 @@ "@babel/preset-react": "^7.24.7", "@smithy/types": "^4.3.1", "@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", @@ -190,7 +189,6 @@ "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/ee/services/dynamic-secret/dynamic-secret-service.ts b/backend/src/ee/services/dynamic-secret/dynamic-secret-service.ts index bfa39f39b..ab59b1a1d 100644 --- a/backend/src/ee/services/dynamic-secret/dynamic-secret-service.ts +++ b/backend/src/ee/services/dynamic-secret/dynamic-secret-service.ts @@ -6,6 +6,7 @@ import { ProjectPermissionDynamicSecretActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission"; +import { crypto } from "@app/lib/crypto"; import { BadRequestError, NotFoundError } from "@app/lib/errors"; import { OrderByDirection } from "@app/lib/types"; import { TKmsServiceFactory } from "@app/services/kms/kms-service"; @@ -92,6 +93,12 @@ export const dynamicSecretServiceFactory = ({ }); } + if (provider.type === DynamicSecretProviders.MongoAtlas && crypto.isFipsModeEnabled()) { + throw new BadRequestError({ + message: "MongoDB Atlas dynamic secret is not supported in FIPS mode of operation" + }); + } + const folder = await folderDAL.findBySecretPath(projectId, environmentSlug, path); if (!folder) { throw new NotFoundError({ message: `Folder with path '${path}' in environment '${environmentSlug}' not found` }); diff --git a/backend/src/lib/crypto/cryptography/crypto.ts b/backend/src/lib/crypto/cryptography/crypto.ts index c9f5a7bc7..9a986efbb 100644 --- a/backend/src/lib/crypto/cryptography/crypto.ts +++ b/backend/src/lib/crypto/cryptography/crypto.ts @@ -4,7 +4,6 @@ import crypto, { subtle } from "node:crypto"; import bcrypt from "bcrypt"; -import cryptoJs from "crypto-js"; import jwtDep from "jsonwebtoken"; import nacl from "tweetnacl"; import naclUtils from "tweetnacl-util"; @@ -74,24 +73,6 @@ export const generateAsymmetricKeyPair = () => { }; }; -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 CryptographyError({ - message: `Invalid digest type: ${digest as string}` - }); - } - - return cryptoJs.MD5(message).toString(encoder); -}; - const cryptographyFactory = () => { let $fipsEnabled = false; let $isInitialized = false; @@ -354,15 +335,15 @@ const cryptographyFactory = () => { 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) => { - // 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 FIPS is enabled, we block MD5 directly. if (isFipsModeEnabled()) { - return computeMd5(message, digest); + throw new CryptographyError({ + message: "MD5 is not supported in FIPS mode of operation" + }); } return crypto.createHash("md5").update(message).digest(digest); };