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"
|
"github.com/spf13/viper"
|
||||||
)
|
)
|
||||||
|
|
||||||
type cliCryptoService struct {
|
type CliCryptoService struct {
|
||||||
privateKeys map[string]*data.PrivateKey
|
privateKeys map[string]*data.PrivateKey
|
||||||
gun string
|
gun string
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewCryptoService(gun string) *cliCryptoService {
|
// NewCryptoService returns an instance ofS cliCryptoService
|
||||||
return &cliCryptoService{privateKeys: make(map[string]*data.PrivateKey), gun: gun}
|
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
|
// 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)
|
_, cert, err := generateKeyAndCert(ccs.gun)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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
|
// 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
|
// Create hasher and hash data
|
||||||
hash := crypto.SHA256
|
hash := crypto.SHA256
|
||||||
hashed := sha256.Sum256(payload)
|
hashed := sha256.Sum256(payload)
|
||||||
|
|
@ -83,7 +84,7 @@ func (ccs *cliCryptoService) Sign(keyIDs []string, payload []byte) ([]data.Signa
|
||||||
return signatures, nil
|
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) {
|
func generateKeyAndCert(gun string) (crypto.PrivateKey, *x509.Certificate, error) {
|
||||||
// Generates a new RSA key
|
// Generates a new RSA key
|
||||||
key, err := rsa.GenerateKey(rand.Reader, 2048)
|
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)
|
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
|
// Creates a new Certificate template. We need the certificate to calculate the
|
||||||
// TUF-compliant keyID
|
// TUF-compliant keyID
|
||||||
//TODO (diogo): We're hardcoding the Organization to be the GUN. Probably want to
|
//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 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.
|
// the filename will be the TUF-compliant ID. The Store takes care of extensions.
|
||||||
privKeyFilename := filepath.Join(gun, fingerprint)
|
privKeyFilename := filepath.Join(gun, fingerprint)
|
||||||
privKeyStore.Add(privKeyFilename, trustmanager.KeyToPEM(keyBytes))
|
pemKey, err := trustmanager.KeyToPEM(key)
|
||||||
return key, cert, nil
|
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
|
// Remove all the keys under the Global Unique Name
|
||||||
err = privKeyStore.RemoveDir(gunOrID)
|
err = privKeyStore.RemoveAll(gunOrID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fatalf("failed to remove all Private keys under Global Unique Name: %s", gunOrID)
|
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 caStore trustmanager.X509Store
|
||||||
var certificateStore trustmanager.X509Store
|
var certificateStore trustmanager.X509Store
|
||||||
var privKeyStore trustmanager.FileStore
|
var privKeyStore *trustmanager.KeyFileStore
|
||||||
|
|
||||||
var rawOutput bool
|
var rawOutput bool
|
||||||
|
|
||||||
|
|
@ -91,7 +91,7 @@ func init() {
|
||||||
fatalf("could not create X509FileStore: %v", err)
|
fatalf("could not create X509FileStore: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
privKeyStore, err = trustmanager.NewPrivateFileStore(finalPrivDir, "key")
|
privKeyStore, err = trustmanager.NewKeyFileStore(finalPrivDir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fatalf("could not create FileStore: %v", err)
|
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)
|
GetVerifyOptions(dnsName string) (x509.VerifyOptions, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CertID represent the ID used to identify certificates
|
||||||
type CertID string
|
type CertID string
|
||||||
|
|
||||||
// Validator is a convenience type to create validating function that filters
|
// Validator is a convenience type to create validating function that filters
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@ package trustmanager
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"fmt"
|
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -35,10 +34,6 @@ func TestVerifyLeafSuccessfully(t *testing.T) {
|
||||||
// Get our certList with Leaf Cert and Intermediate
|
// Get our certList with Leaf Cert and Intermediate
|
||||||
certList := []*x509.Certificate{leafCert, intermediateCA}
|
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
|
// Try to find a valid chain for cert
|
||||||
err = Verify(store, "secure.docker.com", certList)
|
err = Verify(store, "secure.docker.com", certList)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -75,10 +70,6 @@ func TestVerifyLeafSuccessfullyWithMultipleIntermediates(t *testing.T) {
|
||||||
// Get our certList with Leaf Cert and Intermediate
|
// Get our certList with Leaf Cert and Intermediate
|
||||||
certList := []*x509.Certificate{leafCert, intermediateCA, intermediateCA, rootCA}
|
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
|
// Try to find a valid chain for cert
|
||||||
err = Verify(store, "secure.docker.com", certList)
|
err = Verify(store, "secure.docker.com", certList)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -109,10 +100,6 @@ func TestVerifyLeafWithNoIntermediate(t *testing.T) {
|
||||||
// Get our certList with Leaf Cert and Intermediate
|
// Get our certList with Leaf Cert and Intermediate
|
||||||
certList := []*x509.Certificate{leafCert, leafCert}
|
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
|
// Try to find a valid chain for cert
|
||||||
err = Verify(store, "secure.docker.com", certList)
|
err = Verify(store, "secure.docker.com", certList)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
|
@ -143,10 +130,6 @@ func TestVerifyLeafWithNoLeaf(t *testing.T) {
|
||||||
// Get our certList with Leaf Cert and Intermediate
|
// Get our certList with Leaf Cert and Intermediate
|
||||||
certList := []*x509.Certificate{intermediateCA, intermediateCA}
|
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
|
// Try to find a valid chain for cert
|
||||||
err = Verify(store, "secure.docker.com", certList)
|
err = Verify(store, "secure.docker.com", certList)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,13 @@
|
||||||
package trustmanager
|
package trustmanager
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto"
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/rsa"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"encoding/pem"
|
"encoding/pem"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
|
@ -20,7 +24,7 @@ func GetCertFromURL(urlStr string) (*x509.Certificate, error) {
|
||||||
|
|
||||||
// Check if we are adding via HTTPS
|
// Check if we are adding via HTTPS
|
||||||
if url.Scheme != "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
|
// Download the certificate and write to directory
|
||||||
|
|
@ -52,11 +56,41 @@ func ToPEM(cert *x509.Certificate) []byte {
|
||||||
return pemCert
|
return pemCert
|
||||||
}
|
}
|
||||||
|
|
||||||
// TeyToPEM is an utility function returns a PEM encoded Key
|
// KeyToPEM is an utility function returns a PEM encoded Key
|
||||||
func KeyToPEM(keyBytes []byte) []byte {
|
func KeyToPEM(key crypto.PrivateKey) ([]byte, error) {
|
||||||
keyPEMBytes := pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: keyBytes})
|
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
|
// 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")
|
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 {
|
func FingerprintCert(cert *x509.Certificate) string {
|
||||||
return string(fingerprintCert(cert))
|
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")
|
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