diff --git a/.travis.yml b/.travis.yml index 97b3116..80cce81 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,17 +1,7 @@ -branches: - only: - - master language: go go: - - 1.4 - - 1.5 - - 1.6 - - tip + - 1.10 install: - - go get golang.org/x/crypto/openpgp - - go get golang.org/x/crypto/cast5 - - go get golang.org/x/crypto/ssh/terminal - - go get golang.org/x/tools/cmd/cover - go build -v ./... script: - go test -v -cover diff --git a/README.md b/README.md index 4604039..25fed83 100644 --- a/README.md +++ b/README.md @@ -1,70 +1,3 @@ -[![GoDoc](https://godoc.org/github.com/jsipprell/keyctl?status.svg)](https://godoc.org/github.com/jsipprell/keyctl) -[![Build Status](https://travis-ci.org/jsipprell/keyctl.svg?branch=master)](https://travis-ci.org/jsipprell/keyctl) - # keyctl -A native Go API for the security key management system (aka "keyrings") found in Linux 2.6+ - -The keyctl interface is nominally provided by three or so Linux-specific syscalls, however it is almost always wrapped -in a library named `libkeyutils.so`. - -This package interacts directly with the syscall interface and does not require CGO for linkage to the helper library -provided on most systems. - -## Example Usages - -To access the default session keyring (and create it if it doesn't exist) - - -```go -package main - -import ( - "log" - "github.com/jsipprell/keyctl" -) - -func main() { - keyring, err := keyctl.SessionKeyring() - if err != nil { - log.Fatal(err) - } - - // default timeout of 10 seconds for new or updated keys - keyring.SetDefaultTimeout(10) - secureData := []byte{1,2,3,4} - id, err := keyring.Add("some-data", secureData) - if err != nil { - log.Fatal(err) - } - log.Printf("created session key id %v", id) -} -``` - -To search for an existing key by name: - -```go -package main - -import ( - "log" - "github.com/jsipprell/keyctl" -) - -func main() { - keyring, err := keyctl.SessionKeyring() - if err != nil { - log.Fatal(err) - } - key, err := keyring.Search("some-data") - if err != nil { - log.Fatal(err) - } - - data, err := key.Get() - if err != nil { - log.Fatal(err) - } - log.Printf("secure data: %v\n", data) -} -``` +Slim version of `github.com/jsipprell/keyctl` with basic keyring operations. \ No newline at end of file diff --git a/key.go b/key.go index 78c5c88..88b2977 100644 --- a/key.go +++ b/key.go @@ -1,40 +1,25 @@ package keyctl import ( - "time" "unsafe" ) -// Represents a single key linked to one or more kernel keyrings. +// Key represents a single key linked to one or more kernel keyrings. type Key struct { Name string id, ring keyId size int - ttl time.Duration } func (k *Key) private() {} -// Returns the 32-bit kernel identifier for a specific key +// Id returns the 32-bit kernel identifier for a specific key func (k *Key) Id() int32 { return int32(k.id) } -// To expire a key automatically after some period of time call this method. -func (k *Key) ExpireAfter(nsecs uint) error { - k.ttl = time.Duration(nsecs) * time.Second - - _, _, err := keyctl(keyctlSetTimeout, uintptr(k.id), uintptr(nsecs)) - return err -} - -// Return information about a key. -func (k *Key) Info() (Info, error) { - return getInfo(k.id) -} - -// Get the key's value as a byte slice +// Get returns the key's value as a byte slice func (k *Key) Get() ([]byte, error) { var ( b []byte @@ -70,9 +55,6 @@ func (k *Key) Get() ([]byte, error) { // Set the key's value from a bytes slice. Expiration, if active, is reset by calling this method. func (k *Key) Set(b []byte) error { err := updateKey(k.id, b) - if err == nil && k.ttl > 0 { - err = k.ExpireAfter(uint(k.ttl.Seconds())) - } return err } diff --git a/key_test.go b/key_test.go index 4d45c59..671cefd 100644 --- a/key_test.go +++ b/key_test.go @@ -3,7 +3,6 @@ package keyctl import ( "math/rand" "testing" - "time" ) func helperRandBlock(sz int) []byte { @@ -28,7 +27,7 @@ func helperCompareBlock(t *testing.T, name string, blk2 []byte, ring Keyring) { err error ) if ring == nil { - ring, err = UserSessionKeyring() + ring, err = SessionKeyring() if err != nil { t.Fatal(err) } @@ -38,10 +37,6 @@ func helperCompareBlock(t *testing.T, name string, blk2 []byte, ring Keyring) { t.Fatal(err) } - if err = key.ExpireAfter(5); err != nil { - t.Fatal(err) - } - blk1, err := key.Get() if err != nil { t.Fatal(err) @@ -63,7 +58,7 @@ func helperCmp(t *testing.T, blk1 []byte, blk2 []byte) { } func TestRandomKey256(t *testing.T) { - ring, err := UserSessionKeyring() + ring, err := SessionKeyring() if err != nil { t.Fatal(err) } @@ -78,7 +73,7 @@ func TestRandomKey256(t *testing.T) { } func TestRandomKey700(t *testing.T) { - ring, err := UserSessionKeyring() + ring, err := SessionKeyring() if err != nil { t.Fatal(err) } @@ -91,10 +86,4 @@ func TestRandomKey700(t *testing.T) { t.Logf("added %d byte random value key as: %v (%v)\n", len(r700), id.Id(), r700) helperCompareBlock(t, "rand700", r700, nil) - time.Sleep(time.Duration(5)*time.Second + time.Duration(250000)) - - if _, err = ring.Search("rand700"); err == nil { - t.Fatal("'rand700' key did not expire in five seconds") - } - t.Logf("key %v expired after five seconds", id.Id()) } diff --git a/keyring.go b/keyring.go index 5e8305e..2d64b34 100644 --- a/keyring.go +++ b/keyring.go @@ -10,7 +10,6 @@ package keyctl // All Keys and Keyrings have unique 32-bit serial number identifiers. type Id interface { Id() int32 - Info() (Info, error) private() } @@ -20,7 +19,6 @@ type Keyring interface { Id Add(string, []byte) (*Key, error) Search(string) (*Key, error) - SetDefaultTimeout(uint) } // Named keyrings are user-created keyrings linked to a parent keyring. The @@ -33,15 +31,7 @@ type NamedKeyring interface { } type keyring struct { - id keyId - defaultTtl uint -} - -type namedKeyring struct { - *keyring - parent keyId - name string // for non-anonymous keyrings - ttl uint + id keyId } func (kr *keyring) private() {} @@ -51,31 +41,11 @@ func (kr *keyring) Id() int32 { return int32(kr.id) } -// Returns information about a keyring. -func (kr *keyring) Info() (Info, error) { - return getInfo(kr.id) -} - -// Return the name of a NamedKeyring that was set when the keyring was created -// or opened. -func (kr *namedKeyring) Name() string { - return kr.name -} - -// Set a default timeout, in seconds, after which newly added keys will be -// destroyed. -func (kr *keyring) SetDefaultTimeout(nsecs uint) { - kr.defaultTtl = nsecs -} - // Add a new key to a keyring. The key can be searched for later by name. func (kr *keyring) Add(name string, key []byte) (*Key, error) { r, err := add_key("user", name, key, int32(kr.id)) if err == nil { key := &Key{Name: name, id: keyId(r), ring: kr.id} - if kr.defaultTtl != 0 { - err = key.ExpireAfter(kr.defaultTtl) - } return key, err } @@ -98,95 +68,8 @@ func SessionKeyring() (Keyring, error) { return newKeyring(keySpecSessionKeyring) } -// Return the current user-session keyring (part of session, but private to -// current user) -func UserSessionKeyring() (Keyring, error) { - return newKeyring(keySpecUserSessionKeyring) -} - -// Return the current group keyring. -func GroupKeyring() (Keyring, error) { - return newKeyring(keySpecGroupKeyring) -} - -// Return the keyring specific to the current executing thread. -func ThreadKeyring() (Keyring, error) { - return newKeyring(keySpecThreadKeyring) -} - -// Return the keyring specific to the current executing process. -func ProcessKeyring() (Keyring, error) { - return newKeyring(keySpecProcessKeyring) -} - -// Creates a new named-keyring linked to a parent keyring. The parent may be -// one of those returned by SessionKeyring(), UserSessionKeyring() and friends -// or it may be an existing named-keyring. When searching is performed, all -// keyrings form a hierarchy and are searched top-down. If the keyring already -// exists it will be destroyed and a new one with the same name created. Named -// sub-keyrings inherit their initial ttl (if set) from the parent but can -// outlive the parent as the timer is restarted at creation. -func CreateKeyring(parent Keyring, name string) (NamedKeyring, error) { - var ttl uint - - parentId := keyId(parent.Id()) - kr, err := createKeyring(parentId, name) - if err != nil { - return nil, err - } - - if pkr, ok := parent.(*namedKeyring); ok { - ttl = pkr.ttl - } - ring := &namedKeyring{ - keyring: kr, - parent: parentId, - name: name, - ttl: ttl, - } - - if ttl > 0 { - _, _, err = keyctl(keyctlSetTimeout, uintptr(ring.id), uintptr(ttl)) - } - - return ring, nil -} - -// Search for and open an existing keyring with the given name linked to a -// parent keyring (at any depth). -func OpenKeyring(parent Keyring, name string) (NamedKeyring, error) { - parentId := keyId(parent.Id()) - id, err := searchKeyring(parentId, name, "keyring") - if err != nil { - return nil, err - } - - return &namedKeyring{ - keyring: &keyring{id: id}, - parent: parentId, - name: name, - }, nil -} - -// Set the time to live in seconds for an entire keyring and all of its keys. -// Only named keyrings can have their time-to-live set, the in-built keyrings -// cannot (Session, UserSession, etc). -func SetKeyringTTL(kr NamedKeyring, nsecs uint) error { - _, _, err := keyctl(keyctlSetTimeout, uintptr(kr.Id()), uintptr(nsecs)) - if err == nil { - kr.(*namedKeyring).ttl = nsecs - } - return err -} - // Unlink an object from a keyring func Unlink(parent Keyring, child Id) error { _, _, err := keyctl(keyctlUnlink, uintptr(parent.Id()), uintptr(child.Id())) return err } - -// Unlink a named keyring from its parent. -func UnlinkKeyring(kr NamedKeyring) error { - _, _, err := keyctl(keyctlUnlink, uintptr(kr.Id()), uintptr(kr.(*namedKeyring).parent)) - return err -} diff --git a/keyring_test.go b/keyring_test.go index 5661573..e27ac06 100644 --- a/keyring_test.go +++ b/keyring_test.go @@ -4,8 +4,8 @@ import ( "testing" ) -func TestAdd100BytesToUserSessionKeyring(t *testing.T) { - ring, err := UserSessionKeyring() +func TestAdd100BytesToSessionKeyring(t *testing.T) { + ring, err := SessionKeyring() if err != nil { t.Fatal(err) } @@ -24,40 +24,8 @@ func TestAdd100BytesToUserSessionKeyring(t *testing.T) { t.Logf("read %d octets from key: %v\n", len(buf), buf) } -func TestAdd128BytesToUserSessionExpireAfter10Seconds(t *testing.T) { - ring, err := UserSessionKeyring() - if err != nil { - t.Fatal(err) - } - ring.SetDefaultTimeout(10) - key, err := ring.Add("expire-test", make([]byte, 128)) - if err != nil { - t.Fatal(err) - } - t.Logf("added 128 byte empty key as: %v\n", key.Id()) -} - -func TestFetchKey(t *testing.T) { - ring, err := UserSessionKeyring() - if err != nil { - t.Fatal(err) - } - key, err := ring.Search("blank") - if err != nil { - t.Fatal(err) - } - - buf := make([]byte, 1024) - r := NewReader(key) - n, err := r.Read(buf) - if err != nil { - t.Fatal(err) - } - t.Logf("read %d octets from key: %v\n", n, buf[:n]) -} - func TestFetchKeyFail(t *testing.T) { - ring, err := UserSessionKeyring() + ring, err := SessionKeyring() if err != nil { t.Fatal(err) } @@ -68,7 +36,7 @@ func TestFetchKeyFail(t *testing.T) { } func TestUnlinkKey(t *testing.T) { - ring, err := UserSessionKeyring() + ring, err := SessionKeyring() if err != nil { t.Fatal(err) } @@ -80,93 +48,3 @@ func TestUnlinkKey(t *testing.T) { t.Fatal(err) } } - -func helperTestCreateKeyring(ring Keyring, name string, t *testing.T) NamedKeyring { - var err error - - if ring == nil { - ring, err = UserSessionKeyring() - if err != nil { - t.Fatal(err) - } - } - - if name == "" { - name = "testring" - } - ring, err = CreateKeyring(ring, name) - if err != nil { - t.Fatal(err) - } - - t.Logf("created keyring %v named %q", ring.Id(), ring.(NamedKeyring).Name()) - return ring.(NamedKeyring) -} - -func TestCreateKeyring(t *testing.T) { - ring := helperTestCreateKeyring(nil, "", t) - - err := SetKeyringTTL(ring, 10) - if err != nil { - t.Fatal(err) - } -} - -func TestCreateNestedKeyring(t *testing.T) { - ring := helperTestCreateKeyring(nil, "", t) - - err := SetKeyringTTL(ring, 30) - if err != nil { - t.Fatal(err) - } - - ring = helperTestCreateKeyring(ring, "testring2", t) - t.Logf("created nested keyring %v named %q", ring.Id(), ring.Name()) - ring = helperTestCreateKeyring(ring, "testring3", t) - t.Logf("created nested keyring %v named %q", ring.Id(), ring.Name()) -} - -func TestOpenNestedKeyring(t *testing.T) { - us, err := UserSessionKeyring() - if err != nil { - t.Fatal(err) - } - ring := helperTestCreateKeyring(us, "", t) - - err = SetKeyringTTL(ring, 30) - if err != nil { - t.Fatal(err) - } - - ring = helperTestCreateKeyring(ring, "testring2", t) - t.Logf("created nested keyring %v named %q", ring.Id(), ring.Name()) - ring = helperTestCreateKeyring(ring, "testring3", t) - t.Logf("created nested keyring %v named %q", ring.Id(), ring.Name()) - - ring, err = OpenKeyring(us, "testring3") - if err != nil { - t.Fatal(err) - } - t.Logf("successfully reopened keyring %v named %q", ring.Id(), ring.Name()) -} - -func TestUnlinkKeyring(t *testing.T) { - ring, err := UserSessionKeyring() - if err != nil { - t.Fatal(err) - } - - nring, err := CreateKeyring(ring, "testring") - if err != nil { - t.Fatal(err) - } - - t.Logf("created keyring %v named %q", nring.Id(), nring.Name()) - - err = UnlinkKeyring(nring) - if err != nil { - t.Fatal(err) - } - - t.Logf("unlinked keyring %v [%s]", nring.Id(), nring.Name()) -} diff --git a/pgp/README.md b/pgp/README.md deleted file mode 100644 index ca3da3b..0000000 --- a/pgp/README.md +++ /dev/null @@ -1,45 +0,0 @@ -[![GoDoc](https://godoc.org/github.com/jsipprell/keyctl/pgp?status.svg)](https://godoc.org/github.com/jsipprell/keyctl/pgp) - -# keyctl/pgp - -A "helper" package for use with the `golang.org/x/crypto/openpgp` package which can transparently cache private key passphrases using the -linux kernel's secure keyring system. Such cached passphrases can automatically expire after a configurable duration. - -## Usage - -To use, simply import the parent pkg `keyctl`, open the user session keyring, embed it in a static `pgp.PassphraseKeyring` struct and call -`ReadMessage` on this struct instead of using `goglang.org/x/crypto/openpgp.ReadMessage`. To customize the passphrase prompt, either -assign your own `pgp.Prompter` compatible interface to `PassphraseKeyring` or pass in an `openpgp.PromptFunction` in the `ReadMessage()` -method call. - -For convenience, an `openpgp.PromptFunction` compatible func named `PassphrasePrompt` is exposed in the package. - -## Example - -```go -package main - -import ( - "io" - "log" - "golang.org/x/crypto/openpgp" - "github.com/jsipprell/keyctl" - "github.com/jsipprell/keyctl/pgp" -) - -func decryptReader(r io.Reader, pgpKeyring openpgp.KeyRing) { - kr, err := keyctl.UserSessionKeyring() - if err != nil { - log.Fatal(err) - } - - pkr := pgp.PassphraseKeyring{Keyring:kr} - // Discard passphrases after 10 minutes - pkr.SetDefaultTimeout(600) - - msgDetails, err := pkr.ReadMessage(r, pgpKeyring, pgp.PassphrasePrompt, nil) - if err != nil { - log.Fatal(err) - } - log.Printf("%#v\n", msgDetails) -} diff --git a/pgp/passphrase.go b/pgp/passphrase.go deleted file mode 100644 index 96bdc9b..0000000 --- a/pgp/passphrase.go +++ /dev/null @@ -1,115 +0,0 @@ -// Copyright 2015 Jesse Sipprell. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// Provides a keyring with an openpgp.ReadMessage wrapper -// method that when called will automatically attempt -// private key decryption and save the passphrase in the -// private session kernel keyring for a configurable -// amount of time. If an encrypted private key is seen again -// before it expires, the original PromptFunction will not -// be called (unless decryption fails) -package pgp - -import ( - "io" - - "github.com/jsipprell/keyctl" - "golang.org/x/crypto/openpgp" - "golang.org/x/crypto/openpgp/packet" -) - -// A standard passphrase prompting interface -type Prompter interface { - Prompt([]openpgp.Key, bool) ([]byte, error) -} - -// A wrapper keyring that can automatically decrypt openpgp secret keys if the -// passphrase was previously used by the keyring (and the ttl has not expired) -// Such caching lives beyond the lifetime of the current process unless the -// process or thread keyring is used. -type PassphraseKeyring struct { - keyctl.Keyring - Prompt Prompter -} - -type passphrase struct { - keyctl.Keyring - handler Prompter - tried map[uint64]struct{} -} - -type prompter openpgp.PromptFunction - -// Create a new Prompter from an openpgp prompting function -func NewPrompter(prompt openpgp.PromptFunction) Prompter { - return prompter(prompt) -} - -func (fn prompter) Prompt(keys []openpgp.Key, symmetric bool) ([]byte, error) { - return fn(keys, symmetric) -} - -// A look-alike to "golang.org/x/crypto/opengpg".ReadMessage. When called it -// calls the openpgp.ReadMessage function, passing the io.Reader and -// openpgp.Keyring verbatim but in -func (pkr PassphraseKeyring) ReadMessage(r io.Reader, keyring openpgp.KeyRing, - prompt interface{}, config *packet.Config) (*openpgp.MessageDetails, error) { - var handler Prompter - switch t := prompt.(type) { - case Prompter: - handler = t - case openpgp.PromptFunction: - handler = prompter(t) - case func([]openpgp.Key, bool) ([]byte, error): - handler = prompter(t) - } - - if handler == nil { - handler = pkr.Prompt - } - - p := &passphrase{ - Keyring: pkr.Keyring, - handler: handler, - tried: make(map[uint64]struct{}), - } - return openpgp.ReadMessage(r, keyring, p.check, config) -} - -func (p *passphrase) check(keys []openpgp.Key, symmetric bool) ([]byte, error) { - if symmetric { - return p.handler.Prompt(keys, symmetric) - } - - for _, k := range keys { - if _, ok := p.tried[k.PrivateKey.KeyId]; !ok { - p.tried[k.PrivateKey.KeyId] = struct{}{} - if !k.PrivateKey.Encrypted { - continue - } - if passkey, err := p.Search("pgp:" + k.PrivateKey.KeyIdString()); err == nil { - if pass, err := passkey.Get(); err == nil { - if err = k.PrivateKey.Decrypt(pass); err == nil { - return nil, nil - } - } - } - } - - for _, k := range keys { - if k.PrivateKey.Encrypted { - pass, err := p.handler.Prompt([]openpgp.Key{k}, true) - if err != nil { - return nil, err - } - if err = k.PrivateKey.Decrypt(pass); err == nil { - _, err = p.Add("pgp:"+k.PrivateKey.KeyIdString(), pass) - return nil, err - } - } - } - } - - return p.handler.Prompt(nil, symmetric) -} diff --git a/pgp/prompt.go b/pgp/prompt.go deleted file mode 100644 index a412e31..0000000 --- a/pgp/prompt.go +++ /dev/null @@ -1,27 +0,0 @@ -package pgp - -import ( - "io" - "os" - - "golang.org/x/crypto/openpgp" - "golang.org/x/crypto/ssh/terminal" -) - -func PassphrasePrompt(keys []openpgp.Key, symmetric bool) ([]byte, error) { - if len(keys) == 0 && !symmetric { - return nil, io.EOF - } - oldState, err := terminal.MakeRaw(0) - if err != nil { - return nil, err - } - defer os.Stdout.Write([]byte{'\n'}) - defer terminal.Restore(0, oldState) - if len(keys) > 0 { - os.Stdout.Write([]byte("Enter passphrase for key " + keys[0].PrivateKey.KeyIdShortString() + " : ")) - } else { - os.Stdout.Write([]byte("Enter passphrase: ")) - } - return terminal.ReadPassword(0) -} diff --git a/reader.go b/reader.go deleted file mode 100644 index c4213d1..0000000 --- a/reader.go +++ /dev/null @@ -1,45 +0,0 @@ -package keyctl - -import ( - "bytes" - "io" - "sync" -) - -type reader struct { - *bytes.Buffer - key *Key - err error - once sync.Once -} - -func (r *reader) Read(b []byte) (int, error) { - r.once.Do(func() { - buf, err := r.key.Get() - if err != nil { - r.err = err - } else { - r.Buffer = bytes.NewBuffer(buf) - } - }) - if r.err != nil { - return -1, r.err - } - - return r.Buffer.Read(b) -} - -// Returns an io.Reader interface object which will read the key's data from -// the kernel. -func NewReader(key *Key) io.Reader { - return &reader{key: key} -} - -// Open an existing key on a keyring given its name -func OpenReader(name string, ring Keyring) (io.Reader, error) { - key, err := ring.Search(name) - if err == nil { - return NewReader(key), nil - } - return nil, err -} diff --git a/reader_test.go b/reader_test.go deleted file mode 100644 index bbfdf1f..0000000 --- a/reader_test.go +++ /dev/null @@ -1,50 +0,0 @@ -package keyctl - -import ( - "io" - "testing" -) - -func helperSetRandBlock(name string, sz int) (key Id, ring Keyring, blk []byte, err error) { - if ring, err = SessionKeyring(); err != nil { - return - } - - blk = helperRandBlock(sz) - if key, err = ring.Search(name); err != nil { - key, err = ring.Add(name, blk) - return - } - - err = key.(*Key).Set(blk) - return -} - -func TestStreamReader(t *testing.T) { - key, ring, blk, err := helperSetRandBlock("test1544bytestream", 719) - - if err != nil { - t.Fatal(err) - } - - r, err := OpenReader("test1544bytestream", ring) - if err != nil { - t.Fatal(err) - } - - var i int - buf := make([]byte, 128) - for i, err = r.Read(buf); err == nil; i, err = r.Read(buf) { - helperCmp(t, blk[:i], buf[:i]) - t.Logf("compared key %v %d bytes: %v", key.Id(), i, blk[:i]) - blk = blk[i:] - } - - if err != io.EOF { - t.Fatal(err) - } - - if len(blk) != 0 { - t.Fatalf("Read on key returned excess %d bytes", len(blk)) - } -} diff --git a/ref.go b/ref.go deleted file mode 100644 index 6eb0056..0000000 --- a/ref.go +++ /dev/null @@ -1,160 +0,0 @@ -package keyctl - -import ( - "bytes" - "errors" - "os" - "strconv" -) - -var ( - // Error returned if the Get() method is called on a Reference that doesn't - // represent a key or keychain. - ErrUnsupportedKeyType = errors.New("unsupported keyctl key type") - // Error returned if a reference is stale when Info() or Get() is called on - // it. - ErrInvalidReference = errors.New("invalid keyctl reference") -) - -// Reference is a reference to an unloaded keyctl Key or Keychain. It can be -// dereferenced by calling the Get() method. -type Reference struct { - // Id is the kernel key or keychain identifier referenced. - Id int32 - - info *Info - parent keyId -} - -// Information about a keyctl reference as returned by ref.Info() -type Info struct { - Type, Name string - Uid, Gid int - Perm KeyPerm - - valid bool -} - -func getInfo(id keyId) (i Info, err error) { - var desc []byte - - if desc, err = describeKeyId(id); err != nil { - i.Name = err.Error() - return - } - - fields := bytes.Split(desc, []byte{';'}) - switch len(fields) { - case 5: - i.Name = string(fields[4]) - fallthrough - case 4: - p, _ := strconv.ParseUint(string(fields[3]), 16, 32) - i.Perm = KeyPerm(p) - fallthrough - case 3: - i.Gid, _ = strconv.Atoi(string(fields[2])) - fallthrough - case 2: - i.Uid, _ = strconv.Atoi(string(fields[1])) - fallthrough - case 1: - if i.Type = string(fields[0]); i.Type == "user" { - i.Type = "key" - } - i.valid = true - default: - panic("invalid field count from kernel keyctl describe sysctl") - } - return -} - -// Returns permissions in symbolic format. -func (i Info) Permissions() string { - if i.Uid == os.Geteuid() { - return encodePerms(uint8(i.Perm >> KeyPerm(16))) - } else { - fsgid, err := getfsgid() - if (err == nil && i.Gid == int(fsgid)) || i.Gid == os.Getegid() { - return encodePerms(uint8(i.Perm >> KeyPerm(8))) - } - } - return encodePerms(uint8(i.Perm)) -} - -// Return Information about a keyctl reference. -func (r *Reference) Info() (i Info, err error) { - if r.info == nil { - i, err = getInfo(keyId(r.Id)) - r.info = &i - return - } - - return *r.info, err -} - -// Returns true if the Info fetched by ref.Info() is valid. -func (i Info) Valid() bool { - return i.valid -} - -// Returns true if the keyctl reference is valid. Refererences can become -// invalid if they have expired since the reference was created. -func (r *Reference) Valid() bool { - if r.info == nil { - r.Info() - } - return r.info.valid -} - -// Loads the referenced keyctl object, which must either be a key or a -// keyring otherwise ErrUnsupportedKeyType will be returned. -func (r *Reference) Get() (Id, error) { - if r.info == nil { - _, err := r.Info() - if err != nil { - return nil, err - } - } - - if !r.info.valid { - return nil, ErrInvalidReference - } - - switch r.info.Type { - case "key": - return &Key{Name: r.info.Name, id: keyId(r.Id), ring: r.parent}, nil - case "keyring": - ring := &keyring{id: keyId(r.Id)} - if r.Id > 0 && r.info.Name != "" { - return &namedKeyring{ - keyring: ring, - parent: r.parent, - name: r.info.Name, - }, nil - } - return ring, nil - default: - return nil, ErrUnsupportedKeyType - } -} - -// List the contents of a keyring. Each contained object is represented by a -// Reference struct. Addl information is available by calling ref.Info(), and -// contained objects which are keys or subordinate keyrings can be fetched by -// calling ref.Get() -func ListKeyring(kr Keyring) ([]Reference, error) { - id := keyId(kr.Id()) - keys, err := listKeys(id) - if err != nil { - return nil, err - } - - refs := make([]Reference, len(keys)) - - for i, k := range keys { - refs[i].Id, refs[i].parent = int32(k), id - } - - return refs, nil -} diff --git a/ref_test.go b/ref_test.go deleted file mode 100644 index 80e7f7a..0000000 --- a/ref_test.go +++ /dev/null @@ -1,85 +0,0 @@ -package keyctl - -import ( - "syscall" - "testing" -) - -func mustInfo(r Reference) Info { - info, err := r.Info() - if err != nil { - if msg := err.Error(); msg == "key has expired" { - return Info{Name: msg} - } - } - return info -} - -func helperTestKeyRefs(ring Keyring, t *testing.T) []Reference { - var err error - - if ring == nil { - if ring, err = SessionKeyring(); err != nil { - t.Fatal(err) - } - } - - refs, err := ListKeyring(ring) - if err != nil { - t.Fatal(err) - } - - for _, r := range refs { - t.Logf("%d: %+v [%s]\n", r.Id, mustInfo(r), mustInfo(r).Permissions()) - } - - return refs -} - -func filterErrno(e error, ignore ...syscall.Errno) error { - if en, ok := e.(syscall.Errno); ok { - for _, enok := range ignore { - if enok == en { - return nil - } - } - } - - return e -} - -func helperRecurseKeyringRefs(kr Keyring, t *testing.T) { - for _, r := range helperTestKeyRefs(kr, t) { - if !r.Valid() { - continue - } - key, err := r.Get() - if filterErrno(err, syscall.EPERM, syscall.EACCES) != nil { - t.Fatal(err) - } - if err != nil { - return - } - switch k := key.(type) { - case *namedKeyring: - t.Logf("keyring %v: %q, parent %v", k.id, k.Name(), k.parent) - helperRecurseKeyringRefs(k, t) - case *keyring: - t.Logf("keyring %v", k.id) - helperRecurseKeyringRefs(k, t) - case *Key: - t.Logf("key %v: %q, keyring %v", k.id, k.Name, k.ring) - data, err := k.Get() - if filterErrno(err, syscall.EPERM, syscall.EACCES) != nil { - t.Fatalf("%v %T(%d)", err, err, err) - } - t.Logf(" %v: %v", k.id, data) - default: - panic("unsupported type") - } - } -} - -func TestSessionKeyringRefs(t *testing.T) { - helperRecurseKeyringRefs(nil, t) -} diff --git a/sys_linux.go b/sys_linux.go index ec5c381..4bd3e54 100644 --- a/sys_linux.go +++ b/sys_linux.go @@ -145,22 +145,6 @@ func add_key(keyType, keyDesc string, payload []byte, id int32) (int32, error) { return int32(r1), nil } -func getfsgid() (int32, error) { - var ( - a1 int32 - err error - errno syscall.Errno - r1 uintptr - ) - - a1 = -1 - if r1, _, errno = syscall.Syscall(syscall_setfsgid, uintptr(a1), 0, 0); errno != 0 { - err = errno - return int32(-1), err - } - return int32(r1), nil -} - func newKeyring(id keyId) (*keyring, error) { r1, _, err := keyctl(keyctlGetKeyringId, uintptr(id), uintptr(1)) if err != nil { @@ -173,15 +157,6 @@ func newKeyring(id keyId) (*keyring, error) { return &keyring{id: keyId(r1)}, nil } -func createKeyring(parent keyId, name string) (*keyring, error) { - id, err := add_key("keyring", name, nil, int32(parent)) - if err != nil { - return nil, err - } - - return &keyring{id: keyId(id)}, nil -} - func searchKeyring(id keyId, name, keyType string) (keyId, error) { var ( r1 int32 @@ -200,70 +175,6 @@ func searchKeyring(id keyId, name, keyType string) (keyId, error) { return keyId(r1), err } -func describeKeyId(id keyId) ([]byte, error) { - var ( - r1 int32 - b1 []byte - err error - size, sizeRead int - ) - - b1 = make([]byte, 64) - size = len(b1) - sizeRead = size + 1 - for sizeRead > size { - r1, _, err = keyctl(keyctlDescribe, uintptr(id), uintptr(unsafe.Pointer(&b1[0])), uintptr(size)) - if err != nil { - return nil, err - } - - if sizeRead = int(r1); sizeRead > size { - b1 = make([]byte, sizeRead) - size = sizeRead - sizeRead++ - } else { - size = sizeRead - } - } - - return b1[:size-1], nil -} - -func listKeys(id keyId) ([]keyId, error) { - var ( - r1 int32 - b1 []byte - err error - size, sizeRead int - ) - - bsz := int(unsafe.Sizeof(r1)) - b1 = make([]byte, 16*bsz) - size = len(b1) - sizeRead = size + 1 - for sizeRead > size { - r1, _, err = keyctl(keyctlRead, uintptr(id), uintptr(unsafe.Pointer(&b1[0])), uintptr(size)) - - if err != nil { - return nil, err - } - - if sizeRead = int(r1); sizeRead > size { - b1 = make([]byte, sizeRead) - size = sizeRead - sizeRead++ - } else { - size = sizeRead - } - } - keys := make([]keyId, size/bsz) - for i := range keys { - keys[i] = *((*keyId)(unsafe.Pointer(&b1[i*bsz]))) - } - - return keys, nil -} - func updateKey(id keyId, payload []byte) error { size := len(payload) if size == 0 { diff --git a/sys_test.go b/sys_test.go deleted file mode 100644 index b531368..0000000 --- a/sys_test.go +++ /dev/null @@ -1,33 +0,0 @@ -package keyctl - -import ( - "os" - "testing" -) - -func TestListKeyring(t *testing.T) { - ring, err := UserSessionKeyring() - if err != nil { - t.Fatal(err) - } - - keys, err := listKeys(keyId(ring.Id())) - if err != nil { - t.Fatal(err) - } - - for _, k := range keys { - t.Logf("id %v", k) - } -} - -func TestFSGID(t *testing.T) { - gid, err := getfsgid() - if err != nil { - t.Fatal(err) - } - if int(gid) != os.Getegid() { - t.Fatalf("getfsgid() returned unexpected results (%d!=%d)", gid, os.Getegid()) - } - t.Logf("fsgid = %v\n", gid) -} diff --git a/writer.go b/writer.go deleted file mode 100644 index 3573688..0000000 --- a/writer.go +++ /dev/null @@ -1,70 +0,0 @@ -package keyctl - -import ( - "bytes" - "errors" - "io" -) - -type Flusher interface { - io.Writer - io.Closer - Flush() error -} - -// Error returned when attempting to close or flush an already closed stream -var ErrStreamClosed = errors.New("keyctl write stream closed") - -type writer struct { - *bytes.Buffer - key Id - name string - closed bool -} - -// Close a stream writer. *This or Flush() MUST be called in order to flush -// the key value to the kernel. -func (w *writer) Close() error { - if !w.closed { - defer setClosed(w) - return w.Flush() - } - return ErrStreamClosed -} - -// Flush the current stream writer buffer key data to the kernel. New writes -// after this will need to be re-flushed or have Close() called. -func (w *writer) Flush() (err error) { - if !w.closed { - switch t := w.key.(type) { - case Keyring: - var key Id - key, err = t.Add(w.name, w.Bytes()) - if err == nil { - w.key = key - } - case *Key: - err = updateKey(t.id, w.Bytes()) - if err == nil && t.ttl != 0 { - err = t.ExpireAfter(uint(t.ttl.Seconds())) - } - } - return - } - return ErrStreamClosed -} - -func setClosed(w *writer) { - w.closed = true -} - -// Create a new stream writer to write key data to. The writer MUST Close() or -// Flush() the stream before the data will be flushed to the kernel. -func NewWriter(key *Key) Flusher { - return &writer{Buffer: bytes.NewBuffer(make([]byte, 0, 1024)), key: key} -} - -// Create a new key and stream writer with a given name on an open keyring. -func CreateWriter(name string, ring Keyring) (Flusher, error) { - return &writer{Buffer: bytes.NewBuffer(make([]byte, 0, 1024)), key: ring, name: name}, nil -} diff --git a/writer_test.go b/writer_test.go deleted file mode 100644 index 08b9ca9..0000000 --- a/writer_test.go +++ /dev/null @@ -1,99 +0,0 @@ -package keyctl - -import ( - "testing" -) - -func TestStreamWriter(t *testing.T) { - blk1 := helperRandBlock(1544) - - ring, err := SessionKeyring() - if err != nil { - t.Fatal(err) - } - - w, err := CreateWriter("test1544bytestream", ring) - if err != nil { - t.Fatal(err) - } - - i, err := w.Write(blk1) - if err != nil { - t.Fatal(err) - } - if err = w.Close(); err != nil { - t.Fatal(err) - } - - if i != len(blk1) { - t.Fatal("write mismatch (%d!=%d)", len(blk1), i) - } - helperCompareBlock(t, "test1544bytestream", blk1, ring) - t.Logf("compared %d random block key in common session ring: %v", len(blk1), blk1[:200]) -} - -func TestStreamWriterUpdate(t *testing.T) { - blk1 := helperRandBlock(218) - - ring, err := SessionKeyring() - if err != nil { - t.Fatal(err) - } - - ring, err = CreateKeyring(ring, "test") - var key *Key - - for key == nil { - key, err = ring.Search("test218bytestream") - if err != nil { - key, err = ring.Add("test218bytestream", []byte{0}) - if err != nil { - t.Fatal(err) - } - } - } - - w := NewWriter(key) - - i, err := w.Write(blk1) - if err != nil { - t.Fatal(err) - } - if err = w.(Flusher).Flush(); err != nil { - t.Fatal(err) - } - - if i != len(blk1) { - t.Fatal("write mismatch (%d!=%d)", len(blk1), i) - } - helperCompareBlock(t, "test218bytestream", blk1, ring) - t.Logf("[flushed] compared %d random block key in %q ring: %v", len(blk1), ring.(NamedKeyring).Name(), blk1) -} - -func TestStreamWriterFlush(t *testing.T) { - blk1 := helperRandBlock(218) - - ring, err := SessionKeyring() - if err != nil { - t.Fatal(err) - } - - w, err := CreateWriter("test218bytestream", ring) - if err != nil { - t.Fatal(err) - } - - i, err := w.Write(blk1) - if err != nil { - t.Fatal(err) - } - if err = w.(Flusher).Flush(); err != nil { - t.Fatal(err) - } - - if i != len(blk1) { - t.Fatal("write mismatch (%d!=%d)", len(blk1), i) - } - helperCompareBlock(t, "test218bytestream", blk1, ring) - t.Logf("[flushed] compared %d random block key in common session ring: %v", len(blk1), blk1) -}