mirror of https://github.com/docker/docs.git
Merge pull request #37 from docker/tuf_rebase
Tuf rebase, ready to merge into master.
This commit is contained in:
commit
7e467501a2
|
@ -43,11 +43,11 @@
|
|||
},
|
||||
{
|
||||
"ImportPath": "github.com/docker/rufus/proto",
|
||||
"Rev": "61b53384b24bfa83e8e0a5f11f28ae83457fd80c"
|
||||
"Rev": "7f61f678c264ae0a329f25cbaa8af6fd55ada7b6"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/endophage/gotuf",
|
||||
"Rev": "682ec56d6a7b60e432bc2560e17d8e1aec84d171"
|
||||
"Rev": "66da486b58ef378c96433af965f61ca0efaccb9a"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/go-sql-driver/mysql",
|
||||
|
|
|
@ -10,6 +10,7 @@ It is generated from these files:
|
|||
|
||||
It has these top-level messages:
|
||||
KeyID
|
||||
Algorithm
|
||||
PublicKey
|
||||
Signature
|
||||
SignatureRequest
|
||||
|
@ -40,10 +41,20 @@ func (m *KeyID) Reset() { *m = KeyID{} }
|
|||
func (m *KeyID) String() string { return proto1.CompactTextString(m) }
|
||||
func (*KeyID) ProtoMessage() {}
|
||||
|
||||
// PublicKey has a KeyID that is used to reference the key and opaque bytes of a publicKey
|
||||
// Type holds the type of crypto algorithm used
|
||||
type Algorithm struct {
|
||||
Algorithm string `protobuf:"bytes,1,opt,name=algorithm" json:"algorithm,omitempty"`
|
||||
}
|
||||
|
||||
func (m *Algorithm) Reset() { *m = Algorithm{} }
|
||||
func (m *Algorithm) String() string { return proto1.CompactTextString(m) }
|
||||
func (*Algorithm) ProtoMessage() {}
|
||||
|
||||
// PublicKey has a KeyID that is used to reference the key, the key type, and opaque bytes of a publicKey
|
||||
type PublicKey struct {
|
||||
KeyID *KeyID `protobuf:"bytes,1,opt,name=keyID" json:"keyID,omitempty"`
|
||||
PublicKey []byte `protobuf:"bytes,2,opt,name=publicKey,proto3" json:"publicKey,omitempty"`
|
||||
Algorithm *Algorithm `protobuf:"bytes,2,opt,name=algorithm" json:"algorithm,omitempty"`
|
||||
PublicKey []byte `protobuf:"bytes,3,opt,name=publicKey,proto3" json:"publicKey,omitempty"`
|
||||
}
|
||||
|
||||
func (m *PublicKey) Reset() { *m = PublicKey{} }
|
||||
|
@ -57,10 +68,18 @@ func (m *PublicKey) GetKeyID() *KeyID {
|
|||
return nil
|
||||
}
|
||||
|
||||
// Signature specifies a KeyID that was used for signing and signed content
|
||||
func (m *PublicKey) GetAlgorithm() *Algorithm {
|
||||
if m != nil {
|
||||
return m.Algorithm
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Signature specifies a KeyID that was used for signing, the key type, and signed content
|
||||
type Signature struct {
|
||||
KeyID *KeyID `protobuf:"bytes,1,opt,name=keyID" json:"keyID,omitempty"`
|
||||
Content []byte `protobuf:"bytes,2,opt,name=content,proto3" json:"content,omitempty"`
|
||||
Algorithm *Algorithm `protobuf:"bytes,2,opt,name=algorithm" json:"algorithm,omitempty"`
|
||||
Content []byte `protobuf:"bytes,3,opt,name=content,proto3" json:"content,omitempty"`
|
||||
}
|
||||
|
||||
func (m *Signature) Reset() { *m = Signature{} }
|
||||
|
@ -74,10 +93,18 @@ func (m *Signature) GetKeyID() *KeyID {
|
|||
return nil
|
||||
}
|
||||
|
||||
// SignatureRequests specifies a KeyID for signing and content to be signed
|
||||
func (m *Signature) GetAlgorithm() *Algorithm {
|
||||
if m != nil {
|
||||
return m.Algorithm
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SignatureRequests specifies a KeyID for signing, the type of signature requested, and content to be signed
|
||||
type SignatureRequest struct {
|
||||
KeyID *KeyID `protobuf:"bytes,1,opt,name=keyID" json:"keyID,omitempty"`
|
||||
Content []byte `protobuf:"bytes,2,opt,name=content,proto3" json:"content,omitempty"`
|
||||
Algorithm *Algorithm `protobuf:"bytes,2,opt,name=algorithm" json:"algorithm,omitempty"`
|
||||
Content []byte `protobuf:"bytes,3,opt,name=content,proto3" json:"content,omitempty"`
|
||||
}
|
||||
|
||||
func (m *SignatureRequest) Reset() { *m = SignatureRequest{} }
|
||||
|
@ -91,6 +118,13 @@ func (m *SignatureRequest) GetKeyID() *KeyID {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (m *SignatureRequest) GetAlgorithm() *Algorithm {
|
||||
if m != nil {
|
||||
return m.Algorithm
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Void represents an empty message type
|
||||
type Void struct {
|
||||
}
|
||||
|
|
|
@ -26,22 +26,30 @@ message KeyID {
|
|||
string ID = 1;
|
||||
}
|
||||
|
||||
// PublicKey has a KeyID that is used to reference the key and opaque bytes of a publicKey
|
||||
// Type holds the type of crypto algorithm used
|
||||
message Algorithm {
|
||||
string algorithm = 1;
|
||||
}
|
||||
|
||||
// PublicKey has a KeyID that is used to reference the key, the key type, and opaque bytes of a publicKey
|
||||
message PublicKey {
|
||||
KeyID keyID = 1;
|
||||
bytes publicKey = 2;
|
||||
Algorithm algorithm = 2;
|
||||
bytes publicKey = 3;
|
||||
}
|
||||
|
||||
// Signature specifies a KeyID that was used for signing and signed content
|
||||
// Signature specifies a KeyID that was used for signing, the key type, and signed content
|
||||
message Signature {
|
||||
KeyID keyID = 1;
|
||||
bytes content = 2;
|
||||
Algorithm algorithm = 2;
|
||||
bytes content = 3;
|
||||
}
|
||||
|
||||
// SignatureRequests specifies a KeyID for signing and content to be signed
|
||||
// SignatureRequests specifies a KeyID for signing, the type of signature requested, and content to be signed
|
||||
message SignatureRequest {
|
||||
KeyID keyID = 1;
|
||||
bytes content = 2;
|
||||
Algorithm algorithm = 2;
|
||||
bytes content = 3;
|
||||
}
|
||||
|
||||
// Void represents an empty message type
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
Aaron Lehmann <aaron.lehmann@docker.com> (github: aaronlehmann)
|
||||
Lewis Marshall <lewis@flynn.io> (github: lmars)
|
||||
Jonathan Rudenberg <jonathan@flynn.io> (github: titanous)
|
|
@ -1,14 +1,22 @@
|
|||
package signed
|
||||
|
||||
import (
|
||||
"encoding/pem"
|
||||
"testing"
|
||||
|
||||
"github.com/endophage/gotuf/data"
|
||||
"github.com/endophage/gotuf/keys"
|
||||
)
|
||||
|
||||
const (
|
||||
testKeyPEM1 = "-----BEGIN PUBLIC KEY-----\nMIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEAnKuXZeefa2LmgxaL5NsM\nzKOHNe+x/nL6ik+lDBCTV6OdcwAhHQS+PONGhrChIUVR6Vth3hUCrreLzPO73Oo5\nVSCuRJ53UronENl6lsa5mFKP8StYLvIDITNvkoT3j52BJIjyNUK9UKY9As2TNqDf\nBEPIRp28ev/NViwGOEkBu2UAbwCIdnDXm8JQErCZA0Ydm7PKGgjLbFsFGrVzqXHK\n6pdzJXlhr9yap3UpgQ/iO9JtoEYB2EXsnSrPc9JRjR30bNHHtnVql3fvinXrAEwq\n3xmN4p+R4VGzfdQN+8Kl/IPjqWB535twhFYEG/B7Ze8IwbygBjK3co/KnOPqMUrM\nBI8ztvPiogz+MvXb8WvarZ6TMTh8ifZI96r7zzqyzjR1hJulEy3IsMGvz8XS2J0X\n7sXoaqszEtXdq5ef5zKVxkiyIQZcbPgmpHLq4MgfdryuVVc/RPASoRIXG4lKaTJj\n1ANMFPxDQpHudCLxwCzjCb+sVa20HBRPTnzo8LSZkI6jAgMBAAE=\n-----END PUBLIC KEY-----"
|
||||
testKeyID1 = "51324b59d4888faa91219ebbe5a3876bb4efb21f0602ddf363cd4c3996ded3d4"
|
||||
|
||||
testKeyPEM2 = "-----BEGIN PUBLIC KEY-----\nMIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEArvqUPYb6JJROPJQglPTj\n5uDrsxQKl34Mo+3pSlBVuD6puE4lDnG649a2YksJy+C8ZIPJgokn5w+C3alh+dMe\nzbdWHHxrY1h9CLpYz5cbMlE16303ubkt1rvwDqEezG0HDBzPaKj4oP9YJ9x7wbsq\ndvFcy+Qc3wWd7UWcieo6E0ihbJkYcY8chRXVLg1rL7EfZ+e3bq5+ojA2ECM5JqzZ\nzgDpqCv5hTCYYZp72MZcG7dfSPAHrcSGIrwg7whzz2UsEtCOpsJTuCl96FPN7kAu\n4w/WyM3+SPzzr4/RQXuY1SrLCFD8ebM2zHt/3ATLhPnGmyG5I0RGYoegFaZ2AViw\nlqZDOYnBtgDvKP0zakMtFMbkh2XuNBUBO7Sjs0YcZMjLkh9gYUHL1yWS3Aqus1Lw\nlI0gHS22oyGObVBWkZEgk/Foy08sECLGao+5VvhmGpfVuiz9OKFUmtPVjWzRE4ng\niekEu4drSxpH41inLGSvdByDWLpcTvWQI9nkgclh3AT/AgMBAAE=\n-----END PUBLIC KEY-----"
|
||||
testKeyID2 = "26f2f5c0fbfa98823bf1ad39d5f3b32575895793baf80f1df675597d5b95dba8"
|
||||
)
|
||||
|
||||
type MockCryptoService struct {
|
||||
testKey keys.PublicKey
|
||||
testKey data.PublicKey
|
||||
}
|
||||
|
||||
func (mts *MockCryptoService) Sign(keyIDs []string, _ []byte) ([]data.Signature, error) {
|
||||
|
@ -19,12 +27,12 @@ func (mts *MockCryptoService) Sign(keyIDs []string, _ []byte) ([]data.Signature,
|
|||
return sigs, nil
|
||||
}
|
||||
|
||||
func (mts *MockCryptoService) Create() (*keys.PublicKey, error) {
|
||||
func (mts *MockCryptoService) Create(_ string) (*data.PublicKey, error) {
|
||||
return &mts.testKey, nil
|
||||
}
|
||||
|
||||
func (mts *MockCryptoService) PublicKeys(keyIDs ...string) (map[string]*keys.PublicKey, error) {
|
||||
keys := map[string]*keys.PublicKey{"testID": &mts.testKey}
|
||||
func (mts *MockCryptoService) PublicKeys(keyIDs ...string) (map[string]*data.PublicKey, error) {
|
||||
keys := map[string]*data.PublicKey{"testID": &mts.testKey}
|
||||
return keys, nil
|
||||
}
|
||||
|
||||
|
@ -32,8 +40,10 @@ var _ CryptoService = &MockCryptoService{}
|
|||
|
||||
// Test signing and ensure the expected signature is added
|
||||
func TestBasicSign(t *testing.T) {
|
||||
testKey, _ := pem.Decode([]byte(testKeyPEM1))
|
||||
k := data.NewPublicKey("rsa", testKey.Bytes)
|
||||
signer := Signer{&MockCryptoService{
|
||||
testKey: keys.PublicKey{ID: "testID"},
|
||||
testKey: *k,
|
||||
}}
|
||||
key, err := signer.Create("root")
|
||||
if err != nil {
|
||||
|
@ -47,7 +57,7 @@ func TestBasicSign(t *testing.T) {
|
|||
t.Fatalf("Incorrect number of signatures: %d", len(testData.Signatures))
|
||||
}
|
||||
|
||||
if testData.Signatures[0].KeyID != "testID" {
|
||||
if testData.Signatures[0].KeyID != testKeyID1 {
|
||||
t.Fatalf("Wrong signature ID returned: %s", testData.Signatures[0].KeyID)
|
||||
}
|
||||
|
||||
|
@ -57,20 +67,21 @@ func TestBasicSign(t *testing.T) {
|
|||
// for the key (N.B. MockCryptoService.Sign will still be called again, but Signer.Sign
|
||||
// should be cleaning previous signatures by the KeyID when asked to sign again)
|
||||
func TestReSign(t *testing.T) {
|
||||
testKey, _ := pem.Decode([]byte(testKeyPEM1))
|
||||
k := data.NewPublicKey("rsa", testKey.Bytes)
|
||||
signer := Signer{&MockCryptoService{
|
||||
testKey: keys.PublicKey{},
|
||||
testKey: *k,
|
||||
}}
|
||||
key := keys.PublicKey{ID: "testID"}
|
||||
testData := data.Signed{}
|
||||
|
||||
signer.Sign(&testData, &key)
|
||||
signer.Sign(&testData, &key)
|
||||
signer.Sign(&testData, k)
|
||||
signer.Sign(&testData, k)
|
||||
|
||||
if len(testData.Signatures) != 1 {
|
||||
t.Fatalf("Incorrect number of signatures: %d", len(testData.Signatures))
|
||||
}
|
||||
|
||||
if testData.Signatures[0].KeyID != "testID" {
|
||||
if testData.Signatures[0].KeyID != testKeyID1 {
|
||||
t.Fatalf("Wrong signature ID returned: %s", testData.Signatures[0].KeyID)
|
||||
}
|
||||
|
||||
|
@ -78,19 +89,21 @@ func TestReSign(t *testing.T) {
|
|||
|
||||
func TestMultiSign(t *testing.T) {
|
||||
signer := Signer{&MockCryptoService{}}
|
||||
key := keys.PublicKey{ID: "testID1"}
|
||||
testData := data.Signed{}
|
||||
|
||||
signer.Sign(&testData, &key)
|
||||
testKey, _ := pem.Decode([]byte(testKeyPEM1))
|
||||
key := data.NewPublicKey("rsa", testKey.Bytes)
|
||||
signer.Sign(&testData, key)
|
||||
|
||||
key = keys.PublicKey{ID: "testID2"}
|
||||
signer.Sign(&testData, &key)
|
||||
testKey, _ = pem.Decode([]byte(testKeyPEM2))
|
||||
key = data.NewPublicKey("rsa", testKey.Bytes)
|
||||
signer.Sign(&testData, key)
|
||||
|
||||
if len(testData.Signatures) != 2 {
|
||||
t.Fatalf("Incorrect number of signatures: %d", len(testData.Signatures))
|
||||
}
|
||||
|
||||
keyIDs := map[string]struct{}{"testID1": struct{}{}, "testID2": struct{}{}}
|
||||
keyIDs := map[string]struct{}{testKeyID1: struct{}{}, testKeyID2: struct{}{}}
|
||||
for _, sig := range testData.Signatures {
|
||||
if _, ok := keyIDs[sig.KeyID]; !ok {
|
||||
t.Fatalf("Got a signature we didn't expect: %s", sig.KeyID)
|
||||
|
@ -100,8 +113,10 @@ func TestMultiSign(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestCreate(t *testing.T) {
|
||||
testKey, _ := pem.Decode([]byte(testKeyPEM1))
|
||||
k := data.NewPublicKey("rsa", testKey.Bytes)
|
||||
signer := Signer{&MockCryptoService{
|
||||
testKey: keys.PublicKey{ID: "testID"},
|
||||
testKey: *k,
|
||||
}}
|
||||
|
||||
key, err := signer.Create("root")
|
||||
|
@ -109,7 +124,7 @@ func TestCreate(t *testing.T) {
|
|||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if key.ID != "testID" {
|
||||
t.Fatalf("Expected key ID not found: %s", key.ID)
|
||||
if key.ID() != testKeyID1 {
|
||||
t.Fatalf("Expected key ID not found: %s", key.ID())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
package signed
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/rsa"
|
||||
"crypto/sha256"
|
||||
"crypto/x509"
|
||||
_ "crypto"
|
||||
_ "crypto/rsa"
|
||||
_ "crypto/sha256"
|
||||
_ "crypto/x509"
|
||||
"testing"
|
||||
)
|
||||
|
||||
|
|
|
@ -23,7 +23,7 @@ func (VerifySuite) Test(c *C) {
|
|||
signer := NewSigner(trust)
|
||||
type test struct {
|
||||
name string
|
||||
keys []*keys.PublicKey
|
||||
keys []*data.PublicKey
|
||||
roles map[string]*data.Role
|
||||
s *data.Signed
|
||||
ver int
|
||||
|
@ -81,7 +81,7 @@ func (VerifySuite) Test(c *C) {
|
|||
k, _ := signer.Create("root")
|
||||
signer.Sign(t.s, k)
|
||||
t.keys = append(t.keys, k)
|
||||
t.roles["root"].KeyIDs = append(t.roles["root"].KeyIDs, k.ID)
|
||||
t.roles["root"].KeyIDs = append(t.roles["root"].KeyIDs, k.ID())
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -139,7 +139,7 @@ func (VerifySuite) Test(c *C) {
|
|||
{
|
||||
name: "expired",
|
||||
exp: &expiredTime,
|
||||
err: ErrExpired{expiredTime},
|
||||
err: ErrExpired{expiredTime.Format("2006-01-02 15:04:05 MST")},
|
||||
},
|
||||
}
|
||||
for _, t := range tests {
|
||||
|
@ -154,25 +154,28 @@ func (VerifySuite) Test(c *C) {
|
|||
t.exp = &expires
|
||||
}
|
||||
if t.typ == "" {
|
||||
t.typ = t.role
|
||||
t.typ = data.TUFTypes[t.role]
|
||||
}
|
||||
if t.keys == nil && t.s == nil {
|
||||
k, _ := signer.Create("root")
|
||||
meta := &signedMeta{Type: t.typ, Version: t.ver, Expires: *t.exp}
|
||||
meta := &signedMeta{Type: t.typ, Version: t.ver, Expires: t.exp.Format("2006-01-02 15:04:05 MST")}
|
||||
|
||||
b, err := cjson.Marshal(meta)
|
||||
c.Assert(err, IsNil)
|
||||
s := &data.Signed{Signed: b}
|
||||
signer.Sign(s, k)
|
||||
t.s = s
|
||||
t.keys = []*keys.PublicKey{k}
|
||||
t.keys = []*data.PublicKey{k}
|
||||
}
|
||||
if t.roles == nil {
|
||||
t.roles = map[string]*data.Role{
|
||||
"root": &data.Role{
|
||||
KeyIDs: []string{t.keys[0].ID},
|
||||
RootRole: data.RootRole{
|
||||
KeyIDs: []string{t.keys[0].ID()},
|
||||
Threshold: 1,
|
||||
},
|
||||
Name: "root",
|
||||
},
|
||||
}
|
||||
}
|
||||
if t.mut != nil {
|
||||
|
@ -181,11 +184,10 @@ func (VerifySuite) Test(c *C) {
|
|||
|
||||
db := keys.NewDB()
|
||||
for _, k := range t.keys {
|
||||
err := db.AddKey(k)
|
||||
c.Assert(err, IsNil)
|
||||
db.AddKey(k)
|
||||
}
|
||||
for n, r := range t.roles {
|
||||
err := db.AddRole(n, r)
|
||||
for _, r := range t.roles {
|
||||
err := db.AddRole(r)
|
||||
c.Assert(err, IsNil)
|
||||
}
|
||||
|
||||
|
@ -203,5 +205,5 @@ func assertErrExpired(c *C, err error, expected ErrExpired) {
|
|||
if !ok {
|
||||
c.Fatalf("expected err to have type ErrExpired, got %T", err)
|
||||
}
|
||||
c.Assert(actual.Expired.Unix(), Equals, expected.Expired.Unix())
|
||||
c.Assert(actual.Expired, Equals, expected.Expired)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
package store
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type ErrMetaNotFound struct {
|
||||
role string
|
||||
}
|
||||
|
||||
func (err ErrMetaNotFound) Error() string {
|
||||
return fmt.Sprintf("no metadata for %s", err.role)
|
||||
}
|
|
@ -7,6 +7,7 @@ import (
|
|||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path"
|
||||
|
||||
|
@ -61,6 +62,9 @@ func (s HTTPStore) GetMeta(name string, size int64) (json.RawMessage, error) {
|
|||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode == http.StatusNotFound {
|
||||
return nil, &ErrMetaNotFound{role: name}
|
||||
}
|
||||
b := io.LimitReader(resp.Body, int64(size))
|
||||
body, err := ioutil.ReadAll(b)
|
||||
|
||||
|
|
|
@ -177,6 +177,22 @@ func (tr *TufRepo) UpdateDelegations(role *data.Role, keys []data.Key, before st
|
|||
// also relies on the keysDB having already been populated with the keys and
|
||||
// roles.
|
||||
func (tr *TufRepo) InitRepo(consistent bool) error {
|
||||
err := tr.InitRoot(consistent)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tr.InitTargets()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tr.InitSnapshot()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return tr.InitTimestamp()
|
||||
}
|
||||
|
||||
func (tr *TufRepo) InitRoot(consistent bool) error {
|
||||
rootRoles := make(map[string]*data.RootRole)
|
||||
rootKeys := make(map[string]*data.PublicKey)
|
||||
for _, r := range data.ValidRoles {
|
||||
|
@ -199,15 +215,21 @@ func (tr *TufRepo) InitRepo(consistent bool) error {
|
|||
return err
|
||||
}
|
||||
tr.Root = root
|
||||
return nil
|
||||
}
|
||||
|
||||
func (tr *TufRepo) InitTargets() error {
|
||||
targets := data.NewTargets()
|
||||
tr.Targets[data.ValidRoles["targets"]] = targets
|
||||
return nil
|
||||
}
|
||||
|
||||
signedRoot, err := tr.SignRoot(data.DefaultExpires("root"))
|
||||
func (tr *TufRepo) InitSnapshot() error {
|
||||
signedRoot, err := tr.SignRoot(data.DefaultExpires("root"), nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
signedTargets, err := tr.SignTargets("targets", data.DefaultExpires("targets"))
|
||||
signedTargets, err := tr.SignTargets("targets", data.DefaultExpires("targets"), nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -216,8 +238,11 @@ func (tr *TufRepo) InitRepo(consistent bool) error {
|
|||
return err
|
||||
}
|
||||
tr.Snapshot = snapshot
|
||||
return nil
|
||||
}
|
||||
|
||||
signedSnapshot, err := tr.SignSnapshot(data.DefaultExpires("snapshot"))
|
||||
func (tr *TufRepo) InitTimestamp() error {
|
||||
signedSnapshot, err := tr.SignSnapshot(data.DefaultExpires("snapshot"), nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -428,7 +453,7 @@ func (tr *TufRepo) UpdateTimestamp(s *data.Signed) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (tr *TufRepo) SignRoot(expires time.Time) (*data.Signed, error) {
|
||||
func (tr *TufRepo) SignRoot(expires time.Time, signer *signed.Signer) (*data.Signed, error) {
|
||||
logrus.Debug("SignRoot")
|
||||
if tr.Root.Dirty {
|
||||
tr.Root.Signed.Version++
|
||||
|
@ -438,7 +463,7 @@ func (tr *TufRepo) SignRoot(expires time.Time) (*data.Signed, error) {
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
signed, err = tr.sign(signed, *root)
|
||||
signed, err = tr.sign(signed, *root, signer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -446,7 +471,7 @@ func (tr *TufRepo) SignRoot(expires time.Time) (*data.Signed, error) {
|
|||
return signed, nil
|
||||
}
|
||||
|
||||
func (tr *TufRepo) SignTargets(role string, expires time.Time) (*data.Signed, error) {
|
||||
func (tr *TufRepo) SignTargets(role string, expires time.Time, signer *signed.Signer) (*data.Signed, error) {
|
||||
logrus.Debug("SignTargets")
|
||||
logrus.Debug("Got targets data.Signed object")
|
||||
if tr.Targets[role].Dirty {
|
||||
|
@ -458,7 +483,7 @@ func (tr *TufRepo) SignTargets(role string, expires time.Time) (*data.Signed, er
|
|||
}
|
||||
targets := tr.keysDB.GetRole(role)
|
||||
logrus.Debug("About to sign ", role)
|
||||
signed, err = tr.sign(signed, *targets)
|
||||
signed, err = tr.sign(signed, *targets, signer)
|
||||
if err != nil {
|
||||
logrus.Debug("errored signing ", role)
|
||||
return nil, err
|
||||
|
@ -476,10 +501,10 @@ func (tr *TufRepo) SignTargets(role string, expires time.Time) (*data.Signed, er
|
|||
}
|
||||
}
|
||||
|
||||
func (tr *TufRepo) SignSnapshot(expires time.Time) (*data.Signed, error) {
|
||||
func (tr *TufRepo) SignSnapshot(expires time.Time, signer *signed.Signer) (*data.Signed, error) {
|
||||
logrus.Debug("SignSnapshot")
|
||||
if tr.Root.Dirty {
|
||||
signedRoot, err := tr.SignRoot(data.DefaultExpires("root"))
|
||||
signedRoot, err := tr.SignRoot(data.DefaultExpires("root"), signer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -493,7 +518,7 @@ func (tr *TufRepo) SignSnapshot(expires time.Time) (*data.Signed, error) {
|
|||
if !targets.Dirty {
|
||||
continue
|
||||
}
|
||||
signedTargets, err := tr.SignTargets(role, data.DefaultExpires("targets"))
|
||||
signedTargets, err := tr.SignTargets(role, data.DefaultExpires("targets"), signer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -510,7 +535,7 @@ func (tr *TufRepo) SignSnapshot(expires time.Time) (*data.Signed, error) {
|
|||
return nil, err
|
||||
}
|
||||
snapshot := tr.keysDB.GetRole(data.ValidRoles["snapshot"])
|
||||
signed, err = tr.sign(signed, *snapshot)
|
||||
signed, err = tr.sign(signed, *snapshot, signer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -525,10 +550,10 @@ func (tr *TufRepo) SignSnapshot(expires time.Time) (*data.Signed, error) {
|
|||
}
|
||||
}
|
||||
|
||||
func (tr *TufRepo) SignTimestamp(expires time.Time) (*data.Signed, error) {
|
||||
func (tr *TufRepo) SignTimestamp(expires time.Time, signer *signed.Signer) (*data.Signed, error) {
|
||||
logrus.Debug("SignTimestamp")
|
||||
if tr.Snapshot.Dirty {
|
||||
signedSnapshot, err := tr.SignSnapshot(data.DefaultExpires("snapshot"))
|
||||
signedSnapshot, err := tr.SignSnapshot(data.DefaultExpires("snapshot"), signer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -544,7 +569,7 @@ func (tr *TufRepo) SignTimestamp(expires time.Time) (*data.Signed, error) {
|
|||
return nil, err
|
||||
}
|
||||
timestamp := tr.keysDB.GetRole(data.ValidRoles["timestamp"])
|
||||
signed, err = tr.sign(signed, *timestamp)
|
||||
signed, err = tr.sign(signed, *timestamp, signer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -560,7 +585,7 @@ func (tr *TufRepo) SignTimestamp(expires time.Time) (*data.Signed, error) {
|
|||
}
|
||||
}
|
||||
|
||||
func (tr TufRepo) sign(signed *data.Signed, role data.Role) (*data.Signed, error) {
|
||||
func (tr TufRepo) sign(signed *data.Signed, role data.Role, signer *signed.Signer) (*data.Signed, error) {
|
||||
ks := make([]*data.PublicKey, 0, len(role.KeyIDs))
|
||||
for _, kid := range role.KeyIDs {
|
||||
k := tr.keysDB.GetKey(kid)
|
||||
|
@ -572,9 +597,16 @@ func (tr TufRepo) sign(signed *data.Signed, role data.Role) (*data.Signed, error
|
|||
if len(ks) < 1 {
|
||||
return nil, keys.ErrInvalidKey
|
||||
}
|
||||
if signer != nil {
|
||||
err := signer.Sign(signed, ks...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
err := tr.signer.Sign(signed, ks...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return signed, nil
|
||||
}
|
||||
|
|
|
@ -88,7 +88,7 @@ func writeRepo(t *testing.T, dir string, repo *TufRepo) {
|
|||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
signedRoot, err := repo.SignRoot(data.DefaultExpires("root"))
|
||||
signedRoot, err := repo.SignRoot(data.DefaultExpires("root"), nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -96,7 +96,7 @@ func writeRepo(t *testing.T, dir string, repo *TufRepo) {
|
|||
ioutil.WriteFile(dir+"/root.json", rootJSON, 0755)
|
||||
|
||||
for r, _ := range repo.Targets {
|
||||
signedTargets, err := repo.SignTargets(r, data.DefaultExpires("targets"))
|
||||
signedTargets, err := repo.SignTargets(r, data.DefaultExpires("targets"), nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -107,14 +107,14 @@ func writeRepo(t *testing.T, dir string, repo *TufRepo) {
|
|||
ioutil.WriteFile(p, targetsJSON, 0755)
|
||||
}
|
||||
|
||||
signedSnapshot, err := repo.SignSnapshot(data.DefaultExpires("snapshot"))
|
||||
signedSnapshot, err := repo.SignSnapshot(data.DefaultExpires("snapshot"), nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
snapshotJSON, _ := json.Marshal(signedSnapshot)
|
||||
ioutil.WriteFile(dir+"/snapshot.json", snapshotJSON, 0755)
|
||||
|
||||
signedTimestamp, err := repo.SignTimestamp(data.DefaultExpires("timestamp"))
|
||||
signedTimestamp, err := repo.SignTimestamp(data.DefaultExpires("timestamp"), nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,47 @@
|
|||
package changelist
|
||||
|
||||
// TufChange represents a change to a TUF repo
|
||||
type TufChange struct {
|
||||
// Abbreviated because Go doesn't permit a field and method of the same name
|
||||
Actn int `json:"action"`
|
||||
Role string `json:"role"`
|
||||
ChangeType string `json:"type"`
|
||||
ChangePath string `json:"path"`
|
||||
Data []byte `json:"data"`
|
||||
}
|
||||
|
||||
// NewTufChange initializes a tufChange object
|
||||
func NewTufChange(action int, role, changeType, changePath string, content []byte) *TufChange {
|
||||
return &TufChange{
|
||||
Actn: action,
|
||||
Role: role,
|
||||
ChangeType: changeType,
|
||||
ChangePath: changePath,
|
||||
Data: content,
|
||||
}
|
||||
}
|
||||
|
||||
// Action return c.Actn
|
||||
func (c TufChange) Action() int {
|
||||
return c.Actn
|
||||
}
|
||||
|
||||
// Scope returns c.Role
|
||||
func (c TufChange) Scope() string {
|
||||
return c.Role
|
||||
}
|
||||
|
||||
// Type returns c.ChangeType
|
||||
func (c TufChange) Type() string {
|
||||
return c.ChangeType
|
||||
}
|
||||
|
||||
// Path return c.ChangePath
|
||||
func (c TufChange) Path() string {
|
||||
return c.ChangePath
|
||||
}
|
||||
|
||||
// Content returns c.Data
|
||||
func (c TufChange) Content() []byte {
|
||||
return c.Data
|
||||
}
|
|
@ -0,0 +1,112 @@
|
|||
package changelist
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
|
||||
// AppendChangelist represents a list of TUF changes
|
||||
type AppendChangelist struct {
|
||||
path string
|
||||
file *os.File
|
||||
closed bool
|
||||
}
|
||||
|
||||
// NewAppendChangelist is a convinience method that returns an append only TUF
|
||||
// change list
|
||||
func NewAppendChangelist(path string) (*AppendChangelist, error) {
|
||||
file, err := os.OpenFile(path, os.O_CREATE|os.O_RDWR|os.O_APPEND, 0600)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &AppendChangelist{
|
||||
path: path,
|
||||
file: file,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// List returns a list of Changes
|
||||
func (cl *AppendChangelist) List() []Change {
|
||||
cl.file.Seek(0, 0) // seek to start of file
|
||||
var changes []Change
|
||||
scnr := bufio.NewScanner(cl.file)
|
||||
for scnr.Scan() {
|
||||
line := scnr.Bytes()
|
||||
c := &TufChange{}
|
||||
err := json.Unmarshal(line, c)
|
||||
if err != nil {
|
||||
// TODO(david): How should we handle this?
|
||||
fmt.Println(err.Error())
|
||||
continue
|
||||
}
|
||||
changes = append(changes, c)
|
||||
}
|
||||
return changes
|
||||
}
|
||||
|
||||
// Add adds a change to the append only changelist
|
||||
func (cl *AppendChangelist) Add(c Change) error {
|
||||
cl.file.Seek(0, 2) // seek to end of file
|
||||
entry, err := json.Marshal(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
n, err := cl.file.Write(entry)
|
||||
if err != nil {
|
||||
if n > 0 {
|
||||
// trim partial write if necessary
|
||||
size, _ := cl.file.Seek(-int64(n), 2)
|
||||
cl.file.Truncate(size)
|
||||
}
|
||||
return err
|
||||
}
|
||||
cl.file.Write([]byte("\n"))
|
||||
cl.file.Sync()
|
||||
return nil
|
||||
}
|
||||
|
||||
// Clear empties the changelist file. It does not currently
|
||||
// support archiving
|
||||
func (cl *AppendChangelist) Clear(archive string) error {
|
||||
cl.file.Seek(0, 0) // seek to start
|
||||
cl.file.Truncate(0) // truncate
|
||||
cl.file.Sync()
|
||||
return nil
|
||||
}
|
||||
|
||||
// Close marks the change list as closed
|
||||
func (cl *AppendChangelist) Close() error {
|
||||
cl.file.Sync()
|
||||
cl.closed = true
|
||||
return cl.file.Close()
|
||||
}
|
||||
|
||||
// memChangeList implements a simple in memory change list.
|
||||
type memChangelist struct {
|
||||
changes []Change
|
||||
}
|
||||
|
||||
// List returns a list of Changes
|
||||
func (cl memChangelist) List() []Change {
|
||||
return cl.changes
|
||||
}
|
||||
|
||||
// Add adds a change to the in-memory change list
|
||||
func (cl *memChangelist) Add(c Change) error {
|
||||
cl.changes = append(cl.changes, c)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Clear empties the changelist file.
|
||||
func (cl *memChangelist) Clear(archive string) error {
|
||||
// appending to a nil list initializes it.
|
||||
cl.changes = nil
|
||||
return nil
|
||||
}
|
||||
|
||||
// Close is a no-op in this in-memory change-list
|
||||
func (cl *memChangelist) Close() error {
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
package changelist
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestFileChangelist(t *testing.T) {
|
||||
tmpDir, err := ioutil.TempDir("/tmp", "test")
|
||||
if err != nil {
|
||||
t.Fatal(err.Error())
|
||||
}
|
||||
defer os.RemoveAll(tmpDir)
|
||||
file := path.Join(tmpDir, "list")
|
||||
cl, err := NewAppendChangelist(file)
|
||||
assert.Nil(t, err, "Error initializing appendChangelist")
|
||||
|
||||
c := NewTufChange(ActionCreate, "targets", "target", "test/targ", []byte{1})
|
||||
|
||||
err = cl.Add(c)
|
||||
assert.Nil(t, err, "Non-nil error while adding change")
|
||||
|
||||
cs := cl.List()
|
||||
|
||||
assert.Equal(t, 1, len(cs), "List should have returned exactly one item")
|
||||
assert.Equal(t, c.Action(), cs[0].Action(), "Action mismatch")
|
||||
assert.Equal(t, c.Scope(), cs[0].Scope(), "Scope mismatch")
|
||||
assert.Equal(t, c.Type(), cs[0].Type(), "Type mismatch")
|
||||
assert.Equal(t, c.Path(), cs[0].Path(), "Path mismatch")
|
||||
assert.Equal(t, c.Content(), cs[0].Content(), "Content mismatch")
|
||||
|
||||
err = cl.Clear("")
|
||||
assert.Nil(t, err, "Non-nil error while clearing")
|
||||
|
||||
cs = cl.List()
|
||||
assert.Equal(t, 0, len(cs), "List should be empty")
|
||||
}
|
|
@ -0,0 +1,116 @@
|
|||
package changelist
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"code.google.com/p/go-uuid/uuid"
|
||||
"github.com/Sirupsen/logrus"
|
||||
)
|
||||
|
||||
// FileChangelist stores all the changes as files
|
||||
type FileChangelist struct {
|
||||
dir string
|
||||
}
|
||||
|
||||
// NewFileChangelist is a convenience method for returning FileChangeLists
|
||||
func NewFileChangelist(dir string) (*FileChangelist, error) {
|
||||
logrus.Debug("Making dir path: ", dir)
|
||||
err := os.MkdirAll(dir, 0700)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &FileChangelist{dir: dir}, nil
|
||||
}
|
||||
|
||||
// List returns a list of sorted changes
|
||||
func (cl FileChangelist) List() []Change {
|
||||
var changes []Change
|
||||
dir, err := os.Open(cl.dir)
|
||||
if err != nil {
|
||||
return changes
|
||||
}
|
||||
defer dir.Close()
|
||||
fileInfos, err := dir.Readdir(0)
|
||||
if err != nil {
|
||||
return changes
|
||||
}
|
||||
sort.Sort(fileChanges(fileInfos))
|
||||
for _, f := range fileInfos {
|
||||
if f.IsDir() {
|
||||
continue
|
||||
}
|
||||
raw, err := ioutil.ReadFile(path.Join(cl.dir, f.Name()))
|
||||
if err != nil {
|
||||
// TODO(david): How should we handle this?
|
||||
fmt.Println(err.Error())
|
||||
continue
|
||||
}
|
||||
c := &TufChange{}
|
||||
err = json.Unmarshal(raw, c)
|
||||
if err != nil {
|
||||
// TODO(david): How should we handle this?
|
||||
fmt.Println(err.Error())
|
||||
continue
|
||||
}
|
||||
changes = append(changes, c)
|
||||
}
|
||||
return changes
|
||||
}
|
||||
|
||||
// Add adds a change to the file change list
|
||||
func (cl FileChangelist) Add(c Change) error {
|
||||
cJSON, err := json.Marshal(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
filename := fmt.Sprintf("%020d_%s.change", time.Now().UnixNano(), uuid.New())
|
||||
return ioutil.WriteFile(path.Join(cl.dir, filename), cJSON, 0644)
|
||||
}
|
||||
|
||||
// Clear clears the change list
|
||||
func (cl FileChangelist) Clear(archive string) error {
|
||||
dir, err := os.Open(cl.dir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer dir.Close()
|
||||
files, err := dir.Readdir(0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, f := range files {
|
||||
os.Remove(path.Join(cl.dir, f.Name()))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Close is a no-op
|
||||
func (cl FileChangelist) Close() error {
|
||||
// Nothing to do here
|
||||
return nil
|
||||
}
|
||||
|
||||
type fileChanges []os.FileInfo
|
||||
|
||||
// Len returns the length of a file change list
|
||||
func (cs fileChanges) Len() int {
|
||||
return len(cs)
|
||||
}
|
||||
|
||||
// Less compares the names of two different file changes
|
||||
func (cs fileChanges) Less(i, j int) bool {
|
||||
return cs[i].Name() < cs[j].Name()
|
||||
}
|
||||
|
||||
// Swap swaps the position of two file changes
|
||||
func (cs fileChanges) Swap(i, j int) {
|
||||
tmp := cs[i]
|
||||
cs[i] = cs[j]
|
||||
cs[j] = tmp
|
||||
}
|
|
@ -0,0 +1,76 @@
|
|||
package changelist
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestAdd(t *testing.T) {
|
||||
tmpDir, err := ioutil.TempDir("/tmp", "test")
|
||||
if err != nil {
|
||||
t.Fatal(err.Error())
|
||||
}
|
||||
defer os.RemoveAll(tmpDir)
|
||||
|
||||
cl, err := NewFileChangelist(tmpDir)
|
||||
assert.Nil(t, err, "Error initializing fileChangelist")
|
||||
|
||||
c := NewTufChange(ActionCreate, "targets", "target", "test/targ", []byte{1})
|
||||
err = cl.Add(c)
|
||||
assert.Nil(t, err, "Non-nil error while adding change")
|
||||
|
||||
cs := cl.List()
|
||||
|
||||
assert.Equal(t, 1, len(cs), "List should have returned exactly one item")
|
||||
assert.Equal(t, c.Action(), cs[0].Action(), "Action mismatch")
|
||||
assert.Equal(t, c.Scope(), cs[0].Scope(), "Scope mismatch")
|
||||
assert.Equal(t, c.Type(), cs[0].Type(), "Type mismatch")
|
||||
assert.Equal(t, c.Path(), cs[0].Path(), "Path mismatch")
|
||||
assert.Equal(t, c.Content(), cs[0].Content(), "Content mismatch")
|
||||
|
||||
err = cl.Clear("")
|
||||
assert.Nil(t, err, "Non-nil error while clearing")
|
||||
|
||||
cs = cl.List()
|
||||
assert.Equal(t, 0, len(cs), "List should be empty")
|
||||
|
||||
err = os.Remove(tmpDir) // will error if anything left in dir
|
||||
assert.Nil(t, err, "Clear should have left the tmpDir empty")
|
||||
}
|
||||
|
||||
func TestListOrder(t *testing.T) {
|
||||
tmpDir, err := ioutil.TempDir("/tmp", "test")
|
||||
if err != nil {
|
||||
t.Fatal(err.Error())
|
||||
}
|
||||
defer os.RemoveAll(tmpDir)
|
||||
|
||||
cl, err := NewFileChangelist(tmpDir)
|
||||
assert.Nil(t, err, "Error initializing fileChangelist")
|
||||
|
||||
c1 := NewTufChange(ActionCreate, "targets", "target", "test/targ1", []byte{1})
|
||||
err = cl.Add(c1)
|
||||
assert.Nil(t, err, "Non-nil error while adding change")
|
||||
|
||||
c2 := NewTufChange(ActionCreate, "targets", "target", "test/targ2", []byte{1})
|
||||
err = cl.Add(c2)
|
||||
assert.Nil(t, err, "Non-nil error while adding change")
|
||||
|
||||
cs := cl.List()
|
||||
|
||||
assert.Equal(t, 2, len(cs), "List should have returned exactly one item")
|
||||
assert.Equal(t, c1.Action(), cs[0].Action(), "Action mismatch")
|
||||
assert.Equal(t, c1.Scope(), cs[0].Scope(), "Scope mismatch")
|
||||
assert.Equal(t, c1.Type(), cs[0].Type(), "Type mismatch")
|
||||
assert.Equal(t, c1.Path(), cs[0].Path(), "Path mismatch")
|
||||
assert.Equal(t, c1.Content(), cs[0].Content(), "Content mismatch")
|
||||
|
||||
assert.Equal(t, c2.Action(), cs[1].Action(), "Action 2 mismatch")
|
||||
assert.Equal(t, c2.Scope(), cs[1].Scope(), "Scope 2 mismatch")
|
||||
assert.Equal(t, c2.Type(), cs[1].Type(), "Type 2 mismatch")
|
||||
assert.Equal(t, c2.Path(), cs[1].Path(), "Path 2 mismatch")
|
||||
assert.Equal(t, c2.Content(), cs[1].Content(), "Content 2 mismatch")
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
package changelist
|
||||
|
||||
// Changelist is the interface for all TUF change lists
|
||||
type Changelist interface {
|
||||
// List returns the ordered list of changes
|
||||
// currently stored
|
||||
List() []Change
|
||||
|
||||
// Add change appends the provided change to
|
||||
// the list of changes
|
||||
Add(Change) error
|
||||
|
||||
// Clear empties the current change list.
|
||||
// Archive may be provided as a directory path
|
||||
// to save a copy of the changelist in that location
|
||||
Clear(archive string) error
|
||||
|
||||
// Close syncronizes any pending writes to the underlying
|
||||
// storage and closes the file/connection
|
||||
Close() error
|
||||
}
|
||||
|
||||
const (
|
||||
// ActionCreate represents a Create action
|
||||
ActionCreate = iota
|
||||
// ActionUpdate represents an Update action
|
||||
ActionUpdate
|
||||
// ActionDelete represents a Delete action
|
||||
ActionDelete
|
||||
)
|
||||
|
||||
// Change is the interface for a TUF Change
|
||||
type Change interface {
|
||||
// "create","update", or "delete"
|
||||
Action() int
|
||||
|
||||
// Where the change should be made.
|
||||
// For TUF this will be the role
|
||||
Scope() string
|
||||
|
||||
// The content type being affected.
|
||||
// For TUF this will be "target", or "delegation".
|
||||
// If the type is "delegation", the Scope will be
|
||||
// used to determine if a root role is being updated
|
||||
// or a target delegation.
|
||||
Type() string
|
||||
|
||||
// Path indicates the entry within a role to be affected by the
|
||||
// change. For targets, this is simply the target's path,
|
||||
// for delegations it's the delegated role name.
|
||||
Path() string
|
||||
|
||||
// Serialized content that the interpreter of a changelist
|
||||
// can use to apply the change.
|
||||
// For TUF this will be the serialized JSON that needs
|
||||
// to be inserted or merged. In the case of a "delete"
|
||||
// action, it will be nil.
|
||||
Content() []byte
|
||||
}
|
|
@ -0,0 +1,143 @@
|
|||
package client
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/sha256"
|
||||
"crypto/x509"
|
||||
"errors"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/docker/notary/trustmanager"
|
||||
"github.com/endophage/gotuf/data"
|
||||
)
|
||||
|
||||
// CryptoService implements Sign and Create, holding a specific GUN and keystore to
|
||||
// operate on
|
||||
type CryptoService struct {
|
||||
gun string
|
||||
keyStore *trustmanager.KeyFileStore
|
||||
}
|
||||
|
||||
// RootCryptoService implements Sign and Create and operates on a rootKeyStore,
|
||||
// taking in a passphrase and calling decrypt when signing.
|
||||
type RootCryptoService struct {
|
||||
// TODO(diogo): support multiple passphrases per key
|
||||
passphrase string
|
||||
rootKeyStore *trustmanager.KeyFileStore
|
||||
}
|
||||
|
||||
// NewCryptoService returns an instance of CryptoService
|
||||
func NewCryptoService(gun string, keyStore *trustmanager.KeyFileStore) *CryptoService {
|
||||
return &CryptoService{gun: gun, keyStore: keyStore}
|
||||
}
|
||||
|
||||
// NewRootCryptoService returns an instance of CryptoService
|
||||
func NewRootCryptoService(rootKeyStore *trustmanager.KeyFileStore, passphrase string) *RootCryptoService {
|
||||
return &RootCryptoService{rootKeyStore: rootKeyStore, passphrase: passphrase}
|
||||
}
|
||||
|
||||
// Create is used to generate keys for targets, snapshots and timestamps
|
||||
func (ccs *CryptoService) Create(role string) (*data.PublicKey, error) {
|
||||
privKey, err := trustmanager.GenerateRSAKey(rand.Reader, rsaKeySize)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to generate RSA key: %v", err)
|
||||
}
|
||||
|
||||
// Store the private key into our keystore with the name being: /GUN/ID.key
|
||||
ccs.keyStore.AddKey(filepath.Join(ccs.gun, privKey.ID()), privKey)
|
||||
|
||||
return data.PublicKeyFromPrivate(*privKey), nil
|
||||
}
|
||||
|
||||
// Sign returns the signatures for data with the given keyIDs
|
||||
func (ccs *CryptoService) Sign(keyIDs []string, payload []byte) ([]data.Signature, error) {
|
||||
// Create hasher and hash data
|
||||
hash := crypto.SHA256
|
||||
hashed := sha256.Sum256(payload)
|
||||
|
||||
signatures := make([]data.Signature, 0, len(keyIDs))
|
||||
for _, fingerprint := range keyIDs {
|
||||
// Get the PrivateKey filename
|
||||
privKeyFilename := filepath.Join(ccs.gun, fingerprint)
|
||||
// Read PrivateKey from file
|
||||
privKey, err := ccs.keyStore.GetKey(privKeyFilename)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
sig, err := sign(privKey, hash, hashed[:])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Append signatures to result array
|
||||
signatures = append(signatures, data.Signature{
|
||||
KeyID: fingerprint,
|
||||
Method: "RSA",
|
||||
Signature: sig[:],
|
||||
})
|
||||
}
|
||||
return signatures, nil
|
||||
}
|
||||
|
||||
// Create in a root crypto service is not implemented
|
||||
func (rcs *RootCryptoService) Create(role string) (*data.PublicKey, error) {
|
||||
return nil, errors.New("create on a root key filestore is not implemented")
|
||||
}
|
||||
|
||||
// Sign returns the signatures for data with the given root Key ID, falling back
|
||||
// if not rootKeyID is found
|
||||
// TODO(diogo): This code has 1 line change from the Sign from Crypto service. DRY it up.
|
||||
func (rcs *RootCryptoService) Sign(keyIDs []string, payload []byte) ([]data.Signature, error) {
|
||||
// Create hasher and hash data
|
||||
hash := crypto.SHA256
|
||||
hashed := sha256.Sum256(payload)
|
||||
|
||||
signatures := make([]data.Signature, 0, len(keyIDs))
|
||||
for _, fingerprint := range keyIDs {
|
||||
// Read PrivateKey from file
|
||||
privKey, err := rcs.rootKeyStore.GetDecryptedKey(fingerprint, rcs.passphrase)
|
||||
if err != nil {
|
||||
// TODO(diogo): This error should be returned to the user in someway
|
||||
continue
|
||||
}
|
||||
|
||||
sig, err := sign(privKey, hash, hashed[:])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Append signatures to result array
|
||||
signatures = append(signatures, data.Signature{
|
||||
KeyID: fingerprint,
|
||||
Method: "RSASSA-PKCS1-V1_5-SIGN",
|
||||
Signature: sig[:],
|
||||
})
|
||||
}
|
||||
|
||||
return signatures, nil
|
||||
}
|
||||
|
||||
func sign(privKey *data.PrivateKey, hash crypto.Hash, hashed []byte) ([]byte, error) {
|
||||
// TODO(diogo): Implement support for ECDSA.
|
||||
if privKey.Cipher() != "RSA" {
|
||||
return nil, fmt.Errorf("private key type not supported: %s", privKey.Cipher())
|
||||
}
|
||||
|
||||
// Create an rsa.PrivateKey out of the private key bytes
|
||||
rsaPrivKey, err := x509.ParsePKCS1PrivateKey(privKey.Private())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Use the RSA key to sign the data
|
||||
sig, err := rsa.SignPKCS1v15(rand.Reader, rsaPrivKey, hash, hashed[:])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return sig, nil
|
||||
}
|
|
@ -0,0 +1,717 @@
|
|||
package client
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"crypto/x509"
|
||||
"encoding/json"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/docker/notary/client/changelist"
|
||||
"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"
|
||||
)
|
||||
|
||||
// ErrRepoNotInitialized is returned when trying to can publish on an uninitialized
|
||||
// notary repository
|
||||
type ErrRepoNotInitialized struct{}
|
||||
|
||||
type passwordRetriever func() (string, error)
|
||||
|
||||
// ErrRepoNotInitialized is returned when trying to can publish on an uninitialized
|
||||
// notary repository
|
||||
func (err *ErrRepoNotInitialized) Error() string {
|
||||
return "Repository has not been initialized"
|
||||
}
|
||||
|
||||
// Default paths should end with a '/' so directory creation works correctly
|
||||
const (
|
||||
trustDir string = "/trusted_certificates/"
|
||||
privDir string = "/private/"
|
||||
tufDir string = "/tuf/"
|
||||
rootKeysDir string = privDir + "/root_keys/"
|
||||
rsaKeySize int = 2048 // Used for snapshots and targets keys
|
||||
rsaRootKeySize int = 4096 // Used for new 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")
|
||||
|
||||
// UnlockedSigner encapsulates a private key and a signer that uses that private key,
|
||||
// providing convinience methods for generation of certificates.
|
||||
type UnlockedSigner struct {
|
||||
privKey *data.PrivateKey
|
||||
signer *signed.Signer
|
||||
}
|
||||
|
||||
// NotaryRepository stores all the information needed to operate on a notary
|
||||
// repository.
|
||||
type NotaryRepository struct {
|
||||
baseDir string
|
||||
Gun string
|
||||
baseURL string
|
||||
tufRepoPath string
|
||||
caStore trustmanager.X509Store
|
||||
certificateStore trustmanager.X509Store
|
||||
fileStore store.MetadataStore
|
||||
signer *signed.Signer
|
||||
tufRepo *tuf.TufRepo
|
||||
privKeyStore *trustmanager.KeyFileStore
|
||||
rootKeyStore *trustmanager.KeyFileStore
|
||||
rootSigner *UnlockedSigner
|
||||
}
|
||||
|
||||
// Target represents a simplified version of the data TUF operates on, so external
|
||||
// applications don't have to depend on tuf data types.
|
||||
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
|
||||
}
|
||||
|
||||
// NewNotaryRepository is a helper method that returns a new notary repository.
|
||||
// It takes the base directory under where all the trust files will be stored
|
||||
// (usually ~/.docker/trust/).
|
||||
func NewNotaryRepository(baseDir, gun, baseURL string) (*NotaryRepository, error) {
|
||||
trustDir := filepath.Join(baseDir, trustDir)
|
||||
rootKeysDir := filepath.Join(baseDir, rootKeysDir)
|
||||
|
||||
privKeyStore, err := trustmanager.NewKeyFileStore(filepath.Join(baseDir, privDir))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
signer := signed.NewSigner(NewCryptoService(gun, privKeyStore))
|
||||
|
||||
nRepo := &NotaryRepository{
|
||||
Gun: gun,
|
||||
baseDir: baseDir,
|
||||
baseURL: baseURL,
|
||||
tufRepoPath: filepath.Join(baseDir, tufDir, gun),
|
||||
signer: signer,
|
||||
privKeyStore: privKeyStore,
|
||||
}
|
||||
|
||||
if err := nRepo.loadKeys(trustDir, rootKeysDir); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return nRepo, nil
|
||||
}
|
||||
|
||||
// Initialize creates a new repository by using rootKey as the root Key for the
|
||||
// TUF repository.
|
||||
func (r *NotaryRepository) Initialize(uSigner *UnlockedSigner) error {
|
||||
rootCert, err := uSigner.GenerateCertificate(r.Gun)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
r.certificateStore.AddCert(rootCert)
|
||||
rootKey := data.NewPublicKey("RSA", trustmanager.CertToPEM(rootCert))
|
||||
err = r.rootKeyStore.Link(uSigner.ID(), rootKey.ID())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
remote, err := getRemoteStore(r.baseURL, r.Gun)
|
||||
rawTSKey, err := remote.GetKey("timestamp")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
if err := kdb.AddRole(rootRole); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := kdb.AddRole(targetsRole); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := kdb.AddRole(snapshotRole); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := kdb.AddRole(timestampRole); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
r.tufRepo = tuf.NewTufRepo(kdb, r.signer)
|
||||
|
||||
r.fileStore, err = store.NewFilesystemStore(
|
||||
r.tufRepoPath,
|
||||
"metadata",
|
||||
"json",
|
||||
"targets",
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := r.tufRepo.InitRepo(false); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := r.saveMetadata(uSigner.signer); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Creates an empty snapshot
|
||||
return r.snapshot()
|
||||
}
|
||||
|
||||
// AddTarget adds a new target to the repository, forcing a timestamps check from TUF
|
||||
func (r *NotaryRepository) AddTarget(target *Target) error {
|
||||
cl, err := changelist.NewFileChangelist(filepath.Join(r.tufRepoPath, "changelist"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
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}
|
||||
metaJSON, err := json.Marshal(meta)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c := changelist.NewTufChange(changelist.ActionCreate, "targets", "target", target.Name, metaJSON)
|
||||
err = cl.Add(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return cl.Close()
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
var targetList []*Target
|
||||
for name, meta := range r.tufRepo.Targets["targets"].Signed.Targets {
|
||||
target := &Target{Name: name, Hashes: meta.Hashes, Length: meta.Length}
|
||||
targetList = append(targetList, target)
|
||||
}
|
||||
|
||||
return targetList, 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
|
||||
// Conceptually it performs an operation similar to a `git rebase`
|
||||
func (r *NotaryRepository) Publish(getPass passwordRetriever) error {
|
||||
var updateRoot bool
|
||||
var root *data.Signed
|
||||
// attempt to initialize the repo from the remote store
|
||||
c, err := r.bootstrapClient()
|
||||
if err != nil {
|
||||
if _, ok := err.(*store.ErrMetaNotFound); ok {
|
||||
// if the remote store return a 404 (translated into ErrMetaNotFound),
|
||||
// the repo hasn't been initialized yet. Attempt to load it from disk.
|
||||
err := r.bootstrapRepo()
|
||||
if err != nil {
|
||||
// Repo hasn't been initialized, It must be initialized before
|
||||
// it can be published. Return an error and let caller determine
|
||||
// what it wants to do.
|
||||
logrus.Debug("Repository not initialized during Publish")
|
||||
return &ErrRepoNotInitialized{}
|
||||
}
|
||||
// We had local data but the server doesn't know about the repo yet,
|
||||
// ensure we will push the initial root file
|
||||
root, err = r.tufRepo.Root.ToSigned()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
updateRoot = true
|
||||
} else {
|
||||
// The remote store returned an error other than 404. We're
|
||||
// unable to determine if the repo has been initialized or not.
|
||||
logrus.Error("Could not publish Repository: ", err.Error())
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
// If we were successfully able to bootstrap the client (which only pulls
|
||||
// root.json), update it the rest of the tuf metadata in preparation for
|
||||
// applying the changelist.
|
||||
err = c.Update()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// load the changelist for this repo
|
||||
cl, err := changelist.NewFileChangelist(filepath.Join(r.tufRepoPath, "changelist"))
|
||||
if err != nil {
|
||||
logrus.Debug("Error initializing changelist")
|
||||
return err
|
||||
}
|
||||
// apply the changelist to the repo
|
||||
err = applyChangelist(r.tufRepo, cl)
|
||||
if err != nil {
|
||||
logrus.Debug("Error applying changelist")
|
||||
return err
|
||||
}
|
||||
|
||||
// check if our root file is nearing expiry. Resign if it is.
|
||||
if nearExpiry(r.tufRepo.Root) || r.tufRepo.Root.Dirty {
|
||||
passphrase, err := getPass()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
rootKeyID := r.tufRepo.Root.Signed.Roles["root"].KeyIDs[0]
|
||||
rootSigner, err := r.GetRootSigner(rootKeyID, passphrase)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
root, err = r.tufRepo.SignRoot(data.DefaultExpires("root"), rootSigner.signer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
updateRoot = true
|
||||
}
|
||||
// we will always resign targets and snapshots
|
||||
targets, err := r.tufRepo.SignTargets("targets", data.DefaultExpires("targets"), nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
snapshot, err := r.tufRepo.SignSnapshot(data.DefaultExpires("snapshot"), nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
remote, err := getRemoteStore(r.baseURL, r.Gun)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// ensure we can marshal all the json before sending anything to remote
|
||||
targetsJSON, err := json.Marshal(targets)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
snapshotJSON, err := json.Marshal(snapshot)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// if we need to update the root, marshal it and push the update to remote
|
||||
if updateRoot {
|
||||
rootJSON, err := json.Marshal(root)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = remote.SetMeta("root", rootJSON)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
err = remote.SetMeta("targets", targetsJSON)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = remote.SetMeta("snapshot", snapshotJSON)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *NotaryRepository) bootstrapRepo() error {
|
||||
fileStore, err := store.NewFilesystemStore(
|
||||
r.tufRepoPath,
|
||||
"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) saveMetadata(rootSigner *signed.Signer) error {
|
||||
signedRoot, err := r.tufRepo.SignRoot(data.DefaultExpires("root"), rootSigner)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
rootJSON, _ := json.Marshal(signedRoot)
|
||||
return r.fileStore.SetMeta("root", rootJSON)
|
||||
}
|
||||
|
||||
func (r *NotaryRepository) snapshot() error {
|
||||
fmt.Println("Saving changes to Trusted Collection.")
|
||||
|
||||
for t := range r.tufRepo.Targets {
|
||||
signedTargets, err := r.tufRepo.SignTargets(t, data.DefaultExpires("targets"), nil)
|
||||
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"), nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
snapshotJSON, _ := json.Marshal(signedSnapshot)
|
||||
|
||||
return r.fileStore.SetMeta("snapshot", snapshotJSON)
|
||||
}
|
||||
|
||||
/*
|
||||
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[fingerprint].Public()))
|
||||
logrus.Debug("Root PEM: ", k)
|
||||
logrus.Debug("Root ID: ", fingerprint)
|
||||
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]
|
||||
}
|
||||
}
|
||||
|
||||
if len(certs) < 1 {
|
||||
return errors.New("could not validate the path to a trusted root")
|
||||
}
|
||||
|
||||
_, err = signed.VerifyRoot(root, 0, certs, 1)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *NotaryRepository) bootstrapClient() (*tufclient.Client, error) {
|
||||
remote, err := getRemoteStore(r.baseURL, r.Gun)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rootJSON, err := remote.GetMeta("root", 5<<20)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
root := &data.Signed{}
|
||||
err = json.Unmarshal(rootJSON, root)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = r.validateRoot(root)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
kdb := keys.NewDB()
|
||||
r.tufRepo = tuf.NewTufRepo(kdb, r.signer)
|
||||
|
||||
err = r.tufRepo.SetRoot(root)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return tufclient.NewClient(
|
||||
r.tufRepo,
|
||||
remote,
|
||||
kdb,
|
||||
), nil
|
||||
}
|
||||
|
||||
// ListRootKeys returns the IDs for all of the root keys. It ignores symlinks
|
||||
// if any exist.
|
||||
func (r *NotaryRepository) ListRootKeys() []string {
|
||||
return r.rootKeyStore.ListKeys()
|
||||
}
|
||||
|
||||
// GenRootKey generates a new root key protected by a given passphrase
|
||||
func (r *NotaryRepository) GenRootKey(passphrase string) (string, error) {
|
||||
privKey, err := trustmanager.GenerateRSAKey(rand.Reader, rsaRootKeySize)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to convert private key: %v", err)
|
||||
}
|
||||
|
||||
r.rootKeyStore.AddEncryptedKey(privKey.ID(), privKey, passphrase)
|
||||
|
||||
return privKey.ID(), nil
|
||||
}
|
||||
|
||||
// GetRootSigner retreives a root key that includes the ID and a signer
|
||||
func (r *NotaryRepository) GetRootSigner(rootKeyID, passphrase string) (*UnlockedSigner, error) {
|
||||
privKey, err := r.rootKeyStore.GetDecryptedKey(rootKeyID, passphrase)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not get decrypted root key: %v", err)
|
||||
}
|
||||
|
||||
// This signer will be used for all of the normal TUF operations, except for
|
||||
// when a root key is needed.
|
||||
signer := signed.NewSigner(NewRootCryptoService(r.rootKeyStore, passphrase))
|
||||
|
||||
return &UnlockedSigner{
|
||||
privKey: privKey,
|
||||
signer: signer}, nil
|
||||
}
|
||||
|
||||
func (r *NotaryRepository) loadKeys(trustDir, rootKeysDir string) error {
|
||||
// Load all CAs that aren't expired and don't use SHA1
|
||||
caStore, err := trustmanager.NewX509FilteredFileStore(trustDir, 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
|
||||
certificateStore, err := trustmanager.NewX509FilteredFileStore(trustDir, 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
|
||||
}
|
||||
|
||||
// Load the keystore that will hold all of our encrypted Root Private Keys
|
||||
rootKeyStore, err := trustmanager.NewKeyFileStore(rootKeysDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
r.caStore = caStore
|
||||
r.certificateStore = certificateStore
|
||||
r.rootKeyStore = rootKeyStore
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ID gets a consistent ID based on the PrivateKey bytes and cipher type
|
||||
func (uk *UnlockedSigner) ID() string {
|
||||
return uk.PublicKey().ID()
|
||||
}
|
||||
|
||||
// PublicKey Returns the public key associated with the Private Key within the Signer
|
||||
func (uk *UnlockedSigner) PublicKey() *data.PublicKey {
|
||||
return data.PublicKeyFromPrivate(*uk.privKey)
|
||||
}
|
||||
|
||||
// GenerateCertificate generates an X509 Certificate from a template, given a GUN
|
||||
func (uk *UnlockedSigner) GenerateCertificate(gun string) (*x509.Certificate, error) {
|
||||
privKey, err := x509.ParsePKCS1PrivateKey(uk.privKey.Private())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse root key: %v (%s)", gun, err.Error())
|
||||
}
|
||||
|
||||
template, err := trustmanager.NewCertificate(gun)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create the certificate template for: %s (%v)", gun, err)
|
||||
}
|
||||
|
||||
derBytes, err := x509.CreateCertificate(rand.Reader, template, template, privKey.Public(), privKey)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create the certificate for: %s (%v)", gun, err)
|
||||
}
|
||||
|
||||
// Encode the new certificate into PEM
|
||||
cert, err := x509.ParseCertificate(derBytes)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse the certificate for key: %s (%v)", gun, err)
|
||||
}
|
||||
|
||||
return cert, nil
|
||||
}
|
|
@ -0,0 +1,314 @@
|
|||
package client
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/notary/trustmanager"
|
||||
"github.com/endophage/gotuf/data"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func createTestServer(t *testing.T) *httptest.Server {
|
||||
// TUF will request /v2/docker.com/notary/_trust/tuf/timestamp.key
|
||||
// Return a canned timestamp.key
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Fprint(w, `{"keytype":"ed25519","keyval":{"public":"y4wnCW7Y8NYCmKZyWqxxUUj8p7SSoV5Cr1Zc+jqBxBw=","private":null}}`)
|
||||
}))
|
||||
|
||||
return ts
|
||||
}
|
||||
|
||||
// TestInitRepo runs through the process of initializing a repository and makes
|
||||
// sure the repository looks correct on disk.
|
||||
func TestInitRepo(t *testing.T) {
|
||||
gun := "docker.com/notary"
|
||||
// Temporary directory where test files will be created
|
||||
tempBaseDir, err := ioutil.TempDir("", "notary-test-")
|
||||
assert.NoError(t, err, "failed to create a temporary directory: %s", err)
|
||||
|
||||
ts := createTestServer(t)
|
||||
defer ts.Close()
|
||||
|
||||
repo, err := NewNotaryRepository(tempBaseDir, gun, ts.URL)
|
||||
assert.NoError(t, err, "error creating repo: %s", err)
|
||||
|
||||
rootKeyID, err := repo.GenRootKey("passphrase")
|
||||
assert.NoError(t, err, "error generating root key: %s", err)
|
||||
|
||||
rootSigner, err := repo.GetRootSigner(rootKeyID, "passphrase")
|
||||
assert.NoError(t, err, "error retreiving root key: %s", err)
|
||||
|
||||
err = repo.Initialize(rootSigner)
|
||||
assert.NoError(t, err, "error creating repository: %s", err)
|
||||
|
||||
// Inspect contents of the temporary directory
|
||||
expectedDirs := []string{
|
||||
"private",
|
||||
filepath.Join("private", gun),
|
||||
filepath.Join("private", "root_keys"),
|
||||
"trusted_certificates",
|
||||
filepath.Join("trusted_certificates", gun),
|
||||
"tuf",
|
||||
filepath.Join("tuf", gun, "metadata"),
|
||||
filepath.Join("tuf", gun, "targets"),
|
||||
}
|
||||
for _, dir := range expectedDirs {
|
||||
fi, err := os.Stat(filepath.Join(tempBaseDir, dir))
|
||||
assert.NoError(t, err, "missing directory in base directory: %s", dir)
|
||||
assert.True(t, fi.Mode().IsDir(), "%s is not a directory", dir)
|
||||
}
|
||||
|
||||
// Look for keys in private. The filenames should match the key IDs
|
||||
// in the private key store.
|
||||
privKeyList := repo.privKeyStore.ListFiles(true)
|
||||
for _, privKeyName := range privKeyList {
|
||||
_, err := os.Stat(privKeyName)
|
||||
assert.NoError(t, err, "missing private key: %s", privKeyName)
|
||||
}
|
||||
|
||||
// Look for keys in root_keys
|
||||
// There should be a file named after the key ID of the root key we
|
||||
// passed in.
|
||||
rootKeyFilename := rootSigner.ID() + ".key"
|
||||
_, err = os.Stat(filepath.Join(tempBaseDir, "private", "root_keys", rootKeyFilename))
|
||||
assert.NoError(t, err, "missing root key")
|
||||
|
||||
// Also expect a symlink from the key ID of the certificate key to this
|
||||
// root key
|
||||
certificates := repo.certificateStore.GetCertificates()
|
||||
assert.Len(t, certificates, 1, "unexpected number of certificates")
|
||||
|
||||
certID := trustmanager.FingerprintCert(certificates[0])
|
||||
|
||||
actualDest, err := os.Readlink(filepath.Join(tempBaseDir, "private", "root_keys", certID+".key"))
|
||||
assert.NoError(t, err, "missing symlink to root key")
|
||||
|
||||
assert.Equal(t, rootKeyFilename, actualDest, "symlink to root key has wrong destination")
|
||||
|
||||
// There should be a trusted certificate
|
||||
_, err = os.Stat(filepath.Join(tempBaseDir, "trusted_certificates", gun, certID+".crt"))
|
||||
assert.NoError(t, err, "missing trusted certificate")
|
||||
|
||||
// Sanity check the TUF metadata files. Verify that they exist, the JSON is
|
||||
// well-formed, and the signatures exist. For the root.json file, also check
|
||||
// that the root, snapshot, and targets key IDs are present.
|
||||
expectedTUFMetadataFiles := []string{
|
||||
filepath.Join("tuf", gun, "metadata", "root.json"),
|
||||
filepath.Join("tuf", gun, "metadata", "snapshot.json"),
|
||||
filepath.Join("tuf", gun, "metadata", "targets.json"),
|
||||
}
|
||||
for _, filename := range expectedTUFMetadataFiles {
|
||||
fullPath := filepath.Join(tempBaseDir, filename)
|
||||
_, err := os.Stat(fullPath)
|
||||
assert.NoError(t, err, "missing TUF metadata file: %s", filename)
|
||||
|
||||
jsonBytes, err := ioutil.ReadFile(fullPath)
|
||||
assert.NoError(t, err, "error reading TUF metadata file %s: %s", filename, err)
|
||||
|
||||
var decoded data.Signed
|
||||
err = json.Unmarshal(jsonBytes, &decoded)
|
||||
assert.NoError(t, err, "error parsing TUF metadata file %s: %s", filename, err)
|
||||
|
||||
assert.Len(t, decoded.Signatures, 1, "incorrect number of signatures in TUF metadata file %s", filename)
|
||||
|
||||
assert.NotEmpty(t, decoded.Signatures[0].KeyID, "empty key ID field in TUF metadata file %s", filename)
|
||||
assert.NotEmpty(t, decoded.Signatures[0].Method, "empty method field in TUF metadata file %s", filename)
|
||||
assert.NotEmpty(t, decoded.Signatures[0].Signature, "empty signature in TUF metadata file %s", filename)
|
||||
|
||||
// Special case for root.json: also check that the signed
|
||||
// content for keys and roles
|
||||
if strings.HasSuffix(filename, "root.json") {
|
||||
var decodedRoot data.Root
|
||||
err := json.Unmarshal(decoded.Signed, &decodedRoot)
|
||||
assert.NoError(t, err, "error parsing root.json signed section: %s", err)
|
||||
|
||||
assert.Equal(t, "Root", decodedRoot.Type, "_type mismatch in root.json")
|
||||
|
||||
// Expect 4 keys in the Keys map: root, targets, snapshot, timestamp
|
||||
assert.Len(t, decodedRoot.Keys, 4, "wrong number of keys in root.json")
|
||||
|
||||
roleCount := 0
|
||||
for role := range decodedRoot.Roles {
|
||||
roleCount++
|
||||
if role != "root" && role != "snapshot" && role != "targets" && role != "timestamp" {
|
||||
t.Fatalf("unexpected role %s in root.json", role)
|
||||
}
|
||||
}
|
||||
assert.Equal(t, 4, roleCount, "wrong number of roles (%d) in root.json", roleCount)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type tufChange struct {
|
||||
// Abbreviated because Go doesn't permit a field and method of the same name
|
||||
Actn int `json:"action"`
|
||||
Role string `json:"role"`
|
||||
ChangeType string `json:"type"`
|
||||
ChangePath string `json:"path"`
|
||||
Data []byte `json:"data"`
|
||||
}
|
||||
|
||||
// TestAddTarget adds a target to the repo and confirms that the changelist
|
||||
// is updated correctly.
|
||||
func TestAddTarget(t *testing.T) {
|
||||
// Temporary directory where test files will be created
|
||||
tempBaseDir, err := ioutil.TempDir("", "notary-test-")
|
||||
assert.NoError(t, err, "failed to create a temporary directory: %s", err)
|
||||
|
||||
gun := "docker.com/notary"
|
||||
|
||||
ts := createTestServer(t)
|
||||
defer ts.Close()
|
||||
|
||||
repo, err := NewNotaryRepository(tempBaseDir, gun, ts.URL)
|
||||
assert.NoError(t, err, "error creating repository: %s", err)
|
||||
|
||||
rootKeyID, err := repo.GenRootKey("passphrase")
|
||||
assert.NoError(t, err, "error generating root key: %s", err)
|
||||
|
||||
rootSigner, err := repo.GetRootSigner(rootKeyID, "passphrase")
|
||||
assert.NoError(t, err, "error retreiving root key: %s", err)
|
||||
|
||||
err = repo.Initialize(rootSigner)
|
||||
assert.NoError(t, err, "error creating repository: %s", err)
|
||||
|
||||
// Add fixtures/ca.cert as a target. There's no particular reason
|
||||
// for using this file except that it happens to be available as
|
||||
// a fixture.
|
||||
target, err := NewTarget("latest", "../fixtures/ca.cert")
|
||||
assert.NoError(t, err, "error creating target")
|
||||
err = repo.AddTarget(target)
|
||||
assert.NoError(t, err, "error adding target")
|
||||
|
||||
// Look for the changelist file
|
||||
changelistDirPath := filepath.Join(tempBaseDir, "tuf", gun, "changelist")
|
||||
|
||||
changelistDir, err := os.Open(changelistDirPath)
|
||||
assert.NoError(t, err, "could not open changelist directory")
|
||||
|
||||
fileInfos, err := changelistDir.Readdir(0)
|
||||
assert.NoError(t, err, "could not read changelist directory")
|
||||
|
||||
// Should only be one file in the directory
|
||||
assert.Len(t, fileInfos, 1, "wrong number of changelist files found")
|
||||
|
||||
clName := fileInfos[0].Name()
|
||||
raw, err := ioutil.ReadFile(filepath.Join(changelistDirPath, clName))
|
||||
assert.NoError(t, err, "could not read changelist file %s", clName)
|
||||
|
||||
c := &tufChange{}
|
||||
err = json.Unmarshal(raw, c)
|
||||
assert.NoError(t, err, "could not unmarshal changelist file %s", clName)
|
||||
|
||||
assert.EqualValues(t, 0, c.Actn)
|
||||
assert.Equal(t, "targets", c.Role)
|
||||
assert.Equal(t, "target", c.ChangeType)
|
||||
assert.Equal(t, "latest", c.ChangePath)
|
||||
assert.NotEmpty(t, c.Data)
|
||||
|
||||
changelistDir.Close()
|
||||
|
||||
// Create a second target
|
||||
target, err = NewTarget("current", "../fixtures/ca.cert")
|
||||
assert.NoError(t, err, "error creating target")
|
||||
err = repo.AddTarget(target)
|
||||
assert.NoError(t, err, "error adding target")
|
||||
|
||||
changelistDir, err = os.Open(changelistDirPath)
|
||||
assert.NoError(t, err, "could not open changelist directory")
|
||||
|
||||
// There should now be a second file in the directory
|
||||
fileInfos, err = changelistDir.Readdir(0)
|
||||
assert.NoError(t, err, "could not read changelist directory")
|
||||
|
||||
assert.Len(t, fileInfos, 2, "wrong number of changelist files found")
|
||||
|
||||
newFileFound := false
|
||||
for _, fileInfo := range fileInfos {
|
||||
if fileInfo.Name() != clName {
|
||||
clName2 := fileInfo.Name()
|
||||
raw, err := ioutil.ReadFile(filepath.Join(changelistDirPath, clName2))
|
||||
assert.NoError(t, err, "could not read changelist file %s", clName2)
|
||||
|
||||
c := &tufChange{}
|
||||
err = json.Unmarshal(raw, c)
|
||||
assert.NoError(t, err, "could not unmarshal changelist file %s", clName2)
|
||||
|
||||
assert.EqualValues(t, 0, c.Actn)
|
||||
assert.Equal(t, "targets", c.Role)
|
||||
assert.Equal(t, "target", c.ChangeType)
|
||||
assert.Equal(t, "current", c.ChangePath)
|
||||
assert.NotEmpty(t, c.Data)
|
||||
|
||||
newFileFound = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
assert.True(t, newFileFound, "second changelist file not found")
|
||||
|
||||
changelistDir.Close()
|
||||
}
|
||||
|
||||
// TestValidateRootKey verifies that the public data in root.json for the root
|
||||
// key is a valid x509 certificate.
|
||||
func TestValidateRootKey(t *testing.T) {
|
||||
// Temporary directory where test files will be created
|
||||
tempBaseDir, err := ioutil.TempDir("", "notary-test-")
|
||||
assert.NoError(t, err, "failed to create a temporary directory: %s", err)
|
||||
|
||||
gun := "docker.com/notary"
|
||||
|
||||
ts := createTestServer(t)
|
||||
defer ts.Close()
|
||||
|
||||
repo, err := NewNotaryRepository(tempBaseDir, gun, ts.URL)
|
||||
assert.NoError(t, err, "error creating repository: %s", err)
|
||||
|
||||
rootKeyID, err := repo.GenRootKey("passphrase")
|
||||
assert.NoError(t, err, "error generating root key: %s", err)
|
||||
|
||||
rootSigner, err := repo.GetRootSigner(rootKeyID, "passphrase")
|
||||
assert.NoError(t, err, "error retreiving root key: %s", err)
|
||||
|
||||
err = repo.Initialize(rootSigner)
|
||||
assert.NoError(t, err, "error creating repository: %s", err)
|
||||
|
||||
rootJSONFile := filepath.Join(tempBaseDir, "tuf", gun, "metadata", "root.json")
|
||||
|
||||
jsonBytes, err := ioutil.ReadFile(rootJSONFile)
|
||||
assert.NoError(t, err, "error reading TUF metadata file %s: %s", rootJSONFile, err)
|
||||
|
||||
var decoded data.Signed
|
||||
err = json.Unmarshal(jsonBytes, &decoded)
|
||||
assert.NoError(t, err, "error parsing TUF metadata file %s: %s", rootJSONFile, err)
|
||||
|
||||
var decodedRoot data.Root
|
||||
err = json.Unmarshal(decoded.Signed, &decodedRoot)
|
||||
assert.NoError(t, err, "error parsing root.json signed section: %s", err)
|
||||
|
||||
keyids := []string{}
|
||||
for role, roleData := range decodedRoot.Roles {
|
||||
if role == "root" {
|
||||
keyids = append(keyids, roleData.KeyIDs...)
|
||||
}
|
||||
}
|
||||
assert.NotEmpty(t, keyids)
|
||||
|
||||
for _, keyid := range keyids {
|
||||
if key, ok := decodedRoot.Keys[keyid]; !ok {
|
||||
t.Fatal("key id not found in keys")
|
||||
} else {
|
||||
_, err := trustmanager.LoadCertFromPEM(key.Value.Public)
|
||||
assert.NoError(t, err, "key is not a valid cert")
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
package client
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"time"
|
||||
|
||||
"github.com/docker/notary/client/changelist"
|
||||
"github.com/endophage/gotuf"
|
||||
"github.com/endophage/gotuf/data"
|
||||
"github.com/endophage/gotuf/store"
|
||||
)
|
||||
|
||||
// Use this to initialize remote HTTPStores from the config settings
|
||||
func getRemoteStore(baseURL, gun string) (store.RemoteStore, error) {
|
||||
return store.NewHTTPStore(
|
||||
baseURL+"/v2/"+gun+"/_trust/tuf/",
|
||||
"",
|
||||
"json",
|
||||
"",
|
||||
"key",
|
||||
)
|
||||
}
|
||||
|
||||
func applyChangelist(repo *tuf.TufRepo, cl changelist.Changelist) error {
|
||||
changes := cl.List()
|
||||
var err error
|
||||
for _, c := range changes {
|
||||
if c.Scope() == "targets" {
|
||||
applyTargetsChange(repo, c)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func applyTargetsChange(repo *tuf.TufRepo, c changelist.Change) error {
|
||||
var err error
|
||||
meta := &data.FileMeta{}
|
||||
err = json.Unmarshal(c.Content(), meta)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
if c.Action() == changelist.ActionCreate {
|
||||
files := data.Files{c.Path(): *meta}
|
||||
_, err = repo.AddTargets("targets", files)
|
||||
} else if c.Action() == changelist.ActionDelete {
|
||||
err = repo.RemoveTargets("targets", c.Path())
|
||||
}
|
||||
if err != nil {
|
||||
// TODO(endophage): print out rem entries as files that couldn't
|
||||
// be added.
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func nearExpiry(r *data.SignedRoot) bool {
|
||||
plus6mo := time.Now().AddDate(0, 6, 0)
|
||||
return r.Signed.Expires.Before(plus6mo)
|
||||
}
|
|
@ -4,10 +4,11 @@
|
|||
"tls_cert_file": "./fixtures/notary.pem",
|
||||
"tls_key_file": "./fixtures/notary.key"
|
||||
},
|
||||
"trust_service":{
|
||||
"trust_service": {
|
||||
"type": "local",
|
||||
"hostname": "",
|
||||
"port": ""
|
||||
"hostname": "rufus",
|
||||
"port": "7899",
|
||||
"tls_ca_file": "./fixtures/ca.cert"
|
||||
},
|
||||
"logging": {
|
||||
"level": 5
|
||||
|
|
|
@ -1,121 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/sha256"
|
||||
"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
|
||||
}
|
||||
|
||||
// NewCryptoService returns an instance ofS cliCryptoService
|
||||
func NewCryptoService(gun string) *CliCryptoService {
|
||||
return &CliCryptoService{privateKeys: make(map[string]*data.PrivateKey), gun: gun}
|
||||
}
|
||||
|
||||
// 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)
|
||||
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)
|
||||
}
|
||||
|
||||
return data.NewPublicKey("RSA", pemdata), nil
|
||||
}
|
||||
|
||||
// Sign returns the signatures for data with the given keyIDs
|
||||
func (ccs *CliCryptoService) Sign(keyIDs []string, payload []byte) ([]data.Signature, error) {
|
||||
// Create hasher and hash data
|
||||
hash := crypto.SHA256
|
||||
hashed := sha256.Sum256(payload)
|
||||
|
||||
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")
|
||||
// Read PrivateKey from file
|
||||
privPEMBytes, err := ioutil.ReadFile(privKeyFilename)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// Parse PrivateKey
|
||||
privKeyBytes, _ := pem.Decode(privPEMBytes)
|
||||
privKey, err := x509.ParsePKCS1PrivateKey(privKeyBytes.Bytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Sign the data
|
||||
sig, err := rsa.SignPKCS1v15(rand.Reader, privKey, hash, hashed[:])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Append signatures to result array
|
||||
signatures = append(signatures, data.Signature{
|
||||
KeyID: fingerprint,
|
||||
Method: "RSASSA-PKCS1-V1_5-SIGN",
|
||||
Signature: sig[:],
|
||||
})
|
||||
}
|
||||
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) {
|
||||
// Generates a new RSA key
|
||||
key, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("could not generate private key: %v", err)
|
||||
}
|
||||
|
||||
// Creates a new Certificate template. We need the certificate to calculate the
|
||||
// TUF-compliant keyID
|
||||
//TODO (diogo): We're hardcoding the Organization to be the GUN. Probably want to
|
||||
// change it
|
||||
template := 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)
|
||||
}
|
||||
|
||||
// Encode the new certificate into PEM
|
||||
cert, err := x509.ParseCertificate(derBytes)
|
||||
if err != nil {
|
||||
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)
|
||||
}
|
|
@ -1,12 +1,9 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"fmt"
|
||||
"math"
|
||||
"math/big"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
@ -95,7 +92,7 @@ func keysRemove(cmd *cobra.Command, args []string) {
|
|||
}
|
||||
|
||||
// We didn't find a certificate with this ID, let's try to see if we can find keys.
|
||||
keyList := privKeyStore.ListDir(gunOrID)
|
||||
keyList := privKeyStore.ListDir(gunOrID, true)
|
||||
if len(keyList) < 1 {
|
||||
fatalf("no Private Keys found under Global Unique Name: %s", gunOrID)
|
||||
}
|
||||
|
@ -190,7 +187,7 @@ func keysList(cmd *cobra.Command, args []string) {
|
|||
|
||||
fmt.Println("")
|
||||
fmt.Println("# Signing keys: ")
|
||||
for _, k := range privKeyStore.ListAll() {
|
||||
for _, k := range privKeyStore.ListFiles(true) {
|
||||
printKey(k)
|
||||
}
|
||||
}
|
||||
|
@ -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) {
|
||||
|
|
|
@ -16,18 +16,15 @@ import (
|
|||
)
|
||||
|
||||
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
|
||||
const trustDir string = "trusted_certificates/"
|
||||
const privDir string = "private/"
|
||||
const rootKeysDir string = "root_keys/"
|
||||
|
||||
var rawOutput bool
|
||||
var caStore trustmanager.X509Store
|
||||
var certificateStore trustmanager.X509Store
|
||||
var privKeyStore trustmanager.FileStore
|
||||
|
||||
func init() {
|
||||
logrus.SetLevel(logrus.DebugLevel)
|
||||
|
@ -59,13 +56,11 @@ 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)))
|
||||
viper.SetDefault("baseTrustDir", path.Join(homeDir, path.Dir(configPath)))
|
||||
|
||||
// Get the final value for the CA directory
|
||||
finalTrustDir := viper.GetString("trustDir")
|
||||
finalPrivDir := viper.GetString("privDir")
|
||||
finalTrustDir := path.Join(viper.GetString("baseTrustDir"), trustDir)
|
||||
finalPrivDir := path.Join(viper.GetString("baseTrustDir"), privDir)
|
||||
|
||||
// Load all CAs that aren't expired and don't use SHA1
|
||||
caStore, err = trustmanager.NewX509FilteredFileStore(finalTrustDir, func(cert *x509.Certificate) bool {
|
||||
|
@ -76,10 +71,10 @@ func init() {
|
|||
cert.SignatureAlgorithm != x509.ECDSAWithSHA1
|
||||
})
|
||||
if err != nil {
|
||||
fatalf("could not create X509FileStore: %v", err)
|
||||
fatalf("could not create CA 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) &&
|
||||
|
@ -88,12 +83,12 @@ func init() {
|
|||
cert.SignatureAlgorithm != x509.ECDSAWithSHA1
|
||||
})
|
||||
if err != nil {
|
||||
fatalf("could not create X509FileStore: %v", err)
|
||||
fatalf("could not create Certificate X509FileStore: %v", err)
|
||||
}
|
||||
|
||||
privKeyStore, err = trustmanager.NewKeyFileStore(finalPrivDir)
|
||||
if err != nil {
|
||||
fatalf("could not create FileStore: %v", err)
|
||||
fatalf("could not create KeyFileStore: %v", err)
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -108,13 +103,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,29 +1,21 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/sha256"
|
||||
"crypto/x509"
|
||||
"encoding/json"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"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"
|
||||
"github.com/endophage/gotuf/store"
|
||||
notaryclient "github.com/docker/notary/client"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
// FIXME: This should not be hardcoded
|
||||
const hardcodedBaseURL = "https://notary:4443"
|
||||
|
||||
var remoteTrustServer string
|
||||
|
||||
var cmdTufList = &cobra.Command{
|
||||
|
@ -84,30 +76,21 @@ 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)
|
||||
repo, err := notaryclient.NewNotaryRepository(viper.GetString("baseTrustDir"), gun, hardcodedBaseURL)
|
||||
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})
|
||||
err = repo.AddTarget(target)
|
||||
if err != nil {
|
||||
fatalf(err.Error())
|
||||
}
|
||||
|
||||
saveRepo(repo, filestore)
|
||||
fmt.Println("Successfully added targets")
|
||||
}
|
||||
|
||||
func tufInit(cmd *cobra.Command, args []string) {
|
||||
|
@ -117,91 +100,44 @@ func tufInit(cmd *cobra.Command, args []string) {
|
|||
}
|
||||
|
||||
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")
|
||||
nRepo, err := notaryclient.NewNotaryRepository(viper.GetString("baseTrustDir"), gun, hardcodedBaseURL)
|
||||
if err != nil {
|
||||
fatalf(err.Error())
|
||||
}
|
||||
|
||||
kdb.AddKey(rootKey)
|
||||
kdb.AddKey(targetsKey)
|
||||
kdb.AddKey(snapshotKey)
|
||||
kdb.AddKey(timestampKey)
|
||||
keysList := nRepo.ListRootKeys()
|
||||
var passphrase string
|
||||
var rootKeyID string
|
||||
if len(keysList) < 1 {
|
||||
fmt.Println("No root keys found. Generating a new root key...")
|
||||
passphrase, err = passphraseRetriever()
|
||||
if err != nil {
|
||||
fatalf(err.Error())
|
||||
}
|
||||
rootKeyID, err = nRepo.GenRootKey(passphrase)
|
||||
if err != nil {
|
||||
fatalf(err.Error())
|
||||
}
|
||||
} else {
|
||||
rootKeyID = keysList[0]
|
||||
fmt.Println("Root key found.")
|
||||
fmt.Printf("Enter passphrase for: %s (%d)\n", rootKeyID, len(rootKeyID))
|
||||
passphrase, err = passphraseRetriever()
|
||||
if err != nil {
|
||||
fatalf(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
rootRole, err := data.NewRole("root", 1, []string{rootKey.ID()}, nil, nil)
|
||||
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)
|
||||
rootSigner, err := nRepo.GetRootSigner(rootKeyID, passphrase)
|
||||
if err != nil {
|
||||
fatalf(err.Error())
|
||||
}
|
||||
|
||||
err = kdb.AddRole(rootRole)
|
||||
nRepo.Initialize(rootSigner)
|
||||
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) {
|
||||
|
@ -210,31 +146,21 @@ 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)
|
||||
repo, err := notaryclient.NewNotaryRepository(viper.GetString("baseTrustDir"), gun, hardcodedBaseURL)
|
||||
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)
|
||||
// Retreive the remote list of signed targets
|
||||
targetList, err := repo.ListTargets()
|
||||
if err != nil {
|
||||
fatalf(err.Error())
|
||||
}
|
||||
|
||||
// Print all the available targets
|
||||
for _, t := range targetList {
|
||||
fmt.Println(t.Name, " ", t.Hashes["sha256"], " ", t.Length)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -245,29 +171,19 @@ 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)
|
||||
repo, err := notaryclient.NewNotaryRepository(viper.GetString("baseTrustDir"), gun, hardcodedBaseURL)
|
||||
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,41 +193,15 @@ 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",
|
||||
)
|
||||
repo, err := notaryclient.NewNotaryRepository(viper.GetString("baseTrustDir"), gun, hardcodedBaseURL)
|
||||
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)
|
||||
err = repo.Publish(passphraseRetriever)
|
||||
if err != nil {
|
||||
fatalf(err.Error())
|
||||
}
|
||||
|
@ -324,20 +214,15 @@ 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)
|
||||
|
||||
//c := changelist.NewTufChange(changelist.ActionDelete, "targets", "target", targetName, nil)
|
||||
//err := cl.Add(c)
|
||||
//if err != nil {
|
||||
// fatalf(err.Error())
|
||||
//}
|
||||
|
||||
// TODO(diogo): Implement RemoveTargets in libnotary
|
||||
fmt.Println("Removing target ", targetName, " from ", gun)
|
||||
|
||||
filestore := bootstrapRepo(gun, repo)
|
||||
|
||||
err := repo.RemoveTargets("targets", targetName)
|
||||
if err != nil {
|
||||
fatalf(err.Error())
|
||||
}
|
||||
|
||||
saveRepo(repo, filestore)
|
||||
}
|
||||
|
||||
func verify(cmd *cobra.Command, args []string) {
|
||||
|
@ -356,31 +241,21 @@ 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)
|
||||
repo, err := notaryclient.NewNotaryRepository(viper.GetString("baseTrustDir"), gun, hardcodedBaseURL)
|
||||
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,179 +265,16 @@ 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"))
|
||||
func passphraseRetriever() (string, error) {
|
||||
fmt.Println("Please provide a passphrase for this root key: ")
|
||||
var passphrase string
|
||||
_, err := fmt.Scanln(&passphrase)
|
||||
if err != nil {
|
||||
return err
|
||||
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
|
||||
if len(passphrase) < 8 {
|
||||
fmt.Println("Please use a password manager to generate and store a good random passphrase.")
|
||||
return "", errors.New("Passphrase too short")
|
||||
}
|
||||
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(
|
||||
"https://notary:4443/v2/"+gun+"/_trust/tuf/",
|
||||
"",
|
||||
"json",
|
||||
"",
|
||||
"key",
|
||||
)
|
||||
return passphrase, nil
|
||||
}
|
||||
|
|
|
@ -0,0 +1,197 @@
|
|||
[1mdiff --git a/client/client.go b/client/client.go[m
|
||||
[1mindex 6916daf..8029996 100644[m
|
||||
[1m--- a/client/client.go[m
|
||||
[1m+++ b/client/client.go[m
|
||||
[36m@@ -618,7 +618,7 @@[m [mfunc (r *NotaryRepository) ListRootKeys() []string {[m
|
||||
func (r *NotaryRepository) GenRootKey(passphrase string) (string, error) {[m
|
||||
privKey, err := trustmanager.GenerateRSAKey(rand.Reader, rsaRootKeySize)[m
|
||||
if err != nil {[m
|
||||
[31m- return "", fmt.Errorf("failed to convert private key: ", err)[m
|
||||
[32m+[m [32mreturn "", fmt.Errorf("failed to convert private key: %v", err)[m
|
||||
}[m
|
||||
[m
|
||||
r.rootKeyStore.AddEncryptedKey(privKey.ID(), privKey, passphrase)[m
|
||||
[1mdiff --git a/cmd/notary/tuf.go b/cmd/notary/tuf.go[m
|
||||
[1mindex af21933..7825170 100644[m
|
||||
[1m--- a/cmd/notary/tuf.go[m
|
||||
[1m+++ b/cmd/notary/tuf.go[m
|
||||
[36m@@ -2,14 +2,13 @@[m [mpackage main[m
|
||||
[m
|
||||
import ([m
|
||||
"crypto/sha256"[m
|
||||
[32m+[m [32m"errors"[m
|
||||
"fmt"[m
|
||||
"io/ioutil"[m
|
||||
"os"[m
|
||||
[m
|
||||
"github.com/Sirupsen/logrus"[m
|
||||
notaryclient "github.com/docker/notary/client"[m
|
||||
[31m- "github.com/endophage/gotuf/data"[m
|
||||
[31m- "github.com/endophage/gotuf/keys"[m
|
||||
"github.com/spf13/cobra"[m
|
||||
"github.com/spf13/viper"[m
|
||||
)[m
|
||||
[36m@@ -107,13 +106,30 @@[m [mfunc tufInit(cmd *cobra.Command, args []string) {[m
|
||||
fatalf(err.Error())[m
|
||||
}[m
|
||||
[m
|
||||
[31m- // TODO(diogo): We don't want to generate a new root every time. Ask the user[m
|
||||
[31m- // which key she wants to use if there > 0 root keys available.[m
|
||||
[31m- rootKeyID, err := nRepo.GenRootKey("passphrase")[m
|
||||
[31m- if err != nil {[m
|
||||
[31m- fatalf(err.Error())[m
|
||||
[32m+[m [32mkeysList := nRepo.ListRootKeys()[m
|
||||
[32m+[m [32mvar passphrase string[m
|
||||
[32m+[m [32mvar rootKeyID string[m
|
||||
[32m+[m [32mif len(keysList) < 1 {[m
|
||||
[32m+[m [32mfmt.Println("No root keys found. Generating a new root key...")[m
|
||||
[32m+[m [32mpassphrase, err = passphraseRetriever()[m
|
||||
[32m+[m [32mif err != nil {[m
|
||||
[32m+[m [32mfatalf(err.Error())[m
|
||||
[32m+[m [32m}[m
|
||||
[32m+[m [32mrootKeyID, err = nRepo.GenRootKey(passphrase)[m
|
||||
[32m+[m [32mif err != nil {[m
|
||||
[32m+[m [32mfatalf(err.Error())[m
|
||||
[32m+[m [32m}[m
|
||||
[32m+[m [32m} else {[m
|
||||
[32m+[m [32mrootKeyID = keysList[0][m
|
||||
[32m+[m [32mfmt.Println("Root key found.")[m
|
||||
[32m+[m [32mfmt.Printf("Enter passphrase for: %s (%d)\n", rootKeyID, len(rootKeyID))[m
|
||||
[32m+[m [32mpassphrase, err = passphraseRetriever()[m
|
||||
[32m+[m [32mif err != nil {[m
|
||||
[32m+[m [32mfatalf(err.Error())[m
|
||||
[32m+[m [32m}[m
|
||||
}[m
|
||||
[31m- rootSigner, err := nRepo.GetRootSigner(rootKeyID, "passphrase")[m
|
||||
[32m+[m
|
||||
[32m+[m [32mrootSigner, err := nRepo.GetRootSigner(rootKeyID, passphrase)[m
|
||||
if err != nil {[m
|
||||
fatalf(err.Error())[m
|
||||
}[m
|
||||
[36m@@ -185,7 +201,7 @@[m [mfunc tufPublish(cmd *cobra.Command, args []string) {[m
|
||||
fatalf(err.Error())[m
|
||||
}[m
|
||||
[m
|
||||
[31m- err = repo.Publish(passwordRetriever)[m
|
||||
[32m+[m [32merr = repo.Publish(passphraseRetriever)[m
|
||||
if err != nil {[m
|
||||
fatalf(err.Error())[m
|
||||
}[m
|
||||
[36m@@ -249,76 +265,20 @@[m [mfunc verify(cmd *cobra.Command, args []string) {[m
|
||||
return[m
|
||||
}[m
|
||||
[m
|
||||
[31m-//func generateKeys(kdb *keys.KeyDB, signer *signed.Signer, remote store.RemoteStore) (string, string, string, string, error) {[m
|
||||
[31m-// rawTSKey, err := remote.GetKey("timestamp")[m
|
||||
[31m-// if err != nil {[m
|
||||
[31m-// return "", "", "", "", err[m
|
||||
[31m-// }[m
|
||||
[31m-// fmt.Println("RawKey: ", string(rawTSKey))[m
|
||||
[31m-// parsedKey := &data.TUFKey{}[m
|
||||
[31m-// err = json.Unmarshal(rawTSKey, parsedKey)[m
|
||||
[31m-// if err != nil {[m
|
||||
[31m-// return "", "", "", "", err[m
|
||||
[31m-// }[m
|
||||
[31m-// timestampKey := data.NewPublicKey(parsedKey.Cipher(), parsedKey.Public())[m
|
||||
[31m-//[m
|
||||
[31m-// rootKey, err := signer.Create("root")[m
|
||||
[31m-// if err != nil {[m
|
||||
[31m-// return "", "", "", "", err[m
|
||||
[31m-// }[m
|
||||
[31m-// targetsKey, err := signer.Create("targets")[m
|
||||
[31m-// if err != nil {[m
|
||||
[31m-// return "", "", "", "", err[m
|
||||
[31m-// }[m
|
||||
[31m-// snapshotKey, err := signer.Create("snapshot")[m
|
||||
[31m-// if err != nil {[m
|
||||
[31m-// return "", "", "", "", err[m
|
||||
[31m-// }[m
|
||||
[31m-//[m
|
||||
[31m-// kdb.AddKey(rootKey)[m
|
||||
[31m-// kdb.AddKey(targetsKey)[m
|
||||
[31m-// kdb.AddKey(snapshotKey)[m
|
||||
[31m-// kdb.AddKey(timestampKey)[m
|
||||
[31m-// return rootKey.ID(), targetsKey.ID(), snapshotKey.ID(), timestampKey.ID(), nil[m
|
||||
[31m-//}[m
|
||||
[31m-[m
|
||||
[31m-func generateRoles(kdb *keys.KeyDB, rootKeyID, targetsKeyID, snapshotKeyID, timestampKeyID string) error {[m
|
||||
[31m- rootRole, err := data.NewRole("root", 1, []string{rootKeyID}, nil, nil)[m
|
||||
[31m- if err != nil {[m
|
||||
[31m- return err[m
|
||||
[31m- }[m
|
||||
[31m- targetsRole, err := data.NewRole("targets", 1, []string{targetsKeyID}, nil, nil)[m
|
||||
[31m- if err != nil {[m
|
||||
[31m- return err[m
|
||||
[31m- }[m
|
||||
[31m- snapshotRole, err := data.NewRole("snapshot", 1, []string{snapshotKeyID}, nil, nil)[m
|
||||
[31m- if err != nil {[m
|
||||
[31m- return err[m
|
||||
[31m- }[m
|
||||
[31m- timestampRole, err := data.NewRole("timestamp", 1, []string{timestampKeyID}, nil, nil)[m
|
||||
[31m- if err != nil {[m
|
||||
[31m- return err[m
|
||||
[31m- }[m
|
||||
[32m+[m[32m// func passwordRetriever() (string, error) {[m
|
||||
[32m+[m[32m// return "passphrase", nil[m
|
||||
[32m+[m[32m// }[m
|
||||
[m
|
||||
[31m- err = kdb.AddRole(rootRole)[m
|
||||
[32m+[m[32mfunc passphraseRetriever() (string, error) {[m
|
||||
[32m+[m [32mfmt.Println("Please provide a passphrase for this root key: ")[m
|
||||
[32m+[m [32mvar passphrase string[m
|
||||
[32m+[m [32m_, err := fmt.Scanln(&passphrase)[m
|
||||
if err != nil {[m
|
||||
[31m- return err[m
|
||||
[32m+[m [32mreturn "", err[m
|
||||
}[m
|
||||
[31m- err = kdb.AddRole(targetsRole)[m
|
||||
[31m- if err != nil {[m
|
||||
[31m- return err[m
|
||||
[31m- }[m
|
||||
[31m- err = kdb.AddRole(snapshotRole)[m
|
||||
[31m- if err != nil {[m
|
||||
[31m- return err[m
|
||||
[32m+[m [32mif len(passphrase) < 8 {[m
|
||||
[32m+[m [32mfmt.Println("Please use a password manager to generate and store a good random passphrase.")[m
|
||||
[32m+[m [32mreturn "", errors.New("Passphrase too short")[m
|
||||
}[m
|
||||
[31m- err = kdb.AddRole(timestampRole)[m
|
||||
[31m- if err != nil {[m
|
||||
[31m- return err[m
|
||||
[31m- }[m
|
||||
[31m- return nil[m
|
||||
[31m-}[m
|
||||
[31m-[m
|
||||
[31m-func passwordRetriever() (string, error) {[m
|
||||
[31m- return "passphrase", nil[m
|
||||
[32m+[m [32mreturn passphrase, nil[m
|
||||
}[m
|
||||
[1mdiff --git a/trustmanager/keyfilestore.go b/trustmanager/keyfilestore.go[m
|
||||
[1mindex 6418139..f076c79 100644[m
|
||||
[1m--- a/trustmanager/keyfilestore.go[m
|
||||
[1m+++ b/trustmanager/keyfilestore.go[m
|
||||
[36m@@ -1,6 +1,11 @@[m
|
||||
package trustmanager[m
|
||||
[m
|
||||
[31m-import "github.com/endophage/gotuf/data"[m
|
||||
[32m+[m[32mimport ([m
|
||||
[32m+[m [32m"path/filepath"[m
|
||||
[32m+[m [32m"strings"[m
|
||||
[32m+[m
|
||||
[32m+[m [32m"github.com/endophage/gotuf/data"[m
|
||||
[32m+[m[32m)[m
|
||||
[m
|
||||
const ([m
|
||||
keyExtension = "key"[m
|
||||
[36m@@ -79,5 +84,10 @@[m [mfunc (s *KeyFileStore) GetDecryptedKey(name string, passphrase string) (*data.Pr[m
|
||||
// There might be symlinks associating Certificate IDs to Public Keys, so this[m
|
||||
// method only returns the IDs that aren't symlinks[m
|
||||
func (s *KeyFileStore) ListKeys() []string {[m
|
||||
[31m- return s.ListFiles(false)[m
|
||||
[32m+[m [32mvar keyIDList []string[m
|
||||
[32m+[m [32mfor _, f := range s.ListFiles(false) {[m
|
||||
[32m+[m [32mkeyID := strings.TrimSpace(strings.TrimSuffix(filepath.Base(f), filepath.Ext(f)))[m
|
||||
[32m+[m [32mkeyIDList = append(keyIDList, keyID)[m
|
||||
[32m+[m [32m}[m
|
||||
[32m+[m [32mreturn keyIDList[m
|
||||
}[m
|
|
@ -35,7 +35,7 @@ func MainHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) *e
|
|||
return nil
|
||||
}
|
||||
|
||||
// AddHandler adds the provided json data for the role and GUN specified in the URL
|
||||
// UpdateHandler adds the provided json data for the role and GUN specified in the URL
|
||||
func UpdateHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) *errors.HTTPError {
|
||||
defer r.Body.Close()
|
||||
s := ctx.Value("metaStore")
|
||||
|
@ -102,6 +102,13 @@ func GetHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) *er
|
|||
tufRole := vars["tufRole"]
|
||||
out, err := store.GetCurrent(gun, tufRole)
|
||||
if err != nil {
|
||||
if _, ok := err.(*storage.ErrNotFound); ok {
|
||||
return &errors.HTTPError{
|
||||
HTTPStatus: http.StatusNotFound,
|
||||
Code: 9999,
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
logrus.Errorf("[Notary Server] 500 GET repository: %s, role: %s", gun, tufRole)
|
||||
return &errors.HTTPError{
|
||||
HTTPStatus: http.StatusInternalServerError,
|
||||
|
@ -147,6 +154,7 @@ func DeleteHandler(ctx context.Context, w http.ResponseWriter, r *http.Request)
|
|||
return nil
|
||||
}
|
||||
|
||||
// GetTimestampHandler returns a timestamp.json given a GUN
|
||||
func GetTimestampHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) *errors.HTTPError {
|
||||
s := ctx.Value("metaStore")
|
||||
store, ok := s.(storage.MetaStore)
|
||||
|
@ -172,6 +180,13 @@ func GetTimestampHandler(ctx context.Context, w http.ResponseWriter, r *http.Req
|
|||
|
||||
out, err := timestamp.GetOrCreateTimestamp(gun, store, signer)
|
||||
if err != nil {
|
||||
if _, ok := err.(*storage.ErrNoKey); ok {
|
||||
return &errors.HTTPError{
|
||||
HTTPStatus: http.StatusNotFound,
|
||||
Code: 9999,
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
return &errors.HTTPError{
|
||||
HTTPStatus: http.StatusInternalServerError,
|
||||
Code: 9999,
|
||||
|
@ -184,6 +199,8 @@ func GetTimestampHandler(ctx context.Context, w http.ResponseWriter, r *http.Req
|
|||
return nil
|
||||
}
|
||||
|
||||
// GetTimestampKeyHandler returns a timestamp public key, creating a new key-pair
|
||||
// it if it doesn't yet exist
|
||||
func GetTimestampKeyHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) *errors.HTTPError {
|
||||
s := ctx.Value("metaStore")
|
||||
store, ok := s.(storage.MetaStore)
|
||||
|
|
|
@ -58,7 +58,7 @@ func Run(ctx context.Context, addr, tlsCertFile, tlsKeyFile string, trust signed
|
|||
lsnr = tls.NewListener(lsnr, tlsConfig)
|
||||
}
|
||||
|
||||
var ac auth.AccessController = nil
|
||||
var ac auth.AccessController
|
||||
hand := utils.RootHandlerFactory(ac, ctx, trust)
|
||||
|
||||
r := mux.NewRouter()
|
||||
|
|
|
@ -27,13 +27,14 @@ type MySQLStorage struct {
|
|||
sql.DB
|
||||
}
|
||||
|
||||
// NewMySQLStorage is a convenience method to create a MySQLStorage
|
||||
func NewMySQLStorage(db *sql.DB) *MySQLStorage {
|
||||
return &MySQLStorage{
|
||||
DB: *db,
|
||||
}
|
||||
}
|
||||
|
||||
// Update multiple TUF records in a single transaction.
|
||||
// UpdateCurrent updates multiple TUF records in a single transaction.
|
||||
// Always insert a new row. The unique constraint will ensure there is only ever
|
||||
func (db *MySQLStorage) UpdateCurrent(gun, role string, version int, data []byte) error {
|
||||
checkStmt := "SELECT count(*) FROM `tuf_files` WHERE `gun`=? AND `role`=? AND `version`>=?;"
|
||||
|
@ -62,7 +63,7 @@ func (db *MySQLStorage) UpdateCurrent(gun, role string, version int, data []byte
|
|||
return nil
|
||||
}
|
||||
|
||||
// Get a specific TUF record
|
||||
// GetCurrent gets a specific TUF record
|
||||
func (db *MySQLStorage) GetCurrent(gun, tufRole string) (data []byte, err error) {
|
||||
stmt := "SELECT `data` FROM `tuf_files` WHERE `gun`=? AND `role`=? ORDER BY `version` DESC LIMIT 1;"
|
||||
rows, err := db.Query(stmt, gun, tufRole) // this should be a QueryRow()
|
||||
|
@ -82,11 +83,14 @@ func (db *MySQLStorage) GetCurrent(gun, tufRole string) (data []byte, err error)
|
|||
return data, nil
|
||||
}
|
||||
|
||||
// Delete deletes all the records for a specific GUN
|
||||
func (db *MySQLStorage) Delete(gun string) error {
|
||||
stmt := "DELETE FROM `tuf_files` WHERE `gun`=?;"
|
||||
_, err := db.Exec(stmt, gun)
|
||||
return err
|
||||
}
|
||||
|
||||
// GetTimestampKey returns the timestamps Public Key data
|
||||
func (db *MySQLStorage) GetTimestampKey(gun string) (cipher string, public []byte, err error) {
|
||||
stmt := "SELECT `cipher`, `public` FROM `timestamp_keys` WHERE `gun`=?;"
|
||||
row := db.QueryRow(stmt, gun)
|
||||
|
@ -100,6 +104,8 @@ func (db *MySQLStorage) GetTimestampKey(gun string) (cipher string, public []byt
|
|||
|
||||
return cipher, public, err
|
||||
}
|
||||
|
||||
// SetTimestampKey attempts to write a TimeStamp key and returns an error if it already exists
|
||||
func (db *MySQLStorage) SetTimestampKey(gun, cipher string, public []byte) error {
|
||||
stmt := "INSERT INTO `timestamp_keys` (`gun`, `cipher`, `public`) VALUES (?,?,?);"
|
||||
_, err := db.Exec(stmt, gun, cipher, public)
|
||||
|
|
|
@ -4,30 +4,38 @@ import (
|
|||
"fmt"
|
||||
)
|
||||
|
||||
// ErrOldVersion is returned when a newer version of TUF metadada is already available
|
||||
type ErrOldVersion struct{}
|
||||
|
||||
// ErrOldVersion is returned when a newer version of TUF metadada is already available
|
||||
func (err ErrOldVersion) Error() string {
|
||||
return fmt.Sprintf("Error updating metadata. A newer version is already available")
|
||||
}
|
||||
|
||||
// ErrNotFound is returned when TUF metadata isn't found for a specific record
|
||||
type ErrNotFound struct{}
|
||||
|
||||
// Error implements error
|
||||
func (err ErrNotFound) Error() string {
|
||||
return fmt.Sprintf("No record found")
|
||||
}
|
||||
|
||||
// ErrTimestampKeyExists is returned when a timestamp key already exists
|
||||
type ErrTimestampKeyExists struct {
|
||||
gun string
|
||||
}
|
||||
|
||||
// ErrTimestampKeyExists is returned when a timestamp key already exists
|
||||
func (err ErrTimestampKeyExists) Error() string {
|
||||
return fmt.Sprintf("Error, timestamp key already exists for %s", err.gun)
|
||||
}
|
||||
|
||||
// ErrNoKey is returned when no timestamp key is found
|
||||
type ErrNoKey struct {
|
||||
gun string
|
||||
}
|
||||
|
||||
// ErrNoKey is returned when no timestamp key is found
|
||||
func (err ErrNoKey) Error() string {
|
||||
return fmt.Sprintf("Error, no timestamp key found for %s", err.gun)
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package storage
|
||||
|
||||
// MetaStore holds the methods that are used for a Metadata Store
|
||||
type MetaStore interface {
|
||||
UpdateCurrent(gun, role string, version int, data []byte) error
|
||||
GetCurrent(gun, tufRole string) (data []byte, err error)
|
||||
|
|
|
@ -16,23 +16,24 @@ type ver struct {
|
|||
data []byte
|
||||
}
|
||||
|
||||
// memStorage is really just designed for dev and testing. It is very
|
||||
// MemStorage is really just designed for dev and testing. It is very
|
||||
// inefficient in many scenarios
|
||||
type memStorage struct {
|
||||
type MemStorage struct {
|
||||
lock sync.Mutex
|
||||
tufMeta map[string][]*ver
|
||||
tsKeys map[string]*key
|
||||
}
|
||||
|
||||
// NewMemStorage instantiates a memStorage instance
|
||||
func NewMemStorage() *memStorage {
|
||||
return &memStorage{
|
||||
func NewMemStorage() *MemStorage {
|
||||
return &MemStorage{
|
||||
tufMeta: make(map[string][]*ver),
|
||||
tsKeys: make(map[string]*key),
|
||||
}
|
||||
}
|
||||
|
||||
func (st *memStorage) UpdateCurrent(gun, role string, version int, data []byte) error {
|
||||
// UpdateCurrent updates the meta data for a specific role
|
||||
func (st *MemStorage) UpdateCurrent(gun, role string, version int, data []byte) error {
|
||||
id := entryKey(gun, role)
|
||||
st.lock.Lock()
|
||||
defer st.lock.Unlock()
|
||||
|
@ -47,7 +48,8 @@ func (st *memStorage) UpdateCurrent(gun, role string, version int, data []byte)
|
|||
return nil
|
||||
}
|
||||
|
||||
func (st *memStorage) GetCurrent(gun, role string) (data []byte, err error) {
|
||||
// GetCurrent returns the metadada for a given role, under a GUN
|
||||
func (st *MemStorage) GetCurrent(gun, role string) (data []byte, err error) {
|
||||
id := entryKey(gun, role)
|
||||
st.lock.Lock()
|
||||
defer st.lock.Unlock()
|
||||
|
@ -58,10 +60,11 @@ func (st *memStorage) GetCurrent(gun, role string) (data []byte, err error) {
|
|||
return space[len(st.tufMeta[id])-1].data, nil
|
||||
}
|
||||
|
||||
func (st *memStorage) Delete(gun string) error {
|
||||
// Delete delets all the metadata for a given GUN
|
||||
func (st *MemStorage) Delete(gun string) error {
|
||||
st.lock.Lock()
|
||||
defer st.lock.Unlock()
|
||||
for k, _ := range st.tufMeta {
|
||||
for k := range st.tufMeta {
|
||||
if strings.HasPrefix(k, gun) {
|
||||
delete(st.tufMeta, k)
|
||||
}
|
||||
|
@ -69,17 +72,20 @@ func (st *memStorage) Delete(gun string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (st *memStorage) GetTimestampKey(gun string) (cipher string, public []byte, err error) {
|
||||
// GetTimestampKey returns the public key material of the timestamp key of a given gun
|
||||
func (st *MemStorage) GetTimestampKey(gun string) (cipher string, public []byte, err error) {
|
||||
// no need for lock. It's ok to return nil if an update
|
||||
// wasn't observed
|
||||
if k, ok := st.tsKeys[gun]; !ok {
|
||||
k, ok := st.tsKeys[gun]
|
||||
if !ok {
|
||||
return "", nil, &ErrNoKey{gun: gun}
|
||||
} else {
|
||||
return k.cipher, k.public, nil
|
||||
}
|
||||
|
||||
return k.cipher, k.public, nil
|
||||
}
|
||||
|
||||
func (st *memStorage) SetTimestampKey(gun, cipher string, public []byte) error {
|
||||
// SetTimestampKey sets a Timestamp key under a gun
|
||||
func (st *MemStorage) SetTimestampKey(gun, cipher string, public []byte) error {
|
||||
k := &key{cipher: cipher, public: public}
|
||||
st.lock.Lock()
|
||||
defer st.lock.Unlock()
|
||||
|
|
|
@ -17,6 +17,7 @@ type RufusSigner struct {
|
|||
sClient pb.SignerClient
|
||||
}
|
||||
|
||||
// NewRufusSigner is a convinience method that returns RufusSigner
|
||||
func NewRufusSigner(hostname string, port string, tlscafile string) *RufusSigner {
|
||||
var opts []grpc.DialOption
|
||||
netAddr := net.JoinHostPort(hostname, port)
|
||||
|
@ -53,7 +54,7 @@ func (trust *RufusSigner) Sign(keyIDs []string, toSign []byte) ([]data.Signature
|
|||
}
|
||||
signatures = append(signatures, data.Signature{
|
||||
KeyID: sig.KeyID.ID,
|
||||
Method: sig.Algorithm,
|
||||
Method: sig.Algorithm.Algorithm,
|
||||
Signature: sig.Content,
|
||||
})
|
||||
}
|
||||
|
@ -67,7 +68,7 @@ func (trust *RufusSigner) Create(role string) (*data.PublicKey, error) {
|
|||
return nil, err
|
||||
}
|
||||
//TODO(mccauley): Update API to return algorithm and/or take it as a param
|
||||
public := data.NewPublicKey(publicKey.Algorithm, publicKey.PublicKey)
|
||||
public := data.NewPublicKey(publicKey.Algorithm.Algorithm, publicKey.PublicKey)
|
||||
return public, nil
|
||||
}
|
||||
|
||||
|
@ -81,7 +82,7 @@ func (trust *RufusSigner) PublicKeys(keyIDs ...string) (map[string]*data.PublicK
|
|||
return nil, err
|
||||
}
|
||||
publicKeys[public.KeyID.ID] =
|
||||
data.NewPublicKey(public.Algorithm, public.PublicKey)
|
||||
data.NewPublicKey(public.Algorithm.Algorithm, public.PublicKey)
|
||||
}
|
||||
return publicKeys, nil
|
||||
}
|
||||
|
|
|
@ -17,14 +17,9 @@ type FileStore interface {
|
|||
RemoveDir(directoryName string) error
|
||||
Get(fileName string) ([]byte, error)
|
||||
GetPath(fileName string) string
|
||||
ListAll() []string
|
||||
ListDir(directoryName string) []string
|
||||
}
|
||||
|
||||
type EncryptedFileStore interface {
|
||||
FileStore
|
||||
AddEncrypted(fileName string, keyBytes []byte, passphrase string) error
|
||||
GetDecrypted(fileName string, passphrase string) ([]byte, error)
|
||||
ListFiles(symlinks bool) []string
|
||||
ListDir(directoryName string, symlinks bool) []string
|
||||
Link(src, dst string) error
|
||||
}
|
||||
|
||||
// SimpleFileStore implements FileStore
|
||||
|
@ -34,8 +29,8 @@ type SimpleFileStore struct {
|
|||
perms os.FileMode
|
||||
}
|
||||
|
||||
// NewFileStore creates a directory with 755 permissions
|
||||
func NewFileStore(baseDir string, fileExt string) (FileStore, error) {
|
||||
// NewSimpleFileStore creates a directory with 755 permissions
|
||||
func NewSimpleFileStore(baseDir string, fileExt string) (FileStore, error) {
|
||||
if err := CreateDirectory(baseDir); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -47,8 +42,8 @@ func NewFileStore(baseDir string, fileExt string) (FileStore, error) {
|
|||
}, nil
|
||||
}
|
||||
|
||||
// NewPrivateFileStore creates a directory with 700 permissions
|
||||
func NewPrivateFileStore(baseDir string, fileExt string) (FileStore, error) {
|
||||
// NewPrivateSimpleFileStore creates a directory with 700 permissions
|
||||
func NewPrivateSimpleFileStore(baseDir string, fileExt string) (FileStore, error) {
|
||||
if err := CreatePrivateDirectory(baseDir); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -108,19 +103,19 @@ func (f *SimpleFileStore) GetPath(name string) string {
|
|||
return f.genFilePath(name)
|
||||
}
|
||||
|
||||
// List lists all the files inside of a store
|
||||
func (f *SimpleFileStore) ListAll() []string {
|
||||
return f.list(f.baseDir)
|
||||
// ListFiles lists all the files inside of a store
|
||||
func (f *SimpleFileStore) ListFiles(symlinks bool) []string {
|
||||
return f.list(f.baseDir, symlinks)
|
||||
}
|
||||
|
||||
// List lists all the files inside of a directory identified by a name
|
||||
func (f *SimpleFileStore) ListDir(name string) []string {
|
||||
// ListDir lists all the files inside of a directory identified by a name
|
||||
func (f *SimpleFileStore) ListDir(name string, symlinks bool) []string {
|
||||
fullPath := filepath.Join(f.baseDir, name)
|
||||
return f.list(fullPath)
|
||||
return f.list(fullPath, symlinks)
|
||||
}
|
||||
|
||||
// list lists all the files in a directory given a full path
|
||||
func (f *SimpleFileStore) list(path string) []string {
|
||||
// list lists all the files in a directory given a full path. Ignores symlinks.
|
||||
func (f *SimpleFileStore) list(path string, symlinks bool) []string {
|
||||
files := make([]string, 0, 0)
|
||||
filepath.Walk(path, func(fp string, fi os.FileInfo, err error) error {
|
||||
// If there are errors, ignore this particular file
|
||||
|
@ -131,6 +126,12 @@ func (f *SimpleFileStore) list(path string) []string {
|
|||
if fi.IsDir() {
|
||||
return nil
|
||||
}
|
||||
|
||||
// If this is a symlink, and symlinks is true, ignore it
|
||||
if !symlinks && fi.Mode()&os.ModeSymlink == os.ModeSymlink {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Only allow matches that end with our certificate extension (e.g. *.crt)
|
||||
matched, _ := filepath.Match("*"+f.fileExt, fi.Name())
|
||||
|
||||
|
@ -144,10 +145,26 @@ func (f *SimpleFileStore) list(path string) []string {
|
|||
|
||||
// genFilePath returns the full path with extension given a file name
|
||||
func (f *SimpleFileStore) genFilePath(name string) string {
|
||||
fileName := fmt.Sprintf("%s.%s", name, f.fileExt)
|
||||
fileName := f.genFileName(name)
|
||||
return filepath.Join(f.baseDir, fileName)
|
||||
}
|
||||
|
||||
// genFileName returns the name using the right extension
|
||||
func (f *SimpleFileStore) genFileName(name string) string {
|
||||
return fmt.Sprintf("%s.%s", name, f.fileExt)
|
||||
}
|
||||
|
||||
// Link creates a symlink beetween the ID of the certificate used by a repository
|
||||
// and the ID of the root key that is being used.
|
||||
// We use full path for the source and local for the destination to use relative
|
||||
// path for the symlink
|
||||
func (f *SimpleFileStore) Link(oldname, newname string) error {
|
||||
return os.Symlink(
|
||||
f.genFileName(oldname),
|
||||
f.genFilePath(newname),
|
||||
)
|
||||
}
|
||||
|
||||
// CreateDirectory uses createDirectory to create a chmod 755 Directory
|
||||
func CreateDirectory(dir string) error {
|
||||
return createDirectory(dir, visible)
|
||||
|
|
|
@ -129,7 +129,7 @@ func TestRemoveDir(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestListAll(t *testing.T) {
|
||||
func TestListFiles(t *testing.T) {
|
||||
testName := "docker.com/notary/certificate"
|
||||
testExt := "crt"
|
||||
perms := os.FileMode(0755)
|
||||
|
@ -144,11 +144,18 @@ func TestListAll(t *testing.T) {
|
|||
// Create 10 randomfiles
|
||||
for i := 1; i <= 10; i++ {
|
||||
// Since we're generating this manually we need to add the extension '.'
|
||||
expectedFilePath = filepath.Join(tempBaseDir, testName+string(i)+"."+testExt)
|
||||
expectedFilename := testName + strconv.Itoa(i) + "." + testExt
|
||||
expectedFilePath = filepath.Join(tempBaseDir, expectedFilename)
|
||||
_, err = generateRandomFile(expectedFilePath, perms)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to generate random file: %v", err)
|
||||
}
|
||||
|
||||
// Create symlinks for all the files
|
||||
err = os.Symlink(expectedFilename, filepath.Join(tempBaseDir, expectedFilename+".link."+testExt))
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create symlink: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Create our SimpleFileStore
|
||||
|
@ -158,11 +165,17 @@ func TestListAll(t *testing.T) {
|
|||
perms: perms,
|
||||
}
|
||||
|
||||
// Call the List function
|
||||
files := store.ListAll()
|
||||
// Call the List function. Expect 10 real files when not listing symlinks
|
||||
files := store.ListFiles(false)
|
||||
if len(files) != 10 {
|
||||
t.Fatalf("expected 10 files in listing, got: %d", len(files))
|
||||
}
|
||||
|
||||
// Call the List function. Expect 20 total files when listing symlinks
|
||||
files = store.ListFiles(true)
|
||||
if len(files) != 20 {
|
||||
t.Fatalf("expected 20 files in listing, got: %d", len(files))
|
||||
}
|
||||
}
|
||||
|
||||
func TestListDir(t *testing.T) {
|
||||
|
@ -196,19 +209,64 @@ func TestListDir(t *testing.T) {
|
|||
}
|
||||
|
||||
// Call the ListDir function
|
||||
files := store.ListDir("docker.com/")
|
||||
files := store.ListDir("docker.com/", true)
|
||||
if len(files) != 10 {
|
||||
t.Fatalf("expected 10 files in listing, got: %d", len(files))
|
||||
}
|
||||
files = store.ListDir("docker.com/notary")
|
||||
files = store.ListDir("docker.com/notary", true)
|
||||
if len(files) != 10 {
|
||||
t.Fatalf("expected 10 files in listing, got: %d", len(files))
|
||||
}
|
||||
files = store.ListDir("fakedocker.com/")
|
||||
files = store.ListDir("fakedocker.com/", true)
|
||||
if len(files) != 0 {
|
||||
t.Fatalf("expected 0 files in listing, got: %d", len(files))
|
||||
}
|
||||
}
|
||||
|
||||
func TestLink(t *testing.T) {
|
||||
testName := "docker.com/notary/certificate"
|
||||
testSymlink := "docker.com/notary/certificate-symlink"
|
||||
testExt := "crt"
|
||||
perms := os.FileMode(0755)
|
||||
|
||||
// Temporary directory where test files will be created
|
||||
tempBaseDir, err := ioutil.TempDir("", "notary-test-")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create a temporary directory: %v", err)
|
||||
}
|
||||
|
||||
// Since we're generating this manually we need to add the extension '.'
|
||||
expectedFilePath := filepath.Join(tempBaseDir, testName+"."+testExt)
|
||||
expectedSymlinkPath := filepath.Join(tempBaseDir, testSymlink+"."+testExt)
|
||||
|
||||
_, err = generateRandomFile(expectedFilePath, perms)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to generate random file: %v", err)
|
||||
}
|
||||
|
||||
// Create our SimpleFileStore
|
||||
store := &SimpleFileStore{
|
||||
baseDir: tempBaseDir,
|
||||
fileExt: testExt,
|
||||
perms: perms,
|
||||
}
|
||||
|
||||
// Call the Link function
|
||||
err = store.Link("certificate", testSymlink)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create symlink: %v", err)
|
||||
}
|
||||
|
||||
// Check to see if the symlink exists
|
||||
actualSymlinkDest, err := os.Readlink(expectedSymlinkPath)
|
||||
if err != nil {
|
||||
t.Fatalf("expected to find symlink at path: %s", expectedSymlinkPath)
|
||||
}
|
||||
if actualSymlinkDest != "certificate."+testExt {
|
||||
t.Fatalf("symlink has wrong destination: %s", actualSymlinkDest)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetPath(t *testing.T) {
|
||||
testExt := "crt"
|
||||
perms := os.FileMode(0755)
|
||||
|
|
|
@ -1,6 +1,11 @@
|
|||
package trustmanager
|
||||
|
||||
import "errors"
|
||||
import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/endophage/gotuf/data"
|
||||
)
|
||||
|
||||
const (
|
||||
keyExtension = "key"
|
||||
|
@ -14,7 +19,7 @@ type KeyFileStore struct {
|
|||
// NewKeyFileStore returns a new KeyFileStore creating a private directory to
|
||||
// hold the keys.
|
||||
func NewKeyFileStore(baseDir string) (*KeyFileStore, error) {
|
||||
fileStore, err := NewFileStore(baseDir, keyExtension)
|
||||
fileStore, err := NewPrivateSimpleFileStore(baseDir, keyExtension)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -22,35 +27,67 @@ func NewKeyFileStore(baseDir string) (*KeyFileStore, error) {
|
|||
return &KeyFileStore{fileStore}, nil
|
||||
}
|
||||
|
||||
// AddEncrypted stores the contents of a PEM-encoded private key as an encrypted PEM block
|
||||
func (s *KeyFileStore) AddEncrypted(fileName string, pemKey []byte, passphrase string) error {
|
||||
|
||||
privKey, err := ParsePEMPrivateKey(pemKey)
|
||||
// AddKey stores the contents of a PEM-encoded private key as a PEM block
|
||||
func (s *KeyFileStore) AddKey(name string, privKey *data.PrivateKey) error {
|
||||
pemPrivKey, err := KeyToPEM(privKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
encryptedKey, err := EncryptPrivateKey(privKey, passphrase)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return s.Add(fileName, encryptedKey)
|
||||
return s.Add(name, pemPrivKey)
|
||||
}
|
||||
|
||||
// GetDecrypted decrypts and returns the PEM Encoded private key given a flename
|
||||
// and a passphrase
|
||||
func (s *KeyFileStore) GetDecrypted(fileName string, passphrase string) ([]byte, error) {
|
||||
keyBytes, err := s.Get(fileName)
|
||||
if err != nil {
|
||||
return nil, errors.New("could not retrieve private key material")
|
||||
}
|
||||
|
||||
// Gets an unencrypted PrivateKey.
|
||||
privKey, err := ParsePEMEncryptedPrivateKey(keyBytes, passphrase)
|
||||
// GetKey returns the PrivateKey given a KeyID
|
||||
func (s *KeyFileStore) GetKey(name string) (*data.PrivateKey, error) {
|
||||
keyBytes, err := s.Get(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return KeyToPEM(privKey)
|
||||
// Convert PEM encoded bytes back to a PrivateKey
|
||||
privKey, err := ParsePEMPrivateKey(keyBytes, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return privKey, nil
|
||||
}
|
||||
|
||||
// AddEncryptedKey stores the contents of a PEM-encoded private key as an encrypted PEM block
|
||||
func (s *KeyFileStore) AddEncryptedKey(name string, privKey *data.PrivateKey, passphrase string) error {
|
||||
encryptedPrivKey, err := EncryptPrivateKey(privKey, passphrase)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return s.Add(name, encryptedPrivKey)
|
||||
}
|
||||
|
||||
// GetDecryptedKey decrypts and returns the PEM Encoded private key given a flename
|
||||
// and a passphrase
|
||||
func (s *KeyFileStore) GetDecryptedKey(name string, passphrase string) (*data.PrivateKey, error) {
|
||||
keyBytes, err := s.Get(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Gets an unencrypted PrivateKey.
|
||||
privKey, err := ParsePEMPrivateKey(keyBytes, passphrase)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return privKey, nil
|
||||
}
|
||||
|
||||
// ListKeys returns a list of unique PublicKeys present on the KeyFileStore.
|
||||
// There might be symlinks associating Certificate IDs to Public Keys, so this
|
||||
// method only returns the IDs that aren't symlinks
|
||||
func (s *KeyFileStore) ListKeys() []string {
|
||||
var keyIDList []string
|
||||
for _, f := range s.ListFiles(false) {
|
||||
keyID := strings.TrimSpace(strings.TrimSuffix(filepath.Base(f), filepath.Ext(f)))
|
||||
keyIDList = append(keyIDList, keyID)
|
||||
}
|
||||
return keyIDList
|
||||
}
|
||||
|
|
|
@ -3,7 +3,6 @@ package trustmanager
|
|||
import (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
@ -30,19 +29,13 @@ func TestAddKey(t *testing.T) {
|
|||
t.Fatalf("failed to create new key filestore: %v", err)
|
||||
}
|
||||
|
||||
key, err := rsa.GenerateKey(rand.Reader, 1024)
|
||||
privKey, err := GenerateRSAKey(rand.Reader, 512)
|
||||
if err != nil {
|
||||
t.Fatalf("could not generate private key: %v", err)
|
||||
}
|
||||
|
||||
// Get the PEM for the key
|
||||
pemKey, err := KeyToPEM(key)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to convert private key to PEM: %v", err)
|
||||
}
|
||||
|
||||
// Call the Add function
|
||||
err = store.Add(testName, pemKey)
|
||||
// Call the AddKey function
|
||||
err = store.AddKey(testName, privKey)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to add file to store: %v", err)
|
||||
}
|
||||
|
@ -111,19 +104,23 @@ EMl3eFOJXjIch/wIesRSN+2dGOsl7neercjMh1i9RvpCwHDx/E0=
|
|||
t.Fatalf("failed to create new key filestore: %v", err)
|
||||
}
|
||||
|
||||
// Call the Get function
|
||||
pemKey, err := store.Get(testName)
|
||||
// Call the GetKey function
|
||||
privKey, err := store.GetKey(testName)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to get file from store: %v", err)
|
||||
}
|
||||
|
||||
if !bytes.Equal(testData, pemKey) {
|
||||
pemPrivKey, err := KeyToPEM(privKey)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to convert key to PEM: %v", err)
|
||||
}
|
||||
|
||||
if !bytes.Equal(testData, pemPrivKey) {
|
||||
t.Fatalf("unexpected content in the file: %s", filePath)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAddEncryptedAndGetDecrypted(t *testing.T) {
|
||||
testName := "docker.com/notary/root"
|
||||
testExt := "key"
|
||||
|
||||
// Temporary directory where test files will be created
|
||||
|
@ -132,9 +129,6 @@ func TestAddEncryptedAndGetDecrypted(t *testing.T) {
|
|||
t.Fatalf("failed to create a temporary directory: %v", err)
|
||||
}
|
||||
|
||||
// Since we're generating this manually we need to add the extension '.'
|
||||
expectedFilePath := filepath.Join(tempBaseDir, testName+"."+testExt)
|
||||
|
||||
// Create our FileStore
|
||||
store, err := NewKeyFileStore(tempBaseDir)
|
||||
if err != nil {
|
||||
|
@ -142,35 +136,38 @@ func TestAddEncryptedAndGetDecrypted(t *testing.T) {
|
|||
}
|
||||
|
||||
// Generate new PrivateKey
|
||||
key, err := rsa.GenerateKey(rand.Reader, 1024)
|
||||
privKey, err := GenerateRSAKey(rand.Reader, 512)
|
||||
if err != nil {
|
||||
t.Fatalf("could not generate private key: %v", err)
|
||||
}
|
||||
|
||||
// Get PEM encodedd key
|
||||
pemKey, err := KeyToPEM(key)
|
||||
if err != nil {
|
||||
t.Fatalf("Could not encode key to PEM: %v", err)
|
||||
}
|
||||
|
||||
// Call the Add function
|
||||
err = store.AddEncrypted(testName, pemKey, "diogomonica")
|
||||
// Call the AddEncryptedKey function
|
||||
err = store.AddEncryptedKey(privKey.ID(), privKey, "diogomonica")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to add file to store: %v", err)
|
||||
}
|
||||
|
||||
pemPrivKey, err := store.GetDecrypted(testName, "diogomonica")
|
||||
// Since we're generating this manually we need to add the extension '.'
|
||||
expectedFilePath := filepath.Join(tempBaseDir, privKey.ID()+"."+testExt)
|
||||
|
||||
// Check to see if file exists
|
||||
_, err = ioutil.ReadFile(expectedFilePath)
|
||||
if err != nil {
|
||||
t.Fatalf("expected file not found: %v", err)
|
||||
}
|
||||
|
||||
// Call the GetDecryptedKey function
|
||||
readPrivKey, err := store.GetDecryptedKey(privKey.ID(), "diogomonica")
|
||||
if err != nil {
|
||||
t.Fatalf("could not decrypt private key: %v", err)
|
||||
}
|
||||
|
||||
if !strings.Contains(string(pemKey), string(pemPrivKey)) {
|
||||
t.Fatalf("expected private key content in the file: %s", expectedFilePath)
|
||||
if !bytes.Equal(privKey.Private(), readPrivKey.Private()) {
|
||||
t.Fatalf("written key and loaded key do not match")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetDecryptedWithTamperedCipherText(t *testing.T) {
|
||||
testName := "docker.com/notary/root"
|
||||
testExt := "key"
|
||||
|
||||
// Temporary directory where test files will be created
|
||||
|
@ -179,9 +176,6 @@ func TestGetDecryptedWithTamperedCipherText(t *testing.T) {
|
|||
t.Fatalf("failed to create a temporary directory: %v", err)
|
||||
}
|
||||
|
||||
// Since we're generating this manually we need to add the extension '.'
|
||||
expectedFilePath := filepath.Join(tempBaseDir, testName+"."+testExt)
|
||||
|
||||
// Create our FileStore
|
||||
store, err := NewKeyFileStore(tempBaseDir)
|
||||
if err != nil {
|
||||
|
@ -189,24 +183,22 @@ func TestGetDecryptedWithTamperedCipherText(t *testing.T) {
|
|||
}
|
||||
|
||||
// Generate a new Private Key
|
||||
key, err := rsa.GenerateKey(rand.Reader, 1024)
|
||||
privKey, err := GenerateRSAKey(rand.Reader, 512)
|
||||
if err != nil {
|
||||
t.Fatalf("could not generate private key: %v", err)
|
||||
}
|
||||
|
||||
// Get PEM encodedd key
|
||||
pemKey, err := KeyToPEM(key)
|
||||
if err != nil {
|
||||
t.Fatalf("Could not encode key to PEM: %v", err)
|
||||
}
|
||||
// Call the Add function
|
||||
err = store.AddEncrypted(testName, pemKey, "diogomonica")
|
||||
// Call the AddEncryptedKey function
|
||||
err = store.AddEncryptedKey(privKey.ID(), privKey, "diogomonica")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to add file to store: %v", err)
|
||||
}
|
||||
|
||||
// Since we're generating this manually we need to add the extension '.'
|
||||
expectedFilePath := filepath.Join(tempBaseDir, privKey.ID()+"."+testExt)
|
||||
|
||||
// Get file description, open file
|
||||
fp, _ := os.OpenFile(expectedFilePath, os.O_WRONLY, 0600)
|
||||
fp, err := os.OpenFile(expectedFilePath, os.O_WRONLY, 0600)
|
||||
if err != nil {
|
||||
t.Fatalf("expected file not found: %v", err)
|
||||
}
|
||||
|
@ -215,7 +207,7 @@ func TestGetDecryptedWithTamperedCipherText(t *testing.T) {
|
|||
fp.WriteAt([]byte("a"), int64(1))
|
||||
|
||||
// Try to decrypt the file
|
||||
_, err = store.GetDecrypted(testName, "diogomonica")
|
||||
_, err = store.GetDecryptedKey(privKey.ID(), "diogomonica")
|
||||
if err == nil {
|
||||
t.Fatalf("expected error while decrypting the content due to invalid cipher text")
|
||||
}
|
||||
|
@ -237,24 +229,19 @@ func TestGetDecryptedWithInvalidPassphrase(t *testing.T) {
|
|||
}
|
||||
|
||||
// Generate a new random RSA Key
|
||||
key, err := rsa.GenerateKey(rand.Reader, 1024)
|
||||
privKey, err := GenerateRSAKey(rand.Reader, 512)
|
||||
if err != nil {
|
||||
t.Fatalf("could not generate private key: %v", err)
|
||||
}
|
||||
|
||||
// Get PEM encodedd key
|
||||
pemKey, err := KeyToPEM(key)
|
||||
if err != nil {
|
||||
t.Fatalf("Could not encode key to PEM: %v", err)
|
||||
}
|
||||
// Call the Add function
|
||||
err = store.AddEncrypted(testName, pemKey, "diogomonica")
|
||||
// Call the AddEncryptedKey function
|
||||
err = store.AddEncryptedKey(privKey.ID(), privKey, "diogomonica")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to add file to stoAFre: %v", err)
|
||||
}
|
||||
|
||||
// Try to decrypt the file with an invalid passphrase
|
||||
_, err = store.GetDecrypted(testName, "diegomonica")
|
||||
_, err = store.GetDecryptedKey(testName, "diegomonica")
|
||||
if err == nil {
|
||||
t.Fatalf("expected error while decrypting the content due to invalid passphrase")
|
||||
}
|
||||
|
|
|
@ -5,6 +5,8 @@ import (
|
|||
"errors"
|
||||
"os"
|
||||
"path"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
)
|
||||
|
||||
// X509FileStore implements X509Store that persists on disk
|
||||
|
@ -29,7 +31,7 @@ func NewX509FilteredFileStore(directory string, validate func(*x509.Certificate)
|
|||
}
|
||||
|
||||
func newX509FileStore(directory string, validate func(*x509.Certificate) bool) (*X509FileStore, error) {
|
||||
fileStore, err := NewFileStore(directory, certExtension)
|
||||
fileStore, err := NewSimpleFileStore(directory, certExtension)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -69,7 +71,7 @@ func (s X509FileStore) AddCert(cert *x509.Certificate) error {
|
|||
// stored under. If the file does not exist on disk, saves it.
|
||||
func (s X509FileStore) addNamedCert(cert *x509.Certificate) error {
|
||||
fingerprint := fingerprintCert(cert)
|
||||
|
||||
logrus.Debug("Adding cert with fingerprint: ", fingerprint)
|
||||
// Validate if we already loaded this certificate before
|
||||
if _, ok := s.fingerprintMap[fingerprint]; ok {
|
||||
return errors.New("certificate already in the store")
|
||||
|
@ -81,10 +83,12 @@ func (s X509FileStore) addNamedCert(cert *x509.Certificate) error {
|
|||
fileName := fileName(cert)
|
||||
|
||||
// Save the file to disk if not already there.
|
||||
if _, err := os.Stat(fileName); os.IsNotExist(err) {
|
||||
if _, err := os.Stat(s.fileStore.GetPath(fileName)); os.IsNotExist(err) {
|
||||
if err := s.fileStore.Add(fileName, certBytes); err != nil {
|
||||
return err
|
||||
}
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// We wrote the certificate succcessfully, add it to our in-memory storage
|
||||
|
@ -131,7 +135,7 @@ func (s X509FileStore) RemoveCert(cert *x509.Certificate) error {
|
|||
// AddCertFromPEM adds the first certificate that it finds in the byte[], returning
|
||||
// an error if no Certificates are found
|
||||
func (s X509FileStore) AddCertFromPEM(pemBytes []byte) error {
|
||||
cert, err := loadCertFromPEM(pemBytes)
|
||||
cert, err := LoadCertFromPEM(pemBytes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -1,16 +1,19 @@
|
|||
package trustmanager
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"math/big"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"github.com/endophage/gotuf/data"
|
||||
)
|
||||
|
@ -41,7 +44,7 @@ func GetCertFromURL(urlStr string) (*x509.Certificate, error) {
|
|||
}
|
||||
|
||||
// Try to extract the first valid PEM certificate from the bytes
|
||||
cert, err := loadCertFromPEM(certBytes)
|
||||
cert, err := LoadCertFromPEM(certBytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -56,34 +59,30 @@ func CertToPEM(cert *x509.Certificate) []byte {
|
|||
return pemCert
|
||||
}
|
||||
|
||||
// KeyToPEM returns a PEM encoded key from a crypto.PrivateKey
|
||||
func KeyToPEM(key crypto.PrivateKey) ([]byte, error) {
|
||||
rsaKey, ok := key.(*rsa.PrivateKey)
|
||||
if !ok {
|
||||
// KeyToPEM returns a PEM encoded key from a Private Key
|
||||
func KeyToPEM(privKey *data.PrivateKey) ([]byte, error) {
|
||||
if privKey.Cipher() != "RSA" {
|
||||
return nil, errors.New("only RSA keys are currently supported")
|
||||
}
|
||||
|
||||
keyBytes := x509.MarshalPKCS1PrivateKey(rsaKey)
|
||||
return pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: keyBytes}), nil
|
||||
return pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: privKey.Private()}), nil
|
||||
}
|
||||
|
||||
// EncryptPrivateKey returns an encrypted PEM encoded key given a Private key
|
||||
// EncryptPrivateKey returns an encrypted PEM key given a Privatekey
|
||||
// and a passphrase
|
||||
func EncryptPrivateKey(key crypto.PrivateKey, passphrase string) ([]byte, error) {
|
||||
rsaKey, ok := key.(*rsa.PrivateKey)
|
||||
if !ok {
|
||||
func EncryptPrivateKey(key *data.PrivateKey, passphrase string) ([]byte, error) {
|
||||
// TODO(diogo): Currently only supports RSA Private keys
|
||||
if key.Cipher() != "RSA" {
|
||||
return nil, errors.New("only RSA keys are currently supported")
|
||||
}
|
||||
|
||||
keyBytes := x509.MarshalPKCS1PrivateKey(rsaKey)
|
||||
|
||||
password := []byte(passphrase)
|
||||
cipherType := x509.PEMCipherAES256
|
||||
blockType := "RSA PRIVATE KEY"
|
||||
|
||||
encryptedPEMBlock, err := x509.EncryptPEMBlock(rand.Reader,
|
||||
blockType,
|
||||
keyBytes,
|
||||
key.Private(),
|
||||
password,
|
||||
cipherType)
|
||||
if err != nil {
|
||||
|
@ -93,9 +92,9 @@ func EncryptPrivateKey(key crypto.PrivateKey, passphrase string) ([]byte, error)
|
|||
return pem.EncodeToMemory(encryptedPEMBlock), nil
|
||||
}
|
||||
|
||||
// loadCertFromPEM returns the first certificate found in a bunch of bytes or error
|
||||
// LoadCertFromPEM returns the first certificate found in a bunch of bytes or error
|
||||
// if nothing is found. Taken from https://golang.org/src/crypto/x509/cert_pool.go#L85.
|
||||
func loadCertFromPEM(pemBytes []byte) (*x509.Certificate, error) {
|
||||
func LoadCertFromPEM(pemBytes []byte) (*x509.Certificate, error) {
|
||||
for len(pemBytes) > 0 {
|
||||
var block *pem.Block
|
||||
block, pemBytes = pem.Decode(pemBytes)
|
||||
|
@ -134,7 +133,7 @@ func fingerprintCert(cert *x509.Certificate) CertID {
|
|||
|
||||
// loadCertsFromDir receives a store AddCertFromFile for each certificate found
|
||||
func loadCertsFromDir(s *X509FileStore) {
|
||||
certFiles := s.fileStore.ListAll()
|
||||
certFiles := s.fileStore.ListFiles(true)
|
||||
for _, c := range certFiles {
|
||||
s.AddCertFromFile(c)
|
||||
}
|
||||
|
@ -161,39 +160,9 @@ func LoadCertFromFile(filename string) (*x509.Certificate, error) {
|
|||
return nil, errors.New("could not load certificate from file")
|
||||
}
|
||||
|
||||
// LoadKeyFromFile returns a PrivateKey given a filename
|
||||
func LoadKeyFromFile(filename string) (crypto.PrivateKey, error) {
|
||||
pemBytes, err := ioutil.ReadFile(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
key, err := ParsePEMPrivateKey(pemBytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return key, nil
|
||||
}
|
||||
|
||||
// ParsePEMPrivateKey returns a private key from a PEM encoded private key. It
|
||||
// only supports RSA (PKCS#1).
|
||||
func ParsePEMPrivateKey(pemBytes []byte) (crypto.PrivateKey, error) {
|
||||
block, _ := pem.Decode(pemBytes)
|
||||
if block == nil {
|
||||
return nil, errors.New("no valid key found")
|
||||
}
|
||||
|
||||
switch block.Type {
|
||||
case "RSA PRIVATE KEY":
|
||||
return x509.ParsePKCS1PrivateKey(block.Bytes)
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported key type %q", block.Type)
|
||||
}
|
||||
}
|
||||
|
||||
// ParsePEMEncryptedPrivateKey returns a private key from a PEM encrypted private key. It
|
||||
// only supports RSA (PKCS#1).
|
||||
func ParsePEMEncryptedPrivateKey(pemBytes []byte, passphrase string) (crypto.PrivateKey, error) {
|
||||
// ParsePEMPrivateKey returns a data.PrivateKey from a PEM encoded private key. It
|
||||
// only supports RSA (PKCS#1) and attempts to decrypt using the passphrase, if encrypted.
|
||||
func ParsePEMPrivateKey(pemBytes []byte, passphrase string) (*data.PrivateKey, error) {
|
||||
block, _ := pem.Decode(pemBytes)
|
||||
if block == nil {
|
||||
return nil, errors.New("no valid private key found")
|
||||
|
@ -201,17 +170,82 @@ func ParsePEMEncryptedPrivateKey(pemBytes []byte, passphrase string) (crypto.Pri
|
|||
|
||||
switch block.Type {
|
||||
case "RSA PRIVATE KEY":
|
||||
if !x509.IsEncryptedPEMBlock(block) {
|
||||
return nil, errors.New("private key is not encrypted")
|
||||
}
|
||||
var privKeyBytes []byte
|
||||
var err error
|
||||
|
||||
decryptedPEMBlock, err := x509.DecryptPEMBlock(block, []byte(passphrase))
|
||||
if x509.IsEncryptedPEMBlock(block) {
|
||||
privKeyBytes, err = x509.DecryptPEMBlock(block, []byte(passphrase))
|
||||
if err != nil {
|
||||
return nil, errors.New("could not decrypt private key")
|
||||
}
|
||||
} else {
|
||||
privKeyBytes = block.Bytes
|
||||
}
|
||||
|
||||
return x509.ParsePKCS1PrivateKey(decryptedPEMBlock)
|
||||
rsaPrivKey, err := x509.ParsePKCS1PrivateKey(privKeyBytes)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not parse DER encoded key: %v", err)
|
||||
}
|
||||
|
||||
tufRSAPrivateKey, err := RSAToPrivateKey(rsaPrivKey)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not convert rsa.PrivateKey to data.PrivateKey: %v", err)
|
||||
}
|
||||
|
||||
return tufRSAPrivateKey, nil
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported key type %q", block.Type)
|
||||
}
|
||||
}
|
||||
|
||||
// GenerateRSAKey generates an RSA Private key and returns a TUF PrivateKey
|
||||
func GenerateRSAKey(random io.Reader, bits int) (*data.PrivateKey, error) {
|
||||
rsaPrivKey, err := rsa.GenerateKey(random, bits)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not generate private key: %v", err)
|
||||
}
|
||||
|
||||
return RSAToPrivateKey(rsaPrivKey)
|
||||
}
|
||||
|
||||
// RSAToPrivateKey converts an rsa.Private key to a TUF data.PrivateKey type
|
||||
func RSAToPrivateKey(rsaPrivKey *rsa.PrivateKey) (*data.PrivateKey, error) {
|
||||
// Get a DER-encoded representation of the PublicKey
|
||||
rsaPubBytes, err := x509.MarshalPKIXPublicKey(&rsaPrivKey.PublicKey)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to marshal private key: %v", err)
|
||||
}
|
||||
|
||||
// Get a DER-encoded representation of the PrivateKey
|
||||
rsaPrivBytes := x509.MarshalPKCS1PrivateKey(rsaPrivKey)
|
||||
|
||||
return data.NewPrivateKey("RSA", rsaPubBytes, rsaPrivBytes), nil
|
||||
}
|
||||
|
||||
// NewCertificate returns an X509 Certificate following a template, given a GUN.
|
||||
func NewCertificate(gun string) (*x509.Certificate, error) {
|
||||
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 {
|
||||
return nil, fmt.Errorf("failed to generate new certificate: %v", err)
|
||||
}
|
||||
|
||||
// TODO(diogo): Currently hard coding organization to be the gun. Revisit.
|
||||
return &x509.Certificate{
|
||||
SerialNumber: serialNumber,
|
||||
Subject: pkix.Name{
|
||||
Organization: []string{gun},
|
||||
CommonName: gun,
|
||||
},
|
||||
NotBefore: notBefore,
|
||||
NotAfter: notAfter,
|
||||
|
||||
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
|
||||
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageCodeSigning},
|
||||
BasicConstraintsValid: true,
|
||||
}, nil
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue