certapi 0.1.0__py3-none-any.whl
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/Acme.py +386 -0
- certapi/__init__.py +5 -0
- certapi/certauthority.py +154 -0
- certapi/challenge.py +121 -0
- certapi/crypto.py +222 -0
- certapi/crypto_classes.py +135 -0
- certapi/custom_certauthority.py +94 -0
- certapi/db.py +365 -0
- certapi/util.py +19 -0
- certapi-0.1.0.dist-info/METADATA +45 -0
- certapi-0.1.0.dist-info/RECORD +13 -0
- certapi-0.1.0.dist-info/WHEEL +5 -0
- certapi-0.1.0.dist-info/top_level.txt +1 -0
certapi/db.py
ADDED
|
@@ -0,0 +1,365 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import sqlite3
|
|
3
|
+
from typing import Union, Tuple, Optional
|
|
4
|
+
|
|
5
|
+
from .crypto import *
|
|
6
|
+
from flask import g
|
|
7
|
+
from abc import ABC, abstractmethod
|
|
8
|
+
|
|
9
|
+
from .crypto_classes import Key
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class KeyStore(ABC):
|
|
13
|
+
account_key: RSAPrivateKey
|
|
14
|
+
|
|
15
|
+
@abstractmethod
|
|
16
|
+
def save_key(self, key: RSAPrivateKey, name: str = None) -> int | str:
|
|
17
|
+
pass
|
|
18
|
+
|
|
19
|
+
@abstractmethod
|
|
20
|
+
def gen_key(self, name: str = None, size: int = 4096) -> RSAPrivateKey:
|
|
21
|
+
pass
|
|
22
|
+
|
|
23
|
+
@abstractmethod
|
|
24
|
+
def save_cert(self, private_key_id: int, cert: Certificate, domains: List[str], name: str = None) -> int:
|
|
25
|
+
pass
|
|
26
|
+
|
|
27
|
+
@abstractmethod
|
|
28
|
+
def get_cert(self, domain: str) -> None | Tuple[int | str, Key, Certificate]:
|
|
29
|
+
pass
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class SqliteKeyStore(KeyStore):
|
|
33
|
+
def __init__(self, db_path="db/database.db"):
|
|
34
|
+
self.db_path = db_path
|
|
35
|
+
self._initialize_db()
|
|
36
|
+
self.account_key = self._init_account_key()
|
|
37
|
+
|
|
38
|
+
def _initialize_db(self):
|
|
39
|
+
os.makedirs(os.path.dirname(self.db_path), exist_ok=True)
|
|
40
|
+
with sqlite3.connect(self.db_path) as conn:
|
|
41
|
+
conn.executescript(
|
|
42
|
+
"""
|
|
43
|
+
CREATE TABLE IF NOT EXISTS private_keys (
|
|
44
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
45
|
+
name VARCHAR(50) NULL,
|
|
46
|
+
content BLOB
|
|
47
|
+
);
|
|
48
|
+
CREATE TABLE IF NOT EXISTS certificates (
|
|
49
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
50
|
+
name VARCHAR(50) NULL,
|
|
51
|
+
priv_id INTEGER REFERENCES private_keys NOT NULL,
|
|
52
|
+
content BLOB,
|
|
53
|
+
sign_id INTEGER REFERENCES private_keys NULL
|
|
54
|
+
);
|
|
55
|
+
CREATE TABLE IF NOT EXISTS ssl_domains (
|
|
56
|
+
domain VARCHAR(255),
|
|
57
|
+
certificate_id INTEGER REFERENCES certificates
|
|
58
|
+
);
|
|
59
|
+
CREATE TABLE IF NOT EXISTS ssl_wildcards (
|
|
60
|
+
domain VARCHAR(255),
|
|
61
|
+
certificate_id INTEGER REFERENCES certificates
|
|
62
|
+
);
|
|
63
|
+
"""
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
def _get_db_connection(self):
|
|
67
|
+
if "db" not in g:
|
|
68
|
+
g.db = sqlite3.connect(self.db_path)
|
|
69
|
+
return g.db
|
|
70
|
+
|
|
71
|
+
def save_key(self, key: RSAPrivateKey, name: str = None) -> int:
|
|
72
|
+
conn = self._get_db_connection()
|
|
73
|
+
cur = conn.cursor()
|
|
74
|
+
cur.execute("INSERT INTO private_keys (name, content) VALUES (?, ?)", (name, key_to_der(key)))
|
|
75
|
+
cur.close()
|
|
76
|
+
conn.commit()
|
|
77
|
+
return cur.lastrowid
|
|
78
|
+
|
|
79
|
+
def gen_key(self, name: str = None, size: int = 4096) -> RSAPrivateKey:
|
|
80
|
+
key = gen_key_rsa(size)
|
|
81
|
+
self.save_key(key, name)
|
|
82
|
+
return key
|
|
83
|
+
|
|
84
|
+
def save_cert(self, private_key_id: int, cert: Certificate, domains: List[str], name: str = None) -> int:
|
|
85
|
+
conn = self._get_db_connection()
|
|
86
|
+
cur = conn.cursor()
|
|
87
|
+
cur.execute(
|
|
88
|
+
"INSERT INTO certificates (name, priv_id, content) VALUES (?, ?, ?)",
|
|
89
|
+
(name, private_key_id, cert.public_bytes(serialization.Encoding.DER)),
|
|
90
|
+
)
|
|
91
|
+
cert_id = cur.lastrowid
|
|
92
|
+
|
|
93
|
+
for domain in domains:
|
|
94
|
+
cur.execute("INSERT INTO ssl_domains (domain, certificate_id) VALUES (?, ?)", (domain, cert_id))
|
|
95
|
+
cur.close()
|
|
96
|
+
conn.commit()
|
|
97
|
+
return cert_id
|
|
98
|
+
|
|
99
|
+
def get_cert(self, domain: str) -> None | Tuple[int | str, Key, Certificate]:
|
|
100
|
+
conn = self._get_db_connection()
|
|
101
|
+
cur = conn.cursor()
|
|
102
|
+
cur.execute(
|
|
103
|
+
"""
|
|
104
|
+
SELECT c.id, p.content, c.content
|
|
105
|
+
FROM ssl_domains s
|
|
106
|
+
JOIN certificates c ON s.certificate_id = c.id
|
|
107
|
+
JOIN private_keys p ON c.priv_id = p.id
|
|
108
|
+
WHERE s.domain = ?
|
|
109
|
+
""",
|
|
110
|
+
(domain,),
|
|
111
|
+
)
|
|
112
|
+
res = cur.fetchone()
|
|
113
|
+
|
|
114
|
+
cur.close()
|
|
115
|
+
|
|
116
|
+
return res if res is None else (res[0], Key.from_der(res[1]), cert_from_der(res[2]))
|
|
117
|
+
|
|
118
|
+
def _init_account_key(self) -> RSAPrivateKey:
|
|
119
|
+
acme_key_name = "ACME Account Key"
|
|
120
|
+
conn = sqlite3.connect(self.db_path)
|
|
121
|
+
account_key_data = conn.execute("SELECT content FROM private_keys WHERE name = ?", [acme_key_name]).fetchone()
|
|
122
|
+
|
|
123
|
+
if not account_key_data:
|
|
124
|
+
account_key = self.gen_key(acme_key_name)
|
|
125
|
+
else:
|
|
126
|
+
account_key = key_from_der(account_key_data[0])
|
|
127
|
+
|
|
128
|
+
print(key_to_pem(account_key).decode("utf-8"))
|
|
129
|
+
conn.close()
|
|
130
|
+
return account_key
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
class FilesystemKeyStore(KeyStore):
|
|
134
|
+
def __init__(self, base_dir="."):
|
|
135
|
+
self.keys_dir = os.path.join(base_dir, "keys")
|
|
136
|
+
self.certs_dir = os.path.join(base_dir, "certs")
|
|
137
|
+
os.makedirs(self.keys_dir, exist_ok=True)
|
|
138
|
+
os.makedirs(self.certs_dir, exist_ok=True)
|
|
139
|
+
self._init_account_key()
|
|
140
|
+
|
|
141
|
+
def _init_account_key(self) -> RSAPrivateKey:
|
|
142
|
+
acme_key_name = "acme_account"
|
|
143
|
+
self.account_key = self.find_key(acme_key_name)
|
|
144
|
+
if self.account_key is None:
|
|
145
|
+
self.account_key = self.gen_key("acme_account")
|
|
146
|
+
return self.account_key
|
|
147
|
+
|
|
148
|
+
def save_key(self, key: RSAPrivateKey, name: str = None) -> str:
|
|
149
|
+
key_path = os.path.join(self.keys_dir, f"{name}.key")
|
|
150
|
+
with open(key_path, "wb") as f:
|
|
151
|
+
f.write(key_to_pem(key))
|
|
152
|
+
return name # Dummy ID since filesystem does not use numeric IDs
|
|
153
|
+
|
|
154
|
+
def gen_key(self, name: str = None, size: int = 4096) -> RSAPrivateKey:
|
|
155
|
+
key = gen_key_rsa(size)
|
|
156
|
+
self.save_key(key, name)
|
|
157
|
+
return key
|
|
158
|
+
|
|
159
|
+
def find_key(self, name: str) -> Union[None, RSAPrivateKey]:
|
|
160
|
+
key_path = os.path.join(self.keys_dir, f"{name}.key")
|
|
161
|
+
if os.path.exists(key_path):
|
|
162
|
+
with open(key_path, "rb") as f:
|
|
163
|
+
key_data = f.read()
|
|
164
|
+
return key_from_pem(key_data)
|
|
165
|
+
return None
|
|
166
|
+
|
|
167
|
+
def find_cert(self, name: str) -> Union[None, Certificate]:
|
|
168
|
+
cert_path = os.path.join(self.certs_dir, f"{name}.crt")
|
|
169
|
+
if os.path.exists(cert_path):
|
|
170
|
+
with open(cert_path, "rb") as f:
|
|
171
|
+
cert_data = f.read()
|
|
172
|
+
return cert_from_pem(cert_data)
|
|
173
|
+
return None
|
|
174
|
+
|
|
175
|
+
def save_cert(self, private_key_id: str, cert: Certificate, domains: list, name: str = None) -> int:
|
|
176
|
+
if name:
|
|
177
|
+
cert_path = os.path.join(self.certs_dir, f"{name}.crt")
|
|
178
|
+
with open(cert_path, "wb") as f:
|
|
179
|
+
f.write(cert_to_pem(cert))
|
|
180
|
+
key_content = None
|
|
181
|
+
key_path = os.path.join(self.keys_dir, f"{private_key_id}.key")
|
|
182
|
+
with open(key_path, "rb") as f:
|
|
183
|
+
key_content = f.read()
|
|
184
|
+
for domain in domains:
|
|
185
|
+
if domain != private_key_id:
|
|
186
|
+
with open(os.path.join(self.keys_dir, f"{domain}.key"), "wb") as f:
|
|
187
|
+
f.write(key_content)
|
|
188
|
+
domain_cert_path = os.path.join(self.certs_dir, f"{domain}.crt")
|
|
189
|
+
with open(domain_cert_path, "wb") as f:
|
|
190
|
+
f.write(cert_to_pem(cert))
|
|
191
|
+
|
|
192
|
+
return name if name else domains[0] # Dummy ID since filesystem does not use numeric IDs
|
|
193
|
+
|
|
194
|
+
def get_cert(self, domain: str) -> None | Tuple[str, Key, Certificate]:
|
|
195
|
+
cert_path = os.path.join(self.certs_dir, f"{domain}.crt")
|
|
196
|
+
key_path = os.path.join(self.keys_dir, f"{domain}.key")
|
|
197
|
+
key = None
|
|
198
|
+
cert = None
|
|
199
|
+
if os.path.exists(key_path):
|
|
200
|
+
try:
|
|
201
|
+
with open(key_path, "rb") as f:
|
|
202
|
+
key = Key.from_pem(f.read())
|
|
203
|
+
except ValueError:
|
|
204
|
+
pass
|
|
205
|
+
|
|
206
|
+
if os.path.exists(cert_path):
|
|
207
|
+
try:
|
|
208
|
+
with open(cert_path, "rb") as f:
|
|
209
|
+
cert = cert_from_pem(f.read())
|
|
210
|
+
except ValueError:
|
|
211
|
+
pass
|
|
212
|
+
|
|
213
|
+
if cert is None or key is None:
|
|
214
|
+
return None
|
|
215
|
+
return (domain, key, cert)
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
class PostgresKeyStore(KeyStore):
|
|
219
|
+
def __init__(self, db_url="postgresql://user:password@localhost/dbname"):
|
|
220
|
+
self.db_url = db_url
|
|
221
|
+
|
|
222
|
+
import psycopg2
|
|
223
|
+
|
|
224
|
+
self.psycopg2 = psycopg2
|
|
225
|
+
|
|
226
|
+
def setup(self):
|
|
227
|
+
self._initialize_pool()
|
|
228
|
+
self._initialize_db()
|
|
229
|
+
self.account_key = self._init_account_key()
|
|
230
|
+
|
|
231
|
+
def _initialize_pool(self):
|
|
232
|
+
"""Initialize the connection pool."""
|
|
233
|
+
from psycopg2.pool import SimpleConnectionPool
|
|
234
|
+
|
|
235
|
+
self.pool = SimpleConnectionPool(1, 1, self.db_url)
|
|
236
|
+
|
|
237
|
+
def _check_connection(self, conn):
|
|
238
|
+
"""Check if the connection is alive by using the `ping` method."""
|
|
239
|
+
try:
|
|
240
|
+
conn.ping() # This will raise an exception if the connection is not alive.
|
|
241
|
+
return True
|
|
242
|
+
except self.psycopg2.OperationalError:
|
|
243
|
+
return False
|
|
244
|
+
|
|
245
|
+
def _get_db_connection(self):
|
|
246
|
+
"""Get a connection from the pool and ensure it is healthy."""
|
|
247
|
+
conn = self.pool.getconn()
|
|
248
|
+
|
|
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
|
|
256
|
+
|
|
257
|
+
def _initialize_db(self):
|
|
258
|
+
"""Initializes the database with necessary tables."""
|
|
259
|
+
conn = self._get_db_connection()
|
|
260
|
+
cur = conn.cursor()
|
|
261
|
+
|
|
262
|
+
cur.execute(
|
|
263
|
+
"""
|
|
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()
|
|
289
|
+
|
|
290
|
+
def save_key(self, key: RSAPrivateKey, name: str = None) -> int:
|
|
291
|
+
"""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()
|
|
298
|
+
return key_id
|
|
299
|
+
|
|
300
|
+
def gen_key(self, name: str = None, size: int = 4096) -> RSAPrivateKey:
|
|
301
|
+
"""Generates a new RSA private key and saves it."""
|
|
302
|
+
key = gen_key_rsa(size)
|
|
303
|
+
self.save_key(key, name)
|
|
304
|
+
return key
|
|
305
|
+
|
|
306
|
+
def save_cert(self, private_key_id: int, cert: Certificate, domains: List[str], name: str = None) -> int:
|
|
307
|
+
"""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]
|
|
317
|
+
|
|
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))
|
|
321
|
+
|
|
322
|
+
cur.close()
|
|
323
|
+
conn.commit()
|
|
324
|
+
return cert_id
|
|
325
|
+
|
|
326
|
+
def get_cert(self, domain: str) -> Optional[Tuple[int, RSAPrivateKey, Certificate]]:
|
|
327
|
+
"""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()
|
|
345
|
+
|
|
346
|
+
if res:
|
|
347
|
+
return (res[0], Key.from_der(res[1]), cert_from_der(res[2]))
|
|
348
|
+
return None
|
|
349
|
+
|
|
350
|
+
def _init_account_key(self) -> RSAPrivateKey:
|
|
351
|
+
"""Initializes or retrieves the ACME account key."""
|
|
352
|
+
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()
|
|
364
|
+
|
|
365
|
+
return account_key
|
certapi/util.py
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import base64
|
|
3
|
+
from typing import Union
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def b64_encode(data: Union[str, bytes, bytearray, dict, list]) -> bytes:
|
|
7
|
+
if type(data) in (dict, list):
|
|
8
|
+
data = json.dumps(data).encode("utf-8")
|
|
9
|
+
elif type(data) == str:
|
|
10
|
+
data = data.encode("utf-8")
|
|
11
|
+
return base64.urlsafe_b64encode(data).rstrip(b"=")
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def b64_string(data: Union[str, bytes, bytearray, dict, list]) -> str:
|
|
15
|
+
return b64_encode(data).decode("utf-8")
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def crypto():
|
|
19
|
+
return None
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: certapi
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Python Package for managing keys, request SSL certificates from ACME.
|
|
5
|
+
Home-page: https://github.com/mesudip/certmanager
|
|
6
|
+
Author: Sudip Bhattarai
|
|
7
|
+
Author-email: sudipbhattarai100@gmail.com
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Requires-Python: >=3.6
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
Requires-Dist: cryptography
|
|
14
|
+
Requires-Dist: requests
|
|
15
|
+
|
|
16
|
+
# CertManager
|
|
17
|
+
|
|
18
|
+
CertManager is a Python package for requesting SSL certificates from ACME.
|
|
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
|
+
|
|
21
|
+
## Installation
|
|
22
|
+
|
|
23
|
+
You can install CertManager using pip:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
pip install certapi
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Example Usage
|
|
30
|
+
|
|
31
|
+
```python
|
|
32
|
+
import json
|
|
33
|
+
from certmanager import FileSystemChallengeStore, FilesystemKeyStore, CertAuthority
|
|
34
|
+
|
|
35
|
+
key_store = FilesystemKeyStore("data")
|
|
36
|
+
challenge_store = FileSystemChallengeStore("./acme-challenges") # this should be where your web server hosts the .well-known/acme-challenges.
|
|
37
|
+
|
|
38
|
+
certAuthority = CertAuthority(challenge_store, key_store)
|
|
39
|
+
certAuthority.setup()
|
|
40
|
+
|
|
41
|
+
(response,_) = certAuthority.obtainCert("example.com")
|
|
42
|
+
|
|
43
|
+
json.dumps(response.__json__(),indent=2)
|
|
44
|
+
|
|
45
|
+
```
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
certapi/Acme.py,sha256=Ndcn2kDEXcSVQktisGcScnMJNMLRcv2H1aZQHHZar_E,15952
|
|
2
|
+
certapi/__init__.py,sha256=AW8jV-jOuqt5xW1dW8e0Am_uasVLd6Jsp0zhvho5mPA,314
|
|
3
|
+
certapi/certauthority.py,sha256=-GKMNyzgCjeVs4AM-aKqe5XTSMi-wP-nj3WGhZ23-pI,5759
|
|
4
|
+
certapi/challenge.py,sha256=OT_0zx526U0uNNIxT6Iz8wEBXJXtkSEGon_yk04GT8g,3681
|
|
5
|
+
certapi/crypto.py,sha256=OBdQkwqDjEt_Ths3Hlqou0D4tSZHBr_m3F7cQJ-RzZY,7481
|
|
6
|
+
certapi/crypto_classes.py,sha256=VBIBAFRl4PO_e2K6xperD6kZsQh9LX0y1nPESrXVlYM,4028
|
|
7
|
+
certapi/custom_certauthority.py,sha256=YAoqBIVKcSywbXUhW6d0HwxjKk7fA0VMxr9XRO3xQi0,3521
|
|
8
|
+
certapi/db.py,sha256=E8tAZ4Fqt6UD4a4v0uQaVsLZR3Gf22fpcnTPVDgONvM,12821
|
|
9
|
+
certapi/util.py,sha256=M1EUcaOFXqHGrq54_AXgI9hqZpneRBqY7V-fyDUPUIM,477
|
|
10
|
+
certapi-0.1.0.dist-info/METADATA,sha256=vFr1gPhbwhRgWHNewOQvCA633OVw_YgKDDmZzUsBs80,1309
|
|
11
|
+
certapi-0.1.0.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
|
|
12
|
+
certapi-0.1.0.dist-info/top_level.txt,sha256=zK_AUsm1tjAi3d6hpO6rhh5BdfpSj8__9ROReFanBxo,8
|
|
13
|
+
certapi-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
certapi
|