refactor: key rotation (#2383)

This commit is contained in:
Mo
2023-08-04 09:25:28 -05:00
committed by GitHub
parent a7f266bb68
commit 494436bdb6
65 changed files with 1354 additions and 1232 deletions

View File

@@ -4,8 +4,8 @@ import { WebSocketsService } from './../Services/Api/WebsocketsService'
import { MigrationService } from './../Services/Migration/MigrationService'
import { LegacyApiService } from './../Services/Api/ApiService'
import { FeaturesService } from '@Lib/Services/Features/FeaturesService'
import { SNPreferencesService } from './../Services/Preferences/PreferencesService'
import { SNProtectionService } from './../Services/Protection/ProtectionService'
import { PreferencesService } from './../Services/Preferences/PreferencesService'
import { ProtectionService } from './../Services/Protection/ProtectionService'
import { SessionManager } from './../Services/Session/SessionManager'
import { HttpService, HttpServiceInterface, UserRegistrationResponseBody } from '@standardnotes/api'
import { ApplicationIdentifier, compareVersions, ProtocolVersion, KeyParamsOrigination } from '@standardnotes/common'
@@ -32,7 +32,7 @@ import {
FeaturesClientInterface,
ItemManagerInterface,
SyncServiceInterface,
UserClientInterface,
UserServiceInterface,
MutatorClientInterface,
StatusServiceInterface,
AlertService,
@@ -74,7 +74,6 @@ import {
VaultUserServiceInterface,
VaultInviteServiceInterface,
NotificationServiceEvent,
VaultServiceEvent,
VaultLockServiceInterface,
} from '@standardnotes/services'
import {
@@ -297,7 +296,7 @@ export class SNApplication implements ApplicationInterface, AppGroupManagedAppli
const uninstall = syncService.addEventObserver(syncEventCallback)
this.serviceObservers.push(uninstall)
const protectionService = this.dependencies.get<SNProtectionService>(TYPES.ProtectionService)
const protectionService = this.dependencies.get<ProtectionService>(TYPES.ProtectionService)
this.serviceObservers.push(
protectionService.addEventObserver((event) => {
if (event === ProtectionEvent.UnprotectedSessionBegan) {
@@ -329,7 +328,7 @@ export class SNApplication implements ApplicationInterface, AppGroupManagedAppli
}),
)
const preferencesService = this.dependencies.get<SNPreferencesService>(TYPES.PreferencesService)
const preferencesService = this.dependencies.get<PreferencesService>(TYPES.PreferencesService)
this.serviceObservers.push(
preferencesService.addEventObserver(() => {
void this.notifyEvent(ApplicationEvent.PreferencesChanged)
@@ -1141,7 +1140,6 @@ export class SNApplication implements ApplicationInterface, AppGroupManagedAppli
this.dependencies.get(TYPES.SharedVaultService),
NotificationServiceEvent.NotificationReceived,
)
this.events.addEventHandler(this.dependencies.get(TYPES.SharedVaultService), VaultServiceEvent.VaultRootKeyRotated)
this.events.addEventHandler(this.dependencies.get(TYPES.SharedVaultService), SyncEvent.ReceivedRemoteSharedVaults)
this.events.addEventHandler(
@@ -1267,8 +1265,8 @@ export class SNApplication implements ApplicationInterface, AppGroupManagedAppli
return this.dependencies.get<SyncServiceInterface>(TYPES.SyncService)
}
public get user(): UserClientInterface {
return this.dependencies.get<UserClientInterface>(TYPES.UserService)
public get user(): UserServiceInterface {
return this.dependencies.get<UserServiceInterface>(TYPES.UserService)
}
public get settings(): SettingsService {

File diff suppressed because it is too large Load Diff

View File

@@ -153,6 +153,9 @@ export const TYPES = {
IsVaultOwner: Symbol.for('IsVaultOwner'),
RemoveItemsFromMemory: Symbol.for('RemoveItemsFromMemory'),
ReencryptTypeAItems: Symbol.for('ReencryptTypeAItems'),
DecryptErroredPayloads: Symbol.for('DecryptErroredPayloads'),
GetKeyPairs: Symbol.for('GetKeyPairs'),
ChangeVaultStorageMode: Symbol.for('ChangeVaultStorageMode'),
// Mappers
SessionStorageMapper: Symbol.for('SessionStorageMapper'),

View File

@@ -1,4 +1,4 @@
import { SNPreferencesService } from '../Preferences/PreferencesService'
import { PreferencesService } from '../Preferences/PreferencesService'
import { GenericItem, Environment, Platform } from '@standardnotes/models'
import {
InternalEventBusInterface,
@@ -68,7 +68,7 @@ describe('featuresService', () => {
features = {} as jest.Mocked<FeaturesService>
prefs = {} as jest.Mocked<SNPreferencesService>
prefs = {} as jest.Mocked<PreferencesService>
prefs.addEventObserver = jest.fn()
alerts = {} as jest.Mocked<AlertService>

View File

@@ -20,7 +20,7 @@ import {
StorageServiceInterface,
SubscriptionManagerInterface,
SyncServiceInterface,
UserClientInterface,
UserServiceInterface,
UserService,
} from '@standardnotes/services'
import { LegacyApiService, SessionManager } from '../Api'
@@ -37,7 +37,7 @@ describe('FeaturesService', () => {
let apiService: LegacyApiServiceInterface
let webSocketsService: WebSocketsService
let settingsService: SettingsClientInterface
let userService: UserClientInterface
let userService: UserServiceInterface
let syncService: SyncServiceInterface
let alertService: AlertService
let sessionManager: SessionsClientInterface

View File

@@ -43,7 +43,7 @@ import {
ItemManagerInterface,
SyncServiceInterface,
SessionsClientInterface,
UserClientInterface,
UserServiceInterface,
SubscriptionManagerInterface,
AccountEvent,
SubscriptionManagerEvent,
@@ -76,7 +76,7 @@ export class FeaturesService
private api: LegacyApiServiceInterface,
sockets: WebSocketsService,
private settings: SettingsClientInterface,
private user: UserClientInterface,
private user: UserServiceInterface,
private sync: SyncServiceInterface,
private alerts: AlertService,
private sessions: SessionsClientInterface,

View File

@@ -14,7 +14,7 @@ import {
MutatorClientInterface,
SyncServiceInterface,
} from '@standardnotes/services'
import { SNProtectionService } from '../Protection'
import { ProtectionService } from '../Protection'
import { ContentType } from '@standardnotes/domain-core'
export class ListedService extends AbstractService implements ListedClientInterface {
@@ -23,7 +23,7 @@ export class ListedService extends AbstractService implements ListedClientInterf
private itemManager: ItemManager,
private settingsService: SettingsService,
private httpSerivce: DeprecatedHttpService,
private protectionService: SNProtectionService,
private protectionService: ProtectionService,
private mutator: MutatorClientInterface,
private sync: SyncServiceInterface,
protected override internalEventBus: InternalEventBusInterface,

View File

@@ -56,6 +56,14 @@ describe('mutator service', () => {
return mutatorService.insertItem(note)
}
describe('insertItem', () => {
it('should throw if attempting to insert already inserted item', async () => {
const note = await insertNote('hello')
expect(mutatorService.insertItem(note)).rejects.toThrow()
})
})
describe('note modifications', () => {
it('pinning should not update timestamps', async () => {
const note = await insertNote('hello')

View File

@@ -358,6 +358,11 @@ export class MutatorService extends AbstractService implements MutatorClientInte
}
public async insertItem<T extends DecryptedItemInterface>(item: DecryptedItemInterface, setDirty = true): Promise<T> {
const existingItem = this.itemManager.findItem<T>(item.uuid)
if (existingItem) {
throw Error('Attempting to insert item that already exists')
}
if (setDirty) {
const mutator = CreateDecryptedMutatorForItem(item, MutationType.UpdateUserTimestamps)
const dirtiedPayload = mutator.getResult()

View File

@@ -17,7 +17,7 @@ import {
} from '@standardnotes/services'
import { ContentType } from '@standardnotes/domain-core'
export class SNPreferencesService
export class PreferencesService
extends AbstractService<PreferencesServiceEvent>
implements PreferenceServiceInterface, InternalEventHandlerInterface
{

View File

@@ -1,6 +1,6 @@
import { ChallengeService } from '../Challenge'
import { DiskStorageService } from '../Storage/DiskStorageService'
import { SNProtectionService } from './ProtectionService'
import { ProtectionService } from './ProtectionService'
import {
InternalEventBus,
InternalEventBusInterface,
@@ -28,10 +28,10 @@ describe('protectionService', () => {
let challengeService: ChallengeService
let storageService: DiskStorageService
let internalEventBus: InternalEventBusInterface
let protectionService: SNProtectionService
let protectionService: ProtectionService
const createService = () => {
return new SNProtectionService(encryptionService, mutator, challengeService, storageService, internalEventBus)
return new ProtectionService(encryptionService, mutator, challengeService, storageService, internalEventBus)
}
const createFile = (name: string, isProtected?: boolean) => {

View File

@@ -74,7 +74,7 @@ export const ProtectionSessionDurations = [
* like viewing a protected note, as well as managing how long that
* authentication should be valid for.
*/
export class SNProtectionService
export class ProtectionService
extends AbstractService<ProtectionEvent>
implements ProtectionsClientInterface, InternalEventHandlerInterface
{

View File

@@ -32,8 +32,9 @@ import {
ApplicationEvent,
ApplicationStageChangedEventPayload,
ApplicationStage,
GetKeyPairs,
} from '@standardnotes/services'
import { Base64String, PkcKeyPair, PureCryptoInterface } from '@standardnotes/sncrypto-common'
import { Base64String, PureCryptoInterface } from '@standardnotes/sncrypto-common'
import {
SessionBody,
ErrorTag,
@@ -103,6 +104,7 @@ export class SessionManager
private sessionStorageMapper: MapperInterface<Session, Record<string, unknown>>,
private legacySessionStorageMapper: MapperInterface<LegacySession, Record<string, unknown>>,
private workspaceIdentifier: string,
private _getKeyPairs: GetKeyPairs,
protected override internalEventBus: InternalEventBusInterface,
) {
super(internalEventBus)
@@ -215,11 +217,15 @@ export class SessionManager
}
public getPublicKey(): string {
return this.encryptionService.getKeyPair().publicKey
const keys = this._getKeyPairs.execute()
return keys.getValue().encryption.publicKey
}
public getSigningPublicKey(): string {
return this.encryptionService.getSigningKeyPair().publicKey
const keys = this._getKeyPairs.execute()
return keys.getValue().signing.publicKey
}
public get userUuid(): string {
@@ -625,15 +631,7 @@ export class SessionManager
newEmail: parameters.newEmail,
})
let oldKeyPair: PkcKeyPair | undefined
let oldSigningKeyPair: PkcKeyPair | undefined
try {
oldKeyPair = this.encryptionService.getKeyPair()
oldSigningKeyPair = this.encryptionService.getSigningKeyPair()
} catch (error) {
void error
}
const oldKeys = this._getKeyPairs.execute()
const processedResponse = await this.processChangeCredentialsResponse(
rawResponse,
@@ -644,13 +642,12 @@ export class SessionManager
if (!isErrorResponse(rawResponse)) {
if (InternalFeatureService.get().isFeatureEnabled(InternalFeature.Vaults)) {
const eventData: UserKeyPairChangedEventData = {
previous:
oldKeyPair && oldSigningKeyPair
? {
encryption: oldKeyPair,
signing: oldSigningKeyPair,
}
: undefined,
previous: !oldKeys.isFailed()
? {
encryption: oldKeys.getValue().encryption,
signing: oldKeys.getValue().signing,
}
: undefined,
current: {
encryption: parameters.newRootKey.encryptionKeyPair,
signing: parameters.newRootKey.signingKeyPair,