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