summaryrefslogtreecommitdiff
path: root/plugins/ssh/ssh_keys.c
diff options
context:
space:
mode:
Diffstat (limited to 'plugins/ssh/ssh_keys.c')
-rw-r--r--plugins/ssh/ssh_keys.c146
1 files changed, 146 insertions, 0 deletions
diff --git a/plugins/ssh/ssh_keys.c b/plugins/ssh/ssh_keys.c
new file mode 100644
index 0000000..656b9d4
--- /dev/null
+++ b/plugins/ssh/ssh_keys.c
@@ -0,0 +1,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;
+}
git clone -b <branch> https://cgit.mode42.com/<repo>.git
git clone -b <branch> git://cgit.mode42.com/<repo>.git

info@mode42.com