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",
|
"ImportPath": "github.com/docker/rufus/proto",
|
||||||
"Rev": "61b53384b24bfa83e8e0a5f11f28ae83457fd80c"
|
"Rev": "7f61f678c264ae0a329f25cbaa8af6fd55ada7b6"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/endophage/gotuf",
|
"ImportPath": "github.com/endophage/gotuf",
|
||||||
"Rev": "682ec56d6a7b60e432bc2560e17d8e1aec84d171"
|
"Rev": "66da486b58ef378c96433af965f61ca0efaccb9a"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/go-sql-driver/mysql",
|
"ImportPath": "github.com/go-sql-driver/mysql",
|
||||||
|
|
|
@ -10,6 +10,7 @@ It is generated from these files:
|
||||||
|
|
||||||
It has these top-level messages:
|
It has these top-level messages:
|
||||||
KeyID
|
KeyID
|
||||||
|
Algorithm
|
||||||
PublicKey
|
PublicKey
|
||||||
Signature
|
Signature
|
||||||
SignatureRequest
|
SignatureRequest
|
||||||
|
@ -40,10 +41,20 @@ func (m *KeyID) Reset() { *m = KeyID{} }
|
||||||
func (m *KeyID) String() string { return proto1.CompactTextString(m) }
|
func (m *KeyID) String() string { return proto1.CompactTextString(m) }
|
||||||
func (*KeyID) ProtoMessage() {}
|
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 {
|
type PublicKey struct {
|
||||||
KeyID *KeyID `protobuf:"bytes,1,opt,name=keyID" json:"keyID,omitempty"`
|
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{} }
|
func (m *PublicKey) Reset() { *m = PublicKey{} }
|
||||||
|
@ -57,10 +68,18 @@ func (m *PublicKey) GetKeyID() *KeyID {
|
||||||
return nil
|
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 {
|
type Signature struct {
|
||||||
KeyID *KeyID `protobuf:"bytes,1,opt,name=keyID" json:"keyID,omitempty"`
|
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{} }
|
func (m *Signature) Reset() { *m = Signature{} }
|
||||||
|
@ -74,10 +93,18 @@ func (m *Signature) GetKeyID() *KeyID {
|
||||||
return nil
|
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 {
|
type SignatureRequest struct {
|
||||||
KeyID *KeyID `protobuf:"bytes,1,opt,name=keyID" json:"keyID,omitempty"`
|
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{} }
|
func (m *SignatureRequest) Reset() { *m = SignatureRequest{} }
|
||||||
|
@ -91,6 +118,13 @@ func (m *SignatureRequest) GetKeyID() *KeyID {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *SignatureRequest) GetAlgorithm() *Algorithm {
|
||||||
|
if m != nil {
|
||||||
|
return m.Algorithm
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// Void represents an empty message type
|
// Void represents an empty message type
|
||||||
type Void struct {
|
type Void struct {
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,22 +26,30 @@ message KeyID {
|
||||||
string ID = 1;
|
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 {
|
message PublicKey {
|
||||||
KeyID keyID = 1;
|
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 {
|
message Signature {
|
||||||
KeyID keyID = 1;
|
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 {
|
message SignatureRequest {
|
||||||
KeyID keyID = 1;
|
KeyID keyID = 1;
|
||||||
bytes content = 2;
|
Algorithm algorithm = 2;
|
||||||
|
bytes content = 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Void represents an empty message type
|
// 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
|
package signed
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/pem"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/endophage/gotuf/data"
|
"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 {
|
type MockCryptoService struct {
|
||||||
testKey keys.PublicKey
|
testKey data.PublicKey
|
||||||
}
|
}
|
||||||
|
|
||||||
func (mts *MockCryptoService) Sign(keyIDs []string, _ []byte) ([]data.Signature, error) {
|
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
|
return sigs, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (mts *MockCryptoService) Create() (*keys.PublicKey, error) {
|
func (mts *MockCryptoService) Create(_ string) (*data.PublicKey, error) {
|
||||||
return &mts.testKey, nil
|
return &mts.testKey, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (mts *MockCryptoService) PublicKeys(keyIDs ...string) (map[string]*keys.PublicKey, error) {
|
func (mts *MockCryptoService) PublicKeys(keyIDs ...string) (map[string]*data.PublicKey, error) {
|
||||||
keys := map[string]*keys.PublicKey{"testID": &mts.testKey}
|
keys := map[string]*data.PublicKey{"testID": &mts.testKey}
|
||||||
return keys, nil
|
return keys, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -32,8 +40,10 @@ var _ CryptoService = &MockCryptoService{}
|
||||||
|
|
||||||
// Test signing and ensure the expected signature is added
|
// Test signing and ensure the expected signature is added
|
||||||
func TestBasicSign(t *testing.T) {
|
func TestBasicSign(t *testing.T) {
|
||||||
|
testKey, _ := pem.Decode([]byte(testKeyPEM1))
|
||||||
|
k := data.NewPublicKey("rsa", testKey.Bytes)
|
||||||
signer := Signer{&MockCryptoService{
|
signer := Signer{&MockCryptoService{
|
||||||
testKey: keys.PublicKey{ID: "testID"},
|
testKey: *k,
|
||||||
}}
|
}}
|
||||||
key, err := signer.Create("root")
|
key, err := signer.Create("root")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -47,7 +57,7 @@ func TestBasicSign(t *testing.T) {
|
||||||
t.Fatalf("Incorrect number of signatures: %d", len(testData.Signatures))
|
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)
|
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
|
// 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)
|
// should be cleaning previous signatures by the KeyID when asked to sign again)
|
||||||
func TestReSign(t *testing.T) {
|
func TestReSign(t *testing.T) {
|
||||||
|
testKey, _ := pem.Decode([]byte(testKeyPEM1))
|
||||||
|
k := data.NewPublicKey("rsa", testKey.Bytes)
|
||||||
signer := Signer{&MockCryptoService{
|
signer := Signer{&MockCryptoService{
|
||||||
testKey: keys.PublicKey{},
|
testKey: *k,
|
||||||
}}
|
}}
|
||||||
key := keys.PublicKey{ID: "testID"}
|
|
||||||
testData := data.Signed{}
|
testData := data.Signed{}
|
||||||
|
|
||||||
signer.Sign(&testData, &key)
|
signer.Sign(&testData, k)
|
||||||
signer.Sign(&testData, &key)
|
signer.Sign(&testData, k)
|
||||||
|
|
||||||
if len(testData.Signatures) != 1 {
|
if len(testData.Signatures) != 1 {
|
||||||
t.Fatalf("Incorrect number of signatures: %d", len(testData.Signatures))
|
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)
|
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) {
|
func TestMultiSign(t *testing.T) {
|
||||||
signer := Signer{&MockCryptoService{}}
|
signer := Signer{&MockCryptoService{}}
|
||||||
key := keys.PublicKey{ID: "testID1"}
|
|
||||||
testData := data.Signed{}
|
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"}
|
testKey, _ = pem.Decode([]byte(testKeyPEM2))
|
||||||
signer.Sign(&testData, &key)
|
key = data.NewPublicKey("rsa", testKey.Bytes)
|
||||||
|
signer.Sign(&testData, key)
|
||||||
|
|
||||||
if len(testData.Signatures) != 2 {
|
if len(testData.Signatures) != 2 {
|
||||||
t.Fatalf("Incorrect number of signatures: %d", len(testData.Signatures))
|
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 {
|
for _, sig := range testData.Signatures {
|
||||||
if _, ok := keyIDs[sig.KeyID]; !ok {
|
if _, ok := keyIDs[sig.KeyID]; !ok {
|
||||||
t.Fatalf("Got a signature we didn't expect: %s", sig.KeyID)
|
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) {
|
func TestCreate(t *testing.T) {
|
||||||
|
testKey, _ := pem.Decode([]byte(testKeyPEM1))
|
||||||
|
k := data.NewPublicKey("rsa", testKey.Bytes)
|
||||||
signer := Signer{&MockCryptoService{
|
signer := Signer{&MockCryptoService{
|
||||||
testKey: keys.PublicKey{ID: "testID"},
|
testKey: *k,
|
||||||
}}
|
}}
|
||||||
|
|
||||||
key, err := signer.Create("root")
|
key, err := signer.Create("root")
|
||||||
|
@ -109,7 +124,7 @@ func TestCreate(t *testing.T) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
if key.ID != "testID" {
|
if key.ID() != testKeyID1 {
|
||||||
t.Fatalf("Expected key ID not found: %s", key.ID)
|
t.Fatalf("Expected key ID not found: %s", key.ID())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
package signed
|
package signed
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto"
|
_ "crypto"
|
||||||
"crypto/rsa"
|
_ "crypto/rsa"
|
||||||
"crypto/sha256"
|
_ "crypto/sha256"
|
||||||
"crypto/x509"
|
_ "crypto/x509"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -23,7 +23,7 @@ func (VerifySuite) Test(c *C) {
|
||||||
signer := NewSigner(trust)
|
signer := NewSigner(trust)
|
||||||
type test struct {
|
type test struct {
|
||||||
name string
|
name string
|
||||||
keys []*keys.PublicKey
|
keys []*data.PublicKey
|
||||||
roles map[string]*data.Role
|
roles map[string]*data.Role
|
||||||
s *data.Signed
|
s *data.Signed
|
||||||
ver int
|
ver int
|
||||||
|
@ -81,7 +81,7 @@ func (VerifySuite) Test(c *C) {
|
||||||
k, _ := signer.Create("root")
|
k, _ := signer.Create("root")
|
||||||
signer.Sign(t.s, k)
|
signer.Sign(t.s, k)
|
||||||
t.keys = append(t.keys, 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",
|
name: "expired",
|
||||||
exp: &expiredTime,
|
exp: &expiredTime,
|
||||||
err: ErrExpired{expiredTime},
|
err: ErrExpired{expiredTime.Format("2006-01-02 15:04:05 MST")},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, t := range tests {
|
for _, t := range tests {
|
||||||
|
@ -154,24 +154,27 @@ func (VerifySuite) Test(c *C) {
|
||||||
t.exp = &expires
|
t.exp = &expires
|
||||||
}
|
}
|
||||||
if t.typ == "" {
|
if t.typ == "" {
|
||||||
t.typ = t.role
|
t.typ = data.TUFTypes[t.role]
|
||||||
}
|
}
|
||||||
if t.keys == nil && t.s == nil {
|
if t.keys == nil && t.s == nil {
|
||||||
k, _ := signer.Create("root")
|
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)
|
b, err := cjson.Marshal(meta)
|
||||||
c.Assert(err, IsNil)
|
c.Assert(err, IsNil)
|
||||||
s := &data.Signed{Signed: b}
|
s := &data.Signed{Signed: b}
|
||||||
signer.Sign(s, k)
|
signer.Sign(s, k)
|
||||||
t.s = s
|
t.s = s
|
||||||
t.keys = []*keys.PublicKey{k}
|
t.keys = []*data.PublicKey{k}
|
||||||
}
|
}
|
||||||
if t.roles == nil {
|
if t.roles == nil {
|
||||||
t.roles = map[string]*data.Role{
|
t.roles = map[string]*data.Role{
|
||||||
"root": &data.Role{
|
"root": &data.Role{
|
||||||
KeyIDs: []string{t.keys[0].ID},
|
RootRole: data.RootRole{
|
||||||
Threshold: 1,
|
KeyIDs: []string{t.keys[0].ID()},
|
||||||
|
Threshold: 1,
|
||||||
|
},
|
||||||
|
Name: "root",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -181,11 +184,10 @@ func (VerifySuite) Test(c *C) {
|
||||||
|
|
||||||
db := keys.NewDB()
|
db := keys.NewDB()
|
||||||
for _, k := range t.keys {
|
for _, k := range t.keys {
|
||||||
err := db.AddKey(k)
|
db.AddKey(k)
|
||||||
c.Assert(err, IsNil)
|
|
||||||
}
|
}
|
||||||
for n, r := range t.roles {
|
for _, r := range t.roles {
|
||||||
err := db.AddRole(n, r)
|
err := db.AddRole(r)
|
||||||
c.Assert(err, IsNil)
|
c.Assert(err, IsNil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -203,5 +205,5 @@ func assertErrExpired(c *C, err error, expected ErrExpired) {
|
||||||
if !ok {
|
if !ok {
|
||||||
c.Fatalf("expected err to have type ErrExpired, got %T", err)
|
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"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"path"
|
"path"
|
||||||
|
|
||||||
|
@ -61,6 +62,9 @@ func (s HTTPStore) GetMeta(name string, size int64) (json.RawMessage, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
if resp.StatusCode == http.StatusNotFound {
|
||||||
|
return nil, &ErrMetaNotFound{role: name}
|
||||||
|
}
|
||||||
b := io.LimitReader(resp.Body, int64(size))
|
b := io.LimitReader(resp.Body, int64(size))
|
||||||
body, err := ioutil.ReadAll(b)
|
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
|
// also relies on the keysDB having already been populated with the keys and
|
||||||
// roles.
|
// roles.
|
||||||
func (tr *TufRepo) InitRepo(consistent bool) error {
|
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)
|
rootRoles := make(map[string]*data.RootRole)
|
||||||
rootKeys := make(map[string]*data.PublicKey)
|
rootKeys := make(map[string]*data.PublicKey)
|
||||||
for _, r := range data.ValidRoles {
|
for _, r := range data.ValidRoles {
|
||||||
|
@ -199,15 +215,21 @@ func (tr *TufRepo) InitRepo(consistent bool) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
tr.Root = root
|
tr.Root = root
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tr *TufRepo) InitTargets() error {
|
||||||
targets := data.NewTargets()
|
targets := data.NewTargets()
|
||||||
tr.Targets[data.ValidRoles["targets"]] = targets
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
signedTargets, err := tr.SignTargets("targets", data.DefaultExpires("targets"))
|
signedTargets, err := tr.SignTargets("targets", data.DefaultExpires("targets"), nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -216,8 +238,11 @@ func (tr *TufRepo) InitRepo(consistent bool) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
tr.Snapshot = snapshot
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -428,7 +453,7 @@ func (tr *TufRepo) UpdateTimestamp(s *data.Signed) error {
|
||||||
return nil
|
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")
|
logrus.Debug("SignRoot")
|
||||||
if tr.Root.Dirty {
|
if tr.Root.Dirty {
|
||||||
tr.Root.Signed.Version++
|
tr.Root.Signed.Version++
|
||||||
|
@ -438,7 +463,7 @@ func (tr *TufRepo) SignRoot(expires time.Time) (*data.Signed, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
signed, err = tr.sign(signed, *root)
|
signed, err = tr.sign(signed, *root, signer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -446,7 +471,7 @@ func (tr *TufRepo) SignRoot(expires time.Time) (*data.Signed, error) {
|
||||||
return signed, nil
|
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("SignTargets")
|
||||||
logrus.Debug("Got targets data.Signed object")
|
logrus.Debug("Got targets data.Signed object")
|
||||||
if tr.Targets[role].Dirty {
|
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)
|
targets := tr.keysDB.GetRole(role)
|
||||||
logrus.Debug("About to sign ", role)
|
logrus.Debug("About to sign ", role)
|
||||||
signed, err = tr.sign(signed, *targets)
|
signed, err = tr.sign(signed, *targets, signer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logrus.Debug("errored signing ", role)
|
logrus.Debug("errored signing ", role)
|
||||||
return nil, err
|
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")
|
logrus.Debug("SignSnapshot")
|
||||||
if tr.Root.Dirty {
|
if tr.Root.Dirty {
|
||||||
signedRoot, err := tr.SignRoot(data.DefaultExpires("root"))
|
signedRoot, err := tr.SignRoot(data.DefaultExpires("root"), signer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -493,7 +518,7 @@ func (tr *TufRepo) SignSnapshot(expires time.Time) (*data.Signed, error) {
|
||||||
if !targets.Dirty {
|
if !targets.Dirty {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
signedTargets, err := tr.SignTargets(role, data.DefaultExpires("targets"))
|
signedTargets, err := tr.SignTargets(role, data.DefaultExpires("targets"), signer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -510,7 +535,7 @@ func (tr *TufRepo) SignSnapshot(expires time.Time) (*data.Signed, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
snapshot := tr.keysDB.GetRole(data.ValidRoles["snapshot"])
|
snapshot := tr.keysDB.GetRole(data.ValidRoles["snapshot"])
|
||||||
signed, err = tr.sign(signed, *snapshot)
|
signed, err = tr.sign(signed, *snapshot, signer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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")
|
logrus.Debug("SignTimestamp")
|
||||||
if tr.Snapshot.Dirty {
|
if tr.Snapshot.Dirty {
|
||||||
signedSnapshot, err := tr.SignSnapshot(data.DefaultExpires("snapshot"))
|
signedSnapshot, err := tr.SignSnapshot(data.DefaultExpires("snapshot"), signer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -544,7 +569,7 @@ func (tr *TufRepo) SignTimestamp(expires time.Time) (*data.Signed, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
timestamp := tr.keysDB.GetRole(data.ValidRoles["timestamp"])
|
timestamp := tr.keysDB.GetRole(data.ValidRoles["timestamp"])
|
||||||
signed, err = tr.sign(signed, *timestamp)
|
signed, err = tr.sign(signed, *timestamp, signer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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))
|
ks := make([]*data.PublicKey, 0, len(role.KeyIDs))
|
||||||
for _, kid := range role.KeyIDs {
|
for _, kid := range role.KeyIDs {
|
||||||
k := tr.keysDB.GetKey(kid)
|
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 {
|
if len(ks) < 1 {
|
||||||
return nil, keys.ErrInvalidKey
|
return nil, keys.ErrInvalidKey
|
||||||
}
|
}
|
||||||
err := tr.signer.Sign(signed, ks...)
|
if signer != nil {
|
||||||
if err != nil {
|
err := signer.Sign(signed, ks...)
|
||||||
return nil, err
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
err := tr.signer.Sign(signed, ks...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return signed, nil
|
return signed, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -88,7 +88,7 @@ func writeRepo(t *testing.T, dir string, repo *TufRepo) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
signedRoot, err := repo.SignRoot(data.DefaultExpires("root"))
|
signedRoot, err := repo.SignRoot(data.DefaultExpires("root"), nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -96,7 +96,7 @@ func writeRepo(t *testing.T, dir string, repo *TufRepo) {
|
||||||
ioutil.WriteFile(dir+"/root.json", rootJSON, 0755)
|
ioutil.WriteFile(dir+"/root.json", rootJSON, 0755)
|
||||||
|
|
||||||
for r, _ := range repo.Targets {
|
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 {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -107,14 +107,14 @@ func writeRepo(t *testing.T, dir string, repo *TufRepo) {
|
||||||
ioutil.WriteFile(p, targetsJSON, 0755)
|
ioutil.WriteFile(p, targetsJSON, 0755)
|
||||||
}
|
}
|
||||||
|
|
||||||
signedSnapshot, err := repo.SignSnapshot(data.DefaultExpires("snapshot"))
|
signedSnapshot, err := repo.SignSnapshot(data.DefaultExpires("snapshot"), nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
snapshotJSON, _ := json.Marshal(signedSnapshot)
|
snapshotJSON, _ := json.Marshal(signedSnapshot)
|
||||||
ioutil.WriteFile(dir+"/snapshot.json", snapshotJSON, 0755)
|
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 {
|
if err != nil {
|
||||||
t.Fatal(err)
|
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_cert_file": "./fixtures/notary.pem",
|
||||||
"tls_key_file": "./fixtures/notary.key"
|
"tls_key_file": "./fixtures/notary.key"
|
||||||
},
|
},
|
||||||
"trust_service":{
|
"trust_service": {
|
||||||
"type": "local",
|
"type": "local",
|
||||||
"hostname": "",
|
"hostname": "rufus",
|
||||||
"port": ""
|
"port": "7899",
|
||||||
|
"tls_ca_file": "./fixtures/ca.cert"
|
||||||
},
|
},
|
||||||
"logging": {
|
"logging": {
|
||||||
"level": 5
|
"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
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/rand"
|
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"crypto/x509/pkix"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
"math/big"
|
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
@ -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.
|
// 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 {
|
if len(keyList) < 1 {
|
||||||
fatalf("no Private Keys found under Global Unique Name: %s", gunOrID)
|
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("")
|
||||||
fmt.Println("# Signing keys: ")
|
fmt.Println("# Signing keys: ")
|
||||||
for _, k := range privKeyStore.ListAll() {
|
for _, k := range privKeyStore.ListFiles(true) {
|
||||||
printKey(k)
|
printKey(k)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -207,39 +204,14 @@ func keysGenerate(cmd *cobra.Command, args []string) {
|
||||||
fatalf("invalid Global Unique Name: %s", gun)
|
fatalf("invalid Global Unique Name: %s", gun)
|
||||||
}
|
}
|
||||||
|
|
||||||
_, cert, err := generateKeyAndCert(gun)
|
// _, cert, err := generateKeyAndCert(gun)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
fatalf("could not generate key: %v", err)
|
// fatalf("could not generate key: %v", err)
|
||||||
}
|
// }
|
||||||
|
|
||||||
certificateStore.AddCert(cert)
|
// certificateStore.AddCert(cert)
|
||||||
fingerprint := trustmanager.FingerprintCert(cert)
|
// fingerprint := trustmanager.FingerprintCert(cert)
|
||||||
fmt.Println("Generated new keypair with ID: ", fingerprint)
|
// fmt.Println("Generated new keypair with ID: ", fingerprint)
|
||||||
}
|
|
||||||
|
|
||||||
func newCertificate(gun, organization string) *x509.Certificate {
|
|
||||||
notBefore := time.Now()
|
|
||||||
notAfter := notBefore.Add(time.Hour * 24 * 365 * 2)
|
|
||||||
|
|
||||||
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
|
|
||||||
serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
|
|
||||||
if err != nil {
|
|
||||||
fatalf("failed to generate serial number: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &x509.Certificate{
|
|
||||||
SerialNumber: serialNumber,
|
|
||||||
Subject: pkix.Name{
|
|
||||||
Organization: []string{organization},
|
|
||||||
CommonName: gun,
|
|
||||||
},
|
|
||||||
NotBefore: notBefore,
|
|
||||||
NotAfter: notAfter,
|
|
||||||
|
|
||||||
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
|
|
||||||
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageCodeSigning},
|
|
||||||
BasicConstraintsValid: true,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func printCert(cert *x509.Certificate) {
|
func printCert(cert *x509.Certificate) {
|
||||||
|
|
|
@ -16,18 +16,15 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
const configFileName string = "config"
|
const configFileName string = "config"
|
||||||
|
|
||||||
// Default paths should end with a '/' so directory creation works correctly
|
|
||||||
const configPath string = ".docker/trust/"
|
const configPath string = ".docker/trust/"
|
||||||
const trustDir string = configPath + "trusted_certificates/"
|
const trustDir string = "trusted_certificates/"
|
||||||
const privDir string = configPath + "private/"
|
const privDir string = "private/"
|
||||||
const tufDir string = configPath + "tuf/"
|
const rootKeysDir string = "root_keys/"
|
||||||
|
|
||||||
var caStore trustmanager.X509Store
|
|
||||||
var certificateStore trustmanager.X509Store
|
|
||||||
var privKeyStore trustmanager.EncryptedFileStore
|
|
||||||
|
|
||||||
var rawOutput bool
|
var rawOutput bool
|
||||||
|
var caStore trustmanager.X509Store
|
||||||
|
var certificateStore trustmanager.X509Store
|
||||||
|
var privKeyStore trustmanager.FileStore
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
logrus.SetLevel(logrus.DebugLevel)
|
logrus.SetLevel(logrus.DebugLevel)
|
||||||
|
@ -59,13 +56,11 @@ func init() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set up the defaults for our config
|
// Set up the defaults for our config
|
||||||
viper.SetDefault("trustDir", path.Join(homeDir, path.Dir(trustDir)))
|
viper.SetDefault("baseTrustDir", path.Join(homeDir, path.Dir(configPath)))
|
||||||
viper.SetDefault("privDir", path.Join(homeDir, path.Dir(privDir)))
|
|
||||||
viper.SetDefault("tufDir", path.Join(homeDir, path.Dir(tufDir)))
|
|
||||||
|
|
||||||
// Get the final value for the CA directory
|
// Get the final value for the CA directory
|
||||||
finalTrustDir := viper.GetString("trustDir")
|
finalTrustDir := path.Join(viper.GetString("baseTrustDir"), trustDir)
|
||||||
finalPrivDir := viper.GetString("privDir")
|
finalPrivDir := path.Join(viper.GetString("baseTrustDir"), privDir)
|
||||||
|
|
||||||
// Load all CAs that aren't expired and don't use SHA1
|
// Load all CAs that aren't expired and don't use SHA1
|
||||||
caStore, err = trustmanager.NewX509FilteredFileStore(finalTrustDir, func(cert *x509.Certificate) bool {
|
caStore, err = trustmanager.NewX509FilteredFileStore(finalTrustDir, func(cert *x509.Certificate) bool {
|
||||||
|
@ -76,10 +71,10 @@ func init() {
|
||||||
cert.SignatureAlgorithm != x509.ECDSAWithSHA1
|
cert.SignatureAlgorithm != x509.ECDSAWithSHA1
|
||||||
})
|
})
|
||||||
if err != nil {
|
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 {
|
certificateStore, err = trustmanager.NewX509FilteredFileStore(finalTrustDir, func(cert *x509.Certificate) bool {
|
||||||
return !cert.IsCA &&
|
return !cert.IsCA &&
|
||||||
time.Now().Before(cert.NotAfter) &&
|
time.Now().Before(cert.NotAfter) &&
|
||||||
|
@ -88,12 +83,12 @@ func init() {
|
||||||
cert.SignatureAlgorithm != x509.ECDSAWithSHA1
|
cert.SignatureAlgorithm != x509.ECDSAWithSHA1
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fatalf("could not create X509FileStore: %v", err)
|
fatalf("could not create Certificate X509FileStore: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
privKeyStore, err = trustmanager.NewKeyFileStore(finalPrivDir)
|
privKeyStore, err = trustmanager.NewKeyFileStore(finalPrivDir)
|
||||||
if err != nil {
|
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(cmdKeys)
|
||||||
NotaryCmd.AddCommand(cmdTufInit)
|
NotaryCmd.AddCommand(cmdTufInit)
|
||||||
NotaryCmd.AddCommand(cmdTufList)
|
NotaryCmd.AddCommand(cmdTufList)
|
||||||
cmdTufList.Flags().BoolVarP(&rawOutput, "raw", "", false, "Instructs notary list to output a non-pretty printed version of the targets list. Useful if you need to parse the list.")
|
cmdTufList.Flags().BoolVarP(&rawOutput, "raw", "", false, "Instructs notary list to output a nonpretty printed version of the targets list. Useful if you need to parse the list.")
|
||||||
NotaryCmd.AddCommand(cmdTufAdd)
|
NotaryCmd.AddCommand(cmdTufAdd)
|
||||||
NotaryCmd.AddCommand(cmdTufRemove)
|
NotaryCmd.AddCommand(cmdTufRemove)
|
||||||
NotaryCmd.AddCommand(cmdTufPublish)
|
NotaryCmd.AddCommand(cmdTufPublish)
|
||||||
cmdTufPublish.Flags().StringVarP(&remoteTrustServer, "remote", "r", "", "Remote trust server location")
|
cmdTufPublish.Flags().StringVarP(&remoteTrustServer, "remote", "r", "", "Remote trust server location")
|
||||||
NotaryCmd.AddCommand(cmdTufLookup)
|
NotaryCmd.AddCommand(cmdTufLookup)
|
||||||
cmdTufLookup.Flags().BoolVarP(&rawOutput, "raw", "", false, "Instructs notary lookup to output a non-pretty printed version of the targets list. Useful if you need to parse the list.")
|
cmdTufLookup.Flags().BoolVarP(&rawOutput, "raw", "", false, "Instructs notary lookup to output a nonpretty printed version of the targets list. Useful if you need to parse the list.")
|
||||||
cmdTufLookup.Flags().StringVarP(&remoteTrustServer, "remote", "r", "", "Remote trust server location")
|
cmdTufLookup.Flags().StringVarP(&remoteTrustServer, "remote", "r", "", "Remote trust server location")
|
||||||
NotaryCmd.AddCommand(cmdVerify)
|
NotaryCmd.AddCommand(cmdVerify)
|
||||||
|
|
||||||
|
|
|
@ -1,29 +1,21 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"crypto/x509"
|
"errors"
|
||||||
"encoding/json"
|
|
||||||
"encoding/pem"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
|
||||||
"path/filepath"
|
|
||||||
|
|
||||||
"github.com/Sirupsen/logrus"
|
"github.com/Sirupsen/logrus"
|
||||||
"github.com/docker/notary/trustmanager"
|
notaryclient "github.com/docker/notary/client"
|
||||||
"github.com/endophage/gotuf"
|
|
||||||
"github.com/endophage/gotuf/client"
|
|
||||||
"github.com/endophage/gotuf/data"
|
|
||||||
"github.com/endophage/gotuf/keys"
|
|
||||||
"github.com/endophage/gotuf/signed"
|
|
||||||
"github.com/endophage/gotuf/store"
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// FIXME: This should not be hardcoded
|
||||||
|
const hardcodedBaseURL = "https://notary:4443"
|
||||||
|
|
||||||
var remoteTrustServer string
|
var remoteTrustServer string
|
||||||
|
|
||||||
var cmdTufList = &cobra.Command{
|
var cmdTufList = &cobra.Command{
|
||||||
|
@ -84,30 +76,21 @@ func tufAdd(cmd *cobra.Command, args []string) {
|
||||||
gun := args[0]
|
gun := args[0]
|
||||||
targetName := args[1]
|
targetName := args[1]
|
||||||
targetPath := args[2]
|
targetPath := args[2]
|
||||||
kdb := keys.NewDB()
|
|
||||||
signer := signed.NewSigner(NewCryptoService(gun))
|
|
||||||
repo := tuf.NewTufRepo(kdb, signer)
|
|
||||||
|
|
||||||
b, err := ioutil.ReadFile(targetPath)
|
repo, err := notaryclient.NewNotaryRepository(viper.GetString("baseTrustDir"), gun, hardcodedBaseURL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fatalf(err.Error())
|
fatalf(err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
filestore := bootstrapRepo(gun, repo)
|
target, err := notaryclient.NewTarget(targetName, targetPath)
|
||||||
|
|
||||||
fmt.Println("Generating metadata for target")
|
|
||||||
meta, err := data.NewFileMeta(bytes.NewBuffer(b))
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fatalf(err.Error())
|
fatalf(err.Error())
|
||||||
}
|
}
|
||||||
|
err = repo.AddTarget(target)
|
||||||
fmt.Printf("Adding target \"%s\" with sha256 \"%s\" and size %d bytes.\n", targetName, meta.Hashes["sha256"], meta.Length)
|
|
||||||
_, err = repo.AddTargets("targets", data.Files{targetName: meta})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fatalf(err.Error())
|
fatalf(err.Error())
|
||||||
}
|
}
|
||||||
|
fmt.Println("Successfully added targets")
|
||||||
saveRepo(repo, filestore)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func tufInit(cmd *cobra.Command, args []string) {
|
func tufInit(cmd *cobra.Command, args []string) {
|
||||||
|
@ -117,91 +100,44 @@ func tufInit(cmd *cobra.Command, args []string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
gun := args[0]
|
gun := args[0]
|
||||||
kdb := keys.NewDB()
|
|
||||||
signer := signed.NewSigner(NewCryptoService(gun))
|
|
||||||
|
|
||||||
remote, err := getRemoteStore(gun)
|
nRepo, err := notaryclient.NewNotaryRepository(viper.GetString("baseTrustDir"), gun, hardcodedBaseURL)
|
||||||
rawTSKey, err := remote.GetKey("timestamp")
|
|
||||||
if err != nil {
|
|
||||||
fatalf(err.Error())
|
|
||||||
}
|
|
||||||
fmt.Println("RawKey: ", string(rawTSKey))
|
|
||||||
parsedKey := &data.TUFKey{}
|
|
||||||
err = json.Unmarshal(rawTSKey, parsedKey)
|
|
||||||
if err != nil {
|
|
||||||
fatalf(err.Error())
|
|
||||||
}
|
|
||||||
timestampKey := data.NewPublicKey(parsedKey.Cipher(), parsedKey.Public())
|
|
||||||
|
|
||||||
rootKey, err := signer.Create("root")
|
|
||||||
if err != nil {
|
|
||||||
fatalf(err.Error())
|
|
||||||
}
|
|
||||||
targetsKey, err := signer.Create("targets")
|
|
||||||
if err != nil {
|
|
||||||
fatalf(err.Error())
|
|
||||||
}
|
|
||||||
snapshotKey, err := signer.Create("snapshot")
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fatalf(err.Error())
|
fatalf(err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
kdb.AddKey(rootKey)
|
keysList := nRepo.ListRootKeys()
|
||||||
kdb.AddKey(targetsKey)
|
var passphrase string
|
||||||
kdb.AddKey(snapshotKey)
|
var rootKeyID string
|
||||||
kdb.AddKey(timestampKey)
|
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)
|
rootSigner, err := nRepo.GetRootSigner(rootKeyID, passphrase)
|
||||||
if err != nil {
|
|
||||||
fatalf(err.Error())
|
|
||||||
}
|
|
||||||
targetsRole, err := data.NewRole("targets", 1, []string{targetsKey.ID()}, nil, nil)
|
|
||||||
if err != nil {
|
|
||||||
fatalf(err.Error())
|
|
||||||
}
|
|
||||||
snapshotRole, err := data.NewRole("snapshot", 1, []string{snapshotKey.ID()}, nil, nil)
|
|
||||||
if err != nil {
|
|
||||||
fatalf(err.Error())
|
|
||||||
}
|
|
||||||
timestampRole, err := data.NewRole("timestamp", 1, []string{timestampKey.ID()}, nil, nil)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fatalf(err.Error())
|
fatalf(err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
err = kdb.AddRole(rootRole)
|
nRepo.Initialize(rootSigner)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fatalf(err.Error())
|
fatalf(err.Error())
|
||||||
}
|
}
|
||||||
err = kdb.AddRole(targetsRole)
|
|
||||||
if err != nil {
|
|
||||||
fatalf(err.Error())
|
|
||||||
}
|
|
||||||
err = kdb.AddRole(snapshotRole)
|
|
||||||
if err != nil {
|
|
||||||
fatalf(err.Error())
|
|
||||||
}
|
|
||||||
err = kdb.AddRole(timestampRole)
|
|
||||||
if err != nil {
|
|
||||||
fatalf(err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
repo := tuf.NewTufRepo(kdb, signer)
|
|
||||||
|
|
||||||
filestore, err := store.NewFilesystemStore(
|
|
||||||
path.Join(viper.GetString("tufDir"), gun), // TODO: base trust dir from config
|
|
||||||
"metadata",
|
|
||||||
"json",
|
|
||||||
"targets",
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
fatalf(err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
err = repo.InitRepo(false)
|
|
||||||
if err != nil {
|
|
||||||
fatalf(err.Error())
|
|
||||||
}
|
|
||||||
saveRepo(repo, filestore)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func tufList(cmd *cobra.Command, args []string) {
|
func tufList(cmd *cobra.Command, args []string) {
|
||||||
|
@ -210,31 +146,21 @@ func tufList(cmd *cobra.Command, args []string) {
|
||||||
fatalf("must specify a GUN")
|
fatalf("must specify a GUN")
|
||||||
}
|
}
|
||||||
gun := args[0]
|
gun := args[0]
|
||||||
kdb := keys.NewDB()
|
|
||||||
repo := tuf.NewTufRepo(kdb, nil)
|
|
||||||
|
|
||||||
remote, err := getRemoteStore(gun)
|
repo, err := notaryclient.NewNotaryRepository(viper.GetString("baseTrustDir"), gun, hardcodedBaseURL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
fatalf(err.Error())
|
||||||
}
|
|
||||||
c, err := bootstrapClient(gun, remote, repo, kdb)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
err = c.Update()
|
|
||||||
if err != nil {
|
|
||||||
logrus.Error("Error updating client: ", err.Error())
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if rawOutput {
|
// Retreive the remote list of signed targets
|
||||||
for name, meta := range repo.Targets["targets"].Signed.Targets {
|
targetList, err := repo.ListTargets()
|
||||||
fmt.Println(name, " ", meta.Hashes["sha256"], " ", meta.Length)
|
if err != nil {
|
||||||
}
|
fatalf(err.Error())
|
||||||
} else {
|
}
|
||||||
for name, meta := range repo.Targets["targets"].Signed.Targets {
|
|
||||||
fmt.Println(name, " ", meta.Hashes["sha256"], " ", meta.Length)
|
// 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]
|
gun := args[0]
|
||||||
targetName := args[1]
|
targetName := args[1]
|
||||||
kdb := keys.NewDB()
|
|
||||||
repo := tuf.NewTufRepo(kdb, nil)
|
|
||||||
|
|
||||||
remote, err := getRemoteStore(gun)
|
repo, err := notaryclient.NewNotaryRepository(viper.GetString("baseTrustDir"), gun, hardcodedBaseURL)
|
||||||
c, err := bootstrapClient(gun, remote, repo, kdb)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
fatalf(err.Error())
|
||||||
}
|
}
|
||||||
err = c.Update()
|
|
||||||
|
// TODO(diogo): Parse Targets and print them
|
||||||
|
target, err := repo.GetTargetByName(targetName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logrus.Error("Error updating client: ", err.Error())
|
fatalf(err.Error())
|
||||||
return
|
|
||||||
}
|
|
||||||
meta := c.TargetMeta(targetName)
|
|
||||||
if meta == nil {
|
|
||||||
logrus.Infof("Target %s not found in %s.", targetName, gun)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if rawOutput {
|
|
||||||
fmt.Println(targetName, fmt.Sprintf("sha256:%s", meta.Hashes["sha256"]), meta.Length)
|
|
||||||
} else {
|
|
||||||
fmt.Println(targetName, fmt.Sprintf("sha256:%s", meta.Hashes["sha256"]), meta.Length)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fmt.Println(target.Name, fmt.Sprintf("sha256:%s", target.Hashes["sha256"]), target.Length)
|
||||||
}
|
}
|
||||||
|
|
||||||
func tufPublish(cmd *cobra.Command, args []string) {
|
func tufPublish(cmd *cobra.Command, args []string) {
|
||||||
|
@ -277,41 +193,15 @@ func tufPublish(cmd *cobra.Command, args []string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
gun := args[0]
|
gun := args[0]
|
||||||
|
|
||||||
fmt.Println("Pushing changes to ", gun, ".")
|
fmt.Println("Pushing changes to ", gun, ".")
|
||||||
|
|
||||||
remote, err := getRemoteStore(gun)
|
repo, err := notaryclient.NewNotaryRepository(viper.GetString("baseTrustDir"), gun, hardcodedBaseURL)
|
||||||
filestore, err := store.NewFilesystemStore(
|
|
||||||
path.Join(viper.GetString("tufDir"), gun),
|
|
||||||
"metadata",
|
|
||||||
"json",
|
|
||||||
"targets",
|
|
||||||
)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fatalf(err.Error())
|
fatalf(err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
root, err := filestore.GetMeta("root", 0)
|
err = repo.Publish(passphraseRetriever)
|
||||||
if err != nil {
|
|
||||||
fatalf(err.Error())
|
|
||||||
}
|
|
||||||
targets, err := filestore.GetMeta("targets", 0)
|
|
||||||
if err != nil {
|
|
||||||
fatalf(err.Error())
|
|
||||||
}
|
|
||||||
snapshot, err := filestore.GetMeta("snapshot", 0)
|
|
||||||
if err != nil {
|
|
||||||
fatalf(err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
err = remote.SetMeta("root", root)
|
|
||||||
if err != nil {
|
|
||||||
fatalf(err.Error())
|
|
||||||
}
|
|
||||||
err = remote.SetMeta("targets", targets)
|
|
||||||
if err != nil {
|
|
||||||
fatalf(err.Error())
|
|
||||||
}
|
|
||||||
err = remote.SetMeta("snapshot", snapshot)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fatalf(err.Error())
|
fatalf(err.Error())
|
||||||
}
|
}
|
||||||
|
@ -324,20 +214,15 @@ func tufRemove(cmd *cobra.Command, args []string) {
|
||||||
}
|
}
|
||||||
gun := args[0]
|
gun := args[0]
|
||||||
targetName := args[1]
|
targetName := args[1]
|
||||||
kdb := keys.NewDB()
|
|
||||||
signer := signed.NewSigner(NewCryptoService(gun))
|
|
||||||
repo := tuf.NewTufRepo(kdb, signer)
|
|
||||||
|
|
||||||
|
//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)
|
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) {
|
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.
|
//TODO (diogo): This code is copy/pasted from lookup.
|
||||||
gun := args[0]
|
gun := args[0]
|
||||||
targetName := args[1]
|
targetName := args[1]
|
||||||
kdb := keys.NewDB()
|
repo, err := notaryclient.NewNotaryRepository(viper.GetString("baseTrustDir"), gun, hardcodedBaseURL)
|
||||||
repo := tuf.NewTufRepo(kdb, nil)
|
|
||||||
|
|
||||||
remote, err := getRemoteStore(gun)
|
|
||||||
|
|
||||||
c, err := bootstrapClient(gun, remote, repo, kdb)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logrus.Error("Unable to setup client.")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
err = c.Update()
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println("Update failed")
|
|
||||||
fatalf(err.Error())
|
fatalf(err.Error())
|
||||||
}
|
}
|
||||||
meta := c.TargetMeta(targetName)
|
|
||||||
if meta == nil {
|
// TODO(diogo): Parse Targets and print them
|
||||||
|
target, err := repo.GetTargetByName(targetName)
|
||||||
|
if err != nil {
|
||||||
logrus.Error("notary: data not present in the trusted collection.")
|
logrus.Error("notary: data not present in the trusted collection.")
|
||||||
os.Exit(1)
|
os.Exit(-11)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create hasher and hash data
|
// Create hasher and hash data
|
||||||
stdinHash := fmt.Sprintf("sha256:%x", sha256.Sum256(payload))
|
stdinHash := fmt.Sprintf("sha256:%x", sha256.Sum256(payload))
|
||||||
serverHash := fmt.Sprintf("sha256:%s", meta.Hashes["sha256"])
|
serverHash := fmt.Sprintf("sha256:%s", target.Hashes["sha256"])
|
||||||
if stdinHash != serverHash {
|
if stdinHash != serverHash {
|
||||||
logrus.Error("notary: data not present in the trusted collection.")
|
logrus.Error("notary: data not present in the trusted collection.")
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
|
@ -390,179 +265,16 @@ func verify(cmd *cobra.Command, args []string) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func saveRepo(repo *tuf.TufRepo, filestore store.MetadataStore) error {
|
func passphraseRetriever() (string, error) {
|
||||||
fmt.Println("Saving changes to Trusted Collection.")
|
fmt.Println("Please provide a passphrase for this root key: ")
|
||||||
signedRoot, err := repo.SignRoot(data.DefaultExpires("root"))
|
var passphrase string
|
||||||
|
_, err := fmt.Scanln(&passphrase)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return "", err
|
||||||
}
|
}
|
||||||
rootJSON, _ := json.Marshal(signedRoot)
|
if len(passphrase) < 8 {
|
||||||
filestore.SetMeta("root", rootJSON)
|
fmt.Println("Please use a password manager to generate and store a good random passphrase.")
|
||||||
|
return "", errors.New("Passphrase too short")
|
||||||
for r, _ := range repo.Targets {
|
|
||||||
signedTargets, err := repo.SignTargets(r, data.DefaultExpires("targets"))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
targetsJSON, _ := json.Marshal(signedTargets)
|
|
||||||
parentDir := filepath.Dir(r)
|
|
||||||
os.MkdirAll(parentDir, 0755)
|
|
||||||
filestore.SetMeta(r, targetsJSON)
|
|
||||||
}
|
}
|
||||||
|
return passphrase, nil
|
||||||
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",
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
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 {
|
func UpdateHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) *errors.HTTPError {
|
||||||
defer r.Body.Close()
|
defer r.Body.Close()
|
||||||
s := ctx.Value("metaStore")
|
s := ctx.Value("metaStore")
|
||||||
|
@ -102,6 +102,13 @@ func GetHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) *er
|
||||||
tufRole := vars["tufRole"]
|
tufRole := vars["tufRole"]
|
||||||
out, err := store.GetCurrent(gun, tufRole)
|
out, err := store.GetCurrent(gun, tufRole)
|
||||||
if err != nil {
|
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)
|
logrus.Errorf("[Notary Server] 500 GET repository: %s, role: %s", gun, tufRole)
|
||||||
return &errors.HTTPError{
|
return &errors.HTTPError{
|
||||||
HTTPStatus: http.StatusInternalServerError,
|
HTTPStatus: http.StatusInternalServerError,
|
||||||
|
@ -147,6 +154,7 @@ func DeleteHandler(ctx context.Context, w http.ResponseWriter, r *http.Request)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetTimestampHandler returns a timestamp.json given a GUN
|
||||||
func GetTimestampHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) *errors.HTTPError {
|
func GetTimestampHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) *errors.HTTPError {
|
||||||
s := ctx.Value("metaStore")
|
s := ctx.Value("metaStore")
|
||||||
store, ok := s.(storage.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)
|
out, err := timestamp.GetOrCreateTimestamp(gun, store, signer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if _, ok := err.(*storage.ErrNoKey); ok {
|
||||||
|
return &errors.HTTPError{
|
||||||
|
HTTPStatus: http.StatusNotFound,
|
||||||
|
Code: 9999,
|
||||||
|
Err: err,
|
||||||
|
}
|
||||||
|
}
|
||||||
return &errors.HTTPError{
|
return &errors.HTTPError{
|
||||||
HTTPStatus: http.StatusInternalServerError,
|
HTTPStatus: http.StatusInternalServerError,
|
||||||
Code: 9999,
|
Code: 9999,
|
||||||
|
@ -184,6 +199,8 @@ func GetTimestampHandler(ctx context.Context, w http.ResponseWriter, r *http.Req
|
||||||
return nil
|
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 {
|
func GetTimestampKeyHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) *errors.HTTPError {
|
||||||
s := ctx.Value("metaStore")
|
s := ctx.Value("metaStore")
|
||||||
store, ok := s.(storage.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)
|
lsnr = tls.NewListener(lsnr, tlsConfig)
|
||||||
}
|
}
|
||||||
|
|
||||||
var ac auth.AccessController = nil
|
var ac auth.AccessController
|
||||||
hand := utils.RootHandlerFactory(ac, ctx, trust)
|
hand := utils.RootHandlerFactory(ac, ctx, trust)
|
||||||
|
|
||||||
r := mux.NewRouter()
|
r := mux.NewRouter()
|
||||||
|
|
|
@ -27,13 +27,14 @@ type MySQLStorage struct {
|
||||||
sql.DB
|
sql.DB
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewMySQLStorage is a convenience method to create a MySQLStorage
|
||||||
func NewMySQLStorage(db *sql.DB) *MySQLStorage {
|
func NewMySQLStorage(db *sql.DB) *MySQLStorage {
|
||||||
return &MySQLStorage{
|
return &MySQLStorage{
|
||||||
DB: *db,
|
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
|
// 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 {
|
func (db *MySQLStorage) UpdateCurrent(gun, role string, version int, data []byte) error {
|
||||||
checkStmt := "SELECT count(*) FROM `tuf_files` WHERE `gun`=? AND `role`=? AND `version`>=?;"
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get a specific TUF record
|
// GetCurrent gets a specific TUF record
|
||||||
func (db *MySQLStorage) GetCurrent(gun, tufRole string) (data []byte, err error) {
|
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;"
|
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()
|
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
|
return data, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Delete deletes all the records for a specific GUN
|
||||||
func (db *MySQLStorage) Delete(gun string) error {
|
func (db *MySQLStorage) Delete(gun string) error {
|
||||||
stmt := "DELETE FROM `tuf_files` WHERE `gun`=?;"
|
stmt := "DELETE FROM `tuf_files` WHERE `gun`=?;"
|
||||||
_, err := db.Exec(stmt, gun)
|
_, err := db.Exec(stmt, gun)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetTimestampKey returns the timestamps Public Key data
|
||||||
func (db *MySQLStorage) GetTimestampKey(gun string) (cipher string, public []byte, err error) {
|
func (db *MySQLStorage) GetTimestampKey(gun string) (cipher string, public []byte, err error) {
|
||||||
stmt := "SELECT `cipher`, `public` FROM `timestamp_keys` WHERE `gun`=?;"
|
stmt := "SELECT `cipher`, `public` FROM `timestamp_keys` WHERE `gun`=?;"
|
||||||
row := db.QueryRow(stmt, gun)
|
row := db.QueryRow(stmt, gun)
|
||||||
|
@ -100,6 +104,8 @@ func (db *MySQLStorage) GetTimestampKey(gun string) (cipher string, public []byt
|
||||||
|
|
||||||
return cipher, public, err
|
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 {
|
func (db *MySQLStorage) SetTimestampKey(gun, cipher string, public []byte) error {
|
||||||
stmt := "INSERT INTO `timestamp_keys` (`gun`, `cipher`, `public`) VALUES (?,?,?);"
|
stmt := "INSERT INTO `timestamp_keys` (`gun`, `cipher`, `public`) VALUES (?,?,?);"
|
||||||
_, err := db.Exec(stmt, gun, cipher, public)
|
_, err := db.Exec(stmt, gun, cipher, public)
|
||||||
|
|
|
@ -4,30 +4,38 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// ErrOldVersion is returned when a newer version of TUF metadada is already available
|
||||||
type ErrOldVersion struct{}
|
type ErrOldVersion struct{}
|
||||||
|
|
||||||
|
// ErrOldVersion is returned when a newer version of TUF metadada is already available
|
||||||
func (err ErrOldVersion) Error() string {
|
func (err ErrOldVersion) Error() string {
|
||||||
return fmt.Sprintf("Error updating metadata. A newer version is already available")
|
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{}
|
type ErrNotFound struct{}
|
||||||
|
|
||||||
|
// Error implements error
|
||||||
func (err ErrNotFound) Error() string {
|
func (err ErrNotFound) Error() string {
|
||||||
return fmt.Sprintf("No record found")
|
return fmt.Sprintf("No record found")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ErrTimestampKeyExists is returned when a timestamp key already exists
|
||||||
type ErrTimestampKeyExists struct {
|
type ErrTimestampKeyExists struct {
|
||||||
gun string
|
gun string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ErrTimestampKeyExists is returned when a timestamp key already exists
|
||||||
func (err ErrTimestampKeyExists) Error() string {
|
func (err ErrTimestampKeyExists) Error() string {
|
||||||
return fmt.Sprintf("Error, timestamp key already exists for %s", err.gun)
|
return fmt.Sprintf("Error, timestamp key already exists for %s", err.gun)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ErrNoKey is returned when no timestamp key is found
|
||||||
type ErrNoKey struct {
|
type ErrNoKey struct {
|
||||||
gun string
|
gun string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ErrNoKey is returned when no timestamp key is found
|
||||||
func (err ErrNoKey) Error() string {
|
func (err ErrNoKey) Error() string {
|
||||||
return fmt.Sprintf("Error, no timestamp key found for %s", err.gun)
|
return fmt.Sprintf("Error, no timestamp key found for %s", err.gun)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package storage
|
package storage
|
||||||
|
|
||||||
|
// MetaStore holds the methods that are used for a Metadata Store
|
||||||
type MetaStore interface {
|
type MetaStore interface {
|
||||||
UpdateCurrent(gun, role string, version int, data []byte) error
|
UpdateCurrent(gun, role string, version int, data []byte) error
|
||||||
GetCurrent(gun, tufRole string) (data []byte, err error)
|
GetCurrent(gun, tufRole string) (data []byte, err error)
|
||||||
|
|
|
@ -16,23 +16,24 @@ type ver struct {
|
||||||
data []byte
|
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
|
// inefficient in many scenarios
|
||||||
type memStorage struct {
|
type MemStorage struct {
|
||||||
lock sync.Mutex
|
lock sync.Mutex
|
||||||
tufMeta map[string][]*ver
|
tufMeta map[string][]*ver
|
||||||
tsKeys map[string]*key
|
tsKeys map[string]*key
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewMemStorage instantiates a memStorage instance
|
// NewMemStorage instantiates a memStorage instance
|
||||||
func NewMemStorage() *memStorage {
|
func NewMemStorage() *MemStorage {
|
||||||
return &memStorage{
|
return &MemStorage{
|
||||||
tufMeta: make(map[string][]*ver),
|
tufMeta: make(map[string][]*ver),
|
||||||
tsKeys: make(map[string]*key),
|
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)
|
id := entryKey(gun, role)
|
||||||
st.lock.Lock()
|
st.lock.Lock()
|
||||||
defer st.lock.Unlock()
|
defer st.lock.Unlock()
|
||||||
|
@ -47,7 +48,8 @@ func (st *memStorage) UpdateCurrent(gun, role string, version int, data []byte)
|
||||||
return nil
|
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)
|
id := entryKey(gun, role)
|
||||||
st.lock.Lock()
|
st.lock.Lock()
|
||||||
defer st.lock.Unlock()
|
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
|
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()
|
st.lock.Lock()
|
||||||
defer st.lock.Unlock()
|
defer st.lock.Unlock()
|
||||||
for k, _ := range st.tufMeta {
|
for k := range st.tufMeta {
|
||||||
if strings.HasPrefix(k, gun) {
|
if strings.HasPrefix(k, gun) {
|
||||||
delete(st.tufMeta, k)
|
delete(st.tufMeta, k)
|
||||||
}
|
}
|
||||||
|
@ -69,17 +72,20 @@ func (st *memStorage) Delete(gun string) error {
|
||||||
return nil
|
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
|
// no need for lock. It's ok to return nil if an update
|
||||||
// wasn't observed
|
// wasn't observed
|
||||||
if k, ok := st.tsKeys[gun]; !ok {
|
k, ok := st.tsKeys[gun]
|
||||||
|
if !ok {
|
||||||
return "", nil, &ErrNoKey{gun: gun}
|
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}
|
k := &key{cipher: cipher, public: public}
|
||||||
st.lock.Lock()
|
st.lock.Lock()
|
||||||
defer st.lock.Unlock()
|
defer st.lock.Unlock()
|
||||||
|
|
|
@ -17,6 +17,7 @@ type RufusSigner struct {
|
||||||
sClient pb.SignerClient
|
sClient pb.SignerClient
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewRufusSigner is a convinience method that returns RufusSigner
|
||||||
func NewRufusSigner(hostname string, port string, tlscafile string) *RufusSigner {
|
func NewRufusSigner(hostname string, port string, tlscafile string) *RufusSigner {
|
||||||
var opts []grpc.DialOption
|
var opts []grpc.DialOption
|
||||||
netAddr := net.JoinHostPort(hostname, port)
|
netAddr := net.JoinHostPort(hostname, port)
|
||||||
|
@ -53,7 +54,7 @@ func (trust *RufusSigner) Sign(keyIDs []string, toSign []byte) ([]data.Signature
|
||||||
}
|
}
|
||||||
signatures = append(signatures, data.Signature{
|
signatures = append(signatures, data.Signature{
|
||||||
KeyID: sig.KeyID.ID,
|
KeyID: sig.KeyID.ID,
|
||||||
Method: sig.Algorithm,
|
Method: sig.Algorithm.Algorithm,
|
||||||
Signature: sig.Content,
|
Signature: sig.Content,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -67,7 +68,7 @@ func (trust *RufusSigner) Create(role string) (*data.PublicKey, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
//TODO(mccauley): Update API to return algorithm and/or take it as a param
|
//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
|
return public, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -81,7 +82,7 @@ func (trust *RufusSigner) PublicKeys(keyIDs ...string) (map[string]*data.PublicK
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
publicKeys[public.KeyID.ID] =
|
publicKeys[public.KeyID.ID] =
|
||||||
data.NewPublicKey(public.Algorithm, public.PublicKey)
|
data.NewPublicKey(public.Algorithm.Algorithm, public.PublicKey)
|
||||||
}
|
}
|
||||||
return publicKeys, nil
|
return publicKeys, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,14 +17,9 @@ type FileStore interface {
|
||||||
RemoveDir(directoryName string) error
|
RemoveDir(directoryName string) error
|
||||||
Get(fileName string) ([]byte, error)
|
Get(fileName string) ([]byte, error)
|
||||||
GetPath(fileName string) string
|
GetPath(fileName string) string
|
||||||
ListAll() []string
|
ListFiles(symlinks bool) []string
|
||||||
ListDir(directoryName string) []string
|
ListDir(directoryName string, symlinks bool) []string
|
||||||
}
|
Link(src, dst string) error
|
||||||
|
|
||||||
type EncryptedFileStore interface {
|
|
||||||
FileStore
|
|
||||||
AddEncrypted(fileName string, keyBytes []byte, passphrase string) error
|
|
||||||
GetDecrypted(fileName string, passphrase string) ([]byte, error)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// SimpleFileStore implements FileStore
|
// SimpleFileStore implements FileStore
|
||||||
|
@ -34,8 +29,8 @@ type SimpleFileStore struct {
|
||||||
perms os.FileMode
|
perms os.FileMode
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewFileStore creates a directory with 755 permissions
|
// NewSimpleFileStore creates a directory with 755 permissions
|
||||||
func NewFileStore(baseDir string, fileExt string) (FileStore, error) {
|
func NewSimpleFileStore(baseDir string, fileExt string) (FileStore, error) {
|
||||||
if err := CreateDirectory(baseDir); err != nil {
|
if err := CreateDirectory(baseDir); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -47,8 +42,8 @@ func NewFileStore(baseDir string, fileExt string) (FileStore, error) {
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewPrivateFileStore creates a directory with 700 permissions
|
// NewPrivateSimpleFileStore creates a directory with 700 permissions
|
||||||
func NewPrivateFileStore(baseDir string, fileExt string) (FileStore, error) {
|
func NewPrivateSimpleFileStore(baseDir string, fileExt string) (FileStore, error) {
|
||||||
if err := CreatePrivateDirectory(baseDir); err != nil {
|
if err := CreatePrivateDirectory(baseDir); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -108,19 +103,19 @@ func (f *SimpleFileStore) GetPath(name string) string {
|
||||||
return f.genFilePath(name)
|
return f.genFilePath(name)
|
||||||
}
|
}
|
||||||
|
|
||||||
// List lists all the files inside of a store
|
// ListFiles lists all the files inside of a store
|
||||||
func (f *SimpleFileStore) ListAll() []string {
|
func (f *SimpleFileStore) ListFiles(symlinks bool) []string {
|
||||||
return f.list(f.baseDir)
|
return f.list(f.baseDir, symlinks)
|
||||||
}
|
}
|
||||||
|
|
||||||
// List lists all the files inside of a directory identified by a name
|
// ListDir lists all the files inside of a directory identified by a name
|
||||||
func (f *SimpleFileStore) ListDir(name string) []string {
|
func (f *SimpleFileStore) ListDir(name string, symlinks bool) []string {
|
||||||
fullPath := filepath.Join(f.baseDir, name)
|
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
|
// list lists all the files in a directory given a full path. Ignores symlinks.
|
||||||
func (f *SimpleFileStore) list(path string) []string {
|
func (f *SimpleFileStore) list(path string, symlinks bool) []string {
|
||||||
files := make([]string, 0, 0)
|
files := make([]string, 0, 0)
|
||||||
filepath.Walk(path, func(fp string, fi os.FileInfo, err error) error {
|
filepath.Walk(path, func(fp string, fi os.FileInfo, err error) error {
|
||||||
// If there are errors, ignore this particular file
|
// If there are errors, ignore this particular file
|
||||||
|
@ -131,6 +126,12 @@ func (f *SimpleFileStore) list(path string) []string {
|
||||||
if fi.IsDir() {
|
if fi.IsDir() {
|
||||||
return nil
|
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)
|
// Only allow matches that end with our certificate extension (e.g. *.crt)
|
||||||
matched, _ := filepath.Match("*"+f.fileExt, fi.Name())
|
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
|
// genFilePath returns the full path with extension given a file name
|
||||||
func (f *SimpleFileStore) genFilePath(name string) string {
|
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)
|
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
|
// CreateDirectory uses createDirectory to create a chmod 755 Directory
|
||||||
func CreateDirectory(dir string) error {
|
func CreateDirectory(dir string) error {
|
||||||
return createDirectory(dir, visible)
|
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"
|
testName := "docker.com/notary/certificate"
|
||||||
testExt := "crt"
|
testExt := "crt"
|
||||||
perms := os.FileMode(0755)
|
perms := os.FileMode(0755)
|
||||||
|
@ -144,11 +144,18 @@ func TestListAll(t *testing.T) {
|
||||||
// Create 10 randomfiles
|
// Create 10 randomfiles
|
||||||
for i := 1; i <= 10; i++ {
|
for i := 1; i <= 10; i++ {
|
||||||
// Since we're generating this manually we need to add the extension '.'
|
// 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)
|
_, err = generateRandomFile(expectedFilePath, perms)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to generate random file: %v", err)
|
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
|
// Create our SimpleFileStore
|
||||||
|
@ -158,11 +165,17 @@ func TestListAll(t *testing.T) {
|
||||||
perms: perms,
|
perms: perms,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Call the List function
|
// Call the List function. Expect 10 real files when not listing symlinks
|
||||||
files := store.ListAll()
|
files := store.ListFiles(false)
|
||||||
if len(files) != 10 {
|
if len(files) != 10 {
|
||||||
t.Fatalf("expected 10 files in listing, got: %d", len(files))
|
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) {
|
func TestListDir(t *testing.T) {
|
||||||
|
@ -196,19 +209,64 @@ func TestListDir(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Call the ListDir function
|
// Call the ListDir function
|
||||||
files := store.ListDir("docker.com/")
|
files := store.ListDir("docker.com/", true)
|
||||||
if len(files) != 10 {
|
if len(files) != 10 {
|
||||||
t.Fatalf("expected 10 files in listing, got: %d", len(files))
|
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 {
|
if len(files) != 10 {
|
||||||
t.Fatalf("expected 10 files in listing, got: %d", len(files))
|
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 {
|
if len(files) != 0 {
|
||||||
t.Fatalf("expected 0 files in listing, got: %d", len(files))
|
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) {
|
func TestGetPath(t *testing.T) {
|
||||||
testExt := "crt"
|
testExt := "crt"
|
||||||
perms := os.FileMode(0755)
|
perms := os.FileMode(0755)
|
||||||
|
|
|
@ -1,6 +1,11 @@
|
||||||
package trustmanager
|
package trustmanager
|
||||||
|
|
||||||
import "errors"
|
import (
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/endophage/gotuf/data"
|
||||||
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
keyExtension = "key"
|
keyExtension = "key"
|
||||||
|
@ -14,7 +19,7 @@ type KeyFileStore struct {
|
||||||
// NewKeyFileStore returns a new KeyFileStore creating a private directory to
|
// NewKeyFileStore returns a new KeyFileStore creating a private directory to
|
||||||
// hold the keys.
|
// hold the keys.
|
||||||
func NewKeyFileStore(baseDir string) (*KeyFileStore, error) {
|
func NewKeyFileStore(baseDir string) (*KeyFileStore, error) {
|
||||||
fileStore, err := NewFileStore(baseDir, keyExtension)
|
fileStore, err := NewPrivateSimpleFileStore(baseDir, keyExtension)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -22,35 +27,67 @@ func NewKeyFileStore(baseDir string) (*KeyFileStore, error) {
|
||||||
return &KeyFileStore{fileStore}, nil
|
return &KeyFileStore{fileStore}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddEncrypted stores the contents of a PEM-encoded private key as an encrypted PEM block
|
// AddKey stores the contents of a PEM-encoded private key as a PEM block
|
||||||
func (s *KeyFileStore) AddEncrypted(fileName string, pemKey []byte, passphrase string) error {
|
func (s *KeyFileStore) AddKey(name string, privKey *data.PrivateKey) error {
|
||||||
|
pemPrivKey, err := KeyToPEM(privKey)
|
||||||
privKey, err := ParsePEMPrivateKey(pemKey)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
encryptedKey, err := EncryptPrivateKey(privKey, passphrase)
|
return s.Add(name, pemPrivKey)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return s.Add(fileName, encryptedKey)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetDecrypted decrypts and returns the PEM Encoded private key given a flename
|
// GetKey returns the PrivateKey given a KeyID
|
||||||
// and a passphrase
|
func (s *KeyFileStore) GetKey(name string) (*data.PrivateKey, error) {
|
||||||
func (s *KeyFileStore) GetDecrypted(fileName string, passphrase string) ([]byte, error) {
|
keyBytes, err := s.Get(name)
|
||||||
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)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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 (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"crypto/rsa"
|
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
@ -30,19 +29,13 @@ func TestAddKey(t *testing.T) {
|
||||||
t.Fatalf("failed to create new key filestore: %v", err)
|
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 {
|
if err != nil {
|
||||||
t.Fatalf("could not generate private key: %v", err)
|
t.Fatalf("could not generate private key: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the PEM for the key
|
// Call the AddKey function
|
||||||
pemKey, err := KeyToPEM(key)
|
err = store.AddKey(testName, privKey)
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("failed to convert private key to PEM: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Call the Add function
|
|
||||||
err = store.Add(testName, pemKey)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to add file to store: %v", err)
|
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)
|
t.Fatalf("failed to create new key filestore: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Call the Get function
|
// Call the GetKey function
|
||||||
pemKey, err := store.Get(testName)
|
privKey, err := store.GetKey(testName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to get file from store: %v", err)
|
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)
|
t.Fatalf("unexpected content in the file: %s", filePath)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAddEncryptedAndGetDecrypted(t *testing.T) {
|
func TestAddEncryptedAndGetDecrypted(t *testing.T) {
|
||||||
testName := "docker.com/notary/root"
|
|
||||||
testExt := "key"
|
testExt := "key"
|
||||||
|
|
||||||
// Temporary directory where test files will be created
|
// 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)
|
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
|
// Create our FileStore
|
||||||
store, err := NewKeyFileStore(tempBaseDir)
|
store, err := NewKeyFileStore(tempBaseDir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -142,35 +136,38 @@ func TestAddEncryptedAndGetDecrypted(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate new PrivateKey
|
// Generate new PrivateKey
|
||||||
key, err := rsa.GenerateKey(rand.Reader, 1024)
|
privKey, err := GenerateRSAKey(rand.Reader, 512)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("could not generate private key: %v", err)
|
t.Fatalf("could not generate private key: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get PEM encodedd key
|
// Call the AddEncryptedKey function
|
||||||
pemKey, err := KeyToPEM(key)
|
err = store.AddEncryptedKey(privKey.ID(), privKey, "diogomonica")
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Could not encode key to PEM: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Call the Add function
|
|
||||||
err = store.AddEncrypted(testName, pemKey, "diogomonica")
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to add file to store: %v", err)
|
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 {
|
if err != nil {
|
||||||
t.Fatalf("could not decrypt private key: %v", err)
|
t.Fatalf("could not decrypt private key: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !strings.Contains(string(pemKey), string(pemPrivKey)) {
|
if !bytes.Equal(privKey.Private(), readPrivKey.Private()) {
|
||||||
t.Fatalf("expected private key content in the file: %s", expectedFilePath)
|
t.Fatalf("written key and loaded key do not match")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGetDecryptedWithTamperedCipherText(t *testing.T) {
|
func TestGetDecryptedWithTamperedCipherText(t *testing.T) {
|
||||||
testName := "docker.com/notary/root"
|
|
||||||
testExt := "key"
|
testExt := "key"
|
||||||
|
|
||||||
// Temporary directory where test files will be created
|
// 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)
|
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
|
// Create our FileStore
|
||||||
store, err := NewKeyFileStore(tempBaseDir)
|
store, err := NewKeyFileStore(tempBaseDir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -189,24 +183,22 @@ func TestGetDecryptedWithTamperedCipherText(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate a new Private Key
|
// Generate a new Private Key
|
||||||
key, err := rsa.GenerateKey(rand.Reader, 1024)
|
privKey, err := GenerateRSAKey(rand.Reader, 512)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("could not generate private key: %v", err)
|
t.Fatalf("could not generate private key: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get PEM encodedd key
|
// Call the AddEncryptedKey function
|
||||||
pemKey, err := KeyToPEM(key)
|
err = store.AddEncryptedKey(privKey.ID(), privKey, "diogomonica")
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Could not encode key to PEM: %v", err)
|
|
||||||
}
|
|
||||||
// Call the Add function
|
|
||||||
err = store.AddEncrypted(testName, pemKey, "diogomonica")
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to add file to store: %v", err)
|
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
|
// 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 {
|
if err != nil {
|
||||||
t.Fatalf("expected file not found: %v", err)
|
t.Fatalf("expected file not found: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -215,7 +207,7 @@ func TestGetDecryptedWithTamperedCipherText(t *testing.T) {
|
||||||
fp.WriteAt([]byte("a"), int64(1))
|
fp.WriteAt([]byte("a"), int64(1))
|
||||||
|
|
||||||
// Try to decrypt the file
|
// Try to decrypt the file
|
||||||
_, err = store.GetDecrypted(testName, "diogomonica")
|
_, err = store.GetDecryptedKey(privKey.ID(), "diogomonica")
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Fatalf("expected error while decrypting the content due to invalid cipher text")
|
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
|
// Generate a new random RSA Key
|
||||||
key, err := rsa.GenerateKey(rand.Reader, 1024)
|
privKey, err := GenerateRSAKey(rand.Reader, 512)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("could not generate private key: %v", err)
|
t.Fatalf("could not generate private key: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get PEM encodedd key
|
// Call the AddEncryptedKey function
|
||||||
pemKey, err := KeyToPEM(key)
|
err = store.AddEncryptedKey(privKey.ID(), privKey, "diogomonica")
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Could not encode key to PEM: %v", err)
|
|
||||||
}
|
|
||||||
// Call the Add function
|
|
||||||
err = store.AddEncrypted(testName, pemKey, "diogomonica")
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to add file to stoAFre: %v", err)
|
t.Fatalf("failed to add file to stoAFre: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try to decrypt the file with an invalid passphrase
|
// Try to decrypt the file with an invalid passphrase
|
||||||
_, err = store.GetDecrypted(testName, "diegomonica")
|
_, err = store.GetDecryptedKey(testName, "diegomonica")
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Fatalf("expected error while decrypting the content due to invalid passphrase")
|
t.Fatalf("expected error while decrypting the content due to invalid passphrase")
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,8 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
|
|
||||||
|
"github.com/Sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
// X509FileStore implements X509Store that persists on disk
|
// 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) {
|
func newX509FileStore(directory string, validate func(*x509.Certificate) bool) (*X509FileStore, error) {
|
||||||
fileStore, err := NewFileStore(directory, certExtension)
|
fileStore, err := NewSimpleFileStore(directory, certExtension)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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.
|
// stored under. If the file does not exist on disk, saves it.
|
||||||
func (s X509FileStore) addNamedCert(cert *x509.Certificate) error {
|
func (s X509FileStore) addNamedCert(cert *x509.Certificate) error {
|
||||||
fingerprint := fingerprintCert(cert)
|
fingerprint := fingerprintCert(cert)
|
||||||
|
logrus.Debug("Adding cert with fingerprint: ", fingerprint)
|
||||||
// Validate if we already loaded this certificate before
|
// Validate if we already loaded this certificate before
|
||||||
if _, ok := s.fingerprintMap[fingerprint]; ok {
|
if _, ok := s.fingerprintMap[fingerprint]; ok {
|
||||||
return errors.New("certificate already in the store")
|
return errors.New("certificate already in the store")
|
||||||
|
@ -81,10 +83,12 @@ func (s X509FileStore) addNamedCert(cert *x509.Certificate) error {
|
||||||
fileName := fileName(cert)
|
fileName := fileName(cert)
|
||||||
|
|
||||||
// Save the file to disk if not already there.
|
// 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 {
|
if err := s.fileStore.Add(fileName, certBytes); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
} else if err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// We wrote the certificate succcessfully, add it to our in-memory storage
|
// 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
|
// AddCertFromPEM adds the first certificate that it finds in the byte[], returning
|
||||||
// an error if no Certificates are found
|
// an error if no Certificates are found
|
||||||
func (s X509FileStore) AddCertFromPEM(pemBytes []byte) error {
|
func (s X509FileStore) AddCertFromPEM(pemBytes []byte) error {
|
||||||
cert, err := loadCertFromPEM(pemBytes)
|
cert, err := LoadCertFromPEM(pemBytes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,16 +1,19 @@
|
||||||
package trustmanager
|
package trustmanager
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto"
|
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"crypto/rsa"
|
"crypto/rsa"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
|
"crypto/x509/pkix"
|
||||||
"encoding/pem"
|
"encoding/pem"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"math/big"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/endophage/gotuf/data"
|
"github.com/endophage/gotuf/data"
|
||||||
)
|
)
|
||||||
|
@ -41,7 +44,7 @@ func GetCertFromURL(urlStr string) (*x509.Certificate, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try to extract the first valid PEM certificate from the bytes
|
// Try to extract the first valid PEM certificate from the bytes
|
||||||
cert, err := loadCertFromPEM(certBytes)
|
cert, err := LoadCertFromPEM(certBytes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -56,34 +59,30 @@ func CertToPEM(cert *x509.Certificate) []byte {
|
||||||
return pemCert
|
return pemCert
|
||||||
}
|
}
|
||||||
|
|
||||||
// KeyToPEM returns a PEM encoded key from a crypto.PrivateKey
|
// KeyToPEM returns a PEM encoded key from a Private Key
|
||||||
func KeyToPEM(key crypto.PrivateKey) ([]byte, error) {
|
func KeyToPEM(privKey *data.PrivateKey) ([]byte, error) {
|
||||||
rsaKey, ok := key.(*rsa.PrivateKey)
|
if privKey.Cipher() != "RSA" {
|
||||||
if !ok {
|
|
||||||
return nil, errors.New("only RSA keys are currently supported")
|
return nil, errors.New("only RSA keys are currently supported")
|
||||||
}
|
}
|
||||||
|
|
||||||
keyBytes := x509.MarshalPKCS1PrivateKey(rsaKey)
|
return pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: privKey.Private()}), nil
|
||||||
return pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: keyBytes}), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// EncryptPrivateKey returns an encrypted PEM encoded key given a Private key
|
// EncryptPrivateKey returns an encrypted PEM key given a Privatekey
|
||||||
// and a passphrase
|
// and a passphrase
|
||||||
func EncryptPrivateKey(key crypto.PrivateKey, passphrase string) ([]byte, error) {
|
func EncryptPrivateKey(key *data.PrivateKey, passphrase string) ([]byte, error) {
|
||||||
rsaKey, ok := key.(*rsa.PrivateKey)
|
// TODO(diogo): Currently only supports RSA Private keys
|
||||||
if !ok {
|
if key.Cipher() != "RSA" {
|
||||||
return nil, errors.New("only RSA keys are currently supported")
|
return nil, errors.New("only RSA keys are currently supported")
|
||||||
}
|
}
|
||||||
|
|
||||||
keyBytes := x509.MarshalPKCS1PrivateKey(rsaKey)
|
|
||||||
|
|
||||||
password := []byte(passphrase)
|
password := []byte(passphrase)
|
||||||
cipherType := x509.PEMCipherAES256
|
cipherType := x509.PEMCipherAES256
|
||||||
blockType := "RSA PRIVATE KEY"
|
blockType := "RSA PRIVATE KEY"
|
||||||
|
|
||||||
encryptedPEMBlock, err := x509.EncryptPEMBlock(rand.Reader,
|
encryptedPEMBlock, err := x509.EncryptPEMBlock(rand.Reader,
|
||||||
blockType,
|
blockType,
|
||||||
keyBytes,
|
key.Private(),
|
||||||
password,
|
password,
|
||||||
cipherType)
|
cipherType)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -93,9 +92,9 @@ func EncryptPrivateKey(key crypto.PrivateKey, passphrase string) ([]byte, error)
|
||||||
return pem.EncodeToMemory(encryptedPEMBlock), nil
|
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.
|
// 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 {
|
for len(pemBytes) > 0 {
|
||||||
var block *pem.Block
|
var block *pem.Block
|
||||||
block, pemBytes = pem.Decode(pemBytes)
|
block, pemBytes = pem.Decode(pemBytes)
|
||||||
|
@ -134,7 +133,7 @@ func fingerprintCert(cert *x509.Certificate) CertID {
|
||||||
|
|
||||||
// loadCertsFromDir receives a store AddCertFromFile for each certificate found
|
// loadCertsFromDir receives a store AddCertFromFile for each certificate found
|
||||||
func loadCertsFromDir(s *X509FileStore) {
|
func loadCertsFromDir(s *X509FileStore) {
|
||||||
certFiles := s.fileStore.ListAll()
|
certFiles := s.fileStore.ListFiles(true)
|
||||||
for _, c := range certFiles {
|
for _, c := range certFiles {
|
||||||
s.AddCertFromFile(c)
|
s.AddCertFromFile(c)
|
||||||
}
|
}
|
||||||
|
@ -161,39 +160,9 @@ func LoadCertFromFile(filename string) (*x509.Certificate, error) {
|
||||||
return nil, errors.New("could not load certificate from file")
|
return nil, errors.New("could not load certificate from file")
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoadKeyFromFile returns a PrivateKey given a filename
|
// ParsePEMPrivateKey returns a data.PrivateKey from a PEM encoded private key. It
|
||||||
func LoadKeyFromFile(filename string) (crypto.PrivateKey, error) {
|
// only supports RSA (PKCS#1) and attempts to decrypt using the passphrase, if encrypted.
|
||||||
pemBytes, err := ioutil.ReadFile(filename)
|
func ParsePEMPrivateKey(pemBytes []byte, passphrase string) (*data.PrivateKey, error) {
|
||||||
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) {
|
|
||||||
block, _ := pem.Decode(pemBytes)
|
block, _ := pem.Decode(pemBytes)
|
||||||
if block == nil {
|
if block == nil {
|
||||||
return nil, errors.New("no valid private key found")
|
return nil, errors.New("no valid private key found")
|
||||||
|
@ -201,17 +170,82 @@ func ParsePEMEncryptedPrivateKey(pemBytes []byte, passphrase string) (crypto.Pri
|
||||||
|
|
||||||
switch block.Type {
|
switch block.Type {
|
||||||
case "RSA PRIVATE KEY":
|
case "RSA PRIVATE KEY":
|
||||||
if !x509.IsEncryptedPEMBlock(block) {
|
var privKeyBytes []byte
|
||||||
return nil, errors.New("private key is not encrypted")
|
var err error
|
||||||
|
|
||||||
|
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
|
||||||
}
|
}
|
||||||
|
|
||||||
decryptedPEMBlock, err := x509.DecryptPEMBlock(block, []byte(passphrase))
|
rsaPrivKey, err := x509.ParsePKCS1PrivateKey(privKeyBytes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.New("could not decrypt private key")
|
return nil, fmt.Errorf("could not parse DER encoded key: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return x509.ParsePKCS1PrivateKey(decryptedPEMBlock)
|
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:
|
default:
|
||||||
return nil, fmt.Errorf("unsupported key type %q", block.Type)
|
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