mirror of https://github.com/docker/docs.git
Merge pull request #62 from docker/import-export
Add key import and export features
This commit is contained in:
commit
c54f2d0768
|
@ -95,7 +95,7 @@ func NewNotaryRepository(baseDir, gun, baseURL string, rt http.RoundTripper) (*N
|
|||
gun: gun,
|
||||
baseDir: baseDir,
|
||||
baseURL: baseURL,
|
||||
tufRepoPath: filepath.Join(baseDir, tufDir, gun),
|
||||
tufRepoPath: filepath.Join(baseDir, tufDir, filepath.FromSlash(gun)),
|
||||
cryptoService: cryptoService,
|
||||
roundTrip: rt,
|
||||
KeyStoreManager: keyStoreManager,
|
||||
|
|
|
@ -75,13 +75,13 @@ func testInitRepo(t *testing.T, rootType data.KeyAlgorithm) {
|
|||
// Inspect contents of the temporary directory
|
||||
expectedDirs := []string{
|
||||
"private",
|
||||
filepath.Join("private", gun),
|
||||
filepath.Join("private", "tuf_keys", filepath.FromSlash(gun)),
|
||||
filepath.Join("private", "root_keys"),
|
||||
"trusted_certificates",
|
||||
filepath.Join("trusted_certificates", gun),
|
||||
filepath.Join("trusted_certificates", filepath.FromSlash(gun)),
|
||||
"tuf",
|
||||
filepath.Join("tuf", gun, "metadata"),
|
||||
filepath.Join("tuf", gun, "targets"),
|
||||
filepath.Join("tuf", filepath.FromSlash(gun), "metadata"),
|
||||
filepath.Join("tuf", filepath.FromSlash(gun), "targets"),
|
||||
}
|
||||
for _, dir := range expectedDirs {
|
||||
fi, err := os.Stat(filepath.Join(tempBaseDir, dir))
|
||||
|
@ -118,16 +118,16 @@ func testInitRepo(t *testing.T, rootType data.KeyAlgorithm) {
|
|||
assert.Equal(t, rootKeyFilename, actualDest, "symlink to root key has wrong destination")
|
||||
|
||||
// There should be a trusted certificate
|
||||
_, err = os.Stat(filepath.Join(tempBaseDir, "trusted_certificates", gun, certID+".crt"))
|
||||
_, err = os.Stat(filepath.Join(tempBaseDir, "trusted_certificates", filepath.FromSlash(gun), certID+".crt"))
|
||||
assert.NoError(t, err, "missing trusted certificate")
|
||||
|
||||
// Sanity check the TUF metadata files. Verify that they exist, the JSON is
|
||||
// well-formed, and the signatures exist. For the root.json file, also check
|
||||
// that the root, snapshot, and targets key IDs are present.
|
||||
expectedTUFMetadataFiles := []string{
|
||||
filepath.Join("tuf", gun, "metadata", "root.json"),
|
||||
filepath.Join("tuf", gun, "metadata", "snapshot.json"),
|
||||
filepath.Join("tuf", gun, "metadata", "targets.json"),
|
||||
filepath.Join("tuf", filepath.FromSlash(gun), "metadata", "root.json"),
|
||||
filepath.Join("tuf", filepath.FromSlash(gun), "metadata", "snapshot.json"),
|
||||
filepath.Join("tuf", filepath.FromSlash(gun), "metadata", "targets.json"),
|
||||
}
|
||||
for _, filename := range expectedTUFMetadataFiles {
|
||||
fullPath := filepath.Join(tempBaseDir, filename)
|
||||
|
@ -225,7 +225,7 @@ func testAddListTarget(t *testing.T, rootType data.KeyAlgorithm) {
|
|||
assert.NoError(t, err, "error adding target")
|
||||
|
||||
// Look for the changelist file
|
||||
changelistDirPath := filepath.Join(tempBaseDir, "tuf", gun, "changelist")
|
||||
changelistDirPath := filepath.Join(tempBaseDir, "tuf", filepath.FromSlash(gun), "changelist")
|
||||
|
||||
changelistDir, err := os.Open(changelistDirPath)
|
||||
assert.NoError(t, err, "could not open changelist directory")
|
||||
|
@ -299,7 +299,7 @@ func testAddListTarget(t *testing.T, rootType data.KeyAlgorithm) {
|
|||
// Apply the changelist. Normally, this would be done by Publish
|
||||
|
||||
// load the changelist for this repo
|
||||
cl, err := changelist.NewFileChangelist(filepath.Join(tempBaseDir, "tuf", gun, "changelist"))
|
||||
cl, err := changelist.NewFileChangelist(filepath.Join(tempBaseDir, "tuf", filepath.FromSlash(gun), "changelist"))
|
||||
assert.NoError(t, err, "could not open changelist")
|
||||
|
||||
// apply the changelist to the repo
|
||||
|
@ -309,10 +309,10 @@ func testAddListTarget(t *testing.T, rootType data.KeyAlgorithm) {
|
|||
var tempKey data.PrivateKey
|
||||
json.Unmarshal([]byte(timestampECDSAKeyJSON), &tempKey)
|
||||
|
||||
repo.KeyStoreManager.NonRootKeyStore().AddKey(filepath.Join(gun, tempKey.ID()), &tempKey)
|
||||
repo.KeyStoreManager.NonRootKeyStore().AddKey(filepath.Join(filepath.FromSlash(gun), tempKey.ID()), &tempKey)
|
||||
|
||||
mux.HandleFunc("/v2/docker.com/notary/_trust/tuf/root.json", func(w http.ResponseWriter, r *http.Request) {
|
||||
rootJSONFile := filepath.Join(tempBaseDir, "tuf", gun, "metadata", "root.json")
|
||||
rootJSONFile := filepath.Join(tempBaseDir, "tuf", filepath.FromSlash(gun), "metadata", "root.json")
|
||||
rootFileBytes, err := ioutil.ReadFile(rootJSONFile)
|
||||
assert.NoError(t, err)
|
||||
fmt.Fprint(w, string(rootFileBytes))
|
||||
|
@ -401,7 +401,7 @@ func testValidateRootKey(t *testing.T, rootType data.KeyAlgorithm) {
|
|||
err = repo.Initialize(rootCryptoService)
|
||||
assert.NoError(t, err, "error creating repository: %s", err)
|
||||
|
||||
rootJSONFile := filepath.Join(tempBaseDir, "tuf", gun, "metadata", "root.json")
|
||||
rootJSONFile := filepath.Join(tempBaseDir, "tuf", filepath.FromSlash(gun), "metadata", "root.json")
|
||||
|
||||
jsonBytes, err := ioutil.ReadFile(rootJSONFile)
|
||||
assert.NoError(t, err, "error reading TUF metadata file %s: %s", rootJSONFile, err)
|
||||
|
|
|
@ -0,0 +1,350 @@
|
|||
package keystoremanager
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/docker/notary/trustmanager"
|
||||
"github.com/endophage/gotuf/data"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrNoValidPrivateKey is returned if a key being imported doesn't
|
||||
// look like a private key
|
||||
ErrNoValidPrivateKey = errors.New("no valid private key found")
|
||||
|
||||
// ErrRootKeyNotEncrypted is returned if a root key being imported is
|
||||
// unencrypted
|
||||
ErrRootKeyNotEncrypted = errors.New("only encrypted root keys may be imported")
|
||||
|
||||
// ErrNonRootKeyEncrypted is returned if a non-root key is found to
|
||||
// be encrypted while exporting
|
||||
ErrNonRootKeyEncrypted = errors.New("found encrypted non-root key")
|
||||
|
||||
// ErrNoKeysFoundForGUN is returned if no keys are found for the
|
||||
// specified GUN during export
|
||||
ErrNoKeysFoundForGUN = errors.New("no keys found for specified GUN")
|
||||
)
|
||||
|
||||
// ExportRootKey exports the specified root key to an io.Writer in PEM format.
|
||||
// The key's existing encryption is preserved.
|
||||
func (km *KeyStoreManager) ExportRootKey(dest io.Writer, keyID string) error {
|
||||
pemBytes, err := km.rootKeyStore.Get(keyID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = dest.Write(pemBytes)
|
||||
return err
|
||||
}
|
||||
|
||||
// checkRootKeyIsEncrypted makes sure the root key is encrypted. We have
|
||||
// internal assumptions that depend on this.
|
||||
func checkRootKeyIsEncrypted(pemBytes []byte) error {
|
||||
block, _ := pem.Decode(pemBytes)
|
||||
if block == nil {
|
||||
return ErrNoValidPrivateKey
|
||||
}
|
||||
|
||||
if !x509.IsEncryptedPEMBlock(block) {
|
||||
return ErrRootKeyNotEncrypted
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ImportRootKey imports a root in PEM format key from an io.Reader
|
||||
// The key's existing encryption is preserved. The keyID parameter is
|
||||
// necessary because otherwise we'd need the passphrase to decrypt the key
|
||||
// in order to compute the ID.
|
||||
func (km *KeyStoreManager) ImportRootKey(source io.Reader, keyID string) error {
|
||||
pemBytes, err := ioutil.ReadAll(source)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = checkRootKeyIsEncrypted(pemBytes); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = km.rootKeyStore.Add(keyID, pemBytes); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func moveKeysWithNewPassphrase(oldKeyStore, newKeyStore *trustmanager.KeyFileStore, outputPassphrase string) error {
|
||||
// List all files but no symlinks
|
||||
for _, f := range oldKeyStore.ListFiles(false) {
|
||||
fullKeyPath := strings.TrimSpace(strings.TrimSuffix(f, filepath.Ext(f)))
|
||||
relKeyPath := strings.TrimPrefix(fullKeyPath, oldKeyStore.BaseDir())
|
||||
relKeyPath = strings.TrimPrefix(relKeyPath, string(filepath.Separator))
|
||||
|
||||
pemBytes, err := oldKeyStore.Get(relKeyPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
block, _ := pem.Decode(pemBytes)
|
||||
if block == nil {
|
||||
return ErrNoValidPrivateKey
|
||||
}
|
||||
|
||||
if !x509.IsEncryptedPEMBlock(block) {
|
||||
// Key is not encrypted. Parse it, and add it
|
||||
// to the temporary store as an encrypted key.
|
||||
privKey, err := trustmanager.ParsePEMPrivateKey(pemBytes, "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = newKeyStore.AddEncryptedKey(relKeyPath, privKey, outputPassphrase)
|
||||
} else {
|
||||
// Encrypted key - pass it through without
|
||||
// decrypting
|
||||
err = newKeyStore.Add(relKeyPath, pemBytes)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func addKeysToArchive(zipWriter *zip.Writer, newKeyStore *trustmanager.KeyFileStore, tempBaseDir string) error {
|
||||
// List all files but no symlinks
|
||||
for _, fullKeyPath := range newKeyStore.ListFiles(false) {
|
||||
relKeyPath := strings.TrimPrefix(fullKeyPath, tempBaseDir)
|
||||
relKeyPath = strings.TrimPrefix(relKeyPath, string(filepath.Separator))
|
||||
|
||||
fi, err := os.Stat(fullKeyPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
infoHeader, err := zip.FileInfoHeader(fi)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
infoHeader.Name = relKeyPath
|
||||
zipFileEntryWriter, err := zipWriter.CreateHeader(infoHeader)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fileContents, err := ioutil.ReadFile(fullKeyPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err = zipFileEntryWriter.Write(fileContents); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ExportAllKeys exports all keys to an io.Writer in zip format.
|
||||
// outputPassphrase is the new passphrase to use to encrypt the existing keys.
|
||||
// If blank, the keys will not be encrypted. Note that keys which are already
|
||||
// encrypted are not re-encrypted. They will be included in the zip with their
|
||||
// original encryption.
|
||||
func (km *KeyStoreManager) ExportAllKeys(dest io.Writer, outputPassphrase string) error {
|
||||
tempBaseDir, err := ioutil.TempDir("", "notary-key-export-")
|
||||
defer os.RemoveAll(tempBaseDir)
|
||||
|
||||
// Create temporary keystores to use as a staging area
|
||||
tempNonRootKeysPath := filepath.Join(tempBaseDir, privDir, nonRootKeysSubdir)
|
||||
tempNonRootKeyStore, err := trustmanager.NewKeyFileStore(tempNonRootKeysPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tempRootKeysPath := filepath.Join(tempBaseDir, privDir, rootKeysSubdir)
|
||||
tempRootKeyStore, err := trustmanager.NewKeyFileStore(tempRootKeysPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := moveKeysWithNewPassphrase(km.rootKeyStore, tempRootKeyStore, outputPassphrase); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := moveKeysWithNewPassphrase(km.nonRootKeyStore, tempNonRootKeyStore, outputPassphrase); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
zipWriter := zip.NewWriter(dest)
|
||||
|
||||
if err := addKeysToArchive(zipWriter, tempRootKeyStore, tempBaseDir); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := addKeysToArchive(zipWriter, tempNonRootKeyStore, tempBaseDir); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
zipWriter.Close()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ImportKeysZip imports keys from a zip file provided as an io.ReaderAt. The
|
||||
// keys in the root_keys directory are left encrypted, but the other keys are
|
||||
// decrypted with the specified passphrase.
|
||||
func (km *KeyStoreManager) ImportKeysZip(zipReader zip.Reader, passphrase string) error {
|
||||
// Temporarily store the keys in maps, so we can bail early if there's
|
||||
// an error (for example, wrong passphrase), without leaving the key
|
||||
// store in an inconsistent state
|
||||
newRootKeys := make(map[string][]byte)
|
||||
newNonRootKeys := make(map[string]*data.PrivateKey)
|
||||
|
||||
// Note that using / as a separator is okay here - the zip package
|
||||
// guarantees that the separator will be /
|
||||
rootKeysPrefix := privDir + "/" + rootKeysSubdir + "/"
|
||||
nonRootKeysPrefix := privDir + "/" + nonRootKeysSubdir + "/"
|
||||
|
||||
// Iterate through the files in the archive. Don't add the keys
|
||||
for _, f := range zipReader.File {
|
||||
fNameTrimmed := strings.TrimSuffix(f.Name, filepath.Ext(f.Name))
|
||||
|
||||
rc, err := f.Open()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
pemBytes, err := ioutil.ReadAll(rc)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Is this in the root_keys directory?
|
||||
// Note that using / as a separator is okay here - the zip
|
||||
// package guarantees that the separator will be /
|
||||
if strings.HasPrefix(fNameTrimmed, rootKeysPrefix) {
|
||||
if err = checkRootKeyIsEncrypted(pemBytes); err != nil {
|
||||
rc.Close()
|
||||
return err
|
||||
}
|
||||
// Root keys are preserved without decrypting
|
||||
keyName := strings.TrimPrefix(fNameTrimmed, rootKeysPrefix)
|
||||
newRootKeys[keyName] = pemBytes
|
||||
} else if strings.HasPrefix(fNameTrimmed, nonRootKeysPrefix) {
|
||||
// Non-root keys need to be decrypted
|
||||
key, err := trustmanager.ParsePEMPrivateKey(pemBytes, passphrase)
|
||||
if err != nil {
|
||||
rc.Close()
|
||||
return err
|
||||
}
|
||||
keyName := strings.TrimPrefix(fNameTrimmed, nonRootKeysPrefix)
|
||||
newNonRootKeys[keyName] = key
|
||||
} else {
|
||||
// This path inside the zip archive doesn't look like a
|
||||
// root key or a non-root key. To avoid adding a file
|
||||
// to the filestore that we won't be able to use, skip
|
||||
// this file in the import.
|
||||
logrus.Warnf("skipping import of key with a path that doesn't begin with %s or %s: %s", rootKeysPrefix, nonRootKeysPrefix, f.Name)
|
||||
rc.Close()
|
||||
continue
|
||||
}
|
||||
|
||||
rc.Close()
|
||||
}
|
||||
|
||||
for keyName, pemBytes := range newRootKeys {
|
||||
if err := km.rootKeyStore.Add(keyName, pemBytes); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
for keyName, privKey := range newNonRootKeys {
|
||||
if err := km.nonRootKeyStore.AddKey(keyName, privKey); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func moveKeysByGUN(oldKeyStore, newKeyStore *trustmanager.KeyFileStore, gun, outputPassphrase string) error {
|
||||
// List all files but no symlinks
|
||||
for _, f := range oldKeyStore.ListFiles(false) {
|
||||
fullKeyPath := strings.TrimSpace(strings.TrimSuffix(f, filepath.Ext(f)))
|
||||
relKeyPath := strings.TrimPrefix(fullKeyPath, oldKeyStore.BaseDir())
|
||||
relKeyPath = strings.TrimPrefix(relKeyPath, string(filepath.Separator))
|
||||
|
||||
// Skip keys that aren't associated with this GUN
|
||||
if !strings.HasPrefix(relKeyPath, filepath.FromSlash(gun)) {
|
||||
continue
|
||||
}
|
||||
|
||||
pemBytes, err := oldKeyStore.Get(relKeyPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
block, _ := pem.Decode(pemBytes)
|
||||
if block == nil {
|
||||
return ErrNoValidPrivateKey
|
||||
}
|
||||
|
||||
if x509.IsEncryptedPEMBlock(block) {
|
||||
return ErrNonRootKeyEncrypted
|
||||
}
|
||||
|
||||
// Key is not encrypted. Parse it, and add it
|
||||
// to the temporary store as an encrypted key.
|
||||
privKey, err := trustmanager.ParsePEMPrivateKey(pemBytes, "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = newKeyStore.AddEncryptedKey(relKeyPath, privKey, outputPassphrase)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ExportKeysByGUN exports all keys associated with a specified GUN to an
|
||||
// io.Writer in zip format. outputPassphrase is the new passphrase to use to
|
||||
// encrypt the keys. If blank, the keys will not be encrypted.
|
||||
func (km *KeyStoreManager) ExportKeysByGUN(dest io.Writer, gun, outputPassphrase string) error {
|
||||
tempBaseDir, err := ioutil.TempDir("", "notary-key-export-")
|
||||
defer os.RemoveAll(tempBaseDir)
|
||||
|
||||
// Create temporary keystore to use as a staging area
|
||||
tempNonRootKeysPath := filepath.Join(tempBaseDir, privDir, nonRootKeysSubdir)
|
||||
tempNonRootKeyStore, err := trustmanager.NewKeyFileStore(tempNonRootKeysPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := moveKeysByGUN(km.nonRootKeyStore, tempNonRootKeyStore, gun, outputPassphrase); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
zipWriter := zip.NewWriter(dest)
|
||||
|
||||
if len(tempNonRootKeyStore.ListKeys()) == 0 {
|
||||
return ErrNoKeysFoundForGUN
|
||||
}
|
||||
|
||||
if err := addKeysToArchive(zipWriter, tempNonRootKeyStore, tempBaseDir); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
zipWriter.Close()
|
||||
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,392 @@
|
|||
package keystoremanager_test
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/notary/client"
|
||||
"github.com/docker/notary/keystoremanager"
|
||||
"github.com/docker/notary/trustmanager"
|
||||
"github.com/endophage/gotuf/data"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
const timestampECDSAKeyJSON = `
|
||||
{"keytype":"ecdsa","keyval":{"public":"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEgl3rzMPMEKhS1k/AX16MM4PdidpjJr+z4pj0Td+30QnpbOIARgpyR1PiFztU8BZlqG3cUazvFclr2q/xHvfrqw==","private":"MHcCAQEEIDqtcdzU7H3AbIPSQaxHl9+xYECt7NpK7B1+6ep5cv9CoAoGCCqGSM49AwEHoUQDQgAEgl3rzMPMEKhS1k/AX16MM4PdidpjJr+z4pj0Td+30QnpbOIARgpyR1PiFztU8BZlqG3cUazvFclr2q/xHvfrqw=="}}`
|
||||
|
||||
func createTestServer(t *testing.T) (*httptest.Server, *http.ServeMux) {
|
||||
mux := http.NewServeMux()
|
||||
// TUF will request /v2/docker.com/notary/_trust/tuf/timestamp.key
|
||||
// Return a canned timestamp.key
|
||||
mux.HandleFunc("/v2/docker.com/notary/_trust/tuf/timestamp.key", func(w http.ResponseWriter, r *http.Request) {
|
||||
// Also contains the private key, but for the purpose of this
|
||||
// test, we don't care
|
||||
fmt.Fprint(w, timestampECDSAKeyJSON)
|
||||
})
|
||||
|
||||
ts := httptest.NewServer(mux)
|
||||
|
||||
return ts, mux
|
||||
}
|
||||
|
||||
func TestImportExportZip(t *testing.T) {
|
||||
gun := "docker.com/notary"
|
||||
oldPassphrase := "oldPassphrase"
|
||||
exportPassphrase := "exportPassphrase"
|
||||
|
||||
// Temporary directory where test files will be created
|
||||
tempBaseDir, err := ioutil.TempDir("", "notary-test-")
|
||||
defer os.RemoveAll(tempBaseDir)
|
||||
|
||||
assert.NoError(t, err, "failed to create a temporary directory: %s", err)
|
||||
|
||||
ts, _ := createTestServer(t)
|
||||
defer ts.Close()
|
||||
|
||||
repo, err := client.NewNotaryRepository(tempBaseDir, gun, ts.URL, http.DefaultTransport)
|
||||
assert.NoError(t, err, "error creating repo: %s", err)
|
||||
|
||||
rootKeyID, err := repo.KeyStoreManager.GenRootKey(data.ECDSAKey.String(), oldPassphrase)
|
||||
assert.NoError(t, err, "error generating root key: %s", err)
|
||||
|
||||
rootCryptoService, err := repo.KeyStoreManager.GetRootCryptoService(rootKeyID, oldPassphrase)
|
||||
assert.NoError(t, err, "error retrieving root key: %s", err)
|
||||
|
||||
err = repo.Initialize(rootCryptoService)
|
||||
assert.NoError(t, err, "error creating repository: %s", err)
|
||||
|
||||
tempZipFile, err := ioutil.TempFile("", "notary-test-export-")
|
||||
tempZipFilePath := tempZipFile.Name()
|
||||
defer os.Remove(tempZipFilePath)
|
||||
|
||||
err = repo.KeyStoreManager.ExportAllKeys(tempZipFile, exportPassphrase)
|
||||
tempZipFile.Close()
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Reopen the zip file for importing
|
||||
zipReader, err := zip.OpenReader(tempZipFilePath)
|
||||
assert.NoError(t, err, "could not open zip file")
|
||||
|
||||
// Map of files to expect in the zip file, with the passphrases
|
||||
passphraseByFile := make(map[string]string)
|
||||
|
||||
// Add non-root keys to the map. These should use the new passphrase
|
||||
// because they were formerly unencrypted.
|
||||
privKeyList := repo.KeyStoreManager.NonRootKeyStore().ListFiles(false)
|
||||
for _, privKeyName := range privKeyList {
|
||||
relName := strings.TrimPrefix(privKeyName, tempBaseDir+string(filepath.Separator))
|
||||
passphraseByFile[relName] = exportPassphrase
|
||||
}
|
||||
|
||||
// Add root key to the map. This will use the old passphrase because it
|
||||
// won't be reencrypted.
|
||||
relRootKey := filepath.Join("private", "root_keys", rootCryptoService.ID()+".key")
|
||||
passphraseByFile[relRootKey] = oldPassphrase
|
||||
|
||||
// Iterate through the files in the archive, checking that the files
|
||||
// exist and are encrypted with the expected passphrase.
|
||||
for _, f := range zipReader.File {
|
||||
expectedPassphrase, present := passphraseByFile[f.Name]
|
||||
if !present {
|
||||
t.Fatalf("unexpected file %s in zip file", f.Name)
|
||||
}
|
||||
|
||||
delete(passphraseByFile, f.Name)
|
||||
|
||||
rc, err := f.Open()
|
||||
assert.NoError(t, err, "could not open file inside zip archive")
|
||||
|
||||
pemBytes, err := ioutil.ReadAll(rc)
|
||||
assert.NoError(t, err, "could not read file from zip")
|
||||
|
||||
_, err = trustmanager.ParsePEMPrivateKey(pemBytes, expectedPassphrase)
|
||||
assert.NoError(t, err, "PEM not encrypted with the expected passphrase")
|
||||
|
||||
rc.Close()
|
||||
}
|
||||
|
||||
zipReader.Close()
|
||||
|
||||
// Are there any keys that didn't make it to the zip?
|
||||
for fileNotFound := range passphraseByFile {
|
||||
t.Fatalf("%s not found in zip", fileNotFound)
|
||||
}
|
||||
|
||||
// Create new repo to test import
|
||||
tempBaseDir2, err := ioutil.TempDir("", "notary-test-")
|
||||
defer os.RemoveAll(tempBaseDir2)
|
||||
|
||||
assert.NoError(t, err, "failed to create a temporary directory: %s", err)
|
||||
|
||||
repo2, err := client.NewNotaryRepository(tempBaseDir2, gun, ts.URL, http.DefaultTransport)
|
||||
assert.NoError(t, err, "error creating repo: %s", err)
|
||||
|
||||
rootKeyID2, err := repo2.KeyStoreManager.GenRootKey(data.ECDSAKey.String(), "oldPassphrase")
|
||||
assert.NoError(t, err, "error generating root key: %s", err)
|
||||
|
||||
rootCryptoService2, err := repo2.KeyStoreManager.GetRootCryptoService(rootKeyID2, "oldPassphrase")
|
||||
assert.NoError(t, err, "error retrieving root key: %s", err)
|
||||
|
||||
err = repo2.Initialize(rootCryptoService2)
|
||||
assert.NoError(t, err, "error creating repository: %s", err)
|
||||
|
||||
// Reopen the zip file for importing
|
||||
zipReader, err = zip.OpenReader(tempZipFilePath)
|
||||
assert.NoError(t, err, "could not open zip file")
|
||||
|
||||
// First try with an incorrect passphrase
|
||||
err = repo2.KeyStoreManager.ImportKeysZip(zipReader.Reader, "wrongpassphrase")
|
||||
// Don't use EqualError here because occasionally decrypting with the
|
||||
// wrong passphrase returns a parse error
|
||||
assert.Error(t, err)
|
||||
zipReader.Close()
|
||||
|
||||
// Reopen the zip file for importing
|
||||
zipReader, err = zip.OpenReader(tempZipFilePath)
|
||||
assert.NoError(t, err, "could not open zip file")
|
||||
|
||||
// Now try with a valid passphrase. This time it should succeed.
|
||||
err = repo2.KeyStoreManager.ImportKeysZip(zipReader.Reader, exportPassphrase)
|
||||
assert.NoError(t, err)
|
||||
zipReader.Close()
|
||||
|
||||
// Look for repo's keys in repo2
|
||||
|
||||
// Look for keys in private. The filenames should match the key IDs
|
||||
// in the repo's private key store.
|
||||
for _, privKeyName := range privKeyList {
|
||||
privKeyRel := strings.TrimPrefix(privKeyName, tempBaseDir)
|
||||
_, err := os.Stat(filepath.Join(tempBaseDir2, privKeyRel))
|
||||
assert.NoError(t, err, "missing private key: %s", privKeyName)
|
||||
}
|
||||
|
||||
// Look for keys in root_keys
|
||||
// There should be a file named after the key ID of the root key we
|
||||
// passed in.
|
||||
rootKeyFilename := rootCryptoService.ID() + ".key"
|
||||
_, err = os.Stat(filepath.Join(tempBaseDir2, "private", "root_keys", rootKeyFilename))
|
||||
assert.NoError(t, err, "missing root key")
|
||||
}
|
||||
|
||||
func TestImportExportGUN(t *testing.T) {
|
||||
gun := "docker.com/notary"
|
||||
oldPassphrase := "oldPassphrase"
|
||||
exportPassphrase := "exportPassphrase"
|
||||
|
||||
// Temporary directory where test files will be created
|
||||
tempBaseDir, err := ioutil.TempDir("", "notary-test-")
|
||||
defer os.RemoveAll(tempBaseDir)
|
||||
|
||||
assert.NoError(t, err, "failed to create a temporary directory: %s", err)
|
||||
|
||||
ts, _ := createTestServer(t)
|
||||
defer ts.Close()
|
||||
|
||||
repo, err := client.NewNotaryRepository(tempBaseDir, gun, ts.URL, http.DefaultTransport)
|
||||
assert.NoError(t, err, "error creating repo: %s", err)
|
||||
|
||||
rootKeyID, err := repo.KeyStoreManager.GenRootKey(data.ECDSAKey.String(), oldPassphrase)
|
||||
assert.NoError(t, err, "error generating root key: %s", err)
|
||||
|
||||
rootCryptoService, err := repo.KeyStoreManager.GetRootCryptoService(rootKeyID, oldPassphrase)
|
||||
assert.NoError(t, err, "error retrieving root key: %s", err)
|
||||
|
||||
err = repo.Initialize(rootCryptoService)
|
||||
assert.NoError(t, err, "error creating repository: %s", err)
|
||||
|
||||
tempZipFile, err := ioutil.TempFile("", "notary-test-export-")
|
||||
tempZipFilePath := tempZipFile.Name()
|
||||
defer os.Remove(tempZipFilePath)
|
||||
|
||||
err = repo.KeyStoreManager.ExportKeysByGUN(tempZipFile, gun, exportPassphrase)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// With an invalid GUN, this should return an error
|
||||
err = repo.KeyStoreManager.ExportKeysByGUN(tempZipFile, "does.not.exist/in/repository", exportPassphrase)
|
||||
assert.EqualError(t, err, keystoremanager.ErrNoKeysFoundForGUN.Error())
|
||||
|
||||
tempZipFile.Close()
|
||||
|
||||
// Reopen the zip file for importing
|
||||
zipReader, err := zip.OpenReader(tempZipFilePath)
|
||||
assert.NoError(t, err, "could not open zip file")
|
||||
|
||||
// Map of files to expect in the zip file, with the passphrases
|
||||
passphraseByFile := make(map[string]string)
|
||||
|
||||
// Add keys non-root keys to the map. These should use the new passphrase
|
||||
// because they were formerly unencrypted.
|
||||
privKeyList := repo.KeyStoreManager.NonRootKeyStore().ListFiles(false)
|
||||
for _, privKeyName := range privKeyList {
|
||||
relName := strings.TrimPrefix(privKeyName, tempBaseDir+string(filepath.Separator))
|
||||
passphraseByFile[relName] = exportPassphrase
|
||||
}
|
||||
|
||||
// Iterate through the files in the archive, checking that the files
|
||||
// exist and are encrypted with the expected passphrase.
|
||||
for _, f := range zipReader.File {
|
||||
expectedPassphrase, present := passphraseByFile[f.Name]
|
||||
if !present {
|
||||
t.Fatalf("unexpected file %s in zip file", f.Name)
|
||||
}
|
||||
|
||||
delete(passphraseByFile, f.Name)
|
||||
|
||||
rc, err := f.Open()
|
||||
assert.NoError(t, err, "could not open file inside zip archive")
|
||||
|
||||
pemBytes, err := ioutil.ReadAll(rc)
|
||||
assert.NoError(t, err, "could not read file from zip")
|
||||
|
||||
_, err = trustmanager.ParsePEMPrivateKey(pemBytes, expectedPassphrase)
|
||||
assert.NoError(t, err, "PEM not encrypted with the expected passphrase")
|
||||
|
||||
rc.Close()
|
||||
}
|
||||
|
||||
zipReader.Close()
|
||||
|
||||
// Are there any keys that didn't make it to the zip?
|
||||
for fileNotFound := range passphraseByFile {
|
||||
t.Fatalf("%s not found in zip", fileNotFound)
|
||||
}
|
||||
|
||||
// Create new repo to test import
|
||||
tempBaseDir2, err := ioutil.TempDir("", "notary-test-")
|
||||
defer os.RemoveAll(tempBaseDir2)
|
||||
|
||||
assert.NoError(t, err, "failed to create a temporary directory: %s", err)
|
||||
|
||||
repo2, err := client.NewNotaryRepository(tempBaseDir2, gun, ts.URL, http.DefaultTransport)
|
||||
assert.NoError(t, err, "error creating repo: %s", err)
|
||||
|
||||
rootKeyID2, err := repo2.KeyStoreManager.GenRootKey(data.ECDSAKey.String(), "oldPassphrase")
|
||||
assert.NoError(t, err, "error generating root key: %s", err)
|
||||
|
||||
rootCryptoService2, err := repo2.KeyStoreManager.GetRootCryptoService(rootKeyID2, "oldPassphrase")
|
||||
assert.NoError(t, err, "error retrieving root key: %s", err)
|
||||
|
||||
err = repo2.Initialize(rootCryptoService2)
|
||||
assert.NoError(t, err, "error creating repository: %s", err)
|
||||
|
||||
// Reopen the zip file for importing
|
||||
zipReader, err = zip.OpenReader(tempZipFilePath)
|
||||
assert.NoError(t, err, "could not open zip file")
|
||||
|
||||
// First try with an incorrect passphrase
|
||||
err = repo2.KeyStoreManager.ImportKeysZip(zipReader.Reader, "wrongpassphrase")
|
||||
// Don't use EqualError here because occasionally decrypting with the
|
||||
// wrong passphrase returns a parse error
|
||||
assert.Error(t, err)
|
||||
zipReader.Close()
|
||||
|
||||
// Reopen the zip file for importing
|
||||
zipReader, err = zip.OpenReader(tempZipFilePath)
|
||||
assert.NoError(t, err, "could not open zip file")
|
||||
|
||||
// Now try with a valid passphrase. This time it should succeed.
|
||||
err = repo2.KeyStoreManager.ImportKeysZip(zipReader.Reader, exportPassphrase)
|
||||
assert.NoError(t, err)
|
||||
zipReader.Close()
|
||||
|
||||
// Look for repo's non-root keys in repo2
|
||||
|
||||
// Look for keys in private. The filenames should match the key IDs
|
||||
// in the repo's private key store.
|
||||
for _, privKeyName := range privKeyList {
|
||||
privKeyRel := strings.TrimPrefix(privKeyName, tempBaseDir)
|
||||
_, err := os.Stat(filepath.Join(tempBaseDir2, privKeyRel))
|
||||
assert.NoError(t, err, "missing private key: %s", privKeyName)
|
||||
}
|
||||
}
|
||||
|
||||
func TestImportExportRootKey(t *testing.T) {
|
||||
gun := "docker.com/notary"
|
||||
oldPassphrase := "oldPassphrase"
|
||||
|
||||
// Temporary directory where test files will be created
|
||||
tempBaseDir, err := ioutil.TempDir("", "notary-test-")
|
||||
defer os.RemoveAll(tempBaseDir)
|
||||
|
||||
assert.NoError(t, err, "failed to create a temporary directory: %s", err)
|
||||
|
||||
ts, _ := createTestServer(t)
|
||||
defer ts.Close()
|
||||
|
||||
repo, err := client.NewNotaryRepository(tempBaseDir, gun, ts.URL, http.DefaultTransport)
|
||||
assert.NoError(t, err, "error creating repo: %s", err)
|
||||
|
||||
rootKeyID, err := repo.KeyStoreManager.GenRootKey(data.ECDSAKey.String(), oldPassphrase)
|
||||
assert.NoError(t, err, "error generating root key: %s", err)
|
||||
|
||||
rootCryptoService, err := repo.KeyStoreManager.GetRootCryptoService(rootKeyID, oldPassphrase)
|
||||
assert.NoError(t, err, "error retrieving root key: %s", err)
|
||||
|
||||
err = repo.Initialize(rootCryptoService)
|
||||
assert.NoError(t, err, "error creating repository: %s", err)
|
||||
|
||||
tempKeyFile, err := ioutil.TempFile("", "notary-test-export-")
|
||||
tempKeyFilePath := tempKeyFile.Name()
|
||||
defer os.Remove(tempKeyFilePath)
|
||||
|
||||
err = repo.KeyStoreManager.ExportRootKey(tempKeyFile, rootKeyID)
|
||||
assert.NoError(t, err)
|
||||
tempKeyFile.Close()
|
||||
|
||||
// Create new repo to test import
|
||||
tempBaseDir2, err := ioutil.TempDir("", "notary-test-")
|
||||
defer os.RemoveAll(tempBaseDir2)
|
||||
|
||||
assert.NoError(t, err, "failed to create a temporary directory: %s", err)
|
||||
|
||||
repo2, err := client.NewNotaryRepository(tempBaseDir2, gun, ts.URL, http.DefaultTransport)
|
||||
assert.NoError(t, err, "error creating repo: %s", err)
|
||||
|
||||
rootKeyID2, err := repo2.KeyStoreManager.GenRootKey(data.ECDSAKey.String(), oldPassphrase)
|
||||
assert.NoError(t, err, "error generating root key: %s", err)
|
||||
|
||||
rootCryptoService2, err := repo2.KeyStoreManager.GetRootCryptoService(rootKeyID2, oldPassphrase)
|
||||
assert.NoError(t, err, "error retrieving root key: %s", err)
|
||||
|
||||
err = repo2.Initialize(rootCryptoService2)
|
||||
assert.NoError(t, err, "error creating repository: %s", err)
|
||||
|
||||
// Check the contents of the zip file
|
||||
keyReader, err := os.Open(tempKeyFilePath)
|
||||
assert.NoError(t, err, "could not open key file")
|
||||
|
||||
err = repo2.KeyStoreManager.ImportRootKey(keyReader, rootKeyID)
|
||||
assert.NoError(t, err)
|
||||
keyReader.Close()
|
||||
|
||||
// Look for repo's root key in repo2
|
||||
// There should be a file named after the key ID of the root key we
|
||||
// imported.
|
||||
rootKeyFilename := rootKeyID + ".key"
|
||||
_, err = os.Stat(filepath.Join(tempBaseDir2, "private", "root_keys", rootKeyFilename))
|
||||
assert.NoError(t, err, "missing root key")
|
||||
|
||||
// Try to import a decrypted version of the root key and make sure it
|
||||
// doesn't succeed
|
||||
pemBytes, err := ioutil.ReadFile(tempKeyFilePath)
|
||||
assert.NoError(t, err, "could not read key file")
|
||||
privKey, err := trustmanager.ParsePEMPrivateKey(pemBytes, oldPassphrase)
|
||||
assert.NoError(t, err, "could not decrypt key file")
|
||||
decryptedPEMBytes, err := trustmanager.KeyToPEM(privKey)
|
||||
assert.NoError(t, err, "could not convert key to PEM")
|
||||
|
||||
err = repo2.KeyStoreManager.ImportRootKey(bytes.NewReader(decryptedPEMBytes), rootKeyID)
|
||||
assert.EqualError(t, err, keystoremanager.ErrRootKeyNotEncrypted.Error())
|
||||
|
||||
// Try to import garbage and make sure it doesn't succeed
|
||||
err = repo2.KeyStoreManager.ImportRootKey(strings.NewReader("this is not PEM"), rootKeyID)
|
||||
assert.EqualError(t, err, keystoremanager.ErrNoValidPrivateKey.Error())
|
||||
}
|
|
@ -29,16 +29,18 @@ type KeyStoreManager struct {
|
|||
}
|
||||
|
||||
const (
|
||||
trustDir = "trusted_certificates"
|
||||
privDir = "private"
|
||||
rootKeysSubdir = "root_keys"
|
||||
rsaRootKeySize = 4096 // Used for new root keys
|
||||
trustDir = "trusted_certificates"
|
||||
privDir = "private"
|
||||
rootKeysSubdir = "root_keys"
|
||||
nonRootKeysSubdir = "tuf_keys"
|
||||
rsaRootKeySize = 4096 // Used for new root keys
|
||||
)
|
||||
|
||||
// NewKeyStoreManager returns an initialized KeyStoreManager, or an error
|
||||
// if it fails to create the KeyFileStores or load certificates
|
||||
func NewKeyStoreManager(baseDir string) (*KeyStoreManager, error) {
|
||||
nonRootKeyStore, err := trustmanager.NewKeyFileStore(filepath.Join(baseDir, privDir))
|
||||
nonRootKeysPath := filepath.Join(baseDir, privDir, nonRootKeysSubdir)
|
||||
nonRootKeyStore, err := trustmanager.NewKeyFileStore(nonRootKeysPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
@ -1,14 +1,24 @@
|
|||
package trustmanager
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const visible os.FileMode = 0755
|
||||
const private os.FileMode = 0700
|
||||
const (
|
||||
visible os.FileMode = 0755
|
||||
private os.FileMode = 0700
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrPathOutsideStore indicates that the returned path would be
|
||||
// outside the store
|
||||
ErrPathOutsideStore = errors.New("path outside file store")
|
||||
)
|
||||
|
||||
// FileStore is the interface for all FileStores
|
||||
type FileStore interface {
|
||||
|
@ -16,10 +26,11 @@ type FileStore interface {
|
|||
Remove(fileName string) error
|
||||
RemoveDir(directoryName string) error
|
||||
Get(fileName string) ([]byte, error)
|
||||
GetPath(fileName string) string
|
||||
GetPath(fileName string) (string, error)
|
||||
ListFiles(symlinks bool) []string
|
||||
ListDir(directoryName string, symlinks bool) []string
|
||||
Link(src, dst string) error
|
||||
BaseDir() string
|
||||
}
|
||||
|
||||
// SimpleFileStore implements FileStore
|
||||
|
@ -31,6 +42,8 @@ type SimpleFileStore struct {
|
|||
|
||||
// NewSimpleFileStore creates a directory with 755 permissions
|
||||
func NewSimpleFileStore(baseDir string, fileExt string) (FileStore, error) {
|
||||
baseDir = filepath.Clean(baseDir)
|
||||
|
||||
if err := CreateDirectory(baseDir); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -57,7 +70,10 @@ func NewPrivateSimpleFileStore(baseDir string, fileExt string) (FileStore, error
|
|||
|
||||
// Add writes data to a file with a given name
|
||||
func (f *SimpleFileStore) Add(name string, data []byte) error {
|
||||
filePath := f.genFilePath(name)
|
||||
filePath, err := f.GetPath(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
createDirectory(filepath.Dir(filePath), f.perms)
|
||||
return ioutil.WriteFile(filePath, data, f.perms)
|
||||
}
|
||||
|
@ -65,7 +81,10 @@ func (f *SimpleFileStore) Add(name string, data []byte) error {
|
|||
// Remove removes a file identified by name
|
||||
func (f *SimpleFileStore) Remove(name string) error {
|
||||
// Attempt to remove
|
||||
filePath := f.genFilePath(name)
|
||||
filePath, err := f.GetPath(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return os.Remove(filePath)
|
||||
}
|
||||
|
||||
|
@ -89,7 +108,10 @@ func (f *SimpleFileStore) RemoveDir(name string) error {
|
|||
|
||||
// Get returns the data given a file name
|
||||
func (f *SimpleFileStore) Get(name string) ([]byte, error) {
|
||||
filePath := f.genFilePath(name)
|
||||
filePath, err := f.GetPath(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
data, err := ioutil.ReadFile(filePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -99,8 +121,14 @@ func (f *SimpleFileStore) Get(name string) ([]byte, error) {
|
|||
}
|
||||
|
||||
// GetPath returns the full final path of a file with a given name
|
||||
func (f *SimpleFileStore) GetPath(name string) string {
|
||||
return f.genFilePath(name)
|
||||
func (f *SimpleFileStore) GetPath(name string) (string, error) {
|
||||
fileName := f.genFileName(name)
|
||||
fullPath := filepath.Clean(filepath.Join(f.baseDir, fileName))
|
||||
|
||||
if !strings.HasPrefix(fullPath, f.baseDir) {
|
||||
return "", ErrPathOutsideStore
|
||||
}
|
||||
return fullPath, nil
|
||||
}
|
||||
|
||||
// ListFiles lists all the files inside of a store
|
||||
|
@ -143,12 +171,6 @@ func (f *SimpleFileStore) list(path string, symlinks bool) []string {
|
|||
return files
|
||||
}
|
||||
|
||||
// genFilePath returns the full path with extension given a file name
|
||||
func (f *SimpleFileStore) genFilePath(name string) string {
|
||||
fileName := f.genFileName(name)
|
||||
return filepath.Join(f.baseDir, fileName)
|
||||
}
|
||||
|
||||
// genFileName returns the name using the right extension
|
||||
func (f *SimpleFileStore) genFileName(name string) string {
|
||||
return fmt.Sprintf("%s.%s", name, f.fileExt)
|
||||
|
@ -159,10 +181,17 @@ func (f *SimpleFileStore) genFileName(name string) string {
|
|||
// We use full path for the source and local for the destination to use relative
|
||||
// path for the symlink
|
||||
func (f *SimpleFileStore) Link(oldname, newname string) error {
|
||||
return os.Symlink(
|
||||
f.genFileName(oldname),
|
||||
f.genFilePath(newname),
|
||||
)
|
||||
newnamePath, err := f.GetPath(newname)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return os.Symlink(f.genFileName(oldname), newnamePath)
|
||||
}
|
||||
|
||||
// BaseDir returns the base directory of the filestore
|
||||
func (f *SimpleFileStore) BaseDir() string {
|
||||
return f.baseDir
|
||||
}
|
||||
|
||||
// CreateDirectory uses createDirectory to create a chmod 755 Directory
|
||||
|
|
|
@ -286,14 +286,79 @@ func TestGetPath(t *testing.T) {
|
|||
|
||||
firstPath := "diogomonica.com/openvpn/0xdeadbeef.crt"
|
||||
secondPath := "/docker.io/testing-dashes/@#$%^&().crt"
|
||||
if store.GetPath("diogomonica.com/openvpn/0xdeadbeef") != firstPath {
|
||||
|
||||
result, err := store.GetPath("diogomonica.com/openvpn/0xdeadbeef")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error from GetPath: %v", err)
|
||||
}
|
||||
if result != firstPath {
|
||||
t.Fatalf("Expecting: %s", firstPath)
|
||||
}
|
||||
if store.GetPath("/docker.io/testing-dashes/@#$%^&()") != secondPath {
|
||||
|
||||
result, err = store.GetPath("/docker.io/testing-dashes/@#$%^&()")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error from GetPath: %v", err)
|
||||
}
|
||||
if result != secondPath {
|
||||
t.Fatalf("Expecting: %s", secondPath)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetPathProtection(t *testing.T) {
|
||||
testExt := "crt"
|
||||
perms := os.FileMode(0755)
|
||||
|
||||
// Create our SimpleFileStore
|
||||
store := &SimpleFileStore{
|
||||
baseDir: "/path/to/filestore/",
|
||||
fileExt: testExt,
|
||||
perms: perms,
|
||||
}
|
||||
|
||||
// Should deny requests for paths outside the filestore
|
||||
if _, err := store.GetPath("../../etc/passwd"); err != ErrPathOutsideStore {
|
||||
t.Fatalf("expected ErrPathOutsideStore error from GetPath")
|
||||
}
|
||||
if _, err := store.GetPath("private/../../../etc/passwd"); err != ErrPathOutsideStore {
|
||||
t.Fatalf("expected ErrPathOutsideStore error from GetPath")
|
||||
}
|
||||
|
||||
// Convoluted paths should work as long as they end up inside the store
|
||||
expected := "/path/to/filestore/filename.crt"
|
||||
result, err := store.GetPath("private/../../filestore/./filename")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error from GetPath: %v", err)
|
||||
}
|
||||
if result != expected {
|
||||
t.Fatalf("Expecting: %s (got: %s)", expected, result)
|
||||
}
|
||||
|
||||
// Repeat tests with a relative baseDir
|
||||
relStore := &SimpleFileStore{
|
||||
baseDir: "relative/file/path",
|
||||
fileExt: testExt,
|
||||
perms: perms,
|
||||
}
|
||||
|
||||
// Should deny requests for paths outside the filestore
|
||||
if _, err := relStore.GetPath("../../etc/passwd"); err != ErrPathOutsideStore {
|
||||
t.Fatalf("expected ErrPathOutsideStore error from GetPath")
|
||||
}
|
||||
if _, err := relStore.GetPath("private/../../../etc/passwd"); err != ErrPathOutsideStore {
|
||||
t.Fatalf("expected ErrPathOutsideStore error from GetPath")
|
||||
}
|
||||
|
||||
// Convoluted paths should work as long as they end up inside the store
|
||||
expected = "relative/file/path/filename.crt"
|
||||
result, err = relStore.GetPath("private/../../path/./filename")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error from GetPath: %v", err)
|
||||
}
|
||||
if result != expected {
|
||||
t.Fatalf("Expecting: %s (got: %s)", expected, result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetData(t *testing.T) {
|
||||
testName := "docker.com/notary/certificate"
|
||||
testExt := "crt"
|
||||
|
|
|
@ -85,7 +85,11 @@ func (s X509FileStore) addNamedCert(cert *x509.Certificate) error {
|
|||
certBytes := CertToPEM(cert)
|
||||
|
||||
// Save the file to disk if not already there.
|
||||
if _, err := os.Stat(s.fileStore.GetPath(fileName)); os.IsNotExist(err) {
|
||||
filePath, err := s.fileStore.GetPath(fileName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := os.Stat(filePath); os.IsNotExist(err) {
|
||||
if err := s.fileStore.Add(fileName, certBytes); err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue