mirror of
https://git.tartarus.org/simon/putty.git
synced 2025-01-26 01:32:25 +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:
parent
71f43af547
commit
6737a19072
85
cmdgen.c
85
cmdgen.c
@ -237,7 +237,7 @@ int main(int argc, char **argv)
|
|||||||
enum { NOKEYGEN, RSA1, RSA2, DSA, ECDSA, EDDSA } keytype = NOKEYGEN;
|
enum { NOKEYGEN, RSA1, RSA2, DSA, ECDSA, EDDSA } keytype = NOKEYGEN;
|
||||||
char *outfile = NULL, *outfiletmp = NULL;
|
char *outfile = NULL, *outfiletmp = NULL;
|
||||||
enum { PRIVATE, PUBLIC, PUBLICO, FP, OPENSSH_AUTO,
|
enum { PRIVATE, PUBLIC, PUBLICO, FP, OPENSSH_AUTO,
|
||||||
OPENSSH_NEW, SSHCOM, TEXT } outtype = PRIVATE;
|
OPENSSH_NEW, SSHCOM, TEXT, CERTINFO } outtype = PRIVATE;
|
||||||
int bits = -1;
|
int bits = -1;
|
||||||
const char *comment = NULL;
|
const char *comment = NULL;
|
||||||
char *origcomment = NULL;
|
char *origcomment = NULL;
|
||||||
@ -368,6 +368,10 @@ int main(int argc, char **argv)
|
|||||||
}
|
}
|
||||||
} else if (!strcmp(opt, "-dump")) {
|
} else if (!strcmp(opt, "-dump")) {
|
||||||
outtype = TEXT;
|
outtype = TEXT;
|
||||||
|
} else if (!strcmp(opt, "-cert-info") ||
|
||||||
|
!strcmp(opt, "-certinfo") ||
|
||||||
|
!strcmp(opt, "-cert_info")) {
|
||||||
|
outtype = CERTINFO;
|
||||||
} else if (!strcmp(opt, "-primes")) {
|
} else if (!strcmp(opt, "-primes")) {
|
||||||
if (!val && argc > 1)
|
if (!val && argc > 1)
|
||||||
--argc, val = *++argv;
|
--argc, val = *++argv;
|
||||||
@ -594,6 +598,8 @@ int main(int argc, char **argv)
|
|||||||
outtype = SSHCOM, sshver = 2;
|
outtype = SSHCOM, sshver = 2;
|
||||||
else if (!strcmp(p, "text"))
|
else if (!strcmp(p, "text"))
|
||||||
outtype = TEXT;
|
outtype = TEXT;
|
||||||
|
else if (!strcmp(p, "cert-info"))
|
||||||
|
outtype = CERTINFO;
|
||||||
else {
|
else {
|
||||||
fprintf(stderr,
|
fprintf(stderr,
|
||||||
"puttygen: unknown output type `%s'\n", p);
|
"puttygen: unknown output type `%s'\n", p);
|
||||||
@ -1524,6 +1530,83 @@ int main(int argc, char **argv)
|
|||||||
key_components_free(kc);
|
key_components_free(kc);
|
||||||
break;
|
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:
|
out:
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "ssh.h"
|
#include "ssh.h"
|
||||||
|
#include "putty.h"
|
||||||
|
|
||||||
enum {
|
enum {
|
||||||
SSH_CERT_TYPE_USER = 1,
|
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_openssh_blob(ssh_key *key, BinarySink *bs);
|
||||||
static void opensshcert_ca_public_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 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 bool opensshcert_has_private(ssh_key *key);
|
||||||
static char *opensshcert_cache_str(ssh_key *key);
|
static char *opensshcert_cache_str(ssh_key *key);
|
||||||
static key_components *opensshcert_components(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, \
|
.ca_public_blob = opensshcert_ca_public_blob, \
|
||||||
.check_cert = opensshcert_check_cert, \
|
.check_cert = opensshcert_check_cert, \
|
||||||
.cert_id_string = opensshcert_cert_id_string, \
|
.cert_id_string = opensshcert_cert_id_string, \
|
||||||
|
.cert_info = opensshcert_cert_info, \
|
||||||
.pubkey_bits = opensshcert_pubkey_bits, \
|
.pubkey_bits = opensshcert_pubkey_bits, \
|
||||||
.supported_flags = opensshcert_supported_flags, \
|
.supported_flags = opensshcert_supported_flags, \
|
||||||
.alternate_ssh_id = opensshcert_alternate_ssh_id, \
|
.alternate_ssh_id = opensshcert_alternate_ssh_id, \
|
||||||
@ -666,6 +669,205 @@ static key_components *opensshcert_components(ssh_key *key)
|
|||||||
return kc;
|
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)
|
static int opensshcert_pubkey_bits(const ssh_keyalg *self, ptrlen blob)
|
||||||
{
|
{
|
||||||
BinarySource src[1];
|
BinarySource src[1];
|
||||||
|
3
ssh.h
3
ssh.h
@ -851,6 +851,7 @@ struct ssh_keyalg {
|
|||||||
uint64_t time, const ca_options *opts,
|
uint64_t time, const ca_options *opts,
|
||||||
BinarySink *error);
|
BinarySink *error);
|
||||||
void (*cert_id_string)(ssh_key *key, BinarySink *);
|
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 */
|
/* 'Class methods' that don't deal with an ssh_key at all */
|
||||||
int (*pubkey_bits) (const ssh_keyalg *self, ptrlen blob);
|
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); }
|
{ key->vt->ca_public_blob(key, bs); }
|
||||||
static inline void ssh_key_cert_id_string(ssh_key *key, BinarySink *bs)
|
static inline void ssh_key_cert_id_string(ssh_key *key, BinarySink *bs)
|
||||||
{ key->vt->cert_id_string(key, 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(
|
static inline bool ssh_key_check_cert(
|
||||||
ssh_key *key, bool host, ptrlen principal, uint64_t time,
|
ssh_key *key, bool host, ptrlen principal, uint64_t time,
|
||||||
const ca_options *opts, BinarySink *error)
|
const ca_options *opts, BinarySink *error)
|
||||||
|
Loading…
Reference in New Issue
Block a user