]> Cypherpunks.ru repositories - pygost.git/blobdiff - pygost/asn1schemas/cert-selfsigned-example.py
cert-selfsigned-example.py creates more correct CA
[pygost.git] / pygost / asn1schemas / cert-selfsigned-example.py
old mode 100644 (file)
new mode 100755 (executable)
index 89da091..df832ba
@@ -1,25 +1,40 @@
+#!/usr/bin/env python3
 """Create example self-signed X.509 certificate
 """
 
 """Create example self-signed X.509 certificate
 """
 
+from argparse import ArgumentParser
+from base64 import standard_b64decode
 from base64 import standard_b64encode
 from datetime import datetime
 from datetime import timedelta
 from os import urandom
 from base64 import standard_b64encode
 from datetime import datetime
 from datetime import timedelta
 from os import urandom
-from sys import argv
-from sys import exit as sys_exit
 from textwrap import fill
 
 from pyderasn import Any
 from pyderasn import BitString
 from textwrap import fill
 
 from pyderasn import Any
 from pyderasn import BitString
+from pyderasn import Boolean
+from pyderasn import IA5String
 from pyderasn import Integer
 from pyderasn import OctetString
 from pyderasn import PrintableString
 from pyderasn import UTCTime
 
 from pygost.asn1schemas.oids import id_at_commonName
 from pyderasn import Integer
 from pyderasn import OctetString
 from pyderasn import PrintableString
 from pyderasn import UTCTime
 
 from pygost.asn1schemas.oids import id_at_commonName
+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_ce_subjectKeyIdentifier
+from pygost.asn1schemas.oids import id_tc26_gost3410_2012_256
+from pygost.asn1schemas.oids import id_tc26_gost3410_2012_256_paramSetA
+from pygost.asn1schemas.oids import id_tc26_gost3410_2012_256_paramSetB
+from pygost.asn1schemas.oids import id_tc26_gost3410_2012_256_paramSetC
+from pygost.asn1schemas.oids import id_tc26_gost3410_2012_256_paramSetD
 from pygost.asn1schemas.oids import id_tc26_gost3410_2012_512
 from pygost.asn1schemas.oids import id_tc26_gost3410_2012_512_paramSetA
 from pygost.asn1schemas.oids import id_tc26_gost3410_2012_512
 from pygost.asn1schemas.oids import id_tc26_gost3410_2012_512_paramSetA
+from pygost.asn1schemas.oids import id_tc26_gost3410_2012_512_paramSetB
+from pygost.asn1schemas.oids import id_tc26_gost3410_2012_512_paramSetC
+from pygost.asn1schemas.oids import id_tc26_signwithdigest_gost3410_2012_256
 from pygost.asn1schemas.oids import id_tc26_signwithdigest_gost3410_2012_512
 from pygost.asn1schemas.prvkey import PrivateKey
 from pygost.asn1schemas.prvkey import PrivateKeyAlgorithmIdentifier
 from pygost.asn1schemas.oids import id_tc26_signwithdigest_gost3410_2012_512
 from pygost.asn1schemas.prvkey import PrivateKey
 from pygost.asn1schemas.prvkey import PrivateKeyAlgorithmIdentifier
@@ -28,14 +43,20 @@ 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 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
 from pygost.asn1schemas.x509 import Extension
 from pygost.asn1schemas.x509 import Extensions
 from pygost.asn1schemas.x509 import Certificate
 from pygost.asn1schemas.x509 import CertificateSerialNumber
 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 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
 from pygost.asn1schemas.x509 import Name
 from pygost.asn1schemas.x509 import RDNSequence
 from pygost.asn1schemas.x509 import RelativeDistinguishedName
+from pygost.asn1schemas.x509 import SubjectAltName
 from pygost.asn1schemas.x509 import SubjectKeyIdentifier
 from pygost.asn1schemas.x509 import SubjectPublicKeyInfo
 from pygost.asn1schemas.x509 import TBSCertificate
 from pygost.asn1schemas.x509 import SubjectKeyIdentifier
 from pygost.asn1schemas.x509 import SubjectPublicKeyInfo
 from pygost.asn1schemas.x509 import TBSCertificate
@@ -47,53 +68,199 @@ from pygost.gost3410 import prv_unmarshal
 from pygost.gost3410 import pub_marshal
 from pygost.gost3410 import public_key
 from pygost.gost3410 import sign
 from pygost.gost3410 import pub_marshal
 from pygost.gost3410 import public_key
 from pygost.gost3410 import sign
+from pygost.gost34112012256 import GOST34112012256
 from pygost.gost34112012512 import GOST34112012512
 
 from pygost.gost34112012512 import GOST34112012512
 
-if len(argv) != 2:
-    sys_exit("Usage: cert-selfsigned-example.py COMMON-NAME")
+parser = ArgumentParser(description="Self-signed X.509 certificate creator")
+parser.add_argument(
+    "--ca",
+    action="store_true",
+    help="Enable BasicConstraints.cA",
+)
+parser.add_argument(
+    "--cn",
+    required=True,
+    help="Subject's CommonName",
+)
+parser.add_argument(
+    "--ai",
+    required=True,
+    help="Signing algorithm: {256[ABCD],512[ABC]}",
+)
+parser.add_argument(
+    "--issue-with",
+    help="Path to PEM with CA to issue the child",
+)
+args = parser.parse_args()
+AIs = {
+    "256A": {
+        "publicKeyParamSet": id_tc26_gost3410_2012_256_paramSetA,
+        "key_algorithm": id_tc26_gost3410_2012_256,
+        "prv_len": 32,
+        "curve": CURVES["id-tc26-gost-3410-2012-256-paramSetA"],
+        "sign_algorithm": id_tc26_signwithdigest_gost3410_2012_256,
+        "hasher": GOST34112012256,
+    },
+    "256B": {
+        "publicKeyParamSet": id_tc26_gost3410_2012_256_paramSetB,
+        "key_algorithm": id_tc26_gost3410_2012_256,
+        "prv_len": 32,
+        "curve": CURVES["id-tc26-gost-3410-2012-256-paramSetB"],
+        "sign_algorithm": id_tc26_signwithdigest_gost3410_2012_256,
+        "hasher": GOST34112012256,
+    },
+    "256C": {
+        "publicKeyParamSet": id_tc26_gost3410_2012_256_paramSetC,
+        "key_algorithm": id_tc26_gost3410_2012_256,
+        "prv_len": 32,
+        "curve": CURVES["id-tc26-gost-3410-2012-256-paramSetC"],
+        "sign_algorithm": id_tc26_signwithdigest_gost3410_2012_256,
+        "hasher": GOST34112012256,
+    },
+    "256D": {
+        "publicKeyParamSet": id_tc26_gost3410_2012_256_paramSetD,
+        "key_algorithm": id_tc26_gost3410_2012_256,
+        "prv_len": 32,
+        "curve": CURVES["id-tc26-gost-3410-2012-256-paramSetD"],
+        "sign_algorithm": id_tc26_signwithdigest_gost3410_2012_256,
+        "hasher": GOST34112012256,
+    },
+    "512A": {
+        "publicKeyParamSet": id_tc26_gost3410_2012_512_paramSetA,
+        "key_algorithm": id_tc26_gost3410_2012_512,
+        "prv_len": 64,
+        "curve": CURVES["id-tc26-gost-3410-12-512-paramSetA"],
+        "sign_algorithm": id_tc26_signwithdigest_gost3410_2012_512,
+        "hasher": GOST34112012512,
+    },
+    "512B": {
+        "publicKeyParamSet": id_tc26_gost3410_2012_512_paramSetB,
+        "key_algorithm": id_tc26_gost3410_2012_512,
+        "prv_len": 64,
+        "curve": CURVES["id-tc26-gost-3410-12-512-paramSetB"],
+        "sign_algorithm": id_tc26_signwithdigest_gost3410_2012_512,
+        "hasher": GOST34112012512,
+    },
+    "512C": {
+        "publicKeyParamSet": id_tc26_gost3410_2012_512_paramSetC,
+        "key_algorithm": id_tc26_gost3410_2012_512,
+        "prv_len": 64,
+        "curve": CURVES["id-tc26-gost-3410-2012-512-paramSetC"],
+        "sign_algorithm": id_tc26_signwithdigest_gost3410_2012_512,
+        "hasher": GOST34112012512,
+    },
+}
+ai = AIs[args.ai]
+
+ca_prv = None
+ca_cert = None
+ca_subj = None
+ca_ai = None
+if args.issue_with is not None:
+    with open(args.issue_with, "rb") as fd:
+        lines = fd.read().decode("ascii").split("-----")
+    idx = lines.index("BEGIN PRIVATE KEY")
+    if idx == -1:
+        raise ValueError("PEM has no PRIVATE KEY")
+    prv_raw = standard_b64decode(lines[idx + 1])
+    idx = lines.index("BEGIN CERTIFICATE")
+    if idx == -1:
+        raise ValueError("PEM has no CERTIFICATE")
+    cert_raw = standard_b64decode(lines[idx + 1])
+    pki = PrivateKeyInfo().decod(prv_raw)
+    ca_prv = prv_unmarshal(bytes(OctetString().decod(bytes(pki["privateKey"]))))
+    ca_cert = Certificate().decod(cert_raw)
+    tbs = ca_cert["tbsCertificate"]
+    ca_subj = tbs["subject"]
+    curve_oid = GostR34102012PublicKeyParameters().decod(bytes(
+        tbs["subjectPublicKeyInfo"]["algorithm"]["parameters"]
+    ))["publicKeyParamSet"]
+    ca_ai = next(iter([
+        params for params in AIs.values()
+        if params["publicKeyParamSet"] == curve_oid
+    ]))
 
 
 def pem(obj):
 
 
 def pem(obj):
-    return fill(standard_b64encode(obj.encode()).decode('ascii'), 64)
+    return fill(standard_b64encode(obj.encode()).decode("ascii"), 64)
 
 
 key_params = GostR34102012PublicKeyParameters((
 
 
 key_params = GostR34102012PublicKeyParameters((
-    ("publicKeyParamSet", id_tc26_gost3410_2012_512_paramSetA),
+    ("publicKeyParamSet", ai["publicKeyParamSet"]),
 ))
 
 ))
 
-prv_raw = urandom(64)
+prv_raw = urandom(ai["prv_len"])
 print("-----BEGIN PRIVATE KEY-----")
 print(pem(PrivateKeyInfo((
     ("version", Integer(0)),
     ("privateKeyAlgorithm", PrivateKeyAlgorithmIdentifier((
 print("-----BEGIN PRIVATE KEY-----")
 print(pem(PrivateKeyInfo((
     ("version", Integer(0)),
     ("privateKeyAlgorithm", PrivateKeyAlgorithmIdentifier((
-        ("algorithm", id_tc26_gost3410_2012_512),
+        ("algorithm", ai["key_algorithm"]),
         ("parameters", Any(key_params)),
     ))),
         ("parameters", Any(key_params)),
     ))),
-    ("privateKey", PrivateKey(prv_raw)),
+    ("privateKey", PrivateKey(OctetString(prv_raw).encode())),
 ))))
 print("-----END PRIVATE KEY-----")
 
 prv = prv_unmarshal(prv_raw)
 ))))
 print("-----END PRIVATE KEY-----")
 
 prv = prv_unmarshal(prv_raw)
-curve = CURVES["id-tc26-gost-3410-12-512-paramSetA"]
-pub_raw = pub_marshal(public_key(curve, prv), mode=2012)
+curve = ai["curve"]
+pub_raw = pub_marshal(public_key(curve, prv))
 subj = Name(("rdnSequence", RDNSequence([
     RelativeDistinguishedName((
         AttributeTypeAndValue((
             ("type", AttributeType(id_at_commonName)),
 subj = Name(("rdnSequence", RDNSequence([
     RelativeDistinguishedName((
         AttributeTypeAndValue((
             ("type", AttributeType(id_at_commonName)),
-            ("value", AttributeValue(PrintableString(argv[1]))),
+            ("value", AttributeValue(PrintableString(args.cn))),
         )),
     ))
 ])))
 not_before = datetime.utcnow()
         )),
     ))
 ])))
 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((
 ai_sign = AlgorithmIdentifier((
-    ("algorithm", id_tc26_signwithdigest_gost3410_2012_512),
+    ("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_subjectAltName),
+        ("extnValue", OctetString(
+            SubjectAltName((
+                GeneralName(("dNSName", IA5String(args.cn))),
+            )).encode()
+        )),
+    )),
+]
+if args.ca:
+    exts.append(Extension((
+        ("extnID", id_ce_basicConstraints),
+        ("extnValue", OctetString(BasicConstraints((("cA", Boolean(True)),)).encode())),
+    )))
+    exts.append(Extension((
+        ("extnID", id_ce_keyUsage),
+        ("extnValue", OctetString(KeyUsage(("keyCertSign",)).encode())),
+    )))
+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_authorityKeyIdentifier),
+        ("extnValue", OctetString(AuthorityKeyIdentifier((
+            ("keyIdentifier", KeyIdentifier(caKeyId)),
+        )).encode())),
+    )))
+
 tbs = TBSCertificate((
     ("version", Version("v3")),
     ("serialNumber", CertificateSerialNumber(12345)),
     ("signature", ai_sign),
 tbs = TBSCertificate((
     ("version", Version("v3")),
     ("serialNumber", CertificateSerialNumber(12345)),
     ("signature", ai_sign),
-    ("issuer", subj),
+    ("issuer", subj if ca_ai is None else ca_subj),
     ("validity", Validity((
         ("notBefore", Time(("utcTime", UTCTime(not_before)))),
         ("notAfter", Time(("utcTime", UTCTime(not_after)))),
     ("validity", Validity((
         ("notBefore", Time(("utcTime", UTCTime(not_before)))),
         ("notAfter", Time(("utcTime", UTCTime(not_after)))),
@@ -101,29 +268,21 @@ tbs = TBSCertificate((
     ("subject", subj),
     ("subjectPublicKeyInfo", SubjectPublicKeyInfo((
         ("algorithm", AlgorithmIdentifier((
     ("subject", subj),
     ("subjectPublicKeyInfo", SubjectPublicKeyInfo((
         ("algorithm", AlgorithmIdentifier((
-            ("algorithm", id_tc26_gost3410_2012_512),
+            ("algorithm", ai["key_algorithm"]),
             ("parameters", Any(key_params)),
         ))),
         ("subjectPublicKey", BitString(OctetString(pub_raw).encode())),
     ))),
             ("parameters", Any(key_params)),
         ))),
         ("subjectPublicKey", BitString(OctetString(pub_raw).encode())),
     ))),
-    ("extensions", Extensions((
-        Extension((
-            ("extnID", id_ce_subjectKeyIdentifier),
-            ("extnValue", OctetString(
-                SubjectKeyIdentifier(GOST34112012512(pub_raw).digest()[:20]).encode()
-            )),
-        )),
-    ))),
+    ("extensions", Extensions(exts)),
 ))
 cert = Certificate((
     ("tbsCertificate", tbs),
     ("signatureAlgorithm", ai_sign),
 ))
 cert = Certificate((
     ("tbsCertificate", tbs),
     ("signatureAlgorithm", ai_sign),
-    ("signatureValue", BitString(sign(
-        curve,
-        prv,
-        GOST34112012512(tbs.encode()).digest()[::-1],
-        mode=2012,
-    ))),
+    ("signatureValue", BitString(
+        sign(curve, prv, ai["hasher"](tbs.encode()).digest()[::-1])
+        if ca_ai is None else
+        sign(ca_ai["curve"], ca_prv, ca_ai["hasher"](tbs.encode()).digest()[::-1])
+    )),
 ))
 print("-----BEGIN CERTIFICATE-----")
 print(pem(cert))
 ))
 print("-----BEGIN CERTIFICATE-----")
 print(pem(cert))