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>
* Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
@ -135,6 +135,7 @@ typedef enum {
oPasswordAuthentication, oRSAAuthentication,
oChallengeResponseAuthentication, oXAuthLocation,
oIdentityFile, oHostName, oPort, oCipher, oRemoteForward, oLocalForward,
oCertificateFile,
oUser, oEscapeChar, oRhostsRSAAuthentication, oProxyCommand,
oGlobalKnownHostsFile, oUserKnownHostsFile, oConnectionAttempts,
oBatchMode, oCheckHostIP, oStrictHostKeyChecking, oCompression,
@ -202,6 +203,7 @@ static struct {
{ "identityfile", oIdentityFile },
{ "identityfile2", oIdentityFile }, /* obsolete */
{ "identitiesonly", oIdentitiesOnly },
{ "certificatefile", oCertificateFile },
{ "hostname", oHostName },
{ "hostkeyalias", oHostKeyAlias },
{ "proxycommand", oProxyCommand },
@ -365,6 +367,30 @@ clear_forwardings(Options *options)
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
add_identity_file(Options *options, const char *dir, const char *filename,
int userprovided)
@ -981,6 +1007,24 @@ parse_time:
}
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:
charptr=&options->xauth_location;
goto parse_string;
@ -1625,6 +1669,7 @@ initialize_options(Options * options)
options->hostkeyalgorithms = NULL;
options->protocol = SSH_PROTO_UNKNOWN;
options->num_identity_files = 0;
options->num_certificate_files = 0;
options->hostname = NULL;
options->host_key_alias = 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>
@ -95,6 +95,11 @@ typedef struct {
int identity_file_userprovided[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. */
int num_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_remote_forward(Options *, const struct Forward *);
void add_identity_file(Options *, const char *, const char *, int);
void add_certificate_file(Options *, const char *, int);
#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
.\" 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 $
.Dd $Mdocdate: September 11 2015 $
.\" $OpenBSD: ssh.1,v 1.363 2015/09/24 06:15:11 djm Exp $
.Dd $Mdocdate: September 24 2015 $
.Dt SSH 1
.Os
.Sh NAME
@ -304,6 +304,9 @@ It is possible to have multiple
.Fl i
options (and multiple identities specified in
configuration files).
If no certificates have been explicitly specified by
.Cm CertificateFile
directive,
.Nm
will also try to load certificate information from the filename obtained
by appending
@ -468,6 +471,7 @@ For full details of the options listed below, and their possible values, see
.It CanonicalizeHostname
.It CanonicalizeMaxDots
.It CanonicalizePermittedCNAMEs
.It CertificateFile
.It ChallengeResponseAuthentication
.It CheckHostIP
.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>
* 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;
}
}
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();
packet_close();
@ -1940,25 +1944,30 @@ ssh_session2(void)
options.escape_char : SSH_ESCAPECHAR_NONE, id);
}
/* Loads all IdentityFile and CertificateFile keys */
static void
load_public_identity_files(void)
{
char *filename, *cp, thishost[NI_MAXHOST];
char *pwdir = NULL, *pwname = NULL;
int i = 0;
Key *public;
struct passwd *pw;
u_int n_ids;
int i;
u_int n_ids, n_certs;
char *identity_files[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
Key **keys;
int nkeys;
#endif /* PKCS11 */
n_ids = 0;
n_ids = n_certs = 0;
memset(identity_files, 0, sizeof(identity_files));
memset(identity_keys, 0, sizeof(identity_keys));
memset(certificate_files, 0, sizeof(certificate_files));
memset(certificates, 0, sizeof(certificates));
#ifdef ENABLE_PKCS11
if (options.pkcs11_provider != NULL &&
@ -1990,6 +1999,7 @@ load_public_identity_files(void)
if (n_ids >= SSH_MAX_IDENTITY_FILES ||
strcasecmp(options.identity_files[i], "none") == 0) {
free(options.identity_files[i]);
options.identity_files[i] = NULL;
continue;
}
cp = tilde_expand_filename(options.identity_files[i],
@ -2008,7 +2018,12 @@ load_public_identity_files(void)
if (++n_ids >= SSH_MAX_IDENTITY_FILES)
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);
public = key_load_public(cp, NULL);
debug("identity file %s type %d", cp,
@ -2025,14 +2040,50 @@ load_public_identity_files(void)
continue;
}
identity_keys[n_ids] = public;
/* point to the original path, most likely the private key */
identity_files[n_ids] = xstrdup(filename);
identity_files[n_ids] = cp;
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;
memcpy(options.identity_files, identity_files, sizeof(identity_files));
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));
free(pwname);
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>
@ -18,6 +18,12 @@
/* Default port number. */
#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
* 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
.\" 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 $
.Dd $Mdocdate: September 22 2015 $
.\" $OpenBSD: ssh_config.5,v 1.221 2015/09/24 06:15:11 djm Exp $
.Dd $Mdocdate: September 24 2015 $
.Dt SSH_CONFIG 5
.Os
.Sh NAME
@ -325,6 +325,41 @@ to be canonicalized to names in the
or
.Dq *.c.example.com
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
Specifies whether to use challenge-response authentication.
The argument to this keyword must be
@ -869,9 +904,13 @@ specifications).
.It Cm IdentitiesOnly
Specifies that
.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
files,
files
or passed on the
.Xr ssh 1
command-line,
even if
.Xr ssh-agent 1
or a
@ -901,6 +940,8 @@ Additionally, any identities represented by the authentication agent
will be used for authentication unless
.Cm IdentitiesOnly
is set.
If no certificates have been explicitly specified by
.Cm CertificateFile ,
.Xr ssh 1
will try to load certificate information from the filename obtained by
appending
@ -934,6 +975,11 @@ differs from that of other configuration directives).
may be used in conjunction with
.Cm IdentitiesOnly
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
Specifies a pattern-list of unknown options to be ignored if they are
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) 2008 Damien Miller. All rights reserved.
@ -1001,18 +1001,17 @@ static int
sign_and_send_pubkey(Authctxt *authctxt, Identity *id)
{
Buffer b;
Identity *private_id;
u_char *blob, *signature;
u_int bloblen;
size_t slen;
u_int skip = 0;
int ret = -1;
int have_sig = 1;
u_int bloblen, skip = 0;
int matched, ret = -1, have_sig = 1;
char *fp;
if ((fp = sshkey_fingerprint(id->key, options.fingerprint_hash,
SSH_FP_DEFAULT)) == NULL)
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);
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);
/*
* 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 */
ret = identity_sign(id, &signature, &slen,
buffer_ptr(&b), buffer_len(&b), datafellows);
@ -1180,9 +1209,11 @@ load_identity_file(char *filename, int userprovided)
/*
* try keys in the following order:
* 1. agent keys that are found in the config file
* 2. other agent keys
* 3. keys that are only listed in the config file
* 1. certificates listed in the config file
* 2. other input certificates
* 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
pubkey_prepare(Authctxt *authctxt)
@ -1236,6 +1267,18 @@ pubkey_prepare(Authctxt *authctxt)
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 */
if ((r = ssh_get_authentication_socket(&agent_fd)) != 0) {
if (r != SSH_ERR_AGENT_NOT_PRESENT)