-/*
- * Hashing function for CUPS.
- *
- * Copyright © 2015-2019 by Apple Inc.
- *
- * Licensed under Apache License v2.0. See the file "LICENSE" for more
- * information.
- */
-
-/*
- * Include necessary headers...
- */
+//
+// Hashing functions for CUPS.
+//
+// Copyright © 2022-2023 by OpenPrinting.
+// Copyright © 2015-2019 by Apple Inc.
+//
+// Licensed under Apache License v2.0. See the file "LICENSE" for more
+// information.
+//
#include "cups-private.h"
-#include "debug-internal.h"
-#ifdef __APPLE__
-# include <CommonCrypto/CommonDigest.h>
-#elif defined(HAVE_GNUTLS)
+#include "md5-internal.h"
+#ifdef HAVE_OPENSSL
+# include <openssl/evp.h>
+#else // HAVE_GNUTLS
# include <gnutls/crypto.h>
-# include "md5-internal.h"
-#elif _WIN32
-# include <windows.h>
-# include <bcrypt.h>
-#else
-# include "md5-internal.h"
-#endif /* __APPLE__ */
-
-
-/*
- * 'cupsHashData()' - Perform a hash function on the given data.
- *
- * The "algorithm" argument can be any of the registered, non-deprecated IPP
- * hash algorithms for the "job-password-encryption" attribute, including
- * "sha" for SHA-1, "sha-256" for SHA2-256, etc.
- *
- * The "hash" argument points to a buffer of "hashsize" bytes and should be at
- * least 64 bytes in length for all of the supported algorithms.
- *
- * The returned hash is binary data.
- *
- * @since CUPS 2.2/macOS 10.12@
- */
-
-ssize_t /* O - Size of hash or -1 on error */
-cupsHashData(const char *algorithm, /* I - Algorithm name */
- const void *data, /* I - Data to hash */
- size_t datalen, /* I - Length of data to hash */
- unsigned char *hash, /* I - Hash buffer */
- size_t hashsize) /* I - Size of hash buffer */
+#endif // HAVE_OPENSSL
+
+
+//
+// Note: While both GNU TLS and OpenSSL offer HMAC functions, they also exclude
+// certain hashes depending on the version of library and whatever patches are
+// applied by the OS vendor/Linux distribution. Since printers sometimes rely
+// on otherwise deprecated/obsolete hash functions for things like PIN printing
+// ("job-password"), and since such uses already have poor security regardless
+// of the hash function used, it is more important to provide guaranteed
+// implementations over some imaginary notion of "guaranteed security"...
+//
+
+//
+// Local functions...
+//
+
+static ssize_t hash_data(const char *algorithm, unsigned char *hash, size_t hashsize, const void *a, size_t alen, const void *b, size_t blen);
+
+
+//
+// 'cupsHashData()' - Perform a hash function on the given data.
+//
+// This function performs a hash function on the given data. The "algorithm"
+// argument can be any of the registered, non-deprecated IPP hash algorithms for
+// the "job-password-encryption" attribute, including "sha" for SHA-1,
+// "sha2-256" for SHA2-256, etc.
+//
+// The "hash" argument points to a buffer of "hashsize" bytes and should be at
+// least 64 bytes in length for all of the supported algorithms.
+//
+// The returned hash is binary data.
+//
+
+ssize_t // O - Size of hash or -1 on error
+cupsHashData(const char *algorithm, // I - Algorithm name
+ const void *data, // I - Data to hash
+ size_t datalen, // I - Length of data to hash
+ unsigned char *hash, // I - Hash buffer
+ size_t hashsize) // I - Size of hash buffer
{
if (!algorithm || !data || datalen == 0 || !hash || hashsize == 0)
{
return (-1);
}
-#ifdef __APPLE__
- if (!strcmp(algorithm, "md5"))
- {
- /*
- * MD5 (deprecated but widely used...)
- */
-
- CC_MD5_CTX ctx; /* MD5 context */
-
- if (hashsize < CC_MD5_DIGEST_LENGTH)
- goto too_small;
-
- CC_MD5_Init(&ctx);
- CC_MD5_Update(&ctx, data, (CC_LONG)datalen);
- CC_MD5_Final(hash, &ctx);
+ return (hash_data(algorithm, hash, hashsize, data, datalen, NULL, 0));
+}
- return (CC_MD5_DIGEST_LENGTH);
- }
- else if (!strcmp(algorithm, "sha"))
- {
- /*
- * SHA-1...
- */
- CC_SHA1_CTX ctx; /* SHA-1 context */
+//
+// 'cupsHashString()' - Format a hash value as a hexadecimal string.
+//
+// The passed buffer must be at least 2 * hashsize + 1 characters in length.
+//
- if (hashsize < CC_SHA1_DIGEST_LENGTH)
- goto too_small;
+const char * // O - Formatted string
+cupsHashString(
+ const unsigned char *hash, // I - Hash
+ size_t hashsize, // I - Size of hash
+ char *buffer, // I - String buffer
+ size_t bufsize) // I - Size of string buffer
+{
+ char *bufptr = buffer; // Pointer into buffer
+ static const char *hex = "0123456789abcdef";
+ // Hex characters (lowercase!)
- CC_SHA1_Init(&ctx);
- CC_SHA1_Update(&ctx, data, (CC_LONG)datalen);
- CC_SHA1_Final(hash, &ctx);
- return (CC_SHA1_DIGEST_LENGTH);
- }
-# ifdef CC_SHA224_DIGEST_LENGTH
- else if (!strcmp(algorithm, "sha2-224"))
+ // Range check input...
+ if (!hash || hashsize < 1 || !buffer || bufsize < (2 * hashsize + 1))
{
- CC_SHA256_CTX ctx; /* SHA-224 context */
-
- if (hashsize < CC_SHA224_DIGEST_LENGTH)
- goto too_small;
-
- CC_SHA224_Init(&ctx);
- CC_SHA224_Update(&ctx, data, (CC_LONG)datalen);
- CC_SHA224_Final(hash, &ctx);
-
- return (CC_SHA224_DIGEST_LENGTH);
+ if (buffer)
+ *buffer = '\0';
+ return (NULL);
}
-# endif /* CC_SHA224_DIGEST_LENGTH */
- else if (!strcmp(algorithm, "sha2-256"))
- {
- CC_SHA256_CTX ctx; /* SHA-256 context */
-
- if (hashsize < CC_SHA256_DIGEST_LENGTH)
- goto too_small;
- CC_SHA256_Init(&ctx);
- CC_SHA256_Update(&ctx, data, (CC_LONG)datalen);
- CC_SHA256_Final(hash, &ctx);
-
- return (CC_SHA256_DIGEST_LENGTH);
- }
- else if (!strcmp(algorithm, "sha2-384"))
+ // Loop until we've converted the whole hash...
+ while (hashsize > 0)
{
- CC_SHA512_CTX ctx; /* SHA-384 context */
-
- if (hashsize < CC_SHA384_DIGEST_LENGTH)
- goto too_small;
-
- CC_SHA384_Init(&ctx);
- CC_SHA384_Update(&ctx, data, (CC_LONG)datalen);
- CC_SHA384_Final(hash, &ctx);
+ *bufptr++ = hex[*hash >> 4];
+ *bufptr++ = hex[*hash & 15];
- return (CC_SHA384_DIGEST_LENGTH);
+ hash ++;
+ hashsize --;
}
- else if (!strcmp(algorithm, "sha2-512"))
- {
- CC_SHA512_CTX ctx; /* SHA-512 context */
- if (hashsize < CC_SHA512_DIGEST_LENGTH)
- goto too_small;
+ *bufptr = '\0';
- CC_SHA512_Init(&ctx);
- CC_SHA512_Update(&ctx, data, (CC_LONG)datalen);
- CC_SHA512_Final(hash, &ctx);
+ return (buffer);
+}
- return (CC_SHA512_DIGEST_LENGTH);
- }
-# ifdef CC_SHA224_DIGEST_LENGTH
- else if (!strcmp(algorithm, "sha2-512_224"))
- {
- CC_SHA512_CTX ctx; /* SHA-512 context */
- unsigned char temp[CC_SHA512_DIGEST_LENGTH];
- /* SHA-512 hash */
- /*
- * SHA2-512 truncated to 224 bits (28 bytes)...
- */
+//
+// 'cupsHMACData()' - Perform a HMAC function on the given data.
+//
+// This function performs a HMAC function on the given data with the given key.
+// The "algorithm" argument can be any of the registered, non-deprecated IPP
+// hash algorithms for the "job-password-encryption" attribute, including
+// "sha" for SHA-1, "sha2-256" for SHA2-256, etc.
+//
+// The "hmac" argument points to a buffer of "hmacsize" bytes and should be at
+// least 64 bytes in length for all of the supported algorithms.
+//
+// The returned HMAC is binary data.
+//
+
+ssize_t // O - The length of the HMAC or `-1` on error
+cupsHMACData(
+ const char *algorithm, // I - Hash algorithm
+ const unsigned char *key, // I - Key
+ size_t keylen, // I - Length of key
+ const void *data, // I - Data to hash
+ size_t datalen, // I - Length of data to hash
+ unsigned char *hmac, // I - HMAC buffer
+ size_t hmacsize) // I - Size of HMAC buffer
+{
+ size_t i, // Looping var
+ b; // Block size
+ unsigned char buffer[128], // Intermediate buffer
+ hash[128], // Hash buffer
+ hkey[128]; // Hashed key buffer
+ ssize_t hashlen; // Length of hash
- if (hashsize < CC_SHA224_DIGEST_LENGTH)
- goto too_small;
- CC_SHA512_Init(&ctx);
- CC_SHA512_Update(&ctx, data, (CC_LONG)datalen);
- CC_SHA512_Final(temp, &ctx);
+ // Range check input...
+ if (!algorithm || !key || keylen == 0 || !data || datalen == 0 || !hmac || hmacsize < 32)
+ return (-1);
- memcpy(hash, temp, CC_SHA224_DIGEST_LENGTH);
+ // Determine the block size...
+ if (!strcmp(algorithm, "sha2-384") || !strncmp(algorithm, "sha2-512", 8))
+ b = 128;
+ else
+ b = 64;
- return (CC_SHA224_DIGEST_LENGTH);
- }
-# endif /* CC_SHA224_DIGEST_LENGTH */
- else if (!strcmp(algorithm, "sha2-512_256"))
+ // If the key length is larger than the block size, hash it and use that
+ // instead...
+ if (keylen > b)
{
- CC_SHA512_CTX ctx; /* SHA-512 context */
- unsigned char temp[CC_SHA512_DIGEST_LENGTH];
- /* SHA-512 hash */
+ if ((hashlen = hash_data(algorithm, hkey, sizeof(hkey), key, keylen, NULL, 0)) < 0)
+ return (-1);
- /*
- * SHA2-512 truncated to 256 bits (32 bytes)...
- */
+ key = hkey;
+ keylen = (size_t)hashlen;
+ }
- if (hashsize < CC_SHA256_DIGEST_LENGTH)
- goto too_small;
+ // HMAC = H(K' ^ opad, H(K' ^ ipad, data))
+ // K' = Klen > b ? H(K) : K, padded with 0's
+ // opad = 0x5c, ipad = 0x36
+ for (i = 0; i < b && i < keylen; i ++)
+ buffer[i] = key[i] ^ 0x36;
+ for (; i < b; i ++)
+ buffer[i] = 0x36;
- CC_SHA512_Init(&ctx);
- CC_SHA512_Update(&ctx, data, (CC_LONG)datalen);
- CC_SHA512_Final(temp, &ctx);
+ if ((hashlen = hash_data(algorithm, hash, sizeof(hash), buffer, b, data, datalen)) < 0)
+ return (-1);
- memcpy(hash, temp, CC_SHA256_DIGEST_LENGTH);
+ for (i = 0; i < b && i < keylen; i ++)
+ buffer[i] = key[i] ^ 0x5c;
+ for (; i < b; i ++)
+ buffer[i] = 0x5c;
+
+ return (hash_data(algorithm, hmac, hmacsize, buffer, b, hash, (size_t)hashlen));
+}
- return (CC_SHA256_DIGEST_LENGTH);
- }
-#elif defined(HAVE_GNUTLS)
+//
+// 'hash_data()' - Hash up to two blocks of data.
+//
+
+static ssize_t // O - Size of hash or `-1` on error
+hash_data(const char *algorithm, // I - Algorithm
+ unsigned char *hash, // I - Hash buffer
+ size_t hashsize, // I - Size of hash buffer
+ const void *a, // I - First block
+ size_t alen, // I - Length of first block
+ const void *b, // I - Second block or `NULL` for none
+ size_t blen) // I - Length of second block or `0` for none
+{
+ unsigned hashlen; // Length of hash
+ unsigned char hashtemp[64]; // Temporary hash buffer
+#ifdef HAVE_OPENSSL
+ const EVP_MD *md = NULL; // Message digest implementation
+ EVP_MD_CTX *ctx; // Context
+#else // HAVE_GNUTLS
gnutls_digest_algorithm_t alg = GNUTLS_DIG_UNKNOWN;
- /* Algorithm */
- unsigned char temp[64]; /* Temporary hash buffer */
- size_t tempsize = 0; /* Truncate to this size? */
+ // Algorithm
+ gnutls_hash_hd_t ctx; // Context
+#endif // HAVE_OPENSSL
if (!strcmp(algorithm, "md5"))
{
- /*
- * Some versions of GNU TLS disable MD5 without warning...
- */
-
- _cups_md5_state_t state; /* MD5 state info */
+ // Some versions of GNU TLS and OpenSSL disable MD5 without warning...
+ _cups_md5_state_t state; // MD5 state info
if (hashsize < 16)
goto too_small;
_cupsMD5Init(&state);
- _cupsMD5Append(&state, data, (int)datalen);
+ _cupsMD5Append(&state, a, (int)alen);
+ if (b && blen)
+ _cupsMD5Append(&state, b, (int)blen);
_cupsMD5Finish(&state, hash);
return (16);
}
- else if (!strcmp(algorithm, "sha"))
- alg = GNUTLS_DIG_SHA1;
+
+#ifdef HAVE_OPENSSL
+ if (!strcmp(algorithm, "sha"))
+ {
+ // SHA-1
+ md = EVP_sha1();
+ }
else if (!strcmp(algorithm, "sha2-224"))
- alg = GNUTLS_DIG_SHA224;
+ {
+ md = EVP_sha224();
+ }
else if (!strcmp(algorithm, "sha2-256"))
- alg = GNUTLS_DIG_SHA256;
+ {
+ md = EVP_sha256();
+ }
else if (!strcmp(algorithm, "sha2-384"))
- alg = GNUTLS_DIG_SHA384;
- else if (!strcmp(algorithm, "sha2-512"))
- alg = GNUTLS_DIG_SHA512;
- else if (!strcmp(algorithm, "sha2-512_224"))
{
- alg = GNUTLS_DIG_SHA512;
- tempsize = 28;
+ md = EVP_sha384();
}
- else if (!strcmp(algorithm, "sha2-512_256"))
+ else if (!strcmp(algorithm, "sha2-512"))
{
- alg = GNUTLS_DIG_SHA512;
- tempsize = 32;
+ md = EVP_sha512();
}
- if (alg != GNUTLS_DIG_UNKNOWN)
+ if (md)
{
- if (tempsize > 0)
- {
- /*
- * Truncate result to tempsize bytes...
- */
-
- if (hashsize < tempsize)
- goto too_small;
-
- gnutls_hash_fast(alg, data, datalen, temp);
- memcpy(hash, temp, tempsize);
-
- return ((ssize_t)tempsize);
- }
-
- if (hashsize < gnutls_hash_get_len(alg))
+ ctx = EVP_MD_CTX_new();
+ EVP_DigestInit(ctx, md);
+ EVP_DigestUpdate(ctx, a, alen);
+ if (b && blen)
+ EVP_DigestUpdate(ctx, b, blen);
+ EVP_DigestFinal(ctx, hashtemp, &hashlen);
+
+ if (hashlen > hashsize)
goto too_small;
- gnutls_hash_fast(alg, data, datalen, hash);
+ memcpy(hash, hashtemp, hashlen);
- return ((ssize_t)gnutls_hash_get_len(alg));
+ return ((ssize_t)hashlen);
}
-#elif _WIN32
- // Use Windows CNG APIs to perform hashing...
- BCRYPT_ALG_HANDLE alg; // Algorithm handle
- LPCWSTR algid = NULL; // Algorithm ID
- ssize_t hashlen; // Hash length
- NTSTATUS status; // Status of hash
- unsigned char temp[64]; // Temporary hash buffer
- size_t tempsize = 0; // Truncate to this size?
-
-
- if (!strcmp(algorithm, "md5"))
+#else // HAVE_GNUTLS
+ if (!strcmp(algorithm, "sha"))
{
- algid = BCRYPT_MD5_ALGORITHM;
- hashlen = 16;
+ // SHA-1
+ alg = GNUTLS_DIG_SHA1;
}
- else if (!strcmp(algorithm, "sha"))
+ else if (!strcmp(algorithm, "sha2-224"))
{
- algid = BCRYPT_SHA1_ALGORITHM;
- hashlen = 20;
+ alg = GNUTLS_DIG_SHA224;
}
else if (!strcmp(algorithm, "sha2-256"))
{
- algid = BCRYPT_SHA256_ALGORITHM;
- hashlen = 32;
+ alg = GNUTLS_DIG_SHA256;
}
else if (!strcmp(algorithm, "sha2-384"))
{
- algid = BCRYPT_SHA384_ALGORITHM;
- hashlen = 48;
+ alg = GNUTLS_DIG_SHA384;
}
else if (!strcmp(algorithm, "sha2-512"))
{
- algid = BCRYPT_SHA512_ALGORITHM;
- hashlen = 64;
- }
- else if (!strcmp(algorithm, "sha2-512_224"))
- {
- algid = BCRYPT_SHA512_ALGORITHM;
- hashlen = tempsize = 28;
- }
- else if (!strcmp(algorithm, "sha2-512_256"))
- {
- algid = BCRYPT_SHA512_ALGORITHM;
- hashlen = tempsize = 32;
- }
-
- if (algid)
- {
- if (hashsize < (size_t)hashlen)
- goto too_small;
-
- if ((status = BCryptOpenAlgorithmProvider(&alg, algid, NULL, 0)) < 0)
- {
- DEBUG_printf(("2cupsHashData: BCryptOpenAlgorithmProvider returned %d.", status));
-
- if (status == STATUS_INVALID_PARAMETER)
- _cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("Bad algorithm parameter."), 1);
- else
- _cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("Unable to access cryptographic provider."), 1);
-
- return (-1);
- }
-
- if (tempsize > 0)
- {
- // Do a truncated SHA2-512 hash...
- status = BCryptHash(alg, NULL, 0, (PUCHAR)data, (ULONG)datalen, temp, sizeof(temp));
- memcpy(hash, temp, hashlen);
- }
- else
- {
- // Hash directly to buffer...
- status = BCryptHash(alg, NULL, 0, (PUCHAR)data, (ULONG)datalen, hash, (ULONG)hashlen);
- }
-
- BCryptCloseAlgorithmProvider(alg, 0);
-
- if (status < 0)
- {
- DEBUG_printf(("2cupsHashData: BCryptHash returned %d.", status));
-
- if (status == STATUS_INVALID_PARAMETER)
- _cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("Bad hashing parameter."), 1);
- else
- _cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("Hashing failed."), 1);
-
- return (-1);
- }
-
- return (hashlen);
+ alg = GNUTLS_DIG_SHA512;
}
-#else
- /*
- * No hash support beyond MD5 without CommonCrypto, GNU TLS, or CNG...
- */
-
- if (!strcmp(algorithm, "md5"))
+ if (alg != GNUTLS_DIG_UNKNOWN)
{
- _cups_md5_state_t state; /* MD5 state info */
+ hashlen = gnutls_hash_get_len(alg);
- if (hashsize < 16)
+ if (hashlen > hashsize)
goto too_small;
- _cupsMD5Init(&state);
- _cupsMD5Append(&state, data, datalen);
- _cupsMD5Finish(&state, hash);
+ gnutls_hash_init(&ctx, alg);
+ gnutls_hash(ctx, a, alen);
+ if (b && blen)
+ gnutls_hash(ctx, b, blen);
+ gnutls_hash_deinit(ctx, hashtemp);
- return (16);
- }
- else if (hashsize < 64)
- goto too_small;
-#endif /* __APPLE__ */
+ memcpy(hash, hashtemp, hashlen);
- /*
- * Unknown hash algorithm...
- */
+ return ((ssize_t)hashlen);
+ }
+#endif // HAVE_OPENSSL
+ // Unknown hash algorithm...
_cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("Unknown hash algorithm."), 1);
return (-1);
- /*
- * We get here if the buffer is too small.
- */
-
+ // We get here if the buffer is too small.
too_small:
_cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("Hash buffer too small."), 1);
return (-1);
}
-
-/*
- * 'cupsHashString()' - Format a hash value as a hexadecimal string.
- *
- * The passed buffer must be at least 2 * hashsize + 1 characters in length.
- *
- * @since CUPS 2.2.7@
- */
-
-const char * /* O - Formatted string */
-cupsHashString(
- const unsigned char *hash, /* I - Hash */
- size_t hashsize, /* I - Size of hash */
- char *buffer, /* I - String buffer */
- size_t bufsize) /* I - Size of string buffer */
-{
- char *bufptr = buffer; /* Pointer into buffer */
- static const char *hex = "0123456789abcdef";
- /* Hex characters (lowercase!) */
-
-
- /*
- * Range check input...
- */
-
- if (!hash || hashsize < 1 || !buffer || bufsize < (2 * hashsize + 1))
- {
- if (buffer)
- *buffer = '\0';
- return (NULL);
- }
-
- /*
- * Loop until we've converted the whole hash...
- */
-
- while (hashsize > 0)
- {
- *bufptr++ = hex[*hash >> 4];
- *bufptr++ = hex[*hash & 15];
-
- hash ++;
- hashsize --;
- }
-
- *bufptr = '\0';
-
- return (buffer);
-}