diff --git a/cmd/main.go b/cmd/main.go index 93ad9d5..d7f97f1 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -32,6 +32,7 @@ var ( } trustStoreDir string + trustStoreKeyring string noSpec string minimumSecurityLevel = 0 defaultSymmetricKeySize = 0 @@ -50,7 +51,10 @@ func main() { } rootCmd.PersistentFlags().StringVarP(&trustStoreDir, "tsdir", "d", "", - "specify a truststore directory (default loaded from JESS_TSDIR env variable)", + "specify a truststore directory (default loaded from JESS_TS_DIR env variable)", + ) + rootCmd.PersistentFlags().StringVarP(&trustStoreDir, "tskeyring", "k", "", + "specify a truststore keyring namespace (default loaded from JESS_TS_KEYRING env variable) - lower priority than tsdir", ) rootCmd.PersistentFlags().StringVarP(&noSpec, "no", "n", "", "remove requirements using the abbreviations C, I, R, S", @@ -67,17 +71,36 @@ func main() { } func initGlobalFlags(cmd *cobra.Command, args []string) (err error) { - // trust store + // trust store directory if trustStoreDir == "" { - trustStoreDir, _ = os.LookupEnv("JESS_TSDIR") + trustStoreDir, _ = os.LookupEnv("JESS_TS_DIR") + if trustStoreDir == "" { + trustStoreDir, _ = os.LookupEnv("JESS_TSDIR") + } } if trustStoreDir != "" { - var err error trustStore, err = truststores.NewDirTrustStore(trustStoreDir) if err != nil { return err } } + + // trust store keyring + if trustStore == nil { + if trustStoreKeyring == "" { + trustStoreKeyring, _ = os.LookupEnv("JESS_TS_KEYRING") + if trustStoreKeyring == "" { + trustStoreKeyring, _ = os.LookupEnv("JESS_TSKEYRING") + } + } + if trustStoreKeyring != "" { + trustStore, err = truststores.NewKeyringTrustStore(trustStoreKeyring) + if err != nil { + return err + } + } + } + // requirements if noSpec != "" { requirements, err = jess.ParseRequirementsFromNoSpec(noSpec) diff --git a/truststores/extended.go b/truststores/extended.go index 06c41b3..5d41e6c 100644 --- a/truststores/extended.go +++ b/truststores/extended.go @@ -1,9 +1,15 @@ package truststores import ( + "errors" + "github.com/safing/jess" ) +// ErrNotSupportedByTrustStore is returned by trust stores if they do not +// support certain actions. +var ErrNotSupportedByTrustStore = errors.New("action not supported by trust store") + // ExtendedTrustStore holds a set of trusted Signets, Recipients and Envelopes. type ExtendedTrustStore interface { jess.TrustStore diff --git a/truststores/keyring.go b/truststores/keyring.go new file mode 100644 index 0000000..1132b53 --- /dev/null +++ b/truststores/keyring.go @@ -0,0 +1,148 @@ +package truststores + +import ( + "errors" + "fmt" + + "github.com/zalando/go-keyring" + + "github.com/safing/jess" +) + +const ( + keyringServiceNamePrefix = "jess:" + + keyringSelfcheckKey = "_selfcheck" + keyringSelfcheckValue = "!selfcheck" +) + +// KeyringTrustStore is a trust store that uses the system keyring. +// It does not support listing entries, so it cannot be easily managed. +type KeyringTrustStore struct { + serviceName string +} + +// NewKeyringTrustStore returns a new keyring trust store with the given service name. +// The effect of the service name depends on the operating system. +// Read more at https://pkg.go.dev/github.com/zalando/go-keyring +func NewKeyringTrustStore(serviceName string) (*KeyringTrustStore, error) { + krts := &KeyringTrustStore{ + serviceName: keyringServiceNamePrefix + serviceName, + } + + // Run a self-check. + err := keyring.Set(krts.serviceName, keyringSelfcheckKey, keyringSelfcheckValue) + if err != nil { + return nil, err + } + selfcheckReturn, err := keyring.Get(krts.serviceName, keyringSelfcheckKey) + if err != nil { + return nil, err + } + if selfcheckReturn != keyringSelfcheckValue { + return nil, errors.New("keyring is faulty") + } + + return krts, nil +} + +// GetSignet returns the Signet with the given ID. +func (krts *KeyringTrustStore) GetSignet(id string, recipient bool) (*jess.Signet, error) { + // Build ID. + if recipient { + id += recipientSuffix + } else { + id += signetSuffix + } + + // Get data from keyring. + data, err := keyring.Get(krts.serviceName, id) + if err != nil { + return nil, fmt.Errorf("%w: %s", jess.ErrSignetNotFound, err) + } + + // Parse and return. + return jess.SignetFromBase58(data) +} + +// StoreSignet stores a Signet. +func (krts *KeyringTrustStore) StoreSignet(signet *jess.Signet) error { + // Build ID. + var id string + if signet.Public { + id = signet.ID + recipientSuffix + } else { + id = signet.ID + signetSuffix + } + + // Serialize. + data, err := signet.ToBase58() + if err != nil { + return err + } + + // Save to keyring. + return keyring.Set(krts.serviceName, id, data) +} + +// DeleteSignet deletes the Signet or Recipient with the given ID. +func (krts *KeyringTrustStore) DeleteSignet(id string, recipient bool) error { + // Build ID. + if recipient { + id += recipientSuffix + } else { + id += signetSuffix + } + + // Delete from keyring. + return keyring.Delete(krts.serviceName, id) +} + +// SelectSignets returns a selection of the signets in the trust store. Results are filtered by tool/algorithm and whether it you're looking for a signet (private key) or a recipient (public key). +func (krts *KeyringTrustStore) SelectSignets(filter uint8, schemes ...string) ([]*jess.Signet, error) { + return nil, ErrNotSupportedByTrustStore +} + +// GetEnvelope returns the Envelope with the given name. +func (krts *KeyringTrustStore) GetEnvelope(name string) (*jess.Envelope, error) { + // Build ID. + name += envelopeSuffix + + // Get data from keyring. + data, err := keyring.Get(krts.serviceName, name) + if err != nil { + return nil, fmt.Errorf("%w: %s", jess.ErrEnvelopeNotFound, err) + } + + // Parse and return. + return jess.EnvelopeFromBase58(data) +} + +// StoreEnvelope stores an Envelope. +func (krts *KeyringTrustStore) StoreEnvelope(envelope *jess.Envelope) error { + // Build ID. + name := envelope.Name + envelopeSuffix + + // Serialize. + data, err := envelope.ToBase58() + if err != nil { + return err + } + + // Save to keyring. + return keyring.Set(krts.serviceName, name, data) +} + +// DeleteEnvelope deletes the Envelope with the given name. +func (krts *KeyringTrustStore) DeleteEnvelope(name string) error { + // Build ID. + name += envelopeSuffix + + // Delete from keyring. + return keyring.Delete(krts.serviceName, name) +} + +// AllEnvelopes returns all envelopes. +func (krts *KeyringTrustStore) AllEnvelopes() ([]*jess.Envelope, error) { + return nil, ErrNotSupportedByTrustStore +}