diff --git a/dnstapir/key_cache.py b/dnstapir/key_cache.py
index 5cb8611..2ef68b7 100644
--- a/dnstapir/key_cache.py
+++ b/dnstapir/key_cache.py
@@ -5,8 +5,32 @@
 import redis
 from expiringdict import ExpiringDict
 from opentelemetry import trace
+from pydantic import BaseModel, Field
 
-tracer = trace.get_tracer("tapir.tracer")
+tracer = trace.get_tracer("dnstapir.tracer")
+
+
+class RedisSettings(BaseModel):
+    host: str = Field(description="Redis hostname")
+    port: int = Field(description="Redis port", default=6379)
+
+
+class KeyCacheSettings(BaseModel):
+    size: int = Field(description="Cache size", default=1000)
+    ttl: int = Field(description="Cache TTL", default=300)
+    redis: RedisSettings | None = None
+
+
+def key_cache_from_settings(settings: KeyCacheSettings):
+    memory_key_cache = MemoryKeyCache(size=settings.size, ttl=settings.ttl)
+    if settings.redis:
+        redis_client = redis.StrictRedis(host=settings.redis.host, port=settings.redis.port)
+        redis_key_cache = RedisKeyCache(redis_client=redis_client, ttl=settings.ttl)
+        return CombinedKeyCache([memory_key_cache, redis_key_cache]) if settings.size else redis_key_cache
+    elif settings.size:
+        return memory_key_cache
+    else:
+        return DummyKeyCache()
 
 
 class KeyCache:
diff --git a/dnstapir/key_resolver.py b/dnstapir/key_resolver.py
index c00904e..577b9eb 100644
--- a/dnstapir/key_resolver.py
+++ b/dnstapir/key_resolver.py
@@ -3,15 +3,19 @@
 from urllib.parse import urljoin
 
 import httpx
+from cryptography.hazmat.primitives.asymmetric.ec import EllipticCurvePublicKey
+from cryptography.hazmat.primitives.asymmetric.ed448 import Ed448PublicKey
+from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey
+from cryptography.hazmat.primitives.asymmetric.rsa import RSAPublicKey
 from cryptography.hazmat.primitives.serialization import load_pem_public_key
-from http_message_signatures import HTTPSignatureKeyResolver
 from opentelemetry import metrics, trace
 
 from .key_cache import KeyCache
 
-tracer = trace.get_tracer("tapir.tracer")
-meter = metrics.get_meter("tapir.meter")
+type PublicKey = Ed25519PublicKey | Ed448PublicKey | EllipticCurvePublicKey | RSAPublicKey
 
+tracer = trace.get_tracer("dnstapir.tracer")
+meter = metrics.get_meter("dnstapir.meter")
 
 public_key_get_counter = meter.create_counter(
     "aggregates.public_key_get_counter",
@@ -19,7 +23,20 @@
 )
 
 
-class CacheKeyResolver(HTTPSignatureKeyResolver):
+def key_resolver_from_client_database(client_database: str, key_cache: KeyCache | None = None):
+    if client_database.startswith("http://") or client_database.startswith("https://"):
+        return UrlKeyResolver(client_database_base_url=client_database, key_cache=key_cache)
+    else:
+        return FileKeyResolver(client_database_directory=client_database, key_cache=key_cache)
+
+
+class KeyResolver:
+    @abstractmethod
+    def resolve_public_key(self, key_id: str) -> PublicKey:
+        pass
+
+
+class CacheKeyResolver(KeyResolver):
     def __init__(self, key_cache: KeyCache | None):
         self.key_cache = key_cache
 
diff --git a/poetry.lock b/poetry.lock
index 83e5740..8042a9d 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -516,41 +516,6 @@ files = [
     {file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"},
 ]
 
-[[package]]
-name = "http-message-signatures"
-version = "0.5.0"
-description = "An implementation of the IETF HTTP Message Signatures draft standard"
-optional = false
-python-versions = "*"
-files = [
-    {file = "http-message-signatures-0.5.0.tar.gz", hash = "sha256:5a59de19b90dce0eaf62021ee776d6562e5a166c96e4107db36f9c01f25552a3"},
-    {file = "http_message_signatures-0.5.0-py3-none-any.whl", hash = "sha256:719933cba48943b5e148fe7bbbf520927573f72c6ca00855cb2c79fdecee2cb2"},
-]
-
-[package.dependencies]
-cryptography = ">=36.0.2"
-http-sfv = ">=0.9.3"
-
-[package.extras]
-tests = ["build", "coverage", "flake8", "mypy", "requests", "ruff", "wheel"]
-
-[[package]]
-name = "http-sfv"
-version = "0.9.9"
-description = "Parse and serialise HTTP Structured Field Values"
-optional = false
-python-versions = ">=3.8"
-files = [
-    {file = "http_sfv-0.9.9-py3-none-any.whl", hash = "sha256:5feed51c90e9a1dc797701662d044d936923cf0027255c75452b8240e33d6c82"},
-    {file = "http_sfv-0.9.9.tar.gz", hash = "sha256:e132dc9bef990832bc01824f5fa9d4efc7d0f4271e4b227db35c8ef38540c739"},
-]
-
-[package.dependencies]
-typing-extensions = "*"
-
-[package.extras]
-dev = ["black", "mypy"]
-
 [[package]]
 name = "httpcore"
 version = "1.0.6"
@@ -1504,4 +1469,4 @@ type = ["pytest-mypy"]
 [metadata]
 lock-version = "2.0"
 python-versions = "^3.12"
-content-hash = "0ad3544908af6eedf713e2987a6180255a4c665f0d8efebcd06d7e6573ce16bd"
+content-hash = "2dabe0df1cf20cd530e451205a01335ee89506d77f734a5b64d10d115a9caf16"
diff --git a/pyproject.toml b/pyproject.toml
index 7c47f3d..1695bd7 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -7,6 +7,19 @@ readme = "README.md"
 
 [tool.poetry.dependencies]
 python = "^3.12"
+botocore = "^1.35.44"
+expiringdict = "^1.2.2"
+fastapi = "^0.115.2"
+httpx = "^0.27.2"
+jsonformatter = "^0.3.2"
+opentelemetry-exporter-otlp = "^1.27.0"
+opentelemetry-instrumentation-botocore = "^0.48b0"
+opentelemetry-instrumentation-fastapi = "^0.48b0"
+opentelemetry-instrumentation-pymongo = "^0.48b0"
+pydantic = "^2.9.2"
+pymongo = "^4.10.1"
+redis = "^5.1.1"
+cryptography = "^43.0.3"
 
 [tool.poetry.group.dev.dependencies]
 pytest = "^8.2.0"
@@ -14,18 +27,6 @@ ruff = ">=0.7.0"
 pytest-ruff = "^0.4.1"
 fakeredis = "^2.25.1"
 pytest-httpx = "^0.32.0"
-http-message-signatures = "^0.5.0"
-jsonformatter = "^0.3.2"
-opentelemetry-instrumentation-fastapi = "^0.48b0"
-opentelemetry-exporter-otlp = "^1.27.0"
-opentelemetry-instrumentation-botocore = "^0.48b0"
-opentelemetry-instrumentation-pymongo = "^0.48b0"
-httpx = "^0.27.2"
-redis = "^5.1.1"
-expiringdict = "^1.2.2"
-fastapi = "^0.115.2"
-botocore = "^1.35.44"
-pymongo = "^4.10.1"
 
 [build-system]
 requires = ["poetry-core"]