1
0
mirror of https://git.tartarus.org/simon/putty.git synced 2025-01-09 17:38:00 +00:00

cmdgen: human-readable certificate info dump.

The recently added SeatDialogText type was just what I needed to add a
method to the ssh_key vtable for dumping certificate information in a
human-readable format. It will be good for displaying in a Windows
dialog box as well as in cmdgen's text format.

This commit introduces and implements the new method, and adds a
--cert-info mode to command-line Unix PuTTYgen that uses it. The
Windows side will follow shortly.
This commit is contained in:
Simon Tatham 2022-07-30 16:10:45 +01:00
parent 71f43af547
commit 6737a19072
3 changed files with 289 additions and 1 deletions

View File

@ -237,7 +237,7 @@ int main(int argc, char **argv)
enum { NOKEYGEN, RSA1, RSA2, DSA, ECDSA, EDDSA } keytype = NOKEYGEN;
char *outfile = NULL, *outfiletmp = NULL;
enum { PRIVATE, PUBLIC, PUBLICO, FP, OPENSSH_AUTO,
OPENSSH_NEW, SSHCOM, TEXT } outtype = PRIVATE;
OPENSSH_NEW, SSHCOM, TEXT, CERTINFO } outtype = PRIVATE;
int bits = -1;
const char *comment = NULL;
char *origcomment = NULL;
@ -368,6 +368,10 @@ int main(int argc, char **argv)
}
} else if (!strcmp(opt, "-dump")) {
outtype = TEXT;
} else if (!strcmp(opt, "-cert-info") ||
!strcmp(opt, "-certinfo") ||
!strcmp(opt, "-cert_info")) {
outtype = CERTINFO;
} else if (!strcmp(opt, "-primes")) {
if (!val && argc > 1)
--argc, val = *++argv;
@ -594,6 +598,8 @@ int main(int argc, char **argv)
outtype = SSHCOM, sshver = 2;
else if (!strcmp(p, "text"))
outtype = TEXT;
else if (!strcmp(p, "cert-info"))
outtype = CERTINFO;
else {
fprintf(stderr,
"puttygen: unknown output type `%s'\n", p);
@ -1524,6 +1530,83 @@ int main(int argc, char **argv)
key_components_free(kc);
break;
}
case CERTINFO: {
if (sshver == 1) {
fprintf(stderr, "puttygen: SSH-1 keys cannot contain "
"certificates\n");
RETURN(1);
}
const ssh_keyalg *alg;
ssh_key *sk;
bool sk_allocated = false;
if (ssh2key) {
sk = ssh2key->key;
alg = ssh_key_alg(sk);
} else {
assert(ssh2blob);
ptrlen algname = pubkey_blob_to_alg_name(
ptrlen_from_strbuf(ssh2blob));
alg = find_pubkey_alg_len(algname);
if (!alg) {
fprintf(stderr, "puttygen: cannot extract certificate info "
"from public key of unknown type '%.*s'\n",
PTRLEN_PRINTF(algname));
RETURN(1);
}
sk = ssh_key_new_pub(alg, ptrlen_from_strbuf(ssh2blob));
if (!sk) {
fprintf(stderr, "puttygen: unable to decode public key\n");
RETURN(1);
}
sk_allocated = true;
}
if (!alg->is_certificate) {
fprintf(stderr, "puttygen: key is not a certificate\n");
} else {
SeatDialogText *text = ssh_key_cert_info(sk);
FILE *fp;
if (outfile) {
fp = f_open(outfilename, "w", false);
if (!fp) {
fprintf(stderr, "unable to open output file\n");
exit(1);
}
} else {
fp = stdout;
}
for (SeatDialogTextItem *item = text->items,
*end = item+text->nitems; item < end; item++) {
switch (item->type) {
case SDT_MORE_INFO_KEY:
fprintf(fp, "%s", item->text);
break;
case SDT_MORE_INFO_VALUE_SHORT:
fprintf(fp, ": %s\n", item->text);
break;
case SDT_MORE_INFO_VALUE_BLOB:
fprintf(fp, ":\n%s\n", item->text);
break;
default:
break;
}
}
if (outfile)
fclose(fp);
seat_dialog_text_free(text);
}
if (sk_allocated)
ssh_key_free(sk);
break;
}
}
out:

View File

@ -3,6 +3,7 @@
*/
#include "ssh.h"
#include "putty.h"
enum {
SSH_CERT_TYPE_USER = 1,
@ -203,6 +204,7 @@ static void opensshcert_private_blob(ssh_key *key, BinarySink *bs);
static void opensshcert_openssh_blob(ssh_key *key, BinarySink *bs);
static void opensshcert_ca_public_blob(ssh_key *key, BinarySink *bs);
static void opensshcert_cert_id_string(ssh_key *key, BinarySink *bs);
static SeatDialogText *opensshcert_cert_info(ssh_key *key);
static bool opensshcert_has_private(ssh_key *key);
static char *opensshcert_cache_str(ssh_key *key);
static key_components *opensshcert_components(ssh_key *key);
@ -263,6 +265,7 @@ static const ssh_keyalg *opensshcert_related_alg(const ssh_keyalg *self,
.ca_public_blob = opensshcert_ca_public_blob, \
.check_cert = opensshcert_check_cert, \
.cert_id_string = opensshcert_cert_id_string, \
.cert_info = opensshcert_cert_info, \
.pubkey_bits = opensshcert_pubkey_bits, \
.supported_flags = opensshcert_supported_flags, \
.alternate_ssh_id = opensshcert_alternate_ssh_id, \
@ -666,6 +669,205 @@ static key_components *opensshcert_components(ssh_key *key)
return kc;
}
static SeatDialogText *opensshcert_cert_info(ssh_key *key)
{
opensshcert_key *ck = container_of(key, opensshcert_key, sshk);
SeatDialogText *text = seat_dialog_text_new();
strbuf *tmp = strbuf_new();
seat_dialog_text_append(text, SDT_MORE_INFO_KEY,
"Certificate type");
switch (ck->type) {
case SSH_CERT_TYPE_HOST:
seat_dialog_text_append(text, SDT_MORE_INFO_VALUE_SHORT,
"host key");
seat_dialog_text_append(text, SDT_MORE_INFO_KEY,
"Valid host names");
break;
case SSH_CERT_TYPE_USER:
seat_dialog_text_append(text, SDT_MORE_INFO_VALUE_SHORT,
"user authentication key");
seat_dialog_text_append(text, SDT_MORE_INFO_KEY,
"Valid user names");
break;
default:
seat_dialog_text_append(text, SDT_MORE_INFO_VALUE_SHORT,
"unknown type %" PRIu32, ck->type);
seat_dialog_text_append(text, SDT_MORE_INFO_KEY,
"Valid principals");
break;
}
{
BinarySource src[1];
BinarySource_BARE_INIT_PL(src, ptrlen_from_strbuf(
ck->valid_principals));
const char *sep = "";
strbuf_clear(tmp);
while (get_avail(src)) {
ptrlen principal = get_string(src);
if (get_err(src))
break;
put_dataz(tmp, sep);
sep = ",";
put_datapl(tmp, principal);
}
seat_dialog_text_append(text, SDT_MORE_INFO_VALUE_SHORT,
"%s", tmp->s);
}
seat_dialog_text_append(text, SDT_MORE_INFO_KEY,
"Validity period");
strbuf_clear(tmp);
if (ck->valid_after == 0) {
if (ck->valid_before == 0xFFFFFFFFFFFFFFFF) {
put_dataz(tmp, "forever");
} else {
put_dataz(tmp, "until ");
opensshcert_time_to_iso8601(BinarySink_UPCAST(tmp),
ck->valid_before);
}
} else {
if (ck->valid_before == 0xFFFFFFFFFFFFFFFF) {
put_dataz(tmp, "after ");
opensshcert_time_to_iso8601(BinarySink_UPCAST(tmp),
ck->valid_after);
} else {
opensshcert_time_to_iso8601(BinarySink_UPCAST(tmp),
ck->valid_after);
put_dataz(tmp, " - ");
opensshcert_time_to_iso8601(BinarySink_UPCAST(tmp),
ck->valid_before);
}
}
seat_dialog_text_append(text, SDT_MORE_INFO_VALUE_SHORT, "%s", tmp->s);
/*
* List critical options we know about. (This is everything listed
* in PROTOCOL.certkeys that isn't specific to U2F/FIDO key types
* that PuTTY doesn't currently support.)
*/
{
BinarySource src[1];
BinarySource_BARE_INIT_PL(src, ptrlen_from_strbuf(
ck->critical_options));
strbuf_clear(tmp);
while (get_avail(src)) {
ptrlen key = get_string(src);
ptrlen value = get_string(src);
if (get_err(src))
break;
if (ck->type == SSH_CERT_TYPE_USER &&
ptrlen_eq_string(key, "source-address")) {
BinarySource src2[1];
BinarySource_BARE_INIT_PL(src2, value);
ptrlen addresslist = get_string(src2);
seat_dialog_text_append(text, SDT_MORE_INFO_KEY,
"Permitted client IP addresses");
seat_dialog_text_append(text, SDT_MORE_INFO_VALUE_SHORT,
"%.*s", PTRLEN_PRINTF(addresslist));
} else if (ck->type == SSH_CERT_TYPE_USER &&
ptrlen_eq_string(key, "force-command")) {
BinarySource src2[1];
BinarySource_BARE_INIT_PL(src2, value);
ptrlen command = get_string(src2);
seat_dialog_text_append(text, SDT_MORE_INFO_KEY,
"Forced remote command");
seat_dialog_text_append(text, SDT_MORE_INFO_VALUE_SHORT,
"%.*s", PTRLEN_PRINTF(command));
}
}
}
/*
* List certificate extensions. Again, we go through everything in
* PROTOCOL.certkeys that isn't specific to U2F/FIDO key types.
* But we also flip the sense round for user-readability: I think
* it's more likely that the typical key will permit all these
* things, so we emit no output in that case, and only mention the
* things that _aren't_ enabled.
*/
bool x11_ok = false, agent_ok = false, portfwd_ok = false;
bool pty_ok = false, user_rc_ok = false;
{
BinarySource src[1];
BinarySource_BARE_INIT_PL(src, ptrlen_from_strbuf(
ck->extensions));
while (get_avail(src)) {
ptrlen key = get_string(src);
/* ptrlen value = */ get_string(src); // nothing needs this yet
if (get_err(src))
break;
if (ptrlen_eq_string(key, "permit-X11-forwarding")) {
x11_ok = true;
} else if (ptrlen_eq_string(key, "permit-agent-forwarding")) {
agent_ok = true;
} else if (ptrlen_eq_string(key, "permit-port-forwarding")) {
portfwd_ok = true;
} else if (ptrlen_eq_string(key, "permit-pty")) {
pty_ok = true;
} else if (ptrlen_eq_string(key, "permit-user-rc")) {
user_rc_ok = true;
}
}
}
if (ck->type == SSH_CERT_TYPE_USER) {
if (!x11_ok) {
seat_dialog_text_append(text, SDT_MORE_INFO_KEY,
"X11 forwarding permitted");
seat_dialog_text_append(text, SDT_MORE_INFO_VALUE_SHORT, "no");
}
if (!agent_ok) {
seat_dialog_text_append(text, SDT_MORE_INFO_KEY,
"Agent forwarding permitted");
seat_dialog_text_append(text, SDT_MORE_INFO_VALUE_SHORT, "no");
}
if (!portfwd_ok) {
seat_dialog_text_append(text, SDT_MORE_INFO_KEY,
"Port forwarding permitted");
seat_dialog_text_append(text, SDT_MORE_INFO_VALUE_SHORT, "no");
}
if (!pty_ok) {
seat_dialog_text_append(text, SDT_MORE_INFO_KEY,
"PTY allocation permitted");
seat_dialog_text_append(text, SDT_MORE_INFO_VALUE_SHORT, "no");
}
if (!user_rc_ok) {
seat_dialog_text_append(text, SDT_MORE_INFO_KEY,
"Running user ~/.ssh.rc permitted");
seat_dialog_text_append(text, SDT_MORE_INFO_VALUE_SHORT, "no");
}
}
seat_dialog_text_append(text, SDT_MORE_INFO_KEY,
"Certificate ID string");
seat_dialog_text_append(text, SDT_MORE_INFO_VALUE_SHORT,
"%s", ck->key_id->s);
seat_dialog_text_append(text, SDT_MORE_INFO_KEY,
"Certificate serial number");
seat_dialog_text_append(text, SDT_MORE_INFO_VALUE_SHORT,
"%" PRIu64, ck->serial);
char *fp = ssh2_fingerprint_blob(ptrlen_from_strbuf(ck->signature_key),
SSH_FPTYPE_DEFAULT);
seat_dialog_text_append(text, SDT_MORE_INFO_KEY,
"Fingerprint of signing CA key");
seat_dialog_text_append(text, SDT_MORE_INFO_VALUE_SHORT, "%s", fp);
sfree(fp);
fp = ssh2_fingerprint(ck->basekey, SSH_FPTYPE_DEFAULT);
seat_dialog_text_append(text, SDT_MORE_INFO_KEY,
"Fingerprint of underlying key");
seat_dialog_text_append(text, SDT_MORE_INFO_VALUE_SHORT, "%s", fp);
sfree(fp);
strbuf_free(tmp);
return text;
}
static int opensshcert_pubkey_bits(const ssh_keyalg *self, ptrlen blob)
{
BinarySource src[1];

3
ssh.h
View File

@ -851,6 +851,7 @@ struct ssh_keyalg {
uint64_t time, const ca_options *opts,
BinarySink *error);
void (*cert_id_string)(ssh_key *key, BinarySink *);
SeatDialogText *(*cert_info)(ssh_key *key);
/* 'Class methods' that don't deal with an ssh_key at all */
int (*pubkey_bits) (const ssh_keyalg *self, ptrlen blob);
@ -903,6 +904,8 @@ static inline void ssh_key_ca_public_blob(ssh_key *key, BinarySink *bs)
{ key->vt->ca_public_blob(key, bs); }
static inline void ssh_key_cert_id_string(ssh_key *key, BinarySink *bs)
{ key->vt->cert_id_string(key, bs); }
static inline SeatDialogText *ssh_key_cert_info(ssh_key *key)
{ return key->vt->cert_info(key); }
static inline bool ssh_key_check_cert(
ssh_key *key, bool host, ptrlen principal, uint64_t time,
const ca_options *opts, BinarySink *error)