feat: support password-protected private keys
Encrypted PKCS#8 private keys are decrypted during staging using the provided password. The decrypted key is stored for Traefik (which needs cleartext PEM). Unencrypted keys continue to work without a password. - CertificateManager.stage() accepts optional keyPassword - DockerCertificateManager handles EncryptedPrivateKeyInfo decryption - UI: password field in upload form (vendor CertificatesPage) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
17
ui/src/pages/vendor/CertificatesPage.tsx
vendored
17
ui/src/pages/vendor/CertificatesPage.tsx
vendored
@@ -1,9 +1,10 @@
|
||||
import { useRef } from 'react';
|
||||
import { useRef, useState } from 'react';
|
||||
import {
|
||||
Alert,
|
||||
Badge,
|
||||
Button,
|
||||
Card,
|
||||
Input,
|
||||
Spinner,
|
||||
useToast,
|
||||
} from '@cameleer/design-system';
|
||||
@@ -139,6 +140,7 @@ export function CertificatesPage() {
|
||||
const certInputRef = useRef<HTMLInputElement>(null);
|
||||
const keyInputRef = useRef<HTMLInputElement>(null);
|
||||
const caInputRef = useRef<HTMLInputElement>(null);
|
||||
const [keyPassword, setKeyPassword] = useState('');
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
@@ -172,6 +174,7 @@ export function CertificatesPage() {
|
||||
formData.append('cert', certFile);
|
||||
formData.append('key', keyFile);
|
||||
if (caFile) formData.append('ca', caFile);
|
||||
if (keyPassword) formData.append('password', keyPassword);
|
||||
|
||||
try {
|
||||
const result = await stageMutation.mutateAsync(formData);
|
||||
@@ -180,6 +183,7 @@ export function CertificatesPage() {
|
||||
if (certInputRef.current) certInputRef.current.value = '';
|
||||
if (keyInputRef.current) keyInputRef.current.value = '';
|
||||
if (caInputRef.current) caInputRef.current.value = '';
|
||||
setKeyPassword('');
|
||||
} else {
|
||||
toast({ title: 'Validation failed', description: result.errors.join(', '), variant: 'error' });
|
||||
}
|
||||
@@ -285,6 +289,17 @@ export function CertificatesPage() {
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
|
||||
<FileField label="Certificate (PEM) *" inputRef={certInputRef} accept=".pem,.crt,.cer" />
|
||||
<FileField label="Private Key (PEM) *" inputRef={keyInputRef} accept=".pem,.key" />
|
||||
<div>
|
||||
<label style={{ fontSize: 13, color: 'var(--text-secondary)', display: 'block', marginBottom: 4 }}>
|
||||
Key Password (if encrypted)
|
||||
</label>
|
||||
<Input
|
||||
type="password"
|
||||
placeholder="Leave empty if key is not encrypted"
|
||||
value={keyPassword}
|
||||
onChange={(e) => setKeyPassword(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<FileField label="CA Bundle (PEM, optional)" inputRef={caInputRef} accept=".pem,.crt,.cer" />
|
||||
<Button
|
||||
variant="primary"
|
||||
|
||||
Reference in New Issue
Block a user