diff --git a/builtin/logical/pki/path_fetch.go b/builtin/logical/pki/path_fetch.go index e0a1079ea..340f0c4d0 100644 --- a/builtin/logical/pki/path_fetch.go +++ b/builtin/logical/pki/path_fetch.go @@ -5,6 +5,7 @@ import ( "encoding/pem" "fmt" "strings" + "time" "github.com/hashicorp/vault/sdk/framework" "github.com/hashicorp/vault/sdk/helper/errutil" @@ -156,6 +157,9 @@ func (b *backend) pathFetchRead(ctx context.Context, req *logical.Request, data var certificate []byte var fullChain []byte var revocationTime int64 + var revocationIssuerId string + var revocationTimeRfc3339 string + response = &logical.Response{ Data: map[string]interface{}{}, } @@ -322,6 +326,11 @@ func (b *backend) pathFetchRead(ctx context.Context, req *logical.Request, data return logical.ErrorResponse(fmt.Sprintf("Error decoding revocation entry for serial %s: %s", serial, err)), nil } revocationTime = revInfo.RevocationTime + revocationIssuerId = revInfo.CertificateIssuer.String() + + if !revInfo.RevocationTimeUTC.IsZero() { + revocationTimeRfc3339 = revInfo.RevocationTimeUTC.Format(time.RFC3339Nano) + } } reply: @@ -354,6 +363,12 @@ reply: default: response.Data["certificate"] = string(certificate) response.Data["revocation_time"] = revocationTime + response.Data["revocation_time_rfc3339"] = revocationTimeRfc3339 + // Only output this field if we have a value for it as it doesn't make sense for a + // bunch of code paths that go through here + if revocationIssuerId != "" { + response.Data["issuer_id"] = revocationIssuerId + } if len(fullChain) > 0 { response.Data["ca_chain"] = string(fullChain) diff --git a/builtin/logical/pki/path_tidy_test.go b/builtin/logical/pki/path_tidy_test.go index e06c7068b..be950f865 100644 --- a/builtin/logical/pki/path_tidy_test.go +++ b/builtin/logical/pki/path_tidy_test.go @@ -1,6 +1,7 @@ package pki import ( + "encoding/json" "testing" "time" @@ -63,6 +64,7 @@ func TestAutoTidy(t *testing.T) { require.NotNil(t, resp) require.NotEmpty(t, resp.Data) require.NotEmpty(t, resp.Data["issuer_id"]) + issuerId := resp.Data["issuer_id"] // Run tidy so status is not empty when we run it later... _, err = client.Logical().Write("pki/tidy", map[string]interface{}{ @@ -101,6 +103,17 @@ func TestAutoTidy(t *testing.T) { leafSerial := resp.Data["serial_number"].(string) leafCert := parseCert(t, resp.Data["certificate"].(string)) + // Read cert before revoking + resp, err = client.Logical().Read("pki/cert/" + leafSerial) + require.NoError(t, err) + require.NotNil(t, resp) + require.NotNil(t, resp.Data) + require.NotEmpty(t, resp.Data["certificate"]) + revocationTime, err := (resp.Data["revocation_time"].(json.Number)).Int64() + require.Equal(t, int64(0), revocationTime, "revocation time was not zero") + require.Empty(t, resp.Data["revocation_time_rfc3339"], "revocation_time_rfc3339 was not empty") + require.Empty(t, resp.Data["issuer_id"], "issuer_id was not empty") + _, err = client.Logical().Write("pki/revoke", map[string]interface{}{ "serial_number": leafSerial, }) @@ -112,6 +125,22 @@ func TestAutoTidy(t *testing.T) { require.NotNil(t, resp) require.NotNil(t, resp.Data) require.NotEmpty(t, resp.Data["certificate"]) + revocationTime, err = (resp.Data["revocation_time"].(json.Number)).Int64() + require.NoError(t, err, "failed converting %s to int", resp.Data["revocation_time"]) + revTime := time.Unix(revocationTime, 0) + now := time.Now() + if !(now.After(revTime) && now.Add(-10*time.Minute).Before(revTime)) { + t.Fatalf("parsed revocation time not within the last 10 minutes current time: %s, revocation time: %s", now, revTime) + } + utcLoc, err := time.LoadLocation("UTC") + require.NoError(t, err, "failed to parse UTC location?") + + rfc3339RevocationTime, err := time.Parse(time.RFC3339Nano, resp.Data["revocation_time_rfc3339"].(string)) + require.NoError(t, err, "failed parsing revocation_time_rfc3339 field: %s", resp.Data["revocation_time_rfc3339"]) + + require.Equal(t, revTime.In(utcLoc), rfc3339RevocationTime.Truncate(time.Second), + "revocation times did not match revocation_time: %s, "+"rfc3339 time: %s", revTime, rfc3339RevocationTime) + require.Equal(t, issuerId, resp.Data["issuer_id"], "issuer_id on leaf cert did not match") // Wait for cert to expire and the safety buffer to elapse. time.Sleep(time.Until(leafCert.NotAfter) + 3*time.Second) diff --git a/changelog/17774.txt b/changelog/17774.txt new file mode 100644 index 000000000..f6103ec60 --- /dev/null +++ b/changelog/17774.txt @@ -0,0 +1,3 @@ +```release-note:improvement +secrets/pki: Return new fields revocation_time_rfc3339 and issuer_id to existing certificate serial lookup api if it is revoked +``` diff --git a/website/content/api-docs/secret/pki.mdx b/website/content/api-docs/secret/pki.mdx index 135a1b418..198bc1cd8 100644 --- a/website/content/api-docs/secret/pki.mdx +++ b/website/content/api-docs/secret/pki.mdx @@ -1314,7 +1314,10 @@ $ curl \ ```json { "data": { - "certificate": "-----BEGIN CERTIFICATE-----\nMIIGmDCCBYCgAwIBAgIHBzEB3fTzhTANBgkqhkiG9w0BAQsFADCBjDELMAkGA1UE\n..." + "certificate": "-----BEGIN CERTIFICATE-----\nMIIGmDCCBYCgAwIBAgIHBzEB3fTzhTANBgkqhkiG9w0BAQsFADCBjDELMAkGA1UE\n...", + "revocation_time": 1667400107, + "revocation_time_rfc3339": "2022-11-02T14:41:47.327515Z", + "issuer_id": "e27bf456-51e1-d937-0001-4a609184fd9b" } } ```