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 (
|
import (
|
||||||
"crypto"
|
"crypto"
|
||||||
|
|
@ -8,41 +8,42 @@ import (
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"encoding/pem"
|
"encoding/pem"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
"github.com/docker/notary/trustmanager"
|
"github.com/docker/notary/trustmanager"
|
||||||
"github.com/endophage/gotuf/data"
|
"github.com/endophage/gotuf/data"
|
||||||
"github.com/spf13/viper"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type CliCryptoService struct {
|
type CliCryptoService struct {
|
||||||
privateKeys map[string]*data.PrivateKey
|
gun string
|
||||||
gun string
|
keyStore trustmanager.FileStore
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewCryptoService returns an instance ofS cliCryptoService
|
// NewCryptoService returns an instance ofS cliCryptoService
|
||||||
func NewCryptoService(gun string) *CliCryptoService {
|
func NewCryptoService(gun string, keyStore trustmanager.FileStore) *CliCryptoService {
|
||||||
return &CliCryptoService{privateKeys: make(map[string]*data.PrivateKey), gun: gun}
|
return &CliCryptoService{gun: gun, keyStore: keyStore}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create is used to generate keys for targets, snapshots and timestamps
|
// Create is used to generate keys for targets, snapshots and timestamps
|
||||||
func (ccs *CliCryptoService) Create(role string) (*data.PublicKey, error) {
|
func (ccs *CliCryptoService) Create(role string) (*data.PublicKey, error) {
|
||||||
_, cert, err := generateKeyAndCert(ccs.gun)
|
keyData, pemCert, err := GenerateKeyAndCert(ccs.gun)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// PEM ENcode the certificate, which will be put directly inside of TUF's root.json
|
fingerprint, err := trustmanager.FingerprintPEMCert(pemCert)
|
||||||
block := pem.Block{Type: "CERTIFICATE", Bytes: cert.Raw}
|
if err != nil {
|
||||||
pemdata := pem.EncodeToMemory(&block)
|
return nil, err
|
||||||
|
|
||||||
// If this key has the role root, save it as a trusted certificate on our certificateStore
|
|
||||||
if role == "root" {
|
|
||||||
certificateStore.AddCertFromPEM(pemdata)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
// 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))
|
signatures := make([]data.Signature, 0, len(keyIDs))
|
||||||
for _, fingerprint := range keyIDs {
|
for _, fingerprint := range keyIDs {
|
||||||
// Get the PrivateKey filename
|
// Get the PrivateKey filename
|
||||||
privKeyFilename := filepath.Join(viper.GetString("privDir"), ccs.gun, fingerprint+".key")
|
privKeyFilename := filepath.Join(ccs.gun, fingerprint)
|
||||||
// Read PrivateKey from file
|
// Read PrivateKey from file
|
||||||
privPEMBytes, err := ioutil.ReadFile(privKeyFilename)
|
privPEMBytes, err := ccs.keyStore.Get(privKeyFilename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
@ -84,8 +85,9 @@ func (ccs *CliCryptoService) Sign(keyIDs []string, payload []byte) ([]data.Signa
|
||||||
return signatures, nil
|
return signatures, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// generateKeyAndCert deals with the creation and storage of a key and returns a cert
|
// generateKeyAndCert deals with the creation and storage of a key and returns a
|
||||||
func generateKeyAndCert(gun string) (crypto.PrivateKey, *x509.Certificate, error) {
|
// PEM encoded cert
|
||||||
|
func GenerateKeyAndCert(gun string) ([]byte, []byte, error) {
|
||||||
// Generates a new RSA key
|
// Generates a new RSA key
|
||||||
key, err := rsa.GenerateKey(rand.Reader, 2048)
|
key, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -96,7 +98,7 @@ func generateKeyAndCert(gun string) (crypto.PrivateKey, *x509.Certificate, error
|
||||||
// TUF-compliant keyID
|
// TUF-compliant keyID
|
||||||
//TODO (diogo): We're hardcoding the Organization to be the GUN. Probably want to
|
//TODO (diogo): We're hardcoding the Organization to be the GUN. Probably want to
|
||||||
// change it
|
// change it
|
||||||
template := newCertificate(gun, gun)
|
template := trustmanager.NewCertificate(gun, gun)
|
||||||
derBytes, err := x509.CreateCertificate(rand.Reader, template, template, key.Public(), key)
|
derBytes, err := x509.CreateCertificate(rand.Reader, template, template, key.Public(), key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, fmt.Errorf("failed to generate the certificate for key: %v", err)
|
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)
|
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)
|
pemKey, err := trustmanager.KeyToPEM(key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, fmt.Errorf("failed to generate the certificate for key: %v", err)
|
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
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/rand"
|
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"crypto/x509/pkix"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
"math/big"
|
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
@ -207,39 +204,14 @@ func keysGenerate(cmd *cobra.Command, args []string) {
|
||||||
fatalf("invalid Global Unique Name: %s", gun)
|
fatalf("invalid Global Unique Name: %s", gun)
|
||||||
}
|
}
|
||||||
|
|
||||||
_, cert, err := generateKeyAndCert(gun)
|
// _, cert, err := generateKeyAndCert(gun)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
fatalf("could not generate key: %v", err)
|
// fatalf("could not generate key: %v", err)
|
||||||
}
|
// }
|
||||||
|
|
||||||
certificateStore.AddCert(cert)
|
// certificateStore.AddCert(cert)
|
||||||
fingerprint := trustmanager.FingerprintCert(cert)
|
// fingerprint := trustmanager.FingerprintCert(cert)
|
||||||
fmt.Println("Generated new keypair with ID: ", fingerprint)
|
// 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,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func printCert(cert *x509.Certificate) {
|
func printCert(cert *x509.Certificate) {
|
||||||
|
|
|
||||||
|
|
@ -12,22 +12,20 @@ import (
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
|
|
||||||
|
notaryclient "github.com/docker/notary/client"
|
||||||
"github.com/docker/notary/trustmanager"
|
"github.com/docker/notary/trustmanager"
|
||||||
)
|
)
|
||||||
|
|
||||||
const configFileName string = "config"
|
const configFileName string = "config"
|
||||||
|
|
||||||
// Default paths should end with a '/' so directory creation works correctly
|
|
||||||
const configPath string = ".docker/trust/"
|
const configPath string = ".docker/trust/"
|
||||||
const trustDir string = configPath + "trusted_certificates/"
|
const trustDir string = configPath + "trusted_certificates/"
|
||||||
const privDir string = configPath + "private/"
|
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 rawOutput bool
|
||||||
|
var nClient *notaryclient.NotaryClient
|
||||||
|
var caStore trustmanager.X509Store
|
||||||
|
var certificateStore trustmanager.X509Store
|
||||||
|
var privKeyStore trustmanager.FileStore
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
logrus.SetLevel(logrus.DebugLevel)
|
logrus.SetLevel(logrus.DebugLevel)
|
||||||
|
|
@ -48,6 +46,7 @@ func init() {
|
||||||
viper.SetConfigName(configFileName)
|
viper.SetConfigName(configFileName)
|
||||||
viper.AddConfigPath(path.Join(homeDir, path.Dir(configPath)))
|
viper.AddConfigPath(path.Join(homeDir, path.Dir(configPath)))
|
||||||
viper.SetConfigType("json")
|
viper.SetConfigType("json")
|
||||||
|
configFilePath := path.Join(homeDir, path.Dir(configPath)) + "/" + configFileName + ".json"
|
||||||
|
|
||||||
// Find and read the config file
|
// Find and read the config file
|
||||||
err = viper.ReadInConfig()
|
err = viper.ReadInConfig()
|
||||||
|
|
@ -60,8 +59,6 @@ func init() {
|
||||||
|
|
||||||
// Set up the defaults for our config
|
// Set up the defaults for our config
|
||||||
viper.SetDefault("trustDir", path.Join(homeDir, path.Dir(trustDir)))
|
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
|
// Get the final value for the CA directory
|
||||||
finalTrustDir := viper.GetString("trustDir")
|
finalTrustDir := viper.GetString("trustDir")
|
||||||
|
|
@ -79,7 +76,7 @@ func init() {
|
||||||
fatalf("could not create X509FileStore: %v", err)
|
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 {
|
certificateStore, err = trustmanager.NewX509FilteredFileStore(finalTrustDir, func(cert *x509.Certificate) bool {
|
||||||
return !cert.IsCA &&
|
return !cert.IsCA &&
|
||||||
time.Now().Before(cert.NotAfter) &&
|
time.Now().Before(cert.NotAfter) &&
|
||||||
|
|
@ -96,6 +93,12 @@ func init() {
|
||||||
fatalf("could not create FileStore: %v", err)
|
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() {
|
func main() {
|
||||||
|
|
@ -108,13 +111,13 @@ func main() {
|
||||||
NotaryCmd.AddCommand(cmdKeys)
|
NotaryCmd.AddCommand(cmdKeys)
|
||||||
NotaryCmd.AddCommand(cmdTufInit)
|
NotaryCmd.AddCommand(cmdTufInit)
|
||||||
NotaryCmd.AddCommand(cmdTufList)
|
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(cmdTufAdd)
|
||||||
NotaryCmd.AddCommand(cmdTufRemove)
|
NotaryCmd.AddCommand(cmdTufRemove)
|
||||||
NotaryCmd.AddCommand(cmdTufPublish)
|
NotaryCmd.AddCommand(cmdTufPublish)
|
||||||
cmdTufPublish.Flags().StringVarP(&remoteTrustServer, "remote", "r", "", "Remote trust server location")
|
cmdTufPublish.Flags().StringVarP(&remoteTrustServer, "remote", "r", "", "Remote trust server location")
|
||||||
NotaryCmd.AddCommand(cmdTufLookup)
|
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")
|
cmdTufLookup.Flags().StringVarP(&remoteTrustServer, "remote", "r", "", "Remote trust server location")
|
||||||
NotaryCmd.AddCommand(cmdVerify)
|
NotaryCmd.AddCommand(cmdVerify)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,27 +1,16 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"crypto/x509"
|
|
||||||
"encoding/json"
|
|
||||||
"encoding/pem"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
|
||||||
"path/filepath"
|
|
||||||
|
|
||||||
"github.com/Sirupsen/logrus"
|
"github.com/Sirupsen/logrus"
|
||||||
"github.com/docker/notary/trustmanager"
|
notaryclient "github.com/docker/notary/client"
|
||||||
"github.com/endophage/gotuf"
|
|
||||||
"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/endophage/gotuf/store"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/spf13/viper"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var remoteTrustServer string
|
var remoteTrustServer string
|
||||||
|
|
@ -84,30 +73,19 @@ func tufAdd(cmd *cobra.Command, args []string) {
|
||||||
gun := args[0]
|
gun := args[0]
|
||||||
targetName := args[1]
|
targetName := args[1]
|
||||||
targetPath := args[2]
|
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 {
|
if err != nil {
|
||||||
fatalf(err.Error())
|
fatalf(err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
filestore := bootstrapRepo(gun, repo)
|
target, err := notaryclient.NewTarget(targetName, targetPath)
|
||||||
|
|
||||||
fmt.Println("Generating metadata for target")
|
|
||||||
meta, err := data.NewFileMeta(bytes.NewBuffer(b))
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fatalf(err.Error())
|
fatalf(err.Error())
|
||||||
}
|
}
|
||||||
|
repo.AddTarget(target)
|
||||||
fmt.Printf("Adding target \"%s\" with sha256 \"%s\" and size %d bytes.\n", targetName, meta.Hashes["sha256"], meta.Length)
|
fmt.Println("Successfully added targets")
|
||||||
_, err = repo.AddTargets("targets", data.Files{targetName: meta})
|
|
||||||
if err != nil {
|
|
||||||
fatalf(err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
saveRepo(repo, filestore)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func tufInit(cmd *cobra.Command, args []string) {
|
func tufInit(cmd *cobra.Command, args []string) {
|
||||||
|
|
@ -116,92 +94,19 @@ func tufInit(cmd *cobra.Command, args []string) {
|
||||||
fatalf("Must specify a GUN")
|
fatalf("Must specify a GUN")
|
||||||
}
|
}
|
||||||
|
|
||||||
gun := args[0]
|
t := &http.Transport{}
|
||||||
kdb := keys.NewDB()
|
repo, err := nClient.GetRepository(args[0], "", t)
|
||||||
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")
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fatalf(err.Error())
|
fatalf(err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
kdb.AddKey(rootKey)
|
// TODO(diogo): We don't want to generate a new root every time. Ask the user
|
||||||
kdb.AddKey(targetsKey)
|
// which key she wants to use if there > 0 root keys available.
|
||||||
kdb.AddKey(snapshotKey)
|
newRootKey, err := nClient.GenRootKey("passphrase")
|
||||||
kdb.AddKey(timestampKey)
|
|
||||||
|
|
||||||
rootRole, err := data.NewRole("root", 1, []string{rootKey.ID()}, nil, nil)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fatalf(err.Error())
|
fatalf(err.Error())
|
||||||
}
|
}
|
||||||
targetsRole, err := data.NewRole("targets", 1, []string{targetsKey.ID()}, nil, nil)
|
repo.Initialize(newRootKey)
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func tufList(cmd *cobra.Command, args []string) {
|
func tufList(cmd *cobra.Command, args []string) {
|
||||||
|
|
@ -210,32 +115,15 @@ func tufList(cmd *cobra.Command, args []string) {
|
||||||
fatalf("must specify a GUN")
|
fatalf("must specify a GUN")
|
||||||
}
|
}
|
||||||
gun := args[0]
|
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 {
|
if err != nil {
|
||||||
return
|
fatalf(err.Error())
|
||||||
}
|
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if rawOutput {
|
// TODO(diogo): Parse Targets and print them
|
||||||
for name, meta := range repo.Targets["targets"].Signed.Targets {
|
_, _ = repo.ListTargets()
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func tufLookup(cmd *cobra.Command, args []string) {
|
func tufLookup(cmd *cobra.Command, args []string) {
|
||||||
|
|
@ -245,29 +133,20 @@ func tufLookup(cmd *cobra.Command, args []string) {
|
||||||
}
|
}
|
||||||
gun := args[0]
|
gun := args[0]
|
||||||
targetName := args[1]
|
targetName := args[1]
|
||||||
kdb := keys.NewDB()
|
|
||||||
repo := tuf.NewTufRepo(kdb, nil)
|
|
||||||
|
|
||||||
remote, err := getRemoteStore(gun)
|
t := &http.Transport{}
|
||||||
c, err := bootstrapClient(gun, remote, repo, kdb)
|
repo, err := nClient.GetRepository(gun, "", t)
|
||||||
if err != nil {
|
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 {
|
if err != nil {
|
||||||
logrus.Error("Error updating client: ", err.Error())
|
fatalf(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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fmt.Println(target.Name, fmt.Sprintf("sha256:%s", target.Hashes["sha256"]), target.Length)
|
||||||
}
|
}
|
||||||
|
|
||||||
func tufPublish(cmd *cobra.Command, args []string) {
|
func tufPublish(cmd *cobra.Command, args []string) {
|
||||||
|
|
@ -277,44 +156,16 @@ func tufPublish(cmd *cobra.Command, args []string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
gun := args[0]
|
gun := args[0]
|
||||||
|
|
||||||
fmt.Println("Pushing changes to ", gun, ".")
|
fmt.Println("Pushing changes to ", gun, ".")
|
||||||
|
|
||||||
remote, err := getRemoteStore(gun)
|
t := &http.Transport{}
|
||||||
filestore, err := store.NewFilesystemStore(
|
repo, err := nClient.GetRepository(gun, "", t)
|
||||||
path.Join(viper.GetString("tufDir"), gun),
|
|
||||||
"metadata",
|
|
||||||
"json",
|
|
||||||
"targets",
|
|
||||||
)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fatalf(err.Error())
|
fatalf(err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
root, err := filestore.GetMeta("root", 0)
|
repo.Publish()
|
||||||
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())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func tufRemove(cmd *cobra.Command, args []string) {
|
func tufRemove(cmd *cobra.Command, args []string) {
|
||||||
|
|
@ -324,20 +175,19 @@ func tufRemove(cmd *cobra.Command, args []string) {
|
||||||
}
|
}
|
||||||
gun := args[0]
|
gun := args[0]
|
||||||
targetName := args[1]
|
targetName := args[1]
|
||||||
kdb := keys.NewDB()
|
|
||||||
signer := signed.NewSigner(NewCryptoService(gun))
|
|
||||||
repo := tuf.NewTufRepo(kdb, signer)
|
|
||||||
|
|
||||||
fmt.Println("Removing target ", targetName, " from ", gun)
|
t := &http.Transport{}
|
||||||
|
_, err := nClient.GetRepository(gun, "", t)
|
||||||
filestore := bootstrapRepo(gun, repo)
|
|
||||||
|
|
||||||
err := repo.RemoveTargets("targets", targetName)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fatalf(err.Error())
|
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) {
|
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.
|
//TODO (diogo): This code is copy/pasted from lookup.
|
||||||
gun := args[0]
|
gun := args[0]
|
||||||
targetName := args[1]
|
targetName := args[1]
|
||||||
kdb := keys.NewDB()
|
t := &http.Transport{}
|
||||||
repo := tuf.NewTufRepo(kdb, nil)
|
repo, err := nClient.GetRepository(gun, "", t)
|
||||||
|
|
||||||
remote, err := getRemoteStore(gun)
|
|
||||||
|
|
||||||
c, err := bootstrapClient(gun, remote, repo, kdb)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logrus.Error("Unable to setup client.")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
err = c.Update()
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println("Update failed")
|
|
||||||
fatalf(err.Error())
|
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.")
|
logrus.Error("notary: data not present in the trusted collection.")
|
||||||
os.Exit(1)
|
os.Exit(-11)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create hasher and hash data
|
// Create hasher and hash data
|
||||||
stdinHash := fmt.Sprintf("sha256:%x", sha256.Sum256(payload))
|
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 {
|
if stdinHash != serverHash {
|
||||||
logrus.Error("notary: data not present in the trusted collection.")
|
logrus.Error("notary: data not present in the trusted collection.")
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
|
|
@ -390,172 +231,6 @@ func verify(cmd *cobra.Command, args []string) {
|
||||||
return
|
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
|
// Use this to initialize remote HTTPStores from the config settings
|
||||||
func getRemoteStore(gun string) (store.RemoteStore, error) {
|
func getRemoteStore(gun string) (store.RemoteStore, error) {
|
||||||
return store.NewHTTPStore(
|
return store.NewHTTPStore(
|
||||||
|
|
|
||||||
|
|
@ -5,12 +5,15 @@ import (
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"crypto/rsa"
|
"crypto/rsa"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
|
"crypto/x509/pkix"
|
||||||
"encoding/pem"
|
"encoding/pem"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"math/big"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/endophage/gotuf/data"
|
"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")
|
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
|
// FingerprintCert returns a TUF compliant fingerprint for a X509 Certificate
|
||||||
func FingerprintCert(cert *x509.Certificate) string {
|
func FingerprintCert(cert *x509.Certificate) string {
|
||||||
return string(fingerprintCert(cert))
|
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)
|
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