From 4635bed2db8f22740aa8ca4653feab62ca23a56d Mon Sep 17 00:00:00 2001 From: Diogo Monica Date: Thu, 9 Jul 2015 01:57:19 -0700 Subject: [PATCH] Major refactor of keys Signed-off-by: Diogo Monica --- client/cli_crypto_service.go | 83 ++++++++----------- client/client.go | 55 +++++-------- trustmanager/filestore.go | 4 +- trustmanager/keyfilestore.go | 52 ++++++++---- trustmanager/keyfilestore_test.go | 91 +++++++++------------ trustmanager/x509utils.go | 128 ++++++++++++------------------ 6 files changed, 179 insertions(+), 234 deletions(-) diff --git a/client/cli_crypto_service.go b/client/cli_crypto_service.go index ea47d59395..62273998cb 100644 --- a/client/cli_crypto_service.go +++ b/client/cli_crypto_service.go @@ -6,7 +6,6 @@ import ( "crypto/rsa" "crypto/sha256" "crypto/x509" - "encoding/pem" "errors" "fmt" "path/filepath" @@ -38,36 +37,15 @@ func NewRootCryptoService(rootKeyStore *trustmanager.KeyFileStore, passphrase st // Create is used to generate keys for targets, snapshots and timestamps func (ccs *CryptoService) Create(role string) (*data.PublicKey, error) { - // Generates a new RSA key - rsaPrivKey, err := rsa.GenerateKey(rand.Reader, rsaKeySize) + privKey, err := trustmanager.GenerateRSAKey(rand.Reader, rsaKeySize) 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 - 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 + return data.PublicKeyFromPrivate(*privKey), nil } // Sign returns the signatures for data with the given keyIDs @@ -81,29 +59,20 @@ func (ccs *CryptoService) Sign(keyIDs []string, payload []byte) ([]data.Signatur // Get the PrivateKey filename privKeyFilename := filepath.Join(ccs.gun, fingerprint) // Read PrivateKey from file - privPEMBytes, err := ccs.keyStore.Get(privKeyFilename) + privKey, err := ccs.keyStore.GetKey(privKeyFilename) if err != nil { continue } - // Parse PrivateKey - 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[:]) + sig, err := sign(privKey, hash, hashed[:]) if err != nil { return nil, err } // Append signatures to result array signatures = append(signatures, data.Signature{ - KeyID: fingerprint, - Method: "RSA", - //Method: "RSASSA-PKCS1-V1_5-SIGN", + KeyID: fingerprint, + Method: "RSA", Signature: sig[:], }) } @@ -126,21 +95,13 @@ func (ccs *RootCryptoService) Sign(keyIDs []string, payload []byte) ([]data.Sign signatures := make([]data.Signature, 0, len(keyIDs)) for _, fingerprint := range keyIDs { // Read PrivateKey from file - privPEMBytes, err := ccs.rootKeyStore.GetDecrypted(fingerprint, ccs.passphrase) + privKey, err := ccs.rootKeyStore.GetDecryptedKey(fingerprint, ccs.passphrase) if err != nil { // TODO(diogo): This error should be returned to the user in someway continue } - // Parse PrivateKey - 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[:]) + sig, err := sign(privKey, hash, hashed[:]) if err != nil { return nil, err } @@ -152,5 +113,27 @@ func (ccs *RootCryptoService) Sign(keyIDs []string, payload []byte) ([]data.Sign Signature: sig[:], }) } + 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 +} diff --git a/client/client.go b/client/client.go index 67cea167c8..c66047cf78 100644 --- a/client/client.go +++ b/client/client.go @@ -3,7 +3,6 @@ package client import ( "bytes" "crypto/rand" - "crypto/rsa" "crypto/x509" "encoding/json" "encoding/pem" @@ -58,12 +57,12 @@ type NotaryRepository struct { baseURL string tufRepoPath string transport http.RoundTripper - signer *signed.Signer - tufRepo *tuf.TufRepo - fileStore store.MetadataStore - privKeyStore *trustmanager.KeyFileStore caStore trustmanager.X509Store certificateStore trustmanager.X509Store + fileStore store.MetadataStore + signer *signed.Signer + tufRepo *tuf.TufRepo + privKeyStore *trustmanager.KeyFileStore rootKeyStore *trustmanager.KeyFileStore rootSigner *UnlockedSigner } @@ -609,45 +608,29 @@ func (c *NotaryRepository) ListPrivateKeys() []string { // GenRootKey generates a new root key protected by a given passphrase func (c *NotaryRepository) GenRootKey(passphrase string) (string, error) { - // TODO(diogo): Refactor TUF Key creation. We should never see crypto.privatekeys - // 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) + privKey, err := trustmanager.GenerateRSAKey(rand.Reader, rsaKeySize) if err != nil { 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 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 { return nil, fmt.Errorf("could not get decrypted root key: %v", err) } - tufPrivKey, err := trustmanager.TufParsePEMPrivateKey(pemPrivKey) - if err != nil { - return nil, fmt.Errorf("could not get parse root key: %v", err) - } - + // This signer will be used for all of the normal TUF operations, except for + // when a root key is needed. signer := signed.NewSigner(NewRootCryptoService(c.rootKeyStore, passphrase)) return &UnlockedSigner{ - privKey: tufPrivKey, + privKey: privKey, signer: signer}, nil } @@ -676,6 +659,7 @@ func (c *NotaryRepository) loadKeys(trustDir, rootKeysDir string) error { return err } + // Load the keystore that will hold all of our encrypted Root Private Keys rootKeyStore, err := trustmanager.NewKeyFileStore(rootKeysDir) if err != nil { return err @@ -693,29 +677,32 @@ func (uk *UnlockedSigner) ID() string { 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 { 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) { privKey, err := x509.ParsePKCS1PrivateKey(uk.privKey.Private()) if err != nil { 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 := trustmanager.NewCertificate(gun, gun) + template, err := trustmanager.NewCertificate(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) 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 cert, err := x509.ParseCertificate(derBytes) 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 diff --git a/trustmanager/filestore.go b/trustmanager/filestore.go index 54d41bb50e..e55dbdeba0 100644 --- a/trustmanager/filestore.go +++ b/trustmanager/filestore.go @@ -24,8 +24,8 @@ type FileStore interface { type EncryptedFileStore interface { FileStore - AddEncrypted(fileName string, keyBytes []byte, passphrase string) error - GetDecrypted(fileName string, passphrase string) ([]byte, error) + AddEncrypted(fileName string, data []byte, passphrase string) error + GetDecrypted(fileName, passphrase string) ([]byte, error) } // SimpleFileStore implements FileStore diff --git a/trustmanager/keyfilestore.go b/trustmanager/keyfilestore.go index 9c25581c7e..beaa7fbef9 100644 --- a/trustmanager/keyfilestore.go +++ b/trustmanager/keyfilestore.go @@ -1,5 +1,7 @@ package trustmanager +import "github.com/endophage/gotuf/data" + const ( keyExtension = "key" ) @@ -20,39 +22,55 @@ func NewKeyFileStore(baseDir string) (*KeyFileStore, error) { 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 -func (s *KeyFileStore) AddEncrypted(fileName string, pemKey []byte, passphrase string) error { - - privKey, err := ParsePEMPrivateKey(pemKey) +func (s *KeyFileStore) AddEncryptedKey(name string, privKey *data.PrivateKey, passphrase string) error { + encryptedPrivKey, err := EncryptPrivateKey(privKey, passphrase) if err != nil { return err } - encryptedKey, err := EncryptPrivateKey(privKey, passphrase) - if err != nil { - return err - } - - return s.Add(fileName, encryptedKey) + return s.Add(name, encryptedPrivKey) } // GetDecrypted decrypts and returns the PEM Encoded private key given a flename // and a passphrase -func (s *KeyFileStore) GetDecrypted(fileName string, passphrase string) ([]byte, error) { - keyBytes, err := s.Get(fileName) +func (s *KeyFileStore) GetDecryptedKey(name string, passphrase string) (*data.PrivateKey, error) { + keyBytes, err := s.Get(name) if err != nil { return nil, err } // Gets an unencrypted PrivateKey. - privKey, err := ParsePEMEncryptedPrivateKey(keyBytes, passphrase) + privKey, err := ParsePEMPrivateKey(keyBytes, passphrase) if err != nil { return nil, err } - return KeyToPEM(privKey) -} - -func (s *KeyFileStore) Link(src, dst string) error { - return s.FileStore.Link(src, dst) + return privKey, nil } diff --git a/trustmanager/keyfilestore_test.go b/trustmanager/keyfilestore_test.go index f76283d455..3cbff4ef90 100644 --- a/trustmanager/keyfilestore_test.go +++ b/trustmanager/keyfilestore_test.go @@ -3,7 +3,6 @@ package trustmanager import ( "bytes" "crypto/rand" - "crypto/rsa" "io/ioutil" "os" "path/filepath" @@ -30,19 +29,13 @@ func TestAddKey(t *testing.T) { 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 { t.Fatalf("could not generate private key: %v", err) } - // Get the PEM for the key - pemKey, err := KeyToPEM(key) - if err != nil { - t.Fatalf("failed to convert private key to PEM: %v", err) - } - - // Call the Add function - err = store.Add(testName, pemKey) + // Call the AddKey function + err = store.AddKey(testName, privKey) if err != nil { 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) } - // Call the Get function - pemKey, err := store.Get(testName) + // Call the GetKey function + privKey, err := store.GetKey(testName) if err != nil { 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) } } func TestAddEncryptedAndGetDecrypted(t *testing.T) { - testName := "docker.com/notary/root" testExt := "key" // 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) } - // 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 { @@ -142,35 +136,38 @@ func TestAddEncryptedAndGetDecrypted(t *testing.T) { } // Generate new PrivateKey - key, err := rsa.GenerateKey(rand.Reader, 1024) + privKey, err := GenerateRSAKey(rand.Reader, 512) if err != nil { t.Fatalf("could not generate private key: %v", err) } - // Get PEM encodedd key - pemKey, err := KeyToPEM(key) - if err != nil { - t.Fatalf("Could not encode key to PEM: %v", err) - } - - // Call the Add function - err = store.AddEncrypted(testName, pemKey, "diogomonica") + // Call the AddEncryptedKey function + err = store.AddEncryptedKey(privKey.ID(), privKey, "diogomonica") if err != nil { 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 { t.Fatalf("could not decrypt private key: %v", err) } - if !strings.Contains(string(pemKey), string(pemPrivKey)) { - t.Fatalf("expected private key content in the file: %s", expectedFilePath) + if !bytes.Equal(privKey.Private(), readPrivKey.Private()) { + t.Fatalf("written key and loaded key do not match") } } func TestGetDecryptedWithTamperedCipherText(t *testing.T) { - testName := "docker.com/notary/root" testExt := "key" // 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) } - // 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 { @@ -189,24 +183,22 @@ func TestGetDecryptedWithTamperedCipherText(t *testing.T) { } // Generate a new Private Key - key, err := rsa.GenerateKey(rand.Reader, 1024) + privKey, err := GenerateRSAKey(rand.Reader, 512) if err != nil { t.Fatalf("could not generate private key: %v", err) } - // Get PEM encodedd key - pemKey, err := KeyToPEM(key) - if err != nil { - t.Fatalf("Could not encode key to PEM: %v", err) - } - // Call the Add function - err = store.AddEncrypted(testName, pemKey, "diogomonica") + // Call the AddEncryptedKey function + err = store.AddEncryptedKey(privKey.ID(), privKey, "diogomonica") if err != nil { 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 - fp, _ := os.OpenFile(expectedFilePath, os.O_WRONLY, 0600) + fp, err := os.OpenFile(expectedFilePath, os.O_WRONLY, 0600) if err != nil { t.Fatalf("expected file not found: %v", err) } @@ -215,7 +207,7 @@ func TestGetDecryptedWithTamperedCipherText(t *testing.T) { fp.WriteAt([]byte("a"), int64(1)) // Try to decrypt the file - _, err = store.GetDecrypted(testName, "diogomonica") + _, err = store.GetDecryptedKey(privKey.ID(), "diogomonica") if err == nil { 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 - key, err := rsa.GenerateKey(rand.Reader, 1024) + privKey, err := GenerateRSAKey(rand.Reader, 512) if err != nil { t.Fatalf("could not generate private key: %v", err) } - // Get PEM encodedd key - pemKey, err := KeyToPEM(key) - if err != nil { - t.Fatalf("Could not encode key to PEM: %v", err) - } - // Call the Add function - err = store.AddEncrypted(testName, pemKey, "diogomonica") + // Call the AddEncryptedKey function + err = store.AddEncryptedKey(privKey.ID(), privKey, "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") + _, err = store.GetDecryptedKey(testName, "diegomonica") if err == nil { t.Fatalf("expected error while decrypting the content due to invalid passphrase") } diff --git a/trustmanager/x509utils.go b/trustmanager/x509utils.go index aeb8f8b009..1994d2de50 100644 --- a/trustmanager/x509utils.go +++ b/trustmanager/x509utils.go @@ -1,7 +1,6 @@ package trustmanager import ( - "crypto" "crypto/rand" "crypto/rsa" "crypto/x509" @@ -9,6 +8,7 @@ import ( "encoding/pem" "errors" "fmt" + "io" "io/ioutil" "math/big" "net/http" @@ -59,34 +59,30 @@ func CertToPEM(cert *x509.Certificate) []byte { return pemCert } -// KeyToPEM returns a PEM encoded key from a crypto.PrivateKey -func KeyToPEM(key crypto.PrivateKey) ([]byte, error) { - rsaKey, ok := key.(*rsa.PrivateKey) - if !ok { +// KeyToPEM returns a PEM encoded key from a Private Key +func KeyToPEM(privKey *data.PrivateKey) ([]byte, error) { + if privKey.Cipher() != "RSA" { return nil, errors.New("only RSA keys are currently supported") } - keyBytes := x509.MarshalPKCS1PrivateKey(rsaKey) - return pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: keyBytes}), nil + return pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: privKey.Private()}), nil } -// EncryptPrivateKey returns an encrypted PEM encoded key given a Private key +// EncryptPrivateKey returns an encrypted PEM key given a Privatekey // and a passphrase -func EncryptPrivateKey(key crypto.PrivateKey, passphrase string) ([]byte, error) { - rsaKey, ok := key.(*rsa.PrivateKey) - if !ok { +func EncryptPrivateKey(key *data.PrivateKey, passphrase string) ([]byte, error) { + // TODO(diogo): Currently only supports RSA Private keys + if key.Cipher() != "RSA" { return nil, errors.New("only RSA keys are currently supported") } - keyBytes := x509.MarshalPKCS1PrivateKey(rsaKey) - password := []byte(passphrase) cipherType := x509.PEMCipherAES256 blockType := "RSA PRIVATE KEY" encryptedPEMBlock, err := x509.EncryptPEMBlock(rand.Reader, blockType, - keyBytes, + key.Private(), password, cipherType) if err != nil { @@ -164,61 +160,55 @@ 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 := 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) { +// ParsePEMPrivateKey returns a data.PrivateKey from a PEM encoded private key. It +// only supports RSA (PKCS#1) and attempts to decrypt using the passphrase, if encrypted. +func ParsePEMPrivateKey(pemBytes []byte, passphrase string) (*data.PrivateKey, error) { block, _ := pem.Decode(pemBytes) if block == nil { - return nil, errors.New("no valid key found") + return nil, errors.New("no valid private 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) - } -} + var privKeyBytes []byte + var err error -// TufParsePEMPrivateKey returns a data.PrivateKey from a PEM encoded private key. It -// only supports RSA (PKCS#1). -func TufParsePEMPrivateKey(pemBytes []byte) (*data.PrivateKey, error) { - block, _ := pem.Decode(pemBytes) - if block == nil { - return nil, errors.New("no valid key found") - } + if x509.IsEncryptedPEMBlock(block) { + privKeyBytes, err = x509.DecryptPEMBlock(block, []byte(passphrase)) + if err != nil { + return nil, errors.New("could not decrypt private key") + } + } else { + privKeyBytes = block.Bytes + } - switch block.Type { - case "RSA PRIVATE KEY": - rsaPrivKey, err := x509.ParsePKCS1PrivateKey(block.Bytes) + rsaPrivKey, err := x509.ParsePKCS1PrivateKey(privKeyBytes) if err != nil { - return nil, fmt.Errorf("could not parse PEM: %v", err) + return nil, fmt.Errorf("could not parse DER encoded key: %v", err) } tufRSAPrivateKey, err := RSAToPrivateKey(rsaPrivKey) 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 default: 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) { // Get a DER-encoded representation of the 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 } -// ParsePEMEncryptedPrivateKey returns a private key from a PEM encrypted private key. It -// only supports RSA (PKCS#1). -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 { +// NewCertificate returns an X509 Certificate following a template, given a GUN. +func NewCertificate(gun string) (*x509.Certificate, error) { notBefore := time.Now() notAfter := notBefore.Add(time.Hour * 24 * 365 * 2) 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{ SerialNumber: serialNumber, Subject: pkix.Name{ - Organization: []string{organization}, + Organization: []string{gun}, CommonName: gun, }, NotBefore: notBefore, @@ -277,5 +247,5 @@ func NewCertificate(gun, organization string) *x509.Certificate { KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageCodeSigning}, BasicConstraintsValid: true, - } + }, nil }