Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,10 @@ internal static extern bool CipherSetTagLength(
SafeEvpCipherCtxHandle ctx,
int tagLength);

[DllImport(Libraries.AndroidCryptoNative, EntryPoint = "AndroidCryptoNative_CipherIsSupported")]
[return: MarshalAs(UnmanagedType.Bool)]
internal static extern bool CipherIsSupported(IntPtr cipher);

[DllImport(Libraries.AndroidCryptoNative, EntryPoint = "AndroidCryptoNative_Aes128Ecb")]
internal static extern IntPtr EvpAes128Ecb();

Expand Down Expand Up @@ -216,6 +220,9 @@ internal static extern bool CipherSetTagLength(
[DllImport(Libraries.AndroidCryptoNative, EntryPoint = "AndroidCryptoNative_RC2Ecb")]
internal static extern IntPtr EvpRC2Ecb();

[DllImport(Libraries.AndroidCryptoNative, EntryPoint = "AndroidCryptoNative_ChaCha20Poly1305")]
internal static extern IntPtr EvpChaCha20Poly1305();

internal enum EvpCipherDirection : int
{
NoChange = -1,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,31 +26,32 @@ CipherInfo* AndroidCryptoNative_ ## cipherId() \
return &info; \
}

DEFINE_CIPHER(Aes128Ecb, 128, "AES/ECB/NoPadding", CIPHER_NONE)
DEFINE_CIPHER(Aes128Cbc, 128, "AES/CBC/NoPadding", CIPHER_REQUIRES_IV)
DEFINE_CIPHER(Aes128Cfb8, 128, "AES/CFB8/NoPadding", CIPHER_REQUIRES_IV)
DEFINE_CIPHER(Aes128Cfb128, 128, "AES/CFB128/NoPadding", CIPHER_REQUIRES_IV)
DEFINE_CIPHER(Aes128Gcm, 128, "AES/GCM/NoPadding", CIPHER_HAS_TAG | CIPHER_REQUIRES_IV)
DEFINE_CIPHER(Aes128Ccm, 128, "AES/CCM/NoPadding", CIPHER_HAS_TAG | CIPHER_REQUIRES_IV)
DEFINE_CIPHER(Aes192Ecb, 192, "AES/ECB/NoPadding", CIPHER_NONE)
DEFINE_CIPHER(Aes192Cbc, 192, "AES/CBC/NoPadding", CIPHER_REQUIRES_IV)
DEFINE_CIPHER(Aes192Cfb8, 192, "AES/CFB8/NoPadding", CIPHER_REQUIRES_IV)
DEFINE_CIPHER(Aes192Cfb128, 192, "AES/CFB128/NoPadding", CIPHER_REQUIRES_IV)
DEFINE_CIPHER(Aes192Gcm, 192, "AES/GCM/NoPadding", CIPHER_HAS_TAG | CIPHER_REQUIRES_IV)
DEFINE_CIPHER(Aes192Ccm, 192, "AES/CCM/NoPadding", CIPHER_HAS_TAG | CIPHER_REQUIRES_IV)
DEFINE_CIPHER(Aes256Ecb, 256, "AES/ECB/NoPadding", CIPHER_NONE)
DEFINE_CIPHER(Aes256Cbc, 256, "AES/CBC/NoPadding", CIPHER_REQUIRES_IV)
DEFINE_CIPHER(Aes256Cfb8, 256, "AES/CFB8/NoPadding", CIPHER_REQUIRES_IV)
DEFINE_CIPHER(Aes256Cfb128, 256, "AES/CFB128/NoPadding", CIPHER_REQUIRES_IV)
DEFINE_CIPHER(Aes256Gcm, 256, "AES/GCM/NoPadding", CIPHER_HAS_TAG | CIPHER_REQUIRES_IV)
DEFINE_CIPHER(Aes256Ccm, 256, "AES/CCM/NoPadding", CIPHER_HAS_TAG | CIPHER_REQUIRES_IV)
DEFINE_CIPHER(DesEcb, 64, "DES/ECB/NoPadding", CIPHER_NONE)
DEFINE_CIPHER(DesCbc, 64, "DES/CBC/NoPadding", CIPHER_REQUIRES_IV)
DEFINE_CIPHER(DesCfb8, 64, "DES/CFB8/NoPadding", CIPHER_REQUIRES_IV)
DEFINE_CIPHER(Des3Ecb, 128, "DESede/ECB/NoPadding", CIPHER_NONE)
DEFINE_CIPHER(Des3Cbc, 128, "DESede/CBC/NoPadding", CIPHER_REQUIRES_IV)
DEFINE_CIPHER(Des3Cfb8, 128, "DESede/CFB8/NoPadding", CIPHER_REQUIRES_IV)
DEFINE_CIPHER(Des3Cfb64, 128, "DESede/CFB/NoPadding", CIPHER_REQUIRES_IV)
DEFINE_CIPHER(Aes128Ecb, 128, "AES/ECB/NoPadding", CIPHER_NONE)
DEFINE_CIPHER(Aes128Cbc, 128, "AES/CBC/NoPadding", CIPHER_REQUIRES_IV)
DEFINE_CIPHER(Aes128Cfb8, 128, "AES/CFB8/NoPadding", CIPHER_REQUIRES_IV)
DEFINE_CIPHER(Aes128Cfb128, 128, "AES/CFB128/NoPadding", CIPHER_REQUIRES_IV)
DEFINE_CIPHER(Aes128Gcm, 128, "AES/GCM/NoPadding", CIPHER_HAS_TAG | CIPHER_REQUIRES_IV)
DEFINE_CIPHER(Aes128Ccm, 128, "AES/CCM/NoPadding", CIPHER_HAS_TAG | CIPHER_REQUIRES_IV)
DEFINE_CIPHER(Aes192Ecb, 192, "AES/ECB/NoPadding", CIPHER_NONE)
DEFINE_CIPHER(Aes192Cbc, 192, "AES/CBC/NoPadding", CIPHER_REQUIRES_IV)
DEFINE_CIPHER(Aes192Cfb8, 192, "AES/CFB8/NoPadding", CIPHER_REQUIRES_IV)
DEFINE_CIPHER(Aes192Cfb128, 192, "AES/CFB128/NoPadding", CIPHER_REQUIRES_IV)
DEFINE_CIPHER(Aes192Gcm, 192, "AES/GCM/NoPadding", CIPHER_HAS_TAG | CIPHER_REQUIRES_IV)
DEFINE_CIPHER(Aes192Ccm, 192, "AES/CCM/NoPadding", CIPHER_HAS_TAG | CIPHER_REQUIRES_IV)
DEFINE_CIPHER(Aes256Ecb, 256, "AES/ECB/NoPadding", CIPHER_NONE)
DEFINE_CIPHER(Aes256Cbc, 256, "AES/CBC/NoPadding", CIPHER_REQUIRES_IV)
DEFINE_CIPHER(Aes256Cfb8, 256, "AES/CFB8/NoPadding", CIPHER_REQUIRES_IV)
DEFINE_CIPHER(Aes256Cfb128, 256, "AES/CFB128/NoPadding", CIPHER_REQUIRES_IV)
DEFINE_CIPHER(Aes256Gcm, 256, "AES/GCM/NoPadding", CIPHER_HAS_TAG | CIPHER_REQUIRES_IV)
DEFINE_CIPHER(Aes256Ccm, 256, "AES/CCM/NoPadding", CIPHER_HAS_TAG | CIPHER_REQUIRES_IV)
DEFINE_CIPHER(DesEcb, 64, "DES/ECB/NoPadding", CIPHER_NONE)
DEFINE_CIPHER(DesCbc, 64, "DES/CBC/NoPadding", CIPHER_REQUIRES_IV)
DEFINE_CIPHER(DesCfb8, 64, "DES/CFB8/NoPadding", CIPHER_REQUIRES_IV)
DEFINE_CIPHER(Des3Ecb, 128, "DESede/ECB/NoPadding", CIPHER_NONE)
DEFINE_CIPHER(Des3Cbc, 128, "DESede/CBC/NoPadding", CIPHER_REQUIRES_IV)
DEFINE_CIPHER(Des3Cfb8, 128, "DESede/CFB8/NoPadding", CIPHER_REQUIRES_IV)
DEFINE_CIPHER(Des3Cfb64, 128, "DESede/CFB/NoPadding", CIPHER_REQUIRES_IV)
DEFINE_CIPHER(ChaCha20Poly1305, 256, "ChaCha20/Poly1305/NoPadding", CIPHER_HAS_TAG | CIPHER_REQUIRES_IV)

//
// We don't have to check whether `CipherInfo` arguments are valid pointers, as these functions will be called after the
Expand All @@ -75,6 +76,24 @@ ARGS_NON_NULL_ALL static jobject GetAlgorithmName(JNIEnv* env, CipherInfo* type)
return make_java_string(env, type->name);
}

int32_t AndroidCryptoNative_CipherIsSupported(CipherInfo* type)
{
abort_if_invalid_pointer_argument (type);

JNIEnv* env = GetJNIEnv();
jobject algName = GetAlgorithmName(env, type);
if (!algName)
return FAIL;

jobject cipher = (*env)->CallStaticObjectMethod(env, g_cipherClass, g_cipherGetInstanceMethod, algName);
(*env)->DeleteLocalRef(env, algName);
(*env)->DeleteLocalRef(env, cipher);

// If we were able to call Cipher.getInstance without an exception, like NoSuchAlgorithmException,
// then the algorithm is supported.
return CheckJNIExceptions(env) ? FAIL : SUCCESS;
}

CipherCtx* AndroidCryptoNative_CipherCreatePartial(CipherInfo* type)
{
abort_if_invalid_pointer_argument (type);
Expand Down Expand Up @@ -138,7 +157,15 @@ ARGS_NON_NULL_ALL static int32_t ReinitializeCipher(CipherCtx* ctx)
{
jbyteArray ivBytes = make_java_byte_array(env, ctx->ivLength);
(*env)->SetByteArrayRegion(env, ivBytes, 0, ctx->ivLength, (jbyte*)ctx->iv);
if (HasTag(ctx->type))

// ChaChaPoly1305 does not allow configuration of the tag length and
// we don't permit user-defined counters, so we can use IvParameterSpec
// for ChaChaPoly1305.
if (strcmp("ChaCha20/Poly1305/NoPadding", ctx->type->name) == 0)
{
ivPsObj = (*env)->NewObject(env, g_ivPsClass, g_ivPsCtor, ivBytes);
}
else if (HasTag(ctx->type))
{
ivPsObj = (*env)->NewObject(env, g_GCMParameterSpecClass, g_GCMParameterSpecCtor, ctx->tagLength * 8, ivBytes);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ typedef struct CipherCtx
uint8_t* iv;
} CipherCtx;

PALEXPORT int32_t AndroidCryptoNative_CipherIsSupported(CipherInfo* type);
PALEXPORT CipherCtx* AndroidCryptoNative_CipherCreate(CipherInfo* type, uint8_t* key, int32_t keySizeInBits, 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);
Expand Down Expand Up @@ -60,3 +61,4 @@ PALEXPORT CipherInfo* AndroidCryptoNative_Des3Cfb64(void);
PALEXPORT CipherInfo* AndroidCryptoNative_DesEcb(void);
PALEXPORT CipherInfo* AndroidCryptoNative_DesCfb8(void);
PALEXPORT CipherInfo* AndroidCryptoNative_DesCbc(void);
PALEXPORT CipherInfo* AndroidCryptoNative_ChaCha20Poly1305(void);
Original file line number Diff line number Diff line change
Expand Up @@ -697,7 +697,7 @@
<Compile Include="Internal\Cryptography\RC2Implementation.Android.cs" />
<Compile Include="System\Security\Cryptography\AesCcm.Android.cs" />
<Compile Include="System\Security\Cryptography\AesGcm.Android.cs" />
<Compile Include="System\Security\Cryptography\ChaCha20Poly1305.NotSupported.cs" />
<Compile Include="System\Security\Cryptography\ChaCha20Poly1305.Android.cs" />
<Compile Include="System\Security\Cryptography\ECDiffieHellman.Create.Android.cs" />
<Compile Include="System\Security\Cryptography\ECDsa.Create.Android.cs" />
<Compile Include="System\Security\Cryptography\RSA.Create.Android.cs" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
// 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 ChaCha20Poly1305
{
private SafeEvpCipherCtxHandle _ctxHandle;

public static bool IsSupported { get; } = Interop.Crypto.CipherIsSupported(Interop.Crypto.EvpChaCha20Poly1305());

[MemberNotNull(nameof(_ctxHandle))]
private void ImportKey(ReadOnlySpan<byte> key)
{
// Constructors should check key size before calling ImportKey.
Debug.Assert(key.Length == KeySizeInBytes);
_ctxHandle = Interop.Crypto.EvpCipherCreatePartial(Interop.Crypto.EvpChaCha20Poly1305());

Interop.Crypto.CheckValidOpenSslHandle(_ctxHandle);
Interop.Crypto.EvpCipherSetKeyAndIV(
_ctxHandle,
key,
Span<byte>.Empty,
Interop.Crypto.EvpCipherDirection.NoChange);

Interop.Crypto.CipherSetNonceLength(_ctxHandle, NonceSizeInBytes);
}

private void EncryptCore(
ReadOnlySpan<byte> nonce,
ReadOnlySpan<byte> plaintext,
Span<byte> ciphertext,
Span<byte> tag,
ReadOnlySpan<byte> associatedData = default)
{

Interop.Crypto.EvpCipherSetKeyAndIV(
_ctxHandle,
Span<byte>.Empty,
nonce,
Interop.Crypto.EvpCipherDirection.Encrypt);

if (!associatedData.IsEmpty)
{
Interop.Crypto.CipherUpdateAAD(_ctxHandle, associatedData);
}

byte[]? rented = null;
int ciphertextAndTagLength = checked(ciphertext.Length + tag.Length);

try
{
// Arbitrary limit.
const int StackAllocMax = 128;
Span<byte> ciphertextAndTag = stackalloc byte[StackAllocMax];

if (ciphertextAndTagLength > StackAllocMax)
{
rented = CryptoPool.Rent(ciphertextAndTagLength);
ciphertextAndTag = rented;
}

ciphertextAndTag = ciphertextAndTag.Slice(0, ciphertextAndTagLength);

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 ChaCha20Poly1305 and "encryption" mode

if (ciphertextBytesWritten != ciphertextAndTagLength)
{
Debug.Fail($"ChaCha20Poly1305 encrypt wrote {ciphertextBytesWritten} of {ciphertextAndTagLength} bytes.");
throw new CryptographicException();
}

ciphertextAndTag.Slice(0, ciphertext.Length).CopyTo(ciphertext);
ciphertextAndTag.Slice(ciphertext.Length).CopyTo(tag);
}
finally
{
if (rented is not null)
{
CryptoPool.Return(rented, clearSize: ciphertextAndTagLength);
}
}
}

private void DecryptCore(
ReadOnlySpan<byte> nonce,
ReadOnlySpan<byte> ciphertext,
ReadOnlySpan<byte> tag,
Span<byte> plaintext,
ReadOnlySpan<byte> associatedData)
{
Interop.Crypto.EvpCipherSetKeyAndIV(
_ctxHandle,
ReadOnlySpan<byte>.Empty,
nonce,
Interop.Crypto.EvpCipherDirection.Decrypt);

if (!associatedData.IsEmpty)
{
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($"ChaCha20Poly1305 decrypt wrote {plaintextBytesWritten} of {plaintext.Length} bytes.");
throw new CryptographicException();
}
}

public void Dispose()
{
_ctxHandle.Dispose();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -447,6 +447,10 @@ public static void CheckIsSupported()
// The test queries the OS directly to ensure our version check is correct.
expectedIsSupported = CngUtility.IsAlgorithmSupported("CHACHA20_POLY1305");
}
else if (PlatformDetection.IsAndroid)
{
expectedIsSupported = true;
}
else if (PlatformDetection.OpenSslPresentOnSystem &&
(PlatformDetection.IsOSX || PlatformDetection.IsOpenSslSupported))
{
Expand Down