Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Manually depad RSAES-PKCS1 on Apple OSes #97738

Merged
merged 7 commits into from
Feb 12, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Buffers;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
Expand Down Expand Up @@ -69,8 +70,8 @@ private static partial int RsaDecryptOaep(
out SafeCFDataHandle pEncryptedOut,
out SafeCFErrorHandle pErrorOut);

[LibraryImport(Libraries.AppleCryptoNative, EntryPoint = "AppleCryptoNative_RsaDecryptPkcs")]
private static partial int RsaDecryptPkcs(
[LibraryImport(Libraries.AppleCryptoNative, EntryPoint = "AppleCryptoNative_RsaDecryptRaw")]
vcsjones marked this conversation as resolved.
Show resolved Hide resolved
private static partial int RsaDecryptRaw(
SafeSecKeyRefHandle publicKey,
ReadOnlySpan<byte> pbData,
int cbData,
Expand Down Expand Up @@ -166,17 +167,40 @@ internal static byte[] RsaDecrypt(
byte[] data,
RSAEncryptionPadding padding)
{
if (padding == RSAEncryptionPadding.Pkcs1)
{
byte[] padded = ExecuteTransform(
data,
(ReadOnlySpan<byte> source, out SafeCFDataHandle decrypted, out SafeCFErrorHandle error) =>
RsaDecryptRaw(privateKey, source, source.Length, out decrypted, out error));

byte[] depad = CryptoPool.Rent(padded.Length);
OperationStatus status = RsaPaddingProcessor.DepadPkcs1Encryption(padded, depad, out int written);
byte[]? ret = null;

if (status == OperationStatus.Done)
{
ret = depad.AsSpan(0, written).ToArray();
}

// Clear the whole thing, especially on failure.
CryptoPool.Return(depad);
CryptographicOperations.ZeroMemory(padded);

if (ret is null)
{
throw new CryptographicException(SR.Cryptography_InvalidPadding);
}

return ret;
}

Debug.Assert(padding.Mode == RSAEncryptionPaddingMode.Oaep);

return ExecuteTransform(
data,
(ReadOnlySpan<byte> source, out SafeCFDataHandle decrypted, out SafeCFErrorHandle error) =>
{
if (padding == RSAEncryptionPadding.Pkcs1)
{
return RsaDecryptPkcs(privateKey, source, source.Length, out decrypted, out error);
}

Debug.Assert(padding.Mode == RSAEncryptionPaddingMode.Oaep);

return RsaDecryptOaep(
privateKey,
source,
Expand All @@ -195,14 +219,63 @@ internal static bool TryRsaDecrypt(
out int bytesWritten)
{
Debug.Assert(padding.Mode == RSAEncryptionPaddingMode.Pkcs1 || padding.Mode == RSAEncryptionPaddingMode.Oaep);

if (padding.Mode == RSAEncryptionPaddingMode.Pkcs1)
{
byte[] padded = CryptoPool.Rent(source.Length);
byte[] depad = CryptoPool.Rent(source.Length);

bool processed = TryExecuteTransform(
source,
padded,
out int paddedLength,
(ReadOnlySpan<byte> innerSource, out SafeCFDataHandle outputHandle, out SafeCFErrorHandle errorHandle) =>
RsaDecryptRaw(privateKey, innerSource, innerSource.Length, out outputHandle, out errorHandle));

Debug.Assert(
processed,
"TryExecuteTransform should always return true for a large enough buffer.");

OperationStatus status = OperationStatus.InvalidData;
int depaddedLength = 0;

if (processed)
{
status = RsaPaddingProcessor.DepadPkcs1Encryption(
new ReadOnlySpan<byte>(padded, 0, paddedLength),
depad,
out depaddedLength);
}

CryptoPool.Return(padded);

if (status == OperationStatus.Done)
{
if (depaddedLength <= destination.Length)
{
depad.AsSpan(0, depaddedLength).CopyTo(destination);
CryptoPool.Return(depad);
bytesWritten = depaddedLength;
return true;
}

CryptoPool.Return(depad);
bytesWritten = 0;
return false;
}

CryptoPool.Return(depad);
Debug.Assert(status == OperationStatus.InvalidData);
throw new CryptographicException(SR.Cryptography_InvalidPadding);
}

return TryExecuteTransform(
source,
destination,
out bytesWritten,
delegate (ReadOnlySpan<byte> innerSource, out SafeCFDataHandle outputHandle, out SafeCFErrorHandle errorHandle)
{
return padding.Mode == RSAEncryptionPaddingMode.Pkcs1 ?
RsaDecryptPkcs(privateKey, innerSource, innerSource.Length, out outputHandle, out errorHandle) :
return
RsaDecryptOaep(privateKey, innerSource, innerSource.Length, PalAlgorithmFromAlgorithmName(padding.OaepHashAlgorithm), out outputHandle, out errorHandle);
});
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Buffers;
using System.Buffers.Binary;
using System.Collections.Concurrent;
using System.Diagnostics;
Expand Down Expand Up @@ -142,6 +143,106 @@ internal static void PadPkcs1Encryption(
source.CopyTo(mInEM);
}

internal static OperationStatus DepadPkcs1Encryption(
ReadOnlySpan<byte> source,
Span<byte> destination,
out int bytesWritten)
{
int primitive = DepadPkcs1Encryption(source);
int primitiveSign = SignStretch(primitive);

// len = abs(primitive);
int len = Choose(primitiveSign, -primitive, primitive);
int spaceRemain = destination.Length - len;
vcsjones marked this conversation as resolved.
Show resolved Hide resolved
int spaceRemainSign = SignStretch(spaceRemain);

// len = clampHigh(len, destination.Length);
len = Choose(spaceRemainSign, destination.Length, len);

// ret = spaceRemain < 0 ? DestinationTooSmall : Done
int ret = Choose(
spaceRemainSign,
(int)OperationStatus.DestinationTooSmall,
(int)OperationStatus.Done);

// ret = primitive < 0 ? InvalidData : ret;
ret = Choose(primitiveSign, (int)OperationStatus.InvalidData, ret);

// Write some number of bytes, regardless of the final return.
source[^len..].CopyTo(destination);

// bytesWritten = ret == Done ? len : 0;
bytesWritten = Choose(CheckZero(ret), len, 0);
return (OperationStatus)ret;
}

private static int DepadPkcs1Encryption(ReadOnlySpan<byte> source)
{
Debug.Assert(source.Length > 11);
ReadOnlySpan<byte> afterPadding = source.Slice(10);
ReadOnlySpan<byte> noZeros = source.Slice(2, 8);

// Find the first zero in noZeros, or -1 for no zeros.
int zeroPos = BlindFindFirstZero(noZeros);

// If zeroPos is negative, valid is -1, otherwise 0.
int valid = SignStretch(zeroPos);

// If there are no zeros in afterPadding then zeroPos is negative,
// so negating the sign stretch is 0, which makes hasPos 0.
// If there -was- a zero, sign stretching is 0, so negating it makes hasPos -1.
zeroPos = BlindFindFirstZero(afterPadding);
int hasLen = ~SignStretch(zeroPos);
valid &= hasLen;

// Check that the first two bytes are { 00 02 }
valid &= CheckZero(source[0] | (source[1] ^ 0x02));

// If there were no zeros, use the full after-min-padding segment.
int lenIfBad = Choose(hasLen, zeroPos, source.Length - 11);
int lenIfGood = afterPadding.Length - zeroPos - 1;
return Choose(valid, lenIfGood, 0 - lenIfBad);
}

private static int BlindFindFirstZero(ReadOnlySpan<byte> source)
{
// Any vectorization of this routine needs to use non-early termination,
// and instructions that do not vary their completion time on the input.

int pos = -1;

for (int i = source.Length - 1; i >= 0; i--)
{
// pos = source[i] == 0 ? i : pos;
int local = CheckZero(source[i]);
pos = Choose(local, i, pos);
}

return pos;
}

private static int SignStretch(int value)
{
return value >> 31;
}

private static int Choose(int selector, int yes, int no)
{
Debug.Assert((selector | (selector - 1)) == -1);
return (selector & yes) | (~selector & no);
}

private static int CheckZero(int value)
{
// For zero, ~value and value-1 are both all bits set (negative).
// For positive values, ~value is negative and value-1 is positive.
// For negative values except MinValue, ~value is positive and value-1 is negative.
// For MinValue, ~value is positive and value-1 is also positive.
// All together, the only thing that has negative & negative is 0, so stretch the sign bit.
int mask = ~value & (value - 1);
return SignStretch(mask);
}

internal static void PadPkcs1Signature(
HashAlgorithmName hashAlgorithmName,
ReadOnlySpan<byte> source,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ static const Entry s_cryptoAppleNative[] =
DllImportEntry(AppleCryptoNative_RsaGenerateKey)
DllImportEntry(AppleCryptoNative_RsaDecryptOaep)
DllImportEntry(AppleCryptoNative_RsaDecryptPkcs)
DllImportEntry(AppleCryptoNative_RsaDecryptRaw)
DllImportEntry(AppleCryptoNative_RsaEncryptOaep)
DllImportEntry(AppleCryptoNative_RsaEncryptPkcs)
DllImportEntry(AppleCryptoNative_RsaSignaturePrimitive)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,13 @@ int32_t AppleCryptoNative_RsaDecryptOaep(SecKeyRef privateKey,
privateKey, pbData, cbData, pDecryptedOut, pErrorOut, mgfAlgorithm, SecKeyCreateDecryptedData);
}

int32_t AppleCryptoNative_RsaDecryptRaw(
SecKeyRef privateKey, uint8_t* pbData, int32_t cbData, CFDataRef* pDecryptedOut, CFErrorRef* pErrorOut)
{
return RsaPrimitive(
privateKey, pbData, cbData, pDecryptedOut, pErrorOut, kSecKeyAlgorithmRSAEncryptionRaw, SecKeyCreateDecryptedData);
}

int32_t AppleCryptoNative_RsaDecryptPkcs(
SecKeyRef privateKey, uint8_t* pbData, int32_t cbData, CFDataRef* pDecryptedOut, CFErrorRef* pErrorOut)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,14 @@ PALEXPORT int32_t AppleCryptoNative_RsaDecryptOaep(SecKeyRef privateKey,
CFDataRef* pDecryptedOut,
CFErrorRef* pErrorOut);

/*
Decrypt the contents of pbData using the provided privateKey without validating or removing padding.

Follows pal_seckey return conventions.
*/
PALEXPORT int32_t AppleCryptoNative_RsaDecryptRaw(
SecKeyRef privateKey, uint8_t* pbData, int32_t cbData, CFDataRef* pDecryptedOut, CFErrorRef* pErrorOut);

/*
Decrypt the contents of pbData using the provided privateKey under PKCS#1 padding.

Expand Down
Loading