certapi 0.2.4__tar.gz → 0.2.7__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {certapi-0.2.4 → certapi-0.2.7}/PKG-INFO +2 -2
- {certapi-0.2.4 → certapi-0.2.7}/README.md +1 -1
- {certapi-0.2.4 → certapi-0.2.7}/setup.py +1 -1
- {certapi-0.2.4 → certapi-0.2.7}/src/certapi/Acme.py +10 -3
- {certapi-0.2.4 → certapi-0.2.7}/src/certapi/certauthority.py +14 -4
- {certapi-0.2.4 → certapi-0.2.7}/src/certapi/crypto.py +2 -0
- {certapi-0.2.4 → certapi-0.2.7}/src/certapi/db.py +40 -19
- {certapi-0.2.4 → certapi-0.2.7}/src/certapi.egg-info/PKG-INFO +2 -2
- {certapi-0.2.4 → certapi-0.2.7}/MANIFEST.in +0 -0
- {certapi-0.2.4 → certapi-0.2.7}/pyproject.toml +0 -0
- {certapi-0.2.4 → certapi-0.2.7}/setup.cfg +0 -0
- {certapi-0.2.4 → certapi-0.2.7}/src/certapi/__init__.py +0 -0
- {certapi-0.2.4 → certapi-0.2.7}/src/certapi/challenge.py +0 -0
- {certapi-0.2.4 → certapi-0.2.7}/src/certapi/cloudflare_challenge_store.py +0 -0
- {certapi-0.2.4 → certapi-0.2.7}/src/certapi/cloudflare_client.py +0 -0
- {certapi-0.2.4 → certapi-0.2.7}/src/certapi/crypto_classes.py +0 -0
- {certapi-0.2.4 → certapi-0.2.7}/src/certapi/custom_certauthority.py +0 -0
- {certapi-0.2.4 → certapi-0.2.7}/src/certapi/digitalocean_challenge_store.py +0 -0
- {certapi-0.2.4 → certapi-0.2.7}/src/certapi/digitalocean_client.py +0 -0
- {certapi-0.2.4 → certapi-0.2.7}/src/certapi/util.py +0 -0
- {certapi-0.2.4 → certapi-0.2.7}/src/certapi.egg-info/SOURCES.txt +0 -0
- {certapi-0.2.4 → certapi-0.2.7}/src/certapi.egg-info/dependency_links.txt +0 -0
- {certapi-0.2.4 → certapi-0.2.7}/src/certapi.egg-info/requires.txt +0 -0
- {certapi-0.2.4 → certapi-0.2.7}/src/certapi.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: certapi
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.7
|
|
4
4
|
Summary: Python Package for managing keys, request SSL certificates from ACME.
|
|
5
5
|
Home-page: https://github.com/mesudip/certapi
|
|
6
6
|
Author: Sudip Bhattarai
|
|
@@ -14,7 +14,7 @@ Description-Content-Type: text/markdown
|
|
|
14
14
|
# CertApi
|
|
15
15
|
|
|
16
16
|
CertApi is a Python package for requesting SSL certificates from ACME.
|
|
17
|
-
This is
|
|
17
|
+
This is to be used as a base library for building other tools, or to integrate Certificate creation feature in you app.
|
|
18
18
|
|
|
19
19
|
> ⚠️ Warning: This project is not polished for production use. Please stay tuned for the LTS `v1.0.0` release.
|
|
20
20
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# CertApi
|
|
2
2
|
|
|
3
3
|
CertApi is a Python package for requesting SSL certificates from ACME.
|
|
4
|
-
This is
|
|
4
|
+
This is to be used as a base library for building other tools, or to integrate Certificate creation feature in you app.
|
|
5
5
|
|
|
6
6
|
> ⚠️ Warning: This project is not polished for production use. Please stay tuned for the LTS `v1.0.0` release.
|
|
7
7
|
|
|
@@ -72,9 +72,12 @@ class AcmeHttpError(AcmeError, requests.HTTPError):
|
|
|
72
72
|
error["dns"] = {"error": "DNS record doesn't exist"}
|
|
73
73
|
error["hostname"] = re.findall(r"looking up [A-Z]+ for ([\w.-]+)", err_detail)[0]
|
|
74
74
|
message = error["hostname"] + " doesn't have a valid DNS record"
|
|
75
|
-
|
|
75
|
+
elif err_detail:
|
|
76
76
|
error["dns"] = {"error": err_detail}
|
|
77
77
|
message = err_detail
|
|
78
|
+
else:
|
|
79
|
+
error["dns"] = {"error": error}
|
|
80
|
+
message = err_detail
|
|
78
81
|
else:
|
|
79
82
|
validation_record = validation_record[0]
|
|
80
83
|
error["hostname"] = validation_record["hostname"]
|
|
@@ -103,9 +106,11 @@ class AcmeHttpError(AcmeError, requests.HTTPError):
|
|
|
103
106
|
+ validation_record["port"]
|
|
104
107
|
+ "] Connection Refused (Is http server running?)"
|
|
105
108
|
)
|
|
106
|
-
|
|
109
|
+
elif err_detail:
|
|
107
110
|
message = err_detail
|
|
108
|
-
|
|
111
|
+
else:
|
|
112
|
+
message = error
|
|
113
|
+
elif err_detail:
|
|
109
114
|
pattern = r'Invalid response from .*?: "(.*)"'
|
|
110
115
|
|
|
111
116
|
match = re.search(pattern, err_detail)
|
|
@@ -121,6 +126,8 @@ class AcmeHttpError(AcmeError, requests.HTTPError):
|
|
|
121
126
|
)
|
|
122
127
|
else:
|
|
123
128
|
message = err_detail
|
|
129
|
+
else:
|
|
130
|
+
message=error
|
|
124
131
|
|
|
125
132
|
if message is None:
|
|
126
133
|
if res_json.get("detail"):
|
|
@@ -145,7 +145,7 @@ class CertAuthority:
|
|
|
145
145
|
return createExistingResponse(existing, [])
|
|
146
146
|
|
|
147
147
|
|
|
148
|
-
def createExistingResponse(existing: Dict[str, Tuple[int | str, Key, Certificate | str]], issued_certs: List["IssuedCert"]):
|
|
148
|
+
def createExistingResponse(existing: Dict[str, Tuple[int | str, Key, List[Certificate] | str]], issued_certs: List["IssuedCert"]):
|
|
149
149
|
certs = []
|
|
150
150
|
certMap = {}
|
|
151
151
|
|
|
@@ -153,7 +153,13 @@ def createExistingResponse(existing: Dict[str, Tuple[int | str, Key, Certificate
|
|
|
153
153
|
if id in certMap:
|
|
154
154
|
certMap[id][0].append(h)
|
|
155
155
|
else:
|
|
156
|
-
|
|
156
|
+
if isinstance(cert, str):
|
|
157
|
+
cert_pem = cert
|
|
158
|
+
elif isinstance(cert, list):
|
|
159
|
+
cert_pem = certs_to_pem(cert).decode("utf-8")
|
|
160
|
+
else:
|
|
161
|
+
cert_pem = cert_to_pem(cert).decode("utf-8")
|
|
162
|
+
|
|
157
163
|
certMap[id] = (
|
|
158
164
|
[h],
|
|
159
165
|
key.to_pem().decode("utf-8"),
|
|
@@ -189,15 +195,19 @@ class CertificateResponse:
|
|
|
189
195
|
|
|
190
196
|
|
|
191
197
|
class IssuedCert:
|
|
192
|
-
def __init__(self, key: str | Key, cert: str | Certificate, domains: [str]):
|
|
198
|
+
def __init__(self, key: str | Key, cert: str | Certificate | List[Certificate], domains: [str]):
|
|
193
199
|
if isinstance(key, Key):
|
|
194
200
|
key = key.to_pem().decode("utf-8")
|
|
195
201
|
elif isinstance(key, bytes):
|
|
196
202
|
key = key.decode("utf-8")
|
|
197
|
-
|
|
203
|
+
|
|
204
|
+
if isinstance(cert, list):
|
|
205
|
+
cert = certs_to_pem(cert).decode("utf-8")
|
|
206
|
+
elif isinstance(cert, Certificate):
|
|
198
207
|
cert = cert_to_pem(cert).decode("utf-8")
|
|
199
208
|
elif isinstance(cert, bytes):
|
|
200
209
|
cert = cert.decode("utf-8")
|
|
210
|
+
|
|
201
211
|
self.privateKey = key
|
|
202
212
|
self.certificate = cert
|
|
203
213
|
self.domains = domains
|
|
@@ -115,6 +115,8 @@ def cert_to_der(cert):
|
|
|
115
115
|
def cert_to_pem(cert):
|
|
116
116
|
return cert.public_bytes(serialization.Encoding.PEM)
|
|
117
117
|
|
|
118
|
+
def certs_to_pem(certs: List[Certificate]) -> bytes:
|
|
119
|
+
return b"".join([cert_to_pem(cert) for cert in certs])
|
|
118
120
|
|
|
119
121
|
def cert_from_der(data: bytes) -> Certificate:
|
|
120
122
|
return x509.load_der_x509_certificate(data)
|
|
@@ -21,11 +21,11 @@ class KeyStore(ABC):
|
|
|
21
21
|
pass
|
|
22
22
|
|
|
23
23
|
@abstractmethod
|
|
24
|
-
def save_cert(self, private_key_id: int, cert: Certificate|str, domains: List[str], name: str = None) -> int:
|
|
24
|
+
def save_cert(self, private_key_id: int, cert: Certificate|str | List[Certificate] , domains: List[str], name: str = None) -> int:
|
|
25
25
|
pass
|
|
26
26
|
|
|
27
27
|
@abstractmethod
|
|
28
|
-
def get_cert(self, domain: str) -> None | Tuple[int | str, Key, Certificate]:
|
|
28
|
+
def get_cert(self, domain: str) -> None | Tuple[int | str, Key, Certificate | List[Certificate]]:
|
|
29
29
|
pass
|
|
30
30
|
|
|
31
31
|
|
|
@@ -91,12 +91,20 @@ class SqliteKeyStore(KeyStore):
|
|
|
91
91
|
self.save_key(key, name)
|
|
92
92
|
return key
|
|
93
93
|
|
|
94
|
-
def save_cert(self, private_key_id: int, cert: Certificate|str, domains: List[str], name: str = None) -> int:
|
|
94
|
+
def save_cert(self, private_key_id: int, cert: Certificate|str | List[Certificate], domains: List[str], name: str = None) -> int:
|
|
95
95
|
conn = self._get_db_connection()
|
|
96
96
|
cur = conn.cursor()
|
|
97
|
+
|
|
98
|
+
if isinstance(cert, list):
|
|
99
|
+
cert_data = certs_to_pem(cert)
|
|
100
|
+
elif isinstance(cert, str):
|
|
101
|
+
cert_data = cert.encode()
|
|
102
|
+
else:
|
|
103
|
+
cert_data = cert_to_pem(cert)
|
|
104
|
+
|
|
97
105
|
cur.execute(
|
|
98
106
|
"INSERT INTO certificates (name, priv_id, content) VALUES (?, ?, ?)",
|
|
99
|
-
(name, private_key_id,
|
|
107
|
+
(name, private_key_id, cert_data),
|
|
100
108
|
)
|
|
101
109
|
cert_id = cur.lastrowid
|
|
102
110
|
|
|
@@ -106,7 +114,7 @@ class SqliteKeyStore(KeyStore):
|
|
|
106
114
|
conn.commit()
|
|
107
115
|
return cert_id
|
|
108
116
|
|
|
109
|
-
def get_cert(self, domain: str) -> None | Tuple[int | str, Key, Certificate]:
|
|
117
|
+
def get_cert(self, domain: str) -> None | Tuple[int | str, Key, List[Certificate]]:
|
|
110
118
|
conn = self._get_db_connection()
|
|
111
119
|
cur = conn.cursor()
|
|
112
120
|
cur.execute(
|
|
@@ -123,7 +131,11 @@ class SqliteKeyStore(KeyStore):
|
|
|
123
131
|
|
|
124
132
|
cur.close()
|
|
125
133
|
|
|
126
|
-
|
|
134
|
+
if res is None:
|
|
135
|
+
return None
|
|
136
|
+
|
|
137
|
+
certs = certs_from_pem(res[2])
|
|
138
|
+
return (res[0], Key.from_der(res[1]), certs)
|
|
127
139
|
|
|
128
140
|
def _init_account_key(self) -> RSAPrivateKey:
|
|
129
141
|
acme_key_name = "ACME Account Key"
|
|
@@ -174,20 +186,22 @@ class FilesystemKeyStore(KeyStore):
|
|
|
174
186
|
return key_from_pem(key_data)
|
|
175
187
|
return None
|
|
176
188
|
|
|
177
|
-
def find_cert(self, name: str) -> Union[None, Certificate]:
|
|
189
|
+
def find_cert(self, name: str) -> Union[None, List[Certificate]]:
|
|
178
190
|
cert_path = os.path.join(self.certs_dir, f"{name}.crt")
|
|
179
191
|
if os.path.exists(cert_path):
|
|
180
192
|
with open(cert_path, "rb") as f:
|
|
181
193
|
cert_data = f.read()
|
|
182
|
-
return
|
|
194
|
+
return certs_from_pem(cert_data)
|
|
183
195
|
return None
|
|
184
196
|
|
|
185
197
|
|
|
186
|
-
def save_cert(self, private_key_id: str, cert: Certificate | str, domains: list, name: str = None) -> int:
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
198
|
+
def save_cert(self, private_key_id: str, cert: Certificate | str | List[Certificate], domains: list, name: str = None) -> int:
|
|
199
|
+
if isinstance(cert, list):
|
|
200
|
+
cert_pem = certs_to_pem(cert)
|
|
201
|
+
elif isinstance(cert, str):
|
|
202
|
+
cert_pem = cert.encode()
|
|
203
|
+
else:
|
|
204
|
+
cert_pem = cert_to_pem(cert)
|
|
191
205
|
|
|
192
206
|
if name:
|
|
193
207
|
cert_path = os.path.join(self.certs_dir, f"{name}.crt")
|
|
@@ -215,7 +229,7 @@ class FilesystemKeyStore(KeyStore):
|
|
|
215
229
|
|
|
216
230
|
return name if name else domains[0] # Dummy ID since filesystem does not use numeric IDs
|
|
217
231
|
|
|
218
|
-
def get_cert(self, name: str) -> None | Tuple[str, Key, Certificate]:
|
|
232
|
+
def get_cert(self, name: str) -> None | Tuple[str, Key, List[Certificate]]:
|
|
219
233
|
cert_path = os.path.join(self.certs_dir, f"{name}.crt")
|
|
220
234
|
key_path = os.path.join(self.keys_dir, f"{name}.key")
|
|
221
235
|
key = None
|
|
@@ -230,7 +244,7 @@ class FilesystemKeyStore(KeyStore):
|
|
|
230
244
|
if os.path.exists(cert_path):
|
|
231
245
|
try:
|
|
232
246
|
with open(cert_path, "rb") as f:
|
|
233
|
-
cert =
|
|
247
|
+
cert = certs_from_pem(f.read())
|
|
234
248
|
except ValueError:
|
|
235
249
|
pass
|
|
236
250
|
|
|
@@ -329,13 +343,20 @@ class PostgresKeyStore(KeyStore):
|
|
|
329
343
|
self.save_key(key, name)
|
|
330
344
|
return key
|
|
331
345
|
|
|
332
|
-
def save_cert(self, private_key_id: int, cert: Certificate, domains: List[str], name: str = None) -> int:
|
|
346
|
+
def save_cert(self, private_key_id: int, cert: Certificate | str | List[Certificate], domains: List[str], name: str = None) -> int:
|
|
333
347
|
"""Saves a certificate along with associated domains."""
|
|
334
348
|
with self.get_connection() as conn:
|
|
349
|
+
if isinstance(cert, list):
|
|
350
|
+
cert_data = certs_to_pem(cert)
|
|
351
|
+
elif isinstance(cert, str):
|
|
352
|
+
cert_data = cert.encode()
|
|
353
|
+
else:
|
|
354
|
+
cert_data = cert_to_pem(cert)
|
|
355
|
+
|
|
335
356
|
# Insert certificate and associated domains directly
|
|
336
357
|
conn.execute(
|
|
337
358
|
"INSERT INTO certificates (name, priv_id, content) VALUES (%s, %s, %s) RETURNING id",
|
|
338
|
-
(name, private_key_id,
|
|
359
|
+
(name, private_key_id, cert_data),
|
|
339
360
|
)
|
|
340
361
|
cert_id = conn.fetchone()[0]
|
|
341
362
|
|
|
@@ -345,7 +366,7 @@ class PostgresKeyStore(KeyStore):
|
|
|
345
366
|
|
|
346
367
|
return cert_id
|
|
347
368
|
|
|
348
|
-
def get_cert(self, domain: str) -> Optional[Tuple[int, RSAPrivateKey, Certificate]]:
|
|
369
|
+
def get_cert(self, domain: str) -> Optional[Tuple[int, RSAPrivateKey, List[Certificate]]]:
|
|
349
370
|
"""Fetches a certificate and its associated private key for a domain."""
|
|
350
371
|
with self.get_connection() as conn:
|
|
351
372
|
# Directly execute and fetch result
|
|
@@ -362,7 +383,7 @@ class PostgresKeyStore(KeyStore):
|
|
|
362
383
|
res = conn.fetchone()
|
|
363
384
|
|
|
364
385
|
if res:
|
|
365
|
-
return (res[0], Key.from_der(res[1]),
|
|
386
|
+
return (res[0], Key.from_der(res[1]), certs_from_pem(res[2]))
|
|
366
387
|
return None
|
|
367
388
|
|
|
368
389
|
def _init_account_key(self) -> RSAPrivateKey:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: certapi
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.7
|
|
4
4
|
Summary: Python Package for managing keys, request SSL certificates from ACME.
|
|
5
5
|
Home-page: https://github.com/mesudip/certapi
|
|
6
6
|
Author: Sudip Bhattarai
|
|
@@ -14,7 +14,7 @@ Description-Content-Type: text/markdown
|
|
|
14
14
|
# CertApi
|
|
15
15
|
|
|
16
16
|
CertApi is a Python package for requesting SSL certificates from ACME.
|
|
17
|
-
This is
|
|
17
|
+
This is to be used as a base library for building other tools, or to integrate Certificate creation feature in you app.
|
|
18
18
|
|
|
19
19
|
> ⚠️ Warning: This project is not polished for production use. Please stay tuned for the LTS `v1.0.0` release.
|
|
20
20
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|