Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
d578ea4
[feature] Added support for importing ECDSA certificates #118
stktyagi Jan 11, 2026
c2f3be8
[feature] Included the P-521 curve in choices #118
stktyagi Jan 11, 2026
86a6b9f
[Improvements] Handling of unsupported keys and message clarity #118
stktyagi Jan 11, 2026
678566d
[Update] Remove SECP224R1 (224-bit) curve support #118
stktyagi Jan 11, 2026
cf82aca
[Improvement] Added explicit validation for imported EC certificates …
stktyagi Jan 11, 2026
c6e4de0
[Test] Added test to check whole ecdsa lifecycle #118
stktyagi Jan 11, 2026
f74fa1d
[Improvement] Add curve-appropriate hash algorithms to test different…
stktyagi Jan 11, 2026
00b6542
[Improvements] Added additional assertions for completeness #118
stktyagi Jan 11, 2026
1a5604f
[tests] Added test to verify certificates with added implementation #118
stktyagi Jan 14, 2026
7b4217a
[fix] Fixed Aasertion comparing private_key with certificate variable…
stktyagi Jan 14, 2026
4519ac2
[fix] Fixed incomplete digest normalization pattern #118
stktyagi Jan 14, 2026
e3eaa43
[refactor] Added ECDSA support for CAs and Certificates #118
stktyagi Jan 16, 2026
94b0793
[migrations] Add migrations #118
stktyagi Jan 16, 2026
54316bd
[refactor] Simplified clean() to previous better implementation #118
stktyagi Jan 16, 2026
8b56b13
[fix] Remove 512-bit RSA support #118
stktyagi Jan 17, 2026
0b99dde
Merge branch 'master' into issues/118-import-edcsa-existing-ca-KeyError
stktyagi Jan 30, 2026
70fea92
[Improvements] Merge multiple migration files into one #118
stktyagi Jan 30, 2026
bf2d7ba
[docs] Updated docs to mention ECDSA and RSA support #118
stktyagi Jan 30, 2026
818a53f
[docs] Updated key length choices #118
stktyagi Jan 30, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 71 additions & 7 deletions django_x509/base/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,23 @@
from .. import settings as app_settings

KEY_LENGTH_CHOICES = (
("256", "256"),
("384", "384"),
("521", "521"),
("512", "512"),
("1024", "1024"),
("2048", "2048"),
("4096", "4096"),
)

KEY_TYPE_CHOICES = (
("rsa", "RSA"),
("ec", "Elliptic Curve"),
)

RSA_KEY_LENGTHS = ("512", "1024", "2048", "4096")
EC_KEY_LENGTHS = ("256", "384", "521")

DIGEST_CHOICES = (
("sha1", "SHA1"),
("sha224", "SHA224"),
Expand Down Expand Up @@ -148,6 +159,9 @@ class BaseX509(models.Model):
blank=True,
help_text=_("Passphrase for the private key, if present"),
)
key_type = models.CharField(
_("key type"), max_length=3, choices=KEY_TYPE_CHOICES, default="rsa"
)

class Meta:
abstract = True
Expand Down Expand Up @@ -178,6 +192,20 @@ def clean(self):
if self.serial_number:
self._validate_serial_number()
self._verify_extension_format()
if self.key_type == "rsa" and self.key_length in EC_KEY_LENGTHS:
raise ValidationError(
{
"key_length": _(
"Selected length is only valid for Elliptic Curve keys. "
"RSA keys must use 512, 1024, 2048, or 4096 bits."
)
}
)

if self.key_type == "ec" and self.key_length in RSA_KEY_LENGTHS:
raise ValidationError(
{"key_length": _("Selected length is not valid for Elliptic Curve.")}
)

def save(self, *args, **kwargs):
if self._state.adding and not self.certificate and not self.private_key:
Expand Down Expand Up @@ -280,10 +308,25 @@ def _generate(self):
for attr in ["x509", "pkey"]:
if attr in self.__dict__:
del self.__dict__[attr]
key = rsa.generate_private_key(
public_exponent=65537,
key_size=int(self.key_length),
)
if self.key_type == "rsa":
key = rsa.generate_private_key(
public_exponent=65537,
key_size=int(self.key_length),
)
elif self.key_type == "ec":
curves = {
"256": ec.SECP256R1(),
"384": ec.SECP384R1(),
"521": ec.SECP521R1(),
}
curve = curves.get(self.key_length)
if not curve:
raise ValidationError(
_("Unsupported EC key length: %s") % self.key_length
)
key = ec.generate_private_key(curve)
else:
raise ValidationError(_("Unsupported key type: %s") % self.key_type)
if hasattr(self, "ca"):
signing_key = self.ca.pkey
issuer_name = self.ca.x509.subject
Expand All @@ -307,7 +350,11 @@ def _generate(self):
"sha384": hashes.SHA384,
"sha512": hashes.SHA512,
}
digest_name = self.digest.lower().replace("withrsaencryption", "")
digest_name = (
self.digest.lower()
.replace("withrsaencryption", "")
.replace("ecdsa-with-", "")
)
digest_alg = HASH_MAP.get(digest_name, hashes.SHA256)()
cert = builder.sign(signing_key, digest_alg)
self.certificate = cert.public_bytes(serialization.Encoding.PEM).decode("utf-8")
Expand All @@ -318,7 +365,11 @@ def _generate(self):
)
self.private_key = key.private_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PrivateFormat.TraditionalOpenSSL,
format=(
serialization.PrivateFormat.PKCS8
if self.key_type == "ec"
else serialization.PrivateFormat.TraditionalOpenSSL
),
encryption_algorithm=encryption,
).decode("utf-8")

Expand Down Expand Up @@ -352,6 +403,20 @@ def _import(self):
imports existing x509 certificates
"""
cert = self.x509
public_key = cert.public_key()
if isinstance(public_key, rsa.RSAPublicKey):
self.key_type = "rsa"
self.key_length = str(public_key.key_size)
elif isinstance(public_key, ec.EllipticCurvePublicKey):
self.key_type = "ec"
self.key_length = str(public_key.curve.key_size)
else:
raise ValidationError(
_(
"Unsupported key type in certificate. "
"Only RSA and EC keys are supported."
)
)
# when importing an end entity certificate
if hasattr(self, "ca"):
self._verify_ca()
Expand All @@ -366,7 +431,6 @@ def _import(self):
email = str(attrs[0].value) if attrs else ""

self.email = email
self.key_length = str(cert.public_key().key_size)
self.digest = cert.signature_hash_algorithm.name.lower()
self.validity_start = cert.not_valid_before_utc
self.validity_end = cert.not_valid_after_utc
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# Generated by Django 5.2.9 on 2026-01-11 10:50

import django_x509.base.models
from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
("django_x509", "0009_alter_ca_digest_alter_ca_key_length_and_more"),
]

operations = [
migrations.AddField(
model_name="ca",
name="key_type",
field=models.CharField(
choices=[("rsa", "RSA"), ("ec", "Elliptic Curve")],
default="rsa",
max_length=3,
verbose_name="key type",
),
),
migrations.AddField(
model_name="cert",
name="key_type",
field=models.CharField(
choices=[("rsa", "RSA"), ("ec", "Elliptic Curve")],
default="rsa",
max_length=3,
verbose_name="key type",
),
),
migrations.AlterField(
model_name="ca",
name="key_length",
field=models.CharField(
choices=[
("224", "224"),
("256", "256"),
("384", "384"),
("512", "512"),
("1024", "1024"),
("2048", "2048"),
("4096", "4096"),
],
default=django_x509.base.models.default_key_length,
help_text="bits",
max_length=6,
verbose_name="key length",
),
),
migrations.AlterField(
model_name="cert",
name="key_length",
field=models.CharField(
choices=[
("224", "224"),
("256", "256"),
("384", "384"),
("512", "512"),
("1024", "1024"),
("2048", "2048"),
("4096", "4096"),
],
default=django_x509.base.models.default_key_length,
help_text="bits",
max_length=6,
verbose_name="key length",
),
),
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# Generated by Django 5.2.9 on 2026-01-11 11:10

import django_x509.base.models
from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
("django_x509", "0010_ca_key_type_cert_key_type_alter_ca_key_length_and_more"),
]

operations = [
migrations.AlterField(
model_name="ca",
name="key_length",
field=models.CharField(
choices=[
("224", "224"),
("256", "256"),
("384", "384"),
("521", "521"),
("512", "512"),
("1024", "1024"),
("2048", "2048"),
("4096", "4096"),
],
default=django_x509.base.models.default_key_length,
help_text="bits",
max_length=6,
verbose_name="key length",
),
),
migrations.AlterField(
model_name="cert",
name="key_length",
field=models.CharField(
choices=[
("224", "224"),
("256", "256"),
("384", "384"),
("521", "521"),
("512", "512"),
("1024", "1024"),
("2048", "2048"),
("4096", "4096"),
],
default=django_x509.base.models.default_key_length,
help_text="bits",
max_length=6,
verbose_name="key length",
),
),
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# Generated by Django 5.2.9 on 2026-01-11 11:47

import django_x509.base.models
from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
("django_x509", "0011_alter_ca_key_length_alter_cert_key_length"),
]

operations = [
migrations.AlterField(
model_name="ca",
name="key_length",
field=models.CharField(
choices=[
("256", "256"),
("384", "384"),
("521", "521"),
("512", "512"),
("1024", "1024"),
("2048", "2048"),
("4096", "4096"),
],
default=django_x509.base.models.default_key_length,
help_text="bits",
max_length=6,
verbose_name="key length",
),
),
migrations.AlterField(
model_name="cert",
name="key_length",
field=models.CharField(
choices=[
("256", "256"),
("384", "384"),
("521", "521"),
("512", "512"),
("1024", "1024"),
("2048", "2048"),
("4096", "4096"),
],
default=django_x509.base.models.default_key_length,
help_text="bits",
max_length=6,
verbose_name="key length",
),
),
]
Loading
Loading