diff --git a/ca/django_ca/migration_helpers.py b/ca/django_ca/migration_helpers.py
index a93f58394..4d46d1581 100644
--- a/ca/django_ca/migration_helpers.py
+++ b/ca/django_ca/migration_helpers.py
@@ -17,14 +17,29 @@
that they are tested properly.
"""
+import shlex
import typing
import warnings
+from collections.abc import Iterator
from typing import Optional
from cryptography import x509
from cryptography.x509.oid import AuthorityInformationAccessOID, ExtensionOID
-from django_ca.utils import format_general_name, parse_general_name, split_str
+from django_ca.utils import format_general_name, parse_general_name
+
+
+def split_str(val: str, sep: str) -> Iterator[str]:
+ """Split a character on the given set of characters.
+
+ This function was originally in ``django_ca.utils`` but has since been deprecated/removed. We keep a copy
+ here so that the function keeps working.
+ """
+ lex = shlex.shlex(val, posix=True)
+ lex.commenters = ""
+ lex.whitespace = sep
+ lex.whitespace_split = True
+ yield from lex
class Migration0040Helper:
diff --git a/ca/django_ca/tests/test_utils.py b/ca/django_ca/tests/test_utils.py
index 2f781b667..c4c104409 100644
--- a/ca/django_ca/tests/test_utils.py
+++ b/ca/django_ca/tests/test_utils.py
@@ -17,7 +17,6 @@
import itertools
import os
import typing
-import unittest
from collections.abc import Iterable
from datetime import datetime, timedelta, timezone as tz
from pathlib import Path
@@ -37,7 +36,7 @@
from django_ca import utils
from django_ca.conf import model_settings
-from django_ca.tests.base.constants import CRYPTOGRAPHY_VERSION
+from django_ca.tests.base.assertions import assert_removed_in_230
from django_ca.tests.base.doctest import doctest_module
from django_ca.tests.base.utils import cn, country, dns
from django_ca.typehints import SerializedObjectIdentifier
@@ -98,7 +97,8 @@ def test_parse_serialized_name_attributes(
serialized: list[SerializedObjectIdentifier] = [
{"oid": attr[0].dotted_string, "value": attr[1]} for attr in attributes
]
- assert parse_serialized_name_attributes(serialized) == expected
+ with assert_removed_in_230():
+ assert parse_serialized_name_attributes(serialized) == expected
class GeneratePrivateKeyTestCase(TestCase):
@@ -147,19 +147,23 @@ class SerializeName(TestCase):
def test_name(self) -> None:
"""Test passing a standard Name."""
- assert serialize_name(x509.Name([cn("example.com")])) == [{"oid": "2.5.4.3", "value": "example.com"}]
- assert serialize_name(x509.Name([country("AT"), cn("example.com")])) == [
- {"oid": "2.5.4.6", "value": "AT"},
- {"oid": "2.5.4.3", "value": "example.com"},
- ]
+ with assert_removed_in_230():
+ assert serialize_name(x509.Name([cn("example.com")])) == [
+ {"oid": "2.5.4.3", "value": "example.com"}
+ ]
+ with assert_removed_in_230():
+ assert serialize_name(x509.Name([country("AT"), cn("example.com")])) == [
+ {"oid": "2.5.4.6", "value": "AT"},
+ {"oid": "2.5.4.3", "value": "example.com"},
+ ]
- @unittest.skipIf(CRYPTOGRAPHY_VERSION < (37, 0), "cg<36 does not yet have bytes.")
def test_bytes(self) -> None:
"""Test names with byte values - probably never happens."""
name = x509.Name(
[x509.NameAttribute(NameOID.X500_UNIQUE_IDENTIFIER, b"example.com", _type=_ASN1Type.BitString)]
)
- assert serialize_name(name) == [{"oid": "2.5.4.45", "value": "65:78:61:6D:70:6C:65:2E:63:6F:6D"}]
+ with assert_removed_in_230():
+ assert serialize_name(name) == [{"oid": "2.5.4.45", "value": "65:78:61:6D:70:6C:65:2E:63:6F:6D"}]
@pytest.mark.parametrize(
@@ -354,13 +358,20 @@ def test_str(self) -> None:
("CN", "example.com"),
("emailAddress", "user@example.com"),
]
- assert x509_name(subject) == self.name
+ with assert_removed_in_230():
+ assert x509_name(subject) == self.name
def test_multiple_other(self) -> None:
"""Test multiple other tokens (only OUs work)."""
- with pytest.raises(ValueError, match='^Subject contains multiple "countryName" fields$'):
+ with (
+ assert_removed_in_230(),
+ pytest.raises(ValueError, match='^Subject contains multiple "countryName" fields$'),
+ ):
x509_name([("C", "AT"), ("C", "DE")])
- with pytest.raises(ValueError, match='^Subject contains multiple "commonName" fields$'):
+ with (
+ assert_removed_in_230(),
+ pytest.raises(ValueError, match='^Subject contains multiple "commonName" fields$'),
+ ):
x509_name([("CN", "AT"), ("CN", "FOO")])
diff --git a/ca/django_ca/tests/utils/test_name_for_display.py b/ca/django_ca/tests/utils/test_name_for_display.py
new file mode 100644
index 000000000..a52b094ba
--- /dev/null
+++ b/ca/django_ca/tests/utils/test_name_for_display.py
@@ -0,0 +1,48 @@
+# This file is part of django-ca (https://github.com/mathiasertl/django-ca).
+#
+# django-ca is free software: you can redistribute it and/or modify it under the terms of the GNU General
+# Public License as published by the Free Software Foundation, either version 3 of the License, or (at your
+# option) any later version.
+#
+# django-ca is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the
+# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License along with django-ca. If not, see
+# .
+
+"""Test ``django_ca.utils.name_for_display``."""
+
+from cryptography import x509
+from cryptography.x509 import NameOID
+from cryptography.x509.name import _ASN1Type
+
+import pytest
+
+from django_ca.tests.base.utils import cn
+from django_ca.utils import name_for_display
+
+
+@pytest.mark.parametrize(
+ ("value", "expected"),
+ (
+ (x509.Name([cn("example.com")]), [("commonName (CN)", "example.com")]),
+ (
+ x509.Name([cn("example.net"), cn("example.com")]),
+ [("commonName (CN)", "example.net"), ("commonName (CN)", "example.com")],
+ ),
+ (
+ x509.Name(
+ [
+ x509.NameAttribute(
+ oid=NameOID.X500_UNIQUE_IDENTIFIER, value=b"example.com", _type=_ASN1Type.BitString
+ )
+ ]
+ ),
+ [("x500UniqueIdentifier", "65:78:61:6D:70:6C:65:2E:63:6F:6D")],
+ ),
+ ),
+)
+def test_name_for_display(value: x509.Name, expected: list[tuple[str, str]]) -> None:
+ """Test the function."""
+ assert name_for_display(value) == expected
diff --git a/ca/django_ca/tests/utils/test_parse_name_x509.py b/ca/django_ca/tests/utils/test_parse_name_x509.py
index dd6353dc3..4f1f5fc49 100644
--- a/ca/django_ca/tests/utils/test_parse_name_x509.py
+++ b/ca/django_ca/tests/utils/test_parse_name_x509.py
@@ -18,6 +18,7 @@
import pytest
+from django_ca.tests.base.assertions import assert_removed_in_230
from django_ca.utils import parse_name_x509
@@ -152,10 +153,11 @@
)
def test_parse_name_x509(value: str, expected: list[tuple[x509.ObjectIdentifier, str]]) -> None:
"""Some basic tests."""
- assert parse_name_x509(value) == tuple(x509.NameAttribute(oid, value) for oid, value in expected)
+ with assert_removed_in_230():
+ assert parse_name_x509(value) == tuple(x509.NameAttribute(oid, value) for oid, value in expected)
def test_unknown() -> None:
"""Test unknown field."""
- with pytest.raises(ValueError, match=r"^Unknown x509 name field: ABC$"):
+ with assert_removed_in_230(), pytest.raises(ValueError, match=r"^Unknown x509 name field: ABC$"):
parse_name_x509("/ABC=example.com")
diff --git a/ca/django_ca/tests/utils/test_split_str.py b/ca/django_ca/tests/utils/test_split_str.py
index ed4dcecc0..eaf7c09da 100644
--- a/ca/django_ca/tests/utils/test_split_str.py
+++ b/ca/django_ca/tests/utils/test_split_str.py
@@ -15,6 +15,7 @@
import pytest
+from django_ca.tests.base.assertions import assert_removed_in_230
from django_ca.utils import split_str
@@ -85,7 +86,8 @@
)
def test_basic(value: str, seperator: str, expected: list[str]) -> None:
"""Some basic split_str() test cases."""
- assert list(split_str(value, seperator)) == expected
+ with assert_removed_in_230():
+ assert list(split_str(value, seperator)) == expected
@pytest.mark.parametrize(
@@ -100,5 +102,5 @@ def test_basic(value: str, seperator: str, expected: list[str]) -> None:
)
def test_quotation_errors(value: str, match: str) -> None:
"""Test quoting."""
- with pytest.raises(ValueError, match=match):
+ with assert_removed_in_230(), pytest.raises(ValueError, match=match):
list(split_str(value, "/"))
diff --git a/ca/django_ca/typehints.py b/ca/django_ca/typehints.py
index 0caa2d2e2..39e10483a 100644
--- a/ca/django_ca/typehints.py
+++ b/ca/django_ca/typehints.py
@@ -73,7 +73,7 @@ class OCSPKeyBackendDict(TypedDict):
hashes.SHA3_512,
]
-ParsableName = Union[str, Iterable[tuple[str, str]]]
+ParsableName = Union[str, Iterable[tuple[str, str]]] # TODO: remove?
ParsableKeyType = Literal["RSA", "DSA", "EC", "Ed25519", "Ed448"]
ParsableSubject = Union[
diff --git a/ca/django_ca/utils.py b/ca/django_ca/utils.py
index be5e8ef65..3dc7424df 100644
--- a/ca/django_ca/utils.py
+++ b/ca/django_ca/utils.py
@@ -39,19 +39,14 @@
from django_ca import constants
from django_ca.conf import model_settings
from django_ca.constants import MULTIPLE_OIDS, NAME_OID_DISPLAY_NAMES
+from django_ca.deprecation import RemovedInDjangoCA230Warning, deprecate_function
from django_ca.pydantic.validators import (
dns_validator,
email_validator,
is_power_two_validator,
url_validator,
)
-from django_ca.typehints import (
- AllowedHashTypes,
- ParsableGeneralName,
- ParsableKeyType,
- ParsableName,
- SerializedName,
-)
+from django_ca.typehints import AllowedHashTypes, ParsableGeneralName, ParsableKeyType, SerializedName
#: Regular expression to match general names.
GENERAL_NAME_RE = re.compile("^(email|URI|IP|DNS|RID|dirName|otherName):(.*)", flags=re.I)
@@ -130,23 +125,18 @@ def _serialize_name_attribute_value(name_attribute: x509.NameAttribute) -> str:
return name_attribute.value
+@deprecate_function(RemovedInDjangoCA230Warning)
def serialize_name(name: Union[x509.Name, x509.RelativeDistinguishedName]) -> SerializedName:
"""Serialize a :py:class:`~cg:cryptography.x509.Name`.
+ .. deprecated:: 2.2.0
+
+ This function is deprecated and will be removed in ``django-ca==2.3.0``. Use Pydantic models instead.
+
The value also accepts a :py:class:`~cg:cryptography.x509.RelativeDistinguishedName`.
The returned value is a list of tuples, each consisting of two strings. If an attribute contains
``bytes``, it is converted using :py:func:`~django_ca.utils.bytes_to_hex`.
-
- Examples::
-
- >>> serialize_name(x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, 'example.com')]))
- [{'oid': '2.5.4.3', 'value': 'example.com'}]
- >>> serialize_name(x509.RelativeDistinguishedName([
- ... x509.NameAttribute(NameOID.COUNTRY_NAME, 'AT'),
- ... x509.NameAttribute(NameOID.COMMON_NAME, 'example.com'),
- ... ]))
- [{'oid': '2.5.4.6', 'value': 'AT'}, {'oid': '2.5.4.3', 'value': 'example.com'}]
"""
return [{"oid": attr.oid.dotted_string, "value": _serialize_name_attribute_value(attr)} for attr in name]
@@ -169,16 +159,18 @@ def name_for_display(name: Union[x509.Name, x509.RelativeDistinguishedName]) ->
]
+@deprecate_function(RemovedInDjangoCA230Warning)
def parse_serialized_name_attributes(name: SerializedName) -> list[x509.NameAttribute]:
"""Parse a serialized list of name attributes into a list of NameAttributes.
+ .. deprecated:: 2.2.0
+
+ This function is deprecated and will be removed in ``django-ca==2.3.0``. Use Pydantic models instead.
+
This function takes care of parsing hex-encoded byte values name attributes that are known to use bytes
(currently only :py:attr:`NameOID.X500_UNIQUE_IDENTIFIER
`).
- >>> parse_serialized_name_attributes([{"oid": "2.5.4.3", "value": "example.com"}])
- [, value='example.com')>]
-
This function is more or less the inverse of :py:func:`~django_ca.utils.serialize_name`, except that it
returns a list of :py:class:`~cg:cryptography.x509.NameAttribute` instances (``serialize_name()`` takes a
:py:class:`~cg:cryptography.x509.Name` or :py:class:`~cg:cryptography.x509.RelativeDistinguishedName`)
@@ -308,10 +300,14 @@ def sanitize_serial(value: str) -> str:
return serial
-# @deprecate_function(RemovedInDjangoCA200Warning)
-def parse_name_x509(name: ParsableName) -> tuple[x509.NameAttribute, ...]:
+@deprecate_function(RemovedInDjangoCA230Warning)
+def parse_name_x509(name: Union[str, Iterable[tuple[str, str]]]) -> tuple[x509.NameAttribute, ...]:
"""Parses a subject string as used in OpenSSLs command line utilities.
+ .. deprecated:: 2.2.0
+
+ This function is deprecated and will be removed in ``django-ca==2.3.0``. Use Pydantic models instead.
+
.. versionchanged:: 1.20.0
This function no longer returns the subject in pseudo-sorted order.
@@ -320,16 +316,6 @@ def parse_name_x509(name: ParsableName) -> tuple[x509.NameAttribute, ...]:
``/C=AT/L=Vienna/CN=example.com/emailAddress=user@example.com``. The function does its best to be lenient
on deviations from the format, object identifiers are case-insensitive, whitespace at the start and end is
stripped and the subject does not have to start with a slash (``/``).
-
- >>> parse_name_x509([("CN", "example.com")])
- (, value='example.com')>,)
- >>> parse_name_x509(
- ... [("c", "AT"), ("l", "Vienna"), ("o", "quoting/works"), ("CN", "example.com")]
- ... ) # doctest: +NORMALIZE_WHITESPACE
- (, value='AT')>,
- , value='Vienna')>,
- , value='quoting/works')>,
- , value='example.com')>)
"""
if isinstance(name, str):
# TYPE NOTE: mypy detects t.split() as Tuple[str, ...] and does not recognize the maxsplit parameter
@@ -344,12 +330,13 @@ def parse_name_x509(name: ParsableName) -> tuple[x509.NameAttribute, ...]:
return tuple(x509.NameAttribute(oid, value) for oid, value in items)
-# @deprecate_function(RemovedInDjangoCA200Warning)
-def x509_name(name: ParsableName) -> x509.Name:
+@deprecate_function(RemovedInDjangoCA230Warning)
+def x509_name(name: Union[str, Iterable[tuple[str, str]]]) -> x509.Name:
"""Parses a string or iterable of two-tuples into a :py:class:`x509.Name `.
- >>> x509_name([('C', 'AT'), ('CN', 'example.com')])
-
+ .. deprecated:: 2.2.0
+
+ This function is deprecated and will be removed in ``django-ca==2.3.0``. Use Pydantic models instead.
"""
return check_name(x509.Name(parse_name_x509(name)))
@@ -938,6 +925,7 @@ def read_file(path: str) -> bytes:
stream.close()
+@deprecate_function(RemovedInDjangoCA230Warning)
def split_str(val: str, sep: str) -> Iterator[str]:
"""Split a character on the given set of characters."""
lex = shlex.shlex(val, posix=True)
diff --git a/devscripts/recreate_fixtures_helpers.py b/devscripts/recreate_fixtures_helpers.py
index 6d784d016..d4e888505 100644
--- a/devscripts/recreate_fixtures_helpers.py
+++ b/devscripts/recreate_fixtures_helpers.py
@@ -51,6 +51,7 @@
)
from django_ca.models import Certificate, CertificateAuthority
from django_ca.profiles import profiles
+from django_ca.pydantic import NameModel
from django_ca.pydantic.extensions import (
EXTENSION_MODELS,
AuthorityInformationAccessModel,
@@ -60,7 +61,7 @@
)
from django_ca.tests.base.typehints import CertFixtureData, OcspFixtureData
from django_ca.typehints import ParsableKeyType
-from django_ca.utils import bytes_to_hex, parse_serialized_name_attributes, serialize_name
+from django_ca.utils import bytes_to_hex
DEFAULT_KEY_SIZE = 2048 # Size for private keys
TIMEFORMAT = "%Y-%m-%d %H:%M:%S"
@@ -174,7 +175,7 @@ def _copy_cert(dest: Path, cert: Certificate, data: CertFixtureData, key_path: P
with open(dest / data["pub_filename"], "wb") as stream:
stream.write(cert.pub.der)
- data["subject"] = serialize_name(cert.subject)
+ data["subject"] = [{"oid": attr.oid.dotted_string, "value": attr.value} for attr in cert.subject]
data["parsed_cert"] = cert
_update_cert_data(cert, data)
@@ -196,7 +197,7 @@ def _update_contrib(
"key_filename": False,
"csr_filename": False,
"serial": cert.serial,
- "subject": serialize_name(cert.subject),
+ "subject": [{"oid": attr.oid.dotted_string, "value": attr.value} for attr in cert.subject],
"md5": cert.get_fingerprint(hashes.MD5()),
"sha1": cert.get_fingerprint(hashes.SHA1()),
"sha256": cert.get_fingerprint(hashes.SHA256()),
@@ -295,7 +296,7 @@ def create_cas(dest: Path, now: datetime, delay: bool, data: CertFixtureData) ->
data[name]["name"],
key_backend,
key_backend_options,
- subject=x509.Name(parse_serialized_name_attributes(data[name]["subject"])),
+ subject=NameModel.model_validate(data[name]["subject"]).cryptography,
expires=datetime.now(tz=tz.utc) + data[name]["not_after"],
key_type=data[name]["key_type"],
algorithm=data[name].get("algorithm"),
@@ -322,7 +323,7 @@ def create_certs(
name = f"{ca.name}-cert"
key_path = Path(os.path.join(settings.CA_DIR, f"{name}.key"))
csr_path = Path(os.path.join(settings.CA_DIR, f"{name}.csr"))
- csr_subject = x509.Name(parse_serialized_name_attributes(data[name]["csr_subject"]))
+ csr_subject = NameModel.model_validate(data[name]["csr_subject"]).cryptography
csr = _create_csr(
key_path,
csr_path,
@@ -355,7 +356,7 @@ def create_certs(
key_path = Path(os.path.join(settings.CA_DIR, f"{name}.key"))
csr_path = Path(os.path.join(settings.CA_DIR, f"{name}.csr"))
- csr_subject = x509.Name(parse_serialized_name_attributes(data[name]["csr_subject"]))
+ csr_subject = NameModel.model_validate(data[name]["csr_subject"]).cryptography
csr = _create_csr(
key_path,
csr_path,
@@ -393,7 +394,7 @@ def create_special_certs( # noqa: PLR0915
ca = CertificateAuthority.objects.get(name=data[name]["ca"])
key_path = Path(os.path.join(settings.CA_DIR, f"{name}.key"))
csr_path = Path(os.path.join(settings.CA_DIR, f"{name}.csr"))
- csr_subject = x509.Name(parse_serialized_name_attributes(data[name]["csr_subject"]))
+ csr_subject = NameModel.model_validate(data[name]["csr_subject"]).cryptography
csr = _create_csr(key_path, csr_path, subject=csr_subject)
freeze_now = now
@@ -402,7 +403,7 @@ def create_special_certs( # noqa: PLR0915
with freeze_time(freeze_now):
no_ext_now = datetime.now(tz=tz.utc).replace(tzinfo=None)
pwd = data[ca.name].get("password")
- subject = x509.Name(parse_serialized_name_attributes(data[name]["subject"]))
+ subject = NameModel.model_validate(data[name]["subject"]).cryptography
builder = x509.CertificateBuilder()
builder = builder.not_valid_before(no_ext_now)
@@ -435,7 +436,7 @@ def create_special_certs( # noqa: PLR0915
ca = CertificateAuthority.objects.get(name=data[name]["ca"])
key_path = Path(os.path.join(settings.CA_DIR, f"{name}.key"))
csr_path = Path(os.path.join(settings.CA_DIR, f"{name}.csr"))
- csr_subject = x509.Name(parse_serialized_name_attributes(data[name]["csr_subject"]))
+ csr_subject = NameModel.model_validate(data[name]["csr_subject"]).cryptography
csr = _create_csr(key_path, csr_path, subject=csr_subject)
with freeze_time(now + data[name]["delta"]):
@@ -445,7 +446,7 @@ def create_special_certs( # noqa: PLR0915
csr=csr,
profile=profiles["webserver"],
algorithm=data[name].get("algorithm"),
- subject=x509.Name(parse_serialized_name_attributes(data[name]["subject"])),
+ subject=NameModel.model_validate(data[name]["subject"]).cryptography,
expires=data[name]["not_after"],
extensions=data[name]["extensions"].values(),
)
@@ -457,7 +458,7 @@ def create_special_certs( # noqa: PLR0915
ca = CertificateAuthority.objects.get(name=data[name]["ca"])
key_path = Path(os.path.join(settings.CA_DIR, f"{name}.key"))
csr_path = Path(os.path.join(settings.CA_DIR, f"{name}.csr"))
- csr_subject = x509.Name(parse_serialized_name_attributes(data[name]["csr_subject"]))
+ csr_subject = NameModel.model_validate(data[name]["csr_subject"]).cryptography
csr = _create_csr(key_path, csr_path, subject=csr_subject)
with freeze_time(now + data[name]["delta"]):
@@ -467,7 +468,7 @@ def create_special_certs( # noqa: PLR0915
csr=csr,
profile=profiles["webserver"],
algorithm=data[name].get("algorithm"),
- subject=x509.Name(parse_serialized_name_attributes(data[name]["subject"])),
+ subject=NameModel.model_validate(data[name]["subject"]).cryptography,
expires=data[name]["not_after"],
extensions=data[name]["extensions"].values(),
)
@@ -478,7 +479,7 @@ def create_special_certs( # noqa: PLR0915
ca = CertificateAuthority.objects.get(name=data[name]["ca"])
key_path = Path(os.path.join(settings.CA_DIR, f"{name}.key"))
csr_path = Path(os.path.join(settings.CA_DIR, f"{name}.csr"))
- csr_subject = x509.Name(parse_serialized_name_attributes(data[name]["csr_subject"]))
+ csr_subject = NameModel.model_validate(data[name]["csr_subject"]).cryptography
csr = _create_csr(key_path, csr_path, subject=csr_subject)
freeze_now = now
diff --git a/docs/source/changelog/TBR_2.2.0.rst b/docs/source/changelog/TBR_2.2.0.rst
index 516518667..03dec1684 100644
--- a/docs/source/changelog/TBR_2.2.0.rst
+++ b/docs/source/changelog/TBR_2.2.0.rst
@@ -36,3 +36,11 @@ Deprecation notices
* ``django_ca.extensions.parse_extension()`` is deprecated and will be removed in ``django-ca==2.3.0``. Use
:doc:`Pydantic models ` instead.
+* Functions related to the old OpenSSL style subject format are deprecated and will be removed in
+ ``django_ca==2.3.0``:
+
+ * ``django_ca.utils.parse_name_x509()``
+ * ``django_ca.utils.parse_serialized_name_attributes()``
+ * ``django_ca.utils.serialize_name()``
+ * ``django_ca.utils.split_str()``
+ * ``django_ca.utils.x509_name()``
diff --git a/docs/source/python/intro.rst b/docs/source/python/intro.rst
index cda575a31..b4f6fb880 100644
--- a/docs/source/python/intro.rst
+++ b/docs/source/python/intro.rst
@@ -49,7 +49,6 @@ creates a minimal CA using the file system storage backend::
... StoragesUsePrivateKeyOptions,
... )
>>> from django_ca.models import CertificateAuthority
- >>> from django_ca.utils import x509_name
>>> key_backend = key_backends["default"]
>>> key_backend_options = StoragesCreatePrivateKeyOptions(
... key_type="RSA", key_size=1024, password=None, path="ca"
@@ -80,7 +79,6 @@ Django model::
Much like with certificate authorities, creating a new certificate requires a manager method,
:py:func:`Certificate.objects.create_cert() `::
- >>> from django_ca.utils import x509_name
>>> Certificate.objects.create_cert(
... ca,
... StoragesUsePrivateKeyOptions(password=None),