summaryrefslogtreecommitdiff
path: root/usr.sbin/httpd
diff options
context:
space:
mode:
authorTodd C. Miller <millert@cvs.openbsd.org>2001-10-03 01:24:23 +0000
committerTodd C. Miller <millert@cvs.openbsd.org>2001-10-03 01:24:23 +0000
commit23c1889c495e74261ab1b06638b57444bc0871c3 (patch)
treec8a9da86ef4225fd9799154f560940ac30edf9ce /usr.sbin/httpd
parentd7a890902c58a09d78671df1a002a358d308a11c (diff)
simple keynote module for apache and mod_ssl
Diffstat (limited to 'usr.sbin/httpd')
-rw-r--r--usr.sbin/httpd/Makefile.bsd-wrapper3
-rw-r--r--usr.sbin/httpd/src/Configuration.tmpl5
-rw-r--r--usr.sbin/httpd/src/modules/keynote/Makefile.tmpl14
-rw-r--r--usr.sbin/httpd/src/modules/keynote/mod_keynote.c779
-rw-r--r--usr.sbin/httpd/src/modules/keynote/mod_keynote.module4
5 files changed, 804 insertions, 1 deletions
diff --git a/usr.sbin/httpd/Makefile.bsd-wrapper b/usr.sbin/httpd/Makefile.bsd-wrapper
index dfb84740f58..dacabc04de1 100644
--- a/usr.sbin/httpd/Makefile.bsd-wrapper
+++ b/usr.sbin/httpd/Makefile.bsd-wrapper
@@ -1,5 +1,5 @@
# Build wrapper for Apache
-# $OpenBSD: Makefile.bsd-wrapper,v 1.23 2001/08/06 18:45:03 brad Exp $
+# $OpenBSD: Makefile.bsd-wrapper,v 1.24 2001/10/03 01:24:22 millert Exp $
# Our lndir is hacked; specify a full path to avoid potential conflicts
# with the one installed with X11.
@@ -38,6 +38,7 @@ HTTPD_RUNDIR= ${HTTPD_PREFIX}/logs
HTTPD_LOGDIR= ${HTTPD_PREFIX}/logs
CONFIG_ARGS= --with-layout="OpenBSD" --enable-module="ssl" \
+ --enable-module="keynote" \
--enable-suexec --suexec-caller="www" \
--suexec-docroot="${HTTPD_HTDOCSDIR}" \
--suexec-logfile="/var/log/suexec_log" --suexec-userdir="public_html" \
diff --git a/usr.sbin/httpd/src/Configuration.tmpl b/usr.sbin/httpd/src/Configuration.tmpl
index 71163d636af..56336d0b365 100644
--- a/usr.sbin/httpd/src/Configuration.tmpl
+++ b/usr.sbin/httpd/src/Configuration.tmpl
@@ -496,6 +496,11 @@ AddModule modules/standard/mod_auth.o
AddModule modules/standard/mod_setenvif.o
+## mod_keynote adds RFC 2704 KeyNote-based authentication support.
+## It requires that mod_ssl also be configured in order to function.
+
+# AddModule modules/keynote/mod_keynote.o
+
## mod_ssl incorporates SSL into Apache.
## It must stay last here to be first in execution to
## fake basic authorization.
diff --git a/usr.sbin/httpd/src/modules/keynote/Makefile.tmpl b/usr.sbin/httpd/src/modules/keynote/Makefile.tmpl
new file mode 100644
index 00000000000..97843ae5f70
--- /dev/null
+++ b/usr.sbin/httpd/src/modules/keynote/Makefile.tmpl
@@ -0,0 +1,14 @@
+EXTRA_INCLUDES= -I$(SRCDIR)/modules/ssl
+
+#Dependencies
+
+$(OBJS) $(OBJS_PIC): Makefile
+
+# DO NOT REMOVE
+mod_keynote.o: mod_keynote.c $(INCDIR)/httpd.h \
+ $(INCDIR)/ap_config.h $(INCDIR)/ap_mmn.h \
+ $(INCDIR)/ap_config_auto.h $(OSDIR)/os.h \
+ $(INCDIR)/ap_ctype.h $(INCDIR)/hsregex.h \
+ $(INCDIR)/ap_alloc.h $(INCDIR)/buff.h $(INCDIR)/ap.h \
+ $(INCDIR)/util_uri.h $(INCDIR)/http_config.h \
+ $(INCDIR)/http_log.h $(INCDIR)/util_script.h
diff --git a/usr.sbin/httpd/src/modules/keynote/mod_keynote.c b/usr.sbin/httpd/src/modules/keynote/mod_keynote.c
new file mode 100644
index 00000000000..0264a081971
--- /dev/null
+++ b/usr.sbin/httpd/src/modules/keynote/mod_keynote.c
@@ -0,0 +1,779 @@
+/*
+ * Copyright (c) 1998, 1999 Niels Provos. All rights reserved.
+ * Copyright (c) 1999, 2000, 2001 Niklas Hallqvist. All rights reserved.
+ * Copyright (c) 1999, 2000, 2001 Angelos D. Keromytis. All rights reserved.
+ * Copyright (c) 2001 Todd C. Miller. 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.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by Ericsson Radio Systems.
+ * 4. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * 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 <mod_ssl.h>
+#include <keynote.h>
+
+MODULE_VAR_EXPORT module keynote_module;
+
+/*
+ * This function gets called to create a per-directory configuration
+ * record. This will be called for the "default" server environment, and for
+ * each directory for which the parser finds any of our directives applicable.
+ * If a directory doesn't have any of our directives involved (i.e., they
+ * aren't in the .htaccess file, or a <Location>, <Directory>, or related
+ * block), this routine will *not* be called - the configuration for the
+ * closest ancestor is used.
+ *
+ * The return value is a pointer to the created module-specific
+ * structure.
+ */
+static void *
+create_keynote_dir_config(pool *p, char *d)
+{
+ return(ap_make_array(p, 1, sizeof(char **)));
+}
+
+/*
+ * This function gets called to merge two per-directory configuration
+ * records. This is typically done to cope with things like .htaccess files
+ * or <Location> directives for directories that are beneath one for which a
+ * configuration record was already created. The routine has the
+ * responsibility of creating a new record and merging the contents of the
+ * other two into it appropriately. If the module doesn't declare a merge
+ * routine, the record for the closest ancestor location (that has one) is
+ * used exclusively.
+ *
+ * The routine MUST NOT modify any of its arguments!
+ *
+ * The return value is a pointer to the created module-specific structure
+ * containing the merged values.
+ */
+static void *
+merge_keynote_dir_config(pool *p, void *basev, void *addv)
+{
+ array_header *base = (array_header *)basev;
+ array_header *add = (array_header *)addv;
+
+ return(ap_append_arrays(p, base, add));
+}
+
+/*
+ * Add an action attribute to the environment of the specified session
+ * and log any errors we get, apache style.
+ */
+static void
+add_action_attribute(int sessid, char *name, char *value, request_rec *r)
+{
+
+ if (kn_add_action(sessid, name, value, 0) == 0)
+ return;
+
+ /* Got an error */
+ switch (keynote_errno) {
+ case ERROR_SYNTAX:
+ ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO,
+ r->connection->server,
+ "Invalid action attribute name \"%s\"", name);
+ break;
+ case ERROR_MEMORY:
+ ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO,
+ r->connection->server,
+ "Out of memory adding action attribute [%s = \"%s\"]",
+ name, value);
+ break;
+ case ERROR_NOTFOUND:
+ ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO,
+ r->connection->server,
+ "Session %d not found while adding action attribute "
+ "[%s = \"%s\"]", sessid, name, value);
+ break;
+ default:
+ ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO,
+ r->connection->server, "Unspecified error %d (shouldn't happen)"
+ " while adding action attribute [%s = \"%s\"]", keynote_errno,
+ name, value);
+ break;
+ }
+}
+
+/*
+ * Add action attributes to the environment.
+ * Currently adds:
+ * app_domain -> apache
+ * method -> GET, HEAD, POST, etc.
+ * uri -> the URI that got us here
+ *
+ * XXX should probably add more stuff...
+ * cert expiration date?
+ * current time?
+ */
+static void
+add_action_attributes(int sessid, request_rec *r)
+{
+
+ add_action_attribute(sessid, "app_domain", "apache", r);
+ add_action_attribute(sessid, "method", (char *)r->method, r);
+ /* XXX - make the split URI components available too? */
+ add_action_attribute(sessid, "uri", r->unparsed_uri, r);
+}
+
+static int
+keynote_add_authorizer(request_rec *r, int sessid, X509 *cert)
+{
+ struct keynote_deckey dc;
+ EVP_PKEY *key;
+ X509_NAME *subject;
+ char *akey, *principals[3], *cp;
+ int i;
+
+ key = X509_get_pubkey(cert);
+ subject = X509_get_subject_name(cert);
+ if (!key || (key->type != EVP_PKEY_RSA && key->type != EVP_PKEY_DSA)) {
+ ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, r->connection->server,
+ "Unable to get public key from client's certificate");
+ return(1);
+ }
+
+ /* Get ascii-encoded version of the key and add as an authorizer. */
+ if (key->type == EVP_PKEY_RSA) {
+ dc.dec_algorithm = KEYNOTE_ALGORITHM_RSA;
+ dc.dec_key = key->pkey.rsa;
+ } else {
+ dc.dec_algorithm = KEYNOTE_ALGORITHM_DSA;
+ dc.dec_key = key->pkey.dsa;
+ }
+ akey = kn_encode_key(&dc, INTERNAL_ENC_PKCS1, ENCODING_HEX,
+ KEYNOTE_PUBLIC_KEY);
+ if (akey == NULL) {
+ if (keynote_errno == ERROR_MEMORY) {
+ ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO,
+ r->connection->server, "Out of memory storing public key");
+ return(-1);
+ } else {
+ ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO,
+ r->connection->server, "Error storing public key");
+ return(1);
+ }
+ } else {
+ i = 0;
+ principals[i++] = ap_pstrcat(r->pool, "rsa-hex:", akey, NULL);
+ free(akey);
+
+ /* Generate a "DN:" principal */
+ if (subject && (cp = X509_NAME_oneline(subject, NULL, 0)) != NULL) {
+ principals[i++] = ap_pstrcat(r->pool, "DN:", cp, NULL);
+ free(cp);
+ }
+ principals[i] = NULL;
+ }
+
+ for (i = 0; principals[i]; i++) {
+ if (kn_add_authorizer(sessid, principals[i]) == -1) {
+ switch (keynote_errno) {
+ case ERROR_MEMORY:
+ ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO,
+ r->connection->server,
+ "Out of memory while adding action authorizer %s",
+ principals[i]);
+ break;
+ case ERROR_SYNTAX:
+ ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO,
+ r->connection->server,
+ "Malformed action authorizer %s", principals[i]);
+ break;
+ case ERROR_NOTFOUND:
+ ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO,
+ r->connection->server,
+ "Session %d not found while adding action "
+ "authorizer %s", sessid, principals[i]);
+ default:
+ ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO,
+ r->connection->server,
+ "Unspecified error %d (shouldn't happen) "
+ "while adding action authorizer %s",
+ keynote_errno, principals[i]);
+ break;
+ }
+ }
+ }
+
+ return(0);
+}
+
+static int
+keynote_get_valid_times(request_rec *r, X509 *cert, char *before, char **timecomp, char *after, char **timecomp2)
+{
+ ASN1_TIME *tm;
+ time_t tt;
+ int i;
+
+ if (((tm = X509_get_notBefore(cert)) == NULL) ||
+ (tm->type != V_ASN1_UTCTIME && tm->type != V_ASN1_GENERALIZEDTIME)) {
+ tt = time((time_t) NULL);
+ strftime(before, 14, "%G%m%d%H%M%S", localtime(&tt));
+ *timecomp = "LocalTimeOfDay";
+ } else {
+ if (tm->data[tm->length - 1] == 'Z') {
+ *timecomp = "GMTTimeOfDay";
+ i = tm->length - 2;
+ } else {
+ *timecomp = "LocalTimeOfDay";
+ i = tm->length - 1;
+ }
+
+ for (; i >= 0; i--) {
+ if (tm->data[i] < '0' || tm->data[i] > '9') {
+ ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO,
+ r->connection->server,
+ "Invalid data in certificate's NotValidBefore time field");
+ return(-1);
+ }
+ }
+
+ if (tm->type == V_ASN1_UTCTIME) {
+ if (tm->length < 10 || tm->length > 13) {
+ ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO,
+ r->connection->server,
+ "Invalid length of certificate's NotValidBefore time field (%d)",
+ tm->length);
+ return(-1);
+ }
+
+ /* Validity checks. */
+ if ((tm->data[2] != '0' && tm->data[2] != '1')
+ || (tm->data[2] == '0' && tm->data[3] == '0')
+ || (tm->data[2] == '1' && tm->data[3] > '2')
+ || (tm->data[4] > '3')
+ || (tm->data[4] == '0' && tm->data[5] == '0')
+ || (tm->data[4] == '3' && tm->data[5] > '1')
+ || (tm->data[6] > '2')
+ || (tm->data[6] == '2' && tm->data[7] > '3')
+ || (tm->data[8] > '5')) {
+ ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO,
+ r->connection->server,
+ "Invalid value in certificate's NotValidBefore time field");
+ return(-1);
+ }
+
+ /* Stupid UTC tricks. */
+ if (tm->data[0] < '5')
+ sprintf(before, "20%s", tm->data);
+ else
+ sprintf(before, "19%s", tm->data);
+ } else {
+ /* V_ASN1_GENERICTIME */
+ if (tm->length < 12 || tm->length > 15) {
+ ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO,
+ r->connection->server,
+ "Invalid length of certificate's NotValidBefore time field (%d)",
+ tm->length);
+ return(-1);
+ }
+
+ /* Validity checks. */
+ if ((tm->data[4] != '0' && tm->data[4] != '1')
+ || (tm->data[4] == '0' && tm->data[5] == '0')
+ || (tm->data[4] == '1' && tm->data[5] > '2')
+ || (tm->data[6] > '3')
+ || (tm->data[6] == '0' && tm->data[7] == '0')
+ || (tm->data[6] == '3' && tm->data[7] > '1')
+ || (tm->data[8] > '2')
+ || (tm->data[8] == '2' && tm->data[9] > '3')
+ || (tm->data[10] > '5')) {
+ ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO,
+ r->connection->server,
+ "Invalid value in certificate's NotValidBefore time field");
+ return(-1);
+ }
+ sprintf(before, "%s", tm->data);
+ }
+
+ /* Fix missing seconds. */
+ if (tm->length < 12) {
+ before[12] = '0';
+ before[13] = '0';
+ }
+
+ /* This will overwrite trailing 'Z'. */
+ before[14] = '\0';
+ }
+
+ tm = X509_get_notAfter(cert);
+ if (tm == NULL &&
+ (tm->type != V_ASN1_UTCTIME && tm->type != V_ASN1_GENERALIZEDTIME)) {
+ tt = time(0);
+ strftime(after, 14, "%G%m%d%H%M%S", localtime(&tt));
+ *timecomp2 = "LocalTimeOfDay";
+ } else {
+ if (tm->data[tm->length - 1] == 'Z') {
+ *timecomp2 = "GMTTimeOfDay";
+ i = tm->length - 2;
+ } else {
+ *timecomp2 = "LocalTimeOfDay";
+ i = tm->length - 1;
+ }
+
+ for (; i >= 0; i--) {
+ if (tm->data[i] < '0' || tm->data[i] > '9') {
+ ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO,
+ r->connection->server,
+ "Invalid data in certificate's NotValidAfter time field");
+ return(-1);
+ }
+ }
+
+ if (tm->type == V_ASN1_UTCTIME) {
+ if (tm->length < 10 || tm->length > 13) {
+ ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO,
+ r->connection->server,
+ "Invalid length of certificate's NotValidAfter time field (%d)",
+ tm->length);
+ return(-1);
+ }
+
+ /* Validity checks. */
+ if ((tm->data[2] != '0' && tm->data[2] != '1')
+ || (tm->data[2] == '0' && tm->data[3] == '0')
+ || (tm->data[2] == '1' && tm->data[3] > '2')
+ || (tm->data[4] > '3')
+ || (tm->data[4] == '0' && tm->data[5] == '0')
+ || (tm->data[4] == '3' && tm->data[5] > '1')
+ || (tm->data[6] > '2')
+ || (tm->data[6] == '2' && tm->data[7] > '3')
+ || (tm->data[8] > '5')) {
+ ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO,
+ r->connection->server,
+ "Invalid value in certificate's NotValidAfter time field");
+ return(-1);
+ }
+
+ /* Stupid UTC tricks. */
+ if (tm->data[0] < '5')
+ sprintf(after, "20%s", tm->data);
+ else
+ sprintf(after, "19%s", tm->data);
+ } else {
+ /* V_ASN1_GENERICTIME */
+ if (tm->length < 12 || tm->length > 15) {
+ ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO,
+ r->connection->server,
+ "Invalid length of certificate's NotValidAfter time field (%d)",
+ tm->length);
+ return(-1);
+ }
+
+ /* Validity checks. */
+ if ((tm->data[4] != '0' && tm->data[4] != '1')
+ || (tm->data[4] == '0' && tm->data[5] == '0')
+ || (tm->data[4] == '1' && tm->data[5] > '2')
+ || (tm->data[6] > '3')
+ || (tm->data[6] == '0' && tm->data[7] == '0')
+ || (tm->data[6] == '3' && tm->data[7] > '1')
+ || (tm->data[8] > '2')
+ || (tm->data[8] == '2' && tm->data[9] > '3')
+ || (tm->data[10] > '5')) {
+ ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO,
+ r->connection->server,
+ "Invalid value in certificate's NotValidAfter time field");
+ return(-1);
+ }
+ sprintf(after, "%s", tm->data);
+ }
+
+ /* Fix missing seconds. */
+ if (tm->length < 12) {
+ after[12] = '0';
+ after[13] = '0';
+ }
+ after[14] = '\0'; /* This will overwrite trailing 'Z' */
+ }
+ return(0);
+}
+
+static int
+keynote_fake_assertion(request_rec *r, int sessid, X509 *cert, EVP_PKEY *pkey, X509_NAME *name)
+{
+ struct keynote_deckey dc;
+ EVP_PKEY *key;
+ X509_NAME *issuer, *subject;
+ char *akey, *ikey, *buf, *stext, *itext;
+ char before[15], after[15];
+ char *timecomp, *timecomp2;
+ char *fmt = "Authorizer: \"%s%s\"\nLicensees: \"%s%s\"\n"
+ "Conditions: %s >= \"%s\" && %s <= \"%s\";\n";
+
+ if (pkey && pkey->type != EVP_PKEY_RSA && pkey->type != EVP_PKEY_DSA) {
+ ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, r->connection->server,
+ "Issuer's public key is invalid");
+ return(1);
+ }
+
+ issuer = X509_get_issuer_name(cert);
+ subject = X509_get_subject_name(cert);
+ if (X509_NAME_cmp(issuer, name) != 0) {
+ itext = X509_NAME_oneline(issuer, NULL, 0);
+ stext = X509_NAME_oneline(name, NULL, 0);
+ ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, r->connection->server,
+ "Subject doesn't match issuer's certificate: %s != %s", itext, stext);
+ free(itext);
+ free(stext);
+ return(1);
+ }
+
+ key = X509_get_pubkey(cert);
+ if (!key || (key->type != EVP_PKEY_RSA && key->type != EVP_PKEY_DSA)) {
+ ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, r->connection->server,
+ "Unable to get public key from client's certificate");
+ return(1);
+ }
+
+ /* Get ascii-encoded version of the public key */
+ if (key->type == EVP_PKEY_RSA) {
+ dc.dec_algorithm = KEYNOTE_ALGORITHM_RSA;
+ dc.dec_key = key->pkey.rsa;
+ } else {
+ dc.dec_algorithm = KEYNOTE_ALGORITHM_DSA;
+ dc.dec_key = key->pkey.dsa;
+ }
+ akey = kn_encode_key(&dc, INTERNAL_ENC_PKCS1, ENCODING_HEX,
+ KEYNOTE_PUBLIC_KEY);
+ if (akey == NULL) {
+ if (keynote_errno == ERROR_MEMORY) {
+ ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO,
+ r->connection->server, "Out of memory storing public key");
+ return(-1);
+ } else {
+ ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO,
+ r->connection->server, "Error storing public key");
+ return(1);
+ }
+ }
+
+ /* Get ascii-encoded version of the issuer's public key */
+ if (pkey) {
+ if (pkey->type == EVP_PKEY_RSA) {
+ dc.dec_algorithm = KEYNOTE_ALGORITHM_RSA;
+ dc.dec_key = pkey->pkey.rsa;
+ } else {
+ dc.dec_algorithm = KEYNOTE_ALGORITHM_DSA;
+ dc.dec_key = pkey->pkey.dsa;
+ }
+ ikey = kn_encode_key(&dc, INTERNAL_ENC_PKCS1, ENCODING_HEX,
+ KEYNOTE_PUBLIC_KEY);
+ if (ikey == NULL) {
+ free(akey);
+ if (keynote_errno == ERROR_MEMORY) {
+ ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO,
+ r->connection->server, "Out of memory storing public key");
+ return(-1);
+ } else {
+ ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO,
+ r->connection->server, "Error storing public key");
+ return(1);
+ }
+ }
+ } else
+ ikey = NULL;
+
+ if (keynote_get_valid_times(r, cert, before, &timecomp, after, &timecomp2) == -1) {
+ free(akey);
+ if (ikey)
+ free(ikey);
+ return(-1);
+ }
+
+ itext = X509_NAME_oneline(issuer, NULL, 0);
+ stext = X509_NAME_oneline(subject, NULL, 0);
+
+ if (ikey)
+ buf = ap_psprintf(r->pool, fmt, "rsa-hex:", ikey, "rsa-hex:", akey,
+ timecomp, before, timecomp2, after);
+ else
+ buf = ap_psprintf(r->pool, fmt, "DN:", itext, "rsa-hex:", akey,
+ timecomp, before, timecomp2, after);
+ if (kn_add_assertion(sessid, buf, strlen(buf), ASSERT_FLAG_LOCAL) == -1) {
+ free(stext);
+ free(itext);
+ free(akey);
+ if (ikey)
+ free(ikey);
+ goto assert_failed;
+ }
+
+ buf = ap_psprintf(r->pool, fmt, "DN:", itext, "DN:", stext,
+ timecomp, before, timecomp2, after);
+ free(stext);
+ free(itext);
+ free(akey);
+ if (ikey)
+ free(ikey);
+ if (kn_add_assertion(sessid, buf, strlen(buf), ASSERT_FLAG_LOCAL) != -1)
+ return(0);
+
+assert_failed:
+ switch (keynote_errno) {
+ case ERROR_MEMORY:
+ ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO,
+ r->connection->server,
+ "Out of memory, trying to add policy assertion %s", buf);
+ break;
+ case ERROR_SYNTAX:
+ ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO,
+ r->connection->server, "Syntax error parsing policy assertion %s",
+ buf);
+ break;
+ default:
+ ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO,
+ r->connection->server,
+ "Unspecified error %d (shouldn't happen) "
+ "while adding policy assertion %s", keynote_errno, buf);
+ break;
+ }
+ return(-1);
+}
+
+static int
+check_keynote_assertions(request_rec *r)
+{
+ array_header *policy_asserts = (array_header *)ap_get_module_config(r->per_dir_config, &keynote_module);
+ int sessid, res, i;
+ int rval = OK;
+ char **assertions;
+ SSL_CTX *ctx;
+ SSL *ssl;
+ X509 *cert, *icert;
+ STACK_OF(X509) *certstack;
+ STACK_OF(X509_NAME) *CA_list;
+ X509_NAME *issuer, *subject;
+ static char *return_values[] = { "false", "true" };
+
+ /* If there are no KeyNote assertions we have nothing to do. */
+ if (policy_asserts->nelts == 0)
+ return(DECLINED);
+
+ /* If this is not an SSL'd connection we deny them. */
+ if ((ssl = ap_ctx_get(r->connection->client->ctx, "ssl")) == NULL)
+ return(FORBIDDEN);
+ ctx = SSL_get_SSL_CTX(ssl);
+
+ /* Get client's certificate or deny them */
+ certstack = SSL_get_peer_cert_chain(ssl);
+ if ((cert = SSL_get_peer_certificate(ssl)) == NULL)
+ return(FORBIDDEN);
+
+ /* Missing or self-signed, deny them */
+ issuer = X509_get_issuer_name(cert);
+ subject = X509_get_subject_name(cert);
+ if (!issuer || !subject || X509_name_cmp(issuer, subject) == NULL)
+ return(FORBIDDEN);
+
+ /* Initialize keynote and setup our environment */
+ sessid = kn_init();
+ add_action_attributes(sessid, r);
+
+ /* Build a set of fake assertions corresponding to the certificate chain. */
+ for (i = 0; i < sk_X509_num(certstack) && (icert = sk_X509_value(certstack, i)); i++) {
+ if (keynote_fake_assertion(r, sessid, cert, X509_get_pubkey(icert), X509_get_subject_name(icert)) == -1)
+ return(FORBIDDEN);
+ cert = icert;
+ }
+
+ /* The issuer of the last cert in the chain should be in the CA list. */
+ issuer = X509_get_issuer_name(cert);
+ CA_list = SSL_CTX_get_client_CA_list(ctx);
+ for (i = 0; i < sk_X509_num(CA_list); i++) {
+ subject = sk_X509_NAME_value(CA_list, i);
+ if (subject && X509_NAME_cmp(issuer, subject) == 0) {
+ /* An X509_NAME does not contain the public key */
+ if (keynote_fake_assertion(r, sessid, cert, NULL, subject) == -1)
+ return(FORBIDDEN);
+ break;
+ }
+ }
+ if (i >= sk_X509_num(CA_list))
+ ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, r->connection->server,
+ "didn't find CA for issuer of last cert in chain");
+
+ /* Add our policy assertions (as specified in the config file). */
+ assertions = (char **)policy_asserts->elts;
+ for (i = 0; i < policy_asserts->nelts; i++) {
+ if (kn_add_assertion(sessid, assertions[i],
+ strlen(assertions[i]), ASSERT_FLAG_LOCAL) == -1) {
+ switch (keynote_errno) {
+ case ERROR_MEMORY:
+ ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO,
+ r->connection->server,
+ "Out of memory, trying to add policy assertion %s",
+ assertions[i]);
+ break;
+ case ERROR_SYNTAX:
+ ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO,
+ r->connection->server, "Syntax error "
+ "parsing policy assertion %s", assertions[i]);
+ break;
+ default:
+ ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO,
+ r->connection->server,
+ "Unspecified error %d (shouldn't happen) "
+ "while adding policy assertion %s",
+ keynote_errno, assertions[i]);
+ break;
+ }
+ rval = FORBIDDEN;
+ goto done;
+ }
+ }
+
+ /* Add the user's public key as an authorizer. */
+ if (keynote_add_authorizer(r, sessid, cert) == -1)
+ return(FORBIDDEN);
+
+ /* Now do the actual query. */
+ switch ((res = kn_do_query(sessid, return_values, 2))) {
+ case 0:
+ rval = FORBIDDEN;
+
+ /* Log failed assertions */
+ for (i = 0; i < policy_asserts->nelts; i++) {
+ if (kn_get_failed(sessid, KEYNOTE_ERROR_SYNTAX, i) != -1) {
+ ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO,
+ r->connection->server, "Assertion failed "
+ "due to a syntax error: %s", assertions[i]);
+ } else if (kn_get_failed(sessid, KEYNOTE_ERROR_SIGNATURE, i) != -1) {
+ ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO,
+ r->connection->server, "Failed to verify "
+ "signature on assertion: %s", assertions[i]);
+ } else if (kn_get_failed(sessid, KEYNOTE_ERROR_ANY, i) != -1) {
+ ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO,
+ r->connection->server, "Unspecified error "
+ "when processing assertion: %s", assertions[i]);
+ }
+ }
+ break;
+ case 1:
+ rval = OK;
+ break;
+ case -1:
+ rval = FORBIDDEN;
+ switch (keynote_errno) {
+ case ERROR_MEMORY:
+ ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO,
+ r->connection->server,
+ "Out of memory while performing authorization "
+ "query.");
+ break;
+ case ERROR_NOTFOUND:
+ ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO,
+ r->connection->server,
+ "Session %d not found while performing "
+ "authorization query.", sessid);
+ break;
+ default:
+ ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO,
+ r->connection->server,
+ "Unspecified error %d (shouldn't happen) while "
+ "performing authorization query.", keynote_errno);
+ break;
+ }
+default:
+ ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO,
+ r->connection->server, "Weird KeyNote result=%d", res);
+ break;
+ }
+
+done:
+ kn_close(sessid);
+
+ return(rval);
+}
+
+/*
+ * Take an assertion stored in a file and push it (verbatim) into
+ * the policy_asserts array.
+ */
+static const char *
+store_assertion(cmd_parms *cmd, void *policy_assertsv, char *filename)
+{
+ int fd, serrno;
+ ssize_t nread;
+ struct stat sb;
+ char *assert;
+ array_header *policy_asserts = (array_header *)policy_assertsv;
+
+ filename = ap_server_root_relative(cmd->pool, filename);
+ if ((fd = open(filename, O_RDONLY)) == -1)
+ return(ap_pstrcat(cmd->pool, "Can't open ", filename, ": ",
+ strerror(errno), NULL));
+
+ if (fstat(fd, &sb) == -1)
+ return(ap_pstrcat(cmd->pool, "Can't fstat ", filename, ": ",
+ strerror(errno), NULL));
+
+ assert = ap_pcalloc(cmd->pool, sb.st_size + 1);
+ nread = read(fd, assert, sb.st_size);
+ serrno = errno;
+ close(fd);
+ if (nread != sb.st_size) {
+ if (nread == -1)
+ return(ap_pstrcat(cmd->pool, "Can't read ", filename, ": ",
+ strerror(serrno), NULL));
+ else
+ return(ap_pstrcat(cmd->pool, "Short read from", filename, NULL));
+ }
+
+ /* Now store the assertion in the array */
+ *(char **)ap_push_array(policy_asserts) = assert;
+
+ return(NULL);
+}
+
+static command_rec keynote_cmds[] = {
+ {
+ "KeyNotePolicy", /* directive name */
+ store_assertion, /* config action routine */
+ NULL, /* arg to include in call */
+ OR_FILEINFO, /* where available (FileInfo) */
+ ITERATE, /* call once for each arg */
+ "Add a KeyNote policy file" /* directive description */
+ },
+ { NULL }
+};
+
+module MODULE_VAR_EXPORT keynote_module =
+{
+ STANDARD_MODULE_STUFF,
+ NULL, /* module initializer */
+ create_keynote_dir_config, /* per-directory config creator */
+ merge_keynote_dir_config, /* dir config merger */
+ NULL, /* server config creator */
+ NULL, /* server config merger */
+ keynote_cmds, /* command table */
+ NULL, /* list of handlers */
+ NULL, /* filename-to-URI translation */
+ NULL, /* check/validate user_id */
+ NULL, /* check user_id is valid *here* */
+ check_keynote_assertions, /* check access by host address */
+ NULL, /* MIME type checker/setter */
+ NULL, /* fixups */
+ NULL, /* logger */
+};
diff --git a/usr.sbin/httpd/src/modules/keynote/mod_keynote.module b/usr.sbin/httpd/src/modules/keynote/mod_keynote.module
new file mode 100644
index 00000000000..44d8e036c30
--- /dev/null
+++ b/usr.sbin/httpd/src/modules/keynote/mod_keynote.module
@@ -0,0 +1,4 @@
+Name: keynote_module
+ConfigStart
+ LIBS="$LIBS -lkeynote -lm"
+ConfigEnd