Major refactor of keys

Signed-off-by: Diogo Monica <diogo@docker.com>
This commit is contained in:
Diogo Monica 2015-07-09 01:57:19 -07:00
parent 73ca456297
commit 4635bed2db
6 changed files with 179 additions and 234 deletions

View File

@ -6,7 +6,6 @@ import (
"crypto/rsa" "crypto/rsa"
"crypto/sha256" "crypto/sha256"
"crypto/x509" "crypto/x509"
"encoding/pem"
"errors" "errors"
"fmt" "fmt"
"path/filepath" "path/filepath"
@ -38,36 +37,15 @@ func NewRootCryptoService(rootKeyStore *trustmanager.KeyFileStore, passphrase st
// 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 string) (*data.PublicKey, error) { func (ccs *CryptoService) Create(role string) (*data.PublicKey, error) {
// Generates a new RSA key privKey, err := trustmanager.GenerateRSAKey(rand.Reader, rsaKeySize)
rsaPrivKey, err := rsa.GenerateKey(rand.Reader, rsaKeySize)
if err != nil { if err != nil {
return nil, fmt.Errorf("could not generate private key: %v", err) return nil, fmt.Errorf("failed to generate RSA key: %v", err)
} }
rsaPublicKey := rsaPrivKey.PublicKey // Store the private key into our keystore with the name being: /GUN/ID.key
ccs.keyStore.AddKey(filepath.Join(ccs.gun, privKey.ID()), privKey)
// Using x509 to Marshal the Public key into DER encoding return data.PublicKeyFromPrivate(*privKey), nil
pubBytes, err := x509.MarshalPKIXPublicKey(&rsaPublicKey)
if err != nil {
return nil, errors.New("Failed to Marshal public key.")
}
tufKey := data.NewPublicKey("RSA", pubBytes)
// Passing in the the GUN + keyID as the name for the private key and adding it
// to our KeyFileStore. Final storage will be under $BASE_PATH/GUN/keyID.key
privKeyFilename := filepath.Join(ccs.gun, tufKey.ID())
// Get a PEM encoded representation of the private key
pemRSAPrivKey, err := trustmanager.KeyToPEM(rsaPrivKey)
if err != nil {
return nil, fmt.Errorf("failed to generate the certificate for key: %v (%s)", role, err)
}
// Store the PEM-encoded private key into our keystore
ccs.keyStore.Add(privKeyFilename, pemRSAPrivKey)
return tufKey, nil
} }
// Sign returns the signatures for data with the given keyIDs // Sign returns the signatures for data with the given keyIDs
@ -81,20 +59,12 @@ func (ccs *CryptoService) Sign(keyIDs []string, payload []byte) ([]data.Signatur
// Get the PrivateKey filename // Get the PrivateKey filename
privKeyFilename := filepath.Join(ccs.gun, fingerprint) privKeyFilename := filepath.Join(ccs.gun, fingerprint)
// Read PrivateKey from file // Read PrivateKey from file
privPEMBytes, err := ccs.keyStore.Get(privKeyFilename) privKey, err := ccs.keyStore.GetKey(privKeyFilename)
if err != nil { if err != nil {
continue continue
} }
// Parse PrivateKey sig, err := sign(privKey, hash, hashed[:])
privKeyBytes, _ := pem.Decode(privPEMBytes)
privKey, err := x509.ParsePKCS1PrivateKey(privKeyBytes.Bytes)
if err != nil {
return nil, err
}
// Sign the data
sig, err := rsa.SignPKCS1v15(rand.Reader, privKey, hash, hashed[:])
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -103,7 +73,6 @@ func (ccs *CryptoService) Sign(keyIDs []string, payload []byte) ([]data.Signatur
signatures = append(signatures, data.Signature{ signatures = append(signatures, data.Signature{
KeyID: fingerprint, KeyID: fingerprint,
Method: "RSA", Method: "RSA",
//Method: "RSASSA-PKCS1-V1_5-SIGN",
Signature: sig[:], Signature: sig[:],
}) })
} }
@ -126,21 +95,13 @@ func (ccs *RootCryptoService) Sign(keyIDs []string, payload []byte) ([]data.Sign
signatures := make([]data.Signature, 0, len(keyIDs)) signatures := make([]data.Signature, 0, len(keyIDs))
for _, fingerprint := range keyIDs { for _, fingerprint := range keyIDs {
// Read PrivateKey from file // Read PrivateKey from file
privPEMBytes, err := ccs.rootKeyStore.GetDecrypted(fingerprint, ccs.passphrase) privKey, err := ccs.rootKeyStore.GetDecryptedKey(fingerprint, ccs.passphrase)
if err != nil { if err != nil {
// TODO(diogo): This error should be returned to the user in someway // TODO(diogo): This error should be returned to the user in someway
continue continue
} }
// Parse PrivateKey sig, err := sign(privKey, hash, hashed[:])
privKeyBytes, _ := pem.Decode(privPEMBytes)
privKey, err := x509.ParsePKCS1PrivateKey(privKeyBytes.Bytes)
if err != nil {
return nil, err
}
// Sign the data
sig, err := rsa.SignPKCS1v15(rand.Reader, privKey, hash, hashed[:])
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -152,5 +113,27 @@ func (ccs *RootCryptoService) Sign(keyIDs []string, payload []byte) ([]data.Sign
Signature: sig[:], Signature: sig[:],
}) })
} }
return signatures, nil return signatures, nil
} }
func sign(privKey *data.PrivateKey, hash crypto.Hash, hashed []byte) ([]byte, error) {
// TODO(diogo): Implement support for ECDSA.
if privKey.Cipher() != "RSA" {
return nil, fmt.Errorf("private key type not supported: %s", privKey.Cipher())
}
// Create an rsa.PrivateKey out of the private key bytes
rsaPrivKey, err := x509.ParsePKCS1PrivateKey(privKey.Private())
if err != nil {
return nil, err
}
// Use the RSA key to sign the data
sig, err := rsa.SignPKCS1v15(rand.Reader, rsaPrivKey, hash, hashed[:])
if err != nil {
return nil, err
}
return sig, nil
}

View File

@ -3,7 +3,6 @@ package client
import ( import (
"bytes" "bytes"
"crypto/rand" "crypto/rand"
"crypto/rsa"
"crypto/x509" "crypto/x509"
"encoding/json" "encoding/json"
"encoding/pem" "encoding/pem"
@ -58,12 +57,12 @@ type NotaryRepository struct {
baseURL string baseURL string
tufRepoPath string tufRepoPath string
transport http.RoundTripper transport http.RoundTripper
signer *signed.Signer
tufRepo *tuf.TufRepo
fileStore store.MetadataStore
privKeyStore *trustmanager.KeyFileStore
caStore trustmanager.X509Store caStore trustmanager.X509Store
certificateStore trustmanager.X509Store certificateStore trustmanager.X509Store
fileStore store.MetadataStore
signer *signed.Signer
tufRepo *tuf.TufRepo
privKeyStore *trustmanager.KeyFileStore
rootKeyStore *trustmanager.KeyFileStore rootKeyStore *trustmanager.KeyFileStore
rootSigner *UnlockedSigner rootSigner *UnlockedSigner
} }
@ -609,45 +608,29 @@ func (c *NotaryRepository) ListPrivateKeys() []string {
// GenRootKey generates a new root key protected by a given passphrase // GenRootKey generates a new root key protected by a given passphrase
func (c *NotaryRepository) GenRootKey(passphrase string) (string, error) { func (c *NotaryRepository) GenRootKey(passphrase string) (string, error) {
// TODO(diogo): Refactor TUF Key creation. We should never see crypto.privatekeys privKey, err := trustmanager.GenerateRSAKey(rand.Reader, rsaKeySize)
// Generates a new RSA key
rsaPrivKey, err := rsa.GenerateKey(rand.Reader, rsaKeySize)
if err != nil {
return "", fmt.Errorf("could not generate private key: %v", err)
}
// Encode the private key in PEM format since that is the final storage format
pemPrivKey, err := trustmanager.KeyToPEM(rsaPrivKey)
if err != nil {
return "", fmt.Errorf("failed to encode the private key: %v", err)
}
tufPrivKey, err := trustmanager.RSAToPrivateKey(rsaPrivKey)
if err != nil { if err != nil {
return "", fmt.Errorf("failed to convert private key: ", err) return "", fmt.Errorf("failed to convert private key: ", err)
} }
c.rootKeyStore.AddEncrypted(tufPrivKey.ID(), pemPrivKey, passphrase) c.rootKeyStore.AddEncryptedKey(privKey.ID(), privKey, passphrase)
return tufPrivKey.ID(), nil return privKey.ID(), nil
} }
// GetRootSigner retreives a root key that includes the ID and a signer // GetRootSigner retreives a root key that includes the ID and a signer
func (c *NotaryRepository) GetRootSigner(rootKeyID, passphrase string) (*UnlockedSigner, error) { func (c *NotaryRepository) GetRootSigner(rootKeyID, passphrase string) (*UnlockedSigner, error) {
pemPrivKey, err := c.rootKeyStore.GetDecrypted(rootKeyID, passphrase) privKey, err := c.rootKeyStore.GetDecryptedKey(rootKeyID, passphrase)
if err != nil { if err != nil {
return nil, fmt.Errorf("could not get decrypted root key: %v", err) return nil, fmt.Errorf("could not get decrypted root key: %v", err)
} }
tufPrivKey, err := trustmanager.TufParsePEMPrivateKey(pemPrivKey) // This signer will be used for all of the normal TUF operations, except for
if err != nil { // when a root key is needed.
return nil, fmt.Errorf("could not get parse root key: %v", err)
}
signer := signed.NewSigner(NewRootCryptoService(c.rootKeyStore, passphrase)) signer := signed.NewSigner(NewRootCryptoService(c.rootKeyStore, passphrase))
return &UnlockedSigner{ return &UnlockedSigner{
privKey: tufPrivKey, privKey: privKey,
signer: signer}, nil signer: signer}, nil
} }
@ -676,6 +659,7 @@ func (c *NotaryRepository) loadKeys(trustDir, rootKeysDir string) error {
return err return err
} }
// Load the keystore that will hold all of our encrypted Root Private Keys
rootKeyStore, err := trustmanager.NewKeyFileStore(rootKeysDir) rootKeyStore, err := trustmanager.NewKeyFileStore(rootKeysDir)
if err != nil { if err != nil {
return err return err
@ -693,29 +677,32 @@ func (uk *UnlockedSigner) ID() string {
return uk.PublicKey().ID() return uk.PublicKey().ID()
} }
// PublicKey Returns the public key associated with the Root Key // PublicKey Returns the public key associated with the Private Key within the Signer
func (uk *UnlockedSigner) PublicKey() *data.PublicKey { func (uk *UnlockedSigner) PublicKey() *data.PublicKey {
return data.PublicKeyFromPrivate(*uk.privKey) return data.PublicKeyFromPrivate(*uk.privKey)
} }
// GenerateCertificate // GenerateCertificate generates an X509 Certificate from a template, given a GUN
func (uk *UnlockedSigner) GenerateCertificate(gun string) (*x509.Certificate, error) { func (uk *UnlockedSigner) GenerateCertificate(gun string) (*x509.Certificate, error) {
privKey, err := x509.ParsePKCS1PrivateKey(uk.privKey.Private()) privKey, err := x509.ParsePKCS1PrivateKey(uk.privKey.Private())
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to parse root key: %v (%s)", gun, err.Error()) return nil, fmt.Errorf("failed to parse root key: %v (%s)", gun, err.Error())
} }
//TODO (diogo): We're hardcoding the Organization to be the GUN. Probably want to change it template, err := trustmanager.NewCertificate(gun)
template := trustmanager.NewCertificate(gun, gun) if err != nil {
return nil, fmt.Errorf("failed to create the certificate template for: %s (%v)", gun, err)
}
derBytes, err := x509.CreateCertificate(rand.Reader, template, template, privKey.Public(), privKey) derBytes, err := x509.CreateCertificate(rand.Reader, template, template, privKey.Public(), privKey)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to generate the certificate for: %v (%s)", gun, err.Error()) return nil, fmt.Errorf("failed to create the certificate for: %s (%v)", gun, err)
} }
// Encode the new certificate into PEM // Encode the new certificate into PEM
cert, err := x509.ParseCertificate(derBytes) cert, err := x509.ParseCertificate(derBytes)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to parse the certificate for key: %v (%s)", gun, err.Error()) return nil, fmt.Errorf("failed to parse the certificate for key: %s (%v)", gun, err)
} }
return cert, nil return cert, nil

View File

@ -24,8 +24,8 @@ type FileStore interface {
type EncryptedFileStore interface { type EncryptedFileStore interface {
FileStore FileStore
AddEncrypted(fileName string, keyBytes []byte, passphrase string) error AddEncrypted(fileName string, data []byte, passphrase string) error
GetDecrypted(fileName string, passphrase string) ([]byte, error) GetDecrypted(fileName, passphrase string) ([]byte, error)
} }
// SimpleFileStore implements FileStore // SimpleFileStore implements FileStore

View File

@ -1,5 +1,7 @@
package trustmanager package trustmanager
import "github.com/endophage/gotuf/data"
const ( const (
keyExtension = "key" keyExtension = "key"
) )
@ -20,39 +22,55 @@ func NewKeyFileStore(baseDir string) (*KeyFileStore, error) {
return &KeyFileStore{fileStore}, nil return &KeyFileStore{fileStore}, nil
} }
// AddKey stores the contents of a PEM-encoded private key as a PEM block
func (s *KeyFileStore) AddKey(name string, privKey *data.PrivateKey) error {
pemPrivKey, err := KeyToPEM(privKey)
if err != nil {
return err
}
return s.Add(name, pemPrivKey)
}
// GetKey returns the PrivateKey given a KeyID
func (s *KeyFileStore) GetKey(name string) (*data.PrivateKey, error) {
keyBytes, err := s.Get(name)
if err != nil {
return nil, err
}
// Convert PEM encoded bytes back to a PrivateKey
privKey, err := ParsePEMPrivateKey(keyBytes, "")
if err != nil {
return nil, err
}
return privKey, nil
}
// AddEncrypted stores the contents of a PEM-encoded private key as an encrypted PEM block // AddEncrypted stores the contents of a PEM-encoded private key as an encrypted PEM block
func (s *KeyFileStore) AddEncrypted(fileName string, pemKey []byte, passphrase string) error { func (s *KeyFileStore) AddEncryptedKey(name string, privKey *data.PrivateKey, passphrase string) error {
encryptedPrivKey, err := EncryptPrivateKey(privKey, passphrase)
privKey, err := ParsePEMPrivateKey(pemKey)
if err != nil { if err != nil {
return err return err
} }
encryptedKey, err := EncryptPrivateKey(privKey, passphrase) return s.Add(name, encryptedPrivKey)
if err != nil {
return err
}
return s.Add(fileName, encryptedKey)
} }
// GetDecrypted decrypts and returns the PEM Encoded private key given a flename // GetDecrypted decrypts and returns the PEM Encoded private key given a flename
// and a passphrase // and a passphrase
func (s *KeyFileStore) GetDecrypted(fileName string, passphrase string) ([]byte, error) { func (s *KeyFileStore) GetDecryptedKey(name string, passphrase string) (*data.PrivateKey, error) {
keyBytes, err := s.Get(fileName) keyBytes, err := s.Get(name)
if err != nil { if err != nil {
return nil, err return nil, err
} }
// Gets an unencrypted PrivateKey. // Gets an unencrypted PrivateKey.
privKey, err := ParsePEMEncryptedPrivateKey(keyBytes, passphrase) privKey, err := ParsePEMPrivateKey(keyBytes, passphrase)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return KeyToPEM(privKey) return privKey, nil
}
func (s *KeyFileStore) Link(src, dst string) error {
return s.FileStore.Link(src, dst)
} }

View File

@ -3,7 +3,6 @@ package trustmanager
import ( import (
"bytes" "bytes"
"crypto/rand" "crypto/rand"
"crypto/rsa"
"io/ioutil" "io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
@ -30,19 +29,13 @@ func TestAddKey(t *testing.T) {
t.Fatalf("failed to create new key filestore: %v", err) t.Fatalf("failed to create new key filestore: %v", err)
} }
key, err := rsa.GenerateKey(rand.Reader, 1024) privKey, err := GenerateRSAKey(rand.Reader, 512)
if err != nil { if err != nil {
t.Fatalf("could not generate private key: %v", err) t.Fatalf("could not generate private key: %v", err)
} }
// Get the PEM for the key // Call the AddKey function
pemKey, err := KeyToPEM(key) err = store.AddKey(testName, privKey)
if err != nil {
t.Fatalf("failed to convert private key to PEM: %v", err)
}
// Call the Add function
err = store.Add(testName, pemKey)
if err != nil { if err != nil {
t.Fatalf("failed to add file to store: %v", err) t.Fatalf("failed to add file to store: %v", err)
} }
@ -111,19 +104,23 @@ EMl3eFOJXjIch/wIesRSN+2dGOsl7neercjMh1i9RvpCwHDx/E0=
t.Fatalf("failed to create new key filestore: %v", err) t.Fatalf("failed to create new key filestore: %v", err)
} }
// Call the Get function // Call the GetKey function
pemKey, err := store.Get(testName) privKey, err := store.GetKey(testName)
if err != nil { if err != nil {
t.Fatalf("failed to get file from store: %v", err) t.Fatalf("failed to get file from store: %v", err)
} }
if !bytes.Equal(testData, pemKey) { pemPrivKey, err := KeyToPEM(privKey)
if err != nil {
t.Fatalf("failed to convert key to PEM: %v", err)
}
if !bytes.Equal(testData, pemPrivKey) {
t.Fatalf("unexpected content in the file: %s", filePath) t.Fatalf("unexpected content in the file: %s", filePath)
} }
} }
func TestAddEncryptedAndGetDecrypted(t *testing.T) { func TestAddEncryptedAndGetDecrypted(t *testing.T) {
testName := "docker.com/notary/root"
testExt := "key" testExt := "key"
// Temporary directory where test files will be created // Temporary directory where test files will be created
@ -132,9 +129,6 @@ func TestAddEncryptedAndGetDecrypted(t *testing.T) {
t.Fatalf("failed to create a temporary directory: %v", err) 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 // Create our FileStore
store, err := NewKeyFileStore(tempBaseDir) store, err := NewKeyFileStore(tempBaseDir)
if err != nil { if err != nil {
@ -142,35 +136,38 @@ func TestAddEncryptedAndGetDecrypted(t *testing.T) {
} }
// Generate new PrivateKey // Generate new PrivateKey
key, err := rsa.GenerateKey(rand.Reader, 1024) privKey, err := GenerateRSAKey(rand.Reader, 512)
if err != nil { if err != nil {
t.Fatalf("could not generate private key: %v", err) t.Fatalf("could not generate private key: %v", err)
} }
// Get PEM encodedd key // Call the AddEncryptedKey function
pemKey, err := KeyToPEM(key) err = store.AddEncryptedKey(privKey.ID(), privKey, "diogomonica")
if err != nil {
t.Fatalf("Could not encode key to PEM: %v", err)
}
// Call the Add function
err = store.AddEncrypted(testName, pemKey, "diogomonica")
if err != nil { if err != nil {
t.Fatalf("failed to add file to store: %v", err) t.Fatalf("failed to add file to store: %v", err)
} }
pemPrivKey, err := store.GetDecrypted(testName, "diogomonica") // Since we're generating this manually we need to add the extension '.'
expectedFilePath := filepath.Join(tempBaseDir, privKey.ID()+"."+testExt)
// Check to see if file exists
_, err = ioutil.ReadFile(expectedFilePath)
if err != nil {
t.Fatalf("expected file not found: %v", err)
}
// Call the GetDecryptedKey function
readPrivKey, err := store.GetDecryptedKey(privKey.ID(), "diogomonica")
if err != nil { if err != nil {
t.Fatalf("could not decrypt private key: %v", err) t.Fatalf("could not decrypt private key: %v", err)
} }
if !strings.Contains(string(pemKey), string(pemPrivKey)) { if !bytes.Equal(privKey.Private(), readPrivKey.Private()) {
t.Fatalf("expected private key content in the file: %s", expectedFilePath) t.Fatalf("written key and loaded key do not match")
} }
} }
func TestGetDecryptedWithTamperedCipherText(t *testing.T) { func TestGetDecryptedWithTamperedCipherText(t *testing.T) {
testName := "docker.com/notary/root"
testExt := "key" testExt := "key"
// Temporary directory where test files will be created // Temporary directory where test files will be created
@ -179,9 +176,6 @@ func TestGetDecryptedWithTamperedCipherText(t *testing.T) {
t.Fatalf("failed to create a temporary directory: %v", err) 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 // Create our FileStore
store, err := NewKeyFileStore(tempBaseDir) store, err := NewKeyFileStore(tempBaseDir)
if err != nil { if err != nil {
@ -189,24 +183,22 @@ func TestGetDecryptedWithTamperedCipherText(t *testing.T) {
} }
// Generate a new Private Key // Generate a new Private Key
key, err := rsa.GenerateKey(rand.Reader, 1024) privKey, err := GenerateRSAKey(rand.Reader, 512)
if err != nil { if err != nil {
t.Fatalf("could not generate private key: %v", err) t.Fatalf("could not generate private key: %v", err)
} }
// Get PEM encodedd key // Call the AddEncryptedKey function
pemKey, err := KeyToPEM(key) err = store.AddEncryptedKey(privKey.ID(), privKey, "diogomonica")
if err != nil {
t.Fatalf("Could not encode key to PEM: %v", err)
}
// Call the Add function
err = store.AddEncrypted(testName, pemKey, "diogomonica")
if err != nil { if err != nil {
t.Fatalf("failed to add file to store: %v", err) t.Fatalf("failed to add file to store: %v", err)
} }
// Since we're generating this manually we need to add the extension '.'
expectedFilePath := filepath.Join(tempBaseDir, privKey.ID()+"."+testExt)
// Get file description, open file // Get file description, open file
fp, _ := os.OpenFile(expectedFilePath, os.O_WRONLY, 0600) fp, err := os.OpenFile(expectedFilePath, os.O_WRONLY, 0600)
if err != nil { if err != nil {
t.Fatalf("expected file not found: %v", err) t.Fatalf("expected file not found: %v", err)
} }
@ -215,7 +207,7 @@ func TestGetDecryptedWithTamperedCipherText(t *testing.T) {
fp.WriteAt([]byte("a"), int64(1)) fp.WriteAt([]byte("a"), int64(1))
// Try to decrypt the file // Try to decrypt the file
_, err = store.GetDecrypted(testName, "diogomonica") _, err = store.GetDecryptedKey(privKey.ID(), "diogomonica")
if err == nil { if err == nil {
t.Fatalf("expected error while decrypting the content due to invalid cipher text") t.Fatalf("expected error while decrypting the content due to invalid cipher text")
} }
@ -237,24 +229,19 @@ func TestGetDecryptedWithInvalidPassphrase(t *testing.T) {
} }
// Generate a new random RSA Key // Generate a new random RSA Key
key, err := rsa.GenerateKey(rand.Reader, 1024) privKey, err := GenerateRSAKey(rand.Reader, 512)
if err != nil { if err != nil {
t.Fatalf("could not generate private key: %v", err) t.Fatalf("could not generate private key: %v", err)
} }
// Get PEM encodedd key // Call the AddEncryptedKey function
pemKey, err := KeyToPEM(key) err = store.AddEncryptedKey(privKey.ID(), privKey, "diogomonica")
if err != nil {
t.Fatalf("Could not encode key to PEM: %v", err)
}
// Call the Add function
err = store.AddEncrypted(testName, pemKey, "diogomonica")
if err != nil { if err != nil {
t.Fatalf("failed to add file to stoAFre: %v", err) t.Fatalf("failed to add file to stoAFre: %v", err)
} }
// Try to decrypt the file with an invalid passphrase // Try to decrypt the file with an invalid passphrase
_, err = store.GetDecrypted(testName, "diegomonica") _, err = store.GetDecryptedKey(testName, "diegomonica")
if err == nil { if err == nil {
t.Fatalf("expected error while decrypting the content due to invalid passphrase") t.Fatalf("expected error while decrypting the content due to invalid passphrase")
} }

View File

@ -1,7 +1,6 @@
package trustmanager package trustmanager
import ( import (
"crypto"
"crypto/rand" "crypto/rand"
"crypto/rsa" "crypto/rsa"
"crypto/x509" "crypto/x509"
@ -9,6 +8,7 @@ import (
"encoding/pem" "encoding/pem"
"errors" "errors"
"fmt" "fmt"
"io"
"io/ioutil" "io/ioutil"
"math/big" "math/big"
"net/http" "net/http"
@ -59,34 +59,30 @@ func CertToPEM(cert *x509.Certificate) []byte {
return pemCert return pemCert
} }
// KeyToPEM returns a PEM encoded key from a crypto.PrivateKey // KeyToPEM returns a PEM encoded key from a Private Key
func KeyToPEM(key crypto.PrivateKey) ([]byte, error) { func KeyToPEM(privKey *data.PrivateKey) ([]byte, error) {
rsaKey, ok := key.(*rsa.PrivateKey) if privKey.Cipher() != "RSA" {
if !ok {
return nil, errors.New("only RSA keys are currently supported") return nil, errors.New("only RSA keys are currently supported")
} }
keyBytes := x509.MarshalPKCS1PrivateKey(rsaKey) return pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: privKey.Private()}), nil
return pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: keyBytes}), nil
} }
// EncryptPrivateKey returns an encrypted PEM encoded key given a Private key // EncryptPrivateKey returns an encrypted PEM key given a Privatekey
// and a passphrase // and a passphrase
func EncryptPrivateKey(key crypto.PrivateKey, passphrase string) ([]byte, error) { func EncryptPrivateKey(key *data.PrivateKey, passphrase string) ([]byte, error) {
rsaKey, ok := key.(*rsa.PrivateKey) // TODO(diogo): Currently only supports RSA Private keys
if !ok { if key.Cipher() != "RSA" {
return nil, errors.New("only RSA keys are currently supported") return nil, errors.New("only RSA keys are currently supported")
} }
keyBytes := x509.MarshalPKCS1PrivateKey(rsaKey)
password := []byte(passphrase) password := []byte(passphrase)
cipherType := x509.PEMCipherAES256 cipherType := x509.PEMCipherAES256
blockType := "RSA PRIVATE KEY" blockType := "RSA PRIVATE KEY"
encryptedPEMBlock, err := x509.EncryptPEMBlock(rand.Reader, encryptedPEMBlock, err := x509.EncryptPEMBlock(rand.Reader,
blockType, blockType,
keyBytes, key.Private(),
password, password,
cipherType) cipherType)
if err != nil { if err != nil {
@ -164,61 +160,55 @@ 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 // ParsePEMPrivateKey returns a data.PrivateKey from a PEM encoded private key. It
func LoadKeyFromFile(filename string) (crypto.PrivateKey, error) { // only supports RSA (PKCS#1) and attempts to decrypt using the passphrase, if encrypted.
pemBytes, err := ioutil.ReadFile(filename) func ParsePEMPrivateKey(pemBytes []byte, passphrase string) (*data.PrivateKey, error) {
if err != nil {
return nil, err
}
key, err := ParsePEMPrivateKey(pemBytes)
if err != nil {
return nil, err
}
return key, nil
}
// ParsePEMPrivateKey returns a private key from a PEM encoded private key. It
// only supports RSA (PKCS#1).
func ParsePEMPrivateKey(pemBytes []byte) (crypto.PrivateKey, error) {
block, _ := pem.Decode(pemBytes) block, _ := pem.Decode(pemBytes)
if block == nil { if block == nil {
return nil, errors.New("no valid key found") return nil, errors.New("no valid private key found")
} }
switch block.Type { switch block.Type {
case "RSA PRIVATE KEY": case "RSA PRIVATE KEY":
return x509.ParsePKCS1PrivateKey(block.Bytes) var privKeyBytes []byte
default: var err error
return nil, fmt.Errorf("unsupported key type %q", block.Type)
}
}
// TufParsePEMPrivateKey returns a data.PrivateKey from a PEM encoded private key. It if x509.IsEncryptedPEMBlock(block) {
// only supports RSA (PKCS#1). privKeyBytes, err = x509.DecryptPEMBlock(block, []byte(passphrase))
func TufParsePEMPrivateKey(pemBytes []byte) (*data.PrivateKey, error) {
block, _ := pem.Decode(pemBytes)
if block == nil {
return nil, errors.New("no valid key found")
}
switch block.Type {
case "RSA PRIVATE KEY":
rsaPrivKey, err := x509.ParsePKCS1PrivateKey(block.Bytes)
if err != nil { if err != nil {
return nil, fmt.Errorf("could not parse PEM: %v", err) return nil, errors.New("could not decrypt private key")
}
} else {
privKeyBytes = block.Bytes
}
rsaPrivKey, err := x509.ParsePKCS1PrivateKey(privKeyBytes)
if err != nil {
return nil, fmt.Errorf("could not parse DER encoded key: %v", err)
} }
tufRSAPrivateKey, err := RSAToPrivateKey(rsaPrivKey) tufRSAPrivateKey, err := RSAToPrivateKey(rsaPrivKey)
if err != nil { if err != nil {
return nil, fmt.Errorf("could not convert crypto.PrivateKey to PrivateKey: %v", err) return nil, fmt.Errorf("could not convert rsa.PrivateKey to data.PrivateKey: %v", err)
} }
return tufRSAPrivateKey, nil return tufRSAPrivateKey, nil
default: default:
return nil, fmt.Errorf("unsupported key type %q", block.Type) return nil, fmt.Errorf("unsupported key type %q", block.Type)
} }
} }
// GenerateRSAKey generates an RSA Private key and returns a TUF PrivateKey
func GenerateRSAKey(random io.Reader, bits int) (*data.PrivateKey, error) {
rsaPrivKey, err := rsa.GenerateKey(random, bits)
if err != nil {
return nil, fmt.Errorf("could not generate private key: %v", err)
}
return RSAToPrivateKey(rsaPrivKey)
}
// RSAToPrivateKey converts an rsa.Private key to a TUF data.PrivateKey type
func RSAToPrivateKey(rsaPrivKey *rsa.PrivateKey) (*data.PrivateKey, error) { func RSAToPrivateKey(rsaPrivKey *rsa.PrivateKey) (*data.PrivateKey, error) {
// Get a DER-encoded representation of the PublicKey // Get a DER-encoded representation of the PublicKey
rsaPubBytes, err := x509.MarshalPKIXPublicKey(&rsaPrivKey.PublicKey) rsaPubBytes, err := x509.MarshalPKIXPublicKey(&rsaPrivKey.PublicKey)
@ -232,43 +222,23 @@ func RSAToPrivateKey(rsaPrivKey *rsa.PrivateKey) (*data.PrivateKey, error) {
return data.NewPrivateKey("RSA", rsaPubBytes, rsaPrivBytes), nil return data.NewPrivateKey("RSA", rsaPubBytes, rsaPrivBytes), nil
} }
// ParsePEMEncryptedPrivateKey returns a private key from a PEM encrypted private key. It // NewCertificate returns an X509 Certificate following a template, given a GUN.
// only supports RSA (PKCS#1). func NewCertificate(gun string) (*x509.Certificate, error) {
func ParsePEMEncryptedPrivateKey(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)
}
}
func NewCertificate(gun, organization string) *x509.Certificate {
notBefore := time.Now() notBefore := time.Now()
notAfter := notBefore.Add(time.Hour * 24 * 365 * 2) notAfter := notBefore.Add(time.Hour * 24 * 365 * 2)
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128) serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
// TODO(diogo): Don't silently ignore this error
serialNumber, _ := rand.Int(rand.Reader, serialNumberLimit)
serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
if err != nil {
return nil, fmt.Errorf("failed to generate new certificate: %v", err)
}
// TODO(diogo): Currently hard coding organization to be the gun. Revisit.
return &x509.Certificate{ return &x509.Certificate{
SerialNumber: serialNumber, SerialNumber: serialNumber,
Subject: pkix.Name{ Subject: pkix.Name{
Organization: []string{organization}, Organization: []string{gun},
CommonName: gun, CommonName: gun,
}, },
NotBefore: notBefore, NotBefore: notBefore,
@ -277,5 +247,5 @@ func NewCertificate(gun, organization string) *x509.Certificate {
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageCodeSigning}, ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageCodeSigning},
BasicConstraintsValid: true, BasicConstraintsValid: true,
} }, nil
} }