Viewing cetificate details

Iain Morgan imorgan at nas.nasa.gov
Fri Mar 5 09:47:51 EST 2010


On Wed, Mar 03, 2010 at 20:08:58 -0600, Damien Miller wrote:
> On Wed, 3 Mar 2010, Iain Morgan wrote:
> 
> > Hi,
> > 
> > I don't see any way to view the details of a certificate once it is
> > generated. Having such a capability would be very handy for debugging
> > purposes to check what constraints, principals, and validity interval
> > are associated with a given cert.
> 
> Yes, I had been meaning to add that - thanks for reminding me. This
> diff adds "ssh-keygen -L" to print certificate details.
> 
> Index: ssh-keygen.1
> ===================================================================
> RCS file: /cvs/src/usr.bin/ssh/ssh-keygen.1,v
> retrieving revision 1.85
> diff -u -p -r1.85 ssh-keygen.1
> --- ssh-keygen.1	26 Feb 2010 22:09:28 -0000	1.85
> +++ ssh-keygen.1	4 Mar 2010 02:07:48 -0000
> @@ -115,6 +115,10 @@
>  .Op Fl O Ar constraint
>  .Op Fl V Ar validity_interval
>  .Ar
> +.Nm ssh-keygen
> +.Bk -words
> +.Fl L
> +.Op Fl f Ar input_keyfile
>  .Ek
>  .Sh DESCRIPTION
>  .Nm
> @@ -275,6 +279,8 @@ also reads the
>  RFC 4716 SSH Public Key File Format.
>  This option allows importing keys from several commercial
>  SSH implementations.
> +.It Fl L
> +Prints the contents of a certificate.
>  .It Fl l
>  Show fingerprint of specified public key file.
>  Private RSA1 keys are also supported.
> Index: ssh-keygen.c
> ===================================================================
> RCS file: /cvs/src/usr.bin/ssh/ssh-keygen.c,v
> retrieving revision 1.180
> diff -u -p -r1.180 ssh-keygen.c
> --- ssh-keygen.c	2 Mar 2010 23:20:57 -0000	1.180
> +++ ssh-keygen.c	4 Mar 2010 02:07:49 -0000
> @@ -74,6 +74,9 @@ int find_host = 0;
>  /* Flag indicating that we want to delete a host from a known_hosts file */
>  int delete_host = 0;
>  
> +/* Flag indicating that we want to show the contents of a certificate */
> +int show_cert = 0;
> +
>  /* Flag indicating that we just want to see the key fingerprint */
>  int print_fingerprint = 0;
>  int print_bubblebabble = 0;
> @@ -1055,7 +1058,7 @@ do_change_comment(struct passwd *pw)
>  }
>  
>  static const char *
> -fmt_validity(void)
> +fmt_validity(u_int64_t valid_from, u_int64_t valid_to)
>  {
>  	char from[32], to[32];
>  	static char ret[64];
> @@ -1063,28 +1066,27 @@ fmt_validity(void)
>  	struct tm *tm;
>  
>  	*from = *to = '\0';
> -	if (cert_valid_from == 0 &&
> -	    cert_valid_to == 0xffffffffffffffffULL)
> +	if (valid_from == 0 && valid_to == 0xffffffffffffffffULL)
>  		return "forever";
>  
> -	if (cert_valid_from != 0) {
> +	if (valid_from != 0) {
>  		/* XXX revisit INT_MAX in 2038 :) */
> -		tt = cert_valid_from > INT_MAX ? INT_MAX : cert_valid_from;
> +		tt = valid_from > INT_MAX ? INT_MAX : valid_from;
>  		tm = localtime(&tt);
>  		strftime(from, sizeof(from), "%Y-%m-%dT%H:%M:%S", tm);
>  	}
> -	if (cert_valid_to != 0xffffffffffffffffULL) {
> +	if (valid_to != 0xffffffffffffffffULL) {
>  		/* XXX revisit INT_MAX in 2038 :) */
> -		tt = cert_valid_to > INT_MAX ? INT_MAX : cert_valid_to;
> +		tt = valid_to > INT_MAX ? INT_MAX : valid_to;
>  		tm = localtime(&tt);
>  		strftime(to, sizeof(to), "%Y-%m-%dT%H:%M:%S", tm);
>  	}
>  
> -	if (cert_valid_from == 0) {
> +	if (valid_from == 0) {
>  		snprintf(ret, sizeof(ret), "before %s", to);
>  		return ret;
>  	}
> -	if (cert_valid_to == 0xffffffffffffffffULL) {
> +	if (valid_to == 0xffffffffffffffffULL) {
>  		snprintf(ret, sizeof(ret), "after %s", from);
>  		return ret;
>  	}
> @@ -1208,7 +1210,7 @@ do_ca_sign(struct passwd *pw, int argc, 
>  			    out, cert_key_id,
>  			    cert_principals != NULL ? " for " : "",
>  			    cert_principals != NULL ? cert_principals : "",
> -			    fmt_validity());
> +			    fmt_validity(cert_valid_from, cert_valid_to));
>  
>  		key_free(public);
>  		xfree(out);
> @@ -1358,6 +1360,89 @@ add_cert_constraint(char *opt)
>  }
>  
>  static void
> +do_show_cert(struct passwd *pw)
> +{
> +	Key *key;
> +	struct stat st;
> +	char *key_fp, *ca_fp;
> +	Buffer constraints, constraint;
> +	u_char *name, *data;
> +	u_int i, dlen;
> +
> +	if (!have_identity)
> +		ask_filename(pw, "Enter file in which the key is");
> +	if (stat(identity_file, &st) < 0) {
> +		perror(identity_file);
> +		exit(1);
> +	}
> +	if ((key = key_load_public(identity_file, NULL)) == NULL)
> +		fatal("%s is not a public key", identity_file);
> +	if (!key_is_cert(key))
> +		fatal("%s is not a certificate", identity_file);
> +	
> +	key_fp = key_fingerprint(key, SSH_FP_MD5, SSH_FP_HEX);
> +	ca_fp = key_fingerprint(key->cert->signature_key,
> +	    SSH_FP_MD5, SSH_FP_HEX);
> +
> +	printf("%s:\n", identity_file);
> +	printf("        %s certificate %s\n", key_type(key), key_fp);
> +	printf("        Signed by %s CA %s\n",
> +	    key_type(key->cert->signature_key), ca_fp);
> +	printf("        Key ID \"%s\"\n", key->cert->key_id);
> +	printf("        Valid: %s\n",
> +	    fmt_validity(key->cert->valid_after, key->cert->valid_before));
> +	printf("        Principals: ");
> +	if (key->cert->nprincipals == 0)
> +		printf("(none)\n");
> +	else {
> +		for (i = 0; i < key->cert->nprincipals; i++)
> +			printf("\n                %s",
> +			    key->cert->principals[i]);
> +		printf("\n");
> +	}
> +	printf("        Constraints: ");
> +	if (buffer_len(&key->cert->constraints) == 0)
> +		printf("(none)\n");
> +	else {
> +		printf("\n");
> +		buffer_init(&constraints);
> +		buffer_append(&constraints,
> +		    buffer_ptr(&key->cert->constraints),
> +		    buffer_len(&key->cert->constraints));
> +		buffer_init(&constraint);
> +		while (buffer_len(&constraints) != 0) {
> +			name = buffer_get_string(&constraints, NULL);
> +			data = buffer_get_string_ptr(&constraints, &dlen);
> +			buffer_append(&constraint, data, dlen);
> +			printf("                %s", name);
> +			if (strcmp(name, "permit-X11-forwarding") == 0 ||
> +			    strcmp(name, "permit-agent-forwarding") == 0 ||
> +			    strcmp(name, "permit-port-forwarding") == 0 ||
> +			    strcmp(name, "permit-pty") == 0 ||
> +			    strcmp(name, "permit-user-rc") == 0)
> +				printf("\n");
> +			else if (strcmp(name, "force-command") == 0 ||
> +			    strcmp(name, "source-address") == 0) {
> +				data = buffer_get_string(&constraint, NULL);
> +				printf(" %s\n", data);
> +				xfree(data);
> +			} else {
> +				printf(" UNKNOWN CONSTRAINT (len %u)\n",
> +				    buffer_len(&constraint));
> +				buffer_clear(&constraint);
> +			}
> +			xfree(name);
> +			if (buffer_len(&constraint) != 0)
> +				fatal("Constraint corrupt: extra data at end");
> +		}
> +		buffer_free(&constraint);
> +		buffer_free(&constraints);
> +	}
> +
> +	exit(0);
> +}
> +
> +static void
>  usage(void)
>  {
>  	fprintf(stderr, "usage: %s [options]\n", __progname);
> @@ -1379,6 +1464,7 @@ usage(void)
>  	fprintf(stderr, "  -h          Generate host certificate instead of a user certificate.\n");
>  	fprintf(stderr, "  -I key_id   Key identifier to include in certificate.\n");
>  	fprintf(stderr, "  -i          Convert RFC 4716 to OpenSSH key file.\n");
> +	fprintf(stderr, "  -L          Print the contents of a certificate.\n");
>  	fprintf(stderr, "  -l          Show fingerprint of key file.\n");
>  	fprintf(stderr, "  -M memory   Amount of memory (MB) to use for generating DH-GEX moduli.\n");
>  	fprintf(stderr, "  -n name,... User/host principal names to include in certificate\n");
> @@ -1440,7 +1526,7 @@ main(int argc, char **argv)
>  		exit(1);
>  	}
>  
> -	while ((opt = getopt(argc, argv, "degiqpclBHhvxXyF:b:f:t:D:I:P:N:n:"
> +	while ((opt = getopt(argc, argv, "degiqpclBHLhvxXyF:b:f:t:D:I:P:N:n:"
>  	    "O:C:r:g:R:T:G:M:S:s:a:V:W:")) != -1) {
>  		switch (opt) {
>  		case 'b':
> @@ -1463,6 +1549,9 @@ main(int argc, char **argv)
>  			delete_host = 1;
>  			rr_hostname = optarg;
>  			break;
> +		case 'L':
> +			show_cert = 1;
> +			break;
>  		case 'l':
>  			print_fingerprint = 1;
>  			break;
> @@ -1616,6 +1705,8 @@ main(int argc, char **argv)
>  			fatal("Must specify key id (-I) when certifying");
>  		do_ca_sign(pw, argc, argv);
>  	}
> +	if (show_cert)
> +		do_show_cert(pw);
>  	if (delete_host || hash_hosts || find_host)
>  		do_known_hosts(pw, rr_hostname);
>  	if (print_fingerprint || print_bubblebabble)

Hi Damien,

Thanks. This is what I was looking for. So, far I've only tested it on
one system, but it seems to work. There does seem to be an oddity
however with the handling of the force-command constraint. For example:


$ openssh/ssh-keygen -s ca_key -I "Test cert" -n test -V +12m -O clear
-O source-address=10.2.3.0/24 -O force-command=/bin/date id_test.pub                
Enter passphrase: 
Signed user key id_test-cert.pub: id "Test cert" for test valid from
2010-03-04T13:52:00 to 2010-03-04T14:05:24
$ 
$ openssh/ssh-keygen -Lf id_test-cert.pub
id_test-cert.pub:                        
        RSA-CERT certificate
1b:9b:58:e0:ef:9b:51:17:7b:05:b4:86:16:db:42:19
        Signed by RSA CA d5:1f:0f:3d:84:a1:16:a5:f5:3a:5e:c8:3e:54:1d:92
        Key ID "Test cert"
        Valid: from 2010-03-04T13:52:00 to 2010-03-04T14:05:24
        Principals: 
                test
        Constraints: 
                forced-command UNKNOWN CONSTRAINT (len 13)
                source-address 10.2.3.0/24
$ 

Note that the output refers to "forced-command" rather than
"force-command.

It looks like the problem is in ssh-keygen.c:1145.

		add_string_constraint(c, "forced-command", constraint_command);

There also seems to be a less significant error in auth-optins.c.

448-				error("Certificate has multiple "
449:				    "forced-command constraints");

-- 
Iain Morgan


More information about the openssh-unix-dev mailing list