Skip to content

Commit

Permalink
simplify KeypairManager interface basing it on keyids
Browse files Browse the repository at this point in the history
  • Loading branch information
pedronis committed Jan 7, 2016
1 parent 650834e commit cbad508
Show file tree
Hide file tree
Showing 8 changed files with 105 additions and 144 deletions.
2 changes: 1 addition & 1 deletion asserts/account_key_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ func (aks *accountKeySuite) SetUpSuite(c *C) {
c.Assert(err, IsNil)
aks.fp = pk.PublicKey().Fingerprint()
aks.keyid = pk.PublicKey().ID()
pubKey, err := accDb.PublicKey("acc-id1", aks.fp)
pubKey, err := accDb.PublicKey("acc-id1", aks.keyid)
c.Assert(err, IsNil)
pubKeyEncoded, err := asserts.EncodePublicKey(pubKey)
c.Assert(err, IsNil)
Expand Down
64 changes: 34 additions & 30 deletions asserts/database.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,14 +56,14 @@ type Backstore interface {

// KeypairManager is a manager and backstore for private/public key pairs.
type KeypairManager interface {
// Import stores the given private/public key pair for identity and
// returns its fingerprint
Import(authorityID string, privKey PrivateKey) (fingerprint string, err error)
// Get returns the private/public key pair with the given fingeprint.
Get(authorityID, fingeprint string) (PrivateKey, error)
// Find finds the private/public key pair with the given fingeprint suffix.
// Find will return an error if not eactly one key pair is found.
Find(authorityID, fingerprintSuffix string) (PrivateKey, error)
// Put stores the given private/public key pair for identity,
// making sure it can be later retrieved by authority-id and
// key id with Get().
// Trying to store a key with an already present key id should
// result in an error.
Put(authorityID string, privKey PrivateKey) error
// Get returns the private/public key pair with the given key id.
Get(authorityID, keyID string) (PrivateKey, error)
}

// TODO: for more flexibility plugging the keypair manager make PrivatKey private encoding methods optional, and add an explicit sign method.
Expand Down Expand Up @@ -147,8 +147,8 @@ func OpenDatabase(cfg *DatabaseConfig) (*Database, error) {
}

// GenerateKey generates a private/public key pair for identity and
// stores it returning its fingerprint.
func (db *Database) GenerateKey(authorityID string) (fingerprint string, err error) {
// stores it returning its key id.
func (db *Database) GenerateKey(authorityID string) (keyID string, err error) {
// TODO: optionally delegate the whole thing to the keypair mgr

// TODO: support specifying different key types/algorithms
Expand All @@ -157,48 +157,52 @@ func (db *Database) GenerateKey(authorityID string) (fingerprint string, err err
return "", fmt.Errorf("failed to generate private key: %v", err)
}

return db.keypairMgr.Import(authorityID, OpenPGPPrivateKey(privKey))
pk := OpenPGPPrivateKey(privKey)
return db.ImportKey(authorityID, pk)
}

// ImportKey stores the given private/public key pair for identity and
// returns its fingerprint
func (db *Database) ImportKey(authorityID string, privKey PrivateKey) (fingerprint string, err error) {
return db.keypairMgr.Import(authorityID, privKey)
// returns its keyID
func (db *Database) ImportKey(authorityID string, privKey PrivateKey) (keyID string, err error) {
err = db.keypairMgr.Put(authorityID, privKey)
if err != nil {
return "", err
}
return privKey.PublicKey().ID(), nil
}

var (
// for sanity checking of fingerprint-like strings
fingerprintLike = regexp.MustCompile("^[0-9a-f]*$")
)

// PublicKey exports the public part of a stored key pair for identity
// by matching the given fingerprint suffix, it is an error if no or more
// than one key pair is found.
func (db *Database) PublicKey(authorityID string, fingerprintSuffix string) (PublicKey, error) {
if !fingerprintLike.MatchString(fingerprintSuffix) {
return nil, fmt.Errorf("fingerprint suffix contains unexpected chars: %q", fingerprintSuffix)
func (db *Database) safeGetPrivateKey(authorityID, keyID string) (PrivateKey, error) {
if keyID == "" {
return nil, fmt.Errorf("key id is empty")
}
privKey, err := db.keypairMgr.Find(authorityID, fingerprintSuffix)
if !fingerprintLike.MatchString(keyID) {
return nil, fmt.Errorf("key id contains unexpected chars: %q", keyID)
}
return db.keypairMgr.Get(authorityID, keyID)
}

// PublicKey exports the public part of a stored key pair for identity and key id.
func (db *Database) PublicKey(authorityID string, keyID string) (PublicKey, error) {
privKey, err := db.safeGetPrivateKey(authorityID, keyID)
if err != nil {
return nil, err
}
return privKey.PublicKey(), nil
}

// Sign builds an assertion with the provided information and signs it
// with the private key from `headers["authority-id"]` that has the provided fingerprint.
func (db *Database) Sign(assertType AssertionType, headers map[string]string, body []byte, fingerprint string) (Assertion, error) {
if fingerprint == "" {
return nil, fmt.Errorf("fingerprint is empty")
}
if !fingerprintLike.MatchString(fingerprint) {
return nil, fmt.Errorf("fingerprint contains unexpected chars: %q", fingerprint)
}
// with the private key from `headers["authority-id"]` that has the provided key id.
func (db *Database) Sign(assertType AssertionType, headers map[string]string, body []byte, keyID string) (Assertion, error) {
authorityID, err := checkMandatory(headers, "authority-id")
if err != nil {
return nil, err
}
privKey, err := db.keypairMgr.Get(authorityID, fingerprint)
privKey, err := db.safeGetPrivateKey(authorityID, keyID)
if err != nil {
return nil, err
}
Expand Down
36 changes: 24 additions & 12 deletions asserts/database_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,12 +100,13 @@ func (dbs *databaseSuite) SetUpTest(c *C) {

func (dbs *databaseSuite) TestImportKey(c *C) {
expectedFingerprint := hex.EncodeToString(testPrivKey1.PublicKey.Fingerprint[:])
expectedKeyID := hex.EncodeToString(testPrivKey1.PublicKey.Fingerprint[12:])

fingerp, err := dbs.db.ImportKey("account0", asserts.OpenPGPPrivateKey(testPrivKey1))
keyid, err := dbs.db.ImportKey("account0", asserts.OpenPGPPrivateKey(testPrivKey1))
c.Assert(err, IsNil)
c.Check(fingerp, Equals, expectedFingerprint)
c.Check(keyid, Equals, expectedKeyID)

keyPath := filepath.Join(dbs.rootDir, "private-keys-v0/account0", fingerp)
keyPath := filepath.Join(dbs.rootDir, "private-keys-v0/account0", keyid)
info, err := os.Stat(keyPath)
c.Assert(err, IsNil)
c.Check(info.Mode().Perm(), Equals, os.FileMode(0600)) // secret
Expand All @@ -119,6 +120,14 @@ func (dbs *databaseSuite) TestImportKey(c *C) {
c.Check(privKeyFromDisk.PublicKey().Fingerprint(), Equals, expectedFingerprint)
}

func (dbs *databaseSuite) TestImportKeyAlreadyExists(c *C) {
_, err := dbs.db.ImportKey("account0", asserts.OpenPGPPrivateKey(testPrivKey1))
c.Assert(err, IsNil)

_, err = dbs.db.ImportKey("account0", asserts.OpenPGPPrivateKey(testPrivKey1))
c.Check(err, ErrorMatches, "key pair with given key id already exists")
}

func (dbs *databaseSuite) TestGenerateKey(c *C) {
fingerp, err := dbs.db.GenerateKey("account0")
c.Assert(err, IsNil)
Expand Down Expand Up @@ -150,15 +159,18 @@ func (dbs *databaseSuite) TestPublicKey(c *C) {
c.Assert(pubKey.Fingerprint, DeepEquals, testPrivKey1.PublicKey.Fingerprint)
}

func (dbs *databaseSuite) TestPublicKeyAmbiguous(c *C) {
_, err := dbs.db.ImportKey("account0", asserts.OpenPGPPrivateKey(testPrivKey1))
c.Assert(err, IsNil)
_, err = dbs.db.ImportKey("account0", asserts.OpenPGPPrivateKey(testPrivKey2))
func (dbs *databaseSuite) TestPublicKeyNotFound(c *C) {
pk := asserts.OpenPGPPrivateKey(testPrivKey1)
keyID := pk.PublicKey().ID()

_, err := dbs.db.PublicKey("account0", keyID)
c.Check(err, ErrorMatches, "no matching key pair found")

_, err = dbs.db.ImportKey("account0", pk)
c.Assert(err, IsNil)

pubk, err := dbs.db.PublicKey("account0", "")
c.Assert(err, ErrorMatches, `ambiguous search, more than one key pair found:.*`)
c.Check(pubk, IsNil)
_, err = dbs.db.PublicKey("account0", "ff"+keyID)
c.Check(err, ErrorMatches, "no matching key pair found")
}

type checkSuite struct {
Expand Down Expand Up @@ -265,13 +277,13 @@ func (safs *signAddFindSuite) TestSign(c *C) {
c.Check(err, IsNil)
}

func (safs *signAddFindSuite) TestSignEmptyFingerprint(c *C) {
func (safs *signAddFindSuite) TestSignEmptyKeyID(c *C) {
headers := map[string]string{
"authority-id": "canonical",
"primary-key": "a",
}
a1, err := safs.signingDB.Sign(asserts.AssertionType("test-only"), headers, nil, "")
c.Assert(err, ErrorMatches, "fingerprint is empty")
c.Assert(err, ErrorMatches, "key id is empty")
c.Check(a1, IsNil)
}

Expand Down
5 changes: 5 additions & 0 deletions asserts/fsentryutils.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,11 @@ func atomicWriteEntry(data []byte, secret bool, top string, subpath ...string) e
return helpers.AtomicWriteFile(fpath, data, os.FileMode(fperm), 0)
}

func entryExists(top string, subpath ...string) bool {
fpath := filepath.Join(top, filepath.Join(subpath...))
return helpers.FileExists(fpath)
}

func readEntry(top string, subpath ...string) ([]byte, error) {
fpath := filepath.Join(top, filepath.Join(subpath...))
return ioutil.ReadFile(fpath)
Expand Down
47 changes: 16 additions & 31 deletions asserts/fskeypairmgr.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
"errors"
"fmt"
"net/url"
"os"
"path/filepath"
)

Expand All @@ -41,41 +42,33 @@ func newFilesystemKeypairMananager(path string) *filesystemKeypairManager {
return &filesystemKeypairManager{top: filepath.Join(path, privateKeysRoot)}
}

func (fskm *filesystemKeypairManager) Import(authorityID string, privKey PrivateKey) (fingerprint string, err error) {
var errKeypairAlreadyExists = errors.New("key pair with given key id already exists")

func (fskm *filesystemKeypairManager) Put(authorityID string, privKey PrivateKey) error {
keyID := privKey.PublicKey().ID()
escapedAuthorityID := url.QueryEscape(authorityID)
if entryExists(fskm.top, escapedAuthorityID, keyID) {
return errKeypairAlreadyExists
}
encoded, err := encodePrivateKey(privKey)
if err != nil {
return "", fmt.Errorf("failed to store private key: %v", err)
return fmt.Errorf("failed to store private key: %v", err)
}

fingerp := privKey.PublicKey().Fingerprint()
err = atomicWriteEntry(encoded, true, fskm.top, url.QueryEscape(authorityID), fingerp)
err = atomicWriteEntry(encoded, true, fskm.top, escapedAuthorityID, keyID)
if err != nil {
return "", fmt.Errorf("failed to store private key: %v", err)
return fmt.Errorf("failed to store private key: %v", err)
}
return fingerp, nil
return nil
}

var errKeypairNotFound = errors.New("no matching key pair found")

// findPrivateKey will return an error if not eactly one private key is found
func (fskm *filesystemKeypairManager) findPrivateKey(authorityID, fingerprintWildcard string) (PrivateKey, error) {
keyPath := ""
foundPrivKeyCb := func(relpath string) error {
if keyPath != "" {
return fmt.Errorf("ambiguous search, more than one key pair found: %q and %q", keyPath, relpath)

}
keyPath = relpath
return nil
}
err := findWildcard(fskm.top, []string{url.QueryEscape(authorityID), fingerprintWildcard}, foundPrivKeyCb)
if err != nil {
return nil, err
}
if keyPath == "" {
func (fskm *filesystemKeypairManager) Get(authorityID, keyID string) (PrivateKey, error) {
encoded, err := readEntry(fskm.top, url.QueryEscape(authorityID), keyID)
if os.IsNotExist(err) {
return nil, errKeypairNotFound
}
encoded, err := readEntry(fskm.top, keyPath)
if err != nil {
return nil, fmt.Errorf("failed to read key pair: %v", err)
}
Expand All @@ -85,11 +78,3 @@ func (fskm *filesystemKeypairManager) findPrivateKey(authorityID, fingerprintWil
}
return privKey, nil
}

func (fskm *filesystemKeypairManager) Get(authorityID, fingeprint string) (PrivateKey, error) {
return fskm.findPrivateKey(authorityID, fingeprint)
}

func (fskm *filesystemKeypairManager) Find(authorityID, fingerprintSuffix string) (PrivateKey, error) {
return fskm.findPrivateKey(authorityID, "*"+fingerprintSuffix)
}
36 changes: 8 additions & 28 deletions asserts/memkeypairmgr.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,6 @@

package asserts

import (
"fmt"
"strings"
)

type memoryKeypairManager struct {
pairs map[string]map[string]PrivateKey
}
Expand All @@ -35,38 +30,23 @@ func NewMemoryKeypairMananager() KeypairManager {
}
}

func (mskm memoryKeypairManager) Import(authorityID string, privKey PrivateKey) (fingerprint string, err error) {
fingerp := privKey.PublicKey().Fingerprint()
func (mskm memoryKeypairManager) Put(authorityID string, privKey PrivateKey) error {
keyID := privKey.PublicKey().ID()
perAuthID := mskm.pairs[authorityID]
if perAuthID == nil {
perAuthID = make(map[string]PrivateKey)
mskm.pairs[authorityID] = perAuthID
} else if perAuthID[keyID] != nil {
return errKeypairAlreadyExists
}
perAuthID[fingerp] = privKey
return fingerp, nil
perAuthID[keyID] = privKey
return nil
}

func (mskm memoryKeypairManager) Get(authorityID, fingeprint string) (PrivateKey, error) {
privKey := mskm.pairs[authorityID][fingeprint]
func (mskm memoryKeypairManager) Get(authorityID, keyID string) (PrivateKey, error) {
privKey := mskm.pairs[authorityID][keyID]
if privKey == nil {
return nil, errKeypairNotFound
}
return privKey, nil
}

func (mskm memoryKeypairManager) Find(authorityID, fingerprintSuffix string) (PrivateKey, error) {
var found PrivateKey
for fingerp, privKey := range mskm.pairs[authorityID] {
if strings.HasSuffix(fingerp, fingerprintSuffix) {
if found == nil {
found = privKey
} else {
return nil, fmt.Errorf("ambiguous search, more than one key pair found: %q and %q", found.PublicKey().Fingerprint(), privKey.PublicKey().Fingerprint())
}
}
}
if found == nil {
return nil, errKeypairNotFound
}
return found, nil
}
Loading

0 comments on commit cbad508

Please sign in to comment.