pki: add subject key identifier to read key response (#20642)

* pki: add subject key identifier to read key response

This will be helpful for the Terraform Vault Provider to detect
migration of pre-1.11 exported keys (from CA generation) into post-1.11
Vault.

* add changelog

* Update builtin/logical/pki/path_fetch_keys.go

Co-authored-by: Alexander Scheel <alex.scheel@hashicorp.com>

* check for managed key first

* Validate the SKID matches on root CAs

Signed-off-by: Alexander Scheel <alex.scheel@hashicorp.com>

* Validate SKID matches on int CAs

Signed-off-by: Alexander Scheel <alex.scheel@hashicorp.com>

* Fix formatting of tests

Signed-off-by: Alexander Scheel <alex.scheel@hashicorp.com>

---------

Signed-off-by: Alexander Scheel <alex.scheel@hashicorp.com>
Co-authored-by: Alexander Scheel <alex.scheel@hashicorp.com>
This commit is contained in:
John-Michael Faircloth 2023-05-18 11:49:22 -05:00 committed by GitHub
parent 03a684eb7e
commit f9541a1c96
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 59 additions and 5 deletions

View File

@ -2400,6 +2400,14 @@ func TestBackend_Root_Idempotency(t *testing.T) {
require.NotNil(t, resp, "expected ca info")
keyId1 := resp.Data["key_id"]
issuerId1 := resp.Data["issuer_id"]
cert := parseCert(t, resp.Data["certificate"].(string))
certSkid := certutil.GetHexFormatted(cert.SubjectKeyId, ":")
// -> Validate the SKID matches between the root cert and the key
resp, err = CBRead(b, s, "key/"+keyId1.(keyID).String())
require.NoError(t, err)
require.NotNil(t, resp, "expected a response")
require.Equal(t, resp.Data["subject_key_id"], certSkid)
resp, err = CBRead(b, s, "cert/ca_chain")
require.NoError(t, err, "error reading ca_chain: %v", err)
@ -2414,6 +2422,14 @@ func TestBackend_Root_Idempotency(t *testing.T) {
require.NotNil(t, resp, "expected ca info")
keyId2 := resp.Data["key_id"]
issuerId2 := resp.Data["issuer_id"]
cert = parseCert(t, resp.Data["certificate"].(string))
certSkid = certutil.GetHexFormatted(cert.SubjectKeyId, ":")
// -> Validate the SKID matches between the root cert and the key
resp, err = CBRead(b, s, "key/"+keyId2.(keyID).String())
require.NoError(t, err)
require.NotNil(t, resp, "expected a response")
require.Equal(t, resp.Data["subject_key_id"], certSkid)
// Make sure that we actually generated different issuer and key values
require.NotEqual(t, keyId1, keyId2)
@ -2543,13 +2559,19 @@ func TestBackend_SignIntermediate_AllowedPastCA(t *testing.T) {
"common_name": "myint.com",
})
schema.ValidateResponse(t, schema.GetResponseSchema(t, b_root.Route("intermediate/generate/internal"), logical.UpdateOperation), resp, true)
require.Contains(t, resp.Data, "key_id")
intKeyId := resp.Data["key_id"].(keyID)
csr := resp.Data["csr"]
resp, err = CBRead(b_int, s_int, "key/"+intKeyId.String())
require.NoError(t, err)
require.NotNil(t, resp, "expected a response")
intSkid := resp.Data["subject_key_id"].(string)
if err != nil {
t.Fatal(err)
}
csr := resp.Data["csr"]
_, err = CBWrite(b_root, s_root, "sign/test", map[string]interface{}{
"common_name": "myint.com",
"csr": csr,
@ -2584,6 +2606,10 @@ func TestBackend_SignIntermediate_AllowedPastCA(t *testing.T) {
if len(resp.Warnings) == 0 {
t.Fatalf("expected warnings, got %#v", *resp)
}
cert := parseCert(t, resp.Data["certificate"].(string))
certSkid := certutil.GetHexFormatted(cert.SubjectKeyId, ":")
require.Equal(t, intSkid, certSkid)
}
func TestBackend_ConsulSignLeafWithLegacyRole(t *testing.T) {

View File

@ -16,6 +16,7 @@ const (
keyIdParam = "key_id"
keyTypeParam = "key_type"
keyBitsParam = "key_bits"
skidParam = "subject_key_id"
)
// addIssueAndSignCommonFields adds fields common to both CA and non-CA issuing

View File

@ -5,9 +5,11 @@ package pki
import (
"context"
"crypto"
"fmt"
"net/http"
"github.com/hashicorp/vault/sdk/helper/certutil"
"github.com/hashicorp/vault/sdk/helper/errutil"
"github.com/hashicorp/vault/sdk/framework"
@ -144,6 +146,11 @@ func buildPathKey(b *backend, pattern string, displayAttrs *framework.DisplayAtt
Description: `Key Type`,
Required: true,
},
"subject_key_id": {
Type: framework.TypeString,
Description: `RFC 5280 Subject Key Identifier of the public counterpart`,
Required: false,
},
"managed_key_id": {
Type: framework.TypeString,
Description: `Managed Key Id`,
@ -247,6 +254,7 @@ func (b *backend) pathGetKeyHandler(ctx context.Context, req *logical.Request, d
keyTypeParam: string(key.PrivateKeyType),
}
var pkForSkid crypto.PublicKey
if key.isManagedPrivateKey() {
managedKeyUUID, err := key.getManagedKeyUUID()
if err != nil {
@ -258,13 +266,29 @@ func (b *backend) pathGetKeyHandler(ctx context.Context, req *logical.Request, d
return nil, errutil.InternalError{Err: fmt.Sprintf("failed fetching managed key info from key id %s (%s): %v", key.ID, key.Name, err)}
}
pkForSkid, err = getManagedKeyPublicKey(sc.Context, sc.Backend, managedKeyUUID)
if err != nil {
return nil, err
}
// To remain consistent across the api responses (mainly generate root/intermediate calls), return the actual
// type of key, not that it is a managed key.
respData[keyTypeParam] = string(keyInfo.keyType)
respData[managedKeyIdArg] = string(keyInfo.uuid)
respData[managedKeyNameArg] = string(keyInfo.name)
} else {
pkForSkid, err = getPublicKeyFromBytes([]byte(key.PrivateKey))
if err != nil {
return nil, err
}
}
skid, err := certutil.GetSubjectKeyID(pkForSkid)
if err != nil {
return nil, err
}
respData[skidParam] = certutil.GetHexFormatted([]byte(skid), ":")
return &logical.Response{Data: respData}, nil
}

3
changelog/20642.txt Normal file
View File

@ -0,0 +1,3 @@
```release-note:improvement
secrets/pki: add subject key identifier to read key response
```

View File

@ -126,7 +126,7 @@ func GetSubjKeyID(privateKey crypto.Signer) ([]byte, error) {
if privateKey == nil {
return nil, errutil.InternalError{Err: "passed-in private key is nil"}
}
return getSubjectKeyID(privateKey.Public())
return GetSubjectKeyID(privateKey.Public())
}
// Returns the explicit SKID when used for cross-signing, else computes a new
@ -136,10 +136,10 @@ func getSubjectKeyIDFromBundle(data *CreationBundle) ([]byte, error) {
return data.Params.SKID, nil
}
return getSubjectKeyID(data.CSR.PublicKey)
return GetSubjectKeyID(data.CSR.PublicKey)
}
func getSubjectKeyID(pub interface{}) ([]byte, error) {
func GetSubjectKeyID(pub interface{}) ([]byte, error) {
var publicKeyBytes []byte
switch pub := pub.(type) {
case *rsa.PublicKey: