You are viewing a plain text version of this content. The canonical link for it is here.
Posted to cvs@httpd.apache.org by ic...@apache.org on 2021/12/14 11:33:27 UTC
svn commit: r1895947 [1/5] - in /httpd/httpd/branches/2.4.x/test/modules/md: ./ data/ data/store_migrate/ data/store_migrate/1.0/ data/store_migrate/1.0/sample1/ data/store_migrate/1.0/sample1/accounts/ data/store_migrate/1.0/sample1/accounts/ACME-loca...
Author: icing
Date: Tue Dec 14 11:33:27 2021
New Revision: 1895947
URL: http://svn.apache.org/viewvc?rev=1895947&view=rev
Log:
*) test: adding modules/md test suite
Added:
httpd/httpd/branches/2.4.x/test/modules/md/
httpd/httpd/branches/2.4.x/test/modules/md/__init__.py
httpd/httpd/branches/2.4.x/test/modules/md/conftest.py (with props)
httpd/httpd/branches/2.4.x/test/modules/md/data/
httpd/httpd/branches/2.4.x/test/modules/md/data/store_migrate/
httpd/httpd/branches/2.4.x/test/modules/md/data/store_migrate/1.0/
httpd/httpd/branches/2.4.x/test/modules/md/data/store_migrate/1.0/sample1/
httpd/httpd/branches/2.4.x/test/modules/md/data/store_migrate/1.0/sample1/accounts/
httpd/httpd/branches/2.4.x/test/modules/md/data/store_migrate/1.0/sample1/accounts/ACME-localhost-0000/
httpd/httpd/branches/2.4.x/test/modules/md/data/store_migrate/1.0/sample1/accounts/ACME-localhost-0000/account.json
httpd/httpd/branches/2.4.x/test/modules/md/data/store_migrate/1.0/sample1/accounts/ACME-localhost-0000/account.pem
httpd/httpd/branches/2.4.x/test/modules/md/data/store_migrate/1.0/sample1/archive/
httpd/httpd/branches/2.4.x/test/modules/md/data/store_migrate/1.0/sample1/archive/7007-1502285564.org.1/
httpd/httpd/branches/2.4.x/test/modules/md/data/store_migrate/1.0/sample1/archive/7007-1502285564.org.1/md.json
httpd/httpd/branches/2.4.x/test/modules/md/data/store_migrate/1.0/sample1/challenges/
httpd/httpd/branches/2.4.x/test/modules/md/data/store_migrate/1.0/sample1/domains/
httpd/httpd/branches/2.4.x/test/modules/md/data/store_migrate/1.0/sample1/domains/7007-1502285564.org/
httpd/httpd/branches/2.4.x/test/modules/md/data/store_migrate/1.0/sample1/domains/7007-1502285564.org/cert.pem
httpd/httpd/branches/2.4.x/test/modules/md/data/store_migrate/1.0/sample1/domains/7007-1502285564.org/chain.pem
httpd/httpd/branches/2.4.x/test/modules/md/data/store_migrate/1.0/sample1/domains/7007-1502285564.org/md.json
httpd/httpd/branches/2.4.x/test/modules/md/data/store_migrate/1.0/sample1/domains/7007-1502285564.org/pkey.pem
httpd/httpd/branches/2.4.x/test/modules/md/data/store_migrate/1.0/sample1/httpd.json
httpd/httpd/branches/2.4.x/test/modules/md/data/store_migrate/1.0/sample1/md_store.json
httpd/httpd/branches/2.4.x/test/modules/md/data/store_migrate/1.0/sample1/staging/
httpd/httpd/branches/2.4.x/test/modules/md/data/store_migrate/1.0/sample1/tmp/
httpd/httpd/branches/2.4.x/test/modules/md/data/test_920/
httpd/httpd/branches/2.4.x/test/modules/md/data/test_920/002.pubcert
httpd/httpd/branches/2.4.x/test/modules/md/data/test_conf_validate/
httpd/httpd/branches/2.4.x/test/modules/md/data/test_conf_validate/test_014.conf
httpd/httpd/branches/2.4.x/test/modules/md/data/test_drive/
httpd/httpd/branches/2.4.x/test/modules/md/data/test_drive/test1.example.org.conf
httpd/httpd/branches/2.4.x/test/modules/md/data/test_roundtrip/
httpd/httpd/branches/2.4.x/test/modules/md/data/test_roundtrip/temp.conf
httpd/httpd/branches/2.4.x/test/modules/md/dns01.py (with props)
httpd/httpd/branches/2.4.x/test/modules/md/http_challenge_foobar.py (with props)
httpd/httpd/branches/2.4.x/test/modules/md/md_acme.py (with props)
httpd/httpd/branches/2.4.x/test/modules/md/md_cert_util.py (with props)
httpd/httpd/branches/2.4.x/test/modules/md/md_certs.py (with props)
httpd/httpd/branches/2.4.x/test/modules/md/md_conf.py (with props)
httpd/httpd/branches/2.4.x/test/modules/md/md_env.py (with props)
httpd/httpd/branches/2.4.x/test/modules/md/message.py (with props)
httpd/httpd/branches/2.4.x/test/modules/md/msg_fail_on.py (with props)
httpd/httpd/branches/2.4.x/test/modules/md/notifail.py (with props)
httpd/httpd/branches/2.4.x/test/modules/md/notify.py (with props)
httpd/httpd/branches/2.4.x/test/modules/md/pebble/
httpd/httpd/branches/2.4.x/test/modules/md/pebble/pebble-eab.json.template
httpd/httpd/branches/2.4.x/test/modules/md/pebble/pebble.json.template
httpd/httpd/branches/2.4.x/test/modules/md/test_001_store.py
httpd/httpd/branches/2.4.x/test/modules/md/test_010_store_migrate.py
httpd/httpd/branches/2.4.x/test/modules/md/test_100_reg_add.py
httpd/httpd/branches/2.4.x/test/modules/md/test_110_reg_update.py
httpd/httpd/branches/2.4.x/test/modules/md/test_120_reg_list.py
httpd/httpd/branches/2.4.x/test/modules/md/test_202_acmev2_regs.py
httpd/httpd/branches/2.4.x/test/modules/md/test_300_conf_validate.py
httpd/httpd/branches/2.4.x/test/modules/md/test_310_conf_store.py
httpd/httpd/branches/2.4.x/test/modules/md/test_502_acmev2_drive.py
httpd/httpd/branches/2.4.x/test/modules/md/test_602_roundtrip.py
httpd/httpd/branches/2.4.x/test/modules/md/test_702_auto.py
httpd/httpd/branches/2.4.x/test/modules/md/test_720_wildcard.py
httpd/httpd/branches/2.4.x/test/modules/md/test_730_static.py
httpd/httpd/branches/2.4.x/test/modules/md/test_740_acme_errors.py
httpd/httpd/branches/2.4.x/test/modules/md/test_741_setup_errors.py
httpd/httpd/branches/2.4.x/test/modules/md/test_750_eab.py
httpd/httpd/branches/2.4.x/test/modules/md/test_751_sectigo.py
httpd/httpd/branches/2.4.x/test/modules/md/test_752_zerossl.py
httpd/httpd/branches/2.4.x/test/modules/md/test_800_must_staple.py
httpd/httpd/branches/2.4.x/test/modules/md/test_801_stapling.py
httpd/httpd/branches/2.4.x/test/modules/md/test_810_ec.py
httpd/httpd/branches/2.4.x/test/modules/md/test_900_notify.py
httpd/httpd/branches/2.4.x/test/modules/md/test_901_message.py
httpd/httpd/branches/2.4.x/test/modules/md/test_910_cleanups.py
httpd/httpd/branches/2.4.x/test/modules/md/test_920_status.py
Added: httpd/httpd/branches/2.4.x/test/modules/md/__init__.py
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/2.4.x/test/modules/md/__init__.py?rev=1895947&view=auto
==============================================================================
(empty)
Added: httpd/httpd/branches/2.4.x/test/modules/md/conftest.py
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/2.4.x/test/modules/md/conftest.py?rev=1895947&view=auto
==============================================================================
--- httpd/httpd/branches/2.4.x/test/modules/md/conftest.py (added)
+++ httpd/httpd/branches/2.4.x/test/modules/md/conftest.py Tue Dec 14 11:33:27 2021
@@ -0,0 +1,89 @@
+import logging
+import os
+import re
+import sys
+import pytest
+
+sys.path.append(os.path.join(os.path.dirname(__file__), '../..'))
+
+from .md_conf import HttpdConf
+from .md_env import MDTestEnv
+from .md_acme import MDPebbleRunner, MDBoulderRunner
+
+
+def pytest_report_header(config, startdir):
+ env = MDTestEnv()
+ return "mod_md: [apache: {aversion}({prefix}), mod_{ssl}, ACME server: {acme}]".format(
+ prefix=env.prefix,
+ aversion=env.get_httpd_version(),
+ ssl=env.ssl_module,
+ acme=env.acme_server,
+ )
+
+
+@pytest.fixture(scope="package")
+def env(pytestconfig) -> MDTestEnv:
+ level = logging.INFO
+ console = logging.StreamHandler()
+ console.setLevel(level)
+ console.setFormatter(logging.Formatter('%(levelname)s: %(message)s'))
+ logging.getLogger('').addHandler(console)
+ logging.getLogger('').setLevel(level=level)
+ env = MDTestEnv(pytestconfig=pytestconfig)
+ env.setup_httpd()
+ env.apache_access_log_clear()
+ env.httpd_error_log.clear_log()
+ return env
+
+
+@pytest.fixture(autouse=True, scope="package")
+def _session_scope(env):
+ # we'd like to check the httpd error logs after the test suite has
+ # run to catch anything unusual. For this, we setup the ignore list
+ # of errors and warnings that we do expect.
+ env.httpd_error_log.set_ignored_lognos([
+ 'AH10040', # mod_md, setup complain
+ 'AH10045', # mod_md complains that there is no vhost for an MDomain
+ 'AH10056', # mod_md, invalid params
+ 'AH10105', # mod_md does not find a vhost with SSL enabled for an MDomain
+ 'AH10085', # mod_ssl complains about fallback certificates
+ 'AH01909', # mod_ssl, cert alt name complains
+ 'AH10170', # mod_md, wrong config, tested
+ 'AH10171', # mod_md, wrong config, tested
+ ])
+
+ env.httpd_error_log.add_ignored_patterns([
+ re.compile(r'.*urn:ietf:params:acme:error:.*'),
+ re.compile(r'.*None of the ACME challenge methods configured for this domain are suitable.*'),
+ re.compile(r'.*problem\[(challenge-mismatch|challenge-setup-failure|apache:eab-hmac-invalid)].*'),
+ re.compile(r'.*CA considers answer to challenge invalid.].*'),
+ re.compile(r'.*problem\[urn:org:apache:httpd:log:AH\d+:].*'),
+ re.compile(r'.*Unsuccessful in contacting ACME server at :*'),
+ re.compile(r'.*test-md-720-002-\S+.org: dns-01 setup command failed .*'),
+ ])
+ if env.lacks_ocsp():
+ env.httpd_error_log.add_ignored_patterns([
+ re.compile(r'.*certificate with serial \S+ has no OCSP responder URL.*'),
+ ])
+ yield
+ assert env.apache_stop() == 0
+ errors, warnings = env.httpd_error_log.get_missed()
+ assert (len(errors), len(warnings)) == (0, 0),\
+ f"apache logged {len(errors)} errors and {len(warnings)} warnings: \n"\
+ "{0}\n{1}\n".format("\n".join(errors), "\n".join(warnings))
+
+
+@pytest.fixture(scope="package")
+def acme(env):
+ acme_server = None
+ if env.acme_server == 'pebble':
+ acme_server = MDPebbleRunner(env, configs={
+ 'default': os.path.join(env.gen_dir, 'pebble/pebble.json'),
+ 'eab': os.path.join(env.gen_dir, 'pebble/pebble-eab.json'),
+ })
+ elif env.acme_server == 'boulder':
+ acme_server = MDBoulderRunner(env)
+ yield acme_server
+ if acme_server is not None:
+ acme_server.stop()
+
Propchange: httpd/httpd/branches/2.4.x/test/modules/md/conftest.py
------------------------------------------------------------------------------
svn:executable = *
Added: httpd/httpd/branches/2.4.x/test/modules/md/data/store_migrate/1.0/sample1/accounts/ACME-localhost-0000/account.json
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/2.4.x/test/modules/md/data/store_migrate/1.0/sample1/accounts/ACME-localhost-0000/account.json?rev=1895947&view=auto
==============================================================================
--- httpd/httpd/branches/2.4.x/test/modules/md/data/store_migrate/1.0/sample1/accounts/ACME-localhost-0000/account.json (added)
+++ httpd/httpd/branches/2.4.x/test/modules/md/data/store_migrate/1.0/sample1/accounts/ACME-localhost-0000/account.json Tue Dec 14 11:33:27 2021
@@ -0,0 +1,6 @@
+{
+ "disabled": false,
+ "url": "http://localhost:4000/acme/reg/494",
+ "ca-url": "http://localhost:4000/directory",
+ "id": "ACME-localhost-0000"
+}
\ No newline at end of file
Added: httpd/httpd/branches/2.4.x/test/modules/md/data/store_migrate/1.0/sample1/accounts/ACME-localhost-0000/account.pem
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/2.4.x/test/modules/md/data/store_migrate/1.0/sample1/accounts/ACME-localhost-0000/account.pem?rev=1895947&view=auto
==============================================================================
--- httpd/httpd/branches/2.4.x/test/modules/md/data/store_migrate/1.0/sample1/accounts/ACME-localhost-0000/account.pem (added)
+++ httpd/httpd/branches/2.4.x/test/modules/md/data/store_migrate/1.0/sample1/accounts/ACME-localhost-0000/account.pem Tue Dec 14 11:33:27 2021
@@ -0,0 +1,54 @@
+-----BEGIN ENCRYPTED PRIVATE KEY-----
+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
+DWiLOvp6tLT58pX8TSlplbITcQ==
+-----END ENCRYPTED PRIVATE KEY-----
Added: httpd/httpd/branches/2.4.x/test/modules/md/data/store_migrate/1.0/sample1/archive/7007-1502285564.org.1/md.json
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/2.4.x/test/modules/md/data/store_migrate/1.0/sample1/archive/7007-1502285564.org.1/md.json?rev=1895947&view=auto
==============================================================================
--- httpd/httpd/branches/2.4.x/test/modules/md/data/store_migrate/1.0/sample1/archive/7007-1502285564.org.1/md.json (added)
+++ httpd/httpd/branches/2.4.x/test/modules/md/data/store_migrate/1.0/sample1/archive/7007-1502285564.org.1/md.json Tue Dec 14 11:33:27 2021
@@ -0,0 +1,18 @@
+{
+ "name": "7007-1502285564.org",
+ "domains": [
+ "7007-1502285564.org"
+ ],
+ "contacts": [
+ "mailto:admin@7007-1502285564.org"
+ ],
+ "transitive": 0,
+ "ca": {
+ "proto": "ACME",
+ "url": "http://localhost:4000/directory",
+ "agreement": "http://boulder:4000/terms/v1"
+ },
+ "state": 1,
+ "renew-mode": 2,
+ "renew-window": 1209600
+}
Added: httpd/httpd/branches/2.4.x/test/modules/md/data/store_migrate/1.0/sample1/domains/7007-1502285564.org/cert.pem
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/2.4.x/test/modules/md/data/store_migrate/1.0/sample1/domains/7007-1502285564.org/cert.pem?rev=1895947&view=auto
==============================================================================
--- httpd/httpd/branches/2.4.x/test/modules/md/data/store_migrate/1.0/sample1/domains/7007-1502285564.org/cert.pem (added)
+++ httpd/httpd/branches/2.4.x/test/modules/md/data/store_migrate/1.0/sample1/domains/7007-1502285564.org/cert.pem Tue Dec 14 11:33:27 2021
@@ -0,0 +1,32 @@
+-----BEGIN CERTIFICATE-----
+MIIFkDCCBHigAwIBAgITAP8PGcftT0j60OOjL+Er/XuHrzANBgkqhkiG9w0BAQsF
+ADAfMR0wGwYDVQQDDBRoMnBweSBoMmNrZXIgZmFrZSBDQTAeFw0xNzA4MDkxMjMz
+MDBaFw0xNzExMDcxMjMzMDBaME0xHDAaBgNVBAMTEzcwMDctMTUwMjI4NTU2NC5v
+cmcxLTArBgNVBAUTJGZmMGYxOWM3ZWQ0ZjQ4ZmFkMGUzYTMyZmUxMmJmZDdiODdh
+ZjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMHuhVxT9Jpc6EpNAhrq
+RqzDJ4tWSG9BtguKZzh3sbY92EE5rqym7wpdb5DG5gwew4iD1R+YizY+99+00qlB
+3kNBUVsJCBnew0apmhPq4jjF8v8t3Qqq0ISn2Sdv5bt5mB9NWeO83h3zT1LW0rTm
+847nwxUuGxlIjLXxsibUvPunMfyGJUshflN5V9/Q3YQBOCnDWy5s4FKN2N34cHFE
+IgJo5ToBKZLp9eUaLm03mlfhTFc3/h0AtWwMZ5P2tRRB9EiijqI9nkrVzqyi1QTN
+Hn/XfgDgKRCyMp6i5kcK3hCXo4GjOIU0KA91ttf3IeKhXHKzC7ybc4hdJH2rWzoN
+srYq6tNZ+cOaa1E/H+v+OMSeIRaRrpM56c3nUssIzbneMIXuLHuOluaaL4baCjYp
+Pdc80bUlps06XcnVHysAbsfbtWAtUdzj2l4flVySruGoaqVDudl1GqYoYa+0oReM
+Zqd09Q+pCQvDNE+jiVq3An+JA4msux9EMMz7jkAwnl8iiWy0GMuQPsL5gp3TEXGY
+Cp1wQlzpmxZSdUZ+J6f4UkFOS/Zn6gS6nSxN8nj3XKbRYRbebPQMwRGYGttCyeZO
+dHiUY/3gQBUdpcMBJhAa5GFoabK0J5XPmK2E1P9cGQo7DbNn+Skojnz2WuUtCuyo
+m9la14Ruca9V8NmjBsu+4mXvAgMBAAGjggGVMIIBkTAOBgNVHQ8BAf8EBAMCBaAw
+HQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMAwGA1UdEwEB/wQCMAAwHQYD
+VR0OBBYEFH426IYgY0KXUe9cLMZ3d8tipsDkMB8GA1UdIwQYMBaAFPt4TxL5YBWD
+LJ8XfzQZsy426kGJMGYGCCsGAQUFBwEBBFowWDAiBggrBgEFBQcwAYYWaHR0cDov
+LzEyNy4wLjAuMTo0MDAyLzAyBggrBgEFBQcwAoYmaHR0cDovLzEyNy4wLjAuMTo0
+MDAwL2FjbWUvaXNzdWVyLWNlcnQwHgYDVR0RBBcwFYITNzAwNy0xNTAyMjg1NTY0
+Lm9yZzAnBgNVHR8EIDAeMBygGqAYhhZodHRwOi8vZXhhbXBsZS5jb20vY3JsMGEG
+A1UdIARaMFgwCAYGZ4EMAQIBMEwGAyoDBDBFMCIGCCsGAQUFBwIBFhZodHRwOi8v
+ZXhhbXBsZS5jb20vY3BzMB8GCCsGAQUFBwICMBMMEURvIFdoYXQgVGhvdSBXaWx0
+MA0GCSqGSIb3DQEBCwUAA4IBAQBfqLXSJZ5Izs2I44cXWrAto631aTylValp0Fiy
+Zz1dj00FS6XN5DGtfIyq7Ymd3MMiOZCLkTOMMb7BrJAvcgeJteKwdk3ffXEDyKH0
+1ttXK7l46trEyGOB+f9PMMKxVMyhDhGKyb6ro4Y5WTK/w4862soqKcP1SjHvk65u
+lIkFws1fWYYzqPLKLij2ILm+4NjdGIl8qPQWP2PtbOaDTFspJBz6hvLmqRgmjVVv
+cENwBUML4LCkVY3TUqoBHXDhpocTZlVeAVRVsroosboQJlY5nIKz6cOjilILn4cT
+hgEKa5IRwK5lUveCoeQtYUyLoyp5ncbota+UxNxCnkl/0veK
+-----END CERTIFICATE-----
Added: httpd/httpd/branches/2.4.x/test/modules/md/data/store_migrate/1.0/sample1/domains/7007-1502285564.org/chain.pem
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/2.4.x/test/modules/md/data/store_migrate/1.0/sample1/domains/7007-1502285564.org/chain.pem?rev=1895947&view=auto
==============================================================================
--- httpd/httpd/branches/2.4.x/test/modules/md/data/store_migrate/1.0/sample1/domains/7007-1502285564.org/chain.pem (added)
+++ httpd/httpd/branches/2.4.x/test/modules/md/data/store_migrate/1.0/sample1/domains/7007-1502285564.org/chain.pem Tue Dec 14 11:33:27 2021
@@ -0,0 +1,27 @@
+-----BEGIN CERTIFICATE-----
+MIIEijCCA3KgAwIBAgICEk0wDQYJKoZIhvcNAQELBQAwKzEpMCcGA1UEAwwgY2Fj
+a2xpbmcgY3J5cHRvZ3JhcGhlciBmYWtlIFJPT1QwHhcNMTUxMDIxMjAxMTUyWhcN
+MjAxMDE5MjAxMTUyWjAfMR0wGwYDVQQDExRoYXBweSBoYWNrZXIgZmFrZSBDQTCC
+ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMIKR3maBcUSsncXYzQT13D5
+Nr+Z3mLxMMh3TUdt6sACmqbJ0btRlgXfMtNLM2OU1I6a3Ju+tIZSdn2v21JBwvxU
+zpZQ4zy2cimIiMQDZCQHJwzC9GZn8HaW091iz9H0Go3A7WDXwYNmsdLNRi00o14U
+joaVqaPsYrZWvRKaIRqaU0hHmS0AWwQSvN/93iMIXuyiwywmkwKbWnnxCQ/gsctK
+FUtcNrwEx9Wgj6KlhwDTyI1QWSBbxVYNyUgPFzKxrSmwMO0yNff7ho+QT9x5+Y/7
+XE59S4Mc4ZXxcXKew/gSlN9U5mvT+D2BhDtkCupdfsZNCQWp27A+b/DmrFI9NqsC
+AwEAAaOCAcIwggG+MBIGA1UdEwEB/wQIMAYBAf8CAQAwQwYDVR0eBDwwOqE4MAaC
+BC5taWwwCocIAAAAAAAAAAAwIocgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAwDgYDVR0PAQH/BAQDAgGGMH8GCCsGAQUFBwEBBHMwcTAyBggrBgEFBQcw
+AYYmaHR0cDovL2lzcmcudHJ1c3RpZC5vY3NwLmlkZW50cnVzdC5jb20wOwYIKwYB
+BQUHMAKGL2h0dHA6Ly9hcHBzLmlkZW50cnVzdC5jb20vcm9vdHMvZHN0cm9vdGNh
+eDMucDdjMB8GA1UdIwQYMBaAFOmkP+6epeby1dd5YDyTpi4kjpeqMFQGA1UdIARN
+MEswCAYGZ4EMAQIBMD8GCysGAQQBgt8TAQEBMDAwLgYIKwYBBQUHAgEWImh0dHA6
+Ly9jcHMucm9vdC14MS5sZXRzZW5jcnlwdC5vcmcwPAYDVR0fBDUwMzAxoC+gLYYr
+aHR0cDovL2NybC5pZGVudHJ1c3QuY29tL0RTVFJPT1RDQVgzQ1JMLmNybDAdBgNV
+HQ4EFgQU+3hPEvlgFYMsnxd/NBmzLjbqQYkwDQYJKoZIhvcNAQELBQADggEBAA0Y
+AeLXOklx4hhCikUUl+BdnFfn1g0W5AiQLVNIOL6PnqXu0wjnhNyhqdwnfhYMnoy4
+idRh4lB6pz8Gf9pnlLd/DnWSV3gS+/I/mAl1dCkKby6H2V790e6IHmIK2KYm3jm+
+U++FIdGpBdsQTSdmiX/rAyuxMDM0adMkNBwTfQmZQCz6nGHw1QcSPZMvZpsC8Skv
+ekzxsjF1otOrMUPNPQvtTWrVx8GlR2qfx/4xbQa1v2frNvFBCmO59goz+jnWvfTt
+j2NjwDZ7vlMBsPm16dbKYC840uvRoZjxqsdc3ChCZjqimFqlNG/xoPA8+dTicZzC
+XE9ijPIcvW6y1aa3bGw=
+-----END CERTIFICATE-----
Added: httpd/httpd/branches/2.4.x/test/modules/md/data/store_migrate/1.0/sample1/domains/7007-1502285564.org/md.json
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/2.4.x/test/modules/md/data/store_migrate/1.0/sample1/domains/7007-1502285564.org/md.json?rev=1895947&view=auto
==============================================================================
--- httpd/httpd/branches/2.4.x/test/modules/md/data/store_migrate/1.0/sample1/domains/7007-1502285564.org/md.json (added)
+++ httpd/httpd/branches/2.4.x/test/modules/md/data/store_migrate/1.0/sample1/domains/7007-1502285564.org/md.json Tue Dec 14 11:33:27 2021
@@ -0,0 +1,23 @@
+{
+ "name": "7007-1502285564.org",
+ "domains": [
+ "7007-1502285564.org"
+ ],
+ "contacts": [
+ "mailto:admin@7007-1502285564.org"
+ ],
+ "transitive": 0,
+ "ca": {
+ "account": "ACME-localhost-0000",
+ "proto": "ACME",
+ "url": "http://localhost:4000/directory",
+ "agreement": "http://boulder:4000/terms/v1"
+ },
+ "cert": {
+ "url": "http://localhost:4000/acme/cert/ff0f19c7ed4f48fad0e3a32fe12bfd7b87af",
+ "expires": "Tue, 07 Nov 2017 12:33:00 GMT"
+ },
+ "state": 2,
+ "renew-mode": 2,
+ "renew-window": 1209600
+}
Added: httpd/httpd/branches/2.4.x/test/modules/md/data/store_migrate/1.0/sample1/domains/7007-1502285564.org/pkey.pem
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/2.4.x/test/modules/md/data/store_migrate/1.0/sample1/domains/7007-1502285564.org/pkey.pem?rev=1895947&view=auto
==============================================================================
--- httpd/httpd/branches/2.4.x/test/modules/md/data/store_migrate/1.0/sample1/domains/7007-1502285564.org/pkey.pem (added)
+++ httpd/httpd/branches/2.4.x/test/modules/md/data/store_migrate/1.0/sample1/domains/7007-1502285564.org/pkey.pem Tue Dec 14 11:33:27 2021
@@ -0,0 +1,52 @@
+-----BEGIN PRIVATE KEY-----
+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
+Wc8hH7giOwSubI2D2mauspM5CZlez7A=
+-----END PRIVATE KEY-----
Added: httpd/httpd/branches/2.4.x/test/modules/md/data/store_migrate/1.0/sample1/httpd.json
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/2.4.x/test/modules/md/data/store_migrate/1.0/sample1/httpd.json?rev=1895947&view=auto
==============================================================================
--- httpd/httpd/branches/2.4.x/test/modules/md/data/store_migrate/1.0/sample1/httpd.json (added)
+++ httpd/httpd/branches/2.4.x/test/modules/md/data/store_migrate/1.0/sample1/httpd.json Tue Dec 14 11:33:27 2021
@@ -0,0 +1,6 @@
+{
+ "proto": {
+ "http": true,
+ "https": true
+ }
+}
\ No newline at end of file
Added: httpd/httpd/branches/2.4.x/test/modules/md/data/store_migrate/1.0/sample1/md_store.json
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/2.4.x/test/modules/md/data/store_migrate/1.0/sample1/md_store.json?rev=1895947&view=auto
==============================================================================
--- httpd/httpd/branches/2.4.x/test/modules/md/data/store_migrate/1.0/sample1/md_store.json (added)
+++ httpd/httpd/branches/2.4.x/test/modules/md/data/store_migrate/1.0/sample1/md_store.json Tue Dec 14 11:33:27 2021
@@ -0,0 +1,7 @@
+{
+ "version": "0.6.1-git",
+ "store": {
+ "version": 1.0
+ },
+ "key": "YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXphYmNkZWZnaGlqa2xtbm9wcXJzdHV2"
+}
\ No newline at end of file
Added: httpd/httpd/branches/2.4.x/test/modules/md/data/test_920/002.pubcert
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/2.4.x/test/modules/md/data/test_920/002.pubcert?rev=1895947&view=auto
==============================================================================
--- httpd/httpd/branches/2.4.x/test/modules/md/data/test_920/002.pubcert (added)
+++ httpd/httpd/branches/2.4.x/test/modules/md/data/test_920/002.pubcert Tue Dec 14 11:33:27 2021
@@ -0,0 +1,58 @@
+-----BEGIN CERTIFICATE-----
+MIIFYDCCBEigAwIBAgISAwOcRk1FTt55/NLK6Fn2aPJpMA0GCSqGSIb3DQEBCwUA
+MEoxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MSMwIQYDVQQD
+ExpMZXQncyBFbmNyeXB0IEF1dGhvcml0eSBYMzAeFw0xOTA1MzExNjA2MzVaFw0x
+OTA4MjkxNjA2MzVaMBYxFDASBgNVBAMTC2Vpc3Npbmcub3JnMIIBIjANBgkqhkiG
+9w0BAQEFAAOCAQ8AMIIBCgKCAQEA9d5xZdknImIPfmiUaiiRhHLx4bvazWRTgA2+
+etRNKr42MRjkuLbAhvxGjhw4El0GJlbngKTfiSK0Vq0idW/ehUr++czRSDrRVfqq
+qcI/F4NXLIbIZfmR7/vG0IP8Xc8D9VyQCX0uDapCvw+A/U46p0VOZz4bIB/bl0BW
+/mqBvVhBU9owskUcPjwwI/tK6My933CUVKXuFpPZ4V7zoY0/8Xa6JmWC2q1+7XmE
+h51hPnU35dYH1bA7WblX8rVxnEPCyCOgABVLKb6NhWfTCEqy+yzr32KsoSR1xqe4
+T2EeTcoamwF2yhz2zRC4glX0LM4inJ1/ZOQ+nKbFZTOPVWEnLQIDAQABo4ICcjCC
+Am4wDgYDVR0PAQH/BAQDAgWgMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcD
+AjAMBgNVHRMBAf8EAjAAMB0GA1UdDgQWBBTfO7pZGPLsa0NuPZMG4NGlr1TaWjAf
+BgNVHSMEGDAWgBSoSmpjBH3duubRObemRWXv86jsoTBvBggrBgEFBQcBAQRjMGEw
+LgYIKwYBBQUHMAGGImh0dHA6Ly9vY3NwLmludC14My5sZXRzZW5jcnlwdC5vcmcw
+LwYIKwYBBQUHMAKGI2h0dHA6Ly9jZXJ0LmludC14My5sZXRzZW5jcnlwdC5vcmcv
+MCcGA1UdEQQgMB6CC2Vpc3Npbmcub3Jngg93d3cuZWlzc2luZy5vcmcwTAYDVR0g
+BEUwQzAIBgZngQwBAgEwNwYLKwYBBAGC3xMBAQEwKDAmBggrBgEFBQcCARYaaHR0
+cDovL2Nwcy5sZXRzZW5jcnlwdC5vcmcwggEFBgorBgEEAdZ5AgQCBIH2BIHzAPEA
+dwB0ftqDMa0zEJEhnM4lT0Jwwr/9XkIgCMY3NXnmEHvMVgAAAWsO24QlAAAEAwBI
+MEYCIQD8yd2uHl2DNgvnBkSiA8vsK5pOv204NixI9F89LWERwgIhAPMLLiZkFG2h
+DTpEwF50BbZ+laYH8VP03Teq5csk2lX0AHYAKTxRllTIOWW6qlD8WAfUt2+/WHop
+ctykwwz05UVH9HgAAAFrDtuEFgAABAMARzBFAiEA3bYpKSNigSe0HuDyH/kerTW2
+55ugvODp6d+vNbNmgZoCIGTd4cio769BTKfLJTqNbjc9sKK9T7XkHUO4JgQdY6Nq
+MA0GCSqGSIb3DQEBCwUAA4IBAQBeatZxh8leVmeFE/IYTKKqHyZqTccJKdugXIOr
+uIF6sLup/8Fv/2N0wZc+edkj+NCyWhxxkZULyW6xhlL7rtzcwLYbQBSxKvT4Utur
+01a5bwhM62MdMjzkFgCCa5nRKPQ7bc684RrUFNi94d0KSb5ArFv8wovqPW7jbmFp
+X50dYKCE+wohFPHcsQapnV0lXK4+5qJZSZkp/pHANdndLCvFfzRHhV4nqRA12G2T
+VVWjdHN6ShL2uykJVAnSBhu/XD4mh79Yq9TQtS1DHfP3HcKstLqR0nrwBFaB6087
+jXfIpJ46yObq001qHeUMhT+B3WI2YPp/hY7u8A9+hCmDyyq8
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIEkjCCA3qgAwIBAgIQCgFBQgAAAVOFc2oLheynCDANBgkqhkiG9w0BAQsFADA/
+MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT
+DkRTVCBSb290IENBIFgzMB4XDTE2MDMxNzE2NDA0NloXDTIxMDMxNzE2NDA0Nlow
+SjELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUxldCdzIEVuY3J5cHQxIzAhBgNVBAMT
+GkxldCdzIEVuY3J5cHQgQXV0aG9yaXR5IFgzMIIBIjANBgkqhkiG9w0BAQEFAAOC
+AQ8AMIIBCgKCAQEAnNMM8FrlLke3cl03g7NoYzDq1zUmGSXhvb418XCSL7e4S0EF
+q6meNQhY7LEqxGiHC6PjdeTm86dicbp5gWAf15Gan/PQeGdxyGkOlZHP/uaZ6WA8
+SMx+yk13EiSdRxta67nsHjcAHJyse6cF6s5K671B5TaYucv9bTyWaN8jKkKQDIZ0
+Z8h/pZq4UmEUEz9l6YKHy9v6Dlb2honzhT+Xhq+w3Brvaw2VFn3EK6BlspkENnWA
+a6xK8xuQSXgvopZPKiAlKQTGdMDQMc2PMTiVFrqoM7hD8bEfwzB/onkxEz0tNvjj
+/PIzark5McWvxI0NHWQWM6r6hCm21AvA2H3DkwIDAQABo4IBfTCCAXkwEgYDVR0T
+AQH/BAgwBgEB/wIBADAOBgNVHQ8BAf8EBAMCAYYwfwYIKwYBBQUHAQEEczBxMDIG
+CCsGAQUFBzABhiZodHRwOi8vaXNyZy50cnVzdGlkLm9jc3AuaWRlbnRydXN0LmNv
+bTA7BggrBgEFBQcwAoYvaHR0cDovL2FwcHMuaWRlbnRydXN0LmNvbS9yb290cy9k
+c3Ryb290Y2F4My5wN2MwHwYDVR0jBBgwFoAUxKexpHsscfrb4UuQdf/EFWCFiRAw
+VAYDVR0gBE0wSzAIBgZngQwBAgEwPwYLKwYBBAGC3xMBAQEwMDAuBggrBgEFBQcC
+ARYiaHR0cDovL2Nwcy5yb290LXgxLmxldHNlbmNyeXB0Lm9yZzA8BgNVHR8ENTAz
+MDGgL6AthitodHRwOi8vY3JsLmlkZW50cnVzdC5jb20vRFNUUk9PVENBWDNDUkwu
+Y3JsMB0GA1UdDgQWBBSoSmpjBH3duubRObemRWXv86jsoTANBgkqhkiG9w0BAQsF
+AAOCAQEA3TPXEfNjWDjdGBX7CVW+dla5cEilaUcne8IkCJLxWh9KEik3JHRRHGJo
+uM2VcGfl96S8TihRzZvoroed6ti6WqEBmtzw3Wodatg+VyOeph4EYpr/1wXKtx8/
+wApIvJSwtmVi4MFU5aMqrSDE6ea73Mj2tcMyo5jMd6jmeWUHK8so/joWUoHOUgwu
+X4Po1QYz+3dszkDqMp4fklxBwXRsW10KXzPMTZ+sOPAveyxindmjkW8lGy+QsRlG
+PfZ+G6Z6h7mjem0Y+iWlkYcV4PIWL1iwBi8saCbGS5jN2p8M+X+Q7UNKEkROb3N6
+KOqkqm57TH2H3eDJAkSnh6/DNFu0Qg==
+-----END CERTIFICATE-----
Added: httpd/httpd/branches/2.4.x/test/modules/md/data/test_conf_validate/test_014.conf
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/2.4.x/test/modules/md/data/test_conf_validate/test_014.conf?rev=1895947&view=auto
==============================================================================
--- httpd/httpd/branches/2.4.x/test/modules/md/data/test_conf_validate/test_014.conf (added)
+++ httpd/httpd/branches/2.4.x/test/modules/md/data/test_conf_validate/test_014.conf Tue Dec 14 11:33:27 2021
@@ -0,0 +1,8 @@
+# global server name as managed domain name
+
+MDomain resistance.fritz.box www.example2.org
+
+<VirtualHost *:12346>
+ ServerName www.example2.org
+
+</VirtualHost>
Added: httpd/httpd/branches/2.4.x/test/modules/md/data/test_drive/test1.example.org.conf
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/2.4.x/test/modules/md/data/test_drive/test1.example.org.conf?rev=1895947&view=auto
==============================================================================
--- httpd/httpd/branches/2.4.x/test/modules/md/data/test_drive/test1.example.org.conf (added)
+++ httpd/httpd/branches/2.4.x/test/modules/md/data/test_drive/test1.example.org.conf Tue Dec 14 11:33:27 2021
@@ -0,0 +1,6 @@
+# A setup that required manual driving, e.g. invoking a2md outside apache
+#
+MDRenewMode manual
+
+MDomain test1.not-forbidden.org www.test1.not-forbidden.org mail.test1.not-forbidden.org
+
Added: httpd/httpd/branches/2.4.x/test/modules/md/data/test_roundtrip/temp.conf
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/2.4.x/test/modules/md/data/test_roundtrip/temp.conf?rev=1895947&view=auto
==============================================================================
--- httpd/httpd/branches/2.4.x/test/modules/md/data/test_roundtrip/temp.conf (added)
+++ httpd/httpd/branches/2.4.x/test/modules/md/data/test_roundtrip/temp.conf Tue Dec 14 11:33:27 2021
@@ -0,0 +1,27 @@
+ MDDriveMode manual
+ MDCertificateAuthority http://localhost:4000/directory
+ MDCertificateProtocol ACME
+ MDCertificateAgreement http://boulder:4000/terms/v1
+
+ ServerAdmin mailto:admin@test102-1499953506.org
+
+ ManagedDomain test102-1499953506.org test-a.test102-1499953506.org test-b.test102-1499953506.org
+
+<VirtualHost *:5001>
+ ServerName test-a.test102-1499953506.org
+ DocumentRoot htdocs/a
+
+ SSLEngine on
+ SSLCertificateFile /Users/sei/projects/mod_md/test/gen/apache/md/domains/test102-1499953506.org/cert.pem
+ SSLCertificateKeyFile /Users/sei/projects/mod_md/test/gen/apache/md/domains/test102-1499953506.org/pkey.pem
+</VirtualHost>
+
+<VirtualHost *:5001>
+ ServerName test-b.test102-1499953506.org
+ DocumentRoot htdocs/b
+
+ SSLEngine on
+ SSLCertificateFile /Users/sei/projects/mod_md/test/gen/apache/md/domains/test102-1499953506.org/cert.pem
+ SSLCertificateKeyFile /Users/sei/projects/mod_md/test/gen/apache/md/domains/test102-1499953506.org/pkey.pem
+</VirtualHost>
+
Added: httpd/httpd/branches/2.4.x/test/modules/md/dns01.py
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/2.4.x/test/modules/md/dns01.py?rev=1895947&view=auto
==============================================================================
--- httpd/httpd/branches/2.4.x/test/modules/md/dns01.py (added)
+++ httpd/httpd/branches/2.4.x/test/modules/md/dns01.py Tue Dec 14 11:33:27 2021
@@ -0,0 +1,62 @@
+#!/usr/bin/env python3
+
+import subprocess
+import sys
+
+curl = "curl"
+challtestsrv = "localhost:8055"
+
+
+def run(args):
+ sys.stderr.write(f"run: {' '.join(args)}\n")
+ p = subprocess.Popen(args, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+ output, errput = p.communicate(None)
+ rv = p.wait()
+ if rv != 0:
+ sys.stderr.write(errput.decode())
+ sys.stdout.write(output.decode())
+ return rv
+
+
+def teardown(domain):
+ rv = run([curl, '-s', '-d', f'{{"host":"_acme-challenge.{domain}"}}',
+ f'{challtestsrv}/clear-txt'])
+ if rv == 0:
+ rv = run([curl, '-s', '-d', f'{{"host":"{domain}"}}',
+ f'{challtestsrv}/set-txt'])
+ return rv
+
+
+def setup(domain, challenge):
+ teardown(domain)
+ rv = run([curl, '-s', '-d', f'{{"host":"{domain}", "addresses":["127.0.0.1"]}}',
+ f'{challtestsrv}/set-txt'])
+ if rv == 0:
+ rv = run([curl, '-s', '-d', f'{{"host":"_acme-challenge.{domain}.", "value":"{challenge}"}}',
+ f'{challtestsrv}/set-txt'])
+ return rv
+
+
+def main(argv):
+ if len(argv) > 1:
+ if argv[1] == 'setup':
+ if len(argv) != 4:
+ sys.stderr.write("wrong number of arguments: dns01.py setup <domain> <challenge>\n")
+ sys.exit(2)
+ rv = setup(argv[2], argv[3])
+ elif argv[1] == 'teardown':
+ if len(argv) != 3:
+ sys.stderr.write("wrong number of arguments: dns01.py teardown <domain>\n")
+ sys.exit(1)
+ rv = teardown(argv[2])
+ else:
+ sys.stderr.write(f"unknown option {argv[1]}\n")
+ rv = 2
+ else:
+ sys.stderr.write("dns01.py wrong number of arguments\n")
+ rv = 2
+ sys.exit(rv)
+
+
+if __name__ == "__main__":
+ main(sys.argv)
Propchange: httpd/httpd/branches/2.4.x/test/modules/md/dns01.py
------------------------------------------------------------------------------
svn:executable = *
Added: httpd/httpd/branches/2.4.x/test/modules/md/http_challenge_foobar.py
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/2.4.x/test/modules/md/http_challenge_foobar.py?rev=1895947&view=auto
==============================================================================
--- httpd/httpd/branches/2.4.x/test/modules/md/http_challenge_foobar.py (added)
+++ httpd/httpd/branches/2.4.x/test/modules/md/http_challenge_foobar.py Tue Dec 14 11:33:27 2021
@@ -0,0 +1,27 @@
+#!/usr/bin/env python3
+import os
+import re
+import sys
+
+
+def main(argv):
+ if len(argv) < 4:
+ sys.stderr.write(f"{argv[0]} without too few arguments")
+ sys.exit(7)
+ store_dir = argv[1]
+ event = argv[2]
+ mdomain = argv[3]
+ m = re.match(r'(\S+):(\S+):(\S+)', event)
+ if m and 'challenge-setup' == m.group(1) and 'http-01' == m.group(2):
+ dns_name = m.group(3)
+ challenge_file = f"{store_dir}/challenges/{dns_name}/acme-http-01.txt"
+ if not os.path.isfile(challenge_file):
+ sys.stderr.write(f"{argv[0]} does not exist: {challenge_file}")
+ sys.exit(8)
+ with open(challenge_file, 'w') as fd:
+ fd.write('this_is_an_invalidated_http-01_challenge')
+ sys.exit(0)
+
+
+if __name__ == "__main__":
+ main(sys.argv)
Propchange: httpd/httpd/branches/2.4.x/test/modules/md/http_challenge_foobar.py
------------------------------------------------------------------------------
svn:executable = *
Added: httpd/httpd/branches/2.4.x/test/modules/md/md_acme.py
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/2.4.x/test/modules/md/md_acme.py?rev=1895947&view=auto
==============================================================================
--- httpd/httpd/branches/2.4.x/test/modules/md/md_acme.py (added)
+++ httpd/httpd/branches/2.4.x/test/modules/md/md_acme.py Tue Dec 14 11:33:27 2021
@@ -0,0 +1,125 @@
+import logging
+import os
+import shutil
+import subprocess
+import time
+from abc import ABCMeta, abstractmethod
+from datetime import datetime, timedelta
+from threading import Thread
+from typing import Dict
+
+from .md_env import MDTestEnv
+
+
+log = logging.getLogger(__name__)
+
+
+def monitor_proc(env: MDTestEnv, proc):
+ _env = env
+ proc.wait()
+
+
+class ACMEServer:
+ __metaclass__ = ABCMeta
+
+ @abstractmethod
+ def start(self):
+ raise NotImplementedError
+
+ @abstractmethod
+ def stop(self):
+ raise NotImplementedError
+
+ @abstractmethod
+ def install_ca_bundle(self, dest):
+ raise NotImplementedError
+
+
+class MDPebbleRunner(ACMEServer):
+
+ def __init__(self, env: MDTestEnv, configs: Dict[str, str]):
+ self.env = env
+ self.configs = configs
+ self._current = 'default'
+ self._pebble = None
+ self._challtestsrv = None
+ self._log = None
+
+ def start(self, config: str = None):
+ if config is not None and config != self._current:
+ # change, tear down and start again
+ assert config in self.configs
+ self.stop()
+ self._current = config
+ elif self._pebble is not None:
+ # already running
+ return
+ args = ['pebble', '-config', self.configs[self._current], '-dnsserver', ':8053']
+ env = {}
+ env.update(os.environ)
+ env['PEBBLE_VA_NOSLEEP'] = '1'
+ self._log = open(f'{self.env.gen_dir}/pebble.log', 'w')
+ self._pebble = subprocess.Popen(args=args, env=env,
+ stdout=self._log, stderr=self._log)
+ t = Thread(target=monitor_proc, args=(self.env, self._pebble))
+ t.start()
+
+ args = ['pebble-challtestsrv', '-http01', '', '-https01', '', '-tlsalpn01', '']
+ self._challtestsrv = subprocess.Popen(args, stdout=self._log, stderr=self._log)
+ t = Thread(target=monitor_proc, args=(self.env, self._challtestsrv))
+ t.start()
+ self.install_ca_bundle(self.env.acme_ca_pemfile)
+ # disable ipv6 default address, this gives trouble inside docker
+ end = datetime.now() + timedelta(seconds=5)
+ while True:
+ r = self.env.run(['curl', 'localhost:8055/'])
+ if r.exit_code == 0:
+ break
+ if datetime.now() > end:
+ raise TimeoutError(f'unable to contact pebble-challtestsrv on localhost:8055')
+ time.sleep(.1)
+ r = self.env.run(['curl', '-d', f'{{"ip":""}}',
+ 'localhost:8055/set-default-ipv6'])
+ assert r.exit_code == 0, f"{r}"
+
+ def stop(self):
+ if self._pebble:
+ self._pebble.terminate()
+ self._pebble = None
+ if self._challtestsrv:
+ self._challtestsrv.terminate()
+ self._challtestsrv = None
+ if self._log:
+ self._log.close()
+ self._log = None
+
+ def install_ca_bundle(self, dest):
+ shutil.copyfile(self.env.ca.cert_file, dest)
+ end = datetime.now() + timedelta(seconds=20)
+ while datetime.now() < end:
+ r = self.env.curl_get('https://localhost:15000/roots/0', insecure=True)
+ if r.exit_code == 0:
+ with open(dest, 'a') as fd:
+ fd.write(r.stdout)
+ break
+
+
+class MDBoulderRunner(ACMEServer):
+
+ def __init__(self, env: MDTestEnv):
+ self.env = env
+ self.install_ca_bundle(self.env.acme_ca_pemfile)
+
+ def start(self, config=None):
+ pass
+
+ def stop(self):
+ pass
+
+ def install_ca_bundle(self, dest):
+ r = self.env.run([
+ 'docker', 'exec', 'boulder_boulder_1', 'bash', '-c', "cat /tmp/root*.pem"
+ ])
+ assert r.exit_code == 0
+ with open(dest, 'w') as fd:
+ fd.write(r.stdout)
Propchange: httpd/httpd/branches/2.4.x/test/modules/md/md_acme.py
------------------------------------------------------------------------------
svn:executable = *
Added: httpd/httpd/branches/2.4.x/test/modules/md/md_cert_util.py
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/2.4.x/test/modules/md/md_cert_util.py?rev=1895947&view=auto
==============================================================================
--- httpd/httpd/branches/2.4.x/test/modules/md/md_cert_util.py (added)
+++ httpd/httpd/branches/2.4.x/test/modules/md/md_cert_util.py Tue Dec 14 11:33:27 2021
@@ -0,0 +1,239 @@
+import logging
+import re
+import os
+import socket
+import OpenSSL
+import time
+import sys
+
+from datetime import datetime
+from datetime import tzinfo
+from datetime import timedelta
+from http.client import HTTPConnection
+from urllib.parse import urlparse
+
+
+SEC_PER_DAY = 24 * 60 * 60
+
+
+log = logging.getLogger(__name__)
+
+
+class MDCertUtil(object):
+ # Utility class for inspecting certificates in test cases
+ # Uses PyOpenSSL: https://pyopenssl.org/en/stable/index.html
+
+ @classmethod
+ def create_self_signed_cert(cls, path, name_list, valid_days, serial=1000):
+ domain = name_list[0]
+ if not os.path.exists(path):
+ os.makedirs(path)
+
+ cert_file = os.path.join(path, 'pubcert.pem')
+ pkey_file = os.path.join(path, 'privkey.pem')
+ # create a key pair
+ if os.path.exists(pkey_file):
+ key_buffer = open(pkey_file, 'rt').read()
+ k = OpenSSL.crypto.load_privatekey(OpenSSL.crypto.FILETYPE_PEM, key_buffer)
+ else:
+ k = OpenSSL.crypto.PKey()
+ k.generate_key(OpenSSL.crypto.TYPE_RSA, 2048)
+
+ # create a self-signed cert
+ cert = OpenSSL.crypto.X509()
+ cert.get_subject().C = "DE"
+ cert.get_subject().ST = "NRW"
+ cert.get_subject().L = "Muenster"
+ cert.get_subject().O = "greenbytes GmbH"
+ cert.get_subject().CN = domain
+ cert.set_serial_number(serial)
+ cert.gmtime_adj_notBefore(valid_days["notBefore"] * SEC_PER_DAY)
+ cert.gmtime_adj_notAfter(valid_days["notAfter"] * SEC_PER_DAY)
+ cert.set_issuer(cert.get_subject())
+
+ cert.add_extensions([OpenSSL.crypto.X509Extension(
+ b"subjectAltName", False, b", ".join(map(lambda n: b"DNS:" + n.encode(), name_list))
+ )])
+ cert.set_pubkey(k)
+ cert.sign(k, 'sha1')
+
+ open(cert_file, "wt").write(
+ OpenSSL.crypto.dump_certificate(OpenSSL.crypto.FILETYPE_PEM, cert).decode('utf-8'))
+ open(pkey_file, "wt").write(
+ OpenSSL.crypto.dump_privatekey(OpenSSL.crypto.FILETYPE_PEM, k).decode('utf-8'))
+
+ @classmethod
+ def load_server_cert(cls, host_ip, host_port, host_name, tls=None, ciphers=None):
+ ctx = OpenSSL.SSL.Context(OpenSSL.SSL.SSLv23_METHOD)
+ if tls is not None and tls != 1.0:
+ ctx.set_options(OpenSSL.SSL.OP_NO_TLSv1)
+ if tls is not None and tls != 1.1:
+ ctx.set_options(OpenSSL.SSL.OP_NO_TLSv1_1)
+ if tls is not None and tls != 1.2:
+ ctx.set_options(OpenSSL.SSL.OP_NO_TLSv1_2)
+ if tls is not None and tls != 1.3 and hasattr(OpenSSL.SSL, "OP_NO_TLSv1_3"):
+ ctx.set_options(OpenSSL.SSL.OP_NO_TLSv1_3)
+ if ciphers is not None:
+ ctx.set_cipher_list(ciphers)
+ s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ connection = OpenSSL.SSL.Connection(ctx, s)
+ connection.connect((host_ip, int(host_port)))
+ connection.setblocking(1)
+ connection.set_tlsext_host_name(host_name.encode('utf-8'))
+ connection.do_handshake()
+ peer_cert = connection.get_peer_certificate()
+ return MDCertUtil(None, cert=peer_cert)
+
+ @classmethod
+ def parse_pem_cert(cls, text):
+ cert = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM, text.encode('utf-8'))
+ return MDCertUtil(None, cert=cert)
+
+ @classmethod
+ def get_plain(cls, url, timeout):
+ server = urlparse(url)
+ try_until = time.time() + timeout
+ while time.time() < try_until:
+ # noinspection PyBroadException
+ try:
+ c = HTTPConnection(server.hostname, server.port, timeout=timeout)
+ c.request('GET', server.path)
+ resp = c.getresponse()
+ data = resp.read()
+ c.close()
+ return data
+ except IOError:
+ log.debug("connect error:", sys.exc_info()[0])
+ time.sleep(.1)
+ except:
+ log.error("Unexpected error:", sys.exc_info()[0])
+ log.error("Unable to contact server after %d sec" % timeout)
+ return None
+
+ def __init__(self, cert_path, cert=None):
+ if cert_path is not None:
+ self.cert_path = cert_path
+ # load certificate and private key
+ if cert_path.startswith("http"):
+ cert_data = self.get_plain(cert_path, 1)
+ else:
+ cert_data = MDCertUtil._load_binary_file(cert_path)
+
+ for file_type in (OpenSSL.crypto.FILETYPE_PEM, OpenSSL.crypto.FILETYPE_ASN1):
+ try:
+ self.cert = OpenSSL.crypto.load_certificate(file_type, cert_data)
+ except Exception as error:
+ self.error = error
+ if cert is not None:
+ self.cert = cert
+
+ if self.cert is None:
+ raise self.error
+
+ def get_issuer(self):
+ return self.cert.get_issuer()
+
+ def get_serial(self):
+ # the string representation of a serial number is not unique. Some
+ # add leading 0s to align with word boundaries.
+ return ("%lx" % (self.cert.get_serial_number())).upper()
+
+ def same_serial_as(self, other):
+ if isinstance(other, MDCertUtil):
+ return self.cert.get_serial_number() == other.cert.get_serial_number()
+ elif isinstance(other, OpenSSL.crypto.X509):
+ return self.cert.get_serial_number() == other.get_serial_number()
+ elif isinstance(other, str):
+ # assume a hex number
+ return self.cert.get_serial_number() == int(other, 16)
+ elif isinstance(other, int):
+ return self.cert.get_serial_number() == other
+ return False
+
+ def get_not_before(self):
+ tsp = self.cert.get_notBefore()
+ return self._parse_tsp(tsp)
+
+ def get_not_after(self):
+ tsp = self.cert.get_notAfter()
+ return self._parse_tsp(tsp)
+
+ def get_cn(self):
+ return self.cert.get_subject().CN
+
+ def get_key_length(self):
+ return self.cert.get_pubkey().bits()
+
+ def get_san_list(self):
+ text = OpenSSL.crypto.dump_certificate(OpenSSL.crypto.FILETYPE_TEXT, self.cert).decode("utf-8")
+ m = re.search(r"X509v3 Subject Alternative Name:\s*(.*)", text)
+ sans_list = []
+ if m:
+ sans_list = m.group(1).split(",")
+
+ def _strip_prefix(s):
+ return s.split(":")[1] if s.strip().startswith("DNS:") else s.strip()
+ return list(map(_strip_prefix, sans_list))
+
+ def get_must_staple(self):
+ text = OpenSSL.crypto.dump_certificate(OpenSSL.crypto.FILETYPE_TEXT, self.cert).decode("utf-8")
+ m = re.search(r"1.3.6.1.5.5.7.1.24:\s*\n\s*0....", text)
+ if not m:
+ # Newer openssl versions print this differently
+ m = re.search(r"TLS Feature:\s*\n\s*status_request\s*\n", text)
+ return m is not None
+
+ @classmethod
+ def validate_privkey(cls, privkey_path, passphrase=None):
+ privkey_data = cls._load_binary_file(privkey_path)
+ if passphrase:
+ privkey = OpenSSL.crypto.load_privatekey(OpenSSL.crypto.FILETYPE_PEM, privkey_data, passphrase)
+ else:
+ privkey = OpenSSL.crypto.load_privatekey(OpenSSL.crypto.FILETYPE_PEM, privkey_data)
+ return privkey.check()
+
+ def validate_cert_matches_priv_key(self, privkey_path):
+ # Verifies that the private key and cert match.
+ privkey_data = MDCertUtil._load_binary_file(privkey_path)
+ privkey = OpenSSL.crypto.load_privatekey(OpenSSL.crypto.FILETYPE_PEM, privkey_data)
+ context = OpenSSL.SSL.Context(OpenSSL.SSL.SSLv23_METHOD)
+ context.use_privatekey(privkey)
+ context.use_certificate(self.cert)
+ context.check_privatekey()
+
+ # --------- _utils_ ---------
+
+ def astr(self, s):
+ return s.decode('utf-8')
+
+ def _parse_tsp(self, tsp):
+ # timestampss returned by PyOpenSSL are bytes
+ # parse date and time part
+ s = ("%s-%s-%s %s:%s:%s" % (self.astr(tsp[0:4]), self.astr(tsp[4:6]), self.astr(tsp[6:8]),
+ self.astr(tsp[8:10]), self.astr(tsp[10:12]), self.astr(tsp[12:14])))
+ timestamp = datetime.strptime(s, '%Y-%m-%d %H:%M:%S')
+ # adjust timezone
+ tz_h, tz_m = 0, 0
+ m = re.match(r"([+\-]\d{2})(\d{2})", self.astr(tsp[14:]))
+ if m:
+ tz_h, tz_m = int(m.group(1)), int(m.group(2)) if tz_h > 0 else -1 * int(m.group(2))
+ return timestamp.replace(tzinfo=self.FixedOffset(60 * tz_h + tz_m))
+
+ @classmethod
+ def _load_binary_file(cls, path):
+ with open(path, mode="rb") as file:
+ return file.read()
+
+ class FixedOffset(tzinfo):
+
+ def __init__(self, offset):
+ self.__offset = timedelta(minutes=offset)
+
+ def utcoffset(self, dt):
+ return self.__offset
+
+ def tzname(self, dt):
+ return None
+
+ def dst(self, dt):
+ return timedelta(0)
Propchange: httpd/httpd/branches/2.4.x/test/modules/md/md_cert_util.py
------------------------------------------------------------------------------
svn:executable = *
Added: httpd/httpd/branches/2.4.x/test/modules/md/md_certs.py
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/2.4.x/test/modules/md/md_certs.py?rev=1895947&view=auto
==============================================================================
--- httpd/httpd/branches/2.4.x/test/modules/md/md_certs.py (added)
+++ httpd/httpd/branches/2.4.x/test/modules/md/md_certs.py Tue Dec 14 11:33:27 2021
@@ -0,0 +1,444 @@
+import os
+import re
+from datetime import timedelta, datetime
+from typing import List, Any, Optional
+
+from cryptography import x509
+from cryptography.hazmat.backends import default_backend
+from cryptography.hazmat.primitives import hashes
+from cryptography.hazmat.primitives.asymmetric import ec, rsa
+from cryptography.hazmat.primitives.asymmetric.ec import EllipticCurvePrivateKey
+from cryptography.hazmat.primitives.asymmetric.rsa import RSAPrivateKey
+from cryptography.hazmat.primitives.serialization import Encoding, PrivateFormat, NoEncryption, load_pem_private_key
+from cryptography.x509 import ExtendedKeyUsageOID, NameOID
+
+
+EC_SUPPORTED = {}
+EC_SUPPORTED.update([(curve.name.upper(), curve) for curve in [
+ ec.SECP192R1,
+ ec.SECP224R1,
+ ec.SECP256R1,
+ ec.SECP384R1,
+]])
+
+
+def _private_key(key_type):
+ if isinstance(key_type, str):
+ key_type = key_type.upper()
+ m = re.match(r'^(RSA)?(\d+)$', key_type)
+ if m:
+ key_type = int(m.group(2))
+
+ if isinstance(key_type, int):
+ return rsa.generate_private_key(
+ public_exponent=65537,
+ key_size=key_type,
+ backend=default_backend()
+ )
+ if not isinstance(key_type, ec.EllipticCurve) and key_type in EC_SUPPORTED:
+ key_type = EC_SUPPORTED[key_type]
+ return ec.generate_private_key(
+ curve=key_type,
+ backend=default_backend()
+ )
+
+
+class CertificateSpec:
+
+ def __init__(self, name: str = None, domains: List[str] = None,
+ email: str = None,
+ key_type: str = None, single_file: bool = False,
+ valid_from: timedelta = timedelta(days=-1),
+ valid_to: timedelta = timedelta(days=89),
+ client: bool = False,
+ sub_specs: List['CertificateSpec'] = None):
+ self._name = name
+ self.domains = domains
+ self.client = client
+ self.email = email
+ self.key_type = key_type
+ self.single_file = single_file
+ self.valid_from = valid_from
+ self.valid_to = valid_to
+ self.sub_specs = sub_specs
+
+ @property
+ def name(self) -> Optional[str]:
+ if self._name:
+ return self._name
+ elif self.domains:
+ return self.domains[0]
+ return None
+
+
+class Credentials:
+
+ def __init__(self, name: str, cert: Any, pkey: Any):
+ self._name = name
+ self._cert = cert
+ self._pkey = pkey
+ self._cert_file = None
+ self._pkey_file = None
+ self._store = None
+
+ @property
+ def name(self) -> str:
+ return self._name
+
+ @property
+ def subject(self) -> x509.Name:
+ return self._cert.subject
+
+ @property
+ def key_type(self):
+ if isinstance(self._pkey, RSAPrivateKey):
+ return f"rsa{self._pkey.key_size}"
+ elif isinstance(self._pkey, EllipticCurvePrivateKey):
+ return f"{self._pkey.curve.name}"
+ else:
+ raise Exception(f"unknown key type: {self._pkey}")
+
+ @property
+ def private_key(self) -> Any:
+ return self._pkey
+
+ @property
+ def certificate(self) -> Any:
+ return self._cert
+
+ @property
+ def cert_pem(self) -> bytes:
+ return self._cert.public_bytes(Encoding.PEM)
+
+ @property
+ def pkey_pem(self) -> bytes:
+ return self._pkey.private_bytes(
+ Encoding.PEM,
+ PrivateFormat.TraditionalOpenSSL if self.key_type.startswith('rsa') else PrivateFormat.PKCS8,
+ NoEncryption())
+
+ def set_store(self, store: 'CertStore'):
+ self._store = store
+
+ def set_files(self, cert_file: str, pkey_file: str = None):
+ self._cert_file = cert_file
+ self._pkey_file = pkey_file
+
+ @property
+ def cert_file(self) -> str:
+ return self._cert_file
+
+ @property
+ def pkey_file(self) -> Optional[str]:
+ return self._pkey_file
+
+ def get_first(self, name) -> Optional['Credentials']:
+ creds = self._store.get_credentials_for_name(name) if self._store else []
+ return creds[0] if len(creds) else None
+
+ def get_credentials_for_name(self, name) -> List['Credentials']:
+ return self._store.get_credentials_for_name(name) if self._store else []
+
+ def issue_certs(self, specs: List[CertificateSpec],
+ chain: List['Credentials'] = None) -> List['Credentials']:
+ return [self.issue_cert(spec=spec, chain=chain) for spec in specs]
+
+ def issue_cert(self, spec: CertificateSpec, chain: List['Credentials'] = None) -> 'Credentials':
+ key_type = spec.key_type if spec.key_type else self.key_type
+ creds = self._store.load_credentials(name=spec.name, key_type=key_type, single_file=spec.single_file) \
+ if self._store else None
+ if creds is None:
+ creds = MDTestCA.create_credentials(spec=spec, issuer=self, key_type=key_type,
+ valid_from=spec.valid_from, valid_to=spec.valid_to)
+ if self._store:
+ self._store.save(creds, single_file=spec.single_file)
+
+ if spec.sub_specs:
+ if self._store:
+ sub_store = CertStore(fpath=os.path.join(self._store.path, creds.name))
+ creds.set_store(sub_store)
+ subchain = chain.copy() if chain else []
+ subchain.append(self)
+ creds.issue_certs(spec.sub_specs, chain=subchain)
+ return creds
+
+
+class CertStore:
+
+ def __init__(self, fpath: str):
+ self._store_dir = fpath
+ if not os.path.exists(self._store_dir):
+ os.makedirs(self._store_dir)
+ self._creds_by_name = {}
+
+ @property
+ def path(self) -> str:
+ return self._store_dir
+
+ def save(self, creds: Credentials, name: str = None,
+ chain: List[Credentials] = None,
+ single_file: bool = False) -> None:
+ name = name if name is not None else creds.name
+ cert_file = self.get_cert_file(name=name, key_type=creds.key_type)
+ pkey_file = self.get_pkey_file(name=name, key_type=creds.key_type)
+ if single_file:
+ pkey_file = None
+ with open(cert_file, "wb") as fd:
+ fd.write(creds.cert_pem)
+ if chain:
+ for c in chain:
+ fd.write(c.cert_pem)
+ if pkey_file is None:
+ fd.write(creds.pkey_pem)
+ if pkey_file is not None:
+ with open(pkey_file, "wb") as fd:
+ fd.write(creds.pkey_pem)
+ creds.set_files(cert_file, pkey_file)
+ self._add_credentials(name, creds)
+
+ def _add_credentials(self, name: str, creds: Credentials):
+ if name not in self._creds_by_name:
+ self._creds_by_name[name] = []
+ self._creds_by_name[name].append(creds)
+
+ def get_credentials_for_name(self, name) -> List[Credentials]:
+ return self._creds_by_name[name] if name in self._creds_by_name else []
+
+ def get_cert_file(self, name: str, key_type=None) -> str:
+ key_infix = ".{0}".format(key_type) if key_type is not None else ""
+ return os.path.join(self._store_dir, f'{name}{key_infix}.cert.pem')
+
+ def get_pkey_file(self, name: str, key_type=None) -> str:
+ key_infix = ".{0}".format(key_type) if key_type is not None else ""
+ return os.path.join(self._store_dir, f'{name}{key_infix}.pkey.pem')
+
+ def load_pem_cert(self, fpath: str) -> x509.Certificate:
+ with open(fpath) as fd:
+ return x509.load_pem_x509_certificate("".join(fd.readlines()).encode())
+
+ def load_pem_pkey(self, fpath: str):
+ with open(fpath) as fd:
+ return load_pem_private_key("".join(fd.readlines()).encode(), password=None)
+
+ def load_credentials(self, name: str, key_type=None, single_file: bool = False):
+ cert_file = self.get_cert_file(name=name, key_type=key_type)
+ pkey_file = cert_file if single_file else self.get_pkey_file(name=name, key_type=key_type)
+ if os.path.isfile(cert_file) and os.path.isfile(pkey_file):
+ cert = self.load_pem_cert(cert_file)
+ pkey = self.load_pem_pkey(pkey_file)
+ creds = Credentials(name=name, cert=cert, pkey=pkey)
+ creds.set_store(self)
+ creds.set_files(cert_file, pkey_file)
+ self._add_credentials(name, creds)
+ return creds
+ return None
+
+
+class MDTestCA:
+
+ @classmethod
+ def create_root(cls, name: str, store_dir: str, key_type: str = "rsa2048") -> Credentials:
+ store = CertStore(fpath=store_dir)
+ creds = store.load_credentials(name="ca", key_type=key_type)
+ if creds is None:
+ creds = MDTestCA._make_ca_credentials(name=name, key_type=key_type)
+ store.save(creds, name="ca")
+ creds.set_store(store)
+ return creds
+
+ @staticmethod
+ def create_credentials(spec: CertificateSpec, issuer: Credentials, key_type: Any,
+ valid_from: timedelta = timedelta(days=-1),
+ valid_to: timedelta = timedelta(days=89),
+ ) -> Credentials:
+ """Create a certificate signed by this CA for the given domains.
+ :returns: the certificate and private key PEM file paths
+ """
+ if spec.domains and len(spec.domains):
+ creds = MDTestCA._make_server_credentials(name=spec.name, domains=spec.domains,
+ issuer=issuer, valid_from=valid_from,
+ valid_to=valid_to, key_type=key_type)
+ elif spec.client:
+ creds = MDTestCA._make_client_credentials(name=spec.name, issuer=issuer,
+ email=spec.email, valid_from=valid_from,
+ valid_to=valid_to, key_type=key_type)
+ elif spec.name:
+ creds = MDTestCA._make_ca_credentials(name=spec.name, issuer=issuer,
+ valid_from=valid_from, valid_to=valid_to,
+ key_type=key_type)
+ else:
+ raise Exception(f"unrecognized certificate specification: {spec}")
+ return creds
+
+ @staticmethod
+ def _make_x509_name(org_name: str = None, common_name: str = None, parent: x509.Name = None) -> x509.Name:
+ name_pieces = []
+ if org_name:
+ oid = NameOID.ORGANIZATIONAL_UNIT_NAME if parent else NameOID.ORGANIZATION_NAME
+ name_pieces.append(x509.NameAttribute(oid, org_name))
+ elif common_name:
+ name_pieces.append(x509.NameAttribute(NameOID.COMMON_NAME, common_name))
+ if parent:
+ name_pieces.extend([rdn for rdn in parent])
+ return x509.Name(name_pieces)
+
+ @staticmethod
+ def _make_csr(
+ subject: x509.Name,
+ pkey: Any,
+ issuer_subject: Optional[Credentials],
+ valid_from_delta: timedelta = None,
+ valid_until_delta: timedelta = None
+ ):
+ pubkey = pkey.public_key()
+ issuer_subject = issuer_subject if issuer_subject is not None else subject
+
+ valid_from = datetime.now()
+ if valid_until_delta is not None:
+ valid_from += valid_from_delta
+ valid_until = datetime.now()
+ if valid_until_delta is not None:
+ valid_until += valid_until_delta
+
+ return (
+ x509.CertificateBuilder()
+ .subject_name(subject)
+ .issuer_name(issuer_subject)
+ .public_key(pubkey)
+ .not_valid_before(valid_from)
+ .not_valid_after(valid_until)
+ .serial_number(x509.random_serial_number())
+ .add_extension(
+ x509.SubjectKeyIdentifier.from_public_key(pubkey),
+ critical=False,
+ )
+ )
+
+ @staticmethod
+ def _add_ca_usages(csr: Any) -> Any:
+ return csr.add_extension(
+ x509.BasicConstraints(ca=True, path_length=9),
+ critical=True,
+ ).add_extension(
+ x509.KeyUsage(
+ digital_signature=True,
+ content_commitment=False,
+ key_encipherment=False,
+ data_encipherment=False,
+ key_agreement=False,
+ key_cert_sign=True,
+ crl_sign=True,
+ encipher_only=False,
+ decipher_only=False),
+ critical=True
+ ).add_extension(
+ x509.ExtendedKeyUsage([
+ ExtendedKeyUsageOID.CLIENT_AUTH,
+ ExtendedKeyUsageOID.SERVER_AUTH,
+ ExtendedKeyUsageOID.CODE_SIGNING,
+ ]),
+ critical=True
+ )
+
+ @staticmethod
+ def _add_leaf_usages(csr: Any, domains: List[str], issuer: Credentials) -> Any:
+ return csr.add_extension(
+ x509.BasicConstraints(ca=False, path_length=None),
+ critical=True,
+ ).add_extension(
+ x509.AuthorityKeyIdentifier.from_issuer_subject_key_identifier(
+ issuer.certificate.extensions.get_extension_for_class(
+ x509.SubjectKeyIdentifier).value),
+ critical=False
+ ).add_extension(
+ x509.SubjectAlternativeName([x509.DNSName(domain) for domain in domains]),
+ critical=True,
+ ).add_extension(
+ x509.ExtendedKeyUsage([
+ ExtendedKeyUsageOID.SERVER_AUTH,
+ ]),
+ critical=True
+ )
+
+ @staticmethod
+ def _add_client_usages(csr: Any, issuer: Credentials, rfc82name: str = None) -> Any:
+ cert = csr.add_extension(
+ x509.BasicConstraints(ca=False, path_length=None),
+ critical=True,
+ ).add_extension(
+ x509.AuthorityKeyIdentifier.from_issuer_subject_key_identifier(
+ issuer.certificate.extensions.get_extension_for_class(
+ x509.SubjectKeyIdentifier).value),
+ critical=False
+ )
+ if rfc82name:
+ cert.add_extension(
+ x509.SubjectAlternativeName([x509.RFC822Name(rfc82name)]),
+ critical=True,
+ )
+ cert.add_extension(
+ x509.ExtendedKeyUsage([
+ ExtendedKeyUsageOID.CLIENT_AUTH,
+ ]),
+ critical=True
+ )
+ return cert
+
+ @staticmethod
+ def _make_ca_credentials(name, key_type: Any,
+ issuer: Credentials = None,
+ valid_from: timedelta = timedelta(days=-1),
+ valid_to: timedelta = timedelta(days=89),
+ ) -> Credentials:
+ pkey = _private_key(key_type=key_type)
+ if issuer is not None:
+ issuer_subject = issuer.certificate.subject
+ issuer_key = issuer.private_key
+ else:
+ issuer_subject = None
+ issuer_key = pkey
+ subject = MDTestCA._make_x509_name(org_name=name, parent=issuer.subject if issuer else None)
+ csr = MDTestCA._make_csr(subject=subject,
+ issuer_subject=issuer_subject, pkey=pkey,
+ valid_from_delta=valid_from, valid_until_delta=valid_to)
+ csr = MDTestCA._add_ca_usages(csr)
+ cert = csr.sign(private_key=issuer_key,
+ algorithm=hashes.SHA256(),
+ backend=default_backend())
+ return Credentials(name=name, cert=cert, pkey=pkey)
+
+ @staticmethod
+ def _make_server_credentials(name: str, domains: List[str], issuer: Credentials,
+ key_type: Any,
+ valid_from: timedelta = timedelta(days=-1),
+ valid_to: timedelta = timedelta(days=89),
+ ) -> Credentials:
+ name = name
+ pkey = _private_key(key_type=key_type)
+ subject = MDTestCA._make_x509_name(common_name=name, parent=issuer.subject)
+ csr = MDTestCA._make_csr(subject=subject,
+ issuer_subject=issuer.certificate.subject, pkey=pkey,
+ valid_from_delta=valid_from, valid_until_delta=valid_to)
+ csr = MDTestCA._add_leaf_usages(csr, domains=domains, issuer=issuer)
+ cert = csr.sign(private_key=issuer.private_key,
+ algorithm=hashes.SHA256(),
+ backend=default_backend())
+ return Credentials(name=name, cert=cert, pkey=pkey)
+
+ @staticmethod
+ def _make_client_credentials(name: str,
+ issuer: Credentials, email: Optional[str],
+ key_type: Any,
+ valid_from: timedelta = timedelta(days=-1),
+ valid_to: timedelta = timedelta(days=89),
+ ) -> Credentials:
+ pkey = _private_key(key_type=key_type)
+ subject = MDTestCA._make_x509_name(common_name=name, parent=issuer.subject)
+ csr = MDTestCA._make_csr(subject=subject,
+ issuer_subject=issuer.certificate.subject, pkey=pkey,
+ valid_from_delta=valid_from, valid_until_delta=valid_to)
+ csr = MDTestCA._add_client_usages(csr, issuer=issuer, rfc82name=email)
+ cert = csr.sign(private_key=issuer.private_key,
+ algorithm=hashes.SHA256(),
+ backend=default_backend())
+ return Credentials(name=name, cert=cert, pkey=pkey)
Propchange: httpd/httpd/branches/2.4.x/test/modules/md/md_certs.py
------------------------------------------------------------------------------
svn:executable = *
Added: httpd/httpd/branches/2.4.x/test/modules/md/md_conf.py
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/2.4.x/test/modules/md/md_conf.py?rev=1895947&view=auto
==============================================================================
--- httpd/httpd/branches/2.4.x/test/modules/md/md_conf.py (added)
+++ httpd/httpd/branches/2.4.x/test/modules/md/md_conf.py Tue Dec 14 11:33:27 2021
@@ -0,0 +1,81 @@
+from .md_env import MDTestEnv
+from pyhttpd.conf import HttpdConf
+
+
+class MDConf(HttpdConf):
+
+ def __init__(self, env: MDTestEnv, text=None, std_ports=True,
+ local_ca=True, std_vhosts=True, proxy=False,
+ admin=None):
+ super().__init__(env=env)
+
+ if admin is None:
+ admin = f"admin@{env.http_tld}"
+ if len(admin.strip()):
+ self.add_admin(admin)
+
+ if local_ca:
+ self.add([
+ f"MDCertificateAuthority {env.acme_url}",
+ f"MDCertificateAgreement accepted",
+ f"MDCACertificateFile {env.server_dir}/acme-ca.pem",
+ "",
+ ])
+ if std_ports:
+ self.add(f"MDPortMap 80:{env.http_port} 443:{env.https_port}")
+ if env.ssl_module == "tls":
+ self.add(f"TLSListen {env.https_port}")
+ self.add([
+ "<Location /server-status>",
+ " SetHandler server-status",
+ "</Location>",
+ "<Location /md-status>",
+ " SetHandler md-status",
+ "</Location>",
+ ])
+ if std_vhosts:
+ self.add_vhost_test1()
+ if proxy:
+ self.add([
+ f"Listen {self.env.proxy_port}",
+ f"<VirtualHost *:{self.env.proxy_port}>",
+ " ProxyRequests On",
+ " ProxyVia On",
+ " # be totally open",
+ " AllowCONNECT 0-56535",
+ " <Proxy *>",
+ " # No require or other restrictions, this is just a test server",
+ " </Proxy>",
+ "</VirtualHost>",
+ ])
+ if text is not None:
+ self.add(text)
+
+ def add_drive_mode(self, mode):
+ self.add("MDRenewMode \"%s\"\n" % mode)
+
+ def add_renew_window(self, window):
+ self.add("MDRenewWindow %s\n" % window)
+
+ def add_private_key(self, key_type, key_params):
+ self.add("MDPrivateKeys %s %s\n" % (key_type, " ".join(map(lambda p: str(p), key_params))))
+
+ def add_admin(self, email):
+ self.add(f"ServerAdmin mailto:{email}")
+
+ def add_md(self, domains):
+ dlist = " ".join(domains) # without quotes
+ self.add(f"MDomain {dlist}\n")
+
+ def start_md(self, domains):
+ dlist = " ".join([f"\"{d}\"" for d in domains]) # with quotes, #257
+ self.add(f"<MDomain {dlist}>\n")
+
+ def end_md(self):
+ self.add("</MDomain>\n")
+
+ def start_md2(self, domains):
+ self.add("<MDomainSet %s>\n" % " ".join(domains))
+
+ def end_md2(self):
+ self.add("</MDomainSet>\n")
Propchange: httpd/httpd/branches/2.4.x/test/modules/md/md_conf.py
------------------------------------------------------------------------------
svn:executable = *