blob: 30cbf74465f15ee8c3151c97d7b1eaa4c58a9186 [file] [log] [blame]
package caveat
import (
"bytes"
"crypto/rand"
"crypto/sha256"
"encoding/binary"
"fmt"
"time"
"veyron2/security"
"veyron2/security/wire"
"veyron2/vom"
)
// nonceLength specifies the length in bytes of the random nonce used
// in publicKeyCaveat and publicKeyDischarge.
//
// TODO(ataly, ashankar): Nonce length of 16 bytes was chosen very conservatively.
// The purpose of the random nonce is essentially to separate caveats that have
// the same restriction and validation key (in order to prevent discharge replay).
// Can we use 4bytes instead?.
const nonceLength = 16
// errPublicKeyCaveat returns an error indicating that the provided caveat has
// an invalid or missing discharge.
func errPublicKeyCaveat(c *publicKeyCaveat, err error) error {
return fmt.Errorf("%v for publicKeyCaveat{nonce:%v location:%q}", err, c.RandNonce, c.Location())
}
// publicKeyCaveat implements security.ThirdPartyCaveat. It specifies a restriction,
// a validation key and the location of the third-party responsible for
// discharging the caveat. A discharge for this caveat is a signed assertion whose
// signature can be verified using the validation key.
type publicKeyCaveat struct {
// RandNonce specifies a cryptographically random nonce (of fixed length) that
// uniquely identifies the caveat.
RandNonce []uint8
// DischargeMintingCaveat specifies the caveat that has to be validated
// before minting a discharge for a publicKeyCaveat. A byte slice containing
// VOM-encoded security.Caveat is used to enable a publicKeyCaveat to be
// validated by devices that cannot decode the discharge minting caveats.
DischargeMintingCaveat []byte
// ValidationKey specifies the public key of the discharging-party.
ValidationKey wire.PublicKey
// ThirdPartyLocation specifies the object name of the discharging-party.
ThirdPartyLocation string
// Information wanted in order to issue a discharge.
ThirdPartyRequirements security.ThirdPartyRequirements
}
// ID returns a unique 32bytes long identity for the caveat based on the random nonce
// and SHA-256 hash of the restriction embedded in the caveat.
//
// TODO(ataly, ashankar): A 256bit hash is probably much stronger that what we need
// here. Can we truncate the hash to 96bits?
func (c *publicKeyCaveat) ID() security.ThirdPartyCaveatID {
return security.ThirdPartyCaveatID(id(c.RandNonce, c.DischargeMintingCaveat))
}
// Location returns the third-party location embedded in the caveat.
func (c *publicKeyCaveat) Location() string {
return c.ThirdPartyLocation
}
func (c *publicKeyCaveat) String() string {
return fmt.Sprintf("publicKeyCaveat{DischargeMintingCaveat: (%v bytes), ThirdPartyLocation: %q}", len(c.DischargeMintingCaveat), c.ThirdPartyLocation)
}
func (c *publicKeyCaveat) Requirements() security.ThirdPartyRequirements {
return c.ThirdPartyRequirements
}
// Validate verifies whether the caveat validates for the provided context.
func (c *publicKeyCaveat) Validate(ctx security.Context) error {
// Check whether the context has a dicharge matching the caveat's ID.
dis, ok := ctx.CaveatDischarges()[c.ID()]
if !ok {
return errPublicKeyCaveat(c, fmt.Errorf("missing discharge"))
}
pkDischarge, ok := dis.(*publicKeyDischarge)
if !ok {
return errPublicKeyCaveat(c, fmt.Errorf("caveat of type %T cannot be validated with discharge of type %T", c, dis))
}
// Validate all caveats present on the discharge.
for _, cav := range pkDischarge.Caveats {
if err := cav.Validate(ctx); err != nil {
return errPublicKeyCaveat(c, err)
}
}
// Check the discharge signature with the validation key from the caveat.
key, err := c.ValidationKey.Decode()
if err != nil {
return errPublicKeyCaveat(c, err)
}
if !pkDischarge.Signature.Verify(key, pkDischarge.contentHash()) {
return errPublicKeyCaveat(c, fmt.Errorf("discharge %v has invalid signature", dis))
}
return nil
}
// publicKeyDischarge implements security.ThirdPartyDischarge. It specifies a
// discharge for a publicKeyCaveat, and includes a signature that can be verified
// with the validation key of the caveat. Additionally, the discharge may include
// service caveats which must all be valid in order for the discharge to be
// considered valid.
type publicKeyDischarge struct {
// ThirdPartyCaveatID is used to match a Discharge with the Caveat it is for.
ThirdPartyCaveatID security.ThirdPartyCaveatID
// Caveats under which this Discharge is valid.
Caveats []wire.Caveat
// Signature on the contents of the discharge obtained using the private key
// corresponding to the validaton key in the caveat.
Signature security.Signature
}
// CaveatID returns a unique identity for the discharge based on the random nonce and
// restriction embedded in the discharge.
func (d *publicKeyDischarge) CaveatID() security.ThirdPartyCaveatID {
return d.ThirdPartyCaveatID
}
func (d *publicKeyDischarge) ThirdPartyCaveats() []security.ServiceCaveat {
return wire.DecodeThirdPartyCaveats(d.Caveats)
}
// sign uses the provided identity to sign the contents of the discharge.
func (d *publicKeyDischarge) sign(discharger security.PrivateID) (err error) {
d.Signature, err = discharger.Sign(d.contentHash())
return
}
func (d *publicKeyDischarge) contentHash() []byte {
h := sha256.New()
tmp := make([]byte, binary.MaxVarintLen64)
wire.WriteBytes(h, tmp, []byte(d.ThirdPartyCaveatID))
for _, cav := range d.Caveats {
wire.WriteString(h, tmp, string(cav.Service))
wire.WriteBytes(h, tmp, cav.Bytes)
}
return h.Sum(nil)
}
// NewPublicKeyCaveat returns a new third-party caveat from the provided restriction,
// third-party identity, and third-party location.
func NewPublicKeyCaveat(dischargeMintingCaveat security.Caveat, thirdParty security.PublicID, location string, requirements security.ThirdPartyRequirements) (security.ThirdPartyCaveat, error) {
nonce := make([]uint8, nonceLength)
if _, err := rand.Read(nonce); err != nil {
return nil, err
}
var validationKey wire.PublicKey
if err := validationKey.Encode(thirdParty.PublicKey()); err != nil {
return nil, err
}
var mintingCaveatEncoded bytes.Buffer
vom.NewEncoder(&mintingCaveatEncoded).Encode(dischargeMintingCaveat)
return &publicKeyCaveat{
RandNonce: nonce,
DischargeMintingCaveat: mintingCaveatEncoded.Bytes(),
ValidationKey: validationKey,
ThirdPartyLocation: location,
ThirdPartyRequirements: requirements,
}, nil
}
// NewPublicKeyDischarge returns a new discharge for the provided caveat
// after verifying that the caveats for minting a discharge are met.
//
// The CaveatID of the discharge is the same as the ID of the caveat, and
// the discharge includes the provided service caveats along with a universal
// expiry caveat for the provided duration. The discharge also includes a
// signature over its contents obtained from the provided private key.
func NewPublicKeyDischarge(discharger security.PrivateID, caveat security.ThirdPartyCaveat, ctx security.Context, duration time.Duration, caveats []security.ServiceCaveat) (security.ThirdPartyDischarge, error) {
cav, ok := caveat.(*publicKeyCaveat)
if !ok {
return nil, fmt.Errorf("cannot mint discharges for %T", caveat)
}
var mintingCaveat security.Caveat
mcBuf := bytes.NewReader(cav.DischargeMintingCaveat)
if err := vom.NewDecoder(mcBuf).Decode(&mintingCaveat); err != nil {
return nil, fmt.Errorf("failed to decode DischargeMintingCaveat: %s", err)
}
if err := mintingCaveat.Validate(ctx); err != nil {
return nil, fmt.Errorf("failed to validate DischargeMintingCaveat: %s", err)
}
now := time.Now()
expiryCaveat := &Expiry{IssueTime: now, ExpiryTime: now.Add(duration)}
caveats = append(caveats, UniversalCaveat(expiryCaveat))
encodedCaveats, err := wire.EncodeCaveats(caveats)
if err != nil {
return nil, fmt.Errorf("failed to encode caveats in discharge: %v", err)
}
discharge := &publicKeyDischarge{
ThirdPartyCaveatID: caveat.ID(),
Caveats: encodedCaveats,
}
// TODO(ashankar,ataly): Should discharger necessarily be the same as ctx.LocalID()?
// If so, need the PrivateID object corresponding to ctx.LocalID.
if err := discharge.sign(discharger); err != nil {
return nil, err
}
return discharge, nil
}
// id calculates the sha256 hash of length-delimited byte slices
func id(slices ...[]byte) string {
h := sha256.New()
tmp := make([]byte, binary.MaxVarintLen64)
for _, slice := range slices {
wire.WriteBytes(h, tmp, slice)
}
return string(h.Sum(nil))
}
func init() {
vom.Register(publicKeyCaveat{})
vom.Register(publicKeyDischarge{})
}