Manual

User Manual: manual manual pdf - FTP File Search (13/20)

Open the PDF directly: View PDF PDF.
Page Count: 342 [warning: Documents this large are best viewed by clicking the View PDF Link!]

Security Toolkit
Version 3.3.2
Copyright Peter Gutmann 1992-2008
July 2008
You may print a reasonable number of copies of this work for personal use in conjunction with
cryptlib software development provided that no fee is charged.
cryptlib Overview i
INTRODUCTION 1
cryptlib Overview 1
cryptlib features 2
Architecture 2
S/MIME 3
PGP/OpenPGP 3
Secure Sessions 4
Plug-and-play PKI 4
Certificate Management 4
CA Operations 6
Crypto Devices and Smart Card Support 8
Certificate Store Interface 8
User Interface 9
Security Features 9
Embedded Systems 10
Performance 10
Cryptographic Random Number Management 11
Programming Interface 11
Documentation 11
Algorithm Support 12
Standards Compliance 12
Y2K Compliance 13
Configuration Options 13
cryptlib Applications 13
Encryption Code Example 14
Secure Session Code Example 14
Certificate Management Code Example 15
Document conventions 15
Recommended Reading 15
INSTALLATION 17
AMX 17
BeOS 17
ChorusOS 17
DOS 17
DOS32 17
eCOS 17
FreeRTOS/OpenRTOS 18
µC/OS-II 18
Embedded Linux 18
µITRON 18
Macintosh OS X 18
MVS 18
OS2 19
PalmOS 19
QNX Neutrino 19
RTEMS 19
Tandem 19
ThreadX 20
uClinux 20
Unix 20
VDK 21
VM/CMS 21
VxWorks 21
Windows 3.x 21
Windows 95/98/ME and Windows NT/2000/XP/Vista 22
Windows CE / Pocket PC / SmartPhone 22
Xilinx XMK 23
Other Systems 23
Introduction
ii
Key Database Setup 24
Configuration Issues 25
Customised and Cut-down cryptlib Versions 25
Debug vs. Release Versions of cryptlib 25
cryptlib Version Information 26
Support for Vendor-specific Algorithms 26
CRYPTLIB BASICS 27
Programming Interfaces 28
High-level Interface 28
Mid-level Interface 28
Low-level Interface 28
Objects and Interfaces 29
Objects and Attributes 29
Interfacing with cryptlib 30
Initialisation 30
C / C++ 31
C# / .NET 31
Delphi 32
Java 32
Python 33
Tcl 33
Visual Basic 33
Return Codes 33
Working with Object Attributes 34
Attribute Types 36
Attribute Lists and Attribute Groups 38
Attribute Cursor Management 39
Object Security 42
Role-based Access Control 44
Managing User Roles 44
Creating and Destroying Users and Roles 45
Miscellaneous Issues 46
Multi-threaded cryptlib Operation 46
Safeguarding Cryptographic Operations 47
Interaction with External Events 48
DATA ENVELOPING 49
Creating/Destroying Envelopes 49
The Data Enveloping Process 50
Data Size Considerations 52
Basic Data Enveloping 53
Compressed Data Enveloping 55
Password-based Encryption Enveloping 55
Conventional Encryption Enveloping 57
Authenticated Enveloping 58
De-enveloping Mixed Data 59
De-enveloping with a Large Envelope Buffer 60
Obtaining Envelope Security Parameters 61
Enveloping Large Data Quantities 61
Alternative Processing Techniques 63
Enveloping with Many Enveloping Attributes 64
ADVANCED ENVELOPING 66
Public-Key Encrypted Enveloping 66
Digitally Signed Enveloping 70
Enveloping with Multiple Attributes 72
cryptlib Overview iii
Processing Multiple De-enveloping Attributes 73
Nested Envelopes 75
S/MIME 77
S/MIME Enveloping 77
Encrypted Enveloping 78
Digitally Signed Enveloping 80
Detached Signatures 81
Alternative Detached Signature Processing 82
Extra Signature Information 83
Timestamping 84
PGP 86
PGP Enveloping 86
Encrypted Enveloping 86
Digitally Signed Enveloping 88
Detached Signatures 89
FROM ENVELOPES TO EMAIL 91
S/MIME email 91
Data 91
Signed Data 91
Detached Signature 91
Encrypted Data 92
Nested Content 92
PGP email 92
Implementing S/MIME and PGP email using cryptlib 93
c-client/IMAP4 93
Eudora 94
MAPI 94
Windows 95/98/ME and NT/2000/XP/Vista Shell 94
SECURE SESSIONS 96
Creating/Destroying Session Objects 96
Client vs. Server Sessions 98
Server Names/URLs 98
Server Private Keys 99
Establishing a Session 100
Persistent Connections 100
SSH Sessions 101
SSH Client Sessions 101
SSH Server Sessions 102
SSH Channels 104
SSH Subsystems 105
SSH Port Forwarding 106
SSH Multiple Channels 107
SSL/TLS Sessions 108
SSL/TLS Client Sessions 109
SSL/TLS with Shared Keys 109
SSL/TLS with Client Certificates 110
SSL/TLS Server Sessions 111
SSL/TLS Servers with Shared Keys 111
SSL/TLS Servers with Client Certificates 112
Request/Response Protocol Sessions 113
RTCS Server Sessions 113
OCSP Server Sessions 113
TSP Server Sessions 114
Obtaining Session Status Information 115
Introduction
iv
Obtaining Session Security Parameters 115
Authenticating the Host with Key Fingerprints 115
Authenticating the Host or Client using Certificates 115
Authenticating the Client via Port and Address 116
Exchanging Data 116
Network Issues 118
Secure Sessions with Proxies 118
Network Timeouts 118
Managing your Own Network Connections and I/O 119
KEY GENERATION AND STORAGE 122
Key Generation 122
Generating a Key Pair into an Encryption Context 122
Keyset Types 123
Creating/Destroying Keyset Objects 124
File Keysets 125
HTTP Keysets 127
Database Keysets 127
LDAP Keysets 129
Reading a Key from a Keyset 131
Obtaining a Key for a User 132
General Keyset Queries 134
Handling Multiple Certificates with the Same Name 136
Key Group Management 136
Writing a Key to a Keyset 137
Changing a Private Key Password 138
Deleting a Key 139
CERTIFICATES AND CERTIFICATE MANAGEMENT 140
High-level vs. Low-level Certificate Operations 140
Plug-and-play PKI 140
Mid-level Certificate Management 140
Low-level Certificate Management 140
Certificates and Keys 141
Using Separate Signature and Encryption Certificates 141
Plug-and-play PKI 142
Simple Certificate Creation 143
The Certification Process 145
Obtaining Certificates using CMP 148
CMP Certificate Requests 149
CMP Operation Types 150
CMP Sessions 151
Obtaining Certificates using SCEP 153
SCEP Certificate Requests 153
SCEP Sessions 153
Certificate Status Checking using RTCS 155
Basic RTCS Queries 155
Creating an RTCS Request 156
Communicating with an RTCS Responder 157
Advanced RTCS Queries 158
Certificate Revocation Checking using OCSP 159
Creating an OCSP Request 159
Communicating with an OCSP Responder 159
Advanced OCSP Queries 160
MANAGING A CERTIFICATION AUTHORITY 162
cryptlib Overview v
Creating the Top-level (Root) CA Key 162
Initialising PKI User Information 164
Other PKI User Information 165
PKI User IDs 166
Managing a CA using CMP or SCEP 167
Making Certificates Available Online 168
Managing a CA Directly 170
Recording Incoming Requests 170
Retrieving Stored Requests 170
CA Management Operations 171
Issuing and revoking a Certificate 172
Issuing a CRL 172
Expiring Certificates 172
Recovering after a Restart 172
ENCRYPTION AND DECRYPTION 174
Creating/Destroying Encryption Contexts 174
Generating a Key into an Encryption Context 175
Deriving a Key into an Encryption Context 176
Loading a Key into an Encryption Context 177
Working with Initialisation Vectors 177
Loading Public/Private Keys 178
Loading Multibyte Integers 178
Querying Encryption Contexts 180
Using Encryption Contexts to Process Data 180
Conventional Encryption 181
Public-key Encryption 182
Hashing 182
EXCHANGING KEYS 184
Exporting a Key 184
Exporting using Conventional Encryption 185
Importing a Key 186
Importing using Conventional Encryption 186
Querying an Exported Key Object 187
Extended Key Export/Import 187
Key Agreement 188
SIGNING DATA 190
Querying a Signature Object 191
Extended Signature Creation/Checking 191
CERTIFICATES IN DETAIL 194
Overview of Certificates 194
Certificates and Standards Compliance 194
Certificate Compliance Level Checking 195
Creating/Destroying Certificate Objects 197
Obtaining a Certificate 197
Certificate Structures 198
Attribute Certificate Structure 198
Certificate Structure 200
Certification Request Structure 201
CRL Structure 202
Certificate Attributes 203
Basic Certificate Management 203
Introduction
vi
Certificate Identification Information 205
DN Structure for Business Use 206
DN Structure for Private Use 207
DN Structure for Use with a Web Server 207
Other DN Structures 207
Working with Distinguished Names 207
Creating Customised DNs 208
Extended Certificate Identification Information 210
Working with GeneralName Components 211
Certificate Fingerprints 212
Importing/Exporting Certificates 212
Signing/Verifying Certificates 214
Certificate Chains 216
Working with Certificate Chains 217
Signing Certificate Chains 217
Checking Certificate Chains 218
Exporting Certificate Chains 219
Certificate Revocation using CRLs 220
Working with CRLs 220
Creating CRLs 220
Advanced CRL Creation 221
Checking Certificates against CRLs 222
Automated CRL Checking 222
Certificate Trust Management 223
Controlling Certificate Usage 223
Implicitly Trusted Certificates 223
Working with Trust Settings 224
CERTIFICATE EXTENSIONS 226
Extension Structure 226
Working with Extension Attributes 226
Composite Extension Attributes 227
X.509 Extensions 228
Alternative Names 228
Basic Constraints 228
Certificate Policies, Policy Mappings, Policy Constraints, and Policy Inhibiting 229
CRL Distribution Points/Freshest CRL and Subject/Authority Information Access 230
Directory Attributes 231
Key Usage, Extended Key Usage, and Netscape certificate type 231
Name Constraints 234
Private Key Usage Period 235
Subject and Authority Key Identifiers 235
CRL Extensions 235
CRL Reasons, CRL Numbers, Delta CRL Indicators 235
Hold Instruction Code 237
Invalidity Date 237
Issuing Distribution Point and Certificate Issuer 237
Digital Signature Legislation Extensions 238
Certificate Generation Date 238
Other Restrictions 238
Reliance Limit 238
Signature Delegation 239
Qualified Certificate Extensions 239
Biometric Info 239
QC Statements 239
SET Extensions 240
SET Card Required and Merchant Data 240
cryptlib Overview vii
SET Certificate Type, Hashed Root Key, and Tunnelling 240
Application-specific Extensions 241
OCSP Extensions 241
Vendor-specific Extensions 241
Netscape Certificate Extensions 242
Thawte Certificate Extensions 242
Generic Extensions 242
OTHER CERTIFICATE OBJECT EXTENSIONS 244
CMS/SMIME Attributes 244
Content Type 244
Countersignature 245
Message Digest 245
Signing Description 245
Signing Time 245
Extended CMS/SMIME Attributes 245
AuthentiCode Attributes 246
Content Hints 247
DOMSEC Attributes 247
Mail List Expansion History 247
Nonce 248
Receipt Request 248
SCEP Attributes 248
Security Label, Equivalent Label 249
Signature Policy 250
S/MIME Capabilities 251
Signing Certificate 251
OCSP Attributes 252
CRYPTLIB USER INTERFACE COMPONENTS 253
Displaying Certificates 253
Key/Certificate Generation 253
ENCRYPTION DEVICES AND MODULES 256
Creating/Destroying Device Objects 256
Activating and Controlling Cryptographic Devices 257
Device Initialisation 257
User Authentication 258
Device Zeroisation 259
Working with Device Objects 259
Key Storage in Crypto Devices 260
Querying Device Information 260
Considerations when Working with Devices 261
Fortezza Cards 262
PKCS #11 Devices 262
Installing New PKCS #11 Modules 262
Accessing PKCS #11 Devices 263
CryptoAPI 263
MISCELLANEOUS TOPICS 265
Querying cryptlib’s Capabilities 265
Working with Configuration Options 265
Querying/Setting Configuration Options 268
Saving Configuration Options 269
Obtaining Information About Cryptlib 269
Random Numbers 270
Gathering Random Information 270
Introduction
viii
Obtaining Random Numbers 271
Working with Newer Versions of cryptlib 271
ERROR HANDLING 273
Extended Error Reporting 275
EMBEDDED SYSTEMS 278
Embedded OS Types 278
AMX 278
ChorusOS 279
DOS 279
eCOS 279
µC/OS-II 279
Embedded Linux 279
µITRON 279
PalmOS 280
QNX Neutrino 280
RTEMS 280
uClinux 280
Windows CE 280
VxWorks 280
Xilinx XMK 281
Embedded cryptlib Configuration Options 281
Debugging with Embedded cryptlib 283
Porting to Devices without a Filesystem 283
Porting to Devices without Dynamic Memory Allocation 283
Memory Allocation Strategy 284
cryptlib Memory Usage 284
Tracking Memory Usage 284
Porting to Devices without Randomness/Entropy Sources 285
DATABASE AND NETWORKING PLUGINS 286
The Database Plugin Interface 286
Database Plugin Functions 287
The Network Plugin Interface 290
Network Plugin Functions 290
The Crypto Plugin Interface 291
ALGORITHMS AND STANDARDS CONFORMANCE 293
AES 293
Blowfish 293
CAST-128 293
DES 294
Triple DES 294
Diffie-Hellman 295
DSA 295
Elgamal 295
HMAC-MD5 296
HMAC-SHA1 296
HMAC-RIPEMD-160 296
IDEA 296
MD2 297
MD4 297
MD5 298
cryptlib Overview ix
RC2 298
RC4 298
RC5 299
RIPEMD-160 299
RSA 299
SHA 299
SHA2 300
Skipjack 300
DATA TYPES AND CONSTANTS 301
CRYPT_ALGO_TYPE 301
CRYPT_ATTRIBUTE_TYPE 302
CRYPT_CERTFORMAT_TYPE 302
CRYPT_CERTTYPE_TYPE 303
CRYPT_DEVICE_TYPE 303
CRYPT_FORMAT_TYPE 303
CRYPT_KEYID_TYPE 304
CRYPT_KEYOPT_TYPE 304
CRYPT_KEYSET_TYPE 304
CRYPT_MODE_TYPE 305
CRYPT_OBJECT_TYPE 305
CRYPT_SESSION_TYPE 305
Data Size Constants 306
Miscellaneous Constants 306
DATA STRUCTURES 308
CRYPT_OBJECT_INFO Structure 308
CRYPT_PKCINFO_xxx Structures 308
CRYPT_QUERY_INFO Structure 309
FUNCTION REFERENCE 310
cryptAddCertExtension 310
cryptAddPrivateKey 310
cryptAddPublicKey 310
cryptAddRandom 311
cryptCAAddItem 311
cryptCACertManagement 311
cryptCAGetItem 312
cryptCheckCert 312
cryptCheckSignature 312
cryptCheckSignatureEx 313
cryptCreateCert 313
cryptCreateContext 314
cryptCreateEnvelope 314
cryptCreateSession 314
cryptCreateSignature 315
cryptCreateSignatureEx 315
cryptDecrypt 316
cryptDeleteAttribute 316
cryptDeleteCertExtension 316
Introduction
x
cryptDeleteKey 317
cryptDestroyCert 317
cryptDestroyContext 317
cryptDestroyEnvelope 317
cryptDestroyObject 318
cryptDestroySession 318
cryptDeviceClose 318
cryptDeviceCreateContext 318
cryptDeviceOpen 319
cryptDeviceQueryCapability 319
cryptEncrypt 319
cryptEnd 320
cryptExportCert 320
cryptExportKey 320
cryptExportKeyEx 321
cryptFlushData 322
cryptGenerateKey 322
cryptGetAttribute 322
cryptGetAttributeString 322
cryptGetCertExtension 323
cryptGetPrivateKey 323
cryptGetPublicKey 324
cryptImportCert 324
cryptImportKey 325
cryptInit 325
cryptKeysetClose 325
cryptKeysetOpen 326
cryptPopData 326
cryptPushData 326
cryptQueryCapability 327
cryptQueryObject 327
cryptSetAttribute 327
cryptSetAttributeString 328
cryptSignCert 328
cryptUIDisplayCert 328
cryptUIGenerateKey 329
ACKNOWLEDGEMENTS 330
cryptlib Overview 1
Introduction
The information age has seen the development of electronic pathways that carry vast
amounts of valuable commercial, scientific, and educational information between
financial institutions, companies, individuals, and government organisations.
Unfortunately the unprecedented levels of access provided by systems like the
Internet also expose this data to breaches of confidentiality, disruption of service, and
outright theft. As a result, there is an enormous (and still growing) demand for the
means to secure these online transactions. One report by the Computer Systems
Policy Project (a consortium of virtually every large US computer company,
including Apple, AT&T, Compaq, Digital, IBM, Silicon Graphics, Sun, and Unisys)
estimated that the potential revenue arising from these security requirements in the
US alone could be as much as US$30-60 billion in the next few years, and the
potential exposure to global users from a lack of this security is projected to reach
between US$320 and 640 billion.
Unfortunately the security systems required to protect data are generally extremely
difficult to design and implement, and even when available tend to require
considerable understanding of the underlying principles in order to be used. This has
lead to a proliferation of “snake oil” products that offer only illusionary security, or to
organisations holding back from deploying online information systems because the
means to secure them aren’t readily available, or because they employed weak, easily
broken security that was unacceptable to users.
The cryptlib security toolkit provides the answer to this problem. A complete
description of the capabilities provided by cryptlib is given below.
cryptlib Overview
cryptlib is a powerful security toolkit that allows even inexperienced crypto
programmers to easily add encryption and authentication services to their software.
The high-level interface provides anyone with the ability to add strong security
capabilities to an application in as little as half an hour, without needing to know any
of the low-level details that make the encryption or authentication work. Because of
this, cryptlib dramatically reduces the cost involved in adding security to new or
existing applications.
At the highest level, cryptlib provides implementations of complete security services
such as S/MIME and PGP/OpenPGP secure enveloping, SSL/TLS and SSH secure
sessions, CA services such as CMP, SCEP, RTCS, and OCSP, and other security
operations such as secure timestamping (TSP). Since cryptlib uses industry-standard
X.509, S/MIME, PGP/OpenPGP, and SSH/SSL/TLS data formats, the resulting
encrypted or signed data can be easily transported to other systems and processed
there, and cryptlib itself runs on virtually any operating system — cryptlib doesn’t tie
you to a single platform. This allows email, files, and EDI transactions to be
authenticated with digital signatures and encrypted in an industry-standard format.
cryptlib provides an extensive range of other capabilities including full X.509/PKIX
certificate handling (all X.509 versions from X.509v1 to X.509v4) with additional
support for SET, Microsoft AuthentiCode, Identrus, SigG, S/MIME, SSL, and
Qualified certificates, PKCS #7 certificate chains, handling of certification requests
and CRLs including automated checking of certificates against CRLs and online
checking using RTCS and OCSP, and issuing and revoking certificates using CMP
and SCEP. In addition cryptlib implements a full range of certification authority
(CA) functions, as well as providing complete CMP, SCEP, RTCS, and OCSP server
implementations to handle online certificate enrolment/issue/revocation and
certificate status checking. Alongside the certificate handling, cryptlib provides a
sophisticated key storage interface that allows the use of a wide range of key database
types ranging from PKCS #11 devices, PKCS #15 key files, and PGP/OpenPGP key
rings through to commercial-grade RDBMS’ and LDAP directories with optional
SSL protection.
Introduction
2
In addition to its built-in capabilities, cryptlib can make use of the crypto capabilities
of a variety of external crypto devices such as hardware crypto accelerators, Fortezza
cards, PKCS #11 devices, hardware security modules (HSMs), and crypto smart
cards. For particularly demanding applications cryptlib can be used with a variety of
crypto devices that have received appropriate FIPS 140 or ITSEC/Common Criteria
certification. The crypto device interface also provides a convenient general-purpose
plug-in capability for adding new functionality that will be automatically used by
cryptlib.
cryptlib is supplied as source code for AMX, BeOS, ChorusOS, DOS, DOS32, eCOS,
µC/OS-II, embedded Linux, FreeRTOS/OpenRTOS, IBM MVS, µITRON,
Macintosh/OS X, OS/2, PalmOS, RTEMS, Tandem, ThreadX, a variety of Unix
versions (including AIX, Digital Unix, DGUX, FreeBSD/NetBSD/OpenBSD, HP-
UX, IRIX, Linux, MP-RAS, OSF/1, QNX, SCO/UnixWare, Solaris, SunOS, Ultrix,
and UTS4), uClinux, VM/CMS, VxWorks, Windows 3.x, Windows 95/98/ME,
Windows CE/PocketPC/SmartPhone, Windows NT/2000/XP/Vista, VDK, and Xilinx
XMK. cryptlib’s highly portable nature means that it is also being used in a variety
of custom embedded system environments. In addition, cryptlib is available as a
standard Windows DLL and an ActiveX control.. cryptlib comes with language
bindings for C / C++, C# / .NET, Delphi, Java, Python, and Visual Basic (VB).
cryptlib features
cryptlib provides a standardised interface to a number of popular encryption
algorithms, as well as providing a high-level interface that hides most of the
implementation details and uses operating-system-independent encoding methods that
make it easy to transfer secured data from one operating environment to another.
Although use of the high-level interface is recommended, experienced programmers
can directly access the lower-level encryption routines for implementing custom
encryption protocols or methods not directly provided by cryptlib.
Architecture
cryptlib consists of a set of layered security services and associated programming
interfaces that provide an integrated set of information and communications security
capabilities. Much like the network reference model, cryptlib contains a series of
layers that provide each level of abstraction, with higher layers building on the
capabilities provided by the lower layers.
At the lowest level are basic components such as core encryption and authentication
routines, which are usually implemented in software but may also be implemented in
hardware (due to the speed of the software components used in cryptlib, the software
is usually faster than dedicated hardware). At the next level are components that
wrap up the specialised and often quite complex core components in a layer that
provides abstract functionality and ensures complete cross-platform portability of
data. These functions typically cover areas such as “create a digital signature” or
“exchange an encryption key”. At the highest level are extremely powerful and easy-
to-use functions such as “encrypt a message”, “sign a message”, “open a secure link”,
and “create a digital certificate” that require no knowledge of encryption techniques,
and that take care of complex issues such as key management, data encoding,
en/decryption, and digital signature processing.
cryptlib features 3
Secure data
enveloping
Secure communications
sessions
Certificate
management
Security services interface
Key
exchange
Digital
signature
Key
generation Key management
Encryption services interface Key store interface
Native
database
services
Adaptation
layer
Third-party
database
services
High-level interface
Native
encryption
services Third-party
encryption
services
Third-party
encryption
services
Adaptation
layer
Adaptation
layer
cryptlib’s powerful object management interface provides the ability to add
encryption and authentication capabilities to an application without needing to know
all the low-level details that make the encryption or authentication work. The
automatic object-management routines take care of encoding issues and cross-
platform portability problems, so that a handful of function calls is all that’s needed to
wrap up data in signed or encrypted form with all of the associated information and
parameters needed to recreate it on the other side of a communications channel. This
provides a considerable advantage over other encryption toolkits that often require
hundreds of lines of code and the manipulation of complex encryption data structures
to perform the same task.
S/MIME
cryptlib employs the IETF-standardised Cryptographic Message Syntax (CMS,
formerly called PKCS #7) format as its native data format. CMS is the underlying
format used in the S/MIME secure mail standard, as well as a number of other
standards covering secure EDI and related systems like HL7 medical messaging and
the Session Initiation Protocol (SIP) for services such as Internet telephony and
instant messaging. As an example of its use in secure EDI, cryptlib provides security
services for the Symphonia EDI messaging toolkit which is used to communicate
medical lab reports, patient data, drug prescription information, and similar
information requiring a high level of security.
The S/MIME implementation uses cryptlib’s enveloping interface which allows
simple, rapid integration of strong encryption and authentication capabilities into
existing email agents and messaging software. The resulting signed enveloped data
format provides message integrity and origin authentication services, the encrypted
enveloped data format provides confidentiality. In addition cryptlib’s S/MIME
implementation allows external services such as trusted timestamping authorities
(TSAs) to be used when a signed message is created, providing externally-certified
proof of the time of message creation. The complexity of the S/MIME format means
that the few other toolkits that are available require a high level of programmer
knowledge of S/MIME processing issues. In contrast cryptlib’s enveloping interface
makes the process as simple as pushing raw data into an envelope and popping the
processed data back out, a total of three function calls, plus one more call to add the
appropriate encryption or signature key.
PGP/OpenPGP
Alongside the PKCS #7/CMS/SMIME formats, cryptlib supports the PGP/OpenPGP
message format, allowing it to be used to send and receive PGP-encrypted email and
Introduction
4
data. As with the S/MIME implementation, the PGP implementation uses cryptlib’s
enveloping interface to allow simple, rapid integration of strong encryption and
authentication capabilities into existing email agents and messaging software. Since
the enveloping interface is universal, the process involved in creating PGP and
S/MIME messages is identical except for the envelope format specifier, allowing a
one-off development effort to handle any secure message format.
Secure Sessions
cryptlib secure sessions can include SSH, SSL, and TLS sessions, and general
communications sessions can include protocols such as the certificate management
protocol (CMP), simple certificate enrolment protocol (SCEP), real-time certificate
status protocol (RTCS), online certificate status protocol (OCSP), and timestamping
(TSP). As with envelopes, cryptlib takes care of the session details for you so that all
you need to do is provide basic communications information such as the name of the
server or host to connect to and any other information required for the session such as
a password or certificate. cryptlib takes care of establishing the session and
managing the details of the communications channel and its security parameters.
cryptlib provides both client and server implementations of all session types. By
tying a key or certificate store to the session, you can let cryptlib take care of any key
management issues for you. For example, with an SSH, SSL or TLS server session
cryptlib will use the key/certificate store to authenticate incoming connections, and
with a CMP or SCEP server session cryptlib will use the certificate store to handle the
certificate management process. In this way a complete CMP-based CA that handles
enrolment, certificate update and renewal, and certificate revocation, can be
implemented with only a handful of function calls.
Plug-and-play PKI
Working with certificates can be complex and painful, requiring the use of a number
of arcane and difficult-to-use mechanisms to perform even the simplest operations.
To eliminate this problem, cryptlib provides a plug-and-play PKI interface that
manages all certificate processing and management operations for you, requiring no
special knowledge of certificate formats, protocols, or operations. Using the plug-
and-play PKI interface with an appropriately-configured CA means that cryptlib will
automatically and transparently handle key generation, certificate enrolment, securely
obtaining trusted CA certificates, and certifying the newly-generated keys for the
user, all in a single operation. Similarly, certificate validity checking can be
performed using an online real-time status check that avoids the complexity and
delayed status information provided by mechanisms such as CRLs. The plug-and-
play PKI interface removes most of the complexity and difficulty involved in
working with certificates, making it easier to use certificates than with any of the
conventional certificate management mechanisms.
Certificate Management
cryptlib implements full X.509 certificate support, including all X.509 version 3,
version 4, and version 5 extensions as well as extensions defined in the IETF PKIX
certificate profile. cryptlib also supports additional certificate types and extensions
including SET certificates, Microsoft AuthentiCode and Netscape and Microsoft
server-gated crypto certificates, Identrus certificates, qualified certificates, S/MIME
and SSL client and server certificates, SigG extensions, and various vendor-specific
extensions such as Netscape certificate types and the Thawte secure extranet.
In addition to certificate handling, cryptlib allows the generation of certification
requests suitable for submission to certification authorities (CAs) in order to obtain a
certificate. Since cryptlib is itself capable of processing certification requests into
certificates, it is also possible to use cryptlib to provide full CA services. cryptlib
also supports the creating and handling of the certificate chains required for S/MIME,
SSL, and other applications, and the creation of certificate revocation lists (CRLs)
with the capability to check certificates against existing or new CRLs either
automatically or under programmer control. In addition to CRL-based revocation
cryptlib features 5
checking, cryptlib also supports online status protocols such as RTCS and OCSP.
cryptlib also implements the CMP protocol which fully automates the management of
certificates, allowing online certificate enrolment, issue, update/replacement, and
revocation of certificates, and the SCEP protocol, which automates the certificate
issue process. Using CMP removes from the user any need for technical knowledge
of certificate management, since all details are managed by the CA.
cryptlib can import and export certification requests, certificates, certificate chains,
and CRLs, covering the majority of certificate transport formats used by a wide
variety of software such as web browsers and servers. The certificate types that are
supported include:
Basic X.509 version 1 and 2 certificates
Extended X.509 version 3, 4, and 5 certificates
Certificates conformant to the IETF PKIX profile
SSL/TLS server and client certificates
S/MIME email certificates
SET certificates
SigG certificate extensions
AuthentiCode code signing certificates
Identrus certificates
Qualified certificates
IPsec server, client, end-user, and tunnelling certificates
Server-gated crypto certificates
Timestamping certificates
In addition cryptlib supports X.509v3, X.509v4, X.509v5, IETF, S/MIME, SET, and
SigG certificate extensions and many vendor-specific extensions including ones
covering public and private key usage, certificate policies, path and name constraints,
policy constraints and mappings, and alternative names and other identifiers. This
comprehensive coverage makes cryptlib a single solution for almost all certificate
processing requirements.
The diagram below shows a typical cryptlib application, in which it provides the full
functionality of both a CA (processing certification requests, storing the issued
certificates locally in a certificate database, and optionally publishing the certificates
on the web or in an LDAP directory) and an end entity (generating certification
requests, submitting them to a CA, and retrieving the result from the web or a
directory service).
Introduction
6
cryptlib CA
SSL
server LDAP
directory
Web
server
User
Retrieve
Publish
Cert request/
retrieve
CA
repository
Local
certificate
repository
To handle certificate trust and revocation issues, cryptlib includes a certificate trust
manager that can be used to automatically manage CA trust settings. For example a
CA can be designated as a trusted issuer that will allow cryptlib to automatically
evaluate trust along certificate chains. Similarly, cryptlib can automatically check
certificates against RTCS and OCSP responders and CRLs published by CAs,
removing from the user the need to perform complex manual checking.
CA Operations
cryptlib includes a scalable, flexible Certificate Authority (CA) engine built on the
transaction-processing capabilities of a number of proven, industrial-strength
relational databases running on a variety of hardware platforms. The CA facility
provides an automated means of handling certificate issuance without dealing directly
with the details of processing request, signing certificates, saving the resulting
certificates in keys stores, and assembling CRLs. This constitutes a complete CA
system for issuance and management of certificates and CRLs. A typical cryptlib CA
configuration is shown below.
cryptlib features 7
cryptlib CA HSM
Certificate
store
LDAP
RTCS/
OCSP
CMP/SCEP/
PKCS #10
Certificate
client
Status
client
Smart
card
Certificates/CRLs
Available CA operations include:
Certificate enrolment/initialisation operations
Certificate issue
Certificate update/key update
Certificate expiry management
Revocation request processing
CRL issue
All CA operations are recorded to an event log using cryptlib’s built-in CA
logging/auditing facility, which provides a full account of certificate requests,
certificates issued or renewed, revocations requested and issued, certificates expired,
and general CA management operations. The logs may be queried for information on
all events or a specified subset of events, for example all certificates that were issued
on a certain day.
cryptlib contains a full implementation of a CMP server (to handle online certificate
management), and SCEP server (to handle online certificate issue), a RTCS server (to
handle real-time certificate status checking), and an OCSP server (to handle
revocation checking). All of these servers are fully automated, requiring little user
intervention beyond the initial enrolment process in which user eligibility for a
certificate is established. These services make it easier than ever to manage your own
CA. Certificate expiration and revocation are handled automatically by the CA
engine. Expired certificates are removed from the certificate store, and CRLs are
assembled from previously processed certificate revocation requests. These
operations are handled with a single function call.
The CA keys can optionally be generated and held in tamper-resistant hardware
security modules, with certificate signing being performed by the hardware module.
Issued certificates can be stored on smart cards or similar crypto devices in addition
to being managed using software-only implementations. The CA facility supports the
simultaneous operation of multiple CAs, for example to manage users served through
Introduction
8
divisional CAs certified by a root CA. Each CA can issue multiple certificates to
users, allowing the use of separate keys bound to signature and encryption
certificates.
Crypto Devices and Smart Card Support
In addition to its built-in capabilities, cryptlib can make use of the crypto capabilities
of a variety of external crypto devices such as:
Hardware crypto accelerators
Fortezza cards
PKCS #11 devices
Crypto smart cards
Hardware security modules (HSMs)
PCI crypto cards
Dallas iButtons
Datakeys/iKeys
PCMCIA crypto tokens
USB tokens
These devices will be used by cryptlib to handle functions such as key generation and
storage, certificate creation, digital signatures, and message en- and decryption.
Typical applications include:
Running a certification authority inside tamper-resistant hardware
Smart-card based digital signatures
Message encryption/decryption in secure hardware
cryptlib manages any device-specific interfacing requirements so that the
programming interface for any crypto device is identical to cryptlib’s native interface,
allowing existing applications that use cryptlib to be easily and transparently migrated
to using crypto devices. The ability to mix and match crypto devices and the
software-only implementation allows appropriate tradeoffs to be chosen between
flexibility, cost, and security.
Certificate Store Interface
cryptlib utilizes commercial-strength RDBMS’ to store keys in the internationally
standardised X.509 format. The certificate store integrates seamlessly into existing
databases and can be managed using existing tools. For example a key database
stored on an MS SQL Server might be managed using Visual Basic or MS Access; a
key database stored on an Oracle server might be managed through SQL*Plus.
In addition to standard certificate stores, cryptlib supports the storage and retrieval of
certificates in LDAP directories, HTTP access for keys accessible via the web, and
external flat-file key collections such as PKCS #15 soft-tokens and PGP/OpenPGP
key rings. The key collections may be freely mixed (so for example a private key
could be stored in a PKCS #15 soft-token, a PGP/OpenPGP key ring or on a smart
card with the corresponding X.509 certificate being stored in a certificate store, an
LDAP directory, or on the web).
Private keys may be stored on disk encrypted with an algorithm such as triple DES or
AES (selectable by the user), with the password processed using several thousand
iterations of a hashing algorithm such as SHA-1 (also selectable by the user). Where
the operating system supports it, cryptlib will apply system security features such as
ACLs under Windows NT/2000/XP/Vista and file permissions under Unix to the
private key file to further restrict access.
cryptlib features 9
User Interface
In addition to its general security functionality, cryptlib includes a number of user
interface components that simplify the task of working with keys and certificates.
Components such as the certificate viewer shown below allow users to browse the
contents of certificates, certificate chains, requests, and other certificate objects. The
key generation wizard simplifies the task of key and certificate generation by
handling most of the details of the process automatically, producing a complete
public/private key pair and certificate request suitable for submission to a CA, or a
self-signed certificate for immediate use. These user interface components remove
much of the complexity of the key and certificate management process, allowing
developers to concentrate on applying the completed keys and certificates towards
securing data, email, or communications sessions rather than on the process needed to
create them.
Security Features
cryptlib is built around a security kernel with Orange Book B3-level security features
to implement its security mechanisms. This kernel provides the interface between the
outside world and the architecture’s objects (intra-object security) and between the
objects themselves (inter-object security). The security kernel is the basis of the
entire cryptlib architecture — all objects are accessed and controlled through it, and
all object attributes are manipulated through it. The kernel is implemented as an
interface layer that sits on top of the objects, monitoring all accesses and handling all
protection functions.
Each cryptlib object is contained entirely within the security perimeter, so that data
and control information can only flow in and out in a very tightly-controlled manner,
and objects are isolated from each other within the perimeter by the security kernel.
For example once keying information has been sent to an object, it can’t be retrieved
Introduction
10
by the user except under tightly-controlled conditions. In general keying information
isn’t even visible to the user, since it’s generated inside the object itself and never
leaves the security perimeter. This design is ideally matched to hardware
implementations that perform strict red/black separation, since sensitive information
can never leave the hardware.
Associated with each object is a set of mandatory ACLs that determine who can
access a particular object and under which conditions the access is allowed. If the
operating system supports it, all sensitive information used will be page-locked to
ensure that it’s never swapped to disk from where it could be recovered using a disk
editor. All memory corresponding to security-related data is managed by cryptlib and
will be automatically sanitised and freed when cryptlib shuts down even if the calling
program forgets to release the memory itself.
Where the operating system supports it, cryptlib will apply operating system security
features to any objects that it creates or manages. For example under Windows
NT/2000/XP/Vista cryptlib private key files will be created with an access control list
(ACL) that allows only the key owner access to the file; under Unix the file
permissions will be set to achieve the same result.
Embedded Systems
cryptlib’s high level of portability and configurability makes it ideal for use in
embedded systems with limited resources or specialised requirements, including ones
based on ARM7, ARM9, ARM TDMI, Fujitsu FR-V, Hitachi SuperH, MIPS IV,
MIPS V, Motorola ColdFire, NEC V8xx series, NEC VRxxxx series, Panasonic/
Matsushita AM33/AM34, PowerPC, Samsung CalmRISC, SH3, SH4, SPARC,
SPARClite, StrongArm, TI OMAP, and Intel XScale processors. cryptlib doesn’t
perform any floating-point operations and runs directly on processors without an
FPU.
The code is fully independent of any underlying storage or I/O mechanisms, and
works just as easily with abstractions such as named memory segments in flash
memory as it does with standard key files on disk. It has been deployed on embedded
systems without any conventional I/O capabilities (stdio) or dynamic memory
allocation facilities, with proprietary operating system architectures and services
including ATMs, printers, web-enabled devices, POS systems, embedded device
controllers, and similar environments, and even in devices with no operating system
at all (cryptlib runs on the bare metal). It can also run independent of any form of
operating system, and has been run on the bare metal in environments with minimal
available resources, in effect functioning as a complete crypto operating system for
the underlying hardware.
Because cryptlib functions identically across all supported environments, it’s possible
to perform application development in a full-featured development environment such
as Windows or Unix and only when the application is complete and tested move it to
the embedded system. This flexibility saves countless hours of development time,
greatly reducing the amount of time that needs to be spent with embedded systems
debuggers or in-circuit emulators since most of the development and code testing can
be done on the host system of choice.
If required the cryptlib developers can provide assistance in moving the code to any
new or unusual environments.
Performance
cryptlib is re-entrant and completely thread-safe, allowing it to be used with
multithreaded applications under BeOS, OS/2, Windows 95/98/ME, Windows
NT/2000/XP/Vista, Windows CE, and Unix systems that support threads. Because it
is thread-safe, lengthy cryptlib operations can be run in the background if required
while other processing is performed in the foreground. In addition cryptlib itself is
multithreaded so that computationally intensive internal operations take place in the
background without impacting the performance of the calling application.
cryptlib features 11
Most of the core algorithms used in cryptlib have been implemented in assembly
language in order to provide the maximum possible performance, and will take
advantage of crypto hardware acceleration facilities present in some CPUs such as the
Via C3 family. These routines provide an unprecedented level of performance, in
most cases running faster than expensive, specialised encryption hardware designed
to perform the same task. This means that cryptlib can be used for high-bandwidth
applications such as video/audio encryption and online network and disk encryption
without the need to resort to expensive, specialised encryption hardware.
Cryptographic Random Number Management
cryptlib contains an internal secure random data management system that provides
the cryptographically strong random data used to generate session keys and
public/private keys, in public-key encryption operations, and in various other areas
that require secure random data. The random data pool is updated with unpredictable
process-specific information as well as system-wide data such as current disk I/O and
paging statistics, network, assorted client/server network protocol traffic, packet filter
statistics, multiprocessor statistics, process information, users, VM statistics, process
statistics, battery/power usage statistics, system thermal management data, open files,
inodes, terminals, vector processors, streams, and loaded code, objects in the global
heap, loaded modules, running threads, process, and tasks, and an equally large
number of system performance-related statistics covering virtually every aspect of the
operation of the system.
The exact data collected depends on the hardware and operating system, but generally
includes extremely detailed and constantly changing operating statistics and
information. In addition if a /dev/random, EGD, or PRNGD-style style
randomness driver (which continually accumulates random data from the system) is
available, cryptlib will use this as a source of randomness. Finally, cryptlib supports
a number of cryptographically strong hardware random number generators, either
built into the CPU or system chipset or available as external crypto devices, that can
be used to supplement the internal generator. As a post-processing stage, cryptlib
employs an ANSI X9.17/X9.31 generator for additional security and for FIPS 140
compliance. This level of secure random number management ensures that security
problems such as those present in Netscape’s web browser (which allowed encryption
keys to be predicted without breaking the encryption because the “random” data
wasn’t at all random) can’t occur with cryptlib.
Programming Interface
The application programming interface (API) serves as an interface to a range of
plug-in encryption modules that allow encryption algorithms to be added in a fairly
transparent manner, so that adding a new algorithm or replacing an existing software
implementation with custom encryption hardware can be done without any trouble.
The standardised API allows any of the algorithms and modes supported by cryptlib
to be used with a minimum of coding effort. In addition the easy-to-use high-level
routines allow for the exchange of encrypted or signed messages or the establishment
of secure communications channels with a minimum of programming overhead.
Language bindings are available for C / C++, C# / .NET, Delphi, Java, Python, Tcl,
and Visual Basic (VB).
cryptlib has been written to be as foolproof as possible. On initialisation it performs
extensive self-testing against test data from encryption standards documents, and the
APIs check each parameter and function call for errors before any actions are
performed, with error reporting down to the level of individual parameters. In
addition logical errors such as, for example, a key exchange function being called in
the wrong sequence, are checked for and identified.
Documentation
cryptlib comes with extensive documentation in the form of a 310-page user manual
and a 320-page technical reference manual. The user manual is intended for
everyday cryptlib use and contains detailed documentation on every aspect of
Introduction
12
cryptlib’s functionality. In most cases the code needed to secure an application can
be cut and pasted directly from the appropriate section of the manual, avoiding the
need to learn yet another programming API. The user manual concludes with a
reference section covering the various cryptlib API functions, constants, and data
types.
The technical reference manual covers the design and internals of cryptlib itself,
including the cryptlib security model and security mechanisms that protect every part
of cryptlib’s operation. In addition the technical manual provides a wealth of
background information to help users understand the security foundations on which
cryptlib is built.
Algorithm Support
Included as core cryptlib components are implementations of the most popular
encryption and authentication algorithms, AES, Blowfish, CAST, DES, triple DES,
IDEA, RC2, RC4, RC5, and Skipjack, conventional encryption, MD2, MD4, MD5,
RIPEMD-160, SHA-1, and SHA-2 hash algorithms, HMAC-MD5, HMAC-SHA, and
HMAC-RIPEMD-160 algorithms, and Diffie-Hellman, DSA, Elgamal, and RSA
public-key encryption, with elliptic-curve encryption under development. The
algorithm parameters are summarised below:
Algorithm Key size Block size
AES 128/192/256 128
Blowfish 448 64
CAST-128 128 64
DES 56 64
Triple DES 112 / 168 64
IDEA 128 64
RC2 1024 64
RC4 2048 8
RC5 832 64
Skipjack 80 64
MD2 — 128
MD4 — 128
MD5 — 128
RIPEMD-160 — 160
SHA-1 — 160
SHA-2 / SHA-256 256
HMAC-MD5 128 128
HMAC-SHA 160 160
HMAC-RIPEMD-160 160 160
Diffie-Hellman 4096 —
DSA 40961
Elgamal 4096 —
RSA 4096 —
Standards Compliance
All algorithms, security methods, and data encoding systems in cryptlib either comply
with one or more national and international banking and security standards, or are
implemented and tested to conform to a reference implementation of a particular
algorithm or security system. Compliance with national and international security
standards is automatically provided when cryptlib is integrated into an application.
These standards include ANSI X3.92, ANSI X3.106, ANSI X9.9, ANSI X9.17, ANSI
X9.30-1, ANSI X9.30-2, ANSI X9.31-1, ANSI X9.42, ANSI X9.52, ANSI X9.55,
ANSI X9.57, ANSI X9.73, ETSI TS 101 733, ETSI TS 101 861, ETSI TS 101 862,
ETSI TS 102, FIPS PUB 46-2, FIPS PUB 46-3, FIPS PUB 74, FIPS PUB 81, FIPS
PUB 113, FIPS PUB 180, FIPS PUB 180-1, FIPS PUB 186, FIPS PUB 198, ISO/IEC
1 The DSA standard only defines key sizes from 512 to 1024 bits, cryptlib supports longer keys but there is no
extra security to be gained from using these keys.
cryptlib features 13
8372, ISO/IEC 8731 ISO/IEC 8732, ISO/IEC 8824/ITU-T X.680, ISO/IEC
8825/ITU-T X.690, ISO/IEC 9797, ISO/IEC 10116, ISO/IEC 10118, ISO/IEC 15782,
ITU-T X.842, ITU-T X.843, PKCS #1, PKCS #3, PKCS #5, PKCS #7, PKCS #9,
PKCS #10, PKCS #11, PKCS #15, RFC 1319, RFC 1320, RFC 1321, RFC 1750,
RFC 1991, RFC 2040, RFC 2104, RFC 2144, RFC 2202, RFC 2246, RFC 2268, RFC
2311 (cryptography-related portions), RFC 2312, RFC 2313, RFC 2314, RFC 2315,
RFC 2437, RFC 2440, RFC 2459, RFC 2510, RFC 2511, RFC 2528, RFC 2560, RFC
2585, RFC 2630, RFC 2631, RFC 2632, RFC 2633 (cryptography-related portions),
RFC 2634, RFC 2785, RFC 2876, RFC 2898, RFC 2984, RFC 2985, RFC 2986, RFC
3039, RFC 3058, RFC 3114, RFC 3126, RFC 3161, RFC 3174, RFC 3183, RFC
3211, RFC 3218, RFC 3261 (cryptography-related portions), RFC 3268, RFC 3274,
RFC 3279, RFC 3280, RFC 3281, RFC 3369, RFC 3370, RFC 3447, RFC 3546, RFC
3565, RFC 3739, RFC 3770, RFC 3851, RFC 3852, RFC 4055, RFC 4086, RFC
4108, RFC 4134, RFC 4210, RFC 4211, RFC 4231, RFC 4250, RFC 4251, RFC
4252, RFC 4253, RFC 4254, RFC 4256, RFC 4262, RFC 4279, RFC 4325, RFC
4334, RFC 4346, RFC 4366, RFC 4387, RFC 4419, RFC 4476, RFC 4648, RFC
4680, RFC 4681, and the Payment Card Industry (PCI) Data Security Standard
(cryptography-related portions). Because of the use of internationally recognised and
standardised security algorithms, cryptlib users will avoid the problems caused by
home-grown, proprietary algorithms and security techniques that often fail to provide
any protection against attackers, resulting in embarrassing bad publicity and
expensive product recalls.
Y2K Compliance
cryptlib handles all date information using the ANSI/ISO C time format, which does
not suffer from Y2K problems. Although earlier versions of the X.509 certificate
format do have Y2K problems, cryptlib transparently converts the dates encoded in
certificates to and from the ANSI/ISO format, so cryptlib users will never see this.
cryptlib’s own time/date format is not affected by any Y2K problems, and cryptlib
itself conforms to the requirements in the British Standards Institution’s DISC
PD2000-1:1998 Y2K compliance standard.
Configuration Options
cryptlib works with a configuration database that can be used to tune its operation for
different environments. This allows a system administrator to set a consistent
security policy which is then automatically applied by cryptlib to operations such as
key generation and data encryption and signing, although they can be overridden on a
per-application or per-user basis if required.
cryptlib Applications
The security services provided by cryptlib can be used in virtually any situation that
requires the protection or authentication of sensitive data. Some areas in which
cryptlib is currently used include:
Protection of medical records transmitted over electronic links.
Protection of financial information transmitted between branches of banks.
Transparent disk encryption.
Strong security services added to web browsers with weak, exportable
security.
Running a CA.
Encrypted electronic mail.
File encryption.
Protecting content on Internet servers.
Digitally signed electronic forms.
S/MIME mail gateway.
Introduction
14
Secure database access.
Protection of credit card information.
Encryption Code Example
The best way to illustrate what cryptlib can do is with an example. The following
code encrypts a message using public-key encryption.
/* Create an envelope for the message */
cryptCreateEnvelope( &cryptEnvelope, cryptUser, CRYPT_FORMAT_SMIME );
/* Push in the message recipient's name */
cryptSetAttributeString( cryptEnvelope, CRYPT_ENVINFO_RECIPIENT,
recipientName, recipientNameLength );
/* Push in the message data and pop out the signed and encrypted
result */
cryptPushData( cryptEnvelope, message, messageSize, &bytesIn );
cryptFlushData( cryptEnvelope );
cryptPopData( cryptEnvelope, encryptedMessage, encryptedSize,
&bytesOut );
/* Clean up */
cryptDestroyEnvelope( cryptEnvelope );
This performs the same task as a program like PGP using just 6 function calls (to
create a PGP/OpenPGP message, just change the CRYPT_FORMAT_SMIME to
CRYPT_FORMAT_PGP). All data management is handled automatically by
cryptlib, so there’s no need to worry about encryption modes and algorithms and key
lengths and key types and initialisation vectors and other details (although cryptlib
provides the ability to specify all this if you feel the need).
The code shown above results in cryptlib performing the following actions:
Generate a random session key for the default encryption algorithm (usually
triple DES or AES).
Look up the recipient’s public key in a key database.
Encrypt the session key using the recipient’s public key.
Encrypt the signed data with the session key.
Pass the result back to the user.
However unless you want to call cryptlib using the low-level interface, you never
need to know about any of this. cryptlib will automatically know what to do with the
data based on the resources you add to the envelope — if you add a signature key it
will sign the data, if you add an encryption key it will encrypt the data, and so on.
Secure Session Code Example
Establishing a secure session using SSL/TLS is similarly easy:
CRYPT_SESSION cryptSession;
/* Create the session */
cryptCreateSession( &cryptSession, cryptUser, CRYPT_SESSION_SSL );
/* Add the server name and activate the session */
cryptSetAttributeString( cryptSession, CRYPT_SESSINFO_SERVER_NAME,
serverName, serverNameLength );
cryptSetAttribute( cryptSession, CRYPT_SESSINFO_ACTIVE, 1 );
If you prefer SSH to SSL, just change the CRYPT_SESSION_SSL to CRYPT_-
SESSION_SSH and add a user name and password to log on. As with the encryption
code example above, cryptlib provides a single unified interface to its secure session
mechanisms, so you don’t have to invest a lot of effort in adding special-case
handling for different security protocols and mechanisms.
The corresponding SSL/TLS (or SSH if you prefer) server is:
Document conventions 15
CRYPT_SESSION cryptSession;
/* Create the session */
cryptCreateSession( &cryptSession, cryptUser, CRYPT_SESSION_SSL_SERVER
);
/* Add the server key/certificate and activate the session */
cryptSetAttribute( cryptSession, CRYPT_SESSINFO_PRIVATEKEY, privateKey
);
cryptSetAttribute( cryptSession, CRYPT_SESSINFO_ACTIVE, 1 );
As with the secure enveloping example, cryptlib is performing a large amount of
work in the background, but again there’s no need to know about this since it’s all
taken care of automatically.
Certificate Management Code Example
The following code illustrates cryptlib’s plug-and-play PKI interface:
CRYPT_SESSION cryptSession;
/* Create the CMP session and add the server name/address */
cryptCreateSession( &cryptSession, cryptUser, CRYPT_SESSION_CMP );
cryptSetAttributeString( cryptSession, CRYPT_SESSINFO_SERVER, server,
serverLength );
/* Add the username, password, and smart card */
cryptSetAttributeString( cryptSession, CRYPT_SESSINFO_USERNAME,
userName, userNameLength );
cryptSetAttributeString( cryptSession, CRYPT_SESSINFO_PASSWORD,
password, passwordLength );
cryptSetAttribute( cryptSession, CRYPT_SESSINFO_CMP_PRIVKEYSET,
cryptDevice );
/* Activate the session */
cryptSetAttribute( cryptSession, CRYPT_SESSINFO_ACTIVE, TRUE );
This code takes a smart card and generates separate encryption and signing keys in it,
requests a signature certificate from the CA for the signing key, uses that to obtain a
certificate for the encryption key, obtains any further certificates that may be needed
from the CA (for example for S/MIME signing or SSL server operation), and stores
everything in the smart card. Compare this to the hundreds or even thousands of lines
of code required to do the same thing using other toolkits.
Oh yes, and cryptlib provides the CA-side functionality as well — there’s no need to
pay an expensive commercial CA for your certificates, since cryptlib can perform the
same function.
Document conventions
This manual uses the following document conventions:
Example Description
cryptlib.h This font is used for filenames.
cryptCreateContext Bold type indicates cryptlib function names.
Value Words or portions of words in italics indicate
placeholders for information that you need to
supply.
if( i == 0 ) This font is used for sample code and operating
system commands.
Recommended Reading
One of the best books to help you understand how to use cryptlib is Network Security
by Charlie Kaufman, Radia Perlman, and Mike Speciner, which covers general
security principles, encryption techniques, and a number of potential cryptlib
applications such as X.400/X.500 security, PEM/S/MIME/PGP, Kerberos, and
various other security, authentication, and encryption techniques. The book also
Introduction
16
contains a wealth of practical advice for anyone considering implementing a
cryptographic security system. Security Engineering: A Guide to Building
Dependable Distributed Systems by Ross Anderson also contains a large amount of
useful information and advice on engineering secure systems. Building Secure
Software by John Viega and Gary McGraw and Writing Secure Software by Michael
Howard and David LeBlanc contain a wealth of information on safe programming
techniques and related security issues.
Cryptographic Security Architecture Design and Verification by Peter Gutmann is the
technical documentation for cryptlib and complements the cryptlib user manual. It
contains full details of the architectural and security features of cryptlib, as well as a
wealth of background material to help you understand the security foundations on
which cryptlib is built.
A tutorial in 8 parts totalling over 700 slides and covering all aspects of encryption
and general network security, including encryption and security basics, algorithms,
key management and certificates, CAs, certificate profiles and policies, PEM, PGP,
S/MIME, SSL, SSH, SET, smart cards, and a wide variety of related topics, is
available from http://www.cs.auckland.ac.nz/~pgut001/tutorial/. If
you want to do anything with certificates, you should definitely read Everything you
Never Wanted to Know about PKI but were Forced to Find Out, available from
http://www.cs.auckland.ac.nz/~pgut001/pubs/-
pkitutorial.pdf, to find out what you’re in for if you have to work with
certificates.
In addition to this, there are a number of excellent books available that will help you
in understanding the cryptography used in cryptlib. The foremost of these are
Applied Cryptography by Bruce Schneier and the Handbook of Applied Cryptography
by Alfred Menezes, Paul van Oorschot, and Scott Vanstone. Applied Cryptography
provides an easy-to-read overview while the Handbook of Applied Cryptography
provides extremely comprehensive, in-depth coverage of the field.
For general coverage of computer security issues, Security in Computing by Charles
Pfleeger provides a good overview of security, access control, and secure operating
systems and databases, and also goes into a number of other areas such as ethical
issues that aren’t covered by most books on computer security. Computer Security:
Art and Science by Matt Bishop provides in-depth coverage of all aspects of
computer security modelling and design, with a particular emphasis on access control
and security models and high-assurance systems.
Recommended Reading 17
Installation
This chapter describes how to install cryptlib for a variety of operating systems.
AMX
The AMX Multitasking Executive is a real-time OS (RTOS) with development
hosted under Unix or Windows. You can build cryptlib for AMX using the cross-
compilation capabilities of the standard makefile, see the entry for Unix on page 20
for more details on working with the makefile. The make target for AMX is
target-amx, so you’d build cryptlib with make target-amx. Details on building
and using cryptlib for AMX, and on embedded cryptlib in general, are given in
“Embedded Systems” on page 278.
BeOS
The BeOS version of cryptlib can be built using a procedure which is identical to that
given for Unix on page 20. Any current version of BeOS can build the code directly
from the Unix makefile. Old versions of BeOS using the Be development
environment will require that you edit the Unix makefile slightly by un-commenting
the marked lines at the start of the file.
ChorusOS
ChorusOS is an embedded OS with development hosted under Unix. You can build
cryptlib for ChorusOS using the cross-compilation capabilities of the standard
makefile, see the entry for Unix on page 20 for more details on working with the
makefile. The make target for ChorusOS is target-chorus, so you’d build
cryptlib with make target-chorus. Details on building and using cryptlib for
ChorusOS, and on embedded cryptlib in general, are given in “Embedded Systems”
on page 278.
DOS
The 16-bit DOS version of cryptlib can be built from the same files as the 16-bit
Windows version, so no separate makefile is provided. Because DOS is so limited in
its capabilities, it is in effect an embedded systems OS. Details on building and using
cryptlib for DOS, and on embedded cryptlib in general, are given in “Embedded
Systems” on page 278.
DOS32
The 32-bit DOS version of cryptlib can be built using the supplied makefile, which
requires the djgpp compiler. The DOS32 version of cryptlib uses the same 32-bit
assembly language code used by the Win32 and 80x86 Unix versions, so it runs
significantly faster than the 16-bit DOS version. Like the 16-bit DOS version, any
attempt to use the high-level key export routines will fail with a CRYPT_ERROR_-
RANDOM error code unless a /dev/random-style driver is available because there
isn’t any way to reliably obtain random data under DOS. You can however treat
DOS as an embedded systems environment and use the random seeding capability
described in “Porting to Devices without Randomness/Entropy Sources” on page 285.
eCOS
eCOS is an embedded/real-time OS (RTOS) with development hosted under Unix or
Windows. You can build cryptlib for eCOS using the cross-compilation capabilities
of the standard makefile, see the entry for Unix on page 20 for more details on
working with the makefile. The make target for eCOS is target-ecos, so you’d
build cryptlib with make target-ecos. Details on building and using cryptlib for
eCOS, and on embedded cryptlib in general, are given in “Embedded Systems” on
page 278.
Installation
18
FreeRTOS/OpenRTOS
FreeRTOS/OpenRTOS is a real-time kernel with development hosted under
Windows. You can build cryptlib for FreeRTOS/OpenRTOS using the cross-
compilation capabilities of the standard makefile, see the entry for Unix on page 20
for more details on working with the makefile. The make target for FreeRTOS/-
OpenRTOS is target-freertos, so you’d build cryptlib with make target-
freertos. Details on building and using cryptlib for FreeRTOS/OpenRTOS, and on
embedded cryptlib in general, are given in “Embedded Systems” on page 278.
µC/OS-II
µC/OS-II is an embedded/real-time OS (RTOS) with development usually hosted
under Windows. You can build cryptlib for µC/OS-II using the cross-compilation
capabilities of the standard makefile, see the entry for Unix on page 20 for more
details on working with the makefile. The make target for µC/OS-II is target-
ucos, so you’d build cryptlib with make target-ucos. Details on building and
using cryptlib for µC/OS-II, and on embedded cryptlib in general, are given in
“Embedded Systems” on page 278.
Embedded Linux
The embedded Linux version of cryptlib can be built using the standard Linux
development tools. Since this environment is identical to the generic Unix one, the
installation instructions for Unix on page 20 apply here.
µITRON
µITRON is an embedded/real-time OS (RTOS) with development usually hosted
under Unix or a Unix-like OS. You can build cryptlib for µITRON using the cross-
compilation capabilities of the standard makefile, see the entry for Unix on page 20
for more details on working with the makefile. The make target for µITRON is
target-itron, so you’d build cryptlib with make target-itron. Details on
building and using cryptlib for µITRON, and on embedded cryptlib in general, are
given in “Embedded Systems” on page 278.
Macintosh OS X
The standard Macintosh build environment uses Apple’s Mac OS X Developer Tools,
driven by the standard makefile, for which the instructions in the section on building
cryptlib for Unix on page 20 apply. Alternatively, you can build cryptlib using
Metroworks’ Codewarrior with the Mac.mcp project file. This can build cryptlib
either as a static or shared library for both 68K and PowerPC Macs, although since
this isn’t the primary build environment the project file may apply to a slightly older
cryptlib release and require a little updating to match the current configuration (the
standard makefile will always be current). In addition it’s possible to build it using
Apple’s free MrC compiler, with the same caveat about updating of configuration
files.
MVS
The MVS version of cryptlib can be built using the standard IBM C/C++ compiler
and accompanying tools. Since this environment is very similar to the Unix one, the
installation instructions for Unix on page 20 apply here also. Note that PTF
UQ50384 (which fixes a bug in the macro version of the strcat function as
described in APAR PQ43130) is required if you’re using the V2R10 C/C++ compiler.
You can control the use of ddnames with the DDNAME_IO define. If DDNAME_IO
is defined when building the code, cryptlib will use ddnames for all I/O, and user
options will be saved in dynamically allocated datasets userid.CRYPTLIB.filename.
If DDNAME_IO is not defined when building the code, cryptlib will use HFS for all
I/O, and user options will be saved in $HOME/.cryptlib.
After you’ve built cryptlib, you should run the self-test program to make sure that
everything is working OK. You can use the ussalloc USS shell script to allocate
Recommended Reading 19
MVS data sets for testlib, and the usscopy shell script to copy the files in the test
directory to the MVS data sets allocated with ussalloc. testlib.jcl is the JCL needed
to execute testlib.
OS2
The OS/2 version of cryptlib can be built using the command-line version of the IBM
compiler. The supplied makefile will build the DLL version of cryptlib, and can also
build the cryptlib self-test program, which is a console application. You should run
the self-test program after you’ve built cryptlib to make sure that everything is
working OK.
If you’re using the IBM OS/2 compiler you should set enumerated types to always be
32-bit values because the compiler by default uses variable-length types depending on
the enum range (so one enum could be an 8-bit type and another 32). cryptlib is
immune to this “feature”, and function calls from your code to cryptlib should also be
unaffected because of type promotion to 32-bit integers, but the variable-range enums
may cause problems in your code if you try to work with them under the assumption
that they have a fixed type.
PalmOS
PalmOS is the operating system for the Palm series of PDAs, with development
hosted under Unix or Windows. You can build cryptlib for PalmOS using the
PalmOS 6 SDK and the cross-compilation capabilities of the standard makefile, see
the entry for Unix on page 20 for more details on working with the makefile. The
make target for the PalmOS SDK is target-palmos and for the alternative PRC
development tools is target-palmos-prc, so you’d build cryptlib with make
target-palmos or make target-palmos-prc. Details on building and using cryptlib
for PalmOS, and on embedded cryptlib in general, are given in “Embedded Systems”
on page 278.
QNX Neutrino
The QNX Neutrino version of cryptlib can be built using the standard QNX
development tools. Since this environment is identical to the generic Unix one, the
installation instructions for Unix on page 20 apply here.
RTEMS
The Real-Time Operating System for Multiprocessor Systems (RTEMS) is a real-
time OS (RTOS) with development hosted under Unix or Windows. You can build
cryptlib for RTEMS using the cross-compilation capabilities of the standard makefile,
see the entry for Unix on page 20 for more details on working with the makefile. The
make target for RTEMS is target-rtems, so you’d build cryptlib with make
target-rtems. Details on building and using cryptlib for RTEMS, and on embedded
cryptlib in general, are given in “Embedded Systems” on page 278.
Tandem
The Tandem version of cryptlib can be built using the standard c89 compiler and
accompanying tools under the OSS environment. Since this environment is very
similar to the Unix one, the installation instructions for Unix on page 20 apply here
also. The default target is Tandem OSS, you can re-target the built for NSK using the
-Wsystype=guardian directive in the makefile.
The Guardian sockets implementation changed in newer releases of the OS. Older
releases required the use of non-standard nowait sockets handled via AWAITIOX()
instead of the standard BSD sockets interface. If you’re running an older version of
the OS and need to use any of the secure networking protocols such as SSL/TLS,
SSH, CMP, SCEP, RTCS, or OCSP, you’ll need to use cryptlib’s alternative network
data-handling strategy described in “Network Issues” on page 118.
Installation
20
ThreadX
ThreadX (and optionally FileX) is a real-time OS (RTOS) with development hosted
under Unix or Windows. You can build cryptlib for RTEMS using the cross-
compilation capabilities of the standard makefile, see the entry for Unix on page 20
for more details on working with the makefile. The make target for ThreadX is
target-threadx, so you’d build cryptlib with make target-threadx. Details on
building and using cryptlib for ThreadX, and on embedded cryptlib in general, are
given in “Embedded Systems” on page 278.
uClinux
uClinux is a real-mode/embedded version of Linux with development hosted under
Unix. You can build cryptlib for uClinux using the cross-compilation capabilities of
the standard makefile, see the entry for Unix on page 20 for more details on working
with the makefile. The make target for uClinux is target-uclinux, so you’d
build cryptlib with make target-uclinux. Details on building and using cryptlib for
uClinux, and on embedded cryptlib in general, are given in “Embedded Systems” on
page 278.
Unix
To unzip the code under Unix use the -a option to ensure that the text files are
converted to the Unix format. The makefile by default will build the statically-linked
library when you invoke it with make. To build the shared library, use make
shared. Once cryptlib has been built, use make testlib to build the cryptlib
self-test program testlib, or make stestlib to build the shared-library self-test
program stestlib. This will run fairly extensive self-tests of cryptlib that you can run
after you’ve built it to make sure that everything is working OK. testlib needs to be
run from the cryptlib root directory (the one that the main data files are in) since it
uses a large number of pre-generated data files that are located in a subdirectory
below this one. Depending on your system setup and privileges you may need to
either copy the shared library to /usr/lib or set the LD_LIBRARY_PATH
environment variable (or an OS-specific equivalent) to make sure that the shared
library is used.
If you’re using the statically-linked form of cryptlib in your application rather than
the shared library, you’ll probably need to link in additional (system-specific) static
libraries to handle threads, network access, and system-specific odds and ends. The
makefile contains a list of the needed additional libraries, ordered by system type and
version. The shared-library version of cryptlib doesn’t require these additional
libraries to be linked in, since the references are automatically resolved by the OS.
If your system doesn’t come pre-configured with a /dev/random, EGD, or
PRNGD-style style randomness driver (which continually accumulates random data
from the system), you may want to download one and install it, since cryptlib will
make use of it for gathering entropy. cryptlib has a built-in randomness polling
subsystem so it will function without an external randomness driver, but it never hurts
to have one present to supplement the internal entropy polling.
If you’re using a key database or certificate store, you need to enable the use of the
appropriate interface module for the database backend. Details are given in “Key
Database Setup” on page 24. For the cryptlib self-test code you can define the
database libraries using the TESTLIBS setting at the start of the makefile. If you
don’t enable the use of a database interface, the self-test code will issue a warning
that no key database is present and continue without testing the database interface.
If you’re using an LDAP directory, you need to install the required LDAP client
library on your system, enable the use of LDAP using the USE_LDAP define before
you build cryptlib, and link the LDAP client library into your executable (on most
systems the cryptlib build scripts will take care of this automatically). If you don’t
enable the use of an LDAP directory interface, the self-test code will issue a warning
Recommended Reading 21
that no LDAP directory interface is present and continue without testing the LDAP
interface.
If you’re using special encryption hardware or an external encryption device such as a
PCMCIA card or smart card, you need to install the required device drivers on your
system and enable their use when you build cryptlib by linking in the required
interface libraries. If you don’t enable the use of a crypto device, the self-test code
will issue a warning that no devices are present and continue without testing the
crypto device interface.
If your application forks, you shouldn’t need to take any special actions for cryptlib
beyond the usual precautions with forking a process. In particular forking a process
that contains multiple threads has system-specific semantics, with the behaviour
depending on whether the system implements fork1 or forkall behaviour. With fork1
behaviour (the Posix default), only the thread that calls fork() is copied to the child.
With forkall, all threads in the process are copied. The fork1 behaviour can lead to
deadlock if a thread other than the one that called fork() holds a lock, since the fact
that it’s not copied to the child means that it’ll never be released. You can work
around this with pthread_atfork() to handle lock management, but a better
approach is to simply not mix threads and forking unless you follow the fork() with
an exec(). Note that this isn’t a cryptlib issue, it’s specific to the interaction of
fork() and threads.
For any common Unix system, cryptlib will build without any problems, but in some
rare cases you may need to edit random/unix.c and possibly io/file.h and io/tcp.h if
you’re running an unusual Unix variant that puts include files in strange places or has
broken Posix or sockets support.
VDK
The Visual DSP++ Kernel (VDK) from Analog Devices is a kernel for AD
processors with development hosted under Windows. You can build cryptlib for
VDK using the cross-compilation capabilities of the standard makefile, see the entry
for Unix on page 20 for more details on working with the makefile. The make target
for VDK is target-vdk, so you’d build cryptlib with make target-vdk. Details
on building and using cryptlib for VDK, and on embedded cryptlib in general, are
given in “Embedded Systems” on page 278.
VM/CMS
The VM/CMS version of cryptlib can be built using the standard C/370 compiler and
accompanying tools. The supplied EXEC2 file VMBUILD EXEC will build cryptlib
as a TXTLIB and then build the self-test program as an executable MODULE file.
Since VM sites typically have different system configurations, this file and possibly
portions of the source code may require tuning in order to adjust it to suit the build
process normally used at your site.
VxWorks
VxWorks is an embedded/real-time OS (RTOS) with development hosted under Unix
or Windows. You can build cryptlib for VxWorks using the cross-compilation
capabilities of the standard makefile, see the entry for Unix on page 20 for more
details on working with the makefile. The make target for VxWorks is target-
vxworks, so you’d build cryptlib with make target-vxworks. Details on building
and using cryptlib for VxWorks, and on embedded cryptlib in general, are given in
“Embedded Systems” on page 278.
Windows 3.x
The 16-bit cryptlib DLL can be built using the crypt16.mak makefile, which is for
version 1.5x of the Visual C++ compiler. The mixed C/assembly language
encryption and hashing code will give a number of warnings, the remaining code
should compile without warnings. Once the DLL has been built, test.mak will build
Installation
22
the cryptlib self-test program, which is a console application. You can run this after
you’ve built cryptlib to make sure that everything is working OK.
If you’re using a key database or certificate store, you need to set up an ODBC data
source for this. Details are given in “Key Database Setup” on page 24.
Windows 95/98/ME and Windows NT/2000/XP/Vista
The 32-bit cryptlib DLL can be built using the crypt32 project file, which is for
Visual C++ 6 and Visual C++ .NET. Once the DLL has been built, the test32
project file will build the cryptlib self-test program test32, which is a console
application. You can run this after you’ve built cryptlib to make sure that everything
is working OK. test32 needs to be run from the cryptlib root directory (the one that
the main data files are in) since it uses a large number of pre-generated data files that
are located in a subdirectory below this one. If you’ll be using the cryptlib user
interface components you need to install the cryptlib user interface library cl32ui.dll
alongside cryptlib itself.
If you’re using an older version of Visual C++ .NET, a bug in its version 6 project
file import process results in files having the $(NoInherit) property set, so that a
define made at the project level won’t be passed down to other files. If you want to
enable options based on global defines, you need to disable this property before the
defines will propagate down to other files.
If you’re using a key database or certificate store, you need to set up an ODBC data
source for this. Details are given in “Key Database Setup” on page 24.
If you’re using special encryption hardware or an external encryption device such as a
PCMCIA card or smart card, you need to install the required device drivers on your
system, and if you’re using a generic PKCS #11 device you need to configure the
appropriate driver for it as described in “Encryption Devices and Modules” on page
256. cryptlib will automatically detect and use any devices that it recognises and that
have drivers present. If you don’t enable the use of a crypto device, the self-test code
will issue a warning that no devices are present and continue without testing the
crypto device interface.
Personal firewall products from some vendors can interfere with network operations
for devices other than standard web browsers and mail clients. If you’re experiencing
odd behaviour when using cryptlib for network operations (for example you can
connect but can’t exchange data, or you get strange error messages when you
connect), you can try temporarily disabling the personal firewall to see if this fixes
the problem. If it does, you should contact the personal firewall vendor to fix their
product, or switch to a different product.
If you’re using Borland C++ rather than Visual C++, you’ll need to set up the .def
and .lib files for use with the Borland compiler. To do this, run the following
commands in the cryptlib directory:
impdef cl32 cl32
implib cl32 cl32.def
The first one will produce a Borland-specific .def file from the DLL, the second one
will produce a Borland-specific .lib file from the DLL and .def file.
To install the ActiveX control, put the cryptlib DLL and the ActiveX wrapper
clcom.dll into the Windows system directory and register the ActiveX wrapper with:
regsvr32 clcom.dll
To use the ActiveX control with Visual Basic, use Project | Reference to add
clcom.dll, after which VB will recognise the presence of the ActiveX wrapper.
Windows CE / Pocket PC / SmartPhone
The 32-bit cryptlib DLL for Windows CE/PocketPC/SmartPhone can be built using
the crypt32ce project file, which is for version 3 or 4 of the eMbedded Visual C++
compiler. Once the DLL has been built, the test32ce project file will build the
Recommended Reading 23
cryptlib self-test program test32ce, which is a (pseudo-)console application that
produces its output on the debug console. You can run this after you’ve built cryptlib
to make sure that everything is working OK. test32ce needs to be run from the
cryptlib root directory (the one that the main data files are in) since it uses a large
number of pre-generated data files that are located in a subdirectory below this one.
The cryptlib Windows CE self-test uses the ‘Storage Card’ pseudo-folder to access
the files needed for the self-test. Depending on the system setup, you need to either
copy the files to the storage card or (the easier alternative) use folder sharing to
access the directory containing the test files. From the Windows CE menu, select
Folder Sharing and share the testdata subdirectory, which will appear as \\Storage
Card\ on the Windows CE device.
Windows CE is a Unicode environment, which means that all text strings are passed
to and from cryptlib as Unicode strings. For simplicity the examples in this manual
are presented using the standard char data type used on most systems, however
under Windows CE all character types and strings are Unicode in line with standard
Windows CE practice. When you’re using the examples, you should treat any
occurrence of characters and strings as standard Unicode data types.
A few older versions of eVC++ for some platforms don’t include the ANSI/ISO C
standard time.h header, which is a required file for a conforming ANSI/ISO C
compiler. If you have a version of eVC++ that doesn’t include this standard header,
you need to add it from another source, for example an eVC++ distribution that does
include it or the standard (non-embedded) VC++ distribution.
When compiling cryptlib under eVC++ 4.0 for the Arm architecture with
optimisation enabled, a compiler bug may prevent three files from compiling. If you
get an internal compiler error trying to compile context/kg_rsa.c, crypt/rc2skey.c,
or misc/base64.c, you can work around the problem by disabling optimisation using
#pragma optimize( "g", off ) / #pragma optimize( "g", on )
around the functions initCheckRSAkey() in kg_rsa.c, RC2_set_key() in
rc2skey.c, and adjustPKIUserValue() in base64.c.
Xilinx XMK
The Xilinx Microkernel (XMK) is a real-time OS (RTOS) with development hosted
under Unix or Windows. You can build cryptlib for XMK using the cross-
compilation capabilities of the standard makefile, see the entry for Unix on page 20
for more details on working with the makefile. The make target for XMK is
target-xmk-mb for the MicroBlaze core and target-xmk-ppc for the
PowerPC core, so you’d build cryptlib with make target-xmk-mb or make target-
xmk-ppc. Details on building and using cryptlib for XMK, and on embedded
cryptlib in general, are given in “Embedded Systems” on page 278.
Other Systems
cryptlib should be fairly portable to other systems, the only part that needs special
attention is the randomness-gathering in random/os_name.c (cryptlib won’t work
without this, the code will produce a link error). The idea behind the randomness-
gathering code is to perform a comprehensive poll of every possible entropy source in
the system in a separate thread or background task (“slowPoll”), as well as providing
a less useful but much faster poll of quick-response sources (“fastPoll”). In addition
the filesystem I/O code in io/file.c may need system-specific code and definitions
added to it if the system you’re running on doesn’t use a standard form of file I/O, for
example a system that has its own file I/O layer that isn’t compatible with standard
models or one that doesn’t have file I/O at all such as an embedded device that uses
flash memory for storage.
To find out what to compile, look at the Unix makefile, which contains all of the
necessary source files (the group_name_OBJS dependencies) and compiler
options. Link all of these into a library (as the makefile does) and then compile and
link the modules in the test subdirectory with the library to create the self-test
program. There is additional assembly-language code included that will lead to
Installation
24
noticeable speedups on some systems, you should modify your build options as
appropriate to use these if possible.
Depending on your compiler you may get a few warnings about some of the
encryption and hashing code (one or two) and the bignum code (one or two). This
code mostly relates to the use of C as a high-level assembler and changing things
around to remove the warnings on one system could cause the code to break on
another system.
Key Database Setup
If you want to work with a key database or certificate store, you need to configure a
database for cryptlib to use. Under Windows, go to the Control Panel and click on
the ODBC/ODBC32 item. Click on “Add” and select the ODBC data source (that is,
the database type) that you want to use. If it’s on the local machine, this will
probably be an Access database, if it’s a centralised database on a network this will
probably be SQL Server. Once you’ve selected the data source type, you need to give
it a name for cryptlib to use. “Public Keys” is a good choice (the self-test code uses
two sources called testkeys and testcertstore during the self-test procedure, and
will create these itself if possible). In addition you may need to set up other
parameters like the server that the database is located on and other access
information. Once the data source is set up, you can access it as a CRYPT_-
KEYSET_ODBC keyset using the name that you’ve assigned to it.
Under Unix or similar systems the best way to work with a key database or certificate
store is to use the ODBC interface, either via a layered driver such as unixODBC or
iODBC, or directly via interfaces such as MyODBC. Alternatively, you can use the
cryptlib generic database interface to compile database-specific support code directly
into cryptlib, or the database network plugin capability to make a network connection
to a database server such as IBM DB2, Informix, Ingres, Oracle, Postgres, or Sybase.
The easiest interface to use is the ODBC one, which hides all of the low-level
database interface details. The ODBC configuration process follows the same pattern
as the one given above for ODBC under Windows, with OS-specific variations
depending on the platform that you’re running it under. You can enable the use of the
ODBC interface using the USE_ODBC define before you build cryptlib, and if you’re
not using Windows (which uses dynamic binding to the ODBC interface) you need to
link the ODBC client library into your executable (on most systems the cryptlib build
scripts will take care of this automatically).
For Unix and Unix-like systems the two most common ODBC implementations are
unixODBC and iODBC, although a variety of other products are also available, and
some databases have native ODBC support, examples being MySQL (via MyODBC)
and IBM DB2. These interfaces support a wide range of commercial database
including AdabasD, IBM DB2, Informix, Ingres, Interbase, MySQL, Oracle,
Postgres, and Sybase. unixODBC uses the ODBCConfig GUI application to
configure data sources and drivers in a manner identical to the standard Windows
interface, and also provides the odbcinst CLI utility to configure data sources and
drivers. odbcinst can be used to automatically install and configure database drivers
for ODBC using template files that contain information about the driver such as the
location of the driver binaries, usually somewhere under /usr/local. For example to
configure the Oracle drivers for ODBC using a prepared template file you’d use:
odbcinst –i –d –f oracle.tmpl
iODBC provides drivers as platform-specific binaries that are installed using the
iODBC installation shell scripts. See the documentation for the particular ODBC
interface that you’re using for more information on installation and configuration
issues.
If you don’t want to use the ODBC interface, you can either compile database-
specific interface code directly into cryptlib or use the database network plugin
capability to make a network connection to a database server. To use cryptlib’s
generic database interface you need to define USE_DATABASE when you build
Recommended Reading 25
cryptlib and add the appropriate interface code to communicate with the database
back-end of your choice, as described in “Database and Networking Plugins” on page
286. In addition you need to link the database library or libraries (for example
libmysql.a) into your executable.
To use the database plugin capability to make a network connection to a database
server such as Informix, Ingres, Oracle, Postgres, or Sybase, you need to create the
appropriate plugin for your server as described in “Database and Networking
Plugins” on page 286.
If you need to use a database keyset on an embedded system, you can use a system
like the SQLite embedded database engine, http://sqlite.org/. SQLite is a self-
contained, embeddable, zero-configuration SQL database engine that provides all of
the capabilities needed by cryptlib database keysets.
Configuration Issues
For compatibility with existing deployed code, cryptlib supports a wide variety of
encryption, signature, and hash algorithms, key types, and security mechanisms.
Some of these backwards-compatible items are obsolete, unsound, or even entirely
broken. For this reason the encryption algorithms RC2, RC4, and Skipjack, the hash
algorithms MD2 and MD4, and the SSHv1 protocol, are disabled by default. If you
want to enable these obsolete and insecure items, you can do so via the cryptlib
configuration file misc/config.h. Note that by enabling these unsafe items, you are
voiding cryptlib’s security guarantees and agree to indemnify the cryptlib authors
against any claims or losses from any problems that may arise. In other words you
really, really shouldn’t do this.
cryptlib also contains two algorithms, IDEA and RC5, that may be covered by patents
in some countries. If you’re unsure over whether you can use the algorithms, you
should disable them as described below. Note that disabling IDEA will remove the
ability to read PGP 2 keys and messages, since this version requires the use of the
IDEA algorithm for en/decryption of data.
Customised and Cut-down cryptlib Versions
In some cases you may want to customise the cryptlib build or create a cut-down
version that omits certain capabilities in order to reduce code size for constrained
environments. You can do this by editing the configuration build file misc/config.h,
which allows almost every part of cryptlib’s functionality to be selectively enabled or
disabled (some functionality is used by all of cryptlib and can’t be disabled). Each
portion of functionality is controlled by a USE_name define, by undefining the value
before you build cryptlib the named functionality will be removed. For example,
undefining USE_SSH1 would disable the use of SSHv1 (this is disabled by default,
since it’s been superseded by SSHv2); undefining USE_SKIPJACK would disable
the use of the Skipjack algorithm (this is also disabled by default, since it’s obsolete
and no longer considered secure). In addition you can use the build file to disable the
use of the two patented algorithms IDEA and RC5 (see “Algorithms” on page 293 for
more information on whether these two patents affect your use of cryptlib) by
undefining USE_PATENTED_ALGORITHMS. More details on tuning cryptlib’s
size and capabilities (particularly for use in embedded systems) is given in
“Embedded Systems” on page 278.
Debug vs. Release Versions of cryptlib
cryptlib can be built in one of two forms, a debug version and a release version. The
main difference between the two is that the release version is built with the NDEBUG
value defined, which disables the large number of internal consistency checks that are
present in the debug build of cryptlib. These consistency checks are used to catch
conditions such as inappropriate error codes being returned from internal functions,
invalid data values being passed to functions inside cryptlib, configuration errors, and
general sanity checks that ensure that everything is operating as it should. If one of
these internal checks is triggered, cryptlib will throw an exception and display an
Installation
26
error message indicating that an assertion in the code has failed. These assertions are
useful for tracking down areas of code that may need revision in later releases.
If you don’t want to see these diagnostic messages, you should build cryptlib with the
NDEBUG value defined (this is the default under Unix and is done automatically
under Windows when you build a release version of the code with Visual C++).
Building a version in this manner will disable the extra consistency checks that are
present in the debug version so that, for example, error conditions will be indicated
by cryptlib returning an error code for a function call rather than throwing an
exception. This will have the slight downside that it’ll make tracking the exact
location of a problem a bit more complex, since the error code which is returned
probably won’t be checked until the flow of execution has progressed a long way
from where the problem was detected. On the other hand the release version of the
code is significantly smaller than the debug version.
As always, if you’re working with a debug build of the code and perform an
operation that triggers an internal consistency check you should report the details and
the code necessary to recreate it to the cryptlib developers in order to allow the
exception condition to be analysed and corrected.
cryptlib Version Information
cryptlib uses 3-digit version numbers, available at runtime through the configuration
options CRYPT_OPTION_INFO_MAJORVERSION, CRYPT_OPTION_INFO_-
MINORVERSION, and CRYPT_OPTION_INFO_STEPPING, and at compile time
through the define CRYPTLIB_VERSION. CRYPTLIB_VERSION contains the
current version as a 3-digit decimal value with the first digit being the major version
number (currently 3), the second digit being the minor version number, and the third
digit being the update or stepping number. For example, cryptlib version 3.2.1 would
have a CRYPTLIB_VERSION value of 321.
All cryptlib releases with the same stepping version number are binary-compatible.
This means that if you move from (for example) cryptlib version 3.2.1 to 3.2.2, all
you need to do is replace the cryptlib DLL or shared library to take advantage of new
cryptlib features and updates. All cryptlib releases with the same minor version
number are source-compatible, so that if you move from (for example) 3.2.1 to 3.3.5,
you need to recompile your application to match new features in cryptlib.
Support for Vendor-specific Algorithms
cryptlib supports the use of vendor-specific algorithm types with the predefined
values CRYPT_ALGO_VENDOR1, CRYPT_ALGO_VENDOR2, and
CRYPT_ALGO_VENDOR3. For each of the algorithms you use, you need to add a
call to initialise the algorithm capability information to device/system.c alongside
the existing algorithm initialisation, and then provide your implementation of the
algorithm to compile and link into cryptlib. When you rebuild cryptlib with the
preprocessor define USE_VENDOR_ALGOS set, the new algorithm types will be
included in cryptlib’s capabilities.
For example if you wanted to add support for the Foo256 cipher to cryptlib you
would create the file vendalgo.c containing the capability definitions and then
rebuild cryptlib with USE_VENDOR_ALGOS defined. The Foo256 algorithm
would then become available as algorithm type CRYPT_ALGO_VENDOR1.
Recommended Reading 27
cryptlib Basics
cryptlib works with two classes of objects, container objects and action objects. A
container object is an object that contains one or more items such as data, keys or
certificates. An action object is an object which is used to perform an action such as
encrypting or signing data. The container types used in cryptlib are envelopes (for
data), sessions (for communications sessions), keysets (for keys), and certificates (for
attributes such as key usage restrictions and signature information). Container
objects can have items such as data or public/private keys placed in them and
retrieved from them. In addition to containing data or keys, container objects can
also contain other objects that affect the behaviour of the container object. For
example pushing an encryption object into an envelope container object will result in
all data which is pushed into the envelope being encrypted or decrypted using the
encryption object.
Encryption contexts are the action objects used by cryptlib. Action objects are used
to act on data, for example to encrypt or decrypt a piece of data or to digitally sign or
check the signature on a piece of data.
The usual mechanism for processing data is to use an envelope or session container
object. The process of pushing data into an envelope and popping the processed data
back out is known as enveloping the data. The reverse process is known as de-
enveloping the data. Session objects work in a similar manner, but are used to
encapsulate a secure session with a remote client or server rather than a local data
transformation. The first section of this manual covers the basics of enveloping data,
which introduces the enveloping mechanism and covers various aspects of the
enveloping process such as processing data streams of unknown length and handling
errors. Once you have the code to perform basic enveloping in place, you can add
extra functionality such as password-based data encryption to the processing. After
the basic concepts behind enveloping have been explained, more advanced techniques
such as public-key based enveloping and digital signature enveloping for S/MIME
and PGP are covered.
Session objects are very similar to envelope objects except that they represent a
communications session with a remote client or server. The next section covers the
use of session objects for protocols such as SSL, TLS, and SSH to secure
communications or work with protocols such as CMS, SCEP, RTCS, OCSP, and TSP
that handle functions such as certificate status information and timestamping.
The use of public keys for enveloping requires the use of key management functions,
and the next section covers key generation and storing and retrieving keys from
keyset objects and crypto devices. The public portions of public/private key pairs are
typically managed using X.509 certificates and certificate revocation lists. The next
sections cover the management of certificates including certificate issue, certificate
status checking, and certificate revocation list (CRL) creation and checking, as well
as the full CA management process. This covers the full key life cycle from creation
through certification to revocation and/or destruction.
So far all of the objects that have been covered are high-level container objects. The
next section covers the creation of low-level action objects that you can either push
into a container object or apply directly to data, including the various ways of loading
or generating keys into them. The next sections explain how to apply the action
objects to data and cover the process of encryption, key exchange, and signature
generation and verification, working at a much lower level than the enveloping or
session interface.
The next sections cover certificates and certificate-like objects in more detail than the
earlier ones, covering such topics as DN structures, certificate chains, trust
management, and certificate extensions. This deals with certificates at a very low
level at which they’re rather harder to manage than with the high-level certificate
management functions.
cryptlib Basics
28
The next section covers the use of encryption devices such as smart cards, crypto
devices, HSMs, and Fortezza cards, and explains how to use them to perform many of
the tasks covered in previous sections. Finally, the last sections cover miscellaneous
topics such as random number management, the cryptlib configuration database, key
database and network plugins, and use in embedded systems.
Programming Interfaces
cryptlib provides three levels of interface, of which the highest-level one is the easiest
to use and therefore the recommended one. At this level cryptlib works with
envelope and session container objects, an abstract object into which you can insert
and remove data which is processed as required while it is in the object. Using
envelopes and session objects requires no knowledge of encryption or digital
signature techniques. At an intermediate level, cryptlib works with encryption action
objects, and requires some knowledge of encryption techniques. In addition you will
need to handle some of the management of the encryption objects yourself. At the
very lowest level cryptlib works directly with the encryption action objects and
requires you to know about algorithm details and key and data management methods.
Before you begin you should decide which interface you want to use, as each one has
its own distinct advantages and disadvantages. The three interfaces are:
High-level Interface
This interface requires no knowledge of encryption and digital signature techniques,
and is easiest for use with languages like Visual Basic and Java that don’t interface to
C data structures very well. The container object interface provides services to create
and destroy envelopes and secure sessions, to add security attributes such as
encryption information and signature keys to a container object, and to move data
into and out of a container. Because of its simplicity and ease of use, it’s strongly
recommended that you use this interface if at all possible.
Mid-level Interface
This interface requires some knowledge of encryption and digital signature
techniques. Because it handles encoding of things like session keys and digital
signatures but not of the data itself, it’s better suited for applications that require high-
speed data encryption, or encryption of many small data packets (such as an
encrypted terminal session). The mid-level interface provides services such as
routines to export and import encrypted keys and to create and check digital
signatures. The high-level interface is built on top of this interface.
Low-level Interface
This interface requires quite a bit of knowledge of encryption and digital signature
techniques. It provides a direct interface to the raw encryption capabilities of
cryptlib. The only reason for using these low-level routines is if you need them as
building blocks for your own custom encryption protocol. Note though that cryptlib
is designed to benefit the application of encryption in standard protocols and not the
raw use of crypto in home-made protocols. Getting such security protocols right is
very difficult, with many “obvious” and “simple” approaches being quite vulnerable
to attack. This is why cryptlib encourages the use of vetted security protocols, and
does not encourage roll-your-own security mechanisms. In particular if you don’t
know what PKCS #1 padding is, what CBC does, or why you need an IV, you
shouldn’t be using this interface.
The low-level interface serves as an interface to a range of plug-in encryption
modules that allow encryption algorithms to be added in a fairly transparent manner,
with a standardised interface allowing any of the algorithms and modes supported by
cryptlib to be used with a minimum of coding effort. As such the main function of
the action object interface is to provide a standard, portable interface between the
underlying encryption routines and the user software. The mid-level interface is built
on top of this interface.
Objects and Interfaces 29
Objects and Interfaces
The cryptlib object types are as follows:
Type Description
CRYPT_CERTIFICATE A key certificate objects that usually contain a
key certificate for an individual or organisation
but can also contain other information such as
certificate chains or digital signature attributes.
CRYPT_CONTEXT A encryption context objects that contain
encryption, digital signature, hash, or MAC
information.
CRYPT_DEVICE A device object that provide a mechanism for
working with crypto devices such as crypto
hardware accelerators and PCMCIA and smart
cards.
CRYPT_ENVELOPE An envelope container object that provide an
abstract container for performing encryption,
signing, and other security-related operations on
an item of data.
CRYPT_KEYSET A key collection container object that contain
collections of public or private keys.
CRYPT_SESSION A secure session object that manage a secure
session with a server or client.
These objects are referred to via arbitrary integer values, or handles, which have no
meaning outside of cryptlib. All data pertaining to an object is managed internally by
cryptlib, with no outside access to security-related information being possible. There
is also a generic object handle of type CRYPT_HANDLE which is used in cases
where the exact type of an object is not important. For example most cryptlib
functions that require keys can work with either encryption contexts or key certificate
objects, so the objects they use have a generic CRYPT_HANDLE which is equivalent
to either a CRYPT_CONTEXT or a CRYPT_CERTIFICATE.
Objects and Attributes
Each cryptlib object has a number of attributes of type CRYPT_ATTRIBUTE_TYPE
that you can get, set, and in some cases delete. For example an encryption context
would have a key attribute, a certificate would have issuer name and validity
attributes, and an envelope would have attributes such as passwords or signature
information, depending on the type of the envelope. Most cryptlib objects are
controlled by manipulating these attributes.
The attribute classes are as follows:
Type Description
CRYPT_ATTRIBUTE_name Generic attributes that apply to all objects.
CRYPT_CERTINFO_name Certificate object attributes.
CRYPT_CTXINFO_name Encryption context attributes.
CRYPT_DEVINFO_name Crypto device attributes.
CRYPT_ENVINFO_name Envelope attributes.
CRYPT_KEYINFO_name Keyset attributes.
CRYPT_OPTION_name cryptlib-wide configuration options.
CRYPT_PROPERTY_name Object properties.
cryptlib Basics
30
Type Description
CRYPT_SESSINFO_name Session attributes.
Some of the attributes apply only to a particular object type but others may apply
across multiple objects. For example a certificate contains a public key, so the key
size attribute, which is normally associated with a context, would also apply to a
certificate. To determine the key size for the key in a certificate, you would read its
key size attribute as if it were an encryption context.
Attribute data is either a single numeric value or variable-length data consisting of a
(data, length) pair. Numeric attribute values are used for objects, boolean values and
integers. Variable-length data attribute values are used for text strings, binary data
blobs, and representations of time using the ANSI/ISO standard seconds-since-1970
format.
Interfacing with cryptlib
All necessary constants, types, structures, and function prototypes are defined in a
language-specific header file as described below. You need to use these files for each
module that makes use of cryptlib. Although many of the examples given in this
manual are for C/C++ (the more widely-used ones are given for other languages as
well), they apply equally for the other languages.
All language bindings for cryptlib are provided in the bindings subdirectory. Before
you can use a specific language interface, you may need to copy the file(s) for the
language that you’re using into the cryptlib main directory or the directory containing
the application that you’re building. Alternatively, you can refer to the file(s) in the
bindings directory by the absolute pathname.
Initialisation
Before you can use any of the cryptlib functions, you need to call the cryptInit
function to initialise cryptlib. You also need to call its companion function cryptEnd
at the end of your program after you’ve finished using cryptlib. cryptInit initialises
cryptlib for use, and cryptEnd performs various cleanup functions including
automatic garbage collection of any objects you may have forgotten to destroy. You
don’t have to worry about inadvertently calling cryptInit multiple times (for example
if you’re calling it from multiple threads), it will handle the initialisation correctly.
However you should only call cryptEnd once when you’ve finished using cryptlib.
If you call cryptEnd and there are still objects in existence, it will return CRYPT_-
ERROR_INCOMPLETE to inform you that there were leftover objects present.
cryptlib can tell this because it keeps track of each object so that it can erase any
sensitive data that may be present in the object (cryptEnd will return a CRYPT_-
ERROR_INCOMPLETE error to warn you, but will nevertheless clean up and free
each object for you).
To make the use of cryptEnd in a C or C++ program easier, you may want to use the
C atexit() function or add a call to cryptEnd to a C++ destructor in order to have
cryptEnd called automatically when your program exits.
If you’re going to be doing something that needs encryption keys (which is pretty
much everything), you should also perform a randomness poll fairly early on to give
cryptlib enough random data to create keys:
cryptAddRandom( NULL, CRYPT_RANDOM_SLOWPOLL );
Randomness polls are described in more detail in “Random Numbers” on page 270.
The randomness poll executes asynchronously, so it won’t stall the rest of your code
while it’s running. The one possible exception to this polling on start-up is when
you’re using cryptlib as part of a larger application where you’re not certain that
cryptlib will actually be used. For example a PHP script that’s run repeatedly from
the command line may only use the encryption functionality on rare occasions (or not
at all), so that it’s better to perform the slow poll only when it’s actually needed rather
than unconditionally every time the script is invoked. This is a somewhat special
Interfacing with cryptlib 31
case though, and normally it’s better practice to always perform the slow poll on
start-up.
As the text above mentioned, you should initialise cryptlib when your program first
starts and shut it down when your program is about to exit, rather than repeatedly
starting cryptlib up and shutting it down again each time you use it. Since cryptlib
consists of a complete crypto operating system with extensive initialisation, internal
security self-tests, and full resource management, repeatedly starting and stopping it
will unnecessarily consume resources such as processor time during each
initialisation and shutdown. It can also tie up host operating system resources if the
host contains subsystems that leak memory or handles (under Windows, ODBC and
LDAP are particularly bad, with ODBC leaking memory and LDAP leaking handles.
DNS is also rather leaky — this is one of the reasons why programs like web
browsers and FTP clients consume memory and handles without bounds). To avoid
this problem, you should avoid repeatedly starting up and shutting down cryptlib:
Right Wrong
cryptInit();
serverLoop:
process data;
cryptEnd();
serverLoop:
cryptInit();
process data;
cryptEnd();
C / C++
To use cryptlib from C or C++ you would use:
#include "cryptlib.h"
cryptInit();
/* Calls to cryptlib routines */
cryptEnd();
C# / .NET
To use cryptlib from C# / .NET, add cryptlib.cs to your .NET project and the cryptlib
DLL to your path, and then use:
using cryptlib;
crypt.Init();
// Calls to cryptlib routines
crypt.End();
If you’re using a .NET language other than C# (for example VB.NET), you’ll need to
build cryptlib.cs as a class library first. From Visual Studio, create a new C# project
of type Class Library, add cryptlib.cs to it, and compile it to create a DLL. Now go
to your VB project and add the DLL as a Reference. The cryptlib classes and
methods will be available natively using VB (or whatever .NET language you’re
using).
All cryptlib functions are placed in the crypt class, so that standard cryptlib
functions like:
cryptSetAttribute( cryptContext, CRYPT_CTXINFO_KEYSIZE, 1024 / 8 );
become:
crypt.SetAttribute( cryptContext, crypt.CTXINFO_KEYSIZE, 1024 / 8 );
In general when calling cryptlib functions you can use Strings wherever the cryptlib
interface requires a null-terminated C string, and byte arrays wherever the cryptlib
interface requires binary data.
Instead of returning a status value like the native C interface, the .NET version throws
CryptException for error status returns, and returns integer or string data return
values as the return value:
cryptlib Basics
32
value = crypt.GetAttribute( cryptContext, crypt.CTXINFO_ALGO );
stringValue = crypt.GetAttributeString( cryptContext,
crypt.CTXINFO_ALGO_NAME );
Delphi
To use cryptlib from Delphi, add the cryptlib DLL to your path and then use:
implementation
uses cryptlib;
cryptInit;
{ Calls to cryptlib routines }
cryptEnd;
end;
The Delphi interface to cryptlib is otherwise mostly identical to the standard C/C++
one.
Java
To use cryptlib with Java, put cryptlib.jar on your classpath and use System.-
LoadLibrary() to load the cryptlib shared library. You can then use:
import cryptlib.*;
class Cryptlib
{
public static void main( String[] args )
{
System.loadLibrary( "cl" ); // cryptlib library name
try
{
crypt.Init();
//Calls to cryptlib routines
crypt.End();
}
catch( CryptException e )
{
// cryptlib returned an error
e.printStackTrace();
}
}
};
All cryptlib functions are placed in the crypt class, so that standard cryptlib
functions like:
cryptSetAttribute( cryptContext, CRYPT_CTXINFO_KEYSIZE, 1024 / 8 );
become:
crypt.SetAttribute( cryptContext, crypt.CTXINFO_KEYSIZE, 1024 / 8 );
In general when calling cryptlib functions you can use Java strings wherever the
cryptlib interface requires a null-terminated C string, and Java byte arrays wherever
the cryptlib interface requires binary data. In addition as of JDK 1.4 there is a
nio.ByteBuffer class that can be “direct”, which provides a more efficient
alternative to standard byte arrays since there’s no need to perform any copying.
Instead of returning a status value like the native C interface, the JNI version throws
CryptException for error status returns, and returns integer or string data return
values as the return value:
value = crypt.GetAttribute( cryptContext, crypt.CTXINFO_ALGO );
stringValue = crypt.GetAttributeString( cryptContext,
crypt.CTXINFO_ALGO_NAME );
Interfacing with cryptlib 33
Python
To build the Python interface to cryptlib, run python setup.py install to
build and install the python.c extension module. On a Unix platform you may need
to create a symlink from cl to the actual shared library before you do this. Once
you’ve done this you can use:
from cryptlib_py import *
cryptInit()
# Calls to cryptlib routines
cryptEnd()
Tcl
To use cryptlib from Tcl, you use the Cryptkit extension. Cryptkit is a stubs-enabled
extension that can be used with any modern Tcl interpreter (at least, Tcl 8.4 or later).
To build Cryptkit you’ll need a copy of Tcl that can interpret Starkits, either Tclkit,
the single file Tcl/Tk executable available from
http://www.equi4.com/pub/tk, or ActiveTcl from
http://www.activestate.com. You’ll also need to download the Critcl
Starkit from http://mini.net/sdarchive/critcl.kit, and make sure
that the current directory contains cryptlib.h and a copy of the cryptlib static library,
named libcl_$platfom.a, where $platform is the current platform name as provided
by the Critcl platform command. For example under x86 Linux the library would be
called libcl_Linux-x86.a. Then run the following Critcl command:
critcl -pkg cryptkit
This will leave you with a lib directory containing the information ready for use in
any Tcl application. Once you’ve done this you can use:
package require Cryptkit
cryptInit
# Calls to cryptlib routines
cryptEnd
Since Tcl objects already contain length information, there’s no need to pass length
parameters to cryptlib function calls. This applies for the AddCertExtension,
CheckSignature, CheckSignatureEx, CreateSignature, CreateSignatureEx, Decrypt,
Encrypt, ExportCert, ExportKey, ExportKeyEx, GetCertExtension, ImportKey,
PushData, and SetAttributeString functions.
Visual Basic
To use cryptlib from Visual Basic you would use:
' Add cryptlib.bas to your project
cryptInit
' Calls to cryptlib routines
cryptEnd
The Visual Basic interface to cryptlib is otherwise mostly identical to the standard
C/C++ one.
Return Codes
Every cryptlib function returns a status code to tell you whether it succeeded or
failed. If a function executes successfully, it returns CRYPT_OK. If it fails, it
returns one of the status values detailed in “Error Handling” on page 273. The
sample code used in this manual omits the checking of status values for clarity, but
when using cryptlib you should check return values, particularly for critical functions
cryptlib Basics
34
such as any that perform active crypto operations like processing data in envelopes,
activating and using secure sessions, signing and checking certificates, and
encryption and signing in general.
The previous initialisation code, rewritten to include checking for returned status
values, is:
int status;
status = cryptInit();
if( status != CRYPT_OK )
/* cryptlib initialisation failed */;
/* Calls to cryptlib routines */
status = cryptEnd();
if( status != CRYPT_OK )
/* cryptlib shutdown failed */;
The C/C++ versions of cryptlib provide the cryptStatusOK() and
cryptStatusError() macros to make checking of these status values easier.
The C#, Java, and Python versions throw exceptions of type CryptException
instead of returning error codes. These objects contain both the status code and an
English error message. In C# the CryptException class has Status and Message
properties:
try
{
crypt.Init();
crypt.End();
}
catch( CryptException e )
{
int status = e.Status;
String message = e.Message;
}
In Java the CryptException class has getStatus() and getMessage()
accessors:
try
{
crypt.Init();
crypt.End();
}
catch( CryptException e )
{
int status = e.getStatus();
String message = e.getMessage();
}
In Python the exception value is a tuple containing the status code, then the message:
try:
cryptInit()
cryptEnd()
except CryptException, e:
status, message = e
Working with Object Attributes
All object attributes are read, written, and deleted using a common set of functions:
cryptGetAttribute/cryptGetAttributeString to get the value of an attribute,
cryptSetAttribute/cryptSetAttributeString to set the value of an attribute, and
cryptDeleteAttribute to delete an attribute. Attribute deletion is only valid for a
small subset of attributes for which it makes sense, for example you can delete the
validity date attribute from a certificate before the certificate is signed but not after
it’s signed, and you can never delete the algorithm-type attribute from an encryption
context.
Working with Object Attributes 35
cryptGetAttribute and cryptSetAttribute take as argument an integer value or a
pointer to a location to receive an integer value:
int keySize;
cryptSetAttribute( cryptEnvelope, CRYPT_ENVINFO_PUBLICKEY, cryptKey );
cryptGetAttribute( cryptContext, CRYPT_CTXINFO_KEYSIZE, &keySize );
cryptGetAttributeString and cryptSetAttributeString take as argument a pointer
to the data value to get or set and a length value or pointer to a location to receive the
length value:
char emailAddress[ 128 ]
int emailAddressLength;
cryptSetAttributeString( cryptEnvelope, CRYPT_ENVINFO_PASSWORD,
"1234", 4 );
cryptGetAttributeString( cryptCertificate, CRYPT_CERTINFO_RFC822NAME,
emailAddress, &emailAddressLength );
This leads to a small problem: How do you know how big to make the buffer? The
answer is to use cryptGetAttributeString to tell you. If you pass in a null pointer
for the data value, the function will set the length value to the size of the data, but not
do anything else. You can then use code like:
char *emailAddress;
int emailAddressLength;
cryptGetAttributeString( cryptCertificate, CRYPT_CERTINFO_RFC822NAME,
NULL, &emailAddressLength );
emailAddress = malloc( emailAddressLength );
cryptGetAttributeString( cryptCertificate, CRYPT_CERTINFO_RFC822NAME,
emailAddress, &emailAddressLength );
to obtain the data value. In most cases this two-step process isn’t necessary, the
standards that cryptlib conforms to generally place limits on the size of most
attributes so that cryptlib will never return more data than the fixed limit. For
example most strings in certificates are limited to a maximum length set by the
CRYPT_MAX_TEXTSIZE constant. More information on these sizes is given with
the descriptions of the different attributes.
The Visual Basic version is:
Dim emailAddress as String
Dim emailAddressLength as Integer
cryptGetAttributeString cryptCertificate, CRYPT_CERTINFO_RFC822NAME, _
0, emailAddressLength
emailAddress = String( emailAddressLength, vbNullChar )
cryptGetAttributeString cryptCertificate, CRYPT_CERTINFO_RFC822NAME, _
emailAddress, emailAddressLength
In Python you can use cryptGetAttributeString and cryptSetAttributeString as
usual, or use a shortcut syntax to make accessing attributes less verbose. The normal
syntax follows the C form but migrates the integer output values (the length from
cryptGetAttributeString or the output value from cryptGetAttribute) to return
values, and doesn’t require a length for cryptSetAttributeString:
from array import *
emailAddress = array( 'c', 'x' * 128 )
cryptSetAttributeString( cryptEnvelope, CRYPT_ENVINFO_PASSWORD,
"1234" )
emailAddressLength = cryptGetAttributeString( cryptCertificate,
CRYPT_CERTINFO_RFC822NAME, emailAddress )
The shortcut syntax allows you to get/set attributes as if they were integer and string
members of the object (without the CRYPT_ prefix):
cryptEnvelope.ENVINFO_PASSWORD = "1234"
emailAddress = cryptCertificate.CERTINFO_RFC822NAME
cryptlib Basics
36
Just as with Python, C# and Java also migrate returned data to return values. In the
C# and Java cases the string functions take byte arrays or Strings. When passing a
byte array, you can optionally specify an offset following it for
cryptGetAttributeString and an offset and length following it for
cryptSetAttributeString. There is also a special version of
cryptGetAttributeString that returns Strings for convenience:
crypt.SetAttributeString( cryptEnvelope, crypt.ENVINFO_PASSWORD,
"1234" );
String emailAddress = crypt.GetAttributeString( cryptCertificate,
crypt.CERTINFO_RFC822NAME );
Finally, cryptDeleteAttribute lets you delete an attribute in the cases where that’s
possible:
cryptDeleteAttribute( cryptCertificate, CRYPT_CERTINFO_VALIDFROM );
All access to objects and object attributes is enforced by cryptlib’s security kernel. If
you try to access or manipulate an attribute in a manner that isn’t allowed (for
example by trying to read a write-only attribute, trying to assign a string value to a
numeric attribute, trying to delete an attribute that can’t be deleted, trying to set a
certificate-specific attribute for an envelope, or some similar action) cryptlib will
return an error code to tell you that this type of access is invalid. If there’s a problem
with the object that you’re trying to manipulate, cryptlib will return CRYPT_-
ERROR_PARAM1 to tell you that the object handle parameter passed to the function
is invalid. If there’s a problem with the attribute type (typically because it’s invalid
for this object type) cryptlib will return CRYPT_ERROR_PARAM2. If there’s a
problem with the attribute value, cryptlib will return CRYPT_ERROR_PARAM3,
and if there’s a problem with the length (for the functions that take a length
parameter) cryptlib will return CRYPT_ERROR_PARAM4. If you try to perform an
attribute access which is disallowed (reading an attribute that can’t be read, writing to
or deleting a read-only attribute, or something similar) cryptlib will return
CRYPT_ERROR_PERMISSION.
Finally, if you try to access an attribute that hasn’t been initialised or isn’t present,
cryptlib will return CRYPT_ERROR_NOTINITED or CRYPT_ERROR_-
NOTFOUND, the only real distinction between the two is that the former is typically
returned for fixed attributes that haven’t had a value assigned to them yet while the
latter is returned for optional attributes that aren’t present in the object.
Attribute Types
Attribute values can be boolean or numeric values, cryptlib objects, time values, text
strings, or binary data:
Type Description
Binary A binary data string that can contain almost anything.
Boolean Flags that can be set to ‘true’ (any nonzero value) or ‘false’ (a
zero value), and control whether a certain option or operation
is enabled or not. For example the CRYPT_CERTINFO_CA
attribute in a certificate controls whether a certificate is
marked as being a CA certificate or not. Note that cryptlib
uses the value 1 to represent ‘true’, some languages may
represent this by the value –1.
Numeric A numeric constant such as an integer value or a bitflag. For
example the CRYPT_CTXINFO_KEYSIZE attribute s
pecifies
the size of a key (in bytes) and the CRYPT_CERTINFO_-
CRLREASON attribute specifies a bitflag that indicates why a
CRL was issued.
Object A handle to a cryptlib object. For example the CRYPT_-
CERTINFO_SUBJECTPUBLICKEYINFO attribute specifies
the public key to be added to a certificate.
Working with Object Attributes 37
Type Description
String A text string that contains information such as a name,
message, email address, or URL. Strings are encoded using
the standard system local character set, usually ASCII or
latin-1 or UTF-8 (depending on the system), however under
Windows CE, which is a Unicode environment, these are
Unicode strings. In (very rare) cases where the standard
system character set doesn’t support the characters used in the
string (for example when encoding Asian characters), the
characters used will be Unicode or widechars. For all intents
and purposes you can assume that all strings are encoded in
the standard character set that you’d normally use, cryptlib
will perform all conversions for you.
An example string attribute is CRYPT_CTXINFO_LABEL,
which contains a human-readable label used to identify private
keys.
The most frequently used text string components are those that
make up a certificate’s distinguished name, which identifies
the certificate owner. Most of these components are limited to
a maximum of 64 characters by the X.500 standard that covers
certificates and their components, and cryptlib provides the
CRYPT_MAX_TEXTSIZE constant for use with these
components (this value is also used for most other strings such
as
key labels). Since this value is specified in characters rather
than bytes, Unicode strings can be several times as long as this
value when their length is expressed in bytes, depending on
which data type the system uses to represent Unicode
characters.
Time
The ANSI/ISO C standard time value containing the local time
expressed as seconds since 1970. This is a binary (rather than
numeric) field, with the data being the time value (in C and
C++ this is a time_t, usually a signed long integer).
Due to the vagaries of international time zones and daylight
savings time adjustments, it isn’t possible to accurately
compare two local times from different time zones, or made
across a DST switch (consider for example a country
switching to DST, which has two 2am times while another
country only has one). Because of this ambiguity, times read
from objects such as certificates may appear to be out by an
hour or two.
Since most text strings have a fixed maximum length, you can use code like:
char commonName[ CRYPT_MAX_TEXTSIZE + 1 ];
int commonNameLength;
/* Retrieve the component and null-terminate it */
cryptGetAttributeString( cryptCertificate, CRYPT_CERTINFO_COMMONNAME,
commonName, &commonNameLength );
commonName[ commonNameLength ] = '\0';
to read the value, in this case the common name of a certificate owner.
Note the explicit addition of the terminating null character, since the text strings
returned aren’t null-terminated.
In Visual Basic this is:
Dim commonName As String
Dim commonNameLength As Long
cryptlib Basics
38
commonName = String( CRYPT_MAX_TEXTSIZE + 1 , vbNullChar )
cryptGetAttributeString cryptCertificate, CRYPT_CERTINFO_COMMONNAME, _
commonName, commonNameLength
commonName = Left( commonName, InStr( commonName, vbNullChar ) - 1 )
The description above assumes that the common name is expressed in a single-byte
character set. Since the values passed to cryptGetAttributeString and
cryptSetAttributeString are untyped, their length is given in bytes and not in
characters (which may not be byte-sized). For Unicode strings, you need to multiply
the size of the buffer by the size of a Unicode character on your system to get the
actual size to pass to the function, or divide by the size of a Unicode character to get
the number of characters returned. For example to perform the same operation as
above in a Unicode environment you’d use:
wchar_t commonName[ CRYPT_MAX_TEXTSIZE + 1 ];
int commonNameLength;
/* Retrieve the component and null-terminate it */
cryptGetAttributeString( cryptCertificate, CRYPT_CERTINFO_COMMONNAME,
commonName, &commonNameLength );
commonName[ commonNameLength / sizeof( wchar_t ) ] = L'\0';
Attribute Lists and Attribute Groups
Several of the container object types (certificates, envelopes, and sessions) contain
large collections of attributes that you can process as a list rather than having to
access each attribute individually by type. The list of attributes is managed through
the use of an attribute cursor that cryptlib maintains for each container object. You
can set or move the cursor either to an absolute position in the list of attributes or
relative to the current position.
Object attributes are usually grouped into collections of related attributes. For
example an envelope object may contain a group of attributes consisting of a
signature, the key that generated the signature, associated signing attributes such as
the time and data type being signed, and even a timestamp on the signature itself.
Similarly, a session object may have a group of attributes consisting of a server name,
server port, and server key. So instead of a straight linear list of attributes:
Object Attr Attr Attr Attr
the attributes are arranged by group:
Object
Group
Group
Group
Attr Attr Attr
Attr
Attr Attr
Some objects may contain multiple instances of attribute groups, each of which
contains its own set of attributes. For example an envelope could contain several
signature attribute groups, and each attribute group will contain its own signing keys,
certificates, signature information such as the signing time, and so on. One particular
instance of the abstract group/attribute view shown above would be:
Working with Object Attributes 39
Envelope
Signature
Signature
Signature
Key Sig.
Info
Time-
stamp
Key
Key Sig.
Info
In order to navigate across attribute groups, and across attributes within a group,
cryptlib provides the attribute cursor functionality described in the section that
follows. As well as moving the cursor back and forth across attribute groups and
attributes within the group, you can also position it directly on a group or attribute. In
the common case where only a single attribute group is present, for example an
envelope object that contains a single signature or a session object that contains user
information for a single user:
Envelope Signature
Key
Signature
Info
you can treat the attributes as a single flat list of attributes and not worry about the
hierarchical arrangement into groups.
Attribute Cursor Management
You can move the attribute cursor by setting an attribute that tells cryptlib where to
move it to. This attribute, either CRYPT_ATTRIBUTE_CURRENT_GROUP when
moving by attribute group or CRYPT_ATTRIBUTE_CURRENT when moving by
attribute within the current group, takes as value a cursor movement code that moves
the cursor either to an absolute position (the first or last group or attribute in the list)
or relative to its current position. The movement codes are:
Code Description
CRYPT_CURSOR_FIRST Move the cursor to the first group or attribute.
CRYPT_CURSOR_LAST Move the cursor to the last group or attribute.
CRYPT_CURSOR_NEXT Move the cursor to the next group or attribute.
CRYPT_CURSOR_PREV Move the cursor to the previous group or
attribute.
Moving by attribute group or attribute then works as follows:
Object
Group
Group
Group
Attr Attr Attr
Attr
Attr Attr
CRYPT_ATTRIBUTE_CURRENT_GROUP
CRYPT_ATTRIBUTE_CURRENT
cryptlib Basics
40
Note that CRYPT_ATTRIBUTE_CURRENT only moves the cursor within the
current group. Once you get to the start or end of the group, you need to use
CRYPT_ATTRIBUTE_CURRENT_GROUP to move on to the next one. Moving
the cursor from one group to another will reset the cursor position to the first attribute
within the group if it’s been previously moved to some other attribute within the
group. For example to move the cursor to the start of the first attribute group in a
certificate, you would use:
cryptSetAttribute( cryptCertificate, CRYPT_ATTRIBUTE_CURRENT_GROUP,
CRYPT_CURSOR_FIRST );
To advance the cursor to the start of the next group, you would use:
cryptSetAttribute( cryptCertificate, CRYPT_ATTRIBUTE_CURRENT_GROUP,
CRYPT_CURSOR_NEXT );
To advance the cursor to the next attribute in the current group, you would use:
cryptSetAttribute( cryptCertificate, CRYPT_ATTRIBUTE_CURRENT,
CRYPT_CURSOR_NEXT );
In some cases multiple instances of the same attribute can be present, in which case
you can use a third level of cursor movement, handled via the CRYPT_-
ATTRIBUTE_CURRENT_INSTANCE attribute, and relative cursor movement to
step through the different instances of the attribute. Since the use of multi-valued
attributes is rare, it’s safe to assume one value per attribute in most cases, so that
stepping through multiple attribute instances is unnecessary.
Once you’ve set the cursor position, you can work with the attribute group or attribute
at that position in the usual manner. To obtain the group or attribute type at the
current position, you would use:
CRYPT_ATTRIBUTE_TYPE groupID;
cryptGetAttribute( cryptCertificate, CRYPT_ATTRIBUTE_CURRENT_GROUP,
&groupID );
This example obtains the attribute group type, to obtain the attribute type you would
substitute CRYPT_ATTRIBUTE_CURRENT in place of CRYPT_ATTRIBUTE_-
CURRENT_GROUP. Attribute accesses are relative to the currently selected group,
so for example if you move the cursor in an envelope to a signature attribute group
and then read the signature key/certificate or signing time, it’ll be the one for the
currently-selected signature attribute group. Since there can be multiple signatures
present in an envelope, you can use this mechanism to read the signing information
for each of the ones that are present.
To delete the attribute group at the current cursor position you would use:
cryptDeleteAttribute( cryptCertificate,
CRYPT_ATTRIBUTE_CURRENT_GROUP );
Deleting the attribute group at the cursor position will move the cursor to the start of
the group that follows the deleted one, or to the start of the previous group if the one
being deleted was the last one present. This means that you can delete every attribute
group simply by repeatedly deleting the one under the cursor.
The attribute cursor provides a convenient mechanism for stepping through every
attribute group and attribute which is present in an object. For example to iterate
through every attribute group you would use:
if( cryptSetAttribute( cryptCertificate,
CRYPT_ATTRIBUTE_CURRENT_GROUP, CRYPT_CURSOR_FIRST ) == CRYPT_OK )
do
{
CRYPT_ATTRIBUTE_TYPE groupID;
/* Get the ID of the attribute group under the cursor */
cryptGetAttribute( cryptCertificate,
CRYPT_ATTRIBUTE_CURRENT_GROUP, &groupID );
Working with Object Attributes 41
/* Handle the attribute if required */
/* ... */
}
while( cryptSetAttribute( cryptCertificate,
CRYPT_ATTRIBUTE_CURRENT_GROUP, CRYPT_CURSOR_NEXT ) ==
CRYPT_OK );
The Visual Basic equivalent is:
Dim groupID As CRYPT_ATTRIBUTE_TYPE
If cryptSetAttribute( cryptCertificate, _
CRYPT_ATTRIBUTE_CURRENT_GROUP, CRYPT_CURSOR_FIRST ) == CRYPT_OK
Then
Do
' Get the type of the attribute group under the cursor
cryptGetAttribute cryptCertificate, CRYPT_ATTRIBUTE_CURRENT, _
groupID
' Handle the attribute if required
' ...
Loop While cryptSetAttribute( cryptCertificate, _
CRYPT_ATTRIBUTE_CURRENT_GROUP, CRYPT_CURSOR_NEXT ) == CRYPT_OK
End If
To extend this a stage further and iterate through every attribute in every group in the
object, you would use:
if( cryptSetAttribute( cryptCertificate,
CRYPT_ATTRIBUTE_CURRENT_GROUP, CRYPT_CURSOR_FIRST ) == CRYPT_OK )
do
{
do
{
CRYPT_ATTRIBUTE_TYPE attributeID;
/* Get the ID of the attribute under the cursor */
cryptGetAttribute( cryptCertificate, CRYPT_ATTRIBUTE_CURRENT,
&attributeID );
/* Handle the attribute if required */
/* ... */
}
while( cryptSetAttribute( cryptCertificate,
CRYPT_ATTRIBUTE_CURRENT, CRYPT_CURSOR_NEXT ) == CRYPT_OK );
}
while( cryptSetAttribute( cryptCertificate,
CRYPT_ATTRIBUTE_CURRENT_GROUP, CRYPT_CURSOR_NEXT ) ==
CRYPT_OK );
Note that iterating attribute by attribute works within the current attribute group, but
as mentioned earlier won’t jump from one group to the next — to do that, you need to
iterate by group.
You can also position the attribute cursor directly by telling cryptlib which attribute
you want to move the cursor to. For example to move the cursor in a certificate
object to the extended key usage attribute group you would use:
cryptSetAttribute( cryptCertificate, CRYPT_ATTRIBUTE_CURRENT_GROUP,
CRYPT_CERTINFO_EXTKEYUSAGE );
Usually the absolute cursor-positioning capability is only useful for certificate objects
where you know that certain attributes will be present and that only one instance of
the attribute will be present. For envelope and session objects you generally can’t tell
in advance which attributes will be present and it’s quite possible that multiple
attribute instances (such as multiple signatures on a envelope) will be present. In this
case selecting an attribute will only select the first one that’s present, so it’s better to
use the attribute cursor to walk the list to see what’s there.
Using this absolute cursor positioning in a variation of the attribute enumeration
operation given earlier, you can enumerate only the attributes of a single attribute
group (rather than all groups) by first selecting the group and then stepping through
the attributes in it. For example to read all of a certificate’s extended key usage types
you would use:
cryptlib Basics
42
if( cryptSetAttribute( cryptCertificate,
CRYPT_ATTRIBUTE_CURRENT_GROUP, CRYPT_CERTINFO_EXTKEYUSAGE ) ==
CRYPT_OK )
do
{
CRYPT_ATTRIBUTE_TYPE attributeID;
/* Get the ID of the attribute under the cursor */
cryptGetAttribute( cryptCertificate, CRYPT_ATTRIBUTE_CURRENT,
&attributeID );
}
while( cryptSetAttribute( cryptCertificate,
CRYPT_ATTRIBUTE_CURRENT, CRYPT_CURSOR_NEXT ) == CRYPT_OK );
Object Security
Each cryptlib object has its own security settings that affect the way that you can use
the object. You can set these attributes, identified by CRYPT_PROPERTY_name,
after you create an object to provide enhanced control over how it’s used. For
example on a system that supports threads you can bind an object to an individual
thread within a process so that only the thread that owns the object can see it. For any
other thread in the process, the object handle is invalid.
You can get and set an object’s properties using cryptGetAttribute and
cryptSetAttribute, passing as arguments the object whose property attribute you
want to change, the type of property attribute to change, and the attribute value or a
pointer to a location to receive the attribute’s value. The object property attributes
that you can get or set are:
Property/Description Type
CRYPT_PROPERTY_FORWARDCOUNT Numeric
The number of times an object can be forwarded (that is, the number of
times the ownership of the object can be changed). Each time the object’s
ownership is changed, the forwarding count decreases by one; once it
reaches zero, the object can’t be forwarded any further. For example if you
set this attribute’s value to 1 then you can forward the object to another
thread, but that thread can’t forward it further.
After you set this attribute (and any other security-related attributes), you
should set the CRYPT_PROPERTY_LOCKED attribute to ensure that it
can’t be changed later.
CRYPT_PROPERTY_HIGHSECURITY Boolean
This is a composite value that sets all general security-related attributes to
their highest security setting. Setting this value will make an object owned,
non-exportable (if appropriate), non-forwardable, and locked. Since this is a
composite value representing a number of separate attributes, its value can’t
be read or unset after being set.
CRYPT_PROPERTY_LOCKED Boolean
Locks the security-related object attributes so that they can no longer be
changed. You should set this attribute once you’ve set other security-related
attributes such as CRYPT_PROPERTY_FORWARDCOUNT.
This attribute is a write-once attribute, once you’ve set it can’t be reset.
CRYPT_PROPERTY_NONEXPORTABLE Boolean
Whether a key in an encryption action object can be exported from the object
in encrypted form. Normally only session keys can be exported, and only in
encrypted form, however in some cases private keys are also exported in
encrypted form when they can are saved to a keyset. By setting this attribute
you can make them non-exportable in any form (some keys, such as those
held in crypto devices, are non-exportable by default).
This attribute is a write-once attribute, once you’ve set it can’t be reset.
CRYPT_PROPERTY_OWNER Numeric
The identity of the thread that owns the object. The thread’s identity is
Object Security 43
Property/Description Type
specified using a value that depends on the operating system, but is usually a
thread handle or thread ID. For example under Windows 95/98/ME,
NT/2000/XP/Vista, and Windows CE the thread ID is the value returned by
the GetCurrentThreadID function, which returns a system-
wide unique
handle for the current thread.
You can also pass in a value of CRYPT_UNUSED, which unbinds the
object from the thread and makes it accessible to all threads in the process.
CRYPT_PROPERTY_USAGECOUNT Numeric
The number of times an action object can be used before it deletes itself and
becomes unusable. Every time an action object is used (for example when a
signature encryption object is used to create a signature), its usage count is
decremented; once the usage count reaches zero, the object can’t be used to
perform any further actions (although you can still perform non-action
operations such as reading its attributes).
This attribute is useful when you want to restrict the number of times an
object can be used by other code. For example, before you change the
ownership of a signature object to allow it to be used by another thread, you
would set the usage count to 1 to ensure that it can’t be used to sign arbitrary
numbers of messages or transactions. This eliminates a troubling security
problem with objects such as smart cards where, once a user has
authenticated themselves to the card, the software can ask the card to sign
arbitrary numbers of (unauthorised) transactions alongside the authorised
ones.
This attribute is a write-once attribute, once you’ve set it can’t be reset.
For example to create a triple DES encryption context in one thread and transfer
ownership of the context to another thread you would use:
CRYPT_CONTEXT cryptContext;
/* Create a context and claim it for exclusive use */
cryptCreateContext( &cryptContext, cryptUser, CRYPT_ALGO_3DES );
cryptSetAttribute( cryptContext, CRYPT_PROPERTY_OWNER, threadID );
/* Generate a key into the context */
cryptGenerateKey( cryptContext );
/* Transfer ownership to another thread */
cryptSetAttribute( cryptContext, CRYPT_PROPERTY_OWNER,
otherThreadID );
The other thread now has exclusive ownership of the context containing the loaded
key. If you wanted to prevent the other thread from transferring the context further,
you would also have to set the CRYPT_PROPERTY_FORWARDCOUNT property
to 1 (to allow you to transfer it) and then set the CRYPT_PROPERTY_LOCKED
attribute (to prevent the other thread from changing the attributes you’ve set).
Note that in the above code the object is claimed as soon as it’s created (and before
any sensitive data is loaded into it) to ensure that another thread isn’t given a chance
to use it when it contains sensitive data. The use of this type of object binding is
recommended when working with sensitive information under Windows 95/98/ME,
Windows NT/2000/XP/Vista, and Windows CE, since the Win32 API provides
several security holes whereby any process in the system may interfere with resources
owned by any other process in the system. The checking for object ownership which
is performed typically adds a few microseconds to each call, so in extremely time-
critical applications you may want to avoid binding an object to a thread. On the
other hand for valuable resources such as private keys, you should always consider
binding them to a thread, since the small overhead becomes insignificant compared to
the cost of the public-key operation.
Although the example shown above is for encryption contexts, the same applies to
other types of objects such as keysets and envelopes (although in that case the
cryptlib Basics
44
information they contain isn’t as sensitive as it is for encryption contexts). For
container objects that can themselves contain objects (for example keysets), if the
container is bound to a thread then any objects that are retrieved from it are also
bound to the thread. For example if you’re reading a private key from a keyset, you
should bind the keyset to the current thread after you open it (but before you read any
keys) so that any keys read from it will also automatically be bound to the current
thread. In addition if a key which is used to generate another key (for example the
key that imports a session key) is bound, then the resulting generated key will also be
bound.
On non-multithreaded systems, CRYPT_PROPERTY_OWNER and CRYPT_-
PROPERTY_FORWARDCOUNT have no effect, so you can include them in your
code for any type of system.
Role-based Access Control
cryptlib implements a form of access control called role-based access control or
RBAC in which operations specific to a certain user role can’t be performed by a user
acting in a different role. For example in many organisations a cheque can only be
issued by an accountant and can only be signed by a manager, which prevents a
dishonest accountant or manager from both issuing a cheque to themselves and then
signing it as well. This security measure is referred to as separation of duty, in which
it takes at least two people to perform a critical operation. Similarly, cryptlib uses
RBAC to enforce a strong separation of duty between various roles, providing the
same effect as the corporate accounting controls that prevent an individual from
writing themselves cheques.
cryptlib recognises a variety of user types or roles. The default user type has access
to most of the standard functions in cryptlib but can’t perform CA management
operations or specialised administrative functions that are used to manage certain
aspects of cryptlib’s operation. When you use cryptlib in the role of a standard user,
it functions as a normal crypto/security toolkit.
In addition to the standard user role, it’s also possible to use cryptlib in the role of a
security officer (SO), a special administrative user who can create new users and
perform other administrative functions but can’t perform general crypto operations
like a normal user. This provides a clear separation of duty between administrative
and end-user functionality.
Another role recognised by cryptlib is that of a certification authority that can make
use of cryptlib’s certificate management functionality but can’t perform general
administrative functions or non-CA-related crypto operations. Again, this provides a
clear separation of duty between the role of the CA and the role of a general user or
SO.
Managing User Roles
When a cryptlib object is created, it is associated with a user role which is specified at
creation time and can’t be accessed by any other user. For example if a private key is
created by a CA for signing certificates, it can’t be accessed by a normal user because
it’s only visible to the user acting in the role of the CA. Similarly, although a normal
user may be able to see a certificate store, only a CA user can use it for the purpose of
issuing certificates. The use of RBAC therefore protects objects from misuse by
unauthorised users.
The identity of the user who owns the object is specified as a parameter for the object
creation function. Throughout the rest of the cryptlib documentation this parameter is
denoted through the use of the cryptUser variable. Usually this parameter is set to
CRYPT_UNUSED to indicate that the user is acting in the role of a normal user and
doesn’t care about role-based controls. This is typically used in cases where there’s
only one cryptlib user, for example where cryptlib is running on an end-user PC (e.g.
Windows, Macintosh) or a multi-user system that provides each user with the illusion
of being on a single-user machine (e.g. Unix). In almost all cases therefore you’d
pass in CRYPT_UNUSED as the user identity parameter.
Role-based Access Control 45
In a few specialised cases where the user is acting in a role other than that of a normal
user the default user role isn’t enough. For example when you want to access a CA
certificate store you can’t use the role of a normal user to perform the access because
only a CA can manipulate a certificate store. This prevents a normal user from
issuing themselves certificates in the name of the CA and assorted other mischief
such as revoking another user’s certificate.
When acting in a different role than that of the default, normal user, you specify the
identity of the user whose role you’re acting in as a parameter of the object creation
function as before, this time passing in the handle of the user identity instead of
CRYPT_UNUSED. When the object is created, it is associated with the given user
and role instead of the default user. The creation and use of user objects is covered in
the next section.
Creating and Destroying Users and Roles
The following section is provided purely for forwards compatibility with functionality
included in future versions of cryptlib. For the current version of cryptlib the user
identity parameter should always be CRYPT_UNUSED since user object
management isn’t enabled in this version.
User objects can only be created and destroyed by an SO user, this being one of the
special administrative functions mentioned earlier that can only be performed by an
SO. You create a user object with cryptCreateUser, specifying the identity of the
SO who is creating the user object, type of user role that the object is associated with,
the name of the user, and a password that protects access to the user object:
CRYPT_USER cryptUser;
cryptCreateUser( &cryptUser, cryptSO, type, name, password );
The available user types or roles are:
Role Description
CRYPT_USER_CA A certification authority who can perform CA
management functions but can’t perform
general-purpose crypto operations.
CRYPT_USER_NORMAL A standard cryptlib user.
CRYPT_USER_SO A security officer who can perform
administrative functions such as creating or
deleting users but who can’t perform any other
type of operation.
For example to create a normal user object for “John Doe” with the password
“password” and a CA user object for “John’s Certificate Authority” with the
password “CA password” you would use:
CRYPT_USER cryptUser, cryptCAUser;
cryptCreateUser( &cryptUser, cryptSO, CRYPT_USER_NORMAL, "John Doe",
"password" );
cryptCreateUser( &cryptUser, cryptSO, CRYPT_USER_CA, "John's
Certification Authority", "CA password" );
Once a user object is created it can’t be used immediately because it’s still under the
nominal control of the SO who created it rather than the user it’s intended for. Before
it can be used, control over the object needs to be handed over to the user that it’s
intended for. After the object is created by the SO, it is said to be in the SO initialised
state. Any attempt to use an object when it’s in the SO initialised state will result in
cryptlib returning CRYPT_ERROR_NOTINITED.
To move the newly-created object into a usable state, it’s necessary to change its
password from the initial one set by the SO to one chosen by the user. Once this
change occurs, the object is moved into the user initialised state and is ready for use.
You can change the password from the initial one set by the SO to a user-chosen one
with cryptChangePassword:
cryptlib Basics
46
cryptChangePassword( cryptUser, oldPassword, newPassword );
When the password has been changed from the one set by the SO to the one chosen
by the user, the user object is ready for use.
User objects can also be destroyed by the SO who created them:
cryptDeleteUser( cryptUser, "John Doe" );
Miscellaneous Issues
This section contains additional information that may be useful when working with
cryptlib.
Multi-threaded cryptlib Operation
cryptlib is re-entrant and completely thread-safe (the threading model used is
sometimes known as the free-threading model), allowing it to be used with
multithreaded applications in systems that support threading. When you use cryptlib
in a multithreaded application, you should take standard precautions to ensure that a
single resource shared across multiple threads doesn’t become a bottleneck, with all
threads being forced to wait on a single shared object. For example if you’re
timestamping large numbers of messages then creating a single timestamping session
object (see “Secure Sessions” on page 96) and using that for all timestamping
services will result in all of the operations waiting on a single session object, which
can often take several seconds to turn around a transaction with a remote server. A
better option in this case would be to create a pool of timestamping session objects
and use the next free one when required.
A similar situation occurs with other objects such as crypto devices and keysets that
may be shared across multiple threads. For example cryptlib provides a facility for
automatically fetching a decryption key from a keyset in order to decrypt data (see
“Public-Key Encrypted Enveloping” on page 66). This is convenient when handling
one or two messages since cryptlib will automatically take care of all of the
processing for you, however if you’re processing large numbers of messages then the
need to read and decrypt the same private key for each message is very inefficient,
not only in terms of CPU overhead but also because every message must wait for
each of the previous messages to be processed before it gets its turn at the keyset.
A better alternative in this case is to read the private key from the keyset just once
and then use it with each envelope, rather than having each envelope read and decrypt
the key itself. Extending this even further, if you’re using a very large private key,
running on a slower processor, or processing large numbers of transactions, you may
want to instantiate multiple copies of the private-key object to avoid the single private
key again becoming a bottleneck.
In general most private-key operations, when performed on modern processors, are
fairly quick, so there’s no need to create large numbers of private-key objects for fear
of them becoming a bottleneck. In this case the primary bottleneck is the need to read
and decrypt the key for each message processed. However, when run on a multiple-
CPU system, you should make some attempt to match objects to CPUs — creating a
single private-key object on a four-CPU system guarantees that the overall
performance will be no better than that of a single-CPU system, since the single
object instance acts as a mutex that can only be acquired by one CPU at a time.
Standard programming practice for efficient utilisation of resources on multiprocessor
systems applies to cryptlib just as it does for other applications. Creating a pool of
objects that can be picked up and used as required would be one standard approach to
this problem. Some operating systems provide special support for this with functions
for thread pooling management. For example, Windows 2000 and XP provide the
QueueUserWorkItem function, which submits a work item to a thread pool for
execution when the next thread becomes available. Windows Vista includes an
enhanced version of the thread-pool API that replaces the basic QueueUserWork-
Item with a more conventional CreateThreadpoolWork/Submit-
ThreadpoolWork/CloseThreadpoolWork combination that provides better
control over thread pools, pool management, and pool cleanup.
Miscellaneous Issues 47
In order to protect against potential deadlocks when multiple threads are waiting on a
single object, cryptlib includes a watchdog timer that triggers after a certain amount
of time has been spent waiting on the object. Once this occurs, cryptlib will return
CRYPT_ERROR_TIMEOUT to indicate that an object is still in use after waiting for
it to become available. If you experience timeouts of this kind, you should check
your code to see if there are any bottlenecks due to a single object with a long
response time being shared by several fast-response-time objects. Note that timeouts
are also possible with normal cryptlib object use, for example when communicating
data over a slow or stalled network link, so a CRYPT_ERROR_TIMEOUT status
doesn’t automatically mean that the watchdog timer signalled a problem.
To help diagnose situations of this kind, the debug build of cryptlib will display on
the console output an indication that it waited on a particular object, along with the
object type that it waited on. You can use this information to identify potential
bottlenecks in your application.
Linux has a somewhat unusual threading implementation built around the clone()
system call that can lead to unexpected behaviour with some kernel and/or glibc
versions. Two common symptoms of glibc/kernel threading problems are phantom
processes (which are actually glibc-internal threads created via clone()) being left
behind when you application exits, and cryptlib’s internal consistency-checking
throwing an exception in the debug build when it detects an problem with threading.
If you run into either of these situations, you can try different glibc and/or kernel
versions to find a combination that works. Searching Internet newsgroups will
provide a wealth of information and advice on problems with glibc and Linux
threads.
Safeguarding Cryptographic Operations
Running cryptographic operations on general-purpose CPUs shared with other (often
untrusted) programs can expose them to risk if the other programs can closely
observe or even influence the behaviour of the crypto code. In addition it’s possible
for a remote system with the ability to precisely time network packet flows to deduce
information about crypto operations like a SSL/TLS handshake that result in network
traffic as the output of the crypto operation. The timing attacks only affect RSA
private-key operations, and only those operations that are directly observable by
another party, for example as a result of a network data exchange involving RSA
decryption or signing.
There are several countermeasures that you can take to avoid this problem. The
simplest approach is to use a different crypto mechanism that isn’t vulnerable to this
problem. By default cryptlib will try and use Diffie-Hellman key exchange in
SSL/TLS, which isn’t vulnerable to this type of attack because it uses a new random
value each time, making it impossible to get repeatable timing measurements. In
addition the cryptlib security kernel provides a good degree of protection since it
isolates the RSA crypto operations from external observation, making it quite
difficult to obtain timing information.
If you must use RSA in a manner in which its operation is visible to an external
observer, you can enable randomisation of the RSA operations (known as “blinding”)
in order to provide the same protection that comes built into Diffie-Hellman.
Enabling blinding adds a performance overhead of between two and five percent to
each RSA operation. You can enable blinding for RSA operations (and a few other
protection measures) by setting the CRYPT_OPTION_MISC_-
SIDECHANNELPROTECTION configuration option as described in “Working with
Configuration Options” on page 265.
Many modern CPUs include sophisticated diagnostic and monitoring facilities that
provide extensive insight into both the operation of the CPU and the data that it
processes. If untrusted processes are running on a CPU alongside ones performing
crypto operations, it may be possible for the untrusted processes to recover sensitive
data or even crypto keys using built-in CPU monitoring facilities. This can occur
even through indirect means such as observing memory access latencies for cached
cryptlib Basics
48
vs. un-cached data, or branch times for cached vs. un-cached branch target
information. Since the level of access provided by may of these diagnostic facilities
is almost at the level of an in-circuit emulator (ICE), there are no truly effective
defences against this level of threat.
If you’re using a CPU that provides this detailed monitoring capability and you’re
also working with sensitive data or crypto keys, and in particular the private keys
used in public-key encryption operations, you need to take precautions to ensure that
other code can’t misuse these monitoring capabilities to compromise your keys or
sensitive data. The simplest and most effective defence is “don’t do that, then”:
Don’t allow untrusted code to run alongside your crypto code (or any other code
processing sensitive information for that matter).
If you really need to run arbitrary untrusted code at the same time as code that’s
processing sensitive information, you’ll need to use OS-level scheduling and CPU-
control facilities to ensure that another process or thread can’t run alongside your one
and monitor its operation, and that out-of-band channels like CPU caches are flushed
after your crypto operations have completed.
Interaction with External Events
Internally, cryptlib consists of a number of security-related objects, some of which
can be controlled by the user through handles to the objects. These objects may also
be acted on by external forces such as information coming from encryption and
system hardware, which will result in a message related to the external action being
sent to any affected cryptlib objects. An example of such an event is the withdrawal
of a smart card from a card reader, which would result in a card removal message
being sent to all cryptlib objects that were created using information stored on the
card. This can affect quite a number of objects.
Typically, the affected cryptlib objects will destroy any sensitive information held in
memory and disable themselves from further use. If you try to use any of the objects,
cryptlib will return CRYPT_ERROR_SIGNALLED to indicate that an external event
has caused a change in the state of the object.
After an object has entered the signalled state, the only remaining operation you can
perform with the object is to destroy it using the appropriate function.
Creating/Destroying Envelopes 49
Data Enveloping
Encryption envelopes are the easiest way to use cryptlib. An envelope is a container
object whose behaviour is modified by the data and resources that you push into it.
To use an envelope, you add to it other container and action objects and resources
such as passwords that control the actions performed by the envelope, and then push
in and pop out data that’s processed according to the resources that you’ve pushed in.
cryptlib takes care of the rest. For example to encrypt the message “This is a secret”
with the password “Secret password” you would do the following:
create the envelope;
add the password attribute "Secret password" to the envelope;
push data "This is a secret" into the envelope;
pop encrypted data from the envelope;
destroy the envelope;
That’s all that’s necessary. Since you’ve added a password attribute, cryptlib knows
that you want to encrypt the data in the envelope with the password, so it encrypts the
data and returns it to you. This process is referred to as enveloping the data.
The opposite, de-enveloping process consists of:
create the envelope;
push encrypted data into the envelope;
add the password attribute "Secret password" to the envelope;
pop decrypted data from the envelope;
destroy the envelope;
cryptlib knows the type of encrypted data that it’s working with (it can inform you
that you need to push in a password if you don’t know that in advance), decrypts it
with the provided password, and returns the result to you.
This example illustrates a feature of the de-enveloping process that may at first seem
slightly unusual: You have to push in some encrypted data before you can add the
password attribute needed to decrypt it. This is because cryptlib will automatically
determine what to do with the data you give it, so if you added a password before you
pushed in the encrypted data cryptlib wouldn’t know what to do with the password.
Signing data is almost identical, except that you add a signature key attribute instead
of a password. You can also add a number of other encryption attributes depending
on the type of functionality you want. Since all of these require further knowledge of
cryptlib’s capabilities, only basic data, compressed-data, and password-based
enveloping will be covered in this section.
Due to constraints in the underlying data formats that cryptlib supports, you can’t
perform more than one step of compression, encryption, or signing using a single
envelope (the resulting data stream can’t be encoded using most of the common data
formats supported by cryptlib). If you want to perform more than one of these
operations, you need to use multiple envelopes, one for each of the processing steps
you want to perform. If you try and add an encryption attribute to an envelope which
is set up for signing, or a signing attribute to an envelope which is set up for
encryption, or some other conflicting combination, cryptlib will return a parameter
error to indicate that the attribute type is invalid for this envelope since it’s already
being used for a different purpose.
Creating/Destroying Envelopes
Envelopes are accessed through envelope objects that work in the same general
manner as the other container objects used by cryptlib. Before you can envelope or
de-envelope data you need to create the appropriate type of envelope for the job. If
you want to envelope some data, you would create the envelope with
cryptCreateEnvelope, specifying the user who is to own the device object or
CRYPT_UNUSED for the default, normal user, and the format for the enveloped data
(for now you should use CRYPT_FORMAT_CRYPTLIB, the default format):
Data Enveloping
50
CRYPT_ENVELOPE cryptEnvelope;
cryptCreateEnvelope( &cryptEnvelope, cryptUser,
CRYPT_FORMAT_CRYPTLIB );
/* Perform enveloping */
cryptDestroyEnvelope( cryptEnvelope );
The Visual Basic version is:
Dim cryptEnvelope As Long
cryptCreateEnvelope cryptEnvelope, cryptUser, CRYPT_FORMAT_CRYPTLIB
' Perform enveloping
cryptDestroyEnvelope cryptEvelope
The C#, Java, and Python versions (here as elsewhere) migrate the output value to the
return value, and return errors by throwing exceptions. The Python version is:
cryptEnvelope = cryptCreateEnvelope( cryptUser,
CRYPT_FORMAT_CRYPTLIB )
The C# and Java version is:
int cryptEnvelope;
cryptEnvelope = crypt.CreateEnvelope( cryptUser,
crypt.FORMAT_CRYPTLIB );
If you want to de-envelope the result of the previous enveloping process, you would
create the envelope with format CRYPT_FORMAT_AUTO, which tells cryptlib to
automatically detect and use the appropriate format to process the data:
CRYPT_ENVELOPE cryptEnvelope;
cryptCreateEnvelope( &cryptEnvelope, cryptUser, CRYPT_FORMAT_AUTO );
/* Perform de-enveloping */
cryptDestroyEnvelope( cryptEnvelope );
Note that the CRYPT_ENVELOPE is passed to the cryptCreateEnvelope by
reference as the function modifies it when it creates the envelope. In all other
routines in cryptlib, CRYPT_ENVELOPE is passed by value.
Sometimes when you’re processing data in an envelope, you may not be able to add
all of the data in an envelope, for example when you’re trying to de-envelope a
message that’s been truncated due to a transmission error, or when you don’t retrieve
all of the processed data in the envelope before destroying it. When you destroy the
envelope cryptlib will return CRYPT_ERROR_INCOMPLETE as a warning that not
all of the data has been processed. The envelope will be destroyed as usual, but the
warning is returned to indicate that you should have added further data or retrieved
processed data before destroying the envelope.
The Data Enveloping Process
Although this section only covers basic data and password-based enveloping, the
concepts that it covers apply to all the other types of enveloping as well, so you
should familiarise yourself with this section even if you’re only planning to use the
more advanced types of enveloping such as digitally signed data enveloping. The
general model for enveloping data is:
add any attributes such as passwords or keys
push in data
pop out processed data
To de-envelope data:
The Data Enveloping Process 51
push in data
(cryptlib will inform you what resource(s) it needs to process the
data)
add the required attribute such as a password or key
pop out processed data
The enveloping/de-enveloping functions perform a lot of work in the background.
For example when you add a password attribute to an envelope and follow it with
some data, the function hashes the variable-length password down to create a fixed-
length key for the appropriate encryption algorithm, generates a temporary session
key to use to encrypt the data that you’ll be pushing into the envelope, uses the fixed-
length key to encrypt the session key, encrypts the data (taking into account the fact
that most encryption modes can’t encrypt individual bytes but require data to be
present in fixed-length blocks), and then cleans up by erasing any keys and other
sensitive information still in memory. This is why it’s recommended that you use the
envelope interface rather than trying to do the same thing yourself.
The cryptPushData and cryptPopData functions are used to push data into and pop
data out of an envelope. For example to push the message “Hello world” into an
envelope, you would use:
cryptPushData( envelope, "Hello world", 11, &bytesCopied );
The same operation in C# and Java is:
int bytesCopied = crypt.PushData( envelope, "Hello world" );
In Python this is:
bytesCopied = cryptPushData( envelope, "Hello world" )
The function will return an indication of how many bytes were copied into the
envelope in bytesCopied. Usually this is the same as the number of bytes you
pushed in, but if the envelope is almost full or you’re trying to push in a very large
amount of data, only some of the data may be copied in. This is useful when you
want to process a large quantity of data in multiple sections, which is explained
further on.
When you push in data, cryptlib may return an advisory CRYPT_-
ENVELOPE_RESOURCE status, which indicates that additional information such as
a password or decryption key is required in order to continue. Until you supply the
necessary resource, cryptlib can’t process the data that you’ve given it, and any
further attempts to push or pop data will fail with a CRYPT_ENVELOPE_-
RESOURCE. The handling of de-encryption resources is covered in more detail in
the following sections.
Popping data works similarly to pushing data:
cryptPopData( envelope, buffer, bufferSize, &bytesCopied );
In this case you supply a buffer to copy the data to, and an indication of how many
bytes you want to accept, and the function will return the number of bytes actually
copied in bytesCopied. This could be anything from zero up to the full buffer
size, depending on how much data is present in the envelope.
Once you’ve pushed the entire quantity of data that you want to process into an
envelope, you need to use cryptFlushData to tell the envelope object to wrap up the
data processing. If you try to push in any more data after this point, cryptlib will
return a CRYPT_ERROR_COMPLETE error to indicate that processing of the data
in the envelope has been completed and no more data can be added. Since the
enveloped data contains all the information necessary to de-envelope it, it isn’t
necessary to perform the final flush during de-enveloping.
You can add enveloping and de-enveloping attributes to an envelope in the usual
manner with cryptSetAttribute and cryptSetAttributeString. For example to add
the password “password” to an envelope, you would set the CRYPT_ENVINFO_-
PASSWORD attribute:
cryptSetAttributeString( cryptEnvelope, CRYPT_ENVINFO_PASSWORD,
"password", 8 );
Data Enveloping
52
The same operation in Visual Basic is:
cryptSetAttributeString cryptEnvelope, CRYPT_ENVINFO_PASSWORD, _
"password", 8
The various types of attributes that you can add are explained in more detail further
on.
Data Size Considerations
When you add data to an envelope, cryptlib processes and encodes it in a manner that
allows arbitrary amounts of data to be added. If cryptlib knows in advance how much
data will be pushed into the envelope, it can use a more efficient encoding method
since it doesn’t have to take into account an indefinitely long data stream. You can
notify cryptlib of the overall data size by setting the CRYPT_ENVINFO_DATASIZE
attribute:
cryptSetAttribute( envelope, CRYPT_ENVINFO_DATASIZE, dataSize );
This tells cryptlib how much data will be added, and allows it to use the more
efficient encoding format. If you push in more data than this before you wrap up the
processing with cryptFlushData, cryptlib will return CRYPT_ERROR_-
OVERFLOW; if you push in less, it will return CRYPT_ERROR_UNDERFLOW.
There is one exception to this rule, which occurs when you’re using the
PGP/OpenPGP data format. PGP requires that the length be indicated at the start of
every message, so you always have to set the CRYPT_ENVINFO_DATASIZE
attribute when you perform PGP enveloping. If you try and push data into a PGP
envelope without setting the data size, cryptlib will return CRYPT_ERROR_-
NOTINITED to tell you that it can’t envelope the data without knowing its overall
size in advance. PGP/OpenPGP enveloping is explained in more detail in “PGP” on
page 86.
The amount of data popped out of an envelope never matches the amount pushed in,
because the enveloping process adds encryption headers, digital signature
information, and assorted other paraphernalia which is required to process a message.
In many cases the overhead involved in wrapping up a block of data in an envelope
can be noticeable, so you should always push and pop as much data at once into and
out of an envelope as you can. For example if you have a 100-byte message and push
it in as 10 lots of 10 bytes, this is much slower than pushing a single lot of 100 bytes.
This behaviour is identical to the behaviour in applications like disk or network I/O,
where writing a single big file to disk is a lot more efficient than writing 10 smaller
files, and writing a single big network data packet is more efficient than writing 10
smaller data packets.
Push and popping unnecessarily small blocks of data when the total data size is
unknown can also affect the overall enveloped data size. If you haven’t told cryptlib
how much data you plan to process with CRYPT_ENVINFO_DATASIZE then each
time you pop a block of data from an envelope, cryptlib has to wrap up the current
block and add header information to it to allow it to be de-enveloped later on.
Because this encoding overhead consumes extra space, you should again try to push
and pop a single large data block rather than many small ones (to prevent worst-case
behaviour, cryptlib will coalesce adjacent small blocks into a minimum block size of
10 bytes, so it won’t return an individual block containing less than 10 bytes unless
it’s the last block in the envelope). This is again like disk data storage or network
I/O, where many small files or data packets lead to greater fragmentation and wasted
storage space or network overhead than a single large file or packet.
By default the envelope object which is created will have a 16K data buffer on DOS
and 16-bit Windows systems, and a 32K buffer elsewhere. The size of the internal
buffer affects the amount of extra processing that cryptlib needs to perform; a large
buffer will reduce the amount of copying to and from the buffer, but will consume
more memory (the ideal situation to aim for is one in which the data fits completely
within the buffer, which means that it can be processed in a single operation). Since
the process of encrypting and/or signing the data can increase its overall size, you
The Data Enveloping Process 53
should make the buffer 1-2K larger than the total data size if you want to process the
data in one go. The minimum buffer size is 4K, and on 16-bit systems the maximum
buffer size is 32K-1.
If want to use a buffer which is smaller or larger than the default size, you can specify
its size using the CRYPT_ATTRIBUTE_BUFFERSIZE attribute after the envelope
has been created. For example if you knew you were going to be processing a single
80K message on a 32-bit system (you can’t process more than 32K-1 bytes at once on
a 16-bit system) you would use:
CRYPT_ENVELOPE cryptEnvelope;
cryptCreateEnvelope( &cryptEnvelope, cryptUser,
CRYPT_FORMAT_CRYPTLIB );
cryptSetAttribute( cryptEnvelope, CRYPT_ATTRIBUTE_BUFFERSIZE,
90000L );
/* Perform enveloping */
cryptDestroyEnvelope( cryptEnvelope );
(the extra 10K provides a generous safety margin for message expansion due to the
enveloping process). When you specify the size of the buffer, you should try and
make it as large as possible, unless you’re pretty certain you’ll only be seeing
messages up to a certain size. Remember, the larger the buffer, the less processing
overhead is involved in handling data. However, if you make the buffer excessively
large it increases the probability that the data in it will be swapped out to disk, so it’s
a good idea not to go overboard on buffer size. You don’t have to process the entire
message at once, cryptlib provides the ability to envelope or de-envelope data in
multiple sections to allow processing of arbitrary amounts of data even on systems
with only small amounts of memory available.
Basic Data Enveloping
In the simplest case the entire message you want to process will fit into the
envelope’s internal buffer. The simplest type of enveloping does nothing to the data
at all, but just wraps it and unwraps it:
CRYPT_ENVELOPE cryptEnvelope;
int bytesCopied;
/* Create the envelope */
cryptCreateEnvelope( &cryptEnvelope, cryptUser,
CRYPT_FORMAT_CRYPTLIB );
/* Add the data size information and data, wrap up the processing, and
pop out the processed data */
cryptSetAttribute( cryptEnvelope, CRYPT_ENVINFO_DATASIZE,
messageLength );
cryptPushData( cryptEnvelope, message, messageLength, &bytesCopied );
cryptFlushData( cryptEnvelope );
cryptPopData( cryptEnvelope, envelopedData, envelopedDataBufferSize,
&bytesCopied );
/* Destroy the envelope */
cryptDestroyEnvelope( cryptEnvelope );
The Visual Basic equivalent is:
Dim cryptEnvelope As Long
Dim bytesCopied As Long
' Create the envelope
cryptCreateEnvelope cryptEnvelope, cryptUser, CRYPT_FORMAT_CRYPTLIB
' Add the data size information and data, wrap up the processing, and
' pop out the processed data
cryptSetAttribute cryptEnvelope, CRYPT_ENVINFO_DATASIZE, messageLength
cryptPushData cryptEnvelope, message, messageLength, bytesCopied
cryptFlushData cryptEnvelope
cryptPopData cryptEnvelope, envelopedData, envelopedDataBufferSize, _
bytesCopied
Data Enveloping
54
' Destroy the envelope
cryptDestroyEnvelope cryptEnvelope
The Python version is:
# Create the envelope
cryptEnvelope = cryptCreateEnvelope( cryptUser,
CRYPT_FORMAT_CRYPTLIB )
# Add the data size information and data, wrap up the processing, and
# pop out the processed data
cryptEnvelope.ENVINFO_DATASIZE = len( message )
bytesCopied = cryptPushData( cryptEnvelope, message )
cryptFlushData( cryptEnvelope )
bytesCopied = cryptPopData( cryptEnvelope, envelopedData,
envelopedDataBufferSize )
# Destroy the envelope
cryptDestroyEnvelope( cryptEnvelope )
The C# or Java version is:
int bytesCopied;
// Create the envelope
cryptEnvelope = crypt.CreateEnvelope( cryptUser,
crypt.FORMAT_CRYPTLIB );
// Add the data size information and data, wrap up the processing, and
// pop out the processed data
crypt.SetAttribute( cryptEnvelope, crypt.ENVINFO_DATASIZE,
message.Length );
bytesCopied = crypt.PushData( cryptEnvelope, message );
crypt.FlushData( cryptEnvelope );
bytesCopied = crypt.PopData( cryptEnvelope, envelopedData,
envelopedDataBufferSize );
// Destroy the envelope
crypt.DestroyEnvelope( cryptEnvelope );
(the above code is for C#, the Java version is virtually identical except that the
message.Length of a C# byte array is message.length in Java).
To de-envelope the resulting data you would use:
CRYPT_ENVELOPE cryptEnvelope;
int bytesCopied;
/* Create the envelope */
cryptCreateEnvelope( &cryptEnvelope, cryptUser, CRYPT_FORMAT_AUTO );
/* Push in the enveloped data and pop out the recovered message */
cryptPushData( cryptEnvelope, envelopedData, envelopedDataSize,
&bytesCopied );
cryptFlushData( cryptEnvelope );
cryptPopData( cryptEnvelope, message, messageBufferSize,
&bytesCopied );
/* Destroy the envelope */
cryptDestroyEnvelope( cryptEnvelope );
The Visual Basic form is:
Dim cryptEnvelope As Long
Dim bytesCopied As Long
' Create the envelope
cryptCreateEnvelope cryptEnvelope, cryptUser, CRYPT_FORMAT_AUTO
' Push in the enveloped data and pop out the recovered message
cryptPushData cryptEnvelope, envelopedData, envelopedDataSize, _
bytesCopied
cryptFlushData cryptEnvelope
cryptPopData cryptEnvelope, message, messageBufferSize, bytesCopied
' Destroy the envelope
cryptDestroyEnvelope cryptEnvelope
The Data Enveloping Process 55
This type of enveloping isn’t terribly useful, but it does demonstrate how the
enveloping process works.
Compressed Data Enveloping
A variation of basic data enveloping is compressed data enveloping which
compresses or decompresses data during the enveloping process. Compressing data
before signing or encryption improves the overall enveloping throughput
(compressing data and encrypting the compressed data is faster than just encrypting
the larger, uncompressed data), increases security by removing known patterns in the
data, and saves storage space and network bandwidth.
To tell cryptlib to compress data that you add to an envelope, you should set the
CRYPT_ENVINFO_COMPRESSION attribute before you add the data. This
attribute doesn’t take a value, so you should set it to CRYPT_UNUSED. The code to
compress a message is then:
CRYPT_ENVELOPE cryptEnvelope;
int bytesCopied;
cryptCreateEnvelope( &cryptEnvelope, cryptUser,
CRYPT_FORMAT_CRYPTLIB );
/* Tell cryptlib to compress the data */
cryptSetAttribute( cryptEnvelope, CRYPT_ENVINFO_COMPRESSION,
CRYPT_UNUSED );
/* Add the data size information and data, wrap up the processing, and
pop out the processed data */
cryptSetAttribute( cryptEnvelope, CRYPT_ENVINFO_DATASIZE,
messageLength );
cryptPushData( cryptEnvelope, message, messageLength, &bytesCopied );
cryptFlushData( cryptEnvelope );
cryptPopData( cryptEnvelope, envelopedData, envelopedDataBufferSize,
&bytesCopied );
cryptDestroyEnvelope( cryptEnvelope );
De-enveloping compressed data works exactly like decompressing normal data,
cryptlib will transparently decompress the data for you and return the decompressed
result when you call cryptPopData.
The compression/decompression process can cause a large change in data size
between what you push and what you pop back out, so you typically end up pushing
much more than you pop or popping much more than you push. In particular, you
may end up pushing multiple lots of data before you can pop any compressed data
out, or pushing a single lot of compressed data and having to pop multiple lots of
decompressed data. This applies particularly to the final stages of enveloping when
you flush through any remaining data, which signals the compressor to wrap up
processing and move any remaining data into the envelope. This means that the flush
can return CRYPT_ERROR_OVERFLOW to indicate that there’s more data to be
flushed, requiring multiple iterations of flushing and copying out data:
/* ... */
/* Flush out any remaining data */
do
{
cryptFlushData( cryptEnvelope );
cryptPopData( cryptEnvelope, outBuffer, BUFFER_SIZE, &bytesCopied
);
}
while( bytesCopied > 0 );
To handle this in a more general manner, you should use the processing techniques
described in “Enveloping Large Data Quantities” on page 61.
Password-based Encryption Enveloping
To do something useful (security-wise) to the data, you need to add a container or
action object or other type of attribute to tell the envelope to secure the data in some
Data Enveloping
56
way. For example if you wanted to encrypt a message with a password you would
use:
CRYPT_ENVELOPE cryptEnvelope;
int bytesCopied;
cryptCreateEnvelope( &cryptEnvelope, cryptUser,
CRYPT_FORMAT_CRYPTLIB );
/* Add the password */
cryptSetAttributeString( cryptEnvelope, CRYPT_ENVINFO_PASSWORD,
password, passwordLength );
/* Add the data size information and data, wrap up the processing, and
pop out the processed data */
cryptSetAttribute( cryptEnvelope, CRYPT_ENVINFO_DATASIZE,
messageLength );
cryptPushData( cryptEnvelope, message, messageLength, &bytesCopied );
cryptFlushData( cryptEnvelope );
cryptPopData( cryptEnvelope, envelopedData, envelopedDataBufferSize,
&bytesCopied );
cryptDestroyEnvelope( cryptEnvelope );
The same operation in Java (for C# replace the .length with .Length) is:
int cryptEnvelope = crypt.CreateEnvelope( cryptUser,
crypt.FORMAT_CRYPTLIB );
/* Add the password */
crypt.SetAttributeString( cryptEnvelope, crypt.ENVINFO_PASSWORD,
password );
/* Add the data size information and data, wrap up the processing, and
pop out the processed data */
crypt.SetAttribute( cryptEnvelope, crypt.ENVINFO_DATASIZE,
message.length );
int bytesCopied = crypt.PushData( cryptEnvelope, message );
crypt.FlushData( cryptEnvelope );
bytesCopied = crypt.PopData( cryptEnvelope, envelopedData,
envelopedData.length );
crypt.DestroyEnvelope( cryptEnvelope );
To de-envelope the resulting data you would use:
CRYPT_ENVELOPE cryptEnvelope;
int bytesCopied;
cryptCreateEnvelope( &cryptEnvelope, cryptUser, CRYPT_FORMAT_AUTO );
/* Push in the enveloped data and the password required to de-envelope
it, and pop out the recovered message */
cryptPushData( cryptEnvelope, envelopedData, envelopedDataLength,
&bytesCopied );
cryptSetAttributeString( cryptEnvelope, CRYPT_ENVINFO_PASSWORD,
password, passwordLength );
cryptFlushData( cryptEnvelope );
cryptPopData( cryptEnvelope, message, messageBufferSize,
&bytesCopied );
cryptDestroyEnvelope( cryptEnvelope );
The de-enveloping process in Java is:
int cryptEnvelope = crypt.CreateEnvelope( cryptUser,
crypt.FORMAT_AUTO );
int bytesCopied;
// Push in the enveloped data and the password required to
// de-envelope it, and pop out the recovered message
try {
bytesCopied = crypt.PushData( cryptEnvelope, envelopedData );
}
catch ( CryptException e ) {
if( e.getStatus() != crypt.ENVELOPE_RESOURCE )
throw e;
The Data Enveloping Process 57
}
crypt.SetAttributeString( cryptEnvelope, crypt.ENVINFO_PASSWORD,
password );
crypt.FlushData( cryptEnvelope );
crypt.PopData( cryptEnvelope, messageBuffer, messageBuffer.length );
// Destroy the envelope
crypt.DestroyEnvelope( cryptEnvelope );
The Visual Basic equivalent is:
Dim cryptEnvelope As Long
Dim bytesCopied As Long
cryptCreateEnvelope cryptEnvelope, cryptUser, CRYPT_FORMAT_AUTO
' Push in the enveloped data and the password required to
' de-envelope it, and pop out the recovered message
cryptPushData cryptEnvelope, envelopedData, envelopedDataSize, _
bytesCopied
cryptSetAttributeString cryptEnvelope, CRYPT_ENVINFO_PASSWORD, _
password, Len( password )
cryptFlushData cryptEnvelope
cryptPopData cryptEnvelope, message, messageBufferSize, bytesCopied
' Destroy the envelope
cryptDestroyEnvelope cryptEnvelope
When you push in the password-protected data, cryptPushData will return
CRYPT_ENVELOPE_RESOURCE to indicate that an additional resource (in this
case the password) is required in order to continue. This is an advisory status that
isn’t needed in this case but can be useful for advanced de-envelope processing as
described in “De-enveloping Mixed Data” on page 59.
If you add the wrong password, cryptlib will return a CRYPT_ERROR_-
WRONGKEY error. You can use this to request a new password from the user and
try again. For example to give the user the traditional three attempts at getting the
password right you would replace the code to add the password with:
for( i = 0; i < 3; i++ )
{
password = ...;
if( cryptSetAttributeString( envelope, CRYPT_ENVINFO_PASSWORD,
password, passwordLength ) == CRYPT_OK )
break;
}
Conventional Encryption Enveloping
In addition to encrypting enveloped data with a password, it’s possible to bypass the
password step and encrypt the data directly using an encryption context. This context
can either be used to encrypt the data directly (CRYPT_ENVINFO_SESSIONKEY)
or indirectly by wrapping up a session key (CRYPT_ENVINFO_KEY). For example
to encrypt data directly using IDEA with a raw session key you would do the
following:
CRYPT_ENVELOPE cryptEnvelope;
CRYPT_CONTEXT cryptContext;
int bytesCopied;
cryptCreateEnvelope( &cryptEnvelope, cryptUser,
CRYPT_FORMAT_CRYPTLIB );
/* Create the session key context and add it */
cryptCreateContext( &cryptContext, cryptUser, CRYPT_ALGO_IDEA );
cryptSetAttributeString( cryptContext, CRYPT_CTXINFO_KEY,
"0123456789ABCDEF", 16 );
cryptSetAttribute( cryptEnvelope, CRYPT_ENVINFO_SESSIONKEY,
cryptContext );
cryptDestroyContext( cryptContext );
Data Enveloping
58
/* Add the data size information and data, wrap up the processing, and
pop out the processed data */
cryptSetAttribute( cryptEnvelope, CRYPT_ENVINFO_DATASIZE,
messageLength );
cryptPushData( cryptEnvelope, message, messageLength, &bytesCopied );
cryptFlushData( cryptEnvelope );
cryptPopData( cryptEnvelope, envelopedData, envelopedDataBufferSize,
&bytesCopied );
cryptDestroyEnvelope( cryptEnvelope );
To de-envelope the resulting data you would use:
CRYPT_ENVELOPE cryptEnvelope;
CRYPT_CONTEXT cryptContext;
int bytesCopied;
cryptCreateEnvelope( &cryptEnvelope, cryptUser, CRYPT_FORMAT_AUTO );
/* Push in the enveloped data and the session key context required to
de-envelope it, and pop out the recovered message */
cryptPushData( cryptEnvelope, envelopedData, envelopedDataLength,
&bytesCopied );
cryptCreateContext( &cryptContext, cryptUser, CRYPT_ALGO_IDEA );
cryptSetAttributeString( cryptContext, CRYPT_CTXINFO_KEY,
"0123456789ABCDEF", 16 );
cryptSetAttribute( cryptEnvelope, CRYPT_ENVINFO_SESSIONKEY,
cryptContext );
cryptDestroyContext( cryptContext );
cryptFlushData( cryptEnvelope );
cryptPopData( cryptEnvelope, message, messageBufferSize,
&bytesCopied );
cryptDestroyEnvelope( cryptEnvelope );
Encrypting the data directly by using the context to wrap up the session key and then
encrypting the data with that functions identically, except that the context is added as
CRYPT_ENVINFO_KEY rather than CRYPT_ENVINFO_SESSIONKEY. The
only real difference between the two is the underlying data format that cryptlib
generates. As with the password-based de-enveloping, cryptlib will return an
advisory CRYPT_ENVELOPE_RESOURCE status when you push in the data to let
you know that you need to provide a decryption key in order to continue.
Raw session-key based enveloping isn’t recommended since it bypasses much of the
automated key management which is performed by the enveloping code, and requires
the direct use of low-level encryption contexts. If all you want to do is change the
underlying encryption algorithm used from the default triple DES, it’s easier to do
this by setting the CRYPT_OPTION_ENCR_ALGO attribute for the envelope as
described in “Working with Configuration Options” on page 265.
Authenticated Enveloping
Encryption protects the confidentiality of the data in an envelope, but it doesn’t
provide any integrity protection - an attacker can modify the encrypted form of the
data and obtain a corresponding modification of the decrypted form, and the simple
use of encryption can’t provide any protection against this. To provide integrity
protection for the contents of an envelope, you need to use an authenticated envelope.
You can tell cryptlib to add authentication to an envelope by setting the
CRYPT_ENVINFO_INTEGRITY attribute before you push data into the envelope.
By default this has a setting of CRYPT_INTEGRITY_NONE, which means that the
contents are protected by encryption only. If you want to provide authentication
(without encryption) for the data, you can set the CRYPT_ENVINFO_INTEGRITY
to CRYPT_INTEGRITY_MACONLY (a MAC is a cryptographic message
authentication code used to protect the contents of the envelope). The data
processing works just like a standard encrypted envelope:
CRYPT_ENVELOPE cryptEnvelope;
int bytesCopied;
cryptCreateEnvelope( &cryptEnvelope, cryptUser,
CRYPT_FORMAT_CRYPTLIB );
The Data Enveloping Process 59
/* Tell cryptlib that we want integrity-protection instead of
encryption and add the password */
cryptSetAttribute( cryptEnvelope, CRYPT_ENVINFO_INTEGRITY,
CRYPT_INTEGRITY_MACONLY );
cryptSetAttributeString( cryptEnvelope, CRYPT_ENVINFO_PASSWORD,
password, passwordLength );
/* Add the data size information and data, wrap up the processing, and
pop out the processed data */
cryptSetAttribute( cryptEnvelope, CRYPT_ENVINFO_DATASIZE,
messageLength );
cryptPushData( cryptEnvelope, message, messageLength, &bytesCopied );
cryptFlushData( cryptEnvelope );
cryptPopData( cryptEnvelope, envelopedData, envelopedDataBufferSize,
&bytesCopied );
cryptDestroyEnvelope( cryptEnvelope );
Note that you have to set the CRYPT_ENVINFO_INTEGRITY attribute before you
add an encryption key or password, otherwise cryptlib will assume that you want to
use the key to encrypt rather than authenticate the envelope contents.
When you de-envelope the data, cryptlib will use the MAC to check the integrity of
the envelope contents. If the data has been modified, cryptlib will return
CRYPT_ERROR_SIGNATURE once you’ve pushed the final portion of the
enveloped data into the envelope:
CRYPT_ENVELOPE cryptEnvelope;
int bytesCopied;
cryptCreateEnvelope( &cryptEnvelope, cryptUser, CRYPT_FORMAT_AUTO );
/* Push in the enveloped data and the password required to de-envelope
it, checking whether the data has been altered */
cryptPushData( cryptEnvelope, envelopedData, envelopedDataLength,
&bytesCopied );
cryptSetAttributeString( cryptEnvelope, CRYPT_ENVINFO_PASSWORD,
password, passwordLength );
status = cryptFlushData( cryptEnvelope );
if( status == CRYPT_ERROR_SIGNATURE )
/* Data has been altered */;
/* The data is un-altered, pop out the recovered message */
cryptPopData( cryptEnvelope, message, messageBufferSize,
&bytesCopied );
cryptDestroyEnvelope( cryptEnvelope );
You can also read the result of the integrity check by reading the CRYPT_-
ENVINFO_SIGNATURE_RESULT attribute, which works in much the same way as
it does for signed envelopes, which are discussed in “Digitally Signed Enveloping”
on page 70.
De-enveloping Mixed Data
Sometimes you won’t know exactly what type of processing has been applied to the
data that you’re trying to de-envelope, so you can let cryptlib tell you what to do.
When cryptlib needs some sort of resource (such as a password or an encryption key)
to process the data that you’ve pushed into an envelope, it will return a CRYPT_-
ENVELOPE_RESOURCE status if you try and push in any more data or pop out the
processed data. This status code is returned as soon as cryptlib knows enough about
the data that you’re pushing into the envelope to be able to process it properly.
Typically, as soon as you start pushing in encrypted, signed, or otherwise processed
data, cryptPushData will return CRYPT_ENVELOPE_RESOURCE to tell you that
it needs some sort of resource in order to continue.
If you knew that the data that you were processing was either plain, unencrypted data,
compressed data, or password-encrypted data created using the code shown earlier,
then you could de-envelope it with:
Data Enveloping
60
CRYPT_ENVELOPE cryptEnvelope;
int bytesCopied, status;
cryptCreateEnvelope( &cryptEnvelope, cryptUser, CRYPT_FORMAT_AUTO );
/* Push in the enveloped data and pop out the recovered message */
status = cryptPushData( cryptEnvelope, envelopedData,
envelopedDataLength, &bytesCopied );
if( status == CRYPT_ENVELOPE_RESOURCE )
cryptSetAttributeString( cryptEnvelope, CRYPT_ENVINFO_PASSWORD,
password, passwordLength );
cryptFlushData( cryptEnvelope );
cryptPopData( cryptEnvelope, message, messageBufferSize,
&bytesCopied );
cryptDestroyEnvelope( cryptEnvelope );
If the data is enveloped without any processing or is compressed data, cryptlib will
de-envelope it without requiring any extra input. If the data is enveloped using
password-based encryption, cryptlib will return CRYPT_ENVELOPE_RESOURCE
to indicate that it needs a password before it can continue.
This illustrates the manner in which the enveloped data contains enough information
to allow cryptlib to process it automatically. If the data had been enveloped using
some other form of processing (for example public-key encryption or digital
signatures), cryptlib would ask you for the private decryption key or the signature
check key at this time (it’s actually slightly more complex than this, the details are
explained in “Enveloping with Multiple Attributes” on page 72).
De-enveloping with a Large Envelope Buffer
If you’ve increased the envelope buffer size to allow the processing of large data
quantities, the de-enveloping process may be slightly different. When de-enveloping
data, cryptlib only reads an initial fixed amount of data before stopping and asking for
user input such as the password or private key which is required to process the data.
This is to avoid the situation where an envelope absorbs megabytes or even gigabytes
of data only to report that it can’t even begin to process it for lack of a decryption
key. In this case the envelope will return CRYPT_ERROR_RESOURCE to indicate
that it requires further information in order to continue. Once you’ve added the
necessary de-enveloping attribute(s), you can either pop what’s already been
processed and continue as normal (see “Enveloping Large Data Quantities” on page
61) or, for a sufficiently large envelope buffer, push in the remaining data before
popping it all at once:
CRYPT_ENVELOPE cryptEnvelope;
int bytesCopied, status;
cryptCreateEnvelope( &cryptEnvelope, cryptUser, CRYPT_FORMAT_AUTO );
/* Push in the enveloped data and see if we need any special handling
*/
status = cryptPushData( cryptEnvelope, envelopedData,
envelopedDataLength, &bytesCopied );
if( status == CRYPT_ENVELOPE_RESOURCE )
{
/* Add the necessary de-enveloping attributes */
/* ... */
/* If only some of the data was accepted because the envelope
stopped to request further instructions, push in the rest now */
if( bytesCopied < envelopedDataLength )
{
int remainingBytesCopied;
status = cryptPushData( cryptEnvelope, envelopedData + bytesIn,
envelopedDataLength - bytesIn, &remainingBytesCopied );
bytesIn += remainingBytesCopied;
}
}
cryptFlushData( cryptEnvelope );
cryptPopData( cryptEnvelope, message, messageBufferSize, &bytesCopied
);
Enveloping Large Data Quantities 61
cryptDestroyEnvelope( cryptEnvelope );
This code checks whether the envelope has absorbed all of the enveloped data and, if
not, pushes the remainder after adding the attribute(s) necessary for processing it.
Once all of the data has been pushed, it pops the result as usual.
Obtaining Envelope Security Parameters
If you want to know the details of the encryption mechanism that’s being used to
protect the enveloped data, you can read various CRYPT_CTXINFO_xxx attributes
from the envelope object which will return information from the encryption
context(s) that are being used to secure the data. For example if you’re encrypting or
decrypting data you can get the encryption algorithm and mode and the key size
being used with:
CRYPT_ALGO_TYPE cryptAlgo;
CRYPT_MODE_TYPE cryptMode;
int keySize;
cryptGetAttribute( cryptEnvelope, CRYPT_CTXINFO_ALGO, &cryptAlgo );
cryptGetAttribute( cryptEnvelope, CRYPT_CTXINFO_MODE, &cryptMode );
cryptGetAttribute( cryptEnvelope, CRYPT_CTXINFO_KEYSIZE, &keySize );
Enveloping Large Data Quantities
Sometimes, a message may be too big to process in one go or may not be available in
its entirety, an example being data which is being sent or received over a network
interface where only the currently transmitted or received portion is available.
Although it’s much easier to process a message in one go, it’s also possible to
envelope and de-envelope it a piece at a time (bearing in mind the earlier comment
that the enveloping is most efficient when you push and pop data a single large block
at a time rather than in many small blocks). With unknown amounts of data to be
processed it generally isn’t possible to use CRYPT_ENVINFO_DATASIZE, so in
the sample code below this is omitted.
There are several strategies for processing data in multiple parts. The simplest one
simply pushes and pops a fixed amount of data each time:
loop
push data
pop data
Since there’s a little overhead added by the enveloping process, you should always
push in slightly less data than the envelope buffer size. Alternatively, you can use the
CRYPT_ATTRIBUTE_BUFFERSIZE to specify an envelope buffer which is slightly
larger than the data block size that you want to use. The following code uses the first
technique to password-encrypt a file in blocks of BUFFER_SIZE – 4K bytes:
CRYPT_ENVELOPE cryptEnvelope;
void *buffer;
int bufferCount;
/* Create the envelope with a buffer of size BUFFER_SIZE and add the
password attribute */
cryptCreateEnvelope( &cryptEnvelope, cryptUser,
CRYPT_FORMAT_CRYPTLIB );
cryptSetAttribute( cryptEnvelope, CRYPT_ATTRIBUTE_BUFFERSIZE,
BUFFER_SIZE );
cryptSetAttributeString( cryptEnvelope, CRYPT_ENVINFO_PASSWORD,
password, passwordLength );
/* Allocate a buffer for file I/O */
buffer = malloc( BUFFER_SIZE );
Data Enveloping
62
/* Process the entire file */
while( !endOfFile( inputFile ) )
{
int bytesCopied;
/* Read a (BUFFER_SIZE - 4K) block from the input file, envelope
it, and write the result to the output file */
bufferCount = readFile( inputFile, buffer, BUFFER_SIZE - 4096 );
cryptPushData( cryptEnvelope, buffer, bufferCount, &bytesCopied );
cryptPopData( cryptEnvelope, buffer, BUFFER_SIZE, &bytesCopied );
writeFile( outputFile, buffer, bytesCopied );
}
/* Flush the last lot of data out of the envelope */
cryptFlushData( cryptEnvelope );
cryptPopData( cryptEnvelope, buffer, BUFFER_SIZE, &bytesCopied );
if( bytesCopied > 0 )
writeFile( outputFile, buffer, bytesCopied );
free( buffer );
cryptDestroyEnvelope( cryptEnvelope );
The Visual Basic version is:
Dim cryptEnvelope As Long
Dim buffer() As Byte
Dim bufferCount As Integer
Dim bytesCopied As Long
' Create the envelope with a buffer of size BUFFER_SIZE and add the
' password attribute
cryptCreateEnvelope cryptEnvelope, cryptUser, CRYPT_FORMAT_CRYPTLIB
cryptSetAttribute cryptEnvelope, CRYPT_ATTRIBUTE_BUFFERSIZE, _
BUFFER_SIZE
cryptSetAttributeString cryptEnvelope, CRYPT_ENVINFO_PASSWORD, _
password, Len( password )
' Allocate a buffer for file I/O
buffer = String( BUFFER_SIZE, vbNullChar )
Do While Not EndOfFile( inputFile )
' Read a (BUFFER_SIZE - 4K) block from the input file, envelope
' it, and write the result to an output file
bufferCount = ReadFile inputFile, buffer, BUFFERSIZE - 4096
cryptPushData cryptEnvelope, buffer, bufferCount, bytesCopied
cryptPopData cryptEnvelope, buffer, BUFFER_SIZE, bytesCopied
WriteFile outputFile, buffer, bytesCopied
Loop
cryptFlushData cryptEnvelope, buffer, BUFFER_SIZE, bytesCopied
If bytesCopied > 0 Then WriteFile outputFile, buffer, bytesCopied
cryptDestroyEnvelope cryptEnvelope
The code allocates a BUFFER_SIZE byte I/O buffer, reads up to BUFFER_SIZE –
4K bytes from the input file, and pushes it into the envelope. It then tells cryptlib to
pop up to BUFFER_SIZE bytes of enveloped data back out into the buffer, takes
whatever is popped out, and writes it to the output file. When it has processed the
entire file, it pushes in the usual zero-length data block to flush any remaining data
out of the buffer.
Note that the upper limit on BUFFER_SIZE depends on the system that you’re
running the code on. If you need to run it on a 16-bit system, BUFFER_SIZE is
limited to 32K–1 bytes because of the length limit imposed by 16-bit integers, and the
default envelope buffer size is 16K bytes unless you specify a larger default size
using the CRYPT_ATTRIBUTE_BUFFERSIZE attribute.
Going to a lot of effort to exactly match a certain data size such as a power of two
when pushing and popping data isn’t really worthwhile, since the overhead added by
the envelope encoding will always change the final encoded data length.
When you’re performing compressed data enveloping or de-enveloping, the
processing usually results in a large change in data size, in which case you may need
Enveloping Large Data Quantities 63
to use the technique described below that can handle arbitrarily-sized input and
output quantities.
Alternative Processing Techniques
A slightly more complex technique is to always stuff the envelope as full as possible
before trying to pop anything out of it:
loop
do
push data
while push status != CRYPT_ERROR_OVERFLOW
pop data
This results in the most efficient use of the envelope’s internal buffer, but is probably
overkill for the amount of code complexity required:
CRYPT_ENVELOPE cryptEnvelope;
void *inBuffer, *outBuffer;
int bytesCopiedIn, bytesCopiedOut, bufferCount;
cryptCreateEnvelope( &cryptEnvelope, cryptUser,
CRYPT_FORMAT_CRYPTLIB );
cryptSetAttributeString( cryptEnvelope, CRYPT_ENVINFO_PASSWORD,
password, passwordLength );
/* Allocate input and output buffers */
inBuffer = malloc( BUFFER_SIZE );
outBuffer = malloc( BUFFER_SIZE );
/* Process the entire file */
while( !endOfFile( inputFile ) )
{
int offset = 0;
/* Read a buffer full of data from the file and push and pop it
to/from the envelope */
bufferCount = readFile( inputFile, inBuffer, BUFFER_SIZE );
while( bufferCount > 0 )
{
/* Push as much as we can into the envelope */
cryptPushData( cryptEnvelope, inBuffer + offset, bufferCount,
&bytesCopiedIn );
offset += bytesCopiedIn;
bufferCount -= bytesCopiedIn;
/* If we couldn't push everything in, the envelope is full, so
we empty a buffers worth out */
if( bufferCount > 0 )
{
cryptPopData( cryptEnvelope, outBuffer, BUFFER_SIZE,
&bytesCopiedOut );
writeFile( outputFile, outBuffer, bytesCopiedOut );
}
}
}
/* Flush out any remaining data */
do
{
cryptFlushData( cryptEnvelope );
cryptPopData( cryptEnvelope, outBuffer, BUFFER_SIZE,
&bytesCopiedOut );
if( bytesCopiedOut > 0 )
writeFile( outputFile, outBuffer bytesCopiedOut );
}
while( bytesCopiedOut > 0 );
free( inBuffer );
free( outBuffer );
cryptDestroyEnvelope( cryptEnvelope );
Running the code to fill/empty the envelope in a loop is useful when you’re applying
a transformation such as data compression, which dramatically changes the length of
the enveloped/de-enveloped data. In this case it’s not possible to tell how much data
Data Enveloping
64
you can push into or pop out of the envelope because the length is transformed by the
compression operation. It’s also generally good practice to not write code that makes
assumptions about the amount of internal buffer space available in the envelope, the
above code will make optimal use of the envelope buffer no matter what its size.
Enveloping with Many Enveloping Attributes
There may be a special-case condition when you begin the enveloping that occurs if
you’ve added a large number of password, encryption, or keying attributes to the
envelope so that the header prepended to the enveloped data is particularly large. For
example if you encrypt a message with different keys or passwords for several dozen
recipients, the header information for all the keys could become large enough that it
occupies a noticeable portion of the envelope’s buffer. In this case you can push in a
small amount of data to flush out the header information, and then push and pop data
as usual:
add many password/encryption/keying attributes;
push a small amount of data;
pop data;
loop
push data;
pop data;
If you use this strategy then you can trim the difference between the envelope buffer
size and the amount of data you push in at once down to about 1K; the 4K difference
shown earlier took into account the fact that a little extra data would be generated the
first time data was pushed due to the overhead of adding the envelope header:
CRYPT_ENVELOPE cryptEnvelope;
void *buffer;
int bufferCount;
/* Create the envelope and add many passwords */
cryptCreateEnvelope( &cryptEnvelope, cryptUser,
CRYPT_FORMAT_CRYPTLIB );
cryptSetAttributeString( cryptEnvelope, CRYPT_ENVINFO_PASSWORD,
password1, password1Length );
/* ... */
cryptSetAttributeString( cryptEnvelope, CRYPT_ENVINFO_PASSWORD,
password100, password100Length );
buffer = malloc( BUFFER_SIZE );
/* Read up to 100 bytes from the input file, push it into the envelope
to flush out the header data, and write all the data in the
envelope to the output file */
bufferCount = readFile( inputFile, buffer, 100 );
cryptPushData( cryptEnvelope, buffer, bufferCount, &bytesCopied );
cryptPopData( cryptEnvelope, buffer, BUFFER_SIZE, &bytesCopied );
writeFile( outputFile, buffer, bytesCopied );
/* Process the entire file */
while( !endOfFile( inputFile ) )
{
int bytesCopied;
/* Read a BUFFER_SIZE block from the input file, envelope it, and
write the result to the output file */
bufferCount = readFile( inputFile, buffer, BUFFER_SIZE );
cryptPushData( cryptEnvelope, buffer, bufferCount, &bytesCopied );
cryptPopData( cryptEnvelope, buffer, BUFFER_SIZE, &bytesCopied );
writeFile( outputFile, buffer, bytesCopied );
}
/* Flush the last lot of data out of the envelope */
cryptFlushData( cryptEnvelope );
cryptPopData( cryptEnvelope, buffer, BUFFER_SIZE, &bytesCopied );
if( bytesCopied > 0 )
writeFile( outputFile, buffer, bytesCopied );
free( buffer );
cryptDestroyEnvelope( cryptEnvelope );
Enveloping Large Data Quantities 65
In the most extreme case (hundreds or thousands of passwords, encryption, or keying
attributes added to an envelope), the header could fill the entire envelope buffer, and
you would need to pop the initial data in multiple sections before you could process
any more data using the usual push/pop loop. If you plan to use this many resources,
it’s better to specify the use of a larger envelope buffer using
CRYPT_ATTRIBUTE_BUFFERSIZE in order to eliminate the need for such
special-case processing for the header.
De-enveloping data that has been enveloped with multiple keying resources also has
special requirements and is covered in the next section.
Advanced Enveloping
66
Advanced Enveloping
The previous chapter covered basic enveloping concepts and simple password-based
enveloping. Extending beyond these basic forms of enveloping, you can also
envelope data using public-key encryption or digitally sign the contents of the
envelope. These types of enveloping require the use of public and private keys that
are explained in various other chapters that cover key generation, key databases, and
certificates.
cryptlib automatically manages objects such as public and private keys and keysets,
so you can destroy them as soon as you’ve pushed them into the envelope. Although
the object will appear to have been destroyed, the envelope maintains its own
reference to it which it can continue to use for encryption or signing. This means that
instead of the obvious:
create the key object;
create the envelope;
add the key object to the envelope;
push data into the envelope;
pop encrypted data from the envelope;
destroy the envelope;
destroy the key object;
it’s also quite safe to use something like:
create the envelope;
create the key object;
add the key object to the envelope;
destroy the key object;
push data into the envelope;
pop encrypted data from the envelope;
destroy the envelope;
Keeping an object active for the shortest possible time makes it much easier to track,
it’s a lot easier to let cryptlib manage these things for you by handing them off to the
envelope.
Public-Key Encrypted Enveloping
Public-key based enveloping works just like password-based enveloping except that
instead of adding a password attribute you add a public key or certificate (when
encrypting) or a private decryption key (when decrypting). For example if you
wanted to encrypt data using a public key contained in pubKeyContext, you
would use:
CRYPT_ENVELOPE cryptEnvelope;
int bytesCopied;
cryptCreateEnvelope( &cryptEnvelope, cryptUser,
CRYPT_FORMAT_CRYPTLIB );
/* Add the public key */
cryptSetAttribute( cryptEnvelope, CRYPT_ENVINFO_PUBLICKEY,
pubKeyContext );
/* Add the data size information and data, wrap up the processing, and
pop out the processed data */
cryptSetAttribute( cryptEnvelope, CRYPT_ENVINFO_DATASIZE,
messageLength );
cryptPushData( cryptEnvelope, message, messageLength, &bytesCopied );
cryptFlushData( cryptEnvelope );
cryptPopData( cryptEnvelope, envelopedData, envelopedDataBufferSize,
&bytesCopied );
cryptDestroyEnvelope( cryptEnvelope );
You can also use a certificate in place of the public key, the envelope will handle both
in the same way. The certificate is typically obtained by reading it from a keyset,
either directly using cryptGetPublicKey as described in “Reading a Key from a
Keyset” on page 131, or by setting the CRYPT_ENVINFO_RECIPIENT attribute as
Public-Key Encrypted Enveloping 67
described in “S/MIME Enveloping” on page 77. Using the CRYPT_ENVINFO_-
RECIPIENT attribute is the preferred option since it lets cryptlib handle a number of
the complications that arise from reading keys for you.
When cryptlib encrypts the data in the envelope, it will use the algorithm specified
with the CRYPT_OPTION_ENCR_ALGO option. If you want to change the
encryption algorithm which is used, you can set the CRYPT_OPTION_ENCR_-
ALGO attribute for the envelope (or as a global configuration option) to the algorithm
type you want, as described in “Working with Configuration Options” on page 265.
Alternatively, you can push a raw session-key context into the envelope before you
push in a public key, in which case cryptlib will use the context to encrypt the data
rather than generating one itself.
The same operation in Java (for C# replace the .length with .Length) is:
int cryptEnvelope = crypt.CreateEnvelope( cryptUser,
crypt.FORMAT_CRYPTLIB );
/* Add the public key */
crypt.SetAttribute( cryptEnvelope, crypt.ENVINFO_PUBLICKEY,
pubKeyContext );
/* Add the data size information and data, wrap up the processing, and
pop out the processed data */
crypt.SetAttribute( cryptEnvelope, crypt.ENVINFO_DATASIZE,
message.length );
int bytesCopied = crypt.PushData( cryptEnvelope, message );
crypt.FlushData( cryptEnvelope );
bytesCopied = crypt.PopData( cryptEnvelope, envelopedData,
envelopedData.length );
crypt.DestroyEnvelope( cryptEnvelope );
De-enveloping is slightly more complex since, unlike password-based enveloping,
there are different keys used for enveloping and de-enveloping. In the simplest case
if you know in advance which private decryption key is required to decrypt the data,
you can add it to the envelope in the same way as with password-based enveloping:
CRYPT_ENVELOPE cryptEnvelope;
int bytesCopied;
cryptCreateEnvelope( &cryptEnvelope, cryptUser, CRYPT_FORMAT_AUTO );
/* Push in the enveloped data and the private decryption key required
to de-envelope it, and pop out the recovered message */
cryptPushData( cryptEnvelope, envelopedData, envelopedDataLength,
&bytesCopied );
cryptSetAttribute( cryptEnvelope, CRYPT_ENVINFO_PRIVATEKEY,
privKeyContext );
cryptFlushData( cryptEnvelope );
cryptPopData( cryptEnvelope, message, messageBufferSize, &bytesCopied
);
cryptDestroyEnvelope( cryptEnvelope );
Although this leads to very simple code, it’s somewhat awkward since you may not
know in advance which private key is required to decrypt a message. To make the
private key handling process easier, cryptlib provides the ability to automatically
fetch decryption keys from a private key keyset for you, so that instead of adding a
private key, you add a private key keyset object and cryptlib takes care of obtaining
the key for you. Alternatively, you can use a crypto device such as a smart card or
Fortezza card to perform the decryption.
Using a private key from a keyset is slightly more complex than pushing in the
private key directly since the private key stored in the keyset is usually encrypted or
PIN-protected and will require a password or PIN supplied by the user to access it.
This means that you have to supply a password to the envelope before the private key
can be used to decrypt the data in it. This works as follows:
Advanced Enveloping
68
create the envelope;
add the decryption keyset;
push encrypted data into the envelope;
if( required resource = private key )
add password to decrypt the private key;
pop decrypted data from the envelope;
destroy the envelope;
When you add the password, cryptlib will use it to try to recover the private key
stored in the keyset you added previously. If the password is incorrect, cryptlib will
return CRYPT_ERROR_WRONGKEY, otherwise it will recover the private key and
then use that to decrypt the data. The full code to decrypt public-key enveloped data
is therefore:
CRYPT_ENVELOPE cryptEnvelope;
CRYPT_ATTRIBUTE_TYPE requiredAttribute;
int bytesCopied, status;
/* Create the envelope and add the private key keyset and data */
cryptCreateEnvelope( &cryptEnvelope, cryptUser, CRYPT_FORMAT_AUTO );
cryptSetAttribute( cryptEnvelope, CRYPT_ENVINFO_KEYSET_DECRYPT,
privKeyKeyset );
cryptPushData( cryptEnvelope, envelopedData, envelopedDataLength,
&bytesCopied );
/* Find out what we need to continue and, if it's a private key, add
the password to recover it from the keyset */
cryptGetAttribute( cryptEnvelope, CRYPT_ATTRIBUTE_CURRENT,
&requiredAttribute );
if( requiredAttribute != CRYPT_ENVINFO_PRIVATEKEY )
/* Error */;
cryptSetAttributeString( cryptEnvelope, CRYPT_ENVINFO_PASSWORD,
password, passwordLength );
cryptFlushData( cryptEnvelope );
/* Pop the data and clean up */
cryptPopData( cryptEnvelope, message, messageLength, &bytesCopied );
cryptDestroyEnvelope( cryptEnvelope );
The Visual Basic equivalent is:
Dim cryptEnvelope As Long
Dim requiredAttribute As CRYPT_ATTRIBUTE_TYPE
Dim bytesCopied As Long
Dim status As Long
' Create the envelope and add the private key and data
cryptCreateEnvelope cryptEnvelope, cryptUser, CRYPT_FORMAT_AUTO
cryptSetAttribute cryptEnvelope, CRYPT_ENVINFO_KEYSET_DECRYPT, _
privateKeyset
cryptPushData cryptEnvelope, envelopedData, envelopedDataLength, _
bytesCopied
' Find out what we need to continue, and if it's a private key,
' add the password to recover it from the keyset
cryptGetAttribute cryptEnvelope, CRYPT_ATTRIBUTE_CURRENT, _
requiredAttribute
If ( requredAttribute <> CRYPT_ENVINFO_PRIVATEKEY ) Then
' Error
End If
cryptSetAttributeString cryptEnvelope, CRYPT_ENVINFO_PASSWORD, _
password, len( password )
cryptFlushData cryptEnvelope
' Pop the data and clean up
cryptPopData cryptEnvelope, message, messageLength, bytesCopied
cryptDestroyEnvelope cryptEnvelope
In the unusual case where the private key isn’t protected by a password or PIN,
there’s no need to add the password since cryptlib will use the private key as soon as
you access the attribute information by reading it using cryptGetAttribute.
In order to ask the user for a password, it can be useful to know the name or label
attached to the private key so you can display it as part of the password request
Public-Key Encrypted Enveloping 69
message. You can obtain the label for the required private key by reading the
envelope’s CRYPT_ENVINFO_PRIVATEKEY_LABEL attribute:
char label[ CRYPT_MAX_TEXTSIZE + 1 ];
int labelLength;
cryptGetAttributeString( cryptEnvelope,
CRYPT_ENVINFO_PRIVATEKEY_LABEL, label, &labelLength );
label[ labelLength ] = '\0';
You can then use the key label when you ask the user for the password for the key.
Using a crypto device to perform the decryption is somewhat simpler since the PIN
will already have been entered after cryptDeviceOpen was called, so there’s no need
to supply it as CRYPT_ENVINFO_PASSWORD. To use a crypto device, you add
the device in place of the private key keyset:
CRYPT_ENVELOPE cryptEnvelope;
CRYPT_ATTRIBUTE_TYPE requiredAttribute;
int bytesCopied, status;
/* Create the envelope and add the crypto device and data */
cryptCreateEnvelope( &cryptEnvelope, cryptUser, CRYPT_FORMAT_AUTO );
cryptSetAttribute( cryptEnvelope, CRYPT_ENVINFO_KEYSET_DECRYPT,
cryptDevice );
cryptPushData( cryptEnvelope, envelopedData, envelopedDataLength,
&bytesCopied );
/* Find out what we need to continue. Since we've told the envelope
to use a crypto device, it'll perform the decryption as soon as we
ask it to using the device, so we shouldn't have to supply anything
else */
cryptGetAttribute( cryptEnvelope, CRYPT_ATTRIBUTE_CURRENT,
&requiredAttribute );
if( requiredAttribute != CRYPT_ATTRIBUTE_NONE )
/* Error */;
cryptFlushData( cryptEnvelope );
/* Pop the data and clean up */
cryptPopData( cryptEnvelope, message, messageLength, &bytesCopied );
cryptDestroyEnvelope( cryptEnvelope );
Note how cryptGetAttribute now reports that there’s nothing further required (since
the envelope has used the private key in the crypto device to performed the
decryption), and you can continue with the de-enveloping process.
Code that can handle the use of either a private key keyset or a crypto device for the
decryption is a straightforward extension of the above:
CRYPT_ENVELOPE cryptEnvelope;
CRYPT_ATTRIBUTE_TYPE requiredAttribute;
int bytesCopied, status;
/* Create the envelope and add the keyset or crypto device and data */
cryptCreateEnvelope( &cryptEnvelope, cryptUser, CRYPT_FORMAT_AUTO );
cryptSetAttribute( cryptEnvelope, CRYPT_ENVINFO_KEYSET_DECRYPT,
cryptKeysetOrDevice );
cryptPushData( cryptEnvelope, envelopedData, envelopedDataLength,
&bytesCopied );
/* Find out what we need to continue. If what we added was a crypto
device, the decryption will occur once we query the envelope. If
what we added was a keyset, we need to supply a password for the
decryption to happen */
cryptGetAttribute( cryptEnvelope, CRYPT_ATTRIBUTE_CURRENT,
&requiredAttribute );
if( requiredAttribute != CRYPT_ATTRIBUTE_NONE )
{
char label[ CRYPT_MAX_TEXTSIZE + 1 ];
int labelLength;
if( requiredAttribute != CRYPT_ENVINFO_PASSWORD )
/* Error */;
Advanced Enveloping
70
/* Get the label for the private key and obtain the required
password from the user */
cryptGetAttributeString( cryptEnvelope,
CRYPT_ENVINFO_PRIVATEKEY_LABEL, label, &labelLength );
label[ labelLength ] = '\0';
getPassword( label, password, &passwordLength );
/* Add the password required to decrypt the private key */
cryptSetAttributeString( cryptEnvelope, CRYPT_ENVINFO_PASSWORD,
password, passwordLength );
}
cryptFlushData( cryptEnvelope );
/* Pop the data and clean up */
cryptPopData( cryptEnvelope, message, messageLength, &bytesCopied );
cryptDestroyEnvelope( cryptEnvelope );
Digitally Signed Enveloping
Digitally signed enveloping works much like the other enveloping types except that
instead of adding an encryption or decryption attribute you supply a private signature
key (when enveloping) or a public key or certificate (when de-enveloping). For
example if you wanted to sign data using a private signature key contained in
sigKeyContext, you would use:
CRYPT_ENVELOPE cryptEnvelope;
int bytesCopied;
cryptCreateEnvelope( &cryptEnvelope, cryptUser,
CRYPT_FORMAT_CRYPTLIB );
/* Add the signing key */
cryptSetAttribute( cryptEnvelope, CRYPT_ENVINFO_SIGNATURE,
sigKeyContext );
/* Add the data size information and data, wrap up the processing, and
pop out the processed data */
cryptSetAttribute( cryptEnvelope, CRYPT_ENVINFO_DATASIZE,
messageLength );
cryptPushData( cryptEnvelope, message, messageLength, &bytesCopied );
cryptFlushData( cryptEnvelope );
cryptPopData( cryptEnvelope, envelopedData, envelopedDataBufferSize,
&bytesCopied );
cryptDestroyEnvelope( cryptEnvelope );
The signature key could be a native cryptlib key, but it could also be a key from a
crypto device such as a smart card or Fortezza card. They both work in the same way
for signing data.
The Java version of the signed enveloping process (for C# replace the .length with
.Length) is:
int cryptEnvelope = crypt.CreateEnvelope( cryptUser,
crypt.FORMAT_CRYPTLIB );
/* Add the public key */
crypt.SetAttribute( cryptEnvelope, crypt.ENVINFO_SIGNATURE,
sigKeyContext );
/* Add the data size information and data, wrap up the processing, and
pop out the processed data */
crypt.SetAttribute( cryptEnvelope, crypt.ENVINFO_DATASIZE,
message.length );
int bytesCopied = crypt.PushData( cryptEnvelope, message );
crypt.FlushData( cryptEnvelope );
bytesCopied = crypt.PopData( cryptEnvelope, envelopedData,
envelopedData.length );
crypt.DestroyEnvelope( cryptEnvelope );
The Visual Basic equivalent is:
cryptCreateEnvelope cryptEnvelope, cryptUser, CRYPT_FORMAT_CRYPTLIB
Digitally Signed Enveloping 71
' Add the signing key
cryptSetAttribute cryptEnvelope, CRYPT_ENVINFO_SIGNATURE, _
sigKeyContext
' Add the data size information and data, wrap up the processing,
' and pop out the processed data
cryptSetAttribute cryptEnvelope, CRYPT_ENVINFO_DATASIZE, messageLength
cryptPushData cryptEnvelope, message, messageLength, bytesCopied
cryptFlushData cryptEnvelope
cryptPopData cryptEnvelope, envelopedData, envelopedDataBufferSize, _
bytesCopied
cryptDestroyEnvelope cryptEnvelope
When cryptlib signs the data in the envelope, it will hash it with the algorithm
specified with the CRYPT_OPTION_ENCR_HASH option. If you want to change
the hashing algorithm which is used, you can set the CRYPT_OPTION_ENCR_-
HASH attribute for the envelope (or as a global configuration option) to the algorithm
type you want, as described in “Working with Configuration Options” on page 265.
Alternatively, you can push a hash context into the envelope before you push in a
signature key, in which case cryptlib will associate the signature key with the last
hash context you pushed in.
If you’re worried about some obscure (and rather unlikely) attacks on private keys,
you can enable the CRYPT_OPTION_MISC_SIDECHANNELPROTECTION
option as explained in “Working with Configuration Options” on page 265.
As with public-key based enveloping, verifying the signed data requires a different
key for this part of the operation, in this case a public key or key certificate. In the
simplest case if you know in advance which public key is required to verify the
signature, you can add it to the envelope in the same way as with the other envelope
types:
CRYPT_ENVELOPE cryptEnvelope;
int bytesCopied;
cryptCreateEnvelope( &cryptEnvelope, cryptUser, CRYPT_FORMAT_AUTO );
/* Add the enveloped data and the signature check key required to
verify the signature, and pop out the recovered message */
cryptPushData( cryptEnvelope, envelopedData, envelopedDataLength,
&bytesCopied );
cryptFlushData( cryptEnvelope );
cryptSetAttribute( cryptEnvelope, CRYPT_ENVINFO_SIGNATURE,
sigCheckKeyContext );
cryptPopData( cryptEnvelope, message, messageBufferSize, &bytesCopied
);
cryptDestroyEnvelope( cryptEnvelope );
Although this leads to very simple code, it’s somewhat awkward since you may not
know in advance which public key or key certificate is required to verify the
signature on the message. To make the signature verification process easier, cryptlib
provides the ability to automatically fetch signature verification keys from a public-
key keyset for you, so that instead of supplying a public key or key certificate, you
add a public-key keyset object before you start de-enveloping and cryptlib will take
care of obtaining the key for you. This option works as follows:
create the envelope;
add the signature check keyset;
push signed data into the envelope;
pop plain data from the envelope;
if( required resource = signature check key )
read signature verification result;
The full code to verify signed data is therefore:
Advanced Enveloping
72
CRYPT_ENVELOPE cryptEnvelope;
int bytesCopied, signatureResult, status;
/* Create the envelope and add the signature check keyset */
cryptCreateEnvelope( &cryptEnvelope, cryptUser, CRYPT_FORMAT_AUTO );
cryptSetAttribute( cryptEnvelope, CRYPT_ENVINFO_KEYSET_SIGCHECK,
sigCheckKeyset );
/* Push in the signed data and pop out the recovered message */
cryptPushData( cryptEnvelope, envelopedData, envelopedDataLength,
&bytesCopied );
cryptFlushData( cryptEnvelope );
cryptPopData( cryptEnvelope, message, messageBufferSize,
&bytesCopied );
/* Determine the result of the signature check */
cryptGetAttribute( cryptEnvelope, CRYPT_ENVINFO_SIGNATURE_RESULT,
&signatureResult );
The same process in Java (for C# replace the .length with .Length) is:
/* Create the envelope and add the signature check keyset */
int cryptEnvelope = crypt.CreateEnvelope(cryptUser,
crypt.FORMAT_AUTO );
crypt.SetAttribute( cryptEnvelope, crypt.ENVINFO_KEYSET_SIGCHECK,
sigCheckKeyset );
/* Push in the signed data and pop out the recovered message */
int bytesCopied = crypt.PushData( cryptEnvelope, envelopedData );
crypt.FlushData( cryptEnvelope );
bytesCopied = crypt.PopData( cryptEnvelope, message, message.length );
/* Determine the result of the signature check */
int signatureResult = crypt.GetAttribute( cryptEnvelope,
crypt.ENVINFO_SIGNATURE_RESULT );
The Visual Basic version is:
Dim signatureResult As Long
' Create the envelope and add the signature check keyset
cryptCreateEnvelope cryptEnvelope, cryptUser, CRYPT_FORMAT_AUTO
cryptSetAttribute cryptEnvelope, CRYPT_ENVINFO_KEYSET_SIGCHECK, _
sigCheckKeyset
' Push in the signed data and pop out the recovered message
cryptPushData cryptEnvelope, envelopedData, envelopedDataLength, _
bytesCopied
cryptPopData cryptEnvelope, message, messageBufferSize, bytesCopied
' Determine the result of the signature check
cryptGetAttribute cryptEnvelope, CRYPT_ENVINFO_SIGNATURE_RESULT, _
signatureResult
The signature result will typically be CRYPT_OK (the signature verified), CRYPT_-
ERROR_SIGNATURE (the signature did not verify), or CRYPT_ERROR_-
NOTFOUND (the key needed to check the signature wasn’t found in the keyset).
Most signed data in use today uses a format popularised in S/MIME that includes the
signature verification key with the data being signed as a certificate chain. For this
type of data you don’t need to provide a signature verification key, since it’s already
included with the signed data. Details on creating and processing data in this format
is given in “S/MIME Enveloping” on page 77.
Enveloping with Multiple Attributes
Sometimes enveloped data can have multiple sets of attributes applied to it, for
example encrypted data might be encrypted with two different passwords to allow it
to be decrypted by two different people:
CRYPT_ENVELOPE cryptEnvelope;
int bytesCopied
cryptCreateEnvelope( &cryptEnvelope, cryptUser,
CRYPT_FORMAT_CRYPTLIB );
Enveloping with Multiple Attributes 73
/* Add two different passwords to the envelope */
cryptSetAttributeString( cryptEnvelope, CRYPT_ENVINFO_PASSWORD,
password1, password1Length );
cryptSetAttributeString( cryptEnvelope, CRYPT_ENVINFO_PASSWORD,
password2, password2Length );
/* Add the data size information and data, wrap up the processing, and
pop out the processed data */
cryptSetAttribute( cryptEnvelope, CRYPT_ENVINFO_DATASIZE,
messageLength );
cryptPushData( cryptEnvelope, message, messageLength, &bytesCopied );
cryptFlushData( cryptEnvelope );
cryptPopData( cryptEnvelope, envelopedData, envelopedDataBufferSize,
&bytesCopied );
cryptDestroyEnvelope( cryptEnvelope );
In this case either of the two passwords can be used to decrypt the data. This can be
extended indefinitely, so that 5, 10, 50, or 100 passwords could be used (of course
with 100 different passwords able to decrypt the data, it’s questionable whether it’s
worth the effort of encrypting it at all, however this sort of multi-user encryption
could be useful for public-key encrypting messages sent to collections of people such
as mailing lists). The same applies for public-key enveloping, in fact the various
encryption types can be mixed if required so that (for example) either a private
decryption key or a password could be used to decrypt data.
Similarly, an envelope can have multiple signatures applied to it:
CRYPT_ENVELOPE cryptEnvelope;
int bytesCopied
cryptCreateEnvelope( &cryptEnvelope, cryptUser,
CRYPT_FORMAT_CRYPTLIB );
/* Add two different signing keys to the envelope */
cryptSetAttribute( cryptEnvelope, CRYPT_ENVINFO_SIGNATURE,
cryptSigKey1 );
cryptSetAttribute( cryptEnvelope, CRYPT_ENVINFO_SIGNATURE,
cryptSigKey2 );
/* Add the data size information and data, wrap up the processing, and
pop out the processed data */
cryptSetAttribute( cryptEnvelope, CRYPT_ENVINFO_DATASIZE,
messageLength );
cryptPushData( cryptEnvelope, message, messageLength, &bytesCopied );
cryptFlushData( cryptEnvelope );
cryptPopData( cryptEnvelope, envelopedData, envelopedDataBufferSize,
&bytesCopied );
cryptDestroyEnvelope( cryptEnvelope );
In this case the envelope will be signed by both keys. As with password-based
enveloping, this can also be extended indefinitely to allow additional signatures on
the data, although it would be somewhat unusual to place more than one or two
signatures on a piece of data.
When de-enveloping data that has been enveloped with a choice of multiple
attributes, cryptlib builds a list of the attributes required to decrypt or verify the
signature on the data, and allows you to query the required attribute information and
choose the one you want to work with.
Processing Multiple De-enveloping Attributes
The attributes required for de-enveloping are managed through the use of an attribute
cursor as described in “Attribute Lists and Attribute Groups” on page 38. You can
use the attribute cursor to determine which attribute is required for the de-enveloping
process. Once you’re iterating through the attributes, all that’s left to do is to plug in
the appropriate handler routines to manage each attribute requirement that could be
encountered. As soon as one of the attributes required to continue is added to the
envelope, cryptlib will delete the required-attribute list and continue, so the attempt to
move the cursor to the next entry in the list will fail and the program will drop out of
Advanced Enveloping
74
the processing loop. For example to try a password against all of the possible
passwords that might decrypt the message that was enveloped above, you would use:
int status
/* Get the decryption password from the user */
password = ...;
if( cryptSetAttribute( envelope, CRYPT_ATTRIBUTE_CURRENT_GROUP,
CRYPT_CURSOR_FIRST ) == CRYPT_OK )
do
{
CRYPT_ATTRIBUTE_TYPE requiredAttribute;
/* Get the type of the required attribute at the cursor position
*/
cryptGetAttribute( envelope, CRYPT_ATTRIBUTE_CURRENT,
&requiredAttribute );
/* Make sure we really do require a password resource */
if( requiredAttribute != CRYPT_ENVINFO_PASSWORD )
/* Error */;
/* Try the password. If everything is OK, we'll drop out of the
loop */
status = cryptSetAttributeString( envelope,
CRYPT_ENVINFO_PASSWORD, password, passwordLength );
}
while( status == CRYPT_WRONGKEY && \
cryptSetAttribute( envelope, CRYPT_ATTRIBUTE_CURRENT_GROUP,
CRYPT_CURSOR_NEXT ) == CRYPT_OK );
This steps through each required attribute in turn and tries the supplied password to
see if it matches. As soon as the password matches, the data can be decrypted, and
we drop out of the loop and continue the de-enveloping process.
To extend this a bit further, let’s assume that the data could be enveloped using a
password or a public key (requiring a private decryption key to decrypt it, either one
from a keyset or a crypto device such as a smart card or Fortezza card). The code
inside the loop above then becomes:
CRYPT_ATTRIBUTE_TYPE requiredAttribute;
/* Get the type of the required resource at the cursor position */
cryptGetAttribute( envelope, CRYPT_ATTRIBUTE_CURRENT,
&requiredAttribute );
/* If the decryption is being handled via a crypto device, we don't
need to take any further action, the data has already been
decrypted */
if( requiredAttribute != CRYPT_ATTRIBUTE_NONE )
{
/* Make sure we really do require a password attribute */
if( requiredAttribute != CRYPT_ENVINFO_PASSWORD && \
requiredAttribute != CRYPT_ENVINFO_PRIVATEKEY )
/* Error */;
/* Try the password. If everything is OK, we'll drop out of the
loop */
status = cryptSetAttributeString( envelope, CRYPT_ENVINFO_PASSWORD,
password, passwordLength );
}
If what’s required is a CRYPT_ENVINFO_PASSWORD, cryptlib will apply it
directly to decrypt the data. If what’s required is a CRYPT_ENVINFO_-
PRIVATEKEY, cryptlib will either use the crypto device to decrypt the data if it’s
available, or otherwise use the password to try to recover the private key from the
keyset and then use that to decrypt the data.
Iterating through each required signature attribute when de-enveloping signed data is
similar, but instead of trying to provide the necessary decryption information you
would provide the necessary signature check information (if requested, many
envelopes carry their own signature verification keys with them) and display the
Nested Envelopes 75
resulting signature information. Unlike encryption de-enveloping attributes, cryptlib
won’t delete the signature information once it has been processed, so you can re-read
the information multiple times:
int status
if( cryptSetAttribute( envelope, CRYPT_ATTRIBUTE_CURRENT_GROUP,
CRYPT_CURSOR_FIRST ) == CRYPT_OK )
do
{
CRYPT_ATTRIBUTE_TYPE requiredAttribute;
int sigResult;
/* Get the type of the required attribute at the cursor position
*/
cryptGetAttribute( envelope, CRYPT_ATTRIBUTE_CURRENT,
&requiredAttribute );
/* Make sure we really do have signature */
if( requiredAttribute != CRYPT_ENVINFO_SIGNATURE )
/* Error */;
/* Get the signature result */
status = cryptSetAttribute( envelope,
CRYPT_ENVINFO_SIGNATURE_RESULT, & sigResult );
}
while( cryptStatusOK( status ) && \
cryptSetAttribute( envelope, CRYPT_ATTRIBUTE_CURRENT_GROUP,
CRYPT_CURSOR_NEXT ) == CRYPT_OK );
This steps through each signature in turn and reads the result of the signature
verification for that signature, stopping when an invalid signature is found or when all
signatures are processed.
Nested Envelopes
Sometimes it may be necessary to apply multiple levels of processing to data, for
example you may want to both sign and encrypt data. cryptlib allows enveloped data
to be arbitrarily nested, with each nested content type being either further enveloped
data or (finally) the raw data payload. For example to sign and encrypt data you
would do the following:
create the envelope;
add the signature key;
push in the raw data;
pop out the signed data;
destroy the envelope;
create the envelope;
add the encryption key;
push in the previously signed data;
pop out the signed, encrypted data;
destroy the envelope;
This nesting process can be extended arbitrarily with any of the cryptlib content
types.
Since cryptlib’s enveloping isn’t sensitive to the content type (that is, you can push in
any type of data and it’ll be enveloped in the same way), you need to notify cryptlib
of the actual content type being enveloped if you’re using nested envelopes. You can
set the content type being enveloped using the CRYPT_ENVINFO_-
CONTENTTYPE attribute, giving as value the appropriate CRYPT_CONTENT_-
type. For example to specify that the data being enveloped is signed data, you would
use:
cryptSetAttribute( cryptEnvelope, CRYPT_ENVINFO_CONTENTTYPE,
CRYPT_CONTENT_SIGNEDDATA );
The default content type is plain data, so if you don’t explicitly set a content type
cryptlib will assume it’s just raw data. The other content types are described in
“Other Certificate Object Extensions” on page 244.
Advanced Enveloping
76
Using the nested enveloping example shown above, the full enveloping procedure
would be:
create the envelope;
add the signature key;
(cryptlib sets the content type to the default 'plain data')
push in the raw data;
pop out the signed data;
destroy the envelope;
create the envelope;
set the content type to 'signed data';
add the encryption key;
push in the previously signed data;
pop out the signed, encrypted data;
destroy the envelope;
This will mark the innermost content as plain data (the default), the next level as
signed data, and the outermost level as encrypted data.
Unwrapping nested enveloped data is the opposite of the enveloping process. For
each level of enveloped data, you can obtain its type (once you’ve pushed enough of
it into the envelope to allow cryptlib to decode it) by reading the
CRYPT_ENVINFO_CONTENTTYPE attribute:
CRYPT_ATTRIBUTE_TYPE contentType;
cryptGetAttribute( cryptEnvelope, CRYPT_ENVINFO_CONTENTTYPE,
&contentType );
Processing nested enveloped data therefore involves unwrapping successive layers of
data until you finally reach the raw data content type.
S/MIME Enveloping 77
S/MIME
S/MIME is a standard format for transferring signed, encrypted, or otherwise
processed data as a MIME-encoded message (for example as email or embedded in a
web page). The MIME-encoding is only used to make the result palatable to mailers,
it’s also possible to process the data without the MIME encoding.
The exact data formatting and terminology used requires a bit of further explanation.
In the beginning there was PKCS #7, a standard format for signed, encrypted, or
otherwise processed data. When the earlier PEM secure mail standard failed to take
off, PKCS #7 was wrapped up in MIME encoding and christened S/MIME version 2.
Eventually PKCS #7 was extended to become the Cryptographic Message Syntax
(CMS), and when that’s wrapped in MIME it’s called S/MIME version 3.
In practice it’s somewhat more complicated than this since there’s significant blurring
between S/MIME version 2 and 3 (and PKCS #7 and CMS). The main effective
difference between the two is that PKCS #7/SMIME version 2 is completely tied to
X.509 certificates, certification authorities, certificate chains, and other paraphernalia,
CMS can be used without requiring all these extras if necessary, and S/MIME version
3 restricts CMS back to requiring X.509 for S/MIME version 2 compatibility.
The cryptlib native format is CMS used in the configuration that doesn’t tie it to the
use of certificates (so it’ll work with PGP/OpenPGP keys, raw public/private keys,
and other keying information as well as with X.509 certificates). In addition to this
format, cryptlib also supports the S/MIME format which is tied to X.509 — this is
just the cryptlib native format restricted so that the full range of key management
options aren’t available. If you want to interoperate with other implementations, you
should use this format since many implementations can’t work with the newer key
management options that were added in CMS.
You can specify the use of the restricted CMS/SMIME format when you create an
envelope with the formatting specifier CRYPT_FORMAT_CMS or CRYPT_-
FORMAT_SMIME (they’re almost identical, the few minor differences are explained
in “Extra Signature Information” on page 83), which tells cryptlib to use the restricted
CMS/SMIME rather than the (default) unrestricted CMS format. You can also use
the format specifiers with cryptExportKeyEx and cryptCreateSignatureEx (which
take as their third argument the format specifier) as explained in “Exchanging Keys”
on page 184, and “Signing Data” on page 190.
S/MIME Enveloping
Although it’s possible to use the S/MIME format directly with the mid-level signature
and encryption functions, S/MIME requires a considerable amount of extra
processing above and beyond that required by cryptlib’s default format, so it’s easiest
to let cryptlib take care of this extra work for you by using the enveloping functions
to process S/MIME data.
To create an envelope that uses the S/MIME format, call cryptCreateEnvelope as
usual but specify a format type of CRYPT_FORMAT_SMIME instead of the usual
CRYPT_FORMAT_CRYPTLIB:
CRYPT_ENVELOPE cryptEnvelope;
cryptCreateEnvelope( &cryptEnvelope, cryptUser, CRYPT_FORMAT_SMIME );
/* Perform enveloping */
cryptDestroyEnvelope( cryptEnvelope );
Creating the envelope in this way restricts cryptlib to using the standard X.509-based
S/MIME data format instead of the more flexible data format which is used for
envelopes by default.
S/MIME
78
Encrypted Enveloping
S/MIME supports password-based enveloping in the same way as ordinary cryptlib
envelopes (in fact the two formats are identical). Public-key encrypted enveloping is
supported only when the public key is held in an X.509 certificate. Because of this
restriction the private decryption key must also have a certificate attached to it. Apart
from these restrictions, public-key based S/MIME enveloping works the same way as
standard cryptlib enveloping. For example to encrypt data using the key contained in
an X.509 certificate you would use:
CRYPT_ENVELOPE cryptEnvelope;
int bytesCopied;
cryptCreateEnvelope( &cryptEnvelope, cryptUser, CRYPT_FORMAT_SMIME );
/* Add the certificate */
cryptSetAttribute( cryptEnvelope, CRYPT_ENVINFO_PUBLICKEY,
certificate );
/* Add the data size information and data, wrap up the processing, and
pop out the processed data */
cryptSetAttribute( cryptEnvelope, CRYPT_ENVINFO_DATASIZE,
messageLength );
cryptPushData( cryptEnvelope, message, messageLength, &bytesCopied );
cryptFlushData( cryptEnvelope );
cryptPopData( cryptEnvelope, envelopedData, envelopedDataBufferSize,
&bytesCopied );
cryptDestroyEnvelope( cryptEnvelope );
Since the certificate will originally come from a keyset, a simpler alternative to
reading the certificate yourself and explicitly adding it to the envelope is to let
cryptlib do it for you by first adding the keyset to the envelope and then specifying
the email address of the recipient or recipients of the message with the CRYPT_-
ENVINFO_RECIPIENT attribute:
CRYPT_ENVELOPE cryptEnvelope;
int bytesCopied;
cryptCreateEnvelope( &cryptEnvelope, cryptUser, CRYPT_FORMAT_SMIME );
/* Add the encryption keyset and recipient email address */
cryptSetAttribute( cryptEnvelope, CRYPT_ENVINFO_KEYSET_ENCRYPT,
cryptKeyset );
cryptSetAttributeString( cryptEnvelope, CRYPT_ENVINFO_RECIPIENT,
"person@company.com", 18 );
/* Add the data size information and data, wrap up the processing, and
pop out the processed data */
cryptSetAttribute( cryptEnvelope, CRYPT_ENVINFO_DATASIZE,
messageLength );
cryptPushData( cryptEnvelope, message, messageLength, &bytesCopied );
cryptFlushData( cryptEnvelope );
cryptPopData( cryptEnvelope, envelopedData, envelopedDataBufferSize,
&bytesCopied );
cryptDestroyEnvelope( cryptEnvelope );
The same thing in Java (for C# replace the .length with .Length) is:
int cryptEnvelope = crypt.CreateEnvelope( cryptUser,
crypt.FORMAT_SMIME );
/* Add the encryption keyset and recipient email address */
crypt.SetAttribute( cryptEnvelope, crypt.ENVINFO_KEYSET_ENCRYPT,
cryptKeyset );
crypt.SetAttributeString( cryptEnvelope, crypt.ENVINFO_RECIPIENT,
"person@company.com" );
S/MIME Enveloping 79
/* Add the data size information and data, wrap up the processing, and
pop out the processed data */
crypt.SetAttribute( cryptEnvelope, crypt.ENVINFO_DATASIZE,
message.length );
int bytesCopied = crypt.PushData( cryptEnvelope, message );
crypt.FlushData( cryptEnvelope );
bytesCopied = crypt.PopData( cryptEnvelope, envelopedData,
envelopedData.length );
crypt.DestroyEnvelope( cryptEnvelope );
The Visual Basic equivalent is:
cryptCreateEnvelope cryptEnvelope, cryptUser, CRYPT_FORMAT_SMIME
' Add the encryption keyset and recipient email address
cryptSetAttribute cryptEnvelope, CRYPT_ENVINFO_KEYSET_ENCRYPT, _
cryptKeyset
cryptSetAttributeString cryptEnvelope, CRYPT_ENVINFOR_RECIPIENT, _
"person@company.com", 18
' Add the data size information and data, wrap up the processing,
' and pop out the processed data
cryptSetAttribute cryptEnvelope, CRYPT_ENVINFO_DATASIZE, messageLength
cryptPushData cryptEnvelope, message, messageLength, bytesCopied
cryptFlushData cryptEnvelope
cryptPopData cryptEnvelope, envelopedData, envelopedDataBufferSize, _
bytesCopied
cryptDestroyEnvelope cryptEnvelope
For each message recipient that you add, cryptlib will look up the key in the
encryption keyset and add the appropriate information to the envelope to encrypt the
message to that person. This is the recommended way of handling public-key
encrypted enveloping, since it lets cryptlib handle the certificate details for you and
makes it possible to manage problem areas such as cases where the same email
address is present in multiple certificates of which only one is valid for message
encryption. If you want to handle this case yourself, you have to use a keyset query
to search the duplicate certificates and select the appropriate one as described in
“Handling Multiple Certificates with the Same Name” on page 136.
The encryption keyset doesn’t have to be local. If you use an HTTP keyset as
described in “HTTP Keysets” on page 127, cryptlib will fetch the required certificate
directly from the remote CA, saving you the effort of having to maintain and update a
local set of certificates. This use of HTTP keysets makes it very easy to distribute
certificates over the Internet.
De-enveloping works as for standard enveloping:
CRYPT_ENVELOPE cryptEnvelope;
CRYPT_ATTRIBUTE_TYPE requiredAttribute;
int bytesCopied, status;
/* Create the envelope and add the private key keyset and data */
cryptCreateEnvelope( &cryptEnvelope, cryptUser, CRYPT_FORMAT_AUTO );
cryptSetAttribute( cryptEnvelope, CRYPT_ENVINFO_KEYSET_DECRYPT,
privKeyKeyset );
cryptPushData( cryptEnvelope, envelopedData, envelopedDataLength,
&bytesCopied );
/* Find out what we need to continue and, if it's a private key, add
the password to recover it */
cryptGetAttribute( cryptEnvelope, CRYPT_ATTRIBUTE_CURRENT,
&requiredAttribute );
if( requiredAttribute != CRYPT_ENVINFO_PRIVATEKEY )
/* Error */;
cryptSetAttributeString( cryptEnvelope, CRYPT_ENVINFO_PASSWORD,
password, passwordLength );
cryptFlushData( cryptEnvelope );
/* Pop the data and clean up */
cryptPopData( cryptEnvelope, message, messageLength, &bytesCopied );
cryptDestroyEnvelope( cryptEnvelope );
S/MIME
80
More information on public-key encrypted enveloping, including its use with crypto
devices such as smart cards and Fortezza cards, is given in “Public-Key Encrypted
Enveloping” on page 66.
Digitally Signed Enveloping
S/MIME digitally signed enveloping works just like standard enveloping except that
the signing key is restricted to one that has a full chain of X.509 certificates (or at
least a single certificate) attached to it. For example if you wanted to sign data using
a private key contained in sigKeyContext, you would use:
CRYPT_ENVELOPE cryptEnvelope;
int bytesCopied;
cryptCreateEnvelope( &cryptEnvelope, cryptUser, CRYPT_FORMAT_SMIME );
/* Add the signing key */
cryptSetAttribute( cryptEnvelope, CRYPT_ENVINFO_SIGNATURE,
sigKeyContext );
/* Add the data size information and data, wrap up the processing, and
pop out the processed data */
cryptSetAttribute( cryptEnvelope, CRYPT_ENVINFO_DATASIZE,
messageLength );
cryptPushData( cryptEnvelope, message, messageLength, &bytesCopied );
cryptFlushData( cryptEnvelope );
cryptPopData( cryptEnvelope, envelopedData, envelopedDataBufferSize,
&bytesCopied );
cryptDestroyEnvelope( cryptEnvelope );
When you sign data in this manner, cryptlib includes any certificates attached to the
signing key alongside the message. Although you can sign a message using a key
with a single certificate attached to it, it’s safer to use one that has a full certificate
chain associated with it because including only the key certificate with the message
requires that the recipient locate any other certificates that are required to verify the
signature. Since there’s no easy way to do this, signing a message using only a
standalone certificate can cause problems when the recipient tries to verify the
signature.
Verifying the signature on the data works slightly differently from the normal
signature verification process since the signed data already carries with it the
complete certificate chain required for verification. This means that you don’t have
to push a signature verification keyset or key into the envelope because the required
certificate is already included with the data:
CRYPT_ENVELOPE cryptEnvelope;
int bytesCopied, sigCheckStatus;
cryptCreateEnvelope( &cryptEnvelope, cryptUser, CRYPT_FORMAT_AUTO );
/* Push in the enveloped data and pop out the recovered message */
cryptPushData( cryptEnvelope, envelopedData, envelopedDataLength,
&bytesCopied );
cryptFlushData( cryptEnvelope );
cryptPopData( cryptEnvelope, message, messageBufferSize, &bytesCopied
);
/* Determine the result of the signature check */
cryptGetAttribute( cryptEnvelope, CRYPT_ENVINFO_SIGNATURE_RESULT,
&sigCheckStatus );
cryptDestroyEnvelope( cryptEnvelope );
Since the certificate is included with the data, anyone could alter the data, re-sign it
with their own certificate, and then attach their certificate to the data. To avoid this
problem, cryptlib provides the ability to verify the chain of certificates, which works
in combination with cryptlib’s certificate trust manager. You can obtain the
certificate object containing the signing certificate chain with:
S/MIME Enveloping 81
CRYPT_CERTIFICATE cryptCertChain;
cryptGetAttribute( cryptEnvelope, CRYPT_ENVINFO_SIGNATURE,
&cryptCertChain );
You can work with this certificate chain as usual, for example you may want to
display the certificates and any related information to the user. At the least, you
should verify the chain using cryptCheckCert. You may also want to perform a
validity check using RTCS, revocation checking using CRLs or OCSP, and any other
certificate checks that you consider necessary. More details on working with
certificate chains are given in “Certificate Chains” on page 216, details on basic
signed enveloping (including its use with crypto devices like smart cards and
Fortezza cards) are given in “Digitally Signed Enveloping” on page 70, details on
validity checking with RTCS are given in “Certificate Status Checking using RTCS”
on page 153, and details on revocation checking with OCSP are given in “Certificate
Revocation Checking using OCSP” on page 158.
Detached Signatures
So far, the signature for the signed data has always been included with the data itself,
allowing it to be processed as a single blob. cryptlib also provides the ability to
create detached signatures in which the signature is held separate from the data. This
leaves the data being signed unchanged and produces a standalone signature as the
result of the encoding process.
To specify that an envelope should produce a detached signature rather than standard
signed data, you should set the envelope’s CRYPT_ENVINFO_DETACHED-
SIGNATURE attribute to ‘true’ (any nonzero value) before you push in any data
cryptSetAttribute( cryptEnvelope, CRYPT_ENVINFO_DETACHEDSIGNATURE,
1 );
Apart from that, the creation of detached signatures works just like the creation of
standard signed data, with the result of the enveloping process being the standalone
signature (without the data attached):
CRYPT_ENVELOPE cryptEnvelope;
int bytesCopied;
cryptCreateEnvelope( &cryptEnvelope, cryptUser, CRYPT_FORMAT_SMIME );
/* Add the signing key and specify that we're using a detached
signature */
cryptSetAttribute( cryptEnvelope, CRYPT_ENVINFO_SIGNATURE,
sigKeyContext );
cryptSetAttribute( cryptEnvelope, CRYPT_ENVINFO_DETACHEDSIGNATURE,
1 );
/* Add the data size information and data, wrap up the processing, and
pop out the detached signature */
cryptSetAttribute( cryptEnvelope, CRYPT_ENVINFO_DATASIZE,
messageLength );
cryptPushData( cryptEnvelope, message, messageLength, &bytesCopied );
cryptFlushData( cryptEnvelope );
cryptPopData( cryptEnvelope, detachedSignature,
detachedSignatureBufferSize, &bytesCopied );
cryptDestroyEnvelope( cryptEnvelope );
Verifying a detached signature requires an extra processing step since the signature is
no longer bundled with the data. First, you need to push in the detached signature (to
tell cryptlib what to do with any following data). After you’ve pushed in the
signature and followed it up with the usual cryptFlushData to wrap up the
processing, you need to push in the data that was signed by the detached signature as
the second processing step:
CRYPT_ENVELOPE cryptEnvelope;
int bytesCopied, sigCheckStatus;
cryptCreateEnvelope( &cryptEnvelope, cryptUser, CRYPT_FORMAT_AUTO );
S/MIME
82
/* Push in the detached signature */
cryptPushData( cryptEnvelope, detachedSignature, detachedSigLength,
&bytesCopied );
cryptFlushData( cryptEnvelope );
/* Push in the data */
cryptPushData( cryptEnvelope, data, dataLength, NULL );
cryptFlushData( cryptEnvelope );
/* Determine the result of the signature check */
cryptGetAttribute( cryptEnvelope, CRYPT_ENVINFO_SIGNATURE_RESULT,
&sigCheckStatus );
cryptDestroyEnvelope( cryptEnvelope );
Since the data wasn’t enveloped to begin with, there’s nothing to de-envelope, which
means there’s nothing to pop out of the envelope apart from the signing certificate
chain that you can obtain as before by reading the CRYPT_ENVINFO_SIGNATURE
attribute.
In case you’re not sure whether a signature includes data or not, you can query its
status by checking the value of the CRYPT_ENVINFO_DETACHEDSIGNATURE
attribute after you’ve pushed in the signature:
int isDetachedSignature;
/* Push in the signed enveloped data */
cryptPushData( cryptEnvelope, signedData, signedDataLength,
&bytesCopied );
/* Check the signed data type */
cryptGetAttribute( cryptEnvelope, CRYPT_ENVINFO_DETACHEDSIGNATURE,
&isDetachedSignature );
if( isDetachedSignature )
/* Detached signature */;
else
/* Signed data + signature */;
Alternative Detached Signature Processing
Besides the method described above there is a second way to verify a detached
signature which involves hashing the data yourself and then adding the hash to the
envelope rather than pushing the data into the envelope and having it hashed for you.
This is useful in situations where the signed data is present separate from the
signature, or is in a non-standard format (for example an AuthentiCode signed file)
that can’t be recognised by the enveloping code.
Verifying a detached signature in this manner is a slight variation of the standard
detached signature verification process in which you first add to the envelope the
hash value for the signed data and then push in the detached signature:
CRYPT_CONTEXT hashContext;
CRYPT_ENVELOPE cryptEnvelope;
int bytesCopied, sigCheckStatus;
/* Create the hash context and hash the signed data */
cryptCreateContext( &hashContext, cryptUser, CRYPT_ALGO_SHA );
cryptEncrypt( hashContext, signedData, dataLength );
cryptEncrypt( hashContext, signedData, 0 );
/* Create the envelope and add the hash */
cryptCreateEnvelope( &cryptEnvelope, cryptUser, CRYPT_FORMAT_AUTO );
cryptSetAttribute( cryptEnvelope, CRYPT_ENVINFO_HASH, hashContext );
cryptDestroyContext( hashContext );
/* Add the detached signature */
cryptPushData( cryptEnvelope, signatureData, signatureDataLength,
&bytesCopied );
cryptFlushData( cryptEnvelope );
/* Determine the result of the signature check */
cryptGetAttribute( cryptEnvelope, CRYPT_ENVINFO_SIGNATURE_RESULT,
&sigCheckStatus );
S/MIME Enveloping 83
cryptDestroyEnvelope( cryptEnvelope );
When you push in the detached signature cryptlib will verify that the hash
information in the signature matches the hash that you’ve supplied. If the two don’t
match, cryptlib will return CRYPT_ERROR_SIGNATURE to indicate that the
signature can’t be verified using the given values. Because of this check, you must
add the hash before you push in the detached signature.
Extra Signature Information
S/MIME signatures can include with them extra information such as the time at
which the message was signed. Normally cryptlib will add and verify this
information for you automatically, with the details of what’s added based on the
setting of the CRYPT_OPTION_CMS_DEFAULTATTRIBUTES option as
described in “Working with Configuration Options” on page 265. If this option is set
to false (zero), cryptlib won’t add any additional signature information, which
minimises the size of the resulting signature. If this option is set to true (any nonzero
value), cryptlib will add default signing attributes such as the signing time for you.
You can also handle the extra signing information yourself if you require extra
control over what’s included with the signature. The extra information is specified as
a CRYPT_CERTTYPE_CMS_ATTRIBUTES certificate object. To include this
information with the signature you should add it to the envelope alongside the signing
key as CRYPT_ENVINFO_SIGNATURE_EXTRADATA:
CRYPT_ENVELOPE cryptEnvelope;
CRYPT_CERTIFICATE cmsAttributes;
/* Create the CMS attribute object */
cryptCreateCert( &cmsAttributes, cryptUser,
CRYPT_CERTTYPE_CMS_ATTRIBUTES );
/* ... */
/* Create the envelope and add the signing key and signature
information */
cryptCreateEnvelope( &cryptEnvelope, cryptUser, CRYPT_FORMAT_CMS );
cryptSetAttribute( cryptEnvelope, CRYPT_ENVINFO_SIGNATURE,
sigKeyContext );
cryptSetAttribute( cryptEnvelope, CRYPT_ENVINFO_SIGNATURE_EXTRADATA,
cmsAttributes );
cryptDestroyCert( cmsAttributes );
/* Add the data size information and data, wrap up the processing, and
pop out the processed data */
cryptSetAttribute( cryptEnvelope, CRYPT_ENVINFO_DATASIZE,
messageLength );
cryptPushData( cryptEnvelope, message, messageLength, &bytesCopied );
cryptFlushData( cryptEnvelope );
cryptPopData( cryptEnvelope, envelopedData, envelopedDataBufferSize,
&bytesCopied );
cryptDestroyEnvelope( cryptEnvelope );
You can also use this facility to extend or overwrite the attributes added by cryptlib.
For example if you wanted to add a security label to the data being signed, you would
add it to the CMS attribute object and add that to the envelope. cryptlib will then add
any additional required information (for example the signing time) and finally
generate the signature using the combined collection of attributes. This means that
you can fill in whatever attributes you want, and cryptlib till take care of the rest for
you.
Verifying a signature that includes this extra information works just like standard
signature verification since cryptlib handles it all for you. Just as you can obtain a
certificate chain from a signature, you can also obtain the extra signature information
from the envelope:
CRYPT_CERTIFICATE cmsAttributes;
cryptGetAttribute( cryptEnvelope, CRYPT_ENVINFO_SIGNATURE_EXTRADATA,
&cmsAttributes );
S/MIME
84
You can now work with the signing attributes as in the same manner as standard
certificate attributes, for example you may want to display any relevant information
to the user. More details on working with these attributes are given in “Certificate
Extensions” on page 226, and the attributes themselves are covered in “Other
Certificate Object Extensions” on page 244.
The example above created a CRYPT_FORMAT_CMS envelope, which means that
cryptlib will add certain default signing attributes to the signature when it creates it.
If the envelope is created with CRYPT_FORMAT_SMIME instead of
CRYPT_FORMAT_CMS, cryptlib will add an extra set of S/MIME-specific
attributes that indicate the preferred encryption algorithms for use when an S/MIME
enabled mailer is used to send mail to the signer. This information is used for
backwards-compatibility reasons because many S/MIME mailers will quietly default
to using very weak 40-bit keys if they’re not explicitly told to use proper encryption
such as triple DES or AES (cryptlib will never use weakened encryption since it
doesn’t even provide this capability).
Because of this default-to-insecure encryption problem, cryptlib includes with a
CRYPT_FORMAT_SMIME signature additional information to indicate that the
sender should use a non-weakened algorithm such as triple DES, AES, CAST-128, or
IDEA. With a CRYPT_FORMAT_CMS signature this additional S/MIME-specific
information isn’t needed so cryptlib doesn’t include it.
Timestamping
In addition to the standard signature information which is provided by the signer,
cryptlib also supports the use of a message timestamp which is provided by an
external timestamp authority (TSA). Timestamping signed data in an envelope is
very simple and requires only the addition of a CRYPT_ENVINFO_TIMESTAMP
attribute to tell cryptlib which TSA to obtain the timestamp from. The TSA is
specified as a TSP session object as described in “Secure Sessions” on page 96. For
example to specify a TSA located at http://www.timestamp.com/-
tsa/request.cgi, you would create the TSP session with:
CRYPT_SESSION cryptSession;
/* Create the TSP session and add the server name */
cryptCreateSession( &cryptSession, cryptUser, CRYPT_SESSION_TSP );
cryptSetAttributeString( cryptSession, CRYPT_SESSINFO_SERVER_NAME,
"http://www.timestamp.com/tsa/request.cgi", 40 );
You can also specify additional session information in the usual manner for cryptlib
sessions, after which you add the session to the envelope. Once you’ve added it, you
can destroy it since it’s now managed by the envelope:
cryptSetAttribute( cryptEnvelope, CRYPT_ENVINFO_TIMESTAMP,
cryptSession );
cryptDestroySession( cryptSession );
When cryptlib signs the data in the envelope, it will communicate with the TSA to
obtain a timestamp on the signature, which is then included with the other signed
data. This timestamp can be verified at a later date to prove that the envelope was
indeed signed at the indicated time.
Since communicating with a TSA over a network can be a slow process, the signature
generation may take somewhat longer than usual. When the timestamp is created
cryptlib doesn’t communicate any part of the message or any indication of its
contents to the TSA, it merely sends it the message signature information which is
then countersigned by the TSA. In this way no confidential or sensitive information
is leaked to the outside world through the timestamping process.
A time-stamped message appears the same as a standard signed message, with the
exception that the timestamp data is present as additional signature information of
type CRYPT_ENVINFO_TIMESTAMP. You can read the timestamp data in the
same way that you read other extra signature information:
Timestamping 85
CRYPT_ENVELOPE timeStamp;
cryptGetAttribute( cryptEnvelope, CRYPT_ENVINFO_TIMESTAMP,
&timestamp );
The returned timestamp is a standard signed envelope object that you can check in the
usual manner, for example by verifying the signature on the timestamp data and
checking the certificates used for the timestamp signature.
PGP
86
PGP
PGP is a standard format for encrypting, signing, and compressing data. The original
format, PGP 2.x or PGP classic, has since been superseded by OpenPGP, partially
implemented in PGP 5.0 and later fully in NAI PGP, GPG, and various variations
such as the ckt builds. cryptlib can read both the PGP 2.x and OpenPGP formats,
including handling for assorted variations and peculiarities of different
implementations. As output cryptlib produces data in the OpenPGP format, which
can be read by any recent PGP implementation. Note that PGP 2.x used the patented
IDEA encryption algorithm (see “Algorithms” on page 293 for details), if you’re
using the code for commercial purposes you need to either obtain a license for IDEA
or use only the OpenPGP format (which cryptlib does by default anyway, so this
usually isn’t a concern).
You can specify the use of the PGP format when you create an envelope with the
formatting specifier CRYPT_FORMAT_PGP, which tells cryptlib to use the PGP
format rather than the (default) CMS format. cryptlib doesn’t restrict the use of PGP
envelopes to PGP keys. Any type of keys, including standard cryptlib keys and
X.509 certificates, can be used with PGP envelopes. By extension it’s also possible
to use smart cards, crypto accelerators, and Fortezza cards with PGP envelopes (as an
extreme example, it’s possible to use a Fortezza card to create a PGP envelope).
PGP Enveloping
To create an envelope that uses the PGP format, call cryptCreateEnvelope as usual
but specify a format type of CRYPT_FORMAT_PGP instead of the usual
CRYPT_FORMAT_CRYPTLIB:
CRYPT_ENVELOPE cryptEnvelope;
cryptCreateEnvelope( &cryptEnvelope, cryptUser, CRYPT_FORMAT_PGP );
/* Perform enveloping */
cryptDestroyEnvelope( cryptEnvelope );
Creating the envelope in this way restricts cryptlib to using the PGP data format
instead of the more flexible data format which is used for envelopes by default. This
imposes a number of restrictions on the use of envelopes that are described in more
detail in the sections that cover individual PGP enveloping types. One restriction that
applies to all enveloping types is that PGP requires the presence of the
CRYPT_ENVINFO_DATASIZE attribute before data can be enveloped. This
attribute is described in more detail in “Data Size Considerations” on page 52. If you
try to push data into an envelope without setting the CRYPT_ENVINFO_-
DATASIZE attribute, cryptlib will return CRYPT_ERROR_NOTINITED to indicate
that you haven’t provided the information which is needed for the enveloping to
proceed.
Encrypted Enveloping
PGP supports password-based enveloping in the same general way as ordinary
cryptlib envelopes. However, due to constraints imposed by the PGP format, it’s not
possible to mix password- and public-key-based key exchange actions in the same
envelope. In addition it’s not possible to specify more than one password for an
envelope. If you try to add more than one password, or try to add a password when
you’ve already added a public key or vice versa, cryptlib will return
CRYPT_ERROR_INITED to indicate that the key exchange action has already been
set.
Public-key based PGP enveloping works the same way as standard cryptlib
enveloping. For example to encrypt data using the a public key you would use:
PGP Enveloping 87
CRYPT_ENVELOPE cryptEnvelope;
int bytesCopied;
cryptCreateEnvelope( &cryptEnvelope, cryptUser, CRYPT_FORMAT_PGP );
/* Add the public key */
cryptSetAttribute( cryptEnvelope, CRYPT_ENVINFO_PUBLICKEY,
publicKey );
/* Add the data size information and data, wrap up the processing, and
pop out the processed data */
cryptSetAttribute( cryptEnvelope, CRYPT_ENVINFO_DATASIZE,
messageLength );
cryptPushData( cryptEnvelope, message, messageLength, &bytesCopied );
cryptFlushData( cryptEnvelope );
cryptPopData( cryptEnvelope, envelopedData, envelopedDataBufferSize,
&bytesCopied );
cryptDestroyEnvelope( cryptEnvelope );
Since the key will originally have come from a keyset, a simpler alternative to
reading the key yourself and explicitly adding it to the envelope is to let cryptlib do it
for you by first adding the keyset to the envelope and then specifying the email
address of the recipient or recipients of the message with the CRYPT_ENVINFO_-
RECIPIENT attribute:
CRYPT_ENVELOPE cryptEnvelope;
int bytesCopied;
cryptCreateEnvelope( &cryptEnvelope, cryptUser, CRYPT_FORMAT_PGP );
/* Add the encryption keyset and recipient email address */
cryptSetAttribute( cryptEnvelope, CRYPT_ENVINFO_KEYSET_ENCRYPT,
cryptKeyset );
cryptSetAttributeString( cryptEnvelope, CRYPT_ENVINFO_RECIPIENT,
"person@company.com", 18 );
/* Add the data size information and data, wrap up the processing, and
pop out the processed data */
cryptSetAttribute( cryptEnvelope, CRYPT_ENVINFO_DATASIZE,
messageLength );
cryptPushData( cryptEnvelope, message, messageLength, &bytesCopied );
cryptFlushData( cryptEnvelope );
cryptPopData( cryptEnvelope, envelopedData, envelopedDataBufferSize,
&bytesCopied );
cryptDestroyEnvelope( cryptEnvelope );
For each message recipient that you add, cryptlib will look up the key in the
encryption keyset and add the appropriate information to the envelope to encrypt the
message to that person. This is the recommended way of handling public-key
encrypted enveloping, since it lets cryptlib handle the key details for you and makes it
possible to manage problem areas such as cases where the same email address is
present for multiple keys of which only one is valid for message encryption.
De-enveloping works as for standard enveloping:
CRYPT_ENVELOPE cryptEnvelope;
CRYPT_ATTRIBUTE_TYPE requiredAttribute;
int bytesCopied, status;
/* Create the envelope and add the private key keyset and data */
cryptCreateEnvelope( &cryptEnvelope, cryptUser, CRYPT_FORMAT_AUTO );
cryptSetAttribute( cryptEnvelope, CRYPT_ENVINFO_KEYSET_DECRYPT,
privKeyKeyset );
cryptPushData( cryptEnvelope, envelopedData, envelopedDataLength,
&bytesCopied );
PGP
88
/* Find out what we need to continue and, if it's a private key, add
the password to recover it */
cryptGetAttribute( cryptEnvelope, CRYPT_ATTRIBUTE_CURRENT,
&requiredAttribute );
if( requiredAttribute != CRYPT_ENVINFO_PRIVATEKEY )
/* Error */;
cryptSetAttributeString( cryptEnvelope, CRYPT_ENVINFO_PASSWORD,
password, passwordLength );
cryptFlushData( cryptEnvelope );
/* Pop the data and clean up */
cryptPopData( cryptEnvelope, message, messageLength, &bytesCopied );
cryptDestroyEnvelope( cryptEnvelope );
More information on public-key encrypted enveloping, including its use with crypto
devices such as smart cards, is given in “Public-Key Encrypted Enveloping” on page
66.
Digitally Signed Enveloping
PGP digitally signed enveloping works just like standard enveloping. For example if
you wanted to sign data using a private key contained in sigKeyContext, you
would use:
CRYPT_ENVELOPE cryptEnvelope;
int bytesCopied;
cryptCreateEnvelope( &cryptEnvelope, cryptUser, CRYPT_FORMAT_PGP );
/* Add the signing key */
cryptSetAttribute( cryptEnvelope, CRYPT_ENVINFO_SIGNATURE,
sigKeyContext );
/* Add the data size information and data, wrap up the processing, and
pop out the processed data */
cryptSetAttribute( cryptEnvelope, CRYPT_ENVINFO_DATASIZE,
messageLength );
cryptPushData( cryptEnvelope, message, messageLength, &bytesCopied );
cryptFlushData( cryptEnvelope );
cryptPopData( cryptEnvelope, envelopedData, envelopedDataBufferSize,
&bytesCopied );
cryptDestroyEnvelope( cryptEnvelope );
Verifying the signature works in the usual way:
CRYPT_ENVELOPE cryptEnvelope;
int bytesCopied, signatureResult, status;
/* Create the envelope and add the signature check keyset */
cryptCreateEnvelope( &cryptEnvelope, cryptUser, CRYPT_FORMAT_AUTO );
cryptSetAttribute( cryptEnvelope, CRYPT_ENVINFO_KEYSET_SIGCHECK,
sigCheckKeyset );
/* Push in the signed data and pop out the recovered message */
cryptPushData( cryptEnvelope, envelopedData, envelopedDataLength,
&bytesCopied );
cryptFlushData( cryptEnvelope );
cryptPopData( cryptEnvelope, message, messageBufferSize,
&bytesCopied );
/* Determine the result of the signature check */
cryptGetAttribute( cryptEnvelope, CRYPT_ENVINFO_SIGNATURE_RESULT,
&signatureResult );
The signature result will typically be CRYPT_OK (the signature verified), CRYPT_-
ERROR_SIGNATURE (the signature did not verify), or CRYPT_ERROR_-
NOTFOUND (the key needed to check the signature wasn’t found in the keyset).
When you sign data in the PGP format, the nested content type is always set to plain
data. This is a limitation of the PGP format that always signs data as the innermost
step, so that what’s signed is always plain data. In addition to this restriction, it’s not
possible to have more than one signer per envelope. Multiple signers requires the use
of nested envelopes, however it’s necessary to intersperse a layer of encryption or
PGP Enveloping 89
compression between each signature pass since PGP can’t easily distinguish which
signature belongs to which signature pass. In general it’s best not to try to apply
multiple signatures to a piece of data.
Detached Signatures
So far, the signature for the signed data has always been included with the data itself,
allowing it to be processed as a single blob. cryptlib also provides the ability to
create detached signatures in which the signature is held separate from the data. This
leaves the data being signed unchanged and produces a standalone signature as the
result of the encoding process.
To specify that an envelope should produce a detached signature rather than standard
signed data, you should set the envelope’s CRYPT_ENVINFO_DETACHED-
SIGNATURE attribute to ‘true’ (any nonzero value) before you push in any data
cryptSetAttribute( cryptEnvelope, CRYPT_ENVINFO_DETACHEDSIGNATURE, 1
);
Apart from that, the creation of detached signatures works just like the creation of
standard signed data, with the result of the enveloping process being the standalone
signature (without the data attached):
CRYPT_ENVELOPE cryptEnvelope;
int bytesCopied;
cryptCreateEnvelope( &cryptEnvelope, cryptUser, CRYPT_FORMAT_PGP );
/* Add the signing key and specify that we're using a detached
signature */
cryptSetAttribute( cryptEnvelope, CRYPT_ENVINFO_SIGNATURE,
sigKeyContext );
cryptSetAttribute( cryptEnvelope, CRYPT_ENVINFO_DETACHEDSIGNATURE, 1
);
/* Add the data size information and data, wrap up the processing, and
pop out the detached signature */
cryptSetAttribute( cryptEnvelope, CRYPT_ENVINFO_DATASIZE,
messageLength );
cryptPushData( cryptEnvelope, message, messageLength, &bytesCopied );
cryptFlushData( cryptEnvelope );
cryptPopData( cryptEnvelope, detachedSignature,
detachedSignatureBufferSize, &bytesCopied );
cryptDestroyEnvelope( cryptEnvelope );
Verifying a detached signature works somewhat differently from standard cryptlib
detached signature processing since the PGP format doesn’t differentiate between
standard and detached signatures. Because of this lack of differentiation, it’s not
possible for cryptlib to automatically determine whether a signature should have data
associated with it or not. Normally, cryptlib assumes that a signature is associated
with the data being signed, which is the most common case. When verifying a
detached signature, you need to use the alternative signature processing technique
that involves hashing the data yourself and then adding the hash to the envelope
rather than pushing the data into the envelope and having it hashed for you. Since
PGP hashes further information after hashing the data to be signed, you shouldn’t
complete the hashing before you push the hash context into the envelope. This is in
contrast to standard cryptlib detached signature processing which requires that you
complete the hashing before pushing the context into the envelope:
CRYPT_CONTEXT hashContext;
CRYPT_ENVELOPE cryptEnvelope;
int bytesCopied, sigCheckStatus;
/* Create the hash context and hash the signed data without completing
the hashing */
cryptCreateContext( &hashContext, cryptUser, CRYPT_ALGO_SHA );
cryptEncrypt( hashContext, data, dataLength );
PGP
90
/* Create the envelope and add the signature check keyset */
cryptCreateEnvelope( &cryptEnvelope, cryptUser, CRYPT_FORMAT_AUTO );
cryptSetAttribute( cryptEnvelope, CRYPT_ENVINFO_KEYSET_SIGCHECK,
sigCheckKeyset );
/* Add the hash and follow it with the detached signature */
cryptSetAttribute( cryptEnvelope, CRYPT_ENVINFO_HASH, hashContext );
cryptPushData( cryptEnvelope, data, dataLength, &bytesCopied );
cryptFlushData( cryptEnvelope );
cryptDestroyContext( hashContext );
/* Determine the result of the signature check */
cryptGetAttribute( cryptEnvelope, CRYPT_ENVINFO_SIGNATURE_RESULT,
&sigCheckStatus );
cryptDestroyEnvelope( cryptEnvelope );
When you push in the detached signature cryptlib will verify that the hash
information in the signature matches the hash that you’ve supplied. If the two don’t
match, cryptlib will return CRYPT_ERROR_SIGNATURE to indicate that the
signature can’t be verified using the given values. Because of this check, you must
add the hash before you push in the detached signature.
S/MIME email 91
From Envelopes to email
The enveloping process produces binary data as output that then needs to be wrapped
up in the appropriate MIME headers and formatting before it can really be called
S/MIME or PGP mail. The exact mechanisms used depend on the mailer code or
software interface to the mail system you’re using. General guidelines for the
different enveloped data types are given below.
Note that cryptlib is a security toolkit and not a mail client or server. Although
cryptlib provides all the crypto functionality needed to implement S/MIME and PGP,
it cannot send or receive email, process MIME message parts or base64 or PGP
ASCII encoding, or otherwise act as a mail agent. These functions are performed y
mail-handling software. For mail-processing operations you need to combine it with
mail-handling software of the kind described further on.
S/MIME email
MIME is the Internet standard for communicating complex data types via email, and
provides for tagging of message contents and safe encoding of data to allow it to pass
over data paths that would otherwise damage or alter the message contents. Each
MIME message has a top-level type, subtype, and optional parameters. The top-level
types are application, audio, image, message, multipart, text, and
video.
Most of the S/MIME secured types have a content type of application/pkcs7-
mime, except for detached signatures that have a content type of
application/pkcs7-signature. The content type usually also includes an
additional smime-type parameter whose value depends on the S/MIME type and is
described in further detail below. In addition it’s usual to include a content-
disposition field whose value is also explained below.
Since MIME messages are commonly transferred via email and this doesn’t handle
the binary data produced by cryptlib’s enveloping, MIME also defines a means of
encoding binary data as text. This is known as content-transfer-encoding.
Data
The innermost, plain data content should be converted to canonical MIME format and
have a standard MIME header which is appropriate to the data content, with optional
encoding as required. For the most common type of content (plain text), the header
would have a content-type of text/plain, and possibly optional extra information
such as a content transfer encoding (in this case quoted-printable), content
disposition, and whatever other MIME headers are appropriate. This formatting is
normally handled for you by the mailer code or software interface to the mail system
you’re using.
Signed Data
For signed data the MIME type is application/pkcs7-mime, the smime-type
parameter is signed-data, and the extensions for filenames specified as
parameters is .p7m. A typical MIME header for signed data is therefore:
Content-Type: application/pkcs7-mime; smime-type=signed-data;
name=smime.p7m
Content-Transfer-Encoding: base64
Content-Disposition: attachment; filename=smime.p7m
encoded signed data
Detached Signature
Detached signatures represent a special instance of signed data in which the data to be
signed is carried as one MIME body part and the signature is carried as another body
part. The message is encoded as a multipart MIME message with the overall message
From Envelopes to email
92
having a content type of multipart/signed and a protocol parameter of
application/pkcs7-signature, and the signature part having a content type
of application/pkcs7-signature.
Since the data precedes the signature, it’s useful to include the hash algorithm used
for the data as a parameter with the content type (cryptlib processes the signature
before the data so it doesn’t require it, but other implementations may not be able to
do this). The hash algorithm parameter is given by micalg=sha1 or
micalg=md5 as appropriate. When receiving S/MIME messages you can ignore
this value since cryptlib will automatically use the correct type based on the
signature.
A typical MIME header for a detached signature is therefore:
Content-Type: multipart/signed; protocol=application/pkcs7-signature;
micalg=sha1; boundary=boundary
--boundary
Content-Type: text/plain Content-Transfer-Encoding: quoted-printable
signed text
--boundary
Content-Type: application/pkcs7-signature; name=smime.p7s
Content-Transfer-Encoding: base64
Content-Disposition: attachment; filename=smime.p7s
encoded signature
--boundary—
Encrypted Data
For encrypted data the MIME type is application/pkcs7-mime, the smime-
type parameter is enveloped-data, and the extension for filenames specified as
parameters is .p7m. A typical MIME header for encrypted data is therefore:
Content-Type: application/pkcs7-mime; smime-type=enveloped-data;
name=smime.p7m
Content-Transfer-Encoding: base64
Content-Disposition: attachment; filename=smime.p7m
encoded encrypted data
Nested Content
Unlike straight CMS nested content, S/MIME nested content requires a new level of
MIME encoding for each nesting level. For the minimum level of nesting (straight
signed or encrypted data) you need to first MIME-encode the plain data, then
envelope it to create CMS signed or encrypted data, and then MIME-encode it again.
For the typical case of signed, encrypted data you need to MIME-encode, sign,
MIME-encode again, encrypt, and then MIME-encode yet again (rumours that
S/MIME was designed by a consortium of network bandwidth vendors and disk drive
manufacturers are probably unfounded).
Since the nesting information is contained in the MIME headers, you don’t have to
specify the nested content type using CRYPT_ENVINO_CONTENTTYPE as you do
with straight CMS enveloped data (this is one of the few actual differences between
CRYPT_FORMAT_CMS and CRYPT_FORMAT_SMIME), cryptlib will
automatically set the correct content type for you. Conversely, you need to use the
MIME header information rather than CRYPT_ENVINFO_CONTENTTYPE when
de-enveloping data (this will normally be handled for you by the mailer code or
software interface to the mail system you’re using).
PGP email
Traditionally, PGP has employed its own email encapsulation format that predates
MIME and isn’t directly compatible with it. A PGP message is delimited with the
string -----BEGIN PGP MESSAGE----- and -----END PGP MESSAGE--
Implementing S/MIME and PGP email using cryptlib 93
---, with the (binary) message body present in base64-encoded format between the
delimiters. The body is followed by a base64-encoded CRC24 checksum calculated
on the message body before base64-encoding. In addition the body may be preceded
by one or more lines of type-and-value pairs containing additional information such
as software version information, and separated from the body by a blank line. More
details on the format are given in the PGP standards documents.
An example of a PGP email message is:
-----BEGIN PGP MESSAGE-----
Version: cryptlib 3.1
base64-encoded message body
base64-encoded CRC24 checksum
-----END PGP MESSAGE-----
Signed data with a detached signature is delimited with -----BEGIN PGP
SIGNED MESSAGE----- at the start of the message, followed by -----BEGIN
PGP SIGNATURE----- and -----END PGP SIGNATURE----- around the
signature that follows. The signature follows the standard PGP message-encoding
rules given above:
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1
message body
-----BEGIN PGP SIGNATURE-----
Version: cryptlib 3.1
base64-encoded signature
base64-encoded CRC24 checksum
-----END PGP SIGNATURE-----
The example above shows another use for the type-and-value lines, in this case to
indicate the hashing algorithm used in the signature to allow one-pass processing of
the message.
In addition to the traditional PGP format, there exists a mechanism for encapsulating
the traditional PGP format in an additional layer of MIME wrapping. This isn’t true
MIME message handling since it merely wraps MIME headers around the existing
PGP email encapsulation rather than using the full MIME capabilities directly as does
S/MIME. This format is almost never used, with software expected to use the
traditional PGP format instead. If you need more information about PGP/MIME, you
can find it in the PGP standards documentation.
Implementing S/MIME and PGP email using cryptlib
Most of the MIME processing and encoding issues described above will be handled
for you by the mail software that cryptlib is used with. To use cryptlib to handle
S/MIME and PGP email messages, you would typically register the various MIME
types with the mail software and, when they are encountered, the mailer will hand the
message content (the data that remains after the MIME wrapper has been removed) to
cryptlib. cryptlib can then process the data and hand the processed result back to the
mailer. The same applies for generating S/MIME and PGP email messages.
Note that cryptlib is a security toolkit and not a mail client or server. Although
cryptlib provides all the crypto functionality needed to implement S/MIME and PGP,
it cannot send or receive email, process MIME message parts, or otherwise act as a
mail agent. For mail-processing operations you need to combine it with mail-
handling software of the kind described below.
c-client/IMAP4
c-client is a portable Swiss army chainsaw interface to a wide variety of mail and
news handling systems. One of the services it provides is full handling of MIME
message parts which involves breaking a message down into a sequence of BODY
structures each of which contains one MIME body part. The type member contains
the content type (typically TYPEMULTIPART or TYPEAPPLICATION for the
From Envelopes to email
94
types used in S/MIME or PGP), the subtype member contains the MIME subtype,
the parameter list contains any required parameters, and the
contents.binary member contains outgoing binary data straight from the
cryptlib envelope (c-client will perform any necessary encoding such as base64 if
required). All of this information is converted into an appropriately-formatted MIME
message by c-client before transmission.
Since IMAP supports the fetching of individual MIME body parts from a server,
contents.binary can’t be used to access incoming message data since only the
header information may have been fetched, with the actual content still residing on
the server. To fetch a particular body part, you need to use mail_fetchbody. If
the body part is base64-encoded (denoted by the encoding member of the BODY
having the value ENCBASE64) then you also need to call rfc822_base64 to
decode the data so cryptlib can process it. In the unlikely event that the binary data is
encoded as quoted-printable (denoted by ENCQUOTEDPRINTABLE, at least one
broken mailer occasionally does this) you need to call rfc822_qprint instead. In
either case the output can be pushed straight into a cryptlib envelope.
Eudora
Eudora handles MIME content types through plug-in translators that are called
through two functions, ems_can_translate and ems_translate_file.
Eudora calls ems_can_translate with an emsMIMEtype parameter that
contains information on the MIME type contained in the message. If this is an
S/MIME or PGP type (for example application/pkcs7-mime) the function
should return EMSR_NOW to indicate that it can process this MIME type, otherwise is
returns EMSR_CANT_TRANSLATE.
Once the translator has indicated that it can process a message, Eudora calls
ems_translate_file with input and output files to read the data from and write
the processed result to. The translation is just the standard cryptlib enveloping or de-
enveloping process depending on whether the translator is an on-arrival or on-display
one (used for de-enveloping incoming messages) or a Q4-transmission or Q4-
completion one (used for enveloping outgoing messages).
MAPI
MAPI (Microsoft’s mail API) defines two types of mailer extensions that allow
cryptlib-based S/MIME and PGP functionality to be added to Windows mail
applications. The first type is a spooler hook or hook provider, which can be called
on delivery of incoming messages and on transmission of outgoing messages. The
second type is a preprocessor, which is less useful and operates on outgoing messages
only. The major difference between the two in terms of implementation complexity
is that hook providers are full (although simple) MAPI service providers while pre-
processors are extensions to transport providers (that is, if you’ve already written a
transport provider you can add the preprocessor without too much effort; if you don’t
have a transport provider available, it’s quite a bit more work). In general it’s
probably easiest to use a single spooler hook to handle inbound and outbound
messages. You can do this by setting both the HOOK_INBOUND and
HOOK_OUTBOUND flags in the hook’s PR_RESOURCE_FLAGS value.
Messages are passed to hooks via ISpoolerHook::OutboundMsgHook (for
outgoing messages) and ISpoolerHook::InboundMsgHook (for incoming
messages). The hook implementation itself is contained in a DLL that contains the
HPProviderInit entry point and optional further entry points used to configure it,
for example a message service entry point for program-based configuration and a
WIZARDENTRY for user-based configuration.
Windows 95/98/ME and NT/2000/XP/Vista Shell
Windows allows a given MIME content type to be associated with an application to
process it. You can set up this association by calling MIMEAssociationDialog
and setting the MIMEASSOCDLG_FL_REGISTER_ASSOC flag in the
Implementing S/MIME and PGP email using cryptlib 95
dwInFlags parameter, which will (provided the user approves it) create an
association between the content type you specify in the pcszMIMEContentType
parameter and the application chosen by the user. This provides a somewhat crude
but easy to set up mechanism for processing S/MIME and PGP data using a cryptlib-
based application.
Secure Sessions
96
Secure Sessions
cryptlib’s secure session interface provides a session-oriented equivalent to envelope
objects that can be used to secure a communications link with a host or server or
otherwise communicate with another system over a network. Secure sessions can
include SSH, SSL, and TLS sessions, general request/response-style communications
sessions can include protocols such as the certificate management protocol (CMP),
simple certificate enrolment protocol (SCEP), real-time certificate status protocol
(RTCS), online certificate status protocol (OCSP), and timestamping protocol (TSP).
As with envelopes, cryptlib takes care of all of the session details for you so that all
you need to do is provide basic communications information such as the name of the
server or host to connect to and any other information required for the session such as
a password or certificate. cryptlib takes care of establishing the session and
managing the details of the communications channel and its security parameters.
Secure sessions are very similar to envelopes, with the main difference being that
while an envelope is a pure data object into which you can push data and pop the
processed form of the same data, a session is a communications object into which you
push data and then pop data that constitutes a response from a remove server or
client. This means that a session object can be viewed as a bottomless envelope
through which you can push or pop as much data as the other side can accept or
provide.
As with an envelope, you use a session object by adding to it action objects and
resources such as user names and passwords that control the interaction with the
remote server or client and then push in data intended for the remote system and pop
out data coming from the remote system. For example to connect to a server using
SSH and obtain a directory of files using the ls command you would do the
following:
create the session;
add the server name, user name, and password;
activate the session;
push data "ls";
pop the result of the ls command;
destroy the session
That’s all that’s necessary. Since you’ve added a user name and password, cryptlib
knows that it should establish an encrypted session with the remote server and log on
using the given user name and password. From then on all data which is exchanged
with the server is encrypted and authenticated using the SSH protocol.
Creating an SSH server session is equally simple. In this case all you need is the
server key:
create the session;
add the server key;
activate the session;
pop client data;
push server response;
destroy the session
When you activate the session, cryptlib will listen for an incoming connection from a
client and return once a secure connection has been negotiated, at which point
communication proceeds as before.
Creating/Destroying Session Objects
Secure sessions are accessed as session objects that work in the same general manner
as other cryptlib objects. You create a session using cryptCreateSession, specifying
the user who is to own the session object or CRYPT_UNUSED for the default,
normal user, and the type of session that you want to create. This creates a session
object ready for use in securing a communications link or otherwise communicating
with a remote server or client. Once you’ve finished with the session, you use
cryptDestroySession to end the session and destroy the session object:
Creating/Destroying Session Objects 97
CRYPT_SESSION cryptSession;
cryptCreateSession( &cryptSession, cryptUser, sessionType );
/* Communicate with the remote server or client */
cryptDestroySession( cryptSession );
The available session types are:
Session Description
CRYPT_SESSION_CMP Certificate management protocol (CMP).
CRYPT_SESSION_OCSP Online certificate status protocol (OCSP).
CRYPT_SESSION_RTCS Real-time certificate status protocol (RTCS).
CRYPT_SESSION_SCEP Simple certificate enrolment protocol (SCEP).
CRYPT_SESSION_SSH Secure shell (SSH).
CRYPT_SESSION_SSL Secure sockets layer (SSL and TLS).
CRYPT_SESSION_TSP Timestamping protocol (TSP).
This section will mainly cover the secure communications session types such as SSH,
SSL, and TLS. CMP, SCEP, RTCS, and OCSP client sessions are certificate
management services that are covered in “Obtaining Certificates using CMP”,
“Obtaining Certificates using SCEP”, “Certificate Status Checking using RTCS”, and
“Certificate Revocation Checking using OCSP” on pages 158, 153, 153, and 159, and
a TSP client session is an S/MIME service which is covered in “Timestamping” on
page 84. RTCS, OCSP and TSP server sessions are standard session types and are
also covered here. CMP and SCEP server sessions are somewhat more complex and
are covered in “Managing a CA using CMP or SCEP” on page 167. The general
principles covering sessions apply to all of these session types, so you should
familiarise yourself with the operation of session objects and associated issues such
as network proxies and timeouts before trying to work with these other session types.
By default the secure communications session object which is created will have an
internal buffer whose size is appropriate for the type of security protocol which is
being employed. The size of the buffer may affect the amount of extra processing
that cryptlib needs to perform, so that a large buffer can reduce the amount of
copying to and from the buffer, but will consume more memory. If want to use a
buffer for a secure communications session which is larger than the default size, you
can specify its size using the CRYPT_ATTRIBUTE_BUFFERSIZE attribute after
you’ve created the session. For example if you wanted to set the buffer for an SSH
session to 64 kB you would use:
CRYPT_SESSION cryptSession;
cryptCreateSession( &cryptSession, cryptUser, CRYPT_SESSION_SSH );
cryptSetAttribute( cryptSession, CRYPT_ATTRIBUTE_BUFFERSIZE, 65536L );
/* Communicate with the remote server or client */
cryptDestroySession( cryptSession );
Since cryptlib streams data through the session object, the internal buffer size doesn’t
limit how much data you can push and pop (for example you could push 1 MB of
data into a session object with a 32 kB internal buffer), the only reason you’d want to
change the size is to provide tighter control over memory usage by session objects.
Unless you’re absolutely certain that the other side will only send very small data
quantities, you shouldn’t shrink the buffer below the default size set by cryptlib since
the protocols that cryptlib implements have certain fixed bounds on packet sizes that
need to be met, making the buffer too small would make it impossible to process data
being sent by the other side.
Secure Sessions
98
Note that the CRYPT_SESSION is passed to cryptCreateSession by reference as the
function modifies it when it creates the session. In all other routines in cryptlib,
CRYPT_SESSION is passed by value.
Client vs. Server Sessions
cryptlib distinguishes between two types of session objects, client sessions and server
sessions. Client sessions establish a connection to a remote server while server
sessions wait for incoming communications from a remote client. To distinguish
between client and server objects, you use a session type ending in _SERVER when
you create the session object. For example to create an SSL/TLS server object
instead of an SSL/TLS client you would specify its type on creation as CRYPT_-
SESSION_SSL_SERVER instead of CRYPT_SESSION_SSL.
Because server sessions wait for an incoming connection request to arrive, you need
to run each one in its own thread if you want to handle multiple connections
simultaneously (cryptlib is fully thread-safe so there’s no problem with having
multiple threads processing incoming connections). For example to handle up to 10
connections at once you would do the following:
for i = 1 to 10 do
start_thread( server_thread );
where the server_thread is:
loop
create the session;
add required information to the session;
activate the session;
process client request(s);
destroy the session;
More information on using cryptlib with multiple threads is given in “Multi-threaded
cryptlib Operation” on page 46.
Binding to the default ports used by the various session protocols may require special
privileges on some systems that don’t allow normal users to bind to ports below 1024.
If you need to bind to a reserved port you should consult your operating system’s
documentation for details on any restrictions that may apply, and may need to take
special precautions if binding to one of these ports requires the use of elevated
security privileges. Alternatively, you can bind to a non-default port outside the
reserved range by specifying the port using the CRYPT_SESSINFO_SERVER_-
PORT attribute. You can also specify which interface you want to bind to if the
system has more than one by using the CRYPT_SESSINFO_SERVER_NAME
attribute. If you’re testing code before deploying it, it’s a good idea to specify that
you want to bind to localhost to avoid listening on arbitrary externally-visible
interfaces. For example to listen on local port 2000 you would use:
cryptSetAttributeString( cryptSession, CRYPT_SESSINFO_SERVER_NAME,
"localhost", 9 );
cryptSetAttribute( cryptSession, CRYPT_SESSINFO_SERVER_PORT, 2000 );
Server Names/URLs
Server names can be given using IP addresses (in dotted-decimal form for IPv4 or
colon-delimited form for IPv6), DNS names, or full URLs, with optional ports and
other information provided in the usual manner. You can specify the server name or
URL using the CRYPT_SESSINFO_SERVER_NAME attribute and the port (if
you’re not using the default port fro the protocol and it isn’t already specified in the
URL) using the CRYPT_SESSINFO_SERVER_PORT attribute. For example to
specify a connection to the server www.server.com on port 80 you would use:
cryptSetAttributeString( cryptSession, CRYPT_SESSINFO_SERVER_NAME,
"www.server.com", 14 );
cryptSetAttribute( cryptSession, CRYPT_SESSINFO_SERVER_PORT, 80 );
Alternatively, you could specify both in the same name:
cryptSetAttributeString( cryptSession, CRYPT_SESSINFO_SERVER_NAME,
"www.server.com:80", 17 );
Client vs. Server Sessions 99
Since this is a web server for which port 80 is the default port, you could also use the
more common:
cryptSetAttributeString( cryptSession, CRYPT_SESSINFO_SERVER_NAME,
"http://www.server.com", 20 );
SSL and TLS use a predefined port and are often used in conjunction with HTTP, so
you can specify these URLs with or without the http:// or https:// schema
prefixes. SSH similarly uses a predefined port and can be used with or without the
ssh://, scp://, or sftp:// schema prefixes. All of these protocols allow you
to specify user information before the host name, separated with an ‘@’ sign. For
example to connect as “user” to the SSH server ssh.server.com you could use:
cryptSetAttributeString( cryptSession, CRYPT_SESSINFO_SERVER_NAME,
"ssh://user@ssh.server.com", 25 );
which saves having to explicitly specify the user name with the CRYPT_-
SESSINFO_USERNAME attribute.
All of the PKI protocols use HTTP as their transport mechanism, so cryptlib will
automatically default to using HTTP transport whether you include the http://
schema specifier or not. The CMP and TSP protocols also have alternative,
deprecated transport mechanisms identified by cmp://… (for CMP) and tcp://…
(for TSP) instead of http://…. These are occasionally used by CAs or timestamp
servers, you may need to use these instead of the HTTP default.
Server Private Keys
Most server sessions require the use of a private key in one form or another to decrypt
data from the client or sign responses returned to the client. The server key is
typically stored in a private key file, but for extra security may be held in a crypto
device such as a crypto coprocessor or accelerator. In addition, for most session
types the server key needs to be associated with a certificate or certificate chain
leading up to a trusted root certificate, so that you can’t use just a raw private key as
the server key. You can obtain the required certificate or certificate chain by creating
it yourself using cryptlib or by obtaining it from a commercial CA (it’s generally
much cheaper and easier to create it yourself than to obtain one from a third-party
CA).
When you create or obtain the certificate for your server, you may need to specify the
server name in the common name field of the certificate (how to create your own
certificate is explained in “Certificates and Certificate Management” on page 140).
For example if your server was www.companyname.com then the certificate for the
server would contain this as its common name component (you can actually put in
anything you like as the common name component, but this will result in some web
browsers that use your server displaying a warning message when they connect).
SSH server sessions require a raw RSA (or optionally DSA for SSHv2) key, although
you can also use one with a certificate or certificate chain attached. All other session
types require one with certificate(s) attached. You add the server key as the
CRYPT_SESSINFO_PRIVATEKEY attribute, for example to use a private key held
in a crypto device as the server key you would use:
CRYPT_CONTEXT privateKey;
cryptGetPrivateKey( cryptDevice, &privateKey, CRYPT_KEYID_NAME,
serverKeyName, NULL );
cryptSetAttribute( cryptSession, CRYPT_SESSINFO_PRIVATEKEY,
privateKey );
cryptDestroyContext( privateKey );
Note that, as with envelopes, the private key object can be destroyed as soon as it’s
added to the session, since the session maintains its own copy of the object internally.
If you’re worried about some obscure (and rather unlikely) attacks on private keys,
you can enable the CRYPT_OPTION_MISC_SIDECHANNELPROTECTION
option as explained in “Working with Configuration Options” on page 265.
Secure Sessions
100
Establishing a Session
Much of the secure session process is identical to the enveloping process, so you
should familiarise yourself with the general concept of enveloping as described in
“Data Enveloping” on page 49 if you haven’t already done so. The secure session
establishment process involves adding the information which is required to connect to
the remote server as a client or to establish a server, and then activating the session to
establish the secure session or wait for incoming connections. This process of
activating the session has no real equivalent for envelopes because envelopes are
activated automatically the first time data is pushed into them.
Client sessions can also be activated automatically, however the initial handshake
process which is required to activate a session with a remote server is usually lengthy
and complex so it’s generally better to explicitly activate the session under controlled
conditions and have the ability to react to errors in an appropriate manner rather than
to have the session auto-activate itself the first time that data is pushed. Server
sessions that wait for an incoming connection must be explicitly activated, which
causes them to wait for a client connection.
You can activate a session by setting its CRYPT_SESSINFO_ACTIVE attribute to
true (any nonzero value). You can also determine the activation state of a session by
reading this attribute, if it’s set to true then the session is active, otherwise it’s not
active.
Persistent Connections
Some cryptlib session types such as CMP, SCEP, RTCS, OCSP, and TSP provide
request/response protocols rather than continuous secure sessions like SSH and
SSL/TLS. In many cases it’s possible to perform more than one request/response
transaction per session, avoiding the overhead of creating a new connection for each