mirror of
https://github.com/hashicorp/vault.git
synced 2026-02-03 20:40:45 -05:00
* In the random APIs, add a 'prng' param that causes a DRBG seeded from the selected source(s) to be the source of the returned bytes * fixes, unit test next * unit tests * changelog * memory ramifications * switch to using a string called drbg * Update helper/random/random_api.go * wrong changelog --------- Co-authored-by: Scott Miller <smiller@hashicorp.com> Co-authored-by: Steven Clark <steven.clark@hashicorp.com>
This commit is contained in:
parent
375a59c4cd
commit
a728a665e1
5 changed files with 105 additions and 25 deletions
|
|
@ -44,6 +44,12 @@ func (b *backend) pathRandom() *framework.Path {
|
||||||
Default: "platform",
|
Default: "platform",
|
||||||
Description: `Which system to source random data from, ether "platform", "seal", or "all".`,
|
Description: `Which system to source random data from, ether "platform", "seal", or "all".`,
|
||||||
},
|
},
|
||||||
|
"drbg": {
|
||||||
|
Type: framework.TypeString,
|
||||||
|
Default: "",
|
||||||
|
Description: "If set, seed a secure DRBG from the source and use it to generate the bytes. This can be more performant when using the seal source." +
|
||||||
|
" Possible values are unset (don't use a DRBG), \"auto\" and \"hmacdrbg\" which are equivalent.",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
Callbacks: map[logical.Operation]framework.OperationFunc{
|
Callbacks: map[logical.Operation]framework.OperationFunc{
|
||||||
|
|
|
||||||
6
changelog/_12119.txt
Normal file
6
changelog/_12119.txt
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
```release-note:improvement
|
||||||
|
secrets/transit, core: Boost the limit of random bytes retrievable via random byte
|
||||||
|
APIs. And add the option to get PRNG random bytes seeded by random sources.
|
||||||
|
Note that requests for large numbers of bytes will increase Vault memory
|
||||||
|
usage accordingly.
|
||||||
|
```
|
||||||
|
|
@ -11,13 +11,17 @@ import (
|
||||||
"io"
|
"io"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/hashicorp/go-hmac-drbg/hmacdrbg"
|
||||||
"github.com/hashicorp/go-uuid"
|
"github.com/hashicorp/go-uuid"
|
||||||
"github.com/hashicorp/vault/sdk/framework"
|
"github.com/hashicorp/vault/sdk/framework"
|
||||||
"github.com/hashicorp/vault/sdk/helper/xor"
|
"github.com/hashicorp/vault/sdk/helper/xor"
|
||||||
"github.com/hashicorp/vault/sdk/logical"
|
"github.com/hashicorp/vault/sdk/logical"
|
||||||
)
|
)
|
||||||
|
|
||||||
const APIMaxBytes = 128 * 1024
|
const (
|
||||||
|
APIMaxBytes = 10 * 1024 * 1024
|
||||||
|
SealMaxBytes = 128 * 1024
|
||||||
|
)
|
||||||
|
|
||||||
func HandleRandomAPI(d *framework.FieldData, additionalSource io.Reader) (*logical.Response, error) {
|
func HandleRandomAPI(d *framework.FieldData, additionalSource io.Reader) (*logical.Response, error) {
|
||||||
bytes := 0
|
bytes := 0
|
||||||
|
|
@ -43,6 +47,16 @@ func HandleRandomAPI(d *framework.FieldData, additionalSource io.Reader) (*logic
|
||||||
return logical.ErrorResponse(fmt.Sprintf("error parsing url-set byte count: %s", err)), nil
|
return logical.ErrorResponse(fmt.Sprintf("error parsing url-set byte count: %s", err)), nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
maybeDrbg, ok := d.GetOk("drbg")
|
||||||
|
var drbg string
|
||||||
|
if ok {
|
||||||
|
drbg = maybeDrbg.(string)
|
||||||
|
switch drbg {
|
||||||
|
case "", "auto", "hmacdrbg":
|
||||||
|
default:
|
||||||
|
return logical.ErrorResponse("invalid setting for drbg, must absent, auto, or hmacdrbg"), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
format := d.Get("format").(string)
|
format := d.Get("format").(string)
|
||||||
|
|
||||||
if bytes < 1 {
|
if bytes < 1 {
|
||||||
|
|
@ -50,7 +64,20 @@ func HandleRandomAPI(d *framework.FieldData, additionalSource io.Reader) (*logic
|
||||||
}
|
}
|
||||||
|
|
||||||
if bytes > APIMaxBytes {
|
if bytes > APIMaxBytes {
|
||||||
return logical.ErrorResponse(`"bytes" should be less than %d`, APIMaxBytes), nil
|
return logical.ErrorResponse(`"bytes" must be less than %d`, APIMaxBytes), nil
|
||||||
|
} else if (source == "seal" || source == "all") && bytes > SealMaxBytes && len(drbg) == 0 {
|
||||||
|
return logical.ErrorResponse("bytes cannot be more than %d if sourced from seal. Use drbg mode instead", SealMaxBytes), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var warning string
|
||||||
|
switch source {
|
||||||
|
case "seal":
|
||||||
|
if rand.Reader == additionalSource {
|
||||||
|
warning = "no seal/entropy augmentation available, using platform entropy source"
|
||||||
|
}
|
||||||
|
case "platform", "all", "":
|
||||||
|
default:
|
||||||
|
return logical.ErrorResponse("unsupported entropy source %q; must be \"platform\" or \"seal\", or \"all\"", source), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
switch format {
|
switch format {
|
||||||
|
|
@ -61,32 +88,29 @@ func HandleRandomAPI(d *framework.FieldData, additionalSource io.Reader) (*logic
|
||||||
}
|
}
|
||||||
|
|
||||||
var randBytes []byte
|
var randBytes []byte
|
||||||
var warning string
|
if len(drbg) > 0 {
|
||||||
switch source {
|
// right now only one value for drbg besides unset is possible, so we assume it was validated
|
||||||
case "", "platform":
|
// above
|
||||||
randBytes, err = uuid.GenerateRandomBytes(bytes)
|
|
||||||
|
// Seed the drbg from source, but use it to generate byes
|
||||||
|
var seed []byte
|
||||||
|
// HMACDRBG wants a seed at least 3x the security level
|
||||||
|
seed, err = getEntropy(source, (256*3)/8, additionalSource)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
case "seal":
|
drbg := hmacdrbg.NewHmacDrbg(256, seed, []byte("api-random-with-hmac-drbg"))
|
||||||
if rand.Reader == additionalSource {
|
reader := hmacdrbg.NewHmacDrbgReader(drbg)
|
||||||
warning = "no seal/entropy augmentation available, using platform entropy source"
|
randBytes = make([]byte, bytes)
|
||||||
|
_, err = io.ReadFull(reader, randBytes)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
randBytes, err = uuid.GenerateRandomBytesWithReader(bytes, additionalSource)
|
} else {
|
||||||
case "all":
|
randBytes, err = getEntropy(source, bytes, additionalSource)
|
||||||
randBytes, err = uuid.GenerateRandomBytes(bytes)
|
if err != nil {
|
||||||
if err == nil && rand.Reader != additionalSource {
|
return nil, err
|
||||||
var sealBytes []byte
|
|
||||||
sealBytes, err = uuid.GenerateRandomBytesWithReader(bytes, additionalSource)
|
|
||||||
if err == nil {
|
|
||||||
randBytes, err = xor.XORBytes(sealBytes, randBytes)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
default:
|
|
||||||
return logical.ErrorResponse("unsupported entropy source %q; must be \"platform\" or \"seal\", or \"all\"", source), nil
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var retStr string
|
var retStr string
|
||||||
|
|
@ -109,6 +133,32 @@ func HandleRandomAPI(d *framework.FieldData, additionalSource io.Reader) (*logic
|
||||||
return resp, nil
|
return resp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getEntropy(source string, bytes int, additionalSource io.Reader) ([]byte, error) {
|
||||||
|
var randBytes []byte
|
||||||
|
var err error
|
||||||
|
switch source {
|
||||||
|
case "", "platform":
|
||||||
|
randBytes, err = uuid.GenerateRandomBytes(bytes)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
case "seal":
|
||||||
|
randBytes, err = uuid.GenerateRandomBytesWithReader(bytes, additionalSource)
|
||||||
|
case "all":
|
||||||
|
randBytes, err = uuid.GenerateRandomBytes(bytes)
|
||||||
|
if err == nil && rand.Reader != additionalSource {
|
||||||
|
var sealBytes []byte
|
||||||
|
sealBytes, err = uuid.GenerateRandomBytesWithReader(bytes, additionalSource)
|
||||||
|
if err == nil {
|
||||||
|
randBytes, err = xor.XORBytes(sealBytes, randBytes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("unsupported entropy source %q; must be \"platform\" or \"seal\", or \"all\"", source)
|
||||||
|
}
|
||||||
|
return randBytes, err
|
||||||
|
}
|
||||||
|
|
||||||
func isValidSource(s string) bool {
|
func isValidSource(s string) bool {
|
||||||
switch s {
|
switch s {
|
||||||
case "", "platform", "seal", "all":
|
case "", "platform", "seal", "all":
|
||||||
|
|
|
||||||
|
|
@ -2586,6 +2586,12 @@ func (b *SystemBackend) toolsPaths() []*framework.Path {
|
||||||
Default: "platform",
|
Default: "platform",
|
||||||
Description: `Which system to source random data from, ether "platform", "seal", or "all".`,
|
Description: `Which system to source random data from, ether "platform", "seal", or "all".`,
|
||||||
},
|
},
|
||||||
|
"drbg": {
|
||||||
|
Type: framework.TypeString,
|
||||||
|
Default: "",
|
||||||
|
Description: "If set, seed a secure DRBG from the source and use it to generate the bytes. This can be more performant when using the seal source." +
|
||||||
|
" Possible values are unset (don't use a DRBG), \"auto\" and \"hmacdrbg\" which are equivalent.",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
Operations: map[logical.Operation]framework.OperationHandler{
|
Operations: map[logical.Operation]framework.OperationHandler{
|
||||||
|
|
|
||||||
|
|
@ -4377,7 +4377,7 @@ func TestSystemBackend_ToolsRandom(t *testing.T) {
|
||||||
}
|
}
|
||||||
if errExpected {
|
if errExpected {
|
||||||
if !resp.IsError() {
|
if !resp.IsError() {
|
||||||
t.Fatalf("bad: got error response: %#v", *resp)
|
t.Fatalf("bad: got no error response: %#v", *resp)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -4435,6 +4435,13 @@ func TestSystemBackend_ToolsRandom(t *testing.T) {
|
||||||
req.Data["format"] = "hex"
|
req.Data["format"] = "hex"
|
||||||
doRequest(req, false, "hex", 24)
|
doRequest(req, false, "hex", 24)
|
||||||
|
|
||||||
|
// Test PRNG with large output
|
||||||
|
req.Path = "tools/random/1024768"
|
||||||
|
req.Data["format"] = "hex"
|
||||||
|
req.Data["drbg"] = "hmacdrbg"
|
||||||
|
doRequest(req, false, "hex", 1024768)
|
||||||
|
delete(req.Data, "drbg")
|
||||||
|
|
||||||
// Test bad input/format
|
// Test bad input/format
|
||||||
req.Path = "tools/random"
|
req.Path = "tools/random"
|
||||||
req.Data["format"] = "base92"
|
req.Data["format"] = "base92"
|
||||||
|
|
@ -4445,7 +4452,12 @@ func TestSystemBackend_ToolsRandom(t *testing.T) {
|
||||||
doRequest(req, true, "", 0)
|
doRequest(req, true, "", 0)
|
||||||
|
|
||||||
req.Data["format"] = "hex"
|
req.Data["format"] = "hex"
|
||||||
req.Data["bytes"] = maxBytes + 1
|
req.Data["bytes"] = random.APIMaxBytes + 1
|
||||||
|
doRequest(req, true, "", 0)
|
||||||
|
|
||||||
|
req.Path = "tools/random/seal"
|
||||||
|
req.Data["format"] = "hex"
|
||||||
|
req.Data["bytes"] = random.SealMaxBytes + 1
|
||||||
doRequest(req, true, "", 0)
|
doRequest(req, true, "", 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue