From ec39453b4f1c8d7b40383fa2af43d8a7cfeb4b22 Mon Sep 17 00:00:00 2001 From: Sergey Matveev Date: Wed, 29 Jan 2020 18:55:39 +0300 Subject: [PATCH] Example X.509 self-signed certificate creation utility --- README | 2 + pygost/asn1schemas/cert-selfsigned-example.py | 133 ++++++++++++++++++ pygost/asn1schemas/oids.py | 13 ++ pygost/asn1schemas/prvkey.py | 73 ++++++++++ pygost/asn1schemas/x509.py | 21 +++ 5 files changed, 242 insertions(+) create mode 100644 pygost/asn1schemas/cert-selfsigned-example.py create mode 100644 pygost/asn1schemas/prvkey.py diff --git a/README b/README index d703323..71ee5bb 100644 --- a/README +++ b/README @@ -50,6 +50,8 @@ Example 34.10-2012 keypair generation, signing and verifying: True Other examples can be found in docstrings and unittests. +Example self-signed X.509 certificate creation can be found in +pygost/asn1schemas/cert-selfsigned-example.py. PyGOST is free software: see the file COPYING for copying conditions. diff --git a/pygost/asn1schemas/cert-selfsigned-example.py b/pygost/asn1schemas/cert-selfsigned-example.py new file mode 100644 index 0000000..5cdca8f --- /dev/null +++ b/pygost/asn1schemas/cert-selfsigned-example.py @@ -0,0 +1,133 @@ +"""Create example self-signed X.509 certificate +""" + +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 sys import stderr +from textwrap import fill + +from pyderasn import Any +from pyderasn import BitString +from pyderasn import Integer +from pyderasn import ObjectIdentifier +from pyderasn import OctetString +from pyderasn import PrintableString +from pyderasn import UTCTime + +from pygost.asn1schemas.oids import id_ce_subjectKeyIdentifier +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_gost3411_2012_512 +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.prvkey import PrivateKeyInfo +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 Certificate +from pygost.asn1schemas.x509 import CertificateSerialNumber +from pygost.asn1schemas.x509 import Extension +from pygost.asn1schemas.x509 import Extensions +from pygost.asn1schemas.x509 import GostR34102012PublicKeyParameters +from pygost.asn1schemas.x509 import Name +from pygost.asn1schemas.x509 import RDNSequence +from pygost.asn1schemas.x509 import RelativeDistinguishedName +from pygost.asn1schemas.x509 import SubjectKeyIdentifier +from pygost.asn1schemas.x509 import SubjectPublicKeyInfo +from pygost.asn1schemas.x509 import TBSCertificate +from pygost.asn1schemas.x509 import Time +from pygost.asn1schemas.x509 import Validity +from pygost.asn1schemas.x509 import Version +from pygost.gost3410 import CURVES +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.gost34112012512 import GOST34112012512 + +if len(argv) != 2: + print("Usage: cert-selfsigned-example.py COMMON-NAME", file=stderr) + sys_exit(1) + +def pem(obj): + return fill(standard_b64encode(obj.encode()).decode('ascii'), 64) + +key_params = GostR34102012PublicKeyParameters(( + ("publicKeyParamSet", id_tc26_gost3410_2012_512_paramSetA), + ("digestParamSet", id_tc26_gost3411_2012_512), +)) + +prv_raw = urandom(64) +print("-----BEGIN PRIVATE KEY-----") +print(pem(PrivateKeyInfo(( + ("version", Integer(0)), + ("privateKeyAlgorithm", PrivateKeyAlgorithmIdentifier(( + ("algorithm", id_tc26_gost3410_2012_512), + ("parameters", Any(key_params)), + ))), + ("privateKey", PrivateKey(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) +id_at_commonName = ObjectIdentifier("2.5.4.3") +subj = Name(("rdnSequence", RDNSequence([ + RelativeDistinguishedName(( + AttributeTypeAndValue(( + ("type", AttributeType(id_at_commonName)), + ("value", AttributeValue(PrintableString(argv[1]))), + )), + )) +]))) +not_before = datetime.utcnow() +not_after = not_before + timedelta(days=365) +ai_sign = AlgorithmIdentifier(( + ("algorithm", id_tc26_signwithdigest_gost3410_2012_512), +)) +tbs = TBSCertificate(( + ("version", Version("v3")), + ("serialNumber", CertificateSerialNumber(12345)), + ("signature", ai_sign), + ("issuer", subj), + ("validity", Validity(( + ("notBefore", Time(("utcTime", UTCTime(not_before)))), + ("notAfter", Time(("utcTime", UTCTime(not_after)))), + ))), + ("subject", subj), + ("subjectPublicKeyInfo", SubjectPublicKeyInfo(( + ("algorithm", AlgorithmIdentifier(( + ("algorithm", id_tc26_gost3410_2012_512), + ("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() + )), + )), + ))), +)) +cert = Certificate(( + ("tbsCertificate", tbs), + ("signatureAlgorithm", ai_sign), + ("signatureValue", BitString(sign( + curve, + prv, + GOST34112012512(tbs.encode()).digest(), + mode=2012, + ))), +)) +print("-----BEGIN CERTIFICATE-----") +print(pem(cert)) +print("-----END CERTIFICATE-----") diff --git a/pygost/asn1schemas/oids.py b/pygost/asn1schemas/oids.py index 1e8e4f6..eff060d 100644 --- a/pygost/asn1schemas/oids.py +++ b/pygost/asn1schemas/oids.py @@ -10,7 +10,20 @@ id_encryptedData = id_pkcs7 + (6,) id_data = ObjectIdentifier("1.2.840.113549.1.7.1") id_tc26_gost3410_2012_256 = ObjectIdentifier("1.2.643.7.1.1.1.1") id_tc26_gost3410_2012_512 = ObjectIdentifier("1.2.643.7.1.1.1.2") +id_tc26_gost3411_2012_256 = ObjectIdentifier("1.2.643.7.1.1.2.2") +id_tc26_gost3411_2012_512 = ObjectIdentifier("1.2.643.7.1.1.2.3") +id_tc26_gost3410_2012_256_paramSetA = ObjectIdentifier("1.2.643.7.1.2.1.1.1") +id_tc26_gost3410_2012_256_paramSetB = ObjectIdentifier("1.2.643.7.1.2.1.1.2") +id_tc26_gost3410_2012_256_paramSetC = ObjectIdentifier("1.2.643.7.1.2.1.1.3") +id_tc26_gost3410_2012_256_paramSetD = ObjectIdentifier("1.2.643.7.1.2.1.1.4") +id_tc26_gost3410_2012_512_paramSetA = ObjectIdentifier("1.2.643.7.1.2.1.2.1") +id_tc26_gost3410_2012_512_paramSetB = ObjectIdentifier("1.2.643.7.1.2.1.2.2") +id_tc26_gost3410_2012_512_paramSetC = ObjectIdentifier("1.2.643.7.1.2.1.2.3") +id_tc26_signwithdigest_gost3410_2012_256 = ObjectIdentifier("1.2.643.7.1.1.3.2") +id_tc26_signwithdigest_gost3410_2012_512 = ObjectIdentifier("1.2.643.7.1.1.3.3") id_Gost28147_89 = ObjectIdentifier("1.2.643.2.2.21") id_pbes2 = ObjectIdentifier("1.2.840.113549.1.5.13") id_pbkdf2 = ObjectIdentifier("1.2.840.113549.1.5.12") + +id_ce_subjectKeyIdentifier = ObjectIdentifier("2.5.29.14") diff --git a/pygost/asn1schemas/prvkey.py b/pygost/asn1schemas/prvkey.py new file mode 100644 index 0000000..67e6fb1 --- /dev/null +++ b/pygost/asn1schemas/prvkey.py @@ -0,0 +1,73 @@ +# coding: utf-8 +# PyGOST -- Pure Python GOST cryptographic functions library +# Copyright (C) 2015-2020 Sergey Matveev +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, version 3 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +from pyderasn import Any +from pyderasn import BitString +from pyderasn import Choice +from pyderasn import Integer +from pyderasn import Null +from pyderasn import ObjectIdentifier +from pyderasn import OctetString +from pyderasn import Sequence +from pyderasn import tag_ctxc + +from pygost.asn1schemas.oids import id_tc26_gost3410_2012_256 +from pygost.asn1schemas.oids import id_tc26_gost3410_2012_512 +from pygost.asn1schemas.x509 import GostR34102012PublicKeyParameters + + +class ECParameters(Choice): + schema = ( + ("namedCurve", ObjectIdentifier()), + ("implicitCurve", Null()), + # ("specifiedCurve", SpecifiedECDomain()), + ) + + +ecPrivkeyVer1 = Integer(1) + + +class ECPrivateKey(Sequence): + schema = ( + ("version", Integer(ecPrivkeyVer1)), + ("privateKey", OctetString()), + ("parameters", ECParameters(expl=tag_ctxc(0), optional=True)), + ("publicKey", BitString(expl=tag_ctxc(1), optional=True)), + ) + + +class PrivateKeyAlgorithmIdentifier(Sequence): + schema = ( + ("algorithm", ObjectIdentifier(defines=( + (("parameters",), { + id_tc26_gost3410_2012_256: GostR34102012PublicKeyParameters(), + id_tc26_gost3410_2012_512: GostR34102012PublicKeyParameters(), + }), + ))), + ("parameters", Any(optional=True)), + ) + + +class PrivateKey(OctetString): + pass + + +class PrivateKeyInfo(Sequence): + schema = ( + ("version", Integer(0)), + ("privateKeyAlgorithm", PrivateKeyAlgorithmIdentifier()), + ("privateKey", PrivateKey()), + ) diff --git a/pygost/asn1schemas/x509.py b/pygost/asn1schemas/x509.py index fae6627..bbeca66 100644 --- a/pygost/asn1schemas/x509.py +++ b/pygost/asn1schemas/x509.py @@ -112,6 +112,19 @@ class Validity(Sequence): ) +id_tc26_gost_28147_param_Z = ObjectIdentifier("1.2.643.7.1.2.5.1.1") + + +class GostR34102012PublicKeyParameters(Sequence): + schema = ( + ("publicKeyParamSet", ObjectIdentifier()), + ("digestParamSet", ObjectIdentifier()), + ("encryptionParamSet", ObjectIdentifier( + default=id_tc26_gost_28147_param_Z, + )), + ) + + class SubjectPublicKeyInfo(Sequence): schema = ( ("algorithm", AlgorithmIdentifier()), @@ -123,6 +136,14 @@ class UniqueIdentifier(BitString): pass +class KeyIdentifier(OctetString): + pass + + +class SubjectKeyIdentifier(KeyIdentifier): + pass + + class Extension(Sequence): schema = ( ("extnID", ObjectIdentifier()), -- 2.44.0