From: Sergey Matveev Date: Mon, 20 Feb 2023 12:54:53 +0000 (+0300) Subject: Example X.509 certificate issuing utility X-Git-Tag: v5.10.0~12 X-Git-Url: http://www.git.cypherpunks.ru/?p=gogost.git;a=commitdiff_plain;h=cbf2e9df315b52e968c304d54371b57c6a40ec32 Example X.509 certificate issuing utility --- diff --git a/cmd/cer-selfsigned-example/main.go b/cmd/cer-selfsigned-example/main.go new file mode 100644 index 0000000..f859f8d --- /dev/null +++ b/cmd/cer-selfsigned-example/main.go @@ -0,0 +1,227 @@ +// GoGOST -- Pure Go GOST cryptographic functions library +// Copyright (C) 2015-2023 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 . + +// Example X.509 certificate issuing utility. +package main + +import ( + "crypto/rand" + "crypto/x509" + "crypto/x509/pkix" + "encoding/pem" + "flag" + "io" + "log" + "math/big" + "os" + "time" + + "crypto/go.cypherpunks.ru/gogost/v5/gost3410" + "crypto/go.cypherpunks.ru/gogost/v5/gost34112012256" +) + +const ( + PEMKey = "PRIVATE KEY" + PEMCer = "CERTIFICATE" +) + +func loadKeypair(filename string) (cer *x509.Certificate, prv any, err error) { + var data []byte + data, err = os.ReadFile(filename) + if err != nil { + return + } + var block *pem.Block + for len(data) > 0 { + block, data = pem.Decode(data) + if block == nil { + continue + } + switch block.Type { + case PEMCer: + cer, err = x509.ParseCertificate(block.Bytes) + case PEMKey: + prv, err = x509.ParsePKCS8PrivateKey(block.Bytes) + } + if err != nil { + return + } + } + return +} + +func main() { + ca := flag.Bool("ca", false, "Enable BasicConstraints.cA") + cn := flag.String("cn", "", "Subject's CommonName") + country := flag.String("country", "", "Subject's Country") + serial := flag.Int64("serial", -1, "Serial number") + ai := flag.String("ai", "", "Signing algorithm: {256[ABCD],512[ABC]}") + issueWith := flag.String("issue-with", "", "Path to PEM with CA to issue the child") + reuseKey := flag.String("reuse-key", "", "Path to PEM with the key to reuse") + outKey := flag.String("out-key", "", "Path to PEM with the resulting key") + onlyKey := flag.Bool("only-key", false, "Only generate the key") + outCer := flag.String("out-cert", "", "Path to PEM with the resulting certificate") + flag.Parse() + log.SetFlags(log.Lshortfile) + + if *cn == "" { + log.Fatalln("no CommonName is set") + } + var curve *gost3410.Curve + var sigAlg x509.SignatureAlgorithm + switch *ai { + case "256A": + curve = gost3410.CurveIdtc26gost341012256paramSetA() + sigAlg = x509.GOST256 + case "256B": + curve = gost3410.CurveIdtc26gost341012256paramSetB() + sigAlg = x509.GOST256 + case "256C": + curve = gost3410.CurveIdtc26gost341012256paramSetC() + sigAlg = x509.GOST256 + case "256D": + curve = gost3410.CurveIdtc26gost341012256paramSetD() + sigAlg = x509.GOST256 + case "512A": + curve = gost3410.CurveIdtc26gost341012512paramSetA() + sigAlg = x509.GOST512 + case "512B": + curve = gost3410.CurveIdtc26gost341012512paramSetB() + sigAlg = x509.GOST512 + case "512C": + curve = gost3410.CurveIdtc26gost341012512paramSetC() + sigAlg = x509.GOST512 + default: + log.Fatalln("unknown curve name") + } + + var err error + var caCer *x509.Certificate + var caPrv any + if *issueWith != "" { + caCer, caPrv, err = loadKeypair(*issueWith) + if err != nil { + log.Fatalln(err) + } + sigAlg = caCer.SignatureAlgorithm + } + + var prv any + if *reuseKey == "" { + prvRaw := make([]byte, curve.PointSize()) + if _, err := io.ReadFull(rand.Reader, prvRaw); err != nil { + log.Fatalln(err) + } + prv, err = gost3410.NewPrivateKey(curve, prvRaw) + if err != nil { + log.Fatalln(err) + } + data, err := x509.MarshalPKCS8PrivateKey(prv) + if err != nil { + log.Fatalln(err) + } + data = pem.EncodeToMemory(&pem.Block{Type: PEMKey, Bytes: data}) + if *outKey == "" { + _, err = os.Stdout.Write(data) + } else { + err = os.WriteFile(*outKey, data, 0o666) + } + if err != nil { + log.Fatalln(err) + } + if *onlyKey { + return + } + } else { + _, prv, err = loadKeypair(*reuseKey) + if err != nil { + log.Fatalln(err) + } + } + + notBefore := time.Now().UTC() + days := 365 * 24 * time.Hour + if *ca { + days *= 10 + } + notAfter := notBefore.Add(days) + + sn := big.NewInt(0) + if *serial == -1 { + data := make([]byte, 16, gost34112012256.Size) + if _, err = io.ReadFull(rand.Reader, data); err != nil { + log.Fatalln(err) + } + hasher := gost34112012256.New() + if _, err = hasher.Write(data); err != nil { + log.Fatalln(err) + } + data = hasher.Sum(data[:0]) + sn = sn.SetBytes(data[:20]) + } else { + sn = sn.SetInt64(*serial) + } + + subj := pkix.Name{CommonName: *cn} + if *country != "" { + subj.Country = []string{*country} + } + + pub, err := prv.(*gost3410.PrivateKey).PublicKey() + if err != nil { + log.Fatalln(err) + } + hasher := gost34112012256.New() + if _, err = hasher.Write(pub.Raw()); err != nil { + log.Fatalln(err) + } + spki := hasher.Sum(nil) + spki = spki[:20] + + cerTmpl := x509.Certificate{ + KeyUsage: x509.KeyUsageDigitalSignature, + NotBefore: notBefore, + NotAfter: notAfter, + SerialNumber: sn, + SignatureAlgorithm: sigAlg, + Subject: subj, + SubjectKeyId: spki, + } + if *ca { + cerTmpl.IsCA = true + cerTmpl.KeyUsage |= x509.KeyUsageCertSign + } else { + cerTmpl.DNSNames = []string{*cn} + } + + if caCer == nil { + caCer = &cerTmpl + caPrv = prv + } + data, err := x509.CreateCertificate( + rand.Reader, + &cerTmpl, caCer, pub, + &gost3410.PrivateKeyReverseDigest{Prv: caPrv.(*gost3410.PrivateKey)}, + ) + data = pem.EncodeToMemory(&pem.Block{Type: PEMCer, Bytes: data}) + if *outCer == "" { + _, err = os.Stdout.Write(data) + } else { + err = os.WriteFile(*outCer, data, 0o666) + } + if err != nil { + log.Fatalln(err) + } +} diff --git a/go.mod b/go.mod index ab71e19..377d625 100644 --- a/go.mod +++ b/go.mod @@ -1,5 +1,5 @@ module go.cypherpunks.ru/gogost/v5 -go 1.17 +go 1.18 -require golang.org/x/crypto v0.1.0 +require golang.org/x/crypto v0.6.0 diff --git a/go.sum b/go.sum index 37c516a..cdcbe20 100644 --- a/go.sum +++ b/go.sum @@ -1,2 +1,2 @@ -golang.org/x/crypto v0.1.0 h1:MDRAIl0xIo9Io2xV565hzXHw3zVseKrJKodhohM5CjU= -golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= +golang.org/x/crypto v0.6.0 h1:qfktjS5LUO+fFKeJXZ+ikTRijMmljikvG68fpMMruSc= +golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=