Key Manager
The Key Manager (KM) provides JavaScript applications with key management and cryptographic functionality. It makes this available on the device's public bus via a number of API calls. The Key Manager implements a key store, allowing apps a place to safely keep keys where they are readily available for cryptographic operations.
The Key Manager is designed to do the following:
-
Provide apps and services easy access to cryptographic functions they need to perform local tasks safely, comply with standards, and interact with external services.
-
Protect user secrets (keys, passwords, etc.) in the event of loss or theft. Even if the device is in Developer Mode, possession alone should not provide access to secrets.
-
Protect application secrets (keys, tokens, etc.) from disclosure to the user or other applications.
-
Provide apps and services access, via a flexible and easy-to-use interface, to commonly used encryption algorithms such as AES, DES/3DES, and HMAC, and digest functions such as SHA-1.
Keys are stored encrypted in a database. They have both an app name (i.e., "com.someone.appname") and a key name. The latter is not necessarily unique -- two different apps can both have a key with the same name. However, the Key Manager service obtains the app name from the protocol, making it almost impossible for apps to use the same keys.
An app can use the KM service to store or generate a key while specifying its encryption algorithm type, length, and an option to hide the key and never return it to the application, though it remains available for cryptographic operations.
In this section:
Supported Key Types
In cryptography, a key is a piece of information (a parameter) that determines the functional output of a cryptographic algorithm or cipher. Without a key, the algorithm would have no result. In encryption, a key specifies the particular transformation of plaintext (unencrypted text) into ciphertext (encrypted text), or vice-versa during decryption. Keys are also used in other cryptographic algorithms, such as digital signature schemes and message authentication codes.
Key Manager supports the following key encryption algorithm types:
-
AES -- Advanced Encryption Standard. AES-128, AES-192, and AES-256 are supported.
-
3DES (3 key outer CBC) -- The size includes the padding.
-
DES is supported but discouraged since it is a weak algorithm.
Note: Imported DES and 3DES keys must have valid DES parity.
-
HMAC -- SHA1 -- Hash-based Message Authentication Code - Secure Hash Algorithm-1 . Any size from 16 bytes up is supported.
-
BLOB -- For storing any type of data, up to 4k bytes.
-
ASCIIBLOB -- An ascii (non-base64 encoded) BLOB. The data must be in valid JSON format with no unescaped JSON formatting characters. This can also be used for storing up to 4k of data.
Supported Block Cipher Modes
A block cipher is a type of symmetric-key encryption algorithm that transforms a fixed-length block of plaintext data into a block of ciphertext data of the same length. This transformation takes place with the aid of a user-provided secret key. Decryption is performed by applying the reverse transformation to the ciphertext block using the same secret key. The fixed length is called the block size, and for many block ciphers, the block size is 64 bits or 128 bits.
AES and DES/3DES are the supported block ciphers. The block size for DES/3DES is 8 and for AES it's 16.
Key Manager supports the following block cipher modes:
- CBC -- Cipher Block Chaining.
- ECB -- Electronic CodeBook.
- CFB -- Cipher FeedBack Mode. CFB is used for turning a block cipher into a stream cipher. Note that the drawback of stream ciphers is that reusing the key allows an attacker to recover the key stream and decrypt anything encrypted using it.
Supported Block Cipher Padding Modes
Because a block cipher works on units of a fixed size, but messages come in a variety of lengths, some modes (mainly CBC) require that the final block be padded before encryption. CBC and ECB require it unless the data is a multiple of the block size. CFB does not.
Key Manager supports the following block cipher padding modes:
- None -- Data must be a multiple of the block size.
- PKCS1 -- Public Key Cryptography Standards #1.
Data Formatting
Since JSON objects are composed and formatted with ascii (UTF-8) text, data to and from the Key Manager service needs to be base64 encoded to avoid having it interpreted as JSON formatting instructions. This includes key data, data to be encrypted or decrypted, and IVs (Initialization Vectors). Developers need to do their own bas64 encoding and decoding; code for this is freely available on the Internet.
Methods
- crypt -- Encrypts or decrypts data.
- export -- Wraps a key using a specified wrapping key.
- fetchKey -- Returns key metadata and key data.
- generate -- Generates a key.
- import -- Takes a wrapped key and imports it.
- keyInfo -- Returns key metadata but not the key data.
- remove -- Deletes a key from the database.
- store -- Stores a key in the database.
crypt
You can use this API to encrypt or decrypt data. The key encryption algorithm specified (AES, DES/3DES, or HMACSHA1) must be the same as the one used to create the key.
Key data needs to be base64 encoded. Since JSON objects are composed and formatted with ascii (UTF-8) text, data needs to be base64 encoded to avoid having it interpreted as JSON formatting instructions. Code for this is freely available on the Internet.
Syntax
{
"keyname" : string,
"algorithm" : string,
"pad" : string,
"mode" : string,
"iv" : string,
"decrypt" : boolean,
"data" : string
}
Parameters
| Argument | Required | Type | Description |
| keyname | Yes | string | Key name. |
| algorithm | Yes | string |
Possible values: "AES", "DES", "3DES", or "HMACSHA1".
|
| pad | No | string |
Possible values: "PKCS1", or "none". Required for block ciphers.
|
| mode | No | string |
Possible values: "CBC", "CFB", "ECB", or "none". This is only appropriate for block ciphers. none is not valid for block ciphers.
|
| iv | No | string | Initialization Vector -- a block of bits that is required to allow a stream cipher or a block cipher to to produce a unique stream independent from other streams produced by the same encryption key without having to go through a re-keying process. Only required for CBC and CFB modes. 16 bytes for AES, 8 bytes for DES/3DES. Should be base64-encoded. |
| decrypt | Yes | boolean |
false to encrypt, true to decrypt.
|
| data | Yes | string | Data to encrypt or decrypt. Should be base64-encoded. |
Returns
{
"returnValue" : boolean,
"errorText" : string, |
"data" : string
}
| Argument | Required | Type | Description |
| returnValue | Yes | boolean |
true (success) or false (failure)
|
| errorText | No | string | Error message returned on failure. |
| data | No | string | Encrypted data. |
Examples
Enyo
enyo.kind({
name : "enyo.Canon.KeyManager",
kind : enyo.VFlexBox,
components : [
{ flex : 1,
kind : "Pane",
components : [{
flex : 1,
kind : "Scroller",
components : [
{
name : "cryptData",
kind : "PalmService",
service : "palm://com.palm.keymanager",
method : "crypt",
onSuccess : "cryptDataSuccess",
onFailure : "cryptDataFailure",
subscribe : true
},
{kind : "Button", name : "cryptDataButton", caption : "Encrypt Data", onclick : "cryptDataClick"}
]
}]
}],
cryptDataClick: function() {
// Note that "data" has been base64 encoded
this.$.cryptData.call({ "keyname":"myKey", "algorithm":"AES", "decrypt":false,
"data":"SSB3YW50IHRoaXMgZGF0YSB0byBiZSBlbmNyeXB0ZWQ="});
},
cryptDataSuccess: function(inSender, inResponse) {
this.log("Encrypt data success, results=" + enyo.json.stringify(inResponse));
},
// Log errors to the console for debugging
cryptDataFailure: function(inSender, inError, inRequest) {
this.log(enyo.json.stringify(inError));
}
});
Mojo
this.controller.serviceRequest("palm://com.palm.keymanager/", {
method: "crypt",
parameters: {
"keyname" : "myKey",
"algorithm" : "AES",
"decrypt" : false,
"data" : B64data
},
onSuccess: function(e){ Mojo.Log.info("Crypt success, data="+e.data); },
onFailure: function(e){ Mojo.Log.info("Crypt failure, err="+e.errorText); }
});
Success
Encrypt data success, results=
{
"data":"cfOqsq443VfOtMBUdpCZY6aTq0LmiV7SodWlvVOEQNr8rFhhkLmMXm8nv1cvzteI",
"returnValue":true
}
export
Wraps a key using a specified wrapping key. Both keys must belong to the same owner. A key can not wrap itself.
Syntax
{
"keyname" : string,
"wrappingkeyname" : string
}
Parameters
| Argument | Type | Description |
| keyname | string | Key name. |
| wrappingkeyname | string | Wrapping key name. |
Returns
{
"returnValue" : boolean,
"errorText" : string, |
"wrappedkey" : string
}
| Argument | Required | Type | Description |
| returnValue | Yes | boolean |
true (success) or false (failure)
|
| errorText | No | string | Error message returned on failure. |
| wrappedkey | No | string | Wrapped key returned on success. |
Examples
Mojo
this.controller.serviceRequest("palm://com.palm.keymanager/", {
method: "export",
parameters: {
"keyname": "MyKey",
"wrappingkeyname": "MyWrappingKey"
},
onSuccess: function(e) {Mojo.Log.info("KeyManager: export success="+JSON.stringify(e));},
onFailure: function(e) {Mojo.Log.info("KeyManager: export failure="+JSON.stringify(e));}
});
fetchKey
Returns key metadata and key data. Key data is returned if the key had been originally stored or generated with nohide = true. Otherwise, the call fails and a "key is not exportable" error message is returned.
Syntax
{
"keyname" : string
}
Parameters
| Argument | Required | Type | Description |
| keyname | Yes | string | Key name. |
Returns
{
"returnValue" : boolean,
"errorText" : string, |
"keyname" : string,
"type" : string,
"keydata" : string,
"nohide" : boolean,
"noexport" : boolean,
"backup" : boolean,
"cloud" : boolean,
"shared" : boolean,
"unwrap_only" : boolean
}
| Argument | Required | Type | Description |
| returnValue | Yes | boolean |
true (success) or false (failure).
|
| errorText | No | string | Error message returned on failure. |
| keyname | No | string | Key name. Returned on success. |
| type | No | string |
Key type. One of the following -- "AES", "DES", "3DES", "HMACSHA1", "BLOB", or "ASCIIBLOB". Returned on success.
|
| keydata | No | string |
Optional key data string. Returned if nohide is true. Returned on success.
|
| nohide | No | boolean |
If true, it means key data can be returned with this call. Returned on success.
|
| backup | No | boolean | Is this key backed up in the cloud with the device backup? Returned on success. |
| noexport | No | boolean | Key can be exported flag. Returned on success. |
| cloud | No | boolean | Is this key stored in the cloud and used for backups? Returned on success. |
| unwrap_only | No | boolean |
If true, key can only be used to unwrap wrapped keys and no other operation. Returned on success.
|
| shared | No | boolean | Can other apps access the key flag. Returned on success. |
Examples
luna-send
luna-send -n 1 luna://com.palm.keymanager/fetchKey '{"keyname":"myKey"}'
{"backup":false,"cloud":false,"keydata":"VUV7/1cYn9WRRyxArRXBrg==","keyname":"myKey","noexport":false,"nohide":true,"returnValue":true,"shared":true,"type":"AES","unwrap_only":false}
Enyo
enyo.kind({
name : "enyo.Canon.KeyManager",
kind : enyo.VFlexBox,
components : [
{ flex : 1,
kind : "Pane",
components : [
{
name : "fetchKey",
kind : "PalmService",
service : "palm://com.palm.keymanager",
method : "fetchKey",
onSuccess : "fetchKeySuccess",
onFailure : "fetchKeyFailure",
subscribe : true
},
{kind : "Button", name : "fetchKeyButton", caption : "Fetch Key", onclick : "fetchKeyClick"}
]
}]
}],
fetchKeyClick: function() {
this.$.fetchKey.call({"keyname":"myKey"});
},
fetchKeySuccess: function(inSender, inResponse) {
this.log("Generate key success, results=" + enyo.json.stringify(inResponse));
},
// Log errors to the console for debugging
fetchKeyFailure: function(inSender, inError, inRequest) {
this.log(enyo.json.stringify(inError));
}
});
Mojo
this.controller.serviceRequest("palm://com.palm.keymanager/", {
method: "fetchKey",
parameters: {
"keyname" :"MyKey"
},
onSuccess: function(e){ var MyKeyB64 = Base64.decode(e.keydata); },
onFailure: function(e){ Mojo.Log.info("fetchKey Failure, err="+e.errorText); }
});
Success
Generate key success, results=
{
"backup":false,
"cloud":false,
"keydata":"VUV7/1cYn9WRRyxArRXBrg==",
"keyname":"myKey",
"noexport":false,
"nohide":true,
"returnValue":true,
"shared":true,
"type":"AES",
"unwrap_only":false
}
generate
Generates a key with the name specified. If nohide = true, the key data can be accessed with fetchKey.
Supported types and their size requirements:
- AES -- 16 (default), 24 or 32 bytes. These sizes correspond to AES 128, 192 or 256 bit.
- DES/3DES -- 24 bytes.
- HMACSHA1 -- 16 bytes or up.
Syntax
{
"keyname": string,
"size" : integer,
"type" : string,
"nohide" : boolean
}
Parameters
| Argument | Required | Type | Description |
| keyname | Yes | string | Name of key to generate. |
| size | Yes | integer | Key size in bytes. |
| type | Yes | string | Key type. Must be one of the following -- "AES", "DES", "3DES", or "HMACSHA1". |
| nohide | No | boolean |
If false (default), the key data can not be exported with fetchKey.
|
Returns
{
"returnValue" : boolean,
"errorText" : string
}
| Argument | Required | Type | Description |
| returnValue | Yes | boolean |
true (success) or false (failure).
|
| errorText | No | string | Error message returned on failure. |
Examples
luna-send
luna-send -n 1 luna://com.palm.keymanager/generate '{"keyname":"myKey", "type":"AES", "nohide": true, "size": 16}'
{"returnValue":true}
Enyo
enyo.kind({
name : "enyo.Canon.KeyManager",
kind : enyo.VFlexBox,
components : [
{ flex : 1,
kind : "Pane",
components : [{
flex : 1,
kind : "Scroller",
components : [{
name : "genKey",
kind : "PalmService",
service : "palm://com.palm.keymanager",
method : "generate",
onSuccess : "genKeySuccess",
onFailure : "genKeyFailure",
subscribe : true
},
{kind : "Button", name : "genKeyButton", caption : "Generate Key", onclick : "genKeyClick"}
]
}]
}],
genKeyClick: function() {
this.$.genKey.call({"keyname":"myKey", "type":"AES", "nohide": true, "size": 16});
},
genKeySuccess: function(inSender, inResponse) {
this.log("Generate key success, results=" + enyo.json.stringify(inResponse));
},
// Log errors to the console for debugging
genKeyFailure: function(inSender, inError, inRequest) {
this.log(enyo.json.stringify(inError));
}
});
Mojo
this.controller.serviceRequest("palm://com.palm.keymanager/", {
method: "generate",
parameters: {
"keyname" : "MyGenKey",
"size" : 16,
"type" : "HMACSHA1",
"nohide" : true
},
onSuccess: function(e){ Mojo.Log.info("KeyManager: Generate MyGenKey success, e="+JSON.stringify(e)); },
onFailure: function(e){ Mojo.Log.info("KeyManager: Generate MyGenKey failure, e="+JSON.stringify(e)); }
});
import
Import takes a wrapped key and imports it. If the wrapping key is not present, the key is not installed and an error is returned. If successful, the unwrapped key's name is returned. A wrapped key can not replace an existing key with the same owner and name.
Syntax
{
"wrappedkey": string
}
Parameters
| Argument | Type | Description |
| wrappedkey | string | Wrapped key. |
Returns
{
"returnValue" : boolean,
"errorText" : string, |
"keyname" : string
}
| Argument | Required | Type | Description |
| returnValue | Yes | boolean |
true (success) or false (failure).
|
| errorText | No | string | Error message returned on failure. Returned on success. |
| keyname | No | string | Key name. |
Examples
this.controller.serviceRequest("palm://com.palm.keymanager/", {
method: "import",
parameters: {
"wrappedkey": "MyWrappedKey"
},
onSuccess: function(e){ Mojo.Log.info("Import success, e="+JSON.stringify(e)); },
onFailure: function(e){ Mojo.Log.info("Import failure, e="+JSON.stringify(e)); }
});
keyInfo
Returns the key's metadata but not the key data.
Syntax
{
"keyname" : string
}
Parameters
| Argument | Required | Type | Description |
| keyname | Yes | string | Key name |
Returns
{
"returnValue" : boolean,
"errorText" : string, |
"keyname" : string,
"type" : string,
"nohide" : boolean,
"backup" : boolean,
"noexport" : boolean,
"cloud" : boolean,
"unwrap_only" : boolean,
"shared" : boolean
}
| Argument | Required | Type | Description |
| returnValue | Yes | boolean |
true (success) or false (failure).
|
| errorText | No | string | Error message returned on failure. |
| keyname | No | string | Key name. Returned on success. |
| type | No | string |
One of the following -- "AES", "DES", "3DES", "HMACSHA1", "BLOB", or "ASCIIBLOB". Returned on success.
|
| nohide | No | boolean |
Indicates if key data is accessible via fetchKey (true), or can only be used for operations (false). Returned on success.
|
| backup | No | boolean | Is this key backed up in the cloud with the device backup? Returned on success. |
| noexport | No | boolean | Key can be exported flag. Returned on success. |
| cloud | No | boolean | Is this key stored in the cloud and used for backups? Returned on success. |
| unwrap_only | No | boolean |
If true, key can only be used to unwrap wrapped keys and no other operation. Returned on success.
|
| shared | No | boolean | Can other apps access the key flag. Returned on success. |
Examples
luna-send
luna-send -n 1 luna://com.palm.keymanager/keyInfo '{"keyname":"myKey"}'
{"backup":false,"cloud":false,"keyname":"unknown","noexport":false,"nohide":true,"returnValue":true,"shared":true,"type":"AES","unwrap_only":false}
Enyo
enyo.kind({
name : "enyo.Canon.KeyManager",
kind : enyo.VFlexBox,
components : [
{ flex : 1,
kind : "Pane",
components : [{
flex : 1,
kind : "Scroller",
components : [
{
name : "getKeyInfo",
kind : "PalmService",
service : "palm://com.palm.keymanager",
method : "keyInfo",
onSuccess : "getKeyinfoSuccess",
onFailure : "getKeyinfoFailure",
subscribe : true
},
{kind : "Button", name : "getKeyinfoButton", caption : "Get Key Info", onclick : "getKeyinfoClick"}
]
}]
}],
getKeyinfoClick: function() {
this.$.getKeyInfo.call({"keyname":"myKey"});
},
getKeyinfoSuccess: function(inSender, inResponse) {
this.log("Get key info success, results=" + enyo.json.stringify(inResponse));
},
// Log errors to the console for debugging
getKeyinfoFailure: function(inSender, inError, inRequest) {
this.log(enyo.json.stringify(inError));
}
});
Mojo
this.controller.serviceRequest("palm://com.palm.keymanager/", {
method: "keyInfo",
parameters: {
"keyname" :"MyKey"
},
onSuccess: function(e){ Mojo.Log.info("keyInfo Success: type= "+e.type); },
onFailure: function(e){ Mojo.Log.info("keyInfo Failure err="+JSON.stringify(e)); }
});
Success
Get key info success, results=
{
"backup":false,
"cloud":false,
"keyname":"com.palmdts.enyo.keymanager",
"noexport":false,
"nohide":true,
"returnValue":true,
"shared":true,
"type":"AES",
"unwrap_only":false
}
remove
Given a key name, removes a key from the key store.
Syntax
{
"keyname" : string
}
Parameters
| Argument | Required | Type | Description |
| keyname | Yes | string | Key name. |
Returns
{
"returnValue" : boolean,
"errorText" : string
}
| Argument | Required | Type | Description |
| returnValue | Yes | boolean |
true (success) or false (failure).
|
| errorText | No | string | Error message on failure. |
Examples
luna-send
luna-send -n 1 -a com.palmdts.enyo.keymanager luna://com.palm.keymanager/remove '{"keyname":"myStoredKey"}'
{"returnValue":true}
Enyo
enyo.kind({
name : "enyo.Canon.KeyManager",
kind : enyo.VFlexBox,
components : [
{ flex : 1,
kind : "Pane",
components : [{
flex : 1,
kind : "Scroller",
components : [
{
name : "removeKey",
kind : "PalmService",
service : "palm://com.palm.keymanager",
method : "remove",
onSuccess : "removeKeySuccess",
onFailure : "removeKeyFailure",
subscribe : true
},
{kind : "Button", name : "removeKeyButton", caption : "Remove Key", onclick : "removeKeyClick"}
]
}]
}],
removeKeyClick: function() {
this.$.removeKey.call({ "keyname":"myStoredKey"});
},
removeKeySuccess: function(inSender, inResponse) {
this.log("Remove key success, results=" + enyo.json.stringify(inResponse));
},
// Log errors to the console for debugging
removeKeyFailure: function(inSender, inError, inRequest) {
this.log(enyo.json.stringify(inError));
}
});
Mojo
this.controller.serviceRequest("palm://com.palm.keymanager/", {
method: "remove",
parameters: {
"keyname" : "MyKey4"
},
onSuccess: function(e){ Mojo.Log.info("Remove key success"); },
onFailure: function(e){ Mojo.Log.info("Remove key failure, err="+JSON.stringify(e)); }
});
store
Stores a key in the database given a key name, data, key encryption algorithm type, and other optional parameters.
Key data needs to be base64 encoded unless its type is ASCIIBLOB. Since JSON objects are composed and formatted with ascii (UTF-8) text, data needs to be base64 encoded to avoid having it interpreted as JSON formatting instructions. Code for this is freely available on the Internet.
Type must be one of the supported key types -- AES, DES/3DES, HMACSHA1, BLOB, or ASCIIBLOB. BLOB is not actually a key type, but can be used to safely store data (up to 4096 bytes) of any kind. ASCIIBLOB is the same as BLOB, but for storing ascii, JSON formatted text.
If nohide is set to false (default), the key data can not be exported with fetchKey. BLOB types should not have nohide set, as that makes them useless.
The backup flag (keys backed up in the cloud with the device backup) is false by default.
The cloud flag indicates a key which is stored in the cloud and is a special key for backups. If true, The type should be ASCIIBLOB and the keydata contains the backup-specific part of the URL for fetching the key.
Syntax
{
"keyname" : string,
"keydata" : string,
"type" : string,
"nohide" : boolean,
"backup" : boolean,
"cloud" : boolean
}
Parameters
| Argument | Required | Type | Description |
| keyname | Yes | string | Key name. |
| keydata | Yes | string |
Key data. Key data needs to be base64 encoded unless its type is ASCIIBLOB.
|
| type | Yes | string | Key algroithm encryption type. Must be one of the following -- AES, DES, 3DES, HMACSHA1, BLOB, or ASCIIBLOB. DES/3DES keys must be properly formed with DES parity bits and weak key checking already done. |
| nohide | No | boolean |
If false (default), the key data can not be exported with fetchKey.
|
| backup | No | boolean |
If true, this key is backed up in the cloud with the device backup. This field is false by default.
|
| cloud | No | boolean |
Indicates a key which is stored in the cloud and is a special key for backups. The type should be ASCIIBLOB and the keydata contains the backup-specific part of the URL for fetching the key.
|
Returns
{
"returnValue" : boolean,
"errorText" : string
}
| Argument | Required | Type | Description |
| returnValue | Yes | boolean |
true (success) or false (failure).
|
| errorText | No | string | Error message returned on failure. |
Examples
Enyo
enyo.kind({
name : "enyo.Canon.KeyManager",
kind : enyo.VFlexBox,
components : [
{ flex : 1,
kind : "Pane",
components : [{
flex : 1,
kind : "Scroller",
components : [ {
name : "storeKey",
kind : "PalmService",
service : "palm://com.palm.keymanager",
method : "store",
onSuccess : "storeKeySuccess",
onFailure : "storeKeyFailure",
subscribe : true
},
{kind : "Button", name : "storeKeyButton", caption : "Store Key", onclick : "storeKeyClick"}
]
}]
}],
storeKeyClick: function() {
// Note that keydata has been base64 encoded
this.$.storeKey.call({"keyname":"myStoredKey", "keydata":"dmdyZmY0NWU4OTBnZGVzMg==", "type":"AES",
"nohide": "true", "backup":true, "cloud":true});
},
storeKeySuccess: function(inSender, inResponse) {
this.log("Store key success, results=" + enyo.json.stringify(inResponse));
},
// Log errors to the console for debugging
storeKeyFailure: function(inSender, inError, inRequest) {
this.log(enyo.json.stringify(inError));
}
});
Mojo
// Note that Base64 encoding functionality is something YOU need to provide.
// "KeyDataB64" is 16 bytes.
KeyDataB64 = Base64.encode(KeyData);
this.controller.serviceRequest("palm://com.palm.keymanager/", {
method: "store",
parameters: {
"keyname" : "MyKey",
"keydata" : KeyDataB64,
"type" : "AES",
"nohide" : true
},
onSuccess: function(e){ Mojo.Log.info("Store key success"); },
onFailure: function(e){ Mojo.Log.info("Store key failure, err="+JSON.stringify(e)); }
});