Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds "raw(/pem)" format to individual cert routes closes #10947 #10948

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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