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

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.
This commit is contained in:
Andrew Briening 2022-02-07 09:47:13 -05:00 committed by GitHub
parent e611842ce0
commit ed457aeae7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 132 additions and 1 deletions

View File

@ -71,6 +71,7 @@ func Backend(conf *logical.BackendConfig) *backend {
pathFetchCAChain(&b),
pathFetchCRL(&b),
pathFetchCRLViaCertPath(&b),
pathFetchValidRaw(&b),
pathFetchValid(&b),
pathFetchListCerts(&b),
pathRevoke(&b),

View File

@ -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()
@ -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 {

View File

@ -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 {
@ -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"

3
changelog/10948.txt Normal file
View File

@ -0,0 +1,3 @@
```release-note:improvement
secrets/pki: Add ability to fetch individual certificate as DER or PEM
```

View File

@ -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)
@ -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.