2015-03-13 18:11:59 +00:00
|
|
|
package vault
|
|
|
|
|
|
|
|
import (
|
2015-07-10 22:18:02 +00:00
|
|
|
"bytes"
|
2016-01-15 15:55:35 +00:00
|
|
|
"crypto/rand"
|
2015-11-19 01:26:03 +00:00
|
|
|
"crypto/sha256"
|
2015-07-10 15:56:14 +00:00
|
|
|
"fmt"
|
2016-01-09 02:21:02 +00:00
|
|
|
"log"
|
2015-07-10 22:18:02 +00:00
|
|
|
"net"
|
2016-01-09 02:21:02 +00:00
|
|
|
"os"
|
2015-07-10 22:18:02 +00:00
|
|
|
"os/exec"
|
2015-03-13 18:11:59 +00:00
|
|
|
"testing"
|
2015-09-02 19:56:58 +00:00
|
|
|
"time"
|
2015-03-13 18:11:59 +00:00
|
|
|
|
2015-07-10 22:18:02 +00:00
|
|
|
"golang.org/x/crypto/ssh"
|
|
|
|
|
2016-01-09 02:21:02 +00:00
|
|
|
"github.com/hashicorp/go-uuid"
|
2015-04-02 01:36:13 +00:00
|
|
|
"github.com/hashicorp/vault/audit"
|
2015-11-19 01:26:03 +00:00
|
|
|
"github.com/hashicorp/vault/helper/salt"
|
2015-04-01 03:24:51 +00:00
|
|
|
"github.com/hashicorp/vault/logical"
|
|
|
|
"github.com/hashicorp/vault/logical/framework"
|
2015-03-13 18:11:59 +00:00
|
|
|
"github.com/hashicorp/vault/physical"
|
|
|
|
)
|
|
|
|
|
|
|
|
// This file contains a number of methods that are useful for unit
|
|
|
|
// tests within other packages.
|
|
|
|
|
2015-07-10 22:18:02 +00:00
|
|
|
const (
|
|
|
|
testSharedPublicKey = `
|
2015-07-10 23:27:21 +00:00
|
|
|
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC9i+hFxZHGo6KblVme4zrAcJstR6I0PTJozW286X4WyvPnkMYDQ5mnhEYC7UWCvjoTWbPEXPX7NjhRtwQTGD67bV+lrxgfyzK1JZbUXK4PwgKJvQD+XyyWYMzDgGSQY61KUSqCxymSm/9NZkPU3ElaQ9xQuTzPpztM4ROfb8f2Yv6/ZESZsTo0MTAkp8Pcy+WkioI/uJ1H7zqs0EA4OMY4aDJRu0UtP4rTVeYNEAuRXdX+eH4aW3KMvhzpFTjMbaJHJXlEeUm2SaX5TNQyTOvghCeQILfYIL/Ca2ij8iwCmulwdV6eQGfd4VDu40PvSnmfoaE38o6HaPnX0kUcnKiT
|
2015-07-10 22:18:02 +00:00
|
|
|
`
|
|
|
|
testSharedPrivateKey = `
|
|
|
|
-----BEGIN RSA PRIVATE KEY-----
|
|
|
|
MIIEogIBAAKCAQEAvYvoRcWRxqOim5VZnuM6wHCbLUeiND0yaM1tvOl+Fsrz55DG
|
|
|
|
A0OZp4RGAu1Fgr46E1mzxFz1+zY4UbcEExg+u21fpa8YH8sytSWW1FyuD8ICib0A
|
|
|
|
/l8slmDMw4BkkGOtSlEqgscpkpv/TWZD1NxJWkPcULk8z6c7TOETn2/H9mL+v2RE
|
|
|
|
mbE6NDEwJKfD3MvlpIqCP7idR+86rNBAODjGOGgyUbtFLT+K01XmDRALkV3V/nh+
|
|
|
|
GltyjL4c6RU4zG2iRyV5RHlJtkml+UzUMkzr4IQnkCC32CC/wmtoo/IsAprpcHVe
|
|
|
|
nkBn3eFQ7uND70p5n6GhN/KOh2j519JFHJyokwIDAQABAoIBAHX7VOvBC3kCN9/x
|
|
|
|
+aPdup84OE7Z7MvpX6w+WlUhXVugnmsAAVDczhKoUc/WktLLx2huCGhsmKvyVuH+
|
|
|
|
MioUiE+vx75gm3qGx5xbtmOfALVMRLopjCnJYf6EaFA0ZeQ+NwowNW7Lu0PHmAU8
|
|
|
|
Z3JiX8IwxTz14DU82buDyewO7v+cEr97AnERe3PUcSTDoUXNaoNxjNpEJkKREY6h
|
|
|
|
4hAY676RT/GsRcQ8tqe/rnCqPHNd7JGqL+207FK4tJw7daoBjQyijWuB7K5chSal
|
|
|
|
oPInylM6b13ASXuOAOT/2uSUBWmFVCZPDCmnZxy2SdnJGbsJAMl7Ma3MUlaGvVI+
|
|
|
|
Tfh1aQkCgYEA4JlNOabTb3z42wz6mz+Nz3JRwbawD+PJXOk5JsSnV7DtPtfgkK9y
|
|
|
|
6FTQdhnozGWShAvJvc+C4QAihs9AlHXoaBY5bEU7R/8UK/pSqwzam+MmxmhVDV7G
|
|
|
|
IMQPV0FteoXTaJSikhZ88mETTegI2mik+zleBpVxvfdhE5TR+lq8Br0CgYEA2AwJ
|
|
|
|
CUD5CYUSj09PluR0HHqamWOrJkKPFPwa+5eiTTCzfBBxImYZh7nXnWuoviXC0sg2
|
|
|
|
AuvCW+uZ48ygv/D8gcz3j1JfbErKZJuV+TotK9rRtNIF5Ub7qysP7UjyI7zCssVM
|
|
|
|
kuDd9LfRXaB/qGAHNkcDA8NxmHW3gpln4CFdSY8CgYANs4xwfercHEWaJ1qKagAe
|
|
|
|
rZyrMpffAEhicJ/Z65lB0jtG4CiE6w8ZeUMWUVJQVcnwYD+4YpZbX4S7sJ0B8Ydy
|
|
|
|
AhkSr86D/92dKTIt2STk6aCN7gNyQ1vW198PtaAWH1/cO2UHgHOy3ZUt5X/Uwxl9
|
|
|
|
cex4flln+1Viumts2GgsCQKBgCJH7psgSyPekK5auFdKEr5+Gc/jB8I/Z3K9+g4X
|
|
|
|
5nH3G1PBTCJYLw7hRzw8W/8oALzvddqKzEFHphiGXK94Lqjt/A4q1OdbCrhiE68D
|
|
|
|
My21P/dAKB1UYRSs9Y8CNyHCjuZM9jSMJ8vv6vG/SOJPsnVDWVAckAbQDvlTHC9t
|
|
|
|
O98zAoGAcbW6uFDkrv0XMCpB9Su3KaNXOR0wzag+WIFQRXCcoTvxVi9iYfUReQPi
|
|
|
|
oOyBJU/HMVvBfv4g+OVFLVgSwwm6owwsouZ0+D/LasbuHqYyqYqdyPJQYzWA2Y+F
|
|
|
|
+B6f4RoPdSXj24JHPg/ioRxjaj094UXJxua2yfkcecGNEuBQHSs=
|
|
|
|
-----END RSA PRIVATE KEY-----
|
|
|
|
`
|
|
|
|
)
|
|
|
|
|
2015-03-13 18:11:59 +00:00
|
|
|
// TestCore returns a pure in-memory, uninitialized core for testing.
|
|
|
|
func TestCore(t *testing.T) *Core {
|
2016-04-25 19:39:04 +00:00
|
|
|
return TestCoreWithSeal(t, nil)
|
|
|
|
}
|
|
|
|
|
|
|
|
// TestCoreWithSeal returns a pure in-memory, uninitialized core with the
|
|
|
|
// specified seal for testing.
|
|
|
|
func TestCoreWithSeal(t *testing.T, testSeal Seal) *Core {
|
2015-04-02 01:36:13 +00:00
|
|
|
noopAudits := map[string]audit.Factory{
|
2015-09-18 21:36:42 +00:00
|
|
|
"noop": func(config *audit.BackendConfig) (audit.Backend, error) {
|
2015-11-19 01:26:03 +00:00
|
|
|
view := &logical.InmemStorage{}
|
|
|
|
view.Put(&logical.StorageEntry{
|
|
|
|
Key: "salt",
|
|
|
|
Value: []byte("foo"),
|
|
|
|
})
|
|
|
|
var err error
|
|
|
|
config.Salt, err = salt.NewSalt(view, &salt.Config{
|
|
|
|
HMAC: sha256.New,
|
|
|
|
HMACType: "hmac-sha256",
|
|
|
|
})
|
|
|
|
if err != nil {
|
2016-04-13 19:38:29 +00:00
|
|
|
t.Fatalf("error getting new salt: %v", err)
|
2015-11-19 01:26:03 +00:00
|
|
|
}
|
2015-09-18 21:36:42 +00:00
|
|
|
return &noopAudit{
|
|
|
|
Config: config,
|
|
|
|
}, nil
|
2015-04-02 01:36:13 +00:00
|
|
|
},
|
|
|
|
}
|
2015-04-01 03:24:51 +00:00
|
|
|
noopBackends := make(map[string]logical.Factory)
|
2015-09-04 20:58:12 +00:00
|
|
|
noopBackends["noop"] = func(config *logical.BackendConfig) (logical.Backend, error) {
|
|
|
|
b := new(framework.Backend)
|
|
|
|
b.Setup(config)
|
|
|
|
return b, nil
|
2015-04-01 03:24:51 +00:00
|
|
|
}
|
2015-09-04 20:58:12 +00:00
|
|
|
noopBackends["http"] = func(config *logical.BackendConfig) (logical.Backend, error) {
|
2015-05-27 21:19:12 +00:00
|
|
|
return new(rawHTTP), nil
|
|
|
|
}
|
2015-07-10 15:56:14 +00:00
|
|
|
logicalBackends := make(map[string]logical.Factory)
|
|
|
|
for backendName, backendFactory := range noopBackends {
|
|
|
|
logicalBackends[backendName] = backendFactory
|
|
|
|
}
|
2015-09-19 22:24:53 +00:00
|
|
|
logicalBackends["generic"] = LeasedPassthroughBackendFactory
|
2015-07-10 15:56:14 +00:00
|
|
|
for backendName, backendFactory := range testLogicalBackends {
|
|
|
|
logicalBackends[backendName] = backendFactory
|
|
|
|
}
|
2015-04-01 03:24:51 +00:00
|
|
|
|
2016-04-26 03:10:32 +00:00
|
|
|
logger := log.New(os.Stderr, "", log.LstdFlags)
|
|
|
|
physicalBackend := physical.NewInmem(logger)
|
2016-04-25 19:39:04 +00:00
|
|
|
conf := &CoreConfig{
|
2015-04-01 03:24:51 +00:00
|
|
|
Physical: physicalBackend,
|
2015-04-02 01:36:13 +00:00
|
|
|
AuditBackends: noopAudits,
|
2015-07-10 15:56:14 +00:00
|
|
|
LogicalBackends: logicalBackends,
|
2015-04-01 03:24:51 +00:00
|
|
|
CredentialBackends: noopBackends,
|
2015-04-29 01:12:57 +00:00
|
|
|
DisableMlock: true,
|
2016-04-26 03:10:32 +00:00
|
|
|
Logger: logger,
|
2016-04-25 19:39:04 +00:00
|
|
|
}
|
|
|
|
if testSeal != nil {
|
|
|
|
conf.Seal = testSeal
|
|
|
|
}
|
|
|
|
|
|
|
|
c, err := NewCore(conf)
|
2015-03-13 18:11:59 +00:00
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("err: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return c
|
|
|
|
}
|
|
|
|
|
|
|
|
// TestCoreInit initializes the core with a single key, and returns
|
2015-03-24 18:37:07 +00:00
|
|
|
// the key that must be used to unseal the core and a root token.
|
|
|
|
func TestCoreInit(t *testing.T, core *Core) ([]byte, string) {
|
2015-03-13 18:11:59 +00:00
|
|
|
result, err := core.Initialize(&SealConfig{
|
|
|
|
SecretShares: 1,
|
|
|
|
SecretThreshold: 1,
|
2016-04-04 14:44:22 +00:00
|
|
|
}, nil)
|
2015-03-13 18:11:59 +00:00
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("err: %s", err)
|
|
|
|
}
|
2015-03-24 18:37:07 +00:00
|
|
|
return result.SecretShares[0], result.RootToken
|
2015-03-15 00:47:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// TestCoreUnsealed returns a pure in-memory core that is already
|
|
|
|
// initialized and unsealed.
|
2015-03-29 23:14:54 +00:00
|
|
|
func TestCoreUnsealed(t *testing.T) (*Core, []byte, string) {
|
2015-03-15 00:47:11 +00:00
|
|
|
core := TestCore(t)
|
2015-03-24 18:37:07 +00:00
|
|
|
key, token := TestCoreInit(t, core)
|
2015-03-15 01:25:36 +00:00
|
|
|
if _, err := core.Unseal(TestKeyCopy(key)); err != nil {
|
2015-03-15 00:47:11 +00:00
|
|
|
t.Fatalf("unseal err: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
sealed, err := core.Sealed()
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("err checking seal status: %s", err)
|
|
|
|
}
|
|
|
|
if sealed {
|
|
|
|
t.Fatal("should not be sealed")
|
|
|
|
}
|
|
|
|
|
2015-03-24 18:37:07 +00:00
|
|
|
return core, key, token
|
2015-03-13 18:11:59 +00:00
|
|
|
}
|
2015-03-15 01:25:36 +00:00
|
|
|
|
2016-01-09 02:21:02 +00:00
|
|
|
// TestCoreWithTokenStore returns an in-memory core that has a token store
|
|
|
|
// mounted, so that logical token functions can be used
|
|
|
|
func TestCoreWithTokenStore(t *testing.T) (*Core, *TokenStore, []byte, string) {
|
|
|
|
c, key, root := TestCoreUnsealed(t)
|
|
|
|
|
|
|
|
me := &MountEntry{
|
|
|
|
Path: "token/",
|
|
|
|
Type: "token",
|
|
|
|
Description: "token based credentials",
|
|
|
|
}
|
|
|
|
|
|
|
|
meUUID, err := uuid.GenerateUUID()
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
me.UUID = meUUID
|
|
|
|
|
|
|
|
view := NewBarrierView(c.barrier, credentialBarrierPrefix+me.UUID+"/")
|
|
|
|
|
|
|
|
tokenstore, _ := c.newCredentialBackend("token", c.mountEntrySysView(me), view, nil)
|
|
|
|
ts := tokenstore.(*TokenStore)
|
|
|
|
|
|
|
|
router := NewRouter()
|
|
|
|
router.Mount(ts, "auth/token/", &MountEntry{UUID: ""}, ts.view)
|
|
|
|
|
|
|
|
subview := c.systemBarrierView.SubView(expirationSubPath)
|
|
|
|
logger := log.New(os.Stderr, "", log.LstdFlags)
|
|
|
|
|
|
|
|
exp := NewExpirationManager(router, subview, ts, logger)
|
|
|
|
ts.SetExpirationManager(exp)
|
|
|
|
|
|
|
|
return c, ts, key, root
|
|
|
|
}
|
|
|
|
|
2015-03-15 01:25:36 +00:00
|
|
|
// TestKeyCopy is a silly little function to just copy the key so that
|
|
|
|
// it can be used with Unseal easily.
|
|
|
|
func TestKeyCopy(key []byte) []byte {
|
|
|
|
result := make([]byte, len(key))
|
|
|
|
copy(result, key)
|
|
|
|
return result
|
|
|
|
}
|
2015-04-02 01:36:13 +00:00
|
|
|
|
2015-07-10 15:56:14 +00:00
|
|
|
var testLogicalBackends = map[string]logical.Factory{}
|
|
|
|
|
2015-07-10 22:18:02 +00:00
|
|
|
// Starts the test server which responds to SSH authentication.
|
|
|
|
// Used to test the SSH secret backend.
|
2015-08-18 23:48:50 +00:00
|
|
|
func StartSSHHostTestServer() (string, error) {
|
2015-07-10 22:18:02 +00:00
|
|
|
pubKey, _, _, _, err := ssh.ParseAuthorizedKey([]byte(testSharedPublicKey))
|
|
|
|
if err != nil {
|
|
|
|
return "", fmt.Errorf("Error parsing public key")
|
|
|
|
}
|
|
|
|
serverConfig := &ssh.ServerConfig{
|
|
|
|
PublicKeyCallback: func(conn ssh.ConnMetadata, key ssh.PublicKey) (*ssh.Permissions, error) {
|
|
|
|
if bytes.Compare(pubKey.Marshal(), key.Marshal()) == 0 {
|
|
|
|
return &ssh.Permissions{}, nil
|
|
|
|
} else {
|
|
|
|
return nil, fmt.Errorf("Key does not match")
|
|
|
|
}
|
|
|
|
},
|
|
|
|
}
|
|
|
|
signer, err := ssh.ParsePrivateKey([]byte(testSharedPrivateKey))
|
|
|
|
if err != nil {
|
|
|
|
panic("Error parsing private key")
|
|
|
|
}
|
|
|
|
serverConfig.AddHostKey(signer)
|
|
|
|
|
|
|
|
soc, err := net.Listen("tcp", "127.0.0.1:0")
|
|
|
|
if err != nil {
|
|
|
|
return "", fmt.Errorf("Error listening to connection")
|
|
|
|
}
|
|
|
|
|
|
|
|
go func() {
|
|
|
|
for {
|
|
|
|
conn, err := soc.Accept()
|
|
|
|
if err != nil {
|
|
|
|
panic(fmt.Sprintf("Error accepting incoming connection: %s", err))
|
|
|
|
}
|
|
|
|
defer conn.Close()
|
|
|
|
sshConn, chanReqs, _, err := ssh.NewServerConn(conn, serverConfig)
|
|
|
|
if err != nil {
|
|
|
|
panic(fmt.Sprintf("Handshaking error: %v", err))
|
|
|
|
}
|
|
|
|
|
|
|
|
go func() {
|
|
|
|
for chanReq := range chanReqs {
|
|
|
|
go func(chanReq ssh.NewChannel) {
|
|
|
|
if chanReq.ChannelType() != "session" {
|
|
|
|
chanReq.Reject(ssh.UnknownChannelType, "unknown channel type")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
ch, requests, err := chanReq.Accept()
|
|
|
|
if err != nil {
|
|
|
|
panic(fmt.Sprintf("Error accepting channel: %s", err))
|
|
|
|
}
|
|
|
|
|
|
|
|
go func(ch ssh.Channel, in <-chan *ssh.Request) {
|
|
|
|
for req := range in {
|
|
|
|
executeServerCommand(ch, req)
|
|
|
|
}
|
|
|
|
}(ch, requests)
|
|
|
|
}(chanReq)
|
|
|
|
}
|
|
|
|
sshConn.Close()
|
|
|
|
}()
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
return soc.Addr().String(), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// This executes the commands requested to be run on the server.
|
|
|
|
// Used to test the SSH secret backend.
|
|
|
|
func executeServerCommand(ch ssh.Channel, req *ssh.Request) {
|
|
|
|
command := string(req.Payload[4:])
|
2016-05-15 19:22:21 +00:00
|
|
|
cmd := exec.Command("/bin/bash", []string{"-c", command}...)
|
2015-07-10 22:18:02 +00:00
|
|
|
req.Reply(true, nil)
|
|
|
|
|
|
|
|
cmd.Stdout = ch
|
|
|
|
cmd.Stderr = ch
|
|
|
|
cmd.Stdin = ch
|
|
|
|
|
|
|
|
err := cmd.Start()
|
|
|
|
if err != nil {
|
|
|
|
panic(fmt.Sprintf("Error starting the command: '%s'", err))
|
|
|
|
}
|
|
|
|
|
|
|
|
go func() {
|
|
|
|
_, err := cmd.Process.Wait()
|
|
|
|
if err != nil {
|
|
|
|
panic(fmt.Sprintf("Error while waiting for command to finish:'%s'", err))
|
|
|
|
}
|
|
|
|
ch.Close()
|
|
|
|
}()
|
|
|
|
}
|
|
|
|
|
2015-07-10 15:56:14 +00:00
|
|
|
// This adds a logical backend for the test core. This needs to be
|
|
|
|
// invoked before the test core is created.
|
|
|
|
func AddTestLogicalBackend(name string, factory logical.Factory) error {
|
|
|
|
if name == "" {
|
|
|
|
return fmt.Errorf("Missing backend name")
|
|
|
|
}
|
|
|
|
if factory == nil {
|
|
|
|
return fmt.Errorf("Missing backend factory function")
|
|
|
|
}
|
|
|
|
testLogicalBackends[name] = factory
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2015-09-18 21:36:42 +00:00
|
|
|
type noopAudit struct {
|
|
|
|
Config *audit.BackendConfig
|
|
|
|
}
|
2015-04-02 01:36:13 +00:00
|
|
|
|
2015-11-19 01:26:03 +00:00
|
|
|
func (n *noopAudit) GetHash(data string) string {
|
|
|
|
return n.Config.Salt.GetIdentifiedHMAC(data)
|
|
|
|
}
|
|
|
|
|
2015-06-19 01:30:18 +00:00
|
|
|
func (n *noopAudit) LogRequest(a *logical.Auth, r *logical.Request, e error) error {
|
2015-04-02 01:36:13 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (n *noopAudit) LogResponse(a *logical.Auth, r *logical.Request, re *logical.Response, err error) error {
|
|
|
|
return nil
|
|
|
|
}
|
2015-05-27 21:19:12 +00:00
|
|
|
|
|
|
|
type rawHTTP struct{}
|
|
|
|
|
|
|
|
func (n *rawHTTP) HandleRequest(req *logical.Request) (*logical.Response, error) {
|
|
|
|
return &logical.Response{
|
|
|
|
Data: map[string]interface{}{
|
|
|
|
logical.HTTPStatusCode: 200,
|
|
|
|
logical.HTTPContentType: "plain/text",
|
|
|
|
logical.HTTPRawBody: []byte("hello world"),
|
|
|
|
},
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
2016-01-12 20:09:16 +00:00
|
|
|
func (n *rawHTTP) HandleExistenceCheck(req *logical.Request) (bool, bool, error) {
|
|
|
|
return false, false, nil
|
2016-01-07 20:10:05 +00:00
|
|
|
}
|
|
|
|
|
2015-05-27 21:19:12 +00:00
|
|
|
func (n *rawHTTP) SpecialPaths() *logical.Paths {
|
|
|
|
return &logical.Paths{Unauthenticated: []string{"*"}}
|
|
|
|
}
|
2015-09-02 19:56:58 +00:00
|
|
|
|
|
|
|
func (n *rawHTTP) System() logical.SystemView {
|
|
|
|
return logical.StaticSystemView{
|
|
|
|
DefaultLeaseTTLVal: time.Hour * 24,
|
|
|
|
MaxLeaseTTLVal: time.Hour * 24 * 30,
|
|
|
|
}
|
|
|
|
}
|
2015-09-10 14:11:37 +00:00
|
|
|
|
|
|
|
func (n *rawHTTP) Cleanup() {
|
|
|
|
// noop
|
|
|
|
}
|
2016-01-15 15:55:35 +00:00
|
|
|
|
|
|
|
func GenerateRandBytes(length int) ([]byte, error) {
|
|
|
|
if length < 0 {
|
|
|
|
return nil, fmt.Errorf("length must be >= 0")
|
|
|
|
}
|
|
|
|
|
|
|
|
buf := make([]byte, length)
|
|
|
|
if length == 0 {
|
|
|
|
return buf, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
n, err := rand.Read(buf)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if n != length {
|
|
|
|
return nil, fmt.Errorf("unable to read %d bytes; only read %d", length, n)
|
|
|
|
}
|
|
|
|
|
|
|
|
return buf, nil
|
|
|
|
}
|