certapi 0.1.0__tar.gz → 0.1.1__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.
@@ -1,8 +1,8 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: certapi
3
- Version: 0.1.0
3
+ Version: 0.1.1
4
4
  Summary: Python Package for managing keys, request SSL certificates from ACME.
5
- Home-page: https://github.com/mesudip/certmanager
5
+ Home-page: https://github.com/mesudip/certapi
6
6
  Author: Sudip Bhattarai
7
7
  Author-email: sudipbhattarai100@gmail.com
8
8
  Classifier: Programming Language :: Python :: 3
@@ -13,14 +13,14 @@ Description-Content-Type: text/markdown
13
13
  Requires-Dist: cryptography
14
14
  Requires-Dist: requests
15
15
 
16
- # CertManager
16
+ # CertApi
17
17
 
18
- CertManager is a Python package for requesting SSL certificates from ACME.
18
+ CertApi is a Python package for requesting SSL certificates from ACME.
19
19
  This is supposed to be used as a base library for building other tools, or to integrate Certificate creation feature in you app.
20
20
 
21
21
  ## Installation
22
22
 
23
- You can install CertManager using pip:
23
+ You can install CertApi using pip:
24
24
 
25
25
  ```bash
26
26
  pip install certapi
@@ -30,7 +30,7 @@ pip install certapi
30
30
 
31
31
  ```python
32
32
  import json
33
- from certmanager import FileSystemChallengeStore, FilesystemKeyStore, CertAuthority
33
+ from certapi import FileSystemChallengeStore, FilesystemKeyStore, CertAuthority
34
34
 
35
35
  key_store = FilesystemKeyStore("data")
36
36
  challenge_store = FileSystemChallengeStore("./acme-challenges") # this should be where your web server hosts the .well-known/acme-challenges.
@@ -1,11 +1,11 @@
1
- # CertManager
1
+ # CertApi
2
2
 
3
- CertManager is a Python package for requesting SSL certificates from ACME.
3
+ CertApi is a Python package for requesting SSL certificates from ACME.
4
4
  This is supposed to be used as a base library for building other tools, or to integrate Certificate creation feature in you app.
5
5
 
6
6
  ## Installation
7
7
 
8
- You can install CertManager using pip:
8
+ You can install CertApi using pip:
9
9
 
10
10
  ```bash
11
11
  pip install certapi
@@ -15,7 +15,7 @@ pip install certapi
15
15
 
16
16
  ```python
17
17
  import json
18
- from certmanager import FileSystemChallengeStore, FilesystemKeyStore, CertAuthority
18
+ from certapi import FileSystemChallengeStore, FilesystemKeyStore, CertAuthority
19
19
 
20
20
  key_store = FilesystemKeyStore("data")
21
21
  challenge_store = FileSystemChallengeStore("./acme-challenges") # this should be where your web server hosts the .well-known/acme-challenges.
@@ -2,7 +2,7 @@ from setuptools import setup, find_packages
2
2
 
3
3
  setup(
4
4
  name="certapi",
5
- version="0.1.0",
5
+ version="0.1.1",
6
6
  packages=find_packages(where="src"),
7
7
  package_dir={"": "src"},
8
8
  install_requires=[
@@ -15,7 +15,7 @@ setup(
15
15
  long_description_content_type="text/markdown",
16
16
  author="Sudip Bhattarai",
17
17
  author_email="sudipbhattarai100@gmail.com",
18
- url="https://github.com/mesudip/certmanager",
18
+ url="https://github.com/mesudip/certapi",
19
19
  classifiers=[
20
20
  "Programming Language :: Python :: 3",
21
21
  "License :: OSI Approved :: MIT License",
@@ -12,15 +12,16 @@ from .crypto import sign, digest_sha256, csr_to_der, jwk, get_algorithm_name
12
12
  from .util import b64_encode, b64_string
13
13
 
14
14
  # acme_url = os.environ.get("LETSENCRYPT_API", "https://acme-staging-v02.api.letsencrypt.org/directory")
15
- acme_url = os.environ.get("LETSENCRYPT_API", "https://acme-v02.api.letsencrypt.org/directory")
15
+ acme_url = os.environ.get("LETSENCRYPT_API", None)
16
16
 
17
17
 
18
18
  class AcmeError(Exception):
19
19
  def __init__(self, message, detail, step):
20
- super().__init__(step,message)
20
+ super().__init__(step, message)
21
21
  self.message: str = message
22
22
  self.step: str = step
23
23
  self.detail: dict = detail
24
+ self.can_retry = False
24
25
 
25
26
  def json_obj(self) -> dict:
26
27
  return {"message": self.message, "step": self.step, "detail": self.detail}
@@ -36,6 +37,7 @@ class AcmeNetworkError(AcmeError, requests.RequestException):
36
37
  requests.RequestException.__init__(self, request=request)
37
38
  AcmeError.__init__(self, message, detail, step)
38
39
  requests.RequestException.__init__(self, request=request) # Pass message to RequestException
40
+ self.can_retry = True
39
41
 
40
42
 
41
43
  class AcmeHttpError(AcmeError, requests.HTTPError):
@@ -45,7 +47,7 @@ class AcmeHttpError(AcmeError, requests.HTTPError):
45
47
 
46
48
  def __init__(self, response: requests.Response, step: str):
47
49
  requests.HTTPError.__init__(self, response=response)
48
- self.response=response
50
+ self.response = response
49
51
  (message, detail) = self.extract_acme_response_error()
50
52
  AcmeError.__init__(self, message, detail, step)
51
53
 
@@ -121,8 +123,8 @@ class AcmeHttpError(AcmeError, requests.HTTPError):
121
123
  message = err_detail
122
124
 
123
125
  if message is None:
124
- if res_json.get('detail'):
125
- message = res_json['detail']
126
+ if res_json.get("detail"):
127
+ message = res_json["detail"]
126
128
  else:
127
129
  message = "Received status=" + str(self.response.status_code) + " from AMCE server"
128
130
  if error is None:
@@ -141,22 +143,28 @@ class AcmeInvaliOrderError(AcmeHttpError):
141
143
  super().__init__(response, step)
142
144
 
143
145
 
146
+ class AcmeInvaliNonceError(AcmeHttpError):
147
+ def __init__(self, response: requests.Response, step: str):
148
+ super().__init__(response, step)
149
+ self.can_retry = True
150
+
151
+
144
152
  def request(method, step: str, url: str, json=None, headers=None, throw=True) -> requests.Response:
145
- res=None
153
+ res = None
146
154
  try:
147
155
  res = requests.request(method, url, json=json, headers=headers, timeout=15)
148
- print("Request ["+str(res.status_code)+"] : " + method + " " + url + " step=" + step)
156
+ print("Request [" + str(res.status_code) + "] : " + method + " " + url + " step=" + step)
149
157
  except requests.HTTPError as e:
150
158
  status = res.status_code if res else None
151
159
  status = status if status else (e.response.status_code if e.response else None)
152
160
  if status:
153
- print("Request ["+str(status)+"] : " + method + " " + url + " step=" + step)
161
+ print("Request [" + str(status) + "] : " + method + " " + url + " step=" + step)
154
162
  else:
155
163
  print("Request : " + method + " " + url + " step=" + step)
156
164
 
157
165
  raise e
158
166
  except requests.RequestException as e:
159
- print("Request : " + method + " " + url + " step=" + step)
167
+ print("Request : " + str(method) + " " + str(url) + " step=" + str(step))
160
168
  raise AcmeNetworkError(
161
169
  e.request,
162
170
  f"Error communicating with ACME server",
@@ -169,6 +177,18 @@ def request(method, step: str, url: str, json=None, headers=None, throw=True) ->
169
177
  step,
170
178
  )
171
179
  if 199 <= res.status_code > 299:
180
+ [print(x, y) for (x, y) in res.headers.items()]
181
+ print("Response:", res.text)
182
+ json_data = None
183
+ try:
184
+ json_data = res.json()
185
+ except requests.RequestException as e:
186
+ pass
187
+ if json_data and json_data.get("type"):
188
+ errorType = json_data["type"]
189
+ if errorType == "urn:ietf:params:acme:error:badNonce":
190
+ raise AcmeInvaliNonceError(res, step=step)
191
+
172
192
  if throw:
173
193
  raise AcmeHttpError(res, step=step)
174
194
  return res
@@ -192,7 +212,7 @@ class Acme:
192
212
  # json web key format for public key
193
213
  self.jwk = jwk(self.account_key)
194
214
  self.nonce = []
195
- self.acme_url = url
215
+ self.acme_url = url if url else self.URL_PROD
196
216
  self.key_id = None
197
217
  self.directory = None
198
218
  self._nonce_lock = threading.Lock() # Mutex for safe access to nonce
@@ -210,19 +230,12 @@ class Acme:
210
230
  url = self._directory(path_name)
211
231
  return self._signed_req(url, payload, depth, step="Acme request:" + path_name)
212
232
 
213
- def _signed_req(
214
- self, url, payload: Union[str, dict, list, bytes, None] = None, depth=0, step="Acme Request", throw=True
215
- ) -> requests.Response:
216
- payload64 = b64_encode(payload) if payload is not None else b""
233
+ def get_nonce(self, step: str, counter=1):
217
234
  nonce = None
218
- with self._nonce_lock: # Acquire lock to ensure thread-safe access to nonce
219
- # Check if there are any nonces available
235
+ with self._nonce_lock: # Acquire nonce
220
236
  if self.nonce:
221
- # Pop the first nonce from the list
222
237
  nonce = self.nonce.pop(0)
223
-
224
- # Fetch a new nonce if the list is empty
225
- nonce = (
238
+ return (
226
239
  nonce
227
240
  if nonce
228
241
  else get(
@@ -230,10 +243,20 @@ class Acme:
230
243
  ).headers.get("Replay-Nonce")
231
244
  )
232
245
 
246
+ def record_nonce(self, response: requests.Response) -> requests.Response:
247
+ with self._nonce_lock:
248
+ self.nonce.append(response.headers.get("Replay-Nonce", None))
249
+ return response
250
+
251
+ def _signed_req(
252
+ self, url, payload: Union[str, dict, list, bytes, None] = None, depth=0, step="Acme Request", throw=True
253
+ ) -> requests.Response:
254
+ payload64 = b64_encode(payload) if payload is not None else b""
255
+
233
256
  protected = {
234
257
  "url": url,
235
258
  "alg": get_algorithm_name(self.account_key),
236
- "nonce": nonce,
259
+ "nonce": self.get_nonce(step),
237
260
  }
238
261
 
239
262
  if self.key_id:
@@ -246,19 +269,16 @@ class Acme:
246
269
  "payload": payload64.decode("utf-8"),
247
270
  "signature": b64_string(sign(self.account_key, b".".join([protectedb64, payload64]))),
248
271
  }
272
+ try:
249
273
 
250
- response = post(step, url, json=payload, headers={"Content-Type": "application/jose+json"}, throw=throw)
251
- if response.status_code > 299 or response.status_code < 200 :
252
- print("-" * 30 + " Request " + "-" * 30)
253
- print(response.status_code, " : ", url)
254
- print("status:", response.status_code)
255
- print(json.dumps({x[0]: x[1] for x in response.headers.items()}, indent=2))
256
- print(response.text)
257
- print("-" * 60)
274
+ response = post(step, url, json=payload, headers={"Content-Type": "application/jose+json"}, throw=throw)
275
+ except AcmeError as e:
276
+ if e.can_retry and depth <= 0:
277
+ return self._signed_req(url, payload, depth + 1, step, throw)
278
+ else:
279
+ raise e
258
280
 
259
- with self._nonce_lock:
260
- self.nonce.append(response.headers.get("Replay-Nonce", None))
261
- return response
281
+ return self.record_nonce(response)
262
282
 
263
283
  def register(self):
264
284
  response = self._directory_req("newAccount", {"termsOfServiceAgreed": True})
@@ -15,8 +15,8 @@ from .db import KeyStore
15
15
 
16
16
 
17
17
  class CertAuthority:
18
- def __init__(self, challenge_store: challenge.ChallengeStore, key_store: KeyStore,acme_url=None):
19
- self.acme = Acme(key_store.account_key,url=acme_url)
18
+ def __init__(self, challenge_store: challenge.ChallengeStore, key_store: KeyStore, acme_url=None):
19
+ self.acme = Acme(key_store.account_key, url=acme_url)
20
20
  self.key_store = key_store
21
21
  self.challengesStore: challenge.ChallengeStore = challenge_store
22
22
 
@@ -28,9 +28,7 @@ class CertAuthority:
28
28
  elif res.status_code != 200:
29
29
  raise Exception("Acme registration didn't return 200 or 201 ", res.json())
30
30
 
31
- def obtainCert(
32
- self, host: Union[str, List[str]]
33
- ) -> Union[Tuple["CertificateResponse", None], Tuple[None, requests.Response]]:
31
+ def obtainCert(self, host: Union[str, List[str]]) -> "CertificateResponse":
34
32
  if type(host) == str:
35
33
  host = [host]
36
34
 
@@ -73,12 +71,11 @@ class CertAuthority:
73
71
  order.refresh() # is this refresh necessary?
74
72
 
75
73
  if order.status == "valid":
76
- (certificate, _) = order.get_certificate()
74
+ certificate = order.get_certificate()
77
75
  key_id = self.key_store.save_key(private_key, missing[0])
78
76
  cert_id = self.key_store.save_cert(key_id, certificate, missing)
79
77
  issued_cert = IssuedCert(key_to_pem(private_key), certificate, missing)
80
- response = createExistingResponse(existing, [issued_cert])
81
- return (response, None)
78
+ return createExistingResponse(existing, [issued_cert])
82
79
  elif order.status == "processing":
83
80
  if count == 0:
84
81
  return None
@@ -87,7 +84,7 @@ class CertAuthority:
87
84
 
88
85
  return obtain_cert()
89
86
  else:
90
- return createExistingResponse(existing, []), None
87
+ return createExistingResponse(existing, [])
91
88
 
92
89
 
93
90
  def createExistingResponse(existing: Dict[str, Tuple[int | str, Key, Certificate]], issued_certs: List["IssuedCert"]):
@@ -1,9 +1,9 @@
1
1
  import os
2
2
  import sqlite3
3
- from typing import Union, Tuple, Optional
3
+ from typing import Tuple, Optional
4
+ from contextlib import contextmanager
4
5
 
5
6
  from .crypto import *
6
- from flask import g
7
7
  from abc import ABC, abstractmethod
8
8
 
9
9
  from .crypto_classes import Key
@@ -32,6 +32,7 @@ class KeyStore(ABC):
32
32
  class SqliteKeyStore(KeyStore):
33
33
  def __init__(self, db_path="db/database.db"):
34
34
  self.db_path = db_path
35
+ self.db = None
35
36
  self._initialize_db()
36
37
  self.account_key = self._init_account_key()
37
38
 
@@ -64,9 +65,18 @@ class SqliteKeyStore(KeyStore):
64
65
  )
65
66
 
66
67
  def _get_db_connection(self):
67
- if "db" not in g:
68
- g.db = sqlite3.connect(self.db_path)
69
- return g.db
68
+ if self.db:
69
+ return self.db
70
+ target = self
71
+ try:
72
+ from flask import g
73
+
74
+ target = g
75
+ except:
76
+ pass
77
+ if "db" not in target or target.db is None:
78
+ target.db = sqlite3.connect(self.db_path)
79
+ return target.db
70
80
 
71
81
  def save_key(self, key: RSAPrivateKey, name: str = None) -> int:
72
82
  conn = self._get_db_connection()
@@ -218,7 +228,6 @@ class FilesystemKeyStore(KeyStore):
218
228
  class PostgresKeyStore(KeyStore):
219
229
  def __init__(self, db_url="postgresql://user:password@localhost/dbname"):
220
230
  self.db_url = db_url
221
-
222
231
  import psycopg2
223
232
 
224
233
  self.psycopg2 = psycopg2
@@ -232,7 +241,7 @@ class PostgresKeyStore(KeyStore):
232
241
  """Initialize the connection pool."""
233
242
  from psycopg2.pool import SimpleConnectionPool
234
243
 
235
- self.pool = SimpleConnectionPool(1, 1, self.db_url)
244
+ self.pool = SimpleConnectionPool(1, 10, self.db_url)
236
245
 
237
246
  def _check_connection(self, conn):
238
247
  """Check if the connection is alive by using the `ping` method."""
@@ -242,59 +251,62 @@ class PostgresKeyStore(KeyStore):
242
251
  except self.psycopg2.OperationalError:
243
252
  return False
244
253
 
245
- def _get_db_connection(self):
246
- """Get a connection from the pool and ensure it is healthy."""
254
+ @contextmanager
255
+ def get_connection(self):
256
+ """Context manager for acquiring and releasing a database connection."""
247
257
  conn = self.pool.getconn()
248
258
 
249
- # Check connection health
250
- if not self._check_connection(conn):
251
- print("Connection is not healthy, reconnecting...")
252
- self.pool.putconn(conn, close=True) # Close the bad connection
253
- conn = self.pool.getconn() # Get a fresh connection
254
-
255
- return conn
259
+ try:
260
+ # Check connection health
261
+ if not self._check_connection(conn):
262
+ print("Connection is not healthy, reconnecting...")
263
+ self.pool.putconn(conn, close=True) # Close the bad connection
264
+ conn = self.pool.getconn() # Get a fresh connection
265
+
266
+ # Yield the connection to the caller
267
+ yield conn
268
+ finally:
269
+ # Return the connection to the pool
270
+ self.pool.putconn(conn)
256
271
 
257
272
  def _initialize_db(self):
258
273
  """Initializes the database with necessary tables."""
259
- conn = self._get_db_connection()
260
- cur = conn.cursor()
261
-
262
- cur.execute(
274
+ with self.get_connection() as conn:
275
+ # Use the connection directly to execute the query
276
+ conn.execute(
277
+ """
278
+ CREATE TABLE IF NOT EXISTS private_keys (
279
+ id SERIAL PRIMARY KEY,
280
+ name VARCHAR(50) NULL,
281
+ content BYTEA
282
+ );
283
+ CREATE TABLE IF NOT EXISTS certificates (
284
+ id SERIAL PRIMARY KEY,
285
+ name VARCHAR(50) NULL,
286
+ priv_id INTEGER REFERENCES private_keys NOT NULL,
287
+ content BYTEA,
288
+ sign_id INTEGER REFERENCES private_keys NULL
289
+ );
290
+ CREATE TABLE IF NOT EXISTS ssl_domains (
291
+ domain VARCHAR(255),
292
+ certificate_id INTEGER REFERENCES certificates
293
+ );
294
+ CREATE TABLE IF NOT EXISTS ssl_wildcards (
295
+ domain VARCHAR(255),
296
+ certificate_id INTEGER REFERENCES certificates
297
+ );
263
298
  """
264
- CREATE TABLE IF NOT EXISTS private_keys (
265
- id SERIAL PRIMARY KEY,
266
- name VARCHAR(50) NULL,
267
- content BYTEA
268
- );
269
- CREATE TABLE IF NOT EXISTS certificates (
270
- id SERIAL PRIMARY KEY,
271
- name VARCHAR(50) NULL,
272
- priv_id INTEGER REFERENCES private_keys NOT NULL,
273
- content BYTEA,
274
- sign_id INTEGER REFERENCES private_keys NULL
275
- );
276
- CREATE TABLE IF NOT EXISTS ssl_domains (
277
- domain VARCHAR(255),
278
- certificate_id INTEGER REFERENCES certificates
279
- );
280
- CREATE TABLE IF NOT EXISTS ssl_wildcards (
281
- domain VARCHAR(255),
282
- certificate_id INTEGER REFERENCES certificates
283
- );
284
- """
285
- )
286
-
287
- cur.close()
288
- conn.commit()
299
+ )
300
+ # No need to explicitly commit here
289
301
 
290
302
  def save_key(self, key: RSAPrivateKey, name: str = None) -> int:
291
303
  """Saves a private key in the database."""
292
- conn = self._get_db_connection()
293
- cur = conn.cursor()
294
- cur.execute("INSERT INTO private_keys (name, content) VALUES (%s, %s) RETURNING id", (name, key_to_der(key)))
295
- key_id = cur.fetchone()[0]
296
- cur.close()
297
- conn.commit()
304
+ with self.get_connection() as conn:
305
+ # Execute the insert directly
306
+ conn.execute(
307
+ "INSERT INTO private_keys (name, content) VALUES (%s, %s) RETURNING id", (name, key_to_der(key))
308
+ )
309
+ key_id = conn.fetchone()[0] # Fetch the inserted ID
298
310
  return key_id
299
311
 
300
312
  def gen_key(self, name: str = None, size: int = 4096) -> RSAPrivateKey:
@@ -305,61 +317,51 @@ class PostgresKeyStore(KeyStore):
305
317
 
306
318
  def save_cert(self, private_key_id: int, cert: Certificate, domains: List[str], name: str = None) -> int:
307
319
  """Saves a certificate along with associated domains."""
308
- conn = self._get_db_connection()
309
- cur = conn.cursor()
310
-
311
- # Insert certificate
312
- cur.execute(
313
- "INSERT INTO certificates (name, priv_id, content) VALUES (%s, %s, %s) RETURNING id",
314
- (name, private_key_id, cert.public_bytes(serialization.Encoding.DER)),
315
- )
316
- cert_id = cur.fetchone()[0]
320
+ with self.get_connection() as conn:
321
+ # Insert certificate and associated domains directly
322
+ conn.execute(
323
+ "INSERT INTO certificates (name, priv_id, content) VALUES (%s, %s, %s) RETURNING id",
324
+ (name, private_key_id, cert.public_bytes(serialization.Encoding.DER)),
325
+ )
326
+ cert_id = conn.fetchone()[0]
317
327
 
318
- # Insert associated domains
319
- for domain in domains:
320
- cur.execute("INSERT INTO ssl_domains (domain, certificate_id) VALUES (%s, %s)", (domain, cert_id))
328
+ # Insert associated domains
329
+ for domain in domains:
330
+ conn.execute("INSERT INTO ssl_domains (domain, certificate_id) VALUES (%s, %s)", (domain, cert_id))
321
331
 
322
- cur.close()
323
- conn.commit()
324
332
  return cert_id
325
333
 
326
334
  def get_cert(self, domain: str) -> Optional[Tuple[int, RSAPrivateKey, Certificate]]:
327
335
  """Fetches a certificate and its associated private key for a domain."""
328
- conn = self._get_db_connection()
329
- cur = conn.cursor()
330
-
331
- cur.execute(
332
- """
333
- SELECT c.id, p.content, c.content
334
- FROM ssl_domains s
335
- JOIN certificates c ON s.certificate_id = c.id
336
- JOIN private_keys p ON c.priv_id = p.id
337
- WHERE s.domain = %s
338
- """,
339
- (domain,),
340
- )
341
- res = cur.fetchone()
342
-
343
- cur.close()
344
- conn.commit()
336
+ with self.get_connection() as conn:
337
+ # Directly execute and fetch result
338
+ conn.execute(
339
+ """
340
+ SELECT c.id, p.content, c.content
341
+ FROM ssl_domains s
342
+ JOIN certificates c ON s.certificate_id = c.id
343
+ JOIN private_keys p ON c.priv_id = p.id
344
+ WHERE s.domain = %s
345
+ """,
346
+ (domain,),
347
+ )
348
+ res = conn.fetchone()
345
349
 
346
- if res:
347
- return (res[0], Key.from_der(res[1]), cert_from_der(res[2]))
350
+ if res:
351
+ return (res[0], Key.from_der(res[1]), cert_from_der(res[2]))
348
352
  return None
349
353
 
350
354
  def _init_account_key(self) -> RSAPrivateKey:
351
355
  """Initializes or retrieves the ACME account key."""
352
356
  acme_key_name = "ACME Account Key"
353
- conn = self._get_db_connection()
354
- cur = conn.cursor()
355
- cur.execute("SELECT content FROM private_keys WHERE name = %s", (acme_key_name,))
356
- account_key_data = cur.fetchone()
357
-
358
- if not account_key_data:
359
- account_key = self.gen_key(acme_key_name)
360
- else:
361
- account_key = key_from_der(account_key_data[0])
362
-
363
- cur.close()
357
+ with self.get_connection() as conn:
358
+ # Directly execute to fetch the account key
359
+ conn.execute("SELECT content FROM private_keys WHERE name = %s", (acme_key_name,))
360
+ account_key_data = conn.fetchone()
361
+
362
+ if not account_key_data:
363
+ account_key = self.gen_key(acme_key_name)
364
+ else:
365
+ account_key = key_from_der(account_key_data[0])
364
366
 
365
367
  return account_key
@@ -1,8 +1,8 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: certapi
3
- Version: 0.1.0
3
+ Version: 0.1.1
4
4
  Summary: Python Package for managing keys, request SSL certificates from ACME.
5
- Home-page: https://github.com/mesudip/certmanager
5
+ Home-page: https://github.com/mesudip/certapi
6
6
  Author: Sudip Bhattarai
7
7
  Author-email: sudipbhattarai100@gmail.com
8
8
  Classifier: Programming Language :: Python :: 3
@@ -13,14 +13,14 @@ Description-Content-Type: text/markdown
13
13
  Requires-Dist: cryptography
14
14
  Requires-Dist: requests
15
15
 
16
- # CertManager
16
+ # CertApi
17
17
 
18
- CertManager is a Python package for requesting SSL certificates from ACME.
18
+ CertApi is a Python package for requesting SSL certificates from ACME.
19
19
  This is supposed to be used as a base library for building other tools, or to integrate Certificate creation feature in you app.
20
20
 
21
21
  ## Installation
22
22
 
23
- You can install CertManager using pip:
23
+ You can install CertApi using pip:
24
24
 
25
25
  ```bash
26
26
  pip install certapi
@@ -30,7 +30,7 @@ pip install certapi
30
30
 
31
31
  ```python
32
32
  import json
33
- from certmanager import FileSystemChallengeStore, FilesystemKeyStore, CertAuthority
33
+ from certapi import FileSystemChallengeStore, FilesystemKeyStore, CertAuthority
34
34
 
35
35
  key_store = FilesystemKeyStore("data")
36
36
  challenge_store = FileSystemChallengeStore("./acme-challenges") # this should be where your web server hosts the .well-known/acme-challenges.
@@ -1,7 +1,7 @@
1
1
  import pytest
2
2
  from cryptography.hazmat.primitives import serialization
3
- from certmanager.crypto import gen_key_rsa, key_to_pem
4
- from certmanager.custom_certauthority import CustomCertAuthority
3
+ from certapi.crypto import gen_key_rsa, key_to_pem
4
+ from certapi.custom_certauthority import CustomCertAuthority
5
5
  import os
6
6
  from contextlib import contextmanager
7
7
 
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes