Skip to content
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

Add secure credential storage APIs for Windows+macOS #2

Merged
merged 7 commits into from
Dec 7, 2018
Merged

Add secure credential storage APIs for Windows+macOS #2

merged 7 commits into from
Dec 7, 2018

Conversation

mjcheetham
Copy link
Collaborator

Add secure credential storage APIs for:

  • Windows Credential Manager
  • macOS Keychain

..using the native syscalls on each platform. Should we wish to support Linux in the future, options such as GNOME Keyring could be used.

Add secure credential storage APIs for the Windows Credential
Manager and the macOS Keychain.
@mjcheetham mjcheetham requested review from jeschu1 and jamill December 3, 2018 11:46
Fix an incorrect documentation comment on `ICredentialStore` that
an exception is thrown for a missing credential key - it should
return `null` instead.

Add some more links to Apple documentation showing where various
constant values came from.
@mjcheetham
Copy link
Collaborator Author

Closing and reopening to kick off an PR build...

@mjcheetham mjcheetham closed this Dec 4, 2018
@mjcheetham mjcheetham reopened this Dec 4, 2018
Copy link
Contributor

@jrbriggs jrbriggs left a comment

Choose a reason for hiding this comment

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

Looks good. Left a few bits of feedback for your consideration.

- Add messages to the `Debug.Assert` calls in `MacOSKeychain`.
- Rename the `GetDefaultCredentialStore` helper method to
  `CreateCredentialStore` in `CommandContext`.
try
{
// Check if an entry already exists in the keychain
SecKeychainFindGenericPassword(
Copy link
Contributor

Choose a reason for hiding this comment

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

Do we need to check return value here?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I don't think so since in either case (this call fails or this call succeeds) we either try and update an existing entry (itemRef != null) or create a new one.

In both of these paths we're doing ThrowOnError so any generic errors like access exceptions or 'no such keychain' will be checked & thrown as managed exceptions there.

What do you think? To add the check we'll need to filter out the 'expected error' that the key does not exist already so we can create it.

Copy link
Contributor

Choose a reason for hiding this comment

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

hmmm... what other errors could there be? If we did hit a problem failing to find the key (that was specific to find), then it would not be surfaced and it might be harder to debug. Maybe there are no other errors that we need to be aware of.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

@jamill I've updated the approach for error handling in other the Windows and Mac stores.

I am now always checking the return values of any P/Invoke call, and am explicitly filtering out any 'expected' error codes (such as 'not found').

}
catch (KeyNotFoundException)
{
return false;
Copy link
Contributor

Choose a reason for hiding this comment

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

Do callers need to discern between "failed to remove" and "nothing to remove"? I was trying to think how callers might handle different return values...

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I don't think so since there's not really much we (GCM) can do if we fail to delete the stored credential (as advised by Git).

Except for get, I don't think Git cares about the output of store and erase commands (I might be mistaken there).

{
Type = CredentialType.Generic,
TargetName = key,
CredentialBlob = Marshal.AllocCoTaskMem(passwordBytes.Length),
Copy link
Contributor

Choose a reason for hiding this comment

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

AllocCoTaskMem or AllocHGlobal?

Copy link
Collaborator Author

@mjcheetham mjcheetham Dec 6, 2018

Choose a reason for hiding this comment

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

This code was copied from the .NET Framework version of GCM so I didn't want to change that since we know that's working; it's been battle tested.

It might very well be wrong, but are we sure? Are the WinCredManager APIs using COM at some point?

Copy link
Contributor

Choose a reason for hiding this comment

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

It might very well be wrong, but are we sure?

With the re-write, I wanted to double check some of the assumptions from the original code (at least the parts that feel wrong). I would prefer to understand the differences between the two calls and which one we should use (from 1st principles)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I believe you might be correct here.. the CredRead, CredWrite, and CredDelete calls are all Win32 APIs which are not COM.

I've updated the code here to use the global heap allocator which is confirmed to be working fine in the tests.

Rename the `MacOSKeychain` and `WindowsCredentialManager` static
contructor methods from `OpenDefault` to `Open`. In the future should
we want to or need to disambiguate between _which_ store is being
used we can add overloads to the that singularly named method.
Replace usages of the COM allocator (`Marshal.AllocCoTaskMem` and
`Marshal.FreeCoTaskMem`) in the credential stores code with the
global allocator (`Marshal.AllocHGlobal` and `Marshal.FreeHGlobal`).

Since there is no COM heap on Mac using this allocator was not the
correct thing to do. On Windows the CredRead/Write/Delete calls are
Win32 API calls which don't use COM either.

Use the global allocator for WindowsCredMgr native code
Simplify some of the marshaling code between native and managed
by using more methods from the `Marshal` type, rather than using
our own helpers.
Update the error handling performed in both `MacOSKeychain` and
`WindowsCredentialManager` to be clearer about which native return
codes we consider to be 'OK', and ensure we're throwing exceptions
for all other types of unknown errors.
@mjcheetham mjcheetham merged commit 5bca3b5 into git-ecosystem:master Dec 7, 2018
@mjcheetham mjcheetham deleted the secure-storage branch December 7, 2018 10:47
@mjcheetham mjcheetham added enhancement New feature or request platform:osx Specific to the macOS platform platform:windows Specific to the Windows platform labels Apr 24, 2019
ldennington pushed a commit that referenced this pull request Jun 9, 2022
ldennington pushed a commit that referenced this pull request Oct 19, 2023
install from source: refactor install location and fix path
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request platform:osx Specific to the macOS platform platform:windows Specific to the Windows platform
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants