diff --git a/src/libraries/Common/src/Interop/Android/System.Security.Cryptography.Native.Android/Interop.Rsa.cs b/src/libraries/Common/src/Interop/Android/System.Security.Cryptography.Native.Android/Interop.Rsa.cs new file mode 100644 index 00000000000000..5d61a76943d8b8 --- /dev/null +++ b/src/libraries/Common/src/Interop/Android/System.Security.Cryptography.Native.Android/Interop.Rsa.cs @@ -0,0 +1,189 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; +using System.Security.Cryptography; +using Microsoft.Win32.SafeHandles; + +internal static partial class Interop +{ + internal static partial class AndroidCrypto + { + [DllImport(Libraries.CryptoNative, EntryPoint = "AndroidCryptoNative_RsaCreate")] + internal static extern SafeRsaHandle RsaCreate(); + + [DllImport(Libraries.CryptoNative, EntryPoint = "AndroidCryptoNative_RsaUpRef")] + [return: MarshalAs(UnmanagedType.Bool)] + internal static extern bool RsaUpRef(IntPtr rsa); + + [DllImport(Libraries.CryptoNative, EntryPoint = "AndroidCryptoNative_RsaDestroy")] + internal static extern void RsaDestroy(IntPtr rsa); + + internal static SafeRsaHandle DecodeRsaPublicKey(ReadOnlySpan buf) => + DecodeRsaPublicKey(ref MemoryMarshal.GetReference(buf), buf.Length); + + [DllImport(Libraries.CryptoNative, EntryPoint = "AndroidCryptoNative_DecodeRsaPublicKey")] + private static extern SafeRsaHandle DecodeRsaPublicKey(ref byte buf, int len); + + internal static int RsaPublicEncrypt( + int flen, + ReadOnlySpan from, + Span to, + SafeRsaHandle rsa, + RsaPadding padding) => + RsaPublicEncrypt(flen, ref MemoryMarshal.GetReference(from), ref MemoryMarshal.GetReference(to), rsa, padding); + + [DllImport(Libraries.CryptoNative, EntryPoint = "AndroidCryptoNative_RsaPublicEncrypt")] + private static extern int RsaPublicEncrypt( + int flen, + ref byte from, + ref byte to, + SafeRsaHandle rsa, + RsaPadding padding); + + internal static int RsaPrivateDecrypt( + int flen, + ReadOnlySpan from, + Span to, + SafeRsaHandle rsa, + RsaPadding padding) => + RsaPrivateDecrypt(flen, ref MemoryMarshal.GetReference(from), ref MemoryMarshal.GetReference(to), rsa, padding); + + [DllImport(Libraries.CryptoNative, EntryPoint = "AndroidCryptoNative_RsaPrivateDecrypt")] + private static extern int RsaPrivateDecrypt( + int flen, + ref byte from, + ref byte to, + SafeRsaHandle rsa, + RsaPadding padding); + + internal static int RsaSignPrimitive( + ReadOnlySpan from, + Span to, + SafeRsaHandle rsa) => + RsaSignPrimitive(from.Length, ref MemoryMarshal.GetReference(from), ref MemoryMarshal.GetReference(to), rsa); + + [DllImport(Libraries.CryptoNative, EntryPoint = "AndroidCryptoNative_RsaSignPrimitive")] + private static extern int RsaSignPrimitive( + int flen, + ref byte from, + ref byte to, + SafeRsaHandle rsa); + + internal static int RsaVerificationPrimitive( + ReadOnlySpan from, + Span to, + SafeRsaHandle rsa) => + RsaVerificationPrimitive(from.Length, ref MemoryMarshal.GetReference(from), ref MemoryMarshal.GetReference(to), rsa); + + [DllImport(Libraries.CryptoNative, EntryPoint = "AndroidCryptoNative_RsaVerificationPrimitive")] + private static extern int RsaVerificationPrimitive( + int flen, + ref byte from, + ref byte to, + SafeRsaHandle rsa); + + [DllImport(Libraries.CryptoNative, EntryPoint = "AndroidCryptoNative_RsaSize")] + internal static extern int RsaSize(SafeRsaHandle rsa); + + [DllImport(Libraries.CryptoNative, EntryPoint = "AndroidCryptoNative_RsaGenerateKeyEx")] + internal static extern int RsaGenerateKeyEx(SafeRsaHandle rsa, int bits); + + internal static RSAParameters ExportRsaParameters(SafeRsaHandle key, bool includePrivateParameters) + { + Debug.Assert( + key != null && !key.IsInvalid, + "Callers should check the key is invalid and throw an exception with a message"); + + if (key == null || key.IsInvalid) + { + throw new CryptographicException(); + } + + bool addedRef = false; + + try + { + key.DangerousAddRef(ref addedRef); + + IntPtr n, e, d, p, dmp1, q, dmq1, iqmp; + if (!GetRsaParameters(key, out n, out e, out d, out p, out dmp1, out q, out dmq1, out iqmp)) + { + throw new CryptographicException(); + } + + int modulusSize = RsaSize(key); + + // RSACryptoServiceProvider expects P, DP, Q, DQ, and InverseQ to all + // be padded up to half the modulus size. + int halfModulus = modulusSize / 2; + + RSAParameters rsaParameters = new RSAParameters + { + Modulus = Crypto.ExtractBignum(n, modulusSize)!, + Exponent = Crypto.ExtractBignum(e, 0)!, + }; + + if (includePrivateParameters) + { + rsaParameters.D = Crypto.ExtractBignum(d, modulusSize); + rsaParameters.P = Crypto.ExtractBignum(p, halfModulus); + rsaParameters.DP = Crypto.ExtractBignum(dmp1, halfModulus); + rsaParameters.Q = Crypto.ExtractBignum(q, halfModulus); + rsaParameters.DQ = Crypto.ExtractBignum(dmq1, halfModulus); + rsaParameters.InverseQ = Crypto.ExtractBignum(iqmp, halfModulus); + } + + return rsaParameters; + } + finally + { + if (addedRef) + key.DangerousRelease(); + } + } + + [DllImport(Libraries.CryptoNative, EntryPoint = "AndroidCryptoNative_GetRsaParameters")] + [return: MarshalAs(UnmanagedType.Bool)] + private static extern bool GetRsaParameters( + SafeRsaHandle key, + out IntPtr n, + out IntPtr e, + out IntPtr d, + out IntPtr p, + out IntPtr dmp1, + out IntPtr q, + out IntPtr dmq1, + out IntPtr iqmp); + + [DllImport(Libraries.CryptoNative, EntryPoint = "AndroidCryptoNative_SetRsaParameters")] + [return: MarshalAs(UnmanagedType.Bool)] + internal static extern bool SetRsaParameters( + SafeRsaHandle key, + byte[]? n, + int nLength, + byte[]? e, + int eLength, + byte[]? d, + int dLength, + byte[]? p, + int pLength, + byte[]? dmp1, + int dmp1Length, + byte[]? q, + int qLength, + byte[]? dmq1, + int dmq1Length, + byte[]? iqmp, + int iqmpLength); + + internal enum RsaPadding : int + { + Pkcs1 = 0, + OaepSHA1 = 1, + NoPadding = 2, + } + } +} diff --git a/src/libraries/Common/src/Microsoft/Win32/SafeHandles/SafeRsaHandle.Android.cs b/src/libraries/Common/src/Microsoft/Win32/SafeHandles/SafeRsaHandle.Android.cs new file mode 100644 index 00000000000000..bd4744b1f49af5 --- /dev/null +++ b/src/libraries/Common/src/Microsoft/Win32/SafeHandles/SafeRsaHandle.Android.cs @@ -0,0 +1,47 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Diagnostics; +using System.Security; +using System.Runtime.InteropServices; + +namespace System.Security.Cryptography +{ + internal sealed class SafeRsaHandle : SafeHandle + { + public SafeRsaHandle() : + base(IntPtr.Zero, ownsHandle: true) + { + } + + protected override bool ReleaseHandle() + { + Interop.AndroidCrypto.RsaDestroy(handle); + SetHandle(IntPtr.Zero); + return true; + } + + public override bool IsInvalid + { + get { return handle == IntPtr.Zero; } + } + + internal static SafeRsaHandle DuplicateHandle(IntPtr handle) + { + Debug.Assert(handle != IntPtr.Zero); + + // Reliability: Allocate the SafeHandle before calling RSA_up_ref so + // that we don't lose a tracked reference in low-memory situations. + SafeRsaHandle safeHandle = new SafeRsaHandle(); + + if (!Interop.AndroidCrypto.RsaUpRef(handle)) + { + throw new CryptographicException(); + } + + safeHandle.SetHandle(handle); + return safeHandle; + } + } +} diff --git a/src/libraries/Common/src/System/Security/Cryptography/RsaPaddingProcessor.cs b/src/libraries/Common/src/System/Security/Cryptography/RsaPaddingProcessor.cs index c385ca1a459844..f7aa5b4204b318 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/RsaPaddingProcessor.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/RsaPaddingProcessor.cs @@ -9,16 +9,56 @@ namespace System.Security.Cryptography { internal sealed class RsaPaddingProcessor { + // DigestInfo header values taken from https://tools.ietf.org/html/rfc3447#section-9.2, Note 1. + private static readonly byte[] s_digestInfoMD5 = + { + 0x30, 0x20, 0x30, 0x0C, 0x06, 0x08, 0x2A, 0x86, + 0x48, 0x86, 0xF7, 0x0D, 0x02, 0x05, 0x05, 0x00, + 0x04, 0x10, + }; + + private static readonly byte[] s_digestInfoSha1 = + { + 0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2B, 0x0E, 0x03, + 0x02, 0x1A, 0x05, 0x00, 0x04, 0x14, + }; + + private static readonly byte[] s_digestInfoSha256 = + { + 0x30, 0x31, 0x30, 0x0D, 0x06, 0x09, 0x60, 0x86, 0x48, + 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x05, 0x00, 0x04, + 0x20, + }; + + private static readonly byte[] s_digestInfoSha384 = + { + 0x30, 0x41, 0x30, 0x0D, 0x06, 0x09, 0x60, 0x86, 0x48, + 0x01, 0x65, 0x03, 0x04, 0x02, 0x02, 0x05, 0x00, 0x04, + 0x30, + }; + + private static readonly byte[] s_digestInfoSha512 = + { + 0x30, 0x41, 0x30, 0x0D, 0x06, 0x09, 0x60, 0x86, 0x48, + 0x01, 0x65, 0x03, 0x04, 0x02, 0x03, 0x05, 0x00, 0x04, + 0x40, + }; + private static readonly ConcurrentDictionary s_lookup = new ConcurrentDictionary(); private readonly HashAlgorithmName _hashAlgorithmName; private readonly int _hLen; + private readonly ReadOnlyMemory _digestInfoPrefix; - private RsaPaddingProcessor(HashAlgorithmName hashAlgorithmName, int hLen) + private RsaPaddingProcessor( + HashAlgorithmName hashAlgorithmName, + int hLen, + ReadOnlyMemory digestInfoPrefix) { _hashAlgorithmName = hashAlgorithmName; _hLen = hLen; + _digestInfoPrefix = digestInfoPrefix; } internal static int BytesRequiredForBitCount(int keySizeInBits) @@ -40,14 +80,41 @@ internal static RsaPaddingProcessor OpenProcessor(HashAlgorithmName hashAlgorith { // SHA-2-512 is the biggest we expect Span stackDest = stackalloc byte[512 / 8]; + ReadOnlyMemory digestInfoPrefix; + + if (hashAlgorithmName == HashAlgorithmName.MD5) + { + digestInfoPrefix = s_digestInfoMD5; + } + else if (hashAlgorithmName == HashAlgorithmName.SHA1) + { + digestInfoPrefix = s_digestInfoSha1; + } + else if (hashAlgorithmName == HashAlgorithmName.SHA256) + { + digestInfoPrefix = s_digestInfoSha256; + } + else if (hashAlgorithmName == HashAlgorithmName.SHA384) + { + digestInfoPrefix = s_digestInfoSha384; + } + else if (hashAlgorithmName == HashAlgorithmName.SHA512) + { + digestInfoPrefix = s_digestInfoSha512; + } + else + { + Debug.Fail("Unknown digest algorithm"); + throw new CryptographicException(); + } if (hasher.TryGetHashAndReset(stackDest, out int bytesWritten)) { - return new RsaPaddingProcessor(hashAlgorithmName, bytesWritten); + return new RsaPaddingProcessor(hashAlgorithmName, bytesWritten, digestInfoPrefix); } byte[] big = hasher.GetHashAndReset(); - return new RsaPaddingProcessor(hashAlgorithmName, big.Length); + return new RsaPaddingProcessor(hashAlgorithmName, big.Length, digestInfoPrefix); } }); } @@ -80,6 +147,44 @@ internal static void PadPkcs1Encryption( source.CopyTo(mInEM); } + internal void PadPkcs1Signature( + ReadOnlySpan source, + Span destination) + { + // https://tools.ietf.org/html/rfc3447#section-9.2 + + // 1. H = Hash(M) + // Done by the caller. + + // 2. Encode the DigestInfo value + ReadOnlySpan digestInfoPrefix = _digestInfoPrefix.Span; + int expectedLength = digestInfoPrefix[^1]; + + if (source.Length != expectedLength) + { + throw new CryptographicException(SR.Cryptography_SignHash_WrongSize); + } + + int tLen = digestInfoPrefix.Length + expectedLength; + + // 3. If emLen < tLen + 11, fail + if (destination.Length - 11 < tLen) + { + throw new CryptographicException(SR.Cryptography_KeyTooSmall); + } + + // 4. Generate emLen - tLen - 3 bytes of 0xFF as "PS" + int paddingLength = destination.Length - tLen - 3; + + // 5. EM = 0x00 || 0x01 || PS || 0x00 || T + destination[0] = 0; + destination[1] = 1; + destination.Slice(2, paddingLength).Fill(0xFF); + destination[paddingLength + 2] = 0; + digestInfoPrefix.CopyTo(destination.Slice(paddingLength + 3)); + source.CopyTo(destination.Slice(paddingLength + 3 + digestInfoPrefix.Length)); + } + internal void PadOaep( ReadOnlySpan source, Span destination) diff --git a/src/libraries/Native/Unix/Common/pal_config.h.in b/src/libraries/Native/Unix/Common/pal_config.h.in index 84196a23d5434c..428579edb2e9cf 100644 --- a/src/libraries/Native/Unix/Common/pal_config.h.in +++ b/src/libraries/Native/Unix/Common/pal_config.h.in @@ -31,6 +31,8 @@ #cmakedefine01 HAVE_NON_LEGACY_STATFS #cmakedefine01 HAVE_STRCPY_S #cmakedefine01 HAVE_STRLCPY +#cmakedefine01 HAVE_STRCAT_S +#cmakedefine01 HAVE_STRLCAT #cmakedefine01 HAVE_SHM_OPEN_THAT_WORKS_WELL_ENOUGH_WITH_MMAP #cmakedefine01 HAVE_POSIX_ADVISE #cmakedefine01 PRIORITY_REQUIRES_INT_WHO diff --git a/src/libraries/Native/Unix/Common/pal_utilities.h b/src/libraries/Native/Unix/Common/pal_utilities.h index 55f01e9137eb59..55d35030ce24df 100644 --- a/src/libraries/Native/Unix/Common/pal_utilities.h +++ b/src/libraries/Native/Unix/Common/pal_utilities.h @@ -66,6 +66,24 @@ inline static void SafeStringCopy(char* destination, size_t destinationSize, con #endif } +/** + * Abstraction helper method to safely copy strings using strlcpy or strcpy_s + * or a different safe copy method, depending on the current platform. + */ +inline static void SafeStringConcat(char* destination, size_t destinationSize, const char* str1, const char* str2) +{ + memset(destination, 0, destinationSize); +#if HAVE_STRCAT_S + strcat_s(destination, destinationSize, str1); + strcat_s(destination, destinationSize, str2); +#elif HAVE_STRLCAT + strlcat(destination, str1, destinationSize); + strlcat(destination, str2, destinationSize); +#else + snprintf(destination, destinationSize, "%s%s", str1, str2); +#endif +} + /** * Converts an intptr_t to a file descriptor. * intptr_t is the type used to marshal file descriptors so we can use SafeHandles effectively. diff --git a/src/libraries/Native/Unix/System.Security.Cryptography.Native.Android/CMakeLists.txt b/src/libraries/Native/Unix/System.Security.Cryptography.Native.Android/CMakeLists.txt index c04f75bf10fca0..444f744f07f007 100644 --- a/src/libraries/Native/Unix/System.Security.Cryptography.Native.Android/CMakeLists.txt +++ b/src/libraries/Native/Unix/System.Security.Cryptography.Native.Android/CMakeLists.txt @@ -18,6 +18,7 @@ set(NATIVECRYPTO_SOURCES pal_lifetime.c pal_misc.c pal_rsa.c + pal_signature.c pal_ssl.c pal_sslstream.c pal_x509.c diff --git a/src/libraries/Native/Unix/System.Security.Cryptography.Native.Android/pal_ecdsa.c b/src/libraries/Native/Unix/System.Security.Cryptography.Native.Android/pal_ecdsa.c index 12f8e604746b09..d42a6041d98f95 100644 --- a/src/libraries/Native/Unix/System.Security.Cryptography.Native.Android/pal_ecdsa.c +++ b/src/libraries/Native/Unix/System.Security.Cryptography.Native.Android/pal_ecdsa.c @@ -3,9 +3,10 @@ #include "pal_ecdsa.h" #include "pal_bignum.h" +#include "pal_signature.h" #include "pal_utilities.h" -static jobject AndroidCryptoNative_GetEsDsaSignatureObject(JNIEnv* env) +static jobject GetEcDsaSignatureObject(JNIEnv* env) { jstring algorithmName = JSTRING("NONEwithECDSA"); jobject signatureObject = @@ -28,37 +29,23 @@ int32_t AndroidCryptoNative_EcDsaSign(const uint8_t* dgst, int32_t dgstlen, uint JNIEnv* env = GetJNIEnv(); - jobject signatureObject = AndroidCryptoNative_GetEsDsaSignatureObject(env); + jobject signatureObject = GetEcDsaSignatureObject(env); if (!signatureObject) { return FAIL; } jobject privateKey = (*env)->CallObjectMethod(env, key->keyPair, g_keyPairGetPrivateMethod); - (*env)->CallVoidMethod(env, signatureObject, g_SignatureInitSign, privateKey); - ReleaseLRef(env, privateKey); - ON_EXCEPTION_PRINT_AND_GOTO(error); - - jbyteArray digestArray = (*env)->NewByteArray(env, dgstlen); - (*env)->SetByteArrayRegion(env, digestArray, 0, dgstlen, (const jbyte*)dgst); - (*env)->CallVoidMethod(env, signatureObject, g_SignatureUpdate, digestArray); - ReleaseLRef(env, digestArray); - ON_EXCEPTION_PRINT_AND_GOTO(error); - - jbyteArray sigResult = (*env)->CallObjectMethod(env, signatureObject, g_SignatureSign); - ON_EXCEPTION_PRINT_AND_GOTO(error); - jsize sigSize = (*env)->GetArrayLength(env, sigResult); - *siglen = sigSize; - (*env)->GetByteArrayRegion(env, sigResult, 0, sigSize, (jbyte*)sig); - ReleaseLRef(env, sigResult); - - ReleaseLRef(env, signatureObject); - - return SUCCESS; + if (!privateKey) + { + ReleaseLRef(env, signatureObject); + return FAIL; + } -error: + int32_t returnValue = AndroidCryptoNative_SignWithSignatureObject(env, signatureObject, privateKey, dgst, dgstlen, sig, siglen); + ReleaseLRef(env, privateKey); ReleaseLRef(env, signatureObject); - return FAIL; + return returnValue; } int32_t AndroidCryptoNative_EcDsaVerify(const uint8_t* dgst, int32_t dgstlen, const uint8_t* sig, int32_t siglen, EC_KEY* key) @@ -68,36 +55,17 @@ int32_t AndroidCryptoNative_EcDsaVerify(const uint8_t* dgst, int32_t dgstlen, co assert(key); JNIEnv* env = GetJNIEnv(); - jobject signatureObject = AndroidCryptoNative_GetEsDsaSignatureObject(env); + jobject signatureObject = GetEcDsaSignatureObject(env); if (!signatureObject) { return FAIL; } jobject publicKey = (*env)->CallObjectMethod(env, key->keyPair, g_keyPairGetPublicMethod); - (*env)->CallVoidMethod(env, signatureObject, g_SignatureInitVerify, publicKey); + int32_t returnValue = AndroidCryptoNative_VerifyWithSignatureObject(env, signatureObject, publicKey, dgst, dgstlen, sig, siglen); ReleaseLRef(env, publicKey); - ON_EXCEPTION_PRINT_AND_GOTO(error); - - jbyteArray digestArray = (*env)->NewByteArray(env, dgstlen); - (*env)->SetByteArrayRegion(env, digestArray, 0, dgstlen, (const jbyte*)dgst); - (*env)->CallVoidMethod(env, signatureObject, g_SignatureUpdate, digestArray); - ReleaseLRef(env, digestArray); - ON_EXCEPTION_PRINT_AND_GOTO(error); - - jbyteArray sigArray = (*env)->NewByteArray(env, siglen); - (*env)->SetByteArrayRegion(env, sigArray, 0, siglen, (const jbyte*)sig); - jboolean verified = (*env)->CallBooleanMethod(env, signatureObject, g_SignatureVerify, sigArray); - ReleaseLRef(env, sigArray); - ON_EXCEPTION_PRINT_AND_GOTO(error); - - ReleaseLRef(env, signatureObject); - - return verified ? SUCCESS : FAIL; - -error: ReleaseLRef(env, signatureObject); - return -1; + return returnValue; } int32_t AndroidCryptoNative_EcDsaSize(const EC_KEY* key) diff --git a/src/libraries/Native/Unix/System.Security.Cryptography.Native.Android/pal_rsa.c b/src/libraries/Native/Unix/System.Security.Cryptography.Native.Android/pal_rsa.c index 14432988d94dbe..64bda52f7bc780 100644 --- a/src/libraries/Native/Unix/System.Security.Cryptography.Native.Android/pal_rsa.c +++ b/src/libraries/Native/Unix/System.Security.Cryptography.Native.Android/pal_rsa.c @@ -2,21 +2,26 @@ // The .NET Foundation licenses this file to you under the MIT license. #include "pal_rsa.h" +#include "pal_bignum.h" +#include "pal_utilities.h" +#include "pal_signature.h" #define RSA_FAIL -1 -PALEXPORT RSA* CryptoNative_RsaCreate() +PALEXPORT RSA* AndroidCryptoNative_RsaCreate() { RSA* rsa = malloc(sizeof(RSA)); rsa->privateKey = NULL; rsa->publicKey = NULL; - rsa->pubExp = NULL; rsa->keyWidth = 0; - rsa->refCount = 1; + atomic_init(&rsa->refCount, 1); return rsa; } -PALEXPORT int32_t CryptoNative_RsaUpRef(RSA* rsa) +#pragma clang diagnostic push +// There's no way to specify explicit memory ordering for increment/decrement with C atomics. +#pragma clang diagnostic ignored "-Watomic-implicit-seq-cst" +PALEXPORT int32_t AndroidCryptoNative_RsaUpRef(RSA* rsa) { if (!rsa) return FAIL; @@ -24,7 +29,7 @@ PALEXPORT int32_t CryptoNative_RsaUpRef(RSA* rsa) return SUCCESS; } -PALEXPORT void CryptoNative_RsaDestroy(RSA* rsa) +PALEXPORT void AndroidCryptoNative_RsaDestroy(RSA* rsa) { if (rsa) { @@ -34,13 +39,13 @@ PALEXPORT void CryptoNative_RsaDestroy(RSA* rsa) JNIEnv* env = GetJNIEnv(); ReleaseGRef(env, rsa->privateKey); ReleaseGRef(env, rsa->publicKey); - ReleaseGRef(env, rsa->pubExp); free(rsa); } } } +#pragma clang diagnostic pop -PALEXPORT int32_t CryptoNative_RsaPublicEncrypt(int32_t flen, uint8_t* from, uint8_t* to, RSA* rsa, RsaPadding padding) +PALEXPORT int32_t AndroidCryptoNative_RsaPublicEncrypt(int32_t flen, uint8_t* from, uint8_t* to, RSA* rsa, RsaPadding padding) { if (!rsa) return RSA_FAIL; @@ -77,11 +82,14 @@ PALEXPORT int32_t CryptoNative_RsaPublicEncrypt(int32_t flen, uint8_t* from, uin return (int32_t)encryptedBytesLen; } -PALEXPORT int32_t CryptoNative_RsaPrivateDecrypt(int32_t flen, uint8_t* from, uint8_t* to, RSA* rsa, RsaPadding padding) +PALEXPORT int32_t AndroidCryptoNative_RsaPrivateDecrypt(int32_t flen, uint8_t* from, uint8_t* to, RSA* rsa, RsaPadding padding) { if (!rsa) return RSA_FAIL; + if (!rsa->privateKey) + return RSA_FAIL; + JNIEnv* env = GetJNIEnv(); jobject algName; @@ -117,14 +125,14 @@ PALEXPORT int32_t CryptoNative_RsaPrivateDecrypt(int32_t flen, uint8_t* from, ui return (int32_t)decryptedBytesLen; } -PALEXPORT int32_t CryptoNative_RsaSize(RSA* rsa) +PALEXPORT int32_t AndroidCryptoNative_RsaSize(RSA* rsa) { if (!rsa) return FAIL; return rsa->keyWidth / 8; } -PALEXPORT RSA* CryptoNative_DecodeRsaPublicKey(uint8_t* buf, int32_t len) +PALEXPORT RSA* AndroidCryptoNative_DecodeRsaPublicKey(uint8_t* buf, int32_t len) { if (!buf || !len) { @@ -143,7 +151,7 @@ PALEXPORT RSA* CryptoNative_DecodeRsaPublicKey(uint8_t* buf, int32_t len) (*env)->SetByteArrayRegion(env, bytes, 0, len, (jbyte*)buf); jobject x509keySpec = (*env)->NewObject(env, g_X509EncodedKeySpecClass, g_X509EncodedKeySpecCtor, bytes); - RSA* rsa = CryptoNative_RsaCreate(); + RSA* rsa = AndroidCryptoNative_RsaCreate(); rsa->publicKey = ToGRef(env, (*env)->CallObjectMethod(env, keyFactory, g_KeyFactoryGenPublicMethod, x509keySpec)); (*env)->DeleteLocalRef(env, algName); @@ -153,18 +161,24 @@ PALEXPORT RSA* CryptoNative_DecodeRsaPublicKey(uint8_t* buf, int32_t len) if (CheckJNIExceptions(env)) { - CryptoNative_RsaDestroy(rsa); + AndroidCryptoNative_RsaDestroy(rsa); return FAIL; } return rsa; } -PALEXPORT int32_t CryptoNative_RsaSignPrimitive(int32_t flen, uint8_t* from, uint8_t* to, RSA* rsa) +PALEXPORT int32_t AndroidCryptoNative_RsaSignPrimitive(int32_t flen, uint8_t* from, uint8_t* to, RSA* rsa) { if (!rsa) return RSA_FAIL; + if (!rsa->privateKey) + { + LOG_ERROR("RSA private key required to sign."); + return RSA_FAIL; + } + JNIEnv* env = GetJNIEnv(); jobject algName = JSTRING("RSA/ECB/NoPadding"); @@ -192,7 +206,7 @@ PALEXPORT int32_t CryptoNative_RsaSignPrimitive(int32_t flen, uint8_t* from, uin return (int32_t)encryptedBytesLen; } -PALEXPORT int32_t CryptoNative_RsaVerificationPrimitive(int32_t flen, uint8_t* from, uint8_t* to, RSA* rsa) +PALEXPORT int32_t AndroidCryptoNative_RsaVerificationPrimitive(int32_t flen, uint8_t* from, uint8_t* to, RSA* rsa) { if (!rsa) return RSA_FAIL; @@ -217,21 +231,7 @@ PALEXPORT int32_t CryptoNative_RsaVerificationPrimitive(int32_t flen, uint8_t* f return (int32_t)decryptedBytesLen; } -PALEXPORT int32_t CryptoNative_RsaSign(int32_t type, uint8_t* m, int32_t mlen, uint8_t* sigret, int32_t* siglen, RSA* rsa) -{ - // TODO: - LOG_ERROR("RSA signing NYI"); - return RSA_FAIL; -} - -PALEXPORT int32_t CryptoNative_RsaVerify(int32_t type, uint8_t* m, int32_t mlen, uint8_t* sigbuf, int32_t siglen, RSA* rsa) -{ - // TODO: - LOG_ERROR("RSA signing NYI"); - return RSA_FAIL; -} - -PALEXPORT int32_t CryptoNative_RsaGenerateKeyEx(RSA* rsa, int32_t bits, jobject pubExp) +PALEXPORT int32_t AndroidCryptoNative_RsaGenerateKeyEx(RSA* rsa, int32_t bits) { if (!rsa) return FAIL; @@ -249,8 +249,6 @@ PALEXPORT int32_t CryptoNative_RsaGenerateKeyEx(RSA* rsa, int32_t bits, jobject rsa->privateKey = ToGRef(env, (*env)->CallObjectMethod(env, keyPair, g_keyPairGetPrivateMethod)); rsa->publicKey = ToGRef(env, (*env)->CallObjectMethod(env, keyPair, g_keyPairGetPublicMethod)); rsa->keyWidth = bits; - // pubExp is already expected to be a gref at this point but we need to create another one. - rsa->pubExp = AddGRef(env, pubExp); (*env)->DeleteLocalRef(env, rsaStr); (*env)->DeleteLocalRef(env, kpgObj); @@ -259,7 +257,7 @@ PALEXPORT int32_t CryptoNative_RsaGenerateKeyEx(RSA* rsa, int32_t bits, jobject return CheckJNIExceptions(env) ? FAIL : SUCCESS; } -PALEXPORT int32_t CryptoNative_GetRsaParameters(RSA* rsa, +PALEXPORT int32_t AndroidCryptoNative_GetRsaParameters(RSA* rsa, jobject* n, jobject* e, jobject* d, jobject* p, jobject* dmp1, jobject* q, jobject* dmq1, jobject* iqmp) { if (!rsa || !n || !e || !d || !p || !dmp1 || !q || !dmq1 || !iqmp) @@ -321,17 +319,7 @@ PALEXPORT int32_t CryptoNative_GetRsaParameters(RSA* rsa, return CheckJNIExceptions(env) ? FAIL : SUCCESS; } -jobject BigNumFromBinary(JNIEnv* env, uint8_t* bytes, int32_t len) -{ - assert(len > 0); - jbyteArray buffArray = (*env)->NewByteArray(env, len); - (*env)->SetByteArrayRegion(env, buffArray, 0, len, (jbyte*)bytes); - jobject bigNum = (*env)->NewObject(env, g_bigNumClass, g_bigNumCtor, buffArray); - (*env)->DeleteLocalRef(env, buffArray); - return bigNum; -} - -PALEXPORT int32_t CryptoNative_SetRsaParameters(RSA* rsa, +PALEXPORT int32_t AndroidCryptoNative_SetRsaParameters(RSA* rsa, uint8_t* n, int32_t nLength, uint8_t* e, int32_t eLength, uint8_t* d, int32_t dLength, uint8_t* p, int32_t pLength, uint8_t* dmp1, int32_t dmp1Length, uint8_t* q, int32_t qLength, uint8_t* dmq1, int32_t dmq1Length, uint8_t* iqmp, int32_t iqmpLength) @@ -341,10 +329,10 @@ PALEXPORT int32_t CryptoNative_SetRsaParameters(RSA* rsa, JNIEnv* env = GetJNIEnv(); - jobject nObj = BigNumFromBinary(env, n, nLength); - jobject eObj = BigNumFromBinary(env, e, eLength); + jobject nObj = AndroidCryptoNative_BigNumFromBinary(n, nLength); + jobject eObj = AndroidCryptoNative_BigNumFromBinary(e, eLength); - rsa->keyWidth = (nLength - 1) * 8; // Android SDK has an extra byte in Modulus(?) + rsa->keyWidth = nLength * 8; jobject algName = JSTRING("RSA"); jobject keyFactory = (*env)->CallStaticObjectMethod(env, g_KeyFactoryClass, g_KeyFactoryGetInstanceMethod, algName); @@ -352,12 +340,12 @@ PALEXPORT int32_t CryptoNative_SetRsaParameters(RSA* rsa, if (dLength > 0) { // private key section - jobject dObj = BigNumFromBinary(env, d, dLength); - jobject pObj = BigNumFromBinary(env, p, pLength); - jobject qObj = BigNumFromBinary(env, q, qLength); - jobject dmp1Obj = BigNumFromBinary(env, dmp1, dmp1Length); - jobject dmq1Obj = BigNumFromBinary(env, dmq1, dmq1Length); - jobject iqmpObj = BigNumFromBinary(env, iqmp, iqmpLength); + jobject dObj = AndroidCryptoNative_BigNumFromBinary(d, dLength); + jobject pObj = AndroidCryptoNative_BigNumFromBinary(p, pLength); + jobject qObj = AndroidCryptoNative_BigNumFromBinary(q, qLength); + jobject dmp1Obj = AndroidCryptoNative_BigNumFromBinary(dmp1, dmp1Length); + jobject dmq1Obj = AndroidCryptoNative_BigNumFromBinary(dmq1, dmq1Length); + jobject iqmpObj = AndroidCryptoNative_BigNumFromBinary(iqmp, iqmpLength); jobject rsaPrivateKeySpec = (*env)->NewObject(env, g_RSAPrivateCrtKeySpecClass, g_RSAPrivateCrtKeySpecCtor, nObj, eObj, dObj, pObj, qObj, dmp1Obj, dmq1Obj, iqmpObj); @@ -365,13 +353,13 @@ PALEXPORT int32_t CryptoNative_SetRsaParameters(RSA* rsa, ReleaseGRef(env, rsa->privateKey); rsa->privateKey = ToGRef(env, (*env)->CallObjectMethod(env, keyFactory, g_KeyFactoryGenPrivateMethod, rsaPrivateKeySpec)); - (*env)->DeleteLocalRef(env, dObj); - (*env)->DeleteLocalRef(env, pObj); - (*env)->DeleteLocalRef(env, qObj); - (*env)->DeleteLocalRef(env, dmp1Obj); - (*env)->DeleteLocalRef(env, dmq1Obj); - (*env)->DeleteLocalRef(env, iqmpObj); - (*env)->DeleteLocalRef(env, rsaPrivateKeySpec); + (*env)->DeleteGlobalRef(env, dObj); + (*env)->DeleteGlobalRef(env, pObj); + (*env)->DeleteGlobalRef(env, qObj); + (*env)->DeleteGlobalRef(env, dmp1Obj); + (*env)->DeleteGlobalRef(env, dmq1Obj); + (*env)->DeleteGlobalRef(env, iqmpObj); + (*env)->DeleteGlobalRef(env, rsaPrivateKeySpec); } jobject rsaPubKeySpec = (*env)->NewObject(env, g_RSAPublicCrtKeySpecClass, g_RSAPublicCrtKeySpecCtor, nObj, eObj); @@ -381,8 +369,8 @@ PALEXPORT int32_t CryptoNative_SetRsaParameters(RSA* rsa, (*env)->DeleteLocalRef(env, algName); (*env)->DeleteLocalRef(env, keyFactory); - (*env)->DeleteLocalRef(env, nObj); - (*env)->DeleteLocalRef(env, eObj); + (*env)->DeleteGlobalRef(env, nObj); + (*env)->DeleteGlobalRef(env, eObj); (*env)->DeleteLocalRef(env, rsaPubKeySpec); return CheckJNIExceptions(env) ? FAIL : SUCCESS; diff --git a/src/libraries/Native/Unix/System.Security.Cryptography.Native.Android/pal_rsa.h b/src/libraries/Native/Unix/System.Security.Cryptography.Native.Android/pal_rsa.h index f514da3e9b53fd..59827f74a55130 100644 --- a/src/libraries/Native/Unix/System.Security.Cryptography.Native.Android/pal_rsa.h +++ b/src/libraries/Native/Unix/System.Security.Cryptography.Native.Android/pal_rsa.h @@ -4,6 +4,7 @@ #pragma once #include "pal_jni.h" +#include "pal_atomic.h" typedef enum { @@ -14,33 +15,28 @@ typedef enum typedef struct RSA { - jobject pubExp; jobject privateKey; // RSAPrivateCrtKey jobject publicKey; // RSAPublicCrtKey - int32_t refCount; + atomic_int refCount; int32_t keyWidth; } RSA; #define CIPHER_ENCRYPT_MODE 1 #define CIPHER_DECRYPT_MODE 2 -jobject BigNumFromBinary(JNIEnv* env, uint8_t* bytes, int32_t len); - -PALEXPORT RSA* CryptoNative_RsaCreate(void); -PALEXPORT int32_t CryptoNative_RsaUpRef(RSA* rsa); -PALEXPORT void CryptoNative_RsaDestroy(RSA* rsa); -PALEXPORT RSA* CryptoNative_DecodeRsaPublicKey(uint8_t* buf, int32_t len); -PALEXPORT int32_t CryptoNative_RsaPublicEncrypt(int32_t flen, uint8_t* from, uint8_t* to, RSA* rsa, RsaPadding padding); -PALEXPORT int32_t CryptoNative_RsaPrivateDecrypt(int32_t flen, uint8_t* from, uint8_t* to, RSA* rsa, RsaPadding padding); -PALEXPORT int32_t CryptoNative_RsaSignPrimitive(int32_t flen, uint8_t* from, uint8_t* to, RSA* rsa); -PALEXPORT int32_t CryptoNative_RsaVerificationPrimitive(int32_t flen, uint8_t* from, uint8_t* to, RSA* rsa); -PALEXPORT int32_t CryptoNative_RsaSize(RSA* rsa); -PALEXPORT int32_t CryptoNative_RsaGenerateKeyEx(RSA* rsa, int32_t bits, jobject pubExp); -PALEXPORT int32_t CryptoNative_RsaSign(int32_t type, uint8_t* m, int32_t mlen, uint8_t* sigret, int32_t* siglen, RSA* rsa); -PALEXPORT int32_t CryptoNative_RsaVerify(int32_t type, uint8_t* m, int32_t mlen, uint8_t* sigbuf, int32_t siglen, RSA* rsa); -PALEXPORT int32_t CryptoNative_GetRsaParameters(RSA* rsa, +PALEXPORT RSA* AndroidCryptoNative_RsaCreate(void); +PALEXPORT int32_t AndroidCryptoNative_RsaUpRef(RSA* rsa); +PALEXPORT void AndroidCryptoNative_RsaDestroy(RSA* rsa); +PALEXPORT RSA* AndroidCryptoNative_DecodeRsaPublicKey(uint8_t* buf, int32_t len); +PALEXPORT int32_t AndroidCryptoNative_RsaPublicEncrypt(int32_t flen, uint8_t* from, uint8_t* to, RSA* rsa, RsaPadding padding); +PALEXPORT int32_t AndroidCryptoNative_RsaPrivateDecrypt(int32_t flen, uint8_t* from, uint8_t* to, RSA* rsa, RsaPadding padding); +PALEXPORT int32_t AndroidCryptoNative_RsaSignPrimitive(int32_t flen, uint8_t* from, uint8_t* to, RSA* rsa); +PALEXPORT int32_t AndroidCryptoNative_RsaVerificationPrimitive(int32_t flen, uint8_t* from, uint8_t* to, RSA* rsa); +PALEXPORT int32_t AndroidCryptoNative_RsaSize(RSA* rsa); +PALEXPORT int32_t AndroidCryptoNative_RsaGenerateKeyEx(RSA* rsa, int32_t bits); +PALEXPORT int32_t AndroidCryptoNative_GetRsaParameters(RSA* rsa, jobject* n, jobject* e, jobject* d, jobject* p, jobject* dmp1, jobject* q, jobject* dmq1, jobject* iqmp); -PALEXPORT int32_t CryptoNative_SetRsaParameters(RSA* rsa, +PALEXPORT int32_t AndroidCryptoNative_SetRsaParameters(RSA* rsa, uint8_t* n, int32_t nLength, uint8_t* e, int32_t eLength, uint8_t* d, int32_t dLength, uint8_t* p, int32_t pLength, uint8_t* dmp1, int32_t dmp1Length, uint8_t* q, int32_t qLength, uint8_t* dmq1, int32_t dmq1Length, uint8_t* iqmp, int32_t iqmpLength); diff --git a/src/libraries/Native/Unix/System.Security.Cryptography.Native.Android/pal_signature.c b/src/libraries/Native/Unix/System.Security.Cryptography.Native.Android/pal_signature.c new file mode 100644 index 00000000000000..5a90c62daa722e --- /dev/null +++ b/src/libraries/Native/Unix/System.Security.Cryptography.Native.Android/pal_signature.c @@ -0,0 +1,76 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#include "pal_signature.h" + +int32_t AndroidCryptoNative_SignWithSignatureObject(JNIEnv* env, + jobject signatureObject, + jobject privateKey, + const uint8_t* dgst, + int32_t dgstlen, + uint8_t* sig, + int32_t* siglen) +{ + assert(dgst); + assert(sig); + assert(signatureObject); + assert(privateKey); + if (!siglen) + { + return FAIL; + } + + (*env)->CallVoidMethod(env, signatureObject, g_SignatureInitSign, privateKey); + ON_EXCEPTION_PRINT_AND_GOTO(error); + + jbyteArray digestArray = (*env)->NewByteArray(env, dgstlen); + (*env)->SetByteArrayRegion(env, digestArray, 0, dgstlen, (const jbyte*)dgst); + (*env)->CallVoidMethod(env, signatureObject, g_SignatureUpdate, digestArray); + ReleaseLRef(env, digestArray); + ON_EXCEPTION_PRINT_AND_GOTO(error); + + jbyteArray sigResult = (*env)->CallObjectMethod(env, signatureObject, g_SignatureSign); + ON_EXCEPTION_PRINT_AND_GOTO(error); + jsize sigSize = (*env)->GetArrayLength(env, sigResult); + *siglen = sigSize; + (*env)->GetByteArrayRegion(env, sigResult, 0, sigSize, (jbyte*)sig); + ReleaseLRef(env, sigResult); + return SUCCESS; + +error: + return FAIL; +} + +int32_t AndroidCryptoNative_VerifyWithSignatureObject(JNIEnv* env, + jobject signatureObject, + jobject publicKey, + const uint8_t* dgst, + int32_t dgstlen, + const uint8_t* sig, + int32_t siglen) +{ + assert(dgst); + assert(sig); + assert(signatureObject); + assert(publicKey); + + (*env)->CallVoidMethod(env, signatureObject, g_SignatureInitVerify, publicKey); + ON_EXCEPTION_PRINT_AND_GOTO(error); + + jbyteArray digestArray = (*env)->NewByteArray(env, dgstlen); + (*env)->SetByteArrayRegion(env, digestArray, 0, dgstlen, (const jbyte*)dgst); + (*env)->CallVoidMethod(env, signatureObject, g_SignatureUpdate, digestArray); + ReleaseLRef(env, digestArray); + ON_EXCEPTION_PRINT_AND_GOTO(error); + + jbyteArray sigArray = (*env)->NewByteArray(env, siglen); + (*env)->SetByteArrayRegion(env, sigArray, 0, siglen, (const jbyte*)sig); + jboolean verified = (*env)->CallBooleanMethod(env, signatureObject, g_SignatureVerify, sigArray); + ReleaseLRef(env, sigArray); + ON_EXCEPTION_PRINT_AND_GOTO(error); + + return verified ? SUCCESS : FAIL; + +error: + return SIGNATURE_VERIFICATION_ERROR; +} diff --git a/src/libraries/Native/Unix/System.Security.Cryptography.Native.Android/pal_signature.h b/src/libraries/Native/Unix/System.Security.Cryptography.Native.Android/pal_signature.h new file mode 100644 index 00000000000000..414c9bbeaa3da6 --- /dev/null +++ b/src/libraries/Native/Unix/System.Security.Cryptography.Native.Android/pal_signature.h @@ -0,0 +1,24 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#pragma once + +#include "pal_jni.h" + +#define SIGNATURE_VERIFICATION_ERROR -1 + +int32_t AndroidCryptoNative_SignWithSignatureObject(JNIEnv* env, + jobject signatureObject, + jobject privateKey, + const uint8_t* dgst, + int32_t dgstlen, + uint8_t* sig, + int32_t* siglen); + +int32_t AndroidCryptoNative_VerifyWithSignatureObject(JNIEnv* env, + jobject signatureObject, + jobject publicKey, + const uint8_t* dgst, + int32_t dgstlen, + const uint8_t* sig, + int32_t siglen); diff --git a/src/libraries/Native/Unix/configure.cmake b/src/libraries/Native/Unix/configure.cmake index d06059d5dd5274..cc33055c9cfd89 100644 --- a/src/libraries/Native/Unix/configure.cmake +++ b/src/libraries/Native/Unix/configure.cmake @@ -195,6 +195,16 @@ check_symbol_exists( string.h HAVE_STRLCPY) +check_symbol_exists( + strcat_s + string.h + HAVE_STRCAT_S) + +check_symbol_exists( + strlcat + string.h + HAVE_STRLCAT) + check_symbol_exists( posix_fadvise fcntl.h diff --git a/src/libraries/System.Security.Cryptography.Algorithms/src/System.Security.Cryptography.Algorithms.csproj b/src/libraries/System.Security.Cryptography.Algorithms/src/System.Security.Cryptography.Algorithms.csproj index 0061df697ac057..e1c4b60191af1e 100644 --- a/src/libraries/System.Security.Cryptography.Algorithms/src/System.Security.Cryptography.Algorithms.csproj +++ b/src/libraries/System.Security.Cryptography.Algorithms/src/System.Security.Cryptography.Algorithms.csproj @@ -460,8 +460,6 @@ Link="Common\Interop\Unix\System.Security.Cryptography.Native\Interop.Hmac.cs" /> - - - @@ -606,6 +600,12 @@ Link="Common\System\Security\Cryptography\ECDiffieHellmanOpenSsl.Derive.cs" /> + + + @@ -640,6 +640,11 @@ + + + new RSAImplementation.RSAAndroid(); + } + + internal static partial class RSAImplementation + { + public sealed partial class RSAAndroid : RSA + { + private const int BitsPerByte = 8; + + private Lazy _key; + + public RSAAndroid() + : this(2048) + { + } + + public RSAAndroid(int keySize) + { + base.KeySize = keySize; + _key = new Lazy(GenerateKey); + } + + public override int KeySize + { + set + { + if (KeySize == value) + { + return; + } + + // Set the KeySize before FreeKey so that an invalid value doesn't throw away the key + base.KeySize = value; + + ThrowIfDisposed(); + FreeKey(); + _key = new Lazy(GenerateKey); + } + } + + private void ForceSetKeySize(int newKeySize) + { + // In the event that a key was loaded via ImportParameters or an IntPtr/SafeHandle + // it could be outside of the bounds that we currently represent as "legal key sizes". + // Since that is our view into the underlying component it can be detached from the + // component's understanding. If it said it has opened a key, and this is the size, trust it. + KeySizeValue = newKeySize; + } + + public override KeySizes[] LegalKeySizes + { + get + { + return new[] { new KeySizes(512, 16384, 8) }; + } + } + + public override byte[] Decrypt(byte[] data, RSAEncryptionPadding padding) + { + if (data == null) + throw new ArgumentNullException(nameof(data)); + if (padding == null) + throw new ArgumentNullException(nameof(padding)); + + Interop.AndroidCrypto.RsaPadding rsaPadding = GetInteropPadding(padding, out RsaPaddingProcessor? oaepProcessor); + SafeRsaHandle key = GetKey(); + + int rsaSize = Interop.AndroidCrypto.RsaSize(key); + byte[]? buf = null; + Span destination = default; + + try + { + buf = CryptoPool.Rent(rsaSize); + destination = new Span(buf, 0, rsaSize); + + if (!TryDecrypt(key, data, destination, rsaPadding, oaepProcessor, out int bytesWritten)) + { + Debug.Fail($"{nameof(TryDecrypt)} should not return false for RSA_size buffer"); + throw new CryptographicException(); + } + + return destination.Slice(0, bytesWritten).ToArray(); + } + finally + { + CryptographicOperations.ZeroMemory(destination); + CryptoPool.Return(buf!, clearSize: 0); + } + } + + public override bool TryDecrypt( + ReadOnlySpan data, + Span destination, + RSAEncryptionPadding padding, + out int bytesWritten) + { + if (padding == null) + { + throw new ArgumentNullException(nameof(padding)); + } + + Interop.AndroidCrypto.RsaPadding rsaPadding = GetInteropPadding(padding, out RsaPaddingProcessor? oaepProcessor); + SafeRsaHandle key = GetKey(); + + int keySizeBytes = Interop.AndroidCrypto.RsaSize(key); + + // Android does not take a length value for the destination, so it can write out of bounds. + // To prevent the OOB write, decrypt into a temporary buffer. + if (destination.Length < keySizeBytes) + { + Span tmp = stackalloc byte[0]; + byte[]? rent = null; + + // RSA up through 4096 stackalloc + if (keySizeBytes <= 512) + { + tmp = stackalloc byte[keySizeBytes]; + } + else + { + rent = ArrayPool.Shared.Rent(keySizeBytes); + tmp = rent; + } + + bool ret = TryDecrypt(key, data, tmp, rsaPadding, oaepProcessor, out bytesWritten); + + if (ret) + { + tmp = tmp.Slice(0, bytesWritten); + + if (bytesWritten > destination.Length) + { + ret = false; + bytesWritten = 0; + } + else + { + tmp.CopyTo(destination); + } + + CryptographicOperations.ZeroMemory(tmp); + } + + if (rent != null) + { + // Already cleared + ArrayPool.Shared.Return(rent); + } + + return ret; + } + + return TryDecrypt(key, data, destination, rsaPadding, oaepProcessor, out bytesWritten); + } + + private static bool TryDecrypt( + SafeRsaHandle key, + ReadOnlySpan data, + Span destination, + Interop.AndroidCrypto.RsaPadding rsaPadding, + RsaPaddingProcessor? rsaPaddingProcessor, + out int bytesWritten) + { + // If rsaPadding is PKCS1 or OAEP-SHA1 then no depadding method should be present. + // If rsaPadding is NoPadding then a depadding method should be present. + Debug.Assert( + (rsaPadding == Interop.AndroidCrypto.RsaPadding.NoPadding) == + (rsaPaddingProcessor != null)); + + // Caller should have already checked this. + Debug.Assert(!key.IsInvalid); + + int rsaSize = Interop.AndroidCrypto.RsaSize(key); + + if (data.Length != rsaSize) + { + throw new CryptographicException(SR.Cryptography_RSA_DecryptWrongSize); + } + + if (destination.Length < rsaSize) + { + bytesWritten = 0; + return false; + } + + Span decryptBuf = destination; + byte[]? paddingBuf = null; + + if (rsaPaddingProcessor != null) + { + paddingBuf = CryptoPool.Rent(rsaSize); + decryptBuf = paddingBuf; + } + + try + { + int returnValue = Interop.AndroidCrypto.RsaPrivateDecrypt(data.Length, data, decryptBuf, key, rsaPadding); + CheckReturn(returnValue); + + if (rsaPaddingProcessor != null) + { + return rsaPaddingProcessor.DepadOaep(paddingBuf, destination, out bytesWritten); + } + else + { + // If the padding mode is RSA_NO_PADDING then the size of the decrypted block + // will be RSA_size. If any padding was used, then some amount (determined by the padding algorithm) + // will have been reduced, and only returnValue bytes were part of the decrypted + // body. Either way, we can just use returnValue, but some additional bytes may have been overwritten + // in the destination span. + bytesWritten = returnValue; + } + + return true; + } + finally + { + if (paddingBuf != null) + { + // DecryptBuf is paddingBuf if paddingBuf is not null, erase it before returning it. + // If paddingBuf IS null then decryptBuf was destination, and shouldn't be cleared. + CryptographicOperations.ZeroMemory(decryptBuf); + CryptoPool.Return(paddingBuf, clearSize: 0); + } + } + } + + public override byte[] Encrypt(byte[] data, RSAEncryptionPadding padding) + { + if (data == null) + throw new ArgumentNullException(nameof(data)); + if (padding == null) + throw new ArgumentNullException(nameof(padding)); + + Interop.AndroidCrypto.RsaPadding rsaPadding = GetInteropPadding(padding, out RsaPaddingProcessor? oaepProcessor); + SafeRsaHandle key = GetKey(); + + byte[] buf = new byte[Interop.AndroidCrypto.RsaSize(key)]; + + bool encrypted = TryEncrypt( + key, + data, + buf, + rsaPadding, + oaepProcessor, + out int bytesWritten); + + if (!encrypted || bytesWritten != buf.Length) + { + Debug.Fail($"TryEncrypt behaved unexpectedly: {nameof(encrypted)}=={encrypted}, {nameof(bytesWritten)}=={bytesWritten}, {nameof(buf.Length)}=={buf.Length}"); + throw new CryptographicException(); + } + + return buf; + } + + public override bool TryEncrypt(ReadOnlySpan data, Span destination, RSAEncryptionPadding padding, out int bytesWritten) + { + if (padding == null) + { + throw new ArgumentNullException(nameof(padding)); + } + + Interop.AndroidCrypto.RsaPadding rsaPadding = GetInteropPadding(padding, out RsaPaddingProcessor? oaepProcessor); + SafeRsaHandle key = GetKey(); + + return TryEncrypt(key, data, destination, rsaPadding, oaepProcessor, out bytesWritten); + } + + private static bool TryEncrypt( + SafeRsaHandle key, + ReadOnlySpan data, + Span destination, + Interop.AndroidCrypto.RsaPadding rsaPadding, + RsaPaddingProcessor? rsaPaddingProcessor, + out int bytesWritten) + { + int rsaSize = Interop.AndroidCrypto.RsaSize(key); + + if (destination.Length < rsaSize) + { + bytesWritten = 0; + return false; + } + + int returnValue; + + if (rsaPaddingProcessor != null) + { + Debug.Assert(rsaPadding == Interop.AndroidCrypto.RsaPadding.NoPadding); + byte[] rented = CryptoPool.Rent(rsaSize); + Span tmp = new Span(rented, 0, rsaSize); + + try + { + rsaPaddingProcessor.PadOaep(data, tmp); + returnValue = Interop.AndroidCrypto.RsaPublicEncrypt(tmp.Length, tmp, destination, key, rsaPadding); + } + finally + { + CryptographicOperations.ZeroMemory(tmp); + CryptoPool.Return(rented, clearSize: 0); + } + } + else + { + Debug.Assert(rsaPadding != Interop.AndroidCrypto.RsaPadding.NoPadding); + + returnValue = Interop.AndroidCrypto.RsaPublicEncrypt(data.Length, data, destination, key, rsaPadding); + } + + CheckReturn(returnValue); + + bytesWritten = returnValue; + Debug.Assert(returnValue == rsaSize, $"{returnValue} != {rsaSize}"); + return true; + + } + + private static Interop.AndroidCrypto.RsaPadding GetInteropPadding( + RSAEncryptionPadding padding, + out RsaPaddingProcessor? rsaPaddingProcessor) + { + if (padding == RSAEncryptionPadding.Pkcs1) + { + rsaPaddingProcessor = null; + return Interop.AndroidCrypto.RsaPadding.Pkcs1; + } + + if (padding == RSAEncryptionPadding.OaepSHA1) + { + rsaPaddingProcessor = null; + return Interop.AndroidCrypto.RsaPadding.OaepSHA1; + } + + if (padding.Mode == RSAEncryptionPaddingMode.Oaep) + { + rsaPaddingProcessor = RsaPaddingProcessor.OpenProcessor(padding.OaepHashAlgorithm); + return Interop.AndroidCrypto.RsaPadding.NoPadding; + } + + throw PaddingModeNotSupported(); + } + + public override RSAParameters ExportParameters(bool includePrivateParameters) + { + // It's entirely possible that this line will cause the key to be generated in the first place. + SafeRsaHandle key = GetKey(); + + RSAParameters rsaParameters = Interop.AndroidCrypto.ExportRsaParameters(key, includePrivateParameters); + bool hasPrivateKey = rsaParameters.D != null; + + if (hasPrivateKey != includePrivateParameters || !HasConsistentPrivateKey(ref rsaParameters)) + { + throw new CryptographicException(SR.Cryptography_CSP_NoPrivateKey); + } + + return rsaParameters; + } + + public override void ImportParameters(RSAParameters parameters) + { + ValidateParameters(ref parameters); + ThrowIfDisposed(); + + SafeRsaHandle key = Interop.AndroidCrypto.RsaCreate(); + bool imported = false; + + if (key is null || key.IsInvalid) + { + throw new CryptographicException(); + } + + try + { + if (!Interop.AndroidCrypto.SetRsaParameters( + key, + parameters.Modulus, + parameters.Modulus != null ? parameters.Modulus.Length : 0, + parameters.Exponent, + parameters.Exponent != null ? parameters.Exponent.Length : 0, + parameters.D, + parameters.D != null ? parameters.D.Length : 0, + parameters.P, + parameters.P != null ? parameters.P.Length : 0, + parameters.DP, + parameters.DP != null ? parameters.DP.Length : 0, + parameters.Q, + parameters.Q != null ? parameters.Q.Length : 0, + parameters.DQ, + parameters.DQ != null ? parameters.DQ.Length : 0, + parameters.InverseQ, + parameters.InverseQ != null ? parameters.InverseQ.Length : 0)) + { + throw new CryptographicException(); + } + + imported = true; + } + finally + { + if (!imported) + { + key.Dispose(); + } + } + + FreeKey(); + _key = new Lazy(key); + + // Use ForceSet instead of the property setter to ensure that LegalKeySizes doesn't interfere + // with the already loaded key. + ForceSetKeySize(BitsPerByte * Interop.AndroidCrypto.RsaSize(key)); + } + + public override void ImportRSAPublicKey(ReadOnlySpan source, out int bytesRead) + { + ThrowIfDisposed(); + + int read; + + try + { + AsnDecoder.ReadEncodedValue( + source, + AsnEncodingRules.BER, + out _, + out _, + out read); + } + catch (AsnContentException e) + { + throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding, e); + } + + SafeRsaHandle key = Interop.AndroidCrypto.DecodeRsaPublicKey(source.Slice(0, read)); + + if (key is null || key.IsInvalid) + { + throw new CryptographicException(); + } + + FreeKey(); + _key = new Lazy(key); + + // Use ForceSet instead of the property setter to ensure that LegalKeySizes doesn't interfere + // with the already loaded key. + ForceSetKeySize(BitsPerByte * Interop.AndroidCrypto.RsaSize(key)); + + bytesRead = read; + } + + public override void ImportEncryptedPkcs8PrivateKey( + ReadOnlySpan passwordBytes, + ReadOnlySpan source, + out int bytesRead) + { + ThrowIfDisposed(); + base.ImportEncryptedPkcs8PrivateKey(passwordBytes, source, out bytesRead); + } + + public override void ImportEncryptedPkcs8PrivateKey( + ReadOnlySpan password, + ReadOnlySpan source, + out int bytesRead) + { + ThrowIfDisposed(); + base.ImportEncryptedPkcs8PrivateKey(password, source, out bytesRead); + } + + protected override void Dispose(bool disposing) + { + if (disposing) + { + FreeKey(); + _key = null!; + } + + base.Dispose(disposing); + } + + private void FreeKey() + { + if (_key != null && _key.IsValueCreated) + { + SafeRsaHandle handle = _key.Value; + handle?.Dispose(); + } + } + + private static void ValidateParameters(ref RSAParameters parameters) + { + if (parameters.Modulus == null || parameters.Exponent == null) + throw new CryptographicException(SR.Argument_InvalidValue); + + if (!HasConsistentPrivateKey(ref parameters)) + throw new CryptographicException(SR.Argument_InvalidValue); + } + + private static bool HasConsistentPrivateKey(ref RSAParameters parameters) + { + if (parameters.D == null) + { + if (parameters.P != null || + parameters.DP != null || + parameters.Q != null || + parameters.DQ != null || + parameters.InverseQ != null) + { + return false; + } + } + else + { + if (parameters.P == null || + parameters.DP == null || + parameters.Q == null || + parameters.DQ == null || + parameters.InverseQ == null) + { + return false; + } + } + + return true; + } + + private void ThrowIfDisposed() + { + if (_key == null) + { + throw new ObjectDisposedException(nameof(RSA)); + } + } + + private SafeRsaHandle GetKey() + { + ThrowIfDisposed(); + + SafeRsaHandle key = _key.Value; + + if (key == null || key.IsInvalid) + { + throw new CryptographicException(SR.Cryptography_OpenInvalidHandle); + } + + return key; + } + + private static void CheckReturn(int returnValue) + { + if (returnValue == -1) + { + throw new CryptographicException(); + } + } + + private static void CheckBoolReturn(int returnValue) + { + if (returnValue != 1) + { + throw new CryptographicException(); + } + } + + private SafeRsaHandle GenerateKey() + { + SafeRsaHandle key = Interop.AndroidCrypto.RsaCreate(); + bool generated = false; + + if (key is null || key.IsInvalid) + { + throw new CryptographicException(); + } + + try + { + // The documentation for RSA_generate_key_ex does not say that it returns only + // 0 or 1, so the call marshals it back as a full Int32 and checks for a value + // of 1 explicitly. + int response = Interop.AndroidCrypto.RsaGenerateKeyEx( + key, + KeySize); + + CheckBoolReturn(response); + generated = true; + } + finally + { + if (!generated) + { + key.Dispose(); + } + } + + return key; + } + + protected override byte[] HashData(byte[] data, int offset, int count, HashAlgorithmName hashAlgorithm) => + AsymmetricAlgorithmHelpers.HashData(data, offset, count, hashAlgorithm); + + protected override byte[] HashData(Stream data, HashAlgorithmName hashAlgorithm) => + AsymmetricAlgorithmHelpers.HashData(data, hashAlgorithm); + + protected override bool TryHashData(ReadOnlySpan data, Span destination, HashAlgorithmName hashAlgorithm, out int bytesWritten) => + AsymmetricAlgorithmHelpers.TryHashData(data, destination, hashAlgorithm, out bytesWritten); + + public override byte[] SignHash(byte[] hash, HashAlgorithmName hashAlgorithm, RSASignaturePadding padding) + { + if (hash == null) + throw new ArgumentNullException(nameof(hash)); + if (string.IsNullOrEmpty(hashAlgorithm.Name)) + throw HashAlgorithmNameNullOrEmpty(); + if (padding == null) + throw new ArgumentNullException(nameof(padding)); + + if (!TrySignHash( + hash, + Span.Empty, + hashAlgorithm, padding, + true, + out int bytesWritten, + out byte[]? signature)) + { + Debug.Fail("TrySignHash should not return false in allocation mode"); + throw new CryptographicException(); + } + + Debug.Assert(signature != null); + return signature; + } + + public override bool TrySignHash( + ReadOnlySpan hash, + Span destination, + HashAlgorithmName hashAlgorithm, + RSASignaturePadding padding, + out int bytesWritten) + { + if (string.IsNullOrEmpty(hashAlgorithm.Name)) + { + throw HashAlgorithmNameNullOrEmpty(); + } + if (padding == null) + { + throw new ArgumentNullException(nameof(padding)); + } + + bool ret = TrySignHash( + hash, + destination, + hashAlgorithm, + padding, + false, + out bytesWritten, + out byte[]? alloced); + + Debug.Assert(alloced == null); + return ret; + } + + private bool TrySignHash( + ReadOnlySpan hash, + Span destination, + HashAlgorithmName hashAlgorithm, + RSASignaturePadding padding, + bool allocateSignature, + out int bytesWritten, + out byte[]? signature) + { + Debug.Assert(!string.IsNullOrEmpty(hashAlgorithm.Name)); + Debug.Assert(padding != null); + signature = null; + + if (padding == RSASignaturePadding.Pkcs1 && padding == RSASignaturePadding.Pss) + { + throw PaddingModeNotSupported(); + } + + RsaPaddingProcessor processor = RsaPaddingProcessor.OpenProcessor(hashAlgorithm); + SafeRsaHandle rsa = GetKey(); + + int bytesRequired = Interop.AndroidCrypto.RsaSize(rsa); + + if (allocateSignature) + { + Debug.Assert(destination.Length == 0); + signature = new byte[bytesRequired]; + destination = signature; + } + + if (destination.Length < bytesRequired) + { + bytesWritten = 0; + return false; + } + + byte[] encodedRented = CryptoPool.Rent(bytesRequired); + Span encodedBytes = new Span(encodedRented, 0, bytesRequired); + + if (padding.Mode == RSASignaturePaddingMode.Pkcs1) + { + processor.PadPkcs1Signature(hash, encodedBytes); + } + else if (padding.Mode == RSASignaturePaddingMode.Pss) + { + processor.EncodePss(hash, encodedBytes, KeySize); + } + else + { + Debug.Fail("Padding mode should be checked prior to this point."); + throw PaddingModeNotSupported(); + } + + int ret = Interop.AndroidCrypto.RsaSignPrimitive(encodedBytes, destination, rsa); + + CryptoPool.Return(encodedRented, bytesRequired); + + CheckReturn(ret); + + Debug.Assert( + ret == bytesRequired, + $"RsaSignPrimitive returned {ret} when {bytesRequired} was expected"); + + bytesWritten = ret; + return true; + } + + public override bool VerifyHash( + byte[] hash, + byte[] signature, + HashAlgorithmName hashAlgorithm, + RSASignaturePadding padding) + { + if (hash == null) + { + throw new ArgumentNullException(nameof(hash)); + } + if (signature == null) + { + throw new ArgumentNullException(nameof(signature)); + } + + return VerifyHash(new ReadOnlySpan(hash), new ReadOnlySpan(signature), hashAlgorithm, padding); + } + + public override bool VerifyHash(ReadOnlySpan hash, ReadOnlySpan signature, HashAlgorithmName hashAlgorithm, RSASignaturePadding padding) + { + if (string.IsNullOrEmpty(hashAlgorithm.Name)) + { + throw HashAlgorithmNameNullOrEmpty(); + } + if (padding == null) + { + throw new ArgumentNullException(nameof(padding)); + } + if (padding == RSASignaturePadding.Pkcs1 && padding == RSASignaturePadding.Pss) + { + throw PaddingModeNotSupported(); + } + + RsaPaddingProcessor processor = RsaPaddingProcessor.OpenProcessor(hashAlgorithm); + SafeRsaHandle rsa = GetKey(); + + int requiredBytes = Interop.AndroidCrypto.RsaSize(rsa); + + if (signature.Length != requiredBytes) + { + return false; + } + + if (hash.Length != processor.HashLength) + { + return false; + } + + byte[] rented = CryptoPool.Rent(requiredBytes); + Span unwrapped = new Span(rented, 0, requiredBytes); + + try + { + int ret = Interop.AndroidCrypto.RsaVerificationPrimitive(signature, unwrapped, rsa); + + CheckReturn(ret); + + Debug.Assert( + ret == requiredBytes, + $"RsaVerificationPrimitive returned {ret} when {requiredBytes} was expected"); + + if (padding == RSASignaturePadding.Pkcs1) + { + byte[] repadRent = CryptoPool.Rent(unwrapped.Length); + Span repadded = repadRent.AsSpan(0, requiredBytes); + processor.PadPkcs1Signature(hash, repadded); + bool valid = CryptographicOperations.FixedTimeEquals(repadded, unwrapped); + CryptoPool.Return(repadRent, requiredBytes); + return valid; + } + else if (padding == RSASignaturePadding.Pss) + { + return processor.VerifyPss(hash, unwrapped, KeySize); + } + else + { + Debug.Fail("Padding mode should be checked prior to this point."); + throw PaddingModeNotSupported(); + } + } + finally + { + CryptoPool.Return(rented, requiredBytes); + } + + throw PaddingModeNotSupported(); + } + + private static Exception PaddingModeNotSupported() => + new CryptographicException(SR.Cryptography_InvalidPaddingMode); + + private static Exception HashAlgorithmNameNullOrEmpty() => + new ArgumentException(SR.Cryptography_HashAlgorithmNameNullOrEmpty, "hashAlgorithm"); + } + } +} diff --git a/src/libraries/System.Security.Cryptography.Algorithms/tests/DefaultECDiffieHellmanProvider.Android.cs b/src/libraries/System.Security.Cryptography.Algorithms/tests/DefaultECDiffieHellmanProvider.Android.cs new file mode 100644 index 00000000000000..033d4d45d3a7fe --- /dev/null +++ b/src/libraries/System.Security.Cryptography.Algorithms/tests/DefaultECDiffieHellmanProvider.Android.cs @@ -0,0 +1,49 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Runtime.InteropServices; + +namespace System.Security.Cryptography.EcDiffieHellman.Tests +{ + public partial class ECDiffieHellmanProvider : IECDiffieHellmanProvider + { + public bool IsCurveValid(Oid oid) + { + if (!string.IsNullOrEmpty(oid.Value)) + { + // Value is passed before FriendlyName + return IsValueOrFriendlyNameValid(oid.Value); + } + return IsValueOrFriendlyNameValid(oid.FriendlyName); + } + + public bool ExplicitCurvesSupported + { + get + { + if (PlatformDetection.IsOSXLike) + { + return false; + } + + return true; + } + } + + private static bool IsValueOrFriendlyNameValid(string friendlyNameOrValue) + { + if (string.IsNullOrEmpty(friendlyNameOrValue)) + { + return false; + } + + IntPtr key = Interop.AndroidCrypto.EcKeyCreateByOid(friendlyNameOrValue); + if (key != IntPtr.Zero) + { + Interop.AndroidCrypto.EcKeyDestroy(key); + return true; + } + return false; + } + } +} diff --git a/src/libraries/System.Security.Cryptography.Algorithms/tests/DefaultECDsaProvider.Android.cs b/src/libraries/System.Security.Cryptography.Algorithms/tests/DefaultECDsaProvider.Android.cs new file mode 100644 index 00000000000000..e78bb9932c9b79 --- /dev/null +++ b/src/libraries/System.Security.Cryptography.Algorithms/tests/DefaultECDsaProvider.Android.cs @@ -0,0 +1,61 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Runtime.InteropServices; + +namespace System.Security.Cryptography.EcDsa.Tests +{ + public partial class ECDsaProvider : IECDsaProvider + { + public bool IsCurveValid(Oid oid) + { + if (!string.IsNullOrEmpty(oid.Value)) + { + // Value is passed before FriendlyName + return IsValueOrFriendlyNameValid(oid.Value); + } + return IsValueOrFriendlyNameValid(oid.FriendlyName); + } + + public bool ExplicitCurvesSupported + { + get + { + if (PlatformDetection.IsOSXLike) + { + return false; + } + + return true; + } + } + + private static bool IsValueOrFriendlyNameValid(string friendlyNameOrValue) + { + if (string.IsNullOrEmpty(friendlyNameOrValue)) + { + return false; + } + + IntPtr key = Interop.AndroidCrypto.EcKeyCreateByOid(friendlyNameOrValue); + if (key != IntPtr.Zero) + { + Interop.AndroidCrypto.EcKeyDestroy(key); + return true; + } + return false; + } + } +} + +internal static partial class Interop +{ + internal static partial class AndroidCrypto + { + [DllImport(Libraries.CryptoNative, EntryPoint = "AndroidCryptoNative_EcKeyCreateByOid")] + internal static extern System.IntPtr EcKeyCreateByOid(string oid); + + [DllImport(Libraries.CryptoNative, EntryPoint = "AndroidCryptoNative_EcKeyDestroy")] + internal static extern void EcKeyDestroy(System.IntPtr r); + } +} diff --git a/src/libraries/System.Security.Cryptography.Algorithms/tests/System.Security.Cryptography.Algorithms.Tests.csproj b/src/libraries/System.Security.Cryptography.Algorithms/tests/System.Security.Cryptography.Algorithms.Tests.csproj index f48d669b029416..2bb9d8ae82d356 100644 --- a/src/libraries/System.Security.Cryptography.Algorithms/tests/System.Security.Cryptography.Algorithms.Tests.csproj +++ b/src/libraries/System.Security.Cryptography.Algorithms/tests/System.Security.Cryptography.Algorithms.Tests.csproj @@ -1,6 +1,10 @@ - $(NetCoreAppCurrent)-windows;$(NetCoreAppCurrent)-Unix;$(NetCoreAppCurrent)-Browser + $(NetCoreAppCurrent)-windows;$(NetCoreAppCurrent)-Unix;$(NetCoreAppCurrent)-Android;$(NetCoreAppCurrent)-Browser + + true + true - + + + + + + + + - - -