1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
|
#include "hybbx/ssh.h"
#include "hybbx/util.h"
#include "hybbx/log.h"
#include <libssh/libssh.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <time.h>
#include <unistd.h>
static hybbx_result_t mkdir_keys_dir(const char *keys_dir)
{
char parent[HYBBX_PATH_MAX];
struct stat st;
if (keys_dir == NULL || keys_dir[0] == '\0') {
return HYBBX_ERR_INVALID;
}
if (stat(keys_dir, &st) == 0) {
return S_ISDIR(st.st_mode) ? HYBBX_OK : HYBBX_ERR_IO;
}
if (hybbx_path_dirname(keys_dir, parent, sizeof(parent)) == HYBBX_OK &&
parent[0] != '\0' && strcmp(parent, keys_dir) != 0 &&
stat(parent, &st) != 0) {
if (mkdir_keys_dir(parent) != HYBBX_OK) {
return HYBBX_ERR_IO;
}
}
if (mkdir(keys_dir, 0700) != 0) {
return HYBBX_ERR_IO;
}
return HYBBX_OK;
}
static hybbx_result_t generate_ed25519_keypair(const char *priv_path,
const char *pub_path)
{
ssh_key key = NULL;
int rc;
rc = ssh_pki_generate(SSH_KEYTYPE_ED25519, 0, &key);
if (rc != SSH_OK || key == NULL) {
hybbx_log_warn("[ssh] Ed25519 generate failed (rc=%d)", rc);
return HYBBX_ERR_IO;
}
rc = ssh_pki_export_privkey_file(key, NULL, NULL, NULL, priv_path);
if (rc != SSH_OK) {
hybbx_log_warn("[ssh] export private key failed: %s",
ssh_get_error(key));
ssh_key_free(key);
return HYBBX_ERR_IO;
}
rc = ssh_pki_export_pubkey_file(key, pub_path);
ssh_key_free(key);
if (rc != SSH_OK) {
hybbx_log_warn("[ssh] export public key failed");
return HYBBX_ERR_IO;
}
(void)chmod(priv_path, 0600);
(void)chmod(pub_path, 0644);
return HYBBX_OK;
}
static int hostkey_needs_rotation(const char *priv_path)
{
struct stat st;
time_t now;
time_t age;
if (stat(priv_path, &st) != 0) {
return 0;
}
now = time(NULL);
if (now <= st.st_mtime) {
return 0;
}
age = now - st.st_mtime;
return age > (time_t)HYBBX_SSH_HOSTKEY_VALID_DAYS * 86400L;
}
hybbx_result_t hybbx_ssh_keys_ensure(const char *keys_dir,
char *hostkey_path,
size_t hostkey_path_len)
{
char resolved_dir[HYBBX_PATH_MAX];
char priv_path[HYBBX_PATH_MAX];
char pub_path[HYBBX_PATH_MAX];
struct stat st;
if (keys_dir == NULL || hostkey_path == NULL || hostkey_path_len == 0) {
return HYBBX_ERR_INVALID;
}
if (ssh_init() < 0) {
return HYBBX_ERR_IO;
}
if (hybbx_path_resolve(resolved_dir, sizeof(resolved_dir),
keys_dir) != HYBBX_OK) {
hybbx_strlcpy(resolved_dir, keys_dir, sizeof(resolved_dir));
}
if (mkdir_keys_dir(resolved_dir) != HYBBX_OK) {
return HYBBX_ERR_IO;
}
if (hybbx_path_join(priv_path, sizeof(priv_path), resolved_dir,
HYBBX_SSH_HOSTKEY_ED25519) != HYBBX_OK) {
return HYBBX_ERR_IO;
}
if (hybbx_path_join(pub_path, sizeof(pub_path), resolved_dir,
HYBBX_SSH_HOSTKEY_ED25519 ".pub") != HYBBX_OK) {
return HYBBX_ERR_IO;
}
if (stat(priv_path, &st) == 0 && hostkey_needs_rotation(priv_path)) {
hybbx_log_warn("[ssh] host key older than %u days — rotating %s",
HYBBX_SSH_HOSTKEY_VALID_DAYS, priv_path);
(void)unlink(priv_path);
(void)unlink(pub_path);
}
if (stat(priv_path, &st) != 0) {
if (generate_ed25519_keypair(priv_path, pub_path) != HYBBX_OK) {
hybbx_log_warn("[ssh] failed to generate host key in %s",
resolved_dir);
return HYBBX_ERR_IO;
}
hybbx_log_info("[ssh] generated host key %s (valid %u days)", priv_path,
HYBBX_SSH_HOSTKEY_VALID_DAYS);
}
hybbx_strlcpy(hostkey_path, priv_path, hostkey_path_len);
return HYBBX_OK;
}
|