easycert 0.1.0__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.
- easycert-0.1.0/LICENSE +21 -0
- easycert-0.1.0/PKG-INFO +13 -0
- easycert-0.1.0/README.md +99 -0
- easycert-0.1.0/easycert/__init__.py +54 -0
- easycert-0.1.0/easycert/generate.py +173 -0
- easycert-0.1.0/easycert/verify.py +300 -0
- easycert-0.1.0/easycert.egg-info/PKG-INFO +13 -0
- easycert-0.1.0/easycert.egg-info/SOURCES.txt +11 -0
- easycert-0.1.0/easycert.egg-info/dependency_links.txt +1 -0
- easycert-0.1.0/easycert.egg-info/requires.txt +2 -0
- easycert-0.1.0/easycert.egg-info/top_level.txt +1 -0
- easycert-0.1.0/setup.cfg +4 -0
- easycert-0.1.0/setup.py +20 -0
easycert-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 lshdevtech
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
easycert-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: easycert
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Simplified X.509 certificate generation and verification toolkit
|
|
5
|
+
Author: lsh101123
|
|
6
|
+
Author-email: lsh101123@163.com
|
|
7
|
+
Classifier: Programming Language :: Python :: 3
|
|
8
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
9
|
+
Classifier: Operating System :: OS Independent
|
|
10
|
+
Requires-Python: >=3.8
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Requires-Dist: cryptography>=42.0.0
|
|
13
|
+
Requires-Dist: pycryptodome>=3.20.0
|
easycert-0.1.0/README.md
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# easycert
|
|
2
|
+
|
|
3
|
+
Simplified X.509 certificate generation and verification toolkit.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- Generate RSA key pairs
|
|
8
|
+
- Create self-signed X.509 certificates
|
|
9
|
+
- Verify certificate signatures
|
|
10
|
+
- Check certificate validity periods
|
|
11
|
+
- Object-oriented verification interface
|
|
12
|
+
- Detailed error handling
|
|
13
|
+
|
|
14
|
+
## Installation
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
pip install .
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Dependencies
|
|
21
|
+
|
|
22
|
+
- cryptography
|
|
23
|
+
- pycryptodome
|
|
24
|
+
|
|
25
|
+
## Usage
|
|
26
|
+
|
|
27
|
+
### Generate self-signed certificate
|
|
28
|
+
|
|
29
|
+
```python
|
|
30
|
+
import easycert
|
|
31
|
+
|
|
32
|
+
# Generate self-signed certificate
|
|
33
|
+
private_key_pem, cert = easycert.generate_self_signed_certificate(
|
|
34
|
+
common_name="example.com",
|
|
35
|
+
valid_days=365
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
# Save to files
|
|
39
|
+
easycert.save_key_pair(private_key_pem, private_key_pem, "private.pem", "public.pem")
|
|
40
|
+
easycert.save_certificate(cert, "cert.crt")
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### Verify certificate
|
|
44
|
+
|
|
45
|
+
```python
|
|
46
|
+
import easycert
|
|
47
|
+
|
|
48
|
+
# Using functional interface
|
|
49
|
+
result = easycert.verify_certificate_from_file("cert.crt")
|
|
50
|
+
easycert.print_verification_result(result)
|
|
51
|
+
|
|
52
|
+
# Using object-oriented interface
|
|
53
|
+
verifier = easycert.Verify("cert.crt")
|
|
54
|
+
verifier.print_info()
|
|
55
|
+
verifier.print_result()
|
|
56
|
+
print(f"Certificate valid: {verifier.is_valid}")
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## API Reference
|
|
60
|
+
|
|
61
|
+
### Generation Module
|
|
62
|
+
|
|
63
|
+
- `generate_key_pair(key_size=2048)`: Generate RSA key pair
|
|
64
|
+
- `generate_certificate(private_key_pem, common_name="localhost", valid_days=365)`: Generate X.509 certificate
|
|
65
|
+
- `generate_self_signed_certificate(common_name="localhost", key_size=2048, valid_days=365)`: Generate self-signed certificate
|
|
66
|
+
- `save_key_pair(private_key_pem, public_key_pem, private_key_path, public_key_path)`: Save key pair to files
|
|
67
|
+
- `load_key_pair(private_key_path, public_key_path)`: Load key pair from files
|
|
68
|
+
- `save_certificate(cert, cert_path)`: Save certificate to file
|
|
69
|
+
- `load_certificate(cert_path)`: Load certificate from file
|
|
70
|
+
|
|
71
|
+
### Verification Module
|
|
72
|
+
|
|
73
|
+
- `verify_certificate_signature(cert)`: Verify certificate signature
|
|
74
|
+
- `verify_certificate_validity(cert, check_time=None)`: Verify certificate validity period
|
|
75
|
+
- `verify_certificate(cert, check_time=None)`: Complete certificate verification
|
|
76
|
+
- `verify_certificate_from_file(cert_path, check_time=None)`: Load and verify certificate from file
|
|
77
|
+
- `print_certificate_info(cert)`: Print certificate information
|
|
78
|
+
- `print_verification_result(result)`: Print verification result
|
|
79
|
+
|
|
80
|
+
### Verify Class
|
|
81
|
+
|
|
82
|
+
- `Verify(cert_input)`: Initialize verifier with certificate file path or object
|
|
83
|
+
- `verify_signature()`: Verify certificate signature
|
|
84
|
+
- `verify_validity(check_time=None)`: Verify certificate validity period
|
|
85
|
+
- `verify(check_time=None)`: Complete certificate verification
|
|
86
|
+
- `print_info()`: Print certificate information
|
|
87
|
+
- `print_result(check_time=None)`: Print verification result
|
|
88
|
+
- Properties: `subject`, `issuer`, `serial_number`, `not_valid_before`, `not_valid_after`, `is_valid`, `certificate`
|
|
89
|
+
|
|
90
|
+
## Exceptions
|
|
91
|
+
|
|
92
|
+
- `CertificateValidationError`: Base exception for certificate validation errors
|
|
93
|
+
- `CertificateSignatureError`: Certificate signature verification failed
|
|
94
|
+
- `CertificateExpiredError`: Certificate has expired
|
|
95
|
+
- `CertificateNotYetValidError`: Certificate is not yet valid
|
|
96
|
+
|
|
97
|
+
## License
|
|
98
|
+
|
|
99
|
+
MIT License
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"""
|
|
2
|
+
easycert - Simplified X.509 certificate generation and verification toolkit
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from .generate import (
|
|
6
|
+
generate_key_pair,
|
|
7
|
+
load_private_key,
|
|
8
|
+
generate_certificate,
|
|
9
|
+
save_key_pair,
|
|
10
|
+
load_key_pair,
|
|
11
|
+
save_certificate,
|
|
12
|
+
load_certificate,
|
|
13
|
+
generate_self_signed_certificate,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
from .verify import (
|
|
17
|
+
CertificateValidationError,
|
|
18
|
+
CertificateSignatureError,
|
|
19
|
+
CertificateExpiredError,
|
|
20
|
+
CertificateNotYetValidError,
|
|
21
|
+
Verify,
|
|
22
|
+
verify_certificate_signature,
|
|
23
|
+
verify_certificate_validity,
|
|
24
|
+
verify_certificate,
|
|
25
|
+
verify_certificate_from_file,
|
|
26
|
+
print_certificate_info,
|
|
27
|
+
print_verification_result,
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
__version__ = "0.1.0"
|
|
31
|
+
|
|
32
|
+
__all__ = [
|
|
33
|
+
# Generation module
|
|
34
|
+
"generate_key_pair",
|
|
35
|
+
"load_private_key",
|
|
36
|
+
"generate_certificate",
|
|
37
|
+
"save_key_pair",
|
|
38
|
+
"load_key_pair",
|
|
39
|
+
"save_certificate",
|
|
40
|
+
"load_certificate",
|
|
41
|
+
"generate_self_signed_certificate",
|
|
42
|
+
# Verification module
|
|
43
|
+
"CertificateValidationError",
|
|
44
|
+
"CertificateSignatureError",
|
|
45
|
+
"CertificateExpiredError",
|
|
46
|
+
"CertificateNotYetValidError",
|
|
47
|
+
"Verify",
|
|
48
|
+
"verify_certificate_signature",
|
|
49
|
+
"verify_certificate_validity",
|
|
50
|
+
"verify_certificate",
|
|
51
|
+
"verify_certificate_from_file",
|
|
52
|
+
"print_certificate_info",
|
|
53
|
+
"print_verification_result",
|
|
54
|
+
]
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
from Crypto.PublicKey import RSA
|
|
2
|
+
from cryptography.hazmat.primitives import serialization
|
|
3
|
+
from cryptography import x509
|
|
4
|
+
from cryptography.x509.oid import NameOID
|
|
5
|
+
from cryptography.hazmat.primitives import hashes
|
|
6
|
+
import datetime
|
|
7
|
+
from typing import Optional, Tuple
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def generate_key_pair(key_size: int = 2048) -> Tuple[bytes, bytes]:
|
|
11
|
+
"""
|
|
12
|
+
Generate RSA key pair
|
|
13
|
+
|
|
14
|
+
Args:
|
|
15
|
+
key_size: Key size in bits, default is 2048
|
|
16
|
+
|
|
17
|
+
Returns:
|
|
18
|
+
(private_key_pem, public_key_pem) Private and public keys in PEM format
|
|
19
|
+
"""
|
|
20
|
+
rsa_key = RSA.generate(key_size)
|
|
21
|
+
private_key_pem = rsa_key.export_key()
|
|
22
|
+
public_key_pem = rsa_key.publickey().export_key()
|
|
23
|
+
return private_key_pem, public_key_pem
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def load_private_key(private_key_pem: bytes, password: Optional[bytes] = None):
|
|
27
|
+
"""
|
|
28
|
+
Load private key in PEM format
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
private_key_pem: Private key in PEM format
|
|
32
|
+
password: Private key password (if any)
|
|
33
|
+
|
|
34
|
+
Returns:
|
|
35
|
+
Loaded private key object
|
|
36
|
+
"""
|
|
37
|
+
return serialization.load_pem_private_key(private_key_pem, password=password)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def generate_certificate(
|
|
41
|
+
private_key_pem: bytes,
|
|
42
|
+
common_name: str = "localhost",
|
|
43
|
+
valid_days: int = 365,
|
|
44
|
+
custom_subject: Optional[x509.Name] = None,
|
|
45
|
+
custom_issuer: Optional[x509.Name] = None
|
|
46
|
+
) -> x509.Certificate:
|
|
47
|
+
"""
|
|
48
|
+
Generate X.509 certificate
|
|
49
|
+
|
|
50
|
+
Args:
|
|
51
|
+
private_key_pem: Private key in PEM format
|
|
52
|
+
common_name: Certificate common name, default is localhost
|
|
53
|
+
valid_days: Certificate validity period in days, default is 365 days
|
|
54
|
+
custom_subject: Custom subject information
|
|
55
|
+
custom_issuer: Custom issuer information
|
|
56
|
+
|
|
57
|
+
Returns:
|
|
58
|
+
Generated X.509 certificate object
|
|
59
|
+
"""
|
|
60
|
+
private_key = load_private_key(private_key_pem)
|
|
61
|
+
|
|
62
|
+
# Use custom subject or default subject
|
|
63
|
+
if custom_subject:
|
|
64
|
+
subject = custom_subject
|
|
65
|
+
else:
|
|
66
|
+
subject = x509.Name([
|
|
67
|
+
x509.NameAttribute(NameOID.COMMON_NAME, common_name),
|
|
68
|
+
])
|
|
69
|
+
|
|
70
|
+
# Use custom issuer or default issuer (self-signed)
|
|
71
|
+
issuer = custom_issuer if custom_issuer else subject
|
|
72
|
+
|
|
73
|
+
# Build certificate
|
|
74
|
+
cert = x509.CertificateBuilder().subject_name(
|
|
75
|
+
subject
|
|
76
|
+
).issuer_name(
|
|
77
|
+
issuer
|
|
78
|
+
).public_key(
|
|
79
|
+
private_key.public_key()
|
|
80
|
+
).serial_number(
|
|
81
|
+
x509.random_serial_number()
|
|
82
|
+
).not_valid_before(
|
|
83
|
+
datetime.datetime.now(datetime.UTC)
|
|
84
|
+
).not_valid_after(
|
|
85
|
+
datetime.datetime.now(datetime.UTC) + datetime.timedelta(days=valid_days)
|
|
86
|
+
).sign(private_key, hashes.SHA256())
|
|
87
|
+
|
|
88
|
+
return cert
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def save_key_pair(private_key_pem: bytes, public_key_pem: bytes,
|
|
92
|
+
private_key_path: str,
|
|
93
|
+
public_key_path: str) -> None:
|
|
94
|
+
"""
|
|
95
|
+
Save key pair to files
|
|
96
|
+
|
|
97
|
+
Args:
|
|
98
|
+
private_key_pem: Private key in PEM format
|
|
99
|
+
public_key_pem: Public key in PEM format
|
|
100
|
+
private_key_path: Private key save path
|
|
101
|
+
public_key_path: Public key save path
|
|
102
|
+
"""
|
|
103
|
+
with open(private_key_path, "wb") as f:
|
|
104
|
+
f.write(private_key_pem)
|
|
105
|
+
with open(public_key_path, "wb") as f:
|
|
106
|
+
f.write(public_key_pem)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def load_key_pair(private_key_path: str,
|
|
110
|
+
public_key_path: str) -> Tuple[bytes, bytes]:
|
|
111
|
+
"""
|
|
112
|
+
Load key pair from files
|
|
113
|
+
|
|
114
|
+
Args:
|
|
115
|
+
private_key_path: Private key file path
|
|
116
|
+
public_key_path: Public key file path
|
|
117
|
+
|
|
118
|
+
Returns:
|
|
119
|
+
(private_key_pem, public_key_pem) Private and public keys in PEM format
|
|
120
|
+
"""
|
|
121
|
+
with open(private_key_path, "rb") as f:
|
|
122
|
+
private_key_pem = f.read()
|
|
123
|
+
with open(public_key_path, "rb") as f:
|
|
124
|
+
public_key_pem = f.read()
|
|
125
|
+
return private_key_pem, public_key_pem
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def save_certificate(cert: x509.Certificate, cert_path: str) -> None:
|
|
129
|
+
"""
|
|
130
|
+
Save certificate to file
|
|
131
|
+
|
|
132
|
+
Args:
|
|
133
|
+
cert: X.509 certificate object
|
|
134
|
+
cert_path: Certificate save path
|
|
135
|
+
"""
|
|
136
|
+
with open(cert_path, "wb") as f:
|
|
137
|
+
f.write(cert.public_bytes(serialization.Encoding.PEM))
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def load_certificate(cert_path: str) -> x509.Certificate:
|
|
141
|
+
"""
|
|
142
|
+
Load certificate from file
|
|
143
|
+
|
|
144
|
+
Args:
|
|
145
|
+
cert_path: Certificate file path
|
|
146
|
+
|
|
147
|
+
Returns:
|
|
148
|
+
X.509 certificate object
|
|
149
|
+
"""
|
|
150
|
+
with open(cert_path, "rb") as f:
|
|
151
|
+
cert = x509.load_pem_x509_certificate(f.read())
|
|
152
|
+
return cert
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def generate_self_signed_certificate(
|
|
156
|
+
common_name: str = "localhost",
|
|
157
|
+
key_size: int = 2048,
|
|
158
|
+
valid_days: int = 365
|
|
159
|
+
) -> Tuple[bytes, x509.Certificate]:
|
|
160
|
+
"""
|
|
161
|
+
Generate self-signed certificate (includes key generation)
|
|
162
|
+
|
|
163
|
+
Args:
|
|
164
|
+
common_name: Certificate common name
|
|
165
|
+
key_size: Key size in bits
|
|
166
|
+
valid_days: Certificate validity period in days
|
|
167
|
+
|
|
168
|
+
Returns:
|
|
169
|
+
(private_key_pem, certificate) Private key and certificate
|
|
170
|
+
"""
|
|
171
|
+
private_key_pem, _ = generate_key_pair(key_size)
|
|
172
|
+
cert = generate_certificate(private_key_pem, common_name, valid_days)
|
|
173
|
+
return private_key_pem, cert
|
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
from cryptography import x509
|
|
2
|
+
from cryptography.hazmat.primitives.asymmetric import padding
|
|
3
|
+
from cryptography.exceptions import InvalidSignature
|
|
4
|
+
import datetime
|
|
5
|
+
from typing import Dict, Any, Optional
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class CertificateValidationError(Exception):
|
|
9
|
+
"""Base exception for certificate validation errors"""
|
|
10
|
+
pass
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class CertificateSignatureError(CertificateValidationError):
|
|
14
|
+
"""Certificate signature verification failed"""
|
|
15
|
+
pass
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class CertificateExpiredError(CertificateValidationError):
|
|
19
|
+
"""Certificate has expired"""
|
|
20
|
+
pass
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class CertificateNotYetValidError(CertificateValidationError):
|
|
24
|
+
"""Certificate is not yet valid"""
|
|
25
|
+
pass
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def verify_certificate_signature(cert: x509.Certificate) -> bool:
|
|
29
|
+
"""
|
|
30
|
+
Verify certificate signature
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
cert: X.509 certificate object
|
|
34
|
+
|
|
35
|
+
Returns:
|
|
36
|
+
Whether the signature is valid
|
|
37
|
+
|
|
38
|
+
Raises:
|
|
39
|
+
CertificateSignatureError: Signature verification failed
|
|
40
|
+
"""
|
|
41
|
+
try:
|
|
42
|
+
cert.public_key().verify(
|
|
43
|
+
cert.signature,
|
|
44
|
+
cert.tbs_certificate_bytes,
|
|
45
|
+
padding.PKCS1v15(),
|
|
46
|
+
cert.signature_hash_algorithm
|
|
47
|
+
)
|
|
48
|
+
return True
|
|
49
|
+
except InvalidSignature as e:
|
|
50
|
+
raise CertificateSignatureError("Certificate signature is invalid") from e
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def verify_certificate_validity(cert: x509.Certificate,
|
|
54
|
+
check_time: Optional[datetime.datetime] = None) -> bool:
|
|
55
|
+
"""
|
|
56
|
+
Verify certificate validity period
|
|
57
|
+
|
|
58
|
+
Args:
|
|
59
|
+
cert: X.509 certificate object
|
|
60
|
+
check_time: Check time, default is current time
|
|
61
|
+
|
|
62
|
+
Returns:
|
|
63
|
+
Whether the certificate is within validity period
|
|
64
|
+
|
|
65
|
+
Raises:
|
|
66
|
+
CertificateNotYetValidError: Certificate is not yet valid
|
|
67
|
+
CertificateExpiredError: Certificate has expired
|
|
68
|
+
"""
|
|
69
|
+
if check_time is None:
|
|
70
|
+
check_time = datetime.datetime.now(datetime.UTC)
|
|
71
|
+
|
|
72
|
+
if cert.not_valid_before_utc > check_time:
|
|
73
|
+
raise CertificateNotYetValidError("Certificate is not yet valid")
|
|
74
|
+
|
|
75
|
+
if cert.not_valid_after_utc < check_time:
|
|
76
|
+
raise CertificateExpiredError("Certificate has expired")
|
|
77
|
+
|
|
78
|
+
return True
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def verify_certificate(cert: x509.Certificate,
|
|
82
|
+
check_time: Optional[datetime.datetime] = None) -> Dict[str, Any]:
|
|
83
|
+
"""
|
|
84
|
+
Complete certificate verification (signature + validity)
|
|
85
|
+
|
|
86
|
+
Args:
|
|
87
|
+
cert: X.509 certificate object
|
|
88
|
+
check_time: Check time, default is current time
|
|
89
|
+
|
|
90
|
+
Returns:
|
|
91
|
+
Verification result dictionary containing valid status and detailed information
|
|
92
|
+
|
|
93
|
+
Raises:
|
|
94
|
+
CertificateValidationError: Certificate verification failed
|
|
95
|
+
"""
|
|
96
|
+
result = {
|
|
97
|
+
"valid": False,
|
|
98
|
+
"signature_valid": False,
|
|
99
|
+
"validity_valid": False,
|
|
100
|
+
"not_valid_before": cert.not_valid_before_utc.isoformat(),
|
|
101
|
+
"not_valid_after": cert.not_valid_after_utc.isoformat(),
|
|
102
|
+
"subject": cert.subject.rfc4514_string(),
|
|
103
|
+
"issuer": cert.issuer.rfc4514_string(),
|
|
104
|
+
"serial_number": str(cert.serial_number),
|
|
105
|
+
"errors": []
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
# Verify signature
|
|
109
|
+
try:
|
|
110
|
+
verify_certificate_signature(cert)
|
|
111
|
+
result["signature_valid"] = True
|
|
112
|
+
except CertificateValidationError as e:
|
|
113
|
+
result["errors"].append(str(e))
|
|
114
|
+
|
|
115
|
+
# Verify validity
|
|
116
|
+
try:
|
|
117
|
+
verify_certificate_validity(cert, check_time)
|
|
118
|
+
result["validity_valid"] = True
|
|
119
|
+
except CertificateValidationError as e:
|
|
120
|
+
result["errors"].append(str(e))
|
|
121
|
+
|
|
122
|
+
# Overall validity
|
|
123
|
+
result["valid"] = result["signature_valid"] and result["validity_valid"]
|
|
124
|
+
|
|
125
|
+
return result
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def verify_certificate_from_file(cert_path: str,
|
|
129
|
+
check_time: Optional[datetime.datetime] = None) -> Dict[str, Any]:
|
|
130
|
+
"""
|
|
131
|
+
Load certificate from file and verify it
|
|
132
|
+
|
|
133
|
+
Args:
|
|
134
|
+
cert_path: Certificate file path
|
|
135
|
+
check_time: Check time, default is current time
|
|
136
|
+
|
|
137
|
+
Returns:
|
|
138
|
+
Verification result dictionary
|
|
139
|
+
"""
|
|
140
|
+
with open(cert_path, "rb") as f:
|
|
141
|
+
cert = x509.load_pem_x509_certificate(f.read())
|
|
142
|
+
return verify_certificate(cert, check_time)
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def print_certificate_info(cert: x509.Certificate) -> None:
|
|
146
|
+
"""
|
|
147
|
+
Print certificate basic information
|
|
148
|
+
|
|
149
|
+
Args:
|
|
150
|
+
cert: X.509 certificate object
|
|
151
|
+
"""
|
|
152
|
+
print(f"Subject: {cert.subject.rfc4514_string()}")
|
|
153
|
+
print(f"Issuer: {cert.issuer.rfc4514_string()}")
|
|
154
|
+
print(f"Serial Number: {cert.serial_number}")
|
|
155
|
+
print(f"Valid From: {cert.not_valid_before_utc}")
|
|
156
|
+
print(f"Valid To: {cert.not_valid_after_utc}")
|
|
157
|
+
print(f"Signature Algorithm: {cert.signature_algorithm_oid._name}")
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def print_verification_result(result: Dict[str, Any]) -> None:
|
|
161
|
+
"""
|
|
162
|
+
Print verification result
|
|
163
|
+
|
|
164
|
+
Args:
|
|
165
|
+
result: Result dictionary returned by verify_certificate
|
|
166
|
+
"""
|
|
167
|
+
if result["valid"]:
|
|
168
|
+
print("Certificate is fully valid (signature and validity are both normal)")
|
|
169
|
+
else:
|
|
170
|
+
print("Certificate is invalid")
|
|
171
|
+
for error in result["errors"]:
|
|
172
|
+
print(f" - {error}")
|
|
173
|
+
|
|
174
|
+
print(f"\nDetailed Information:")
|
|
175
|
+
print(f" Subject: {result['subject']}")
|
|
176
|
+
print(f" Issuer: {result['issuer']}")
|
|
177
|
+
print(f" Serial Number: {result['serial_number']}")
|
|
178
|
+
print(f" Valid From: {result['not_valid_before']}")
|
|
179
|
+
print(f" Valid To: {result['not_valid_after']}")
|
|
180
|
+
print(f" Signature Verification: {'Passed' if result['signature_valid'] else 'Failed'}")
|
|
181
|
+
print(f" Validity Verification: {'Passed' if result['validity_valid'] else 'Failed'}")
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
class Verify:
|
|
185
|
+
"""
|
|
186
|
+
Certificate verification class with object-oriented interface
|
|
187
|
+
|
|
188
|
+
Example:
|
|
189
|
+
verifier = Verify("cert.crt")
|
|
190
|
+
verifier.verify()
|
|
191
|
+
verifier.print_info()
|
|
192
|
+
"""
|
|
193
|
+
|
|
194
|
+
def __init__(self, cert_input: str | x509.Certificate):
|
|
195
|
+
"""
|
|
196
|
+
Initialize verifier with certificate file path or certificate object
|
|
197
|
+
|
|
198
|
+
Args:
|
|
199
|
+
cert_input: Certificate file path or x509.Certificate object
|
|
200
|
+
"""
|
|
201
|
+
if isinstance(cert_input, str):
|
|
202
|
+
with open(cert_input, "rb") as f:
|
|
203
|
+
self.cert = x509.load_pem_x509_certificate(f.read())
|
|
204
|
+
elif isinstance(cert_input, x509.Certificate):
|
|
205
|
+
self.cert = cert_input
|
|
206
|
+
else:
|
|
207
|
+
raise TypeError("cert_input must be a file path (str) or x509.Certificate object")
|
|
208
|
+
|
|
209
|
+
def verify_signature(self) -> bool:
|
|
210
|
+
"""
|
|
211
|
+
Verify certificate signature
|
|
212
|
+
|
|
213
|
+
Returns:
|
|
214
|
+
True if signature is valid
|
|
215
|
+
|
|
216
|
+
Raises:
|
|
217
|
+
CertificateSignatureError: Signature verification failed
|
|
218
|
+
"""
|
|
219
|
+
return verify_certificate_signature(self.cert)
|
|
220
|
+
|
|
221
|
+
def verify_validity(self, check_time: Optional[datetime.datetime] = None) -> bool:
|
|
222
|
+
"""
|
|
223
|
+
Verify certificate validity period
|
|
224
|
+
|
|
225
|
+
Args:
|
|
226
|
+
check_time: Check time, default is current time
|
|
227
|
+
|
|
228
|
+
Returns:
|
|
229
|
+
True if certificate is within validity period
|
|
230
|
+
|
|
231
|
+
Raises:
|
|
232
|
+
CertificateNotYetValidError: Certificate is not yet valid
|
|
233
|
+
CertificateExpiredError: Certificate has expired
|
|
234
|
+
"""
|
|
235
|
+
return verify_certificate_validity(self.cert, check_time)
|
|
236
|
+
|
|
237
|
+
def verify(self, check_time: Optional[datetime.datetime] = None) -> Dict[str, Any]:
|
|
238
|
+
"""
|
|
239
|
+
Complete certificate verification (signature + validity)
|
|
240
|
+
|
|
241
|
+
Args:
|
|
242
|
+
check_time: Check time, default is current time
|
|
243
|
+
|
|
244
|
+
Returns:
|
|
245
|
+
Verification result dictionary containing valid status and detailed information
|
|
246
|
+
"""
|
|
247
|
+
return verify_certificate(self.cert, check_time)
|
|
248
|
+
|
|
249
|
+
def print_info(self) -> None:
|
|
250
|
+
"""Print certificate basic information"""
|
|
251
|
+
print_certificate_info(self.cert)
|
|
252
|
+
|
|
253
|
+
def print_result(self, check_time: Optional[datetime.datetime] = None) -> None:
|
|
254
|
+
"""
|
|
255
|
+
Print verification result
|
|
256
|
+
|
|
257
|
+
Args:
|
|
258
|
+
check_time: Check time, default is current time
|
|
259
|
+
"""
|
|
260
|
+
result = self.verify(check_time)
|
|
261
|
+
print_verification_result(result)
|
|
262
|
+
|
|
263
|
+
@property
|
|
264
|
+
def subject(self) -> str:
|
|
265
|
+
"""Get certificate subject"""
|
|
266
|
+
return self.cert.subject.rfc4514_string()
|
|
267
|
+
|
|
268
|
+
@property
|
|
269
|
+
def issuer(self) -> str:
|
|
270
|
+
"""Get certificate issuer"""
|
|
271
|
+
return self.cert.issuer.rfc4514_string()
|
|
272
|
+
|
|
273
|
+
@property
|
|
274
|
+
def serial_number(self) -> int:
|
|
275
|
+
"""Get certificate serial number"""
|
|
276
|
+
return self.cert.serial_number
|
|
277
|
+
|
|
278
|
+
@property
|
|
279
|
+
def not_valid_before(self) -> datetime.datetime:
|
|
280
|
+
"""Get certificate validity start time"""
|
|
281
|
+
return self.cert.not_valid_before_utc
|
|
282
|
+
|
|
283
|
+
@property
|
|
284
|
+
def not_valid_after(self) -> datetime.datetime:
|
|
285
|
+
"""Get certificate validity end time"""
|
|
286
|
+
return self.cert.not_valid_after_utc
|
|
287
|
+
|
|
288
|
+
@property
|
|
289
|
+
def is_valid(self) -> bool:
|
|
290
|
+
"""Check if certificate is currently valid (signature + validity)"""
|
|
291
|
+
try:
|
|
292
|
+
result = self.verify()
|
|
293
|
+
return result["valid"]
|
|
294
|
+
except CertificateValidationError:
|
|
295
|
+
return False
|
|
296
|
+
|
|
297
|
+
@property
|
|
298
|
+
def certificate(self) -> x509.Certificate:
|
|
299
|
+
"""Get the underlying certificate object"""
|
|
300
|
+
return self.cert
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: easycert
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Simplified X.509 certificate generation and verification toolkit
|
|
5
|
+
Author: lsh101123
|
|
6
|
+
Author-email: lsh101123@163.com
|
|
7
|
+
Classifier: Programming Language :: Python :: 3
|
|
8
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
9
|
+
Classifier: Operating System :: OS Independent
|
|
10
|
+
Requires-Python: >=3.8
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Requires-Dist: cryptography>=42.0.0
|
|
13
|
+
Requires-Dist: pycryptodome>=3.20.0
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
setup.py
|
|
4
|
+
easycert/__init__.py
|
|
5
|
+
easycert/generate.py
|
|
6
|
+
easycert/verify.py
|
|
7
|
+
easycert.egg-info/PKG-INFO
|
|
8
|
+
easycert.egg-info/SOURCES.txt
|
|
9
|
+
easycert.egg-info/dependency_links.txt
|
|
10
|
+
easycert.egg-info/requires.txt
|
|
11
|
+
easycert.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
easycert
|
easycert-0.1.0/setup.cfg
ADDED
easycert-0.1.0/setup.py
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
from setuptools import setup, find_packages
|
|
2
|
+
|
|
3
|
+
setup(
|
|
4
|
+
name="easycert",
|
|
5
|
+
version="0.1.0",
|
|
6
|
+
description="Simplified X.509 certificate generation and verification toolkit",
|
|
7
|
+
author="lsh101123",
|
|
8
|
+
author_email="lsh101123@163.com",
|
|
9
|
+
packages=find_packages(),
|
|
10
|
+
classifiers=[
|
|
11
|
+
"Programming Language :: Python :: 3",
|
|
12
|
+
"License :: OSI Approved :: MIT License",
|
|
13
|
+
"Operating System :: OS Independent",
|
|
14
|
+
],
|
|
15
|
+
python_requires=">=3.8",
|
|
16
|
+
install_requires=[
|
|
17
|
+
"cryptography>=42.0.0",
|
|
18
|
+
"pycryptodome>=3.20.0"
|
|
19
|
+
]
|
|
20
|
+
)
|