Bugzilla – Attachment 2679 Details for
Bug 2436
Add ssh option to present certificates on command line
Home
|
New
|
Browse
|
Search
|
[?]
|
Reports
|
Requests
|
Help
|
New Account
|
Log In
[x]
|
Forgot Password
Login:
[x]
[patch]
[PATCH] Add ssh -z option to present certificates on command line
sshcertpatch.patch (text/plain), 18.91 KB, created by
Meghana Bhat
on 2015-07-31 03:41:34 AEST
(
hide
)
Description:
[PATCH] Add ssh -z option to present certificates on command line
Filename:
MIME Type:
Creator:
Meghana Bhat
Created:
2015-07-31 03:41:34 AEST
Size:
18.91 KB
patch
obsolete
>From: Meghana Bhat <mebhat@akamai.com> >Date: Mon, 27 Jul 2015 14:22:28 -0400 >Subject: [PATCH] ssh: Add option to present certificates on command line > >Allow users to specify certificates to be used for authentication on >the command line with the '-z' argument when running ssh. For >successful authentication, the key pair associated with the certificate >must also be presented during the ssh. > >Certificates may also be specified in ssh_config as a >CertificateFile. > >Patch developed against 6.9p. > >--- > readconf.c | 48 +++++++++++++++++++ > readconf.h | 6 +++ > regress/Makefile | 1 + > regress/ssh-cert.sh | 136 ++++++++++++++++++++++++++++++++++++++++++++++++++++ > ssh.1 | 17 +++++++ > ssh.c | 85 +++++++++++++++++++++++++++++++- > ssh.h | 7 +++ > ssh_config.5 | 33 +++++++++++++ > sshconnect2.c | 47 ++++++++++++++++-- > 9 files changed, 375 insertions(+), 5 deletions(-) > create mode 100644 regress/ssh-cert.sh > >diff --git a/readconf.c b/readconf.c >index f1c860b..b34213d 100644 >--- a/readconf.c >+++ b/readconf.c >@@ -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 }, >@@ -366,6 +368,37 @@ clear_forwardings(Options *options) > } > > void >+add_certificate_file(Options *options, const char *dir, const char *filename, >+ int userprovided) >+{ >+ char *path; >+ int i; >+ >+ if (options->num_certificate_files >= SSH_MAX_CERTIFICATE_FILES) >+ fatal("Too many certificate files specified (max %d)", >+ SSH_MAX_CERTIFICATE_FILES); >+ >+ if (dir == NULL) /* no dir, filename is absolute */ >+ path = xstrdup(filename); >+ else >+ (void)xasprintf(&path, "%.100s%.100s", dir, filename); >+ >+ /* 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); >+ free(path); >+ return; >+ } >+ } >+ >+ options->certificate_file_userprovided[options->num_certificate_files] = >+ userprovided; >+ options->certificate_files[options->num_certificate_files++] = path; >+} >+ >+void > add_identity_file(Options *options, const char *dir, const char *filename, > int userprovided) > { >@@ -981,6 +1014,20 @@ 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 identity files specified (max %d).", >+ filename, linenum, SSH_MAX_CERTIFICATE_FILES); >+ add_certificate_file(options, NULL, >+ arg, flags & SSHCONF_USERCONF); >+ } >+ break; >+ > case oXAuthLocation: > charptr=&options->xauth_location; > goto parse_string; >@@ -1625,6 +1672,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; >diff --git a/readconf.h b/readconf.h >index bb2d552..f839016 100644 >--- a/readconf.h >+++ b/readconf.h >@@ -94,6 +94,11 @@ typedef struct { > char *identity_files[SSH_MAX_IDENTITY_FILES]; > 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; >@@ -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 *, const char *, int); > > #endif /* READCONF_H */ >diff --git a/regress/Makefile b/regress/Makefile >index cba83f4..67455a8 100644 >--- a/regress/Makefile >+++ b/regress/Makefile >@@ -74,6 +74,7 @@ LTESTS= connect \ > hostkey-agent \ > keygen-knownhosts \ > hostkey-rotate \ >+ ssh-cert \ > principals-command > > >diff --git a/regress/ssh-cert.sh b/regress/ssh-cert.sh >new file mode 100644 >index 0000000..152278b >--- /dev/null >+++ b/regress/ssh-cert.sh >@@ -0,0 +1,136 @@ >+# $OpenBSD: multicert.sh,v 1.1 2014/12/22 08:06:03 djm Exp $ >+# Placed in the Public Domain. >+ >+tid="ssh with certificates" >+ >+rm -f $OBJ/user_ca_key* $OBJ/user_key* >+rm -f $OBJ/cert_user_key* >+ >+# Create a CA key >+${SSHKEYGEN} -q -N '' -t ed25519 -f $OBJ/user_ca_key1 ||\ >+ fatal "ssh-keygen failed" >+${SSHKEYGEN} -q -N '' -t ed25519 -f $OBJ/user_ca_key2 ||\ >+ fatal "ssh-keygen failed" >+ >+# Make some keys and certificates. >+${SSHKEYGEN} -q -N '' -t ed25519 -f $OBJ/user_key1 || \ >+ fatal "ssh-keygen failed" >+${SSHKEYGEN} -q -N '' -t ed25519 -f $OBJ/user_key2 || \ >+ fatal "ssh-keygen failed" >+# Move the certificate to a different address to better control >+# when it is offered. >+${SSHKEYGEN} -q -s $OBJ/user_ca_key1 -I "regress user key for $USER" \ >+ -z $$ -n ${USER} $OBJ/user_key1 || >+ fail "couldn't sign user_key1 with user_ca_key1" >+mv $OBJ/user_key1-cert.pub $OBJ/cert_user_key1_1.pub >+${SSHKEYGEN} -q -s $OBJ/user_ca_key2 -I "regress user key for $USER" \ >+ -z $$ -n ${USER} $OBJ/user_key1 || >+ fail "couldn't sign user_key1 with user_ca_key2" >+mv $OBJ/user_key1-cert.pub $OBJ/cert_user_key1_2.pub >+ >+trace 'try with identity files' >+opts="-F $OBJ/ssh_proxy -oIdentitiesOnly=yes" >+opts2="$opts -i $OBJ/user_key1 -i $OBJ/user_key2" >+echo "cert-authority $(cat $OBJ/user_ca_key1.pub)" > $OBJ/authorized_keys_$USER >+ >+for p in ${SSH_PROTOCOLS}; do >+ # Just keys should fail >+ ${SSH} $opts2 somehost exit 5$p >+ r=$? >+ if [ $r -eq 5$p ]; then >+ fail "ssh succeeded with no certs in protocol $p" >+ fi >+ >+ # Keys with untrusted cert should fail. >+ opts3="$opts2 -z $OBJ/cert_user_key1_2.pub" >+ ${SSH} $opts3 somehost exit 5$p >+ r=$? >+ if [ $r -eq 5$p ]; then >+ fail "ssh succeeded with bad cert in protocol $p" >+ fi >+ >+ # Good cert with bad key should fail. >+ opts3="$opts -i $OBJ/user_key2 -z $OBJ/cert_user_key1_1.pub" >+ ${SSH} $opts3 somehost exit 5$p >+ r=$? >+ if [ $r -eq 5$p ]; then >+ fail "ssh succeeded with no matching key in protocol $p" >+ fi >+ >+ # Keys with one trusted cert, should succeed. >+ opts3="$opts2 -z $OBJ/cert_user_key1_1.pub" >+ ${SSH} $opts3 somehost exit 5$p >+ r=$? >+ if [ $r -ne 5$p ]; then >+ fail "ssh failed with trusted cert and key in protocol $p" >+ fi >+ >+ # Multiple certs and keys, with one trusted cert, should succeed. >+ opts3="$opts2 -z $OBJ/cert_user_key1_2.pub -z $OBJ/cert_user_key1_1.pub" >+ ${SSH} $opts3 somehost exit 5$p >+ r=$? >+ if [ $r -ne 5$p ]; then >+ fail "ssh failed with multiple certs in protocol $p" >+ fi >+ >+ #Keys with trusted certificate specified in config options, should succeed. >+ opts3="$opts2 -oCertificateFile=$OBJ/cert_user_key1_1.pub" >+ ${SSH} $opts3 somehost exit 5$p >+ r=$? >+ if [ $r -ne 5$p ]; then >+ fail "ssh failed with trusted cert in config in protocol $p" >+ fi >+done >+ >+#next, using an agent in combination with the keys >+SSH_AUTH_SOCK=/nonexistent ${SSHADD} -l > /dev/null 2>&1 >+if [ $? -ne 2 ]; then >+ fatal "ssh-add -l did not fail with exit code 2" >+fi >+ >+trace "start agent" >+eval `${SSHAGENT} -s` > /dev/null >+r=$? >+if [ $r -ne 0 ]; then >+ fatal "could not start ssh-agent: exit code $r" >+fi >+ >+# add private keys to agent >+${SSHADD} -k $OBJ/user_key2 > /dev/null 2>&1 >+if [ $? -ne 0 ]; then >+ fatal "ssh-add did not succeed with exit code 0" >+fi >+${SSHADD} -k $OBJ/user_key1 > /dev/null 2>&1 >+if [ $? -ne 0 ]; then >+ fatal "ssh-add did not succeed with exit code 0" >+fi >+ >+# try ssh with the agent and certificates >+# note: ssh agent only uses certificates in protocol 2 >+opts="-F $OBJ/ssh_proxy" >+# with no certificates, shoud fail >+${SSH} -2 $opts somehost exit 52 >+if [ $? -eq 52 ]; then >+ fail "ssh connect with agent in protocol 2 succeeded with no cert" >+fi >+ >+#with an untrusted certificate, should fail >+opts="$opts -z $OBJ/cert_user_key1_2.pub" >+${SSH} -2 $opts somehost exit 52 >+if [ $? -eq 52 ]; then >+ fail "ssh connect with agent in protocol 2 succeeded with bad cert" >+fi >+ >+#with an additional trusted certificate, should succeed >+opts="$opts -z $OBJ/cert_user_key1_1.pub" >+${SSH} -2 $opts somehost exit 52 >+if [ $? -ne 52 ]; then >+ fail "ssh connect with agent in protocol 2 failed with good cert" >+fi >+ >+trace "kill agent" >+${SSHAGENT} -k > /dev/null >+ >+#cleanup >+rm -f $OBJ/user_ca_key* $OBJ/user_key* >+rm -f $OBJ/cert_user_key* >diff --git a/ssh.1 b/ssh.1 >index 2ea0a20..76a9459 100644 >--- a/ssh.1 >+++ b/ssh.1 >@@ -63,6 +63,7 @@ > .Op Fl S Ar ctl_path > .Op Fl W Ar host : Ns Ar port > .Op Fl w Ar local_tun Ns Op : Ns Ar remote_tun >+.Op Fl z Ar certificate_file > .Oo Ar user Ns @ Oc Ns Ar hostname > .Op Ar command > .Ek >@@ -468,6 +469,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 >@@ -768,6 +770,21 @@ Send log information using the > .Xr syslog 3 > system module. > By default this information is sent to stderr. >+.It Fl z Ar certificate_file >+Selects a file from which certificate information is loaded for public >+key authentication. For the certificate to be signed, the private key >+corresponding to >+.Ar certificate_file >+must also be provided for authentication, whether through >+.Xr ssh_agent 1 . >+or through an >+.Ar identity_file >+specified on the command line or in configuration files. >+Certificate files may also be specified on a per-host basis in >+the configuration file. It is possible to have multiple >+.Fl z >+options (and multiple certificates specified in >+configuration files). > .El > .Pp > .Nm >diff --git a/ssh.c b/ssh.c >index 3239108..e01790a 100644 >--- a/ssh.c >+++ b/ssh.c >@@ -207,7 +207,8 @@ usage(void) > " [-O ctl_cmd] [-o option] [-p port]\n" > " [-Q cipher | cipher-auth | mac | kex | key]\n" > " [-R address] [-S ctl_path] [-W host:port]\n" >-" [-w local_tun[:remote_tun]] [user@]hostname [command]\n" >+" [-w local_tun[:remote_tun]] [-z certificate_file]\n" >+" [user@]hostname [command]\n" > ); > exit(255); > } >@@ -215,6 +216,7 @@ usage(void) > static int ssh_session(void); > static int ssh_session2(void); > static void load_public_identity_files(void); >+static void load_certificate_files(void); > static void main_sigchld_handler(int); > > /* from muxclient.c */ >@@ -595,7 +597,7 @@ main(int ac, char **av) > > again: > while ((opt = getopt(ac, av, "1246ab:c:e:fgi:kl:m:no:p:qstvx" >- "ACD:E:F:GI:KL:MNO:PQ:R:S:TVw:W:XYy")) != -1) { >+ "ACD:E:F:GI:KL:MNO:PQ:R:S:TVw:W:XYyz:")) != -1) { > switch (opt) { > case '1': > options.protocol = SSH_PROTO_1; >@@ -906,6 +908,9 @@ main(int ac, char **av) > case 'F': > config = optarg; > break; >+ case 'z': >+ add_certificate_file(&options, NULL, optarg, 1); >+ break; > default: > usage(); > } >@@ -1013,6 +1018,9 @@ main(int ac, char **av) > options.hostname = xstrdup(host); > } > >+ /* If the user has specified certificate(s), load it now. */ >+ load_certificate_files(); >+ > /* If canonicalization requested then try to apply it */ > lowercase(host); > if (options.canonicalize_hostname != SSH_CANONICALISE_NO) >@@ -1353,6 +1361,13 @@ main(int ac, char **av) > } > } > >+ 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(); > >@@ -1938,6 +1953,72 @@ ssh_session2(void) > options.escape_char : SSH_ESCAPECHAR_NONE, id); > } > >+/* Load certificate file(s) specified in options. */ >+static void >+load_certificate_files(void) >+{ >+ char *filename, *cp, thishost[NI_MAXHOST]; >+ char *pwdir = NULL, *pwname = NULL; >+ struct passwd *pw; >+ int i, n_ids; >+ struct sshkey *cert; >+ char *certificate_files[SSH_MAX_CERTIFICATE_FILES]; >+ struct sshkey *certificates[SSH_MAX_CERTIFICATE_FILES]; >+ >+ n_ids = 0; >+ memset(certificate_files, 0, sizeof(certificate_files)); >+ memset(certificates, 0, sizeof(certificates)); >+ >+ if ((pw = getpwuid(original_real_uid)) == NULL) >+ fatal("load_certificate_files: getpwuid failed"); >+ pwname = xstrdup(pw->pw_name); >+ pwdir = xstrdup(pw->pw_dir); >+ if (gethostname(thishost, sizeof(thishost)) == -1) >+ fatal("load_certificate_files: gethostname: %s", >+ strerror(errno)); >+ >+ if (options.num_certificate_files > SSH_MAX_CERTIFICATE_FILES) >+ fatal("load_certificate_files: too many certificates"); >+ 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); >+ >+ cert = key_load_public(filename, NULL); >+ debug("certificate file %s type %d", filename, >+ cert ? cert->type : -1); >+ free(options.certificate_files[i]); >+ if (cert == NULL) { >+ free(filename); >+ continue; >+ } >+ if (!key_is_cert(cert)) { >+ debug("%s: key %s type %s is not a certificate", >+ __func__, filename, key_type(cert)); >+ key_free(cert); >+ free(filename); >+ continue; >+ } >+ >+ certificate_files[n_ids] = filename; >+ certificates[n_ids] = cert; >+ ++n_ids; >+ } >+ options.num_certificate_files = n_ids; >+ 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)); >+ free(pwdir); >+} >+ >+ >+ > static void > load_public_identity_files(void) > { >diff --git a/ssh.h b/ssh.h >index 4f8da5c..8fb7ba3 100644 >--- a/ssh.h >+++ b/ssh.h >@@ -19,6 +19,13 @@ > #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. > */ >diff --git a/ssh_config.5 b/ssh_config.5 >index e514398..17741b7 100644 >--- a/ssh_config.5 >+++ b/ssh_config.5 >@@ -325,6 +325,34 @@ 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. >+.Xr ssh 1 >+will attempt to use private keys provided as identity files >+or in the agent for such authentication. >+.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 >@@ -911,6 +939,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. >diff --git a/sshconnect2.c b/sshconnect2.c >index 34dbf9a..fb24b5e 100644 >--- a/sshconnect2.c >+++ b/sshconnect2.c >@@ -1016,6 +1016,7 @@ sign_and_send_pubkey(Authctxt *authctxt, Identity *id) > u_int skip = 0; > int ret = -1; > int have_sig = 1; >+ int i; > char *fp; > > if ((fp = sshkey_fingerprint(id->key, options.fingerprint_hash, >@@ -1053,6 +1054,33 @@ sign_and_send_pubkey(Authctxt *authctxt, Identity *id) > } > buffer_put_string(&b, blob, bloblen); > >+ /* If the key is an input certificate, sign its private key instead. >+ * If no such private key exists, return failure and continue with >+ * other methods of authentication. >+ * Else, just continue with the normal signing process. */ >+ if (key_is_cert(id->key)) { >+ for (i = 0; i < options.num_certificate_files; i++) { >+ if (key_equal(id->key, options.certificates[i])) { >+ Identity *id2; >+ int matched = 0; >+ TAILQ_FOREACH(id2, &authctxt->keys, next) { >+ if (sshkey_equal_public(id->key, id2->key) && >+ id->key->type != id2->key->type) { >+ id = id2; >+ matched = 1; >+ break; >+ } >+ } >+ if (!matched) { >+ free(blob); >+ buffer_free(&b); >+ return 0; >+ } >+ break; >+ } >+ } >+ } >+ > /* generate signature */ > ret = identity_sign(id, &signature, &slen, > buffer_ptr(&b), buffer_len(&b), datafellows); >@@ -1189,9 +1217,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) >@@ -1245,6 +1275,17 @@ 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)) >+ 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) >-- >1.9.1 >
You cannot view the attachment while viewing its details because your browser does not support IFRAMEs.
View the attachment on a separate page
.
View Attachment As Diff
View Attachment As Raw
Actions:
View
|
Diff
Attachments on
bug 2436
: 2679 |
2694
|
2700