mirror of https://github.com/docker/docs.git
				
				
				
			Added a keyfilestore with encrypted PEM support
This commit is contained in:
		
							parent
							
								
									bce5930763
								
							
						
					
					
						commit
						fd8471038c
					
				|  | @ -16,17 +16,18 @@ import ( | |||
| 	"github.com/spf13/viper" | ||||
| ) | ||||
| 
 | ||||
| type cliCryptoService struct { | ||||
| type CliCryptoService struct { | ||||
| 	privateKeys map[string]*data.PrivateKey | ||||
| 	gun         string | ||||
| } | ||||
| 
 | ||||
| func NewCryptoService(gun string) *cliCryptoService { | ||||
| 	return &cliCryptoService{privateKeys: make(map[string]*data.PrivateKey), gun: gun} | ||||
| // NewCryptoService returns an instance ofS cliCryptoService
 | ||||
| func NewCryptoService(gun string) *CliCryptoService { | ||||
| 	return &CliCryptoService{privateKeys: make(map[string]*data.PrivateKey), gun: gun} | ||||
| } | ||||
| 
 | ||||
| // Create is used to generate keys for targets, snapshots and timestamps
 | ||||
| func (ccs *cliCryptoService) Create(role string) (*data.PublicKey, error) { | ||||
| func (ccs *CliCryptoService) Create(role string) (*data.PublicKey, error) { | ||||
| 	_, cert, err := generateKeyAndCert(ccs.gun) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
|  | @ -45,7 +46,7 @@ func (ccs *cliCryptoService) Create(role string) (*data.PublicKey, error) { | |||
| } | ||||
| 
 | ||||
| // Sign returns the signatures for data with the given keyIDs
 | ||||
| func (ccs *cliCryptoService) Sign(keyIDs []string, payload []byte) ([]data.Signature, error) { | ||||
| func (ccs *CliCryptoService) Sign(keyIDs []string, payload []byte) ([]data.Signature, error) { | ||||
| 	// Create hasher and hash data
 | ||||
| 	hash := crypto.SHA256 | ||||
| 	hashed := sha256.Sum256(payload) | ||||
|  | @ -83,7 +84,7 @@ func (ccs *cliCryptoService) Sign(keyIDs []string, payload []byte) ([]data.Signa | |||
| 	return signatures, nil | ||||
| } | ||||
| 
 | ||||
| //TODO (diogo): Add support for EC P384
 | ||||
| // generateKeyAndCert deals with the creation and storage of a key and returns a cert
 | ||||
| func generateKeyAndCert(gun string) (crypto.PrivateKey, *x509.Certificate, error) { | ||||
| 	// Generates a new RSA key
 | ||||
| 	key, err := rsa.GenerateKey(rand.Reader, 2048) | ||||
|  | @ -91,8 +92,6 @@ func generateKeyAndCert(gun string) (crypto.PrivateKey, *x509.Certificate, error | |||
| 		return nil, nil, fmt.Errorf("could not generate private key: %v", err) | ||||
| 	} | ||||
| 
 | ||||
| 	keyBytes := x509.MarshalPKCS1PrivateKey(key) | ||||
| 
 | ||||
| 	// Creates a new Certificate template. We need the certificate to calculate the
 | ||||
| 	// TUF-compliant keyID
 | ||||
| 	//TODO (diogo): We're hardcoding the Organization to be the GUN. Probably want to
 | ||||
|  | @ -113,6 +112,10 @@ func generateKeyAndCert(gun string) (crypto.PrivateKey, *x509.Certificate, error | |||
| 	// The key is going to be stored in the private directory, using the GUN and
 | ||||
| 	// the filename will be the TUF-compliant ID. The Store takes care of extensions.
 | ||||
| 	privKeyFilename := filepath.Join(gun, fingerprint) | ||||
| 	privKeyStore.Add(privKeyFilename, trustmanager.KeyToPEM(keyBytes)) | ||||
| 	return key, cert, nil | ||||
| 	pemKey, err := trustmanager.KeyToPEM(key) | ||||
| 	if err != nil { | ||||
| 		return nil, nil, fmt.Errorf("failed to generate the certificate for key: %v", err) | ||||
| 	} | ||||
| 
 | ||||
| 	return key, cert, privKeyStore.Add(privKeyFilename, pemKey) | ||||
| } | ||||
|  |  | |||
|  | @ -113,7 +113,7 @@ func keysRemove(cmd *cobra.Command, args []string) { | |||
| 	} | ||||
| 
 | ||||
| 	// Remove all the keys under the Global Unique Name
 | ||||
| 	err = privKeyStore.RemoveDir(gunOrID) | ||||
| 	err = privKeyStore.RemoveAll(gunOrID) | ||||
| 	if err != nil { | ||||
| 		fatalf("failed to remove all Private keys under Global Unique Name: %s", gunOrID) | ||||
| 	} | ||||
|  |  | |||
|  | @ -25,7 +25,7 @@ const tufDir string = configPath + "tuf/" | |||
| 
 | ||||
| var caStore trustmanager.X509Store | ||||
| var certificateStore trustmanager.X509Store | ||||
| var privKeyStore trustmanager.FileStore | ||||
| var privKeyStore *trustmanager.KeyFileStore | ||||
| 
 | ||||
| var rawOutput bool | ||||
| 
 | ||||
|  | @ -91,7 +91,7 @@ func init() { | |||
| 		fatalf("could not create X509FileStore: %v", err) | ||||
| 	} | ||||
| 
 | ||||
| 	privKeyStore, err = trustmanager.NewPrivateFileStore(finalPrivDir, "key") | ||||
| 	privKeyStore, err = trustmanager.NewKeyFileStore(finalPrivDir) | ||||
| 	if err != nil { | ||||
| 		fatalf("could not create FileStore: %v", err) | ||||
| 	} | ||||
|  |  | |||
|  | @ -0,0 +1,99 @@ | |||
| package trustmanager | ||||
| 
 | ||||
| import ( | ||||
| 	"crypto" | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| ) | ||||
| 
 | ||||
| const ( | ||||
| 	keyExtension = "key" | ||||
| ) | ||||
| 
 | ||||
| // KeyFileStore persists and manages private keys on disk
 | ||||
| type KeyFileStore struct { | ||||
| 	fingerprintMap map[string]string | ||||
| 	fileStore      FileStore | ||||
| } | ||||
| 
 | ||||
| // NewKeyFileStore returns a new KeyFileStore.
 | ||||
| func NewKeyFileStore(directory string) (*KeyFileStore, error) { | ||||
| 	fileStore, err := NewPrivateFileStore(directory, keyExtension) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 
 | ||||
| 	return &KeyFileStore{ | ||||
| 		fingerprintMap: make(map[string]string), | ||||
| 		fileStore:      fileStore, | ||||
| 	}, nil | ||||
| } | ||||
| 
 | ||||
| // Add stores both the PrivateKey bytes in a file
 | ||||
| func (s *KeyFileStore) Add(fileName string, privKey crypto.PrivateKey) error { | ||||
| 	if privKey == nil { | ||||
| 		return errors.New("adding nil key to keyFileStore") | ||||
| 	} | ||||
| 
 | ||||
| 	pemKey, err := KeyToPEM(privKey) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 
 | ||||
| 	return s.fileStore.Add(fileName, pemKey) | ||||
| } | ||||
| 
 | ||||
| // Get returns a PrivateKey given a filename
 | ||||
| func (s *KeyFileStore) Get(fileName string) (crypto.PrivateKey, error) { | ||||
| 	keyBytes, err := s.fileStore.GetData(fileName) | ||||
| 	if err != nil { | ||||
| 		return nil, errors.New("Could not retrieve private key material") | ||||
| 	} | ||||
| 
 | ||||
| 	return ParseRawPrivateKey(keyBytes) | ||||
| } | ||||
| 
 | ||||
| // AddEncrypted stores the contents of the private key as an encrypted PEM block
 | ||||
| func (s *KeyFileStore) AddEncrypted(fileName string, privKey crypto.PrivateKey, passphrase string) error { | ||||
| 	if privKey == nil { | ||||
| 		return errors.New("adding nil key to keyFileStore") | ||||
| 	} | ||||
| 
 | ||||
| 	encryptedKey, err := KeyToEncryptedPEM(privKey, passphrase) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 
 | ||||
| 	fmt.Println(string(encryptedKey)) | ||||
| 	return s.fileStore.Add(fileName, encryptedKey) | ||||
| } | ||||
| 
 | ||||
| // GetDecrypted decrypts and returns the private key
 | ||||
| func (s *KeyFileStore) GetDecrypted(fileName string, passphrase string) (crypto.PrivateKey, error) { | ||||
| 	keyBytes, err := s.fileStore.GetData(fileName) | ||||
| 	if err != nil { | ||||
| 		return nil, errors.New("could not retrieve private key material") | ||||
| 	} | ||||
| 
 | ||||
| 	return ParseRawEncryptedPrivateKey(keyBytes, passphrase) | ||||
| } | ||||
| 
 | ||||
| // Remove removes a key from a store
 | ||||
| func (s *KeyFileStore) Remove(fileName string) error { | ||||
| 	return s.fileStore.Remove(fileName) | ||||
| } | ||||
| 
 | ||||
| // RemoveAll removes all the keys under a directory
 | ||||
| func (s *KeyFileStore) RemoveAll(directoryName string) error { | ||||
| 	return s.fileStore.RemoveDir(directoryName) | ||||
| } | ||||
| 
 | ||||
| // List returns a list of all the keys the store is currently managing
 | ||||
| func (s *KeyFileStore) ListAll() []string { | ||||
| 	return s.fileStore.ListAll() | ||||
| } | ||||
| 
 | ||||
| // List returns a list of all the keys the store is currently managing
 | ||||
| func (s *KeyFileStore) ListDir(directoryName string) []string { | ||||
| 	return s.fileStore.ListDir(directoryName) | ||||
| } | ||||
|  | @ -0,0 +1,254 @@ | |||
| package trustmanager | ||||
| 
 | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"crypto/rand" | ||||
| 	"crypto/rsa" | ||||
| 	"io/ioutil" | ||||
| 	"os" | ||||
| 	"path/filepath" | ||||
| 	"strings" | ||||
| 	"testing" | ||||
| ) | ||||
| 
 | ||||
| func TestAddKey(t *testing.T) { | ||||
| 	testName := "docker.com/notary/root" | ||||
| 	testExt := "key" | ||||
| 
 | ||||
| 	// Temporary directory where test files will be created
 | ||||
| 	tempBaseDir, err := ioutil.TempDir("", "notary-test-") | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("failed to create a temporary directory: %v", err) | ||||
| 	} | ||||
| 
 | ||||
| 	// Since we're generating this manually we need to add the extension '.'
 | ||||
| 	expectedFilePath := filepath.Join(tempBaseDir, testName+"."+testExt) | ||||
| 
 | ||||
| 	// Create our store
 | ||||
| 	store, err := NewKeyFileStore(tempBaseDir) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("failed to create new key filestore: %v", err) | ||||
| 	} | ||||
| 
 | ||||
| 	key, err := rsa.GenerateKey(rand.Reader, 1024) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("could not generate private key: %v", err) | ||||
| 	} | ||||
| 
 | ||||
| 	// Call the Add function
 | ||||
| 	err = store.Add(testName, key) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("failed to add file to store: %v", err) | ||||
| 	} | ||||
| 
 | ||||
| 	// Check to see if file exists
 | ||||
| 	b, err := ioutil.ReadFile(expectedFilePath) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("expected file not found: %v", err) | ||||
| 	} | ||||
| 
 | ||||
| 	if !strings.Contains(string(b), "-----BEGIN RSA PRIVATE KEY-----") { | ||||
| 		t.Fatalf("expected private key content in the file: %s", expectedFilePath) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func TestGet(t *testing.T) { | ||||
| 	testData := []byte(`-----BEGIN RSA PRIVATE KEY----- | ||||
| MIIEogIBAAKCAQEAyUIXjsrWRrvPa4Bzp3VJ6uOUGPay2fUpSV8XzNxZxIG/Opdr | ||||
| +k3EQi1im6WOqF3Y5AS1UjYRxNuRN+cAZeo3uS1pOTuoSupBXuchVw8s4hZJ5vXn | ||||
| TRmGb+xY7tZ1ZVgPfAZDib9sRSUsL/gC+aSyprAjG/YBdbF06qKbfOfsoCEYW1OQ | ||||
| 82JqHzQH514RFYPTnEGpvfxWaqmFQLmv0uMxV/cAYvqtrGkXuP0+a8PknlD2obw5 | ||||
| 0rHE56Su1c3Q42S7L51K38tpbgWOSRcTfDUWEj5v9wokkNQvyKBwbS996s4EJaZd | ||||
| 7r6M0h1pHnuRxcSaZLYRwgOe1VNGg2VfWzgd5QIDAQABAoIBAF9LGwpygmj1jm3R | ||||
| YXGd+ITugvYbAW5wRb9G9mb6wspnwNsGTYsz/UR0ZudZyaVw4jx8+jnV/i3e5PC6 | ||||
| QRcAgqf8l4EQ/UuThaZg/AlT1yWp9g4UyxNXja87EpTsGKQGwTYxZRM4/xPyWOzR | ||||
| mt8Hm8uPROB9aA2JG9npaoQG8KSUj25G2Qot3ukw/IOtqwN/Sx1EqF0EfCH1K4KU | ||||
| a5TrqlYDFmHbqT1zTRec/BTtVXNsg8xmF94U1HpWf3Lpg0BPYT7JiN2DPoLelRDy | ||||
| a/A+a3ZMRNISL5wbq/jyALLOOyOkIqa+KEOeW3USuePd6RhDMzMm/0ocp5FCwYfo | ||||
| k4DDeaECgYEA0eSMD1dPGo+u8UTD8i7ZsZCS5lmXLNuuAg5f5B/FGghD8ymPROIb | ||||
| dnJL5QSbUpmBsYJ+nnO8RiLrICGBe7BehOitCKi/iiZKJO6edrfNKzhf4XlU0HFl | ||||
| jAOMa975pHjeCoZ1cXJOEO9oW4SWTCyBDBSqH3/ZMgIOiIEk896lSmkCgYEA9Xf5 | ||||
| Jqv3HtQVvjugV/axAh9aI8LMjlfFr9SK7iXpY53UdcylOSWKrrDok3UnrSEykjm7 | ||||
| UL3eCU5jwtkVnEXesNn6DdYo3r43E6iAiph7IBkB5dh0yv3vhIXPgYqyTnpdz4pg | ||||
| 3yPGBHMPnJUBThg1qM7k6a2BKHWySxEgC1DTMB0CgYAGvdmF0J8Y0k6jLzs/9yNE | ||||
| 4cjmHzCM3016gW2xDRgumt9b2xTf+Ic7SbaIV5qJj6arxe49NqhwdESrFohrKaIP | ||||
| kM2l/o2QaWRuRT/Pvl2Xqsrhmh0QSOQjGCYVfOb10nAHVIRHLY22W4o1jk+piLBo | ||||
| a+1+74NRaOGAnu1J6/fRKQKBgAF180+dmlzemjqFlFCxsR/4G8s2r4zxTMXdF+6O | ||||
| 3zKuj8MbsqgCZy7e8qNeARxwpCJmoYy7dITNqJ5SOGSzrb2Trn9ClP+uVhmR2SH6 | ||||
| AlGQlIhPn3JNzI0XVsLIloMNC13ezvDE/7qrDJ677EQQtNEKWiZh1/DrsmHr+irX | ||||
| EkqpAoGAJWe8PC0XK2RE9VkbSPg9Ehr939mOLWiHGYTVWPttUcum/rTKu73/X/mj | ||||
| WxnPWGtzM1pHWypSokW90SP4/xedMxludvBvmz+CTYkNJcBGCrJumy11qJhii9xp | ||||
| EMl3eFOJXjIch/wIesRSN+2dGOsl7neercjMh1i9RvpCwHDx/E0= | ||||
| -----END RSA PRIVATE KEY----- | ||||
| `) | ||||
| 	testName := "docker.com/notary/root" | ||||
| 	testExt := "key" | ||||
| 	perms := os.FileMode(0755) | ||||
| 
 | ||||
| 	// Temporary directory where test files will be created
 | ||||
| 	tempBaseDir, err := ioutil.TempDir("", "notary-test-") | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("failed to create a temporary directory: %v", err) | ||||
| 	} | ||||
| 
 | ||||
| 	// Since we're generating this manually we need to add the extension '.'
 | ||||
| 	filePath := filepath.Join(tempBaseDir, testName+"."+testExt) | ||||
| 
 | ||||
| 	os.MkdirAll(filepath.Dir(filePath), perms) | ||||
| 	if err = ioutil.WriteFile(filePath, testData, perms); err != nil { | ||||
| 		t.Fatalf("Failed to write test file: %v", err) | ||||
| 	} | ||||
| 
 | ||||
| 	// Create our store
 | ||||
| 	store, err := NewKeyFileStore(tempBaseDir) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("failed to create new key filestore: %v", err) | ||||
| 	} | ||||
| 
 | ||||
| 	// Call the Get function
 | ||||
| 	privKey, err := store.Get(testName) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("failed to get file from store: %v", err) | ||||
| 	} | ||||
| 
 | ||||
| 	pemKey, err := KeyToPEM(privKey) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("failed to convert key to PEM: %v", err) | ||||
| 	} | ||||
| 
 | ||||
| 	if !bytes.Equal(testData, pemKey) { | ||||
| 		t.Fatalf("unexpected content in the file: %s", filePath) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func TestAddEncryptedAndGetDecrypted(t *testing.T) { | ||||
| 	testName := "docker.com/notary/root" | ||||
| 	testExt := "key" | ||||
| 
 | ||||
| 	// Temporary directory where test files will be created
 | ||||
| 	tempBaseDir, err := ioutil.TempDir("", "notary-test-") | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("failed to create a temporary directory: %v", err) | ||||
| 	} | ||||
| 
 | ||||
| 	// Since we're generating this manually we need to add the extension '.'
 | ||||
| 	expectedFilePath := filepath.Join(tempBaseDir, testName+"."+testExt) | ||||
| 
 | ||||
| 	// Create our FileStore
 | ||||
| 	store, err := NewKeyFileStore(tempBaseDir) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("failed to create new key filestore: %v", err) | ||||
| 	} | ||||
| 
 | ||||
| 	// Generate new PrivateKey
 | ||||
| 	key, err := rsa.GenerateKey(rand.Reader, 1024) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("could not generate private key: %v", err) | ||||
| 	} | ||||
| 
 | ||||
| 	// Call the Add function
 | ||||
| 	err = store.AddEncrypted(testName, key, "diogomonica") | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("failed to add file to store: %v", err) | ||||
| 	} | ||||
| 
 | ||||
| 	pemPrivKey, err := store.GetDecrypted(testName, "diogomonica") | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("could not decrypt private key: %v", err) | ||||
| 	} | ||||
| 
 | ||||
| 	pemKey, err := KeyToPEM(key) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("could not convert private key to PEM: %v", err) | ||||
| 	} | ||||
| 
 | ||||
| 	decryptedPemKey, err := KeyToPEM(pemPrivKey) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("could not convert private key to PEM: %v", err) | ||||
| 	} | ||||
| 
 | ||||
| 	if !strings.Contains(string(pemKey), string(decryptedPemKey)) { | ||||
| 		t.Fatalf("expected private key content in the file: %s", expectedFilePath) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func TestGetDecryptedWithTamperedCipherText(t *testing.T) { | ||||
| 	testName := "docker.com/notary/root" | ||||
| 	testExt := "key" | ||||
| 
 | ||||
| 	// Temporary directory where test files will be created
 | ||||
| 	tempBaseDir, err := ioutil.TempDir("", "notary-test-") | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("failed to create a temporary directory: %v", err) | ||||
| 	} | ||||
| 
 | ||||
| 	// Since we're generating this manually we need to add the extension '.'
 | ||||
| 	expectedFilePath := filepath.Join(tempBaseDir, testName+"."+testExt) | ||||
| 
 | ||||
| 	// Create our FileStore
 | ||||
| 	store, err := NewKeyFileStore(tempBaseDir) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("failed to create new key filestore: %v", err) | ||||
| 	} | ||||
| 
 | ||||
| 	// Generate a new Private Key
 | ||||
| 	key, err := rsa.GenerateKey(rand.Reader, 1024) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("could not generate private key: %v", err) | ||||
| 	} | ||||
| 
 | ||||
| 	// Call the Add function
 | ||||
| 	err = store.AddEncrypted(testName, key, "diogomonica") | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("failed to add file to store: %v", err) | ||||
| 	} | ||||
| 
 | ||||
| 	// Get file description, open file
 | ||||
| 	fp, _ := os.OpenFile(expectedFilePath, os.O_WRONLY, 0600) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("expected file not found: %v", err) | ||||
| 	} | ||||
| 
 | ||||
| 	// Tamper the file
 | ||||
| 	fp.WriteAt([]byte("a"), int64(1)) | ||||
| 
 | ||||
| 	// Try to decrypt the file
 | ||||
| 	_, err = store.GetDecrypted(testName, "diogomonica") | ||||
| 	if err == nil { | ||||
| 		t.Fatalf("expected error while decrypting the content due to invalid cipher text") | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func TestGetDecryptedWithInvalidPassphrase(t *testing.T) { | ||||
| 	testName := "docker.com/notary/root" | ||||
| 
 | ||||
| 	// Temporary directory where test files will be created
 | ||||
| 	tempBaseDir, err := ioutil.TempDir("", "notary-test-") | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("failed to create a temporary directory: %v", err) | ||||
| 	} | ||||
| 
 | ||||
| 	// Create our FileStore
 | ||||
| 	store, err := NewKeyFileStore(tempBaseDir) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("failed to create new key filestore: %v", err) | ||||
| 	} | ||||
| 
 | ||||
| 	// Generate a new random RSA Key
 | ||||
| 	key, err := rsa.GenerateKey(rand.Reader, 1024) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("could not generate private key: %v", err) | ||||
| 	} | ||||
| 
 | ||||
| 	// Call the Add function
 | ||||
| 	err = store.AddEncrypted(testName, key, "diogomonica") | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("failed to add file to stoAFre: %v", err) | ||||
| 	} | ||||
| 
 | ||||
| 	// Try to decrypt the file with an invalid passphrase
 | ||||
| 	_, err = store.GetDecrypted(testName, "diegomonica") | ||||
| 	if err == nil { | ||||
| 		t.Fatalf("expected error while decrypting the content due to invalid passphrase") | ||||
| 	} | ||||
| } | ||||
|  | @ -20,6 +20,7 @@ type X509Store interface { | |||
| 	GetVerifyOptions(dnsName string) (x509.VerifyOptions, error) | ||||
| } | ||||
| 
 | ||||
| // CertID represent the ID used to identify certificates
 | ||||
| type CertID string | ||||
| 
 | ||||
| // Validator is a convenience type to create validating function that filters
 | ||||
|  |  | |||
|  | @ -2,7 +2,6 @@ package trustmanager | |||
| 
 | ||||
| import ( | ||||
| 	"crypto/x509" | ||||
| 	"fmt" | ||||
| 	"testing" | ||||
| ) | ||||
| 
 | ||||
|  | @ -35,10 +34,6 @@ func TestVerifyLeafSuccessfully(t *testing.T) { | |||
| 	// Get our certList with Leaf Cert and Intermediate
 | ||||
| 	certList := []*x509.Certificate{leafCert, intermediateCA} | ||||
| 
 | ||||
| 	// Get the VerifyOptions from our Store
 | ||||
| 	opts, err := store.GetVerifyOptions("secure.docker.com") | ||||
| 	fmt.Println(opts) | ||||
| 
 | ||||
| 	// Try to find a valid chain for cert
 | ||||
| 	err = Verify(store, "secure.docker.com", certList) | ||||
| 	if err != nil { | ||||
|  | @ -75,10 +70,6 @@ func TestVerifyLeafSuccessfullyWithMultipleIntermediates(t *testing.T) { | |||
| 	// Get our certList with Leaf Cert and Intermediate
 | ||||
| 	certList := []*x509.Certificate{leafCert, intermediateCA, intermediateCA, rootCA} | ||||
| 
 | ||||
| 	// Get the VerifyOptions from our Store
 | ||||
| 	opts, err := store.GetVerifyOptions("secure.docker.com") | ||||
| 	fmt.Println(opts) | ||||
| 
 | ||||
| 	// Try to find a valid chain for cert
 | ||||
| 	err = Verify(store, "secure.docker.com", certList) | ||||
| 	if err != nil { | ||||
|  | @ -109,10 +100,6 @@ func TestVerifyLeafWithNoIntermediate(t *testing.T) { | |||
| 	// Get our certList with Leaf Cert and Intermediate
 | ||||
| 	certList := []*x509.Certificate{leafCert, leafCert} | ||||
| 
 | ||||
| 	// Get the VerifyOptions from our Store
 | ||||
| 	opts, err := store.GetVerifyOptions("secure.docker.com") | ||||
| 	fmt.Println(opts) | ||||
| 
 | ||||
| 	// Try to find a valid chain for cert
 | ||||
| 	err = Verify(store, "secure.docker.com", certList) | ||||
| 	if err == nil { | ||||
|  | @ -143,10 +130,6 @@ func TestVerifyLeafWithNoLeaf(t *testing.T) { | |||
| 	// Get our certList with Leaf Cert and Intermediate
 | ||||
| 	certList := []*x509.Certificate{intermediateCA, intermediateCA} | ||||
| 
 | ||||
| 	// Get the VerifyOptions from our Store
 | ||||
| 	opts, err := store.GetVerifyOptions("secure.docker.com") | ||||
| 	fmt.Println(opts) | ||||
| 
 | ||||
| 	// Try to find a valid chain for cert
 | ||||
| 	err = Verify(store, "secure.docker.com", certList) | ||||
| 	if err == nil { | ||||
|  |  | |||
|  | @ -1,9 +1,13 @@ | |||
| package trustmanager | ||||
| 
 | ||||
| import ( | ||||
| 	"crypto" | ||||
| 	"crypto/rand" | ||||
| 	"crypto/rsa" | ||||
| 	"crypto/x509" | ||||
| 	"encoding/pem" | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"io/ioutil" | ||||
| 	"net/http" | ||||
| 	"net/url" | ||||
|  | @ -20,7 +24,7 @@ func GetCertFromURL(urlStr string) (*x509.Certificate, error) { | |||
| 
 | ||||
| 	// Check if we are adding via HTTPS
 | ||||
| 	if url.Scheme != "https" { | ||||
| 		return nil, errors.New("only HTTPS URLs allowed.") | ||||
| 		return nil, errors.New("only HTTPS URLs allowed") | ||||
| 	} | ||||
| 
 | ||||
| 	// Download the certificate and write to directory
 | ||||
|  | @ -52,11 +56,41 @@ func ToPEM(cert *x509.Certificate) []byte { | |||
| 	return pemCert | ||||
| } | ||||
| 
 | ||||
| // TeyToPEM is an utility function returns a PEM encoded Key
 | ||||
| func KeyToPEM(keyBytes []byte) []byte { | ||||
| 	keyPEMBytes := pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: keyBytes}) | ||||
| // KeyToPEM is an utility function returns a PEM encoded Key
 | ||||
| func KeyToPEM(key crypto.PrivateKey) ([]byte, error) { | ||||
| 	rsaKey, ok := key.(*rsa.PrivateKey) | ||||
| 	if !ok { | ||||
| 		return nil, errors.New("only RSA keys are currently supported") | ||||
| 	} | ||||
| 
 | ||||
| 	return keyPEMBytes | ||||
| 	keyBytes := x509.MarshalPKCS1PrivateKey(rsaKey) | ||||
| 	return pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: keyBytes}), nil | ||||
| } | ||||
| 
 | ||||
| // KeyToEncryptedPEM is an utility function returns a PEM encoded Key
 | ||||
| func KeyToEncryptedPEM(key crypto.PrivateKey, passphrase string) ([]byte, error) { | ||||
| 	rsaKey, ok := key.(*rsa.PrivateKey) | ||||
| 	if !ok { | ||||
| 		return nil, errors.New("only RSA keys are currently supported") | ||||
| 	} | ||||
| 
 | ||||
| 	keyBytes := x509.MarshalPKCS1PrivateKey(rsaKey) | ||||
| 
 | ||||
| 	//TODO(diogo): if we do keystretching, where do we keep the salt + params?
 | ||||
| 	password := []byte(passphrase) | ||||
| 	cipherType := x509.PEMCipherAES256 | ||||
| 	blockType := "RSA PRIVATE KEY" | ||||
| 
 | ||||
| 	encryptedPEMBlock, err := x509.EncryptPEMBlock(rand.Reader, | ||||
| 		blockType, | ||||
| 		keyBytes, | ||||
| 		password, | ||||
| 		cipherType) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 
 | ||||
| 	return pem.EncodeToMemory(encryptedPEMBlock), nil | ||||
| } | ||||
| 
 | ||||
| // loadCertFromPEM returns the first certificate found in a bunch of bytes or error
 | ||||
|  | @ -83,6 +117,7 @@ func loadCertFromPEM(pemBytes []byte) (*x509.Certificate, error) { | |||
| 	return nil, errors.New("no certificates found in PEM data") | ||||
| } | ||||
| 
 | ||||
| // FingerprintCert returns a TUF compliant fingerprint for a X509 Certificate
 | ||||
| func FingerprintCert(cert *x509.Certificate) string { | ||||
| 	return string(fingerprintCert(cert)) | ||||
| } | ||||
|  | @ -125,3 +160,58 @@ func LoadCertFromFile(filename string) (*x509.Certificate, error) { | |||
| 
 | ||||
| 	return nil, errors.New("could not load certificate from file") | ||||
| } | ||||
| 
 | ||||
| // LoadKeyFromFile returns a PrivateKey given a filename
 | ||||
| func LoadKeyFromFile(filename string) (crypto.PrivateKey, error) { | ||||
| 	pemBytes, err := ioutil.ReadFile(filename) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 
 | ||||
| 	key, err := ParseRawPrivateKey(pemBytes) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	return key, nil | ||||
| } | ||||
| 
 | ||||
| // ParseRawPrivateKey returns a private key from a PEM encoded private key. It
 | ||||
| // only supports RSA (PKCS#1).
 | ||||
| func ParseRawPrivateKey(pemBytes []byte) (crypto.PrivateKey, error) { | ||||
| 	block, _ := pem.Decode(pemBytes) | ||||
| 	if block == nil { | ||||
| 		return nil, errors.New("no valid key found") | ||||
| 	} | ||||
| 
 | ||||
| 	switch block.Type { | ||||
| 	case "RSA PRIVATE KEY": | ||||
| 		return x509.ParsePKCS1PrivateKey(block.Bytes) | ||||
| 	default: | ||||
| 		return nil, fmt.Errorf("unsupported key type %q", block.Type) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // ParseRawEncryptedPrivateKey returns a private key from a PEM encrypted private key. It
 | ||||
| // only supports RSA (PKCS#1).
 | ||||
| func ParseRawEncryptedPrivateKey(pemBytes []byte, passphrase string) (crypto.PrivateKey, error) { | ||||
| 	block, _ := pem.Decode(pemBytes) | ||||
| 	if block == nil { | ||||
| 		return nil, errors.New("no valid private key found") | ||||
| 	} | ||||
| 
 | ||||
| 	switch block.Type { | ||||
| 	case "RSA PRIVATE KEY": | ||||
| 		if !x509.IsEncryptedPEMBlock(block) { | ||||
| 			return nil, errors.New("private key is not encrypted") | ||||
| 		} | ||||
| 
 | ||||
| 		decryptedPEMBlock, err := x509.DecryptPEMBlock(block, []byte(passphrase)) | ||||
| 		if err != nil { | ||||
| 			return nil, errors.New("could not decrypt private key") | ||||
| 		} | ||||
| 
 | ||||
| 		return x509.ParsePKCS1PrivateKey(decryptedPEMBlock) | ||||
| 	default: | ||||
| 		return nil, fmt.Errorf("unsupported key type %q", block.Type) | ||||
| 	} | ||||
| } | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue