Add passphrase support for SSH key generation
- Add optional passphrase input field in key generation container - Implement AES-128-CBC encryption for protected private keys - Auto-fill key password field when passphrase is provided - Support passphrase protection for all key types (Ed25519, ECDSA, RSA) - Enhance user experience with automatic form field population
This commit is contained in:
@@ -11,7 +11,7 @@ import ssh2Pkg from "ssh2";
|
|||||||
const { utils: ssh2Utils } = ssh2Pkg;
|
const { utils: ssh2Utils } = ssh2Pkg;
|
||||||
|
|
||||||
// Direct SSH key generation with ssh2 - the right way
|
// Direct SSH key generation with ssh2 - the right way
|
||||||
function generateSSHKeyPair(keyType: string, keySize?: number): { success: boolean; privateKey?: string; publicKey?: string; error?: string } {
|
function generateSSHKeyPair(keyType: string, keySize?: number, passphrase?: string): { success: boolean; privateKey?: string; publicKey?: string; error?: string } {
|
||||||
console.log('Generating SSH key pair with ssh2:', keyType);
|
console.log('Generating SSH key pair with ssh2:', keyType);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -29,6 +29,12 @@ function generateSSHKeyPair(keyType: string, keySize?: number): { success: boole
|
|||||||
options.bits = 256; // ECDSA P-256 uses 256 bits
|
options.bits = 256; // ECDSA P-256 uses 256 bits
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add passphrase protection if provided
|
||||||
|
if (passphrase && passphrase.trim()) {
|
||||||
|
options.passphrase = passphrase;
|
||||||
|
options.cipher = 'aes128-cbc'; // Default cipher for encrypted private keys
|
||||||
|
}
|
||||||
|
|
||||||
// Use ssh2's native key generation
|
// Use ssh2's native key generation
|
||||||
const keyPair = ssh2Utils.generateKeyPairSync(ssh2Type as any, options);
|
const keyPair = ssh2Utils.generateKeyPairSync(ssh2Type as any, options);
|
||||||
|
|
||||||
@@ -882,8 +888,8 @@ router.post("/generate-key-pair", authenticateJWT, async (req: Request, res: Res
|
|||||||
console.log("Has passphrase:", !!passphrase);
|
console.log("Has passphrase:", !!passphrase);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Generate keys with crypto, convert public key to SSH format
|
// Generate SSH keys directly with ssh2
|
||||||
const result = generateSSHKeyPair(keyType, keySize);
|
const result = generateSSHKeyPair(keyType, keySize, passphrase);
|
||||||
|
|
||||||
if (result.success && result.privateKey && result.publicKey) {
|
if (result.success && result.privateKey && result.publicKey) {
|
||||||
const response = {
|
const response = {
|
||||||
|
|||||||
@@ -376,6 +376,7 @@ export function CredentialEditor({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const [tagInput, setTagInput] = useState("");
|
const [tagInput, setTagInput] = useState("");
|
||||||
|
const [keyGenerationPassphrase, setKeyGenerationPassphrase] = useState("");
|
||||||
|
|
||||||
const [folderDropdownOpen, setFolderDropdownOpen] = useState(false);
|
const [folderDropdownOpen, setFolderDropdownOpen] = useState(false);
|
||||||
const folderInputRef = useRef<HTMLInputElement>(null);
|
const folderInputRef = useRef<HTMLInputElement>(null);
|
||||||
@@ -675,6 +676,23 @@ export function CredentialEditor({
|
|||||||
<FormLabel className="mb-3 font-bold block">
|
<FormLabel className="mb-3 font-bold block">
|
||||||
{t("credentials.generateKeyPair")}
|
{t("credentials.generateKeyPair")}
|
||||||
</FormLabel>
|
</FormLabel>
|
||||||
|
|
||||||
|
{/* Key Generation Passphrase Input */}
|
||||||
|
<div className="mb-3">
|
||||||
|
<FormLabel className="text-sm mb-2 block">
|
||||||
|
{t("credentials.keyPassword")} ({t("credentials.optional")})
|
||||||
|
</FormLabel>
|
||||||
|
<PasswordInput
|
||||||
|
placeholder={t("placeholders.keyPassword")}
|
||||||
|
value={keyGenerationPassphrase}
|
||||||
|
onChange={(e) => setKeyGenerationPassphrase(e.target.value)}
|
||||||
|
className="max-w-xs"
|
||||||
|
/>
|
||||||
|
<div className="text-xs text-muted-foreground mt-1">
|
||||||
|
{t("credentials.keyPassphraseOptional")}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="flex gap-2 flex-wrap">
|
<div className="flex gap-2 flex-wrap">
|
||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
@@ -682,13 +700,16 @@ export function CredentialEditor({
|
|||||||
size="sm"
|
size="sm"
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
try {
|
try {
|
||||||
const keyPassword = form.watch("keyPassword");
|
const result = await generateKeyPair('ssh-ed25519', undefined, keyGenerationPassphrase);
|
||||||
const result = await generateKeyPair('ssh-ed25519', undefined, keyPassword);
|
|
||||||
|
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
form.setValue("key", result.privateKey);
|
form.setValue("key", result.privateKey);
|
||||||
form.setValue("publicKey", result.publicKey);
|
form.setValue("publicKey", result.publicKey);
|
||||||
debouncedKeyDetection(result.privateKey, keyPassword);
|
// Auto-fill the key password field if passphrase was used
|
||||||
|
if (keyGenerationPassphrase) {
|
||||||
|
form.setValue("keyPassword", keyGenerationPassphrase);
|
||||||
|
}
|
||||||
|
debouncedKeyDetection(result.privateKey, keyGenerationPassphrase);
|
||||||
debouncedPublicKeyDetection(result.publicKey);
|
debouncedPublicKeyDetection(result.publicKey);
|
||||||
toast.success(t("credentials.keyPairGeneratedSuccessfully", { keyType: "Ed25519" }));
|
toast.success(t("credentials.keyPairGeneratedSuccessfully", { keyType: "Ed25519" }));
|
||||||
} else {
|
} else {
|
||||||
@@ -708,13 +729,16 @@ export function CredentialEditor({
|
|||||||
size="sm"
|
size="sm"
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
try {
|
try {
|
||||||
const keyPassword = form.watch("keyPassword");
|
const result = await generateKeyPair('ecdsa-sha2-nistp256', undefined, keyGenerationPassphrase);
|
||||||
const result = await generateKeyPair('ecdsa-sha2-nistp256', undefined, keyPassword);
|
|
||||||
|
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
form.setValue("key", result.privateKey);
|
form.setValue("key", result.privateKey);
|
||||||
form.setValue("publicKey", result.publicKey);
|
form.setValue("publicKey", result.publicKey);
|
||||||
debouncedKeyDetection(result.privateKey, keyPassword);
|
// Auto-fill the key password field if passphrase was used
|
||||||
|
if (keyGenerationPassphrase) {
|
||||||
|
form.setValue("keyPassword", keyGenerationPassphrase);
|
||||||
|
}
|
||||||
|
debouncedKeyDetection(result.privateKey, keyGenerationPassphrase);
|
||||||
debouncedPublicKeyDetection(result.publicKey);
|
debouncedPublicKeyDetection(result.publicKey);
|
||||||
toast.success(t("credentials.keyPairGeneratedSuccessfully", { keyType: "ECDSA" }));
|
toast.success(t("credentials.keyPairGeneratedSuccessfully", { keyType: "ECDSA" }));
|
||||||
} else {
|
} else {
|
||||||
@@ -734,13 +758,16 @@ export function CredentialEditor({
|
|||||||
size="sm"
|
size="sm"
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
try {
|
try {
|
||||||
const keyPassword = form.watch("keyPassword");
|
const result = await generateKeyPair('ssh-rsa', 2048, keyGenerationPassphrase);
|
||||||
const result = await generateKeyPair('ssh-rsa', 2048, keyPassword);
|
|
||||||
|
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
form.setValue("key", result.privateKey);
|
form.setValue("key", result.privateKey);
|
||||||
form.setValue("publicKey", result.publicKey);
|
form.setValue("publicKey", result.publicKey);
|
||||||
debouncedKeyDetection(result.privateKey, keyPassword);
|
// Auto-fill the key password field if passphrase was used
|
||||||
|
if (keyGenerationPassphrase) {
|
||||||
|
form.setValue("keyPassword", keyGenerationPassphrase);
|
||||||
|
}
|
||||||
|
debouncedKeyDetection(result.privateKey, keyGenerationPassphrase);
|
||||||
debouncedPublicKeyDetection(result.publicKey);
|
debouncedPublicKeyDetection(result.publicKey);
|
||||||
toast.success(t("credentials.keyPairGeneratedSuccessfully", { keyType: "RSA" }));
|
toast.success(t("credentials.keyPairGeneratedSuccessfully", { keyType: "RSA" }));
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
Reference in New Issue
Block a user