Skip to content

Commit

Permalink
move elliptic curve back to general options
Browse files Browse the repository at this point in the history
  • Loading branch information
mathiasertl committed May 5, 2024
1 parent daafa7b commit 88259ca
Show file tree
Hide file tree
Showing 5 changed files with 61 additions and 11 deletions.
12 changes: 11 additions & 1 deletion ca/django_ca/key_backends/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,12 @@ class KeyBackend(
#: one of the named values.
supported_key_types: tuple[str, ...]

#: Elliptic curves supported by this backend for elliptic curve keys. This defines the choices for the
#: ``--elliptic-curve`` parameter and the `elliptic_curve` parameter in
#: :py:func:`~django_ca.key_backends.base.KeyBackend.get_create_private_key_options` is guaranteed to be
#: one of the named values if ``--key-type=EC`` is passed.
supported_elliptic_curves: tuple[str, ...]

#: Title used for the ArgumentGroup in :command:`manage.py init_ca`.
title: typing.ClassVar[str]

Expand Down Expand Up @@ -161,7 +167,11 @@ def add_use_private_key_arguments(self, group: ArgumentGroup) -> None:

@abc.abstractmethod
def get_create_private_key_options(
self, key_type: ParsableKeyType, options: dict[str, Any]
self,
key_type: ParsableKeyType,
key_size: Optional[int],
elliptic_curve: Optional[str],
options: dict[str, Any],
) -> CreatePrivateKeyOptionsTypeVar:
"""Get options to create private keys into a Pydantic model.
Expand Down
17 changes: 10 additions & 7 deletions ca/django_ca/key_backends/storages.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,8 @@
from django_ca import ca_settings, constants
from django_ca.key_backends.base import KeyBackend
from django_ca.management.actions import PasswordAction
from django_ca.management.base import add_elliptic_curve
from django_ca.pydantic.type_aliases import Base64EncodedBytes, PowerOfTwoTypeAlias
from django_ca.typehints import AllowedHashTypes, ArgumentGroup, ParsableKeyType
from django_ca.pydantic.type_aliases import Base64EncodedBytes, EllipticCurveTypeAlias, PowerOfTwoTypeAlias
from django_ca.typehints import AllowedHashTypes, ArgumentGroup, EllipticCurves, ParsableKeyType
from django_ca.utils import generate_private_key, get_cert_builder

if typing.TYPE_CHECKING:
Expand All @@ -61,7 +60,7 @@ class CreatePrivateKeyOptions(BaseModel):
password: Optional[bytes]
path: Path
key_size: Optional[Annotated[PowerOfTwoTypeAlias, Field(ge=ca_settings.CA_MIN_KEY_SIZE)]] = None
elliptic_curve: Optional[ec.EllipticCurve] = None
elliptic_curve: Optional[EllipticCurveTypeAlias] = None

@model_validator(mode="after")
def validate_key_size(self) -> "CreatePrivateKeyOptions":
Expand Down Expand Up @@ -150,6 +149,7 @@ class StoragesBackend(KeyBackend[CreatePrivateKeyOptions, StorePrivateKeyOptions
use_model = UsePrivateKeyOptions

supported_key_types: tuple[ParsableKeyType, ...] = constants.PARSABLE_KEY_TYPES
supported_elliptic_curves: tuple[EllipticCurves, ...] = tuple(constants.ELLIPTIC_CURVE_TYPES)

# Backend options
storage_alias: str
Expand Down Expand Up @@ -182,7 +182,6 @@ def _add_path_argument(self, group: ArgumentGroup) -> None:

def add_create_private_key_arguments(self, group: ArgumentGroup) -> None:
self._add_path_argument(group)
add_elliptic_curve(group, prefix=self.argparse_prefix)
group.add_argument(
f"--{self.argparse_prefix}password",
nargs="?",
Expand All @@ -209,14 +208,18 @@ def add_use_private_key_arguments(self, group: ArgumentGroup) -> None:
self._add_password_argument(group)

def get_create_private_key_options(
self, key_type: ParsableKeyType, key_size: Optional[int], options: dict[str, Any]
self,
key_type: ParsableKeyType,
key_size: Optional[int],
elliptic_curve: Optional[EllipticCurves], # type: ignore[override]
options: dict[str, Any],
) -> CreatePrivateKeyOptions:
return CreatePrivateKeyOptions(
key_type=key_type,
password=options[f"{self.options_prefix}password"],
path=options[f"{self.options_prefix}path"],
key_size=key_size,
elliptic_curve=options[f"{self.options_prefix}elliptic_curve"],
elliptic_curve=elliptic_curve,
)

def get_store_private_key_options(self, options: dict[str, Any]) -> StorePrivateKeyOptions:
Expand Down
20 changes: 19 additions & 1 deletion ca/django_ca/management/commands/init_ca.py
Original file line number Diff line number Diff line change
Expand Up @@ -141,10 +141,12 @@ def add_policy_constraints_group(self, parser: CommandParser) -> None:
def add_create_private_key_arguments(self, parser: CommandParser) -> None:
"""Add general arguments for private keys."""
key_types: set[str] = set()
elliptic_curves: set[str] = set()

# Calculate all key types supported by any configured backend.
for backend in key_backends:
key_types |= set(backend.supported_key_types)
elliptic_curves |= set(backend.supported_elliptic_curves)

parser.add_argument(
"--key-type",
Expand All @@ -153,6 +155,11 @@ def add_create_private_key_arguments(self, parser: CommandParser) -> None:
help="Key type for the private key (default: %(default)s).",
)
add_key_size(parser)
parser.add_argument(
"--elliptic-curve",
choices=sorted(elliptic_curves),
help=f"Elliptic Curve used for EC keys (default: {ca_settings.CA_DEFAULT_ELLIPTIC_CURVE.name}).",
)

# Add argument groups for backend-specific options.
for backend in key_backends:
Expand Down Expand Up @@ -269,6 +276,7 @@ def handle( # pylint: disable=too-many-locals # noqa: PLR0912,PLR0913,PLR0915
key_backend: KeyBackend[BaseModel, BaseModel, BaseModel],
key_type: ParsableKeyType,
key_size: Optional[int],
elliptic_curve: Optional[str],
algorithm: Optional[AllowedHashTypes],
# Authority Information Access extension (MUST be non-critical)
authority_information_access: Optional[x509.AuthorityInformationAccess],
Expand Down Expand Up @@ -323,9 +331,19 @@ def handle( # pylint: disable=too-many-locals # noqa: PLR0912,PLR0913,PLR0915
# Make sure that selected private key options are supported by the selected key backend
if key_type not in key_backend.supported_key_types:
raise CommandError(f"{key_type}: Key type not supported by {key_backend.alias} key backend.")
if (
key_type == "EC"
and elliptic_curve is not None
and elliptic_curve not in key_backend.supported_elliptic_curves
):
raise CommandError(
f"{elliptic_curve}: Elliptic curve not supported by {key_backend.alias} key backend."
)

try:
key_backend_options = key_backend.get_create_private_key_options(key_type, key_size, options)
key_backend_options = key_backend.get_create_private_key_options(
key_type, key_size, elliptic_curve=elliptic_curve, options=options
)
load_key_backend_options = key_backend.get_use_private_key_options(None, options)

# If there is a parent CA, test if we can use it (here) to sign certificates. The most common case
Expand Down
7 changes: 6 additions & 1 deletion ca/django_ca/tests/base/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ class DummyBackend(KeyBackend[DummyModel, DummyModel, DummyModel]): # pragma: n

# This backend only supports RSA and EC keys, but also the (invented) "STRANGE" key type.
supported_key_types = ("RSA", "EC", "STRANGE")
supported_elliptic_curves = ("sect571r1",)

def __eq__(self, other: Any) -> bool:
return isinstance(other, DummyBackend)
Expand All @@ -76,7 +77,11 @@ def add_store_private_key_arguments(self, group: ArgumentGroup) -> None:
return None

def get_create_private_key_options(
self, key_type: ParsableKeyType, options: dict[str, Any]
self,
key_type: ParsableKeyType,
key_size: Optional[int],
elliptic_curve: Optional[str],
options: dict[str, Any],
) -> DummyModel:
return DummyModel()

Expand Down
16 changes: 15 additions & 1 deletion ca/django_ca/tests/commands/test_init_ca.py
Original file line number Diff line number Diff line change
Expand Up @@ -1048,7 +1048,7 @@ def test_non_default_key_backend_with_ec_key(
"--subject-format=rfc4514",
"--key-backend=secondary",
"--key-type=EC",
"--secondary-elliptic-curve=sect571r1", # non default curve
"--elliptic-curve=sect571r1", # non default curve
)
assert ca.key_backend_alias == "secondary"

Expand All @@ -1064,6 +1064,20 @@ def test_invalid_public_key_parameters(ca_name: str) -> None:
init_ca(name=ca_name, key_type="Ed25519", algorithm=hashes.SHA256())


def test_unsupported_elliptic_curve(ca_name: str, settings: SettingsWrapper) -> None:
"""Test passing a valid elliptic curve that is not supported by the backend."""
settings.CA_KEY_BACKENDS = {
**settings.CA_KEY_BACKENDS,
"dummy": {
"BACKEND": f"{DummyBackend.__module__}.DummyBackend",
"OPTIONS": {},
},
}
with assert_command_error(r"^secp384r1: Elliptic curve not supported by dummy key backend\.$"):
# secp384r1 is not supported by secondary backend
init_ca(name=ca_name, key_type="EC", key_backend=key_backends["dummy"], elliptic_curve="secp384r1")


def test_root_ca_crl_url(ca_name: str) -> None:
"""Test that you cannot create a CA with a CRL URL."""
with (
Expand Down

0 comments on commit 88259ca

Please sign in to comment.