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 (
"bytes"
"crypto"
"crypto/ecdsa"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"encoding/json"
"errors"
"fmt"
@ -14,10 +9,10 @@ import (
"net/http"
"os"
"path/filepath"
"strings"
"github.com/Sirupsen/logrus"
"github.com/docker/notary/client/changelist"
"github.com/docker/notary/cryptoservice"
"github.com/docker/notary/keystoremanager"
"github.com/docker/notary/trustmanager"
"github.com/endophage/gotuf"
@ -40,25 +35,14 @@ func (err *ErrRepoNotInitialized) Error() string {
return "Repository has not been initialized"
}
// Default paths should end with a '/' so directory creation works correctly
const (
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
/// that doesn't 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
// repository.
type NotaryRepository struct {
@ -105,7 +89,7 @@ func NewNotaryRepository(baseDir, gun, baseURL string, rt http.RoundTripper) (*N
return nil, err
}
cryptoService := NewCryptoService(gun, keyStoreManager.NonRootKeyStore(), "")
cryptoService := cryptoservice.NewCryptoService(gun, keyStoreManager.NonRootKeyStore(), "")
nRepo := &NotaryRepository{
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
// TUF repository.
func (r *NotaryRepository) Initialize(uCryptoService *UnlockedCryptoService) error {
func (r *NotaryRepository) Initialize(uCryptoService *cryptoservice.UnlockedCryptoService) error {
rootCert, err := uCryptoService.GenerateCertificate(r.gun)
if err != nil {
return err
@ -135,7 +119,7 @@ func (r *NotaryRepository) Initialize(uCryptoService *UnlockedCryptoService) err
// as ECDSAx509 to allow the gotuf verifiers to correctly decode the
// key on verification of signatures.
var algorithmType data.KeyAlgorithm
algorithm := uCryptoService.privKey.Algorithm()
algorithm := uCryptoService.PrivKey.Algorithm()
switch algorithm {
case data.RSAKey:
algorithmType = data.RSAx509Key
@ -238,7 +222,7 @@ func (r *NotaryRepository) Initialize(uCryptoService *UnlockedCryptoService) err
return err
}
if err := r.saveMetadata(uCryptoService.cryptoService); err != nil {
if err := r.saveMetadata(uCryptoService.CryptoService); err != nil {
return err
}
@ -372,11 +356,11 @@ func (r *NotaryRepository) Publish(getPass passwordRetriever) error {
return err
}
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 {
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 {
return err
}
@ -549,98 +533,3 @@ func (r *NotaryRepository) bootstrapClient() (*tufclient.Client, error) {
kdb,
), 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)
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)
rootCryptoService, err := repo.GetRootCryptoService(rootKeyID, "passphrase")
rootCryptoService, err := repo.KeyStoreManager.GetRootCryptoService(rootKeyID, "passphrase")
assert.NoError(t, err, "error retrieving root key: %s", err)
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)
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)
rootCryptoService, err := repo.GetRootCryptoService(rootKeyID, "passphrase")
rootCryptoService, err := repo.KeyStoreManager.GetRootCryptoService(rootKeyID, "passphrase")
assert.NoError(t, err, "error retreiving root key: %s", err)
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)
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)
rootCryptoService, err := repo.GetRootCryptoService(rootKeyID, "passphrase")
rootCryptoService, err := repo.KeyStoreManager.GetRootCryptoService(rootKeyID, "passphrase")
assert.NoError(t, err, "error retreiving root key: %s", err)
err = repo.Initialize(rootCryptoService)

View File

@ -119,7 +119,7 @@ func tufInit(cmd *cobra.Command, args []string) {
if err != nil {
fatalf(err.Error())
}
rootKeyID, err = nRepo.GenRootKey("ECDSA", passphrase)
rootKeyID, err = nRepo.KeyStoreManager.GenRootKey("ECDSA", passphrase)
if err != nil {
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 {
fatalf(err.Error())
}

View File

@ -1,4 +1,4 @@
package client
package cryptoservice
import (
"crypto"
@ -15,6 +15,10 @@ import (
"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
// operate on
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
import (
"crypto/rand"
"crypto/x509"
"encoding/json"
"encoding/pem"
"errors"
"fmt"
"path/filepath"
"strings"
"time"
"github.com/Sirupsen/logrus"
"github.com/docker/notary/cryptoservice"
"github.com/docker/notary/trustmanager"
"github.com/endophage/gotuf/data"
"github.com/endophage/gotuf/signed"
@ -28,6 +32,7 @@ const (
trustDir = "trusted_certificates"
privDir = "private"
rootKeysSubdir = "root_keys"
rsaRootKeySize = 4096 // Used for new root keys
)
// NewKeyStoreManager returns an initialized KeyStoreManager, or an error
@ -102,6 +107,46 @@ func (km *KeyStoreManager) CAStore() trustmanager.X509Store {
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
attempts to validate the certificate by first checking for an exact match on