js: Making objects inmutable
authorMaximilian Krambach <maximilian.krambach@intevation.de>
Mon, 30 Jul 2018 10:31:27 +0000 (12:31 +0200)
committerMaximilian Krambach <maximilian.krambach@intevation.de>
Mon, 30 Jul 2018 10:31:27 +0000 (12:31 +0200)
--

* An Object.freeze should stop any malicious third party from changing
  objects' methods once the objects are instantiated (see unittest for
  an approach that would have worked before)
  - An initialized gpgmejs- object doesn't have a '_Keyring' property
    anymore (it still has its 'Keyring')
  - The internal expect='base64' needed to be turned into a method.

lang/js/BrowserTestExtension/tests/startup.js
lang/js/src/Connection.js
lang/js/src/Errors.js
lang/js/src/Key.js
lang/js/src/Message.js
lang/js/src/Signature.js
lang/js/src/gpgmejs.js
lang/js/src/index.js
lang/js/unittests.js

index 1e2784d..63358aa 100644 (file)
@@ -37,7 +37,6 @@ describe('GPGME context', function(){
                 context.Keyring = input;
                 expect(context.Keyring).to.be.an('object');
                 expect(context.Keyring).to.not.equal(input);
-                expect(context._Keyring).to.equal(context.Keyring);
                 expect(context.Keyring.getKeys).to.be.a('function');
                 expect(context.Keyring.getDefaultKey).to.be.a('function');
                 expect(context.Keyring.importKey).to.be.a('function');
index 561a5b7..b010575 100644 (file)
@@ -118,7 +118,7 @@ export class Connection{
             }
             let chunksize = message.chunksize;
             return new Promise(function(resolve, reject){
-                let answer = new Answer(message);
+                let answer = Object.freeze(new Answer(message));
                 let listener = function(msg) {
                     if (!msg){
                         _connection.onMessage.removeListener(listener);
@@ -188,14 +188,15 @@ class Answer{
      */
     constructor(message){
         const operation = message.operation;
-        const expect = message.expect;
+        const expected = message.getExpect();
         let response_b64 = null;
 
         this.getOperation = function(){
             return operation;
         };
+
         this.getExpect = function(){
-            return expect;
+            return expected;
         };
 
         /**
@@ -260,7 +261,7 @@ class Answer{
                     }
                     if (_decodedResponse.base64 === true
                         && poa.data[key] === 'string'
-                        && this.getExpect() === undefined
+                        && this.getExpect() !== 'base64'
                     ){
                         _response[key] = decodeURIComponent(
                             atob(_decodedResponse[key]).split('').map(
index 0cf1af1..39e3a74 100644 (file)
@@ -119,7 +119,7 @@ const err_list = {
 export function gpgme_error(code = 'GENERIC_ERROR', info){
     if (err_list.hasOwnProperty(code)){
         if (err_list[code].type === 'error'){
-            return new GPGME_Error(code);
+            return Object.freeze(new GPGME_Error(code));
         }
         if (err_list[code].type === 'warning'){
             // eslint-disable-next-line no-console
@@ -127,10 +127,10 @@ export function gpgme_error(code = 'GENERIC_ERROR', info){
         }
         return null;
     } else if (code === 'GNUPG_ERROR'){
-        return new GPGME_Error(code, info);
+        return Object.freeze(new GPGME_Error(code, info));
     }
     else {
-        return new GPGME_Error('GENERIC_ERROR');
+        return Object.freeze(new GPGME_Error('GENERIC_ERROR'));
     }
 }
 
index d5873a7..f431a28 100644 (file)
@@ -37,7 +37,7 @@ export function createKey(fingerprint, async = false){
     if (!isFingerprint(fingerprint) || typeof(async) !== 'boolean'){
         return gpgme_error('PARAM_WRONG');
     }
-    else return new GPGME_Key(fingerprint, async);
+    else return Object.freeze(new GPGME_Key(fingerprint, async));
 }
 
 /**
@@ -104,15 +104,15 @@ export class GPGME_Key {
                 case 'subkeys':
                     _data.subkeys = [];
                     for (let i=0; i< data.subkeys.length; i++) {
-                        _data.subkeys.push(
-                            new GPGME_Subkey(data.subkeys[i]));
+                        _data.subkeys.push(Object.freeze(
+                            new GPGME_Subkey(data.subkeys[i])));
                     }
                     break;
                 case 'userids':
                     _data.userids = [];
                     for (let i=0; i< data.userids.length; i++) {
-                        _data.userids.push(
-                            new GPGME_UserId(data.userids[i]));
+                        _data.userids.push(Object.freeze(
+                            new GPGME_UserId(data.userids[i])));
                     }
                     break;
                 case 'last_update':
index c0b6ed5..e2c0734 100644 (file)
@@ -36,7 +36,7 @@ export function createMessage(operation){
         return gpgme_error('PARAM_WRONG');
     }
     if (permittedOperations.hasOwnProperty(operation)){
-        return new GPGME_Message(operation);
+        return Object.freeze(new GPGME_Message(operation));
     } else {
         return gpgme_error('MSG_WRONG_OP');
     }
@@ -56,11 +56,21 @@ export class GPGME_Message {
             op: operation,
             chunksize: 1023* 1024
         };
+        let expected = null;
 
         this.getOperation = function(){
             return _msg.op;
         };
 
+        this.setExpect = function(value){
+            if (value === 'base64'){
+                expected = value;
+            }
+        };
+        this.getExpect = function(){
+            return expected;
+        };
+
         /**
          * The maximum size of responses from gpgme in bytes. As of July 2018,
          * most browsers will only accept answers up to 1 MB of size.
@@ -204,7 +214,7 @@ export class GPGME_Message {
             return new Promise(function(resolve, reject) {
                 if (me.isComplete() === true) {
 
-                    let conn  = new Connection;
+                    let conn  = Object.freeze(new Connection);
                     conn.post(me).then(function(response) {
                         resolve(response);
                     }, function(reason) {
index 0ee58e9..55131b0 100644 (file)
@@ -66,7 +66,7 @@ export function createSignature(sigObject){
             }
         }
     }
-    return new GPGME_Signature(sigObject);
+    return Object.freeze(new GPGME_Signature(sigObject));
 }
 
 
index 720490d..9154979 100644 (file)
@@ -102,7 +102,7 @@ export class GpgME {
          */
         this.getKeyring = function(){
             if (!_Keyring){
-                _Keyring = new GPGME_Keyring;
+                _Keyring = Object.freeze(new GPGME_Keyring);
             }
             return _Keyring;
         };
@@ -241,7 +241,7 @@ export class GpgME {
             putData(msg, data);
             return new Promise(function(resolve,reject) {
                 if (mode ==='detached'){
-                    msg.expect= 'base64';
+                    msg.setExpect('base64');
                 }
                 msg.post().then( function(message) {
                     if (mode === 'clearsign'){
@@ -319,10 +319,7 @@ export class GpgME {
      * Accesses the {@link GPGME_Keyring}.
      */
     get Keyring(){
-        if (!this._Keyring){
-            this._Keyring = new GPGME_Keyring;
-        }
-        return this._Keyring;
+        return this.getKeyring();
     }
 }
 
index dc613fc..2fed95f 100644 (file)
@@ -34,11 +34,11 @@ import { Connection } from './Connection';
  */
 function init(){
     return new Promise(function(resolve, reject){
-        let connection = new Connection;
+        let connection = Object.freeze(new Connection);
         connection.checkConnection(false).then(
             function(result){
                 if (result === true) {
-                    resolve(new GpgME());
+                    resolve(Object.freeze(new GpgME()));
                 } else {
                     reject(gpgme_error('CONN_NO_CONNECT'));
                 }
index 6228993..3304b1e 100644 (file)
@@ -253,6 +253,22 @@ function unittests (){
                 expect(key.fingerprint.code).to.equal('KEY_INVALID');
             }
         });
+
+        it('Overwriting getFingerprint does not work', function(){
+            const evilFunction = function(){
+                return 'bad Data';
+            };
+            let key = createKey(kp.validKeyFingerprint, true);
+            expect(key.fingerprint).to.equal(kp.validKeyFingerprint);
+            try {
+                key.getFingerprint = evilFunction;
+            }
+            catch(e) {
+                expect(e).to.be.an.instanceof(TypeError);
+            }
+            expect(key.fingerprint).to.equal(kp.validKeyFingerprint);
+            expect(key.getFingerprint).to.not.equal(evilFunction);
+        });
         // TODO: tests for subkeys
         // TODO: tests for userids
         // TODO: some invalid tests for key/keyring