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