Add support for encrypted SSL keys (#386)

This commit is contained in:
Giovanni Barillari 2024-08-31 19:07:48 +02:00 committed by GitHub
parent 2c982303a9
commit 033d9e5ffc
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 322 additions and 24 deletions

191
Cargo.lock generated
View file

@ -17,6 +17,17 @@ version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
[[package]]
name = "aes"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0"
dependencies = [
"cfg-if",
"cipher",
"cpufeatures",
]
[[package]]
name = "anyhow"
version = "1.0.86"
@ -62,6 +73,12 @@ version = "0.22.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
[[package]]
name = "base64ct"
version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b"
[[package]]
name = "bitflags"
version = "2.6.0"
@ -77,6 +94,15 @@ dependencies = [
"generic-array",
]
[[package]]
name = "block-padding"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8894febbff9f758034a5b8e12d87918f56dfc64a8e1fe757d65e29041538d93"
dependencies = [
"generic-array",
]
[[package]]
name = "byteorder"
version = "1.5.0"
@ -89,6 +115,15 @@ version = "1.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a12916984aab3fa6e39d655a33e09c0071eb36d6ab3aea5c2d78551f1df6d952"
[[package]]
name = "cbc"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26b52a9543ae338f279b96b0b9fed9c8093744685043739079ce85cd58f289a6"
dependencies = [
"cipher",
]
[[package]]
name = "cc"
version = "1.1.7"
@ -101,6 +136,22 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "cipher"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad"
dependencies = [
"crypto-common",
"inout",
]
[[package]]
name = "const-oid"
version = "0.9.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8"
[[package]]
name = "cpufeatures"
version = "0.2.12"
@ -141,6 +192,16 @@ version = "2.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8566979429cf69b49a5c740c60791108e86440e8be149bbea4fe54d2c32d6e2"
[[package]]
name = "der"
version = "0.7.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0"
dependencies = [
"const-oid",
"zeroize",
]
[[package]]
name = "digest"
version = "0.10.7"
@ -149,6 +210,7 @@ checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
dependencies = [
"block-buffer",
"crypto-common",
"subtle",
]
[[package]]
@ -298,8 +360,10 @@ dependencies = [
"itertools",
"log",
"mimalloc",
"pem",
"percent-encoding",
"pin-project",
"pkcs8",
"pyo3",
"pyo3-log",
"rustls-pemfile",
@ -349,6 +413,15 @@ version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024"
[[package]]
name = "hmac"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e"
dependencies = [
"digest",
]
[[package]]
name = "http"
version = "1.1.0"
@ -446,6 +519,16 @@ version = "2.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b248f5224d1d606005e02c97f5aa4e88eeb230488bcc03bc9ca4d7991399f2b5"
[[package]]
name = "inout"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5"
dependencies = [
"block-padding",
"generic-array",
]
[[package]]
name = "itertools"
version = "0.13.0"
@ -576,6 +659,26 @@ dependencies = [
"windows-targets",
]
[[package]]
name = "pbkdf2"
version = "0.12.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2"
dependencies = [
"digest",
"hmac",
]
[[package]]
name = "pem"
version = "3.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e459365e590736a54c3fa561947c84837534b8e9af6fc5bf781307e82658fae"
dependencies = [
"base64",
"serde",
]
[[package]]
name = "percent-encoding"
version = "2.3.1"
@ -614,6 +717,33 @@ version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
[[package]]
name = "pkcs5"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e847e2c91a18bfa887dd028ec33f2fe6f25db77db3619024764914affe8b69a6"
dependencies = [
"aes",
"cbc",
"der",
"pbkdf2",
"scrypt",
"sha2",
"spki",
]
[[package]]
name = "pkcs8"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7"
dependencies = [
"der",
"pkcs5",
"rand_core",
"spki",
]
[[package]]
name = "portable-atomic"
version = "1.7.0"
@ -833,12 +963,52 @@ dependencies = [
"untrusted",
]
[[package]]
name = "salsa20"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97a22f5af31f73a954c10289c93e8a50cc23d971e80ee446f1f6f7137a088213"
dependencies = [
"cipher",
]
[[package]]
name = "scopeguard"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
name = "scrypt"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0516a385866c09368f0b5bcd1caff3366aace790fcd46e2bb032697bb172fd1f"
dependencies = [
"pbkdf2",
"salsa20",
"sha2",
]
[[package]]
name = "serde"
version = "1.0.208"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cff085d2cb684faa248efb494c39b68e522822ac0de72ccf08109abde717cfb2"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.208"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24008e81ff7613ed8e5ba0cfaf24e2c2f1e5b8a0495711e44fcd4882fca62bcf"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "sha1"
version = "0.10.6"
@ -850,6 +1020,17 @@ dependencies = [
"digest",
]
[[package]]
name = "sha2"
version = "0.10.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8"
dependencies = [
"cfg-if",
"cpufeatures",
"digest",
]
[[package]]
name = "signal-hook-registry"
version = "1.4.2"
@ -890,6 +1071,16 @@ version = "0.9.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
[[package]]
name = "spki"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d"
dependencies = [
"base64ct",
"der",
]
[[package]]
name = "subtle"
version = "2.6.1"

View file

@ -39,8 +39,10 @@ hyper = { version = "=1.4", features = ["http1", "http2", "server"] }
hyper-util = { version = "=0.1", features = ["server-auto", "tokio"] }
itertools = "0.13"
log = "0.4"
pem = "=3.0"
percent-encoding = "=2.3"
pin-project = "1.1"
pkcs8 = { version = "=0.10", features = ["encryption", "pkcs5"] }
pyo3 = { version = "=0.22", features = ["anyhow", "extension-module", "generate-import-lib"] }
pyo3-log = { version = "=0.11", git = "https://github.com/vorner/pyo3-log", rev = "b62a250533" }
rustls-pemfile = "2.1"

View file

@ -176,9 +176,11 @@ Options:
(disabled)]
--access-log-fmt TEXT Access log format [env var:
GRANIAN_LOG_ACCESS_FMT]
--ssl-keyfile FILE SSL key file [env var: GRANIAN_SSL_KEYFILE]
--ssl-certificate FILE SSL certificate file [env var:
GRANIAN_SSL_CERTIFICATE]
--ssl-keyfile FILE SSL key file [env var: GRANIAN_SSL_KEYFILE]
--ssl-keyfile-password TEXT SSL key password [env var:
GRANIAN_SSL_KEYFILE_PASSWORD]
--url-path-prefix TEXT URL path prefix the app is mounted on [env
var: GRANIAN_URL_PATH_PREFIX]
--respawn-failed-workers / --no-respawn-failed-workers

View file

@ -168,16 +168,17 @@ def option(*param_decls: str, cls: Optional[Type[click.Option]] = None, **attrs:
)
@option('--access-log/--no-access-log', 'log_access_enabled', default=False, help='Enable access log')
@option('--access-log-fmt', 'log_access_fmt', help='Access log format')
@option(
'--ssl-keyfile',
type=click.Path(exists=True, file_okay=True, dir_okay=False, readable=True, path_type=pathlib.Path),
help='SSL key file',
)
@option(
'--ssl-certificate',
type=click.Path(exists=True, file_okay=True, dir_okay=False, readable=True, path_type=pathlib.Path),
help='SSL certificate file',
)
@option(
'--ssl-keyfile',
type=click.Path(exists=True, file_okay=True, dir_okay=False, readable=True, path_type=pathlib.Path),
help='SSL key file',
)
@option('--ssl-keyfile-password', help='SSL key password')
@option('--url-path-prefix', help='URL path prefix the app is mounted on')
@option(
'--respawn-failed-workers/--no-respawn-failed-workers',
@ -270,8 +271,9 @@ def cli(
log_access_fmt: Optional[str],
log_level: LogLevels,
log_config: Optional[pathlib.Path],
ssl_keyfile: Optional[pathlib.Path],
ssl_certificate: Optional[pathlib.Path],
ssl_keyfile: Optional[pathlib.Path],
ssl_keyfile_password: Optional[str],
url_path_prefix: Optional[str],
respawn_failed_workers: bool,
respawn_interval: float,
@ -329,6 +331,7 @@ def cli(
log_access_format=log_access_fmt,
ssl_cert=ssl_certificate,
ssl_key=ssl_keyfile,
ssl_key_password=ssl_keyfile_password,
url_path_prefix=url_path_prefix,
respawn_failed_workers=respawn_failed_workers,
respawn_interval=respawn_interval,

View file

@ -92,6 +92,7 @@ class Granian:
log_access_format: Optional[str] = None,
ssl_cert: Optional[Path] = None,
ssl_key: Optional[Path] = None,
ssl_key_password: Optional[str] = None,
url_path_prefix: Optional[str] = None,
respawn_failed_workers: bool = False,
respawn_interval: float = 3.5,
@ -147,7 +148,7 @@ class Granian:
configure_logging(self.log_level, self.log_config, self.log_enabled)
self.build_ssl_context(ssl_cert, ssl_key)
self.build_ssl_context(ssl_cert, ssl_key, ssl_key_password)
self._shd = None
self._sfd = None
self.procs: List[Worker] = []
@ -159,17 +160,17 @@ class Granian:
self.lifetime_signal = False
self.pid = None
def build_ssl_context(self, cert: Optional[Path], key: Optional[Path]):
def build_ssl_context(self, cert: Optional[Path], key: Optional[Path], password: Optional[str]):
if not (cert and key):
self.ssl_ctx = (False, None, None)
return
ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
ctx.load_cert_chain(cert, key, None)
ctx.load_cert_chain(cert, key, password)
# with cert.open("rb") as f:
# cert_contents = f.read()
# with key.open("rb") as f:
# key_contents = f.read()
self.ssl_ctx = (True, str(cert.resolve()), str(key.resolve()))
self.ssl_ctx = (True, str(cert.resolve()), str(key.resolve()), password)
@staticmethod
def _spawn_asgi_worker(
@ -191,7 +192,7 @@ class Granian:
log_level: LogLevels,
log_config: Dict[str, Any],
log_access_fmt: Optional[str],
ssl_ctx: Tuple[bool, Optional[str], Optional[str]],
ssl_ctx: Tuple[bool, Optional[str], Optional[str], Optional[str]],
scope_opts: Dict[str, Any],
):
from granian._loops import loops, set_loop_signals
@ -246,7 +247,7 @@ class Granian:
log_level: LogLevels,
log_config: Dict[str, Any],
log_access_fmt: Optional[str],
ssl_ctx: Tuple[bool, Optional[str], Optional[str]],
ssl_ctx: Tuple[bool, Optional[str], Optional[str], Optional[str]],
scope_opts: Dict[str, Any],
):
from granian._loops import loops, set_loop_signals
@ -308,7 +309,7 @@ class Granian:
log_level: LogLevels,
log_config: Dict[str, Any],
log_access_fmt: Optional[str],
ssl_ctx: Tuple[bool, Optional[str], Optional[str]],
ssl_ctx: Tuple[bool, Optional[str], Optional[str], Optional[str]],
scope_opts: Dict[str, Any],
):
from granian._loops import loops, set_loop_signals
@ -374,7 +375,7 @@ class Granian:
log_level: LogLevels,
log_config: Dict[str, Any],
log_access_fmt: Optional[str],
ssl_ctx: Tuple[bool, Optional[str], Optional[str]],
ssl_ctx: Tuple[bool, Optional[str], Optional[str], Optional[str]],
scope_opts: Dict[str, Any],
):
from granian._loops import loops, set_loop_signals

View file

@ -46,7 +46,8 @@ impl ASGIWorker {
opt_enabled=true,
ssl_enabled=false,
ssl_cert=None,
ssl_key=None
ssl_key=None,
ssl_key_password=None
)
)]
fn new(
@ -64,6 +65,7 @@ impl ASGIWorker {
ssl_enabled: bool,
ssl_cert: Option<&str>,
ssl_key: Option<&str>,
ssl_key_password: Option<&str>,
) -> PyResult<Self> {
Ok(Self {
config: WorkerConfig::new(
@ -80,6 +82,7 @@ impl ASGIWorker {
ssl_enabled,
ssl_cert,
ssl_key,
ssl_key_password,
),
})
}

View file

@ -46,7 +46,8 @@ impl RSGIWorker {
opt_enabled=true,
ssl_enabled=false,
ssl_cert=None,
ssl_key=None
ssl_key=None,
ssl_key_password=None
)
)]
fn new(
@ -64,6 +65,7 @@ impl RSGIWorker {
ssl_enabled: bool,
ssl_cert: Option<&str>,
ssl_key: Option<&str>,
ssl_key_password: Option<&str>,
) -> PyResult<Self> {
Ok(Self {
config: WorkerConfig::new(
@ -80,6 +82,7 @@ impl RSGIWorker {
ssl_enabled,
ssl_cert,
ssl_key,
ssl_key_password,
),
})
}

View file

@ -1,3 +1,4 @@
use anyhow::{anyhow, Result};
use std::{
fs, io,
iter::Iterator,
@ -18,7 +19,7 @@ use tls_listener::{
pub(crate) fn tls_listener(
config: Arc<ServerConfig>,
tcp: TcpListener,
) -> anyhow::Result<(TlsListener<tokio::net::TcpListener, TlsAcceptor>, SocketAddr)> {
) -> Result<(TlsListener<tokio::net::TcpListener, TlsAcceptor>, SocketAddr)> {
let tcp_listener = tokio::net::TcpListener::from_std(tcp).unwrap();
let local_addr = tcp_listener.local_addr()?;
let listener = TlsListener::new(TlsAcceptor::from(config), tcp_listener);
@ -29,6 +30,26 @@ pub(crate) fn load_certs(filename: String) -> io::Result<Vec<Certificate<'static
rustls_pemfile::certs(&mut io::BufReader::new(fs::File::open(filename)?)).collect()
}
pub(crate) fn load_private_key(filename: String) -> io::Result<PrivateKey<'static>> {
rustls_pemfile::private_key(&mut io::BufReader::new(fs::File::open(filename)?)).map(std::option::Option::unwrap)
pub(crate) fn load_private_key(filename: String, password: Option<String>) -> Result<PrivateKey<'static>> {
match &password {
Some(pwd) => {
let expected_tag = "ENCRYPTED PRIVATE KEY";
let content = fs::read(filename)?;
let sections = pem::parse_many(content)?;
let mut iter = sections
.into_iter()
.filter(|v| v.tag() == expected_tag)
.map(|v| v.contents().to_vec());
let key = pkcs8::EncryptedPrivateKeyInfo::try_from(
iter.next().map_or_else(|| Err(anyhow!("Invalid key")), Ok)?.as_slice(),
)
.map_err(|_| anyhow!("Invalid key"))?
.decrypt(pwd)
.map_err(|_| anyhow!("Cannot decrypt key"))?;
Ok(PrivateKey::Pkcs8(key.to_bytes().to_vec().into()))
}
None => rustls_pemfile::private_key(&mut io::BufReader::new(fs::File::open(filename)?))
.map_err(std::convert::Into::into)
.map(std::option::Option::unwrap),
}
}

View file

@ -68,6 +68,7 @@ pub(crate) struct WorkerConfig {
pub ssl_enabled: bool,
ssl_cert: Option<String>,
ssl_key: Option<String>,
ssl_key_password: Option<String>,
}
impl WorkerConfig {
@ -85,6 +86,7 @@ impl WorkerConfig {
ssl_enabled: bool,
ssl_cert: Option<&str>,
ssl_key: Option<&str>,
ssl_key_password: Option<&str>,
) -> Self {
Self {
id,
@ -100,6 +102,7 @@ impl WorkerConfig {
ssl_enabled,
ssl_cert: ssl_cert.map(std::convert::Into::into),
ssl_key: ssl_key.map(std::convert::Into::into),
ssl_key_password: ssl_key_password.map(std::convert::Into::into),
}
}
@ -122,7 +125,7 @@ impl WorkerConfig {
.with_no_client_auth()
.with_single_cert(
tls_load_certs(self.ssl_cert.clone().unwrap()).unwrap(),
tls_load_pkey(self.ssl_key.clone().unwrap()).unwrap(),
tls_load_pkey(self.ssl_key.clone().unwrap(), self.ssl_key_password.clone()).unwrap(),
)
.unwrap();
cfg.alpn_protocols = match &self.http_mode[..] {

View file

@ -32,7 +32,8 @@ impl WSGIWorker {
http2_opts=None,
ssl_enabled=false,
ssl_cert=None,
ssl_key=None
ssl_key=None,
ssl_key_password=None
)
)]
fn new(
@ -48,6 +49,7 @@ impl WSGIWorker {
ssl_enabled: bool,
ssl_cert: Option<&str>,
ssl_key: Option<&str>,
ssl_key_password: Option<&str>,
) -> PyResult<Self> {
Ok(Self {
config: WorkerConfig::new(
@ -64,6 +66,7 @@ impl WSGIWorker {
ssl_enabled,
ssl_cert,
ssl_key,
ssl_key_password,
),
})
}

View file

@ -26,8 +26,13 @@ async def _server(interface, port, threading_mode, tls=False):
'loop_opt': bool(os.getenv('LOOP_OPT')),
}
if tls:
kwargs['ssl_cert'] = certs_path / 'cert.pem'
kwargs['ssl_key'] = certs_path / 'key.pem'
if tls == 'private':
kwargs['ssl_cert'] = certs_path / 'pcert.pem'
kwargs['ssl_key'] = certs_path / 'pkey.pem'
kwargs['ssl_key_password'] = 'foobar' # noqa: S105
else:
kwargs['ssl_cert'] = certs_path / 'cert.pem'
kwargs['ssl_key'] = certs_path / 'key.pem'
succeeded, spawn_failures = False, 0
while spawn_failures < 3:

21
tests/fixtures/tls/pcert.pem vendored Normal file
View file

@ -0,0 +1,21 @@
-----BEGIN CERTIFICATE-----
MIIDazCCAlOgAwIBAgIUSpxGABYdWSF5SL/UUy+nSERO82IwDQYJKoZIhvcNAQEL
BQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM
GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0yNDA4MzExNjM5NTNaFw0zNDA4
MjkxNjM5NTNaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEw
HwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwggEiMA0GCSqGSIb3DQEB
AQUAA4IBDwAwggEKAoIBAQCVu4Y8vVxnRL1Ve+v/WdnkoETwxOeXxS8eFhknh5Td
kk9pUQLVvOgl/xoHEXbqtllqoAJBvMipFN/2drzfPQdES+TFg89Z5z4fzVMK5LDU
0D5NTrPIDooHKBD9VF8CKoWQE0bqsyE7VezL550lconK5gQBT1qVNmy3uJDvTdN5
+SAxa1VlIXnfzl56HN4lEjGwwMIku2hTIE/g2Np4mY+753TP3+lJEMJV0MC7Jm+p
0E/K30eea6vHRekfanASFGUTQuYTi1MRHOhG6mSgBcLrjhkesibCVhIcojwouqw0
rHISnTwdVnNgsHYYZwXzyG90B43PiQhiOtQvCzVOyrWnAgMBAAGjUzBRMB0GA1Ud
DgQWBBQcVutKZhcuW/oKo/nuHnkiz7h2rjAfBgNVHSMEGDAWgBQcVutKZhcuW/oK
o/nuHnkiz7h2rjAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQBt
xs3aozUrPXcJFZpobAWyAwLO2TRLcD2wwyNLDeeCDoKD2c+SQY7FMv7nQZ7C2Jpl
DE5h7PedD1OAHLXTbsNpXMCAEnHewMO0UCsHl6Er/8f+KuCcBWVcm1Xbv5ksF5sA
KrbDpNsQVRwDqhwVJxvZ0YfHnr4+UheimT0KzI9zZKsl7FaIrAUm6Gxh/6EYOhAG
sC5OTtK5OYk/uafHEoScxM+hx+QabEsCb48QgU3pmlGSrp3g1zxEE30spIk7ek3g
1TP/Gf+RcdzDYODZHZGms0aKDXluVn+bIarUoZ4LJ6wtJC+s7yQWBjrzWUJ4jDkY
Dcgo5IV2CEL7Npy4dI40
-----END CERTIFICATE-----

30
tests/fixtures/tls/pkey.pem vendored Normal file
View file

@ -0,0 +1,30 @@
-----BEGIN ENCRYPTED PRIVATE KEY-----
MIIFNTBfBgkqhkiG9w0BBQ0wUjAxBgkqhkiG9w0BBQwwJAQQzFiK6MdnEqUsy4S4
8LvT6gICCAAwDAYIKoZIhvcNAgkFADAdBglghkgBZQMEAQIEEN08/ZZm2q2lPfUp
veXsugYEggTQpUYfRbjFeHJz40loVAnLbhDfrs7MSOvSf0op97l5o92y2w9fIaqo
Gu8zOOqhv83uM+9OE84MOymh0eBClSMgDYE2futF8pnD354V4uZp0uXHhA1qQPUZ
Wm/dGZNNXvdlrOqMA0ga130hkttAyNnta19hY+IhBGPvT5qUclyR2fIc4fghTOhQ
8kzujJGjwZEEEzFdERV4X+1wHvCjv3uqUBVfQBd6qLswAqW1I6GBHwCn1/xXpKtT
zxnY+/jDCsRQld6wZriW/QmDvOKtmPGVYJDHIyivNpgPMoOgll8V2OXCP9YR+XfV
Y0y8ih39AkGYqCQFlbE8ysArA4gg1qQPOtGSo6RiFPoGKQGd8SAT3Jil2L3Dg3GG
S+6hEaycyBiMha3FUkQHu5CGkQokSk2ghfAwlJ4oSEik3a5dcN5yZGR6I1JY2sLJ
xqHHao21TDkM9KnomKbThYRixb1IW28KqXjI9Uk/2oFfsuUuzBPg1oGx6ptSUAJr
fjOGtRGxmQ0zUzk589c3I7KZ+QapyfdPr/XKo4bnmRH6Sn8j/SE/Zxqmg7Qg5AMI
irEcXmUGvE6D4OR5V9hEzgUSxxz7Cq+J42KY5MCLWJdXD5FYWiD8tTgXoo58r2e2
AgzlzJAiMfPWkCFh8ZJapZoFF0EdPADWV9GiEGfjbO1aP0jZTfTb4BbVOVDzTZVz
j631Bf1PROSbRMxMwsG0ZIAykvKVyeAOsckFwMP4c30TW1OIfjz50FDLRg0c+SOz
eFhCeof/y9AP7YyAweN/xYcdje2z5wK0AGFJk9HGkbgD37Jzb5KY3kYaAEQpd6jG
uMjcJpGyxn/CiSk1IY/zmXr9eapt4UybqTWHcP5LyZf9Y/2LlEjmXJIo6L2rZPkw
gFwlqQWnqEdyLeUI+Z9k3dyd6dk/LilIxsdHnYlDbwdDpS3kSy1m2LCKcS+j3Ook
Jg1XttjePjG+6bQsN4ClgMlQl/ZokEyIQhSlI4lpkB5xo4Gx2Cia917eGfyAUu5X
ScMeQYLIepZgRYcvz1Bz4/xq3q8FR524j4I4Uk22nP7mPvk2Sm+WrHri/qKmVYll
6O1/sHrN29F8udDezSjKiSxSiHuu6Gybw9ZVYcAuBVz054bsOy4kV1M8EFinGTDm
FJnZSDvhQ+hYY9xkuj6DXWV/kXF80wCEMaXo4UKi5v8JotGS7aHWbXTczwMLakeI
1y+3bV5UIQEU8yzzsAPPTDz2ncWnSxMPM33mk77uBKOz4SctGK1/jJ14LwxfEVIZ
lnyA/Zgy5bZuCUny3vi+vUGTl0jgGD5TE2KosHe74XldmTQ/BXhb7/vuDXKcKyAe
XYw+CQsREZ4ESQaD3oBkxLQPTtnrcvlGe5oJyKgio3bcDYdk6WBLAOJj122IY9V8
ilREZhIon7aFWt7zBcl4I73DEPiwLFV9KDFqYm3viCZztWq3QyLBbBuKinksEg3j
6jQSA8DLUbDV2ENcXpngmU7U71pnUn2QRTmfxMNhd5KdDnMCgyOgJPSI3/WSbIHt
TO9MaOIujEX5YdEIrZCMRU9nFt9chhUhj9+z4vAGjKUj22+QJrjCqz9X3m9B3cFL
02/f5QWJRkblG9vgIAGuruJT89ZOg6v6Z8q7yO6lHLTBPnkiantSdFg=
-----END ENCRYPTED PRIVATE KEY-----

View file

@ -47,3 +47,13 @@ async def test_rsgi_ws_scope(rsgi_server, threading_mode):
data = json.loads(res)
assert data['scheme'] == 'https'
@pytest.mark.asyncio
async def test_tls_encrypted_key(rsgi_server):
async with rsgi_server('workers', tls='priv') as port:
res = httpx.get(f'https://localhost:{port}/info?test=true', verify=False)
assert res.status_code == 200
data = res.json()
assert data['scheme'] == 'https'