Skip to content

Commit

Permalink
Adds "raw(/pem)" format to individual cert routes (#10947) (#10948)
Browse files Browse the repository at this point in the history
Similar to "/pki/ca(/pem)" routes to retrieve
certificates in raw or pem formats, this adds
"pki/cert/{serial}/raw(/pem)" routes for any
certificate.
  • Loading branch information
abriening authored and fairclothjm committed Feb 12, 2022
1 parent 4c8784a commit dd43a65
Show file tree
Hide file tree
Showing 5 changed files with 132 additions and 1 deletion.
1 change: 1 addition & 0 deletions builtin/logical/pki/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ func Backend(conf *logical.BackendConfig) *backend {
pathFetchCAChain(&b),
pathFetchCRL(&b),
pathFetchCRLViaCertPath(&b),
pathFetchValidRaw(&b),
pathFetchValid(&b),
pathFetchListCerts(&b),
pathRevoke(&b),
Expand Down
70 changes: 69 additions & 1 deletion builtin/logical/pki/backend_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1705,6 +1705,74 @@ func generateRoleSteps(t *testing.T, useCSRs bool) []logicaltest.TestStep {
return ret
}

func TestBackend_PathFetchValidRaw(t *testing.T) {
// create the backend
config := logical.TestBackendConfig()
storage := &logical.InmemStorage{}
config.StorageView = storage

b := Backend(config)
err := b.Setup(context.Background(), config)
if err != nil {
t.Fatal(err)
}

expectedSerial := "17:67:16:b0:b9:45:58:c0:3a:29:e3:cb:d6:98:33:7a:a6:3b:66:c1"
expectedCert := []byte("test certificate")
entry := &logical.StorageEntry{
Key: fmt.Sprintf("certs/%s", normalizeSerial(expectedSerial)),
Value: expectedCert,
}
err = storage.Put(context.Background(), entry)

// get der cert
resp, err := b.HandleRequest(context.Background(), &logical.Request{
Operation: logical.ReadOperation,
Path: fmt.Sprintf("cert/%s/raw", expectedSerial),
Storage: storage,
})
if resp != nil && resp.IsError() {
t.Fatalf("failed to get raw cert, %#v", resp)
}
if err != nil {
t.Fatal(err)
}

// check the raw cert matches the response body
if bytes.Compare(resp.Data[logical.HTTPRawBody].([]byte), expectedCert) != 0 {
t.Fatalf("failed to get raw cert")
}
if resp.Data[logical.HTTPContentType] != "application/pkix-cert" {
t.Fatalf("failed to get raw cert content-type")
}

// get pem
resp, err = b.HandleRequest(context.Background(), &logical.Request{
Operation: logical.ReadOperation,
Path: fmt.Sprintf("cert/%s/raw/pem", expectedSerial),
Storage: storage,
})
if resp != nil && resp.IsError() {
t.Fatalf("failed to get raw, %#v", resp)
}
if err != nil {
t.Fatal(err)
}

pemBlock := &pem.Block{
Type: "CERTIFICATE",
Bytes: expectedCert,
}
pemCert := []byte(strings.TrimSpace(string(pem.EncodeToMemory(pemBlock))))
// check the pem cert matches the response body
if bytes.Compare(resp.Data[logical.HTTPRawBody].([]byte), pemCert) != 0 {
t.Fatalf("failed to get pem cert")
}
if resp.Data[logical.HTTPContentType] != "application/pkix-cert" {
t.Fatalf("failed to get raw cert content-type")
}
}

func TestBackend_PathFetchCertList(t *testing.T) {
// create the backend
config := logical.TestBackendConfig()
Expand Down Expand Up @@ -3039,7 +3107,7 @@ func TestBackend_AllowedURISANsTemplate(t *testing.T) {

// Write test policy for userpass auth method.
err := client.Sys().PutPolicy("test", `
path "pki/*" {
path "pki/*" {
capabilities = ["update"]
}`)
if err != nil {
Expand Down
27 changes: 27 additions & 0 deletions builtin/logical/pki/path_fetch.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,27 @@ func pathFetchCRL(b *backend) *framework.Path {
}
}

// Returns any valid (non-revoked) cert in raw format.
func pathFetchValidRaw(b *backend) *framework.Path {
return &framework.Path{
Pattern: `cert/(?P<serial>[0-9A-Fa-f-:]+)/raw(/pem)?`,
Fields: map[string]*framework.FieldSchema{
"serial": {
Type: framework.TypeString,
Description: `Certificate serial number, in colon- or
hyphen-separated octal`,
},
},

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

HelpSynopsis: pathFetchHelpSyn,
HelpDescription: pathFetchHelpDesc,
}
}

// Returns any valid (non-revoked) cert. Since "ca" fits the pattern, this path
// also handles returning the CA cert in a non-raw format.
func pathFetchValid(b *backend) *framework.Path {
Expand Down Expand Up @@ -150,6 +171,12 @@ func (b *backend) pathFetchRead(ctx context.Context, req *logical.Request, data
case req.Path == "cert/crl":
serial = "crl"
pemType = "X509 CRL"
case strings.HasSuffix(req.Path, "/pem") || strings.HasSuffix(req.Path, "/raw"):
serial = data.Get("serial").(string)
contentType = "application/pkix-cert"
if strings.HasSuffix(req.Path, "/pem") {
pemType = "CERTIFICATE"
}
default:
serial = data.Get("serial").(string)
pemType = "CERTIFICATE"
Expand Down
3 changes: 3 additions & 0 deletions changelog/10948.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:improvement
secrets/pki: Add ability to fetch individual certificate as DER or PEM
```
32 changes: 32 additions & 0 deletions website/content/api-docs/secret/pki.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ update your API calls accordingly.
- [Read CA Certificate](#read-ca-certificate)
- [Read CA Certificate Chain](#read-ca-certificate-chain)
- [Read Certificate](#read-certificate)
- [Read Raw Certificate](#read-raw-certificate)
- [List Certificates](#list-certificates)
- [Submit CA Information](#submit-ca-information)
- [Read CRL Configuration](#read-crl-configuration)
Expand Down Expand Up @@ -132,6 +133,37 @@ $ curl \
}
```

## Read Raw Certificate

This endpoint retrieves one of a selection of certificates _in raw DER-encoded
form_. This is a bare endpoint that does not return a standard Vault data
structure and cannot be read by the Vault CLI; use `/pki/cert/:serial` for that. If
`/pem` is added to the endpoint, the selected certificate is returned in PEM format.

This is an unauthenticated endpoint.

| Method | Path |
| :----- | :---------------------------- |
| `GET` | `/pki/cert/:serial/raw(/pem)` |

### Parameters

- `serial` `(string: <required>)` – Specifies the serial number of the
certificate to select, in hyphen-separated or colon-separated octal.

### Sample Request

```shell-session
$ curl \
http://127.0.0.1:8200/v1/pki/cert/39:dd:2e.../raw
```

### Sample Response

```text
<binary DER-encoded certificate>
```

## List Certificates

This endpoint returns a list of the current certificates by serial number only.
Expand Down

0 comments on commit dd43a65

Please sign in to comment.