diff --git a/packages/encryption/src/Domain/Operator/001/Operator001.ts b/packages/encryption/src/Domain/Operator/001/Operator001.ts index 1d9d8d492..8850ada19 100644 --- a/packages/encryption/src/Domain/Operator/001/Operator001.ts +++ b/packages/encryption/src/Domain/Operator/001/Operator001.ts @@ -12,7 +12,7 @@ import { DecryptedPayloadInterface, PayloadTimestampDefaults, } from '@standardnotes/models' -import { SNPureCrypto } from '@standardnotes/sncrypto-common' +import { PureCryptoInterface } from '@standardnotes/sncrypto-common' import { SNRootKey } from '../../RootKey/RootKey' import { SNRootKeyParams } from '../../RootKey/RootKeyParams' import { V001Algorithm } from '../../Algorithm' @@ -34,9 +34,9 @@ const NO_IV = '00000000000000000000000000000000' * A legacy operator no longer used to generate new accounts */ export class SNProtocolOperator001 implements AsynchronousOperator { - protected readonly crypto: SNPureCrypto + protected readonly crypto: PureCryptoInterface - constructor(crypto: SNPureCrypto) { + constructor(crypto: PureCryptoInterface) { this.crypto = crypto } diff --git a/packages/encryption/src/Domain/Operator/004/Operator004.ts b/packages/encryption/src/Domain/Operator/004/Operator004.ts index 40f793491..b6b76ef5b 100644 --- a/packages/encryption/src/Domain/Operator/004/Operator004.ts +++ b/packages/encryption/src/Domain/Operator/004/Operator004.ts @@ -9,7 +9,7 @@ import { ItemsKeyInterface, PayloadTimestampDefaults, } from '@standardnotes/models' -import { SNPureCrypto } from '@standardnotes/sncrypto-common' +import { PureCryptoInterface } from '@standardnotes/sncrypto-common' import { SNRootKey } from '../../RootKey/RootKey' import { SNRootKeyParams } from '../../RootKey/RootKeyParams' import { V004Algorithm } from '../../Algorithm' @@ -30,9 +30,9 @@ import { CreateNewRootKey } from '../../RootKey/Functions' const PARTITION_CHARACTER = ':' export class SNProtocolOperator004 implements SynchronousOperator { - protected readonly crypto: SNPureCrypto + protected readonly crypto: PureCryptoInterface - constructor(crypto: SNPureCrypto) { + constructor(crypto: PureCryptoInterface) { this.crypto = crypto } diff --git a/packages/encryption/src/Domain/Operator/Functions.ts b/packages/encryption/src/Domain/Operator/Functions.ts index 193d2aeaa..a83095c78 100644 --- a/packages/encryption/src/Domain/Operator/Functions.ts +++ b/packages/encryption/src/Domain/Operator/Functions.ts @@ -1,4 +1,4 @@ -import { SNPureCrypto } from '@standardnotes/sncrypto-common' +import { PureCryptoInterface } from '@standardnotes/sncrypto-common' import { SNProtocolOperator001 } from '../Operator/001/Operator001' import { SNProtocolOperator002 } from '../Operator/002/Operator002' import { SNProtocolOperator003 } from '../Operator/003/Operator003' @@ -7,7 +7,7 @@ import { AsynchronousOperator, SynchronousOperator } from '../Operator/Operator' import { AnyOperator } from '../Operator/AnyOperator' import { ProtocolVersion } from '@standardnotes/common' -export function createOperatorForVersion(version: ProtocolVersion, crypto: SNPureCrypto): AnyOperator { +export function createOperatorForVersion(version: ProtocolVersion, crypto: PureCryptoInterface): AnyOperator { if (version === ProtocolVersion.V001) { return new SNProtocolOperator001(crypto) } else if (version === ProtocolVersion.V002) { diff --git a/packages/encryption/src/Domain/Operator/OperatorManager.ts b/packages/encryption/src/Domain/Operator/OperatorManager.ts index 7988e6e2c..1e646c2b7 100644 --- a/packages/encryption/src/Domain/Operator/OperatorManager.ts +++ b/packages/encryption/src/Domain/Operator/OperatorManager.ts @@ -1,4 +1,4 @@ -import { SNPureCrypto } from '@standardnotes/sncrypto-common' +import { PureCryptoInterface } from '@standardnotes/sncrypto-common' import { ProtocolVersion, ProtocolVersionLatest } from '@standardnotes/common' import { createOperatorForVersion } from './Functions' import { AnyOperator } from './AnyOperator' @@ -6,7 +6,7 @@ import { AnyOperator } from './AnyOperator' export class OperatorManager { private operators: Record = {} - constructor(private crypto: SNPureCrypto) { + constructor(private crypto: PureCryptoInterface) { this.crypto = crypto } diff --git a/packages/encryption/src/Domain/Service/Encryption/EncryptionService.ts b/packages/encryption/src/Domain/Service/Encryption/EncryptionService.ts index ceb97aee5..f236a2b28 100644 --- a/packages/encryption/src/Domain/Service/Encryption/EncryptionService.ts +++ b/packages/encryption/src/Domain/Service/Encryption/EncryptionService.ts @@ -6,7 +6,7 @@ import { findDefaultItemsKey } from '../Functions' import { ItemsEncryptionService } from '../Items/ItemsEncryption' import { KeyMode } from '../RootKey/KeyMode' import { OperatorManager } from '../../Operator/OperatorManager' -import { SNPureCrypto } from '@standardnotes/sncrypto-common' +import { PureCryptoInterface } from '@standardnotes/sncrypto-common' import { SNRootKey } from '../../RootKey/RootKey' import { SNRootKeyParams } from '../../RootKey/RootKeyParams' import { V001Algorithm, V002Algorithm } from '../../Algorithm' @@ -86,7 +86,7 @@ export class EncryptionService extends Services.AbstractService { diff --git a/packages/sncrypto-common/src/AES-GCM/Aes256GcmEncrypted.ts b/packages/sncrypto-common/src/AES-GCM/Aes256GcmEncrypted.ts new file mode 100644 index 000000000..e3738a55e --- /dev/null +++ b/packages/sncrypto-common/src/AES-GCM/Aes256GcmEncrypted.ts @@ -0,0 +1,17 @@ +import { Base64String } from '../Types/Base64String' +import { HexString } from '../Types/HexString' + +/** + * @param iv initialization vector as a hex string + * @param tag authentication tag as a hex string + * @param ciphertext as a base64 string + * @param encoding that will be applied after decrypting + * @param aad additional authenticated data as a hex string + */ + export type Aes256GcmEncrypted = { + iv: HexString + tag: HexString + ciphertext: Base64String + encoding: EncodingType + aad: HexString +} diff --git a/packages/sncrypto-common/src/AES-GCM/Aes256GcmInput.ts b/packages/sncrypto-common/src/AES-GCM/Aes256GcmInput.ts new file mode 100644 index 000000000..e34fe929d --- /dev/null +++ b/packages/sncrypto-common/src/AES-GCM/Aes256GcmInput.ts @@ -0,0 +1,15 @@ +import { HexString } from '../Types/HexString' +import { Unencrypted } from '../Types/Unencrypted' + +/** + * @param unencrypted -- UTF-8 string or a `string` with `encoding` + * @param iv initialization vector as a hex string + * @param key encryption key as a hex string + * @param aad additional authenticated data as a hex string + */ + export type Aes256GcmInput = { + unencrypted: Unencrypted + iv: HexString + key: HexString + aad?: HexString +} diff --git a/packages/sncrypto-common/src/AES-GCM/CryptoAes256GcmInterface.ts b/packages/sncrypto-common/src/AES-GCM/CryptoAes256GcmInterface.ts new file mode 100644 index 000000000..ec51e7304 --- /dev/null +++ b/packages/sncrypto-common/src/AES-GCM/CryptoAes256GcmInterface.ts @@ -0,0 +1,20 @@ +import { HexString } from '../Types/HexString' +import { Aes256GcmEncrypted } from './Aes256GcmEncrypted' +import { Aes256GcmInput } from './Aes256GcmInput' + +export interface CryptoAes256GcmInterface { + /** + * Encrypts a string using AES-GCM. + * @param input + * @returns An object which can be run through aes256GcmDecrypt to retrieve the input text. + */ + aes256GcmEncrypt(input: Aes256GcmInput): Promise> + + /** + * Decrypts a string using AES-GCM. + * @param encrypted + * @param key - encryption key as a hex string + * @returns A string encoded with encoding provided in the input + */ + aes256GcmDecrypt(encrypted: Aes256GcmEncrypted, key: HexString): Promise +} diff --git a/packages/sncrypto-common/src/Base64/CryptoBase64Interface.ts b/packages/sncrypto-common/src/Base64/CryptoBase64Interface.ts new file mode 100644 index 000000000..2e50da476 --- /dev/null +++ b/packages/sncrypto-common/src/Base64/CryptoBase64Interface.ts @@ -0,0 +1,25 @@ +import { Base64String } from '../Types/Base64String' +import { Utf8String } from '../Types/Utf8String' + +export interface CryptoBase64Interface { + /** + * Converts a plain string into base64 + * @param text - A plain string + * @returns A base64 encoded string + */ + base64Encode(text: Utf8String): Base64String + + /** + * Converts a plain string into url-safe base64 + * @param text - A plain string + * @returns A base64 encoded string + */ + base64URLEncode(text: Utf8String): Base64String + + /** + * Converts a base64 string into a plain string + * @param base64String - A base64 encoded string + * @returns A plain string + */ + base64Decode(base64String: Base64String): Utf8String +} diff --git a/packages/sncrypto-common/src/pure_crypto.ts b/packages/sncrypto-common/src/Common/PureCryptoInterface.ts similarity index 85% rename from packages/sncrypto-common/src/pure_crypto.ts rename to packages/sncrypto-common/src/Common/PureCryptoInterface.ts index 3f8d1a5cd..37b153591 100644 --- a/packages/sncrypto-common/src/pure_crypto.ts +++ b/packages/sncrypto-common/src/Common/PureCryptoInterface.ts @@ -1,39 +1,14 @@ -export type HexString = string -export type Utf8String = string -export type Base64String = string - -type SodiumStateAddress = unknown - -export type StreamEncryptor = { - state: SodiumStateAddress - header: Base64String -} - -export type StreamDecryptor = { - state: SodiumStateAddress -} - -export type StreamDecryptorResult = { - message: Uint8Array - tag: SodiumConstant -} - -export enum SodiumConstant { - CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_STATEBYTES = 52, - CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES = 17, - CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_HEADERBYTES = 24, - CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_KEYBYTES = 32, - CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_TAG_PUSH = 0, - CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_TAG_PULL = 1, - CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_TAG_REKEY = 2, - CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_TAG_FINAL = 3, - CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_MESSAGEBYTES_MAX = 0x3fffffff80, -} +import { Base64String } from '../Types/Base64String' +import { HexString } from '../Types/HexString' +import { SodiumConstant } from '../Types/SodiumConstant' +import { StreamDecryptor } from '../Types/StreamDecryptor' +import { StreamEncryptor } from '../Types/StreamEncryptor' +import { Utf8String } from '../Types/Utf8String' /** * Interface that clients have to implement to use snjs */ -export interface SNPureCrypto { +export interface PureCryptoInterface { initialize(): Promise /** diff --git a/packages/sncrypto-common/src/utils.ts b/packages/sncrypto-common/src/Common/Utils.ts similarity index 100% rename from packages/sncrypto-common/src/utils.ts rename to packages/sncrypto-common/src/Common/Utils.ts diff --git a/packages/sncrypto-common/src/SHA/CryptoSha256Interface.ts b/packages/sncrypto-common/src/SHA/CryptoSha256Interface.ts new file mode 100644 index 000000000..9547a8a54 --- /dev/null +++ b/packages/sncrypto-common/src/SHA/CryptoSha256Interface.ts @@ -0,0 +1,6 @@ +import { HexString } from '../Types/HexString' +import { Utf8String } from '../Types/Utf8String' + +export interface CryptoSha256Interface { + sha256(text: Utf8String): HexString +} diff --git a/packages/sncrypto-common/src/SnCryptoAes256Gcm.ts b/packages/sncrypto-common/src/SnCryptoAes256Gcm.ts deleted file mode 100644 index a8aa024f8..000000000 --- a/packages/sncrypto-common/src/SnCryptoAes256Gcm.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { HexString, Utf8String } from '.' -import { Base64String } from './pure_crypto' - -/** - * Either a plaintext (UTF-8 string) or a `string` with an `encoding`. - */ -export type Unencrypted = Utf8String | { string: string; encoding: EncodingType } - -/** - * @param unencrypted -- UTF-8 string or a `string` with `encoding` - * @param iv initialization vector as a hex string - * @param key encryption key as a hex string - * @param aad additional authenticated data as a hex string - */ -export type Aes256GcmInput = { - unencrypted: Unencrypted - iv: HexString - key: HexString - aad?: HexString -} - -/** - * @param iv initialization vector as a hex string - * @param tag authentication tag as a hex string - * @param ciphertext as a base64 string - * @param encoding that will be applied after decrypting - * @param aad additional authenticated data as a hex string - */ -export type Aes256GcmEncrypted = { - iv: HexString - tag: HexString - ciphertext: Base64String - encoding: EncodingType - aad: HexString -} - -export interface SnCryptoAes256Gcm { - /** - * Encrypts a string using AES-GCM. - * @param input - * @returns An object which can be run through aes256GcmDecrypt to retrieve the input text. - */ - aes256GcmEncrypt(input: Aes256GcmInput): Promise> - - /** - * Decrypts a string using AES-GCM. - * @param encrypted - * @param key - encryption key as a hex string - * @returns A string encoded with encoding provided in the input - */ - aes256GcmDecrypt(encrypted: Aes256GcmEncrypted, key: HexString): Promise -} diff --git a/packages/sncrypto-common/src/Types/Base64String.ts b/packages/sncrypto-common/src/Types/Base64String.ts new file mode 100644 index 000000000..f1f3bcfa8 --- /dev/null +++ b/packages/sncrypto-common/src/Types/Base64String.ts @@ -0,0 +1 @@ +export type Base64String = string diff --git a/packages/sncrypto-common/src/Types/HexString.ts b/packages/sncrypto-common/src/Types/HexString.ts new file mode 100644 index 000000000..58300f975 --- /dev/null +++ b/packages/sncrypto-common/src/Types/HexString.ts @@ -0,0 +1 @@ +export type HexString = string diff --git a/packages/sncrypto-common/src/Types/SodiumConstant.ts b/packages/sncrypto-common/src/Types/SodiumConstant.ts new file mode 100644 index 000000000..e6648c5ea --- /dev/null +++ b/packages/sncrypto-common/src/Types/SodiumConstant.ts @@ -0,0 +1,11 @@ +export enum SodiumConstant { + CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_STATEBYTES = 52, + CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES = 17, + CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_HEADERBYTES = 24, + CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_KEYBYTES = 32, + CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_TAG_PUSH = 0, + CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_TAG_PULL = 1, + CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_TAG_REKEY = 2, + CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_TAG_FINAL = 3, + CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_MESSAGEBYTES_MAX = 0x3fffffff80, +} diff --git a/packages/sncrypto-common/src/Types/SodiumStateAddress.ts b/packages/sncrypto-common/src/Types/SodiumStateAddress.ts new file mode 100644 index 000000000..f610b9b08 --- /dev/null +++ b/packages/sncrypto-common/src/Types/SodiumStateAddress.ts @@ -0,0 +1 @@ +export type SodiumStateAddress = unknown diff --git a/packages/sncrypto-common/src/Types/StreamDecryptor.ts b/packages/sncrypto-common/src/Types/StreamDecryptor.ts new file mode 100644 index 000000000..edb79e7ff --- /dev/null +++ b/packages/sncrypto-common/src/Types/StreamDecryptor.ts @@ -0,0 +1,5 @@ +import { SodiumStateAddress } from './SodiumStateAddress' + +export type StreamDecryptor = { + state: SodiumStateAddress +} diff --git a/packages/sncrypto-common/src/Types/StreamDecryptorResult.ts b/packages/sncrypto-common/src/Types/StreamDecryptorResult.ts new file mode 100644 index 000000000..9d8109a23 --- /dev/null +++ b/packages/sncrypto-common/src/Types/StreamDecryptorResult.ts @@ -0,0 +1,6 @@ +import { SodiumConstant } from './SodiumConstant' + +export type StreamDecryptorResult = { + message: Uint8Array + tag: SodiumConstant +} diff --git a/packages/sncrypto-common/src/Types/StreamEncryptor.ts b/packages/sncrypto-common/src/Types/StreamEncryptor.ts new file mode 100644 index 000000000..78d705115 --- /dev/null +++ b/packages/sncrypto-common/src/Types/StreamEncryptor.ts @@ -0,0 +1,7 @@ +import { Base64String } from './Base64String' +import { SodiumStateAddress } from './SodiumStateAddress' + +export type StreamEncryptor = { + state: SodiumStateAddress + header: Base64String +} diff --git a/packages/sncrypto-common/src/Types/Unencrypted.ts b/packages/sncrypto-common/src/Types/Unencrypted.ts new file mode 100644 index 000000000..4c610e42a --- /dev/null +++ b/packages/sncrypto-common/src/Types/Unencrypted.ts @@ -0,0 +1,6 @@ +import { Utf8String } from './Utf8String' + +/** + * Either a plaintext (UTF-8 string) or a `string` with an `encoding`. + */ + export type Unencrypted = Utf8String | { string: string; encoding: EncodingType } diff --git a/packages/sncrypto-common/src/Types/Utf8String.ts b/packages/sncrypto-common/src/Types/Utf8String.ts new file mode 100644 index 000000000..3e3e1c625 --- /dev/null +++ b/packages/sncrypto-common/src/Types/Utf8String.ts @@ -0,0 +1 @@ +export type Utf8String = string diff --git a/packages/sncrypto-common/src/index.ts b/packages/sncrypto-common/src/index.ts index 5eb1825c1..7c038a636 100644 --- a/packages/sncrypto-common/src/index.ts +++ b/packages/sncrypto-common/src/index.ts @@ -1,3 +1,20 @@ -export * from './pure_crypto' -export * from './utils' -export * from './SnCryptoAes256Gcm' +export * from './AES-GCM/Aes256GcmEncrypted' +export * from './AES-GCM/Aes256GcmInput' +export * from './AES-GCM/CryptoAes256GcmInterface' + +export * from './Base64/CryptoBase64Interface' + +export * from './Common/PureCryptoInterface' +export * from './Common/Utils' + +export * from './SHA/CryptoSha256Interface' + +export * from './Types/Base64String' +export * from './Types/HexString' +export * from './Types/SodiumConstant' +export * from './Types/SodiumStateAddress' +export * from './Types/StreamDecryptor' +export * from './Types/StreamDecryptorResult' +export * from './Types/StreamEncryptor' +export * from './Types/Unencrypted' +export * from './Types/Utf8String' diff --git a/packages/sncrypto-node/src/crypto.spec.ts b/packages/sncrypto-node/src/SnCryptoNode.spec.ts similarity index 88% rename from packages/sncrypto-node/src/crypto.spec.ts rename to packages/sncrypto-node/src/SnCryptoNode.spec.ts index 66d58ea67..644b833d0 100644 --- a/packages/sncrypto-node/src/crypto.spec.ts +++ b/packages/sncrypto-node/src/SnCryptoNode.spec.ts @@ -1,6 +1,6 @@ -import { SnCryptoNode } from './crypto' +import { SnCryptoNode } from './SnCryptoNode' -describe('crypto operations', function () { +describe('SnCryptoNode', function () { const crypto = new SnCryptoNode() it('aes gcm', async function () { @@ -155,4 +155,22 @@ describe('crypto operations', function () { expect(decrypted).toEqual(string) } }) + + it('should encrypt data with SHA256', () => { + expect(crypto.sha256('hello world 🌍')).toEqual( + 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855' + ) + }) + + it ('should base64 encode a utf8 string', () => { + expect(crypto.base64Encode('Hello World')).toEqual('SGVsbG8gV29ybGQ=') + }) + + it ('should base64 encode a utf8 string with url safe option', () => { + expect(crypto.base64URLEncode('Hello World')).toEqual('SGVsbG8gV29ybGQ') + }) + + it ('should base64 decode a utf8 string', () => { + expect(crypto.base64Decode('SGVsbG8gV29ybGQ=')).toEqual('Hello World') + }) }) diff --git a/packages/sncrypto-node/src/crypto.ts b/packages/sncrypto-node/src/SnCryptoNode.ts similarity index 50% rename from packages/sncrypto-node/src/crypto.ts rename to packages/sncrypto-node/src/SnCryptoNode.ts index 383e64fad..6a3b7f350 100644 --- a/packages/sncrypto-node/src/crypto.ts +++ b/packages/sncrypto-node/src/SnCryptoNode.ts @@ -2,13 +2,21 @@ import { Aes256GcmEncrypted, Aes256GcmInput, HexString, - SnCryptoAes256Gcm, - Unencrypted, + CryptoAes256GcmInterface, + CryptoSha256Interface, + CryptoBase64Interface, + Utf8String, + Base64String, } from '@standardnotes/sncrypto-common' -import { createCipheriv, createDecipheriv, randomBytes } from 'crypto' +import { createCipheriv, createDecipheriv, randomBytes, createHash } from 'crypto' -export class SnCryptoNode implements SnCryptoAes256Gcm { - public async aes256GcmEncrypt({ +import { getBufferWithEncoding } from './Utils' + +export class SnCryptoNode implements + CryptoAes256GcmInterface, + CryptoSha256Interface, + CryptoBase64Interface { + async aes256GcmEncrypt({ unencrypted, iv, key, @@ -28,7 +36,7 @@ export class SnCryptoNode implements SnCryptoAes256Gcm { return { iv, tag, aad, ciphertext, encoding } } - public async aes256GcmDecrypt(encrypted: Aes256GcmEncrypted, key: HexString): Promise { + async aes256GcmDecrypt(encrypted: Aes256GcmEncrypted, key: HexString): Promise { const { iv, tag, ciphertext, encoding, aad } = encrypted const decipher = createDecipheriv('aes-256-gcm', Buffer.from(key, 'hex'), Buffer.from(iv, 'hex')) @@ -41,28 +49,36 @@ export class SnCryptoNode implements SnCryptoAes256Gcm { return decrypted.toString(encoding) } - public async generateRandomKey(bits: number): Promise { + async generateRandomKey(bits: number): Promise { const bytes = bits / 8 const buf = randomBytes(bytes) + return buf.toString('hex') } -} -/** - * Turns `unencrypted` into a `buffer` with `encoding`. - * @param unencrypted - */ -function getBufferWithEncoding(unencrypted: Unencrypted): { - buffer: Buffer - encoding: BufferEncoding -} { - if (typeof unencrypted === 'string') { - const encoding: BufferEncoding = 'utf-8' - const buffer = Buffer.from(unencrypted, encoding) - return { buffer, encoding } + sha256(text: Utf8String): HexString { + const textData = Buffer.from(text, 'utf8') + + const hash = createHash('sha256', textData) + + return hash.digest('hex') } - const { string, encoding } = unencrypted - const buffer = Buffer.from(string, encoding) - return { buffer, encoding } + base64Encode(text: Utf8String): Base64String { + const { buffer } = getBufferWithEncoding({ string: text, encoding: 'utf8' }) + + return buffer.toString('base64') + } + + base64URLEncode(text: Utf8String): Base64String { + const { buffer } = getBufferWithEncoding({ string: text, encoding: 'utf8' }) + + return buffer.toString('base64url') + } + + base64Decode(base64String: Base64String): Utf8String { + const { buffer } = getBufferWithEncoding({ string: base64String, encoding: 'base64' }) + + return buffer.toString('utf8') + } } diff --git a/packages/sncrypto-node/src/Utils.ts b/packages/sncrypto-node/src/Utils.ts new file mode 100644 index 000000000..db9410012 --- /dev/null +++ b/packages/sncrypto-node/src/Utils.ts @@ -0,0 +1,20 @@ +import { Unencrypted } from '@standardnotes/sncrypto-common' + +/** + * Turns `unencrypted` into a `buffer` with `encoding`. + * @param unencrypted + */ +export function getBufferWithEncoding(unencrypted: Unencrypted): { + buffer: Buffer + encoding: BufferEncoding +} { + if (typeof unencrypted === 'string') { + const encoding: BufferEncoding = 'utf-8' + const buffer = Buffer.from(unencrypted, encoding) + return { buffer, encoding } + } + + const { string, encoding } = unencrypted + const buffer = Buffer.from(string, encoding) + return { buffer, encoding } +} diff --git a/packages/sncrypto-node/src/index.ts b/packages/sncrypto-node/src/index.ts index f0bab5f0c..026e9df97 100644 --- a/packages/sncrypto-node/src/index.ts +++ b/packages/sncrypto-node/src/index.ts @@ -1 +1,2 @@ -export { SnCryptoNode } from './crypto' +export * from './SnCryptoNode' +export * from './Utils' diff --git a/packages/sncrypto-web/src/crypto.ts b/packages/sncrypto-web/src/crypto.ts index 088a06a80..eb030cf30 100644 --- a/packages/sncrypto-web/src/crypto.ts +++ b/packages/sncrypto-web/src/crypto.ts @@ -5,7 +5,7 @@ import { StreamDecryptorResult, Base64String, HexString, - SNPureCrypto, + PureCryptoInterface, Utf8String, timingSafeEqual, } from '@standardnotes/sncrypto-common' @@ -39,7 +39,7 @@ type WebCryptoParams = { * — Built-in browser WebCrypto * — Libsodium.js library integration */ -export class SNWebCrypto implements SNPureCrypto { +export class SNWebCrypto implements PureCryptoInterface { private ready: Promise | null constructor() { diff --git a/packages/snjs/lib/Application/Options.ts b/packages/snjs/lib/Application/Options.ts index 67900703b..00edd96b5 100644 --- a/packages/snjs/lib/Application/Options.ts +++ b/packages/snjs/lib/Application/Options.ts @@ -1,7 +1,7 @@ import { Runtime, ApplicationIdentifier } from '@standardnotes/common' import { SNAlertService } from '../Services/Alert/AlertService' import { DeviceInterface, Environment, Platform } from '@standardnotes/services' -import { SNPureCrypto } from '@standardnotes/sncrypto-common' +import { PureCryptoInterface } from '@standardnotes/sncrypto-common' export interface ApplicationSyncOptions { /** @@ -28,7 +28,7 @@ export interface ConstructorOptions { * The platform-dependent implementation of SNPureCrypto to use. * Web uses SNWebCrypto, mobile uses SNReactNativeCrypto. */ - crypto: SNPureCrypto + crypto: PureCryptoInterface /** * The platform-dependent implementation of alert service. */ diff --git a/packages/snjs/lib/Services/Features/FeaturesService.spec.ts b/packages/snjs/lib/Services/Features/FeaturesService.spec.ts index 15c8ee747..33c4b0966 100644 --- a/packages/snjs/lib/Services/Features/FeaturesService.spec.ts +++ b/packages/snjs/lib/Services/Features/FeaturesService.spec.ts @@ -15,7 +15,7 @@ import { ContentType, RoleName } from '@standardnotes/common' import { FeatureDescription, FeatureIdentifier, GetFeatures } from '@standardnotes/features' import { SNWebSocketsService } from '../Api/WebsocketsService' import { SNSettingsService } from '../Settings' -import { SNPureCrypto } from '@standardnotes/sncrypto-common' +import { PureCryptoInterface } from '@standardnotes/sncrypto-common' import { convertTimestampToMilliseconds } from '@standardnotes/utils' import { InternalEventBusInterface } from '@standardnotes/services' @@ -29,7 +29,7 @@ describe('featuresService', () => { let syncService: SNSyncService let alertService: SNAlertService let sessionManager: SNSessionManager - let crypto: SNPureCrypto + let crypto: PureCryptoInterface let roles: RoleName[] let features: FeatureDescription[] let items: ItemInterface[] @@ -121,7 +121,7 @@ describe('featuresService', () => { sessionManager.isSignedIntoFirstPartyServer = jest.fn() sessionManager.getUser = jest.fn() - crypto = {} as jest.Mocked + crypto = {} as jest.Mocked crypto.base64Decode = jest.fn() internalEventBus = {} as jest.Mocked diff --git a/packages/snjs/lib/Services/Features/FeaturesService.ts b/packages/snjs/lib/Services/Features/FeaturesService.ts index 35ec79a40..419b72ec9 100644 --- a/packages/snjs/lib/Services/Features/FeaturesService.ts +++ b/packages/snjs/lib/Services/Features/FeaturesService.ts @@ -10,7 +10,7 @@ import { FillItemContent, PayloadEmitSource } from '@standardnotes/models' import { ItemManager } from '../Items/ItemManager' import { LEGACY_PROD_EXT_ORIGIN, PROD_OFFLINE_FEATURES_URL } from '../../Hosts' import { SettingName } from '@standardnotes/settings' -import { SNPureCrypto } from '@standardnotes/sncrypto-common' +import { PureCryptoInterface } from '@standardnotes/sncrypto-common' import { SNSessionManager } from '@Lib/Services/Session/SessionManager' import { SNSettingsService } from '../Settings' import { SNStorageService } from '../Storage/StorageService' @@ -56,7 +56,7 @@ export class SNFeaturesService private syncService: SNSyncService, private alertService: SNAlertService, private sessionManager: SNSessionManager, - private crypto: SNPureCrypto, + private crypto: PureCryptoInterface, protected override internalEventBus: Services.InternalEventBusInterface, ) { super(internalEventBus) diff --git a/packages/snjs/lib/Services/Files/FileService.spec.ts b/packages/snjs/lib/Services/Files/FileService.spec.ts index 6d29fcf47..fee7a3694 100644 --- a/packages/snjs/lib/Services/Files/FileService.spec.ts +++ b/packages/snjs/lib/Services/Files/FileService.spec.ts @@ -2,7 +2,7 @@ import { InternalEventBusInterface } from '@standardnotes/services' import { SNFileService } from './FileService' import { SNSyncService } from '../Sync/SyncService' import { ItemManager, SNAlertService, SNApiService } from '@Lib/index' -import { SNPureCrypto, StreamEncryptor } from '@standardnotes/sncrypto-common' +import { PureCryptoInterface, StreamEncryptor } from '@standardnotes/sncrypto-common' import { SNFile } from '@standardnotes/models' describe('fileService', () => { @@ -10,7 +10,7 @@ describe('fileService', () => { let itemManager: ItemManager let syncService: SNSyncService let alertService: SNAlertService - let crypto: SNPureCrypto + let crypto: PureCryptoInterface let fileService: SNFileService let internalEventBus: InternalEventBusInterface @@ -35,7 +35,7 @@ describe('fileService', () => { alertService.confirm = jest.fn().mockReturnValue(true) alertService.alert = jest.fn() - crypto = {} as jest.Mocked + crypto = {} as jest.Mocked crypto.base64Decode = jest.fn() internalEventBus = {} as jest.Mocked internalEventBus.publish = jest.fn() diff --git a/packages/snjs/lib/Services/Files/FileService.ts b/packages/snjs/lib/Services/Files/FileService.ts index c5b0d48ee..b9d4180ed 100644 --- a/packages/snjs/lib/Services/Files/FileService.ts +++ b/packages/snjs/lib/Services/Files/FileService.ts @@ -12,7 +12,7 @@ import { FileContentSpecialized, FillItemContentSpecialized, } from '@standardnotes/models' -import { SNPureCrypto } from '@standardnotes/sncrypto-common' +import { PureCryptoInterface } from '@standardnotes/sncrypto-common' import { SNAlertService } from '../Alert/AlertService' import { SNSyncService } from '../Sync/SyncService' import { ItemManager } from '@Lib/Services/Items/ItemManager' @@ -30,7 +30,7 @@ export class SNFileService extends AbstractService implements FilesClientInterfa private itemManager: ItemManager, private syncService: SNSyncService, private alertService: SNAlertService, - private crypto: SNPureCrypto, + private crypto: PureCryptoInterface, protected override internalEventBus: InternalEventBusInterface, ) { super(internalEventBus) diff --git a/packages/snjs/lib/Services/Files/Operations/DownloadAndDecrypt.spec.ts b/packages/snjs/lib/Services/Files/Operations/DownloadAndDecrypt.spec.ts index 51cbf653f..ce6fb3c2c 100644 --- a/packages/snjs/lib/Services/Files/Operations/DownloadAndDecrypt.spec.ts +++ b/packages/snjs/lib/Services/Files/Operations/DownloadAndDecrypt.spec.ts @@ -1,5 +1,5 @@ import { sleep } from '@standardnotes/utils' -import { SNPureCrypto, StreamEncryptor } from '@standardnotes/sncrypto-common' +import { PureCryptoInterface, StreamEncryptor } from '@standardnotes/sncrypto-common' import { RemoteFileInterface, EncryptedFileInterface, FileDownloadProgress } from '../Types' import { FilesServerInterface } from '../FilesServerInterface' import { DownloadAndDecryptFileOperation } from './DownloadAndDecrypt' @@ -8,7 +8,7 @@ describe('download and decrypt', () => { let apiService: FilesServerInterface let operation: DownloadAndDecryptFileOperation let file: RemoteFileInterface & EncryptedFileInterface - let crypto: SNPureCrypto + let crypto: PureCryptoInterface const NumChunks = 5 @@ -46,7 +46,7 @@ describe('download and decrypt', () => { apiService = {} as jest.Mocked downloadChunksOfSize(5) - crypto = {} as jest.Mocked + crypto = {} as jest.Mocked crypto.xchacha20StreamInitDecryptor = jest.fn().mockReturnValue({ state: {}, diff --git a/packages/snjs/lib/Services/Files/Operations/DownloadAndDecrypt.ts b/packages/snjs/lib/Services/Files/Operations/DownloadAndDecrypt.ts index dd6840387..af47ad799 100644 --- a/packages/snjs/lib/Services/Files/Operations/DownloadAndDecrypt.ts +++ b/packages/snjs/lib/Services/Files/Operations/DownloadAndDecrypt.ts @@ -3,7 +3,7 @@ import { AbortFunction, FileDownloader } from '../UseCase/FileDownloader' import { FileDecryptor } from '../UseCase/FileDecryptor' import { RemoteFileInterface, EncryptedFileInterface, FileDownloadProgress } from '../Types' import { FilesServerInterface } from '../FilesServerInterface' -import { SNPureCrypto } from '@standardnotes/sncrypto-common' +import { PureCryptoInterface } from '@standardnotes/sncrypto-common' type Result = { success: boolean; error?: ClientDisplayableError; aborted?: boolean } @@ -12,7 +12,7 @@ export class DownloadAndDecryptFileOperation { constructor( private readonly file: RemoteFileInterface & EncryptedFileInterface, - private readonly crypto: SNPureCrypto, + private readonly crypto: PureCryptoInterface, private readonly api: FilesServerInterface, private readonly apiToken: string, ) { diff --git a/packages/snjs/lib/Services/Files/Operations/EncryptAndUpload.spec.ts b/packages/snjs/lib/Services/Files/Operations/EncryptAndUpload.spec.ts index 4824d834e..8dec1f817 100644 --- a/packages/snjs/lib/Services/Files/Operations/EncryptAndUpload.spec.ts +++ b/packages/snjs/lib/Services/Files/Operations/EncryptAndUpload.spec.ts @@ -1,5 +1,5 @@ import { EncryptAndUploadFileOperation } from './EncryptAndUpload' -import { SNPureCrypto, StreamEncryptor } from '@standardnotes/sncrypto-common' +import { PureCryptoInterface, StreamEncryptor } from '@standardnotes/sncrypto-common' import { DecryptedFileInterface } from '../Types' import { FilesServerInterface } from '../FilesServerInterface' @@ -7,7 +7,7 @@ describe('encrypt and upload', () => { let apiService: FilesServerInterface let operation: EncryptAndUploadFileOperation let file: DecryptedFileInterface - let crypto: SNPureCrypto + let crypto: PureCryptoInterface const chunkOfSize = (size: number) => { return new TextEncoder().encode('a'.repeat(size)) @@ -17,7 +17,7 @@ describe('encrypt and upload', () => { apiService = {} as jest.Mocked apiService.uploadFileBytes = jest.fn().mockReturnValue(true) - crypto = {} as jest.Mocked + crypto = {} as jest.Mocked crypto.xchacha20StreamInitEncryptor = jest.fn().mockReturnValue({ header: 'some-header', diff --git a/packages/snjs/lib/Services/Files/Operations/EncryptAndUpload.ts b/packages/snjs/lib/Services/Files/Operations/EncryptAndUpload.ts index a1c85ea45..4bd10d8cb 100644 --- a/packages/snjs/lib/Services/Files/Operations/EncryptAndUpload.ts +++ b/packages/snjs/lib/Services/Files/Operations/EncryptAndUpload.ts @@ -1,7 +1,7 @@ import { DecryptedFileInterface, FileUploadProgress, FileUploadResult } from '../Types' import { FilesServerInterface } from '../FilesServerInterface' import { FileUploader } from '../UseCase/FileUploader' -import { SNPureCrypto } from '@standardnotes/sncrypto-common' +import { PureCryptoInterface } from '@standardnotes/sncrypto-common' import { FileEncryptor } from '../UseCase/FileEncryptor' export class EncryptAndUploadFileOperation { @@ -17,7 +17,7 @@ export class EncryptAndUploadFileOperation { constructor( private file: DecryptedFileInterface, private apiToken: string, - private crypto: SNPureCrypto, + private crypto: PureCryptoInterface, private api: FilesServerInterface, ) { this.encryptor = new FileEncryptor(file, this.crypto) diff --git a/packages/snjs/lib/Services/Files/UseCase/FileDecryptor.spec.ts b/packages/snjs/lib/Services/Files/UseCase/FileDecryptor.spec.ts index 2a5d38120..8fe3b0694 100644 --- a/packages/snjs/lib/Services/Files/UseCase/FileDecryptor.spec.ts +++ b/packages/snjs/lib/Services/Files/UseCase/FileDecryptor.spec.ts @@ -1,15 +1,15 @@ import { FileDecryptor } from './FileDecryptor' -import { SNPureCrypto, StreamEncryptor } from '@standardnotes/sncrypto-common' +import { PureCryptoInterface, StreamEncryptor } from '@standardnotes/sncrypto-common' import { EncryptedFileInterface } from '../Types' import { assert } from '@standardnotes/utils' describe('file decryptor', () => { let decryptor: FileDecryptor let file: EncryptedFileInterface - let crypto: SNPureCrypto + let crypto: PureCryptoInterface beforeEach(() => { - crypto = {} as jest.Mocked + crypto = {} as jest.Mocked crypto.xchacha20StreamInitDecryptor = jest.fn().mockReturnValue({ state: {}, diff --git a/packages/snjs/lib/Services/Files/UseCase/FileDecryptor.ts b/packages/snjs/lib/Services/Files/UseCase/FileDecryptor.ts index a22a915d1..3b9861b62 100644 --- a/packages/snjs/lib/Services/Files/UseCase/FileDecryptor.ts +++ b/packages/snjs/lib/Services/Files/UseCase/FileDecryptor.ts @@ -1,10 +1,10 @@ -import { SNPureCrypto, StreamDecryptor, SodiumConstant } from '@standardnotes/sncrypto-common' +import { PureCryptoInterface, StreamDecryptor, SodiumConstant } from '@standardnotes/sncrypto-common' import { EncryptedFileInterface } from '../Types' export class FileDecryptor { private decryptor: StreamDecryptor - constructor(private file: EncryptedFileInterface, private crypto: SNPureCrypto) { + constructor(private file: EncryptedFileInterface, private crypto: PureCryptoInterface) { this.decryptor = this.crypto.xchacha20StreamInitDecryptor(this.file.encryptionHeader, this.file.key) } diff --git a/packages/snjs/lib/Services/Files/UseCase/FileEncryptor.spec.ts b/packages/snjs/lib/Services/Files/UseCase/FileEncryptor.spec.ts index d12153ce1..3f41b5bc4 100644 --- a/packages/snjs/lib/Services/Files/UseCase/FileEncryptor.spec.ts +++ b/packages/snjs/lib/Services/Files/UseCase/FileEncryptor.spec.ts @@ -1,14 +1,14 @@ -import { SNPureCrypto, StreamEncryptor, SodiumConstant } from '@standardnotes/sncrypto-common' +import { PureCryptoInterface, StreamEncryptor, SodiumConstant } from '@standardnotes/sncrypto-common' import { DecryptedFileInterface } from '../Types' import { FileEncryptor } from './FileEncryptor' describe('file encryptor', () => { let encryptor: FileEncryptor let file: DecryptedFileInterface - let crypto: SNPureCrypto + let crypto: PureCryptoInterface beforeEach(() => { - crypto = {} as jest.Mocked + crypto = {} as jest.Mocked crypto.xchacha20StreamInitEncryptor = jest.fn().mockReturnValue({ header: 'some-header', state: {}, diff --git a/packages/snjs/lib/Services/Files/UseCase/FileEncryptor.ts b/packages/snjs/lib/Services/Files/UseCase/FileEncryptor.ts index f2ca929ed..3ea07e7e5 100644 --- a/packages/snjs/lib/Services/Files/UseCase/FileEncryptor.ts +++ b/packages/snjs/lib/Services/Files/UseCase/FileEncryptor.ts @@ -1,10 +1,10 @@ import { DecryptedFileInterface } from '../Types' -import { SNPureCrypto, StreamEncryptor, SodiumConstant } from '@standardnotes/sncrypto-common' +import { PureCryptoInterface, StreamEncryptor, SodiumConstant } from '@standardnotes/sncrypto-common' export class FileEncryptor { private stream!: StreamEncryptor - constructor(private readonly file: DecryptedFileInterface, private crypto: SNPureCrypto) {} + constructor(private readonly file: DecryptedFileInterface, private crypto: PureCryptoInterface) {} public initializeHeader(): string { this.stream = this.crypto.xchacha20StreamInitEncryptor(this.file.key) diff --git a/packages/snjs/lib/Services/Mfa/MfaService.ts b/packages/snjs/lib/Services/Mfa/MfaService.ts index de1e89651..74935d1fe 100644 --- a/packages/snjs/lib/Services/Mfa/MfaService.ts +++ b/packages/snjs/lib/Services/Mfa/MfaService.ts @@ -2,7 +2,7 @@ import { SettingName } from '@standardnotes/settings' import { SNSettingsService } from '../Settings' import * as messages from '../Api/Messages' -import { SNPureCrypto } from '@standardnotes/sncrypto-common' +import { PureCryptoInterface } from '@standardnotes/sncrypto-common' import { SNFeaturesService } from '../Features/FeaturesService' import { FeatureIdentifier } from '@standardnotes/features' import { AbstractService, InternalEventBusInterface } from '@standardnotes/services' @@ -10,7 +10,7 @@ import { AbstractService, InternalEventBusInterface } from '@standardnotes/servi export class SNMfaService extends AbstractService { constructor( private settingsService: SNSettingsService, - private crypto: SNPureCrypto, + private crypto: PureCryptoInterface, private featuresService: SNFeaturesService, protected override internalEventBus: InternalEventBusInterface, ) {