More test fixes

This commit is contained in:
mcrakhman 2022-09-26 14:41:50 +02:00 committed by Mikhail Iudin
parent a01d14f541
commit efd7cafd59
No known key found for this signature in database
GPG Key ID: FAAAA8BAABDFF1C0
8 changed files with 78 additions and 87 deletions

View File

@ -7,7 +7,6 @@ import (
"github.com/anytypeio/go-anytype-infrastructure-experiments/app/logger" "github.com/anytypeio/go-anytype-infrastructure-experiments/app/logger"
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/aclrecordproto" "github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/aclrecordproto"
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/common" "github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/common"
"github.com/anytypeio/go-anytype-infrastructure-experiments/util/keys"
"github.com/anytypeio/go-anytype-infrastructure-experiments/util/keys/asymmetric/encryptionkey" "github.com/anytypeio/go-anytype-infrastructure-experiments/util/keys/asymmetric/encryptionkey"
"github.com/anytypeio/go-anytype-infrastructure-experiments/util/keys/asymmetric/signingkey" "github.com/anytypeio/go-anytype-infrastructure-experiments/util/keys/asymmetric/signingkey"
"github.com/anytypeio/go-anytype-infrastructure-experiments/util/keys/symmetric" "github.com/anytypeio/go-anytype-infrastructure-experiments/util/keys/symmetric"
@ -50,6 +49,7 @@ type ACLState struct {
} }
func newACLStateWithKeys( func newACLStateWithKeys(
id string,
signingKey signingkey.PrivKey, signingKey signingkey.PrivKey,
encryptionKey encryptionkey.PrivKey) (*ACLState, error) { encryptionKey encryptionkey.PrivKey) (*ACLState, error) {
identity, err := signingKey.Raw() identity, err := signingKey.Raw()
@ -57,6 +57,7 @@ func newACLStateWithKeys(
return nil, err return nil, err
} }
return &ACLState{ return &ACLState{
id: id,
identity: string(identity), identity: string(identity),
signingKey: signingKey, signingKey: signingKey,
encryptionKey: encryptionKey, encryptionKey: encryptionKey,
@ -67,8 +68,9 @@ func newACLStateWithKeys(
}, nil }, nil
} }
func newACLState(decoder keys.Decoder) *ACLState { func newACLState(id string) *ACLState {
return &ACLState{ return &ACLState{
id: id,
userReadKeys: make(map[uint64]*symmetric.Key), userReadKeys: make(map[uint64]*symmetric.Key),
userStates: make(map[string]*aclrecordproto.ACLUserState), userStates: make(map[string]*aclrecordproto.ACLUserState),
userInvites: make(map[string]*aclrecordproto.ACLUserInvite), userInvites: make(map[string]*aclrecordproto.ACLUserInvite),

View File

@ -2,7 +2,6 @@ package list
import ( import (
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/account" "github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/account"
"github.com/anytypeio/go-anytype-infrastructure-experiments/util/keys"
"github.com/anytypeio/go-anytype-infrastructure-experiments/util/keys/asymmetric/encryptionkey" "github.com/anytypeio/go-anytype-infrastructure-experiments/util/keys/asymmetric/encryptionkey"
"github.com/anytypeio/go-anytype-infrastructure-experiments/util/keys/asymmetric/signingkey" "github.com/anytypeio/go-anytype-infrastructure-experiments/util/keys/asymmetric/signingkey"
) )
@ -10,31 +9,32 @@ import (
type aclStateBuilder struct { type aclStateBuilder struct {
signPrivKey signingkey.PrivKey signPrivKey signingkey.PrivKey
encPrivKey encryptionkey.PrivKey encPrivKey encryptionkey.PrivKey
decoder keys.Decoder id string
} }
func newACLStateBuilderWithIdentity(decoder keys.Decoder, accountData *account.AccountData) *aclStateBuilder { func newACLStateBuilderWithIdentity(accountData *account.AccountData) *aclStateBuilder {
return &aclStateBuilder{ return &aclStateBuilder{
decoder: decoder,
signPrivKey: accountData.SignKey, signPrivKey: accountData.SignKey,
encPrivKey: accountData.EncKey, encPrivKey: accountData.EncKey,
} }
} }
func newACLStateBuilder(decoder keys.Decoder) *aclStateBuilder { func newACLStateBuilder() *aclStateBuilder {
return &aclStateBuilder{ return &aclStateBuilder{}
decoder: decoder,
} }
func (sb *aclStateBuilder) Init(id string) {
sb.id = id
} }
func (sb *aclStateBuilder) Build(records []*ACLRecord) (state *ACLState, err error) { func (sb *aclStateBuilder) Build(records []*ACLRecord) (state *ACLState, err error) {
if sb.encPrivKey != nil && sb.signPrivKey != nil { if sb.encPrivKey != nil && sb.signPrivKey != nil {
state, err = newACLStateWithKeys(sb.signPrivKey, sb.encPrivKey) state, err = newACLStateWithKeys(sb.id, sb.signPrivKey, sb.encPrivKey)
if err != nil { if err != nil {
return return
} }
} else { } else {
state = newACLState(sb.decoder) state = newACLState(sb.id)
} }
for _, rec := range records { for _, rec := range records {
err = state.applyRecord(rec) err = state.applyRecord(rec)

View File

@ -54,7 +54,7 @@ func BuildACLListWithIdentity(acc *account.AccountData, storage storage.ListStor
if err != nil { if err != nil {
return nil, err return nil, err
} }
builder := newACLStateBuilderWithIdentity(acc.Decoder, acc) builder := newACLStateBuilderWithIdentity(acc)
return build(id, builder, newACLRecordBuilder(id, common.NewKeychain()), storage) return build(id, builder, newACLRecordBuilder(id, common.NewKeychain()), storage)
} }
@ -63,7 +63,7 @@ func BuildACLList(decoder keys.Decoder, storage storage.ListStorage) (ACLList, e
if err != nil { if err != nil {
return nil, err return nil, err
} }
return build(id, newACLStateBuilder(decoder), newACLRecordBuilder(id, common.NewKeychain()), storage) return build(id, newACLStateBuilder(), newACLRecordBuilder(id, common.NewKeychain()), storage)
} }
func build(id string, stateBuilder *aclStateBuilder, recBuilder ACLRecordBuilder, storage storage.ListStorage) (list ACLList, err error) { func build(id string, stateBuilder *aclStateBuilder, recBuilder ACLRecordBuilder, storage storage.ListStorage) (list ACLList, err error) {
@ -87,7 +87,7 @@ func build(id string, stateBuilder *aclStateBuilder, recBuilder ACLRecordBuilder
} }
records := []*ACLRecord{record} records := []*ACLRecord{record}
for record.PrevId != "" { for record.PrevId != "" && record.PrevId != id {
rawRecordWithId, err = storage.GetRawRecord(context.Background(), record.PrevId) rawRecordWithId, err = storage.GetRawRecord(context.Background(), record.PrevId)
if err != nil { if err != nil {
return return
@ -99,6 +99,8 @@ func build(id string, stateBuilder *aclStateBuilder, recBuilder ACLRecordBuilder
} }
records = append(records, record) records = append(records, record)
} }
// adding root in the end, because we already parsed it
records = append(records, aclRecRoot)
indexes := make(map[string]int) indexes := make(map[string]int)
for i, j := 0, len(records)-1; i < j; i, j = i+1, j-1 { for i, j := 0, len(records)-1; i < j; i, j = i+1, j-1 {
@ -111,6 +113,7 @@ func build(id string, stateBuilder *aclStateBuilder, recBuilder ACLRecordBuilder
indexes[records[len(records)/2].Id] = len(records) / 2 indexes[records[len(records)/2].Id] = len(records) / 2
} }
stateBuilder.Init(id)
state, err := stateBuilder.Build(records) state, err := stateBuilder.Build(records)
if err != nil { if err != nil {
return return

View File

@ -15,30 +15,30 @@ type SymKey struct {
Key *symmetric.Key Key *symmetric.Key
} }
type Keychain struct { type YAMLKeychain struct {
SigningKeys map[string]signingkey.PrivKey SigningKeysByYAMLIdentity map[string]signingkey.PrivKey
SigningKeysByIdentity map[string]signingkey.PrivKey SigningKeysByRealIdentity map[string]signingkey.PrivKey
EncryptionKeys map[string]encryptionkey.PrivKey EncryptionKeysByYAMLIdentity map[string]encryptionkey.PrivKey
ReadKeys map[string]*SymKey ReadKeysByYAMLIdentity map[string]*SymKey
ReadKeysByHash map[uint64]*SymKey ReadKeysByHash map[uint64]*SymKey
GeneratedIdentities map[string]string GeneratedIdentities map[string]string
DerivedIdentity string DerivedIdentity string
coder signingkey.PubKeyDecoder coder signingkey.PubKeyDecoder
} }
func NewKeychain() *Keychain { func NewKeychain() *YAMLKeychain {
return &Keychain{ return &YAMLKeychain{
SigningKeys: map[string]signingkey.PrivKey{}, SigningKeysByYAMLIdentity: map[string]signingkey.PrivKey{},
SigningKeysByIdentity: map[string]signingkey.PrivKey{}, SigningKeysByRealIdentity: map[string]signingkey.PrivKey{},
EncryptionKeys: map[string]encryptionkey.PrivKey{}, EncryptionKeysByYAMLIdentity: map[string]encryptionkey.PrivKey{},
GeneratedIdentities: map[string]string{}, GeneratedIdentities: map[string]string{},
ReadKeys: map[string]*SymKey{}, ReadKeysByYAMLIdentity: map[string]*SymKey{},
ReadKeysByHash: map[uint64]*SymKey{}, ReadKeysByHash: map[uint64]*SymKey{},
coder: signingkey.NewEd25519PubKeyDecoder(), coder: signingkey.NewEd25519PubKeyDecoder(),
} }
} }
func (k *Keychain) ParseKeys(keys *Keys) { func (k *YAMLKeychain) ParseKeys(keys *Keys) {
k.DerivedIdentity = keys.Derived k.DerivedIdentity = keys.Derived
for _, encKey := range keys.Enc { for _, encKey := range keys.Enc {
k.AddEncryptionKey(encKey) k.AddEncryptionKey(encKey)
@ -53,8 +53,8 @@ func (k *Keychain) ParseKeys(keys *Keys) {
} }
} }
func (k *Keychain) AddEncryptionKey(key *Key) { func (k *YAMLKeychain) AddEncryptionKey(key *Key) {
if _, exists := k.EncryptionKeys[key.Name]; exists { if _, exists := k.EncryptionKeysByYAMLIdentity[key.Name]; exists {
return return
} }
var ( var (
@ -74,11 +74,11 @@ func (k *Keychain) AddEncryptionKey(key *Key) {
} }
newPrivKey = privKey.(encryptionkey.PrivKey) newPrivKey = privKey.(encryptionkey.PrivKey)
} }
k.EncryptionKeys[key.Name] = newPrivKey k.EncryptionKeysByYAMLIdentity[key.Name] = newPrivKey
} }
func (k *Keychain) AddSigningKey(key *Key) { func (k *YAMLKeychain) AddSigningKey(key *Key) {
if _, exists := k.SigningKeys[key.Name]; exists { if _, exists := k.SigningKeysByYAMLIdentity[key.Name]; exists {
return return
} }
var ( var (
@ -101,19 +101,19 @@ func (k *Keychain) AddSigningKey(key *Key) {
pubKey = newPrivKey.GetPublic() pubKey = newPrivKey.GetPublic()
} }
k.SigningKeys[key.Name] = newPrivKey k.SigningKeysByYAMLIdentity[key.Name] = newPrivKey
rawPubKey, err := pubKey.Raw() rawPubKey, err := pubKey.Raw()
if err != nil { if err != nil {
panic(err) panic(err)
} }
encoded := string(rawPubKey) encoded := string(rawPubKey)
k.SigningKeysByIdentity[encoded] = newPrivKey k.SigningKeysByRealIdentity[encoded] = newPrivKey
k.GeneratedIdentities[key.Name] = encoded k.GeneratedIdentities[key.Name] = encoded
} }
func (k *Keychain) AddReadKey(key *Key) { func (k *YAMLKeychain) AddReadKey(key *Key) {
if _, exists := k.ReadKeys[key.Name]; exists { if _, exists := k.ReadKeysByYAMLIdentity[key.Name]; exists {
return return
} }
@ -127,8 +127,8 @@ func (k *Keychain) AddReadKey(key *Key) {
panic("should be able to generate symmetric key") panic("should be able to generate symmetric key")
} }
} else if key.Value == "derived" { } else if key.Value == "derived" {
signKey, _ := k.SigningKeys[k.DerivedIdentity].Raw() signKey, _ := k.SigningKeysByYAMLIdentity[k.DerivedIdentity].Raw()
encKey, _ := k.EncryptionKeys[k.DerivedIdentity].Raw() encKey, _ := k.EncryptionKeysByYAMLIdentity[k.DerivedIdentity].Raw()
rkey, err = aclrecordproto.ACLReadKeyDerive(signKey, encKey) rkey, err = aclrecordproto.ACLReadKeyDerive(signKey, encKey)
if err != nil { if err != nil {
panic("should be able to derive symmetric key") panic("should be able to derive symmetric key")
@ -143,7 +143,7 @@ func (k *Keychain) AddReadKey(key *Key) {
hasher := fnv.New64() hasher := fnv.New64()
hasher.Write(rkey.Bytes()) hasher.Write(rkey.Bytes())
k.ReadKeys[key.Name] = &SymKey{ k.ReadKeysByYAMLIdentity[key.Name] = &SymKey{
Hash: hasher.Sum64(), Hash: hasher.Sum64(),
Key: rkey, Key: rkey,
} }
@ -153,7 +153,7 @@ func (k *Keychain) AddReadKey(key *Key) {
} }
} }
func (k *Keychain) AddKey(key *Key) { func (k *YAMLKeychain) AddKey(key *Key) {
parts := strings.Split(key.Name, ".") parts := strings.Split(key.Name, ".")
if len(parts) != 3 { if len(parts) != 3 {
panic("cannot parse a key") panic("cannot parse a key")
@ -171,7 +171,7 @@ func (k *Keychain) AddKey(key *Key) {
} }
} }
func (k *Keychain) GetKey(key string) interface{} { func (k *YAMLKeychain) GetKey(key string) interface{} {
parts := strings.Split(key, ".") parts := strings.Split(key, ".")
if len(parts) != 3 { if len(parts) != 3 {
panic("cannot parse a key") panic("cannot parse a key")
@ -180,15 +180,15 @@ func (k *Keychain) GetKey(key string) interface{} {
switch parts[1] { switch parts[1] {
case "Sign": case "Sign":
if key, exists := k.SigningKeys[name]; exists { if key, exists := k.SigningKeysByYAMLIdentity[name]; exists {
return key return key
} }
case "Enc": case "Enc":
if key, exists := k.EncryptionKeys[name]; exists { if key, exists := k.EncryptionKeysByYAMLIdentity[name]; exists {
return key return key
} }
case "Read": case "Read":
if key, exists := k.ReadKeys[name]; exists { if key, exists := k.ReadKeysByYAMLIdentity[name]; exists {
return key return key
} }
default: default:
@ -197,6 +197,6 @@ func (k *Keychain) GetKey(key string) interface{} {
return nil return nil
} }
func (k *Keychain) GetIdentity(name string) string { func (k *YAMLKeychain) GetIdentity(name string) string {
return k.GeneratedIdentities[name] return k.GeneratedIdentities[name]
} }

View File

@ -23,13 +23,13 @@ type ACLListStorageBuilder struct {
records []*aclrecordproto.ACLRecord records []*aclrecordproto.ACLRecord
rawRecords []*aclrecordproto.RawACLRecordWithId rawRecords []*aclrecordproto.RawACLRecordWithId
indexes map[string]int indexes map[string]int
keychain *Keychain keychain *YAMLKeychain
rawRoot *aclrecordproto.RawACLRecordWithId rawRoot *aclrecordproto.RawACLRecordWithId
root *aclrecordproto.ACLRoot root *aclrecordproto.ACLRoot
id string id string
} }
func NewACLListStorageBuilder(keychain *Keychain) *ACLListStorageBuilder { func NewACLListStorageBuilder(keychain *YAMLKeychain) *ACLListStorageBuilder {
return &ACLListStorageBuilder{ return &ACLListStorageBuilder{
records: make([]*aclrecordproto.ACLRecord, 0), records: make([]*aclrecordproto.ACLRecord, 0),
indexes: make(map[string]int), indexes: make(map[string]int),
@ -66,7 +66,7 @@ func (t *ACLListStorageBuilder) createRaw(rec proto.Marshaler, identity []byte)
panic("should be able to marshal final acl message!") panic("should be able to marshal final acl message!")
} }
signature, err := t.keychain.SigningKeysByIdentity[string(identity)].Sign(protoMarshalled) signature, err := t.keychain.SigningKeysByRealIdentity[string(identity)].Sign(protoMarshalled)
if err != nil { if err != nil {
panic("should be able to sign final acl message!") panic("should be able to sign final acl message!")
} }
@ -89,17 +89,10 @@ func (t *ACLListStorageBuilder) createRaw(rec proto.Marshaler, identity []byte)
} }
} }
func (t *ACLListStorageBuilder) getRecord(idx int) *aclrecordproto.RawACLRecordWithId {
if idx > 0 {
return t.rawRecords[idx-1]
}
return t.rawRoot
}
func (t *ACLListStorageBuilder) Head() (*aclrecordproto.RawACLRecordWithId, error) { func (t *ACLListStorageBuilder) Head() (*aclrecordproto.RawACLRecordWithId, error) {
l := len(t.records) l := len(t.records)
if l > 1 { if l > 0 {
return t.rawRecords[l-2], nil return t.rawRecords[l-1], nil
} }
return t.rawRoot, nil return t.rawRoot, nil
} }
@ -116,7 +109,7 @@ func (t *ACLListStorageBuilder) GetRawRecord(ctx context.Context, id string) (*a
} }
return nil, fmt.Errorf("no such record") return nil, fmt.Errorf("no such record")
} }
return t.getRecord(recIdx), nil return t.rawRecords[recIdx], nil
} }
func (t *ACLListStorageBuilder) AddRawRecord(ctx context.Context, rec *aclrecordproto.RawACLRecordWithId) error { func (t *ACLListStorageBuilder) AddRawRecord(ctx context.Context, rec *aclrecordproto.RawACLRecordWithId) error {
@ -131,7 +124,7 @@ func (t *ACLListStorageBuilder) GetRawRecords() []*aclrecordproto.RawACLRecordWi
return t.rawRecords return t.rawRecords
} }
func (t *ACLListStorageBuilder) GetKeychain() *Keychain { func (t *ACLListStorageBuilder) GetKeychain() *YAMLKeychain {
return t.keychain return t.keychain
} }
@ -140,7 +133,8 @@ func (t *ACLListStorageBuilder) Parse(tree *YMLList) {
// are specified in the yml file, because our identities should be Ed25519 // are specified in the yml file, because our identities should be Ed25519
// the same thing is happening for the encryption keys // the same thing is happening for the encryption keys
t.keychain.ParseKeys(&tree.Keys) t.keychain.ParseKeys(&tree.Keys)
prevId := "" t.parseRoot(tree.Root)
prevId := t.id
for idx, rec := range tree.Records { for idx, rec := range tree.Records {
newRecord := t.parseRecord(rec, prevId) newRecord := t.parseRecord(rec, prevId)
rawRecord := t.createRaw(newRecord, newRecord.Identity) rawRecord := t.createRaw(newRecord, newRecord.Identity)
@ -149,8 +143,6 @@ func (t *ACLListStorageBuilder) Parse(tree *YMLList) {
t.indexes[rawRecord.Id] = idx t.indexes[rawRecord.Id] = idx
prevId = rawRecord.Id prevId = rawRecord.Id
} }
t.parseRoot(tree.Root)
} }
func (t *ACLListStorageBuilder) parseRecord(rec *Record, prevId string) *aclrecordproto.ACLRecord { func (t *ACLListStorageBuilder) parseRecord(rec *Record, prevId string) *aclrecordproto.ACLRecord {
@ -199,7 +191,7 @@ func (t *ACLListStorageBuilder) parseACLChange(ch *ACLChange) (convCh *aclrecord
GetKey(join.EncryptionKey).(encryptionkey.PrivKey) GetKey(join.EncryptionKey).(encryptionkey.PrivKey)
rawKey, _ := encKey.GetPublic().Raw() rawKey, _ := encKey.GetPublic().Raw()
idKey, _ := t.keychain.SigningKeys[join.Identity].GetPublic().Raw() idKey, _ := t.keychain.SigningKeysByYAMLIdentity[join.Identity].GetPublic().Raw()
signKey := t.keychain.GetKey(join.AcceptSignature).(signingkey.PrivKey) signKey := t.keychain.GetKey(join.AcceptSignature).(signingkey.PrivKey)
signature, err := signKey.Sign(idKey) signature, err := signKey.Sign(idKey)
if err != nil { if err != nil {
@ -253,15 +245,14 @@ func (t *ACLListStorageBuilder) parseACLChange(ch *ACLChange) (convCh *aclrecord
var replaces []*aclrecordproto.ACLReadKeyReplace var replaces []*aclrecordproto.ACLReadKeyReplace
for _, id := range remove.IdentitiesLeft { for _, id := range remove.IdentitiesLeft {
identity := t.keychain.GetIdentity(id) encKey := t.keychain.EncryptionKeysByYAMLIdentity[id]
encKey := t.keychain.EncryptionKeys[id]
rawEncKey, _ := encKey.GetPublic().Raw() rawEncKey, _ := encKey.GetPublic().Raw()
encReadKey, err := encKey.GetPublic().Encrypt(newReadKey.Key.Bytes()) encReadKey, err := encKey.GetPublic().Encrypt(newReadKey.Key.Bytes())
if err != nil { if err != nil {
panic(err) panic(err)
} }
replaces = append(replaces, &aclrecordproto.ACLReadKeyReplace{ replaces = append(replaces, &aclrecordproto.ACLReadKeyReplace{
Identity: []byte(identity), Identity: []byte(t.keychain.GetIdentity(id)),
EncryptionKey: rawEncKey, EncryptionKey: rawEncKey,
EncryptedReadKey: encReadKey, EncryptedReadKey: encReadKey,
}) })
@ -320,8 +311,8 @@ func (t *ACLListStorageBuilder) traverseFromHead(f func(rec *aclrecordproto.ACLR
} }
func (t *ACLListStorageBuilder) parseRoot(root *Root) { func (t *ACLListStorageBuilder) parseRoot(root *Root) {
rawSignKey, _ := t.keychain.GetKey(root.Identity).(signingkey.PrivKey).GetPublic().Raw() rawSignKey, _ := t.keychain.SigningKeysByYAMLIdentity[root.Identity].GetPublic().Raw()
rawEncKey, _ := t.keychain.GetKey(root.EncryptionKey).(encryptionkey.PrivKey).GetPublic().Raw() rawEncKey, _ := t.keychain.EncryptionKeysByYAMLIdentity[root.Identity].GetPublic().Raw()
readKey, _ := aclrecordproto.ACLReadKeyDerive(rawSignKey, rawEncKey) readKey, _ := aclrecordproto.ACLReadKeyDerive(rawSignKey, rawEncKey)
hasher := fnv.New64() hasher := fnv.New64()
hasher.Write(readKey.Bytes()) hasher.Write(readKey.Bytes())
@ -334,7 +325,5 @@ func (t *ACLListStorageBuilder) parseRoot(root *Root) {
CurrentReadKeyHash: hasher.Sum64(), CurrentReadKeyHash: hasher.Sum64(),
} }
t.rawRoot = t.createRaw(t.root, rawSignKey) t.rawRoot = t.createRaw(t.root, rawSignKey)
bytes, _ := t.rawRoot.Marshal() t.id = t.rawRoot.Id
id, _ := cid.NewCIDFromBytes(bytes)
t.id = id
} }

View File

@ -61,7 +61,6 @@ type Header struct {
type Root struct { type Root struct {
Identity string `yaml:"identity"` Identity string `yaml:"identity"`
EncryptionKey string `yaml:"encryptionKey"`
SpaceId string `yaml:"spaceId"` SpaceId string `yaml:"spaceId"`
} }

View File

@ -1,6 +1,5 @@
root: root:
- identity: A identity: A
encryptionKey: key.Enc.A
spaceId: space spaceId: space
records: records:
- identity: A - identity: A

View File

@ -1,6 +1,5 @@
root: root:
- identity: A identity: A
encryptionKey: key.Enc.A
spaceId: space spaceId: space
records: records:
- identity: A - identity: A