mirror of https://github.com/docker/docs.git
Refactor crypto service
Signed-off-by: Diogo Monica <diogo@docker.com>
This commit is contained in:
parent
17af21f00c
commit
765a2cf661
|
@ -15,51 +15,52 @@ import (
|
|||
"github.com/endophage/gotuf/data"
|
||||
)
|
||||
|
||||
type genericCryptoService struct {
|
||||
// CryptoService implements Sign and Create, holding a specific GUN and keystore to
|
||||
// operate on
|
||||
type CryptoService struct {
|
||||
gun string
|
||||
passphrase string
|
||||
keyStore *trustmanager.KeyFileStore
|
||||
}
|
||||
|
||||
// RSACryptoService implements Sign and Create, holding a specific GUN and keystore to
|
||||
// operate on
|
||||
type RSACryptoService struct {
|
||||
genericCryptoService
|
||||
}
|
||||
|
||||
// ECDSACryptoService implements Sign and Create, holding a specific GUN and keystore to
|
||||
// operate on
|
||||
type ECDSACryptoService struct {
|
||||
genericCryptoService
|
||||
}
|
||||
|
||||
// NewRSACryptoService returns an instance of CryptoService
|
||||
func NewRSACryptoService(gun string, keyStore *trustmanager.KeyFileStore, passphrase string) *RSACryptoService {
|
||||
return &RSACryptoService{genericCryptoService{gun: gun, keyStore: keyStore, passphrase: passphrase}}
|
||||
// NewCryptoService returns an instance of CryptoService
|
||||
func NewCryptoService(gun string, keyStore *trustmanager.KeyFileStore, passphrase string) *CryptoService {
|
||||
return &CryptoService{gun: gun, keyStore: keyStore, passphrase: passphrase}
|
||||
}
|
||||
|
||||
// Create is used to generate keys for targets, snapshots and timestamps
|
||||
func (ccs *RSACryptoService) Create(role string) (*data.PublicKey, error) {
|
||||
privKey, err := trustmanager.GenerateRSAKey(rand.Reader, rsaKeySize)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to generate RSA key: %v", err)
|
||||
func (ccs *CryptoService) Create(role string, algorithm data.KeyAlgorithm) (*data.PublicKey, error) {
|
||||
var privKey *data.PrivateKey
|
||||
var err error
|
||||
|
||||
switch algorithm {
|
||||
case data.RSAKey:
|
||||
privKey, err = trustmanager.GenerateRSAKey(rand.Reader, rsaKeySize)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to generate RSA key: %v", err)
|
||||
}
|
||||
case data.ECDSAKey:
|
||||
privKey, err = trustmanager.GenerateECDSAKey(rand.Reader)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to generate EC key: %v", err)
|
||||
}
|
||||
default:
|
||||
return nil, fmt.Errorf("private key type not supported for key generation: %s", algorithm)
|
||||
}
|
||||
logrus.Debugf("generated new %s key for role: %s and keyID: %s", algorithm, role, privKey.ID())
|
||||
|
||||
// Store the private key into our keystore with the name being: /GUN/ID.key
|
||||
err = ccs.keyStore.AddKey(filepath.Join(ccs.gun, privKey.ID()), privKey)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to add key to filestore: %v", err)
|
||||
}
|
||||
|
||||
logrus.Debugf("generated new RSA key for role: %s and keyID: %s", role, privKey.ID())
|
||||
|
||||
return data.PublicKeyFromPrivate(*privKey), nil
|
||||
}
|
||||
|
||||
// Sign returns the signatures for the payload with a set of keyIDs. It ignores
|
||||
// errors to sign and expects the called to validate if the number of returned
|
||||
// signatures is adequate.
|
||||
func (ccs *RSACryptoService) Sign(keyIDs []string, payload []byte) ([]data.Signature, error) {
|
||||
func (ccs *CryptoService) Sign(keyIDs []string, payload []byte) ([]data.Signature, error) {
|
||||
// Create hasher and hash data
|
||||
hash := crypto.SHA256
|
||||
hashed := sha256.Sum256(payload)
|
||||
|
@ -72,11 +73,8 @@ func (ccs *RSACryptoService) Sign(keyIDs []string, payload []byte) ([]data.Signa
|
|||
var privKey *data.PrivateKey
|
||||
var err error
|
||||
|
||||
// Read PrivateKey from file.
|
||||
// TODO(diogo): This assumes both that only root keys are encrypted and
|
||||
// that encrypted keys are always X509 encoded
|
||||
// Read PrivateKey from file and decrypt it if there is a passphrase.
|
||||
if ccs.passphrase != "" {
|
||||
// This is a root key
|
||||
privKey, err = ccs.keyStore.GetDecryptedKey(keyName, ccs.passphrase)
|
||||
} else {
|
||||
privKey, err = ccs.keyStore.GetKey(keyName)
|
||||
|
@ -86,27 +84,33 @@ func (ccs *RSACryptoService) Sign(keyIDs []string, payload []byte) ([]data.Signa
|
|||
// InitRepo gets a signer that doesn't have access to
|
||||
// the root keys. Continuing here is safe because we
|
||||
// end up not returning any signatures.
|
||||
logrus.Debugf("ignoring error attempting to retrieve RSA key ID: %s, %v", keyid, err)
|
||||
logrus.Debugf("ignoring error attempting to retrieve key ID: %s, %v", keyid, err)
|
||||
continue
|
||||
}
|
||||
|
||||
sig, err := rsaSign(privKey, hash, hashed[:])
|
||||
algorithm := privKey.Algorithm()
|
||||
var sigAlgorithm data.SigAlgorithm
|
||||
var sig []byte
|
||||
|
||||
switch algorithm {
|
||||
case data.RSAKey:
|
||||
sig, err = rsaSign(privKey, hash, hashed[:])
|
||||
sigAlgorithm = data.RSAPSSSignature
|
||||
case data.ECDSAKey:
|
||||
sig, err = ecdsaSign(privKey, hashed[:])
|
||||
sigAlgorithm = data.ECDSASignature
|
||||
}
|
||||
if err != nil {
|
||||
// If the rsaSign method got called with a non RSA private key,
|
||||
// we ignore this call.
|
||||
// This might happen when root is ECDSA, targets and snapshots RSA, and
|
||||
// gotuf still attempts to sign root with this cryptoserver
|
||||
// return nil, err
|
||||
logrus.Debugf("ignoring error attempting to RSA sign with keyID: %s, %v", keyid, err)
|
||||
logrus.Debugf("ignoring error attempting to %s sign with keyID: %s, %v", algorithm, keyid, err)
|
||||
continue
|
||||
}
|
||||
|
||||
logrus.Debugf("appending RSA signature with Key ID: %s", privKey.ID())
|
||||
logrus.Debugf("appending %s signature with Key ID: %s", algorithm, keyid)
|
||||
|
||||
// Append signatures to result array
|
||||
signatures = append(signatures, data.Signature{
|
||||
KeyID: keyid,
|
||||
Method: data.RSAPSSSignature,
|
||||
Method: sigAlgorithm,
|
||||
Signature: sig[:],
|
||||
})
|
||||
}
|
||||
|
@ -115,8 +119,8 @@ func (ccs *RSACryptoService) Sign(keyIDs []string, payload []byte) ([]data.Signa
|
|||
}
|
||||
|
||||
func rsaSign(privKey *data.PrivateKey, hash crypto.Hash, hashed []byte) ([]byte, error) {
|
||||
if privKey.Cipher() != data.RSAKey {
|
||||
return nil, fmt.Errorf("private key type not supported: %s", privKey.Cipher())
|
||||
if privKey.Algorithm() != data.RSAKey {
|
||||
return nil, fmt.Errorf("private key type not supported: %s", privKey.Algorithm())
|
||||
}
|
||||
|
||||
// Create an rsa.PrivateKey out of the private key bytes
|
||||
|
@ -134,94 +138,9 @@ func rsaSign(privKey *data.PrivateKey, hash crypto.Hash, hashed []byte) ([]byte,
|
|||
return sig, nil
|
||||
}
|
||||
|
||||
// NewECDSACryptoService returns an instance of CryptoService
|
||||
func NewECDSACryptoService(gun string, keyStore *trustmanager.KeyFileStore, passphrase string) *ECDSACryptoService {
|
||||
return &ECDSACryptoService{genericCryptoService{gun: gun, keyStore: keyStore, passphrase: passphrase}}
|
||||
}
|
||||
|
||||
// Create is used to generate keys for targets, snapshots and timestamps
|
||||
func (ccs *ECDSACryptoService) Create(role string) (*data.PublicKey, error) {
|
||||
privKey, err := trustmanager.GenerateECDSAKey(rand.Reader)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to generate EC key: %v", err)
|
||||
}
|
||||
|
||||
// Store the private key into our keystore with the name being: /GUN/ID.key
|
||||
err = ccs.keyStore.AddKey(filepath.Join(ccs.gun, privKey.ID()), privKey)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to add key to filestore: %v", err)
|
||||
}
|
||||
|
||||
logrus.Debugf("generated new ECDSA key for role %s with keyID: %s", role, privKey.ID())
|
||||
|
||||
return data.PublicKeyFromPrivate(*privKey), nil
|
||||
}
|
||||
|
||||
// Sign returns the signatures for the payload with a set of keyIDs. It ignores
|
||||
// errors to sign and expects the called to validate if the number of returned
|
||||
// signatures is adequate.
|
||||
func (ccs *ECDSACryptoService) Sign(keyIDs []string, payload []byte) ([]data.Signature, error) {
|
||||
// Create hasher and hash data
|
||||
hashed := sha256.Sum256(payload)
|
||||
|
||||
signatures := make([]data.Signature, 0, len(keyIDs))
|
||||
for _, keyid := range keyIDs {
|
||||
// ccs.gun will be empty if this is the root key
|
||||
keyName := filepath.Join(ccs.gun, keyid)
|
||||
|
||||
var privKey *data.PrivateKey
|
||||
var err error
|
||||
|
||||
// Read PrivateKey from file
|
||||
// TODO(diogo): This assumes both that only root keys are encrypted and
|
||||
// that encrypted keys are always X509 encoded
|
||||
if ccs.passphrase != "" {
|
||||
// This is a root key
|
||||
privKey, err = ccs.keyStore.GetDecryptedKey(keyName, ccs.passphrase)
|
||||
} else {
|
||||
privKey, err = ccs.keyStore.GetKey(keyName)
|
||||
}
|
||||
if err != nil {
|
||||
// Note that GetDecryptedKey always fails on InitRepo.
|
||||
// InitRepo gets a signer that doesn't have access to
|
||||
// the root keys. Continuing here is safe because we
|
||||
// end up not returning any signatures.
|
||||
// TODO(diogo): figure out if there are any specific error types to
|
||||
// check. We're swallowing all errors.
|
||||
logrus.Debugf("Ignoring error attempting to retrieve ECDSA key ID: %s, %v", keyid, err)
|
||||
continue
|
||||
}
|
||||
if err != nil {
|
||||
fmt.Println("ERROR: ", err.Error())
|
||||
}
|
||||
|
||||
sig, err := ecdsaSign(privKey, hashed[:])
|
||||
if err != nil {
|
||||
// If the ecdsaSign method got called with a non ECDSA private key,
|
||||
// we ignore this call.
|
||||
// This might happen when root is RSA, targets and snapshots ECDSA, and
|
||||
// gotuf still attempts to sign root with this cryptoserver
|
||||
// return nil, err
|
||||
logrus.Debugf("ignoring error attempting to ECDSA sign with keyID: %s, %v", privKey.ID(), err)
|
||||
continue
|
||||
}
|
||||
|
||||
logrus.Debugf("appending ECDSA signature with Key ID: %s", privKey.ID())
|
||||
|
||||
// Append signatures to result array
|
||||
signatures = append(signatures, data.Signature{
|
||||
KeyID: keyid,
|
||||
Method: data.ECDSASignature,
|
||||
Signature: sig[:],
|
||||
})
|
||||
}
|
||||
|
||||
return signatures, nil
|
||||
}
|
||||
|
||||
func ecdsaSign(privKey *data.PrivateKey, hashed []byte) ([]byte, error) {
|
||||
if privKey.Cipher() != data.ECDSAKey {
|
||||
return nil, fmt.Errorf("private key type not supported: %s", privKey.Cipher())
|
||||
if privKey.Algorithm() != data.ECDSAKey {
|
||||
return nil, fmt.Errorf("private key type not supported: %s", privKey.Algorithm())
|
||||
}
|
||||
|
||||
// Create an ecdsa.PrivateKey out of the private key bytes
|
||||
|
|
|
@ -73,6 +73,7 @@ type NotaryRepository struct {
|
|||
certificateStore trustmanager.X509Store
|
||||
fileStore store.MetadataStore
|
||||
signer *signed.Signer
|
||||
cryptoService signed.CryptoService
|
||||
tufRepo *tuf.TufRepo
|
||||
privKeyStore *trustmanager.KeyFileStore
|
||||
rootKeyStore *trustmanager.KeyFileStore
|
||||
|
@ -115,17 +116,18 @@ func NewNotaryRepository(baseDir, gun, baseURL string, rt http.RoundTripper) (*N
|
|||
return nil, err
|
||||
}
|
||||
|
||||
// TODO(diogo): This hardcodes snapshots and targets to using EC. Change it.
|
||||
signer := signed.NewSigner(NewECDSACryptoService(gun, privKeyStore, ""))
|
||||
cryptoService := NewCryptoService(gun, privKeyStore, "")
|
||||
signer := signed.NewSigner(cryptoService)
|
||||
|
||||
nRepo := &NotaryRepository{
|
||||
gun: gun,
|
||||
baseDir: baseDir,
|
||||
baseURL: baseURL,
|
||||
tufRepoPath: filepath.Join(baseDir, tufDir, gun),
|
||||
signer: signer,
|
||||
privKeyStore: privKeyStore,
|
||||
roundTrip: rt,
|
||||
gun: gun,
|
||||
baseDir: baseDir,
|
||||
baseURL: baseURL,
|
||||
tufRepoPath: filepath.Join(baseDir, tufDir, gun),
|
||||
signer: signer,
|
||||
cryptoService: cryptoService,
|
||||
privKeyStore: privKeyStore,
|
||||
roundTrip: rt,
|
||||
}
|
||||
|
||||
if err := nRepo.loadKeys(trustDir, rootKeysDir); err != nil {
|
||||
|
@ -149,19 +151,19 @@ func (r *NotaryRepository) Initialize(uSigner *UnlockedSigner) error {
|
|||
// If the key is RSA, we store it as type RSAx509, if it is ECDSA we store it
|
||||
// as ECDSAx509 to allow the gotuf verifiers to correctly decode the
|
||||
// key on verification of signatures.
|
||||
var cipherType string
|
||||
cipher := uSigner.privKey.Cipher()
|
||||
switch cipher {
|
||||
var algorithmType data.KeyAlgorithm
|
||||
algorithm := uSigner.privKey.Algorithm()
|
||||
switch algorithm {
|
||||
case data.RSAKey:
|
||||
cipherType = data.RSAx509Key
|
||||
algorithmType = data.RSAx509Key
|
||||
case data.ECDSAKey:
|
||||
cipherType = data.ECDSAx509Key
|
||||
algorithmType = data.ECDSAx509Key
|
||||
default:
|
||||
return fmt.Errorf("invalid format for root key: %s", cipher)
|
||||
return fmt.Errorf("invalid format for root key: %s", algorithm)
|
||||
}
|
||||
|
||||
// Generate a x509Key using the rootCert as the public key
|
||||
rootKey := data.NewPublicKey(cipherType, trustmanager.CertToPEM(rootCert))
|
||||
rootKey := data.NewPublicKey(algorithmType, trustmanager.CertToPEM(rootCert))
|
||||
|
||||
// Creates a symlink between the certificate ID and the real public key it
|
||||
// is associated with. This is used to be able to retrieve the root private key
|
||||
|
@ -186,15 +188,16 @@ func (r *NotaryRepository) Initialize(uSigner *UnlockedSigner) error {
|
|||
}
|
||||
|
||||
// Turn the JSON timestamp key from the remote server into a TUFKey
|
||||
timestampKey := data.NewPublicKey(parsedKey.Cipher(), parsedKey.Public())
|
||||
logrus.Debugf("got remote %s timestamp key with keyID: %s", parsedKey.Cipher(), timestampKey.ID())
|
||||
timestampKey := data.NewPublicKey(parsedKey.Algorithm(), parsedKey.Public())
|
||||
logrus.Debugf("got remote %s timestamp key with keyID: %s", parsedKey.Algorithm(), timestampKey.ID())
|
||||
|
||||
// This is currently hardcoding the targets and snapshots keys to ECDSA
|
||||
// Targets and snapshot keys are always generated locally.
|
||||
targetsKey, err := r.signer.Create("targets")
|
||||
targetsKey, err := r.cryptoService.Create("targets", data.ECDSAKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
snapshotKey, err := r.signer.Create("snapshot")
|
||||
snapshotKey, err := r.cryptoService.Create("snapshot", data.ECDSAKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -652,12 +655,16 @@ func (r *NotaryRepository) ListRootKeys() []string {
|
|||
return r.rootKeyStore.ListKeys()
|
||||
}
|
||||
|
||||
// TODO(diogo): show not create keys manually, should use a cryptoservice instead
|
||||
// GenRootKey generates a new root key protected by a given passphrase
|
||||
func (r *NotaryRepository) GenRootKey(algorithm, passphrase string) (string, error) {
|
||||
var err error
|
||||
var privKey *data.PrivateKey
|
||||
|
||||
switch strings.ToLower(algorithm) {
|
||||
// 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:
|
||||
|
@ -683,17 +690,7 @@ func (r *NotaryRepository) GetRootSigner(rootKeyID, passphrase string) (*Unlocke
|
|||
return nil, fmt.Errorf("could not get decrypted root key with keyID: %s, %v", rootKeyID, err)
|
||||
}
|
||||
|
||||
var signer *signed.Signer
|
||||
cipher := privKey.Cipher()
|
||||
// Passing an empty GUN because root keys aren't associated with a GUN.
|
||||
switch strings.ToLower(cipher) {
|
||||
case data.RSAKey:
|
||||
signer = signed.NewSigner(NewRSACryptoService("", r.rootKeyStore, passphrase))
|
||||
case data.ECDSAKey:
|
||||
signer = signed.NewSigner(NewECDSACryptoService("", r.rootKeyStore, passphrase))
|
||||
default:
|
||||
return nil, fmt.Errorf("only RSA or ECDSA keys are currently supported. Found: %s", cipher)
|
||||
}
|
||||
signer := signed.NewSigner(NewCryptoService("", r.rootKeyStore, passphrase))
|
||||
|
||||
return &UnlockedSigner{
|
||||
privKey: privKey,
|
||||
|
@ -738,7 +735,7 @@ func (r *NotaryRepository) loadKeys(trustDir, rootKeysDir string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// ID gets a consistent ID based on the PrivateKey bytes and cipher type
|
||||
// ID gets a consistent ID based on the PrivateKey bytes and algorithm type
|
||||
func (uk *UnlockedSigner) ID() string {
|
||||
return uk.PublicKey().ID()
|
||||
}
|
||||
|
@ -750,11 +747,11 @@ func (uk *UnlockedSigner) PublicKey() *data.PublicKey {
|
|||
|
||||
// GenerateCertificate generates an X509 Certificate from a template, given a GUN
|
||||
func (uk *UnlockedSigner) GenerateCertificate(gun string) (*x509.Certificate, error) {
|
||||
cipher := uk.privKey.Cipher()
|
||||
algorithm := uk.privKey.Algorithm()
|
||||
var publicKey crypto.PublicKey
|
||||
var privateKey crypto.PrivateKey
|
||||
var err error
|
||||
switch cipher {
|
||||
switch algorithm {
|
||||
case data.RSAKey:
|
||||
var rsaPrivateKey *rsa.PrivateKey
|
||||
rsaPrivateKey, err = x509.ParsePKCS1PrivateKey(uk.privKey.Private())
|
||||
|
@ -766,7 +763,7 @@ func (uk *UnlockedSigner) GenerateCertificate(gun string) (*x509.Certificate, er
|
|||
privateKey = ecdsaPrivateKey
|
||||
publicKey = ecdsaPrivateKey.Public()
|
||||
default:
|
||||
return nil, fmt.Errorf("only RSA or ECDSA keys are currently supported. Found: %s", cipher)
|
||||
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)
|
||||
|
|
|
@ -43,11 +43,13 @@ func createTestServer(t *testing.T) (*httptest.Server, *http.ServeMux) {
|
|||
// sure the repository looks correct on disk.
|
||||
// We test this with both an RSA and ECDSA root key
|
||||
func TestInitRepo(t *testing.T) {
|
||||
testInitRepo(t, data.RSAKey)
|
||||
testInitRepo(t, data.ECDSAKey)
|
||||
if !testing.Short() {
|
||||
testInitRepo(t, data.RSAKey)
|
||||
}
|
||||
}
|
||||
|
||||
func testInitRepo(t *testing.T, rootType string) {
|
||||
func testInitRepo(t *testing.T, rootType data.KeyAlgorithm) {
|
||||
gun := "docker.com/notary"
|
||||
// Temporary directory where test files will be created
|
||||
tempBaseDir, err := ioutil.TempDir("", "notary-test-")
|
||||
|
@ -61,7 +63,7 @@ func testInitRepo(t *testing.T, rootType string) {
|
|||
repo, err := NewNotaryRepository(tempBaseDir, gun, ts.URL, http.DefaultTransport)
|
||||
assert.NoError(t, err, "error creating repo: %s", err)
|
||||
|
||||
rootKeyID, err := repo.GenRootKey(rootType, "passphrase")
|
||||
rootKeyID, err := repo.GenRootKey(rootType.String(), "passphrase")
|
||||
assert.NoError(t, err, "error generating root key: %s", err)
|
||||
|
||||
rootSigner, err := repo.GetRootSigner(rootKeyID, "passphrase")
|
||||
|
@ -184,11 +186,13 @@ type tufChange struct {
|
|||
// internal HTTP server.
|
||||
// We test this with both an RSA and ECDSA root key
|
||||
func TestAddListTarget(t *testing.T) {
|
||||
testAddListTarget(t, data.RSAKey)
|
||||
testAddListTarget(t, data.ECDSAKey)
|
||||
if !testing.Short() {
|
||||
testInitRepo(t, data.RSAKey)
|
||||
}
|
||||
}
|
||||
|
||||
func testAddListTarget(t *testing.T, rootType string) {
|
||||
func testAddListTarget(t *testing.T, rootType data.KeyAlgorithm) {
|
||||
// Temporary directory where test files will be created
|
||||
tempBaseDir, err := ioutil.TempDir("", "notary-test-")
|
||||
defer os.RemoveAll(tempBaseDir)
|
||||
|
@ -203,7 +207,7 @@ func testAddListTarget(t *testing.T, rootType string) {
|
|||
repo, err := NewNotaryRepository(tempBaseDir, gun, ts.URL, http.DefaultTransport)
|
||||
assert.NoError(t, err, "error creating repository: %s", err)
|
||||
|
||||
rootKeyID, err := repo.GenRootKey(rootType, "passphrase")
|
||||
rootKeyID, err := repo.GenRootKey(rootType.String(), "passphrase")
|
||||
assert.NoError(t, err, "error generating root key: %s", err)
|
||||
|
||||
rootSigner, err := repo.GetRootSigner(rootKeyID, "passphrase")
|
||||
|
@ -367,11 +371,13 @@ func testAddListTarget(t *testing.T, rootType string) {
|
|||
// TestValidateRootKey verifies that the public data in root.json for the root
|
||||
// key is a valid x509 certificate.
|
||||
func TestValidateRootKey(t *testing.T) {
|
||||
testValidateRootKey(t, data.RSAKey)
|
||||
testValidateRootKey(t, data.ECDSAKey)
|
||||
if !testing.Short() {
|
||||
testInitRepo(t, data.RSAKey)
|
||||
}
|
||||
}
|
||||
|
||||
func testValidateRootKey(t *testing.T, rootType string) {
|
||||
func testValidateRootKey(t *testing.T, rootType data.KeyAlgorithm) {
|
||||
// Temporary directory where test files will be created
|
||||
tempBaseDir, err := ioutil.TempDir("", "notary-test-")
|
||||
defer os.RemoveAll(tempBaseDir)
|
||||
|
@ -386,7 +392,7 @@ func testValidateRootKey(t *testing.T, rootType string) {
|
|||
repo, err := NewNotaryRepository(tempBaseDir, gun, ts.URL, http.DefaultTransport)
|
||||
assert.NoError(t, err, "error creating repository: %s", err)
|
||||
|
||||
rootKeyID, err := repo.GenRootKey(rootType, "passphrase")
|
||||
rootKeyID, err := repo.GenRootKey(rootType.String(), "passphrase")
|
||||
assert.NoError(t, err, "error generating root key: %s", err)
|
||||
|
||||
rootSigner, err := repo.GetRootSigner(rootKeyID, "passphrase")
|
||||
|
|
|
@ -13,7 +13,6 @@ import (
|
|||
"github.com/Sirupsen/logrus"
|
||||
"github.com/docker/docker/pkg/term"
|
||||
notaryclient "github.com/docker/notary/client"
|
||||
"github.com/endophage/gotuf/data"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
@ -120,7 +119,7 @@ func tufInit(cmd *cobra.Command, args []string) {
|
|||
if err != nil {
|
||||
fatalf(err.Error())
|
||||
}
|
||||
rootKeyID, err = nRepo.GenRootKey(data.ECDSAKey, passphrase)
|
||||
rootKeyID, err = nRepo.GenRootKey("ECDSA", passphrase)
|
||||
if err != nil {
|
||||
fatalf(err.Error())
|
||||
}
|
||||
|
|
|
@ -224,7 +224,7 @@ func GetTimestampKeyHandler(ctx context.Context, w http.ResponseWriter, r *http.
|
|||
vars := mux.Vars(r)
|
||||
gun := vars["imageName"]
|
||||
|
||||
key, err := timestamp.GetOrCreateTimestampKey(gun, store, crypto)
|
||||
key, err := timestamp.GetOrCreateTimestampKey(gun, store, crypto, data.ECDSAKey)
|
||||
if err != nil {
|
||||
return &errors.HTTPError{
|
||||
HTTPStatus: http.StatusInternalServerError,
|
||||
|
|
|
@ -3,6 +3,7 @@ package storage
|
|||
import (
|
||||
"database/sql"
|
||||
|
||||
"github.com/endophage/gotuf/data"
|
||||
"github.com/go-sql-driver/mysql"
|
||||
)
|
||||
|
||||
|
@ -91,10 +92,11 @@ func (db *MySQLStorage) Delete(gun string) error {
|
|||
}
|
||||
|
||||
// GetTimestampKey returns the timestamps Public Key data
|
||||
func (db *MySQLStorage) GetTimestampKey(gun string) (cipher string, public []byte, err error) {
|
||||
func (db *MySQLStorage) GetTimestampKey(gun string) (algorithm data.KeyAlgorithm, public []byte, err error) {
|
||||
stmt := "SELECT `cipher`, `public` FROM `timestamp_keys` WHERE `gun`=?;"
|
||||
row := db.QueryRow(stmt, gun)
|
||||
|
||||
var cipher string
|
||||
err = row.Scan(&cipher, &public)
|
||||
if err == sql.ErrNoRows {
|
||||
return "", nil, ErrNoKey{gun: gun}
|
||||
|
@ -102,13 +104,13 @@ func (db *MySQLStorage) GetTimestampKey(gun string) (cipher string, public []byt
|
|||
return "", nil, err
|
||||
}
|
||||
|
||||
return cipher, public, err
|
||||
return data.KeyAlgorithm(cipher), public, err
|
||||
}
|
||||
|
||||
// SetTimestampKey attempts to write a TimeStamp key and returns an error if it already exists
|
||||
func (db *MySQLStorage) SetTimestampKey(gun, cipher string, public []byte) error {
|
||||
func (db *MySQLStorage) SetTimestampKey(gun string, algorithm data.KeyAlgorithm, public []byte) error {
|
||||
stmt := "INSERT INTO `timestamp_keys` (`gun`, `cipher`, `public`) VALUES (?,?,?);"
|
||||
_, err := db.Exec(stmt, gun, cipher, public)
|
||||
_, err := db.Exec(stmt, gun, string(algorithm), public)
|
||||
if err, ok := err.(*mysql.MySQLError); ok {
|
||||
if err.Number == 1022 { // duplicate key error
|
||||
return &ErrTimestampKeyExists{gun: gun}
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
package storage
|
||||
|
||||
import "github.com/endophage/gotuf/data"
|
||||
|
||||
// MetaStore holds the methods that are used for a Metadata Store
|
||||
type MetaStore interface {
|
||||
UpdateCurrent(gun, role string, version int, data []byte) error
|
||||
GetCurrent(gun, tufRole string) (data []byte, err error)
|
||||
Delete(gun string) error
|
||||
GetTimestampKey(gun string) (cipher string, public []byte, err error)
|
||||
SetTimestampKey(gun, cipher string, public []byte) error
|
||||
GetTimestampKey(gun string) (algorithm data.KeyAlgorithm, public []byte, err error)
|
||||
SetTimestampKey(gun string, algorithm data.KeyAlgorithm, public []byte) error
|
||||
}
|
||||
|
|
|
@ -4,11 +4,13 @@ import (
|
|||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/endophage/gotuf/data"
|
||||
)
|
||||
|
||||
type key struct {
|
||||
cipher string
|
||||
public []byte
|
||||
algorithm data.KeyAlgorithm
|
||||
public []byte
|
||||
}
|
||||
|
||||
type ver struct {
|
||||
|
@ -73,7 +75,7 @@ func (st *MemStorage) Delete(gun string) error {
|
|||
}
|
||||
|
||||
// GetTimestampKey returns the public key material of the timestamp key of a given gun
|
||||
func (st *MemStorage) GetTimestampKey(gun string) (cipher string, public []byte, err error) {
|
||||
func (st *MemStorage) GetTimestampKey(gun string) (algorithm data.KeyAlgorithm, public []byte, err error) {
|
||||
// no need for lock. It's ok to return nil if an update
|
||||
// wasn't observed
|
||||
k, ok := st.tsKeys[gun]
|
||||
|
@ -81,12 +83,12 @@ func (st *MemStorage) GetTimestampKey(gun string) (cipher string, public []byte,
|
|||
return "", nil, &ErrNoKey{gun: gun}
|
||||
}
|
||||
|
||||
return k.cipher, k.public, nil
|
||||
return k.algorithm, k.public, nil
|
||||
}
|
||||
|
||||
// SetTimestampKey sets a Timestamp key under a gun
|
||||
func (st *MemStorage) SetTimestampKey(gun, cipher string, public []byte) error {
|
||||
k := &key{cipher: cipher, public: public}
|
||||
func (st *MemStorage) SetTimestampKey(gun string, algorithm data.KeyAlgorithm, public []byte) error {
|
||||
k := &key{algorithm: algorithm, public: public}
|
||||
st.lock.Lock()
|
||||
defer st.lock.Unlock()
|
||||
if _, ok := st.tsKeys[gun]; ok {
|
||||
|
|
|
@ -51,7 +51,7 @@ func TestGetTimestampKey(t *testing.T) {
|
|||
|
||||
c, k, err := s.GetTimestampKey("gun")
|
||||
assert.Nil(t, err, "Expected error to be nil")
|
||||
assert.Equal(t, data.RSAKey, c, "Expected cipher rsa, received %s", c)
|
||||
assert.Equal(t, data.RSAKey, c, "Expected algorithm rsa, received %s", c)
|
||||
assert.Equal(t, []byte("test"), k, "Key data was wrong")
|
||||
}
|
||||
|
||||
|
@ -63,7 +63,7 @@ func TestSetTimestampKey(t *testing.T) {
|
|||
assert.IsType(t, &ErrTimestampKeyExists{}, err, "Expected err to be ErrTimestampKeyExists")
|
||||
|
||||
k := s.tsKeys["gun"]
|
||||
assert.Equal(t, data.RSAKey, k.cipher, "Expected cipher to be rsa, received %s", k.cipher)
|
||||
assert.Equal(t, data.RSAKey, k.algorithm, "Expected algorithm to be rsa, received %s", k.algorithm)
|
||||
assert.Equal(t, []byte("test"), k.public, "Public key did not match expected")
|
||||
|
||||
}
|
||||
|
|
|
@ -17,28 +17,28 @@ import (
|
|||
// found. It attempts to handle the race condition that may occur if 2 servers try to
|
||||
// create the key at the same time by simply querying the store a second time if it
|
||||
// receives a conflict when writing.
|
||||
func GetOrCreateTimestampKey(gun string, store storage.MetaStore, crypto signed.CryptoService) (*data.TUFKey, error) {
|
||||
cipher, public, err := store.GetTimestampKey(gun)
|
||||
func GetOrCreateTimestampKey(gun string, store storage.MetaStore, crypto signed.CryptoService, fallBackAlgorithm data.KeyAlgorithm) (*data.TUFKey, error) {
|
||||
keyAlgorithm, public, err := store.GetTimestampKey(gun)
|
||||
if err == nil {
|
||||
return data.NewTUFKey(cipher, public, nil), nil
|
||||
return data.NewTUFKey(keyAlgorithm, public, nil), nil
|
||||
}
|
||||
|
||||
if _, ok := err.(*storage.ErrNoKey); ok {
|
||||
key, err := crypto.Create("timestamp")
|
||||
key, err := crypto.Create("timestamp", fallBackAlgorithm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = store.SetTimestampKey(gun, key.Cipher(), key.Public())
|
||||
err = store.SetTimestampKey(gun, key.Algorithm(), key.Public())
|
||||
if err == nil {
|
||||
return &key.TUFKey, nil
|
||||
}
|
||||
|
||||
if _, ok := err.(*storage.ErrTimestampKeyExists); ok {
|
||||
cipher, public, err = store.GetTimestampKey(gun)
|
||||
keyAlgorithm, public, err = store.GetTimestampKey(gun)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return data.NewTUFKey(cipher, public, nil), nil
|
||||
return data.NewTUFKey(keyAlgorithm, public, nil), nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
@ -96,13 +96,13 @@ func timestampExpired(ts *data.SignedTimestamp) bool {
|
|||
// version number one higher than prev. The store is used to lookup the current
|
||||
// snapshot, this function does not save the newly generated timestamp.
|
||||
func createTimestamp(gun string, prev *data.SignedTimestamp, store storage.MetaStore, signer *signed.Signer) (*data.Signed, int, error) {
|
||||
cipher, public, err := store.GetTimestampKey(gun)
|
||||
algorithm, public, err := store.GetTimestampKey(gun)
|
||||
if err != nil {
|
||||
// owner of gun must have generated a timestamp key otherwise
|
||||
// we won't proceed with generating everything.
|
||||
return nil, 0, err
|
||||
}
|
||||
key := data.NewPublicKey(cipher, public)
|
||||
key := data.NewPublicKey(algorithm, public)
|
||||
snapshot, err := store.GetCurrent(gun, "snapshot")
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
|
|
|
@ -32,11 +32,11 @@ func TestTimestampExpired(t *testing.T) {
|
|||
func TestGetTimestampKey(t *testing.T) {
|
||||
store := storage.NewMemStorage()
|
||||
crypto := signed.NewEd25519()
|
||||
k, err := GetOrCreateTimestampKey("gun", store, crypto)
|
||||
k, err := GetOrCreateTimestampKey("gun", store, crypto, data.ED25519Key)
|
||||
assert.Nil(t, err, "Expected nil error")
|
||||
assert.NotNil(t, k, "Key should not be nil")
|
||||
|
||||
k2, err := GetOrCreateTimestampKey("gun", store, crypto)
|
||||
k2, err := GetOrCreateTimestampKey("gun", store, crypto, data.ED25519Key)
|
||||
|
||||
assert.Nil(t, err, "Expected nil error")
|
||||
|
||||
|
@ -55,7 +55,7 @@ func TestGetTimestamp(t *testing.T) {
|
|||
|
||||
store.UpdateCurrent("gun", "snapshot", 0, snapJSON)
|
||||
// create a key to be used by GetTimestamp
|
||||
_, err := GetOrCreateTimestampKey("gun", store, crypto)
|
||||
_, err := GetOrCreateTimestampKey("gun", store, crypto, data.ED25519Key)
|
||||
assert.Nil(t, err, "GetTimestampKey errored")
|
||||
|
||||
_, err = GetOrCreateTimestamp("gun", store, signer)
|
||||
|
|
|
@ -54,21 +54,22 @@ func (trust *RufusSigner) Sign(keyIDs []string, toSign []byte) ([]data.Signature
|
|||
}
|
||||
signatures = append(signatures, data.Signature{
|
||||
KeyID: sig.KeyID.ID,
|
||||
Method: sig.Algorithm.Algorithm,
|
||||
Method: data.SigAlgorithm(sig.Algorithm.Algorithm),
|
||||
Signature: sig.Content,
|
||||
})
|
||||
}
|
||||
return signatures, nil
|
||||
}
|
||||
|
||||
// TODO(diogo): Ignoring algorithm for now until rufus supports it
|
||||
// Create creates a remote key and returns the PublicKey associated with the remote private key
|
||||
func (trust *RufusSigner) Create(role string) (*data.PublicKey, error) {
|
||||
func (trust *RufusSigner) Create(role string, _ data.KeyAlgorithm) (*data.PublicKey, error) {
|
||||
publicKey, err := trust.kmClient.CreateKey(context.Background(), &pb.Void{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
//TODO(mccauley): Update API to return algorithm and/or take it as a param
|
||||
public := data.NewPublicKey(publicKey.Algorithm.Algorithm, publicKey.PublicKey)
|
||||
public := data.NewPublicKey(data.KeyAlgorithm(publicKey.Algorithm.Algorithm), publicKey.PublicKey)
|
||||
return public, nil
|
||||
}
|
||||
|
||||
|
@ -82,7 +83,7 @@ func (trust *RufusSigner) PublicKeys(keyIDs ...string) (map[string]*data.PublicK
|
|||
return nil, err
|
||||
}
|
||||
publicKeys[public.KeyID.ID] =
|
||||
data.NewPublicKey(public.Algorithm.Algorithm, public.PublicKey)
|
||||
data.NewPublicKey(data.KeyAlgorithm(public.Algorithm.Algorithm), public.PublicKey)
|
||||
}
|
||||
return publicKeys, nil
|
||||
}
|
||||
|
|
|
@ -100,7 +100,7 @@ func fingerprintCert(cert *x509.Certificate) (CertID, error) {
|
|||
block := pem.Block{Type: "CERTIFICATE", Bytes: cert.Raw}
|
||||
pemdata := pem.EncodeToMemory(&block)
|
||||
|
||||
keyType := ""
|
||||
var keyType data.KeyAlgorithm
|
||||
switch cert.PublicKeyAlgorithm {
|
||||
case x509.RSA:
|
||||
keyType = data.RSAx509Key
|
||||
|
@ -228,7 +228,7 @@ func GenerateRSAKey(random io.Reader, bits int) (*data.PrivateKey, error) {
|
|||
}
|
||||
|
||||
// RSAToPrivateKey converts an rsa.Private key to a TUF data.PrivateKey type
|
||||
func RSAToPrivateKey(rsaPrivKey *rsa.PrivateKey, keyType string) (*data.PrivateKey, error) {
|
||||
func RSAToPrivateKey(rsaPrivKey *rsa.PrivateKey, keyType data.KeyAlgorithm) (*data.PrivateKey, error) {
|
||||
// Get a DER-encoded representation of the PublicKey
|
||||
rsaPubBytes, err := x509.MarshalPKIXPublicKey(&rsaPrivKey.PublicKey)
|
||||
if err != nil {
|
||||
|
@ -261,7 +261,7 @@ func GenerateECDSAKey(random io.Reader) (*data.PrivateKey, error) {
|
|||
}
|
||||
|
||||
// ECDSAToPrivateKey converts an rsa.Private key to a TUF data.PrivateKey type
|
||||
func ECDSAToPrivateKey(ecdsaPrivKey *ecdsa.PrivateKey, keyType string) (*data.PrivateKey, error) {
|
||||
func ECDSAToPrivateKey(ecdsaPrivKey *ecdsa.PrivateKey, keyType data.KeyAlgorithm) (*data.PrivateKey, error) {
|
||||
// Get a DER-encoded representation of the PublicKey
|
||||
ecdsaPubBytes, err := x509.MarshalPKIXPublicKey(&ecdsaPrivKey.PublicKey)
|
||||
if err != nil {
|
||||
|
@ -280,15 +280,15 @@ func ECDSAToPrivateKey(ecdsaPrivKey *ecdsa.PrivateKey, keyType string) (*data.Pr
|
|||
// KeyToPEM returns a PEM encoded key from a Private Key
|
||||
func KeyToPEM(privKey *data.PrivateKey) ([]byte, error) {
|
||||
var pemType string
|
||||
cipher := privKey.Cipher()
|
||||
algorithm := privKey.Algorithm()
|
||||
|
||||
switch cipher {
|
||||
switch algorithm {
|
||||
case data.RSAKey:
|
||||
pemType = "RSA PRIVATE KEY"
|
||||
case data.ECDSAKey:
|
||||
pemType = "EC PRIVATE KEY"
|
||||
default:
|
||||
return nil, fmt.Errorf("only RSA or ECDSA keys are currently supported. Found: %s", cipher)
|
||||
return nil, fmt.Errorf("only RSA or ECDSA keys are currently supported. Found: %s", algorithm)
|
||||
}
|
||||
|
||||
return pem.EncodeToMemory(&pem.Block{Type: pemType, Bytes: privKey.Private()}), nil
|
||||
|
@ -298,15 +298,15 @@ func KeyToPEM(privKey *data.PrivateKey) ([]byte, error) {
|
|||
// and a passphrase
|
||||
func EncryptPrivateKey(key *data.PrivateKey, passphrase string) ([]byte, error) {
|
||||
var blockType string
|
||||
cipher := key.Cipher()
|
||||
algorithm := key.Algorithm()
|
||||
|
||||
switch cipher {
|
||||
switch algorithm {
|
||||
case data.RSAKey:
|
||||
blockType = "RSA PRIVATE KEY"
|
||||
case data.ECDSAKey:
|
||||
blockType = "EC PRIVATE KEY"
|
||||
default:
|
||||
return nil, fmt.Errorf("only RSA or ECDSA keys are currently supported. Found: %s", cipher)
|
||||
return nil, fmt.Errorf("only RSA or ECDSA keys are currently supported. Found: %s", algorithm)
|
||||
}
|
||||
|
||||
password := []byte(passphrase)
|
||||
|
|
Loading…
Reference in New Issue