From bcdfbf66c28a834804c3f29529e128145eb21918 Mon Sep 17 00:00:00 2001 From: Yuan Ruo Date: Thu, 4 Jun 2020 20:29:06 +0800 Subject: [PATCH] Release v0.8.2 - Patch for encrypting non-UTF8 characters (#35) * Revert "Replace tweetnacl-util with StableLib (#27)" This reverts commit 2dbce2daf827aed34b13994aab6fb891ddcf6111. * Add tests for multi-language plaintext with non-UTF8 characters * 0.8.2 --- package-lock.json | 17 ++++++----------- package.json | 7 +++---- spec/crypto.spec.ts | 16 ++++++++++++++++ spec/resources/crypto-data-20200604.ts | 18 ++++++++++++++++++ src/crypto.ts | 19 +++++++++++-------- src/util/signature.ts | 7 +++---- src/verification/authenticate.ts | 5 ++--- src/verification/generate-signature.ts | 9 ++------- 8 files changed, 61 insertions(+), 37 deletions(-) create mode 100644 spec/resources/crypto-data-20200604.ts diff --git a/package-lock.json b/package-lock.json index 93acc35..666d6da 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "@opengovsg/formsg-sdk", - "version": "0.8.1", + "version": "0.8.2", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -1436,16 +1436,6 @@ "type-detect": "4.0.8" } }, - "@stablelib/base64": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@stablelib/base64/-/base64-1.0.0.tgz", - "integrity": "sha512-s/wTc/3+vYSalh4gfayJrupzhT7SDBqNtiYOeEMlkSDqL/8cExh5FAeTzLpmYq+7BLLv36EjBL5xrb0bUHWJWQ==" - }, - "@stablelib/utf8": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@stablelib/utf8/-/utf8-1.0.0.tgz", - "integrity": "sha512-Y8QWrK4T0yW0HMFfSI3ZaMHKV37q27hX5ilsmKV358x01mzYfj5fwIf2LjzTlF+UIemHEXSlSN9XJnv1ML4znQ==" - }, "@types/babel__core": { "version": "7.1.7", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.7.tgz", @@ -6300,6 +6290,11 @@ "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz", "integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==" }, + "tweetnacl-util": { + "version": "0.15.1", + "resolved": "https://registry.npmjs.org/tweetnacl-util/-/tweetnacl-util-0.15.1.tgz", + "integrity": "sha512-RKJBIj8lySrShN4w6i/BonWp2Z/uxwC3h4y7xsRrpP59ZboCd0GpEVsOnMDYLMmKBpYhb5TgHzZXy7wTfYFBRw==" + }, "type-check": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", diff --git a/package.json b/package.json index 0c23565..cd2bc22 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@opengovsg/formsg-sdk", - "version": "0.8.1", + "version": "0.8.2", "repository": { "type": "git", "url": "https://github.com/opengovsg/formsg-javascript-sdk.git" @@ -22,9 +22,8 @@ "author": "Open Government Products (FormSG)", "license": "MIT", "dependencies": { - "@stablelib/base64": "^1.0.0", - "@stablelib/utf8": "^1.0.0", - "tweetnacl": "^1.0.3" + "tweetnacl": "^1.0.3", + "tweetnacl-util": "^0.15.1" }, "devDependencies": { "@babel/cli": "^7.8.4", diff --git a/spec/crypto.spec.ts b/spec/crypto.spec.ts index 29f11a3..26a7cd4 100644 --- a/spec/crypto.spec.ts +++ b/spec/crypto.spec.ts @@ -7,6 +7,8 @@ import { formPublicKey, } from './resources/crypto-data-20200322' +import { plaintextMultiLang } from './resources/crypto-data-20200604' + const formsg = formsgPackage({ mode: 'test' }) const INTERNAL_TEST_VERSION = 1 @@ -90,6 +92,20 @@ describe('Crypto', function () { expect(decrypted).toHaveProperty('responses', plaintext) }) + it('should be able to encrypt and decrypt multi-language submission from 2020-06-04 end-to-end successfully', () => { + // Arrange + const { publicKey, secretKey } = formsg.crypto.generate() + + // Act + const ciphertext = formsg.crypto.encrypt(plaintextMultiLang, publicKey) + const decrypted = formsg.crypto.decrypt(secretKey, { + encryptedContent: ciphertext, + version: INTERNAL_TEST_VERSION, + }) + // Assert + expect(decrypted).toHaveProperty('responses', plaintextMultiLang) + }) + it('should be able to encrypt submissions without signing if signingPrivateKey is missing', () => { // Arrange const { publicKey, secretKey } = formsg.crypto.generate() diff --git a/spec/resources/crypto-data-20200604.ts b/spec/resources/crypto-data-20200604.ts new file mode 100644 index 0000000..839a3d4 --- /dev/null +++ b/spec/resources/crypto-data-20200604.ts @@ -0,0 +1,18 @@ +const plaintextMultiLang = [ + { + _id: '5e771c8a6b3c5100240368e1', + question: 'Radio', + fieldType: 'radiobutton', + answer: '96%, or more / 96% (விழுக்காடு) அல்லது அதை விட அதிகமா? / ৯৬% বা বেশী / 96% या ज़्यादा / 96%以上', + }, + { + _id: '5e771c8a6b3c5100240368e2', + question: 'Radio', + fieldType: 'radiobutton', + answer: 'less than 96% / 96% (விழுக்காட்டை) விட குறைவா? / ৯৬% চেয়ে কম / 96% से कम / 少于96%', + }, +] + +export { + plaintextMultiLang, +} diff --git a/src/crypto.ts b/src/crypto.ts index bc9c480..0495bd4 100644 --- a/src/crypto.ts +++ b/src/crypto.ts @@ -1,9 +1,4 @@ import nacl from 'tweetnacl' -import { - encode as encodeBase64, - decode as decodeBase64, -} from '@stablelib/base64' -import { encode as encodeUTF8, decode as decodeUTF8 } from '@stablelib/utf8' import { DecryptParams, @@ -14,6 +9,14 @@ import { Keypair, PackageInitParams, } from './types' + +import { + encodeBase64, + decodeBase64, + encodeUTF8, + decodeUTF8, +} from 'tweetnacl-util' + import { getPublicKey } from './util/publicKey' import { determineIsFormFields } from './util/validate' @@ -29,7 +32,7 @@ function encrypt( encryptionPublicKey: string, signingPrivateKey?: string ): EncryptedContent { - let processedMsg = encodeUTF8(JSON.stringify(msg)) + let processedMsg = decodeUTF8(JSON.stringify(msg)) if (signingPrivateKey) { processedMsg = nacl.sign(processedMsg, decodeBase64(signingPrivateKey)) @@ -96,7 +99,7 @@ function _verifySignedMessage( const openedMessage = nacl.sign.open(msg, decodeBase64(publicKey)) if (!openedMessage) throw new Error('Failed to open signed message with given public key') - return JSON.parse(decodeUTF8(openedMessage)) + return JSON.parse(encodeUTF8(openedMessage)) } /** @@ -127,7 +130,7 @@ function decrypt(signingPublicKey: string) { if (!decryptedContent) { throw new Error('Failed to decrypt content') } - const decryptedObject: Object = JSON.parse(decodeUTF8(decryptedContent)) + const decryptedObject: Object = JSON.parse(encodeUTF8(decryptedContent)) if (!determineIsFormFields(decryptedObject)) { throw new Error('Decrypted object does not fit expected shape') } diff --git a/src/util/signature.ts b/src/util/signature.ts index 0a64077..3289cb8 100644 --- a/src/util/signature.ts +++ b/src/util/signature.ts @@ -1,6 +1,5 @@ import * as tweetnacl from 'tweetnacl' -import { encode as encodeUTF8 } from '@stablelib/utf8' -import { encode as encodeBase64, decode as decodeBase64 } from '@stablelib/base64' +import { decodeUTF8, encodeBase64, decodeBase64 } from 'tweetnacl-util' /** * Returns a signature from a basestring and secret key @@ -10,7 +9,7 @@ import { encode as encodeBase64, decode as decodeBase64 } from '@stablelib/base6 */ function sign(basestring: string, secretKey: string): string { return encodeBase64( - tweetnacl.sign.detached(encodeUTF8(basestring), decodeBase64(secretKey)) + tweetnacl.sign.detached(decodeUTF8(basestring), decodeBase64(secretKey)) ) } @@ -27,7 +26,7 @@ function verify( publicKey: string ): boolean { return tweetnacl.sign.detached.verify( - encodeUTF8(message), + decodeUTF8(message), decodeBase64(signature), decodeBase64(publicKey) ) diff --git a/src/verification/authenticate.ts b/src/verification/authenticate.ts index 7595f84..18fa114 100644 --- a/src/verification/authenticate.ts +++ b/src/verification/authenticate.ts @@ -1,8 +1,7 @@ import nacl from 'tweetnacl' -import { encode as encodeUTF8 } from '@stablelib/utf8' -import { decode as decodeBase64 } from '@stablelib/base64' import { VerificationAuthenticateOptions } from '../types' +import { decodeUTF8, decodeBase64 } from 'tweetnacl-util' import basestring from './basestring' export default function ( @@ -62,7 +61,7 @@ export default function ( time: signatureDate, }) return nacl.sign.detached.verify( - encodeUTF8(data), + decodeUTF8(data), decodeBase64(signature), decodeBase64(publicKey) ) diff --git a/src/verification/generate-signature.ts b/src/verification/generate-signature.ts index bca1ecb..fb1a8db 100644 --- a/src/verification/generate-signature.ts +++ b/src/verification/generate-signature.ts @@ -1,11 +1,6 @@ import nacl from 'tweetnacl' -import { encode as encodeUTF8 } from '@stablelib/utf8' -import { - encode as encodeBase64, - decode as decodeBase64, -} from '@stablelib/base64' - import { VerificationSignatureOptions } from '../types' +import { decodeUTF8, decodeBase64, encodeBase64 } from 'tweetnacl-util' import basestring from './basestring' export default function (privateKey: string) { @@ -18,7 +13,7 @@ export default function (privateKey: string) { const time = Date.now() const data = basestring({ transactionId, formId, fieldId, answer, time }) const signature = nacl.sign.detached( - encodeUTF8(data), + decodeUTF8(data), decodeBase64(privateKey) ) return `f=${formId},v=${transactionId},t=${time},s=${encodeBase64(