From 5ea70c02aeedec0c7eafb6fd397ec736b8a3171f Mon Sep 17 00:00:00 2001 From: Aleksandr Rykalin Date: Thu, 28 Feb 2019 18:14:20 +0300 Subject: [PATCH] switch to vcert certificate.Request --- plugin/pki/path_roles.go | 69 +++++---- plugin/pki/path_venafi_cert_enroll.go | 206 +++++++++----------------- 2 files changed, 113 insertions(+), 162 deletions(-) diff --git a/plugin/pki/path_roles.go b/plugin/pki/path_roles.go index 2a8720db..5ff0102b 100644 --- a/plugin/pki/path_roles.go +++ b/plugin/pki/path_roles.go @@ -84,10 +84,21 @@ Example: Description: `Set it to true to store certificates by unique serial number in certs/ path`, }, + "service_generated_cert": { + Type: framework.TypeBool, + Description: `Use service generated CSR for Venafi Platfrom (ignored if Saas endpoint used)`, + Default: false, + }, "store_pkey": { Type: framework.TypeBool, Description: `Set it to true to store certificates privates key in certificate fields`, }, + "chain_option": { + Type: framework.TypeString, + Description: `Specify ordering certificates in chain. Root can be "first" or + "last"`, + Default: "last", + }, "key_type": { Type: framework.TypeString, Default: "rsa", @@ -242,23 +253,25 @@ func (b *backend) pathRoleCreate(ctx context.Context, req *logical.Request, data name := data.Get("name").(string) entry := &roleEntry{ - TPPURL: data.Get("tpp_url").(string), - CloudURL: data.Get("cloud_url").(string), - Zone: data.Get("zone").(string), - TPPPassword: data.Get("tpp_password").(string), - Apikey: data.Get("apikey").(string), - TPPUser: data.Get("tpp_user").(string), - TrustBundleFile: data.Get("trust_bundle_file").(string), - Fakemode: data.Get("fakemode").(bool), - StoreByCN: data.Get("store_by_cn").(bool), - StoreBySerial: data.Get("store_by_serial").(bool), - StorePrivateKey: data.Get("store_pkey").(bool), - KeyType: data.Get("key_type").(string), - KeyBits: data.Get("key_bits").(int), - KeyCurve: data.Get("key_curve").(string), - MaxTTL: time.Duration(data.Get("max_ttl").(int)) * time.Second, - TTL: time.Duration(data.Get("ttl").(int)) * time.Second, - GenerateLease: data.Get("generate_lease").(bool), + TPPURL: data.Get("tpp_url").(string), + CloudURL: data.Get("cloud_url").(string), + Zone: data.Get("zone").(string), + TPPPassword: data.Get("tpp_password").(string), + Apikey: data.Get("apikey").(string), + TPPUser: data.Get("tpp_user").(string), + TrustBundleFile: data.Get("trust_bundle_file").(string), + Fakemode: data.Get("fakemode").(bool), + ChainOption: data.Get("chain_option").(string), + StoreByCN: data.Get("store_by_cn").(bool), + StoreBySerial: data.Get("store_by_serial").(bool), + ServiceGenerated: data.Get("service_generated_cert").(bool), + StorePrivateKey: data.Get("store_pkey").(bool), + KeyType: data.Get("key_type").(string), + KeyBits: data.Get("key_bits").(int), + KeyCurve: data.Get("key_curve").(string), + MaxTTL: time.Duration(data.Get("max_ttl").(int)) * time.Second, + TTL: time.Duration(data.Get("ttl").(int)) * time.Second, + GenerateLease: data.Get("generate_lease").(bool), } if !entry.Fakemode && entry.Apikey == "" && (entry.TPPURL == "" || entry.TPPUser == "" || entry.TPPPassword == "") { return logical.ErrorResponse("Invalid mode. fakemode or apikey or tpp credentials required"), nil @@ -292,8 +305,10 @@ type roleEntry struct { TPPUser string `json:"tpp_user"` TrustBundleFile string `json:"trust_bundle_file"` Fakemode bool `json:"fakemode"` + ChainOption string `json:"chain_option"` StoreByCN bool `json:"store_by_cn"` StoreBySerial bool `json:"store_by_serial"` + ServiceGenerated bool `json:"service_generated_cert"` StorePrivateKey bool `json:"store_pkey"` KeyType string `json:"key_type"` KeyBits int `json:"key_bits"` @@ -316,15 +331,17 @@ func (r *roleEntry) ToResponseData() map[string]interface{} { //We shouldn't show credentials //"tpp_password": r.TPPPassword, //"apikey": r.Apikey, - "tpp_user": r.TPPUser, - "trust_bundle_file": r.TrustBundleFile, - "fakemode": r.Fakemode, - "store_by_cn": r.StoreByCN, - "store_by_serial": r.StoreBySerial, - "store_pkey": r.StorePrivateKey, - "ttl": int64(r.TTL.Seconds()), - "max_ttl": int64(r.MaxTTL.Seconds()), - "generate_lease": r.GenerateLease, + "tpp_user": r.TPPUser, + "trust_bundle_file": r.TrustBundleFile, + "fakemode": r.Fakemode, + "store_by_cn": r.StoreByCN, + "store_by_serial": r.StoreBySerial, + "service_generated_cert": r.ServiceGenerated, + "store_pkey": r.StorePrivateKey, + "ttl": int64(r.TTL.Seconds()), + "max_ttl": int64(r.MaxTTL.Seconds()), + "generate_lease": r.GenerateLease, + "chain_option": r.ChainOption, } return responseData } diff --git a/plugin/pki/path_venafi_cert_enroll.go b/plugin/pki/path_venafi_cert_enroll.go index 3145f42c..c4f38a65 100644 --- a/plugin/pki/path_venafi_cert_enroll.go +++ b/plugin/pki/path_venafi_cert_enroll.go @@ -2,11 +2,11 @@ package pki import ( "context" - "crypto/rand" "crypto/x509" + "crypto/x509/pkix" "encoding/pem" "fmt" - vcertificate "github.com/Venafi/vcert/pkg/certificate" + "github.com/Venafi/vcert/pkg/certificate" "github.com/Venafi/vcert/pkg/endpoint" "github.com/hashicorp/vault/logical" "github.com/hashicorp/vault/logical/framework" @@ -75,67 +75,95 @@ func (b *backend) pathVenafiCertObtain(ctx context.Context, req *logical.Request var pk privateKey pk.keyType = role.KeyType - pk.keyBits = role.KeyBits - pk.keyCurve = role.KeyCurve - log.Printf("Signing private key with parameteres %v", pk) - certReq, pkey, err := createVenafiCSR(commonName, altNames, pk) + certReq := &certificate.Request{ + Subject: pkix.Name{ + CommonName: commonName, + }, + DNSNames: altNames, + //TODO: add email, ip address parsing or split this options in certificate request + //EmailAddresses: []string{"e1@venafi.example.com", "e2@venafi.example.com"}, + //IPAddresses: []net.IP{net.IPv4(127, 0, 0, 1), net.IPv4(127, 0, 0, 2)}, + CsrOrigin: certificate.LocalGeneratedCSR, + //TODO: add key password support + } + + if cl.GetType() == endpoint.ConnectorTypeTPP && role.ServiceGenerated { + log.Println("Using service generated certificate for Venafi Platform") + certReq.CsrOrigin = certificate.ServiceGeneratedCSR + } + + if role.KeyType == "rsa" { + certReq.KeyLength = role.KeyBits + } else if role.KeyType == "ec" { + certReq.KeyType = certificate.KeyTypeECDSA + switch { + case role.KeyCurve == "P224": + certReq.KeyCurve = certificate.EllipticCurveP224 + case role.KeyCurve == "P256": + certReq.KeyCurve = certificate.EllipticCurveP256 + case role.KeyCurve == "P384": + certReq.KeyCurve = certificate.EllipticCurveP384 + case role.KeyCurve == "P521": + certReq.KeyCurve = certificate.EllipticCurveP521 + default: + return logical.ErrorResponse(fmt.Sprintf("can't use key curve %s", role.KeyCurve)), nil + } + + } else { + return logical.ErrorResponse(fmt.Sprintf("can't determine key algorithm for %s", role.KeyType)), nil + } + + + if role.ChainOption == "first" { + certReq.ChainOption = certificate.ChainOptionRootFirst + } else if role.ChainOption == "last" { + certReq.ChainOption = certificate.ChainOptionRootLast + } else { + return logical.ErrorResponse(fmt.Sprintf("Invalid chain option %s", role.ChainOption)), nil + } + + log.Println("Making certificate request") + err = cl.GenerateRequest(nil, certReq) if err != nil { return logical.ErrorResponse(err.Error()), nil } + log.Printf("Running enroll request") - id, err := cl.RequestCertificate(certReq, "") + + requestID, err := cl.RequestCertificate(certReq, "") if err != nil { return logical.ErrorResponse(err.Error()), nil } - certReq.PickupID = id - - var cert *vcertificate.PEMCollection - for { - cert, err = cl.RetrieveCertificate(certReq) - if err != nil { - _, pending := err.(endpoint.ErrCertificatePending) - _, timeout := err.(endpoint.ErrRetrieveCertificateTimeout) - - if pending || timeout { - log.Printf("Certificate %s Issue pending with id %s", commonName, id) - time.Sleep(5 * time.Second) - continue - } else { - return logical.ErrorResponse(err.Error()), nil - } - } - log.Printf("Certificate is %s", *cert) - log.Printf("successfully got certificate: cn=%q altNames=%+v", commonName, altNames) - break + pickupReq := &certificate.Request{ + PickupID: requestID, + //TODO: make timeout configurable + Timeout: 180 * time.Second, + } + pcc, err := cl.RetrieveCertificate(pickupReq) + if err != nil { + return logical.ErrorResponse(err.Error()), nil } - certificate := strings.Join([]string{cert.Certificate}, "\n") - cs := append([]string{cert.Certificate}, cert.Chain...) - chain := strings.Join(cs, "\n") - log.Println("certificate: ", chain) - - //Parsing certificate and getting it's serial number - pemBlock, _ := pem.Decode([]byte(certificate)) + pemBlock, _ := pem.Decode([]byte(pcc.Certificate)) parsedCertificate, err := x509.ParseCertificate(pemBlock.Bytes) serialNumber := getHexFormatted(parsedCertificate.SerialNumber.Bytes(), ":") - encoded_key := pem.EncodeToMemory(pkey) - log.Println("Writing chain:", chain, "And key: ", string(encoded_key)) var entry *logical.StorageEntry - + chain := strings.Join(append([]string{pcc.Certificate}, pcc.Chain...), "\n") + pcc.AddPrivateKey(certReq.PrivateKey, []byte("")) if role.StorePrivateKey { entry, err = logical.StorageEntryJSON("", VenafiCert{ - Certificate: certificate, + Certificate: pcc.Certificate, CertificateChain: chain, - PrivateKey: string(encoded_key), + PrivateKey: pcc.PrivateKey, SerialNumber: serialNumber, }) } else { entry, err = logical.StorageEntryJSON("", VenafiCert{ - Certificate: certificate, + Certificate: pcc.Certificate, CertificateChain: chain, SerialNumber: serialNumber, }) @@ -169,8 +197,8 @@ func (b *backend) pathVenafiCertObtain(ctx context.Context, req *logical.Request "common_name": commonName, "serial_number": serialNumber, "certificate_chain": chain, - "certificate": certificate, - "private_key": string(encoded_key), + "certificate": pcc.Certificate, + "private_key": pcc.PrivateKey, } var logResp *logical.Response @@ -202,100 +230,6 @@ type privateKey struct { keyType string } -func createVenafiCSR(commonName string, altNames []string, pk privateKey) (*vcertificate.Request, *pem.Block, error) { - var err error - const defaultKeySize = 2048 - req := &vcertificate.Request{} - - if len(commonName) == 0 && len(altNames) == 0 { - return req, nil, fmt.Errorf("no domains specified on certificate") - } - if len(commonName) == 0 && len(altNames) > 0 { - commonName = altNames[0] - } - - //Obtain a certificate from the Venafi server - log.Printf("Using CN %s and SAN %s", commonName, altNames) - req.Subject.CommonName = commonName - //Adding alt names if exists - dnsnum := len(altNames) - if dnsnum > 0 { - req.DNSNames = make([]string, 0, dnsnum) - for i := 0; i < dnsnum; i++ { - val := altNames[i] - log.Printf("Adding SAN %s.", val) - req.DNSNames = append(req.DNSNames, val) - } - } - //Appending common name to the DNS names if it is not there - if !sliceContains(req.DNSNames, commonName) { - log.Printf("Adding CN %s to SAN because it wasn't included.", commonName) - req.DNSNames = append(req.DNSNames, commonName) - } - - log.Printf("Requested SAN: %s", req.DNSNames) - - if pk.keyType == "rsa" { - req.KeyLength = pk.keyBits - } else if pk.keyType == "ec" { - req.KeyType = vcertificate.KeyTypeECDSA - switch { - case pk.keyCurve == "P224": - req.KeyCurve = vcertificate.EllipticCurveP224 - case pk.keyCurve == "P256": - req.KeyCurve = vcertificate.EllipticCurveP256 - case pk.keyCurve == "P384": - req.KeyCurve = vcertificate.EllipticCurveP384 - case pk.keyCurve == "P521": - req.KeyCurve = vcertificate.EllipticCurveP521 - default: - - } - - } else { - return req, nil, fmt.Errorf("can't determine key algorithm %s", pk.keyType) - } - - switch req.KeyType { - case vcertificate.KeyTypeECDSA: - req.PrivateKey, err = vcertificate.GenerateECDSAPrivateKey(req.KeyCurve) - case vcertificate.KeyTypeRSA: - req.PrivateKey, err = vcertificate.GenerateRSAPrivateKey(req.KeyLength) - default: - log.Printf("Unable to generate certificate request, key type %s is not supported", req.KeyType.String()) - return nil, nil, err - } - - key, err := getPrivateKeyPEMBock(req.PrivateKey) - if err != nil { - return nil, nil, err - } - - //Setting up CSR - certificateRequest := x509.CertificateRequest{} - certificateRequest.Subject = req.Subject - certificateRequest.DNSNames = req.DNSNames - certificateRequest.EmailAddresses = req.EmailAddresses - certificateRequest.IPAddresses = req.IPAddresses - certificateRequest.Attributes = req.Attributes - - /* TODO: - zoneConfig, err = cs.Conn.ReadZoneConfiguration(cf.Zone) - zoneConfig.UpdateCertificateRequest(req) - ...should happen somewhere here before CSR is signed */ - - csr, err := x509.CreateCertificateRequest(rand.Reader, &certificateRequest, req.PrivateKey) - if err != nil { - return nil, nil, err - } - - req.CSR = csr - - req.CSR = pem.EncodeToMemory(vcertificate.GetCertificateRequestPEMBlock(req.CSR)) - - return req, key, nil -} - type VenafiCert struct { Certificate string `json:"certificate"` CertificateChain string `json:"certificate_chain"`