diff --git a/src/libraries/Common/src/Interop/Android/System.Security.Cryptography.Native.Android/Interop.Cipher.cs b/src/libraries/Common/src/Interop/Android/System.Security.Cryptography.Native.Android/Interop.Cipher.cs new file mode 100644 index 00000000000000..23caee885edf4c --- /dev/null +++ b/src/libraries/Common/src/Interop/Android/System.Security.Cryptography.Native.Android/Interop.Cipher.cs @@ -0,0 +1,226 @@ +// 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.Runtime.InteropServices; +using System.Security.Cryptography; +using Microsoft.Win32.SafeHandles; + +internal static partial class Interop +{ + internal static partial class Crypto + { + [DllImport(Libraries.CryptoNative, EntryPoint = "AndroidCryptoNative_CipherCreate")] + internal static extern SafeEvpCipherCtxHandle EvpCipherCreate( + IntPtr cipher, + ref byte key, + int keyLength, + int effectivekeyLength, + ref byte iv, + int enc); + + [DllImport(Libraries.CryptoNative, EntryPoint = "AndroidCryptoNative_CipherCreatePartial")] + internal static extern SafeEvpCipherCtxHandle EvpCipherCreatePartial( + IntPtr cipher); + + [DllImport(Libraries.CryptoNative, EntryPoint = "AndroidCryptoNative_CipherSetKeyAndIV")] + [return: MarshalAs(UnmanagedType.Bool)] + private static extern bool EvpCipherSetKeyAndIV( + SafeEvpCipherCtxHandle ctx, + ref byte key, + ref byte iv, + EvpCipherDirection direction); + + internal static void EvpCipherSetKeyAndIV( + SafeEvpCipherCtxHandle ctx, + ReadOnlySpan key, + ReadOnlySpan iv, + EvpCipherDirection direction) + { + if (!EvpCipherSetKeyAndIV( + ctx, + ref MemoryMarshal.GetReference(key), + ref MemoryMarshal.GetReference(iv), + direction)) + { + throw new CryptographicException(); + } + } + + [DllImport(Libraries.CryptoNative, EntryPoint = "AndroidCryptoNative_CipherSetNonceLength")] + [return: MarshalAs(UnmanagedType.Bool)] + private static extern bool AndroidCryptoNative_CipherSetNonceLength( + SafeEvpCipherCtxHandle ctx, int nonceLength); + + internal static void CipherSetNonceLength(SafeEvpCipherCtxHandle ctx, int nonceLength) + { + if (!AndroidCryptoNative_CipherSetNonceLength(ctx, nonceLength)) + { + throw new CryptographicException(); + } + } + + [DllImport(Libraries.CryptoNative, EntryPoint = "AndroidCryptoNative_CipherDestroy")] + internal static extern void EvpCipherDestroy(IntPtr ctx); + + [DllImport(Libraries.CryptoNative, EntryPoint = "AndroidCryptoNative_CipherReset")] + [return: MarshalAs(UnmanagedType.Bool)] + internal static extern bool EvpCipherReset(SafeEvpCipherCtxHandle ctx); + + [DllImport(Libraries.CryptoNative, EntryPoint = "AndroidCryptoNative_CipherCtxSetPadding")] + [return: MarshalAs(UnmanagedType.Bool)] + internal static extern bool EvpCipherCtxSetPadding(SafeEvpCipherCtxHandle x, int padding); + + [DllImport(Libraries.CryptoNative, EntryPoint = "AndroidCryptoNative_CipherUpdate")] + [return: MarshalAs(UnmanagedType.Bool)] + private static extern bool EvpCipherUpdate( + SafeEvpCipherCtxHandle ctx, + ref byte @out, + out int outl, + ref byte @in, + int inl); + + internal static bool EvpCipherUpdate( + SafeEvpCipherCtxHandle ctx, + Span output, + out int bytesWritten, + ReadOnlySpan input) + { + return EvpCipherUpdate( + ctx, + ref MemoryMarshal.GetReference(output), + out bytesWritten, + ref MemoryMarshal.GetReference(input), + input.Length); + } + + [DllImport(Libraries.CryptoNative, EntryPoint = "AndroidCryptoNative_CipherUpdateAAD")] + [return: MarshalAs(UnmanagedType.Bool)] + private static extern bool CipherUpdateAAD( + SafeEvpCipherCtxHandle ctx, + ref byte @in, + int inl); + + internal static void CipherUpdateAAD( + SafeEvpCipherCtxHandle ctx, + ReadOnlySpan input) + { + if (!CipherUpdateAAD( + ctx, + ref MemoryMarshal.GetReference(input), + input.Length)) + { + throw new CryptographicException(); + } + } + + [DllImport(Libraries.CryptoNative, EntryPoint = "AndroidCryptoNative_CipherFinalEx")] + [return: MarshalAs(UnmanagedType.Bool)] + private static extern bool EvpCipherFinalEx( + SafeEvpCipherCtxHandle ctx, + ref byte outm, + out int outl); + + internal static bool EvpCipherFinalEx( + SafeEvpCipherCtxHandle ctx, + Span output, + out int bytesWritten) + { + return EvpCipherFinalEx(ctx, ref MemoryMarshal.GetReference(output), out bytesWritten); + } + + [DllImport(Libraries.CryptoNative, EntryPoint = "AndroidCryptoNative_CipherSetTagLength")] + [return: MarshalAs(UnmanagedType.Bool)] + internal static extern bool CipherSetTagLength( + SafeEvpCipherCtxHandle ctx, + int tagLength); + + [DllImport(Libraries.CryptoNative, EntryPoint = "AndroidCryptoNative_Aes128Ecb")] + internal static extern IntPtr EvpAes128Ecb(); + + [DllImport(Libraries.CryptoNative, EntryPoint = "AndroidCryptoNative_Aes128Cbc")] + internal static extern IntPtr EvpAes128Cbc(); + + [DllImport(Libraries.CryptoNative, EntryPoint = "AndroidCryptoNative_Aes128Gcm")] + internal static extern IntPtr EvpAes128Gcm(); + + [DllImport(Libraries.CryptoNative, EntryPoint = "AndroidCryptoNative_Aes128Cfb8")] + internal static extern IntPtr EvpAes128Cfb8(); + + [DllImport(Libraries.CryptoNative, EntryPoint = "AndroidCryptoNative_Aes128Cfb128")] + internal static extern IntPtr EvpAes128Cfb128(); + + [DllImport(Libraries.CryptoNative, EntryPoint = "AndroidCryptoNative_Aes128Ccm")] + internal static extern IntPtr EvpAes128Ccm(); + + [DllImport(Libraries.CryptoNative, EntryPoint = "AndroidCryptoNative_Aes192Ecb")] + internal static extern IntPtr EvpAes192Ecb(); + + [DllImport(Libraries.CryptoNative, EntryPoint = "AndroidCryptoNative_Aes192Cbc")] + internal static extern IntPtr EvpAes192Cbc(); + + [DllImport(Libraries.CryptoNative, EntryPoint = "AndroidCryptoNative_Aes192Gcm")] + internal static extern IntPtr EvpAes192Gcm(); + + [DllImport(Libraries.CryptoNative, EntryPoint = "AndroidCryptoNative_Aes192Cfb8")] + internal static extern IntPtr EvpAes192Cfb8(); + + [DllImport(Libraries.CryptoNative, EntryPoint = "AndroidCryptoNative_Aes192Cfb128")] + internal static extern IntPtr EvpAes192Cfb128(); + + [DllImport(Libraries.CryptoNative, EntryPoint = "AndroidCryptoNative_Aes192Ccm")] + internal static extern IntPtr EvpAes192Ccm(); + + [DllImport(Libraries.CryptoNative, EntryPoint = "AndroidCryptoNative_Aes256Ecb")] + internal static extern IntPtr EvpAes256Ecb(); + + [DllImport(Libraries.CryptoNative, EntryPoint = "AndroidCryptoNative_Aes256Cbc")] + internal static extern IntPtr EvpAes256Cbc(); + + [DllImport(Libraries.CryptoNative, EntryPoint = "AndroidCryptoNative_Aes256Gcm")] + internal static extern IntPtr EvpAes256Gcm(); + + [DllImport(Libraries.CryptoNative, EntryPoint = "AndroidCryptoNative_Aes256Cfb128")] + internal static extern IntPtr EvpAes256Cfb128(); + + [DllImport(Libraries.CryptoNative, EntryPoint = "AndroidCryptoNative_Aes256Cfb8")] + internal static extern IntPtr EvpAes256Cfb8(); + + [DllImport(Libraries.CryptoNative, EntryPoint = "AndroidCryptoNative_Aes256Ccm")] + internal static extern IntPtr EvpAes256Ccm(); + + [DllImport(Libraries.CryptoNative, EntryPoint = "AndroidCryptoNative_DesCbc")] + internal static extern IntPtr EvpDesCbc(); + + [DllImport(Libraries.CryptoNative, EntryPoint = "AndroidCryptoNative_DesEcb")] + internal static extern IntPtr EvpDesEcb(); + + [DllImport(Libraries.CryptoNative, EntryPoint = "AndroidCryptoNative_DesCfb8")] + internal static extern IntPtr EvpDesCfb8(); + + [DllImport(Libraries.CryptoNative, EntryPoint = "AndroidCryptoNative_Des3Cbc")] + internal static extern IntPtr EvpDes3Cbc(); + + [DllImport(Libraries.CryptoNative, EntryPoint = "AndroidCryptoNative_Des3Ecb")] + internal static extern IntPtr EvpDes3Ecb(); + + [DllImport(Libraries.CryptoNative, EntryPoint = "AndroidCryptoNative_Des3Cfb8")] + internal static extern IntPtr EvpDes3Cfb8(); + + [DllImport(Libraries.CryptoNative, EntryPoint = "AndroidCryptoNative_Des3Cfb64")] + internal static extern IntPtr EvpDes3Cfb64(); + + [DllImport(Libraries.CryptoNative, EntryPoint = "AndroidCryptoNative_RC2Cbc")] + internal static extern IntPtr EvpRC2Cbc(); + + [DllImport(Libraries.CryptoNative, EntryPoint = "AndroidCryptoNative_RC2Ecb")] + internal static extern IntPtr EvpRC2Ecb(); + + internal enum EvpCipherDirection : int + { + NoChange = -1, + Decrypt = 0, + Encrypt = 1, + } + } +} 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 c3415e47e15d15..5e048592865363 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 @@ -8,7 +8,7 @@ set(NATIVECRYPTO_SOURCES pal_bignum.c pal_err.c pal_evp.c - pal_evp_cipher.c + pal_cipher.c pal_hmac.c pal_jni.c pal_lifetime.c diff --git a/src/libraries/Native/Unix/System.Security.Cryptography.Native.Android/pal_cipher.c b/src/libraries/Native/Unix/System.Security.Cryptography.Native.Android/pal_cipher.c new file mode 100644 index 00000000000000..d07b47d6d2647f --- /dev/null +++ b/src/libraries/Native/Unix/System.Security.Cryptography.Native.Android/pal_cipher.c @@ -0,0 +1,328 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#include "pal_cipher.h" +#include "pal_utilities.h" + +typedef struct CipherInfo +{ + bool isSupported; + bool hasTag; + int32_t width; + const char* name; +} CipherInfo; + +#define DEFINE_CIPHER(cipherId, width, javaName, hasTag) \ +CipherInfo* AndroidCryptoNative_ ## cipherId() \ +{ \ + static CipherInfo info = { true, hasTag, width, javaName }; \ + return &info; \ +} + +#define DEFINE_UNSUPPORTED_CIPHER(cipherId) \ +CipherInfo* AndroidCryptoNative_ ## cipherId() \ +{ \ + static CipherInfo info = { false, false, 0, NULL }; \ + return &info; \ +} + +DEFINE_CIPHER(Aes128Ecb, 128, "AES/ECB/NoPadding", false) +DEFINE_CIPHER(Aes128Cbc, 128, "AES/CBC/NoPadding", false) +DEFINE_CIPHER(Aes128Cfb8, 128, "AES/CFB/NoPadding", false) +DEFINE_CIPHER(Aes128Cfb128, 128, "AES/CFB128/NoPadding", false) +DEFINE_CIPHER(Aes128Gcm, 128, "AES/GCM/NoPadding", true) +DEFINE_CIPHER(Aes128Ccm, 128, "AES/CCM/NoPadding", true) +DEFINE_CIPHER(Aes192Ecb, 192, "AES/ECB/NoPadding", false) +DEFINE_CIPHER(Aes192Cbc, 192, "AES/CBC/NoPadding", false) +DEFINE_CIPHER(Aes192Cfb8, 192, "AES/CFB/NoPadding", false) +DEFINE_CIPHER(Aes192Cfb128, 192, "AES/CFB128/NoPadding", false) +DEFINE_CIPHER(Aes192Gcm, 192, "AES/GCM/NoPadding", true) +DEFINE_CIPHER(Aes192Ccm, 192, "AES/CCM/NoPadding", true) +DEFINE_CIPHER(Aes256Ecb, 256, "AES/ECB/NoPadding", false) +DEFINE_CIPHER(Aes256Cbc, 256, "AES/CBC/NoPadding", false) +DEFINE_CIPHER(Aes256Cfb8, 256, "AES/CFB/NoPadding", false) +DEFINE_CIPHER(Aes256Cfb128, 256, "AES/CFB128/NoPadding", false) +DEFINE_CIPHER(Aes256Gcm, 256, "AES/GCM/NoPadding", true) +DEFINE_CIPHER(Aes256Ccm, 256, "AES/CCM/NoPadding", true) +DEFINE_CIPHER(DesEcb, 56, "DES/ECB/NoPadding", false) +DEFINE_CIPHER(DesCbc, 56, "DES/CBC/NoPadding", false) +DEFINE_CIPHER(DesCfb8, 56, "DES/CFB/NoPadding", false) +DEFINE_CIPHER(Des3Ecb, 168, "DESede/ECB/NoPadding", false) +DEFINE_CIPHER(Des3Cbc, 168, "DESede/CBC/NoPadding", false) +DEFINE_CIPHER(Des3Cfb8, 168, "DESede/CFB/NoPadding", false) +DEFINE_CIPHER(Des3Cfb64, 168, "DESede/CFB/NoPadding", false) +DEFINE_UNSUPPORTED_CIPHER(RC2Ecb) +DEFINE_UNSUPPORTED_CIPHER(RC2Cbc) + + +static int32_t GetAlgorithmWidth(CipherInfo* type) +{ + if (!type->isSupported) + { + assert(false); + return FAIL; + } + return type->width; +} + +static jobject GetAlgorithmName(JNIEnv* env, CipherInfo* type) +{ + if (!type->isSupported) + { + LOG_ERROR("This cipher is not supported"); + assert(false); + return FAIL; + } + return JSTRING(type->name); +} + +static bool HasTag(CipherInfo* type) +{ + return type->hasTag; +} + +CipherCtx* AndroidCryptoNative_CipherCreatePartial(CipherInfo* type) +{ + JNIEnv* env = GetJNIEnv(); + jobject algName = GetAlgorithmName(env, type); + if (!algName) + return FAIL; + + jobject cipher = ToGRef(env, (*env)->CallStaticObjectMethod(env, g_cipherClass, g_cipherGetInstanceMethod, algName)); + (*env)->DeleteLocalRef(env, algName); + + if (CheckJNIExceptions(env)) + { + return FAIL; + } + + CipherCtx* ctx = malloc(sizeof(CipherCtx)); + ctx->cipher = cipher; + ctx->type = type; + ctx->tagLength = TAG_MAX_LENGTH; + ctx->ivLength = 0; + ctx->encMode = 0; + ctx->key = NULL; + ctx->iv = NULL; + return ctx; +} + +int32_t AndroidCryptoNative_CipherSetTagLength(CipherCtx* ctx, int32_t tagLength) +{ + if (!ctx) + return FAIL; + + if(tagLength > TAG_MAX_LENGTH) + return FAIL; + + ctx->tagLength = tagLength; + return SUCCESS; +} + +static int32_t ReinitializeCipher(CipherCtx* ctx) +{ + JNIEnv* env = GetJNIEnv(); + + int32_t keyLength = GetAlgorithmWidth(ctx->type); + + // int ivSize = cipher.getBlockSize(); + // SecretKeySpec keySpec = new SecretKeySpec(key.getEncoded(), "AES"); + // IvParameterSpec ivSpec = new IvParameterSpec(IV); or GCMParameterSpec for GCM/CCM + // cipher.init(encMode, keySpec, ivSpec); + + jobject algName = GetAlgorithmName(env, ctx->type); + if (!algName) + return FAIL; + + if (!ctx->ivLength) + ctx->ivLength = (*env)->CallIntMethod(env, ctx->cipher, g_getBlockSizeMethod); + + jbyteArray keyBytes = (*env)->NewByteArray(env, keyLength / 8); // bits to bytes, e.g. 256 -> 32 + (*env)->SetByteArrayRegion(env, keyBytes, 0, keyLength / 8, (jbyte*)ctx->key); + jbyteArray ivBytes = (*env)->NewByteArray(env, ctx->ivLength); + (*env)->SetByteArrayRegion(env, ivBytes, 0, ctx->ivLength, (jbyte*)ctx->iv); + + jobject sksObj = (*env)->NewObject(env, g_sksClass, g_sksCtor, keyBytes, algName); + jobject ivPsObj; + + if (HasTag(ctx->type)) + ivPsObj = (*env)->NewObject(env, g_GCMParameterSpecClass, g_GCMParameterSpecCtor, ctx->tagLength * 8, ivBytes); + else + ivPsObj = (*env)->NewObject(env, g_ivPsClass, g_ivPsCtor, ivBytes); + + (*env)->CallVoidMethod(env, ctx->cipher, g_cipherInitMethod, ctx->encMode, sksObj, ivPsObj); + (*env)->DeleteLocalRef(env, algName); + (*env)->DeleteLocalRef(env, sksObj); + (*env)->DeleteLocalRef(env, ivPsObj); + (*env)->DeleteLocalRef(env, keyBytes); + (*env)->DeleteLocalRef(env, ivBytes); + + return CheckJNIExceptions(env) ? FAIL : SUCCESS; +} + +int32_t AndroidCryptoNative_CipherSetKeyAndIV(CipherCtx* ctx, uint8_t* key, uint8_t* iv, int32_t enc) +{ + if (!ctx) + return FAIL; + + int32_t keyLength = GetAlgorithmWidth(ctx->type); + + // input: 0 for Decrypt, 1 for Encrypt, -1 leave untouched + // Cipher: 2 for Decrypt, 1 for Encrypt, N/A + if (enc != -1) + { + assert(enc == 0 || enc == 1); + ctx->encMode = enc == 0 ? CIPHER_DECRYPT_MODE : CIPHER_ENCRYPT_MODE; + } + + // CryptoNative_CipherSetKeyAndIV can be called separately for key and iv + // so we need to wait for both and do Init after. + if (key) + SaveTo(key, &ctx->key, (size_t)keyLength, /* overwrite */ true); + if (iv) + SaveTo(iv, &ctx->iv, (size_t)ctx->ivLength, /* overwrite */ true); + + if (!ctx->key || !ctx->iv) + return SUCCESS; + + return ReinitializeCipher(ctx); +} + +CipherCtx* AndroidCryptoNative_CipherCreate(CipherInfo* type, uint8_t* key, int32_t keyLength, int32_t effectiveKeyLength, uint8_t* iv, int32_t enc) +{ + if (effectiveKeyLength != 0) + { + LOG_ERROR("Non-zero effectiveKeyLength is not supported"); + return FAIL; + } + + CipherCtx* ctx = AndroidCryptoNative_CipherCreatePartial(type); + if (AndroidCryptoNative_CipherSetKeyAndIV(ctx, key, iv, enc) != SUCCESS) + return FAIL; + + if (keyLength != GetAlgorithmWidth(type)) + { + LOG_ERROR("Key length must match algorithm width."); + return FAIL; + } + return ctx; +} + +int32_t AndroidCryptoNative_CipherUpdateAAD(CipherCtx* ctx, uint8_t* in, int32_t inl) +{ + if (!ctx) + return FAIL; + + JNIEnv* env = GetJNIEnv(); + jbyteArray inDataBytes = (*env)->NewByteArray(env, inl); + (*env)->SetByteArrayRegion(env, inDataBytes, 0, inl, (jbyte*)in); + (*env)->CallVoidMethod(env, ctx->cipher, g_cipherUpdateAADMethod, inDataBytes); + (*env)->DeleteLocalRef(env, inDataBytes); + return CheckJNIExceptions(env) ? FAIL : SUCCESS; +} + +int32_t AndroidCryptoNative_CipherUpdate(CipherCtx* ctx, uint8_t* outm, int32_t* outl, uint8_t* in, int32_t inl) +{ + if (!ctx) + return FAIL; + + if (!outl && !in) + // it means caller wants us to record "inl" but we don't need it. + return SUCCESS; + + JNIEnv* env = GetJNIEnv(); + jbyteArray inDataBytes = (*env)->NewByteArray(env, inl); + (*env)->SetByteArrayRegion(env, inDataBytes, 0, inl, (jbyte*)in); + + *outl = 0; + jbyteArray outDataBytes = (jbyteArray)(*env)->CallObjectMethod(env, ctx->cipher, g_cipherUpdateMethod, inDataBytes); + + if (outDataBytes && outm) + { + jsize outDataBytesLen = (*env)->GetArrayLength(env, outDataBytes); + *outl = outDataBytesLen; + (*env)->GetByteArrayRegion(env, outDataBytes, 0, outDataBytesLen, (jbyte*) outm); + (*env)->DeleteLocalRef(env, outDataBytes); + } + + (*env)->DeleteLocalRef(env, inDataBytes); + return CheckJNIExceptions(env) ? FAIL : SUCCESS; +} + +int32_t AndroidCryptoNative_CipherFinalEx(CipherCtx* ctx, uint8_t* outm, int32_t* outl) +{ + if (!ctx) + return FAIL; + + JNIEnv* env = GetJNIEnv(); + + *outl = 0; + + jbyteArray outBytes = (jbyteArray)(*env)->CallObjectMethod(env, ctx->cipher, g_cipherDoFinalMethod); + if (CheckJNIExceptions(env)) return FAIL; + + jsize outBytesLen = (*env)->GetArrayLength(env, outBytes); + *outl = outBytesLen; + (*env)->GetByteArrayRegion(env, outBytes, 0, outBytesLen, (jbyte*) outm); + + (*env)->DeleteLocalRef(env, outBytes); + return CheckJNIExceptions(env) ? FAIL : SUCCESS; +} + +int32_t AndroidCryptoNative_CipherCtxSetPadding(CipherCtx* ctx, int32_t padding) +{ + if (!ctx) + return FAIL; + + if (padding == 0) + { + return SUCCESS; + } + else + { + // TODO: re-init ctx->cipher ? + LOG_ERROR("Non-zero padding (%d) is not supported yet", (int)padding); + return FAIL; + } +} + +int32_t AndroidCryptoNative_CipherReset(CipherCtx* ctx) +{ + if (!ctx) + return FAIL; + + free(ctx->iv); + ctx->iv = NULL; + ctx->ivLength = 0; + + JNIEnv* env = GetJNIEnv(); + ReleaseGRef(env, ctx->cipher); + jobject algName = GetAlgorithmName(env, ctx->type); + if (!algName) + return FAIL; + + ctx->cipher = ToGRef(env, (*env)->CallStaticObjectMethod(env, g_cipherClass, g_cipherGetInstanceMethod, algName)); + (*env)->DeleteLocalRef(env, algName); + return CheckJNIExceptions(env) ? FAIL : SUCCESS; +} + +int32_t AndroidCryptoNative_CipherSetNonceLength(CipherCtx* ctx, int32_t ivLength) +{ + if (!ctx) + return FAIL; + + ctx->ivLength = ivLength; + return SUCCESS; +} + +void AndroidCryptoNative_CipherDestroy(CipherCtx* ctx) +{ + if (ctx) + { + JNIEnv* env = GetJNIEnv(); + ReleaseGRef(env, ctx->cipher); + free(ctx->key); + free(ctx->iv); + free(ctx); + } +} diff --git a/src/libraries/Native/Unix/System.Security.Cryptography.Native.Android/pal_cipher.h b/src/libraries/Native/Unix/System.Security.Cryptography.Native.Android/pal_cipher.h new file mode 100644 index 00000000000000..bd7963a770f362 --- /dev/null +++ b/src/libraries/Native/Unix/System.Security.Cryptography.Native.Android/pal_cipher.h @@ -0,0 +1,63 @@ +// 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 TAG_MAX_LENGTH 16 + +#define CIPHER_ENCRYPT_MODE 1 +#define CIPHER_DECRYPT_MODE 2 + +typedef struct CipherInfo CipherInfo; + +typedef struct CipherCtx +{ + jobject cipher; + CipherInfo* type; + int32_t ivLength; + int32_t tagLength; + int32_t encMode; + uint8_t* key; + uint8_t* iv; +} CipherCtx; + +PALEXPORT CipherCtx* AndroidCryptoNative_CipherCreate(CipherInfo* type, uint8_t* key, int32_t keyLength, int32_t effectiveKeyLength, uint8_t* iv, int32_t enc); +PALEXPORT CipherCtx* AndroidCryptoNative_CipherCreatePartial(CipherInfo* type); +PALEXPORT int32_t AndroidCryptoNative_CipherSetTagLength(CipherCtx* ctx, int32_t tagLength); +PALEXPORT int32_t AndroidCryptoNative_CipherSetKeyAndIV(CipherCtx* ctx, uint8_t* key, uint8_t* iv, int32_t enc); +PALEXPORT int32_t AndroidCryptoNative_CipherSetNonceLength(CipherCtx* ctx, int32_t ivLength); +PALEXPORT void AndroidCryptoNative_CipherDestroy(CipherCtx* ctx); +PALEXPORT int32_t AndroidCryptoNative_CipherReset(CipherCtx* ctx); +PALEXPORT int32_t AndroidCryptoNative_CipherCtxSetPadding(CipherCtx* ctx, int32_t padding); +PALEXPORT int32_t AndroidCryptoNative_CipherUpdateAAD(CipherCtx* ctx, uint8_t* in, int32_t inl); +PALEXPORT int32_t AndroidCryptoNative_CipherUpdate(CipherCtx* ctx, uint8_t* out, int32_t* outl, uint8_t* in, int32_t inl); +PALEXPORT int32_t AndroidCryptoNative_CipherFinalEx(CipherCtx* ctx, uint8_t* outm, int32_t* outl); +PALEXPORT CipherInfo* AndroidCryptoNative_Aes128Ecb(void); +PALEXPORT CipherInfo* AndroidCryptoNative_Aes128Cbc(void); +PALEXPORT CipherInfo* AndroidCryptoNative_Aes128Cfb8(void); +PALEXPORT CipherInfo* AndroidCryptoNative_Aes128Cfb128(void); +PALEXPORT CipherInfo* AndroidCryptoNative_Aes128Gcm(void); +PALEXPORT CipherInfo* AndroidCryptoNative_Aes128Ccm(void); +PALEXPORT CipherInfo* AndroidCryptoNative_Aes192Ecb(void); +PALEXPORT CipherInfo* AndroidCryptoNative_Aes192Cbc(void); +PALEXPORT CipherInfo* AndroidCryptoNative_Aes192Cfb8(void); +PALEXPORT CipherInfo* AndroidCryptoNative_Aes192Cfb128(void); +PALEXPORT CipherInfo* AndroidCryptoNative_Aes192Gcm(void); +PALEXPORT CipherInfo* AndroidCryptoNative_Aes192Ccm(void); +PALEXPORT CipherInfo* AndroidCryptoNative_Aes256Ecb(void); +PALEXPORT CipherInfo* AndroidCryptoNative_Aes256Cbc(void); +PALEXPORT CipherInfo* AndroidCryptoNative_Aes256Cfb8(void); +PALEXPORT CipherInfo* AndroidCryptoNative_Aes256Cfb128(void); +PALEXPORT CipherInfo* AndroidCryptoNative_Aes256Gcm(void); +PALEXPORT CipherInfo* AndroidCryptoNative_Aes256Ccm(void); +PALEXPORT CipherInfo* AndroidCryptoNative_Des3Ecb(void); +PALEXPORT CipherInfo* AndroidCryptoNative_Des3Cbc(void); +PALEXPORT CipherInfo* AndroidCryptoNative_Des3Cfb8(void); +PALEXPORT CipherInfo* AndroidCryptoNative_Des3Cfb64(void); +PALEXPORT CipherInfo* AndroidCryptoNative_DesEcb(void); +PALEXPORT CipherInfo* AndroidCryptoNative_DesCfb8(void); +PALEXPORT CipherInfo* AndroidCryptoNative_DesCbc(void); +PALEXPORT CipherInfo* AndroidCryptoNative_RC2Ecb(void); +PALEXPORT CipherInfo* AndroidCryptoNative_RC2Cbc(void); diff --git a/src/libraries/Native/Unix/System.Security.Cryptography.Native.Android/pal_evp_cipher.c b/src/libraries/Native/Unix/System.Security.Cryptography.Native.Android/pal_evp_cipher.c deleted file mode 100644 index 9551335f9ec906..00000000000000 --- a/src/libraries/Native/Unix/System.Security.Cryptography.Native.Android/pal_evp_cipher.c +++ /dev/null @@ -1,392 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -#include "pal_evp_cipher.h" - -// just some unique IDs -intptr_t CryptoNative_EvpAes128Ecb() { return 1001; } -intptr_t CryptoNative_EvpAes128Cbc() { return 1002; } -intptr_t CryptoNative_EvpAes128Cfb8() { return 1003; } -intptr_t CryptoNative_EvpAes128Cfb128() { return 1004; } -intptr_t CryptoNative_EvpAes128Gcm() { return 1005; } -intptr_t CryptoNative_EvpAes128Ccm() { return 1006; } - -intptr_t CryptoNative_EvpAes192Ecb() { return 1007; } -intptr_t CryptoNative_EvpAes192Cbc() { return 1008; } -intptr_t CryptoNative_EvpAes192Cfb8() { return 1009; } -intptr_t CryptoNative_EvpAes192Cfb128() { return 1010; } -intptr_t CryptoNative_EvpAes192Gcm() { return 1011; } -intptr_t CryptoNative_EvpAes192Ccm() { return 1012; } - -intptr_t CryptoNative_EvpAes256Ecb() { return 1013; } -intptr_t CryptoNative_EvpAes256Cbc() { return 1014; } -intptr_t CryptoNative_EvpAes256Cfb8() { return 1015; } -intptr_t CryptoNative_EvpAes256Cfb128() { return 1016; } -intptr_t CryptoNative_EvpAes256Gcm() { return 1017; } -intptr_t CryptoNative_EvpAes256Ccm() { return 1018; } - -intptr_t CryptoNative_EvpDes3Ecb() { return 1019; } -intptr_t CryptoNative_EvpDes3Cbc() { return 1020; } -intptr_t CryptoNative_EvpDes3Cfb8() { return 1021; } -intptr_t CryptoNative_EvpDes3Cfb64() { return 1022; } - -intptr_t CryptoNative_EvpDesEcb() { return 1023; } -intptr_t CryptoNative_EvpDesCfb8() { return 1024; } -intptr_t CryptoNative_EvpDesCbc() { return 1025; } - -intptr_t CryptoNative_EvpRC2Ecb() { return 1026; } -intptr_t CryptoNative_EvpRC2Cbc() { return 1027; } - -static int32_t GetAlgorithmWidth(intptr_t type) -{ - if (type == CryptoNative_EvpAes128Ecb()) return 128; - if (type == CryptoNative_EvpAes128Cbc()) return 128; - if (type == CryptoNative_EvpAes128Gcm()) return 128; - if (type == CryptoNative_EvpAes128Ccm()) return 128; - if (type == CryptoNative_EvpAes128Cfb8()) return 128; - if (type == CryptoNative_EvpAes128Cfb128()) return 128; - - if (type == CryptoNative_EvpAes192Ecb()) return 192; - if (type == CryptoNative_EvpAes192Cbc()) return 192; - if (type == CryptoNative_EvpAes192Gcm()) return 192; - if (type == CryptoNative_EvpAes192Ccm()) return 192; - if (type == CryptoNative_EvpAes192Cfb8()) return 192; - if (type == CryptoNative_EvpAes192Cfb128()) return 192; - - if (type == CryptoNative_EvpAes256Ecb()) return 256; - if (type == CryptoNative_EvpAes256Cbc()) return 256; - if (type == CryptoNative_EvpAes256Gcm()) return 256; - if (type == CryptoNative_EvpAes256Ccm()) return 256; - if (type == CryptoNative_EvpAes256Cfb8()) return 256; - if (type == CryptoNative_EvpAes256Cfb128()) return 256; - - if (type == CryptoNative_EvpDesEcb()) return 56; - if (type == CryptoNative_EvpDesCfb8()) return 56; - if (type == CryptoNative_EvpDesCbc()) return 56; - - if (type == CryptoNative_EvpDes3Ecb()) return 168; - if (type == CryptoNative_EvpDes3Cbc()) return 168; - if (type == CryptoNative_EvpDes3Cfb8()) return 168; - if (type == CryptoNative_EvpDes3Cfb64()) return 168; - - assert(0 && "unexpected type"); - return FAIL; -} - -static jobject GetAlgorithmName(JNIEnv* env, intptr_t type) -{ - if (type == CryptoNative_EvpAes128Ecb()) return JSTRING("AES/ECB/NoPadding"); - if (type == CryptoNative_EvpAes128Cbc()) return JSTRING("AES/CBC/NoPadding"); - if (type == CryptoNative_EvpAes128Gcm()) return JSTRING("AES/GCM/NoPadding"); - if (type == CryptoNative_EvpAes128Ccm()) return JSTRING("AES/CCM/NoPadding"); - if (type == CryptoNative_EvpAes128Cfb8()) return JSTRING("AES/CFB/NoPadding"); - - if (type == CryptoNative_EvpAes192Ecb()) return JSTRING("AES/ECB/NoPadding"); - if (type == CryptoNative_EvpAes192Cbc()) return JSTRING("AES/CBC/NoPadding"); - if (type == CryptoNative_EvpAes192Gcm()) return JSTRING("AES/GCM/NoPadding"); - if (type == CryptoNative_EvpAes192Ccm()) return JSTRING("AES/CCM/NoPadding"); - if (type == CryptoNative_EvpAes192Cfb8()) return JSTRING("AES/CFB/NoPadding"); - - if (type == CryptoNative_EvpAes256Ecb()) return JSTRING("AES/ECB/NoPadding"); - if (type == CryptoNative_EvpAes256Cbc()) return JSTRING("AES/CBC/NoPadding"); - if (type == CryptoNative_EvpAes256Gcm()) return JSTRING("AES/GCM/NoPadding"); - if (type == CryptoNative_EvpAes256Ccm()) return JSTRING("AES/CCM/NoPadding"); - if (type == CryptoNative_EvpAes256Cfb8()) return JSTRING("AES/CFB/NoPadding"); - - if (type == CryptoNative_EvpDesEcb()) return JSTRING("DES/ECB/NoPadding"); - if (type == CryptoNative_EvpDesCfb8()) return JSTRING("DES/CFB/NoPadding"); - if (type == CryptoNative_EvpDesCbc()) return JSTRING("DES/CBC/NoPadding"); - - if (type == CryptoNative_EvpDes3Ecb()) return JSTRING("DESede/ECB/NoPadding"); - if (type == CryptoNative_EvpDes3Cbc()) return JSTRING("DESede/CBC/NoPadding"); - if (type == CryptoNative_EvpDes3Cfb8()) return JSTRING("DESede/CFB/NoPadding"); - if (type == CryptoNative_EvpDes3Cfb64()) return JSTRING("DESede/CFB/NoPadding"); - - // custom feedback size is not supported yet: - //if (type == CryptoNative_EvpAes128Cfb128()) return JSTRING("AES/CFB/NoPadding"); - //if (type == CryptoNative_EvpAes192Cfb128()) return JSTRING("AES/CFB/NoPadding"); - //if (type == CryptoNative_EvpAes256Cfb128()) return JSTRING("AES/CFB/NoPadding"); - - LOG_ERROR("This algorithm (%ld) is not supported", (long)type); - return FAIL; -} - -static bool HasTag(intptr_t type) -{ - return (type == CryptoNative_EvpAes128Gcm()) || - (type == CryptoNative_EvpAes128Ccm()) || - (type == CryptoNative_EvpAes192Gcm()) || - (type == CryptoNative_EvpAes192Ccm()) || - (type == CryptoNative_EvpAes256Gcm()) || - (type == CryptoNative_EvpAes256Ccm()); -} - -CipherCtx* CryptoNative_EvpCipherCreatePartial(intptr_t type) -{ - JNIEnv* env = GetJNIEnv(); - jobject algName = GetAlgorithmName(env, type); - if (!algName) - return FAIL; - - jobject cipher = ToGRef(env, (*env)->CallStaticObjectMethod(env, g_cipherClass, g_cipherGetInstanceMethod, algName)); - (*env)->DeleteLocalRef(env, algName); - - CipherCtx* ctx = malloc(sizeof(CipherCtx)); - ctx->cipher = cipher; - ctx->type = type; - ctx->ivLength = 0; - ctx->encMode = 0; - ctx->key = NULL; - ctx->iv = NULL; - memset(ctx->tag, 0, TAG_MAX_LENGTH); - return CheckJNIExceptions(env) ? FAIL : ctx; -} - -int32_t CryptoNative_EvpCipherSetKeyAndIV(CipherCtx* ctx, uint8_t* key, uint8_t* iv, int32_t enc) -{ - if (!ctx) - return FAIL; - - int32_t keyLength = GetAlgorithmWidth(ctx->type); - - // CryptoNative_EvpCipherSetKeyAndIV can be called separately for key and iv - // so we need to wait for both and do Init after. - if (key && !ctx->key) - SaveTo(key, &ctx->key, (size_t)keyLength, /* overwrite */ true); - if (iv && !ctx->iv) - SaveTo(iv, &ctx->iv, (size_t)ctx->ivLength, /* overwrite */ true); - - if (!ctx->key || !ctx->iv) - return SUCCESS; - - // input: 0 for Decrypt, 1 for Encrypt, -1 leave untouched - // Cipher: 2 for Decrypt, 1 for Encrypt, N/A - if (enc != -1) - { - assert(enc == 0 || enc == 1); - ctx->encMode = enc == 0 ? CIPHER_DECRYPT_MODE : CIPHER_ENCRYPT_MODE; - } - - JNIEnv* env = GetJNIEnv(); - - // int ivSize = cipher.getBlockSize(); - // SecretKeySpec keySpec = new SecretKeySpec(key.getEncoded(), "AES"); - // IvParameterSpec ivSpec = new IvParameterSpec(IV); or GCMParameterSpec for GCM/CCM - // cipher.init(encMode, keySpec, ivSpec); - - jobject algName = GetAlgorithmName(env, ctx->type); - if (!algName) - return FAIL; - - if (!ctx->ivLength) - ctx->ivLength = (*env)->CallIntMethod(env, ctx->cipher, g_getBlockSizeMethod); - - jbyteArray keyBytes = (*env)->NewByteArray(env, keyLength / 8); // bits to bytes, e.g. 256 -> 32 - (*env)->SetByteArrayRegion(env, keyBytes, 0, keyLength / 8, (jbyte*)ctx->key); - jbyteArray ivBytes = (*env)->NewByteArray(env, ctx->ivLength); - (*env)->SetByteArrayRegion(env, ivBytes, 0, ctx->ivLength, (jbyte*)ctx->iv); - - jobject sksObj = (*env)->NewObject(env, g_sksClass, g_sksCtor, keyBytes, algName); - jobject ivPsObj; - - if (HasTag(ctx->type)) - ivPsObj = (*env)->NewObject(env, g_GCMParameterSpecClass, g_GCMParameterSpecCtor, TAG_MAX_LENGTH * 8, ivBytes); - else - ivPsObj = (*env)->NewObject(env, g_ivPsClass, g_ivPsCtor, ivBytes); - - (*env)->CallVoidMethod(env, ctx->cipher, g_cipherInitMethod, ctx->encMode, sksObj, ivPsObj); - (*env)->DeleteLocalRef(env, algName); - (*env)->DeleteLocalRef(env, sksObj); - (*env)->DeleteLocalRef(env, ivPsObj); - (*env)->DeleteLocalRef(env, keyBytes); - (*env)->DeleteLocalRef(env, ivBytes); - - return CheckJNIExceptions(env) ? FAIL : SUCCESS; -} - -CipherCtx* CryptoNative_EvpCipherCreate2(intptr_t type, uint8_t* key, int32_t keyLength, int32_t effectiveKeyLength, uint8_t* iv, int32_t enc) -{ - if (effectiveKeyLength != 0) - { - LOG_ERROR("Non-zero effectiveKeyLength is not supported"); - return FAIL; - } - - CipherCtx* ctx = CryptoNative_EvpCipherCreatePartial(type); - if (CryptoNative_EvpCipherSetKeyAndIV(ctx, key, iv, enc) != SUCCESS) - return FAIL; - - if (keyLength != GetAlgorithmWidth(type)) - { - LOG_ERROR("Key length must match algorithm width."); - return FAIL; - } - return ctx; -} - -int32_t CryptoNative_EvpCipherUpdate(CipherCtx* ctx, uint8_t* outm, int32_t* outl, uint8_t* in, int32_t inl) -{ - if (!ctx) - return FAIL; - - if (!outl && !in) - // it means caller wants us to record "inl" but we don't need it. - return SUCCESS; - - if (!in) - return FAIL; - - JNIEnv* env = GetJNIEnv(); - jbyteArray inDataBytes = (*env)->NewByteArray(env, inl); - (*env)->SetByteArrayRegion(env, inDataBytes, 0, inl, (jbyte*)in); - - // it's AAD if outm is null and it's GCM/CCM - if (HasTag(ctx->type) && !outm) - { - (*env)->CallVoidMethod(env, ctx->cipher, g_cipherUpdateAADMethod, inDataBytes); - } - else - { - jbyteArray outDataBytes = (jbyteArray)(*env)->CallObjectMethod(env, ctx->cipher, g_cipherUpdateMethod, inDataBytes); - if (outDataBytes && outm) { - jsize outDataBytesLen = (*env)->GetArrayLength(env, outDataBytes); - *outl = (int32_t)outDataBytesLen; - (*env)->GetByteArrayRegion(env, outDataBytes, 0, outDataBytesLen, (jbyte*) outm); - (*env)->DeleteLocalRef(env, outDataBytes); - } else { - *outl = 0; - } - } - - (*env)->DeleteLocalRef(env, inDataBytes); - return CheckJNIExceptions(env) ? FAIL : SUCCESS; -} - -int32_t CryptoNative_EvpCipherFinalEx(CipherCtx* ctx, uint8_t* outm, int32_t* outl) -{ - if (!ctx) - return FAIL; - - JNIEnv* env = GetJNIEnv(); - - // NOTE: Cipher appends TAG to the end of outBytes in case of CCM/GCM and "encryption" mode - bool hasTag = HasTag(ctx->type); - bool decrypt = ctx->encMode == CIPHER_DECRYPT_MODE; - int tagLength = (hasTag && !decrypt) ? TAG_MAX_LENGTH : 0; - - jbyteArray outBytes = (jbyteArray)(*env)->CallObjectMethod(env, ctx->cipher, g_cipherDoFinalMethod); - if (CheckJNIExceptions(env)) return FAIL; - - jsize outBytesLen = (*env)->GetArrayLength(env, outBytes); - - if (outBytesLen > tagLength) - { - (*env)->GetByteArrayRegion(env, outBytes, 0, outBytesLen - tagLength, (jbyte*) outm); - *outl = outBytesLen - tagLength; - - if (hasTag && !decrypt) - (*env)->GetByteArrayRegion(env, outBytes, outBytesLen - TAG_MAX_LENGTH, TAG_MAX_LENGTH, (jbyte*) ctx->tag); - } - else - { - *outl = 0; - } - - (*env)->DeleteLocalRef(env, outBytes); - return CheckJNIExceptions(env) ? FAIL : SUCCESS; -} - -int32_t CryptoNative_EvpCipherCtxSetPadding(CipherCtx* ctx, int32_t padding) -{ - if (!ctx) - return FAIL; - - if (padding == 0) - { - return SUCCESS; - } - else - { - // TODO: re-init ctx->cipher ? - LOG_ERROR("Non-zero padding (%d) is not supported yet", (int)padding); - return FAIL; - } -} - -int32_t CryptoNative_EvpCipherReset(CipherCtx* ctx) -{ - // TODO: re-init ctx->cipher ? - LOG_ERROR("EvpCipherReset is no-op."); - - if (!ctx) - return FAIL; - - return SUCCESS; -} - -int32_t CryptoNative_EvpCipherSetGcmNonceLength(CipherCtx* ctx, int32_t ivLength) -{ - if (!ctx) - return FAIL; - - ctx->ivLength = ivLength; - return SUCCESS; -} - -int32_t CryptoNative_EvpCipherSetCcmNonceLength(CipherCtx* ctx, int32_t ivLength) -{ - return CryptoNative_EvpCipherSetGcmNonceLength(ctx, ivLength); -} - -int32_t CryptoNative_EvpCipherGetGcmTag(CipherCtx* ctx, uint8_t* tag, int32_t tagLength) -{ - if (!ctx) - return FAIL; - - assert(tagLength <= TAG_MAX_LENGTH); - - // Just return what we extracted during CryptoNative_EvpCipherFinalEx - memcpy(tag, ctx->tag, (size_t)tagLength); - return SUCCESS; -} - -int32_t CryptoNative_EvpCipherGetCcmTag(CipherCtx* ctx, uint8_t* tag, int32_t tagLength) -{ - return CryptoNative_EvpCipherGetGcmTag(ctx, tag, tagLength); -} - -int32_t CryptoNative_EvpCipherSetGcmTag(CipherCtx* ctx, uint8_t* tag, int32_t tagLength) -{ - if (!ctx) - return FAIL; - - if (!tag) - return FAIL; - - assert(tagLength <= TAG_MAX_LENGTH); - - // Tag is provided using regular "cipher.update(tag)" - JNIEnv* env = GetJNIEnv(); - jbyteArray inDataBytes = (*env)->NewByteArray(env, tagLength); - (*env)->SetByteArrayRegion(env, inDataBytes, 0, tagLength, (jbyte*)tag); - jbyteArray outDataBytes = (jbyteArray)(*env)->CallObjectMethod(env, ctx->cipher, g_cipherUpdateMethod, inDataBytes); - (*env)->DeleteLocalRef(env, outDataBytes); - (*env)->DeleteLocalRef(env, inDataBytes); - return CheckJNIExceptions(env) ? FAIL : SUCCESS; -} - -int32_t CryptoNative_EvpCipherSetCcmTag(CipherCtx* ctx, uint8_t* tag, int32_t tagLength) -{ - return CryptoNative_EvpCipherSetGcmTag(ctx, tag, tagLength); -} - -void CryptoNative_EvpCipherDestroy(CipherCtx* ctx) -{ - if (ctx) - { - ReleaseGRef(GetJNIEnv(), ctx->cipher); - free(ctx->key); - free(ctx->iv); - free(ctx); - } -} diff --git a/src/libraries/Native/Unix/System.Security.Cryptography.Native.Android/pal_evp_cipher.h b/src/libraries/Native/Unix/System.Security.Cryptography.Native.Android/pal_evp_cipher.h deleted file mode 100644 index 27dbb07da2290f..00000000000000 --- a/src/libraries/Native/Unix/System.Security.Cryptography.Native.Android/pal_evp_cipher.h +++ /dev/null @@ -1,66 +0,0 @@ -// 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" -#include "pal_evp.h" - - -#define TAG_MAX_LENGTH 16 - -#define CIPHER_ENCRYPT_MODE 1 -#define CIPHER_DECRYPT_MODE 2 - -typedef struct CipherCtx -{ - jobject cipher; - intptr_t type; - int32_t ivLength; - int32_t encMode; - uint8_t* key; - uint8_t* iv; - uint8_t* tag[TAG_MAX_LENGTH]; -} CipherCtx; - -PALEXPORT CipherCtx* CryptoNative_EvpCipherCreate2(intptr_t type, uint8_t* key, int32_t keyLength, int32_t effectiveKeyLength, uint8_t* iv, int32_t enc); -PALEXPORT CipherCtx* CryptoNative_EvpCipherCreatePartial(intptr_t type); -PALEXPORT int32_t CryptoNative_EvpCipherSetKeyAndIV(CipherCtx* ctx, uint8_t* key, uint8_t* iv, int32_t enc); -PALEXPORT int32_t CryptoNative_EvpCipherSetGcmNonceLength(CipherCtx* ctx, int32_t ivLength); -PALEXPORT int32_t CryptoNative_EvpCipherSetCcmNonceLength(CipherCtx* ctx, int32_t ivLength); -PALEXPORT void CryptoNative_EvpCipherDestroy(CipherCtx* ctx); -PALEXPORT int32_t CryptoNative_EvpCipherReset(CipherCtx* ctx); -PALEXPORT int32_t CryptoNative_EvpCipherCtxSetPadding(CipherCtx* ctx, int32_t padding); -PALEXPORT int32_t CryptoNative_EvpCipherUpdate(CipherCtx* ctx, uint8_t* out, int32_t* outl, uint8_t* in, int32_t inl); -PALEXPORT int32_t CryptoNative_EvpCipherFinalEx(CipherCtx* ctx, uint8_t* outm, int32_t* outl); -PALEXPORT int32_t CryptoNative_EvpCipherGetGcmTag(CipherCtx* ctx, uint8_t* tag, int32_t tagLength); -PALEXPORT int32_t CryptoNative_EvpCipherSetGcmTag(CipherCtx* ctx, uint8_t* tag, int32_t tagLength); -PALEXPORT int32_t CryptoNative_EvpCipherGetCcmTag(CipherCtx* ctx, uint8_t* tag, int32_t tagLength); -PALEXPORT int32_t CryptoNative_EvpCipherSetCcmTag(CipherCtx* ctx, uint8_t* tag, int32_t tagLength); -PALEXPORT intptr_t CryptoNative_EvpAes128Ecb(void); -PALEXPORT intptr_t CryptoNative_EvpAes128Cbc(void); -PALEXPORT intptr_t CryptoNative_EvpAes128Cfb8(void); -PALEXPORT intptr_t CryptoNative_EvpAes128Cfb128(void); -PALEXPORT intptr_t CryptoNative_EvpAes128Gcm(void); -PALEXPORT intptr_t CryptoNative_EvpAes128Ccm(void); -PALEXPORT intptr_t CryptoNative_EvpAes192Ecb(void); -PALEXPORT intptr_t CryptoNative_EvpAes192Cbc(void); -PALEXPORT intptr_t CryptoNative_EvpAes192Cfb8(void); -PALEXPORT intptr_t CryptoNative_EvpAes192Cfb128(void); -PALEXPORT intptr_t CryptoNative_EvpAes192Gcm(void); -PALEXPORT intptr_t CryptoNative_EvpAes192Ccm(void); -PALEXPORT intptr_t CryptoNative_EvpAes256Ecb(void); -PALEXPORT intptr_t CryptoNative_EvpAes256Cbc(void); -PALEXPORT intptr_t CryptoNative_EvpAes256Cfb8(void); -PALEXPORT intptr_t CryptoNative_EvpAes256Cfb128(void); -PALEXPORT intptr_t CryptoNative_EvpAes256Gcm(void); -PALEXPORT intptr_t CryptoNative_EvpAes256Ccm(void); -PALEXPORT intptr_t CryptoNative_EvpDes3Ecb(void); -PALEXPORT intptr_t CryptoNative_EvpDes3Cbc(void); -PALEXPORT intptr_t CryptoNative_EvpDes3Cfb8(void); -PALEXPORT intptr_t CryptoNative_EvpDes3Cfb64(void); -PALEXPORT intptr_t CryptoNative_EvpDesEcb(void); -PALEXPORT intptr_t CryptoNative_EvpDesCfb8(void); -PALEXPORT intptr_t CryptoNative_EvpDesCbc(void); -PALEXPORT intptr_t CryptoNative_EvpRC2Ecb(void); -PALEXPORT intptr_t CryptoNative_EvpRC2Cbc(void); 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 ece334bcfdba14..550f30bf4a0346 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 @@ -11,6 +11,9 @@ SR.SystemSecurityCryptographyAlgorithms_PlatformNotSupported ExcludeApiList.PNSE.Browser.txt + + true @@ -583,8 +586,6 @@ - + + + + + + + + diff --git a/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/AesCcm.Android.cs b/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/AesCcm.Android.cs new file mode 100644 index 00000000000000..d6c5b9726dbaa9 --- /dev/null +++ b/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/AesCcm.Android.cs @@ -0,0 +1,176 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using Microsoft.Win32.SafeHandles; + +namespace System.Security.Cryptography +{ + public sealed partial class AesCcm + { + private byte[] _key; + + [MemberNotNull(nameof(_key))] + private void ImportKey(ReadOnlySpan key) + { + _key = key.ToArray(); + } + + private void EncryptInternal( + ReadOnlySpan nonce, + ReadOnlySpan plaintext, + Span ciphertext, + Span tag, + ReadOnlySpan associatedData = default) + { + // Convert key length to bits. + using (SafeEvpCipherCtxHandle ctx = Interop.Crypto.EvpCipherCreatePartial(GetCipher(_key.Length * 8))) + { + if (ctx.IsInvalid) + { + throw new CryptographicException(); + } + + if (!Interop.Crypto.CipherSetTagLength(ctx, tag.Length)) + { + throw new CryptographicException(); + } + + Interop.Crypto.CipherSetNonceLength(ctx, nonce.Length); + Interop.Crypto.EvpCipherSetKeyAndIV(ctx, _key, nonce, Interop.Crypto.EvpCipherDirection.Encrypt); + + if (associatedData.Length != 0) + { + Interop.Crypto.CipherUpdateAAD(ctx, associatedData); + } + + byte[]? rented = null; + try + { + Span ciphertextAndTag = stackalloc byte[0]; + // Arbitrary limit. + const int StackAllocMax = 128; + if (checked(ciphertext.Length + tag.Length) <= StackAllocMax) + { + ciphertextAndTag = stackalloc byte[ciphertext.Length + tag.Length]; + } + else + { + rented = CryptoPool.Rent(ciphertext.Length + tag.Length); + ciphertextAndTag = new Span(rented, 0, ciphertext.Length + tag.Length); + } + + if (!Interop.Crypto.EvpCipherUpdate(ctx, ciphertextAndTag, out int ciphertextBytesWritten, plaintext)) + { + throw new CryptographicException(); + } + + if (!Interop.Crypto.EvpCipherFinalEx( + ctx, + ciphertextAndTag.Slice(ciphertextBytesWritten), + out int bytesWritten)) + { + throw new CryptographicException(); + } + + ciphertextBytesWritten += bytesWritten; + + // NOTE: Android appends tag to the end of the ciphertext in case of CCM/GCM and "encryption" mode + + if (ciphertextBytesWritten != ciphertextAndTag.Length) + { + Debug.Fail($"CCM encrypt wrote {ciphertextBytesWritten} of {ciphertextAndTag.Length} bytes."); + throw new CryptographicException(); + } + + ciphertextAndTag[..ciphertext.Length].CopyTo(ciphertext); + ciphertextAndTag[ciphertext.Length..].CopyTo(tag); + } + finally + { + if (rented != null) + { + CryptoPool.Return(rented, ciphertext.Length + tag.Length); + } + } + } + } + + private void DecryptInternal( + ReadOnlySpan nonce, + ReadOnlySpan ciphertext, + ReadOnlySpan tag, + Span plaintext, + ReadOnlySpan associatedData) + { + using (SafeEvpCipherCtxHandle ctx = Interop.Crypto.EvpCipherCreatePartial(GetCipher(_key.Length * 8))) + { + if (ctx.IsInvalid) + { + throw new CryptographicException(); + } + Interop.Crypto.CipherSetNonceLength(ctx, nonce.Length); + + if (!Interop.Crypto.CipherSetTagLength(ctx, tag.Length)) + { + throw new CryptographicException(); + } + + Interop.Crypto.EvpCipherSetKeyAndIV(ctx, _key, nonce, Interop.Crypto.EvpCipherDirection.Decrypt); + + if (associatedData.Length != 0) + { + Interop.Crypto.CipherUpdateAAD(ctx, associatedData); + } + + if (!Interop.Crypto.EvpCipherUpdate(ctx, plaintext, out int plaintextBytesWritten, ciphertext)) + { + CryptographicOperations.ZeroMemory(plaintext); + throw new CryptographicException(); + } + + if (!Interop.Crypto.EvpCipherUpdate(ctx, plaintext.Slice(plaintextBytesWritten), out int bytesWritten, tag)) + { + CryptographicOperations.ZeroMemory(plaintext); + throw new CryptographicException(); + } + + plaintextBytesWritten += bytesWritten; + + if (!Interop.Crypto.EvpCipherFinalEx( + ctx, + plaintext.Slice(plaintextBytesWritten), + out bytesWritten)) + { + CryptographicOperations.ZeroMemory(plaintext); + throw new CryptographicException(SR.Cryptography_AuthTagMismatch); + } + + plaintextBytesWritten += bytesWritten; + + if (plaintextBytesWritten != plaintext.Length) + { + Debug.Fail($"CCM decrypt wrote {plaintextBytesWritten} of {plaintext.Length} bytes."); + throw new CryptographicException(); + } + } + } + + private static IntPtr GetCipher(int keySizeInBits) + { + return keySizeInBits switch + { + 128 => Interop.Crypto.EvpAes128Ccm(), + 192 => Interop.Crypto.EvpAes192Ccm(), + 256 => Interop.Crypto.EvpAes256Ccm(), + _ => IntPtr.Zero + }; + } + + public void Dispose() + { + CryptographicOperations.ZeroMemory(_key); + } + } +} diff --git a/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/AesGcm.Android.cs b/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/AesGcm.Android.cs new file mode 100644 index 00000000000000..fd6b898db67c7f --- /dev/null +++ b/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/AesGcm.Android.cs @@ -0,0 +1,175 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using Microsoft.Win32.SafeHandles; + +namespace System.Security.Cryptography +{ + public sealed partial class AesGcm + { + private SafeEvpCipherCtxHandle _ctxHandle; + + [MemberNotNull(nameof(_ctxHandle))] + private void ImportKey(ReadOnlySpan key) + { + // Convert key length to bits. + _ctxHandle = Interop.Crypto.EvpCipherCreatePartial(GetCipher(key.Length * 8)); + + Interop.Crypto.CheckValidOpenSslHandle(_ctxHandle); + Interop.Crypto.EvpCipherSetKeyAndIV( + _ctxHandle, + key, + Span.Empty, + Interop.Crypto.EvpCipherDirection.NoChange); + Interop.Crypto.CipherSetNonceLength(_ctxHandle, NonceSize); + } + + private void EncryptInternal( + ReadOnlySpan nonce, + ReadOnlySpan plaintext, + Span ciphertext, + Span tag, + ReadOnlySpan associatedData = default) + { + + if (!Interop.Crypto.CipherSetTagLength(_ctxHandle, tag.Length)) + { + throw new CryptographicException(); + } + + Interop.Crypto.EvpCipherSetKeyAndIV( + _ctxHandle, + Span.Empty, + nonce, + Interop.Crypto.EvpCipherDirection.Encrypt); + + if (associatedData.Length != 0) + { + Interop.Crypto.CipherUpdateAAD(_ctxHandle, associatedData); + } + + byte[]? rented = null; + try + { + Span ciphertextAndTag = stackalloc byte[0]; + // Arbitrary limit. + const int StackAllocMax = 128; + if (checked(ciphertext.Length + tag.Length) <= StackAllocMax) + { + ciphertextAndTag = stackalloc byte[ciphertext.Length + tag.Length]; + } + else + { + rented = CryptoPool.Rent(ciphertext.Length + tag.Length); + ciphertextAndTag = new Span(rented, 0, ciphertext.Length + tag.Length); + } + + if (!Interop.Crypto.EvpCipherUpdate(_ctxHandle, ciphertextAndTag, out int ciphertextBytesWritten, plaintext)) + { + throw new CryptographicException(); + } + + if (!Interop.Crypto.EvpCipherFinalEx( + _ctxHandle, + ciphertextAndTag.Slice(ciphertextBytesWritten), + out int bytesWritten)) + { + throw new CryptographicException(); + } + + ciphertextBytesWritten += bytesWritten; + + // NOTE: Android appends tag to the end of the ciphertext in case of CCM/GCM and "encryption" mode + + if (ciphertextBytesWritten != ciphertextAndTag.Length) + { + Debug.Fail($"GCM encrypt wrote {ciphertextBytesWritten} of {ciphertextAndTag.Length} bytes."); + throw new CryptographicException(); + } + + ciphertextAndTag[..ciphertext.Length].CopyTo(ciphertext); + ciphertextAndTag[ciphertext.Length..].CopyTo(tag); + } + finally + { + if (rented != null) + { + CryptoPool.Return(rented, ciphertext.Length + tag.Length); + } + } + } + + private void DecryptInternal( + ReadOnlySpan nonce, + ReadOnlySpan ciphertext, + ReadOnlySpan tag, + Span plaintext, + ReadOnlySpan associatedData) + { + if (!Interop.Crypto.CipherSetTagLength(_ctxHandle, tag.Length)) + { + throw new CryptographicException(); + } + + Interop.Crypto.EvpCipherSetKeyAndIV( + _ctxHandle, + ReadOnlySpan.Empty, + nonce, + Interop.Crypto.EvpCipherDirection.Decrypt); + + if (associatedData.Length != 0) + { + Interop.Crypto.CipherUpdateAAD(_ctxHandle, associatedData); + } + + if (!Interop.Crypto.EvpCipherUpdate(_ctxHandle, plaintext, out int plaintextBytesWritten, ciphertext)) + { + CryptographicOperations.ZeroMemory(plaintext); + throw new CryptographicException(); + } + + if (!Interop.Crypto.EvpCipherUpdate(_ctxHandle, plaintext.Slice(plaintextBytesWritten), out int bytesWritten, tag)) + { + CryptographicOperations.ZeroMemory(plaintext); + throw new CryptographicException(); + } + + plaintextBytesWritten += bytesWritten; + + if (!Interop.Crypto.EvpCipherFinalEx( + _ctxHandle, + plaintext.Slice(plaintextBytesWritten), + out bytesWritten)) + { + CryptographicOperations.ZeroMemory(plaintext); + throw new CryptographicException(SR.Cryptography_AuthTagMismatch); + } + + plaintextBytesWritten += bytesWritten; + + if (plaintextBytesWritten != plaintext.Length) + { + Debug.Fail($"GCM decrypt wrote {plaintextBytesWritten} of {plaintext.Length} bytes."); + throw new CryptographicException(); + } + } + + private static IntPtr GetCipher(int keySizeInBits) + { + return keySizeInBits switch + { + 128 => Interop.Crypto.EvpAes128Gcm(), + 192 => Interop.Crypto.EvpAes192Gcm(), + 256 => Interop.Crypto.EvpAes256Gcm(), + _ => IntPtr.Zero + }; + } + + public void Dispose() + { + _ctxHandle.Dispose(); + } + } +} diff --git a/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/AesGcm.Unix.cs b/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/AesGcm.Unix.cs index ca45b0be50b1c1..d050604168a0e5 100644 --- a/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/AesGcm.Unix.cs +++ b/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/AesGcm.Unix.cs @@ -111,7 +111,7 @@ private void DecryptInternal( if (plaintextBytesWritten != plaintext.Length) { - // Debug.Fail($"GCM decrypt wrote {plaintextBytesWritten} of {plaintext.Length} bytes."); + Debug.Fail($"GCM decrypt wrote {plaintextBytesWritten} of {plaintext.Length} bytes."); throw new CryptographicException(); } }