Skip to content
Open
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
@@ -0,0 +1,221 @@
// 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;

internal static partial class Interop
{
internal static partial class AndroidCrypto
{
[LibraryImport(Libraries.AndroidCryptoNative, EntryPoint = "AndroidCryptoNative_X25519IsSupported")]
[return: MarshalAs(UnmanagedType.Bool)]
internal static partial bool X25519IsSupported();

[LibraryImport(Libraries.AndroidCryptoNative, EntryPoint = "AndroidCryptoNative_X25519DestroyKey")]
internal static partial void X25519DestroyKey(IntPtr key);

[LibraryImport(Libraries.AndroidCryptoNative, EntryPoint = "AndroidCryptoNative_X25519GenerateKey")]
private static partial int X25519GenerateKeyNative(
out SafeX25519PublicKeyHandle publicKey,
out SafeX25519PrivateKeyHandle privateKey);

internal static void X25519GenerateKey(
out SafeX25519PublicKeyHandle publicKey,
out SafeX25519PrivateKeyHandle privateKey)
{
const int Success = 1;

int result = X25519GenerateKeyNative(out publicKey, out privateKey);

if (result != Success)
{
publicKey.Dispose();
privateKey.Dispose();
throw new CryptographicException();
}
}

[LibraryImport(Libraries.AndroidCryptoNative, EntryPoint = "AndroidCryptoNative_X25519ExportSubjectPublicKeyInfo")]
private static partial int X25519ExportSubjectPublicKeyInfoNative(
SafeX25519PublicKeyHandle publicKey,
Span<byte> buffer,
int bufferLength,
out int bytesWritten);

internal static bool X25519TryExportSubjectPublicKeyInfo(
Comment thread
bartonjs marked this conversation as resolved.
SafeX25519PublicKeyHandle publicKey,
Span<byte> buffer,
out int bytesWritten)
{
const int Success = 1;
const int InsufficientBuffer = -1;

int result = X25519ExportSubjectPublicKeyInfoNative(
publicKey,
buffer,
buffer.Length,
out bytesWritten);

return result switch
{
Success => true,
InsufficientBuffer => false,
_ => throw new CryptographicException(),
};
}

[LibraryImport(Libraries.AndroidCryptoNative, EntryPoint = "AndroidCryptoNative_X25519ExportPkcs8PrivateKey")]
private static partial int X25519ExportPkcs8PrivateKeyNative(
SafeX25519PrivateKeyHandle privateKey,
Span<byte> buffer,
int bufferLength,
out int bytesWritten);

internal static bool X25519TryExportPkcs8PrivateKey(
SafeX25519PrivateKeyHandle privateKey,
Span<byte> buffer,
out int bytesWritten)
{
const int Success = 1;
const int InsufficientBuffer = -1;

int result = X25519ExportPkcs8PrivateKeyNative(
privateKey,
buffer,
buffer.Length,
out bytesWritten);

return result switch
{
Success => true,
InsufficientBuffer => false,
_ => throw new CryptographicException(),
};
}

[LibraryImport(Libraries.AndroidCryptoNative, EntryPoint = "AndroidCryptoNative_X25519ImportSubjectPublicKeyInfo")]
private static partial SafeX25519PublicKeyHandle X25519ImportSubjectPublicKeyInfoNative(
ReadOnlySpan<byte> buffer,
int bufferLength);

internal static SafeX25519PublicKeyHandle X25519ImportSubjectPublicKeyInfo(ReadOnlySpan<byte> spki)
{
SafeX25519PublicKeyHandle handle = X25519ImportSubjectPublicKeyInfoNative(spki, spki.Length);

if (handle.IsInvalid)
{
handle.Dispose();
throw new CryptographicException(SR.Cryptography_NotValidPublicOrPrivateKey);
}

return handle;
}

[LibraryImport(Libraries.AndroidCryptoNative, EntryPoint = "AndroidCryptoNative_X25519ImportPkcs8PrivateKey")]
private static partial SafeX25519PrivateKeyHandle X25519ImportPkcs8PrivateKeyNative(
ReadOnlySpan<byte> buffer,
int bufferLength);

internal static SafeX25519PrivateKeyHandle X25519ImportPkcs8PrivateKey(ReadOnlySpan<byte> pkcs8)
{
SafeX25519PrivateKeyHandle handle = X25519ImportPkcs8PrivateKeyNative(pkcs8, pkcs8.Length);

if (handle.IsInvalid)
{
handle.Dispose();
throw new CryptographicException(SR.Cryptography_NotValidPublicOrPrivateKey);
}

return handle;
}

[LibraryImport(Libraries.AndroidCryptoNative, EntryPoint = "AndroidCryptoNative_X25519DeriveSecret")]
private static partial int X25519DeriveSecretNative(
SafeX25519PrivateKeyHandle privateKey,
SafeX25519PublicKeyHandle publicKey,
Span<byte> destination,
int destinationLength);

internal static void X25519DeriveSecret(
SafeX25519PrivateKeyHandle privateKey,
SafeX25519PublicKeyHandle publicKey,
Span<byte> destination)
{
const int Success = 1;

int result = X25519DeriveSecretNative(privateKey, publicKey, destination, destination.Length);

if (result != Success)
{
throw new CryptographicException();
}
}

[LibraryImport(Libraries.AndroidCryptoNative, EntryPoint = "AndroidCryptoNative_X25519DeriveSecretWithSubjectPublicKeyInfo")]
private static partial int X25519DeriveSecretWithSubjectPublicKeyInfoNative(
SafeX25519PrivateKeyHandle privateKey,
ReadOnlySpan<byte> subjectPublicKeyInfo,
int subjectPublicKeyInfoLength,
Span<byte> destination,
int destinationLength);

internal static void X25519DeriveSecretWithSubjectPublicKeyInfo(
SafeX25519PrivateKeyHandle privateKey,
ReadOnlySpan<byte> subjectPublicKeyInfo,
Span<byte> destination)
{
const int Success = 1;

int result = X25519DeriveSecretWithSubjectPublicKeyInfoNative(
privateKey,
subjectPublicKeyInfo,
subjectPublicKeyInfo.Length,
destination,
destination.Length);

if (result != Success)
{
throw new CryptographicException();
}
}
}
}

namespace System.Security.Cryptography
{
internal sealed class SafeX25519PublicKeyHandle : SafeHandle
{
public SafeX25519PublicKeyHandle()
: base(IntPtr.Zero, ownsHandle: true)
{
}

public override bool IsInvalid => handle == IntPtr.Zero;

protected override bool ReleaseHandle()
{
Interop.AndroidCrypto.X25519DestroyKey(handle);
SetHandle(IntPtr.Zero);
return true;
}
}

internal sealed class SafeX25519PrivateKeyHandle : SafeHandle
{
public SafeX25519PrivateKeyHandle()
: base(IntPtr.Zero, ownsHandle: true)
{
}

public override bool IsInvalid => handle == IntPtr.Zero;

protected override bool ReleaseHandle()
{
Interop.AndroidCrypto.X25519DestroyKey(handle);
SetHandle(IntPtr.Zero);
return true;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,8 @@ internal static void ReadSubjectPublicKeyInfo<TRet>(
internal static ReadOnlySpan<byte> ReadSubjectPublicKeyInfo(
string[] validOids,
ReadOnlySpan<byte> source,
out int bytesRead)
out int bytesRead,
bool permitParameters = true)
{
ValueSubjectPublicKeyInfoAsn spki;
int read;
Expand All @@ -70,7 +71,8 @@ internal static ReadOnlySpan<byte> ReadSubjectPublicKeyInfo(
throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding, e);
}

if (Array.IndexOf(validOids, spki.Algorithm.Algorithm) < 0)
if (Array.IndexOf(validOids, spki.Algorithm.Algorithm) < 0 ||
(!permitParameters && spki.Algorithm.HasParameters))
{
throw new CryptographicException(SR.Cryptography_NotValidPublicOrPrivateKey);
}
Expand Down Expand Up @@ -130,15 +132,17 @@ internal static void ReadPkcs8<TRet, TState>(
internal static ReadOnlySpan<byte> ReadPkcs8(
string[] validOids,
ReadOnlySpan<byte> source,
out int bytesRead)
out int bytesRead,
bool permitParameters = true)
{
try
{
ValueAsnReader reader = new ValueAsnReader(source, AsnEncodingRules.BER);
int read = reader.PeekEncodedValue().Length;
ValuePrivateKeyInfoAsn.Decode(ref reader, out ValuePrivateKeyInfoAsn privateKeyInfo);

if (Array.IndexOf(validOids, privateKeyInfo.PrivateKeyAlgorithm.Algorithm) < 0)
if (Array.IndexOf(validOids, privateKeyInfo.PrivateKeyAlgorithm.Algorithm) < 0 ||
(!permitParameters && privateKeyInfo.PrivateKeyAlgorithm.HasParameters))
{
throw new CryptographicException(SR.Cryptography_NotValidPublicOrPrivateKey);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1208,6 +1208,8 @@
Link="Common\Interop\Android\System.Security.Cryptography.Native.Android\Interop.Random.cs" />
<Compile Include="$(CommonPath)Interop\Android\System.Security.Cryptography.Native.Android\Interop.Rsa.cs"
Link="Common\Interop\Android\System.Security.Cryptography.Native.Android\Interop.Rsa.cs" />
<Compile Include="$(CommonPath)Interop\Android\System.Security.Cryptography.Native.Android\Interop.X25519.cs"
Link="Common\Interop\Android\System.Security.Cryptography.Native.Android\Interop.X25519.cs" />
<Compile Include="$(CommonPath)Interop\Android\System.Security.Cryptography.Native.Android\Interop.X509.cs"
Link="Common\Interop\Android\System.Security.Cryptography.Native.Android\Interop.X509.cs" />
<Compile Include="$(CommonPath)Interop\Android\System.Security.Cryptography.Native.Android\Interop.X509Chain.cs"
Expand Down Expand Up @@ -1302,7 +1304,7 @@
<Compile Include="System\Security\Cryptography\X509Certificates\ManagedCertificateFinder.cs" />
<Compile Include="System\Security\Cryptography\Shake128.NonWindows.cs" />
<Compile Include="System\Security\Cryptography\Shake256.NonWindows.cs" />
<Compile Include="System\Security\Cryptography\X25519DiffieHellmanImplementation.NotSupported.cs" />
<Compile Include="System\Security\Cryptography\X25519DiffieHellmanImplementation.Android.cs" />
<Compile Include="System\Security\Cryptography\X25519DiffieHellmanOpenSsl.NotSupported.cs" />
<Compile Include="System\Security\Cryptography\X509Certificates\StorePal.Android.cs" />
<Compile Include="System\Security\Cryptography\X509Certificates\StorePal.Android.AndroidKeyStore.cs" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ namespace System.Security.Cryptography
/// </remarks>
public abstract class X25519DiffieHellman : IDisposable
{
private static readonly string[] s_knownOids = [Oids.X25519];
private protected static readonly string[] s_knownOids = [Oids.X25519];

private bool _disposed;

Expand All @@ -41,8 +41,11 @@ public abstract class X25519DiffieHellman : IDisposable
/// </summary>
public const int PublicKeySizeInBytes = 32;

// Pre-encoded PKCS#8 for X25519 is 48 bytes: 16 byte preamble + 32 byte private key.
private protected const int Pkcs8SizeInBytes = 16 + PrivateKeySizeInBytes;

// Pre-encoded SPKI for X25519 is 44 bytes: 12 byte preamble + 32 byte public key.
private const int SpkiSizeInBytes = 12 + PublicKeySizeInBytes;
private protected const int SpkiSizeInBytes = 12 + PublicKeySizeInBytes;

/// <summary>
/// Gets a value that indicates whether the algorithm is supported on the current platform.
Expand Down Expand Up @@ -1427,7 +1430,12 @@ protected virtual void Dispose(bool disposing)
{
}

private bool TryExportSubjectPublicKeyInfoCore(Span<byte> destination, out int bytesWritten)
private protected static bool TryWriteSubjectPublicKeyInfo<TState>(
Span<byte> destination,
TState state,
Action<TState, Span<byte>> writer,
out int bytesWritten)
where TState : allows ref struct
{
// Pre-encoded SubjectPublicKeyInfo for X25519 (RFC 8410):
ReadOnlySpan<byte> spkiPreamble =
Expand All @@ -1446,11 +1454,20 @@ private bool TryExportSubjectPublicKeyInfoCore(Span<byte> destination, out int b
}

spkiPreamble.CopyTo(destination);
ExportPublicKeyCore(destination.Slice(spkiPreamble.Length, PublicKeySizeInBytes));
writer(state, destination.Slice(spkiPreamble.Length, PublicKeySizeInBytes));
bytesWritten = SpkiSizeInBytes;
return true;
}

private bool TryExportSubjectPublicKeyInfoCore(Span<byte> destination, out int bytesWritten)
{
return TryWriteSubjectPublicKeyInfo(
destination,
this,
static (self, buffer) => self.ExportPublicKeyCore(buffer),
out bytesWritten);
}

private TResult ExportPkcs8PrivateKeyCallback<TResult>(Func<ReadOnlySpan<byte>, TResult> func)
{
// A PKCS#8 X25519 PrivateKeyInfo has an ASN.1 overhead of 16 bytes, assuming no attributes.
Expand Down Expand Up @@ -1479,6 +1496,20 @@ private TResult ExportPkcs8PrivateKeyCallback<TResult>(Func<ReadOnlySpan<byte>,
}

private protected bool TryExportPkcs8PrivateKeyImpl(Span<byte> destination, out int bytesWritten)
{
return TryWritePkcs8PrivateKey(
destination,
this,
static (self, buffer) => self.ExportPrivateKeyCore(buffer),
out bytesWritten);
}

private protected static bool TryWritePkcs8PrivateKey<TState>(
Span<byte> destination,
TState state,
Action<TState, Span<byte>> writer,
out int bytesWritten)
where TState : allows ref struct
{
// Pre-encoded PKCS#8 PrivateKeyInfo for X25519 (RFC 8410):
ReadOnlySpan<byte> pkcs8Preamble =
Expand All @@ -1490,9 +1521,9 @@ private protected bool TryExportPkcs8PrivateKeyImpl(Span<byte> destination, out
0x04, 0x20, // OCTET STRING (32 bytes)
];

int pkcs8SizeInBytes = pkcs8Preamble.Length + PrivateKeySizeInBytes;
Debug.Assert(pkcs8Preamble.Length + PrivateKeySizeInBytes == Pkcs8SizeInBytes);

if (destination.Length < pkcs8SizeInBytes)
if (destination.Length < Pkcs8SizeInBytes)
{
bytesWritten = 0;
return false;
Expand All @@ -1503,8 +1534,8 @@ private protected bool TryExportPkcs8PrivateKeyImpl(Span<byte> destination, out

try
{
ExportPrivateKey(privateKeyBuffer);
bytesWritten = pkcs8SizeInBytes;
writer(state, privateKeyBuffer);
bytesWritten = Pkcs8SizeInBytes;
return true;
}
catch
Expand Down
Loading
Loading