summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDamien Miller <djm@mindrot.org>2010-05-10 11:58:03 +1000
committerDamien Miller <djm@mindrot.org>2010-05-10 11:58:03 +1000
commit30da3447d2ef3329cb0eb083cdddf84532659454 (patch)
tree02537d2355d77cc15d1bf9d266d474e660848012
parent099fc1634e1cc0f96b77a811e554bf9d796def8f (diff)
- djm@cvs.openbsd.org 2010/05/07 11:30:30
[auth-options.c auth-options.h auth.c auth.h auth2-pubkey.c] [key.c servconf.c servconf.h sshd.8 sshd_config.5] add some optional indirection to matching of principal names listed in certificates. Currently, a certificate must include the a user's name to be accepted for authentication. This change adds the ability to specify a list of certificate principal names that are acceptable. When authenticating using a CA trusted through ~/.ssh/authorized_keys, this adds a new principals="name1[,name2,...]" key option. For CAs listed through sshd_config's TrustedCAKeys option, a new config option "AuthorizedPrincipalsFile" specifies a per-user file containing the list of acceptable names. If either option is absent, the current behaviour of requiring the username to appear in principals continues to apply. These options are useful for role accounts, disjoint account namespaces and "user@realm"-style naming policies in certificates. feedback and ok markus@
-rw-r--r--ChangeLog22
-rw-r--r--auth-options.c43
-rw-r--r--auth-options.h3
-rw-r--r--auth.c41
-rw-r--r--auth.h4
-rw-r--r--auth2-pubkey.c102
-rw-r--r--key.c4
-rw-r--r--servconf.c18
-rw-r--r--servconf.h3
-rw-r--r--sshd.815
-rw-r--r--sshd_config.541
11 files changed, 262 insertions, 34 deletions
diff --git a/ChangeLog b/ChangeLog
index 68460945..090e2352 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -31,6 +31,28 @@
[sftp.c]
restore mput and mget which got lost in the tab-completion changes.
found by Kenneth Whitaker, ok djm@
+ - djm@cvs.openbsd.org 2010/05/07 11:30:30
+ [auth-options.c auth-options.h auth.c auth.h auth2-pubkey.c]
+ [key.c servconf.c servconf.h sshd.8 sshd_config.5]
+ add some optional indirection to matching of principal names listed
+ in certificates. Currently, a certificate must include the a user's name
+ to be accepted for authentication. This change adds the ability to
+ specify a list of certificate principal names that are acceptable.
+
+ When authenticating using a CA trusted through ~/.ssh/authorized_keys,
+ this adds a new principals="name1[,name2,...]" key option.
+
+ For CAs listed through sshd_config's TrustedCAKeys option, a new config
+ option "AuthorizedPrincipalsFile" specifies a per-user file containing
+ the list of acceptable names.
+
+ If either option is absent, the current behaviour of requiring the
+ username to appear in principals continues to apply.
+
+ These options are useful for role accounts, disjoint account namespaces
+ and "user@realm"-style naming policies in certificates.
+
+ feedback and ok markus@
20100423
- (dtucker) [configure.ac] Bug #1756: Check for the existence of a lib64 dir
diff --git a/auth-options.c b/auth-options.c
index 60d5f749..57a67ec7 100644
--- a/auth-options.c
+++ b/auth-options.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: auth-options.c,v 1.50 2010/04/16 01:47:26 djm Exp $ */
+/* $OpenBSD: auth-options.c,v 1.51 2010/05/07 11:30:29 djm Exp $ */
/*
* Author: Tatu Ylonen <ylo@cs.hut.fi>
* Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
@@ -55,6 +55,9 @@ struct envstring *custom_environment = NULL;
/* "tunnel=" option. */
int forced_tun_device = -1;
+/* "principals=" option. */
+char *authorized_principals = NULL;
+
extern ServerOptions options;
void
@@ -76,6 +79,10 @@ auth_clear_options(void)
xfree(forced_command);
forced_command = NULL;
}
+ if (authorized_principals) {
+ xfree(authorized_principals);
+ authorized_principals = NULL;
+ }
forced_tun_device = -1;
channel_clear_permitted_opens();
}
@@ -141,6 +148,8 @@ auth_parse_options(struct passwd *pw, char *opts, char *file, u_long linenum)
cp = "command=\"";
if (strncasecmp(opts, cp, strlen(cp)) == 0) {
opts += strlen(cp);
+ if (forced_command != NULL)
+ xfree(forced_command);
forced_command = xmalloc(strlen(opts) + 1);
i = 0;
while (*opts) {
@@ -167,6 +176,38 @@ auth_parse_options(struct passwd *pw, char *opts, char *file, u_long linenum)
opts++;
goto next_option;
}
+ cp = "principals=\"";
+ if (strncasecmp(opts, cp, strlen(cp)) == 0) {
+ opts += strlen(cp);
+ if (authorized_principals != NULL)
+ xfree(authorized_principals);
+ authorized_principals = xmalloc(strlen(opts) + 1);
+ i = 0;
+ while (*opts) {
+ if (*opts == '"')
+ break;
+ if (*opts == '\\' && opts[1] == '"') {
+ opts += 2;
+ authorized_principals[i++] = '"';
+ continue;
+ }
+ authorized_principals[i++] = *opts++;
+ }
+ if (!*opts) {
+ debug("%.100s, line %lu: missing end quote",
+ file, linenum);
+ auth_debug_add("%.100s, line %lu: missing end quote",
+ file, linenum);
+ xfree(authorized_principals);
+ authorized_principals = NULL;
+ goto bad_option;
+ }
+ authorized_principals[i] = '\0';
+ auth_debug_add("principals: %.900s",
+ authorized_principals);
+ opts++;
+ goto next_option;
+ }
cp = "environment=\"";
if (options.permit_user_env &&
strncasecmp(opts, cp, strlen(cp)) == 0) {
diff --git a/auth-options.h b/auth-options.h
index 20f0dbe3..7455c945 100644
--- a/auth-options.h
+++ b/auth-options.h
@@ -1,4 +1,4 @@
-/* $OpenBSD: auth-options.h,v 1.19 2010/04/16 01:47:26 djm Exp $ */
+/* $OpenBSD: auth-options.h,v 1.20 2010/05/07 11:30:29 djm Exp $ */
/*
* Author: Tatu Ylonen <ylo@cs.hut.fi>
@@ -31,6 +31,7 @@ extern char *forced_command;
extern struct envstring *custom_environment;
extern int forced_tun_device;
extern int key_is_cert_authority;
+extern char *authorized_principals;
int auth_parse_options(struct passwd *, char *, char *, u_long);
void auth_clear_options(void);
diff --git a/auth.c b/auth.c
index 89a93606..bec191a5 100644
--- a/auth.c
+++ b/auth.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: auth.c,v 1.86 2010/03/05 02:58:11 djm Exp $ */
+/* $OpenBSD: auth.c,v 1.87 2010/05/07 11:30:29 djm Exp $ */
/*
* Copyright (c) 2000 Markus Friedl. All rights reserved.
*
@@ -366,6 +366,14 @@ authorized_keys_file2(struct passwd *pw)
return expand_authorized_keys(options.authorized_keys_file2, pw);
}
+char *
+authorized_principals_file(struct passwd *pw)
+{
+ if (options.authorized_principals_file == NULL)
+ return NULL;
+ return expand_authorized_keys(options.authorized_principals_file, pw);
+}
+
/* return ok if key exists in sysfile or userfile */
HostStatus
check_key_in_hostfiles(struct passwd *pw, Key *key, const char *host,
@@ -477,21 +485,18 @@ secure_filename(FILE *f, const char *file, struct passwd *pw,
return 0;
}
-FILE *
-auth_openkeyfile(const char *file, struct passwd *pw, int strict_modes)
+static FILE *
+auth_openfile(const char *file, struct passwd *pw, int strict_modes,
+ int log_missing, char *file_type)
{
char line[1024];
struct stat st;
int fd;
FILE *f;
- /*
- * Open the file containing the authorized keys
- * Fail quietly if file does not exist
- */
if ((fd = open(file, O_RDONLY|O_NONBLOCK)) == -1) {
- if (errno != ENOENT)
- debug("Could not open keyfile '%s': %s", file,
+ if (log_missing || errno != ENOENT)
+ debug("Could not open %s '%s': %s", file_type, file,
strerror(errno));
return NULL;
}
@@ -501,8 +506,8 @@ auth_openkeyfile(const char *file, struct passwd *pw, int strict_modes)
return NULL;
}
if (!S_ISREG(st.st_mode)) {
- logit("User %s authorized keys %s is not a regular file",
- pw->pw_name, file);
+ logit("User %s %s %s is not a regular file",
+ pw->pw_name, file_type, file);
close(fd);
return NULL;
}
@@ -521,6 +526,20 @@ auth_openkeyfile(const char *file, struct passwd *pw, int strict_modes)
return f;
}
+
+FILE *
+auth_openkeyfile(const char *file, struct passwd *pw, int strict_modes)
+{
+ return auth_openfile(file, pw, strict_modes, 1, "authorized keys");
+}
+
+FILE *
+auth_openprincipals(const char *file, struct passwd *pw, int strict_modes)
+{
+ return auth_openfile(file, pw, strict_modes, 0,
+ "authorized principals");
+}
+
struct passwd *
getpwnamallow(const char *user)
{
diff --git a/auth.h b/auth.h
index a65b87dd..77317aee 100644
--- a/auth.h
+++ b/auth.h
@@ -1,4 +1,4 @@
-/* $OpenBSD: auth.h,v 1.65 2010/03/04 10:36:03 djm Exp $ */
+/* $OpenBSD: auth.h,v 1.66 2010/05/07 11:30:29 djm Exp $ */
/*
* Copyright (c) 2000 Markus Friedl. All rights reserved.
@@ -169,8 +169,10 @@ void abandon_challenge_response(Authctxt *);
char *authorized_keys_file(struct passwd *);
char *authorized_keys_file2(struct passwd *);
+char *authorized_principals_file(struct passwd *);
FILE *auth_openkeyfile(const char *, struct passwd *, int);
+FILE *auth_openprincipals(const char *, struct passwd *, int);
int auth_key_is_revoked(Key *);
HostStatus
diff --git a/auth2-pubkey.c b/auth2-pubkey.c
index 83ecd659..6b4a9972 100644
--- a/auth2-pubkey.c
+++ b/auth2-pubkey.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: auth2-pubkey.c,v 1.23 2010/04/16 01:47:26 djm Exp $ */
+/* $OpenBSD: auth2-pubkey.c,v 1.24 2010/05/07 11:30:29 djm Exp $ */
/*
* Copyright (c) 2000 Markus Friedl. All rights reserved.
*
@@ -57,6 +57,7 @@
#include "monitor_wrap.h"
#include "misc.h"
#include "authfile.h"
+#include "match.h"
/* import */
extern ServerOptions options;
@@ -176,6 +177,63 @@ done:
return authenticated;
}
+static int
+match_principals_option(const char *principal_list, struct KeyCert *cert)
+{
+ char *result;
+ u_int i;
+
+ /* XXX percent_expand() sequences for authorized_principals? */
+
+ for (i = 0; i < cert->nprincipals; i++) {
+ if ((result = match_list(cert->principals[i],
+ principal_list, NULL)) != NULL) {
+ debug3("matched principal from key options \"%.100s\"",
+ result);
+ xfree(result);
+ return 1;
+ }
+ }
+ return 0;
+}
+
+static int
+match_principals_file(const char *file, struct passwd *pw, struct KeyCert *cert)
+{
+ FILE *f;
+ char line[SSH_MAX_PUBKEY_BYTES], *cp;
+ u_long linenum = 0;
+ u_int i;
+
+ temporarily_use_uid(pw);
+ debug("trying authorized principals file %s", file);
+ if ((f = auth_openprincipals(file, pw, options.strict_modes)) == NULL) {
+ restore_uid();
+ return 0;
+ }
+ while (read_keyfile_line(f, file, line, sizeof(line), &linenum) != -1) {
+ /* Skip leading whitespace, empty and comment lines. */
+ for (cp = line; *cp == ' ' || *cp == '\t'; cp++)
+ ;
+ if (!*cp || *cp == '\n' || *cp == '#')
+ continue;
+ line[strcspn(line, "\n")] = '\0';
+
+ for (i = 0; i < cert->nprincipals; i++) {
+ if (strcmp(cp, cert->principals[i]) == 0) {
+ debug3("matched principal from file \"%.100s\"",
+ cert->principals[i]);
+ fclose(f);
+ restore_uid();
+ return 1;
+ }
+ }
+ }
+ fclose(f);
+ restore_uid();
+ return 0;
+}
+
/* return 1 if user allows given key */
static int
user_key_allowed2(struct passwd *pw, Key *key, char *file)
@@ -244,13 +302,26 @@ user_key_allowed2(struct passwd *pw, Key *key, char *file)
SSH_FP_HEX);
debug("matching CA found: file %s, line %lu, %s %s",
file, linenum, key_type(found), fp);
- if (key_cert_check_authority(key, 0, 0, pw->pw_name,
- &reason) != 0) {
+ /*
+ * If the user has specified a list of principals as
+ * a key option, then prefer that list to matching
+ * their username in the certificate principals list.
+ */
+ if (authorized_principals != NULL &&
+ !match_principals_option(authorized_principals,
+ key->cert)) {
+ reason = "Certificate does not contain an "
+ "authorized principal";
+ fail_reason:
xfree(fp);
error("%s", reason);
auth_debug_add("%s", reason);
continue;
}
+ if (key_cert_check_authority(key, 0, 0,
+ authorized_principals == NULL ? pw->pw_name : NULL,
+ &reason) != 0)
+ goto fail_reason;
if (auth_cert_options(key, pw) != 0) {
xfree(fp);
continue;
@@ -284,7 +355,7 @@ user_key_allowed2(struct passwd *pw, Key *key, char *file)
static int
user_cert_trusted_ca(struct passwd *pw, Key *key)
{
- char *ca_fp;
+ char *ca_fp, *principals_file = NULL;
const char *reason;
int ret = 0;
@@ -301,11 +372,24 @@ user_cert_trusted_ca(struct passwd *pw, Key *key)
options.trusted_user_ca_keys);
goto out;
}
- if (key_cert_check_authority(key, 0, 1, pw->pw_name, &reason) != 0) {
- error("%s", reason);
- auth_debug_add("%s", reason);
- goto out;
+ /*
+ * If AuthorizedPrincipals is in use, then compare the certificate
+ * principals against the names in that file rather than matching
+ * against the username.
+ */
+ if ((principals_file = authorized_principals_file(pw)) != NULL) {
+ if (!match_principals_file(principals_file, pw, key->cert)) {
+ reason = "Certificate does not contain an "
+ "authorized principal";
+ fail_reason:
+ error("%s", reason);
+ auth_debug_add("%s", reason);
+ goto out;
+ }
}
+ if (key_cert_check_authority(key, 0, 1,
+ principals_file == NULL ? pw->pw_name : NULL, &reason) != 0)
+ goto fail_reason;
if (auth_cert_options(key, pw) != 0)
goto out;
@@ -315,6 +399,8 @@ user_cert_trusted_ca(struct passwd *pw, Key *key)
ret = 1;
out:
+ if (principals_file != NULL)
+ xfree(principals_file);
if (ca_fp != NULL)
xfree(ca_fp);
return ret;
diff --git a/key.c b/key.c
index 34f678b3..c4d9d577 100644
--- a/key.c
+++ b/key.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: key.c,v 1.87 2010/04/16 01:47:26 djm Exp $ */
+/* $OpenBSD: key.c,v 1.88 2010/05/07 11:30:29 djm Exp $ */
/*
* read_bignum():
* Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
@@ -1623,7 +1623,7 @@ key_cert_check_authority(const Key *k, int want_host, int require_principal,
*reason = "Certificate lacks principal list";
return -1;
}
- } else {
+ } else if (name != NULL) {
principal_matches = 0;
for (i = 0; i < k->cert->nprincipals; i++) {
if (strcmp(name, k->cert->principals[i]) == 0) {
diff --git a/servconf.c b/servconf.c
index 7d027ddb..c556986e 100644
--- a/servconf.c
+++ b/servconf.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: servconf.c,v 1.207 2010/03/25 23:38:28 djm Exp $ */
+/* $OpenBSD: servconf.c,v 1.208 2010/05/07 11:30:29 djm Exp $ */
/*
* Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
* All rights reserved
@@ -131,6 +131,7 @@ initialize_server_options(ServerOptions *options)
options->zero_knowledge_password_authentication = -1;
options->revoked_keys_file = NULL;
options->trusted_user_ca_keys = NULL;
+ options->authorized_principals_file = NULL;
}
void
@@ -310,7 +311,7 @@ typedef enum {
sMatch, sPermitOpen, sForceCommand, sChrootDirectory,
sUsePrivilegeSeparation, sAllowAgentForwarding,
sZeroKnowledgePasswordAuthentication, sHostCertificate,
- sRevokedKeys, sTrustedUserCAKeys,
+ sRevokedKeys, sTrustedUserCAKeys, sAuthorizedPrincipalsFile,
sDeprecated, sUnsupported
} ServerOpCodes;
@@ -432,6 +433,7 @@ static struct {
{ "hostcertificate", sHostCertificate, SSHCFG_GLOBAL },
{ "revokedkeys", sRevokedKeys, SSHCFG_ALL },
{ "trustedusercakeys", sTrustedUserCAKeys, SSHCFG_ALL },
+ { "authorizedprincipalsfile", sAuthorizedPrincipalsFile, SSHCFG_GLOBAL },
{ NULL, sBadOption, 0 }
};
@@ -1218,10 +1220,14 @@ process_server_config_line(ServerOptions *options, char *line,
* AuthorizedKeysFile /etc/ssh_keys/%u
*/
case sAuthorizedKeysFile:
+ charptr = &options->authorized_keys_file;
+ goto parse_tilde_filename;
case sAuthorizedKeysFile2:
- charptr = (opcode == sAuthorizedKeysFile) ?
- &options->authorized_keys_file :
- &options->authorized_keys_file2;
+ charptr = &options->authorized_keys_file2;
+ goto parse_tilde_filename;
+ case sAuthorizedPrincipalsFile:
+ charptr = &options->authorized_principals_file;
+ parse_tilde_filename:
arg = strdelim(&cp);
if (!arg || *arg == '\0')
fatal("%s line %d: missing file name.",
@@ -1682,6 +1688,8 @@ dump_config(ServerOptions *o)
dump_cfg_string(sChrootDirectory, o->chroot_directory);
dump_cfg_string(sTrustedUserCAKeys, o->trusted_user_ca_keys);
dump_cfg_string(sRevokedKeys, o->revoked_keys_file);
+ dump_cfg_string(sAuthorizedPrincipalsFile,
+ o->authorized_principals_file);
/* string arguments requiring a lookup */
dump_cfg_string(sLogLevel, log_level_name(o->log_level));
diff --git a/servconf.h b/servconf.h
index 860009f9..45d2a2ae 100644
--- a/servconf.h
+++ b/servconf.h
@@ -1,4 +1,4 @@
-/* $OpenBSD: servconf.h,v 1.92 2010/03/04 10:36:03 djm Exp $ */
+/* $OpenBSD: servconf.h,v 1.93 2010/05/07 11:30:30 djm Exp $ */
/*
* Author: Tatu Ylonen <ylo@cs.hut.fi>
@@ -156,6 +156,7 @@ typedef struct {
char *chroot_directory;
char *revoked_keys_file;
char *trusted_user_ca_keys;
+ char *authorized_principals_file;
} ServerOptions;
void initialize_server_options(ServerOptions *);
diff --git a/sshd.8 b/sshd.8
index 5f196600..6eb49238 100644
--- a/sshd.8
+++ b/sshd.8
@@ -34,8 +34,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: sshd.8,v 1.255 2010/03/05 06:50:35 jmc Exp $
-.Dd $Mdocdate: March 5 2010 $
+.\" $OpenBSD: sshd.8,v 1.256 2010/05/07 11:30:30 djm Exp $
+.Dd $Mdocdate: May 7 2010 $
.Dt SSHD 8
.Os
.Sh NAME
@@ -602,6 +602,17 @@ Multiple
options may be applied separated by commas.
No pattern matching is performed on the specified hostnames,
they must be literal domains or addresses.
+.It Cm principals="principals"
+On a
+.Cm cert-authority
+line, specifies allowed principals for certificate authentication as a
+comma-separated list.
+At least one name from the list must appear in the certificate's
+list of principals for the certificate to be accepted.
+This option is ignored for keys that are not marked as trusted certificate
+signers using the
+.Cm cert-authority
+option.
.It Cm tunnel="n"
Force a
.Xr tun 4
diff --git a/sshd_config.5 b/sshd_config.5
index 2f541028..a5260d35 100644
--- a/sshd_config.5
+++ b/sshd_config.5
@@ -34,8 +34,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: sshd_config.5,v 1.120 2010/03/04 23:17:25 djm Exp $
-.Dd $Mdocdate: March 4 2010 $
+.\" $OpenBSD: sshd_config.5,v 1.121 2010/05/07 11:30:30 djm Exp $
+.Dd $Mdocdate: May 7 2010 $
.Dt SSHD_CONFIG 5
.Os
.Sh NAME
@@ -167,6 +167,43 @@ is taken to be an absolute path or one relative to the user's home
directory.
The default is
.Dq .ssh/authorized_keys .
+.It Cm AuthorizedPrincipalsFile
+Specifies a file that lists principal names that are accepted for
+certificate authentication.
+When using certificates signed by a key listed in
+.Cm TrustedUserCAKeys ,
+this file lists names, one of which must appear in the certificate for it
+to be accepted for authentication.
+Names are listed one per line; empty lines and comments starting with
+.Ql #
+are ignored.
+.Pp
+.Cm AuthorizedPrincipalsFile
+may contain tokens of the form %T which are substituted during connection
+setup.
+The following tokens are defined: %% is replaced by a literal '%',
+%h is replaced by the home directory of the user being authenticated, and
+%u is replaced by the username of that user.
+After expansion,
+.Cm AuthorizedPrincipalsFile
+is taken to be an absolute path or one relative to the user's home
+directory.
+.Pp
+The default is not to use a principals file - in this case, the username
+of the user must appear in a certificate's principals list for it to be
+accepted.
+Note that
+.Cm AuthorizedPrincipalsFile
+is only used when authentication proceeds using a CA listed in
+.Cm TrustedUserCAKeys
+and is not consulted for certification authorities trusted via
+.Pa ~/.ssh/authorized_keys ,
+though the
+.Cm principals=
+key option offers a similar facility (see
+.Xr sshd 8
+for details).
+.Pp
.It Cm Banner
The contents of the specified file are sent to the remote user before
authentication is allowed.