mirror of https://github.com/docker/docs.git
Major refactor the the FileStore
This commit is contained in:
parent
6625f1fc86
commit
db847379df
|
|
@ -9,7 +9,6 @@ import (
|
|||
"encoding/pem"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/docker/notary/trustmanager"
|
||||
|
|
@ -113,28 +112,8 @@ func generateKeyAndCert(gun string) (crypto.PrivateKey, *x509.Certificate, error
|
|||
|
||||
kID := trustmanager.FingerprintCert(cert)
|
||||
// The key is going to be stored in the private directory, using the GUN and
|
||||
// the filename will be the TUF-compliant ID
|
||||
privKeyFilename := filepath.Join(viper.GetString("privDir"), gun, string(kID)+".key")
|
||||
|
||||
// If GUN is in the form of 'foo/bar' ensures that private key is stored in the
|
||||
// adequate sub-directory
|
||||
err = trustmanager.CreateDirectory(privKeyFilename)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("could not create directory for private key: %v", err)
|
||||
}
|
||||
|
||||
// Opens a FD to the file with the correct permissions
|
||||
keyOut, err := os.OpenFile(privKeyFilename, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("could not write privatekey: %v", err)
|
||||
}
|
||||
defer keyOut.Close()
|
||||
|
||||
// Encodes the private key as PEM and writes it
|
||||
err = pem.Encode(keyOut, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: keyBytes})
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to encode key: %v", err)
|
||||
}
|
||||
|
||||
// the filename will be the TUF-compliant ID. The Store takes care of extensions.
|
||||
privKeyFilename := filepath.Join(gun, string(kID))
|
||||
privKeyStore.Add(privKeyFilename, trustmanager.KeyToPEM(keyBytes))
|
||||
return key, cert, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -137,31 +137,14 @@ func keysList(cmd *cobra.Command, args []string) {
|
|||
|
||||
fmt.Println("")
|
||||
fmt.Println("# Signing keys: ")
|
||||
filepath.Walk(viper.GetString("privDir"), printAllPrivateKeys)
|
||||
}
|
||||
|
||||
func printAllPrivateKeys(fp string, fi os.FileInfo, err error) error {
|
||||
// If there are errors, ignore this particular file
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
// Ignore if it is a directory
|
||||
if fi.IsDir() {
|
||||
return nil
|
||||
}
|
||||
//TODO (diogo): make the key extension not be hardcoded
|
||||
// Only allow matches that end with our key extension .key
|
||||
matched, _ := filepath.Match("*.key", fi.Name())
|
||||
if matched {
|
||||
fp = strings.TrimSuffix(fp, filepath.Ext(fp))
|
||||
fp = strings.TrimPrefix(fp, viper.GetString("privDir"))
|
||||
|
||||
fingerprint := filepath.Base(fp)
|
||||
gun := filepath.Dir(fp)[1:]
|
||||
for _, k := range privKeyStore.List() {
|
||||
k = strings.TrimSuffix(k, filepath.Ext(k))
|
||||
k = strings.TrimPrefix(k, viper.GetString("privDir"))
|
||||
|
||||
fingerprint := filepath.Base(k)
|
||||
gun := filepath.Dir(k)[1:]
|
||||
fmt.Printf("%s %s\n", gun, fingerprint)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func keysGenerate(cmd *cobra.Command, args []string) {
|
||||
|
|
|
|||
|
|
@ -24,6 +24,8 @@ const privDir string = configPath + "private/"
|
|||
const tufDir string = configPath + "tuf/"
|
||||
|
||||
var caStore trustmanager.X509Store
|
||||
var privKeyStore trustmanager.FileStore
|
||||
|
||||
var rawOutput bool
|
||||
|
||||
func init() {
|
||||
|
|
@ -64,25 +66,24 @@ func init() {
|
|||
finalTrustDir := viper.GetString("trustDir")
|
||||
finalPrivDir := viper.GetString("privDir")
|
||||
|
||||
// Ensure the existence of the CAs directory
|
||||
err = trustmanager.CreateDirectory(finalTrustDir)
|
||||
if err != nil {
|
||||
fatalf("could not create directory: %v", err)
|
||||
}
|
||||
err = trustmanager.CreateDirectory(finalPrivDir)
|
||||
if err != nil {
|
||||
fatalf("could not create directory: %v", err)
|
||||
}
|
||||
|
||||
// Load all CAs that aren't expired and don't use SHA1
|
||||
// We could easily add "return cert.IsCA && cert.BasicConstraintsValid" in order
|
||||
// to have only valid CA certificates being loaded
|
||||
caStore = trustmanager.NewX509FilteredFileStore(finalTrustDir, func(cert *x509.Certificate) bool {
|
||||
caStore, err = trustmanager.NewX509FilteredFileStore(finalTrustDir, func(cert *x509.Certificate) bool {
|
||||
return time.Now().Before(cert.NotAfter) &&
|
||||
cert.SignatureAlgorithm != x509.SHA1WithRSA &&
|
||||
cert.SignatureAlgorithm != x509.DSAWithSHA1 &&
|
||||
cert.SignatureAlgorithm != x509.ECDSAWithSHA1
|
||||
})
|
||||
if err != nil {
|
||||
fatalf("could not create X509FileStore: %v", err)
|
||||
}
|
||||
|
||||
privKeyStore, err = trustmanager.NewPrivateFileStore(finalPrivDir, "key")
|
||||
if err != nil {
|
||||
fatalf("could not create FileStore: %v", err)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func main() {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,132 @@
|
|||
package trustmanager
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
const visible os.FileMode = 0755
|
||||
const private os.FileMode = 0700
|
||||
|
||||
// FileStore is the interface for all FileStores
|
||||
type FileStore interface {
|
||||
Add(name string, data []byte) error
|
||||
Remove(name string) error
|
||||
GetData(name string) ([]byte, error)
|
||||
GetPath(name string) string
|
||||
List() []string
|
||||
}
|
||||
|
||||
type fileStore struct {
|
||||
baseDir string
|
||||
fileExt string
|
||||
perms os.FileMode
|
||||
}
|
||||
|
||||
func NewFileStore(baseDir string, fileExt string) (FileStore, error) {
|
||||
if err := CreateDirectory(baseDir); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &fileStore{
|
||||
baseDir: baseDir,
|
||||
fileExt: fileExt,
|
||||
perms: visible,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func NewPrivateFileStore(baseDir string, fileExt string) (FileStore, error) {
|
||||
if err := CreatePrivateDirectory(baseDir); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &fileStore{
|
||||
baseDir: baseDir,
|
||||
fileExt: fileExt,
|
||||
perms: private,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (f *fileStore) Add(name string, data []byte) error {
|
||||
filePath := f.genFilePath(name)
|
||||
createDirectory(filepath.Dir(filePath), f.perms)
|
||||
if err := ioutil.WriteFile(filePath, data, f.perms); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *fileStore) Remove(name string) error {
|
||||
filePath := f.genFilePath(name)
|
||||
if err := os.Remove(filePath); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *fileStore) GetData(name string) ([]byte, error) {
|
||||
filePath := f.genFilePath(name)
|
||||
data, err := ioutil.ReadFile(filePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func (f *fileStore) GetPath(name string) string {
|
||||
return f.genFilePath(name)
|
||||
}
|
||||
|
||||
func (f *fileStore) List() []string {
|
||||
files := make([]string, 0, 0)
|
||||
filepath.Walk(f.baseDir, func(fp string, fi os.FileInfo, err error) error {
|
||||
// If there are errors, ignore this particular file
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
// Ignore if it is a directory
|
||||
if fi.IsDir() {
|
||||
return nil
|
||||
}
|
||||
// Only allow matches that end with our certificate extension (e.g. *.crt)
|
||||
matched, _ := filepath.Match("*"+f.fileExt, fi.Name())
|
||||
|
||||
if matched {
|
||||
files = append(files, fp)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
return files
|
||||
}
|
||||
|
||||
func (f *fileStore) genFilePath(name string) string {
|
||||
fileName := fmt.Sprintf("%s.%s", name, f.fileExt)
|
||||
filePath := filepath.Join(f.baseDir, fileName)
|
||||
return filePath
|
||||
}
|
||||
|
||||
func CreateDirectory(dir string) error {
|
||||
return createDirectory(dir, visible)
|
||||
}
|
||||
|
||||
func CreatePrivateDirectory(dir string) error {
|
||||
return createDirectory(dir, private)
|
||||
}
|
||||
|
||||
// createDirectory receives a string of the path to a directory.
|
||||
// It does not support passing files, so the caller has to remove
|
||||
// the filename by doing filepath.Dir(full_path_to_file)
|
||||
func createDirectory(dir string, perms os.FileMode) error {
|
||||
// This prevents someone passing /path/to/dir and 'dir' not being created
|
||||
// If two '//' exist, MkdirAll deals it with correctly
|
||||
dir = dir + "/"
|
||||
if err := os.MkdirAll(dir, perms); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
@ -0,0 +1,257 @@
|
|||
package trustmanager
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestAddFile(t *testing.T) {
|
||||
testData := []byte("This test data should be part of the file.")
|
||||
testName := "docker.com/notary/certificate"
|
||||
testExt := "crt"
|
||||
perms := os.FileMode(0755)
|
||||
|
||||
// Temporary directory where test files will be created
|
||||
tempBaseDir, err := ioutil.TempDir("", "notary-test-")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create a temporary directory: %v", err)
|
||||
}
|
||||
|
||||
// Since we're generating this manually we need to add the extension '.'
|
||||
expectedFilePath := filepath.Join(tempBaseDir, testName+"."+testExt)
|
||||
|
||||
// Create our FileStore
|
||||
store := &fileStore{
|
||||
baseDir: tempBaseDir,
|
||||
fileExt: testExt,
|
||||
perms: perms,
|
||||
}
|
||||
|
||||
// Call the Add function
|
||||
err = store.Add(testName, testData)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to add file to store: %v", err)
|
||||
}
|
||||
|
||||
// Check to see if file exists
|
||||
b, err := ioutil.ReadFile(expectedFilePath)
|
||||
if err != nil {
|
||||
t.Fatalf("expected file not found: %v", err)
|
||||
}
|
||||
|
||||
if !bytes.Equal(b, testData) {
|
||||
t.Fatalf("unexpected content in the file: %s", expectedFilePath)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemoveFile(t *testing.T) {
|
||||
testName := "docker.com/notary/certificate"
|
||||
testExt := "crt"
|
||||
perms := os.FileMode(0755)
|
||||
|
||||
// Temporary directory where test files will be created
|
||||
tempBaseDir, err := ioutil.TempDir("", "notary-test-")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create a temporary directory: %v", err)
|
||||
}
|
||||
|
||||
// Since we're generating this manually we need to add the extension '.'
|
||||
expectedFilePath := filepath.Join(tempBaseDir, testName+"."+testExt)
|
||||
|
||||
_, err = generateRandomFile(expectedFilePath, perms)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to generate random file: %v", err)
|
||||
}
|
||||
|
||||
// Create our FileStore
|
||||
store := &fileStore{
|
||||
baseDir: tempBaseDir,
|
||||
fileExt: testExt,
|
||||
perms: perms,
|
||||
}
|
||||
|
||||
// Call the Add function
|
||||
err = store.Remove(testName)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to remove file from store: %v", err)
|
||||
}
|
||||
|
||||
// Check to see if file exists
|
||||
_, err = os.Stat(expectedFilePath)
|
||||
if err == nil {
|
||||
t.Fatalf("expected not to find file: %s", expectedFilePath)
|
||||
}
|
||||
}
|
||||
|
||||
func TestList(t *testing.T) {
|
||||
testName := "docker.com/notary/certificate"
|
||||
testExt := "crt"
|
||||
perms := os.FileMode(0755)
|
||||
|
||||
// Temporary directory where test files will be created
|
||||
tempBaseDir, err := ioutil.TempDir("", "notary-test-")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create a temporary directory: %v", err)
|
||||
}
|
||||
|
||||
var expectedFilePath string
|
||||
// Create 10 randomfiles
|
||||
for i := 1; i <= 10; i++ {
|
||||
// Since we're generating this manually we need to add the extension '.'
|
||||
expectedFilePath = filepath.Join(tempBaseDir, testName+string(i)+"."+testExt)
|
||||
_, err = generateRandomFile(expectedFilePath, perms)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to generate random file: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Create our FileStore
|
||||
store := &fileStore{
|
||||
baseDir: tempBaseDir,
|
||||
fileExt: testExt,
|
||||
perms: perms,
|
||||
}
|
||||
|
||||
// Call the Add function
|
||||
files := store.List()
|
||||
if len(files) != 10 {
|
||||
t.Fatalf("expected 10 files in listing, got: %d", len(files))
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetPath(t *testing.T) {
|
||||
testExt := "crt"
|
||||
perms := os.FileMode(0755)
|
||||
|
||||
// Create our FileStore
|
||||
store := &fileStore{
|
||||
baseDir: "",
|
||||
fileExt: testExt,
|
||||
perms: perms,
|
||||
}
|
||||
|
||||
firstPath := "diogomonica.com/openvpn/0xdeadbeef.crt"
|
||||
secondPath := "/docker.io/testing-dashes/@#$%^&().crt"
|
||||
if store.GetPath("diogomonica.com/openvpn/0xdeadbeef") != firstPath {
|
||||
t.Fatalf("Expecting: %s", firstPath)
|
||||
}
|
||||
if store.GetPath("/docker.io/testing-dashes/@#$%^&()") != secondPath {
|
||||
t.Fatalf("Expecting: %s", secondPath)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetData(t *testing.T) {
|
||||
testName := "docker.com/notary/certificate"
|
||||
testExt := "crt"
|
||||
perms := os.FileMode(0755)
|
||||
|
||||
// Temporary directory where test files will be created
|
||||
tempBaseDir, err := ioutil.TempDir("", "notary-test-")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create a temporary directory: %v", err)
|
||||
}
|
||||
|
||||
// Since we're generating this manually we need to add the extension '.'
|
||||
expectedFilePath := filepath.Join(tempBaseDir, testName+"."+testExt)
|
||||
|
||||
expectedData, err := generateRandomFile(expectedFilePath, perms)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to generate random file: %v", err)
|
||||
}
|
||||
|
||||
// Create our FileStore
|
||||
store := &fileStore{
|
||||
baseDir: tempBaseDir,
|
||||
fileExt: testExt,
|
||||
perms: perms,
|
||||
}
|
||||
testData, err := store.GetData(testName)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to get data from: %s", testName)
|
||||
|
||||
}
|
||||
if !bytes.Equal(testData, expectedData) {
|
||||
t.Fatalf("unexpected content for the file: %s", expectedFilePath)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateDirectory(t *testing.T) {
|
||||
testDir := "fake/path/to/directory"
|
||||
|
||||
// Temporary directory where test files will be created
|
||||
tempBaseDir, err := ioutil.TempDir("", "notary-test-")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create a temporary directory: %v", err)
|
||||
}
|
||||
|
||||
dirPath := filepath.Join(tempBaseDir, testDir)
|
||||
|
||||
// Call createDirectory
|
||||
CreateDirectory(dirPath)
|
||||
|
||||
// Check to see if file exists
|
||||
fi, err := os.Stat(dirPath)
|
||||
if err != nil {
|
||||
t.Fatalf("expected find directory: %s", dirPath)
|
||||
}
|
||||
|
||||
// Check to see if it is a directory
|
||||
if !fi.IsDir() {
|
||||
t.Fatalf("expected to be directory: %s", dirPath)
|
||||
}
|
||||
|
||||
// Check to see if the permissions match
|
||||
if fi.Mode().String() != "drwxr-xr-x" {
|
||||
t.Fatalf("permissions are wrong for: %s. Got: %s", dirPath, fi.Mode().String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreatePrivateDirectory(t *testing.T) {
|
||||
testDir := "fake/path/to/private/directory"
|
||||
|
||||
// Temporary directory where test files will be created
|
||||
tempBaseDir, err := ioutil.TempDir("", "notary-test-")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create a temporary directory: %v", err)
|
||||
}
|
||||
|
||||
dirPath := filepath.Join(tempBaseDir, testDir)
|
||||
|
||||
// Call createDirectory
|
||||
CreatePrivateDirectory(dirPath)
|
||||
|
||||
// Check to see if file exists
|
||||
fi, err := os.Stat(dirPath)
|
||||
if err != nil {
|
||||
t.Fatalf("expected find directory: %s", dirPath)
|
||||
}
|
||||
|
||||
// Check to see if it is a directory
|
||||
if !fi.IsDir() {
|
||||
t.Fatalf("expected to be directory: %s", dirPath)
|
||||
}
|
||||
|
||||
// Check to see if the permissions match
|
||||
if fi.Mode().String() != "drwx------" {
|
||||
t.Fatalf("permissions are wrong for: %s. Got: %s", dirPath, fi.Mode().String())
|
||||
}
|
||||
}
|
||||
|
||||
func generateRandomFile(filePath string, perms os.FileMode) ([]byte, error) {
|
||||
rndBytes := make([]byte, 10)
|
||||
_, err := rand.Read(rndBytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
os.MkdirAll(filepath.Dir(filePath), perms)
|
||||
if err = ioutil.WriteFile(filePath, rndBytes, perms); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return rndBytes, nil
|
||||
}
|
||||
|
|
@ -9,46 +9,42 @@ import (
|
|||
|
||||
// X509FileStore implements X509Store that persists on disk
|
||||
type X509FileStore struct {
|
||||
baseDir string
|
||||
validate Validator
|
||||
fileMap map[ID]string
|
||||
fingerprintMap map[ID]*x509.Certificate
|
||||
nameMap map[string][]ID
|
||||
fileStore FileStore
|
||||
}
|
||||
|
||||
// NewX509FileStore returns a new X509FileStore.
|
||||
func NewX509FileStore(directory string) *X509FileStore {
|
||||
func NewX509FileStore(directory string) (*X509FileStore, error) {
|
||||
validate := ValidatorFunc(func(cert *x509.Certificate) bool { return true })
|
||||
|
||||
s := &X509FileStore{
|
||||
|
||||
baseDir: directory,
|
||||
validate: validate,
|
||||
fileMap: make(map[ID]string),
|
||||
fingerprintMap: make(map[ID]*x509.Certificate),
|
||||
nameMap: make(map[string][]ID),
|
||||
}
|
||||
|
||||
loadCertsFromDir(s, directory)
|
||||
|
||||
return s
|
||||
return newX509FileStore(directory, validate)
|
||||
}
|
||||
|
||||
// NewX509FilteredFileStore returns a new X509FileStore that validates certificates
|
||||
// that are added.
|
||||
func NewX509FilteredFileStore(directory string, validate func(*x509.Certificate) bool) *X509FileStore {
|
||||
s := &X509FileStore{
|
||||
func NewX509FilteredFileStore(directory string, validate func(*x509.Certificate) bool) (*X509FileStore, error) {
|
||||
return newX509FileStore(directory, validate)
|
||||
}
|
||||
|
||||
baseDir: directory,
|
||||
func newX509FileStore(directory string, validate func(*x509.Certificate) bool) (*X509FileStore, error) {
|
||||
fileStore, err := NewFileStore(directory, certExtension)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
s := &X509FileStore{
|
||||
validate: ValidatorFunc(validate),
|
||||
fileMap: make(map[ID]string),
|
||||
fingerprintMap: make(map[ID]*x509.Certificate),
|
||||
nameMap: make(map[string][]ID),
|
||||
fileStore: fileStore,
|
||||
}
|
||||
|
||||
loadCertsFromDir(s, directory)
|
||||
loadCertsFromDir(s)
|
||||
|
||||
return s
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// AddCert creates a filename for a given cert and adds a certificate with that name
|
||||
|
|
@ -57,11 +53,12 @@ func (s X509FileStore) AddCert(cert *x509.Certificate) error {
|
|||
return errors.New("adding nil Certificate to X509Store")
|
||||
}
|
||||
|
||||
var filename string
|
||||
fingerprint := string(FingerprintCert(cert))
|
||||
filename = path.Join(s.baseDir, cert.Subject.CommonName, fingerprint+certExtension)
|
||||
|
||||
if err := s.addNamedCert(cert, filename); err != nil {
|
||||
// Check if this certificate meets our validation criteria
|
||||
if !s.validate.Validate(cert) {
|
||||
return errors.New("certificate validation failed")
|
||||
}
|
||||
// Attempt to write the certificate to the file
|
||||
if err := s.addNamedCert(cert); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
|
@ -70,11 +67,7 @@ func (s X509FileStore) AddCert(cert *x509.Certificate) error {
|
|||
|
||||
// addNamedCert allows adding a certificate while controling the filename it gets
|
||||
// stored under. If the file does not exist on disk, saves it.
|
||||
func (s X509FileStore) addNamedCert(cert *x509.Certificate, filename string) error {
|
||||
if cert == nil {
|
||||
return errors.New("adding nil Certificate to X509Store")
|
||||
}
|
||||
|
||||
func (s X509FileStore) addNamedCert(cert *x509.Certificate) error {
|
||||
fingerprint := FingerprintCert(cert)
|
||||
|
||||
// Validate if we already loaded this certificate before
|
||||
|
|
@ -82,23 +75,25 @@ func (s X509FileStore) addNamedCert(cert *x509.Certificate, filename string) err
|
|||
return errors.New("certificate already in the store")
|
||||
}
|
||||
|
||||
// Check if this certificate meets our validation criteria
|
||||
if !s.validate.Validate(cert) {
|
||||
return errors.New("certificate validation failed")
|
||||
// Convert certificate to PEM
|
||||
certBytes := ToPEM(cert)
|
||||
// Compute FileName
|
||||
fileName := fileName(cert)
|
||||
|
||||
// Save the file to disk if not already there.
|
||||
if _, err := os.Stat(fileName); os.IsNotExist(err) {
|
||||
if err := s.fileStore.Add(fileName, certBytes); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Add the certificate to our in-memory storage
|
||||
// We wrote the certificate succcessfully, add it to our in-memory storage
|
||||
s.fingerprintMap[fingerprint] = cert
|
||||
s.fileMap[fingerprint] = filename
|
||||
s.fileMap[fingerprint] = fileName
|
||||
|
||||
name := string(cert.RawSubject)
|
||||
s.nameMap[name] = append(s.nameMap[name], fingerprint)
|
||||
|
||||
// Save the file to disk if not already there.
|
||||
if _, err := os.Stat(filename); os.IsNotExist(err) {
|
||||
return saveCertificate(cert, filename)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
@ -126,7 +121,7 @@ func (s X509FileStore) RemoveCert(cert *x509.Certificate) error {
|
|||
|
||||
s.nameMap[name] = newfpList
|
||||
|
||||
if err := os.Remove(filename); err != nil {
|
||||
if err := s.fileStore.Remove(filename); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
|
@ -207,3 +202,7 @@ func (s X509FileStore) GetVerifyOptions(dnsName string) (x509.VerifyOptions, err
|
|||
|
||||
return opts, nil
|
||||
}
|
||||
|
||||
func fileName(cert *x509.Certificate) string {
|
||||
return path.Join(cert.Subject.CommonName, string(FingerprintCert(cert)))
|
||||
}
|
||||
|
|
@ -2,7 +2,7 @@ package trustmanager
|
|||
|
||||
import "crypto/x509"
|
||||
|
||||
const certExtension string = ".crt"
|
||||
const certExtension string = "crt"
|
||||
|
||||
// X509Store is the interface for all X509Stores
|
||||
type X509Store interface {
|
||||
|
|
@ -4,12 +4,9 @@ import (
|
|||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/endophage/gotuf/data"
|
||||
)
|
||||
|
|
@ -48,76 +45,18 @@ func GetCertFromURL(urlStr string) (*x509.Certificate, error) {
|
|||
return cert, nil
|
||||
}
|
||||
|
||||
// saveCertificate is an utility function that saves a certificate as a PEM
|
||||
// encoded block to a file.
|
||||
func saveCertificate(cert *x509.Certificate, filename string) error {
|
||||
block := pem.Block{Type: "CERTIFICATE", Bytes: cert.Raw}
|
||||
pemdata := string(pem.EncodeToMemory(&block))
|
||||
// ToPEM is an utility function returns a PEM encoded x509 Certificate
|
||||
func ToPEM(cert *x509.Certificate) []byte {
|
||||
pemCert := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: cert.Raw})
|
||||
|
||||
err := CreateDirectory(filename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = ioutil.WriteFile(filename, []byte(pemdata), 0600)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
return pemCert
|
||||
}
|
||||
|
||||
func FingerprintCert(cert *x509.Certificate) ID {
|
||||
block := pem.Block{Type: "CERTIFICATE", Bytes: cert.Raw}
|
||||
pemdata := string(pem.EncodeToMemory(&block))
|
||||
// TeyToPEM is an utility function returns a PEM encoded Key
|
||||
func KeyToPEM(keyBytes []byte) []byte {
|
||||
keyPEMBytes := pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: keyBytes})
|
||||
|
||||
// Create new TUF Key so we can compute the TUF-compliant ID
|
||||
tufKey := data.NewTUFKey("RSA", pemdata, "")
|
||||
|
||||
return ID(tufKey.ID())
|
||||
}
|
||||
|
||||
// loadCertsFromDir receives a store and a directory and calls AddCertFromFile
|
||||
// for each certificate found
|
||||
func loadCertsFromDir(s *X509FileStore, directory string) {
|
||||
filepath.Walk(directory, func(fp string, fi os.FileInfo, err error) error {
|
||||
// If there are errors, ignore this particular file
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
// Ignore if it is a directory
|
||||
if !!fi.IsDir() {
|
||||
return nil
|
||||
}
|
||||
// Only allow matches that end with our certificate extension (e.g. *.crt)
|
||||
matched, _ := filepath.Match("*"+certExtension, fi.Name())
|
||||
|
||||
if matched {
|
||||
s.AddCertFromFile(fp)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// LoadCertFromFile tries to adds a X509 certificate to the store given a filename
|
||||
func LoadCertFromFile(filename string) (*x509.Certificate, error) {
|
||||
// TODO(diogo): handle multiple certificates in one file. Demultiplex into
|
||||
// multiple files or load only first
|
||||
b, err := ioutil.ReadFile(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var block *pem.Block
|
||||
block, b = pem.Decode(b)
|
||||
for ; block != nil; block, b = pem.Decode(b) {
|
||||
if block.Type == "CERTIFICATE" {
|
||||
cert, err := x509.ParseCertificate(block.Bytes)
|
||||
if err == nil {
|
||||
return cert, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil, errors.New("could not load certificate from file")
|
||||
return keyPEMBytes
|
||||
}
|
||||
|
||||
// loadCertFromPEM returns the first certificate found in a bunch of bytes or error
|
||||
|
|
@ -144,10 +83,41 @@ func loadCertFromPEM(pemBytes []byte) (*x509.Certificate, error) {
|
|||
return nil, errors.New("no certificates found in PEM data")
|
||||
}
|
||||
|
||||
func CreateDirectory(dir string) error {
|
||||
cleanDir := filepath.Dir(dir)
|
||||
if err := os.MkdirAll(cleanDir, 0700); err != nil {
|
||||
return fmt.Errorf("cannot create directory: %v", err)
|
||||
}
|
||||
return nil
|
||||
func FingerprintCert(cert *x509.Certificate) ID {
|
||||
block := pem.Block{Type: "CERTIFICATE", Bytes: cert.Raw}
|
||||
pemdata := string(pem.EncodeToMemory(&block))
|
||||
|
||||
// Create new TUF Key so we can compute the TUF-compliant ID
|
||||
tufKey := data.NewTUFKey("RSA", pemdata, "")
|
||||
|
||||
return ID(tufKey.ID())
|
||||
}
|
||||
|
||||
// loadCertsFromDir receives a store AddCertFromFile for each certificate found
|
||||
func loadCertsFromDir(s *X509FileStore) {
|
||||
certFiles := s.fileStore.List()
|
||||
for _, c := range certFiles {
|
||||
s.AddCertFromFile(c)
|
||||
}
|
||||
}
|
||||
|
||||
// LoadCertFromFile tries to adds a X509 certificate to the store given a filename
|
||||
func LoadCertFromFile(filename string) (*x509.Certificate, error) {
|
||||
// TODO(diogo): handle multiple certificates in one file.
|
||||
b, err := ioutil.ReadFile(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var block *pem.Block
|
||||
block, b = pem.Decode(b)
|
||||
for ; block != nil; block, b = pem.Decode(b) {
|
||||
if block.Type == "CERTIFICATE" {
|
||||
cert, err := x509.ParseCertificate(block.Bytes)
|
||||
if err == nil {
|
||||
return cert, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil, errors.New("could not load certificate from file")
|
||||
}
|
||||
Loading…
Reference in New Issue