https://bz.apache.org/bugzilla/show_bug.cgi?id=70276

--- Comment #2 from Syed Hyder Ali <[email protected]> ---
PR PACKAGE — mod_autht_jwt: fix JOSE build + add tests

PR TITLE

mod_autht_jwt: fix build against apr-util with JOSE, and add tests

PR DESCRIPTION (paste into GitHub)

WHAT

mod_autht_jwt currently cannot be built into its working form against an
apr-util that provides JOSE (apr-util >= 1.7, or APR 2.0). Two build defects:

1. The HAVE_APU_JOSE feature gate tests APU_MAJOR_VERSION before any APR/APU
header is included, so the macro is undefined (= 0) and the entire module body
is #ifdef-ed out. The result is a no-op stub whose directives (AuthtJwtVerify,
AuthtJwtDriver, …) don't exist, so any JWT config makes httpd fail to start
(AH00526: Invalid command 'AuthtJwtDriver').

2. Once the real body is enabled, jwt_get_token() calls
apr_jose_signature_make() with 4 arguments, but apr-util (1.7.x and trunk)
declares it with 5 (…, void *ctx, apr_pool_t *pool). Compilation fails:
error: too few arguments to function 'apr_jose_signature_make'.


FIX

- Include apu_version.h before the version gate so APU_MAJOR_VERSION is defined
when it is tested.
- Pass the missing ctx argument (NULL) to apr_jose_signature_make().


TESTS

Adds test/modules/aaa/test_015_autht_jwt.py: a round-trip test that configures
AuthtJwtVerify HS256, then checks no-token → 401, valid HS256 token → 200, bad
signature → 401, wrong aud → 401. This also serves as a regression test proving
the module now builds, loads, and verifies correctly.


TESTING DONE

Built httpd trunk against APR 2.0 (OpenSSL crypto + JOSE); without this patch
the module builds as a 23 KB stub and httpd refuses the config; with it the
module builds (~85 KB, references apr_jose_decode / apr_crypto_digest_*) and
the new tests pass.


PATCH 1 — THE FIX (modules/aaa/mod_autht_jwt.c)

--- a/modules/aaa/mod_autht_jwt.c
+++ b/modules/aaa/mod_autht_jwt.c
@@ -20,6 +20,8 @@
  * of JWT bearer tokens, and as an acceptor of JWT Bearer tokens for
authentication.
  */

+#include "apu_version.h"        /* for APU_MAJOR_VERSION, before the gate
below */
+
 /* apr_jose support requires >= 1.7 */
 #if APU_MAJOR_VERSION > 1 || 
     (APU_MAJOR_VERSION == 1 && APU_MINOR_VERSION > 6)
@@ -668,7 +670,7 @@ static const char *jwt_get_token(request_rec *r)
                         APR_JSON_VALUE_STRING), r->pool);
     }

-    apr_jose_signature_make(&signature, NULL, protect, r->pool);
+    apr_jose_signature_make(&signature, NULL, protect, NULL, r->pool);
     apr_jose_jws_make(&jws, &signature, NULL, &jwt, r->pool);

     bb = apr_brigade_create(r->pool, r->connection->bucket_alloc);


PATCH 2 — CHANGELOG ENTRY (changes-entries/autht_jwt_build.txt, new file)

  *) mod_autht_jwt: Fix the build against apr-util with JOSE support: include
     apu_version.h before the HAVE_APU_JOSE version check (otherwise the module
     was compiled as a no-op stub), and pass the missing ctx argument to
     apr_jose_signature_make().  [<your name>]


PATCH 3 — TEST (test/modules/aaa/test_015_autht_jwt.py, new file)

import base64
import hashlib
import hmac
import json
import os

import pytest

from pyhttpd.conf import HttpdConf

SECRET = b"supersecretkey0123456789"


def _b64url(data: bytes) -> str:
    return base64.urlsafe_b64encode(data).rstrip(b"=").decode()


def make_jwt(secret: bytes, payload: dict, alg: str = "HS256") -> str:
    header = _b64url(json.dumps({"alg": alg, "typ": "JWT"}).encode())
    body = _b64url(json.dumps(payload).encode())
    signing_input = f"{header}.{body}"
    sig = hmac.new(secret, signing_input.encode(), hashlib.sha256).digest()
    return f"{signing_input}.{_b64url(sig)}"


class TestAuthtJwt:

    @pytest.fixture(autouse=True, scope="class")
    def _class_scope(self, env):
        secret_file = os.path.join(env.server_dir, "jwt.secret")
        with open(secret_file, "wb") as fd:
            fd.write(SECRET)
        conf = HttpdConf(env, extras={
            "base": "AuthtJwtDriver openssl",
            f"test1.{env.http_tld}": f"""
            <Location "/jwt">
                AuthType bearer
                AuthName "myaud"
                AuthBearerProvider jwt
                AuthtJwtVerify HS256 file "{secret_file}"
                Require valid-user
            </Location>
            """,
        })
        conf.add_vhost_test1()
        conf.install()
        assert env.apache_restart() == 0

    def _get(self, env, token=None):
        url = f"https://test1.{env.http_tld}:{env.https_port}/jwt/index.html";
        options = ["-H", f"Authorization: Bearer {token}"] if token else []
        return env.curl_get(url, options=options)

    def test_aaa_015_01_no_token(self, env):
        assert self._get(env).response["status"] == 401

    def test_aaa_015_02_valid_token(self, env):
        token = make_jwt(SECRET, {"sub": "alice", "aud": "myaud"})
        assert self._get(env, token).response["status"] == 200

    def test_aaa_015_03_bad_signature(self, env):
        token = make_jwt(b"wrong-secret", {"sub": "alice", "aud": "myaud"})
        assert self._get(env, token).response["status"] == 401

    def test_aaa_015_04_wrong_aud(self, env):
        token = make_jwt(SECRET, {"sub": "alice", "aud": "nope"})
        assert self._get(env, token).response["status"] == 401


HARNESS NOTE

Method/fixture names (env.server_dir, env.curl_get, add_vhost_test1,
env.http_tld, env.https_port) follow the pyhttpd conventions used by the other
test/modules/aaa tests — run pytest test/modules/aaa/test_015_autht_jwt.py
against your checkout and adjust 015 to the next free number / any helper name
that differs in your tree. test1 vhost must have an index.html (the standard
harness provides one).


GIT COMMANDS

# 1. Fork apache/httpd on GitHub, then:
git clone https://github.com/<you>/httpd.git && cd httpd
git remote add upstream https://github.com/apache/httpd.git
git checkout -b mod_autht_jwt-build-fix trunk

# 2. apply Patch 1 (or edit by hand), create the two new files, then:
git add modules/aaa/mod_autht_jwt.c changes-entries/autht_jwt_build.txt 
        test/modules/aaa/test_015_autht_jwt.py
git commit -m "mod_autht_jwt: fix build against apr-util with JOSE, and add
tests

- include apu_version.h before the HAVE_APU_JOSE gate (the module was
  otherwise compiled as a no-op stub)
- pass the missing ctx argument to apr_jose_signature_make()
- add test/modules/aaa/test_015_autht_jwt.py (HS256 verify round-trip)"

git push origin mod_autht_jwt-build-fix

# 3. Open the PR from your branch against apache/httpd:trunk.

-- 
You are receiving this mail because:
You are the assignee for the bug.
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to