Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Broad strokes of how this package might look #1

Merged
merged 13 commits into from
Oct 25, 2019
38 changes: 38 additions & 0 deletions autograph_utils/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import binascii
import re
from abc import ABC
from datetime import datetime

import cryptography
import ecdsa.util
Expand Down Expand Up @@ -71,6 +72,24 @@ def detail(self):
return f"Could not parse certificate: {self.extra}"


class CertificateNotYetValid(BadCertificate):
def __init__(self, not_before):
self.not_before = not_before

@property
def detail(self):
return f"Certificate is not valid until {self.not_before}"


class CertificateExpired(BadCertificate):
def __init__(self, not_after):
self.not_after = not_after

@property
def detail(self):
return f"Certificate expired in the past on {self.not_after}"
glasserc marked this conversation as resolved.
Show resolved Hide resolved


class BadSignature(Exception):
detail = "Unknown signature problem"

Expand Down Expand Up @@ -167,7 +186,18 @@ async def verify_x5u(self, url):
for pem in pems
]

now = _now()
for cert in certs:
if cert.not_valid_before > cert.not_valid_after:
raise BadCertificate(
f"not_before ({cert.not_valid_before}) after "
f"not_after ({cert.not_valid_after})"
)
if now < cert.not_valid_before:
raise CertificateNotYetValid(cert.not_valid_before)
if now > cert.not_valid_after:
raise CertificateExpired(cert.not_valid_after)

print(cert)

root_hash = certs[-1].fingerprint(SHA256())
Expand Down Expand Up @@ -222,3 +252,11 @@ def decode_mozilla_hash(s):
b'\x4c\x35\xb1\xc3')
"""
return bytes.fromhex(s.replace(":", " "))


def _now(self):
"""Mockable function to get "now".

:returns: naive datetime representing a UTC timestamp
"""
return datetime.utcnow()
43 changes: 39 additions & 4 deletions tests/test_autograph_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@

"""Tests for `autograph_utils` package."""

import datetime
import os.path
from unittest import mock

import aiohttp
import pytest
Expand Down Expand Up @@ -65,6 +67,15 @@ def cache():
return MemoryCache()


@pytest.fixture
def now_fixed():
with mock.patch("autograph_utils._now") as m:
# A common static time used in a lot of tests.
m.return_value = datetime.datetime(2019, 10, 23, 16, 16)
# Yield the mock so someone can change the time if they want
yield m


@pytest.fixture
async def aiohttp_session(loop):
async with aiohttp.ClientSession() as s:
Expand All @@ -75,28 +86,52 @@ def test_decode_mozilla_hash():
assert decode_mozilla_hash("4C:35:B1:C3") == b"\x4c\x35\xb1\xc3"


async def test_verify_x5u(aiohttp_session, mock_with_x5u, cache):
async def test_verify_x5u(aiohttp_session, mock_with_x5u, cache, now_fixed):
s = SignatureVerifier(aiohttp_session, cache, DEV_ROOT_HASH)
await s.verify_x5u(FAKE_CERT_URL)


async def test_verify_signature(aiohttp_session, mock_with_x5u, cache):
async def test_verify_signature(aiohttp_session, mock_with_x5u, cache, now_fixed):
s = SignatureVerifier(aiohttp_session, cache, DEV_ROOT_HASH)
await s.verify(SIGNED_DATA, SAMPLE_SIGNATURE, FAKE_CERT_URL)


async def test_verify_signature_bad_base64(aiohttp_session, mock_with_x5u, cache):
async def test_verify_signature_bad_base64(
aiohttp_session, mock_with_x5u, cache, now_fixed
):
s = SignatureVerifier(aiohttp_session, cache, DEV_ROOT_HASH)
with pytest.raises(autograph_utils.WrongSignatureSize):
await s.verify(SIGNED_DATA, SAMPLE_SIGNATURE[:-3], FAKE_CERT_URL)


async def test_verify_signature_bad_numbers(aiohttp_session, mock_with_x5u, cache):
async def test_verify_signature_bad_numbers(
aiohttp_session, mock_with_x5u, cache, now_fixed
):
s = SignatureVerifier(aiohttp_session, cache, DEV_ROOT_HASH)
with pytest.raises(autograph_utils.WrongSignatureSize):
await s.verify(SIGNED_DATA, SAMPLE_SIGNATURE[:-4], FAKE_CERT_URL)


async def test_verify_x5u_expired(aiohttp_session, mock_with_x5u, cache, now_fixed):
now_fixed.return_value = datetime.datetime(2022, 10, 23, 16, 16, 16)
s = SignatureVerifier(aiohttp_session, cache, DEV_ROOT_HASH)
with pytest.raises(autograph_utils.CertificateExpired) as excinfo:
await s.verify(SIGNED_DATA, SAMPLE_SIGNATURE, FAKE_CERT_URL)

assert (
excinfo.value.detail == "Certificate expired in the past on 2021-07-05 21:57:15"
)


async def test_verify_x5u_too_soon(aiohttp_session, mock_with_x5u, cache, now_fixed):
now_fixed.return_value = datetime.datetime(2010, 10, 23, 16, 16, 16)
s = SignatureVerifier(aiohttp_session, cache, DEV_ROOT_HASH)
with pytest.raises(autograph_utils.CertificateNotYetValid) as excinfo:
await s.verify(SIGNED_DATA, SAMPLE_SIGNATURE, FAKE_CERT_URL)

assert excinfo.value.detail == "Certificate is not valid until 2016-07-06 21:57:15"


def test_command_line_interface():
"""Test the CLI."""
runner = CliRunner()
Expand Down