Address comments from review.
This commit is contained in:
parent
0b580d0521
commit
cc232e6f79
|
@ -14,17 +14,8 @@ func (c *Sys) InitStatus() (bool, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Sys) Init(opts *InitRequest) (*InitResponse, error) {
|
func (c *Sys) Init(opts *InitRequest) (*InitResponse, error) {
|
||||||
body := map[string]interface{}{
|
|
||||||
"secret_shares": opts.SecretShares,
|
|
||||||
"secret_threshold": opts.SecretThreshold,
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(opts.SecretPGPKeys) != 0 {
|
|
||||||
body["secret_pgp_keys"] = opts.SecretPGPKeys
|
|
||||||
}
|
|
||||||
|
|
||||||
r := c.c.NewRequest("PUT", "/v1/sys/init")
|
r := c.c.NewRequest("PUT", "/v1/sys/init")
|
||||||
if err := r.SetJSONBody(body); err != nil {
|
if err := r.SetJSONBody(opts); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -40,9 +31,9 @@ func (c *Sys) Init(opts *InitRequest) (*InitResponse, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
type InitRequest struct {
|
type InitRequest struct {
|
||||||
SecretShares int
|
SecretShares int `json:"secret_shares"`
|
||||||
SecretThreshold int
|
SecretThreshold int `json:"secret_threshold"`
|
||||||
SecretPGPKeys []string
|
PGPKeys []string `json:"pgp_keys"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type InitStatusResponse struct {
|
type InitStatusResponse struct {
|
||||||
|
|
|
@ -57,7 +57,7 @@ func (c *Sys) RekeyUpdate(shard string) (*RekeyUpdateResponse, error) {
|
||||||
type RekeyInitRequest struct {
|
type RekeyInitRequest struct {
|
||||||
SecretShares int `json:"secret_shares"`
|
SecretShares int `json:"secret_shares"`
|
||||||
SecretThreshold int `json:"secret_threshold"`
|
SecretThreshold int `json:"secret_threshold"`
|
||||||
SecretPGPKeys []string `json:"secret_pgp_keys"`
|
PGPKeys []string `json:"pgp_keys"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type RekeyStatusResponse struct {
|
type RekeyStatusResponse struct {
|
||||||
|
|
|
@ -18,7 +18,7 @@ func (c *InitCommand) Run(args []string) int {
|
||||||
var pgpKeys pgpkeys.PubKeyFilesFlag
|
var pgpKeys pgpkeys.PubKeyFilesFlag
|
||||||
flags := c.Meta.FlagSet("init", FlagSetDefault)
|
flags := c.Meta.FlagSet("init", FlagSetDefault)
|
||||||
flags.Usage = func() { c.Ui.Error(c.Help()) }
|
flags.Usage = func() { c.Ui.Error(c.Help()) }
|
||||||
flags.IntVar(&shares, "key-shares", 0, "")
|
flags.IntVar(&shares, "key-shares", 5, "")
|
||||||
flags.IntVar(&threshold, "key-threshold", 3, "")
|
flags.IntVar(&threshold, "key-threshold", 3, "")
|
||||||
flags.Var(&pgpKeys, "pgp-keys", "")
|
flags.Var(&pgpKeys, "pgp-keys", "")
|
||||||
if err := flags.Parse(args); err != nil {
|
if err := flags.Parse(args); err != nil {
|
||||||
|
@ -32,18 +32,10 @@ func (c *InitCommand) Run(args []string) int {
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
if shares == 0 {
|
|
||||||
if pgpKeys == nil {
|
|
||||||
shares = 5
|
|
||||||
} else {
|
|
||||||
shares = len(pgpKeys)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := client.Sys().Init(&api.InitRequest{
|
resp, err := client.Sys().Init(&api.InitRequest{
|
||||||
SecretShares: shares,
|
SecretShares: shares,
|
||||||
SecretThreshold: threshold,
|
SecretThreshold: threshold,
|
||||||
SecretPGPKeys: pgpKeys,
|
PGPKeys: pgpKeys,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.Ui.Error(fmt.Sprintf(
|
c.Ui.Error(fmt.Sprintf(
|
||||||
|
@ -104,8 +96,7 @@ Init Options:
|
||||||
|
|
||||||
-pgp-keys If provided, must be a comma-separated list of
|
-pgp-keys If provided, must be a comma-separated list of
|
||||||
files on disk containing binary-format public PGP
|
files on disk containing binary-format public PGP
|
||||||
keys. The number of files must match 'key-shares',
|
keys. The number of files must match 'key-shares'.
|
||||||
or you can omit 'key-shares' if using this option.
|
|
||||||
The output unseal keys will be hex-encoded and
|
The output unseal keys will be hex-encoded and
|
||||||
encrypted, in order, with the given public keys.
|
encrypted, in order, with the given public keys.
|
||||||
If you want to use them with the 'vault unseal'
|
If you want to use them with the 'vault unseal'
|
||||||
|
|
|
@ -147,6 +147,7 @@ func TestInit_PGP(t *testing.T) {
|
||||||
|
|
||||||
args = []string{
|
args = []string{
|
||||||
"-address", addr,
|
"-address", addr,
|
||||||
|
"-key-shares", "3",
|
||||||
"-pgp-keys", pubFiles[0] + ",@" + pubFiles[1] + "," + pubFiles[2],
|
"-pgp-keys", pubFiles[0] + ",@" + pubFiles[1] + "," + pubFiles[2],
|
||||||
"-key-threshold", "2",
|
"-key-threshold", "2",
|
||||||
}
|
}
|
||||||
|
@ -177,7 +178,7 @@ func TestInit_PGP(t *testing.T) {
|
||||||
if !reflect.DeepEqual(expected, sealConf) {
|
if !reflect.DeepEqual(expected, sealConf) {
|
||||||
t.Fatalf("bad:\nexpected: %#v\ngot: %#v", expected, sealConf)
|
t.Fatalf("bad:\nexpected: %#v\ngot: %#v", expected, sealConf)
|
||||||
}
|
}
|
||||||
|
|
||||||
re, err := regexp.Compile("\\s+Initial Root Token:\\s+(.*)")
|
re, err := regexp.Compile("\\s+Initial Root Token:\\s+(.*)")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Error compiling regex: %s", err)
|
t.Fatalf("Error compiling regex: %s", err)
|
||||||
|
|
|
@ -27,7 +27,7 @@ func (c *RekeyCommand) Run(args []string) int {
|
||||||
flags.BoolVar(&init, "init", false, "")
|
flags.BoolVar(&init, "init", false, "")
|
||||||
flags.BoolVar(&cancel, "cancel", false, "")
|
flags.BoolVar(&cancel, "cancel", false, "")
|
||||||
flags.BoolVar(&status, "status", false, "")
|
flags.BoolVar(&status, "status", false, "")
|
||||||
flags.IntVar(&shares, "key-shares", 0, "")
|
flags.IntVar(&shares, "key-shares", 5, "")
|
||||||
flags.IntVar(&threshold, "key-threshold", 3, "")
|
flags.IntVar(&threshold, "key-threshold", 3, "")
|
||||||
flags.Var(&pgpKeys, "pgp-keys", "")
|
flags.Var(&pgpKeys, "pgp-keys", "")
|
||||||
flags.Usage = func() { c.Ui.Error(c.Help()) }
|
flags.Usage = func() { c.Ui.Error(c.Help()) }
|
||||||
|
@ -42,14 +42,6 @@ func (c *RekeyCommand) Run(args []string) int {
|
||||||
return 2
|
return 2
|
||||||
}
|
}
|
||||||
|
|
||||||
if shares == 0 {
|
|
||||||
if pgpKeys == nil {
|
|
||||||
shares = 5
|
|
||||||
} else {
|
|
||||||
shares = len(pgpKeys)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if we are running doing any restricted variants
|
// Check if we are running doing any restricted variants
|
||||||
if init {
|
if init {
|
||||||
return c.initRekey(client, shares, threshold, pgpKeys)
|
return c.initRekey(client, shares, threshold, pgpKeys)
|
||||||
|
@ -71,7 +63,7 @@ func (c *RekeyCommand) Run(args []string) int {
|
||||||
err := client.Sys().RekeyInit(&api.RekeyInitRequest{
|
err := client.Sys().RekeyInit(&api.RekeyInitRequest{
|
||||||
SecretShares: shares,
|
SecretShares: shares,
|
||||||
SecretThreshold: threshold,
|
SecretThreshold: threshold,
|
||||||
SecretPGPKeys: pgpKeys,
|
PGPKeys: pgpKeys,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.Ui.Error(fmt.Sprintf("Error initializing rekey: %s", err))
|
c.Ui.Error(fmt.Sprintf("Error initializing rekey: %s", err))
|
||||||
|
@ -153,7 +145,7 @@ func (c *RekeyCommand) initRekey(client *api.Client, shares, threshold int, pgpK
|
||||||
err := client.Sys().RekeyInit(&api.RekeyInitRequest{
|
err := client.Sys().RekeyInit(&api.RekeyInitRequest{
|
||||||
SecretShares: shares,
|
SecretShares: shares,
|
||||||
SecretThreshold: threshold,
|
SecretThreshold: threshold,
|
||||||
SecretPGPKeys: pgpKeys,
|
PGPKeys: pgpKeys,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.Ui.Error(fmt.Sprintf("Error initializing rekey: %s", err))
|
c.Ui.Error(fmt.Sprintf("Error initializing rekey: %s", err))
|
||||||
|
@ -241,8 +233,7 @@ Unseal Options:
|
||||||
|
|
||||||
-pgp-keys If provided, must be a comma-separated list of
|
-pgp-keys If provided, must be a comma-separated list of
|
||||||
files on disk containing binary-format public PGP
|
files on disk containing binary-format public PGP
|
||||||
keys. The number of files must match 'key-shares',
|
keys. The number of files must match 'key-shares'.
|
||||||
or you can omit 'key-shares' if using this option.
|
|
||||||
The output unseal keys will be hex-encoded and
|
The output unseal keys will be hex-encoded and
|
||||||
encrypted, in order, with the given public keys.
|
encrypted, in order, with the given public keys.
|
||||||
If you want to use them with the 'vault unseal'
|
If you want to use them with the 'vault unseal'
|
||||||
|
|
|
@ -176,8 +176,9 @@ func TestRekey_init_pgp(t *testing.T) {
|
||||||
args := []string{
|
args := []string{
|
||||||
"-address", addr,
|
"-address", addr,
|
||||||
"-init",
|
"-init",
|
||||||
|
"-key-shares", "3",
|
||||||
"-pgp-keys", pubFiles[0] + ",@" + pubFiles[1] + "," + pubFiles[2],
|
"-pgp-keys", pubFiles[0] + ",@" + pubFiles[1] + "," + pubFiles[2],
|
||||||
"-key-threshold=2",
|
"-key-threshold", "2",
|
||||||
}
|
}
|
||||||
|
|
||||||
if code := c.Run(args); code != 0 {
|
if code := c.Run(args); code != 0 {
|
||||||
|
|
|
@ -15,30 +15,31 @@ import (
|
||||||
//
|
//
|
||||||
// Note: There is no corresponding test function; this functionality is
|
// Note: There is no corresponding test function; this functionality is
|
||||||
// thoroughly tested in the init and rekey command unit tests
|
// thoroughly tested in the init and rekey command unit tests
|
||||||
func EncryptShares(secretShares *[][]byte, pgpKeys *[]string) error {
|
func EncryptShares(secretShares [][]byte, pgpKeys []string) ([][]byte, error) {
|
||||||
if len(*secretShares) != len(*pgpKeys) {
|
if len(secretShares) != len(pgpKeys) {
|
||||||
return fmt.Errorf("Mismatch between number of generated shares and number of PGP keys")
|
return nil, fmt.Errorf("Mismatch between number of generated shares and number of PGP keys")
|
||||||
}
|
}
|
||||||
for i, keystring := range *pgpKeys {
|
encryptedShares := [][]byte{}
|
||||||
|
for i, keystring := range pgpKeys {
|
||||||
data, err := base64.StdEncoding.DecodeString(keystring)
|
data, err := base64.StdEncoding.DecodeString(keystring)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Error decoding given PGP key: %s", err)
|
return nil, fmt.Errorf("Error decoding given PGP key: %s", err)
|
||||||
}
|
}
|
||||||
entity, err := openpgp.ReadEntity(packet.NewReader(bytes.NewBuffer(data)))
|
entity, err := openpgp.ReadEntity(packet.NewReader(bytes.NewBuffer(data)))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Error parsing given PGP key: %s", err)
|
return nil, fmt.Errorf("Error parsing given PGP key: %s", err)
|
||||||
}
|
}
|
||||||
ctBuf := bytes.NewBuffer(nil)
|
ctBuf := bytes.NewBuffer(nil)
|
||||||
pt, err := openpgp.Encrypt(ctBuf, []*openpgp.Entity{entity}, nil, nil, nil)
|
pt, err := openpgp.Encrypt(ctBuf, []*openpgp.Entity{entity}, nil, nil, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Error setting up encryption for PGP message: %s", err)
|
return nil, fmt.Errorf("Error setting up encryption for PGP message: %s", err)
|
||||||
}
|
}
|
||||||
_, err = pt.Write((*secretShares)[i])
|
_, err = pt.Write(secretShares[i])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Error encrypting PGP message: %s", err)
|
return nil, fmt.Errorf("Error encrypting PGP message: %s", err)
|
||||||
}
|
}
|
||||||
pt.Close()
|
pt.Close()
|
||||||
(*secretShares)[i] = ctBuf.Bytes()
|
encryptedShares = append(encryptedShares, ctBuf.Bytes())
|
||||||
}
|
}
|
||||||
return nil
|
return encryptedShares, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,6 @@ package http
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"fmt"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/hashicorp/vault/vault"
|
"github.com/hashicorp/vault/vault"
|
||||||
|
@ -41,19 +40,11 @@ func handleSysInitPut(core *vault.Core, w http.ResponseWriter, r *http.Request)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
switch {
|
|
||||||
case req.SecretShares > 0 && len(req.SecretPGPKeys) > 0 && len(req.SecretPGPKeys) != req.SecretShares:
|
|
||||||
respondError(w, http.StatusBadRequest, fmt.Errorf("Mismatch between key-shares and length of pgp-keys (you can specify pgp-keys alone)"))
|
|
||||||
return
|
|
||||||
case req.SecretShares == 0 && len(req.SecretPGPKeys) > 0:
|
|
||||||
req.SecretShares = len(req.SecretPGPKeys)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize
|
// Initialize
|
||||||
result, err := core.Initialize(&vault.SealConfig{
|
result, err := core.Initialize(&vault.SealConfig{
|
||||||
SecretShares: req.SecretShares,
|
SecretShares: req.SecretShares,
|
||||||
SecretThreshold: req.SecretThreshold,
|
SecretThreshold: req.SecretThreshold,
|
||||||
SecretPGPKeys: req.SecretPGPKeys,
|
PGPKeys: req.PGPKeys,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
respondError(w, http.StatusBadRequest, err)
|
respondError(w, http.StatusBadRequest, err)
|
||||||
|
@ -75,7 +66,7 @@ func handleSysInitPut(core *vault.Core, w http.ResponseWriter, r *http.Request)
|
||||||
type InitRequest struct {
|
type InitRequest struct {
|
||||||
SecretShares int `json:"secret_shares"`
|
SecretShares int `json:"secret_shares"`
|
||||||
SecretThreshold int `json:"secret_threshold"`
|
SecretThreshold int `json:"secret_threshold"`
|
||||||
SecretPGPKeys []string `json:"secret_pgp_keys"`
|
PGPKeys []string `json:"pgp_keys"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type InitResponse struct {
|
type InitResponse struct {
|
||||||
|
|
|
@ -79,7 +79,7 @@ func handleSysRekeyInitPut(core *vault.Core, w http.ResponseWriter, r *http.Requ
|
||||||
err := core.RekeyInit(&vault.SealConfig{
|
err := core.RekeyInit(&vault.SealConfig{
|
||||||
SecretShares: req.SecretShares,
|
SecretShares: req.SecretShares,
|
||||||
SecretThreshold: req.SecretThreshold,
|
SecretThreshold: req.SecretThreshold,
|
||||||
SecretPGPKeys: req.SecretPGPKeys,
|
PGPKeys: req.PGPKeys,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
respondError(w, http.StatusBadRequest, err)
|
respondError(w, http.StatusBadRequest, err)
|
||||||
|
@ -152,7 +152,7 @@ func handleSysRekeyUpdate(core *vault.Core) http.Handler {
|
||||||
type RekeyRequest struct {
|
type RekeyRequest struct {
|
||||||
SecretShares int `json:"secret_shares"`
|
SecretShares int `json:"secret_shares"`
|
||||||
SecretThreshold int `json:"secret_threshold"`
|
SecretThreshold int `json:"secret_threshold"`
|
||||||
SecretPGPKeys []string `json:"secret_pgp_keys"`
|
PGPKeys []string `json:"pgp_keys"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type RekeyStatusResponse struct {
|
type RekeyStatusResponse struct {
|
||||||
|
|
|
@ -86,11 +86,11 @@ type SealConfig struct {
|
||||||
// split into. This is the N value of Shamir.
|
// split into. This is the N value of Shamir.
|
||||||
SecretShares int `json:"secret_shares"`
|
SecretShares int `json:"secret_shares"`
|
||||||
|
|
||||||
// SecretPGPKeys is the array of public PGP keys used,
|
// PGPKeys is the array of public PGP keys used,
|
||||||
// if requested, to encrypt the output unseal tokens. If
|
// if requested, to encrypt the output unseal tokens. If
|
||||||
// provided, it sets the value of SecretShares. Ordering
|
// provided, it sets the value of SecretShares. Ordering
|
||||||
// is important.
|
// is important.
|
||||||
SecretPGPKeys []string `json:"-"`
|
PGPKeys []string `json:"-"`
|
||||||
|
|
||||||
// SecretThreshold is the number of parts required
|
// SecretThreshold is the number of parts required
|
||||||
// to open the vault. This is the T value of Shamir
|
// to open the vault. This is the T value of Shamir
|
||||||
|
@ -117,11 +117,11 @@ func (s *SealConfig) Validate() error {
|
||||||
if s.SecretThreshold > s.SecretShares {
|
if s.SecretThreshold > s.SecretShares {
|
||||||
return fmt.Errorf("secret threshold cannot be larger than secret shares")
|
return fmt.Errorf("secret threshold cannot be larger than secret shares")
|
||||||
}
|
}
|
||||||
if len(s.SecretPGPKeys) > 0 && len(s.SecretPGPKeys) != s.SecretShares {
|
if len(s.PGPKeys) > 0 && len(s.PGPKeys) != s.SecretShares {
|
||||||
return fmt.Errorf("count mismatch between number of provided PGP keys and number of shares")
|
return fmt.Errorf("count mismatch between number of provided PGP keys and number of shares")
|
||||||
}
|
}
|
||||||
if len(s.SecretPGPKeys) > 0 {
|
if len(s.PGPKeys) > 0 {
|
||||||
for _, keystring := range s.SecretPGPKeys {
|
for _, keystring := range s.PGPKeys {
|
||||||
data, err := base64.StdEncoding.DecodeString(keystring)
|
data, err := base64.StdEncoding.DecodeString(keystring)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Error decoding given PGP key: %s", err)
|
return fmt.Errorf("Error decoding given PGP key: %s", err)
|
||||||
|
@ -739,10 +739,12 @@ func (c *Core) Initialize(config *SealConfig) (*InitResult, error) {
|
||||||
results.SecretShares = shares
|
results.SecretShares = shares
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(config.SecretPGPKeys) > 0 {
|
if len(config.PGPKeys) > 0 {
|
||||||
if err := pgpkeys.EncryptShares(&results.SecretShares, &config.SecretPGPKeys); err != nil {
|
encryptedShares, err := pgpkeys.EncryptShares(results.SecretShares, config.PGPKeys)
|
||||||
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
results.SecretShares = encryptedShares
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize the barrier
|
// Initialize the barrier
|
||||||
|
@ -1217,10 +1219,12 @@ func (c *Core) RekeyUpdate(key []byte) (*RekeyResult, error) {
|
||||||
results.SecretShares = shares
|
results.SecretShares = shares
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(c.rekeyConfig.SecretPGPKeys) > 0 {
|
if len(c.rekeyConfig.PGPKeys) > 0 {
|
||||||
if err := pgpkeys.EncryptShares(&results.SecretShares, &c.rekeyConfig.SecretPGPKeys); err != nil {
|
encryptedShares, err := pgpkeys.EncryptShares(results.SecretShares, c.rekeyConfig.PGPKeys)
|
||||||
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
results.SecretShares = encryptedShares
|
||||||
}
|
}
|
||||||
|
|
||||||
// Encode the seal configuration
|
// Encode the seal configuration
|
||||||
|
|
|
@ -61,7 +61,7 @@ description: |-
|
||||||
This must be less than or equal to <code>secret_shares</code>.
|
This must be less than or equal to <code>secret_shares</code>.
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<spam class="param">secret_pgp_keys</span>
|
<spam class="param">pgp_keys</span>
|
||||||
<span class="param-flags">optional</spam>
|
<span class="param-flags">optional</spam>
|
||||||
An array of PGP public keys used to encrypt the output unseal keys.
|
An array of PGP public keys used to encrypt the output unseal keys.
|
||||||
Ordering is preserved. The keys must be base64-encoded from their
|
Ordering is preserved. The keys must be base64-encoded from their
|
||||||
|
|
|
@ -78,7 +78,7 @@ description: |-
|
||||||
This must be less than or equal to <code>secret_shares</code>.
|
This must be less than or equal to <code>secret_shares</code>.
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<spam class="param">secret_pgp_keys</span>
|
<spam class="param">pgp_keys</span>
|
||||||
<span class="param-flags">optional</spam>
|
<span class="param-flags">optional</spam>
|
||||||
An array of PGP public keys used to encrypt the output unseal keys.
|
An array of PGP public keys used to encrypt the output unseal keys.
|
||||||
Ordering is preserved. The keys must be base64-encoded from their
|
Ordering is preserved. The keys must be base64-encoded from their
|
||||||
|
|
Loading…
Reference in New Issue