Skip to content

[Valet 4.0] Allow kSecAttrService to be a customer-friendly string on Mac#154

Merged
dfed merged 34 commits intodevelop--4.0from
dfed--mac-friendly-service
Jan 17, 2020
Merged

[Valet 4.0] Allow kSecAttrService to be a customer-friendly string on Mac#154
dfed merged 34 commits intodevelop--4.0from
dfed--mac-friendly-service

Conversation

@dfed
Copy link
Copy Markdown
Collaborator

@dfed dfed commented Oct 2, 2018

Addresses #140. Note that I haven't added unit tests or README documentation for these cases yet, so this PR is WIP. Before I commit to writing unit tests and README docs, I'm curious what the reaction is to adding this API, and I'd also like feedback on how we can improve the API documentation to make it clear what is going on here. Note that by explicitly setting the kSecAttrService, each Valet instance is no longer guaranteed to be a sandbox, unless the kSecAttrService is unique to the organization. Since this API is dangerous, I've only added it to the macOS target.

cc @abey, @antons, and @gregcotten

@dfed
Copy link
Copy Markdown
Collaborator Author

dfed commented Oct 2, 2018

Note that I'll probably want to tackle #123 before this merges.

@dfed dfed force-pushed the dfed--mac-friendly-service branch from fadccad to 1a1d23c Compare October 2, 2018 20:11
@gregcotten
Copy link
Copy Markdown

This all looks good to me!

@dfed dfed force-pushed the dfed--mac-friendly-service branch from 1a1d23c to 64979d4 Compare May 25, 2019 04:06
@dfed
Copy link
Copy Markdown
Collaborator Author

dfed commented May 25, 2019

I've updated the README to include information on this new API. Note that getting this API to compile will require dropping Xcode 9 support. I'm not yet willing to do that, so this PR will stay open until we have more we'd want to roll into a breaking change.

@dfed dfed changed the base branch from master to dfed--simplify-ci-v4 December 22, 2019 08:45
@dfed dfed changed the title [WIP] Allow kSecAttrService to be a customer-friendly string on Mac [WIP][Valet 4.0] Allow kSecAttrService to be a customer-friendly string on Mac Dec 22, 2019
@dfed dfed force-pushed the dfed--mac-friendly-service branch from 0c9efbe to b03a958 Compare December 23, 2019 00:08
@dfed dfed changed the title [WIP][Valet 4.0] Allow kSecAttrService to be a customer-friendly string on Mac [Valet 4.0] Allow kSecAttrService to be a customer-friendly string on Mac Dec 23, 2019
@dfed dfed force-pushed the dfed--simplify-ci-v4 branch from 921aa52 to f087d9e Compare December 23, 2019 00:14
@dfed dfed force-pushed the dfed--mac-friendly-service branch from 51caa23 to f009f7b Compare December 23, 2019 06:23
@dfed dfed force-pushed the dfed--simplify-ci-v4 branch from f087d9e to e9848e0 Compare December 23, 2019 20:07
@dfed dfed force-pushed the dfed--mac-friendly-service branch 2 times, most recently from d00a634 to 100df92 Compare December 24, 2019 03:28
@dfed dfed changed the base branch from dfed--simplify-ci-v4 to develop--4.0 December 24, 2019 03:28
@dfed dfed force-pushed the dfed--mac-friendly-service branch from 100df92 to ebf1e11 Compare December 24, 2019 03:30
@dfed dfed force-pushed the dfed--mac-friendly-service branch from ebf1e11 to 0bb4fa1 Compare December 24, 2019 22:06
@dfed dfed requested a review from fdiaz January 13, 2020 19:57
The identifier you choose for your Valet is used to create a sandbox for the data your Valet writes to the keychain. Two Valets of the same type created via the same initializer, accessibility value, and identifier will be able to read and write the same key:value pairs; Valets with different identifiers each have their own sandbox. Choose an identifier that describes the kind of data your Valet will protect. You do not need to include your application name or bundleIdentifier in your Valet’s identifier.
The identifier you choose for your Valet is used to create a sandbox for the data your Valet writes to the keychain. Two Valets of the same type created via the same initializer, accessibility value, and identifier will be able to read and write the same key:value pairs; Valets with different identifiers each have their own sandbox. Choose an identifier that describes the kind of data your Valet will protect. You do not need to include your application name or bundle identifier in your Valet’s identifier.

#### Choosing a User-friendly Identifier on macOS
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

nit: Should we have some type ⚠️ or an asterisk in the title to make sure it's explicit that this breaks Valet sandbox?


```swift
let mySecureEnclaveValet = Valet.valet(withExplicitlySet: Identifier(nonEmpty: "Druidia")!, accessibility: .whenUnlocked)
```
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

nit: Let's add an Obj-C example

#endif
case .standard:
noPromptValet = .valet(with: identifier, accessibility: .whenPasscodeSetThisDeviceOnly)
#if os(macOS)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

nit: This is not correctly indented

README.md Outdated
let mySecureEnclaveValet = Valet.valet(withExplicitlySet: Identifier(nonEmpty: "Druidia")!, accessibility: .whenUnlocked)
```

Mac apps signed with a developer ID may see their Valet’s identifier [shown to their users](https://github.com/square/Valet/issues/140). While it is possible to explicitly set a user-friendly identifier, note that doing so bypasses this project’s guarantee that one Valet type will not have access to one another type’s key:value pairs. To maintain this guarantee, ensure that each Valet’s identifier is globally unique.
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I think providing an example of what types of Valets will be sharing the same key:value pairs would be useful here.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Literally any with the same identifier, hence the suggestion to make the identifier globally unique.

/// - parameter identifier: A non-empty string that must correspond with the value for keychain-access-groups in your Entitlements file. Must be unique relative to other Valet identifiers.
/// - parameter accessibility: The desired accessibility for the Valet.
/// - returns: A Valet (synchronized with iCloud) that reads/writes keychain elements that can be shared across applications written by the same development team.
public class func iCloudSharedAccessGroupValet(withExplicitlySet identifier: Identifier, accessibility: CloudAccessibility) -> Valet {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I'm guessing all of these are class and not static for Obj-c interoperability?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

I think I'm using class because of a decision I made years ago where I thought it was nicer? I'll do a separate PR to use static, since we're not using subclassing here.

}
return findOrCreate(explicitlySet: identifier, configuration: .iCloud(accessibility), sharedAccessGroup: true)
}
#endif
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Just for my understanding: all of this is also for Obj-C interoperability given structs are not exposed?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

exactly. Identifier is not Objective-C compatible. Given that we're making breaking changes, I'm open to changing this up if you've got better ideas 🙂

class func permutations(withExplictlySet identifier: Identifier, shared: Bool = false) -> [Valet] {
return Accessibility.allValues().map { accessibility in
let valet: Valet = shared ? .sharedAccessGroupValet(withExplicitlySet: identifier, accessibility: accessibility) : .valet(withExplicitlySet: identifier, accessibility: accessibility)
return valet
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I'm somewhat surprised Swift can't infer this?

nit, but do you think making this closure be explicitly

accessibility -> Valet

so this can be a one-liner makes this nicer?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

yeah let's make it a one-liner. It looks like in the year since this PR started I made the other methods (above) one-liners 😅

}
}

class func iCloudPermutations(withExplictlySet identifier: Identifier, shared: Bool = false) -> [Valet] {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I think you might be missing marking this explicitly as internal on this one and on the top

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

This is all in internal extension Valet 🙂

// This is not be the case upon setting a breakpoint and inspecting before the valet.setString(, forKey:) call above.
}

func test_withExplicitlySet_identifierHasExplicitlySetIdentifier() {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

nit: identifierHasExplicitlySetIdentifier -> assignsExplicitIdentifier


Valet.iCloudPermutations(withExplictlySet: explicitlySetIdentifier, shared: false).forEach {
XCTAssertEqual($0.keychainQuery?[kSecAttrService as String], explicitlySetIdentifier.description)
}
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This is (probably?) a nit, but should these be 2 tests? We're testing the same thing but on different contexts.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

I mean, this could be 4 tests. But I don't gain a lot by splitting this up?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I'd indeed do 4 tests here 😁

We mostly gain the ability that when a test fails, we immediately know what assert is causing it (since we'd have 1 per test)

I'm a fan of that. I think using XCTest doesn't play that well with this though since you need to write a whole new test for that, but I also think this is fine though 🤷‍♂

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

I think another solve for that is writing good descriptions for the failure case. In this case, I have 4 classes of tests, each with Accessibility.allValues().count asserts. That's... a lot of tests to write. And if we ever add a new accessibility value, it'll get worse.

Let me touch this up by adding some failure strings.

Fwiw, when one of these tests failed earlier in CI, I looked up the line number of the failure. That worked well enough 🤷‍♂

@codecov-io
Copy link
Copy Markdown

codecov-io commented Jan 16, 2020

Codecov Report

Merging #154 into develop--4.0 will increase coverage by 0.19%.
The diff coverage is 71.42%.

Impacted file tree graph

@@               Coverage Diff                @@
##           develop--4.0     #154      +/-   ##
================================================
+ Coverage         75.37%   75.57%   +0.19%     
================================================
  Files                14       14              
  Lines              1206     1269      +63     
================================================
+ Hits                909      959      +50     
- Misses              297      310      +13
Impacted Files Coverage Δ
Sources/Valet/Internal/Keychain.swift 89.19% <100%> (+1.04%) ⬆️
Sources/Valet/Valet.swift 75.3% <100%> (-4.14%) ⬇️
Sources/Valet/SecureEnclaveValet.swift 52.99% <50%> (ø) ⬆️
Sources/Valet/SecureEnclave.swift 62.16% <50%> (ø) ⬆️
Sources/Valet/SinglePromptSecureEnclaveValet.swift 54.74% <50%> (ø) ⬆️
Sources/Valet/Internal/Service.swift 100% <0%> (+2.29%) ⬆️
Sources/Valet/Internal/SecItem.swift 83.66% <0%> (+3.26%) ⬆️

@dfed
Copy link
Copy Markdown
Collaborator Author

dfed commented Jan 17, 2020

The lower test coverage mostly comes from the objc-compatibility layer not being tested. I currently have no test coverage on the compatibility layer. I need to fix that, but that's for another PR.

@dfed dfed merged commit d23922e into develop--4.0 Jan 17, 2020
@dfed dfed deleted the dfed--mac-friendly-service branch January 17, 2020 02:20
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.

4 participants