func TestYubiImportKeyCleansUpOnError(t *testing.T) { if !YubikeyAccessible() { t.Skip("Must have Yubikey access.") } clearAllKeys(t) SetYubikeyKeyMode(KeymodeNone) defer func() { SetYubikeyKeyMode(KeymodeTouch | KeymodePinOnce) }() store, err := NewYubiKeyStore(trustmanager.NewKeyMemoryStore(ret), ret) assert.NoError(t, err) privKey, err := trustmanager.GenerateECDSAKey(rand.Reader) assert.NoError(t, err) pemBytes, err := trustmanager.EncryptPrivateKey(privKey, "passphrase") assert.NoError(t, err) var _importkey = func() error { return store.ImportKey(pemBytes, "root") } testYubiFunctionCleansUpOnLoginError(t, store, _importkey) // all the PKCS11 functions ImportKey depends on that aren't the login/logout testYubiFunctionCleansUpOnSpecifiedErrors(t, store, _importkey, append( setupErrors, "FindObjectsInit", "FindObjects", "FindObjectsFinal", "CreateObject", ), true) // given that everything should have errored, there should be no keys on // the yubikey assert.Len(t, cleanListKeys(t), 0) // Logout should not cause a function failure - it s a cleanup failure, // which shouldn't break anything, and it should clean up after itself. // The key should be added to both stores testYubiFunctionCleansUpOnSpecifiedErrors(t, store, _importkey, []string{"Logout"}, false) listedKeys := cleanListKeys(t) assert.Len(t, listedKeys, 1) // Currently, if GetAttributeValue fails, the function succeeds, because if // we can't get the attribute value of an object, we don't know what slot // it's in, we assume its occupied slot is free (hence this failure will // cause the previous key to be overwritten). This behavior may need to // be revisited. for k := range listedKeys { err := store.RemoveKey(k) assert.NoError(t, err) } testYubiFunctionCleansUpOnSpecifiedErrors(t, store, _importkey, []string{"GetAttributeValue"}, false) assert.Len(t, cleanListKeys(t), 1) }
// Importing a key not as root fails, and it is not added to the backup store func TestYubiImportNonRootKey(t *testing.T) { if !YubikeyAccessible() { t.Skip("Must have Yubikey access.") } clearAllKeys(t) SetYubikeyKeyMode(KeymodeNone) defer func() { SetYubikeyKeyMode(KeymodeTouch | KeymodePinOnce) }() backup := trustmanager.NewKeyMemoryStore(ret) store, err := NewYubiKeyStore(backup, ret) assert.NoError(t, err) // generate key and import it privKey, err := trustmanager.GenerateECDSAKey(rand.Reader) assert.NoError(t, err) pemBytes, err := trustmanager.EncryptPrivateKey(privKey, "passphrase") assert.NoError(t, err) err = store.ImportKey(pemBytes, privKey.ID()) assert.Error(t, err) // key is not in backup store _, _, err = backup.GetKey(privKey.ID()) assert.Error(t, err) }
// Importing an existing key succeeds, but doesn't actually add the key, nor // does it write it to backup. func TestYubiImportExistingKey(t *testing.T) { if !YubikeyAccessible() { t.Skip("Must have Yubikey access.") } clearAllKeys(t) SetYubikeyKeyMode(KeymodeNone) defer func() { SetYubikeyKeyMode(KeymodeTouch | KeymodePinOnce) }() store, err := NewYubiKeyStore(trustmanager.NewKeyMemoryStore(ret), ret) assert.NoError(t, err) key, err := testAddKey(t, store) backup := trustmanager.NewKeyMemoryStore(ret) newStore, err := NewYubiKeyStore(backup, ret) assert.NoError(t, err) // for sanity, ensure that the key is already in the Yubikey k, _, err := newStore.GetKey(key.ID()) assert.NoError(t, err) assert.NotNil(t, k) // import the key, which should have already been added to the yubikey pemBytes, err := trustmanager.EncryptPrivateKey(key, "passphrase") assert.NoError(t, err) err = newStore.ImportKey(pemBytes, "root") assert.NoError(t, err) // key is not in backup store _, _, err = backup.GetKey(key.ID()) assert.Error(t, err) }
// Tests import/export root key only func TestClientKeyImportExportRootOnly(t *testing.T) { // -- setup -- cleanup := setUp(t) defer cleanup() tempDir := tempDirWithConfig(t, "{}") 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, "root", 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", tempFile.Name()) assert.NoError(t, err) // if there is hardware available, root will only be on hardware, and not // on disk newRoot, _ := assertNumKeys(t, tempDir, 1, 0, !rootOnHardware()) 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, !rootOnHardware()) assertSuccessfullyPublish( t, tempDir, server.URL, "gun", target, tempFile.Name()) }
// ImportKey imports a key as root without adding it to the backup store func TestYubiImportNewKey(t *testing.T) { if !YubikeyAccessible() { t.Skip("Must have Yubikey access.") } clearAllKeys(t) SetYubikeyKeyMode(KeymodeNone) defer func() { SetYubikeyKeyMode(KeymodeTouch | KeymodePinOnce) }() backup := trustmanager.NewKeyMemoryStore(ret) store, err := NewYubiKeyStore(backup, ret) assert.NoError(t, err) // generate key and import it privKey, err := trustmanager.GenerateECDSAKey(rand.Reader) assert.NoError(t, err) pemBytes, err := trustmanager.EncryptPrivateKey(privKey, "passphrase") assert.NoError(t, err) err = store.ImportKey(pemBytes, "root") assert.NoError(t, err) // key is not in backup store _, _, err = backup.GetKey(privKey.ID()) assert.Error(t, err) // create a new store, since we want to be sure the original store's cache // is not masking any issues cleanStore, err := NewYubiKeyStore(trustmanager.NewKeyMemoryStore(ret), ret) assert.NoError(t, err) for _, store := range []*YubiKeyStore{store, cleanStore} { gottenKey, role, err := store.GetKey(privKey.ID()) assert.NoError(t, err) assert.Equal(t, data.CanonicalRootRole, role) assert.Equal(t, privKey.Public(), gottenKey.Public()) } }
// Tests importing and exporting keys for all different roles and GUNs func TestClientKeyImportExportAllRoles(t *testing.T) { if rootOnHardware() { t.Log("Cannot import or export a non-root key from hardware. Will skip test.") return } // -- setup -- setUp(t) tempDir := tempDirWithConfig(t, "{}") defer os.RemoveAll(tempDir) server := setupServer() defer server.Close() // -- tests -- _, err := runCommand(t, tempDir, "-s", server.URL, "init", "gun") assert.NoError(t, err) testRoles := append(data.BaseRoles, "targets/releases") // Test importing and exporting keys to all base roles and delegation role for _, role := range testRoles { // Do this while importing keys that have the PEM header role set or have --role set on import for _, setKeyRole := range []bool{true, false} { // Make a new key for this role privKey, err := trustmanager.GenerateECDSAKey(rand.Reader) assert.NoError(t, err) // Make a tempfile for importing tempFile, err := ioutil.TempFile("", "pemfile") assert.NoError(t, err) // Specify the role in the PEM header pemBytes, err := trustmanager.EncryptPrivateKey(privKey, role, testPassphrase) assert.NoError(t, err) ioutil.WriteFile(tempFile.Name(), pemBytes, 0644) // If we need to set the key role with the --role flag, do so on import if setKeyRole { // If it's targets/snapshot we must specify the GUN if role == data.CanonicalTargetsRole || role == data.CanonicalSnapshotRole { _, err = runCommand(t, tempDir, "key", "import", tempFile.Name(), "--gun", "gun", "--role", role) } else { _, err = runCommand(t, tempDir, "key", "import", tempFile.Name(), "--role", role) } } else { // If it's targets/snapshot we must specify the GUN if role == data.CanonicalTargetsRole || role == data.CanonicalSnapshotRole { _, err = runCommand(t, tempDir, "key", "import", tempFile.Name(), "--gun", "gun") } else { _, err = runCommand(t, tempDir, "key", "import", tempFile.Name()) } } assert.NoError(t, err) // Test that we imported correctly keySubdir := getKeySubdir(role, "gun") _, err = os.Stat(filepath.Join(tempDir, keySubdir, privKey.ID()+".key")) assert.Nil(t, err) // Remove the input file so we can test exporting assert.NoError(t, os.Remove(tempFile.Name())) // Make a tempfile for exporting to tempFile, err = ioutil.TempFile("", "pemfile") assert.NoError(t, err) // Ensure exporting this key by ID gets the same key _, err = runCommand(t, tempDir, "key", "export", privKey.ID(), tempFile.Name()) assert.NoError(t, err) // Compare the bytes of the exported file and the root key file in the repo exportedBytes, err := ioutil.ReadFile(tempFile.Name()) assert.NoError(t, err) repoBytes, err := ioutil.ReadFile(filepath.Join(tempDir, keySubdir, privKey.ID()+".key")) assert.NoError(t, err) assert.Equal(t, repoBytes, exportedBytes) // Ensure exporting this key and changing the passphrase works _, err = runCommand(t, tempDir, "key", "export", privKey.ID(), tempFile.Name(), "-p") assert.NoError(t, err) // Remove the export file for cleanup assert.NoError(t, os.Remove(tempFile.Name())) } } }
// Tests import/export root key only func TestClientKeyImportExportRootOnly(t *testing.T) { // -- setup -- setUp(t) tempDir := tempDirWithConfig(t, "{}") defer os.RemoveAll(tempDir) server := setupServer() defer server.Close() var ( target = "sdgkadga" rootKeyID string ) tempFile, err := ioutil.TempFile("", "pemfile") require.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) require.NoError(t, err) pemBytes, err := trustmanager.EncryptPrivateKey(privKey, "root", testPassphrase) require.NoError(t, err) nBytes, err := tempFile.Write(pemBytes) require.NoError(t, err) tempFile.Close() require.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", tempFile.Name()) require.NoError(t, err) // if there is hardware available, root will only be on hardware, and not // on disk newRoot, _ := assertNumKeys(t, tempDir, 1, 0, !rootOnHardware()) require.Equal(t, rootKeyID, newRoot[0]) // Just to make sure, init a repo and publish _, err = runCommand(t, tempDir, "-s", server.URL, "init", "gun") require.NoError(t, err) assertNumKeys(t, tempDir, 1, 2, !rootOnHardware()) assertSuccessfullyPublish( t, tempDir, server.URL, "gun", target, tempFile.Name()) // Now assert that bad root keys give an error // Try importing an unencrypted root key: privKey, err := trustmanager.GenerateECDSAKey(rand.Reader) require.NoError(t, err) decryptedPEMBytes, err := trustmanager.KeyToPEM(privKey, data.CanonicalRootRole) decryptedKeyFile, err := ioutil.TempFile("", "decryptedPem") require.NoError(t, err) // close later, because we might need to write to it defer os.Remove(decryptedKeyFile.Name()) nBytes, err := decryptedKeyFile.Write(decryptedPEMBytes) require.NoError(t, err) decryptedKeyFile.Close() require.Equal(t, len(decryptedPEMBytes), nBytes) // import the key _, err = runCommand(t, tempDir, "key", "import", decryptedKeyFile.Name()) require.Error(t, err) // Now try importing an invalid PEM as a root key invalidPEMBytes := []byte("this is not PEM") invalidPEMFile, err := ioutil.TempFile("", "invalidPem") require.NoError(t, err) // close later, because we might need to write to it defer os.Remove(invalidPEMFile.Name()) nBytes, err = invalidPEMFile.Write(invalidPEMBytes) require.NoError(t, err) invalidPEMFile.Close() require.Equal(t, len(invalidPEMBytes), nBytes) // import the key _, err = runCommand(t, tempDir, "key", "import", invalidPEMFile.Name()) require.Error(t, err) }