diff --git a/CMakeLists.txt b/CMakeLists.txt index b5e84655f6..c26e239789 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -36,16 +36,16 @@ set(PACKAGE_URL "https://www.clamav.net/") HexVersion(PACKAGE_VERSION_NUM ${PROJECT_VERSION_MAJOR} ${PROJECT_VERSION_MINOR} ${PROJECT_VERSION_PATCH}) # libtool library versioning rules: http://www.gnu.org/software/libtool/manual/html_node/Updating-version-info.html -set(LIBCLAMAV_CURRENT 12) -set(LIBCLAMAV_REVISION 3) -set(LIBCLAMAV_AGE 0) +set(LIBCLAMAV_CURRENT 13) +set(LIBCLAMAV_REVISION 0) +set(LIBCLAMAV_AGE 1) math(EXPR LIBCLAMAV_SOVERSION "${LIBCLAMAV_CURRENT} - ${LIBCLAMAV_AGE}") set(LIBCLAMAV_VERSION "${LIBCLAMAV_SOVERSION}.${LIBCLAMAV_AGE}.${LIBCLAMAV_REVISION}") HexVersion(LIBCLAMAV_VERSION_NUM ${LIBCLAMAV_CURRENT} ${LIBCLAMAV_REVISION} ${LIBCLAMAV_AGE}) -set(LIBFRESHCLAM_CURRENT 3) -set(LIBFRESHCLAM_REVISION 2) +set(LIBFRESHCLAM_CURRENT 4) +set(LIBFRESHCLAM_REVISION 0) set(LIBFRESHCLAM_AGE 0) math(EXPR LIBFRESHCLAM_SOVERSION "${LIBFRESHCLAM_CURRENT} - ${LIBFRESHCLAM_AGE}") @@ -925,6 +925,12 @@ if(IS_ABSOLUTE ${DATABASE_DIRECTORY}) else() set(DATADIR "${CMAKE_INSTALL_PREFIX}/${DATABASE_DIRECTORY}") endif() +# Absolute path of ClamAV CA certificates directory +if(IS_ABSOLUTE ${CVD_CERTS_DIRECTORY}) + set(CERTSDIR "${CVD_CERTS_DIRECTORY}") +else() + set(CERTSDIR "${CMAKE_INSTALL_PREFIX}/${CVD_CERTS_DIRECTORY}") +endif() # Absolute path of the applications' config directory if(IS_ABSOLUTE ${APP_CONFIG_DIRECTORY}) set(CONFDIR "${APP_CONFIG_DIRECTORY}") diff --git a/CMakeOptions.cmake b/CMakeOptions.cmake index e6e9b11e4e..8b80b4dbfa 100644 --- a/CMakeOptions.cmake +++ b/CMakeOptions.cmake @@ -7,6 +7,9 @@ if(WIN32) set(DATABASE_DIRECTORY "database" CACHE STRING "Database directory.") + set(CVD_CERTS_DIRECTORY + "certs" CACHE STRING + "ClamAV CA certificates directory.") else() set(APP_CONFIG_DIRECTORY "etc" CACHE STRING @@ -14,6 +17,9 @@ else() set(DATABASE_DIRECTORY "share/clamav" CACHE STRING "Database directory.") + set(CVD_CERTS_DIRECTORY + "${APP_CONFIG_DIRECTORY}/certs" CACHE STRING + "ClamAV CA certificates directory.") endif() set(CLAMAV_USER "clamav" CACHE STRING "ClamAV User") diff --git a/Cargo.lock b/Cargo.lock index bc6773fbf7..cb23358a50 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3,10 +3,10 @@ version = 3 [[package]] -name = "adler" -version = "1.0.2" +name = "adler2" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" [[package]] name = "adler32" @@ -38,11 +38,86 @@ dependencies = [ "libc", ] +[[package]] +name = "ansi_term" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" +dependencies = [ + "winapi", +] + +[[package]] +name = "anstream" +version = "0.6.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" + +[[package]] +name = "anstyle-parse" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125" +dependencies = [ + "anstyle", + "windows-sys 0.59.0", +] + +[[package]] +name = "anyhow" +version = "1.0.94" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1fd03a028ef38ba2276dce7e33fcd6369c158a1bca17946c4b1b701891c1ff7" + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi", +] + [[package]] name = "autocfg" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "base64" @@ -69,10 +144,25 @@ dependencies = [ "regex", "rustc-hash", "shlex", - "syn 2.0.60", + "syn 2.0.90", "which", ] +[[package]] +name = "bit-set" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" + [[package]] name = "bit_field" version = "0.10.2" @@ -87,9 +177,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.5.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" [[package]] name = "block-buffer" @@ -108,9 +198,9 @@ checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" [[package]] name = "bytemuck" -version = "1.15.0" +version = "1.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d6d68c57235a3a081186990eca2867354726650f42f7516ca50c28d6281fd15" +checksum = "8b37c88a63ffd85d15b406896cc343916d7cf57838a847b3a6f2ca5d39a5695a" [[package]] name = "byteorder" @@ -120,9 +210,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.6.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" +checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b" [[package]] name = "bzip2-rs" @@ -140,7 +230,7 @@ version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "faeaa693e5a727975a79211b8f35c0cb09b031fdb6eaa4a788bc6713d01488ca" dependencies = [ - "heck", + "heck 0.4.1", "indexmap", "log", "proc-macro2", @@ -154,9 +244,12 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.97" +version = "1.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "099a5357d84c4c61eb35fc8eafa9a79a902c2f76911e5747ced4e032edd8d9b4" +checksum = "27f657647bcff5394bf56c7317665bbf790a137a50eaaa5c6bfbb9e27a518f2d" +dependencies = [ + "shlex", +] [[package]] name = "cexpr" @@ -173,11 +266,22 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "change-case" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2cb9b85fd173fbe43d17902c970f23e5feda453526f4b28dbb4d2e688dffb92c" +dependencies = [ + "fancy-regex", + "lazy_static", + "regex", +] + [[package]] name = "chrono" -version = "0.4.38" +version = "0.4.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" +checksum = "7e36cc9d416881d2e24f9a963be5fb1cd90966419ac844274161d10488b3e825" dependencies = [ "android-tzdata", "iana-time-zone", @@ -185,6 +289,33 @@ dependencies = [ "windows-targets", ] +[[package]] +name = "clam-sigutil" +version = "1.2.0" +dependencies = [ + "anyhow", + "change-case", + "clap 4.5.23", + "downcast-rs", + "enum-variants-strings", + "enumflags2", + "flexi_logger", + "hex", + "hex-literal", + "humantime", + "itertools 0.12.1", + "log", + "num-derive", + "num-traits", + "openssl", + "structopt", + "strum", + "strum_macros", + "thiserror 1.0.69", + "tinyvec", + "url", +] + [[package]] name = "clamav_rust" version = "0.0.1" @@ -194,62 +325,128 @@ dependencies = [ "byteorder", "bzip2-rs", "cbindgen", + "clam-sigutil", "delharc", "flate2", + "glob", "hex", "hex-literal", "image", "inflate", "libc", "log", + "md5", "num-traits", "onenote_parser", + "openssl", "rustdct", "sha1", "sha2", + "tar", "tempfile", - "thiserror", + "thiserror 1.0.69", "transpose", "unicode-segmentation", ] [[package]] name = "clang-sys" -version = "1.7.0" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67523a3b4be3ce1989d607a828d036249522dd9c1c8de7f4dd2dae43a37369d1" +checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" dependencies = [ "glob", "libc", "libloading", ] +[[package]] +name = "clap" +version = "2.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" +dependencies = [ + "ansi_term", + "atty", + "bitflags 1.3.2", + "strsim 0.8.0", + "textwrap", + "unicode-width", + "vec_map", +] + +[[package]] +name = "clap" +version = "4.5.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3135e7ec2ef7b10c6ed8950f0f792ed96ee093fa088608f1c76e569722700c84" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30582fc632330df2bd26877bde0c1f4470d57c582bbc070376afcd04d8cb4838" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim 0.11.1", +] + +[[package]] +name = "clap_derive" +version = "4.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "syn 2.0.90", +] + +[[package]] +name = "clap_lex" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" + [[package]] name = "color_quant" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" +[[package]] +name = "colorchoice" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" + [[package]] name = "core-foundation-sys" -version = "0.8.6" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "cpufeatures" -version = "0.2.12" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" +checksum = "16b80225097f2e5ae4e7179dd2266824648f3e2f49d9134d584b76389d31c4c3" dependencies = [ "libc", ] [[package]] name = "crc32fast" -version = "1.4.0" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3855a8a784b474f333699ef2bbca9db2c4a1f6d9088a90a2d25b1eb53111eaa" +checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" dependencies = [ "cfg-if", ] @@ -275,9 +472,9 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.8.19" +version = "0.8.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" +checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" [[package]] name = "crunchy" @@ -301,7 +498,7 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c93ba2617f5094875af777b3e1e5d66e79d7c832e4ae2e25722c965a482e5a1" dependencies = [ - "bitflags 2.5.0", + "bitflags 2.6.0", "chrono", "memchr", ] @@ -316,17 +513,40 @@ dependencies = [ "crypto-common", ] +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.90", +] + +[[package]] +name = "downcast-rs" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" + [[package]] name = "either" -version = "1.11.0" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a47c1c47d2f5964e29c61246e81db715514cd532db6b5116a25ea3c03d6780a2" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" + +[[package]] +name = "either_n" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c91ae510829160d5cfb19eb4ae7b6e01d44b767ca8f727c6cee936e53cc9ae5" [[package]] name = "encoding_rs" -version = "0.8.34" +version = "0.8.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b45de904aa0b010bce2ab45264d0631681847fa7b6f2eaa7dab7619943bc4f59" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" dependencies = [ "cfg-if", ] @@ -342,24 +562,65 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "enum-variants-strings" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "208ec1cfed58007d641f74552a523a405cd374417ec65ba01fb89ab2796054a1" +dependencies = [ + "enum-variants-strings-derive", +] + +[[package]] +name = "enum-variants-strings-derive" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4acea45431925e008a911e3fded23d302c9eb81493e7b9cae0c5aa29a9342a5a" +dependencies = [ + "either_n", + "proc-macro2", + "quote", + "string-cases", + "syn 1.0.109", +] + +[[package]] +name = "enumflags2" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d232db7f5956f3f14313dc2f87985c58bd2c695ce124c8cdd984e08e15ac133d" +dependencies = [ + "enumflags2_derive", +] + +[[package]] +name = "enumflags2_derive" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de0d48a183585823424a4ce1aa132d174a6a81bd540895822eb4c8373a8e49e8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.90", +] + [[package]] name = "errno" -version = "0.3.8" +version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" +checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" dependencies = [ "libc", - "windows-sys", + "windows-sys 0.59.0", ] [[package]] name = "exr" -version = "1.72.0" +version = "1.73.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "887d93f60543e9a9362ef8a21beedd0a833c5d9610e18c67abe15a5963dcb1a4" +checksum = "f83197f59927b46c04a183a619b7c29df34e63e63c7869320862268c0ef687e0" dependencies = [ "bit_field", - "flume", "half", "lebe", "miniz_oxide", @@ -368,38 +629,88 @@ dependencies = [ "zune-inflate", ] +[[package]] +name = "fancy-regex" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae91abf6555234338687bb47913978d275539235fcb77ba9863b779090b42b14" +dependencies = [ + "bit-set", + "regex", +] + [[package]] name = "fastrand" -version = "2.1.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" [[package]] name = "fdeflate" -version = "0.3.4" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f9bfee30e4dedf0ab8b422f03af778d9612b63f502710fc500a334ebe2de645" +checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c" dependencies = [ "simd-adler32", ] +[[package]] +name = "filetime" +version = "0.2.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35c0522e981e68cbfa8c3f978441a5f34b30b96e146b33cd3359176b50fe8586" +dependencies = [ + "cfg-if", + "libc", + "libredox", + "windows-sys 0.59.0", +] + [[package]] name = "flate2" -version = "1.0.30" +version = "1.0.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f54427cfd1c7829e2a139fcefea601bf088ebca651d2bf53ebc600eac295dae" +checksum = "c936bfdafb507ebbf50b8074c54fa31c5be9a1e7e5f467dd659697041407d07c" dependencies = [ "crc32fast", "miniz_oxide", ] [[package]] -name = "flume" -version = "0.11.0" +name = "flexi_logger" +version = "0.29.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4613c3fa90ebf91dff72ff383a9324329c017819711bda86142be81368ac6fad" +dependencies = [ + "chrono", + "log", + "nu-ansi-term", + "regex", + "thiserror 2.0.6", +] + +[[package]] +name = "foreign-types" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55ac459de2512911e4b674ce33cf20befaba382d05b62b008afc1c8b57cbf181" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" dependencies = [ - "spin", + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[package]] +name = "form_urlencoded" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +dependencies = [ + "percent-encoding", ] [[package]] @@ -444,12 +755,36 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +[[package]] +name = "heck" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" +dependencies = [ + "unicode-segmentation", +] + [[package]] name = "heck" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + [[package]] name = "hex" version = "0.4.3" @@ -468,14 +803,20 @@ version = "0.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" dependencies = [ - "windows-sys", + "windows-sys 0.52.0", ] +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + [[package]] name = "iana-time-zone" -version = "0.1.60" +version = "0.1.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141" +checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" dependencies = [ "android_system_properties", "core-foundation-sys", @@ -494,6 +835,145 @@ dependencies = [ "cc", ] +[[package]] +name = "icu_collections" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locid" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_locid_transform" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_locid_transform_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_locid_transform_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" + +[[package]] +name = "icu_normalizer" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "utf16_iter", + "utf8_iter", + "write16", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" + +[[package]] +name = "icu_properties" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locid_transform", + "icu_properties_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" + +[[package]] +name = "icu_provider" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_provider_macros", + "stable_deref_trait", + "tinystr", + "writeable", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_provider_macros" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.90", +] + +[[package]] +name = "idna" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + [[package]] name = "image" version = "0.24.9" @@ -531,6 +1011,12 @@ dependencies = [ "adler32", ] +[[package]] +name = "is_terminal_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" + [[package]] name = "itertools" version = "0.10.5" @@ -540,11 +1026,20 @@ dependencies = [ "either", ] +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + [[package]] name = "itoa" -version = "1.0.11" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" +checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" [[package]] name = "jpeg-decoder" @@ -557,18 +1052,19 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.69" +version = "0.3.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" +checksum = "6717b6b5b077764fb5966237269cb3c64edddde4b14ce42647430a78ced9e7b7" dependencies = [ + "once_cell", "wasm-bindgen", ] [[package]] name = "lazy_static" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "lazycell" @@ -584,47 +1080,60 @@ checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8" [[package]] name = "libc" -version = "0.2.155" +version = "0.2.168" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" +checksum = "5aaeb2981e0606ca11d79718f8bb01164f1d6ed75080182d3abf017e6d244b6d" [[package]] name = "libloading" -version = "0.8.3" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c2a198fb6b0eada2a8df47933734e6d35d350665a33a3593d7164fa52c75c19" +checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34" dependencies = [ "cfg-if", "windows-targets", ] +[[package]] +name = "libredox" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" +dependencies = [ + "bitflags 2.6.0", + "libc", + "redox_syscall", +] + [[package]] name = "linux-raw-sys" -version = "0.4.13" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" +checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" [[package]] -name = "lock_api" -version = "0.4.12" +name = "litemap" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" -dependencies = [ - "autocfg", - "scopeguard", -] +checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104" + +[[package]] +name = "log" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" [[package]] -name = "log" -version = "0.4.21" +name = "md5" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" +checksum = "490cc448043f947bae3cbee9c203358d62dbee0db12107a74be5c30ccfd09771" [[package]] name = "memchr" -version = "2.7.2" +version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "minimal-lexical" @@ -634,11 +1143,11 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.7.2" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7" +checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" dependencies = [ - "adler", + "adler2", "simd-adler32", ] @@ -652,15 +1161,35 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "nu-ansi-term" +version = "0.50.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4a28e057d01f97e61255210fcff094d74ed0466038633e95017f5beb68e4399" +dependencies = [ + "windows-sys 0.52.0", +] + [[package]] name = "num-complex" -version = "0.4.5" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23c6602fda94a57c990fe0df199a035d83576b496aa29f4e634a8ac6004e68a6" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" dependencies = [ "num-traits", ] +[[package]] +name = "num-derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.90", +] + [[package]] name = "num-integer" version = "0.1.46" @@ -681,31 +1210,69 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.19.0" +version = "1.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" [[package]] name = "onenote_parser" version = "0.3.1" -source = "git+https://github.com/Cisco-Talos/onenote.rs.git?branch=CLAM-2329-new-from-slice#8b450447e58143004b68dd21c11b710fdb79be92" +source = "git+https://github.com/Cisco-Talos/onenote.rs.git?branch=CLAM-2329-new-from-slice#29c08532252b917543ff268284f926f30876bb79" dependencies = [ "bytes", "encoding_rs", "enum-primitive-derive", - "itertools", + "itertools 0.10.5", "num-traits", "paste", - "thiserror", + "thiserror 1.0.69", "uuid", "widestring", ] +[[package]] +name = "openssl" +version = "0.10.68" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6174bc48f102d208783c2c84bf931bb75927a617866870de8a4ea85597f871f5" +dependencies = [ + "bitflags 2.6.0", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.90", +] + +[[package]] +name = "openssl-sys" +version = "0.9.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45abf306cbf99debc8195b66b7346498d7b10c210de50418b5ccd7ceba08c741" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + [[package]] name = "paste" -version = "1.0.14" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" [[package]] name = "peeking_take_while" @@ -713,11 +1280,23 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[package]] +name = "pkg-config" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" + [[package]] name = "png" -version = "0.17.13" +version = "0.17.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06e4b0d3d1312775e782c86c91a111aa1f910cbb65e1337f9975b5f9a554b5e1" +checksum = "b67582bd5b65bdff614270e2ea89a1cf15bef71245cc1e5f7ea126977144211d" dependencies = [ "bitflags 1.3.2", "crc32fast", @@ -728,28 +1307,52 @@ dependencies = [ [[package]] name = "prettyplease" -version = "0.2.19" +version = "0.2.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ac2cf0f2e4f42b49f5ffd07dae8d746508ef7526c13940e5f524012ae6c6550" +checksum = "64d1ec885c64d0457d564db4ec299b2dae3f9c02808b8ad9c3a089c591b18033" dependencies = [ "proc-macro2", - "syn 2.0.60", + "syn 2.0.90", ] [[package]] name = "primal-check" -version = "0.3.3" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9df7f93fd637f083201473dab4fee2db4c429d32e55e3299980ab3957ab916a0" +checksum = "dc0d895b311e3af9902528fbb8f928688abbd95872819320517cc24ca6b2bd08" dependencies = [ "num-integer", ] +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn 1.0.109", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + [[package]] name = "proc-macro2" -version = "1.0.81" +version = "1.0.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d1597b0c024618f09a9c3b8655b7e430397a36d23fdafec26d6965e9eec3eba" +checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" dependencies = [ "unicode-ident", ] @@ -765,9 +1368,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.36" +version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" dependencies = [ "proc-macro2", ] @@ -792,11 +1395,20 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "redox_syscall" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834" +dependencies = [ + "bitflags 2.6.0", +] + [[package]] name = "regex" -version = "1.10.4" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" dependencies = [ "aho-corasick", "memchr", @@ -806,9 +1418,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.6" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" dependencies = [ "aho-corasick", "memchr", @@ -817,9 +1429,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.3" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "rustc-hash" @@ -853,56 +1465,57 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.34" +version = "0.38.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" +checksum = "f93dc38ecbab2eb790ff964bb77fa94faf256fd3e73285fd7ba0903b76bedb85" dependencies = [ - "bitflags 2.5.0", + "bitflags 2.6.0", "errno", "libc", "linux-raw-sys", - "windows-sys", + "windows-sys 0.59.0", ] [[package]] -name = "ryu" -version = "1.0.17" +name = "rustversion" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1" +checksum = "0e819f2bc632f285be6d7cd36e25940d45b2391dd6d9b939e79de557f7014248" [[package]] -name = "scopeguard" -version = "1.2.0" +name = "ryu" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" [[package]] name = "serde" -version = "1.0.200" +version = "1.0.216" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddc6f9cc94d67c0e21aaf7eda3a010fd3af78ebf6e096aa6e2e13c79749cce4f" +checksum = "0b9781016e935a97e8beecf0c933758c97a5520d32930e460142b4cd80c6338e" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.200" +version = "1.0.216" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "856f046b9400cee3c8c94ed572ecdb752444c24528c035cd35882aad6f492bcb" +checksum = "46f859dbbf73865c6627ed570e78961cd3ac92407a2d117204c49232485da55e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.60", + "syn 2.0.90", ] [[package]] name = "serde_json" -version = "1.0.116" +version = "1.0.133" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e17db7126d17feb94eb3fad46bf1a96b034e8aacbc2e775fe81505f8b0b2813" +checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377" dependencies = [ "itoa", + "memchr", "ryu", "serde", ] @@ -948,13 +1561,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[package]] -name = "spin" -version = "0.9.8" +name = "stable_deref_trait" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" -dependencies = [ - "lock_api", -] +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" [[package]] name = "strength_reduce" @@ -962,6 +1572,67 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fe895eb47f22e2ddd4dabc02bce419d2e643c8e3b585c78158b349195bc24d82" +[[package]] +name = "string-cases" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a31d23461f9e0fbe756cf9d5a36be93740fe12c8b094409a5f78f0f912ee2b6f" + +[[package]] +name = "strsim" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "structopt" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c6b5c64445ba8094a6ab0c3cd2ad323e07171012d9c98b0b15651daf1787a10" +dependencies = [ + "clap 2.34.0", + "lazy_static", + "structopt-derive", +] + +[[package]] +name = "structopt-derive" +version = "0.4.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcb5ae327f9cc13b68763b5749770cb9e048a99bd9dfdfa58d0cf05d5f64afe0" +dependencies = [ + "heck 0.3.3", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "strum" +version = "0.26.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" + +[[package]] +name = "strum_macros" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "rustversion", + "syn 2.0.90", +] + [[package]] name = "syn" version = "1.0.109" @@ -975,45 +1646,97 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.60" +version = "2.0.90" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "909518bc7b1c9b779f1bbf07f2929d35af9f0f37e47c6e9ef7f9dddc1e1821f3" +checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] +[[package]] +name = "synstructure" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.90", +] + +[[package]] +name = "tar" +version = "0.4.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c65998313f8e17d0d553d28f91a0df93e4dbbbf770279c7bc21ca0f09ea1a1f6" +dependencies = [ + "filetime", + "libc", + "xattr", +] + [[package]] name = "tempfile" -version = "3.10.1" +version = "3.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" +checksum = "28cce251fcbc87fac86a866eeb0d6c2d536fc16d06f184bb61aeae11aa4cee0c" dependencies = [ "cfg-if", "fastrand", + "once_cell", "rustix", - "windows-sys", + "windows-sys 0.59.0", +] + +[[package]] +name = "textwrap" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" +dependencies = [ + "unicode-width", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", ] [[package]] name = "thiserror" -version = "1.0.59" +version = "2.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fec2a1820ebd077e2b90c4df007bebf344cd394098a13c563957d0afc83ea47" +dependencies = [ + "thiserror-impl 2.0.6", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0126ad08bff79f29fc3ae6a55cc72352056dfff61e3ff8bb7129476d44b23aa" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ - "thiserror-impl", + "proc-macro2", + "quote", + "syn 2.0.90", ] [[package]] name = "thiserror-impl" -version = "1.0.59" +version = "2.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1cd413b5d558b4c5bf3680e324a6fa5014e7b7c067a51e69dbdf47eb7148b66" +checksum = "d65750cab40f4ff1929fb1ba509e9914eb756131cef4210da8d5d700d26f6312" dependencies = [ "proc-macro2", "quote", - "syn 2.0.60", + "syn 2.0.90", ] [[package]] @@ -1027,11 +1750,21 @@ dependencies = [ "weezl", ] +[[package]] +name = "tinystr" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" +dependencies = [ + "displaydoc", + "zerovec", +] + [[package]] name = "tinyvec" -version = "1.6.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" [[package]] name = "toml" @@ -1060,58 +1793,105 @@ checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" [[package]] name = "unicode-ident" -version = "1.0.12" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" [[package]] name = "unicode-segmentation" -version = "1.11.0" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + +[[package]] +name = "unicode-width" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" + +[[package]] +name = "url" +version = "2.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + +[[package]] +name = "utf16_iter" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "utf8parse" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.8.0" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8c5f0a0af699448548ad1a2fbf920fb4bee257eae39953ba95cb84891a0446a" + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "vec_map" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a183cf7feeba97b4dd1c0d46788634f6221d87fa961b305bed08c851829efcc0" +checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" [[package]] name = "version_check" -version = "0.9.4" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "wasm-bindgen" -version = "0.2.92" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" +checksum = "a474f6281d1d70c17ae7aa6a613c87fce69a127e2624002df63dcb39d6cf6396" dependencies = [ "cfg-if", + "once_cell", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" -version = "0.2.92" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" +checksum = "5f89bb38646b4f81674e8f5c3fb81b562be1fd936d84320f3264486418519c79" dependencies = [ "bumpalo", "log", - "once_cell", "proc-macro2", "quote", - "syn 2.0.60", + "syn 2.0.90", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.92" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" +checksum = "2cc6181fd9a7492eef6fef1f33961e3695e4579b9872a6f7c83aee556666d4fe" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -1119,22 +1899,22 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.92" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" +checksum = "30d7a95b763d3c45903ed6c81f156801839e5ee968bb07e534c44df0fcd330c2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.60", + "syn 2.0.90", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.92" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" +checksum = "943aab3fdaaa029a6e0271b35ea10b72b943135afe9bffca82384098ad0e06a6" [[package]] name = "weezl" @@ -1160,6 +1940,28 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7219d36b6eac893fa81e84ebe06485e7dcbb616177469b142df14f1f4deb1311" +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + [[package]] name = "windows-core" version = "0.52.0" @@ -1178,11 +1980,20 @@ dependencies = [ "windows-targets", ] +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets", +] + [[package]] name = "windows-targets" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ "windows_aarch64_gnullvm", "windows_aarch64_msvc", @@ -1196,51 +2007,141 @@ dependencies = [ [[package]] name = "windows_aarch64_gnullvm" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_msvc" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_i686_gnu" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] name = "windows_i686_gnullvm" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_msvc" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_x86_64_gnu" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnullvm" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_msvc" -version = "0.52.5" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "write16" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" + +[[package]] +name = "writeable" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" + +[[package]] +name = "xattr" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8da84f1a25939b27f6820d92aed108f83ff920fdf11a7b19366c27c4cda81d4f" +dependencies = [ + "libc", + "linux-raw-sys", + "rustix", +] + +[[package]] +name = "yoke" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" +dependencies = [ + "serde", + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.90", + "synstructure", +] + +[[package]] +name = "zerofrom" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cff3ee08c995dee1859d998dea82f7374f2826091dd9cd47def953cae446cd2e" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.90", + "synstructure", +] + +[[package]] +name = "zerovec" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" +checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.90", +] [[package]] name = "zune-inflate" diff --git a/INSTALL.md b/INSTALL.md index a25f1dfa2a..41b417e96a 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -272,6 +272,7 @@ Use the following variables to customize the install paths: - `CMAKE_INSTALL_PREFIX`: Customize the install prefix. - `APP_CONFIG_DIRECTORY`: Customize the config directory, may be relative. - `DATABASE_DIRECTORY`: Customize the database directory, may be relative. +- `CVD_CERTS_DIRECTORY`: Customize the ClamAV CA certificates directory, may be relative. - `SYSTEMD_UNIT_DIR`: Install SystemD service files to a specific directory. This example configuration should be familiar if you've used the ClamAV @@ -283,6 +284,7 @@ cmake .. \ -D CMAKE_INSTALL_PREFIX=/usr \ -D CMAKE_INSTALL_LIBDIR=/usr/lib \ -D APP_CONFIG_DIRECTORY=/etc/clamav \ + -D CVD_CERTS_DIRECTORY=/etc/clamav/certs \ -D DATABASE_DIRECTORY=/var/lib/clamav \ -D ENABLE_JSON_SHARED=OFF # require libjson-c to be static # Build @@ -372,6 +374,11 @@ The following is a complete list of CMake options unique to configuring ClamAV: _Default: Windows: `database`, POSIX: `share/clamav`_ +- `CVD_CERTS_DIRECTORY`: ClamAV CA certificates directory. + Relative to the `CMAKE_INSTALL_PREFIX` unless an absolute path is given. + + _Default: Windows: `certs`, POSIX: `etc/certs`_ + - `CLAMAV_USER`: ClamAV User (POSIX-only). _Default: `clamav`_ @@ -688,7 +695,7 @@ ClamAV has two bytecode runtimes: execution should be faster. Not all scans will run bytecode signatures, so performance testing will depend heavily depending on what files are tested. - We can work with LLVM 8.0 to 13.x. + We can work with LLVM 8.0 to 13.x. #### Interpreter Bytecode Runtime diff --git a/clamav-config.h.cmake.in b/clamav-config.h.cmake.in index a016d24e70..856f25cc14 100644 --- a/clamav-config.h.cmake.in +++ b/clamav-config.h.cmake.in @@ -78,6 +78,9 @@ /* where to look for the config file */ #define CONFDIR "@CONFDIR@" + +/* where to look for the CA certificate file(s) */ +#define CERTSDIR "@CERTSDIR@" #endif /* Have sys/fanotify.h */ diff --git a/clamd/clamd.c b/clamd/clamd.c index 191d9a6734..1474cbe009 100644 --- a/clamd/clamd.c +++ b/clamd/clamd.c @@ -109,6 +109,18 @@ static void help(void) printf(" --fail-if-cvd-older-than=days Return with a nonzero error code if virus database outdated\n"); printf(" --datadir=DIRECTORY Load signatures from DIRECTORY\n"); printf(" --pid=FILE -p FILE Write the daemon's pid to FILE\n"); + printf(" --cvdcertsdir=DIRECTORY Specify a directory containing the root\n"); + printf(" CA cert needed to verify detached CVD digital signatures.\n"); + printf(" If not provided, then clamscan will look in:\n"); + printf(" " CERTSDIR "\n"); + printf("\n"); + printf("Environment Variables:\n"); + printf("\n"); + printf(" LD_LIBRARY_PATH May be used on startup to find the libclamunrar_iface\n"); + printf(" shared library module to enable RAR archive support.\n"); + printf(" CVD_CERTS_DIR Specify a directory containing the root CA cert needed\n"); + printf(" to verify detached CVD digital signatures.\n"); + printf(" If not provided, then clamd will look in the default directory.\n"); printf("\n"); printf("Pass in - as the filename for stdin.\n"); printf("\n"); @@ -160,6 +172,8 @@ int main(int argc, char **argv) pid_t mainpid = 0; mode_t old_umask = 0; const char *user_name = NULL; + char *cvdcertsdir = NULL; + STATBUF statbuf; if (check_flevel()) exit(1); @@ -577,6 +591,32 @@ int main(int argc, char **argv) } } + cvdcertsdir = optget(opts, "cvdcertsdir")->strarg; + if (NULL == cvdcertsdir) { + // Check if the CVD_CERTS_DIR environment variable is set + cvdcertsdir = getenv("CVD_CERTS_DIR"); + + // If not, use the default value + if (NULL == cvdcertsdir) { + cvdcertsdir = CERTSDIR; + } + } + + if (LSTAT(cvdcertsdir, &statbuf) == -1) { + logg(LOGG_ERROR, + "ClamAV CA certificates directory is missing: %s\n" + "It should have been provided as a part of installation.", + cvdcertsdir); + ret = 1; + break; + } + + if ((ret = cl_engine_set_str(engine, CL_ENGINE_CVDCERTSDIR, cvdcertsdir))) { + logg(LOGG_ERROR, "cli_engine_set_str(CL_ENGINE_CVDCERTSDIR) failed: %s\n", cl_strerror(ret)); + ret = 1; + break; + } + cl_engine_set_clcb_hash(engine, hash_callback); cl_engine_set_clcb_virus_found(engine, clamd_virus_found_cb); diff --git a/clamscan/clamscan.c b/clamscan/clamscan.c index f3e32f26fa..60987596cd 100644 --- a/clamscan/clamscan.c +++ b/clamscan/clamscan.c @@ -342,6 +342,10 @@ void help(void) mprintf(LOGG_INFO, " --pcre-recmatch-limit=#n Maximum recursive calls to the PCRE match function.\n"); mprintf(LOGG_INFO, " --pcre-max-filesize=#n Maximum size file to perform PCRE subsig matching.\n"); mprintf(LOGG_INFO, " --disable-cache Disable caching and cache checks for hash sums of scanned files.\n"); + mprintf(LOGG_INFO, " --cvdcertsdir=DIRECTORY Specify a directory containing the root\n"); + mprintf(LOGG_INFO, " CA cert needed to verify detached CVD digital signatures.\n"); + mprintf(LOGG_INFO, " If not provided, then clamscan will look in:\n"); + mprintf(LOGG_INFO, " " CERTSDIR "\n"); mprintf(LOGG_INFO, "\n"); mprintf(LOGG_INFO, "Pass in - as the filename for stdin.\n"); mprintf(LOGG_INFO, "\n"); diff --git a/clamscan/manager.c b/clamscan/manager.c index cd9564b2dd..ec3e5e1f59 100644 --- a/clamscan/manager.c +++ b/clamscan/manager.c @@ -1051,6 +1051,9 @@ int scanmanager(const struct optstruct *opts) struct engine_free_progress engine_free_progress_ctx = {0}; #endif + char *cvdcertsdir = NULL; + STATBUF statbuf; + /* Initialize scan options struct */ memset(&options, 0, sizeof(struct cl_scan_options)); @@ -1249,6 +1252,34 @@ int scanmanager(const struct optstruct *opts) } } + cvdcertsdir = optget(opts, "cvdcertsdir")->strarg; + if (NULL == cvdcertsdir) { + // Check if the CVD_CERTS_DIR environment variable is set + cvdcertsdir = getenv("CVD_CERTS_DIR"); + + // If not, use the default value + if (NULL == cvdcertsdir) { + cvdcertsdir = CERTSDIR; + } + } + + if (LSTAT(cvdcertsdir, &statbuf) == -1) { + logg(LOGG_ERROR, + "ClamAV CA certificates directory is missing: %s\n" + "It should have been provided as a part of installation.", + cvdcertsdir); + + ret = 2; + goto done; + } + + if ((ret = cl_engine_set_str(engine, CL_ENGINE_CVDCERTSDIR, cvdcertsdir))) { + logg(LOGG_ERROR, "cli_engine_set_str(CL_ENGINE_CVDCERTSDIR) failed: %s\n", cl_strerror(ret)); + + ret = 2; + goto done; + } + if ((opt = optget(opts, "database"))->active) { while (opt) { if (optget(opts, "fail-if-cvd-older-than")->enabled) { diff --git a/cmake/FindRust.cmake b/cmake/FindRust.cmake index 5d7e838bd8..19c6e9f717 100644 --- a/cmake/FindRust.cmake +++ b/cmake/FindRust.cmake @@ -224,6 +224,7 @@ endfunction() function(add_rust_executable) set(options) set(oneValueArgs TARGET SOURCE_DIRECTORY BINARY_DIRECTORY) + set(multiValueArgs ENVIRONMENT) cmake_parse_arguments(ARGS "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) if(WIN32) @@ -238,13 +239,15 @@ function(add_rust_executable) list(APPEND MY_CARGO_ARGS "--target-dir" ${ARGS_BINARY_DIRECTORY}) list(JOIN MY_CARGO_ARGS " " MY_CARGO_ARGS_STRING) + list(APPEND ARGS_ENVIRONMENT "CARGO_TARGET_DIR=${ARGS_BINARY_DIRECTORY}" "CARGO_INCLUDE_DIRECTORIES=\"${ARGS_INCLUDE_DIRECTORIES}\"") + # Build the executable. add_custom_command( OUTPUT "${OUTPUT}" - COMMAND ${CMAKE_COMMAND} -E env "CARGO_TARGET_DIR=${ARGS_BINARY_DIRECTORY}" "CARGO_INCLUDE_DIRECTORIES=\"${ARGS_INCLUDE_DIRECTORIES}\"" ${cargo_EXECUTABLE} ${MY_CARGO_ARGS} + COMMAND ${CMAKE_COMMAND} -E env ${ARGS_ENVIRONMENT} ${cargo_EXECUTABLE} ${MY_CARGO_ARGS} WORKING_DIRECTORY "${ARGS_SOURCE_DIRECTORY}" DEPENDS ${EXE_SOURCES} - COMMENT "Building ${ARGS_TARGET} in ${ARGS_BINARY_DIRECTORY} with:\n\t ${cargo_EXECUTABLE} ${MY_CARGO_ARGS_STRING}") + COMMENT "Building ${ARGS_TARGET} in ${ARGS_BINARY_DIRECTORY} with:\n\t ${cargo_EXECUTABLE} ${MY_CARGO_ARGS_STRING}\n Environment: ${ARGS_ENVIRONMENT}") # Create a target from the build output add_custom_target(${ARGS_TARGET}_target @@ -271,6 +274,7 @@ endfunction() function(add_rust_library) set(options) set(oneValueArgs TARGET SOURCE_DIRECTORY BINARY_DIRECTORY PRECOMPILE_TESTS INCLUDE_DIRECTORIES) + set(multiValueArgs ENVIRONMENT) cmake_parse_arguments(ARGS "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) if(WIN32) @@ -288,39 +292,41 @@ function(add_rust_library) list(APPEND MY_CARGO_ARGS "--target-dir" ${ARGS_BINARY_DIRECTORY}) list(JOIN MY_CARGO_ARGS " " MY_CARGO_ARGS_STRING) + list(APPEND ARGS_ENVIRONMENT "CARGO_CMD=build" "CARGO_TARGET_DIR=${ARGS_BINARY_DIRECTORY}" "MAINTAINER_MODE=${MAINTAINER_MODE}" "CARGO_INCLUDE_DIRECTORIES=\"${ARGS_INCLUDE_DIRECTORIES}\"" "RUSTFLAGS=${RUSTFLAGS}") + # Build the library and generate the c-binding if("${CMAKE_OSX_ARCHITECTURES}" MATCHES "^(arm64;x86_64|x86_64;arm64)$") add_custom_command( OUTPUT "${OUTPUT}" - COMMAND ${CMAKE_COMMAND} -E env "CARGO_CMD=build" "CARGO_TARGET_DIR=${ARGS_BINARY_DIRECTORY}" "MAINTAINER_MODE=${MAINTAINER_MODE}" "CARGO_INCLUDE_DIRECTORIES=\"${ARGS_INCLUDE_DIRECTORIES}\"" "RUSTFLAGS=${RUSTFLAGS}" ${cargo_EXECUTABLE} ${MY_CARGO_ARGS} --target=x86_64-apple-darwin - COMMAND ${CMAKE_COMMAND} -E env "CARGO_CMD=build" "CARGO_TARGET_DIR=${ARGS_BINARY_DIRECTORY}" "MAINTAINER_MODE=${MAINTAINER_MODE}" "CARGO_INCLUDE_DIRECTORIES=\"${ARGS_INCLUDE_DIRECTORIES}\"" "RUSTFLAGS=${RUSTFLAGS}" ${cargo_EXECUTABLE} ${MY_CARGO_ARGS} --target=aarch64-apple-darwin + COMMAND ${CMAKE_COMMAND} -E env ${ARGS_ENVIRONMENT} ${cargo_EXECUTABLE} ${MY_CARGO_ARGS} --target=x86_64-apple-darwin + COMMAND ${CMAKE_COMMAND} -E env ${ARGS_ENVIRONMENT} ${cargo_EXECUTABLE} ${MY_CARGO_ARGS} --target=aarch64-apple-darwin COMMAND ${CMAKE_COMMAND} -E make_directory "${ARGS_BINARY_DIRECTORY}/${RUST_COMPILER_TARGET}/${CARGO_BUILD_TYPE}" COMMAND lipo -create ${ARGS_BINARY_DIRECTORY}/x86_64-apple-darwin/${CARGO_BUILD_TYPE}/lib${ARGS_TARGET}.a ${ARGS_BINARY_DIRECTORY}/aarch64-apple-darwin/${CARGO_BUILD_TYPE}/lib${ARGS_TARGET}.a -output "${OUTPUT}" WORKING_DIRECTORY "${ARGS_SOURCE_DIRECTORY}" DEPENDS ${LIB_SOURCES} - COMMENT "Building ${ARGS_TARGET} in ${ARGS_BINARY_DIRECTORY} with: ${cargo_EXECUTABLE} ${MY_CARGO_ARGS_STRING}") + COMMENT "Building ${ARGS_TARGET} in ${ARGS_BINARY_DIRECTORY} with: ${cargo_EXECUTABLE} ${MY_CARGO_ARGS_STRING}\n Environment: ${ARGS_ENVIRONMENT}") elseif("${CMAKE_OSX_ARCHITECTURES}" MATCHES "^(arm64)$") add_custom_command( OUTPUT "${OUTPUT}" - COMMAND ${CMAKE_COMMAND} -E env "CARGO_CMD=build" "CARGO_TARGET_DIR=${ARGS_BINARY_DIRECTORY}" "MAINTAINER_MODE=${MAINTAINER_MODE}" "CARGO_INCLUDE_DIRECTORIES=\"${ARGS_INCLUDE_DIRECTORIES}\"" "RUSTFLAGS=${RUSTFLAGS}" ${cargo_EXECUTABLE} ${MY_CARGO_ARGS} --target=aarch64-apple-darwin + COMMAND ${CMAKE_COMMAND} -E env ${ARGS_ENVIRONMENT} ${cargo_EXECUTABLE} ${MY_CARGO_ARGS} --target=aarch64-apple-darwin WORKING_DIRECTORY "${ARGS_SOURCE_DIRECTORY}" DEPENDS ${LIB_SOURCES} - COMMENT "Building ${ARGS_TARGET} in ${ARGS_BINARY_DIRECTORY} with: ${cargo_EXECUTABLE} ${MY_CARGO_ARGS_STRING}") - elseif("${CMAKE_OSX_ARCHITECTURES}" MATCHES "^(x86_64)$") + COMMENT "Building ${ARGS_TARGET} in ${ARGS_BINARY_DIRECTORY} with: ${cargo_EXECUTABLE} ${MY_CARGO_ARGS_STRING}\n Environment: ${ARGS_ENVIRONMENT}") + elseif("${CMAKE_OSX_ARCHITECTURES}" MATCHES "^(x86_64)$") add_custom_command( OUTPUT "${OUTPUT}" - COMMAND ${CMAKE_COMMAND} -E env "CARGO_CMD=build" "CARGO_TARGET_DIR=${ARGS_BINARY_DIRECTORY}" "MAINTAINER_MODE=${MAINTAINER_MODE}" "CARGO_INCLUDE_DIRECTORIES=\"${ARGS_INCLUDE_DIRECTORIES}\"" "RUSTFLAGS=${RUSTFLAGS}" ${cargo_EXECUTABLE} ${MY_CARGO_ARGS} --target=x86_64-apple-darwin + COMMAND ${CMAKE_COMMAND} -E env ${ARGS_ENVIRONMENT} ${cargo_EXECUTABLE} ${MY_CARGO_ARGS} --target=x86_64-apple-darwin COMMAND ${CMAKE_COMMAND} -E make_directory "${ARGS_BINARY_DIRECTORY}/${RUST_COMPILER_TARGET}/${CARGO_BUILD_TYPE}" WORKING_DIRECTORY "${ARGS_SOURCE_DIRECTORY}" DEPENDS ${LIB_SOURCES} - COMMENT "Building ${ARGS_TARGET} in ${ARGS_BINARY_DIRECTORY} with: ${cargo_EXECUTABLE} ${MY_CARGO_ARGS_STRING}") + COMMENT "Building ${ARGS_TARGET} in ${ARGS_BINARY_DIRECTORY} with: ${cargo_EXECUTABLE} ${MY_CARGO_ARGS_STRING}\n Environment: ${ARGS_ENVIRONMENT}") else() add_custom_command( OUTPUT "${OUTPUT}" - COMMAND ${CMAKE_COMMAND} -E env "CARGO_CMD=build" "CARGO_TARGET_DIR=${ARGS_BINARY_DIRECTORY}" "MAINTAINER_MODE=${MAINTAINER_MODE}" "CARGO_INCLUDE_DIRECTORIES=\"${ARGS_INCLUDE_DIRECTORIES}\"" "RUSTFLAGS=${RUSTFLAGS}" ${cargo_EXECUTABLE} ${MY_CARGO_ARGS} + COMMAND ${CMAKE_COMMAND} -E env ${ARGS_ENVIRONMENT} ${cargo_EXECUTABLE} ${MY_CARGO_ARGS} WORKING_DIRECTORY "${ARGS_SOURCE_DIRECTORY}" DEPENDS ${LIB_SOURCES} - COMMENT "Building ${ARGS_TARGET} in ${ARGS_BINARY_DIRECTORY} with: ${cargo_EXECUTABLE} ${MY_CARGO_ARGS_STRING}") + COMMENT "Building ${ARGS_TARGET} in ${ARGS_BINARY_DIRECTORY} with: ${cargo_EXECUTABLE} ${MY_CARGO_ARGS_STRING}\n Environment: ${ARGS_ENVIRONMENT}") endif() # Create a target from the build output diff --git a/common/optparser.c b/common/optparser.c index bbf3bfa2f6..050ddce26e 100644 --- a/common/optparser.c +++ b/common/optparser.c @@ -88,6 +88,7 @@ char _CONFDIR_MILTER[MAX_PATH] = BACKUP_CONFDIR "\\clamav-milter.conf"; #define CONST_DATADIR _DATADIR #define CONST_CONFDIR _CONFDIR +#define CONST_CERTSDIR _CERTSDIR #define CONST_CONFDIR_CLAMD _CONFDIR_CLAMD #define CONST_CONFDIR_FRESHCLAM _CONFDIR_FRESHCLAM #define CONST_CONFDIR_MILTER _CONFDIR_MILTER @@ -96,6 +97,7 @@ char _CONFDIR_MILTER[MAX_PATH] = BACKUP_CONFDIR "\\clamav-milter.conf"; #define CONST_DATADIR DATADIR #define CONST_CONFDIR CONFDIR +#define CONST_CERTSDIR CERTSDIR #define CONST_CONFDIR_CLAMD CONFDIR_CLAMD #define CONST_CONFDIR_FRESHCLAM CONFDIR_FRESHCLAM #define CONST_CONFDIR_MILTER CONFDIR_MILTER @@ -164,6 +166,11 @@ const struct clam_option __clam_options[] = { {NULL, "ascii-normalise", 0, CLOPT_TYPE_STRING, NULL, -1, NULL, 0, OPT_SIGTOOL, "", ""}, {NULL, "utf16-decode", 0, CLOPT_TYPE_STRING, NULL, -1, NULL, 0, OPT_SIGTOOL, "", ""}, {NULL, "build", 'b', CLOPT_TYPE_STRING, NULL, -1, NULL, 0, OPT_SIGTOOL, "", ""}, + {NULL, "sign", 0, CLOPT_TYPE_STRING, NULL, -1, NULL, 0, OPT_SIGTOOL, "", ""}, + {NULL, "verify", 0, CLOPT_TYPE_STRING, NULL, -1, NULL, 0, OPT_SIGTOOL, "", ""}, + {NULL, "key", 0, CLOPT_TYPE_STRING, NULL, -1, NULL, 0, OPT_SIGTOOL, "", ""}, + {NULL, "cert", 0, CLOPT_TYPE_STRING, NULL, -1, NULL, FLAG_MULTIPLE, OPT_SIGTOOL, "", ""}, + {NULL, "append", 0, CLOPT_TYPE_BOOL, MATCH_BOOL, 0, NULL, 0, OPT_SIGTOOL, "", ""}, {NULL, "max-bad-sigs", 0, CLOPT_TYPE_NUMBER, MATCH_NUMBER, 3000, NULL, 0, OPT_SIGTOOL, "Maximum number of mismatched signatures when building a CVD. Zero disables this limit.", "3000"}, {NULL, "flevel", 0, CLOPT_TYPE_NUMBER, MATCH_NUMBER, CL_FLEVEL, NULL, 0, OPT_SIGTOOL, "Feature level to put in the CVD", ""}, {NULL, "cvd-version", 0, CLOPT_TYPE_NUMBER, MATCH_NUMBER, 0, NULL, 0, OPT_SIGTOOL, "Version number of the CVD to build", ""}, @@ -280,6 +287,8 @@ const struct clam_option __clam_options[] = { {"DatabaseDirectory", "datadir", 0, CLOPT_TYPE_STRING, NULL, -1, CONST_DATADIR, 0, OPT_CLAMD | OPT_FRESHCLAM | OPT_SIGTOOL, "This option allows you to change the default database directory.\nIf you enable it, please make sure it points to the same directory in\nboth clamd and freshclam.", "/var/lib/clamav"}, + {"CVDCertsDirectory", "cvdcertsdir", 0, CLOPT_TYPE_STRING, NULL, -1, NULL, 0, OPT_CLAMD | OPT_CLAMSCAN | OPT_FRESHCLAM | OPT_SIGTOOL, "This option allows you to change the default ClamAV CA certificates directory used to verify database external digital signatures.\nIf you enable it, please make sure it points to the same directory in\nboth clamd and freshclam.", "/etc/clamav/certs"}, + {"OfficialDatabaseOnly", "official-db-only", 0, CLOPT_TYPE_BOOL, MATCH_BOOL, 0, NULL, 0, OPT_CLAMD | OPT_CLAMSCAN, "Only load the official signatures published by the ClamAV project.", "no"}, {"FailIfCvdOlderThan", "fail-if-cvd-older-than", 0, CLOPT_TYPE_NUMBER, MATCH_NUMBER, -1, NULL, 0, OPT_CLAMD | OPT_CLAMSCAN, "Return with a nonzero error code if the virus database is older than the specified number of days.", "-1"}, diff --git a/common/optparser.h b/common/optparser.h index 1bb9389e97..1b60f01043 100644 --- a/common/optparser.h +++ b/common/optparser.h @@ -45,12 +45,14 @@ #ifdef _WIN32 extern char _DATADIR[MAX_PATH]; extern char _CONFDIR[MAX_PATH]; +extern char _CERTSDIR[MAX_PATH]; extern char _CONFDIR_CLAMD[MAX_PATH]; extern char _CONFDIR_FRESHCLAM[MAX_PATH]; extern char _CONFDIR_MILTER[MAX_PATH]; #define DATADIR _DATADIR #define CONFDIR _CONFDIR +#define CERTSDIR _CERTSDIR #define CONFDIR_CLAMD _CONFDIR_CLAMD #define CONFDIR_FRESHCLAM _CONFDIR_FRESHCLAM #define CONFDIR_MILTER _CONFDIR_MILTER diff --git a/docs/man/clamd.8.in b/docs/man/clamd.8.in index ff037c5e4e..a2cba70197 100644 --- a/docs/man/clamd.8.in +++ b/docs/man/clamd.8.in @@ -121,12 +121,19 @@ Load signatures from DIRECTORY. .TP \fB\-p FILE, \-\-pid=FILE\fR Write the daemon's pid to FILE. +.TP +\fB\-\-cvdcertsdir=DIR\fR +Specify a directory containing the root CA cert needed to verify detached CVD digital signatures. If not provided, then clamd will look in the default directory. .SH "ENVIRONMENT VARIABLES" .LP clamd uses the following environment variables: .TP -LD_LIBRARY_PATH - May be used on startup to find the libclamunrar_iface shared library module to enable RAR archive support. +\fBLD_LIBRARY_PATH\fR +May be used on startup to find the libclamunrar_iface shared library module to enable RAR archive support. +.TP +\fBCVD_CERTS_DIR\fR +Specify a directory containing the root CA cert needed to verify detached CVD digital signatures. If not provided, then clamd will look in the default directory. .SH "SIGNALS" .LP diff --git a/docs/man/clamd.conf.5.in b/docs/man/clamd.conf.5.in index bdc4c7f76f..29eff1b0e0 100644 --- a/docs/man/clamd.conf.5.in +++ b/docs/man/clamd.conf.5.in @@ -97,6 +97,11 @@ This option allows you to change the default database directory. If you enable i .br Default: defined at configuration (/usr/local/share/clamav) .TP +\fBCVDCertsDirectory STRING\fR +Path to a directory containing ClamAV CA certificate files used to verify signed database archives (e.g. CVD files). This directory must already exist, be an absolute path, be readable by freshclam, clamd, clamscan, and sigtool. +.br +Default: @CERTSDIR@ +.TP \fBOfficialDatabaseOnly BOOL\fR Only load the official signatures published by the ClamAV project. .br diff --git a/docs/man/clamscan.1.in b/docs/man/clamscan.1.in index 6eba8d0dfd..a69c30a653 100644 --- a/docs/man/clamscan.1.in +++ b/docs/man/clamscan.1.in @@ -273,12 +273,19 @@ Maximum size file to perform PCRE subsig matching (default: 100 MB). .TP \fB\-\-disable\-cache\fR Disable caching and cache checks for hash sums of scanned files. +.TP +\fB\-\-cvdcertsdir=DIR\fR +Specify a directory containing the root CA cert needed to verify detached CVD digital signatures. If not provided, then clamscan will look in the default directory. .SH "ENVIRONMENT VARIABLES" .LP clamscan uses the following environment variables: .TP -LD_LIBRARY_PATH - May be used on startup to find the libclamunrar_iface shared library module to enable RAR archive support. +\fBLD_LIBRARY_PATH\fR +May be used on startup to find the libclamunrar_iface shared library module to enable RAR archive support. +.TP +\fBCVD_CERTS_DIR\fR +Specify a directory containing the root CA cert needed to verify detached CVD digital signatures. If not provided, then clamd will look in the default directory. .SH "EXAMPLES" .LP diff --git a/docs/man/freshclam.1.in b/docs/man/freshclam.1.in index 31ad5d02d0..e8294fa0c0 100644 --- a/docs/man/freshclam.1.in +++ b/docs/man/freshclam.1.in @@ -96,6 +96,9 @@ May be set to the path of a file (PEM) containing the client private key. This i .TP \fBFRESHCLAM_CLIENT_KEY_PASSWD\fR May be set to a password for the client key PEM file. This is required if FRESHCLAM_CLIENT_KEY is set and the PEM file is password protected. +.TP +\fBCVD_CERTS_DIR\fR +Specify a directory containing the root CA cert needed to verify detached CVD digital signatures. If not provided, then clamd will look in the default directory. .TP Note that the CURL_CA_BUNDLE environment variable is also used by the curl command line tool for the same purpose. diff --git a/docs/man/freshclam.conf.5.in b/docs/man/freshclam.conf.5.in index 19f9dcd983..3ce6cc7713 100644 --- a/docs/man/freshclam.conf.5.in +++ b/docs/man/freshclam.conf.5.in @@ -66,6 +66,11 @@ Path to a directory containing database files. This directory must already exis .br Default: @DATADIR@ .TP +\fBCVDCertsDirectory STRING\fR +Path to a directory containing ClamAV CA certificate files used to verify signed database archives (e.g. CVD files). This directory must already exist, be an absolute path, be readable by freshclam, clamd, clamscan, and sigtool. +.br +Default: @CERTSDIR@ +.TP \fBForeground BOOL\fR Don't fork into background. .br diff --git a/docs/man/sigtool.1.in b/docs/man/sigtool.1.in index 990349393d..94833d19c2 100644 --- a/docs/man/sigtool.1.in +++ b/docs/man/sigtool.1.in @@ -1,16 +1,19 @@ .TH "sigtool" "1" "February 12, 2007" "ClamAV @VERSION@" "Clam AntiVirus" + .SH "NAME" .LP sigtool \- signature and database management tool + .SH "SYNOPSIS" .LP sigtool [options] + .SH "DESCRIPTION" .LP sigtool can be used to generate MD5 checksums, convert data into hexadecimal format, list virus signatures and build/unpack/test/verify CVD databases and update scripts. -.SH "OPTIONS" -.LP +.SH "COMMON OPTIONS" +.LP .TP \fB\-h, \-\-help\fR Output help information and exit. @@ -19,13 +22,40 @@ Output help information and exit. Print version number and exit. .TP \fB\-\-quiet\fR -Be quiet \- output only error messages. +Be quiet, output only error messages. +.TP +\fB\-\-debug\fR +Enable debug messages .TP \fB\-\-stdout\fR Write all messages to stdout. .TP -\fB\-\-hex\-dump\fR -Read data from stdin and write hex string to stdout. +\fB\-\-tempdir=DIRECTORY\fR +Create temporary files in DIRECTORY. Directory must be writable for the user running sigtool. +.TP +\fB\-\-leave\-temps\fR +Do not remove temporary files. +.TP +\fB\-\-datadir=DIR\fR +Use DIR as the default database directory for all operations. + +.SH "COMMANDS FOR WORKING WITH SIGNATURES" +.LP +.TP +\fB\-l[FILE], \-\-list\-sigs[=FILE]\fR +List all signature names from the local database directory (default) or from FILE. +.TP +\fB\-fREGEX, \-\-find\-sigs=REGEX\fR +Find and display signatures from the local database directory which match the given REGEX. The whole signature body (name, hex string, etc.) is checked. +.TP +\fB\-\-decode\-sigs=REGEX\fR +Decode signatures read from the standard input (eg. piped from \-\-find\-sigs) +.TP +\fB\-\-test\-sigs=DATABASE TARGET_FILE\fR +Test all signatures from DATABASE against TARGET_FILE. This option will only give valid results if the target file is the final one (after unpacking, normalization, etc.) for which the signatures were created. + +.SH "COMMANDS TO GENERATE SIGNATURES" +.LP .TP \fB\-\-md5 [FILES]\fR Generate MD5 checksum from stdin or MD5 sigs for FILES. @@ -37,13 +67,28 @@ Generate SHA1 checksum from stdin or SHA1 sigs for FILES. Generate SHA256 checksum from stdin or SHA256 sigs for FILES. .TP \fB\-\-mdb [FILES]\fR -Generate .mdb signatures for FILES. +Generate .mdb (PE section hash) signatures for FILES. +.TP +\fB\-\-imp [FILES]\fR +Generate .imp (PE import address table hash) signatures for FILES. +.TP +\fB\-\-fuzzy\-img [FILES]\fR +Generate image fuzzy hash for each file. + +.SH "COMMANDS TO NORMALIZE FILES" +.LP .TP \fB\-\-html\-normalise=FILE\fR Create normalised HTML files comment.html, nocomment.html, and script.html in current working directory. .TP +\fB\-\-ascii\-normalise=FILE\fR +Create normalised text file from ascii source. +.TP \fB\-\-utf16\-decode=FILE\fR Decode UTF16 encoded data. + +.SH "COMMANDS FOR FILE ANALYSIS" +.LP .TP \fB\-\-vba=FILE\fR Extract VBA/Word6 macros from given MS Office document. @@ -51,9 +96,19 @@ Extract VBA/Word6 macros from given MS Office document. \fB\-\-vba\-hex=FILE\fR Extract Word6 macros from given MS Office document and display the corresponding hex values. .TP +\fB\-\-print\-certs=FILE\fR +Print Authenticode details from a PE file. +.TP +\fB\-\-hex\-dump\fR +Read data from stdin and write hex string to stdout. + +.SH "COMMANDS FOR WORKING WITH CVDS" +.LP +.TP \fB\-i, \-\-info\fR Print a CVD information and verify MD5 and a digital signature. .TP +.TP \fB\-\-build=FILE, \-b FILE\fR Build a CVD file. \-s, \-\-server is required for signed virus databases(.cvd), or, \-\-unsigned for unsigned(.cud). .TP @@ -73,20 +128,25 @@ NOTE: If a CVD is found in the \-\-datadir its version+1 is used and this value \fB\-\-no\-cdiff\fR Don't create a .cdiff file when building a new database file. .TP +\fB\-\-hybrid\fR +Create a hybrid (standard and bytecode) database file. +.TP \fB\-\-unsigned\fR Create a database file without digital signatures (.cud). .TP -\fB\-\-server\fR +\fB\-\-server=ADDR\fR ClamAV Signing Service address (for virus database maintainers only). .TP -\fB\-\-datadir=DIR\fR -Use DIR as the default database directory for all operations. .TP \fB\-\-unpack=FILE, \-u FILE\fR Unpack FILE (CVD) to a current directory. .TP +.TP \fB\-\-unpack\-current\fR Unpack a local CVD file (main or daily) to current directory. + +.SH "COMMANDS FOR WORKING WITH CDIFF PATCH FILES" +.LP .TP \fB\-\-diff=OLD NEW, \-d OLD NEW\fR Create a diff file for OLD and NEW CVDs/INCDIRs. @@ -99,35 +159,46 @@ Execute update script FILE in current directory. .TP \fB\-\-verify\-cdiff=FILE, \-r FILE\fR Verify DIFF against CVD/INCDIR. + +.SH "COMMANDS FOR CREATING AND VERIFYING DETACHED DIGITAL SIGNATURES" +.LP .TP -\fB\-l[FILE], \-\-list\-sigs[=FILE]\fR -List all signature names from the local database directory (default) or from FILE. +\fB\-\-sign\fR +Sign a file. The resulting .sign file name will be in the form: dbname\-version.cvd.sign +or FILE.sign for non\-CVD targets. It will be created next to the target file. +If a .sign file already exists, then the new signature will be appended to file. .TP -\fB\-fREGEX, \-\-find\-sigs=REGEX\fR -Find and display signatures from the local database directory which match the given REGEX. The whole signature body (name, hex string, etc.) is checked. +\fB\-\-key=FILE\fR +Specify a signing key. .TP -\fB\-\-decode\-sigs=REGEX\fR -Decode signatures read from the standard input (eg. piped from \-\-find\-sigs) +\fB\-\-cert=FILE\fR +Specify a signing cert. May be used more than once to add intermediate and root certificates. .TP -\fB\-\-test\-sigs=DATABASE TARGET_FILE\fR -Test all signatures from DATABASE against TARGET_FILE. This option will only give valid results if the target file is the final one (after unpacking, normalization, etc.) for which the signatures were created. +\fB\-\-append\fR +Use to add a signature line to an existing .sign file. Otherwise an existing .sign file will be overwritten. .TP -\fB\-\-print\-certs=FILE\fR -Print Authenticode details from a PE file. .TP -\fB\-\-tempdir=DIRECTORY\fR -Create temporary files in DIRECTORY. Directory must be writable for the user running sigtool. +\fB\-\-verify\fR +Find and verify a detached digital signature for the given file. +The digital signature file name must be in the form: dbname\-version.cvd.sign or FILE.sign for non-CVD targets. +It must be found next to the target file. .TP -\fB\-\-leave\-temps\fR -Do not remove temporary files. +\fB\-\-cvdcertsdir=DIR\fR +Specify a directory containing the root CA cert needed to verify the signature. +If not provided, then sigtool will look in the default certs directory. .SH "ENVIRONMENT VARIABLES" .LP Sigtool uses the following environment variables: .TP -SIGNDUSER - The username to authenticate with the signing server when building a signed CVD database. +\fBSIGNDUSER\fR +The username to authenticate with the signing server when building a signed CVD database. +.TP +\fBSIGNDPASS\fR +The password to authenticate with the signing server when building a signed CVD database. .TP -SIGNDPASS - The password to authenticate with the signing server when building a signed CVD database. +\fBCVD_CERTS_DIR\fR +Specify a directory containing the root CA cert needed to verify detached CVD digital signatures. If not provided, then clamd will look in the default directory. .SH "EXAMPLES" .LP diff --git a/etc/CMakeLists.txt b/etc/CMakeLists.txt index 826fff1d96..e1b8dd073f 100644 --- a/etc/CMakeLists.txt +++ b/etc/CMakeLists.txt @@ -24,3 +24,17 @@ if(ENABLE_MILTER) ${APP_CONFIG_DIRECTORY} COMPONENT programs) endif() + +# +# clamav certs directory and root CA cert +# + +# Create the certs directory. This is where the root CA cert will be installed. +# Then copy the root CA cert to the certs directory. +install(DIRECTORY DESTINATION ${CVD_CERTS_DIRECTORY} COMPONENT programs) +install( + FILES + ${CMAKE_CURRENT_SOURCE_DIR}/certs/clamav.crt + DESTINATION + ${CVD_CERTS_DIRECTORY} + COMPONENT programs) diff --git a/etc/certs/clamav.crt b/etc/certs/clamav.crt new file mode 100644 index 0000000000..931acf0ca6 --- /dev/null +++ b/etc/certs/clamav.crt @@ -0,0 +1,31 @@ +-----BEGIN CERTIFICATE----- +MIIFTzCCAzcCFG9bzRiWhD7GrtKN5++v9AZJaJ0sMA0GCSqGSIb3DQEBCwUAMGQx +CzAJBgNVBAYTAlVTMQswCQYDVQQIDAJNRDEPMA0GA1UEBwwGTGF1cmVsMQ4wDAYD +VQQKDAVDaXNjbzEOMAwGA1UECwwFVGFsb3MxFzAVBgNVBAMMDkNsYW1BViBSb290 +IENBMB4XDTI0MTAyNDE3MjcyNVoXDTI2MTAyNDE3MjcyNVowZDELMAkGA1UEBhMC +VVMxCzAJBgNVBAgMAk1EMQ8wDQYDVQQHDAZMYXVyZWwxDjAMBgNVBAoMBUNpc2Nv +MQ4wDAYDVQQLDAVUYWxvczEXMBUGA1UEAwwOQ2xhbUFWIFJvb3QgQ0EwggIiMA0G +CSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDNp2WpxPv42Of3yiFUA8Bq6OiPDtjO +bBfPcffqcx1rtsjlgsyXPLr709+ktjdPOrlOAmkuZXUnWeQ6VMltxfUiz0uzMOpV +WR8tEQSaGLR4CBdEe8srnojB7Uwx0x8BEWzROUBBlXdDJjTteEomLduXYaRxoXc3 +S0qxIFx4jL4lfgj1uA8GCdT7oLdWK1gmmc6yijmZyLygh02b660vz8t8pTFp6dWC +zktl4IpnYenktF5mtldfORtdJsBK6dqFhDNTRDzM8VO0nqvVItPs/keCPVCx6+xe +9OI9pDx62NX3LZJX99CJR2Rt1IxIyo46SEBtUJudhMhDJm7xSis9Myu7almGBzpd +YbISdU+riMlgifKAEwWhOJ3eCCqycWA2uw3m0rOAMP775A2tc9FQ56ZhhJNQf5Pc +iZYE+Q2fjGiRDHqYzGF8Gr4YQIdDEkYLLVeq6Xkrb1QIgbzKn6PvrK1QrEgH1Ha+ +Qfpp6tkbu3CxfSnPJjTuL/cRw9jipnrcwesrMpeFS22htNgXm4wUAs8UHhnUNfry +QYan0ver6UDrhlmRn8QN4Wikhp1aZjKE7cAKztIVyZ2H1oWh7cuFCWMOBQ7BkI1a +UL6LYeI0VUrmDmAF7/tlIR62dDqPCJWfzHVfODAJR+I9NPxOSZmaJ5XVMbUh20vM +SsEOasY2nqLbKQIDAQABMA0GCSqGSIb3DQEBCwUAA4ICAQBkLEmFkTt+WX4nUIrY +9uu8vDt5fLC9G/LdNRkgOoeYhSBrktokjuH+Cj1RJ4LqkzXbfpcWkXFh+VidnDi7 +RpuH9I5bQscdxYgZbqHTIDD61uAqWWre/O5r3tqw8v5Lf/qPZ+8YfQSWHI0s11EF +qtNxMxYOshAna1nLHbqAyS8PpBw4Ia2EKjbf9uPch08jR5GS8CQ4FnTJvOysolZO +/YtRpD2ueu5hIarBT9mvlxX59fhSvxARJ7vaG0UrS6w1Ou7lUej5Ww/CKWWfGCAl +Yf2+BmQyM1ir6zXMuRbFwFzKSPxSMDtcTtmS+EugPtm6B0imz3OoDCm6GGNJ5LoE +xY07cbdDxcE8RSZeQhCQSzBUjgkO8nWiZ9okfP9lOiSCjzp25ymvvdqHHRjdjQZc +QafLdQjEWPGFV+XzHEjy5Tx5IWo8vu4G9Xp+x2jK5iR5dXwvuKV7r8jkc9WTDfgT +A0xd9GGK3/c2XQLTNGFaJhhbYfGs8Hptdt+69cABcMib0wHBQGKPjPJ7cCFssVmZ +FhuZRgsoqkCoDGTkcxkALlHEnjlJV58yHW71JA0XUX3sQOOHAefzTM+Zig0ip6yL +qEHRtMbqcYAb8QKzRh7OoVgENrZgtKPbM/RmmXyxCDaj4p8yGsT2wVgekTYgZgs/ +571h1ecLkL4h0vaq5BtDjW/Drg== +-----END CERTIFICATE----- diff --git a/etc/clamd.conf.sample b/etc/clamd.conf.sample index 9e68942a0d..5b15495d8d 100644 --- a/etc/clamd.conf.sample +++ b/etc/clamd.conf.sample @@ -84,6 +84,11 @@ Example # Default: hardcoded (depends on installation options) #DatabaseDirectory /var/lib/clamav +# Path to the ClamAV CA certificates directory for verifying CVD signature +# archive digital signatures. +# Default: hardcoded (depends on installation options) +#CVDCertsDirectory /etc/clamav/certs + # Only load the official signatures published by the ClamAV project. # Default: no #OfficialDatabaseOnly no diff --git a/etc/freshclam.conf.sample b/etc/freshclam.conf.sample index d91fe813b5..ec28fc8560 100644 --- a/etc/freshclam.conf.sample +++ b/etc/freshclam.conf.sample @@ -14,6 +14,14 @@ Example # Default: hardcoded (depends on installation options) #DatabaseDirectory /var/lib/clamav +# Path to the ClamAV CA certificates directory for verifying CVD signature +# archive digital signatures. +# WARNING: It must match clamd.conf's directive! +# WARNING: It must already exist, be an absolute path, be readable by +# freshclam, clamd, clamscan and sigtool. +# Default: hardcoded (depends on installation options) +#CVDCertsDirectory /etc/clamav/certs + # Path to the log file (make sure it has proper permissions) # Default: disabled #UpdateLogFile /var/log/freshclam.log diff --git a/examples/ex_cl_cvdunpack.c b/examples/ex_cl_cvdunpack.c index 0f68337e51..3d3bd9c114 100644 --- a/examples/ex_cl_cvdunpack.c +++ b/examples/ex_cl_cvdunpack.c @@ -71,7 +71,9 @@ int main(int argc, char **argv) return CL_EARG; } - ret = cl_cvdunpack(filename, destination_directory, dont_verify); + // Note: using NULL for certs_directory will disable external digital signature verification. + + ret = cl_cvdunpack_ex(filename, destination_directory, dont_verify, NULL); if (ret != CL_SUCCESS) { printf("ERROR: %s\n", cl_strerror(ret)); } diff --git a/freshclam/freshclam.c b/freshclam/freshclam.c index 8953024c82..97724d6078 100644 --- a/freshclam/freshclam.c +++ b/freshclam/freshclam.c @@ -200,26 +200,32 @@ static void help(void) printf(" --on-error-execute=COMMAND Execute COMMAND if errors occurred\n"); printf(" --on-outdated-execute=COMMAND Execute COMMAND when software is outdated\n"); printf(" --update-db=DBNAME Only update database DBNAME\n"); + printf(" --cvdcertsdir=DIRECTORY Specify a directory containing the root\n"); + printf(" CA cert needed to verify detached CVD digital signatures.\n"); + printf(" If not provided, then clamscan will look in:\n"); + printf(" " CERTSDIR "\n"); printf("\n"); printf("Environment Variables:\n"); printf("\n"); #if !defined(C_DARWIN) && !defined(_WIN32) - printf(" CURL_CA_BUNDLE May be set to the path of a file (bundle)\n"); + printf(" CURL_CA_BUNDLE May be set to the path of a file (bundle)\n"); printf(" containing one or more CA certificates.\n"); printf(" This will override the default openssl\n"); printf(" certificate path.\n"); - printf("\n"); #endif - printf(" FRESHCLAM_CLIENT_CERT May be set to the path of a file (PEM)\n"); + printf(" FRESHCLAM_CLIENT_CERT May be set to the path of a file (PEM)\n"); printf(" containing the client certificate.\n"); printf(" This may be used for client authentication\n"); printf(" to a private mirror.\n"); - printf(" FRESHCLAM_CLIENT_KEY May be set to the path of a file (PEM)\n"); + printf(" FRESHCLAM_CLIENT_KEY May be set to the path of a file (PEM)\n"); printf(" containing the client private key.\n"); printf(" This is required if FRESHCLAM_CLIENT_CERT is set.\n"); - printf(" FRESHCLAM_CLIENT_KEY_PASSWD May be set to a password for the client key PEM file.\n"); + printf(" FRESHCLAM_CLIENT_KEY_PASSWD May be set to a password for the client key PEM file.\n"); printf(" This is required if FRESHCLAM_CLIENT_KEY is\n"); printf(" set and the PEM file is password protected.\n"); + printf(" CVD_CERTS_DIR Specify a directory containing the root CA cert needed\n"); + printf(" to verify detached CVD digital signatures.\n"); + printf(" If not provided, then clamd will look in the default directory.\n"); printf("\n"); } @@ -811,6 +817,31 @@ static fc_error_t initialize(struct optstruct *opts) #endif } + /* + * Verify that the clamav ca certificates directory exists. + * Create certs directory if missing. + */ + fcConfig.certsDirectory = optget(opts, "cvdcertsdir")->strarg; + if (NULL == fcConfig.certsDirectory) { + // Check if the CVD_CERTS_DIR environment variable is set + fcConfig.certsDirectory = getenv("CVD_CERTS_DIR"); + + // If not, use the default value + if (NULL == fcConfig.certsDirectory) { + fcConfig.certsDirectory = CERTSDIR; + } + } + + if (LSTAT(fcConfig.certsDirectory, &statbuf) == -1) { + logg(LOGG_ERROR, + "ClamAV CA certificates directory is missing: %s\n" + "It should have been provided as a part of installation.", + fcConfig.certsDirectory); + + status = FC_EDIRECTORY; + goto done; + } + #ifdef HAVE_PWD_H /* Drop database privileges here if we are not planning on daemonizing. If * we are, we should wait until after we create the PidFile to drop diff --git a/libclamav/clamav.h b/libclamav/clamav.h index 9f0636ab19..10a90a9489 100644 --- a/libclamav/clamav.h +++ b/libclamav/clamav.h @@ -318,6 +318,7 @@ enum cl_engine_field { CL_ENGINE_PCRE_MAX_FILESIZE, /* uint64_t */ CL_ENGINE_DISABLE_PE_CERTS, /* uint32_t */ CL_ENGINE_PE_DUMPCERTS, /* uint32_t */ + CL_ENGINE_CVDCERTSDIR, /* (char *) */ }; enum bytecode_security { @@ -1134,11 +1135,22 @@ extern struct cl_cvd *cl_cvdparse(const char *head); /** * @brief Verify a CVD file by loading and unloading it. * + * This function is deprecated. Use cl_cvdverify_ex() instead. + * * @param file Filepath of CVD file. * @return cl_error_t CL_SUCCESS if success, else a CL_E* error code. */ extern cl_error_t cl_cvdverify(const char *file); +/** + * @brief Verify a CVD file by loading and unloading it. + * + * @param file Filepath of CVD file. + * @param file Directory containing CA certificates required to verify the CVD digital signature. + * @return cl_error_t CL_SUCCESS if success, else a CL_E* error code. + */ +extern cl_error_t cl_cvdverify_ex(const char *file, const char *certs_directory); + /** * @brief Free a CVD header struct. * @@ -1147,10 +1159,12 @@ extern cl_error_t cl_cvdverify(const char *file); extern void cl_cvdfree(struct cl_cvd *cvd); /** - * @brief Unpack a CVD file. + * @brief Unpack a CVD file. (deprecated) * * Will verify the CVD is correctly signed unless the `dont_verify` parameter is true. * + * This function is deprecated. Use cl_cvdunpack_ex() instead. + * * @param file Filepath of CVD file. * @param dir Destination directory. * @param dont_verify If true, don't verify the CVD. @@ -1158,6 +1172,19 @@ extern void cl_cvdfree(struct cl_cvd *cvd); */ extern cl_error_t cl_cvdunpack(const char *file, const char *dir, bool dont_verify); +/** + * @brief Unpack a CVD file. + * + * Will verify the CVD is correctly signed unless the `dont_verify` parameter is true. + * + * @param file Filepath of CVD file. + * @param dir Destination directory. + * @param dont_verify If true, don't verify the CVD. + * @param certs_directory Path where ClamAV public certs are located, needed to verify external digital signatures. + * @return cl_error_t CL_SUCCESS if success, else a CL_E* error code. + */ +extern cl_error_t cl_cvdunpack_ex(const char *file, const char *dir, bool dont_verify, const char *certs_directory); + /** * @brief Retrieve the age of CVD disk data. * diff --git a/libclamav/cvd.c b/libclamav/cvd.c index 6d54dc7998..4e44e13c8c 100644 --- a/libclamav/cvd.c +++ b/libclamav/cvd.c @@ -43,6 +43,7 @@ #include #include "clamav.h" +#include "clamav_rust.h" #include "others.h" #include "dsig.h" #include "str.h" @@ -52,141 +53,6 @@ #define TAR_BLOCKSIZE 512 -static void cli_untgz_cleanup(char *path, gzFile infile, FILE *outfile, int fdd) -{ - UNUSEDPARAM(fdd); - cli_dbgmsg("in cli_untgz_cleanup()\n"); - if (path != NULL) - free(path); - if (infile != NULL) - gzclose(infile); - if (outfile != NULL) - fclose(outfile); -} - -static int cli_untgz(int fd, const char *destdir) -{ - char *path, osize[13], name[101], type; - char block[TAR_BLOCKSIZE]; - int nbytes, nread, nwritten, in_block = 0, fdd = -1; - unsigned int size, pathlen = strlen(destdir) + 100 + 5; - FILE *outfile = NULL; - STATBUF foo; - gzFile infile = NULL; - - cli_dbgmsg("in cli_untgz()\n"); - - if ((fdd = dup(fd)) == -1) { - cli_errmsg("cli_untgz: Can't duplicate descriptor %d\n", fd); - return -1; - } - - if ((infile = gzdopen(fdd, "rb")) == NULL) { - cli_errmsg("cli_untgz: Can't gzdopen() descriptor %d, errno = %d\n", fdd, errno); - if (FSTAT(fdd, &foo) == 0) - close(fdd); - return -1; - } - - path = (char *)calloc(sizeof(char), pathlen); - if (!path) { - cli_errmsg("cli_untgz: Can't allocate memory for path\n"); - cli_untgz_cleanup(NULL, infile, NULL, fdd); - return -1; - } - - while (1) { - - nread = gzread(infile, block, TAR_BLOCKSIZE); - - if (!in_block && !nread) - break; - - if (nread != TAR_BLOCKSIZE) { - cli_errmsg("cli_untgz: Incomplete block read\n"); - cli_untgz_cleanup(path, infile, outfile, fdd); - return -1; - } - - if (!in_block) { - if (block[0] == '\0') /* We're done */ - break; - - strncpy(name, block, 100); - name[100] = '\0'; - - if (strchr(name, '/')) { - cli_errmsg("cli_untgz: Slash separators are not allowed in CVD\n"); - cli_untgz_cleanup(path, infile, outfile, fdd); - return -1; - } - - snprintf(path, pathlen, "%s" PATHSEP "%s", destdir, name); - cli_dbgmsg("cli_untgz: Unpacking %s\n", path); - type = block[156]; - - switch (type) { - case '0': - case '\0': - break; - case '5': - cli_errmsg("cli_untgz: Directories are not supported in CVD\n"); - cli_untgz_cleanup(path, infile, outfile, fdd); - return -1; - default: - cli_errmsg("cli_untgz: Unknown type flag '%c'\n", type); - cli_untgz_cleanup(path, infile, outfile, fdd); - return -1; - } - - if (outfile) { - if (fclose(outfile)) { - cli_errmsg("cli_untgz: Cannot close file %s\n", path); - outfile = NULL; - cli_untgz_cleanup(path, infile, outfile, fdd); - return -1; - } - outfile = NULL; - } - - if (!(outfile = fopen(path, "wb"))) { - cli_errmsg("cli_untgz: Cannot create file %s\n", path); - cli_untgz_cleanup(path, infile, outfile, fdd); - return -1; - } - - strncpy(osize, block + 124, 12); - osize[12] = '\0'; - - if ((sscanf(osize, "%o", &size)) == 0) { - cli_errmsg("cli_untgz: Invalid size in header\n"); - cli_untgz_cleanup(path, infile, outfile, fdd); - return -1; - } - - if (size > 0) - in_block = 1; - - } else { /* write or continue writing file contents */ - nbytes = size > TAR_BLOCKSIZE ? TAR_BLOCKSIZE : size; - nwritten = fwrite(block, 1, nbytes, outfile); - - if (nwritten != nbytes) { - cli_errmsg("cli_untgz: Wrote %d instead of %d (%s)\n", nwritten, nbytes, path); - cli_untgz_cleanup(path, infile, outfile, fdd); - return -1; - } - - size -= nbytes; - if (size == 0) - in_block = 0; - } - } - - cli_untgz_cleanup(path, infile, outfile, fdd); - return 0; -} - static void cli_tgzload_cleanup(int comp, struct cli_dbio *dbio, int fdd) { UNUSEDPARAM(fdd); @@ -209,7 +75,7 @@ static void cli_tgzload_cleanup(int comp, struct cli_dbio *dbio, int fdd) } } -static int cli_tgzload(int fd, struct cl_engine *engine, unsigned int *signo, unsigned int options, struct cli_dbio *dbio, struct cli_dbinfo *dbinfo) +static int cli_tgzload(cvd_t *cvd, struct cl_engine *engine, unsigned int *signo, unsigned int options, struct cli_dbio *dbio, struct cli_dbinfo *dbinfo, void *sign_verifier) { char osize[13], name[101]; char block[TAR_BLOCKSIZE]; @@ -218,9 +84,12 @@ static int cli_tgzload(int fd, struct cl_engine *engine, unsigned int *signo, un off_t off; struct cli_dbinfo *db; char hash[32]; + int fd = -1; cli_dbgmsg("in cli_tgzload()\n"); + fd = cvd_get_file(cvd); + if (lseek(fd, 512, SEEK_SET) < 0) { return CL_ESEEK; } @@ -341,7 +210,7 @@ static int cli_tgzload(int fd, struct cl_engine *engine, unsigned int *signo, un off = ftell(dbio->fs); if ((!dbinfo && cli_strbcasestr(name, ".info")) || (dbinfo && CLI_DBEXT(name))) { - ret = cli_load(name, engine, signo, options, dbio); + ret = cli_load(name, engine, signo, options, dbio, sign_verifier); if (ret) { cli_errmsg("cli_tgzload: Can't load %s\n", name); cli_tgzload_cleanup(compr, dbio, fdd); @@ -504,8 +373,7 @@ struct cl_cvd *cl_cvdhead(const char *file) if ((pt = strpbrk(head, "\n\r"))) *pt = 0; - for (i = bread - 1; i > 0 && (head[i] == ' ' || head[i] == '\n' || head[i] == '\r'); head[i] = 0, i--) - ; + for (i = bread - 1; i > 0 && (head[i] == ' ' || head[i] == '\n' || head[i] == '\r'); head[i] = 0, i--); return cl_cvdparse(head); } @@ -519,197 +387,207 @@ void cl_cvdfree(struct cl_cvd *cvd) free(cvd); } -/** - * @brief Verify the signature of a CVD file. - * - * @param fs CVD File stream to read from. - * @param [out] cvdpt (optional) Pointer to a CVD header struct to fill in . - * @param skipsig If non-zero, skip the signature verification. - * @return cl_error_t CL_SUCCESS on success. CL_ECVD, CL_EMEM, or CL_EVERIFY on error. - */ -static cl_error_t cli_cvdverify(FILE *fs, struct cl_cvd *cvdpt, unsigned int skipsig) +cl_error_t cl_cvdverify(const char *file) { - struct cl_cvd *cvd; - char *md5, head[513]; - int i; - - fseek(fs, 0, SEEK_SET); - if (fread(head, 1, 512, fs) != 512) { - cli_errmsg("cli_cvdverify: Can't read CVD header\n"); - return CL_ECVD; - } - - head[512] = 0; - for (i = 511; i > 0 && (head[i] == ' ' || head[i] == 10); head[i] = 0, i--) - ; - - if ((cvd = cl_cvdparse(head)) == NULL) - return CL_ECVD; + return cl_cvdverify_ex(file, NULL); +} - if (cvdpt) - memcpy(cvdpt, cvd, sizeof(struct cl_cvd)); +cl_error_t cl_cvdverify_ex(const char *file, const char *certs_directory) +{ + struct cl_engine *engine = NULL; + cl_error_t ret; + cvd_type dbtype = CVD_TYPE_UNKNOWN; + void *verifier = NULL; + FFIError *new_verifier_error = NULL; - if (skipsig) { - cl_cvdfree(cvd); - return CL_SUCCESS; + if (!(engine = cl_engine_new())) { + cli_errmsg("cl_cvdverify: Can't create new engine\n"); + ret = CL_EMEM; + goto done; } + engine->cb_stats_submit = NULL; /* Don't submit stats if we're just verifying a CVD */ - md5 = cli_hashstream(fs, NULL, 1); - if (md5 == NULL) { - cli_dbgmsg("cli_cvdverify: Cannot generate hash, out of memory\n"); - cl_cvdfree(cvd); - return CL_EMEM; + if (NULL != certs_directory) { + ret = cl_engine_set_str(engine, CL_ENGINE_CVDCERTSDIR, certs_directory); + if (CL_SUCCESS != ret) { + cli_errmsg("cl_cvdverify: Failed to set engine certs directory\n"); + goto done; + } } - cli_dbgmsg("MD5(.tar.gz) = %s\n", md5); - if (strncmp(md5, cvd->md5, 32)) { - cli_dbgmsg("cli_cvdverify: MD5 verification error\n"); - free(md5); - cl_cvdfree(cvd); - return CL_EVERIFY; + if (!!cli_strbcasestr(file, ".cvd")) { + dbtype = CVD_TYPE_CVD; + } else if (!!cli_strbcasestr(file, ".cld")) { + dbtype = CVD_TYPE_CLD; + } else if (!!cli_strbcasestr(file, ".cud")) { + dbtype = CVD_TYPE_CUD; + } else { + cli_errmsg("cl_cvdverify: File is not a CVD, CLD, or CUD: %s\n", file); + ret = CL_ECVD; + goto done; } - if (cli_versig(md5, cvd->dsig)) { - cli_dbgmsg("cli_cvdverify: Digital signature verification error\n"); - free(md5); - cl_cvdfree(cvd); - return CL_EVERIFY; + if (!codesign_verifier_new(engine->certs_directory, &verifier, &new_verifier_error)) { + cli_errmsg("cl_cvdverify: Failed to create a new code-signature verifier: %s\n", ffierror_fmt(new_verifier_error)); + ret = CL_ECVD; + goto done; } - free(md5); - cl_cvdfree(cvd); - return CL_SUCCESS; -} - -cl_error_t cl_cvdverify(const char *file) -{ - struct cl_engine *engine; - FILE *fs; - cl_error_t ret; - int dbtype = 0; + ret = cli_cvdload(engine, NULL, CL_DB_STDOPT | CL_DB_PUA, dbtype, file, verifier, 1); - if ((fs = fopen(file, "rb")) == NULL) { - cli_errmsg("cl_cvdverify: Can't open file %s\n", file); - return CL_EOPEN; +done: + if (NULL != engine) { + cl_engine_free(engine); } - - if (!(engine = cl_engine_new())) { - cli_errmsg("cl_cvdverify: Can't create new engine\n"); - fclose(fs); - return CL_EMEM; + if (NULL != verifier) { + codesign_verifier_free(verifier); + } + if (NULL != new_verifier_error) { + ffierror_free(new_verifier_error); } - engine->cb_stats_submit = NULL; /* Don't submit stats if we're just verifying a CVD */ - - if (!!cli_strbcasestr(file, ".cld")) - dbtype = 1; - else if (!!cli_strbcasestr(file, ".cud")) - dbtype = 2; - - ret = cli_cvdload(fs, engine, NULL, CL_DB_STDOPT | CL_DB_PUA, dbtype, file, 1); - cl_engine_free(engine); - fclose(fs); return ret; } -cl_error_t cli_cvdload(FILE *fs, struct cl_engine *engine, unsigned int *signo, unsigned int options, unsigned int dbtype, const char *filename, unsigned int chkonly) +cl_error_t cli_cvdload(struct cl_engine *engine, unsigned int *signo, unsigned int options, cvd_type dbtype, const char *filename, void *sign_verifier, unsigned int chkonly) { - struct cl_cvd cvd, dupcvd; - FILE *dupfs; + cl_error_t status = CL_ECVD; cl_error_t ret; time_t s_time; - int cfd; struct cli_dbio dbio; - struct cli_dbinfo *dbinfo = NULL; - char *dupname; + struct cli_dbinfo *dbinfo = NULL; + char *dupname = NULL; + cvd_t *cvd = NULL; + cvd_t *dupcvd = NULL; + FFIError *cvd_open_error = NULL; + FFIError *cvd_verify_error = NULL; + char *signer_name = NULL; dbio.hashctx = NULL; cli_dbgmsg("in cli_cvdload()\n"); - /* verify */ - if ((ret = cli_cvdverify(fs, &cvd, dbtype))) - return ret; + /* Open the cvd and read the header */ + cvd = cvd_open(filename, &cvd_open_error); + if (!cvd) { + cli_errmsg("cli_cvdload: Can't open CVD file %s: %s\n", filename, ffierror_fmt(cvd_open_error)); + goto done; + } + + /* For actual .cvd files, verify the digital signature. */ + if (dbtype == CVD_TYPE_CVD) { + if (!cvd_verify( + cvd, + sign_verifier, + false, + &signer_name, + &cvd_verify_error)) { + cli_errmsg("cli_cvdload: Can't verify CVD file %s: %s\n", filename, ffierror_fmt(cvd_verify_error)); + status = CL_EVERIFY; + goto done; + } + } - if (dbtype <= 1) { + /* For .cvd files, check if there is a .cld of the same name. + Reminder, .cld's are patched .cvd's so that would be a duplicate. + Because it shouldn't happen, we treat it as an error. */ + if (dbtype == CVD_TYPE_CVD) { /* check for duplicate db */ dupname = cli_safer_strdup(filename); - if (!dupname) - return CL_EMEM; - dupname[strlen(dupname) - 2] = (dbtype == 1 ? 'v' : 'l'); - if (!access(dupname, R_OK) && (dupfs = fopen(dupname, "rb"))) { - if ((ret = cli_cvdverify(dupfs, &dupcvd, !dbtype))) { - fclose(dupfs); - free(dupname); - return ret; - } - fclose(dupfs); - if (dupcvd.version > cvd.version) { + if (!dupname) { + status = CL_EMEM; + goto done; + } + + dupname[strlen(dupname) - 2] = (dbtype == CVD_TYPE_CLD ? 'v' : 'l'); + + dupcvd = cvd_open(dupname, &cvd_open_error); + if (dupcvd) { + if (cvd_get_version(dupcvd) > cvd_get_version(cvd)) { cli_warnmsg("Detected duplicate databases %s and %s. The %s database is older and will not be loaded, you should manually remove it from the database directory.\n", filename, dupname, filename); - free(dupname); - return CL_SUCCESS; - } else if (dupcvd.version == cvd.version && !dbtype) { + status = CL_SUCCESS; + goto done; + } else if ((cvd_get_version(dupcvd) == cvd_get_version(cvd)) && + dbtype == CVD_TYPE_CVD) { cli_warnmsg("Detected duplicate databases %s and %s, please manually remove one of them\n", filename, dupname); - free(dupname); - return CL_SUCCESS; + status = CL_SUCCESS; + goto done; + } + } else { + // If the .cld file doesn't exist, it's not an error. + if (NULL != cvd_open_error) { + ffierror_free(cvd_open_error); + cvd_open_error = NULL; } } - free(dupname); } if (strstr(filename, "daily.")) { time(&s_time); - if (cvd.stime > s_time) { - if (cvd.stime - (unsigned int)s_time > 3600) { + if (cvd_get_time_creation(cvd) > (uint64_t)s_time) { + if (cvd_get_time_creation(cvd) - (unsigned int)s_time > 3600) { cli_warnmsg("******************************************************\n"); cli_warnmsg("*** Virus database timestamp in the future! ***\n"); cli_warnmsg("*** Please check the timezone and clock settings ***\n"); cli_warnmsg("******************************************************\n"); } - } else if ((unsigned int)s_time - cvd.stime > 604800) { + } else if ((unsigned int)s_time - cvd_get_time_creation(cvd) > 604800) { cli_warnmsg("**************************************************\n"); cli_warnmsg("*** The virus database is older than 7 days! ***\n"); cli_warnmsg("*** Please update it as soon as possible. ***\n"); cli_warnmsg("**************************************************\n"); } - engine->dbversion[0] = cvd.version; - engine->dbversion[1] = cvd.stime; + engine->dbversion[0] = cvd_get_version(cvd); + engine->dbversion[1] = cvd_get_time_creation(cvd); } - if (cvd.fl > cl_retflevel()) { + if (cvd_get_min_flevel(cvd) > cl_retflevel()) { cli_warnmsg("*******************************************************************\n"); cli_warnmsg("*** This version of the ClamAV engine is outdated. ***\n"); cli_warnmsg("*** Read https://docs.clamav.net/manual/Installing.html ***\n"); cli_warnmsg("*******************************************************************\n"); } - cfd = fileno(fs); dbio.chkonly = 0; - if (dbtype == 2) - ret = cli_tgzload(cfd, engine, signo, options | CL_DB_UNSIGNED, &dbio, NULL); - else - ret = cli_tgzload(cfd, engine, signo, options | CL_DB_OFFICIAL, &dbio, NULL); - if (ret != CL_SUCCESS) - return ret; + if (dbtype == CVD_TYPE_CUD) { + ret = cli_tgzload(cvd, engine, signo, options | CL_DB_UNSIGNED, &dbio, NULL, sign_verifier); + } else { + ret = cli_tgzload(cvd, engine, signo, options | CL_DB_OFFICIAL, &dbio, NULL, sign_verifier); + } + if (ret != CL_SUCCESS) { + status = ret; + goto done; + } dbinfo = engine->dbinfo; - if (!dbinfo || !dbinfo->cvd || (dbinfo->cvd->version != cvd.version) || (dbinfo->cvd->sigs != cvd.sigs) || (dbinfo->cvd->fl != cvd.fl) || (dbinfo->cvd->stime != cvd.stime)) { + if (!dbinfo || + !dbinfo->cvd || + ((uint32_t)dbinfo->cvd->version != cvd_get_version(cvd)) || + ((uint32_t)dbinfo->cvd->sigs != cvd_get_num_sigs(cvd)) || + ((uint32_t)dbinfo->cvd->fl != cvd_get_min_flevel(cvd)) || + ((uint64_t)dbinfo->cvd->stime != cvd_get_time_creation(cvd))) { + cli_errmsg("cli_cvdload: Corrupted CVD header\n"); - return CL_EMALFDB; + status = CL_EMALFDB; + goto done; } dbinfo = engine->dbinfo ? engine->dbinfo->next : NULL; if (!dbinfo) { cli_errmsg("cli_cvdload: dbinfo error\n"); - return CL_EMALFDB; + status = CL_EMALFDB; + goto done; } dbio.chkonly = chkonly; - if (dbtype == 2) + if (dbtype == CVD_TYPE_CUD) { options |= CL_DB_UNSIGNED; - else + } else { options |= CL_DB_SIGNED | CL_DB_OFFICIAL; + } - ret = cli_tgzload(cfd, engine, signo, options, &dbio, dbinfo); + status = cli_tgzload(cvd, engine, signo, options, &dbio, dbinfo, sign_verifier); + +done: while (engine->dbinfo) { dbinfo = engine->dbinfo; @@ -721,56 +599,180 @@ cl_error_t cli_cvdload(FILE *fs, struct cl_engine *engine, unsigned int *signo, MPOOL_FREE(engine->mempool, dbinfo); } - return ret; + if (NULL != signer_name) { + ffi_cstring_free(signer_name); + } + free(dupname); + if (NULL != cvd) { + cvd_free(cvd); + } + if (NULL != dupcvd) { + cvd_free(dupcvd); + } + if (NULL != cvd_open_error) { + ffierror_free(cvd_open_error); + } + if (NULL != cvd_verify_error) { + ffierror_free(cvd_verify_error); + } + + return status; } -static cl_error_t cli_cvdunpack(const char *file, const char *dir) +cl_error_t cli_cvdunpack_and_verify(const char *file, const char *dir, bool dont_verify, void *verifier) { - int fd, ret; + cl_error_t status = CL_SUCCESS; + cvd_t *cvd = NULL; + FFIError *cvd_open_error = NULL; + FFIError *cvd_verify_error = NULL; + FFIError *cvd_unpack_error = NULL; + char *signer_name = NULL; + + cvd = cvd_open(file, &cvd_open_error); + if (!cvd) { + cli_errmsg("Can't open CVD file %s: %s\n", file, ffierror_fmt(cvd_open_error)); + return CL_EOPEN; + } - fd = open(file, O_RDONLY | O_BINARY); - if (fd == -1) - return -1; + if (!dont_verify) { + if (!cvd_verify(cvd, verifier, false, &signer_name, &cvd_verify_error)) { + cli_errmsg("CVD verification failed: %s\n", ffierror_fmt(cvd_verify_error)); + status = CL_EVERIFY; + goto done; + } + } - if (lseek(fd, 512, SEEK_SET) < 0) { - close(fd); - return -1; + if (!cvd_unpack(cvd, dir, &cvd_unpack_error)) { + cli_errmsg("CVD unpacking failed: %s\n", ffierror_fmt(cvd_unpack_error)); + status = CL_EUNPACK; + goto done; } - ret = cli_untgz(fd, dir); - close(fd); - return ret; +done: + + if (NULL != signer_name) { + ffi_cstring_free(signer_name); + } + if (NULL != cvd) { + cvd_free(cvd); + } + if (NULL != cvd_open_error) { + ffierror_free(cvd_open_error); + } + if (NULL != cvd_verify_error) { + ffierror_free(cvd_verify_error); + } + if (NULL != cvd_unpack_error) { + ffierror_free(cvd_unpack_error); + } + + return status; } -cl_error_t cl_cvdunpack(const char *file, const char *dir, bool dont_verify) +cl_error_t cl_cvdunpack_ex(const char *file, const char *dir, bool dont_verify, const char *certs_directory) { - cl_error_t status = CL_SUCCESS; - FILE *fs = NULL; + cl_error_t status = CL_SUCCESS; + cvd_t *cvd = NULL; + FFIError *cvd_open_error = NULL; + FFIError *new_verifier_error = NULL; + FFIError *cvd_unpack_error = NULL; + char *signer_name = NULL; + void *verifier = NULL; + + cvd = cvd_open(file, &cvd_open_error); + if (!cvd) { + cli_errmsg("Can't open CVD file %s: %s\n", file, ffierror_fmt(cvd_open_error)); + return CL_EOPEN; + } + + if (!dont_verify) { + if (!codesign_verifier_new(certs_directory, &verifier, &new_verifier_error)) { + cli_errmsg("Failed to create a new code-signature verifier: %s\n", ffierror_fmt(new_verifier_error)); + status = CL_EUNPACK; + goto done; + } + + status = cli_cvdunpack_and_verify(file, dir, dont_verify, verifier); + if (status != CL_SUCCESS) { + goto done; + } + } + + if (!cvd_unpack(cvd, dir, &cvd_unpack_error)) { + cli_errmsg("CVD unpacking failed: %s\n", ffierror_fmt(cvd_unpack_error)); + status = CL_EUNPACK; + goto done; + } + +done: + + if (NULL != signer_name) { + ffi_cstring_free(signer_name); + } + if (NULL != cvd) { + cvd_free(cvd); + } + if (NULL != cvd_open_error) { + ffierror_free(cvd_open_error); + } + if (NULL != new_verifier_error) { + ffierror_free(new_verifier_error); + } + if (NULL != cvd_unpack_error) { + ffierror_free(cvd_unpack_error); + } + if (NULL != verifier) { + codesign_verifier_free(verifier); + } + + return status; +} - fs = fopen(file, "rb"); - if (NULL == fs) { - char err[128]; - cli_errmsg("Can't open CVD: %s -- %s\n", file, cli_strerror(errno, err, sizeof(err))); +cl_error_t cl_cvdunpack(const char *file, const char *dir, bool dont_verify) +{ + cl_error_t status = CL_SUCCESS; + cvd_t *cvd = NULL; + FFIError *cvd_open_error = NULL; + FFIError *cvd_verify_error = NULL; + FFIError *cvd_unpack_error = NULL; + char *signer_name = NULL; + + cvd = cvd_open(file, &cvd_open_error); + if (!cvd) { + cli_errmsg("Can't open CVD file %s: %s\n", file, ffierror_fmt(cvd_open_error)); return CL_EOPEN; } if (!dont_verify) { - status = cli_cvdverify(fs, NULL, 0); - if (CL_SUCCESS != status) { - cli_errmsg("CVD verification failed for: %s\n", file); + if (!cvd_verify(cvd, NULL, false, &signer_name, &cvd_verify_error)) { + cli_errmsg("CVD verification failed: %s\n", ffierror_fmt(cvd_verify_error)); + status = CL_EVERIFY; goto done; } } - status = cli_cvdunpack(file, dir); - if (CL_SUCCESS != status) { - cli_errmsg("CVD unpacking failed for: %s\n", file); + if (!cvd_unpack(cvd, dir, &cvd_unpack_error)) { + cli_errmsg("CVD unpacking failed: %s\n", ffierror_fmt(cvd_unpack_error)); + status = CL_EUNPACK; goto done; } done: - if (NULL != fs) { - fclose(fs); + + if (NULL != signer_name) { + ffi_cstring_free(signer_name); + } + if (NULL != cvd) { + cvd_free(cvd); + } + if (NULL != cvd_open_error) { + ffierror_free(cvd_open_error); + } + if (NULL != cvd_verify_error) { + ffierror_free(cvd_verify_error); + } + if (NULL != cvd_unpack_error) { + ffierror_free(cvd_unpack_error); } return status; @@ -778,29 +780,34 @@ cl_error_t cl_cvdunpack(const char *file, const char *dir, bool dont_verify) static cl_error_t cvdgetfileage(const char *path, time_t *age_seconds) { - struct cl_cvd cvd; time_t s_time; - cl_error_t status = CL_SUCCESS; - FILE *fs = NULL; - - if ((fs = fopen(path, "rb")) == NULL) { - cli_errmsg("cvdgetfileage: Can't open file %s\n", path); - return CL_EOPEN; - } + cl_error_t status = CL_EOPEN; + cvd_t *cvd = NULL; + FFIError *cvd_open_error = NULL; - if ((status = cli_cvdverify(fs, &cvd, 1)) != CL_SUCCESS) + cvd = cvd_open(path, &cvd_open_error); + if (!cvd) { + cli_errmsg("Can't open CVD file %s: %s\n", path, ffierror_fmt(cvd_open_error)); goto done; + } time(&s_time); - if (cvd.stime > s_time) + if (cvd_get_time_creation(cvd) > (uint64_t)s_time) { *age_seconds = 0; - else - *age_seconds = s_time - cvd.stime; + } else { + *age_seconds = (uint64_t)s_time - cvd_get_time_creation(cvd); + } + + status = CL_SUCCESS; done: - if (fs) - fclose(fs); + if (NULL != cvd) { + cvd_free(cvd); + } + if (NULL != cvd_open_error) { + ffierror_free(cvd_open_error); + } return status; } diff --git a/libclamav/cvd.h b/libclamav/cvd.h index 588c021106..cff81fb217 100644 --- a/libclamav/cvd.h +++ b/libclamav/cvd.h @@ -41,6 +41,18 @@ struct cli_dbio { void *hashctx; }; -cl_error_t cli_cvdload(FILE *fs, struct cl_engine *engine, unsigned int *signo, unsigned int options, unsigned int dbtype, const char *filename, unsigned int chkonly); +typedef enum cvd_type { + // unknown / uninitialized + CVD_TYPE_UNKNOWN, + // signed signature archive + CVD_TYPE_CVD, + // unsigned signature archive that was updated from a CVD or CUD + CVD_TYPE_CLD, + // unsigned signature archive + CVD_TYPE_CUD, +} cvd_type; + +cl_error_t cli_cvdload(struct cl_engine *engine, unsigned int *signo, unsigned int options, cvd_type dbtype, const char *filename, void *sign_verifier, unsigned int chkonly); +cl_error_t cli_cvdunpack_and_verify(const char *file, const char *dir, bool dont_verify, void *verifier); #endif diff --git a/libclamav/libclamav.map b/libclamav/libclamav.map index a34a05dbb6..5609e71fcc 100644 --- a/libclamav/libclamav.map +++ b/libclamav/libclamav.map @@ -59,23 +59,16 @@ CLAMAV_PUBLIC { cl_hash_destroy; cl_engine_stats_enable; lsig_sub_matched; -}; -CLAMAV_0.104.0 { - global: cl_engine_set_clcb_engine_compile_progress; cl_engine_set_clcb_engine_free_progress; cl_engine_set_clcb_sigload_progress; -} CLAMAV_PUBLIC; -CLAMAV_1.0.0 { - global: cl_cvdunpack; cl_engine_set_clcb_file_inspection; -} CLAMAV_0.104.0; -CLAMAV_1.1.0 { - global: cl_cvdgetage; cl_engine_set_clcb_vba; -} CLAMAV_1.0.0; + cl_cvdunpack_ex; + cl_cvdverify_ex; +}; CLAMAV_PRIVATE { global: cli_sigperf_print; @@ -302,6 +295,7 @@ CLAMAV_PRIVATE { cli_magic_scan_buff; cli_checklimits; cli_matchmeta; + cli_cvdunpack_and_verify; __cli_strcasestr; __cli_strndup; diff --git a/libclamav/others.c b/libclamav/others.c index e4b19a0955..5619d540d5 100644 --- a/libclamav/others.c +++ b/libclamav/others.c @@ -77,6 +77,7 @@ #include "readdb.h" #include "stats.h" #include "json_api.h" +#include "mpool.h" #include "clamav_rust.h" @@ -598,6 +599,8 @@ struct cl_engine *cl_engine_new(void) #endif + new->certs_directory = CLI_MPOOL_STRDUP(new->mempool, CERTSDIR); + cli_dbgmsg("Initialized %s engine\n", cl_retver()); return new; } @@ -907,6 +910,15 @@ cl_error_t cl_engine_set_str(struct cl_engine *engine, enum cl_engine_field fiel if (NULL == engine->tmpdir) return CL_EMEM; break; + case CL_ENGINE_CVDCERTSDIR: + if (NULL != engine->certs_directory) { + MPOOL_FREE(engine->mempool, engine->certs_directory); + engine->certs_directory = NULL; + } + engine->certs_directory = CLI_MPOOL_STRDUP(engine->mempool, str); + if (NULL == engine->certs_directory) + return CL_EMEM; + break; default: cli_errmsg("cl_engine_set_num: Incorrect field number\n"); return CL_EARG; @@ -932,6 +944,8 @@ const char *cl_engine_get_str(const struct cl_engine *engine, enum cl_engine_fie return engine->pua_cats; case CL_ENGINE_TMPDIR: return engine->tmpdir; + case CL_ENGINE_CVDCERTSDIR: + return engine->certs_directory; default: cli_errmsg("cl_engine_get: Incorrect field number\n"); if (err) diff --git a/libclamav/others.h b/libclamav/others.h index 18f1e72e45..0d4d0b4f5d 100644 --- a/libclamav/others.h +++ b/libclamav/others.h @@ -188,6 +188,7 @@ typedef struct recursion_level_tag { typedef void *evidence_t; typedef void *onedump_t; +typedef void *cvd_t; /* internal clamav context */ typedef struct cli_ctx_tag { @@ -320,6 +321,7 @@ struct cl_engine { uint32_t ac_mindepth; uint32_t ac_maxdepth; char *tmpdir; + char* certs_directory; uint32_t keeptmp; uint64_t engine_options; uint32_t cache_size; diff --git a/libclamav/readdb.c b/libclamav/readdb.c index 9d37d44323..2cae2cffe1 100644 --- a/libclamav/readdb.c +++ b/libclamav/readdb.c @@ -4690,9 +4690,9 @@ static int cli_loadpwdb(FILE *fs, struct cl_engine *engine, unsigned int options return CL_SUCCESS; } -static cl_error_t cli_loaddbdir(const char *dirname, struct cl_engine *engine, unsigned int *signo, unsigned int options); +static cl_error_t cli_loaddbdir(const char *dirname, struct cl_engine *engine, unsigned int *signo, unsigned int options, void *sign_verifier); -cl_error_t cli_load(const char *filename, struct cl_engine *engine, unsigned int *signo, unsigned int options, struct cli_dbio *dbio) +cl_error_t cli_load(const char *filename, struct cl_engine *engine, unsigned int *signo, unsigned int options, struct cli_dbio *dbio, void *sign_verifier) { cl_error_t ret = CL_SUCCESS; @@ -4736,13 +4736,13 @@ cl_error_t cli_load(const char *filename, struct cl_engine *engine, unsigned int ret = cli_loaddb(fs, engine, signo, options, dbio, dbname); } else if (cli_strbcasestr(dbname, ".cvd")) { - ret = cli_cvdload(fs, engine, signo, options, 0, filename, 0); + ret = cli_cvdload(engine, signo, options, CVD_TYPE_CVD, filename, sign_verifier, 0); } else if (cli_strbcasestr(dbname, ".cld")) { - ret = cli_cvdload(fs, engine, signo, options, 1, filename, 0); + ret = cli_cvdload(engine, signo, options, CVD_TYPE_CLD, filename, sign_verifier, 0); } else if (cli_strbcasestr(dbname, ".cud")) { - ret = cli_cvdload(fs, engine, signo, options, 2, filename, 0); + ret = cli_cvdload(engine, signo, options, CVD_TYPE_CUD, filename, sign_verifier, 0); } else if (cli_strbcasestr(dbname, ".crb")) { ret = cli_loadcrt(fs, engine, dbio); @@ -5017,7 +5017,7 @@ static size_t count_signatures(const char *filepath, struct cl_engine *engine, u return num_signatures; } -static cl_error_t cli_loaddbdir(const char *dirname, struct cl_engine *engine, unsigned int *signo, unsigned int options) +static cl_error_t cli_loaddbdir(const char *dirname, struct cl_engine *engine, unsigned int *signo, unsigned int options, void *sign_verifier) { cl_error_t ret = CL_EOPEN; @@ -5200,7 +5200,7 @@ static cl_error_t cli_loaddbdir(const char *dirname, struct cl_engine *engine, u } } - ret = cli_load(iter->path, engine, signo, options, NULL); + ret = cli_load(iter->path, engine, signo, options, NULL, sign_verifier); if (ret) { cli_errmsg("cli_loaddbdir: error loading database %s\n", iter->path); goto done; @@ -5239,7 +5239,9 @@ static cl_error_t cli_loaddbdir(const char *dirname, struct cl_engine *engine, u cl_error_t cl_load(const char *path, struct cl_engine *engine, unsigned int *signo, unsigned int dboptions) { STATBUF sb; - int ret; + cl_error_t ret; + void *sign_verifier = NULL; + FFIError *new_verifier_error = NULL; if (!engine) { cli_errmsg("cl_load: engine == NULL\n"); @@ -5301,24 +5303,33 @@ cl_error_t cl_load(const char *path, struct cl_engine *engine, unsigned int *sig engine->dboptions |= dboptions; + if (!codesign_verifier_new(engine->certs_directory, &sign_verifier, &new_verifier_error)) { + cli_errmsg("Failed to create a new code-signature verifier: %s\n", ffierror_fmt(new_verifier_error)); + ffierror_free(new_verifier_error); + ret = CL_ECVD; + return ret; + } + switch (sb.st_mode & S_IFMT) { case S_IFREG: /* Count # of sigs in the database now */ engine->num_total_signatures += count_signatures(path, engine, dboptions); - - ret = cli_load(path, engine, signo, dboptions, NULL); + ret = cli_load(path, engine, signo, dboptions, NULL, sign_verifier); break; case S_IFDIR: /* Count # of signatures inside cli_loaddbdir(), before loading */ - ret = cli_loaddbdir(path, engine, signo, dboptions | CL_DB_DIRECTORY); + ret = cli_loaddbdir(path, engine, signo, dboptions | CL_DB_DIRECTORY, sign_verifier); break; default: cli_errmsg("cl_load(%s): Not supported database file type\n", path); + codesign_verifier_free(sign_verifier); return CL_EOPEN; } + codesign_verifier_free(sign_verifier); + if (engine->cb_sigload_progress) { /* Let the progress callback function know we're done! */ (void)engine->cb_sigload_progress(*signo, *signo, engine->cb_sigload_progress_ctx); @@ -5758,7 +5769,7 @@ cl_error_t cl_engine_free(struct cl_engine *engine) MPOOL_FREE(engine->mempool, pt->name); MPOOL_FREE(engine->mempool, pt->hash); if (pt->cvd) - cl_cvdfree(pt->cvd); + cvd_free(pt->cvd); MPOOL_FREE(engine->mempool, pt); } TASK_COMPLETE(); diff --git a/libclamav/readdb.h b/libclamav/readdb.h index 358f5d3f05..17fd1ede90 100644 --- a/libclamav/readdb.h +++ b/libclamav/readdb.h @@ -205,7 +205,7 @@ cl_error_t readdb_parse_ldb_subsignature(struct cli_matcher *root, const char *v const char *offset, const uint32_t *lsigid, unsigned int options, int current_subsig_index, int num_subsigs, struct cli_lsig_tdb *tdb); -cl_error_t cli_load(const char *filename, struct cl_engine *engine, unsigned int *signo, unsigned int options, struct cli_dbio *dbio); +cl_error_t cli_load(const char *filename, struct cl_engine *engine, unsigned int *signo, unsigned int options, struct cli_dbio *dbio, void *sign_verifier); char *cli_dbgets(char *buff, unsigned int size, FILE *fs, struct cli_dbio *dbio); diff --git a/libclamav_rust/CMakeLists.txt b/libclamav_rust/CMakeLists.txt index 0d6c707945..bae604db0c 100644 --- a/libclamav_rust/CMakeLists.txt +++ b/libclamav_rust/CMakeLists.txt @@ -4,10 +4,18 @@ # Copyright (C) 2021-2024 Cisco Systems, Inc. and/or its affiliates. All rights reserved. # +# if OPENSSL_ROOT_DIR is set, pass it to the environment +if(OPENSSL_ROOT_DIR) + set(ENVIRONMENT "OPENSSL_DIR=${OPENSSL_ROOT_DIR}") +else () + set(ENVIRONMENT "") +endif() + # libclamav rust static library add_rust_library(TARGET clamav_rust SOURCE_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" BINARY_DIRECTORY "${CMAKE_BINARY_DIR}" + ENVIRONMENT "${ENVIRONMENT}" INCLUDE_DIRECTORIES "$" # Tests cannot be pre-compiled here, because there are circular dependencies # between libclamav_rust and libclamav to include calls like `cli_getdsig()` diff --git a/libclamav_rust/Cargo.toml b/libclamav_rust/Cargo.toml index 526951c519..768a65e6f6 100644 --- a/libclamav_rust/Cargo.toml +++ b/libclamav_rust/Cargo.toml @@ -1,6 +1,6 @@ [package] authors = ["The ClamAV Team "] -edition = "2018" +edition = "2021" name = "clamav_rust" version = "0.0.1" @@ -26,6 +26,14 @@ inflate = "0.4" bzip2-rs = "0.1" byteorder = "1.5" delharc = "0.6" +clam-sigutil = { path = "../../clamav-signature-util" } +tar = "0.4.43" +md5 = "0.7.0" +openssl = "0.10.68" +glob = "0.3.1" + +[features] +not_ready = [] [lib] crate-type = ["staticlib"] diff --git a/libclamav_rust/build.rs b/libclamav_rust/build.rs index bc05a0f73e..cecad154e0 100644 --- a/libclamav_rust/build.rs +++ b/libclamav_rust/build.rs @@ -42,6 +42,7 @@ const WINDOWS_TRIM_LOCAL_LIB: &[&str] = &["libclamav", "libclammspack"]; // Generate bindings for these functions: const BINDGEN_FUNCTIONS: &[&str] = &[ + "cl_retflevel", "cli_ctx", "cli_warnmsg", "cli_dbgmsg_no_inline", @@ -49,6 +50,7 @@ const BINDGEN_FUNCTIONS: &[&str] = &[ "cli_errmsg", "cli_append_virus", "lsig_increment_subsig_match", + "cli_versig", "cli_versig2", "cli_getdsig", "cli_get_debug_flag", @@ -58,7 +60,7 @@ const BINDGEN_FUNCTIONS: &[&str] = &[ ]; // Generate bindings for these types (structs, enums): -const BINDGEN_TYPES: &[&str] = &["cli_matcher", "cli_ac_data", "cli_ac_result", "onedump_t"]; +const BINDGEN_TYPES: &[&str] = &["cli_matcher", "cli_ac_data", "cli_ac_result", "onedump_t", "cvd_t"]; // Find the required functions and types in these headers: const BINDGEN_HEADERS: &[&str] = &[ diff --git a/libclamav_rust/cbindgen.toml b/libclamav_rust/cbindgen.toml index ef642ef7c5..162d38a750 100644 --- a/libclamav_rust/cbindgen.toml +++ b/libclamav_rust/cbindgen.toml @@ -21,12 +21,29 @@ exclude = [] include = [ "cdiff::cdiff_apply", "cdiff::script2cdiff", + "cvd::cvd_check", + "cvd::cvd_unpack", + "cvd::cvd_open", + "cvd::cvd_verify", + "cvd::cvd_free", + "cvd::cvd_get_time_creation", + "cvd::cvd_get_version", + "cvd::cvd_get_name", + "cvd::cvd_get_num_sigs", + "cvd::cvd_get_min_flevel", + "cvd::cvd_get_builder", + "cvd::cvd_get_file", + "codesign::codesign_sign_file", + "codesign::codesign_verify_file", + "codesign::codesign_verifier_new", + "codesign::codesign_verifier_free", "fuzzy_hash::fuzzy_hash_calculate_image", "fuzzy_hash::fuzzy_hash_load_subsignature", "fuzzy_hash::fuzzy_hash_check", "ffi_util::FFIError", "ffi_util::ffierror_fmt", "ffi_util::ffierror_free", + "ffi_util::ffi_cstring_free", "logging::clrs_eprint", "evidence::evidence_new", "evidence::evidence_free", @@ -38,6 +55,7 @@ include = [ "evidence::IndicatorType", "scanners::scan_onenote", "scanners::cli_scanalz", + "util::glob_rm", ] # prefix = "CAPI_" diff --git a/libclamav_rust/src/alz.rs b/libclamav_rust/src/alz.rs index fef676a0db..a87c2511b3 100644 --- a/libclamav_rust/src/alz.rs +++ b/libclamav_rust/src/alz.rs @@ -358,7 +358,7 @@ impl AlzLocalFileHeader { data: buffer.to_vec(), }; - if 0 != extracted_file.data.len() { + if !extracted_file.data.is_empty() { files.push(extracted_file); } } diff --git a/libclamav_rust/src/cdiff.rs b/libclamav_rust/src/cdiff.rs index 9c4e460e5a..04603d1043 100644 --- a/libclamav_rust/src/cdiff.rs +++ b/libclamav_rust/src/cdiff.rs @@ -19,19 +19,15 @@ */ use std::{ - collections::BTreeMap, - ffi::{CStr, CString}, - fs::{self, File, OpenOptions}, - io::{prelude::*, BufReader, BufWriter, Read, Seek, SeekFrom, Write}, - iter::*, - os::raw::c_char, - path::{Path, PathBuf}, - str::{self, FromStr}, + collections::BTreeMap, ffi::{c_void, CStr, CString}, fs::{self, File, OpenOptions}, io::{prelude::*, BufReader, BufWriter, Read, Seek, SeekFrom, Write}, iter::*, mem::ManuallyDrop, os::raw::c_char, path::{Path, PathBuf}, str::{self, FromStr} }; -use crate::sys; -use crate::util; -use crate::validate_str_param; +use crate::{ + codesign::{self, Verifier}, + ffi_error, + ffi_util::FFIError, + sys, validate_str_param, +}; use flate2::{read::GzDecoder, write::GzEncoder, Compression}; use log::{debug, error, warn}; @@ -146,6 +142,9 @@ pub enum Error { #[error("NUL found within CString")] CstringNulError(#[from] std::ffi::NulError), + + #[error("Can't verify: {0}")] + CannotVerify(String), } /// Errors particular to input handling (i.e., syntax, or side effects from @@ -187,9 +186,13 @@ pub enum InputError { LineNotUnicode(#[from] std::str::Utf8Error), /// Errors encountered while executing a command - #[error("processing: {0}")] + #[error("Processing: {0}")] Processing(#[from] ProcessingError), + /// Errors encountered while executing a command + #[error("Processing: {0}")] + ProcessingString(String), + #[error("no final newline")] MissingNL, @@ -465,7 +468,7 @@ pub fn script2cdiff(script_file_name: &str, builder: &str, server: &str) -> Resu // Make a copy of the script file name to use for the cdiff file let cdiff_file_name_string = script_file_name.to_string(); let mut cdiff_file_name = cdiff_file_name_string.as_str(); - debug!("script2cdiff() - script file name: {:?}", cdiff_file_name); + debug!("script2cdiff: script file name: {:?}", cdiff_file_name); // Remove the "".script" suffix if let Some(file_name) = cdiff_file_name.strip_suffix(".script") { @@ -490,7 +493,7 @@ pub fn script2cdiff(script_file_name: &str, builder: &str, server: &str) -> Resu // Add .cdiff suffix let cdiff_file_name = format!("{}.{}", cdiff_file_name, "cdiff"); - debug!("script2cdiff() - writing to: {:?}", &cdiff_file_name); + debug!("script2cdiff: writing to: {:?}", &cdiff_file_name); // Open cdiff_file_name for writing let mut cdiff_file: File = File::create(&cdiff_file_name) @@ -528,7 +531,7 @@ pub fn script2cdiff(script_file_name: &str, builder: &str, server: &str) -> Resu .map_err(|e| Error::FileMeta(cdiff_file_name.to_owned(), e))? .len(); debug!( - "script2cdiff() - wrote {} bytes to {}", + "script2cdiff: wrote {} bytes to {}", cdiff_file_len, cdiff_file_name ); @@ -571,13 +574,31 @@ pub fn script2cdiff(script_file_name: &str, builder: &str, server: &str) -> Resu Ok(()) } -/// This function is only meant to be called from sigtool.c +/// C interface for cdiff_apply() (below). +/// This function is for use in sigtool.c and libfreshclam_internal.c +/// +/// # Safety +/// +/// No parameters may be NULL. #[export_name = "cdiff_apply"] -pub extern "C" fn _cdiff_apply(fd: i32, mode: u16) -> i32 { - debug!( - "cdiff_apply() - called with file_descriptor={}, mode={}", - fd, mode - ); +pub unsafe extern "C" fn _cdiff_apply( + cdiff_file_path_str: *const c_char, + verifier_ptr: *const c_void, + mode: u16, + err: *mut *mut FFIError, +) -> bool { + let cdiff_file_path_str = validate_str_param!(cdiff_file_path_str); + let cdiff_file_path = match Path::new(cdiff_file_path_str).canonicalize() { + Ok(p) => p, + Err(e) => { + return ffi_error!( + err = err, + Error::CannotVerify(format!("Invalid cdiff file path: {}", e)) + ); + } + }; + + let verifier = ManuallyDrop::new(Box::from_raw(verifier_ptr as *mut Verifier)); let mode = if mode == 1 { ApplyMode::Cdiff @@ -585,13 +606,11 @@ pub extern "C" fn _cdiff_apply(fd: i32, mode: u16) -> i32 { ApplyMode::Script }; - let mut file = util::file_from_fd_or_handle(fd); - - if let Err(e) = cdiff_apply(&mut file, mode) { - error!("{}", e); - -1 + if let Err(e) = cdiff_apply(&cdiff_file_path, &verifier, mode) { + error!("Failed to apply {:?}: {}", cdiff_file_path, e); + ffi_error!(err = err, e) } else { - 0 + true } } @@ -608,58 +627,96 @@ pub extern "C" fn _cdiff_apply(fd: i32, mode: u16) -> i32 { /// A cdiff file contains a footer that is the signed signature of the sha256 /// file contains of the header and the body. The footer begins after the first /// ':' character to the left of EOF. -pub fn cdiff_apply(file: &mut File, mode: ApplyMode) -> Result<(), Error> { +pub fn cdiff_apply( + cdiff_file_path: &Path, + verifier: &Verifier, + mode: ApplyMode, +) -> Result<(), Error> { let path = std::env::current_dir().unwrap(); - debug!("cdiff_apply() - current directory is {}", path.display()); + debug!("cdiff_apply: applying {}", cdiff_file_path.display()); + debug!("cdiff_apply: current directory is {}", path.display()); + + // Open cdiff file for reading + let mut file = File::open(cdiff_file_path).map_err(Error::IoError)?; // Only read dsig, header, etc. if this is a cdiff file let header_length = match mode { ApplyMode::Script => 0, ApplyMode::Cdiff => { - let dsig = read_dsig(file)?; - debug!("cdiff_apply() - final dsig length is {}", dsig.len()); - if is_debug_enabled() { - print_file_data(dsig.clone(), dsig.len()); - } - // Get file length let file_len = file.metadata()?.len() as usize; - let footer_offset = file_len - dsig.len() - 1; - - // The SHA is calculated from the contents of the beginning of the file - // up until the ':' before the dsig at the end of the file. - let sha256 = get_hash(file, footer_offset)?; - - debug!("cdiff_apply() - sha256: {}", hex::encode(sha256)); - - // cli_versig2 will expect dsig to be a null-terminated string - let dsig_cstring = CString::new(dsig)?; - - // Verify cdiff - let n = CString::new(PUBLIC_KEY_MODULUS).unwrap(); - let e = CString::new(PUBLIC_KEY_EXPONENT).unwrap(); - let versig_result = unsafe { - sys::cli_versig2( - sha256.to_vec().as_ptr(), - dsig_cstring.as_ptr(), - n.as_ptr() as *const c_char, - e.as_ptr() as *const c_char, - ) + + // Check if there is an external digital signature + // The filename would be the same as the cdiff file with an extra .sign extension + let sign_file_path = cdiff_file_path.with_extension("cdiff.sign"); + let verify_result = + codesign::verify_signed_file(cdiff_file_path, &sign_file_path, verifier); + let verified = match verify_result { + Ok(signer) => { + debug!( + "cdiff_apply: external signature verified. Signed by: {}", + signer + ); + true + } + Err(codesign::Error::InvalidDigitalSignature(m)) => { + debug!("cdiff_apply: invalid external signature: {}", m); + return Err(Error::InvalidDigitalSignature); + } + Err(e) => { + debug!("cdiff_apply: error validating external signature: {:?}", e); + + // If the external signature could not be validated (e.g. does not exist) + // then continue on and try to validate the internal signature. + false + } }; - debug!("cdiff_apply() - cli_versig2() result = {}", versig_result); - if versig_result != 0 { - return Err(Error::InvalidDigitalSignature); + + if !verified { + // try to verify the internal (legacy) digital signature + let dsig = read_dsig(&mut file)?; + debug!("cdiff_apply: final dsig length is {}", dsig.len()); + if is_debug_enabled() { + print_file_data(dsig.clone(), dsig.len()); + } + + let footer_offset = file_len - dsig.len() - 1; + + // The SHA is calculated from the contents of the beginning of the file + // up until the ':' before the dsig at the end of the file. + let sha256 = get_hash(&mut file, footer_offset)?; + + debug!("cdiff_apply: sha256: {}", hex::encode(sha256)); + + // cli_versig2 will expect dsig to be a null-terminated string + let dsig_cstring = CString::new(dsig)?; + + // Verify cdiff + let n = CString::new(PUBLIC_KEY_MODULUS).unwrap(); + let e = CString::new(PUBLIC_KEY_EXPONENT).unwrap(); + let versig_result = unsafe { + sys::cli_versig2( + sha256.to_vec().as_ptr(), + dsig_cstring.as_ptr(), + n.as_ptr() as *const c_char, + e.as_ptr() as *const c_char, + ) + }; + debug!("cdiff_apply: cli_versig2() result = {}", versig_result); + if versig_result != 0 { + return Err(Error::InvalidDigitalSignature); + } } // Read file length from header - let (header_len, header_offset) = read_size(file)?; + let (header_len, header_offset) = read_size(&mut file)?; debug!( - "cdiff_apply() - header len = {}, file len = {}, header offset = {}", + "cdiff_apply: header len = {}, file len = {}, header offset = {}", header_len, file_len, header_offset ); let current_pos = file.seek(SeekFrom::Start(header_offset as u64))?; - debug!("cdiff_apply() - current file offset = {}", current_pos); + debug!("cdiff_apply: current file offset = {}", current_pos); header_len as usize } }; @@ -781,17 +838,33 @@ fn cmd_move(ctx: &mut Context, move_op: MoveOp) -> Result<(), InputError> { let mut dst_file = OpenOptions::new() .append(true) .open(&move_op.dst) - .map_err(|e| InputError::Processing(e.into()))?; + .map_err(|e| { + InputError::ProcessingString(format!( + "Failed to open destination file {:?} for MOVE command: {}", + &move_op.dst, e + )) + })?; // Create tmp file and open for writing let tmp_named_file = tempfile::Builder::new() .prefix("_tmp_move_file") .tempfile_in("./") - .map_err(|e| InputError::Processing(e.into()))?; + .map_err(|e| { + InputError::ProcessingString(format!( + "Failed to create temp file in current directory {:?} for MOVE command: {}", + std::env::current_dir(), + e + )) + })?; let mut tmp_file = tmp_named_file.as_file(); // Open src in read-only mode - let mut src_reader = BufReader::new(File::open(&move_op.src).map_err(ProcessingError::from)?); + let mut src_reader = BufReader::new(File::open(&move_op.src).map_err(|e| { + InputError::ProcessingString(format!( + "Failed to open source file {:?}: {} for MOVE command", + &move_op.src, e + )) + })?); let mut line = vec![]; let mut line_no = 0; @@ -799,9 +872,12 @@ fn cmd_move(ctx: &mut Context, move_op: MoveOp) -> Result<(), InputError> { // cdiff files start at line 1 line_no += 1; line.clear(); - let n_read = src_reader - .read_until(b'\n', &mut line) - .map_err(ProcessingError::from)?; + let n_read = src_reader.read_until(b'\n', &mut line).map_err(|e| { + InputError::ProcessingString(format!( + "Failed to read from source file {:?} for MOVE command: {}", + &move_op.src, e + )) + })?; if n_read == 0 { break; } @@ -830,7 +906,13 @@ fn cmd_move(ctx: &mut Context, move_op: MoveOp) -> Result<(), InputError> { } // Write everything outside of start and end to tmp else { - tmp_file.write_all(&line).map_err(ProcessingError::from)?; + tmp_file.write_all(&line).map_err(|e| { + InputError::ProcessingString(format!( + "Failed to write line to temp file {:?} for MOVE command: {}", + tmp_named_file.path(), + e + )) + })?; } } @@ -845,8 +927,20 @@ fn cmd_move(ctx: &mut Context, move_op: MoveOp) -> Result<(), InputError> { // Delete src and replace it with tmp #[cfg(windows)] - fs::remove_file(&move_op.src).map_err(ProcessingError::from)?; - fs::rename(tmp_named_file.path(), &move_op.src).map_err(ProcessingError::from)?; + fs::remove_file(&move_op.src).map_err(|e| { + InputError::ProcessingString(format!( + "Failed to remove original source file {:?} for MOVE command: {}", + &move_op.src, e + )) + })?; + fs::rename(tmp_named_file.path(), &move_op.src).map_err(|e| { + InputError::ProcessingString(format!( + "Failed to rename temp file {:?} to {:?} for MOVE command: {}", + tmp_named_file.path(), + &move_op.src, + e + )) + })?; Ok(()) } @@ -863,22 +957,37 @@ fn cmd_close(ctx: &mut Context) -> Result<(), InputError> { if next_edit.is_some() { // Open src in read-only mode - let mut src_reader = BufReader::new(File::open(&open_db).map_err(ProcessingError::from)?); + let mut src_reader = BufReader::new(File::open(&open_db).map_err(|e| { + InputError::ProcessingString(format!( + "Failed to open db file {:?} for CLOSE command: {}", + &open_db, e + )) + })?); // Create tmp file and open for writing let tmp_named_file = tempfile::Builder::new() .prefix("_tmp_move_file") .tempfile_in("./") - .map_err(ProcessingError::from)?; + .map_err(|e| { + InputError::ProcessingString(format!( + "Failed to create temp file in current directory {:?} for CLOSE command: {}", + std::env::current_dir(), + e + )) + })?; let tmp_file = tmp_named_file.as_file(); let mut tmp_file = BufWriter::new(tmp_file); let mut linebuf = Vec::new(); for line_no in 1.. { linebuf.clear(); - let n_read = src_reader - .read_until(b'\n', &mut linebuf) - .map_err(ProcessingError::from)?; + let n_read = src_reader.read_until(b'\n', &mut linebuf).map_err(|e| { + InputError::ProcessingString(format!( + "Failed to read temp file {:?} for CLOSE command: {}", + tmp_named_file.path(), + e + )) + })?; if n_read == 0 { // No more input break; @@ -924,10 +1033,20 @@ fn cmd_close(ctx: &mut Context) -> Result<(), InputError> { // Anything to output? if let Some(new_line) = new_line { - tmp_file - .write_all(new_line) - .map_err(ProcessingError::from)?; - tmp_file.write_all(b"\n").map_err(ProcessingError::from)?; + tmp_file.write_all(new_line).map_err(|e| { + InputError::ProcessingString(format!( + "Failed to write line to temp file {:?} for CLOSE command: {}", + tmp_named_file.path(), + e + )) + })?; + tmp_file.write_all(b"\n").map_err(|e| { + InputError::ProcessingString(format!( + "Failed to write new line to temp file {:?} for CLOSE command: {}", + tmp_named_file.path(), + e + )) + })?; } } @@ -961,12 +1080,29 @@ fn cmd_close(ctx: &mut Context) -> Result<(), InputError> { #[cfg(windows)] if let Err(e) = fs::remove_file(&open_db) { // Try to remove the tempfile, since we failed to remove the original - fs::remove_file(tmpfile_path).map_err(ProcessingError::from)?; - return Err(ProcessingError::from(e).into()); + fs::remove_file(tmpfile_path).map_err(|e| { + InputError::ProcessingString(format!( + "Failed to remove the temp file file {:?} for CLOSE command: {}", + tmpfile_path, e + )) + })?; + return Err(InputError::ProcessingString(format!( + "Failed to remove open db file {:?} for CLOSE command: {}", + &open_db, e + )) + .into()); } if let Err(e) = fs::rename(&tmpfile_path, &open_db) { - fs::remove_file(&tmpfile_path).map_err(ProcessingError::from)?; - return Err(ProcessingError::from(e).into()); + fs::remove_file(&tmpfile_path).map_err(|e| { + InputError::ProcessingString(format!( + "Failed to remove temp file {:?}: {} for CLOSE command", + &tmpfile_path, e + )) + })?; + return Err(InputError::ProcessingString(format!( + "Failed to rename temp file {:?} to {:?} for CLOSE command: {}", + tmpfile_path, &open_db, e + ))); } } @@ -976,14 +1112,22 @@ fn cmd_close(ctx: &mut Context) -> Result<(), InputError> { .create(true) .append(true) .open(&open_db) - .map_err(ProcessingError::from)?; - db_file - .write_all(&ctx.additions) - .map_err(ProcessingError::from)?; + .map_err(|e| { + InputError::ProcessingString(format!( + "Failed to open db file {:?} for CLOSE command: {}", + open_db, e + )) + })?; + db_file.write_all(&ctx.additions).map_err(|e| { + InputError::ProcessingString(format!( + "Failed to write add lines to db {:?} for CLOSE command: {}", + open_db, e + )) + })?; ctx.additions.clear(); } - debug!("cmd_close() - finished"); + debug!("cmd_close: finished"); Ok(()) } @@ -997,7 +1141,12 @@ fn cmd_unlink(ctx: &mut Context, unlink_op: UnlinkOp) -> Result<(), InputError> // We checked that the db_name doesn't have any '/' or '\\' in it before // adding to the UnlinkOp struct, so it's safe to say the path is just a local file and // won't accidentally delete something in a different directory. - fs::remove_file(unlink_op.db_name).map_err(ProcessingError::from)?; + fs::remove_file(unlink_op.db_name).map_err(|e| { + InputError::ProcessingString(format!( + "Failed to remove db file {:?} for UNLINK command: {}", + unlink_op.db_name, e + )) + })?; Ok(()) } @@ -1007,7 +1156,7 @@ fn process_line(ctx: &mut Context, line: &[u8]) -> Result<(), InputError> { let mut tokens = line.splitn(2, |b| *b == b' ' || *b == b'\n'); let cmd = tokens.next().ok_or(InputError::MissingCommand)?; let remainder_with_nl = tokens.next(); - let remainder = remainder_with_nl.and_then(|s| s.strip_suffix(&[b'\n'])); + let remainder = remainder_with_nl.and_then(|s| s.strip_suffix(b"\n")); // Call the appropriate command function match cmd { @@ -1087,7 +1236,7 @@ fn read_dsig(file: &mut File) -> Result, SignatureError> { // Read from dsig_offset to EOF let mut dsig: Vec = vec![]; file.read_to_end(&mut dsig)?; - debug!("read_dsig() - dsig length is {}", dsig.len()); + debug!("read_dsig: dsig length is {}", dsig.len()); // Find the signature let offset: usize = SIG_SIZE + 1; diff --git a/libclamav_rust/src/codesign.rs b/libclamav_rust/src/codesign.rs new file mode 100644 index 0000000000..618c3f90db --- /dev/null +++ b/libclamav_rust/src/codesign.rs @@ -0,0 +1,651 @@ +/* + * Copyright (C) 2024 Cisco Systems, Inc. and/or its affiliates. All rights reserved. + * + * Authors: Micah Snyder + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301, USA. + */ + +use std::{ + ffi::{c_void, CStr}, + fs::File, + io::{prelude::*, BufReader}, + mem::ManuallyDrop, + os::raw::c_char, + path::{Path, PathBuf}, +}; + +use openssl::{ + pkcs7::{Pkcs7, Pkcs7Flags}, + pkey::{PKey, Private}, + stack::{self, Stack}, + x509::{ + store::{X509Store, X509StoreBuilder}, + X509, + }, +}; + +use clam_sigutil::{ + sigbytes::{AppendSigBytes, SigBytes}, + signature::{digital_sig::DigitalSig, parse_from_cvd_with_meta}, + SigType, Signature, +}; + +use log::{debug, error, warn}; + +use crate::{ffi_error, ffi_util::FFIError, sys::cl_retflevel, validate_str_param}; + +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[error("Can't verify: {0}")] + CannotVerify(String), + + #[error("Can't sign: {0}")] + SignFailed(String), + + #[error("Signature verification failed")] + VerifyFailed, + + #[error("File is not signed")] + NotSigned, + + #[error("IO error: {0}")] + IoError(#[from] std::io::Error), + + #[error("Cert Store: {0}")] + CertificateStore(String), + + #[error("OpenSSL error: {0}")] + OpenSSLError(#[from] openssl::error::ErrorStack), + + #[error("Error converting digital signature to .sign file line: {0}")] + SigBytesError(#[from] clam_sigutil::signature::ToSigBytesError), + + #[error("Error verifying signature: {0}")] + InvalidDigitalSignature(String), + + #[error( + "Incorrect public key, does not match any serial number in the signature's signers chain" + )] + IncorrectPublicKey, +} + +/// C interface for verify_signed_file() which verifies a file's external digital signature. +/// Handles all the unsafe ffi stuff. +/// +/// # Safety +/// +/// No parameters may be NULL. +#[export_name = "codesign_sign_file"] +pub unsafe extern "C" fn codesign_sign_file( + target_file_path_str: *const c_char, + signature_file_path_str: *const c_char, + signing_key_path_str: *const c_char, + cert_paths_str: *const *const c_char, + cert_paths_len: usize, + append: bool, + err: *mut *mut FFIError, +) -> bool { + let target_file_path_str = validate_str_param!(target_file_path_str); + let target_file_path = match Path::new(target_file_path_str).canonicalize() { + Ok(p) => p, + Err(e) => { + return ffi_error!( + err = err, + Error::SignFailed(format!( + "Invalid target file path '{}': {}", + target_file_path_str, e + )) + ); + } + }; + + let signature_file_path_str = validate_str_param!(signature_file_path_str); + let signature_file_path = Path::new(signature_file_path_str); + + let cert_path_strs: &[*const i8] = std::slice::from_raw_parts(cert_paths_str, cert_paths_len); + + // now convert the cert_path_strs to a Vec<&Path> + let cert_paths: Vec = cert_path_strs + .iter() + .filter_map(|&path_str| -> Option { + let path_str = if path_str.is_null() { + warn!("Intermiediate path string is NULL"); + return None; + } else { + #[allow(unused_unsafe)] + match unsafe { CStr::from_ptr(path_str) }.to_str() { + Err(e) => { + warn!("Intermediate path string is not valid unicode: {}", e); + return None; + } + Ok(s) => Some(s), + } + }; + + if let Some(path_str) = path_str { + match Path::new(path_str).canonicalize() { + Ok(path) => Some(path), + Err(e) => { + warn!( + "Invalid intermediate certificate path: '{}' {}", + path_str, e + ); + None + } + } + } else { + None + } + }) + .collect(); + + let signing_key_path_str = validate_str_param!(signing_key_path_str); + let signing_key_path = match Path::new(signing_key_path_str).canonicalize() { + Ok(p) => p, + Err(e) => { + return ffi_error!( + err = err, + Error::SignFailed(format!( + "Invalid signing key path '{}': {}", + signing_key_path_str, e + )) + ); + } + }; + + match sign_file( + &target_file_path, + signature_file_path, + &cert_paths, + &signing_key_path, + append, + ) { + Ok(()) => { + debug!("File signed successfully"); + true + } + Err(e) => { + ffi_error!(err = err, e) + } + } +} + +/// Signs a file. +/// The signature is appended to the signature file. +pub fn sign_file

( + target_file_path: &Path, + signature_file_path: &Path, + cert_paths: &[P], + signing_key_path: &Path, + append: bool, +) -> Result<(), Error> +where + P: AsRef, +{ + let signer = Signer::new(signing_key_path, cert_paths)?; + + let data = std::fs::read(target_file_path)?; + let pkcs7 = signer.sign(&data)?; + + // Now convert the pkcs7 to a DigitalSig struct which may be converted to a .sign file signature line. + let signature = DigitalSig::Pkcs7(pkcs7); + let mut sig_bytes: SigBytes = SigBytes::new(); + signature.append_sigbytes(&mut sig_bytes)?; + + let mut writer = { + let mut options = std::fs::OpenOptions::new(); + options.write(true).create(true); + + let need_header = if signature_file_path.exists() { + if append { + options.append(true); + false + } else { + options.truncate(true); + true + } + } else { + true + }; + + let mut writer = std::io::BufWriter::new(options.open(signature_file_path)?); + + // TODO: When introducing next clamsign-version, may need to re-write the header if + // adding a signature to an existing file where the clamsign-version is older. + // Or potentially fail out if the new format is not backwards compatible. + + if need_header { + writer.write_all(b"#clamsign-1.0\n")?; + } else { + writer.write_all(b"\n")?; + } + + writer + }; + + writer.write_all(sig_bytes.as_bytes())?; + + // Write a newline after the signature + writer.write_all(b"\n")?; + + Ok(()) +} + +/// C interface for verify_signed_file() which verifies a file's external digital signature. +/// Handles all the unsafe ffi stuff. +/// +/// The signer_name output parameter is a pointer to a pointer to a C string. +/// The caller is responsible for freeing the CString. See `ffi_cstring_free`. +/// +/// # Safety +/// +/// No parameters may be NULL. +#[export_name = "codesign_verify_file"] +pub unsafe extern "C" fn codesign_verify_file( + signed_file_path_str: *const c_char, + signature_file_path_str: *const c_char, + verifier_ptr: *const c_void, + signer_name: *mut *mut c_char, + err: *mut *mut FFIError, +) -> bool { + let signed_file_path_str = validate_str_param!(signed_file_path_str); + let signed_file_path = match Path::new(signed_file_path_str).canonicalize() { + Ok(p) => p, + Err(e) => { + return ffi_error!( + err = err, + Error::CannotVerify(format!( + "Invalid signed file path '{}': {}", + signed_file_path_str, e + )) + ); + } + }; + + let signature_file_path_str = validate_str_param!(signature_file_path_str); + let signature_file_path = match Path::new(signature_file_path_str).canonicalize() { + Ok(p) => p, + Err(e) => { + return ffi_error!( + err = err, + Error::CannotVerify(format!( + "Invalid signature file path '{}': {}", + signature_file_path_str, e + )) + ); + } + }; + + let verifier = ManuallyDrop::new(Box::from_raw(verifier_ptr as *mut Verifier)); + + // verify that signer_name is not NULL + if signer_name.is_null() { + // invalid parameter + return ffi_error!( + err = err, + Error::CannotVerify("signer_name output parameter is NULL".to_string()) + ); + } + + match verify_signed_file(&signed_file_path, &signature_file_path, &verifier) { + Ok(signer) => { + debug!("CVD verified successfully"); + // convert the signer_name to a CString and store it in the output parameter + let signer_cstr = std::ffi::CString::new(signer).unwrap(); + *signer_name = signer_cstr.into_raw(); + true + } + Err(e) => { + ffi_error!(err = err, e) + } + } +} + +/// C interface for creating a new Verifier. +/// Handles all the unsafe ffi stuff. +/// The verifier output parameter is a pointer to a pointer to a Verifier. +/// +/// # Safety +/// +/// No parameters may be NULL. +#[export_name = "codesign_verifier_new"] +pub unsafe extern "C" fn codesign_verifier_new( + certs_directory_str: *const c_char, + verifier: *mut *mut c_void, + err: *mut *mut FFIError, +) -> bool { + let certs_directory_str = validate_str_param!(certs_directory_str); + let certs_directory = match Path::new(certs_directory_str).canonicalize() { + Ok(p) => p, + Err(e) => { + return ffi_error!( + err = err, + Error::CannotVerify(format!( + "Invalid certs directory '{}': {}", + certs_directory_str, e + )) + ); + } + }; + + // verify that verifier is not NULL + if verifier.is_null() { + // invalid parameter + return ffi_error!( + err = err, + Error::CannotVerify("verifier output parameter is NULL".to_string()) + ); + } + + let new_verifier = Verifier::new(&certs_directory); + match new_verifier { + Ok(new_verifier) => { + debug!("Verifier created successfully"); + *verifier = Box::into_raw(Box::new(new_verifier)) as *mut c_void; + true + } + Err(e) => { + ffi_error!(err = err, e) + } + } +} + +/// C interface for freeing a Verifier. +/// Handles all the unsafe ffi stuff. +/// +/// # Safety +/// +/// No parameters may be NULL. +#[export_name = "codesign_verifier_free"] +pub unsafe extern "C" fn codesign_verifier_free(verifier: *mut c_void) { + if verifier.is_null() { + return; + } else { + let _ = unsafe { Box::from_raw(verifier as *mut Verifier) }; + } +} + +/// Verifies a signed file. +/// The signature file is expected to be in the ClamAV '.sign' format. +/// The certificates directory is expected to contain the public keys of the signers. +/// Returns the name of the signer. +pub fn verify_signed_file( + signed_file_path: &Path, + signature_file_path: &Path, + verifier: &Verifier, +) -> Result { + let signature_file: File = File::open(signature_file_path)?; + + let mut signed_file: File = File::open(signed_file_path)?; + + let mut file_data = Vec::::new(); + let read_result = signed_file.read_to_end(&mut file_data); + if let Err(e) = read_result { + return Err(Error::CannotVerify(format!( + "Error reading file '{:?}': {}", + signed_file_path, e + ))); + } + + let reader = BufReader::new(signature_file); + + for (index, line) in reader.lines().enumerate() { + // First line should be "#clamsign-MAJOR.MINOR" + if index == 0 { + let line = line?; + if !line.starts_with("#clamsign") { + return Err(Error::CannotVerify( + "Unsupported signature file format, expected first line start with '#clamsign-1.0'".to_string(), + )); + } + + // Check clamsign version + let version = line.split('-').nth(1).unwrap(); + if version != "1.0" { + return Err(Error::CannotVerify( + "Unsupported signature file version, expected '1.0'".to_string(), + )); + } + + continue; + } + + // Skip empty lines + let line = line?; + let line = line.trim(); + if line.is_empty() { + continue; + } + + // Skip lines starting with '#' + if line.starts_with('#') { + continue; + } + + // Convert line to bytes, which is preferred by our signature parser. + let data = line.as_bytes(); + + match parse_from_cvd_with_meta(SigType::DigitalSignature, &data.into()) { + Ok((sig, meta)) => { + let sig = sig.downcast::().unwrap(); + + sig.validate(&meta).map_err(|e| { + Error::CannotVerify(format!( + "{:?}:{}: Invalid signature: {}", + signature_file_path, index, e + )) + })?; + + // verify the flevel bounds of this signature compared with the current flevel + let current_flevel = unsafe { cl_retflevel() }; + let sig_flevel_range = meta.f_level.unwrap(); + if !sig_flevel_range.contains(¤t_flevel) { + debug!( + "{:?}:{}: Signature feature level range {:?} does not include current feature level {}", + signature_file_path, index, sig_flevel_range, current_flevel + ); + continue; + } + + match *sig { + DigitalSig::Pkcs7(pkcs7) => { + match verifier.verify(&file_data, &pkcs7) { + Ok(signer) => { + return Ok(signer); + } + Err(Error::InvalidDigitalSignature(m)) => { + warn!( + "Invalid digital signature for {:?}: {}", + signed_file_path, m + ); + return Err(Error::InvalidDigitalSignature(m)); + } + Err(e) => { + debug!( + "Error verifying signature with the certs found in {:?}: {:?}", + verifier.certs_directory, e + ); + + // Try the next certificate + } + } + } + } + } + Err(e) => { + eprintln!( + "{:?}:{}: Error parsing signature: {}", + signature_file_path, index, e + ); + return Err(Error::CannotVerify(e.to_string())); + } + }; + } + + Err(Error::CannotVerify( + "Unable to verify any digital signatures".to_string(), + )) +} + +pub struct Signer { + cert: X509, + certs: Stack, + key: PKey, +} + +impl Signer { + pub fn new

(key_path: &Path, cert_paths: &[P]) -> Result + where + P: AsRef, + { + let mut signing_cert: Option = None; + + // take the first cert from the vec of certs to use as the signing cert + let mut cert_stack: Stack = Stack::new()?; + + for cert_path in cert_paths { + let cert_bytes = std::fs::read(cert_path)?; + let certs = X509::stack_from_pem(&cert_bytes)?; + + for cert in certs { + if signing_cert.is_none() { + debug!("Signing cert: {:?}", cert); + signing_cert = Some(cert.clone()); + } else { + debug!("Trust chain cert: {:?}", cert); + cert_stack.push(cert.clone())?; + } + } + } + + let signing_cert = if let Some(cert) = signing_cert { + cert + } else { + return Err(Error::SignFailed( + "No signing certificate found in the provided certificate file".to_string(), + )); + }; + + let signing_key_bytes = std::fs::read(key_path)?; + let key = PKey::private_key_from_pem(&signing_key_bytes)?; + debug!("Signing key: {:?}", key); + + Ok(Signer { + cert: signing_cert, + certs: cert_stack, + key, + }) + } + + pub fn sign(&self, data: &[u8]) -> Result { + let flags = Pkcs7Flags::DETACHED | Pkcs7Flags::BINARY; + + Pkcs7::sign(&self.cert, &self.key, &self.certs, data, flags).map_err(Error::OpenSSLError) + } +} + +pub struct Verifier { + store: X509Store, + certs_directory: PathBuf, +} + +impl Verifier { + pub fn new(certs_directory: &Path) -> Result { + // create store with root CA + let mut store_builder = X509StoreBuilder::new()?; + + let mut root_common_names = Vec::::new(); + + for file in std::fs::read_dir(certs_directory)? { + let file = file?; + let path = file.path(); + if path.is_file() { + let ext = path.extension(); + if ext.is_some() && (ext.unwrap() == "pem" || ext.unwrap() == "crt") { + let certs_in_file = X509::stack_from_pem(&std::fs::read(path)?)?; + for cert in certs_in_file { + // get cert common name + let common_name = cert + .subject_name() + .entries() + .find(|name_entry| { + name_entry.object().nid() == openssl::nid::Nid::COMMONNAME + }) + .map(|name_entry| name_entry.data().as_utf8().unwrap().to_string()) + .unwrap(); + + if root_common_names.contains(&common_name) { + return Err(Error::CertificateStore(format!( + "More than one certificate with the same common name '{}' found in the certs directory. Ref: https://github.com/openssl/openssl/issues/16304", common_name))); + } + root_common_names.push(common_name.clone()); + + debug!("Adding certificate to verifier store: {:?}", cert); + store_builder.add_cert(cert.clone())?; + } + } + } + } + + let store = store_builder.build(); + Ok(Verifier { + store, + certs_directory: certs_directory.to_path_buf(), + }) + } + + pub fn verify(&self, data: &[u8], pkcs7: &Pkcs7) -> Result { + if let Some(_signed) = pkcs7.signed() { + // verify signature + let certs = stack::Stack::new()?; + let flags = Pkcs7Flags::DETACHED | Pkcs7Flags::BINARY | Pkcs7Flags::NOCRL; + let mut output = Vec::new(); + + let result = pkcs7.verify(&certs, &self.store, Some(data), Some(&mut output), flags); + + // get the signer's common name + let signer = pkcs7.signers(&certs, flags)?; + let signers: Vec = signer + .iter() + .map(|cert| { + cert.subject_name() + .entries() + .find(|name_entry| { + name_entry.object().nid() == openssl::nid::Nid::COMMONNAME + }) + .map(|name_entry| name_entry.data().as_utf8().unwrap().to_string()) + .unwrap() + }) + .collect(); + + match result { + Ok(()) => { + debug!("Successfully verified signature signed by: {:?}", signers); + + Ok(signers.join(", ")) + } + Err(e) => { + // TODO: add more error handling here in case we have the wrong CA. + + eprintln!("Error verifying signature signed by {:?}: {}", signers, e); + Err(Error::InvalidDigitalSignature(e.to_string())) + } + } + } else { + Err(Error::NotSigned) + } + } +} diff --git a/libclamav_rust/src/cvd.rs b/libclamav_rust/src/cvd.rs new file mode 100644 index 0000000000..f335446476 --- /dev/null +++ b/libclamav_rust/src/cvd.rs @@ -0,0 +1,785 @@ +/* + * Copyright (C) 2024 Cisco Systems, Inc. and/or its affiliates. All rights reserved. + * + * Authors: Micah Snyder + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301, USA. + */ + +use std::{ + ffi::{CStr, CString}, + fs::File, + io::{prelude::*, BufReader}, + mem::ManuallyDrop, + os::{ + fd::AsRawFd, + raw::{c_char, c_void}, + }, + path::{Path, PathBuf}, + str::FromStr, + time::{Duration, SystemTime}, +}; + +use crate::codesign::Verifier; +use flate2::read::GzDecoder; +use hex; +use log::{debug, error, warn}; +use tar::Archive; + +use crate::{ + codesign, ffi_error, ffi_error_null, ffi_util::FFIError, sys, validate_str_param, + validate_str_param_null, +}; + +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[error("Error parsing CVD file: {0}")] + Parse(String), + + #[error("Error verifying signature: {0}")] + InvalidDigitalSignature(String), + + #[error("Can't verify: {0}")] + CannotVerify(String), + + #[error("Signature verification failed: signature is invalid")] + VerifyFailed, + + #[error("Unpacking error: {0}")] + UnpackFailed(String), + + #[error("IO error: {0}")] + IoError(#[from] std::io::Error), +} + +pub struct CVD { + pub name: String, + pub time_creation: SystemTime, + pub version: u32, + pub num_sigs: u32, + pub min_flevel: u32, + pub rsa_dsig: Option, + pub md5: Option, + pub builder: String, + pub file: File, + pub path: PathBuf, + pub is_compressed: bool, +} + +impl CVD { + pub fn from_file(file_path: &Path) -> Result { + let file = File::open(file_path) + .map_err(|_| Error::Parse(format!("Failed to open file: {:?}", file_path)))?; + let mut reader = BufReader::new(&file); + + // We need to extract the name from the filename. + // CVD filenames are usually either: + // name.cvd + // or: + // name-version.cvd + // where version is a number. + let database_file_stem = file_path.file_stem().ok_or_else(|| { + Error::Parse("Failed to get database file stem from CVD file path".to_string()) + })?; + let database_file_stem_str = database_file_stem + .to_str() + .ok_or_else(|| Error::Parse("Database file stem is not valid unicode".to_string()))?; + let name = database_file_stem_str + .split(['-', '.']) + .next() + .ok_or_else(|| { + Error::Parse("Failed to get database name from database file stem".to_string()) + })? + .to_string(); + + // read the 512 byte header + let mut header: [u8; 512] = [0; 512]; + reader + .read_exact(&mut header) + .map_err(|_| Error::Parse("File is smaller than 512-byte CVD header".to_string()))?; + + let mut maybe_copying: [u8; 7] = [0; 7]; + reader.read_exact(&mut maybe_copying).map_err(|_| { + Error::Parse( + "Can't read first 7 bytes of the tar file following the header.".to_string(), + ) + })?; + + let is_compressed = if &maybe_copying == b"COPYING" { + // Able to read the contents of the first file, which must be COPYING. + // This means the CVD is not compressed. + false + } else { + true + }; + + let mut fields = header.split(|&n| n == b':'); + + let magic = fields + .next() + .ok_or_else(|| Error::Parse("Invalid CVD file".to_string()))?; + if magic != b"ClamAV-VDB" { + return Err(Error::Parse( + "Invalid CVD file: First field does not match magic bytes for CVD file".to_string(), + )); + } + + let _ = fields.next().ok_or_else(|| { + Error::Parse("Invalid CVD file: Missing creation time stamp".to_string()) + })?; + // let time_str = std::str::from_utf8(time_bytes) + // .map_err(|_| Error::Parse("Time string is not valid unicode".to_string()))?; + + let version_bytes = fields + .next() + .ok_or_else(|| Error::Parse("Invalid CVD file: Missing version".to_string()))?; + let version_str = std::str::from_utf8(version_bytes) + .map_err(|_| Error::Parse("Version string is not valid unicode".to_string()))?; + let version: u32 = version_str.parse().map_err(|_| { + Error::Parse(format!( + "Version string is not an unsigned integer: {}", + version_str + )) + })?; + + let num_sigs_bytes = fields.next().ok_or_else(|| { + Error::Parse("Invalid CVD file: Missing number of signatures".to_string()) + })?; + let num_sigs_str = std::str::from_utf8(num_sigs_bytes) + .map_err(|_| Error::Parse("Signature Count string is not valid unicode".to_string()))?; + let num_sigs: u32 = num_sigs_str.parse().map_err(|_| { + Error::Parse(format!( + "Signature count is not an unsigned integer: {}", + num_sigs_str + )) + })?; + + let min_flevel_bytes = fields.next().ok_or_else(|| { + Error::Parse("Invalid CVD file: Missing minimum feature level".to_string()) + })?; + let min_flevel_str = std::str::from_utf8(min_flevel_bytes).map_err(|_| { + Error::Parse("Minimum Functionality Level string is not valid unicode".to_string()) + })?; + let min_flevel: u32 = min_flevel_str.parse().map_err(|_| { + Error::Parse(format!( + "Minimum Functionality Level is not an unsigned integer: {}", + min_flevel_str + )) + })?; + + let md5_bytes = fields.next().ok_or_else(|| { + Error::Parse( + "Invalid CVD file: Missing MD5 hash field for the compressed archive".to_string(), + ) + })?; + let md5_str = std::str::from_utf8(md5_bytes) + .map_err(|_| Error::Parse("MD5 hash string is not valid unicode".to_string()))?; + let md5: Option = if md5_str.len() != 32 { + debug!("MD5 hash string not present."); + None + } else { + Some(md5_str.to_string()) + }; + + let rsa_dsig_bytes = fields.next().ok_or_else(|| { + Error::Parse("Invalid CVD file: Missing minimum feature level".to_string()) + })?; + let rsa_dsig_str = std::str::from_utf8(rsa_dsig_bytes) + .map_err(|_| { + Error::Parse( + "MD5-based RSA digital signature string is not valid unicode".to_string(), + ) + })? + .to_string(); + + // the rsa dsig field might be empty or like just 'x' + let rsa_dsig = if rsa_dsig_str.len() > 1 { + Some(rsa_dsig_str) + } else { + None + }; + + let builder_bytes = fields + .next() + .ok_or_else(|| Error::Parse("Invalid CVD file: Missing builder string".to_string()))?; + let builder = std::str::from_utf8(builder_bytes) + .map_err(|_| Error::Parse("Builder string is not valid unicode".to_string()))? + .to_string(); + + let time_bytes = fields + .next() + .ok_or_else(|| Error::Parse("Invalid CVD file: Missing creation time".to_string()))?; + let time_str = std::str::from_utf8(time_bytes) + .map_err(|_| Error::Parse("Time string is not valid unicode".to_string()))?; + // trim any trailing whitespace, since this is the last field and the rest should be padding + let time_str = time_str.trim_end(); + let time_seconds: u64 = time_str.parse().map_err(|_| { + Error::Parse(format!( + "Time string is not an unsigned integer: {}", + time_str + )) + })?; + let time_creation = SystemTime::UNIX_EPOCH + Duration::from_secs(time_seconds); + + Ok(Self { + name, + time_creation, + version, + num_sigs, + min_flevel, + rsa_dsig, + md5, + builder, + file, + path: file_path.to_path_buf(), + is_compressed, + }) + } + + pub fn unpack_to(&mut self, path: &Path) -> Result<(), Error> { + debug!("Unpacking CVD file to {:?}", path); + + // skip the 512 byte header + self.file + .seek(std::io::SeekFrom::Start(512)) + .map_err(|_| Error::Parse("Failed to seek past CVD header".to_string()))?; + + let mut file_bytes = Vec::::new(); + let bytes_read = self + .file + .read_to_end(&mut file_bytes) + .map_err(|_| Error::Parse("Failed to read CVD file".to_string()))?; + + debug!("Read {} bytes from CVD file", bytes_read); + + let mut archive: Archive> = if self.is_compressed { + tar::Archive::new(Box::new(GzDecoder::new(file_bytes.as_slice()))) + } else { + tar::Archive::new(Box::new(BufReader::new(file_bytes.as_slice()))) + }; + + archive + .entries() + .map_err(|e| { + Error::Parse(format!( + "Failed to enumerate files in signature archive: {}", + e + )) + })? + // .filter_map(|e| e.ok()) + .for_each(|entry| { + let mut entry = match entry { + Ok(entry) => entry, + Err(e) => { + error!("Failed to get entry in signature archive: {}", e); + return; + } + }; + + let file_path = match entry.path() { + Ok(file_path) => file_path, + Err(e) => { + error!("Failed to get path for file in signature archive: {}", e); + return; + } + }; + + let filename = match file_path.file_name() { + Some(filename) => filename, + None => { + error!( + "Failed to get filename for file in signature archive: {:?}", + file_path + ); + return; + } + }; + + let destination_file_path = path.join(filename); + + debug!("Unpacking {:?} to: {:?}", filename, destination_file_path); + + if let Err(e) = entry.unpack(&destination_file_path) { + error!("Unpack failed: {}", e); + } + }); + + Ok(()) + } + + pub fn verify_rsa_dsig(&mut self) -> Result<(), Error> { + let mut file_bytes = Vec::::new(); + + self.file + .seek(std::io::SeekFrom::Start(512)) + .map_err(|_| Error::Parse("Failed to seek past CVD header".to_string()))?; + + let bytes_read = self + .file + .read_to_end(&mut file_bytes) + .map_err(|_| Error::Parse("Failed to read CVD file".to_string()))?; + + debug!("Read {} bytes from CVD file", bytes_read); + + let digest = md5::compute(&file_bytes); + let calculated_md5 = digest.as_slice(); + let calculated_md5 = hex::encode(calculated_md5); + + debug!("MD5 hash: {}", calculated_md5); + + if let Some(md5) = &self.md5 { + if calculated_md5 != md5[..] { + warn!("MD5 hash does not match the expected hash"); + return Err(Error::InvalidDigitalSignature( + "MD5 hash does not match the expected hash".to_string(), + )); + } + } else { + debug!("MD5 hash is not present in the CVD file"); + } + + if let Some(rsa_dsig) = &self.rsa_dsig { + debug!("RSA digital signature: {:?}", rsa_dsig); + + // versig2 will expect dsig to be a null-terminated string + let dsig_cstring = CString::new(rsa_dsig.as_bytes()).map_err(|_| { + Error::Parse("Failed to convert RSA digital signature to CString".to_string()) + })?; + + // convert the calculated MD5 hash to a null-terminated string + let calculated_md5_cstring = CString::new(calculated_md5).map_err(|_| { + Error::Parse("Failed to convert calculated MD5 hash to CString".to_string()) + })?; + + // Verify cdiff + let versig_result = + unsafe { sys::cli_versig(calculated_md5_cstring.as_ptr(), dsig_cstring.as_ptr()) }; + + debug!("verify_rsa_dsig: versig() result = {}", versig_result); + if versig_result != 0 { + warn!("RSA digital signature verification failed"); + return Err(Error::InvalidDigitalSignature( + "RSA digital signature verification failed".to_string(), + )); + } + + debug!("RSA digital signature verification succeeded"); + } else { + warn!("RSA digital signature is not present in the CVD file"); + return Err(Error::InvalidDigitalSignature( + "RSA digital signature is not present in the CVD file".to_string(), + )); + } + + Ok(()) + } + + pub fn verify_external_sign_file(&mut self, verifier: &Verifier) -> Result { + let database_directory = self.path.parent().ok_or_else(|| { + Error::Parse("Failed to get database directory from CVD file path".to_string()) + })?; + + // The signature file for a CVD should be "databasename-.cvd.sign" + // This is true regardless of whethe ror not the CVD filename has a version number in it. + let database_file_stem = self.path.file_stem().ok_or_else(|| { + Error::Parse("Failed to get database file stem from CVD file path".to_string()) + })?; + let database_file_stem_str = database_file_stem + .to_str() + .ok_or_else(|| Error::Parse("Database file stem is not valid unicode".to_string()))?; + let database_name = database_file_stem_str + .split(['-', '.']) + .next() + .ok_or_else(|| { + Error::Parse("Failed to get database name from database file stem".to_string()) + })?; + + let signature_file_name = format!("{}-{}.cvd.sign", database_name, self.version); + let signature_file_path = database_directory.join(signature_file_name); + + match codesign::verify_signed_file(&self.path, &signature_file_path, verifier) { + Ok(signer) => { + debug!("Successfully verified {:?} signed by {}", self.path, signer); + Ok(signer) + } + Err(codesign::Error::InvalidDigitalSignature(m)) => { + warn!( + "Failed to verify {:?} with {:?}: Signature is invalid: {}", + self.path, signature_file_path, m + ); + Err(Error::InvalidDigitalSignature(m)) + } + Err(e) => { + debug!( + "Failed to verify {:?} with {:?}: {}", + self.path, signature_file_path, e + ); + Err(Error::CannotVerify(e.to_string())) + } + } + } + + pub fn verify( + &mut self, + verifier: Option<&Verifier>, + disable_md5: bool, + ) -> Result { + // First try to verify the CVD with the detached signature file. + // If that fails, fall back to verifying with the MD5-based attached RSA digital signature. + if let Some(verifier) = verifier { + match self.verify_external_sign_file(verifier) { + Ok(signer) => { + debug!("CVD verified successfully with detached signature file"); + return Ok(signer); + } + Err(Error::InvalidDigitalSignature(e)) => { + warn!("Detached CVD signature is invalid: {}", e); + return Err(Error::InvalidDigitalSignature(e)); + } + Err(e) => { + debug!( + "Failed to verify {:?} with detached signature file: {}", + self.path, e + ); + + // If the error because of an invalid signature, fall back to verifying with the MD5-based attached RSA digital signature + } + } + } else { + debug!("No certs directory provided. Skipping external signature verification."); + } + + if disable_md5 { + warn!("Unable to verify CVD with detached signature file and MD5 verification is disabled"); + return Err(Error::CannotVerify("Unable to verify CVD with detached signature file and MD5 verification is disabled".to_string())); + } + + // Fall back to verifying with the MD5-based attached RSA digital signature + match self.verify_rsa_dsig() { + Ok(()) => { + debug!("CVD verified successfully with Legacy ClamAV RSA Public Key"); + Ok("Legacy ClamAV RSA Public Key".to_string()) + } + Err(e) => { + warn!( + "Failed to verify CVD with MD5-based RSA digital signature: {}", + e + ); + Err(e) + } + } + } +} + +/// C interface for checking a CVD. This includes parsing the header, and (optionally) verifying the digital signature. +/// Handles all the unsafe ffi stuff. +/// +/// # Safety +/// +/// No parameters may be NULL +#[export_name = "cvd_check"] +pub unsafe extern "C" fn cvd_check( + cvd_file_path_str: *const c_char, + certs_directory_str: *const c_char, + skip_sign_verify: bool, + disable_md5: bool, + signer_name: *mut *mut c_char, + err: *mut *mut FFIError, +) -> bool { + let cvd_file_path_str = validate_str_param!(cvd_file_path_str); + let cvd_file_path = match Path::new(cvd_file_path_str).canonicalize() { + Ok(p) => p, + Err(e) => { + return ffi_error!( + err = err, + Error::CannotVerify(format!("Invalid CVD file path: {}", e)) + ); + } + }; + + let certs_directory_str = validate_str_param!(certs_directory_str); + let certs_directory = match PathBuf::from_str(certs_directory_str) { + Ok(p) => p, + Err(e) => { + return ffi_error!( + err = err, + Error::CannotVerify(format!("Invalid certs directory path: {}", e)) + ); + } + }; + + let verifier = match Verifier::new(&certs_directory) { + Ok(v) => v, + Err(e) => { + return ffi_error!(err = err, e); + } + }; + + match CVD::from_file(&cvd_file_path) { + Ok(mut cvd) => { + if skip_sign_verify { + debug!("CVD parsed successfully, but we're skipping signature verification."); + return true; + } + + match cvd.verify(Some(&verifier), disable_md5) { + Ok(signer) => { + let signer_cstr = std::ffi::CString::new(signer).unwrap(); + *signer_name = signer_cstr.into_raw(); + true + } + Err(e) => { + ffi_error!(err = err, e) + } + } + } + Err(e) => { + ffi_error!( + err = err, + Error::CannotVerify(format!("Failed to parse CVD: {}", e)) + ) + } + } +} + +/// C interface for unpacking a CVD. This includes parsing the header, verifying the digital signature, and unpacking the archive. +/// Handles all the unsafe ffi stuff. +/// +/// # Safety +/// +/// No parameters may be NULL +/// The CVD pointer must be valid +/// The destination path must be a valid path +#[export_name = "cvd_unpack"] +pub unsafe extern "C" fn cvd_unpack( + cvd: *mut c_void, + destination_path_str: *const c_char, + err: *mut *mut FFIError, +) -> bool { + let mut cvd = ManuallyDrop::new(Box::from_raw(cvd as *mut CVD)); + + let destination_path_str = validate_str_param!(destination_path_str); + let destination_path = match Path::new(destination_path_str).canonicalize() { + Ok(p) => p, + Err(e) => { + return ffi_error!( + err = err, + Error::CannotVerify(format!("Invalid destination path: {}", e)) + ); + } + }; + + match cvd.unpack_to(&destination_path) { + Ok(()) => { + debug!("CVD unpacked successfully"); + true + } + Err(e) => { + ffi_error!(err = err, e); + false + } + } +} + +/// C interface for opening a CVD file. This includes parsing the header. +/// Handles all the unsafe ffi stuff. +/// Returns a pointer to the CVD struct. +/// +/// # Safety +/// +/// No parameters may be NULL +/// The returned pointer must be freed with `cli_cvd_free` +#[export_name = "cvd_open"] +pub unsafe extern "C" fn cvd_open( + cvd_file_path_str: *const c_char, + err: *mut *mut FFIError, +) -> *mut c_void { + let cvd_file_path_str = validate_str_param_null!(cvd_file_path_str); + let cvd_file_path = match Path::new(cvd_file_path_str).canonicalize() { + Ok(p) => p, + Err(e) => { + return ffi_error_null!( + err = err, + Error::CannotVerify(format!("Invalid CVD file path: {}", e)) + ); + } + }; + + match CVD::from_file(&cvd_file_path) { + Ok(cvd) => Box::into_raw(Box::::new(cvd)) as sys::cvd_t, + Err(e) => { + ffi_error_null!( + err = err, + Error::CannotVerify(format!("Failed to parse CVD: {}", e)) + ) + } + } +} + +/// C interface for verifying a CVD. This includes verifying the digital signature. +/// Handles all the unsafe ffi stuff. +/// +/// If `certs_directory_str` is NULL, then only the MD5-based RSA digital signature will be verified. +/// +/// # Safety +/// +/// No parameters may be NULL except for `certs_directory_str`. +/// The CVD pointer must be valid +#[export_name = "cvd_verify"] +pub unsafe extern "C" fn cvd_verify( + cvd: *const c_void, + verifier_ptr: *const c_void, + disable_md5: bool, + signer_name: *mut *mut c_char, + err: *mut *mut FFIError, +) -> bool { + let mut cvd = ManuallyDrop::new(Box::from_raw(cvd as *mut CVD)); + + let verifier = ManuallyDrop::new(Box::from_raw(verifier_ptr as *mut Verifier)); + + match cvd.verify(Some(&verifier), disable_md5) { + Ok(signer) => { + let signer_cstr = std::ffi::CString::new(signer).unwrap(); + *signer_name = signer_cstr.into_raw(); + true + } + Err(e) => { + ffi_error!(err = err, e) + } + } +} + +/// C interface for freeing a CVD struct. +/// Handles all the unsafe ffi stuff. +/// Frees the CVD struct. +/// +/// # Safety +/// +/// The CVD pointer must be valid +/// The CVD pointer must not be used after calling this function +#[export_name = "cvd_free"] +pub unsafe extern "C" fn cvd_free(cvd: *mut c_void) { + if cvd.is_null() { + warn!("Attempted to free a NULL CVD pointer. Please report this at: https://github.com/Cisco-Talos/clamav/issues"); + } else { + let _ = unsafe { Box::from_raw(cvd as *mut CVD) }; + } +} + +/// C interface for getting the creation time of a CVD. +/// Handles all the unsafe ffi stuff. +/// Returns the creation time as a u64. +/// +/// # Safety +/// +/// No parameters may be NULL +/// The CVD pointer must be valid +#[export_name = "cvd_get_time_creation"] +pub unsafe extern "C" fn cvd_get_time_creation(cvd: *const c_void) -> u64 { + let cvd = ManuallyDrop::new(Box::from_raw(cvd as *mut CVD)); + cvd.time_creation + .duration_since(SystemTime::UNIX_EPOCH) + .unwrap() + .as_secs() +} + +/// C interface for getting the version of a CVD. +/// Handles all the unsafe ffi stuff. +/// Returns the version as a u32. +/// +/// # Safety +/// +/// No parameters may be NULL +/// The CVD pointer must be valid +#[export_name = "cvd_get_version"] +pub unsafe extern "C" fn cvd_get_version(cvd: *const c_void) -> u32 { + let cvd = ManuallyDrop::new(Box::from_raw(cvd as *mut CVD)); + cvd.version +} + +/// C interface for getting the name of a CVD. +/// Handles all the unsafe ffi stuff. +/// Returns the version as a C string (aka char *). +/// +/// # Safety +/// +/// No parameters may be NULL +/// The CVD pointer must be valid +/// The caller is responsible for freeing the C string. See `ffi_cstring_free`. +#[export_name = "cvd_get_name"] +pub unsafe extern "C" fn cvd_get_name(cvd: *const c_void) -> *mut c_char { + let cvd = ManuallyDrop::new(Box::from_raw(cvd as *mut CVD)); + + CString::new(cvd.name.clone()).unwrap().into_raw() +} + +/// C interface for getting the number of signatures in a CVD. +/// Handles all the unsafe ffi stuff. +/// Returns the number of signatures as a u32. +/// +/// # Safety +/// +/// No parameters may be NULL +/// The CVD pointer must be valid +#[export_name = "cvd_get_num_sigs"] +pub unsafe extern "C" fn cvd_get_num_sigs(cvd: *const c_void) -> u32 { + let cvd = ManuallyDrop::new(Box::from_raw(cvd as *mut CVD)); + cvd.num_sigs +} + +/// C interface for getting the minimum feature level of a CVD. +/// Handles all the unsafe ffi stuff. +/// Returns the minimum feature level as a u32. +/// +/// # Safety +/// +/// No parameters may be NULL +/// The CVD pointer must be valid +#[export_name = "cvd_get_min_flevel"] +pub unsafe extern "C" fn cvd_get_min_flevel(cvd: *const c_void) -> u32 { + let cvd = ManuallyDrop::new(Box::from_raw(cvd as *mut CVD)); + cvd.min_flevel +} + +/// C interface for getting the CVD builder. +/// Handles all the unsafe ffi stuff. +/// Returns the builder as a CString. +/// The caller is responsible for freeing the CString. See `ffi_cstring_free`. +/// +/// # Safety +/// +/// No parameters may be NULL +/// The CVD pointer must be valid +#[export_name = "cvd_get_builder"] +pub unsafe extern "C" fn cvd_get_builder(cvd: *const c_void) -> *mut c_char { + let cvd = ManuallyDrop::new(Box::from_raw(cvd as *mut CVD)); + CString::new(cvd.builder.clone()).unwrap().into_raw() +} + +/// C interface for getting the file handle of a CVD. +/// Handles all the unsafe ffi stuff. +/// Returns the file handle an integer. +/// The caller must not close the file handle. +/// The file handle is not guaranteed to be valid after the CVD struct is freed. +/// +/// # Safety +/// +/// No parameters may be NULL +/// The CVD pointer must be valid +#[export_name = "cvd_get_file"] +pub unsafe extern "C" fn cvd_get_file(cvd: *const c_void) -> i32 { + let cvd = ManuallyDrop::new(Box::from_raw(cvd as *mut CVD)); + cvd.file.as_raw_fd() +} diff --git a/libclamav_rust/src/ffi_util.rs b/libclamav_rust/src/ffi_util.rs index 64a737b6ab..cc502d6754 100644 --- a/libclamav_rust/src/ffi_util.rs +++ b/libclamav_rust/src/ffi_util.rs @@ -26,6 +26,8 @@ use std::{ os::raw::c_char, }; +use log::{warn}; + /// Wraps a call to a "result-returning function", allowing it to specify an /// error receiver and (optionally) result receiver. /// @@ -57,7 +59,7 @@ use std::{ /// result: *mut i64, /// err: *mut *mut FFIError, /// ) -> bool { -/// frs_call!(out = result, err = err, checked_div(numerator, denominator)) +/// rrf_call!(out = result, err = err, checked_div(numerator, denominator)) /// } /// ``` /// @@ -206,6 +208,24 @@ macro_rules! ffi_error { }; } +/// Consume the specified error and update an output variable, returning null. +/// +/// The given error must implement `std::error::Error`. +/// +/// This macro is best used when specifically returning an error encountered +/// outside of wrapping a Result-returning function (such as input validation). +#[macro_export] +macro_rules! ffi_error_null { + (err=$err_out:ident, $err:expr) => { + if $err_out.is_null() { + panic!("{} is NULL", stringify!($err)); + } else { + *$err_out = Box::into_raw(Box::new($err.into())); + std::ptr::null_mut() + } + }; +} + /// A generic container for any error that implements `Into` pub struct FFIError { /// The contained error @@ -306,3 +326,83 @@ macro_rules! validate_str_param { } }; } + +/// Verify that the given parameter is not NULL, and valid UTF-8, +/// returns a &str if successful else returns sys::cl_error_t_CL_EARG +/// +/// # Examples +/// +/// ```edition2018 +/// use util::validate_optional_str_param; +/// +/// # pub extern "C" fn _my_c_interface(blah: *const c_char) -> sys::cl_error_t { +/// let blah = validate_optional_str_param!(blah); +/// # } +/// ``` +#[macro_export] +macro_rules! validate_optional_str_param { + ($ptr:ident) => { + if $ptr.is_null() { + None + } else { + #[allow(unused_unsafe)] + match unsafe { CStr::from_ptr($ptr) }.to_str() { + Err(e) => { + warn!("{} is not valid unicode: {}", stringify!($ptr), e); + return false; + } + Ok(s) => Some(s), + } + } + }; +} + +/// Verify that the given parameter is not NULL, and valid UTF-8, +/// returns a &str if successful else returns sys::cl_error_t_CL_EARG +/// +/// This variant is for use in functions that return *mut c_void success value. +/// +/// # Examples +/// +/// ```edition2018 +/// use util::validate_str_param; +/// +/// # pub extern "C" fn _my_c_interface(blah: *const c_char) -> sys::cl_error_t { +/// let blah = validate_str_param!(blah); +/// # } +/// ``` +#[macro_export] +macro_rules! validate_str_param_null { + ($ptr:ident) => { + if $ptr.is_null() { + warn!("{} is NULL", stringify!($ptr)); + return std::ptr::null_mut(); + } else { + #[allow(unused_unsafe)] + match unsafe { CStr::from_ptr($ptr) }.to_str() { + Err(e) => { + warn!("{} is not valid unicode: {}", stringify!($ptr), e); + return std::ptr::null_mut(); + } + Ok(s) => s, + } + } + }; +} + +/// C interface to free a CString. +/// Handles all the unsafe ffi stuff. +/// Frees the CString. +/// +/// # Safety +/// +/// The CString pointer must be valid +/// The CString pointer must not be used after calling this function +#[export_name = "ffi_cstring_free"] +pub unsafe extern "C" fn ffi_cstring_free(cstring: *mut c_char) { + if cstring.is_null() { + warn!("Attempted to free a NULL CString pointer. Please report this at:: https://github.com/Cisco-Talos/clamav/issues"); + } else { + let _ = unsafe { CString::from_raw(cstring) }; + } +} diff --git a/libclamav_rust/src/fuzzy_hash.rs b/libclamav_rust/src/fuzzy_hash.rs index 6d848370ae..cee06683c5 100644 --- a/libclamav_rust/src/fuzzy_hash.rs +++ b/libclamav_rust/src/fuzzy_hash.rs @@ -359,8 +359,8 @@ impl FuzzyHashMap { /// of the mean -- this change is required. /// /// 2) scipy.fftpack.dct behaves differently on twodimensional arrays than -/// single-dimensional arrays. -/// See https://docs.scipy.org/doc/scipy/reference/generated/scipy.fftpack.dct.html: +/// single-dimensional arrays. +/// See https://docs.scipy.org/doc/scipy/reference/generated/scipy.fftpack.dct.html: /// /// Note the optional "axis" argument: /// Axis along which the dct is computed; the default is over the last axis @@ -368,17 +368,17 @@ impl FuzzyHashMap { /// /// For the Python `imagehash` package: /// - The `phash_simple()` function is doing a DCT-2 transform on a 2-dimensional -/// 32x32 array which means, just on the 2nd axis (just the rows). +/// 32x32 array which means, just on the 2nd axis (just the rows). /// - The `phash()` function is doing a 2D DCT-2 transform, by running the DCT-2 on -/// both X and Y axis, which is the same as transposing before or after each -/// DCT-2 call. +/// both X and Y axis, which is the same as transposing before or after each +/// DCT-2 call. /// /// 3) I observed that the DCT2 results from Python are consistently 2x greater -/// than those from Rust. If I multiply every value by 2 after running the DCT, -/// then the results are the same. +/// than those from Rust. If I multiply every value by 2 after running the DCT, +/// then the results are the same. /// /// 4) We need to get a subset of the 2-D array representing the lower -/// frequencies of the image, the same way the Python implementation does it. +/// frequencies of the image, the same way the Python implementation does it. /// /// The way the python implementation does this is with this line: /// ```python diff --git a/libclamav_rust/src/lib.rs b/libclamav_rust/src/lib.rs index 7db023a234..5afacdab6b 100644 --- a/libclamav_rust/src/lib.rs +++ b/libclamav_rust/src/lib.rs @@ -25,8 +25,10 @@ pub mod sys; pub mod alz; pub mod cdiff; +pub mod codesign; pub mod css_image_extract; pub mod ctx; +pub mod cvd; pub mod evidence; pub mod ffi_util; pub mod fmap; diff --git a/libclamav_rust/src/scanners.rs b/libclamav_rust/src/scanners.rs index be8c52acd1..6efb68619d 100644 --- a/libclamav_rust/src/scanners.rs +++ b/libclamav_rust/src/scanners.rs @@ -45,7 +45,11 @@ use crate::{ /// Rust wrapper of libclamav's cli_magic_scan_buff() function. /// Use magic sigs to identify the file type and then scan it. -pub fn magic_scan(ctx: *mut cli_ctx, buf: &[u8], name: Option) -> cl_error_t { +/// +/// # Safety +/// +/// The ctx pointer must be valid. +pub unsafe fn magic_scan(ctx: *mut cli_ctx, buf: &[u8], name: Option) -> cl_error_t { let ptr = buf.as_ptr(); let len = buf.len(); @@ -59,10 +63,7 @@ pub fn magic_scan(ctx: *mut cli_ctx, buf: &[u8], name: Option) -> cl_err } // Convert name to a C string. - let name = match name { - Some(name) => name, - None => String::from(""), - }; + let name = name.unwrap_or_default(); let name_ptr: *mut c_char = match CString::new(name) { Ok(name_cstr) => { @@ -234,49 +235,46 @@ pub unsafe extern "C" fn scan_lha_lzh(ctx: *mut cli_ctx) -> cl_error_t { != cl_error_t_CL_SUCCESS { debug!("Extracted file '{filename}' would exceed size limits. Skipping."); + } else if !decoder.is_decoder_supported() { + debug!("err: unsupported compression method"); } else { - if !decoder.is_decoder_supported() { - debug!("err: unsupported compression method"); - } else { - // Read the file into a buffer. - let mut file_data: Vec = Vec::::new(); - - match decoder.read_to_end(&mut file_data) { - Ok(bytes_read) => { - if bytes_read > 0 { - debug!( + // Read the file into a buffer. + let mut file_data: Vec = Vec::::new(); + + match decoder.read_to_end(&mut file_data) { + Ok(bytes_read) => { + if bytes_read > 0 { + debug!( "Read {bytes_read} bytes from file {filename} in the LHA archive." ); - // Verify the CRC check *after* reading the file. - match decoder.crc_check() { - Ok(crc) => { - // CRC is valid. Very likely this is an LHA or LZH archive. - debug!("CRC check passed. Very likely this is an LHA or LZH archive. CRC: {crc}"); - } - Err(err) => { - // Error checking CRC. - debug!("An error occurred when checking the CRC of this LHA or LZH archive: {err}"); - - // Allow the scan to continue even with a CRC error, for now. - // break; - } + // Verify the CRC check *after* reading the file. + match decoder.crc_check() { + Ok(crc) => { + // CRC is valid. Very likely this is an LHA or LZH archive. + debug!("CRC check passed. Very likely this is an LHA or LZH archive. CRC: {crc}"); } + Err(err) => { + // Error checking CRC. + debug!("An error occurred when checking the CRC of this LHA or LZH archive: {err}"); - // Scan the file. - let ret = - magic_scan(ctx, &file_data, Some(filename.to_string())); - if ret != cl_error_t_CL_SUCCESS { - debug!("cl_scandesc_magic returned error: {}", ret); - return ret; + // Allow the scan to continue even with a CRC error, for now. + // break; } - } else { - debug!("Read zero-byte file."); } + + // Scan the file. + let ret = magic_scan(ctx, &file_data, Some(filename.to_string())); + if ret != cl_error_t_CL_SUCCESS { + debug!("cl_scandesc_magic returned error: {}", ret); + return ret; + } + } else { + debug!("Read zero-byte file."); } - err => { - debug!("Error reading file {err:?}"); - } + } + err => { + debug!("Error reading file {err:?}"); } } } diff --git a/libclamav_rust/src/sys.rs b/libclamav_rust/src/sys.rs index 399ec438a4..db4ab67fe2 100644 --- a/libclamav_rust/src/sys.rs +++ b/libclamav_rust/src/sys.rs @@ -6,7 +6,6 @@ pub type __off_t = ::std::os::raw::c_long; pub type __time_t = ::std::os::raw::c_long; pub type __suseconds_t = ::std::os::raw::c_long; pub type off_t = __off_t; -pub type time_t = __time_t; #[repr(C)] #[derive(Debug, Copy, Clone)] pub struct timeval { @@ -251,6 +250,10 @@ pub struct cl_cvd { pub builder: *mut ::std::os::raw::c_char, pub stime: ::std::os::raw::c_uint, } +extern "C" { + #[doc = " @brief Get the Functionality Level (FLEVEL).\n\n @return unsigned int The FLEVEL."] + pub fn cl_retflevel() -> ::std::os::raw::c_uint; +} pub type cl_fmap_t = cl_fmap; #[doc = " @brief Read callback function type.\n\n A callback function pointer type for reading data from a cl_fmap_t that uses\n reads data from a handle interface.\n\n Read 'count' bytes starting at 'offset' into the buffer 'buf'\n\n Thread safety: It is guaranteed that only one callback is executing for a\n specific handle at any time, but there might be multiple callbacks executing\n for different handle at the same time.\n\n @param handle The handle passed to cl_fmap_open_handle, its meaning is up\n to the callback's implementation\n @param buf A buffer to read data into, must be at least offset + count\n bytes in size.\n @param count The number of bytes to read.\n @param offset The offset into buf to read the data to. If successful,\n the number of bytes actually read is returned. Upon reading\n end-of-file, zero is returned. Otherwise, a -1 is returned\n and the global variable errno is set to indicate the error."] pub type clcb_pread = ::std::option::Option< @@ -380,35 +383,37 @@ pub const cli_file_CL_TYPE_PS: cli_file = 552; pub const cli_file_CL_TYPE_EGG: cli_file = 553; pub const cli_file_CL_TYPE_ONENOTE: cli_file = 554; pub const cli_file_CL_TYPE_PYTHON_COMPILED: cli_file = 555; -pub const cli_file_CL_TYPE_PART_ANY: cli_file = 556; -pub const cli_file_CL_TYPE_PART_HFSPLUS: cli_file = 557; -pub const cli_file_CL_TYPE_MBR: cli_file = 558; -pub const cli_file_CL_TYPE_HTML: cli_file = 559; -pub const cli_file_CL_TYPE_MAIL: cli_file = 560; -pub const cli_file_CL_TYPE_SFX: cli_file = 561; -pub const cli_file_CL_TYPE_ZIPSFX: cli_file = 562; -pub const cli_file_CL_TYPE_RARSFX: cli_file = 563; -pub const cli_file_CL_TYPE_7ZSFX: cli_file = 564; -pub const cli_file_CL_TYPE_CABSFX: cli_file = 565; -pub const cli_file_CL_TYPE_ARJSFX: cli_file = 566; -pub const cli_file_CL_TYPE_EGGSFX: cli_file = 567; -pub const cli_file_CL_TYPE_NULSFT: cli_file = 568; -pub const cli_file_CL_TYPE_AUTOIT: cli_file = 569; -pub const cli_file_CL_TYPE_ISHIELD_MSI: cli_file = 570; -pub const cli_file_CL_TYPE_ISO9660: cli_file = 571; -pub const cli_file_CL_TYPE_DMG: cli_file = 572; -pub const cli_file_CL_TYPE_GPT: cli_file = 573; -pub const cli_file_CL_TYPE_APM: cli_file = 574; -pub const cli_file_CL_TYPE_XDP: cli_file = 575; -pub const cli_file_CL_TYPE_XML_WORD: cli_file = 576; -pub const cli_file_CL_TYPE_XML_XL: cli_file = 577; -pub const cli_file_CL_TYPE_XML_HWP: cli_file = 578; -pub const cli_file_CL_TYPE_HWPOLE2: cli_file = 579; -pub const cli_file_CL_TYPE_MHTML: cli_file = 580; -pub const cli_file_CL_TYPE_LNK: cli_file = 581; -pub const cli_file_CL_TYPE_UDF: cli_file = 582; -pub const cli_file_CL_TYPE_OTHER: cli_file = 583; -pub const cli_file_CL_TYPE_IGNORED: cli_file = 584; +pub const cli_file_CL_TYPE_LHA_LZH: cli_file = 556; +pub const cli_file_CL_TYPE_PART_ANY: cli_file = 557; +pub const cli_file_CL_TYPE_PART_HFSPLUS: cli_file = 558; +pub const cli_file_CL_TYPE_MBR: cli_file = 559; +pub const cli_file_CL_TYPE_HTML: cli_file = 560; +pub const cli_file_CL_TYPE_MAIL: cli_file = 561; +pub const cli_file_CL_TYPE_SFX: cli_file = 562; +pub const cli_file_CL_TYPE_ZIPSFX: cli_file = 563; +pub const cli_file_CL_TYPE_RARSFX: cli_file = 564; +pub const cli_file_CL_TYPE_7ZSFX: cli_file = 565; +pub const cli_file_CL_TYPE_CABSFX: cli_file = 566; +pub const cli_file_CL_TYPE_ARJSFX: cli_file = 567; +pub const cli_file_CL_TYPE_EGGSFX: cli_file = 568; +pub const cli_file_CL_TYPE_NULSFT: cli_file = 569; +pub const cli_file_CL_TYPE_AUTOIT: cli_file = 570; +pub const cli_file_CL_TYPE_ISHIELD_MSI: cli_file = 571; +pub const cli_file_CL_TYPE_ISO9660: cli_file = 572; +pub const cli_file_CL_TYPE_DMG: cli_file = 573; +pub const cli_file_CL_TYPE_GPT: cli_file = 574; +pub const cli_file_CL_TYPE_APM: cli_file = 575; +pub const cli_file_CL_TYPE_XDP: cli_file = 576; +pub const cli_file_CL_TYPE_XML_WORD: cli_file = 577; +pub const cli_file_CL_TYPE_XML_XL: cli_file = 578; +pub const cli_file_CL_TYPE_XML_HWP: cli_file = 579; +pub const cli_file_CL_TYPE_HWPOLE2: cli_file = 580; +pub const cli_file_CL_TYPE_MHTML: cli_file = 581; +pub const cli_file_CL_TYPE_LNK: cli_file = 582; +pub const cli_file_CL_TYPE_UDF: cli_file = 583; +pub const cli_file_CL_TYPE_ALZ: cli_file = 584; +pub const cli_file_CL_TYPE_OTHER: cli_file = 585; +pub const cli_file_CL_TYPE_IGNORED: cli_file = 586; pub type cli_file = ::std::os::raw::c_uint; pub use self::cli_file as cli_file_t; #[repr(C)] @@ -421,6 +426,12 @@ pub struct cli_ftype { pub next: *mut cli_ftype, pub length: u16, } +#[doc = " forward declaration of json-c's JSON value instance structure"] +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct json_object { + _unused: [u8; 0], +} pub type mpool_t = ::std::os::raw::c_void; #[repr(C)] #[derive(Debug, Copy, Clone)] @@ -574,8 +585,8 @@ pub struct cli_crt_t { pub n: *mut BIGNUM, pub e: *mut BIGNUM, pub sig: *mut BIGNUM, - pub not_before: time_t, - pub not_after: time_t, + pub not_before: i64, + pub not_after: i64, pub hashtype: cli_crt_hashtype, pub certSign: ::std::os::raw::c_int, pub codeSign: ::std::os::raw::c_int, @@ -619,6 +630,7 @@ pub struct recursion_level_tag { pub type recursion_level_t = recursion_level_tag; pub type evidence_t = *mut ::std::os::raw::c_void; pub type onedump_t = *mut ::std::os::raw::c_void; +pub type cvd_t = *mut ::std::os::raw::c_void; #[repr(C)] #[derive(Debug, Copy, Clone)] pub struct cli_ctx_tag { @@ -642,6 +654,8 @@ pub struct cli_ctx_tag { pub hook_lsig_matches: *mut bitset_t, pub cb_ctx: *mut ::std::os::raw::c_void, pub perf: *mut cli_events_t, + pub properties: *mut json_object, + pub wrkproperty: *mut json_object, pub time_limit: timeval, pub limit_exceeded: bool, pub abort_scan: bool, @@ -711,6 +725,7 @@ pub struct cl_engine { pub ac_mindepth: u32, pub ac_maxdepth: u32, pub tmpdir: *mut ::std::os::raw::c_char, + pub certs_directory: *mut ::std::os::raw::c_char, pub keeptmp: u32, pub engine_options: u64, pub cache_size: u32, @@ -1178,6 +1193,12 @@ extern "C" { res1: ::std::os::raw::c_int, ) -> cl_error_t; } +extern "C" { + pub fn cli_versig( + md5: *const ::std::os::raw::c_char, + dsig: *const ::std::os::raw::c_char, + ) -> cl_error_t; +} extern "C" { pub fn cli_versig2( sha256: *const ::std::os::raw::c_uchar, @@ -1196,8 +1217,6 @@ extern "C" { mode: ::std::os::raw::c_ushort, ) -> *mut ::std::os::raw::c_char; } -pub type css_image_extractor_t = *mut ::std::os::raw::c_void; -pub type css_image_handle_t = *mut ::std::os::raw::c_void; extern "C" { #[doc = " @brief Convenience wrapper for cli_magic_scan_nested_fmap_type().\n\n Creates an fmap and calls cli_magic_scan_nested_fmap_type() for you, with type CL_TYPE_ANY.\n\n @param buffer Pointer to the buffer to be scanned.\n @param length Size in bytes of the buffer being scanned.\n @param ctx Scanning context structure.\n @param name (optional) Original name of the file (to set fmap name metadata)\n @param attributes Layer attributes of the file being scanned (is it normalized, decrypted, etc)\n @return int CL_SUCCESS, or an error code."] pub fn cli_magic_scan_buff( diff --git a/libclamav_rust/src/util.rs b/libclamav_rust/src/util.rs index 086fdf2786..d836d11b0f 100644 --- a/libclamav_rust/src/util.rs +++ b/libclamav_rust/src/util.rs @@ -20,11 +20,21 @@ * MA 02110-1301, USA. */ -use std::{ffi::CStr, fs::File}; +use std::{ffi::CStr, fs::File, os::raw::c_char}; -use log::error; +use glob::glob; +use log::{debug, error, warn}; -use crate::sys; +use crate::{ffi_error, ffi_util::FFIError, sys, validate_str_param}; + +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[error("Glob error: {0}")] + GlobError(#[from] glob::GlobError), + + #[error("IO error: {0}")] + IoError(#[from] std::io::Error), +} /// Obtain a std::fs::File from an i32 in a platform-independent manner. /// @@ -128,3 +138,33 @@ pub unsafe fn scan_archive_metadata( ) } } + +/// C interface to delete files using a glob pattern. +/// +/// # Safety +/// +/// No parameters may be NULL. +#[export_name = "glob_rm"] +pub unsafe extern "C" fn glob_rm( + glob_str: *const c_char, + err: *mut *mut FFIError, +) -> bool { + let glob_str = validate_str_param!(glob_str); + + for entry in glob(glob_str).expect("Failed to read glob pattern") { + match entry { + Ok(path) => { + debug!("Deleting: {path:?}"); + if let Err(e) = std::fs::remove_file(&path) { + warn!("Failed to delete file: {path:?}"); + return ffi_error!(err = err, Error::IoError(e)); + } + } + Err(e) => { + return ffi_error!(err = err, Error::GlobError(e)); + } + } + } + + true +} diff --git a/libfreshclam/libfreshclam.c b/libfreshclam/libfreshclam.c index 400f44694c..635de6542e 100644 --- a/libfreshclam/libfreshclam.c +++ b/libfreshclam/libfreshclam.c @@ -127,6 +127,8 @@ fc_error_t fc_initialize(fc_config *fcConfig) { fc_error_t status = FC_EARG; STATBUF statbuf; + char *certsDirectory = NULL; + FFIError *new_verifier_error = NULL; if (NULL == fcConfig) { printf("fc_initialize: Invalid arguments.\n"); @@ -249,6 +251,28 @@ fc_error_t fc_initialize(fc_config *fcConfig) goto done; } +#ifdef _WIN32 + if ((fcConfig->certsDirectory[strlen(fcConfig->certsDirectory) - 1] != '/') && + ((fcConfig->certsDirectory[strlen(fcConfig->certsDirectory) - 1] != '\\'))) { +#else + if (fcConfig->certsDirectory[strlen(fcConfig->certsDirectory) - 1] != '/') { +#endif + certsDirectory = malloc(strlen(fcConfig->certsDirectory) + strlen(PATHSEP) + 1); + snprintf( + certsDirectory, + strlen(fcConfig->certsDirectory) + strlen(PATHSEP) + 1, + "%s" PATHSEP, + fcConfig->certsDirectory); + } else { + certsDirectory = cli_safer_strdup(fcConfig->certsDirectory); + } + + if (!codesign_verifier_new(certsDirectory, &g_signVerifier, &new_verifier_error)) { + logg(LOGG_ERROR, "Failed to create a new code-signature verifier: %s\n", ffierror_fmt(new_verifier_error)); + status = FC_EINIT; + goto done; + } + g_tempDirectory = cli_safer_strdup(fcConfig->tempDirectory); g_maxAttempts = fcConfig->maxAttempts; @@ -274,6 +298,12 @@ fc_error_t fc_initialize(fc_config *fcConfig) if (FC_SUCCESS != status) { fc_cleanup(); } + if (NULL != certsDirectory) { + free(certsDirectory); + } + if (NULL != new_verifier_error) { + ffierror_free(new_verifier_error); + } return status; } @@ -319,6 +349,9 @@ void fc_cleanup(void) free(g_freshclamDat); g_freshclamDat = NULL; } + if (NULL != g_signVerifier) { + codesign_verifier_free(g_signVerifier); + } } fc_error_t fc_prune_database_directory(char **databaseList, uint32_t nDatabases) @@ -346,13 +379,24 @@ fc_error_t fc_prune_database_directory(char **databaseList, uint32_t nDatabases) while ((dent = readdir(dir))) { if (dent->d_ino) { + // prune any CVD/CLD files that are not in the database list if ((NULL != (extension = strstr(dent->d_name, ".cld"))) || (NULL != (extension = strstr(dent->d_name, ".cvd")))) { + // find the first '-' or '.' in the filename + // Use this to determine the database name. + // We need this so we can ALSO prune the .sign files for unwanted databases. + // Will also be useful in case the database filename includes a hyphenated version number. + const char *first_dash_or_dot = strchr(dent->d_name, '-'); + if (NULL == first_dash_or_dot) { + first_dash_or_dot = extension; + } + uint32_t i; int bFound = 0; for (i = 0; i < nDatabases; i++) { - if (0 == strncmp(databaseList[i], dent->d_name, extension - dent->d_name)) { + // check that the database name is in the database list + if (0 == strncmp(databaseList[i], dent->d_name, first_dash_or_dot - dent->d_name)) { bFound = 1; } } diff --git a/libfreshclam/libfreshclam.h b/libfreshclam/libfreshclam.h index 25d7293fef..3562920935 100644 --- a/libfreshclam/libfreshclam.h +++ b/libfreshclam/libfreshclam.h @@ -60,6 +60,7 @@ typedef struct fc_config_ { const char *proxyPassword; /**< (optional) Password for proxy server authentication. */ const char *databaseDirectory; /**< Filepath of database directory. */ const char *tempDirectory; /**< Filepath to store temp files. */ + const char *certsDirectory; /**< Filepath of clamav ca certificates directory to verify database external digital signatures. */ } fc_config; typedef enum fc_error_tag { diff --git a/libfreshclam/libfreshclam_internal.c b/libfreshclam/libfreshclam_internal.c index 5afde46710..ebd5b64b06 100644 --- a/libfreshclam/libfreshclam_internal.c +++ b/libfreshclam/libfreshclam_internal.c @@ -109,6 +109,7 @@ char *g_proxyPassword = NULL; char *g_tempDirectory = NULL; char *g_databaseDirectory = NULL; +void *g_signVerifier = NULL; uint32_t g_maxAttempts = 0; uint32_t g_connectTimeout = 0; @@ -1442,27 +1443,42 @@ static fc_error_t downloadFile( } static fc_error_t getcvd( + const char *database, const char *cvdfile, const char *tmpfile, char *server, uint32_t ifModifiedSince, - unsigned int remoteVersion, + uint32_t remoteVersion, + char **sign_file, + uint32_t *downloadedVersion, int logerr) { fc_error_t ret; cl_error_t cl_ret; fc_error_t status = FC_EARG; - struct cl_cvd *cvd = NULL; - char *tmpfile_with_extension = NULL; - char *url = NULL; - size_t urlLen = 0; + struct cl_cvd *cvd = NULL; + char extension[5] = {0}; + + char *tmpsignfile = NULL; + size_t tmpsignfileLen = 0; + char *url = NULL; + size_t urlLen = 0; + + char *sign_filename = NULL; + size_t sign_filenameLen = 0; + char *sign_file_url = NULL; + size_t sign_file_urlLen = 0; if ((NULL == cvdfile) || (NULL == tmpfile) || (NULL == server)) { logg(LOGG_ERROR, "getcvd: Invalid arguments.\n"); goto done; } + if (NULL != sign_file) { + *sign_file = NULL; + } + urlLen = strlen(server) + strlen("/") + strlen(cvdfile); url = malloc(urlLen + 1); snprintf(url, urlLen + 1, "%s/%s", server, cvdfile); @@ -1478,36 +1494,53 @@ static fc_error_t getcvd( goto done; } - /* Temporarily rename file to correct extension for verification. */ - tmpfile_with_extension = strdup(tmpfile); - if (!tmpfile_with_extension) { - logg(LOGG_ERROR, "Can't allocate memory for temp file with extension!\n"); - status = FC_EMEM; - goto done; - } - strncpy(tmpfile_with_extension + strlen(tmpfile_with_extension) - 4, cvdfile + strlen(cvdfile) - 4, 4); - if (rename(tmpfile, tmpfile_with_extension) == -1) { - logg(LOGG_ERROR, "Can't rename %s to %s: %s\n", tmpfile, tmpfile_with_extension, strerror(errno)); - status = FC_EDBDIRACCESS; - goto done; - } + // grab the extension from the cvdfile + strncpy(extension, cvdfile + strlen(cvdfile) - 4, 4); - if (CL_SUCCESS != (cl_ret = cl_cvdverify(tmpfile_with_extension))) { - logg(LOGG_ERROR, "Verification: %s\n", cl_strerror(cl_ret)); + if (NULL == (cvd = cl_cvdhead(tmpfile))) { + logg(LOGG_ERROR, "Can't read CVD header of new %s database.\n", cvdfile); status = FC_EBADCVD; goto done; } - if (NULL == (cvd = cl_cvdhead(tmpfile_with_extension))) { - logg(LOGG_ERROR, "Can't read CVD header of new %s database.\n", cvdfile); - status = FC_EBADCVD; - goto done; + // try to get the sign file before verifying the cvd + // use the cvd name + version to get the signature file + // sign-file = database + "-" + version + ".sign" + sign_filenameLen = strlen(database) + strlen("-") + 10 + strlen(".cvd") + strlen(".sign"); + sign_filename = malloc(sign_filenameLen + 1); + snprintf(sign_filename, sign_filenameLen + 1, "%s-%u%s.sign", database, cvd->version, extension); + + // sign-file-url = server + "/" + sign_filename + sign_file_urlLen = strlen(server) + strlen("/") + strlen(sign_filename); + sign_file_url = malloc(sign_file_urlLen + 1); + snprintf(sign_file_url, sign_file_urlLen + 1, "%s/%s", server, sign_filename); + + // sign-file-tempfilename = g_tempDirectory + sign_filename + tmpsignfileLen = strlen(g_tempDirectory) + strlen(PATHSEP) + strlen(sign_filename); + tmpsignfile = malloc(tmpsignfileLen + 1); + snprintf(tmpsignfile, tmpsignfileLen + 1, "%s" PATHSEP "%s", g_tempDirectory, sign_filename); + + ret = downloadFile(sign_file_url, tmpsignfile, 1, logerr, 0); + if (ret != FC_SUCCESS) { + logg(LOGG_DEBUG, "No external .sign digital signature file for %s-%u\n", database, cvd->version); + // It's not an error if the .sign file doesn't exist. + // Just continue with the cvd verification and hope we can use the legacy md5-based rsa method. + } else { + // Set the output variable to the sign file name so we can move it later. + logg(LOGG_DEBUG, "Downloaded digital signature file: %s\n", tmpsignfile); + if (NULL != sign_file) { + CLI_SAFER_STRDUP_OR_GOTO_DONE( + tmpsignfile, + *sign_file, + logg(LOGG_ERROR, "getcvd: Failed to duplicate sign file name.\n"); + status = FC_EMEM); + } } - /* Rename the file back to the original, since verification passed. */ - if (rename(tmpfile_with_extension, tmpfile) == -1) { - logg(LOGG_ERROR, "Can't rename %s to %s: %s\n", tmpfile_with_extension, tmpfile, strerror(errno)); - status = FC_EDBDIRACCESS; + // Now that we have the cvd and the sign file, we can verify the cvd. + if (CL_SUCCESS != (cl_ret = cl_cvdverify(tmpfile))) { + logg(LOGG_ERROR, "Verification: %s\n", cl_strerror(cl_ret)); + status = FC_EBADCVD; goto done; } @@ -1519,16 +1552,15 @@ static fc_error_t getcvd( goto done; } + if (NULL != downloadedVersion) { + *downloadedVersion = cvd->version; + } status = FC_SUCCESS; done: if (NULL != cvd) { cl_cvdfree(cvd); } - if (NULL != tmpfile_with_extension) { - unlink(tmpfile_with_extension); - free(tmpfile_with_extension); - } if (NULL != url) { free(url); } @@ -1540,6 +1572,15 @@ static fc_error_t getcvd( unlink(tmpfile); } } + if (NULL != sign_filename) { + free(sign_filename); + } + if (NULL != sign_file_url) { + free(sign_file_url); + } + if (NULL != tmpsignfile) { + free(tmpsignfile); + } return status; } @@ -1570,8 +1611,7 @@ static fc_error_t mkdir_and_chdir_for_cdiff_tmp(const char *database, const char if (-1 == access(tmpdir, R_OK | W_OK)) { /* - * Temp directory for incremental update (cdiff download) does not - * yet exist. + * Temp directory for incremental update (cdiff download) does not yet exist. */ int ret; bool is_cld = false; @@ -1611,7 +1651,7 @@ static fc_error_t mkdir_and_chdir_for_cdiff_tmp(const char *database, const char /* * 3) Unpack the existing CVD/CLD database to this directory. */ - if (CL_SUCCESS != cl_cvdunpack(cvdfile, tmpdir, is_cld == true)) { + if (CL_SUCCESS != cli_cvdunpack_and_verify(cvdfile, tmpdir, is_cld == true, g_signVerifier)) { logg(LOGG_ERROR, "mkdir_and_chdir_for_cdiff_tmp: Can't unpack %s into %s\n", cvdfile, tmpdir); cli_rmdirs(tmpdir); goto done; @@ -1630,7 +1670,7 @@ static fc_error_t mkdir_and_chdir_for_cdiff_tmp(const char *database, const char return status; } -static fc_error_t downloadPatch( +static fc_error_t downloadPatchAndApply( const char *database, const char *tmpdir, int version, @@ -1640,61 +1680,86 @@ static fc_error_t downloadPatch( fc_error_t ret; fc_error_t status = FC_EARG; - char *tempname = NULL; char patch[DB_FILENAME_MAX]; + char patch_sign_file[DB_FILENAME_MAX + 5]; char olddir[PATH_MAX]; char *url = NULL; size_t urlLen = 0; - int fd = -1; + char *sign_url = NULL; + size_t sign_urlLen = 0; + + FFIError *cdiff_apply_error = NULL; olddir[0] = '\0'; if ((NULL == database) || (NULL == tmpdir) || (NULL == server) || (0 == version)) { - logg(LOGG_ERROR, "downloadPatch: Invalid arguments.\n"); + logg(LOGG_ERROR, "downloadPatchAndApply: Invalid arguments.\n"); goto done; } if (NULL == getcwd(olddir, sizeof(olddir))) { - logg(LOGG_ERROR, "downloadPatch: Can't get path of current working directory\n"); + logg(LOGG_ERROR, "downloadPatchAndApply: Can't get path of current working directory\n"); status = FC_EDIRECTORY; goto done; } + /* + * Unpack the database into a new temp directory where we'll apply the patch, and chdir to it. + * If the directory already exists, we'll just chdir to it. + */ if (FC_SUCCESS != mkdir_and_chdir_for_cdiff_tmp(database, tmpdir)) { status = FC_EDIRECTORY; goto done; } - if (NULL == (tempname = cli_gentemp("."))) { - status = FC_EMEM; - goto done; - } - + /* + * Download the patch. + */ snprintf(patch, sizeof(patch), "%s-%d.cdiff", database, version); + urlLen = strlen(server) + strlen("/") + strlen(patch); url = malloc(urlLen + 1); snprintf(url, urlLen + 1, "%s/%s", server, patch); - if (FC_SUCCESS != (ret = downloadFile(url, tempname, 1, logerr, 0))) { + if (FC_SUCCESS != (ret = downloadFile(url, patch, 1, logerr, 0))) { if (ret == FC_EEMPTYFILE) { logg(LOGG_INFO, "Empty script %s, need to download entire database\n", patch); } else { - logg(logerr ? LOGG_ERROR : LOGG_WARNING, "downloadPatch: Can't download %s from %s\n", patch, url); + logg(logerr ? LOGG_ERROR : LOGG_WARNING, "downloadPatchAndApply: Can't download %s from %s\n", patch, url); } status = ret; goto done; } - if (-1 == (fd = open(tempname, O_RDONLY | O_BINARY))) { - logg(LOGG_ERROR, "downloadPatch: Can't open %s for reading\n", tempname); - status = FC_EFILE; - goto done; + /* + * Download the patch sign file. + */ + snprintf(patch_sign_file, sizeof(patch_sign_file), "%s.sign", patch); + + sign_urlLen = strlen(server) + strlen("/") + strlen(patch_sign_file); + sign_url = malloc(sign_urlLen + 1); + snprintf(sign_url, sign_urlLen + 1, "%s/%s", server, patch_sign_file); + + if (FC_SUCCESS != (ret = downloadFile(sign_url, patch_sign_file, 1, logerr, 0))) { + // No sign file is not an error. + // Just means we'll have to fall back to the legacy sha256-based rsa method for verifying CDIFFs. + logg(LOGG_DEBUG, "No external .sign digital signature file for %s\n", patch); + } else { + logg(LOGG_DEBUG, "Downloaded digital signature file: %s\n", patch_sign_file); } - if (-1 == cdiff_apply(fd, 1)) { - logg(LOGG_ERROR, "downloadPatch: Can't apply patch\n"); + /* + * Apply the patch. + */ + if (!cdiff_apply( + patch, + g_signVerifier, + 1, + &cdiff_apply_error)) { + logg(LOGG_ERROR, "downloadPatchAndApply: Can't apply '%s': %s\n", + patch, ffierror_fmt(cdiff_apply_error)); status = FC_EFAILEDUPDATE; goto done; } @@ -1707,18 +1772,20 @@ static fc_error_t downloadPatch( free(url); } - if (-1 != fd) { - close(fd); + if (NULL != sign_url) { + free(sign_url); } - if (NULL != tempname) { - unlink(tempname); - free(tempname); + if (NULL != cdiff_apply_error) { + ffierror_free(cdiff_apply_error); } + /* + * Change back to the original directory. + */ if ('\0' != olddir[0]) { if (-1 == chdir(olddir)) { - logg(LOGG_ERROR, "downloadPatch: Can't chdir to %s\n", olddir); + logg(LOGG_ERROR, "downloadPatchAndApply: Can't chdir to %s\n", olddir); status = FC_EDIRECTORY; } } @@ -1780,6 +1847,7 @@ static fc_error_t buildcld( char olddir[PATH_MAX] = {0}; char info[DB_FILENAME_MAX]; + char cfg[DB_FILENAME_MAX]; char buff[CVD_HEADER_SIZE + 1]; char *pt; @@ -1871,9 +1939,11 @@ static fc_error_t buildcld( } } - if (-1 != access("daily.cfg", R_OK)) { - if (-1 == tar_addfile(fd, gzs, "daily.cfg")) { - logg(LOGG_ERROR, "buildcld: Can't add daily.cfg to new %s.cld - please check if there is enough disk space available\n", database); + snprintf(cfg, sizeof(cfg), "%s.cfg", database); + cfg[sizeof(cfg) - 1] = 0; + if (-1 != access(cfg, R_OK)) { + if (-1 == tar_addfile(fd, gzs, cfg)) { + logg(LOGG_ERROR, "buildcld: Can't add %s to new %s.cld - please check if there is enough disk space available\n", cfg, database); status = FC_EFAILEDUPDATE; goto done; } @@ -1887,7 +1957,7 @@ static fc_error_t buildcld( while (NULL != (dent = readdir(dir))) { if (dent->d_ino) { - if (!strcmp(dent->d_name, ".") || !strcmp(dent->d_name, "..") || !strcmp(dent->d_name, "COPYING") || !strcmp(dent->d_name, "daily.cfg") || !strcmp(dent->d_name, info)) + if (!strcmp(dent->d_name, ".") || !strcmp(dent->d_name, "..") || !strcmp(dent->d_name, "COPYING") || !strcmp(dent->d_name, cfg) || !strcmp(dent->d_name, info)) continue; if (tar_addfile(fd, gzs, dent->d_name) == -1) { @@ -2272,8 +2342,12 @@ fc_error_t updatedb( char *remoteFilename = NULL; char *newLocalFilename = NULL; - char *tmpdir = NULL; - char *tmpfile = NULL; + char *cld_build_dir = NULL; + char *tmpfile = NULL; + + char *signfile = NULL; + uint32_t downloadedVersion = 0; + FFIError *glob_rm_error = NULL; unsigned int flevel; @@ -2312,18 +2386,24 @@ fc_error_t updatedb( goto up_to_date; } - /* Download CVD or CLD to temp file */ - tmpfile = cli_gentemp(g_tempDirectory); + /* + * Download CVD or CLD to a file in the temp directory. + */ + + // Create a temp file for the new database. + tmpfile = calloc(1, strlen(g_tempDirectory) + strlen("/") + strlen(remoteFilename) + 1); if (!tmpfile) { status = FC_EMEM; goto done; } + snprintf(tmpfile, strlen(g_tempDirectory) + strlen("/") + strlen(remoteFilename) + 1, + "%s/%s", g_tempDirectory, remoteFilename); if ((localVersion == 0) || (!bScriptedUpdates)) { /* * Download entire file. */ - ret = getcvd(remoteFilename, tmpfile, server, localTimestamp, remoteVersion, logerr); + ret = getcvd(database, remoteFilename, tmpfile, server, localTimestamp, remoteVersion, &signfile, &downloadedVersion, logerr); if (FC_UPTODATE == ret) { logg(LOGG_WARNING, "Expected newer version of %s database but the server's copy is not newer than our local file (version %d).\n", database, localVersion); if (NULL != localFilename) { @@ -2342,6 +2422,8 @@ fc_error_t updatedb( goto done; } + // The file name won't change for a simple download. + // It will only change if we're doing a scripted update. newLocalFilename = cli_safer_strdup(remoteFilename); } else { /* @@ -2350,8 +2432,9 @@ fc_error_t updatedb( ret = FC_SUCCESS; uint32_t numPatchesReceived = 0; - tmpdir = cli_gentemp(g_tempDirectory); - if (!tmpdir) { + // Create a temp directory where we'll build the new CLD. + cld_build_dir = cli_gentemp_with_prefix(g_tempDirectory, "cld"); + if (!cld_build_dir) { status = FC_EMEM; goto done; } @@ -2382,7 +2465,10 @@ fc_error_t updatedb( { mprintf(LOGG_INFO, "Downloading database patch # %u...\n", i); } - ret = downloadPatch(database, tmpdir, i, server, llogerr); + + // If the build directory doesn't exist, we'll create it and unpack the database into it. + // Then we download and apply the patch. + ret = downloadPatchAndApply(database, cld_build_dir, i, server, llogerr); if (ret == FC_ECONNECTION || ret == FC_EFAILEDGET) { continue; } else { @@ -2412,7 +2498,7 @@ fc_error_t updatedb( logg(LOGG_WARNING, "Incremental update failed, trying to download %s\n", remoteFilename); } - ret = getcvd(remoteFilename, tmpfile, server, localTimestamp, remoteVersion, logerr); + ret = getcvd(database, remoteFilename, tmpfile, server, localTimestamp, remoteVersion, &signfile, &downloadedVersion, logerr); if (FC_SUCCESS != ret) { if (FC_EMIRRORNOTSYNC == ret) { /* Note: We can't retry with CDIFF's if FC_EMIRRORNOTSYNC happened here. @@ -2427,6 +2513,8 @@ fc_error_t updatedb( } } + // We gave up on patching, so it's back to a simple file download. + // The file name won't change for a simple download. newLocalFilename = cli_safer_strdup(remoteFilename); } else if (0 == numPatchesReceived) { logg(LOGG_INFO, "The database server doesn't have the latest patch for the %s database (version %u). The server will likely have updated if you check again in a few hours.\n", database, remoteVersion); @@ -2434,23 +2522,33 @@ fc_error_t updatedb( goto up_to_date; } else { /* - * CDIFFs downloaded; Use CDIFFs to turn old CVD/CLD into new updated CLD. + * CDIFFs downloaded and applied; Use CDIFFs to turn old CVD/CLD into new updated CLD. */ if (numPatchesReceived < remoteVersion - localVersion) { logg(LOGG_INFO, "Downloaded %u patches for %s, which is fewer than the %u expected patches.\n", numPatchesReceived, database, remoteVersion - localVersion); logg(LOGG_INFO, "We'll settle for this partial-update, at least for now.\n"); } - size_t newLocalFilenameLen = 0; - if (FC_SUCCESS != buildcld(tmpdir, database, tmpfile, g_bCompressLocalDatabase)) { + // For a scripted update, the new database will have + // a .cld extension. + // Overwrite the tmpfile's .cvd extension with a .cld extension + sprintf(tmpfile + strlen(tmpfile) - 3, "cld"); + + // And set the new filename that we'll used to copy to the DB directory + size_t newLocalFilenameLen = strlen(database) + strlen(".cld"); + newLocalFilename = malloc(newLocalFilenameLen + 1); + snprintf(newLocalFilename, newLocalFilenameLen + 1, "%s.cld", database); + + if (FC_SUCCESS != buildcld(cld_build_dir, database, tmpfile, g_bCompressLocalDatabase)) { logg(LOGG_ERROR, "updatedb: Incremental update failed. Failed to build CLD.\n"); status = FC_EBADCVD; goto done; } - newLocalFilenameLen = strlen(database) + strlen(".cld"); - newLocalFilename = malloc(newLocalFilenameLen + 1); - snprintf(newLocalFilename, newLocalFilenameLen + 1, "%s.cld", database); + // CLD's can't be signed, so we don't need to worry about the signature file. + // It's in the tmp directory so we don't need to manually delete it. + // Just free up the filename and we won't copy it into the DB directory later. + CLI_FREE_AND_SET_NULL(signfile); } } @@ -2459,26 +2557,6 @@ fc_error_t updatedb( * Test database before replacing original database with new database. */ if (NULL != g_cb_download_complete) { - char *tmpfile_with_extension = NULL; - size_t tmpfile_with_extension_len = strlen(tmpfile) + 1 + strlen(newLocalFilename); - - /* Suffix tmpfile with real database name & extension so it can be loaded. */ - tmpfile_with_extension = malloc(tmpfile_with_extension_len + 1); - if (!tmpfile_with_extension) { - status = FC_ETESTFAIL; - goto done; - } - snprintf(tmpfile_with_extension, tmpfile_with_extension_len + 1, "%s-%s", tmpfile, newLocalFilename); - if (rename(tmpfile, tmpfile_with_extension) == -1) { - logg(LOGG_ERROR, "updatedb: Can't rename %s to %s: %s\n", tmpfile, tmpfile_with_extension, strerror(errno)); - free(tmpfile_with_extension); - status = FC_EDBDIRACCESS; - goto done; - } - free(tmpfile); - tmpfile = tmpfile_with_extension; - tmpfile_with_extension = NULL; - /* Run callback to test it. */ logg(LOGG_DEBUG, "updatedb: Running g_cb_download_complete callback...\n"); if (FC_SUCCESS != (ret = g_cb_download_complete(tmpfile, context))) { @@ -2491,6 +2569,8 @@ fc_error_t updatedb( /* * Replace original database with new database. */ + logg(LOGG_DEBUG, "updatedb: Moving %s to %s" PATHSEP "%s\n", tmpfile, g_databaseDirectory, newLocalFilename); + #ifdef _WIN32 if (!access(newLocalFilename, R_OK) && unlink(newLocalFilename)) { logg(LOGG_ERROR, "Update failed. Can't delete the old database %s to replace it with a new database. Please fix the problem manually and try again.\n", newLocalFilename); @@ -2504,10 +2584,53 @@ fc_error_t updatedb( goto done; } + // If there are any old signature files for this database in the DB directory, delete them. + // We'll use a glob pattern to match the signature files + char *pattern = calloc(1, strlen(database) + strlen("-*.sign") + 1); + if (!pattern) { + logg(LOGG_ERROR, "updatedb: Failed to allocate memory for signature file pattern.\n"); + status = FC_EMEM; + goto done; + } + sprintf(pattern, "%s-*.sign", database); + + if (!glob_rm(pattern, &glob_rm_error)) { + cli_errmsg("updatedb: Failed to glob-delete old .sign files with pattern '%s': %s\n", + pattern, ffierror_fmt(glob_rm_error)); + ffierror_free(glob_rm_error); + free(pattern); + status = FC_ERROR; + goto done; + } + free(pattern); + + // If we have a signature file, move it from the temp directory to the database directory + if (NULL != signfile) { + char *newSignFilename = NULL; + + logg(LOGG_DEBUG, "updatedb: Moving signature file %s to database directory\n", signfile); + + // get the basename of the signfile + if (CL_SUCCESS != cli_basename(signfile, strlen(signfile), &newSignFilename)) { + logg(LOGG_ERROR, "updatedb: Failed to get basename of '%s'\n", signfile); + goto done; + } + + if (rename(signfile, newSignFilename) == -1) { + logg(LOGG_ERROR, "updatedb: Can't rename %s to %s: %s\n", signfile, newSignFilename, strerror(errno)); + free(newSignFilename); + status = FC_EDBDIRACCESS; + goto done; + } + free(newSignFilename); + } + /* If we just updated from a CVD to a CLD, delete the old CVD */ - if ((NULL != localFilename) && !access(localFilename, R_OK) && strcmp(newLocalFilename, localFilename)) - if (unlink(localFilename)) + if ((NULL != localFilename) && !access(localFilename, R_OK) && strcmp(newLocalFilename, localFilename)) { + if (unlink(localFilename)) { logg(LOGG_WARNING, "updatedb: Can't delete the old database file %s. Please remove it manually.\n", localFilename); + } + } /* Parse header to record number of sigs. */ if (NULL == (cvd = cl_cvdhead(newLocalFilename))) { @@ -2561,9 +2684,12 @@ fc_error_t updatedb( unlink(tmpfile); free(tmpfile); } - if (NULL != tmpdir) { - cli_rmdirs(tmpdir); - free(tmpdir); + if (NULL != cld_build_dir) { + cli_rmdirs(cld_build_dir); + free(cld_build_dir); + } + if (NULL != signfile) { + free(signfile); } return status; diff --git a/libfreshclam/libfreshclam_internal.h b/libfreshclam/libfreshclam_internal.h index 3683135633..722f864fc9 100644 --- a/libfreshclam/libfreshclam_internal.h +++ b/libfreshclam/libfreshclam_internal.h @@ -59,6 +59,7 @@ extern char *g_proxyPassword; extern char *g_tempDirectory; extern char *g_databaseDirectory; +extern void *g_signVerifier; extern uint32_t g_maxAttempts; extern uint32_t g_connectTimeout; diff --git a/sigtool/sigtool.c b/sigtool/sigtool.c index 8e36a4684b..b38420ad85 100644 --- a/sigtool/sigtool.c +++ b/sigtool/sigtool.c @@ -855,6 +855,294 @@ void removeTempDir(const struct optstruct *opts, char *dir) } } +/** + * @brief Determine the name of the sign file based on the target file name. + * + * If the target file name ends with '.cvd', '.cud', or '.cld' then the sign file name will be + * 'name-version.cvd.sign', 'name-version.cud.sign', or 'name-version.cld.sign' respectively. + * + * If the target file name does not end with '.cvd', '.cud', or '.cld' then the sign file name will be + * 'target.sign'. + * + * The sign file name will be placed in the same directory as the target file. + * + * If the target file is a CVD file, the version number will be extracted from the CVD file. + * + * The caller is responsible for freeing the memory allocated for the sign file name. + * + * @param target The target file name. + * @return char* + */ +static char *get_sign_file_name(char *target) +{ + char *sign_file_name = NULL; + char *dir = NULL; + FFIError *cvd_open_error = NULL; + uint32_t cvd_version = 0; + char *cvd_name = NULL; + cvd_t *cvd = NULL; + char *target_dup = NULL; + + cvd_type cvd_type = CVD_TYPE_UNKNOWN; + const char *cvd_extension = NULL; + + // If the target filename ends with '.cvd', + // the sign file name will be 'name-version.cvd.sign' + if (cli_strbcasestr(target, ".cvd")) { + cvd_type = CVD_TYPE_CVD; + cvd_extension = "cvd"; + } else if (cli_strbcasestr(target, ".cld")) { + cvd_type = CVD_TYPE_CLD; + cvd_extension = "cld"; + } else if (cli_strbcasestr(target, ".cud")) { + cvd_type = CVD_TYPE_CUD; + cvd_extension = "cud"; + } + + if (cvd_type != CVD_TYPE_UNKNOWN) { + // Signing a signature archive. We need to open the CVD file to get the version to use in the .sign file name. + cvd = cvd_open(target, &cvd_open_error); + if (NULL == cvd) { + mprintf(LOGG_ERROR, "get_sign_file_name: Failed to open CVD file '%s': %s\n", target, ffierror_fmt(cvd_open_error)); + goto done; + } + + cvd_version = cvd_get_version(cvd); + cvd_name = cvd_get_name(cvd); + + // get the directory from the target filename + target_dup = strdup(target); + if (NULL == target_dup) { + mprintf(LOGG_ERROR, "get_sign_file_name: Failed to duplicate target filepath.\n"); + goto done; + } + dir = dirname(target_dup); + if (NULL == dir) { + mprintf(LOGG_ERROR, "get_sign_file_name: Failed to get directory from target filename.\n"); + goto done; + } + + sign_file_name = calloc(1, strlen(dir) + 1 + strlen(cvd_name) + 1 + (3 * sizeof(uint32_t) + 2) + 1 + strlen(cvd_extension) + strlen(".sign") + 1); + if (NULL == sign_file_name) { + mprintf(LOGG_ERROR, "get_sign_file_name: Memory allocation error.\n"); + goto done; + } + + sprintf(sign_file_name, "%s/%s-%u.%s.sign", dir, cvd_name, cvd_version, cvd_extension); + } else { + // sign file name will just be the target filename with an added '.sign' extension + sign_file_name = malloc(strlen(target) + strlen(".sign") + 1); + if (NULL == sign_file_name) { + mprintf(LOGG_ERROR, "get_sign_file_name: Memory allocation error.\n"); + goto done; + } + strcpy(sign_file_name, target); + strcat(sign_file_name, ".sign"); + } + +done: + + if (NULL != cvd_name) { + ffi_cstring_free(cvd_name); + } + if (NULL != cvd) { + cvd_free(cvd); + } + if (NULL != target_dup) { + free(target_dup); + } + if (NULL != cvd_open_error) { + ffierror_free(cvd_open_error); + } + + return sign_file_name; +} + +static int sign(const struct optstruct *opts) +{ + int ret = -1; + const struct optstruct *opt; + + char *target = NULL; + char *sign_file_name = NULL; + + char *signing_key = NULL; + + char **certs = NULL; + size_t certs_count = 0; + + bool sign_result = false; + FFIError *sign_file_error = NULL; + + bool append = false; + + target = optget(opts, "sign")->strarg; + if (NULL == target) { + mprintf(LOGG_ERROR, "sign: No target file specified.\n"); + mprintf(LOGG_ERROR, "To sign a file with sigtool, you must specify a target file and use the --key and --cert options.\n"); + mprintf(LOGG_ERROR, "For example: sigtool --sign myfile.cvd --key /path/to/private.key --cert /path/to/public.pem --cert /path/to/intermediate.pem --cert /path/to/root-ca.pem\n"); + goto done; + } + + sign_file_name = get_sign_file_name(target); + if (NULL == sign_file_name) { + mprintf(LOGG_ERROR, "sign: Failed to determine sign file name from target: %s\n", target); + goto done; + } + + signing_key = optget(opts, "key")->strarg; + if (NULL == target) { + mprintf(LOGG_ERROR, "sign: No private key specified.\n"); + mprintf(LOGG_ERROR, "To sign a file with sigtool, you must specify a target file and use the --key and --cert options.\n"); + mprintf(LOGG_ERROR, "For example: sigtool --sign myfile.cvd --key /path/to/private.key --cert /path/to/public.pem --cert /path/to/intermediate.pem --cert /path/to/root-ca.pem\n"); + goto done; + } + + opt = optget(opts, "cert"); + if (NULL == opt) { + mprintf(LOGG_ERROR, "sign: No signing or intermediate certificates specified.\n"); + mprintf(LOGG_ERROR, "To sign a file with sigtool, you must specify a target file and use the --key and --cert options.\n"); + mprintf(LOGG_ERROR, "For example: sigtool --sign myfile.cvd --key /path/to/private.key --cert /path/to/public.pem --cert /path/to/intermediate.pem --cert /path/to/root-ca.pem\n"); + goto done; + } + + while (opt) { + if (!opt->strarg) { + mprintf(LOGG_ERROR, "sign: The --cert option requires a path value to a signing or intermediate certificate.\n"); + mprintf(LOGG_ERROR, "To sign a file with sigtool, you must specify a target file and use the --key and --cert options.\n"); + mprintf(LOGG_ERROR, "For example: sigtool --sign myfile.cvd --key /path/to/private.key --cert /path/to/public.pem --cert /path/to/intermediate.pem --cert /path/to/root-ca.pem\n"); + goto done; + } + + certs_count += 1; + + certs = realloc(certs, certs_count * sizeof(char *)); + if (NULL == certs) { + mprintf(LOGG_ERROR, "sign: Memory allocation error.\n"); + goto done; + } + + certs[certs_count - 1] = opt->strarg; + + opt = opt->nextarg; + } + + if (optget(opts, "append")->enabled) { + append = true; + } + + sign_result = codesign_sign_file( + target, + sign_file_name, + signing_key, + (const char **)certs, + certs_count, + append, + &sign_file_error); + + if (!sign_result) { + mprintf(LOGG_ERROR, "sign: Failed to sign file '%s': %s\n", target, ffierror_fmt(sign_file_error)); + goto done; + } + + mprintf(LOGG_INFO, "sign: Successfully signed file '%s', and placed the signature in '%s'\n", target, sign_file_name); + ret = 0; + +done: + + if (NULL != sign_file_name) { + free(sign_file_name); + } + if (NULL != certs) { + free(certs); + } + if (NULL != sign_file_error) { + ffierror_free(sign_file_error); + } + + return ret; +} + +static int verify(const struct optstruct *opts) +{ + int ret = -1; + char *target = NULL; + char *sign_file_name = NULL; + + char *cvdcertsdir = NULL; + + char *signer_name = NULL; + bool verify_result = false; + FFIError *verify_file_error = NULL; + + void *verifier = NULL; + FFIError *new_verifier_error = NULL; + + target = optget(opts, "verify")->strarg; + if (NULL == target) { + mprintf(LOGG_ERROR, "verify: No target file specified.\n"); + mprintf(LOGG_ERROR, "To verify a file signed with sigtool, you must specify a target file. You may also override the default certificates directory using --cvdcertsdir.\n"); + mprintf(LOGG_ERROR, "For example: sigtool --verify myfile.cvd --cvdcertsdir /path/to/certs/\n"); + goto done; + } + + sign_file_name = get_sign_file_name(target); + if (NULL == sign_file_name) { + mprintf(LOGG_ERROR, "verify: Failed to determine sign file name.\n"); + goto done; + } + + cvdcertsdir = optget(opts, "cvdcertsdir")->strarg; + if (NULL == cvdcertsdir) { + // Check if the CVD_CERTS_DIR environment variable is set + cvdcertsdir = getenv("CVD_CERTS_DIR"); + + // If not, use the default value + if (NULL == cvdcertsdir) { + cvdcertsdir = CERTSDIR; + } + } + + if (!codesign_verifier_new(cvdcertsdir, &verifier, &new_verifier_error)) { + mprintf(LOGG_ERROR, "verify: Failed to create verifier: %s\n", ffierror_fmt(new_verifier_error)); + goto done; + } + + verify_result = codesign_verify_file( + target, + sign_file_name, + verifier, + &signer_name, + &verify_file_error); + if (!verify_result) { + mprintf(LOGG_ERROR, "verify: Failed to verify file '%s': %s\n", target, ffierror_fmt(verify_file_error)); + goto done; + } + + mprintf(LOGG_INFO, "verify: Successfully verified file '%s' with signature '%s', signed by '%s'\n", target, sign_file_name, signer_name); + ret = 0; + +done: + + if (NULL != signer_name) { + ffi_cstring_free(signer_name); + } + if (NULL != sign_file_name) { + free(sign_file_name); + } + if (NULL != verify_file_error) { + ffierror_free(verify_file_error); + } + if (NULL != verifier) { + codesign_verifier_free(verifier); + } + if (NULL != new_verifier_error) { + ffierror_free(new_verifier_error); + } + + return ret; +} + static int build(const struct optstruct *opts) { int ret, bc = 0, hy = 0; @@ -1242,7 +1530,7 @@ static int build(const struct optstruct *opts) return -1; } - if (CL_SUCCESS != cl_cvdunpack(olddb, pt, true)) { + if (CL_SUCCESS != cl_cvdunpack_ex(olddb, pt, true, NULL)) { mprintf(LOGG_ERROR, "build: Can't unpack CVD file %s\n", olddb); removeTempDir(opts, pt); free(pt); @@ -1257,7 +1545,7 @@ static int build(const struct optstruct *opts) return -1; } - if (CL_SUCCESS != cl_cvdunpack(newcvd, pt, true)) { + if (CL_SUCCESS != cl_cvdunpack_ex(newcvd, pt, true, NULL)) { mprintf(LOGG_ERROR, "build: Can't unpack CVD file %s\n", newcvd); removeTempDir(opts, pt); free(pt); @@ -1302,7 +1590,8 @@ static int build(const struct optstruct *opts) static int unpack(const struct optstruct *opts) { char name[512], *dbdir; - const char *localdbdir = NULL; + const char *localdbdir = NULL; + const char *certs_directory = NULL; if (optget(opts, "datadir")->active) localdbdir = optget(opts, "datadir")->strarg; @@ -1325,12 +1614,23 @@ static int unpack(const struct optstruct *opts) name[sizeof(name) - 1] = '\0'; } - if (cl_cvdverify(name) != CL_SUCCESS) { + certs_directory = optget(opts, "cvdcertsdir")->strarg; + if (NULL == certs_directory) { + // Check if the CVD_CERTS_DIR environment variable is set + certs_directory = getenv("CVD_CERTS_DIR"); + + // If not, use the default value + if (NULL == certs_directory) { + certs_directory = CERTSDIR; + } + } + + if (cl_cvdverify_ex(name, certs_directory) != CL_SUCCESS) { mprintf(LOGG_ERROR, "unpack: %s is not a valid CVD\n", name); return -1; } - if (CL_SUCCESS != cl_cvdunpack(name, ".", true)) { + if (CL_SUCCESS != cl_cvdunpack_ex(name, ".", true, NULL)) { mprintf(LOGG_ERROR, "unpack: Can't unpack file %s\n", name); return -1; } @@ -1477,7 +1777,7 @@ static int listdb(const struct optstruct *opts, const char *filename, const rege return -1; } - if (CL_SUCCESS != cl_cvdunpack(filename, dir, true)) { + if (CL_SUCCESS != cl_cvdunpack_ex(filename, dir, true, NULL)) { mprintf(LOGG_ERROR, "listdb: Can't unpack CVD file %s\n", filename); removeTempDir(opts, dir); free(dir); @@ -1948,9 +2248,13 @@ static int comparesha(const char *diff) static int rundiff(const struct optstruct *opts) { - int fd, ret; + int ret; unsigned short mode; const char *diff; + FFIError *cdiff_apply_error = NULL; + char *cvdcertsdir = NULL; + void *verifier = NULL; + FFIError *new_verifier_error = NULL; diff = optget(opts, "run-cdiff")->strarg; if (strstr(diff, ".cdiff")) { @@ -1962,16 +2266,49 @@ static int rundiff(const struct optstruct *opts) return -1; } - if ((fd = open(diff, O_RDONLY | O_BINARY)) == -1) { - mprintf(LOGG_ERROR, "rundiff: Can't open file %s\n", diff); - return -1; + cvdcertsdir = optget(opts, "cvdcertsdir")->strarg; + if (NULL == cvdcertsdir) { + // Check if the CVD_CERTS_DIR environment variable is set + cvdcertsdir = getenv("CVD_CERTS_DIR"); + + // If not, use the default value + if (NULL == cvdcertsdir) { + cvdcertsdir = CERTSDIR; + } } - ret = cdiff_apply(fd, mode); - close(fd); + if (!codesign_verifier_new(cvdcertsdir, &verifier, &new_verifier_error)) { + cli_errmsg("rundiff: Failed to create a new code-signature verifier: %s\n", ffierror_fmt(new_verifier_error)); + ret = -1; + goto done; + } - if (!ret) - ret = comparesha(diff); + if (!cdiff_apply( + diff, + verifier, + mode, + &cdiff_apply_error)) { + + mprintf(LOGG_ERROR, "rundiff: Error applying '%s': %s\n", + diff, ffierror_fmt(cdiff_apply_error)); + ret = -1; + goto done; + } + + // success. compare the SHA256 checksums of the files + ret = comparesha(diff); + +done: + + if (NULL != verifier) { + codesign_verifier_free(verifier); + } + if (NULL != new_verifier_error) { + ffierror_free(new_verifier_error); + } + if (NULL != cdiff_apply_error) { + ffierror_free(cdiff_apply_error); + } return ret; } @@ -2221,9 +2558,13 @@ static int dircopy(const char *src, const char *dest) static int verifydiff(const struct optstruct *opts, const char *diff, const char *cvd, const char *incdir) { - char *tempdir, cwd[512]; - int ret = 0, fd; + char *tempdir = NULL; + char cwd[512]; + int ret = -1; unsigned short mode; + FFIError *cdiff_apply_error = NULL; + char *cvdcertsdir = NULL; + bool created_temp_dir = false; if (strstr(diff, ".cdiff")) { mode = 1; @@ -2231,74 +2572,71 @@ static int verifydiff(const struct optstruct *opts, const char *diff, const char mode = 0; } else { mprintf(LOGG_ERROR, "verifydiff: Incorrect file name (no .cdiff/.script extension)\n"); - return -1; + goto done; } if (!(tempdir = createTempDir(opts))) { - return -1; + goto done; } + created_temp_dir = true; if (cvd) { - if (CL_SUCCESS != cl_cvdunpack(cvd, tempdir, true)) { + if (CL_SUCCESS != cl_cvdunpack_ex(cvd, tempdir, true, NULL)) { mprintf(LOGG_ERROR, "verifydiff: Can't unpack CVD file %s\n", cvd); - removeTempDir(opts, tempdir); - free(tempdir); - return -1; + goto done; } } else { if (dircopy(incdir, tempdir) == -1) { mprintf(LOGG_ERROR, "verifydiff: Can't copy dir %s to %s\n", incdir, tempdir); - removeTempDir(opts, tempdir); - free(tempdir); - return -1; + goto done; } } if (!getcwd(cwd, sizeof(cwd))) { mprintf(LOGG_ERROR, "verifydiff: getcwd() failed\n"); - removeTempDir(opts, tempdir); - free(tempdir); - return -1; - } - - if ((fd = open(diff, O_RDONLY | O_BINARY)) == -1) { - mprintf(LOGG_ERROR, "verifydiff: Can't open diff file %s\n", diff); - removeTempDir(opts, tempdir); - free(tempdir); - return -1; + goto done; } if (chdir(tempdir) == -1) { mprintf(LOGG_ERROR, "verifydiff: Can't chdir to %s\n", tempdir); - removeTempDir(opts, tempdir); - free(tempdir); - close(fd); - return -1; + goto done; } - if (cdiff_apply(fd, mode) == -1) { + if (!cdiff_apply( + diff, + cvdcertsdir, + mode, + &cdiff_apply_error)) { mprintf(LOGG_ERROR, "verifydiff: Can't apply %s\n", diff); - if (chdir(cwd) == -1) + if (chdir(cwd) == -1) { mprintf(LOGG_WARNING, "verifydiff: Can't chdir to %s\n", cwd); - removeTempDir(opts, tempdir); - free(tempdir); - close(fd); - return -1; + } + goto done; } - close(fd); ret = comparesha(diff); - if (chdir(cwd) == -1) + if (chdir(cwd) == -1) { mprintf(LOGG_WARNING, "verifydiff: Can't chdir to %s\n", cwd); - removeTempDir(opts, tempdir); - free(tempdir); + } - if (!ret) { - if (cvd) + if (0 == ret) { + if (cvd) { mprintf(LOGG_INFO, "Verification: %s correctly applies to %s\n", diff, cvd); - else + } else { mprintf(LOGG_INFO, "Verification: %s correctly applies to the previous version\n", diff); + } + } + +done: + if (NULL != tempdir) { + free(tempdir); + } + if (NULL != cdiff_apply_error) { + ffierror_free(cdiff_apply_error); + } + if (created_temp_dir) { + removeTempDir(opts, tempdir); } return ret; @@ -3458,7 +3796,7 @@ static int makediff(const struct optstruct *opts) return -1; } - if (CL_SUCCESS != cl_cvdunpack(optget(opts, "diff")->strarg, odir, true)) { + if (CL_SUCCESS != cl_cvdunpack_ex(optget(opts, "diff")->strarg, odir, true, NULL)) { mprintf(LOGG_ERROR, "makediff: Can't unpack CVD file %s\n", optget(opts, "diff")->strarg); removeTempDir(opts, odir); free(odir); @@ -3469,7 +3807,7 @@ static int makediff(const struct optstruct *opts) return -1; } - if (CL_SUCCESS != cl_cvdunpack(opts->filename[0], ndir, true)) { + if (CL_SUCCESS != cl_cvdunpack_ex(opts->filename[0], ndir, true, NULL)) { mprintf(LOGG_ERROR, "makediff: Can't unpack CVD file %s\n", opts->filename[0]); removeTempDir(opts, odir); removeTempDir(opts, ndir); @@ -3644,22 +3982,50 @@ static void help(void) mprintf(LOGG_INFO, " --quiet Be quiet, output only error messages\n"); mprintf(LOGG_INFO, " --debug Enable debug messages\n"); mprintf(LOGG_INFO, " --stdout Write to stdout instead of stderr. Does not affect 'debug' messages.\n"); - mprintf(LOGG_INFO, " --hex-dump Convert data from stdin to a hex\n"); - mprintf(LOGG_INFO, " string and print it on stdout\n"); - mprintf(LOGG_INFO, " --md5 [FILES] Generate MD5 checksum from stdin\n"); + mprintf(LOGG_INFO, " --tempdir=DIRECTORY Create temporary files in DIRECTORY\n"); + mprintf(LOGG_INFO, " --leave-temps[=yes/no(*)] Do not remove temporary files\n"); + mprintf(LOGG_INFO, " --datadir=DIR Use DIR as default database directory\n"); + mprintf(LOGG_INFO, "\n"); + mprintf(LOGG_INFO, " Commands for working with signatures:\n"); + mprintf(LOGG_INFO, "\n"); + mprintf(LOGG_INFO, " --list-sigs[=FILE] -l[FILE] List signature names\n"); + mprintf(LOGG_INFO, " --find-sigs=REGEX -fREGEX Find signatures matching REGEX\n"); + mprintf(LOGG_INFO, " --decode-sigs Decode signatures from stdin\n"); + mprintf(LOGG_INFO, " --test-sigs=DATABASE TARGET_FILE Test signatures from DATABASE against \n"); + mprintf(LOGG_INFO, " TARGET_FILE\n"); + mprintf(LOGG_INFO, "\n"); + mprintf(LOGG_INFO, " Commands to generate signatures:\n"); + mprintf(LOGG_INFO, "\n"); + mprintf(LOGG_INFO, " --md5 [FILES] Generate MD5 hash from stdin\n"); mprintf(LOGG_INFO, " or MD5 sigs for FILES\n"); - mprintf(LOGG_INFO, " --sha1 [FILES] Generate SHA1 checksum from stdin\n"); + mprintf(LOGG_INFO, " --sha1 [FILES] Generate SHA1 hash from stdin\n"); mprintf(LOGG_INFO, " or SHA1 sigs for FILES\n"); - mprintf(LOGG_INFO, " --sha256 [FILES] Generate SHA256 checksum from stdin\n"); + mprintf(LOGG_INFO, " --sha256 [FILES] Generate SHA256 hash from stdin\n"); mprintf(LOGG_INFO, " or SHA256 sigs for FILES\n"); mprintf(LOGG_INFO, " --mdb [FILES] Generate .mdb (section hash) sigs\n"); mprintf(LOGG_INFO, " --imp [FILES] Generate .imp (import table hash) sigs\n"); mprintf(LOGG_INFO, " --fuzzy-img FILE(S) Generate image fuzzy hash for each file\n"); + mprintf(LOGG_INFO, "\n"); + mprintf(LOGG_INFO, " Commands to normalize files, so you can write more generic signatures:\n"); + mprintf(LOGG_INFO, "\n"); mprintf(LOGG_INFO, " --html-normalise=FILE Create normalised parts of HTML file\n"); mprintf(LOGG_INFO, " --ascii-normalise=FILE Create normalised text file from ascii source\n"); mprintf(LOGG_INFO, " --utf16-decode=FILE Decode UTF16 encoded files\n"); - mprintf(LOGG_INFO, " --info=FILE -i FILE Print database information\n"); - mprintf(LOGG_INFO, " --build=NAME [cvd] -b NAME Build a CVD file\n"); + mprintf(LOGG_INFO, "\n"); + mprintf(LOGG_INFO, " Assorted commands to aid file analysis:\n"); + mprintf(LOGG_INFO, "\n"); + mprintf(LOGG_INFO, " --vba=FILE Extract VBA/Word6 macro code\n"); + mprintf(LOGG_INFO, " --vba-hex=FILE Extract Word6 macro code with hex values\n"); + mprintf(LOGG_INFO, " --print-certs=FILE Print Authenticode details from a PE\n"); + mprintf(LOGG_INFO, " --hex-dump Convert data from stdin to a hex\n"); + mprintf(LOGG_INFO, " string and print it on stdout\n"); + mprintf(LOGG_INFO, "\n"); + mprintf(LOGG_INFO, " Commands for working with CVD signature database archives:\n"); + mprintf(LOGG_INFO, "\n"); + mprintf(LOGG_INFO, " --info=FILE -i FILE Print CVD database archive information\n"); + mprintf(LOGG_INFO, "\n"); + mprintf(LOGG_INFO, " --build=NAME [cvd] -b NAME Build a CVD file.\n"); + mprintf(LOGG_INFO, " The following options augment --build.\n"); mprintf(LOGG_INFO, " --max-bad-sigs=NUMBER Maximum number of mismatched signatures\n"); mprintf(LOGG_INFO, " When building a CVD. Default: 3000\n"); mprintf(LOGG_INFO, " --flevel=FLEVEL Specify a custom flevel.\n"); @@ -3673,28 +4039,55 @@ static void help(void) mprintf(LOGG_INFO, " prompt. NOTE: If a CVD is found in the\n"); mprintf(LOGG_INFO, " --datadir its version+1 is used and\n"); mprintf(LOGG_INFO, " this value is ignored.\n"); - mprintf(LOGG_INFO, " --no-cdiff Don't generate .cdiff file\n"); + mprintf(LOGG_INFO, " --no-cdiff Don't generate .cdiff file.\n"); + mprintf(LOGG_INFO, " If not specified, --build will try to\n"); + mprintf(LOGG_INFO, " create a cdiff by comparing with the\n"); + mprintf(LOGG_INFO, " current CVD in --datadir\n"); + mprintf(LOGG_INFO, " If no datafile is found the default\n"); + mprintf(LOGG_INFO, " behaviour is to skip making a CDIFF.\n"); + mprintf(LOGG_INFO, " --hybrid Create a hybrid (standard and bytecode)\n"); + mprintf(LOGG_INFO, " database file\n"); mprintf(LOGG_INFO, " --unsigned Create unsigned database file (.cud)\n"); - mprintf(LOGG_INFO, " --hybrid Create a hybrid (standard and bytecode) database file\n"); - mprintf(LOGG_INFO, " --print-certs=FILE Print Authenticode details from a PE\n"); mprintf(LOGG_INFO, " --server=ADDR ClamAV Signing Service address\n"); - mprintf(LOGG_INFO, " --datadir=DIR Use DIR as default database directory\n"); + mprintf(LOGG_INFO, "\n"); mprintf(LOGG_INFO, " --unpack=FILE -u FILE Unpack a CVD/CLD file\n"); + mprintf(LOGG_INFO, "\n"); mprintf(LOGG_INFO, " --unpack-current=SHORTNAME Unpack local CVD/CLD into cwd\n"); - mprintf(LOGG_INFO, " --list-sigs[=FILE] -l[FILE] List signature names\n"); - mprintf(LOGG_INFO, " --find-sigs=REGEX -fREGEX Find signatures matching REGEX\n"); - mprintf(LOGG_INFO, " --decode-sigs Decode signatures from stdin\n"); - mprintf(LOGG_INFO, " --test-sigs=DATABASE TARGET_FILE Test signatures from DATABASE against \n"); - mprintf(LOGG_INFO, " TARGET_FILE\n"); - mprintf(LOGG_INFO, " --vba=FILE Extract VBA/Word6 macro code\n"); - mprintf(LOGG_INFO, " --vba-hex=FILE Extract Word6 macro code with hex values\n"); + mprintf(LOGG_INFO, "\n"); + mprintf(LOGG_INFO, " Commands for working with CDIFF patch files:\n"); + mprintf(LOGG_INFO, "\n"); mprintf(LOGG_INFO, " --diff=OLD NEW -d OLD NEW Create diff for OLD and NEW CVDs\n"); mprintf(LOGG_INFO, " --compare=OLD NEW -c OLD NEW Show diff between OLD and NEW files in\n"); mprintf(LOGG_INFO, " cdiff format\n"); mprintf(LOGG_INFO, " --run-cdiff=FILE -r FILE Execute update script FILE in cwd\n"); mprintf(LOGG_INFO, " --verify-cdiff=DIFF CVD/CLD Verify DIFF against CVD/CLD\n"); - mprintf(LOGG_INFO, " --tempdir=DIRECTORY Create temporary files in DIRECTORY\n"); - mprintf(LOGG_INFO, " --leave-temps[=yes/no(*)] Do not remove temporary files\n"); + mprintf(LOGG_INFO, "\n"); + mprintf(LOGG_INFO, " Commands creating and verifying .sign detached digital signatures:\n"); + mprintf(LOGG_INFO, "\n"); + mprintf(LOGG_INFO, " --sign FILE Sign a file.\n"); + mprintf(LOGG_INFO, " The resulting .sign file name will\n"); + mprintf(LOGG_INFO, " be in the form: dbname-version.cvd.sign\n"); + mprintf(LOGG_INFO, " or FILE.sign for non-CVD targets.\n"); + mprintf(LOGG_INFO, " It will be created next to the target file.\n"); + mprintf(LOGG_INFO, " If a .sign file already exists, then the\n"); + mprintf(LOGG_INFO, " new signature will be appended to file.\n"); + mprintf(LOGG_INFO, " --key /path/to/private.key Specify a signing key.\n"); + mprintf(LOGG_INFO, " --cert /path/to/private.key Specify a signing cert.\n"); + mprintf(LOGG_INFO, " May be used more than once to add\n"); + mprintf(LOGG_INFO, " intermediate and root certificates.\n"); + mprintf(LOGG_INFO, " --append Use to add a signature line to an existing .sign file.\n"); + mprintf(LOGG_INFO, " Otherwise an existing .sign file will be overwritten.\n"); + mprintf(LOGG_INFO, "\n"); + mprintf(LOGG_INFO, " --verify FILE Find and verify a detached digital\n"); + mprintf(LOGG_INFO, " signature for the given file.\n"); + mprintf(LOGG_INFO, " The digital signature file name must\n"); + mprintf(LOGG_INFO, " be in the form: dbname-version.cvd.sign\n"); + mprintf(LOGG_INFO, " or FILE.sign for non-CVD targets.\n"); + mprintf(LOGG_INFO, " It must be found next to the target file.\n"); + mprintf(LOGG_INFO, " --cvdcertsdir DIRECTORY Specify a directory containing the root\n"); + mprintf(LOGG_INFO, " CA cert needed to verify the signature.\n"); + mprintf(LOGG_INFO, " If not provided, then sigtool will look in:\n"); + mprintf(LOGG_INFO, " " CERTSDIR "\n"); mprintf(LOGG_INFO, "\n"); return; @@ -3770,6 +4163,10 @@ int main(int argc, char **argv) ret = utf16decode(opts); else if (optget(opts, "build")->enabled) ret = build(opts); + else if (optget(opts, "sign")->enabled) + ret = sign(opts); + else if (optget(opts, "verify")->enabled) + ret = verify(opts); else if (optget(opts, "unpack")->enabled) ret = unpack(opts); else if (optget(opts, "unpack-current")->enabled) diff --git a/unit_tests/CMakeLists.txt b/unit_tests/CMakeLists.txt index e750e03932..dc125229bb 100644 --- a/unit_tests/CMakeLists.txt +++ b/unit_tests/CMakeLists.txt @@ -264,6 +264,7 @@ set(ENVIRONMENT CK_DEFAULT_TIMEOUT=300 LD_LIBRARY_PATH=${LD_LIBRARY_PATH} DYLD_LIBRARY_PATH=${LD_LIBRARY_PATH} + CVD_CERTS_DIR=${CMAKE_SOURCE_DIR}/unit_tests/input/signing/public PATH=${NEW_PATH} LIBSSL=${LIBSSL} LIBCRYPTO=${LIBCRYPTO} diff --git a/unit_tests/check_clamav.c b/unit_tests/check_clamav.c index 1d55f1763c..c4c4fa92f4 100644 --- a/unit_tests/check_clamav.c +++ b/unit_tests/check_clamav.c @@ -416,6 +416,7 @@ START_TEST(test_cl_load) struct cl_engine *engine; unsigned int sigs = 0; const char *testfile; + const char* cvdcertsdir; ret = cl_init(CL_INIT_DEFAULT); ck_assert_msg(ret == CL_SUCCESS, "cl_init failed: %s", cl_strerror(ret)); @@ -423,6 +424,12 @@ START_TEST(test_cl_load) engine = cl_engine_new(); ck_assert_msg(engine != NULL, "cl_engine_new failed"); + cvdcertsdir = getenv("CVD_CERTS_DIR"); + ck_assert_msg(cvdcertsdir != NULL, "CVD_CERTS_DIR not set"); + + ret = cl_engine_set_str(engine, CL_ENGINE_CVDCERTSDIR, cvdcertsdir); + ck_assert_msg(ret == CL_SUCCESS, "cl_engine_set_str failed: %s", cl_strerror(ret)); + /* load test cvd */ testfile = SRCDIR PATHSEP "input" PATHSEP "freshclam_testfiles" PATHSEP "test-5.cvd"; ret = cl_load(testfile, engine, &sigs, CL_DB_STDOPT); @@ -442,26 +449,30 @@ START_TEST(test_cl_cvdverify) FILE *orig_fs; FILE *new_fs; char cvd_bytes[5000]; + const char* cvdcertsdir; + + cvdcertsdir = getenv("CVD_CERTS_DIR"); + ck_assert_msg(cvdcertsdir != NULL, "CVD_CERTS_DIR not set"); // Should be able to verify this cvd testfile = SRCDIR "/input/freshclam_testfiles/test-1.cvd"; - ret = cl_cvdverify(testfile); - ck_assert_msg(CL_SUCCESS == ret, "cl_cvdverify failed for: %s -- %s", testfile, cl_strerror(ret)); + ret = cl_cvdverify_ex(testfile, cvdcertsdir); + ck_assert_msg(CL_SUCCESS == ret, "cl_cvdverify_ex failed for: %s -- %s", testfile, cl_strerror(ret)); // Can't verify a cvd that doesn't exist testfile = SRCDIR "/input/freshclam_testfiles/test-na.cvd"; - ret = cl_cvdverify(testfile); - ck_assert_msg(CL_EOPEN == ret, "cl_cvdverify should have failed for: %s -- %s", testfile, cl_strerror(ret)); + ret = cl_cvdverify_ex(testfile, cvdcertsdir); + ck_assert_msg(CL_ECVD == ret, "cl_cvdverify_ex should have failed for: %s -- %s", testfile, cl_strerror(ret)); - // A cdiff is not a cvd. Cannot verify with cl_cvdverify! + // A cdiff is not a cvd. Cannot verify with cl_cvdverify_ex! testfile = SRCDIR "/input/freshclam_testfiles/test-2.cdiff"; - ret = cl_cvdverify(testfile); - ck_assert_msg(CL_ECVD == ret, "cl_cvdverify should have failed for: %s -- %s", testfile, cl_strerror(ret)); + ret = cl_cvdverify_ex(testfile, cvdcertsdir); + ck_assert_msg(CL_ECVD == ret, "cl_cvdverify_ex should have failed for: %s -- %s", testfile, cl_strerror(ret)); // Can't verify an hdb file testfile = SRCDIR "/input/clamav.hdb"; - ret = cl_cvdverify(testfile); - ck_assert_msg(CL_ECVD == ret, "cl_cvdverify should have failed for: %s -- %s", testfile, cl_strerror(ret)); + ret = cl_cvdverify_ex(testfile, cvdcertsdir); + ck_assert_msg(CL_ECVD == ret, "cl_cvdverify_ex should have failed for: %s -- %s", testfile, cl_strerror(ret)); // Modify the cvd to make it invalid sprintf(newtestfile, "%s/modified.cvd", tmpdir); @@ -485,8 +496,8 @@ START_TEST(test_cl_cvdverify) } END_TEST -/* cl_error_t cl_cvdunpack(const char *file, const char *dir, bool dont_verify) */ -START_TEST(test_cl_cvdunpack) +/* cl_error_t cl_cvdunpack_ex(const char *file, const char *dir, bool dont_verify, const char* certs_directory) */ +START_TEST(test_cl_cvdunpack_ex) { cl_error_t ret; char *utf8 = NULL; @@ -494,13 +505,13 @@ START_TEST(test_cl_cvdunpack) const char *testfile; testfile = SRCDIR "/input/freshclam_testfiles/test-1.cvd"; - ret = cl_cvdunpack(testfile, tmpdir, true); - ck_assert_msg(CL_SUCCESS == ret, "cl_cvdunpack: failed for: %s -- %s", testfile, cl_strerror(ret)); + ret = cl_cvdunpack_ex(testfile, tmpdir, true, NULL); + ck_assert_msg(CL_SUCCESS == ret, "cl_cvdunpack_ex: failed for: %s -- %s", testfile, cl_strerror(ret)); // Can't unpack a cdiff testfile = SRCDIR "/input/freshclam_testfiles/test-2.cdiff"; - ret = cl_cvdunpack(testfile, tmpdir, true); - ck_assert_msg(CL_ECVD == ret, "cl_cvdunpack: should have failed for: %s -- %s", testfile, cl_strerror(ret)); + ret = cl_cvdunpack_ex(testfile, tmpdir, true, NULL); + ck_assert_msg(CL_ECVD == ret, "cl_cvdunpack_ex: should have failed for: %s -- %s", testfile, cl_strerror(ret)); } END_TEST diff --git a/unit_tests/clamscan/fp_check_test.py b/unit_tests/clamscan/fp_check_test.py index 8175de8a0a..f0f57b64da 100644 --- a/unit_tests/clamscan/fp_check_test.py +++ b/unit_tests/clamscan/fp_check_test.py @@ -62,20 +62,20 @@ def setUpClass(cls): # Generate hash of the zipped file. # Since we generated the zip in python, we don't know the hash in advance. - hash_md5 = hashlib.md5() + hash_sha256 = hashlib.sha256() with TC.test_file_zipped.open("rb") as f: for chunk in iter(lambda: f.read(4096), b""): - hash_md5.update(chunk) - hash_md5 = hash_md5.hexdigest() + hash_sha256.update(chunk) + hash_sha256 = hash_sha256.hexdigest() TC.test_file_zipped_hash_fp = TC.path_tmp / 'test_file.zip.hash.fp' TC.test_file_zipped_hash_fp.write_text('{hash}:{size}:test_file.zip'.format( - hash=hash_md5, + hash=hash_sha256, size=TC.test_file_zipped.stat().st_size)) TC.test_file_zipped_hash_wild_fp = TC.path_tmp / 'test_file.zip.hash.wild.fp' TC.test_file_zipped_hash_wild_fp.write_text('{hash}:*:test_file.zip.wild:73'.format( - hash=hash_md5)) + hash=hash_sha256)) @classmethod def tearDownClass(cls): diff --git a/unit_tests/freshclam_test.py b/unit_tests/freshclam_test.py index 78de30c028..24ec6a7598 100644 --- a/unit_tests/freshclam_test.py +++ b/unit_tests/freshclam_test.py @@ -720,7 +720,7 @@ def test_freshclam_08_cdiff_update_twice(self): # # Now run the update for the first set up updates. # - command = '{valgrind} {valgrind_args} {freshclam} --no-dns --config-file={freshclam_config} --update-db=test'.format( + command = '{valgrind} {valgrind_args} {freshclam} --no-dns --debug --config-file={freshclam_config} --update-db=test'.format( valgrind=TC.valgrind, valgrind_args=TC.valgrind_args, freshclam=TC.freshclam, freshclam_config=TC.freshclam_config ) output = self.execute_command(command) @@ -751,7 +751,7 @@ def test_freshclam_08_cdiff_update_twice(self): # # Now re-run the update for more updates. # - command = '{valgrind} {valgrind_args} {freshclam} --no-dns --config-file={freshclam_config} --update-db=test'.format( + command = '{valgrind} {valgrind_args} {freshclam} --no-dns --debug --config-file={freshclam_config} --update-db=test'.format( valgrind=TC.valgrind, valgrind_args=TC.valgrind_args, freshclam=TC.freshclam, freshclam_config=TC.freshclam_config ) output = self.execute_command(command) diff --git a/unit_tests/input/CMakeLists.txt b/unit_tests/input/CMakeLists.txt index 8e82d843fe..85faf02f4d 100644 --- a/unit_tests/input/CMakeLists.txt +++ b/unit_tests/input/CMakeLists.txt @@ -6,80 +6,86 @@ # - daily.cvd # - another antivirus (E.g. heuristic detection of broken or packed PE files) # -set(TESTFILES - clam.cab - clam.exe - clam.zip - clam.arj - clam.exe.rtf - clam.exe.szdd - clam.tar.gz - clam.chm - clam.sis - clam-aspack.exe - clam-pespin.exe - clam-upx.exe - clam-fsg.exe - clam-mew.exe - clam-nsis.exe - clam-petite.exe - clam-upack.exe - clam-wwpack.exe - clam.pdf - clam.mail - clam.ppt - clam.tnef - clam.ea05.exe - clam.ea06.exe - clam.d64.zip - clam.exe.mbox.base64 - clam.exe.mbox.uu - clam.exe.binhex - clam.ole.doc - clam.impl.zip - clam.exe.html - clam.bin-be.cpio - clam.bin-le.cpio - clam.newc.cpio - clam.odc.cpio - clam-yc.exe - clam_IScab_int.exe - clam_IScab_ext.exe - clam_ISmsi_int.exe - clam_ISmsi_ext.exe - clam.7z - clam_cache_emax.tgz - clam.iso - clamjol.iso - clam.exe.bz2 - clam.bz2.zip - clam.exe_and_mail.tar.gz - clam.exe.2007.one - clam.exe.2010.one - clam.exe.webapp-export.one +set(ENCRYPTED_TESTFILES + clamav_hdb_scanfiles/clam.cab + clamav_hdb_scanfiles/clam.exe + clamav_hdb_scanfiles/clam.zip + clamav_hdb_scanfiles/clam.arj + clamav_hdb_scanfiles/clam.exe.rtf + clamav_hdb_scanfiles/clam.exe.szdd + clamav_hdb_scanfiles/clam.tar.gz + clamav_hdb_scanfiles/clam.chm + clamav_hdb_scanfiles/clam.sis + clamav_hdb_scanfiles/clam-aspack.exe + clamav_hdb_scanfiles/clam-pespin.exe + clamav_hdb_scanfiles/clam-upx.exe + clamav_hdb_scanfiles/clam-fsg.exe + clamav_hdb_scanfiles/clam-mew.exe + clamav_hdb_scanfiles/clam-nsis.exe + clamav_hdb_scanfiles/clam-petite.exe + clamav_hdb_scanfiles/clam-upack.exe + clamav_hdb_scanfiles/clam-wwpack.exe + clamav_hdb_scanfiles/clam.pdf + clamav_hdb_scanfiles/clam.mail + clamav_hdb_scanfiles/clam.ppt + clamav_hdb_scanfiles/clam.tnef + clamav_hdb_scanfiles/clam.ea05.exe + clamav_hdb_scanfiles/clam.ea06.exe + clamav_hdb_scanfiles/clam.d64.zip + clamav_hdb_scanfiles/clam.exe.mbox.base64 + clamav_hdb_scanfiles/clam.exe.mbox.uu + clamav_hdb_scanfiles/clam.exe.binhex + clamav_hdb_scanfiles/clam.ole.doc + clamav_hdb_scanfiles/clam.impl.zip + clamav_hdb_scanfiles/clam.exe.html + clamav_hdb_scanfiles/clam.bin-be.cpio + clamav_hdb_scanfiles/clam.bin-le.cpio + clamav_hdb_scanfiles/clam.newc.cpio + clamav_hdb_scanfiles/clam.odc.cpio + clamav_hdb_scanfiles/clam-yc.exe + clamav_hdb_scanfiles/clam_IScab_int.exe + clamav_hdb_scanfiles/clam_IScab_ext.exe + clamav_hdb_scanfiles/clam_ISmsi_int.exe + clamav_hdb_scanfiles/clam_ISmsi_ext.exe + clamav_hdb_scanfiles/clam.7z + clamav_hdb_scanfiles/clam_cache_emax.tgz + clamav_hdb_scanfiles/clam.iso + clamav_hdb_scanfiles/clamjol.iso + clamav_hdb_scanfiles/clam.exe.bz2 + clamav_hdb_scanfiles/clam.bz2.zip + clamav_hdb_scanfiles/clam.exe_and_mail.tar.gz + clamav_hdb_scanfiles/clam.exe.2007.one + clamav_hdb_scanfiles/clam.exe.2010.one + clamav_hdb_scanfiles/clam.exe.webapp-export.one + signing/private/test-signing.key ) if(ENABLE_UNRAR) - set(TESTFILES ${TESTFILES} - clam-v2.rar clam-v3.rar + set(ENCRYPTED_TESTFILES ${ENCRYPTED_TESTFILES} + clamav_hdb_scanfiles/clam-v2.rar + clamav_hdb_scanfiles/clam-v3.rar ) endif() -add_custom_target(tgt_clamav_hdb_scanfiles ALL - COMMAND ${CMAKE_COMMAND} -E make_directory ${CMAKE_CURRENT_BINARY_DIR}/clamav_hdb_scanfiles) +add_custom_target(tgt_build_unit_tests_directories ALL + COMMAND ${CMAKE_COMMAND} -E make_directory ${CMAKE_CURRENT_BINARY_DIR}/clamav_hdb_scanfiles + COMMAND ${CMAKE_COMMAND} -E make_directory ${CMAKE_CURRENT_BINARY_DIR}/signing/private +) # Decrypt test file function(decrypt_testfile test_file) - add_custom_command(OUTPUT clamav_hdb_scanfiles/${test_file} + add_custom_command(OUTPUT ${test_file} COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/xor_testfile.py - --in_file ${CMAKE_CURRENT_SOURCE_DIR}/clamav_hdb_scanfiles/${test_file}.xor - --out_file ${CMAKE_CURRENT_BINARY_DIR}/clamav_hdb_scanfiles/${test_file} + --in_file ${CMAKE_CURRENT_SOURCE_DIR}/${test_file}.xor + --out_file ${CMAKE_CURRENT_BINARY_DIR}/${test_file} COMMENT "Decrypting test file ${test_file}...") - add_custom_target(tgt_${test_file} ALL DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/clamav_hdb_scanfiles/${test_file}) - ADD_DEPENDENCIES(tgt_${test_file} tgt_clamav_hdb_scanfiles) + # Replace / with _ in test_file to make it a valid target name + string(REPLACE "/" "_" test_file_tgt ${test_file}) + add_custom_target(tgt_${test_file_tgt} ALL DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/${test_file}) + ADD_DEPENDENCIES(tgt_${test_file_tgt} tgt_build_unit_tests_directories) endfunction() -foreach(TESTFILE ${TESTFILES}) +foreach(TESTFILE ${ENCRYPTED_TESTFILES}) decrypt_testfile(${TESTFILE}) endforeach() diff --git a/unit_tests/input/freshclam_testfiles/test-1.cvd.sign b/unit_tests/input/freshclam_testfiles/test-1.cvd.sign new file mode 100644 index 0000000000..fdb00c537e --- /dev/null +++ b/unit_tests/input/freshclam_testfiles/test-1.cvd.sign @@ -0,0 +1,2 @@ +#clamsign-1.0 +220::pkcs7-pem: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 diff --git a/unit_tests/input/freshclam_testfiles/test-2.cdiff.sign b/unit_tests/input/freshclam_testfiles/test-2.cdiff.sign new file mode 100644 index 0000000000..a9477fa0ff --- /dev/null +++ b/unit_tests/input/freshclam_testfiles/test-2.cdiff.sign @@ -0,0 +1,2 @@ +#clamsign-1.0 +220::pkcs7-pem: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 diff --git a/unit_tests/input/freshclam_testfiles/test-2.cvd.sign b/unit_tests/input/freshclam_testfiles/test-2.cvd.sign new file mode 100644 index 0000000000..61316a9dc6 --- /dev/null +++ b/unit_tests/input/freshclam_testfiles/test-2.cvd.sign @@ -0,0 +1,2 @@ +#clamsign-1.0 +220::pkcs7-pem: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 diff --git a/unit_tests/input/freshclam_testfiles/test-3.cdiff.sign b/unit_tests/input/freshclam_testfiles/test-3.cdiff.sign new file mode 100644 index 0000000000..5f7b4608a4 --- /dev/null +++ b/unit_tests/input/freshclam_testfiles/test-3.cdiff.sign @@ -0,0 +1,2 @@ +#clamsign-1.0 +220::pkcs7-pem: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 diff --git a/unit_tests/input/freshclam_testfiles/test-3.cvd.sign b/unit_tests/input/freshclam_testfiles/test-3.cvd.sign new file mode 100644 index 0000000000..c6e9344108 --- /dev/null +++ b/unit_tests/input/freshclam_testfiles/test-3.cvd.sign @@ -0,0 +1,2 @@ +#clamsign-1.0 +220::pkcs7-pem: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 diff --git a/unit_tests/input/freshclam_testfiles/test-4.cdiff.sign b/unit_tests/input/freshclam_testfiles/test-4.cdiff.sign new file mode 100644 index 0000000000..b7b4b10cf5 --- /dev/null +++ b/unit_tests/input/freshclam_testfiles/test-4.cdiff.sign @@ -0,0 +1,2 @@ +#clamsign-1.0 +220::pkcs7-pem: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 diff --git a/unit_tests/input/freshclam_testfiles/test-4.cvd.sign b/unit_tests/input/freshclam_testfiles/test-4.cvd.sign new file mode 100644 index 0000000000..983a213270 --- /dev/null +++ b/unit_tests/input/freshclam_testfiles/test-4.cvd.sign @@ -0,0 +1,2 @@ +#clamsign-1.0 +220::pkcs7-pem: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 diff --git a/unit_tests/input/freshclam_testfiles/test-5.cdiff.sign b/unit_tests/input/freshclam_testfiles/test-5.cdiff.sign new file mode 100644 index 0000000000..e66e353661 --- /dev/null +++ b/unit_tests/input/freshclam_testfiles/test-5.cdiff.sign @@ -0,0 +1,2 @@ +#clamsign-1.0 +220::pkcs7-pem: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 diff --git a/unit_tests/input/freshclam_testfiles/test-5.cvd.sign b/unit_tests/input/freshclam_testfiles/test-5.cvd.sign new file mode 100644 index 0000000000..c3ecf24602 --- /dev/null +++ b/unit_tests/input/freshclam_testfiles/test-5.cvd.sign @@ -0,0 +1,2 @@ +#clamsign-1.0 +220::pkcs7-pem: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 diff --git a/unit_tests/input/freshclam_testfiles/test-6.cdiff.sign b/unit_tests/input/freshclam_testfiles/test-6.cdiff.sign new file mode 100644 index 0000000000..ca76ed8103 --- /dev/null +++ b/unit_tests/input/freshclam_testfiles/test-6.cdiff.sign @@ -0,0 +1,2 @@ +#clamsign-1.0 +220::pkcs7-pem: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 diff --git a/unit_tests/input/freshclam_testfiles/test-6.cvd.sign b/unit_tests/input/freshclam_testfiles/test-6.cvd.sign new file mode 100644 index 0000000000..26f502bd89 --- /dev/null +++ b/unit_tests/input/freshclam_testfiles/test-6.cvd.sign @@ -0,0 +1,2 @@ +#clamsign-1.0 +220::pkcs7-pem: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 diff --git a/unit_tests/input/signing/private/test-signing.crt b/unit_tests/input/signing/private/test-signing.crt new file mode 100644 index 0000000000..64d15b358e --- /dev/null +++ b/unit_tests/input/signing/private/test-signing.crt @@ -0,0 +1,244 @@ +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 0 (0x0) + Signature Algorithm: sha256WithRSAEncryption + Issuer: C=US, ST=MD, O=Cisco, OU=Talos, CN=ClamAV TEST Intermediate Signing CA + Validity + Not Before: Dec 18 18:57:06 2024 GMT + Not After : Mar 18 18:57:06 2025 GMT + Subject: C=US, ST=MD, O=Cisco, OU=Talos, CN=ClamAV TEST CVD Signing Cert + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + RSA Public-Key: (4096 bit) + Modulus: + 00:bf:ed:94:a8:df:9f:12:fa:19:d6:d7:2a:fe:52: + 01:be:64:05:ee:d5:06:12:35:70:64:f5:54:58:55: + 6a:01:56:1e:a4:44:4d:77:1a:4a:46:ba:0a:5c:b9: + d1:b6:c4:b5:36:7c:a1:06:53:48:78:d7:63:55:2a: + ef:96:68:32:c0:9b:d6:ca:00:18:c1:0e:02:ec:81: + 4b:70:19:a7:ca:cb:ce:84:f7:a0:c6:3d:a2:04:ef: + b0:cd:50:60:7a:db:65:c0:68:41:41:90:4f:d9:5f: + 1c:65:a3:61:e7:3e:9a:b2:b1:f0:8e:19:8a:92:1c: + 5d:69:74:51:03:03:ca:45:7d:51:bc:42:0b:1c:41: + a6:bc:0a:0d:b0:c2:1c:ed:7d:f2:fd:a2:df:7c:fa: + 99:fc:d9:3a:aa:47:be:30:5e:01:87:c2:47:7a:e5: + 3f:ff:09:05:d2:04:26:41:31:89:a4:7a:d0:b3:92: + e9:d2:33:1e:93:f6:68:b2:4e:c8:e2:32:f8:1b:45: + 53:c7:6a:cd:1d:69:46:7b:59:7d:e7:0b:41:4f:bb: + 51:0b:7f:8c:98:d3:fb:ef:24:ef:41:e9:51:7c:cc: + 48:33:78:42:de:30:f5:20:c9:f1:6b:eb:33:9e:9d: + 11:9c:6d:57:41:8c:14:f3:cc:86:39:fe:04:9d:fc: + 26:0f:10:e5:cc:cc:14:43:09:c6:b3:96:f7:f3:31: + 5c:fa:05:86:2e:f7:be:12:f3:52:48:ac:00:b3:a0: + 76:ac:cf:0a:ef:d5:94:8f:94:9f:d2:06:ce:a5:6f: + dc:08:c3:7a:db:02:99:78:75:2a:fe:cd:5c:d8:dc: + 60:d8:0f:d0:11:dc:eb:80:73:53:9b:15:c0:ac:66: + 1d:63:c5:a0:67:5d:4f:d8:ad:d3:08:89:7f:b0:89: + 66:cf:be:ed:07:ba:dd:8e:c4:e3:ab:80:87:b7:ea: + db:9c:c3:7e:43:eb:9e:d2:0b:ce:98:4b:3b:d4:7c: + 37:fc:ed:7b:c9:ff:08:87:b6:a6:bd:08:28:2d:6e: + a0:a2:b6:a8:fe:10:ae:27:13:9c:72:7e:43:2c:95: + b4:02:8f:cd:92:ba:57:85:e2:a0:0f:39:cd:da:83: + f0:d0:1b:eb:11:0a:00:95:89:c3:c1:03:42:dd:7a: + 88:2e:38:4b:23:6b:82:04:91:48:47:08:89:61:9b: + 1e:12:51:75:f2:cc:cb:01:62:a8:8b:74:a4:af:17: + e2:91:71:b3:3b:cb:1e:b4:31:69:4c:e0:46:2d:b5: + fd:e2:9a:55:f8:d7:15:15:13:f0:ba:23:61:d2:f1: + d1:27:af:4c:09:da:a1:42:fd:59:66:91:f7:d7:d1: + 8c:6e:8b + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Basic Constraints: critical + CA:FALSE + X509v3 Key Usage: critical + Digital Signature + X509v3 Extended Key Usage: critical + Code Signing, E-mail Protection + X509v3 Subject Key Identifier: + 7D:A7:44:98:FC:84:AB:2B:00:5E:73:69:F9:6B:CB:B5:E3:18:2B:EF + Signature Algorithm: sha256WithRSAEncryption + 0d:43:7b:b8:8d:24:4d:1e:ad:95:9f:79:6c:b4:65:b1:70:ed: + 07:22:40:bb:d2:40:f5:1f:c9:f8:c2:19:51:18:a2:57:6a:a8: + 49:6b:d7:7b:49:22:51:23:ab:6e:99:bd:72:8e:72:64:a6:3d: + 60:92:88:11:cb:d1:2a:52:51:cd:1e:ab:84:c8:23:1d:02:67: + cc:ce:4a:01:d1:f2:e7:93:da:9b:aa:e6:2d:99:1a:d3:5f:c0: + 88:bd:b1:89:d8:fb:40:df:7a:0a:91:f5:59:f7:b2:0d:5c:e9: + 77:27:f1:70:c2:96:6e:8d:ef:0e:91:06:17:c9:85:c8:8d:70: + 1a:b8:c0:6c:96:c2:2f:01:2d:74:0d:97:32:6e:d3:12:46:61: + ef:a4:68:a4:43:52:0a:af:60:11:0e:86:32:4d:6d:84:31:da: + 99:f9:75:cc:ec:ac:45:08:b7:de:11:88:d4:33:14:92:8f:05: + 34:9d:8c:d6:2a:f5:b8:b8:53:c6:1b:ab:90:f2:12:93:d8:90: + a5:03:85:6b:98:c4:07:31:a0:19:88:f7:9a:dc:fc:3d:bb:d0: + 4d:8a:1a:c7:d7:4c:e2:f1:55:c7:2f:08:ae:13:dd:56:ee:68: + 84:c6:35:d7:3c:97:d0:bc:ca:2e:02:49:36:f7:45:19:2e:39: + 76:ef:10:03:70:5b:83:e0:64:36:e3:7e:12:1c:13:4d:6d:fc: + b6:4c:87:5d:12:40:51:0d:59:2b:0d:fd:03:b5:4e:da:06:fc: + 77:d9:24:de:ec:fd:f4:aa:ba:bc:05:5c:d3:69:43:85:d6:21: + 06:0d:e7:5f:c7:ad:39:7b:ec:47:ce:4e:a1:e5:b8:0d:d4:41: + bb:ad:f7:f1:17:c3:8b:11:c7:92:aa:38:f5:f3:71:56:76:db: + f7:a1:f4:ec:cc:fa:2a:90:02:d1:0a:41:2d:a5:37:e9:e1:a0: + f9:7f:e3:51:da:e9:11:62:b1:33:22:41:71:69:99:96:40:56: + 65:b4:6b:e9:76:7f:64:78:c9:b1:71:86:f9:d2:e6:88:40:01: + e5:f9:b0:7d:0a:05:a1:f7:42:09:d5:d4:4e:ff:3c:2b:0d:00: + ce:fc:25:4b:fd:8a:84:ce:c1:d1:82:b4:f9:96:4c:56:c7:01: + 22:a2:92:5d:e2:53:6e:e7:97:5a:58:c6:5d:23:bb:23:b8:56: + 1f:72:ad:97:09:b0:b4:93:2e:00:47:0b:ae:d3:63:a1:62:96: + 7a:2e:2a:95:ff:b8:e6:70:ea:43:12:76:c4:6c:bc:a0:21:e2: + 0c:12:f8:9c:8b:fa:60:55:2a:c2:ce:96:fb:22:bc:2b:4a:fa: + d0:eb:61:08:c2:da:3e:98 +-----BEGIN CERTIFICATE----- +MIIFpTCCA42gAwIBAgIBADANBgkqhkiG9w0BAQsFADBoMQswCQYDVQQGEwJVUzEL +MAkGA1UECAwCTUQxDjAMBgNVBAoMBUNpc2NvMQ4wDAYDVQQLDAVUYWxvczEsMCoG +A1UEAwwjQ2xhbUFWIFRFU1QgSW50ZXJtZWRpYXRlIFNpZ25pbmcgQ0EwHhcNMjQx +MjE4MTg1NzA2WhcNMjUwMzE4MTg1NzA2WjBhMQswCQYDVQQGEwJVUzELMAkGA1UE +CAwCTUQxDjAMBgNVBAoMBUNpc2NvMQ4wDAYDVQQLDAVUYWxvczElMCMGA1UEAwwc +Q2xhbUFWIFRFU1QgQ1ZEIFNpZ25pbmcgQ2VydDCCAiIwDQYJKoZIhvcNAQEBBQAD +ggIPADCCAgoCggIBAL/tlKjfnxL6GdbXKv5SAb5kBe7VBhI1cGT1VFhVagFWHqRE +TXcaSka6Cly50bbEtTZ8oQZTSHjXY1Uq75ZoMsCb1soAGMEOAuyBS3AZp8rLzoT3 +oMY9ogTvsM1QYHrbZcBoQUGQT9lfHGWjYec+mrKx8I4ZipIcXWl0UQMDykV9UbxC +CxxBprwKDbDCHO198v2i33z6mfzZOqpHvjBeAYfCR3rlP/8JBdIEJkExiaR60LOS +6dIzHpP2aLJOyOIy+BtFU8dqzR1pRntZfecLQU+7UQt/jJjT++8k70HpUXzMSDN4 +Qt4w9SDJ8WvrM56dEZxtV0GMFPPMhjn+BJ38Jg8Q5czMFEMJxrOW9/MxXPoFhi73 +vhLzUkisALOgdqzPCu/VlI+Un9IGzqVv3AjDetsCmXh1Kv7NXNjcYNgP0BHc64Bz +U5sVwKxmHWPFoGddT9it0wiJf7CJZs++7Qe63Y7E46uAh7fq25zDfkPrntILzphL +O9R8N/zte8n/CIe2pr0IKC1uoKK2qP4QricTnHJ+QyyVtAKPzZK6V4XioA85zdqD +8NAb6xEKAJWJw8EDQt16iC44SyNrggSRSEcIiWGbHhJRdfLMywFiqIt0pK8X4pFx +szvLHrQxaUzgRi21/eKaVfjXFRUT8LojYdLx0SevTAnaoUL9WWaR99fRjG6LAgMB +AAGjYTBfMAwGA1UdEwEB/wQCMAAwDgYDVR0PAQH/BAQDAgeAMCAGA1UdJQEB/wQW +MBQGCCsGAQUFBwMDBggrBgEFBQcDBDAdBgNVHQ4EFgQUfadEmPyEqysAXnNp+WvL +teMYK+8wDQYJKoZIhvcNAQELBQADggIBAA1De7iNJE0erZWfeWy0ZbFw7QciQLvS +QPUfyfjCGVEYoldqqElr13tJIlEjq26ZvXKOcmSmPWCSiBHL0SpSUc0eq4TIIx0C +Z8zOSgHR8ueT2puq5i2ZGtNfwIi9sYnY+0DfegqR9Vn3sg1c6Xcn8XDClm6N7w6R +BhfJhciNcBq4wGyWwi8BLXQNlzJu0xJGYe+kaKRDUgqvYBEOhjJNbYQx2pn5dczs +rEUIt94RiNQzFJKPBTSdjNYq9bi4U8Ybq5DyEpPYkKUDhWuYxAcxoBmI95rc/D27 +0E2KGsfXTOLxVccvCK4T3VbuaITGNdc8l9C8yi4CSTb3RRkuOXbvEANwW4PgZDbj +fhIcE01t/LZMh10SQFENWSsN/QO1TtoG/HfZJN7s/fSqurwFXNNpQ4XWIQYN51/H +rTl77EfOTqHluA3UQbut9/EXw4sRx5KqOPXzcVZ22/eh9OzM+iqQAtEKQS2lN+nh +oPl/41Ha6RFisTMiQXFpmZZAVmW0a+l2f2R4ybFxhvnS5ohAAeX5sH0KBaH3QgnV +1E7/PCsNAM78JUv9ioTOwdGCtPmWTFbHASKikl3iU27nl1pYxl0juyO4Vh9yrZcJ +sLSTLgBHC67TY6FilnouKpX/uOZw6kMSdsRsvKAh4gwS+JyL+mBVKsLOlvsivCtK ++tDrYQjC2j6Y +-----END CERTIFICATE----- +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 0 (0x0) + Signature Algorithm: sha256WithRSAEncryption + Issuer: C=US, ST=MD, L=Laurel, O=Cisco, OU=Talos, CN=ClamAV TEST Root CA + Validity + Not Before: Dec 18 18:57:05 2024 GMT + Not After : Dec 18 18:57:05 2025 GMT + Subject: C=US, ST=MD, O=Cisco, OU=Talos, CN=ClamAV TEST Intermediate Signing CA + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + RSA Public-Key: (4096 bit) + Modulus: + 00:c8:90:9e:95:83:89:83:27:c9:c6:c3:8f:df:67: + c1:bc:15:9c:5e:05:4d:d0:fa:86:89:5a:b7:6a:d9: + ff:f4:4b:36:7f:b2:43:89:64:a2:67:fc:94:fb:2f: + 42:d3:b9:77:92:92:13:04:cb:2a:89:ee:c4:89:5e: + cd:de:43:08:df:5a:2a:02:f4:fd:cf:ec:24:87:94: + 5a:da:44:fe:1a:32:09:a8:97:50:aa:b8:a0:ea:29: + 39:69:7a:c0:87:4b:82:48:c0:5c:10:8a:74:79:bc: + ad:42:e8:7b:ce:30:92:72:37:1c:4f:bd:c7:b3:32: + 1d:e0:99:2e:d5:f2:71:77:15:4f:c2:41:41:4a:e5: + 48:89:53:53:0e:d2:f2:4d:20:9c:c2:8b:7b:a3:b5: + d8:70:22:24:34:ef:f0:3f:73:aa:04:7c:c4:d7:8b: + 04:75:09:74:8b:6b:a9:7e:d4:aa:9c:16:4a:91:df: + 1c:b2:ef:78:7b:f8:36:63:87:f9:e9:75:46:21:b5: + 2f:44:52:7f:76:9a:71:74:4b:0d:a4:e2:e8:8b:cb: + 65:b4:65:07:e6:b6:73:38:9d:5f:1d:98:f5:31:0b: + bd:52:87:bb:39:ff:95:4a:85:68:9e:c2:70:b1:03: + 1c:b6:3c:b1:c5:cb:83:53:67:bb:ab:e2:c3:e6:bd: + 3f:62:d5:fa:ea:65:2a:5e:88:b4:3a:9b:ac:87:91: + 52:e0:81:3b:e4:44:0c:83:01:08:76:ae:f2:f9:0e: + 3b:43:15:fe:57:20:75:a5:bc:99:d3:d8:96:af:f6: + 83:a1:cd:79:1c:4a:72:c3:03:20:71:f5:9c:c4:bc: + c4:0f:44:7b:04:fb:f0:be:21:06:a2:3d:0f:9d:d1: + 22:c9:c2:9f:fc:40:1c:d6:72:99:10:d8:cc:0a:31: + 20:77:33:2e:44:d8:f9:b5:be:c1:f1:54:93:8a:06: + 6c:68:94:e2:7c:7e:d8:07:39:ac:e7:cc:f8:41:b8: + 78:37:a1:b5:29:3a:33:67:27:79:71:07:5e:f2:45: + b9:b8:44:4a:b9:ff:3d:9d:b1:43:27:ba:dc:d2:95: + 4d:dd:c4:28:a9:fd:c4:7f:84:72:98:19:22:8d:15: + 03:d8:67:44:db:cd:a7:44:38:1b:83:ba:96:16:f5: + e1:24:d4:de:8f:e0:2a:d4:e4:06:08:cb:b9:02:5e: + 0f:74:4d:d9:af:9e:26:55:65:00:90:1d:4a:ad:1d: + b0:16:37:a5:d9:45:59:7f:52:a9:ac:38:5c:80:2b: + 63:11:e7:ce:8b:c8:1b:0b:5e:36:a0:b7:80:8e:9e: + c8:39:d1:79:30:08:f1:84:a2:e2:34:cb:9a:8c:1b: + 71:a8:6b + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Basic Constraints: critical + CA:TRUE + X509v3 Key Usage: critical + Certificate Sign, CRL Sign + X509v3 Extended Key Usage: + OCSP Signing, E-mail Protection + Netscape Cert Type: + SSL CA + Signature Algorithm: sha256WithRSAEncryption + 5a:3d:ba:cb:d1:72:13:d9:0d:5f:72:b0:e3:3e:1e:1d:a8:16: + 73:16:31:79:83:40:43:78:67:61:62:43:5a:d4:e0:9b:c7:41: + 0c:17:14:77:5f:e2:e1:78:f5:ec:41:92:0d:bb:a9:1e:0e:e3: + 3e:31:e7:fa:4f:be:c1:2a:f2:2f:c4:0c:08:94:96:2d:ef:69: + e1:30:20:67:6c:f5:b8:90:06:84:94:43:9a:ff:72:b3:4b:84: + 0d:e6:6b:a7:03:75:04:8a:b7:07:32:6b:0e:27:1b:08:b9:d5: + 0f:89:62:dd:b5:a3:d2:4d:a5:66:8e:7b:82:8d:57:b7:35:6e: + 12:ad:11:c5:37:18:44:dd:cc:e4:73:71:c3:08:0e:9c:f8:b9: + 59:8a:35:27:ef:28:d0:85:46:95:42:85:82:6c:5c:19:bb:78: + 9f:27:4e:9a:9d:19:62:79:1d:f4:97:30:49:81:cc:d3:03:38: + b6:bc:6b:ea:83:03:19:31:32:66:71:b6:21:87:3c:98:c4:3e: + b3:4b:04:ea:bf:4e:87:e7:77:9c:2c:de:d9:f8:17:1c:13:af: + d1:76:4f:92:4e:25:fd:55:57:3b:21:3f:ee:c4:8b:96:99:10: + 7a:66:cb:fc:0b:7b:ec:0e:fe:37:4a:6d:5c:89:68:fd:d3:86: + a7:68:0c:f6:03:2f:8b:10:52:a3:69:2d:21:0b:38:09:28:87: + 70:0c:31:29:81:f7:82:53:2a:9f:ae:38:16:db:53:0c:df:6a: + 0e:b8:c8:71:a4:e9:08:29:6a:c8:eb:74:bf:43:7e:68:9c:96: + 4b:d6:d0:14:80:6a:a2:67:48:53:43:55:a1:57:74:22:f9:b1: + 65:e0:35:5a:8d:f9:16:5e:b3:c4:c0:78:22:0f:55:e1:e9:a0: + 3b:85:82:4b:10:44:18:05:cf:44:5b:12:b5:ac:3c:0b:84:dc: + 82:a6:9e:58:7d:c5:6d:40:e5:26:40:07:42:c9:0f:55:ae:c4: + d5:dc:66:da:41:72:3b:5a:cd:c4:99:7d:b6:d8:95:7c:5a:18: + 99:cb:bb:10:d7:6f:ea:1e:9d:9e:c1:17:b2:51:b1:23:8a:c5: + de:bf:16:69:8b:f7:9c:a6:b7:a9:1f:0e:46:f4:f7:bf:b5:05: + f1:3d:0d:06:82:ba:d5:bf:41:19:8d:13:3b:29:37:19:cf:66: + aa:df:37:31:a6:f8:5f:f1:e7:48:02:ca:10:9f:08:88:ee:df: + 59:7e:84:6b:e7:2f:82:fa:cd:81:df:04:03:7a:43:b9:98:26: + 01:5a:29:a2:28:38:2c:24:53:13:48:b4:87:d3:10:2d:ef:a1: + 83:eb:ec:88:c9:e7:86:c5 +-----BEGIN CERTIFICATE----- +MIIFoTCCA4mgAwIBAgIBADANBgkqhkiG9w0BAQsFADBpMQswCQYDVQQGEwJVUzEL +MAkGA1UECAwCTUQxDzANBgNVBAcMBkxhdXJlbDEOMAwGA1UECgwFQ2lzY28xDjAM +BgNVBAsMBVRhbG9zMRwwGgYDVQQDDBNDbGFtQVYgVEVTVCBSb290IENBMB4XDTI0 +MTIxODE4NTcwNVoXDTI1MTIxODE4NTcwNVowaDELMAkGA1UEBhMCVVMxCzAJBgNV +BAgMAk1EMQ4wDAYDVQQKDAVDaXNjbzEOMAwGA1UECwwFVGFsb3MxLDAqBgNVBAMM +I0NsYW1BViBURVNUIEludGVybWVkaWF0ZSBTaWduaW5nIENBMIICIjANBgkqhkiG +9w0BAQEFAAOCAg8AMIICCgKCAgEAyJCelYOJgyfJxsOP32fBvBWcXgVN0PqGiVq3 +atn/9Es2f7JDiWSiZ/yU+y9C07l3kpITBMsqie7EiV7N3kMI31oqAvT9z+wkh5Ra +2kT+GjIJqJdQqrig6ik5aXrAh0uCSMBcEIp0ebytQuh7zjCScjccT73HszId4Jku +1fJxdxVPwkFBSuVIiVNTDtLyTSCcwot7o7XYcCIkNO/wP3OqBHzE14sEdQl0i2up +ftSqnBZKkd8csu94e/g2Y4f56XVGIbUvRFJ/dppxdEsNpOLoi8tltGUH5rZzOJ1f +HZj1MQu9Uoe7Of+VSoVonsJwsQMctjyxxcuDU2e7q+LD5r0/YtX66mUqXoi0Opus +h5FS4IE75EQMgwEIdq7y+Q47QxX+VyB1pbyZ09iWr/aDoc15HEpywwMgcfWcxLzE +D0R7BPvwviEGoj0PndEiycKf/EAc1nKZENjMCjEgdzMuRNj5tb7B8VSTigZsaJTi +fH7YBzms58z4Qbh4N6G1KTozZyd5cQde8kW5uERKuf89nbFDJ7rc0pVN3cQoqf3E +f4RymBkijRUD2GdE282nRDgbg7qWFvXhJNTej+Aq1OQGCMu5Al4PdE3Zr54mVWUA +kB1KrR2wFjel2UVZf1KprDhcgCtjEefOi8gbC142oLeAjp7IOdF5MAjxhKLiNMua +jBtxqGsCAwEAAaNVMFMwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYw +HQYDVR0lBBYwFAYIKwYBBQUHAwkGCCsGAQUFBwMEMBEGCWCGSAGG+EIBAQQEAwIC +BDANBgkqhkiG9w0BAQsFAAOCAgEAWj26y9FyE9kNX3Kw4z4eHagWcxYxeYNAQ3hn +YWJDWtTgm8dBDBcUd1/i4Xj17EGSDbupHg7jPjHn+k++wSryL8QMCJSWLe9p4TAg +Z2z1uJAGhJRDmv9ys0uEDeZrpwN1BIq3BzJrDicbCLnVD4li3bWj0k2lZo57go1X +tzVuEq0RxTcYRN3M5HNxwwgOnPi5WYo1J+8o0IVGlUKFgmxcGbt4nydOmp0ZYnkd +9JcwSYHM0wM4trxr6oMDGTEyZnG2IYc8mMQ+s0sE6r9Oh+d3nCze2fgXHBOv0XZP +kk4l/VVXOyE/7sSLlpkQembL/At77A7+N0ptXIlo/dOGp2gM9gMvixBSo2ktIQs4 +CSiHcAwxKYH3glMqn644FttTDN9qDrjIcaTpCClqyOt0v0N+aJyWS9bQFIBqomdI +U0NVoVd0IvmxZeA1Wo35Fl6zxMB4Ig9V4emgO4WCSxBEGAXPRFsStaw8C4Tcgqae +WH3FbUDlJkAHQskPVa7E1dxm2kFyO1rNxJl9ttiVfFoYmcu7ENdv6h6dnsEXslGx +I4rF3r8WaYv3nKa3qR8ORvT3v7UF8T0NBoK61b9BGY0TOyk3Gc9mqt83Mab4X/Hn +SALKEJ8IiO7fWX6Ea+cvgvrNgd8EA3pDuZgmAVopoig4LCRTE0i0h9MQLe+hg+vs +iMnnhsU= +-----END CERTIFICATE----- diff --git a/unit_tests/input/signing/private/test-signing.key.xor b/unit_tests/input/signing/private/test-signing.key.xor new file mode 100644 index 0000000000..7eb168e94b Binary files /dev/null and b/unit_tests/input/signing/private/test-signing.key.xor differ diff --git a/unit_tests/input/signing/public/test-clamav.crt b/unit_tests/input/signing/public/test-clamav.crt new file mode 100644 index 0000000000..e9a2618904 --- /dev/null +++ b/unit_tests/input/signing/public/test-clamav.crt @@ -0,0 +1,31 @@ +-----BEGIN CERTIFICATE----- +MIIFWTCCA0ECFBdoppBJ3hR8wXBcrw3YrohNlfGMMA0GCSqGSIb3DQEBCwUAMGkx +CzAJBgNVBAYTAlVTMQswCQYDVQQIDAJNRDEPMA0GA1UEBwwGTGF1cmVsMQ4wDAYD +VQQKDAVDaXNjbzEOMAwGA1UECwwFVGFsb3MxHDAaBgNVBAMME0NsYW1BViBURVNU +IFJvb3QgQ0EwHhcNMjQxMjE4MTg1NzA1WhcNMjYxMjE4MTg1NzA1WjBpMQswCQYD +VQQGEwJVUzELMAkGA1UECAwCTUQxDzANBgNVBAcMBkxhdXJlbDEOMAwGA1UECgwF +Q2lzY28xDjAMBgNVBAsMBVRhbG9zMRwwGgYDVQQDDBNDbGFtQVYgVEVTVCBSb290 +IENBMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA3q3OM3j6aZGLHsiz +Ug3ARf9X5UCd0mSrSnPMNs+jUaQVwSkohAihv3edGUIa3vAgPG39ZAvgI/DHoMwr +wq2vOUWd1DQYcl9Ha2rySF1y/xJfmb5VCSYIKRua3S3hEooRDxNqQlyPNl2nw6Wf +Br/kgGWxwTRRFSfXvQqhLVzG14O1PDnRhXspOGrela+grS8vJlWN4mu4SD8BjmDj +k5TAcb7MtSSCp8cX+wikoAO5ZnlesIF4qHLyij46kSz4xw4SXsEdJcU1WgXeh3W6 +6DyVbixfAc14vi7jWzvqubJbmiOHkHGUg2fEQyeYDR1bzPDON5BuMyS16jszYwT/ +qJTNs0aD007SQJDomQuHlJXwAxQAudw95LmCSenILw1HfzXd3BZACKjfbiXHdsub +ZBzpo4yFfAm+5g03moLffCYtMm0LywYF7GJMrCSdHfoJXZI44bt4gVV/K8T0Href +GANSz4mEgevBtNOX47BiIFzCNS4Dsne1h44Id1UsLohOUdf5TrfH9Pbggaars0Bp +zjxtvNjtl81Jd+wmRXYtnLoH6S9UVAfONIhRawaz/zb/R7AdxGBcGjacRqJ2r4Cv +zbAszL4/cArI2d+yX/e90qzf2nfwnsbhGjA5Gkw/FLicp/hOjxWStOU0PmmELTVy +3LXn7lgguznhBNqWYgFlnEDxS7kCAwEAATANBgkqhkiG9w0BAQsFAAOCAgEAaWtQ +66laB4GNkEjqzKwkc+cLbsaLtzZdOaZwgxpTYU4aVxZ6G4qqnLskN2Ad4R4+OymR +mGyHLEvTCHTqpP74lSH0gQNIKihtElll2/2GdwF1HzCeSMpyD/2O0jPevQNw81nt +qcDxPB6MzenDF1sOPe9qNiHl+oiFoqWnd3qLK4+PEOsEM+TN/+IAJDu4DXyeWyQg +z/jihUV3QZqF7w/hwOeoCdrI+2MheoPzWyAjQlhKvFkZvIo3KOXtW/p6s/Y8TNQU +yAZBTBSaSleXSIBhfCeEuCkQ8crnloVNaLMrggHag2lbwSWYGN6R3Exb0Uikj+DP +Uh/QIizohUDUZle3aSb7uuYhcfORHkK2XMVmooz0uXD9XDOw22f/4FQhU1drNMds +FPQLst5/Gs39PSUKK9Fredn+M7L0kJZt1wT2mCso1IerqsBUBph2tr2I8+96eRJl +0GpKwSA1Krff97N/EbzilKKDpS0zMl4UU2YgbOXMNqju0q0uhtw1O829uqhiENtf +fM+jZze4Q9pJLtjSZgNYmDShupSErHZRAttl7CiZOKFrIMeQRDX+kP9bEIBoeILq +Z83ordOBwOQ8lLvZha1QBkpdg5Q5lauwQJKbD2WlUgVwY3JC1tRTBNlBW1chH+5O +YG+NnGVpNPLhC6V+Y36mPTI7ldnF5iSnvXQ72uk= +-----END CERTIFICATE----- diff --git a/unit_tests/sigtool_test.py b/unit_tests/sigtool_test.py index 657c942f91..6224dbf767 100644 --- a/unit_tests/sigtool_test.py +++ b/unit_tests/sigtool_test.py @@ -157,3 +157,64 @@ def test_sigtool_02_rust_logs_messages_work(self): 'LibClamAV Error', ] self.verify_output(output.err, expected=expected_results) + + def test_sigtool_03_sign_and_verify(self): + self.step_name('sigtool test for --sign and --verify') + # Verify that you can sign and verify any file. + + # Create a file to sign. + (TC.path_tmp / 'file_to_sign').write_text('This is a file to sign.') + + self.log.warning('VG: {}'.format(os.getenv("VG"))) + + command = '{valgrind} {valgrind_args} {sigtool} --sign {input} --key {key} --cert {cert}'.format( + valgrind=TC.valgrind, valgrind_args=TC.valgrind_args, sigtool=TC.sigtool, + input=TC.path_tmp / 'file_to_sign', + key=TC.path_build / 'unit_tests' / 'input' / 'signing' / 'private' / 'test-signing.key', + cert=TC.path_source / 'unit_tests' / 'input' / 'signing' / 'private' / 'test-signing.crt' + ) + output = self.execute_command(command) + + assert output.ec == 0 # success + + # Verify the signed file (should pass) + + command = '{valgrind} {valgrind_args} {sigtool} --verify {input} --cvdcertsdir {cvdcertsdir}'.format( + valgrind=TC.valgrind, valgrind_args=TC.valgrind_args, sigtool=TC.sigtool, + input=TC.path_tmp / 'file_to_sign', + cvdcertsdir=TC.path_source / 'unit_tests' / 'input' / 'signing' / 'public' + ) + output = self.execute_command(command) + + assert output.ec == 0 # success + + expected_results = [ + 'Successfully verified file', + "signed by 'ClamAV TEST CVD Signing Cert'", + ] + self.verify_output(output.out, expected=expected_results) + + # Modify the signed file + + (TC.path_tmp / 'file_to_sign').write_text(' Modified.') + + # verify the signed file (should fail now) + + command = '{valgrind} {valgrind_args} {sigtool} --verify {input} --cvdcertsdir {cvdcertsdir}'.format( + valgrind=TC.valgrind, valgrind_args=TC.valgrind_args, sigtool=TC.sigtool, + input=TC.path_tmp / 'file_to_sign', + cvdcertsdir=TC.path_source / 'unit_tests' / 'input' / 'signing' / 'public' + ) + output = self.execute_command(command) + + assert output.ec != 0 # not success + + expected_results = [ + 'Failed to verify file', + ] + unexpected_results = [ + 'Successfully verified file', + "signed by 'ClamAV TEST CVD Signing Cert'", + ] + self.verify_output(output.err, expected=expected_results, ) + self.verify_output(output.out, unexpected=unexpected_results) diff --git a/unit_tests/testcase.py b/unit_tests/testcase.py index 172dd22c59..2613fcdf4e 100644 --- a/unit_tests/testcase.py +++ b/unit_tests/testcase.py @@ -359,8 +359,8 @@ def verify_cmd_result( if stdout_unexpected: self.verify_unexpected_output(stdout_unexpected, stdout) - def _md5(self, filepath): - """Get md5 hash sum of a given file. + def _sha256(self, filepath): + """Get sha256 hash sum of a given file. :Parameters: - `filepath`: path to file. @@ -375,31 +375,31 @@ def _md5(self, filepath): assert isinstance(filepath, str), "Invalid filepath: %s." % (filepath,) assert os.path.exists(filepath), "file does not exist: %s." % (filepath,) - hash_md5 = hashlib.md5() + hash_sha256 = hashlib.sha256() with open(filepath, "rb") as f: for chunk in iter(lambda: f.read(4096), b""): - hash_md5.update(chunk) - return hash_md5.hexdigest() + hash_sha256.update(chunk) + return hash_sha256.hexdigest() - def get_md5(self, files): - """Get md5 hash sum of every given file. + def get_sha256(self, files): + """Get sha256 hash sum of every given file. :Parameters: - `files`: a list or a tuple of files. :Return: - - dictionary like {file: md5sum}. + - dictionary like {file: sha256sum}. :Exceptions: - `AssertionError`: is raised if `files` is empty. """ assert files, "`files` should not be empty." files = files if isinstance(files, (list, tuple)) else [files] - md5_dict = {} + sha256_dict = {} for path in files: if os.path.isfile(path): - md5_dict[path] = self._md5(path) - return md5_dict + sha256_dict[path] = self._sha256(path) + return sha256_dict def _pkill(self, process, options=["-9 -f"], sudo=False): """Wrapper for CLI *nix `pkill` command. diff --git a/win32/conf_examples/clamd.conf.sample b/win32/conf_examples/clamd.conf.sample index 580afe0ea9..0a15b7fa99 100644 --- a/win32/conf_examples/clamd.conf.sample +++ b/win32/conf_examples/clamd.conf.sample @@ -72,6 +72,11 @@ Example # Default: hardcoded (depends on installation options) #DatabaseDirectory "C:\Program Files\ClamAV\database" +# Path to the ClamAV CA certificates directory for verifying CVD signature +# archive digital signatures. +# Default: "certs" +#CVDCertsDirectory "C:\Program Files\ClamAV\certs" + # Only load the official signatures published by the ClamAV project. # Default: no #OfficialDatabaseOnly no diff --git a/win32/conf_examples/freshclam.conf.sample b/win32/conf_examples/freshclam.conf.sample index fa7dd961be..26225c101b 100644 --- a/win32/conf_examples/freshclam.conf.sample +++ b/win32/conf_examples/freshclam.conf.sample @@ -14,6 +14,14 @@ Example # Default: hardcoded (depends on installation options) #DatabaseDirectory "C:\Program Files\ClamAV\database" +# Path to the ClamAV CA certificates directory for verifying CVD signature +# archive digital signatures. +# WARNING: It must match clamd.conf's directive! +# WARNING: It must already exist, be an absolute path, be readable by +# freshclam, clamd, clamscan and sigtool. +# Default: "certs" +#CVDCertsDirectory "C:\Program Files\ClamAV\certs" + # Path to the log file (make sure it has proper permissions) # Default: disabled #UpdateLogFile "C:\Program Files\ClamAV\freshclam.log"