mirror of https://github.com/docker/docs.git
Merge pull request #28 from docker/import_to_yubikey
Import to yubikey Signed-off-by: David Lawrence <david.lawrence@docker.com> Signed-off-by: David Lawrence <dclwrnc@gmail.com> (github: endophage)
This commit is contained in:
commit
28c3eca478
|
|
@ -7,9 +7,10 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
|
"github.com/docker/notary"
|
||||||
"github.com/docker/notary/cryptoservice"
|
"github.com/docker/notary/cryptoservice"
|
||||||
"github.com/docker/notary/keystoremanager"
|
"github.com/docker/notary/keystoremanager"
|
||||||
"github.com/docker/notary/pkg/passphrase"
|
"github.com/docker/notary/passphrase"
|
||||||
"github.com/docker/notary/trustmanager"
|
"github.com/docker/notary/trustmanager"
|
||||||
"github.com/docker/notary/tuf/store"
|
"github.com/docker/notary/tuf/store"
|
||||||
)
|
)
|
||||||
|
|
@ -19,7 +20,7 @@ import (
|
||||||
// (usually ~/.docker/trust/).
|
// (usually ~/.docker/trust/).
|
||||||
func NewNotaryRepository(baseDir, gun, baseURL string, rt http.RoundTripper,
|
func NewNotaryRepository(baseDir, gun, baseURL string, rt http.RoundTripper,
|
||||||
retriever passphrase.Retriever) (*NotaryRepository, error) {
|
retriever passphrase.Retriever) (*NotaryRepository, error) {
|
||||||
keysPath := filepath.Join(baseDir, keystoremanager.PrivDir)
|
keysPath := filepath.Join(baseDir, notary.PrivDir)
|
||||||
fileKeyStore, err := trustmanager.NewKeyFileStore(keysPath, retriever)
|
fileKeyStore, err := trustmanager.NewKeyFileStore(keysPath, retriever)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to create private key store in directory: %s", keysPath)
|
return nil, fmt.Errorf("failed to create private key store in directory: %s", keysPath)
|
||||||
|
|
|
||||||
|
|
@ -7,11 +7,13 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
|
"github.com/docker/notary"
|
||||||
"github.com/docker/notary/cryptoservice"
|
"github.com/docker/notary/cryptoservice"
|
||||||
"github.com/docker/notary/keystoremanager"
|
"github.com/docker/notary/keystoremanager"
|
||||||
"github.com/docker/notary/pkg/passphrase"
|
"github.com/docker/notary/passphrase"
|
||||||
"github.com/docker/notary/signer/api"
|
"github.com/docker/notary/signer/api"
|
||||||
"github.com/docker/notary/trustmanager"
|
"github.com/docker/notary/trustmanager"
|
||||||
|
"github.com/docker/notary/tuf/signed"
|
||||||
"github.com/docker/notary/tuf/store"
|
"github.com/docker/notary/tuf/store"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -21,15 +23,20 @@ import (
|
||||||
func NewNotaryRepository(baseDir, gun, baseURL string, rt http.RoundTripper,
|
func NewNotaryRepository(baseDir, gun, baseURL string, rt http.RoundTripper,
|
||||||
retriever passphrase.Retriever) (*NotaryRepository, error) {
|
retriever passphrase.Retriever) (*NotaryRepository, error) {
|
||||||
|
|
||||||
keysPath := filepath.Join(baseDir, keystoremanager.PrivDir)
|
keysPath := filepath.Join(baseDir, notary.PrivDir)
|
||||||
fileKeyStore, err := trustmanager.NewKeyFileStore(keysPath, retriever)
|
fileKeyStore, err := trustmanager.NewKeyFileStore(keysPath, retriever)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to create private key store in directory: %s", keysPath)
|
return nil, fmt.Errorf("failed to create private key store in directory: %s", keysPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
keyStoreManager, err := keystoremanager.NewKeyStoreManager(baseDir, fileKeyStore)
|
keyStoreManager, err := keystoremanager.NewKeyStoreManager(baseDir, fileKeyStore)
|
||||||
yubiKeyStore := api.NewYubiKeyStore(retriever)
|
yubiKeyStore, _ := api.NewYubiKeyStore(fileKeyStore, retriever)
|
||||||
cryptoService := cryptoservice.NewCryptoService(gun, yubiKeyStore, keyStoreManager.KeyStore)
|
var cryptoService signed.CryptoService
|
||||||
|
if yubiKeyStore == nil {
|
||||||
|
cryptoService = cryptoservice.NewCryptoService(gun, keyStoreManager.KeyStore)
|
||||||
|
} else {
|
||||||
|
cryptoService = cryptoservice.NewCryptoService(gun, yubiKeyStore, keyStoreManager.KeyStore)
|
||||||
|
}
|
||||||
|
|
||||||
nRepo := &NotaryRepository{
|
nRepo := &NotaryRepository{
|
||||||
gun: gun,
|
gun: gun,
|
||||||
|
|
|
||||||
|
|
@ -131,20 +131,6 @@ func main() {
|
||||||
|
|
||||||
cryptoServices := make(signer.CryptoServiceIndex)
|
cryptoServices := make(signer.CryptoServiceIndex)
|
||||||
|
|
||||||
pin := mainViper.GetString(pinCode)
|
|
||||||
pkcs11Lib := mainViper.GetString("crypto.pkcs11lib")
|
|
||||||
if pkcs11Lib != "" {
|
|
||||||
if pin == "" {
|
|
||||||
log.Fatalf("Using PIN is mandatory with pkcs11")
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx, session := SetupHSMEnv(pkcs11Lib, pin)
|
|
||||||
|
|
||||||
defer cleanup(ctx, session)
|
|
||||||
|
|
||||||
cryptoServices[data.RSAKey] = api.NewRSAHardwareCryptoService(ctx, session)
|
|
||||||
}
|
|
||||||
|
|
||||||
configDBType := strings.ToLower(mainViper.GetString("storage.backend"))
|
configDBType := strings.ToLower(mainViper.GetString("storage.backend"))
|
||||||
dbURL := mainViper.GetString("storage.db_url")
|
dbURL := mainViper.GetString("storage.db_url")
|
||||||
if configDBType != dbType || dbURL == "" {
|
if configDBType != dbType || dbURL == "" {
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/docker/notary"
|
||||||
"github.com/docker/notary/keystoremanager"
|
"github.com/docker/notary/keystoremanager"
|
||||||
"github.com/docker/notary/trustmanager"
|
"github.com/docker/notary/trustmanager"
|
||||||
|
|
||||||
|
|
@ -56,7 +57,7 @@ func certRemove(cmd *cobra.Command, args []string) {
|
||||||
|
|
||||||
parseConfig()
|
parseConfig()
|
||||||
|
|
||||||
keysPath := filepath.Join(trustDir, keystoremanager.PrivDir)
|
keysPath := filepath.Join(trustDir, notary.PrivDir)
|
||||||
fileKeyStore, err := trustmanager.NewKeyFileStore(keysPath, retriever)
|
fileKeyStore, err := trustmanager.NewKeyFileStore(keysPath, retriever)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fatalf("failed to create private key store in directory: %s", keysPath)
|
fatalf("failed to create private key store in directory: %s", keysPath)
|
||||||
|
|
@ -125,7 +126,7 @@ func certList(cmd *cobra.Command, args []string) {
|
||||||
|
|
||||||
parseConfig()
|
parseConfig()
|
||||||
|
|
||||||
keysPath := filepath.Join(trustDir, keystoremanager.PrivDir)
|
keysPath := filepath.Join(trustDir, notary.PrivDir)
|
||||||
fileKeyStore, err := trustmanager.NewKeyFileStore(keysPath, retriever)
|
fileKeyStore, err := trustmanager.NewKeyFileStore(keysPath, retriever)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fatalf("failed to create private key store in directory: %s", keysPath)
|
fatalf("failed to create private key store in directory: %s", keysPath)
|
||||||
|
|
|
||||||
|
|
@ -8,20 +8,20 @@ import (
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/docker/notary"
|
||||||
notaryclient "github.com/docker/notary/client"
|
notaryclient "github.com/docker/notary/client"
|
||||||
"github.com/docker/notary/keystoremanager"
|
"github.com/docker/notary/cryptoservice"
|
||||||
"github.com/docker/notary/pkg/passphrase"
|
"github.com/docker/notary/passphrase"
|
||||||
|
"github.com/docker/notary/signer/api"
|
||||||
"github.com/docker/notary/trustmanager"
|
"github.com/docker/notary/trustmanager"
|
||||||
|
|
||||||
|
"github.com/docker/notary/tuf/data"
|
||||||
|
"github.com/docker/notary/tuf/signed"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
cmdKey.AddCommand(cmdKeyList)
|
cmdKey.AddCommand(cmdKeyList)
|
||||||
cmdKey.AddCommand(cmdKeyRemoveKey)
|
|
||||||
cmdKeyRemoveKey.Flags().StringVarP(&keyRemoveGUN, "gun", "g", "", "Globally unique name to remove keys for")
|
|
||||||
cmdKeyRemoveKey.Flags().BoolVarP(&keyRemoveRoot, "root", "r", false, "Remove root keys")
|
|
||||||
cmdKeyRemoveKey.Flags().BoolVarP(&keyRemoveYes, "yes", "y", false, "Answer yes to the removal question (no confirmation)")
|
|
||||||
cmdKey.AddCommand(cmdKeyGenerateRootKey)
|
cmdKey.AddCommand(cmdKeyGenerateRootKey)
|
||||||
|
|
||||||
cmdKeyExport.Flags().StringVarP(&keysExportGUN, "gun", "g", "", "Globally unique name to export keys for")
|
cmdKeyExport.Flags().StringVarP(&keysExportGUN, "gun", "g", "", "Globally unique name to export keys for")
|
||||||
|
|
@ -53,17 +53,6 @@ var cmdRotateKey = &cobra.Command{
|
||||||
Run: keysRotate,
|
Run: keysRotate,
|
||||||
}
|
}
|
||||||
|
|
||||||
var keyRemoveGUN string
|
|
||||||
var keyRemoveRoot bool
|
|
||||||
var keyRemoveYes bool
|
|
||||||
|
|
||||||
var cmdKeyRemoveKey = &cobra.Command{
|
|
||||||
Use: "remove [ keyID ]",
|
|
||||||
Short: "Removes the key with the given keyID.",
|
|
||||||
Long: "remove the key with the given keyID from the local host.",
|
|
||||||
Run: keysRemoveKey,
|
|
||||||
}
|
|
||||||
|
|
||||||
var cmdKeyGenerateRootKey = &cobra.Command{
|
var cmdKeyGenerateRootKey = &cobra.Command{
|
||||||
Use: "generate [ algorithm ]",
|
Use: "generate [ algorithm ]",
|
||||||
Short: "Generates a new root key with a given algorithm.",
|
Short: "Generates a new root key with a given algorithm.",
|
||||||
|
|
@ -103,68 +92,6 @@ var cmdKeyImportRoot = &cobra.Command{
|
||||||
Run: keysImportRoot,
|
Run: keysImportRoot,
|
||||||
}
|
}
|
||||||
|
|
||||||
// keysRemoveKey deletes a private key based on ID
|
|
||||||
func keysRemoveKey(cmd *cobra.Command, args []string) {
|
|
||||||
if len(args) < 1 {
|
|
||||||
cmd.Usage()
|
|
||||||
fatalf("must specify the key ID of the key to remove")
|
|
||||||
}
|
|
||||||
|
|
||||||
parseConfig()
|
|
||||||
|
|
||||||
keysPath := filepath.Join(trustDir, keystoremanager.PrivDir)
|
|
||||||
fileKeyStore, err := trustmanager.NewKeyFileStore(keysPath, retriever)
|
|
||||||
if err != nil {
|
|
||||||
fatalf("failed to create private key store in directory: %s", keysPath)
|
|
||||||
}
|
|
||||||
keyStoreManager, err := keystoremanager.NewKeyStoreManager(trustDir, fileKeyStore)
|
|
||||||
if err != nil {
|
|
||||||
fatalf("failed to create a new truststore manager with directory: %s", trustDir)
|
|
||||||
}
|
|
||||||
|
|
||||||
keyID := args[0]
|
|
||||||
|
|
||||||
// This is an invalid ID
|
|
||||||
if len(keyID) != idSize {
|
|
||||||
fatalf("invalid key ID provided: %s", keyID)
|
|
||||||
}
|
|
||||||
|
|
||||||
// List the key about to be removed
|
|
||||||
fmt.Println("Are you sure you want to remove the following key?")
|
|
||||||
fmt.Printf("%s\n(yes/no)\n", keyID)
|
|
||||||
|
|
||||||
// Ask for confirmation before removing the key, unless -y is passed
|
|
||||||
if !keyRemoveYes {
|
|
||||||
confirmed := askConfirm()
|
|
||||||
if !confirmed {
|
|
||||||
fatalf("aborting action.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Choose the correct filestore to remove the key from
|
|
||||||
keyMap := keyStoreManager.KeyStore.ListKeys()
|
|
||||||
|
|
||||||
// Attempt to find the full GUN to the key in the map
|
|
||||||
// This is irrelevant for removing root keys, but does no harm
|
|
||||||
var keyWithGUN string
|
|
||||||
for k := range keyMap {
|
|
||||||
if filepath.Base(k) == keyID {
|
|
||||||
keyWithGUN = k
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If empty, we didn't find any matches
|
|
||||||
if keyWithGUN == "" {
|
|
||||||
fatalf("key with key ID: %s not found\n", keyID)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Attempt to remove the key
|
|
||||||
err = keyStoreManager.KeyStore.RemoveKey(keyWithGUN)
|
|
||||||
if err != nil {
|
|
||||||
fatalf("failed to remove key with key ID: %s, %v", keyID, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func keysList(cmd *cobra.Command, args []string) {
|
func keysList(cmd *cobra.Command, args []string) {
|
||||||
if len(args) > 0 {
|
if len(args) > 0 {
|
||||||
cmd.Usage()
|
cmd.Usage()
|
||||||
|
|
@ -173,18 +100,21 @@ func keysList(cmd *cobra.Command, args []string) {
|
||||||
|
|
||||||
parseConfig()
|
parseConfig()
|
||||||
|
|
||||||
keysPath := filepath.Join(trustDir, keystoremanager.PrivDir)
|
keysPath := filepath.Join(trustDir, notary.PrivDir)
|
||||||
fileKeyStore, err := trustmanager.NewKeyFileStore(keysPath, retriever)
|
fileKeyStore, err := trustmanager.NewKeyFileStore(keysPath, retriever)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fatalf("failed to create private key store in directory: %s", keysPath)
|
fatalf("failed to create private key store in directory: %s", keysPath)
|
||||||
}
|
}
|
||||||
keyStoreManager, err := keystoremanager.NewKeyStoreManager(trustDir, fileKeyStore)
|
yubiStore, _ := api.NewYubiKeyStore(fileKeyStore, retriever)
|
||||||
if err != nil {
|
var cs signed.CryptoService
|
||||||
fatalf("failed to create a new truststore manager with directory: %s", trustDir)
|
if yubiStore == nil {
|
||||||
|
cs = cryptoservice.NewCryptoService("", fileKeyStore)
|
||||||
|
} else {
|
||||||
|
cs = cryptoservice.NewCryptoService("", yubiStore, fileKeyStore)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get a map of all the keys/roles
|
// Get a map of all the keys/roles
|
||||||
keysMap := keyStoreManager.KeyStore.ListKeys()
|
keysMap := cs.ListAllKeys()
|
||||||
|
|
||||||
fmt.Println("")
|
fmt.Println("")
|
||||||
fmt.Println("# Root keys: ")
|
fmt.Println("# Root keys: ")
|
||||||
|
|
@ -231,22 +161,26 @@ func keysGenerateRootKey(cmd *cobra.Command, args []string) {
|
||||||
|
|
||||||
parseConfig()
|
parseConfig()
|
||||||
|
|
||||||
keysPath := filepath.Join(trustDir, keystoremanager.PrivDir)
|
keysPath := filepath.Join(trustDir, notary.PrivDir)
|
||||||
fileKeyStore, err := trustmanager.NewKeyFileStore(keysPath, retriever)
|
fileKeyStore, err := trustmanager.NewKeyFileStore(keysPath, retriever)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fatalf("failed to create private key store in directory: %s", keysPath)
|
fatalf("failed to create private key store in directory: %s", keysPath)
|
||||||
}
|
}
|
||||||
keyStoreManager, err := keystoremanager.NewKeyStoreManager(trustDir, fileKeyStore)
|
yubiStore, err := api.NewYubiKeyStore(fileKeyStore, retriever)
|
||||||
|
var cs signed.CryptoService
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fatalf("failed to create a new truststore manager with directory: %s", trustDir)
|
cmd.Printf("No Yubikey detected, importing to local filesystem.")
|
||||||
|
cs = cryptoservice.NewCryptoService("", fileKeyStore)
|
||||||
|
} else {
|
||||||
|
cs = cryptoservice.NewCryptoService("", yubiStore, fileKeyStore)
|
||||||
}
|
}
|
||||||
|
|
||||||
keyID, err := keyStoreManager.GenRootKey(algorithm)
|
pubKey, err := cs.Create(data.CanonicalRootRole, algorithm)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fatalf("failed to create a new root key: %v", err)
|
fatalf("failed to create a new root key: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("Generated new %s key with keyID: %s\n", algorithm, keyID)
|
fmt.Printf("Generated new %s root key with keyID: %s\n", algorithm, pubKey.ID())
|
||||||
}
|
}
|
||||||
|
|
||||||
// keysExport exports a collection of keys to a ZIP file
|
// keysExport exports a collection of keys to a ZIP file
|
||||||
|
|
@ -260,15 +194,12 @@ func keysExport(cmd *cobra.Command, args []string) {
|
||||||
|
|
||||||
parseConfig()
|
parseConfig()
|
||||||
|
|
||||||
keysPath := filepath.Join(trustDir, keystoremanager.PrivDir)
|
keysPath := filepath.Join(trustDir, notary.PrivDir)
|
||||||
fileKeyStore, err := trustmanager.NewKeyFileStore(keysPath, retriever)
|
fileKeyStore, err := trustmanager.NewKeyFileStore(keysPath, retriever)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fatalf("failed to create private key store in directory: %s", keysPath)
|
fatalf("failed to create private key store in directory: %s", keysPath)
|
||||||
}
|
}
|
||||||
keyStoreManager, err := keystoremanager.NewKeyStoreManager(trustDir, fileKeyStore)
|
cs := cryptoservice.NewCryptoService("", fileKeyStore)
|
||||||
if err != nil {
|
|
||||||
fatalf("failed to create a new truststore manager with directory: %s", trustDir)
|
|
||||||
}
|
|
||||||
|
|
||||||
exportFile, err := os.Create(exportFilename)
|
exportFile, err := os.Create(exportFilename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -279,9 +210,9 @@ func keysExport(cmd *cobra.Command, args []string) {
|
||||||
// unlocking passphrase and reusing that.
|
// unlocking passphrase and reusing that.
|
||||||
exportRetriever := passphrase.PromptRetriever()
|
exportRetriever := passphrase.PromptRetriever()
|
||||||
if keysExportGUN != "" {
|
if keysExportGUN != "" {
|
||||||
err = keyStoreManager.ExportKeysByGUN(exportFile, keysExportGUN, exportRetriever)
|
err = cs.ExportKeysByGUN(exportFile, keysExportGUN, exportRetriever)
|
||||||
} else {
|
} else {
|
||||||
err = keyStoreManager.ExportAllKeys(exportFile, exportRetriever)
|
err = cs.ExportAllKeys(exportFile, exportRetriever)
|
||||||
}
|
}
|
||||||
|
|
||||||
exportFile.Close()
|
exportFile.Close()
|
||||||
|
|
@ -308,15 +239,12 @@ func keysExportRoot(cmd *cobra.Command, args []string) {
|
||||||
|
|
||||||
parseConfig()
|
parseConfig()
|
||||||
|
|
||||||
keysPath := filepath.Join(trustDir, keystoremanager.PrivDir)
|
keysPath := filepath.Join(trustDir, notary.PrivDir)
|
||||||
fileKeyStore, err := trustmanager.NewKeyFileStore(keysPath, retriever)
|
fileKeyStore, err := trustmanager.NewKeyFileStore(keysPath, retriever)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fatalf("failed to create private key store in directory: %s", keysPath)
|
fatalf("failed to create private key store in directory: %s", keysPath)
|
||||||
}
|
}
|
||||||
keyStoreManager, err := keystoremanager.NewKeyStoreManager(trustDir, fileKeyStore)
|
cs := cryptoservice.NewCryptoService("", fileKeyStore)
|
||||||
if err != nil {
|
|
||||||
fatalf("failed to create a new truststore manager with directory: %s", trustDir)
|
|
||||||
}
|
|
||||||
|
|
||||||
exportFile, err := os.Create(exportFilename)
|
exportFile, err := os.Create(exportFilename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -326,9 +254,9 @@ func keysExportRoot(cmd *cobra.Command, args []string) {
|
||||||
// Must use a different passphrase retriever to avoid caching the
|
// Must use a different passphrase retriever to avoid caching the
|
||||||
// unlocking passphrase and reusing that.
|
// unlocking passphrase and reusing that.
|
||||||
exportRetriever := passphrase.PromptRetriever()
|
exportRetriever := passphrase.PromptRetriever()
|
||||||
err = keyStoreManager.ExportRootKeyReencrypt(exportFile, keyID, exportRetriever)
|
err = cs.ExportRootKeyReencrypt(exportFile, keyID, exportRetriever)
|
||||||
} else {
|
} else {
|
||||||
err = keyStoreManager.ExportRootKey(exportFile, keyID)
|
err = cs.ExportRootKey(exportFile, keyID)
|
||||||
}
|
}
|
||||||
exportFile.Close()
|
exportFile.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -348,15 +276,12 @@ func keysImport(cmd *cobra.Command, args []string) {
|
||||||
|
|
||||||
parseConfig()
|
parseConfig()
|
||||||
|
|
||||||
keysPath := filepath.Join(trustDir, keystoremanager.PrivDir)
|
keysPath := filepath.Join(trustDir, notary.PrivDir)
|
||||||
fileKeyStore, err := trustmanager.NewKeyFileStore(keysPath, retriever)
|
fileKeyStore, err := trustmanager.NewKeyFileStore(keysPath, retriever)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fatalf("failed to create private key store in directory: %s", keysPath)
|
fatalf("failed to create private key store in directory: %s", keysPath)
|
||||||
}
|
}
|
||||||
keyStoreManager, err := keystoremanager.NewKeyStoreManager(trustDir, fileKeyStore)
|
cs := cryptoservice.NewCryptoService("", fileKeyStore)
|
||||||
if err != nil {
|
|
||||||
fatalf("failed to create a new truststore manager with directory: %s", trustDir)
|
|
||||||
}
|
|
||||||
|
|
||||||
zipReader, err := zip.OpenReader(importFilename)
|
zipReader, err := zip.OpenReader(importFilename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -364,7 +289,7 @@ func keysImport(cmd *cobra.Command, args []string) {
|
||||||
}
|
}
|
||||||
defer zipReader.Close()
|
defer zipReader.Close()
|
||||||
|
|
||||||
err = keyStoreManager.ImportKeysZip(zipReader.Reader)
|
err = cs.ImportKeysZip(zipReader.Reader)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fatalf("error importing keys: %v", err)
|
fatalf("error importing keys: %v", err)
|
||||||
|
|
@ -382,14 +307,18 @@ func keysImportRoot(cmd *cobra.Command, args []string) {
|
||||||
|
|
||||||
parseConfig()
|
parseConfig()
|
||||||
|
|
||||||
keysPath := filepath.Join(trustDir, keystoremanager.PrivDir)
|
keysPath := filepath.Join(trustDir, notary.PrivDir)
|
||||||
fileKeyStore, err := trustmanager.NewKeyFileStore(keysPath, retriever)
|
fileKeyStore, err := trustmanager.NewKeyFileStore(keysPath, retriever)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fatalf("failed to create private key store in directory: %s", keysPath)
|
fatalf("failed to create private key store in directory: %s", keysPath)
|
||||||
}
|
}
|
||||||
keyStoreManager, err := keystoremanager.NewKeyStoreManager(trustDir, fileKeyStore)
|
yubiStore, err := api.NewYubiKeyStore(fileKeyStore, retriever)
|
||||||
|
var cs signed.CryptoService
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fatalf("failed to create a new truststore manager with directory: %s", trustDir)
|
cmd.Printf("No Yubikey detected, importing to local filesystem.")
|
||||||
|
cs = cryptoservice.NewCryptoService("", fileKeyStore)
|
||||||
|
} else {
|
||||||
|
cs = cryptoservice.NewCryptoService("", yubiStore, fileKeyStore)
|
||||||
}
|
}
|
||||||
|
|
||||||
importFile, err := os.Open(importFilename)
|
importFile, err := os.Open(importFilename)
|
||||||
|
|
@ -398,7 +327,7 @@ func keysImportRoot(cmd *cobra.Command, args []string) {
|
||||||
}
|
}
|
||||||
defer importFile.Close()
|
defer importFile.Close()
|
||||||
|
|
||||||
err = keyStoreManager.ImportRootKey(importFile)
|
err = cs.ImportRootKey(importFile)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fatalf("error importing root key: %v", err)
|
fatalf("error importing root key: %v", err)
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/Sirupsen/logrus"
|
"github.com/Sirupsen/logrus"
|
||||||
"github.com/docker/notary/pkg/passphrase"
|
"github.com/docker/notary/passphrase"
|
||||||
"github.com/docker/notary/version"
|
"github.com/docker/notary/version"
|
||||||
homedir "github.com/mitchellh/go-homedir"
|
homedir "github.com/mitchellh/go-homedir"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
package notary
|
||||||
|
|
||||||
|
// application wide constants
|
||||||
|
const (
|
||||||
|
BackupDir = "backup"
|
||||||
|
PrivDir = "private"
|
||||||
|
PrivKeyPerms = 0700
|
||||||
|
PubCertPerms = 0755
|
||||||
|
)
|
||||||
|
|
@ -27,7 +27,7 @@ func NewCryptoService(gun string, keyStores ...trustmanager.KeyStore) *CryptoSer
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create is used to generate keys for targets, snapshots and timestamps
|
// Create is used to generate keys for targets, snapshots and timestamps
|
||||||
func (ccs *CryptoService) Create(role, algorithm string) (data.PublicKey, error) {
|
func (cs *CryptoService) Create(role, algorithm string) (data.PublicKey, error) {
|
||||||
var privKey data.PrivateKey
|
var privKey data.PrivateKey
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
|
|
@ -57,10 +57,10 @@ func (ccs *CryptoService) Create(role, algorithm string) (data.PublicKey, error)
|
||||||
if role == data.CanonicalRootRole {
|
if role == data.CanonicalRootRole {
|
||||||
keyPath = privKey.ID()
|
keyPath = privKey.ID()
|
||||||
} else {
|
} else {
|
||||||
keyPath = filepath.Join(ccs.gun, privKey.ID())
|
keyPath = filepath.Join(cs.gun, privKey.ID())
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, ks := range ccs.keyStores {
|
for _, ks := range cs.keyStores {
|
||||||
err = ks.AddKey(keyPath, role, privKey)
|
err = ks.AddKey(keyPath, role, privKey)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return data.PublicKeyFromPrivate(privKey), nil
|
return data.PublicKeyFromPrivate(privKey), nil
|
||||||
|
|
@ -77,11 +77,11 @@ func (ccs *CryptoService) Create(role, algorithm string) (data.PublicKey, error)
|
||||||
// without a GUN (in which case it's a root key). If that fails, try to get
|
// without a GUN (in which case it's a root key). If that fails, try to get
|
||||||
// the key with the GUN (non-root key).
|
// the key with the GUN (non-root key).
|
||||||
// If that fails, then we don't have the key.
|
// If that fails, then we don't have the key.
|
||||||
func (ccs *CryptoService) GetPrivateKey(keyID string) (k data.PrivateKey, id string, err error) {
|
func (cs *CryptoService) GetPrivateKey(keyID string) (k data.PrivateKey, role string, err error) {
|
||||||
keyPaths := []string{keyID, filepath.Join(ccs.gun, keyID)}
|
keyPaths := []string{keyID, filepath.Join(cs.gun, keyID)}
|
||||||
for _, ks := range ccs.keyStores {
|
for _, ks := range cs.keyStores {
|
||||||
for _, keyPath := range keyPaths {
|
for _, keyPath := range keyPaths {
|
||||||
k, id, err = ks.GetKey(keyPath)
|
k, role, err = ks.GetKey(keyPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
@ -92,8 +92,8 @@ func (ccs *CryptoService) GetPrivateKey(keyID string) (k data.PrivateKey, id str
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetKey returns a key by ID
|
// GetKey returns a key by ID
|
||||||
func (ccs *CryptoService) GetKey(keyID string) data.PublicKey {
|
func (cs *CryptoService) GetKey(keyID string) data.PublicKey {
|
||||||
privKey, _, err := ccs.GetPrivateKey(keyID)
|
privKey, _, err := cs.GetPrivateKey(keyID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
@ -101,16 +101,11 @@ func (ccs *CryptoService) GetKey(keyID string) data.PublicKey {
|
||||||
}
|
}
|
||||||
|
|
||||||
// RemoveKey deletes a key by ID
|
// RemoveKey deletes a key by ID
|
||||||
func (ccs *CryptoService) RemoveKey(keyID string) (err error) {
|
func (cs *CryptoService) RemoveKey(keyID string) (err error) {
|
||||||
keyPaths := []string{keyID, filepath.Join(ccs.gun, keyID)}
|
keyPaths := []string{keyID, filepath.Join(cs.gun, keyID)}
|
||||||
for _, ks := range ccs.keyStores {
|
for _, ks := range cs.keyStores {
|
||||||
for _, keyPath := range keyPaths {
|
for _, keyPath := range keyPaths {
|
||||||
_, _, err = ks.GetKey(keyPath)
|
ks.RemoveKey(keyPath)
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
err = ks.RemoveKey(keyPath)
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return // returns whatever the final values were
|
return // returns whatever the final values were
|
||||||
|
|
@ -119,12 +114,12 @@ func (ccs *CryptoService) RemoveKey(keyID string) (err error) {
|
||||||
// Sign returns the signatures for the payload with a set of keyIDs. It ignores
|
// Sign returns the signatures for the payload with a set of keyIDs. It ignores
|
||||||
// errors to sign and expects the called to validate if the number of returned
|
// errors to sign and expects the called to validate if the number of returned
|
||||||
// signatures is adequate.
|
// signatures is adequate.
|
||||||
func (ccs *CryptoService) Sign(keyIDs []string, payload []byte) ([]data.Signature, error) {
|
func (cs *CryptoService) Sign(keyIDs []string, payload []byte) ([]data.Signature, error) {
|
||||||
signatures := make([]data.Signature, 0, len(keyIDs))
|
signatures := make([]data.Signature, 0, len(keyIDs))
|
||||||
for _, keyid := range keyIDs {
|
for _, keyid := range keyIDs {
|
||||||
keyName := keyid
|
keyName := keyid
|
||||||
|
|
||||||
privKey, _, err := ccs.GetPrivateKey(keyName)
|
privKey, _, err := cs.GetPrivateKey(keyName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logrus.Debugf("error attempting to retrieve private key: %s, %v", keyid, err)
|
logrus.Debugf("error attempting to retrieve private key: %s, %v", keyid, err)
|
||||||
continue
|
continue
|
||||||
|
|
@ -152,9 +147,9 @@ func (ccs *CryptoService) Sign(keyIDs []string, payload []byte) ([]data.Signatur
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListKeys returns a list of key IDs valid for the given role
|
// ListKeys returns a list of key IDs valid for the given role
|
||||||
func (ccs *CryptoService) ListKeys(role string) []string {
|
func (cs *CryptoService) ListKeys(role string) []string {
|
||||||
var res []string
|
var res []string
|
||||||
for _, ks := range ccs.keyStores {
|
for _, ks := range cs.keyStores {
|
||||||
for k, r := range ks.ListKeys() {
|
for k, r := range ks.ListKeys() {
|
||||||
if r == role {
|
if r == role {
|
||||||
res = append(res, k)
|
res = append(res, k)
|
||||||
|
|
@ -163,3 +158,14 @@ func (ccs *CryptoService) ListKeys(role string) []string {
|
||||||
}
|
}
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ListAllKeys returns a map of key IDs to role
|
||||||
|
func (cs *CryptoService) ListAllKeys() map[string]string {
|
||||||
|
res := make(map[string]string)
|
||||||
|
for _, ks := range cs.keyStores {
|
||||||
|
for k, r := range ks.ListKeys() {
|
||||||
|
res[k] = r // keys are content addressed so don't care about overwrites
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package keystoremanager
|
package cryptoservice
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"archive/zip"
|
"archive/zip"
|
||||||
|
|
@ -11,7 +11,8 @@ import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/docker/notary/pkg/passphrase"
|
"github.com/docker/notary"
|
||||||
|
"github.com/docker/notary/passphrase"
|
||||||
"github.com/docker/notary/trustmanager"
|
"github.com/docker/notary/trustmanager"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -33,11 +34,22 @@ var (
|
||||||
|
|
||||||
// ExportRootKey exports the specified root key to an io.Writer in PEM format.
|
// ExportRootKey exports the specified root key to an io.Writer in PEM format.
|
||||||
// The key's existing encryption is preserved.
|
// The key's existing encryption is preserved.
|
||||||
func (km *KeyStoreManager) ExportRootKey(dest io.Writer, keyID string) error {
|
func (cs *CryptoService) ExportRootKey(dest io.Writer, keyID string) error {
|
||||||
pemBytes, err := km.KeyStore.ExportKey(keyID)
|
var (
|
||||||
|
pemBytes []byte
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
|
||||||
|
for _, ks := range cs.keyStores {
|
||||||
|
pemBytes, err = ks.ExportKey(keyID)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
nBytes, err := dest.Write(pemBytes)
|
nBytes, err := dest.Write(pemBytes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
@ -50,8 +62,8 @@ func (km *KeyStoreManager) ExportRootKey(dest io.Writer, keyID string) error {
|
||||||
|
|
||||||
// ExportRootKeyReencrypt exports the specified root key to an io.Writer in
|
// ExportRootKeyReencrypt exports the specified root key to an io.Writer in
|
||||||
// PEM format. The key is reencrypted with a new passphrase.
|
// PEM format. The key is reencrypted with a new passphrase.
|
||||||
func (km *KeyStoreManager) ExportRootKeyReencrypt(dest io.Writer, keyID string, newPassphraseRetriever passphrase.Retriever) error {
|
func (cs *CryptoService) ExportRootKeyReencrypt(dest io.Writer, keyID string, newPassphraseRetriever passphrase.Retriever) error {
|
||||||
privateKey, alias, err := km.KeyStore.GetKey(keyID)
|
privateKey, role, err := cs.GetPrivateKey(keyID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -60,13 +72,13 @@ func (km *KeyStoreManager) ExportRootKeyReencrypt(dest io.Writer, keyID string,
|
||||||
tempBaseDir, err := ioutil.TempDir("", "notary-key-export-")
|
tempBaseDir, err := ioutil.TempDir("", "notary-key-export-")
|
||||||
defer os.RemoveAll(tempBaseDir)
|
defer os.RemoveAll(tempBaseDir)
|
||||||
|
|
||||||
tempKeysPath := filepath.Join(tempBaseDir, PrivDir)
|
tempKeysPath := filepath.Join(tempBaseDir, notary.PrivDir)
|
||||||
tempKeyStore, err := trustmanager.NewKeyFileStore(tempKeysPath, newPassphraseRetriever)
|
tempKeyStore, err := trustmanager.NewKeyFileStore(tempKeysPath, newPassphraseRetriever)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = tempKeyStore.AddKey(keyID, alias, privateKey)
|
err = tempKeyStore.AddKey(keyID, role, privateKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -85,25 +97,10 @@ func (km *KeyStoreManager) ExportRootKeyReencrypt(dest io.Writer, keyID string,
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// checkRootKeyIsEncrypted makes sure the root key is encrypted. We have
|
|
||||||
// internal assumptions that depend on this.
|
|
||||||
func checkRootKeyIsEncrypted(pemBytes []byte) error {
|
|
||||||
block, _ := pem.Decode(pemBytes)
|
|
||||||
if block == nil {
|
|
||||||
return ErrNoValidPrivateKey
|
|
||||||
}
|
|
||||||
|
|
||||||
if !x509.IsEncryptedPEMBlock(block) {
|
|
||||||
return ErrRootKeyNotEncrypted
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ImportRootKey imports a root in PEM format key from an io.Reader
|
// ImportRootKey imports a root in PEM format key from an io.Reader
|
||||||
// It prompts for the key's passphrase to verify the data and to determine
|
// It prompts for the key's passphrase to verify the data and to determine
|
||||||
// the key ID.
|
// the key ID.
|
||||||
func (km *KeyStoreManager) ImportRootKey(source io.Reader) error {
|
func (cs *CryptoService) ImportRootKey(source io.Reader) error {
|
||||||
pemBytes, err := ioutil.ReadAll(source)
|
pemBytes, err := ioutil.ReadAll(source)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
@ -113,14 +110,160 @@ func (km *KeyStoreManager) ImportRootKey(source io.Reader) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = km.KeyStore.ImportKey(pemBytes, "root"); err != nil {
|
for _, ks := range cs.keyStores {
|
||||||
return err
|
// don't redeclare err, we want the value carried out of the loop
|
||||||
|
if err = ks.ImportKey(pemBytes, "root"); err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func moveKeys(oldKeyStore, newKeyStore *trustmanager.KeyFileStore) error {
|
// ExportAllKeys exports all keys to an io.Writer in zip format.
|
||||||
|
// newPassphraseRetriever will be used to obtain passphrases to use to encrypt the existing keys.
|
||||||
|
func (cs *CryptoService) ExportAllKeys(dest io.Writer, newPassphraseRetriever passphrase.Retriever) error {
|
||||||
|
tempBaseDir, err := ioutil.TempDir("", "notary-key-export-")
|
||||||
|
defer os.RemoveAll(tempBaseDir)
|
||||||
|
|
||||||
|
// Create temporary keystore to use as a staging area
|
||||||
|
tempKeysPath := filepath.Join(tempBaseDir, notary.PrivDir)
|
||||||
|
tempKeyStore, err := trustmanager.NewKeyFileStore(tempKeysPath, newPassphraseRetriever)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, ks := range cs.keyStores {
|
||||||
|
if err := moveKeys(ks, tempKeyStore); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
zipWriter := zip.NewWriter(dest)
|
||||||
|
|
||||||
|
if err := addKeysToArchive(zipWriter, tempKeyStore, notary.PrivDir); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
zipWriter.Close()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ImportKeysZip imports keys from a zip file provided as an zip.Reader. The
|
||||||
|
// keys in the root_keys directory are left encrypted, but the other keys are
|
||||||
|
// decrypted with the specified passphrase.
|
||||||
|
func (cs *CryptoService) ImportKeysZip(zipReader zip.Reader) error {
|
||||||
|
// Temporarily store the keys in maps, so we can bail early if there's
|
||||||
|
// an error (for example, wrong passphrase), without leaving the key
|
||||||
|
// store in an inconsistent state
|
||||||
|
newKeys := make(map[string][]byte)
|
||||||
|
|
||||||
|
// Iterate through the files in the archive. Don't add the keys
|
||||||
|
for _, f := range zipReader.File {
|
||||||
|
fNameTrimmed := strings.TrimSuffix(f.Name, filepath.Ext(f.Name))
|
||||||
|
|
||||||
|
rc, err := f.Open()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer rc.Close()
|
||||||
|
|
||||||
|
fileBytes, err := ioutil.ReadAll(rc)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note that using / as a separator is okay here - the zip
|
||||||
|
// package guarantees that the separator will be /
|
||||||
|
if strings.HasPrefix(fNameTrimmed, notary.PrivDir) {
|
||||||
|
if fNameTrimmed[len(fNameTrimmed)-5:] == "_root" {
|
||||||
|
if err = checkRootKeyIsEncrypted(fileBytes); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
keyName := strings.TrimPrefix(fNameTrimmed, notary.PrivDir)
|
||||||
|
newKeys[keyName] = fileBytes
|
||||||
|
} else {
|
||||||
|
// This path inside the zip archive doesn't look like a
|
||||||
|
// root key, non-root key, or alias. To avoid adding a file
|
||||||
|
// to the filestore that we won't be able to use, skip
|
||||||
|
// this file in the import.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for keyName, pemBytes := range newKeys {
|
||||||
|
if keyName[len(keyName)-5:] == "_root" {
|
||||||
|
keyName = "root"
|
||||||
|
}
|
||||||
|
for _, ks := range cs.keyStores {
|
||||||
|
if err := ks.ImportKey(pemBytes, keyName); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExportKeysByGUN exports all keys associated with a specified GUN to an
|
||||||
|
// io.Writer in zip format. passphraseRetriever is used to select new passphrases to use to
|
||||||
|
// encrypt the keys.
|
||||||
|
func (cs *CryptoService) ExportKeysByGUN(dest io.Writer, gun string, passphraseRetriever passphrase.Retriever) error {
|
||||||
|
tempBaseDir, err := ioutil.TempDir("", "notary-key-export-")
|
||||||
|
defer os.RemoveAll(tempBaseDir)
|
||||||
|
|
||||||
|
// Create temporary keystore to use as a staging area
|
||||||
|
tempKeysPath := filepath.Join(tempBaseDir, notary.PrivDir)
|
||||||
|
tempKeyStore, err := trustmanager.NewKeyFileStore(tempKeysPath, passphraseRetriever)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, ks := range cs.keyStores {
|
||||||
|
if err := moveKeysByGUN(ks, tempKeyStore, gun); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
zipWriter := zip.NewWriter(dest)
|
||||||
|
|
||||||
|
if len(tempKeyStore.ListKeys()) == 0 {
|
||||||
|
return ErrNoKeysFoundForGUN
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := addKeysToArchive(zipWriter, tempKeyStore, notary.PrivDir); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
zipWriter.Close()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func moveKeysByGUN(oldKeyStore, newKeyStore trustmanager.KeyStore, gun string) error {
|
||||||
|
for relKeyPath := range oldKeyStore.ListKeys() {
|
||||||
|
// Skip keys that aren't associated with this GUN
|
||||||
|
if !strings.HasPrefix(relKeyPath, filepath.FromSlash(gun)) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
privKey, alias, err := oldKeyStore.GetKey(relKeyPath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = newKeyStore.AddKey(relKeyPath, alias, privKey)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func moveKeys(oldKeyStore, newKeyStore trustmanager.KeyStore) error {
|
||||||
for f := range oldKeyStore.ListKeys() {
|
for f := range oldKeyStore.ListKeys() {
|
||||||
privateKey, alias, err := oldKeyStore.GetKey(f)
|
privateKey, alias, err := oldKeyStore.GetKey(f)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -171,139 +314,17 @@ func addKeysToArchive(zipWriter *zip.Writer, newKeyStore *trustmanager.KeyFileSt
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExportAllKeys exports all keys to an io.Writer in zip format.
|
// checkRootKeyIsEncrypted makes sure the root key is encrypted. We have
|
||||||
// newPassphraseRetriever will be used to obtain passphrases to use to encrypt the existing keys.
|
// internal assumptions that depend on this.
|
||||||
func (km *KeyStoreManager) ExportAllKeys(dest io.Writer, newPassphraseRetriever passphrase.Retriever) error {
|
func checkRootKeyIsEncrypted(pemBytes []byte) error {
|
||||||
tempBaseDir, err := ioutil.TempDir("", "notary-key-export-")
|
block, _ := pem.Decode(pemBytes)
|
||||||
defer os.RemoveAll(tempBaseDir)
|
if block == nil {
|
||||||
|
return ErrNoValidPrivateKey
|
||||||
// Create temporary keystore to use as a staging area
|
|
||||||
tempKeysPath := filepath.Join(tempBaseDir, PrivDir)
|
|
||||||
tempKeyStore, err := trustmanager.NewKeyFileStore(tempKeysPath, newPassphraseRetriever)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := moveKeys(km.KeyStore, tempKeyStore); err != nil {
|
if !x509.IsEncryptedPEMBlock(block) {
|
||||||
return err
|
return ErrRootKeyNotEncrypted
|
||||||
}
|
|
||||||
|
|
||||||
zipWriter := zip.NewWriter(dest)
|
|
||||||
|
|
||||||
if err := addKeysToArchive(zipWriter, tempKeyStore, PrivDir); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
zipWriter.Close()
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ImportKeysZip imports keys from a zip file provided as an zip.Reader. The
|
|
||||||
// keys in the root_keys directory are left encrypted, but the other keys are
|
|
||||||
// decrypted with the specified passphrase.
|
|
||||||
func (km *KeyStoreManager) ImportKeysZip(zipReader zip.Reader) error {
|
|
||||||
// Temporarily store the keys in maps, so we can bail early if there's
|
|
||||||
// an error (for example, wrong passphrase), without leaving the key
|
|
||||||
// store in an inconsistent state
|
|
||||||
newKeys := make(map[string][]byte)
|
|
||||||
|
|
||||||
// Iterate through the files in the archive. Don't add the keys
|
|
||||||
for _, f := range zipReader.File {
|
|
||||||
fNameTrimmed := strings.TrimSuffix(f.Name, filepath.Ext(f.Name))
|
|
||||||
|
|
||||||
rc, err := f.Open()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
fileBytes, err := ioutil.ReadAll(rc)
|
|
||||||
if err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Note that using / as a separator is okay here - the zip
|
|
||||||
// package guarantees that the separator will be /
|
|
||||||
if strings.HasPrefix(fNameTrimmed, PrivDir) {
|
|
||||||
if fNameTrimmed[len(fNameTrimmed)-5:] == "_root" {
|
|
||||||
if err = checkRootKeyIsEncrypted(fileBytes); err != nil {
|
|
||||||
rc.Close()
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
keyName := strings.TrimPrefix(fNameTrimmed, PrivDir)
|
|
||||||
newKeys[keyName] = fileBytes
|
|
||||||
} else {
|
|
||||||
// This path inside the zip archive doesn't look like a
|
|
||||||
// root key, non-root key, or alias. To avoid adding a file
|
|
||||||
// to the filestore that we won't be able to use, skip
|
|
||||||
// this file in the import.
|
|
||||||
rc.Close()
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
rc.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
for keyName, pemBytes := range newKeys {
|
|
||||||
if err := km.KeyStore.Add(keyName, pemBytes); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func moveKeysByGUN(oldKeyStore, newKeyStore *trustmanager.KeyFileStore, gun string) error {
|
|
||||||
for relKeyPath := range oldKeyStore.ListKeys() {
|
|
||||||
// Skip keys that aren't associated with this GUN
|
|
||||||
if !strings.HasPrefix(relKeyPath, filepath.FromSlash(gun)) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
privKey, alias, err := oldKeyStore.GetKey(relKeyPath)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = newKeyStore.AddKey(relKeyPath, alias, privKey)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ExportKeysByGUN exports all keys associated with a specified GUN to an
|
|
||||||
// io.Writer in zip format. passphraseRetriever is used to select new passphrases to use to
|
|
||||||
// encrypt the keys.
|
|
||||||
func (km *KeyStoreManager) ExportKeysByGUN(dest io.Writer, gun string, passphraseRetriever passphrase.Retriever) error {
|
|
||||||
tempBaseDir, err := ioutil.TempDir("", "notary-key-export-")
|
|
||||||
defer os.RemoveAll(tempBaseDir)
|
|
||||||
|
|
||||||
// Create temporary keystore to use as a staging area
|
|
||||||
tempKeysPath := filepath.Join(tempBaseDir, PrivDir)
|
|
||||||
tempKeyStore, err := trustmanager.NewKeyFileStore(tempKeysPath, passphraseRetriever)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := moveKeysByGUN(km.KeyStore, tempKeyStore, gun); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
zipWriter := zip.NewWriter(dest)
|
|
||||||
|
|
||||||
if len(tempKeyStore.ListKeys()) == 0 {
|
|
||||||
return ErrNoKeysFoundForGUN
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := addKeysToArchive(zipWriter, tempKeyStore, PrivDir); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
zipWriter.Close()
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package keystoremanager_test
|
package cryptoservice
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"archive/zip"
|
"archive/zip"
|
||||||
|
|
@ -12,8 +12,6 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/docker/notary/client"
|
|
||||||
"github.com/docker/notary/keystoremanager"
|
|
||||||
"github.com/docker/notary/trustmanager"
|
"github.com/docker/notary/trustmanager"
|
||||||
"github.com/docker/notary/tuf/data"
|
"github.com/docker/notary/tuf/data"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
@ -48,26 +46,20 @@ func TestImportExportZip(t *testing.T) {
|
||||||
// Temporary directory where test files will be created
|
// Temporary directory where test files will be created
|
||||||
tempBaseDir, err := ioutil.TempDir("", "notary-test-")
|
tempBaseDir, err := ioutil.TempDir("", "notary-test-")
|
||||||
defer os.RemoveAll(tempBaseDir)
|
defer os.RemoveAll(tempBaseDir)
|
||||||
|
|
||||||
assert.NoError(t, err, "failed to create a temporary directory: %s", err)
|
assert.NoError(t, err, "failed to create a temporary directory: %s", err)
|
||||||
|
|
||||||
ts, _ := createTestServer(t)
|
fileStore, err := trustmanager.NewKeyFileStore(filepath.Join(tempBaseDir, "private"), newPassphraseRetriever)
|
||||||
defer ts.Close()
|
cs := NewCryptoService(gun, fileStore)
|
||||||
|
pubKey, err := cs.Create(data.CanonicalRootRole, data.ECDSAKey)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
repo, err := client.NewNotaryRepository(tempBaseDir, gun, ts.URL, http.DefaultTransport, oldPassphraseRetriever)
|
rootKeyID := pubKey.ID()
|
||||||
assert.NoError(t, err, "error creating repo: %s", err)
|
|
||||||
|
|
||||||
rootKeyID, err := repo.KeyStoreManager.GenRootKey(data.ECDSAKey)
|
|
||||||
assert.NoError(t, err, "error generating root key: %s", err)
|
|
||||||
|
|
||||||
err = repo.Initialize(rootKeyID)
|
|
||||||
assert.NoError(t, err, "error creating repository: %s", err)
|
|
||||||
|
|
||||||
tempZipFile, err := ioutil.TempFile("", "notary-test-export-")
|
tempZipFile, err := ioutil.TempFile("", "notary-test-export-")
|
||||||
tempZipFilePath := tempZipFile.Name()
|
tempZipFilePath := tempZipFile.Name()
|
||||||
defer os.Remove(tempZipFilePath)
|
defer os.Remove(tempZipFilePath)
|
||||||
|
|
||||||
err = repo.KeyStoreManager.ExportAllKeys(tempZipFile, newPassphraseRetriever)
|
err = cs.ExportAllKeys(tempZipFile, newPassphraseRetriever)
|
||||||
tempZipFile.Close()
|
tempZipFile.Close()
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
|
@ -80,9 +72,9 @@ func TestImportExportZip(t *testing.T) {
|
||||||
|
|
||||||
// Add non-root keys to the map. These should use the new passphrase
|
// Add non-root keys to the map. These should use the new passphrase
|
||||||
// because the passwords were chosen by the newPassphraseRetriever.
|
// because the passwords were chosen by the newPassphraseRetriever.
|
||||||
privKeyMap := repo.KeyStoreManager.KeyStore.ListKeys()
|
privKeyMap := cs.ListAllKeys()
|
||||||
for privKeyName := range privKeyMap {
|
for privKeyName := range privKeyMap {
|
||||||
_, alias, err := repo.KeyStoreManager.KeyStore.GetKey(privKeyName)
|
_, alias, err := cs.GetPrivateKey(privKeyName)
|
||||||
assert.NoError(t, err, "privKey %s has no alias", privKeyName)
|
assert.NoError(t, err, "privKey %s has no alias", privKeyName)
|
||||||
|
|
||||||
if alias == "root" {
|
if alias == "root" {
|
||||||
|
|
@ -129,31 +121,25 @@ func TestImportExportZip(t *testing.T) {
|
||||||
// Create new repo to test import
|
// Create new repo to test import
|
||||||
tempBaseDir2, err := ioutil.TempDir("", "notary-test-")
|
tempBaseDir2, err := ioutil.TempDir("", "notary-test-")
|
||||||
defer os.RemoveAll(tempBaseDir2)
|
defer os.RemoveAll(tempBaseDir2)
|
||||||
|
|
||||||
assert.NoError(t, err, "failed to create a temporary directory: %s", err)
|
assert.NoError(t, err, "failed to create a temporary directory: %s", err)
|
||||||
|
|
||||||
repo2, err := client.NewNotaryRepository(tempBaseDir2, gun, ts.URL, http.DefaultTransport, newPassphraseRetriever)
|
fileStore2, err := trustmanager.NewKeyFileStore(filepath.Join(tempBaseDir2, "private"), newPassphraseRetriever)
|
||||||
assert.NoError(t, err, "error creating repo: %s", err)
|
assert.NoError(t, err)
|
||||||
|
cs2 := NewCryptoService(gun, fileStore2)
|
||||||
rootKeyID2, err := repo2.KeyStoreManager.GenRootKey(data.ECDSAKey)
|
|
||||||
assert.NoError(t, err, "error generating root key: %s", err)
|
|
||||||
|
|
||||||
err = repo2.Initialize(rootKeyID2)
|
|
||||||
assert.NoError(t, err, "error creating repository: %s", err)
|
|
||||||
|
|
||||||
// Reopen the zip file for importing
|
// Reopen the zip file for importing
|
||||||
zipReader, err = zip.OpenReader(tempZipFilePath)
|
zipReader, err = zip.OpenReader(tempZipFilePath)
|
||||||
assert.NoError(t, err, "could not open zip file")
|
assert.NoError(t, err, "could not open zip file")
|
||||||
|
|
||||||
// Now try with a valid passphrase. This time it should succeed.
|
// Now try with a valid passphrase. This time it should succeed.
|
||||||
err = repo2.KeyStoreManager.ImportKeysZip(zipReader.Reader)
|
err = cs2.ImportKeysZip(zipReader.Reader)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
zipReader.Close()
|
zipReader.Close()
|
||||||
|
|
||||||
// Look for keys in private. The filenames should match the key IDs
|
// Look for keys in private. The filenames should match the key IDs
|
||||||
// in the repo's private key store.
|
// in the repo's private key store.
|
||||||
for privKeyName := range privKeyMap {
|
for privKeyName := range privKeyMap {
|
||||||
_, alias, err := repo.KeyStoreManager.KeyStore.GetKey(privKeyName)
|
_, alias, err := cs2.GetPrivateKey(privKeyName)
|
||||||
assert.NoError(t, err, "privKey %s has no alias", privKeyName)
|
assert.NoError(t, err, "privKey %s has no alias", privKeyName)
|
||||||
|
|
||||||
if alias == "root" {
|
if alias == "root" {
|
||||||
|
|
@ -162,7 +148,7 @@ func TestImportExportZip(t *testing.T) {
|
||||||
relKeyPath := filepath.Join("private", "tuf_keys", privKeyName+"_"+alias+".key")
|
relKeyPath := filepath.Join("private", "tuf_keys", privKeyName+"_"+alias+".key")
|
||||||
privKeyFileName := filepath.Join(tempBaseDir2, relKeyPath)
|
privKeyFileName := filepath.Join(tempBaseDir2, relKeyPath)
|
||||||
_, err = os.Stat(privKeyFileName)
|
_, err = os.Stat(privKeyFileName)
|
||||||
assert.NoError(t, err, "missing private key: %s", privKeyName)
|
assert.NoError(t, err, "missing private key for role %s: %s", alias, privKeyName)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Look for keys in root_keys
|
// Look for keys in root_keys
|
||||||
|
|
@ -179,31 +165,25 @@ func TestImportExportGUN(t *testing.T) {
|
||||||
// Temporary directory where test files will be created
|
// Temporary directory where test files will be created
|
||||||
tempBaseDir, err := ioutil.TempDir("", "notary-test-")
|
tempBaseDir, err := ioutil.TempDir("", "notary-test-")
|
||||||
defer os.RemoveAll(tempBaseDir)
|
defer os.RemoveAll(tempBaseDir)
|
||||||
|
|
||||||
assert.NoError(t, err, "failed to create a temporary directory: %s", err)
|
assert.NoError(t, err, "failed to create a temporary directory: %s", err)
|
||||||
|
|
||||||
ts, _ := createTestServer(t)
|
fileStore, err := trustmanager.NewKeyFileStore(filepath.Join(tempBaseDir, "private"), newPassphraseRetriever)
|
||||||
defer ts.Close()
|
cs := NewCryptoService(gun, fileStore)
|
||||||
|
_, err = cs.Create(data.CanonicalRootRole, data.ECDSAKey)
|
||||||
repo, err := client.NewNotaryRepository(tempBaseDir, gun, ts.URL, http.DefaultTransport, oldPassphraseRetriever)
|
_, err = cs.Create(data.CanonicalTargetsRole, data.ECDSAKey)
|
||||||
assert.NoError(t, err, "error creating repo: %s", err)
|
_, err = cs.Create(data.CanonicalSnapshotRole, data.ECDSAKey)
|
||||||
|
assert.NoError(t, err)
|
||||||
rootKeyID, err := repo.KeyStoreManager.GenRootKey(data.ECDSAKey)
|
|
||||||
assert.NoError(t, err, "error generating root key: %s", err)
|
|
||||||
|
|
||||||
err = repo.Initialize(rootKeyID)
|
|
||||||
assert.NoError(t, err, "error creating repository: %s", err)
|
|
||||||
|
|
||||||
tempZipFile, err := ioutil.TempFile("", "notary-test-export-")
|
tempZipFile, err := ioutil.TempFile("", "notary-test-export-")
|
||||||
tempZipFilePath := tempZipFile.Name()
|
tempZipFilePath := tempZipFile.Name()
|
||||||
defer os.Remove(tempZipFilePath)
|
defer os.Remove(tempZipFilePath)
|
||||||
|
|
||||||
err = repo.KeyStoreManager.ExportKeysByGUN(tempZipFile, gun, newPassphraseRetriever)
|
err = cs.ExportKeysByGUN(tempZipFile, gun, newPassphraseRetriever)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
// With an invalid GUN, this should return an error
|
// With an invalid GUN, this should return an error
|
||||||
err = repo.KeyStoreManager.ExportKeysByGUN(tempZipFile, "does.not.exist/in/repository", newPassphraseRetriever)
|
err = cs.ExportKeysByGUN(tempZipFile, "does.not.exist/in/repository", newPassphraseRetriever)
|
||||||
assert.EqualError(t, err, keystoremanager.ErrNoKeysFoundForGUN.Error())
|
assert.EqualError(t, err, ErrNoKeysFoundForGUN.Error())
|
||||||
|
|
||||||
tempZipFile.Close()
|
tempZipFile.Close()
|
||||||
|
|
||||||
|
|
@ -216,9 +196,9 @@ func TestImportExportGUN(t *testing.T) {
|
||||||
|
|
||||||
// Add keys non-root keys to the map. These should use the new passphrase
|
// Add keys non-root keys to the map. These should use the new passphrase
|
||||||
// because they were formerly unencrypted.
|
// because they were formerly unencrypted.
|
||||||
privKeyMap := repo.KeyStoreManager.KeyStore.ListKeys()
|
privKeyMap := cs.ListAllKeys()
|
||||||
for privKeyName := range privKeyMap {
|
for privKeyName := range privKeyMap {
|
||||||
_, alias, err := repo.KeyStoreManager.KeyStore.GetKey(privKeyName)
|
_, alias, err := cs.GetPrivateKey(privKeyName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("privKey %s has no alias", privKeyName)
|
t.Fatalf("privKey %s has no alias", privKeyName)
|
||||||
}
|
}
|
||||||
|
|
@ -263,31 +243,27 @@ func TestImportExportGUN(t *testing.T) {
|
||||||
// Create new repo to test import
|
// Create new repo to test import
|
||||||
tempBaseDir2, err := ioutil.TempDir("", "notary-test-")
|
tempBaseDir2, err := ioutil.TempDir("", "notary-test-")
|
||||||
defer os.RemoveAll(tempBaseDir2)
|
defer os.RemoveAll(tempBaseDir2)
|
||||||
|
|
||||||
assert.NoError(t, err, "failed to create a temporary directory: %s", err)
|
assert.NoError(t, err, "failed to create a temporary directory: %s", err)
|
||||||
|
|
||||||
repo2, err := client.NewNotaryRepository(tempBaseDir2, gun, ts.URL, http.DefaultTransport, oldPassphraseRetriever)
|
fileStore2, err := trustmanager.NewKeyFileStore(filepath.Join(tempBaseDir2, "private"), newPassphraseRetriever)
|
||||||
assert.NoError(t, err, "error creating repo: %s", err)
|
cs2 := NewCryptoService(gun, fileStore2)
|
||||||
|
|
||||||
rootKeyID2, err := repo2.KeyStoreManager.GenRootKey(data.ECDSAKey)
|
|
||||||
assert.NoError(t, err, "error generating root key: %s", err)
|
|
||||||
|
|
||||||
err = repo2.Initialize(rootKeyID2)
|
|
||||||
assert.NoError(t, err, "error creating repository: %s", err)
|
|
||||||
|
|
||||||
// Reopen the zip file for importing
|
// Reopen the zip file for importing
|
||||||
zipReader, err = zip.OpenReader(tempZipFilePath)
|
zipReader, err = zip.OpenReader(tempZipFilePath)
|
||||||
assert.NoError(t, err, "could not open zip file")
|
assert.NoError(t, err, "could not open zip file")
|
||||||
|
|
||||||
// Now try with a valid passphrase. This time it should succeed.
|
// Now try with a valid passphrase. This time it should succeed.
|
||||||
err = repo2.KeyStoreManager.ImportKeysZip(zipReader.Reader)
|
err = cs2.ImportKeysZip(zipReader.Reader)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
zipReader.Close()
|
zipReader.Close()
|
||||||
|
|
||||||
// Look for keys in private. The filenames should match the key IDs
|
// Look for keys in private. The filenames should match the key IDs
|
||||||
// in the repo's private key store.
|
// in the repo's private key store.
|
||||||
for privKeyName := range privKeyMap {
|
for privKeyName, role := range privKeyMap {
|
||||||
_, alias, err := repo.KeyStoreManager.KeyStore.GetKey(privKeyName)
|
if role == "root" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
_, alias, err := cs2.GetPrivateKey(privKeyName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("privKey %s has no alias", privKeyName)
|
t.Fatalf("privKey %s has no alias", privKeyName)
|
||||||
}
|
}
|
||||||
|
|
@ -297,6 +273,7 @@ func TestImportExportGUN(t *testing.T) {
|
||||||
relKeyPath := filepath.Join("private", "tuf_keys", privKeyName+"_"+alias+".key")
|
relKeyPath := filepath.Join("private", "tuf_keys", privKeyName+"_"+alias+".key")
|
||||||
privKeyFileName := filepath.Join(tempBaseDir2, relKeyPath)
|
privKeyFileName := filepath.Join(tempBaseDir2, relKeyPath)
|
||||||
_, err = os.Stat(privKeyFileName)
|
_, err = os.Stat(privKeyFileName)
|
||||||
|
assert.NoError(t, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -306,48 +283,35 @@ func TestImportExportRootKey(t *testing.T) {
|
||||||
// Temporary directory where test files will be created
|
// Temporary directory where test files will be created
|
||||||
tempBaseDir, err := ioutil.TempDir("", "notary-test-")
|
tempBaseDir, err := ioutil.TempDir("", "notary-test-")
|
||||||
defer os.RemoveAll(tempBaseDir)
|
defer os.RemoveAll(tempBaseDir)
|
||||||
|
|
||||||
assert.NoError(t, err, "failed to create a temporary directory: %s", err)
|
assert.NoError(t, err, "failed to create a temporary directory: %s", err)
|
||||||
|
|
||||||
ts, _ := createTestServer(t)
|
fileStore, err := trustmanager.NewKeyFileStore(filepath.Join(tempBaseDir, "private"), oldPassphraseRetriever)
|
||||||
defer ts.Close()
|
cs := NewCryptoService(gun, fileStore)
|
||||||
|
pubKey, err := cs.Create(data.CanonicalRootRole, data.ECDSAKey)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
repo, err := client.NewNotaryRepository(tempBaseDir, gun, ts.URL, http.DefaultTransport, oldPassphraseRetriever)
|
rootKeyID := pubKey.ID()
|
||||||
assert.NoError(t, err, "error creating repo: %s", err)
|
|
||||||
|
|
||||||
rootKeyID, err := repo.KeyStoreManager.GenRootKey(data.ECDSAKey)
|
|
||||||
assert.NoError(t, err, "error generating root key: %s", err)
|
|
||||||
|
|
||||||
err = repo.Initialize(rootKeyID)
|
|
||||||
assert.NoError(t, err, "error creating repository: %s", err)
|
|
||||||
|
|
||||||
tempKeyFile, err := ioutil.TempFile("", "notary-test-export-")
|
tempKeyFile, err := ioutil.TempFile("", "notary-test-export-")
|
||||||
tempKeyFilePath := tempKeyFile.Name()
|
tempKeyFilePath := tempKeyFile.Name()
|
||||||
defer os.Remove(tempKeyFilePath)
|
defer os.Remove(tempKeyFilePath)
|
||||||
|
|
||||||
err = repo.KeyStoreManager.ExportRootKey(tempKeyFile, rootKeyID)
|
err = cs.ExportRootKey(tempKeyFile, rootKeyID)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
tempKeyFile.Close()
|
tempKeyFile.Close()
|
||||||
|
|
||||||
// Create new repo to test import
|
// Create new repo to test import
|
||||||
tempBaseDir2, err := ioutil.TempDir("", "notary-test-")
|
tempBaseDir2, err := ioutil.TempDir("", "notary-test-")
|
||||||
defer os.RemoveAll(tempBaseDir2)
|
defer os.RemoveAll(tempBaseDir2)
|
||||||
|
|
||||||
assert.NoError(t, err, "failed to create a temporary directory: %s", err)
|
assert.NoError(t, err, "failed to create a temporary directory: %s", err)
|
||||||
|
|
||||||
repo2, err := client.NewNotaryRepository(tempBaseDir2, gun, ts.URL, http.DefaultTransport, oldPassphraseRetriever)
|
fileStore2, err := trustmanager.NewKeyFileStore(filepath.Join(tempBaseDir2, "private"), oldPassphraseRetriever)
|
||||||
assert.NoError(t, err, "error creating repo: %s", err)
|
cs2 := NewCryptoService(gun, fileStore2)
|
||||||
|
|
||||||
rootKeyID2, err := repo2.KeyStoreManager.GenRootKey(data.ECDSAKey)
|
|
||||||
assert.NoError(t, err, "error generating root key: %s", err)
|
|
||||||
|
|
||||||
err = repo2.Initialize(rootKeyID2)
|
|
||||||
assert.NoError(t, err, "error creating repository: %s", err)
|
|
||||||
|
|
||||||
keyReader, err := os.Open(tempKeyFilePath)
|
keyReader, err := os.Open(tempKeyFilePath)
|
||||||
assert.NoError(t, err, "could not open key file")
|
assert.NoError(t, err, "could not open key file")
|
||||||
|
|
||||||
err = repo2.KeyStoreManager.ImportRootKey(keyReader)
|
err = cs2.ImportRootKey(keyReader)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
keyReader.Close()
|
keyReader.Close()
|
||||||
|
|
||||||
|
|
@ -367,15 +331,15 @@ func TestImportExportRootKey(t *testing.T) {
|
||||||
decryptedPEMBytes, err := trustmanager.KeyToPEM(privKey)
|
decryptedPEMBytes, err := trustmanager.KeyToPEM(privKey)
|
||||||
assert.NoError(t, err, "could not convert key to PEM")
|
assert.NoError(t, err, "could not convert key to PEM")
|
||||||
|
|
||||||
err = repo2.KeyStoreManager.ImportRootKey(bytes.NewReader(decryptedPEMBytes))
|
err = cs2.ImportRootKey(bytes.NewReader(decryptedPEMBytes))
|
||||||
assert.EqualError(t, err, keystoremanager.ErrRootKeyNotEncrypted.Error())
|
assert.EqualError(t, err, ErrRootKeyNotEncrypted.Error())
|
||||||
|
|
||||||
// Try to import garbage and make sure it doesn't succeed
|
// Try to import garbage and make sure it doesn't succeed
|
||||||
err = repo2.KeyStoreManager.ImportRootKey(strings.NewReader("this is not PEM"))
|
err = cs2.ImportRootKey(strings.NewReader("this is not PEM"))
|
||||||
assert.EqualError(t, err, keystoremanager.ErrNoValidPrivateKey.Error())
|
assert.EqualError(t, err, ErrNoValidPrivateKey.Error())
|
||||||
|
|
||||||
// Should be able to unlock the root key with the old password
|
// Should be able to unlock the root key with the old password
|
||||||
key, alias, err := repo2.KeyStoreManager.KeyStore.GetKey(rootKeyID)
|
key, alias, err := cs2.GetPrivateKey(rootKeyID)
|
||||||
assert.NoError(t, err, "could not unlock root key")
|
assert.NoError(t, err, "could not unlock root key")
|
||||||
assert.Equal(t, "root", alias)
|
assert.Equal(t, "root", alias)
|
||||||
assert.Equal(t, rootKeyID, key.ID())
|
assert.Equal(t, rootKeyID, key.ID())
|
||||||
|
|
@ -387,48 +351,35 @@ func TestImportExportRootKeyReencrypt(t *testing.T) {
|
||||||
// Temporary directory where test files will be created
|
// Temporary directory where test files will be created
|
||||||
tempBaseDir, err := ioutil.TempDir("", "notary-test-")
|
tempBaseDir, err := ioutil.TempDir("", "notary-test-")
|
||||||
defer os.RemoveAll(tempBaseDir)
|
defer os.RemoveAll(tempBaseDir)
|
||||||
|
|
||||||
assert.NoError(t, err, "failed to create a temporary directory: %s", err)
|
assert.NoError(t, err, "failed to create a temporary directory: %s", err)
|
||||||
|
|
||||||
ts, _ := createTestServer(t)
|
fileStore, err := trustmanager.NewKeyFileStore(filepath.Join(tempBaseDir, "private"), oldPassphraseRetriever)
|
||||||
defer ts.Close()
|
cs := NewCryptoService(gun, fileStore)
|
||||||
|
pubKey, err := cs.Create(data.CanonicalRootRole, data.ECDSAKey)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
repo, err := client.NewNotaryRepository(tempBaseDir, gun, ts.URL, http.DefaultTransport, oldPassphraseRetriever)
|
rootKeyID := pubKey.ID()
|
||||||
assert.NoError(t, err, "error creating repo: %s", err)
|
|
||||||
|
|
||||||
rootKeyID, err := repo.KeyStoreManager.GenRootKey(data.ECDSAKey)
|
|
||||||
assert.NoError(t, err, "error generating root key: %s", err)
|
|
||||||
|
|
||||||
err = repo.Initialize(rootKeyID)
|
|
||||||
assert.NoError(t, err, "error creating repository: %s", err)
|
|
||||||
|
|
||||||
tempKeyFile, err := ioutil.TempFile("", "notary-test-export-")
|
tempKeyFile, err := ioutil.TempFile("", "notary-test-export-")
|
||||||
tempKeyFilePath := tempKeyFile.Name()
|
tempKeyFilePath := tempKeyFile.Name()
|
||||||
defer os.Remove(tempKeyFilePath)
|
defer os.Remove(tempKeyFilePath)
|
||||||
|
|
||||||
err = repo.KeyStoreManager.ExportRootKeyReencrypt(tempKeyFile, rootKeyID, newPassphraseRetriever)
|
err = cs.ExportRootKeyReencrypt(tempKeyFile, rootKeyID, newPassphraseRetriever)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
tempKeyFile.Close()
|
tempKeyFile.Close()
|
||||||
|
|
||||||
// Create new repo to test import
|
// Create new repo to test import
|
||||||
tempBaseDir2, err := ioutil.TempDir("", "notary-test-")
|
tempBaseDir2, err := ioutil.TempDir("", "notary-test-")
|
||||||
defer os.RemoveAll(tempBaseDir2)
|
defer os.RemoveAll(tempBaseDir2)
|
||||||
|
|
||||||
assert.NoError(t, err, "failed to create a temporary directory: %s", err)
|
assert.NoError(t, err, "failed to create a temporary directory: %s", err)
|
||||||
|
|
||||||
repo2, err := client.NewNotaryRepository(tempBaseDir2, gun, ts.URL, http.DefaultTransport, newPassphraseRetriever)
|
fileStore2, err := trustmanager.NewKeyFileStore(filepath.Join(tempBaseDir2, "private"), newPassphraseRetriever)
|
||||||
assert.NoError(t, err, "error creating repo: %s", err)
|
cs2 := NewCryptoService(gun, fileStore2)
|
||||||
|
|
||||||
rootKeyID2, err := repo2.KeyStoreManager.GenRootKey(data.ECDSAKey)
|
|
||||||
assert.NoError(t, err, "error generating root key: %s", err)
|
|
||||||
|
|
||||||
err = repo2.Initialize(rootKeyID2)
|
|
||||||
assert.NoError(t, err, "error creating repository: %s", err)
|
|
||||||
|
|
||||||
keyReader, err := os.Open(tempKeyFilePath)
|
keyReader, err := os.Open(tempKeyFilePath)
|
||||||
assert.NoError(t, err, "could not open key file")
|
assert.NoError(t, err, "could not open key file")
|
||||||
|
|
||||||
err = repo2.KeyStoreManager.ImportRootKey(keyReader)
|
err = cs2.ImportRootKey(keyReader)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
keyReader.Close()
|
keyReader.Close()
|
||||||
|
|
||||||
|
|
@ -440,7 +391,7 @@ func TestImportExportRootKeyReencrypt(t *testing.T) {
|
||||||
assert.NoError(t, err, "missing root key")
|
assert.NoError(t, err, "missing root key")
|
||||||
|
|
||||||
// Should be able to unlock the root key with the new password
|
// Should be able to unlock the root key with the new password
|
||||||
key, alias, err := repo2.KeyStoreManager.KeyStore.GetKey(rootKeyID)
|
key, alias, err := cs2.GetPrivateKey(rootKeyID)
|
||||||
assert.NoError(t, err, "could not unlock root key")
|
assert.NoError(t, err, "could not unlock root key")
|
||||||
assert.Equal(t, "root", alias)
|
assert.Equal(t, "root", alias)
|
||||||
assert.Equal(t, rootKeyID, key.ID())
|
assert.Equal(t, rootKeyID, key.ID())
|
||||||
|
|
@ -25,8 +25,6 @@ type KeyStoreManager struct {
|
||||||
|
|
||||||
const (
|
const (
|
||||||
trustDir = "trusted_certificates"
|
trustDir = "trusted_certificates"
|
||||||
// PrivDir is the name of the private directory
|
|
||||||
PrivDir = "private"
|
|
||||||
rsaRootKeySize = 4096 // Used for new root keys
|
rsaRootKeySize = 4096 // Used for new root keys
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
"text/template"
|
"text/template"
|
||||||
|
|
||||||
|
"github.com/docker/notary"
|
||||||
"github.com/docker/notary/cryptoservice"
|
"github.com/docker/notary/cryptoservice"
|
||||||
"github.com/docker/notary/trustmanager"
|
"github.com/docker/notary/trustmanager"
|
||||||
"github.com/docker/notary/tuf/data"
|
"github.com/docker/notary/tuf/data"
|
||||||
|
|
@ -122,7 +123,7 @@ func TestValidateRoot(t *testing.T) {
|
||||||
defer os.RemoveAll(tempBaseDir)
|
defer os.RemoveAll(tempBaseDir)
|
||||||
assert.NoError(t, err, "failed to create a temporary directory: %s", err)
|
assert.NoError(t, err, "failed to create a temporary directory: %s", err)
|
||||||
|
|
||||||
keysPath := filepath.Join(tempBaseDir, PrivDir)
|
keysPath := filepath.Join(tempBaseDir, notary.PrivDir)
|
||||||
fileKeyStore, err := trustmanager.NewKeyFileStore(keysPath, passphraseRetriever)
|
fileKeyStore, err := trustmanager.NewKeyFileStore(keysPath, passphraseRetriever)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
|
@ -234,7 +235,7 @@ func filestoreWithTwoCerts(t *testing.T, gun, keyAlg string) (
|
||||||
tempBaseDir, err := ioutil.TempDir("", "notary-test-")
|
tempBaseDir, err := ioutil.TempDir("", "notary-test-")
|
||||||
assert.NoError(t, err, "failed to create a temporary directory: %s", err)
|
assert.NoError(t, err, "failed to create a temporary directory: %s", err)
|
||||||
|
|
||||||
keysPath := filepath.Join(tempBaseDir, PrivDir)
|
keysPath := filepath.Join(tempBaseDir, notary.PrivDir)
|
||||||
fileKeyStore, err := trustmanager.NewKeyFileStore(keysPath, passphraseRetriever)
|
fileKeyStore, err := trustmanager.NewKeyFileStore(keysPath, passphraseRetriever)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -113,22 +113,23 @@ func PromptRetrieverWithInOut(in io.Reader, out io.Writer, aliasMap map[string]s
|
||||||
indexOfLastSeparator = 0
|
indexOfLastSeparator = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var shortName string
|
||||||
if len(keyName) > indexOfLastSeparator+idBytesToDisplay {
|
if len(keyName) > indexOfLastSeparator+idBytesToDisplay {
|
||||||
if indexOfLastSeparator > 0 {
|
if indexOfLastSeparator > 0 {
|
||||||
keyNamePrefix := keyName[:indexOfLastSeparator]
|
keyNamePrefix := keyName[:indexOfLastSeparator]
|
||||||
keyNameID := keyName[indexOfLastSeparator+1 : indexOfLastSeparator+idBytesToDisplay+1]
|
keyNameID := keyName[indexOfLastSeparator+1 : indexOfLastSeparator+idBytesToDisplay+1]
|
||||||
keyName = keyNamePrefix + " (" + keyNameID + ")"
|
shortName = keyNamePrefix + " (" + keyNameID + ")"
|
||||||
} else {
|
} else {
|
||||||
keyName = keyName[indexOfLastSeparator : indexOfLastSeparator+idBytesToDisplay]
|
shortName = keyName[indexOfLastSeparator : indexOfLastSeparator+idBytesToDisplay]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if createNew {
|
if createNew {
|
||||||
fmt.Fprintf(out, "Enter passphrase for new %s key with id %s: ", displayAlias, keyName)
|
fmt.Fprintf(out, "Enter passphrase for new %s key with id %s: ", displayAlias, shortName)
|
||||||
} else if displayAlias == "yubikey" {
|
} else if displayAlias == "yubikey" {
|
||||||
fmt.Fprintf(out, "Enter the %s for the attached Yubikey: ", keyName)
|
fmt.Fprintf(out, "Enter the %s for the attached Yubikey: ", keyName)
|
||||||
} else {
|
} else {
|
||||||
fmt.Fprintf(out, "Enter passphrase for %s key with id %s: ", displayAlias, keyName)
|
fmt.Fprintf(out, "Enter passphrase for %s key with id %s: ", displayAlias, shortName)
|
||||||
}
|
}
|
||||||
|
|
||||||
passphrase, err := stdin.ReadBytes('\n')
|
passphrase, err := stdin.ReadBytes('\n')
|
||||||
|
|
@ -15,6 +15,7 @@ import (
|
||||||
"github.com/docker/notary/signer/api"
|
"github.com/docker/notary/signer/api"
|
||||||
"github.com/docker/notary/trustmanager"
|
"github.com/docker/notary/trustmanager"
|
||||||
"github.com/docker/notary/tuf/data"
|
"github.com/docker/notary/tuf/data"
|
||||||
|
"github.com/docker/notary/tuf/signed"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
pb "github.com/docker/notary/proto"
|
pb "github.com/docker/notary/proto"
|
||||||
|
|
@ -131,7 +132,7 @@ func TestHSMCreateKeyHandler(t *testing.T) {
|
||||||
defer ctx.CloseSession(session)
|
defer ctx.CloseSession(session)
|
||||||
defer ctx.Logout(session)
|
defer ctx.Logout(session)
|
||||||
|
|
||||||
cryptoService := api.NewRSAHardwareCryptoService(ctx, session)
|
cryptoService := signed.NewEd25519()
|
||||||
setup(signer.CryptoServiceIndex{data.RSAKey: cryptoService})
|
setup(signer.CryptoServiceIndex{data.RSAKey: cryptoService})
|
||||||
|
|
||||||
createKeyURL := fmt.Sprintf("%s/%s", createKeyBaseURL, data.RSAKey)
|
createKeyURL := fmt.Sprintf("%s/%s", createKeyBaseURL, data.RSAKey)
|
||||||
|
|
@ -182,7 +183,7 @@ func TestHSMSignHandler(t *testing.T) {
|
||||||
defer ctx.CloseSession(session)
|
defer ctx.CloseSession(session)
|
||||||
defer ctx.Logout(session)
|
defer ctx.Logout(session)
|
||||||
|
|
||||||
cryptoService := api.NewRSAHardwareCryptoService(ctx, session)
|
cryptoService := signed.NewEd25519()
|
||||||
setup(signer.CryptoServiceIndex{data.RSAKey: cryptoService})
|
setup(signer.CryptoServiceIndex{data.RSAKey: cryptoService})
|
||||||
|
|
||||||
tufKey, _ := cryptoService.Create("", data.RSAKey)
|
tufKey, _ := cryptoService.Create("", data.RSAKey)
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ import (
|
||||||
"math/big"
|
"math/big"
|
||||||
|
|
||||||
"github.com/Sirupsen/logrus"
|
"github.com/Sirupsen/logrus"
|
||||||
"github.com/docker/notary/pkg/passphrase"
|
"github.com/docker/notary/passphrase"
|
||||||
"github.com/docker/notary/trustmanager"
|
"github.com/docker/notary/trustmanager"
|
||||||
"github.com/docker/notary/tuf/data"
|
"github.com/docker/notary/tuf/data"
|
||||||
"github.com/miekg/pkcs11"
|
"github.com/miekg/pkcs11"
|
||||||
|
|
@ -24,25 +24,41 @@ import (
|
||||||
const (
|
const (
|
||||||
USER_PIN = "123456"
|
USER_PIN = "123456"
|
||||||
SO_USER_PIN = "010203040506070801020304050607080102030405060708"
|
SO_USER_PIN = "010203040506070801020304050607080102030405060708"
|
||||||
|
numSlots = 4 // number of slots in the yubikey
|
||||||
)
|
)
|
||||||
|
|
||||||
// Hardcoded yubikey PKCS11 ID
|
// Hardcoded yubikey PKCS11 ID
|
||||||
var YUBIKEY_ROOT_KEY_ID = []byte{2}
|
var YUBIKEY_ROOT_KEY_ID = []byte{2}
|
||||||
|
|
||||||
|
type ErrBackupFailed struct {
|
||||||
|
err string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (err ErrBackupFailed) Error() string {
|
||||||
|
return fmt.Sprintf("Failed to backup private key to: %s", err.err)
|
||||||
|
}
|
||||||
|
|
||||||
|
type yubiSlot struct {
|
||||||
|
role string
|
||||||
|
slotID []byte
|
||||||
|
}
|
||||||
|
|
||||||
// YubiPrivateKey represents a private key inside of a yubikey
|
// YubiPrivateKey represents a private key inside of a yubikey
|
||||||
type YubiPrivateKey struct {
|
type YubiPrivateKey struct {
|
||||||
data.ECDSAPublicKey
|
data.ECDSAPublicKey
|
||||||
passRetriever passphrase.Retriever
|
passRetriever passphrase.Retriever
|
||||||
|
slot []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
type YubikeySigner struct {
|
type YubikeySigner struct {
|
||||||
YubiPrivateKey
|
YubiPrivateKey
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewYubiPrivateKey(pubKey data.ECDSAPublicKey, passRetriever passphrase.Retriever) *YubiPrivateKey {
|
func NewYubiPrivateKey(slot []byte, pubKey data.ECDSAPublicKey, passRetriever passphrase.Retriever) *YubiPrivateKey {
|
||||||
return &YubiPrivateKey{
|
return &YubiPrivateKey{
|
||||||
ECDSAPublicKey: pubKey,
|
ECDSAPublicKey: pubKey,
|
||||||
passRetriever: passRetriever,
|
passRetriever: passRetriever,
|
||||||
|
slot: slot,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -79,7 +95,7 @@ func (y *YubiPrivateKey) Sign(rand io.Reader, msg []byte, opts crypto.SignerOpts
|
||||||
}
|
}
|
||||||
defer cleanup(ctx, session)
|
defer cleanup(ctx, session)
|
||||||
|
|
||||||
sig, err := sign(ctx, session, YUBIKEY_ROOT_KEY_ID, y.passRetriever, msg)
|
sig, err := sign(ctx, session, y.slot, y.passRetriever, msg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to sign using Yubikey: %v", err)
|
return nil, fmt.Errorf("failed to sign using Yubikey: %v", err)
|
||||||
}
|
}
|
||||||
|
|
@ -88,7 +104,15 @@ func (y *YubiPrivateKey) Sign(rand io.Reader, msg []byte, opts crypto.SignerOpts
|
||||||
}
|
}
|
||||||
|
|
||||||
// addECDSAKey adds a key to the yubikey
|
// addECDSAKey adds a key to the yubikey
|
||||||
func addECDSAKey(ctx *pkcs11.Ctx, session pkcs11.SessionHandle, privKey data.PrivateKey, pkcs11KeyID []byte, passRetriever passphrase.Retriever) error {
|
func addECDSAKey(
|
||||||
|
ctx *pkcs11.Ctx,
|
||||||
|
session pkcs11.SessionHandle,
|
||||||
|
privKey data.PrivateKey,
|
||||||
|
pkcs11KeyID []byte,
|
||||||
|
passRetriever passphrase.Retriever,
|
||||||
|
role string,
|
||||||
|
backupStore trustmanager.KeyStore,
|
||||||
|
) error {
|
||||||
logrus.Debugf("Got into add key with key: %s\n", privKey.ID())
|
logrus.Debugf("Got into add key with key: %s\n", privKey.ID())
|
||||||
|
|
||||||
// TODO(diogo): Figure out CKU_SO with yubikey
|
// TODO(diogo): Figure out CKU_SO with yubikey
|
||||||
|
|
@ -107,7 +131,7 @@ func addECDSAKey(ctx *pkcs11.Ctx, session pkcs11.SessionHandle, privKey data.Pri
|
||||||
ecdsaPrivKeyD := ecdsaPrivKey.D.Bytes()
|
ecdsaPrivKeyD := ecdsaPrivKey.D.Bytes()
|
||||||
logrus.Debugf("Getting D bytes: %v\n", ecdsaPrivKeyD)
|
logrus.Debugf("Getting D bytes: %v\n", ecdsaPrivKeyD)
|
||||||
|
|
||||||
template, err := trustmanager.NewCertificate(data.CanonicalRootRole)
|
template, err := trustmanager.NewCertificate(role)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to create the certificate template: %v", err)
|
return fmt.Errorf("failed to create the certificate template: %v", err)
|
||||||
}
|
}
|
||||||
|
|
@ -145,6 +169,11 @@ func addECDSAKey(ctx *pkcs11.Ctx, session pkcs11.SessionHandle, privKey data.Pri
|
||||||
return fmt.Errorf("error importing: %v", err)
|
return fmt.Errorf("error importing: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
err = backupStore.AddKey(privKey.ID(), role, privKey)
|
||||||
|
if err != nil {
|
||||||
|
return ErrBackupFailed{err: err.Error()}
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -256,32 +285,239 @@ func sign(ctx *pkcs11.Ctx, session pkcs11.SessionHandle, pkcs11KeyID []byte, pas
|
||||||
return sig[:], nil
|
return sig[:], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type YubiKeyStore struct {
|
func removeKey(ctx *pkcs11.Ctx, session pkcs11.SessionHandle, pkcs11KeyID []byte, passRetriever passphrase.Retriever, keyID string) error {
|
||||||
passRetriever passphrase.Retriever
|
err := login(ctx, session, passRetriever, pkcs11.CKU_SO, SO_USER_PIN)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer ctx.Logout(session)
|
||||||
|
|
||||||
|
template := []*pkcs11.Attribute{
|
||||||
|
pkcs11.NewAttribute(pkcs11.CKA_TOKEN, true),
|
||||||
|
pkcs11.NewAttribute(pkcs11.CKA_ID, pkcs11KeyID),
|
||||||
|
//pkcs11.NewAttribute(pkcs11.CKA_CLASS, pkcs11.CKO_PRIVATE_KEY),
|
||||||
|
pkcs11.NewAttribute(pkcs11.CKA_CLASS, pkcs11.CKO_CERTIFICATE),
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := ctx.FindObjectsInit(session, template); err != nil {
|
||||||
|
logrus.Printf("Failed to init: %s\n", err.Error())
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
obj, b, err := ctx.FindObjects(session, 1)
|
||||||
|
if err != nil {
|
||||||
|
logrus.Printf("Failed to find: %s %v\n", err.Error(), b)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := ctx.FindObjectsFinal(session); err != nil {
|
||||||
|
logrus.Printf("Failed to finalize: %s\n", err.Error())
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if len(obj) != 1 {
|
||||||
|
logrus.Printf("should have found one object")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete the certificate
|
||||||
|
err = ctx.DestroyObject(session, obj[0])
|
||||||
|
if err != nil {
|
||||||
|
logrus.Printf("Failed to delete cert")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewYubiKeyStore(passphraseRetriever passphrase.Retriever) *YubiKeyStore {
|
func listKeys(ctx *pkcs11.Ctx, session pkcs11.SessionHandle) (keys map[string]yubiSlot, err error) {
|
||||||
return &YubiKeyStore{passRetriever: passphraseRetriever}
|
keys = make(map[string]yubiSlot)
|
||||||
|
findTemplate := []*pkcs11.Attribute{
|
||||||
|
pkcs11.NewAttribute(pkcs11.CKA_TOKEN, true),
|
||||||
|
//pkcs11.NewAttribute(pkcs11.CKA_ID, pkcs11KeyID),
|
||||||
|
pkcs11.NewAttribute(pkcs11.CKA_CLASS, pkcs11.CKO_CERTIFICATE),
|
||||||
|
}
|
||||||
|
|
||||||
|
attrTemplate := []*pkcs11.Attribute{
|
||||||
|
pkcs11.NewAttribute(pkcs11.CKA_ID, []byte{0}),
|
||||||
|
pkcs11.NewAttribute(pkcs11.CKA_VALUE, []byte{0}),
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = ctx.FindObjectsInit(session, findTemplate); err != nil {
|
||||||
|
logrus.Debugf("Failed to init: %s\n", err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
objs, b, err := ctx.FindObjects(session, numSlots)
|
||||||
|
for err == nil {
|
||||||
|
var o []pkcs11.ObjectHandle
|
||||||
|
o, b, err = ctx.FindObjects(session, numSlots)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if len(o) == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
objs = append(objs, o...)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
logrus.Debugf("Failed to find: %s %v\n", err.Error(), b)
|
||||||
|
if len(objs) == 0 {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err = ctx.FindObjectsFinal(session); err != nil {
|
||||||
|
logrus.Debugf("Failed to finalize: %s\n", err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(objs) == 0 {
|
||||||
|
return nil, errors.New("No keys found in yubikey.")
|
||||||
|
}
|
||||||
|
logrus.Debugf("Found %d objects matching list filters", len(objs))
|
||||||
|
for _, obj := range objs {
|
||||||
|
var (
|
||||||
|
cert *x509.Certificate
|
||||||
|
slot []byte
|
||||||
|
)
|
||||||
|
// Retrieve the public-key material to be able to create a new HSMRSAKey
|
||||||
|
attr, err := ctx.GetAttributeValue(session, obj, attrTemplate)
|
||||||
|
if err != nil {
|
||||||
|
logrus.Debugf("Failed to get Attribute for: %v\n", obj)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Iterate through all the attributes of this key and saves CKA_PUBLIC_EXPONENT and CKA_MODULUS. Removes ordering specific issues.
|
||||||
|
for _, a := range attr {
|
||||||
|
if a.Type == pkcs11.CKA_ID {
|
||||||
|
slot = a.Value
|
||||||
|
}
|
||||||
|
if a.Type == pkcs11.CKA_VALUE {
|
||||||
|
cert, err = x509.ParseCertificate(a.Value)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !data.ValidRole(cert.Subject.CommonName) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var ecdsaPubKey *ecdsa.PublicKey
|
||||||
|
switch cert.PublicKeyAlgorithm {
|
||||||
|
case x509.ECDSA:
|
||||||
|
ecdsaPubKey = cert.PublicKey.(*ecdsa.PublicKey)
|
||||||
|
default:
|
||||||
|
logrus.Infof("Unsupported x509 PublicKeyAlgorithm: %d", cert.PublicKeyAlgorithm)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
pubBytes, err := x509.MarshalPKIXPublicKey(ecdsaPubKey)
|
||||||
|
if err != nil {
|
||||||
|
logrus.Debugf("Failed to Marshal public key")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
keys[data.NewECDSAPublicKey(pubBytes).ID()] = yubiSlot{
|
||||||
|
role: cert.Subject.CommonName,
|
||||||
|
slotID: slot,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func getNextEmptySlot(ctx *pkcs11.Ctx, session pkcs11.SessionHandle) ([]byte, error) {
|
||||||
|
findTemplate := []*pkcs11.Attribute{
|
||||||
|
pkcs11.NewAttribute(pkcs11.CKA_TOKEN, true),
|
||||||
|
}
|
||||||
|
attrTemplate := []*pkcs11.Attribute{
|
||||||
|
pkcs11.NewAttribute(pkcs11.CKA_ID, []byte{0}),
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := ctx.FindObjectsInit(session, findTemplate); err != nil {
|
||||||
|
logrus.Debugf("Failed to init: %s\n", err.Error())
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
objs, b, err := ctx.FindObjects(session, numSlots)
|
||||||
|
for err == nil {
|
||||||
|
var o []pkcs11.ObjectHandle
|
||||||
|
o, b, err = ctx.FindObjects(session, numSlots)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if len(o) == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
objs = append(objs, o...)
|
||||||
|
}
|
||||||
|
taken := make(map[int]bool)
|
||||||
|
if err != nil {
|
||||||
|
logrus.Debugf("Failed to find: %s %v\n", err.Error(), b)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for _, obj := range objs {
|
||||||
|
// Retrieve the slot ID
|
||||||
|
attr, err := ctx.GetAttributeValue(session, obj, attrTemplate)
|
||||||
|
if err != nil {
|
||||||
|
logrus.Debugf("Failed to get Attribute for: %v\n", obj)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Iterate through attributes. If an ID attr was found, mark it as taken
|
||||||
|
for _, a := range attr {
|
||||||
|
if a.Type == pkcs11.CKA_ID {
|
||||||
|
if len(a.Value) < 1 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// a byte will always be capable of representing all slot IDs
|
||||||
|
// for the Yubikeys
|
||||||
|
slotNum := int(a.Value[0])
|
||||||
|
if slotNum >= len(taken) {
|
||||||
|
// defensive
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
taken[slotNum] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for i := 0; i < numSlots; i++ {
|
||||||
|
if !taken[i] {
|
||||||
|
return []byte{byte(i)}, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, errors.New("Yubikey has no available slots.")
|
||||||
|
}
|
||||||
|
|
||||||
|
type YubiKeyStore struct {
|
||||||
|
passRetriever passphrase.Retriever
|
||||||
|
keys map[string]yubiSlot
|
||||||
|
backupStore trustmanager.KeyStore
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewYubiKeyStore(backupStore trustmanager.KeyStore, passphraseRetriever passphrase.Retriever) (*YubiKeyStore, error) {
|
||||||
|
s := &YubiKeyStore{
|
||||||
|
passRetriever: passphraseRetriever,
|
||||||
|
keys: make(map[string]yubiSlot),
|
||||||
|
backupStore: backupStore,
|
||||||
|
}
|
||||||
|
s.ListKeys() // populate keys field
|
||||||
|
return s, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *YubiKeyStore) ListKeys() map[string]string {
|
func (s *YubiKeyStore) ListKeys() map[string]string {
|
||||||
|
if len(s.keys) > 0 {
|
||||||
|
return buildKeyMap(s.keys)
|
||||||
|
}
|
||||||
ctx, session, err := SetupHSMEnv(pkcs11Lib)
|
ctx, session, err := SetupHSMEnv(pkcs11Lib)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
defer cleanup(ctx, session)
|
defer cleanup(ctx, session)
|
||||||
ecdsaPubKey, alias, err := getECDSAKey(ctx, session, YUBIKEY_ROOT_KEY_ID)
|
keys, err := listKeys(ctx, session)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logrus.Debugf("error while listing keys from Yubikey: %s", err)
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return map[string]string{ecdsaPubKey.ID(): alias}
|
s.keys = keys
|
||||||
|
return buildKeyMap(keys)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *YubiKeyStore) AddKey(keyID, alias string, privKey data.PrivateKey) error {
|
func (s *YubiKeyStore) AddKey(keyID, role string, privKey data.PrivateKey) error {
|
||||||
// We only allow adding root keys for now
|
// We only allow adding root keys for now
|
||||||
if alias != data.CanonicalRootRole {
|
if role != data.CanonicalRootRole {
|
||||||
return fmt.Errorf("yubikey only supports storing root keys, got %s for key: %s\n", alias, keyID)
|
return fmt.Errorf("yubikey only supports storing root keys, got %s for key: %s\n", role, keyID)
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx, session, err := SetupHSMEnv(pkcs11Lib)
|
ctx, session, err := SetupHSMEnv(pkcs11Lib)
|
||||||
|
|
@ -290,7 +526,27 @@ func (s *YubiKeyStore) AddKey(keyID, alias string, privKey data.PrivateKey) erro
|
||||||
}
|
}
|
||||||
defer cleanup(ctx, session)
|
defer cleanup(ctx, session)
|
||||||
|
|
||||||
return addECDSAKey(ctx, session, privKey, YUBIKEY_ROOT_KEY_ID, s.passRetriever)
|
if k, ok := s.keys[keyID]; ok {
|
||||||
|
if k.role == role {
|
||||||
|
// already have the key and it's associated with the correct role
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
slot, err := getNextEmptySlot(ctx, session)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
logrus.Debugf("Using yubikey slot %v", slot)
|
||||||
|
err = addECDSAKey(ctx, session, privKey, slot, s.passRetriever, role, s.backupStore)
|
||||||
|
if err == nil {
|
||||||
|
s.keys[privKey.ID()] = yubiSlot{
|
||||||
|
role: role,
|
||||||
|
slotID: slot,
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *YubiKeyStore) GetKey(keyID string) (data.PrivateKey, string, error) {
|
func (s *YubiKeyStore) GetKey(keyID string) (data.PrivateKey, string, error) {
|
||||||
|
|
@ -300,7 +556,12 @@ func (s *YubiKeyStore) GetKey(keyID string) (data.PrivateKey, string, error) {
|
||||||
}
|
}
|
||||||
defer cleanup(ctx, session)
|
defer cleanup(ctx, session)
|
||||||
|
|
||||||
pubKey, alias, err := getECDSAKey(ctx, session, YUBIKEY_ROOT_KEY_ID)
|
key, ok := s.keys[keyID]
|
||||||
|
if !ok {
|
||||||
|
return nil, "", errors.New("no matching keys found inside of yubikey")
|
||||||
|
}
|
||||||
|
|
||||||
|
pubKey, alias, err := getECDSAKey(ctx, session, key.slotID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, "", err
|
return nil, "", err
|
||||||
}
|
}
|
||||||
|
|
@ -308,7 +569,7 @@ func (s *YubiKeyStore) GetKey(keyID string) (data.PrivateKey, string, error) {
|
||||||
if pubKey.ID() != keyID {
|
if pubKey.ID() != keyID {
|
||||||
return nil, "", fmt.Errorf("expected root key: %s, but found: %s\n", keyID, pubKey.ID())
|
return nil, "", fmt.Errorf("expected root key: %s, but found: %s\n", keyID, pubKey.ID())
|
||||||
}
|
}
|
||||||
privKey := NewYubiPrivateKey(*pubKey, s.passRetriever)
|
privKey := NewYubiPrivateKey(key.slotID, *pubKey, s.passRetriever)
|
||||||
if privKey == nil {
|
if privKey == nil {
|
||||||
return nil, "", errors.New("could not initialize new YubiPrivateKey")
|
return nil, "", errors.New("could not initialize new YubiPrivateKey")
|
||||||
}
|
}
|
||||||
|
|
@ -317,21 +578,30 @@ func (s *YubiKeyStore) GetKey(keyID string) (data.PrivateKey, string, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *YubiKeyStore) RemoveKey(keyID string) error {
|
func (s *YubiKeyStore) RemoveKey(keyID string) error {
|
||||||
// TODO(diogo): actually implement this
|
ctx, session, err := SetupHSMEnv(pkcs11Lib)
|
||||||
logrus.Debugf("Attempting to remove: %s key inside of YubiKeyStore", keyID)
|
if err != nil {
|
||||||
return nil
|
return nil
|
||||||
|
}
|
||||||
|
defer cleanup(ctx, session)
|
||||||
|
key, ok := s.keys[keyID]
|
||||||
|
if !ok {
|
||||||
|
return errors.New("Key not present in yubikey")
|
||||||
|
}
|
||||||
|
return removeKey(ctx, session, key.slotID, s.passRetriever, keyID)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *YubiKeyStore) ExportKey(keyID string) ([]byte, error) {
|
func (s *YubiKeyStore) ExportKey(keyID string) ([]byte, error) {
|
||||||
// TODO(diogo): actually implement this
|
|
||||||
logrus.Debugf("Attempting to export: %s key inside of YubiKeyStore", keyID)
|
logrus.Debugf("Attempting to export: %s key inside of YubiKeyStore", keyID)
|
||||||
return nil, nil
|
return nil, errors.New("Keys cannot be exported from a Yubikey.")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *YubiKeyStore) ImportKey(pemBytes []byte, keyID string) error {
|
func (s *YubiKeyStore) ImportKey(pemBytes []byte, keyID string) error {
|
||||||
// TODO(diogo): actually implement this
|
|
||||||
logrus.Debugf("Attempting to import: %s key inside of YubiKeyStore", keyID)
|
logrus.Debugf("Attempting to import: %s key inside of YubiKeyStore", keyID)
|
||||||
return nil
|
privKey, _, err := trustmanager.GetPasswdDecryptBytes(s.passRetriever, pemBytes, "imported", "root")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return s.AddKey(privKey.ID(), "root", privKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
func cleanup(ctx *pkcs11.Ctx, session pkcs11.SessionHandle) {
|
func cleanup(ctx *pkcs11.Ctx, session pkcs11.SessionHandle) {
|
||||||
|
|
@ -407,3 +677,11 @@ func login(ctx *pkcs11.Ctx, session pkcs11.SessionHandle, passRetriever passphra
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func buildKeyMap(keys map[string]yubiSlot) map[string]string {
|
||||||
|
res := make(map[string]string)
|
||||||
|
for k, v := range keys {
|
||||||
|
res[k] = v.role
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/docker/notary/cryptoservice"
|
"github.com/docker/notary/cryptoservice"
|
||||||
"github.com/docker/notary/pkg/passphrase"
|
"github.com/docker/notary/passphrase"
|
||||||
"github.com/docker/notary/signer"
|
"github.com/docker/notary/signer"
|
||||||
"github.com/docker/notary/signer/api"
|
"github.com/docker/notary/signer/api"
|
||||||
"github.com/docker/notary/trustmanager"
|
"github.com/docker/notary/trustmanager"
|
||||||
|
|
|
||||||
|
|
@ -1,212 +0,0 @@
|
||||||
// +build pkcs11
|
|
||||||
|
|
||||||
package api
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto"
|
|
||||||
"crypto/rand"
|
|
||||||
"crypto/rsa"
|
|
||||||
"crypto/sha256"
|
|
||||||
"crypto/x509"
|
|
||||||
"errors"
|
|
||||||
"log"
|
|
||||||
"math/big"
|
|
||||||
|
|
||||||
"github.com/docker/notary/signer/keys"
|
|
||||||
"github.com/docker/notary/tuf/data"
|
|
||||||
"github.com/miekg/pkcs11"
|
|
||||||
)
|
|
||||||
|
|
||||||
// RSAHardwareCryptoService is an implementation of SigningService
|
|
||||||
type RSAHardwareCryptoService struct {
|
|
||||||
keys map[string]*keys.HSMRSAKey
|
|
||||||
context *pkcs11.Ctx
|
|
||||||
session pkcs11.SessionHandle
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListKeys not implemented yet
|
|
||||||
func (s *RSAHardwareCryptoService) ListKeys(role string) []string {
|
|
||||||
return []string{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create creates a key and returns its public components
|
|
||||||
func (s *RSAHardwareCryptoService) Create(role, algo string) (data.PublicKey, error) {
|
|
||||||
// For now generate random labels for keys
|
|
||||||
// (diogo): add link between keyID and label in database so we can support multiple keys
|
|
||||||
randomLabel := make([]byte, 32)
|
|
||||||
_, err := rand.Read(randomLabel)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.New("Could not generate a random key label.")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set the public key template
|
|
||||||
// CKA_TOKEN: Guarantees key persistence in hardware
|
|
||||||
// CKA_LABEL: Identifies this specific key inside of the HSM
|
|
||||||
publicKeyTemplate := []*pkcs11.Attribute{
|
|
||||||
pkcs11.NewAttribute(pkcs11.CKA_TOKEN, true),
|
|
||||||
pkcs11.NewAttribute(pkcs11.CKA_PUBLIC_EXPONENT, []byte{3}),
|
|
||||||
pkcs11.NewAttribute(pkcs11.CKA_MODULUS_BITS, 2048),
|
|
||||||
pkcs11.NewAttribute(pkcs11.CKA_LABEL, string(randomLabel)),
|
|
||||||
}
|
|
||||||
privateKeyTemplate := []*pkcs11.Attribute{
|
|
||||||
pkcs11.NewAttribute(pkcs11.CKA_TOKEN, true),
|
|
||||||
pkcs11.NewAttribute(pkcs11.CKA_PRIVATE, true),
|
|
||||||
pkcs11.NewAttribute(pkcs11.CKA_SIGN, true),
|
|
||||||
pkcs11.NewAttribute(pkcs11.CKA_LABEL, string(randomLabel)),
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generate a new RSA private/public keypair inside of the HSM
|
|
||||||
pub, priv, err := s.context.GenerateKeyPair(s.session,
|
|
||||||
[]*pkcs11.Mechanism{pkcs11.NewMechanism(pkcs11.CKM_RSA_PKCS_KEY_PAIR_GEN, nil)},
|
|
||||||
publicKeyTemplate, privateKeyTemplate)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.New("Could not generate a new key inside of the HSM.")
|
|
||||||
}
|
|
||||||
|
|
||||||
// (diogo): This template is used for the GetAttribute
|
|
||||||
template := []*pkcs11.Attribute{
|
|
||||||
pkcs11.NewAttribute(pkcs11.CKA_PUBLIC_EXPONENT, nil),
|
|
||||||
pkcs11.NewAttribute(pkcs11.CKA_MODULUS_BITS, nil),
|
|
||||||
pkcs11.NewAttribute(pkcs11.CKA_MODULUS, nil),
|
|
||||||
}
|
|
||||||
|
|
||||||
// Retrieve the public-key material to be able to create a new HSMRSAKey
|
|
||||||
attr, err := s.context.GetAttributeValue(s.session, pub, template)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.New("Failed to get Attribute value.")
|
|
||||||
}
|
|
||||||
|
|
||||||
// We're going to store the elements of the RSA Public key, exponent and Modulus inside of exp and mod
|
|
||||||
var exp int
|
|
||||||
mod := big.NewInt(0)
|
|
||||||
|
|
||||||
// Iterate through all the attributes of this key and saves CKA_PUBLIC_EXPONENT and CKA_MODULUS. Removes ordering specific issues.
|
|
||||||
for _, a := range attr {
|
|
||||||
if a.Type == pkcs11.CKA_PUBLIC_EXPONENT {
|
|
||||||
exp, _ = readInt(a.Value)
|
|
||||||
}
|
|
||||||
|
|
||||||
if a.Type == pkcs11.CKA_MODULUS {
|
|
||||||
mod.SetBytes(a.Value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
rsaPublicKey := rsa.PublicKey{N: mod, E: exp}
|
|
||||||
// Using x509 to Marshal the Public key into der encoding
|
|
||||||
pubBytes, err := x509.MarshalPKIXPublicKey(&rsaPublicKey)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.New("Failed to Marshal public key.")
|
|
||||||
}
|
|
||||||
|
|
||||||
// (diogo): Ideally I would like to return base64 PEM encoded public keys to the client
|
|
||||||
k := keys.NewHSMRSAKey(pubBytes, priv)
|
|
||||||
|
|
||||||
keyID := k.ID()
|
|
||||||
|
|
||||||
s.keys[keyID] = k
|
|
||||||
|
|
||||||
return k, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// RemoveKey removes a key from the key database
|
|
||||||
func (s *RSAHardwareCryptoService) RemoveKey(keyID string) error {
|
|
||||||
if _, ok := s.keys[keyID]; !ok {
|
|
||||||
return keys.ErrInvalidKeyID
|
|
||||||
}
|
|
||||||
|
|
||||||
delete(s.keys, keyID)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetKey returns the public components of a particular key
|
|
||||||
func (s *RSAHardwareCryptoService) GetKey(keyID string) data.PublicKey {
|
|
||||||
key, ok := s.keys[keyID]
|
|
||||||
if !ok {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return key
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetPrivateKey is not implemented
|
|
||||||
func (s *RSAHardwareCryptoService) GetPrivateKey(keyID string) (data.PrivateKey, string, error) {
|
|
||||||
return nil, "", errors.New("Not yet implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sign returns a signature for a given signature request
|
|
||||||
func (s *RSAHardwareCryptoService) Sign(keyIDs []string, payload []byte) ([]data.Signature, error) {
|
|
||||||
signatures := make([]data.Signature, 0, len(keyIDs))
|
|
||||||
for _, keyid := range keyIDs {
|
|
||||||
privateKey, present := s.keys[keyid]
|
|
||||||
if !present {
|
|
||||||
// We skip keys that aren't found
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
priv := privateKey.PKCS11ObjectHandle()
|
|
||||||
var sig []byte
|
|
||||||
var err error
|
|
||||||
for i := 0; i < 3; i++ {
|
|
||||||
s.context.SignInit(s.session, []*pkcs11.Mechanism{pkcs11.NewMechanism(pkcs11.CKM_SHA256_RSA_PKCS, nil)}, priv)
|
|
||||||
|
|
||||||
sig, err = s.context.Sign(s.session, payload)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("Error while signing: %s", err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
digest := sha256.Sum256(payload)
|
|
||||||
pub, err := x509.ParsePKIXPublicKey(privateKey.Public())
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("Failed to parse public key: %s\n", err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
rsaPub, ok := pub.(*rsa.PublicKey)
|
|
||||||
if !ok {
|
|
||||||
log.Printf("Value returned from ParsePKIXPublicKey was not an RSA public key")
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = rsa.VerifyPKCS1v15(rsaPub, crypto.SHA256, digest[:], sig)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("Failed verification. Retrying: %s", err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
if sig == nil {
|
|
||||||
return nil, errors.New("Failed to create signature")
|
|
||||||
}
|
|
||||||
|
|
||||||
signatures = append(signatures, data.Signature{
|
|
||||||
KeyID: keyid,
|
|
||||||
Method: data.RSAPKCS1v15Signature,
|
|
||||||
Signature: sig[:],
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return signatures, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewRSAHardwareCryptoService returns an instance of RSAHardwareCryptoService
|
|
||||||
func NewRSAHardwareCryptoService(ctx *pkcs11.Ctx, session pkcs11.SessionHandle) *RSAHardwareCryptoService {
|
|
||||||
return &RSAHardwareCryptoService{
|
|
||||||
keys: make(map[string]*keys.HSMRSAKey),
|
|
||||||
context: ctx,
|
|
||||||
session: session,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// readInt converts a []byte into an int. It is used to convert the RSA Public key exponent into an int to create a crypto.PublicKey
|
|
||||||
func readInt(data []byte) (int, error) {
|
|
||||||
var ret int
|
|
||||||
if len(data) > 4 {
|
|
||||||
return 0, errors.New("Cannot convert byte array due to size")
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, a := range data {
|
|
||||||
ret |= (int(a) << uint(i*8))
|
|
||||||
}
|
|
||||||
return ret, nil
|
|
||||||
}
|
|
||||||
|
|
@ -6,7 +6,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/docker/notary/pkg/passphrase"
|
"github.com/docker/notary/passphrase"
|
||||||
"github.com/docker/notary/trustmanager"
|
"github.com/docker/notary/trustmanager"
|
||||||
"github.com/docker/notary/tuf/data"
|
"github.com/docker/notary/tuf/data"
|
||||||
jose "github.com/dvsekhvalnov/jose2go"
|
jose "github.com/dvsekhvalnov/jose2go"
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"net"
|
"net"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
|
@ -105,6 +106,11 @@ func (trust *NotarySigner) ListKeys(role string) []string {
|
||||||
return []string{}
|
return []string{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ListAllKeys not supported for NotarySigner
|
||||||
|
func (trust *NotarySigner) ListAllKeys() map[string]string {
|
||||||
|
return map[string]string{}
|
||||||
|
}
|
||||||
|
|
||||||
// CheckHealth checks the health of one of the clients, since both clients run
|
// CheckHealth checks the health of one of the clients, since both clients run
|
||||||
// from the same GRPC server.
|
// from the same GRPC server.
|
||||||
func (trust *NotarySigner) CheckHealth(timeout time.Duration) error {
|
func (trust *NotarySigner) CheckHealth(timeout time.Duration) error {
|
||||||
|
|
@ -126,3 +132,9 @@ func (trust *NotarySigner) CheckHealth(timeout time.Duration) error {
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ImportRootKey satisfies the CryptoService interface. It should not be implemented
|
||||||
|
// for a NotarySigner.
|
||||||
|
func (trust *NotarySigner) ImportRootKey(r io.Reader) error {
|
||||||
|
return errors.New("Importing a root key to NotarySigner is not supported")
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ package trustmanager
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/docker/notary"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
@ -11,8 +12,8 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
visible os.FileMode = 0755
|
visible = notary.PubCertPerms
|
||||||
private os.FileMode = 0700
|
private = notary.PrivKeyPerms
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/docker/notary/pkg/passphrase"
|
"github.com/docker/notary/passphrase"
|
||||||
"github.com/docker/notary/tuf/data"
|
"github.com/docker/notary/tuf/data"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -197,7 +197,7 @@ func getKey(s LimitedFileStore, passphraseRetriever passphrase.Retriever, cached
|
||||||
// See if the key is encrypted. If its encrypted we'll fail to parse the private key
|
// See if the key is encrypted. If its encrypted we'll fail to parse the private key
|
||||||
privKey, err := ParsePEMPrivateKey(keyBytes, "")
|
privKey, err := ParsePEMPrivateKey(keyBytes, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
privKey, _, retErr = getPasswdDecryptBytes(s, passphraseRetriever, keyBytes, name, string(keyAlias))
|
privKey, _, retErr = GetPasswdDecryptBytes(passphraseRetriever, keyBytes, name, string(keyAlias))
|
||||||
}
|
}
|
||||||
if retErr != nil {
|
if retErr != nil {
|
||||||
return nil, "", retErr
|
return nil, "", retErr
|
||||||
|
|
@ -268,9 +268,9 @@ func getRawKey(s LimitedFileStore, name string) ([]byte, string, error) {
|
||||||
return keyBytes, keyAlias, nil
|
return keyBytes, keyAlias, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the password to decript the given pem bytes. Return the password,
|
// GetPasswdDecryptBytes gets the password to decript the given pem bytes.
|
||||||
// because it is useful for importing
|
// Returns the password and private key
|
||||||
func getPasswdDecryptBytes(s LimitedFileStore, passphraseRetriever passphrase.Retriever, pemBytes []byte, name, alias string) (data.PrivateKey, string, error) {
|
func GetPasswdDecryptBytes(passphraseRetriever passphrase.Retriever, pemBytes []byte, name, alias string) (data.PrivateKey, string, error) {
|
||||||
var (
|
var (
|
||||||
passwd string
|
passwd string
|
||||||
retErr error
|
retErr error
|
||||||
|
|
@ -329,12 +329,17 @@ func encryptAndAddKey(s LimitedFileStore, passwd string, cachedKeys map[string]*
|
||||||
|
|
||||||
func importKey(s LimitedFileStore, passphraseRetriever passphrase.Retriever, cachedKeys map[string]*cachedKey, alias string, pemBytes []byte) error {
|
func importKey(s LimitedFileStore, passphraseRetriever passphrase.Retriever, cachedKeys map[string]*cachedKey, alias string, pemBytes []byte) error {
|
||||||
|
|
||||||
privKey, passphrase, err := getPasswdDecryptBytes(s, passphraseRetriever, pemBytes, "imported", alias)
|
if alias != data.CanonicalRootRole {
|
||||||
|
return s.Add(alias, pemBytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
privKey, passphrase, err := GetPasswdDecryptBytes(passphraseRetriever, pemBytes, "imported", alias)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return encryptAndAddKey(
|
var name string
|
||||||
s, passphrase, cachedKeys, privKey.ID(), alias, privKey)
|
name = privKey.ID()
|
||||||
|
return encryptAndAddKey(s, passphrase, cachedKeys, name, alias, privKey)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/docker/notary/pkg/passphrase"
|
"github.com/docker/notary/passphrase"
|
||||||
"github.com/docker/notary/tuf/data"
|
"github.com/docker/notary/tuf/data"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -3,27 +3,37 @@ package signed
|
||||||
import (
|
import (
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"errors"
|
"errors"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
|
||||||
"github.com/agl/ed25519"
|
"github.com/agl/ed25519"
|
||||||
"github.com/docker/notary/tuf/data"
|
"github.com/docker/notary/tuf/data"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type edCryptoKey struct {
|
||||||
|
role string
|
||||||
|
privKey data.PrivateKey
|
||||||
|
}
|
||||||
|
|
||||||
// Ed25519 implements a simple in memory cryptosystem for ED25519 keys
|
// Ed25519 implements a simple in memory cryptosystem for ED25519 keys
|
||||||
type Ed25519 struct {
|
type Ed25519 struct {
|
||||||
keys map[string]data.PrivateKey
|
keys map[string]edCryptoKey
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewEd25519 initializes a new empty Ed25519 CryptoService that operates
|
// NewEd25519 initializes a new empty Ed25519 CryptoService that operates
|
||||||
// entirely in memory
|
// entirely in memory
|
||||||
func NewEd25519() *Ed25519 {
|
func NewEd25519() *Ed25519 {
|
||||||
return &Ed25519{
|
return &Ed25519{
|
||||||
make(map[string]data.PrivateKey),
|
make(map[string]edCryptoKey),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// addKey allows you to add a private key
|
// addKey allows you to add a private key
|
||||||
func (e *Ed25519) addKey(k data.PrivateKey) {
|
func (e *Ed25519) addKey(role string, k data.PrivateKey) {
|
||||||
e.keys[k.ID()] = k
|
e.keys[k.ID()] = edCryptoKey{
|
||||||
|
role: role,
|
||||||
|
privKey: k,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// RemoveKey deletes a key from the signer
|
// RemoveKey deletes a key from the signer
|
||||||
|
|
@ -41,12 +51,21 @@ func (e *Ed25519) ListKeys(role string) []string {
|
||||||
return keyIDs
|
return keyIDs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ListAllKeys returns the map of keys IDs to role
|
||||||
|
func (e *Ed25519) ListAllKeys() map[string]string {
|
||||||
|
keys := make(map[string]string)
|
||||||
|
for id, edKey := range e.keys {
|
||||||
|
keys[id] = edKey.role
|
||||||
|
}
|
||||||
|
return keys
|
||||||
|
}
|
||||||
|
|
||||||
// Sign generates an Ed25519 signature over the data
|
// Sign generates an Ed25519 signature over the data
|
||||||
func (e *Ed25519) Sign(keyIDs []string, toSign []byte) ([]data.Signature, error) {
|
func (e *Ed25519) Sign(keyIDs []string, toSign []byte) ([]data.Signature, error) {
|
||||||
signatures := make([]data.Signature, 0, len(keyIDs))
|
signatures := make([]data.Signature, 0, len(keyIDs))
|
||||||
for _, keyID := range keyIDs {
|
for _, keyID := range keyIDs {
|
||||||
priv := [ed25519.PrivateKeySize]byte{}
|
priv := [ed25519.PrivateKeySize]byte{}
|
||||||
copy(priv[:], e.keys[keyID].Private())
|
copy(priv[:], e.keys[keyID].privKey.Private())
|
||||||
sig := ed25519.Sign(&priv, toSign)
|
sig := ed25519.Sign(&priv, toSign)
|
||||||
signatures = append(signatures, data.Signature{
|
signatures = append(signatures, data.Signature{
|
||||||
KeyID: keyID,
|
KeyID: keyID,
|
||||||
|
|
@ -74,7 +93,7 @@ func (e *Ed25519) Create(role, algorithm string) (data.PublicKey, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
e.addKey(private)
|
e.addKey(role, private)
|
||||||
return public, nil
|
return public, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -83,8 +102,8 @@ func (e *Ed25519) Create(role, algorithm string) (data.PublicKey, error) {
|
||||||
func (e *Ed25519) PublicKeys(keyIDs ...string) (map[string]data.PublicKey, error) {
|
func (e *Ed25519) PublicKeys(keyIDs ...string) (map[string]data.PublicKey, error) {
|
||||||
k := make(map[string]data.PublicKey)
|
k := make(map[string]data.PublicKey)
|
||||||
for _, keyID := range keyIDs {
|
for _, keyID := range keyIDs {
|
||||||
if key, ok := e.keys[keyID]; ok {
|
if edKey, ok := e.keys[keyID]; ok {
|
||||||
k[keyID] = data.PublicKeyFromPrivate(key)
|
k[keyID] = data.PublicKeyFromPrivate(edKey.privKey)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return k, nil
|
return k, nil
|
||||||
|
|
@ -92,10 +111,29 @@ func (e *Ed25519) PublicKeys(keyIDs ...string) (map[string]data.PublicKey, error
|
||||||
|
|
||||||
// GetKey returns a single public key based on the ID
|
// GetKey returns a single public key based on the ID
|
||||||
func (e *Ed25519) GetKey(keyID string) data.PublicKey {
|
func (e *Ed25519) GetKey(keyID string) data.PublicKey {
|
||||||
return data.PublicKeyFromPrivate(e.keys[keyID])
|
return data.PublicKeyFromPrivate(e.keys[keyID].privKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetPrivateKey returns a single private key based on the ID
|
// GetPrivateKey returns a single private key based on the ID
|
||||||
func (e *Ed25519) GetPrivateKey(keyID string) (data.PrivateKey, string, error) {
|
func (e *Ed25519) GetPrivateKey(keyID string) (data.PrivateKey, string, error) {
|
||||||
return e.keys[keyID], "", nil
|
return e.keys[keyID].privKey, "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ImportRootKey adds an Ed25519 key to the store as a root key
|
||||||
|
func (e *Ed25519) ImportRootKey(r io.Reader) error {
|
||||||
|
raw, err := ioutil.ReadAll(r)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
dataSize := ed25519.PublicKeySize + ed25519.PrivateKeySize
|
||||||
|
if len(raw) < dataSize || len(raw) > dataSize {
|
||||||
|
return errors.New("Wrong length of data for Ed25519 Key Import")
|
||||||
|
}
|
||||||
|
public := data.NewED25519PublicKey(raw[:ed25519.PublicKeySize])
|
||||||
|
private, err := data.NewED25519PrivateKey(*public, raw[ed25519.PublicKeySize:])
|
||||||
|
e.keys[private.ID()] = edCryptoKey{
|
||||||
|
role: "root",
|
||||||
|
privKey: private,
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ package signed
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/docker/notary/tuf/data"
|
"github.com/docker/notary/tuf/data"
|
||||||
|
"io"
|
||||||
)
|
)
|
||||||
|
|
||||||
// SigningService defines the necessary functions to determine
|
// SigningService defines the necessary functions to determine
|
||||||
|
|
@ -32,8 +33,15 @@ type KeyService interface {
|
||||||
// RemoveKey deletes the specified key
|
// RemoveKey deletes the specified key
|
||||||
RemoveKey(keyID string) error
|
RemoveKey(keyID string) error
|
||||||
|
|
||||||
// ListKeys returns a map of IDs to role
|
// ListKeys returns a list of key IDs for the role
|
||||||
ListKeys(role string) []string
|
ListKeys(role string) []string
|
||||||
|
|
||||||
|
// ListAllKeys returns a map of all available signing key IDs to role
|
||||||
|
ListAllKeys() map[string]string
|
||||||
|
|
||||||
|
// ImportRootKey imports a root key to the highest priority keystore associated with
|
||||||
|
// the cryptoservice
|
||||||
|
ImportRootKey(source io.Reader) error
|
||||||
}
|
}
|
||||||
|
|
||||||
// CryptoService defines a unified Signing and Key Service as this
|
// CryptoService defines a unified Signing and Key Service as this
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"encoding/pem"
|
"encoding/pem"
|
||||||
"errors"
|
"errors"
|
||||||
|
"io"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/docker/notary/trustmanager"
|
"github.com/docker/notary/trustmanager"
|
||||||
|
|
@ -38,6 +39,15 @@ func (mts *FailingCryptoService) ListKeys(role string) []string {
|
||||||
return []string{mts.testKey.ID()}
|
return []string{mts.testKey.ID()}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (mts *FailingCryptoService) ListAllKeys() map[string]string {
|
||||||
|
return map[string]string{
|
||||||
|
mts.testKey.ID(): "root",
|
||||||
|
mts.testKey.ID(): "targets",
|
||||||
|
mts.testKey.ID(): "snapshot",
|
||||||
|
mts.testKey.ID(): "timestamp",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (mts *FailingCryptoService) GetKey(keyID string) data.PublicKey {
|
func (mts *FailingCryptoService) GetKey(keyID string) data.PublicKey {
|
||||||
if keyID == "testID" {
|
if keyID == "testID" {
|
||||||
return mts.testKey
|
return mts.testKey
|
||||||
|
|
@ -53,6 +63,10 @@ func (mts *FailingCryptoService) RemoveKey(keyID string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (mts *FailingCryptoService) ImportRootKey(r io.Reader) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
type MockCryptoService struct {
|
type MockCryptoService struct {
|
||||||
testKey data.PublicKey
|
testKey data.PublicKey
|
||||||
}
|
}
|
||||||
|
|
@ -80,6 +94,15 @@ func (mts *MockCryptoService) ListKeys(role string) []string {
|
||||||
return []string{mts.testKey.ID()}
|
return []string{mts.testKey.ID()}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (mts *MockCryptoService) ListAllKeys() map[string]string {
|
||||||
|
return map[string]string{
|
||||||
|
mts.testKey.ID(): "root",
|
||||||
|
mts.testKey.ID(): "targets",
|
||||||
|
mts.testKey.ID(): "snapshot",
|
||||||
|
mts.testKey.ID(): "timestamp",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (mts *MockCryptoService) GetPrivateKey(keyID string) (data.PrivateKey, string, error) {
|
func (mts *MockCryptoService) GetPrivateKey(keyID string) (data.PrivateKey, string, error) {
|
||||||
return nil, "", errors.New("Not implemented")
|
return nil, "", errors.New("Not implemented")
|
||||||
}
|
}
|
||||||
|
|
@ -88,6 +111,10 @@ func (mts *MockCryptoService) RemoveKey(keyID string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (mts *MockCryptoService) ImportRootKey(r io.Reader) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
var _ CryptoService = &MockCryptoService{}
|
var _ CryptoService = &MockCryptoService{}
|
||||||
|
|
||||||
type StrictMockCryptoService struct {
|
type StrictMockCryptoService struct {
|
||||||
|
|
@ -115,6 +142,19 @@ func (mts *StrictMockCryptoService) ListKeys(role string) []string {
|
||||||
return []string{mts.testKey.ID()}
|
return []string{mts.testKey.ID()}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (mts *StrictMockCryptoService) ListAllKeys() map[string]string {
|
||||||
|
return map[string]string{
|
||||||
|
mts.testKey.ID(): "root",
|
||||||
|
mts.testKey.ID(): "targets",
|
||||||
|
mts.testKey.ID(): "snapshot",
|
||||||
|
mts.testKey.ID(): "timestamp",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mts *StrictMockCryptoService) ImportRootKey(r io.Reader) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// Test signing and ensure the expected signature is added
|
// Test signing and ensure the expected signature is added
|
||||||
func TestBasicSign(t *testing.T) {
|
func TestBasicSign(t *testing.T) {
|
||||||
testKey, _ := pem.Decode([]byte(testKeyPEM1))
|
testKey, _ := pem.Decode([]byte(testKeyPEM1))
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue