-
-
Notifications
You must be signed in to change notification settings - Fork 392
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: experimental 005 operator (#1753)
- Loading branch information
Showing
21 changed files
with
284 additions
and
46 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
75 changes: 75 additions & 0 deletions
75
packages/encryption/src/Domain/Operator/005/Operator005.spec.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
import { ProtocolOperator005 } from './Operator005' | ||
import { PureCryptoInterface } from '@standardnotes/sncrypto-common' | ||
|
||
describe('operator 005', () => { | ||
let crypto: PureCryptoInterface | ||
let operator: ProtocolOperator005 | ||
|
||
beforeEach(() => { | ||
crypto = {} as jest.Mocked<PureCryptoInterface> | ||
crypto.generateRandomKey = jest.fn().mockImplementation(() => { | ||
return 'random-string' | ||
}) | ||
crypto.xchacha20Encrypt = jest.fn().mockImplementation((text: string) => { | ||
return `<e>${text}<e>` | ||
}) | ||
crypto.xchacha20Decrypt = jest.fn().mockImplementation((text: string) => { | ||
return text.split('<e>')[1] | ||
}) | ||
crypto.sodiumCryptoBoxGenerateKeypair = jest.fn().mockImplementation(() => { | ||
return { privateKey: 'private-key', publicKey: 'public-key', keyType: 'x25519' } | ||
}) | ||
crypto.sodiumCryptoBoxEasyEncrypt = jest.fn().mockImplementation((text: string) => { | ||
return `<e>${text}<e>` | ||
}) | ||
crypto.sodiumCryptoBoxEasyDecrypt = jest.fn().mockImplementation((text: string) => { | ||
return text.split('<e>')[1] | ||
}) | ||
|
||
operator = new ProtocolOperator005(crypto) | ||
}) | ||
|
||
it('should generateKeyPair', () => { | ||
const result = operator.generateKeyPair() | ||
|
||
expect(result).toEqual({ privateKey: 'private-key', publicKey: 'public-key', keyType: 'x25519' }) | ||
}) | ||
|
||
it('should asymmetricEncryptKey', () => { | ||
const senderKeypair = operator.generateKeyPair() | ||
const recipientKeypair = operator.generateKeyPair() | ||
|
||
const plaintext = 'foo' | ||
|
||
const result = operator.asymmetricEncryptKey(plaintext, senderKeypair.privateKey, recipientKeypair.publicKey) | ||
|
||
expect(result).toEqual(`${'005_KeyAsym'}:random-string:<e>foo<e>`) | ||
}) | ||
|
||
it('should asymmetricDecryptKey', () => { | ||
const senderKeypair = operator.generateKeyPair() | ||
const recipientKeypair = operator.generateKeyPair() | ||
const plaintext = 'foo' | ||
const ciphertext = operator.asymmetricEncryptKey(plaintext, senderKeypair.privateKey, recipientKeypair.publicKey) | ||
const decrypted = operator.asymmetricDecryptKey(ciphertext, senderKeypair.publicKey, recipientKeypair.privateKey) | ||
|
||
expect(decrypted).toEqual('foo') | ||
}) | ||
|
||
it('should symmetricEncryptPrivateKey', () => { | ||
const keypair = operator.generateKeyPair() | ||
const symmetricKey = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' | ||
const encryptedKey = operator.symmetricEncryptPrivateKey(keypair.privateKey, symmetricKey) | ||
|
||
expect(encryptedKey).toEqual(`${'005_KeySym'}:random-string:<e>${keypair.privateKey}<e>`) | ||
}) | ||
|
||
it('should symmetricDecryptPrivateKey', () => { | ||
const keypair = operator.generateKeyPair() | ||
const symmetricKey = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' | ||
const encryptedKey = operator.symmetricEncryptPrivateKey(keypair.privateKey, symmetricKey) | ||
const decryptedKey = operator.symmetricDecryptPrivateKey(encryptedKey, symmetricKey) | ||
|
||
expect(decryptedKey).toEqual(keypair.privateKey) | ||
}) | ||
}) |
80 changes: 80 additions & 0 deletions
80
packages/encryption/src/Domain/Operator/005/Operator005.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
import { ProtocolVersion } from '@standardnotes/common' | ||
import { Base64String, HexString, PkcKeyPair, Utf8String } from '@standardnotes/sncrypto-common' | ||
import { V005Algorithm } from '../../Algorithm' | ||
import { SNProtocolOperator004 } from '../004/Operator004' | ||
|
||
const VersionString = '005' | ||
const SymmetricCiphertextPrefix = `${VersionString}_KeySym` | ||
const AsymmetricCiphertextPrefix = `${VersionString}_KeyAsym` | ||
|
||
export type AsymmetricallyEncryptedKey = Base64String | ||
export type SymmetricallyEncryptedPrivateKey = Base64String | ||
|
||
/** | ||
* @experimental | ||
* @unreleased | ||
*/ | ||
export class ProtocolOperator005 extends SNProtocolOperator004 { | ||
public override getEncryptionDisplayName(): string { | ||
return 'XChaCha20-Poly1305' | ||
} | ||
|
||
override get version(): ProtocolVersion { | ||
return VersionString as ProtocolVersion | ||
} | ||
|
||
generateKeyPair(): PkcKeyPair { | ||
return this.crypto.sodiumCryptoBoxGenerateKeypair() | ||
} | ||
|
||
asymmetricEncryptKey( | ||
keyToEncrypt: HexString, | ||
senderSecretKey: HexString, | ||
recipientPublicKey: HexString, | ||
): AsymmetricallyEncryptedKey { | ||
const nonce = this.crypto.generateRandomKey(V005Algorithm.AsymmetricEncryptionNonceLength) | ||
|
||
const ciphertext = this.crypto.sodiumCryptoBoxEasyEncrypt(keyToEncrypt, nonce, senderSecretKey, recipientPublicKey) | ||
|
||
return [AsymmetricCiphertextPrefix, nonce, ciphertext].join(':') | ||
} | ||
|
||
asymmetricDecryptKey( | ||
keyToDecrypt: AsymmetricallyEncryptedKey, | ||
senderPublicKey: HexString, | ||
recipientSecretKey: HexString, | ||
): Utf8String { | ||
const components = keyToDecrypt.split(':') | ||
|
||
const nonce = components[1] | ||
|
||
return this.crypto.sodiumCryptoBoxEasyDecrypt(keyToDecrypt, nonce, senderPublicKey, recipientSecretKey) | ||
} | ||
|
||
symmetricEncryptPrivateKey(privateKey: HexString, symmetricKey: HexString): SymmetricallyEncryptedPrivateKey { | ||
if (symmetricKey.length !== 64) { | ||
throw new Error('Symmetric key length must be 256 bits') | ||
} | ||
|
||
const nonce = this.crypto.generateRandomKey(V005Algorithm.SymmetricEncryptionNonceLength) | ||
|
||
const encryptedKey = this.crypto.xchacha20Encrypt(privateKey, nonce, symmetricKey) | ||
|
||
return [SymmetricCiphertextPrefix, nonce, encryptedKey].join(':') | ||
} | ||
|
||
symmetricDecryptPrivateKey( | ||
encryptedPrivateKey: SymmetricallyEncryptedPrivateKey, | ||
symmetricKey: HexString, | ||
): HexString | null { | ||
if (symmetricKey.length !== 64) { | ||
throw new Error('Symmetric key length must be 256 bits') | ||
} | ||
|
||
const components = encryptedPrivateKey.split(':') | ||
|
||
const nonce = components[1] | ||
|
||
return this.crypto.xchacha20Decrypt(encryptedPrivateKey, nonce, symmetricKey) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.