Skip to content

[appletls]: Use SecIdentityCreate() to avoid using the Mac keychain.#4671

Merged
baulig merged 9 commits intomasterfrom
work-appletls-keychain-landing
Apr 11, 2017
Merged

[appletls]: Use SecIdentityCreate() to avoid using the Mac keychain.#4671
baulig merged 9 commits intomasterfrom
work-appletls-keychain-landing

Conversation

@baulig
Copy link
Contributor

@baulig baulig commented Apr 11, 2017

Reading Certificates from the Mac Keychain

Reading the private key from the keychain is a new feature introduced with
AppleTls on XamMac and iOS. On Desktop Mono, this new feature has several
known issues and it also did not received any testing yet. We go back to the old
way of doing things, which is to explicitly provide an X509Certificate2 with a
private key.

Keychain Dialog Popups

When using Xamarin.Mac or Xamarin.iOS, we try to search the keychain
for the certificate and private key.

On Xamarin.iOS, this is easy because each app has its own keychain.

On Xamarin.Mac, the .app package needs to be trusted via code-sign
to get permission to access the user's keychain. [FIXME: I still have to
research how to actually do that.] Without this, you will get a popup
message each time, asking you whether you want to allow the app to access
the keychain, but you can make these go away by selecting "Trust always".

On Desktop Mono, this is problematic because selecting "Trust always"
give the 'mono' binary (and thus everything you'll ever run with Mono)
permission to retrieve the private key from the keychain.

This code would also trigger constant keychain popup messages,
which could only be suppressed by granting full trust. It also makes it
impossible to run Mono in headless mode.

SecIdentityCreate

To avoid these problems, we are currently using an undocumented API
called SecIdentityRef() to avoid using the Mac keychain whenever a
X509Certificate2 with a private key is used.

On iOS and XamMac, you can still provide the X509Certificate without
a private key - in this case, a keychain search will be performed (and you
may get a popup message on XamMac).

@baulig baulig requested a review from chamons April 11, 2017 15:02
@chamons chamons requested a review from spouliot April 11, 2017 15:09
return SecKeyChain.FindIdentity (secCert, true);
}
#else
return null;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are internal users of this API going to check for null or could this cause NRE?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Null is a valid return value - the caller is responsible for throwing an exception.

CFObject.CFRetain (handle);
}

/*
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should ^ ctor set this.owner explicitly?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, we could also make owner the object that's CFRelease()d and set it to IntPtr.Zero if we don't "own" it.

if (owner != IntPtr.Zero) {
CFObject.CFRelease (owner);
owner = handle = IntPtr.Zero;
} else if (handle != IntPtr.Zero) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems a tad weird to have this else in the Dispose. Not necessary wrong, but not expected.

{
var descriptorHandle = CFString.Create (descriptor);
if (descriptorHandle == null)
throw new InvalidOperationException ();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this the right exception?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe OutOfMemoryException()?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If that method is used (I did not see it used in the diff) then it seems this should be a null check on descriptor to throw an ArgumentNullException


return new SecAccess (accessRef, true);
} finally {
descriptorHandle.Dispose ();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we use a using block here instead?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bad idea, but we're incorrectly doing it in so many other places. We'd have to make all those CFString.Create() etc. functions throw instead of possibly returning null.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The idea is not to change CFString.Create() but keep the code as it but replace try/finally with using


IntPtr result = IntPtr.Zero;
SecStatusCode status = SecStatusCode.Success;
for (var test = 0; test < 13; test++) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why 13 here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oops - that entire loop must be removed!

using (var cert = new SecCertificate (certificate)) {
var identity = SecIdentityCreate (IntPtr.Zero, cert.Handle, key.Handle);
if (CFType.GetTypeID (identity) != SecIdentity.GetTypeID ())
throw new InvalidOperationException ();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right exception here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should never happen - do we have some kind of "internal error" exception?


IntPtr result = IntPtr.Zero;
SecStatusCode status = SecStatusCode.Success;
keyParams = new SecItemImportExportKeyParameters ();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is this line for?

Marshal.StructureToPtr (keyParams.Value, keyParamsPtr, false);
}

IntPtr result = IntPtr.Zero;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You don't need to explicitly set there when they are set by SecItemImport

// Native enum; don't change.
enum SecExternalFormat : int {
Unknown = 0,
OpenSSL,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If it's native enum, set OpenSSL value explicitly


// Native enum; don't change.
enum SecExternalItemType : int {
Unknown,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Explicitly set values


IntPtr ptr;
result = SecItem.SecItemCopyMatching (query.Handle, out ptr);
if ((result == SecStatusCode.Success) && (ptr != IntPtr.Zero)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Extra parens

IntPtr ptr;
result = SecItem.SecItemCopyMatching (query.Handle, out ptr);
if ((result == SecStatusCode.Success) && (ptr != IntPtr.Zero)) {
var array = CFArray.ArrayFromHandle<INativeObject> (ptr, p => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why not just return CFArray.ArrayFromHandle...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not my code, I only moved it around a little bit. I just made this private as it's only supported as called from FindIdentity().

In some situations, the result array may contain elements of different types.

IntPtr cfType = CFType.GetTypeID (p);
if (cfType == SecCertificate.GetTypeID ())
return new SecCertificate (p, true);
else if (cfType == SecKey.GetTypeID ())
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant else(s)

Copy link
Contributor

@spouliot spouliot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in progress...

[DllImport (AppleTlsContext.SecurityLibrary)]
extern static /* OSStatus */ SecStatusCode SecAccessCreate (/* CFStringRef */ IntPtr descriptor, /* CFArrayRef */ IntPtr trustedList, /* SecAccessRef _Nullable * */ out IntPtr accessRef);

public static SecAccess Create (string descriptor)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

where is that method used ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It will be used later on - after we re-enabled the keychain on mobile.

{
var descriptorHandle = CFString.Create (descriptor);
if (descriptorHandle == null)
throw new InvalidOperationException ();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If that method is used (I did not see it used in the diff) then it seems this should be a null check on descriptor to throw an ArgumentNullException

// values are defined in Security.framework/Headers/SecBase.h
enum SecStatusCode {
Success = 0,
DuplicateItem = -25299,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is also already public in Xamarin.*.dll so we'll end up shipping it twice (lots of string metadata that only grows over time).

I understand it's nice for debugging the internals but it's not exposed. Maybe it could live under a #if XXX ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could probably remove some of the more obscure values.

Martin Baulig added 6 commits April 11, 2017 12:03
…hen we have a private key.

Reading Certificates from the Mac Keychain
==========================================

Reading the private key from the keychain is a new feature introduced with
AppleTls on XamMac and iOS. On Desktop Mono, this new feature has several
known issues and it also did not received any testing yet. We go back to the old
way of doing things, which is to explicitly provide an X509Certificate2 with a
private key.

Keychain Dialog Popups
======================

When using Xamarin.Mac or Xamarin.iOS, we try to search the keychain
for the certificate and private key.

On Xamarin.iOS, this is easy because each app has its own keychain.

On Xamarin.Mac, the .app package needs to be trusted via code-sign
to get permission to access the user's keychain. [FIXME: I still have to
research how to actually do that.] Without this, you will get a popup
message each time, asking you whether you want to allow the app to access
the keychain, but you can make these go away by selecting "Trust always".

On Desktop Mono, this is problematic because selecting "Trust always"
give the 'mono' binary (and thus everything you'll ever run with Mono)
permission to retrieve the private key from the keychain.

This code would also trigger constant keychain popup messages,
which could only be suppressed by granting full trust. It also makes it
impossible to run Mono in headless mode.

SecIdentityCreate
=================

To avoid these problems, we are currently using an undocumented API
called SecIdentityRef() to avoid using the Mac keychain whenever a
X509Certificate2 with a private key is used.

On iOS and XamMac, you can still provide the X509Certificate without
a private key - in this case, a keychain search will be performed (and you
may get a popup message on XamMac).
@baulig baulig dismissed marek-safar’s stale review April 11, 2017 18:53

The issues are now fixed.

@baulig
Copy link
Contributor Author

baulig commented Apr 11, 2017

I did some cleanups, fixed all the problems you guys addressed and added some documentation.

Could you please have another look at this to see whether this can now go in? @marek-safar @chamons

@baulig baulig requested review from chamons and marek-safar April 11, 2017 21:08
@baulig baulig self-assigned this Apr 11, 2017
internal SecKeyChain (IntPtr handle, bool owns = false)
{
if (handle == IntPtr.Zero)
throw new Exception ("Invalid handle");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be ArgumentException

@baulig baulig merged commit 1eb27c1 into master Apr 11, 2017
@baulig baulig deleted the work-appletls-keychain-landing branch April 11, 2017 21:46
baulig pushed a commit that referenced this pull request Apr 12, 2017
…4671)

* [appletls]: Use SecIdentityCreate() to avoid using the Mac keychain when we have a private key.

Reading Certificates from the Mac Keychain
==========================================

Reading the private key from the keychain is a new feature introduced with
AppleTls on XamMac and iOS. On Desktop Mono, this new feature has several
known issues and it also did not received any testing yet. We go back to the old
way of doing things, which is to explicitly provide an X509Certificate2 with a
private key.

Keychain Dialog Popups
======================

When using Xamarin.Mac or Xamarin.iOS, we try to search the keychain
for the certificate and private key.

On Xamarin.iOS, this is easy because each app has its own keychain.

On Xamarin.Mac, the .app package needs to be trusted via code-sign
to get permission to access the user's keychain. [FIXME: I still have to
research how to actually do that.] Without this, you will get a popup
message each time, asking you whether you want to allow the app to access
the keychain, but you can make these go away by selecting "Trust always".

On Desktop Mono, this is problematic because selecting "Trust always"
give the 'mono' binary (and thus everything you'll ever run with Mono)
permission to retrieve the private key from the keychain.

This code would also trigger constant keychain popup messages,
which could only be suppressed by granting full trust. It also makes it
impossible to run Mono in headless mode.

SecIdentityCreate
=================

To avoid these problems, we are currently using an undocumented API
called SecIdentityRef() to avoid using the Mac keychain whenever a
X509Certificate2 with a private key is used.

On iOS and XamMac, you can still provide the X509Certificate without
a private key - in this case, a keychain search will be performed (and you
may get a popup message on XamMac).

(cherry picked from commit 1eb27c1)
baulig pushed a commit to dotnet/macios that referenced this pull request Apr 12, 2017
Temporary PR, preparing the cherry-picking of Mono's PR #4671
(mono/mono#4671) into mono/2017-02.
baulig pushed a commit that referenced this pull request Apr 13, 2017
* [BTLS]: Cleanup certificate store initialization.

* Kill unused MonoBtlsX509Store.AddTrustedRoots().

* In server-mode, MonoBtlsProvider.SetupCertificateStore() now only adds
  certificates explicitly trused via MonoTlsSettings.TrustAnchors.

* MonoTlsProvider.ValidateCertificate() - which is called from
  X509Certificate2.Verify() via X509CertificateImplBtls.Verify() - now
  uses MonoTlsSettings.DefaultSettings and assumes client-mode.

* [appletls]: Actually use the new code that was added in PR #4671.

* [btls]: Add new MonoTlsSettings.CertificateValidationTime property.

This allows you to set a custom time for certificate expiration checks.

* [appletls]: Bind SecTrustSetVerifyDate() and check MonoTlsSettings.CertificateValidationTime.
baulig pushed a commit that referenced this pull request Apr 13, 2017
…4671) (#4677)

* [appletls]: Use SecIdentityCreate() to avoid using the Mac keychain. (#4671)

Reading Certificates from the Mac Keychain
==========================================

Reading the private key from the keychain is a new feature introduced with
AppleTls on XamMac and iOS. On Desktop Mono, this new feature has several
known issues and it also did not received any testing yet. We go back to the old
way of doing things, which is to explicitly provide an X509Certificate2 with a
private key.

Keychain Dialog Popups
======================

When using Xamarin.Mac or Xamarin.iOS, we try to search the keychain
for the certificate and private key.

On Xamarin.iOS, this is easy because each app has its own keychain.

On Xamarin.Mac, the .app package needs to be trusted via code-sign
to get permission to access the user's keychain. [FIXME: I still have to
research how to actually do that.] Without this, you will get a popup
message each time, asking you whether you want to allow the app to access
the keychain, but you can make these go away by selecting "Trust always".

On Desktop Mono, this is problematic because selecting "Trust always"
give the 'mono' binary (and thus everything you'll ever run with Mono)
permission to retrieve the private key from the keychain.

This code would also trigger constant keychain popup messages,
which could only be suppressed by granting full trust. It also makes it
impossible to run Mono in headless mode.

SecIdentityCreate
=================

To avoid these problems, we are currently using an undocumented API
called SecIdentityRef() to avoid using the Mac keychain whenever a
X509Certificate2 with a private key is used.

On iOS and XamMac, you can still provide the X509Certificate without
a private key - in this case, a keychain search will be performed (and you
may get a popup message on XamMac).
baulig pushed a commit that referenced this pull request Apr 13, 2017
…4671) (#4677)

* [appletls]: Use SecIdentityCreate() to avoid using the Mac keychain. (#4671)

Reading Certificates from the Mac Keychain
==========================================

Reading the private key from the keychain is a new feature introduced with
AppleTls on XamMac and iOS. On Desktop Mono, this new feature has several
known issues and it also did not received any testing yet. We go back to the old
way of doing things, which is to explicitly provide an X509Certificate2 with a
private key.

Keychain Dialog Popups
======================

When using Xamarin.Mac or Xamarin.iOS, we try to search the keychain
for the certificate and private key.

On Xamarin.iOS, this is easy because each app has its own keychain.

On Xamarin.Mac, the .app package needs to be trusted via code-sign
to get permission to access the user's keychain. [FIXME: I still have to
research how to actually do that.] Without this, you will get a popup
message each time, asking you whether you want to allow the app to access
the keychain, but you can make these go away by selecting "Trust always".

On Desktop Mono, this is problematic because selecting "Trust always"
give the 'mono' binary (and thus everything you'll ever run with Mono)
permission to retrieve the private key from the keychain.

This code would also trigger constant keychain popup messages,
which could only be suppressed by granting full trust. It also makes it
impossible to run Mono in headless mode.

SecIdentityCreate
=================

To avoid these problems, we are currently using an undocumented API
called SecIdentityRef() to avoid using the Mac keychain whenever a
X509Certificate2 with a private key is used.

On iOS and XamMac, you can still provide the X509Certificate without
a private key - in this case, a keychain search will be performed (and you
may get a popup message on XamMac).

(cherry picked from commit b9b2d23)
baulig pushed a commit that referenced this pull request Apr 13, 2017
…4671) (#4677) (#4689)

* [appletls]: Use SecIdentityCreate() to avoid using the Mac keychain. (#4671)

Reading Certificates from the Mac Keychain
==========================================

Reading the private key from the keychain is a new feature introduced with
AppleTls on XamMac and iOS. On Desktop Mono, this new feature has several
known issues and it also did not received any testing yet. We go back to the old
way of doing things, which is to explicitly provide an X509Certificate2 with a
private key.

Keychain Dialog Popups
======================

When using Xamarin.Mac or Xamarin.iOS, we try to search the keychain
for the certificate and private key.

On Xamarin.iOS, this is easy because each app has its own keychain.

On Xamarin.Mac, the .app package needs to be trusted via code-sign
to get permission to access the user's keychain. [FIXME: I still have to
research how to actually do that.] Without this, you will get a popup
message each time, asking you whether you want to allow the app to access
the keychain, but you can make these go away by selecting "Trust always".

On Desktop Mono, this is problematic because selecting "Trust always"
give the 'mono' binary (and thus everything you'll ever run with Mono)
permission to retrieve the private key from the keychain.

This code would also trigger constant keychain popup messages,
which could only be suppressed by granting full trust. It also makes it
impossible to run Mono in headless mode.

SecIdentityCreate
=================

To avoid these problems, we are currently using an undocumented API
called SecIdentityRef() to avoid using the Mac keychain whenever a
X509Certificate2 with a private key is used.

On iOS and XamMac, you can still provide the X509Certificate without
a private key - in this case, a keychain search will be performed (and you
may get a popup message on XamMac).

(cherry picked from commit b9b2d23)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants