blob: c18bb1b73959c1d2cfbfe6883b85ad9e146ffb41 [file] [log] [blame]
// Copyright 2015 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.
// The following enables go generate to generate the doc.go file.
//go:generate go run $V23_ROOT/release/go/src/v.io/x/lib/cmdline/testdata/gendoc.go .
package main
import (
"bytes"
"crypto/rand"
"crypto/subtle"
"encoding/base64"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"os"
"os/user"
"strings"
"time"
"v.io/v23"
"v.io/v23/context"
"v.io/v23/options"
"v.io/v23/rpc"
"v.io/v23/security"
"v.io/v23/vom"
"v.io/x/lib/cmdline"
"v.io/x/ref"
vsecurity "v.io/x/ref/lib/security"
"v.io/x/ref/lib/v23cmd"
_ "v.io/x/ref/runtime/factories/static"
)
var (
// Flags for the "blessself" command
flagBlessSelfCaveats caveatsFlag
flagBlessSelfFor time.Duration
// Flags for the "bless" command
flagBlessCaveats caveatsFlag
flagBlessFor time.Duration
flagBlessRequireCaveats bool
flagBlessWith string
flagBlessRemoteKey string
flagBlessRemoteToken string
// Flags for the "dump" command
flagDumpShort bool
// Flags for the "fork" command
flagForkCaveats caveatsFlag
flagForkFor time.Duration
flagForkRequireCaveats bool
flagForkWith string
// Flags for the "seekblessings" command
flagSeekBlessingsFrom string
flagSeekBlessingsSetDefault bool
flagSeekBlessingsForPeer string
flagSeekBlessingsBrowser bool
// Flags common to many commands
flagAddToRoots bool
flagCreateOverwrite bool
flagRemoteArgFile string
// Flags for the "recvblessings" command
flagRecvBlessingsSetDefault bool
flagRecvBlessingsForPeer string
// Flags for the commands that get blessings
flagBlessingsNames bool
flagBlessingsRootKey string
flagBlessingsCaveats string
// Flags for the get publickey command.
flagGetPublicKeyPretty bool
errNoCaveats = fmt.Errorf("no caveats provided: it is generally dangerous to bless another principal without any caveats as that gives them almost unrestricted access to the blesser's credentials. If you really want to do this, set --require-caveats=false")
cmdDump = &cmdline.Command{
Name: "dump",
Short: "Dump out information about the principal",
Long: `
Prints out information about the principal specified by the environment
that this tool is running in.
`,
Runner: v23cmd.RunnerFunc(func(ctx *context.T, env *cmdline.Env, args []string) error {
p := v23.GetPrincipal(ctx)
if flagDumpShort {
fmt.Printf("%s\n", printAnnotatedBlessingsNames(p.BlessingStore().Default()))
return nil
}
fmt.Printf("Public key : %v\n", p.PublicKey())
// NOTE(caprita): We print the default blessings name
// twice (it's also printed as part of the blessing
// store below) -- the reason we print it here is to
// expose whether the blessings are expired. Ideally,
// the blessings store would print the expiry
// information about each blessing in the store, but
// that would require deeper changes beyond the
// principal tool.
fmt.Printf("Default Blessings : %s\n", printAnnotatedBlessingsNames(p.BlessingStore().Default()))
fmt.Println("---------------- BlessingStore ----------------")
fmt.Printf("%v", p.BlessingStore().DebugString())
fmt.Println("---------------- BlessingRoots ----------------")
fmt.Printf("%v", p.Roots().DebugString())
return nil
}),
}
cmdDumpBlessings = &cmdline.Command{
Name: "dumpblessings",
Short: "Dump out information about the provided blessings",
Long: `
Prints out information about the blessings (typically obtained from this tool)
encoded in the provided file.
`,
ArgsName: "<file>",
ArgsLong: `
<file> is the path to a file containing blessings typically obtained from
this tool. - is used for STDIN.
`,
Runner: cmdline.RunnerFunc(func(env *cmdline.Env, args []string) error {
if len(args) != 1 {
return fmt.Errorf("requires exactly one argument, <file>, provided %d", len(args))
}
blessings, err := decodeBlessings(args[0])
if err != nil {
return fmt.Errorf("failed to decode provided blessings: %v", err)
}
wire, err := blessings2wire(blessings)
if err != nil {
return fmt.Errorf("failed to decode certificate chains: %v", err)
}
fmt.Printf("Blessings : %s\n", printAnnotatedBlessingsNames(blessings))
fmt.Printf("PublicKey : %v\n", blessings.PublicKey())
fmt.Printf("Certificate chains : %d\n", len(wire.CertificateChains))
for idx, chain := range wire.CertificateChains {
fmt.Printf("Chain #%d (%d certificates). Root certificate public key: %v\n", idx, len(chain), rootkey(chain))
for certidx, cert := range chain {
fmt.Printf(" Certificate #%d: %v with ", certidx, cert.Extension)
switch n := len(cert.Caveats); n {
case 1:
fmt.Printf("1 caveat")
default:
fmt.Printf("%d caveats", n)
}
fmt.Println("")
for cavidx, cav := range cert.Caveats {
fmt.Printf(" (%d) %v\n", cavidx, &cav)
}
}
}
return nil
}),
}
cmdBlessSelf = &cmdline.Command{
Name: "blessself",
Short: "Generate a self-signed blessing",
Long: `
Returns a blessing with name <name> and self-signed by the principal specified
by the environment that this tool is running in. Optionally, the blessing can
be restricted with an expiry caveat specified using the --for flag. Additional
caveats can be added with the --caveat flag.
`,
ArgsName: "[<name>]",
ArgsLong: `
<name> is the name used to create the self-signed blessing. If not
specified, a name will be generated based on the hostname of the
machine and the name of the user running this command.
`,
Runner: v23cmd.RunnerFunc(func(ctx *context.T, env *cmdline.Env, args []string) error {
var name string
switch len(args) {
case 0:
name = defaultBlessingName()
case 1:
name = args[0]
default:
return fmt.Errorf("requires at most one argument, provided %d", len(args))
}
caveats, err := caveatsFromFlags(flagBlessSelfFor, &flagBlessSelfCaveats)
if err != nil {
return err
}
principal := v23.GetPrincipal(ctx)
blessing, err := principal.BlessSelf(name, caveats...)
if err != nil {
return fmt.Errorf("failed to create self-signed blessing for name %q: %v", name, err)
}
return dumpBlessings(blessing)
}),
}
cmdBless = &cmdline.Command{
Name: "bless",
Short: "Bless another principal",
Long: `
Bless another principal.
The blesser is obtained from the runtime this tool is using. The blessing that
will be extended is the default one from the blesser's store, or specified by
the --with flag. Expiration on the blessing are controlled via the --for flag.
Additional caveats are controlled with the --caveat flag.
For example, let's say a principal "alice" wants to bless another principal "bob"
as "alice/friend", the invocation would be:
V23_CREDENTIALS=<path to alice> principal bless <path to bob> friend
and this will dump the blessing to STDOUT.
With the --remote-key and --remote-token flags, this command can be used to
bless a principal on a remote machine as well. In this case, the blessing is
not dumped to STDOUT but sent to the remote end. Use 'principal help
recvblessings' for more details on that.
When --remote-arg-file is specified, only the blessing extension is required, as all other
arguments will be extracted from the specified file.
`,
ArgsName: "[<principal to bless>] [<extension>]",
ArgsLong: `
<principal to bless> represents the principal to be blessed (i.e., whose public
key will be provided with a name). This can be either:
(a) The directory containing credentials for that principal,
OR
(b) The filename (- for STDIN) containing any other blessings of that
principal,
OR
(c) The object name produced by the 'recvblessings' command of this tool
running on behalf of another principal (if the --remote-key and
--remote-token flags are specified).
OR
(d) None (if the --remote-arg-file flag is specified, only <extension> should be provided
to bless).
<extension> is the string extension that will be applied to create the
blessing.
`,
Runner: v23cmd.RunnerFunc(func(ctx *context.T, env *cmdline.Env, args []string) error {
if len(flagRemoteArgFile) > 0 {
if len(args) > 1 {
return fmt.Errorf("when --remote-arg-file is provided, only <extension> is expected, provided %d", len(args))
}
if (len(flagBlessRemoteKey) + len(flagBlessRemoteToken)) > 0 {
return fmt.Errorf("--remote-key and --remote-token should not be specified when --remote-arg-file is")
}
} else if len(args) > 2 {
return fmt.Errorf("got %d arguments, require at most 2", len(args))
} else if (len(flagBlessRemoteKey) == 0) != (len(flagBlessRemoteToken) == 0) {
return fmt.Errorf("either both --remote-key and --remote-token should be set, or neither should")
}
p := v23.GetPrincipal(ctx)
var (
err error
with security.Blessings
)
if len(flagBlessWith) > 0 {
if with, err = decodeBlessings(flagBlessWith); err != nil {
return fmt.Errorf("failed to read blessings from --with=%q: %v", flagBlessWith, err)
}
} else {
with = p.BlessingStore().Default()
}
caveats, err := caveatsFromFlags(flagBlessFor, &flagBlessCaveats)
if err != nil {
return err
}
if len(caveats) == 0 {
if flagBlessRequireCaveats {
if err := confirmNoCaveats(env); err != nil {
return err
}
}
caveats = []security.Caveat{security.UnconstrainedUse()}
}
if len(caveats) == 0 {
return errNoCaveats
}
tobless, extension, remoteKey, remoteToken, err := blessArgs(env, args)
if err != nil {
return err
}
// Send blessings to a "server" started by a "recvblessings" command, either
// with the --remote-arg-file flag, or with --remote-key and --remote-token flags.
if len(remoteKey) > 0 {
granter := &granter{with, extension, caveats, remoteKey}
return blessOverNetwork(ctx, tobless, granter, remoteToken)
}
// Blessing a principal whose key is available locally.
blessings, err := blessOverFileSystem(p, tobless, with, extension, caveats)
if err != nil {
return err
}
return dumpBlessings(blessings)
}),
}
cmdGetPublicKey = &cmdline.Command{
Name: "publickey",
Short: "Prints the public key of the principal.",
Long: `
Prints out the public key of the principal specified by the environment
that this tool is running in.
The key is printed as a base64 encoded bytes of the DER-format representation
of the key (suitable to be provided as an argument to the 'recognize' command
for example).
With --pretty, a 16-byte fingerprint of the key instead. This format is easier
for humans to read and is used in output of other commands in this program, but
is not suitable as an argument to the 'recognize' command.
`,
Runner: v23cmd.RunnerFunc(func(ctx *context.T, env *cmdline.Env, args []string) error {
key := v23.GetPrincipal(ctx).PublicKey()
if flagGetPublicKeyPretty {
fmt.Println(key)
return nil
}
der, err := key.MarshalBinary()
if err != nil {
return fmt.Errorf("corrupted key: %v", err)
}
fmt.Println(base64.URLEncoding.EncodeToString(der))
return nil
}),
}
cmdGetTrustedRoots = &cmdline.Command{
Name: "recognizedroots",
Short: "Return recognized blessings, and their associated public key.",
Long: `
Shows list of blessing names that the principal recognizes, and their associated
public key. If the principal is operating as a client, contacted servers must
appear on this list. If the principal is operating as a server, clients must
present blessings derived from this list.
`,
Runner: v23cmd.RunnerFunc(func(ctx *context.T, env *cmdline.Env, args []string) error {
fmt.Printf(v23.GetPrincipal(ctx).Roots().DebugString())
return nil
}),
}
cmdGetPeerMap = &cmdline.Command{
Name: "peermap",
Short: "Shows the map from peer pattern to which blessing name to present.",
Long: `
Shows the map from peer pattern to which blessing name to present.
If the principal operates as a server, it presents its default blessing to all peers.
If the principal operates as a client, it presents the map value associated with
the peer it contacts.
`,
Runner: v23cmd.RunnerFunc(func(ctx *context.T, env *cmdline.Env, args []string) error {
fmt.Printf(v23.GetPrincipal(ctx).BlessingStore().DebugString())
return nil
}),
}
cmdGetForPeer = &cmdline.Command{
Name: "forpeer",
Short: "Return blessings marked for the provided peer",
Long: `
Returns blessings that are marked for the provided peer in the
BlessingStore specified by the environment that this tool is
running in.
Providing --names will print the blessings' chain names.
Providing --rootkey <chain_name> will print the root key of the certificate chain
with chain_name.
Providing --caveats <chain_name> will print the caveats on the certificate chain
with chain_name.
`,
ArgsName: "[<peer_1> ... <peer_k>]",
ArgsLong: `
<peer_1> ... <peer_k> are the (human-readable string) blessings bound
to the peer. The returned blessings are marked with a pattern that is
matched by at least one of these. If no arguments are specified,
store.forpeer returns the blessings that are marked for all peers (i.e.,
blessings set on the store with the "..." pattern).
`,
Runner: v23cmd.RunnerFunc(func(ctx *context.T, env *cmdline.Env, args []string) error {
return printBlessingsInfo(v23.GetPrincipal(ctx).BlessingStore().ForPeer(args...))
}),
}
cmdGetDefault = &cmdline.Command{
Name: "default",
Short: "Return blessings marked as default",
Long: `
Returns blessings that are marked as default in the BlessingStore specified by
the environment that this tool is running in.
Providing --names will print the default blessings' chain names.
Providing --rootkey <chain_name> will print the root key of the certificate chain
with chain_name.
Providing --caveats <chain_name> will print the caveats on the certificate chain
with chain_name.
`,
Runner: v23cmd.RunnerFunc(func(ctx *context.T, env *cmdline.Env, args []string) error {
return printBlessingsInfo(v23.GetPrincipal(ctx).BlessingStore().Default())
}),
}
cmdSetForPeer = &cmdline.Command{
Name: "forpeer",
Short: "Set provided blessings for peer",
Long: `
Marks the provided blessings to be shared with the provided peers on the
BlessingStore specified by the environment that this tool is running in.
'set b pattern' marks the intention to reveal b to peers who
present blessings of their own matching 'pattern'.
'set nil pattern' can be used to remove the blessings previously
associated with the pattern (by a prior 'set' command).
It is an error to call 'set forpeer' with blessings whose public
key does not match the public key of this principal specified
by the environment.
`,
ArgsName: "<file> <pattern>",
ArgsLong: `
<file> is the path to a file containing a blessing typically obtained
from this tool. - is used for STDIN.
<pattern> is the BlessingPattern used to identify peers with whom this
blessing can be shared with.
`,
Runner: v23cmd.RunnerFunc(func(ctx *context.T, env *cmdline.Env, args []string) error {
if len(args) != 2 {
return fmt.Errorf("requires exactly two arguments <file>, <pattern>, provided %d", len(args))
}
blessings, err := decodeBlessings(args[0])
if err != nil {
return fmt.Errorf("failed to decode provided blessings: %v", err)
}
pattern := security.BlessingPattern(args[1])
p := v23.GetPrincipal(ctx)
if _, err := p.BlessingStore().Set(blessings, pattern); err != nil {
return fmt.Errorf("failed to set blessings %v for peers %v: %v", blessings, pattern, err)
}
if flagAddToRoots {
if err := p.AddToRoots(blessings); err != nil {
return fmt.Errorf("AddToRoots failed: %v", err)
}
}
return nil
}),
}
cmdRecognize = &cmdline.Command{
Name: "recognize",
Short: "Add to the set of identity providers recognized by this principal",
Long: `
Adds an identity provider to the set of recognized roots public keys for this principal.
It accepts either a single argument (which points to a file containing a blessing)
or two arguments (a name and a base64-encoded DER-encoded public key).
For example, to make the principal in credentials directory A recognize the
root of the default blessing in credentials directory B:
principal -v23.credentials=B bless A some_extension |
principal -v23.credentials=A recognize -
The extension 'some_extension' has no effect in the command above.
Or to make the principal in credentials directory A recognize the base64-encoded
public key KEY for blessing pattern P:
principal -v23.credentials=A recognize P KEY
`,
ArgsName: "<key|blessing> [<blessing pattern>]",
ArgsLong: `
<blessing> is the path to a file containing a blessing typically obtained from
this tool. - is used for STDIN.
<key> is a base64-encoded, DER-encoded public key.
<blessing pattern> is the blessing pattern for which <key> should be recognized.
`,
Runner: v23cmd.RunnerFunc(func(ctx *context.T, env *cmdline.Env, args []string) error {
if len(args) != 1 && len(args) != 2 {
return fmt.Errorf("requires either one argument <file>, or two arguments <key> <blessing pattern>, provided %d", len(args))
}
p := v23.GetPrincipal(ctx)
if len(args) == 1 {
blessings, err := decodeBlessings(args[0])
if err != nil {
return fmt.Errorf("failed to decode provided blessings: %v", err)
}
if err := p.AddToRoots(blessings); err != nil {
return fmt.Errorf("AddToRoots failed: %v", err)
}
return nil
}
// len(args) == 2
der, err := base64.URLEncoding.DecodeString(args[1])
if err != nil {
return fmt.Errorf("invalid base64 encoding of public key: %v", err)
}
key, err := security.UnmarshalPublicKey(der)
if err != nil {
return fmt.Errorf("invalid DER encoding of public key: %v", err)
}
return p.Roots().Add(key, security.BlessingPattern(args[0]))
}),
}
cmdSetDefault = &cmdline.Command{
Name: "default",
Short: "Set provided blessings as default",
Long: `
Sets the provided blessings as default in the BlessingStore specified by the
environment that this tool is running in.
It is an error to call 'set default' with blessings whose public key does
not match the public key of the principal specified by the environment.
`,
ArgsName: "<file>",
ArgsLong: `
<file> is the path to a file containing a blessing typically obtained from
this tool. - is used for STDIN.
`,
Runner: v23cmd.RunnerFunc(func(ctx *context.T, env *cmdline.Env, args []string) error {
if len(args) != 1 {
return fmt.Errorf("requires exactly one argument, <file>, provided %d", len(args))
}
blessings, err := decodeBlessings(args[0])
if err != nil {
return fmt.Errorf("failed to decode provided blessings: %v", err)
}
p := v23.GetPrincipal(ctx)
if err := p.BlessingStore().SetDefault(blessings); err != nil {
return fmt.Errorf("failed to set blessings %v as default: %v", blessings, err)
}
if flagAddToRoots {
if err := p.AddToRoots(blessings); err != nil {
return fmt.Errorf("AddToRoots failed: %v", err)
}
}
return nil
}),
}
cmdCreate = &cmdline.Command{
Name: "create",
Short: "Create a new principal and persist it into a directory",
Long: `
Creates a new principal with a single self-blessed blessing and writes it out
to the provided directory. The same directory can then be used to set the
V23_CREDENTIALS environment variable for other vanadium applications.
The operation fails if the directory already contains a principal. In this case
the --overwrite flag can be provided to clear the directory and write out the
new principal.
`,
ArgsName: "<directory> <blessing>",
ArgsLong: `
<directory> is the directory to which the new principal will be persisted.
<blessing> is the self-blessed blessing that the principal will be setup to use by default.
`,
Runner: cmdline.RunnerFunc(func(env *cmdline.Env, args []string) error {
if len(args) != 2 {
return fmt.Errorf("requires exactly two arguments: <directory> and <blessing>, provided %d", len(args))
}
dir, name := args[0], args[1]
if flagCreateOverwrite {
if err := os.RemoveAll(dir); err != nil {
return err
}
}
p, err := vsecurity.CreatePersistentPrincipal(dir, nil)
if err != nil {
return err
}
blessings, err := p.BlessSelf(name)
if err != nil {
return fmt.Errorf("BlessSelf(%q) failed: %v", name, err)
}
if err := vsecurity.SetDefaultBlessings(p, blessings); err != nil {
return fmt.Errorf("could not set blessings %v as default: %v", blessings, err)
}
return nil
}),
}
cmdFork = &cmdline.Command{
Name: "fork",
Short: "Fork a new principal from the principal that this tool is running as and persist it into a directory",
Long: `
Creates a new principal with a blessing from the principal specified by the
environment that this tool is running in, and writes it out to the provided
directory. The blessing that will be extended is the default one from the
blesser's store, or specified by the --with flag. Expiration on the blessing
are controlled via the --for flag. Additional caveats on the blessing are
controlled with the --caveat flag. The blessing is marked as default and
shareable with all peers on the new principal's blessing store.
The operation fails if the directory already contains a principal. In this case
the --overwrite flag can be provided to clear the directory and write out the
forked principal.
`,
ArgsName: "<directory> <extension>",
ArgsLong: `
<directory> is the directory to which the forked principal will be persisted.
<extension> is the extension under which the forked principal is blessed.
`,
Runner: v23cmd.RunnerFunc(func(ctx *context.T, env *cmdline.Env, args []string) error {
if len(args) != 2 {
return fmt.Errorf("requires exactly two arguments: <directory> and <extension>, provided %d", len(args))
}
dir, extension := args[0], args[1]
caveats, err := caveatsFromFlags(flagForkFor, &flagForkCaveats)
if err != nil {
return err
}
if !flagForkRequireCaveats && len(caveats) == 0 {
caveats = []security.Caveat{security.UnconstrainedUse()}
}
if len(caveats) == 0 {
return errNoCaveats
}
var with security.Blessings
if len(flagForkWith) > 0 {
if with, err = decodeBlessings(flagForkWith); err != nil {
return fmt.Errorf("failed to read blessings from --with=%q: %v", flagForkWith, err)
}
} else {
with = v23.GetPrincipal(ctx).BlessingStore().Default()
}
if flagCreateOverwrite {
if err := os.RemoveAll(dir); err != nil {
return err
}
}
p, err := vsecurity.CreatePersistentPrincipal(dir, nil)
if err != nil {
return err
}
key := p.PublicKey()
rp := v23.GetPrincipal(ctx)
blessings, err := rp.Bless(key, with, extension, caveats[0], caveats[1:]...)
if err != nil {
return fmt.Errorf("Bless(%v, %v, %q, ...) failed: %v", key, with, extension, err)
}
if err := vsecurity.SetDefaultBlessings(p, blessings); err != nil {
return fmt.Errorf("could not set blessings %v as default: %v", blessings, err)
}
return nil
}),
}
cmdSeekBlessings = &cmdline.Command{
Name: "seekblessings",
Short: "Seek blessings from a web-based Vanadium blessing service",
Long: `
Seeks blessings from a web-based Vanadium blesser which
requires the caller to first authenticate with Google using OAuth. Simply
run the command to see what happens.
The blessings are sought for the principal specified by the environment that
this tool is running in.
The blessings obtained are set as default, unless the --set-default flag is
set to true, and are also set for sharing with all peers, unless a more
specific peer pattern is provided using the --for-peer flag.
`,
Runner: v23cmd.RunnerFunc(func(ctx *context.T, env *cmdline.Env, args []string) error {
p := v23.GetPrincipal(ctx)
blessedChan := make(chan string)
defer close(blessedChan)
macaroonChan, err := getMacaroonForBlessRPC(p.PublicKey(), flagSeekBlessingsFrom, blessedChan, flagSeekBlessingsBrowser)
if err != nil {
return fmt.Errorf("failed to get macaroon from Vanadium blesser: %v", err)
}
blessings, err := exchangeMacaroonForBlessing(ctx, macaroonChan)
if err != nil {
return err
}
blessedChan <- fmt.Sprint(blessings)
// Wait for getTokenForBlessRPC to clean up:
<-macaroonChan
if flagSeekBlessingsSetDefault {
if err := p.BlessingStore().SetDefault(blessings); err != nil {
return fmt.Errorf("failed to set blessings %v as default: %v", blessings, err)
}
}
if pattern := security.BlessingPattern(flagSeekBlessingsForPeer); len(pattern) > 0 {
if _, err := p.BlessingStore().Set(blessings, pattern); err != nil {
return fmt.Errorf("failed to set blessings %v for peers %v: %v", blessings, pattern, err)
}
}
if flagAddToRoots {
if err := p.AddToRoots(blessings); err != nil {
return fmt.Errorf("AddToRoots failed: %v", err)
}
}
fmt.Fprintf(env.Stdout, "Received blessings: %v\n", blessings)
return nil
}),
}
cmdRecvBlessings = &cmdline.Command{
Name: "recvblessings",
Short: "Receive blessings sent by another principal and use them as the default",
Long: `
Allow another principal (likely a remote process) to bless this one.
This command sets up the invoker (this process) to wait for a blessing
from another invocation of this tool (remote process) and prints out the
command to be run as the remote principal.
The received blessings are set as default, unless the --set-default flag is
set to true, and are also set for sharing with all peers, unless a more
specific peer pattern is provided using the --for-peer flag.
TODO(ashankar,cnicolaou): Make this next paragraph possible! Requires
the ability to obtain the proxied endpoint.
Typically, this command should require no arguments.
However, if the sender and receiver are on different network domains, it may
make sense to use the --v23.proxy flag:
principal --v23.proxy=proxy recvblessings
The command to be run at the sender is of the form:
principal bless --remote-key=KEY --remote-token=TOKEN ADDRESS EXTENSION
The --remote-key flag is used to by the sender to "authenticate" the receiver,
ensuring it blesses the intended recipient and not any attacker that may have
taken over the address.
The --remote-token flag is used by the sender to authenticate itself to the
receiver. This helps ensure that the receiver rejects blessings from senders
who just happened to guess the network address of the 'recvblessings'
invocation.
If the --remote-arg-file flag is provided to recvblessings, the remote key, remote token
and object address of this principal will be written to the specified location.
This file can be supplied to bless:
principal bless --remote-arg-file FILE EXTENSION
`,
Runner: v23cmd.RunnerFunc(func(ctx *context.T, env *cmdline.Env, args []string) error {
if len(args) != 0 {
return fmt.Errorf("command accepts no arguments")
}
server, err := v23.NewServer(ctx)
if err != nil {
return fmt.Errorf("failed to create server to listen for blessings: %v", err)
}
defer server.Stop()
eps, err := server.Listen(v23.GetListenSpec(ctx))
if err != nil {
return fmt.Errorf("failed to setup listening: %v", err)
}
var token [24]byte
if _, err := rand.Read(token[:]); err != nil {
return fmt.Errorf("unable to generate token: %v", err)
}
p := v23.GetPrincipal(ctx)
service := &recvBlessingsService{
principal: p,
token: base64.URLEncoding.EncodeToString(token[:]),
notify: make(chan error),
}
if err := server.Serve("", service, security.AllowEveryone()); err != nil {
return fmt.Errorf("failed to setup service: %v", err)
}
fmt.Println("Run the following command on behalf of the principal that will send blessings:")
fmt.Println("You may want to adjust flags affecting the caveats on this blessing, for example using")
fmt.Println("the --for flag")
fmt.Println()
if len(flagRemoteArgFile) > 0 {
if err := writeRecvBlessingsInfo(flagRemoteArgFile, p.PublicKey().String(), service.token, eps[0].Name()); err != nil {
return fmt.Errorf("failed to write recvblessings info to %v: %v", flagRemoteArgFile, err)
}
fmt.Printf("make %q accessible to the blesser, possibly by copying the file over and then run:\n", flagRemoteArgFile)
fmt.Printf("principal bless --remote-arg-file=%v", flagRemoteArgFile)
} else {
fmt.Printf("principal bless --remote-key=%v --remote-token=%v %v\n", p.PublicKey(), service.token, eps[0].Name())
}
fmt.Println()
fmt.Println("...waiting for sender..")
return <-service.notify
}),
}
)
func printAnnotatedBlessingsNames(b security.Blessings) string {
// If the Blessings are expired, print a message saying so.
expiredMessage := ""
if exp := b.Expiry(); !exp.IsZero() && exp.Before(time.Now()) {
expiredMessage = " [EXPIRED]"
}
return fmt.Sprintf("%v%s", b, expiredMessage)
}
func blessArgs(env *cmdline.Env, args []string) (tobless, extension, remoteKey, remoteToken string, err error) {
extensionInArgs := false
if len(flagRemoteArgFile) == 0 {
tobless = args[0]
remoteKey = flagBlessRemoteKey
remoteToken = flagBlessRemoteToken
extensionInArgs = len(args) > 1
} else if len(flagRemoteArgFile) > 0 {
remoteKey, remoteToken, tobless, err = blessArgsFromFile(flagRemoteArgFile)
extensionInArgs = len(args) > 0
}
if extensionInArgs {
extension = args[len(args)-1]
} else {
extension, err = readFromStdin(env, "Extension to use for blessing:")
}
return
}
func confirmNoCaveats(env *cmdline.Env) error {
text, err := readFromStdin(env, `WARNING: No caveats provided
It is generally dangerous to bless another principal without any caveats as
that gives them unrestricted access to the blesser's credentials.
Caveats can be specified with the --for or --caveat flags.
Do you really wish to bless without caveats? (YES to confirm)`)
if err != nil || strings.ToUpper(text) != "YES" {
return errNoCaveats
}
return nil
}
func readFromStdin(env *cmdline.Env, prompt string) (string, error) {
fmt.Fprintf(env.Stdout, "%v ", prompt)
os.Stdout.Sync()
// Cannot use bufio because that may "lose" data beyond the line (the
// remainder in the buffer).
// Do the inefficient byte-by-byte scan for now - shouldn't be a problem
// given the common use case. If that becomes a problem, switch to bufio
// and share the bufio.Reader between multiple calls to readFromStdin.
buf := make([]byte, 0, 100)
r := make([]byte, 1)
for {
n, err := env.Stdin.Read(r)
if n == 1 && r[0] == '\n' {
break
}
if n == 1 {
buf = append(buf, r[0])
continue
}
if err != nil {
return "", err
}
}
return strings.TrimSpace(string(buf)), nil
}
func blessOverFileSystem(p security.Principal, tobless string, with security.Blessings, extension string, caveats []security.Caveat) (security.Blessings, error) {
var key security.PublicKey
if finfo, err := os.Stat(tobless); err == nil && finfo.IsDir() {
other, err := vsecurity.LoadPersistentPrincipal(tobless, nil)
if err != nil {
if other, err = vsecurity.CreatePersistentPrincipal(tobless, nil); err != nil {
return security.Blessings{}, fmt.Errorf("failed to read principal in directory %q: %v", tobless, err)
}
}
key = other.PublicKey()
} else if other, err := decodeBlessings(tobless); err != nil {
return security.Blessings{}, fmt.Errorf("failed to decode blessings in %q: %v", tobless, err)
} else {
key = other.PublicKey()
}
return p.Bless(key, with, extension, caveats[0], caveats[1:]...)
}
type recvBlessingsInfo struct {
RemoteKey string `json:"remote_key"`
RemoteToken string `json:"remote_token"`
Name string `json:"name"`
}
func writeRecvBlessingsInfo(fname string, remoteKey, remoteToken, name string) error {
f, err := os.OpenFile(fname, os.O_WRONLY|os.O_CREATE, 0666)
if err != nil {
return err
}
b, err := json.Marshal(recvBlessingsInfo{remoteKey, remoteToken, name})
if err != nil {
return err
}
if _, err := f.Write(b); err != nil {
return err
}
return nil
}
func blessArgsFromFile(fname string) (remoteKey, remoteToken, tobless string, err error) {
blessJSON, err := ioutil.ReadFile(fname)
if err != nil {
return "", "", "", err
}
var binfo recvBlessingsInfo
if err := json.Unmarshal(blessJSON, &binfo); err != nil {
return "", "", "", err
}
return binfo.RemoteKey, binfo.RemoteToken, binfo.Name, err
}
func main() {
cmdline.HideGlobalFlagsExcept()
cmdBlessSelf.Flags.Var(&flagBlessSelfCaveats, "caveat", flagBlessSelfCaveats.usage())
cmdBlessSelf.Flags.DurationVar(&flagBlessSelfFor, "for", 0, "Duration of blessing validity (zero implies no expiration)")
cmdDump.Flags.BoolVar(&flagDumpShort, "s", false, "If true, show only the default blessing names")
cmdFork.Flags.BoolVar(&flagCreateOverwrite, "overwrite", false, "If true, any existing principal data in the directory will be overwritten")
cmdFork.Flags.Var(&flagForkCaveats, "caveat", flagForkCaveats.usage())
cmdFork.Flags.DurationVar(&flagForkFor, "for", 0, "Duration of blessing validity (zero implies no expiration caveat)")
cmdFork.Flags.BoolVar(&flagForkRequireCaveats, "require-caveats", true, "If false, allow blessing without any caveats. This is typically not advised as the principal wielding the blessing will be almost as powerful as its blesser")
cmdFork.Flags.StringVar(&flagForkWith, "with", "", "Path to file containing blessing to extend")
cmdBless.Flags.Var(&flagBlessCaveats, "caveat", flagBlessCaveats.usage())
cmdBless.Flags.DurationVar(&flagBlessFor, "for", 0, "Duration of blessing validity (zero implies no expiration caveat)")
cmdBless.Flags.BoolVar(&flagBlessRequireCaveats, "require-caveats", true, "If false, allow blessing without any caveats. This is typically not advised as the principal wielding the blessing will be almost as powerful as its blesser")
cmdBless.Flags.StringVar(&flagBlessWith, "with", "", "Path to file containing blessing to extend")
cmdBless.Flags.StringVar(&flagBlessRemoteKey, "remote-key", "", "Public key of the remote principal to bless (obtained from the 'recvblessings' command run by the remote principal")
cmdBless.Flags.StringVar(&flagBlessRemoteToken, "remote-token", "", "Token provided by principal running the 'recvblessings' command")
cmdBless.Flags.StringVar(&flagRemoteArgFile, "remote-arg-file", "", "File containing bless arguments written by 'principal recvblessings -remote-arg-file FILE EXTENSION' command. This can be provided to bless in place of --remote-key, --remote-token, and <principal>")
defaultFrom := "https://dev.v.io/auth/google"
if e := os.Getenv(ref.EnvOAuthIdentityProvider); e != "" {
defaultFrom = e
}
cmdSeekBlessings.Flags.StringVar(&flagSeekBlessingsFrom, "from", defaultFrom, "URL to use to begin the seek blessings process")
cmdSeekBlessings.Flags.BoolVar(&flagSeekBlessingsSetDefault, "set-default", true, "If true, the blessings obtained will be set as the default blessing in the store")
cmdSeekBlessings.Flags.StringVar(&flagSeekBlessingsForPeer, "for-peer", string(security.AllPrincipals), "If non-empty, the blessings obtained will be marked for peers matching this pattern in the store")
cmdSeekBlessings.Flags.BoolVar(&flagSeekBlessingsBrowser, "browser", true, "If false, the seekblessings command will not open the browser and only print the url to visit.")
cmdSeekBlessings.Flags.BoolVar(&flagAddToRoots, "add-to-roots", true, "If true, the root certificate of the blessing will be added to the principal's set of recognized root certificates")
cmdSetForPeer.Flags.BoolVar(&flagAddToRoots, "add-to-roots", true, "If true, the root certificate of the blessing will be added to the principal's set of recognized root certificates")
cmdSetDefault.Flags.BoolVar(&flagAddToRoots, "add-to-roots", true, "If true, the root certificate of the blessing will be added to the principal's set of recognized root certificates")
cmdCreate.Flags.BoolVar(&flagCreateOverwrite, "overwrite", false, "If true, any existing principal data in the directory will be overwritten")
cmdRecvBlessings.Flags.BoolVar(&flagRecvBlessingsSetDefault, "set-default", true, "If true, the blessings received will be set as the default blessing in the store")
cmdRecvBlessings.Flags.StringVar(&flagRecvBlessingsForPeer, "for-peer", string(security.AllPrincipals), "If non-empty, the blessings received will be marked for peers matching this pattern in the store")
cmdRecvBlessings.Flags.StringVar(&flagRemoteArgFile, "remote-arg-file", "", "If non-empty, the remote key, remote token, and principal will be written to the specified file in a JSON object. This can be provided to 'principal bless --remote-arg-file FILE EXTENSION'")
cmdGetForPeer.Flags.BoolVar(&flagBlessingsNames, "names", false, "If true, shows the value of the blessing name to be presented to the peer")
cmdGetForPeer.Flags.StringVar(&flagBlessingsRootKey, "rootkey", "", "Shows the value of the root key of the provided certificate chain name.")
cmdGetForPeer.Flags.StringVar(&flagBlessingsCaveats, "caveats", "", "Shows the caveats on the provided certificate chain name.")
cmdGetDefault.Flags.BoolVar(&flagBlessingsNames, "names", false, "If true, shows the value of the blessing name to be presented to the peer")
cmdGetDefault.Flags.StringVar(&flagBlessingsRootKey, "rootkey", "", "Shows the value of the root key of the provided certificate chain name.")
cmdGetDefault.Flags.StringVar(&flagBlessingsCaveats, "caveats", "", "Shows the caveats on the provided certificate chain name.")
cmdGetPublicKey.Flags.BoolVar(&flagGetPublicKeyPretty, "pretty", false, "If true, print the key out in a more human-readable but lossy representation.")
cmdSet := &cmdline.Command{
Name: "set",
Short: "Mutate the principal's blessings.",
Long: `
Commands to mutate the blessings of the principal.
All input blessings are expected to be serialized using base64-VOM-encoding.
See 'principal get'.
`,
Children: []*cmdline.Command{cmdSetDefault, cmdSetForPeer},
}
cmdGet := &cmdline.Command{
Name: "get",
Short: "Read the principal's blessings.",
Long: `
Commands to inspect the blessings of the principal.
All blessings are printed to stdout using base64-VOM-encoding.
`,
Children: []*cmdline.Command{cmdGetDefault, cmdGetForPeer, cmdGetPublicKey, cmdGetTrustedRoots, cmdGetPeerMap},
}
root := &cmdline.Command{
Name: "principal",
Short: "creates and manages Vanadium principals and blessings",
Long: `
Command principal creates and manages Vanadium principals and blessings.
All objects are printed using base64-VOM-encoding.
`,
Children: []*cmdline.Command{cmdCreate, cmdFork, cmdSeekBlessings, cmdRecvBlessings, cmdDump, cmdDumpBlessings, cmdBlessSelf, cmdBless, cmdSet, cmdGet, cmdRecognize},
}
cmdline.Main(root)
}
func decodeBlessings(fname string) (security.Blessings, error) {
var b security.Blessings
err := decode(fname, &b)
return b, err
}
func dumpBlessings(blessings security.Blessings) error {
if blessings.IsZero() {
return fmt.Errorf("no blessings found")
}
str, err := base64VomEncode(blessings)
if err != nil {
return fmt.Errorf("base64-VOM encoding failed: %v", err)
}
fmt.Println(str)
return nil
}
func printBlessingsInfo(blessings security.Blessings) error {
if blessings.IsZero() {
return fmt.Errorf("no blessings found")
}
if flagBlessingsNames {
fmt.Println(strings.Replace(fmt.Sprint(blessings), ",", "\n", -1))
return nil
} else if len(flagBlessingsRootKey) > 0 {
chain, err := getChainByName(blessings, flagBlessingsRootKey)
if err != nil {
return err
}
fmt.Println(rootkey(chain))
return nil
} else if len(flagBlessingsCaveats) > 0 {
chain, err := getChainByName(blessings, flagBlessingsCaveats)
if err != nil {
return err
}
cavs, err := prettyPrintCaveats(chain)
if err != nil {
return err
}
for _, c := range cavs {
fmt.Println(c)
}
return nil
}
return dumpBlessings(blessings)
}
func prettyPrintCaveats(chain []security.Certificate) ([]string, error) {
var cavs []security.Caveat
for _, cert := range chain {
cavs = append(cavs, cert.Caveats...)
}
var s []string
for _, cav := range cavs {
if cav.Id == security.PublicKeyThirdPartyCaveat.Id {
c := cav.ThirdPartyDetails()
s = append(s, fmt.Sprintf("ThirdPartyCaveat: Requires discharge from %v (ID=%q)", c.Location(), c.ID()))
continue
}
var param interface{}
if err := vom.Decode(cav.ParamVom, &param); err != nil {
return nil, err
}
switch cav.Id {
case security.ConstCaveat.Id:
// In the case a ConstCaveat is specified, we only want to print it
// if it never validates.
if !param.(bool) {
s = append(s, fmt.Sprintf("Never validates"))
}
case security.ExpiryCaveat.Id:
s = append(s, fmt.Sprintf("Expires at %v", param))
case security.MethodCaveat.Id:
s = append(s, fmt.Sprintf("Restricted to methods %v", param))
case security.PeerBlessingsCaveat.Id:
s = append(s, fmt.Sprintf("Restricted to peers with blessings %v", param))
default:
s = append(s, cav.String())
}
}
return s, nil
}
func getChainByName(b security.Blessings, name string) ([]security.Certificate, error) {
wire, err := blessings2wire(b)
if err != nil {
return nil, err
}
for _, chain := range wire.CertificateChains {
if chainName(chain) == name {
return chain, nil
}
}
return nil, fmt.Errorf("no chains of name %v in %v", name, b)
}
func read(fname string) (string, error) {
if len(fname) == 0 {
return "", nil
}
f := os.Stdin
if fname != "-" {
var err error
if f, err = os.Open(fname); err != nil {
return "", fmt.Errorf("failed to open %q: %v", fname, err)
}
}
defer f.Close()
var buf bytes.Buffer
if _, err := io.Copy(&buf, f); err != nil {
return "", fmt.Errorf("failed to read %q: %v", fname, err)
}
return buf.String(), nil
}
func decode(fname string, val interface{}) error {
str, err := read(fname)
if err != nil {
return err
}
if err := base64VomDecode(str, val); err != nil || val == nil {
return fmt.Errorf("failed to decode %q: %v", fname, err)
}
return nil
}
func defaultBlessingName() string {
var name string
if user, _ := user.Current(); user != nil && len(user.Username) > 0 {
name = user.Username
} else {
name = "anonymous"
}
if host, _ := os.Hostname(); len(host) > 0 {
name = name + "@" + host
}
return name
}
func rootkey(chain []security.Certificate) string {
if len(chain) == 0 {
return "<empty certificate chain>"
}
key, err := security.UnmarshalPublicKey(chain[0].PublicKey)
if err != nil {
return fmt.Sprintf("<invalid PublicKey: %v>", err)
}
return fmt.Sprintf("%v", key)
}
func chainName(chain []security.Certificate) string {
exts := make([]string, len(chain))
for i, cert := range chain {
exts[i] = cert.Extension
}
return strings.Join(exts, security.ChainSeparator)
}
func base64VomEncode(i interface{}) (string, error) {
buf := &bytes.Buffer{}
closer := base64.NewEncoder(base64.URLEncoding, buf)
enc := vom.NewEncoder(closer)
if err := enc.Encode(i); err != nil {
return "", err
}
// Must close the base64 encoder to flush out any partially written
// blocks.
if err := closer.Close(); err != nil {
return "", err
}
return buf.String(), nil
}
func base64VomDecode(s string, i interface{}) error {
b, err := base64.URLEncoding.DecodeString(s)
if err != nil {
return err
}
dec := vom.NewDecoder(bytes.NewBuffer(b))
return dec.Decode(i)
}
type recvBlessingsService struct {
principal security.Principal
notify chan error
token string
}
func (r *recvBlessingsService) Grant(_ *context.T, call rpc.ServerCall, token string) error {
b := call.GrantedBlessings()
if b.IsZero() {
return fmt.Errorf("no blessings granted by sender")
}
if len(token) != len(r.token) {
// A timing attack can be used to figure out the length
// of the token, but then again, so can looking at the
// source code. So, it's okay.
return fmt.Errorf("blessings received from unexpected sender")
}
if subtle.ConstantTimeCompare([]byte(token), []byte(r.token)) != 1 {
return fmt.Errorf("blessings received from unexpected sender")
}
if flagRecvBlessingsSetDefault {
if err := r.principal.BlessingStore().SetDefault(b); err != nil {
return fmt.Errorf("failed to set blessings %v as default: %v", b, err)
}
}
if pattern := security.BlessingPattern(flagRecvBlessingsForPeer); len(pattern) > 0 {
if _, err := r.principal.BlessingStore().Set(b, pattern); err != nil {
return fmt.Errorf("failed to set blessings %v for peers %v: %v", b, pattern, err)
}
}
if flagAddToRoots {
if err := r.principal.AddToRoots(b); err != nil {
return fmt.Errorf("failed to add blessings to recognized roots: %v", err)
}
}
fmt.Println("Received blessings:", b)
r.notify <- nil
return nil
}
type granter struct {
with security.Blessings
extension string
caveats []security.Caveat
serverKey string
}
func (g *granter) Grant(ctx *context.T, call security.Call) (security.Blessings, error) {
server := call.RemoteBlessings()
p := call.LocalPrincipal()
if got := fmt.Sprintf("%v", server.PublicKey()); got != g.serverKey {
// If the granter returns an error, the RPC framework should
// abort the RPC before sending the request to the server.
// Thus, there is no concern about leaking the token to an
// imposter server.
return security.Blessings{}, fmt.Errorf("key mismatch: Remote end has public key %v, want %v", got, g.serverKey)
}
return p.Bless(server.PublicKey(), g.with, g.extension, g.caveats[0], g.caveats[1:]...)
}
func (*granter) RPCCallOpt() {}
func blessOverNetwork(ctx *context.T, object string, granter *granter, remoteToken string) error {
client := v23.GetClient(ctx)
// The receiver is being authorized based on the hash of its public key
// (see Grant), so it should be fine to ignore the blessing names in the endpoint
// (which are likely to not be recognized by the sender anyway).
//
// At worst, there is a privacy leak of the senders intent to send some
// blessings. That could be addressed by making the full public key of
// the recipeint available to the sender and using
// options.ServerPublicKey instead of providing a "hash" of the
// recipients public key and verifying in the Granter implementation.
if err := client.Call(ctx, object, "Grant", []interface{}{remoteToken}, nil, granter, options.SkipServerEndpointAuthorization{}); err != nil {
return fmt.Errorf("failed to make RPC to %q: %v", object, err)
}
return nil
}
func caveatsFromFlags(expiry time.Duration, caveatsFlag *caveatsFlag) ([]security.Caveat, error) {
caveats, err := caveatsFlag.Compile()
if err != nil {
return nil, fmt.Errorf("failed to parse caveats: %v", err)
}
if expiry != 0 {
ecav, err := security.NewExpiryCaveat(time.Now().Add(expiry))
if err != nil {
return nil, fmt.Errorf("failed to create expiration caveat: %v", err)
}
caveats = append(caveats, ecav)
}
return caveats, nil
}
// Circuitous route to get to the certificate chains.
// See comments on why security.MarshalBlessings is discouraged.
// Though, a better alternative is worth looking into.
func blessings2wire(b security.Blessings) (security.WireBlessings, error) {
var wire security.WireBlessings
data, err := vom.Encode(b)
if err != nil {
return wire, err
}
err = vom.Decode(data, &wire)
return wire, err
}