219 if (!key_path || !key_out) {
226 safe_snprintf(pub_key_path,
sizeof(pub_key_path),
"%s.pub", key_path);
231 if (fgets(pub_line,
sizeof(pub_line), pub_f)) {
239 log_info(
"Key found in ssh-agent - using cached key (no password required)");
248 log_debug(
"Key not found in ssh-agent - will decrypt from file");
258 return validation_result;
268 char *file_content = NULL;
269 size_t file_size = 0;
273 while ((bytes_read = fread(buffer, 1,
sizeof(buffer), f)) > 0) {
274 file_content =
SAFE_REALLOC(file_content, file_size + bytes_read + 1,
char *);
279 memcpy(file_content + file_size, buffer, bytes_read);
280 file_size += bytes_read;
284 if (file_size == 0) {
289 file_content[file_size] =
'\0';
292 if (strstr(file_content,
"BEGIN OPENSSH PRIVATE KEY") == NULL) {
304 const char *base64_start = strstr(file_content,
"-----BEGIN OPENSSH PRIVATE KEY-----");
311 base64_start = strchr(base64_start,
'\n');
319 const char *base64_end = strstr(base64_start,
"-----END OPENSSH PRIVATE KEY-----");
326 char *clean_base64 =
SAFE_MALLOC(base64_end - base64_start + 1,
char *);
327 char *clean_ptr = clean_base64;
328 for (
const char *p = base64_start; p < base64_end; p++) {
329 if (*p !=
'\n' && *p !=
'\r' && *p !=
' ' && *p !=
'\t') {
338 asciichat_error_t decode_result = base64_decode_ssh_key(clean_base64, strlen(clean_base64), &key_blob, &key_blob_len);
343 return decode_result;
351 if (key_blob_len < 4) {
358 if (memcmp(key_blob,
"openssh-key-v1\0", 15) != 0) {
367 if (offset + 4 > key_blob_len) {
373 uint32_t ciphername_len = read_u32_be(&key_blob[offset]);
376 if (offset + ciphername_len > key_blob_len) {
383 size_t ciphername_pos = offset;
386 bool is_encrypted = (ciphername_len > 0 && memcmp(key_blob + offset,
"none", 4) != 0);
388 offset += ciphername_len;
391 if (offset + 4 > key_blob_len) {
397 uint32_t kdfname_len = read_u32_be(&key_blob[offset]);
401 size_t kdfname_pos = offset;
403 offset += kdfname_len;
405 if (offset + 4 > key_blob_len) {
411 uint32_t kdfoptions_len = read_u32_be(&key_blob[offset]);
412 offset += 4 + kdfoptions_len;
417 char ciphername[32] = {0};
418 if (ciphername_len > 0 && ciphername_len <
sizeof(ciphername)) {
419 memcpy(ciphername, key_blob + ciphername_pos, ciphername_len);
423 char kdfname[32] = {0};
424 if (kdfname_len > 0 && kdfname_len <
sizeof(kdfname)) {
425 memcpy(kdfname, key_blob + kdfname_pos, kdfname_len);
428 log_debug(
"Cipher: %s, KDF: %s", ciphername, kdfname);
431 if (strcmp(ciphername,
"aes256-ctr") != 0 && strcmp(ciphername,
"aes256-cbc") != 0) {
435 "Unsupported cipher '%s' for encrypted SSH key: %s\n"
436 "Supported ciphers: aes256-ctr, aes256-cbc",
437 ciphername, key_path);
440 if (strcmp(kdfname,
"bcrypt") != 0) {
444 "Unsupported KDF '%s' for encrypted SSH key: %s\n"
445 "Only bcrypt KDF is supported",
450 if (kdfoptions_len < 8) {
457 size_t kdf_opt_offset = offset - kdfoptions_len;
460 if (kdf_opt_offset + 4 > key_blob_len) {
465 uint32_t salt_len = read_u32_be(&key_blob[kdf_opt_offset]);
469 if (salt_len != 16) {
474 if (kdf_opt_offset + salt_len > key_blob_len) {
480 memcpy(bcrypt_salt, key_blob + kdf_opt_offset, salt_len);
481 kdf_opt_offset += salt_len;
484 if (kdf_opt_offset + 4 > key_blob_len) {
489 uint32_t bcrypt_rounds = read_u32_be(&key_blob[kdf_opt_offset]);
493 char *password = NULL;
494 if (env_password && strlen(env_password) > 0) {
523 if (offset + 4 > key_blob_len) {
524 sodium_memzero(password, strlen(password));
530 uint32_t num_keys = read_u32_be(&key_blob[offset]);
535 for (
uint32_t i = 0; i < num_keys; i++) {
536 if (offset + 4 > key_blob_len) {
537 sodium_memzero(password, strlen(password));
543 uint32_t pubkey_len = read_u32_be(&key_blob[offset]);
545 log_debug(
"Skipping public key %u: %u bytes", i, pubkey_len);
547 if (offset + pubkey_len > key_blob_len) {
548 sodium_memzero(password, strlen(password));
554 offset += pubkey_len;
558 if (offset + 4 > key_blob_len) {
559 sodium_memzero(password, strlen(password));
565 uint32_t encrypted_len = read_u32_be(&key_blob[offset]);
569 size_t encrypted_data_start = offset;
570 size_t encrypted_data_len = encrypted_len;
572 if (encrypted_data_len < 16) {
573 sodium_memzero(password, strlen(password));
581 const uint8_t *encrypted_blob = key_blob + encrypted_data_start;
584 uint8_t *decrypted_blob = NULL;
585 size_t decrypted_blob_len = 0;
589 decrypt_openssh_private_key(encrypted_blob, encrypted_data_len, password, bcrypt_salt, salt_len, bcrypt_rounds,
590 ciphername, &decrypted_blob, &decrypted_blob_len);
593 sodium_memzero(password, strlen(password));
599 return decrypt_result;
606 if (decrypted_blob_len < 8) {
614 uint32_t checkint1 = read_u32_be(&decrypted_blob[0]);
615 uint32_t checkint2 = read_u32_be(&decrypted_blob[4]);
616 if (checkint1 != checkint2) {
621 "Incorrect passphrase or corrupted key (checkint mismatch): %s\n"
622 "Expected matching checkints, got 0x%08x != 0x%08x",
623 key_path, checkint1, checkint2);
628 size_t dec_offset = 8;
631 if (dec_offset + 4 > decrypted_blob_len) {
637 uint32_t keytype_len = read_u32_be(&decrypted_blob[dec_offset]);
641 if (dec_offset + keytype_len > decrypted_blob_len) {
648 if (keytype_len > 0 && keytype_len <
sizeof(keytype)) {
649 memcpy(keytype, decrypted_blob + dec_offset, keytype_len);
651 dec_offset += keytype_len;
654 if (strcmp(keytype,
"ssh-ed25519") != 0) {
662 if (dec_offset + 4 > decrypted_blob_len) {
668 uint32_t pubkey_data_len = read_u32_be(&decrypted_blob[dec_offset]);
672 if (pubkey_data_len != 32) {
678 if (dec_offset + pubkey_data_len > decrypted_blob_len) {
685 memcpy(ed25519_pk, decrypted_blob + dec_offset, 32);
689 if (dec_offset + 4 > decrypted_blob_len) {
695 uint32_t privkey_data_len = read_u32_be(&decrypted_blob[dec_offset]);
699 if (privkey_data_len != 64) {
705 if (dec_offset + privkey_data_len > decrypted_blob_len) {
712 memcpy(ed25519_sk, decrypted_blob + dec_offset, 64);
718 memcpy(key_out->
key.
ed25519 + 32, ed25519_sk + 32, 32);
721 sodium_memzero(decrypted_blob, decrypted_blob_len);
726 log_debug(
"Successfully parsed decrypted Ed25519 key");
729 log_info(
"Attempting to add decrypted key to ssh-agent");
732 log_info(
"Successfully added key to ssh-agent - password will not be required on next run");
735 log_warn(
"Failed to add key to ssh-agent (non-fatal): %s", asciichat_error_string(agent_result));
736 log_warn(
"You can manually add it with: ssh-add %s", key_path);
743 if (offset + 4 > key_blob_len) {
749 uint32_t num_keys = read_u32_be(&key_blob[offset]);
752 log_debug(
"num_keys=%u, offset=%zu, key_blob_len=%zu", num_keys, offset, key_blob_len);
753 log_debug(
"Raw bytes at offset %zu: %02x %02x %02x %02x", offset, key_blob[offset], key_blob[offset + 1],
754 key_blob[offset + 2], key_blob[offset + 3]);
755 log_debug(
"After num_keys, offset=%zu", offset);
764 if (offset + 4 > key_blob_len) {
770 log_debug(
"About to read pubkey_len at offset=%zu, bytes: %02x %02x %02x %02x", offset, key_blob[offset],
771 key_blob[offset + 1], key_blob[offset + 2], key_blob[offset + 3]);
773 uint32_t pubkey_len = read_u32_be(&key_blob[offset]);
776 log_debug(
"pubkey_len=%u, offset=%zu", pubkey_len, offset);
778 if (offset + pubkey_len > key_blob_len) {
785 if (pubkey_len < 4) {
791 uint32_t key_type_len = read_u32_be(&key_blob[offset]);
795 if (key_type_len != 11 || memcmp(key_blob + offset,
"ssh-ed25519", 11) != 0) {
801 offset += key_type_len;
802 log_debug(
"After skipping key type, offset=%zu", offset);
805 if (offset + 4 > key_blob_len) {
811 uint32_t pubkey_data_len = read_u32_be(&key_blob[offset]);
813 log_debug(
"Public key data length: %u, offset=%zu", pubkey_data_len, offset);
815 log_debug(
"Public key data length: %u (expected 32 for Ed25519)", pubkey_data_len);
818 if (pubkey_data_len < 32) {
822 pubkey_data_len, key_path);
826 if (offset + 32 > key_blob_len) {
833 memcpy(ed25519_pubkey, key_blob + offset, 32);
834 offset += pubkey_data_len;
840 if (pubkey_len >= 51) {
841 size_t remaining_pubkey = pubkey_len - 51;
842 if (remaining_pubkey > 0) {
843 offset += remaining_pubkey;
848 if (offset + 4 > key_blob_len) {
854 uint32_t privkey_len = read_u32_be(&key_blob[offset]);
857 if (offset + privkey_len > key_blob_len) {
868 if (privkey_len < 8) {
875 uint32_t checkint1 = read_u32_be(&key_blob[offset]);
876 uint32_t checkint2 = read_u32_be(&key_blob[offset + 4]);
879 if (checkint1 != checkint2) {
886 if (offset + 4 > key_blob_len) {
892 uint32_t key_type_len_priv = read_u32_be(&key_blob[offset]);
894 if (key_type_len_priv > key_blob_len || offset + 4 + key_type_len_priv > key_blob_len) {
899 offset += 4 + key_type_len_priv;
902 if (offset + 4 > key_blob_len) {
908 uint32_t pubkey_len_priv = read_u32_be(&key_blob[offset]);
910 if (pubkey_len_priv > key_blob_len || offset + 4 + pubkey_len_priv > key_blob_len) {
915 offset += 4 + pubkey_len_priv;
918 if (offset + 4 > key_blob_len) {
924 uint32_t privkey_data_len = read_u32_be(&key_blob[offset]);
927 if (offset + privkey_data_len > key_blob_len) {
935 if (privkey_data_len < 64) {
939 privkey_data_len, key_path);
944 memcpy(ed25519_privkey, key_blob + offset, 32);
950 if (sodium_memcmp(key_blob + offset + 32, ed25519_pubkey, 32) != 0) {
962 memcpy(key_out->
key.
ed25519, ed25519_privkey, 32);
963 memcpy(key_out->
key.
ed25519 + 32, ed25519_pubkey, 32);
966 memcpy(key_out->
public_key, ed25519_pubkey, 32);