mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-20 13:43:37 +00:00
feat: fips inside support (checkpoint)
This commit is contained in:
@@ -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<Errors>({});
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user