/* * LITLS -- OpenSSH private key parser * Parses unencrypted OpenSSH ed25519 private keys ("openssh-key-v1" format). * Only supports ciphername="none" (unencrypted keys). */ #include "litls.h" #include #define OPENSSH_MAGIC "openssh-key-v1\0" #define OPENSSH_MAGIC_LEN 15 /* Read a uint32 big-endian from buf, advance pos. */ static int read_u32(const uint8_t *buf, size_t len, size_t *pos, uint32_t *out) { if (*pos + 4 > len) return -1; *out = ((uint32_t)buf[*pos] << 24) | ((uint32_t)buf[*pos + 1] << 16) | ((uint32_t)buf[*pos + 2] << 8) | (uint32_t)buf[*pos + 3]; *pos += 4; return 0; } /* Read a length-prefixed string, returning pointer and length. */ static int read_string(const uint8_t *buf, size_t len, size_t *pos, const uint8_t **out, uint32_t *out_len) { uint32_t slen; if (read_u32(buf, len, pos, &slen) != 0) return -1; if (*pos + slen > len) return -1; *out = buf + *pos; *out_len = slen; *pos += slen; return 0; } /* Skip a length-prefixed string. */ static int skip_string(const uint8_t *buf, size_t len, size_t *pos) { const uint8_t *dummy; uint32_t dummy_len; return read_string(buf, len, pos, &dummy, &dummy_len); } int litls_openssh_parse_ed25519(const uint8_t *data, size_t data_len, uint8_t seed[32], uint8_t public_key[32]) { size_t pos = 0; uint32_t nkeys, check1, check2; const uint8_t *str; uint32_t slen; /* Validate magic */ if (data_len < OPENSSH_MAGIC_LEN) return -1; if (memcmp(data, OPENSSH_MAGIC, OPENSSH_MAGIC_LEN) != 0) return -1; pos = OPENSSH_MAGIC_LEN; /* ciphername must be "none" */ if (read_string(data, data_len, &pos, &str, &slen) != 0) return -1; if (slen != 4 || memcmp(str, "none", 4) != 0) return -2; /* encrypted key */ /* kdfname must be "none" */ if (read_string(data, data_len, &pos, &str, &slen) != 0) return -1; if (slen != 4 || memcmp(str, "none", 4) != 0) return -2; /* kdfoptions (empty for unencrypted) */ if (skip_string(data, data_len, &pos) != 0) return -1; /* number of keys */ if (read_u32(data, data_len, &pos, &nkeys) != 0) return -1; if (nkeys != 1) return -1; /* only single key supported */ /* Skip public key blob */ if (skip_string(data, data_len, &pos) != 0) return -1; /* Private section (not encrypted) */ const uint8_t *priv_section; uint32_t priv_len; if (read_string(data, data_len, &pos, &priv_section, &priv_len) != 0) return -1; /* Parse private section */ size_t pp = 0; if (read_u32(priv_section, priv_len, &pp, &check1) != 0) return -1; if (read_u32(priv_section, priv_len, &pp, &check2) != 0) return -1; if (check1 != check2) return -3; /* checkint mismatch (wrong password or corrupt) */ /* Key type string: "ssh-ed25519" */ if (read_string(priv_section, priv_len, &pp, &str, &slen) != 0) return -1; if (slen != 11 || memcmp(str, "ssh-ed25519", 11) != 0) return -4; /* not ed25519 */ /* Public key (32 bytes) */ if (read_string(priv_section, priv_len, &pp, &str, &slen) != 0) return -1; if (slen != 32) return -1; memcpy(public_key, str, 32); /* Private key (64 bytes: 32-byte seed + 32-byte pubkey) */ if (read_string(priv_section, priv_len, &pp, &str, &slen) != 0) return -1; if (slen != 64) return -1; memcpy(seed, str, 32); /* Verify consistency: second half of private key should match public key */ if (litls_secure_memcmp(str + 32, public_key, 32) != 0) return -1; return 0; }