Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding support for chained intermediate CAs in pki backend #1694

Merged
merged 33 commits into from
Sep 28, 2016
Merged
Show file tree
Hide file tree
Changes from 30 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
8cafd44
Added Issuing CA Chain support
chrishoffman Aug 1, 2016
150ca81
fixing parsing of CA chain
chrishoffman Aug 1, 2016
63b4866
Adding config path for CA Chain
chrishoffman Aug 1, 2016
8260062
Store the CA Chain in the cert bundle
chrishoffman Aug 2, 2016
2cd3859
Adding issuing_ca_chain to secret data and pem_bundle
chrishoffman Aug 2, 2016
3e50925
Adding delete endpoint for CA chain
chrishoffman Aug 3, 2016
0c1a614
Making pem parsing consistent
chrishoffman Aug 4, 2016
98aeab4
Updating PEM parser to validate chain and handle more levels of inter…
chrishoffman Aug 4, 2016
01e21ef
Fixing public key checking for PEMParse
chrishoffman Aug 4, 2016
8175e17
Fixing test certs to have full chain
chrishoffman Aug 4, 2016
39ec2be
Removing parser overrides now that the parser handles more of the load
chrishoffman Aug 4, 2016
5e9110f
Adding coumentation
chrishoffman Aug 5, 2016
bebb375
Normalizing some names added for certificate chain, extracting some c…
chrishoffman Aug 5, 2016
07f777d
Updating docs
chrishoffman Aug 5, 2016
e565f22
Properly recreating the full CA path when issuing certificates
chrishoffman Aug 5, 2016
60598fa
Fixing type conversion test for removing issuing_ca_chain
chrishoffman Aug 5, 2016
14fda90
small cert path refactor
chrishoffman Aug 7, 2016
c854eca
removing chain delete, adding backend tests
chrishoffman Aug 8, 2016
23e2c41
Updating pki docs
chrishoffman Aug 8, 2016
3ba090b
Removing redundant /pki/config/chain endpoints
chrishoffman Aug 19, 2016
7e14f5b
Updating bundle verification on set-signed endpoint
chrishoffman Aug 19, 2016
f5866ab
cleanup error handling
chrishoffman Sep 14, 2016
2fb0914
DER format does not support more than one certificate
chrishoffman Sep 15, 2016
ebda83a
Converting issuing ca chain to slice
chrishoffman Sep 20, 2016
5e77c2f
fixing tests
chrishoffman Sep 20, 2016
31e82e1
Undoing unnecessary function split
chrishoffman Sep 20, 2016
c1f3ff1
Adding lenth check for outputting issuing_ca_chain
chrishoffman Sep 20, 2016
a5747a4
issuing_ca_chain -> ca_chain
chrishoffman Sep 20, 2016
a19f428
Update test with chain
chrishoffman Sep 24, 2016
ade4d2a
Adding endpiont to retrieve CA chain
chrishoffman Sep 24, 2016
12719db
Support returning CA Chain in both raw format and Vault response
chrishoffman Sep 26, 2016
04d3681
Updating documentation
chrishoffman Sep 27, 2016
b402e4d
minor cleanup
chrishoffman Sep 27, 2016
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions builtin/logical/pki/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ func Backend() *backend {
Unauthenticated: []string{
"cert/*",
"ca/pem",
"ca/chain",
"ca",
"crl/pem",
"crl",
Expand All @@ -45,6 +46,7 @@ func Backend() *backend {
pathIssue(&b),
pathRotateCRL(&b),
pathFetchCA(&b),
pathFetchCAChain(&b),
pathFetchCRL(&b),
pathFetchCRLViaCertPath(&b),
pathFetchValid(&b),
Expand Down
136 changes: 91 additions & 45 deletions builtin/logical/pki/backend_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,7 @@ func TestBackend_RSARoles_CSR(t *testing.T) {
Operation: logical.UpdateOperation,
Path: "config/ca",
Data: map[string]interface{}{
"pem_bundle": rsaCAKey + rsaCACert,
"pem_bundle": rsaCAKey + rsaCACert + rsaCAChain,
},
},
},
Expand Down Expand Up @@ -344,7 +344,7 @@ func checkCertsAndPrivateKey(keyType string, key crypto.Signer, usage x509.KeyUs
switch {
case parsedCertBundle.Certificate == nil:
return nil, fmt.Errorf("Did not find a certificate in the cert bundle")
case parsedCertBundle.IssuingCA == nil:
case len(parsedCertBundle.CAChain) == 0 || parsedCertBundle.CAChain[0].Certificate == nil:
return nil, fmt.Errorf("Did not find a CA in the cert bundle")
case parsedCertBundle.PrivateKey == nil:
return nil, fmt.Errorf("Did not find a private key in the cert bundle")
Expand Down Expand Up @@ -1920,52 +1920,98 @@ func TestBackend_PathFetchCertList(t *testing.T) {

const (
rsaCAKey string = `-----BEGIN RSA PRIVATE KEY-----
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
-----END RSA PRIVATE KEY-----
`
rsaCACert string = `-----BEGIN CERTIFICATE-----
MIIDUTCCAjmgAwIBAgIJAKM+z4MSfw2mMA0GCSqGSIb3DQEBCwUAMBsxGTAXBgNV
BAMMEFZhdWx0IFRlc3RpbmcgQ0EwHhcNMTUwNjAxMjA1MTUzWhcNMjUwNTI5MjA1
MTUzWjAbMRkwFwYDVQQDDBBWYXVsdCBUZXN0aW5nIENBMIIBIjANBgkqhkiG9w0B
AQEFAAOCAQ8AMIIBCgKCAQEA1eKB2nFbRqTFs7KyZjbzB5VRCBbnLZfEXVP1c3bH
e+YGjlfl34cy52dmancUzOf1/Jfo+VglocjTLVy5wHSGJwQYs8b6pEuuvAVo/6wU
L5Z7ZlQDR4kDe5Q+xgoRT6Bi/Bs57E+fNYgyUq/YAUY5WLuC+ZliCbJkLnb15Itu
P1yVUTDXTYORRE3qJS5RRol8D3QvteG9LyPEc7C+jsm5iBCagyxluzU0dnEOib5q
7xwZncoMbQz+rZH3QnwOij41FOGRPazrD5Mv6xLBkFnE5VAJ+GIgvd4bpOwvYMuo
fvF4PS7SFzxkGssMLlICap6PFpKz86DpAoDxPuoZeOhU4QIDAQABo4GXMIGUMB0G
A1UdDgQWBBTknN5eFxxo5aTlfq+G4ZXs3AsxWTAfBgNVHSMEGDAWgBTknN5eFxxo
5aTlfq+G4ZXs3AsxWTAxBgNVHR8EKjAoMCagJKAihiBodHRwOi8vbG9jYWxob3N0
OjgyMDAvdjEvcGtpL2NybDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB
BjANBgkqhkiG9w0BAQsFAAOCAQEAsINcA4PZm+OyldgNrwRVgxoSrhV1I9zszhc9
VV340ZWlpTTxFKVb/K5Hg+jMF9tv70X1HwlYdlutE6KdrsA3gks5zanh4/3zlrYk
ABNBmSD6SSU2HKX1bFCBAAS3YHONE5o1K5tzwLsMl5uilNf+Wid3NjFnQ4KfuYI5
loN/opnM6+a/O3Zua8RAuMMAv9wyqwn88aVuLvVzDNSMe5qC5kkuLGmRkNgY06rI
S/fXIHIOldeQxgYCqhdVmcDWJ1PtVaDfBsKVpRg1GRU8LUGw2E4AY+twd+J2FBfa
G/7g4koczXLoUM3OQXd5Aq2cs4SS1vODrYmgbioFsQ3eDHd1fg==
MIIDljCCAn6gAwIBAgIUQVapfgyAeDH9rAmpw3PQrhMcjRMwDQYJKoZIhvcNAQEL
BQAwMzExMC8GA1UEAxMoVmF1bHQgVGVzdGluZyBJbnRlcm1lZGlhdGUgU3ViIEF1
dGhvcml0eTAeFw0xNjA4MDcyMjUzNTRaFw0yNjA3MjQxMDU0MjRaMDcxNTAzBgNV
BAMTLFZhdWx0IFRlc3RpbmcgSW50ZXJtZWRpYXRlIFN1YiBTdWIgQXV0aG9yaXR5
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAmPQlK7xD5p+E8iLQ8XlV
mll5uU2NKMxKY3UF5tbh+0vkc+FyXmutLxxXAyYRPoztZ1g7ocr8XBFYsQPK26TF
c3TzrLL7bBEYHQArd8M+VUHjziB7zwwpbV7tG8WPqIScDKMNncavDcT8sDg3DUqb
8/zWkBD8WEYmsVr1VfKY5pFdxIZUkHP3/MkDpGmfrED9K5qPu17dIHTL2VYi4KxK
htIryapZTk6vDwRNfIYJD23QbQntSi1j0X9MTRUf3BIcd0Ch60aGvv0VSL+1NTaf
sZQD+z1RY/zNp9IUHz5bNIiePZ6lJrlddodAAXZ4sN1CMetf4bA2RXssxBEIb5Fy
iQIDAQABo4GdMIGaMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0G
A1UdDgQWBBRMeQTX9VkLqb1wzrvN/vFG09yhUTAfBgNVHSMEGDAWgBR0Oq2VTUBE
dOm6a1sKJTvdZMV5LjA3BgNVHREEMDAugixWYXVsdCBUZXN0aW5nIEludGVybWVk
aWF0ZSBTdWIgU3ViIEF1dGhvcml0eTANBgkqhkiG9w0BAQsFAAOCAQEAagYM7uFa
tUziraBkuU7cIyX83y7lYFsDhUse2hkpqmgO14oEOwFsDox1Jg2QGt4FEfJoCOXf
oCZZN8XmaWdSrfgs1nDmtE0xwXiX1z7JuJZ+Ygt3dcRHO1zs5tmuHLxrvMnKfIfG
bsGmES4mknt0qQ7tGhpyC+KgEmcVL1QQJXNjzCrw5iQ9sgvQt+oCqV28pxOUSYkq
FdrozmNdJwMgVADywiY/FqYJWgkixlFHQkPR7eiXwpahON+zRMk1JSgr/8N8fRDj
aqVBRppPzVU9joUME0vOc8cK3VozNe4iRkKNZFelHU2NPPJSDjRLVH9tJ7jPVOEA
/k6w2PwdoRom7Q==
-----END CERTIFICATE-----
`

rsaCAChain string = `-----BEGIN CERTIFICATE-----
MIIDijCCAnKgAwIBAgIUOiGo/1EOhRhuupTRGDYnqdALk/swDQYJKoZIhvcNAQEL
BQAwLzEtMCsGA1UEAxMkVmF1bHQgVGVzdGluZyBJbnRlcm1lZGlhdGUgQXV0aG9y
aXR5MB4XDTE2MDgwNzIyNTA1MloXDTI2MDcyODE0NTEyMlowMzExMC8GA1UEAxMo
VmF1bHQgVGVzdGluZyBJbnRlcm1lZGlhdGUgU3ViIEF1dGhvcml0eTCCASIwDQYJ
KoZIhvcNAQEBBQADggEPADCCAQoCggEBAMTPRQREwW3BEifNcm0XElMRB0GNTXHr
XCuNoFVsVBlIEsNVQkka+SHZcmNBdEcZLBXP/W3tBT82B48GVN8jyxAGfYZ5hoOQ
ed3GVft1A7lAnxcGvf5e9kfecKDcBB4G4rBhqdDNcAtklS2hV4uZUcVcEJKggpsQ
a1wZkCn8eg6sqEYG/SxPouwL52PblxIN+Dd57sBeqx4qdL297XR8LuLkxqftwUCZ
l2iFBnSDID/06ZmHDXA38I0n3jT2ZGjgPGFnIFKxRGq1vpVc3F5ga8qk+u66ybBu
xWHzINQrrryjELbl2YBTr6i0R9HnZle6OPcXMWp0JuGjtDC1xb5NmnkCAwEAAaOB
mTCBljAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU
dDqtlU1ARHTpumtbCiU73WTFeS4wHwYDVR0jBBgwFoAU+UO/nrlKr4COZCxLZSY/
ul+YMvMwMwYDVR0RBCwwKoIoVmF1bHQgVGVzdGluZyBJbnRlcm1lZGlhdGUgU3Vi
IEF1dGhvcml0eTANBgkqhkiG9w0BAQsFAAOCAQEAjgCuTXsLFkf0DVkfSsKReNwI
U/yBcP8Ttbx/ltanJGIVfD5TZoCnNTWm6RkML29ohfxI27sHTUhj+/6Ba0MRiLeI
FXdclXmHOU2dTHlrmUa0m/4cb5uYoiiEnpmyWL5k94fqPOZAvJcFHnP3db4vsaUW
47YcOvJbPSJqFXZHadqnsf3Fur5NCeTkIk6yZSvwTaZJT0JIWcqfE5LK3mYAMMC3
iPaIa1cYqOZhWx9ilQfW6u6WxWeOphGuDIusP7Q4qc2Dr9sekyD59dfIYsroK5TP
QVJb69nIYINpYdg3l3VNmmkY4G30N9QNs6acaH49rYzLcRX6tLBgPklO6d+TPA==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDejCCAmKgAwIBAgIUULdIdrdK4Y8d+XM9fuOpDlNcJIYwDQYJKoZIhvcNAQEL
BQAwJzElMCMGA1UEAxMcVmF1bHQgVGVzdGluZyBSb290IEF1dGhvcml0eTAeFw0x
NjA4MDcyMjUwNTFaFw0yNjA4MDExODUxMjFaMC8xLTArBgNVBAMTJFZhdWx0IFRl
c3RpbmcgSW50ZXJtZWRpYXRlIEF1dGhvcml0eTCCASIwDQYJKoZIhvcNAQEBBQAD
ggEPADCCAQoCggEBANXa6U+MDiUrryeZeGxgkmAZdrm9wCKz/6SmxYSebKr8aZwD
nfbsPLRFxU6BXp9Nc6pP7e8HLBv6PtFTQG389zxOBwAHxZQvUsFESumUd64oTLRG
J+AErTh7rtSWbLZsgDtQVvpx+6mKkvm53f/aKcq+DbqAFOg6slYOaQix0ZvP/qL0
iWGIPr1JZk9uBJOUuIUBJdbsgTk+KQqJL9M6up8bCnM0noCafwrNKwZWtsbkfOZE
OLSycdzCEBeHejpHTIU0vgAkdj63oEy2AbK3hMPxKzNthL3DX6W0tssoVgL//92i
oSfpDTxiXqqdr+J3accpsAvA+F+D2TqaxdAfjLcCAwEAAaOBlTCBkjAOBgNVHQ8B
Af8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU+UO/nrlKr4COZCxL
ZSY/ul+YMvMwHwYDVR0jBBgwFoAUA3jY4OUWi1Y7zQgM7S9QeXjNgIQwLwYDVR0R
BCgwJoIkVmF1bHQgVGVzdGluZyBJbnRlcm1lZGlhdGUgQXV0aG9yaXR5MA0GCSqG
SIb3DQEBCwUAA4IBAQA9VJt92LsOOegtAx35rr41LSSfPWB2SCKg0fphL2gMPO5y
fE2u8O5TF5dJWJ1fF3cg9/XK30Ohdl1/ujfHbVcX+O6Sb3cgwKTQQOhTdsAZKFRT
RPHaf/Ja8uqAXMITApxOp7YiYQwukwZr+OsKi66s+zhlfI790PoQbC3UJvgmNDmv
V5oP63mw4yhqRNPn4NOjzoC/hJSIM0AIdRB1nx2rsSUw0P354R1j9gO43L/Lj33S
NEaPmw+SC3Tbcx4yxeKnTvGdu3sw/ndmZkCjaq5jxgTy9FONqT45TPJOyk29o5gl
+AVQz5fD2M3C1L/sZIPH2OQbXxePHcsvUZVgaKyk
-----END CERTIFICATE-----
`

Expand Down
39 changes: 32 additions & 7 deletions builtin/logical/pki/cert_util.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package pki

import (
"bytes"
"crypto/ecdsa"
"crypto/rand"
"crypto/rsa"
Expand Down Expand Up @@ -58,6 +59,25 @@ type caInfoBundle struct {
URLs *urlEntries
}

func (b *caInfoBundle) GetCAChain() []*certutil.CertBlock {
chain := []*certutil.CertBlock{}

// Include issuing CA in Chain, not including Root Authority
if len(b.Certificate.AuthorityKeyId) > 0 &&
!bytes.Equal(b.Certificate.AuthorityKeyId, b.Certificate.SubjectKeyId) {

chain = append(chain, &certutil.CertBlock{
Certificate: b.Certificate,
Bytes: b.CertificateBytes,
})
if b.CAChain != nil && len(b.CAChain) > 0 {
chain = append(chain, b.CAChain...)
}
}

return chain
}

var (
hostnameRegex = regexp.MustCompile(`^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])$`)
oidExtensionBasicConstraints = []int{2, 5, 29, 19}
Expand Down Expand Up @@ -856,11 +876,17 @@ func createCertificate(creationInfo *creationBundle) (*certutil.ParsedCertBundle
}

if creationInfo.SigningBundle != nil {
result.IssuingCABytes = creationInfo.SigningBundle.CertificateBytes
result.IssuingCA = creationInfo.SigningBundle.Certificate
} else {
result.IssuingCABytes = result.CertificateBytes
result.IssuingCA = result.Certificate
if len(creationInfo.SigningBundle.Certificate.AuthorityKeyId) > 0 &&
!bytes.Equal(creationInfo.SigningBundle.Certificate.AuthorityKeyId, creationInfo.SigningBundle.Certificate.SubjectKeyId) {

result.CAChain = []*certutil.CertBlock{
&certutil.CertBlock{
Certificate: creationInfo.SigningBundle.Certificate,
Bytes: creationInfo.SigningBundle.CertificateBytes,
},
}
result.CAChain = append(result.CAChain, creationInfo.SigningBundle.CAChain...)
}
}

return result, nil
Expand Down Expand Up @@ -1011,8 +1037,7 @@ func signCertificate(creationInfo *creationBundle,
return nil, errutil.InternalError{Err: fmt.Sprintf("unable to parse created certificate: %s", err)}
}

result.IssuingCABytes = creationInfo.SigningBundle.CertificateBytes
result.IssuingCA = creationInfo.SigningBundle.Certificate
result.CAChain = creationInfo.SigningBundle.GetCAChain()

return result, nil
}
17 changes: 0 additions & 17 deletions builtin/logical/pki/path_config_ca.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,23 +50,6 @@ func (b *backend) pathCAWrite(
return logical.ErrorResponse("private key not found in the PEM bundle"), nil
}

// Handle the case of a self-signed certificate; the parsing function will
// see the CA and put it into the issuer
if parsedBundle.Certificate == nil &&
parsedBundle.IssuingCA != nil {
equal, err := certutil.ComparePublicKeys(parsedBundle.IssuingCA.PublicKey, parsedBundle.PrivateKey.Public())
if err != nil {
return logical.ErrorResponse(fmt.Sprintf(
"got only a CA and private key but could not verify the public keys match: %v", err)), nil
}
if !equal {
return logical.ErrorResponse(
"got only a CA and private key but keys do not match"), nil
}
parsedBundle.Certificate = parsedBundle.IssuingCA
parsedBundle.CertificateBytes = parsedBundle.IssuingCABytes
}

if parsedBundle.Certificate == nil {
return logical.ErrorResponse("no certificate found in the PEM bundle"), nil
}
Expand Down
45 changes: 45 additions & 0 deletions builtin/logical/pki/path_fetch.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,20 @@ func pathFetchCA(b *backend) *framework.Path {
}
}

// Returns the CA chain in raw format
func pathFetchCAChain(b *backend) *framework.Path {
return &framework.Path{
Pattern: `ca/chain`,

Callbacks: map[logical.Operation]framework.OperationFunc{
logical.ReadOperation: b.pathFetchReadCAChain,
},

HelpSynopsis: pathFetchHelpSyn,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The text for this probably needs some changes to accommodate the new info.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I need to add the ca_chain to a few other documents too.

HelpDescription: pathFetchHelpDesc,
}
}

// Returns the CRL in raw format
func pathFetchCRL(b *backend) *framework.Path {
return &framework.Path{
Expand Down Expand Up @@ -96,6 +110,37 @@ func (b *backend) pathFetchCertList(req *logical.Request, data *framework.FieldD
return logical.ListResponse(entries), nil
}

func (b *backend) pathFetchReadCAChain(req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
caInfo, err := fetchCAInfo(req)
switch err.(type) {
case errutil.UserError:
return nil,
errutil.UserError{Err: fmt.Sprintf("Could not fetch the CA certificate: %s", err)}
case errutil.InternalError:
return nil,
errutil.InternalError{Err: fmt.Sprintf("Error fetching CA certificate: %s", err)}
}

caChain := caInfo.GetCAChain()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this return nil if there is no certs? Then you can easily fast-path that with a nil, nil response which will 204.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It may make sense. This is only used in two places right now and should be pretty easy to transition to this way.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Returning nil here bypasses the Response where we want to provide raw output (this is currently a raw endpoint). When I returned nil here, it fast-path to a default response of 404 with an empty error array. I think we should just keep it the way it is.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, you're right -- a GET will return a 404 on no data returned, unlike a POST or DELETE.

Keeping the way it is sounds fine but if there's no data we should return 204, not 200.

certs := []byte{}
for _, ca := range caChain {
block := pem.Block{
Type: "CERTIFICATE",
Bytes: ca.Bytes,
}
certs = append(certs, pem.EncodeToMemory(&block)...)
}

response := &logical.Response{
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This tweaks me a little in a consistency sense.

ca returns a Vault response; ca/pem returns a raw response.
crl returns a Vault response; crl/pem returns a raw response.

It feels like ca/chain should return a Vault response and ca/chain/pem should return a raw response. Although in that scenario it's probably better to just use ca_chain and ca_chain/pem.

The reason for having both is so that the Vault CLI (and API clients, generally) can do a read against one of the endpoints and understand how to parse it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both ca and ca/pam return a raw response, ca being in DER format. I think you were thinking about cert/ca which returns a Vault response. Same for crl. The reason I didn't store the ca_chain by a special serial in the cert store was because it expects the bytes of a single certificate so there was no easy way to store the slice of []byte. I also can't support the DER format since it could be multiple certificates and that is why I left the /pem off the end.

You do bring up a valid point about API clients, which I didn't fully consider. I'll think about the best way to support the cert/ca_chain endpoint which would be more suitable for API clients.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you were thinking about cert/ca which returns a Vault response.
I was! Mea culpa. So this seems fine as-is, but it would be nice to support cert/ca_chain. It could just build up the chain rather than attempt to fetch one from the backend, unless this is difficult for a reason I'm not thinking of right now.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was able to merge the fetching of the ca chain into the standard fetch method. It is a little different in how this is generated vs. the rest of the certs but I think it is ok.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Neet-o!

Data: map[string]interface{}{
logical.HTTPContentType: "application/pkix-cert",
logical.HTTPRawBody: certs,
}}
response.Data[logical.HTTPStatusCode] = 200

return response, nil
}

func (b *backend) pathFetchRead(req *logical.Request, data *framework.FieldData) (response *logical.Response, retErr error) {
var serial, pemType, contentType string
var certEntry, revokedEntry *logical.StorageEntry
Expand Down
22 changes: 4 additions & 18 deletions builtin/logical/pki/path_intermediate.go
Original file line number Diff line number Diff line change
Expand Up @@ -140,15 +140,6 @@ func (b *backend) pathSetSignedIntermediate(
}
}

// If only one certificate is provided and it's a CA
// the parsing will assign it to the IssuingCA, so move it over
if inputBundle.Certificate == nil && inputBundle.IssuingCA != nil {
inputBundle.Certificate = inputBundle.IssuingCA
inputBundle.IssuingCA = nil
inputBundle.CertificateBytes = inputBundle.IssuingCABytes
inputBundle.IssuingCABytes = nil
}

if inputBundle.Certificate == nil {
return logical.ErrorResponse("supplied certificate could not be successfully parsed"), nil
}
Expand Down Expand Up @@ -179,15 +170,6 @@ func (b *backend) pathSetSignedIntermediate(
return nil, fmt.Errorf("saved key could not be parsed successfully")
}

equal, err := certutil.ComparePublicKeys(parsedCB.PrivateKey.Public(), inputBundle.Certificate.PublicKey)
if err != nil {
return logical.ErrorResponse(fmt.Sprintf(
"error matching public keys: %v", err)), nil
}
if !equal {
return logical.ErrorResponse("key in certificate does not match stored key"), nil
}

inputBundle.PrivateKey = parsedCB.PrivateKey
inputBundle.PrivateKeyType = parsedCB.PrivateKeyType
inputBundle.PrivateKeyBytes = parsedCB.PrivateKeyBytes
Expand All @@ -196,6 +178,10 @@ func (b *backend) pathSetSignedIntermediate(
return logical.ErrorResponse("the given certificate is not marked for CA use and cannot be used with this backend"), nil
}

if err := inputBundle.Verify(); err != nil {
return nil, fmt.Errorf("verification of parsed bundle failed: %s", err)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the reason for removing this? This is ensuring that the certificate that you're setting matches the backend's stored key. Seems like a decent check to have.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see; this is handled later in Verify.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct. All the chain verification logic was consolidated in Verify.

}

cb, err = inputBundle.ToCertBundle()
if err != nil {
return nil, fmt.Errorf("error converting raw values into cert bundle: %s", err)
Expand Down
Loading