mirror of
https://github.com/hashicorp/vault.git
synced 2026-04-26 00:30:11 -04:00
Add support for PGP encrypting the initial root token. (#1883)
This commit is contained in:
parent
7898a66624
commit
941b066780
12 changed files with 239 additions and 31 deletions
|
|
@ -38,6 +38,7 @@ type InitRequest struct {
|
|||
RecoveryShares int `json:"recovery_shares"`
|
||||
RecoveryThreshold int `json:"recovery_threshold"`
|
||||
RecoveryPGPKeys []string `json:"recovery_pgp_keys"`
|
||||
RootTokenPGPKey string `json:"root_token_pgp_key"`
|
||||
}
|
||||
|
||||
type InitStatusResponse struct {
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ type InitCommand struct {
|
|||
|
||||
func (c *InitCommand) Run(args []string) int {
|
||||
var threshold, shares, storedShares, recoveryThreshold, recoveryShares int
|
||||
var pgpKeys, recoveryPgpKeys pgpkeys.PubKeyFilesFlag
|
||||
var pgpKeys, recoveryPgpKeys, rootTokenPgpKey pgpkeys.PubKeyFilesFlag
|
||||
var auto, check bool
|
||||
var consulServiceName string
|
||||
flags := c.Meta.FlagSet("init", meta.FlagSetDefault)
|
||||
|
|
@ -30,6 +30,7 @@ func (c *InitCommand) Run(args []string) int {
|
|||
flags.IntVar(&threshold, "key-threshold", 3, "")
|
||||
flags.IntVar(&storedShares, "stored-shares", 0, "")
|
||||
flags.Var(&pgpKeys, "pgp-keys", "")
|
||||
flags.Var(&rootTokenPgpKey, "root-token-pgp-key", "")
|
||||
flags.IntVar(&recoveryShares, "recovery-shares", 5, "")
|
||||
flags.IntVar(&recoveryThreshold, "recovery-threshold", 3, "")
|
||||
flags.Var(&recoveryPgpKeys, "recovery-pgp-keys", "")
|
||||
|
|
@ -50,6 +51,15 @@ func (c *InitCommand) Run(args []string) int {
|
|||
RecoveryPGPKeys: recoveryPgpKeys,
|
||||
}
|
||||
|
||||
switch len(rootTokenPgpKey) {
|
||||
case 0:
|
||||
case 1:
|
||||
initRequest.RootTokenPGPKey = rootTokenPgpKey[0]
|
||||
default:
|
||||
c.Ui.Error("Only one PGP key can be specified for encrypting the root token")
|
||||
return 1
|
||||
}
|
||||
|
||||
// If running in 'auto' mode, run service discovery based on environment
|
||||
// variables of Consul.
|
||||
if auto {
|
||||
|
|
@ -60,7 +70,7 @@ func (c *InitCommand) Run(args []string) int {
|
|||
// Create a client to communicate with Consul
|
||||
consulClient, err := consulapi.NewClient(consulConfig)
|
||||
if err != nil {
|
||||
c.Ui.Error(fmt.Sprintf("failed to create Consul client:%v", err))
|
||||
c.Ui.Error(fmt.Sprintf("Failed to create Consul client:%v", err))
|
||||
return 1
|
||||
}
|
||||
|
||||
|
|
@ -289,8 +299,9 @@ Init Options:
|
|||
-key-threshold=3 The number of key shares required to reconstruct
|
||||
the master key.
|
||||
|
||||
-stored-shares=0 The number of unseal keys to store. This is not
|
||||
normally available.
|
||||
-stored-shares=0 The number of unseal keys to store. Only used with
|
||||
Vault HSM. Must currently be equivalent to the
|
||||
number of shares.
|
||||
|
||||
-pgp-keys If provided, must be a comma-separated list of
|
||||
files on disk containing binary- or base64-format
|
||||
|
|
@ -298,11 +309,18 @@ Init Options:
|
|||
"keybase:<username>". The number of given entries
|
||||
must match 'key-shares'. The output unseal keys will
|
||||
be encrypted and base64-encoded, in order, with the
|
||||
given public keys. If you want to use them with the
|
||||
given public keys. If you want to use them with the
|
||||
'vault unseal' command, you will need to base64-
|
||||
decode and decrypt; this will be the plaintext
|
||||
unseal key.
|
||||
|
||||
-root-token-pgp-key If provided, a file on disk with a binary- or
|
||||
base64-format public PGP key, or a Keybase username
|
||||
specified as "keybase:<username>". The output root
|
||||
token will be encrypted and base64-encoded, in
|
||||
order, with the given public key. You will need
|
||||
to base64-decode and decrypt the result.
|
||||
|
||||
-recovery-shares=5 The number of key shares to split the recovery key
|
||||
into. Only used with Vault HSM.
|
||||
|
||||
|
|
|
|||
|
|
@ -1,15 +1,20 @@
|
|||
package command
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"os"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/vault/helper/pgpkeys"
|
||||
"github.com/hashicorp/vault/http"
|
||||
"github.com/hashicorp/vault/meta"
|
||||
"github.com/hashicorp/vault/vault"
|
||||
"github.com/keybase/go-crypto/openpgp"
|
||||
"github.com/keybase/go-crypto/openpgp/packet"
|
||||
"github.com/mitchellh/cli"
|
||||
)
|
||||
|
||||
|
|
@ -148,6 +153,57 @@ func TestInit_custom(t *testing.T) {
|
|||
if !reflect.DeepEqual(expected, sealConf) {
|
||||
t.Fatalf("expected:\n%#v\ngot:\n%#v\n", expected, sealConf)
|
||||
}
|
||||
|
||||
re, err := regexp.Compile("\\s+Initial Root Token:\\s+(.*)")
|
||||
if err != nil {
|
||||
t.Fatalf("Error compiling regex: %s", err)
|
||||
}
|
||||
matches := re.FindAllStringSubmatch(ui.OutputWriter.String(), -1)
|
||||
if len(matches) != 1 {
|
||||
t.Fatalf("Unexpected number of tokens found, got %d", len(matches))
|
||||
}
|
||||
|
||||
rootToken := matches[0][1]
|
||||
|
||||
client, err := c.Client()
|
||||
if err != nil {
|
||||
t.Fatalf("Error fetching client: %v", err)
|
||||
}
|
||||
|
||||
client.SetToken(rootToken)
|
||||
|
||||
re, err = regexp.Compile("\\s*Unseal Key \\d+: (.*)")
|
||||
if err != nil {
|
||||
t.Fatalf("Error compiling regex: %s", err)
|
||||
}
|
||||
matches = re.FindAllStringSubmatch(ui.OutputWriter.String(), -1)
|
||||
if len(matches) != 7 {
|
||||
t.Fatalf("Unexpected number of keys returned, got %d, matches was \n\n%#v\n\n, input was \n\n%s\n\n", len(matches), matches, ui.OutputWriter.String())
|
||||
}
|
||||
|
||||
var unsealed bool
|
||||
for i := 0; i < 3; i++ {
|
||||
decodedKey, err := base64.StdEncoding.DecodeString(strings.TrimSpace(matches[i][1]))
|
||||
if err != nil {
|
||||
t.Fatalf("err decoding key %v: %v", matches[i][1], err)
|
||||
}
|
||||
unsealed, err = core.Unseal(decodedKey)
|
||||
if err != nil {
|
||||
t.Fatalf("err during unseal: %v; key was %v", err, matches[i][1])
|
||||
}
|
||||
}
|
||||
if !unsealed {
|
||||
t.Fatal("expected to be unsealed")
|
||||
}
|
||||
|
||||
tokenInfo, err := client.Auth().Token().LookupSelf()
|
||||
if err != nil {
|
||||
t.Fatalf("Error looking up root token info: %v", err)
|
||||
}
|
||||
|
||||
if tokenInfo.Data["policies"].([]interface{})[0].(string) != "root" {
|
||||
t.Fatalf("expected root policy")
|
||||
}
|
||||
}
|
||||
|
||||
func TestInit_PGP(t *testing.T) {
|
||||
|
|
@ -181,6 +237,7 @@ func TestInit_PGP(t *testing.T) {
|
|||
"-key-shares", "2",
|
||||
"-pgp-keys", pubFiles[0] + ",@" + pubFiles[1] + "," + pubFiles[2],
|
||||
"-key-threshold", "2",
|
||||
"-root-token-pgp-key", pubFiles[0],
|
||||
}
|
||||
|
||||
// This should fail, as key-shares does not match pgp-keys size
|
||||
|
|
@ -193,6 +250,7 @@ func TestInit_PGP(t *testing.T) {
|
|||
"-key-shares", "4",
|
||||
"-pgp-keys", pubFiles[0] + ",@" + pubFiles[1] + "," + pubFiles[2] + "," + pubFiles[3],
|
||||
"-key-threshold", "2",
|
||||
"-root-token-pgp-key", pubFiles[0],
|
||||
}
|
||||
|
||||
ui.OutputWriter.Reset()
|
||||
|
|
@ -242,7 +300,44 @@ func TestInit_PGP(t *testing.T) {
|
|||
t.Fatalf("Unexpected number of tokens found, got %d", len(matches))
|
||||
}
|
||||
|
||||
rootToken := matches[0][1]
|
||||
encRootToken := matches[0][1]
|
||||
privKeyBytes, err := base64.StdEncoding.DecodeString(pgpkeys.TestPrivKey1)
|
||||
if err != nil {
|
||||
t.Fatalf("error decoding private key: %v", err)
|
||||
}
|
||||
ptBuf := bytes.NewBuffer(nil)
|
||||
entity, err := openpgp.ReadEntity(packet.NewReader(bytes.NewBuffer(privKeyBytes)))
|
||||
if err != nil {
|
||||
t.Fatalf("Error parsing private key: %s", err)
|
||||
}
|
||||
var rootBytes []byte
|
||||
rootBytes, err = base64.StdEncoding.DecodeString(encRootToken)
|
||||
if err != nil {
|
||||
t.Fatalf("Error decoding root token: %s", err)
|
||||
}
|
||||
entityList := &openpgp.EntityList{entity}
|
||||
md, err := openpgp.ReadMessage(bytes.NewBuffer(rootBytes), entityList, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("Error decrypting root token: %s", err)
|
||||
}
|
||||
ptBuf.ReadFrom(md.UnverifiedBody)
|
||||
rootToken := ptBuf.String()
|
||||
|
||||
parseDecryptAndTestUnsealKeys(t, ui.OutputWriter.String(), rootToken, false, nil, nil, core)
|
||||
|
||||
client, err := c.Client()
|
||||
if err != nil {
|
||||
t.Fatalf("Error fetching client: %v", err)
|
||||
}
|
||||
|
||||
client.SetToken(rootToken)
|
||||
|
||||
tokenInfo, err := client.Auth().Token().LookupSelf()
|
||||
if err != nil {
|
||||
t.Fatalf("Error looking up root token info: %v", err)
|
||||
}
|
||||
|
||||
if tokenInfo.Data["policies"].([]interface{})[0].(string) != "root" {
|
||||
t.Fatalf("expected root policy")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -574,10 +574,13 @@ func (c *ServerCommand) Run(args []string) int {
|
|||
|
||||
func (c *ServerCommand) enableDev(core *vault.Core, rootTokenID string) (*vault.InitResult, error) {
|
||||
// Initialize it with a basic single key
|
||||
init, err := core.Initialize(&vault.SealConfig{
|
||||
SecretShares: 1,
|
||||
SecretThreshold: 1,
|
||||
}, nil)
|
||||
init, err := core.Initialize(&vault.InitParams{
|
||||
BarrierConfig: &vault.SealConfig{
|
||||
SecretShares: 1,
|
||||
SecretThreshold: 1,
|
||||
},
|
||||
RecoveryConfig: nil,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -81,7 +81,13 @@ func handleSysInitPut(core *vault.Core, w http.ResponseWriter, r *http.Request)
|
|||
}
|
||||
}
|
||||
|
||||
result, initErr := core.Initialize(barrierConfig, recoveryConfig)
|
||||
initParams := &vault.InitParams{
|
||||
BarrierConfig: barrierConfig,
|
||||
RecoveryConfig: recoveryConfig,
|
||||
RootTokenPGPKey: req.RootTokenPGPKey,
|
||||
}
|
||||
|
||||
result, initErr := core.Initialize(initParams)
|
||||
if initErr != nil {
|
||||
if !errwrap.ContainsType(initErr, new(vault.NonFatalError)) {
|
||||
respondError(w, http.StatusBadRequest, initErr)
|
||||
|
|
@ -128,6 +134,7 @@ type InitRequest struct {
|
|||
RecoveryShares int `json:"recovery_shares"`
|
||||
RecoveryThreshold int `json:"recovery_threshold"`
|
||||
RecoveryPGPKeys []string `json:"recovery_pgp_keys"`
|
||||
RootTokenPGPKey string `json:"root_token_pgp_key"`
|
||||
}
|
||||
|
||||
type InitResponse struct {
|
||||
|
|
|
|||
|
|
@ -154,10 +154,13 @@ func Test(tt TestT, c TestCase) {
|
|||
}
|
||||
|
||||
// Initialize the core
|
||||
init, err := core.Initialize(&vault.SealConfig{
|
||||
SecretShares: 1,
|
||||
SecretThreshold: 1,
|
||||
}, nil)
|
||||
init, err := core.Initialize(&vault.InitParams{
|
||||
BarrierConfig: &vault.SealConfig{
|
||||
SecretShares: 1,
|
||||
SecretThreshold: 1,
|
||||
},
|
||||
RecoveryConfig: nil,
|
||||
})
|
||||
if err != nil {
|
||||
tt.Fatal("error initializing core: ", err)
|
||||
return
|
||||
|
|
|
|||
|
|
@ -56,7 +56,10 @@ func TestCore_Unseal_MultiShare(t *testing.T) {
|
|||
SecretShares: 5,
|
||||
SecretThreshold: 3,
|
||||
}
|
||||
res, err := c.Initialize(sealConf, nil)
|
||||
res, err := c.Initialize(&InitParams{
|
||||
BarrierConfig: sealConf,
|
||||
RecoveryConfig: nil,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
|
@ -141,7 +144,10 @@ func TestCore_Unseal_Single(t *testing.T) {
|
|||
SecretShares: 1,
|
||||
SecretThreshold: 1,
|
||||
}
|
||||
res, err := c.Initialize(sealConf, nil)
|
||||
res, err := c.Initialize(&InitParams{
|
||||
BarrierConfig: sealConf,
|
||||
RecoveryConfig: nil,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
|
@ -196,7 +202,10 @@ func TestCore_Route_Sealed(t *testing.T) {
|
|||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
res, err := c.Initialize(sealConf, nil)
|
||||
res, err := c.Initialize(&InitParams{
|
||||
BarrierConfig: sealConf,
|
||||
RecoveryConfig: nil,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package vault
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
|
||||
|
|
@ -8,6 +9,14 @@ import (
|
|||
"github.com/hashicorp/vault/shamir"
|
||||
)
|
||||
|
||||
// InitParams keeps the init function from being littered with too many
|
||||
// params, that's it!
|
||||
type InitParams struct {
|
||||
BarrierConfig *SealConfig
|
||||
RecoveryConfig *SealConfig
|
||||
RootTokenPGPKey string
|
||||
}
|
||||
|
||||
// InitResult is used to provide the key parts back after
|
||||
// they are generated as part of the initialization.
|
||||
type InitResult struct {
|
||||
|
|
@ -79,7 +88,10 @@ func (c *Core) generateShares(sc *SealConfig) ([]byte, [][]byte, error) {
|
|||
|
||||
// Initialize is used to initialize the Vault with the given
|
||||
// configurations.
|
||||
func (c *Core) Initialize(barrierConfig, recoveryConfig *SealConfig) (*InitResult, error) {
|
||||
func (c *Core) Initialize(initParams *InitParams) (*InitResult, error) {
|
||||
barrierConfig := initParams.BarrierConfig
|
||||
recoveryConfig := initParams.RecoveryConfig
|
||||
|
||||
if c.seal.RecoveryKeySupported() {
|
||||
if recoveryConfig == nil {
|
||||
return nil, fmt.Errorf("recovery configuration must be supplied")
|
||||
|
|
@ -219,6 +231,15 @@ func (c *Core) Initialize(barrierConfig, recoveryConfig *SealConfig) (*InitResul
|
|||
results.RootToken = rootToken.ID
|
||||
c.logger.Info("core: root token generated")
|
||||
|
||||
if initParams.RootTokenPGPKey != "" {
|
||||
_, encryptedVals, err := pgpkeys.EncryptShares([][]byte{[]byte(results.RootToken)}, []string{initParams.RootTokenPGPKey})
|
||||
if err != nil {
|
||||
c.logger.Error("core: root token encryption failed", "error", err)
|
||||
return nil, err
|
||||
}
|
||||
results.RootToken = base64.StdEncoding.EncodeToString(encryptedVals[0])
|
||||
}
|
||||
|
||||
// Prepare to re-seal
|
||||
if err := c.preSeal(); err != nil {
|
||||
c.logger.Error("core: pre-seal teardown failed", "error", err)
|
||||
|
|
|
|||
|
|
@ -69,7 +69,10 @@ func testCore_Init_Common(t *testing.T, c *Core, conf *CoreConfig, barrierConf,
|
|||
}
|
||||
}
|
||||
|
||||
res, err := c.Initialize(barrierConf, recoveryConf)
|
||||
res, err := c.Initialize(&InitParams{
|
||||
BarrierConfig: barrierConf,
|
||||
RecoveryConfig: recoveryConf,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
|
@ -87,7 +90,10 @@ func testCore_Init_Common(t *testing.T, c *Core, conf *CoreConfig, barrierConf,
|
|||
t.Fatalf("Bad: %#v", res)
|
||||
}
|
||||
|
||||
_, err = c.Initialize(barrierConf, recoveryConf)
|
||||
_, err = c.Initialize(&InitParams{
|
||||
BarrierConfig: barrierConf,
|
||||
RecoveryConfig: recoveryConf,
|
||||
})
|
||||
if err != ErrAlreadyInit {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
|
@ -125,7 +131,10 @@ func testCore_Init_Common(t *testing.T, c *Core, conf *CoreConfig, barrierConf,
|
|||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
_, err = c2.Initialize(barrierConf, recoveryConf)
|
||||
_, err = c2.Initialize(&InitParams{
|
||||
BarrierConfig: barrierConf,
|
||||
RecoveryConfig: recoveryConf,
|
||||
})
|
||||
if err != ErrAlreadyInit {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -95,7 +95,10 @@ func (d *TestSeal) SetRecoveryKey(key []byte) error {
|
|||
func TestCoreUnsealedWithConfigs(t *testing.T, barrierConf, recoveryConf *SealConfig) (*Core, [][]byte, [][]byte, string) {
|
||||
seal := &TestSeal{}
|
||||
core := TestCoreWithSeal(t, seal)
|
||||
result, err := core.Initialize(barrierConf, recoveryConf)
|
||||
result, err := core.Initialize(&InitParams{
|
||||
BarrierConfig: barrierConf,
|
||||
RecoveryConfig: recoveryConf,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -143,10 +143,13 @@ func TestCoreInit(t *testing.T, core *Core) ([]byte, string) {
|
|||
func TestCoreInitClusterWrapperSetup(t *testing.T, core *Core, clusterAddrs []*net.TCPAddr, handlerSetupFunc func() (http.Handler, http.Handler)) ([]byte, string) {
|
||||
core.SetClusterListenerAddrs(clusterAddrs)
|
||||
core.SetClusterSetupFuncs(handlerSetupFunc)
|
||||
result, err := core.Initialize(&SealConfig{
|
||||
SecretShares: 1,
|
||||
SecretThreshold: 1,
|
||||
}, nil)
|
||||
result, err := core.Initialize(&InitParams{
|
||||
BarrierConfig: &SealConfig{
|
||||
SecretShares: 1,
|
||||
SecretThreshold: 1,
|
||||
},
|
||||
RecoveryConfig: nil,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,8 +39,9 @@ description: |-
|
|||
<dl>
|
||||
<dt>Description</dt>
|
||||
<dd>
|
||||
Initializes a new Vault. The Vault must've not been previously
|
||||
initialized.
|
||||
Initializes a new Vault. The Vault must not have been previously
|
||||
initialized. The recovery options, as well as the stored shares option, are
|
||||
only available when using Vault HSM.
|
||||
</dd>
|
||||
|
||||
<dt>Method</dt>
|
||||
|
|
@ -49,6 +50,12 @@ description: |-
|
|||
<dt>Parameters</dt>
|
||||
<dd>
|
||||
<ul>
|
||||
<li>
|
||||
<span class="param">root_token_pgp_key</span>
|
||||
<span class="param-flags">optional</span>
|
||||
A PGP public key used to encrypt the initial root token. The key
|
||||
must be base64-encoded from its original binary representation.
|
||||
</li>
|
||||
<li>
|
||||
<span class="param">secret_shares</span>
|
||||
<span class="param-flags">required</span>
|
||||
|
|
@ -57,8 +64,10 @@ description: |-
|
|||
<li>
|
||||
<span class="param">secret_threshold</span>
|
||||
<span class="param-flags">required</span>
|
||||
The number of shares required to reconstruct the master key.
|
||||
This must be less than or equal to <code>secret_shares</code>.
|
||||
The number of shares required to reconstruct the master key. This must
|
||||
be less than or equal to <code>secret_shares</code>. If using Vault HSM
|
||||
with auto-unsealing, this value must be the same as
|
||||
<code>secret_shares</code>.
|
||||
</li>
|
||||
<li>
|
||||
<span class="param">pgp_keys</span>
|
||||
|
|
@ -68,6 +77,33 @@ description: |-
|
|||
original binary representation. The size of this array must be the
|
||||
same as <code>secret_shares</code>.
|
||||
</li>
|
||||
<li>
|
||||
<span class="param">stored_shares</span>
|
||||
<span class="param-flags">required</span>
|
||||
The number of shares that should be encrypted by the HSM and stored for
|
||||
auto-unsealing (Vault HSM only). Currently must be the same as
|
||||
<code>secret_shares</code>.
|
||||
</li>
|
||||
<li>
|
||||
<span class="param">recovery_shares</span>
|
||||
<span class="param-flags">required</span>
|
||||
The number of shares to split the recovery key into (Vault HSM only).
|
||||
</li>
|
||||
<li>
|
||||
<span class="param">recovery_threshold</span>
|
||||
<span class="param-flags">required</span>
|
||||
The number of shares required to reconstruct the recovery key (Vault
|
||||
HSM only). This must be less than or equal to
|
||||
<code>recovery_shares</code>.
|
||||
</li>
|
||||
<li>
|
||||
<span class="param">recovery_pgp_keys</span>
|
||||
<span class="param-flags">optional</span>
|
||||
An array of PGP public keys used to encrypt the output recovery keys
|
||||
(Vault HSM only). Ordering is preserved. The keys must be
|
||||
base64-encoded from their original binary representation. The size of
|
||||
this array must be the same as <code>recovery_shares</code>.
|
||||
</li>
|
||||
</ul>
|
||||
</dd>
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue