S/MIME Freeware Library Cryptographic Token Interface Application Programming Interface Version 0.1 27 March 1998 Produced By: J.G. Van Dyke & Associates, Inc. 6550 Rock Spring Dr, Suite 360 Bethesda, MD 20817 http://www.jgvandyke.com 1 SFL CTI OVERVIEW 3 1.1 GENERAL SFL-CTI INTERACTION CONCEPT 3 1.2 GENERAL CTI REQUIREMENTS 4 2 THE CRYPTOGRAPHIC TOKEN INTERFACE API 4 2.1 INITIALIZATION AND THE CTI 4 2.2 SMTI API 4 2.2.1 SMTI_Sign 4 2.2.2 SMTI_Verify 5 2.2.3 SMTI_Encrypt 5 2.2.4 SMTI_GenerateEMEK 6 2.2.5 SMTI_ExtractMEK 6 2.2.6 SMTI_Decrypt 6 2.2.7 SMTI_DigestData 7 2.2.8 SMTI_Random 7 2.2.9 SMTI_Lock 7 2.2.10 SMTI_Unlock 7 2.2.11 BTI Functions 7 2.3 GENERAL SMTI CONVENTIONS 8 2.3.1 Unimplemented SMTI Functions 8 2.3.2 ASN.1 Encoding and Decoding by the CTI 8 2.3.3 Insufficient Input from the SFL? 8 2.3.4 Return Value from SMTI Functions 8 2.3.5 Error Handling and Other Classes 8 2.3.6 Key, Password, and Certificate Storage 9 2.3.7 Typical SFL Usage 9 2.3.8 Setting Default AlgIDs 9 3 CTI INHERITANCE 9 4 EXAMPLES 10 5 CONTACT INFORMATION 10 1 SFL CTI Overview The S/MIME Freeware Library (SFL) uses a lower level API called the Cryptographic Token Interface (CTI) to abstract crypto services from the SFL. The intent is to allow the SFL to work through the CTI API to access crypto services in an abstract manner so other CTI Libraries can be developed and linked into the SFL. The purpose of this document is to describe the SFL CTI. Please refer to the SFL Software Design Document and the SFL API for information regarding the rest of the architecture. Disclaimer: The SFL and the CTI (and associated libraries) are under constant development and testing. As short-comings in design are discovered, they are corrected and occasionally this results in a change to the CTI. 1.1 General SFL-CTI Interaction Concept The application must be aware of what CTI Libraries it wants to use. If, for example, an application wants to use an RSA suite of algorithms (rsaEncryption, RC2, and MD5) and the S/MIME v3 MUST implement suite of algorithms (DH, DSA, 3DES, SHA1) and the application has CTI libraries that implement these algorithms, then it must explicitly initialize both the RSA CTI Library and the S/MIME v3 MUST algorithm CTI Library. When the application calls an initialization function for a given CTI Library, the application must provide whatever parameters that CTI Library requires. These parameters may differ from one CTI library to another. For example, a CTI library that stores its private keys in an encrypted file might need a password to access that file while a CTI library that stores its private keys on hardware (such as a PCMCIA card) might need whatever details are necessary to access that data on the hardware. This is the first general concept of the CTI library: Each CTI must provide a documented way for the using application to initialize the CTI library. Initialization of a CTI library by the application does not include any SFL specific processing. While the initialization procedure may use some SFL low level component classes such as the CSM_Buffer class, this is not mandatory. The primary purpose of initializing a CTI library is to prepare a list of crypto service instances (CSM_CSInst class) that the application and SFL will use. The CTI library creates and passes these instances back to the application via the initialization process. For example, if the CTI Library stores its private keys and associated certificates in a file, then the application might initialize the CTI library by providing the name of the file and a password to access the file. Then the CTI might unprotect the file with the password and access its contents. The CTI might scan through the contents of the file and create an instance for each user certificate in the file. Therefore, if a given file had a user’s DH certificate and a user’s DSA certificate, then the CTI would create an instance for both. These instances are passed back to the application. Then the application can use them to indicate what the SFL should use to access the crypto services it needs. For example, if a CTI initialized and created 6 instances containing DSA certs and keys and then the application wanted to create a SignedData, the application would identify which instance(s) it wants to sign the SignedData. This is the second general concept of the CTI library: Each CTI must create and fill crypto service instances (CSM_CSInst classes) for the application and the SFL to use. After the instances are created by the CTI, passed back to the application and the application manipulates them as necessary (selecting which ones to use), they are passed into the SFL for use. Each instance contains a pointer to the CTI class that has been cast as a CSM_TokenInterface which is an abstract base class. Therefore, the SFL uses this pointer to blindly call SMTI (S/MIME Token Interface) functions. In other words, the SFL might call SMTI_DigestData when it needs a digest of some data. It uses the token interface pointer from the application specified instance to call SMTI_DigestData not knowing or caring which actual CTI is used. This is the third general concept of the CTI library: The crypto service instances that were created by the CTI initialization process each contain a pointer to the CTI class cast as a CSM_TokenInterface abstract base class. These instances (specifically, the pointers contained therein) are used by the SFL to call SMTI functions in the CTI library in an abstract way. 1.2 General CTI Requirements From the general concepts described above, we derive two general requirements for a developer to create a CTI library that can be used with the SFL. First, the CTI must provide a way for the application to initialize the CTI. The end result of this initialization is the creation of crypto service instances that each contain a pointer to the CTI class cast as a CSM_TokenInterface. Second, the CTI must implement each SMTI pure virtual function listed in the CSM_TokenInterface class. These SMTI function implementations must accept the specified parameters and must return the appropriate responses in order for the SFL to successfully use them. 2 The Cryptographic Token Interface API 2.1 Initialization and the CTI The initialization process is basically left up to the developer for the most part. However, the developer needs to remember a couple of basic requirements/guidelines. ? The application’s initialization of the SFL basically consists of constructing the CSMIME class. One member of this class (m_pCSInsts) is a list (see CSM_List) of instances (see CSM_CSInst). This member is the CTI initialization’s primary responsibility to create. ? The application may initialize more than one CTI, therefore, when the CSMIME class is provided to the CTI, the CTI must be capable of both creating a new list of instances and adding instances to an already existing list. ? During initialization, the CTI should instantiate the CTI class (the class that is derived from CSM_BaseTokenInterface which is derived from CSM_TokenInterface), cast the pointer to the class as a CSM_TokenInterface pointer, and set the pointer into the instance using CSM_CSInst::SetTokenInterface. ? In general, each instance will represent a user certificate and associated private key. Conceivably, there are some situations where a private key may not be available or where some other variant may occur, however, most instances represent a user certificate and private key. ? The CTI should document the initialization process so other applications can correctly initialize them. The initialization process may be one call (like FreeInit or FortInit) or may be a sequence of calls. 2.2 SMTI API 2.2.1 SMTI_Sign virtual SM_RET_VAL SMTI_Sign(CSM_Buffer *pData, CSM_Buffer *pEncryptedDigest, CSM_Buffer *pDigest) =0; SMTI_Sign generates a digital signature. pData is an input parameter and will contain the data to be signed. Note that this is not the digest, but the actual data that should be digested. SMTI_Sign should digest the data (probably using SMTI_DigestData) and store the resulting digest value in pDigest. SMTI_Sign should encrypt the digest and store the result (digital signature) in pEncryptedDigest (which is also an output parameter). All parameters except pDigest must come into SMTI_Sign allocated. If pDigest is NULL, then the caller doesn’t want the digest value and SMTI_Sign should use a temporary local buffer to hold the digest. SMTI_Sign should allocate the storage inside the CSM_Buffer as appropriate. 2.2.2 SMTI_Verify virtual SM_RET_VAL SMTI_Verify( CSM_Buffer *pSignerPublicKey, CSM_Alg *pDigestAlg, CSM_Alg *pSignatureAlg, CSM_Buffer *pData, CSM_Buffer *pSignature) =0; SMTI_Verify verifies a digital signature. pSignerPublicKey is an input parameter and will contain an ASN.1 DER encoded integer representing the signer’s public key. This key should have been validated by the application. pDigestAlg is an input parameter and will contain the algorithm information (including OID and parameters) for the digest algorithm used to generate the digest that was encrypted to generate the signature. SMTI_Verify should check and use (if appropriate) the pDigestAlg. pSignatureAlg is an input parameter and will contain the algorithm information for the signature (digest encryption) algorithm used to generate the signature. SMTI_Verify should check and use (if appropriate) the pSignatureAlg. pData is an input parameter and will contain the original data that was digested in the process of generating the original signature (i.e. the data that was signed). SMTI_Verify should digest this data and use the resulting digest value to verify the signature contained in pSignature (another input parameter). SMTI_Verify should return SM_NO_ERROR if the signature verifies successfully. All parameters must come into SMTI_Verify allocated. 2.2.3 SMTI_Encrypt virtual SM_RET_VAL SMTI_Encrypt(CSM_Buffer *pData, CSM_Buffer *pParameters, CSM_Buffer *pEncryptedData, CSM_Buffer *pMEK) =0; SMTI_Encrypt does the symmetric encryption of the provided data. pData is an input parameter and will contain the data to be encrypted. SMTI_Encrypt should do whatever is necessary to encrypt the data. The encrypted data should be returned in pEncryptedData. ASN.1 encoded parameters should be returned in pParameters. These parameters might include, for example, the IV generated for the encryption process. SMTI_Encrypt should return the message encryption key (MEK) in pMEK. If SMTI_Encrypt does not have access to the MEK (it is protected on a hardware device, for example), then it may return a special phrase that can be recognized by other SMTI functions in this CTI and handled appropriately. For example, the Fortezza hardware CTI that we are writing returns “FORTEZZA MEK: REGx” in pMEK where x indicates the register on the fortezza card that contains the MEK. All parameters must come into SMTI_Encrypt allocated. 2.2.4 SMTI_GenerateEMEK virtual SM_RET_VAL SMTI_GenerateEMEK( CSM_Buffer *pRecipient, CSM_Buffer *pParameters, CSM_Buffer *pMEK, CSM_Buffer *pEMEK, CSM_Buffer *pUKM) =0; SMTI_GenerateEMEK creates the encrypted MEK (EMEK) by using the KM algorithm to encrypt the MEK. pRecipient contains the public value of the recipient of the EMEK. This is only applicable if the KM algorithm needs it (RSA doesn’t). pMEK should contain the MEK generated by SMTI_Encrypt. If pMEK doesn’t contain the MEK, it should have a special phrase that can be handled by SMTI_GenerateEMEK so it can access the MEK to encrypt it (such as register on a PCMCIA card). SMTI_GenerateEMEK should encrypt the MEK using the originator’s private value (internal to the CTI) and the recipient’s public value (if applicable), process the EMEK as appropriate, and store the result in pEMEK. SMTI_GenerateEMEK should provide ASN.1 encoded parameters for the algorithm in pParameters. The caller may provide a UKM in pUKM. In general, if the supported KM algorithm uses UKMs, then it should use the one provided in pUKM. If pUKM is empty, then SMTI_GenerateEMEK should generate a UKM, use it, and return it in pUKM. All parameters must come into SMTI_GenerateEMEK allocated except for pRecipient. However, if your KM algorithm needs the recipient’s public value and pRecipient is NULL, SMTI_GenerateEMEK should return an error. 2.2.5 SMTI_ExtractMEK virtual SM_RET_VAL SMTI_ExtractMEK(CSM_Buffer *pOriginator, CSM_Buffer *pParameters, CSM_Buffer *pEMEK, CSM_Buffer *pUKM, CSM_Buffer *pMEK) =0; SMTI_ExtractMEK decrypts the provided EMEK using the KM algorithm supported by this CTI. pOriginator is an input parameter and may contain the public value of the originator. pParameters is an input parameter and will contain the ASN.1 encoded parameters used to generate the EMEK. pEMEK is an input parameter and will contain the EMEK. pUKM is an input parameter and will contain the UKM, if applicable. SMTI_ExtractMEK should decrypt the EMEK and return the result in pMEK. If the MEK cannot be accessed (such as our Fortezza CTI), then SMTI_ExtractMEK should return a special phrase indicating this situation (as described above). All parameters must come into SMTI_GenerateEMEK allocated except for pOriginator and pUKM. pOriginator may be absent because some key encryption algorithms such as RSA do not require this data. In RSA’s case, the private value necessary to decrypt the EMEK should be available inside the CTI class. However, if you need the Originator or UKM and pOriginator or pUKM is NULL, SMTI_ExtractMEK should return an error. 2.2.6 SMTI_Decrypt virtual SM_RET_VAL SMTI_Decrypt(CSM_Buffer *pParameters, CSM_Buffer *pEncryptedData, CSM_Buffer *pMEK, CSM_Buffer *pData) =0; SMTI_Decrypt uses symmetric decryption to decrypt the provided encrypted data. pParameters is an input parameter and will contain the ASN.1 encoded parameters such as the IV (if applicable). pEncryptedData is an input parameter and will contain the encrypted data. pMEK is an input parameter and should contain the MEK. If the MEK could not be accessed by SMTI_ExtractMEK, the SMTI_ExtractMEK should have returned a special phrase in pMEK that the SFL would have passed into SMTI_Decrypt. SMTI_Decrypt should decrypt the data and put the result in pData. All parameters must come into SMTI_Decrypt allocated. 2.2.7 SMTI_DigestData virtual SM_RET_VAL SMTI_DigestData(CSM_Buffer *pData, CSM_Buffer *pDigest) =0; SMTI_DigestData digests the provided data and returns the result. pData is an input parameter and contains the data to be digested. pDigest is an output parameter and should contain the digest value calculated by SMTI_DigestData. All parameters must come into SMTI_DigestData allocated. 2.2.8 SMTI_Random virtual SM_RET_VAL SMTI_Random(CSM_Buffer *pSeed, CSM_Buffer *pRandom, SM_SIZE_T lLength) =0; SMTI_Random generates random data. pSeed is an input parameter and may be used by SMTI_Random to seed the random number generator. pRandom is an output parameter and should contain the random bytes produced by SMTI_Random. lLength is an input parameter and will indicate the number of random bytes requested. All parameters must come into SMTI_Random allocated or set. 2.2.9 SMTI_Lock virtual SM_RET_VAL SMTI_Lock(); If the cryptographic services you are wrapping with your CTI have access control issues, then you may put access control implementation in SMTI_Lock. For example, the Fortezza card could potentially be used by more than one application however there is session sensitive information on the card during the encryption/key encryption and key decryption/decryption sequence of calls. Therefore, the SMTI_Lock for the fortezza CTI locks the card. On the other hand, the Free CTI provided with the SFL does not have this access control issue so CSM_Free::SMTI_Lock simply returns SM_NO_ERROR. 2.2.10 SMTI_Unlock virtual SM_RET_VAL SMTI_Unlock(); This function is the opposite end of access control for SMTI_Lock. See the previous section. If your CTI does not support access control, simply return SM_NO_ERROR. 2.2.11 BTI Functions The Base Token Interface functions (prefixed with BTI) are already implemented in the CSM_BaseTokenInterface class. You do not need to implement these functions. However, your CTI class must be derived from CSM_BaseTokenInterface. CSM_BaseTokenInterface is derived from CSM_TokenInterface. 2.3 General SMTI Conventions 2.3.1 Unimplemented SMTI Functions If your CTI class does not fully implement one of the SMTI functions or a set of the SMTI functions, then those unimplemented functions should return an error code. If, for example, your CTI only does digital signature generation and verification, then the SMTI_Encrypt, SMTI_GenerateEMEK, SMTI_ExtractMEK, and SMTI_Decrypt functions should return errors. In this example, the application must be aware of what the CTIs it uses support and should not mark instances in such a manner that your CTI will be used for encryption or key encryption. Nevertheless, you need to return the error so the SFL can further handle the error without causing a crash. 2.3.2 ASN.1 Encoding and Decoding by the CTI The CTI will probably need to do some ASN.1 encoding and decoding as required. This occurs primarily with algorithm parameters. Because algorithm parameters are algorithm specific (e.g. DH parameters are different than KEA parameters), the SFL cannot encode or decode them and instead simply stuffs them into or takes them out of the ASN.1 objects it is working with. The SFL and the CTIs delivered with the SFL use SNACC (a free ASN.1 compiler) to do ASN.1 encoding and decoding. 2.3.3 Insufficient Input from the SFL? If you are implementing one of the SMTI functions for your CTI and find that you need some additional input that doesn’t appear to be available through the provided parameters, consider where that input should come from. If it is algorithm specific, then it probably shouldn’t come from the SFL and should instead be contained inside the algorithm specific parts of your CTI and should be initialized in some other means (by the CTI itself or the application). While the CTI API is being tested, it is possible that some changes may occur because we did not foresee requirements. If you believe that the CTI API needs to be modified for a non algorithm specific need or a need that cannot be met in any other way, please let us know. 2.3.4 Return Value from SMTI Functions Upon success, your SMTI functions should always return SM_NO_ERROR. Otherwise, you should return the error values you define. They should be non-negative and should not conflict with other error values that may be used in conjunction with your CTI (if possible). 2.3.5 Error Handling and Other Classes You may want to consider using the exception handling scheme used by the SFL and the other SFL CTIs. It is documented in the SFL API and defined in the sm_error.h file. You will need to use some other SFL classes including CSM_Buffer and CSM_Alg. They are documented in the SFL API and defined in the header files included with the SFL. 2.3.6 Key, Password, and Certificate Storage Your CTI may be faced with data storage issues such as how/where do I store the private keys at run time and permanently? How/where do I store and protect a password or pin? How/where do I store X.509 certificates or other such data? These issues are primarily up to the CTI developer since they do not affect the SFL directly. Security is a concern of course and the current Free CTI that comes with the SFL does not adequately meet these issues (as of March, 1998). While perfect security is nearly impossible, it is important to have MEKs, Private Keys, and Passwords/Pins “in the clear” for as little time as possible…especially in “production” CTI libraries vs. “test” CTI libraries. Again, storage and retrieval of these items is completely up to the CTI library. We intend to use PKCS#8 to protect private keys and we use the SFL MAB_AB_def class as a storage mechanism for the PKCS#8 blobs and the encoded certificates. Alternately, the CTI developer could use the Certificate Management library to store certificates. 2.3.7 Typical SFL Usage SMTI_Sign is called by the Sign members of CSM_EncryptMsg/CSM_EncryptData. SMTI_DigestData may be called by the Sign members of CSM_EncryptMsg/CSM_EncryptData. SMTI_Verify is called by the Verify members of CSM_VerifyMsg/CSM_VerifyData. SMTI_Encrypt is called once by CSM_EncryptData::Encrypt. SMTI_GenerateEMEK is called one or more times (depending on the number of recipients) by CSM_EncryptData::Encrypt. SMTI_Decrypt is called once by CSM_DecryptData::Decrypt. SMTI_ExtractMEK is called one or more times (depending on the number of recipients to process) by CSM_DecryptData::Decrypt. 2.3.8 Setting Default AlgIDs During the initialization process, the CTI should register the algorithms it supports by calling BTISetAlgIDs and providing lists of the algorithms it supports for digesting, digest encryption, key encryption, and content encryption. Then it should mark the “preferred” algorithms by calling BTISetPreferredCSInstAlgs. 3 CTI Inheritance The CTI was designed so that you can (if you want) inherit functionality from another CTI. For example, if you had a CTI that supported SHA1 digesting, you could derive your CTI from that CTI. In that case, it would be unnecessary to derive your CTI from CSM_BaseTokenInterface as well since the CTI you derived yourself from would have been derived from CSM_BaseTokenInterface. Then, in SMTI_DigestData, you could explicitly call the SMTI_DigestData in the CTI you derived yourself from. You can use this mechanism to add functionality too. If, in the above example, your CTI supported MD5, then your SMTI_DigestData could check the preferred digest algorithm ID. If it was MD5, you would do the digest in your own SMTI_DigestData. If it wasn’t MD5, then you would simply call the SMTI_DigestData in the CTI you derived yourself from and that would either handle the SHA1 digesting or return an error if the preferred digest algorithm is unsupported. 4 Examples The reader is encouraged to study CTI libraries that come with the SFL to see an example of how the CTI is implemented and used. 5 Contact Information John Pawling J.G. Van Dyke and Associates, Inc. (301) 953-3600 http://www.jgvandyke.com 1 3