certapi 0.2.5__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.
Files changed (24) hide show
  1. {certapi-0.2.5 → certapi-0.2.7}/PKG-INFO +2 -2
  2. {certapi-0.2.5 → certapi-0.2.7}/README.md +1 -1
  3. {certapi-0.2.5 → certapi-0.2.7}/setup.py +1 -1
  4. {certapi-0.2.5 → certapi-0.2.7}/src/certapi/Acme.py +10 -3
  5. {certapi-0.2.5 → certapi-0.2.7}/src/certapi/certauthority.py +15 -5
  6. {certapi-0.2.5 → certapi-0.2.7}/src/certapi/crypto.py +2 -0
  7. {certapi-0.2.5 → certapi-0.2.7}/src/certapi/db.py +40 -19
  8. {certapi-0.2.5 → certapi-0.2.7}/src/certapi.egg-info/PKG-INFO +2 -2
  9. {certapi-0.2.5 → certapi-0.2.7}/MANIFEST.in +0 -0
  10. {certapi-0.2.5 → certapi-0.2.7}/pyproject.toml +0 -0
  11. {certapi-0.2.5 → certapi-0.2.7}/setup.cfg +0 -0
  12. {certapi-0.2.5 → certapi-0.2.7}/src/certapi/__init__.py +0 -0
  13. {certapi-0.2.5 → certapi-0.2.7}/src/certapi/challenge.py +0 -0
  14. {certapi-0.2.5 → certapi-0.2.7}/src/certapi/cloudflare_challenge_store.py +0 -0
  15. {certapi-0.2.5 → certapi-0.2.7}/src/certapi/cloudflare_client.py +0 -0
  16. {certapi-0.2.5 → certapi-0.2.7}/src/certapi/crypto_classes.py +0 -0
  17. {certapi-0.2.5 → certapi-0.2.7}/src/certapi/custom_certauthority.py +0 -0
  18. {certapi-0.2.5 → certapi-0.2.7}/src/certapi/digitalocean_challenge_store.py +0 -0
  19. {certapi-0.2.5 → certapi-0.2.7}/src/certapi/digitalocean_client.py +0 -0
  20. {certapi-0.2.5 → certapi-0.2.7}/src/certapi/util.py +0 -0
  21. {certapi-0.2.5 → certapi-0.2.7}/src/certapi.egg-info/SOURCES.txt +0 -0
  22. {certapi-0.2.5 → certapi-0.2.7}/src/certapi.egg-info/dependency_links.txt +0 -0
  23. {certapi-0.2.5 → certapi-0.2.7}/src/certapi.egg-info/requires.txt +0 -0
  24. {certapi-0.2.5 → 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.5
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 supposed to be used as a base library for building other tools, or to integrate Certificate creation feature in you app.
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 supposed to be used as a base library for building other tools, or to integrate Certificate creation feature in you app.
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
 
@@ -2,7 +2,7 @@ from setuptools import setup, find_packages
2
2
 
3
3
  setup(
4
4
  name="certapi",
5
- version="0.2.5",
5
+ version="0.2.7",
6
6
  packages=find_packages(where="src"),
7
7
  package_dir={"": "src"},
8
8
  install_requires=[
@@ -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
- else:
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
- else:
109
+ elif err_detail:
107
110
  message = err_detail
108
- else:
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"):
@@ -123,7 +123,7 @@ class CertAuthority:
123
123
  if order.status == "valid":
124
124
  fullchain_cert,certificate = order.get_certificate()
125
125
  key_id = self.key_store.save_key(private_key, missing[0])
126
- cert_id = self.key_store.save_cert(key_id, certificate[0], missing)
126
+ cert_id = self.key_store.save_cert(key_id, certificate, missing)
127
127
  issued_cert = IssuedCert(key_to_pem(private_key), fullchain_cert, missing)
128
128
  # Clean up challenges after successful certificate issuance
129
129
  for c in challenges:
@@ -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
- cert_pem = cert if isinstance(cert, str) else cert_to_pem(cert).decode("utf-8")
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
- if isinstance(cert, Certificate):
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, cert_to_der(cert)),
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
- return res if res is None else (res[0], Key.from_der(res[1]), cert_from_der(res[2]))
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 cert_from_pem(cert_data)
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
- def get_cert_pem(cert) -> bytes:
188
- return cert.encode() if isinstance(cert, str) else cert_to_pem(cert)
189
-
190
- cert_pem = get_cert_pem(cert)
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 = cert_from_pem(f.read())
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, cert.public_bytes(serialization.Encoding.DER)),
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]), cert_from_der(res[2]))
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.5
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 supposed to be used as a base library for building other tools, or to integrate Certificate creation feature in you app.
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