Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ pip install certmonitor

**Using uv:**
```sh
uv pip install certmonitor
uv add certmonitor
```

For instructions on installing from source for development, please see the [Development Guide](docs/development.md).
Expand Down
10 changes: 6 additions & 4 deletions certmonitor/validators/expiration.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# validators/expiration.py

import datetime
from typing import Any, Dict

from .base import BaseCertValidator

Expand All @@ -13,9 +14,9 @@ class ExpirationValidator(BaseCertValidator):
name (str): The name of the validator.
"""

name = "expiration"
name: str = "expiration"

def validate(self, cert, host, port) -> dict:
def validate(self, cert: Dict[str, Any], host: str, port: int) -> Dict[str, Any]:
"""
Validates the expiration date of the provided SSL certificate.

Expand Down Expand Up @@ -55,10 +56,11 @@ def validate(self, cert, host, port) -> dict:
}
```
"""
now = datetime.datetime.utcnow()
# Use timezone.utc for Python 3.8+ compatibility
now = datetime.datetime.now(datetime.timezone.utc)
not_after = datetime.datetime.strptime(
cert["cert_info"]["notAfter"], "%b %d %H:%M:%S %Y GMT"
)
).replace(tzinfo=datetime.timezone.utc)

is_valid = now < not_after
days_to_expiry = (not_after - now).days
Expand Down
57 changes: 57 additions & 0 deletions tests/test_validators/test_expiration.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,63 @@ def test_expired_cert(sample_cert):
assert not result["is_valid"]


def test_expiration_validator_certificate_too_long():
"""Test expiration validator with certificate valid for more than 398 days to cover line 72."""
from datetime import datetime, timedelta

from certmonitor.validators.expiration import ExpirationValidator

validator = ExpirationValidator()

# Create mock certificate data with expiry more than 398 days in future
future_date = datetime.now() + timedelta(days=500) # 500 days in future

mock_cert_data = {
"cert_info": {"notAfter": future_date.strftime("%b %d %H:%M:%S %Y GMT")}
}

result = validator.validate(mock_cert_data, "example.com", 443)

# Should have a warning about certificate being valid for too long
assert isinstance(result, dict)
assert "warnings" in result
warnings = result["warnings"]
assert any(
"valid for more than industry standard" in warning for warning in warnings
)


def test_expiration_long_validity_certificate():
"""Test certificate with validity > 398 days to ensure comprehensive coverage."""
from datetime import timezone

validator = ExpirationValidator()

# Create a certificate with exactly 400 days validity to ensure we hit the > 398 condition
now = datetime.now(timezone.utc)
not_before = now - timedelta(days=1)
not_after = now + timedelta(days=400) # Exactly 400 days from now

cert_data = {
"cert_info": {
"notBefore": not_before.strftime("%b %d %H:%M:%S %Y GMT"),
"notAfter": not_after.strftime("%b %d %H:%M:%S %Y GMT"),
}
}

result = validator.validate(cert_data, "example.com", 443)

# Verify we get the warning about industry standard
assert result["is_valid"] is True
assert "warnings" in result

# Check that we have a warning about industry standard
industry_warning_found = any(
"more than industry standard" in warning for warning in result["warnings"]
)
assert industry_warning_found


if __name__ == "__main__":
import pytest

Expand Down
Loading