From e28dc39a68ae73a3bc4aec0dc74722c71d0f791c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9gory=20Soutad=C3=A9?= Date: Mon, 29 Aug 2022 12:18:29 +0200 Subject: [PATCH] Make DRMProcessorClient API more consistent --- include/drmprocessorclient.h | 30 ++--- include/libgourou_common.h | 2 + src/libgourou.cpp | 12 +- utils/drmprocessorclientimpl.cpp | 185 +++++++++++++++++++------------ utils/drmprocessorclientimpl.h | 22 ++-- 5 files changed, 143 insertions(+), 108 deletions(-) diff --git a/include/drmprocessorclient.h b/include/drmprocessorclient.h index 3926595..8d24ed7 100644 --- a/include/drmprocessorclient.h +++ b/include/drmprocessorclient.h @@ -47,20 +47,16 @@ namespace gourou * @param handler Digest handler * @param data Data to digest * @param length Length of data - * - * @return OK/KO */ - virtual int digestUpdate(void* handler, unsigned char* data, unsigned int length) = 0; + virtual void digestUpdate(void* handler, unsigned char* data, unsigned int length) = 0; /** * @brief Finalize digest with remained buffered data and destroy handler * * @param handler Digest handler * @param digestOut Digest result (buffer must be pre allocated with right size) - * - * @return OK/KO */ - virtual int digestFinalize(void* handler, unsigned char* digestOut) = 0; + virtual void digestFinalize(void* handler, unsigned char* digestOut) = 0; /** * @brief Global digest function @@ -69,10 +65,8 @@ namespace gourou * @param data Data to digest * @param length Length of data * @param digestOut Digest result (buffer must be pre allocated with right size) - * - * @return OK/KO */ - virtual int digest(const std::string& digestName, unsigned char* data, unsigned int length, unsigned char* digestOut) = 0; + virtual void digest(const std::string& digestName, unsigned char* data, unsigned int length, unsigned char* digestOut) = 0; }; class RandomInterface @@ -239,7 +233,7 @@ namespace gourou * @param dataOut Encrypted data * @param dataOutLength Length of encrypted data */ - virtual void Encrypt(CRYPTO_ALGO algo, CHAINING_MODE chaining, + virtual void encrypt(CRYPTO_ALGO algo, CHAINING_MODE chaining, const unsigned char* key, unsigned int keyLength, const unsigned char* iv, unsigned int ivLength, const unsigned char* dataIn, unsigned int dataInLength, @@ -256,7 +250,7 @@ namespace gourou * * @return AES handler */ - virtual void* EncryptInit(CRYPTO_ALGO algo, CHAINING_MODE chaining, + virtual void* encryptInit(CRYPTO_ALGO algo, CHAINING_MODE chaining, const unsigned char* key, unsigned int keyLength, const unsigned char* iv=0, unsigned int ivLength=0) = 0; @@ -269,18 +263,18 @@ namespace gourou * @param dataOut Encrypted data * @param dataOutLength Length of encrypted data */ - virtual void EncryptUpdate(void* handler, const unsigned char* dataIn, unsigned int dataInLength, + virtual void encryptUpdate(void* handler, const unsigned char* dataIn, unsigned int dataInLength, unsigned char* dataOut, unsigned int* dataOutLength) = 0; /** - * @brief Finalizeencryption (pad and encrypt last block if needed) + * @brief Finalize encryption (pad and encrypt last block if needed) * Destroy handler at the end * * @param handler Crypto handler * @param dataOut Last block of encrypted data * @param dataOutLength Length of encrypted data */ - virtual void EncryptFinalize(void* handler, unsigned char* dataOut, unsigned int* dataOutLength) = 0; + virtual void encryptFinalize(void* handler, unsigned char* dataOut, unsigned int* dataOutLength) = 0; /** * @brief Do decryption. If length of data is not multiple of block size, PKCS#5 padding is done @@ -296,7 +290,7 @@ namespace gourou * @param dataOut Encrypted data * @param dataOutLength Length of encrypted data */ - virtual void Decrypt(CRYPTO_ALGO algo, CHAINING_MODE chaining, + virtual void decrypt(CRYPTO_ALGO algo, CHAINING_MODE chaining, const unsigned char* key, unsigned int keyLength, const unsigned char* iv, unsigned int ivLength, const unsigned char* dataIn, unsigned int dataInLength, @@ -313,7 +307,7 @@ namespace gourou * * @return AES handler */ - virtual void* DecryptInit(CRYPTO_ALGO algo, CHAINING_MODE chaining, + virtual void* decryptInit(CRYPTO_ALGO algo, CHAINING_MODE chaining, const unsigned char* key, unsigned int keyLength, const unsigned char* iv=0, unsigned int ivLength=0) = 0; @@ -326,7 +320,7 @@ namespace gourou * @param dataOut Decrypted data * @param dataOutLength Length of decrypted data */ - virtual void DecryptUpdate(void* handler, const unsigned char* dataIn, unsigned int dataInLength, + virtual void decryptUpdate(void* handler, const unsigned char* dataIn, unsigned int dataInLength, unsigned char* dataOut, unsigned int* dataOutLength) = 0; /** * @brief Finalize decryption (decrypt last block and remove padding if it is set). @@ -336,7 +330,7 @@ namespace gourou * @param dataOut Last block decrypted data * @param dataOutLength Length of decrypted data */ - virtual void DecryptFinalize(void* handler, unsigned char* dataOut, unsigned int* dataOutLength) = 0; + virtual void decryptFinalize(void* handler, unsigned char* dataOut, unsigned int* dataOutLength) = 0; }; diff --git a/include/libgourou_common.h b/include/libgourou_common.h index 4a71aa3..826a1a3 100644 --- a/include/libgourou_common.h +++ b/include/libgourou_common.h @@ -117,6 +117,8 @@ namespace gourou CLIENT_INVALID_PKCS8, CLIENT_FILE_ERROR, CLIENT_OSSL_ERROR, + CLIENT_CRYPT_ERROR, + CLIENT_DIGEST_ERROR, }; enum DRM_REMOVAL_ERROR { diff --git a/src/libgourou.cpp b/src/libgourou.cpp index d059e93..e828910 100644 --- a/src/libgourou.cpp +++ b/src/libgourou.cpp @@ -869,7 +869,7 @@ namespace gourou // Generate IV in front client->randBytes(encrypted_data, 16); - client->Encrypt(CryptoInterface::ALGO_AES, CryptoInterface::CHAIN_CBC, + client->encrypt(CryptoInterface::ALGO_AES, CryptoInterface::CHAIN_CBC, deviceKey, 16, encrypted_data, 16, data, len, encrypted_data+16, &outLen); @@ -888,7 +888,7 @@ namespace gourou const unsigned char* deviceKey = device->getDeviceKey(); unsigned char* decrypted_data = new unsigned char[len-16]; - client->Decrypt(CryptoInterface::ALGO_AES, CryptoInterface::CHAIN_CBC, + client->decrypt(CryptoInterface::ALGO_AES, CryptoInterface::CHAIN_CBC, deviceKey, 16, data, 16, data+16, len-16, decrypted_data, &outLen); @@ -1009,7 +1009,7 @@ namespace gourou unsigned char* clearRSAKey = new unsigned char[arrayEncryptedKey.size()]; - client->Decrypt(CryptoInterface::ALGO_AES, CryptoInterface::CHAIN_CBC, + client->decrypt(CryptoInterface::ALGO_AES, CryptoInterface::CHAIN_CBC, (const unsigned char*)key, (unsigned int)sizeof(key), (const unsigned char*)iv, (unsigned int)sizeof(iv), (const unsigned char*)arrayEncryptedKey.data(), arrayEncryptedKey.size(), @@ -1109,7 +1109,7 @@ namespace gourou gourou::ByteArray inflateData(true); unsigned int dataOutLength; - client->Decrypt(CryptoInterface::ALGO_AES, CryptoInterface::CHAIN_CBC, + client->decrypt(CryptoInterface::ALGO_AES, CryptoInterface::CHAIN_CBC, decryptedKey+sizeof(decryptedKey)-16, 16, /* Key */ _data, 16, /* IV */ &_data[16], zipData.length()-16, @@ -1313,7 +1313,7 @@ namespace gourou GOUROU_LOG(DEBUG, "Decrypt string " << dictIt->first << " " << dataLength); - client->Decrypt(CryptoInterface::ALGO_RC4, CryptoInterface::CHAIN_ECB, + client->decrypt(CryptoInterface::ALGO_RC4, CryptoInterface::CHAIN_ECB, tmpKey, 16, /* Key */ NULL, 0, /* IV */ encryptedData, dataLength, @@ -1346,7 +1346,7 @@ namespace gourou GOUROU_LOG(DEBUG, "Decrypt stream id " << object->objectId() << ", size " << stream->dataLength()); - client->Decrypt(CryptoInterface::ALGO_RC4, CryptoInterface::CHAIN_ECB, + client->decrypt(CryptoInterface::ALGO_RC4, CryptoInterface::CHAIN_ECB, tmpKey, 16, /* Key */ NULL, 0, /* IV */ encryptedData, dataLength, diff --git a/utils/drmprocessorclientimpl.cpp b/utils/drmprocessorclientimpl.cpp index 9f17171..d451834 100644 --- a/utils/drmprocessorclientimpl.cpp +++ b/utils/drmprocessorclientimpl.cpp @@ -82,32 +82,32 @@ void* DRMProcessorClientImpl::createDigest(const std::string& digestName) if (EVP_DigestInit(md_ctx, md) != 1) { EVP_MD_CTX_free(md_ctx); - return 0; + EXCEPTION(gourou::CLIENT_DIGEST_ERROR, ERR_error_string(ERR_get_error(), NULL)); } return md_ctx; } -int DRMProcessorClientImpl::digestUpdate(void* handler, unsigned char* data, unsigned int length) +void DRMProcessorClientImpl::digestUpdate(void* handler, unsigned char* data, unsigned int length) { - return (EVP_DigestUpdate((EVP_MD_CTX *)handler, data, length)) ? 0 : -1; + if (EVP_DigestUpdate((EVP_MD_CTX *)handler, data, length) != 1) + EXCEPTION(gourou::CLIENT_DIGEST_ERROR, ERR_error_string(ERR_get_error(), NULL)); } -int DRMProcessorClientImpl::digestFinalize(void* handler, unsigned char* digestOut) +void DRMProcessorClientImpl::digestFinalize(void* handler, unsigned char* digestOut) { int res = EVP_DigestFinal((EVP_MD_CTX *)handler, digestOut, NULL); EVP_MD_CTX_free((EVP_MD_CTX *)handler); - return (res == 1) ? 0 : -1; + + if (res <= 0) + EXCEPTION(gourou::CLIENT_DIGEST_ERROR, ERR_error_string(ERR_get_error(), NULL)); } -int DRMProcessorClientImpl::digest(const std::string& digestName, unsigned char* data, unsigned int length, unsigned char* digestOut) +void DRMProcessorClientImpl::digest(const std::string& digestName, unsigned char* data, unsigned int length, unsigned char* digestOut) { void* handler = createDigest(digestName); - if (!handler) - return -1; - if (digestUpdate(handler, data, length)) - return -1; - return digestFinalize(handler, digestOut); + digestUpdate(handler, data, length); + digestFinalize(handler, digestOut); } /* Random interface */ @@ -506,24 +506,27 @@ void DRMProcessorClientImpl::extractCertificate(const unsigned char* RSAKey, uns } /* Crypto interface */ -void DRMProcessorClientImpl::Encrypt(CRYPTO_ALGO algo, CHAINING_MODE chaining, +void DRMProcessorClientImpl::encrypt(CRYPTO_ALGO algo, CHAINING_MODE chaining, const unsigned char* key, unsigned int keyLength, const unsigned char* iv, unsigned int ivLength, const unsigned char* dataIn, unsigned int dataInLength, unsigned char* dataOut, unsigned int* dataOutLength) { - void* handler = EncryptInit(algo, chaining, key, keyLength, iv, ivLength); - EncryptUpdate(handler, dataIn, dataInLength, dataOut, dataOutLength); - EncryptFinalize(handler, dataOut+*dataOutLength, dataOutLength); + void* handler = encryptInit(algo, chaining, key, keyLength, iv, ivLength); + encryptUpdate(handler, dataIn, dataInLength, dataOut, dataOutLength); + encryptFinalize(handler, dataOut+*dataOutLength, dataOutLength); } -void* DRMProcessorClientImpl::EncryptInit(CRYPTO_ALGO algo, CHAINING_MODE chaining, +void* DRMProcessorClientImpl::encryptInit(CRYPTO_ALGO algo, CHAINING_MODE chaining, const unsigned char* key, unsigned int keyLength, const unsigned char* iv, unsigned int ivLength) { EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); - - if (algo == ALGO_AES) + int ret = 0; + + switch (algo) + { + case ALGO_AES: { switch(keyLength) { @@ -531,10 +534,10 @@ void* DRMProcessorClientImpl::EncryptInit(CRYPTO_ALGO algo, CHAINING_MODE chaini switch(chaining) { case CHAIN_ECB: - EVP_EncryptInit(ctx, EVP_aes_128_ecb(), key, iv); + ret = EVP_EncryptInit(ctx, EVP_aes_128_ecb(), key, iv); break; case CHAIN_CBC: - EVP_EncryptInit(ctx, EVP_aes_128_cbc(), key, iv); + ret = EVP_EncryptInit(ctx, EVP_aes_128_cbc(), key, iv); break; default: EXCEPTION(gourou::CLIENT_BAD_CHAINING, "Unknown chaining mode " << chaining); @@ -544,98 +547,134 @@ void* DRMProcessorClientImpl::EncryptInit(CRYPTO_ALGO algo, CHAINING_MODE chaini EVP_CIPHER_CTX_free(ctx); EXCEPTION(gourou::CLIENT_BAD_KEY_SIZE, "Invalid key size " << keyLength); } + break; } - else if (algo == ALGO_RC4) + case ALGO_RC4: { if (keyLength != 16) { EVP_CIPHER_CTX_free(ctx); EXCEPTION(gourou::CLIENT_BAD_KEY_SIZE, "Invalid key size " << keyLength); } - EVP_DecryptInit(ctx, EVP_rc4(), key, iv); + ret = EVP_DecryptInit(ctx, EVP_rc4(), key, iv); + break; } - return ctx; -} - -void* DRMProcessorClientImpl::DecryptInit(CRYPTO_ALGO algo, CHAINING_MODE chaining, - const unsigned char* key, unsigned int keyLength, - const unsigned char* iv, unsigned int ivLength) -{ - EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); - - if (algo == ALGO_AES) - { - switch(keyLength) - { - case 16: - switch(chaining) - { - case CHAIN_ECB: - EVP_DecryptInit(ctx, EVP_aes_128_ecb(), key, iv); - break; - case CHAIN_CBC: - EVP_DecryptInit(ctx, EVP_aes_128_cbc(), key, iv); - break; - default: - EXCEPTION(gourou::CLIENT_BAD_CHAINING, "Unknown chaining mode " << chaining); - } - break; - default: - EVP_CIPHER_CTX_free(ctx); - EXCEPTION(gourou::CLIENT_BAD_KEY_SIZE, "Invalid key size " << keyLength); - } } - else if (algo == ALGO_RC4) + + if (ret <= 0) { - if (keyLength != 16) - { - EVP_CIPHER_CTX_free(ctx); - EXCEPTION(gourou::CLIENT_BAD_KEY_SIZE, "Invalid key size " << keyLength); - } - EVP_DecryptInit(ctx, EVP_rc4(), key, iv); + EVP_CIPHER_CTX_free(ctx); + EXCEPTION(gourou::CLIENT_CRYPT_ERROR, ERR_error_string(ERR_get_error(), NULL)); } return ctx; } -void DRMProcessorClientImpl::EncryptUpdate(void* handler, const unsigned char* dataIn, unsigned int dataInLength, +void* DRMProcessorClientImpl::decryptInit(CRYPTO_ALGO algo, CHAINING_MODE chaining, + const unsigned char* key, unsigned int keyLength, + const unsigned char* iv, unsigned int ivLength) +{ + EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); + int ret = 0; + + switch(algo) + { + case ALGO_AES: + { + switch(keyLength) + { + case 16: + switch(chaining) + { + case CHAIN_ECB: + ret = EVP_DecryptInit(ctx, EVP_aes_128_ecb(), key, iv); + break; + case CHAIN_CBC: + ret = EVP_DecryptInit(ctx, EVP_aes_128_cbc(), key, iv); + break; + default: + EXCEPTION(gourou::CLIENT_BAD_CHAINING, "Unknown chaining mode " << chaining); + } + break; + default: + EVP_CIPHER_CTX_free(ctx); + EXCEPTION(gourou::CLIENT_BAD_KEY_SIZE, "Invalid key size " << keyLength); + } + break; + } + case ALGO_RC4: + { + if (keyLength != 16) + { + EVP_CIPHER_CTX_free(ctx); + EXCEPTION(gourou::CLIENT_BAD_KEY_SIZE, "Invalid key size " << keyLength); + } + ret = EVP_DecryptInit(ctx, EVP_rc4(), key, iv); + break; + } + } + + if (ret <= 0) + { + EVP_CIPHER_CTX_free(ctx); + EXCEPTION(gourou::CLIENT_CRYPT_ERROR, ERR_error_string(ERR_get_error(), NULL)); + } + + return ctx; +} + +void DRMProcessorClientImpl::encryptUpdate(void* handler, const unsigned char* dataIn, unsigned int dataInLength, unsigned char* dataOut, unsigned int* dataOutLength) { - EVP_EncryptUpdate((EVP_CIPHER_CTX*)handler, dataOut, (int*)dataOutLength, dataIn, dataInLength); + int ret = EVP_EncryptUpdate((EVP_CIPHER_CTX*)handler, dataOut, (int*)dataOutLength, dataIn, dataInLength); + + if (ret <= 0) + EXCEPTION(gourou::CLIENT_CRYPT_ERROR, ERR_error_string(ERR_get_error(), NULL)); } -void DRMProcessorClientImpl::EncryptFinalize(void* handler, +void DRMProcessorClientImpl::encryptFinalize(void* handler, unsigned char* dataOut, unsigned int* dataOutLength) { - int len; - EVP_EncryptFinal_ex((EVP_CIPHER_CTX*)handler, dataOut, &len); + int len, ret; + + ret = EVP_EncryptFinal_ex((EVP_CIPHER_CTX*)handler, dataOut, &len); *dataOutLength += len; EVP_CIPHER_CTX_free((EVP_CIPHER_CTX*)handler); + + if (ret <= 0) + EXCEPTION(gourou::CLIENT_CRYPT_ERROR, ERR_error_string(ERR_get_error(), NULL)); } -void DRMProcessorClientImpl::Decrypt(CRYPTO_ALGO algo, CHAINING_MODE chaining, +void DRMProcessorClientImpl::decrypt(CRYPTO_ALGO algo, CHAINING_MODE chaining, const unsigned char* key, unsigned int keyLength, const unsigned char* iv, unsigned int ivLength, const unsigned char* dataIn, unsigned int dataInLength, unsigned char* dataOut, unsigned int* dataOutLength) { - void* handler = DecryptInit(algo, chaining, key, keyLength, iv, ivLength); - DecryptUpdate(handler, dataIn, dataInLength, dataOut, dataOutLength); - DecryptFinalize(handler, dataOut+*dataOutLength, dataOutLength); + void* handler = decryptInit(algo, chaining, key, keyLength, iv, ivLength); + decryptUpdate(handler, dataIn, dataInLength, dataOut, dataOutLength); + decryptFinalize(handler, dataOut+*dataOutLength, dataOutLength); } -void DRMProcessorClientImpl::DecryptUpdate(void* handler, const unsigned char* dataIn, unsigned int dataInLength, +void DRMProcessorClientImpl::decryptUpdate(void* handler, const unsigned char* dataIn, unsigned int dataInLength, unsigned char* dataOut, unsigned int* dataOutLength) { - EVP_DecryptUpdate((EVP_CIPHER_CTX*)handler, dataOut, (int*)dataOutLength, dataIn, dataInLength); + int ret = EVP_DecryptUpdate((EVP_CIPHER_CTX*)handler, dataOut, (int*)dataOutLength, dataIn, dataInLength); + + if (ret <= 0) + EXCEPTION(gourou::CLIENT_CRYPT_ERROR, ERR_error_string(ERR_get_error(), NULL)); } -void DRMProcessorClientImpl::DecryptFinalize(void* handler, unsigned char* dataOut, unsigned int* dataOutLength) +void DRMProcessorClientImpl::decryptFinalize(void* handler, unsigned char* dataOut, unsigned int* dataOutLength) { - int len; - EVP_DecryptFinal_ex((EVP_CIPHER_CTX*)handler, dataOut, &len); + int len, ret; + + ret = EVP_DecryptFinal_ex((EVP_CIPHER_CTX*)handler, dataOut, &len); *dataOutLength += len; EVP_CIPHER_CTX_free((EVP_CIPHER_CTX*)handler); + + if (ret <= 0) + EXCEPTION(gourou::CLIENT_CRYPT_ERROR, ERR_error_string(ERR_get_error(), NULL)); } void* DRMProcessorClientImpl::zipOpen(const std::string& path) diff --git a/utils/drmprocessorclientimpl.h b/utils/drmprocessorclientimpl.h index b878e8c..c2198c6 100644 --- a/utils/drmprocessorclientimpl.h +++ b/utils/drmprocessorclientimpl.h @@ -45,9 +45,9 @@ public: /* Digest interface */ virtual void* createDigest(const std::string& digestName); - virtual int digestUpdate(void* handler, unsigned char* data, unsigned int length); - virtual int digestFinalize(void* handler,unsigned char* digestOut); - virtual int digest(const std::string& digestName, unsigned char* data, unsigned int length, unsigned char* digestOut); + virtual void digestUpdate(void* handler, unsigned char* data, unsigned int length); + virtual void digestFinalize(void* handler,unsigned char* digestOut); + virtual void digest(const std::string& digestName, unsigned char* data, unsigned int length, unsigned char* digestOut); /* Random interface */ virtual void randBytes(unsigned char* bytesOut, unsigned int length); @@ -80,34 +80,34 @@ public: unsigned char** certOut, unsigned int* certOutLength); /* Crypto interface */ - virtual void Encrypt(CRYPTO_ALGO algo, CHAINING_MODE chaining, + virtual void encrypt(CRYPTO_ALGO algo, CHAINING_MODE chaining, const unsigned char* key, unsigned int keyLength, const unsigned char* iv, unsigned int ivLength, const unsigned char* dataIn, unsigned int dataInLength, unsigned char* dataOut, unsigned int* dataOutLength); - virtual void* EncryptInit(CRYPTO_ALGO algo, CHAINING_MODE chaining, + virtual void* encryptInit(CRYPTO_ALGO algo, CHAINING_MODE chaining, const unsigned char* key, unsigned int keyLength, const unsigned char* iv=0, unsigned int ivLength=0); - virtual void EncryptUpdate(void* handler, const unsigned char* dataIn, unsigned int dataInLength, + virtual void encryptUpdate(void* handler, const unsigned char* dataIn, unsigned int dataInLength, unsigned char* dataOut, unsigned int* dataOutLength); - virtual void EncryptFinalize(void* handler, unsigned char* dataOut, unsigned int* dataOutLength); + virtual void encryptFinalize(void* handler, unsigned char* dataOut, unsigned int* dataOutLength); - virtual void Decrypt(CRYPTO_ALGO algo, CHAINING_MODE chaining, + virtual void decrypt(CRYPTO_ALGO algo, CHAINING_MODE chaining, const unsigned char* key, unsigned int keyLength, const unsigned char* iv, unsigned int ivLength, const unsigned char* dataIn, unsigned int dataInLength, unsigned char* dataOut, unsigned int* dataOutLength); - virtual void* DecryptInit(CRYPTO_ALGO algo, CHAINING_MODE chaining, + virtual void* decryptInit(CRYPTO_ALGO algo, CHAINING_MODE chaining, const unsigned char* key, unsigned int keyLength, const unsigned char* iv=0, unsigned int ivLength=0); - virtual void DecryptUpdate(void* handler, const unsigned char* dataIn, unsigned int dataInLength, + virtual void decryptUpdate(void* handler, const unsigned char* dataIn, unsigned int dataInLength, unsigned char* dataOut, unsigned int* dataOutLength); - virtual void DecryptFinalize(void* handler, unsigned char* dataOut, unsigned int* dataOutLength); + virtual void decryptFinalize(void* handler, unsigned char* dataOut, unsigned int* dataOutLength); /* ZIP Interface */ virtual void* zipOpen(const std::string& path);