207 {
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);
210 }
211
212
213
214 char pub_key_path[BUFFER_SIZE_LARGE];
215 safe_snprintf(pub_key_path,
sizeof(pub_key_path),
"%s.pub", key_path);
216
218 if (pub_f) {
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;
223
225
227 fclose(pub_f);
228 log_dev("Key found in ssh-agent - using cached key (no password required)");
229
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);
234
235 return ASCIICHAT_OK;
236 } else {
237 log_debug("Key not found in ssh-agent - will decrypt from file");
238 }
239 }
240 }
241 fclose(pub_f);
242 }
243
244
246 if (validation_result != ASCIICHAT_OK) {
247 return validation_result;
248 }
249
250
252 if (!f) {
253 return SET_ERRNO(ERROR_CRYPTO_KEY, "Cannot read private key file: %s", key_path);
254 }
255
256
257 char *file_content = NULL;
258 size_t file_size = 0;
259 char buffer[BUFFER_SIZE_XXLARGE];
260 size_t bytes_read;
261
262 while ((bytes_read = fread(buffer, 1, sizeof(buffer), f)) > 0) {
263 file_content = SAFE_REALLOC(file_content, file_size + bytes_read + 1, char *);
264 if (!file_content) {
265 (void)fclose(f);
266 return SET_ERRNO(ERROR_CRYPTO_KEY, "Out of memory reading private key file");
267 }
268 memcpy(file_content + file_size, buffer, bytes_read);
269 file_size += bytes_read;
270 }
271 (void)fclose(f);
272
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);
276 }
277
278 file_content[file_size] = '\0';
279
280
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);
285 }
286
287
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') {
292 *clean_ptr++ = *p;
293 }
294 }
295 *clean_ptr = '\0';
296 SAFE_FREE(pem_base64_data);
297
298
299 uint8_t *key_blob;
300 size_t key_blob_len;
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);
303
304 if (decode_result != ASCIICHAT_OK) {
305 SAFE_FREE(file_content);
306 return decode_result;
307 }
308
309
310
311
312
313
314 if (key_blob_len < 4) {
315 SAFE_FREE(key_blob);
316 SAFE_FREE(file_content);
317 return SET_ERRNO(ERROR_CRYPTO_KEY, "OpenSSH private key blob too small: %s", key_path);
318 }
319
320
321 if (memcmp(key_blob, "openssh-key-v1\0", 15) != 0) {
322 SAFE_FREE(key_blob);
323 SAFE_FREE(file_content);
324 return SET_ERRNO(ERROR_CRYPTO_KEY, "Invalid OpenSSH private key magic: %s", key_path);
325 }
326
327 size_t offset = 15;
328
329
330 if (offset + 4 > key_blob_len) {
331 SAFE_FREE(key_blob);
332 SAFE_FREE(file_content);
333 return SET_ERRNO(ERROR_CRYPTO_KEY, "OpenSSH private key truncated at ciphername: %s", key_path);
334 }
335
336 uint32_t ciphername_len = read_u32_be(&key_blob[offset]);
337 offset += 4;
338
339 if (offset + ciphername_len > key_blob_len) {
340 SAFE_FREE(key_blob);
341 SAFE_FREE(file_content);
342 return SET_ERRNO(ERROR_CRYPTO_KEY, "OpenSSH private key truncated at ciphername data: %s", key_path);
343 }
344
345
346 size_t ciphername_pos = offset;
347
348
349 bool is_encrypted = (ciphername_len > 0 && memcmp(key_blob + offset, "none", 4) != 0);
350
351 offset += ciphername_len;
352
353
354 if (offset + 4 > key_blob_len) {
355 SAFE_FREE(key_blob);
356 SAFE_FREE(file_content);
357 return SET_ERRNO(ERROR_CRYPTO_KEY, "OpenSSH private key truncated at kdfname: %s", key_path);
358 }
359
360 uint32_t kdfname_len = read_u32_be(&key_blob[offset]);
361 offset += 4;
362
363
364 size_t kdfname_pos = offset;
365
366 offset += kdfname_len;
367
368 if (offset + 4 > key_blob_len) {
369 SAFE_FREE(key_blob);
370 SAFE_FREE(file_content);
371 return SET_ERRNO(ERROR_CRYPTO_KEY, "OpenSSH private key truncated at kdfoptions: %s", key_path);
372 }
373
374 uint32_t kdfoptions_len = read_u32_be(&key_blob[offset]);
375 offset += 4 + kdfoptions_len;
376
377
378 if (is_encrypted) {
379
380 char ciphername[32] = {0};
381 if (ciphername_len > 0 && ciphername_len < sizeof(ciphername)) {
382 memcpy(ciphername, key_blob + ciphername_pos, ciphername_len);
383 }
384
385
386 char kdfname[32] = {0};
387 if (kdfname_len > 0 && kdfname_len < sizeof(kdfname)) {
388 memcpy(kdfname, key_blob + kdfname_pos, kdfname_len);
389 }
390
391 log_debug("Cipher: %s, KDF: %s", ciphername, kdfname);
392
393
394 if (strcmp(ciphername, "aes256-ctr") != 0 && strcmp(ciphername, "aes256-cbc") != 0) {
395 SAFE_FREE(key_blob);
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);
401 }
402
403 if (strcmp(kdfname, "bcrypt") != 0) {
404 SAFE_FREE(key_blob);
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",
409 kdfname, key_path);
410 }
411
412
413 if (kdfoptions_len < 8) {
414 SAFE_FREE(key_blob);
415 SAFE_FREE(file_content);
416 return SET_ERRNO(ERROR_CRYPTO_KEY, "Invalid KDF options length: %s", key_path);
417 }
418
419
420 size_t kdf_opt_offset = offset - kdfoptions_len;
421
422
423 if (kdf_opt_offset + 4 > key_blob_len) {
424 SAFE_FREE(key_blob);
425 SAFE_FREE(file_content);
426 return SET_ERRNO(ERROR_CRYPTO_KEY, "KDF options truncated at salt length: %s", key_path);
427 }
428 uint32_t salt_len = read_u32_be(&key_blob[kdf_opt_offset]);
429 kdf_opt_offset += 4;
430
431
432 if (salt_len != 16) {
433 SAFE_FREE(key_blob);
434 SAFE_FREE(file_content);
435 return SET_ERRNO(ERROR_CRYPTO_KEY, "Unexpected bcrypt salt length %u (expected 16): %s", salt_len, key_path);
436 }
437 if (kdf_opt_offset + salt_len > key_blob_len) {
438 SAFE_FREE(key_blob);
439 SAFE_FREE(file_content);
440 return SET_ERRNO(ERROR_CRYPTO_KEY, "KDF options truncated at salt data: %s", key_path);
441 }
442 uint8_t bcrypt_salt[16];
443 memcpy(bcrypt_salt, key_blob + kdf_opt_offset, salt_len);
444 kdf_opt_offset += salt_len;
445
446
447 if (kdf_opt_offset + 4 > key_blob_len) {
448 SAFE_FREE(key_blob);
449 SAFE_FREE(file_content);
450 return SET_ERRNO(ERROR_CRYPTO_KEY, "KDF options truncated at rounds: %s", key_path);
451 }
452 uint32_t bcrypt_rounds = read_u32_be(&key_blob[kdf_opt_offset]);
453
454
456 char *password = NULL;
457 if (env_password && strlen(env_password) > 0) {
458
460 if (!password) {
461 SAFE_FREE(key_blob);
462 SAFE_FREE(file_content);
463 return SET_ERRNO(ERROR_MEMORY, "Failed to allocate memory for password");
464 }
465 } else {
466
467 password = SAFE_MALLOC(1024, char *);
468 if (!password) {
469 SAFE_FREE(key_blob);
470 SAFE_FREE(file_content);
471 return SET_ERRNO(ERROR_MEMORY, "Failed to allocate memory for password");
472 }
474 SAFE_FREE(key_blob);
475 SAFE_FREE(file_content);
476 return SET_ERRNO(ERROR_CRYPTO_KEY, "Failed to read passphrase for encrypted key: %s", key_path);
477 }
478 }
479
480
481
482
483
484
485
486 if (offset + 4 > key_blob_len) {
487 sodium_memzero(password, strlen(password));
488 SAFE_FREE(password);
489 SAFE_FREE(key_blob);
490 SAFE_FREE(file_content);
491 return SET_ERRNO(ERROR_CRYPTO_KEY, "Encrypted key truncated at num_keys: %s", key_path);
492 }
493 uint32_t num_keys = read_u32_be(&key_blob[offset]);
494 offset += 4;
495 log_debug("num_keys=%u", num_keys);
496
497
498 for (uint32_t i = 0; i < num_keys; i++) {
499 if (offset + 4 > key_blob_len) {
500 sodium_memzero(password, strlen(password));
501 SAFE_FREE(password);
502 SAFE_FREE(key_blob);
503 SAFE_FREE(file_content);
504 return SET_ERRNO(ERROR_CRYPTO_KEY, "Encrypted key truncated at pubkey %u length: %s", i, key_path);
505 }
506 uint32_t pubkey_len = read_u32_be(&key_blob[offset]);
507 offset += 4;
508 log_debug("Skipping public key %u: %u bytes", i, pubkey_len);
509
510 if (offset + pubkey_len > key_blob_len) {
511 sodium_memzero(password, strlen(password));
512 SAFE_FREE(password);
513 SAFE_FREE(key_blob);
514 SAFE_FREE(file_content);
515 return SET_ERRNO(ERROR_CRYPTO_KEY, "Encrypted key truncated at pubkey %u data: %s", i, key_path);
516 }
517 offset += pubkey_len;
518 }
519
520
521 if (offset + 4 > key_blob_len) {
522 sodium_memzero(password, strlen(password));
523 SAFE_FREE(password);
524 SAFE_FREE(key_blob);
525 SAFE_FREE(file_content);
526 return SET_ERRNO(ERROR_CRYPTO_KEY, "Encrypted key truncated at encrypted_len: %s", key_path);
527 }
528 uint32_t encrypted_len = read_u32_be(&key_blob[offset]);
529 offset += 4;
530
531
532 size_t encrypted_data_start = offset;
533 size_t encrypted_data_len = encrypted_len;
534
535 if (encrypted_data_len < 16) {
536 sodium_memzero(password, strlen(password));
537 SAFE_FREE(password);
538 SAFE_FREE(key_blob);
539 SAFE_FREE(file_content);
540 return SET_ERRNO(ERROR_CRYPTO_KEY, "Encrypted data too small: %s", key_path);
541 }
542
543
544 const uint8_t *encrypted_blob = key_blob + encrypted_data_start;
545
546
547 uint8_t *decrypted_blob = NULL;
548 size_t decrypted_blob_len = 0;
549
550
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);
554
555
556 sodium_memzero(password, strlen(password));
557 SAFE_FREE(password);
558
559 if (decrypt_result != ASCIICHAT_OK) {
560 SAFE_FREE(key_blob);
561 SAFE_FREE(file_content);
562 return decrypt_result;
563 }
564
565
566
567
568
569 if (decrypted_blob_len < 8) {
570 SAFE_FREE(decrypted_blob);
571 SAFE_FREE(key_blob);
572 SAFE_FREE(file_content);
573 return SET_ERRNO(ERROR_CRYPTO_KEY, "Decrypted data too small (no checkints): %s", key_path);
574 }
575
576
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);
581 SAFE_FREE(key_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);
587 }
588
589
590
591 size_t dec_offset = 8;
592
593
594 if (dec_offset + 4 > decrypted_blob_len) {
595 SAFE_FREE(decrypted_blob);
596 SAFE_FREE(key_blob);
597 SAFE_FREE(file_content);
598 return SET_ERRNO(ERROR_CRYPTO_KEY, "Decrypted key truncated at keytype length: %s", key_path);
599 }
600 uint32_t keytype_len = read_u32_be(&decrypted_blob[dec_offset]);
601 dec_offset += 4;
602
603
604 if (dec_offset + keytype_len > decrypted_blob_len) {
605 SAFE_FREE(decrypted_blob);
606 SAFE_FREE(key_blob);
607 SAFE_FREE(file_content);
608 return SET_ERRNO(ERROR_CRYPTO_KEY, "Decrypted key truncated at keytype data: %s", key_path);
609 }
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);
613 }
614 dec_offset += keytype_len;
615
616
617 if (strcmp(keytype, "ssh-ed25519") != 0) {
618 SAFE_FREE(decrypted_blob);
619 SAFE_FREE(key_blob);
620 SAFE_FREE(file_content);
621 return SET_ERRNO(ERROR_CRYPTO_KEY, "Unsupported key type after decryption: '%s'", keytype);
622 }
623
624
625 if (dec_offset + 4 > decrypted_blob_len) {
626 SAFE_FREE(decrypted_blob);
627 SAFE_FREE(key_blob);
628 SAFE_FREE(file_content);
629 return SET_ERRNO(ERROR_CRYPTO_KEY, "Decrypted key truncated at pubkey length: %s", key_path);
630 }
631 uint32_t pubkey_data_len = read_u32_be(&decrypted_blob[dec_offset]);
632 dec_offset += 4;
633
634
635 if (pubkey_data_len != 32) {
636 SAFE_FREE(decrypted_blob);
637 SAFE_FREE(key_blob);
638 SAFE_FREE(file_content);
639 return SET_ERRNO(ERROR_CRYPTO_KEY, "Invalid Ed25519 public key length: %u (expected 32)", pubkey_data_len);
640 }
641 if (dec_offset + pubkey_data_len > decrypted_blob_len) {
642 SAFE_FREE(decrypted_blob);
643 SAFE_FREE(key_blob);
644 SAFE_FREE(file_content);
645 return SET_ERRNO(ERROR_CRYPTO_KEY, "Decrypted key truncated at pubkey data: %s", key_path);
646 }
647 uint8_t ed25519_pk[32];
648 memcpy(ed25519_pk, decrypted_blob + dec_offset, 32);
649 dec_offset += 32;
650
651
652 if (dec_offset + 4 > decrypted_blob_len) {
653 SAFE_FREE(decrypted_blob);
654 SAFE_FREE(key_blob);
655 SAFE_FREE(file_content);
656 return SET_ERRNO(ERROR_CRYPTO_KEY, "Decrypted key truncated at privkey length: %s", key_path);
657 }
658 uint32_t privkey_data_len = read_u32_be(&decrypted_blob[dec_offset]);
659 dec_offset += 4;
660
661
662 if (privkey_data_len != 64) {
663 SAFE_FREE(decrypted_blob);
664 SAFE_FREE(key_blob);
665 SAFE_FREE(file_content);
666 return SET_ERRNO(ERROR_CRYPTO_KEY, "Invalid Ed25519 private key length: %u (expected 64)", privkey_data_len);
667 }
668 if (dec_offset + privkey_data_len > decrypted_blob_len) {
669 SAFE_FREE(decrypted_blob);
670 SAFE_FREE(key_blob);
671 SAFE_FREE(file_content);
672 return SET_ERRNO(ERROR_CRYPTO_KEY, "Decrypted key truncated at privkey data: %s", key_path);
673 }
674 uint8_t ed25519_sk[64];
675 memcpy(ed25519_sk, decrypted_blob + dec_offset, 64);
676 dec_offset += 64;
677
678
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);
682
683
684 sodium_memzero(decrypted_blob, decrypted_blob_len);
685 SAFE_FREE(decrypted_blob);
686 SAFE_FREE(key_blob);
687 SAFE_FREE(file_content);
688
689 log_debug("Successfully parsed decrypted Ed25519 key");
690
691
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");
696 } else {
697
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);
700 }
701
702 return ASCIICHAT_OK;
703 }
704
705
706 if (offset + 4 > key_blob_len) {
707 SAFE_FREE(key_blob);
708 SAFE_FREE(file_content);
709 return SET_ERRNO(ERROR_CRYPTO_KEY, "OpenSSH private key truncated at num keys: %s", key_path);
710 }
711
712 uint32_t num_keys = read_u32_be(&key_blob[offset]);
713 offset += 4;
714
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);
719
720 if (num_keys != 1) {
721 SAFE_FREE(key_blob);
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);
724 }
725
726
727 if (offset + 4 > key_blob_len) {
728 SAFE_FREE(key_blob);
729 SAFE_FREE(file_content);
730 return SET_ERRNO(ERROR_CRYPTO_KEY, "OpenSSH private key truncated at pubkey length: %s", key_path);
731 }
732
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]);
735
736 uint32_t pubkey_len = read_u32_be(&key_blob[offset]);
737 offset += 4;
738
739 log_debug("pubkey_len=%u, offset=%zu", pubkey_len, offset);
740
741 if (offset + pubkey_len > key_blob_len) {
742 SAFE_FREE(key_blob);
743 SAFE_FREE(file_content);
744 return SET_ERRNO(ERROR_CRYPTO_KEY, "OpenSSH private key truncated at pubkey data: %s", key_path);
745 }
746
747
748 if (pubkey_len < 4) {
749 SAFE_FREE(key_blob);
750 SAFE_FREE(file_content);
751 return SET_ERRNO(ERROR_CRYPTO_KEY, "OpenSSH public key too small: %s", key_path);
752 }
753
754 uint32_t key_type_len = read_u32_be(&key_blob[offset]);
755 offset += 4;
756
757
758 if (key_type_len != 11 || memcmp(key_blob + offset, "ssh-ed25519", 11) != 0) {
759 SAFE_FREE(key_blob);
760 SAFE_FREE(file_content);
761 return SET_ERRNO(ERROR_CRYPTO_KEY, "OpenSSH private key is not Ed25519: %s", key_path);
762 }
763
764 offset += key_type_len;
765 log_debug("After skipping key type, offset=%zu", offset);
766
767
768 if (offset + 4 > key_blob_len) {
769 SAFE_FREE(key_blob);
770 SAFE_FREE(file_content);
771 return SET_ERRNO(ERROR_CRYPTO_KEY, "OpenSSH private key truncated at public key length: %s", key_path);
772 }
773
774 uint32_t pubkey_data_len = read_u32_be(&key_blob[offset]);
775 offset += 4;
776 log_debug("Public key data length: %u, offset=%zu", pubkey_data_len, offset);
777
778 log_debug("Public key data length: %u (expected 32 for Ed25519)", pubkey_data_len);
779
780
781 if (pubkey_data_len < 32) {
782 SAFE_FREE(key_blob);
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);
786 }
787
788
789 if (offset + 32 > key_blob_len) {
790 SAFE_FREE(key_blob);
791 SAFE_FREE(file_content);
792 return SET_ERRNO(ERROR_CRYPTO_KEY, "OpenSSH private key truncated at public key: %s", key_path);
793 }
794
795 uint8_t ed25519_pubkey[32];
796 memcpy(ed25519_pubkey, key_blob + offset, 32);
797 offset += pubkey_data_len;
798
799
800
801
802
803 if (pubkey_len >= 51) {
804 size_t remaining_pubkey = pubkey_len - 51;
805 if (remaining_pubkey > 0) {
806 offset += remaining_pubkey;
807 }
808 }
809
810
811 if (offset + 4 > key_blob_len) {
812 SAFE_FREE(key_blob);
813 SAFE_FREE(file_content);
814 return SET_ERRNO(ERROR_CRYPTO_KEY, "OpenSSH private key truncated at privkey length: %s", key_path);
815 }
816
817 uint32_t privkey_len = read_u32_be(&key_blob[offset]);
818 offset += 4;
819
820 if (offset + privkey_len > key_blob_len) {
821 SAFE_FREE(key_blob);
822 SAFE_FREE(file_content);
823 return SET_ERRNO(ERROR_CRYPTO_KEY, "OpenSSH private key truncated at privkey data: %s", key_path);
824 }
825
826
827
828
829
830
831 if (privkey_len < 8) {
832 SAFE_FREE(key_blob);
833 SAFE_FREE(file_content);
834 return SET_ERRNO(ERROR_CRYPTO_KEY, "OpenSSH private key data too small: %s", key_path);
835 }
836
837
838 uint32_t checkint1 = read_u32_be(&key_blob[offset]);
839 uint32_t checkint2 = read_u32_be(&key_blob[offset + 4]);
840 offset += 8;
841
842 if (checkint1 != checkint2) {
843 SAFE_FREE(key_blob);
844 SAFE_FREE(file_content);
845 return SET_ERRNO(ERROR_CRYPTO_KEY, "OpenSSH private key checkints don't match: %s", key_path);
846 }
847
848
849 if (offset + 4 > key_blob_len) {
850 SAFE_FREE(key_blob);
851 SAFE_FREE(file_content);
852 return SET_ERRNO(ERROR_CRYPTO_KEY, "OpenSSH private key truncated at key type length: %s", key_path);
853 }
854
855 uint32_t key_type_len_priv = read_u32_be(&key_blob[offset]);
856
857 if (key_type_len_priv > key_blob_len || offset + 4 + key_type_len_priv > key_blob_len) {
858 SAFE_FREE(key_blob);
859 SAFE_FREE(file_content);
860 return SET_ERRNO(ERROR_CRYPTO_KEY, "OpenSSH private key type length overflow: %u", key_type_len_priv);
861 }
862 offset += 4 + key_type_len_priv;
863
864
865 if (offset + 4 > key_blob_len) {
866 SAFE_FREE(key_blob);
867 SAFE_FREE(file_content);
868 return SET_ERRNO(ERROR_CRYPTO_KEY, "OpenSSH private key truncated at pubkey length: %s", key_path);
869 }
870
871 uint32_t pubkey_len_priv = read_u32_be(&key_blob[offset]);
872
873 if (pubkey_len_priv > key_blob_len || offset + 4 + pubkey_len_priv > key_blob_len) {
874 SAFE_FREE(key_blob);
875 SAFE_FREE(file_content);
876 return SET_ERRNO(ERROR_CRYPTO_KEY, "OpenSSH private key public key length overflow: %u", pubkey_len_priv);
877 }
878 offset += 4 + pubkey_len_priv;
879
880
881 if (offset + 4 > key_blob_len) {
882 SAFE_FREE(key_blob);
883 SAFE_FREE(file_content);
884 return SET_ERRNO(ERROR_CRYPTO_KEY, "OpenSSH private key truncated at privkey length: %s", key_path);
885 }
886
887 uint32_t privkey_data_len = read_u32_be(&key_blob[offset]);
888 offset += 4;
889
890 if (offset + privkey_data_len > key_blob_len) {
891 SAFE_FREE(key_blob);
892 SAFE_FREE(file_content);
893 return SET_ERRNO(ERROR_CRYPTO_KEY, "OpenSSH private key truncated at privkey data: %s", key_path);
894 }
895
896
897
898 if (privkey_data_len < 64) {
899 SAFE_FREE(key_blob);
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);
903 }
904
905
906 uint8_t ed25519_privkey[32];
907 memcpy(ed25519_privkey, key_blob + offset, 32);
908
909
910
911
912
913 if (sodium_memcmp(key_blob + offset + 32, ed25519_pubkey, 32) != 0) {
914 SAFE_FREE(key_blob);
915 SAFE_FREE(file_content);
916 return SET_ERRNO(ERROR_CRYPTO_KEY, "OpenSSH private key public key mismatch: %s", key_path);
917 }
918
919
920 memset(key_out, 0, sizeof(private_key_t));
921 key_out->type = KEY_TYPE_ED25519;
922
923
924
925 memcpy(key_out->key.ed25519, ed25519_privkey, 32);
926 memcpy(key_out->key.ed25519 + 32, ed25519_pubkey, 32);
927
928
929 memcpy(key_out->public_key, ed25519_pubkey, 32);
930
931
932 SAFE_STRNCPY(key_out->key_comment, "ssh-ed25519", sizeof(key_out->key_comment) - 1);
933
934 SAFE_FREE(key_blob);
935 SAFE_FREE(file_content);
936
937 return ASCIICHAT_OK;
938}
int prompt_password_simple(const char *prompt, char *password, size_t max_len)
bool crypto_regex_extract_pem_base64(const char *file_content, char **base64_data_out)
bool ssh_agent_has_key(const public_key_t *public_key)
asciichat_error_t ssh_agent_add_key(const private_key_t *private_key, const char *key_path)
asciichat_error_t parse_ssh_ed25519_line(const char *line, uint8_t ed25519_pk[32])
asciichat_error_t validate_ssh_key_file(const char *key_path)
int safe_snprintf(char *buffer, size_t buffer_size, const char *format,...)
Safe formatted string printing to buffer.
FILE * platform_fopen(const char *filename, const char *mode)