blob: d72aa872502e5e4bd95b7997887172ced46819ac [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
import (
"crypto/sha256"
"errors"
"fmt"
"strings"
"v.io/v23/context"
"v.io/v23/security"
"v.io/x/lib/ibe"
)
const hashTruncation = 16
// ibeEncrypter implements an security.BlessingsBasedEncrypter that
// uses the security.IBE cryptographic scheme.
type ibeEncrypter struct {
params ibe.Params
}
func (b *ibeEncrypter) Encrypt(ctx *context.T, forPatterns []security.BlessingPattern, plaintext *[32]byte) (*security.Ciphertext, error) {
ciphertext := &security.Ciphertext{
Scheme: security.IBE,
Ciphertexts: make(map[string][]byte),
}
if len(forPatterns) == 0 {
return ciphertext, nil
}
for _, p := range forPatterns {
ctxt := make([]byte, ibe.CiphertextSize)
if err := b.params.Encrypt(string(p), (*plaintext)[:], ctxt); err != nil {
return nil, NewErrInternal(ctx, err)
}
h := hash(p)
// Verify that the hash does not collide with the hashes of the patterns
// seen so far in this loop.
if _, ok := ciphertext.Ciphertexts[h]; ok {
return nil, NewErrInternal(ctx, fmt.Errorf("cannot encrypt as the hash of the pattern %v collides with one of the other patterns", p))
}
ciphertext.Ciphertexts[h] = ctxt
}
return ciphertext, nil
}
// ibeDecrypter implements a security.BlessingsBasedDecrypter that
// uses the security.IBE cryptographic scheme.
type ibeDecrypter struct {
keys map[string]ibe.PrivateKey
}
func (b *ibeDecrypter) Decrypt(ctx *context.T, ciphertext *security.Ciphertext) (*[32]byte, error) {
if ciphertext.Scheme != security.IBE {
return nil, NewErrInvalidScheme(ctx, int32(ciphertext.Scheme), []int32{int32(security.IBE)})
}
var (
key ibe.PrivateKey
keyFound bool
err error
plaintext [32]byte
)
for p, ctxt := range ciphertext.Ciphertexts {
key, keyFound = b.keys[p]
if !keyFound {
continue
}
if err = key.Decrypt(ctxt, plaintext[:]); err == nil {
break
}
}
if !keyFound {
return nil, NewErrPrivateKeyNotFound(ctx)
}
if err != nil {
return nil, NewErrInternal(ctx, err)
}
return &plaintext, nil
}
// NewIBEEncrypter constucts a new encrypter using the provided ibe.Params
// that uses the security.IBE cryptographic scheme.
func NewIBEEncrypter(params ibe.Params) security.BlessingsBasedEncrypter {
return &ibeEncrypter{params: params}
}
// NewIBEDecrypter constructs a new decrypter for the provided blessing using
// provided slice of IBE private keys corresponding to the blessing. The
// decrypter uses the security.IBE cryptographic scheme. See Also: ExtractPrivateKeys.
func NewIBEDecrypter(blessing string, privateKeys []ibe.PrivateKey) (security.BlessingsBasedDecrypter, error) {
if len(blessing) == 0 {
return nil, errors.New("blessing cannot be empty")
}
decrypter := &ibeDecrypter{keys: make(map[string]ibe.PrivateKey)}
patterns := matchedBy(blessing)
if got, want := len(privateKeys), len(patterns); got != want {
return nil, fmt.Errorf("got %d private keys for blessing %v, expected %d", got, blessing, want)
}
for i, p := range patterns {
decrypter.keys[hash(p)] = privateKeys[i]
}
return decrypter, nil
}
// ExtractPrivateKeys returns a slice of IBE private keys for the provided
// blessing, extracted using the provided IBE Master.
//
// The slice of private keys contains private keys extracted for each blessing
// pattern matched by the blessing (i.e., the blessing pattern string is
// the identity for which the private key is extracted). Furthermore, the private
// keys are organized in increasing order of the lengths of the corresponding
// patterns.
func ExtractPrivateKeys(master ibe.Master, blessing string) ([]ibe.PrivateKey, error) {
if len(blessing) == 0 {
return nil, errors.New("blessing must be non-empty")
}
patterns := matchedBy(blessing)
keys := make([]ibe.PrivateKey, len(patterns))
for i, p := range patterns {
ibeKey, err := master.Extract(string(p))
if err != nil {
return nil, err
}
keys[i] = ibeKey
}
return keys, nil
}
// matchedBy returns the set of blessing patterns (in increasing order
// of length) matched by the provided blessing. The provided blessing
// must be non-empty.
func matchedBy(blessing string) []security.BlessingPattern {
patterns := make([]security.BlessingPattern, strings.Count(blessing, security.ChainSeparator)+2)
patterns[len(patterns)-1] = security.BlessingPattern(blessing) + security.ChainSeparator + security.NoExtension
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
}
// hash returns a 128-bit truncated SHA-256 hash of a blessing pattern.
func hash(pattern security.BlessingPattern) string {
h := sha256.Sum256([]byte(pattern))
truncated := h[:hashTruncation]
return string(truncated)
}