Skip to content

Commit

Permalink
Generate ACME EAB tokens that do not start with '-' (hashicorp#20945)
Browse files Browse the repository at this point in the history
* Generate ACME EAB tokens that do not start with -

 - To avoid people having issues copying EAB tokens and using them on command lines when they start with - from the base64 encoded values, append a prefix.
 - Remove the key_bits data from the eab api, not really useful and now technically wrong
 - Fix up some issues with tests not running in parallel.
 - Update docs to reflect new EAB apis.

* Add ACME directory to the various EAB output APIs

* Update EAB token prefix to be divisable by 3

 - Our decoded prefix was not divisable by 3, which meant the last
   character might be tweaked by the rest of the input
  • Loading branch information
stevendpclark committed Jun 5, 2023
1 parent 2c9a75b commit 0bd356f
Show file tree
Hide file tree
Showing 3 changed files with 37 additions and 19 deletions.
26 changes: 19 additions & 7 deletions builtin/logical/pki/path_acme_eab.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,29 @@ import (
"crypto/rand"
"encoding/base64"
"fmt"
"path"
"time"

"github.com/hashicorp/go-uuid"
"github.com/hashicorp/vault/sdk/framework"
"github.com/hashicorp/vault/sdk/logical"
)

var decodedTokenPrefix = mustBase64Decode("vault-eab-0-")

func mustBase64Decode(s string) []byte {
bytes, err := base64.RawURLEncoding.DecodeString(s)
if err != nil {
panic(fmt.Sprintf("Token prefix value: %s failed decoding: %v", s, err))
}

// Should be dividable by 3 otherwise our prefix will not be properly honored.
if len(bytes)%3 != 0 {
panic(fmt.Sprintf("Token prefix value: %s is not dividable by 3, will not prefix properly", s))
}
return bytes
}

/*
* This file unlike the other path_acme_xxx.go are VAULT APIs to manage the
* ACME External Account Bindings, this isn't providing any APIs that an ACME
Expand Down Expand Up @@ -115,7 +131,6 @@ a warning that it did not exist.`,
type eabType struct {
KeyID string `json:"-"`
KeyType string `json:"key-type"`
KeyBits int `json:"key-bits"`
PrivateBytes []byte `json:"private-bytes"`
AcmeDirectory string `json:"acme-directory"`
CreatedOn time.Time `json:"created-on"`
Expand Down Expand Up @@ -143,8 +158,7 @@ func (b *backend) pathAcmeListEab(ctx context.Context, r *logical.Request, _ *fr
keyIds = append(keyIds, eab.KeyID)
keyInfos[eab.KeyID] = map[string]interface{}{
"key_type": eab.KeyType,
"key_bits": eab.KeyBits,
"acme_directory": eab.AcmeDirectory,
"acme_directory": path.Join(eab.AcmeDirectory, "directory"),
"created_on": eab.CreatedOn.Format(time.RFC3339),
}
}
Expand Down Expand Up @@ -172,8 +186,7 @@ func (b *backend) pathAcmeCreateEab(ctx context.Context, r *logical.Request, dat
eab := &eabType{
KeyID: kid,
KeyType: "hs",
KeyBits: size * 8,
PrivateBytes: bytes,
PrivateBytes: append(decodedTokenPrefix, bytes...), // we do this to avoid generating tokens that start with -
AcmeDirectory: acmeDirectory,
CreatedOn: time.Now(),
}
Expand All @@ -190,9 +203,8 @@ func (b *backend) pathAcmeCreateEab(ctx context.Context, r *logical.Request, dat
Data: map[string]interface{}{
"id": eab.KeyID,
"key_type": eab.KeyType,
"key_bits": eab.KeyBits,
"key": encodedKey,
"acme_directory": eab.AcmeDirectory,
"acme_directory": path.Join(eab.AcmeDirectory, "directory"),
"created_on": eab.CreatedOn.Format(time.RFC3339),
},
}, nil
Expand Down
11 changes: 6 additions & 5 deletions builtin/logical/pki/path_acme_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -375,10 +375,8 @@ func TestAcmeBasicWorkflowWithEab(t *testing.T) {
require.Contains(t, keyInfo, kid)

infoForKid := keyInfo[kid].(map[string]interface{})
keyBits := infoForKid["key_bits"].(json.Number)
require.Equal(t, "256", keyBits.String())
require.Equal(t, "hs", infoForKid["key_type"])
require.Equal(t, tc.prefixUrl, infoForKid["acme_directory"])
require.Equal(t, tc.prefixUrl+"directory", infoForKid["acme_directory"])

// Create new account with EAB
t.Logf("Testing register on %s", baseAcmeURL)
Expand Down Expand Up @@ -651,6 +649,8 @@ func TestAcmeConfigChecksPublicAcmeEnv(t *testing.T) {
// CSR's selected TTL value in ACME and the issuer's leaf_not_after_behavior setting is set to Err,
// we will override the configured behavior and truncate to the issuer's NotAfter
func TestAcmeTruncatesToIssuerExpiry(t *testing.T) {
t.Parallel()

cluster, client, _ := setupAcmeBackend(t)
defer cluster.Cleanup()

Expand Down Expand Up @@ -1048,6 +1048,7 @@ func testAcmeCertSignedByCa(t *testing.T, client *api.Client, derCerts [][]byte,

// TestAcmeValidationError make sure that we properly return errors on validation errors.
func TestAcmeValidationError(t *testing.T) {
t.Parallel()
cluster, _, _ := setupAcmeBackend(t)
defer cluster.Cleanup()

Expand Down Expand Up @@ -1198,12 +1199,12 @@ func getEABKey(t *testing.T, client *api.Client, baseUrl string) (string, []byte

require.NotEmpty(t, resp.Data["key"], "eab key response missing private_key field")
base64Key := resp.Data["key"].(string)
require.True(t, strings.HasPrefix(base64Key, "vault-eab-0-"), "%s should have had a prefix of vault-eab-0-", base64Key)
privateKeyBytes, err := base64.RawURLEncoding.DecodeString(base64Key)
require.NoError(t, err, "failed base 64 decoding eab key response")

require.Equal(t, "hs", resp.Data["key_type"], "eab key_type field mis-match")
require.Equal(t, json.Number("256"), resp.Data["key_bits"], "eab key_bits field mis-match")
require.Equal(t, baseUrl, resp.Data["acme_directory"], "eab acme_directory field mis-match")
require.Equal(t, baseUrl+"directory", resp.Data["acme_directory"], "eab acme_directory field mis-match")
require.NotEmpty(t, resp.Data["created_on"], "empty created_on field")
_, err = time.Parse(time.RFC3339, resp.Data["created_on"].(string))
require.NoError(t, err, "failed parsing eab created_on field")
Expand Down
19 changes: 12 additions & 7 deletions website/content/api-docs/secret/pki.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -224,11 +224,16 @@ This endpoint returns a new ACME binding token. The `id` response field can
be used as the key identifier and the `key` response field be used as the
EAB HMAC key in the ACME Client.

Each call to this endpoint will generate and return a new EAB binding token.
Each call to this endpoint will generate and return a new EAB binding token
that is linked to the specific ACME directory it resides under. EAB tokens
are not usable across different ACME directories.

| Method | Path |
| :----- | :------------------ |
| `POST` | `/pki/acme/new-eab` |
| Method | Path |
|:-------|:---------------------------------------------------|
| `POST` | `/pki/acme/new-eab` |
| `POST` | `/pki/issuer/:issuer_ref/acme/new-eab` |
| `POST` | `/pki/roles/:role/acme/new-eab` |
| `POST` | `/pki/issuer/:issuer_ref/roles/:role/acme/new-eab` |

#### Parameters

Expand All @@ -250,8 +255,8 @@ $ curl \
"data": {
"created_on": "2023-05-24T14:33:00-04:00",
"id": "bc8088d9-3816-5177-ae8e-d8393265f7dd",
"key_bits": "256",
"key_type": "hs",
"acme_directory": "acme/directory",
"key": "MHcCAQE... additional data elided ...",
}
}
Expand Down Expand Up @@ -283,8 +288,8 @@ $ curl \
"key_info": {
"bc8088d9-3816-5177-ae8e-d8393265f7dd": {
"created_on": "2023-05-24T14:33:00-04:00",
"key_bits": "256",
"key_type": "hs"
"key_type": "hs",
"acme_directory": "acme/directory"
}
},
"keys": [
Expand Down

0 comments on commit 0bd356f

Please sign in to comment.