LCOV - code coverage report
Current view: top level - libemail/src/platform/posix - credential_key.c (source / functions) Coverage Total Hit
Test: coverage-functional.info Lines: 83.3 % 36 30
Test Date: 2026-05-07 15:53:08 Functions: 100.0 % 4 4

            Line data    Source code
       1              : /**
       2              :  * POSIX credential key derivation.
       3              :  *
       4              :  * Priority order for key source material:
       5              :  *   1. ~/.ssh/id_ed25519
       6              :  *   2. ~/.ssh/id_ecdsa
       7              :  *   3. ~/.ssh/id_rsa
       8              :  *   4. /etc/machine-id         (Linux)
       9              :  *   4. kern.uuid sysctl        (macOS)
      10              :  *
      11              :  * Key derivation: HMAC-SHA256(key=<source bytes>, msg="email-cli:<user>:<email>")
      12              :  */
      13              : #include "../credential_key.h"
      14              : #include <openssl/hmac.h>
      15              : #include <openssl/evp.h>
      16              : #include <stdio.h>
      17              : #include <string.h>
      18              : #include <stdlib.h>
      19              : #include <unistd.h>
      20              : #include <pwd.h>
      21              : #include <sys/types.h>
      22              : #ifdef __APPLE__
      23              : #include <sys/sysctl.h>
      24              : #endif
      25              : 
      26              : /* Read up to bufsize-1 bytes from path into buf. Returns bytes read, -1 on error. */
      27         4032 : static ssize_t read_source(const char *path, char *buf, size_t bufsize) {
      28         4032 :     FILE *fp = fopen(path, "rb");
      29         4032 :     if (!fp) return -1;
      30         1008 :     size_t n = fread(buf, 1, bufsize - 1, fp);
      31         1008 :     fclose(fp);
      32         1008 :     if (n == 0) return -1;
      33         1008 :     buf[n] = '\0';
      34         1008 :     return (ssize_t)n;
      35              : }
      36              : 
      37         1008 : static const char *get_username(void) {
      38         1008 :     const char *u = getenv("USER");
      39         1008 :     if (u && *u) return u;
      40            0 :     struct passwd *pw = getpwuid(getuid());
      41            0 :     return (pw && pw->pw_name) ? pw->pw_name : "unknown";
      42              : }
      43              : 
      44         1008 : static const char *get_home(void) {
      45         1008 :     const char *h = getenv("HOME");
      46         1008 :     if (h && *h) return h;
      47            0 :     struct passwd *pw = getpwuid(getuid());
      48            0 :     return (pw && pw->pw_dir) ? pw->pw_dir : NULL;
      49              : }
      50              : 
      51         1008 : int platform_derive_credential_key(const char *email, unsigned char key_out[32]) {
      52              :     static const char *SSH_KEYS[] = {
      53              :         "/.ssh/id_ed25519",
      54              :         "/.ssh/id_ecdsa",
      55              :         "/.ssh/id_rsa",
      56              :         NULL
      57              :     };
      58              : 
      59              :     char source[65536]; /* large enough for any SSH private key */
      60         1008 :     ssize_t source_len = -1;
      61              : 
      62              :     /* 1. Try SSH private keys */
      63         1008 :     const char *home = get_home();
      64         1008 :     if (home) {
      65         4032 :         for (int i = 0; SSH_KEYS[i] && source_len < 0; i++) {
      66              :             char path[4096];
      67         3024 :             snprintf(path, sizeof(path), "%s%s", home, SSH_KEYS[i]);
      68         3024 :             source_len = read_source(path, source, sizeof(source));
      69              :         }
      70              :     }
      71              : 
      72              :     /* 2. Machine-specific fallback */
      73              : #ifdef __APPLE__
      74              :     if (source_len < 0) {
      75              :         /* macOS: hardware UUID via kern.uuid sysctl (stable across reboots/updates) */
      76              :         size_t len = sizeof(source) - 1;
      77              :         if (sysctlbyname("kern.uuid", source, &len, NULL, 0) == 0) {
      78              :             source[len] = '\0';
      79              :             source_len = (ssize_t)len;
      80              :         }
      81              :     }
      82              : #else
      83         1008 :     if (source_len < 0)
      84         1008 :         source_len = read_source("/etc/machine-id", source, sizeof(source));
      85              : #endif
      86              : 
      87         1008 :     if (source_len <= 0)
      88            0 :         return -1;
      89              : 
      90              :     /* Build HMAC message: "email-cli:<username>:<email>" */
      91         1008 :     const char *username = get_username();
      92              :     char context[1024];
      93         1008 :     snprintf(context, sizeof(context), "email-cli:%s:%s",
      94              :              username, email ? email : "");
      95              : 
      96              :     /* HMAC-SHA256(key=source, msg=context) → 32 bytes */
      97         1008 :     unsigned int out_len = 32;
      98         1008 :     if (!HMAC(EVP_sha256(),
      99              :               source, (int)source_len,
     100         1008 :               (unsigned char *)context, (int)strlen(context),
     101              :               key_out, &out_len))
     102            0 :         return -1;
     103              : 
     104         1008 :     return 0;
     105              : }
        

Generated by: LCOV version 2.0-1