Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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 @@ -7,7 +7,7 @@
enum
{
CIPHER_NONE = 0,
CIPHER_HAS_TAG = 1,
CIPHER_HAS_VARIABLE_TAG = 1,
CIPHER_REQUIRES_IV = 2,
};
typedef uint32_t CipherFlags;
Expand All @@ -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_VARIABLE_TAG | CIPHER_REQUIRES_IV)
DEFINE_CIPHER(Aes128Ccm, 128, "AES/CCM/NoPadding", CIPHER_HAS_VARIABLE_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_VARIABLE_TAG | CIPHER_REQUIRES_IV)
DEFINE_CIPHER(Aes192Ccm, 192, "AES/CCM/NoPadding", CIPHER_HAS_VARIABLE_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_VARIABLE_TAG | CIPHER_REQUIRES_IV)
DEFINE_CIPHER(Aes256Ccm, 256, "AES/CCM/NoPadding", CIPHER_HAS_VARIABLE_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_REQUIRES_IV)

//
// We don't have to check whether `CipherInfo` arguments are valid pointers, as these functions will be called after the
Expand All @@ -60,9 +61,9 @@ DEFINE_CIPHER(Des3Cfb64, 128, "DESede/CFB/NoPadding", CIPHER_REQUIRES_IV)
// The entry functions (those that can be called by external code) take care to validate that the context passed to them
// is a valid pointer and so we can assume the assertion from the preceding paragraph.
//
ARGS_NON_NULL_ALL static bool HasTag(CipherInfo* type)
ARGS_NON_NULL_ALL static bool HasVariableTag(CipherInfo* type)
{
return (type->flags & CIPHER_HAS_TAG) == CIPHER_HAS_TAG;
return (type->flags & CIPHER_HAS_VARIABLE_TAG) == CIPHER_HAS_VARIABLE_TAG;
}

ARGS_NON_NULL_ALL static bool RequiresIV(CipherInfo* type)
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);
Comment thread
vcsjones marked this conversation as resolved.
(*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 TryClearJNIExceptions(env) ? FAIL : SUCCESS;
}

CipherCtx* AndroidCryptoNative_CipherCreatePartial(CipherInfo* type)
{
abort_if_invalid_pointer_argument (type);
Expand Down Expand Up @@ -138,7 +157,8 @@ 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))

if (HasVariableTag(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);
}
Comment thread
bartonjs marked this conversation as resolved.

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 @@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.

using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using Test.Cryptography;
using Xunit;
Expand Down Expand Up @@ -447,6 +448,11 @@ 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)
{
// Android with API Level 28 is the minimum API Level support for ChaChaPoly1305.
expectedIsSupported = GetAndroidSdkVersion() >= 28;
Comment thread
vcsjones marked this conversation as resolved.
}
else if (PlatformDetection.OpenSslPresentOnSystem &&
(PlatformDetection.IsOSX || PlatformDetection.IsOpenSslSupported))
{
Expand All @@ -456,5 +462,22 @@ public static void CheckIsSupported()

Assert.Equal(expectedIsSupported, ChaCha20Poly1305.IsSupported);
}

private static int GetAndroidSdkVersion()
Comment thread
vcsjones marked this conversation as resolved.
{
using Process proc = new Process();
proc.StartInfo.FileName = "getprop";
proc.StartInfo.Arguments = " ro.build.version.sdk";
proc.StartInfo.UseShellExecute = false;
proc.StartInfo.RedirectStandardOutput = true;
proc.Start();
string stdout = proc.StandardOutput.ReadToEnd();

// This should never take more than a second.
int sdkVersion = -1;
bool success = proc.WaitForExit(5_000) && int.TryParse(stdout, out sdkVersion);
Assert.True(success, "Could not determine Android SDK version for current device.");
return sdkVersion;
}
}
}