// AddKey stores the contents of a PEM-encoded private key as a PEM block func (s *GenericKeyStore) AddKey(keyInfo KeyInfo, privKey data.PrivateKey) error { var ( chosenPassphrase string giveup bool err error pemPrivKey []byte ) s.Lock() defer s.Unlock() if keyInfo.Role == data.CanonicalRootRole || data.IsDelegation(keyInfo.Role) || !data.ValidRole(keyInfo.Role) { keyInfo.Gun = "" } keyID := privKey.ID() for attempts := 0; ; attempts++ { chosenPassphrase, giveup, err = s.PassRetriever(keyID, keyInfo.Role, true, attempts) if err == nil { break } if giveup || attempts > 10 { return ErrAttemptsExceeded{} } } if chosenPassphrase != "" { pemPrivKey, err = utils.EncryptPrivateKey(privKey, keyInfo.Role, keyInfo.Gun, chosenPassphrase) } else { pemPrivKey, err = utils.KeyToPEM(privKey, keyInfo.Role, keyInfo.Gun) } if err != nil { return err } s.cachedKeys[keyID] = &cachedKey{alias: keyInfo.Role, key: privKey} err = s.store.Set(keyID, pemPrivKey) if err != nil { return err } s.keyInfoMap[privKey.ID()] = keyInfo return nil }
func TestKeyStoreInternalState(t *testing.T) { // Temporary directory where test files will be created tempBaseDir, err := ioutil.TempDir("", "notary-test-") require.NoError(t, err, "failed to create a temporary directory") defer os.RemoveAll(tempBaseDir) gun := "docker.com/notary" // Mimic a notary repo setup, and test that bringing up a keyfilestore creates the correct keyInfoMap roles := []string{data.CanonicalRootRole, data.CanonicalTargetsRole, data.CanonicalSnapshotRole, "targets/delegation"} // Keep track of the key IDs for each role, so we can validate later against the keystore state roleToID := make(map[string]string) for _, role := range roles { // generate a key for the role privKey, err := utils.GenerateECDSAKey(rand.Reader) require.NoError(t, err, "could not generate private key") var privKeyPEM []byte // generate the correct PEM role header if role == data.CanonicalRootRole || data.IsDelegation(role) || !data.ValidRole(role) { privKeyPEM, err = utils.KeyToPEM(privKey, role, "") } else { privKeyPEM, err = utils.KeyToPEM(privKey, role, gun) } require.NoError(t, err, "could not generate PEM") // write the key file to the correct location keyPath := filepath.Join(tempBaseDir, notary.PrivDir) keyPath = filepath.Join(keyPath, privKey.ID()) require.NoError(t, os.MkdirAll(filepath.Dir(keyPath), 0755)) require.NoError(t, ioutil.WriteFile(keyPath+".key", privKeyPEM, 0755)) roleToID[role] = privKey.ID() } store, err := NewKeyFileStore(tempBaseDir, passphraseRetriever) require.NoError(t, err) require.Len(t, store.keyInfoMap, 4) for _, role := range roles { keyID, _ := roleToID[role] // make sure this keyID is the right length require.Len(t, keyID, notary.SHA256HexSize) require.Equal(t, role, store.keyInfoMap[keyID].Role) // targets and snapshot keys should have a gun set, root and delegation keys should not if role == data.CanonicalTargetsRole || role == data.CanonicalSnapshotRole { require.Equal(t, gun, store.keyInfoMap[keyID].Gun) } else { require.Empty(t, store.keyInfoMap[keyID].Gun) } } // Try removing the targets key only by ID (no gun provided) require.NoError(t, store.RemoveKey(roleToID[data.CanonicalTargetsRole])) // The key file itself should have been removed _, err = os.Stat(filepath.Join(tempBaseDir, notary.PrivDir, roleToID[data.CanonicalTargetsRole]+".key")) require.Error(t, err) // The keyInfoMap should have also updated by deleting the key _, ok := store.keyInfoMap[roleToID[data.CanonicalTargetsRole]] require.False(t, ok) // Try removing the delegation key only by ID (no gun provided) require.NoError(t, store.RemoveKey(roleToID["targets/delegation"])) // The key file itself should have been removed _, err = os.Stat(filepath.Join(tempBaseDir, notary.PrivDir, roleToID["targets/delegation"]+".key")) require.Error(t, err) // The keyInfoMap should have also updated _, ok = store.keyInfoMap[roleToID["targets/delegation"]] require.False(t, ok) // Try removing the root key only by ID (no gun provided) require.NoError(t, store.RemoveKey(roleToID[data.CanonicalRootRole])) // The key file itself should have been removed _, err = os.Stat(filepath.Join(tempBaseDir, notary.PrivDir, roleToID[data.CanonicalRootRole]+".key")) require.Error(t, err) // The keyInfoMap should have also updated_ _, ok = store.keyInfoMap[roleToID[data.CanonicalRootRole]] require.False(t, ok) // Generate a new targets key and add it with its gun, check that the map gets updated back privKey, err := utils.GenerateECDSAKey(rand.Reader) require.NoError(t, err, "could not generate private key") require.NoError(t, store.AddKey(KeyInfo{Role: data.CanonicalTargetsRole, Gun: gun}, privKey)) require.Equal(t, gun, store.keyInfoMap[privKey.ID()].Gun) require.Equal(t, data.CanonicalTargetsRole, store.keyInfoMap[privKey.ID()].Role) }
func testGetKeyWithRole(t *testing.T, gun, role string) { var testData []byte if gun == "" { testData = []byte(fmt.Sprintf(`-----BEGIN RSA PRIVATE KEY----- role: %s MIIEogIBAAKCAQEAyUIXjsrWRrvPa4Bzp3VJ6uOUGPay2fUpSV8XzNxZxIG/Opdr +k3EQi1im6WOqF3Y5AS1UjYRxNuRN+cAZeo3uS1pOTuoSupBXuchVw8s4hZJ5vXn TRmGb+xY7tZ1ZVgPfAZDib9sRSUsL/gC+aSyprAjG/YBdbF06qKbfOfsoCEYW1OQ 82JqHzQH514RFYPTnEGpvfxWaqmFQLmv0uMxV/cAYvqtrGkXuP0+a8PknlD2obw5 0rHE56Su1c3Q42S7L51K38tpbgWOSRcTfDUWEj5v9wokkNQvyKBwbS996s4EJaZd 7r6M0h1pHnuRxcSaZLYRwgOe1VNGg2VfWzgd5QIDAQABAoIBAF9LGwpygmj1jm3R YXGd+ITugvYbAW5wRb9G9mb6wspnwNsGTYsz/UR0ZudZyaVw4jx8+jnV/i3e5PC6 QRcAgqf8l4EQ/UuThaZg/AlT1yWp9g4UyxNXja87EpTsGKQGwTYxZRM4/xPyWOzR mt8Hm8uPROB9aA2JG9npaoQG8KSUj25G2Qot3ukw/IOtqwN/Sx1EqF0EfCH1K4KU a5TrqlYDFmHbqT1zTRec/BTtVXNsg8xmF94U1HpWf3Lpg0BPYT7JiN2DPoLelRDy a/A+a3ZMRNISL5wbq/jyALLOOyOkIqa+KEOeW3USuePd6RhDMzMm/0ocp5FCwYfo k4DDeaECgYEA0eSMD1dPGo+u8UTD8i7ZsZCS5lmXLNuuAg5f5B/FGghD8ymPROIb dnJL5QSbUpmBsYJ+nnO8RiLrICGBe7BehOitCKi/iiZKJO6edrfNKzhf4XlU0HFl jAOMa975pHjeCoZ1cXJOEO9oW4SWTCyBDBSqH3/ZMgIOiIEk896lSmkCgYEA9Xf5 Jqv3HtQVvjugV/axAh9aI8LMjlfFr9SK7iXpY53UdcylOSWKrrDok3UnrSEykjm7 UL3eCU5jwtkVnEXesNn6DdYo3r43E6iAiph7IBkB5dh0yv3vhIXPgYqyTnpdz4pg 3yPGBHMPnJUBThg1qM7k6a2BKHWySxEgC1DTMB0CgYAGvdmF0J8Y0k6jLzs/9yNE 4cjmHzCM3016gW2xDRgumt9b2xTf+Ic7SbaIV5qJj6arxe49NqhwdESrFohrKaIP kM2l/o2QaWRuRT/Pvl2Xqsrhmh0QSOQjGCYVfOb10nAHVIRHLY22W4o1jk+piLBo a+1+74NRaOGAnu1J6/fRKQKBgAF180+dmlzemjqFlFCxsR/4G8s2r4zxTMXdF+6O 3zKuj8MbsqgCZy7e8qNeARxwpCJmoYy7dITNqJ5SOGSzrb2Trn9ClP+uVhmR2SH6 AlGQlIhPn3JNzI0XVsLIloMNC13ezvDE/7qrDJ677EQQtNEKWiZh1/DrsmHr+irX EkqpAoGAJWe8PC0XK2RE9VkbSPg9Ehr939mOLWiHGYTVWPttUcum/rTKu73/X/mj WxnPWGtzM1pHWypSokW90SP4/xedMxludvBvmz+CTYkNJcBGCrJumy11qJhii9xp EMl3eFOJXjIch/wIesRSN+2dGOsl7neercjMh1i9RvpCwHDx/E0= -----END RSA PRIVATE KEY----- `, role)) } else { testData = []byte(fmt.Sprintf(`-----BEGIN RSA PRIVATE KEY----- gun: %s role: %s MIIEogIBAAKCAQEAyUIXjsrWRrvPa4Bzp3VJ6uOUGPay2fUpSV8XzNxZxIG/Opdr +k3EQi1im6WOqF3Y5AS1UjYRxNuRN+cAZeo3uS1pOTuoSupBXuchVw8s4hZJ5vXn TRmGb+xY7tZ1ZVgPfAZDib9sRSUsL/gC+aSyprAjG/YBdbF06qKbfOfsoCEYW1OQ 82JqHzQH514RFYPTnEGpvfxWaqmFQLmv0uMxV/cAYvqtrGkXuP0+a8PknlD2obw5 0rHE56Su1c3Q42S7L51K38tpbgWOSRcTfDUWEj5v9wokkNQvyKBwbS996s4EJaZd 7r6M0h1pHnuRxcSaZLYRwgOe1VNGg2VfWzgd5QIDAQABAoIBAF9LGwpygmj1jm3R YXGd+ITugvYbAW5wRb9G9mb6wspnwNsGTYsz/UR0ZudZyaVw4jx8+jnV/i3e5PC6 QRcAgqf8l4EQ/UuThaZg/AlT1yWp9g4UyxNXja87EpTsGKQGwTYxZRM4/xPyWOzR mt8Hm8uPROB9aA2JG9npaoQG8KSUj25G2Qot3ukw/IOtqwN/Sx1EqF0EfCH1K4KU a5TrqlYDFmHbqT1zTRec/BTtVXNsg8xmF94U1HpWf3Lpg0BPYT7JiN2DPoLelRDy a/A+a3ZMRNISL5wbq/jyALLOOyOkIqa+KEOeW3USuePd6RhDMzMm/0ocp5FCwYfo k4DDeaECgYEA0eSMD1dPGo+u8UTD8i7ZsZCS5lmXLNuuAg5f5B/FGghD8ymPROIb dnJL5QSbUpmBsYJ+nnO8RiLrICGBe7BehOitCKi/iiZKJO6edrfNKzhf4XlU0HFl jAOMa975pHjeCoZ1cXJOEO9oW4SWTCyBDBSqH3/ZMgIOiIEk896lSmkCgYEA9Xf5 Jqv3HtQVvjugV/axAh9aI8LMjlfFr9SK7iXpY53UdcylOSWKrrDok3UnrSEykjm7 UL3eCU5jwtkVnEXesNn6DdYo3r43E6iAiph7IBkB5dh0yv3vhIXPgYqyTnpdz4pg 3yPGBHMPnJUBThg1qM7k6a2BKHWySxEgC1DTMB0CgYAGvdmF0J8Y0k6jLzs/9yNE 4cjmHzCM3016gW2xDRgumt9b2xTf+Ic7SbaIV5qJj6arxe49NqhwdESrFohrKaIP kM2l/o2QaWRuRT/Pvl2Xqsrhmh0QSOQjGCYVfOb10nAHVIRHLY22W4o1jk+piLBo a+1+74NRaOGAnu1J6/fRKQKBgAF180+dmlzemjqFlFCxsR/4G8s2r4zxTMXdF+6O 3zKuj8MbsqgCZy7e8qNeARxwpCJmoYy7dITNqJ5SOGSzrb2Trn9ClP+uVhmR2SH6 AlGQlIhPn3JNzI0XVsLIloMNC13ezvDE/7qrDJ677EQQtNEKWiZh1/DrsmHr+irX EkqpAoGAJWe8PC0XK2RE9VkbSPg9Ehr939mOLWiHGYTVWPttUcum/rTKu73/X/mj WxnPWGtzM1pHWypSokW90SP4/xedMxludvBvmz+CTYkNJcBGCrJumy11qJhii9xp EMl3eFOJXjIch/wIesRSN+2dGOsl7neercjMh1i9RvpCwHDx/E0= -----END RSA PRIVATE KEY----- `, gun, role)) } testName := "keyID" testExt := "key" perms := os.FileMode(0755) emptyPassphraseRetriever := func(string, string, bool, int) (string, bool, error) { return "", false, nil } // Temporary directory where test files will be created tempBaseDir, err := ioutil.TempDir("", "notary-test-") require.NoError(t, err, "failed to create a temporary directory") defer os.RemoveAll(tempBaseDir) // Since we're generating this manually we need to add the extension '.' filePath := filepath.Join(tempBaseDir, notary.PrivDir, testName+"."+testExt) os.MkdirAll(filepath.Dir(filePath), perms) err = ioutil.WriteFile(filePath, testData, perms) require.NoError(t, err, "failed to write test file") // Create our store store, err := NewKeyFileStore(tempBaseDir, emptyPassphraseRetriever) require.NoError(t, err, "failed to create new key filestore") // Call the GetKey function privKey, _, err := store.GetKey(testName) require.NoError(t, err, "failed to get %s key from store (it's in %s)", role, filepath.Join(tempBaseDir, notary.PrivDir)) pemPrivKey, err := utils.KeyToPEM(privKey, role, gun) require.NoError(t, err, "failed to convert key to PEM") require.Equal(t, testData, pemPrivKey) }