/* * Copyright (C) 2024 - 2026, Stephan Mueller * * License: see LICENSE file in root directory * * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, ALL OF * WHICH ARE HEREBY DISCLAIMED. NO EVENT SHALL THE AUTHOR BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING ANY WAY OF THE * USE OF THIS SOFTWARE, EVEN IF NOT ADVISED OF THE POSSIBILITY OF SUCH * DAMAGE. */ #ifndef _CRYPTO_PKCS7_H #define _CRYPTO_PKCS7_H #include "ext_headers.h" #include "lc_hash.h" #include "lc_pkcs7_common.h" #include "lc_x509_parser.h" /** @defgroup PKCS7 PKCS#7 Message Parsing * * Note, this description uses the term PKCS#7 since it is a widely known * terminology. However, technically, leancrypto implements CMS (RFC5652). * * Concept of PKCS#7 parsing in leancrypto * * The leancrypto library provides a PKCS#7 parser which can read and understand * PKCS#7 messages. To appropriately use the PKCS#7 parser, please consider * the following rules: * * 1. The parser interprets the provided PKCS#7 data blob and fills a data * structure which allows immediate access to the certificate properties * by the leancrypto code. The data structure \p pkcs7_message is provided * as part of the official header file. But it is NOT considered to be an * API. I.e. member variables or the structure format may change between * versions of leancrypto without announcement. The reason for providing the * data structure in the official header file is to support stack-only * clients. Aas a PKCS#7 message may contain an arbitrary amount of X.509 * certificates and signer information sections, the macro * \p LC_PKCS7_MSG_ON_STACK is provided to allocate stack memory for a given * number of signers and X.509 certificates. If more certificates or signers * are found, heap is transparently allocated. * * 2. The parser fills the data structure with pointers into the original PKCS#7 * data blob. The caller MUST keep the original PKCS#7 data blob at the same * location for the life time of the associated instance of the * \p pkcs7_message data structure. * * 3. The PKCS#7 parser API call only interprets and parses the PKCS#7 data * blob. It does NOT enforce any kind of restrictions or policies. The caller * MUST use the provided verification API to enforce policies on the given * certificate. * * The PKCS#7 message parser currently only covers signature verification to * support use cases of secure boot. * * For performing a proper PKCS#7 validation, execute the following steps: * * 1. Initialize and load the certificate trust store by executing the following * sub tasks. This assumes that each trusted public key is represented as an * X.509 certificate which implies the set of sub steps must be executed for * each certificate that shall be part of the trust store. * * a. Load the X.509 data blob with \p lc_x509_certificate_parse * * b. Register the certificate in the PKCS#7 trust store with * \p lc_pkcs7_trust_anchor_add * * 2. Load the PKCS#7 data blob using \p lc_pkcs7_message_parse. * * 3. Validate the PKCS#7 message which verifies the encapsulated data and the * certificate chain using \p lc_pkcs7_verify. */ /// \cond DO_NOT_DOCUMENT struct lc_pkcs7_parse_context { struct lc_pkcs7_message *msg; /* Message being constructed */ struct lc_x509_certificate *certs; /* Certificate cache */ struct lc_x509_certificate **ppcerts; /* linked list of certs */ const uint8_t *data; /* Start of data */ enum OID last_oid; /* Last OID encountered */ const uint8_t *last_oid_data; size_t last_oid_datalen; unsigned int x509_index; unsigned int sinfo_index; size_t raw_serial_size; size_t raw_issuer_size; size_t raw_skid_size; const uint8_t *raw_serial; const uint8_t *raw_issuer; const uint8_t *raw_skid; unsigned int expect_skid : 1; /* Subject key ID */ /********************************************************************** * Caller-provided data **********************************************************************/ /* * SignerInfo */ enum OID data_type; /* Type of Data */ }; /// \endcond /** * @ingroup PKCS7 * @brief Decode a PKCS#7 message * * The function parses a PKCS#7 data buffer into a data structure that allows * accessing the various data points of the PKCS#7 message. * * \note The \p pkcs7 data structure will contain pointers to the \p data * buffer. I.e. the message parsing analyzes \p data and finds all relevant * data in the raw X.509 data blob. The caller MUST therefore keep the * \p data pointer constant as long as the \p pkcs7 pointer is valid. * * \note This function only loads and parses the PKCS#7 message into the data * structure to allow leancrypto to immediately access the information. This * function call does not validate the PKCS#7 message (except for a self-signed * signature). Thus, the caller MUST apply the PKCS#7 verification API to * validate the PKCS#7 message considering that the loading of the PKCS#7 * message has no information about the use case. * * @param [in,out] pkcs7 The data structure that is filled with all parameters * from the PKCS#7 message data buffer. The buffer must * have been allocated by the caller. It is permissible * to keep it on the stack. * @param [in] data Raw PKCS#7 data blob following RFC5652 * @param [in] datalen Length of the raw PKCS#7 message buffer * * @return 0 on success or < 0 on error */ int lc_pkcs7_decode(struct lc_pkcs7_message *pkcs7, const uint8_t *data, size_t datalen); /** * @ingroup PKCS7 * @brief Initialize a context for decoding a PKCS#7 message * * This function initializes a context where the memory is provided by the * caller. Before invoking \p lc_pkcs7_decode_ctx, the caller may invoke * different lc_pkcs7_decode_ctx_* functions to alter how the PKCS#7 message * is parsed. * * When no further settings are applied on \p ctx, and \p lc_pkcs7_decode_ctx * is invoked immediately after the initialization, the same behavior as * \p lc_pkcs7_decode is triggered. * * @param [in,out] ctx The context data structure shaping the PKCS#7 message * decoding. * * @return 0 on success or < 0 on error */ int lc_pkcs7_decode_ctx_init(struct lc_pkcs7_parse_context *ctx); /** * @ingroup PKCS7 * @brief Clear the context for decoding a PKCS#7 message * * @param [in] ctx Context to be cleared */ void lc_pkcs7_decode_ctx_clear(struct lc_pkcs7_parse_context *ctx); /** * @ingroup PKCS7 * @brief Set the PKCS#7 message definition * * The PKCS#7 message is parsed into this data structure. * * @param [in] ctx The context data structure shaping the PKCS#7 message * generation. * @param [in] pkcs7 The data structure that is filled by decoding. * * @return 0 on success or < 0 on error */ int lc_pkcs7_decode_ctx_set_pkcs7(struct lc_pkcs7_parse_context *ctx, struct lc_pkcs7_message *pkcs7); /** * @ingroup PKCS7 * @brief Set the expected authenticated attribute ContentType OID * * Provide a specific OID for the PKCS#7 message field * SignedData->SignerInfo->authenticated attributes->ContentType. * * @param [in,out] ctx The context data structure shaping the PKCS#7 message * generation. * @param [in] oid Binary version of the OID * * @return 0 on success or < 0 on error */ int lc_pkcs7_decode_ctx_set_aa_content_type(struct lc_pkcs7_parse_context *ctx, enum OID oid); /** * @ingroup PKCS7 * @brief Decode a PKCS#7 message using the provided context. * * The function generates a PKCS#7 data blob from the filled PKCS#7 data * structure. * * Note, this is an enhanced version of \p lc_pkcs7_encode where the * PKCS#7 message is generated by applying the settings defined with \p ctx. * * The signature of the data using the signer is created within this call. * * @param [in] ctx The context data structure shaping the PKCS#7 message * generation. * @param [in] data Raw PKCS#7 data blob following RFC5652 * @param [in] datalen Length of the raw PKCS#7 message buffer * * @return 0 on success or < 0 on error */ int lc_pkcs7_decode_ctx(struct lc_pkcs7_parse_context *ctx, const uint8_t *data, size_t datalen); /** * @ingroup PKCS7 * @brief Clear the resources used by the PKCS#7 message parsing state * * @param [in] pkcs7 Certificate structure to be cleared */ void lc_pkcs7_message_clear(struct lc_pkcs7_message *pkcs7); /** * @ingroup PKCS7 * @brief Get access to the PKCS#7 encapsulated content * * Get access to the data content of the PKCS#7 message. The size of the * header of the ASN.1 object that contains it is also provided and can be used * to adjust *data and *data_len to get the entire object. * * @param [in] pkcs7 The preparsed PKCS#7 message to access * @param [out] data Place to return a pointer to the data * @param [out] datalen Place to return the data length * * @return 0 on success or < 0 on error (returns -ENODATA if the data object was * missing from the message) */ int lc_pkcs7_get_content_data(const struct lc_pkcs7_message *pkcs7, const uint8_t **data, size_t *datalen); struct lc_verify_rules { /** * Specify the key usage flags a signer certificate must possess. * * The allowed flags are documented as part of struct lc_public_key. * If any key usage flag should match (e.g. the key usage field is * irrelevant, use 0). */ uint16_t required_keyusage; /** * Specify the extended key usage flags a signer certificate must * possess. * * The allowed flags are documented as part of struct lc_public_key. * If any key usage flag should match (e.g. the key usage field is * irrelevant, use 0). */ uint16_t required_eku; }; /** * @ingroup PKCS7 * @brief Verify a PKCS#7 message * * Verify a PKCS#7 message is internally consistent - that is, the data digest * matches the digest in the AuthAttrs and any signature in the message or one * of the X.509 certificates it carries that matches another X.509 cert in the * message can be verified. * * If a \p trust_store is provided, perform the certificate validation also * against this trust store to find intermediate or root CA certificates. The * final certificate of a certificate chain must end in a root CA certificate * which must also be present in the \p trust_store. * * The validation against the \p trust_store checks that the certificate chains * inside the PKCS#7 message intersects keys we already know and trust. I.e. at * least one certificate chain must lead to the \p trust_store. * * \note The PKCS7 message block MAY be a detached signature, i.e. the data to * be integrity-protected and authentiated is not embedded into the PKCS7 block. * In this case, the caller MUST use \p lc_pkcs7_supply_detached_data to refer * to this detached data before the \p lc_pkcs7_verify can be executed. * * @param [in] pkcs7 The PKCS#7 message to be verified * @param [in] trust_store Trust store with trust anchor certificates - it MAY * be NULL which implies that no check against the * a trust anchor store is performed. In this case, * the presence of a root certificate is considered * sufficient. * @param [in] verify_rules If non-NULL, the given rules are applied during * certificate verification. * * @return 0 on success or < 0 on error (-ENODATA refers to the case when no * the detached data was not provided) * * Returns, in order of descending priority: * * (*) -EKEYREJECTED if a key was selected that had a usage restriction at * odds with the specified usage, or: * * (*) -EKEYREJECTED if a signature failed to match for which we found an * appropriate X.509 certificate, or: * * (*) -EBADMSG if some part of the message was invalid, or: * * (*) 0 if a signature chain passed verification, or: * * (*) -EKEYREJECTED if a blacklisted key was encountered, or: * * (*) -ENOPKG if none of the signature chains are verifiable because suitable * crypto modules couldn't be found. * */ int lc_pkcs7_verify(struct lc_pkcs7_message *pkcs7, const struct lc_pkcs7_trust_store *trust_store, const struct lc_verify_rules *verify_rules); /** * @ingroup PKCS7 * @brief Supply the data needed to verify a PKCS#7 message * * Supply the detached data needed to verify a PKCS#7 message. Note that no * attempt to retain/pin the data is made. That is left to the caller. The * data will not be modified by pkcs7_verify() and will not be freed when the * PKCS#7 message is freed. * * @param [in] pkcs7 The PKCS#7 message * @param [in] data The data to be verified * @param [in] datalen The amount of data * * * @return 0 on success or < 0 on error (-EEXIST refers to the case if data is * already supplied in the message) */ int lc_pkcs7_supply_detached_data(struct lc_pkcs7_message *pkcs7, const uint8_t *data, size_t datalen); /** * @ingroup PKCS7 * @brief Calculate and return the message digest of the data * * Using the PKCS#7 message, calculate the message digest of the data * encapsulated by the PKCS#7 structure and return the message digest. It uses * the message digest algorithm defined by the PKCS#7 structure. Optionally * the function returns the used hash algorithm. * * \note The returned buffer pointer points to an entry in the \p pkcs7 data * structure. It has the same life time as this structure and thus should * not be freed or modified. * * @param [in] pkcs7 The PKCS#7 message with the encapsulated data * @param [out] message_digest Pointer to the message digest returned to the * caller * @param [out] message_digest_len Length of the message digest returned to the * caller * @param [out] hash_algo Reference to used hash algorithm (if the caller * supplies NULL, no data is returned) * * @return 0 on success or < 0 on error */ int lc_pkcs7_get_digest(struct lc_pkcs7_message *pkcs7, const uint8_t **message_digest, size_t *message_digest_len, const struct lc_hash **hash_algo); /** * @ingroup PKCS7 * @brief Add a certificate to a certificate trust store * * The caller provides the \p trust_store which may be empty at the beginning. * The function initializes the trust store and registers the certificate as * a trusted certificate. * * This function can be called repeatedly with the same trust store to add * an arbitrary number of X.509 certificates. * * Only certificates marked as CAs are allowed to be registered in the trust * store. * * It is permissible to load intermediate certificates. But the loading of such * intermediate certificate requires the presence of the certificate chain * leading to the associated root CA. * * @param [in] trust_store Trust store to add the certificate to * @param [in] x509 Certificate to be added to trust store * * @return 0 on success or < 0 on error (-EKEYREJECTED implies that the * provided certificate does not have a chain to a root CA in the trust store) */ int lc_pkcs7_trust_store_add(struct lc_pkcs7_trust_store *trust_store, struct lc_x509_certificate *x509); /** * @ingroup PKCS7 * @brief Release and clear the trust store * * @param [in] trust_store Trust store be released */ void lc_pkcs7_trust_store_clear(struct lc_pkcs7_trust_store *trust_store); #endif /* _CRYPTO_PKCS7_H */