Permalink
Browse files

Mutation improvements (#870)

* mutation.Check and SerializeAndSign(_, trustedTreeSize)

* Entry Mutation Improvements

- Add a sanity check to the sign operation.
- Selectively copy fields from previous entry.
- Check() now returns bool, error

* client.Retry operates on mutations

* fixup tests
  • Loading branch information...
gdbelvin committed Nov 30, 2017
1 parent a9b1090 commit 0cbe7d54a958ede85bd4c443fab4eaa155f0a051
@@ -39,7 +39,6 @@ import (
"github.com/google/trillian/crypto/keyspb"
"github.com/google/trillian/merkle/hashers"
"github.com/golang/protobuf/proto"
"google.golang.org/grpc"
gpb "github.com/google/keytransparency/core/proto/keytransparency_v1_grpc"
@@ -81,7 +80,6 @@ var (
type Client struct {
cli gpb.KeyTransparencyServiceClient
domainID string
vrf vrf.PublicKey
kt *kt.Verifier
mutator mutator.Mutator
RetryCount int
@@ -121,6 +119,7 @@ func NewFromConfig(cc *grpc.ClientConn, config *pb.GetDomainInfoResponse) (*Clie
return nil, fmt.Errorf("Error parsing vrf public key: %v", err)
}
// TODO(gbelvin): set retry delay.
logVerifier := client.NewLogVerifier(logHasher, logPubKey)
return New(cc, config.DomainId, vrfPubKey, mapPubKey, mapHasher, logVerifier), nil
}
@@ -135,7 +134,6 @@ func New(cc *grpc.ClientConn,
return &Client{
cli: gpb.NewKeyTransparencyServiceClient(cc),
domainID: domainID,
vrf: vrf,
kt: kt.New(vrf, mapHasher, mapPubKey, logVerifier),
mutator: entry.New(),
RetryCount: 1,
@@ -155,7 +153,7 @@ func (c *Client) GetEntry(ctx context.Context, userID, appID string, opts ...grp
return nil, nil, err
}
if err := c.kt.VerifyGetEntryResponse(ctx, userID, appID, &c.trusted, e); err != nil {
if err := c.kt.VerifyGetEntryResponse(ctx, c.domainID, appID, userID, &c.trusted, e); err != nil {
return nil, nil, err
}
@@ -199,7 +197,7 @@ func (c *Client) ListHistory(ctx context.Context, userID, appID string, start, e
for i, v := range resp.GetValues() {
Vlog.Printf("Processing entry for %v, epoch %v", userID, start+int64(i))
err = c.kt.VerifyGetEntryResponse(ctx, userID, appID, &c.trusted, v)
err = c.kt.VerifyGetEntryResponse(ctx, c.domainID, appID, userID, &c.trusted, v)
if err != nil {
return nil, err
}
@@ -230,9 +228,9 @@ func (c *Client) ListHistory(ctx context.Context, userID, appID string, start, e
// Update creates an UpdateEntryRequest for a user, attempt to submit it multiple
// times depending on RetryCount.
func (c *Client) Update(ctx context.Context, userID, appID string, profileData []byte,
func (c *Client) Update(ctx context.Context, appID, userID string, profileData []byte,
signers []signatures.Signer, authorizedKeys []*keyspb.PublicKey,
opts ...grpc.CallOption) (*pb.UpdateEntryRequest, error) {
opts ...grpc.CallOption) (*entry.Mutation, error) {
getResp, err := c.cli.GetEntry(ctx, &pb.GetEntryRequest{
DomainId: c.domainID,
UserId: userID,
@@ -244,58 +242,54 @@ func (c *Client) Update(ctx context.Context, userID, appID string, profileData [
}
Vlog.Printf("Got current entry...")
if err := c.kt.VerifyGetEntryResponse(ctx, userID, appID, &c.trusted, getResp); err != nil {
if err := c.kt.VerifyGetEntryResponse(ctx, c.domainID, appID, userID, &c.trusted, getResp); err != nil {
return nil, fmt.Errorf("VerifyGetEntryResponse(): %v", err)
}
req, err := kt.CreateUpdateEntryRequest(&c.trusted, getResp, c.vrf, c.domainID, userID, appID, profileData, signers, authorizedKeys)
m, err := c.kt.NewMutation(c.domainID, appID, userID, profileData, authorizedKeys,
getResp.GetVrfProof(), getResp.GetLeafProof().GetLeaf().GetLeafValue())
if err != nil {
return nil, fmt.Errorf("CreateUpdateEntryRequest: %v", err)
}
oldLeafB := getResp.GetLeafProof().GetLeaf().GetLeafValue()
oldLeaf, err := entry.FromLeafValue(oldLeafB)
if err != nil {
return nil, fmt.Errorf("entry.FromLeafValue: %v", err)
}
if _, err := c.mutator.Mutate(oldLeaf, req.GetEntryUpdate().GetMutation()); err != nil {
return nil, fmt.Errorf("Mutate: %v", err)
}
err = c.Retry(ctx, req, opts...)
err = c.Retry(ctx, m, signers, opts...)
// Retry submitting until an inclusion proof is returned.
for i := 0; err == ErrRetry && i < c.RetryCount; i++ {
time.Sleep(c.RetryDelay)
err = c.Retry(ctx, req, opts...)
err = c.Retry(ctx, m, signers, opts...)
}
return req, err
return m, err
}
// Retry will take a pre-fabricated request and send it again.
func (c *Client) Retry(ctx context.Context, req *pb.UpdateEntryRequest, opts ...grpc.CallOption) error {
// Retry takes take a mutation, signs, and sends it again, and updates the back pointer with the current leaf value.
func (c *Client) Retry(ctx context.Context, m *entry.Mutation, signers []signatures.Signer, opts ...grpc.CallOption) error {
req, err := m.SerializeAndSign(signers, c.trusted.TreeSize)
if err != nil {
return fmt.Errorf("SerializeAndSign(): %v", err)
}
Vlog.Printf("Sending Update request...")
updateResp, err := c.cli.UpdateEntry(ctx, req, opts...)
if err != nil {
return err
return fmt.Errorf("cli.UpdateEntry(): %v", err)
}
Vlog.Printf("Got current entry...")
// Validate response.
if err := c.kt.VerifyGetEntryResponse(ctx, req.UserId, req.AppId, &c.trusted, updateResp.GetProof()); err != nil {
if err := c.kt.VerifyGetEntryResponse(ctx, c.domainID, req.AppId, req.UserId, &c.trusted, updateResp.GetProof()); err != nil {
return fmt.Errorf("VerifyGetEntryResponse(): %v", err)
}
// Mutations are no longer stable serialized byte slices, so we need to use
// an equality operation on the proto itself.
leafValue, err := entry.FromLeafValue(
updateResp.GetProof().GetLeafProof().GetLeaf().GetLeafValue())
cntLeaf := updateResp.GetProof().GetLeafProof().GetLeaf().GetLeafValue()
equal, err := m.Check(cntLeaf)
if err != nil {
return fmt.Errorf("failed to decode current entry: %v", err)
return fmt.Errorf("mutation.Check(): %v", err)
}
// Check if the response is a replay.
if got, want := leafValue, req.GetEntryUpdate().GetMutation(); !proto.Equal(got, want) {
if err := m.SetPrevious(cntLeaf, false); err != nil {
return fmt.Errorf("mutation.SetPrevious(): %v", err)
}
if !equal {
return ErrRetry
}
return nil
// TODO: Update previous entry pointer
}
View
@@ -18,51 +18,45 @@ package kt
import (
"fmt"
"github.com/google/keytransparency/core/crypto/signatures"
"github.com/google/keytransparency/core/crypto/vrf"
"github.com/google/keytransparency/core/mutator/entry"
"github.com/google/trillian/crypto/keyspb"
"github.com/google/trillian"
tpb "github.com/google/keytransparency/core/proto/keytransparency_v1_proto"
)
// CreateUpdateEntryRequest creates UpdateEntryRequest given GetEntryResponse,
// user ID and a profile.
func CreateUpdateEntryRequest(
trusted *trillian.SignedLogRoot, getResp *tpb.GetEntryResponse,
vrfPub vrf.PublicKey, domainID, userID, appID string, profileData []byte,
signers []signatures.Signer, authorizedKeys []*keyspb.PublicKey) (*tpb.UpdateEntryRequest, error) {
// Extract index from a prior GetEntry call.
index, err := vrfPub.ProofToHash(vrf.UniqueID(userID, appID), getResp.VrfProof)
func (v *Verifier) index(vrfProof []byte, domainID, appID, userID string) ([]byte, error) {
uid := vrf.UniqueID(userID, appID)
index, err := v.vrf.ProofToHash(uid, vrfProof)
if err != nil {
return nil, fmt.Errorf("ProofToHash(): %v", err)
return nil, fmt.Errorf("vrf.ProofToHash(%v, %v): %v", appID, userID, err)
}
return index[:], nil
}
oldLeaf := getResp.GetLeafProof().GetLeaf().GetLeafValue()
mutation := entry.NewMutation(index[:], domainID, appID, userID)
if err := mutation.SetPrevious(oldLeaf); err != nil {
return nil, fmt.Errorf("Error unmarshaling Entry from leaf proof: %v", err)
// NewMutation creates a Mutation given the userID, desired state, and previous entry.
func (v *Verifier) NewMutation(
domainID, appID, userID string,
profileData []byte, authorizedKeys []*keyspb.PublicKey,
vrfProof, oldLeaf []byte) (
*entry.Mutation, error) {
index, err := v.index(vrfProof, domainID, appID, userID)
if err != nil {
return nil, err
}
mutation := entry.NewMutation(index, domainID, appID, userID)
if err := mutation.SetPrevious(oldLeaf, true); err != nil {
return nil, err
}
// Update Commitment.
if err := mutation.SetCommitment(profileData); err != nil {
return nil, err
}
// Update Authorization.
if len(authorizedKeys) != 0 {
if err := mutation.ReplaceAuthorizedKeys(authorizedKeys); err != nil {
return nil, err
}
}
// Sign Entry
updateRequest, err := mutation.SerializeAndSign(signers)
if err != nil {
return nil, err
}
updateRequest.FirstTreeSize = trusted.TreeSize
return updateRequest, nil
return mutation, nil
}
View
@@ -72,7 +72,7 @@ func New(vrf vrf.PublicKey,
// - Verify signature.
// - Verify consistency proof from log.Root().
// - Verify inclusion proof.
func (v *Verifier) VerifyGetEntryResponse(ctx context.Context, userID, appID string,
func (v *Verifier) VerifyGetEntryResponse(ctx context.Context, domainID, appID, userID string,
trusted *trillian.SignedLogRoot, in *tpb.GetEntryResponse) error {
// Unpack the merkle tree leaf value.
e, err := entry.FromLeafValue(in.GetLeafProof().GetLeaf().GetLeafValue())
@@ -93,10 +93,10 @@ func (v *Verifier) VerifyGetEntryResponse(ctx context.Context, userID, appID str
}
Vlog.Printf("✓ Commitment verified.")
index, err := v.vrf.ProofToHash(vrf.UniqueID(userID, appID), in.GetVrfProof())
index, err := v.index(in.GetVrfProof(), domainID, appID, userID)
if err != nil {
Vlog.Printf("✗ VRF verification failed.")
return fmt.Errorf("vrf.ProofToHash(%v, %v): %v", userID, appID, err)
return err
}
Vlog.Printf("✓ VRF verified.")
@@ -35,7 +35,8 @@ import (
)
var (
VRFPub = []byte(`-----BEGIN PUBLIC KEY-----
domainID = "default"
VRFPub = []byte(`-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE5AV2WCmStBt4N2Dx+7BrycJFbxhW
f5JqSoyp0uiL8LeNYyj5vgklK8pLcyDbRqch9Az8jXVAmcBAkvaSrLW8wQ==
-----END PUBLIC KEY-----`)
@@ -148,7 +149,7 @@ func TestVerifyGetEntryResponse(t *testing.T) {
},
},
} {
err := v.VerifyGetEntryResponse(ctx, tc.userID, tc.appID, tc.trusted, tc.in)
err := v.VerifyGetEntryResponse(ctx, domainID, tc.appID, tc.userID, tc.trusted, tc.in)
if got, want := err != nil, tc.wantErr; got != want {
t.Errorf("VerifyGetEntryResponse(%v, %v, %v, %v): %t, wantErr %t (err=%v)",
tc.userID, tc.appID, tc.trusted, tc.in, got, want, err)
@@ -15,6 +15,9 @@
package entry
import (
"fmt"
"github.com/golang/protobuf/proto"
"github.com/google/keytransparency/core/crypto/commitments"
"github.com/google/keytransparency/core/crypto/signatures"
"github.com/google/keytransparency/core/mutator"
@@ -56,8 +59,8 @@ func NewMutation(index []byte, domainID, appID, userID string) *Mutation {
}
// SetPrevious sets the previous hash.
// Also sets AuthorizedKeys and Commitment.
func (m *Mutation) SetPrevious(oldValue []byte) error {
// If copyPrevious is true, AuthorizedKeys and Commitment are also copied.
func (m *Mutation) SetPrevious(oldValue []byte, copyPrevious bool) error {
prevEntry, err := FromLeafValue(oldValue)
if err != nil {
return err
@@ -74,8 +77,10 @@ func (m *Mutation) SetPrevious(oldValue []byte) error {
m.prevEntry = prevEntry
m.entry.Previous = hash[:]
m.entry.AuthorizedKeys = prevEntry.GetAuthorizedKeys()
m.entry.Commitment = prevEntry.GetCommitment()
if copyPrevious {
m.entry.AuthorizedKeys = prevEntry.GetAuthorizedKeys()
m.entry.Commitment = prevEntry.GetCommitment()
}
return nil
}
@@ -103,28 +108,35 @@ func (m *Mutation) ReplaceAuthorizedKeys(pubkeys []*keyspb.PublicKey) error {
}
// SerializeAndSign produces the mutation.
func (m *Mutation) SerializeAndSign(signers []signatures.Signer) (*pb.UpdateEntryRequest, error) {
signedkv, err := m.sign(signers)
func (m *Mutation) SerializeAndSign(signers []signatures.Signer, trustedTreeSize int64) (*pb.UpdateEntryRequest, error) {
mutation, err := m.sign(signers)
if err != nil {
return nil, err
}
// Check authorization.
skv := *signedkv
skv := *mutation
skv.Signatures = nil
if err := verifyKeys(m.prevEntry.GetAuthorizedKeys(),
m.entry.GetAuthorizedKeys(),
skv,
signedkv.GetSignatures()); err != nil {
return nil, err
mutation.GetSignatures()); err != nil {
return nil, fmt.Errorf("verifyKeys(prevauth: %v, newauth: %v, sig: %v): %v",
len(m.prevEntry.GetAuthorizedKeys()), len(m.entry.GetAuthorizedKeys()), len(mutation.GetSignatures()), err)
}
// Sanity check the mutation's correctness.
if _, err := New().Mutate(m.prevEntry, mutation); err != nil {
return nil, fmt.Errorf("presign mutation check: %v", err)
}
return &pb.UpdateEntryRequest{
DomainId: m.domainID,
UserId: m.userID,
AppId: m.appID,
DomainId: m.domainID,
UserId: m.userID,
AppId: m.appID,
FirstTreeSize: trustedTreeSize,
EntryUpdate: &pb.EntryUpdate{
Mutation: signedkv,
Mutation: mutation,
Committed: &pb.Committed{
Key: m.nonce,
Data: m.data,
@@ -133,7 +145,7 @@ func (m *Mutation) SerializeAndSign(signers []signatures.Signer) (*pb.UpdateEntr
}, nil
}
// Sign produces the SignedKV
// Sign produces the mutation
func (m *Mutation) sign(signers []signatures.Signer) (*pb.Entry, error) {
m.entry.Signatures = nil
sigs := make(map[string]*sigpb.DigitallySigned)
@@ -148,3 +160,17 @@ func (m *Mutation) sign(signers []signatures.Signer) (*pb.Entry, error) {
m.entry.Signatures = sigs
return m.entry, nil
}
// Check verifies that an update was successfully applied.
// Returns nil if newLeaf is equal to the entry in this mutation.
func (m *Mutation) Check(newLeaf []byte) (bool, error) {
// TODO(gbelvin): Figure out reliable object comparison.
// Mutations are no longer stable serialized byte slices, so we need to
// use an equality operation on the proto itself.
leafValue, err := FromLeafValue(newLeaf)
if err != nil {
return false, fmt.Errorf("failed to decode current entry: %v", err)
}
return proto.Equal(leafValue, m.entry), nil
}
Oops, something went wrong.

0 comments on commit 0cbe7d5

Please sign in to comment.