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