Skip to content

Commit e7b7e74

Browse files
committed
PkiIndexer
Provide a PkiManager that can read a directory or zip of already issued certificates and keys. Changes from carthage-contrib: * issue_credentials returns the chain not just the end entity cert for consistency with other PkiManagers. * trust_store returns all self signed certificates * trust_root_for is removed. * If we cannot chain back to a self-signed cert, stop where the chain ends, do not error out.
1 parent b89c5ed commit e7b7e74

File tree

1 file changed

+46
-22
lines changed

1 file changed

+46
-22
lines changed

carthage_base/pki_indexer.py

Lines changed: 46 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,41 @@
1+
# Copyright (C) 2018, 2019, 2020, 2021, 2022, 2023, 2025, Hadron Industries, Inc.
2+
# Carthage is free software; you can redistribute it and/or modify
3+
# it under the terms of the GNU Lesser General Public License version 3
4+
# as published by the Free Software Foundation. It is distributed
5+
# WITHOUT ANY WARRANTY; without even the implied warranty of
6+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the file
7+
# LICENSE for details.
18
import carthage
9+
import carthage.pki
10+
import carthage.pki_utils as pki_utils
211
from carthage import *
3-
from carthage_aws import *
412
from pathlib import Path
513
import cryptography
614
import logging
715
import dataclasses
816
import re
917
import pathlib
18+
from cryptography.hazmat.primitives import serialization
19+
20+
def serialize_certificate(cert):
21+
s = cert.public_bytes(encoding=serialization.Encoding.PEM).decode('ascii')
22+
return s
23+
24+
def serialize_key(key):
25+
s = key.private_bytes(encoding=serialization.Encoding.PEM,
26+
format=serialization.PrivateFormat.PKCS8,
27+
encryption_algorithm=serialization.NoEncryption())
28+
return s.decode('ascii')
1029

1130
@dataclasses.dataclass()
1231
class PkiReference:
1332
source: object
1433
ref: object
1534
obj: object
1635

17-
@inject_autokwargs(ainjector=AsyncInjector)
18-
class PkiIndexer(AsyncInjectable):
36+
class PkiIndexer(carthage.pki.PkiManager):
1937

20-
def __init__(self, *, baseurl=None, **kwargs):
38+
def __init__(self, **kwargs):
2139
super().__init__(**kwargs)
2240
self.keys_by_nums = dict()
2341
self.csrs_by_nums = dict()
@@ -26,10 +44,10 @@ def __init__(self, *, baseurl=None, **kwargs):
2644
self.certs_by_nums = dict()
2745
self.certs_by_dnsname = dict()
2846
self.certs_by_subject = dict()
47+
self.hostname_tags = {}
2948

3049
async def async_ready(self):
3150
await self.index_certificates()
32-
# await self.relocate_certificates()
3351
await super().async_ready()
3452

3553
async def index_certificates(self):
@@ -38,8 +56,20 @@ async def index_certificates(self):
3856
async def relocate_certificates(self):
3957
return
4058

41-
def _process_key(self, k, key):
59+
async def issue_credentials(self, hostname:str, tag:str):
60+
tags = self.hostname_tags.setdefault(hostname, set())
61+
if tag in tags:
62+
raise ValueError(f'Duplicate tag {tag} for {hostname}')
63+
tags.add(tag)
64+
certs = await self.certificates_for(hostname)
65+
key = await self.key_for(hostname)
66+
return serialize_key(key), pki_utils.x509_annotate('\n'.join(serialize_certificate(cert) for cert in certs))
4267

68+
async def trust_store(self):
69+
trust_roots = {ref.ref:ref.obj for ref in self.certs_by_nums.values() if ref.obj.subject == ref.obj.issuer}
70+
return await self.ainjector(carthage.pki.SimpleTrustStore, 'pki_indexer', trust_roots)
71+
72+
def _process_key(self, k, key):
4373
pk = key.public_key()
4474
nums = pk.public_numbers()
4575
self.keys_by_nums[nums] = PkiReference(source=self, ref=k, obj=key)
@@ -81,13 +111,14 @@ def _iterate_pem(self, s):
81111
m = re.search(b'^-----BEGIN ([A-Z][A-Z ]+[A-Z])-----[\r\n]+', s, re.MULTILINE)
82112
if m is None: return
83113
m2 = re.search(b'^-----END ' + m.group(1) + b'-----[\r\n]+', s[m.span()[1]:], re.MULTILINE)
84-
assert m2, breakpoint()
114+
assert m2
85115
r = s[m.span()[0] : m2.span()[1] + m.span()[1]]
86116
s = s[m2.span()[1] + m.span()[1]:]
87117
yield r
88118

89119
def _process_bytes(self, k, ss):
90-
120+
if isinstance(ss, str):
121+
ss = ss.encode('utf-8')
91122
ret = []
92123

93124
try:
@@ -98,7 +129,6 @@ def _process_bytes(self, k, ss):
98129
pass
99130

100131
for s in self._iterate_pem(ss):
101-
102132
if (b'-----BEGIN PRIVATE KEY-----' in s) \
103133
or (b'-----BEGIN RSA PRIVATE KEY-----' in s):
104134
key = cryptography.hazmat.primitives.serialization.load_pem_private_key(s, password=None)
@@ -117,15 +147,7 @@ def _process_bytes(self, k, ss):
117147

118148
return ret
119149

120-
async def trust_store(self):
121-
return None
122-
123-
async def trustroot_for(self, hn):
124150

125-
certs = await self.certificates_for(hn, include_trustroot=True)
126-
cur = certs[-1]
127-
assert (cur.issuer == cur.subject)
128-
return cur
129151

130152
async def validate_for(self, manifest):
131153

@@ -179,7 +201,7 @@ async def certificates_for(self, fqdn, include_trustroot=False, required=True):
179201
if wildcard in self.certs_by_dnsname:
180202
options = self.certs_by_dnsname[wildcard]
181203
if len(options) > 1:
182-
breakpoint() # raise ValueError(options)
204+
raise ValueError(options)
183205
cur = options[0].obj
184206

185207
if cur is None:
@@ -194,10 +216,13 @@ async def certificates_for(self, fqdn, include_trustroot=False, required=True):
194216
ret.append(cur)
195217
break
196218
ret.append(cur)
197-
cur = self.certs_by_subject[cur.issuer].obj
219+
try:
220+
cur = self.certs_by_subject[cur.issuer].obj
221+
except KeyError:
222+
break
198223
return ret
199224

200-
@inject_autokwargs(ainjector=AsyncInjector, config_layout=ConfigLayout)
225+
201226
class PkiIndexerZip(PkiIndexer):
202227

203228
def __init__(self, path, **kwargs):
@@ -218,7 +243,7 @@ async def index_certificates(self):
218243

219244
await self.validate()
220245

221-
@inject_autokwargs(ainjector=AsyncInjector, config_layout=ConfigLayout)
246+
222247
class PkiIndexerPath(PkiIndexer):
223248

224249
def __init__(self, path, **kwargs):
@@ -239,7 +264,6 @@ async def index_certificates(self):
239264

240265
await self.validate()
241266

242-
@inject_autokwargs(ainjector=AsyncInjector, config_layout=ConfigLayout)
243267
class PkiIndexerMulti(PkiIndexer):
244268

245269
def __init__(self, *args, **kwargs):

0 commit comments

Comments
 (0)