Skip to content

[ILASM] Add wrappers for platform-specific SHA-256 implementations#109559

Merged
amanasifkhalid merged 13 commits intodotnet:mainfrom
amanasifkhalid:sha256-impl
Nov 7, 2024
Merged

[ILASM] Add wrappers for platform-specific SHA-256 implementations#109559
amanasifkhalid merged 13 commits intodotnet:mainfrom
amanasifkhalid:sha256-impl

Conversation

@amanasifkhalid
Copy link
Contributor

@amanasifkhalid amanasifkhalid commented Nov 5, 2024

Part of #108627. I'm splitting this out of #109091 to expedite review, as the crypto part of the ILAsm determinism work seems to be in decent shape. The intent is to consume this API surface in ILAsm to produce checksums, though I imagine these wrappers could be useful elsewhere.

@ghost ghost added the needs-area-label An area label is needed to ensure this gets routed to the appropriate area owners label Nov 5, 2024
@amanasifkhalid amanasifkhalid added area-ILTools-coreclr and removed needs-area-label An area label is needed to ensure this gets routed to the appropriate area owners labels Nov 5, 2024
@dotnet-policy-service
Copy link
Contributor

Tagging subscribers to this area: @JulieLeeMSFT
See info in area-owners.md if you want to be subscribed.

@amanasifkhalid
Copy link
Contributor Author

@jkotas could you PTAL? Thanks!

{
CC_SHA256_CTX ctx = {{ 0 }};

if (!CC_SHA256_Init(&ctx))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Apple has a one-shot you can use too. It would look something like this:

 HRESULT Sha256Hash(BYTE* pSrc, DWORD srcSize, BYTE* pDst, DWORD dstSize)
 {
     if (dstSize < CC_SHA256_DIGEST_LENGTH)
     {
         return E_FAIL;
     }
         
     CC_SHA256(pSrc, (CC_LONG)srcSize, pDst);
     return S_OK;
 }

If there is an actual need for it, you can copy to a temp buffer like Windows and Linux do.

goto cleanup;
}

memcpy(pDst, hash, min(hashLength, dstSize));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we use min here? Is there a real scenario where dstSize is not big enough to hold the whole SHA-256 and it needs to be truncated?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In #109091, it's convenient to be able to get a truncated hash that can be used as a checksum (example). We could move the temp buffer logic to the caller, and require that pDst in Sha256Hash is big enough for a whole SHA-256 digest, if you'd prefer.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could move the temp buffer logic to the caller

I think it would make more sense.

@jkotas
Copy link
Member

jkotas commented Nov 5, 2024

cc @bartonjs This change is borrowing distro agnostic OpenSSL loading logic from System.Security.Cryptography.Native for ildasm

@amanasifkhalid amanasifkhalid changed the title Add wrappers for platform-specific SHA-256 implementations [ILASM] Add wrappers for platform-specific SHA-256 implementations Nov 5, 2024

return S_OK;
}
#elif defined(__linux__)
Copy link
Member

@am11 am11 Nov 6, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can be just #else as CryptoNative_EvpDigestOneShot is available on any Unix.

sha256.h
)

if(CLR_CMAKE_TARGET_LINUX)
Copy link
Member

@am11 am11 Nov 6, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if(CLR_CMAKE_TARGET_LINUX)
if(CLR_CMAKE_TARGET_UNIX AND NOT CLR_CMAKE_TARGET_APPLE)

)
endif(CLR_CMAKE_TARGET_WIN32)

if(CLR_CMAKE_TARGET_LINUX)
Copy link
Member

@am11 am11 Nov 6, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if(CLR_CMAKE_TARGET_LINUX)
if(CLR_CMAKE_TARGET_UNIX AND NOT CLR_CMAKE_TARGET_APPLE)

{
BCryptDestroyHash(hashHandle);
}
if(NULL != algHandle)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if(NULL != algHandle)
if (NULL != algHandle)

{
goto cleanup;
}
if (hashLength != 32)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure why we need to check this - we do not have the same check for other OSes. SHA256 is defined as returning 32 bytes. If the crypto provider returns something else here, something is seriously broken.

Comment on lines +92 to +99
inline bool IsOpenSslAvailable()
{
return CryptoNative_OpenSslAvailable() != 0;
}

inline HRESULT Sha256Hash(BYTE* pSrc, DWORD srcSize, BYTE* pDst, DWORD dstSize)
{
if (!IsOpenSslAvailable() || CryptoNative_EnsureOpenSslInitialized() || (dstSize < 32))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: perhaps a bit simpler this way:

Suggested change
inline bool IsOpenSslAvailable()
{
return CryptoNative_OpenSslAvailable() != 0;
}
inline HRESULT Sha256Hash(BYTE* pSrc, DWORD srcSize, BYTE* pDst, DWORD dstSize)
{
if (!IsOpenSslAvailable() || CryptoNative_EnsureOpenSslInitialized() || (dstSize < 32))
inline HRESULT Sha256Hash(BYTE* pSrc, DWORD srcSize, BYTE* pDst, DWORD dstSize)
{
if (CryptoNative_OpenSslAvailable() == 0 || CryptoNative_EnsureOpenSslInitialized() != 0 || (dstSize < 32))

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree it looks a bit redundant in its current form. The intent behind IsOpenSslAvailable is to allow ILAsm to disable features based on OpenSSL availability, without leaking the CryptoNative API surface to it. Including it in this PR is probably premature, though I will want something like it when ILAsm starts using these wrappers.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought I saw a comment from Jan K that said that we should treat OpenSSL here as required as it is for the platform... which is to say "it's required for 'Linux'" (which includes OpenBSD/NetBSD/etc).

If you're taking it as required, you can drop the call to CryptoNative_OpenSslAvailable; CryptoNative_EnsureOpenSslInitialized will do the same dlopen that Available does, it just fails (via assert) if the library isn't found.

If you're taking it as optional, then using OpenSslAvailable is correct.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suppose it makes sense to delegate the responsibility of checking for OpenSSL availability to the caller, if we're going to provide IsOpenSslAvailable anyway.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you just call CryptoNative_EnsureOpenSslInitialized and nothing else, CryptoNative_EnsureOpenSslInitialized is going to print "No usable version of libssl was found" and abort if there is not openssl available on the machine.

I think it is good enough. Explicitly calling OpenSslAvailable makes sense only if you would like to provide a different error message or handle the error situation differently.

return E_FAIL;
}

DWORD hashLength = 0;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
DWORD hashLength = 0;
uint32_t hashLength = 0;

Comment on lines +25 to +57
DWORD hashLength = 0;
DWORD resultLength = 0;

NTSTATUS status = BCryptOpenAlgorithmProvider(&algHandle, BCRYPT_SHA256_ALGORITHM, NULL, 0);

if (!NT_SUCCESS(status))
{
goto cleanup;
}

status = BCryptGetProperty(algHandle, BCRYPT_HASH_LENGTH, (PBYTE)&hashLength, sizeof(hashLength), &resultLength, 0);

if (!NT_SUCCESS(status))
{
goto cleanup;
}

assert(hashLength == 32);
status = BCryptCreateHash(algHandle, &hashHandle, NULL, 0, NULL, 0, 0);

if (!NT_SUCCESS(status))
{
goto cleanup;
}

status = BCryptHashData(hashHandle, pSrc, srcSize, 0);

if (!NT_SUCCESS(status))
{
goto cleanup;
}

status = BCryptFinishHash(hashHandle, pDst, hashLength, 0);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
DWORD hashLength = 0;
DWORD resultLength = 0;
NTSTATUS status = BCryptOpenAlgorithmProvider(&algHandle, BCRYPT_SHA256_ALGORITHM, NULL, 0);
if (!NT_SUCCESS(status))
{
goto cleanup;
}
status = BCryptGetProperty(algHandle, BCRYPT_HASH_LENGTH, (PBYTE)&hashLength, sizeof(hashLength), &resultLength, 0);
if (!NT_SUCCESS(status))
{
goto cleanup;
}
assert(hashLength == 32);
status = BCryptCreateHash(algHandle, &hashHandle, NULL, 0, NULL, 0, 0);
if (!NT_SUCCESS(status))
{
goto cleanup;
}
status = BCryptHashData(hashHandle, pSrc, srcSize, 0);
if (!NT_SUCCESS(status))
{
goto cleanup;
}
status = BCryptFinishHash(hashHandle, pDst, hashLength, 0);
DWORD resultLength = 0;
NTSTATUS status = BCryptOpenAlgorithmProvider(&algHandle, BCRYPT_SHA256_ALGORITHM, NULL, 0);
if (!NT_SUCCESS(status))
{
goto cleanup;
}
status = BCryptCreateHash(algHandle, &hashHandle, NULL, 0, NULL, 0, 0);
if (!NT_SUCCESS(status))
{
goto cleanup;
}
status = BCryptHashData(hashHandle, pSrc, srcSize, 0);
if (!NT_SUCCESS(status))
{
goto cleanup;
}
status = BCryptFinishHash(hashHandle, pDst, dstSize, 0);

#ifdef _WIN32
inline HRESULT Sha256Hash(BYTE* pSrc, DWORD srcSize, BYTE* pDst, DWORD dstSize)
{
if (dstSize < 32)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this check for exact match?

Taking output buffer, filling part of it, and not telling the caller how much of it was filled is odd. The typical patterns for output buffers are:

  • Take output buffer and fill it completely, fail if it is not possible to fill it completely
  • Take output buffer and return how much of it was filled


inline HRESULT Sha256Hash(BYTE* pSrc, DWORD srcSize, BYTE* pDst, DWORD dstSize)
{
if (!IsOpenSslAvailable() || CryptoNative_EnsureOpenSslInitialized() || (dstSize < 32))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (!IsOpenSslAvailable() || CryptoNative_EnsureOpenSslInitialized() || (dstSize < 32))
if (CryptoNative_EnsureOpenSslInitialized() || (dstSize < 32))

CryptoNative_OpenSslAvailable call is unnecessary here. It is done by CryptoNative_EnsureOpenSslInitialized

Copy link
Member

@jkotas jkotas left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

amanasifkhalid and others added 2 commits November 7, 2024 00:23
Co-authored-by: Adeel Mujahid <[email protected]>
Co-authored-by: Adeel Mujahid <[email protected]>
@amanasifkhalid
Copy link
Contributor Author

Thanks for the reviews!

@amanasifkhalid amanasifkhalid merged commit 36bcc2c into dotnet:main Nov 7, 2024
@amanasifkhalid amanasifkhalid deleted the sha256-impl branch November 7, 2024 05:47
@github-actions github-actions bot locked and limited conversation to collaborators Dec 7, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants