1. Application Level Access Authorization Support
Access to RecordStores can now be controlled using Application Level Access Authorization.
The constant
AUTHMODE_APPLEVEL
has been added to the RecordStore class to enable this.
This value can be passed to any RecordStore method which takes an authorization mode.
Adding this support permits much finer-grained sharing of RecordStores between MIDlet Suites
than was possible previously, when a RecordStore was either private to a MIDlet Suite, or
accessible by any MIDlet Suite.
For example, it is now possible to share a RecordStore only between MIDlet Suites in the same
domain, or only between MIDlet suites from a specific vendor.
2. RecordStore Encryption
It is now possible to create a RecordStore the contents of which are stored on the device encrypted.
The encryption is performed using a symmetric-key cipher algorithm, the key being derived
from a password supplied when the RecordStore is created. The algorithm used to
perform the encryption, and the algorithm used to derive the key from the supplied
password, are both implementation specific.
2.1 Creating An Encrypted RecordStore
An encrypted RecordStore can be created using the new RecordStore static method
openRecordStore(
String recordStoreName,
boolean createIfNecessary,
int authMode,
boolean writeable,
String password)
The first four arguments are identical to the original version of this method.
The password argument specifies the password from which to derive the key to use to encrypt the created RecordStore.
2.2 Opening An Encrypted RecordStore
An existing local encrypted RecordStore can be opened using the variant of openRecordStore() described above.
An existing non-local encrypted RecordStore that is shared can be opened using using the new RecordStore static method
openRecordStore(
String recordStoreName,
String vendorName,
String suiteName,
String password)
The recordStoreName argument specifies the name of the encrypted RecordStore.
The vendorName argument specifies the vendor of the MIDlet suite, or LIBlet, which owns the encrypted RecordStore.
The suiteName argument specifies the name of the MIDlet suite, or LIBlet, which owns the encrypted RecordStore.
The password argument specifies the password that was supplied when the RecordStore was created.
It is an error if the specified RecordStore does not exist, and a RecordStoreNotFoundException will be thrown.
It is an error if the invoking MIDlet is not authorized to access the specified RecordStore, and a SecurityException will be thrown.
Note
The specification does not explicitly require that an implementation detect when the password used to open a previously created encrypted RecordStore is incorrect. Depending upon the implementation, this situation could either result in an exception being thrown or in invalid data being returned when a record is accessed. This implies that in the case where a MIDlet may specify an incorrect password, because it is obtained from the user for example, the MIDlet itself is responsible for detecting this situation in some way. Depending upon the context, this may be more or less difficult.
3. The Interchange File Format
The RMS Interchange File Format (IFF) specifies a binary data format for representing a single RecordStore.
Data in this format can be used to create RecordStores at installation, and runtime, and an existing RecordStore can be exported as data in this format.
The recommended suffix for files in IFF is
.rms
and the MIME type is
application/vnd.jcp.javame.midlet-rms
3.1 Encryption Support
The IFF supports the optional encryption of the data representing the RecordStore using a symmetric-key cipher algorithm. The key to use is derived from a password, using the key derivation algorithm PBKDF2, as specified by PKCS#5 v2.0.
The cipher and parameters used to perform the encryption, and the parameters used to derive the key are included in the IFF data.
An implementation must support the AES cipher algorithm, used with a 128 bit key, in CBC mode with PKCS5Padding.
Note
Although the IFF permits the use of arbitrary ciphers any data produced in IFF for the purposes of interchange is more or less going to have to use the default cipher, AES/CBS/PKCS5Padding with a 128-bit key, or there is no guarantee that the data will actually be interchangable.
3.2 Format
Data in the Interchange File Format comprises four sections, the second of which is optional.
| Header |
| EncryptionParameters [Optional] |
| RecordStoreData |
| MessageDigestData |
In the IFF a string (UTF8String) is represented as though written by the method
java.io.DataOutputStream.writeUTF()
In the IFF a 32-bit integer (int) is represented as though written by the method
java.io.DataOutputStream.writeInt()
In the IFF a 64-bit integer (long) is represented as though written by the method
java.io.DataOutputStream.writeLong()
3.2.1 Header Format
| Signature |
byte[6] |
| Major |
byte |
| Minor |
byte |
| Encrypted |
byte |
| MessageDigestAlgorithm |
UTF8String |
3.2.1.1 Signature
The six bytes
0x4d, 0x49, 0x44, 0x52, 0x4d, 0x53
These are the ASCII characters of the string
MIDRMS
3.2.1.2 Major
Specifies the major version number of the IFF to which the data conforms. Must be 3.
3.2.1.2 Minor
Specifies the minor version number of the IFF to which the data conforms. Must be 0.
3.2.1.3 Encrypted
Specifies whether RecordStoreData and MessageDigestData are encrypted. 1 if they are, 0 otherwise.
3.2.1.4 MessageDigestAlgorithm
Specifies the Message Digest algorithm used to compute MessageDigestData.
3.2.2 EncryptionParameters Format
The EncryptionParameters section is only present if the Encrypted field of the Header has the value 1.
| EncryptionAlgorithm |
UTF8String |
| IVLength |
int |
| IV |
byte[IVLength] |
| SaltLength |
int |
| Salt |
bytes[SaltLength] |
| IterationCount |
int |
| KeyLength |
int |
3.2.2.1 EncryptionAlgorithm
Specifies the cipher used to perform the encryption.
It must be of the form
algorithm
or
algorithm/mode/padding
3.2.2.2 IVLength
Specifies the length in bytes of the following IV field.
3.2.2.3 IV
The value of the Initialization Vector used by the cipher when the encryption was performed.
3.2.2.4 SaltLength
Specifies the length in bytes of the following Salt field.
3.2.2.5 Salt
Specifies the salt to use when deriving the key.
3.2.2.6 IterationCount
Specifies the iteration count to use when deriving the key.
3.2.2.7 KeyLength
Specifies the length in bits of the key to derive.
3.2.3 RecordStoreData Format
RecordStoreData comprises the RecordStore section followed by zero or more Records.
| RecordStore |
| Record [Zero or more] |
Note
Although the specification does not say so, there are a number of integrity constraints that the data in this section must conform to if it is to be used to create a valid RecordStore. I would assume that an implementation is responsible for enforcing those constraints when constructing a RecordStore from data in IFF.
3.2.3.1 RecordStore Format
| Name |
UTF8String |
| LastModified |
long |
| Version |
int |
| AuthMode |
int |
| Writable |
byte |
| NumberOfRecords |
int |
3.2.3.1.1 Name
The name of this RecordStore.
3.2.3.1.2 LastModified
The time of the last modification of this RecordStore.
The value is equal to that which would have been returned by a call to System.currentTimeMillis() made at that time.
3.2.3.1.3 Version
The version of the RecordStore.
3.2.3.1.4 AuthMode
The authorization mode of this RecordStore. Must be one of the RecordStore constants
</p
AUTHMODE_PRIVATE
AUTHMODE_ANY
AUTHMODE_APPLEVEL
3.2.3.1.5 Writable
1 if this RecordStore is writable by authorized MIDlets in other MIDlet Suites, 0 otherwise.
3.2.3.1.6 NumberOfRecords
The number of Records following this section.
3.2.3.2 Record Format
| RecordID |
int |
| Tag |
int |
| RecordDataSize |
int |
| RecordData |
byte[RecordDataSize] |
3.2.3.2.1 RecordID
The id of this record.
3.2.3.2.2 Tag
The tag of this record.
3.2.3.2.3 RecordDataSize
The size in bytes of the following RecordData field.
3.2.3.2.4 RecordData
The contents of this record.
3.2.4 MessageDigestData Format
| MessageDigestLength |
int |
| MessageDigest |
byte[MessageDigestLength] |
3.2.4.1 MessageDigestLength
Specifies the length in bytes of the following MessageDigest field.
3.2.4.1 MessageDigest
The value of the message digest computed on EncryptionParameters, if present, and RecordStoreData
4. LIBlet RecordStores
RecordStores can be created on a per-LIBlet basis. These stores can only be created at installation time. They can only be accessed at runtime by MIDlets contained in MIDlet suites which are dependent upon the LIBlets which created the RecordStores.
4.1 Creation
Per-LIBlet RecordStores can be created at installation time by the specification of one or more
LIBLet-Persistent-Data-URL-<n>
attributes in the LIBlet JAD and the manifest of the LIBlet Jar.
Note
The description of this attribute in the specification does not contain the usual strictures about ordinals starting at 1 and then being consecutive, with attributes after the first gap being ignored, but I would assume they do in fact apply.
The value of each specified attribute is of the form
<dataURL> [" " encryptLocally]
where <dataURL> must be an RFC3986 compliant absolute or relative URL.
If the URI is absolute then the data is external to the LIBlet, and it is automatically downloaded from the specified location.
If the URI is relative then the data is bundled with the LIBlet, and it is loaded from the specified
location within the LIBlet Jar.
If encryptLocally is specified then the created RecordStore will be encrypted, the password being obtained from the user.
It is an error if the specified file cannot be found.
It is an error if the data in the specified file does not conform to the IFF.
It is an error if the value of RecordStore.AuthMode field in the data is not equal to RecordStore.AUTHMODE_PRIVATE.
If there is an error the Installation of the LIBlet, and hence of the MIDlet Suite will fail .
4.2 Runtime Access
A per-LIBlet RecordStore can be accessed at runtime using either the new RecordStore static method
openRecordStore(
String recordStoreName,
String vendorName,
String suiteName,
String password)
if the RecordStore is encrypted, or the original RecordStore static method
openRecordStore(
String recordStoreName,
String vendorName,
String suiteName)
if it is not, and specifying the vendor and name of the LIBlet that owns the RecordStore.
5. Exporting A RecordStore
The contents of a RecordStore can now be exported using the newly defined RecordStore static method
exportRecordStore(
Outputstream outputStream,
String recordStoreName,
String internalPassword,
String exportPassword)
The outputStream argument is an instance of OutputStream to which the contents of the RexcordStore will be written in Interchange File Format.
The recordStoreName argument is the name of the RecordStore to export.
The internalPassword argument is the password supplied when the RecordStore was created, or null. This argument is ignored if the specified RecordStore is not encrypted.
The exportPassword argument is the password to use to encrypt the exported RecordStore. If null the exported RecordStore is not encrypted.
It is an error if the specified RecordStore does not exist, and a RecordStoreException will be thrown.
Notes
-
The specification does not state what message digest algorithm should be used, nor what cipher should be used if the exported data is to be encrypted, nor is it possible to specify either the message digest or cipher via the method call. I would assume that it is intended to be implementation specific. I would also assume that unless it was feeling exceptionally anti-social an implementation would use the default message digest algorithm, SHA1, and the default ciper, AES/CBC/PKCS5Padding, and a 128-bit key.
-
The specification does not explicitly require that an implementation detect when the password passed to this method is not the same as the password used to create the encrypted RecordStore being exported. Depending on the implementation, this could lead either to an exception being throw, or invalid data being exported. It is not clear how a MIDlet could detect this situation without considerable effort.
Code Fragment
Export an unencrypted store unencrypted
import java.io.OuputStream;
import javax.microedition.rms.RecordStore;
...
// Get the OutputStream to write the data to
OutputStream os = ...
RecordStore.export(os, "ExampleStore", null, null);
...
Code Fragment
Export an unencrypted store encrypted
import java.io.OuputStream;
import javax.microedition.rms.RecordStore;
...
// Get the OutputStream to write the data to
OutputStream os = ...
// Get the password for the exported data
String exportPassword = ...
RecordStore.export(os, "ExampleStore", null, exportPassword);
...
Code Fragment
Export an encrypted store unencrypted. Possibly not a good idea.
import java.io.OuputStream;
import javax.microedition.rms.RecordStore;
...
// Get the OutputStream to write the data to
OutputStream os = ...
// Get the password for theRecordStore
String recordStorePassword = ...
RecordStore.export(os, "ExampleStore", recordStorePassword, null);
...
Code Fragment
Export an encrypted store encrypted.
import java.io.OuputStream;
import javax.microedition.rms.RecordStore;
...
// Get the OutputStream to write the data to
OutputStream os = ...
// Get the password for theRecordStore
String recordStorePassword = ...
// Get the password for the exported data
String exportPassword = ...
RecordStore.export(os, "ExampleStore", recordStorePassword, exportPassword);
...
6. Importing A RecordStore
A RecordStore can now be created at runtime from data in the Interchange File Format by using the newly defined RecordStore static method
importRecordStore(
InputStream inputStream,
String importPassword,
String internalPassword)
The inputStream argument is an instance of java.io.InputStream from which to read the data.
The importPassword argument is the password to use to decrypt the data being imported.
The internalPassword argument is the password to use to encrypt the created RecordStore. If it is null then the created RecordStore will not be encrypted.
It is an error if a RecordStore with the same name as that being imported already exists, and a RecordStoreException will be thrown.
If the data to import is encrypted and there is a cryptographic error a SecureRecordStoreException will be thrown.
Note
The specification does not enumerate any further possible errors, although there is at least one,
which is when the supplied data does not conform to the IFF, and for this or, for any other error
the only suitable declared exception is RecordStoreException.
Code Fragment
Create an unencrypted RecordStore from unencrypted data.
import java.io.InputStream;
import javax.microedition.rms.RecordStore;
...
// Get data to import as an InputStream
InputStream is = ...
RecordStore.importRecordStore(is, null, null);
...
Code Fragment
Create an unencrypted RecordStore from encrypted data. Possibly not a good idea.
import java.io.InputStream;
import javax.microedition.rms.RecordStore;
...
// Get the data to import as an InputStream
InputStream is = ...
// Get the password to use to decrypt the imported data.
String importPassword = ...
RecordStore.importRecordStore(is, importPassword, null);
...
Code Fragment
Create an encrypted RecordStore from unencrypted data.
import java.io.InputStream;
import javax.microedition.rms.RecordStore;
...
// Get the data to import as an InputStream
InputStream is = ...
// Get the password to use to encrypt the created RecordStore.
String recordStorePassword = ...
RecordStore.importRecordStore(is, null, recordStorePassword);
...
Code Fragment
Create an encrypted RecordStore from encrypted data.
import java.io.InputStream;
import javax.microedition.rms.RecordStore;
...
// Get the data to import as an InputStream
InputStream is = ...
// Get the password to use to decrypt the imported data.
String importPassword = ...
// Get the password to use to encrypt the created RecordStore.
String recordStorePassword = ...
RecordStore.importRecordStore(is, importPassword, recordStorePassword);
...
7. MIDlet Suite Installation Time RecordStore Creation
At installation time a MIDlet Suite can now create RecordStores using data in the IFF. The data can either bundled with the MIDlet Suite, or the location of the data can be specified and it will be downloaded automatically.
7.1 The MIDlet-Persistent-Data-URL-<n> Attribute
A MIDlet suite can create one or more RecordStores at installation time by specifying the appropriate number of
MIDlet-Persistent-Data-URL-<n> attributes in the manifest of the MIDlet suite JAR.
Note
The description of this attribute in the specification does not contain the usual strictures about
ordinals starting at 1 and then being consecutive, with attributes after the first gap
being ignored, but I would assume they do in fact apply.
The value of each attribute specified must be of the form
<dataURL> [" " overwrite] [" " encryptLocally]
The <dataURL> must be an RFC 3986 conformant absolute or relative URI.
If the URI is absolute then the data is external to the MIDlet Suite, and it is automatically downloaded
from the specified location.
If the URI is relative then the data is bundled with the MIDlet Suite, and it is loaded from the specified
location within the MIDlet Suite Jar.
Note
The specification states
If overwrite is specified in the MIDlet-Persistent-Data-URL-<n> attribute value, data from the RMS file MUST replace any existing record store of the same name in the device. If a record store with the same name exists in the device and overwrite is not specified, the data from the RMS file MUST be discarded.
I assume that this describes the required behaviour during the upgrade of a MIDlet Suite.
If encryptLocally is present the created RecordStore will be encrypted, the password to use being obtained from the user.
If the data specified for use in creating the RecordStore is encrypted the user will be prompted for the password to use to decrypt it.
It is an error if an attempt is made to create more than one RecordStore of the same name at installation time.
It is an error if the data at the specified location does not conform to the IFF.
If an error occurs then the MIDlet Suite installation will fail.
8. RecordStoreInfo
A new class RecordStoreInfo has been defined in the javax.microedition.rms package.
The class defines methods which can be used to access information about a RecordStore which was either, previously inaccessible,
getAuthMode()
isWritable()
did not previously exist,
or whose type has been changed.
getSize()
getSizeAvailable()
Note
The methods
getAuthMode()
isEncrypted()
isWritable()
could all have been added to the RecordStore class, but the return types of the existing RecordStore
methods
getSize()
getSizeAvailable()
could not have been changed without breaking binary compatibility.
I assume this is why the decision was made to introduce a new class.
An instance of RecordStoreInfo can be obtained by calling the RecordStore method getRecordStoreInfo().
The values returned by the methods of an instance of RecordStoreInfo reflect the current state of the associated RecordStore.
Once the associated RecordStore has been closed all the methods will throw a RecordStoreNotOpenException.
8.1 getAuthMode()
This method returns the authorization mode of the associated RecordStore as an int.
The returned value is guaranteed to be equal to the value of one of the following RecordStore constants.
AUTHMODE_PRIVATE
AUTHMODE_ANY
AUTHMODE_APPLEVEL
8.2 getSize()
This method returns the size in bytes of the associated RecordStore, including implementation overhead, as a long.
8.3 getSizeAvailable()
This method returns the size in bytes of the storage available for the growth of the associated RecordStore, as a long.
8.4 isEncrypted()
Returns true if the associated RecordStore is encrypted, false otherwise.
8.5 isWriteable()
Returns true if the associated RecordStore can be written by MIDlets authorized to access it, false otherwise.
9. Record Tags
Each record in a RecordStore now has an associated int valued tag.
9.1 Adding A Tagged Record
A tagged record can be added to a RecordStore by using the new RecordStore method
addRecord(byte[] data, int offset, int nBytes, int tag)
The semantics of the method are the same as those of the original addRecord() method,
but in addition the tag of created record is the value specified.
The tag of a record added using the original addRecord() method is 0.
9.2 Getting A Record's Tag
The tag of a specific record can be obtained by using the RecordStore method
getTag(int recordId)
The method returns an int.
If a record with the specified does not exist then an InvalidRecordIDException is thrown.
9.3 Modifying A Record's Tag
The tag of a specific record can be modified by using the RecordStore method
setRecord(
int recordId,
byte[] newData,
int offset,
int numBytes,
int tag)
The first four arguments are identical to the arguments of the original version of this method.
The tag argument specifies the new value of the tag.
The behavior of this method is identical to that of the original version of the method, but in
addition the value of the tag is set to the value specified.
Note
This method is decidedly sub-optimal if only the tag is to be changed as in this case it is always
necessary to obtain the contents of the record first.
9.4 Enumerating Tagged Records
An enumeration of the records whose tag matches one of a set of tags can be obtained using the method
enumerateRecords(
RecordFilter filter,
RecordComparator comparator,
boolean keepUpdated,
int[] tags)
The first three arguments are identical to those of the original version of the method.
The tags argument specifies the set of tags to be matched. The value can be one of
null
int[0] (an empty int array)
int[n > 0] (a non-empty int array)
The semantics of the method are the same as those of the original, except that the set of records returned,
modulo the filter, is constrained by the tags argument as follows
- if it is
null there is no constraint and the method behaves identically to the original version
- if it is an empty array then the set of records is empty
- if it is a non-empty array then the set will contain only those records with a tag equal to one of the values of
tags
10. SecureRecordStoreException
The exception class SecureRecordStoreException has been defined in the package javax.microedition.rms. It is thrown when there is an encryption/decryption related error.
11. Deprecated RecordStore Methods
Following the addition of the RecordStoreInfo class, the RecordStore methods
getSize()
getSizeAvailable()
have been deprecated.
The corresponding methods on an associated instance of RecordStoreInfo should be used instead.
12. Support For Large RecordStores ?
The addition of the RecordStoreInfo class whose getSize() and getSizeAvailable() methods both return a long makes it possible for an implementation to support RecordStores larger than 2GB, in the sense that it can actually report the size of such a RecordStore correctly.
RecordStores of this size are clearly feasible in terms of the kind of storage capacity now available at least on some mobile devices, but they remain constrained in other respects.
- the number of records in a RecordStore is still represented using an
int
- the version number of a RecordStore is still represented using an
int
- the id of a record is still represented using an
int
- the size of a record is still represented using an
int
Unless the changes were seriously intended to make it possible for people to create terabyte-scale RecordStores none of this is likely to be a major problem. What may be a problem if people do begin to create gigabyte-scale RecordStores
is performance.
The RecordStore API is extremely low-level. The creation, deletion, and modification of records is single record atomic,
and the effects of these operations may have to be made visible to multiple MIDlets. This constrains the implementation
and the possible optimization techniques that can be applied. Operations on gigabyte scale RecordStores may not be all
that fast.
Copyright (c) 2009 By Simon Lewis. All Rights Reserved.