OpenSSH Certkey (PKI)
Daniel Hartmeier
daniel at benzedrine.cx
Thu Nov 16 01:28:20 EST 2006
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
@@ -0,0 +1,176 @@
+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
@@ -115,6 +115,7 @@
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
@@ -55,6 +55,7 @@
/* methods */
extern Authmethod method_none;
+extern Authmethod method_certkey;
extern Authmethod method_pubkey;
extern Authmethod method_passwd;
extern Authmethod method_kbdint;
@@ -65,6 +66,7 @@
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
@@ -302,6 +302,43 @@
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)
@@ -315,6 +352,8 @@
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;
@@ -604,6 +643,8 @@
/* closes fd */
prv = key_load_private_rsa1(fd, filename, passphrase, NULL);
}
+ if (prv != NULL)
+ key_try_load_cert(prv, filename);
return prv;
}
@@ -634,6 +675,7 @@
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
@@ -312,6 +312,9 @@
} 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
@@ -32,6 +32,7 @@
#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
@@ -62,6 +63,7 @@
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
@@ -124,8 +124,6 @@
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)
@@ -141,7 +139,20 @@
/* 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
@@ -183,6 +183,13 @@
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
@@ -57,6 +57,7 @@
k->type = type;
k->dsa = NULL;
k->rsa = NULL;
+ k->cert = NULL;
switch (k->type) {
case KEY_RSA1:
case KEY_RSA:
@@ -145,6 +146,9 @@
fatal("key_free: bad key type %d", k->type);
break;
}
+ if (k->cert != NULL)
+ xfree(k->cert);
+ k->cert = NULL;
xfree(k);
}
@@ -833,6 +837,7 @@
pk->flags = k->flags;
pk->dsa = NULL;
pk->rsa = NULL;
+ pk->cert = k->cert ? xstrdup(k->cert) : NULL;
switch (k->type) {
case KEY_RSA1:
@@ -862,4 +867,100 @@
}
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
@@ -53,6 +53,7 @@
int flags;
RSA *rsa;
DSA *dsa;
+ u_char *cert;
};
Key *key_new(int);
@@ -83,5 +84,7 @@
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
@@ -797,6 +797,17 @@
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);
@@ -859,7 +870,7 @@
}
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;
@@ -900,7 +911,7 @@
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))
@@ -992,8 +1003,11 @@
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,
@@ -1015,7 +1029,12 @@
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
@@ -295,6 +295,12 @@
}
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));
@@ -351,6 +357,8 @@
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
@@ -31,7 +31,7 @@
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;
@@ -46,6 +46,7 @@
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
@@ -24,6 +24,7 @@
* 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
@@ -36,6 +36,7 @@
#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
@@ -117,7 +117,8 @@
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,
@@ -148,6 +149,8 @@
{ "kbdinteractiveauthentication", oKbdInteractiveAuthentication },
{ "kbdinteractivedevices", oKbdInteractiveDevices },
{ "rsaauthentication", oRSAAuthentication },
+ { "certkeyauthentication", oCertkeyAuthentication },
+ { "cakeyfile", oCAKeyFile },
{ "pubkeyauthentication", oPubkeyAuthentication },
{ "dsaauthentication", oPubkeyAuthentication }, /* alias */
{ "rhostsrsaauthentication", oRhostsRSAAuthentication },
@@ -412,6 +415,10 @@
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;
@@ -560,6 +567,10 @@
*charptr = xstrdup(arg);
break;
+ case oCAKeyFile:
+ charptr = &options->ca_key_file;
+ goto parse_string;
+
case oGlobalKnownHostsFile:
charptr = &options->system_hostfile;
goto parse_string;
@@ -1002,6 +1013,8 @@
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;
@@ -1088,6 +1101,10 @@
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
@@ -39,6 +39,8 @@
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
@@ -56,6 +56,7 @@
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;
@@ -77,6 +78,7 @@
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;
@@ -134,6 +136,8 @@
_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)
@@ -180,6 +184,8 @@
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)
@@ -259,9 +265,9 @@
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,
@@ -282,6 +288,7 @@
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 },
@@ -296,6 +303,7 @@
{ "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
@@ -738,6 +746,10 @@
}
break;
+ case sCAKeyFile:
+ charptr = &options->ca_key_file;
+ goto parse_filename;
+
case sPidFile:
charptr = &options->pid_file;
goto parse_filename;
@@ -803,6 +815,10 @@
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
@@ -43,6 +43,7 @@
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 */
@@ -75,6 +76,7 @@
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
@@ -94,6 +94,8 @@
int print_public = 0;
int print_generic = 0;
+int sign_host_key = 0;
+
char *key_type_name = NULL;
/* argv0 */
@@ -494,6 +496,142 @@
#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;
@@ -1026,6 +1164,7 @@
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
@@ -1079,7 +1218,7 @@
}
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);
@@ -1156,6 +1295,9 @@
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;
@@ -1221,6 +1363,8 @@
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
@@ -145,6 +145,15 @@
.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
@@ -21,6 +21,7 @@
#include <netinet/in.h>
+#include <openssl/objects.h>
#include <ctype.h>
#include <errno.h>
#include <netdb.h>
@@ -48,6 +49,7 @@
#include "misc.h"
#include "dns.h"
#include "version.h"
+#include "authfile.h"
char *client_version_string = NULL;
char *server_version_string = NULL;
@@ -884,6 +886,19 @@
{
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
@@ -133,6 +133,7 @@
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;
@@ -168,6 +169,7 @@
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);
@@ -206,6 +208,7 @@
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 *);
@@ -224,6 +227,7 @@
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 *);
@@ -243,6 +247,10 @@
userauth_hostbased,
&options.hostbased_authentication,
NULL},
+ {"certkey",
+ userauth_certkey,
+ &options.certkey_authentication,
+ NULL},
{"publickey",
userauth_pubkey,
&options.pubkey_authentication,
@@ -472,7 +480,11 @@
*/
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;
}
}
@@ -851,6 +863,93 @@
}
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;
@@ -936,6 +1035,31 @@
}
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;
@@ -1095,6 +1219,42 @@
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
@@ -1999,6 +1999,7 @@
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
@@ -167,6 +167,15 @@
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
@@ -14,7 +14,7 @@
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
@@ -0,0 +1,196 @@
+/* $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
+};
More information about the openssh-unix-dev
mailing list