You are viewing a plain text version of this content. The canonical link for it is here.
Posted to cvs@httpd.apache.org by mi...@apache.org on 2023/04/25 17:52:18 UTC

svn commit: r1909411 - in /httpd/httpd/trunk: ./ docs/manual/mod/ modules/aaa/

Author: minfrin
Date: Tue Apr 25 17:52:18 2023
New Revision: 1909411

URL: http://svn.apache.org/viewvc?rev=1909411&view=rev
Log:
  *) mod_autht_jwt: New module to handle RFC 7519 JWT tokens within
     bearer tokens, both as part of the aaa framework, and as a way to
     generate tokens and pass them to backend servers and services.

  *) mod_auth_bearer: New module to handle RFC 6750 Bearer tokens, using
     the token_checker hook.

  *) mod_autht_core: New module to handle provider aliases for token
     authentication.


Added:
    httpd/httpd/trunk/docs/manual/mod/mod_auth_bearer.xml
    httpd/httpd/trunk/docs/manual/mod/mod_autht_core.xml
    httpd/httpd/trunk/docs/manual/mod/mod_autht_jwt.xml
    httpd/httpd/trunk/modules/aaa/mod_auth_bearer.c
    httpd/httpd/trunk/modules/aaa/mod_autht_core.c
    httpd/httpd/trunk/modules/aaa/mod_autht_jwt.c
Modified:
    httpd/httpd/trunk/CHANGES
    httpd/httpd/trunk/modules/aaa/config.m4

Modified: httpd/httpd/trunk/CHANGES
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/CHANGES?rev=1909411&r1=1909410&r2=1909411&view=diff
==============================================================================
--- httpd/httpd/trunk/CHANGES [utf-8] (original)
+++ httpd/httpd/trunk/CHANGES [utf-8] Tue Apr 25 17:52:18 2023
@@ -1,6 +1,17 @@
                                                          -*- coding: utf-8 -*-
 Changes with Apache 2.5.1
 
+  *) mod_autht_jwt: New module to handle RFC 7519 JWT tokens within
+     bearer tokens, both as part of the aaa framework, and as a way to
+     generate tokens and pass them to backend servers and services.
+     [Graham Leggett]
+
+  *) mod_auth_bearer: New module to handle RFC 6750 Bearer tokens, using
+     the token_checker hook. [Graham Leggett]
+
+  *) mod_autht_core: New module to handle provider aliases for token
+     authentication. [Graham Leggett]
+
   *) core: Add the token_checker hook, that allows authentication to take
      place using mechanisms other than username/password, such as bearer
      tokens. [Graham Leggett]

Added: httpd/httpd/trunk/docs/manual/mod/mod_auth_bearer.xml
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/docs/manual/mod/mod_auth_bearer.xml?rev=1909411&view=auto
==============================================================================
--- httpd/httpd/trunk/docs/manual/mod/mod_auth_bearer.xml (added)
+++ httpd/httpd/trunk/docs/manual/mod/mod_auth_bearer.xml Tue Apr 25 17:52:18 2023
@@ -0,0 +1,160 @@
+<?xml version="1.0"?>
+<!DOCTYPE modulesynopsis SYSTEM "../style/modulesynopsis.dtd">
+<?xml-stylesheet type="text/xsl" href="../style/manual.en.xsl"?>
+<!-- $LastChangedRevision: 1907762 $ -->
+
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements.  See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License.  You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<modulesynopsis metafile="mod_auth_bearer.xml.meta">
+
+<name>mod_auth_bearer</name>
+<description>Bearer HTTP authentication</description>
+<status>Base</status>
+<sourcefile>mod_auth_bearer.c</sourcefile>
+<identifier>auth_bearer_module</identifier>
+
+<summary>
+    <p>This module allows the use of HTTP Bearer Authentication to
+    restrict access by passing the bearer token to the given providers.
+    This module should be combined with at least one token module
+    such as <module>mod_autht_jwt</module> and one authorization
+    module such as <module>mod_authz_user</module>.</p>
+</summary>
+<seealso><directive module="mod_authn_core">AuthName</directive></seealso>
+<seealso><directive module="mod_authn_core">AuthType</directive></seealso>
+<seealso><directive module="mod_authz_core">Require</directive></seealso>
+<seealso><a href="../howto/auth.html">Authentication howto</a></seealso>
+
+<directivesynopsis>
+<name>AuthBearerProvider</name>
+<description>Sets the authentication provider(s) for this location</description>
+<syntax>AuthBearerProvider <var>provider-name</var>
+[<var>provider-name</var>] ...</syntax>
+<default>AuthBearerProvider file</default>
+<contextlist><context>directory</context><context>.htaccess</context>
+</contextlist>
+<override>AuthConfig</override>
+
+<usage>
+    <p>The <directive>AuthBearerProvider</directive> directive sets
+    which provider is used to verify tokens for this location.
+    The default <code>jwt</code> provider is implemented
+    by the <module>mod_autht_jwt</module> module.  Make sure
+    that the chosen provider module is present in the server.</p>
+    <example><title>Example</title>
+    <highlight language="config">
+&lt;Location "/secure"&gt;
+    AuthType bearer
+    AuthName "private area"
+    AuthBearerProvider jwt
+    AuthtJwtVerify hs256 file "/www/etc/jwt.secret"
+    Require            valid-user
+&lt;/Location&gt;
+    </highlight>
+    </example>
+    <p>Providers are queried in order until a provider finds a match
+    for the requested token. This usually means that the token has been
+    correctly signed, or that the token has not expired.</p>
+
+    <p>The first implemented provider is <module>mod_autht_jwt</module>.</p>
+</usage>
+</directivesynopsis>
+
+<directivesynopsis>
+<name>AuthBearerAuthoritative</name>
+<description>Sets whether token verification is passed to lower level
+modules</description>
+<syntax>AuthBearerAuthoritative On|Off</syntax>
+<default>AuthBearerAuthoritative On</default>
+<contextlist><context>directory</context><context>.htaccess</context>
+</contextlist>
+<override>AuthConfig</override>
+
+<usage>
+    <p>Normally, each token verification module listed in <directive
+    module="mod_auth_bearer">AuthBearerProvider</directive> will attempt
+    to verify the token, and if the token is not found to be valid,
+    access will be denied. Setting the
+    <directive>AuthBearerAuthoritative</directive> directive explicitly
+    to <code>Off</code> allows for token verification to be passed on to
+    other non-provider-based modules if the token is not recognised.
+    This should only be necessary when combining
+    <module>mod_auth_bearer</module> with third-party modules that are not
+    configured with the
+    <directive module="mod_auth_bearer">AuthBearerProvider</directive>
+    directive.  When using such modules, the order of processing
+    is determined in the modules' source code and is not configurable.</p>
+</usage>
+</directivesynopsis>
+
+<directivesynopsis>
+<name>AuthBearerProxy</name>
+<description>Pass a bearer authentication token over a proxy connection
+generated using the given expression</description>
+<syntax>AuthBearerProxy off|<var>expression</var></syntax>
+<default>none</default>
+<contextlist><context>directory</context><context>.htaccess</context>
+</contextlist>
+<override>AuthConfig</override>
+<compatibility>Apache HTTP Server 2.5.1 and later</compatibility>
+<usage>
+    <p>The expression specified is passed as a bearer token in the
+    Authorization header, which is passed to the server or service
+    behind the webserver. The expression is interpreted using the
+    <a href="../expr.html">expression parser</a>, which allows the
+    token to be set based on request parameters.</p>
+
+    <note>
+    The Authorization header added by this directive is <em>not</em>
+    input into any authentication or authorization within the local 
+    server.  It is designed to be passed along to upstream servers.
+    </note>
+
+    <p>In this example, we pass a fixed token to a backend server.</p>
+
+    <example><title>Fixed Example</title>
+    <highlight language="config">
+&lt;Location "/demo"&gt;
+    AuthBearerProxy my-fixed-token
+&lt;/Location&gt;
+    </highlight>
+    </example>
+
+    <p>In this example, we pass the query string as the token to the
+    backend server.</p>
+
+    <example><title>Query String Example</title>
+    <highlight language="config">
+&lt;Location "/secure"&gt;
+    AuthBearerProxy "%{QUERY_STRING}"
+&lt;/Location&gt;
+    </highlight>
+    </example>
+
+    <example><title>Exclusion Example</title>
+    <highlight language="config">
+&lt;Location "/public"&gt;
+    AuthBearerProxy off
+&lt;/Location&gt;
+    </highlight>
+    </example>
+
+</usage>
+</directivesynopsis>
+
+</modulesynopsis>

Added: httpd/httpd/trunk/docs/manual/mod/mod_autht_core.xml
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/docs/manual/mod/mod_autht_core.xml?rev=1909411&view=auto
==============================================================================
--- httpd/httpd/trunk/docs/manual/mod/mod_autht_core.xml (added)
+++ httpd/httpd/trunk/docs/manual/mod/mod_autht_core.xml Tue Apr 25 17:52:18 2023
@@ -0,0 +1,104 @@
+<?xml version="1.0"?>
+<!DOCTYPE modulesynopsis SYSTEM "../style/modulesynopsis.dtd">
+<?xml-stylesheet type="text/xsl" href="../style/manual.en.xsl"?>
+<!-- $LastChangedRevision: 1834267 $ -->
+
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements.  See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License.  You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<modulesynopsis metafile="mod_autht_core.xml.meta">
+
+<name>mod_autht_core</name>
+<description>Core Token Handling</description>
+<status>Base</status>
+<sourcefile>mod_autht_core.c</sourcefile>
+<identifier>autht_core_module</identifier>
+<compatibility>Available in Apache 2.5 and later</compatibility>
+
+<summary>
+    <p>This module provides core token handling capabilities to
+    allow or deny access to portions of the web site.
+    <module>mod_autht_core</module> provides directives that are
+    common to all token providers.</p>
+</summary>
+
+<section id="authtalias"><title>Creating Token Provider Aliases</title>
+
+    <p>Extended token providers can be created within the configuration
+    file and assigned an alias name.  The alias providers can then be
+    referenced through the
+    <directive module="mod_auth_bearer">AuthBearerProvider</directive>
+    directive in the same way as a base token provider.  Besides the
+    ability to create and alias an extended provider, it also allows
+    the same extended token provider to be reference by multiple
+    locations.</p>
+
+    <section id="example"><title>Examples</title>
+
+        <p>This example checks for JWT token signatures in two different
+        secret files.</p>
+
+        <example><title>Checking multiple sources for JWT tokens</title>
+        <highlight language="config">
+# Check here first
+&lt;AuthtProviderAlias jwt jwt1&gt;
+    AuthtJwtVerify hs256 file "/www/conf/realm1.secret"
+&lt;/AuthtProviderAlias&gt;
+
+# Then check here
+&lt;AuthtProviderAlias jwt jwt2&gt;
+    AuthtJwtVerify hs256 file "/www/conf/realm2.secret"
+&lt;/AuthtProviderAlias&gt;
+
+&lt;Directory "/var/web/pages/secure"&gt;
+    AuthBearerProvider jwt1 jwt2
+
+    AuthType Basic
+    AuthName "Protected Area"
+    Require valid-user
+&lt;/Directory&gt;
+        </highlight>
+        </example>
+
+    </section>
+
+</section>
+
+<directivesynopsis type="section">
+<name>AuthtProviderAlias</name>
+<description>Enclose a group of directives that represent an
+extension of a base token provider and referenced by the specified
+alias</description>
+<syntax>&lt;AuthtProviderAlias <var>baseProvider Alias</var>&gt;
+... &lt;/AuthtProviderAlias&gt;</syntax>
+<contextlist><context>server config</context>
+</contextlist>
+
+<usage>
+    <p><code>&lt;AuthtProviderAlias&gt;</code> and
+    <code>&lt;/AuthtProviderAlias&gt;</code> are used to enclose a group of
+    authentication directives that can be referenced by the alias name
+    using the
+    <directive module="mod_auth_bearer">AuthBearerProvider</directive>.</p>
+
+    <note>This directive has no affect on authentication or authorization,
+    even for modules that provide both authentication and authorization
+    in addition to token handling.</note>
+</usage>
+</directivesynopsis>
+
+</modulesynopsis>

Added: httpd/httpd/trunk/docs/manual/mod/mod_autht_jwt.xml
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/docs/manual/mod/mod_autht_jwt.xml?rev=1909411&view=auto
==============================================================================
--- httpd/httpd/trunk/docs/manual/mod/mod_autht_jwt.xml (added)
+++ httpd/httpd/trunk/docs/manual/mod/mod_autht_jwt.xml Tue Apr 25 17:52:18 2023
@@ -0,0 +1,216 @@
+<?xml version="1.0"?>
+<!DOCTYPE modulesynopsis SYSTEM "../style/modulesynopsis.dtd">
+<?xml-stylesheet type="text/xsl" href="../style/manual.en.xsl"?>
+<!-- $LastChangedRevision: 1421821 $ -->
+
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements.  See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License.  You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<modulesynopsis metafile="mod_autht_jwt.xml.meta">
+
+<name>mod_autht_jwt</name>
+<description>Token authentication using JWT tokens</description>
+<status>Base</status>
+<sourcefile>mod_autht_jwt.c</sourcefile>
+<identifier>autht_jwt_module</identifier>
+
+<summary>
+    <p>This module provides token parsing front-ends such as
+    <module>mod_auth_bearer</module> the ability to authenticate users
+    by verifying a JWT token as described in
+    <a href="http://www.ietf.org/rfc/rfc7519.txt">RFC 7519</a>.</p>
+
+    <p>A JWT token is read from the <var>Authorization</var> header
+    with an <var>auth-scheme</var> of <var>Bearer</var>.</p>
+
+    <p>When using <module>mod_auth_bearer</module> this module is invoked
+    via the
+    <directive module="mod_auth_bearer">AuthBearerProvider</directive>
+    with the <code>jwt</code> value.</p>
+
+    <p>This module can also be used standalone to generate JWT tokens
+    for passing to a backend server or service. Claims are embedded within
+    a token, which is then optionally signed, and passed using the
+    <var>Authorization</var> header as a <var>Bearer</var> token.</p>
+</summary>
+<seealso>
+  <directive module="mod_auth_bearer">AuthBearerProvider</directive>
+</seealso>
+
+<directivesynopsis>
+<name>AuthtJwtDriver</name>
+<description>Sets the name of the underlying crypto driver to
+use</description>
+<syntax>AuthtJwtDriver <var>name</var> <var>[param[=value]]</var></syntax>
+<contextlist><context>server config</context>
+<context>virtual host</context></contextlist>
+
+<usage>
+    <p>The <directive>AuthtJwtDriver</directive> directive specifies the name of
+    the crypto driver to be used for signing and verification. If not specified,
+    the driver defaults to the recommended driver compiled into APR-util.</p>
+
+    <p>Follow the instructions in the
+    <directive module="mod_session_crypto">SessionCryptoDriver</directive> to
+    set up the driver.</p>
+</usage>
+</directivesynopsis>
+
+<directivesynopsis>
+<name>AuthtJwtVerify</name>
+<description>The JWS signing algorithm and passphrase/key to verify an incoming
+JWT token</description>
+<syntax>AuthtJwtVerify <var>algorithm</var> [<var>type</var> <var>param</var>]</syntax>
+<contextlist><context>directory</context><context>.htaccess</context>
+</contextlist>
+<override>AuthConfig</override>
+
+<usage>
+    <p>The <directive>AuthtJwtVerify</directive> directive specifies the algorithm
+    and secret used to verify incoming bearer tokens.</p>
+
+    <p>If the algorithm type <var>none</var> is selected, the token is not
+    protected, and will be accepted as is. Use only when the client is trusted, and the
+    channel is protected through other means, such as mutually authenticated TLS, or
+    unix domain sockets.</p>
+
+    <p>If present, the <var>sub</var> claim is assigned to REMOTE_USER.</p>
+
+    <example><title>No Verification Example</title>
+    <highlight language="config">
+&lt;Location "/mutual-tls-secured"&gt;
+  AuthType bearer
+  AuthName example-name
+  AuthBearerProvider jwt
+  AuthtJwtVerify none
+  Require valid-user
+&lt;/Location&gt;
+    </highlight>
+    </example>
+
+    <p>If the algorithm type <var>HS256</var> is used, the algorithm is set to
+    <var>HMAC-SHA256</var>, and the secret is set within the <var>file</var> specified
+    as the third parameter. The contents of the bearer token is still visible, and so
+    the channel must still be protected from evesdropping through TLS.</p>
+
+    <p>If the signature is verified, and if present, the <var>sub</var> claim is
+    assigned to REMOTE_USER.</p>
+
+    <example><title>Verification Example</title>
+    <highlight language="config">
+&lt;Location "/secure"&gt;
+  AuthType bearer
+  AuthName example-name
+  AuthBearerProvider jwt
+  AuthtJwtVerify hs256 file "/www/conf/jwt.secret"
+  Require valid-user
+&lt;/Location&gt;
+    </highlight>
+    </example>
+
+</usage>
+</directivesynopsis>
+
+<directivesynopsis>
+<name>AuthtJwtSign</name>
+<description>The JWS signing algorithm and passphrase/key to sign an outgoing
+JWT token</description>
+<syntax>AuthtJwtSign <var>algorithm</var> [<var>type</var> <var>param</var>]</syntax>
+<contextlist><context>directory</context><context>.htaccess</context>
+</contextlist>
+<override>AuthConfig</override>
+
+<usage>
+    <p>The <directive>AuthtJwtSign</directive> directive specifies the algorithm
+    and secret used to sign outgoing bearer tokens passed to a server or service.</p>
+
+    <p>If the algorithm type <var>none</var> is selected, the token is not
+    protected. Use only when the client is trusted, and the channel is protected
+    through other means, such as mutually authenticated TLS, or unix domain sockets.</p>
+
+    <p>Set the claims to be sent in the token using the
+    <directive module="mod_autht_jwt">AuthtJwtClaim</directive> directive. The
+    <var>sub</var> claim is used to pass the remote user.</p>
+
+    <example><title>No Verification Example</title>
+    <highlight language="config">
+&lt;Location "/mutual-tls-secured"&gt;
+  AuthtJwtClaim set sub %{REMOTE_USER}
+  AuthtJwtSign none
+&lt;/Location&gt;
+    </highlight>
+    </example>
+
+    <p>If the algorithm type <var>HS256</var> is used, the algorithm is set to
+    <var>HMAC-SHA256</var>, and the secret is set within the <var>file</var> specified
+    as the third parameter. The contents of the bearer token is still visible, and so
+    the channel must still be protected from evesdropping through TLS.</p>
+
+    <example><title>Verification Example</title>
+    <highlight language="config">
+&lt;Location "/secure"&gt;
+  AuthtJwtClaim set sub %{REMOTE_USER}
+  AuthtJwtSign hs256 file "/www/conf/jwt.secret"
+&lt;/Location&gt;
+    </highlight>
+    </example>
+
+</usage>
+</directivesynopsis>
+
+<directivesynopsis>
+<name>AuthtJwtClaim</name>
+<description>Set a claim with the given name and expression, or unset the claim with the given name</description>
+<syntax>AuthtJwtVerify <var>[set|unset]</var> <var>name</var> [<var>value</var>]</syntax>
+<contextlist><context>directory</context><context>.htaccess</context>
+</contextlist>
+<override>AuthConfig</override>
+
+<usage>
+    <p>The <directive>AuthtJwtClaim</directive> directive adds and/or removes
+    claims from token being passed to the backend server or service.</p>
+
+    <p>When a claim is set, the value of the claim is the result of an expression. The
+    expression may include parameters from a digital certificate, or the name of the
+    user that has been authenticated to Apache httpd.</p>
+
+    <example><title>Pass Remote User Example</title>
+    <highlight language="config">
+&lt;Location "/secure"&gt;
+  AuthtJwtClaim set sub %{REMOTE_USER}
+  AuthtJwtSign hs256 file "/www/conf/jwt.secret"
+&lt;/Location&gt;
+    </highlight>
+    </example>
+
+    <p>When a claim is unset, the claim previously set is removed from the token.</p>
+
+    <example><title>Unset Claim Example</title>
+    <highlight language="config">
+AuthtJwtClaim set my-claim present
+&lt;Location "/secure"&gt;
+  AuthtJwtClaim set sub %{REMOTE_USER}
+  AuthtJwtClaim unset my-claim
+  AuthtJwtSign hs256 file "/www/conf/jwt.secret"
+&lt;/Location&gt;
+    </highlight>
+    </example>
+
+</usage>
+</directivesynopsis>
+
+</modulesynopsis>

Modified: httpd/httpd/trunk/modules/aaa/config.m4
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/aaa/config.m4?rev=1909411&r1=1909410&r2=1909411&view=diff
==============================================================================
--- httpd/httpd/trunk/modules/aaa/config.m4 (original)
+++ httpd/httpd/trunk/modules/aaa/config.m4 Tue Apr 25 17:52:18 2023
@@ -6,6 +6,16 @@ dnl APACHE_MODULE(name, helptext[, objec
 
 APACHE_MODPATH_INIT(aaa)
 
+dnl Token modules: modules that parse or reference a token, that may
+dnl contain or reference further data like usernames, or IP addresses.
+dnl
+APACHE_MODULE(autht_jwt, RFC7519 JSON Web Token based authentication control, , , most)
+
+dnl General Authentication modules; module which implements the 
+dnl non-autht module specific directives.
+dnl
+APACHE_MODULE(autht_core, core token authentication module, , , yes)
+
 dnl Authentication modules; modules checking a username and password against a
 dnl file, database, or other similar magic.
 dnl
@@ -67,6 +77,7 @@ APACHE_MODULE(access_compat, mod_access
 dnl these are the front-end authentication modules
 
 APACHE_MODULE(auth_basic, basic authentication, , , yes)
+APACHE_MODULE(auth_bearer, bearer authentication, , , yes)
 APACHE_MODULE(auth_form, form authentication, , , most)
 APACHE_MODULE(auth_digest, RFC2617 Digest authentication, , , most, [
   APR_CHECK_APR_DEFINE(APR_HAS_RANDOM)

Added: httpd/httpd/trunk/modules/aaa/mod_auth_bearer.c
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/aaa/mod_auth_bearer.c?rev=1909411&view=auto
==============================================================================
--- httpd/httpd/trunk/modules/aaa/mod_auth_bearer.c (added)
+++ httpd/httpd/trunk/modules/aaa/mod_auth_bearer.c Tue Apr 25 17:52:18 2023
@@ -0,0 +1,422 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * This module adds support for https://tools.ietf.org/html/rfc6750 Bearer
+ * tokens, both as a generator of bearer tokens, and as an acceptor of
+ * Bearer tokens for authentication.
+ */
+
+#include "apr_strings.h"
+#include "apr_hash.h"
+#include "apr_lib.h"            /* for apr_isspace */
+#define APR_WANT_STRFUNC        /* for strcasecmp */
+#include "apr_want.h"
+
+#include "ap_config.h"
+#include "httpd.h"
+#include "http_config.h"
+#include "http_core.h"
+#include "http_log.h"
+#include "http_protocol.h"
+#include "http_request.h"
+#include "ap_provider.h"
+#include "ap_expr.h"
+
+#include "mod_auth.h"
+
+module AP_MODULE_DECLARE_DATA auth_bearer_module;
+
+typedef struct {
+    autht_provider_list *providers;
+    int authoritative;
+    ap_expr_info_t *proxy;
+    int authoritative_set:1;
+    int proxy_set:1;
+} auth_bearer_config_rec;
+
+static void *create_auth_bearer_dir_config(apr_pool_t *p, char *d)
+{
+    auth_bearer_config_rec *conf = apr_pcalloc(p, sizeof(*conf));
+
+    /* Any failures are fatal. */
+    conf->authoritative = 1;
+
+    return conf;
+}
+
+static void *merge_auth_bearer_dir_config(apr_pool_t *p, void *basev, void *overridesv)
+{
+    auth_bearer_config_rec *newconf = apr_pcalloc(p, sizeof(*newconf));
+    auth_bearer_config_rec *base = basev;
+    auth_bearer_config_rec *overrides = overridesv;
+
+    newconf->authoritative =
+            overrides->authoritative_set ? overrides->authoritative :
+                    base->authoritative;
+    newconf->authoritative_set = overrides->authoritative_set
+            || base->authoritative_set;
+
+    newconf->providers = overrides->providers ? overrides->providers : base->providers;
+
+    newconf->proxy =
+            overrides->proxy_set ? overrides->proxy : base->proxy;
+    newconf->proxy_set = overrides->proxy_set || base->proxy_set;
+
+    return newconf;
+}
+
+static const char *add_autht_provider(cmd_parms *cmd, void *config,
+                                      const char *arg)
+{
+    auth_bearer_config_rec *conf = (auth_bearer_config_rec*)config;
+    autht_provider_list *newp;
+
+    newp = apr_pcalloc(cmd->pool, sizeof(autht_provider_list));
+    newp->provider_name = arg;
+
+    /* lookup and cache the actual provider now */
+    newp->provider = ap_lookup_provider(AUTHT_PROVIDER_GROUP,
+                                        newp->provider_name,
+                                        AUTHT_PROVIDER_VERSION);
+
+    if (newp->provider == NULL) {
+        /* by the time they use it, the provider should be loaded and
+           registered with us. */
+        return apr_psprintf(cmd->pool,
+                            "Unknown Autht provider: %s",
+                            newp->provider_name);
+    }
+
+    if (!newp->provider->check_token) {
+        /* if it doesn't provide the appropriate function, reject it */
+        return apr_psprintf(cmd->pool,
+                            "The '%s' Autht provider doesn't support "
+                            "Bearer Authentication", newp->provider_name);
+    }
+
+    /* Add it to the list now. */
+    if (!conf->providers) {
+        conf->providers = newp;
+    }
+    else {
+        autht_provider_list *last = conf->providers;
+
+        while (last->next) {
+            last = last->next;
+        }
+        last->next = newp;
+    }
+
+    return NULL;
+}
+
+static const char *set_authoritative(cmd_parms * cmd, void *config, int flag)
+{
+    auth_bearer_config_rec *conf = (auth_bearer_config_rec *) config;
+
+    conf->authoritative = flag;
+    conf->authoritative_set = 1;
+
+    return NULL;
+}
+
+static const char *set_bearer_proxy(cmd_parms * cmd, void *config,
+        const char *user)
+{
+    auth_bearer_config_rec *conf = (auth_bearer_config_rec *) config;
+    const char *err;
+
+    if (!strcasecmp(user, "off")) {
+        conf->proxy = NULL;
+        conf->proxy_set = 1;
+    }
+    else {
+
+        conf->proxy =
+                ap_expr_parse_cmd(cmd, user, AP_EXPR_FLAG_STRING_RESULT,
+                        &err, NULL);
+        if (err) {
+            return apr_psprintf(cmd->pool,
+                    "Could not parse proxy expression '%s': %s", user,
+                    err);
+        }
+        conf->proxy_set = 1;
+    }
+
+    return NULL;
+}
+
+static const command_rec auth_bearer_cmds[] =
+{
+    AP_INIT_ITERATE("AuthBearerProvider", add_autht_provider, NULL, OR_AUTHCFG,
+                    "specify the auth providers for a directory or location"),
+    AP_INIT_FLAG("AuthBearerAuthoritative", set_authoritative, NULL, OR_AUTHCFG,
+                 "Set to 'Off' to allow access control to be passed along to "
+                 "lower modules if the token is not known to this module"),
+    AP_INIT_TAKE1("AuthBearerProxy", set_bearer_proxy, NULL, OR_AUTHCFG,
+                  "Pass a bearer authentication token over a proxy connection "
+                  "generated using the given expression, 'off' to disable."),
+    {NULL}
+};
+
+/* These functions return 0 if client is OK, and proper error status
+ * if not... either HTTP_UNAUTHORIZED, if we made a check, and it failed, or
+ * HTTP_INTERNAL_SERVER_ERROR, if things are so totally confused that we
+ * couldn't figure out how to tell if the client is authorized or not.
+ *
+ * If they return DECLINED, and all other modules also decline, that's
+ * treated by the server core as a configuration error, logged and
+ * reported as such.
+ */
+
+static void note_bearer_auth_failure(request_rec *r)
+{
+    apr_table_setn(r->err_headers_out,
+                   (PROXYREQ_PROXY == r->proxyreq) ? "Proxy-Authenticate"
+                                                   : "WWW-Authenticate",
+                   apr_pstrcat(r->pool, "Bearer realm=\"", ap_auth_name(r),
+                               "\"", NULL));
+}
+
+static int hook_note_bearer_auth_failure(request_rec *r, const char *auth_type)
+{
+    if (strcasecmp(auth_type, "Bearer"))
+        return DECLINED;
+
+    note_bearer_auth_failure(r);
+    return OK;
+}
+
+static int get_bearer_auth(request_rec *r, const char **token)
+{
+    const char *auth_line;
+
+    /* Get the appropriate header */
+    auth_line = apr_table_get(r->headers_in, (PROXYREQ_PROXY == r->proxyreq)
+                                              ? "Proxy-Authorization"
+                                              : "Authorization");
+
+    if (!auth_line) {
+        note_bearer_auth_failure(r);
+        return HTTP_UNAUTHORIZED;
+    }
+
+    if (strcasecmp(ap_getword(r->pool, &auth_line, ' '), "Bearer")) {
+        /* Client tried to authenticate using wrong auth scheme */
+        ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01614)
+                      "client used wrong authentication scheme: %s", r->uri);
+        note_bearer_auth_failure(r);
+        return HTTP_UNAUTHORIZED;
+    }
+
+    /* Skip leading spaces. */
+    while (apr_isspace(*auth_line)) {
+        auth_line++;
+    }
+
+    *token = auth_line;
+
+    return OK;
+}
+
+/* Determine the token, and check if we can process the token, for HTTP
+ * bearer authentication...
+ */
+static int authenticate_bearer_token(request_rec *r)
+{
+    auth_bearer_config_rec *conf = ap_get_module_config(r->per_dir_config,
+                                                       &auth_bearer_module);
+    const char *sent_token, *current_auth;
+    int res;
+    autht_status auth_result;
+    autht_provider_list *current_provider;
+
+    /* Are we configured to be Bearer auth? */
+    current_auth = ap_auth_type(r);
+    if (!current_auth || strcasecmp(current_auth, "Bearer")) {
+        return DECLINED;
+    }
+
+    /* We need an authentication realm. */
+    if (!ap_auth_name(r)) {
+        ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01615)
+                      "need AuthName: %s", r->uri);
+        return HTTP_INTERNAL_SERVER_ERROR;
+    }
+
+    r->ap_auth_type = (char*)current_auth;
+
+    res = get_bearer_auth(r, &sent_token);
+    if (res) {
+        return res;
+    }
+
+    current_provider = conf->providers;
+    do {
+        const autht_provider *provider;
+
+        /* For now, if a provider isn't set, we'll be nice and use the jwt
+         * provider.
+         */
+        if (!current_provider) {
+            provider = ap_lookup_provider(AUTHT_PROVIDER_GROUP,
+                                          AUTHT_DEFAULT_PROVIDER,
+                                          AUTHT_PROVIDER_VERSION);
+
+            if (!provider || !provider->check_token) {
+                ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01616)
+                              "No Autht provider configured");
+                auth_result = AUTHT_GENERAL_ERROR;
+                break;
+            }
+            apr_table_setn(r->notes, AUTHT_PROVIDER_NAME_NOTE, AUTHT_DEFAULT_PROVIDER);
+        }
+        else {
+            provider = current_provider->provider;
+            apr_table_setn(r->notes, AUTHT_PROVIDER_NAME_NOTE, current_provider->provider_name);
+        }
+
+        auth_result = provider->check_token(r, "bearer", sent_token);
+
+        apr_table_unset(r->notes, AUTHT_PROVIDER_NAME_NOTE);
+
+        /* Something occurred. Stop checking. */
+        if (auth_result != AUTHT_MISMATCH) {
+            break;
+        }
+
+        /* If we're not really configured for providers, stop now. */
+        if (!conf->providers) {
+            break;
+        }
+
+        current_provider = current_provider->next;
+    } while (current_provider);
+
+    if (auth_result != AUTHT_GRANTED) {
+        int return_code;
+
+        /* If we're not authoritative, then any error is ignored. */
+        if (!(conf->authoritative) && auth_result != AUTHT_DENIED) {
+            return DECLINED;
+        }
+
+        switch (auth_result) {
+        case AUTHT_DENIED:
+            ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO()
+                      "bearer token %s: authentication failure for \"%s\": "
+                      "Token Rejected",
+                      sent_token, r->uri);
+            return_code = HTTP_UNAUTHORIZED;
+            break;
+        case AUTHT_EXPIRED:
+            ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO()
+                      "bearer token %s: authentication failure for \"%s\": "
+                      "Token has expired",
+                      sent_token, r->uri);
+            return_code = HTTP_UNAUTHORIZED;
+            break;
+        case AUTHT_INVALID:
+            ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO()
+                      "bearer token %s: authentication failure for \"%s\": "
+                      "Token is not yet valid",
+                      sent_token, r->uri);
+            return_code = HTTP_UNAUTHORIZED;
+            break;
+        case AUTHT_MISMATCH:
+            ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO()
+                      "bearer token %s: did not match '%s': %s", sent_token,
+					  ap_auth_name(r), r->uri);
+            return_code = HTTP_UNAUTHORIZED;
+            break;
+        case AUTH_GENERAL_ERROR:
+        default:
+            /* We'll assume that the module has already said what its error
+             * was in the logs.
+             */
+            return_code = HTTP_INTERNAL_SERVER_ERROR;
+            break;
+        }
+
+        /* If we're returning 401, tell them to try again. */
+        if (return_code == HTTP_UNAUTHORIZED) {
+            note_bearer_auth_failure(r);
+        }
+        return return_code;
+    }
+
+    return OK;
+}
+
+/* If we have set claims to be made, create a bearer authentication header
+ * for the benefit of a proxy or application running behind this server.
+ */
+static int authenticate_bearer_fixup(request_rec *r)
+{
+    const char *auth_line, *token, *err;
+    auth_bearer_config_rec *conf = ap_get_module_config(r->per_dir_config,
+                                                       &auth_bearer_module);
+
+    if (!conf->proxy) {
+        return DECLINED;
+    }
+
+    token = ap_expr_str_exec(r, conf->proxy, &err);
+    if (err) {
+        ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02455)
+                      "AuthBearerProxy: could not evaluate token expression for URI '%s': %s", r->uri, err);
+        return HTTP_INTERNAL_SERVER_ERROR;
+    }
+    if (!token || !*token) {
+        ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(02458)
+                      "AuthBearerProxy: empty token expression for URI '%s', ignoring", r->uri);
+
+        apr_table_unset(r->headers_in, "Authorization");
+
+        return DECLINED;
+    }
+
+    auth_line = apr_pstrcat(r->pool, "Bearer ", token,
+                            NULL);
+    apr_table_setn(r->headers_in, "Authorization", auth_line);
+
+    ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(02457)
+                  "AuthBearerProxy: \"Authorization: %s\"",
+                  auth_line);
+
+    return OK;
+}
+
+static void register_hooks(apr_pool_t *p)
+{
+    ap_hook_check_autht(authenticate_bearer_token, NULL, NULL, APR_HOOK_MIDDLE,
+                        AP_AUTH_INTERNAL_PER_CONF);
+    ap_hook_fixups(authenticate_bearer_fixup, NULL, NULL, APR_HOOK_LAST);
+    ap_hook_note_auth_failure(hook_note_bearer_auth_failure, NULL, NULL,
+                              APR_HOOK_MIDDLE);
+}
+
+AP_DECLARE_MODULE(auth_bearer) =
+{
+    STANDARD20_MODULE_STUFF,
+    create_auth_bearer_dir_config,  /* dir config creater */
+    merge_auth_bearer_dir_config,   /* dir merger --- default is to override */
+    NULL,                           /* server config */
+    NULL,                           /* merge server config */
+    auth_bearer_cmds,               /* command apr_table_t */
+    register_hooks                  /* register hooks */
+};

Added: httpd/httpd/trunk/modules/aaa/mod_autht_core.c
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/aaa/mod_autht_core.c?rev=1909411&view=auto
==============================================================================
--- httpd/httpd/trunk/modules/aaa/mod_autht_core.c (added)
+++ httpd/httpd/trunk/modules/aaa/mod_autht_core.c Tue Apr 25 17:52:18 2023
@@ -0,0 +1,227 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * Security options etc.
+ *
+ * Module derived from code originally written by Rob McCool
+ *
+ */
+
+#include "apr_strings.h"
+#include "apr_network_io.h"
+#define APR_WANT_STRFUNC
+#define APR_WANT_BYTEFUNC
+#include "apr_want.h"
+
+#include "ap_config.h"
+#include "httpd.h"
+#include "http_config.h"
+#include "http_core.h"
+#include "http_log.h"
+#include "http_request.h"
+#include "http_protocol.h"
+#include "ap_provider.h"
+
+#include "mod_auth.h"
+
+#if APR_HAVE_NETINET_IN_H
+#include <netinet/in.h>
+#endif
+
+typedef struct provider_alias_rec {
+    char *provider_name;
+    char *provider_alias;
+    ap_conf_vector_t *sec_auth;
+    const autht_provider *provider;
+} provider_alias_rec;
+
+typedef struct autht_alias_srv_conf {
+    apr_hash_t *alias_rec;
+} autht_alias_srv_conf;
+
+
+module AP_MODULE_DECLARE_DATA autht_core_module;
+
+static autht_status authn_alias_check_token(request_rec *r, const char *type,
+                                            const char *token)
+{
+    /* Look up the provider alias in the alias list */
+    /* Get the dir_config and call ap_Merge_per_dir_configs() */
+    /* Call the real provider->check_password() function */
+    /* return the result of the above function call */
+
+    const char *provider_name = apr_table_get(r->notes, AUTHT_PROVIDER_NAME_NOTE);
+    autht_status ret = AUTHT_MISMATCH;
+    autht_alias_srv_conf *authcfg =
+        (autht_alias_srv_conf *)ap_get_module_config(r->server->module_config,
+                                                     &autht_core_module);
+
+    if (provider_name) {
+        provider_alias_rec *prvdraliasrec = apr_hash_get(authcfg->alias_rec,
+                                                         provider_name, APR_HASH_KEY_STRING);
+        ap_conf_vector_t *orig_dir_config = r->per_dir_config;
+
+        /* If we found the alias provider in the list, then merge the directory
+           configurations and call the real provider */
+        if (prvdraliasrec) {
+            r->per_dir_config = ap_merge_per_dir_configs(r->pool, orig_dir_config,
+                                                         prvdraliasrec->sec_auth);
+            ret = prvdraliasrec->provider->check_token(r, type, token);
+            r->per_dir_config = orig_dir_config;
+        }
+    }
+
+    return ret;
+}
+
+static void *create_autht_alias_svr_config(apr_pool_t *p, server_rec *s)
+{
+
+    autht_alias_srv_conf *authcfg;
+
+    authcfg = (autht_alias_srv_conf *) apr_pcalloc(p, sizeof(autht_alias_srv_conf));
+    authcfg->alias_rec = apr_hash_make(p);
+
+    return (void *) authcfg;
+}
+
+/* Only per-server directive we have is GLOBAL_ONLY */
+static void *merge_autht_alias_svr_config(apr_pool_t *p, void *basev, void *overridesv)
+{
+    return basev;
+}
+
+static const autht_provider autht_alias_provider =
+{
+    &authn_alias_check_token
+};
+
+static const char *authaliassection(cmd_parms *cmd, void *mconfig, const char *arg)
+{
+    const char *endp = ap_strrchr_c(arg, '>');
+    const char *args;
+    char *provider_alias;
+    char *provider_name;
+    int old_overrides = cmd->override;
+    const char *errmsg;
+    const autht_provider *provider = NULL;
+    ap_conf_vector_t *new_auth_config = ap_create_per_dir_config(cmd->pool);
+    autht_alias_srv_conf *authcfg =
+        (autht_alias_srv_conf *)ap_get_module_config(cmd->server->module_config,
+                                                     &autht_core_module);
+
+    const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
+    if (err != NULL) {
+        return err;
+    }
+
+    if (endp == NULL) {
+        return apr_pstrcat(cmd->pool, cmd->cmd->name,
+                           "> directive missing closing '>'", NULL);
+    }
+
+    args = apr_pstrndup(cmd->temp_pool, arg, endp - arg);
+
+    if (!args[0]) {
+        return apr_pstrcat(cmd->pool, cmd->cmd->name,
+                           "> directive requires additional arguments", NULL);
+    }
+
+    /* Pull the real provider name and the alias name from the block header */
+    provider_name = ap_getword_conf(cmd->pool, &args);
+    provider_alias = ap_getword_conf(cmd->pool, &args);
+
+    if (!provider_name[0] || !provider_alias[0]) {
+        return apr_pstrcat(cmd->pool, cmd->cmd->name,
+                           "> directive requires additional arguments", NULL);
+    }
+
+    if (strcasecmp(provider_name, provider_alias) == 0) {
+        return apr_pstrcat(cmd->pool,
+                           "The alias provider name must be different from the base provider name.", NULL);
+    }
+
+    /* Look up the alias provider to make sure that it hasn't already been registered. */
+    provider = ap_lookup_provider(AUTHN_PROVIDER_GROUP, provider_alias,
+                                  AUTHN_PROVIDER_VERSION);
+    if (provider) {
+        return apr_pstrcat(cmd->pool, "The alias provider ", provider_alias,
+                           " has already be registered previously as either a base provider or an alias provider.",
+                           NULL);
+    }
+
+    /* walk the subsection configuration to get the per_dir config that we will
+       merge just before the real provider is called. */
+    cmd->override = OR_AUTHCFG | ACCESS_CONF;
+    errmsg = ap_walk_config(cmd->directive->first_child, cmd, new_auth_config);
+    cmd->override = old_overrides;
+
+    if (!errmsg) {
+        provider_alias_rec *prvdraliasrec = apr_pcalloc(cmd->pool, sizeof(provider_alias_rec));
+        provider = ap_lookup_provider(AUTHN_PROVIDER_GROUP, provider_name,
+                                      AUTHN_PROVIDER_VERSION);
+
+        if (!provider) {
+            /* by the time they use it, the provider should be loaded and
+               registered with us. */
+            return apr_psprintf(cmd->pool,
+                                "Unknown Authn provider: %s",
+                                provider_name);
+        }
+
+        /* Save off the new directory config along with the original provider name
+           and function pointer data */
+        prvdraliasrec->sec_auth = new_auth_config;
+        prvdraliasrec->provider_name = provider_name;
+        prvdraliasrec->provider_alias = provider_alias;
+        prvdraliasrec->provider = provider;
+        apr_hash_set(authcfg->alias_rec, provider_alias, APR_HASH_KEY_STRING, prvdraliasrec);
+
+        /* Register the fake provider so that we get called first */
+        ap_register_auth_provider(cmd->pool, AUTHT_PROVIDER_GROUP,
+                                   provider_alias, AUTHT_PROVIDER_VERSION,
+								   &autht_alias_provider,
+                                   AP_AUTH_INTERNAL_PER_CONF);
+    }
+
+    return errmsg;
+}
+
+static const command_rec autht_cmds[] =
+{
+    AP_INIT_RAW_ARGS("<AuthtProviderAlias", authaliassection, NULL, RSRC_CONF,
+                     "container for grouping an authentication provider's "
+                     "directives under a provider alias"),
+    {NULL}
+};
+
+static void register_hooks(apr_pool_t *p)
+{
+
+}
+
+AP_DECLARE_MODULE(autht_core) =
+{
+    STANDARD20_MODULE_STUFF,
+    NULL,                           /* dir config creater */
+    NULL,                           /* dir merger --- default is to override */
+    create_autht_alias_svr_config,  /* server config */
+    merge_autht_alias_svr_config,   /* merge server config */
+    autht_cmds,
+    register_hooks                  /* register hooks */
+};
+

Added: httpd/httpd/trunk/modules/aaa/mod_autht_jwt.c
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/aaa/mod_autht_jwt.c?rev=1909411&view=auto
==============================================================================
--- httpd/httpd/trunk/modules/aaa/mod_autht_jwt.c (added)
+++ httpd/httpd/trunk/modules/aaa/mod_autht_jwt.c Tue Apr 25 17:52:18 2023
@@ -0,0 +1,1089 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * This module adds support for https://tools.ietf.org/html/rfc7519 JWT tokens
+ * as https://tools.ietf.org/html/rfc6750 Bearer tokens, both as a generator
+ * of JWT bearer tokens, and as an acceptor of JWT Bearer tokens for authentication.
+ */
+
+#include "apr_strings.h"
+#include "apr_hash.h"
+#include "apr_crypto.h"
+#include "apr_jose.h"
+#include "apr_lib.h"            /* for apr_isspace */
+#include "apr_base64.h"         /* for apr_base64_decode et al */
+#define APR_WANT_STRFUNC        /* for strcasecmp */
+#include "apr_want.h"
+
+#include "ap_config.h"
+#include "httpd.h"
+#include "http_config.h"
+#include "http_core.h"
+#include "http_log.h"
+#include "http_protocol.h"
+#include "http_request.h"
+#include "util_md5.h"
+#include "ap_provider.h"
+#include "ap_expr.h"
+
+#include "mod_auth.h"
+
+#define CRYPTO_KEY "auth_bearer_context"
+
+module AP_MODULE_DECLARE_DATA autht_jwt_module;
+
+typedef enum jws_alg_type_e {
+    /** No specific type. */
+    JWS_ALG_TYPE_NONE = 0,
+    /** HMAC SHA256 */
+    JWS_ALG_TYPE_HS256 = 1,
+} jws_alg_type_e;
+
+typedef struct {
+    unsigned char *secret;
+    apr_size_t secret_len;
+    jws_alg_type_e jws_alg;
+} auth_bearer_signature_rec;
+
+typedef struct {
+    apr_hash_t *claims;
+    apr_array_header_t *signs;
+    apr_array_header_t *verifies;
+    int signs_set:1;
+    int verifies_set:1;
+    int fake_set:1;
+} auth_bearer_config_rec;
+
+typedef struct {
+    const char *library;
+    const char *params;
+    apr_crypto_t **crypto;
+    int library_set;
+} auth_bearer_conf;
+
+static int auth_bearer_init(apr_pool_t *p, apr_pool_t *plog, apr_pool_t *ptemp,
+        server_rec *s) {
+    const apr_crypto_driver_t *driver = NULL;
+
+    /* auth_bearer_init() will be called twice. Don't bother
+     * going through all of the initialization on the first call
+     * because it will just be thrown away.*/
+    if (ap_state_query(AP_SQ_MAIN_STATE) == AP_SQ_MS_CREATE_PRE_CONFIG) {
+        return OK;
+    }
+
+    while (s) {
+
+        auth_bearer_conf *conf = ap_get_module_config(s->module_config,
+                &autht_jwt_module);
+
+        if (conf->library_set && !*conf->crypto) {
+
+            const apu_err_t *err = NULL;
+            apr_status_t rv;
+
+            rv = apr_crypto_init(p);
+            if (APR_SUCCESS != rv) {
+                ap_log_error(APLOG_MARK, APLOG_ERR, rv, s,
+                        APLOGNO() "APR crypto could not be initialised");
+                return rv;
+            }
+
+            rv = apr_crypto_get_driver(&driver, conf->library, conf->params,
+                    &err, p);
+            if (APR_EREINIT == rv) {
+                ap_log_error(APLOG_MARK, APLOG_WARNING, rv, s,
+                        APLOGNO() "warning: crypto for '%s' was already initialised, " "using existing configuration",
+                        conf->library);
+                rv = APR_SUCCESS;
+            }
+            if (APR_SUCCESS != rv && err) {
+                ap_log_error(APLOG_MARK, APLOG_ERR, rv, s,
+                        APLOGNO() "The crypto library '%s' could not be loaded: %s (%s: %d)",
+                        conf->library, err->msg, err->reason, err->rc);
+                return rv;
+            }
+            if (APR_ENOTIMPL == rv) {
+                ap_log_error(APLOG_MARK, APLOG_ERR, rv, s,
+                        APLOGNO() "The crypto library '%s' could not be found",
+                        conf->library);
+                return rv;
+            }
+            if (APR_SUCCESS != rv || !driver) {
+                ap_log_error(APLOG_MARK, APLOG_ERR, rv, s,
+                        APLOGNO() "The crypto library '%s' could not be loaded",
+                        conf->library);
+                return rv;
+            }
+
+            rv = apr_crypto_make(conf->crypto, driver, conf->params, p);
+            if (APR_SUCCESS != rv) {
+                ap_log_error(APLOG_MARK, APLOG_ERR, rv, s,
+                        APLOGNO() "The crypto library '%s' could not be initialised",
+                        conf->library);
+                return rv;
+            }
+
+            ap_log_error(APLOG_MARK, APLOG_INFO, rv, s,
+                    APLOGNO() "The crypto library '%s' was loaded successfully",
+                    conf->library);
+
+        }
+
+        s = s->next;
+    }
+
+    return OK;
+}
+
+static void *create_auth_bearer_config(apr_pool_t * p, server_rec *s)
+{
+    auth_bearer_conf *new =
+    (auth_bearer_conf *) apr_pcalloc(p, sizeof(auth_bearer_conf));
+
+    /* if no library has been configured, set the recommended library
+     * as a sensible default.
+     */
+#ifdef APU_CRYPTO_RECOMMENDED_DRIVER
+    new->library = APU_CRYPTO_RECOMMENDED_DRIVER;
+#endif
+    new->crypto = apr_pcalloc(p, sizeof(apr_crypto_t *));
+
+    return (void *) new;
+}
+
+static void *merge_auth_bearer_config(apr_pool_t * p, void *basev, void *addv)
+{
+    auth_bearer_conf *new = (auth_bearer_conf *) apr_pcalloc(p, sizeof(auth_bearer_conf));
+    auth_bearer_conf *add = (auth_bearer_conf *) addv;
+    auth_bearer_conf *base = (auth_bearer_conf *) basev;
+
+    new->library = (add->library_set == 0) ? base->library : add->library;
+    new->params = (add->library_set == 0) ? base->params : add->params;
+    new->library_set = add->library_set || base->library_set;
+
+    new->crypto = base->crypto;
+
+    return (void *) new;
+}
+
+static void *create_auth_bearer_dir_config(apr_pool_t *p, char *d)
+{
+    auth_bearer_config_rec *conf = apr_pcalloc(p, sizeof(*conf));
+
+    conf->claims = apr_hash_make(p);
+    conf->signs = apr_array_make(p, 1, sizeof(auth_bearer_signature_rec));
+    conf->verifies = apr_array_make(p, 1, sizeof(auth_bearer_signature_rec));
+
+    return conf;
+}
+
+static void *merge_auth_bearer_dir_config(apr_pool_t *p, void *basev, void *overridesv)
+{
+    auth_bearer_config_rec *newconf = apr_pcalloc(p, sizeof(*newconf));
+    auth_bearer_config_rec *base = basev;
+    auth_bearer_config_rec *overrides = overridesv;
+
+    newconf->claims = apr_hash_overlay(p, overrides->claims,
+                                          base->claims);
+
+    newconf->signs =
+            overrides->signs_set ? overrides->signs : base->signs;
+    newconf->signs_set = overrides->signs_set || base->signs_set;
+
+    newconf->verifies =
+            overrides->verifies_set ? overrides->verifies : base->verifies;
+    newconf->verifies_set = overrides->verifies_set || base->verifies_set;
+
+    return newconf;
+}
+
+static const char *set_jwt_claim(cmd_parms *cmd, void *config,
+        const char *op, const char *key, const char *expression)
+{
+    auth_bearer_config_rec *conf = (auth_bearer_config_rec *) config;
+    const char *err;
+
+    if (!strcasecmp(op, "set")) {
+        ap_expr_info_t *expr;
+
+        expr = ap_expr_parse_cmd(cmd, expression, AP_EXPR_FLAG_STRING_RESULT,
+                &err, NULL);
+        if (err) {
+            return apr_psprintf(cmd->pool,
+                    "Could not parse claim '%s' expression '%s': %s", key,
+                    expression, err);
+        }
+
+        apr_hash_set(conf->claims, key, APR_HASH_KEY_STRING, expr);
+
+    } else if (!strcasecmp(op, "unset")) {
+
+        apr_hash_set(conf->claims, key, APR_HASH_KEY_STRING, NULL);
+
+    } else {
+
+        return apr_psprintf(cmd->pool,
+                "Could not parse claim operation '%s', "
+                "values should be 'set' or 'unset'", op);
+
+    }
+
+    return NULL;
+}
+
+static const char *set_jwt_sign(cmd_parms * cmd, void *config,
+        const char *alg, const char *type, const char *sig)
+{
+    auth_bearer_config_rec *dconf = (auth_bearer_config_rec *) config;
+
+    auth_bearer_signature_rec *srec = apr_array_push(dconf->signs);
+
+    /* handle the algorithm */
+    if (!strcasecmp(alg, "none")) {
+        srec->jws_alg = JWS_ALG_TYPE_NONE;
+        if (type || sig) {
+            return "AuthtJwtSign: algorithm 'none' has extra parameters";
+        }
+    }
+    else if (!strcasecmp(alg, "HS256")) {
+        srec->jws_alg = JWS_ALG_TYPE_HS256;
+    }
+    else {
+        return apr_psprintf(cmd->pool, "AuthtJwtSign: algorithm not supported: %s", alg);
+    }
+
+    /* handle the file */
+    if (type) {
+        if (!strcasecmp(type, "file")) {
+            apr_file_t *file;
+            apr_finfo_t finfo;
+            apr_status_t status;
+
+            sig = ap_server_root_relative(cmd->temp_pool, sig);
+
+            status = apr_file_open(&file, sig, APR_READ | APR_BUFFERED,
+            APR_OS_DEFAULT, cmd->pool);
+            if (status != APR_SUCCESS) {
+                char buf[1024];
+                apr_strerror(status, buf, sizeof(buf));
+                return apr_psprintf(cmd->pool,
+                        "AuthtJwtSign: file '%s' could not be opened: %s", sig,
+                        buf);
+            }
+
+            status = apr_file_info_get(&finfo, APR_FINFO_TYPE | APR_FINFO_SIZE,
+                    file);
+            if (status != APR_SUCCESS) {
+                char buf[1024];
+                apr_strerror(status, buf, sizeof(buf));
+                return apr_psprintf(cmd->pool,
+                        "AuthtJwtSign: info could not be obtained for '%s': %s",
+                        sig, buf);
+            }
+
+            srec->secret = apr_palloc(cmd->pool, finfo.size);
+            srec->secret_len = finfo.size;
+
+            status = apr_file_read_full(file, srec->secret,
+                    srec->secret_len, NULL);
+            if (status != APR_SUCCESS) {
+                char buf[1024];
+                apr_strerror(status, buf, sizeof(buf));
+                return apr_psprintf(cmd->pool,
+                        "AuthtJwtSign: file '%s' could not be read: %s", sig,
+                        buf);
+            }
+
+            apr_file_close(file);
+
+        }
+        else {
+            return apr_psprintf(cmd->pool,
+                    "AuthtJwtSign: parameter '%s' is not 'file'", type);
+        }
+    }
+
+    dconf->signs_set = 1;
+
+    return NULL;
+}
+
+static const char *set_jwt_verify(cmd_parms * cmd, void *config,
+        const char *alg, const char *type, const char *sig)
+{
+    auth_bearer_config_rec *dconf = (auth_bearer_config_rec *) config;
+
+    auth_bearer_signature_rec *srec = apr_array_push(dconf->verifies);
+
+    /* handle the algorithm */
+    if (!strcasecmp(alg, "none")) {
+        srec->jws_alg = JWS_ALG_TYPE_NONE;
+        if (type || sig) {
+            return "AuthtJwtVerify: algorithm 'none' has extra parameters";
+        }
+    }
+    else if (!strcasecmp(alg, "HS256")) {
+        srec->jws_alg = JWS_ALG_TYPE_HS256;
+    }
+    else {
+        return apr_psprintf(cmd->pool, "AuthtJwtVerify: algorithm not supported: %s", alg);
+    }
+
+    /* handle the file */
+    if (type) {
+        if (!strcasecmp(type, "file")) {
+            apr_file_t *file;
+            apr_finfo_t finfo;
+            apr_status_t status;
+
+            sig = ap_server_root_relative(cmd->temp_pool, sig);
+
+            status = apr_file_open(&file, sig, APR_READ | APR_BUFFERED,
+            APR_OS_DEFAULT, cmd->pool);
+            if (status != APR_SUCCESS) {
+                char buf[1024];
+                apr_strerror(status, buf, sizeof(buf));
+                return apr_psprintf(cmd->pool,
+                        "AuthtJwtVerify: file '%s' could not be opened: %s", sig,
+                        buf);
+            }
+
+            status = apr_file_info_get(&finfo, APR_FINFO_TYPE | APR_FINFO_SIZE,
+                    file);
+            if (status != APR_SUCCESS) {
+                char buf[1024];
+                apr_strerror(status, buf, sizeof(buf));
+                return apr_psprintf(cmd->pool,
+                        "AuthtJwtVerify: info could not be obtained for '%s': %s",
+                        sig, buf);
+            }
+
+            srec->secret = apr_palloc(cmd->pool, finfo.size);
+            srec->secret_len = finfo.size;
+
+            status = apr_file_read_full(file, srec->secret,
+                    srec->secret_len, NULL);
+            if (status != APR_SUCCESS) {
+                char buf[1024];
+                apr_strerror(status, buf, sizeof(buf));
+                return apr_psprintf(cmd->pool,
+                        "AuthtJwtVerify: file '%s' could not be read: %s", sig,
+                        buf);
+            }
+
+            apr_file_close(file);
+
+        }
+        else {
+            return apr_psprintf(cmd->pool,
+                    "AuthtJwtVerify: parameter '%s' is not 'file'", type);
+        }
+    }
+
+    dconf->verifies_set = 1;
+
+    return NULL;
+}
+
+static const char *set_jwt_driver(cmd_parms * cmd, void *config, const char *arg)
+{
+    auth_bearer_conf *conf =
+            (auth_bearer_conf *)ap_get_module_config(cmd->server->module_config,
+            &autht_jwt_module);
+
+    const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
+
+    if (err != NULL) {
+        return err;
+    }
+
+    conf->library = ap_getword_conf(cmd->pool, &arg);
+    conf->params = arg;
+    conf->library_set = 1;
+
+    return NULL;
+}
+
+static const command_rec auth_bearer_cmds[] =
+{
+    AP_INIT_TAKE13("AuthtJwtVerify", set_jwt_verify, NULL, RSRC_CONF|OR_AUTHCFG,
+                   "The JWS signing algorithm and passphrase/key to verify an incoming JWT token"),
+    AP_INIT_TAKE13("AuthtJwtSign", set_jwt_sign, NULL, RSRC_CONF|OR_AUTHCFG,
+            "The JWS signing algorithm and passphrase/key to sign an outgoing JWT token"),
+
+    AP_INIT_TAKE23("AuthtJwtClaim", set_jwt_claim, NULL, OR_AUTHCFG,
+            "Set a claim with the given name and expression, or "
+            "unset the claim with the given name."),
+
+    AP_INIT_RAW_ARGS("AuthtJwtDriver", set_jwt_driver, NULL, RSRC_CONF,
+            "The underlying crypto library driver to use"),
+
+    {NULL}
+};
+
+typedef struct claim_iter_t {
+    request_rec *r;
+    apr_json_value_t *object;
+} claim_iter_t;
+
+static int claim_iter(void *ctx, const void *key, apr_ssize_t klen,
+                     const void *val)
+{
+    const char *err, *value;
+    claim_iter_t *iter = ctx;
+    request_rec *r = iter->r;
+
+    value = ap_expr_str_exec(r, val, &err);
+    if (err) {
+        ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO()
+                "AuthtJwtClaim: could not evaluate '%s' expression "
+                "'%s' for URI '%s': %s",
+                (char * )key, (char * )val, r->uri, err);
+        return FALSE;
+    }
+
+
+    apr_json_object_set(iter->object, key, klen,
+            apr_json_string_create(r->pool, value, strlen(value)), r->pool);
+
+    return TRUE;
+}
+
+static apr_status_t sign_cb(apr_bucket_brigade *bb, apr_jose_t *jose,
+        apr_jose_signature_t *signature, void *ctx, apr_pool_t *pool) {
+    auth_bearer_signature_rec *srec = NULL;
+    request_rec *r = ctx;
+
+    auth_bearer_conf *sconf = ap_get_module_config(r->server->module_config,
+            &autht_jwt_module);
+
+    auth_bearer_config_rec *conf = ap_get_module_config(r->per_dir_config,
+            &autht_jwt_module);
+
+    if (conf->signs_set) {
+        srec = (auth_bearer_signature_rec *) conf->signs->elts;
+    }
+
+    if (srec) {
+        switch (srec->jws_alg) {
+        case JWS_ALG_TYPE_NONE: {
+
+            return APR_SUCCESS;
+        }
+        case JWS_ALG_TYPE_HS256: {
+            apr_bucket *e;
+            apr_crypto_key_rec_t *krec;
+            apr_crypto_key_t *key = NULL;
+            apr_crypto_digest_t *digest = NULL;
+            apr_crypto_digest_rec_t *rec;
+            char * buf;
+            apr_status_t status;
+
+            if (!*sconf->crypto) {
+                jose->result.msg = "token could not be signed";
+                jose->result.reason = "no crypto driver configured (set AuthtJwtDriver)";
+                return APR_EGENERAL;
+            }
+
+            krec = apr_crypto_key_rec_make(APR_CRYPTO_KTYPE_HMAC, pool);
+
+            krec->k.hmac.digest = APR_CRYPTO_DIGEST_SHA256;
+            krec->k.hmac.secret = srec->secret;
+            krec->k.hmac.secretLen = srec->secret_len;
+
+            status = apr_crypto_key(&key, krec, *sconf->crypto, pool);
+            if (status != APR_SUCCESS) {
+                jose->result.reason = buf = apr_pcalloc(pool, HUGE_STRING_LEN);
+                apr_strerror(status, buf, HUGE_STRING_LEN);
+                jose->result.msg = "token could not be signed";
+                return status;
+            }
+
+            rec = apr_crypto_digest_rec_make(APR_CRYPTO_DTYPE_SIGN, pool);
+
+            status = apr_crypto_digest_init(&digest, key, rec, pool);
+            if (status != APR_SUCCESS) {
+                jose->result.reason = buf = apr_pcalloc(pool, HUGE_STRING_LEN);
+                apr_strerror(status, buf, HUGE_STRING_LEN);
+                jose->result.msg = "token could not be signed";
+                return status;
+            }
+
+            for (e = APR_BRIGADE_FIRST(bb); e != APR_BRIGADE_SENTINEL(bb); e =
+                    APR_BUCKET_NEXT(e)) {
+                const char *str;
+                apr_size_t len;
+
+                /* If we see an EOS, don't bother doing anything more. */
+                if (APR_BUCKET_IS_EOS(e)) {
+                    break;
+                }
+
+                status = apr_bucket_read(e, &str, &len, APR_BLOCK_READ);
+                if (status != APR_SUCCESS) {
+                    jose->result.reason = buf = apr_pcalloc(pool, HUGE_STRING_LEN);
+                    apr_strerror(status, buf, HUGE_STRING_LEN);
+                    jose->result.msg = "token could not be signed";
+                    return status;
+                }
+
+                status = apr_crypto_digest_update(digest,
+                        (const unsigned char *) str, len);
+                if (status != APR_SUCCESS) {
+                    jose->result.reason = buf = apr_pcalloc(pool, HUGE_STRING_LEN);
+                    apr_strerror(status, buf, HUGE_STRING_LEN);
+                    jose->result.msg = "token could not be signed";
+                    return status;
+                }
+            }
+
+            status = apr_crypto_digest_final(digest);
+            if (status != APR_SUCCESS) {
+                jose->result.reason = buf = apr_pcalloc(pool, HUGE_STRING_LEN);
+                apr_strerror(status, buf, HUGE_STRING_LEN);
+                jose->result.msg = "token could not be signed";
+                return status;
+            }
+
+            signature->sig.data = rec->d.sign.s;
+            signature->sig.len = rec->d.sign.slen;
+
+            return APR_SUCCESS;
+        }
+        }
+    }
+    else {
+        /* algorithm is none */
+        return APR_SUCCESS;
+    }
+
+    return APR_ENOTIMPL;
+}
+
+/* If we have set claims to be made, create a JWT token.
+ */
+static const char *jwt_get_token(request_rec *r)
+{
+    claim_iter_t iter = { 0 };
+    apr_json_value_t *claims;
+    apr_json_value_t *protect;
+    apr_jose_t jwt = { 0 };
+    apr_jose_t jws = { 0 };
+    apr_jose_signature_t signature = { 0 };
+    auth_bearer_signature_rec *srec = NULL;
+    apr_bucket_brigade *bb;
+    char *auth_line;
+    apr_size_t len;
+    apr_off_t offset;
+    apr_status_t status;
+
+    auth_bearer_config_rec *conf = ap_get_module_config(r->per_dir_config,
+                                                       &autht_jwt_module);
+
+    apr_jose_cb_t cb = { 0 };
+
+    cb.sign = sign_cb;
+    cb.ctx = r;
+
+    if (!conf->claims || !apr_hash_count(conf->claims)) {
+        ap_log_rerror(APLOG_MARK, APLOG_ERR, APR_SUCCESS, r,
+                APLOGNO() "AuthtJwtClaim: could not encode a JWT token for URI '%s': no claims",
+                r->uri);
+        return "error:no-claims";
+    }
+
+    if (conf->verifies_set) {
+        srec = (auth_bearer_signature_rec *)conf->verifies->elts;
+    }
+
+    /* create a JWT containing the claims */
+    claims = apr_json_object_create(r->pool);
+    iter.object = claims;
+    iter.r = r;
+
+    /* iterate through our claims */
+    if (!apr_hash_do(claim_iter, &iter, conf->claims)) {
+        return "error:claim-failed";
+    }
+
+    apr_jose_jwt_make(&jwt, claims, r->pool);
+    protect = apr_json_object_create(r->pool);
+
+    apr_json_object_set(protect, APR_JOSE_JWSE_TYPE,
+            APR_JSON_VALUE_STRING,
+            apr_json_string_create(r->pool, APR_JOSE_JWSE_TYPE_JWT,
+                    APR_JSON_VALUE_STRING), r->pool);
+
+    if (srec) {
+        /* which signature type do we have? */
+        switch (srec->jws_alg) {
+        case JWS_ALG_TYPE_NONE: {
+            apr_json_object_set(protect, APR_JOSE_JWKSE_ALGORITHM,
+                    APR_JSON_VALUE_STRING,
+                    apr_json_string_create(r->pool, APR_JOSE_JWA_NONE,
+                            APR_JSON_VALUE_STRING), r->pool);
+
+            break;
+        }
+        case JWS_ALG_TYPE_HS256: {
+            apr_json_object_set(protect, APR_JOSE_JWKSE_ALGORITHM,
+                    APR_JSON_VALUE_STRING,
+                    apr_json_string_create(r->pool, APR_JOSE_JWA_HS256,
+                            APR_JSON_VALUE_STRING), r->pool);
+
+            break;
+        }
+        }
+
+    }
+    else {
+        /* no srec defaults to none */
+        apr_json_object_set(protect, APR_JOSE_JWKSE_ALGORITHM,
+                APR_JSON_VALUE_STRING,
+                apr_json_string_create(r->pool, APR_JOSE_JWA_NONE,
+                        APR_JSON_VALUE_STRING), r->pool);
+    }
+
+    apr_jose_signature_make(&signature, NULL, protect, r->pool);
+    apr_jose_jws_make(&jws, &signature, NULL, &jwt, r->pool);
+
+    bb = apr_brigade_create(r->pool, r->connection->bucket_alloc);
+
+    status = apr_jose_encode(bb, NULL, NULL, &jws, &cb, r->pool);
+    if (APR_SUCCESS != status) {
+        const apu_err_t *err = apr_jose_error(&jws);
+        ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r,
+                APLOGNO() "AuthtJwtClaim: could not encode a JWT token for URI '%s': %s: %s",
+                r->uri, err->msg, err->reason);
+        return "error:could-not-encode";
+    }
+
+    apr_brigade_length(bb, 1, &offset);
+    auth_line = apr_pcalloc(r->pool, offset + 1);
+    len = offset;
+    apr_brigade_flatten(bb, auth_line, &len);
+    auth_line[offset] = 0;
+
+    return auth_line;
+}
+
+static const char *jwt_expr_var_fn(ap_expr_eval_ctx_t *ctx, const void *data)
+{
+    char *var = (char *)data;
+
+    if (var && *var && ctx->r && ap_cstr_casecmp(var, "TOKEN") == 0) {
+        return jwt_get_token(ctx->r);
+    }
+    return NULL;
+}
+
+static int jwt_expr_lookup(ap_expr_lookup_parms *parms)
+{
+    switch (parms->type) {
+    case AP_EXPR_FUNC_VAR:
+        /* for now, we just handle everything that starts with JWT_.
+         */
+        if (strncasecmp(parms->name, "JWT_", 4) == 0) {
+            *parms->func = jwt_expr_var_fn;
+            *parms->data = parms->name + 4;
+            return OK;
+        }
+        break;
+    }
+    return DECLINED;
+}
+
+static apr_status_t verify_cb(apr_bucket_brigade *bb,
+        apr_jose_t *jose, apr_jose_signature_t *signature, void *ctx,
+        int *vflags, apr_pool_t *pool)
+{
+    request_rec *r = ctx;
+    apr_json_kv_t *alg = NULL;
+
+    auth_bearer_conf *sconf = ap_get_module_config(r->server->module_config,
+            &autht_jwt_module);
+
+    auth_bearer_config_rec *conf = ap_get_module_config(r->per_dir_config,
+                                                       &autht_jwt_module);
+
+    int alg_supported = 0;
+
+    if (signature) {
+        apr_json_value_t *ph = signature->protected_header;
+
+        if (ph && ph->type == APR_JSON_OBJECT) {
+
+            alg = apr_json_object_get(ph, APR_JOSE_JWKSE_ALGORITHM,
+                    APR_JSON_VALUE_STRING);
+
+        }
+    }
+
+    if (!alg) {
+        apr_errprintf(&jose->result, r->pool, "", APR_EGENERAL,
+                "JWT token protected header has no '"
+                APR_JOSE_JWKSE_ALGORITHM
+                "' for URI '%s'",
+                r->uri);
+        return APR_EGENERAL;
+    }
+
+    if (alg->v->type != APR_JSON_STRING) {
+        apr_errprintf(&jose->result, r->pool, "", APR_EGENERAL,
+                "JWT token protected header '"
+                APR_JOSE_JWKSE_ALGORITHM
+                "' is not a string for URI '%s'",
+                r->uri);
+        return APR_EGENERAL;
+    }
+
+    /* first pass, is our algorithm supported? */
+    for (int i = 0; i < conf->verifies->nelts; i++) {
+    	auth_bearer_signature_rec *srec = &APR_ARRAY_IDX(conf->verifies,
+    			i, auth_bearer_signature_rec);
+
+    	/* which signature type do we have? */
+    	switch (srec->jws_alg) {
+    	case JWS_ALG_TYPE_NONE: {
+        	if (!strncmp(alg->v->value.string.p, "none",
+                        alg->v->value.string.len)) {
+        		alg_supported = 1;
+        	}
+        	break;
+    	}
+    	case JWS_ALG_TYPE_HS256: {
+        	if (!strncmp(alg->v->value.string.p, "HS256",
+        			alg->v->value.string.len)) {
+        		alg_supported = 1;
+        	}
+    		break;
+    	}
+    	}
+
+    }
+
+    /* we don't support the algorithm */
+    if (!alg_supported) {
+    	apr_errprintf(&jose->result, r->pool, "", APR_ENODIGEST,
+    			"JWT token protected header '"
+    			APR_JOSE_JWKSE_ALGORITHM
+				"' %s is not supported for URI '%s'",
+				alg->v->value.string.p, r->uri);
+    	return APR_ENODIGEST;
+    }
+
+    /* second pass, does the signature match? */
+    for (int i = 0; i < conf->verifies->nelts; i++) {
+    	auth_bearer_signature_rec *srec = &APR_ARRAY_IDX(conf->verifies,
+    			i, auth_bearer_signature_rec);
+
+    	/* which signature type do we have? */
+    	switch (srec->jws_alg) {
+    	case JWS_ALG_TYPE_NONE: {
+        	if (!strncmp(alg->v->value.string.p, "none",
+                        alg->v->value.string.len)) {
+        		return APR_SUCCESS;
+        	}
+        	break;
+    	}
+    	case JWS_ALG_TYPE_HS256: {
+        	if (!strncmp(alg->v->value.string.p, "HS256",
+        			alg->v->value.string.len)) {
+
+                apr_bucket *e;
+                apr_crypto_key_rec_t *krec;
+                apr_crypto_key_t *key = NULL;
+                apr_crypto_digest_t *digest = NULL;
+                apr_crypto_digest_rec_t *rec;
+                char * buf;
+                apr_status_t status;
+
+                if (!*sconf->crypto) {
+                    jose->result.msg = "token could not be verified";
+                    jose->result.reason = "no crypto driver configured (set AuthtJwtDriver)";
+                    return APR_EGENERAL;
+                }
+
+                krec = apr_crypto_key_rec_make(APR_CRYPTO_KTYPE_HMAC, pool);
+
+                krec->k.hmac.digest = APR_CRYPTO_DIGEST_SHA256;
+                krec->k.hmac.secret = srec->secret;
+                krec->k.hmac.secretLen = srec->secret_len;
+
+                status = apr_crypto_key(&key, krec, *sconf->crypto, pool);
+                if (status != APR_SUCCESS) {
+                    jose->result.reason = buf = apr_pcalloc(pool, HUGE_STRING_LEN);
+                    apr_strerror(status, buf, HUGE_STRING_LEN);
+                    jose->result.msg = "token could not be verified";
+                    return status;
+                }
+
+                rec = apr_crypto_digest_rec_make(APR_CRYPTO_DTYPE_SIGN, pool);
+
+                status = apr_crypto_digest_init(&digest, key, rec, pool);
+                if (status != APR_SUCCESS) {
+                    jose->result.reason = buf = apr_pcalloc(pool, HUGE_STRING_LEN);
+                    apr_strerror(status, buf, HUGE_STRING_LEN);
+                    jose->result.msg = "token could not be verified";
+                    return status;
+                }
+
+                for (e = APR_BRIGADE_FIRST(bb); e != APR_BRIGADE_SENTINEL(bb); e =
+                        APR_BUCKET_NEXT(e)) {
+                    const char *str;
+                    apr_size_t len;
+
+                    /* If we see an EOS, don't bother doing anything more. */
+                    if (APR_BUCKET_IS_EOS(e)) {
+                        break;
+                    }
+
+                    status = apr_bucket_read(e, &str, &len, APR_BLOCK_READ);
+                    if (status != APR_SUCCESS) {
+                        jose->result.reason = buf = apr_pcalloc(pool, HUGE_STRING_LEN);
+                        apr_strerror(status, buf, HUGE_STRING_LEN);
+                        jose->result.msg = "token could not be verified";
+                        return status;
+                    }
+
+                    status = apr_crypto_digest_update(digest,
+                            (const unsigned char *) str, len);
+                    if (status != APR_SUCCESS) {
+                        jose->result.reason = buf = apr_pcalloc(pool, HUGE_STRING_LEN);
+                        apr_strerror(status, buf, HUGE_STRING_LEN);
+                        jose->result.msg = "token could not be verified";
+                        return status;
+                    }
+                }
+
+                status = apr_crypto_digest_final(digest);
+                if (status != APR_SUCCESS) {
+                    jose->result.reason = buf = apr_pcalloc(pool, HUGE_STRING_LEN);
+                    apr_strerror(status, buf, HUGE_STRING_LEN);
+                    jose->result.msg = "token could not be verified";
+                    return status;
+                }
+
+                if (signature->sig.len == rec->d.sign.slen &&
+                		!memcmp(signature->sig.data, rec->d.sign.s, rec->d.sign.slen)) {
+                    return APR_SUCCESS;
+                }
+
+        	}
+    		break;
+    	}
+    	}
+
+    }
+
+    /* no match, oh well */
+	apr_errprintf(&jose->result, r->pool, "", APR_ENODIGEST,
+			"JWT token protected header '"
+			APR_JOSE_JWKSE_ALGORITHM
+			"' %s is not supported for URI '%s'",
+			alg->v->value.string.p, r->uri);
+    return APR_ENOVERIFY;
+}
+
+static autht_status check_token(request_rec *r, const char *type,
+                                   const char *token)
+{
+    apr_bucket_brigade *bb;
+    apr_jose_t *jose = NULL;
+    apr_json_kv_t *kv;
+    apr_status_t status;
+
+    apr_jose_cb_t cb;
+
+    apr_table_t *e = r->subprocess_env;
+
+    const char *aud = NULL;
+    const char *sub = NULL;
+
+    apr_int64_t exp;
+    apr_int64_t nbf;
+
+    int exp_set = 0;
+    int nbf_set = 0;
+
+    cb.verify = verify_cb;
+    cb.decrypt = NULL;
+    cb.ctx = r;
+
+    bb = apr_brigade_create(r->pool, r->connection->bucket_alloc);
+
+    if (token) {
+        apr_brigade_write(bb, NULL, NULL, token, strlen(token));
+    }
+
+    status = apr_jose_decode(&jose, "JWT", bb, &cb, 10, APR_JOSE_FLAG_NONE, r->pool);
+
+    if (APR_SUCCESS != status) {
+        const apu_err_t *err = apr_jose_error(jose);
+        ap_log_rerror(APLOG_MARK, APLOG_DEBUG, status, r,
+                APLOGNO() "AuthtJwt: could not decode a JWT token for URI '%s': %s: %s",
+                r->uri, err->msg, err->reason);
+        return AUTHT_DENIED;
+    }
+
+    if (jose->type != APR_JOSE_TYPE_JWT) {
+        ap_log_rerror(APLOG_MARK, APLOG_DEBUG, status, r,
+                APLOGNO() "AuthtJwt: JOSE token was not a JWT token for URI '%s'",
+                r->uri);
+        return AUTHT_DENIED;
+    }
+
+    /* first pass - identity sub and aud */
+    kv = apr_json_object_first(jose->jose.jwt->claims);
+    do {
+
+        /* ignore any key that isn't a string */
+        if (kv->k->type != APR_JSON_STRING) {
+            continue;
+        }
+
+        if (!strncmp("aud", kv->k->value.string.p, kv->k->value.string.len)) {
+            if (kv->v->type == APR_JSON_STRING) {
+                aud = apr_pstrndup(r->pool, kv->v->value.string.p,
+                        kv->v->value.string.len);
+            }
+        }
+
+        if (!strncmp("sub", kv->k->value.string.p, kv->k->value.string.len)) {
+            if (kv->v->type == APR_JSON_STRING) {
+                sub = apr_pstrndup(r->pool, kv->v->value.string.p,
+                        kv->v->value.string.len);
+            }
+        }
+
+        if (!strncmp("exp", kv->k->value.string.p, kv->k->value.string.len)) {
+            if (kv->v->type == APR_JSON_LONG) {
+                exp = kv->v->value.lnumber;
+                exp_set = 1;
+            }
+        }
+
+        if (!strncmp("nbf", kv->k->value.string.p, kv->k->value.string.len)) {
+            if (kv->v->type == APR_JSON_LONG) {
+                nbf = kv->v->value.lnumber;
+                nbf_set = 1;
+            }
+        }
+
+
+    } while ((kv = apr_json_object_next(jose->jose.jwt->claims, kv)));
+
+    if (!aud) {
+        ap_log_rerror(APLOG_MARK, APLOG_DEBUG, status, r,
+                APLOGNO() "AuthtJwt: JWT token 'aud' value was missing and did not match AuthName '%s' for URI '%s'",
+                ap_auth_name(r), r->uri);
+        return AUTHT_MISMATCH;
+    }
+
+    if (strcmp(aud, ap_auth_name(r))) {
+        ap_log_rerror(APLOG_MARK, APLOG_DEBUG, status, r,
+                APLOGNO() "AuthtJwt: JWT token 'aud' value '%s' did not match AuthName '%s' for URI '%s'",
+                aud, ap_auth_name(r), r->uri);
+        return AUTHT_MISMATCH;
+    }
+
+    if (exp_set || nbf_set) {
+        apr_time_t now = apr_time_now();
+
+        if (exp_set &&
+                exp < apr_time_sec(now)) {
+            ap_log_rerror(APLOG_MARK, APLOG_DEBUG, status, r,
+                    APLOGNO() "AuthtJwt: JWT token is expired (%"
+                    APR_INT64_T_FMT " < %" APR_TIME_T_FMT ") for URI '%s'",
+                    exp, apr_time_sec(now), r->uri);
+            return AUTHT_EXPIRED;
+        }
+
+        if (nbf_set &&
+                nbf > apr_time_sec(now)) {
+            ap_log_rerror(APLOG_MARK, APLOG_DEBUG, status, r,
+                    APLOGNO() "AuthtJwt: JWT token is not yet valid (%"
+                    APR_INT64_T_FMT " > %" APR_TIME_T_FMT ") for URI '%s'",
+                    nbf, apr_time_sec(now), r->uri);
+            return AUTHT_INVALID;
+        }
+    }
+
+    /* we are good at this point - accept the token */
+
+    if (sub) {
+        r->user = apr_pstrdup(r->pool, sub);
+    }
+
+    /* second pass - add all string claims to the environment, prefixed by TOKEN_ */
+    kv = apr_json_object_first(jose->jose.jwt->claims);
+    do {
+        char *key, *val;
+        int j;
+
+        /* ignore anything that isn't a string */
+        if (kv->k->type != APR_JSON_STRING || kv->v->type != APR_JSON_STRING) {
+            continue;
+        }
+
+        key = apr_psprintf(r->pool, AUTHT_PREFIX "%.*s", (int)kv->k->value.string.len, kv->k->value.string.p);
+        j = sizeof(AUTHT_PREFIX);
+        while (key[j]) {
+            if (apr_isalnum(key[j])) {
+                key[j] = apr_toupper(key[j]);
+            }
+            else {
+                key[j] = '_';
+            }
+            j++;
+        }
+
+        val = apr_pstrndup(r->pool, kv->v->value.string.p,
+                kv->v->value.string.len);
+
+        apr_table_setn(e, key, val);
+
+    } while ((kv = apr_json_object_next(jose->jose.jwt->claims, kv)));
+
+    return AUTHT_GRANTED;
+}
+
+static const autht_provider autht_jwt_provider =
+{
+    &check_token
+};
+
+static void register_hooks(apr_pool_t *p)
+{
+    ap_register_auth_provider(p, AUTHT_PROVIDER_GROUP, "jwt",
+                              AUTHT_PROVIDER_VERSION,
+                              &autht_jwt_provider, AP_AUTH_INTERNAL_PER_CONF);
+    ap_hook_expr_lookup(jwt_expr_lookup, NULL, NULL, APR_HOOK_MIDDLE);
+    ap_hook_post_config(auth_bearer_init, NULL, NULL, APR_HOOK_LAST);
+}
+
+AP_DECLARE_MODULE(autht_jwt) =
+{
+    STANDARD20_MODULE_STUFF,
+    create_auth_bearer_dir_config,  /* dir config creater */
+    merge_auth_bearer_dir_config,   /* dir merger --- default is to override */
+    create_auth_bearer_config,      /* server config */
+    merge_auth_bearer_config,       /* merge server config */
+    auth_bearer_cmds,               /* command apr_table_t */
+    register_hooks                  /* register hooks */
+};



Re: svn commit: r1909411 - in /httpd/httpd/trunk: ./ docs/manual/mod/ modules/aaa/

Posted by Ruediger Pluem <rp...@apache.org>.
Any feedback on my comments below?

Regards

RĂ¼diger

On 5/5/23 7:37 PM, Ruediger Pluem wrote:
> 
> 
> On 4/25/23 7:52 PM, minfrin@apache.org wrote:
>> Author: minfrin
>> Date: Tue Apr 25 17:52:18 2023
>> New Revision: 1909411
>>
>> URL: http://svn.apache.org/viewvc?rev=1909411&view=rev
>> Log:
>>   *) mod_autht_jwt: New module to handle RFC 7519 JWT tokens within
>>      bearer tokens, both as part of the aaa framework, and as a way to
>>      generate tokens and pass them to backend servers and services.
>>
>>   *) mod_auth_bearer: New module to handle RFC 6750 Bearer tokens, using
>>      the token_checker hook.
>>
>>   *) mod_autht_core: New module to handle provider aliases for token
>>      authentication.
>>
>>
>> Added:
>>     httpd/httpd/trunk/docs/manual/mod/mod_auth_bearer.xml
>>     httpd/httpd/trunk/docs/manual/mod/mod_autht_core.xml
>>     httpd/httpd/trunk/docs/manual/mod/mod_autht_jwt.xml
>>     httpd/httpd/trunk/modules/aaa/mod_auth_bearer.c
>>     httpd/httpd/trunk/modules/aaa/mod_autht_core.c
>>     httpd/httpd/trunk/modules/aaa/mod_autht_jwt.c
>> Modified:
>>     httpd/httpd/trunk/CHANGES
>>     httpd/httpd/trunk/modules/aaa/config.m4
>>
> 
>> Added: httpd/httpd/trunk/modules/aaa/mod_autht_jwt.c
>> URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/aaa/mod_autht_jwt.c?rev=1909411&view=auto
>> ==============================================================================
>> --- httpd/httpd/trunk/modules/aaa/mod_autht_jwt.c (added)
>> +++ httpd/httpd/trunk/modules/aaa/mod_autht_jwt.c Tue Apr 25 17:52:18 2023
>> @@ -0,0 +1,1089 @@
>> +/* Licensed to the Apache Software Foundation (ASF) under one or more
>> + * contributor license agreements.  See the NOTICE file distributed with
>> + * this work for additional information regarding copyright ownership.
>> + * The ASF licenses this file to You under the Apache License, Version 2.0
>> + * (the "License"); you may not use this file except in compliance with
>> + * the License.  You may obtain a copy of the License at
>> + *
>> + *     http://www.apache.org/licenses/LICENSE-2.0
>> + *
>> + * Unless required by applicable law or agreed to in writing, software
>> + * distributed under the License is distributed on an "AS IS" BASIS,
>> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
>> + * See the License for the specific language governing permissions and
>> + * limitations under the License.
>> + */
>> +
>> +/**
>> + * This module adds support for https://tools.ietf.org/html/rfc7519 JWT tokens
>> + * as https://tools.ietf.org/html/rfc6750 Bearer tokens, both as a generator
>> + * of JWT bearer tokens, and as an acceptor of JWT Bearer tokens for authentication.
>> + */
>> +
>> +#include "apr_strings.h"
>> +#include "apr_hash.h"
>> +#include "apr_crypto.h"
>> +#include "apr_jose.h"
>> +#include "apr_lib.h"            /* for apr_isspace */
>> +#include "apr_base64.h"         /* for apr_base64_decode et al */
>> +#define APR_WANT_STRFUNC        /* for strcasecmp */
>> +#include "apr_want.h"
>> +
>> +#include "ap_config.h"
>> +#include "httpd.h"
>> +#include "http_config.h"
>> +#include "http_core.h"
>> +#include "http_log.h"
>> +#include "http_protocol.h"
>> +#include "http_request.h"
>> +#include "util_md5.h"
>> +#include "ap_provider.h"
>> +#include "ap_expr.h"
>> +
>> +#include "mod_auth.h"
>> +
>> +#define CRYPTO_KEY "auth_bearer_context"
>> +
>> +module AP_MODULE_DECLARE_DATA autht_jwt_module;
>> +
>> +typedef enum jws_alg_type_e {
>> +    /** No specific type. */
>> +    JWS_ALG_TYPE_NONE = 0,
>> +    /** HMAC SHA256 */
>> +    JWS_ALG_TYPE_HS256 = 1,
>> +} jws_alg_type_e;
>> +
>> +typedef struct {
>> +    unsigned char *secret;
>> +    apr_size_t secret_len;
>> +    jws_alg_type_e jws_alg;
>> +} auth_bearer_signature_rec;
>> +
>> +typedef struct {
>> +    apr_hash_t *claims;
>> +    apr_array_header_t *signs;
>> +    apr_array_header_t *verifies;
>> +    int signs_set:1;
>> +    int verifies_set:1;
>> +    int fake_set:1;
>> +} auth_bearer_config_rec;
>> +
>> +typedef struct {
>> +    const char *library;
>> +    const char *params;
>> +    apr_crypto_t **crypto;
> 
> 
> Why not apr_crypto_t *crypto instead and using &(var->crypto) where apr_crypto_t ** is needed below?
> 
>> +    int library_set;
>> +} auth_bearer_conf;
>> +
>> +static int auth_bearer_init(apr_pool_t *p, apr_pool_t *plog, apr_pool_t *ptemp,
>> +        server_rec *s) {
>> +    const apr_crypto_driver_t *driver = NULL;
>> +
>> +    /* auth_bearer_init() will be called twice. Don't bother
>> +     * going through all of the initialization on the first call
>> +     * because it will just be thrown away.*/
>> +    if (ap_state_query(AP_SQ_MAIN_STATE) == AP_SQ_MS_CREATE_PRE_CONFIG) {
>> +        return OK;
>> +    }
>> +
>> +    while (s) {
>> +
>> +        auth_bearer_conf *conf = ap_get_module_config(s->module_config,
>> +                &autht_jwt_module);
>> +
>> +        if (conf->library_set && !*conf->crypto) {
>> +
>> +            const apu_err_t *err = NULL;
>> +            apr_status_t rv;
>> +
>> +            rv = apr_crypto_init(p);
>> +            if (APR_SUCCESS != rv) {
>> +                ap_log_error(APLOG_MARK, APLOG_ERR, rv, s,
>> +                        APLOGNO() "APR crypto could not be initialised");
>> +                return rv;
>> +            }
>> +
>> +            rv = apr_crypto_get_driver(&driver, conf->library, conf->params,
>> +                    &err, p);
>> +            if (APR_EREINIT == rv) {
>> +                ap_log_error(APLOG_MARK, APLOG_WARNING, rv, s,
>> +                        APLOGNO() "warning: crypto for '%s' was already initialised, " "using existing configuration",
>> +                        conf->library);
>> +                rv = APR_SUCCESS;
>> +            }
>> +            if (APR_SUCCESS != rv && err) {
>> +                ap_log_error(APLOG_MARK, APLOG_ERR, rv, s,
>> +                        APLOGNO() "The crypto library '%s' could not be loaded: %s (%s: %d)",
>> +                        conf->library, err->msg, err->reason, err->rc);
>> +                return rv;
>> +            }
>> +            if (APR_ENOTIMPL == rv) {
>> +                ap_log_error(APLOG_MARK, APLOG_ERR, rv, s,
>> +                        APLOGNO() "The crypto library '%s' could not be found",
>> +                        conf->library);
>> +                return rv;
>> +            }
>> +            if (APR_SUCCESS != rv || !driver) {
>> +                ap_log_error(APLOG_MARK, APLOG_ERR, rv, s,
>> +                        APLOGNO() "The crypto library '%s' could not be loaded",
>> +                        conf->library);
>> +                return rv;
>> +            }
>> +
>> +            rv = apr_crypto_make(conf->crypto, driver, conf->params, p);
>> +            if (APR_SUCCESS != rv) {
>> +                ap_log_error(APLOG_MARK, APLOG_ERR, rv, s,
>> +                        APLOGNO() "The crypto library '%s' could not be initialised",
>> +                        conf->library);
>> +                return rv;
>> +            }
>> +
>> +            ap_log_error(APLOG_MARK, APLOG_INFO, rv, s,
>> +                    APLOGNO() "The crypto library '%s' was loaded successfully",
>> +                    conf->library);
>> +
>> +        }
>> +
>> +        s = s->next;
>> +    }
>> +
>> +    return OK;
>> +}
>> +
>> +static void *create_auth_bearer_config(apr_pool_t * p, server_rec *s)
>> +{
>> +    auth_bearer_conf *new =
>> +    (auth_bearer_conf *) apr_pcalloc(p, sizeof(auth_bearer_conf));
>> +
>> +    /* if no library has been configured, set the recommended library
>> +     * as a sensible default.
>> +     */
>> +#ifdef APU_CRYPTO_RECOMMENDED_DRIVER
>> +    new->library = APU_CRYPTO_RECOMMENDED_DRIVER;
>> +#endif
>> +    new->crypto = apr_pcalloc(p, sizeof(apr_crypto_t *));
>> +
>> +    return (void *) new;
>> +}
>> +
>> +static void *merge_auth_bearer_config(apr_pool_t * p, void *basev, void *addv)
>> +{
>> +    auth_bearer_conf *new = (auth_bearer_conf *) apr_pcalloc(p, sizeof(auth_bearer_conf));
>> +    auth_bearer_conf *add = (auth_bearer_conf *) addv;
>> +    auth_bearer_conf *base = (auth_bearer_conf *) basev;
>> +
>> +    new->library = (add->library_set == 0) ? base->library : add->library;
>> +    new->params = (add->library_set == 0) ? base->params : add->params;
>> +    new->library_set = add->library_set || base->library_set;
>> +
>> +    new->crypto = base->crypto;
>> +
>> +    return (void *) new;
>> +}
>> +
>> +static void *create_auth_bearer_dir_config(apr_pool_t *p, char *d)
>> +{
>> +    auth_bearer_config_rec *conf = apr_pcalloc(p, sizeof(*conf));
>> +
>> +    conf->claims = apr_hash_make(p);
>> +    conf->signs = apr_array_make(p, 1, sizeof(auth_bearer_signature_rec));
>> +    conf->verifies = apr_array_make(p, 1, sizeof(auth_bearer_signature_rec));
>> +
>> +    return conf;
>> +}
>> +
>> +static void *merge_auth_bearer_dir_config(apr_pool_t *p, void *basev, void *overridesv)
>> +{
>> +    auth_bearer_config_rec *newconf = apr_pcalloc(p, sizeof(*newconf));
>> +    auth_bearer_config_rec *base = basev;
>> +    auth_bearer_config_rec *overrides = overridesv;
>> +
>> +    newconf->claims = apr_hash_overlay(p, overrides->claims,
>> +                                          base->claims);
>> +
>> +    newconf->signs =
>> +            overrides->signs_set ? overrides->signs : base->signs;
>> +    newconf->signs_set = overrides->signs_set || base->signs_set;
>> +
>> +    newconf->verifies =
>> +            overrides->verifies_set ? overrides->verifies : base->verifies;
>> +    newconf->verifies_set = overrides->verifies_set || base->verifies_set;
>> +
>> +    return newconf;
>> +}
>> +
>> +static const char *set_jwt_claim(cmd_parms *cmd, void *config,
>> +        const char *op, const char *key, const char *expression)
>> +{
>> +    auth_bearer_config_rec *conf = (auth_bearer_config_rec *) config;
>> +    const char *err;
>> +
>> +    if (!strcasecmp(op, "set")) {
>> +        ap_expr_info_t *expr;
>> +
>> +        expr = ap_expr_parse_cmd(cmd, expression, AP_EXPR_FLAG_STRING_RESULT,
>> +                &err, NULL);
>> +        if (err) {
>> +            return apr_psprintf(cmd->pool,
>> +                    "Could not parse claim '%s' expression '%s': %s", key,
>> +                    expression, err);
>> +        }
>> +
>> +        apr_hash_set(conf->claims, key, APR_HASH_KEY_STRING, expr);
>> +
>> +    } else if (!strcasecmp(op, "unset")) {
>> +
>> +        apr_hash_set(conf->claims, key, APR_HASH_KEY_STRING, NULL);
>> +
>> +    } else {
>> +
>> +        return apr_psprintf(cmd->pool,
>> +                "Could not parse claim operation '%s', "
>> +                "values should be 'set' or 'unset'", op);
>> +
>> +    }
>> +
>> +    return NULL;
>> +}
>> +
>> +static const char *set_jwt_sign(cmd_parms * cmd, void *config,
>> +        const char *alg, const char *type, const char *sig)
>> +{
>> +    auth_bearer_config_rec *dconf = (auth_bearer_config_rec *) config;
>> +
>> +    auth_bearer_signature_rec *srec = apr_array_push(dconf->signs);
>> +
>> +    /* handle the algorithm */
>> +    if (!strcasecmp(alg, "none")) {
>> +        srec->jws_alg = JWS_ALG_TYPE_NONE;
>> +        if (type || sig) {
>> +            return "AuthtJwtSign: algorithm 'none' has extra parameters";
>> +        }
>> +    }
>> +    else if (!strcasecmp(alg, "HS256")) {
>> +        srec->jws_alg = JWS_ALG_TYPE_HS256;
>> +    }
>> +    else {
>> +        return apr_psprintf(cmd->pool, "AuthtJwtSign: algorithm not supported: %s", alg);
>> +    }
>> +
>> +    /* handle the file */
>> +    if (type) {
>> +        if (!strcasecmp(type, "file")) {
>> +            apr_file_t *file;
>> +            apr_finfo_t finfo;
>> +            apr_status_t status;
>> +
>> +            sig = ap_server_root_relative(cmd->temp_pool, sig);
>> +
>> +            status = apr_file_open(&file, sig, APR_READ | APR_BUFFERED,
>> +            APR_OS_DEFAULT, cmd->pool);
>> +            if (status != APR_SUCCESS) {
>> +                char buf[1024];
>> +                apr_strerror(status, buf, sizeof(buf));
>> +                return apr_psprintf(cmd->pool,
>> +                        "AuthtJwtSign: file '%s' could not be opened: %s", sig,
>> +                        buf);
>> +            }
>> +
>> +            status = apr_file_info_get(&finfo, APR_FINFO_TYPE | APR_FINFO_SIZE,
>> +                    file);
>> +            if (status != APR_SUCCESS) {
>> +                char buf[1024];
>> +                apr_strerror(status, buf, sizeof(buf));
>> +                return apr_psprintf(cmd->pool,
>> +                        "AuthtJwtSign: info could not be obtained for '%s': %s",
>> +                        sig, buf);
>> +            }
>> +
>> +            srec->secret = apr_palloc(cmd->pool, finfo.size);
>> +            srec->secret_len = finfo.size;
>> +
>> +            status = apr_file_read_full(file, srec->secret,
>> +                    srec->secret_len, NULL);
>> +            if (status != APR_SUCCESS) {
>> +                char buf[1024];
>> +                apr_strerror(status, buf, sizeof(buf));
>> +                return apr_psprintf(cmd->pool,
>> +                        "AuthtJwtSign: file '%s' could not be read: %s", sig,
>> +                        buf);
>> +            }
>> +
>> +            apr_file_close(file);
>> +
>> +        }
>> +        else {
>> +            return apr_psprintf(cmd->pool,
>> +                    "AuthtJwtSign: parameter '%s' is not 'file'", type);
>> +        }
>> +    }
>> +
>> +    dconf->signs_set = 1;
>> +
>> +    return NULL;
>> +}
>> +
>> +static const char *set_jwt_verify(cmd_parms * cmd, void *config,
>> +        const char *alg, const char *type, const char *sig)
>> +{
>> +    auth_bearer_config_rec *dconf = (auth_bearer_config_rec *) config;
>> +
>> +    auth_bearer_signature_rec *srec = apr_array_push(dconf->verifies);
>> +
>> +    /* handle the algorithm */
>> +    if (!strcasecmp(alg, "none")) {
>> +        srec->jws_alg = JWS_ALG_TYPE_NONE;
>> +        if (type || sig) {
>> +            return "AuthtJwtVerify: algorithm 'none' has extra parameters";
>> +        }
>> +    }
>> +    else if (!strcasecmp(alg, "HS256")) {
>> +        srec->jws_alg = JWS_ALG_TYPE_HS256;
>> +    }
>> +    else {
>> +        return apr_psprintf(cmd->pool, "AuthtJwtVerify: algorithm not supported: %s", alg);
>> +    }
>> +
>> +    /* handle the file */
>> +    if (type) {
>> +        if (!strcasecmp(type, "file")) {
>> +            apr_file_t *file;
>> +            apr_finfo_t finfo;
>> +            apr_status_t status;
>> +
>> +            sig = ap_server_root_relative(cmd->temp_pool, sig);
>> +
>> +            status = apr_file_open(&file, sig, APR_READ | APR_BUFFERED,
>> +            APR_OS_DEFAULT, cmd->pool);
>> +            if (status != APR_SUCCESS) {
>> +                char buf[1024];
>> +                apr_strerror(status, buf, sizeof(buf));
>> +                return apr_psprintf(cmd->pool,
>> +                        "AuthtJwtVerify: file '%s' could not be opened: %s", sig,
>> +                        buf);
>> +            }
>> +
>> +            status = apr_file_info_get(&finfo, APR_FINFO_TYPE | APR_FINFO_SIZE,
>> +                    file);
>> +            if (status != APR_SUCCESS) {
>> +                char buf[1024];
>> +                apr_strerror(status, buf, sizeof(buf));
>> +                return apr_psprintf(cmd->pool,
>> +                        "AuthtJwtVerify: info could not be obtained for '%s': %s",
>> +                        sig, buf);
>> +            }
>> +
>> +            srec->secret = apr_palloc(cmd->pool, finfo.size);
>> +            srec->secret_len = finfo.size;
>> +
>> +            status = apr_file_read_full(file, srec->secret,
>> +                    srec->secret_len, NULL);
>> +            if (status != APR_SUCCESS) {
>> +                char buf[1024];
>> +                apr_strerror(status, buf, sizeof(buf));
>> +                return apr_psprintf(cmd->pool,
>> +                        "AuthtJwtVerify: file '%s' could not be read: %s", sig,
>> +                        buf);
>> +            }
>> +
>> +            apr_file_close(file);
>> +
>> +        }
>> +        else {
>> +            return apr_psprintf(cmd->pool,
>> +                    "AuthtJwtVerify: parameter '%s' is not 'file'", type);
>> +        }
>> +    }
>> +
>> +    dconf->verifies_set = 1;
>> +
>> +    return NULL;
>> +}
> 
> *set_jwt_sign and set_jwt_verify share a lot of code. Can't this be factored out into a common function?
> 
>> +
>> +static const char *set_jwt_driver(cmd_parms * cmd, void *config, const char *arg)
>> +{
>> +    auth_bearer_conf *conf =
>> +            (auth_bearer_conf *)ap_get_module_config(cmd->server->module_config,
>> +            &autht_jwt_module);
>> +
>> +    const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
>> +
>> +    if (err != NULL) {
>> +        return err;
>> +    }
>> +
>> +    conf->library = ap_getword_conf(cmd->pool, &arg);
>> +    conf->params = arg;
>> +    conf->library_set = 1;
>> +
>> +    return NULL;
>> +}
>> +
>> +static const command_rec auth_bearer_cmds[] =
>> +{
>> +    AP_INIT_TAKE13("AuthtJwtVerify", set_jwt_verify, NULL, RSRC_CONF|OR_AUTHCFG,
>> +                   "The JWS signing algorithm and passphrase/key to verify an incoming JWT token"),
>> +    AP_INIT_TAKE13("AuthtJwtSign", set_jwt_sign, NULL, RSRC_CONF|OR_AUTHCFG,
>> +            "The JWS signing algorithm and passphrase/key to sign an outgoing JWT token"),
>> +
>> +    AP_INIT_TAKE23("AuthtJwtClaim", set_jwt_claim, NULL, OR_AUTHCFG,
>> +            "Set a claim with the given name and expression, or "
>> +            "unset the claim with the given name."),
>> +
>> +    AP_INIT_RAW_ARGS("AuthtJwtDriver", set_jwt_driver, NULL, RSRC_CONF,
>> +            "The underlying crypto library driver to use"),
>> +
>> +    {NULL}
>> +};
>> +
>> +typedef struct claim_iter_t {
>> +    request_rec *r;
>> +    apr_json_value_t *object;
>> +} claim_iter_t;
>> +
>> +static int claim_iter(void *ctx, const void *key, apr_ssize_t klen,
>> +                     const void *val)
>> +{
>> +    const char *err, *value;
>> +    claim_iter_t *iter = ctx;
>> +    request_rec *r = iter->r;
>> +
>> +    value = ap_expr_str_exec(r, val, &err);
>> +    if (err) {
>> +        ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO()
>> +                "AuthtJwtClaim: could not evaluate '%s' expression "
>> +                "'%s' for URI '%s': %s",
>> +                (char * )key, (char * )val, r->uri, err);
>> +        return FALSE;
>> +    }
>> +
>> +
>> +    apr_json_object_set(iter->object, key, klen,
>> +            apr_json_string_create(r->pool, value, strlen(value)), r->pool);
>> +
>> +    return TRUE;
>> +}
>> +
>> +static apr_status_t sign_cb(apr_bucket_brigade *bb, apr_jose_t *jose,
>> +        apr_jose_signature_t *signature, void *ctx, apr_pool_t *pool) {
>> +    auth_bearer_signature_rec *srec = NULL;
>> +    request_rec *r = ctx;
>> +
>> +    auth_bearer_conf *sconf = ap_get_module_config(r->server->module_config,
>> +            &autht_jwt_module);
>> +
>> +    auth_bearer_config_rec *conf = ap_get_module_config(r->per_dir_config,
>> +            &autht_jwt_module);
>> +
>> +    if (conf->signs_set) {
>> +        srec = (auth_bearer_signature_rec *) conf->signs->elts;
>> +    }
>> +
>> +    if (srec) {
> 
> srec points to the first element of an array. Why only processing the first element?
> 
>> +        switch (srec->jws_alg) {
>> +        case JWS_ALG_TYPE_NONE: {
>> +
>> +            return APR_SUCCESS;
>> +        }
>> +        case JWS_ALG_TYPE_HS256: {
>> +            apr_bucket *e;
>> +            apr_crypto_key_rec_t *krec;
>> +            apr_crypto_key_t *key = NULL;
>> +            apr_crypto_digest_t *digest = NULL;
>> +            apr_crypto_digest_rec_t *rec;
>> +            char * buf;
>> +            apr_status_t status;
>> +
>> +            if (!*sconf->crypto) {
>> +                jose->result.msg = "token could not be signed";
>> +                jose->result.reason = "no crypto driver configured (set AuthtJwtDriver)";
>> +                return APR_EGENERAL;
>> +            }
>> +
>> +            krec = apr_crypto_key_rec_make(APR_CRYPTO_KTYPE_HMAC, pool);
>> +
>> +            krec->k.hmac.digest = APR_CRYPTO_DIGEST_SHA256;
>> +            krec->k.hmac.secret = srec->secret;
>> +            krec->k.hmac.secretLen = srec->secret_len;
>> +
>> +            status = apr_crypto_key(&key, krec, *sconf->crypto, pool);
>> +            if (status != APR_SUCCESS) {
>> +                jose->result.reason = buf = apr_pcalloc(pool, HUGE_STRING_LEN);
>> +                apr_strerror(status, buf, HUGE_STRING_LEN);
>> +                jose->result.msg = "token could not be signed";
>> +                return status;
>> +            }
>> +
>> +            rec = apr_crypto_digest_rec_make(APR_CRYPTO_DTYPE_SIGN, pool);
>> +
>> +            status = apr_crypto_digest_init(&digest, key, rec, pool);
>> +            if (status != APR_SUCCESS) {
>> +                jose->result.reason = buf = apr_pcalloc(pool, HUGE_STRING_LEN);
>> +                apr_strerror(status, buf, HUGE_STRING_LEN);
>> +                jose->result.msg = "token could not be signed";
>> +                return status;
>> +            }
>> +
>> +            for (e = APR_BRIGADE_FIRST(bb); e != APR_BRIGADE_SENTINEL(bb); e =
>> +                    APR_BUCKET_NEXT(e)) {
>> +                const char *str;
>> +                apr_size_t len;
>> +
>> +                /* If we see an EOS, don't bother doing anything more. */
>> +                if (APR_BUCKET_IS_EOS(e)) {
>> +                    break;
>> +                }
>> +
>> +                status = apr_bucket_read(e, &str, &len, APR_BLOCK_READ);
>> +                if (status != APR_SUCCESS) {
>> +                    jose->result.reason = buf = apr_pcalloc(pool, HUGE_STRING_LEN);
>> +                    apr_strerror(status, buf, HUGE_STRING_LEN);
>> +                    jose->result.msg = "token could not be signed";
>> +                    return status;
>> +                }
>> +
>> +                status = apr_crypto_digest_update(digest,
>> +                        (const unsigned char *) str, len);
>> +                if (status != APR_SUCCESS) {
>> +                    jose->result.reason = buf = apr_pcalloc(pool, HUGE_STRING_LEN);
>> +                    apr_strerror(status, buf, HUGE_STRING_LEN);
>> +                    jose->result.msg = "token could not be signed";
>> +                    return status;
>> +                }
>> +            }
>> +
>> +            status = apr_crypto_digest_final(digest);
>> +            if (status != APR_SUCCESS) {
>> +                jose->result.reason = buf = apr_pcalloc(pool, HUGE_STRING_LEN);
>> +                apr_strerror(status, buf, HUGE_STRING_LEN);
>> +                jose->result.msg = "token could not be signed";
>> +                return status;
>> +            }
>> +
>> +            signature->sig.data = rec->d.sign.s;
>> +            signature->sig.len = rec->d.sign.slen;
>> +
>> +            return APR_SUCCESS;
>> +        }
>> +        }
>> +    }
>> +    else {
>> +        /* algorithm is none */
>> +        return APR_SUCCESS;
>> +    }
>> +
>> +    return APR_ENOTIMPL;
>> +}
>> +
>> +/* If we have set claims to be made, create a JWT token.
>> + */
>> +static const char *jwt_get_token(request_rec *r)
>> +{
>> +    claim_iter_t iter = { 0 };
>> +    apr_json_value_t *claims;
>> +    apr_json_value_t *protect;
>> +    apr_jose_t jwt = { 0 };
>> +    apr_jose_t jws = { 0 };
>> +    apr_jose_signature_t signature = { 0 };
>> +    auth_bearer_signature_rec *srec = NULL;
>> +    apr_bucket_brigade *bb;
>> +    char *auth_line;
>> +    apr_size_t len;
>> +    apr_off_t offset;
>> +    apr_status_t status;
>> +
>> +    auth_bearer_config_rec *conf = ap_get_module_config(r->per_dir_config,
>> +                                                       &autht_jwt_module);
>> +
>> +    apr_jose_cb_t cb = { 0 };
>> +
>> +    cb.sign = sign_cb;
>> +    cb.ctx = r;
>> +
>> +    if (!conf->claims || !apr_hash_count(conf->claims)) {
>> +        ap_log_rerror(APLOG_MARK, APLOG_ERR, APR_SUCCESS, r,
>> +                APLOGNO() "AuthtJwtClaim: could not encode a JWT token for URI '%s': no claims",
>> +                r->uri);
>> +        return "error:no-claims";
>> +    }
>> +
>> +    if (conf->verifies_set) {
>> +        srec = (auth_bearer_signature_rec *)conf->verifies->elts;
>> +    }
>> +
>> +    /* create a JWT containing the claims */
>> +    claims = apr_json_object_create(r->pool);
>> +    iter.object = claims;
>> +    iter.r = r;
>> +
>> +    /* iterate through our claims */
>> +    if (!apr_hash_do(claim_iter, &iter, conf->claims)) {
>> +        return "error:claim-failed";
>> +    }
>> +
>> +    apr_jose_jwt_make(&jwt, claims, r->pool);
>> +    protect = apr_json_object_create(r->pool);
>> +
>> +    apr_json_object_set(protect, APR_JOSE_JWSE_TYPE,
>> +            APR_JSON_VALUE_STRING,
>> +            apr_json_string_create(r->pool, APR_JOSE_JWSE_TYPE_JWT,
>> +                    APR_JSON_VALUE_STRING), r->pool);
>> +
>> +    if (srec) {
> 
> Again: srec points to the first element of an array. Why only processing the first element?
> 
>> +        /* which signature type do we have? */
>> +        switch (srec->jws_alg) {
>> +        case JWS_ALG_TYPE_NONE: {
>> +            apr_json_object_set(protect, APR_JOSE_JWKSE_ALGORITHM,
>> +                    APR_JSON_VALUE_STRING,
>> +                    apr_json_string_create(r->pool, APR_JOSE_JWA_NONE,
>> +                            APR_JSON_VALUE_STRING), r->pool);
>> +
>> +            break;
>> +        }
>> +        case JWS_ALG_TYPE_HS256: {
>> +            apr_json_object_set(protect, APR_JOSE_JWKSE_ALGORITHM,
>> +                    APR_JSON_VALUE_STRING,
>> +                    apr_json_string_create(r->pool, APR_JOSE_JWA_HS256,
>> +                            APR_JSON_VALUE_STRING), r->pool);
>> +
>> +            break;
>> +        }
>> +        }
>> +
>> +    }
>> +    else {
>> +        /* no srec defaults to none */
>> +        apr_json_object_set(protect, APR_JOSE_JWKSE_ALGORITHM,
>> +                APR_JSON_VALUE_STRING,
>> +                apr_json_string_create(r->pool, APR_JOSE_JWA_NONE,
>> +                        APR_JSON_VALUE_STRING), r->pool);
>> +    }
>> +
>> +    apr_jose_signature_make(&signature, NULL, protect, r->pool);
>> +    apr_jose_jws_make(&jws, &signature, NULL, &jwt, r->pool);
>> +
>> +    bb = apr_brigade_create(r->pool, r->connection->bucket_alloc);
>> +
>> +    status = apr_jose_encode(bb, NULL, NULL, &jws, &cb, r->pool);
>> +    if (APR_SUCCESS != status) {
>> +        const apu_err_t *err = apr_jose_error(&jws);
>> +        ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r,
>> +                APLOGNO() "AuthtJwtClaim: could not encode a JWT token for URI '%s': %s: %s",
>> +                r->uri, err->msg, err->reason);
>> +        return "error:could-not-encode";
>> +    }
>> +
>> +    apr_brigade_length(bb, 1, &offset);
>> +    auth_line = apr_pcalloc(r->pool, offset + 1);
>> +    len = offset;
>> +    apr_brigade_flatten(bb, auth_line, &len);
>> +    auth_line[offset] = 0;
>> +
>> +    return auth_line;
>> +}
>> +
>> +static const char *jwt_expr_var_fn(ap_expr_eval_ctx_t *ctx, const void *data)
>> +{
>> +    char *var = (char *)data;
>> +
>> +    if (var && *var && ctx->r && ap_cstr_casecmp(var, "TOKEN") == 0) {
>> +        return jwt_get_token(ctx->r);
>> +    }
>> +    return NULL;
>> +}
>> +
>> +static int jwt_expr_lookup(ap_expr_lookup_parms *parms)
>> +{
>> +    switch (parms->type) {
>> +    case AP_EXPR_FUNC_VAR:
>> +        /* for now, we just handle everything that starts with JWT_.
>> +         */
>> +        if (strncasecmp(parms->name, "JWT_", 4) == 0) {
>> +            *parms->func = jwt_expr_var_fn;
>> +            *parms->data = parms->name + 4;
>> +            return OK;
>> +        }
>> +        break;
>> +    }
>> +    return DECLINED;
>> +}
>> +
>> +static apr_status_t verify_cb(apr_bucket_brigade *bb,
>> +        apr_jose_t *jose, apr_jose_signature_t *signature, void *ctx,
>> +        int *vflags, apr_pool_t *pool)
>> +{
>> +    request_rec *r = ctx;
>> +    apr_json_kv_t *alg = NULL;
>> +
>> +    auth_bearer_conf *sconf = ap_get_module_config(r->server->module_config,
>> +            &autht_jwt_module);
>> +
>> +    auth_bearer_config_rec *conf = ap_get_module_config(r->per_dir_config,
>> +                                                       &autht_jwt_module);
>> +
>> +    int alg_supported = 0;
>> +
>> +    if (signature) {
>> +        apr_json_value_t *ph = signature->protected_header;
>> +
>> +        if (ph && ph->type == APR_JSON_OBJECT) {
>> +
>> +            alg = apr_json_object_get(ph, APR_JOSE_JWKSE_ALGORITHM,
>> +                    APR_JSON_VALUE_STRING);
>> +
>> +        }
>> +    }
>> +
>> +    if (!alg) {
>> +        apr_errprintf(&jose->result, r->pool, "", APR_EGENERAL,
>> +                "JWT token protected header has no '"
>> +                APR_JOSE_JWKSE_ALGORITHM
>> +                "' for URI '%s'",
>> +                r->uri);
>> +        return APR_EGENERAL;
>> +    }
>> +
>> +    if (alg->v->type != APR_JSON_STRING) {
>> +        apr_errprintf(&jose->result, r->pool, "", APR_EGENERAL,
>> +                "JWT token protected header '"
>> +                APR_JOSE_JWKSE_ALGORITHM
>> +                "' is not a string for URI '%s'",
>> +                r->uri);
>> +        return APR_EGENERAL;
>> +    }
>> +
>> +    /* first pass, is our algorithm supported? */
>> +    for (int i = 0; i < conf->verifies->nelts; i++) {
>> +    	auth_bearer_signature_rec *srec = &APR_ARRAY_IDX(conf->verifies,
>> +    			i, auth_bearer_signature_rec);
>> +
>> +    	/* which signature type do we have? */
>> +    	switch (srec->jws_alg) {
>> +    	case JWS_ALG_TYPE_NONE: {
>> +        	if (!strncmp(alg->v->value.string.p, "none",
>> +                        alg->v->value.string.len)) {
>> +        		alg_supported = 1;
>> +        	}
>> +        	break;
>> +    	}
>> +    	case JWS_ALG_TYPE_HS256: {
>> +        	if (!strncmp(alg->v->value.string.p, "HS256",
>> +        			alg->v->value.string.len)) {
>> +        		alg_supported = 1;
>> +        	}
>> +    		break;
>> +    	}
> 
> Why don't leave the loop as soon as alg_supported is 1?
> 
>> +    	}
>> +
>> +    }
>> +
>> +    /* we don't support the algorithm */
>> +    if (!alg_supported) {
>> +    	apr_errprintf(&jose->result, r->pool, "", APR_ENODIGEST,
>> +    			"JWT token protected header '"
>> +    			APR_JOSE_JWKSE_ALGORITHM
>> +				"' %s is not supported for URI '%s'",
>> +				alg->v->value.string.p, r->uri);
>> +    	return APR_ENODIGEST;
>> +    }
>> +
>> +    /* second pass, does the signature match? */
>> +    for (int i = 0; i < conf->verifies->nelts; i++) {
>> +    	auth_bearer_signature_rec *srec = &APR_ARRAY_IDX(conf->verifies,
>> +    			i, auth_bearer_signature_rec);
>> +
>> +    	/* which signature type do we have? */
>> +    	switch (srec->jws_alg) {
>> +    	case JWS_ALG_TYPE_NONE: {
>> +        	if (!strncmp(alg->v->value.string.p, "none",
>> +                        alg->v->value.string.len)) {
>> +        		return APR_SUCCESS;
>> +        	}
>> +        	break;
>> +    	}
>> +    	case JWS_ALG_TYPE_HS256: {
>> +        	if (!strncmp(alg->v->value.string.p, "HS256",
>> +        			alg->v->value.string.len)) {
>> +
>> +                apr_bucket *e;
>> +                apr_crypto_key_rec_t *krec;
>> +                apr_crypto_key_t *key = NULL;
>> +                apr_crypto_digest_t *digest = NULL;
>> +                apr_crypto_digest_rec_t *rec;
>> +                char * buf;
>> +                apr_status_t status;
>> +
>> +                if (!*sconf->crypto) {
>> +                    jose->result.msg = "token could not be verified";
>> +                    jose->result.reason = "no crypto driver configured (set AuthtJwtDriver)";
>> +                    return APR_EGENERAL;
>> +                }
>> +
>> +                krec = apr_crypto_key_rec_make(APR_CRYPTO_KTYPE_HMAC, pool);
>> +
>> +                krec->k.hmac.digest = APR_CRYPTO_DIGEST_SHA256;
>> +                krec->k.hmac.secret = srec->secret;
>> +                krec->k.hmac.secretLen = srec->secret_len;
>> +
>> +                status = apr_crypto_key(&key, krec, *sconf->crypto, pool);
>> +                if (status != APR_SUCCESS) {
>> +                    jose->result.reason = buf = apr_pcalloc(pool, HUGE_STRING_LEN);
>> +                    apr_strerror(status, buf, HUGE_STRING_LEN);
>> +                    jose->result.msg = "token could not be verified";
>> +                    return status;
>> +                }
>> +
>> +                rec = apr_crypto_digest_rec_make(APR_CRYPTO_DTYPE_SIGN, pool);
>> +
>> +                status = apr_crypto_digest_init(&digest, key, rec, pool);
>> +                if (status != APR_SUCCESS) {
>> +                    jose->result.reason = buf = apr_pcalloc(pool, HUGE_STRING_LEN);
>> +                    apr_strerror(status, buf, HUGE_STRING_LEN);
>> +                    jose->result.msg = "token could not be verified";
>> +                    return status;
>> +                }
>> +
>> +                for (e = APR_BRIGADE_FIRST(bb); e != APR_BRIGADE_SENTINEL(bb); e =
>> +                        APR_BUCKET_NEXT(e)) {
>> +                    const char *str;
>> +                    apr_size_t len;
>> +
>> +                    /* If we see an EOS, don't bother doing anything more. */
>> +                    if (APR_BUCKET_IS_EOS(e)) {
>> +                        break;
>> +                    }
>> +
>> +                    status = apr_bucket_read(e, &str, &len, APR_BLOCK_READ);
>> +                    if (status != APR_SUCCESS) {
>> +                        jose->result.reason = buf = apr_pcalloc(pool, HUGE_STRING_LEN);
>> +                        apr_strerror(status, buf, HUGE_STRING_LEN);
>> +                        jose->result.msg = "token could not be verified";
>> +                        return status;
>> +                    }
>> +
>> +                    status = apr_crypto_digest_update(digest,
>> +                            (const unsigned char *) str, len);
>> +                    if (status != APR_SUCCESS) {
>> +                        jose->result.reason = buf = apr_pcalloc(pool, HUGE_STRING_LEN);
>> +                        apr_strerror(status, buf, HUGE_STRING_LEN);
>> +                        jose->result.msg = "token could not be verified";
>> +                        return status;
>> +                    }
>> +                }
>> +
>> +                status = apr_crypto_digest_final(digest);
>> +                if (status != APR_SUCCESS) {
>> +                    jose->result.reason = buf = apr_pcalloc(pool, HUGE_STRING_LEN);
>> +                    apr_strerror(status, buf, HUGE_STRING_LEN);
>> +                    jose->result.msg = "token could not be verified";
>> +                    return status;
>> +                }
>> +
>> +                if (signature->sig.len == rec->d.sign.slen &&
>> +                		!memcmp(signature->sig.data, rec->d.sign.s, rec->d.sign.slen)) {
>> +                    return APR_SUCCESS;
>> +                }
>> +
>> +        	}
>> +    		break;
>> +    	}
>> +    	}
>> +
>> +    }
>> +
>> +    /* no match, oh well */
>> +	apr_errprintf(&jose->result, r->pool, "", APR_ENODIGEST,
>> +			"JWT token protected header '"
>> +			APR_JOSE_JWKSE_ALGORITHM
>> +			"' %s is not supported for URI '%s'",
>> +			alg->v->value.string.p, r->uri);
>> +    return APR_ENOVERIFY;
>> +}
> 
> sign_cb and verify_cb seem to share quite some amount of code. Can't this be factored out into a common function?
> 
> Regards
> 
> RĂ¼diger
> 

Re: svn commit: r1909411 - in /httpd/httpd/trunk: ./ docs/manual/mod/ modules/aaa/

Posted by Ruediger Pluem <rp...@apache.org>.

On 4/25/23 7:52 PM, minfrin@apache.org wrote:
> Author: minfrin
> Date: Tue Apr 25 17:52:18 2023
> New Revision: 1909411
> 
> URL: http://svn.apache.org/viewvc?rev=1909411&view=rev
> Log:
>   *) mod_autht_jwt: New module to handle RFC 7519 JWT tokens within
>      bearer tokens, both as part of the aaa framework, and as a way to
>      generate tokens and pass them to backend servers and services.
> 
>   *) mod_auth_bearer: New module to handle RFC 6750 Bearer tokens, using
>      the token_checker hook.
> 
>   *) mod_autht_core: New module to handle provider aliases for token
>      authentication.
> 
> 
> Added:
>     httpd/httpd/trunk/docs/manual/mod/mod_auth_bearer.xml
>     httpd/httpd/trunk/docs/manual/mod/mod_autht_core.xml
>     httpd/httpd/trunk/docs/manual/mod/mod_autht_jwt.xml
>     httpd/httpd/trunk/modules/aaa/mod_auth_bearer.c
>     httpd/httpd/trunk/modules/aaa/mod_autht_core.c
>     httpd/httpd/trunk/modules/aaa/mod_autht_jwt.c
> Modified:
>     httpd/httpd/trunk/CHANGES
>     httpd/httpd/trunk/modules/aaa/config.m4
> 

> Added: httpd/httpd/trunk/modules/aaa/mod_autht_jwt.c
> URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/aaa/mod_autht_jwt.c?rev=1909411&view=auto
> ==============================================================================
> --- httpd/httpd/trunk/modules/aaa/mod_autht_jwt.c (added)
> +++ httpd/httpd/trunk/modules/aaa/mod_autht_jwt.c Tue Apr 25 17:52:18 2023
> @@ -0,0 +1,1089 @@
> +/* Licensed to the Apache Software Foundation (ASF) under one or more
> + * contributor license agreements.  See the NOTICE file distributed with
> + * this work for additional information regarding copyright ownership.
> + * The ASF licenses this file to You under the Apache License, Version 2.0
> + * (the "License"); you may not use this file except in compliance with
> + * the License.  You may obtain a copy of the License at
> + *
> + *     http://www.apache.org/licenses/LICENSE-2.0
> + *
> + * Unless required by applicable law or agreed to in writing, software
> + * distributed under the License is distributed on an "AS IS" BASIS,
> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
> + * See the License for the specific language governing permissions and
> + * limitations under the License.
> + */
> +
> +/**
> + * This module adds support for https://tools.ietf.org/html/rfc7519 JWT tokens
> + * as https://tools.ietf.org/html/rfc6750 Bearer tokens, both as a generator
> + * of JWT bearer tokens, and as an acceptor of JWT Bearer tokens for authentication.
> + */
> +
> +#include "apr_strings.h"
> +#include "apr_hash.h"
> +#include "apr_crypto.h"
> +#include "apr_jose.h"
> +#include "apr_lib.h"            /* for apr_isspace */
> +#include "apr_base64.h"         /* for apr_base64_decode et al */
> +#define APR_WANT_STRFUNC        /* for strcasecmp */
> +#include "apr_want.h"
> +
> +#include "ap_config.h"
> +#include "httpd.h"
> +#include "http_config.h"
> +#include "http_core.h"
> +#include "http_log.h"
> +#include "http_protocol.h"
> +#include "http_request.h"
> +#include "util_md5.h"
> +#include "ap_provider.h"
> +#include "ap_expr.h"
> +
> +#include "mod_auth.h"
> +
> +#define CRYPTO_KEY "auth_bearer_context"
> +
> +module AP_MODULE_DECLARE_DATA autht_jwt_module;
> +
> +typedef enum jws_alg_type_e {
> +    /** No specific type. */
> +    JWS_ALG_TYPE_NONE = 0,
> +    /** HMAC SHA256 */
> +    JWS_ALG_TYPE_HS256 = 1,
> +} jws_alg_type_e;
> +
> +typedef struct {
> +    unsigned char *secret;
> +    apr_size_t secret_len;
> +    jws_alg_type_e jws_alg;
> +} auth_bearer_signature_rec;
> +
> +typedef struct {
> +    apr_hash_t *claims;
> +    apr_array_header_t *signs;
> +    apr_array_header_t *verifies;
> +    int signs_set:1;
> +    int verifies_set:1;
> +    int fake_set:1;
> +} auth_bearer_config_rec;
> +
> +typedef struct {
> +    const char *library;
> +    const char *params;
> +    apr_crypto_t **crypto;


Why not apr_crypto_t *crypto instead and using &(var->crypto) where apr_crypto_t ** is needed below?

> +    int library_set;
> +} auth_bearer_conf;
> +
> +static int auth_bearer_init(apr_pool_t *p, apr_pool_t *plog, apr_pool_t *ptemp,
> +        server_rec *s) {
> +    const apr_crypto_driver_t *driver = NULL;
> +
> +    /* auth_bearer_init() will be called twice. Don't bother
> +     * going through all of the initialization on the first call
> +     * because it will just be thrown away.*/
> +    if (ap_state_query(AP_SQ_MAIN_STATE) == AP_SQ_MS_CREATE_PRE_CONFIG) {
> +        return OK;
> +    }
> +
> +    while (s) {
> +
> +        auth_bearer_conf *conf = ap_get_module_config(s->module_config,
> +                &autht_jwt_module);
> +
> +        if (conf->library_set && !*conf->crypto) {
> +
> +            const apu_err_t *err = NULL;
> +            apr_status_t rv;
> +
> +            rv = apr_crypto_init(p);
> +            if (APR_SUCCESS != rv) {
> +                ap_log_error(APLOG_MARK, APLOG_ERR, rv, s,
> +                        APLOGNO() "APR crypto could not be initialised");
> +                return rv;
> +            }
> +
> +            rv = apr_crypto_get_driver(&driver, conf->library, conf->params,
> +                    &err, p);
> +            if (APR_EREINIT == rv) {
> +                ap_log_error(APLOG_MARK, APLOG_WARNING, rv, s,
> +                        APLOGNO() "warning: crypto for '%s' was already initialised, " "using existing configuration",
> +                        conf->library);
> +                rv = APR_SUCCESS;
> +            }
> +            if (APR_SUCCESS != rv && err) {
> +                ap_log_error(APLOG_MARK, APLOG_ERR, rv, s,
> +                        APLOGNO() "The crypto library '%s' could not be loaded: %s (%s: %d)",
> +                        conf->library, err->msg, err->reason, err->rc);
> +                return rv;
> +            }
> +            if (APR_ENOTIMPL == rv) {
> +                ap_log_error(APLOG_MARK, APLOG_ERR, rv, s,
> +                        APLOGNO() "The crypto library '%s' could not be found",
> +                        conf->library);
> +                return rv;
> +            }
> +            if (APR_SUCCESS != rv || !driver) {
> +                ap_log_error(APLOG_MARK, APLOG_ERR, rv, s,
> +                        APLOGNO() "The crypto library '%s' could not be loaded",
> +                        conf->library);
> +                return rv;
> +            }
> +
> +            rv = apr_crypto_make(conf->crypto, driver, conf->params, p);
> +            if (APR_SUCCESS != rv) {
> +                ap_log_error(APLOG_MARK, APLOG_ERR, rv, s,
> +                        APLOGNO() "The crypto library '%s' could not be initialised",
> +                        conf->library);
> +                return rv;
> +            }
> +
> +            ap_log_error(APLOG_MARK, APLOG_INFO, rv, s,
> +                    APLOGNO() "The crypto library '%s' was loaded successfully",
> +                    conf->library);
> +
> +        }
> +
> +        s = s->next;
> +    }
> +
> +    return OK;
> +}
> +
> +static void *create_auth_bearer_config(apr_pool_t * p, server_rec *s)
> +{
> +    auth_bearer_conf *new =
> +    (auth_bearer_conf *) apr_pcalloc(p, sizeof(auth_bearer_conf));
> +
> +    /* if no library has been configured, set the recommended library
> +     * as a sensible default.
> +     */
> +#ifdef APU_CRYPTO_RECOMMENDED_DRIVER
> +    new->library = APU_CRYPTO_RECOMMENDED_DRIVER;
> +#endif
> +    new->crypto = apr_pcalloc(p, sizeof(apr_crypto_t *));
> +
> +    return (void *) new;
> +}
> +
> +static void *merge_auth_bearer_config(apr_pool_t * p, void *basev, void *addv)
> +{
> +    auth_bearer_conf *new = (auth_bearer_conf *) apr_pcalloc(p, sizeof(auth_bearer_conf));
> +    auth_bearer_conf *add = (auth_bearer_conf *) addv;
> +    auth_bearer_conf *base = (auth_bearer_conf *) basev;
> +
> +    new->library = (add->library_set == 0) ? base->library : add->library;
> +    new->params = (add->library_set == 0) ? base->params : add->params;
> +    new->library_set = add->library_set || base->library_set;
> +
> +    new->crypto = base->crypto;
> +
> +    return (void *) new;
> +}
> +
> +static void *create_auth_bearer_dir_config(apr_pool_t *p, char *d)
> +{
> +    auth_bearer_config_rec *conf = apr_pcalloc(p, sizeof(*conf));
> +
> +    conf->claims = apr_hash_make(p);
> +    conf->signs = apr_array_make(p, 1, sizeof(auth_bearer_signature_rec));
> +    conf->verifies = apr_array_make(p, 1, sizeof(auth_bearer_signature_rec));
> +
> +    return conf;
> +}
> +
> +static void *merge_auth_bearer_dir_config(apr_pool_t *p, void *basev, void *overridesv)
> +{
> +    auth_bearer_config_rec *newconf = apr_pcalloc(p, sizeof(*newconf));
> +    auth_bearer_config_rec *base = basev;
> +    auth_bearer_config_rec *overrides = overridesv;
> +
> +    newconf->claims = apr_hash_overlay(p, overrides->claims,
> +                                          base->claims);
> +
> +    newconf->signs =
> +            overrides->signs_set ? overrides->signs : base->signs;
> +    newconf->signs_set = overrides->signs_set || base->signs_set;
> +
> +    newconf->verifies =
> +            overrides->verifies_set ? overrides->verifies : base->verifies;
> +    newconf->verifies_set = overrides->verifies_set || base->verifies_set;
> +
> +    return newconf;
> +}
> +
> +static const char *set_jwt_claim(cmd_parms *cmd, void *config,
> +        const char *op, const char *key, const char *expression)
> +{
> +    auth_bearer_config_rec *conf = (auth_bearer_config_rec *) config;
> +    const char *err;
> +
> +    if (!strcasecmp(op, "set")) {
> +        ap_expr_info_t *expr;
> +
> +        expr = ap_expr_parse_cmd(cmd, expression, AP_EXPR_FLAG_STRING_RESULT,
> +                &err, NULL);
> +        if (err) {
> +            return apr_psprintf(cmd->pool,
> +                    "Could not parse claim '%s' expression '%s': %s", key,
> +                    expression, err);
> +        }
> +
> +        apr_hash_set(conf->claims, key, APR_HASH_KEY_STRING, expr);
> +
> +    } else if (!strcasecmp(op, "unset")) {
> +
> +        apr_hash_set(conf->claims, key, APR_HASH_KEY_STRING, NULL);
> +
> +    } else {
> +
> +        return apr_psprintf(cmd->pool,
> +                "Could not parse claim operation '%s', "
> +                "values should be 'set' or 'unset'", op);
> +
> +    }
> +
> +    return NULL;
> +}
> +
> +static const char *set_jwt_sign(cmd_parms * cmd, void *config,
> +        const char *alg, const char *type, const char *sig)
> +{
> +    auth_bearer_config_rec *dconf = (auth_bearer_config_rec *) config;
> +
> +    auth_bearer_signature_rec *srec = apr_array_push(dconf->signs);
> +
> +    /* handle the algorithm */
> +    if (!strcasecmp(alg, "none")) {
> +        srec->jws_alg = JWS_ALG_TYPE_NONE;
> +        if (type || sig) {
> +            return "AuthtJwtSign: algorithm 'none' has extra parameters";
> +        }
> +    }
> +    else if (!strcasecmp(alg, "HS256")) {
> +        srec->jws_alg = JWS_ALG_TYPE_HS256;
> +    }
> +    else {
> +        return apr_psprintf(cmd->pool, "AuthtJwtSign: algorithm not supported: %s", alg);
> +    }
> +
> +    /* handle the file */
> +    if (type) {
> +        if (!strcasecmp(type, "file")) {
> +            apr_file_t *file;
> +            apr_finfo_t finfo;
> +            apr_status_t status;
> +
> +            sig = ap_server_root_relative(cmd->temp_pool, sig);
> +
> +            status = apr_file_open(&file, sig, APR_READ | APR_BUFFERED,
> +            APR_OS_DEFAULT, cmd->pool);
> +            if (status != APR_SUCCESS) {
> +                char buf[1024];
> +                apr_strerror(status, buf, sizeof(buf));
> +                return apr_psprintf(cmd->pool,
> +                        "AuthtJwtSign: file '%s' could not be opened: %s", sig,
> +                        buf);
> +            }
> +
> +            status = apr_file_info_get(&finfo, APR_FINFO_TYPE | APR_FINFO_SIZE,
> +                    file);
> +            if (status != APR_SUCCESS) {
> +                char buf[1024];
> +                apr_strerror(status, buf, sizeof(buf));
> +                return apr_psprintf(cmd->pool,
> +                        "AuthtJwtSign: info could not be obtained for '%s': %s",
> +                        sig, buf);
> +            }
> +
> +            srec->secret = apr_palloc(cmd->pool, finfo.size);
> +            srec->secret_len = finfo.size;
> +
> +            status = apr_file_read_full(file, srec->secret,
> +                    srec->secret_len, NULL);
> +            if (status != APR_SUCCESS) {
> +                char buf[1024];
> +                apr_strerror(status, buf, sizeof(buf));
> +                return apr_psprintf(cmd->pool,
> +                        "AuthtJwtSign: file '%s' could not be read: %s", sig,
> +                        buf);
> +            }
> +
> +            apr_file_close(file);
> +
> +        }
> +        else {
> +            return apr_psprintf(cmd->pool,
> +                    "AuthtJwtSign: parameter '%s' is not 'file'", type);
> +        }
> +    }
> +
> +    dconf->signs_set = 1;
> +
> +    return NULL;
> +}
> +
> +static const char *set_jwt_verify(cmd_parms * cmd, void *config,
> +        const char *alg, const char *type, const char *sig)
> +{
> +    auth_bearer_config_rec *dconf = (auth_bearer_config_rec *) config;
> +
> +    auth_bearer_signature_rec *srec = apr_array_push(dconf->verifies);
> +
> +    /* handle the algorithm */
> +    if (!strcasecmp(alg, "none")) {
> +        srec->jws_alg = JWS_ALG_TYPE_NONE;
> +        if (type || sig) {
> +            return "AuthtJwtVerify: algorithm 'none' has extra parameters";
> +        }
> +    }
> +    else if (!strcasecmp(alg, "HS256")) {
> +        srec->jws_alg = JWS_ALG_TYPE_HS256;
> +    }
> +    else {
> +        return apr_psprintf(cmd->pool, "AuthtJwtVerify: algorithm not supported: %s", alg);
> +    }
> +
> +    /* handle the file */
> +    if (type) {
> +        if (!strcasecmp(type, "file")) {
> +            apr_file_t *file;
> +            apr_finfo_t finfo;
> +            apr_status_t status;
> +
> +            sig = ap_server_root_relative(cmd->temp_pool, sig);
> +
> +            status = apr_file_open(&file, sig, APR_READ | APR_BUFFERED,
> +            APR_OS_DEFAULT, cmd->pool);
> +            if (status != APR_SUCCESS) {
> +                char buf[1024];
> +                apr_strerror(status, buf, sizeof(buf));
> +                return apr_psprintf(cmd->pool,
> +                        "AuthtJwtVerify: file '%s' could not be opened: %s", sig,
> +                        buf);
> +            }
> +
> +            status = apr_file_info_get(&finfo, APR_FINFO_TYPE | APR_FINFO_SIZE,
> +                    file);
> +            if (status != APR_SUCCESS) {
> +                char buf[1024];
> +                apr_strerror(status, buf, sizeof(buf));
> +                return apr_psprintf(cmd->pool,
> +                        "AuthtJwtVerify: info could not be obtained for '%s': %s",
> +                        sig, buf);
> +            }
> +
> +            srec->secret = apr_palloc(cmd->pool, finfo.size);
> +            srec->secret_len = finfo.size;
> +
> +            status = apr_file_read_full(file, srec->secret,
> +                    srec->secret_len, NULL);
> +            if (status != APR_SUCCESS) {
> +                char buf[1024];
> +                apr_strerror(status, buf, sizeof(buf));
> +                return apr_psprintf(cmd->pool,
> +                        "AuthtJwtVerify: file '%s' could not be read: %s", sig,
> +                        buf);
> +            }
> +
> +            apr_file_close(file);
> +
> +        }
> +        else {
> +            return apr_psprintf(cmd->pool,
> +                    "AuthtJwtVerify: parameter '%s' is not 'file'", type);
> +        }
> +    }
> +
> +    dconf->verifies_set = 1;
> +
> +    return NULL;
> +}

*set_jwt_sign and set_jwt_verify share a lot of code. Can't this be factored out into a common function?

> +
> +static const char *set_jwt_driver(cmd_parms * cmd, void *config, const char *arg)
> +{
> +    auth_bearer_conf *conf =
> +            (auth_bearer_conf *)ap_get_module_config(cmd->server->module_config,
> +            &autht_jwt_module);
> +
> +    const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
> +
> +    if (err != NULL) {
> +        return err;
> +    }
> +
> +    conf->library = ap_getword_conf(cmd->pool, &arg);
> +    conf->params = arg;
> +    conf->library_set = 1;
> +
> +    return NULL;
> +}
> +
> +static const command_rec auth_bearer_cmds[] =
> +{
> +    AP_INIT_TAKE13("AuthtJwtVerify", set_jwt_verify, NULL, RSRC_CONF|OR_AUTHCFG,
> +                   "The JWS signing algorithm and passphrase/key to verify an incoming JWT token"),
> +    AP_INIT_TAKE13("AuthtJwtSign", set_jwt_sign, NULL, RSRC_CONF|OR_AUTHCFG,
> +            "The JWS signing algorithm and passphrase/key to sign an outgoing JWT token"),
> +
> +    AP_INIT_TAKE23("AuthtJwtClaim", set_jwt_claim, NULL, OR_AUTHCFG,
> +            "Set a claim with the given name and expression, or "
> +            "unset the claim with the given name."),
> +
> +    AP_INIT_RAW_ARGS("AuthtJwtDriver", set_jwt_driver, NULL, RSRC_CONF,
> +            "The underlying crypto library driver to use"),
> +
> +    {NULL}
> +};
> +
> +typedef struct claim_iter_t {
> +    request_rec *r;
> +    apr_json_value_t *object;
> +} claim_iter_t;
> +
> +static int claim_iter(void *ctx, const void *key, apr_ssize_t klen,
> +                     const void *val)
> +{
> +    const char *err, *value;
> +    claim_iter_t *iter = ctx;
> +    request_rec *r = iter->r;
> +
> +    value = ap_expr_str_exec(r, val, &err);
> +    if (err) {
> +        ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO()
> +                "AuthtJwtClaim: could not evaluate '%s' expression "
> +                "'%s' for URI '%s': %s",
> +                (char * )key, (char * )val, r->uri, err);
> +        return FALSE;
> +    }
> +
> +
> +    apr_json_object_set(iter->object, key, klen,
> +            apr_json_string_create(r->pool, value, strlen(value)), r->pool);
> +
> +    return TRUE;
> +}
> +
> +static apr_status_t sign_cb(apr_bucket_brigade *bb, apr_jose_t *jose,
> +        apr_jose_signature_t *signature, void *ctx, apr_pool_t *pool) {
> +    auth_bearer_signature_rec *srec = NULL;
> +    request_rec *r = ctx;
> +
> +    auth_bearer_conf *sconf = ap_get_module_config(r->server->module_config,
> +            &autht_jwt_module);
> +
> +    auth_bearer_config_rec *conf = ap_get_module_config(r->per_dir_config,
> +            &autht_jwt_module);
> +
> +    if (conf->signs_set) {
> +        srec = (auth_bearer_signature_rec *) conf->signs->elts;
> +    }
> +
> +    if (srec) {

srec points to the first element of an array. Why only processing the first element?

> +        switch (srec->jws_alg) {
> +        case JWS_ALG_TYPE_NONE: {
> +
> +            return APR_SUCCESS;
> +        }
> +        case JWS_ALG_TYPE_HS256: {
> +            apr_bucket *e;
> +            apr_crypto_key_rec_t *krec;
> +            apr_crypto_key_t *key = NULL;
> +            apr_crypto_digest_t *digest = NULL;
> +            apr_crypto_digest_rec_t *rec;
> +            char * buf;
> +            apr_status_t status;
> +
> +            if (!*sconf->crypto) {
> +                jose->result.msg = "token could not be signed";
> +                jose->result.reason = "no crypto driver configured (set AuthtJwtDriver)";
> +                return APR_EGENERAL;
> +            }
> +
> +            krec = apr_crypto_key_rec_make(APR_CRYPTO_KTYPE_HMAC, pool);
> +
> +            krec->k.hmac.digest = APR_CRYPTO_DIGEST_SHA256;
> +            krec->k.hmac.secret = srec->secret;
> +            krec->k.hmac.secretLen = srec->secret_len;
> +
> +            status = apr_crypto_key(&key, krec, *sconf->crypto, pool);
> +            if (status != APR_SUCCESS) {
> +                jose->result.reason = buf = apr_pcalloc(pool, HUGE_STRING_LEN);
> +                apr_strerror(status, buf, HUGE_STRING_LEN);
> +                jose->result.msg = "token could not be signed";
> +                return status;
> +            }
> +
> +            rec = apr_crypto_digest_rec_make(APR_CRYPTO_DTYPE_SIGN, pool);
> +
> +            status = apr_crypto_digest_init(&digest, key, rec, pool);
> +            if (status != APR_SUCCESS) {
> +                jose->result.reason = buf = apr_pcalloc(pool, HUGE_STRING_LEN);
> +                apr_strerror(status, buf, HUGE_STRING_LEN);
> +                jose->result.msg = "token could not be signed";
> +                return status;
> +            }
> +
> +            for (e = APR_BRIGADE_FIRST(bb); e != APR_BRIGADE_SENTINEL(bb); e =
> +                    APR_BUCKET_NEXT(e)) {
> +                const char *str;
> +                apr_size_t len;
> +
> +                /* If we see an EOS, don't bother doing anything more. */
> +                if (APR_BUCKET_IS_EOS(e)) {
> +                    break;
> +                }
> +
> +                status = apr_bucket_read(e, &str, &len, APR_BLOCK_READ);
> +                if (status != APR_SUCCESS) {
> +                    jose->result.reason = buf = apr_pcalloc(pool, HUGE_STRING_LEN);
> +                    apr_strerror(status, buf, HUGE_STRING_LEN);
> +                    jose->result.msg = "token could not be signed";
> +                    return status;
> +                }
> +
> +                status = apr_crypto_digest_update(digest,
> +                        (const unsigned char *) str, len);
> +                if (status != APR_SUCCESS) {
> +                    jose->result.reason = buf = apr_pcalloc(pool, HUGE_STRING_LEN);
> +                    apr_strerror(status, buf, HUGE_STRING_LEN);
> +                    jose->result.msg = "token could not be signed";
> +                    return status;
> +                }
> +            }
> +
> +            status = apr_crypto_digest_final(digest);
> +            if (status != APR_SUCCESS) {
> +                jose->result.reason = buf = apr_pcalloc(pool, HUGE_STRING_LEN);
> +                apr_strerror(status, buf, HUGE_STRING_LEN);
> +                jose->result.msg = "token could not be signed";
> +                return status;
> +            }
> +
> +            signature->sig.data = rec->d.sign.s;
> +            signature->sig.len = rec->d.sign.slen;
> +
> +            return APR_SUCCESS;
> +        }
> +        }
> +    }
> +    else {
> +        /* algorithm is none */
> +        return APR_SUCCESS;
> +    }
> +
> +    return APR_ENOTIMPL;
> +}
> +
> +/* If we have set claims to be made, create a JWT token.
> + */
> +static const char *jwt_get_token(request_rec *r)
> +{
> +    claim_iter_t iter = { 0 };
> +    apr_json_value_t *claims;
> +    apr_json_value_t *protect;
> +    apr_jose_t jwt = { 0 };
> +    apr_jose_t jws = { 0 };
> +    apr_jose_signature_t signature = { 0 };
> +    auth_bearer_signature_rec *srec = NULL;
> +    apr_bucket_brigade *bb;
> +    char *auth_line;
> +    apr_size_t len;
> +    apr_off_t offset;
> +    apr_status_t status;
> +
> +    auth_bearer_config_rec *conf = ap_get_module_config(r->per_dir_config,
> +                                                       &autht_jwt_module);
> +
> +    apr_jose_cb_t cb = { 0 };
> +
> +    cb.sign = sign_cb;
> +    cb.ctx = r;
> +
> +    if (!conf->claims || !apr_hash_count(conf->claims)) {
> +        ap_log_rerror(APLOG_MARK, APLOG_ERR, APR_SUCCESS, r,
> +                APLOGNO() "AuthtJwtClaim: could not encode a JWT token for URI '%s': no claims",
> +                r->uri);
> +        return "error:no-claims";
> +    }
> +
> +    if (conf->verifies_set) {
> +        srec = (auth_bearer_signature_rec *)conf->verifies->elts;
> +    }
> +
> +    /* create a JWT containing the claims */
> +    claims = apr_json_object_create(r->pool);
> +    iter.object = claims;
> +    iter.r = r;
> +
> +    /* iterate through our claims */
> +    if (!apr_hash_do(claim_iter, &iter, conf->claims)) {
> +        return "error:claim-failed";
> +    }
> +
> +    apr_jose_jwt_make(&jwt, claims, r->pool);
> +    protect = apr_json_object_create(r->pool);
> +
> +    apr_json_object_set(protect, APR_JOSE_JWSE_TYPE,
> +            APR_JSON_VALUE_STRING,
> +            apr_json_string_create(r->pool, APR_JOSE_JWSE_TYPE_JWT,
> +                    APR_JSON_VALUE_STRING), r->pool);
> +
> +    if (srec) {

Again: srec points to the first element of an array. Why only processing the first element?

> +        /* which signature type do we have? */
> +        switch (srec->jws_alg) {
> +        case JWS_ALG_TYPE_NONE: {
> +            apr_json_object_set(protect, APR_JOSE_JWKSE_ALGORITHM,
> +                    APR_JSON_VALUE_STRING,
> +                    apr_json_string_create(r->pool, APR_JOSE_JWA_NONE,
> +                            APR_JSON_VALUE_STRING), r->pool);
> +
> +            break;
> +        }
> +        case JWS_ALG_TYPE_HS256: {
> +            apr_json_object_set(protect, APR_JOSE_JWKSE_ALGORITHM,
> +                    APR_JSON_VALUE_STRING,
> +                    apr_json_string_create(r->pool, APR_JOSE_JWA_HS256,
> +                            APR_JSON_VALUE_STRING), r->pool);
> +
> +            break;
> +        }
> +        }
> +
> +    }
> +    else {
> +        /* no srec defaults to none */
> +        apr_json_object_set(protect, APR_JOSE_JWKSE_ALGORITHM,
> +                APR_JSON_VALUE_STRING,
> +                apr_json_string_create(r->pool, APR_JOSE_JWA_NONE,
> +                        APR_JSON_VALUE_STRING), r->pool);
> +    }
> +
> +    apr_jose_signature_make(&signature, NULL, protect, r->pool);
> +    apr_jose_jws_make(&jws, &signature, NULL, &jwt, r->pool);
> +
> +    bb = apr_brigade_create(r->pool, r->connection->bucket_alloc);
> +
> +    status = apr_jose_encode(bb, NULL, NULL, &jws, &cb, r->pool);
> +    if (APR_SUCCESS != status) {
> +        const apu_err_t *err = apr_jose_error(&jws);
> +        ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r,
> +                APLOGNO() "AuthtJwtClaim: could not encode a JWT token for URI '%s': %s: %s",
> +                r->uri, err->msg, err->reason);
> +        return "error:could-not-encode";
> +    }
> +
> +    apr_brigade_length(bb, 1, &offset);
> +    auth_line = apr_pcalloc(r->pool, offset + 1);
> +    len = offset;
> +    apr_brigade_flatten(bb, auth_line, &len);
> +    auth_line[offset] = 0;
> +
> +    return auth_line;
> +}
> +
> +static const char *jwt_expr_var_fn(ap_expr_eval_ctx_t *ctx, const void *data)
> +{
> +    char *var = (char *)data;
> +
> +    if (var && *var && ctx->r && ap_cstr_casecmp(var, "TOKEN") == 0) {
> +        return jwt_get_token(ctx->r);
> +    }
> +    return NULL;
> +}
> +
> +static int jwt_expr_lookup(ap_expr_lookup_parms *parms)
> +{
> +    switch (parms->type) {
> +    case AP_EXPR_FUNC_VAR:
> +        /* for now, we just handle everything that starts with JWT_.
> +         */
> +        if (strncasecmp(parms->name, "JWT_", 4) == 0) {
> +            *parms->func = jwt_expr_var_fn;
> +            *parms->data = parms->name + 4;
> +            return OK;
> +        }
> +        break;
> +    }
> +    return DECLINED;
> +}
> +
> +static apr_status_t verify_cb(apr_bucket_brigade *bb,
> +        apr_jose_t *jose, apr_jose_signature_t *signature, void *ctx,
> +        int *vflags, apr_pool_t *pool)
> +{
> +    request_rec *r = ctx;
> +    apr_json_kv_t *alg = NULL;
> +
> +    auth_bearer_conf *sconf = ap_get_module_config(r->server->module_config,
> +            &autht_jwt_module);
> +
> +    auth_bearer_config_rec *conf = ap_get_module_config(r->per_dir_config,
> +                                                       &autht_jwt_module);
> +
> +    int alg_supported = 0;
> +
> +    if (signature) {
> +        apr_json_value_t *ph = signature->protected_header;
> +
> +        if (ph && ph->type == APR_JSON_OBJECT) {
> +
> +            alg = apr_json_object_get(ph, APR_JOSE_JWKSE_ALGORITHM,
> +                    APR_JSON_VALUE_STRING);
> +
> +        }
> +    }
> +
> +    if (!alg) {
> +        apr_errprintf(&jose->result, r->pool, "", APR_EGENERAL,
> +                "JWT token protected header has no '"
> +                APR_JOSE_JWKSE_ALGORITHM
> +                "' for URI '%s'",
> +                r->uri);
> +        return APR_EGENERAL;
> +    }
> +
> +    if (alg->v->type != APR_JSON_STRING) {
> +        apr_errprintf(&jose->result, r->pool, "", APR_EGENERAL,
> +                "JWT token protected header '"
> +                APR_JOSE_JWKSE_ALGORITHM
> +                "' is not a string for URI '%s'",
> +                r->uri);
> +        return APR_EGENERAL;
> +    }
> +
> +    /* first pass, is our algorithm supported? */
> +    for (int i = 0; i < conf->verifies->nelts; i++) {
> +    	auth_bearer_signature_rec *srec = &APR_ARRAY_IDX(conf->verifies,
> +    			i, auth_bearer_signature_rec);
> +
> +    	/* which signature type do we have? */
> +    	switch (srec->jws_alg) {
> +    	case JWS_ALG_TYPE_NONE: {
> +        	if (!strncmp(alg->v->value.string.p, "none",
> +                        alg->v->value.string.len)) {
> +        		alg_supported = 1;
> +        	}
> +        	break;
> +    	}
> +    	case JWS_ALG_TYPE_HS256: {
> +        	if (!strncmp(alg->v->value.string.p, "HS256",
> +        			alg->v->value.string.len)) {
> +        		alg_supported = 1;
> +        	}
> +    		break;
> +    	}

Why don't leave the loop as soon as alg_supported is 1?

> +    	}
> +
> +    }
> +
> +    /* we don't support the algorithm */
> +    if (!alg_supported) {
> +    	apr_errprintf(&jose->result, r->pool, "", APR_ENODIGEST,
> +    			"JWT token protected header '"
> +    			APR_JOSE_JWKSE_ALGORITHM
> +				"' %s is not supported for URI '%s'",
> +				alg->v->value.string.p, r->uri);
> +    	return APR_ENODIGEST;
> +    }
> +
> +    /* second pass, does the signature match? */
> +    for (int i = 0; i < conf->verifies->nelts; i++) {
> +    	auth_bearer_signature_rec *srec = &APR_ARRAY_IDX(conf->verifies,
> +    			i, auth_bearer_signature_rec);
> +
> +    	/* which signature type do we have? */
> +    	switch (srec->jws_alg) {
> +    	case JWS_ALG_TYPE_NONE: {
> +        	if (!strncmp(alg->v->value.string.p, "none",
> +                        alg->v->value.string.len)) {
> +        		return APR_SUCCESS;
> +        	}
> +        	break;
> +    	}
> +    	case JWS_ALG_TYPE_HS256: {
> +        	if (!strncmp(alg->v->value.string.p, "HS256",
> +        			alg->v->value.string.len)) {
> +
> +                apr_bucket *e;
> +                apr_crypto_key_rec_t *krec;
> +                apr_crypto_key_t *key = NULL;
> +                apr_crypto_digest_t *digest = NULL;
> +                apr_crypto_digest_rec_t *rec;
> +                char * buf;
> +                apr_status_t status;
> +
> +                if (!*sconf->crypto) {
> +                    jose->result.msg = "token could not be verified";
> +                    jose->result.reason = "no crypto driver configured (set AuthtJwtDriver)";
> +                    return APR_EGENERAL;
> +                }
> +
> +                krec = apr_crypto_key_rec_make(APR_CRYPTO_KTYPE_HMAC, pool);
> +
> +                krec->k.hmac.digest = APR_CRYPTO_DIGEST_SHA256;
> +                krec->k.hmac.secret = srec->secret;
> +                krec->k.hmac.secretLen = srec->secret_len;
> +
> +                status = apr_crypto_key(&key, krec, *sconf->crypto, pool);
> +                if (status != APR_SUCCESS) {
> +                    jose->result.reason = buf = apr_pcalloc(pool, HUGE_STRING_LEN);
> +                    apr_strerror(status, buf, HUGE_STRING_LEN);
> +                    jose->result.msg = "token could not be verified";
> +                    return status;
> +                }
> +
> +                rec = apr_crypto_digest_rec_make(APR_CRYPTO_DTYPE_SIGN, pool);
> +
> +                status = apr_crypto_digest_init(&digest, key, rec, pool);
> +                if (status != APR_SUCCESS) {
> +                    jose->result.reason = buf = apr_pcalloc(pool, HUGE_STRING_LEN);
> +                    apr_strerror(status, buf, HUGE_STRING_LEN);
> +                    jose->result.msg = "token could not be verified";
> +                    return status;
> +                }
> +
> +                for (e = APR_BRIGADE_FIRST(bb); e != APR_BRIGADE_SENTINEL(bb); e =
> +                        APR_BUCKET_NEXT(e)) {
> +                    const char *str;
> +                    apr_size_t len;
> +
> +                    /* If we see an EOS, don't bother doing anything more. */
> +                    if (APR_BUCKET_IS_EOS(e)) {
> +                        break;
> +                    }
> +
> +                    status = apr_bucket_read(e, &str, &len, APR_BLOCK_READ);
> +                    if (status != APR_SUCCESS) {
> +                        jose->result.reason = buf = apr_pcalloc(pool, HUGE_STRING_LEN);
> +                        apr_strerror(status, buf, HUGE_STRING_LEN);
> +                        jose->result.msg = "token could not be verified";
> +                        return status;
> +                    }
> +
> +                    status = apr_crypto_digest_update(digest,
> +                            (const unsigned char *) str, len);
> +                    if (status != APR_SUCCESS) {
> +                        jose->result.reason = buf = apr_pcalloc(pool, HUGE_STRING_LEN);
> +                        apr_strerror(status, buf, HUGE_STRING_LEN);
> +                        jose->result.msg = "token could not be verified";
> +                        return status;
> +                    }
> +                }
> +
> +                status = apr_crypto_digest_final(digest);
> +                if (status != APR_SUCCESS) {
> +                    jose->result.reason = buf = apr_pcalloc(pool, HUGE_STRING_LEN);
> +                    apr_strerror(status, buf, HUGE_STRING_LEN);
> +                    jose->result.msg = "token could not be verified";
> +                    return status;
> +                }
> +
> +                if (signature->sig.len == rec->d.sign.slen &&
> +                		!memcmp(signature->sig.data, rec->d.sign.s, rec->d.sign.slen)) {
> +                    return APR_SUCCESS;
> +                }
> +
> +        	}
> +    		break;
> +    	}
> +    	}
> +
> +    }
> +
> +    /* no match, oh well */
> +	apr_errprintf(&jose->result, r->pool, "", APR_ENODIGEST,
> +			"JWT token protected header '"
> +			APR_JOSE_JWKSE_ALGORITHM
> +			"' %s is not supported for URI '%s'",
> +			alg->v->value.string.p, r->uri);
> +    return APR_ENOVERIFY;
> +}

sign_cb and verify_cb seem to share quite some amount of code. Can't this be factored out into a common function?

Regards

RĂ¼diger