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:
parent
03a684eb7e
commit
f9541a1c96
|
@ -2400,6 +2400,14 @@ func TestBackend_Root_Idempotency(t *testing.T) {
|
||||||
require.NotNil(t, resp, "expected ca info")
|
require.NotNil(t, resp, "expected ca info")
|
||||||
keyId1 := resp.Data["key_id"]
|
keyId1 := resp.Data["key_id"]
|
||||||
issuerId1 := resp.Data["issuer_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")
|
resp, err = CBRead(b, s, "cert/ca_chain")
|
||||||
require.NoError(t, err, "error reading ca_chain: %v", err)
|
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")
|
require.NotNil(t, resp, "expected ca info")
|
||||||
keyId2 := resp.Data["key_id"]
|
keyId2 := resp.Data["key_id"]
|
||||||
issuerId2 := resp.Data["issuer_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
|
// Make sure that we actually generated different issuer and key values
|
||||||
require.NotEqual(t, keyId1, keyId2)
|
require.NotEqual(t, keyId1, keyId2)
|
||||||
|
@ -2543,13 +2559,19 @@ func TestBackend_SignIntermediate_AllowedPastCA(t *testing.T) {
|
||||||
"common_name": "myint.com",
|
"common_name": "myint.com",
|
||||||
})
|
})
|
||||||
schema.ValidateResponse(t, schema.GetResponseSchema(t, b_root.Route("intermediate/generate/internal"), logical.UpdateOperation), resp, true)
|
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 {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
csr := resp.Data["csr"]
|
|
||||||
|
|
||||||
_, err = CBWrite(b_root, s_root, "sign/test", map[string]interface{}{
|
_, err = CBWrite(b_root, s_root, "sign/test", map[string]interface{}{
|
||||||
"common_name": "myint.com",
|
"common_name": "myint.com",
|
||||||
"csr": csr,
|
"csr": csr,
|
||||||
|
@ -2584,6 +2606,10 @@ func TestBackend_SignIntermediate_AllowedPastCA(t *testing.T) {
|
||||||
if len(resp.Warnings) == 0 {
|
if len(resp.Warnings) == 0 {
|
||||||
t.Fatalf("expected warnings, got %#v", *resp)
|
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) {
|
func TestBackend_ConsulSignLeafWithLegacyRole(t *testing.T) {
|
||||||
|
|
|
@ -16,6 +16,7 @@ const (
|
||||||
keyIdParam = "key_id"
|
keyIdParam = "key_id"
|
||||||
keyTypeParam = "key_type"
|
keyTypeParam = "key_type"
|
||||||
keyBitsParam = "key_bits"
|
keyBitsParam = "key_bits"
|
||||||
|
skidParam = "subject_key_id"
|
||||||
)
|
)
|
||||||
|
|
||||||
// addIssueAndSignCommonFields adds fields common to both CA and non-CA issuing
|
// addIssueAndSignCommonFields adds fields common to both CA and non-CA issuing
|
||||||
|
|
|
@ -5,9 +5,11 @@ package pki
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"crypto"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/hashicorp/vault/sdk/helper/certutil"
|
||||||
"github.com/hashicorp/vault/sdk/helper/errutil"
|
"github.com/hashicorp/vault/sdk/helper/errutil"
|
||||||
|
|
||||||
"github.com/hashicorp/vault/sdk/framework"
|
"github.com/hashicorp/vault/sdk/framework"
|
||||||
|
@ -144,6 +146,11 @@ func buildPathKey(b *backend, pattern string, displayAttrs *framework.DisplayAtt
|
||||||
Description: `Key Type`,
|
Description: `Key Type`,
|
||||||
Required: true,
|
Required: true,
|
||||||
},
|
},
|
||||||
|
"subject_key_id": {
|
||||||
|
Type: framework.TypeString,
|
||||||
|
Description: `RFC 5280 Subject Key Identifier of the public counterpart`,
|
||||||
|
Required: false,
|
||||||
|
},
|
||||||
"managed_key_id": {
|
"managed_key_id": {
|
||||||
Type: framework.TypeString,
|
Type: framework.TypeString,
|
||||||
Description: `Managed Key Id`,
|
Description: `Managed Key Id`,
|
||||||
|
@ -247,6 +254,7 @@ func (b *backend) pathGetKeyHandler(ctx context.Context, req *logical.Request, d
|
||||||
keyTypeParam: string(key.PrivateKeyType),
|
keyTypeParam: string(key.PrivateKeyType),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var pkForSkid crypto.PublicKey
|
||||||
if key.isManagedPrivateKey() {
|
if key.isManagedPrivateKey() {
|
||||||
managedKeyUUID, err := key.getManagedKeyUUID()
|
managedKeyUUID, err := key.getManagedKeyUUID()
|
||||||
if err != nil {
|
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)}
|
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
|
// 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.
|
// type of key, not that it is a managed key.
|
||||||
respData[keyTypeParam] = string(keyInfo.keyType)
|
respData[keyTypeParam] = string(keyInfo.keyType)
|
||||||
respData[managedKeyIdArg] = string(keyInfo.uuid)
|
respData[managedKeyIdArg] = string(keyInfo.uuid)
|
||||||
respData[managedKeyNameArg] = string(keyInfo.name)
|
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
|
return &logical.Response{Data: respData}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
```release-note:improvement
|
||||||
|
secrets/pki: add subject key identifier to read key response
|
||||||
|
```
|
|
@ -126,7 +126,7 @@ func GetSubjKeyID(privateKey crypto.Signer) ([]byte, error) {
|
||||||
if privateKey == nil {
|
if privateKey == nil {
|
||||||
return nil, errutil.InternalError{Err: "passed-in private key is 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
|
// 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 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
|
var publicKeyBytes []byte
|
||||||
switch pub := pub.(type) {
|
switch pub := pub.(type) {
|
||||||
case *rsa.PublicKey:
|
case *rsa.PublicKey:
|
||||||
|
|
Loading…
Reference in New Issue