upstream commit

add ssh_config CertificateFile option to explicitly list
 a certificate; patch from Meghana Bhat on bz#2436; ok markus@

Upstream-ID: 58648ec53c510b41c1f46d8fe293aadc87229ab8
This commit is contained in:
djm@openbsd.org 2015-09-24 06:15:11 +00:00 committed by Damien Miller
parent e3cbb06ade
commit 4e44a79a07
7 changed files with 226 additions and 25 deletions

View File

@ -1,4 +1,4 @@
/* $OpenBSD: readconf.c,v 1.240 2015/08/21 23:53:08 djm Exp $ */ /* $OpenBSD: readconf.c,v 1.241 2015/09/24 06:15:11 djm Exp $ */
/* /*
* Author: Tatu Ylonen <ylo@cs.hut.fi> * Author: Tatu Ylonen <ylo@cs.hut.fi>
* Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
@ -135,6 +135,7 @@ typedef enum {
oPasswordAuthentication, oRSAAuthentication, oPasswordAuthentication, oRSAAuthentication,
oChallengeResponseAuthentication, oXAuthLocation, oChallengeResponseAuthentication, oXAuthLocation,
oIdentityFile, oHostName, oPort, oCipher, oRemoteForward, oLocalForward, oIdentityFile, oHostName, oPort, oCipher, oRemoteForward, oLocalForward,
oCertificateFile,
oUser, oEscapeChar, oRhostsRSAAuthentication, oProxyCommand, oUser, oEscapeChar, oRhostsRSAAuthentication, oProxyCommand,
oGlobalKnownHostsFile, oUserKnownHostsFile, oConnectionAttempts, oGlobalKnownHostsFile, oUserKnownHostsFile, oConnectionAttempts,
oBatchMode, oCheckHostIP, oStrictHostKeyChecking, oCompression, oBatchMode, oCheckHostIP, oStrictHostKeyChecking, oCompression,
@ -202,6 +203,7 @@ static struct {
{ "identityfile", oIdentityFile }, { "identityfile", oIdentityFile },
{ "identityfile2", oIdentityFile }, /* obsolete */ { "identityfile2", oIdentityFile }, /* obsolete */
{ "identitiesonly", oIdentitiesOnly }, { "identitiesonly", oIdentitiesOnly },
{ "certificatefile", oCertificateFile },
{ "hostname", oHostName }, { "hostname", oHostName },
{ "hostkeyalias", oHostKeyAlias }, { "hostkeyalias", oHostKeyAlias },
{ "proxycommand", oProxyCommand }, { "proxycommand", oProxyCommand },
@ -365,6 +367,30 @@ clear_forwardings(Options *options)
options->tun_open = SSH_TUNMODE_NO; options->tun_open = SSH_TUNMODE_NO;
} }
void
add_certificate_file(Options *options, const char *path, int userprovided)
{
int i;
if (options->num_certificate_files >= SSH_MAX_CERTIFICATE_FILES)
fatal("Too many certificate files specified (max %d)",
SSH_MAX_CERTIFICATE_FILES);
/* Avoid registering duplicates */
for (i = 0; i < options->num_certificate_files; i++) {
if (options->certificate_file_userprovided[i] == userprovided &&
strcmp(options->certificate_files[i], path) == 0) {
debug2("%s: ignoring duplicate key %s", __func__, path);
return;
}
}
options->certificate_file_userprovided[options->num_certificate_files] =
userprovided;
options->certificate_files[options->num_certificate_files++] =
xstrdup(path);
}
void void
add_identity_file(Options *options, const char *dir, const char *filename, add_identity_file(Options *options, const char *dir, const char *filename,
int userprovided) int userprovided)
@ -981,6 +1007,24 @@ parse_time:
} }
break; break;
case oCertificateFile:
arg = strdelim(&s);
if (!arg || *arg == '\0')
fatal("%.200s line %d: Missing argument.",
filename, linenum);
if (*activep) {
intptr = &options->num_certificate_files;
if (*intptr >= SSH_MAX_CERTIFICATE_FILES) {
fatal("%.200s line %d: Too many certificate "
"files specified (max %d).",
filename, linenum,
SSH_MAX_CERTIFICATE_FILES);
}
add_certificate_file(options, arg,
flags & SSHCONF_USERCONF);
}
break;
case oXAuthLocation: case oXAuthLocation:
charptr=&options->xauth_location; charptr=&options->xauth_location;
goto parse_string; goto parse_string;
@ -1625,6 +1669,7 @@ initialize_options(Options * options)
options->hostkeyalgorithms = NULL; options->hostkeyalgorithms = NULL;
options->protocol = SSH_PROTO_UNKNOWN; options->protocol = SSH_PROTO_UNKNOWN;
options->num_identity_files = 0; options->num_identity_files = 0;
options->num_certificate_files = 0;
options->hostname = NULL; options->hostname = NULL;
options->host_key_alias = NULL; options->host_key_alias = NULL;
options->proxy_command = NULL; options->proxy_command = NULL;

View File

@ -1,4 +1,4 @@
/* $OpenBSD: readconf.h,v 1.110 2015/07/10 06:21:53 markus Exp $ */ /* $OpenBSD: readconf.h,v 1.111 2015/09/24 06:15:11 djm Exp $ */
/* /*
* Author: Tatu Ylonen <ylo@cs.hut.fi> * Author: Tatu Ylonen <ylo@cs.hut.fi>
@ -95,6 +95,11 @@ typedef struct {
int identity_file_userprovided[SSH_MAX_IDENTITY_FILES]; int identity_file_userprovided[SSH_MAX_IDENTITY_FILES];
struct sshkey *identity_keys[SSH_MAX_IDENTITY_FILES]; struct sshkey *identity_keys[SSH_MAX_IDENTITY_FILES];
int num_certificate_files; /* Number of extra certificates for ssh. */
char *certificate_files[SSH_MAX_CERTIFICATE_FILES];
int certificate_file_userprovided[SSH_MAX_CERTIFICATE_FILES];
struct sshkey *certificates[SSH_MAX_CERTIFICATE_FILES];
/* Local TCP/IP forward requests. */ /* Local TCP/IP forward requests. */
int num_local_forwards; int num_local_forwards;
struct Forward *local_forwards; struct Forward *local_forwards;
@ -194,5 +199,6 @@ void dump_client_config(Options *o, const char *host);
void add_local_forward(Options *, const struct Forward *); void add_local_forward(Options *, const struct Forward *);
void add_remote_forward(Options *, const struct Forward *); void add_remote_forward(Options *, const struct Forward *);
void add_identity_file(Options *, const char *, const char *, int); void add_identity_file(Options *, const char *, const char *, int);
void add_certificate_file(Options *, const char *, int);
#endif /* READCONF_H */ #endif /* READCONF_H */

8
ssh.1
View File

@ -33,8 +33,8 @@
.\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF .\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
.\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. .\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
.\" .\"
.\" $OpenBSD: ssh.1,v 1.362 2015/09/11 03:42:32 djm Exp $ .\" $OpenBSD: ssh.1,v 1.363 2015/09/24 06:15:11 djm Exp $
.Dd $Mdocdate: September 11 2015 $ .Dd $Mdocdate: September 24 2015 $
.Dt SSH 1 .Dt SSH 1
.Os .Os
.Sh NAME .Sh NAME
@ -304,6 +304,9 @@ It is possible to have multiple
.Fl i .Fl i
options (and multiple identities specified in options (and multiple identities specified in
configuration files). configuration files).
If no certificates have been explicitly specified by
.Cm CertificateFile
directive,
.Nm .Nm
will also try to load certificate information from the filename obtained will also try to load certificate information from the filename obtained
by appending by appending
@ -468,6 +471,7 @@ For full details of the options listed below, and their possible values, see
.It CanonicalizeHostname .It CanonicalizeHostname
.It CanonicalizeMaxDots .It CanonicalizeMaxDots
.It CanonicalizePermittedCNAMEs .It CanonicalizePermittedCNAMEs
.It CertificateFile
.It ChallengeResponseAuthentication .It ChallengeResponseAuthentication
.It CheckHostIP .It CheckHostIP
.It Cipher .It Cipher

65
ssh.c
View File

@ -1,4 +1,4 @@
/* $OpenBSD: ssh.c,v 1.425 2015/09/11 06:55:46 jmc Exp $ */ /* $OpenBSD: ssh.c,v 1.426 2015/09/24 06:15:11 djm Exp $ */
/* /*
* Author: Tatu Ylonen <ylo@cs.hut.fi> * Author: Tatu Ylonen <ylo@cs.hut.fi>
* Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
@ -1354,6 +1354,10 @@ main(int ac, char **av)
options.identity_keys[i] = NULL; options.identity_keys[i] = NULL;
} }
} }
for (i = 0; i < options.num_certificate_files; i++) {
free(options.certificate_files[i]);
options.certificate_files[i] = NULL;
}
exit_status = compat20 ? ssh_session2() : ssh_session(); exit_status = compat20 ? ssh_session2() : ssh_session();
packet_close(); packet_close();
@ -1940,25 +1944,30 @@ ssh_session2(void)
options.escape_char : SSH_ESCAPECHAR_NONE, id); options.escape_char : SSH_ESCAPECHAR_NONE, id);
} }
/* Loads all IdentityFile and CertificateFile keys */
static void static void
load_public_identity_files(void) load_public_identity_files(void)
{ {
char *filename, *cp, thishost[NI_MAXHOST]; char *filename, *cp, thishost[NI_MAXHOST];
char *pwdir = NULL, *pwname = NULL; char *pwdir = NULL, *pwname = NULL;
int i = 0;
Key *public; Key *public;
struct passwd *pw; struct passwd *pw;
u_int n_ids; int i;
u_int n_ids, n_certs;
char *identity_files[SSH_MAX_IDENTITY_FILES]; char *identity_files[SSH_MAX_IDENTITY_FILES];
Key *identity_keys[SSH_MAX_IDENTITY_FILES]; Key *identity_keys[SSH_MAX_IDENTITY_FILES];
char *certificate_files[SSH_MAX_CERTIFICATE_FILES];
struct sshkey *certificates[SSH_MAX_CERTIFICATE_FILES];
#ifdef ENABLE_PKCS11 #ifdef ENABLE_PKCS11
Key **keys; Key **keys;
int nkeys; int nkeys;
#endif /* PKCS11 */ #endif /* PKCS11 */
n_ids = 0; n_ids = n_certs = 0;
memset(identity_files, 0, sizeof(identity_files)); memset(identity_files, 0, sizeof(identity_files));
memset(identity_keys, 0, sizeof(identity_keys)); memset(identity_keys, 0, sizeof(identity_keys));
memset(certificate_files, 0, sizeof(certificate_files));
memset(certificates, 0, sizeof(certificates));
#ifdef ENABLE_PKCS11 #ifdef ENABLE_PKCS11
if (options.pkcs11_provider != NULL && if (options.pkcs11_provider != NULL &&
@ -1990,6 +1999,7 @@ load_public_identity_files(void)
if (n_ids >= SSH_MAX_IDENTITY_FILES || if (n_ids >= SSH_MAX_IDENTITY_FILES ||
strcasecmp(options.identity_files[i], "none") == 0) { strcasecmp(options.identity_files[i], "none") == 0) {
free(options.identity_files[i]); free(options.identity_files[i]);
options.identity_files[i] = NULL;
continue; continue;
} }
cp = tilde_expand_filename(options.identity_files[i], cp = tilde_expand_filename(options.identity_files[i],
@ -2008,7 +2018,12 @@ load_public_identity_files(void)
if (++n_ids >= SSH_MAX_IDENTITY_FILES) if (++n_ids >= SSH_MAX_IDENTITY_FILES)
continue; continue;
/* Try to add the certificate variant too */ /*
* If no certificates have been explicitly listed then try
* to add the default certificate variant too.
*/
if (options.num_certificate_files != 0)
continue;
xasprintf(&cp, "%s-cert", filename); xasprintf(&cp, "%s-cert", filename);
public = key_load_public(cp, NULL); public = key_load_public(cp, NULL);
debug("identity file %s type %d", cp, debug("identity file %s type %d", cp,
@ -2025,14 +2040,50 @@ load_public_identity_files(void)
continue; continue;
} }
identity_keys[n_ids] = public; identity_keys[n_ids] = public;
/* point to the original path, most likely the private key */ identity_files[n_ids] = cp;
identity_files[n_ids] = xstrdup(filename);
n_ids++; n_ids++;
} }
if (options.num_certificate_files > SSH_MAX_CERTIFICATE_FILES)
fatal("%s: too many certificates", __func__);
for (i = 0; i < options.num_certificate_files; i++) {
cp = tilde_expand_filename(options.certificate_files[i],
original_real_uid);
filename = percent_expand(cp, "d", pwdir,
"u", pwname, "l", thishost, "h", host,
"r", options.user, (char *)NULL);
free(cp);
public = key_load_public(filename, NULL);
debug("certificate file %s type %d", filename,
public ? public->type : -1);
free(options.certificate_files[i]);
options.certificate_files[i] = NULL;
if (public == NULL) {
free(filename);
continue;
}
if (!key_is_cert(public)) {
debug("%s: key %s type %s is not a certificate",
__func__, filename, key_type(public));
key_free(public);
free(filename);
continue;
}
certificate_files[n_certs] = filename;
certificates[n_certs] = public;
++n_certs;
}
options.num_identity_files = n_ids; options.num_identity_files = n_ids;
memcpy(options.identity_files, identity_files, sizeof(identity_files)); memcpy(options.identity_files, identity_files, sizeof(identity_files));
memcpy(options.identity_keys, identity_keys, sizeof(identity_keys)); memcpy(options.identity_keys, identity_keys, sizeof(identity_keys));
options.num_certificate_files = n_certs;
memcpy(options.certificate_files,
certificate_files, sizeof(certificate_files));
memcpy(options.certificates, certificates, sizeof(certificates));
explicit_bzero(pwname, strlen(pwname)); explicit_bzero(pwname, strlen(pwname));
free(pwname); free(pwname);
explicit_bzero(pwdir, strlen(pwdir)); explicit_bzero(pwdir, strlen(pwdir));

8
ssh.h
View File

@ -1,4 +1,4 @@
/* $OpenBSD: ssh.h,v 1.81 2015/08/04 05:23:06 djm Exp $ */ /* $OpenBSD: ssh.h,v 1.82 2015/09/24 06:15:11 djm Exp $ */
/* /*
* Author: Tatu Ylonen <ylo@cs.hut.fi> * Author: Tatu Ylonen <ylo@cs.hut.fi>
@ -18,6 +18,12 @@
/* Default port number. */ /* Default port number. */
#define SSH_DEFAULT_PORT 22 #define SSH_DEFAULT_PORT 22
/*
* Maximum number of certificate files that can be specified
* in configuration files or on the command line.
*/
#define SSH_MAX_CERTIFICATE_FILES 100
/* /*
* Maximum number of RSA authentication identity files that can be specified * Maximum number of RSA authentication identity files that can be specified
* in configuration files or on the command line. * in configuration files or on the command line.

View File

@ -33,8 +33,8 @@
.\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF .\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
.\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. .\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
.\" .\"
.\" $OpenBSD: ssh_config.5,v 1.220 2015/09/22 08:33:23 sobrado Exp $ .\" $OpenBSD: ssh_config.5,v 1.221 2015/09/24 06:15:11 djm Exp $
.Dd $Mdocdate: September 22 2015 $ .Dd $Mdocdate: September 24 2015 $
.Dt SSH_CONFIG 5 .Dt SSH_CONFIG 5
.Os .Os
.Sh NAME .Sh NAME
@ -325,6 +325,41 @@ to be canonicalized to names in the
or or
.Dq *.c.example.com .Dq *.c.example.com
domains. domains.
.It Cm CertificateFile
Specifies a file from which the user's certificate is read.
A corresponding private key must be provided separately in order
to use this certificate either
from an
.Cm IdentityFile
directive or
.Fl i
flag to
.Xr ssh 1 ,
via
.Xr ssh-agent 1 ,
or via a
.Cm PKCS11Provider .
.Pp
The file name may use the tilde
syntax to refer to a user's home directory or one of the following
escape characters:
.Ql %d
(local user's home directory),
.Ql %u
(local user name),
.Ql %l
(local host name),
.Ql %h
(remote host name) or
.Ql %r
(remote user name).
.Pp
It is possible to have multiple certificate files specified in
configuration files; these certificates will be tried in sequence.
Multiple
.Cm CertificateFile
directives will add to the list of certificates used for
authentication.
.It Cm ChallengeResponseAuthentication .It Cm ChallengeResponseAuthentication
Specifies whether to use challenge-response authentication. Specifies whether to use challenge-response authentication.
The argument to this keyword must be The argument to this keyword must be
@ -869,9 +904,13 @@ specifications).
.It Cm IdentitiesOnly .It Cm IdentitiesOnly
Specifies that Specifies that
.Xr ssh 1 .Xr ssh 1
should only use the authentication identity files configured in the should only use the authentication identity and certificate files explicitly
configured in the
.Nm .Nm
files, files
or passed on the
.Xr ssh 1
command-line,
even if even if
.Xr ssh-agent 1 .Xr ssh-agent 1
or a or a
@ -901,6 +940,8 @@ Additionally, any identities represented by the authentication agent
will be used for authentication unless will be used for authentication unless
.Cm IdentitiesOnly .Cm IdentitiesOnly
is set. is set.
If no certificates have been explicitly specified by
.Cm CertificateFile ,
.Xr ssh 1 .Xr ssh 1
will try to load certificate information from the filename obtained by will try to load certificate information from the filename obtained by
appending appending
@ -934,6 +975,11 @@ differs from that of other configuration directives).
may be used in conjunction with may be used in conjunction with
.Cm IdentitiesOnly .Cm IdentitiesOnly
to select which identities in an agent are offered during authentication. to select which identities in an agent are offered during authentication.
.Cm IdentityFile
may also be used in conjunction with
.Cm CertificateFile
in order to provide any certificate also needed for authentication with
the identity.
.It Cm IgnoreUnknown .It Cm IgnoreUnknown
Specifies a pattern-list of unknown options to be ignored if they are Specifies a pattern-list of unknown options to be ignored if they are
encountered in configuration parsing. encountered in configuration parsing.

View File

@ -1,4 +1,4 @@
/* $OpenBSD: sshconnect2.c,v 1.226 2015/07/30 00:01:34 djm Exp $ */ /* $OpenBSD: sshconnect2.c,v 1.227 2015/09/24 06:15:11 djm Exp $ */
/* /*
* Copyright (c) 2000 Markus Friedl. All rights reserved. * Copyright (c) 2000 Markus Friedl. All rights reserved.
* Copyright (c) 2008 Damien Miller. All rights reserved. * Copyright (c) 2008 Damien Miller. All rights reserved.
@ -1001,18 +1001,17 @@ static int
sign_and_send_pubkey(Authctxt *authctxt, Identity *id) sign_and_send_pubkey(Authctxt *authctxt, Identity *id)
{ {
Buffer b; Buffer b;
Identity *private_id;
u_char *blob, *signature; u_char *blob, *signature;
u_int bloblen;
size_t slen; size_t slen;
u_int skip = 0; u_int bloblen, skip = 0;
int ret = -1; int matched, ret = -1, have_sig = 1;
int have_sig = 1;
char *fp; char *fp;
if ((fp = sshkey_fingerprint(id->key, options.fingerprint_hash, if ((fp = sshkey_fingerprint(id->key, options.fingerprint_hash,
SSH_FP_DEFAULT)) == NULL) SSH_FP_DEFAULT)) == NULL)
return 0; return 0;
debug3("sign_and_send_pubkey: %s %s", key_type(id->key), fp); debug3("%s: %s %s", __func__, key_type(id->key), fp);
free(fp); free(fp);
if (key_to_blob(id->key, &blob, &bloblen) == 0) { if (key_to_blob(id->key, &blob, &bloblen) == 0) {
@ -1044,6 +1043,36 @@ sign_and_send_pubkey(Authctxt *authctxt, Identity *id)
} }
buffer_put_string(&b, blob, bloblen); buffer_put_string(&b, blob, bloblen);
/*
* If the key is an certificate, try to find a matching private key
* and use it to complete the signature.
* If no such private key exists, return failure and continue with
* other methods of authentication.
*/
if (key_is_cert(id->key)) {
matched = 0;
TAILQ_FOREACH(private_id, &authctxt->keys, next) {
if (sshkey_equal_public(id->key, private_id->key) &&
id->key->type != private_id->key->type) {
id = private_id;
matched = 1;
break;
}
}
if (matched) {
debug2("%s: using private key \"%s\"%s for "
"certificate", __func__, id->filename,
id->agent_fd != -1 ? " from agent" : "");
} else {
/* XXX maybe verbose/error? */
debug("%s: no private key for certificate "
"\"%s\"", __func__, id->filename);
free(blob);
buffer_free(&b);
return 0;
}
}
/* generate signature */ /* generate signature */
ret = identity_sign(id, &signature, &slen, ret = identity_sign(id, &signature, &slen,
buffer_ptr(&b), buffer_len(&b), datafellows); buffer_ptr(&b), buffer_len(&b), datafellows);
@ -1180,9 +1209,11 @@ load_identity_file(char *filename, int userprovided)
/* /*
* try keys in the following order: * try keys in the following order:
* 1. agent keys that are found in the config file * 1. certificates listed in the config file
* 2. other agent keys * 2. other input certificates
* 3. keys that are only listed in the config file * 3. agent keys that are found in the config file
* 4. other agent keys
* 5. keys that are only listed in the config file
*/ */
static void static void
pubkey_prepare(Authctxt *authctxt) pubkey_prepare(Authctxt *authctxt)
@ -1236,6 +1267,18 @@ pubkey_prepare(Authctxt *authctxt)
free(id); free(id);
} }
} }
/* list of certificates specified by user */
for (i = 0; i < options.num_certificate_files; i++) {
key = options.certificates[i];
if (!key_is_cert(key) || key->cert == NULL ||
key->cert->type != SSH2_CERT_TYPE_USER)
continue;
id = xcalloc(1, sizeof(*id));
id->key = key;
id->filename = xstrdup(options.certificate_files[i]);
id->userprovided = options.certificate_file_userprovided[i];
TAILQ_INSERT_TAIL(preferred, id, next);
}
/* list of keys supported by the agent */ /* list of keys supported by the agent */
if ((r = ssh_get_authentication_socket(&agent_fd)) != 0) { if ((r = ssh_get_authentication_socket(&agent_fd)) != 0) {
if (r != SSH_ERR_AGENT_NOT_PRESENT) if (r != SSH_ERR_AGENT_NOT_PRESENT)