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.
18import carthage
9+ import carthage .pki
10+ import carthage .pki_utils as pki_utils
211from carthage import *
3- from carthage_aws import *
412from pathlib import Path
513import cryptography
614import logging
715import dataclasses
816import re
917import 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 ()
1231class 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+
201226class 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+
222247class 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 )
243267class PkiIndexerMulti (PkiIndexer ):
244268
245269 def __init__ (self , * args , ** kwargs ):
0 commit comments