Make signed certificates an object instead of a dict.

This commit is contained in:
Itamar Turner-Trauring 2021-08-26 16:55:12 -04:00
parent c3f6184960
commit c88130d8a8
3 changed files with 42 additions and 25 deletions

View File

@ -1,3 +1,5 @@
from __future__ import print_function
from datetime import ( from datetime import (
datetime, datetime,
timedelta, timedelta,
@ -188,7 +190,7 @@ def sign(ctx, name, expiry_days):
"No storage-server called '{}' exists".format(name) "No storage-server called '{}' exists".format(name)
) )
certificate_data = json.dumps(certificate, indent=4) certificate_data = json.dumps(certificate.asdict(), indent=4)
click.echo(certificate_data) click.echo(certificate_data)
if fp is not None: if fp is not None:
next_serial = 0 next_serial = 0

View File

@ -2,6 +2,9 @@
Functions and classes relating to the Grid Manager internal state Functions and classes relating to the Grid Manager internal state
""" """
from future.utils import PY2, PY3
from past.builtins import unicode
import sys import sys
import json import json
from datetime import ( from datetime import (
@ -18,6 +21,24 @@ from allmydata.util import (
import attr import attr
@attr.s
class SignedCertificate(object):
"""
A signed certificate.
"""
# A JSON-encoded certificate.
certificate = attr.ib(type=unicode)
# The signature in base32.
signature = attr.ib(type=unicode)
@classmethod
def load(cls, file_like):
return cls(**json.load(file_like))
def asdict(self):
return attr.asdict(self)
@attr.s @attr.s
class _GridManagerStorageServer(object): class _GridManagerStorageServer(object):
""" """
@ -97,10 +118,10 @@ def _load_certificates_for(config_path, name, gm_key=None):
cert_path = config_path.child('{}.cert.{}'.format(name, cert_index)) cert_path = config_path.child('{}.cert.{}'.format(name, cert_index))
certificates = [] certificates = []
while cert_path.exists(): while cert_path.exists():
container = json.load(cert_path.open('r')) container = SignedCertificate.load(cert_path.open('r'))
if gm_key is not None: if gm_key is not None:
validate_grid_manager_certificate(gm_key, container) validate_grid_manager_certificate(gm_key, container)
cert_data = json.loads(container['certificate']) cert_data = json.loads(container.certificate)
if cert_data['version'] != 1: if cert_data['version'] != 1:
raise ValueError( raise ValueError(
"Unknown certificate version '{}' in '{}'".format( "Unknown certificate version '{}' in '{}'".format(
@ -207,8 +228,7 @@ class _GridManager(object):
:param timedelta expiry: how far in the future the certificate :param timedelta expiry: how far in the future the certificate
should expire. should expire.
:returns: a dict defining the certificate (it has :returns SignedCertificate: the signed certificate.
"certificate" and "signature" keys).
""" """
try: try:
srv = self._storage_servers[name] srv = self._storage_servers[name]
@ -225,11 +245,10 @@ class _GridManager(object):
} }
cert_data = json.dumps(cert_info, separators=(',',':'), sort_keys=True).encode('utf8') cert_data = json.dumps(cert_info, separators=(',',':'), sort_keys=True).encode('utf8')
sig = ed25519.sign_data(self._private_key, cert_data) sig = ed25519.sign_data(self._private_key, cert_data)
certificate = { certificate = SignedCertificate(
u"certificate": cert_data, certificate=cert_data,
u"signature": base32.b2a(sig), signature=base32.b2a(sig),
} )
vk = ed25519.verifying_key_from_signing_key(self._private_key) vk = ed25519.verifying_key_from_signing_key(self._private_key)
ed25519.verify_signature(vk, sig, cert_data) ed25519.verify_signature(vk, sig, cert_data)
@ -343,9 +362,7 @@ def validate_grid_manager_certificate(gm_key, alleged_cert):
:param gm_key: a VerifyingKey instance, a Grid Manager's public :param gm_key: a VerifyingKey instance, a Grid Manager's public
key. key.
:param alleged_cert: dict with "certificate" and "signature" keys, where :param alleged_cert SignedCertificate: A signed certificate.
"certificate" contains a JSON-serialized certificate for a Storage
Server (comes from a Grid Manager).
:return: a dict consisting of the deserialized certificate data or :return: a dict consisting of the deserialized certificate data or
None if the signature is invalid. Note we do NOT check the None if the signature is invalid. Note we do NOT check the
@ -354,13 +371,13 @@ def validate_grid_manager_certificate(gm_key, alleged_cert):
try: try:
ed25519.verify_signature( ed25519.verify_signature(
gm_key, gm_key,
base32.a2b(alleged_cert['signature'].encode('ascii')), base32.a2b(alleged_cert.signature.encode('ascii')),
alleged_cert['certificate'].encode('ascii'), alleged_cert.certificate.encode('ascii'),
) )
except ed25519.BadSignature: except ed25519.BadSignature:
return None return None
# signature is valid; now we can load the actual data # signature is valid; now we can load the actual data
cert = json.loads(alleged_cert['certificate']) cert = json.loads(alleged_cert.certificate)
return cert return cert
@ -371,10 +388,10 @@ def create_grid_manager_verifier(keys, certs, public_key, now_fn=None, bad_cert=
(instead of just returning True/False here) so that the (instead of just returning True/False here) so that the
expiry-time can be tested on each call. expiry-time can be tested on each call.
:param list keys: 0 or more `VerifyingKey` instances :param list keys: 0 or more ``VerifyingKey`` instances
:param list certs: 1 or more Grid Manager certificates each of :param list certs: 1 or more Grid Manager certificates each of
which is a `dict` containing 'signature' and 'certificate' keys. which is a ``SignedCertificate``.
:param str public_key: the identifier of the server we expect :param str public_key: the identifier of the server we expect
certificates for. certificates for.

View File

@ -27,6 +27,7 @@ from allmydata.grid_manager import (
create_grid_manager, create_grid_manager,
parse_grid_manager_certificate, parse_grid_manager_certificate,
create_grid_manager_verifier, create_grid_manager_verifier,
SignedCertificate,
) )
from allmydata.test.strategies import ( from allmydata.test.strategies import (
base32text, base32text,
@ -165,16 +166,13 @@ class GridManagerVerifier(SyncTestCase):
cert1 = self.gm.sign("test", timedelta(seconds=3600)) cert1 = self.gm.sign("test", timedelta(seconds=3600))
self.assertNotEqual(cert0, cert1) self.assertNotEqual(cert0, cert1)
self.assertEqual( self.assertIsInstance(cert0, SignedCertificate)
set(cert0.keys()),
{"certificate", "signature"},
)
gm_key = ed25519.verifying_key_from_string(self.gm.public_identity()) gm_key = ed25519.verifying_key_from_string(self.gm.public_identity())
self.assertEqual( self.assertEqual(
ed25519.verify_signature( ed25519.verify_signature(
gm_key, gm_key,
base32.a2b(cert0["signature"]), base32.a2b(cert0.signature.encode("ascii")),
cert0["certificate"], cert0.certificate.encode("ascii"),
), ),
None None
) )
@ -432,7 +430,7 @@ class GridManagerInvalidVerifier(SyncTestCase):
An incorrect signature is rejected An incorrect signature is rejected
""" """
# make signature invalid # make signature invalid
self.cert0["signature"] = invalid_signature self.cert0.signature = invalid_signature
verify = create_grid_manager_verifier( verify = create_grid_manager_verifier(
[self.gm._public_key], [self.gm._public_key],