Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Replace SignedLogRoot.LogID with SignedLogRoot.KeyHint. #1049

Merged
merged 9 commits into from
Mar 19, 2018

Conversation

gdbelvin
Copy link
Contributor

@gdbelvin gdbelvin commented Mar 9, 2018

Trillian logs can misrepresent their LogID in SignedLogRoot. Clients SHOULD derive the LogID associated with a SignedLogRoot object via a valid signature with that log's public key.

In situations where a SignedLogRoot could be coming from any number of known logs, the KeyHint field is provided as a shortcut to quickly identify the correct public key to use for verification.

This PR associates LogID / KeyID with the signer object, which will automatically include it when generating a SignedLogRoot object.

#958

@codecov-io
Copy link

codecov-io commented Mar 9, 2018

Codecov Report

Merging #1049 into master will increase coverage by 0.02%.
The diff coverage is 68.18%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master    #1049      +/-   ##
==========================================
+ Coverage   62.63%   62.65%   +0.02%     
==========================================
  Files         103      103              
  Lines        8432     8448      +16     
==========================================
+ Hits         5281     5293      +12     
- Misses       2624     2627       +3     
- Partials      527      528       +1
Impacted Files Coverage Δ
log/sequencer.go 75.63% <0%> (ø) ⬆️
storage/mysql/log_storage.go 68.86% <100%> (ø) ⬆️
crypto/signer.go 69.23% <71.42%> (-5.24%) ⬇️
types/logroot.go 92.85% <81.81%> (-7.15%) ⬇️
util/tracker.go 100% <0%> (+3.38%) ⬆️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update b39433d...c867aa5. Read the comment docs.

@gdbelvin gdbelvin requested a review from daviddrysdale March 13, 2018 13:47
@daviddrysdale
Copy link
Contributor

'Diffbase' isn't really something that works with GitHub -- could you rebase and regenerate please?

@gdbelvin
Copy link
Contributor Author

It kind of works if one reviews commit by commit, but it's certainly not great.
Rebased and ready for your inspection.

@@ -357,9 +356,6 @@ func (tx *logTX) StoreSignedLogRoot(ctx context.Context, root trillian.SignedLog
return err
}

if got, want := root.LogId, tx.treeID; got != want {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now that we're not storing LogID inside LogRootV1 there is no longer duplicate fields to keep in sync.

Copy link
Contributor

@daviddrysdale daviddrysdale left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not really getting the general point here -- it looks like you're replacing an int encoded as an int with an int encoded as [8]byte; what's the gain?

crypto/signer.go Outdated
Hash crypto.Hash
Signer crypto.Signer
}

// NewSigner returns a new annotated signer.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Needs a comment to explain what "annotated" means.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done where?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Must have gotten lost. Fixed comment again.

crypto/signer.go Outdated
@@ -35,10 +35,20 @@ var sigpbHashLookup = map[crypto.Hash]sigpb.DigitallySigned_HashAlgorithm{
// Signer is responsible for signing log-related data and producing the appropriate
// application specific signature objects.
type Signer struct {
KeyID int64
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BTW, is this used for anything other than diagnostics/logging?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At the moment it's only used for logging to my knowledge.
In the future when we have gossip, this will help gossipers identify which public key to use to verify SignedLogRoots

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now I understand better I wonder: should this be KeyHint []byte ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That would be more consistent. Done.

crypto/signer.go Outdated
Signer: signer,
}
}

// NewSHA256Signer creates a new SHA256 based Signer.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this function going to stick around?

If it is, then it should probably get some comment to indicate that the resulting signed isn't associated with a particular key ID. Also, the changes to various ..._test.go files might not be needed.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would like to remove NewSha256Signer. I've got a PR queued up for the certificatetransparency-go repo, but obviously this PR needs to be merged first.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll add a TODO to remove it.

@@ -304,7 +304,9 @@ func createTree(ctx context.Context, db *sql.DB) (*trillian.Tree, error) {
{
ls := mysql.NewLogStorage(db, nil)
err := ls.ReadWriteTransaction(ctx, tree, func(ctx context.Context, tx storage.LogTreeTX) error {
return tx.StoreSignedLogRoot(ctx, trillian.SignedLogRoot{LogId: tree.TreeId, RootHash: []byte{0}, Signature: &sigpb.DigitallySigned{}})
return tx.StoreSignedLogRoot(ctx, trillian.SignedLogRoot{
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(nit) Although it's just a test, why not use types.SerializeKeyHint here too?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll use a proper signer.

@@ -260,7 +260,6 @@ func createLogForTests(db *sql.DB) int64 {
l := NewLogStorage(db, nil)
err = l.ReadWriteTransaction(ctx, tree, func(ctx context.Context, tx storage.LogTreeTX) error {
if err := tx.StoreSignedLogRoot(ctx, trillian.SignedLogRoot{
LogId: tree.TreeId,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As above.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

trillian.proto Outdated
@@ -217,8 +217,9 @@ message SignedLogRoot {
int64 tree_size = 3;
sigpb.DigitallySigned signature = 4;

int64 log_id = 5;
reserved 5; // (moved to key_hint)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is an API change - are you sure no Trillian users rely on the presence of log_id?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is an API change indeed. I'm a little less clear on whether there are no Trillian users that use log_id. I've run the ct-go unit tests against it, so I'm pretty confident that nothing in the Google ecosystem uses it. Not sure how to detect other uses.

trillian.proto Outdated
int64 tree_revision = 6;
bytes key_hint = 7;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The trillian.proto file is effectively the external API definition, so it would be good to have a comment explaining what's in the bytes, and what format it's in.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good idea.

@daviddrysdale
Copy link
Contributor

(and thanks for the rebase -- much easier to see diffs now)

@gdbelvin
Copy link
Contributor Author

The idea is that LogID can be maliciously set. In keeping with the philosophy that everything a Trillian Log produces should be verified, clients derive the logID from the public key associated with a valid signature.

Using []byte also allows the KeyHint to be generalized to include things other than LogIDs. Perhaps a GUID or something in the future or for non Trillian logs.

@daviddrysdale
Copy link
Contributor

OK, after some offline chats I think I'm catching up -- sorry for missing out on the background to all of this. Let me try explaining things back to see if I've got it right:

Thing A: as a general rule, it's safest to check a signature before doing any decoding, so you only decode things that are trusted. So we should be shipping things around the place as blob + signature-of-blob, and only after checking signature-of-blob is it safe to (say) TLS-decode blob into a structure.

But which public key do you use to verify the signature?

From the perspective of a Trillian client getting an RPC response, this is actually easy: the client only gets back a signed log root (SLR) as a response from a Trillian server after a request for a specific tree, so the client only ever tries to verify the signature with the public key associated with that <trillian-server + treeID>. Easy peasy, O(1), and no need for any identification of which tree is involved.

However, these SLRs may get gossiped around the place, at which point anyone who wants to look at them after the fact doesn't know where they came from, and can't trust anyone who claims to tell them where they came from. So checking the signature (thing A) has the O(N) problem of trying every public key they know about.

Here's where the key hint helps, to make the normal case O(1) again: use the key hint to find the right public key, verify-sig then deserialize-blob. However, if we're dealing with a big pile of SLRs from all sorts of places, the treeID isn't enough as a unique identifier -- different Trillian servers can re-use the same treeIDs, so something like (say) <trillian-log-uri, treeID> might be needed.

In other words, the key hint is context-specific:

  • when talking (just) to a specific Trillian server, treeID is enough
  • when talking more widely, it will need to have a more globally unique identifier
    and so []byte allows for both possibilities.

This also means that the contents of the key hint will change as context changes. For example, a client of a specific Trillian server may get an SLR with a key hint, but when it forwards that SLR onwards into a bigger pool it would change the key hint to <serverURI, treeID> -- without needing to change the blob, signature-over-blob parts.

(Of course, anything that's having to parse a tree_hint will need to do so carefully in case of malicious contents, but any key hint format for a particular environment should be simple enough that it's possible to do safely...)

Is that roughly right?

@daviddrysdale
Copy link
Contributor

@gdbelvin
Copy link
Contributor Author

gdbelvin commented Mar 15, 2018

You got it. All of the above is correct. I'll add the following two thoughts:

  • We might want to discourage clients from ever parsing key_hint and only have them use it as an opaque string / lookup in a table.
  • Great observation about the contextual nature of LogID. In order to minimize mucking around as gossip goes forward we might want to set key_hint to be a GUID? This GUID and its relationship to the public key would have to be publically maintained somewhere. Alternatively we can make key_hint the first x bytes of the public key or some other function of the public key.

trillian.proto Outdated
// key_hint is a hint to identify the public key for signature verification.
// key_hint is not authenticated and may be incorrect or missing, in which
// case all known public keys may be used to verify the signature.
// key_hint MAY contain the LogID encoded as a big endian 64 bit integer.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe add something about context: "When directly communicating with a Trillian gRPC server, the key_hint will typically contain the LogID encoded as a big-endian 64-bit integer; however, in other contexts the key_hint is likely to have different contents (e.g. it could be Trillian URL + tree ID, or it could be data derived from the public key itself)."

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

@gdbelvin
Copy link
Contributor Author

Added additional key_hint context documentation.

@gdbelvin gdbelvin merged commit cf9cd7c into google:master Mar 19, 2018
@gdbelvin gdbelvin deleted the f/sig/keyhint branch March 19, 2018 12:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants