blob: f684b9c375aaf628d354cb9a590c6e4c6c545f5a [file] [log] [blame]
// Copyright 2016 The Vanadium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// TODO(caprita): Move to internal.
// Package server contains utilities for serving a principal using a
// socket-based IPC system.
package server
import (
"fmt"
"os"
"path/filepath"
"time"
"v.io/v23/security"
"v.io/v23/verror"
vsecurity "v.io/x/ref/lib/security"
"v.io/x/ref/lib/security/passphrase"
"v.io/x/ref/services/agent/internal/ipc"
"v.io/x/ref/services/agent/internal/server"
)
const (
pkgPath = "v.io/x/ref/services/agent/server"
agentSocketName = "agent.sock"
)
var (
errCantReadPassphrase = verror.Register(pkgPath+".errCantReadPassphrase", verror.NoRetry, "{1:}{2:} failed to read passphrase{:_}")
errNeedPassphrase = verror.Register(pkgPath+".errNeedPassphrase", verror.NoRetry, "{1:}{2:} Passphrase required for decrypting principal{:_}")
)
// LoadPrincipal returns the principal persisted in the given credentials
// directory. If the private key is encrypted, it prompts for a decryption
// passphrase. If the principal doesn't exist and create is true, it creates
// the principal.
func LoadPrincipal(credentials string, create bool) (security.Principal, error) {
switch p, err := vsecurity.LoadPersistentPrincipal(credentials, nil); {
case err == nil:
return p, nil
case os.IsNotExist(err):
if !create {
return nil, err
}
return handleDoesNotExist(credentials)
case verror.ErrorID(err) == vsecurity.ErrPassphraseRequired.ID:
return handlePassphrase(credentials)
default:
return nil, err
}
}
// IPCState represents the IPC system serving the principal.
type IPCState interface {
// Close shuts the IPC system down.
Close()
// IdleStartTime returns the time when the IPC system became idle (no
// connections). Returns the zero time instant if connections exits.
IdleStartTime() time.Time
// NumConnections returns the number of current connections.
NumConnections() int
}
type ipcState struct {
*ipc.IPC
}
// NumConnections implements IPCState.NumConnections.
func (s ipcState) NumConnections() int {
return len(s.IPC.Connections())
}
// Serve serves the given principal using the given socket file, and returns an
// IPCState for the service.
func Serve(p security.Principal, socketPath string) (IPCState, error) {
var err error
if socketPath, err = filepath.Abs(socketPath); err != nil {
return nil, fmt.Errorf("Abs failed: %v", err)
}
socketPath = filepath.Clean(socketPath)
// Start running our server.
i := ipc.NewIPC()
if err := server.ServeAgent(i, p); err != nil {
i.Close()
return nil, fmt.Errorf("ServeAgent failed: %v", err)
}
if err = os.Remove(socketPath); err != nil && !os.IsNotExist(err) {
i.Close()
return nil, err
}
if err := i.Listen(socketPath); err != nil {
i.Close()
return nil, fmt.Errorf("Listen failed: %v", err)
}
return ipcState{i}, nil
}
func handleDoesNotExist(dir string) (security.Principal, error) {
fmt.Println("Private key file does not exist. Creating new private key...")
pass, err := passphrase.Get("Enter passphrase (entering nothing will store unencrypted): ")
if err != nil {
return nil, verror.New(errCantReadPassphrase, nil, err)
}
defer func() {
// Zero passhphrase out so it doesn't stay in memory.
for i := range pass {
pass[i] = 0
}
}()
p, err := vsecurity.CreatePersistentPrincipal(dir, pass)
if err != nil {
return nil, err
}
return p, nil
}
func handlePassphrase(dir string) (security.Principal, error) {
pass, err := passphrase.Get(fmt.Sprintf("Passphrase required to decrypt encrypted private key file for credentials in %v.\nEnter passphrase: ", dir))
if err != nil {
return nil, verror.New(errCantReadPassphrase, nil, err)
}
p, err := vsecurity.LoadPersistentPrincipal(dir, pass)
// Zero passhphrase out so it doesn't stay in memory.
for i := range pass {
pass[i] = 0
}
return p, err
}