diff --git a/cmd/notary/cert.go b/cmd/notary/cert.go index b4357980e5..2e4789177d 100644 --- a/cmd/notary/cert.go +++ b/cmd/notary/cert.go @@ -2,7 +2,6 @@ package main import ( "crypto/x509" - "fmt" "math" "os" "path/filepath" @@ -92,14 +91,14 @@ func certRemove(cmd *cobra.Command, args []string) { } // List all the keys about to be removed - fmt.Printf("The following certificates will be removed:\n\n") + cmd.Printf("The following certificates will be removed:\n\n") for _, cert := range certsToRemove { // This error can't occur because we're getting certs off of an // x509 store that indexes by ID. certID, _ := trustmanager.FingerprintCert(cert) - fmt.Printf("%s - %s\n", cert.Subject.CommonName, certID) + cmd.Printf("%s - %s\n", cert.Subject.CommonName, certID) } - fmt.Println("\nAre you sure you want to remove these certificates? (yes/no)") + cmd.Println("\nAre you sure you want to remove these certificates? (yes/no)") // Ask for confirmation before removing certificates, unless -y is provided if !certRemoveYes { @@ -136,20 +135,20 @@ func certList(cmd *cobra.Command, args []string) { fatalf("failed to create a new truststore manager with directory: %s", trustDir) } - fmt.Println("") - fmt.Println("# Trusted Certificates:") + cmd.Println("") + cmd.Println("# Trusted Certificates:") trustedCerts := keyStoreManager.TrustedCertificateStore().GetCertificates() for _, c := range trustedCerts { - printCert(c) + printCert(cmd, c) } } -func printCert(cert *x509.Certificate) { +func printCert(cmd *cobra.Command, cert *x509.Certificate) { timeDifference := cert.NotAfter.Sub(time.Now()) certID, err := trustmanager.FingerprintCert(cert) if err != nil { fatalf("could not fingerprint certificate: %v", err) } - fmt.Printf("%s %s (expires in: %v days)\n", cert.Subject.CommonName, certID, math.Floor(timeDifference.Hours()/24)) + cmd.Printf("%s %s (expires in: %v days)\n", cert.Subject.CommonName, certID, math.Floor(timeDifference.Hours()/24)) } diff --git a/cmd/notary/integration_nonpkcs11_test.go b/cmd/notary/integration_nonpkcs11_test.go new file mode 100644 index 0000000000..5247a10d08 --- /dev/null +++ b/cmd/notary/integration_nonpkcs11_test.go @@ -0,0 +1,34 @@ +// +build !pkcs11 + +package main + +import ( + "testing" + + "github.com/docker/notary/passphrase" +) + +func rootOnHardware() bool { + return false +} + +// Per-test set up that returns a cleanup function. This set up changes the +// passphrase retriever to always produce a constant passphrase +func setUp(t *testing.T) func() { + oldRetriever := retriever + + var fake = func(k, a string, c bool, n int) (string, bool, error) { + return testPassphrase, false, nil + } + + retriever = fake + getRetriever = func() passphrase.Retriever { return fake } + + return func() { + retriever = oldRetriever + getRetriever = getPassphraseRetriever + } +} + +// no-op +func verifyRootKeyOnHardware(t *testing.T, rootKeyID string) {} diff --git a/cmd/notary/integration_pkcs11_test.go b/cmd/notary/integration_pkcs11_test.go new file mode 100644 index 0000000000..436bbee373 --- /dev/null +++ b/cmd/notary/integration_pkcs11_test.go @@ -0,0 +1,63 @@ +// +build pkcs11 + +package main + +import ( + "testing" + + "github.com/docker/notary/passphrase" + "github.com/docker/notary/signer/api" + "github.com/docker/notary/tuf/data" + "github.com/stretchr/testify/assert" +) + +var rootOnHardware = api.YubikeyAccessible + +// Per-test set up that returns a cleanup function. This set up: +// - changes the passphrase retriever to always produce a constant passphrase +// - disables touch on yubikeys +// - deletes all keys on the yubikey +func setUp(t *testing.T) func() { + oldRetriever := retriever + + var fake = func(k, a string, c bool, n int) (string, bool, error) { + if k == "Yubikey" { + return oldRetriever(k, a, c, n) + } + return testPassphrase, false, nil + } + + retriever = fake + getRetriever = func() passphrase.Retriever { return fake } + api.SetYubikeyKeyMode(api.KeymodeNone) + + // //we're just removing keys here, so nil is fine + s, err := api.NewYubiKeyStore(nil, retriever) + assert.NoError(t, err) + for k := range s.ListKeys() { + err := s.RemoveKey(k) + assert.NoError(t, err) + } + + return func() { + retriever = oldRetriever + getRetriever = getPassphraseRetriever + api.SetYubikeyKeyMode(api.KeymodeTouch | api.KeymodePinOnce) + } +} + +// ensures that the root is actually on the yubikey - this makes sure the +// commands are hooked up to interact with the yubikey, rather than right files +// on disk +func verifyRootKeyOnHardware(t *testing.T, rootKeyID string) { + // do not bother verifying if there is no yubikey available + if api.YubikeyAccessible() { + // //we're just getting keys here, so nil is fine + s, err := api.NewYubiKeyStore(nil, retriever) + assert.NoError(t, err) + privKey, role, err := s.GetKey(rootKeyID) + assert.NoError(t, err) + assert.NotNil(t, privKey) + assert.Equal(t, data.CanonicalRootRole, role) + } +} diff --git a/cmd/notary/integration_test.go b/cmd/notary/integration_test.go new file mode 100644 index 0000000000..dd6dec2d41 --- /dev/null +++ b/cmd/notary/integration_test.go @@ -0,0 +1,513 @@ +// Actually start up a notary server and run through basic TUF and key +// interactions via the client. + +// Note - if using Yubikey, retrieving pins/touch doesn't seem to work right +// when running in the midst of all tests. + +package main + +import ( + "bytes" + "crypto/rand" + "io/ioutil" + "net/http/httptest" + "os" + "path/filepath" + "sort" + "strings" + "testing" + + "github.com/Sirupsen/logrus" + ctxu "github.com/docker/distribution/context" + "github.com/docker/notary/cryptoservice" + "github.com/docker/notary/server" + "github.com/docker/notary/server/storage" + "github.com/docker/notary/trustmanager" + "github.com/spf13/cobra" + "github.com/stretchr/testify/assert" + "golang.org/x/net/context" +) + +var cmd = &cobra.Command{} +var testPassphrase = "passphrase" + +// run a command and return the output as a string +func runCommand(t *testing.T, tempDir string, args ...string) (string, error) { + b := new(bytes.Buffer) + cmd.SetArgs(append([]string{"-c", "/tmp/ignore.json", "-d", tempDir}, args...)) + cmd.SetOutput(b) + t.Logf("Running `notary %s`", strings.Join(args, " ")) + + retErr := cmd.Execute() + output, err := ioutil.ReadAll(b) + assert.NoError(t, err) + return string(output), retErr +} + +// makes a testing notary-server +func setupServer() *httptest.Server { + // Set up server + ctx := context.WithValue( + context.Background(), "metaStore", storage.NewMemStorage()) + + // Do not pass one of the const KeyAlgorithms here as the value! Passing a + // string is in itself good test that we are handling it correctly as we + // will be receiving a string from the configuration. + ctx = context.WithValue(ctx, "keyAlgorithm", "ecdsa") + + // Eat the logs instead of spewing them out + var b bytes.Buffer + l := logrus.New() + l.Out = &b + ctx = ctxu.WithLogger(ctx, logrus.NewEntry(l)) + + cryptoService := cryptoservice.NewCryptoService( + "", trustmanager.NewKeyMemoryStore(retriever)) + return httptest.NewServer(server.RootHandler(nil, ctx, cryptoService)) +} + +// Initializes a repo, adds a target, publishes the target, lists the target, +// verifies the target, and then removes the target. +func TestClientTufInteraction(t *testing.T) { + // -- setup -- + cleanup := setUp(t) + defer cleanup() + + tempDir, err := ioutil.TempDir("/tmp", "repo") + assert.NoError(t, err) + defer os.RemoveAll(tempDir) + + server := setupServer() + defer server.Close() + + tempFile, err := ioutil.TempFile("/tmp", "targetfile") + assert.NoError(t, err) + tempFile.Close() + defer os.Remove(tempFile.Name()) + + var ( + output string + target = "sdgkadga" + ) + // -- tests -- + + // init repo + _, err = runCommand(t, tempDir, "-s", server.URL, "init", "gun") + assert.NoError(t, err) + + // add a target + _, err = runCommand(t, tempDir, "add", "gun", target, tempFile.Name()) + assert.NoError(t, err) + + // check status - see target + output, err = runCommand(t, tempDir, "status", "gun") + assert.NoError(t, err) + assert.True(t, strings.Contains(output, target)) + + // publish repo + _, err = runCommand(t, tempDir, "-s", server.URL, "publish", "gun") + assert.NoError(t, err) + + // check status - no targets + output, err = runCommand(t, tempDir, "status", "gun") + assert.NoError(t, err) + assert.False(t, strings.Contains(string(output), target)) + + // list repo - see target + output, err = runCommand(t, tempDir, "-s", server.URL, "list", "gun") + assert.NoError(t, err) + assert.True(t, strings.Contains(string(output), target)) + + // verify repo - empty file + output, err = runCommand(t, tempDir, "verify", "gun", target) + assert.NoError(t, err) + + // remove target + _, err = runCommand(t, tempDir, "remove", "gun", target) + assert.NoError(t, err) + + // publish repo + _, err = runCommand(t, tempDir, "-s", server.URL, "publish", "gun") + assert.NoError(t, err) + + // list repo - don't see target + output, err = runCommand(t, tempDir, "-s", server.URL, "list", "gun") + assert.NoError(t, err) + assert.False(t, strings.Contains(string(output), target)) +} + +// Splits a string into lines, and returns any lines that are not empty ( +// striped of whitespace) +func splitLines(chunk string) []string { + splitted := strings.Split(strings.TrimSpace(chunk), "\n") + var results []string + + for _, line := range splitted { + line := strings.TrimSpace(line) + if line != "" { + results = append(results, line) + } + } + + return results +} + +// List keys, parses the output, and returns the keys as an array of root key +// IDs and an array of signing key IDs +func GetKeys(t *testing.T, tempDir string) ([]string, []string) { + output, err := runCommand(t, tempDir, "key", "list") + assert.NoError(t, err) + + parts := strings.Split(output, "# Signing keys:") + assert.Len(t, parts, 2) + + fixed := make([][]string, 2) + for i, part := range parts { + fixed[i] = splitLines( + strings.TrimPrefix(strings.TrimSpace(part), "# Root keys:")) + sort.Strings(fixed[i]) + } + + return fixed[0], fixed[1] +} + +// List keys, parses the output, and asserts something about the number of root +// keys and number of signing keys, as well as returning them. +func assertNumKeys(t *testing.T, tempDir string, numRoot, numSigning int) ( + []string, []string) { + + root, signing := GetKeys(t, tempDir) + assert.Len(t, root, numRoot) + assert.Len(t, signing, numSigning) + for _, rootKeyID := range root { + // it should always be present on disk + _, err := os.Stat(filepath.Join( + tempDir, "private", "root_keys", rootKeyID+"_root.key")) + assert.NoError(t, err) + + // this function is declared is in the build-tagged setup files + verifyRootKeyOnHardware(t, rootKeyID) + } + return root, signing +} + +// Adds the given target to the gun, publishes it, and lists it to ensure that +// it appears. Returns the listing output. +func assertSuccessfullyPublish( + t *testing.T, tempDir, url, gun, target, fname string) string { + + _, err := runCommand(t, tempDir, "add", gun, target, fname) + assert.NoError(t, err) + + _, err = runCommand(t, tempDir, "-s", url, "publish", gun) + assert.NoError(t, err) + + output, err := runCommand(t, tempDir, "-s", url, "list", gun) + assert.NoError(t, err) + assert.True(t, strings.Contains(string(output), target)) + + return output +} + +// Tests root key generation and key rotation +func TestClientKeyGenerationRotation(t *testing.T) { + // -- setup -- + cleanup := setUp(t) + defer cleanup() + + tempDir, err := ioutil.TempDir("/tmp", "repo") + assert.NoError(t, err) + defer os.RemoveAll(tempDir) + + tempfiles := make([]string, 2) + for i := 0; i < 2; i++ { + tempFile, err := ioutil.TempFile("/tmp", "targetfile") + assert.NoError(t, err) + tempFile.Close() + tempfiles[i] = tempFile.Name() + defer os.Remove(tempFile.Name()) + } + + server := setupServer() + defer server.Close() + + var target = "sdgkadga" + + // -- tests -- + + // starts out with no keys + assertNumKeys(t, tempDir, 0, 0) + + // generate root key produces a single root key and no other keys + _, err = runCommand(t, tempDir, "key", "generate", "ecdsa") + assert.NoError(t, err) + assertNumKeys(t, tempDir, 1, 0) + + // initialize a repo, should have signing keys and no new root key + _, err = runCommand(t, tempDir, "-s", server.URL, "init", "gun") + assert.NoError(t, err) + origRoot, origSign := assertNumKeys(t, tempDir, 1, 2) + + // publish using the original keys + assertSuccessfullyPublish(t, tempDir, server.URL, "gun", target, tempfiles[0]) + + // rotate the signing keys + _, err = runCommand(t, tempDir, "key", "rotate", "gun") + assert.NoError(t, err) + root, sign := assertNumKeys(t, tempDir, 1, 4) + assert.Equal(t, origRoot[0], root[0]) + // there should be the new keys and the old keys + for _, origKey := range origSign { + found := false + for _, key := range sign { + if key == origKey { + found = true + } + } + assert.True(t, found, "Old key not found in list of old and new keys") + } + + // publish the key rotation + _, err = runCommand(t, tempDir, "-s", server.URL, "publish", "gun") + assert.NoError(t, err) + root, sign = assertNumKeys(t, tempDir, 1, 2) + assert.Equal(t, origRoot[0], root[0]) + // just do a cursory rotation check that the keys aren't equal anymore + for _, origKey := range origSign { + for _, key := range sign { + assert.NotEqual( + t, key, origKey, "One of the signing keys was not removed") + } + } + + // publish using the new keys + output := assertSuccessfullyPublish( + t, tempDir, server.URL, "gun", target+"2", tempfiles[1]) + // assert that the previous target is sitll there + assert.True(t, strings.Contains(string(output), target)) +} + +// Tests import/export root+signing keys - repo with imported keys should be +// able to publish successfully +func TestClientKeyImportExportRootAndSigning(t *testing.T) { + // -- setup -- + cleanup := setUp(t) + defer cleanup() + + dirs := make([]string, 3) + for i := 0; i < 3; i++ { + tempDir, err := ioutil.TempDir("/tmp", "repo") + assert.NoError(t, err) + defer os.RemoveAll(tempDir) + dirs[i] = tempDir + } + + tempfiles := make([]string, 2) + for i := 0; i < 2; i++ { + tempFile, err := ioutil.TempFile("/tmp", "tempfile") + assert.NoError(t, err) + tempFile.Close() + tempfiles[i] = tempFile.Name() + defer os.Remove(tempFile.Name()) + } + + server := setupServer() + defer server.Close() + + var ( + target = "sdgkadga" + err error + ) + + // create two repos and publish a target + for _, gun := range []string{"gun1", "gun2"} { + _, err = runCommand(t, dirs[0], "-s", server.URL, "init", gun) + assert.NoError(t, err) + + assertSuccessfullyPublish( + t, dirs[0], server.URL, gun, target, tempfiles[0]) + } + assertNumKeys(t, dirs[0], 1, 4) + + // -- tests -- + zipfile := tempfiles[0] + ".zip" + defer os.Remove(zipfile) + + // export then import all keys + _, err = runCommand(t, dirs[0], "key", "export", zipfile) + assert.NoError(t, err) + + _, err = runCommand(t, dirs[1], "key", "import", zipfile) + assert.NoError(t, err) + assertNumKeys(t, dirs[1], 1, 4) // all keys should be there + + // can list and publish to both repos using imported keys + for _, gun := range []string{"gun1", "gun2"} { + output, err := runCommand(t, dirs[1], "-s", server.URL, "list", gun) + assert.NoError(t, err) + assert.True(t, strings.Contains(string(output), target)) + + assertSuccessfullyPublish( + t, dirs[1], server.URL, gun, target+"2", tempfiles[1]) + } + + // export then import keys for one gun + _, err = runCommand(t, dirs[0], "key", "export", zipfile, "-g", "gun1") + assert.NoError(t, err) + + _, err = runCommand(t, dirs[2], "key", "import", zipfile) + assert.NoError(t, err) + + // this function is declared is in the build-tagged setup files + if rootOnHardware() { + // hardware root is still present, but two signing keys moved - we + // can't call assertNumKeys, because in this case it will ONLY be on + // hardware and not on disk + root, signing := GetKeys(t, dirs[2]) + assert.Len(t, root, 1) + verifyRootKeyOnHardware(t, root[0]) + assert.Len(t, signing, 2) + } else { + // only 2 signing keys should be there, and no root key + assertNumKeys(t, dirs[2], 0, 2) + } +} + +// Generate a root key and export the root key only. Return the key ID +// exported. +func exportRoot(t *testing.T, exportTo string) string { + tempDir, err := ioutil.TempDir("/tmp", "repo") + assert.NoError(t, err) + defer os.RemoveAll(tempDir) + + // generate root key produces a single root key and no other keys + _, err = runCommand(t, tempDir, "key", "generate", "ecdsa") + assert.NoError(t, err) + oldRoot, _ := assertNumKeys(t, tempDir, 1, 0) + + // export does not require a password + oldRetriever := retriever + retriever = nil + defer func() { // but import will, later + retriever = oldRetriever + }() + + _, err = runCommand( + t, tempDir, "key", "export-root", oldRoot[0], exportTo) + assert.NoError(t, err) + + return oldRoot[0] +} + +// Tests import/export root key only +func TestClientKeyImportExportRootOnly(t *testing.T) { + // -- setup -- + cleanup := setUp(t) + defer cleanup() + + tempDir, err := ioutil.TempDir("/tmp", "repo") + assert.NoError(t, err) + defer os.RemoveAll(tempDir) + + server := setupServer() + defer server.Close() + + var ( + target = "sdgkadga" + rootKeyID string + ) + + tempFile, err := ioutil.TempFile("/tmp", "pemfile") + assert.NoError(t, err) + // close later, because we might need to write to it + defer os.Remove(tempFile.Name()) + + // -- tests -- + + if rootOnHardware() { + t.Log("Cannot export a key from hardware. Will generate one to import.") + + privKey, err := trustmanager.GenerateECDSAKey(rand.Reader) + assert.NoError(t, err) + + pemBytes, err := trustmanager.EncryptPrivateKey(privKey, testPassphrase) + assert.NoError(t, err) + + nBytes, err := tempFile.Write(pemBytes) + assert.NoError(t, err) + tempFile.Close() + assert.Equal(t, len(pemBytes), nBytes) + rootKeyID = privKey.ID() + } else { + tempFile.Close() + rootKeyID = exportRoot(t, tempFile.Name()) + } + + // import the key + _, err = runCommand(t, tempDir, "key", "import-root", tempFile.Name()) + assert.NoError(t, err) + newRoot, _ := assertNumKeys(t, tempDir, 1, 0) + assert.Equal(t, rootKeyID, newRoot[0]) + + // Just to make sure, init a repo and publish + _, err = runCommand(t, tempDir, "-s", server.URL, "init", "gun") + assert.NoError(t, err) + assertNumKeys(t, tempDir, 1, 2) + assertSuccessfullyPublish( + t, tempDir, server.URL, "gun", target, tempFile.Name()) +} + +func assertNumCerts(t *testing.T, tempDir string, expectedNum int) []string { + output, err := runCommand(t, tempDir, "cert", "list") + assert.NoError(t, err) + certs := splitLines( + strings.TrimPrefix(strings.TrimSpace(output), "# Trusted Certificates:")) + + assert.Len(t, certs, expectedNum) + return certs +} + +// TestClientCertInteraction +func TestClientCertInteraction(t *testing.T) { + // -- setup -- + cleanup := setUp(t) + defer cleanup() + + tempDir, err := ioutil.TempDir("/tmp", "repo") + assert.NoError(t, err) + defer os.RemoveAll(tempDir) + + server := setupServer() + defer server.Close() + + // -- tests -- + _, err = runCommand(t, tempDir, "-s", server.URL, "init", "gun1") + assert.NoError(t, err) + _, err = runCommand(t, tempDir, "-s", server.URL, "init", "gun2") + assert.NoError(t, err) + certs := assertNumCerts(t, tempDir, 2) + + // remove certs for one gun + _, err = runCommand(t, tempDir, "cert", "remove", "-g", "gun1", "-y") + assert.NoError(t, err) + certs = assertNumCerts(t, tempDir, 1) + + // remove a single cert + certID := strings.TrimSpace(strings.Split(certs[0], " ")[1]) + // passing an empty gun here because the string for the previous gun has + // has already been stored (a drawback of running these commands without) + // shelling out + _, err = runCommand(t, tempDir, "cert", "remove", certID, "-y", "-g", "") + assert.NoError(t, err) + assertNumCerts(t, tempDir, 0) + +} + +func TestMain(m *testing.M) { + if testing.Short() { + // skip + os.Exit(0) + } + setupCommand(cmd) + os.Exit(m.Run()) +} diff --git a/cmd/notary/keys.go b/cmd/notary/keys.go index 7a9c82bbfe..aaf897c7a2 100644 --- a/cmd/notary/keys.go +++ b/cmd/notary/keys.go @@ -2,7 +2,6 @@ package main import ( "archive/zip" - "fmt" "os" "path/filepath" "sort" @@ -11,7 +10,6 @@ import ( "github.com/docker/notary" notaryclient "github.com/docker/notary/client" "github.com/docker/notary/cryptoservice" - "github.com/docker/notary/passphrase" "github.com/docker/notary/signer/api" "github.com/docker/notary/trustmanager" @@ -116,16 +114,16 @@ func keysList(cmd *cobra.Command, args []string) { // Get a map of all the keys/roles keysMap := cs.ListAllKeys() - fmt.Println("") - fmt.Println("# Root keys: ") + cmd.Println("") + cmd.Println("# Root keys: ") for k, v := range keysMap { if v == "root" { - fmt.Println(k) + cmd.Println(k) } } - fmt.Println("") - fmt.Println("# Signing keys: ") + cmd.Println("") + cmd.Println("# Signing keys: ") // Get a list of all the keys var sortedKeys []string @@ -138,7 +136,7 @@ func keysList(cmd *cobra.Command, args []string) { // Print a sorted list of the key/role for _, k := range sortedKeys { if keysMap[k] != "root" { - printKey(k, keysMap[k]) + printKey(cmd, k, keysMap[k]) } } } @@ -180,7 +178,7 @@ func keysGenerateRootKey(cmd *cobra.Command, args []string) { fatalf("failed to create a new root key: %v", err) } - fmt.Printf("Generated new %s root key with keyID: %s\n", algorithm, pubKey.ID()) + cmd.Printf("Generated new %s root key with keyID: %s\n", algorithm, pubKey.ID()) } // keysExport exports a collection of keys to a ZIP file @@ -208,7 +206,7 @@ func keysExport(cmd *cobra.Command, args []string) { // Must use a different passphrase retriever to avoid caching the // unlocking passphrase and reusing that. - exportRetriever := passphrase.PromptRetriever() + exportRetriever := getRetriever() if keysExportGUN != "" { err = cs.ExportKeysByGUN(exportFile, keysExportGUN, exportRetriever) } else { @@ -253,7 +251,7 @@ func keysExportRoot(cmd *cobra.Command, args []string) { if keysExportRootChangePassphrase { // Must use a different passphrase retriever to avoid caching the // unlocking passphrase and reusing that. - exportRetriever := passphrase.PromptRetriever() + exportRetriever := getRetriever() err = cs.ExportRootKeyReencrypt(exportFile, keyID, exportRetriever) } else { err = cs.ExportRootKey(exportFile, keyID) @@ -334,10 +332,10 @@ func keysImportRoot(cmd *cobra.Command, args []string) { } } -func printKey(keyPath, alias string) { +func printKey(cmd *cobra.Command, keyPath, alias string) { keyID := filepath.Base(keyPath) gun := filepath.Dir(keyPath) - fmt.Printf("%s - %s - %s\n", gun, alias, keyID) + cmd.Printf("%s - %s - %s\n", gun, alias, keyID) } func keysRotate(cmd *cobra.Command, args []string) { diff --git a/cmd/notary/main.go b/cmd/notary/main.go index 668dd2377b..8281af51e3 100644 --- a/cmd/notary/main.go +++ b/cmd/notary/main.go @@ -30,6 +30,7 @@ var ( configFileName = "config" configFileExt = "json" retriever passphrase.Retriever + getRetriever = getPassphraseRetriever mainViper = viper.New() ) @@ -85,13 +86,7 @@ func parseConfig() { } } -func main() { - var notaryCmd = &cobra.Command{ - Use: "notary", - Short: "notary allows the creation of trusted collections.", - Long: "notary allows the creation and management of collections of signed targets, allowing the signing and validation of arbitrary content.", - } - +func setupCommand(notaryCmd *cobra.Command) { var versionCmd = &cobra.Command{ Use: "version", Short: "Print the version number of notary", @@ -124,7 +119,15 @@ func main() { cmdTufLookup.Flags().StringVarP(&remoteTrustServer, "server", "s", "", "Remote trust server location") notaryCmd.AddCommand(cmdVerify) cmdVerify.Flags().StringVarP(&remoteTrustServer, "server", "s", "", "Remote trust server location") +} +func main() { + var notaryCmd = &cobra.Command{ + Use: "notary", + Short: "notary allows the creation of trusted collections.", + Long: "notary allows the creation and management of collections of signed targets, allowing the signing and validation of arbitrary content.", + } + setupCommand(notaryCmd) notaryCmd.Execute() } diff --git a/cmd/notary/tuf.go b/cmd/notary/tuf.go index 6faf590002..c835351a96 100644 --- a/cmd/notary/tuf.go +++ b/cmd/notary/tuf.go @@ -107,7 +107,7 @@ func tufAdd(cmd *cobra.Command, args []string) { if err != nil { fatalf(err.Error()) } - fmt.Printf("Addition of %s to %s staged for next publish.\n", targetName, gun) + cmd.Printf("Addition of %s to %s staged for next publish.\n", targetName, gun) } func tufInit(cmd *cobra.Command, args []string) { @@ -128,7 +128,7 @@ func tufInit(cmd *cobra.Command, args []string) { var rootKeyID string if len(rootKeyList) < 1 { - fmt.Println("No root keys found. Generating a new root key...") + cmd.Println("No root keys found. Generating a new root key...") rootPublicKey, err := nRepo.CryptoService.Create(data.CanonicalRootRole, data.ECDSAKey) rootKeyID = rootPublicKey.ID() if err != nil { @@ -138,7 +138,7 @@ func tufInit(cmd *cobra.Command, args []string) { // Choses the first root key available, which is initialization specific // but should return the HW one first. rootKeyID = rootKeyList[0] - fmt.Printf("Root key found, using: %s\n", rootKeyID) + cmd.Printf("Root key found, using: %s\n", rootKeyID) } err = nRepo.Initialize(rootKeyID) @@ -168,7 +168,7 @@ func tufList(cmd *cobra.Command, args []string) { // Print all the available targets for _, t := range targetList { - fmt.Printf("%s %x %d\n", t.Name, t.Hashes["sha256"], t.Length) + cmd.Printf("%s %x %d\n", t.Name, t.Hashes["sha256"], t.Length) } } @@ -191,7 +191,7 @@ func tufLookup(cmd *cobra.Command, args []string) { fatalf(err.Error()) } - fmt.Println(target.Name, fmt.Sprintf("sha256:%x", target.Hashes["sha256"]), target.Length) + cmd.Println(target.Name, fmt.Sprintf("sha256:%x", target.Hashes["sha256"]), target.Length) } func tufStatus(cmd *cobra.Command, args []string) { @@ -214,15 +214,15 @@ func tufStatus(cmd *cobra.Command, args []string) { } if len(cl.List()) == 0 { - fmt.Printf("No unpublished changes for %s\n", gun) + cmd.Printf("No unpublished changes for %s\n", gun) return } - fmt.Printf("Unpublished changes for %s:\n\n", gun) - fmt.Printf("%-10s%-10s%-12s%s\n", "action", "scope", "type", "path") - fmt.Println("----------------------------------------------------") + cmd.Printf("Unpublished changes for %s:\n\n", gun) + cmd.Printf("%-10s%-10s%-12s%s\n", "action", "scope", "type", "path") + cmd.Println("----------------------------------------------------") for _, ch := range cl.List() { - fmt.Printf("%-10s%-10s%-12s%s\n", ch.Action(), ch.Scope(), ch.Type(), ch.Path()) + cmd.Printf("%-10s%-10s%-12s%s\n", ch.Action(), ch.Scope(), ch.Type(), ch.Path()) } } @@ -235,7 +235,7 @@ func tufPublish(cmd *cobra.Command, args []string) { gun := args[0] parseConfig() - fmt.Println("Pushing changes to ", gun, ".") + cmd.Println("Pushing changes to", gun) nRepo, err := notaryclient.NewNotaryRepository(trustDir, gun, getRemoteTrustServer(), getTransport(gun, false), retriever) if err != nil { @@ -268,7 +268,7 @@ func tufRemove(cmd *cobra.Command, args []string) { fatalf(err.Error()) } - fmt.Printf("Removal of %s from %s staged for next publish.\n", targetName, gun) + cmd.Printf("Removal of %s from %s staged for next publish.\n", targetName, gun) } func verify(cmd *cobra.Command, args []string) { diff --git a/signer/api/ecdsa_hardware_crypto_service.go b/signer/api/ecdsa_hardware_crypto_service.go index 0ef0c0a8b6..95d13c0de2 100644 --- a/signer/api/ecdsa_hardware_crypto_service.go +++ b/signer/api/ecdsa_hardware_crypto_service.go @@ -25,8 +25,29 @@ const ( USER_PIN = "123456" SO_USER_PIN = "010203040506070801020304050607080102030405060708" numSlots = 4 // number of slots in the yubikey + + KeymodeNone = 0 + KeymodeTouch = 1 // touch enabled + KeymodePinOnce = 2 // require pin entry once + KeymodePinAlways = 4 // require pin entry all the time ) +// what key mode to use when generating keys +var yubikeyKeymode = KeymodeTouch | KeymodePinOnce + +// SetYubikeyKeyMode - sets the mode when generating yubikey keys. +// This is to be used for testing. It does nothing if not building with tag +// pkcs11. +func SetYubikeyKeyMode(keyMode int) error { + // technically 7 (1 | 2 | 4) is valid, but KeymodePinOnce + + // KeymdoePinAlways don't really make sense together + if keyMode < 0 || keyMode > 5 { + return errors.New("Invalid key mode") + } + yubikeyKeymode = keyMode + return nil +} + // Hardcoded yubikey PKCS11 ID var YUBIKEY_ROOT_KEY_ID = []byte{2} @@ -153,10 +174,7 @@ func addECDSAKey( pkcs11.NewAttribute(pkcs11.CKA_ID, pkcs11KeyID), pkcs11.NewAttribute(pkcs11.CKA_EC_PARAMS, []byte{0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07}), pkcs11.NewAttribute(pkcs11.CKA_VALUE, ecdsaPrivKeyD), - // 1 is touch enabled - // 2 is pin once - // 4 is pin always - pkcs11.NewAttribute(pkcs11.CKA_VENDOR_DEFINED, 3), + pkcs11.NewAttribute(pkcs11.CKA_VENDOR_DEFINED, yubikeyKeymode), } _, err = ctx.CreateObject(session, certTemplate) @@ -641,6 +659,16 @@ func SetupHSMEnv(libraryPath string) (*pkcs11.Ctx, pkcs11.SessionHandle, error) return p, session, nil } +// YubikeyEnabled returns true if a Yubikey can be accessed +func YubikeyAccessible() bool { + ctx, session, err := SetupHSMEnv(pkcs11Lib) + if err != nil { + return false + } + defer cleanup(ctx, session) + return true +} + func login(ctx *pkcs11.Ctx, session pkcs11.SessionHandle, passRetriever passphrase.Retriever, userFlag uint, defaultPassw string) error { // try default password err := ctx.Login(session, userFlag, defaultPassw)