diff --git a/client/defines.h b/client/defines.h index a52f6edd..bf3c40d5 100644 --- a/client/defines.h +++ b/client/defines.h @@ -237,6 +237,7 @@ typedef enum {ERROR_TDNF_NO_GPGKEY_CONF_ENTRY, "ERROR_TDNF_NO_GPGKEY_CONF_ENTRY", "gpgkey entry is missing for this repo. please add gpgkey in repo file or use --nogpgcheck to ignore."}, \ {ERROR_TDNF_URL_INVALID, "ERROR_TDNF_URL_INVALID", "URL is invalid."}, \ {ERROR_TDNF_SIZE_MISMATCH, "ERROR_TDNF_SIZE_MISMATCH", "File size does not match."}, \ + {ERROR_TDNF_CHECKSUM_MISMATCH, "ERROR_TDNF_CHECKSUM_MISMATCH", "File checksum does not match."}, \ {ERROR_TDNF_BASEURL_DOES_NOT_EXISTS, "ERROR_TDNF_BASEURL_DOES_NOT_EXISTS", "Base URL and Metalink URL not found in the repo file"},\ {ERROR_TDNF_CHECKSUM_VALIDATION_FAILED, "ERROR_TDNF_CHECKSUM_VALIDATION_FAILED", "Checksum Validation failed for the repomd.xml downloaded using URL from metalink"},\ {ERROR_TDNF_METALINK_RESOURCE_VALIDATION_FAILED, "ERROR_TDNF_METALINK_RESOURCE_VALIDATION_FAILED", "No Resource present in metalink file for file download"},\ diff --git a/client/includes.h b/client/includes.h index 93fee6e9..38c75aa2 100644 --- a/client/includes.h +++ b/client/includes.h @@ -63,13 +63,4 @@ #include "config.h" -// Enum in order of preference -enum { - TDNF_HASH_MD5 = 0, - TDNF_HASH_SHA1, - TDNF_HASH_SHA256, - TDNF_HASH_SHA512, - TDNF_HASH_SENTINEL -}; - #endif /* __CLIENT_INCLUDES_H__ */ diff --git a/client/packageutils.c b/client/packageutils.c index 07008bf5..22f12ea8 100644 --- a/client/packageutils.c +++ b/client/packageutils.c @@ -1082,6 +1082,7 @@ TDNFPopulatePkgInfos( Id dwPkgId = 0; PTDNF_PKG_INFO pPkgInfos = NULL; PTDNF_PKG_INFO pPkgInfo = NULL; + int nChecksumType = 0; if(!ppPkgInfos) { @@ -1136,6 +1137,34 @@ TDNFPopulatePkgInfos( &pPkgInfo->pszLocation); BAIL_ON_TDNF_ERROR(dwError); + dwError = SolvGetPkgChecksumFromId( + pSack, + dwPkgId, + &nChecksumType, + &pPkgInfo->pbChecksum); + //Ignore no data + if(dwError == ERROR_TDNF_NO_DATA) + { + dwError = 0; + } else if (nChecksumType == REPOKEY_TYPE_SHA512) + { + pPkgInfo->nChecksumType = TDNF_HASH_SHA512; + } else if (nChecksumType == REPOKEY_TYPE_SHA256) + { + pPkgInfo->nChecksumType = TDNF_HASH_SHA256; + } else if (nChecksumType == REPOKEY_TYPE_SHA1) + { + pPkgInfo->nChecksumType = TDNF_HASH_SHA1; + } else if (nChecksumType == REPOKEY_TYPE_MD5) + { + pPkgInfo->nChecksumType = TDNF_HASH_MD5; + } else + { + pPkgInfo->pbChecksum = NULL; + } + + BAIL_ON_TDNF_ERROR(dwError); + dwError = SolvGetPkgInstallSizeFromId( pSack, dwPkgId, diff --git a/client/prototypes.h b/client/prototypes.h index d830b960..50abe98c 100644 --- a/client/prototypes.h +++ b/client/prototypes.h @@ -10,9 +10,6 @@ #define __CLIENT_PROTOTYPES_H__ #include -#include -#include -#include extern uid_t gEuid; diff --git a/client/rpmtrans.c b/client/rpmtrans.c index 46bd3a23..ce2e6829 100644 --- a/client/rpmtrans.c +++ b/client/rpmtrans.c @@ -820,6 +820,8 @@ TDNFTransAddInstallPkg( PTDNF_CACHED_RPM_ENTRY pRpmCache = NULL; const char* pszPackageLocation = NULL; const char* pszPkgName = NULL; + uint8_t digest_from_file[EVP_MAX_MD_SIZE] = {0}; + hash_op *hash = NULL; int nSize; if(!pTS || !pTdnf || !pInfo || !pRepo) @@ -898,6 +900,20 @@ TDNFTransAddInstallPkg( BAIL_ON_TDNF_SYSTEM_ERROR(dwError); } + if(pInfo->pbChecksum != NULL) { + hash = hash_ops + pInfo->nChecksumType; + + dwError = TDNFGetDigestForFile(pszFilePath, hash, digest_from_file); + BAIL_ON_TDNF_ERROR(dwError); + + if (memcmp(digest_from_file, pInfo->pbChecksum, hash->length)) + { + pr_err("rpm file (%s) Checksum FAILED (digest mismatch)\n", pszFilePath); + dwError = ERROR_TDNF_CHECKSUM_MISMATCH; + BAIL_ON_TDNF_ERROR(dwError); + } + } + dwError = TDNFGetFileSize(pszFilePath, &nSize); BAIL_ON_TDNF_ERROR(dwError); diff --git a/common/prototypes.h b/common/prototypes.h index 945b30e6..7ef57396 100644 --- a/common/prototypes.h +++ b/common/prototypes.h @@ -323,4 +323,35 @@ int32_t strtoi(const char *ptr); int isTrue(const char *str); +uint32_t +TDNFGetDigestForFile( + const char *filename, + hash_op *hash, + uint8_t *digest + ); + +uint32_t +TDNFCheckHash( + const char *filename, + unsigned char *digest, + int type + ); + +uint32_t +TDNFCheckHexDigest( + const char *hex_digest, + int digest_length + ); + +uint32_t +TDNFHexToUint( + const char *hex_digest, + unsigned char *uintValue + ); + +uint32_t +TDNFChecksumFromHexDigest( + const char *hex_digest, + unsigned char *ppdigest + ); #endif /* __COMMON_PROTOTYPES_H__ */ diff --git a/common/structs.h b/common/structs.h index 602a1d23..b510b957 100644 --- a/common/structs.h +++ b/common/structs.h @@ -1,5 +1,9 @@ #pragma once +#include +#include +#include + typedef struct _KEYVALUE_ { char *pszKey; @@ -46,3 +50,26 @@ enum { TDNFLOCK_WRITE = 1 << 1, TDNFLOCK_WAIT = 1 << 2, }; + +// Enum in order of preference +enum { + TDNF_HASH_MD5 = 0, + TDNF_HASH_SHA1, + TDNF_HASH_SHA256, + TDNF_HASH_SHA512, + TDNF_HASH_SENTINEL +}; + +typedef struct _hash_op { + char *hash_type; + unsigned int length; +} hash_op; + +typedef struct _hash_type { + char *hash_name; + unsigned int hash_value; +}hash_type; + +extern hash_op hash_ops[TDNF_HASH_SENTINEL]; + +extern hash_type hashType[7]; diff --git a/common/utils.c b/common/utils.c index 4422701c..764741ca 100644 --- a/common/utils.c +++ b/common/utils.c @@ -9,6 +9,25 @@ #include #include "includes.h" +hash_op hash_ops[TDNF_HASH_SENTINEL] = + { + [TDNF_HASH_MD5] = {"md5", MD5_DIGEST_LENGTH}, + [TDNF_HASH_SHA1] = {"sha1", SHA_DIGEST_LENGTH}, + [TDNF_HASH_SHA256] = {"sha256", SHA256_DIGEST_LENGTH}, + [TDNF_HASH_SHA512] = {"sha512", SHA512_DIGEST_LENGTH}, + }; + +hash_type hashType[] = + { + {"md5", TDNF_HASH_MD5}, + {"sha1", TDNF_HASH_SHA1}, + {"sha-1", TDNF_HASH_SHA1}, + {"sha256", TDNF_HASH_SHA256}, + {"sha-256", TDNF_HASH_SHA256}, + {"sha512", TDNF_HASH_SHA512}, + {"sha-512", TDNF_HASH_SHA512} + }; + uint32_t TDNFFileReadAllText( const char *pszFileName, @@ -250,6 +269,7 @@ TDNFFreePackageInfoContents( TDNF_SAFE_FREE_MEMORY(pPkgInfo->pszFormattedSize); TDNF_SAFE_FREE_MEMORY(pPkgInfo->pszRelease); TDNF_SAFE_FREE_MEMORY(pPkgInfo->pszLocation); + TDNF_SAFE_FREE_MEMORY(pPkgInfo->pbChecksum); if(pPkgInfo->pppszDependencies) { @@ -989,3 +1009,260 @@ int isTrue(const char *str) return !strcasecmp(str, "true") || strtoi(str); } + +uint32_t +TDNFGetDigestForFile( + const char *filename, + hash_op *hash, + uint8_t *digest + ) +{ + uint32_t dwError = 0; + int fd = -1; + char buf[BUFSIZ] = {0}; + int length = 0; + EVP_MD_CTX *ctx = NULL; + const EVP_MD *digest_type = NULL; + unsigned int digest_length = 0; + + if (IsNullOrEmptyString(filename) || !hash || !digest) + { + dwError = ERROR_TDNF_INVALID_PARAMETER; + BAIL_ON_TDNF_ERROR(dwError); + } + + fd = open(filename, O_RDONLY); + if (fd < 0) + { + pr_err("ERROR: Checksum validating (%s) FAILED\n", filename); + dwError = errno; + BAIL_ON_TDNF_SYSTEM_ERROR_UNCOND(dwError); + } + + digest_type = EVP_get_digestbyname(hash->hash_type); + + if (!digest_type) + { + pr_err("Unknown message digest %s\n", hash->hash_type); + dwError = ERROR_TDNF_INVALID_PARAMETER; + BAIL_ON_TDNF_ERROR(dwError); + } + + ctx = EVP_MD_CTX_create(); + if (!ctx) + { + pr_err("Context Create Failed\n"); + dwError = ERROR_TDNF_CHECKSUM_VALIDATION_FAILED; + BAIL_ON_TDNF_ERROR(dwError); + } + + dwError = EVP_DigestInit_ex(ctx, digest_type, NULL); + if (!dwError) + { + pr_err("Digest Init Failed\n"); + dwError = ERROR_TDNF_CHECKSUM_VALIDATION_FAILED; + /* MD5 is not approved in FIPS mode. So, overrriding + the dwError to show the right error to the user */ +#if defined(OPENSSL_VERSION_MAJOR) && (OPENSSL_VERSION_MAJOR >= 3) + if (EVP_default_properties_is_fips_enabled(NULL) && !strcasecmp(hash->hash_type, "md5")) +#else + if (FIPS_mode() && !strcasecmp(hash->hash_type, "md5")) +#endif + { + dwError = ERROR_TDNF_FIPS_MODE_FORBIDDEN; + } + BAIL_ON_TDNF_ERROR(dwError); + } + + while ((length = read(fd, buf, BUFSIZ - 1) > 0) + { + dwError = EVP_DigestUpdate(ctx, buf, length); + if (!dwError) + { + pr_err("Digest Update Failed\n"); + dwError = ERROR_TDNF_CHECKSUM_VALIDATION_FAILED; + BAIL_ON_TDNF_ERROR(dwError); + } + memset(buf, 0, BUFSIZ); + } + + if (length == -1) + { + pr_err("Error: Checksum validating (%s) FAILED\n", filename); + dwError = errno; + BAIL_ON_TDNF_SYSTEM_ERROR(dwError); + } + + dwError = EVP_DigestFinal_ex(ctx, digest, &digest_length); + if (!dwError) + { + pr_err("Digest Final Failed\n"); + dwError = ERROR_TDNF_CHECKSUM_VALIDATION_FAILED; + BAIL_ON_TDNF_ERROR(dwError); + } + dwError = 0; + +cleanup: + if (fd >= 0) + { + close(fd); + } + if (ctx) + { + EVP_MD_CTX_destroy(ctx); + } + return dwError; +error: + goto cleanup; +} + +uint32_t +TDNFCheckHash( + const char *filename, + unsigned char *digest, + int type + ) +{ + + uint32_t dwError = 0; + uint8_t digest_from_file[EVP_MAX_MD_SIZE] = {0}; + hash_op *hash = NULL; + + if (IsNullOrEmptyString(filename) || + !digest) + { + dwError = ERROR_TDNF_INVALID_PARAMETER; + BAIL_ON_TDNF_ERROR(dwError); + } + + if (type < TDNF_HASH_MD5 || type >= TDNF_HASH_SENTINEL) + { + dwError = ERROR_TDNF_INVALID_PARAMETER; + BAIL_ON_TDNF_ERROR(dwError); + } + + hash = hash_ops + type; + + dwError = TDNFGetDigestForFile(filename, hash, digest_from_file); + BAIL_ON_TDNF_ERROR(dwError); + + if (memcmp(digest_from_file, digest, hash->length)) + { + dwError = ERROR_TDNF_CHECKSUM_VALIDATION_FAILED; + BAIL_ON_TDNF_ERROR(dwError); + } + +cleanup: + return dwError; +error: + if (!IsNullOrEmptyString(filename)) + { + pr_err("Error: Validating Checksum (%s) FAILED (digest mismatch)\n", filename); + } + goto cleanup; +} + +/* Returns nonzero if hex_digest is properly formatted; that is each + letter is in [0-9A-Za-z] and the length of the string equals to the + result length of digest * 2. */ +uint32_t +TDNFCheckHexDigest( + const char *hex_digest, + int digest_length + ) +{ + int i = 0; + + if(IsNullOrEmptyString(hex_digest) || + (digest_length <= 0)) + { + return 0; + } + + for(i = 0; hex_digest[i]; ++i) + { + if(!isxdigit(hex_digest[i])) + { + return 0; + } + } + + return digest_length * 2 == i; +} + +uint32_t +TDNFHexToUint( + const char *hex_digest, + unsigned char *uintValue + ) +{ + uint32_t dwError = 0; + char buf[3] = {0}; + unsigned long val = 0; + + if(IsNullOrEmptyString(hex_digest) || + !uintValue) + { + dwError = ERROR_TDNF_INVALID_PARAMETER; + BAIL_ON_TDNF_ERROR(dwError); + } + + buf[0] = hex_digest[0]; + buf[1] = hex_digest[1]; + + errno = 0; + val = strtoul(buf, NULL, 16); + if(errno) + { + pr_err("Error: strtoul call failed\n"); + dwError = errno; + BAIL_ON_TDNF_SYSTEM_ERROR(dwError); + } + *uintValue = (unsigned char)(val&0xff); + +cleanup: + return dwError; +error: + goto cleanup; +} + +uint32_t +TDNFChecksumFromHexDigest( + const char *hex_digest, + unsigned char *ppdigest + ) +{ + uint32_t dwError = 0; + unsigned char *pdigest = NULL; + size_t i = 0; + size_t len = 0; + unsigned char uintValue = 0; + + if(IsNullOrEmptyString(hex_digest) || + !ppdigest) + { + dwError = ERROR_TDNF_INVALID_PARAMETER; + BAIL_ON_TDNF_ERROR(dwError); + } + + len = strlen(hex_digest); + + dwError = TDNFAllocateMemory(1, len/2, (void **)&pdigest); + BAIL_ON_TDNF_ERROR(dwError); + + for(i = 0; i < len; i += 2) + { + dwError = TDNFHexToUint(hex_digest + i, &uintValue); + BAIL_ON_TDNF_ERROR(dwError); + + pdigest[i>>1] = uintValue; + } + memcpy( ppdigest, pdigest, len>>1 ); + +cleanup: + TDNF_SAFE_FREE_MEMORY(pdigest); + return dwError; + +error: + goto cleanup; +} diff --git a/include/tdnferror.h b/include/tdnferror.h index 12653d1c..7e6246f7 100644 --- a/include/tdnferror.h +++ b/include/tdnferror.h @@ -152,8 +152,9 @@ extern "C" { #define ERROR_TDNF_RPMTS_OPENDB_FAILED 1526 #define ERROR_TDNF_SIZE_MISMATCH 1527 +#define ERROR_TDNF_CHECKSUM_MISMATCH 1528 -#define ERROR_TDNF_RPMTS_FDDUP_FAILED 1528 +#define ERROR_TDNF_RPMTS_FDDUP_FAILED 1529 /* event context */ #define ERROR_TDNF_EVENT_CTXT_ITEM_NOT_FOUND 1551 diff --git a/include/tdnftypes.h b/include/tdnftypes.h index 9a45b00c..74124771 100644 --- a/include/tdnftypes.h +++ b/include/tdnftypes.h @@ -153,6 +153,7 @@ typedef struct _TDNF_PKG_INFO uint32_t dwEpoch; uint32_t dwInstallSizeBytes; uint32_t dwDownloadSizeBytes; + int nChecksumType; char* pszName; char* pszRepoName; char* pszVersion; @@ -169,6 +170,7 @@ typedef struct _TDNF_PKG_INFO char ***pppszDependencies; char **ppszFileList; char *pszSourcePkg; + unsigned char* pbChecksum; PTDNF_PKG_CHANGELOG_ENTRY pChangeLogEntries; struct _TDNF_PKG_INFO* pNext; }TDNF_PKG_INFO, *PTDNF_PKG_INFO; diff --git a/plugins/metalink/utils.c b/plugins/metalink/utils.c index fd16aa20..6af9800c 100644 --- a/plugins/metalink/utils.c +++ b/plugins/metalink/utils.c @@ -27,35 +27,6 @@ #define ATTR_LOCATION (char*)"location" #define ATTR_PREFERENCE (char*)"preference" -typedef struct _hash_op { - char *hash_type; - unsigned int length; -} hash_op; - -static hash_op hash_ops[TDNF_HASH_SENTINEL] = - { - [TDNF_HASH_MD5] = {"md5", MD5_DIGEST_LENGTH}, - [TDNF_HASH_SHA1] = {"sha1", SHA_DIGEST_LENGTH}, - [TDNF_HASH_SHA256] = {"sha256", SHA256_DIGEST_LENGTH}, - [TDNF_HASH_SHA512] = {"sha512", SHA512_DIGEST_LENGTH}, - }; - -typedef struct _hash_type { - char *hash_name; - unsigned int hash_value; -}hash_type; - -static hash_type hashType[] = - { - {"md5", TDNF_HASH_MD5}, - {"sha1", TDNF_HASH_SHA1}, - {"sha-1", TDNF_HASH_SHA1}, - {"sha256", TDNF_HASH_SHA256}, - {"sha-256", TDNF_HASH_SHA256}, - {"sha512", TDNF_HASH_SHA512}, - {"sha-512", TDNF_HASH_SHA512} - }; - static int hashTypeComparator(const void * p1, const void * p2) { return strcmp(*((const char **)p1), *((const char **)p2)); @@ -117,263 +88,6 @@ TDNFGetResourceType( goto cleanup; } -uint32_t -TDNFGetDigestForFile( - const char *filename, - hash_op *hash, - uint8_t *digest - ) -{ - uint32_t dwError = 0; - int fd = -1; - char buf[BUFSIZ] = {0}; - int length = 0; - EVP_MD_CTX *ctx = NULL; - const EVP_MD *digest_type = NULL; - unsigned int digest_length = 0; - - if (IsNullOrEmptyString(filename) || !hash || !digest) - { - dwError = ERROR_TDNF_INVALID_PARAMETER; - BAIL_ON_TDNF_ERROR(dwError); - } - - fd = open(filename, O_RDONLY); - if (fd < 0) - { - pr_err("Metalink: validating (%s) FAILED\n", filename); - dwError = errno; - BAIL_ON_TDNF_SYSTEM_ERROR_UNCOND(dwError); - } - - digest_type = EVP_get_digestbyname(hash->hash_type); - - if (!digest_type) - { - pr_err("Unknown message digest %s\n", hash->hash_type); - dwError = ERROR_TDNF_INVALID_PARAMETER; - BAIL_ON_TDNF_ERROR(dwError); - } - - ctx = EVP_MD_CTX_create(); - if (!ctx) - { - pr_err("Context Create Failed\n"); - dwError = ERROR_TDNF_CHECKSUM_VALIDATION_FAILED; - BAIL_ON_TDNF_ERROR(dwError); - } - - dwError = EVP_DigestInit_ex(ctx, digest_type, NULL); - if (!dwError) - { - pr_err("Digest Init Failed\n"); - dwError = ERROR_TDNF_CHECKSUM_VALIDATION_FAILED; - /* MD5 is not approved in FIPS mode. So, overrriding - the dwError to show the right error to the user */ -#if defined(OPENSSL_VERSION_MAJOR) && (OPENSSL_VERSION_MAJOR >= 3) - if (EVP_default_properties_is_fips_enabled(NULL) && !strcasecmp(hash->hash_type, "md5")) -#else - if (FIPS_mode() && !strcasecmp(hash->hash_type, "md5")) -#endif - { - dwError = ERROR_TDNF_FIPS_MODE_FORBIDDEN; - } - BAIL_ON_TDNF_ERROR(dwError); - } - - while ((length = read(fd, buf, (sizeof(buf)-1))) > 0) - { - dwError = EVP_DigestUpdate(ctx, buf, length); - if (!dwError) - { - pr_err("Digest Update Failed\n"); - dwError = ERROR_TDNF_CHECKSUM_VALIDATION_FAILED; - BAIL_ON_TDNF_ERROR(dwError); - } - memset(buf, 0, BUFSIZ); - } - - if (length == -1) - { - pr_err("Metalink: validating (%s) FAILED\n", filename); - dwError = errno; - BAIL_ON_TDNF_SYSTEM_ERROR(dwError); - } - - dwError = EVP_DigestFinal_ex(ctx, digest, &digest_length); - if (!dwError) - { - pr_err("Digest Final Failed\n"); - dwError = ERROR_TDNF_CHECKSUM_VALIDATION_FAILED; - BAIL_ON_TDNF_ERROR(dwError); - } - dwError = 0; - -cleanup: - if (fd >= 0) - { - close(fd); - } - if (ctx) - { - EVP_MD_CTX_destroy(ctx); - } - return dwError; -error: - goto cleanup; -} - -uint32_t -TDNFCheckHash( - const char *filename, - unsigned char *digest, - int type - ) -{ - - uint32_t dwError = 0; - uint8_t digest_from_file[EVP_MAX_MD_SIZE] = {0}; - hash_op *hash = NULL; - - if (IsNullOrEmptyString(filename) || - !digest) - { - dwError = ERROR_TDNF_INVALID_PARAMETER; - BAIL_ON_TDNF_ERROR(dwError); - } - - if (type < TDNF_HASH_MD5 || type >= TDNF_HASH_SENTINEL) - { - dwError = ERROR_TDNF_INVALID_PARAMETER; - BAIL_ON_TDNF_ERROR(dwError); - } - - hash = hash_ops + type; - - dwError = TDNFGetDigestForFile(filename, hash, digest_from_file); - BAIL_ON_TDNF_ERROR(dwError); - - if (memcmp(digest_from_file, digest, hash->length)) - { - dwError = ERROR_TDNF_CHECKSUM_VALIDATION_FAILED; - BAIL_ON_TDNF_ERROR(dwError); - } - -cleanup: - return dwError; -error: - if (!IsNullOrEmptyString(filename)) - { - pr_err("Error: Validating metalink (%s) FAILED (digest mismatch)\n", filename); - } - goto cleanup; -} - -/* Returns nonzero if hex_digest is properly formatted; that is each - letter is in [0-9A-Za-z] and the length of the string equals to the - result length of digest * 2. */ -static -uint32_t -TDNFCheckHexDigest( - const char *hex_digest, - int digest_length - ) -{ - int i = 0; - if(IsNullOrEmptyString(hex_digest) || - (digest_length <= 0)) - { - return 0; - } - for(i = 0; hex_digest[i]; ++i) - { - if(!isxdigit(hex_digest[i])) - { - return 0; - } - } - return digest_length * 2 == i; -} - -static -uint32_t -TDNFHexToUint( - const char *hex_digest, - unsigned char *uintValue - ) -{ - uint32_t dwError = 0; - char buf[3] = {0}; - unsigned long val = 0; - - if(IsNullOrEmptyString(hex_digest) || - !uintValue) - { - dwError = ERROR_TDNF_INVALID_PARAMETER; - BAIL_ON_TDNF_ERROR(dwError); - } - - buf[0] = hex_digest[0]; - buf[1] = hex_digest[1]; - - errno = 0; - val = strtoul(buf, NULL, 16); - if(errno) - { - pr_err("Error: strtoul call failed\n"); - dwError = errno; - BAIL_ON_TDNF_SYSTEM_ERROR(dwError); - } - *uintValue = (unsigned char)(val&0xff); - -cleanup: - return dwError; -error: - goto cleanup; -} - -static -uint32_t -TDNFChecksumFromHexDigest( - const char *hex_digest, - unsigned char *ppdigest - ) -{ - uint32_t dwError = 0; - unsigned char *pdigest = NULL; - size_t i = 0; - size_t len = 0; - unsigned char uintValue = 0; - - if(IsNullOrEmptyString(hex_digest) || - !ppdigest) - { - dwError = ERROR_TDNF_INVALID_PARAMETER; - BAIL_ON_TDNF_ERROR(dwError); - } - - len = strlen(hex_digest); - - dwError = TDNFAllocateMemory(1, len/2, (void **)&pdigest); - BAIL_ON_TDNF_ERROR(dwError); - - for(i = 0; i < len; i += 2) - { - dwError = TDNFHexToUint(hex_digest + i, &uintValue); - BAIL_ON_TDNF_ERROR(dwError); - - pdigest[i>>1] = uintValue; - } - memcpy( ppdigest, pdigest, len>>1 ); - -cleanup: - TDNF_SAFE_FREE_MEMORY(pdigest); - return dwError; - -error: - goto cleanup; -} - uint32_t TDNFCheckRepoMDFileHashFromMetalink( const char *pszFile, diff --git a/pytests/repo/setup-repo.sh b/pytests/repo/setup-repo.sh index 1a1bc90b..a19bd20b 100755 --- a/pytests/repo/setup-repo.sh +++ b/pytests/repo/setup-repo.sh @@ -46,6 +46,7 @@ export GNUPGHOME=${TEST_REPO_DIR}/gnupg BUILD_PATH=${TEST_REPO_DIR}/build PUBLISH_PATH=${TEST_REPO_DIR}/photon-test PUBLISH_SRC_PATH=${TEST_REPO_DIR}/photon-test-src +PUBLISH_SHA512_PATH=${TEST_REPO_DIR}/photon-test-sha512 ARCH=$(uname -m) @@ -57,6 +58,7 @@ mkdir -p -m 755 ${BUILD_PATH}/BUILD \ ${TEST_REPO_DIR}/yum.repos.d \ ${PUBLISH_PATH} \ ${PUBLISH_SRC_PATH} \ + ${PUBLISH_SHA512_PATH} \ ${GNUPGHOME} #gpgkey data for unattended key generation @@ -99,6 +101,7 @@ rpmsign --addsign ${BUILD_PATH}/RPMS/*/*.rpm check_err "Failed to sign built packages." cp -r ${BUILD_PATH}/RPMS ${PUBLISH_PATH} cp -r ${BUILD_PATH}/SRPMS ${PUBLISH_SRC_PATH} +cp -r ${BUILD_PATH}/RPMS ${PUBLISH_SHA512_PATH} # save key to later be imported: mkdir -p ${PUBLISH_PATH}/keys @@ -106,6 +109,7 @@ gpg --armor --export tdnftest@tdnf.test > ${PUBLISH_PATH}/keys/pubkey.asc createrepo ${PUBLISH_PATH} createrepo ${PUBLISH_SRC_PATH} +createrepo -s sha512 ${PUBLISH_SHA512_PATH} modifyrepo ${REPO_SRC_DIR}/updateinfo-1.xml ${PUBLISH_PATH}/repodata check_err "Failed to modify repo with updateinfo-1.xml." @@ -128,6 +132,16 @@ gpgcheck=0 enabled=1 EOF +cat << EOF > ${TEST_REPO_DIR}/yum.repos.d/photon-test-sha512.repo +[photon-test-sha512] +name=basic +baseurl=http://localhost:8080/photon-test-sha512 +#metalink=http://localhost:8080/photon-test-sha512/metalink +gpgkey=file:///etc/pki/rpm-gpg/VMWARE-RPM-GPG-KEY +gpgcheck=0 +enabled=0 +EOF + cat << EOF > ${TEST_REPO_DIR}/yum.repos.d/photon-test-src.repo [photon-test-src] name=basic diff --git a/pytests/tests/test_checksum.py b/pytests/tests/test_checksum.py new file mode 100644 index 00000000..f65cafc5 --- /dev/null +++ b/pytests/tests/test_checksum.py @@ -0,0 +1,88 @@ +# Copyright (C) 2019 - 2022 VMware, Inc. All Rights Reserved. +# +# Licensed under the GNU General Public License v2 (the "License"); +# you may not use this file except in compliance with the License. The terms +# of the License are located in the COPYING file of this distribution. +# + +import os +import shutil +import filecmp +import glob +import pytest + +WORKDIR = '/root/repofrompath/workdir' + + +@pytest.fixture(scope='module', autouse=True) +def setup_test(utils): + yield + teardown_test(utils) + + +def teardown_test(utils): + utils.run(['tdnf', 'erase', '-y', 'tdnf-test-one']) + if os.path.isdir(WORKDIR): + shutil.rmtree(WORKDIR) + + +def enable_and_create_repo(utils): + workdir = WORKDIR + utils.makedirs(workdir) + reponame = 'photon-test-sha512' + + ret = utils.run(['tdnf', '-v', '--disablerepo=*', '--enablerepo=photon-test-sha512', 'makecache']) + assert ret['retval'] == 0 + + ret = utils.run(['tdnf', '--repo={}'.format(reponame), + '--download-metadata', + 'reposync'], + cwd=workdir) + assert ret['retval'] == 0 + synced_dir = os.path.join(workdir, reponame) + assert os.path.isdir(synced_dir) + assert os.path.isdir(os.path.join(synced_dir, 'repodata')) + assert os.path.isfile(os.path.join(synced_dir, 'repodata', 'repomd.xml')) + + +def copy_rpm(workdir, orig_pkg, copy_pkg): + + orig_path = glob.glob(f"{workdir}/{orig_pkg}*.rpm") + copy_path = glob.glob(f"{workdir}/{copy_pkg}*.rpm") + + if not filecmp.cmp(copy_path[0], orig_path[0]): + shutil.copy2(copy_path[0], orig_path[0]) + + +# install package with SHA512 +def test_install_package_with_sha512_checksum(utils): + ret = utils.run(['tdnf', 'erase', '-y', 'tdnf-test-one']) + assert ret['retval'] == 0 + + ret = utils.run(['tdnf', '-y', '--nogpgcheck', 'install', '-y', 'tdnf-test-one', '--enablerepo=photon-test-sha512']) + assert ret['retval'] == 0 + + +# install package with incorrect SHA512 +def test_install_package_with_incorrect_sha512_checksum(utils): + workdir = WORKDIR + reponame = 'photon-test-sha512' + synced_dir = os.path.join(workdir, reponame) + rpm_dir = os.path.join(synced_dir, 'RPMS') + rpm_dir = os.path.join(rpm_dir, 'x86_64') + + enable_and_create_repo(utils) + copy_rpm(rpm_dir, 'tdnf-test-one', 'tdnf-test-two') + + ret = utils.run(['tdnf', + '--repofrompath=synced-repo,{}'.format(synced_dir), + '--repo=synced-repo', + 'makecache'], + cwd=workdir) + assert ret['retval'] == 0 + + ret = utils.run(['tdnf', 'erase', '-y', 'tdnf-test-one'], cwd=workdir) + assert ret['retval'] == 0 + + ret = utils.run(['tdnf', '-y', '--nogpgcheck', '--repofrompath=synced-repo,{}'.format(synced_dir), '--repo=synced-repo', 'install', '-y', 'tdnf-test-one'], cwd=workdir) + assert ret['retval'] == 1528 diff --git a/pytests/tests/test_metalink.py b/pytests/tests/test_metalink.py index 27c89054..d915065e 100644 --- a/pytests/tests/test_metalink.py +++ b/pytests/tests/test_metalink.py @@ -321,7 +321,7 @@ def test_invalid_md5_digest(utils): set_sha512(utils, False) set_invalid_md5(utils) ret = utils.run(['tdnf', 'makecache']) - assert ret['stderr'][0].startswith('Error: Validating metalink') + assert ret['stderr'][0].startswith('Error: Validating Checksum') cache_dir = utils.tdnf_config.get('main', 'cachedir') tmp_dir = os.path.join(cache_dir, 'photon-test/tmp') assert not os.path.isdir(tmp_dir) @@ -336,7 +336,7 @@ def test_invalid_sha1_digest(utils): set_sha512(utils, False) set_invalid_sha1(utils) ret = utils.run(['tdnf', 'makecache']) - assert ret['stderr'][0].startswith('Error: Validating metalink') + assert ret['stderr'][0].startswith('Error: Validating Checksum') cache_dir = utils.tdnf_config.get('main', 'cachedir') tmp_dir = os.path.join(cache_dir, 'photon-test/tmp') assert not os.path.isdir(tmp_dir) @@ -351,7 +351,7 @@ def test_invalid_sha256_digest(utils): set_sha512(utils, False) set_invalid_sha256(utils) ret = utils.run(['tdnf', 'makecache']) - assert ret['stderr'][0].startswith('Error: Validating metalink') + assert ret['stderr'][0].startswith('Error: Validating Checksum') cache_dir = utils.tdnf_config.get('main', 'cachedir') tmp_dir = os.path.join(cache_dir, 'photon-test/tmp') assert not os.path.isdir(tmp_dir) diff --git a/solv/prototypes.h b/solv/prototypes.h index 41b51816..0386a6db 100644 --- a/solv/prototypes.h +++ b/solv/prototypes.h @@ -187,6 +187,13 @@ SolvGetPackageId( Id* dwPkgId ); +uint32_t +SolvGetPkgChecksumFromId( + PSolvSack pSack, + uint32_t dwPkgId, + int *checksumType, + unsigned char** ppbChecksum + ); uint32_t SolvGetLatest( diff --git a/solv/tdnfpackage.c b/solv/tdnfpackage.c index e5d95133..da2c8d80 100644 --- a/solv/tdnfpackage.c +++ b/solv/tdnfpackage.c @@ -820,6 +820,62 @@ SolvGetPackageId( goto cleanup; } +uint32_t +SolvGetPkgChecksumFromId( + PSolvSack pSack, + uint32_t dwPkgId, + int *checksumType, + unsigned char** ppbChecksum) +{ + + uint32_t dwError = 0; + const unsigned char* pbTemp = NULL; + unsigned char* pbChecksum = NULL; + Solvable *pSolv = NULL; + int checksumLen = 0; + + if(!pSack || !pSack->pPool || !ppbChecksum) + { + dwError = ERROR_TDNF_INVALID_PARAMETER; + BAIL_ON_TDNF_ERROR(dwError); + } + + pSolv = pool_id2solvable(pSack->pPool, dwPkgId); + if(!pSolv) + { + dwError = ERROR_TDNF_NO_DATA; + BAIL_ON_TDNF_ERROR(dwError); + } + + pbTemp = solvable_lookup_bin_checksum(pSolv, SOLVABLE_CHECKSUM, checksumType); + + if(!pbTemp) + { + dwError = ERROR_TDNF_NO_DATA; + BAIL_ON_TDNF_ERROR(dwError); + } + + checksumLen = solv_chksum_len(*checksumType); + + dwError = TDNFAllocateMemory(checksumLen, sizeof(unsigned char), (void **)&pbChecksum); + BAIL_ON_TDNF_ERROR(dwError); + + memcpy(pbChecksum, pbTemp, checksumLen); + *ppbChecksum = pbChecksum; + +cleanup: + return dwError; + +error: + if(ppbChecksum) + { + *ppbChecksum = NULL; + } + TDNF_SAFE_FREE_MEMORY(pbChecksum); + goto cleanup; + +} + uint32_t SolvCmpEvr( PSolvSack pSack,