blob: 5478b29c44b94f6880ed9270d99f8870679f27b6 [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.
package rpc
import (
"reflect"
"v.io/v23/context"
"v.io/v23/options"
"v.io/v23/rpc"
"v.io/v23/security"
"v.io/v23/verror"
)
// TODO(ribrdb): Flip this to true once everything is updated and also update
// the server authorizer tests.
const enableSecureServerAuth = false
var (
// These errors are intended to be used as arguments to higher
// level errors and hence {1}{2} is omitted from their format
// strings to avoid repeating these n-times in the final error
// message visible to the user.
errNoBlessingsFromServer = reg(".errNoBlessingsFromServer", "server has not presented any blessings")
errAuthNoServerBlessingsMatch = reg(".errAuthNoServerBlessingsMatch",
"server blessings {3} do not match client expectations {4}, (rejected blessings: {5})")
errAuthServerNotAllowed = reg(".errAuthServerNotAllowed",
"server blessings {3} do not match any allowed server patterns {4}{:5}")
errAuthServerKeyNotAllowed = reg(".errAuthServerKeyNotAllowed",
"remote public key {3} not matched by server key {4}")
errMultiplePublicKeys = reg(".errMultiplePublicKeyOptions", "at most one ServerPublicKey options can be provided")
)
// serverAuthorizer implements security.Authorizer.
type serverAuthorizer struct {
allowedServerPolicies [][]security.BlessingPattern
serverPublicKey security.PublicKey
ignoreBlessingsInEndpoint bool
}
// newServerAuthorizer returns a security.Authorizer for authorizing the server
// during a flow. The authorization policy is based on options supplied to the
// call that initiated the flow. Additionally, if pattern is non-empty then
// the server will be authorized only if it presents at least one blessing
// that matches pattern.
//
// This method assumes that canCreateServerAuthorizer(opts) is nil.
func newServerAuthorizer(pattern security.BlessingPattern, opts ...rpc.CallOpt) security.Authorizer {
auth := &serverAuthorizer{}
for _, o := range opts {
switch v := o.(type) {
case options.ServerPublicKey:
auth.serverPublicKey = v.PublicKey
case options.AllowedServersPolicy:
auth.allowedServerPolicies = append(auth.allowedServerPolicies, v)
case options.SkipServerEndpointAuthorization:
auth.ignoreBlessingsInEndpoint = true
}
}
if len(pattern) > 0 {
auth.allowedServerPolicies = append(auth.allowedServerPolicies, []security.BlessingPattern{pattern})
}
return auth
}
func (a *serverAuthorizer) Authorize(ctx *context.T, call security.Call) error {
if call.RemoteBlessings().IsZero() {
return verror.New(errNoBlessingsFromServer, ctx)
}
serverBlessings, rejectedBlessings := security.RemoteBlessingNames(ctx, call)
if epb := call.RemoteEndpoint().BlessingNames(); len(epb) > 0 && !a.ignoreBlessingsInEndpoint {
matched := false
for _, b := range epb {
// TODO(ashankar,ataly): Should this be
// security.BlessingPattern(b).MakeNonExtendable().MatchedBy()?
// Because, without that, a delegate of the real server
// can be a man-in-the-middle without failing
// authorization. Is that a desirable property?
if security.BlessingPattern(b).MatchedBy(serverBlessings...) {
matched = true
break
}
}
if !matched {
return verror.New(errAuthNoServerBlessingsMatch, ctx, serverBlessings, epb, rejectedBlessings)
}
} else if enableSecureServerAuth && len(epb) == 0 {
// No blessings in the endpoint to set expectations on the
// "identity" of the server. Use the default authorization
// policy.
if err := security.DefaultAuthorizer().Authorize(ctx, call); err != nil {
return err
}
}
for _, patterns := range a.allowedServerPolicies {
if !matchedBy(patterns, serverBlessings) {
return verror.New(errAuthServerNotAllowed, ctx, serverBlessings, patterns, rejectedBlessings)
}
}
if remoteKey, key := call.RemoteBlessings().PublicKey(), a.serverPublicKey; key != nil && !reflect.DeepEqual(remoteKey, key) {
return verror.New(errAuthServerKeyNotAllowed, ctx, remoteKey, key)
}
return nil
}
func matchedBy(patterns []security.BlessingPattern, blessings []string) bool {
if patterns == nil {
return true
}
for _, p := range patterns {
if p.MatchedBy(blessings...) {
return true
}
}
return false
}
func canCreateServerAuthorizer(ctx *context.T, opts []rpc.CallOpt) error {
var pkey security.PublicKey
for _, o := range opts {
switch v := o.(type) {
case options.ServerPublicKey:
if pkey != nil && !reflect.DeepEqual(pkey, v.PublicKey) {
return verror.New(errMultiplePublicKeys, ctx)
}
pkey = v.PublicKey
}
}
return nil
}