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 : }
|