mirror of
https://git.tartarus.org/simon/putty.git
synced 2025-01-09 17:38:00 +00:00
PuTTYgen: options to add and remove certificates.
This allows you to actually use an OpenSSH user certificate for authentication, by combining the PPK you already had with the certificate from the CA to produce a new PPK whose public half contains the certificate. I don't intend that this should be the only way to do it. It's cumbersome to have to use the key passphrase in order to re-encrypt the modified PPK. But the flip side is that once you've done it you have everything you need in one convenient file, and also, the certificate itself is covered by the PPK's tamperproofing (in case you can think of any attacks in which the admin of your file server swaps out just the certificate for a different one on the same key). So this is *a* useful way to do it, even if not the only useful way. The new options to add and remove certificates are supported by both Windows GUI PuTTYgen and cmdgen. cmdgen can also operate on pure public keys, so you can say 'puttygen --remove-certificate foo-cert.pub' and get back the underlying foo.pub; Windows PuTTYgen doesn't support that mode, but only because it doesn't in general have any support for the loaded key not being a full public+private pair.
This commit is contained in:
parent
4cde00efc0
commit
7cb3142a57
157
cmdgen.c
157
cmdgen.c
@ -137,6 +137,8 @@ void help(void)
|
||||
" -L equivalent to `-O public-openssh'\n"
|
||||
" -p equivalent to `-O public'\n"
|
||||
" --dump equivalent to `-O text'\n"
|
||||
" --certificate file incorporate a certificate into the key\n"
|
||||
" --remove-certificate remove any certificate from the key\n"
|
||||
" --reencrypt load a key and save it with fresh "
|
||||
"encryption\n"
|
||||
" --old-passphrase file\n"
|
||||
@ -250,6 +252,8 @@ int main(int argc, char **argv)
|
||||
char *old_passphrase = NULL, *new_passphrase = NULL;
|
||||
bool load_encrypted;
|
||||
const char *random_device = NULL;
|
||||
char *certfile = NULL;
|
||||
bool remove_cert = false;
|
||||
int exit_status = 0;
|
||||
const PrimeGenerationPolicy *primegen = &primegen_probabilistic;
|
||||
bool strong_rsa = false;
|
||||
@ -392,6 +396,18 @@ int main(int argc, char **argv)
|
||||
}
|
||||
} else if (!strcmp(opt, "-strong-rsa")) {
|
||||
strong_rsa = true;
|
||||
} else if (!strcmp(opt, "-certificate")) {
|
||||
if (!val && argc > 1)
|
||||
--argc, val = *++argv;
|
||||
if (!val) {
|
||||
errs = true;
|
||||
fprintf(stderr, "puttygen: option `-%s'"
|
||||
" expects an argument\n", opt);
|
||||
} else {
|
||||
certfile = val;
|
||||
}
|
||||
} else if (!strcmp(opt, "-remove-certificate")) {
|
||||
remove_cert = true;
|
||||
} else if (!strcmp(opt, "-reencrypt")) {
|
||||
reencrypt = true;
|
||||
} else if (!strcmp(opt, "-ppk-param") ||
|
||||
@ -799,7 +815,8 @@ int main(int argc, char **argv)
|
||||
outfiletmp = dupcat(outfile, ".tmp");
|
||||
}
|
||||
|
||||
if (!change_passphrase && !comment && !reencrypt) {
|
||||
if (!change_passphrase && !comment && !reencrypt && !certfile &&
|
||||
!remove_cert) {
|
||||
fprintf(stderr, "puttygen: this command would perform no useful"
|
||||
" action\n");
|
||||
RETURN(1);
|
||||
@ -849,6 +866,26 @@ int main(int argc, char **argv)
|
||||
RETURN(1);
|
||||
}
|
||||
|
||||
/*
|
||||
* Check consistency properties relating to certificates.
|
||||
*/
|
||||
if (certfile && !(sshver == 2 && intype_has_private &&
|
||||
outtype_has_private && infile)) {
|
||||
fprintf(stderr, "puttygen: certificates can only be added to "
|
||||
"existing SSH-2 private key files\n");
|
||||
RETURN(1);
|
||||
}
|
||||
if (remove_cert && !(sshver == 2 && infile)) {
|
||||
fprintf(stderr, "puttygen: certificates can only be removed from "
|
||||
"existing SSH-2 key files\n");
|
||||
RETURN(1);
|
||||
}
|
||||
if (certfile && remove_cert) {
|
||||
fprintf(stderr, "puttygen: cannot both add and remove a "
|
||||
"certificate\n");
|
||||
RETURN(1);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------
|
||||
* Now we're ready to actually do some stuff.
|
||||
*/
|
||||
@ -1081,6 +1118,124 @@ int main(int argc, char **argv)
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Swap out the public key for a different one, if asked to.
|
||||
*/
|
||||
if (certfile) {
|
||||
Filename *certfilename = filename_from_str(certfile);
|
||||
LoadedFile *certfile_lf;
|
||||
const char *error = NULL;
|
||||
|
||||
if (!strcmp(certfile, "-"))
|
||||
certfile_lf = lf_load_keyfile_fp(stdin, &error);
|
||||
else
|
||||
certfile_lf = lf_load_keyfile(certfilename, &error);
|
||||
|
||||
filename_free(certfilename);
|
||||
|
||||
if (!certfile_lf) {
|
||||
fprintf(stderr, "puttygen: unable to load certificate file `%s': "
|
||||
"%s\n", certfile, error);
|
||||
RETURN(1);
|
||||
}
|
||||
|
||||
char *algname = NULL;
|
||||
char *comment = NULL;
|
||||
strbuf *pub = strbuf_new();
|
||||
if (!ppk_loadpub_s(BinarySource_UPCAST(certfile_lf), &algname,
|
||||
BinarySink_UPCAST(pub), &comment, &error)) {
|
||||
fprintf(stderr, "puttygen: unable to load certificate file `%s': "
|
||||
"%s\n", certfile, error);
|
||||
strbuf_free(pub);
|
||||
sfree(algname);
|
||||
sfree(comment);
|
||||
lf_free(certfile_lf);
|
||||
RETURN(1);
|
||||
}
|
||||
|
||||
lf_free(certfile_lf);
|
||||
sfree(comment);
|
||||
|
||||
const ssh_keyalg *alg = find_pubkey_alg(algname);
|
||||
if (!alg) {
|
||||
fprintf(stderr, "puttygen: certificate file `%s' has unsupported "
|
||||
"algorithm name `%s'\n", certfile, algname);
|
||||
strbuf_free(pub);
|
||||
sfree(algname);
|
||||
RETURN(1);
|
||||
}
|
||||
|
||||
sfree(algname);
|
||||
|
||||
/* Check the two public keys match apart from certificates */
|
||||
strbuf *old_basepub = strbuf_new();
|
||||
ssh_key_public_blob(ssh_key_base_key(ssh2key->key),
|
||||
BinarySink_UPCAST(old_basepub));
|
||||
|
||||
ssh_key *new_pubkey = ssh_key_new_pub(alg, ptrlen_from_strbuf(pub));
|
||||
strbuf *new_basepub = strbuf_new();
|
||||
ssh_key_public_blob(ssh_key_base_key(new_pubkey),
|
||||
BinarySink_UPCAST(new_basepub));
|
||||
ssh_key_free(new_pubkey);
|
||||
|
||||
bool match = ptrlen_eq_ptrlen(ptrlen_from_strbuf(old_basepub),
|
||||
ptrlen_from_strbuf(new_basepub));
|
||||
strbuf_free(old_basepub);
|
||||
strbuf_free(new_basepub);
|
||||
|
||||
if (!match) {
|
||||
fprintf(stderr, "puttygen: certificate in `%s' does not match "
|
||||
"public key in `%s'\n", certfile, infile);
|
||||
strbuf_free(pub);
|
||||
RETURN(1);
|
||||
}
|
||||
|
||||
strbuf *priv = strbuf_new_nm();
|
||||
ssh_key_private_blob(ssh2key->key, BinarySink_UPCAST(priv));
|
||||
ssh_key *newkey = ssh_key_new_priv(
|
||||
alg, ptrlen_from_strbuf(pub), ptrlen_from_strbuf(priv));
|
||||
strbuf_free(pub);
|
||||
strbuf_free(priv);
|
||||
|
||||
if (!newkey) {
|
||||
fprintf(stderr, "puttygen: unable to combine certificate in `%s' "
|
||||
"with private key\n", certfile);
|
||||
RETURN(1);
|
||||
}
|
||||
|
||||
ssh_key_free(ssh2key->key);
|
||||
ssh2key->key = newkey;
|
||||
} else if (remove_cert) {
|
||||
/*
|
||||
* Removing a certificate can be meaningfully done to a pure
|
||||
* public key blob, as well as a full key pair.
|
||||
*/
|
||||
if (ssh2key) {
|
||||
ssh_key *newkey = ssh_key_clone(ssh_key_base_key(ssh2key->key));
|
||||
ssh_key_free(ssh2key->key);
|
||||
ssh2key->key = newkey;
|
||||
} else if (ssh2blob) {
|
||||
ptrlen algname = pubkey_blob_to_alg_name(
|
||||
ptrlen_from_strbuf(ssh2blob));
|
||||
|
||||
const ssh_keyalg *alg = find_pubkey_alg_len(algname);
|
||||
|
||||
if (!alg) {
|
||||
fprintf(stderr, "puttygen: input file `%s' has unsupported "
|
||||
"algorithm name `%.*s'\n", infile,
|
||||
PTRLEN_PRINTF(algname));
|
||||
RETURN(1);
|
||||
}
|
||||
|
||||
ssh_key *tmpkey = ssh_key_new_pub(
|
||||
alg, ptrlen_from_strbuf(ssh2blob));
|
||||
strbuf_clear(ssh2blob);
|
||||
ssh_key_public_blob(ssh_key_base_key(tmpkey),
|
||||
BinarySink_UPCAST(ssh2blob));
|
||||
ssh_key_free(tmpkey);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Unless we're changing the passphrase, the old one (if any) is a
|
||||
* reasonable default.
|
||||
|
@ -758,7 +758,8 @@ enum {
|
||||
IDC_GIVEHELP,
|
||||
IDC_IMPORT,
|
||||
IDC_EXPORT_OPENSSH_AUTO, IDC_EXPORT_OPENSSH_NEW,
|
||||
IDC_EXPORT_SSHCOM
|
||||
IDC_EXPORT_SSHCOM,
|
||||
IDC_ADDCERT, IDC_REMCERT,
|
||||
};
|
||||
|
||||
static const int nokey_ids[] = { IDC_NOKEY, 0 };
|
||||
@ -812,6 +813,8 @@ void ui_set_state(HWND hwnd, struct MainDlgState *state, int status)
|
||||
MF_GRAYED|MF_BYCOMMAND);
|
||||
EnableMenuItem(state->cvtmenu, IDC_EXPORT_SSHCOM,
|
||||
MF_GRAYED|MF_BYCOMMAND);
|
||||
EnableMenuItem(state->keymenu, IDC_ADDCERT, MF_GRAYED|MF_BYCOMMAND);
|
||||
EnableMenuItem(state->keymenu, IDC_REMCERT, MF_GRAYED|MF_BYCOMMAND);
|
||||
break;
|
||||
case 1: /* generating key */
|
||||
hidemany(hwnd, nokey_ids, true);
|
||||
@ -845,6 +848,8 @@ void ui_set_state(HWND hwnd, struct MainDlgState *state, int status)
|
||||
MF_GRAYED|MF_BYCOMMAND);
|
||||
EnableMenuItem(state->cvtmenu, IDC_EXPORT_SSHCOM,
|
||||
MF_GRAYED|MF_BYCOMMAND);
|
||||
EnableMenuItem(state->keymenu, IDC_ADDCERT, MF_GRAYED|MF_BYCOMMAND);
|
||||
EnableMenuItem(state->keymenu, IDC_REMCERT, MF_GRAYED|MF_BYCOMMAND);
|
||||
break;
|
||||
case 2:
|
||||
hidemany(hwnd, nokey_ids, true);
|
||||
@ -884,6 +889,35 @@ void ui_set_state(HWND hwnd, struct MainDlgState *state, int status)
|
||||
do_export_menuitem(IDC_EXPORT_OPENSSH_NEW, SSH_KEYTYPE_OPENSSH_NEW);
|
||||
do_export_menuitem(IDC_EXPORT_SSHCOM, SSH_KEYTYPE_SSHCOM);
|
||||
#undef do_export_menuitem
|
||||
/*
|
||||
* Enable certificate menu items similarly.
|
||||
*/
|
||||
{
|
||||
bool add_cert_allowed = false, rem_cert_allowed = false;
|
||||
|
||||
if (state->ssh2 && state->ssh2key.key) {
|
||||
const ssh_keyalg *alg = ssh_key_alg(state->ssh2key.key);
|
||||
if (alg->is_certificate) {
|
||||
/* If there's a certificate, we can remove it */
|
||||
rem_cert_allowed = true;
|
||||
/* And reset to the base algorithm for the next check */
|
||||
alg = alg->base_alg;
|
||||
}
|
||||
|
||||
/* Now, do we have any certified version of this alg? */
|
||||
for (size_t i = 0; i < n_keyalgs; i++) {
|
||||
if (all_keyalgs[i]->base_alg == alg) {
|
||||
add_cert_allowed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
EnableMenuItem(state->keymenu, IDC_ADDCERT, MF_BYCOMMAND |
|
||||
(add_cert_allowed ? MF_ENABLED : MF_GRAYED));
|
||||
EnableMenuItem(state->keymenu, IDC_REMCERT, MF_BYCOMMAND |
|
||||
(rem_cert_allowed ? MF_ENABLED : MF_GRAYED));
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -994,6 +1028,23 @@ void ui_set_fptype(HWND hwnd, struct MainDlgState *state, int option)
|
||||
}
|
||||
}
|
||||
|
||||
static void update_ui_after_ssh2_pubkey_change(
|
||||
HWND hwnd, struct MainDlgState *state)
|
||||
{
|
||||
/* Smaller version of update_ui_after_load which doesn't need to
|
||||
* be told things like the passphrase, which we aren't changing
|
||||
* anyway */
|
||||
char *savecomment = state->ssh2key.comment;
|
||||
state->ssh2key.comment = NULL;
|
||||
char *fp = ssh2_fingerprint(state->ssh2key.key, state->fptype);
|
||||
state->ssh2key.comment = savecomment;
|
||||
|
||||
SetDlgItemText(hwnd, IDC_FINGERPRINT, fp);
|
||||
sfree(fp);
|
||||
|
||||
setupbigedit2(hwnd, IDC_KEYDISPLAY, IDC_PKSTATIC, &state->ssh2key);
|
||||
}
|
||||
|
||||
static void update_ui_after_load(HWND hwnd, struct MainDlgState *state,
|
||||
const char *passphrase, int type,
|
||||
RSAKey *newkey1, ssh2_userkey *newkey2)
|
||||
@ -1024,24 +1075,12 @@ static void update_ui_after_load(HWND hwnd, struct MainDlgState *state,
|
||||
*/
|
||||
setupbigedit1(hwnd, IDC_KEYDISPLAY, IDC_PKSTATIC, &state->key);
|
||||
} else {
|
||||
char *fp;
|
||||
char *savecomment;
|
||||
|
||||
state->ssh2 = true;
|
||||
state->commentptr = &state->ssh2key.comment;
|
||||
state->ssh2key = *newkey2; /* structure copy */
|
||||
sfree(newkey2);
|
||||
|
||||
savecomment = state->ssh2key.comment;
|
||||
state->ssh2key.comment = NULL;
|
||||
fp = ssh2_fingerprint(state->ssh2key.key, state->fptype);
|
||||
state->ssh2key.comment = savecomment;
|
||||
|
||||
SetDlgItemText(hwnd, IDC_FINGERPRINT, fp);
|
||||
sfree(fp);
|
||||
|
||||
setupbigedit2(hwnd, IDC_KEYDISPLAY,
|
||||
IDC_PKSTATIC, &state->ssh2key);
|
||||
update_ui_after_ssh2_pubkey_change(hwnd, state);
|
||||
}
|
||||
SetDlgItemText(hwnd, IDC_COMMENTEDIT,
|
||||
*state->commentptr);
|
||||
@ -1165,6 +1204,105 @@ void load_key_file(HWND hwnd, struct MainDlgState *state,
|
||||
burnstr(passphrase);
|
||||
}
|
||||
|
||||
void add_certificate(HWND hwnd, struct MainDlgState *state,
|
||||
Filename *filename)
|
||||
{
|
||||
int type = key_type(filename);
|
||||
if (type != SSH_KEYTYPE_SSH2_PUBLIC_RFC4716 &&
|
||||
type != SSH_KEYTYPE_SSH2_PUBLIC_OPENSSH) {
|
||||
char *msg = dupprintf("Couldn't load certificate (%s)",
|
||||
key_type_to_str(type));
|
||||
message_box(hwnd, msg, "PuTTYgen Error", MB_OK | MB_ICONERROR,
|
||||
HELPCTXID(errors_cantloadkey));
|
||||
sfree(msg);
|
||||
return;
|
||||
}
|
||||
|
||||
char *algname = NULL;
|
||||
char *comment = NULL;
|
||||
const char *error = NULL;
|
||||
strbuf *pub = strbuf_new();
|
||||
if (!ppk_loadpub_f(filename, &algname, BinarySink_UPCAST(pub), &comment,
|
||||
&error)) {
|
||||
char *msg = dupprintf("Couldn't load certificate (%s)", error);
|
||||
message_box(hwnd, msg, "PuTTYgen Error", MB_OK | MB_ICONERROR,
|
||||
HELPCTXID(errors_cantloadkey));
|
||||
sfree(msg);
|
||||
return;
|
||||
}
|
||||
|
||||
sfree(comment);
|
||||
|
||||
const ssh_keyalg *alg = find_pubkey_alg(algname);
|
||||
if (!alg) {
|
||||
char *msg = dupprintf("Couldn't load certificate (unsupported "
|
||||
"algorithm name '%s')", algname);
|
||||
message_box(hwnd, msg, "PuTTYgen Error", MB_OK | MB_ICONERROR,
|
||||
HELPCTXID(errors_cantloadkey));
|
||||
sfree(msg);
|
||||
sfree(algname);
|
||||
strbuf_free(pub);
|
||||
return;
|
||||
}
|
||||
|
||||
sfree(algname);
|
||||
|
||||
/* Check the two public keys match apart from certificates */
|
||||
strbuf *old_basepub = strbuf_new();
|
||||
ssh_key_public_blob(ssh_key_base_key(state->ssh2key.key),
|
||||
BinarySink_UPCAST(old_basepub));
|
||||
|
||||
ssh_key *new_pubkey = ssh_key_new_pub(alg, ptrlen_from_strbuf(pub));
|
||||
strbuf *new_basepub = strbuf_new();
|
||||
ssh_key_public_blob(ssh_key_base_key(new_pubkey),
|
||||
BinarySink_UPCAST(new_basepub));
|
||||
ssh_key_free(new_pubkey);
|
||||
|
||||
bool match = ptrlen_eq_ptrlen(ptrlen_from_strbuf(old_basepub),
|
||||
ptrlen_from_strbuf(new_basepub));
|
||||
strbuf_free(old_basepub);
|
||||
strbuf_free(new_basepub);
|
||||
|
||||
if (!match) {
|
||||
char *msg = dupprintf("Certificate is for a different public key");
|
||||
message_box(hwnd, msg, "PuTTYgen Error", MB_OK | MB_ICONERROR,
|
||||
HELPCTXID(errors_cantloadkey));
|
||||
sfree(msg);
|
||||
strbuf_free(pub);
|
||||
return;
|
||||
}
|
||||
|
||||
strbuf *priv = strbuf_new_nm();
|
||||
ssh_key_private_blob(state->ssh2key.key, BinarySink_UPCAST(priv));
|
||||
ssh_key *newkey = ssh_key_new_priv(
|
||||
alg, ptrlen_from_strbuf(pub), ptrlen_from_strbuf(priv));
|
||||
strbuf_free(pub);
|
||||
strbuf_free(priv);
|
||||
|
||||
if (!newkey) {
|
||||
char *msg = dupprintf("Couldn't combine certificate with key");
|
||||
message_box(hwnd, msg, "PuTTYgen Error", MB_OK | MB_ICONERROR,
|
||||
HELPCTXID(errors_cantloadkey));
|
||||
sfree(msg);
|
||||
return;
|
||||
}
|
||||
|
||||
ssh_key_free(state->ssh2key.key);
|
||||
state->ssh2key.key = newkey;
|
||||
|
||||
update_ui_after_ssh2_pubkey_change(hwnd, state);
|
||||
ui_set_state(hwnd, state, 2);
|
||||
}
|
||||
|
||||
void remove_certificate(HWND hwnd, struct MainDlgState *state)
|
||||
{
|
||||
ssh_key *newkey = ssh_key_clone(ssh_key_base_key(state->ssh2key.key));
|
||||
ssh_key_free(state->ssh2key.key);
|
||||
state->ssh2key.key = newkey;
|
||||
update_ui_after_ssh2_pubkey_change(hwnd, state);
|
||||
ui_set_state(hwnd, state, 2);
|
||||
}
|
||||
|
||||
static void start_generating_key(HWND hwnd, struct MainDlgState *state)
|
||||
{
|
||||
static const char generating_msg[] =
|
||||
@ -1250,6 +1388,11 @@ static INT_PTR CALLBACK MainDlgProc(HWND hwnd, UINT msg,
|
||||
menu1 = CreateMenu();
|
||||
AppendMenu(menu1, MF_ENABLED, IDC_GENERATE, "&Generate key pair");
|
||||
AppendMenu(menu1, MF_SEPARATOR, 0, 0);
|
||||
AppendMenu(menu1, MF_ENABLED, IDC_ADDCERT,
|
||||
"Add &certificate to key");
|
||||
AppendMenu(menu1, MF_ENABLED, IDC_REMCERT,
|
||||
"Remove certificate from key");
|
||||
AppendMenu(menu1, MF_SEPARATOR, 0, 0);
|
||||
AppendMenu(menu1, MF_ENABLED, IDC_KEYSSH1, "SSH-&1 key (RSA)");
|
||||
AppendMenu(menu1, MF_ENABLED, IDC_KEYSSH2RSA, "SSH-2 &RSA key");
|
||||
AppendMenu(menu1, MF_ENABLED, IDC_KEYSSH2DSA, "SSH-2 &DSA key");
|
||||
@ -1870,6 +2013,30 @@ static INT_PTR CALLBACK MainDlgProc(HWND hwnd, UINT msg,
|
||||
}
|
||||
}
|
||||
break;
|
||||
case IDC_ADDCERT:
|
||||
if (HIWORD(wParam) != BN_CLICKED)
|
||||
break;
|
||||
state =
|
||||
(struct MainDlgState *) GetWindowLongPtr(hwnd, GWLP_USERDATA);
|
||||
if (state->key_exists && !state->generation_thread_exists) {
|
||||
char filename[FILENAME_MAX];
|
||||
if (prompt_keyfile(hwnd, "Load certificate:", filename, false,
|
||||
false)) {
|
||||
Filename *fn = filename_from_str(filename);
|
||||
add_certificate(hwnd, state, fn);
|
||||
filename_free(fn);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case IDC_REMCERT:
|
||||
if (HIWORD(wParam) != BN_CLICKED)
|
||||
break;
|
||||
state =
|
||||
(struct MainDlgState *) GetWindowLongPtr(hwnd, GWLP_USERDATA);
|
||||
if (state->key_exists && !state->generation_thread_exists) {
|
||||
remove_certificate(hwnd, state);
|
||||
}
|
||||
break;
|
||||
}
|
||||
return 0;
|
||||
case WM_DONEKEY:
|
||||
|
Loading…
Reference in New Issue
Block a user