Move CryptoService and UnlockedCryptoService into a cryptoservice package

Move GenRootKey and GetRootCryptoService to KeyStoreManager, now that
they don't depend on client-specific types.

Signed-off-by: Aaron Lehmann <aaron.lehmann@docker.com>
This commit is contained in:
Aaron Lehmann 2015-07-14 14:02:00 -07:00
parent 6068f30145
commit a16581ecc7
6 changed files with 150 additions and 129 deletions

View File

@ -2,11 +2,6 @@ package client
import ( import (
"bytes" "bytes"
"crypto"
"crypto/ecdsa"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
@ -14,10 +9,10 @@ import (
"net/http" "net/http"
"os" "os"
"path/filepath" "path/filepath"
"strings"
"github.com/Sirupsen/logrus" "github.com/Sirupsen/logrus"
"github.com/docker/notary/client/changelist" "github.com/docker/notary/client/changelist"
"github.com/docker/notary/cryptoservice"
"github.com/docker/notary/keystoremanager" "github.com/docker/notary/keystoremanager"
"github.com/docker/notary/trustmanager" "github.com/docker/notary/trustmanager"
"github.com/endophage/gotuf" "github.com/endophage/gotuf"
@ -40,25 +35,14 @@ func (err *ErrRepoNotInitialized) Error() string {
return "Repository has not been initialized" return "Repository has not been initialized"
} }
// Default paths should end with a '/' so directory creation works correctly
const ( const (
tufDir = "tuf" tufDir = "tuf"
rsaKeySize = 2048 // Used for snapshots and targets keys
rsaRootKeySize = 4096 // Used for new root keys
) )
// ErrRepositoryNotExist gets returned when trying to make an action over a repository // ErrRepositoryNotExist gets returned when trying to make an action over a repository
/// that doesn't exist. /// that doesn't exist.
var ErrRepositoryNotExist = errors.New("repository does not exist") var ErrRepositoryNotExist = errors.New("repository does not exist")
// UnlockedCryptoService encapsulates a private key and a cryptoservice that
// uses that private key, providing convinience methods for generation of
// certificates.
type UnlockedCryptoService struct {
privKey *data.PrivateKey
cryptoService signed.CryptoService
}
// NotaryRepository stores all the information needed to operate on a notary // NotaryRepository stores all the information needed to operate on a notary
// repository. // repository.
type NotaryRepository struct { type NotaryRepository struct {
@ -81,7 +65,7 @@ type Target struct {
Length int64 Length int64
} }
// NewTarget is a helper method that returns a Target // NewTarget is a helper method that returns a Target
func NewTarget(targetName string, targetPath string) (*Target, error) { func NewTarget(targetName string, targetPath string) (*Target, error) {
b, err := ioutil.ReadFile(targetPath) b, err := ioutil.ReadFile(targetPath)
if err != nil { if err != nil {
@ -105,7 +89,7 @@ func NewNotaryRepository(baseDir, gun, baseURL string, rt http.RoundTripper) (*N
return nil, err return nil, err
} }
cryptoService := NewCryptoService(gun, keyStoreManager.NonRootKeyStore(), "") cryptoService := cryptoservice.NewCryptoService(gun, keyStoreManager.NonRootKeyStore(), "")
nRepo := &NotaryRepository{ nRepo := &NotaryRepository{
gun: gun, gun: gun,
@ -122,7 +106,7 @@ func NewNotaryRepository(baseDir, gun, baseURL string, rt http.RoundTripper) (*N
// Initialize creates a new repository by using rootKey as the root Key for the // Initialize creates a new repository by using rootKey as the root Key for the
// TUF repository. // TUF repository.
func (r *NotaryRepository) Initialize(uCryptoService *UnlockedCryptoService) error { func (r *NotaryRepository) Initialize(uCryptoService *cryptoservice.UnlockedCryptoService) error {
rootCert, err := uCryptoService.GenerateCertificate(r.gun) rootCert, err := uCryptoService.GenerateCertificate(r.gun)
if err != nil { if err != nil {
return err return err
@ -135,7 +119,7 @@ func (r *NotaryRepository) Initialize(uCryptoService *UnlockedCryptoService) err
// as ECDSAx509 to allow the gotuf verifiers to correctly decode the // as ECDSAx509 to allow the gotuf verifiers to correctly decode the
// key on verification of signatures. // key on verification of signatures.
var algorithmType data.KeyAlgorithm var algorithmType data.KeyAlgorithm
algorithm := uCryptoService.privKey.Algorithm() algorithm := uCryptoService.PrivKey.Algorithm()
switch algorithm { switch algorithm {
case data.RSAKey: case data.RSAKey:
algorithmType = data.RSAx509Key algorithmType = data.RSAx509Key
@ -238,7 +222,7 @@ func (r *NotaryRepository) Initialize(uCryptoService *UnlockedCryptoService) err
return err return err
} }
if err := r.saveMetadata(uCryptoService.cryptoService); err != nil { if err := r.saveMetadata(uCryptoService.CryptoService); err != nil {
return err return err
} }
@ -372,11 +356,11 @@ func (r *NotaryRepository) Publish(getPass passwordRetriever) error {
return err return err
} }
rootKeyID := r.tufRepo.Root.Signed.Roles["root"].KeyIDs[0] rootKeyID := r.tufRepo.Root.Signed.Roles["root"].KeyIDs[0]
rootCryptoService, err := r.GetRootCryptoService(rootKeyID, passphrase) rootCryptoService, err := r.KeyStoreManager.GetRootCryptoService(rootKeyID, passphrase)
if err != nil { if err != nil {
return err return err
} }
root, err = r.tufRepo.SignRoot(data.DefaultExpires("root"), rootCryptoService.cryptoService) root, err = r.tufRepo.SignRoot(data.DefaultExpires("root"), rootCryptoService.CryptoService)
if err != nil { if err != nil {
return err return err
} }
@ -549,98 +533,3 @@ func (r *NotaryRepository) bootstrapClient() (*tufclient.Client, error) {
kdb, kdb,
), nil ), nil
} }
// GenRootKey generates a new root key protected by a given passphrase
// TODO(diogo): show not create keys manually, should use a cryptoservice instead
func (r *NotaryRepository) GenRootKey(algorithm, passphrase string) (string, error) {
var err error
var privKey *data.PrivateKey
// We don't want external API callers to rely on internal TUF data types, so
// the API here should continue to receive a string algorithm, and ensure
// that it is downcased
switch data.KeyAlgorithm(strings.ToLower(algorithm)) {
case data.RSAKey:
privKey, err = trustmanager.GenerateRSAKey(rand.Reader, rsaRootKeySize)
case data.ECDSAKey:
privKey, err = trustmanager.GenerateECDSAKey(rand.Reader)
default:
return "", fmt.Errorf("only RSA or ECDSA keys are currently supported. Found: %s", algorithm)
}
if err != nil {
return "", fmt.Errorf("failed to generate private key: %v", err)
}
// Changing the root
r.KeyStoreManager.RootKeyStore().AddEncryptedKey(privKey.ID(), privKey, passphrase)
return privKey.ID(), nil
}
// GetRootCryptoService retreives a root key and a cryptoservice to use with it
func (r *NotaryRepository) GetRootCryptoService(rootKeyID, passphrase string) (*UnlockedCryptoService, error) {
privKey, err := r.KeyStoreManager.RootKeyStore().GetDecryptedKey(rootKeyID, passphrase)
if err != nil {
return nil, fmt.Errorf("could not get decrypted root key with keyID: %s, %v", rootKeyID, err)
}
cryptoService := NewCryptoService("", r.KeyStoreManager.RootKeyStore(), passphrase)
return &UnlockedCryptoService{
privKey: privKey,
cryptoService: cryptoService}, nil
}
// ID gets a consistent ID based on the PrivateKey bytes and algorithm type
func (ucs *UnlockedCryptoService) ID() string {
return ucs.PublicKey().ID()
}
// PublicKey Returns the public key associated with the private key
func (ucs *UnlockedCryptoService) PublicKey() *data.PublicKey {
return data.PublicKeyFromPrivate(*ucs.privKey)
}
// GenerateCertificate generates an X509 Certificate from a template, given a GUN
func (ucs *UnlockedCryptoService) GenerateCertificate(gun string) (*x509.Certificate, error) {
algorithm := ucs.privKey.Algorithm()
var publicKey crypto.PublicKey
var privateKey crypto.PrivateKey
var err error
switch algorithm {
case data.RSAKey:
var rsaPrivateKey *rsa.PrivateKey
rsaPrivateKey, err = x509.ParsePKCS1PrivateKey(ucs.privKey.Private())
privateKey = rsaPrivateKey
publicKey = rsaPrivateKey.Public()
case data.ECDSAKey:
var ecdsaPrivateKey *ecdsa.PrivateKey
ecdsaPrivateKey, err = x509.ParseECPrivateKey(ucs.privKey.Private())
privateKey = ecdsaPrivateKey
publicKey = ecdsaPrivateKey.Public()
default:
return nil, fmt.Errorf("only RSA or ECDSA keys are currently supported. Found: %s", algorithm)
}
if err != nil {
return nil, fmt.Errorf("failed to parse root key: %s (%v)", gun, err)
}
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, publicKey, privateKey)
if err != nil {
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: %s (%v)", gun, err)
}
return cert, nil
}

View File

@ -63,10 +63,10 @@ func testInitRepo(t *testing.T, rootType data.KeyAlgorithm) {
repo, err := NewNotaryRepository(tempBaseDir, gun, ts.URL, http.DefaultTransport) repo, err := NewNotaryRepository(tempBaseDir, gun, ts.URL, http.DefaultTransport)
assert.NoError(t, err, "error creating repo: %s", err) assert.NoError(t, err, "error creating repo: %s", err)
rootKeyID, err := repo.GenRootKey(rootType.String(), "passphrase") rootKeyID, err := repo.KeyStoreManager.GenRootKey(rootType.String(), "passphrase")
assert.NoError(t, err, "error generating root key: %s", err) assert.NoError(t, err, "error generating root key: %s", err)
rootCryptoService, err := repo.GetRootCryptoService(rootKeyID, "passphrase") rootCryptoService, err := repo.KeyStoreManager.GetRootCryptoService(rootKeyID, "passphrase")
assert.NoError(t, err, "error retrieving root key: %s", err) assert.NoError(t, err, "error retrieving root key: %s", err)
err = repo.Initialize(rootCryptoService) err = repo.Initialize(rootCryptoService)
@ -207,10 +207,10 @@ func testAddListTarget(t *testing.T, rootType data.KeyAlgorithm) {
repo, err := NewNotaryRepository(tempBaseDir, gun, ts.URL, http.DefaultTransport) repo, err := NewNotaryRepository(tempBaseDir, gun, ts.URL, http.DefaultTransport)
assert.NoError(t, err, "error creating repository: %s", err) assert.NoError(t, err, "error creating repository: %s", err)
rootKeyID, err := repo.GenRootKey(rootType.String(), "passphrase") rootKeyID, err := repo.KeyStoreManager.GenRootKey(rootType.String(), "passphrase")
assert.NoError(t, err, "error generating root key: %s", err) assert.NoError(t, err, "error generating root key: %s", err)
rootCryptoService, err := repo.GetRootCryptoService(rootKeyID, "passphrase") rootCryptoService, err := repo.KeyStoreManager.GetRootCryptoService(rootKeyID, "passphrase")
assert.NoError(t, err, "error retreiving root key: %s", err) assert.NoError(t, err, "error retreiving root key: %s", err)
err = repo.Initialize(rootCryptoService) err = repo.Initialize(rootCryptoService)
@ -392,10 +392,10 @@ func testValidateRootKey(t *testing.T, rootType data.KeyAlgorithm) {
repo, err := NewNotaryRepository(tempBaseDir, gun, ts.URL, http.DefaultTransport) repo, err := NewNotaryRepository(tempBaseDir, gun, ts.URL, http.DefaultTransport)
assert.NoError(t, err, "error creating repository: %s", err) assert.NoError(t, err, "error creating repository: %s", err)
rootKeyID, err := repo.GenRootKey(rootType.String(), "passphrase") rootKeyID, err := repo.KeyStoreManager.GenRootKey(rootType.String(), "passphrase")
assert.NoError(t, err, "error generating root key: %s", err) assert.NoError(t, err, "error generating root key: %s", err)
rootCryptoService, err := repo.GetRootCryptoService(rootKeyID, "passphrase") rootCryptoService, err := repo.KeyStoreManager.GetRootCryptoService(rootKeyID, "passphrase")
assert.NoError(t, err, "error retreiving root key: %s", err) assert.NoError(t, err, "error retreiving root key: %s", err)
err = repo.Initialize(rootCryptoService) err = repo.Initialize(rootCryptoService)

View File

@ -119,7 +119,7 @@ func tufInit(cmd *cobra.Command, args []string) {
if err != nil { if err != nil {
fatalf(err.Error()) fatalf(err.Error())
} }
rootKeyID, err = nRepo.GenRootKey("ECDSA", passphrase) rootKeyID, err = nRepo.KeyStoreManager.GenRootKey("ECDSA", passphrase)
if err != nil { if err != nil {
fatalf(err.Error()) fatalf(err.Error())
} }
@ -133,7 +133,7 @@ func tufInit(cmd *cobra.Command, args []string) {
} }
} }
rootCryptoService, err := nRepo.GetRootCryptoService(rootKeyID, passphrase) rootCryptoService, err := nRepo.KeyStoreManager.GetRootCryptoService(rootKeyID, passphrase)
if err != nil { if err != nil {
fatalf(err.Error()) fatalf(err.Error())
} }

View File

@ -1,4 +1,4 @@
package client package cryptoservice
import ( import (
"crypto" "crypto"
@ -15,6 +15,10 @@ import (
"github.com/endophage/gotuf/data" "github.com/endophage/gotuf/data"
) )
const (
rsaKeySize = 2048 // Used for snapshots and targets keys
)
// CryptoService implements Sign and Create, holding a specific GUN and keystore to // CryptoService implements Sign and Create, holding a specific GUN and keystore to
// operate on // operate on
type CryptoService struct { type CryptoService struct {

View File

@ -0,0 +1,83 @@
package cryptoservice
import (
"crypto"
"crypto/ecdsa"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"fmt"
"github.com/docker/notary/trustmanager"
"github.com/endophage/gotuf/data"
"github.com/endophage/gotuf/signed"
)
// UnlockedCryptoService encapsulates a private key and a cryptoservice that
// uses that private key, providing convinience methods for generation of
// certificates.
type UnlockedCryptoService struct {
PrivKey *data.PrivateKey
CryptoService signed.CryptoService
}
// NewUnlockedCryptoService creates an UnlockedCryptoService instance
func NewUnlockedCryptoService(privKey *data.PrivateKey, cryptoService signed.CryptoService) *UnlockedCryptoService {
return &UnlockedCryptoService{
PrivKey: privKey,
CryptoService: cryptoService,
}
}
// ID gets a consistent ID based on the PrivateKey bytes and algorithm type
func (ucs *UnlockedCryptoService) ID() string {
return ucs.PublicKey().ID()
}
// PublicKey Returns the public key associated with the private key
func (ucs *UnlockedCryptoService) PublicKey() *data.PublicKey {
return data.PublicKeyFromPrivate(*ucs.PrivKey)
}
// GenerateCertificate generates an X509 Certificate from a template, given a GUN
func (ucs *UnlockedCryptoService) GenerateCertificate(gun string) (*x509.Certificate, error) {
algorithm := ucs.PrivKey.Algorithm()
var publicKey crypto.PublicKey
var privateKey crypto.PrivateKey
var err error
switch algorithm {
case data.RSAKey:
var rsaPrivateKey *rsa.PrivateKey
rsaPrivateKey, err = x509.ParsePKCS1PrivateKey(ucs.PrivKey.Private())
privateKey = rsaPrivateKey
publicKey = rsaPrivateKey.Public()
case data.ECDSAKey:
var ecdsaPrivateKey *ecdsa.PrivateKey
ecdsaPrivateKey, err = x509.ParseECPrivateKey(ucs.PrivKey.Private())
privateKey = ecdsaPrivateKey
publicKey = ecdsaPrivateKey.Public()
default:
return nil, fmt.Errorf("only RSA or ECDSA keys are currently supported. Found: %s", algorithm)
}
if err != nil {
return nil, fmt.Errorf("failed to parse root key: %s (%v)", gun, err)
}
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, publicKey, privateKey)
if err != nil {
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: %s (%v)", gun, err)
}
return cert, nil
}

View File

@ -1,14 +1,18 @@
package keystoremanager package keystoremanager
import ( import (
"crypto/rand"
"crypto/x509" "crypto/x509"
"encoding/json" "encoding/json"
"encoding/pem" "encoding/pem"
"errors" "errors"
"fmt"
"path/filepath" "path/filepath"
"strings"
"time" "time"
"github.com/Sirupsen/logrus" "github.com/Sirupsen/logrus"
"github.com/docker/notary/cryptoservice"
"github.com/docker/notary/trustmanager" "github.com/docker/notary/trustmanager"
"github.com/endophage/gotuf/data" "github.com/endophage/gotuf/data"
"github.com/endophage/gotuf/signed" "github.com/endophage/gotuf/signed"
@ -28,6 +32,7 @@ const (
trustDir = "trusted_certificates" trustDir = "trusted_certificates"
privDir = "private" privDir = "private"
rootKeysSubdir = "root_keys" rootKeysSubdir = "root_keys"
rsaRootKeySize = 4096 // Used for new root keys
) )
// NewKeyStoreManager returns an initialized KeyStoreManager, or an error // NewKeyStoreManager returns an initialized KeyStoreManager, or an error
@ -102,6 +107,46 @@ func (km *KeyStoreManager) CAStore() trustmanager.X509Store {
return km.caStore return km.caStore
} }
// GenRootKey generates a new root key protected by a given passphrase
// TODO(diogo): show not create keys manually, should use a cryptoservice instead
func (km *KeyStoreManager) GenRootKey(algorithm, passphrase string) (string, error) {
var err error
var privKey *data.PrivateKey
// We don't want external API callers to rely on internal TUF data types, so
// the API here should continue to receive a string algorithm, and ensure
// that it is downcased
switch data.KeyAlgorithm(strings.ToLower(algorithm)) {
case data.RSAKey:
privKey, err = trustmanager.GenerateRSAKey(rand.Reader, rsaRootKeySize)
case data.ECDSAKey:
privKey, err = trustmanager.GenerateECDSAKey(rand.Reader)
default:
return "", fmt.Errorf("only RSA or ECDSA keys are currently supported. Found: %s", algorithm)
}
if err != nil {
return "", fmt.Errorf("failed to generate private key: %v", err)
}
// Changing the root
km.rootKeyStore.AddEncryptedKey(privKey.ID(), privKey, passphrase)
return privKey.ID(), nil
}
// GetRootCryptoService retreives a root key and a cryptoservice to use with it
func (km *KeyStoreManager) GetRootCryptoService(rootKeyID, passphrase string) (*cryptoservice.UnlockedCryptoService, error) {
privKey, err := km.rootKeyStore.GetDecryptedKey(rootKeyID, passphrase)
if err != nil {
return nil, fmt.Errorf("could not get decrypted root key with keyID: %s, %v", rootKeyID, err)
}
cryptoService := cryptoservice.NewCryptoService("", km.rootKeyStore, passphrase)
return cryptoservice.NewUnlockedCryptoService(privKey, cryptoService), nil
}
/* /*
ValidateRoot iterates over every root key included in the TUF data and ValidateRoot iterates over every root key included in the TUF data and
attempts to validate the certificate by first checking for an exact match on attempts to validate the certificate by first checking for an exact match on