blob: 752a54e6c335f7b1d894fa5e9f94b473314d09b7 [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 bcrypter defines the mechanisms for blessings based
// encryption and decryption.
package bcrypter
import (
"crypto/sha256"
"fmt"
"strings"
"sync"
"v.io/v23/context"
"v.io/v23/security"
"v.io/x/lib/ibe"
)
type crypterKey struct{}
const hashTruncation = 16
// Crypter provides operations for encrypting and decrypting messages for
// principals with specific blessings.
//
// In particular, it offers a mechanism to encrypt a message for a specific
// blessing pattern so that it can only be decrypted by crypters that possess
// a private key for a blessing matched by that pattern. Such a private key
// is generated by the identity provider that granted the blessing.
type Crypter struct {
mu sync.RWMutex
// root blessing -> []ibe.Params
params map[string][]ibe.Params
// paramsId -> patternId -> ibe.PrivateKey
keys map[string]map[string]ibe.PrivateKey
}
// Ciphertext represents the ciphertext generated by a Crypter.
type Ciphertext struct {
wire WireCiphertext
}
// Root represents an identity provider for the purposes of blessings based
// encryption and decryption.
//
// It generates private keys for specific blessings which can be used
// to decrypt any message encrypted for a pattern matched by the blessing (
// assuming the encryption used this identity provider's parameters).
type Root struct {
// master is the IBE Master that this root uses to extract IBE
// private keys.
master ibe.Master
// blessing is the blessing name of the identity provider. The identity
// provider can extract private keys for blessings that are extensions
// of this blessing name.
blessing string
}
// Params represents the public parameters of an identity provider (aka Root).
type Params struct {
// blessing is the blessing name of the identity provider.
blessing string
// params are the public IBE params of the identity provider
params ibe.Params
}
// PrivateKey represent the private key corresponding to a blessing.
//
// The private key can be used for decrypting any message encrypted using
// a pattern matched by the blessing (assuming the private key and encryption
// used the same identity provider parameters).
type PrivateKey struct {
// blessing is the blessing for which this private key was extracted for.
blessing string
// params represents the public parameters of the identity provider
// that extracted this private key. The blessing must be an extension
// of params.blessing.
params Params
// keys contain private keys extracted for each blessing pattern that is
// matched by the blessing and is an extension of root.blessing.
//
// For example, if the blessing is "google:u:alice:phone" and root.blessing
// is "google:u" then the keys are extracted for patterns "google:u",
// "google:u:alice", "google:u:alice:phone", and "google:u:alice:phone:$".
//
// The private keys are listed in increasing order of the lengths of the
// corresponding patterns.
keys []ibe.PrivateKey
}
// Encrypt encrypts the provided 'plaintext' so that it can only be decrypted
// by a crypter possessing a private key for a blessing matching the provided
// blessing pattern.
//
// Encryption makes use of the public parameters of the identity provider
// that is authoritative on the set of blessings that match the provided
// blessing pattern. These paramaters must have been previously added to
// this crypter via AddParams.
func (c *Crypter) Encrypt(ctx *context.T, forPattern security.BlessingPattern, plaintext []byte) (*Ciphertext, error) {
if !forPattern.IsValid() {
return nil, fmt.Errorf("provided blessing pattern %v is invalid", forPattern)
}
ciphertext := &Ciphertext{wire: WireCiphertext{PatternId: idPattern(forPattern), Bytes: make(map[string][]byte)}}
paramsFound := false
c.mu.RLock()
defer c.mu.RUnlock()
for name, ibeParamsList := range c.params {
if !isExtensionOf(forPattern, name) {
continue
}
for _, ibeParams := range ibeParamsList {
ctxt := make([]byte, len(plaintext)+ibeParams.CiphertextOverhead())
if err := ibeParams.Encrypt(string(forPattern), plaintext, ctxt); err != nil {
return nil, NewErrInternal(ctx, err)
}
paramsId, err := idParams(ibeParams)
if err != nil {
return nil, NewErrInternal(ctx, err)
}
paramsFound = true
ciphertext.wire.Bytes[paramsId] = ctxt
}
}
if !paramsFound {
return nil, NewErrNoParams(ctx, forPattern)
}
return ciphertext, nil
}
func decrypt(key ibe.PrivateKey, ciphertext []byte) ([]byte, error) {
overhead := key.Params().CiphertextOverhead()
if got := len(ciphertext); got < overhead {
return nil, fmt.Errorf("ciphertext is of size %v bytes, want at least %v bytes", got, overhead)
}
plaintext := make([]byte, len(ciphertext)-overhead)
if err := key.Decrypt(ciphertext, plaintext); err != nil {
return nil, err
}
return plaintext, nil
}
// Decrypt decrypts the provided 'ciphertext' and returns the corresponding
// plaintext.
//
// Decryption succeeds only if this crypter possesses a private key for a
// blessing that matches the blessing pattern corresponding to the ciphertext.
func (c *Crypter) Decrypt(ctx *context.T, ciphertext *Ciphertext) ([]byte, error) {
c.mu.RLock()
defer c.mu.RUnlock()
for paramsId, cbytes := range ciphertext.wire.Bytes {
if keys, found := c.keys[paramsId]; !found {
continue
} else if key, found := keys[ciphertext.wire.PatternId]; !found {
continue
} else if ptxt, err := decrypt(key, cbytes); err != nil {
return nil, err
} else {
return ptxt, nil
}
}
return nil, NewErrPrivateKeyNotFound(ctx)
}
// AddKey adds the provided private key 'key' and the associated public
// parameters (key.Params()) to this crypter.
func (c *Crypter) AddKey(ctx *context.T, key *PrivateKey) error {
patterns := matchedBy(key.blessing, key.params.blessing)
if got, want := len(key.keys), len(patterns); got != want {
return NewErrInvalidPrivateKey(ctx, fmt.Errorf("got %d IBE private keys for blessing %v (and root blessing %v), expected %d", got, key.blessing, key.params.blessing, want))
}
paramsId, err := idParams(key.params.params)
if err != nil {
return NewErrInternal(ctx, err)
}
c.mu.Lock()
defer c.mu.Unlock()
c.params[key.params.blessing] = append(c.params[key.params.blessing], key.params.params)
if _, found := c.keys[paramsId]; !found {
c.keys[paramsId] = make(map[string]ibe.PrivateKey)
}
for i, p := range patterns {
c.keys[paramsId][idPattern(p)] = key.keys[i]
}
return nil
}
// AddParams adds the provided identity provider parameters to this crypter.
//
// The added parameters would be used to encrypt plaintexts for blessing patterns
// that the identity provider is authoritative on.
func (c *Crypter) AddParams(ctx *context.T, params Params) error {
c.mu.RLock()
defer c.mu.RUnlock()
// TODO(ataly, ashankar): Avoid adding duplicate params to the list.
c.params[params.blessing] = append(c.params[params.blessing], params.params)
return nil
}
// Blessing returns the blessing that this private key was extracted for.
func (k *PrivateKey) Blessing() string {
return k.blessing
}
// Params returns the public parameters of the identity provider that
// extracted this private key.
func (k *PrivateKey) Params() Params {
return k.params
}
// Params returns the public parameters of the identity provider represented
// by 'r'.
func (r *Root) Params() Params {
return Params{blessing: r.blessing, params: r.master.Params()}
}
// Extract returns a private key for the provided blessing.
//
// The private key can be used for decrypting any message encrypted using a
// pattern matched by the blessing (assuming the encryption made use of the
// public parameters of this root).
func (r *Root) Extract(ctx *context.T, blessing string) (*PrivateKey, error) {
patterns := matchedBy(blessing, r.blessing)
if len(patterns) == 0 {
return nil, fmt.Errorf("blessing %v does not match the blessing pattern this root is authoritative on: %v", blessing, r.blessing)
}
key := &PrivateKey{
blessing: blessing,
params: r.Params(),
keys: make([]ibe.PrivateKey, len(patterns)),
}
for i, p := range patterns {
ibeKey, err := r.master.Extract(string(p))
if err != nil {
return nil, NewErrInternal(ctx, err)
}
key.keys[i] = ibeKey
}
return key, nil
}
// Blessing returns the blessing name of the identity provider with
// public parameters 'p'.
func (p *Params) Blessing() string {
return p.blessing
}
// NewCrypter returns a new Crypter with an empty set of private keys
// and identity provider parameters.
func NewCrypter() *Crypter {
return &Crypter{params: make(map[string][]ibe.Params), keys: make(map[string]map[string]ibe.PrivateKey)}
}
// WithCrypter derives a new context from the provided one by attaching
// the provided crypter to it.
func WithCrypter(ctx *context.T, crypter *Crypter) *context.T {
return context.WithValue(ctx, crypterKey{}, crypter)
}
// GetCrypter returns the crypter attached to the context, or nil if no
// crypter is attached.
func GetCrypter(ctx *context.T) *Crypter {
crypter, _ := ctx.Value(crypterKey{}).(*Crypter)
return crypter
}
// NewRoot returns a new root identity provider that has the provided
// blessing name and uses the provided 'master' for setting up identity-based
// encryption.
func NewRoot(blessing string, master ibe.Master) *Root {
return &Root{blessing: blessing, master: master}
}
// matchedBy returns the set of blessing patterns (in increasing order
// of length) that are matched by the provided 'blessing' and are equal
// to or extensions of the blessing name 'root'.
func matchedBy(blessing, root string) []security.BlessingPattern {
if !security.BlessingPattern(root).MatchedBy(blessing) {
return nil
}
patterns := make([]security.BlessingPattern, strings.Count(blessing, security.ChainSeparator)+2-strings.Count(string(root), security.ChainSeparator))
patterns[len(patterns)-1] = security.BlessingPattern(blessing).MakeNonExtendable()
patterns[len(patterns)-2] = security.BlessingPattern(blessing)
for idx := len(patterns) - 3; idx >= 0; idx-- {
blessing = blessing[0:strings.LastIndex(blessing, string(security.ChainSeparator))]
patterns[idx] = security.BlessingPattern(blessing)
}
return patterns
}
// idPattern returns a 128-bit truncated SHA-256 hash of a blessing pattern.
func idPattern(pattern security.BlessingPattern) string {
h := sha256.Sum256([]byte(pattern))
truncated := h[:hashTruncation]
return string(truncated)
}
// idParams returns a 128-bit truncated SHA-256 hash of the marshaled IBE params.
func idParams(params ibe.Params) (string, error) {
paramsBytes, err := ibe.MarshalParams(params)
if err != nil {
return "", err
}
h := sha256.Sum256(paramsBytes)
truncated := h[:hashTruncation]
return string(truncated), nil
}
// isExtensionOf returns true if the all blessings matching the provided
// 'pattern' are an extension of the provided 'root' blessing
func isExtensionOf(pattern security.BlessingPattern, root string) bool {
return string(pattern) == root || strings.HasPrefix(string(pattern), root+security.ChainSeparator)
}