Skip to content

Commit

Permalink
gpg: add APIs for subkey interactions, revocations and key signing.
Browse files Browse the repository at this point in the history
This commit addresses
LeSuisse#10 by introducing
subkey support in the API. The corresponding golang crypto library
changes are in process of being merged upstream at
https://go-review.googlesource.com/c/crypto/+/161817/. Documentation for
the new APIs has been added in the README file. A brief description of
the changes in the commit can be seen below:

1. Handling concurrent key read/write for all APIs.
2. Subkey create, read, list and delete APIs.
3. Key and subkey revocation API.
4. Key signing API to show trust in another key stored in vault.
5. Breadth tests for all newly introduced APIs.
  • Loading branch information
syadav2015 committed May 13, 2019
1 parent 7f2c7df commit 3c495f2
Show file tree
Hide file tree
Showing 20 changed files with 2,007 additions and 51 deletions.
246 changes: 245 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ $ curl \
https://vault.example.com/v1/gpg/keys/my-imported-key
```

### Read key
### Read key by name

This endpoint returns information about a named GPG key.

Expand All @@ -117,6 +117,39 @@ $ curl \
#### Sample response


```json
{
"data": {
"exportable": false,
"fingerprint": "b0b7e7ca0e4ba1a631d15196ef3331150a45bc4d",
"public_key": "-----BEGIN PGP PUBLIC KEY BLOCK-----\n\nxsBNBFmZ6QQBCAC5QSHMKe6M9S2G9REo3sJuDPX2lm4ZMULXCvwcVekPYyUFWYI8\n...\nnTruSryJ4xYCydiJ1xkTedrkVxhh7hJKHA==\n=4fdy\n-----END PGP PUBLIC KEY BLOCK-----"
}
}
```

### Read key by fingerprint

This endpoint returns information about a key associated with the specified fingerprint.

| Method | Path | Produces |
| :------- | :--------------------------- | :--------------------- |
| `GET` | `/gpg/keys/id/:ID` | `200 application/json` |

#### Parameters

- `ID` `(string: <required>)` – Specifies the fingerprint of the key to read. This is specified as part of the URL.

#### Sample request

```
$ curl \
--header "X-Vault-Token: ..." \
https://vault.example.com/v1/gpg/keys/id/b0b7e7ca0e4ba1a631d15196ef3331150a45bc4d
```

#### Sample response


```json
{
"data": {
Expand Down Expand Up @@ -175,6 +208,138 @@ $ curl \
https://vault.example.com/v1/gpg/keys/my-key
```

### Create subkey

This endpoint creates a new RSA gpg subkey associated with a named key.

| Method | Path | Produces |
| :------- | :------------------------------ | :---------------------- |
| `POST` | `/gpg/subkeys/:name` | `200 application/json` |

#### Parameters

- `name` `(string: <required>)` – Specifies the name of the key to which subkey will be added. This is specified as part of the URL.

- `key_bits` `(int: 2048)` – Specifies the number of bits of the generated GPG subkey to use.

- `canSign` `(bool: false)` – Specifies if the subkey can be used for signing.

- `canEncrypt` `(bool: true)` – Specifies if the subkey can be used for encryption.

#### Sample Payload

```json
{
"canSign": true,
"canEncrypt": true,
"key_bits": 4096
}
```

#### Sample request

```
$ curl \
--header "X-Vault-Token: ..." \
--request POST \
--data @payload.json \
https://vault.example.com/v1/gpg/subkeys/my-key
```

#### Sample response

```json
{
"data": {
"subkey-id": "b0b7e7ca0e4ba1a631d15196ef3331150a45bc4d",
"name": "my-key",
}
}
```

### Read subkey by fingerprint

This endpoint returns information about a specific subkey of a named key referenced using it's fingerprint.

| Method | Path | Produces |
| :------- | :----------------------------- | :--------------------- |
| `GET` | `/gpg/subkeys/:name/:subkeyID` | `200 application/json` |

#### Parameters

- `name` `(string: <required>)` – Specifies the name of the key to read. This is specified as part of the URL.

- `subkeyID` `(string: <required>)` – Specifies the fingerprint of the subkey to read. This is specified as part of the URL.

#### Sample request

```
$ curl \
--header "X-Vault-Token: ..." \
https://vault.example.com/v1/gpg/keys/my-key/b0b7e7ca0e4ba1a631d15196ef3331150a45bc4d
```

#### Sample response

```json
{
"data": {
"exportable": false,
"subkey": "-----BEGIN PGP PUBLIC KEY BLOCK-----\n\nxsBNBFmZ6QQBCAC5QSHMKe6M9S2G9REo3sJuDPX2lm4ZMULXCvwcVekPYyUFWYI8\n...\nnTruSryJ4xYCydiJ1xkTedrkVxhh7hJKHA==\n=4fdy\n-----END PGP PUBLIC KEY BLOCK-----"
}
}
```

### List keys

This endpoint returns a list of subkeys of a named GPG key. Only the fingerprint of the subkeys are returned.

| Method | Path | Produces |
| :------- | :--------------------------- | :--------------------- |
| `LIST` | `/gpg/subkeys/:name` | `200 application/json` |

#### Sample request

```
$ curl \
--header "X-Vault-Token: ..." \
--request LIST \
https://vault.example.com/v1/gpg/subkeys/my-key
```

#### Sample response

```json
{
"data": {
"keys": ["b0b7e7ca0e4ba1a631d15196ef3331150a45bc4d", "20b7e7fa124ba1a631d15196ef3331150a45bc4d"]
}
}
```

### Delete subkey

This endpoint deletes a subkey of a named GPG key referenced using it's fingerprint.

| Method | Path | Produces |
| :------- | :------------------------------ | :--------------------- |
| `DELETE` | `/gpg/subkeys/:name/:subkeyID` | `204 (empty body)` |

#### Parameters

- `name` `(string: <required>)` – Specifies the name of the key to delete. This is specified as part of the URL.

- `subkeyID` `(string: <required>)` – Specifies the fingerprint of the subkey to read. This is specified as part of the URL.

#### Sample request

```
$ curl \
--header "X-Vault-Token: ..." \
--request DELETE \
https://vault.example.com/v1/gpg/subkeys/my-key/b0b7e7ca0e4ba1a631d15196ef3331150a45bc4d
```

### Export key

This endpoint returns the named GPG key ASCII-armored.
Expand Down Expand Up @@ -419,3 +584,82 @@ $ curl \
}
}
```

### Revoke key or subkey

This endpoint revokes a named key or a specific subkey of a named key.

| Method | Path | Produces |
| :------- | :------------------------------ | :---------------------- |
| `POST` | `/gpg/revoke/:name/:subkeyID` | `204 (empty body)` |

#### Parameters

- `name` `(string: <required>)` – Specifies the name of the key to which subkey will ne added. This is specified as part of the URL.

- `subkeyID` `(string: "")` – Specifies the fingerprint of the subkey to be revoked. This is optional and specified as a part of the URL..

- `reasonCode` `(int: 2048)` – Specifies the uint8 reason code for key revocation as per RFC4880.

- `reasonText` `(string: "")` – Specifies the comment associated with the reason for revoking the key/subkey.

#### Sample Payload

```json
{
"reasonCode": 2,
"reasonText": "Key is compromised.",
}
```

#### Sample request for revoking key

```
$ curl \
--header "X-Vault-Token: ..." \
--request POST \
--data @payload.json \
https://vault.example.com/v1/gpg/revoke/my-key
```

#### Sample request for revoking subkey

```
$ curl \
--header "X-Vault-Token: ..." \
--request POST \
--data @payload.json \
https://vault.example.com/v1/gpg/revoke/my-key/b0b7e7ca0e4ba1a631d15196ef3331150a45bc4d
```

### Sign a key stored in vault

This endpoint signs a key stored on vault with another key to show trust in the signed key.

| Method | Path | Produces |
| :------- | :------------------------------ | :---------------------- |
| `POST` | `/gpg/signKey/:signedKey` | `204 (empty body)` |

#### Parameters

- `name` `(string: <required>)` – Specifies the name of the key that will be used for signing.

- `signedKey` `(string: "")` – Specifies the name of the key that needs to be signed. This is specified as a part of the URL.

#### Sample Payload

```json
{
"name": "signer-key",
}
```

#### Sample request for signing a key

```
$ curl \
--header "X-Vault-Token: ..." \
--request POST \
--data @payload.json \
https://vault.example.com/v1/gpg/signkey/my-key
```
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,5 @@ require (
github.com/hashicorp/vault/sdk v0.1.10
golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c
)

replace golang.org/x/crypto => github.com/syadav2015/crypto v0.0.0-20190304101048-6881110aac18
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,8 @@ github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/syadav2015/crypto v0.0.0-20190304101048-6881110aac18 h1:j7cBfzrR6zkHXRzekBcD9SdKGL1/BdQ9MBJivqHqMrA=
github.com/syadav2015/crypto v0.0.0-20190304101048-6881110aac18/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c h1:Vj5n4GlwjmQteupaxJ9+0FNOmBrHfq7vN4btdGoDZgI=
golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
Expand Down
7 changes: 7 additions & 0 deletions gpg/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package gpg

import (
"context"
"sync"

"github.com/hashicorp/vault/sdk/framework"
"github.com/hashicorp/vault/sdk/logical"
Expand All @@ -23,9 +24,14 @@ func Backend() *backend {
Help: backendHelp,
Paths: []*framework.Path{
pathKeys(&b),
pathKeysByFingerprint(&b),
pathListKeys(&b),
pathListSubkeys(&b),
pathRevoke(&b),
pathSubkeys(&b),
pathExportKeys(&b),
pathSign(&b),
pathSignKey(&b),
pathVerify(&b),
pathDecrypt(&b),
pathShowSessionKey(&b),
Expand All @@ -43,6 +49,7 @@ func Backend() *backend {

type backend struct {
*framework.Backend
lock sync.RWMutex
}

const backendHelp = `
Expand Down
3 changes: 3 additions & 0 deletions gpg/path_decrypt.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,10 @@ func (b *backend) pathDecryptWrite(ctx context.Context, req *logical.Request, da
return logical.ErrorResponse(fmt.Sprintf("unsupported encoding format %s; must be \"base64\" or \"ascii-armor\"", format)), nil
}

// Acquire a read lock before the read operation.
b.lock.RLock()
keyEntry, err := b.key(ctx, req.Storage, data.Get("name").(string))
b.lock.RUnlock()
if err != nil {
return nil, err
}
Expand Down
3 changes: 3 additions & 0 deletions gpg/path_export.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,10 @@ func pathExportKeys(b *backend) *framework.Path {

func (b *backend) pathExportKeyRead(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
name := data.Get("name").(string)
// Acquire a read lock before the read operation.
b.lock.RLock()
entry, err := b.key(ctx, req.Storage, name)
b.lock.RUnlock()
if err != nil {
return nil, err
}
Expand Down
Loading

0 comments on commit 3c495f2

Please sign in to comment.