// Package pki contains bits and pieces to work with OpenVPN PKI related operations. package pki import ( "crypto/rand" "crypto/rsa" "crypto/x509" "crypto/x509/pkix" "encoding/asn1" //"github.com/Sirupsen/logrus" "bytes" "encoding/pem" "fmt" "math/big" "time" ) const ( _CrtExpireYears = 10 _CrtKeyLength = 2024 ) // CertHolder encapsulates a public certificate and the corresponding private key. type CertHolder struct { Cert string // PEM Encoded Certificate Key string // PEM Encoded Private Key } // CA is a special type of CertHolder that also has a CSR in it. type CA struct { CertHolder CSR string } // NewCA returns a newly generated CA. // // This will generate a public/private RSA keypair and a authority certificate signed by itself. func NewCA() (*CA, error) { type basicConstraints struct { IsCA bool `asn1:"optional"` MaxPathLen int `asn1:"optional,default:-1"` } key, err := rsa.GenerateKey(rand.Reader, _CrtKeyLength) if err != nil { return nil, fmt.Errorf("private key cannot be created: %s", err) } val, err := asn1.Marshal(basicConstraints{true, 0}) if err != nil { return nil, fmt.Errorf("can not marshal basic constraints: %s", err) } names := pkix.Name{CommonName: "CA"} var csrTemplate = x509.CertificateRequest{ Subject: names, SignatureAlgorithm: x509.SHA512WithRSA, ExtraExtensions: []pkix.Extension{ { Id: asn1.ObjectIdentifier{2, 5, 29, 19}, Value: val, Critical: true, }, }, } csrCertificate, err := x509.CreateCertificateRequest(rand.Reader, &csrTemplate, key) if err != nil { return nil, fmt.Errorf("can not create certificate request: %s", err) } csr := pem.EncodeToMemory(&pem.Block{ Type: PEMCSRBlockType, Bytes: csrCertificate, }) // Serial number serial, err := rand.Int(rand.Reader, (&big.Int{}).Exp(big.NewInt(2), big.NewInt(159), nil)) if err != nil { return nil, err } now := time.Now() // Create the request template template := x509.Certificate{ SerialNumber: serial, Subject: names, NotBefore: now.Add(-10 * time.Minute).UTC(), NotAfter: now.Add(time.Duration(24*365*_CrtExpireYears) * time.Hour).UTC(), BasicConstraintsValid: true, IsCA: true, KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign | x509.KeyUsageCRLSign, //ExtKeyUsage: []x509.ExtKeyUsage{x509.KeyUsageCertSign, x509.ExtKeyUsageClientAuth}, } // Sign the certificate authority certificate, err := x509.CreateCertificate(rand.Reader, &template, &template, &key.PublicKey, key) if err != nil { return nil, fmt.Errorf("failed to generate certificate error: %s", err) } var request bytes.Buffer var privateKey bytes.Buffer if err := pem.Encode(&request, &pem.Block{Type: PEMCertificateBlockType, Bytes: certificate}); err != nil { return nil, err } if err := pem.Encode(&privateKey, &pem.Block{Type: PEMRSAPrivateKeyBlockType, Bytes: x509.MarshalPKCS1PrivateKey(key)}); err != nil { return nil, err } return &CA{ CertHolder: CertHolder{ Key: privateKey.String(), Cert: request.String(), }, CSR: string(csr), }, nil } // NewServerCertHolder generates a RSA key-pair and a x509 certificate signed by the CA for the server. func NewServerCertHolder(ca *CA) (*CertHolder, error) { return newCert(ca, true, "localhost") } // NewClientCertHolder generates a RSA key-pair and a x509 certificate signed by the CA for the client. func NewClientCertHolder(ca *CA, username string) (*CertHolder, error) { return newCert(ca, false, username) } // newCert generates a RSA key-pair and a x509 certificate signed by the CA. func newCert(ca *CA, server bool, cn string) (*CertHolder, error) { // Get CA private key block, _ := pem.Decode([]byte(ca.Key)) if block == nil { return nil, fmt.Errorf("failed to parse ca private key") } caKey, err := x509.ParsePKCS1PrivateKey(block.Bytes) if err != nil { return nil, fmt.Errorf("failed to parse ca private key: %s", err) } caCert, err := ReadCertFromPEM(ca.Cert) if err != nil { return nil, fmt.Errorf("failed to parse ca cert: %v", err) } // Create new cert's key key, err := rsa.GenerateKey(rand.Reader, 2048) if err != nil { return nil, fmt.Errorf("private key cannot be created: %s", err) } serial, err := rand.Int(rand.Reader, (&big.Int{}).Exp(big.NewInt(2), big.NewInt(159), nil)) if err != nil { return nil, err } val, err := asn1.Marshal(asn1.BitString{Bytes: []byte{0x80}, BitLength: 2}) // setting nsCertType to Client Type if err != nil { return nil, fmt.Errorf("can not marshal nsCertType: %v", err) } now := time.Now() tml := x509.Certificate{ NotBefore: now.Add(-10 * time.Minute).UTC(), NotAfter: now.Add(time.Duration(24*365*_CrtExpireYears) * time.Hour).UTC(), SerialNumber: serial, Subject: pkix.Name{ CommonName: cn, Organization: []string{"OVPM"}, }, KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyAgreement, ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}, BasicConstraintsValid: true, ExtraExtensions: []pkix.Extension{ { Id: asn1.ObjectIdentifier{2, 16, 840, 1, 113730, 1, 1}, Value: val, }, }, } if server { tml.KeyUsage = x509.KeyUsageDigitalSignature | x509.KeyUsageKeyAgreement | x509.KeyUsageKeyEncipherment tml.ExtKeyUsage = []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth} val, err := asn1.Marshal(asn1.BitString{Bytes: []byte{0x40}, BitLength: 2}) // setting nsCertType to Server Type if err != nil { return nil, fmt.Errorf("can not marshal nsCertType: %v", err) } tml.ExtraExtensions[0].Id = asn1.ObjectIdentifier{2, 16, 840, 1, 113730, 1, 1} tml.ExtraExtensions[0].Value = val } // Sign with CA's private key cert, err := x509.CreateCertificate(rand.Reader, &tml, caCert, &key.PublicKey, caKey) if err != nil { return nil, fmt.Errorf("certificate cannot be created: %s", err) } priKeyPem := pem.EncodeToMemory(&pem.Block{ Type: PEMRSAPrivateKeyBlockType, Bytes: x509.MarshalPKCS1PrivateKey(key), }) certPem := pem.EncodeToMemory(&pem.Block{ Type: PEMCertificateBlockType, Bytes: cert, }) return &CertHolder{ Key: string(priKeyPem[:]), Cert: string(certPem[:]), }, nil } // NewCRL takes in a list of certificate serial numbers to-be-revoked and a CA then makes a PEM encoded CRL and returns it as a string. func NewCRL(ca *CA, serials ...*big.Int) (string, error) { caCrt, err := ReadCertFromPEM(ca.Cert) if err != nil { return "", err } block, _ := pem.Decode([]byte(ca.Key)) if block == nil { return "", fmt.Errorf("failed to parse ca private key") } priv, err := x509.ParsePKCS1PrivateKey(block.Bytes) if err != nil { return "", fmt.Errorf("failed to parse ca private key: %s", err) } var revokedCertList []pkix.RevokedCertificate for _, serial := range serials { revokedCert := pkix.RevokedCertificate{ SerialNumber: serial, RevocationTime: time.Now().UTC(), } revokedCertList = append(revokedCertList, revokedCert) } crl, err := caCrt.CreateCRL(rand.Reader, priv, revokedCertList, time.Now().UTC(), time.Now().Add(365*24*60*time.Minute).UTC()) if err != nil { return "", err } crlPem := pem.EncodeToMemory(&pem.Block{ Type: PEMx509CRLBlockType, Bytes: crl, }) return string(crlPem[:]), nil } // ReadCertFromPEM decodes a PEM encoded string into a x509.Certificate. func ReadCertFromPEM(s string) (*x509.Certificate, error) { block, _ := pem.Decode([]byte(s)) var cert *x509.Certificate cert, _ = x509.ParseCertificate(block.Bytes) return cert, nil }