Skip to content

Commit

Permalink
Return revocation info within existing certs/<serial> api (#17774)
Browse files Browse the repository at this point in the history
* Return revocation info within existing certs/<serial> api

 - The api already returned both the certificate and a revocation_time
   field populated. Update the api to return revocation_time_rfc3339
   as we do elsewhere and also the issuer id if it was revoked.
 - This will allow callers to associate a revoked cert with an issuer

* Add cl

* PR feedback (docs update)
  • Loading branch information
stevendpclark authored Nov 2, 2022
1 parent 2ae9835 commit 3bac9ae
Show file tree
Hide file tree
Showing 4 changed files with 51 additions and 1 deletion.
15 changes: 15 additions & 0 deletions builtin/logical/pki/path_fetch.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"encoding/pem"
"fmt"
"strings"
"time"

"github.com/hashicorp/vault/sdk/framework"
"github.com/hashicorp/vault/sdk/helper/errutil"
Expand Down Expand Up @@ -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{}{},
}
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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)
Expand Down
29 changes: 29 additions & 0 deletions builtin/logical/pki/path_tidy_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package pki

import (
"encoding/json"
"testing"
"time"

Expand Down Expand Up @@ -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{}{
Expand Down Expand Up @@ -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,
})
Expand All @@ -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)
Expand Down
3 changes: 3 additions & 0 deletions changelog/17774.txt
Original file line number Diff line number Diff line change
@@ -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
```
5 changes: 4 additions & 1 deletion website/content/api-docs/secret/pki.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
}
```
Expand Down

0 comments on commit 3bac9ae

Please sign in to comment.