]> Cypherpunks.ru repositories - pygost.git/blobdiff - pygost/asn1schemas/cert-selfsigned-example.py
More flexible certificate creator
[pygost.git] / pygost / asn1schemas / cert-selfsigned-example.py
index 94b4b34cf4ba318dc5bf75a0ac427b12d58dab65..68876964476077b130fc449d2f3e345180884d83 100755 (executable)
@@ -8,6 +8,7 @@ from base64 import standard_b64encode
 from datetime import datetime
 from datetime import timedelta
 from os import urandom
+from sys import stdout
 from textwrap import fill
 
 from pyderasn import Any
@@ -20,7 +21,10 @@ from pyderasn import PrintableString
 from pyderasn import UTCTime
 
 from pygost.asn1schemas.oids import id_at_commonName
+from pygost.asn1schemas.oids import id_at_countryName
+from pygost.asn1schemas.oids import id_ce_authorityKeyIdentifier
 from pygost.asn1schemas.oids import id_ce_basicConstraints
+from pygost.asn1schemas.oids import id_ce_keyUsage
 from pygost.asn1schemas.oids import id_ce_subjectAltName
 from pygost.asn1schemas.oids import id_ce_subjectKeyIdentifier
 from pygost.asn1schemas.oids import id_tc26_gost3410_2012_256
@@ -41,6 +45,7 @@ from pygost.asn1schemas.x509 import AlgorithmIdentifier
 from pygost.asn1schemas.x509 import AttributeType
 from pygost.asn1schemas.x509 import AttributeTypeAndValue
 from pygost.asn1schemas.x509 import AttributeValue
+from pygost.asn1schemas.x509 import AuthorityKeyIdentifier
 from pygost.asn1schemas.x509 import BasicConstraints
 from pygost.asn1schemas.x509 import Certificate
 from pygost.asn1schemas.x509 import CertificateSerialNumber
@@ -48,6 +53,8 @@ from pygost.asn1schemas.x509 import Extension
 from pygost.asn1schemas.x509 import Extensions
 from pygost.asn1schemas.x509 import GeneralName
 from pygost.asn1schemas.x509 import GostR34102012PublicKeyParameters
+from pygost.asn1schemas.x509 import KeyIdentifier
+from pygost.asn1schemas.x509 import KeyUsage
 from pygost.asn1schemas.x509 import Name
 from pygost.asn1schemas.x509 import RDNSequence
 from pygost.asn1schemas.x509 import RelativeDistinguishedName
@@ -65,6 +72,7 @@ from pygost.gost3410 import public_key
 from pygost.gost3410 import sign
 from pygost.gost34112012256 import GOST34112012256
 from pygost.gost34112012512 import GOST34112012512
+from pygost.utils import bytes2long
 
 parser = ArgumentParser(description="Self-signed X.509 certificate creator")
 parser.add_argument(
@@ -77,6 +85,14 @@ parser.add_argument(
     required=True,
     help="Subject's CommonName",
 )
+parser.add_argument(
+    "--country",
+    help="Subject's Country",
+)
+parser.add_argument(
+    "--serial",
+    help="Serial number",
+)
 parser.add_argument(
     "--ai",
     required=True,
@@ -86,6 +102,14 @@ parser.add_argument(
     "--issue-with",
     help="Path to PEM with CA to issue the child",
 )
+parser.add_argument(
+    "--out-key",
+    help="Path to PEM with the resulting key",
+)
+parser.add_argument(
+    "--out-cert",
+    help="Path to PEM with the resulting certificate",
+)
 args = parser.parse_args()
 AIs = {
     "256A": {
@@ -148,6 +172,7 @@ AIs = {
 ai = AIs[args.ai]
 
 ca_prv = None
+ca_cert = None
 ca_subj = None
 ca_ai = None
 if args.issue_with is not None:
@@ -163,7 +188,8 @@ if args.issue_with is not None:
     cert_raw = standard_b64decode(lines[idx + 1])
     pki = PrivateKeyInfo().decod(prv_raw)
     ca_prv = prv_unmarshal(bytes(OctetString().decod(bytes(pki["privateKey"]))))
-    tbs = Certificate().decod(cert_raw)["tbsCertificate"]
+    ca_cert = Certificate().decod(cert_raw)
+    tbs = ca_cert["tbsCertificate"]
     ca_subj = tbs["subject"]
     curve_oid = GostR34102012PublicKeyParameters().decod(bytes(
         tbs["subjectPublicKeyInfo"]["algorithm"]["parameters"]
@@ -183,7 +209,8 @@ key_params = GostR34102012PublicKeyParameters((
 ))
 
 prv_raw = urandom(ai["prv_len"])
-print("-----BEGIN PRIVATE KEY-----")
+out = stdout if args.out_key is None else open(args.out_key, "w")
+print("-----BEGIN PRIVATE KEY-----", file=out)
 print(pem(PrivateKeyInfo((
     ("version", Integer(0)),
     ("privateKeyAlgorithm", PrivateKeyAlgorithmIdentifier((
@@ -191,50 +218,83 @@ print(pem(PrivateKeyInfo((
         ("parameters", Any(key_params)),
     ))),
     ("privateKey", PrivateKey(OctetString(prv_raw).encode())),
-))))
-print("-----END PRIVATE KEY-----")
+))), file=out)
+print("-----END PRIVATE KEY-----", file=out)
 
 prv = prv_unmarshal(prv_raw)
 curve = ai["curve"]
 pub_raw = pub_marshal(public_key(curve, prv))
-subj = Name(("rdnSequence", RDNSequence([
-    RelativeDistinguishedName((
+rdn = [RelativeDistinguishedName((
+    AttributeTypeAndValue((
+        ("type", AttributeType(id_at_commonName)),
+        ("value", AttributeValue(PrintableString(args.cn))),
+    )),
+))]
+if args.country:
+    rdn.append(RelativeDistinguishedName((
         AttributeTypeAndValue((
-            ("type", AttributeType(id_at_commonName)),
-            ("value", AttributeValue(PrintableString(args.cn))),
+            ("type", AttributeType(id_at_countryName)),
+            ("value", AttributeValue(PrintableString(args.country))),
         )),
-    ))
-])))
+    )))
+subj = Name(("rdnSequence", RDNSequence(rdn)))
 not_before = datetime.utcnow()
-not_after = not_before + timedelta(days=365)
+not_after = not_before + timedelta(days=365 * (10 if args.ca else 1))
 ai_sign = AlgorithmIdentifier((
     ("algorithm", (ai if ca_ai is None else ca_ai)["sign_algorithm"]),
 ))
 exts = [
     Extension((
         ("extnID", id_ce_subjectKeyIdentifier),
-
         ("extnValue", OctetString(
             SubjectKeyIdentifier(GOST34112012256(pub_raw).digest()[:20]).encode()
         )),
     )),
     Extension((
+        ("extnID", id_ce_keyUsage),
+        ("critical", Boolean(True)),
+        ("extnValue", OctetString(KeyUsage(
+            ("keyCertSign" if args.ca else "digitalSignature",),
+        ).encode())),
+    )),
+]
+if args.ca:
+    exts.append(Extension((
+        ("extnID", id_ce_basicConstraints),
+        ("critical", Boolean(True)),
+        ("extnValue", OctetString(BasicConstraints((
+            ("cA", Boolean(True)),
+        )).encode())),
+    )))
+else:
+    exts.append(Extension((
         ("extnID", id_ce_subjectAltName),
         ("extnValue", OctetString(
             SubjectAltName((
                 GeneralName(("dNSName", IA5String(args.cn))),
             )).encode()
         )),
-    )),
-]
-if args.ca:
+    )))
+if ca_ai is not None:
+    caKeyId = [
+        bytes(SubjectKeyIdentifier().decod(bytes(ext["extnValue"])))
+        for ext in ca_cert["tbsCertificate"]["extensions"]
+        if ext["extnID"] == id_ce_subjectKeyIdentifier
+    ][0]
     exts.append(Extension((
-        ("extnID", id_ce_basicConstraints),
-        ("extnValue", OctetString(BasicConstraints((("cA", Boolean(True)),)).encode())),
+        ("extnID", id_ce_authorityKeyIdentifier),
+        ("extnValue", OctetString(AuthorityKeyIdentifier((
+            ("keyIdentifier", KeyIdentifier(caKeyId)),
+        )).encode())),
     )))
+
+serial = (
+    bytes2long(GOST34112012256(urandom(16)).digest()[:20])
+    if args.serial is None else int(args.serial)
+)
 tbs = TBSCertificate((
     ("version", Version("v3")),
-    ("serialNumber", CertificateSerialNumber(12345)),
+    ("serialNumber", CertificateSerialNumber(serial)),
     ("signature", ai_sign),
     ("issuer", subj if ca_ai is None else ca_subj),
     ("validity", Validity((
@@ -260,6 +320,7 @@ cert = Certificate((
         sign(ca_ai["curve"], ca_prv, ca_ai["hasher"](tbs.encode()).digest()[::-1])
     )),
 ))
-print("-----BEGIN CERTIFICATE-----")
-print(pem(cert))
-print("-----END CERTIFICATE-----")
+out = stdout if args.out_cert is None else open(args.out_cert, "w")
+print("-----BEGIN CERTIFICATE-----", file=out)
+print(pem(cert), file=out)
+print("-----END CERTIFICATE-----", file=out)