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

Support for initializing repository with root certificate #1144

Merged
merged 8 commits into from
May 30, 2017

Conversation

cyc115
Copy link

@cyc115 cyc115 commented May 1, 2017

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.

@docker-jenkins
Copy link

Can one of the admins verify this patch?

@GordonTheTurtle
Copy link

Please sign your commits following these rules:
https://github.com/moby/moby/blob/master/CONTRIBUTING.md#sign-your-work
The easiest way to do this is to amend the last commit:

$ 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.

@cyc115 cyc115 force-pushed the cyc_rootcert branch 3 times, most recently from 3b374bd to 364a531 Compare May 2, 2017 18:52
@cyli
Copy link
Contributor

cyli commented May 2, 2017

jenkins, test this please

@endophage
Copy link
Contributor

I think it probably makes sense to allow --rootcert to be specified without a --rootkey. The use case is that the root private key may be in an HSM/Yubikey. You would still be able to generate a CSR and go get a certificate from your CA, but you wouldn't be able to provide a file path to the private key on the CLI.

The behavior in the case that only a --rootcert is provided would be to use it to look up the root key in any and all available key storage.

@lewiada-zz
Copy link

@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?

@endophage
Copy link
Contributor

The KeyID can be generated from the certificate. KeyIDs are the SHA256 checksum of the following canonically serialized JSON object:

{
    "type": "<key type i.e. rsa-x509>"
    "value": {
        "public": "<public bytes of key, i.e. the x509 cert>",
        "private": ""
    }
}

More recent versions of the TUF spec have been updated to simply exclude the empty private field. Specifically for certificates, we also implemented an internal concept of a "Canonical" KeyID. This is generated the same way but rather than using the x509 certificate, it uses only the public key bytes extracted from the certificate.

@cyc115
Copy link
Author

cyc115 commented May 5, 2017

@endophage great advice David! I have updated the PR to allow init with --rootcert and lookup key from existing key store. I will update key rotation PR to allow the same thing. Let me know if you have any more suggestions !

@cyc115 cyc115 force-pushed the cyc_rootcert branch 5 times, most recently from 238a381 to 5c1e88f Compare May 8, 2017 15:43
@endophage endophage mentioned this pull request May 9, 2017
@cyc115 cyc115 force-pushed the cyc_rootcert branch 4 times, most recently from 78aee10 to 51d2943 Compare May 10, 2017 19:47
@cyli
Copy link
Contributor

cyli commented May 10, 2017

jenkins, test this please

Copy link
Contributor

@riyazdf riyazdf left a 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"

Copy link
Contributor

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.
Copy link
Contributor

Choose a reason for hiding this comment

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

typo: wit

"net/http"
"net/http/httptest"
"net/url"
"os"
"path/filepath"
"testing"

"strings"
Copy link
Contributor

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?


// 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 {
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm wondering if we can do something simpler here - I think I've previously discussed this with @lewiada or @dnwake but didn't get to a final solution. Perhaps we could check the private key's fields against those shared with data.PublicKey directly?

Copy link
Author

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().

Copy link
Contributor

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"

Copy link
Contributor

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.
Copy link
Contributor

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.
Copy link
Contributor

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?

Copy link
Author

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)
Copy link
Contributor

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?

Copy link
Author

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())

Copy link
Contributor

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
Copy link
Contributor

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

"net/http"
"net/http/httptest"
"net/url"
"os"
"path/filepath"
"testing"

"strings"
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: same here

@cyc115
Copy link
Author

cyc115 commented May 18, 2017

@cyli Ying the pipe line seems to be blocked again :P ?

@endophage
Copy link
Contributor

@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.

@riyazdf
Copy link
Contributor

riyazdf commented May 24, 2017

@cyc115: we're diagnosing the CI issue still, in the meantime could you please squash your commits?

edit: should be fixed now!

@riyazdf
Copy link
Contributor

riyazdf commented May 25, 2017

jenkins, test this please

Copy link
Contributor

@riyazdf riyazdf left a 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
Copy link
Contributor

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
Copy link
Contributor

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

args args
wantErr bool
}{
// TODO: Add test cases.
Copy link
Contributor

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?

Copy link
Author

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!

wantTimestamp data.BaseRole
wantErr bool
}{
// TODO: Add test cases.
Copy link
Contributor

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?

"init", gun+"4",
"--rootkey", nonMatchingKeyFilename,
"--rootcert", certFilename)
require.Error(t, err, "should not be able to init a repository with missmatched key and cert")
Copy link
Contributor

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
Copy link
Contributor

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.

@@ -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
Copy link
Contributor

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! 👍


// 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 {
Copy link
Contributor

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! 👍

Yuechuan Chen added 7 commits May 25, 2017 11:08
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>
Copy link
Contributor

@riyazdf riyazdf left a 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 :)

//set up temp dir
tempDir := tempDirWithConfig(t, `{
"trust_pinning" : {
"disable_tufu" : false
Copy link
Contributor

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.

Copy link
Author

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 !

@@ -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")
Copy link
Contributor

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"

Copy link
Contributor

@cyli cyli May 25, 2017

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".

Copy link
Author

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"

Copy link
Contributor

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!

@@ -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
Copy link
Contributor

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)

// 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()
Copy link
Contributor

@cyli cyli May 25, 2017

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?

Copy link
Contributor

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.

Copy link
Contributor

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 👍

Copy link
Author

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.

Copy link
Contributor

Choose a reason for hiding this comment

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

That's awesome, thanks!

Copy link
Contributor

@cyli cyli left a 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.

@@ -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
Copy link
Contributor

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. :)

Copy link
Author

Choose a reason for hiding this comment

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

Good cache :)

}
defer certFile.Close()

certPEM, err := ioutil.ReadAll(certFile)
Copy link
Contributor

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>
@cyli
Copy link
Contributor

cyli commented May 30, 2017

Thanks for the fixes @cyc115! LGTM on yubikey green (the builder has a connection failure again - I'm prodding it every so often)

@cyc115
Copy link
Author

cyc115 commented May 30, 2017

:) No problem @cyli ! Thank you for helping me through my first pull request to notary :P

Copy link
Contributor

@endophage endophage left a comment

Choose a reason for hiding this comment

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

LGTM!

@endophage endophage merged commit 101275f into notaryproject:master May 30, 2017
@endophage
Copy link
Contributor

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

cyc115 pushed a commit to cyc115/notary that referenced this pull request Oct 10, 2017
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>
endophage pushed a commit to cyc115/notary that referenced this pull request Oct 26, 2017
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants