254 行
8.3 KiB
C
254 行
8.3 KiB
C
/* pssh add —— SSH Agent 密钥管理 (平替 ssh-add;pssh ssha 为别名)
|
|
*
|
|
* 用法:
|
|
* paze-add 添加默认密钥 (~/.ssh/id_ed25519, id_rsa, id_ecdsa)
|
|
* paze-add <keyfile>... 添加指定私钥
|
|
* paze-add -l 列出 agent 身份
|
|
* paze-add -d <keyfile> 从 agent 移除指定密钥
|
|
* paze-add -D 移除全部密钥
|
|
*/
|
|
#include "paze/ssh_agent.h"
|
|
#include "paze/ssh_keys.h"
|
|
#include "paze/encoding.h"
|
|
#include "paze/hash.h"
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#ifdef _WIN32
|
|
#include <io.h>
|
|
#define CHMOD_0600(f) (void)0
|
|
#else
|
|
#include <sys/stat.h>
|
|
#define CHMOD_0600(f) chmod((f), 0600)
|
|
#endif
|
|
|
|
static void sshdir_path(char *buf, size_t sz, const char *rel) {
|
|
const char *home = NULL;
|
|
#ifdef _WIN32
|
|
home = getenv("USERPROFILE");
|
|
if (!home) home = getenv("HOME");
|
|
#else
|
|
home = getenv("HOME");
|
|
#endif
|
|
if (!home) home = ".";
|
|
snprintf(buf, sz, "%s/.ssh/%s", home, rel);
|
|
}
|
|
|
|
static uint8_t *read_file(const char *path, size_t *len) {
|
|
FILE *f = fopen(path, "rb");
|
|
if (!f) return NULL;
|
|
fseek(f, 0, SEEK_END);
|
|
long sz = ftell(f);
|
|
rewind(f);
|
|
if (sz <= 0 || sz > 16 * 1024 * 1024) { fclose(f); return NULL; }
|
|
uint8_t *buf = (uint8_t *)malloc((size_t)sz);
|
|
if (!buf) { fclose(f); return NULL; }
|
|
if (fread(buf, 1, (size_t)sz, f) != (size_t)sz) {
|
|
free(buf); fclose(f); return NULL;
|
|
}
|
|
fclose(f);
|
|
*len = (size_t)sz;
|
|
return buf;
|
|
}
|
|
|
|
static void fingerprint_str(const uint8_t *blob, size_t blen, char *out, size_t outsz) {
|
|
uint8_t h[32];
|
|
paze_sha256(blob, blen, h);
|
|
char b64[64];
|
|
paze_base64_encode(h, 32, b64);
|
|
size_t n = strlen(b64);
|
|
while (n > 0 && b64[n - 1] == '=') b64[--n] = '\0';
|
|
snprintf(out, outsz, "SHA256:%s", b64);
|
|
}
|
|
|
|
/* 从公钥 blob 提取算法名 */
|
|
static const char *alg_desc(const uint8_t *blob, size_t blen) {
|
|
if (blen >= 4) {
|
|
uint32_t kl = ((uint32_t)blob[0] << 24) | ((uint32_t)blob[1] << 16) |
|
|
((uint32_t)blob[2] << 8) | blob[3];
|
|
if (4 + kl <= blen && kl < 64) {
|
|
static char buf[64];
|
|
memcpy(buf, blob + 4, kl);
|
|
buf[kl] = '\0';
|
|
return buf;
|
|
}
|
|
}
|
|
return "unknown";
|
|
}
|
|
|
|
/* 添加单个私钥文件 */
|
|
static int add_keyfile(ssh_agent_t *a, const char *path) {
|
|
size_t len = 0;
|
|
uint8_t *data = read_file(path, &len);
|
|
if (!data) {
|
|
fprintf(stderr, "paze-add: cannot read %s\n", path);
|
|
return -1;
|
|
}
|
|
ssh_privkey_t *k = NULL;
|
|
if (ssh_privkey_parse(data, len, &k) != 0) {
|
|
fprintf(stderr, "paze-add: unsupported or corrupt key %s\n", path);
|
|
free(data);
|
|
return -1;
|
|
}
|
|
/* 注释:优先取同路径 .pub 首行,否则用文件名 */
|
|
char comment[512];
|
|
comment[0] = '\0';
|
|
char pubpath[1100];
|
|
snprintf(pubpath, sizeof(pubpath), "%s.pub", path);
|
|
FILE *pf = fopen(pubpath, "r");
|
|
if (pf) {
|
|
char line[2048];
|
|
if (fgets(line, sizeof(line), pf)) {
|
|
/* 格式: type b64 comment...;取 comment 部分 */
|
|
char *p = line;
|
|
char *tok = NULL;
|
|
p = strchr(p, ' ');
|
|
if (p) {
|
|
p++;
|
|
tok = strchr(p, ' ');
|
|
if (tok) {
|
|
tok++;
|
|
char *e = tok;
|
|
while (*e && *e != '\n' && *e != '\r') e++;
|
|
size_t cn = (size_t)(e - tok);
|
|
if (cn > sizeof(comment) - 1) cn = sizeof(comment) - 1;
|
|
memcpy(comment, tok, cn);
|
|
comment[cn] = '\0';
|
|
}
|
|
}
|
|
}
|
|
fclose(pf);
|
|
if (!comment[0]) snprintf(comment, sizeof(comment), "%s", path);
|
|
} else {
|
|
snprintf(comment, sizeof(comment), "%s", path);
|
|
}
|
|
|
|
uint8_t *payload = NULL; size_t plen = 0;
|
|
if (ssh_privkey_identity_payload(k, comment, &payload, &plen) != 0 ||
|
|
ssh_agent_add_identity(a, payload, plen) != 0) {
|
|
fprintf(stderr, "paze-add: failed to add %s\n", path);
|
|
free(payload);
|
|
ssh_privkey_free(k);
|
|
free(data);
|
|
return -1;
|
|
}
|
|
size_t blen = 0;
|
|
const uint8_t *blob = ssh_privkey_pubblob(k, &blen);
|
|
char fp[128];
|
|
fingerprint_str(blob, blen, fp, sizeof(fp));
|
|
printf("Identity added: %s (%s)\n", fp, comment);
|
|
free(payload);
|
|
ssh_privkey_free(k);
|
|
free(data);
|
|
return 0;
|
|
}
|
|
|
|
int pssh_cmd_add(int argc, char **argv) {
|
|
if (argc < 2) {
|
|
fprintf(stderr,
|
|
"用法: pssh add [选项] [keyfile...]\n"
|
|
" <keyfile>... 添加私钥到 agent\n"
|
|
" -l 列出 agent 身份\n"
|
|
" -d <keyfile> 从 agent 移除指定密钥\n"
|
|
" -D 清空全部密钥\n");
|
|
return 1;
|
|
}
|
|
int do_list = 0, do_remove_all = 0, do_remove = 0;
|
|
const char *remove_file = NULL;
|
|
const char *files[64];
|
|
int nfiles = 0;
|
|
|
|
for (int i = 1; i < argc; i++) {
|
|
if (strcmp(argv[i], "-l") == 0) do_list = 1;
|
|
else if (strcmp(argv[i], "-D") == 0) do_remove_all = 1;
|
|
else if (strcmp(argv[i], "-d") == 0) {
|
|
do_remove = 1;
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') remove_file = argv[++i];
|
|
} else if (argv[i][0] == '-') continue;
|
|
else if (nfiles < 64) files[nfiles++] = argv[i];
|
|
}
|
|
|
|
ssh_agent_t *a = NULL;
|
|
if (ssh_agent_connect(&a) != 0) {
|
|
fprintf(stderr, "paze-add: cannot connect to ssh-agent "
|
|
"(run paze-agent first)\n");
|
|
return 1;
|
|
}
|
|
|
|
int rc = 0;
|
|
if (do_list) {
|
|
uint8_t **blobs = NULL; size_t *blens = NULL;
|
|
char **comments = NULL; int count = 0;
|
|
if (ssh_agent_list(a, &blobs, &blens, &comments, &count) != 0) {
|
|
fprintf(stderr, "paze-add: failed to list identities\n");
|
|
rc = 1;
|
|
} else {
|
|
if (count == 0)
|
|
printf("The agent has no identities.\n");
|
|
for (int i = 0; i < count; i++) {
|
|
char fp[128];
|
|
fingerprint_str(blobs[i], blens[i], fp, sizeof(fp));
|
|
printf("%s %s (%s)\n", fp,
|
|
comments[i] && comments[i][0] ? comments[i] : "",
|
|
alg_desc(blobs[i], blens[i]));
|
|
}
|
|
ssh_agent_list_free(blobs, blens, comments, count);
|
|
}
|
|
} else if (do_remove_all) {
|
|
if (ssh_agent_remove_all(a) != 0) {
|
|
fprintf(stderr, "paze-add: failed to remove all identities\n");
|
|
rc = 1;
|
|
} else {
|
|
printf("All identities removed.\n");
|
|
}
|
|
} else if (do_remove) {
|
|
if (remove_file) {
|
|
size_t len = 0;
|
|
uint8_t *data = read_file(remove_file, &len);
|
|
if (!data) {
|
|
fprintf(stderr, "paze-add: cannot read %s\n", remove_file);
|
|
rc = 1;
|
|
} else {
|
|
ssh_privkey_t *k = NULL;
|
|
if (ssh_privkey_parse(data, len, &k) != 0) {
|
|
fprintf(stderr, "paze-add: cannot parse %s\n", remove_file);
|
|
rc = 1;
|
|
} else {
|
|
size_t blen = 0;
|
|
const uint8_t *blob = ssh_privkey_pubblob(k, &blen);
|
|
if (ssh_agent_remove_identity(a, blob, blen) != 0)
|
|
fprintf(stderr, "paze-add: identity not found in agent\n");
|
|
else
|
|
printf("Identity removed.\n");
|
|
ssh_privkey_free(k);
|
|
}
|
|
free(data);
|
|
}
|
|
} else {
|
|
fprintf(stderr, "paze-add: usage: paze-add -d <keyfile>\n");
|
|
rc = 1;
|
|
}
|
|
} else {
|
|
/* 添加:指定文件或默认密钥 */
|
|
if (nfiles == 0) {
|
|
static const char *defs[] = { "id_ed25519", "id_rsa", "id_ecdsa" };
|
|
char p[1100];
|
|
for (int i = 0; i < 3; i++) {
|
|
sshdir_path(p, sizeof(p), defs[i]);
|
|
if (read_file(p, NULL) != NULL)
|
|
files[nfiles++] = p;
|
|
}
|
|
if (nfiles == 0) {
|
|
fprintf(stderr, "paze-add: no default keys found in ~/.ssh\n");
|
|
rc = 1;
|
|
}
|
|
}
|
|
for (int i = 0; i < nfiles; i++)
|
|
if (add_keyfile(a, files[i]) != 0) rc = 1;
|
|
}
|
|
|
|
ssh_agent_close(a);
|
|
return rc;
|
|
}
|