veyron/security/agent: Added encryption to agent and option not
to encrypt.
* Password will only be requested when the PEM Block is not encrypted.
* Next step is to change the agent from a signer to a principal. Coming
soon...
Change-Id: I811bb6af636d83243052465039192c7bfa307cc1
diff --git a/lib/testutil/security/util.go b/lib/testutil/security/util.go
index 2bf1fdb..f43789e 100644
--- a/lib/testutil/security/util.go
+++ b/lib/testutil/security/util.go
@@ -24,9 +24,11 @@
if err != nil {
panic(err)
}
- p, _, err := vsecurity.NewPersistentPrincipal(dir)
+ p, err := vsecurity.LoadPersistentPrincipal(dir, nil)
if err != nil {
- panic(err)
+ if p, err = vsecurity.CreatePersistentPrincipal(dir, nil); err != nil {
+ panic(err)
+ }
}
blessings, err := parent.Bless(p.PublicKey(), parent.BlessingStore().Default(), name, security.UnconstrainedUse())
if err != nil {
diff --git a/lib/testutil/security/util_test.go b/lib/testutil/security/util_test.go
index 06ed969..a62d65a 100644
--- a/lib/testutil/security/util_test.go
+++ b/lib/testutil/security/util_test.go
@@ -21,12 +21,8 @@
parent := r.Principal()
childdir := NewVeyronCredentials(parent, "child")
- _, existed, err := vsecurity.NewPersistentPrincipal(childdir)
- if err != nil {
- t.Fatal(err)
- }
- if !existed {
- t.Fatalf("Expected NewVeyronCredentials to have populated the directory %q", childdir)
+ if _, err = vsecurity.LoadPersistentPrincipal(childdir, nil); err != nil {
+ t.Fatalf("Expected NewVeyronCredentials to have populated the directory %q: %v", childdir, err)
}
// TODO(ashankar,ataly): Figure out what APIs should we use for more detailed testing
// of the child principal, for example:
diff --git a/runtimes/google/rt/security.go b/runtimes/google/rt/security.go
index a61edc1..fb9935b 100644
--- a/runtimes/google/rt/security.go
+++ b/runtimes/google/rt/security.go
@@ -59,9 +59,12 @@
// TODO(ataly, ashankar): If multiple runtimes are getting
// initialized at the same time from the same VEYRON_CREDENTIALS
// we will need some kind of locking for the credential files.
- var existed bool
- if rt.principal, existed, err = vsecurity.NewPersistentPrincipal(dir); err != nil {
- return err
+ existed := true
+ if rt.principal, err = vsecurity.LoadPersistentPrincipal(dir, nil); err != nil {
+ existed = false
+ if rt.principal, err = vsecurity.CreatePersistentPrincipal(dir, nil); err != nil {
+ return err
+ }
}
if !existed {
return initDefaultBlessings(rt.principal)
diff --git a/security/agent/agentd/main.go b/security/agent/agentd/main.go
index 8e64de8..4ae6e0a 100644
--- a/security/agent/agentd/main.go
+++ b/security/agent/agentd/main.go
@@ -1,31 +1,45 @@
package main
import (
+ "code.google.com/p/gopass"
"flag"
"fmt"
"os"
"os/exec"
"syscall"
-
- "veyron.io/veyron/veyron2/rt"
-
_ "veyron.io/veyron/veyron/profiles"
+ vsecurity "veyron.io/veyron/veyron/security"
"veyron.io/veyron/veyron/security/agent"
"veyron.io/veyron/veyron/security/agent/server"
+
+ "veyron.io/veyron/veyron2/rt"
+ "veyron.io/veyron/veyron2/security"
+ "veyron.io/veyron/veyron2/vlog"
)
func main() {
flag.Usage = func() {
fmt.Fprintf(os.Stderr, `Usage: %s [agent options] command command_args...
-Loads the identity specified in VEYRON_IDENTITY into memory, then
-starts the specified command with access to the identity via the
+Loads the private key specified in under privatekey.pem in VEYRON_AGENT into memory, then
+starts the specified command with access to the private key via the
agent protocol instead of directly reading from disk.
`, os.Args[0])
flag.PrintDefaults()
}
- // Load the identity specified in the environment
+ // TODO(suharshs): Switch to "VEYRON_CREDENTIALS" after agent is a principal.
+ // This will be the end of the old sec model here. Also change the comment above.
+ dir := os.Getenv("VEYRON_AGENT")
+ if len(dir) == 0 {
+ vlog.Fatal("VEYRON_AGENT must be set to directory")
+ }
+
+ p, err := newPrincipalFromDir(dir)
+ if err != nil {
+ vlog.Fatalf("failed to create new principal from dir(%s): %v", dir, err)
+ }
+
runtime := rt.Init()
log := runtime.Logger()
@@ -34,7 +48,6 @@
os.Exit(1)
}
- var err error
if err = os.Setenv(agent.FdVarName, "3"); err != nil {
log.Fatalf("setenv: %v", err)
}
@@ -44,7 +57,7 @@
// Start running our server.
var sock *os.File
- if sock, err = server.RunAnonymousAgent(runtime, runtime.Identity()); err != nil {
+ if sock, err = server.RunAnonymousAgent(runtime, p); err != nil {
log.Fatalf("RunAgent: %v", err)
}
@@ -64,3 +77,33 @@
status := cmd.ProcessState.Sys().(syscall.WaitStatus)
os.Exit(status.ExitStatus())
}
+
+func newPrincipalFromDir(dir string) (security.Principal, error) {
+ p, err := vsecurity.LoadPersistentPrincipal(dir, nil)
+ if os.IsNotExist(err) {
+ return handleDoesNotExist(dir)
+ }
+ if err == vsecurity.MissingPassphraseErr {
+ return handleMissingPassphrase(dir)
+ }
+ return p, err
+}
+
+func handleDoesNotExist(dir string) (security.Principal, error) {
+ fmt.Println("Private key file does not exist. Creating new private key...")
+ pass, err := gopass.GetPass("Enter passphrase (entering nothing will store unecrypted): ")
+ if err != nil {
+ return nil, fmt.Errorf("failed to read passphrase: %v", err)
+ }
+ p, err := vsecurity.CreatePersistentPrincipal(dir, []byte(pass))
+ return p, err
+}
+
+func handleMissingPassphrase(dir string) (security.Principal, error) {
+ fmt.Println("Private key file is encrypted. Please enter passphrase.")
+ pass, err := gopass.GetPass("Enter passphrase: ")
+ if err != nil {
+ return nil, fmt.Errorf("failed to read passphrase: %v", err)
+ }
+ return vsecurity.LoadPersistentPrincipal(dir, []byte(pass))
+}
diff --git a/security/agent/server/server.go b/security/agent/server/server.go
index 0cf8759..3133501 100644
--- a/security/agent/server/server.go
+++ b/security/agent/server/server.go
@@ -22,6 +22,8 @@
"veyron.io/veyron/veyron2/vlog"
)
+// TODO(suharshs): Remove this when replaced with principal. This is just temporary to
+// avoid having to implement a bunch of principal methods that aren't being used yet.
type Signer interface {
Sign(message []byte) (security.Signature, error)
PublicKey() security.PublicKey
diff --git a/security/agent/test.sh b/security/agent/test.sh
index 4e14566..9ed0277 100755
--- a/security/agent/test.sh
+++ b/security/agent/test.sh
@@ -4,7 +4,7 @@
source "${VEYRON_ROOT}/scripts/lib/shell_test.sh"
-readonly WORKDIR="$(shell::tmp_dir)"
+readonly WORKDIR="$(shell::tmp_dir)"
build() {
veyron go build veyron.io/veyron/veyron/security/agent/agentd || shell_test::fail "line ${LINENO}: failed to build agentd"
@@ -23,6 +23,14 @@
cd "${WORKDIR}"
build
+ # TODO(suharshs): After switching to new security model this shoudl be VEYRON_CREDENTIALS.
+ export VEYRON_AGENT="$(shell::tmp_dir)"
+ echo "-----BEGIN EC PRIVATE KEY-----
+MHcCAQEEIGxO3M/KmUac35mffZAVZf0PcXj2qLj4AtTIxdSrAH1AoAoGCCqGSM49
+AwEHoUQDQgAEoZ4cgHtkSzP/PeUBLIOoSJWjx7t2cAWKxj+dd3AgDct2nJujM2+c
+dwwYdDyKjhBc2nacEDlvA3AqtCMU0c97hA==
+-----END EC PRIVATE KEY-----" > "${VEYRON_AGENT}/privatekey.pem"
+
# TODO(ashankar): Remove this block (and remove the compilation of the identity tool)
# once the agent has been updated to comply with the new security model.
local -r ID=$(shell::tmp_file)
@@ -34,7 +42,6 @@
# Test running a single app.
shell_test::start_server ./pingpong --server
- export VEYRON_PUBLICID_STORE="$(shell::tmp_dir)"
./agentd --v=4 ./pingpong || shell_test::fail "line ${LINENO}: failed to run pingpong"
local -r OUTPUT=$(shell::tmp_file)
RESULT=$(shell::check_result echo_identity "${OUTPUT}")
diff --git a/security/agent/test/main.go b/security/agent/test/main.go
index 6b9fbda..384011c 100644
--- a/security/agent/test/main.go
+++ b/security/agent/test/main.go
@@ -4,12 +4,13 @@
"flag"
"fmt"
- "veyron.io/veyron/veyron2/ipc"
- "veyron.io/veyron/veyron2/rt"
-
"veyron.io/veyron/veyron/lib/signals"
_ "veyron.io/veyron/veyron/profiles"
- sflag "veyron.io/veyron/veyron/security/flag"
+ vsecurity "veyron.io/veyron/veyron/security"
+
+ "veyron.io/veyron/veyron2/ipc"
+ "veyron.io/veyron/veyron2/rt"
+ "veyron.io/veyron/veyron2/security"
)
var runServer = flag.Bool("server", false, "Whether to run in server mode")
@@ -54,7 +55,10 @@
log.Fatal("error listening to service: ", err)
}
- if err := s.Serve("pingpong", ipc.LeafDispatcher(serverPong, sflag.NewAuthorizerOrDie())); err != nil {
+ auth := vsecurity.NewACLAuthorizer(security.ACL{In: map[security.BlessingPattern]security.LabelSet{
+ security.AllPrincipals: security.AllLabels,
+ }})
+ if err := s.Serve("pingpong", ipc.LeafDispatcher(serverPong, auth)); err != nil {
log.Fatal("error serving service: ", err)
}
diff --git a/security/blessingroots_test.go b/security/blessingroots_test.go
index c346126..899651c 100644
--- a/security/blessingroots_test.go
+++ b/security/blessingroots_test.go
@@ -97,13 +97,10 @@
}
defer os.RemoveAll(dir)
tester := newRootsTester()
- p, existed, err := NewPersistentPrincipal(dir)
+ p, err := CreatePersistentPrincipal(dir, nil)
if err != nil {
t.Fatal(err)
}
- if existed {
- t.Fatalf("%q already has a principal", dir)
- }
if err := tester.add(p.Roots()); err != nil {
t.Error(err)
}
@@ -111,7 +108,7 @@
t.Error(err)
}
// Recreate the principal (and thus BlessingRoots)
- p2, _, err := NewPersistentPrincipal(dir)
+ p2, err := LoadPersistentPrincipal(dir, nil)
if err != nil {
t.Fatal(err)
}
diff --git a/security/blessingstore_test.go b/security/blessingstore_test.go
index c7152bb..fd535d8 100644
--- a/security/blessingstore_test.go
+++ b/security/blessingstore_test.go
@@ -134,13 +134,10 @@
t.Fatal(err)
}
defer os.RemoveAll(dir)
- p, existed, err := NewPersistentPrincipal(dir)
+ p, err := CreatePersistentPrincipal(dir, nil)
if err != nil {
t.Fatal(err)
}
- if existed {
- t.Errorf("%q already has a principal in it", dir)
- }
tester := newStoreTester(p)
s := p.BlessingStore()
@@ -155,7 +152,7 @@
}
// Recreate the BlessingStore from the directory.
- p2, _, err := NewPersistentPrincipal(dir)
+ p2, err := LoadPersistentPrincipal(dir, nil)
if err != nil {
t.Fatal(err)
}
diff --git a/security/principal.go b/security/principal.go
index 3f5888e..c92430d 100644
--- a/security/principal.go
+++ b/security/principal.go
@@ -13,15 +13,6 @@
const privateKeyFile = "privatekey.pem"
-// newKey generates an ECDSA (public, private) key pair..
-func newKey() (security.PublicKey, *ecdsa.PrivateKey, error) {
- priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
- if err != nil {
- return nil, nil, err
- }
- return security.NewECDSAPublicKey(&priv.PublicKey), priv, nil
-}
-
// NewPrincipal mints a new private key and generates a principal based on
// this key, storing its BlessingRoots and BlessingStore in memory.
func NewPrincipal() (security.Principal, error) {
@@ -32,69 +23,92 @@
return security.CreatePrincipal(security.NewInMemoryECDSASigner(priv), newInMemoryBlessingStore(pub), newInMemoryBlessingRoots())
}
-// NewPersistentPrincipal reads state for a principal (private key, BlessingRoots, BlessingStore)
+// LoadPersistentPrincipal reads state for a principal (private key, BlessingRoots, BlessingStore)
// from the provided directory 'dir' and commits all state changes to the same directory.
-//
-// If the directory does not contain state, then a new private key is minted and all state of
-// the principal is committed to 'dir'. If the directory does not exist, it is created.
-//
-// Returns the security.Principal implementation, a boolean that is set to true iff the directory
-// already contained a principal and any errors in initialization.
-func NewPersistentPrincipal(dir string) (principal security.Principal, existed bool, err error) {
+// If private key file does not exist then an error 'err' is returned such that os.IsNotExist(err) is true.
+// If private key file exists then 'passphrase' must be non-nil if the contents of the file are encrypted,
+// otherwise a MissingPassphraseErr is returned.
+func LoadPersistentPrincipal(dir string, passphrase []byte) (security.Principal, error) {
+ key, err := loadKeyFromDir(dir, passphrase)
+ if err != nil {
+ return nil, err
+ }
+ return newPersistentPrincipalFromKey(key, dir)
+}
+
+// CreatePersistentPrincipal creates a new principal (private key, BlessingRoots, BlessingStore) and commits all state changes to the provided directory.
+// The generated private key is serialized and saved encrypted if the 'passphrase' is non-nil, and unencrypted otherwise.
+// If the directory has any preexisting key, CreatePersistentPrincipal will return an error.
+// The specified directory may not exist, in which case it gets created by this function.
+func CreatePersistentPrincipal(dir string, passphrase []byte) (principal security.Principal, err error) {
if finfo, err := os.Stat(dir); err == nil {
if !finfo.IsDir() {
- return nil, false, fmt.Errorf("%q is not a directory", dir)
+ return nil, fmt.Errorf("%q is not a directory", dir)
}
} else if err := os.MkdirAll(dir, 0700); err != nil {
- return nil, false, fmt.Errorf("failed to create %q: %v", dir, err)
+ return nil, fmt.Errorf("failed to create %q: %v", dir, err)
}
- key, existed, err := initKey(dir)
+ key, err := initKey(dir, nil)
if err != nil {
- return nil, false, fmt.Errorf("could not initialize private key from credentials directory %v: %v", dir, err)
+ return nil, fmt.Errorf("could not initialize private key from credentials directory %v: %v", dir, err)
}
+ p, err := newPersistentPrincipalFromKey(key, dir)
+ return p, err
+}
+
+func loadKeyFromDir(dir string, passphrase []byte) (*ecdsa.PrivateKey, error) {
+ keyFile := path.Join(dir, privateKeyFile)
+ f, err := os.Open(keyFile)
+ if err != nil {
+ return nil, err
+ }
+ defer f.Close()
+ key, err := loadPEMKey(f, passphrase)
+ if err != nil {
+ return nil, err
+ }
+ return key.(*ecdsa.PrivateKey), nil
+}
+
+func newPersistentPrincipalFromKey(key *ecdsa.PrivateKey, dir string) (security.Principal, error) {
signer, err := security.CreatePrincipal(security.NewInMemoryECDSASigner(key), nil, nil)
if err != nil {
- return nil, false, fmt.Errorf("failed to create serialization.Signer: %v", err)
+ return nil, fmt.Errorf("failed to create serialization.Signer: %v", err)
}
roots, err := newPersistingBlessingRoots(dir, signer)
if err != nil {
- return nil, false, fmt.Errorf("failed to load BlessingRoots from %q: %v", dir, err)
+ return nil, fmt.Errorf("failed to load BlessingRoots from %q: %v", dir, err)
}
store, err := newPersistingBlessingStore(dir, signer)
if err != nil {
- return nil, false, fmt.Errorf("failed to load BlessingStore from %q: %v", dir, err)
+ return nil, fmt.Errorf("failed to load BlessingStore from %q: %v", dir, err)
}
- p, err := security.CreatePrincipal(security.NewInMemoryECDSASigner(key), store, roots)
- return p, existed, err
+ return security.CreatePrincipal(security.NewInMemoryECDSASigner(key), store, roots)
}
-func initKey(dir string) (*ecdsa.PrivateKey, bool, error) {
+// newKey generates an ECDSA (public, private) key pair.
+func newKey() (security.PublicKey, *ecdsa.PrivateKey, error) {
+ priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
+ if err != nil {
+ return nil, nil, err
+ }
+ return security.NewECDSAPublicKey(&priv.PublicKey), priv, nil
+}
+
+func initKey(dir string, passphrase []byte) (*ecdsa.PrivateKey, error) {
keyFile := path.Join(dir, privateKeyFile)
- if f, err := os.Open(keyFile); err == nil {
- defer f.Close()
- v, err := LoadPEMKey(f, nil)
- if err != nil {
- return nil, false, fmt.Errorf("failed to load PEM data from %q: %v", keyFile, v)
- }
- key, ok := v.(*ecdsa.PrivateKey)
- if !ok {
- return nil, false, fmt.Errorf("%q contains a %T, not an ECDSA private key", keyFile, v)
- }
- return key, true, nil
- } else if !os.IsNotExist(err) {
- return nil, false, fmt.Errorf("failed to read %q: %v", keyFile, err)
- }
- _, key, err := newKey()
+ // O_EXCL returns an error if file exists.
+ f, err := os.OpenFile(keyFile, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0600)
if err != nil {
- return nil, false, fmt.Errorf("failed to generate a private key: %v", err)
- }
- f, err := os.OpenFile(keyFile, os.O_WRONLY|os.O_CREATE, 0600)
- if err != nil {
- return nil, false, fmt.Errorf("failed to open %q for writing: %v", keyFile, err)
+ return nil, fmt.Errorf("failed to open %q for writing: %v", keyFile, err)
}
defer f.Close()
- if err := SavePEMKey(f, key, nil); err != nil {
- return nil, false, fmt.Errorf("failed to save private key to %q: %v", keyFile, err)
+ _, key, err := newKey()
+ if err != nil {
+ return nil, fmt.Errorf("failed to generate private key: %v", err)
}
- return key, false, nil
+ if err := savePEMKey(f, key, passphrase); err != nil {
+ return nil, fmt.Errorf("failed to save private key to %q: %v", keyFile, err)
+ }
+ return key, nil
}
diff --git a/security/principal_test.go b/security/principal_test.go
index a8ad464..d2d18ce 100644
--- a/security/principal_test.go
+++ b/security/principal_test.go
@@ -1,38 +1,105 @@
package security
import (
+ "crypto/ecdsa"
+ "crypto/elliptic"
+ "crypto/rand"
"io/ioutil"
"os"
+ "path"
"testing"
)
-func TestNewPersistentPrincipal(t *testing.T) {
+func TestLoadPersistentPrincipal(t *testing.T) {
+ // If the directory does not exist want os.IsNotExists.
+ _, err := LoadPersistentPrincipal("/tmp/fake/path/", nil)
+ if !os.IsNotExist(err) {
+ t.Errorf("invalid path should return does not exist error, instead got %v", err)
+ }
+ // If the key file exists and is unencrypted we should succeed.
+ dir := generatePEMFile(nil)
+ if _, err = LoadPersistentPrincipal(dir, nil); err != nil {
+ t.Errorf("unencrypted LoadPersistentPrincipal should have succeeded: %v", err)
+ }
+ os.RemoveAll(dir)
+
+ // If the private key file exists and is encrypted we should succeed with correct passphrase.
+ passphrase := []byte("passphrase")
+ incorrect_passphrase := []byte("incorrect_passphrase")
+ dir = generatePEMFile(passphrase)
+ if _, err = LoadPersistentPrincipal(dir, passphrase); err != nil {
+ t.Errorf("encrypted LoadPersistentPrincipal should have succeeded: %v", err)
+ }
+ // and fail with an incorrect passphrase.
+ if _, err = LoadPersistentPrincipal(dir, incorrect_passphrase); err == nil {
+ t.Errorf("encrypted LoadPersistentPrincipal with incorrect passphrase should fail")
+ }
+ // and return MissingPassphraseError if the passphrase is nil.
+ if _, err = LoadPersistentPrincipal(dir, nil); err != MissingPassphraseErr {
+ t.Errorf("encrypted LoadPersistentPrincipal with nil passphrase should return MissingPassphraseErr: %v", err)
+ }
+ os.RemoveAll(dir)
+}
+
+func TestCreatePersistentPrincipal(t *testing.T) {
+ tests := []struct {
+ Message, Passphrase []byte
+ }{
+ {[]byte("unencrypted"), nil},
+ {[]byte("encrypted"), []byte("passphrase")},
+ }
+ for _, test := range tests {
+ testCreatePersistentPrincipal(t, test.Message, test.Passphrase)
+ }
+}
+
+func testCreatePersistentPrincipal(t *testing.T, message, passphrase []byte) {
// Persistence of the BlessingRoots and BlessingStore objects is
// tested in other files. Here just test the persistence of the key.
- dir, err := ioutil.TempDir("", "TestNewPersistentPrincipal")
+ dir, err := ioutil.TempDir("", "TestCreatePersistentPrincipal")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(dir)
- p, existed, err := NewPersistentPrincipal(dir)
+ p, err := CreatePersistentPrincipal(dir, passphrase)
if err != nil {
t.Fatal(err)
}
- if existed {
- t.Fatalf("%q already has data", existed)
- }
- message := []byte("this is a test message")
sig, err := p.Sign(message)
if err != nil {
t.Fatal(err)
}
- p2, _, err := NewPersistentPrincipal(dir)
+ p2, err := CreatePersistentPrincipal(dir, passphrase)
+ if err == nil {
+ t.Errorf("p2 CreatePersistentPrincipal should have failed")
+ }
+ p2, err = LoadPersistentPrincipal(dir, passphrase)
if err != nil {
t.Fatal(err)
}
- if !sig.Verify(p2.PublicKey(), message) {
- t.Errorf("p.PublicKey=%v, p2.PublicKey=%v", p.PublicKey(), p2.PublicKey())
+ if !sig.Verify(p.PublicKey(), message) {
+ t.Errorf("%s failed: p.PublicKey=%v, p2.PublicKey=%v", message, p.PublicKey(), p2.PublicKey())
}
}
+
+func generatePEMFile(passphrase []byte) (dir string) {
+ dir, err := ioutil.TempDir("", "TestLoadPersistentPrincipal")
+ if err != nil {
+ panic(err)
+ }
+ key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
+ if err != nil {
+ panic(err)
+ }
+ f, err := os.Create(path.Join(dir, privateKeyFile))
+ if err != nil {
+ panic(err)
+ }
+ defer f.Close()
+ if err = savePEMKey(f, key, passphrase); err != nil {
+ panic(err)
+ }
+ return dir
+}
diff --git a/security/util.go b/security/util.go
index 0ae070f..175d49f 100644
--- a/security/util.go
+++ b/security/util.go
@@ -28,42 +28,50 @@
return acl
}
-// LoadPEMKey loads a key from 'r', assuming that it was saved using SavePEMKey
-// and the specified passphrase 'passphrase'.
-// If passphrase is nil, the key will still be encrypted with a random salt, but
-// this will offer no protection as the salt is stored with the PEMKey.
-func LoadPEMKey(r io.Reader, passphrase []byte) (interface{}, error) {
- pemKeyBytes, err := ioutil.ReadAll(r)
+var MissingPassphraseErr = errors.New("passphrase required for decrypting private key")
+
+// loadPEMKey loads a key from 'r'. passphrase should be non-nil if the key held in 'r' is
+// encrypted, otherwise a MissingPassphraseErr will be returned.
+// If the key held in 'r' is unencrypted, 'passphrase' will be ignored.
+func loadPEMKey(r io.Reader, passphrase []byte) (interface{}, error) {
+ pemBlockBytes, err := ioutil.ReadAll(r)
if err != nil {
return nil, err
}
-
- pemKey, _ := pem.Decode(pemKeyBytes)
- if pemKey == nil {
+ pemBlock, _ := pem.Decode(pemBlockBytes)
+ if pemBlock == nil {
return nil, errors.New("no PEM key block read")
}
-
- data, err := x509.DecryptPEMBlock(pemKey, passphrase)
- if err != nil {
- return nil, err
+ var data []byte
+ if x509.IsEncryptedPEMBlock(pemBlock) {
+ if passphrase == nil {
+ return nil, MissingPassphraseErr
+ }
+ data, err = x509.DecryptPEMBlock(pemBlock, passphrase)
+ if err != nil {
+ return nil, err
+ }
+ } else {
+ data = pemBlock.Bytes
}
- switch pemKey.Type {
+ switch pemBlock.Type {
case ecPrivateKeyPEMType:
return x509.ParseECPrivateKey(data)
}
- return nil, fmt.Errorf("PEM key block has an unrecognized type: %v", pemKey.Type)
+ return nil, fmt.Errorf("PEM key block has an unrecognized type: %v", pemBlock.Type)
}
-// SavePEMKey marshals 'key', encrypts it using 'passphrase', and saves the bytes to 'w' in PEM format.
+// savePEMKey marshals 'key', encrypts it using 'passphrase', and saves the bytes to 'w' in PEM format.
+// If passphrase is nil, the key will not be encrypted.
//
// For example, if key is an ECDSA private key, it will be marshaled
// in ASN.1, DER format, encrypted, and then written in a PEM block.
-func SavePEMKey(w io.Writer, key interface{}, passphrase []byte) error {
+func savePEMKey(w io.Writer, key interface{}, passphrase []byte) error {
var data []byte
+ var err error
switch k := key.(type) {
case *ecdsa.PrivateKey:
- var err error
if data, err = x509.MarshalECPrivateKey(k); err != nil {
return err
}
@@ -71,9 +79,17 @@
return fmt.Errorf("key of type %T cannot be saved", k)
}
- pemKey, err := x509.EncryptPEMBlock(rand.Reader, ecPrivateKeyPEMType, data, passphrase, x509.PEMCipherAES256)
- if err != nil {
- return fmt.Errorf("failed to encrypt pem block: %v", err)
+ var pemKey *pem.Block
+ if passphrase != nil {
+ pemKey, err = x509.EncryptPEMBlock(rand.Reader, ecPrivateKeyPEMType, data, passphrase, x509.PEMCipherAES256)
+ if err != nil {
+ return fmt.Errorf("failed to encrypt pem block: %v", err)
+ }
+ } else {
+ pemKey = &pem.Block{
+ Type: ecPrivateKeyPEMType,
+ Bytes: data,
+ }
}
return pem.Encode(w, pemKey)
diff --git a/security/util_test.go b/security/util_test.go
index bd55641..f4e2aec 100644
--- a/security/util_test.go
+++ b/security/util_test.go
@@ -5,6 +5,7 @@
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
+ "crypto/x509"
"fmt"
"reflect"
"testing"
@@ -20,47 +21,50 @@
}
var buf bytes.Buffer
- if err := SavePEMKey(&buf, key, nil); err != nil {
+ if err := savePEMKey(&buf, key, nil); err != nil {
t.Fatalf("Failed to save ECDSA private key: %v", err)
}
- loadedKey, err := LoadPEMKey(&buf, nil)
- if err != nil {
- t.Fatalf("Failed to load ECDSA private key: %v", err)
- }
+ loadedKey, err := loadPEMKey(&buf, nil)
if !reflect.DeepEqual(loadedKey, key) {
t.Fatalf("Got key %v, but want %v", loadedKey, key)
}
}
-func TestLoadSavePEMKeyWithPassword(t *testing.T) {
+func TestLoadSavePEMKeyWithPassphrase(t *testing.T) {
pass := []byte("openSesame")
- incorrect_pass := []byte("wrongPassword")
+ incorrect_pass := []byte("wrongPassphrase")
key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
t.Fatalf("Failed ecdsa.GenerateKey: %v", err)
}
var buf bytes.Buffer
- // Test incorrect password.
- if err := SavePEMKey(&buf, key, pass); err != nil {
+
+ // Test incorrect passphrase.
+ if err := savePEMKey(&buf, key, pass); err != nil {
t.Fatalf("Failed to save ECDSA private key: %v", err)
}
- loadedKey, err := LoadPEMKey(&buf, incorrect_pass)
- if err == nil {
- t.Errorf("loaded ECDSA private key with incorrect password")
+ loadedKey, err := loadPEMKey(&buf, incorrect_pass)
+ if loadedKey != nil && err != x509.IncorrectPasswordError {
+ t.Errorf("expected (nil, x509.IncorrectPasswordError) received (%v,%v)", loadedKey, err)
}
// Test correct password.
- if err := SavePEMKey(&buf, key, pass); err != nil {
+ if err := savePEMKey(&buf, key, pass); err != nil {
t.Fatalf("Failed to save ECDSA private key: %v", err)
}
- loadedKey, err = LoadPEMKey(&buf, pass)
- if err != nil {
- t.Fatalf("Failed to load ECDSA private key: %v", err)
- }
+ loadedKey, err = loadPEMKey(&buf, pass)
if !reflect.DeepEqual(loadedKey, key) {
t.Fatalf("Got key %v, but want %v", loadedKey, key)
}
+
+ // Test nil passphrase.
+ if err := savePEMKey(&buf, key, pass); err != nil {
+ t.Fatalf("Failed to save ECDSA private key: %v", err)
+ }
+ if loadedKey, err = loadPEMKey(&buf, nil); loadedKey != nil || err != MissingPassphraseErr {
+ t.Fatalf("expected(nil, MissingPassphraseError), instead got (%v, %v)", loadedKey, err)
+ }
}
func TestLoadSaveIdentity(t *testing.T) {
diff --git a/tools/principal/main.go b/tools/principal/main.go
index 62a05ac..0a80da2 100644
--- a/tools/principal/main.go
+++ b/tools/principal/main.go
@@ -189,9 +189,12 @@
var key security.PublicKey
tobless, extension := args[0], args[1]
if finfo, err := os.Stat(tobless); err == nil && finfo.IsDir() {
- other, _, err := vsecurity.NewPersistentPrincipal(tobless)
+ // TODO(suharshs,ashankar,ataly): How should we make an ecrypted pk... or is that up to the agent?
+ other, err := vsecurity.LoadPersistentPrincipal(tobless, nil)
if err != nil {
- return fmt.Errorf("failed to read principal in directory %q: %v", tobless, err)
+ if other, err = vsecurity.CreatePersistentPrincipal(tobless, nil); err != nil {
+ return fmt.Errorf("failed to read principal in directory %q: %v", tobless, err)
+ }
}
key = other.PublicKey()
} else if other, err := decodeBlessings(tobless); err != nil {
@@ -363,9 +366,10 @@
return fmt.Errorf("requires exactly two arguments: <directory> and <blessing>, provided %d", len(args))
}
dir, name := args[0], args[1]
- p, existed, err := vsecurity.NewPersistentPrincipal(dir)
- if existed {
- return fmt.Errorf("principal already exists in %q", dir)
+ // TODO(suharshs,ashankar,ataly): How should we make an ecrypted pk... or is that up to the agent?
+ p, err := vsecurity.CreatePersistentPrincipal(dir, nil)
+ if err != nil {
+ return err
}
blessings, err := p.BlessSelf(name)
if err != nil {