enum
{
CIPHER_NONE = 0,
- CIPHER_HAS_TAG = 1,
+ CIPHER_HAS_VARIABLE_TAG = 1,
CIPHER_REQUIRES_IV = 2,
};
typedef uint32_t CipherFlags;
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
// 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)
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 TryClearJNIExceptions(env) ? FAIL : SUCCESS;
+}
+
CipherCtx* AndroidCryptoNative_CipherCreatePartial(CipherInfo* type)
{
abort_if_invalid_pointer_argument (type);
{
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);
}
--- /dev/null
+// 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();
+ }
+ }
+}