From d1c565a2467aa3c3ffe7cf1427e1e4a1436fb660 Mon Sep 17 00:00:00 2001 From: Download-Fritz Date: Tue, 10 Sep 2019 08:30:57 +0200 Subject: [PATCH] OcCryptoLib: Import secure memory comparison and password verification APIs --- Include/Library/OcCryptoLib.h | 51 +++++++++++++ Library/OcCryptoLib/OcCryptoLib.inf | 2 + Library/OcCryptoLib/PasswordHash.c | 108 ++++++++++++++++++++++++++++ Library/OcCryptoLib/SecureMem.c | 93 ++++++++++++++++++++++++ 4 files changed, 254 insertions(+) create mode 100644 Library/OcCryptoLib/PasswordHash.c create mode 100644 Library/OcCryptoLib/SecureMem.c diff --git a/Include/Library/OcCryptoLib.h b/Include/Library/OcCryptoLib.h index e95026c7..6d2503da 100644 --- a/Include/Library/OcCryptoLib.h +++ b/Include/Library/OcCryptoLib.h @@ -305,4 +305,55 @@ Sha384 ( UINTN Len ); +/** + Performs a cryptographically secure comparison of the contents of two + buffers. + + This function compares Length bytes of SourceBuffer to Length bytes of + DestinationBuffer in a cryptographically secure fashion. This especially + implies that different lengths of the longest shared prefix do not change + execution time in a way relevant to security. + + If Length > 0 and DestinationBuffer is NULL, then ASSERT(). + If Length > 0 and SourceBuffer is NULL, then ASSERT(). + If Length is greater than (MAX_ADDRESS - DestinationBuffer + 1), then ASSERT(). + If Length is greater than (MAX_ADDRESS - SourceBuffer + 1), then ASSERT(). + + @param DestinationBuffer The pointer to the destination buffer to compare. + @param SourceBuffer The pointer to the source buffer to compare. + @param Length The number of bytes to compare. + + @return 0 All Length bytes of the two buffers are identical. + @retval -1 The two buffers are not identical within Length + bytes. +**/ +INTN +SecureCompareMem ( + IN CONST VOID *DestinationBuffer, + IN CONST VOID *SourceBuffer, + IN UINTN Length + ); + +/** + Verify Password and Salt against RefHash. The used hash function is SHA-512, + thus the caller must ensure RefHash is at least 64 bytes in size. + + @param[in] Password The entered password to verify. + @param[in] PasswordSize The size, in bytes, of Password. + @param[in] Salt The cryptographic salt appended to Password on hash. + @param[in] SaltSize The size, in bytes, of Salt. + @param[in] RefHash The SHA-512 hash of the reference password and Salt. + + @returns Whether Password and Salt cryptographically match RefHash. + +**/ +BOOLEAN +OcVerifyPasswordSha512 ( + IN CONST UINT8 *Password, + IN UINT32 PasswordSize, + IN CONST UINT8 *Salt, + IN UINT32 SaltSize, + IN CONST UINT8 *RefHash + ); + #endif // OC_CRYPTO_LIB_H diff --git a/Library/OcCryptoLib/OcCryptoLib.inf b/Library/OcCryptoLib/OcCryptoLib.inf index 7667a2a9..e62a0d77 100644 --- a/Library/OcCryptoLib/OcCryptoLib.inf +++ b/Library/OcCryptoLib/OcCryptoLib.inf @@ -34,6 +34,8 @@ Md5.c Sha1.c Sha2.c + SecureMem.c + PasswordHash.c [Packages] MdePkg/MdePkg.dec diff --git a/Library/OcCryptoLib/PasswordHash.c b/Library/OcCryptoLib/PasswordHash.c new file mode 100644 index 00000000..e557000a --- /dev/null +++ b/Library/OcCryptoLib/PasswordHash.c @@ -0,0 +1,108 @@ +/** @file + Copyright (C) 2019, Download-Fritz. All rights reserved. + +This program and the accompanying materials +are licensed and made available under the terms and conditions of the BSD License +which accompanies this distribution. The full text of the license may be found at +http://opensource.org/licenses/bsd-license.php + +THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS, +WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED. + +**/ + +#include + +#include +#include +#include +#include + +VOID +OcHashPasswordSha512 ( + IN CONST UINT8 *Password, + IN UINT32 PasswordSize, + IN CONST UINT8 *Salt, + IN UINT32 SaltSize, + OUT UINT8 *Hash + ) +{ + UINT32 Index; + SHA512_CONTEXT ShaContext; + + ASSERT (Password != NULL); + ASSERT (PasswordSize > 0); + ASSERT (Hash != NULL); + + Sha512Init (&ShaContext); + Sha512Update (&ShaContext, Password, PasswordSize); + Sha512Update (&ShaContext, Salt, SaltSize); + Sha512Final (&ShaContext, Hash); + // + // The hash function is applied iteratively to slow down bruteforce attacks. + // The iteration count has been chosen to take roughly three seconds on + // modern hardware. + // + for (Index = 0; Index < 5000000; ++Index) { + Sha512Init (&ShaContext); + Sha512Update (&ShaContext, Hash, SHA512_DIGEST_SIZE); + // + // Password and Salt are re-added into hashing to, in case of a hash + // collision, again yield a unique hash in the subsequent iteration. + // + Sha512Update (&ShaContext, Password, PasswordSize); + Sha512Update (&ShaContext, Salt, SaltSize); + Sha512Final (&ShaContext, Hash); + } + // + // The security-critical data constructed by this function is destroyed to + // prevent data leakage by, after returning, free memory. + // FIXME: ZeroMem() is not necessarily safe (it may be optimized away for + // certain implementations) and CPU memory (registers and caches) are + // not considered. + // + ZeroMem (&ShaContext, sizeof (ShaContext)); +} + +/** + Verify Password and Salt against RefHash. The used hash function is SHA-512, + thus the caller must ensure RefHash is at least 64 bytes in size. + + @param[in] Password The entered password to verify. + @param[in] PasswordSize The size, in bytes, of Password. + @param[in] Salt The cryptographic salt appended to Password on hash. + @param[in] SaltSize The size, in bytes, of Salt. + @param[in] RefHash The SHA-512 hash of the reference password and Salt. + + @returns Whether Password and Salt cryptographically match RefHash. + +**/ +BOOLEAN +OcVerifyPasswordSha512 ( + IN CONST UINT8 *Password, + IN UINT32 PasswordSize, + IN CONST UINT8 *Salt, + IN UINT32 SaltSize, + IN CONST UINT8 *RefHash + ) +{ + BOOLEAN Result; + UINT8 VerifyHash[SHA512_DIGEST_SIZE]; + + ASSERT (Password != NULL); + ASSERT (PasswordSize > 0); + ASSERT (RefHash != NULL); + + OcHashPasswordSha512 (Password, PasswordSize, Salt, SaltSize, VerifyHash); + Result = SecureCompareMem (RefHash, VerifyHash, SHA512_DIGEST_SIZE) == 0; + // + // The security-critical data constructed by this function is destroyed to + // prevent data leakage by, after returning, free memory. + // FIXME: ZeroMem() is not necessarily safe (it may be optimized away for + // certain implementations) and CPU memory (registers and caches) are + // not considered. + // + ZeroMem (VerifyHash, SHA512_DIGEST_SIZE); + + return Result; +} diff --git a/Library/OcCryptoLib/SecureMem.c b/Library/OcCryptoLib/SecureMem.c new file mode 100644 index 00000000..75cd7772 --- /dev/null +++ b/Library/OcCryptoLib/SecureMem.c @@ -0,0 +1,93 @@ +/** @file + Copyright (C) 2019, Download-Fritz. All rights reserved. + +This program and the accompanying materials +are licensed and made available under the terms and conditions of the BSD License +which accompanies this distribution. The full text of the license may be found at +http://opensource.org/licenses/bsd-license.php + +THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS, +WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED. + +**/ + +#include + +#include + +/** + Performs a cryptographically secure comparison of the contents of two + buffers. + + This function compares Length bytes of SourceBuffer to Length bytes of + DestinationBuffer in a cryptographically secure fashion. This especially + implies that different lengths of the longest shared prefix do not change + execution time in a way relevant to security. + + If Length > 0 and DestinationBuffer is NULL, then ASSERT(). + If Length > 0 and SourceBuffer is NULL, then ASSERT(). + If Length is greater than (MAX_ADDRESS - DestinationBuffer + 1), then ASSERT(). + If Length is greater than (MAX_ADDRESS - SourceBuffer + 1), then ASSERT(). + + @param DestinationBuffer The pointer to the destination buffer to compare. + @param SourceBuffer The pointer to the source buffer to compare. + @param Length The number of bytes to compare. + + @return 0 All Length bytes of the two buffers are identical. + @retval -1 The two buffers are not identical within Length + bytes. +**/ +INTN +SecureCompareMem ( + IN CONST VOID *DestinationBuffer, + IN CONST VOID *SourceBuffer, + IN UINTN Length + ) +{ + // + // Based on libsodium secure_memcmp function implementation. + // + UINTN Index; + // + // The loop variables are volatile to prevent compiler optimizations, such as + // security-hurting simplifications and intrinsics insertion. + // + volatile CONST UINT8 *volatile Destination; + volatile CONST UINT8 *volatile Source; + volatile UINT8 XorDiff; + + if (Length > 0) { + ASSERT (DestinationBuffer != NULL); + ASSERT (SourceBuffer != NULL); + ASSERT ((Length - 1) <= (MAX_ADDRESS - (UINTN)DestinationBuffer)); + ASSERT ((Length - 1) <= (MAX_ADDRESS - (UINTN)SourceBuffer)); + } + + Destination = (volatile CONST UINT8 *)DestinationBuffer; + Source = (volatile CONST UINT8 *)SourceBuffer; + + XorDiff = 0; + for (Index = 0; Index < Length; ++Index) { + // + // XOR is used to prevent comparison result based branches causing + // slightly different execution times. A XOR operation only yields 0 when + // both operands are equal. + // Do not break early on mismatch to not leak information about prefixes. + // The AND operation with 0xFFU is performed to have a result promotion to + // unsigned int rather than int to ensure a safe cast. + // + XorDiff |= (UINT8)((Destination[Index] ^ (Source[Index])) & 0xFFU); + } + // + // This is implemented as an arithmetic operation to have an uniform + // execution time for success and failure cases. + // + // For XorDiff = 0, the subtraction wraps around and leads to a value of + // UINT_MAX. All other values, as XorDiff is unsigned, must be greater than 0 + // and hence cannot wrap around. This means extracting bit 8 of the + // operation's result will always yield 1 for XorDiff = 0 and always yield 0 + // for XorDiff != 0. This is then cast to INTN, which is safe because it + // can only ever be 0 or 1, to finally yield the appropiate return value. + // + return ((INTN)(1U & ((XorDiff - 1U) >> 8U))) - 1; +}