diff --git a/pkg/connector/creategroup.go b/pkg/connector/creategroup.go index 01c544c..e8a3bd8 100644 --- a/pkg/connector/creategroup.go +++ b/pkg/connector/creategroup.go @@ -140,7 +140,38 @@ type groupKeyReleaser interface { ReleaseGroupKey(int) error } +type groupKeyBatchCrypto interface { + WrapRegistrationGroupKey([]string) ([]string, error) +} + func wrapRegistrationGroupKey(ctx context.Context, crypto groupKeyCrypto, pubKeys map[string]line.E2EEPeerPublicKey) (apiMembers []string, keyIDs []int, encryptedKeys []string, err error) { + if err := ctx.Err(); err != nil { + return nil, nil, nil, err + } + if batch, ok := crypto.(groupKeyBatchCrypto); ok { + apiMembers = make([]string, 0, len(pubKeys)) + keyIDs = make([]int, 0, len(pubKeys)) + memberPubKeys := make([]string, 0, len(pubKeys)) + for mid, pk := range pubKeys { + apiMembers = append(apiMembers, mid) + keyIDs = append(keyIDs, pk.KeyID) + memberPubKeys = append(memberPubKeys, pk.KeyData) + } + if err := ctx.Err(); err != nil { + return nil, nil, nil, err + } + encryptedKeys, err = batch.WrapRegistrationGroupKey(memberPubKeys) + if err != nil { + return nil, nil, nil, fmt.Errorf("wrap group key for members: %w", err) + } + if err := ctx.Err(); err != nil { + return nil, nil, nil, err + } + if len(encryptedKeys) != len(apiMembers) { + return nil, nil, nil, fmt.Errorf("wrapped group key count does not match members") + } + return apiMembers, keyIDs, encryptedKeys, nil + } groupKeyID, err := crypto.GenerateGroupKey() if err != nil { return nil, nil, nil, fmt.Errorf("failed to generate group key: %w", err) diff --git a/pkg/e2ee/manager.go b/pkg/e2ee/manager.go index 0d1c251..b17eda6 100644 --- a/pkg/e2ee/manager.go +++ b/pkg/e2ee/manager.go @@ -650,6 +650,33 @@ func (m *Manager) GenerateGroupKey() (int, error) { return m.runner.RegistrationKeyGenerate() } +func (m *Manager) WrapRegistrationGroupKey(memberPubKeys []string) ([]string, error) { + m.mu.Lock() + defer m.mu.Unlock() + if err := m.errLocked(); err != nil { + return nil, err + } + if m.myKeyID == 0 { + return nil, fmt.Errorf("my E2EE key not loaded") + } + channelIDs := make([]int, len(memberPubKeys)) + for i, memberPubKey := range memberPubKeys { + normalized, err := gen.NormalizePeerPublicKeyB64(memberPubKey) + if err != nil { + return nil, fmt.Errorf("invalid member public key: %w", err) + } + channelIDs[i], err = m.runner.ChannelCreate(m.myKeyID, normalized) + if err != nil { + return nil, fmt.Errorf("failed to create ECDH channel: %w", err) + } + } + wrapped, err := m.runner.WrapRegistrationGroupKey(channelIDs) + if err != nil { + return nil, fmt.Errorf("failed to wrap group key: %w", err) + } + return wrapped, nil +} + func (m *Manager) ReleaseGroupKey(keyID int) error { m.mu.Lock() defer m.mu.Unlock() diff --git a/pkg/ltsm/runtime.go b/pkg/ltsm/runtime.go index a0be4f6..114d602 100644 --- a/pkg/ltsm/runtime.go +++ b/pkg/ltsm/runtime.go @@ -379,17 +379,43 @@ func (rt *Runtime) E2EEChannelUnwrapGroupSharedKey(chanPtr uint32, encKey []byte return ptr, nil } -func (rt *Runtime) E2EEChannelWrapGroupSharedKey(chanPtr uint32, keyHandle uint32) ([]byte, error) { - handle, err := rt.imp.CallMethod("E2EEChannel", "wrapGroupSharedKey", chanPtr, keyHandle) - if err != nil { - return nil, fmt.Errorf("ltsm: E2EEChannel.wrapGroupSharedKey failed: %w", err) - } - data, err := rt.imp.ReadEmvalBytes(handle) +func (rt *Runtime) WrapRegistrationGroupKey(chanPtrs []uint32) ([][]byte, error) { + var wrapped [][]byte + defer func() { rt.cryptoDirty = true }() + _, err := rt.channelCrypto(func(crypto *Runtime) ([]byte, error) { + keyPtr, err := crypto.Curve25519KeyGenerate() + if err != nil { + return nil, err + } + wrapped = make([][]byte, len(chanPtrs)) + for i, chanPtr := range chanPtrs { + data, err := crypto.E2EEChannelWrapGroupSharedKey(chanPtr, keyPtr) + if err != nil { + return nil, err + } + wrapped[i] = append([]byte(nil), data...) + } + return nil, nil + }) if err != nil { - return nil, fmt.Errorf("ltsm: failed to read wrapGroupSharedKey result: %w", err) + return nil, err } - rt.imp.emval.DecRef(handle) - return data, nil + return wrapped, nil +} + +func (rt *Runtime) E2EEChannelWrapGroupSharedKey(chanPtr uint32, keyPtr uint32) ([]byte, error) { + return rt.channelCrypto(func(crypto *Runtime) ([]byte, error) { + handle, err := crypto.imp.CallMethod("E2EEChannel", "wrapGroupSharedKey", chanPtr, keyPtr) + if err != nil { + return nil, fmt.Errorf("ltsm: E2EEChannel.wrapGroupSharedKey failed: %w", err) + } + data, err := crypto.imp.ReadEmvalBytes(handle) + if err != nil { + return nil, fmt.Errorf("ltsm: failed to read wrapGroupSharedKey result: %w", err) + } + crypto.imp.emval.DecRef(handle) + return data, nil + }) } func (rt *Runtime) E2EEChannelGenerateConfirmHash(chanPtr uint32, encKeyChain []byte) ([]byte, error) { diff --git a/pkg/runner.go b/pkg/runner.go index 7907480..e633d37 100644 --- a/pkg/runner.go +++ b/pkg/runner.go @@ -1262,10 +1262,35 @@ func (r *Runner) ChannelWrapGroupSharedKey(channelID int, keyID int) (out0 strin if r.failure != nil { return "", r.failure } - defer r.finishOperation("ChannelWrapGroupSharedKey", &err, true, func() { out0 = "" }) + defer r.finishOperation("ChannelWrapGroupSharedKey", &err, false, func() { out0 = "" }) return r.channelWrapGroupSharedKey(channelID, keyID) } +func (r *Runner) WrapRegistrationGroupKey(channelIDs []int) (out0 []string, err error) { + r.mu.Lock() + defer r.mu.Unlock() + if r.failure != nil { + return nil, r.failure + } + defer r.finishOperation("WrapRegistrationGroupKey", &err, false, func() { out0 = nil }) + chanPtrs := make([]uint32, len(channelIDs)) + for i, channelID := range channelIDs { + chanPtrs[i], err = r.getChannel(channelID) + if err != nil { + return nil, err + } + } + wrapped, err := r.rt.WrapRegistrationGroupKey(chanPtrs) + if err != nil { + return nil, err + } + out0 = make([]string, len(wrapped)) + for i, data := range wrapped { + out0[i] = base64.StdEncoding.EncodeToString(data) + } + return out0, nil +} + func (r *Runner) channelWrapGroupSharedKey(channelID int, keyID int) (string, error) { chanPtr, err := r.getChannel(channelID)