1
0
mirror of https://git.tartarus.org/simon/putty.git synced 2025-07-18 19:41:01 -05: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:
Simon Tatham
2022-04-19 17:59:46 +01:00
parent 4cde00efc0
commit 7cb3142a57
2 changed files with 337 additions and 15 deletions

157
cmdgen.c
View File

@ -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.