]> Cypherpunks.ru repositories - pygost.git/blob - pygost/asn1schemas/cert-selfsigned-example.py
More flexible certificate creator
[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     "--out-key",
107     help="Path to PEM with the resulting key",
108 )
109 parser.add_argument(
110     "--out-cert",
111     help="Path to PEM with the resulting certificate",
112 )
113 args = parser.parse_args()
114 AIs = {
115     "256A": {
116         "publicKeyParamSet": id_tc26_gost3410_2012_256_paramSetA,
117         "key_algorithm": id_tc26_gost3410_2012_256,
118         "prv_len": 32,
119         "curve": CURVES["id-tc26-gost-3410-2012-256-paramSetA"],
120         "sign_algorithm": id_tc26_signwithdigest_gost3410_2012_256,
121         "hasher": GOST34112012256,
122     },
123     "256B": {
124         "publicKeyParamSet": id_tc26_gost3410_2012_256_paramSetB,
125         "key_algorithm": id_tc26_gost3410_2012_256,
126         "prv_len": 32,
127         "curve": CURVES["id-tc26-gost-3410-2012-256-paramSetB"],
128         "sign_algorithm": id_tc26_signwithdigest_gost3410_2012_256,
129         "hasher": GOST34112012256,
130     },
131     "256C": {
132         "publicKeyParamSet": id_tc26_gost3410_2012_256_paramSetC,
133         "key_algorithm": id_tc26_gost3410_2012_256,
134         "prv_len": 32,
135         "curve": CURVES["id-tc26-gost-3410-2012-256-paramSetC"],
136         "sign_algorithm": id_tc26_signwithdigest_gost3410_2012_256,
137         "hasher": GOST34112012256,
138     },
139     "256D": {
140         "publicKeyParamSet": id_tc26_gost3410_2012_256_paramSetD,
141         "key_algorithm": id_tc26_gost3410_2012_256,
142         "prv_len": 32,
143         "curve": CURVES["id-tc26-gost-3410-2012-256-paramSetD"],
144         "sign_algorithm": id_tc26_signwithdigest_gost3410_2012_256,
145         "hasher": GOST34112012256,
146     },
147     "512A": {
148         "publicKeyParamSet": id_tc26_gost3410_2012_512_paramSetA,
149         "key_algorithm": id_tc26_gost3410_2012_512,
150         "prv_len": 64,
151         "curve": CURVES["id-tc26-gost-3410-12-512-paramSetA"],
152         "sign_algorithm": id_tc26_signwithdigest_gost3410_2012_512,
153         "hasher": GOST34112012512,
154     },
155     "512B": {
156         "publicKeyParamSet": id_tc26_gost3410_2012_512_paramSetB,
157         "key_algorithm": id_tc26_gost3410_2012_512,
158         "prv_len": 64,
159         "curve": CURVES["id-tc26-gost-3410-12-512-paramSetB"],
160         "sign_algorithm": id_tc26_signwithdigest_gost3410_2012_512,
161         "hasher": GOST34112012512,
162     },
163     "512C": {
164         "publicKeyParamSet": id_tc26_gost3410_2012_512_paramSetC,
165         "key_algorithm": id_tc26_gost3410_2012_512,
166         "prv_len": 64,
167         "curve": CURVES["id-tc26-gost-3410-2012-512-paramSetC"],
168         "sign_algorithm": id_tc26_signwithdigest_gost3410_2012_512,
169         "hasher": GOST34112012512,
170     },
171 }
172 ai = AIs[args.ai]
173
174 ca_prv = None
175 ca_cert = None
176 ca_subj = None
177 ca_ai = None
178 if args.issue_with is not None:
179     with open(args.issue_with, "rb") as fd:
180         lines = fd.read().decode("ascii").split("-----")
181     idx = lines.index("BEGIN PRIVATE KEY")
182     if idx == -1:
183         raise ValueError("PEM has no PRIVATE KEY")
184     prv_raw = standard_b64decode(lines[idx + 1])
185     idx = lines.index("BEGIN CERTIFICATE")
186     if idx == -1:
187         raise ValueError("PEM has no CERTIFICATE")
188     cert_raw = standard_b64decode(lines[idx + 1])
189     pki = PrivateKeyInfo().decod(prv_raw)
190     ca_prv = prv_unmarshal(bytes(OctetString().decod(bytes(pki["privateKey"]))))
191     ca_cert = Certificate().decod(cert_raw)
192     tbs = ca_cert["tbsCertificate"]
193     ca_subj = tbs["subject"]
194     curve_oid = GostR34102012PublicKeyParameters().decod(bytes(
195         tbs["subjectPublicKeyInfo"]["algorithm"]["parameters"]
196     ))["publicKeyParamSet"]
197     ca_ai = next(iter([
198         params for params in AIs.values()
199         if params["publicKeyParamSet"] == curve_oid
200     ]))
201
202
203 def pem(obj):
204     return fill(standard_b64encode(obj.encode()).decode("ascii"), 64)
205
206
207 key_params = GostR34102012PublicKeyParameters((
208     ("publicKeyParamSet", ai["publicKeyParamSet"]),
209 ))
210
211 prv_raw = urandom(ai["prv_len"])
212 out = stdout if args.out_key is None else open(args.out_key, "w")
213 print("-----BEGIN PRIVATE KEY-----", file=out)
214 print(pem(PrivateKeyInfo((
215     ("version", Integer(0)),
216     ("privateKeyAlgorithm", PrivateKeyAlgorithmIdentifier((
217         ("algorithm", ai["key_algorithm"]),
218         ("parameters", Any(key_params)),
219     ))),
220     ("privateKey", PrivateKey(OctetString(prv_raw).encode())),
221 ))), file=out)
222 print("-----END PRIVATE KEY-----", file=out)
223
224 prv = prv_unmarshal(prv_raw)
225 curve = ai["curve"]
226 pub_raw = pub_marshal(public_key(curve, prv))
227 rdn = [RelativeDistinguishedName((
228     AttributeTypeAndValue((
229         ("type", AttributeType(id_at_commonName)),
230         ("value", AttributeValue(PrintableString(args.cn))),
231     )),
232 ))]
233 if args.country:
234     rdn.append(RelativeDistinguishedName((
235         AttributeTypeAndValue((
236             ("type", AttributeType(id_at_countryName)),
237             ("value", AttributeValue(PrintableString(args.country))),
238         )),
239     )))
240 subj = Name(("rdnSequence", RDNSequence(rdn)))
241 not_before = datetime.utcnow()
242 not_after = not_before + timedelta(days=365 * (10 if args.ca else 1))
243 ai_sign = AlgorithmIdentifier((
244     ("algorithm", (ai if ca_ai is None else ca_ai)["sign_algorithm"]),
245 ))
246 exts = [
247     Extension((
248         ("extnID", id_ce_subjectKeyIdentifier),
249         ("extnValue", OctetString(
250             SubjectKeyIdentifier(GOST34112012256(pub_raw).digest()[:20]).encode()
251         )),
252     )),
253     Extension((
254         ("extnID", id_ce_keyUsage),
255         ("critical", Boolean(True)),
256         ("extnValue", OctetString(KeyUsage(
257             ("keyCertSign" if args.ca else "digitalSignature",),
258         ).encode())),
259     )),
260 ]
261 if args.ca:
262     exts.append(Extension((
263         ("extnID", id_ce_basicConstraints),
264         ("critical", Boolean(True)),
265         ("extnValue", OctetString(BasicConstraints((
266             ("cA", Boolean(True)),
267         )).encode())),
268     )))
269 else:
270     exts.append(Extension((
271         ("extnID", id_ce_subjectAltName),
272         ("extnValue", OctetString(
273             SubjectAltName((
274                 GeneralName(("dNSName", IA5String(args.cn))),
275             )).encode()
276         )),
277     )))
278 if ca_ai is not None:
279     caKeyId = [
280         bytes(SubjectKeyIdentifier().decod(bytes(ext["extnValue"])))
281         for ext in ca_cert["tbsCertificate"]["extensions"]
282         if ext["extnID"] == id_ce_subjectKeyIdentifier
283     ][0]
284     exts.append(Extension((
285         ("extnID", id_ce_authorityKeyIdentifier),
286         ("extnValue", OctetString(AuthorityKeyIdentifier((
287             ("keyIdentifier", KeyIdentifier(caKeyId)),
288         )).encode())),
289     )))
290
291 serial = (
292     bytes2long(GOST34112012256(urandom(16)).digest()[:20])
293     if args.serial is None else int(args.serial)
294 )
295 tbs = TBSCertificate((
296     ("version", Version("v3")),
297     ("serialNumber", CertificateSerialNumber(serial)),
298     ("signature", ai_sign),
299     ("issuer", subj if ca_ai is None else ca_subj),
300     ("validity", Validity((
301         ("notBefore", Time(("utcTime", UTCTime(not_before)))),
302         ("notAfter", Time(("utcTime", UTCTime(not_after)))),
303     ))),
304     ("subject", subj),
305     ("subjectPublicKeyInfo", SubjectPublicKeyInfo((
306         ("algorithm", AlgorithmIdentifier((
307             ("algorithm", ai["key_algorithm"]),
308             ("parameters", Any(key_params)),
309         ))),
310         ("subjectPublicKey", BitString(OctetString(pub_raw).encode())),
311     ))),
312     ("extensions", Extensions(exts)),
313 ))
314 cert = Certificate((
315     ("tbsCertificate", tbs),
316     ("signatureAlgorithm", ai_sign),
317     ("signatureValue", BitString(
318         sign(curve, prv, ai["hasher"](tbs.encode()).digest()[::-1])
319         if ca_ai is None else
320         sign(ca_ai["curve"], ca_prv, ca_ai["hasher"](tbs.encode()).digest()[::-1])
321     )),
322 ))
323 out = stdout if args.out_cert is None else open(args.out_cert, "w")
324 print("-----BEGIN CERTIFICATE-----", file=out)
325 print(pem(cert), file=out)
326 print("-----END CERTIFICATE-----", file=out)