internal: incomplete vault systems behind feature flag (#2340)

This commit is contained in:
Mo
2023-06-30 09:01:56 -05:00
committed by GitHub
parent d16e401bb9
commit b032eb9c9b
638 changed files with 20321 additions and 4813 deletions

View File

@@ -26,7 +26,6 @@
"build:snjs": "yarn workspaces foreach -p --topological-dev --verbose -R --from @standardnotes/snjs run build",
"build:services": "yarn workspaces foreach -pt --topological-dev --verbose -R --from @standardnotes/services run build",
"build:api": "yarn workspaces foreach -pt --topological-dev --verbose -R --from @standardnotes/api run build",
"start:server:web": "lerna run start --scope=@standardnotes/web",
"e2e": "lerna run start:test-server --scope=@standardnotes/snjs",
"reset": "find . -type dir -name node_modules | xargs rm -rf && rm -rf yarn.lock && yarn install",
"release:prod": "lerna version --conventional-commits --yes -m \"chore(release): publish\"",

View File

@@ -36,7 +36,7 @@
"typescript": "*"
},
"dependencies": {
"@standardnotes/common": "^1.46.6",
"@standardnotes/common": "^1.48.3",
"@standardnotes/domain-core": "^1.12.0",
"@standardnotes/encryption": "workspace:*",
"@standardnotes/models": "workspace:*",

View File

@@ -1,14 +1,14 @@
import { ErrorMessage } from '../../Error/ErrorMessage'
import { ApiCallError } from '../../Error/ApiCallError'
import { ApiVersion } from '../../Api/ApiVersion'
import { ApiEndpointParam } from '../../Request/ApiEndpointParam'
import { SubscriptionServerInterface } from '../../Server/Subscription/SubscriptionServerInterface'
import { AppleIAPConfirmResponseBody } from './../../Response/Subscription/AppleIAPConfirmResponseBody'
import { SubscriptionInviteAcceptResponseBody } from '../../Response/Subscription/SubscriptionInviteAcceptResponseBody'
import { SubscriptionInviteCancelResponseBody } from '../../Response/Subscription/SubscriptionInviteCancelResponseBody'
import { SubscriptionInviteListResponseBody } from '../../Response/Subscription/SubscriptionInviteListResponseBody'
import { SubscriptionInviteResponseBody } from '../../Response/Subscription/SubscriptionInviteResponseBody'
import { HttpResponse } from '@standardnotes/responses'
import { HttpResponse, ApiEndpointParam } from '@standardnotes/responses'
import { SubscriptionApiServiceInterface } from './SubscriptionApiServiceInterface'
import { SubscriptionApiOperations } from './SubscriptionApiOperations'

View File

@@ -2,4 +2,5 @@ export enum UserApiOperations {
Registering,
SubmittingRequest,
DeletingAccount,
UpdatingUser,
}

View File

@@ -5,8 +5,7 @@ import { ErrorMessage } from '../../Error/ErrorMessage'
import { ApiCallError } from '../../Error/ApiCallError'
import { UserServerInterface } from '../../Server/User/UserServerInterface'
import { ApiVersion } from '../../Api/ApiVersion'
import { ApiEndpointParam } from '../../Request/ApiEndpointParam'
import { HttpResponse } from '@standardnotes/responses'
import { HttpResponse, ApiEndpointParam } from '@standardnotes/responses'
import { UserDeletionResponseBody } from '../../Response/User/UserDeletionResponseBody'
import { UserRegistrationResponseBody } from '../../Response/User/UserRegistrationResponseBody'
@@ -15,6 +14,7 @@ import { UserRequestServerInterface } from '../../Server/UserRequest/UserRequest
import { UserApiOperations } from './UserApiOperations'
import { UserApiServiceInterface } from './UserApiServiceInterface'
import { UserUpdateResponse } from '../../Response/User/UserUpdateResponse'
export class UserApiService implements UserApiServiceInterface {
private operationsInProgress: Map<UserApiOperations, boolean>
@@ -35,7 +35,7 @@ export class UserApiService implements UserApiServiceInterface {
return response
} catch (error) {
throw new ApiCallError(ErrorMessage.GenericRegistrationFail)
throw new ApiCallError(ErrorMessage.GenericFail)
}
}
@@ -84,6 +84,23 @@ export class UserApiService implements UserApiServiceInterface {
}
}
async updateUser(updateDTO: { userUuid: string }): Promise<HttpResponse<UserUpdateResponse>> {
this.lockOperation(UserApiOperations.UpdatingUser)
try {
const response = await this.userServer.update({
[ApiEndpointParam.ApiVersion]: ApiVersion.v0,
user_uuid: updateDTO.userUuid,
})
this.unlockOperation(UserApiOperations.UpdatingUser)
return response
} catch (error) {
throw new ApiCallError(ErrorMessage.GenericFail)
}
}
private lockOperation(operation: UserApiOperations): void {
if (this.operationsInProgress.get(operation)) {
throw new ApiCallError(ErrorMessage.GenericInProgress)

View File

@@ -5,6 +5,7 @@ import { HttpResponse } from '@standardnotes/responses'
import { UserDeletionResponseBody } from '../../Response/User/UserDeletionResponseBody'
import { UserRegistrationResponseBody } from '../../Response/User/UserRegistrationResponseBody'
import { UserRequestResponseBody } from '../../Response/UserRequest/UserRequestResponseBody'
import { UserUpdateResponse } from '../../Response/User/UserUpdateResponse'
export interface UserApiServiceInterface {
register(registerDTO: {
@@ -13,9 +14,12 @@ export interface UserApiServiceInterface {
keyParams: RootKeyParamsInterface
ephemeral: boolean
}): Promise<HttpResponse<UserRegistrationResponseBody>>
updateUser(updateDTO: { userUuid: string }): Promise<HttpResponse<UserUpdateResponse>>
submitUserRequest(dto: {
userUuid: string
requestType: UserRequestType
}): Promise<HttpResponse<UserRequestResponseBody>>
deleteAccount(userUuid: string): Promise<HttpResponse<UserDeletionResponseBody>>
}

View File

@@ -53,7 +53,15 @@ export class HttpService implements HttpServiceInterface {
this.host = host
}
getHost(): string {
return this.host
}
async get<T>(path: string, params?: HttpRequestParams, authentication?: string): Promise<HttpResponse<T>> {
if (!this.host) {
throw new Error('Attempting to make network request before host is set')
}
return this.runHttp({
url: joinPaths(this.host, path),
params,
@@ -62,7 +70,20 @@ export class HttpService implements HttpServiceInterface {
})
}
async getExternal<T>(url: string, params?: HttpRequestParams): Promise<HttpResponse<T>> {
return this.runHttp({
url,
params,
verb: HttpVerb.Get,
external: true,
})
}
async post<T>(path: string, params?: HttpRequestParams, authentication?: string): Promise<HttpResponse<T>> {
if (!this.host) {
throw new Error('Attempting to make network request before host is set')
}
return this.runHttp({
url: joinPaths(this.host, path),
params,

View File

@@ -3,8 +3,10 @@ import { HttpRequest, HttpRequestParams, HttpResponse, HttpResponseMeta } from '
export interface HttpServiceInterface {
setHost(host: string): void
getHost(): string
setSession(session: Session): void
get<T>(path: string, params?: HttpRequestParams, authentication?: string): Promise<HttpResponse<T>>
getExternal<T>(url: string, params?: HttpRequestParams): Promise<HttpResponse<T>>
post<T>(path: string, params?: HttpRequestParams, authentication?: string): Promise<HttpResponse<T>>
put<T>(path: string, params?: HttpRequestParams, authentication?: string): Promise<HttpResponse<T>>
patch<T>(path: string, params: HttpRequestParams, authentication?: string): Promise<HttpResponse<T>>

View File

@@ -1,7 +0,0 @@
export enum ApiEndpointParam {
LastSyncToken = 'sync_token',
PaginationToken = 'cursor_token',
SyncDlLimit = 'limit',
SyncPayloads = 'items',
ApiVersion = 'api',
}

View File

@@ -0,0 +1,5 @@
export type CreateAsymmetricMessageParams = {
recipientUuid: string
encryptedMessage: string
replaceabilityIdentifier?: string
}

View File

@@ -0,0 +1,3 @@
export type DeleteAsymmetricMessageRequestParams = {
messageUuid: string
}

View File

@@ -0,0 +1,2 @@
// eslint-disable-next-line @typescript-eslint/ban-types
export type GetOutboundAsymmetricMessagesRequestParams = {}

View File

@@ -0,0 +1,2 @@
// eslint-disable-next-line @typescript-eslint/ban-types
export type GetUserAsymmetricMessagesRequestParams = {}

View File

@@ -0,0 +1,4 @@
export type UpdateAsymmetricMessageParams = {
messageUuid: string
encryptedMessage: string
}

View File

@@ -0,0 +1,12 @@
import { ValetTokenOperation } from '@standardnotes/responses'
import { SharedVaultMoveType } from './SharedVaultMoveType'
export type CreateSharedVaultValetTokenParams = {
sharedVaultUuid: string
fileUuid?: string
remoteIdentifier: string
operation: ValetTokenOperation
unencryptedFileSize?: number
moveOperationType?: SharedVaultMoveType
sharedVaultToSharedVaultMoveTargetUuid?: string
}

View File

@@ -0,0 +1 @@
export type SharedVaultMoveType = 'shared-vault-to-user' | 'user-to-shared-vault' | 'shared-vault-to-shared-vault'

View File

@@ -0,0 +1,4 @@
export type AcceptInviteRequestParams = {
sharedVaultUuid: string
inviteUuid: string
}

View File

@@ -0,0 +1,8 @@
import { SharedVaultPermission } from '@standardnotes/responses'
export type CreateSharedVaultInviteParams = {
sharedVaultUuid: string
recipientUuid: string
encryptedMessage: string
permissions: SharedVaultPermission
}

View File

@@ -0,0 +1,4 @@
export type DeclineInviteRequestParams = {
sharedVaultUuid: string
inviteUuid: string
}

View File

@@ -0,0 +1,3 @@
export type DeleteAllSharedVaultInvitesRequestParams = {
sharedVaultUuid: string
}

View File

@@ -0,0 +1,4 @@
export type DeleteInviteRequestParams = {
sharedVaultUuid: string
inviteUuid: string
}

View File

@@ -0,0 +1,2 @@
// eslint-disable-next-line @typescript-eslint/ban-types
export type GetOutboundUserInvitesRequestParams = {}

View File

@@ -0,0 +1,3 @@
export type GetSharedVaultInvitesRequestParams = {
sharedVaultUuid: string
}

View File

@@ -0,0 +1,2 @@
// eslint-disable-next-line @typescript-eslint/ban-types
export type GetUserInvitesRequestParams = {}

View File

@@ -0,0 +1,8 @@
import { SharedVaultPermission } from '@standardnotes/responses'
export type UpdateSharedVaultInviteParams = {
sharedVaultUuid: string
inviteUuid: string
encryptedMessage: string
permissions?: SharedVaultPermission
}

View File

@@ -0,0 +1,4 @@
export type DeleteSharedVaultUserRequestParams = {
sharedVaultUuid: string
userUuid: string
}

View File

@@ -0,0 +1,3 @@
export type GetSharedVaultUsersRequestParams = {
sharedVaultUuid: string
}

View File

@@ -1,4 +1,4 @@
import { ApiEndpointParam } from '../ApiEndpointParam'
import { ApiEndpointParam } from '@standardnotes/responses'
import { ApiVersion } from '../../Api/ApiVersion'
export type SubscriptionInviteCancelRequestParams = {

View File

@@ -1,4 +1,4 @@
import { ApiEndpointParam } from '../ApiEndpointParam'
import { ApiEndpointParam } from '@standardnotes/responses'
import { ApiVersion } from '../../Api/ApiVersion'
export type SubscriptionInviteDeclineRequestParams = {

View File

@@ -1,4 +1,4 @@
import { ApiEndpointParam } from '../ApiEndpointParam'
import { ApiEndpointParam } from '@standardnotes/responses'
import { ApiVersion } from '../../Api/ApiVersion'
export type SubscriptionInviteListRequestParams = {

View File

@@ -1,4 +1,4 @@
import { ApiEndpointParam } from '../ApiEndpointParam'
import { ApiEndpointParam } from '@standardnotes/responses'
import { ApiVersion } from '../../Api/ApiVersion'
export type SubscriptionInviteRequestParams = {

View File

@@ -1,13 +1,11 @@
import { AnyKeyParamsContent } from '@standardnotes/common'
import { ApiEndpointParam } from '../ApiEndpointParam'
import { ApiEndpointParam } from '@standardnotes/responses'
import { ApiVersion } from '../../Api/ApiVersion'
export type UserRegistrationRequestParams = AnyKeyParamsContent & {
[ApiEndpointParam.ApiVersion]: ApiVersion.v0
[additionalParam: string]: unknown
password: string
email: string
ephemeral: boolean
[additionalParam: string]: unknown
pkcPublicKey?: string
pkcEncryptedPrivateKey?: string
}

View File

@@ -0,0 +1,7 @@
import { ApiEndpointParam } from '@standardnotes/responses'
import { ApiVersion } from '../../Api/ApiVersion'
export type UserUpdateRequestParams = {
[ApiEndpointParam.ApiVersion]: ApiVersion.v0
user_uuid: string
}

View File

@@ -1,4 +1,3 @@
export * from './ApiEndpointParam'
export * from './Authenticator/DeleteAuthenticatorRequestParams'
export * from './Authenticator/GenerateAuthenticatorAuthenticationOptionsRequestParams'
export * from './Authenticator/ListAuthenticatorsRequestParams'
@@ -17,3 +16,4 @@ export * from './Subscription/SubscriptionInviteRequestParams'
export * from './User/UserRegistrationRequestParams'
export * from './UserRequest/UserRequestRequestParams'
export * from './WebSocket/WebSocketConnectionTokenRequestParams'
export * from './SharedVault/SharedVaultMoveType'

View File

@@ -0,0 +1,5 @@
import { AsymmetricMessageServerHash } from '@standardnotes/responses'
export type CreateAsymmetricMessageResponse = {
message: AsymmetricMessageServerHash
}

View File

@@ -0,0 +1,3 @@
export type DeleteAsymmetricMessageResponse = {
success: boolean
}

View File

@@ -0,0 +1,5 @@
import { AsymmetricMessageServerHash } from '@standardnotes/responses'
export type GetOutboundAsymmetricMessagesResponse = {
messages: AsymmetricMessageServerHash[]
}

View File

@@ -0,0 +1,5 @@
import { AsymmetricMessageServerHash } from '@standardnotes/responses'
export type GetUserAsymmetricMessagesResponse = {
messages: AsymmetricMessageServerHash[]
}

View File

@@ -0,0 +1,5 @@
import { AsymmetricMessageServerHash } from '@standardnotes/responses'
export type UpdateAsymmetricMessageResponse = {
message: AsymmetricMessageServerHash
}

View File

@@ -7,6 +7,9 @@ export interface GetRevisionResponseBody {
items_key_id: string | null
enc_item_key: string | null
auth_hash: string | null
user_uuid: string
key_system_identifier: string | null
shared_vault_uuid: string | null
created_at: string
updated_at: string
}

View File

@@ -0,0 +1,6 @@
import { SharedVaultUserServerHash, SharedVaultServerHash } from '@standardnotes/responses'
export type CreateSharedVaultResponse = {
sharedVault: SharedVaultServerHash
sharedVaultUser: SharedVaultUserServerHash
}

View File

@@ -0,0 +1,3 @@
export type CreateSharedVaultValetTokenResponse = {
valetToken: string
}

View File

@@ -0,0 +1,5 @@
import { SharedVaultServerHash } from '@standardnotes/responses'
export type GetSharedVaultsResponse = {
sharedVaults: SharedVaultServerHash[]
}

View File

@@ -0,0 +1,3 @@
export type AcceptInviteResponse = {
success: boolean
}

View File

@@ -0,0 +1,5 @@
import { SharedVaultInviteServerHash } from '@standardnotes/responses'
export type CreateSharedVaultInviteResponse = {
invite: SharedVaultInviteServerHash
}

View File

@@ -0,0 +1,3 @@
export type DeclineInviteResponse = {
success: boolean
}

View File

@@ -0,0 +1,3 @@
export type DeleteAllSharedVaultInvitesResponse = {
success: boolean
}

View File

@@ -0,0 +1,3 @@
export type DeleteInviteResponse = {
success: boolean
}

View File

@@ -0,0 +1,5 @@
import { SharedVaultInviteServerHash } from '@standardnotes/responses'
export type GetOutboundUserInvitesResponse = {
invites: SharedVaultInviteServerHash[]
}

View File

@@ -0,0 +1,5 @@
import { SharedVaultInviteServerHash } from '@standardnotes/responses'
export type GetSharedVaultInvitesResponse = {
invites: SharedVaultInviteServerHash[]
}

View File

@@ -0,0 +1,5 @@
import { SharedVaultInviteServerHash } from '@standardnotes/responses'
export type GetUserInvitesResponse = {
invites: SharedVaultInviteServerHash[]
}

View File

@@ -0,0 +1,5 @@
import { SharedVaultInviteServerHash } from '@standardnotes/responses'
export type UpdateSharedVaultInviteResponse = {
invite: SharedVaultInviteServerHash
}

View File

@@ -0,0 +1,3 @@
export type DeleteSharedVaultUserResponse = {
success: boolean
}

View File

@@ -0,0 +1,5 @@
import { SharedVaultUserServerHash } from '@standardnotes/responses'
export type GetSharedVaultUsersResponse = {
users: SharedVaultUserServerHash[]
}

View File

@@ -0,0 +1,6 @@
export type UserUpdateResponse = {
user: {
uuid: string
email: string
}
}

View File

@@ -16,7 +16,9 @@ export * from './Subscription/SubscriptionInviteCancelResponseBody'
export * from './Subscription/SubscriptionInviteDeclineResponseBody'
export * from './Subscription/SubscriptionInviteListResponseBody'
export * from './Subscription/SubscriptionInviteResponseBody'
export * from './User/UserDeletionResponseBody'
export * from './User/UserRegistrationResponseBody'
export * from './UserRequest/UserRequestResponseBody'
export * from './WebSocket/WebSocketConnectionTokenResponseBody'

View File

@@ -0,0 +1,41 @@
import { HttpResponse } from '@standardnotes/responses'
import { HttpServiceInterface } from '../../Http'
import { CreateAsymmetricMessageParams } from '../../Request/AsymmetricMessage/CreateAsymmetricMessageParams'
import { CreateAsymmetricMessageResponse } from '../../Response/AsymmetricMessage/CreateAsymmetricMessageResponse'
import { AsymmetricMessagesPaths } from './Paths'
import { GetUserAsymmetricMessagesResponse } from '../../Response/AsymmetricMessage/GetUserAsymmetricMessagesResponse'
import { AsymmetricMessageServerInterface } from './AsymmetricMessageServerInterface'
import { DeleteAsymmetricMessageRequestParams } from '../../Request/AsymmetricMessage/DeleteAsymmetricMessageRequestParams'
import { DeleteAsymmetricMessageResponse } from '../../Response/AsymmetricMessage/DeleteAsymmetricMessageResponse'
export class AsymmetricMessageServer implements AsymmetricMessageServerInterface {
constructor(private httpService: HttpServiceInterface) {}
createMessage(params: CreateAsymmetricMessageParams): Promise<HttpResponse<CreateAsymmetricMessageResponse>> {
return this.httpService.post(AsymmetricMessagesPaths.createMessage, {
recipient_uuid: params.recipientUuid,
encrypted_message: params.encryptedMessage,
replaceability_identifier: params.replaceabilityIdentifier,
})
}
getInboundUserMessages(): Promise<HttpResponse<GetUserAsymmetricMessagesResponse>> {
return this.httpService.get(AsymmetricMessagesPaths.getInboundUserMessages())
}
getOutboundUserMessages(): Promise<HttpResponse<GetUserAsymmetricMessagesResponse>> {
return this.httpService.get(AsymmetricMessagesPaths.getOutboundUserMessages())
}
getMessages(): Promise<HttpResponse<GetUserAsymmetricMessagesResponse>> {
return this.httpService.get(AsymmetricMessagesPaths.getMessages)
}
deleteMessage(params: DeleteAsymmetricMessageRequestParams): Promise<HttpResponse<DeleteAsymmetricMessageResponse>> {
return this.httpService.delete(AsymmetricMessagesPaths.deleteMessage(params.messageUuid))
}
deleteAllInboundMessages(): Promise<HttpResponse<{ success: boolean }>> {
return this.httpService.delete(AsymmetricMessagesPaths.deleteAllInboundMessages)
}
}

View File

@@ -0,0 +1,17 @@
import { HttpResponse } from '@standardnotes/responses'
import { CreateAsymmetricMessageParams } from '../../Request/AsymmetricMessage/CreateAsymmetricMessageParams'
import { CreateAsymmetricMessageResponse } from '../../Response/AsymmetricMessage/CreateAsymmetricMessageResponse'
import { GetUserAsymmetricMessagesResponse } from '../../Response/AsymmetricMessage/GetUserAsymmetricMessagesResponse'
import { DeleteAsymmetricMessageRequestParams } from '../../Request/AsymmetricMessage/DeleteAsymmetricMessageRequestParams'
import { DeleteAsymmetricMessageResponse } from '../../Response/AsymmetricMessage/DeleteAsymmetricMessageResponse'
export interface AsymmetricMessageServerInterface {
createMessage(params: CreateAsymmetricMessageParams): Promise<HttpResponse<CreateAsymmetricMessageResponse>>
getInboundUserMessages(): Promise<HttpResponse<GetUserAsymmetricMessagesResponse>>
getOutboundUserMessages(): Promise<HttpResponse<GetUserAsymmetricMessagesResponse>>
getMessages(): Promise<HttpResponse<GetUserAsymmetricMessagesResponse>>
deleteMessage(params: DeleteAsymmetricMessageRequestParams): Promise<HttpResponse<DeleteAsymmetricMessageResponse>>
deleteAllInboundMessages(): Promise<HttpResponse<{ success: boolean }>>
}

View File

@@ -0,0 +1,9 @@
export const AsymmetricMessagesPaths = {
createMessage: '/v1/asymmetric-messages',
getMessages: '/v1/asymmetric-messages',
updateMessage: (messageUuid: string) => `/v1/asymmetric-messages/${messageUuid}`,
getInboundUserMessages: () => '/v1/asymmetric-messages',
getOutboundUserMessages: () => '/v1/asymmetric-messages/outbound',
deleteMessage: (messageUuid: string) => `/v1/asymmetric-messages/${messageUuid}`,
deleteAllInboundMessages: '/v1/asymmetric-messages/inbound',
}

View File

@@ -0,0 +1,7 @@
export const SharedVaultsPaths = {
getSharedVaults: '/v1/shared-vaults',
createSharedVault: '/v1/shared-vaults',
deleteSharedVault: (sharedVaultUuid: string) => `/v1/shared-vaults/${sharedVaultUuid}`,
updateSharedVault: (sharedVaultUuid: string) => `/v1/shared-vaults/${sharedVaultUuid}`,
createSharedVaultFileValetToken: (sharedVaultUuid: string) => `/v1/shared-vaults/${sharedVaultUuid}/valet-tokens`,
}

View File

@@ -0,0 +1,37 @@
import { HttpResponse } from '@standardnotes/responses'
import { HttpServiceInterface } from '../../Http'
import { SharedVaultServerInterface } from './SharedVaultServerInterface'
import { SharedVaultsPaths } from './Paths'
import { CreateSharedVaultResponse } from '../../Response/SharedVault/CreateSharedVaultResponse'
import { GetSharedVaultsResponse } from '../../Response/SharedVault/GetSharedVaultsResponse'
import { CreateSharedVaultValetTokenResponse } from '../../Response/SharedVault/CreateSharedVaultValetTokenResponse'
import { CreateSharedVaultValetTokenParams } from '../../Request/SharedVault/CreateSharedVaultValetTokenParams'
export class SharedVaultServer implements SharedVaultServerInterface {
constructor(private httpService: HttpServiceInterface) {}
getSharedVaults(): Promise<HttpResponse<GetSharedVaultsResponse>> {
return this.httpService.get(SharedVaultsPaths.getSharedVaults)
}
createSharedVault(): Promise<HttpResponse<CreateSharedVaultResponse>> {
return this.httpService.post(SharedVaultsPaths.createSharedVault)
}
deleteSharedVault(params: { sharedVaultUuid: string }): Promise<HttpResponse<boolean>> {
return this.httpService.delete(SharedVaultsPaths.deleteSharedVault(params.sharedVaultUuid))
}
createSharedVaultFileValetToken(
params: CreateSharedVaultValetTokenParams,
): Promise<HttpResponse<CreateSharedVaultValetTokenResponse>> {
return this.httpService.post(SharedVaultsPaths.createSharedVaultFileValetToken(params.sharedVaultUuid), {
file_uuid: params.fileUuid,
remote_identifier: params.remoteIdentifier,
operation: params.operation,
unencrypted_file_size: params.unencryptedFileSize,
move_operation_type: params.moveOperationType,
shared_vault_to_shared_vault_move_target_uuid: params.sharedVaultToSharedVaultMoveTargetUuid,
})
}
}

View File

@@ -0,0 +1,17 @@
import { HttpResponse } from '@standardnotes/responses'
import { CreateSharedVaultResponse } from '../../Response/SharedVault/CreateSharedVaultResponse'
import { GetSharedVaultsResponse } from '../../Response/SharedVault/GetSharedVaultsResponse'
import { CreateSharedVaultValetTokenResponse } from '../../Response/SharedVault/CreateSharedVaultValetTokenResponse'
import { CreateSharedVaultValetTokenParams } from '../../Request/SharedVault/CreateSharedVaultValetTokenParams'
export interface SharedVaultServerInterface {
getSharedVaults(): Promise<HttpResponse<GetSharedVaultsResponse>>
createSharedVault(): Promise<HttpResponse<CreateSharedVaultResponse>>
deleteSharedVault(params: { sharedVaultUuid: string }): Promise<HttpResponse<boolean>>
createSharedVaultFileValetToken(
params: CreateSharedVaultValetTokenParams,
): Promise<HttpResponse<CreateSharedVaultValetTokenResponse>>
}

View File

@@ -0,0 +1,16 @@
export const SharedVaultInvitesPaths = {
createInvite: (sharedVaultUuid: string) => `/v1/shared-vaults/${sharedVaultUuid}/invites`,
updateInvite: (sharedVaultUuid: string, inviteUuid: string) =>
`/v1/shared-vaults/${sharedVaultUuid}/invites/${inviteUuid}`,
acceptInvite: (sharedVaultUuid: string, inviteUuid: string) =>
`/v1/shared-vaults/${sharedVaultUuid}/invites/${inviteUuid}/accept`,
declineInvite: (sharedVaultUuid: string, inviteUuid: string) =>
`/v1/shared-vaults/${sharedVaultUuid}/invites/${inviteUuid}/decline`,
getInboundUserInvites: () => '/v1/shared-vaults/invites',
getOutboundUserInvites: () => '/v1/shared-vaults/invites/outbound',
getSharedVaultInvites: (sharedVaultUuid: string) => `/v1/shared-vaults/${sharedVaultUuid}/invites`,
deleteInvite: (sharedVaultUuid: string, inviteUuid: string) =>
`/v1/shared-vaults/${sharedVaultUuid}/invites/${inviteUuid}`,
deleteAllSharedVaultInvites: (sharedVaultUuid: string) => `/v1/shared-vaults/${sharedVaultUuid}/invites`,
deleteAllInboundInvites: '/v1/shared-vaults/invites/inbound',
}

View File

@@ -0,0 +1,75 @@
import { HttpResponse } from '@standardnotes/responses'
import { HttpServiceInterface } from '../../Http'
import { AcceptInviteRequestParams } from '../../Request/SharedVaultInvites/AcceptInviteRequestParams'
import { AcceptInviteResponse } from '../../Response/SharedVaultInvites/AcceptInviteResponse'
import { CreateSharedVaultInviteParams } from '../../Request/SharedVaultInvites/CreateSharedVaultInviteParams'
import { CreateSharedVaultInviteResponse } from '../../Response/SharedVaultInvites/CreateSharedVaultInviteResponse'
import { DeclineInviteRequestParams } from '../../Request/SharedVaultInvites/DeclineInviteRequestParams'
import { DeclineInviteResponse } from '../../Response/SharedVaultInvites/DeclineInviteResponse'
import { DeleteInviteRequestParams } from '../../Request/SharedVaultInvites/DeleteInviteRequestParams'
import { DeleteInviteResponse } from '../../Response/SharedVaultInvites/DeleteInviteResponse'
import { GetSharedVaultInvitesRequestParams } from '../../Request/SharedVaultInvites/GetSharedVaultInvitesRequestParams'
import { GetSharedVaultInvitesResponse } from '../../Response/SharedVaultInvites/GetSharedVaultInvitesResponse'
import { GetUserInvitesResponse } from '../../Response/SharedVaultInvites/GetUserInvitesResponse'
import { SharedVaultInvitesPaths } from './Paths'
import { SharedVaultInvitesServerInterface } from './SharedVaultInvitesServerInterface'
import { UpdateSharedVaultInviteParams } from '../../Request/SharedVaultInvites/UpdateSharedVaultInviteParams'
import { UpdateSharedVaultInviteResponse } from '../../Response/SharedVaultInvites/UpdateSharedVaultInviteResponse'
import { DeleteAllSharedVaultInvitesRequestParams } from '../../Request/SharedVaultInvites/DeleteAllSharedVaultInvitesRequestParams'
import { DeleteAllSharedVaultInvitesResponse } from '../../Response/SharedVaultInvites/DeleteAllSharedVaultInvitesResponse'
export class SharedVaultInvitesServer implements SharedVaultInvitesServerInterface {
constructor(private httpService: HttpServiceInterface) {}
createInvite(params: CreateSharedVaultInviteParams): Promise<HttpResponse<CreateSharedVaultInviteResponse>> {
return this.httpService.post(SharedVaultInvitesPaths.createInvite(params.sharedVaultUuid), {
recipient_uuid: params.recipientUuid,
encrypted_message: params.encryptedMessage,
permissions: params.permissions,
})
}
updateInvite(params: UpdateSharedVaultInviteParams): Promise<HttpResponse<UpdateSharedVaultInviteResponse>> {
return this.httpService.patch(SharedVaultInvitesPaths.updateInvite(params.sharedVaultUuid, params.inviteUuid), {
encrypted_message: params.encryptedMessage,
permissions: params.permissions,
})
}
acceptInvite(params: AcceptInviteRequestParams): Promise<HttpResponse<AcceptInviteResponse>> {
return this.httpService.post(SharedVaultInvitesPaths.acceptInvite(params.sharedVaultUuid, params.inviteUuid))
}
declineInvite(params: DeclineInviteRequestParams): Promise<HttpResponse<DeclineInviteResponse>> {
return this.httpService.post(SharedVaultInvitesPaths.declineInvite(params.sharedVaultUuid, params.inviteUuid))
}
getInboundUserInvites(): Promise<HttpResponse<GetUserInvitesResponse>> {
return this.httpService.get(SharedVaultInvitesPaths.getInboundUserInvites())
}
getOutboundUserInvites(): Promise<HttpResponse<GetUserInvitesResponse>> {
return this.httpService.get(SharedVaultInvitesPaths.getOutboundUserInvites())
}
getSharedVaultInvites(
params: GetSharedVaultInvitesRequestParams,
): Promise<HttpResponse<GetSharedVaultInvitesResponse>> {
return this.httpService.get(SharedVaultInvitesPaths.getSharedVaultInvites(params.sharedVaultUuid))
}
deleteInvite(params: DeleteInviteRequestParams): Promise<HttpResponse<DeleteInviteResponse>> {
return this.httpService.delete(SharedVaultInvitesPaths.deleteInvite(params.sharedVaultUuid, params.inviteUuid))
}
deleteAllSharedVaultInvites(
params: DeleteAllSharedVaultInvitesRequestParams,
): Promise<HttpResponse<DeleteAllSharedVaultInvitesResponse>> {
return this.httpService.delete(SharedVaultInvitesPaths.deleteAllSharedVaultInvites(params.sharedVaultUuid))
}
deleteAllInboundInvites(): Promise<HttpResponse<{ success: boolean }>> {
return this.httpService.delete(SharedVaultInvitesPaths.deleteAllInboundInvites)
}
}

View File

@@ -0,0 +1,35 @@
import { HttpResponse } from '@standardnotes/responses'
import { AcceptInviteRequestParams } from '../../Request/SharedVaultInvites/AcceptInviteRequestParams'
import { AcceptInviteResponse } from '../../Response/SharedVaultInvites/AcceptInviteResponse'
import { CreateSharedVaultInviteParams } from '../../Request/SharedVaultInvites/CreateSharedVaultInviteParams'
import { CreateSharedVaultInviteResponse } from '../../Response/SharedVaultInvites/CreateSharedVaultInviteResponse'
import { DeclineInviteRequestParams } from '../../Request/SharedVaultInvites/DeclineInviteRequestParams'
import { DeclineInviteResponse } from '../../Response/SharedVaultInvites/DeclineInviteResponse'
import { DeleteInviteRequestParams } from '../../Request/SharedVaultInvites/DeleteInviteRequestParams'
import { DeleteInviteResponse } from '../../Response/SharedVaultInvites/DeleteInviteResponse'
import { GetSharedVaultInvitesRequestParams } from '../../Request/SharedVaultInvites/GetSharedVaultInvitesRequestParams'
import { GetSharedVaultInvitesResponse } from '../../Response/SharedVaultInvites/GetSharedVaultInvitesResponse'
import { GetUserInvitesResponse } from '../../Response/SharedVaultInvites/GetUserInvitesResponse'
import { UpdateSharedVaultInviteParams } from '../../Request/SharedVaultInvites/UpdateSharedVaultInviteParams'
import { UpdateSharedVaultInviteResponse } from '../../Response/SharedVaultInvites/UpdateSharedVaultInviteResponse'
import { DeleteAllSharedVaultInvitesRequestParams } from '../../Request/SharedVaultInvites/DeleteAllSharedVaultInvitesRequestParams'
import { DeleteAllSharedVaultInvitesResponse } from '../../Response/SharedVaultInvites/DeleteAllSharedVaultInvitesResponse'
export interface SharedVaultInvitesServerInterface {
createInvite(params: CreateSharedVaultInviteParams): Promise<HttpResponse<CreateSharedVaultInviteResponse>>
updateInvite(params: UpdateSharedVaultInviteParams): Promise<HttpResponse<UpdateSharedVaultInviteResponse>>
acceptInvite(params: AcceptInviteRequestParams): Promise<HttpResponse<AcceptInviteResponse>>
declineInvite(params: DeclineInviteRequestParams): Promise<HttpResponse<DeclineInviteResponse>>
getInboundUserInvites(): Promise<HttpResponse<GetUserInvitesResponse>>
getOutboundUserInvites(): Promise<HttpResponse<GetUserInvitesResponse>>
getSharedVaultInvites(
params: GetSharedVaultInvitesRequestParams,
): Promise<HttpResponse<GetSharedVaultInvitesResponse>>
deleteAllSharedVaultInvites(
params: DeleteAllSharedVaultInvitesRequestParams,
): Promise<HttpResponse<DeleteAllSharedVaultInvitesResponse>>
deleteInvite(params: DeleteInviteRequestParams): Promise<HttpResponse<DeleteInviteResponse>>
deleteAllInboundInvites(): Promise<HttpResponse<{ success: boolean }>>
}

View File

@@ -0,0 +1,5 @@
export const SharedVaultUsersPaths = {
getSharedVaultUsers: (sharedVaultUuid: string) => `/v1/shared-vaults/${sharedVaultUuid}/users`,
deleteSharedVaultUser: (sharedVaultUuid: string, userUuid: string) =>
`/v1/shared-vaults/${sharedVaultUuid}/users/${userUuid}`,
}

View File

@@ -0,0 +1,22 @@
import { HttpResponse } from '@standardnotes/responses'
import { HttpServiceInterface } from '../../Http'
import { GetSharedVaultUsersRequestParams } from '../../Request/SharedVaultUser/GetSharedVaultUsersRequestParams'
import { DeleteSharedVaultUserRequestParams } from '../../Request/SharedVaultUser/DeleteSharedVaultUserRequestParams'
import { DeleteSharedVaultUserResponse } from '../../Response/SharedVaultUsers/DeleteSharedVaultUserResponse'
import { SharedVaultUsersServerInterface } from './SharedVaultUsersServerInterface'
import { SharedVaultUsersPaths } from './Paths'
import { GetSharedVaultUsersResponse } from '../../Response/SharedVaultUsers/GetSharedVaultUsersResponse'
export class SharedVaultUsersServer implements SharedVaultUsersServerInterface {
constructor(private httpService: HttpServiceInterface) {}
getSharedVaultUsers(params: GetSharedVaultUsersRequestParams): Promise<HttpResponse<GetSharedVaultUsersResponse>> {
return this.httpService.get(SharedVaultUsersPaths.getSharedVaultUsers(params.sharedVaultUuid))
}
deleteSharedVaultUser(
params: DeleteSharedVaultUserRequestParams,
): Promise<HttpResponse<DeleteSharedVaultUserResponse>> {
return this.httpService.delete(SharedVaultUsersPaths.deleteSharedVaultUser(params.sharedVaultUuid, params.userUuid))
}
}

View File

@@ -0,0 +1,13 @@
import { HttpResponse } from '@standardnotes/responses'
import { GetSharedVaultUsersRequestParams } from '../../Request/SharedVaultUser/GetSharedVaultUsersRequestParams'
import { DeleteSharedVaultUserRequestParams } from '../../Request/SharedVaultUser/DeleteSharedVaultUserRequestParams'
import { DeleteSharedVaultUserResponse } from '../../Response/SharedVaultUsers/DeleteSharedVaultUserResponse'
import { GetSharedVaultUsersResponse } from '../../Response/SharedVaultUsers/GetSharedVaultUsersResponse'
export interface SharedVaultUsersServerInterface {
getSharedVaultUsers(params: GetSharedVaultUsersRequestParams): Promise<HttpResponse<GetSharedVaultUsersResponse>>
deleteSharedVaultUser(
params: DeleteSharedVaultUserRequestParams,
): Promise<HttpResponse<DeleteSharedVaultUserResponse>>
}

View File

@@ -1,5 +1,6 @@
const UserPaths = {
register: '/v1/users',
updateAccount: (userUuid: string) => `/v1/users/${userUuid}`,
deleteAccount: (userUuid: string) => `/v1/users/${userUuid}`,
}

View File

@@ -1,3 +1,4 @@
import { UserUpdateResponse } from './../../Response/User/UserUpdateResponse'
import { HttpServiceInterface } from '../../Http/HttpServiceInterface'
import { UserDeletionRequestParams } from '../../Request/User/UserDeletionRequestParams'
import { UserRegistrationRequestParams } from '../../Request/User/UserRegistrationRequestParams'
@@ -6,6 +7,7 @@ import { UserDeletionResponseBody } from '../../Response/User/UserDeletionRespon
import { UserRegistrationResponseBody } from '../../Response/User/UserRegistrationResponseBody'
import { Paths } from './Paths'
import { UserServerInterface } from './UserServerInterface'
import { UserUpdateRequestParams } from '../../Request/User/UserUpdateRequestParams'
export class UserServer implements UserServerInterface {
constructor(private httpService: HttpServiceInterface) {}
@@ -17,4 +19,8 @@ export class UserServer implements UserServerInterface {
async register(params: UserRegistrationRequestParams): Promise<HttpResponse<UserRegistrationResponseBody>> {
return this.httpService.post(Paths.v1.register, params)
}
async update(params: UserUpdateRequestParams): Promise<HttpResponse<UserUpdateResponse>> {
return this.httpService.patch(Paths.v1.updateAccount(params.user_uuid), params)
}
}

View File

@@ -3,8 +3,11 @@ import { UserDeletionRequestParams } from '../../Request/User/UserDeletionReques
import { UserRegistrationRequestParams } from '../../Request/User/UserRegistrationRequestParams'
import { UserDeletionResponseBody } from '../../Response/User/UserDeletionResponseBody'
import { UserRegistrationResponseBody } from '../../Response/User/UserRegistrationResponseBody'
import { UserUpdateResponse } from '../../Response/User/UserUpdateResponse'
import { UserUpdateRequestParams } from '../../Request/User/UserUpdateRequestParams'
export interface UserServerInterface {
register(params: UserRegistrationRequestParams): Promise<HttpResponse<UserRegistrationResponseBody>>
deleteAccount(params: UserDeletionRequestParams): Promise<HttpResponse<UserDeletionResponseBody>>
update(params: UserUpdateRequestParams): Promise<HttpResponse<UserUpdateResponse>>
}

View File

@@ -1,14 +1,32 @@
export * from './Auth/AuthServer'
export * from './Auth/AuthServerInterface'
export * from './Authenticator/AuthenticatorServer'
export * from './Authenticator/AuthenticatorServerInterface'
export * from './Revision/RevisionServer'
export * from './Revision/RevisionServerInterface'
export * from './AsymmetricMessage/AsymmetricMessageServer'
export * from './AsymmetricMessage/AsymmetricMessageServerInterface'
export * from './SharedVault/SharedVaultServer'
export * from './SharedVault/SharedVaultServerInterface'
export * from './SharedVaultUsers/SharedVaultUsersServer'
export * from './SharedVaultUsers/SharedVaultUsersServerInterface'
export * from './Subscription/SubscriptionServer'
export * from './Subscription/SubscriptionServerInterface'
export * from './SharedVaultInvites/SharedVaultInvitesServer'
export * from './SharedVaultInvites/SharedVaultInvitesServerInterface'
export * from './User/UserServer'
export * from './User/UserServerInterface'
export * from './UserRequest/UserRequestServer'
export * from './UserRequest/UserRequestServerInterface'
export * from './WebSocket/WebSocketServer'
export * from './WebSocket/WebSocketServerInterface'

View File

@@ -5,22 +5,15 @@
"node": ">=16.0.0 <17.0.0"
},
"description": "Payload encryption used in SNJS library",
"main": "dist/index.js",
"main": "./src/index.ts",
"private": true,
"author": "Standard Notes",
"types": "dist/index.d.ts",
"files": [
"dist"
],
"license": "AGPL-3.0-or-later",
"scripts": {
"clean": "rm -fr dist",
"prestart": "yarn clean",
"start": "tsc -p tsconfig.json --watch",
"prebuild": "yarn clean",
"build": "tsc -p tsconfig.json",
"lint": "eslint src --ext .ts",
"format": "prettier --write src",
"test": "jest"
"test": "jest",
"build": "echo 'Empty build script required for yarn topological install'"
},
"devDependencies": {
"@standardnotes/config": "2.4.3",
@@ -35,7 +28,7 @@
"typescript": "*"
},
"dependencies": {
"@standardnotes/common": "^1.46.6",
"@standardnotes/common": "^1.48.3",
"@standardnotes/models": "workspace:*",
"@standardnotes/responses": "workspace:*",
"@standardnotes/sncrypto-common": "workspace:*",

View File

@@ -1,3 +1,5 @@
import { SodiumConstant } from '@standardnotes/sncrypto-common'
export const V001Algorithm = Object.freeze({
SaltSeedLength: 128,
/**
@@ -41,11 +43,21 @@ export enum V004Algorithm {
ArgonIterations = 5,
ArgonMemLimit = 67108864,
ArgonOutputKeyBytes = 64,
EncryptionKeyLength = 256,
EncryptionNonceLength = 192,
}
export enum V005Algorithm {
AsymmetricEncryptionNonceLength = 192,
SymmetricEncryptionNonceLength = 192,
MasterKeyEncryptionKeyPairSubKeyNumber = 1,
MasterKeyEncryptionKeyPairSubKeyContext = 'sn-pkc-e',
MasterKeyEncryptionKeyPairSubKeyBytes = SodiumConstant.crypto_box_SEEDBYTES,
MasterKeySigningKeyPairSubKeyNumber = 2,
MasterKeySigningKeyPairSubKeyContext = 'sn-pkc-s',
MasterKeySigningKeyPairSubKeyBytes = SodiumConstant.crypto_sign_SEEDBYTES,
PayloadKeyHashingKeySubKeyNumber = 1,
PayloadKeyHashingKeySubKeyContext = 'sn-sym-h',
PayloadKeyHashingKeySubKeyBytes = SodiumConstant.crypto_generichash_KEYBYTES,
}

View File

@@ -7,11 +7,10 @@ import {
HistoryEntryInterface,
ItemsKeyContent,
ItemsKeyInterface,
RootKeyInterface,
} from '@standardnotes/models'
export function isItemsKey(x: ItemsKeyInterface | RootKeyInterface): x is ItemsKeyInterface {
return x.content_type === ContentType.ItemsKey
export function isItemsKey(x: unknown): x is ItemsKeyInterface {
return (x as ItemsKeyInterface).content_type === ContentType.ItemsKey
}
/**

View File

@@ -0,0 +1,41 @@
import { ContentType, ProtocolVersion } from '@standardnotes/common'
import {
ConflictStrategy,
DecryptedItem,
DecryptedItemInterface,
DecryptedPayloadInterface,
HistoryEntryInterface,
KeySystemItemsKeyContent,
KeySystemItemsKeyInterface,
} from '@standardnotes/models'
export function isKeySystemItemsKey(x: unknown): x is KeySystemItemsKeyInterface {
return (x as KeySystemItemsKeyInterface).content_type === ContentType.KeySystemItemsKey
}
/**
* A key used to encrypt other items. Items keys are synced and persisted.
*/
export class KeySystemItemsKey extends DecryptedItem<KeySystemItemsKeyContent> implements KeySystemItemsKeyInterface {
creationTimestamp: number
keyVersion: ProtocolVersion
itemsKey: string
rootKeyToken: string
constructor(payload: DecryptedPayloadInterface<KeySystemItemsKeyContent>) {
super(payload)
this.creationTimestamp = payload.content.creationTimestamp
this.keyVersion = payload.content.version
this.itemsKey = this.payload.content.itemsKey
this.rootKeyToken = this.payload.content.rootKeyToken
}
/** Do not duplicate vault items keys. Always keep original */
override strategyWhenConflictingWithItem(
_item: DecryptedItemInterface,
_previousRevision?: HistoryEntryInterface,
): ConflictStrategy {
return ConflictStrategy.KeepBase
}
}

View File

@@ -0,0 +1,3 @@
import { DecryptedItemMutator, KeySystemItemsKeyContent } from '@standardnotes/models'
export class KeySystemItemsKeyMutator extends DecryptedItemMutator<KeySystemItemsKeyContent> {}

View File

@@ -0,0 +1,10 @@
import { ContentType } from '@standardnotes/common'
import { DecryptedItemMutator, KeySystemItemsKeyContent, RegisterItemClass } from '@standardnotes/models'
import { KeySystemItemsKey } from './KeySystemItemsKey'
import { KeySystemItemsKeyMutator } from './KeySystemItemsKeyMutator'
RegisterItemClass(
ContentType.KeySystemItemsKey,
KeySystemItemsKey,
KeySystemItemsKeyMutator as unknown as DecryptedItemMutator<KeySystemItemsKeyContent>,
)

View File

@@ -5,11 +5,12 @@ import {
PayloadTimestampDefaults,
RootKeyContent,
RootKeyContentSpecialized,
RootKeyInterface,
} from '@standardnotes/models'
import { UuidGenerator } from '@standardnotes/utils'
import { SNRootKey } from './RootKey'
export function CreateNewRootKey(content: RootKeyContentSpecialized): SNRootKey {
export function CreateNewRootKey<K extends RootKeyInterface>(content: RootKeyContentSpecialized): K {
const uuid = UuidGenerator.GenerateUuid()
const payload = new DecryptedPayload<RootKeyContent>({
@@ -19,7 +20,7 @@ export function CreateNewRootKey(content: RootKeyContentSpecialized): SNRootKey
...PayloadTimestampDefaults(),
})
return new SNRootKey(payload)
return new SNRootKey(payload) as K
}
export function FillRootKeyContent(content: Partial<RootKeyContentSpecialized>): RootKeyContent {
@@ -37,15 +38,3 @@ export function FillRootKeyContent(content: Partial<RootKeyContentSpecialized>):
return FillItemContentSpecialized(content)
}
export function ContentTypeUsesRootKeyEncryption(contentType: ContentType): boolean {
return (
contentType === ContentType.RootKey ||
contentType === ContentType.ItemsKey ||
contentType === ContentType.EncryptedStorage
)
}
export function ItemContentTypeUsesRootKeyEncryption(contentType: ContentType): boolean {
return contentType === ContentType.ItemsKey
}

View File

@@ -7,7 +7,7 @@ import {
RootKeyContentInStorage,
RootKeyInterface,
} from '@standardnotes/models'
import { timingSafeEqual } from '@standardnotes/sncrypto-common'
import { PkcKeyPair, timingSafeEqual } from '@standardnotes/sncrypto-common'
import { SNRootKeyParams } from './RootKeyParams'
/**
@@ -47,6 +47,14 @@ export class SNRootKey extends DecryptedItem<RootKeyContent> implements RootKeyI
return this.content.serverPassword
}
get encryptionKeyPair(): PkcKeyPair | undefined {
return this.content.encryptionKeyPair
}
get signingKeyPair(): PkcKeyPair | undefined {
return this.content.signingKeyPair
}
/** 003 and below only. */
public get dataAuthenticationKey(): string | undefined {
return this.content.dataAuthenticationKey
@@ -84,6 +92,8 @@ export class SNRootKey extends DecryptedItem<RootKeyContent> implements RootKeyI
const values: NamespacedRootKeyInKeychain = {
version: this.keyVersion,
masterKey: this.masterKey,
encryptionKeyPair: this.encryptionKeyPair,
signingKeyPair: this.signingKeyPair,
}
if (this.dataAuthenticationKey) {

View File

@@ -8,8 +8,13 @@ import {
ItemsKeyContent,
ItemsKeyInterface,
PayloadTimestampDefaults,
KeySystemItemsKeyInterface,
KeySystemIdentifier,
KeySystemRootKeyInterface,
RootKeyInterface,
KeySystemRootKeyParamsInterface,
} from '@standardnotes/models'
import { PureCryptoInterface } from '@standardnotes/sncrypto-common'
import { PkcKeyPair, PureCryptoInterface } from '@standardnotes/sncrypto-common'
import { firstHalfOfString, secondHalfOfString, splitString, UuidGenerator } from '@standardnotes/utils'
import { V001Algorithm } from '../../Algorithm'
import { isItemsKey } from '../../Keys/ItemsKey/ItemsKey'
@@ -17,11 +22,16 @@ import { CreateNewRootKey } from '../../Keys/RootKey/Functions'
import { Create001KeyParams } from '../../Keys/RootKey/KeyParamsFunctions'
import { SNRootKey } from '../../Keys/RootKey/RootKey'
import { SNRootKeyParams } from '../../Keys/RootKey/RootKeyParams'
import { DecryptedParameters, EncryptedParameters, ErrorDecryptingParameters } from '../../Types/EncryptedParameters'
import { EncryptedOutputParameters, ErrorDecryptingParameters } from '../../Types/EncryptedParameters'
import { DecryptedParameters } from '../../Types/DecryptedParameters'
import { ItemAuthenticatedData } from '../../Types/ItemAuthenticatedData'
import { LegacyAttachedData } from '../../Types/LegacyAttachedData'
import { RootKeyEncryptedAuthenticatedData } from '../../Types/RootKeyEncryptedAuthenticatedData'
import { AsynchronousOperator } from '../Operator'
import { OperatorInterface } from '../OperatorInterface/OperatorInterface'
import { PublicKeySet } from '../Types/PublicKeySet'
import { AsymmetricDecryptResult } from '../Types/AsymmetricDecryptResult'
import { AsymmetricSignatureVerificationDetachedResult } from '../Types/AsymmetricSignatureVerificationDetachedResult'
import { AsyncOperatorInterface } from '../OperatorInterface/AsyncOperatorInterface'
const NO_IV = '00000000000000000000000000000000'
@@ -29,7 +39,7 @@ const NO_IV = '00000000000000000000000000000000'
* @deprecated
* A legacy operator no longer used to generate new accounts
*/
export class SNProtocolOperator001 implements AsynchronousOperator {
export class SNProtocolOperator001 implements OperatorInterface, AsyncOperatorInterface {
protected readonly crypto: PureCryptoInterface
constructor(crypto: PureCryptoInterface) {
@@ -68,11 +78,11 @@ export class SNProtocolOperator001 implements AsynchronousOperator {
return CreateDecryptedItemFromPayload(payload)
}
public async createRootKey(
public async createRootKey<K extends RootKeyInterface>(
identifier: string,
password: string,
origination: KeyParamsOrigination,
): Promise<SNRootKey> {
): Promise<K> {
const pwCost = V001Algorithm.PbkdfMinCost as number
const pwNonce = this.crypto.generateRandomKey(V001Algorithm.SaltSeedLength)
const pwSalt = await this.crypto.unsafeSha1(identifier + 'SN' + pwNonce)
@@ -90,13 +100,13 @@ export class SNProtocolOperator001 implements AsynchronousOperator {
return this.deriveKey(password, keyParams)
}
public getPayloadAuthenticatedData(
_encrypted: EncryptedParameters,
public getPayloadAuthenticatedDataForExternalUse(
_encrypted: EncryptedOutputParameters,
): RootKeyEncryptedAuthenticatedData | ItemAuthenticatedData | LegacyAttachedData | undefined {
return undefined
}
public async computeRootKey(password: string, keyParams: SNRootKeyParams): Promise<SNRootKey> {
public async computeRootKey<K extends RootKeyInterface>(password: string, keyParams: SNRootKeyParams): Promise<K> {
return this.deriveKey(password, keyParams)
}
@@ -111,7 +121,7 @@ export class SNProtocolOperator001 implements AsynchronousOperator {
public async generateEncryptedParametersAsync(
payload: DecryptedPayloadInterface,
key: ItemsKeyInterface | SNRootKey,
): Promise<EncryptedParameters> {
): Promise<EncryptedOutputParameters> {
/**
* Generate new item key that is double the key size.
* Will be split to create encryption key and authentication key.
@@ -132,16 +142,19 @@ export class SNProtocolOperator001 implements AsynchronousOperator {
return {
uuid: payload.uuid,
content_type: payload.content_type,
items_key_id: isItemsKey(key) ? key.uuid : undefined,
content: ciphertext,
enc_item_key: encItemKey,
auth_hash: authHash,
version: this.version,
key_system_identifier: payload.key_system_identifier,
shared_vault_uuid: payload.shared_vault_uuid,
}
}
public async generateDecryptedParametersAsync<C extends ItemContent = ItemContent>(
encrypted: EncryptedParameters,
encrypted: EncryptedOutputParameters,
key: ItemsKeyInterface | SNRootKey,
): Promise<DecryptedParameters<C> | ErrorDecryptingParameters> {
if (!encrypted.enc_item_key) {
@@ -178,6 +191,7 @@ export class SNProtocolOperator001 implements AsynchronousOperator {
return {
uuid: encrypted.uuid,
content: JSON.parse(content),
signatureData: { required: false, contentHash: '' },
}
}
}
@@ -191,7 +205,7 @@ export class SNProtocolOperator001 implements AsynchronousOperator {
}
}
protected async deriveKey(password: string, keyParams: SNRootKeyParams): Promise<SNRootKey> {
protected async deriveKey<K extends RootKeyInterface>(password: string, keyParams: SNRootKeyParams): Promise<K> {
const derivedKey = await this.crypto.pbkdf2(
password,
keyParams.content001.pw_salt,
@@ -205,11 +219,63 @@ export class SNProtocolOperator001 implements AsynchronousOperator {
const partitions = splitString(derivedKey, 2)
return CreateNewRootKey({
return CreateNewRootKey<K>({
serverPassword: partitions[0],
masterKey: partitions[1],
version: ProtocolVersion.V001,
keyParams: keyParams.getPortableValue(),
})
}
createRandomizedKeySystemRootKey(_dto: { systemIdentifier: string }): KeySystemRootKeyInterface {
throw new Error('Method not implemented.')
}
createUserInputtedKeySystemRootKey(_dto: {
systemIdentifier: string
systemName: string
userInputtedPassword: string
}): KeySystemRootKeyInterface {
throw new Error('Method not implemented.')
}
deriveUserInputtedKeySystemRootKey(_dto: {
keyParams: KeySystemRootKeyParamsInterface
userInputtedPassword: string
}): KeySystemRootKeyInterface {
throw new Error('Method not implemented.')
}
createKeySystemItemsKey(
_uuid: string,
_keySystemIdentifier: KeySystemIdentifier,
_sharedVaultUuid: string | undefined,
): KeySystemItemsKeyInterface {
throw new Error('Method not implemented.')
}
versionForAsymmetricallyEncryptedString(_encryptedString: string): ProtocolVersion {
throw new Error('Method not implemented.')
}
asymmetricEncrypt(_dto: {
stringToEncrypt: string
senderKeyPair: PkcKeyPair
senderSigningKeyPair: PkcKeyPair
recipientPublicKey: string
}): string {
throw new Error('Method not implemented.')
}
asymmetricDecrypt(_dto: { stringToDecrypt: string; recipientSecretKey: string }): AsymmetricDecryptResult | null {
throw new Error('Method not implemented.')
}
asymmetricSignatureVerifyDetached(_encryptedString: string): AsymmetricSignatureVerificationDetachedResult {
throw new Error('Method not implemented.')
}
getSenderPublicKeySetFromAsymmetricallyEncryptedString(_string: string): PublicKeySet {
throw new Error('Method not implemented.')
}
}

View File

@@ -9,7 +9,8 @@ import { CreateNewRootKey } from '../../Keys/RootKey/Functions'
import { Create002KeyParams } from '../../Keys/RootKey/KeyParamsFunctions'
import { SNRootKey } from '../../Keys/RootKey/RootKey'
import { SNRootKeyParams } from '../../Keys/RootKey/RootKeyParams'
import { DecryptedParameters, EncryptedParameters, ErrorDecryptingParameters } from '../../Types/EncryptedParameters'
import { EncryptedOutputParameters, ErrorDecryptingParameters } from '../../Types/EncryptedParameters'
import { DecryptedParameters } from '../../Types/DecryptedParameters'
import { ItemAuthenticatedData } from '../../Types/ItemAuthenticatedData'
import { LegacyAttachedData } from '../../Types/LegacyAttachedData'
import { RootKeyEncryptedAuthenticatedData } from '../../Types/RootKeyEncryptedAuthenticatedData'
@@ -50,11 +51,11 @@ export class SNProtocolOperator002 extends SNProtocolOperator001 {
return Models.CreateDecryptedItemFromPayload(payload)
}
public override async createRootKey(
public override async createRootKey<K extends Models.RootKeyInterface>(
identifier: string,
password: string,
origination: Common.KeyParamsOrigination,
): Promise<SNRootKey> {
): Promise<K> {
const pwCost = Utils.lastElement(V002Algorithm.PbkdfCostsUsed) as number
const pwNonce = this.crypto.generateRandomKey(V002Algorithm.SaltSeedLength)
const pwSalt = await this.crypto.unsafeSha1(identifier + ':' + pwNonce)
@@ -77,7 +78,10 @@ export class SNProtocolOperator002 extends SNProtocolOperator001 {
* may have had costs of 5000, and others of 101000. Therefore, when computing
* the root key, we must use the value returned by the server.
*/
public override async computeRootKey(password: string, keyParams: SNRootKeyParams): Promise<SNRootKey> {
public override async computeRootKey<K extends Models.RootKeyInterface>(
password: string,
keyParams: SNRootKeyParams,
): Promise<K> {
return this.deriveKey(password, keyParams)
}
@@ -141,8 +145,8 @@ export class SNProtocolOperator002 extends SNProtocolOperator001 {
return this.decryptString002(contentCiphertext, encryptionKey, iv)
}
public override getPayloadAuthenticatedData(
encrypted: EncryptedParameters,
public override getPayloadAuthenticatedDataForExternalUse(
encrypted: EncryptedOutputParameters,
): RootKeyEncryptedAuthenticatedData | ItemAuthenticatedData | LegacyAttachedData | undefined {
const itemKeyComponents = this.encryptionComponentsFromString002(encrypted.enc_item_key)
const authenticatedData = itemKeyComponents.keyParams
@@ -161,7 +165,7 @@ export class SNProtocolOperator002 extends SNProtocolOperator001 {
public override async generateEncryptedParametersAsync(
payload: Models.DecryptedPayloadInterface,
key: Models.ItemsKeyInterface | SNRootKey,
): Promise<EncryptedParameters> {
): Promise<EncryptedOutputParameters> {
/**
* Generate new item key that is double the key size.
* Will be split to create encryption key and authentication key.
@@ -189,15 +193,18 @@ export class SNProtocolOperator002 extends SNProtocolOperator001 {
return {
uuid: payload.uuid,
content_type: payload.content_type,
items_key_id: isItemsKey(key) ? key.uuid : undefined,
content: ciphertext,
enc_item_key: encItemKey,
version: this.version,
key_system_identifier: payload.key_system_identifier,
shared_vault_uuid: payload.shared_vault_uuid,
}
}
public override async generateDecryptedParametersAsync<C extends ItemContent = ItemContent>(
encrypted: EncryptedParameters,
encrypted: EncryptedOutputParameters,
key: Models.ItemsKeyInterface | SNRootKey,
): Promise<DecryptedParameters<C> | ErrorDecryptingParameters> {
if (!encrypted.enc_item_key) {
@@ -252,11 +259,15 @@ export class SNProtocolOperator002 extends SNProtocolOperator001 {
return {
uuid: encrypted.uuid,
content: JSON.parse(content),
signatureData: { required: false, contentHash: '' },
}
}
}
protected override async deriveKey(password: string, keyParams: SNRootKeyParams): Promise<SNRootKey> {
protected override async deriveKey<K extends Models.RootKeyInterface>(
password: string,
keyParams: SNRootKeyParams,
): Promise<K> {
const derivedKey = await this.crypto.pbkdf2(
password,
keyParams.content002.pw_salt,
@@ -270,7 +281,7 @@ export class SNProtocolOperator002 extends SNProtocolOperator001 {
const partitions = Utils.splitString(derivedKey, 3)
return CreateNewRootKey({
return CreateNewRootKey<K>({
serverPassword: partitions[0],
masterKey: partitions[1],
dataAuthenticationKey: partitions[2],

View File

@@ -6,12 +6,12 @@ import {
ItemsKeyContent,
ItemsKeyInterface,
PayloadTimestampDefaults,
RootKeyInterface,
} from '@standardnotes/models'
import { splitString, UuidGenerator } from '@standardnotes/utils'
import { V003Algorithm } from '../../Algorithm'
import { CreateNewRootKey } from '../../Keys/RootKey/Functions'
import { Create003KeyParams } from '../../Keys/RootKey/KeyParamsFunctions'
import { SNRootKey } from '../../Keys/RootKey/RootKey'
import { SNRootKeyParams } from '../../Keys/RootKey/RootKeyParams'
import { SNProtocolOperator002 } from '../002/Operator002'
@@ -53,11 +53,17 @@ export class SNProtocolOperator003 extends SNProtocolOperator002 {
return CreateDecryptedItemFromPayload(payload)
}
public override async computeRootKey(password: string, keyParams: SNRootKeyParams): Promise<SNRootKey> {
public override async computeRootKey<K extends RootKeyInterface>(
password: string,
keyParams: SNRootKeyParams,
): Promise<K> {
return this.deriveKey(password, keyParams)
}
protected override async deriveKey(password: string, keyParams: SNRootKeyParams): Promise<SNRootKey> {
protected override async deriveKey<K extends RootKeyInterface>(
password: string,
keyParams: SNRootKeyParams,
): Promise<K> {
const salt = await this.generateSalt(
keyParams.content003.identifier,
ProtocolVersion.V003,
@@ -78,7 +84,7 @@ export class SNProtocolOperator003 extends SNProtocolOperator002 {
const partitions = splitString(derivedKey, 3)
return CreateNewRootKey({
return CreateNewRootKey<K>({
serverPassword: partitions[0],
masterKey: partitions[1],
dataAuthenticationKey: partitions[2],
@@ -87,11 +93,11 @@ export class SNProtocolOperator003 extends SNProtocolOperator002 {
})
}
public override async createRootKey(
public override async createRootKey<K extends RootKeyInterface>(
identifier: string,
password: string,
origination: KeyParamsOrigination,
): Promise<SNRootKey> {
): Promise<K> {
const version = ProtocolVersion.V003
const pwNonce = this.crypto.generateRandomKey(V003Algorithm.SaltSeedLength)
const keyParams = Create003KeyParams({

View File

@@ -0,0 +1,87 @@
import { PureCryptoInterface } from '@standardnotes/sncrypto-common'
export function getMockedCrypto(): PureCryptoInterface {
const crypto = {} as jest.Mocked<PureCryptoInterface>
const mockGenerateKeyPair = (seed: string) => {
const publicKey = `public-key-${seed}`
const privateKey = `private-key-${seed}`
return {
publicKey: `${publicKey}:${privateKey}`,
privateKey: `${privateKey}:${publicKey}`,
}
}
const replaceColonsToAvoidJSONConflicts = (text: string) => {
return text.replace(/:/g, '|')
}
const undoReplaceColonsToAvoidJSONConflicts = (text: string) => {
return text.replace(/\|/g, ':')
}
crypto.base64Encode = jest.fn().mockImplementation((text: string) => {
return `base64-${replaceColonsToAvoidJSONConflicts(text)}`
})
crypto.base64Decode = jest.fn().mockImplementation((text: string) => {
const decodedText = text.split('base64-')[1]
return undoReplaceColonsToAvoidJSONConflicts(decodedText)
})
crypto.xchacha20Encrypt = jest.fn().mockImplementation((text: string) => {
return `<e>${replaceColonsToAvoidJSONConflicts(text)}<e>`
})
crypto.xchacha20Decrypt = jest.fn().mockImplementation((text: string) => {
return undoReplaceColonsToAvoidJSONConflicts(text.split('<e>')[1])
})
crypto.generateRandomKey = jest.fn().mockImplementation(() => {
return 'random-string'
})
crypto.sodiumCryptoBoxEasyEncrypt = jest.fn().mockImplementation((text: string) => {
return `<e>${replaceColonsToAvoidJSONConflicts(text)}<e>`
})
crypto.sodiumCryptoBoxEasyDecrypt = jest.fn().mockImplementation((text: string) => {
return undoReplaceColonsToAvoidJSONConflicts(text.split('<e>')[1])
})
crypto.sodiumCryptoBoxSeedKeypair = jest.fn().mockImplementation((seed: string) => {
return mockGenerateKeyPair(seed)
})
crypto.sodiumCryptoKdfDeriveFromKey = jest
.fn()
.mockImplementation((key: string, subkeyNumber: number, subkeyLength: number, context: string) => {
return `subkey-${key}-${subkeyNumber}-${subkeyLength}-${context}`
})
crypto.sodiumCryptoSign = jest.fn().mockImplementation((message: string, privateKey: string) => {
const signature = `signature:m=${message}:pk=${privateKey}`
return signature
})
crypto.sodiumCryptoSignSeedKeypair = jest.fn().mockImplementation((seed: string) => {
return mockGenerateKeyPair(seed)
})
crypto.sodiumCryptoSignVerify = jest
.fn()
.mockImplementation((message: string, signature: string, publicKey: string) => {
const keyComponents = publicKey.split(':')
const privateKeyComponent = keyComponents[1]
const privateKey = `${privateKeyComponent}:${keyComponents[0]}`
const computedSignature = crypto.sodiumCryptoSign(message, privateKey)
return computedSignature === signature
})
crypto.sodiumCryptoGenericHash = jest.fn().mockImplementation((message: string, key: string) => {
return `hash-${message}-${key}`
})
return crypto
}

View File

@@ -1,69 +1,34 @@
import { ContentType, ProtocolVersion } from '@standardnotes/common'
import { DecryptedPayload, ItemContent, ItemsKeyContent, PayloadTimestampDefaults } from '@standardnotes/models'
import { PureCryptoInterface } from '@standardnotes/sncrypto-common'
import { SNItemsKey } from '../../Keys/ItemsKey/ItemsKey'
import { ItemAuthenticatedData } from '../../Types/ItemAuthenticatedData'
import { SNProtocolOperator004 } from './Operator004'
const b64 = (text: string): string => {
return Buffer.from(text).toString('base64')
}
import { getMockedCrypto } from './MockedCrypto'
import { deconstructEncryptedPayloadString } from './V004AlgorithmHelpers'
describe('operator 004', () => {
let crypto: PureCryptoInterface
const crypto = getMockedCrypto()
let operator: SNProtocolOperator004
beforeEach(() => {
crypto = {} as jest.Mocked<PureCryptoInterface>
crypto.base64Encode = jest.fn().mockImplementation((text: string) => {
return b64(text)
})
crypto.base64Decode = jest.fn().mockImplementation((text: string) => {
return Buffer.from(text, 'base64').toString('ascii')
})
crypto.xchacha20Encrypt = jest.fn().mockImplementation((text: string) => {
return `<e>${text}<e>`
})
crypto.xchacha20Decrypt = jest.fn().mockImplementation((text: string) => {
return text.split('<e>')[1]
})
crypto.generateRandomKey = jest.fn().mockImplementation(() => {
return 'random-string'
})
operator = new SNProtocolOperator004(crypto)
})
it('should generateEncryptedProtocolString', () => {
const aad: ItemAuthenticatedData = {
u: '123',
v: ProtocolVersion.V004,
}
const nonce = 'noncy'
const plaintext = 'foo'
operator.generateEncryptionNonce = jest.fn().mockReturnValue(nonce)
const result = operator.generateEncryptedProtocolString(plaintext, 'secret', aad)
expect(result).toEqual(`004:${nonce}:<e>${plaintext}<e>:${b64(JSON.stringify(aad))}`)
})
it('should deconstructEncryptedPayloadString', () => {
const string = '004:noncy:<e>foo<e>:eyJ1IjoiMTIzIiwidiI6IjAwNCJ9'
const result = operator.deconstructEncryptedPayloadString(string)
const result = deconstructEncryptedPayloadString(string)
expect(result).toEqual({
version: '004',
nonce: 'noncy',
ciphertext: '<e>foo<e>',
authenticatedData: 'eyJ1IjoiMTIzIiwidiI6IjAwNCJ9',
additionalData: 'e30=',
})
})
it('should generateEncryptedParametersSync', () => {
it('should generateEncryptedParameters', () => {
const payload = {
uuid: '123',
content_type: ContentType.Note,
@@ -83,13 +48,16 @@ describe('operator 004', () => {
}),
)
const result = operator.generateEncryptedParametersSync(payload, key)
const result = operator.generateEncryptedParameters(payload, key)
expect(result).toEqual({
uuid: '123',
items_key_id: 'key-456',
content: '004:random-string:<e>{"foo":"bar"}<e>:eyJ1IjoiMTIzIiwidiI6IjAwNCJ9',
enc_item_key: '004:random-string:<e>random-string<e>:eyJ1IjoiMTIzIiwidiI6IjAwNCJ9',
key_system_identifier: undefined,
shared_vault_uuid: undefined,
content: '004:random-string:<e>{"foo"|"bar"}<e>:base64-{"u"|"123","v"|"004"}:base64-{}',
content_type: ContentType.Note,
enc_item_key: '004:random-string:<e>random-string<e>:base64-{"u"|"123","v"|"004"}:base64-{}',
version: '004',
})
})

View File

@@ -1,44 +1,56 @@
import { ContentType, KeyParamsOrigination, ProtocolVersion } from '@standardnotes/common'
import * as Models from '@standardnotes/models'
import {
CreateDecryptedItemFromPayload,
FillItemContent,
ItemContent,
ItemsKeyContent,
ItemsKeyInterface,
PayloadTimestampDefaults,
DecryptedPayload,
DecryptedPayloadInterface,
KeySystemItemsKeyInterface,
KeySystemRootKeyInterface,
FillItemContentSpecialized,
ItemsKeyContentSpecialized,
KeySystemIdentifier,
RootKeyInterface,
KeySystemRootKeyParamsInterface,
} from '@standardnotes/models'
import { PureCryptoInterface } from '@standardnotes/sncrypto-common'
import * as Utils from '@standardnotes/utils'
import { ContentType, KeyParamsOrigination, ProtocolVersion } from '@standardnotes/common'
import { HexString, PkcKeyPair, PureCryptoInterface, Utf8String } from '@standardnotes/sncrypto-common'
import { V004Algorithm } from '../../Algorithm'
import { isItemsKey } from '../../Keys/ItemsKey/ItemsKey'
import { ContentTypeUsesRootKeyEncryption, CreateNewRootKey } from '../../Keys/RootKey/Functions'
import { Create004KeyParams } from '../../Keys/RootKey/KeyParamsFunctions'
import { SNRootKey } from '../../Keys/RootKey/RootKey'
import { SNRootKeyParams } from '../../Keys/RootKey/RootKeyParams'
import { DecryptedParameters, EncryptedParameters, ErrorDecryptingParameters } from '../../Types/EncryptedParameters'
import {
EncryptedInputParameters,
EncryptedOutputParameters,
ErrorDecryptingParameters,
} from '../../Types/EncryptedParameters'
import { DecryptedParameters } from '../../Types/DecryptedParameters'
import { ItemAuthenticatedData } from '../../Types/ItemAuthenticatedData'
import { LegacyAttachedData } from '../../Types/LegacyAttachedData'
import { RootKeyEncryptedAuthenticatedData } from '../../Types/RootKeyEncryptedAuthenticatedData'
import { SynchronousOperator } from '../Operator'
import { OperatorInterface } from '../OperatorInterface/OperatorInterface'
import { AsymmetricallyEncryptedString } from '../Types/Types'
import { AsymmetricItemAdditionalData } from '../../Types/EncryptionAdditionalData'
import { V004AsymmetricStringComponents } from './V004AlgorithmTypes'
import { AsymmetricEncryptUseCase } from './UseCase/Asymmetric/AsymmetricEncrypt'
import { ParseConsistentBase64JsonPayloadUseCase } from './UseCase/Utils/ParseConsistentBase64JsonPayload'
import { AsymmetricDecryptUseCase } from './UseCase/Asymmetric/AsymmetricDecrypt'
import { GenerateDecryptedParametersUseCase } from './UseCase/Symmetric/GenerateDecryptedParameters'
import { GenerateEncryptedParametersUseCase } from './UseCase/Symmetric/GenerateEncryptedParameters'
import { DeriveRootKeyUseCase } from './UseCase/RootKey/DeriveRootKey'
import { GetPayloadAuthenticatedDataDetachedUseCase } from './UseCase/Symmetric/GetPayloadAuthenticatedDataDetached'
import { CreateRootKeyUseCase } from './UseCase/RootKey/CreateRootKey'
import { UuidGenerator } from '@standardnotes/utils'
import { CreateKeySystemItemsKeyUseCase } from './UseCase/KeySystem/CreateKeySystemItemsKey'
import { AsymmetricDecryptResult } from '../Types/AsymmetricDecryptResult'
import { PublicKeySet } from '../Types/PublicKeySet'
import { CreateRandomKeySystemRootKey } from './UseCase/KeySystem/CreateRandomKeySystemRootKey'
import { CreateUserInputKeySystemRootKey } from './UseCase/KeySystem/CreateUserInputKeySystemRootKey'
import { AsymmetricSignatureVerificationDetachedResult } from '../Types/AsymmetricSignatureVerificationDetachedResult'
import { AsymmetricSignatureVerificationDetachedUseCase } from './UseCase/Asymmetric/AsymmetricSignatureVerificationDetached'
import { DeriveKeySystemRootKeyUseCase } from './UseCase/KeySystem/DeriveKeySystemRootKey'
import { SyncOperatorInterface } from '../OperatorInterface/SyncOperatorInterface'
type V004StringComponents = [version: string, nonce: string, ciphertext: string, authenticatedData: string]
type V004Components = {
version: V004StringComponents[0]
nonce: V004StringComponents[1]
ciphertext: V004StringComponents[2]
authenticatedData: V004StringComponents[3]
}
const PARTITION_CHARACTER = ':'
export class SNProtocolOperator004 implements SynchronousOperator {
protected readonly crypto: PureCryptoInterface
constructor(crypto: PureCryptoInterface) {
this.crypto = crypto
}
export class SNProtocolOperator004 implements OperatorInterface, SyncOperatorInterface {
constructor(protected readonly crypto: PureCryptoInterface) {}
public getEncryptionDisplayName(): string {
return 'XChaCha20-Poly1305'
@@ -50,7 +62,7 @@ export class SNProtocolOperator004 implements SynchronousOperator {
private generateNewItemsKeyContent() {
const itemsKey = this.crypto.generateRandomKey(V004Algorithm.EncryptionKeyLength)
const response = FillItemContent<ItemsKeyContent>({
const response = FillItemContentSpecialized<ItemsKeyContentSpecialized>({
itemsKey: itemsKey,
version: ProtocolVersion.V004,
})
@@ -62,260 +74,130 @@ export class SNProtocolOperator004 implements SynchronousOperator {
* The consumer must save/sync this item.
*/
public createItemsKey(): ItemsKeyInterface {
const payload = new Models.DecryptedPayload({
uuid: Utils.UuidGenerator.GenerateUuid(),
const payload = new DecryptedPayload({
uuid: UuidGenerator.GenerateUuid(),
content_type: ContentType.ItemsKey,
content: this.generateNewItemsKeyContent(),
key_system_identifier: undefined,
shared_vault_uuid: undefined,
...PayloadTimestampDefaults(),
})
return CreateDecryptedItemFromPayload(payload)
}
/**
* We require both a client-side component and a server-side component in generating a
* salt. This way, a comprimised server cannot benefit from sending the same seed value
* for every user. We mix a client-controlled value that is globally unique
* (their identifier), with a server controlled value to produce a salt for our KDF.
* @param identifier
* @param seed
*/
private async generateSalt004(identifier: string, seed: string) {
const hash = await this.crypto.sha256([identifier, seed].join(PARTITION_CHARACTER))
return Utils.truncateHexString(hash, V004Algorithm.ArgonSaltLength)
createRandomizedKeySystemRootKey(dto: { systemIdentifier: KeySystemIdentifier }): KeySystemRootKeyInterface {
const usecase = new CreateRandomKeySystemRootKey(this.crypto)
return usecase.execute(dto)
}
/**
* Computes a root key given a passworf
* qwd and previous keyParams
* @param password - Plain string representing raw user password
* @param keyParams - KeyParams object
*/
public async computeRootKey(password: string, keyParams: SNRootKeyParams): Promise<SNRootKey> {
return this.deriveKey(password, keyParams)
createUserInputtedKeySystemRootKey(dto: {
systemIdentifier: KeySystemIdentifier
userInputtedPassword: string
}): KeySystemRootKeyInterface {
const usecase = new CreateUserInputKeySystemRootKey(this.crypto)
return usecase.execute(dto)
}
/**
* Creates a new root key given an identifier and a user password
* @param identifier - Plain string representing a unique identifier
* @param password - Plain string representing raw user password
*/
public async createRootKey(
deriveUserInputtedKeySystemRootKey(dto: {
keyParams: KeySystemRootKeyParamsInterface
userInputtedPassword: string
}): KeySystemRootKeyInterface {
const usecase = new DeriveKeySystemRootKeyUseCase(this.crypto)
return usecase.execute({
keyParams: dto.keyParams,
password: dto.userInputtedPassword,
})
}
public createKeySystemItemsKey(
uuid: string,
keySystemIdentifier: KeySystemIdentifier,
sharedVaultUuid: string | undefined,
rootKeyToken: string,
): KeySystemItemsKeyInterface {
const usecase = new CreateKeySystemItemsKeyUseCase(this.crypto)
return usecase.execute({ uuid, keySystemIdentifier, sharedVaultUuid, rootKeyToken })
}
public async computeRootKey<K extends RootKeyInterface>(
password: Utf8String,
keyParams: SNRootKeyParams,
): Promise<K> {
const usecase = new DeriveRootKeyUseCase(this.crypto)
return usecase.execute(password, keyParams)
}
public async createRootKey<K extends RootKeyInterface>(
identifier: string,
password: string,
password: Utf8String,
origination: KeyParamsOrigination,
): Promise<SNRootKey> {
const version = ProtocolVersion.V004
const seed = this.crypto.generateRandomKey(V004Algorithm.ArgonSaltSeedLength)
const keyParams = Create004KeyParams({
identifier: identifier,
pw_nonce: seed,
version: version,
origination: origination,
created: `${Date.now()}`,
})
return this.deriveKey(password, keyParams)
): Promise<K> {
const usecase = new CreateRootKeyUseCase(this.crypto)
return usecase.execute(identifier, password, origination)
}
/**
* @param plaintext - The plaintext to encrypt.
* @param rawKey - The key to use to encrypt the plaintext.
* @param nonce - The nonce for encryption.
* @param authenticatedData - JavaScript object (will be stringified) representing
'Additional authenticated data': data you want to be included in authentication.
*/
encryptString004(plaintext: string, rawKey: string, nonce: string, authenticatedData: ItemAuthenticatedData) {
if (!nonce) {
throw 'encryptString null nonce'
}
if (!rawKey) {
throw 'encryptString null rawKey'
}
return this.crypto.xchacha20Encrypt(plaintext, nonce, rawKey, this.authenticatedDataToString(authenticatedData))
}
/**
* @param ciphertext The encrypted text to decrypt.
* @param rawKey The key to use to decrypt the ciphertext.
* @param nonce The nonce for decryption.
* @param rawAuthenticatedData String representing
'Additional authenticated data' - data you want to be included in authentication.
*/
private decryptString004(ciphertext: string, rawKey: string, nonce: string, rawAuthenticatedData: string) {
return this.crypto.xchacha20Decrypt(ciphertext, nonce, rawKey, rawAuthenticatedData)
}
generateEncryptionNonce(): string {
return this.crypto.generateRandomKey(V004Algorithm.EncryptionNonceLength)
}
/**
* @param plaintext The plaintext text to decrypt.
* @param rawKey The key to use to encrypt the plaintext.
*/
generateEncryptedProtocolString(plaintext: string, rawKey: string, authenticatedData: ItemAuthenticatedData) {
const nonce = this.generateEncryptionNonce()
const ciphertext = this.encryptString004(plaintext, rawKey, nonce, authenticatedData)
const components: V004StringComponents = [
ProtocolVersion.V004 as string,
nonce,
ciphertext,
this.authenticatedDataToString(authenticatedData),
]
return components.join(PARTITION_CHARACTER)
}
deconstructEncryptedPayloadString(payloadString: string): V004Components {
const components = payloadString.split(PARTITION_CHARACTER) as V004StringComponents
return {
version: components[0],
nonce: components[1],
ciphertext: components[2],
authenticatedData: components[3],
}
}
public getPayloadAuthenticatedData(
encrypted: EncryptedParameters,
public getPayloadAuthenticatedDataForExternalUse(
encrypted: EncryptedOutputParameters,
): RootKeyEncryptedAuthenticatedData | ItemAuthenticatedData | LegacyAttachedData | undefined {
const itemKeyComponents = this.deconstructEncryptedPayloadString(encrypted.enc_item_key)
const authenticatedDataString = itemKeyComponents.authenticatedData
const result = this.stringToAuthenticatedData(authenticatedDataString)
return result
const usecase = new GetPayloadAuthenticatedDataDetachedUseCase(this.crypto)
return usecase.execute(encrypted)
}
/**
* For items that are encrypted with a root key, we append the root key's key params, so
* that in the event the client/user loses a reference to their root key, they may still
* decrypt data by regenerating the key based on the attached key params.
*/
private generateAuthenticatedDataForPayload(
payload: Models.DecryptedPayloadInterface,
key: ItemsKeyInterface | SNRootKey,
): ItemAuthenticatedData | RootKeyEncryptedAuthenticatedData {
const baseData: ItemAuthenticatedData = {
u: payload.uuid,
v: ProtocolVersion.V004,
}
if (ContentTypeUsesRootKeyEncryption(payload.content_type)) {
return {
...baseData,
kp: (key as SNRootKey).keyParams.content,
}
} else {
if (!isItemsKey(key)) {
throw Error('Attempting to use non-items key for regular item.')
}
return baseData
}
public generateEncryptedParameters(
payload: DecryptedPayloadInterface,
key: ItemsKeyInterface | KeySystemItemsKeyInterface | KeySystemRootKeyInterface | RootKeyInterface,
signingKeyPair?: PkcKeyPair,
): EncryptedOutputParameters {
const usecase = new GenerateEncryptedParametersUseCase(this.crypto)
return usecase.execute(payload, key, signingKeyPair)
}
private authenticatedDataToString(attachedData: ItemAuthenticatedData) {
return this.crypto.base64Encode(JSON.stringify(Utils.sortedCopy(Utils.omitUndefinedCopy(attachedData))))
}
private stringToAuthenticatedData(
rawAuthenticatedData: string,
override?: Partial<ItemAuthenticatedData>,
): RootKeyEncryptedAuthenticatedData | ItemAuthenticatedData {
const base = JSON.parse(this.crypto.base64Decode(rawAuthenticatedData))
return Utils.sortedCopy({
...base,
...override,
})
}
public generateEncryptedParametersSync(
payload: Models.DecryptedPayloadInterface,
key: ItemsKeyInterface | SNRootKey,
): EncryptedParameters {
const itemKey = this.crypto.generateRandomKey(V004Algorithm.EncryptionKeyLength)
const contentPlaintext = JSON.stringify(payload.content)
const authenticatedData = this.generateAuthenticatedDataForPayload(payload, key)
const encryptedContentString = this.generateEncryptedProtocolString(contentPlaintext, itemKey, authenticatedData)
const encryptedItemKey = this.generateEncryptedProtocolString(itemKey, key.itemsKey, authenticatedData)
return {
uuid: payload.uuid,
items_key_id: isItemsKey(key) ? key.uuid : undefined,
content: encryptedContentString,
enc_item_key: encryptedItemKey,
version: this.version,
}
}
public generateDecryptedParametersSync<C extends ItemContent = ItemContent>(
encrypted: EncryptedParameters,
key: ItemsKeyInterface | SNRootKey,
public generateDecryptedParameters<C extends ItemContent = ItemContent>(
encrypted: EncryptedInputParameters,
key: ItemsKeyInterface | KeySystemItemsKeyInterface | KeySystemRootKeyInterface | RootKeyInterface,
): DecryptedParameters<C> | ErrorDecryptingParameters {
const contentKeyComponents = this.deconstructEncryptedPayloadString(encrypted.enc_item_key)
const authenticatedData = this.stringToAuthenticatedData(contentKeyComponents.authenticatedData, {
u: encrypted.uuid,
v: encrypted.version,
})
const usecase = new GenerateDecryptedParametersUseCase(this.crypto)
return usecase.execute(encrypted, key)
}
const useAuthenticatedString = this.authenticatedDataToString(authenticatedData)
const contentKey = this.decryptString004(
contentKeyComponents.ciphertext,
key.itemsKey,
contentKeyComponents.nonce,
useAuthenticatedString,
)
public asymmetricEncrypt(dto: {
stringToEncrypt: Utf8String
senderKeyPair: PkcKeyPair
senderSigningKeyPair: PkcKeyPair
recipientPublicKey: HexString
}): AsymmetricallyEncryptedString {
const usecase = new AsymmetricEncryptUseCase(this.crypto)
return usecase.execute(dto)
}
if (!contentKey) {
console.error('Error decrypting itemKey parameters', encrypted)
return {
uuid: encrypted.uuid,
errorDecrypting: true,
}
}
asymmetricDecrypt(dto: {
stringToDecrypt: AsymmetricallyEncryptedString
recipientSecretKey: HexString
}): AsymmetricDecryptResult | null {
const usecase = new AsymmetricDecryptUseCase(this.crypto)
return usecase.execute(dto)
}
const contentComponents = this.deconstructEncryptedPayloadString(encrypted.content)
const content = this.decryptString004(
contentComponents.ciphertext,
contentKey,
contentComponents.nonce,
useAuthenticatedString,
)
asymmetricSignatureVerifyDetached(
encryptedString: AsymmetricallyEncryptedString,
): AsymmetricSignatureVerificationDetachedResult {
const usecase = new AsymmetricSignatureVerificationDetachedUseCase(this.crypto)
return usecase.execute({ encryptedString })
}
if (!content) {
return {
uuid: encrypted.uuid,
errorDecrypting: true,
}
} else {
return {
uuid: encrypted.uuid,
content: JSON.parse(content),
}
getSenderPublicKeySetFromAsymmetricallyEncryptedString(string: AsymmetricallyEncryptedString): PublicKeySet {
const [_, __, ___, additionalDataString] = <V004AsymmetricStringComponents>string.split(':')
const parseBase64Usecase = new ParseConsistentBase64JsonPayloadUseCase(this.crypto)
const additionalData = parseBase64Usecase.execute<AsymmetricItemAdditionalData>(additionalDataString)
return {
encryption: additionalData.senderPublicKey,
signing: additionalData.signingData.publicKey,
}
}
private async deriveKey(password: string, keyParams: SNRootKeyParams): Promise<SNRootKey> {
const salt = await this.generateSalt004(keyParams.content004.identifier, keyParams.content004.pw_nonce)
const derivedKey = this.crypto.argon2(
password,
salt,
V004Algorithm.ArgonIterations,
V004Algorithm.ArgonMemLimit,
V004Algorithm.ArgonOutputKeyBytes,
)
const partitions = Utils.splitString(derivedKey, 2)
const masterKey = partitions[0]
const serverPassword = partitions[1]
return CreateNewRootKey({
masterKey,
serverPassword,
version: ProtocolVersion.V004,
keyParams: keyParams.getPortableValue(),
})
versionForAsymmetricallyEncryptedString(string: string): ProtocolVersion {
const [versionPrefix] = <V004AsymmetricStringComponents>string.split(':')
const version = versionPrefix.split('_')[0]
return version as ProtocolVersion
}
}

View File

@@ -0,0 +1,81 @@
import { PkcKeyPair, PureCryptoInterface } from '@standardnotes/sncrypto-common'
import { getMockedCrypto } from '../../MockedCrypto'
import { AsymmetricDecryptUseCase } from './AsymmetricDecrypt'
import { AsymmetricEncryptUseCase } from './AsymmetricEncrypt'
import { V004AsymmetricStringComponents } from '../../V004AlgorithmTypes'
import { AsymmetricItemAdditionalData } from '../../../../Types/EncryptionAdditionalData'
describe('asymmetric decrypt use case', () => {
let crypto: PureCryptoInterface
let usecase: AsymmetricDecryptUseCase
let recipientKeyPair: PkcKeyPair
let senderKeyPair: PkcKeyPair
let senderSigningKeyPair: PkcKeyPair
beforeEach(() => {
crypto = getMockedCrypto()
usecase = new AsymmetricDecryptUseCase(crypto)
recipientKeyPair = crypto.sodiumCryptoBoxSeedKeypair('recipient-seedling')
senderKeyPair = crypto.sodiumCryptoBoxSeedKeypair('sender-seedling')
senderSigningKeyPair = crypto.sodiumCryptoSignSeedKeypair('sender-signing-seedling')
})
const getEncryptedString = () => {
const encryptUsecase = new AsymmetricEncryptUseCase(crypto)
const result = encryptUsecase.execute({
stringToEncrypt: 'foobar',
senderKeyPair: senderKeyPair,
senderSigningKeyPair: senderSigningKeyPair,
recipientPublicKey: recipientKeyPair.publicKey,
})
return result
}
it('should generate decrypted string', () => {
const encryptedString = getEncryptedString()
const decrypted = usecase.execute({
stringToDecrypt: encryptedString,
recipientSecretKey: recipientKeyPair.privateKey,
})
expect(decrypted).toEqual({
plaintext: 'foobar',
signatureVerified: true,
signaturePublicKey: senderSigningKeyPair.publicKey,
senderPublicKey: senderKeyPair.publicKey,
})
})
it('should fail signature verification if signature is changed', () => {
const encryptedString = getEncryptedString()
const [version, nonce, ciphertext] = <V004AsymmetricStringComponents>encryptedString.split(':')
const corruptAdditionalData: AsymmetricItemAdditionalData = {
signingData: {
publicKey: senderSigningKeyPair.publicKey,
signature: 'corrupt',
},
senderPublicKey: senderKeyPair.publicKey,
}
const corruptedAdditionalDataString = crypto.base64Encode(JSON.stringify(corruptAdditionalData))
const corruptEncryptedString = [version, nonce, ciphertext, corruptedAdditionalDataString].join(':')
const decrypted = usecase.execute({
stringToDecrypt: corruptEncryptedString,
recipientSecretKey: recipientKeyPair.privateKey,
})
expect(decrypted).toEqual({
plaintext: 'foobar',
signatureVerified: false,
signaturePublicKey: senderSigningKeyPair.publicKey,
senderPublicKey: senderKeyPair.publicKey,
})
})
})

View File

@@ -0,0 +1,48 @@
import { HexString, PureCryptoInterface } from '@standardnotes/sncrypto-common'
import { AsymmetricallyEncryptedString } from '../../../Types/Types'
import { V004AsymmetricStringComponents } from '../../V004AlgorithmTypes'
import { ParseConsistentBase64JsonPayloadUseCase } from '../Utils/ParseConsistentBase64JsonPayload'
import { AsymmetricItemAdditionalData } from '../../../../Types/EncryptionAdditionalData'
import { AsymmetricDecryptResult } from '../../../Types/AsymmetricDecryptResult'
export class AsymmetricDecryptUseCase {
private parseBase64Usecase = new ParseConsistentBase64JsonPayloadUseCase(this.crypto)
constructor(private readonly crypto: PureCryptoInterface) {}
execute(dto: {
stringToDecrypt: AsymmetricallyEncryptedString
recipientSecretKey: HexString
}): AsymmetricDecryptResult | null {
const [_, nonce, ciphertext, additionalDataString] = <V004AsymmetricStringComponents>dto.stringToDecrypt.split(':')
const additionalData = this.parseBase64Usecase.execute<AsymmetricItemAdditionalData>(additionalDataString)
try {
const plaintext = this.crypto.sodiumCryptoBoxEasyDecrypt(
ciphertext,
nonce,
additionalData.senderPublicKey,
dto.recipientSecretKey,
)
if (!plaintext) {
return null
}
const signatureVerified = this.crypto.sodiumCryptoSignVerify(
ciphertext,
additionalData.signingData.signature,
additionalData.signingData.publicKey,
)
return {
plaintext,
signatureVerified,
signaturePublicKey: additionalData.signingData.publicKey,
senderPublicKey: additionalData.senderPublicKey,
}
} catch (error) {
return null
}
}
}

View File

@@ -0,0 +1,45 @@
import { PkcKeyPair, PureCryptoInterface } from '@standardnotes/sncrypto-common'
import { getMockedCrypto } from '../../MockedCrypto'
import { AsymmetricEncryptUseCase } from './AsymmetricEncrypt'
import { V004AsymmetricStringComponents } from '../../V004AlgorithmTypes'
import { ParseConsistentBase64JsonPayloadUseCase } from '../Utils/ParseConsistentBase64JsonPayload'
import { AsymmetricItemAdditionalData } from '../../../../Types/EncryptionAdditionalData'
describe('asymmetric encrypt use case', () => {
let crypto: PureCryptoInterface
let usecase: AsymmetricEncryptUseCase
let encryptionKeyPair: PkcKeyPair
let signingKeyPair: PkcKeyPair
let parseBase64Usecase: ParseConsistentBase64JsonPayloadUseCase
beforeEach(() => {
crypto = getMockedCrypto()
usecase = new AsymmetricEncryptUseCase(crypto)
encryptionKeyPair = crypto.sodiumCryptoBoxSeedKeypair('seedling')
signingKeyPair = crypto.sodiumCryptoSignSeedKeypair('seedling')
parseBase64Usecase = new ParseConsistentBase64JsonPayloadUseCase(crypto)
})
it('should generate encrypted string', () => {
const recipientKeyPair = crypto.sodiumCryptoBoxSeedKeypair('recipient-seedling')
const result = usecase.execute({
stringToEncrypt: 'foobar',
senderKeyPair: encryptionKeyPair,
senderSigningKeyPair: signingKeyPair,
recipientPublicKey: recipientKeyPair.publicKey,
})
const [version, nonce, ciphertext, additionalDataString] = <V004AsymmetricStringComponents>result.split(':')
expect(version).toEqual('004_Asym')
expect(nonce).toEqual(expect.any(String))
expect(ciphertext).toEqual(expect.any(String))
expect(additionalDataString).toEqual(expect.any(String))
const additionalData = parseBase64Usecase.execute<AsymmetricItemAdditionalData>(additionalDataString)
expect(additionalData.signingData.publicKey).toEqual(signingKeyPair.publicKey)
expect(additionalData.signingData.signature).toEqual(expect.any(String))
expect(additionalData.senderPublicKey).toEqual(encryptionKeyPair.publicKey)
})
})

View File

@@ -0,0 +1,45 @@
import { HexString, PkcKeyPair, PureCryptoInterface, Utf8String } from '@standardnotes/sncrypto-common'
import { AsymmetricallyEncryptedString } from '../../../Types/Types'
import { V004Algorithm } from '../../../../Algorithm'
import { V004AsymmetricCiphertextPrefix, V004AsymmetricStringComponents } from '../../V004AlgorithmTypes'
import { CreateConsistentBase64JsonPayloadUseCase } from '../Utils/CreateConsistentBase64JsonPayload'
import { AsymmetricItemAdditionalData } from '../../../../Types/EncryptionAdditionalData'
export class AsymmetricEncryptUseCase {
private base64DataUsecase = new CreateConsistentBase64JsonPayloadUseCase(this.crypto)
constructor(private readonly crypto: PureCryptoInterface) {}
execute(dto: {
stringToEncrypt: Utf8String
senderKeyPair: PkcKeyPair
senderSigningKeyPair: PkcKeyPair
recipientPublicKey: HexString
}): AsymmetricallyEncryptedString {
const nonce = this.crypto.generateRandomKey(V004Algorithm.AsymmetricEncryptionNonceLength)
const ciphertext = this.crypto.sodiumCryptoBoxEasyEncrypt(
dto.stringToEncrypt,
nonce,
dto.senderKeyPair.privateKey,
dto.recipientPublicKey,
)
const additionalData: AsymmetricItemAdditionalData = {
signingData: {
publicKey: dto.senderSigningKeyPair.publicKey,
signature: this.crypto.sodiumCryptoSign(ciphertext, dto.senderSigningKeyPair.privateKey),
},
senderPublicKey: dto.senderKeyPair.publicKey,
}
const components: V004AsymmetricStringComponents = [
V004AsymmetricCiphertextPrefix,
nonce,
ciphertext,
this.base64DataUsecase.execute(additionalData),
]
return components.join(':')
}
}

View File

@@ -0,0 +1,36 @@
import { PureCryptoInterface } from '@standardnotes/sncrypto-common'
import { AsymmetricallyEncryptedString } from '../../../Types/Types'
import { V004AsymmetricStringComponents } from '../../V004AlgorithmTypes'
import { ParseConsistentBase64JsonPayloadUseCase } from '../Utils/ParseConsistentBase64JsonPayload'
import { AsymmetricItemAdditionalData } from '../../../../Types/EncryptionAdditionalData'
import { AsymmetricSignatureVerificationDetachedResult } from '../../../Types/AsymmetricSignatureVerificationDetachedResult'
export class AsymmetricSignatureVerificationDetachedUseCase {
private parseBase64Usecase = new ParseConsistentBase64JsonPayloadUseCase(this.crypto)
constructor(private readonly crypto: PureCryptoInterface) {}
execute(dto: { encryptedString: AsymmetricallyEncryptedString }): AsymmetricSignatureVerificationDetachedResult {
const [_, __, ciphertext, additionalDataString] = <V004AsymmetricStringComponents>dto.encryptedString.split(':')
const additionalData = this.parseBase64Usecase.execute<AsymmetricItemAdditionalData>(additionalDataString)
try {
const signatureVerified = this.crypto.sodiumCryptoSignVerify(
ciphertext,
additionalData.signingData.signature,
additionalData.signingData.publicKey,
)
return {
signatureVerified,
signaturePublicKey: additionalData.signingData.publicKey,
senderPublicKey: additionalData.senderPublicKey,
}
} catch (error) {
return {
signatureVerified: false,
}
}
}
}

Some files were not shown because too many files have changed in this diff Show More