OpenSSH Certkey (PKI)

From: Daniel Hartmeier <daniel_at_benzedrine.cx>
Date: Wed, 15 Nov 2006 15:28:20 +0100
This patch against OpenBSD -current adds a simple form of PKI to
OpenSSH. We'll be using it at work. See README.certkey (the first chunk
of the patch) for details.

Everything below is BSD licensed, sponsored by Allamanda Networks AG.

Daniel


--- /dev/null	Wed Nov 15 15:14:20 2006
+++ README.certkey	Wed Nov 15 15:13:45 2006
_at__at_ -0,0 +1,176 _at__at_
+OpenSSH Certkey
+
+INTRODUCTION
+
+Certkey allows OpenSSH to transmit certificates from server to client for host
+authentication and from client to server for user authentication. Certificates
+are basically signatures made by a certificate authority (CA) private key.
+
+A host certificate is a guarantee made by the CA that a host public key is
+valid. When a host public key carries a valid certificate, the client can
+use the host public key without asking the user to confirm the fingerprint
+manually and through out-of-band communication the first time. The CA takes
+the responsibility of verifying host keys, and users do no longer need to
+maintain known_hosts files of their own.
+
+A user certificate is an authorization made by the CA that the holder of a
+specific private key may login to the server as a specific user, without the
+need of an authorized_keys file being present. The CA gains the power to grant
+individual users access to the server, and users do no longer need to maintain
+authorized_keys files of their own.
+
+Functionally, the CA assumes responsibility and control over users' known_hosts
+and authorized_keys files.
+
+Certkey does not involve online verfication, the CA is not contacted by either
+client or server. Instead, the CA generates certificates which are (once)
+distributed to hosts and users. Any subsequent logins take place without the
+involvment of the CA, based solely on the certificates provided between client
+and server.
+
+For example, a company sets up a new host where many existing users need to
+login. Traditionally, every one of those users will have to verify the new
+host's key the first time they login. Also, each user will have to authorize
+their public key on the new host. With Certkey enabled in this case (and
+assuming the users have already been certified), this procedure is reduced to
+the CA generating a certificate for the new host and installing the
+certificate on the new host.
+
+
+SECURITY IMPLICATIONS
+
+The CA, specifically the holder of the CA private key (and its password, if it
+is password encrypted), holds broad control over hosts and user accounts set
+up in this way. Should the CA private key become compromised, all user
+accounts become compromised.
+
+There is no way to revoke a certificate once it has been published, the
+certificate is valid until it reaches the expiry date set by the CA.
+
+
+CONFIGURATION
+
+The feature is enabled through the following two options in the client and
+server configurations:
+
+  CertkeyAuthentication yes
+  CAKeyFile /etc/ssh/ca.pub
+
+
+USAGE
+
+(1) Generating a CA key pair
+
+    # ssh-keygen
+    Generating public/private rsa key pair.
+    Enter file in which to save the key (/root/.ssh/id_rsa): /root/.ssh/ca
+    Enter passphrase (empty for no passphrase): 
+    Enter same passphrase again: 
+    Your identification has been saved in /root/.ssh/ca.
+    Your public key has been saved in /root/.ssh/ca.pub.
+    The key fingerprint is:
+    f2:c7:5c:a9:48:d8:8c:82:24:d5:2a:d6:75:48:ab:3d root_at_host
+
+(2) Generating a host certificate
+
+    # ssh-keygen -s
+    Enter file in which the CA key is (/root/.ssh/id_rsa): /root/.ssh/ca
+    CA key fingerprint f2:c7:5c:a9:48:d8:8c:82:24:d5:2a:d6:75:48:ab:3d
+    Enter file in which the user/host key is: /etc/ssh/ssh_host_rsa_key.pub
+    host/user key fingerprint 68:8c:25:e3:b1:17:8a:7f:0c:19:fa:0d:f7:12:6f:8a
+    CA name    : benzedrine.cx
+    identity   : lenovo.benzedrine.cx
+    options    : 
+    valid from : 0
+    valid until: 20061231
+    Certificate has been saved in /etc/ssh/ssh_host_rsa_key.cert.
+
+    # cp /root/.ssh/ca.pub /etc/ssh/ca.pub
+
+(3) Generating a user certificate
+
+    # ssh-keygen -s
+    Enter file in which the CA key is (/root/.ssh/id_rsa): /root/.ssh/ca
+    CA key fingerprint f2:c7:5c:a9:48:d8:8c:82:24:d5:2a:d6:75:48:ab:3d
+    Enter file in which the user/host key is: /home/dhartmei/.ssh/id_dsa.pub
+    host/user key fingerprint 86:c8:52:3e:b1:17:8a:7f:0c:19:fa:0d:f7:12:f6:a8
+    CA name    : benzedrine.cx
+    identity   : dhartmei
+    options    : 
+    valid from : 20061101
+    valid until: 20071231
+    Certificate has been saved in /home/dhartmei/.ssh/id_dsa.cert.
+
+
+IMPLEMENTATION
+
+Host and user certificates are introduced into the transport layer and
+authentication protocol by addition of a new method respectively.
+
+Transport Layer Protocol
+
+An additional key exchange method "diffie-hellman-group-exchange-cert" has
+been added. This method is completely identical to the existing method
+"diffie-hellman-group-exchange-sha1", except for one additional string
+(the host certificate), placed at the end of the message, after the signature.
+
+Authentication Protocol
+
+An additional authentication method "certkey" has been added. This method is
+completely identical to the existing method "publickey", except for one
+additional string (the user certificate), placed at the end of the message,
+after the signature.
+
+Certificate format
+
+Both host and user certificates share the same format. They consist of a single
+string, containing values separated by semi-colons, in the following order
+
+  fingerprint;caname;identity;options;validfrom;validto;algorithm;signature
+
+Values must not contain semi-colons or NUL bytes, but may be empty.
+
+'fingerprint' is the SSH_FP_MD5 SSH_FP_HEX fingerprint of the RSA key signing
+the certificate (the CA key), e.g. the output of ssh-keygen -l for
+/etc/ssh/ca.pub.
+
+'caname' is the name of the CA. This can be used to associate certificates with
+CAs. The format is not defined, though using domain names is suggested.
+
+'identity' is the identity being certified by the CA with this certificate.
+For user certificates, this is the user name the certifcate grants login to.
+For host certificates, the format is not defined, though using the host's
+fully-qualified domain name is suggested.
+
+'options' may contain additional options, in form of key=value pairs separated
+by pipes '|', like 'foo=bar|src=10/8,*.networx.ch|dst=192.168/16'. keys and
+values must not contain semi-colons, pipes, '=' or NUL bytes. The meaning of
+options is not currently defined, though keys 'src' and 'dst' are reserved for
+later implementation of restrictions based on client/server addresses.
+
+'validfrom' and 'validto' are timestamps (UNIX Epoch time) defining the time
+frame the certificate is valid in. When, upon certificate verification, the
+current time is outside this period, the certificate is not valid. If zero is
+used as value for 'validto', the certificate is valid indefinitely after
+'validfrom'.
+
+'algorithm' defines the hash algorithm used for the signature. Currently, the
+only legal value is 'ripemd160'.
+
+'signature' is the signature itself in hex without colons. The data being
+signed consists of
+
+  fingerprint;caname;identity;options;validfrom;validto
+
+where 'fingerprint' is the SSH_FP_MD5 SSH_FP_HEX fingerprint of the user or
+host key (not the CA key).
+
+Example:
+
+  f2:c7:5c:a9:48:d8:8c:82:24:d5:2a:d6:75:48:ab:3d;networx.ch;dhartmei;;
+  1136070000;1451602800;ripemd160;dbd33e932d80b5612...5a0b4759bee451
+
+Note that the certificate does not contain any newline characters, it's
+wrapped onto two lines here to improve readability.
+
+$OpenBSD$
Index: auth.h
===================================================================
RCS file: /cvs/src/usr.bin/ssh/auth.h,v
retrieving revision 1.58
diff -u -r1.58 auth.h
--- auth.h	18 Aug 2006 09:15:20 -0000	1.58
+++ auth.h	15 Nov 2006 14:14:32 -0000
_at__at_ -115,6 +115,7 _at__at_
 int	 auth_rhosts_rsa_key_allowed(struct passwd *, char *, char *, Key *);
 int	 hostbased_key_allowed(struct passwd *, const char *, char *, Key *);
 int	 user_key_allowed(struct passwd *, Key *);
+int	 user_cert_key_allowed(struct passwd *, Key *);
 
 #ifdef KRB5
 int	auth_krb5(Authctxt *authctxt, krb5_data *auth, char **client, krb5_data *);
Index: auth2.c
===================================================================
RCS file: /cvs/src/usr.bin/ssh/auth2.c,v
retrieving revision 1.113
diff -u -r1.113 auth2.c
--- auth2.c	3 Aug 2006 03:34:41 -0000	1.113
+++ auth2.c	15 Nov 2006 14:14:32 -0000
_at__at_ -55,6 +55,7 _at__at_
 /* methods */
 
 extern Authmethod method_none;
+extern Authmethod method_certkey;
 extern Authmethod method_pubkey;
 extern Authmethod method_passwd;
 extern Authmethod method_kbdint;
_at__at_ -65,6 +66,7 _at__at_
 
 Authmethod *authmethods[] = {
 	&method_none,
+	&method_certkey,
 	&method_pubkey,
 #ifdef GSSAPI
 	&method_gssapi,
Index: authfile.c
===================================================================
RCS file: /cvs/src/usr.bin/ssh/authfile.c,v
retrieving revision 1.76
diff -u -r1.76 authfile.c
--- authfile.c	3 Aug 2006 03:34:41 -0000	1.76
+++ authfile.c	15 Nov 2006 14:14:33 -0000
_at__at_ -302,6 +302,43 _at__at_
 	return pub;
 }
 
+static void
+key_try_load_cert(Key *key, const char *filename)
+{
+	char fn[MAXPATHLEN];
+	int fd;
+	ssize_t r;
+
+	if (key->cert) {
+		xfree(key->cert);
+		key->cert = NULL;
+	}
+	if (strlen(filename) > 4 && strlen(filename) < sizeof(fn) &&
+	    !strcmp(filename + strlen(filename) - 4, ".pub"))
+		strlcpy(fn, filename, strlen(filename) - 3);
+	else
+		strlcpy(fn, filename, sizeof(fn));
+	strlcat(fn, ".cert", sizeof(fn));
+
+	fd = open(fn, O_RDONLY);
+	if (fd >= 0) {
+		key->cert = xmalloc(8192);
+		if (key->cert) {
+			r = read(fd, key->cert, 8192);
+			if (r > 0) {
+				if (key->cert[r - 1] == '\n')
+					key->cert[r - 1] = 0;
+				else
+					key->cert[r] = 0;
+			} else {
+				xfree(key->cert);
+				key->cert = NULL;
+			}
+		}
+		close(fd);
+	}
+}
+
 /* load public key from private-key file, works only for SSH v1 */
 Key *
 key_load_public_type(int type, const char *filename, char **commentp)
_at__at_ -315,6 +352,8 _at__at_
 			return NULL;
 		pub = key_load_public_rsa1(fd, filename, commentp);
 		close(fd);
+		if (pub != NULL)
+			key_try_load_cert(pub, filename);
 		return pub;
 	}
 	return NULL;
_at__at_ -604,6 +643,8 _at__at_
 		/* closes fd */
 		prv = key_load_private_rsa1(fd, filename, passphrase, NULL);
 	}
+	if (prv != NULL)
+		key_try_load_cert(prv, filename);
 	return prv;
 }
 
_at__at_ -634,6 +675,7 _at__at_
 					if (commentp)
 						*commentp=xstrdup(filename);
 					fclose(f);
+					key_try_load_cert(k, filename);
 					return 1;
 				}
 			}
Index: kex.c
===================================================================
RCS file: /cvs/src/usr.bin/ssh/kex.c,v
retrieving revision 1.76
diff -u -r1.76 kex.c
--- kex.c	3 Aug 2006 03:34:42 -0000	1.76
+++ kex.c	15 Nov 2006 14:14:33 -0000
_at__at_ -312,6 +312,9 _at__at_
 	} else if (strcmp(k->name, KEX_DHGEX_SHA256) == 0) {
 		k->kex_type = KEX_DH_GEX_SHA256;
 		k->evp_md = evp_ssh_sha256();
+	} else if (strcmp(k->name, KEX_DHGEX_CERT) == 0) {
+		k->kex_type = KEX_DH_GEX_CERT;
+		k->evp_md = EVP_sha1();
 	} else
 		fatal("bad kex alg %s", k->name);
 }
Index: kex.h
===================================================================
RCS file: /cvs/src/usr.bin/ssh/kex.h,v
retrieving revision 1.44
diff -u -r1.44 kex.h
--- kex.h	3 Aug 2006 03:34:42 -0000	1.44
+++ kex.h	15 Nov 2006 14:14:33 -0000
_at__at_ -32,6 +32,7 _at__at_
 #define	KEX_DH14		"diffie-hellman-group14-sha1"
 #define	KEX_DHGEX_SHA1		"diffie-hellman-group-exchange-sha1"
 #define	KEX_DHGEX_SHA256	"diffie-hellman-group-exchange-sha256"
+#define	KEX_DHGEX_CERT		"diffie-hellman-group-exchange-cert"
 
 #define COMP_NONE	0
 #define COMP_ZLIB	1
_at__at_ -62,6 +63,7 _at__at_
 	KEX_DH_GRP14_SHA1,
 	KEX_DH_GEX_SHA1,
 	KEX_DH_GEX_SHA256,
+	KEX_DH_GEX_CERT,
 	KEX_MAX
 };
 
Index: kexgexc.c
===================================================================
RCS file: /cvs/src/usr.bin/ssh/kexgexc.c,v
retrieving revision 1.11
diff -u -r1.11 kexgexc.c
--- kexgexc.c	6 Nov 2006 21:25:28 -0000	1.11
+++ kexgexc.c	15 Nov 2006 14:14:33 -0000
_at__at_ -124,8 +124,6 _at__at_
 		fatal("type mismatch for decoded server_host_key_blob");
 	if (kex->verify_host_key == NULL)
 		fatal("cannot verify server_host_key");
-	if (kex->verify_host_key(server_host_key) == -1)
-		fatal("server_host_key verification failed");
 
 	/* DH parameter f, server public DH key */
 	if ((dh_server_pub = BN_new()) == NULL)
_at__at_ -141,7 +139,20 _at__at_
 
 	/* signed H */
 	signature = packet_get_string(&slen);
+	if (kex->kex_type == KEX_DH_GEX_CERT) {
+		u_char *cert;
+		u_int len;
+
+		cert = packet_get_string(&len);
+		if (cert != NULL) {
+			server_host_key->cert = xstrdup(cert);
+			xfree(cert);
+		}
+	}
 	packet_check_eom();
+
+	if (kex->verify_host_key(server_host_key) == -1)
+		fatal("server_host_key verification failed");
 
 	if (!dh_pub_is_valid(dh, dh_server_pub))
 		packet_disconnect("bad server public DH value");
Index: kexgexs.c
===================================================================
RCS file: /cvs/src/usr.bin/ssh/kexgexs.c,v
retrieving revision 1.10
diff -u -r1.10 kexgexs.c
--- kexgexs.c	6 Nov 2006 21:25:28 -0000	1.10
+++ kexgexs.c	15 Nov 2006 14:14:33 -0000
_at__at_ -183,6 +183,13 _at__at_
 	packet_put_string(server_host_key_blob, sbloblen);
 	packet_put_bignum2(dh->pub_key);	/* f */
 	packet_put_string(signature, slen);
+	if (kex->kex_type == KEX_DH_GEX_CERT) {
+		if (server_host_key->cert != NULL)
+			packet_put_string(server_host_key->cert,
+			    strlen(server_host_key->cert));
+		else
+			packet_put_string("", 0);
+	}
 	packet_send();
 
 	xfree(signature);
Index: key.c
===================================================================
RCS file: /cvs/src/usr.bin/ssh/key.c,v
retrieving revision 1.68
diff -u -r1.68 key.c
--- key.c	6 Nov 2006 21:25:28 -0000	1.68
+++ key.c	15 Nov 2006 14:14:33 -0000
_at__at_ -57,6 +57,7 _at__at_
 	k->type = type;
 	k->dsa = NULL;
 	k->rsa = NULL;
+	k->cert = NULL;
 	switch (k->type) {
 	case KEY_RSA1:
 	case KEY_RSA:
_at__at_ -145,6 +146,9 _at__at_
 		fatal("key_free: bad key type %d", k->type);
 		break;
 	}
+	if (k->cert != NULL)
+		xfree(k->cert);
+	k->cert = NULL;
 	xfree(k);
 }
 
_at__at_ -833,6 +837,7 _at__at_
 	pk->flags = k->flags;
 	pk->dsa = NULL;
 	pk->rsa = NULL;
+	pk->cert = k->cert ? xstrdup(k->cert) : NULL;
 
 	switch (k->type) {
 	case KEY_RSA1:
_at__at_ -862,4 +867,100 _at__at_
 	}
 
 	return (pk);
+}
+
+static void
+cert_token(const u_char **c, u_char *buf, int len)
+{
+	int i = 0;
+
+	while (**c && **c != ';' && i + 1 < len)
+		buf[i++] = *(*c)++;
+	if (**c == ';')
+		(*c)++;
+	buf[i] = 0;
+}
+
+/* check whether certificate is valid and signature correct */
+int
+cert_verify(const u_char *cert, const Key *ca_key, const Key *key,
+    const u_char *identity)
+{
+	u_char ca_fp[128], ca_name[128], ca_id[128], ca_opts[512];
+	u_char ca_vf[16], ca_vt[16], ca_alg[64], ca_sig[1024];
+	u_char sigbuf[1024], datbuf[2048], c, *fp;
+	unsigned long vf, vt, now = time(NULL);
+	u_int siglen, i;
+
+	if (cert == NULL || ca_key == NULL || ca_key->type != KEY_RSA ||
+	    ca_key->rsa == NULL || key == NULL) {
+		debug2("cert_verify: invalid arguments");
+		return 0;
+	}
+
+	cert_token(&cert, ca_fp, sizeof(ca_fp));
+	cert_token(&cert, ca_name, sizeof(ca_name));
+	cert_token(&cert, ca_id, sizeof(ca_id));
+	cert_token(&cert, ca_opts, sizeof(ca_opts));
+	cert_token(&cert, ca_vf, sizeof(ca_vf));
+	vf = strtoul(ca_vf, NULL, 10);
+	cert_token(&cert, ca_vt, sizeof(ca_vt));
+	vt = strtoul(ca_vt, NULL, 10);
+	cert_token(&cert, ca_alg, sizeof(ca_alg));
+	cert_token(&cert, ca_sig, sizeof(ca_sig));
+
+	if (strcmp(ca_alg, "ripemd160")) {
+		debug2("cert_verify: unsupported alg '%s'\n", ca_alg);
+		return 0;
+	}
+
+	siglen = 0;
+	for (i = 0; ca_sig[i]; ++i) {
+		if (ca_sig[i] >= '0' && ca_sig[i] <= '9')
+			c = ca_sig[i] - '0';
+		else if (ca_sig[i] >= 'a' && ca_sig[i] <= 'f')
+			c = ca_sig[i] - 'a' + 10;
+		else
+			break;
+		if ((i % 2) == 0)
+			sigbuf[siglen] = c << 4;
+		else
+			sigbuf[siglen++] |= c;
+	}
+
+	fp = key_fingerprint(ca_key, SSH_FP_MD5, SSH_FP_HEX);
+	if (strcmp(fp, ca_fp)) {
+		debug2("cert_verify: CA key fingerprint mismatch ('%s' != '%s')",
+		    fp, ca_fp);
+		xfree(fp);
+		return 0;
+	}
+	xfree(fp);
+
+	fp = key_fingerprint(key, SSH_FP_MD5, SSH_FP_HEX);
+	snprintf(datbuf, sizeof(datbuf), "%s;%s;%s;%s;%lu;%lu",
+	    fp, ca_name, ca_id, ca_opts, vf, vt);
+	xfree(fp);
+
+	if (RSA_verify(NID_ripemd160, datbuf, strlen(datbuf), sigbuf, siglen,
+	    ca_key->rsa) != 1) {
+		debug2("cert_verify: signature not valid ('%s')", ca_sig);
+		return 0;
+	}
+	if (vf && vf > now) {
+		debug2("cert_verify: certificate is not yet valid (%lu > %lu)",
+		    vf, now);
+		return 0;
+	}
+	if (vt && vt < now) {
+		debug2("cert_verify: certificate has expired (%lu < %lu)",
+		    vt, now);
+		return 0;
+	}
+	if (identity != NULL && strcmp(identity, ca_id)) {
+		debug2("cert_verify: identity mismatches ('%s' != '%s')",
+		    identity, ca_id);
+		return 0;
+	}
+	return 1;
 }
Index: key.h
===================================================================
RCS file: /cvs/src/usr.bin/ssh/key.h,v
retrieving revision 1.26
diff -u -r1.26 key.h
--- key.h	3 Aug 2006 03:34:42 -0000	1.26
+++ key.h	15 Nov 2006 14:14:33 -0000
_at__at_ -53,6 +53,7 _at__at_
 	int	 flags;
 	RSA	*rsa;
 	DSA	*dsa;
+	u_char	*cert;
 };
 
 Key		*key_new(int);
_at__at_ -83,5 +84,7 _at__at_
 int	 ssh_dss_verify(const Key *, const u_char *, u_int, const u_char *, u_int);
 int	 ssh_rsa_sign(const Key *, u_char **, u_int *, const u_char *, u_int);
 int	 ssh_rsa_verify(const Key *, const u_char *, u_int, const u_char *, u_int);
+
+int	 cert_verify(const u_char *cert, const Key *, const Key *, const u_char *);
 
 #endif
Index: monitor.c
===================================================================
RCS file: /cvs/src/usr.bin/ssh/monitor.c,v
retrieving revision 1.89
diff -u -r1.89 monitor.c
--- monitor.c	7 Nov 2006 10:31:31 -0000	1.89
+++ monitor.c	15 Nov 2006 14:14:35 -0000
_at__at_ -797,6 +797,17 _at__at_
 
 	if (key != NULL && authctxt->valid) {
 		switch (type) {
+		case MM_CERTKEY: {
+			u_char *cert;
+			u_int clen;
+
+			cert = buffer_get_string(m, &clen);
+			key->cert = xstrdup(cert);
+			allowed = options.certkey_authentication &&
+			    user_cert_key_allowed(authctxt->pw, key);
+			auth_method = "certkey";
+			break;
+		}
 		case MM_USERKEY:
 			allowed = options.pubkey_authentication &&
 			    user_key_allowed(authctxt->pw, key);
_at__at_ -859,7 +870,7 _at__at_
 }
 
 static int
-monitor_valid_userblob(u_char *data, u_int datalen)
+monitor_valid_userblob(u_char *data, u_int datalen, u_char *name)
 {
 	Buffer b;
 	char *p;
_at__at_ -900,7 +911,7 _at__at_
 			fail++;
 	} else {
 		p = buffer_get_string(&b, NULL);
-		if (strcmp("publickey", p) != 0)
+		if (strcmp(name, p) != 0)
 			fail++;
 		xfree(p);
 		if (!buffer_get_char(&b))
_at__at_ -992,8 +1003,11 _at__at_
 		fatal("%s: bad public key blob", __func__);
 
 	switch (key_blobtype) {
+	case MM_CERTKEY:
+		valid_data = monitor_valid_userblob(data, datalen, "certkey");
+		break;
 	case MM_USERKEY:
-		valid_data = monitor_valid_userblob(data, datalen);
+		valid_data = monitor_valid_userblob(data, datalen, "publickey");
 		break;
 	case MM_HOSTKEY:
 		valid_data = monitor_valid_hostbasedblob(data, datalen,
_at__at_ -1015,7 +1029,12 _at__at_
 	xfree(signature);
 	xfree(data);
 
-	auth_method = key_blobtype == MM_USERKEY ? "publickey" : "hostbased";
+	if (key_blobtype == MM_CERTKEY)
+		auth_method = "certkey";
+	else if (key_blobtype == MM_USERKEY)
+		auth_method = "publickey";
+	else
+		auth_method = "hostbased";
 
 	monitor_reset_key_state();
 
Index: monitor_wrap.c
===================================================================
RCS file: /cvs/src/usr.bin/ssh/monitor_wrap.c,v
retrieving revision 1.54
diff -u -r1.54 monitor_wrap.c
--- monitor_wrap.c	12 Aug 2006 20:46:46 -0000	1.54
+++ monitor_wrap.c	15 Nov 2006 14:14:35 -0000
_at__at_ -295,6 +295,12 _at__at_
 }
 
 int
+mm_user_cert_key_allowed(struct passwd *pw, Key *key)
+{
+	return (mm_key_allowed(MM_CERTKEY, NULL, NULL, key));
+}
+
+int
 mm_user_key_allowed(struct passwd *pw, Key *key)
 {
 	return (mm_key_allowed(MM_USERKEY, NULL, NULL, key));
_at__at_ -351,6 +357,8 _at__at_
 	buffer_put_cstring(&m, user ? user : "");
 	buffer_put_cstring(&m, host ? host : "");
 	buffer_put_string(&m, blob, len);
+	if (type == MM_CERTKEY && key && key->cert)
+		buffer_put_string(&m, key->cert, strlen(key->cert));
 	xfree(blob);
 
 	mm_request_send(pmonitor->m_recvfd, MONITOR_REQ_KEYALLOWED, &m);
Index: monitor_wrap.h
===================================================================
RCS file: /cvs/src/usr.bin/ssh/monitor_wrap.h,v
retrieving revision 1.20
diff -u -r1.20 monitor_wrap.h
--- monitor_wrap.h	3 Aug 2006 03:34:42 -0000	1.20
+++ monitor_wrap.h	15 Nov 2006 14:14:35 -0000
_at__at_ -31,7 +31,7 _at__at_
 extern int use_privsep;
 #define PRIVSEP(x)	(use_privsep ? mm_##x : x)
 
-enum mm_keytype {MM_NOKEY, MM_HOSTKEY, MM_USERKEY, MM_RSAHOSTKEY, MM_RSAUSERKEY};
+enum mm_keytype {MM_NOKEY, MM_HOSTKEY, MM_CERTKEY, MM_USERKEY, MM_RSAHOSTKEY, MM_RSAUSERKEY};
 
 struct monitor;
 struct mm_master;
_at__at_ -46,6 +46,7 _at__at_
 int mm_auth_password(struct Authctxt *, char *);
 int mm_key_allowed(enum mm_keytype, char *, char *, Key *);
 int mm_user_key_allowed(struct passwd *, Key *);
+int mm_user_cert_key_allowed(struct passwd *, Key *);
 int mm_hostbased_key_allowed(struct passwd *, char *, char *, Key *);
 int mm_auth_rhosts_rsa_key_allowed(struct passwd *, char *, char *, Key *);
 int mm_key_verify(Key *, u_char *, u_int, u_char *, u_int);
Index: myproposal.h
===================================================================
RCS file: /cvs/src/usr.bin/ssh/myproposal.h,v
retrieving revision 1.21
diff -u -r1.21 myproposal.h
--- myproposal.h	25 Mar 2006 22:22:43 -0000	1.21
+++ myproposal.h	15 Nov 2006 14:14:35 -0000
_at__at_ -24,6 +24,7 _at__at_
  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 #define KEX_DEFAULT_KEX		\
+	"diffie-hellman-group-exchange-cert," \
 	"diffie-hellman-group-exchange-sha256," \
 	"diffie-hellman-group-exchange-sha1," \
 	"diffie-hellman-group14-sha1," \
Index: pathnames.h
===================================================================
RCS file: /cvs/src/usr.bin/ssh/pathnames.h,v
retrieving revision 1.16
diff -u -r1.16 pathnames.h
--- pathnames.h	25 Mar 2006 22:22:43 -0000	1.16
+++ pathnames.h	15 Nov 2006 14:14:35 -0000
_at__at_ -36,6 +36,7 _at__at_
 #define _PATH_DH_MODULI			ETCDIR "/moduli"
 /* Backwards compatibility */
 #define _PATH_DH_PRIMES			ETCDIR "/primes"
+#define	_PATH_CA_KEY_FILE		SSHDIR "/ca.pub"
 
 #define _PATH_SSH_PROGRAM		"/usr/bin/ssh"
 
Index: readconf.c
===================================================================
RCS file: /cvs/src/usr.bin/ssh/readconf.c,v
retrieving revision 1.159
diff -u -r1.159 readconf.c
--- readconf.c	3 Aug 2006 03:34:42 -0000	1.159
+++ readconf.c	15 Nov 2006 14:14:36 -0000
_at__at_ -117,7 +117,8 _at__at_
 	oBatchMode, oCheckHostIP, oStrictHostKeyChecking, oCompression,
 	oCompressionLevel, oTCPKeepAlive, oNumberOfPasswordPrompts,
 	oUsePrivilegedPort, oLogLevel, oCiphers, oProtocol, oMacs,
-	oGlobalKnownHostsFile2, oUserKnownHostsFile2, oPubkeyAuthentication,
+	oGlobalKnownHostsFile2, oUserKnownHostsFile2, oCertkeyAuthentication,
+	oCAKeyFile, oPubkeyAuthentication,
 	oKbdInteractiveAuthentication, oKbdInteractiveDevices, oHostKeyAlias,
 	oDynamicForward, oPreferredAuthentications, oHostbasedAuthentication,
 	oHostKeyAlgorithms, oBindAddress, oSmartcardDevice,
_at__at_ -148,6 +149,8 _at__at_
 	{ "kbdinteractiveauthentication", oKbdInteractiveAuthentication },
 	{ "kbdinteractivedevices", oKbdInteractiveDevices },
 	{ "rsaauthentication", oRSAAuthentication },
+	{ "certkeyauthentication", oCertkeyAuthentication },
+	{ "cakeyfile", oCAKeyFile },
 	{ "pubkeyauthentication", oPubkeyAuthentication },
 	{ "dsaauthentication", oPubkeyAuthentication },		    /* alias */
 	{ "rhostsrsaauthentication", oRhostsRSAAuthentication },
_at__at_ -412,6 +415,10 _at__at_
 		charptr = &options->kbd_interactive_devices;
 		goto parse_string;
 
+	case oCertkeyAuthentication:
+		intptr = &options->certkey_authentication;
+		goto parse_flag;
+
 	case oPubkeyAuthentication:
 		intptr = &options->pubkey_authentication;
 		goto parse_flag;
_at__at_ -560,6 +567,10 _at__at_
 			*charptr = xstrdup(arg);
 		break;
 
+	case oCAKeyFile:
+		charptr = &options->ca_key_file;
+		goto parse_string;
+
 	case oGlobalKnownHostsFile:
 		charptr = &options->system_hostfile;
 		goto parse_string;
_at__at_ -1002,6 +1013,8 _at__at_
 	options->gateway_ports = -1;
 	options->use_privileged_port = -1;
 	options->rsa_authentication = -1;
+	options->certkey_authentication = -1;
+	options->ca_key_file = NULL;
 	options->pubkey_authentication = -1;
 	options->challenge_response_authentication = -1;
 	options->gss_authentication = -1;
_at__at_ -1088,6 +1101,10 _at__at_
 		options->use_privileged_port = 0;
 	if (options->rsa_authentication == -1)
 		options->rsa_authentication = 1;
+	if (options->certkey_authentication == -1)
+		options->certkey_authentication = 0;
+	if (options->ca_key_file == NULL)
+		options->ca_key_file = _PATH_CA_KEY_FILE;
 	if (options->pubkey_authentication == -1)
 		options->pubkey_authentication = 1;
 	if (options->challenge_response_authentication == -1)
Index: readconf.h
===================================================================
RCS file: /cvs/src/usr.bin/ssh/readconf.h,v
retrieving revision 1.71
diff -u -r1.71 readconf.h
--- readconf.h	3 Aug 2006 03:34:42 -0000	1.71
+++ readconf.h	15 Nov 2006 14:14:36 -0000
_at__at_ -39,6 +39,8 _at__at_
 	int     rhosts_rsa_authentication;	/* Try rhosts with RSA
 						 * authentication. */
 	int     rsa_authentication;	/* Try RSA authentication. */
+	int     certkey_authentication;	/* Try ssh2 certkey authentication. */
+	char   *ca_key_file;		/* File containing CA key. */
 	int     pubkey_authentication;	/* Try ssh2 pubkey authentication. */
 	int     hostbased_authentication;	/* ssh2's rhosts_rsa */
 	int     challenge_response_authentication;
Index: servconf.c
===================================================================
RCS file: /cvs/src/usr.bin/ssh/servconf.c,v
retrieving revision 1.165
diff -u -r1.165 servconf.c
--- servconf.c	14 Aug 2006 12:40:25 -0000	1.165
+++ servconf.c	15 Nov 2006 14:14:37 -0000
_at__at_ -56,6 +56,7 _at__at_
 	options->listen_addrs = NULL;
 	options->address_family = -1;
 	options->num_host_key_files = 0;
+	options->ca_key_file = NULL;
 	options->pid_file = NULL;
 	options->server_key_bits = -1;
 	options->login_grace_time = -1;
_at__at_ -77,6 +78,7 _at__at_
 	options->hostbased_authentication = -1;
 	options->hostbased_uses_name_from_packet_only = -1;
 	options->rsa_authentication = -1;
+	options->certkey_authentication = -1;
 	options->pubkey_authentication = -1;
 	options->kerberos_authentication = -1;
 	options->kerberos_or_local_passwd = -1;
_at__at_ -134,6 +136,8 _at__at_
 			    _PATH_HOST_DSA_KEY_FILE;
 		}
 	}
+	if (options->ca_key_file == NULL)
+		options->ca_key_file = _PATH_CA_KEY_FILE;
 	if (options->num_ports == 0)
 		options->ports[options->num_ports++] = SSH_DEFAULT_PORT;
 	if (options->listen_addrs == NULL)
_at__at_ -180,6 +184,8 _at__at_
 		options->hostbased_uses_name_from_packet_only = 0;
 	if (options->rsa_authentication == -1)
 		options->rsa_authentication = 1;
+	if (options->certkey_authentication == -1)
+		options->certkey_authentication = 0;
 	if (options->pubkey_authentication == -1)
 		options->pubkey_authentication = 1;
 	if (options->kerberos_authentication == -1)
_at__at_ -259,9 +265,9 _at__at_
 	sStrictModes, sEmptyPasswd, sTCPKeepAlive,
 	sPermitUserEnvironment, sUseLogin, sAllowTcpForwarding, sCompression,
 	sAllowUsers, sDenyUsers, sAllowGroups, sDenyGroups,
-	sIgnoreUserKnownHosts, sCiphers, sMacs, sProtocol, sPidFile,
-	sGatewayPorts, sPubkeyAuthentication, sXAuthLocation, sSubsystem,
-	sMaxStartups, sMaxAuthTries,
+	sIgnoreUserKnownHosts, sCiphers, sMacs, sProtocol, sPidFile, sCAKeyFile,
+	sGatewayPorts, sCertkeyAuthentication, sPubkeyAuthentication, sXAuthLocation,
+	sSubsystem, sMaxStartups, sMaxAuthTries,
 	sBanner, sUseDNS, sHostbasedAuthentication,
 	sHostbasedUsesNameFromPacketOnly, sClientAliveInterval,
 	sClientAliveCountMax, sAuthorizedKeysFile, sAuthorizedKeysFile2,
_at__at_ -282,6 +288,7 _at__at_
 	u_int flags;
 } keywords[] = {
 	{ "port", sPort, SSHCFG_GLOBAL },
+	{ "cakeyfile", sCAKeyFile, SSHCFG_GLOBAL },
 	{ "hostkey", sHostKeyFile, SSHCFG_GLOBAL },
 	{ "hostdsakey", sHostKeyFile, SSHCFG_GLOBAL },		/* alias */
 	{ "pidfile", sPidFile, SSHCFG_GLOBAL },
_at__at_ -296,6 +303,7 _at__at_
 	{ "hostbasedauthentication", sHostbasedAuthentication, SSHCFG_GLOBAL },
 	{ "hostbasedusesnamefrompacketonly", sHostbasedUsesNameFromPacketOnly, SSHCFG_GLOBAL },
 	{ "rsaauthentication", sRSAAuthentication, SSHCFG_GLOBAL },
+	{ "certkeyauthentication", sCertkeyAuthentication, SSHCFG_GLOBAL },
 	{ "pubkeyauthentication", sPubkeyAuthentication, SSHCFG_GLOBAL },
 	{ "dsaauthentication", sPubkeyAuthentication, SSHCFG_GLOBAL }, /* alias */
 #ifdef KRB5
_at__at_ -738,6 +746,10 _at__at_
 		}
 		break;
 
+	case sCAKeyFile:
+		charptr = &options->ca_key_file;
+		goto parse_filename;
+
 	case sPidFile:
 		charptr = &options->pid_file;
 		goto parse_filename;
_at__at_ -803,6 +815,10 _at__at_
 
 	case sRSAAuthentication:
 		intptr = &options->rsa_authentication;
+		goto parse_flag;
+
+	case sCertkeyAuthentication:
+		intptr = &options->certkey_authentication;
 		goto parse_flag;
 
 	case sPubkeyAuthentication:
Index: servconf.h
===================================================================
RCS file: /cvs/src/usr.bin/ssh/servconf.h,v
retrieving revision 1.79
diff -u -r1.79 servconf.h
--- servconf.h	14 Aug 2006 12:40:25 -0000	1.79
+++ servconf.h	15 Nov 2006 14:14:37 -0000
_at__at_ -43,6 +43,7 _at__at_
 	char   *listen_addr;		/* Address on which the server listens. */
 	struct addrinfo *listen_addrs;	/* Addresses on which the server listens. */
 	int     address_family;		/* Address family used by the server. */
+	char   *ca_key_file;		/* File containing CA key. */
 	char   *host_key_files[MAX_HOSTKEYS];	/* Files containing host keys. */
 	int     num_host_key_files;     /* Number of files for host keys. */
 	char   *pid_file;	/* Where to put our pid */
_at__at_ -75,6 +76,7 _at__at_
 	int     hostbased_uses_name_from_packet_only; /* experimental */
 	int     rsa_authentication;	/* If true, permit RSA authentication. */
 	int     pubkey_authentication;	/* If true, permit ssh2 pubkey authentication. */
+	int     certkey_authentication;	/* If true, permit ssh2 certkey authentication. */
 	int     kerberos_authentication;	/* If true, permit Kerberos
 						 * authentication. */
 	int     kerberos_or_local_passwd;	/* If true, permit kerberos
Index: ssh-keygen.c
===================================================================
RCS file: /cvs/src/usr.bin/ssh/ssh-keygen.c,v
retrieving revision 1.156
diff -u -r1.156 ssh-keygen.c
--- ssh-keygen.c	14 Nov 2006 19:41:04 -0000	1.156
+++ ssh-keygen.c	15 Nov 2006 14:14:37 -0000
_at__at_ -94,6 +94,8 _at__at_
 int print_public = 0;
 int print_generic = 0;
 
+int sign_host_key = 0;
+
 char *key_type_name = NULL;
 
 /* argv0 */
_at__at_ -494,6 +496,142 _at__at_
 #endif /* SMARTCARD */
 
 static void
+ask_string(const char *question, char *buf, int len)
+{
+	printf("%s", question);
+	if (fgets(buf, len, stdin) == NULL)
+		exit(1);
+	buf[len - 1] = 0;
+	len = strlen(buf);
+	if (len > 0 && buf[len - 1] == '\n')
+		buf[len - 1] = 0;
+}
+
+static unsigned long
+ask_date(const char *question)
+{
+	char buf[64];
+	int len;
+	unsigned year, mon = 1, mday = 1, hour = 0, min = 0, sec = 0;
+	struct tm tm;
+
+	printf("%s", question);
+	if (fgets(buf, sizeof(buf), stdin) == NULL)
+		exit(1);
+	buf[sizeof(buf) - 1] = 0;
+	len = strlen(buf);
+	if (len > 0 && buf[len - 1] == '\n')
+		buf[len - 1] = 0;
+	if (sscanf(buf, "%4u%2u%2u%2u%2u%2u",
+	    &year, &mon, &mday, &hour, &min, &sec) < 1) {
+		error("invalid date");
+		exit(1);	
+	}
+	if (!year)
+		return 0;
+	memset(&tm, 0, sizeof(tm));
+	tm.tm_year = year - 1900;
+	tm.tm_mon = mon - 1;
+	tm.tm_mday = mday;
+	tm.tm_hour = hour;
+	tm.tm_min = min;
+	tm.tm_sec = sec;
+	return timegm(&tm);
+}
+
+static void
+do_sign_host_key(struct passwd *pw)
+{
+	struct stat st;
+	u_char ca_name[128], ca_id[128], ca_opts[512];
+	u_char dat[8192], sig[8192], key_fn[1024], cert_fn[1024];
+	unsigned long valid_from, valid_to;
+	u_int slen;
+	Key *ca_key, *host_key;
+	char *ca_fp, *host_fp;
+	FILE *f;
+	int i;
+
+	if (!have_identity)
+		ask_filename(pw, "Enter file in which the CA key is");
+	if (stat(identity_file, &st) < 0) {
+		perror(identity_file);
+		exit(1);
+	}
+	ca_key = load_identity(identity_file);
+	if (ca_key == NULL) {
+		error("load failed");
+		exit(1);
+	}
+	if (ca_key->type != KEY_RSA || ca_key->rsa == NULL) {
+		error("key invalid");
+		exit(1);
+	}
+	ca_fp = key_fingerprint(ca_key, SSH_FP_MD5, SSH_FP_HEX);
+	printf("CA key fingerprint %s\n", ca_fp);
+
+	ask_string("Enter file in which the user/host key is: ", key_fn, sizeof(key_fn));
+	if (stat(key_fn, &st) < 0) {
+		perror(key_fn);
+		exit(1);
+	}
+	host_key = key_load_public(key_fn, NULL);
+	if (host_key == NULL) {
+		error("load failed");
+		exit(1);
+	}
+	strlcpy(cert_fn, key_fn, sizeof(cert_fn));
+	if (strlen(cert_fn) > 4 && !strcmp(cert_fn + strlen(cert_fn) - 4, ".pub"))
+		cert_fn[strlen(cert_fn) - 4] = 0;
+	strlcat(cert_fn, ".cert", sizeof(cert_fn));
+	host_fp = key_fingerprint(host_key, SSH_FP_MD5, SSH_FP_HEX);
+	printf("host/user key fingerprint %s\n", host_fp);
+
+	ask_string("CA name    : ", ca_name, sizeof(ca_name));
+	if (!ca_name[0] || strchr(ca_name, ';')) {
+		error("invalid CA name");
+		exit(1);
+	}
+	ask_string("identity   : ", ca_id, sizeof(ca_id));
+	if (!ca_id[0] || strchr(ca_id, ';')) {
+		error("invalid identity");
+		exit(1);
+	}
+	ask_string("options    : ", ca_opts, sizeof(ca_opts));
+	if (strchr(ca_opts, ';')) {
+		error("invalid options");
+		exit(1);
+	}
+	valid_from = ask_date("valid from : ");
+	valid_to = ask_date("valid until: ");
+
+	snprintf(dat, sizeof(dat), "%s;%s;%s;%s;%lu;%lu",
+	    host_fp, ca_name, ca_id, ca_opts, valid_from, valid_to);
+	if (RSA_sign(NID_ripemd160, dat, strlen(dat), sig, &slen, ca_key->rsa) != 1 || !slen) {
+		fprintf(stderr, "RSA_sign() failed\n");
+		exit(1);
+	}
+	if (RSA_verify(NID_ripemd160, dat, strlen(dat), sig, slen, ca_key->rsa) != 1) {
+		fprintf(stderr, "RSA_verify() failed\n");
+		exit(1);
+	}
+
+	snprintf(dat, sizeof(dat), "%s;%s;%s;%s;%lu;%lu;ripemd160;",
+	    ca_fp, ca_name, ca_id, ca_opts, valid_from, valid_to);
+	for (i = 0; i < slen; ++i)
+		snprintf(dat + strlen(dat), sizeof(dat) - strlen(dat), "%.2x", sig[i]);
+	f = fopen(cert_fn, "w");
+	if (f == NULL) {
+		fprintf(stderr, "fopen: %s: %s\n", cert_fn, strerror(errno));
+		exit(1);
+	}
+	fprintf(f, "%s", dat);
+	fclose(f);
+	printf("Certificate has been saved in %s.\n", cert_fn);
+	exit(0);
+}
+
+static void
 do_fingerprint(struct passwd *pw)
 {
 	FILE *f;
_at__at_ -1026,6 +1164,7 _at__at_
 	fprintf(stderr, "  -R hostname Remove host from known_hosts file.\n");
 	fprintf(stderr, "  -r hostname Print DNS resource record.\n");
 	fprintf(stderr, "  -S start    Start point (hex) for generating DH-GEX moduli.\n");
+	fprintf(stderr, "  -s          Generate certificate for user/host key using CA key.\n");
 	fprintf(stderr, "  -T file     Screen candidates for DH-GEX moduli.\n");
 	fprintf(stderr, "  -t type     Specify type of key to create.\n");
 #ifdef SMARTCARD
_at__at_ -1079,7 +1218,7 _at__at_
 	}
 
 	while ((opt = getopt(argc, argv,
-	    "degiqpclBHvxXyF:b:f:t:U:D:P:N:C:r:g:R:T:G:M:S:a:W:")) != -1) {
+	    "degiqpsclBHvxXyF:b:f:t:U:D:P:N:C:r:g:R:T:G:M:S:a:W:")) != -1) {
 		switch (opt) {
 		case 'b':
 			bits = (u_int32_t)strtonum(optarg, 768, 32768, &errstr);
_at__at_ -1156,6 +1295,9 _at__at_
 		case 'U':
 			reader_id = optarg;
 			break;
+		case 's':
+			sign_host_key = 1;
+			break;
 		case 'v':
 			if (log_level == SYSLOG_LEVEL_INFO)
 				log_level = SYSLOG_LEVEL_DEBUG1;
_at__at_ -1221,6 +1363,8 _at__at_
 		printf("Can only have one of -p and -c.\n");
 		usage();
 	}
+	if (sign_host_key)
+		do_sign_host_key(pw);
 	if (delete_host || hash_hosts || find_host)
 		do_known_hosts(pw, rr_hostname);
 	if (print_fingerprint || print_bubblebabble)
Index: ssh_config.5
===================================================================
RCS file: /cvs/src/usr.bin/ssh/ssh_config.5,v
retrieving revision 1.97
diff -u -r1.97 ssh_config.5
--- ssh_config.5	27 Jul 2006 08:00:50 -0000	1.97
+++ ssh_config.5	15 Nov 2006 14:14:38 -0000
_at__at_ -145,6 +145,15 _at__at_
 .Cm UsePrivilegedPort
 is set to
 .Dq yes .
+.It Cm CAKeyFile
+Specifies a file containing a public CA key.
+The default is
+.Pa /etc/ssh/ca.pub .
+.It Cm CertkeyAuthentication
+Specifies whether certified key authentication is allowed.
+The default is
+.Dq no .
+Note that this option applies to protocol version 2 only.
 .It Cm ChallengeResponseAuthentication
 Specifies whether to use challenge-response authentication.
 The argument to this keyword must be
Index: sshconnect.c
===================================================================
RCS file: /cvs/src/usr.bin/ssh/sshconnect.c,v
retrieving revision 1.200
diff -u -r1.200 sshconnect.c
--- sshconnect.c	10 Oct 2006 10:12:45 -0000	1.200
+++ sshconnect.c	15 Nov 2006 14:14:39 -0000
_at__at_ -21,6 +21,7 _at__at_
 
 #include <netinet/in.h>
 
+#include <openssl/objects.h>
 #include <ctype.h>
 #include <errno.h>
 #include <netdb.h>
_at__at_ -48,6 +49,7 _at__at_
 #include "misc.h"
 #include "dns.h"
 #include "version.h"
+#include "authfile.h"
 
 char *client_version_string = NULL;
 char *server_version_string = NULL;
_at__at_ -884,6 +886,19 _at__at_
 {
 	struct stat st;
 	int flags = 0;
+
+	if (options.certkey_authentication && host_key->cert != NULL) {
+		Key *ca_key;
+		int verified;
+
+		ca_key = key_load_public(options.ca_key_file, NULL);
+		if (ca_key != NULL) {
+			verified = cert_verify(host_key->cert, ca_key, host_key, NULL);
+			key_free(ca_key);
+			if (verified)
+				return 0;
+		}
+	}
 
 	if (options.verify_host_key_dns &&
 	    verify_host_key_dns(host, hostaddr, host_key, &flags) == 0) {
Index: sshconnect2.c
===================================================================
RCS file: /cvs/src/usr.bin/ssh/sshconnect2.c,v
retrieving revision 1.162
diff -u -r1.162 sshconnect2.c
--- sshconnect2.c	30 Aug 2006 00:06:51 -0000	1.162
+++ sshconnect2.c	15 Nov 2006 14:14:40 -0000
_at__at_ -133,6 +133,7 _at__at_
 	kex->kex[KEX_DH_GRP14_SHA1] = kexdh_client;
 	kex->kex[KEX_DH_GEX_SHA1] = kexgex_client;
 	kex->kex[KEX_DH_GEX_SHA256] = kexgex_client;
+	kex->kex[KEX_DH_GEX_CERT] = kexgex_client;
 	kex->client_version_string=client_version_string;
 	kex->server_version_string=server_version_string;
 	kex->verify_host_key=&verify_host_key_callback;
_at__at_ -168,6 +169,7 _at__at_
 	Key	*key;			/* public/private key */
 	char	*filename;		/* comment for agent-only keys */
 	int	tried;
+	int	triedcert;
 	int	isprivate;		/* key points to the private key */
 };
 TAILQ_HEAD(idlist, identity);
_at__at_ -206,6 +208,7 _at__at_
 void	input_userauth_passwd_changereq(int, u_int32_t, void *);
 
 int	userauth_none(Authctxt *);
+int	userauth_certkey(Authctxt *);
 int	userauth_pubkey(Authctxt *);
 int	userauth_passwd(Authctxt *);
 int	userauth_kbdint(Authctxt *);
_at__at_ -224,6 +227,7 _at__at_
 void	userauth(Authctxt *, char *);
 
 static int sign_and_send_pubkey(Authctxt *, Identity *);
+static int sign_and_send_certkey(Authctxt *, Identity *);
 static void pubkey_prepare(Authctxt *);
 static void pubkey_cleanup(Authctxt *);
 static Key *load_identity_file(char *);
_at__at_ -243,6 +247,10 _at__at_
 		userauth_hostbased,
 		&options.hostbased_authentication,
 		NULL},
+	{"certkey",
+		userauth_certkey,
+		&options.certkey_authentication,
+		NULL},
 	{"publickey",
 		userauth_pubkey,
 		&options.pubkey_authentication,
_at__at_ -472,7 +480,11 _at__at_
 	 */
 	TAILQ_FOREACH_REVERSE(id, &authctxt->keys, idlist, next) {
 		if (key_equal(key, id->key)) {
-			sent = sign_and_send_pubkey(authctxt, id);
+			if (!strcmp(authctxt->method->name, "certkey")) {
+				if (id->key->cert != NULL)
+					sent = sign_and_send_certkey(authctxt, id);
+			} else
+				sent = sign_and_send_pubkey(authctxt, id);
 			break;
 		}
 	}
_at__at_ -851,6 +863,93 _at__at_
 }
 
 static int
+sign_and_send_certkey(Authctxt *authctxt, Identity *id)
+{
+	Buffer b;
+	u_char *blob, *signature;
+	u_int bloblen, slen;
+	u_int skip = 0;
+	int ret = -1;
+	int have_sig = 1;
+
+	debug3("sign_and_send_certkey");
+
+	if (key_to_blob(id->key, &blob, &bloblen) == 0) {
+		/* we cannot handle this key */
+		debug3("sign_and_send_certkey: cannot handle key");
+		return 0;
+	}
+	/* data to be signed */
+	buffer_init(&b);
+	if (datafellows & SSH_OLD_SESSIONID) {
+		buffer_append(&b, session_id2, session_id2_len);
+		skip = session_id2_len;
+	} else {
+		buffer_put_string(&b, session_id2, session_id2_len);
+		skip = buffer_len(&b);
+	}
+	buffer_put_char(&b, SSH2_MSG_USERAUTH_REQUEST);
+	buffer_put_cstring(&b, authctxt->server_user);
+	buffer_put_cstring(&b,
+	    datafellows & SSH_BUG_PKSERVICE ?
+	    "ssh-userauth" :
+	    authctxt->service);
+	if (datafellows & SSH_BUG_PKAUTH) {
+		buffer_put_char(&b, have_sig);
+	} else {
+		buffer_put_cstring(&b, authctxt->method->name);
+		buffer_put_char(&b, have_sig);
+		buffer_put_cstring(&b, key_ssh_name(id->key));
+	}
+	buffer_put_string(&b, blob, bloblen);
+
+	/* generate signature */
+	ret = identity_sign(id, &signature, &slen,
+	    buffer_ptr(&b), buffer_len(&b));
+	if (ret == -1) {
+		xfree(blob);
+		buffer_free(&b);
+		return 0;
+	}
+#ifdef DEBUG_PK
+	buffer_dump(&b);
+#endif
+	if (datafellows & SSH_BUG_PKSERVICE) {
+		buffer_clear(&b);
+		buffer_append(&b, session_id2, session_id2_len);
+		skip = session_id2_len;
+		buffer_put_char(&b, SSH2_MSG_USERAUTH_REQUEST);
+		buffer_put_cstring(&b, authctxt->server_user);
+		buffer_put_cstring(&b, authctxt->service);
+		buffer_put_cstring(&b, authctxt->method->name);
+		buffer_put_char(&b, have_sig);
+		if (!(datafellows & SSH_BUG_PKAUTH))
+			buffer_put_cstring(&b, key_ssh_name(id->key));
+		buffer_put_string(&b, blob, bloblen);
+	}
+	xfree(blob);
+
+	/* append signature */
+	buffer_put_string(&b, signature, slen);
+	xfree(signature);
+
+	buffer_put_string(&b, id->key->cert, strlen(id->key->cert));
+
+	/* skip session id and packet type */
+	if (buffer_len(&b) < skip + 1)
+		fatal("userauth_pubkey: internal error");
+	buffer_consume(&b, skip + 1);
+
+	/* put remaining data from buffer into packet */
+	packet_start(SSH2_MSG_USERAUTH_REQUEST);
+	packet_put_raw(buffer_ptr(&b), buffer_len(&b));
+	buffer_free(&b);
+	packet_send();
+
+	return 1;
+}
+
+static int
 sign_and_send_pubkey(Authctxt *authctxt, Identity *id)
 {
 	Buffer b;
_at__at_ -936,6 +1035,31 _at__at_
 }
 
 static int
+send_certkey_test(Authctxt *authctxt, Identity *id)
+{
+	u_char *blob;
+	u_int bloblen, have_sig = 0;
+
+	if (key_to_blob(id->key, &blob, &bloblen) == 0)
+		return 0;
+	/* register callback for USERAUTH_PK_OK message */
+	dispatch_set(SSH2_MSG_USERAUTH_PK_OK, &input_userauth_pk_ok);
+
+	packet_start(SSH2_MSG_USERAUTH_REQUEST);
+	packet_put_cstring(authctxt->server_user);
+	packet_put_cstring(authctxt->service);
+	packet_put_cstring(authctxt->method->name);
+	packet_put_char(have_sig);
+	if (!(datafellows & SSH_BUG_PKAUTH))
+		packet_put_cstring(key_ssh_name(id->key));
+	packet_put_string(blob, bloblen);
+	xfree(blob);
+	packet_put_string(id->key->cert, strlen(id->key->cert));
+	packet_send();
+	return 1;
+}
+
+static int
 send_pubkey_test(Authctxt *authctxt, Identity *id)
 {
 	u_char *blob;
_at__at_ -1095,6 +1219,42 _at__at_
 			xfree(id->filename);
 		xfree(id);
 	}
+}
+
+int
+userauth_certkey(Authctxt *authctxt)
+{
+	Identity *id;
+	int sent = 0;
+
+	while ((id = TAILQ_FIRST(&authctxt->keys))) {
+		if (id->triedcert++)
+			return (0);
+		/* move key to the end of the queue */
+		TAILQ_REMOVE(&authctxt->keys, id, next);
+		TAILQ_INSERT_TAIL(&authctxt->keys, id, next);
+		/*
+		 * send a test message if we have the public key. for
+		 * encrypted keys we cannot do this and have to load the
+		 * private key instead
+		 */
+		if (id->key && id->key->cert && id->key->type != KEY_RSA1) {
+			debug("Offering public key: %s", id->filename);
+			sent = send_certkey_test(authctxt, id);
+		} else if (id->key == NULL) {
+			debug("Trying private key: %s", id->filename);
+			id->key = load_identity_file(id->filename);
+			if (id->key != NULL && id->key->cert != NULL) {
+				id->isprivate = 1;
+				sent = sign_and_send_certkey(authctxt, id);
+				key_free(id->key);
+				id->key = NULL;
+			}
+		}
+		if (sent)
+			return (sent);
+	}
+	return (0);
 }
 
 int
Index: sshd.c
===================================================================
RCS file: /cvs/src/usr.bin/ssh/sshd.c,v
retrieving revision 1.348
diff -u -r1.348 sshd.c
--- sshd.c	6 Nov 2006 21:25:28 -0000	1.348
+++ sshd.c	15 Nov 2006 14:14:40 -0000
_at__at_ -1999,6 +1999,7 _at__at_
 	kex->kex[KEX_DH_GRP14_SHA1] = kexdh_server;
 	kex->kex[KEX_DH_GEX_SHA1] = kexgex_server;
 	kex->kex[KEX_DH_GEX_SHA256] = kexgex_server;
+	kex->kex[KEX_DH_GEX_CERT] = kexgex_server;
 	kex->server = 1;
 	kex->client_version_string=client_version_string;
 	kex->server_version_string=server_version_string;
Index: sshd_config.5
===================================================================
RCS file: /cvs/src/usr.bin/ssh/sshd_config.5,v
retrieving revision 1.70
diff -u -r1.70 sshd_config.5
--- sshd_config.5	21 Aug 2006 08:14:01 -0000	1.70
+++ sshd_config.5	15 Nov 2006 14:14:41 -0000
_at__at_ -167,6 +167,15 _at__at_
 authentication is allowed.
 This option is only available for protocol version 2.
 By default, no banner is displayed.
+.It Cm CAKeyFile
+Specifies a file containing a public CA key.
+The default is
+.Pa /etc/ssh/ca.pub .
+.It Cm CertkeyAuthentication
+Specifies whether certified key authentication is allowed.
+The default is
+.Dq no .
+Note that this option applies to protocol version 2 only.
 .It Cm ChallengeResponseAuthentication
 Specifies whether challenge-response authentication is allowed.
 All authentication styles from
Index: sshd/Makefile
===================================================================
RCS file: /cvs/src/usr.bin/ssh/sshd/Makefile,v
retrieving revision 1.64
diff -u -r1.64 Makefile
--- sshd/Makefile	23 Aug 2004 14:26:39 -0000	1.64
+++ sshd/Makefile	15 Nov 2006 14:14:41 -0000
_at__at_ -14,7 +14,7 _at__at_
 	auth.c auth1.c auth2.c auth-options.c session.c \
 	auth-chall.c auth2-chall.c groupaccess.c \
 	auth-skey.c auth-bsdauth.c auth2-hostbased.c auth2-kbdint.c \
-	auth2-none.c auth2-passwd.c auth2-pubkey.c \
+	auth2-none.c auth2-passwd.c auth2-pubkey.c auth2-certkey.c \
 	monitor_mm.c monitor.c monitor_wrap.c \
 	kexdhs.c kexgexs.c
 
--- /dev/null	Wed Nov 15 15:14:51 2006
+++ auth2-certkey.c	Wed Nov 15 11:07:56 2006
_at__at_ -0,0 +1,196 _at__at_
+/* $OpenBSD$ */
+/*
+ * Copyright (c) 2000 Markus Friedl.  All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <pwd.h>
+#include <stdio.h>
+#include <stdarg.h>
+
+#include "xmalloc.h"
+#include "ssh.h"
+#include "ssh2.h"
+#include "packet.h"
+#include "buffer.h"
+#include "log.h"
+#include "servconf.h"
+#include "compat.h"
+#include "key.h"
+#include "hostfile.h"
+#include "auth.h"
+#include "pathnames.h"
+#include "uidswap.h"
+#include "auth-options.h"
+#include "canohost.h"
+#ifdef GSSAPI
+#include "ssh-gss.h"
+#endif
+#include "monitor_wrap.h"
+#include "misc.h"
+
+/* import */
+extern ServerOptions options;
+extern u_char *session_id2;
+extern u_int session_id2_len;
+
+static int
+userauth_certkey(Authctxt *authctxt)
+{
+	Buffer b;
+	Key *key = NULL;
+	char *pkalg;
+	u_char *pkblob, *sig, *cert;
+	u_int alen, blen, slen, clen;
+	int have_sig, pktype;
+	int authenticated = 0;
+
+	if (!authctxt->valid) {
+		debug2("userauth_certkey: disabled because of invalid user");
+		return 0;
+	}
+	have_sig = packet_get_char();
+	if (datafellows & SSH_BUG_PKAUTH) {
+		debug2("userauth_certkey: SSH_BUG_PKAUTH");
+		/* no explicit pkalg given */
+		pkblob = packet_get_string(&blen);
+		buffer_init(&b);
+		buffer_append(&b, pkblob, blen);
+		/* so we have to extract the pkalg from the pkblob */
+		pkalg = buffer_get_string(&b, &alen);
+		buffer_free(&b);
+	} else {
+		pkalg = packet_get_string(&alen);
+		pkblob = packet_get_string(&blen);
+	}
+	pktype = key_type_from_name(pkalg);
+	if (pktype == KEY_UNSPEC) {
+		/* this is perfectly legal */
+		logit("userauth_certkey: unsupported public key algorithm: %s",
+		    pkalg);
+		goto done;
+	}
+	key = key_from_blob(pkblob, blen);
+	if (key == NULL) {
+		error("userauth_certkey: cannot decode key: %s", pkalg);
+		goto done;
+	}
+	if (key->type != pktype) {
+		error("userauth_certkey: type mismatch for decoded key "
+		    "(received %d, expected %d)", key->type, pktype);
+		goto done;
+	}
+	if (have_sig) {
+		sig = packet_get_string(&slen);
+		cert = packet_get_string(&clen);
+		if (!cert || clen <= 0) {
+			error("userauth_certkey: no cert");
+			goto done;
+		}
+		key->cert = xstrdup(cert);
+		packet_check_eom();
+		buffer_init(&b);
+		if (datafellows & SSH_OLD_SESSIONID) {
+			buffer_append(&b, session_id2, session_id2_len);
+		} else {
+			buffer_put_string(&b, session_id2, session_id2_len);
+		}
+		/* reconstruct packet */
+		buffer_put_char(&b, SSH2_MSG_USERAUTH_REQUEST);
+		buffer_put_cstring(&b, authctxt->user);
+		buffer_put_cstring(&b,
+		    datafellows & SSH_BUG_PKSERVICE ?
+		    "ssh-userauth" :
+		    authctxt->service);
+		if (datafellows & SSH_BUG_PKAUTH) {
+			buffer_put_char(&b, have_sig);
+		} else {
+			buffer_put_cstring(&b, "certkey");
+			buffer_put_char(&b, have_sig);
+			buffer_put_cstring(&b, pkalg);
+		}
+                buffer_put_string(&b, pkblob, blen);
+#ifdef DEBUG_PK
+		buffer_dump(&b);
+#endif
+		/* test for correct signature */
+		authenticated = 0;
+		if (PRIVSEP(user_cert_key_allowed(authctxt->pw, key)) &&
+		    PRIVSEP(key_verify(key, sig, slen, buffer_ptr(&b),
+		    buffer_len(&b))) == 1)
+			authenticated = 1;
+		buffer_free(&b);
+		xfree(sig);
+	} else {
+		debug("test whether pkalg/pkblob are acceptable");
+		cert = packet_get_string(&clen);
+		if (!cert || clen <= 0) {
+			error("userauth_certkey: no cert");
+			goto done;
+		}
+		key->cert = xstrdup(cert);
+		packet_check_eom();
+
+		if (PRIVSEP(user_cert_key_allowed(authctxt->pw, key))) {
+			packet_start(SSH2_MSG_USERAUTH_PK_OK);
+			packet_put_string(pkalg, alen);
+			packet_put_string(pkblob, blen);
+			packet_send();
+			packet_write_wait();
+			authctxt->postponed = 1;
+		}
+	}
+	if (authenticated != 1)
+		auth_clear_options();
+done:
+	debug2("userauth_certkey: authenticated %d pkalg %s", authenticated, pkalg);
+	if (key != NULL)
+		key_free(key);
+	xfree(pkalg);
+	xfree(pkblob);
+	return authenticated;
+}
+
+/* check whether given key is signed by certificate */
+int
+user_cert_key_allowed(struct passwd *pw, Key *key)
+{
+	int allowed = 0;
+	Key *ca_key;
+
+	temporarily_use_uid(pw);
+	ca_key = key_load_public(options.ca_key_file, NULL);
+	restore_uid();
+	allowed = cert_verify(key->cert, ca_key, key, pw->pw_name);
+	if (ca_key != NULL)
+		key_free(ca_key);
+	return allowed;
+}
+
+Authmethod method_certkey = {
+	"certkey",
+	userauth_certkey,
+	&options.certkey_authentication
+};
Received on Wed Nov 15 2006 - 13:35:42 UTC

This archive was generated by hypermail 2.4.0 : Wed May 19 2021 - 11:39:02 UTC