| package server |
| |
| import ( |
| "crypto/ecdsa" |
| "crypto/elliptic" |
| "crypto/rand" |
| "crypto/sha512" |
| "crypto/x509" |
| "encoding/base64" |
| "fmt" |
| "io" |
| "net" |
| "os" |
| "path/filepath" |
| "strconv" |
| "sync" |
| |
| "v.io/core/veyron/lib/unixfd" |
| vsecurity "v.io/core/veyron/security" |
| "v.io/core/veyron2" |
| "v.io/core/veyron2/context" |
| "v.io/core/veyron2/ipc" |
| "v.io/core/veyron2/options" |
| "v.io/core/veyron2/security" |
| "v.io/core/veyron2/vdl/vdlutil" |
| verror "v.io/core/veyron2/verror2" |
| "v.io/core/veyron2/vlog" |
| ) |
| |
| const PrincipalHandleByteSize = sha512.Size |
| |
| const pkgPath = "v.io/core/veyron/security/agent/server" |
| |
| // Errors |
| var ( |
| errStoragePathRequired = verror.Register(pkgPath+".errStoragePathRequired", |
| verror.NoRetry, "{1:}{2:} RunKeyManager: storage path is required") |
| errNotMultiKeyMode = verror.Register(pkgPath+".errNotMultiKeyMode", |
| verror.NoRetry, "{1:}{2:} Not running in multi-key mode") |
| ) |
| |
| type keyHandle [PrincipalHandleByteSize]byte |
| |
| type agentd struct { |
| principal security.Principal |
| } |
| |
| type keymgr struct { |
| path string |
| principals map[keyHandle]security.Principal // GUARDED_BY(Mutex) |
| passphrase []byte |
| ctx *context.T |
| mu sync.Mutex |
| } |
| |
| // RunAnonymousAgent starts the agent server listening on an |
| // anonymous unix domain socket. It will respond to requests |
| // using 'principal'. |
| // The returned 'client' is typically passed via cmd.ExtraFiles to a child process. |
| func RunAnonymousAgent(ctx *context.T, principal security.Principal) (client *os.File, err error) { |
| local, remote, err := unixfd.Socketpair() |
| if err != nil { |
| return nil, err |
| } |
| if err = startAgent(ctx, local, principal); err != nil { |
| return nil, err |
| } |
| return remote, err |
| } |
| |
| // RunKeyManager starts the key manager server listening on an |
| // anonymous unix domain socket. It will persist principals in 'path' using 'passphrase'. |
| // Typically only used by the device manager. |
| // The returned 'client' is typically passed via cmd.ExtraFiles to a child process. |
| func RunKeyManager(ctx *context.T, path string, passphrase []byte) (client *os.File, err error) { |
| if path == "" { |
| return nil, verror.Make(errStoragePathRequired, nil) |
| } |
| |
| mgr := &keymgr{path: path, passphrase: passphrase, principals: make(map[keyHandle]security.Principal), ctx: ctx} |
| |
| if err := os.MkdirAll(filepath.Join(mgr.path, "keys"), 0700); err != nil { |
| return nil, err |
| } |
| if err := os.MkdirAll(filepath.Join(mgr.path, "creds"), 0700); err != nil { |
| return nil, err |
| } |
| |
| local, client, err := unixfd.Socketpair() |
| if err != nil { |
| return nil, err |
| } |
| |
| go mgr.readDMConns(local) |
| |
| return client, nil |
| } |
| |
| func (a keymgr) readDMConns(conn *net.UnixConn) { |
| defer conn.Close() |
| var buf keyHandle |
| for { |
| addr, n, ack, err := unixfd.ReadConnection(conn, buf[:]) |
| if err == io.EOF { |
| return |
| } else if err != nil { |
| vlog.Infof("Error accepting connection: %v", err) |
| continue |
| } |
| ack() |
| var principal security.Principal |
| if n == len(buf) { |
| principal = a.readKey(buf) |
| } else if n == 1 { |
| var handle []byte |
| if handle, principal, err = a.newKey(buf[0] == 1); err != nil { |
| vlog.Infof("Error creating key: %v", err) |
| unixfd.CloseUnixAddr(addr) |
| continue |
| } |
| if _, err = conn.Write(handle); err != nil { |
| vlog.Infof("Error sending key handle: %v", err) |
| unixfd.CloseUnixAddr(addr) |
| continue |
| } |
| } else { |
| vlog.Infof("invalid key: %d bytes, expected %d or 1", n, len(buf)) |
| unixfd.CloseUnixAddr(addr) |
| continue |
| } |
| conn := dial(addr) |
| if principal != nil && conn != nil { |
| if err := startAgent(a.ctx, conn, principal); err != nil { |
| vlog.Infof("error starting agent: %v", err) |
| } |
| } |
| } |
| } |
| |
| func (a *keymgr) readKey(handle keyHandle) security.Principal { |
| a.mu.Lock() |
| cachedKey, ok := a.principals[handle] |
| a.mu.Unlock() |
| if ok { |
| return cachedKey |
| } |
| filename := base64.URLEncoding.EncodeToString(handle[:]) |
| in, err := os.Open(filepath.Join(a.path, "keys", filename)) |
| if err != nil { |
| vlog.Errorf("unable to open key file: %v", err) |
| return nil |
| } |
| defer in.Close() |
| key, err := vsecurity.LoadPEMKey(in, a.passphrase) |
| if err != nil { |
| vlog.Errorf("unable to load key: %v", err) |
| return nil |
| } |
| state, err := vsecurity.NewPrincipalStateSerializer(filepath.Join(a.path, "creds", filename)) |
| if err != nil { |
| vlog.Errorf("unable to create persisted state serializer: %v", err) |
| return nil |
| } |
| principal, err := vsecurity.NewPrincipalFromSigner(security.NewInMemoryECDSASigner(key.(*ecdsa.PrivateKey)), state) |
| if err != nil { |
| vlog.Errorf("unable to load principal: %v", err) |
| return nil |
| } |
| return principal |
| } |
| |
| func dial(addr net.Addr) *net.UnixConn { |
| fd, err := strconv.ParseInt(addr.String(), 10, 32) |
| if err != nil { |
| vlog.Errorf("Invalid address %v", addr) |
| return nil |
| } |
| file := os.NewFile(uintptr(fd), "client") |
| defer file.Close() |
| conn, err := net.FileConn(file) |
| if err != nil { |
| vlog.Infof("unable to create conn: %v", err) |
| } |
| return conn.(*net.UnixConn) |
| } |
| |
| func startAgent(ctx *context.T, conn *net.UnixConn, principal security.Principal) error { |
| agent := &agentd{principal: principal} |
| serverAgent := AgentServer(agent) |
| go func() { |
| buf := make([]byte, 1) |
| for { |
| clientAddr, _, ack, err := unixfd.ReadConnection(conn, buf) |
| if err == io.EOF { |
| return |
| } |
| if clientAddr != nil { |
| // VCSecurityNone is safe since we're using anonymous unix sockets. |
| // Only our child process can possibly communicate on the socket. |
| // |
| // Also, VCSecurityNone implies that s (ipc.Server) created below does not |
| // authenticate to clients, so runtime.Principal is irrelevant for the agent. |
| // TODO(ribrdb): Shutdown these servers when the connection is closed. |
| s, err := veyron2.NewServer(ctx, options.VCSecurityNone) |
| if err != nil { |
| vlog.Infof("Error creating server: %v", err) |
| ack() |
| continue |
| } |
| a := []struct{ Protocol, Address string }{ |
| {clientAddr.Network(), clientAddr.String()}, |
| } |
| spec := ipc.ListenSpec{Addrs: ipc.ListenAddrs(a)} |
| if _, err = s.Listen(spec); err == nil { |
| err = s.Serve("", serverAgent, nil) |
| } |
| ack() |
| } |
| if err != nil { |
| vlog.Infof("Error accepting connection: %v", err) |
| } |
| } |
| }() |
| return nil |
| } |
| |
| func (a agentd) Bless(_ ipc.ServerContext, key []byte, with security.WireBlessings, extension string, caveat security.Caveat, additionalCaveats []security.Caveat) (security.WireBlessings, error) { |
| pkey, err := security.UnmarshalPublicKey(key) |
| if err != nil { |
| return security.WireBlessings{}, err |
| } |
| withBlessings, err := security.NewBlessings(with) |
| if err != nil { |
| return security.WireBlessings{}, err |
| } |
| blessings, err := a.principal.Bless(pkey, withBlessings, extension, caveat, additionalCaveats...) |
| if err != nil { |
| return security.WireBlessings{}, err |
| } |
| return security.MarshalBlessings(blessings), nil |
| } |
| |
| func (a agentd) BlessSelf(_ ipc.ServerContext, name string, caveats []security.Caveat) (security.WireBlessings, error) { |
| blessings, err := a.principal.BlessSelf(name, caveats...) |
| if err != nil { |
| return security.WireBlessings{}, err |
| } |
| return security.MarshalBlessings(blessings), nil |
| } |
| |
| func (a agentd) Sign(_ ipc.ServerContext, message []byte) (security.Signature, error) { |
| return a.principal.Sign(message) |
| } |
| |
| func (a agentd) MintDischarge(_ ipc.ServerContext, tp vdlutil.Any, caveat security.Caveat, additionalCaveats []security.Caveat) (vdlutil.Any, error) { |
| tpCaveat, ok := tp.(security.ThirdPartyCaveat) |
| if !ok { |
| return nil, fmt.Errorf("provided caveat of type %T does not implement security.ThirdPartyCaveat", tp) |
| } |
| return a.principal.MintDischarge(tpCaveat, caveat, additionalCaveats...) |
| } |
| |
| func (a keymgr) newKey(in_memory bool) (id []byte, p security.Principal, err error) { |
| if a.path == "" { |
| return nil, nil, verror.Make(errNotMultiKeyMode, nil) |
| } |
| key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) |
| keyHandle, err := keyid(key) |
| if err != nil { |
| return nil, nil, err |
| } |
| signer := security.NewInMemoryECDSASigner(key) |
| if in_memory { |
| p, err = vsecurity.NewPrincipalFromSigner(signer, nil) |
| if err != nil { |
| return nil, nil, err |
| } |
| a.principals[keyHandle] = p |
| } else { |
| filename := base64.URLEncoding.EncodeToString(keyHandle[:]) |
| out, err := os.OpenFile(filepath.Join(a.path, "keys", filename), os.O_WRONLY|os.O_CREATE, 0600) |
| if err != nil { |
| return nil, nil, err |
| } |
| defer out.Close() |
| err = vsecurity.SavePEMKey(out, key, a.passphrase) |
| if err != nil { |
| return nil, nil, err |
| } |
| state, err := vsecurity.NewPrincipalStateSerializer(filepath.Join(a.path, "creds", filename)) |
| if err != nil { |
| return nil, nil, err |
| } |
| p, err = vsecurity.NewPrincipalFromSigner(signer, state) |
| if err != nil { |
| return nil, nil, err |
| } |
| } |
| return keyHandle[:], p, nil |
| } |
| |
| func keyid(key *ecdsa.PrivateKey) (handle keyHandle, err error) { |
| slice, err := x509.MarshalPKIXPublicKey(&key.PublicKey) |
| if err != nil { |
| return |
| } |
| return sha512.Sum512(slice), nil |
| } |
| |
| func (a agentd) PublicKey(_ ipc.ServerContext) ([]byte, error) { |
| return a.principal.PublicKey().MarshalBinary() |
| } |
| |
| func (a agentd) BlessingsByName(_ ipc.ServerContext, name security.BlessingPattern) ([]security.WireBlessings, error) { |
| blessings := a.principal.BlessingsByName(name) |
| ret := make([]security.WireBlessings, len(blessings)) |
| for i, b := range blessings { |
| ret[i] = security.MarshalBlessings(b) |
| } |
| return ret, nil |
| } |
| |
| func (a agentd) BlessingsInfo(_ ipc.ServerContext, blessings security.WireBlessings) (map[string][]security.Caveat, error) { |
| b, err := security.NewBlessings(blessings) |
| if err != nil { |
| return nil, err |
| } |
| return a.principal.BlessingsInfo(b), nil |
| } |
| |
| func (a agentd) AddToRoots(_ ipc.ServerContext, wireBlessings security.WireBlessings) error { |
| blessings, err := security.NewBlessings(wireBlessings) |
| if err != nil { |
| return err |
| } |
| return a.principal.AddToRoots(blessings) |
| } |
| |
| func (a agentd) BlessingStoreSet(_ ipc.ServerContext, wireBlessings security.WireBlessings, forPeers security.BlessingPattern) (security.WireBlessings, error) { |
| blessings, err := security.NewBlessings(wireBlessings) |
| if err != nil { |
| return security.WireBlessings{}, err |
| } |
| resultBlessings, err := a.principal.BlessingStore().Set(blessings, forPeers) |
| if err != nil { |
| return security.WireBlessings{}, err |
| } |
| return security.MarshalBlessings(resultBlessings), nil |
| } |
| |
| func (a agentd) BlessingStoreForPeer(_ ipc.ServerContext, peerBlessings []string) (security.WireBlessings, error) { |
| return security.MarshalBlessings(a.principal.BlessingStore().ForPeer(peerBlessings...)), nil |
| } |
| |
| func (a agentd) BlessingStoreSetDefault(_ ipc.ServerContext, wireBlessings security.WireBlessings) error { |
| blessings, err := security.NewBlessings(wireBlessings) |
| if err != nil { |
| return err |
| } |
| return a.principal.BlessingStore().SetDefault(blessings) |
| } |
| |
| func (a agentd) BlessingStorePeerBlessings(_ ipc.ServerContext) (map[security.BlessingPattern]security.WireBlessings, error) { |
| bMap := a.principal.BlessingStore().PeerBlessings() |
| wbMap := make(map[security.BlessingPattern]security.WireBlessings, len(bMap)) |
| for p, b := range bMap { |
| wbMap[p] = security.MarshalBlessings(b) |
| } |
| return wbMap, nil |
| } |
| |
| func (a agentd) BlessingStoreDebugString(_ ipc.ServerContext) (string, error) { |
| return a.principal.BlessingStore().DebugString(), nil |
| } |
| |
| func (a agentd) BlessingStoreDefault(_ ipc.ServerContext) (security.WireBlessings, error) { |
| return security.MarshalBlessings(a.principal.BlessingStore().Default()), nil |
| } |
| |
| func (a agentd) BlessingRootsAdd(_ ipc.ServerContext, root []byte, pattern security.BlessingPattern) error { |
| pkey, err := security.UnmarshalPublicKey(root) |
| if err != nil { |
| return err |
| } |
| return a.principal.Roots().Add(pkey, pattern) |
| } |
| |
| func (a agentd) BlessingRootsRecognized(_ ipc.ServerContext, root []byte, blessing string) error { |
| pkey, err := security.UnmarshalPublicKey(root) |
| if err != nil { |
| return err |
| } |
| return a.principal.Roots().Recognized(pkey, blessing) |
| } |
| |
| func (a agentd) BlessingRootsDebugString(_ ipc.ServerContext) (string, error) { |
| return a.principal.Roots().DebugString(), nil |
| } |