208 if (!key_path || !key_out) {
209 return SET_ERRNO(ERROR_INVALID_PARAM,
"Invalid parameters: key_path=%p, key_out=%p", key_path, key_out);
214 char pub_key_path[BUFFER_SIZE_LARGE];
215 safe_snprintf(pub_key_path,
sizeof(pub_key_path),
"%s.pub", key_path);
219 char pub_line[BUFFER_SIZE_LARGE];
220 if (fgets(pub_line,
sizeof(pub_line), pub_f)) {
221 public_key_t pub_key = {0};
222 pub_key.type = KEY_TYPE_ED25519;
228 log_dev(
"Key found in ssh-agent - using cached key (no password required)");
230 key_out->type = KEY_TYPE_ED25519;
231 key_out->use_ssh_agent =
true;
232 memcpy(key_out->key.ed25519 + 32, pub_key.key, 32);
233 memcpy(key_out->public_key, pub_key.key, 32);
237 log_debug(
"Key not found in ssh-agent - will decrypt from file");
246 if (validation_result != ASCIICHAT_OK) {
247 return validation_result;
253 return SET_ERRNO(ERROR_CRYPTO_KEY,
"Cannot read private key file: %s", key_path);
257 char *file_content = NULL;
258 size_t file_size = 0;
259 char buffer[BUFFER_SIZE_XXLARGE];
262 while ((bytes_read = fread(buffer, 1,
sizeof(buffer), f)) > 0) {
263 file_content = SAFE_REALLOC(file_content, file_size + bytes_read + 1,
char *);
266 return SET_ERRNO(ERROR_CRYPTO_KEY,
"Out of memory reading private key file");
268 memcpy(file_content + file_size, buffer, bytes_read);
269 file_size += bytes_read;
273 if (file_size == 0) {
274 SAFE_FREE(file_content);
275 return SET_ERRNO(ERROR_CRYPTO_KEY,
"Private key file is empty: %s", key_path);
278 file_content[file_size] =
'\0';
281 char *pem_base64_data = NULL;
283 SAFE_FREE(file_content);
284 return SET_ERRNO(ERROR_CRYPTO_KEY,
"Invalid OpenSSH private key format: %s", key_path);
288 char *clean_base64 = SAFE_MALLOC(strlen(pem_base64_data) + 1,
char *);
289 char *clean_ptr = clean_base64;
290 for (
const char *p = pem_base64_data; *p; p++) {
291 if (*p !=
'\n' && *p !=
'\r' && *p !=
' ' && *p !=
'\t') {
296 SAFE_FREE(pem_base64_data);
301 asciichat_error_t decode_result = base64_decode_ssh_key(clean_base64, strlen(clean_base64), &key_blob, &key_blob_len);
302 SAFE_FREE(clean_base64);
304 if (decode_result != ASCIICHAT_OK) {
305 SAFE_FREE(file_content);
306 return decode_result;
314 if (key_blob_len < 4) {
316 SAFE_FREE(file_content);
317 return SET_ERRNO(ERROR_CRYPTO_KEY,
"OpenSSH private key blob too small: %s", key_path);
321 if (memcmp(key_blob,
"openssh-key-v1\0", 15) != 0) {
323 SAFE_FREE(file_content);
324 return SET_ERRNO(ERROR_CRYPTO_KEY,
"Invalid OpenSSH private key magic: %s", key_path);
330 if (offset + 4 > key_blob_len) {
332 SAFE_FREE(file_content);
333 return SET_ERRNO(ERROR_CRYPTO_KEY,
"OpenSSH private key truncated at ciphername: %s", key_path);
336 uint32_t ciphername_len = read_u32_be(&key_blob[offset]);
339 if (offset + ciphername_len > key_blob_len) {
341 SAFE_FREE(file_content);
342 return SET_ERRNO(ERROR_CRYPTO_KEY,
"OpenSSH private key truncated at ciphername data: %s", key_path);
346 size_t ciphername_pos = offset;
349 bool is_encrypted = (ciphername_len > 0 && memcmp(key_blob + offset,
"none", 4) != 0);
351 offset += ciphername_len;
354 if (offset + 4 > key_blob_len) {
356 SAFE_FREE(file_content);
357 return SET_ERRNO(ERROR_CRYPTO_KEY,
"OpenSSH private key truncated at kdfname: %s", key_path);
360 uint32_t kdfname_len = read_u32_be(&key_blob[offset]);
364 size_t kdfname_pos = offset;
366 offset += kdfname_len;
368 if (offset + 4 > key_blob_len) {
370 SAFE_FREE(file_content);
371 return SET_ERRNO(ERROR_CRYPTO_KEY,
"OpenSSH private key truncated at kdfoptions: %s", key_path);
374 uint32_t kdfoptions_len = read_u32_be(&key_blob[offset]);
375 offset += 4 + kdfoptions_len;
380 char ciphername[32] = {0};
381 if (ciphername_len > 0 && ciphername_len <
sizeof(ciphername)) {
382 memcpy(ciphername, key_blob + ciphername_pos, ciphername_len);
386 char kdfname[32] = {0};
387 if (kdfname_len > 0 && kdfname_len <
sizeof(kdfname)) {
388 memcpy(kdfname, key_blob + kdfname_pos, kdfname_len);
391 log_debug(
"Cipher: %s, KDF: %s", ciphername, kdfname);
394 if (strcmp(ciphername,
"aes256-ctr") != 0 && strcmp(ciphername,
"aes256-cbc") != 0) {
396 SAFE_FREE(file_content);
397 return SET_ERRNO(ERROR_CRYPTO_KEY,
398 "Unsupported cipher '%s' for encrypted SSH key: %s\n"
399 "Supported ciphers: aes256-ctr, aes256-cbc",
400 ciphername, key_path);
403 if (strcmp(kdfname,
"bcrypt") != 0) {
405 SAFE_FREE(file_content);
406 return SET_ERRNO(ERROR_CRYPTO_KEY,
407 "Unsupported KDF '%s' for encrypted SSH key: %s\n"
408 "Only bcrypt KDF is supported",
413 if (kdfoptions_len < 8) {
415 SAFE_FREE(file_content);
416 return SET_ERRNO(ERROR_CRYPTO_KEY,
"Invalid KDF options length: %s", key_path);
420 size_t kdf_opt_offset = offset - kdfoptions_len;
423 if (kdf_opt_offset + 4 > key_blob_len) {
425 SAFE_FREE(file_content);
426 return SET_ERRNO(ERROR_CRYPTO_KEY,
"KDF options truncated at salt length: %s", key_path);
428 uint32_t salt_len = read_u32_be(&key_blob[kdf_opt_offset]);
432 if (salt_len != 16) {
434 SAFE_FREE(file_content);
435 return SET_ERRNO(ERROR_CRYPTO_KEY,
"Unexpected bcrypt salt length %u (expected 16): %s", salt_len, key_path);
437 if (kdf_opt_offset + salt_len > key_blob_len) {
439 SAFE_FREE(file_content);
440 return SET_ERRNO(ERROR_CRYPTO_KEY,
"KDF options truncated at salt data: %s", key_path);
442 uint8_t bcrypt_salt[16];
443 memcpy(bcrypt_salt, key_blob + kdf_opt_offset, salt_len);
444 kdf_opt_offset += salt_len;
447 if (kdf_opt_offset + 4 > key_blob_len) {
449 SAFE_FREE(file_content);
450 return SET_ERRNO(ERROR_CRYPTO_KEY,
"KDF options truncated at rounds: %s", key_path);
452 uint32_t bcrypt_rounds = read_u32_be(&key_blob[kdf_opt_offset]);
456 char *password = NULL;
457 if (env_password && strlen(env_password) > 0) {
462 SAFE_FREE(file_content);
463 return SET_ERRNO(ERROR_MEMORY,
"Failed to allocate memory for password");
467 password = SAFE_MALLOC(1024,
char *);
470 SAFE_FREE(file_content);
471 return SET_ERRNO(ERROR_MEMORY,
"Failed to allocate memory for password");
475 SAFE_FREE(file_content);
476 return SET_ERRNO(ERROR_CRYPTO_KEY,
"Failed to read passphrase for encrypted key: %s", key_path);
486 if (offset + 4 > key_blob_len) {
487 sodium_memzero(password, strlen(password));
490 SAFE_FREE(file_content);
491 return SET_ERRNO(ERROR_CRYPTO_KEY,
"Encrypted key truncated at num_keys: %s", key_path);
493 uint32_t num_keys = read_u32_be(&key_blob[offset]);
495 log_debug(
"num_keys=%u", num_keys);
498 for (uint32_t i = 0; i < num_keys; i++) {
499 if (offset + 4 > key_blob_len) {
500 sodium_memzero(password, strlen(password));
503 SAFE_FREE(file_content);
504 return SET_ERRNO(ERROR_CRYPTO_KEY,
"Encrypted key truncated at pubkey %u length: %s", i, key_path);
506 uint32_t pubkey_len = read_u32_be(&key_blob[offset]);
508 log_debug(
"Skipping public key %u: %u bytes", i, pubkey_len);
510 if (offset + pubkey_len > key_blob_len) {
511 sodium_memzero(password, strlen(password));
514 SAFE_FREE(file_content);
515 return SET_ERRNO(ERROR_CRYPTO_KEY,
"Encrypted key truncated at pubkey %u data: %s", i, key_path);
517 offset += pubkey_len;
521 if (offset + 4 > key_blob_len) {
522 sodium_memzero(password, strlen(password));
525 SAFE_FREE(file_content);
526 return SET_ERRNO(ERROR_CRYPTO_KEY,
"Encrypted key truncated at encrypted_len: %s", key_path);
528 uint32_t encrypted_len = read_u32_be(&key_blob[offset]);
532 size_t encrypted_data_start = offset;
533 size_t encrypted_data_len = encrypted_len;
535 if (encrypted_data_len < 16) {
536 sodium_memzero(password, strlen(password));
539 SAFE_FREE(file_content);
540 return SET_ERRNO(ERROR_CRYPTO_KEY,
"Encrypted data too small: %s", key_path);
544 const uint8_t *encrypted_blob = key_blob + encrypted_data_start;
547 uint8_t *decrypted_blob = NULL;
548 size_t decrypted_blob_len = 0;
551 asciichat_error_t decrypt_result =
552 decrypt_openssh_private_key(encrypted_blob, encrypted_data_len, password, bcrypt_salt, salt_len, bcrypt_rounds,
553 ciphername, &decrypted_blob, &decrypted_blob_len);
556 sodium_memzero(password, strlen(password));
559 if (decrypt_result != ASCIICHAT_OK) {
561 SAFE_FREE(file_content);
562 return decrypt_result;
569 if (decrypted_blob_len < 8) {
570 SAFE_FREE(decrypted_blob);
572 SAFE_FREE(file_content);
573 return SET_ERRNO(ERROR_CRYPTO_KEY,
"Decrypted data too small (no checkints): %s", key_path);
577 uint32_t checkint1 = read_u32_be(&decrypted_blob[0]);
578 uint32_t checkint2 = read_u32_be(&decrypted_blob[4]);
579 if (checkint1 != checkint2) {
580 SAFE_FREE(decrypted_blob);
582 SAFE_FREE(file_content);
583 return SET_ERRNO(ERROR_CRYPTO_KEY,
584 "Incorrect passphrase or corrupted key (checkint mismatch): %s\n"
585 "Expected matching checkints, got 0x%08x != 0x%08x",
586 key_path, checkint1, checkint2);
591 size_t dec_offset = 8;
594 if (dec_offset + 4 > decrypted_blob_len) {
595 SAFE_FREE(decrypted_blob);
597 SAFE_FREE(file_content);
598 return SET_ERRNO(ERROR_CRYPTO_KEY,
"Decrypted key truncated at keytype length: %s", key_path);
600 uint32_t keytype_len = read_u32_be(&decrypted_blob[dec_offset]);
604 if (dec_offset + keytype_len > decrypted_blob_len) {
605 SAFE_FREE(decrypted_blob);
607 SAFE_FREE(file_content);
608 return SET_ERRNO(ERROR_CRYPTO_KEY,
"Decrypted key truncated at keytype data: %s", key_path);
610 char keytype[BUFFER_SIZE_SMALL / 4] = {0};
611 if (keytype_len > 0 && keytype_len <
sizeof(keytype)) {
612 memcpy(keytype, decrypted_blob + dec_offset, keytype_len);
614 dec_offset += keytype_len;
617 if (strcmp(keytype,
"ssh-ed25519") != 0) {
618 SAFE_FREE(decrypted_blob);
620 SAFE_FREE(file_content);
621 return SET_ERRNO(ERROR_CRYPTO_KEY,
"Unsupported key type after decryption: '%s'", keytype);
625 if (dec_offset + 4 > decrypted_blob_len) {
626 SAFE_FREE(decrypted_blob);
628 SAFE_FREE(file_content);
629 return SET_ERRNO(ERROR_CRYPTO_KEY,
"Decrypted key truncated at pubkey length: %s", key_path);
631 uint32_t pubkey_data_len = read_u32_be(&decrypted_blob[dec_offset]);
635 if (pubkey_data_len != 32) {
636 SAFE_FREE(decrypted_blob);
638 SAFE_FREE(file_content);
639 return SET_ERRNO(ERROR_CRYPTO_KEY,
"Invalid Ed25519 public key length: %u (expected 32)", pubkey_data_len);
641 if (dec_offset + pubkey_data_len > decrypted_blob_len) {
642 SAFE_FREE(decrypted_blob);
644 SAFE_FREE(file_content);
645 return SET_ERRNO(ERROR_CRYPTO_KEY,
"Decrypted key truncated at pubkey data: %s", key_path);
647 uint8_t ed25519_pk[32];
648 memcpy(ed25519_pk, decrypted_blob + dec_offset, 32);
652 if (dec_offset + 4 > decrypted_blob_len) {
653 SAFE_FREE(decrypted_blob);
655 SAFE_FREE(file_content);
656 return SET_ERRNO(ERROR_CRYPTO_KEY,
"Decrypted key truncated at privkey length: %s", key_path);
658 uint32_t privkey_data_len = read_u32_be(&decrypted_blob[dec_offset]);
662 if (privkey_data_len != 64) {
663 SAFE_FREE(decrypted_blob);
665 SAFE_FREE(file_content);
666 return SET_ERRNO(ERROR_CRYPTO_KEY,
"Invalid Ed25519 private key length: %u (expected 64)", privkey_data_len);
668 if (dec_offset + privkey_data_len > decrypted_blob_len) {
669 SAFE_FREE(decrypted_blob);
671 SAFE_FREE(file_content);
672 return SET_ERRNO(ERROR_CRYPTO_KEY,
"Decrypted key truncated at privkey data: %s", key_path);
674 uint8_t ed25519_sk[64];
675 memcpy(ed25519_sk, decrypted_blob + dec_offset, 64);
679 key_out->type = KEY_TYPE_ED25519;
680 memcpy(key_out->key.ed25519, ed25519_sk, 32);
681 memcpy(key_out->key.ed25519 + 32, ed25519_sk + 32, 32);
684 sodium_memzero(decrypted_blob, decrypted_blob_len);
685 SAFE_FREE(decrypted_blob);
687 SAFE_FREE(file_content);
689 log_debug(
"Successfully parsed decrypted Ed25519 key");
692 log_debug(
"Attempting to add decrypted key to ssh-agent");
694 if (agent_result == ASCIICHAT_OK) {
695 log_debug(
"Successfully added key to ssh-agent - password will not be required on next run");
698 log_warn(
"Failed to add key to ssh-agent (non-fatal): %s", asciichat_error_string(agent_result));
699 log_warn(
"You can manually add it with: ssh-add %s", key_path);
706 if (offset + 4 > key_blob_len) {
708 SAFE_FREE(file_content);
709 return SET_ERRNO(ERROR_CRYPTO_KEY,
"OpenSSH private key truncated at num keys: %s", key_path);
712 uint32_t num_keys = read_u32_be(&key_blob[offset]);
715 log_debug(
"num_keys=%u, offset=%zu, key_blob_len=%zu", num_keys, offset, key_blob_len);
716 log_debug(
"Raw bytes at offset %zu: %02x %02x %02x %02x", offset, key_blob[offset], key_blob[offset + 1],
717 key_blob[offset + 2], key_blob[offset + 3]);
718 log_debug(
"After num_keys, offset=%zu", offset);
722 SAFE_FREE(file_content);
723 return SET_ERRNO(ERROR_CRYPTO_KEY,
"OpenSSH private key contains %u keys (expected 1): %s", num_keys, key_path);
727 if (offset + 4 > key_blob_len) {
729 SAFE_FREE(file_content);
730 return SET_ERRNO(ERROR_CRYPTO_KEY,
"OpenSSH private key truncated at pubkey length: %s", key_path);
733 log_debug(
"About to read pubkey_len at offset=%zu, bytes: %02x %02x %02x %02x", offset, key_blob[offset],
734 key_blob[offset + 1], key_blob[offset + 2], key_blob[offset + 3]);
736 uint32_t pubkey_len = read_u32_be(&key_blob[offset]);
739 log_debug(
"pubkey_len=%u, offset=%zu", pubkey_len, offset);
741 if (offset + pubkey_len > key_blob_len) {
743 SAFE_FREE(file_content);
744 return SET_ERRNO(ERROR_CRYPTO_KEY,
"OpenSSH private key truncated at pubkey data: %s", key_path);
748 if (pubkey_len < 4) {
750 SAFE_FREE(file_content);
751 return SET_ERRNO(ERROR_CRYPTO_KEY,
"OpenSSH public key too small: %s", key_path);
754 uint32_t key_type_len = read_u32_be(&key_blob[offset]);
758 if (key_type_len != 11 || memcmp(key_blob + offset,
"ssh-ed25519", 11) != 0) {
760 SAFE_FREE(file_content);
761 return SET_ERRNO(ERROR_CRYPTO_KEY,
"OpenSSH private key is not Ed25519: %s", key_path);
764 offset += key_type_len;
765 log_debug(
"After skipping key type, offset=%zu", offset);
768 if (offset + 4 > key_blob_len) {
770 SAFE_FREE(file_content);
771 return SET_ERRNO(ERROR_CRYPTO_KEY,
"OpenSSH private key truncated at public key length: %s", key_path);
774 uint32_t pubkey_data_len = read_u32_be(&key_blob[offset]);
776 log_debug(
"Public key data length: %u, offset=%zu", pubkey_data_len, offset);
778 log_debug(
"Public key data length: %u (expected 32 for Ed25519)", pubkey_data_len);
781 if (pubkey_data_len < 32) {
783 SAFE_FREE(file_content);
784 return SET_ERRNO(ERROR_CRYPTO_KEY,
"OpenSSH public key data too small (%u bytes, expected at least 32): %s",
785 pubkey_data_len, key_path);
789 if (offset + 32 > key_blob_len) {
791 SAFE_FREE(file_content);
792 return SET_ERRNO(ERROR_CRYPTO_KEY,
"OpenSSH private key truncated at public key: %s", key_path);
795 uint8_t ed25519_pubkey[32];
796 memcpy(ed25519_pubkey, key_blob + offset, 32);
797 offset += pubkey_data_len;
803 if (pubkey_len >= 51) {
804 size_t remaining_pubkey = pubkey_len - 51;
805 if (remaining_pubkey > 0) {
806 offset += remaining_pubkey;
811 if (offset + 4 > key_blob_len) {
813 SAFE_FREE(file_content);
814 return SET_ERRNO(ERROR_CRYPTO_KEY,
"OpenSSH private key truncated at privkey length: %s", key_path);
817 uint32_t privkey_len = read_u32_be(&key_blob[offset]);
820 if (offset + privkey_len > key_blob_len) {
822 SAFE_FREE(file_content);
823 return SET_ERRNO(ERROR_CRYPTO_KEY,
"OpenSSH private key truncated at privkey data: %s", key_path);
831 if (privkey_len < 8) {
833 SAFE_FREE(file_content);
834 return SET_ERRNO(ERROR_CRYPTO_KEY,
"OpenSSH private key data too small: %s", key_path);
838 uint32_t checkint1 = read_u32_be(&key_blob[offset]);
839 uint32_t checkint2 = read_u32_be(&key_blob[offset + 4]);
842 if (checkint1 != checkint2) {
844 SAFE_FREE(file_content);
845 return SET_ERRNO(ERROR_CRYPTO_KEY,
"OpenSSH private key checkints don't match: %s", key_path);
849 if (offset + 4 > key_blob_len) {
851 SAFE_FREE(file_content);
852 return SET_ERRNO(ERROR_CRYPTO_KEY,
"OpenSSH private key truncated at key type length: %s", key_path);
855 uint32_t key_type_len_priv = read_u32_be(&key_blob[offset]);
857 if (key_type_len_priv > key_blob_len || offset + 4 + key_type_len_priv > key_blob_len) {
859 SAFE_FREE(file_content);
860 return SET_ERRNO(ERROR_CRYPTO_KEY,
"OpenSSH private key type length overflow: %u", key_type_len_priv);
862 offset += 4 + key_type_len_priv;
865 if (offset + 4 > key_blob_len) {
867 SAFE_FREE(file_content);
868 return SET_ERRNO(ERROR_CRYPTO_KEY,
"OpenSSH private key truncated at pubkey length: %s", key_path);
871 uint32_t pubkey_len_priv = read_u32_be(&key_blob[offset]);
873 if (pubkey_len_priv > key_blob_len || offset + 4 + pubkey_len_priv > key_blob_len) {
875 SAFE_FREE(file_content);
876 return SET_ERRNO(ERROR_CRYPTO_KEY,
"OpenSSH private key public key length overflow: %u", pubkey_len_priv);
878 offset += 4 + pubkey_len_priv;
881 if (offset + 4 > key_blob_len) {
883 SAFE_FREE(file_content);
884 return SET_ERRNO(ERROR_CRYPTO_KEY,
"OpenSSH private key truncated at privkey length: %s", key_path);
887 uint32_t privkey_data_len = read_u32_be(&key_blob[offset]);
890 if (offset + privkey_data_len > key_blob_len) {
892 SAFE_FREE(file_content);
893 return SET_ERRNO(ERROR_CRYPTO_KEY,
"OpenSSH private key truncated at privkey data: %s", key_path);
898 if (privkey_data_len < 64) {
900 SAFE_FREE(file_content);
901 return SET_ERRNO(ERROR_CRYPTO_KEY,
"OpenSSH private key data length is %u (expected at least 64): %s",
902 privkey_data_len, key_path);
906 uint8_t ed25519_privkey[32];
907 memcpy(ed25519_privkey, key_blob + offset, 32);
913 if (sodium_memcmp(key_blob + offset + 32, ed25519_pubkey, 32) != 0) {
915 SAFE_FREE(file_content);
916 return SET_ERRNO(ERROR_CRYPTO_KEY,
"OpenSSH private key public key mismatch: %s", key_path);
920 memset(key_out, 0,
sizeof(private_key_t));
921 key_out->type = KEY_TYPE_ED25519;
925 memcpy(key_out->key.ed25519, ed25519_privkey, 32);
926 memcpy(key_out->key.ed25519 + 32, ed25519_pubkey, 32);
929 memcpy(key_out->public_key, ed25519_pubkey, 32);
932 SAFE_STRNCPY(key_out->key_comment,
"ssh-ed25519",
sizeof(key_out->key_comment) - 1);
935 SAFE_FREE(file_content);