This document provides an overview of how to use the libcdoc library for encryption and decryption workflows.
CDOC1 vs. CDOC2 Formats
The libcdoc library supports two container formats:
- CDOC1: A legacy format suitable for compatibility with older systems. It provides basic encryption and decryption functionality.
- CDOC2: A modern format with enhanced security features, such as key-server integration and improved cryptographic algorithms.
Common
The libcdoc library is built around three main components that work together to handle encryption, decryption, and key management. It is compatible with Windows, macOS, and Linux for desktop environments, as well as iOS and Android for mobile platforms:
- libcdoc::CryptoBackend
Handles cryptographic operations such as key management, encryption, and decryption. This is the core component responsible for all cryptographic logic.
- libcdoc::NetworkBackend
Manages interactions with external key-servers. This component is optional and is only required if your workflow involves fetching or storing keys on a remote server.
- libcdoc::Configuration
Provides configuration parameters, such as key-server URLs and certificates, to the libcdoc::NetworkBackend. This component ensures that the libcdoc::NetworkBackend has the necessary information to communicate with external servers.
In addition to these backends, the library provides two key classes for working with CDOC containers:
- libcdoc::CDocWriter: Used for creating encrypted CDOC containers. It supports both CDOC1 and CDOC2 formats, allowing you to specify the desired version during the encryption process.
- libcdoc::CDocReader: Used for reading and decrypting CDOC containers. It can automatically detect whether a container is in CDOC1 or CDOC2 format.
These components interact with each other to enable secure encryption and decryption workflows. The following diagram illustrates their relationships:
+-------------------+ +-------------------+
| CryptoBackend |<----->| NetworkBackend |
| (Handles keys, | | (Optional: Key |
| encryption/decrypt| | server interaction|
+-------------------+ +-------------------+
^ ^
| |
+---------------------------+
|
v
+-------------------+
| Configuration |
| (Provides key |
| server parameters)|
+-------------------+
Most methods in the library return a libcdoc::result_t status value, which can indicate the following:
- OK (= 0): Indicates success.
- Positive value: For read and write methods, this indicates success and the number of bytes read or written.
- END_OF_STREAM (= 1): For nextFile methods, this indicates the end of the file list in a multi-file source.
- Any error value (< -1): Indicates failure.
Encryption
Encryption is managed by the libcdoc::CDocWriter object.
Workflow Diagram
The following diagram illustrates the encryption workflow:
+-------------------+ +-------------------+ +-------------------+
| Data Source | | CryptoBackend | | NetworkBackend |
| (e.g., file data) | | (Handles keys, | | (Optional: Key |
| | | encryption logic) | | server interaction|
+-------------------+ +-------------------+ +-------------------+
| | |
v v v
+---------------------------------------------------------------+
| CDocWriter |
| (Manages encryption process, writes encrypted container file) |
+---------------------------------------------------------------+
|
v
+-------------------+
| Output File |
| (Encrypted CDOC) |
+-------------------+
CryptoBackend
To use encryption, you must create or implement a libcdoc::CryptoBackend class. The default implementation is sufficient for public key encryption schemes. For symmetric key encryption, you must implement at least one of the following methods:
getSecret
int getSecret(std::vector<uint8_t>& dst, unsigned int idx)
This method copies either the password (for PBKDF-based keys) or the plain AES key into the dst vector. While simple, this method may expose the password or key in memory.
getKeyMaterial
int getKeyMaterial(std::vector<uint8_t>& dst, const std::vector<uint8_t>& pw_salt, int32_t kdf_iter, unsigned int idx)
This method returns the key material for a symmetric key (either password or plain key) derivation. The default implementation calls getSecret and performs PBKDF2_SHA256 if the key is password-based.
extractHKDF
result_t extractHKDF(std::vector<uint8_t>& dst, const std::vector<uint8_t>& salt, const std::vector<uint8_t>& pw_salt, int32_t kdf_iter, unsigned int idx)
This method calculates the Key Encryption Key (KEK) pre-master from a symmetric key (either password or key-based). The default implementation calls getKeyMaterial and performs a local HKDF extract.
NetworkBackend
If you intend to use a key-server, you must subclass libcdoc::NetworkBackend and implement the following methods:
getClientTLSCertificate
int getClientTLSCertificate(std::vector<uint8_t>& dst)
Returns the client's TLS certificate for authentication with the key-server.
getPeerTLSCertificates
int getPeerTLSCertificates(std::vector<std::vector<uint8_t>> &dst)
Returns the list of acceptable peer certificates for the key-server.
signTLS
HashAlgorithm
Definition CryptoBackend.h:48
Signs the provided digest for TLS authentication.
In addition to implementing libcdoc::NetworkBackend, you must also instantiate a libcdoc::Configuration subclass.
Configuration
The libcdoc::Configuration class is required to retrieve key-server parameters. You must subclass it and implement the following method:
getValue
std::string getValue(std::string_view domain, std::string_view param)
Returns the configuration value for a given domain/parameter combination. For key-servers:
- Domain: The key-server ID.
- Param: KEYSERVER_SEND_URL.
CDocWriter
The libcdoc::CDocWriter object is created using one of the following static methods:
Provides encryption interface.
Definition CDocWriter.h:39
A configuration provider.
Definition Configuration.h:36
An authentication provider.
Definition CryptoBackend.h:42
The DataConsumer class.
Definition Io.h:38
Definition NetworkBackend.h:26
- dst, ofs, path: Input stream to read file content.
- take_ownership: Indicates if libcdoc::CDocWriter takes ownership of src object.
- version: Specifies the file format (1 for CDOC version 1, 2 for CDOC version 2).
- crypto: A libcdoc::CryptoBackend instance (required).
- network: A libcdoc::NetworkBackend instance (optional, for key-server use) or nullptr.
- conf: A libcdoc::Configuration instance (optional, for key-server use) or nullptr.
The libcdoc::CDocWriter does not take ownership of crypto, network and conf objects, so they should be deleted by caller.
Workflow
Add Recipients
Add one or more recipients using:
int addRecipient(const Recipient& rcpt);
Begin Encryption
Start the encryption process:
Write Files
Add files and write their data:
int addFile(const std::string& name, size_t size);
int64_t writeData(const uint8_t* src, size_t size);
Finish Encryption
Finalize the encryption process:
Implementation Example
int getSecret(std::vector<uint8_t>& dst,
unsigned int idx)
override final {
}
};
MyBackend crypto;
delete writer;
virtual result_t addFile(const std::string &name, size_t size)=0
Add a new file to the container.
virtual result_t finishEncryption()=0
Finalizes the encryption stream.
virtual result_t writeData(const uint8_t *src, size_t size)=0
Write data to the encrypted stream.
virtual result_t addRecipient(const Recipient &rcpt)=0
Add recipient to container.
virtual result_t beginEncryption()=0
Prepares the stream for encryption.
virtual result_t getSecret(std::vector< uint8_t > &dst, unsigned int idx)
Get secret value (either password or symmetric key) for a lock.
Definition CryptoBackend.h:126
Decryption
Decryption is managed by the libcdoc::CDocReader object.
Workflow Diagram
The following diagram illustrates the decryption workflow:
+-------------------+ +-------------------+ +-------------------+
| Input File | | CryptoBackend | | NetworkBackend |
| (Encrypted CDOC) | | (Handles keys, | | (Optional: Key |
| | | decryption logic) | | server interaction|
+-------------------+ +-------------------+ +-------------------+
| | |
v v v
+---------------------------------------------------------------+
| CDocReader |
| (Manages decryption process, reads encrypted container file) |
+---------------------------------------------------------------+
|
v
+-------------------+
| Output Files |
| (Decrypted data) |
+-------------------+
CryptoBackend
Create or implement a CryptoBackend class. To decrypt all lock types, at least the following methods have to be implemented:
int deriveECDH1(std::vector<uint8_t>& dst, const std::vector<uint8_t> &public_key, unsigned int idx)
Derives a shared secret from document's public key and recipient's private key by using ECDH1 algorithm.
int decryptRSA(std::vector<uint8_t>& dst, const std::vector<uint8_t>& data, bool oaep, unsigned int idx)
Decrypts data using RSA private key.
Also one of the symmetric key methods listed in encryption section for symmetric key support.
NetworkBackend
It has to be implemented and supplied if server-based capsules are needed.
Configuration
To decrypt server capsules, configuration has to contain the value of the following entry (domain is key-server id as in encryption):
CDocReader
To determine whether a file or libcdoc::DataSource is a valid CDOC container, use:
static int getCDocFileVersion(const std::string &path)
Try to determine the cdoc file version.
The DataSource class.
Definition Io.h:108
Both methods return the container version (1 or 2) or a negative value if the file/source is invalid.
The libcdoc::CDocReader has to be created with one of the following static methods:
Provides decryption interface.
Definition CDocReader.h:40
The libcdoc::CDocReader does not take ownership of crypto, network and conf objects, so they should be deleted by caller.
Workflow
The list of locks in file can be obtained by method:
const std::vector<Lock>& getLocks()
The order of locks is the same as in CDoc container and the 0-based index is used to refer to the lock in decryption methods.
As a convenience method, a public-key lock can be looked up by a certificate (DER-encoded):
result_t getLockForCert(const std::vector<uint8_t>& cert)
Returns the index of a lock that can be opened by the private key of the certificate, or negative number if not found.
Once the correct lock is chosen, the FMK (File Master Key) of the container has to be obtained:
result_t getFMK(std::vector<uint8_t>& fmk, unsigned int lock_idx)
Depending on the lock type the method calls appropriate methods of CryptoBackend (and NetworkBackend) implementation to obtain and decrypt FMK.
After that the FMK can be used to start the encryption:
result_t beginDecryption(const std::vector<uint8_t>& fmk)
Individual files can be read by nextFile method:
result_t nextFile(std::string& name, int64_t& size)
The method returns the name and size of the next file in encrypted stream, or END_OF_STREAM if there are no more files. Due to the structure of CDoc container, files have to be processed sequentially - there is no way to rewind the stream. The name returned is the exact filename in encrypted stream. If the application intends to save the file with the same name, it has to verify that the path is safe.
The actual decrypted data can be read with method:
result_t readData(uint8_t *dst, size_t size)
This reads the data from current file.
When all files are read, the finalizer has to be called:
result_t finishDecryption()
The decrypted data should not be used before successful finalization because it performs the final check of data integrity. If it fails, the data should be assumed incomplete or corrupted.
Implementation Example
Below is an example of how to implement decryption using the libcdoc::CDocReader object and a custom libcdoc::CryptoBackend subclass:
result_t deriveECDH1(std::vector<uint8_t>& dst, const std::vector<uint8_t> &public_key, unsigned int idx) override final {
}
result_t decryptRSA(std::vector<uint8_t>& dst, const std::vector<uint8_t>& data, bool oaep, unsigned int idx) override final {
}
int getSecret(std::vector<uint8_t>& dst, unsigned int idx) override final {
}
};
MyBackend crypto;
std::vector<uint8_t> fmk;
reader->
getFMK(fmk, lock_idx);
std::string name;
int64_t size;
}
delete reader;
virtual result_t finishDecryption()=0
Finish decrypting container.
virtual result_t readData(uint8_t *dst, size_t size)=0
Read data from the current file.
virtual result_t getFMK(std::vector< uint8_t > &fmk, unsigned int lock_idx)=0
Obtain FMK of given lock.
virtual result_t nextFile(std::string &name, int64_t &size)=0
Go to the next file in container.
virtual result_t beginDecryption(const std::vector< uint8_t > &fmk)=0
Start decrypting container.
virtual const std::vector< Lock > & getLocks()=0
Get decryption locks in given document.
@ OK
Operation completed successfully.
Definition CDoc.h:52
This example demonstrates how to:
- Subclass libcdoc::CryptoBackend to implement the required decryption methods (deriveECDH1 and decryptRSA).
- Create a libcdoc::CDocReader instance and use it to process an encrypted CDOC container.
- Retrieve locks, decrypt the File Master Key (FMK), and read the decrypted files.