mirror of https://github.com/docker/docs.git
Initial libnotary refactor
Signed-off-by: Diogo Monica <diogo@docker.com> Ported more functionality to libnotary
This commit is contained in:
parent
ce0ed53fa2
commit
1346296869
|
|
@ -1,4 +1,4 @@
|
|||
package main
|
||||
package client
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
|
|
@ -8,41 +8,42 @@ import (
|
|||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/docker/notary/trustmanager"
|
||||
"github.com/endophage/gotuf/data"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
type CliCryptoService struct {
|
||||
privateKeys map[string]*data.PrivateKey
|
||||
gun string
|
||||
gun string
|
||||
keyStore trustmanager.FileStore
|
||||
}
|
||||
|
||||
// NewCryptoService returns an instance ofS cliCryptoService
|
||||
func NewCryptoService(gun string) *CliCryptoService {
|
||||
return &CliCryptoService{privateKeys: make(map[string]*data.PrivateKey), gun: gun}
|
||||
func NewCryptoService(gun string, keyStore trustmanager.FileStore) *CliCryptoService {
|
||||
return &CliCryptoService{gun: gun, keyStore: keyStore}
|
||||
}
|
||||
|
||||
// Create is used to generate keys for targets, snapshots and timestamps
|
||||
func (ccs *CliCryptoService) Create(role string) (*data.PublicKey, error) {
|
||||
_, cert, err := generateKeyAndCert(ccs.gun)
|
||||
keyData, pemCert, err := GenerateKeyAndCert(ccs.gun)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// PEM ENcode the certificate, which will be put directly inside of TUF's root.json
|
||||
block := pem.Block{Type: "CERTIFICATE", Bytes: cert.Raw}
|
||||
pemdata := pem.EncodeToMemory(&block)
|
||||
|
||||
// If this key has the role root, save it as a trusted certificate on our certificateStore
|
||||
if role == "root" {
|
||||
certificateStore.AddCertFromPEM(pemdata)
|
||||
fingerprint, err := trustmanager.FingerprintPEMCert(pemCert)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return data.NewPublicKey("RSA", pemdata), nil
|
||||
// The key is going to be stored in the private directory, using the GUN and
|
||||
// the filename will be the TUF-compliant ID. The Store takes care of extensions.
|
||||
privKeyFilename := filepath.Join(ccs.gun, fingerprint)
|
||||
|
||||
// Store this private key
|
||||
ccs.keyStore.Add(privKeyFilename, keyData)
|
||||
|
||||
return data.NewPublicKey("RSA", pemCert), nil
|
||||
}
|
||||
|
||||
// Sign returns the signatures for data with the given keyIDs
|
||||
|
|
@ -54,9 +55,9 @@ func (ccs *CliCryptoService) Sign(keyIDs []string, payload []byte) ([]data.Signa
|
|||
signatures := make([]data.Signature, 0, len(keyIDs))
|
||||
for _, fingerprint := range keyIDs {
|
||||
// Get the PrivateKey filename
|
||||
privKeyFilename := filepath.Join(viper.GetString("privDir"), ccs.gun, fingerprint+".key")
|
||||
privKeyFilename := filepath.Join(ccs.gun, fingerprint)
|
||||
// Read PrivateKey from file
|
||||
privPEMBytes, err := ioutil.ReadFile(privKeyFilename)
|
||||
privPEMBytes, err := ccs.keyStore.Get(privKeyFilename)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
|
@ -84,8 +85,9 @@ func (ccs *CliCryptoService) Sign(keyIDs []string, payload []byte) ([]data.Signa
|
|||
return signatures, nil
|
||||
}
|
||||
|
||||
// generateKeyAndCert deals with the creation and storage of a key and returns a cert
|
||||
func generateKeyAndCert(gun string) (crypto.PrivateKey, *x509.Certificate, error) {
|
||||
// generateKeyAndCert deals with the creation and storage of a key and returns a
|
||||
// PEM encoded cert
|
||||
func GenerateKeyAndCert(gun string) ([]byte, []byte, error) {
|
||||
// Generates a new RSA key
|
||||
key, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||
if err != nil {
|
||||
|
|
@ -96,7 +98,7 @@ func generateKeyAndCert(gun string) (crypto.PrivateKey, *x509.Certificate, error
|
|||
// TUF-compliant keyID
|
||||
//TODO (diogo): We're hardcoding the Organization to be the GUN. Probably want to
|
||||
// change it
|
||||
template := newCertificate(gun, gun)
|
||||
template := trustmanager.NewCertificate(gun, gun)
|
||||
derBytes, err := x509.CreateCertificate(rand.Reader, template, template, key.Public(), key)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to generate the certificate for key: %v", err)
|
||||
|
|
@ -108,14 +110,10 @@ func generateKeyAndCert(gun string) (crypto.PrivateKey, *x509.Certificate, error
|
|||
return nil, nil, fmt.Errorf("failed to generate the certificate for key: %v", err)
|
||||
}
|
||||
|
||||
fingerprint := 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. The Store takes care of extensions.
|
||||
privKeyFilename := filepath.Join(gun, fingerprint)
|
||||
pemKey, err := trustmanager.KeyToPEM(key)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to generate the certificate for key: %v", err)
|
||||
}
|
||||
|
||||
return key, cert, privKeyStore.Add(privKeyFilename, pemKey)
|
||||
return pemKey, trustmanager.CertToPEM(cert), nil
|
||||
}
|
||||
|
|
@ -0,0 +1,596 @@
|
|||
package client
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/x509"
|
||||
"encoding/json"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/docker/notary/trustmanager"
|
||||
"github.com/endophage/gotuf"
|
||||
tufclient "github.com/endophage/gotuf/client"
|
||||
"github.com/endophage/gotuf/data"
|
||||
"github.com/endophage/gotuf/keys"
|
||||
"github.com/endophage/gotuf/signed"
|
||||
"github.com/endophage/gotuf/store"
|
||||
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
// Default paths should end with a '/' so directory creation works correctly
|
||||
const trustDir string = "/trusted_certificates/"
|
||||
const privDir string = "/private/"
|
||||
const tufDir string = "/tuf/"
|
||||
const rootKeysDir string = "/root_keys/"
|
||||
|
||||
// ErrRepositoryNotExist gets returned when trying to make an action over a repository
|
||||
/// that doesn't exist
|
||||
var ErrRepositoryNotExist = errors.New("repository does not exist")
|
||||
|
||||
// Client is the interface that defines the Notary Client type
|
||||
type Client interface {
|
||||
ListPrivateKeys() []data.PrivateKey
|
||||
GenRootKey(passphrase string) (*data.PublicKey, error)
|
||||
GetRepository(gun string, baseURL string, transport http.RoundTripper) (Repository, error)
|
||||
}
|
||||
|
||||
// Repository is the interface that represents a Notary Repository
|
||||
type Repository interface {
|
||||
Update() error
|
||||
Initialize(key *data.PublicKey) error
|
||||
|
||||
AddTarget(target *Target) error
|
||||
ListTargets() ([]*Target, error)
|
||||
GetTargetByName(name string) (*Target, error)
|
||||
|
||||
Publish() error
|
||||
}
|
||||
|
||||
type NotaryClient struct {
|
||||
configFile string
|
||||
caStore trustmanager.X509Store
|
||||
certificateStore trustmanager.X509Store
|
||||
rootKeyStore trustmanager.EncryptedFileStore
|
||||
}
|
||||
|
||||
type NotaryRepository struct {
|
||||
Gun string
|
||||
baseURL string
|
||||
transport http.RoundTripper
|
||||
signer *signed.Signer
|
||||
tufRepo *tuf.TufRepo
|
||||
fileStore store.MetadataStore
|
||||
privKeyStore trustmanager.FileStore
|
||||
caStore trustmanager.X509Store
|
||||
certificateStore trustmanager.X509Store
|
||||
}
|
||||
|
||||
// Target represents a simplified version of the data TUF operates on.
|
||||
type Target struct {
|
||||
Name string
|
||||
Hashes data.Hashes
|
||||
Length int64
|
||||
}
|
||||
|
||||
// NewTarget is a helper method that returns a Target
|
||||
func NewTarget(targetName string, targetPath string) (*Target, error) {
|
||||
b, err := ioutil.ReadFile(targetPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
meta, err := data.NewFileMeta(bytes.NewBuffer(b))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Target{Name: targetName, Hashes: meta.Hashes, Length: meta.Length}, nil
|
||||
}
|
||||
|
||||
// NewClient is a helper method that returns a new notary Client, given a config
|
||||
// file. It makes the assumption that the base directory for the config file will
|
||||
// be the place where trust information is being cached locally.
|
||||
func NewClient(configFile string) (*NotaryClient, error) {
|
||||
nClient := &NotaryClient{configFile: configFile}
|
||||
err := nClient.loadKeys()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return nClient, nil
|
||||
}
|
||||
|
||||
// Update forces TUF to download the remote timestamps and verify if there are
|
||||
// any remote changes.
|
||||
func (r *NotaryRepository) Update() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Initialize creates a new repository by using rootKey as the root Key for the
|
||||
// TUF repository.
|
||||
func (r *NotaryRepository) Initialize(rootKey *data.PublicKey) error {
|
||||
remote, err := getRemoteStore(r.Gun)
|
||||
rawTSKey, err := remote.GetKey("timestamp")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println("RawKey: ", string(rawTSKey))
|
||||
parsedKey := &data.TUFKey{}
|
||||
err = json.Unmarshal(rawTSKey, parsedKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
timestampKey := data.NewPublicKey(parsedKey.Cipher(), parsedKey.Public())
|
||||
|
||||
targetsKey, err := r.signer.Create("targets")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
snapshotKey, err := r.signer.Create("snapshot")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
kdb := keys.NewDB()
|
||||
|
||||
kdb.AddKey(rootKey)
|
||||
kdb.AddKey(targetsKey)
|
||||
kdb.AddKey(snapshotKey)
|
||||
kdb.AddKey(timestampKey)
|
||||
|
||||
rootRole, err := data.NewRole("root", 1, []string{rootKey.ID()}, nil, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
targetsRole, err := data.NewRole("targets", 1, []string{targetsKey.ID()}, nil, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
snapshotRole, err := data.NewRole("snapshot", 1, []string{snapshotKey.ID()}, nil, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
timestampRole, err := data.NewRole("timestamp", 1, []string{timestampKey.ID()}, nil, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = kdb.AddRole(rootRole)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = kdb.AddRole(targetsRole)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = kdb.AddRole(snapshotRole)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = kdb.AddRole(timestampRole)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
r.tufRepo = tuf.NewTufRepo(kdb, r.signer)
|
||||
|
||||
r.fileStore, err = store.NewFilesystemStore(
|
||||
path.Join(viper.GetString("tufDir")),
|
||||
"metadata",
|
||||
"json",
|
||||
"targets",
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = r.tufRepo.InitRepo(false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
r.saveRepo()
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddTarget adds a new target to the repository, forcing a timestamps check from TUF
|
||||
func (r *NotaryRepository) AddTarget(target *Target) error {
|
||||
r.bootstrapRepo()
|
||||
|
||||
fmt.Printf("Adding target \"%s\" with sha256 \"%s\" and size %d bytes.\n", target.Name, target.Hashes["sha256"], target.Length)
|
||||
|
||||
meta := data.FileMeta{Length: target.Length, Hashes: target.Hashes}
|
||||
_, err := r.tufRepo.AddTargets("targets", data.Files{target.Name: meta})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
r.saveRepo()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ListTargets lists all targets for the current repository
|
||||
func (r *NotaryRepository) ListTargets() ([]*Target, error) {
|
||||
r.bootstrapRepo()
|
||||
|
||||
c, err := r.bootstrapClient()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = c.Update()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// TODO(diogo): return hashes
|
||||
for name, meta := range r.tufRepo.Targets["targets"].Signed.Targets {
|
||||
fmt.Println(name, " ", meta.Hashes["sha256"], " ", meta.Length)
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// GetTargetByName returns a target given a name
|
||||
func (r *NotaryRepository) GetTargetByName(name string) (*Target, error) {
|
||||
r.bootstrapRepo()
|
||||
|
||||
c, err := r.bootstrapClient()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = c.Update()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
meta := c.TargetMeta(name)
|
||||
if meta == nil {
|
||||
return nil, errors.New("Meta is nil for target")
|
||||
}
|
||||
|
||||
return &Target{Name: name, Hashes: meta.Hashes, Length: meta.Length}, nil
|
||||
}
|
||||
|
||||
// Publish pushes the local changes in signed material to the remote notary-server
|
||||
func (r *NotaryRepository) Publish() error {
|
||||
r.bootstrapRepo()
|
||||
|
||||
remote, err := getRemoteStore(r.Gun)
|
||||
|
||||
root, err := r.fileStore.GetMeta("root", 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
targets, err := r.fileStore.GetMeta("targets", 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
snapshot, err := r.fileStore.GetMeta("snapshot", 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = remote.SetMeta("root", root)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = remote.SetMeta("targets", targets)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = remote.SetMeta("snapshot", snapshot)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *NotaryRepository) bootstrapRepo() error {
|
||||
fileStore, err := store.NewFilesystemStore(
|
||||
path.Join(viper.GetString("tufDir")),
|
||||
"metadata",
|
||||
"json",
|
||||
"targets",
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
kdb := keys.NewDB()
|
||||
tufRepo := tuf.NewTufRepo(kdb, r.signer)
|
||||
|
||||
fmt.Println("Loading trusted collection.")
|
||||
rootJSON, err := fileStore.GetMeta("root", 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
root := &data.Signed{}
|
||||
err = json.Unmarshal(rootJSON, root)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tufRepo.SetRoot(root)
|
||||
targetsJSON, err := fileStore.GetMeta("targets", 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
targets := &data.Signed{}
|
||||
err = json.Unmarshal(targetsJSON, targets)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tufRepo.SetTargets("targets", targets)
|
||||
snapshotJSON, err := fileStore.GetMeta("snapshot", 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
snapshot := &data.Signed{}
|
||||
err = json.Unmarshal(snapshotJSON, snapshot)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tufRepo.SetSnapshot(snapshot)
|
||||
|
||||
r.tufRepo = tufRepo
|
||||
r.fileStore = fileStore
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *NotaryRepository) saveRepo() error {
|
||||
signedRoot, err := r.tufRepo.SignRoot(data.DefaultExpires("root"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
rootJSON, _ := json.Marshal(signedRoot)
|
||||
r.fileStore.SetMeta("root", rootJSON)
|
||||
|
||||
fmt.Println("Saving changes to Trusted Collection.")
|
||||
|
||||
for t, _ := range r.tufRepo.Targets {
|
||||
signedTargets, err := r.tufRepo.SignTargets(t, data.DefaultExpires("targets"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
targetsJSON, _ := json.Marshal(signedTargets)
|
||||
parentDir := filepath.Dir(t)
|
||||
os.MkdirAll(parentDir, 0755)
|
||||
r.fileStore.SetMeta(t, targetsJSON)
|
||||
}
|
||||
|
||||
signedSnapshot, err := r.tufRepo.SignSnapshot(data.DefaultExpires("snapshot"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
snapshotJSON, _ := json.Marshal(signedSnapshot)
|
||||
r.fileStore.SetMeta("snapshot", snapshotJSON)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
/*
|
||||
validateRoot iterates over every root key included in the TUF data and attempts
|
||||
to validate the certificate by first checking for an exact match on the certificate
|
||||
store, and subsequently trying to find a valid chain on the caStore.
|
||||
|
||||
Example TUF Content for root role:
|
||||
"roles" : {
|
||||
"root" : {
|
||||
"threshold" : 1,
|
||||
"keyids" : [
|
||||
"e6da5c303d572712a086e669ecd4df7b785adfc844e0c9a7b1f21a7dfc477a38"
|
||||
]
|
||||
},
|
||||
...
|
||||
}
|
||||
|
||||
Example TUF Content for root key:
|
||||
"e6da5c303d572712a086e669ecd4df7b785adfc844e0c9a7b1f21a7dfc477a38" : {
|
||||
"keytype" : "RSA",
|
||||
"keyval" : {
|
||||
"private" : "",
|
||||
"public" : "Base64-encoded, PEM encoded x509 Certificate"
|
||||
}
|
||||
}
|
||||
*/
|
||||
func (r *NotaryRepository) ValidateRoot(root *data.Signed) error {
|
||||
rootSigned := &data.Root{}
|
||||
err := json.Unmarshal(root.Signed, rootSigned)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
certs := make(map[string]*data.PublicKey)
|
||||
for _, fingerprint := range rootSigned.Roles["root"].KeyIDs {
|
||||
// TODO(dlaw): currently assuming only one cert contained in
|
||||
// public key entry. Need to fix when we want to pass in chains.
|
||||
k, _ := pem.Decode([]byte(rootSigned.Keys["kid"].Public()))
|
||||
|
||||
decodedCerts, err := x509.ParseCertificates(k.Bytes)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// TODO(diogo): Assuming that first certificate is the leaf-cert. Need to
|
||||
// iterate over all decodedCerts and find a non-CA one (should be the last).
|
||||
leafCert := decodedCerts[0]
|
||||
leafID := trustmanager.FingerprintCert(leafCert)
|
||||
|
||||
// Check to see if there is an exact match of this certificate.
|
||||
// Checking the CommonName is not required since ID is calculated over
|
||||
// Cert.Raw. It's included to prevent breaking logic with changes of how the
|
||||
// ID gets computed.
|
||||
_, err = r.certificateStore.GetCertificateByFingerprint(leafID)
|
||||
if err == nil && leafCert.Subject.CommonName == r.Gun {
|
||||
certs[fingerprint] = rootSigned.Keys[fingerprint]
|
||||
}
|
||||
|
||||
// Check to see if this leafCertificate has a chain to one of the Root CAs
|
||||
// of our CA Store.
|
||||
certList := []*x509.Certificate{leafCert}
|
||||
err = trustmanager.Verify(r.caStore, r.Gun, certList)
|
||||
if err == nil {
|
||||
certs[fingerprint] = rootSigned.Keys[fingerprint]
|
||||
}
|
||||
}
|
||||
_, err = signed.VerifyRoot(root, 0, certs, 1)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *NotaryRepository) bootstrapClient() (*tufclient.Client, error) {
|
||||
remote, err := getRemoteStore(r.Gun)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rootJSON, err := remote.GetMeta("root", 5<<20)
|
||||
root := &data.Signed{}
|
||||
err = json.Unmarshal(rootJSON, root)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = r.ValidateRoot(root)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = r.tufRepo.SetRoot(root)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// TODO(dlaw): Where does this keyDB come in
|
||||
kdb := keys.NewDB()
|
||||
|
||||
return tufclient.NewClient(
|
||||
r.tufRepo,
|
||||
remote,
|
||||
kdb,
|
||||
), nil
|
||||
}
|
||||
|
||||
// ListPrivateKeys lists all availables private keys. Does not include private key
|
||||
// material
|
||||
func (c *NotaryClient) ListPrivateKeys() []data.PrivateKey {
|
||||
// TODO(diogo): Make this work
|
||||
for _, k := range c.rootKeyStore.ListAll() {
|
||||
fmt.Println(k)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GenRootKey generates a new root key protected by a given passphrase
|
||||
func (c *NotaryClient) GenRootKey(passphrase string) (*data.PublicKey, error) {
|
||||
// When generating a root key, passing in a GUN to put into the cert
|
||||
// doesn't make sense since this key can be used for multiple distinct
|
||||
// repositories.
|
||||
pemKey, _, err := GenerateKeyAndCert("TUF root key")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not generate private key: %v", err)
|
||||
}
|
||||
|
||||
c.rootKeyStore.AddEncrypted("root", pemKey, passphrase)
|
||||
|
||||
return data.NewPublicKey("RSA", pemKey), nil
|
||||
}
|
||||
|
||||
// GetRepository returns a new repository
|
||||
func (c *NotaryClient) GetRepository(gun string, baseURL string, transport http.RoundTripper) (*NotaryRepository, error) {
|
||||
privKeyStore, err := trustmanager.NewKeyFileStore(viper.GetString("privDir"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
signer := signed.NewSigner(NewCryptoService(gun, privKeyStore))
|
||||
|
||||
return &NotaryRepository{Gun: gun,
|
||||
baseURL: baseURL,
|
||||
transport: transport,
|
||||
signer: signer,
|
||||
caStore: c.caStore,
|
||||
certificateStore: c.certificateStore}, nil
|
||||
}
|
||||
|
||||
func (c *NotaryClient) loadKeys() error {
|
||||
filename := filepath.Base(c.configFile)
|
||||
ext := filepath.Ext(c.configFile)
|
||||
basePath := filepath.Dir(c.configFile)
|
||||
|
||||
viper.SetConfigType(strings.TrimPrefix(ext, "."))
|
||||
viper.SetConfigName(strings.TrimSuffix(filename, ext))
|
||||
viper.AddConfigPath(basePath)
|
||||
|
||||
// Find and read the config file
|
||||
err := viper.ReadInConfig()
|
||||
if err != nil {
|
||||
// Ignore if the configuration file doesn't exist, we can use the defaults
|
||||
if !os.IsNotExist(err) {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Set up the defaults for our config
|
||||
viper.SetDefault("trustDir", path.Join(basePath, path.Dir(trustDir)))
|
||||
viper.SetDefault("privDir", path.Join(basePath, path.Dir(privDir)))
|
||||
viper.SetDefault("tufDir", path.Join(basePath, path.Dir(tufDir)))
|
||||
viper.SetDefault("rootKeysDir", path.Join(basePath, path.Dir(rootKeysDir)))
|
||||
|
||||
// Get the final value for the CA directory
|
||||
finalTrustDir := viper.GetString("trustDir")
|
||||
finalRootKeysDir := viper.GetString("rootKeysDir")
|
||||
|
||||
// Load all CAs that aren't expired and don't use SHA1
|
||||
c.caStore, err = trustmanager.NewX509FilteredFileStore(finalTrustDir, func(cert *x509.Certificate) bool {
|
||||
return cert.IsCA && cert.BasicConstraintsValid && cert.SubjectKeyId != nil &&
|
||||
time.Now().Before(cert.NotAfter) &&
|
||||
cert.SignatureAlgorithm != x509.SHA1WithRSA &&
|
||||
cert.SignatureAlgorithm != x509.DSAWithSHA1 &&
|
||||
cert.SignatureAlgorithm != x509.ECDSAWithSHA1
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Load all individual (non-CA) certificates that aren't expired and don't use SHA1
|
||||
c.certificateStore, err = trustmanager.NewX509FilteredFileStore(finalTrustDir, func(cert *x509.Certificate) bool {
|
||||
return !cert.IsCA &&
|
||||
time.Now().Before(cert.NotAfter) &&
|
||||
cert.SignatureAlgorithm != x509.SHA1WithRSA &&
|
||||
cert.SignatureAlgorithm != x509.DSAWithSHA1 &&
|
||||
cert.SignatureAlgorithm != x509.ECDSAWithSHA1
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.rootKeyStore, err = trustmanager.NewKeyFileStore(finalRootKeysDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Use this to initialize remote HTTPStores from the config settings
|
||||
func getRemoteStore(gun string) (store.RemoteStore, error) {
|
||||
return store.NewHTTPStore(
|
||||
"https://notary:4443/v2/"+gun+"/_trust/tuf/",
|
||||
"",
|
||||
"json",
|
||||
"",
|
||||
"key",
|
||||
)
|
||||
}
|
||||
|
|
@ -1,12 +1,9 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"fmt"
|
||||
"math"
|
||||
"math/big"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
|
@ -207,39 +204,14 @@ func keysGenerate(cmd *cobra.Command, args []string) {
|
|||
fatalf("invalid Global Unique Name: %s", gun)
|
||||
}
|
||||
|
||||
_, cert, err := generateKeyAndCert(gun)
|
||||
if err != nil {
|
||||
fatalf("could not generate key: %v", err)
|
||||
}
|
||||
// _, cert, err := generateKeyAndCert(gun)
|
||||
// if err != nil {
|
||||
// fatalf("could not generate key: %v", err)
|
||||
// }
|
||||
|
||||
certificateStore.AddCert(cert)
|
||||
fingerprint := trustmanager.FingerprintCert(cert)
|
||||
fmt.Println("Generated new keypair with ID: ", fingerprint)
|
||||
}
|
||||
|
||||
func newCertificate(gun, organization string) *x509.Certificate {
|
||||
notBefore := time.Now()
|
||||
notAfter := notBefore.Add(time.Hour * 24 * 365 * 2)
|
||||
|
||||
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
|
||||
serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
|
||||
if err != nil {
|
||||
fatalf("failed to generate serial number: %s", err)
|
||||
}
|
||||
|
||||
return &x509.Certificate{
|
||||
SerialNumber: serialNumber,
|
||||
Subject: pkix.Name{
|
||||
Organization: []string{organization},
|
||||
CommonName: gun,
|
||||
},
|
||||
NotBefore: notBefore,
|
||||
NotAfter: notAfter,
|
||||
|
||||
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
|
||||
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageCodeSigning},
|
||||
BasicConstraintsValid: true,
|
||||
}
|
||||
// certificateStore.AddCert(cert)
|
||||
// fingerprint := trustmanager.FingerprintCert(cert)
|
||||
// fmt.Println("Generated new keypair with ID: ", fingerprint)
|
||||
}
|
||||
|
||||
func printCert(cert *x509.Certificate) {
|
||||
|
|
|
|||
|
|
@ -12,22 +12,20 @@ import (
|
|||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
|
||||
notaryclient "github.com/docker/notary/client"
|
||||
"github.com/docker/notary/trustmanager"
|
||||
)
|
||||
|
||||
const configFileName string = "config"
|
||||
|
||||
// Default paths should end with a '/' so directory creation works correctly
|
||||
const configPath string = ".docker/trust/"
|
||||
const trustDir string = configPath + "trusted_certificates/"
|
||||
const privDir string = configPath + "private/"
|
||||
const tufDir string = configPath + "tuf/"
|
||||
|
||||
var caStore trustmanager.X509Store
|
||||
var certificateStore trustmanager.X509Store
|
||||
var privKeyStore trustmanager.EncryptedFileStore
|
||||
|
||||
var rawOutput bool
|
||||
var nClient *notaryclient.NotaryClient
|
||||
var caStore trustmanager.X509Store
|
||||
var certificateStore trustmanager.X509Store
|
||||
var privKeyStore trustmanager.FileStore
|
||||
|
||||
func init() {
|
||||
logrus.SetLevel(logrus.DebugLevel)
|
||||
|
|
@ -48,6 +46,7 @@ func init() {
|
|||
viper.SetConfigName(configFileName)
|
||||
viper.AddConfigPath(path.Join(homeDir, path.Dir(configPath)))
|
||||
viper.SetConfigType("json")
|
||||
configFilePath := path.Join(homeDir, path.Dir(configPath)) + "/" + configFileName + ".json"
|
||||
|
||||
// Find and read the config file
|
||||
err = viper.ReadInConfig()
|
||||
|
|
@ -60,8 +59,6 @@ func init() {
|
|||
|
||||
// Set up the defaults for our config
|
||||
viper.SetDefault("trustDir", path.Join(homeDir, path.Dir(trustDir)))
|
||||
viper.SetDefault("privDir", path.Join(homeDir, path.Dir(privDir)))
|
||||
viper.SetDefault("tufDir", path.Join(homeDir, path.Dir(tufDir)))
|
||||
|
||||
// Get the final value for the CA directory
|
||||
finalTrustDir := viper.GetString("trustDir")
|
||||
|
|
@ -79,7 +76,7 @@ func init() {
|
|||
fatalf("could not create X509FileStore: %v", err)
|
||||
}
|
||||
|
||||
// Load all individual (non-CA) certificates that aren't expired and don't use SHA1
|
||||
// Load all individual (nonCA) certificates that aren't expired and don't use SHA1
|
||||
certificateStore, err = trustmanager.NewX509FilteredFileStore(finalTrustDir, func(cert *x509.Certificate) bool {
|
||||
return !cert.IsCA &&
|
||||
time.Now().Before(cert.NotAfter) &&
|
||||
|
|
@ -96,6 +93,12 @@ func init() {
|
|||
fatalf("could not create FileStore: %v", err)
|
||||
}
|
||||
|
||||
// TODO(diogo): Client should receive the config
|
||||
nClient, err = notaryclient.NewClient(configFilePath)
|
||||
if err != nil {
|
||||
fatalf("could not create FileStore: %v", err)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func main() {
|
||||
|
|
@ -108,13 +111,13 @@ func main() {
|
|||
NotaryCmd.AddCommand(cmdKeys)
|
||||
NotaryCmd.AddCommand(cmdTufInit)
|
||||
NotaryCmd.AddCommand(cmdTufList)
|
||||
cmdTufList.Flags().BoolVarP(&rawOutput, "raw", "", false, "Instructs notary list to output a non-pretty printed version of the targets list. Useful if you need to parse the list.")
|
||||
cmdTufList.Flags().BoolVarP(&rawOutput, "raw", "", false, "Instructs notary list to output a nonpretty printed version of the targets list. Useful if you need to parse the list.")
|
||||
NotaryCmd.AddCommand(cmdTufAdd)
|
||||
NotaryCmd.AddCommand(cmdTufRemove)
|
||||
NotaryCmd.AddCommand(cmdTufPublish)
|
||||
cmdTufPublish.Flags().StringVarP(&remoteTrustServer, "remote", "r", "", "Remote trust server location")
|
||||
NotaryCmd.AddCommand(cmdTufLookup)
|
||||
cmdTufLookup.Flags().BoolVarP(&rawOutput, "raw", "", false, "Instructs notary lookup to output a non-pretty printed version of the targets list. Useful if you need to parse the list.")
|
||||
cmdTufLookup.Flags().BoolVarP(&rawOutput, "raw", "", false, "Instructs notary lookup to output a nonpretty printed version of the targets list. Useful if you need to parse the list.")
|
||||
cmdTufLookup.Flags().StringVarP(&remoteTrustServer, "remote", "r", "", "Remote trust server location")
|
||||
NotaryCmd.AddCommand(cmdVerify)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,27 +1,16 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/sha256"
|
||||
"crypto/x509"
|
||||
"encoding/json"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/docker/notary/trustmanager"
|
||||
"github.com/endophage/gotuf"
|
||||
"github.com/endophage/gotuf/client"
|
||||
"github.com/endophage/gotuf/data"
|
||||
"github.com/endophage/gotuf/keys"
|
||||
"github.com/endophage/gotuf/signed"
|
||||
notaryclient "github.com/docker/notary/client"
|
||||
"github.com/endophage/gotuf/store"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
var remoteTrustServer string
|
||||
|
|
@ -84,30 +73,19 @@ func tufAdd(cmd *cobra.Command, args []string) {
|
|||
gun := args[0]
|
||||
targetName := args[1]
|
||||
targetPath := args[2]
|
||||
kdb := keys.NewDB()
|
||||
signer := signed.NewSigner(NewCryptoService(gun))
|
||||
repo := tuf.NewTufRepo(kdb, signer)
|
||||
|
||||
b, err := ioutil.ReadFile(targetPath)
|
||||
t := &http.Transport{}
|
||||
repo, err := nClient.GetRepository(gun, "", t)
|
||||
if err != nil {
|
||||
fatalf(err.Error())
|
||||
}
|
||||
|
||||
filestore := bootstrapRepo(gun, repo)
|
||||
|
||||
fmt.Println("Generating metadata for target")
|
||||
meta, err := data.NewFileMeta(bytes.NewBuffer(b))
|
||||
target, err := notaryclient.NewTarget(targetName, targetPath)
|
||||
if err != nil {
|
||||
fatalf(err.Error())
|
||||
}
|
||||
|
||||
fmt.Printf("Adding target \"%s\" with sha256 \"%s\" and size %d bytes.\n", targetName, meta.Hashes["sha256"], meta.Length)
|
||||
_, err = repo.AddTargets("targets", data.Files{targetName: meta})
|
||||
if err != nil {
|
||||
fatalf(err.Error())
|
||||
}
|
||||
|
||||
saveRepo(repo, filestore)
|
||||
repo.AddTarget(target)
|
||||
fmt.Println("Successfully added targets")
|
||||
}
|
||||
|
||||
func tufInit(cmd *cobra.Command, args []string) {
|
||||
|
|
@ -116,92 +94,19 @@ func tufInit(cmd *cobra.Command, args []string) {
|
|||
fatalf("Must specify a GUN")
|
||||
}
|
||||
|
||||
gun := args[0]
|
||||
kdb := keys.NewDB()
|
||||
signer := signed.NewSigner(NewCryptoService(gun))
|
||||
|
||||
remote, err := getRemoteStore(gun)
|
||||
rawTSKey, err := remote.GetKey("timestamp")
|
||||
if err != nil {
|
||||
fatalf(err.Error())
|
||||
}
|
||||
fmt.Println("RawKey: ", string(rawTSKey))
|
||||
parsedKey := &data.TUFKey{}
|
||||
err = json.Unmarshal(rawTSKey, parsedKey)
|
||||
if err != nil {
|
||||
fatalf(err.Error())
|
||||
}
|
||||
timestampKey := data.NewPublicKey(parsedKey.Cipher(), parsedKey.Public())
|
||||
|
||||
rootKey, err := signer.Create("root")
|
||||
if err != nil {
|
||||
fatalf(err.Error())
|
||||
}
|
||||
targetsKey, err := signer.Create("targets")
|
||||
if err != nil {
|
||||
fatalf(err.Error())
|
||||
}
|
||||
snapshotKey, err := signer.Create("snapshot")
|
||||
t := &http.Transport{}
|
||||
repo, err := nClient.GetRepository(args[0], "", t)
|
||||
if err != nil {
|
||||
fatalf(err.Error())
|
||||
}
|
||||
|
||||
kdb.AddKey(rootKey)
|
||||
kdb.AddKey(targetsKey)
|
||||
kdb.AddKey(snapshotKey)
|
||||
kdb.AddKey(timestampKey)
|
||||
|
||||
rootRole, err := data.NewRole("root", 1, []string{rootKey.ID()}, nil, nil)
|
||||
// TODO(diogo): We don't want to generate a new root every time. Ask the user
|
||||
// which key she wants to use if there > 0 root keys available.
|
||||
newRootKey, err := nClient.GenRootKey("passphrase")
|
||||
if err != nil {
|
||||
fatalf(err.Error())
|
||||
}
|
||||
targetsRole, err := data.NewRole("targets", 1, []string{targetsKey.ID()}, nil, nil)
|
||||
if err != nil {
|
||||
fatalf(err.Error())
|
||||
}
|
||||
snapshotRole, err := data.NewRole("snapshot", 1, []string{snapshotKey.ID()}, nil, nil)
|
||||
if err != nil {
|
||||
fatalf(err.Error())
|
||||
}
|
||||
timestampRole, err := data.NewRole("timestamp", 1, []string{timestampKey.ID()}, nil, nil)
|
||||
if err != nil {
|
||||
fatalf(err.Error())
|
||||
}
|
||||
|
||||
err = kdb.AddRole(rootRole)
|
||||
if err != nil {
|
||||
fatalf(err.Error())
|
||||
}
|
||||
err = kdb.AddRole(targetsRole)
|
||||
if err != nil {
|
||||
fatalf(err.Error())
|
||||
}
|
||||
err = kdb.AddRole(snapshotRole)
|
||||
if err != nil {
|
||||
fatalf(err.Error())
|
||||
}
|
||||
err = kdb.AddRole(timestampRole)
|
||||
if err != nil {
|
||||
fatalf(err.Error())
|
||||
}
|
||||
|
||||
repo := tuf.NewTufRepo(kdb, signer)
|
||||
|
||||
filestore, err := store.NewFilesystemStore(
|
||||
path.Join(viper.GetString("tufDir"), gun), // TODO: base trust dir from config
|
||||
"metadata",
|
||||
"json",
|
||||
"targets",
|
||||
)
|
||||
if err != nil {
|
||||
fatalf(err.Error())
|
||||
}
|
||||
|
||||
err = repo.InitRepo(false)
|
||||
if err != nil {
|
||||
fatalf(err.Error())
|
||||
}
|
||||
saveRepo(repo, filestore)
|
||||
repo.Initialize(newRootKey)
|
||||
}
|
||||
|
||||
func tufList(cmd *cobra.Command, args []string) {
|
||||
|
|
@ -210,32 +115,15 @@ func tufList(cmd *cobra.Command, args []string) {
|
|||
fatalf("must specify a GUN")
|
||||
}
|
||||
gun := args[0]
|
||||
kdb := keys.NewDB()
|
||||
repo := tuf.NewTufRepo(kdb, nil)
|
||||
|
||||
remote, err := getRemoteStore(gun)
|
||||
t := &http.Transport{}
|
||||
repo, err := nClient.GetRepository(gun, "", t)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
c, err := bootstrapClient(gun, remote, repo, kdb)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = c.Update()
|
||||
if err != nil {
|
||||
logrus.Error("Error updating client: ", err.Error())
|
||||
return
|
||||
fatalf(err.Error())
|
||||
}
|
||||
|
||||
if rawOutput {
|
||||
for name, meta := range repo.Targets["targets"].Signed.Targets {
|
||||
fmt.Println(name, " ", meta.Hashes["sha256"], " ", meta.Length)
|
||||
}
|
||||
} else {
|
||||
for name, meta := range repo.Targets["targets"].Signed.Targets {
|
||||
fmt.Println(name, " ", meta.Hashes["sha256"], " ", meta.Length)
|
||||
}
|
||||
}
|
||||
// TODO(diogo): Parse Targets and print them
|
||||
_, _ = repo.ListTargets()
|
||||
}
|
||||
|
||||
func tufLookup(cmd *cobra.Command, args []string) {
|
||||
|
|
@ -245,29 +133,20 @@ func tufLookup(cmd *cobra.Command, args []string) {
|
|||
}
|
||||
gun := args[0]
|
||||
targetName := args[1]
|
||||
kdb := keys.NewDB()
|
||||
repo := tuf.NewTufRepo(kdb, nil)
|
||||
|
||||
remote, err := getRemoteStore(gun)
|
||||
c, err := bootstrapClient(gun, remote, repo, kdb)
|
||||
t := &http.Transport{}
|
||||
repo, err := nClient.GetRepository(gun, "", t)
|
||||
if err != nil {
|
||||
return
|
||||
fatalf(err.Error())
|
||||
}
|
||||
err = c.Update()
|
||||
|
||||
// TODO(diogo): Parse Targets and print them
|
||||
target, err := repo.GetTargetByName(targetName)
|
||||
if err != nil {
|
||||
logrus.Error("Error updating client: ", err.Error())
|
||||
return
|
||||
}
|
||||
meta := c.TargetMeta(targetName)
|
||||
if meta == nil {
|
||||
logrus.Infof("Target %s not found in %s.", targetName, gun)
|
||||
return
|
||||
}
|
||||
if rawOutput {
|
||||
fmt.Println(targetName, fmt.Sprintf("sha256:%s", meta.Hashes["sha256"]), meta.Length)
|
||||
} else {
|
||||
fmt.Println(targetName, fmt.Sprintf("sha256:%s", meta.Hashes["sha256"]), meta.Length)
|
||||
fatalf(err.Error())
|
||||
}
|
||||
|
||||
fmt.Println(target.Name, fmt.Sprintf("sha256:%s", target.Hashes["sha256"]), target.Length)
|
||||
}
|
||||
|
||||
func tufPublish(cmd *cobra.Command, args []string) {
|
||||
|
|
@ -277,44 +156,16 @@ func tufPublish(cmd *cobra.Command, args []string) {
|
|||
}
|
||||
|
||||
gun := args[0]
|
||||
|
||||
fmt.Println("Pushing changes to ", gun, ".")
|
||||
|
||||
remote, err := getRemoteStore(gun)
|
||||
filestore, err := store.NewFilesystemStore(
|
||||
path.Join(viper.GetString("tufDir"), gun),
|
||||
"metadata",
|
||||
"json",
|
||||
"targets",
|
||||
)
|
||||
t := &http.Transport{}
|
||||
repo, err := nClient.GetRepository(gun, "", t)
|
||||
if err != nil {
|
||||
fatalf(err.Error())
|
||||
}
|
||||
|
||||
root, err := filestore.GetMeta("root", 0)
|
||||
if err != nil {
|
||||
fatalf(err.Error())
|
||||
}
|
||||
targets, err := filestore.GetMeta("targets", 0)
|
||||
if err != nil {
|
||||
fatalf(err.Error())
|
||||
}
|
||||
snapshot, err := filestore.GetMeta("snapshot", 0)
|
||||
if err != nil {
|
||||
fatalf(err.Error())
|
||||
}
|
||||
|
||||
err = remote.SetMeta("root", root)
|
||||
if err != nil {
|
||||
fatalf(err.Error())
|
||||
}
|
||||
err = remote.SetMeta("targets", targets)
|
||||
if err != nil {
|
||||
fatalf(err.Error())
|
||||
}
|
||||
err = remote.SetMeta("snapshot", snapshot)
|
||||
if err != nil {
|
||||
fatalf(err.Error())
|
||||
}
|
||||
repo.Publish()
|
||||
}
|
||||
|
||||
func tufRemove(cmd *cobra.Command, args []string) {
|
||||
|
|
@ -324,20 +175,19 @@ func tufRemove(cmd *cobra.Command, args []string) {
|
|||
}
|
||||
gun := args[0]
|
||||
targetName := args[1]
|
||||
kdb := keys.NewDB()
|
||||
signer := signed.NewSigner(NewCryptoService(gun))
|
||||
repo := tuf.NewTufRepo(kdb, signer)
|
||||
|
||||
fmt.Println("Removing target ", targetName, " from ", gun)
|
||||
|
||||
filestore := bootstrapRepo(gun, repo)
|
||||
|
||||
err := repo.RemoveTargets("targets", targetName)
|
||||
t := &http.Transport{}
|
||||
_, err := nClient.GetRepository(gun, "", t)
|
||||
if err != nil {
|
||||
fatalf(err.Error())
|
||||
}
|
||||
|
||||
saveRepo(repo, filestore)
|
||||
// TODO(diogo): Implement RemoveTargets in libnotary
|
||||
fmt.Println("Removing target ", targetName, " from ", gun)
|
||||
// repo.RemoveTargets("targets", targetName)
|
||||
// if err != nil {
|
||||
// fatalf(err.Error())
|
||||
// }
|
||||
}
|
||||
|
||||
func verify(cmd *cobra.Command, args []string) {
|
||||
|
|
@ -356,31 +206,22 @@ func verify(cmd *cobra.Command, args []string) {
|
|||
//TODO (diogo): This code is copy/pasted from lookup.
|
||||
gun := args[0]
|
||||
targetName := args[1]
|
||||
kdb := keys.NewDB()
|
||||
repo := tuf.NewTufRepo(kdb, nil)
|
||||
|
||||
remote, err := getRemoteStore(gun)
|
||||
|
||||
c, err := bootstrapClient(gun, remote, repo, kdb)
|
||||
t := &http.Transport{}
|
||||
repo, err := nClient.GetRepository(gun, "", t)
|
||||
if err != nil {
|
||||
logrus.Error("Unable to setup client.")
|
||||
return
|
||||
}
|
||||
|
||||
err = c.Update()
|
||||
if err != nil {
|
||||
fmt.Println("Update failed")
|
||||
fatalf(err.Error())
|
||||
}
|
||||
meta := c.TargetMeta(targetName)
|
||||
if meta == nil {
|
||||
|
||||
// TODO(diogo): Parse Targets and print them
|
||||
target, err := repo.GetTargetByName(targetName)
|
||||
if err != nil {
|
||||
logrus.Error("notary: data not present in the trusted collection.")
|
||||
os.Exit(1)
|
||||
os.Exit(-11)
|
||||
}
|
||||
|
||||
// Create hasher and hash data
|
||||
stdinHash := fmt.Sprintf("sha256:%x", sha256.Sum256(payload))
|
||||
serverHash := fmt.Sprintf("sha256:%s", meta.Hashes["sha256"])
|
||||
serverHash := fmt.Sprintf("sha256:%s", target.Hashes["sha256"])
|
||||
if stdinHash != serverHash {
|
||||
logrus.Error("notary: data not present in the trusted collection.")
|
||||
os.Exit(1)
|
||||
|
|
@ -390,172 +231,6 @@ func verify(cmd *cobra.Command, args []string) {
|
|||
return
|
||||
}
|
||||
|
||||
func saveRepo(repo *tuf.TufRepo, filestore store.MetadataStore) error {
|
||||
fmt.Println("Saving changes to Trusted Collection.")
|
||||
signedRoot, err := repo.SignRoot(data.DefaultExpires("root"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
rootJSON, _ := json.Marshal(signedRoot)
|
||||
filestore.SetMeta("root", rootJSON)
|
||||
|
||||
for r, _ := range repo.Targets {
|
||||
signedTargets, err := repo.SignTargets(r, data.DefaultExpires("targets"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
targetsJSON, _ := json.Marshal(signedTargets)
|
||||
parentDir := filepath.Dir(r)
|
||||
os.MkdirAll(parentDir, 0755)
|
||||
filestore.SetMeta(r, targetsJSON)
|
||||
}
|
||||
|
||||
signedSnapshot, err := repo.SignSnapshot(data.DefaultExpires("snapshot"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
snapshotJSON, _ := json.Marshal(signedSnapshot)
|
||||
filestore.SetMeta("snapshot", snapshotJSON)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func bootstrapClient(gun string, remote store.RemoteStore, repo *tuf.TufRepo, kdb *keys.KeyDB) (*client.Client, error) {
|
||||
rootJSON, err := remote.GetMeta("root", 5<<20)
|
||||
root := &data.Signed{}
|
||||
err = json.Unmarshal(rootJSON, root)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = validateRoot(gun, root)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = repo.SetRoot(root)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return client.NewClient(
|
||||
repo,
|
||||
remote,
|
||||
kdb,
|
||||
), nil
|
||||
}
|
||||
|
||||
/*
|
||||
validateRoot iterates over every root key included in the TUF data and attempts
|
||||
to validate the certificate by first checking for an exact match on the certificate
|
||||
store, and subsequently trying to find a valid chain on the caStore.
|
||||
|
||||
Example TUF Content for root role:
|
||||
"roles" : {
|
||||
"root" : {
|
||||
"threshold" : 1,
|
||||
"keyids" : [
|
||||
"e6da5c303d572712a086e669ecd4df7b785adfc844e0c9a7b1f21a7dfc477a38"
|
||||
]
|
||||
},
|
||||
...
|
||||
}
|
||||
|
||||
Example TUF Content for root key:
|
||||
"e6da5c303d572712a086e669ecd4df7b785adfc844e0c9a7b1f21a7dfc477a38" : {
|
||||
"keytype" : "RSA",
|
||||
"keyval" : {
|
||||
"private" : "",
|
||||
"public" : "Base64-encoded, PEM encoded x509 Certificate"
|
||||
}
|
||||
}
|
||||
*/
|
||||
func validateRoot(gun string, root *data.Signed) error {
|
||||
rootSigned := &data.Root{}
|
||||
err := json.Unmarshal(root.Signed, rootSigned)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
certs := make(map[string]*data.PublicKey)
|
||||
for _, fingerprint := range rootSigned.Roles["root"].KeyIDs {
|
||||
// TODO(dlaw): currently assuming only one cert contained in
|
||||
// public key entry. Need to fix when we want to pass in chains.
|
||||
k, _ := pem.Decode([]byte(rootSigned.Keys["kid"].Public()))
|
||||
|
||||
decodedCerts, err := x509.ParseCertificates(k.Bytes)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// TODO(diogo): Assuming that first certificate is the leaf-cert. Need to
|
||||
// iterate over all decodedCerts and find a non-CA one (should be the last).
|
||||
leafCert := decodedCerts[0]
|
||||
leafID := trustmanager.FingerprintCert(leafCert)
|
||||
|
||||
// Check to see if there is an exact match of this certificate.
|
||||
// Checking the CommonName is not required since ID is calculated over
|
||||
// Cert.Raw. It's included to prevent breaking logic with changes of how the
|
||||
// ID gets computed.
|
||||
_, err = certificateStore.GetCertificateByFingerprint(leafID)
|
||||
if err == nil && leafCert.Subject.CommonName == gun {
|
||||
certs[fingerprint] = rootSigned.Keys[fingerprint]
|
||||
}
|
||||
|
||||
// Check to see if this leafCertificate has a chain to one of the Root CAs
|
||||
// of our CA Store.
|
||||
certList := []*x509.Certificate{leafCert}
|
||||
err = trustmanager.Verify(caStore, gun, certList)
|
||||
if err == nil {
|
||||
certs[fingerprint] = rootSigned.Keys[fingerprint]
|
||||
}
|
||||
}
|
||||
_, err = signed.VerifyRoot(root, 0, certs, 1)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func bootstrapRepo(gun string, repo *tuf.TufRepo) store.MetadataStore {
|
||||
filestore, err := store.NewFilesystemStore(
|
||||
path.Join(viper.GetString("tufDir"), gun),
|
||||
"metadata",
|
||||
"json",
|
||||
"targets",
|
||||
)
|
||||
if err != nil {
|
||||
fatalf(err.Error())
|
||||
}
|
||||
|
||||
fmt.Println("Loading trusted collection.")
|
||||
rootJSON, err := filestore.GetMeta("root", 0)
|
||||
if err != nil {
|
||||
fatalf(err.Error())
|
||||
}
|
||||
root := &data.Signed{}
|
||||
err = json.Unmarshal(rootJSON, root)
|
||||
if err != nil {
|
||||
fatalf(err.Error())
|
||||
}
|
||||
repo.SetRoot(root)
|
||||
targetsJSON, err := filestore.GetMeta("targets", 0)
|
||||
if err != nil {
|
||||
fatalf(err.Error())
|
||||
}
|
||||
targets := &data.Signed{}
|
||||
err = json.Unmarshal(targetsJSON, targets)
|
||||
if err != nil {
|
||||
fatalf(err.Error())
|
||||
}
|
||||
repo.SetTargets("targets", targets)
|
||||
snapshotJSON, err := filestore.GetMeta("snapshot", 0)
|
||||
if err != nil {
|
||||
fatalf(err.Error())
|
||||
}
|
||||
snapshot := &data.Signed{}
|
||||
err = json.Unmarshal(snapshotJSON, snapshot)
|
||||
if err != nil {
|
||||
fatalf(err.Error())
|
||||
}
|
||||
repo.SetSnapshot(snapshot)
|
||||
return filestore
|
||||
}
|
||||
|
||||
// Use this to initialize remote HTTPStores from the config settings
|
||||
func getRemoteStore(gun string) (store.RemoteStore, error) {
|
||||
return store.NewHTTPStore(
|
||||
|
|
|
|||
|
|
@ -5,12 +5,15 @@ import (
|
|||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"math/big"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"github.com/endophage/gotuf/data"
|
||||
)
|
||||
|
|
@ -117,6 +120,14 @@ func loadCertFromPEM(pemBytes []byte) (*x509.Certificate, error) {
|
|||
return nil, errors.New("no certificates found in PEM data")
|
||||
}
|
||||
|
||||
func FingerprintPEMCert(pemCert []byte) (string, error) {
|
||||
cert, err := loadCertFromPEM(pemCert)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return FingerprintCert(cert), nil
|
||||
}
|
||||
|
||||
// FingerprintCert returns a TUF compliant fingerprint for a X509 Certificate
|
||||
func FingerprintCert(cert *x509.Certificate) string {
|
||||
return string(fingerprintCert(cert))
|
||||
|
|
@ -215,3 +226,26 @@ func ParsePEMEncryptedPrivateKey(pemBytes []byte, passphrase string) (crypto.Pri
|
|||
return nil, fmt.Errorf("unsupported key type %q", block.Type)
|
||||
}
|
||||
}
|
||||
|
||||
func NewCertificate(gun, organization string) *x509.Certificate {
|
||||
notBefore := time.Now()
|
||||
notAfter := notBefore.Add(time.Hour * 24 * 365 * 2)
|
||||
|
||||
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
|
||||
// TODO(diogo): Don't silently ignore this error
|
||||
serialNumber, _ := rand.Int(rand.Reader, serialNumberLimit)
|
||||
|
||||
return &x509.Certificate{
|
||||
SerialNumber: serialNumber,
|
||||
Subject: pkix.Name{
|
||||
Organization: []string{organization},
|
||||
CommonName: gun,
|
||||
},
|
||||
NotBefore: notBefore,
|
||||
NotAfter: notAfter,
|
||||
|
||||
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
|
||||
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageCodeSigning},
|
||||
BasicConstraintsValid: true,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue