-
Notifications
You must be signed in to change notification settings - Fork 512
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
Support for initializing repository with root certificate #1144
Conversation
Can one of the admins verify this patch? |
Please sign your commits following these rules: $ git clone -b "cyc_rootcert" git@github.com:cyc115/notary.git somewhere
$ cd somewhere
$ git rebase -i HEAD~842354469616
editor opens
change each 'pick' to 'edit'
save the file and quit
$ git commit --amend -s --no-edit
$ git rebase --continue # and repeat the amend for each commit
$ git push -f Amending updates the existing PR. You DO NOT need to open a new one. |
3b374bd
to
364a531
Compare
jenkins, test this please |
I think it probably makes sense to allow The behavior in the case that only a |
@endophage - even if it were on a Yubikey/HSM, it seems one might still need to pass at least a KeyID, if not the path to the cert. Or are you thinking that it would follow the current algorithm and take the first root key found? Also are you proposing that this be added to this PR, or would you be okay with it being done as part of a separate PR? |
The KeyID can be generated from the certificate. KeyIDs are the SHA256 checksum of the following canonically serialized JSON object:
More recent versions of the TUF spec have been updated to simply exclude the empty |
@endophage great advice David! I have updated the PR to allow init with |
238a381
to
5c1e88f
Compare
78aee10
to
51d2943
Compare
jenkins, test this please |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
thank you @cyc115 for the contribution! I've done a first pass and have the following comments.
I'd also like to figure out if there's a way for us to clean up and consolidate matchKeyIdsWithCerts
and keyExistsInList
though I haven't come to a concrete suggestion yet
client/client.go
Outdated
@@ -14,6 +14,7 @@ import ( | |||
|
|||
"github.com/Sirupsen/logrus" | |||
"github.com/docker/notary" | |||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit whitespace
client/client.go
Outdated
if err != nil { | ||
return err | ||
} | ||
// repoInitialize initializes the notary repository wit a set of rootkeys, root certificates and roles. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
typo: wit
cmd/notary/tuf_test.go
Outdated
"net/http" | ||
"net/http/httptest" | ||
"net/url" | ||
"os" | ||
"path/filepath" | ||
"testing" | ||
|
||
"strings" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: can this go into the above block?
tuf/signed/verify.go
Outdated
|
||
// VerifyPublicKeyMatchesPrivateKey checks if the private key and the public keys forms valid key pairs. | ||
// This should work with both ecdsa-x509 certificate PublicKey as well as ecdsa PublicKey | ||
func VerifyPublicKeyMatchesPrivateKey(privKey data.PrivateKey, pubKey data.PublicKey) error { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes I agree! I have come across utils.CanonicalKeyID()
and thought maybe that will be a good way of comparing key pair since it will extract the key id of the public key embedded in the certificate which should match privateKey.ID().
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this looks great now, thanks! 👍
client/client.go
Outdated
@@ -15,6 +15,7 @@ import ( | |||
"github.com/Sirupsen/logrus" | |||
canonicaljson "github.com/docker/go/canonical/json" | |||
"github.com/docker/notary" | |||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit whitespace
client/client.go
Outdated
@@ -256,6 +246,116 @@ func (r *NotaryRepository) Initialize(rootKeyIDs []string, serverManagedRoles .. | |||
return r.saveMetadata(serverManagesSnapshot) | |||
} | |||
|
|||
// certsOfKeyIDs either confirms that the certs and keys (represented by Key IDs) forms valid key pairs. | |||
// Or throw error when they missmatch. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
typo: mismatch
client/client.go
Outdated
@@ -256,6 +246,116 @@ func (r *NotaryRepository) Initialize(rootKeyIDs []string, serverManagedRoles .. | |||
return r.saveMetadata(serverManagesSnapshot) | |||
} | |||
|
|||
// certsOfKeyIDs either confirms that the certs and keys (represented by Key IDs) forms valid key pairs. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we document that the lists are strictly ordered in pairs? Ex: keyIDs[0] and certs[0] must match?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes :)
client/client.go
Outdated
@@ -168,24 +170,14 @@ func rootCertKey(gun data.GUN, privKey data.PrivateKey) (data.PublicKey, error) | |||
|
|||
x509PublicKey := utils.CertToKey(cert) | |||
if x509PublicKey == nil { | |||
return nil, fmt.Errorf( | |||
"cannot use regenerated certificate: format %s", cert.PublicKeyAlgorithm) | |||
return nil, fmt.Errorf("cannot use regenerated certificate: format %d", cert.PublicKeyAlgorithm) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nice catch on the type, though I'm wondering if we should instead print the privKey.Algorithm()
string since that will be more descriptive?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed. How is
return nil, fmt.Errorf("cannot generate public key from private key with id: %v. %v is not a supported type", privKey.ID(), privKey.Algorithm())
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
that would be great! I'd simplify slightly to something more generic so that it isn't tightly bound to the algorithm being the error returned from CertToKey
:
return nil, fmt.Errorf("cannot generate public key from private key with id: %v and algorithm: %v", privKey.ID(), privKey.Algorithm())
client/client.go
Outdated
return id, nil | ||
} | ||
} | ||
return "", nil |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should this just return an error? You could use a typed error or fmt.Errorf(...)
so that error is differentiated from the other error case
cmd/notary/tuf_test.go
Outdated
"net/http" | ||
"net/http/httptest" | ||
"net/url" | ||
"os" | ||
"path/filepath" | ||
"testing" | ||
|
||
"strings" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: same here
@cyli Ying the pipe line seems to be blocked again :P ? |
@cyc115 yeah, it's at the point we need to plug in a keyboard and monitor to fix it :-( We're at PyCon right now but somebody will look at it next week. |
@cyc115: we're diagnosing the CI issue still, in the meantime could you please squash your commits? edit: should be fixed now! |
jenkins, test this please |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is really close @cyc115 - thank you for the hard work! I have some fix comments and it would be great if you could rebase and squash commits as well.
client/client.go
Outdated
@@ -256,6 +251,111 @@ func (r *NotaryRepository) Initialize(rootKeyIDs []string, serverManagedRoles .. | |||
return r.saveMetadata(serverManagesSnapshot) | |||
} | |||
|
|||
// createNewPublicKeyFromKeyIDs generate a set of public keys corresponding to the given list of |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit typo: generates
client/client.go
Outdated
return errKeyNotFound{} | ||
} | ||
|
||
// InitializeWithCertificate initializes the repository with root key and their corresponding certificates |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: s/root key/specified root keys
client/client_test.go
Outdated
args args | ||
wantErr bool | ||
}{ | ||
// TODO: Add test cases. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
which test cases are you planning on placing in here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@riyazdf good catch! I think it was generated by accident and checked into the code. I have gone over existing tests and they are not needed. :) Thanks!
client/client_test.go
Outdated
wantTimestamp data.BaseRole | ||
wantErr bool | ||
}{ | ||
// TODO: Add test cases. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same comment: which test cases are you planning on placing in here?
cmd/notary/integration_test.go
Outdated
"init", gun+"4", | ||
"--rootkey", nonMatchingKeyFilename, | ||
"--rootcert", certFilename) | ||
require.Error(t, err, "should not be able to init a repository with missmatched key and cert") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit typo: mismatched
cross.Dockerfile
Outdated
@@ -1,4 +1,4 @@ | |||
FROM golang:1.7.3 | |||
FROM golang:1.8.1 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
could you please rebase? This should not be in this PR.
tuf/signed/sign_test.go
Outdated
@@ -208,7 +208,7 @@ func TestSignReturnsNoSigs(t *testing.T) { | |||
} | |||
|
|||
func TestSignWithX509(t *testing.T) { | |||
// generate a key becase we need a cert | |||
// generate a key because we need a cert |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
thanks for the typo fix! 👍
tuf/signed/verify.go
Outdated
|
||
// VerifyPublicKeyMatchesPrivateKey checks if the private key and the public keys forms valid key pairs. | ||
// This should work with both ecdsa-x509 certificate PublicKey as well as ecdsa PublicKey | ||
func VerifyPublicKeyMatchesPrivateKey(privKey data.PrivateKey, pubKey data.PublicKey) error { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this looks great now, thanks! 👍
Signed-off-by: Yuechuan Chen <yuechuan.chen@motorolasolutions.com>
Signed-off-by: Yuechuan Chen <yuechuan.chen@motorolasolutions.com>
Signed-off-by: Yuechuan Chen <yuechuan.chen@motorolasolutions.com>
Signed-off-by: Yuechuan Chen <yuechuan.chen@motorolasolutions.com>
Clean up, update tests, code refactoring and various fixes. Signed-off-by: Yuechuan Chen <yuechuan.chen@motorolasolutions.com>
Signed-off-by: Yuechuan Chen <yuechuan.chen@motorolasolutions.com>
…other minor changes. Signed-off-by: Yuechuan Chen <yuechuan.chen@motorolasolutions.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM after nits, thank you @cyc115 :)
cmd/notary/integration_test.go
Outdated
//set up temp dir | ||
tempDir := tempDirWithConfig(t, `{ | ||
"trust_pinning" : { | ||
"disable_tufu" : false |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
typo: "disable_tofu"
- this shouldn't affect behavior since disable_tofu
will disable to false
anyhow, but would be nice to be explicit here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hey there @riyazdf ! Thanks for helping me to cache all those typos :P, I will make sure to have those cached in advance for subsequent PRs !
cmd/notary/tuf.go
Outdated
@@ -133,6 +136,7 @@ type tufCommander struct { | |||
func (t *tufCommander) AddToCommand(cmd *cobra.Command) { | |||
cmdTUFInit := cmdTUFInitTemplate.ToCommand(t.tufInit) | |||
cmdTUFInit.Flags().StringVar(&t.rootKey, "rootkey", "", "Root key to initialize the repository with") | |||
cmdTUFInit.Flags().StringVar(&t.rootCert, "rootcert", "", "Root certificate to initialize the repository with") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should we note that if you provide a rootcert
that you must provide a matching root key?
ex: "Root certificate to initialize the repository with, must provide a matching root key"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It looks like a root cert could be provided without a root key, so long as that root cert corresponds to a private key that is in the existing keystore.
But if both a key and a cert are provided, they must match (e.g. the cert must correspond to the key provided). Agree with @riyazdf though that this would be good to include in the comment, since it's possible someone could interpret this as "add this new root key, and also use this cert which corresponds to a key already in the key store".
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what do you guys think of : "root certificate must match root key if a root key is supplied, otherwise it must match a key present in keystore"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@cyc115 capitalize the first Root
for consistency with other flags but otherwise SGTM!
tuf/signed/verify.go
Outdated
@@ -107,3 +108,18 @@ func VerifySignature(msg []byte, sig *data.Signature, pk data.PublicKey) error { | |||
sig.IsValid = true | |||
return nil | |||
} | |||
|
|||
// VerifyPublicKeyMatchesPrivateKey checks if the private key and the public keys forms valid key pairs. | |||
// This should work with both ecdsa-x509 certificate PublicKey as well as ecdsa PublicKey |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nitpick: Should this say just "x509 certificate PublicKeys as well as non-certificate PublicKeys"? (I think RSA keys are also supported, for instance)
tuf/signed/verify.go
Outdated
// This should work with both ecdsa-x509 certificate PublicKey as well as ecdsa PublicKey | ||
func VerifyPublicKeyMatchesPrivateKey(privKey data.PrivateKey, pubKey data.PublicKey) error { | ||
pubKeyID, err := utils.CanonicalKeyID(pubKey) | ||
privKeyID := privKey.ID() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Before we call privKey.ID()
, do we want to check to make sure it's not nil? (maybe we can just do so in the if block below: if privKey == nil || pubKeyID != privKey.ID {...
)
Likewise, in the tests (thanks for adding those!), particularly in TestVerifyPublicKeyMatchesPrivateKeyFails
, could we also test passing a nil
public key and/or nil
private key?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually apologies it looks like utils.CanonicalKeyID
also assumes that the key is not nil, and will also panic :| If you wouldn't mind fixing it in this PR, that would be awesome but I'd also be happy to fix it in a separate PR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@cyli good catch, I missed this!
I agree that for this PR, at minimum we should check the error (if err != nil {
) before any operations with the private key, and then check as @cyli suggests by doing if privKey == nil || pubKeyID != privKey.ID {...
Fixing utils.CanonicalKeyID
would be a nice bonus if you're comfortable but agree that we can handle it in a separate PR 👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok! I have just added a check in CanonicalKeyID
to error if nil
is provided, I am ok if you guys don't mind it to be in this PR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's awesome, thanks!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Apologies for coming into this so late. Thanks for working on this @cyc115!
This LGTM too, aside from a few minor nitpicks.
cmd/notary/tuf.go
Outdated
@@ -446,6 +453,38 @@ func importRootKey(cmd *cobra.Command, rootKey string, nRepo *notaryclient.Notar | |||
return []string{}, nil | |||
} | |||
|
|||
// importRootCert imports the base64 encrypted public certificate corresponding to the root key |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nitpick: "encoded", not "encrypted", since certs aren't encrypted. :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good cache :)
cmd/notary/tuf.go
Outdated
} | ||
defer certFile.Close() | ||
|
||
certPEM, err := ioutil.ReadAll(certFile) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we be checking this error and returning it if not nil
? Also, if we use ioutil.ReadFile
here, this read and the previous open section can be merged into a single function call.
Signed-off-by: Yuechuan Chen <yuechuan.chen@motorolasolutions.com>
Thanks for the fixes @cyc115! LGTM on yubikey green (the builder has a connection failure again - I'm prodding it every so often) |
:) No problem @cyli ! Thank you for helping me through my first pull request to notary :P |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM!
I'll deal with any merge issues if anything unexpected comes up. GH wasn't highlighting any conflicts though and I don't think this conflicts with my key generation PR that I merged earlier |
This will allow user to rotate a repository's root key to a pinned trust, make trust pinning more useful. - add `--rootcert` flag to key rotation - add `-y` flag to key rotate to allow auto-confirmation of rotating root keys (no user interaction required) - allow mismatched key-certificate pair to be provided. an example usage would be : The PR includes the following: `notary key rotate [GUN] root --key path/to/key.key --rootcert path/to/rootcert.pem` related issues: notaryproject#1144, notaryproject#1118, notaryproject#731 Signed-off-by: Chen Yuechuan-XJQW46 <Yuechuan.Chen@motorolasolutions.com>
This will allow user to rotate a repository's root key to a pinned trust, make trust pinning more useful. - add `--rootcert` flag to key rotation - add `-y` flag to key rotate to allow auto-confirmation of rotating root keys (no user interaction required) - allow mismatched key-certificate pair to be provided. an example usage would be : The PR includes the following: `notary key rotate [GUN] root --key path/to/key.key --rootcert path/to/rootcert.pem` related issues: notaryproject#1144, notaryproject#1118, notaryproject#731 Signed-off-by: Chen Yuechuan-XJQW46 <Yuechuan.Chen@motorolasolutions.com>
Supports initializing a notary repository by passing in a root certificate alone with the corresponding root key. This would make wildcard certificate trust pinning usable as an existing trusted certificate can now be included in
root.json
metadata.usage :
notary init [ GUN ] --rootkey path/to/key.key --rootcert path/to/cert.crt
--rootcert
can only be used when--rootkey
is specified.related works include #821 and #883.