Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update acra-keys with symmetric_keys #472

Merged
merged 11 commits into from
Dec 15, 2021
5 changes: 5 additions & 0 deletions CHANGELOG_DEV.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
## 0.91.0 - 2021-12-13
### Changed
- updated `acra-keys` `read` to support work with symmetric storage keys;
- extend `acra-keys` `generate` with `zone_symmetric_key` flag to support rotating zone symmetric keys

## 0.91.0 - 2021-12-01
### Changed
- wrap `acra-censor`'s query writers' manipulations of cached queries with a mutex to avoid race conditions
Expand Down
2 changes: 1 addition & 1 deletion acra-censor/common/logging_logic_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -392,7 +392,7 @@ func TestConcurrentQueryWrite(t *testing.T) {
select {
case <-run:
break
case <-time.NewTimer(time.Millisecond * 100).C:
case <-time.NewTimer(time.Millisecond * 500).C:
t.Fatal("Time out of waiting goroutine start")
}
}
Expand Down
7 changes: 7 additions & 0 deletions cmd/acra-keys/keys/command-line.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,9 @@ const (
KeyZonePublic = "zone-public"
KeyZonePrivate = "zone-private"

KeySymmetric = "symmetric-key"
KeyZoneSymmetric = "symmetric-zone-key"

KeyTransportConnector = "transport-connector"
KeyTransportServer = "transport-server"
KeyTransportTranslator = "transport-translator"
Expand Down Expand Up @@ -169,12 +172,16 @@ func ParseKeyKind(keyID string) (string, []byte, error) {
id := []byte(parts[1])
if parts[0] == "client" {
switch parts[2] {
case "symmetric":
return KeySymmetric, id, nil
case "storage":
return KeyStorageKeypair, id, nil
}
}
if parts[0] == "zone" {
switch parts[2] {
case "symmetric":
return KeyZoneSymmetric, id, nil
case "storage":
return KeyZoneKeypair, id, nil
}
Expand Down
28 changes: 22 additions & 6 deletions cmd/acra-keys/keys/generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,8 @@ import (

"github.com/cossacklabs/acra/cmd"
"github.com/cossacklabs/acra/keystore"
"github.com/cossacklabs/acra/keystore/filesystem"
"github.com/cossacklabs/acra/keystore/keyloader"
keystoreV2 "github.com/cossacklabs/acra/keystore/v2/keystore"
filesystemV2 "github.com/cossacklabs/acra/keystore/v2/keystore/filesystem"
"github.com/cossacklabs/acra/zone"
"github.com/cossacklabs/themis/gothemis/keys"
log "github.com/sirupsen/logrus"
Expand Down Expand Up @@ -57,6 +55,7 @@ type GenerateKeyParams interface {
ZoneID() []byte
GenerateNewZone() bool
GenerateZoneKeys() bool
GenerateZoneSymmetricKey() bool

SpecificKeysRequested() bool
}
Expand Down Expand Up @@ -87,6 +86,7 @@ type GenerateKeySubcommand struct {
acraWriter bool
newZone bool
rotateZone bool
rotateZoneSym bool
acraBlocks bool
auditLog bool
searchHMAC bool
Expand Down Expand Up @@ -169,11 +169,16 @@ func (g *GenerateKeySubcommand) GenerateZoneKeys() bool {
return g.rotateZone
}

// GenerateZoneSymmetricKey returns true if a new sym key for a zone was requested.
func (g *GenerateKeySubcommand) GenerateZoneSymmetricKey() bool {
return g.rotateZoneSym
}

// SpecificKeysRequested returns true if the user has requested any key specifically.
// It returns false if no keys were requested.
func (g *GenerateKeySubcommand) SpecificKeysRequested() bool {
return g.acraConnector || g.acraServer || g.acraTranslator || g.acraWriter || g.newZone ||
g.rotateZone || g.acraBlocks || g.auditLog || g.searchHMAC || g.poisonRecord
g.rotateZone || g.acraBlocks || g.auditLog || g.searchHMAC || g.poisonRecord || g.rotateZoneSym
}

// Name returns the same of this subcommand.
Expand All @@ -200,7 +205,8 @@ func (g *GenerateKeySubcommand) RegisterFlags() {
g.flagSet.BoolVar(&g.acraTranslator, "acratranslator_transport_key", false, "Generate transport keypair for AcraTranslator")
g.flagSet.BoolVar(&g.acraWriter, "client_storage_key", false, "Generate keypair for data encryption/decryption (for a client)")
g.flagSet.BoolVar(&g.newZone, "zone", false, "Generate new Acra storage zone")
g.flagSet.BoolVar(&g.rotateZone, "zone_storage_key", false, "Rotate existing Acra zone storagae keypair")
g.flagSet.BoolVar(&g.rotateZone, "zone_storage_key", false, "Rotate existing Acra zone storage keypair")
g.flagSet.BoolVar(&g.rotateZoneSym, "zone_symmetric_key", false, "Rotate existing Acra zone symmetric key")
g.flagSet.BoolVar(&g.acraBlocks, "client_storage_symmetric_key", false, "Generate symmetric key for data encryption (using AcraBlocks)")
g.flagSet.BoolVar(&g.auditLog, "audit_log_symmetric_key", false, "Generate symmetric key for log integrity checks")
g.flagSet.BoolVar(&g.searchHMAC, "search_hmac_symmetric_key", false, "Generate symmetric key for searchable encryption HMAC")
Expand Down Expand Up @@ -311,9 +317,9 @@ func (g *GenerateKeySubcommand) Execute() {
var err error
keystoreVersion := g.KeystoreVersion()
if keystoreVersion == "" {
if filesystemV2.IsKeyDirectory(g.KeyDir()) {
if IsKeyStoreV2(g) {
keystoreVersion = "v2"
} else if filesystem.IsKeyDirectory(g.KeyDir()) {
} else if IsKeyStoreV1(g) {
keystoreVersion = "v1"
}
}
Expand Down Expand Up @@ -502,6 +508,16 @@ func GenerateAcraKeys(params GenerateKeyParams, keyStore keystore.KeyMaking, def
log.Info("Generated zone storage key")
didSomething = true
}
if params.GenerateZoneSymmetricKey() {
err := keyStore.RotateSymmetricZoneKey(params.ZoneID())
if err != nil {
log.WithError(err).Error("Failed to rotate zone key")
return didSomething, err
}
log.Info("Generated zone storage symmetric key")
didSomething = true
}

if generateAcraBlocks {
err := keyStore.GenerateClientIDSymmetricKey(params.ClientID())
if err != nil {
Expand Down
97 changes: 97 additions & 0 deletions cmd/acra-keys/keys/generate_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
/*
* Copyright 2020, Cossack Labs Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package keys

import (
"bytes"
"encoding/base64"
"fmt"
"io"
"io/ioutil"
"os"
"testing"

"github.com/cossacklabs/acra/keystore"
"github.com/cossacklabs/acra/keystore/keyloader"
log "github.com/sirupsen/logrus"
)

func TestRotateSymmetricZoneKey(t *testing.T) {
zoneID := "DDDDDDDDHCzqZAZNbBvybWLR"
keyLoader := keyloader.NewEnvLoader(keystore.AcraMasterKeyVarName)

masterKey, err := keystore.GenerateSymmetricKey()
if err != nil {
t.Fatal(err)
}

os.Setenv(keystore.AcraMasterKeyVarName, base64.StdEncoding.EncodeToString(masterKey))

dirName, err := ioutil.TempDir("", "")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(dirName)

var keyStore keystore.KeyMaking

generateCmd := &GenerateKeySubcommand{
CommonKeyStoreParameters: CommonKeyStoreParameters{
keyDir: dirName,
},
zoneID: zoneID,
rotateZoneSym: true,
}

keyStore, err = openKeyStoreV1(generateCmd, keyLoader)
if err != nil {
t.Fatal(err)
}

if err := keyStore.GenerateZoneIDSymmetricKey([]byte(zoneID)); err != nil {
log.WithError(err).Errorln("Can't generate symmetric key")
os.Exit(1)
}

zoneKeyPath := fmt.Sprintf("%s/%s_zone_sym", dirName, zoneID)
oldSymKey, err := ioutil.ReadFile(zoneKeyPath)
if err != nil {
t.Fatal("no old symmetric zone key found")
}

generateCmd.Execute()

newSymKey, err := ioutil.ReadFile(zoneKeyPath)
if err != nil {
t.Fatal("no new symmetric zone key found")
}

if bytes.Equal(oldSymKey, newSymKey) {
t.Fatal("same key after rotation error")
}

f, err := os.Open(fmt.Sprintf("%s.old", zoneKeyPath))
if err != nil {
t.Fatal("no backup directory found", err.Error())
}
defer f.Close()

_, err = f.Readdir(1)
if err == io.EOF {
t.Fatal("backup dir is empty")
}
}
40 changes: 37 additions & 3 deletions cmd/acra-keys/keys/keystore.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,14 @@ package keys
import (
"errors"
"flag"
filesystemV2 "github.com/cossacklabs/acra/keystore/v2/keystore/filesystem"

"github.com/cossacklabs/acra/cmd"
"github.com/cossacklabs/acra/keystore"
"github.com/cossacklabs/acra/keystore/filesystem"
"github.com/cossacklabs/acra/keystore/keyloader"
"github.com/cossacklabs/acra/keystore/keyloader/hashicorp"
keystoreV2 "github.com/cossacklabs/acra/keystore/v2/keystore"
"github.com/cossacklabs/acra/keystore/v2/keystore/api"
filesystemV2 "github.com/cossacklabs/acra/keystore/v2/keystore/filesystem"
filesystemBackendV2 "github.com/cossacklabs/acra/keystore/v2/keystore/filesystem/backend"

"github.com/go-redis/redis/v7"
Expand Down Expand Up @@ -248,8 +247,8 @@ func openKeyStoreV2(params KeyStoreParameters, loader keyloader.MasterKeyLoader)

// IsKeyStoreV2 checks if the directory contains a keystore version 2 from KeyStoreParameters
func IsKeyStoreV2(params KeyStoreParameters) bool {
redisOption := params.RedisOptions()
if params.RedisConfigured() {
redisOption := params.RedisOptions()
redisClient, err := filesystemBackendV2.OpenRedisBackend(&filesystemBackendV2.RedisConfig{
RootDir: params.KeyDir(),
Options: &redis.Options{
Expand All @@ -269,3 +268,38 @@ func IsKeyStoreV2(params KeyStoreParameters) bool {
// Otherwise, check the local filesystem storage provided by Acra CE.
return filesystemBackendV2.CheckDirectoryVersion(params.KeyDir()) == nil
}

// IsKeyStoreV1 checks if the directory contains a keystore version 1 from KeyStoreParameters
func IsKeyStoreV1(params KeyStoreParameters) bool {
var fsStorage filesystem.Storage = &filesystem.DummyStorage{}
if params.RedisConfigured() {
redisOption := params.RedisOptions()
redisStorage, err := filesystem.NewRedisStorage(redisOption.Addr, redisOption.Password, redisOption.DB, nil)
if err != nil {
log.WithError(err).Debug("Failed to open redis storage for version check")
return false
}
fsStorage = redisStorage
}

keyDirectory := params.KeyDir()
fi, err := fsStorage.Stat(keyDirectory)
if err != nil {
log.WithError(err).WithField("path", keyDirectory).Debug("Failed to stat key directory for version check")
return false
}
if !fi.IsDir() {
log.WithField("path", keyDirectory).Debug("Key directory is not a directory")
return false
}
files, err := fsStorage.ReadDir(keyDirectory)
if err != nil {
log.WithError(err).WithField("path", keyDirectory).Debug("Failed to read key directory for version check")
return false
}
if len(files) == 0 {
log.WithField("path", keyDirectory).Debug("Key directory is empty")
return false
}
return true
}
29 changes: 27 additions & 2 deletions cmd/acra-keys/keys/read-key.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ var SupportedReadKeyKinds = []string{
KeyStoragePrivate,
KeyZonePublic,
KeyZonePrivate,
KeySymmetric,
KeyZoneSymmetric,
}

// Key parameter errors:
Expand Down Expand Up @@ -112,7 +114,7 @@ func (p *ReadKeySubcommand) Parse(arguments []string) error {
return err
}
switch coarseKind {
case KeyTransportConnector, KeyTransportServer, KeyTransportTranslator:
case KeyTransportConnector, KeyTransportServer, KeyTransportTranslator, KeySymmetric, KeyZoneSymmetric:
p.readKeyKind = coarseKind
p.contextID = id

Expand Down Expand Up @@ -147,7 +149,6 @@ func (p *ReadKeySubcommand) Parse(arguments []string) error {
p.readKeyKind = KeyZonePublic
}
p.contextID = id

default:
return ErrUnknownKeyKind
}
Expand Down Expand Up @@ -242,6 +243,30 @@ func ReadKeyBytes(params ReadKeyParams, keyStore keystore.ServerKeyStore) ([]byt
}
return key.Value, nil

case KeySymmetric:
keys, err := keyStore.GetClientIDSymmetricKeys(params.ClientID())
if err != nil {
log.WithError(err).Error("Cannot read client symmetric key")
return nil, err
}
if len(keys) == 0 {
log.WithError(err).Error("Empty symmetric keys list")
return nil, keystore.ErrKeysNotFound
}
return keys[0], nil

case KeyZoneSymmetric:
keys, err := keyStore.GetZoneIDSymmetricKeys(params.ZoneID())
if err != nil {
log.WithError(err).Error("Cannot read client symmetric key")
return nil, err
}
if len(keys) == 0 {
log.WithError(err).Error("Empty symmetric keys list")
return nil, keystore.ErrKeysNotFound
}
return keys[0], nil

default:
log.WithField("expected", SupportedReadKeyKinds).Errorf("Unknown key kind: %s", kind)
return nil, ErrUnknownKeyKind
Expand Down
Loading