Re: OpenSSH Certkey (PKI) adding CAL (online verification)

From: Daniel Hartmeier <daniel_at_benzedrine.cx>
Date: Thu, 16 Nov 2006 19:01:41 +0100
On Wed, Nov 15, 2006 at 10:47:47AM -0700, Bob Beck wrote:

> 	So, My two cents, make it complete first. Making an archetecture
> for ssh that makes it easy to add trust centrally WITHOUT MAKING IT EASY
> TO REMOVE IT is irresponsible.

Thank you for the rant ;)

Here's the result. Adding a simple daemon that the OpenSSH servers can
query (over UDP port 22) to check user keys. See the first patch chunk
for details.

Is this what you had in mind?

Daniel


--- /dev/null	Thu Nov 16 18:55:22 2006
+++ README.cal	Thu Nov 16 18:54:41 2006
_at__at_ -0,0 +1,155 _at__at_
+OpenSSH CAL
+
+
+INTRODUCTION
+
+Certificate Authorization List (CAL) allows OpenSSH servers to verify the
+validity of user keys online against one or more CAL servers.
+
+Especially in larger installations with many users, once-trusted users may
+become untrusted, or user keys may become compromised. Removing such user keys
+from many machines can become a tedious task that has to be repeated
+regularly.
+
+Online verification has the advantage that the list of valid user keys (and
+therefore, by absence, the list of invalid user keys) can be maintained in one
+single place (or at least in only a few, if multiple CAL servers are used).
+
+The downside is that OpenSSH servers need to be able to contact at least one
+of the CAL servers for every user login.
+
+
+CONFIGURATION
+
+The feature is enabled through the following two options in the server
+configuration:
+
+  CertkeyAuthentication yes
+  CALHost "10.1.2.3"
+  CALHost "10.1.2.4"
+
+A CAL server maintains a list of all valid host's and user's public keys,
+in form of files, one per key, stored under /etc/ssh/cal/users/ and
+/etc/ssh/cal/hosts/ with file names equal to the key's fingerprint.
+
+Revoking a user key is done by simply removing its file on the CAL server.
+
+Conversely, every new user key must be added to the CAL server.
+
+Similarly, host keys must be created and can be revoked by file
+creation/deletion. The CAL server does not have to be restarted or signaled
+after such changes.
+
+
+PROTOCOL
+
+When a user tries to login to an OpenSSH server using a user public key, the
+OpenSSH server sends a query over UDP to the CAL server on port 22. The query
+contains the OpenSSH server's host key fingerprint and the user key
+fingerprint. It is signed by the OpenSSH server's host key.
+
+The CAL server finds the file containing the host public key based on its
+fingerprint. It reads the host public key and verifies the query's signature.
+If either of those two steps fails, the query is ignored. This ensures that
+only holders of trusted host keys can gain any information from the CAL server
+at all.
+
+Then the CAL server finds the file containing the user public key based on its
+fingerprint. If the file is found, the user key is considered valid, otherwise
+it is considered invalid.
+
+The CAL server sends a reply back to the OpenSSH server, indicating whether
+the user key is valid or invalid. The reply contains the user key fingerprint,
+the CAL public key, the CAL certificate, and a signature made by the CAL
+private key.
+
+The OpenSSH server does not need a pre-arranged copy of the CAL public key to
+verify the signature in the reply. Instead, it relies on the pre-arranged copy
+of the CA public key to verify the certificate in the reply. If the
+certificate is valid and has identity "OpenSSH CA CAL", the OpenSSH server
+trusts the certified CAL public key contained in the reply, and uses it to
+verify the signature of the reply.
+
+The OpenSSH server can be configured to use more than one CAL server, by
+specifying additional CALHost configuration options. The OpenSSH server will
+try the first CAL server in the list first, then cycle through the list in a
+round-robin fashion, until it either gets a valid answer or times out. If no
+valid answer can be obtained, the system fails closed, that is the user is not
+allowed to login.
+
+
+SECURITY IMPLICATIONS
+
+The CAL hosts and their /etc/ssh/cal directories must be protected. While a
+compromised CAL cannot grant access to OpenSSH servers for users that don't
+hold any valid keys (either certified or found in authorized_keys), a
+compromise of a CAL host can lead to revoked user keys getting accepted or
+valid user keys getting refused by OpenSSH servers.
+
+Because the OpenSSH servers configured to use CAL servers will fail closed
+when they can't reach (any) of the CAL servers, a denial of service attack
+against the CAL servers can render Certkey user authentication on the OpenSSH
+servers non-functional.
+
+When Certkey user authentication fails either because no CAL server can be
+reached or because one CAL server delivers a valid reply marking the user key
+as invalid, the user key can still be used with other authentication methods
+(publickey) to gain access (if found in authorized_keys).
+
+
+IMPLEMENTATION
+
+Queries
+
+Queries consist of a single UDP packet containing one string of ASCII text.
+The string contains values separated by semi-colons. Values must not contain
+semi-colons, but may be empty. All queries have the form
+
+  hostfp;userfp;rand1;sig
+
+'hostfp' is the host key's SSH_FP_MD5 SSH_FP_HEX fingerprint, as printed by
+ssh-keygen -l.
+
+'userfp' is the user key's fingerprint, in the same format.
+
+'rand1' is a random number, acting as a salt.
+
+'sig' is the signature (in hex) over the first three values, made using the
+host private key.
+
+Example:
+
+  f2:c7:5c:a9:48:d8:8c:82:24:d5:2a:d6:75:48:ab:3d;f2:c7:5c:a9:48:d8:8c:82:\
+  24:d5:2a:d6:75:48:ab:3d;384576238456;dbd33e932d80b5612...5a0b4759bee451
+
+Replies
+
+Replies have the same form as queries (one string, separated by semi-colons),
+but contain other fields:
+
+  decision;userfp;rand1;rand2;calkey,calcert;sig
+
+'decision' is the CAL server's decision about the user key. "VALID" if the
+user key should be accepted, "INVALID" if it should be refused.
+
+'userfp' is the user key's fingerprint, same as in the query.
+
+'rand1' is returned verbatim as supplied in the query.
+
+'rand2' has the same format and purpose as 'rand1', but is chosen by the CAL
+server.
+
+'calkey' is the CAL server's public key, in hex.
+
+'calcert' is a certificate for the CAL server's public key, signed by the CA
+for the special identity "OpenSSH CA CAL", in hex.
+
+'sig' is the signature (in hex) over the first five values, made using the CAL
+server's private key.
+
+Example:
+
+  VALID;f2:c7:5c:a9:48:d8:8c:82:24:d5:2a:d6:75:48:ab:3d;384576238456;122345932;\
+  5a0b4759bee451...932d80b5612;33e932d80b561...4759bee;e932d80b...759bee4
+
+$OpenBSD$
--- /dev/null	Thu Nov 16 18:55:45 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$
--- /dev/null	Thu Nov 16 18:56:03 2006
+++ sshcald.1	Thu Nov 16 17:38:00 2006
_at__at_ -0,0 +1,70 _at__at_
+.\" $OpenBSD$
+.\"
+.\" Copyright (c) 2006 Daniel Hartmeier.  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.
+.\" 3. The name of the author may not be used to endorse or promote products
+.\"    derived from this software without specific prior written permission.
+.\"
+.\" 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.
+.\"
+.Dd November 15, 2006
+.Dt SSHCALD 1
+.Os
+.Sh NAME
+.Nm sshcald
+.Nd Certificate Authorization List (CAL) daemon
+.Sh SYNOPSIS
+.Nm sshcald
+.Bk -words
+.Op Fl D
+.Sh DESCRIPTION
+.Nm
+listens on UDP port 22 and answers queries made by OpenSSH servers during
+Certkey user authentication.
+.Pp
+The user trying to login to the server supplies his public key as well as
+a certificate made by the certificate authority (CA).
+The server then queries
+.Nm
+to check whether the user's public key has been revoked or is still valid.
+.Pp
+The options are as follows:
+.Bl -tag -width Ds
+.It Fl D
+When this option is specified,
+.Nm
+will not detach and does not become a daemon.
+.El
+.Sh FILES
+.Bl -tag -width "/etc/ssh/cal/users/*" -compact
+.It Pa /etc/ssh/cal/cal
+CAL private key.
+.It Pa /etc/ssh/cal/cal.pub
+CAL public key.
+.It Pa /etc/ssh/cal/users/
+Location of user public keys.
+The file names must equal the key fingerprints, as printed by
+.Xr ssh-keygen -l .
+.It Pa /etc/ssh/cal/hosts/
+Location of host public keys.
+.El
+.Sh SEE ALSO
+.Xr sshd_config 5 ,
+.Xr sshd 8
--- /dev/null	Thu Nov 16 18:56:06 2006
+++ sshcald.c	Thu Nov 16 17:49:26 2006
_at__at_ -0,0 +1,296 _at__at_
+/*	$OpenBSD$ */
+
+/*
+ * Copyright (c) 2006 Daniel Hartmeier.  All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ *    - Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *    - 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 COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "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
+ * COPYRIGHT HOLDERS OR CONTRIBUTORS 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 <errno.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include <openssl/objects.h>
+
+#include "key.h"
+#include "authfile.h"
+#include "rsa.h"
+
+#define FILE_CAL_KEY	"/etc/ssh/cal/cal"
+#define	PATH_HOST_KEYS	"/etc/ssh/cal/hosts"
+#define	PATH_USER_KEYS	"/etc/ssh/cal/users"
+#define	INTBLOB_LEN	20
+
+static Key *calkey = NULL;
+static u_char calkeystr[8192] = "";
+static u_char calcertstr[8192] = "";
+
+static u_int
+hex(const u_char *bin, u_int binlen, u_char *buf, u_int bufsiz)
+{
+	u_int len = 0, i;
+
+	buf[0] = 0;
+	for (i = 0; i < binlen && len + 3 < bufsiz; ++i) {
+		snprintf(buf + len, bufsiz - len, "%2.2x", bin[i]);
+		len += 2;
+	}
+	buf[len] = 0;
+	return (len);
+}
+
+static u_int
+de_hex(const u_char *hex, u_char *buf, u_int siz)
+{
+	u_int len = 0, i;
+	u_char c;
+
+	for (i = 0; hex[i] && len <= siz; ++i) {
+		if (hex[i] >= '0' && hex[i] <= '9')
+			c = hex[i] - '0';
+		else if (hex[i] >= 'a' && hex[i] <= 'f')
+			c = hex[i] - 'a' + 10;
+		else
+			break;
+		if ((i % 2) == 0)
+			buf[len] = c << 4;
+		else
+			buf[len++] |= c;
+	}
+	return (len);
+}
+
+static void
+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;
+}
+
+static Key *
+get_key(const u_char *path, const u_char *fp)
+{
+	Key *key = NULL;
+	u_char fn[MAXPATHLEN], *fpl;
+
+	if (strlen(fp) != 47 || strchr(fp, '/')) {
+		fprintf(stderr, "get_key: invalid fingerprint '%s'\n", fp);
+		return (NULL);
+	}
+	snprintf(fn, sizeof(fn), "%s/%s", path, fp);
+	key = key_load_public(fn, NULL);
+	if (key == NULL) {
+		fprintf(stderr, "get_key: key_load_public() failed for %s\n", fn);
+		return (NULL);
+	}
+	fpl = key_fingerprint(key, SSH_FP_MD5, SSH_FP_HEX);
+	if (strcmp(fpl, fp)) {
+		fprintf(stderr, "get_key: fingerprint mismatch in %s\n", fn);
+		xfree(fpl);
+		key_free(key);
+		return (NULL);
+	}
+	xfree(fpl);
+	return (key);
+}
+
+static void
+reply(int fd, const struct sockaddr_in *sa, const u_char *answer,
+    const u_char *fpu, const u_char *ran)
+{
+	u_char pkt[8192], buf[8192], *sig;
+	u_int siglen;
+	ssize_t len;
+
+	snprintf(pkt, sizeof(pkt), "%s;%s;%s;%lu;%s;%s", answer,
+	    fpu, ran, (unsigned long)arc4random(), calkeystr, calcertstr);
+	siglen = sizeof(sig);
+	if (key_sign(calkey, &sig, &siglen, pkt, strlen(pkt)) < 0) {
+		fprintf(stderr, "reply: sign() failed\n");
+		return;
+	}
+	hex(sig, siglen, buf, sizeof(buf));
+	xfree(sig);
+	snprintf(pkt + strlen(pkt), sizeof(pkt) - strlen(pkt), ";%s", buf);
+	len = sendto(fd, pkt, strlen(pkt), 0, (const struct sockaddr *)sa, sizeof(*sa));
+	if (len < 0) {
+		fprintf(stderr, "sendto: %s\n", strerror(errno));
+		return;
+	}
+	if (len != strlen(pkt)) {
+		fprintf(stderr, "sendto: wrote %d != %d bytes\n", (int)len, strlen(pkt));
+		return;
+	}
+}
+
+static void
+message(int fd, const struct sockaddr_in *sa, const u_char *buf)
+{
+	u_char fph[512], fpu[512], ran[512], sig[1024];
+	u_char sigbuf[1024], datbuf[8192];
+	u_int siglen;
+	Key *host, *user;
+
+	printf("%s:%u %s\n", inet_ntoa(sa->sin_addr), (unsigned)ntohs(sa->sin_port), buf);
+	token(&buf, fph, sizeof(fph));
+	token(&buf, fpu, sizeof(fpu));
+	token(&buf, ran, sizeof(ran));
+	token(&buf, sig, sizeof(sig));
+	printf("  fph '%s'\n", fph);
+	printf("  fpu '%s'\n", fpu);
+	printf("  ran '%s'\n", ran);
+	printf("  sig '%s'\n", sig);
+
+	if ((host = get_key(PATH_HOST_KEYS, fph)) == NULL) {
+		fprintf(stderr, "message: couldn't load host key %s\n", fph);
+		return;
+	}
+
+	snprintf(datbuf, sizeof(datbuf), "%s;%s;%s", fph, fpu, ran);
+	siglen = de_hex(sig, sigbuf, sizeof(sigbuf));
+	if (key_verify(host, sigbuf, siglen, datbuf, strlen(datbuf)) < 0) {
+		fprintf(stderr, "message: key_verify() failed, incorrect signature\n");
+		key_free(host);
+		return;
+	}
+
+	/* signature correct, accept message */
+	if ((user = get_key(PATH_USER_KEYS, fpu)) == NULL) {
+		printf("message: couldn't load user key %s\n", fpu);
+		reply(fd, sa, "INVALID", fpu, ran);
+	} else {
+		key_free(user);
+		printf("message: user key %s is valid\n", fpu);
+		reply(fd, sa, "VALID", fpu, ran);
+	}
+
+	key_free(host);
+}
+
+static void
+usage(void)
+{
+	extern char *__progname;
+
+	fprintf(stderr, "%s [-D]\n", __progname);
+	exit(1);
+}
+
+int main(int argc, char *argv[])
+{
+	int detach = 1;
+	int ch;
+	int fd;
+	u_char buf[65536];
+	struct sockaddr_in sa;
+	socklen_t salen;
+	u_char *blob;
+	u_int len;
+	int n;
+
+	while ((ch = getopt(argc, argv, "D")) != -1) {
+		switch (ch) {
+		case 'D':
+			detach = 0;
+			break;
+		default:
+			usage();
+		}
+	}
+	if (optind < argc)
+		usage();
+
+	ERR_load_crypto_strings();
+	OpenSSL_add_all_digests();
+
+	calkey = key_load_private(FILE_CAL_KEY, "", NULL);
+	if (calkey == NULL) {
+		fprintf(stderr, "couldn't load CAL key from %s\n", FILE_CAL_KEY);
+		return (1);
+	}
+	key_to_blob(calkey, &blob, &len);
+	hex(blob, len, calkeystr, sizeof(calkeystr));
+	xfree(blob);
+	if (calkey->cert == NULL) {
+		fprintf(stderr, "CAL key %s has no certificate\n", FILE_CAL_KEY);
+		return (1);
+	}
+	hex(calkey->cert, strlen(calkey->cert), calcertstr, sizeof(calcertstr));
+
+	fd = socket(PF_INET, SOCK_DGRAM, 0);
+	if (fd < 0) {
+		fprintf(stderr, "socket: %s\n", strerror(errno));
+		return (1);
+	}
+	memset(&sa, 0, sizeof(sa));
+	sa.sin_family = AF_INET;
+	sa.sin_addr.s_addr = inet_addr("0.0.0.0");
+	sa.sin_port = htons(22);
+	if (bind(fd, (struct sockaddr *)&sa, sizeof(sa))) {
+		fprintf(stderr, "bind: %s\n", strerror(errno));
+		close(fd);
+		return (1);
+	}
+
+	if (detach && daemon(0, 0)) {
+		fprintf(stderr, "daemon: %s\n", strerror(errno));
+		return (1);
+	}
+
+	while (1) {
+		ssize_t len;
+
+		memset(&sa, 0, sizeof(sa));
+		salen = sizeof(sa);
+		len = recvfrom(fd, buf, sizeof(buf) - 1, 0, (struct sockaddr *)&sa, &salen);
+		if (len <= 0) {
+			if (len < 0 && errno != EINTR)
+				fprintf(stderr, "recvfrom: %s\n", strerror(errno));
+			continue;
+		}
+		if (salen != sizeof(sa)) {
+			fprintf(stderr, "recvfrom: salen %u != sizeof(sa) %u\n",
+			    (unsigned)salen, (unsigned)sizeof(sa));
+			continue;
+		}
+		if (buf[len - 1] == '\n')
+			buf[len - 1] = 0;
+		else
+			buf[len] = 0;
+		message(fd, &sa, buf);
+	}
+
+	close(fd);
+	return (0);
+}
--- /dev/null	Thu Nov 16 18:56:18 2006
+++ sshcald/Makefile	Thu Nov 16 17:17:54 2006
_at__at_ -0,0 +1,32 _at__at_
+#	$OpenBSD$
+
+.PATH:		${.CURDIR}/..
+
+PROG=	sshcald
+BINOWN=	root
+
+#BINMODE?=4555
+
+BINDIR=	/usr/sbin
+MAN=
+MAN=	sshcald.1
+#LINKS=	${BINDIR}/ssh ${BINDIR}/slogin
+#MLINKS=	ssh.1 slogin.1
+
+SRCS=	sshcald.c
+
+.include <bsd.own.mk> # for AFS
+
+.if (${KERBEROS5:L} == "yes")
+CFLAGS+= -DKRB5 -I${DESTDIR}/usr/include/kerberosV -DGSSAPI
+.endif # KERBEROS5
+
+.include <bsd.prog.mk>
+
+.if (${KERBEROS5:L} == "yes")
+DPADD+=  ${LIBGSSAPI} ${LIBKRB5}
+LDADD+=  -lgssapi -lkrb5
+.endif # KERBEROS5
+
+DPADD+=	${LIBCRYPTO} ${LIBZ} ${LIBDES}
+LDADD+=	-lcrypto -lz -ldes
--- /dev/null	Thu Nov 16 18:56:26 2006
+++ auth2-certkey.c	Thu Nov 16 17:18:51 2006
_at__at_ -0,0 +1,319 _at__at_
+/* $OpenBSD$ */
+/*
+ * Copyright (c) 2000 Markus Friedl.  All rights reserved.
+ * Copyright (c) 2006 Daniel Hartmeier.  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 <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include <errno.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 "authfile.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;
+}
+
+static int
+user_cert_key_cal_approved(Key *key, Key *ca_key)
+{
+	const char *caladdr = "127.0.0.1";
+	int approved = 0;
+	Key *host_key;
+	int fd, i, j, ret;
+	struct sockaddr_in sa;
+	socklen_t salen;
+	u_char pkt[65536] = "TEST";
+	u_char fph[128], fpu[128], rand1[128];
+	u_char *fp, *sig;
+	u_int siglen;
+	ssize_t len;
+	time_t timeout;
+
+	if ((host_key = get_hostkey_by_type(KEY_RSA)) == NULL) {
+		debug2("user_cert_key_cal_approved: no host key available");
+		return 0;
+	}
+	if ((fp = key_fingerprint(host_key, SSH_FP_MD5, SSH_FP_HEX)) == NULL)
+		return 0;
+	strlcpy(fph, fp, sizeof(fph));
+	xfree(fp);
+	if ((fp = key_fingerprint(key, SSH_FP_MD5, SSH_FP_HEX)) == NULL)
+		return 0;
+	strlcpy(fpu, fp, sizeof(fpu));
+	xfree(fp);
+	snprintf(rand1, sizeof(rand1), "%lu", (unsigned long)arc4random());
+	snprintf(pkt, sizeof(pkt), "%s;%s;%s", fph, fpu, rand1);
+	if (key_sign(host_key, &sig, &siglen, pkt, strlen(pkt)) < 0) {
+		debug2("user_cert_key_cal_approved: key_sign() failed");
+		return 0;
+	}
+	strlcat(pkt, ";", sizeof(pkt));
+	for (i = 0; i < siglen; ++i)
+		snprintf(pkt + strlen(pkt), sizeof(pkt) - strlen(pkt), "%2.2x", sig[i]);
+	fd = socket(PF_INET, SOCK_DGRAM, 0);
+	if (fd < 0) {
+		debug2("user_cert_key_cal_approved: socket: %s", strerror(errno));
+		return 0;
+	}
+
+	timeout = time(NULL) + 3;
+	for (i = 0; time(NULL) < timeout; i = (i + 1) % options.num_cal_hosts) {
+
+		debug3("user_cert_key_cal_approved: trying host %d '%s'",
+		    i, options.cal_hosts[i]);
+
+		memset(&sa, 0, sizeof(sa));
+		sa.sin_family = AF_INET;
+		sa.sin_addr.s_addr = inet_addr(options.cal_hosts[i]);
+		sa.sin_port = htons(22);
+		len = sendto(fd, pkt, strlen(pkt), 0, (const struct sockaddr *)&sa, sizeof(sa));
+		if (len < 0) {
+			debug2("user_cert_key_cal_approved: sendto: %s", strerror(errno));
+			continue;
+		}
+
+		/* wait at most 1s and read at most 4 messages from this host */
+		for (j = 0; j < 4; ++j) {
+			fd_set fds;
+			struct timeval tv;
+			int r;
+			ssize_t len;
+
+			FD_ZERO(&fds);
+			FD_SET(fd, &fds);
+			tv.tv_sec = 0;
+			tv.tv_usec = 250000;
+			r = select(fd + 1, &fds, NULL, NULL, &tv);
+			if (r < 0) {
+				if (errno == EINTR)
+					continue;
+				debug2("user_cert_key_cal_approved: select: %s",
+				    strerror(errno));
+				break;
+			}
+			if (r == 0 || !FD_ISSET(fd, &fds))
+				continue;
+			salen = sizeof(sa);
+			len = recvfrom(fd, pkt, sizeof(pkt) - 1, 0, (struct sockaddr *)&sa, &salen);
+			if (len < 0) {
+				if (errno == EINTR)
+					continue;
+				debug2("user_cert_key_cal_approved: recvfrom: %s\n",
+				    strerror(errno));
+				break;
+			}
+			if (len == 0 || salen != sizeof(sa))
+				continue;
+			pkt[len] = 0;
+			if (strcmp(inet_ntoa(sa.sin_addr), options.cal_hosts[i]) ||
+			    ntohs(sa.sin_port) != 22) {
+				debug2("user_cert_key_cal_approved: "
+				    "answer from unexpected source %s:%u\n",
+				    inet_ntoa(sa.sin_addr), (unsigned)ntohs(sa.sin_port));
+				continue;
+			}
+			if (!(ret = cal_answer_verify(ca_key, pkt, fpu, rand1))) {
+				debug2("user_cert_key_cal_approved: cal_answer_verify() failed\n");
+				continue;
+			}
+			/* we have a trusted answer, but it may not be positive */
+			if (ret > 0)
+				approved = 1;
+			goto done;
+		}
+	}
+done:
+	close(fd);
+	return approved;
+}
+
+/* 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 (options.num_cal_hosts > 0)
+		allowed = allowed && user_cert_key_cal_approved(key, ca_key);
+	if (ca_key != NULL)
+		key_free(ca_key);
+	return allowed;
+}
+
+
+Authmethod method_certkey = {
+	"certkey",
+	userauth_certkey,
+	&options.certkey_authentication
+};
Index: Makefile
===================================================================
RCS file: /cvs/src/usr.bin/ssh/Makefile,v
retrieving revision 1.12
diff -u -r1.12 Makefile
--- Makefile	1 Dec 2003 15:47:20 -0000	1.12
+++ Makefile	16 Nov 2006 17:56:40 -0000
_at__at_ -3,7 +3,7 _at__at_
 .include <bsd.own.mk>
 
 SUBDIR=	lib ssh sshd ssh-add ssh-keygen ssh-agent scp sftp-server \
-	ssh-keysign ssh-keyscan sftp scard
+	ssh-keysign ssh-keyscan sftp scard sshcald
 
 distribution:
 	${INSTALL} -C -o root -g wheel -m 0644 ${.CURDIR}/ssh_config \
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	16 Nov 2006 17:56:40 -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	16 Nov 2006 17:56:40 -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	16 Nov 2006 17:56:40 -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	16 Nov 2006 17:56:41 -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	16 Nov 2006 17:56:41 -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	16 Nov 2006 17:56:41 -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	16 Nov 2006 17:56:41 -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	16 Nov 2006 17:56:41 -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,167 _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;
+}
+
+static u_int
+de_hex(const u_char *hex, u_char *buf, u_int siz)
+{
+	u_int len = 0, i;
+	u_char c;
+
+	for (i = 0; hex[i] && len <= siz; ++i) {
+		if (hex[i] >= '0' && hex[i] <= '9')
+			c = hex[i] - '0';
+		else if (hex[i] >= 'a' && hex[i] <= 'f')
+			c = hex[i] - 'a' + 10;
+		else
+			break;
+		if ((i % 2) == 0)
+			buf[len] = c << 4;
+		else
+			buf[len++] |= c;
+	}
+	return (len);
+}
+
+/* check whether CAL anwser packet is valid and signature correct */
+int
+cal_answer_verify(const Key *ca_key, const u_char *pkt, const u_char *fpu,
+    const u_char *rand1)
+{
+	u_char cal_reply[64], cal_fpu[128], cal_rand1[128], cal_rand2[128];
+	u_char cal_pubkey_hex[8192], cal_cert_hex[8192], cal_sig_hex[8192];
+	u_char cal_pubkey[8192], cal_cert[8192], cal_sig[8192];
+	u_char dat[8192];
+	u_int cal_pubkey_len, cal_cert_len, cal_sig_len;
+	Key *cal_key;
+
+	cert_token(&pkt, cal_reply, sizeof(cal_reply));
+	cert_token(&pkt, cal_fpu, sizeof(cal_fpu));
+	cert_token(&pkt, cal_rand1, sizeof(cal_rand1));
+	cert_token(&pkt, cal_rand2, sizeof(cal_rand2));
+	cert_token(&pkt, cal_pubkey_hex, sizeof(cal_pubkey_hex));
+	cert_token(&pkt, cal_cert_hex, sizeof(cal_cert_hex));
+	cert_token(&pkt, cal_sig_hex, sizeof(cal_sig_hex));
+	cal_pubkey_len = de_hex(cal_pubkey_hex, cal_pubkey, sizeof(cal_pubkey));
+	cal_cert_len = de_hex(cal_cert_hex, cal_cert, sizeof(cal_cert) - 1);
+	cal_cert[cal_cert_len] = 0;
+	cal_sig_len = de_hex(cal_sig_hex, cal_sig, sizeof(cal_sig));
+
+	cal_key = key_from_blob(cal_pubkey, cal_pubkey_len);
+	if (cal_key == NULL) {
+		debug2("cal_answer_verify: key_from_blob() failed");
+		return 0;
+	}
+
+	if (!cert_verify(cal_cert, ca_key, cal_key, "OpenSSH CA CAL")) {
+		debug2("cal_answer_verify: certificate not valid");
+		key_free(cal_key);
+		return 0;
+	}
+
+	snprintf(dat, sizeof(dat), "%s;%s;%s;%s;%s;%s", cal_reply, cal_fpu,
+	    cal_rand1, cal_rand2, cal_pubkey_hex, cal_cert_hex);
+	if (key_verify(cal_key, cal_sig, cal_sig_len, dat, strlen(dat)) < 0) {
+		debug2("cal_answer_verify: signature is not valid");
+		key_free(cal_key);
+		return 0;
+	}
+	key_free(cal_key);
+
+	if (strcmp(cal_fpu, fpu)) {
+		debug2("cal_answer_verify: CAL fpu mismatch");
+		return 0;
+	}
+	if (strcmp(cal_rand1, rand1)) {
+		debug2("cal_answer_verify: CAL rand1 mismatch");
+		return 0;
+	}
+	if (strcmp(cal_reply, "VALID"))
+		return -1;
+	return 1;
+}
+
+/* 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 = de_hex(ca_sig, sigbuf, sizeof(sigbuf));
+
+	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	16 Nov 2006 17:56:41 -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,9 _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 *);
+int	 cal_answer_verify(const Key *, const u_char *, const u_char *,
+	    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	16 Nov 2006 17:56:42 -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	16 Nov 2006 17:56:42 -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	16 Nov 2006 17:56:42 -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	16 Nov 2006 17:56:42 -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	16 Nov 2006 17:56:42 -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	16 Nov 2006 17:56:43 -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	16 Nov 2006 17:56:43 -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	16 Nov 2006 17:56:43 -0000
_at__at_ -56,6 +56,8 _at__at_
 	options->listen_addrs = NULL;
 	options->address_family = -1;
 	options->num_host_key_files = 0;
+	options->num_cal_hosts = 0;
+	options->ca_key_file = NULL;
 	options->pid_file = NULL;
 	options->server_key_bits = -1;
 	options->login_grace_time = -1;
_at__at_ -77,6 +79,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 +137,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 +185,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_ -260,8 +267,9 _at__at_
 	sPermitUserEnvironment, sUseLogin, sAllowTcpForwarding, sCompression,
 	sAllowUsers, sDenyUsers, sAllowGroups, sDenyGroups,
 	sIgnoreUserKnownHosts, sCiphers, sMacs, sProtocol, sPidFile,
-	sGatewayPorts, sPubkeyAuthentication, sXAuthLocation, sSubsystem,
-	sMaxStartups, sMaxAuthTries,
+	sCAKeyFile, sCALHost,
+	sGatewayPorts, sCertkeyAuthentication, sPubkeyAuthentication, sXAuthLocation,
+	sSubsystem, sMaxStartups, sMaxAuthTries,
 	sBanner, sUseDNS, sHostbasedAuthentication,
 	sHostbasedUsesNameFromPacketOnly, sClientAliveInterval,
 	sClientAliveCountMax, sAuthorizedKeysFile, sAuthorizedKeysFile2,
_at__at_ -282,6 +290,8 _at__at_
 	u_int flags;
 } keywords[] = {
 	{ "port", sPort, SSHCFG_GLOBAL },
+	{ "cakeyfile", sCAKeyFile, SSHCFG_GLOBAL },
+	{ "calhost", sCALHost, SSHCFG_GLOBAL },
 	{ "hostkey", sHostKeyFile, SSHCFG_GLOBAL },
 	{ "hostdsakey", sHostKeyFile, SSHCFG_GLOBAL },		/* alias */
 	{ "pidfile", sPidFile, SSHCFG_GLOBAL },
_at__at_ -296,6 +306,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 +749,28 _at__at_
 		}
 		break;
 
+	case sCAKeyFile:
+		charptr = &options->ca_key_file;
+		goto parse_filename;
+
+	case sCALHost:
+		intptr = &options->num_cal_hosts;
+		if (*intptr >= MAX_CAL_HOSTS)
+			fatal("%s line %d: too many CAL hosts specified (max %d).",
+			    filename, linenum, MAX_CAL_HOSTS);
+		charptr = &options->cal_hosts[*intptr];
+		arg = strdelim(&cp);
+		if (!arg || *arg == '\0')
+			fatal("%s line %d: missing value.",
+			    filename, linenum);
+		if (*activep && *charptr == NULL) {
+			*charptr = xstrdup(arg);
+			/* increase optional counter */
+			if (intptr != NULL)
+				*intptr = *intptr + 1;
+		}
+		break;
+
 	case sPidFile:
 		charptr = &options->pid_file;
 		goto parse_filename;
_at__at_ -803,6 +836,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	16 Nov 2006 17:56:44 -0000
_at__at_ -26,6 +26,7 _at__at_
 #define MAX_HOSTKEYS		256	/* Max # hostkeys. */
 #define MAX_ACCEPT_ENV		256	/* Max # of env vars. */
 #define MAX_MATCH_GROUPS	256	/* Max # of groups for Match. */
+#define	MAX_CAL_HOSTS		 16	/* Max # of CAL hosts. */
 
 /* permit_root_login */
 #define	PERMIT_NOT_SET		-1
_at__at_ -43,6 +44,9 _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. */
+	int     num_cal_hosts;		/* Number of CAL host IP addresses */
+	char   *cal_hosts[MAX_CAL_HOSTS];	/* CAL host IP addresses */
 	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 +79,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	16 Nov 2006 17:56:44 -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	16 Nov 2006 17:56:45 -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	16 Nov 2006 17:56:45 -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	16 Nov 2006 17:56:46 -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	16 Nov 2006 17:56:46 -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	16 Nov 2006 17:56:47 -0000
_at__at_ -167,6 +167,19 _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 CALHost
+Adds a host IP address to the list of CAL hosts.
+If the list is empty, which is the default, Certkey does not verify
+certificates online against CAL hosts.
+.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	16 Nov 2006 17:56:47 -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
 
Received on Thu Nov 16 2006 - 17:01:51 UTC

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