encryptedstorage.js

1       // ${project.version}
2       var argscheck = require('cordova/argscheck'),
3           exec = require("cordova/exec");
4       
5       /**
6        * The EncryptedStorage class is used as a secure local store.  The EncryptedStorage API is based on the
7        * W3C Web storage API, but has two major differences--it is asynchronous, and it has a constructor with
8        * a password.<br/>
9        * <br/>
10       * <b>Note:</b> There is a security flaw on some versions of Android with the Pseudo Random Number Generation.
11       * The first time the native code of this plugin runs it applies the fix for this issue.  However, the
12       * fix needs to be applied before any use of Java Cryptography Architecture primitives.  Therefore, it
13       * is a good idea to run this plugin, for example, call a function that has a native component such as length, key, getItem,
14       * setItem, removeItem, or clear, before using any other security-related plugin, to protect yourself
15       * against the possibility that the other plugin does not apply this fix. This issue affects only the EncryptedStorage plugin,
16       *so you need not do this for other Kapsel plugins.  For more details about the security flaw, see
17       * <a href="http://android-developers.blogspot.com/2013/08/some-securerandom-thoughts.html">
18       * http://android-developers.blogspot.com/2013/08/some-securerandom-thoughts.html</a><br/>
19       * <br/>
20       * <b>Adding and Removing the EncryptedStorage Plugin</b><br/>
21       * Add or remove the EncryptedStorage plugin by using the
22       * <a href="http://cordova.apache.org/docs/en/edge/guide_cli_index.md.html#The%20Command-line%20Interface">Cordova CLI</a>.<br/>
23       * <br/>
24       * To add the EncryptedStorage plugin to your project, use the following command:<br/>
25       * cordova plugin add <path to directory containing Kapsel plugins>\encryptedstorage<br/>
26       * <br/>
27       * To remove the EncryptedStorage plugin from your project, use the following command:<br/>
28       * cordova plugin rm com.sap.mp.cordova.plugins.encryptedstorage
29       * @namespace
30       * @alias EncryptedStorage
31       * @memberof sap
32       * @param {String} storeName The name of the store to create.  All stores with different names
33       * act independently, while stores with the same name (and same password) act as the same store.
34       * If null or undefined is passed, an empty string is used.
35       * @param {String} password The password of the store to create.  If null or undefined is passed,
36       * an empty string is used.
37       */
38      EncryptedStorage = function (storeName, password) {
39          // private variables
40          var that = this;
41          var storagePassword = password ? password : "";
42          var storageName = storeName ? storeName : "";
43      
44          // privileged functions
45      
46          /**
47           * This function gets the length of the store.  The length of a store
48           * is the number of key/value pairs that are in the store.
49           * @param {sap.EncryptedStorage~lengthSuccessCallback} successCallback If successful,
50           * the successCallback is invoked with the length of the store as
51           * the parameter.
52           * @param {sap.EncryptedStorage~errorCallback} errorCallback If there is an error,
53           * the errorCallback is invoked with an ErrorInfo object as the parameter.
54      	 * @memberof sap.EncryptedStorage
55      	 * @function length
56      	 * @instance
57           * @example
58           * var store = new sap.EncryptedStorage("storeName", "storePassword");
59           * var successCallback = function(length) {
60           *    alert("Length is " + length);
61           * }
62           * var errorCallback = function(error) {
63           *    alert("An error occurred: " + JSON.stringify(error));
64           * }
65           * store.length(successCallback, errorCallback);
66           */
67          this.length = function (successCallback, errorCallback) {
68      		try{
69      			argscheck.checkArgs('FF', 'EncryptedStorage.length', arguments);
70      		}catch(ex){
71      			errorCallback(this.ERROR_INVALID_PARAMETER);
72      			return;
73      		}
74      
75              cordova.exec(successCallback, errorCallback, "EncryptedStorage",
76                  "length", [storageName, storagePassword]);
77          }
78      
79          /**
80           * This function gets the key corresponding to the given index.
81           * @param {number} index The index of the store for which to get the key.
82           * Valid indices are integers from zero (the first index), up to, but not including,
83           * the length of the store.  If the index is out of bounds, then the success
84           * callback is invoked with null as the parameter.
85           * @param {sap.EncryptedStorage~keySuccessCallback} successCallback If successful,
86           * the successCallback is invoked with the key as the parameter.
87           * @param {sap.EncryptedStorage~errorCallback} errorCallback If there is an error,
88           * the errorCallback is invoked with an ErrorInfo object as the parameter.
89      	 * @memberof sap.EncryptedStorage
90      	 * @function key
91      	 * @instance
92           * @example
93           * // This example shows how to get the key for the last item.
94           * var store = new sap.EncryptedStorage("storeName", "storePassword");
95           * var errorCallback = function( error ){
96           *    alert("An error occurred: " + JSON.stringify(error));
97           * }
98           * var keySuccessCallback = function(key) {
99           *    alert("Last key is " + key);
100          * }
101          * var lengthSuccessCallback = function(length) {
102          *    store.key(length - 1, keySuccessCallback, errorCallback);
103          * }
104          * store.length(lengthSuccessCallback, errorCallback);
105          */
106         this.key = function (index, successCallback, errorCallback) {
107     		try{
108     			argscheck.checkArgs('NFF', 'EncryptedStorage.key', arguments);
109     		}catch(ex){
110     			errorCallback(this.ERROR_INVALID_PARAMETER);
111     			return;
112     		}
113     
114             cordova.exec(successCallback, errorCallback, "EncryptedStorage",
115                 "key", [storageName, storagePassword, index]);
116         }
117     
118         /**
119          * This function gets the value corresponding to the given key.  If there is no
120          * item with the given key, then the success callback is invoked with null as
121          * the parameter.
122          * @param {String} key The key of the item for which to get the value. If null or undefined is
123          * passed, "null" is used.
124          * @param {sap.EncryptedStorage~getItemSuccessCallback} successCallback If successful,
125          * the successCallback is invoked with the value as the parameter (or null if the key
126          * did not exist).
127          * @param {sap.EncryptedStorage~errorCallback} errorCallback If there is an error,
128          * the errorCallback is invoked with an ErrorInfo object as the parameter.
129     	 * @memberof sap.EncryptedStorage
130     	 * @function getItem
131     	 * @instance
132          * @example
133          * var store = new sap.EncryptedStorage("storeName", "storePassword");
134     	 * var successCallback = function(value) {
135     	 *    alert("Value is " + value);
136     	 * }
137     	 * var errorCallback = function(error) {
138     	 *    alert("An error occurred: " + JSON.stringify(error));
139     	 * }
140          * store.getItem("theKey", successCallback, errorCallback);
141          */
142         this.getItem = function (key, successCallback, errorCallback) {
143     		try{
144     			argscheck.checkArgs('SFF', 'EncryptedStorage.getItem', arguments);
145     		}catch(ex){
146     			errorCallback(this.ERROR_INVALID_PARAMETER);
147     			return;
148     		}
149     		
150             cordova.exec(successCallback, errorCallback, "EncryptedStorage",
151                 "getItem", [storageName, storagePassword, key]);
152         }
153     
154         /**
155          * This function sets an item with the given key and value.  If no item exists with
156          * the given key, a new item is created. If an item does exist with the
157          * the given key, its value is overwritten with the given value.<br/>
158     	 * <br/>
159     	 * Note: On Android there is a size limit on the string to be stored.  See
160     	 * {@link sap.EncryptedStorage#SIMPLE_STRING_MAXIMUM_LENGTH} and {@link sap.EncryptedStorage#COMPLEX_STRING_MAXIMUM_LENGTH}
161     	 * for more details.
162          * @param {String} key The key of the item to set.  If null or undefined is passed,
163          * "null" is used.
164          * @param {String} value The value of the item to set.  If null or undefined is passed,
165          * "null" is used.
166          * @param {sap.EncryptedStorage~successCallback} successCallback If successful,
167          * the successCallback is invoked with no parameters.
168          * @param {sap.EncryptedStorage~errorCallback} errorCallback If there is an error,
169          * the errorCallback is invoked with an ErrorInfo object as the parameter.
170     	 * @memberof sap.EncryptedStorage
171     	 * @function setItem
172     	 * @instance
173          * @example
174          * var store = new sap.EncryptedStorage("storeName", "storePassword");
175     	 * var successCallback = function() {
176     	 *    alert("Item has been set.");
177     	 * }
178     	 * var errorCallback = function(error) {
179     	 *    alert("An error occurred: " + JSON.stringify(error));
180     	 * }
181          * store.setItem("somekey", "somevalue", successCallback, errorCallback);
182          */
183         this.setItem = function (key, value, successCallback, errorCallback) {
184     		try{
185     			argscheck.checkArgs('SSFF', 'EncryptedStorage.setItem', arguments);
186     		}catch(ex){
187     			errorCallback(this.ERROR_INVALID_PARAMETER);
188     			return;
189     		}
190     		
191             cordova.exec(successCallback, errorCallback, "EncryptedStorage",
192                 "setItem", [storageName, storagePassword, key, value]);
193         }
194     
195         /**
196          * This function removes the item corresponding to the given key. If there is no
197          * item with the given key in the first place, that is still counted as a success.
198          * @param {String} key The key of the item to remove.  If null or undefined is
199          * passed, "null" is used.
200          * @param {sap.EncryptedStorage~successCallback} successCallback If successful,
201          * the successCallback is invoked with no parameters.
202          * @param {sap.EncryptedStorage~errorCallback} errorCallback If there is an error,
203          * the errorCallback is invoked with an ErrorInfo object as the parameter.
204     	 * @memberof sap.EncryptedStorage
205     	 * @function removeItem
206     	 * @instance
207          * @example
208          * var store = new sap.EncryptedStorage("storeName", "storePassword");
209          * var successCallback = function() {
210          *    alert("Value removed");
211          * }
212          * var errorCallback = function(error) {
213          *    alert("An error occurred: " + JSON.stringify(error));
214          * }
215          * store.removeItem("somekey", successCallback, errorCallback);
216          */
217         this.removeItem = function (key, successCallback, errorCallback) {
218     		try{
219     			argscheck.checkArgs('SFF', 'EncryptedStorage.removeItem', arguments);
220     		}catch(ex){
221     			errorCallback(this.ERROR_INVALID_PARAMETER);
222     			return;
223     		}
224     		
225             cordova.exec(successCallback, errorCallback, "EncryptedStorage",
226                 "removeItem", [storageName, storagePassword, key]);
227         }
228     
229         /**
230          * This function removes all items from the store. If there are no
231          * items in the store, it is still counted as a success.
232          * @param {sap.EncryptedStorage~successCallback} successCallback If successful,
233          * the successCallback is invoked with no parameters.
234          * @param {sap.EncryptedStorage~errorCallback} errorCallback If there is an error,
235          * the errorCallback is invoked with an ErrorInfo object as the parameter.
236     	 * @memberof sap.EncryptedStorage
237     	 * @function clear
238     	 * @instance
239          * @example
240          * var store = new sap.EncryptedStorage("storeName", "storePassword");
241          * var successCallback = function() {
242          *    alert("Store cleared!");
243          * }
244          * var errorCallback = function(error) {
245          *    alert("An error occurred: " + JSON.stringify();
246          * }
247          * store.clear(successCallback, errorCallback);
248          */
249         this.clear = function (successCallback, errorCallback) {
250     		try{
251     			argscheck.checkArgs('FF', 'EncryptedStorage.clear', arguments);
252     		}catch(ex){
253     			errorCallback(this.ERROR_INVALID_PARAMETER);
254     			return;
255     		}
256     		
257             cordova.exec(successCallback, errorCallback, "EncryptedStorage",
258                 "clear", [storageName, storagePassword]);
259         }
260     };
261     
262     // Error codes
263     /**
264      * This error code indicates an unknown error occurred.
265      * @memberof sap.EncryptedStorage
266      * @name sap.EncryptedStorage#ERROR_UNKNOWN
267      * @constant
268      */
269     EncryptedStorage.prototype.ERROR_UNKNOWN = 0;
270     /**
271      * This error code indicates an invalid parameter was provided.
272      * (eg: a string given where a number was required).
273      * @memberof sap.EncryptedStorage
274      * @name sap.EncryptedStorage#ERROR_INVALID_PARAMETER
275      * @constant
276      */
277     EncryptedStorage.prototype.ERROR_INVALID_PARAMETER = 1;
278     /**
279      * This error code indicates that the operation failed due to an incorrect password.  The password is
280      * set by the constructor of {@link EncryptedStorage}.
281      * @memberof sap.EncryptedStorage
282      * @name sap.EncryptedStorage#ERROR_BAD_PASSWORD
283      * @constant
284      */
285     EncryptedStorage.prototype.ERROR_BAD_PASSWORD = 2;
286     /**
287      * This error indicates that the string was too large to store. This error applies only to Android.  
288      * For iOS, no hard limit is imposed, but be aware of device memory constraints.
289      * @memberof sap.EncryptedStorage
290      * @name sap.EncryptedStorage#ERROR_GREATER_THAN_MAXIMUM_SIZE
291      * @constant
292      */
293     EncryptedStorage.prototype.ERROR_GREATER_THAN_MAXIMUM_SIZE = 3;
294     /**
295      * This constant is the length of the largest string that can successfully be stored on Android.  Only if all the
296      * characters in the string are encoded in 1 byte in UTF-8 can a string actually be this big.  Since
297      * characters in UTF-8 can take up to 4 bytes, if you do not know the contents of a string, the maximum
298      * length that is guaranteed to be successful is {@link EncryptedStorage.COMPLEX_STRING_MAXIMUM_LENGTH}, which is
299      * {@link EncryptedStorage.SIMPLE_STRING_MAXIMUM_LENGTH}/4.  This size restriction is present only on
300      * Android.
301      * @memberof sap.EncryptedStorage
302      * @name sap.EncryptedStorage#SIMPLE_STRING_MAXIMUM_LENGTH
303      * @constant
304      */
305     EncryptedStorage.prototype.SIMPLE_STRING_MAXIMUM_LENGTH = 1048542;
306     /**
307      * This constant is the length of the largest string that is guaranteed to be successfully stored on Android.  The
308      * limit depends on how many bytes the string takes up when encoded with UTF-8 (under which encoding
309      * characters can take up to 4 bytes).  This is the maximum length of a string for which every character
310      * takes all 4 bytes.  This size restriction is present only on Android.
311      * @memberof sap.EncryptedStorage
312      * @name sap.EncryptedStorage#COMPLEX_STRING_MAXIMUM_LENGTH
313      * @constant
314      */
315     EncryptedStorage.prototype.COMPLEX_STRING_MAXIMUM_LENGTH = 262135;
316     
317     module.exports = EncryptedStorage;
318     
319     
320     /**
321      * Callback function that is invoked on a successful call to a function that does
322      * not need to return anything.
323      *
324      * @callback sap.EncryptedStorage~successCallback
325      */
326     
327     /**
328      * Callback function that is invoked on a successful call to {@link EncryptedStorage.length}.
329      *
330      * @callback sap.EncryptedStorage~lengthSuccessCallback
331      *
332      * @param {number} length The number of key/value pairs in the store.
333      */
334     
335     /**
336      * Callback function that is invoked on a successful call to {@link EncryptedStorage.key}.
337      * If the key returned is null, that means the index passed to {@link EncryptedStorage.key} was out of bounds.
338      *
339      * @callback sap.EncryptedStorage~keySuccessCallback
340      *
341      * @param {String} key The key corresponding to the given index.  Will be null if the index passed to
342      * {@link EncryptedStorage.key} was out of bounds.
343      */
344     
345     /**
346      * Callback function that is invoked on a successful call to {@link EncryptedStorage.getItem}.
347      * If the returned value is null, that means the key passed to {@link EncryptedStorage.getItem} did not exist.
348      *
349      * @callback sap.EncryptedStorage~getItemSuccessCallback
350      *
351      * @param {String} value The value of the item with the given key.  Will be null if the key passed to
352      * {@link EncryptedStorage.getItem} did not exist.
353      */
354     
355     /**
356      * Callback function that is invoked in case of an error.
357      *
358      * @callback sap.EncryptedStorage~errorCallback
359      *
360      * @param {number} errorCode An error code indicating what went wrong.  Will be one of {@link sap.EncryptedStorage#ERROR_UNKNOWN},
361      * {@link sap.EncryptedStorage#ERROR_INVALID_PARAMETER}, {@link sap.EncryptedStorage#ERROR_BAD_PASSWORD}, or
362      * {@link sap.EncryptedStorage#ERROR_GREATER_THAN_MAXIMUM_SIZE}.
363      *
364      * @example
365      * function errorCallback(errCode) {
366      *    //Set the default error message. Used if an invalid code is passed to the
367      *    //function (just in case) but also to cover the
368      *    //sap.EncryptedStorage.ERROR_UNKNOWN case as well.
369      *    var msg = "Unkown Error";
370      *    switch (errCode) {
371      *       case sap.EncryptedStorage.ERROR_INVALID_PARAMETER:
372      *          msg = "Invalid parameter passed to method";
373      *          break;
374      *       case sap.EncryptedStorage.ERROR_BAD_PASSWORD :
375      *          msg = "Incorrect password";
376      *          break;
377      *       case sap.EncryptedStorage.ERROR_GREATER_THAN_MAXIMUM_SIZE:
378      *          msg = "Item (string) value too large to write to store";
379      *          break;
380      *    };
381      *    //Write the error to the log
382      *    console.error(msg);
383      *    //Let the user know what happened
384      *    navigator.notification.alert(msg, null, "EncryptedStorage Error", "OK");
385      * };
386      */