From 5c166a8b8e506e23852d99bf5397d98c0d491490 Mon Sep 17 00:00:00 2001 From: kaiyou <dev@kaiyou.fr> Date: Fri, 15 Jul 2022 17:37:42 +0200 Subject: [PATCH] Add a basic pki library --- pkg/pki/io.go | 72 ++++++++++++++++++++++++++++++++++++++++++++ pkg/pki/pki.go | 72 +++++++++++++++++++++++++++++--------------- pkg/pki/templates.go | 55 +++++++++++++++++++++++++++++++++ 3 files changed, 175 insertions(+), 24 deletions(-) create mode 100644 pkg/pki/io.go create mode 100644 pkg/pki/templates.go diff --git a/pkg/pki/io.go b/pkg/pki/io.go new file mode 100644 index 0000000..319ffa8 --- /dev/null +++ b/pkg/pki/io.go @@ -0,0 +1,72 @@ +package pki + +import ( + "crypto/ecdsa" + "crypto/x509" + "encoding/pem" + "errors" + "io/ioutil" + "os" +) + +func getPem(path string) ([]byte, error) { + bytes, err := ioutil.ReadFile(path) + if err != nil { + return nil, err + } + decoded, _ := pem.Decode(bytes) + if decoded == nil { + return nil, errors.New("Could not decode PEM file") + } + return decoded.Bytes, nil +} + +func setPem(path string, blockType string, bytes []byte) error { + block := &pem.Block{ + Type: blockType, + Bytes: bytes, + } + file, err := os.Create(path) + if err != nil { + return err + } + err = pem.Encode(file, block) + if err != nil { + return err + } + return file.Close() +} + +func LoadKey(path string) (*ecdsa.PrivateKey, error) { + decoded, err := getPem(path) + if os.IsNotExist(err) { + return nil, nil + } + if err != nil { + return nil, err + } + return x509.ParseECPrivateKey(decoded) +} + +func SaveKey(path string, key *ecdsa.PrivateKey) error { + keyBytes, err := x509.MarshalECPrivateKey(key) + if err != nil { + return err + } + return setPem(path, "ECDSA PRIVATE KEY", keyBytes) +} + +func LoadCert(path string) (*x509.Certificate, error) { + decoded, err := getPem(path) + if os.IsNotExist(err) { + return nil, nil + } + if err != nil { + return nil, err + } + return x509.ParseCertificate(decoded) +} + +func SaveCert(path string, cert *x509.Certificate) error { + return setPem(path, "CERTIFICATE", cert.Raw) +} diff --git a/pkg/pki/pki.go b/pkg/pki/pki.go index 31b74c8..5a1b002 100644 --- a/pkg/pki/pki.go +++ b/pkg/pki/pki.go @@ -6,9 +6,6 @@ import ( "crypto/elliptic" "crypto/rand" "crypto/x509" - "encoding/pem" - "errors" - "io/ioutil" "os" "path/filepath" ) @@ -19,7 +16,7 @@ const CERT_EXT = ".pem" type CA struct { path string - signer *crypto.Signer + signer crypto.Signer cert *x509.Certificate } @@ -46,36 +43,63 @@ func GetCA(path string) (*CA, error) { }, nil } -func getPem(path string) ([]byte, error) { - bytes, err := ioutil.ReadFile(path) +func (c *CA) Sign(csrBytes []byte, template *x509.Certificate) ([]byte, error) { + csr, err := x509.ParseCertificateRequest(csrBytes) if err != nil { return nil, err } - decoded, _ := pem.Decode(bytes) - if decoded == nil { - return nil, errors.New("Could not decode PEM file") + err = csr.CheckSignature() + if err != nil { + return nil, err } - return decoded.Bytes, nil + // Fill the template fields in + template.Signature = csr.Signature + template.SignatureAlgorithm = csr.SignatureAlgorithm + template.PublicKey = csr.PublicKey + template.PublicKeyAlgorithm = csr.PublicKeyAlgorithm + // Actually sign the certificate + return x509.CreateCertificate(rand.Reader, template, c.cert, template.PublicKey, c.signer) } -func getSigner(path string) (*crypto.Signer, error) { - decoded, err := getPem(path) - // Create the private key if necessary - if err == nil { - - } else if os.IsExist(err) { - signer, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) +func getSigner(path string) (crypto.Signer, error) { + key, err := LoadKey(path) + if err != nil { + return nil, err + } + // Create the key if necessary + if key == nil { + key, err = ecdsa.GenerateKey(elliptic.P256(), rand.Reader) if err != nil { - return err + return nil, err } - err = setPem(path, &pem.Block{ - Type: "ECDSA PRIVATE KEY", - Bytes: x509.MarshalPKCS1PrivateKey(signer), - }) + err = SaveKey(path, key) if err != nil { - return err + return nil, err } - } else { + } + return key, nil +} + +func getCaCert(signer crypto.Signer, path string) (*x509.Certificate, error) { + cert, err := LoadCert(path) + if err != nil { return nil, err } + // Self-sign a certificate if required + if cert == nil { + template := NewCATemplate() + certBytes, err := x509.CreateCertificate(rand.Reader, template, template, signer.Public(), signer) + if err != nil { + return nil, err + } + cert, err = x509.ParseCertificate(certBytes) + if err != nil { + return nil, err + } + err = SaveCert(path, cert) + if err != nil { + return nil, err + } + } + return cert, nil } diff --git a/pkg/pki/templates.go b/pkg/pki/templates.go new file mode 100644 index 0000000..e82ad26 --- /dev/null +++ b/pkg/pki/templates.go @@ -0,0 +1,55 @@ +package pki + +import ( + "crypto/rand" + "crypto/x509" + "crypto/x509/pkix" + "math/big" + "time" +) + +var maxSerial = new(big.Int).Lsh(big.NewInt(1), 128) + +func newSerial() *big.Int { + serial, err := rand.Int(rand.Reader, maxSerial) + if err != nil { + panic(err) + } + return serial +} + +func newTemplate() *x509.Certificate { + return &x509.Certificate{ + SerialNumber: newSerial(), + NotBefore: time.Now(), + NotAfter: time.Now().Add(time.Hour * 24 * 365 * 10), + BasicConstraintsValid: true, + } +} + +func NewCATemplate() *x509.Certificate { + template := newTemplate() + template.Subject = pkix.Name{} + template.KeyUsage = x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign + template.ExtKeyUsage = []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth} + template.IsCA = true + return template +} + +func NewServerTemplate(names []string) *x509.Certificate { + template := newTemplate() + template.Subject = pkix.Name{ + CommonName: names[0], + } + template.DNSNames = names + return template +} + +func NewClientTemplate(cn string, org string) *x509.Certificate { + template := newTemplate() + template.Subject = pkix.Name{ + CommonName: cn, + Organization: []string{org}, + } + return template +} -- GitLab