Palm OS Cobalt has a robust and comprehensive security architecture. Unlike other security solutions that are added in an ad-hoc manner to existing operating systems, the security for Palm OS Cobalt has been designed in from the beginning.
The basis of Palm OS Cobalt security is the secure kernel. The kernel relies on a capabilities model for security. The capabilities model, typically a model of least privilege, has been hybridized for Palm OS Cobalt in order to maintain the open nature of Palm OS. At boot time, keys are carefully distributed to various system managers that need to communicate with each other. Only managers that have keys enabling communication are able to communicate with other system components. This prevents unauthorized access to important system modules.
On top of the secure kernel are the components that make up the basis of a secure infrastructure. Figure 1.1 illustrates how these components interrelate.
Figure 1.1 Palm OS Cobalt security components

The Authorization Manager (AM) provides system managers with the ability to protect managed resources. In the case of Data Manager, database resources are protected via the Authorization Manager. Other components, such as drivers, may protect other resources such as network access through the Authorization Manager. In conjunction with the Authorization Manager the Authentication Manager (AM) provides authentication for authorization rules and supports other authentication requirements. The Authentication Manager employs a plug-in architecture so that additional authentication mechanisms can be added. The Authentication Manager supports password/pass phrase authentication for users and both PKI and cryptographic fingerprint for code modules. Additional authentication mechanisms, such as biometric, can be added via the Authentication Manager plug-in framework.
Palm OS Cobalt also includes the Cryptographic Provider Manager (CPM). The Cryptographic Provider Manager exposes a simple yet robust API for performing cryptographic operations including key generation, hashing, encryption, decryption, signing, and verification. The Cryptographic Provider Manager includes a FIPS–approved pseudo random number generator and a provider architecture for cryptographic algorithms. The default provider, developed by RSA Security, includes RC4 (128 bit), SHA-1 hashing, and RSA public key operations (1024 bit). Additional providers can be added, either statically by the licensee or dynamically, to the Cryptographic Provider Manager to support other algorithms.
Palm OS Cobalt includes a Certificate Manager, developed by RSA Security. The Certificate Manager handles X.509 standard certificates. The Certificate Manager exposes a standard API for applications and system modules that need certificate services.
Making use of both the Cryptographic Provider Manager and the Certificate Manager, Palm OS Cobalt includes a Signature Verification Library that allows applications and system modules to easily verify signatures on code modules and resources.
Palm OS Cobalt also includes a robust and highly optimized version of SSL for end-to-end secure communications. The Palm OS Cobalt SSL implementation, by RSA Security, supports SSL v2, v3, and TLS 1.0.
The Palm OS Cobalt Security Services module that supports a variety of mechanisms for specifying and controlling the security policies of a particular device or class of devices. Security Services supports a policy API that applications and code modules can query to get policies for various operations or functionality. Examples include policies for what types of patches are allowed on the system and polices for what drivers are allowed on the system. The Security Services also supports a set of APIs so that the user can indicate a perceived level of security of the device (None, Medium, or High). Various modules and applications can read the user's security preference and react accordingly.
PalmSource signs all shared library code modules. Code modules easily support multiple signatures, enabling licensees and carriers to sign code modules.
The following sections provide details on each of the Palm OS Cobalt security components. Note that the details of SSL are covered in Chapter 2, "SSL Concepts."
Cryptographic Provider Manager (CPM)
The Cryptographic Provider Manager (CPM) provides an interface for cryptographic related functions. At its heart it contains an X 9.31 FIPS–approved pseudo random number generator. The CPM allows you to do the following:
The CPM also supports the SSL cryptographic package. See Chapter 2, "SSL Concepts," for more on SSL in Palm OS Cobalt.
Note that the CPM is export controlled. CPM providers must be signed.
The Cryptographic Provider Manager, or CPM, provides an easy to use, yet robust cryptographic API. Applications can use the CPM to perform cryptographic operations for data and protocols. Under the CPM there are one or more providers which supply the actual cryptographic functionality via the CPM API.
The CPM supports various classes of functions, some of which are described herein. For further information see the CPM documentation.
The classes of functions discussed herein can be grouped as follows:
Provider Information and Manipulation
Key Functions
Message Digest Functions
Encryption and Decryption Functions
Provider Information and Manipulation
The CPM itself provides the ability to query the number of providers present, modify the order the providers are called, and a pseudo random number generator based on ANSI X9.31
The CPM contains a default provider which includes SHA-1 hashing, RC4 encryption and decryption, and RSA verification (public key operations). Other providers may or may not be present.
The operation of the CPM is different from other cryptographic providers that perform the same types of operations. Applications that use the CPM can get default "reasonable" behavior without specifying a great deal of information related to the cryptographic operation requested. Examples will illustrate this.
Listing 1.1 shows how to enumerate and identify the providers the CPM currently knows about.
Listing 1.1 Enumerating CPM providers
uint32_t *providers; APProviderInfoType providerInfo; uint16_t temp = 0; status_t err; err = CPMLibOpen(&temp); if (err) { DbgPrintf("SSFD: Error on CPMLibOpen 0x%x\n", err); } else { DbgPrintf("SSFD: CPMLibOpen - 0x%x providers returned\n", temp providers = MemPtrNew(sizeof(uint32_t) * temp); err = CPMLibEnumerateProviders(providers, &temp); providers = MemPtrNew(sizeof(uint32_t) * temp); err = CPMLibEnumerateProviders(providers, &temp); if (err) { DbgPrintf("SSFD: Error on CPMLibEnumerateProviders 0x%x\n", err); } else { DbgPrintf("SSFD: CPMLibEnumerateProviders - 0x%x providers returned\n", temp); for (i=0; i < temp; i++) { err = CPMLibGetProviderInfo(providers[i], &providerInfo); if (err) { DbgPrintf("SSFD: Error on CPMLibGetProviderInfo 0x%x\n", err); } else { uint32_t provider provider = providers[i]; provider = providers[i]; DbgPrintf("SSFD: CPMLibGetProviderInfo - provider['%c%c%c%c']\n", (char)((provider >> 24) & 0x000000FF), (char)((provider >> 16) & 0x000000FF), (char)((provider >> 8) & 0x000000FF), (char)((provider & 0x000000FF))); DbgPrintf("\t%s\n", providerInfo.name); DbgPrintf("\t%s\n", providerInfo.other); DbgPrintf("\tAlgs: %d\n", providerInfo.numAlgorithms); DbgPrintf("\tHardware?: %s\n", providerInfo.bHardware?"yes":"no"); } } }
The order that the providers are called is arbitrary. The CPM orders the providers as they are found and calls each one in turn until one returns that it can handle the request. Subsequent calls on the same context of operations will go to the same provider that handled the first request. For example, say the first operation in an encryption context is generating a key to use for encryption. The provider that handles the key generation will also be used to do the encryption unless the application explicitly changes it.
For any given initial operation, or one for which a provider has not yet been selected, the CPM tries each provider in turn until one returns that the operation has been handled. Subsequent providers are not called. Due to this design, the CPM does not readily handle providers that include similar functionality.
The application is free to select a different provider for any operation for which the provider has already been set. The application is also free to set the first provider that the CPM will call, ensuring that all operations will go to a particular provider for the initial context. To set the default provider, do something like what is shown in Listing 1.2.
Listing 1.2 Setting the default CPM provider
status_t err; /* * set the default provider to the provider with id 'foop' */ err = CPMLibSetDefaultProvider((uint32_t) 'foop'); if (err) DbgPrintf("SSFD: Error on CPMLibSetDefaultProvider 0x%x\n", err);
All CPMInfoType
structures have a common structure header which includes provider information about the provider that handled the structure. To change the provider for a given CPMInfoType
structure, the application must copy the structure and reset the provider information. The application is then responsible for making sure that the original structure is passed to the original provider for cleanup. This operation is not recommended without specific knowledge of the operation and functionality of the various providers utilized. See Listing 1.3 for an illustration of how this might be done.
Listing 1.3 Changing the provider
status_t err; APKeyInfoType keyInfo, newkeyInfo; MemSet(&keyInfo, sizeof(APKeyInfoType), 0); err = CPMLibGenerateKey(NULL, 0, &keyInfo); MemSet(&newkeyInfo, sizeof(APKeyInfoType), 0); MemMove(&newkeyInfo, &keyInfo, sizeof(APKeyInfoType)); newkeyInfo.providerContext.localContext = NULL; newkeyInfo.providerContext.providerID = 0; CPMLibReleaseKeyInfo(&keyInfo); /* now go on to use newkeyInfo as you please */
Key Functions
The CPM provides several ways of introducing keys to the cryptographic functions. The CPM has concepts of either generating a brand new key, deriving a key from some initial data, or importing a previously generated key.
Generating a new key implies that every time a request for a new key to be generated is made, a brand new key is generated. If this brand new key is utilized for any cryptographic operations, it must be exported and saved in order to be used again. It is statistically improbable that a generated key could be regenerated.
Deriving a key means that given the same input data, the same key is derived from the data. This is useful for operations like Password Based Encryption (PBE) where the password is used to derive a key for a particular cryptographic operations (usually encryption or decryption).
Importing a key means that a previously generated key which was exported and saved by an application is now being imported for further cryptographic operations. Importing a key can also mean that key data from a derive key operation is now being used to create a APKeyInfoType
object. In general, an application would not export and save a derived key since it could be re-derived by using the same input data. A generated key, however, must be exported and saved if it is to be used for later cryptographic operations.
Note that the CPM is designed to work with very little information about the specific cryptographic operation requested. Especially for data that remains on the originating device, most of the input and output parameters for CPM APIs can be ignored.
To generate a new key (the application does not care about the type of key since it is to be used to encrypt data that remains on the device), do something along the lines of what is shown in Listing 1.4.
Listing 1.4 Generating a new key
status_t err; APKeyInfoType keyInfo; MemSet(&keyInfo, sizeof(APKeyInfoType), 0); err = CPMLibGenerateKey(NULL, 0, &keyInfo); if (err) { DbgPrintf("SSFD: Error on CPMLibGenerateKey 0x%x\n", err); } else { DbgPrintf("SSFD: CPMLibGenerateKey - key of length 0x%x returned\n", keyInfo.length); }
The call to generate a key allows the application to specify some data to use as seed data for the pseudo-random number generator before the pseudo-random number generator is used. Listing 1.5 illustrates how to generate a key of a specific type and length.
Listing 1.5 Generating a key of a specific type and length
status_t err; APKeyInfoType keyInfo; uint8_t *seedData; uint32_t seedDataLength; MemSet(&keyInfo, sizeof(APKeyInfoType), 0); keyInfo.type = apSymmetricTypeRijndael; keyInfo.length = 256/8; /* 256 bit key or 32 byte key */ /* provide some seed data to the random generator for generating the key */ GetSomeSeedData(seedData, seedDataLength); err = CPMLibGenerateKey(seedData, seedDataLength, &keyInfo); if (err) { DbgPrintf("SSFD: Error on CPMLibGenerateKey 0x%x\n", err); } else { DbgPrintf("SSFD: CPMLibGenerateKey - key of length 0x%x returned\n", keyInfo.length); }
All of the CPM APIs that include an output buffer for results allow the application to specify a NULL
output buffer and a valid output buffer size pointer to receive the required output buffer size. In this way an application can request the required output buffer size before making the actual call.
Derived keys can have some complicated parameters like iterations and salts which are better described elsewhere. In general, the CPM, and providers will provide "sane" functionality when parameters are left unspecified.
Deriving a key just returns exportable key data. To actually use a derived key, the application must import the key data to get an APKeyInfoType
. The APKeyInfoType
is used in subsequent cryptographic operations. See Listing 1.6 for sample code that derives a key.
status_t err; APDerivedKeyInfoType dki; APKeyInfoType keyInfo; uint32_t size; uint32_t *key_data; /* * this is provider dependent */ struct { unsigned long length; unsigned char *data; } kdInfo; MemSet(&dki, sizeof(APDerivedKeyInfoType), 0); MemSet(&kdInfo, sizeof(kdInfo), 0); GetUserPassword(kdInfo.data, kdInfo.length) dki.kdInfo = &kdInfo; size = 0; err = CPMLibDeriveKeyData(&dki, NULL, &size); if (err) { DbgPrintf("SSFD: Error on CPMLibDeriveKeyData 0x%x\n", err); if (err == cpmErrBufTooSmall) { DbgPrintf("SSFD: cpmErrBufTooSmall with CPMLibDeriveKeyData returning %d\n", size); key_data = MemPtrNew(size); if (key_data != NULL) { err = CPMLibDeriveKeyData(&dki, key_data, &size); if (err) { DbgPrintf("SSFD: Error on CPMLibDeriveKeyData 0x%x\n", err); } else { DbgPrintf("SSFD: CPMLibDeriveKeyData - 0x%x bytes returned\n", size /* now we can use key_data as import data to get a key */ MemSet(&keyInfo, sizeof(APKeyInfoType), 0); err = CPMLibImportKeyInfo(IMPORT_EXPORT_TYPE_RAW, key_data, size, &keyInfo); if (err) { DbgPrintf("SSFD: Error on CPMLibImportKeyInfo 0x%x\n", err); } else { DbgPrintf("SSFD: CPMLibImportKeyInfo - key of length 0x%x returned\n", keyInfo.length); } } } }
Import is used with keys from various sources such as a saved database, a static application key or a key sourced from a protocol negotiation. Typically an application must specify the type of key that is being imported and the import format1. Since import data is essentially raw byte streams, its important that the application specify something.
status_t err; APKeyInfoType keyInfo; uint8_t key[] = { 0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF }; MemSet(&keyInfo, sizeof(APKeyInfoType), 0); keyInfo.type = apSymmetricTypeDES; err = CPMLibImportKeyInfo(IMPORT_EXPORT_TYPE_RAW, key, sizeof(key), &keyInfo); if (err) { DbgPrintf("SSFD: Error on CPMLibImportKeyInfo 0x%x\n", err); } else { DbgPrintf("SSFD: CPMLibImportKeyInfo - key of length 0x%x returned\n", keyInfo.length); }
Message Digest Functions
Message digests or hashes are cryptographically strong one way functions. A one way function yields a series of bits that represent the input message. Nothing about the input message can be gleaned from the message digest. The same input message always generates the same hash. Typically hashes are used slow operations are to be performed on long messages. Rather than performing the slow operation on the entire message, the long operation is performed on the hash of the message which is much shorter.
The CPM has two modes of operation for message digests. One mode takes the input message as whole and outputs a digest. The other mode takes the input message as parts and doesn't output the digest until the final part of the message is submitted.
In the all-in-one-shot mode of operation, no context is required for the hashing operation. The application can safely ignore the APHashInfoType
parameter for the AIO operations. Listing 1.8 shows how to do a hashing operation in a single pass.
Listing 1.8 A single-pass hashing operation
status_t err; uint32_t size; uint8_t data[] = ( 'f', 'o', 'o' ); uint8_t *md; size = 0; err = CPMLibHash(apHashTypeSHA1, NULL, data, sizeof(data), NULL, &size); if (err) { DbgPrintf("SSFD: Error on CPMLibHash 0x%x\n", err); if (err == cpmErrBufTooSmall) { DbgPrintf("SSFD: cpmErrBufTooSmall with CPMLibHash returning %d\n", size); md = MemPtrNew(size); if (md != NULL) { err = CPMLibHash(apHashTypeSHA1, NULL, data, sizeof(data), md, &size); if (err) { DbgPrintf("SSFD: Error on CPMLibHash 0x%x\n", err); } else { DbgPrintf("SSFD: CPMLibHash - 0x%x bytes returned\n", size); } } }
For the multi-part mode of operation, a context is required to pass from initial operation to subsequent operations. Upon return from the final operation the context must be cleaned up. It is the application's responsibility to pass the context to the Release function for cleanup by the CPM and providers. See Listing 1.9 for a sample illustrating the multi-part hashing operation.
Listing 1.9 A multi-part hashing operation
status_t err; uint32_t size; uint8_t data[] = ( 'f', 'o', 'o' ); uint8_t *md; APHashInfoType hashInfo; MemSet(&hashInfo, sizeof(APHashInfoType), 0); hashInfo.type = apHashTypeSHA1; err = CPMLibHashInit(&hashInfo); /* initialize the context */ if (err) { DbgPrintf("SSFD: Error on CPMLibHashInit 0x%x\n", err); } else { /*update the operation; can do this any number of times */ err = CPMLibHashUpdate(&hashInfo, data, sizeof(data)); if (err) { DbgPrintf("SSFD: Error on CPMLibHashUpdate 0x%x\n", err); } else { size = 0; err = CPMLibHashFinal(&hashInfo, NULL, 0, NULL, &size); if (err) { DbgPrintf("SSFD: Error on CPMLibHashFinal 0x%x\n", err); if (err == cpmErrBufTooSmall) { DbgPrintf("SSFD: cpmErrBufTooSmall with CPMLibHash returning %d\n", size); md = MemPtrNew(size); if (md != NULL) { /* finalize the operation */ err = CPMLibHashFinal(&hashInfo, NULL, 0, md, &size); if (err) { DbgPrintf("SSFD: Error on CPMLibHash 0x%x\n", err); } else { DbgPrintf("SSFD: CPMLibHashFinal - 0x%x bytes returned\n", size); } } } } } /* release the context */ CPMLibReleaseHashInfo(&hashInfo); }
Certain applications may require the hashing context to be saved off and returned to at a later time. The APHashInfoType
structures may be exported and imported in much the same way as keys are imported and exported.
Encryption and Decryption Functions
The CPM supports encryption and decryption in much the same was as hashing is supported. That is encryption and decryption have two modes of operation. An application can either encrypt or decrypt a message as a whole, or in parts. Providers are not required to support both modes.
Encryption algorithms either work on data a byte at a time or in blocks of bytes (usually blocks of 8 bytes) at a time. The former is called stream encryption while the latter is called, appropriately enough, block encryption. Typically with block encryption, some padding is added to the data to make the data an integral number of blocks. Providers are not required to support padding. If the provider does not support padding the application must pad data for a block encryption algorithm or an error occurs.
As with the hashing operation, no context is required for the operation if you perform the encryption in a single step. This is illustrated in Listing 1.10.
Listing 1.10 A single-pass encryption operation
status_t err; uint8_t key[] = {0x7C, 0xA1, 0x10, 0x45, 0x4A, 0x1A, 0x6E, 0x57}; uint8_t plain[] = {0x01, 0xA1, 0xD6, 0xD0, 0x39, 0x77, 0x67, 0x42}; uint8_t *output; uint32_t index, size; APKeyInfoType keyInfo; MemSet(&keyInfo, sizeof(APKeyInfoType), 0); keyInfo.type = apSymmetricTypeDES; err = CPMLibImportKeyInfo(IMPORT_EXPORT_TYPE_RAW, key, 8, &keyInfo); if (err) { DbgPrintf("SSFD: Error on CPMLibImportKeyInfo 0x%x\n", err); } else { DbgPrintf("SSFD: CPMLibImportKeyInfo - key of length 0x%x returned\n", keyInfo.length); size = 0; err = CPMLibEncrypt(&keyInfo, NULL, plain, 8, NULL, &size); if (err) { DbgPrintf("SSFD: Error on CPMLibEncrypt 0x%x with size set to 0x%x\n", err, size); } else { DbgPrintf("SSFD: CPMLibEncrypt - cipher data of length 0x%x returned\n", size); } if (err = cpmErrBufTooSmall) { output = MemPtrNew(size); if (output != NULL) { err = CPMLibEncrypt(&keyInfo, NULL, plain, 8, output, &size); if (err) { DbgPrintf("SSFD: Error on CPMLibEncrypt 0x%x\n", err); } else { DbgPrintf("SSFD: CPMLibEncrypt - cipher data of length 0x%x returned\n", size); } MemPtrFree(output); } } CPMLibReleaseKeyInfo(&keyInfo); }
With the multi-part encryption, the application must pass the context from the initial operation through to the final operation. The multi-part encryption is much the same as the multi-part hashing. The difference is that the provider does not maintain the state of the encrypted data during an update. The application must supply an output buffer for each update. The final operation will handle the last input data and any padding that is required to make a full encryption block of data. This is illustrated in Listing 1.11.
Listing 1.11 A multi-part encryption operation
status_t err; uint32_t size; uint8_t key[] = {0x7C, 0xA1, 0x10, 0x45, 0x4A, 0x1A, 0x6E, 0x57}; uint8_t plain1[] = {0x01, 0xA1, 0xD6, 0xD0, 0x39, 0x77, 0x67, 0x42}; uint8_t plain2[] = {'f', 'o', 'o', 'b', 'a', 'z', 'a', 'r'}; uint8_t *output; APKeyInfoType keyInfo; APCipherInfoType cipherInfo; MemSet(&keyInfo, sizeof(APKeyInfoType), 0); keyInfo.type = apSymmetricTypeDES; err = CPMLibImportKeyInfo(IMPORT_EXPORT_TYPE_RAW, key, 8, &keyInfo); if (err) { DbgPrintf("SSFD: Error on CPMLibImportKeyInfo 0x%x\n", err); } else { DbgPrintf("SSFD: CPMLibImportKeyInfo - key of length 0x%x returned\n", keyInfo.length); MemSet(&cipherInfo, sizeof(APCipherInfoType), 0); /* initialize the context */ err = CPMLibEncryptInit(&keyInfo, &cipherInfo); if (err) { DbgPrintf("SSFD: Error on CPMLibEncryptInit 0x%x\n", err); } else { /* update the operation; can do this any number of times */ size = 0; err = CPMLibEncryptUpdate(&keyInfo, &cipherInfo, plain1, sizeof(plain1), NULL, size); if (err) DbgPrintf("SSFD: Error on CPMLibEncryptUpdate 0x%x\n", err); if (err == cpmErrBufTooSmall) { output = MemPtrNew(size); if (output != NULL) { err = CPMLibEncryptUpdate(&keyInfo, &cipherInfo, plain1, sizeof(plain1), output, size); if (err) { DbgPrintf("SSFD: Error on CPMLibEncryptUpdate 0x%x\n", err); } else { /* do something with output */ err = CPMLibEncryptFinal(&keyInfo, &cipherInfo, plain2, sizeof(plain2), output, &size); if (err) DbgPrintf("SSFD: Error on CPMLibEncryptFinal 0x%x\n", err); if (err == cpmErrBufTooSmall) { DbgPrintf("SSFD: cpmErrBufTooSmall with CPMLibEncryptFinal returning %d\n", size); output = MemPtrRealloc(output, size); if (output != NULL) { err = CPMLibEncryptFinal(&keyInfo, &cipherInfo, plain2, sizeof(plain2), output, &size); if (err) { DbgPrintf("SSFD: Error on CPMLibEncryptFinal 0x%x\n", err); } else { /* do something with output */ DbgPrintf("SSFD: CPMLibEncryptFinal - 0x%x bytes returned\n", size); } } } } MemPtrFree(output); } } CPMLibReleaseCipherInfo(&cipherInfo); } CPMLibReleaseKeyInfo(&keyInfo); }
Decryption is almost exactly the same as encryption. All in one and multi-part decryption is supported if the provider supports the two modes. Padding is required if the provider does not perform padding and the application must ensure that the contexts are released correctly.
As with encryption, the cipher context can be safely ignored if the operation is performed in a single step, as shown in Listing 1.12.
Listing 1.12 A single-pass decryption operation
status_t err; uint8_t key[] = {0x7C, 0xA1, 0x10, 0x45, 0x4A, 0x1A, 0x6E, 0x57}; uint8_t cipher[] = (0x69, 0x0F, 0x5B, 0x0D, 0x9A, 0x26, 0x93, 0x9B}; uint8_t *output; uint32_t index, size; APKeyInfoType keyInfo; MemSet(&keyInfo, sizeof(APKeyInfoType), 0); keyInfo.type = apSymmetricTypeDES; err = CPMLibImportKeyInfo(IMPORT_EXPORT_TYPE_RAW, key, 8, &keyInfo); if (err) { DbgPrintf("SSFD: Error on CPMLibImportKeyInfo 0x%x\n", err); } else { DbgPrintf("SSFD: CPMLibImportKeyInfo - key of length 0x%x returned\n", keyInfo.length); size = 0; err = CPMLibDecrypt(&keyInfo, NULL, cipher, 8, NULL, &size); if (err) { DbgPrintf("SSFD: Error on CPMLibDecrypt 0x%x with size set to 0x%x\n", err, size); } else { DbgPrintf("SSFD: CPMLibDecrypt - deciphered data of length 0x%x returned\n", size); } if (err == cpmErrBufTooSmall) { output = MemPtrNew(size); if (output != NULL) { err = CPMLibDecrypt(&keyInfo, NULL, cipher, 8, output, &size); if (err) { DbgPrintf("SSFD: Error on CPMLibDecrypt 0x%x\n", err); } else { DbgPrintf("SSFD: CPMLibDecrypt - deciphered data of length 0x%x returned\n", size); } MemPtrFree(output); } } CPMLibReleaseKeyInfo(&keyInfo); }
Authentication Manager
The Authentication Manager (AM) is an abstraction layer between applications and authentication methods. The framework provided by the AM allows modules (plug-ins) to be written that implement specific authentication scenarios. Users of the AM deal with generic interfaces and opaque objects that define an authentication context.
The Authentication Manager is the authority that can answer the question "Are you X?" reliably, by utilizing some method of identity verification such as a password. The question "Are you X?" may be asked about a user or an application.
The services provided by the AM handle the following tasks: credential (Token) management (creation, deletion, modification, and storage), authentication against stored credentials (querying user for system password), and a framework for run-time extensibility via plug-ins.
The Palm OS Cobalt implementation of the AM includes three authentication models:
- Password based authentication (as in OS5)
- Signed code (PKI) based authentication
- Code fingerprint (hashed code) based authentication
Authentication Tokens
A token is a reference to an authentication requirement. The structure that represents a token contains credentials. Tokens can either be system tokens or non-system tokens. The only difference is in how the AM behaves when it destroys a token. When a system token is destroyed the AM takes all of the same actions as when destroying a non-system token, except that the named entry for the token is not removed from the AM's list of tokens. This is due to the fact that system tokens should always exist: they are "well known" tokens, such as the user token (password), or the admin token (password). Tokens can be marked as system tokens at the time they are created.
Every token has a unique system ID.
Token Types
AmTokenEnum
is an enumeration of the different types of tokens that can be requested from the system. These are the most common type of tokens that the device will deal with. The custom type (AmTokenCustom
) allows the plug-in to announce a custom type of token that it will service. If an application requests a custom token, the Authentication Manager examines all plug-ins and finds all that match that custom type. Out of all the matches the best fit is picked to create the token.
typedef enum { AmTokenUnknown = 0, AmTokenCustom, AmTokenPassword, AmTokenSignedCode, AmTokenCodeFingerprint } AmTokenEnum
To authenticate a token, the AM invokes a plug-in that implements a specific authentication method.
Token Strength
Associated with each token is the concept of "strength." Tokens can be either strong or weak; weak tokens are authentication tokens that can be easily guessed or broken, such as dictionary words for passwords, or weak cryptography keys. Within the token structure is the minimum level of strength that the plug-in supports for token creation. The following levels are defined:
- AmTokenStrengthLow
- The lowest level. There are no requirements for token creation.
- AmTokenStrengthMedium
- Some measures are taken to reject weak tokens.
- AmTokenStrengthHigh
- The generated token should be guaranteed to not be a weak token.
Token Management Functions
The Authentication Manager APIs include functions to create, destroy, modify, and authenticate tokens:
To get information about a token, you use one of these functions:
Finally, when manipulating the plug-ins themselves you work with these Authentication Manager functions:
The Authentication Manager also supports the "legacy" APIs from earlier versions of Palm OS. These are the functions declared in Password.h
. (PwdExists()
, PwdRemove()
, PwdSet()
, and PwdVerify()
). These functions all act on the user token. and only work if the user token is of type AmTokenPassword
.
The Authorization Manager functions are easy to use. For instance, the code excerpt in Listing 1.13 shows how to authenticate the user token.
Listing 1.13 Authenticating the user token
AmTokenType token; status_t err; AmGetTokenBySystemId(&token, SysUserToken); err = AmAuthenticateToken(token, NULL, AmAuthenticationOther, NULL, NULL); if (err == errNone){ // Authentication succeeded. Do something here. } else { // Authentication failed. }
AmAuthenticateToken()
can take hints about what type of authentication is being performed (database access, device unlock, and so on). In the above example it is AmAuthenticationOther
. This function can also take and optional title and description strings. These can be used by the AM plug-in to clarify to the user just why they are being prompted to enter a password (or provide a thumbprint, or whatever).
Using the Authentication Manager
The Authentication Manager can be used either to authenticate a user or to authenticate code.
User Authentication
User authentication requires that the user knows or has on his possession a secret that identifies him to the system. This secret comes in the form of a PIN, a password, biometrics, and so forth. The Authentication Manager collects the credentials being presented by the user and compares them to the stored credentials, thereby authenticating the user.
Code Authentication
There are two major scenarios when it comes to code authentication: signed code and unsigned code.
Signed code is usually a third-party application or a system patch. It was signed with a certificate, which was assigned by a certificate authority. The AM can verify the signature of the code and authenticate the identity of the certificate that was used when the code was signed. This is how the system protects patchable or replaceable objects: by requiring that the application patching or replacing an object be signed by a well-known certificate.
Code that is not signed is treated differently. It is expensive to acquire certificates from a certificate authority, and most shareware developers will not go through the trouble, yet the system is still able to protect data from access by any other application. In order to provide a non-interactive authentication method, the system creates a token that uniquely identifies an application when that application is installed on the device. An application may use this token to protect objects, and the AM can then verify the identity of the application by re-calculating the identity of the application and matching it against the identity that was calculated at install time.
Signature Verification Library
The Signature Verification Library does the bulk of the work for the following code authentication tasks:
- Interpreting the sign resource in an application's resource database.
- Extracting the X.509 certificate block from the sign resource.
- Verifying the validity of a digital signature in a PRC file.
This library enables any application on the device to verify a signed PRC file. The Authentication Manager also uses this shared library to authenticate signed code. The AM's task is to authenticate currently running applications. Any other type of authentication that needs to be done can be accomplished by using this shared library.
The Signature Verification Library is covered in detail under "Signature Verification Library."
Creating an Authentication Manager Plug-In
An Authentication Manager plug-in is a shared library of type 'ampl'
that extends the authentication services provided by the AM. Each plug-in implements one authentication model, and is responsible for implementing any UI associated with that model. Authentication Manager plug-ins are loaded by the security process when the new plug-in is registered with the AM.
IMPORTANT: Plug-ins execute in privileged space and have access to the whole system. Bugs in these modules can create security holes that can potentially expose all of the data on the device.
Working With Tokens
Plug-ins publish information about the type of tokens they can create when registering with the AM. The AM then uses that information to find the most appropriate plug-in to use when an application creates a token. An application that wishes to use specific features supported by a specific plug-in is able to request that plug-in when creating a new token.
The plug-in that creates the token defines the structure of the token's data. Since the data for the token must be tamper proof, it is stored in system space (owned by the Authentication Manager), and the application is only given a reference: an AmTokenType
. When the AM is asked to authenticate with a token, the plug-in that created the token is asked to collect a new token and compare it with the stored token. If the two tokens match, the authentication passes.
Plug-ins may define their own internal data structures to use for storing information about the token. The memory may be controlled by the AM or by the plug-in. If the memory allocation and deallocation is to be done by the AM, the plug-in must specify how much memory to allocate per token in the registration structure of the plug-in.
The custom token type (AmTokenCustom
) allows the plug-in to announce a custom type of token that it will service. If your plug-in creates custom tokens, be sure that it fills in the identifier
field of the AmTokenPropertiesType
structure with the identifier for the plug-in. Otherwise, the identifier
field can be set to 0.
Each token has a public info block that can be shared with applications through AmGetTokenInfo()
. This info, defined by the AmTokenInfoType
structure, is set by the plug-in when the token is created. Plug-ins may use their own discretion on how much information to divulge. Note that not all fields are applicable to all types of tokens.
Token attributes (defined by the AmTokenAttributesType
structure) are flags that specify information about the token. The plug-in sets these flags and uses them later to allow or reject certain actions.
- destroy
- the token may be destroyed.
- modify
- the token may be modified.
- interactive
- the token is user interactive. That is, it is a password, PIN, or the like.
- empty
- the token is empty.
- system
- the token is a system token.
The remaining fields of the AmTokenInfoType
structure are set or filled in by the Authentication Manager, except for the "friendly name" which should be supplied by the plug-in.
The function AmAuthenticateToken()
can take hints about what type of authentication is being performed (database access, device unlock, and so on) in the form of an AmAuthenticationEnum
value. This function can also take optional title and description strings. These can be used by the AM plug-in to clarify to the user just why they are being prompted to enter a password (or provide a thumbprint, or whatever).
Plug-In Entry Points
A plug-in is a shared library that has a main entry point (see "The Main Entry Point") which receives launch codes. During the processing of the sysAppLaunchCmdNormalLaunch
launch code it fills in an initialization data structure which lets the AM know the address of its entry points. These entry points constitute a protocol for capturing, replacing, verifying, destroying, importing, and exporting tokens. The AM invokes these entry points in a defined sequence when carrying out a task such as creating a token. Some of the entry points have a context argument (an AmApplicationCtxType
) that lets the plug-in know the context in which it is executing. This allows the plug-in to implement the correct UI associated with a given action under different contexts.
An AM plug-in implements entry points for the following actions:
- Open and Close
- Called at load and unload time, respectively.
- Capture
- Called by the AM during the capture of token information.
- Match
- Called by the AM to compare two tokens.
- Destroy Notify
- Called by the AM when a token is being destroyed.
- Get Extended Info
- Called to get extended information about a plug-in.
- Import and Export
- Called to import or export a plug-in.
- Get Derived Data
- Called to get derived data from a token.
- Admin
- Called by the AM to administer a plug-in.
Except for the PluginOpen
and PluginClose
functions, the plug-in exports the above listed functions in an AmPluginFunctionsType
structure.
Open and Close
PluginOpen
is generally called upon receipt of a sysAppLaunchCmdNormalLaunch
launch code, while PluginClose
is usually called upon receipt of a sysLaunchCmdFinalize
launch code.
PluginOpen
and PluginClose
are not directly invoked by the AM. Instead, the shared library support is used to let the plug-in know about these actions. That is, the main entry point for the shared library receives the following launch codes:
-
sysAppLaunchCmdNormalLaunch
- Instructs the plug-in to initialize itself. This is the "
PluginOpen
" functionality. Along with this launch code the plug-in is passed a pointer to anAmPluginPrivType
structure (in the command block pointer argument). The plug-in should initialize theftn
field with pointers to those functions that the plug-in exports. It should also set up theinfo
field with pertinent information about the plug-in (friendly name, vendor, version, and so on) and the token properties, and set thetokenDataLength
andtokenExtendedInfoLength
fields as appropriate. Finally, the plug-in should open any needed libraries (such as the CPM) and then returnerrNone
. -
sysLaunchCmdFinalize
- Instructs the plug-in to close. It should perform any necessary cleanup and close any libraries opened by the
PluginOpen
function.
Capture
The Capture function is called whenever the AM needs your plug-in to create a new token, or to verify or replace an existing token created by the plug-in. Capture is invoked with one of four defined modes (AmCallMode
): Enrollment, Verification, Replacement Start, and Replacement End. Enrollment is used when a new token is being created. Verification is used when capturing tokens for authentication. Replacement is a two-phase protocol: first the AM may need to authenticate access to modify a token (start), and then it captures a new token to replace the old (end) with.
During the processing of this function the plug-in may implement UI to gather tokens. The mode passed in to this entry point can help determine the exact UI to present.
Your Capture function should use the following prototype:
status_t (*pluginCaptureFtn)(AmCallMode, AmApplicationCtxType *, AmTokenPrivType *, AmAuthenticationEnum, char *, char *)
See the description of AmPluginFunctionsType
for a description of this function's parameters.
Your Capture function should return errNone
if the operation completed successfully. Otherwise, return an appropriate Authentication Manager (or other) error code.
Match
When the AM needs to verify a token it invokes the associated plug-in's match entry point, passing in two token structures for comparision. Any success or failure UI is implemented by the plug-in.
Your Match function should use the following prototype:
status_t (*pluginMatchFtn)(AmApplicationCtxType *, AmTokenPrivType *, AmTokenPrivType *)
See the description of AmPluginFunctionsType
for a description of this function's parameters.
Your Match function should return errNone
if the operation completed successfully. Otherwise, return an appropriate error code such as amErrAuthenticationFailed
.
Destroy Notify
The destroy notification is sent to the plug-in that created the token when the token is destroyed. Destroying a token is an action that may be taken if the user has lost the ability to authenticate against that token (as in the case of a lost password). When creating a token the plug-in sets a flag that allows or disallows its destruction.
Your Destroy Notify function, if your plug-in implements one, should use the following prototype:
status_t (*pluginDestroyNotifyFtn)(AmTokenPrivType *)
See the description of AmPluginFunctionsType
for a description of this function's parameters.
Your Destroy Notify function should return errNone
if the operation completed successfully. Otherwise, return an appropriate Authentication Manager (or other) error code.
Get Extended Info
This function is used to answer the query for extended info by an application. A plug-in is not required to support this entry point, though it can be useful for certain types of tokens. The Palm OS PKI plug-in returns the certificate ID of the token (in an AmPluginSignedCodeExtInfoType
structure). The Palm OS Code Fingerprint plug-in returns the type, creator, and name of the database that was fingerprinted (in an AmPluginCodePrintExtInfoType
structure). The Palm OS password plug-in, however, doesn't implement this function.
Your Get Extended Info function, if your plug-in implements one, should use the following prototype:
status_t (*pluginGetTokenExtendedInfoFtn)(AmTokenPrivType *, uint8_t *, uint32_t)
See the description of AmPluginFunctionsType
for a description of this function's parameters.
Your Get Extended Info function should return errNone
if the operation completed successfully. Otherwise, return an appropriate Authentication Manager (or other) error code.
Import and Export
If the Authentication Manager does all of the memory management for a particular plug-in, then the export and import of that plug-in's tokens is mostly taken care of by the AM. The AM will make sure that the buffer it has allocated for internal data for each token is exported and imported correctly. Plug-ins only need to worry about exporting or importing data that they have allocated themselves.
The Import and Export entry points are for copying internal data about a token for import or export. In your export function, memory that is associated with a token and is managed by the plug-in should be copied to the provided buffer and returned to the AM. The import function should do the opposite: copy the contents of a passed-in buffer into the token memory managed by the plug-in. Note that import and export functions are needed only for tokens that have associated data not managed by the AM itself. Because the Authentication Managert knows how to import and export the buffer that is allocated by the AM for the token data, simple plug-ins such as the password plug-in don't need to implement import and export functions.
Your Import and Export functions, if your plug-in implements them, should use the following prototypes:
status_t (*pluginImportTokenFtn)(AmTokenPrivType *, uint8_t *, uint32_t) status_t (*pluginExportTokenFtn)(AmTokenPrivType *, uint8_t *, uint32_t *)
See the description of AmPluginFunctionsType
for a description of the function parameters.
Your Import and Export functions should return errNone
if the operation completed successfully. Otherwise, return an appropriate Authentication Manager (or other) error code.
Get Derived Data
This function is used solely by the operating system to get seed data for a cryptographic key derived from an authentication token (such as password derived keys). Currently the only user of this feature is the Data Manager; it uses this feature to generate password-derived keys for the backup function.
Your Get Derived Data function, if your plug-in implements one, should use the following prototype:
status_t (*pluginGetDerivedData)(AmTokenPrivType *, uint8_t *, uint32_t *)
See the description of AmPluginFunctionsType
for a description of the function parameters.
Your Get Derived Data function should return errNone
if the operation completed successfully. Otherwise, return an appropriate Authentication Manager (or other) error code.
Admin
This function is the admin entry point for the plug-in. Some plug-ins may have settings that can be changed (a biometric plug-in, for instance, might allow the user to tweak the settings it uses to match tokens); accordingly, the plug-in can implement an admin UI in its implementation of this function.
Your Admin function, if your plug-in implements one, should use the following prototype:
status_t (*pluginAdminFtn)(AmPluginType *)
See the description of AmPluginFunctionsType
for a description of the function parameters.
Your Admin function should return errNone
if the operation completed successfully. Otherwise, return an appropriate Authentication Manager (or other) error code.
The Main Entry Point
The main entry point must have a definition as follows:
Prototype
uint32_t AmPluginMain (uint16_t cmd, MemPtr cmdPBP, uint16_t launchFlags)
Parameters
-
cmd
- The launch code. Of particular interest are
sysAppLaunchCmdNormalLaunch
andsysLaunchCmdFinalize
. -
cmdPBP
- When the launch code is
sysAppLaunchCmdNormalLaunch
, this parameter points to anAmPluginPrivType
structure. The plug-in must fill in this structure before returning. -
launchFlags
- Not used.
Returns
Return errNone
to successfully register the plug-in. Otherwise, return one of the error codes declared in Am.h
.
The following is a sample implementation of a plug-in entry point and initialization function:
Listing 1.14 Sample plug-in entry point function
uint32_t AmPkiPluginMain(uint16_t cmd, MemPtr cmdPBP, uint16_t launchFlags){ switch(cmd) { case sysLaunchCmdInitialize: { // Do custom initialization here break; } case sysAppLaunchCmdNormalLaunch: { // For a PkiPlugin -- do the open AmPluginPrivType *pPlugin = (AmPluginPrivType *)cmdPBP; return (AmPkiPluginOpen(pPlugin)); } case sysLaunchCmdFinalize: { // Do custom de-initialization here break; } default: break; } return 0; } status_t AmPwPluginOpen(AmPluginPrivType *pPlugin){ uint16_t numProviders = 0; status_t err; // Setup the function array pPlugin->ftn.pluginCaptureFtn = AmPwPluginCapture; pPlugin->ftn.pluginMatchFtn = AmPwPluginMatch; pPlugin->ftn.pluginDestroyNotifyFtn = AmPwPluginDestroyNotify; pPlugin->ftn.pluginAdminFtn = AmPwPluginAdmin; pPlugin->ftn.pluginGetDerivedData = AmPwPluginGetDerivedData; // Setup the info piece strcpy(pPlugin->info.friendlyName, PwPluginFriendlyName); strcpy(pPlugin->info.vendor, PwPluginVendor); pPlugin->info.version = PwPluginVersion; pPlugin->info.tokenProperties.type = AmTokenPassword; pPlugin->info.tokenProperties.strength = AmTokenStrengthLow; pPlugin->info.tokenProperties.identifier = PwPluginCreator; // Setup the token length pPlugin->tokenDataLength = sizeof(PwPluginTokenType); return (err); }
Sample Plug-In Implementations
The following sections provide some details about how the sample implementations of the three standard plug-ins provided to Palm OS licencees are implemented.
Password Plug-In
The password plug-in doesn't store plain-text passwords. Instead, it stores hashes of the passwords (SHA1 or MD5). Comparisons are done using the hashes.
The Password plug-in implements the AM plug-in interface in the following manner:
- Open
- Initializes the entry point function array, sets the plug-in properties (
friendlyName
,vendor
, andversion
), sets the token properties (type
=AmTokenPassword
,strength
=AmTokenStrengthLow
,identifier
), sets the token data length tosizeof(PwPluginTokenType)
, and opens the CPM. - Close
- Closes the CPM.
- Capture
- Supports each mode as described under "Capture."
- Match
- Compares the supplied password hashes.
- Destroy Notify
- Frees the memory allocated to the password hint.
- Get Token Extended Info
- Not implemented.
- Import/Export
- Not implemented.
- Get Derived Data
- Copies the password hash into the supplied buffer.
- Admin
- Does nothing.
Certificate Plug-in
The certificate plug-in implements a code-signing authentication model. This plug-in can verify whether a specific application has been signed by a specific certificate. Tokens associated with this plug-in authenticate when the executing application has a signature from this certificate (the certificate ID is stored in the token).
The Certificate plug-in implements the AM plug-in interface in the following manner:
- Open
- Initializes the entry point function array, sets the plug-in properties (
friendlyName
,vendor
, andversion
), sets the token properties (type
=AmTokenSignedCode
,strength
=AmTokenStrengthHigh
,identifier
), sets the token data length tosizeof(PkiPluginTokenType)
, sets thetokenExtendedInfoLength
tosizeof(AmPluginSignedCodeExtInfoType)
, and opens the CPM. - Close
- Closes the CPM.
- Capture
- Implements
AmEnrollment
andAmVerification
.AmReplacementStart
andAmReplacementEnd
simply returnamErrActionnotSupported
. - Match
- Compares the supplied tokens.
- Destroy Notify
- Not implemented. Tokens created by this plug-in cannot be destroyed.
- Get Token Extended Info
- Copies the certificate ID to the supplied buffer.
- Import/Export
- Not implemented.
- Get Derived Data
- Not implemented.
- Admin
- Not implemented.
Application Fingerprint Plug-in
A application fingerprint is a cryptographic hash of an application's resources. This plug-in implements an authentication model where current application must match a previously-stored cryptographic hash. This allows the system to set up access control where a specific application is granted access.
The Application Fingerprint plug-in implements the AM plug-in interface in the following manner:
- Open
- Initializes the entry point function array, sets the plug-in properties (
friendlyName
,vendor
, andversion
), sets the token properties (type
=AmTokenCodeFingerprint
,strength
=AmTokenStrengthLow
,identifier
), sets the token data length tosizeof(CodePrintPluginTokenType)
, sets thetokenExtendedInfoLength
tosizeof(AmPluginCodePrintExtInfoType)
, and opens the CPM. - Close
- Closes the CPM.
- Capture
- Implements
AmEnrollment
andAmVerification
.AmReplacementStart
andAmReplacementEnd
simply returnamErrActionnotSupported
. - Match
- Compares the supplied tokens.
- Destroy Notify
- Not implemented. Tokens created by this plug-in cannot be destroyed.
- Get Token Extended Info
- Copies the database name to the supplied buffer.
- Import/Export
- Not implemented.
- Get Derived Data
- Not implemented.
- Admin
- Not implemented.
Manipulating Authentication Manager Plug-Ins
The header file Am.h
defines functions that allow you to install and remove Authentication Manager plug-ins, as well as get information about an installed plug-in and find all references to an installed plug-in.
Installing a Plug-in
Call AmRegisterPlugin()
to install, or register, a plug-in. This function loads and opens the plug-in shared library. If you attempt to register a plug-in that is already registered you will be notified of that fact unless you set the force
parameter to true
, in which case the plug-in is closed and unloaded, then loaded and reopened. In this case the reference to the plug-in doesn't change; it is re-used. This means that all tokens still have a valid reference to their creator.
As explained in "Plug-in Security," the plug-in system space is protected by a token which must be authenticated against prior to installation; this keeps rogue plug-ins from being allowed onto the device that could circumvent all authentication security.
Removing a Plug-in
You remove an installed Authentication Manager plug-in by calling AmRemovePlugin()
. Note that you can only remove a plug-in if there are no tokens on the device that have been created by that plug-in.
Other Plug-in Manipulation Operations
Call AmGetPluginInfo()
to get the public info block for a registered plug-in. The returned AmPluginInfoType
data structure contains information about the plug-in, such as vendor name, friendly name, and information about the type of tokens that the plug-in can create.
To get a list of all currently registered Authentication Manager plug-ins, call AmGetPluginReferences()
. You must allocate the array into which the list of references (each is an AmPluginType
) is written; call AmGetPluginReferences()
with a NULL
pointer for the array to have returned to you the number of elements that would be written to the array.
Plug-in Security
The storage area used to hold plug-in information is protected by a token. Authentication against this token is necessary before a plug-in can be installed or removed. So, for instance, if the plug-in storage space is protected by a password token, the user would need to enter a password before a plug-in could be installed or removed.
The device manufacturer can create a policy that controls how Authentication Manager plug-ins are installed. If no policies are set, the device behaves as follows when installing a plug-in:
- If the user's current security level is set to None, the plug-in is installed.
- If the user's current security level is set to Medium, the user is prompted to choose whether or not the plug-in should be installed.
- If the user's current security level is set to High, the plug-in is not installed.
Authorization Manager
The Authorization Manager (AZM) manages access control lists that are based on authentication tokens. These control lists are called rule sets. Figure 1.2 illustrates the basic rule-set syntax.

Figure 1.3 illustrates a simple rule set. When this rule set is evaluated, it will be satisfied if the currently running application's signature can be verified with either certificate "A" or certificate "B".

The set of APIs exposed by the Authorization Manager to third-party developers is quite small, consisting only of:
-
AzmAddRule()
, which lets you add an access rule to an existing rule-set container for a specific action. -
AzmNonInteractiveAuthorize()
, which authorizes an action given a rule-set reference. -
AzmGetSyncBypass()
andAzmSetSyncBypass()
, which let you get and set the sync bypass flag in an existing rule-set container for a specific action. Sync-bypass must enabled for a specific action in order for an authenticated sync agent to be able to complete that action successfully.
Note that as a third-party developer you cannot create or destroy rule sets.
Applications use AzmAddRule()
to add rules to a rule set. Listing 1.15 presents one common operation: authenticating the user to perform a particular action.
Listing 1.15 Adding a rule to a rule set
AmTokenType usertoken; AzmRulesetType ruleSet; AmGetTokenBySystemId(&usertoken, SysUserToken); ruleSet = CreateProtectedResource(); err = AzmAddRule(ruleSet, action, "%t", usertoken);
The contents of the action parameter depend upon what is being protected. Many applications will use access rules to control access to schema databases. See "Schema Database Access Rule Action Types" of Exploring Palm OS: Memory, Databases, Files for the access rules that can be applied to schema databases.
The above shows the addition of a very simple rule. More complex rules can be constructed using AND and OR logic operations. To construct a rule set that is valid if either of two tokens is authorized, use the logic operator OR when specifying the rule format string, as in "%t OR %t
". An AND operation is even simpler, since you don't supply the word "AND." Simply specify the format like this: "%t %t
".
Certificate Manager
The Certificate Manager provides a secure server for the storing and parsing of DER-encoded X.509 digital certificates. It exposes functions that allow you to import, export, parse, and verify those certificates.
You can use the Certificate Manager in either of two different ways: as a certificate verifier and parser, and as a certificate store. In the verifier/parser mode, the Certificate Manager takes data as input and parses it as a digital certificate. The user can then verify the certificate and access its internal fields. In certificate store mode, the Certificate Manager can securely store a tree of digital certificates (with multiple roots) and make the fields of those certificates available to users.
The Certificate Manager is a system server with a client-side library. To securely store certificates, the Certificate Manager makes use of the Data Manager's vault facilities. This allows the Certificate Manager to guarantee the integrity of any certificate added to its certificate store.
Note that very few applications use the Certificate Manager directly. As was shown in Figure 1.1, both SSL and the Signature Verification Library make use of the Certificate Manager on the application's behalf. The Certificate Manager only exposes a fairly low-level set of APIs.
Certificate Store Operations
The Certificate Manager can securely store a tree of digital certificates (with multiple roots). Figure 1.4 shows the basic certificate hierarchy.
Figure 1.4 Certificate Hierarchy

At boot time the certificate store is seeded with the list of root certificates that were stored in ROM by the device manufacturer. These ROM certificates are used to authenticate RAM certificates.
To get a certificate from the store, call CertMgrFindCert()
. This function can be used in one of two modes: to find a particular certificate by ID or by subject RDN, or to iterate through all of the certificates in the certificate store. You control this function's operation through the use of the searchFlag
parameter.
To add and remove certificates from the store, you use CertMgrAddCert()
and CertMgrRemoveCert()
, respectively. Note that you can only add a certificate if its authentication chain already resides in the certificate store, or if the certificate is self-signed. Also note that removing a certificate that is part of an authentication chain may prevent new certificates from being authenticated.
The code excerpt shown in Listing 1.16 shows how you can use CertMgrAddCert()
to add a self-signed certificate to the certificate store.
Listing 1.16 Adding a self-signed certificate
while (true) { err = CertMgrAddCert(&certInfo, false, &verifyResult); if (err) { CertMgrReleaseCertInfo(&certInfo); goto exit; } if (verifyResult.failureCode == 0) { break; } else { if (verifyResult.failureCode == CertMgrVerifyFailSelfSigned) { verifyResult.failureCode = 0; continue; } /* Another type of failure */ break; } }
Certificate Verification and Parsing
Use CertMgrImportCert()
to import a DER-encoded x509 certificate and get back a CertMgrCertInfoType
structure. This structure represents a certificate object. You then verify this certificate's contents by calling CertMgrVerifyCert()
. Once you have a verified certificate, use CertMgrGetField()
to get fields out of the certificate. Most commonly, applications will want to get the key from the certificate.
Once you are done with a certificate, be sure to call CertMgrReleaseCertInfo()
to release these resources that were allocated by the Certificate Manager during the call to CertMgrFindCert()
or CertMgrImportCert()
.
Certificate Backup and Restore
All certificates in the certificate store are backed up and restored.
Security Services
In Palm OS Cobalt the security services allow the user of the Palm Powered device to specify a level of "paranoia." This maps directly to the paradigm of private records being visible, masked, or hidden but in Palm OS Cobalt this security setting extends to more than just private records. The security services also control the automatic locking and unlocking of the device. Finally, they also allow licensees to specify basic restrictive policies for various managers and services on the device, and provide APIs that let third-party developers examine those policies.
Current Security Setting
SecSvcsGetDeviceSetting()
and SecSvcsSetDeviceSetting()
allow you to get and set the device's current security setting. The security setting is an indication of how much the user wants to be bothered with security and how "paranoid" the user is. The manager or service reading this setting should follow these guidelines with regard to the security setting:
- SecSvcsDeviceSecurityNone
- The user does not want to be bothered at all and the device is totally open. Everything is "ok" by the user.
- SecSvcsDeviceSecurityMedium
- The user should be bothered with a Yes/No question about the pending operation with as much information about the operation as is reasonably possible (whether the code is signed or unsigned, which manager is performing the operation, details about the operation being performed, and so on). A "Yes" from the user indicates that the operation should proceed. "No" means that the operation should not be performed.
- SecSvcsDeviceSecurityHigh
- The user does not want to be bothered at all and the device is essentially closed. In general, no operations should be performed.
The following table shows how certain aspects of Palm OS Cobalt react, by default, to the various security settings:
Lockout Settings
SecSvcsGetDeviceLockout()
and SecSvcsSetDeviceLockout()
get and set the device's current lockout scheme. These functions are intended to be used by security applications that control the locking and unlocking of the device.
Use SecSvcsEncodeLockoutTime()
to encode the lockout parameters into a 32-bit value for use by SecSvcsSetDeviceLockout()
. As you might expect, you use SecSvcsDecodeLockoutTime()
to decode the lockout parameters from the 32-bit value returned from SecSvcsGetDeviceLockout()
.
Low-level modules that control whether or not the device is locked can use SecSvcsIsDeviceLocked()
. This function returns a boolean value: true
if the device is locked, false
if it is not.
Security Policies
Palm OS Cobalt licensees can set security policies for the various managers in their Palm OS system. Specifically, the following can be gated by separate security policies:
- Processes
- FEPs and locale modules
- The BSP key
- IOS drivers
- Sync clients
- Authentication Manager plug-ins
- CPM providers
SecSvcsGetDevicePolicies()
obtains the security policies defined for the device. It checks, in order, the ROM token area of the device, any ROM-based PDB files, and then any RAM-based PDB files. The PDB files and ROM tokens that this function checks are of a specific format. The format used by the policies is a non-terminated list of 20 byte IDs of certificates against which code must be checked for signed status. These IDs can be directly used with the Signature Verification Library using the SignVerifySignatureByID()
function. The Signature Verification Library is responsible for checking that code is signed appropriately before it is used.
Application developers can build a secure application by including a set of security policies in the PRC of the application (using PRCCert and PRCSign; see the book Working with Resource Tools) and then signing the PRC digitally. Before the Program Loader launches the application, it will make sure the application's integrity has not been compromised. When such an application is running, the Program Loader also makes sure that any shared library loaded into that application's process meets the requirement of the security policies carried by the application's PRC.
Signature Verification Library
The Signature Verification Library provides an interface through which applications can access signature and certificate resources ('sign'
and 'cert'
, respectively) in a PRC. With the exposed APIs you can:
- Get the number of signatures or certificates
- Get a certificate or signature by index or certificate ID
- Get an overlay validation certificate list
- Get a shared library validation certificate list
- Validate a signature
Reference documentation for the APIs exposed by this library can be found in Chapter 15, "Signature Verification Library."
Signature Verification
Signature verification is perhaps the most common Signature Verification Library operation. Listing 1.17 contains a code excerpt that shows how you can verify a signature.
Listing 1.17 Validating a signature
DatabaseID dbID; DmOpenRef dbP; status_t err; dbID = DmFindDatabase("Test_SignedCode", 'scta'); dbP = DmOpenDBNoOverlay(dbID, dmModeReadOnly); err = SignVerifySignatureByIndex(dbP, 0); if (err) DbgPrintf("Error in Signature\n"); else DbgPrintf("Signature validation succeeded"); DmCloseDatabase(dbP);
To verify a PRC's digital signature, its sign resource block must be interpreted. Each signature block in a sign resource contains a reference to its verifying certificate. This reference is the certificate's ID (the SHA1 digest of the public key).
The code verifying the signature can get the RSA verify key (the public key) from the Certificate Manager by referencing the certificate ID. If the certificate is not found in the certificate store, search for a matching certificate in the PRC file. If a certificate in the PRC file matches the ID, import it into the certificate store prior to using it for verification. The Certificate Manager verifies the integrity, validity, and suitability of the certificate during the process of importing it into the certificate store.
NOTE: It is possible to import expired certificates into the certificate store for purposes of verifying digital signatures. If a certificate has expired, the signature verification code is responsible for verifying that the PRC file was signed prior to the expiration date of the certificate.
The Authentication Manager, running in the System process, makes use of the signature verification shared library to provide signed code authentication.
Signing Code
Applications are signed when code integrity is a concern. Depending on the device, some code—such as a patch—may need to be signed. Or, a secure database may be configured in such a way as to only allow access by a particular group of signed applications.
What can be Signed
Signed code in Palm OS Cobalt is used to validate the authenticity of a program resource. There are several types of resources that could be signed in Palm OS Cobalt. Applications, system patches, shared libraries, system components, system drivers, and the like. All of these resources are packaged as PRC files and then loaded onto the device.
Unlike the desktop world where digital signatures are used to indicate the author of a piece of software, Palm OS uses digital signatures to represent endorsements. For example, an enterprise can use its own certificate to sign Palm OS applications that it has tested for usefulness and interoperability with other core enterprise applications. This makes it easy for employees to know they are getting good applications.
An application can have multiple endorsements. For example, an application created by a major software vendor could be signed by the vendor as well as by the enterprise. It is also possible for user groups to endorse software that they have reviewed favorably. These endorsements help the user decided whether to install a piece of downloaded software.
Overlay and Shared Library Validation Certificate List
A signed application can define two lists of certificate IDs: an overlay list, and a shared library list. These lists authenticate the integrity of overlays and shared libraries. They are defined at the time that the first signature is applied to the application, and cannot be changed without invalidating that signature.
Signing Algorithm
The algorithm for digital signatures in Palm OS Cobalt is the RSA/SHA1 signing algorithm. This means that Palm OS Cobalt uses RSA private keys to sign a SHA1 digest, and RSA public keys to verify the signature. To be compatible with the widest range of cryptographic hardware vendors, the padding format is PKCS #1 Block Type 1 from version 1.5 of the PKCS #1 specification.
The signing keys can be either self-issued or assigned by a Certificate Authority (CA). These are RSA keys with 1024 bits and can have either double-prime or triple-prime modulus. The exponent can be 3 or 216+1. Verifying a signature produced with exponent 3 is about three times faster than with the larger exponent.
Signing Tools
There are two tools that are required to do code-signing of PRC's: PRCCert and PRCSign. You use PRCCert to create your own RSA key pairs and digital certificates. You may create self-signed certificates for testing, or certificates that are signed by other private keys. PRCCert creates RSA public/private key pairs at 1024-bit length in PEM format. PRCCert also generates a public certificate file in DER format.
The output files from PRCCert are used as input files to PRCSign.
PRCSign is a tool that you use to digitally sign your applications or to embed digital signature certificates in your applications. PRCSign creates a digital signature for a particular PRC using an asymmetric key cipher, storing the signature into the PRC as a resource of type 'sign'
. The signature can be verified as authentic by using your public key to decipher the signature resource. Each application has at most one 'sign'
resource with a resource ID of 1000.
PRCSign takes your private key and signs a SHA1 hash of all of the static (unchanging) resources in the PRC along the signature attributes. PRCSign then adds the resulting output as the 'sign'
resource to your application PRC file. PRCSign also takes a digital signature and adds it to the PRC as a 'cert'
resource in such a way that the Security Manager can retrieve it for application certification.
PRCSign supports keys in regular files, and smart cards.
The tools can handle any kind of X.509 certificate as long as the certificate's key usage constraints include the ability to sign data. Certificates that can only sign certificates, and certificates that can only be used for encryption, are not acceptable. A certificate issued for signing e-mail, however, is acceptable even though it is not marked as being able to sign code. This allows for the widest possible range of certificate-issuing systems or infrastructures to be used to sign Palm OS software. It is up to the Certificate Manager acting on behalf of the user or the administrator to further restrict the suitability of certificates. The tools do not enforce this.
For detailed instructions on using PRCCert and PRCSign, see Palm OS Resource Tools Guide.
Signed Code and Shared Libraries
Palm OS Cobalt can guarantee the integrity of the shared libraries that are loaded by an application while it runs. In order to do this the application has associated with it a list of certificates (the "shared library certificate ID list") that authenticate the shared libraries that are loaded at runtime. The operating system then uses this list of certificates to authenticate any shared library that is loaded by the system for the application. If a shared library has a valid signature (one that can be verified by one of the certificates in the list), then it is loaded. Otherwise the load is cancelled, and the application stops running.
The feature in PRCSign that allows the setting of this list is the –scert
parameter. Multiple –scert
parameters can be supplied. Note that the list of shared library certificates must be set when the PRC is being signed; it cannot be modified after the first signature is applied. Previous signatures will be invalidated if the list is modified; the Application Manager requires that all signatures be valid when it checks the integrity of the certificate list. If any signature is determined to be invalid, the application is stopped.
Shared Library Scenarios
Whether or not a given shared library is loaded depends on whether the application is signed, and whether the shared library's signature appears in the application's shared library certificate ID list.
Unsigned PRC
If the application isn't signed, the application is run and any needed shared libraries are loaded without any verification of signatures.
Signed PRC, Empty Shared Library Certificate ID List
If the application is signed but the shared library certificate ID list is empty, all signatures on the PRC must be valid, or the PRC execution is halted. Any needed shared libraries are loaded without signature verification.
Signed PRC, Shared Library Certificate ID List has Entries
If the application is signed and there are entries in the shared library certificate ID list, all signatures on the PRC must be valid. Any needed shared libraries must have a signature that can be validated by one of the certificates in the shared library certificate ID list.
Signed Code and Overlays
Overlays for signed PRC's must be authenticated before they are applied. A signed PRC may contain a list of overlay certificate IDs. This list contains the IDs of certificates that may be used to authenticate overlays.
The list of overlay certificate IDs is included in the signing hash for each signature, so if a user or application changes this list all previous signatures are invalidated.
Prior to applying an overlay to a base PRC, the Locale Manager must verify that the overlay can be authenticated by one of the certificates in the "overlay certificate ID list" (from the 'sign'
resource).
Overlay Scenarios
Whether or not a given overlay is applied depends on whether the application is signed, and whether the overlay's signature appears in the application's overlay certificate ID list.
Unsigned PRC
If the application isn't signed, the application is run and any overlays are applied without any verification of signatures.
Note that signed overlays may be applied to unsigned PRC files because the operating system doesn't check the signature of the overlay.
Signed PRC, Empty Overlay Certificate ID List
If the application is signed, but its 'sign'
resource doesn't contain any overlay certificate IDs, any overlay is allowed.
Signed PRC, Overlay Certificate ID List has Entries
If the application is signed and its 'sign' resource contains an overlay certificate ID list, an overlay must be verified with the certificate(s) in the list before it will be applied.
Securing Databases
The Data Manager in Palm OS Cobalt supports secure schema databases. These databases have an Authorization Manager rule set associated with them that is evaluated when the database is opened or removed.
The Data Manager defines several different actions that the application can define access rules for:
There are two functions for creating secure databases: DbCreateSecureDatabase()
, and DbCreateSecureDatabaseFromImage()
.
When you first create a secure database, access is only granted to the creator of that database, and the creator is only allowed to modify the database's access-control rule set. Accordingly, an application that creates a secure database must then set rules for those actions for which it wants to grant access.
A rule specifies a series of tokens that must be satisfied prior to access being granted.
The following code shows how to create a secure database that requires a user password for any action performed on that database.
Listing 1.18 Creating a secure database
AzmRuleSetType dbRuleSet; AmTokenType usertoken; UInt32 action = dbActionRead | dbActionWrite | dbActionDelete | dbActionBackup | dbActionRestore | dbActionEditSchema; status_t err; // Create DB – get AZM ruleset reference err = DbCreateSecureDatabase("My DB", 'crea', 'type', numSchemas, schemaList, &dbRuleSet, &dbID); // Set user password required for ALL actions err = AmGetTokenBySystemId(&usertoken, SysUserToken); err = AzmAddRule(dbRuleSet, action , "%t", usertoken);
Synchronization and Backup of Secure Databases
"Sync bypass" must enabled for a specific action in order for an authenticated HotSync agent to be able to complete that action successfully. You use AzmGetSyncBypass()
and AzmSetSyncBypass()
to get and set the sync bypass flag in an existing rule-set container for a specific action. Sync bypass may be flagged for any action; it tells the sync server to grant access to registered sync clients. Sync bypass is used in both synchronization and backup operations.
HotSync and Secure Databases
If sync bypass has been granted for the appropriate actions, any HotSync client that has registered with the HotSync server may access a secure database on the device, and any HotSync conduit may access a secure database from the desktop. Any application can register with the HotSync Manager (although user confirmation may be required).
Be aware that synchronizing a secure database exposes it on the desktop and on the device. The secure database is synchronized "in the clear," meaning that the security of the link between the desktop and the device is up to the HotSync client/server setup. For truly secure data synchronization is not recommended.
Backing up Secure Databases
The database itself is always backed up encrypted, and the encryption key is backed up as well. In order to allow the database to be backed up, the backup action must be set in the bypass vector of the rule set. As well, all actions must be protected by the user token: only data that is protected solely by the user password can be backed up. This is enforced by the Data Manager.
On the desktop the backup image is protected by the user password. That means that the security of the backup depends on strength of the password. However, because it is protected by a user password on both the device and the desktop, the data is no more at risk on the desktop than it is on the device.
1. A given CPM import/export format, such as XML, is only supported if the provider supports it. IMPORT_EXPORT_TYPE_RAW is always supported.