js: Making objects inmutable

--

* 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.
This commit is contained in:
Maximilian Krambach 2018-07-30 12:31:27 +02:00
parent 522121ea7e
commit e16a87e839
9 changed files with 47 additions and 24 deletions

View File

@ -37,7 +37,6 @@ describe('GPGME context', function(){
context.Keyring = input; context.Keyring = input;
expect(context.Keyring).to.be.an('object'); expect(context.Keyring).to.be.an('object');
expect(context.Keyring).to.not.equal(input); 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.getKeys).to.be.a('function');
expect(context.Keyring.getDefaultKey).to.be.a('function'); expect(context.Keyring.getDefaultKey).to.be.a('function');
expect(context.Keyring.importKey).to.be.a('function'); expect(context.Keyring.importKey).to.be.a('function');

View File

@ -118,7 +118,7 @@ export class Connection{
} }
let chunksize = message.chunksize; let chunksize = message.chunksize;
return new Promise(function(resolve, reject){ return new Promise(function(resolve, reject){
let answer = new Answer(message); let answer = Object.freeze(new Answer(message));
let listener = function(msg) { let listener = function(msg) {
if (!msg){ if (!msg){
_connection.onMessage.removeListener(listener); _connection.onMessage.removeListener(listener);
@ -188,14 +188,15 @@ class Answer{
*/ */
constructor(message){ constructor(message){
const operation = message.operation; const operation = message.operation;
const expect = message.expect; const expected = message.getExpect();
let response_b64 = null; let response_b64 = null;
this.getOperation = function(){ this.getOperation = function(){
return operation; return operation;
}; };
this.getExpect = function(){ this.getExpect = function(){
return expect; return expected;
}; };
/** /**
@ -260,7 +261,7 @@ class Answer{
} }
if (_decodedResponse.base64 === true if (_decodedResponse.base64 === true
&& poa.data[key] === 'string' && poa.data[key] === 'string'
&& this.getExpect() === undefined && this.getExpect() !== 'base64'
){ ){
_response[key] = decodeURIComponent( _response[key] = decodeURIComponent(
atob(_decodedResponse[key]).split('').map( atob(_decodedResponse[key]).split('').map(

View File

@ -119,7 +119,7 @@ const err_list = {
export function gpgme_error(code = 'GENERIC_ERROR', info){ export function gpgme_error(code = 'GENERIC_ERROR', info){
if (err_list.hasOwnProperty(code)){ if (err_list.hasOwnProperty(code)){
if (err_list[code].type === 'error'){ if (err_list[code].type === 'error'){
return new GPGME_Error(code); return Object.freeze(new GPGME_Error(code));
} }
if (err_list[code].type === 'warning'){ if (err_list[code].type === 'warning'){
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
@ -127,10 +127,10 @@ export function gpgme_error(code = 'GENERIC_ERROR', info){
} }
return null; return null;
} else if (code === 'GNUPG_ERROR'){ } else if (code === 'GNUPG_ERROR'){
return new GPGME_Error(code, info); return Object.freeze(new GPGME_Error(code, info));
} }
else { else {
return new GPGME_Error('GENERIC_ERROR'); return Object.freeze(new GPGME_Error('GENERIC_ERROR'));
} }
} }

View File

@ -37,7 +37,7 @@ export function createKey(fingerprint, async = false){
if (!isFingerprint(fingerprint) || typeof(async) !== 'boolean'){ if (!isFingerprint(fingerprint) || typeof(async) !== 'boolean'){
return gpgme_error('PARAM_WRONG'); 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': case 'subkeys':
_data.subkeys = []; _data.subkeys = [];
for (let i=0; i< data.subkeys.length; i++) { for (let i=0; i< data.subkeys.length; i++) {
_data.subkeys.push( _data.subkeys.push(Object.freeze(
new GPGME_Subkey(data.subkeys[i])); new GPGME_Subkey(data.subkeys[i])));
} }
break; break;
case 'userids': case 'userids':
_data.userids = []; _data.userids = [];
for (let i=0; i< data.userids.length; i++) { for (let i=0; i< data.userids.length; i++) {
_data.userids.push( _data.userids.push(Object.freeze(
new GPGME_UserId(data.userids[i])); new GPGME_UserId(data.userids[i])));
} }
break; break;
case 'last_update': case 'last_update':

View File

@ -36,7 +36,7 @@ export function createMessage(operation){
return gpgme_error('PARAM_WRONG'); return gpgme_error('PARAM_WRONG');
} }
if (permittedOperations.hasOwnProperty(operation)){ if (permittedOperations.hasOwnProperty(operation)){
return new GPGME_Message(operation); return Object.freeze(new GPGME_Message(operation));
} else { } else {
return gpgme_error('MSG_WRONG_OP'); return gpgme_error('MSG_WRONG_OP');
} }
@ -56,11 +56,21 @@ export class GPGME_Message {
op: operation, op: operation,
chunksize: 1023* 1024 chunksize: 1023* 1024
}; };
let expected = null;
this.getOperation = function(){ this.getOperation = function(){
return _msg.op; 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, * 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. * 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) { return new Promise(function(resolve, reject) {
if (me.isComplete() === true) { if (me.isComplete() === true) {
let conn = new Connection; let conn = Object.freeze(new Connection);
conn.post(me).then(function(response) { conn.post(me).then(function(response) {
resolve(response); resolve(response);
}, function(reason) { }, function(reason) {

View File

@ -66,7 +66,7 @@ export function createSignature(sigObject){
} }
} }
} }
return new GPGME_Signature(sigObject); return Object.freeze(new GPGME_Signature(sigObject));
} }

View File

@ -102,7 +102,7 @@ export class GpgME {
*/ */
this.getKeyring = function(){ this.getKeyring = function(){
if (!_Keyring){ if (!_Keyring){
_Keyring = new GPGME_Keyring; _Keyring = Object.freeze(new GPGME_Keyring);
} }
return _Keyring; return _Keyring;
}; };
@ -241,7 +241,7 @@ export class GpgME {
putData(msg, data); putData(msg, data);
return new Promise(function(resolve,reject) { return new Promise(function(resolve,reject) {
if (mode ==='detached'){ if (mode ==='detached'){
msg.expect= 'base64'; msg.setExpect('base64');
} }
msg.post().then( function(message) { msg.post().then( function(message) {
if (mode === 'clearsign'){ if (mode === 'clearsign'){
@ -319,10 +319,7 @@ export class GpgME {
* Accesses the {@link GPGME_Keyring}. * Accesses the {@link GPGME_Keyring}.
*/ */
get Keyring(){ get Keyring(){
if (!this._Keyring){ return this.getKeyring();
this._Keyring = new GPGME_Keyring;
}
return this._Keyring;
} }
} }

View File

@ -34,11 +34,11 @@ import { Connection } from './Connection';
*/ */
function init(){ function init(){
return new Promise(function(resolve, reject){ return new Promise(function(resolve, reject){
let connection = new Connection; let connection = Object.freeze(new Connection);
connection.checkConnection(false).then( connection.checkConnection(false).then(
function(result){ function(result){
if (result === true) { if (result === true) {
resolve(new GpgME()); resolve(Object.freeze(new GpgME()));
} else { } else {
reject(gpgme_error('CONN_NO_CONNECT')); reject(gpgme_error('CONN_NO_CONNECT'));
} }

View File

@ -253,6 +253,22 @@ function unittests (){
expect(key.fingerprint.code).to.equal('KEY_INVALID'); 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 subkeys
// TODO: tests for userids // TODO: tests for userids
// TODO: some invalid tests for key/keyring // TODO: some invalid tests for key/keyring