文件

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