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 */