]> Cypherpunks.ru repositories - pygost.git/blob - pygost/asn1schemas/cert-selfsigned-example.py
cert-selfsigned-example.py creates more correct CA
[pygost.git] / pygost / asn1schemas / cert-selfsigned-example.py
1 #!/usr/bin/env python3
2 """Create example self-signed X.509 certificate
3 """
4
5 from argparse import ArgumentParser
6 from base64 import standard_b64decode
7 from base64 import standard_b64encode
8 from datetime import datetime
9 from datetime import timedelta
10 from os import urandom
11 from textwrap import fill
12
13 from pyderasn import Any
14 from pyderasn import BitString
15 from pyderasn import Boolean
16 from pyderasn import IA5String
17 from pyderasn import Integer
18 from pyderasn import OctetString
19 from pyderasn import PrintableString
20 from pyderasn import UTCTime
21
22 from pygost.asn1schemas.oids import id_at_commonName
23 from pygost.asn1schemas.oids import id_ce_authorityKeyIdentifier
24 from pygost.asn1schemas.oids import id_ce_basicConstraints
25 from pygost.asn1schemas.oids import id_ce_keyUsage
26 from pygost.asn1schemas.oids import id_ce_subjectAltName
27 from pygost.asn1schemas.oids import id_ce_subjectKeyIdentifier
28 from pygost.asn1schemas.oids import id_tc26_gost3410_2012_256
29 from pygost.asn1schemas.oids import id_tc26_gost3410_2012_256_paramSetA
30 from pygost.asn1schemas.oids import id_tc26_gost3410_2012_256_paramSetB
31 from pygost.asn1schemas.oids import id_tc26_gost3410_2012_256_paramSetC
32 from pygost.asn1schemas.oids import id_tc26_gost3410_2012_256_paramSetD
33 from pygost.asn1schemas.oids import id_tc26_gost3410_2012_512
34 from pygost.asn1schemas.oids import id_tc26_gost3410_2012_512_paramSetA
35 from pygost.asn1schemas.oids import id_tc26_gost3410_2012_512_paramSetB
36 from pygost.asn1schemas.oids import id_tc26_gost3410_2012_512_paramSetC
37 from pygost.asn1schemas.oids import id_tc26_signwithdigest_gost3410_2012_256
38 from pygost.asn1schemas.oids import id_tc26_signwithdigest_gost3410_2012_512
39 from pygost.asn1schemas.prvkey import PrivateKey
40 from pygost.asn1schemas.prvkey import PrivateKeyAlgorithmIdentifier
41 from pygost.asn1schemas.prvkey import PrivateKeyInfo
42 from pygost.asn1schemas.x509 import AlgorithmIdentifier
43 from pygost.asn1schemas.x509 import AttributeType
44 from pygost.asn1schemas.x509 import AttributeTypeAndValue
45 from pygost.asn1schemas.x509 import AttributeValue
46 from pygost.asn1schemas.x509 import AuthorityKeyIdentifier
47 from pygost.asn1schemas.x509 import BasicConstraints
48 from pygost.asn1schemas.x509 import Certificate
49 from pygost.asn1schemas.x509 import CertificateSerialNumber
50 from pygost.asn1schemas.x509 import Extension
51 from pygost.asn1schemas.x509 import Extensions
52 from pygost.asn1schemas.x509 import GeneralName
53 from pygost.asn1schemas.x509 import GostR34102012PublicKeyParameters
54 from pygost.asn1schemas.x509 import KeyIdentifier
55 from pygost.asn1schemas.x509 import KeyUsage
56 from pygost.asn1schemas.x509 import Name
57 from pygost.asn1schemas.x509 import RDNSequence
58 from pygost.asn1schemas.x509 import RelativeDistinguishedName
59 from pygost.asn1schemas.x509 import SubjectAltName
60 from pygost.asn1schemas.x509 import SubjectKeyIdentifier
61 from pygost.asn1schemas.x509 import SubjectPublicKeyInfo
62 from pygost.asn1schemas.x509 import TBSCertificate
63 from pygost.asn1schemas.x509 import Time
64 from pygost.asn1schemas.x509 import Validity
65 from pygost.asn1schemas.x509 import Version
66 from pygost.gost3410 import CURVES
67 from pygost.gost3410 import prv_unmarshal
68 from pygost.gost3410 import pub_marshal
69 from pygost.gost3410 import public_key
70 from pygost.gost3410 import sign
71 from pygost.gost34112012256 import GOST34112012256
72 from pygost.gost34112012512 import GOST34112012512
73
74 parser = ArgumentParser(description="Self-signed X.509 certificate creator")
75 parser.add_argument(
76     "--ca",
77     action="store_true",
78     help="Enable BasicConstraints.cA",
79 )
80 parser.add_argument(
81     "--cn",
82     required=True,
83     help="Subject's CommonName",
84 )
85 parser.add_argument(
86     "--ai",
87     required=True,
88     help="Signing algorithm: {256[ABCD],512[ABC]}",
89 )
90 parser.add_argument(
91     "--issue-with",
92     help="Path to PEM with CA to issue the child",
93 )
94 args = parser.parse_args()
95 AIs = {
96     "256A": {
97         "publicKeyParamSet": id_tc26_gost3410_2012_256_paramSetA,
98         "key_algorithm": id_tc26_gost3410_2012_256,
99         "prv_len": 32,
100         "curve": CURVES["id-tc26-gost-3410-2012-256-paramSetA"],
101         "sign_algorithm": id_tc26_signwithdigest_gost3410_2012_256,
102         "hasher": GOST34112012256,
103     },
104     "256B": {
105         "publicKeyParamSet": id_tc26_gost3410_2012_256_paramSetB,
106         "key_algorithm": id_tc26_gost3410_2012_256,
107         "prv_len": 32,
108         "curve": CURVES["id-tc26-gost-3410-2012-256-paramSetB"],
109         "sign_algorithm": id_tc26_signwithdigest_gost3410_2012_256,
110         "hasher": GOST34112012256,
111     },
112     "256C": {
113         "publicKeyParamSet": id_tc26_gost3410_2012_256_paramSetC,
114         "key_algorithm": id_tc26_gost3410_2012_256,
115         "prv_len": 32,
116         "curve": CURVES["id-tc26-gost-3410-2012-256-paramSetC"],
117         "sign_algorithm": id_tc26_signwithdigest_gost3410_2012_256,
118         "hasher": GOST34112012256,
119     },
120     "256D": {
121         "publicKeyParamSet": id_tc26_gost3410_2012_256_paramSetD,
122         "key_algorithm": id_tc26_gost3410_2012_256,
123         "prv_len": 32,
124         "curve": CURVES["id-tc26-gost-3410-2012-256-paramSetD"],
125         "sign_algorithm": id_tc26_signwithdigest_gost3410_2012_256,
126         "hasher": GOST34112012256,
127     },
128     "512A": {
129         "publicKeyParamSet": id_tc26_gost3410_2012_512_paramSetA,
130         "key_algorithm": id_tc26_gost3410_2012_512,
131         "prv_len": 64,
132         "curve": CURVES["id-tc26-gost-3410-12-512-paramSetA"],
133         "sign_algorithm": id_tc26_signwithdigest_gost3410_2012_512,
134         "hasher": GOST34112012512,
135     },
136     "512B": {
137         "publicKeyParamSet": id_tc26_gost3410_2012_512_paramSetB,
138         "key_algorithm": id_tc26_gost3410_2012_512,
139         "prv_len": 64,
140         "curve": CURVES["id-tc26-gost-3410-12-512-paramSetB"],
141         "sign_algorithm": id_tc26_signwithdigest_gost3410_2012_512,
142         "hasher": GOST34112012512,
143     },
144     "512C": {
145         "publicKeyParamSet": id_tc26_gost3410_2012_512_paramSetC,
146         "key_algorithm": id_tc26_gost3410_2012_512,
147         "prv_len": 64,
148         "curve": CURVES["id-tc26-gost-3410-2012-512-paramSetC"],
149         "sign_algorithm": id_tc26_signwithdigest_gost3410_2012_512,
150         "hasher": GOST34112012512,
151     },
152 }
153 ai = AIs[args.ai]
154
155 ca_prv = None
156 ca_cert = None
157 ca_subj = None
158 ca_ai = None
159 if args.issue_with is not None:
160     with open(args.issue_with, "rb") as fd:
161         lines = fd.read().decode("ascii").split("-----")
162     idx = lines.index("BEGIN PRIVATE KEY")
163     if idx == -1:
164         raise ValueError("PEM has no PRIVATE KEY")
165     prv_raw = standard_b64decode(lines[idx + 1])
166     idx = lines.index("BEGIN CERTIFICATE")
167     if idx == -1:
168         raise ValueError("PEM has no CERTIFICATE")
169     cert_raw = standard_b64decode(lines[idx + 1])
170     pki = PrivateKeyInfo().decod(prv_raw)
171     ca_prv = prv_unmarshal(bytes(OctetString().decod(bytes(pki["privateKey"]))))
172     ca_cert = Certificate().decod(cert_raw)
173     tbs = ca_cert["tbsCertificate"]
174     ca_subj = tbs["subject"]
175     curve_oid = GostR34102012PublicKeyParameters().decod(bytes(
176         tbs["subjectPublicKeyInfo"]["algorithm"]["parameters"]
177     ))["publicKeyParamSet"]
178     ca_ai = next(iter([
179         params for params in AIs.values()
180         if params["publicKeyParamSet"] == curve_oid
181     ]))
182
183
184 def pem(obj):
185     return fill(standard_b64encode(obj.encode()).decode("ascii"), 64)
186
187
188 key_params = GostR34102012PublicKeyParameters((
189     ("publicKeyParamSet", ai["publicKeyParamSet"]),
190 ))
191
192 prv_raw = urandom(ai["prv_len"])
193 print("-----BEGIN PRIVATE KEY-----")
194 print(pem(PrivateKeyInfo((
195     ("version", Integer(0)),
196     ("privateKeyAlgorithm", PrivateKeyAlgorithmIdentifier((
197         ("algorithm", ai["key_algorithm"]),
198         ("parameters", Any(key_params)),
199     ))),
200     ("privateKey", PrivateKey(OctetString(prv_raw).encode())),
201 ))))
202 print("-----END PRIVATE KEY-----")
203
204 prv = prv_unmarshal(prv_raw)
205 curve = ai["curve"]
206 pub_raw = pub_marshal(public_key(curve, prv))
207 subj = Name(("rdnSequence", RDNSequence([
208     RelativeDistinguishedName((
209         AttributeTypeAndValue((
210             ("type", AttributeType(id_at_commonName)),
211             ("value", AttributeValue(PrintableString(args.cn))),
212         )),
213     ))
214 ])))
215 not_before = datetime.utcnow()
216 not_after = not_before + timedelta(days=365 * (10 if args.ca else 1))
217 ai_sign = AlgorithmIdentifier((
218     ("algorithm", (ai if ca_ai is None else ca_ai)["sign_algorithm"]),
219 ))
220 exts = [
221     Extension((
222         ("extnID", id_ce_subjectKeyIdentifier),
223
224         ("extnValue", OctetString(
225             SubjectKeyIdentifier(GOST34112012256(pub_raw).digest()[:20]).encode()
226         )),
227     )),
228     Extension((
229         ("extnID", id_ce_subjectAltName),
230         ("extnValue", OctetString(
231             SubjectAltName((
232                 GeneralName(("dNSName", IA5String(args.cn))),
233             )).encode()
234         )),
235     )),
236 ]
237 if args.ca:
238     exts.append(Extension((
239         ("extnID", id_ce_basicConstraints),
240         ("extnValue", OctetString(BasicConstraints((("cA", Boolean(True)),)).encode())),
241     )))
242     exts.append(Extension((
243         ("extnID", id_ce_keyUsage),
244         ("extnValue", OctetString(KeyUsage(("keyCertSign",)).encode())),
245     )))
246 if ca_ai is not None:
247     caKeyId = [
248         bytes(SubjectKeyIdentifier().decod(bytes(ext["extnValue"])))
249         for ext in ca_cert["tbsCertificate"]["extensions"]
250         if ext["extnID"] == id_ce_subjectKeyIdentifier
251     ][0]
252     exts.append(Extension((
253         ("extnID", id_ce_authorityKeyIdentifier),
254         ("extnValue", OctetString(AuthorityKeyIdentifier((
255             ("keyIdentifier", KeyIdentifier(caKeyId)),
256         )).encode())),
257     )))
258
259 tbs = TBSCertificate((
260     ("version", Version("v3")),
261     ("serialNumber", CertificateSerialNumber(12345)),
262     ("signature", ai_sign),
263     ("issuer", subj if ca_ai is None else ca_subj),
264     ("validity", Validity((
265         ("notBefore", Time(("utcTime", UTCTime(not_before)))),
266         ("notAfter", Time(("utcTime", UTCTime(not_after)))),
267     ))),
268     ("subject", subj),
269     ("subjectPublicKeyInfo", SubjectPublicKeyInfo((
270         ("algorithm", AlgorithmIdentifier((
271             ("algorithm", ai["key_algorithm"]),
272             ("parameters", Any(key_params)),
273         ))),
274         ("subjectPublicKey", BitString(OctetString(pub_raw).encode())),
275     ))),
276     ("extensions", Extensions(exts)),
277 ))
278 cert = Certificate((
279     ("tbsCertificate", tbs),
280     ("signatureAlgorithm", ai_sign),
281     ("signatureValue", BitString(
282         sign(curve, prv, ai["hasher"](tbs.encode()).digest()[::-1])
283         if ca_ai is None else
284         sign(ca_ai["curve"], ca_prv, ca_ai["hasher"](tbs.encode()).digest()[::-1])
285     )),
286 ))
287 print("-----BEGIN CERTIFICATE-----")
288 print(pem(cert))
289 print("-----END CERTIFICATE-----")