Skip to content

Commit

Permalink
Merge pull request #99 from Venafi/fix-privatekey-behavior
Browse files Browse the repository at this point in the history
Fix behavior on stored private keys and mismatch between certificate and key
  • Loading branch information
luispresuelVenafi authored May 12, 2022
2 parents 6b18276 + 81c4043 commit 8a98ead
Show file tree
Hide file tree
Showing 37 changed files with 1,719 additions and 245 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
# v0.10.3 (May 12, 2022)
Fixed a bug about storing private keys behavior and validation of certificate mismatch

# v0.10.2 (March 24, 2022)
Fixed issue with revocation while disabling secrets engine

# v0.10.1 (March 10, 2022)
Fix for a bug with the use of a synchronized block in pathVenafiCertObtain function.

Expand Down
3 changes: 2 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@ go 1.13

require (
github.com/Venafi/vcert v3.18.4+incompatible
github.com/Venafi/vcert/v4 v4.17.1
github.com/Venafi/vcert/v4 v4.19.0
github.com/hashicorp/go-hclog v0.14.1
github.com/hashicorp/vault/api v1.0.4
github.com/hashicorp/vault/sdk v0.1.13
github.com/onsi/ginkgo v1.14.0
github.com/onsi/gomega v1.10.1
github.com/rendon/testcli v0.0.0-20161027181003-6283090d169f
github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a
)
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/Venafi/vcert v3.18.4+incompatible h1:mDXSjd+EpXa8YEkEo9Oad19E270aiPJJMhjoKs63b+8=
github.com/Venafi/vcert v3.18.4+incompatible/go.mod h1:3dpfrCI+31cDZosD+1UX8GFziVFORaegByXtzT1dwNo=
github.com/Venafi/vcert/v4 v4.17.0 h1:iynxM067DeV37c3AzRjcElNVJeDxwf/Vx2wc3bsBqoM=
github.com/Venafi/vcert/v4 v4.17.0/go.mod h1:VcojF47VAzBnYHSRrb0SwOCmMpWJczajTuPiZNDJJSo=
github.com/Venafi/vcert/v4 v4.19.0 h1:/zIl9+s6uIjtI/LazPplrcSgThbwJkUx1XbyET3u8Iw=
github.com/Venafi/vcert/v4 v4.19.0/go.mod h1:VcojF47VAzBnYHSRrb0SwOCmMpWJczajTuPiZNDJJSo=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
Expand Down
53 changes: 40 additions & 13 deletions plugin/pki/path_venafi_cert_enroll.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package pki

import (
"context"
"crypto/rand"
"crypto/tls"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
Expand Down Expand Up @@ -120,10 +122,6 @@ func (b *backend) pathVenafiIssue(ctx context.Context, req *logical.Request, dat
return logical.ErrorResponse("role key type \"any\" not allowed for issuing certificates, only signing"), nil
}

if role.ServiceGenerated && data.Get("key_password").(string) == "" {
return logical.ErrorResponse("for service generated csr, \"key_password\" must not be empty"), nil
}

return b.pathVenafiCertObtain(ctx, req, data, role, false)
}

Expand Down Expand Up @@ -156,6 +154,8 @@ func (b *backend) pathVenafiCertObtain(ctx context.Context, req *logical.Request
return nil, logical.ErrReadOnly
}

keyPass := fmt.Sprintf("t%d-%s.tem.pwd", time.Now().Unix(), randRunes(4))

b.Logger().Debug("Getting the role\n")
roleName := data.Get("role").(string)

Expand Down Expand Up @@ -281,7 +281,7 @@ func (b *backend) pathVenafiCertObtain(ctx context.Context, req *logical.Request

if role.ServiceGenerated {
pickupReq.FetchPrivateKey = true
pickupReq.KeyPassword = data.Get("key_password").(string)
pickupReq.KeyPassword = keyPass
}

pcc, err := cl.RetrieveCertificate(pickupReq)
Expand All @@ -306,30 +306,48 @@ func (b *backend) pathVenafiCertObtain(ctx context.Context, req *logical.Request
format := ""
privateKeyFormat, ok := data.GetOk("private_key_format")
if ok {
if privateKeyFormat == "der" {
if privateKeyFormat == LEGACY_PEM {
format = "legacy-pem"
}
}

// Local generated
if !signCSR && !role.ServiceGenerated {
err = pcc.AddPrivateKey(certReq.PrivateKey, []byte(data.Get("key_password").(string)), format)
privateKeyPemBytes, err := certificate.GetPrivateKeyPEMBock(certReq.PrivateKey, format)
if err != nil {
return nil, err
}
} else {
if reqData.keyPassword != "" && privateKeyFormat == "der" {
privateKey, err := util.DecryptPkcs8PrivateKey(pcc.PrivateKey, reqData.keyPassword)
privateKeyPem := string(pem.EncodeToMemory(privateKeyPemBytes))
pcc.PrivateKey = privateKeyPem
} else if role.ServiceGenerated {
// Service generated
privateKey, err := DecryptPkcs8PrivateKey(pcc.PrivateKey, keyPass)
if err != nil {
return nil, err
}
block, _ := pem.Decode([]byte(privateKey))
if privateKeyFormat == LEGACY_PEM {
encrypted, err := util.X509EncryptPEMBlock(rand.Reader, "RSA PRIVATE KEY", block.Bytes, []byte(keyPass), util.PEMCipherAES256)
if err != nil {
return nil, err
}
privateKey, err = util.EncryptPkcs1PrivateKey(privateKey, reqData.keyPassword)
encryptedPem := pem.EncodeToMemory(encrypted)
privateKeyBytes, err := getPrivateKey(encryptedPem, keyPass)
if err != nil {
return nil, err
}
pcc.PrivateKey = privateKey
privateKey = string(privateKeyBytes)
}
pcc.PrivateKey = privateKey
} else {
b.Logger().Debug("CSR is being provided, not processing private key")
}

_, err = tls.X509KeyPair([]byte(pcc.Certificate), []byte(pcc.PrivateKey))
if err != nil {
fmt.Errorf("the certificate returned by Venafi did not contain the requested private key," +
" key pair has been discarded")
}
if role.StorePrivateKey && !signCSR {
entry, err = logical.StorageEntryJSON("", VenafiCert{
Certificate: pcc.Certificate,
Expand Down Expand Up @@ -390,7 +408,16 @@ func (b *backend) pathVenafiCertObtain(ctx context.Context, req *logical.Request
"expiration": expirationSec,
}
if !signCSR {
respData["private_key"] = pcc.PrivateKey
keyPassword := data.Get("key_password").(string)
if keyPassword == "" {
respData["private_key"] = pcc.PrivateKey
} else {
encryptedPrivateKeyPem, err := encryptPrivateKey(pcc.PrivateKey, keyPassword)
if err != nil {
return nil, err
}
respData["private_key"] = encryptedPrivateKeyPem
}
}

var logResp *logical.Response
Expand Down
12 changes: 12 additions & 0 deletions plugin/pki/path_venafi_cert_read.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ func pathVenafiCertRead(b *backend) *framework.Path {
Type: framework.TypeString,
Description: "Common name or serial number of desired certificate",
},
"key_password": {
Type: framework.TypeString,
Description: "Password for encrypting private key",
},
},
Callbacks: map[logical.Operation]framework.OperationFunc{
logical.ReadOperation: b.pathVenafiCertRead,
Expand Down Expand Up @@ -61,6 +65,14 @@ func (b *backend) pathVenafiCertRead(ctx context.Context, req *logical.Request,
"certificate": cert.Certificate,
"private_key": cert.PrivateKey,
}
keyPassword := data.Get("key_password").(string)
if keyPassword != "" {
encryptedPrivateKeyPem, err := encryptPrivateKey(cert.PrivateKey, keyPassword)
if err != nil {
return nil, err
}
respData["private_key"] = encryptedPrivateKeyPem
}

return &logical.Response{
//Data: structs.New(cert).Map(),
Expand Down
116 changes: 116 additions & 0 deletions plugin/pki/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,19 @@ package pki
import (
"bytes"
"context"
"crypto/rand"
"crypto/tls"
"crypto/x509"
"encoding/pem"
"fmt"
"github.com/Venafi/vcert/v4"
"github.com/Venafi/vcert/v4/pkg/endpoint"
"github.com/Venafi/vcert/v4/pkg/util"
"github.com/Venafi/vcert/v4/pkg/venafi/tpp"
"github.com/hashicorp/vault/sdk/logical"
"github.com/youmark/pkcs8"
"io/ioutil"
mathrand "math/rand"
"net"
"net/http"
"os"
Expand All @@ -24,6 +29,7 @@ const (
role_ttl_test_property = int(120)
ttl_test_property = int(48)
HTTP_UNAUTHORIZED = 401
LEGACY_PEM = "der"
)

func sliceContains(slice []string, item string) bool {
Expand Down Expand Up @@ -422,3 +428,113 @@ func getAccessData(cfg *vcert.Config) (tpp.OauthRefreshAccessTokenResponse, erro
return tokenInfoResponse, err

}

func randRunes(n int) string {
var letterRunes = []rune("abcdefghijklmnopqrstuvwxyz")
b := make([]rune, n)
for i := range b {
/* #nosec */
b[i] = letterRunes[mathrand.Intn(len(letterRunes))]
}
return string(b)
}

func getPrivateKey(keyBytes []byte, passphrase string) ([]byte, error) {
// this section makes some small changes to code from notary/tuf/utils/x509.go
pemBlock, _ := pem.Decode(keyBytes)
if pemBlock == nil {
return nil, fmt.Errorf("no valid private key found")
}

var err error
if util.X509IsEncryptedPEMBlock(pemBlock) {
keyBytes, err = util.X509DecryptPEMBlock(pemBlock, []byte(passphrase))
if err != nil {
return nil, fmt.Errorf("private key is encrypted, but could not decrypt it: %s", err.Error())
}
keyBytes = pem.EncodeToMemory(&pem.Block{Type: pemBlock.Type, Bytes: keyBytes})
}

return keyBytes, nil
}

func encryptPrivateKey(privateKey string, password string) (string, error) {
var encryptedPrivateKeyPem string
var err error
encryptedPrivateKeyPem, err = EncryptPkcs1PrivateKey(privateKey, password)
if err != nil {
// We try PKCS8
encryptedPrivateKeyPem, err = encryptPkcs8PrivateKey(privateKey, password)
if err != nil {
return "", err
}
}
return encryptedPrivateKeyPem, nil
}

func DecryptPkcs8PrivateKey(privateKey string, password string) (string, error) {

block, _ := pem.Decode([]byte(privateKey))
key, _, err := pkcs8.ParsePrivateKey(block.Bytes, []byte(password))

if err != nil {
return "", err
}

pemType := "PRIVATE KEY"

privateKeyBytes, err := pkcs8.MarshalPrivateKey(key, nil, nil)

if err != nil {
return "", err
}

pemBytes := pem.EncodeToMemory(&pem.Block{Type: pemType, Bytes: privateKeyBytes})

return string(pemBytes), nil
}

func EncryptPkcs1PrivateKey(privateKey string, password string) (string, error) {

block, _ := pem.Decode([]byte(privateKey))

keyType := util.GetPrivateKeyType(privateKey, password)
var encrypted *pem.Block
var err error
if keyType == "RSA PRIVATE KEY" {
encrypted, err = util.X509EncryptPEMBlock(rand.Reader, "RSA PRIVATE KEY", block.Bytes, []byte(password), util.PEMCipherAES256)
if err != nil {
return "", nil
}
} else if keyType == "EC PRIVATE KEY" {
encrypted, err = util.X509EncryptPEMBlock(rand.Reader, "EC PRIVATE KEY", block.Bytes, []byte(password), util.PEMCipherAES256)
if err != nil {
return "", nil
}
} else {
return "", fmt.Errorf("unable to encrypt key in PKCS1 format")
}
return string(pem.EncodeToMemory(encrypted)), nil
}

func encryptPkcs8PrivateKey(privateKey string, password string) (string, error) {
block, _ := pem.Decode([]byte(privateKey))
key, _, err := pkcs8.ParsePrivateKey(block.Bytes, []byte(""))
if err != nil {
return "", err
}
privateKeyBytes1, err := pkcs8.MarshalPrivateKey(key, []byte(password), nil)
if err != nil {
return "", err
}

keyType := "ENCRYPTED PRIVATE KEY"

// Generate a pem block with the private key
keyPemBytes := pem.EncodeToMemory(&pem.Block{
Type: keyType,
Bytes: privateKeyBytes1,
})
encryptedPrivateKeyPem := string(keyPemBytes)
return encryptedPrivateKeyPem, nil
}
2 changes: 0 additions & 2 deletions vendor/github.com/Venafi/vcert/v4/CODEOWNERS

This file was deleted.

13 changes: 13 additions & 0 deletions vendor/github.com/Venafi/vcert/v4/Dockerfile

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

29 changes: 21 additions & 8 deletions vendor/github.com/Venafi/vcert/v4/Makefile

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 8a98ead

Please sign in to comment.