-
Notifications
You must be signed in to change notification settings - Fork 532
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Mono.Android] Add convenience KeyChain APIs #9047
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These makes a lot of sense to me. It's boilerplate code that was hard for me to figure out, and this short-circuits that and makes it more accessible in a standard .net way
Looks good, It might be nice to have some documentation on how to use these new methods? Maybe in the features area ? https://github.com/dotnet/android/tree/main/Documentation/docs-mobile/features |
I'm conflicted with this PR. While we make most bindings The one quasi-exception to this is android/src/Mono.Android/metadata Line 864 in b74d0fe
// <attr api-since="5" path="/api/package[@name='android.accounts']/interface[@name='AccountManagerFuture']/method[@name='getResult']" name="generateAsyncWrapper">true</attr>
namespace Android.Accounts {
public partial interface IAccountManagerFuture : IJavaObject, IJavaPeerable {
Java.Lang.Object? Result {
get;
}
}
// Emitted because of `generateAsyncWrapper`
public static partial class IAccountManagerFutureExtensions {
public static global::System.Threading.Tasks.Task<Java.Lang.Object?> GetResultAsync (this Android.Accounts.IAccountManagerFuture self, long timeout, Java.Util.Concurrent.TimeUnit? unit)
{
return global::System.Threading.Tasks.Task.Run (() => self.GetResult (timeout, unit));
}
}
} For classes, it's a normal method: // <attr api-since="8" path="/api/package[@name='javax.xml.validation']/class[@name='Validator']/method[@name='validate']" name="generateAsyncWrapper">true</attr>
namespace Javax.Xml.Validation {
public abstract partial class Validator : Java.Lang.Object {
public abstract void Validate (Javax.Xml.Transform.ISource? source, Javax.Xml.Transform.IResult? result);
public global::System.Threading.Tasks.Task ValidateAsync (Javax.Xml.Transform.ISource? source, Javax.Xml.Transform.IResult? result)
{
return global::System.Threading.Tasks.Task.Run (() => Validate (source, result));
}
}
} In retrospect, I suspect we should have always emitted extension methods (and/or used Alas, the semantics of Which brings us to this PR: is it "future proof"? Is there any conceivable way for Google to add a method to a future Android version which would collide with the methods here? I believe the answer is yes: C# doesn't care about return types in method overload resolution, only parameter types, and for all of these overloads the parameter types used either involve Java bindings (e.g. /* partial */ class KeyChain {
public static SomeFutureType choosePrivateKeyAlias(Activity activity, String[] keyTypes, Principal issuers, Uri uri, String alias) {
// …
}
} which will immediately cause an ABI break. Can the above "method scope" issue be addressed? Of course! The questions are how, and which is "best", and what are the criteria to determine "best"? Various solutions that come to my mind include:
My current preference is toward (1) or (2). |
@jonpryor I'm not sure the "threat" of Google adding conflicting methods is high in this case, but it's certainly a possibility. I think another solution would be to add an "unlikely" prefix to method names (e.g. back in the day it would be |
@jonpryor @grendello thanks for the feedback. My intent was to make these APIs discoverable through intellisense so that when the developer types We could also use a parameter with a .NET type in the method signature so that when Google accidentally introduces methods with the same name, there is no collision in method signatures: static Task<X509Certificate2?> ChoosePrivateKeyAliasAsync(..., System.Uri? uri, ...)
{
// when on API < 23, call the older overload with uri.Host and uri.Port
} Admittedly, I don't know how to ensure the uniqueness of the Instead of the "unlikely" prefix such as KeyChain.GetX509Certificate2WithPrivateKey(context, alias);
KeyChain.ChooseX509Certificate2WithPrivateKeyAsync(activity, ...); Maybe the option (1) is the best after all. The only really important method is I agree that requiring C# 13 might be limiting for some customers, so maybe we should just add a completely custom static class |
which prompts a question: how/why would a developer be writing Is there a "better" scope that a developer could/should know about? Would it be "better" to instead have a C#13 extension on I have no idea about any of this, because I don't understand what led the developer to be typing Assuming that |
My thinking was that the developer could be looking for native APIs to access certificates installed on the device and learn about It is possible that the APIs wouldn't be discoverable this way. In that case, maybe we don't need these APIs at all, and what we need is a .NET specific guide that would help the developers use the |
Because when I googled to figure out how to use the installed certificates, this is where I ended up. It's pretty common that when I need to do something Android specific I look through the android doc instead, and then translate that to C#, while looking for .NET specific overloads to help that work (like what is proposed here). |
@dotMorten: thank you for the feedback! |
Or perhaps the new X509CertificateLoader APIs might be the better place? dotnet/docs#41662 |
I don't think the
I still think the |
Context: dotnet/runtime#99874 Context: dotnet/runtime#103337 dotnet/runtime#103337 added the ability for the [`X509Certificate2(IntPtr)` constructor][0] to accept a [`java.security.KeyStore.PrivateKeyEntry`][1] instance. Add convenience methods to [`Android.Security.KeyChain`][2] to make it easier for developers to access certificates with non-exportable private keys: partial class KeyChain { public static X509Certificate2? GetX509Certificate2WithPrivateKey ( Android.Content.Context context, string alias); public static async Task<string?> ChoosePrivateKeyAliasAsync ( Android.App.Activity activity, string[]? keyTypes, Java.Security.IPrincipal[]? issuers, Android.Net.Uri? uri, string? alias); public static async Task<string?> ChoosePrivateKeyAliasAsync ( Android.App.Activity activity, string[]? keyTypes, Java.Security.IPrincipal[]? issuers, string? host, int port, string? alias); public static async Task<X509Certificate2?> ChooseX509Certificate2WithPrivateKeyAsync ( Android.App.Activity activity, string[]? keyTypes, Java.Security.IPrincipal[]? issuers, Android.Net.Uri? uri, string? alias); public static async Task<X509Certificate2?> ChooseX509Certificate2WithPrivateKeyAsync ( Android.App.Activity activity, string[]? keyTypes, Java.Security.IPrincipal[]? issuers, string? host, int port, string? alias); } [0]: https://learn.microsoft.com/dotnet/api/system.security.cryptography.x509certificates.x509certificate2.-ctor?view=net-8.0#system-security-cryptography-x509certificates-x509certificate2-ctor(system-intptr) [1]: https://developer.android.com/reference/java/security/KeyStore.PrivateKeyEntry [2]: https://learn.microsoft.com/dotnet/api/android.security.keychain?view=net-android-34.0
We have added the ability to work with certificates installed on device available through
Android.Security.KeyChain
for client authentication in SslStream and SocketsHttpHandler (dotnet/runtime#103337) by creating an instance ofX509Certificate2
as a wrapper aroundKeyStore.PrivateKeyEntry
. This API is not straightforward to use though so I am proposing convenience APIs for theKeyChain
class that would make it much easier for .NET developers to access certificates with non-exportable private keys throughKeyChain
.Note: The runtime PR has been only merged very recently so this code currently won't work. I'll keep this PR open as a draft for now to get some initial feedback before the changes from the runtime flow into the android SDK.