Skip to content

Commit

Permalink
Create and adopt SharedAccessGroupIdentifier
Browse files Browse the repository at this point in the history
  • Loading branch information
dfed committed Feb 25, 2020
1 parent be35452 commit 5078ffc
Show file tree
Hide file tree
Showing 19 changed files with 294 additions and 144 deletions.
8 changes: 6 additions & 2 deletions Sources/Valet/Internal/Service.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import Foundation

internal enum Service: CustomStringConvertible, Equatable {
case standard(Identifier, Configuration)
case sharedAccessGroup(Identifier, Configuration)
case sharedAccessGroup(SharedAccessGroupIdentifier, Configuration)

#if os(macOS)
case standardOverride(service: Identifier, Configuration)
Expand All @@ -48,7 +48,11 @@ internal enum Service: CustomStringConvertible, Equatable {
"VAL_\(configuration.description)_initWithIdentifier:accessibility:_\(identifier)_\(accessibilityDescription)"
}

internal static func sharedAccessGroup(with configuration: Configuration, identifier: Identifier, accessibilityDescription: String) -> String {
internal static func sharedAccessGroup(with configuration: Configuration, identifier: SharedAccessGroupIdentifier, accessibilityDescription: String) -> String {
"VAL_\(configuration.description)_initWithSharedAccessGroupIdentifier:accessibility:_\(identifier.groupIdentifier)_\(accessibilityDescription)"
}

internal static func sharedAccessGroup(with configuration: Configuration, explicitlySetIdentifier identifier: Identifier, accessibilityDescription: String) -> String {
"VAL_\(configuration.description)_initWithSharedAccessGroupIdentifier:accessibility:_\(identifier)_\(accessibilityDescription)"
}

Expand Down
18 changes: 8 additions & 10 deletions Sources/Valet/SecureEnclave.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,26 +25,24 @@ public final class SecureEnclave {

// MARK: Internal Methods

/// - Parameters:
/// - service: The service of the keychain slice we want to check if we can access.
/// - identifier: A non-empty identifier that scopes the slice of keychain we want to access.
/// - Parameter service: The service of the keychain slice we want to check if we can access.
/// - Returns: `true` if the keychain is accessible for reading and writing, `false` otherwise.
/// - Note: Determined by writing a value to the keychain and then reading it back out.
internal static func canAccessKeychain(with service: Service, identifier: Identifier) -> Bool {
internal static func canAccessKeychain(with service: Service) -> Bool {
// To avoid prompting the user for Touch ID or passcode, create a Valet with our identifier and accessibility and ask it if it can access the keychain.
let noPromptValet: Valet
switch service {
#if os(macOS)
case .standardOverride:
fallthrough
case let .standardOverride(identifier, _):
noPromptValet = .valet(with: identifier, accessibility: .whenPasscodeSetThisDeviceOnly)
#endif
case .standard:
case let .standard(identifier, _):
noPromptValet = .valet(with: identifier, accessibility: .whenPasscodeSetThisDeviceOnly)
#if os(macOS)
case .sharedAccessGroupOverride:
fallthrough
case let .sharedAccessGroupOverride(identifier, _):
noPromptValet = .sharedAccessGroupValet(withExplicitlySet: identifier, accessibility: .whenPasscodeSetThisDeviceOnly)
#endif
case .sharedAccessGroup:
case let .sharedAccessGroup(identifier, _):
noPromptValet = .sharedAccessGroupValet(with: identifier, accessibility: .whenPasscodeSetThisDeviceOnly)
}

Expand Down
20 changes: 11 additions & 9 deletions Sources/Valet/SecureEnclaveValet.swift
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ public final class SecureEnclaveValet: NSObject {
/// - identifier: A non-empty string that must correspond with the value for keychain-access-groups in your Entitlements file.
/// - accessControl: The desired access control for the SecureEnclaveValet.
/// - Returns: A SecureEnclaveValet that reads/writes keychain elements that can be shared across applications written by the same development team.
public class func sharedAccessGroupValet(with identifier: Identifier, accessControl: SecureEnclaveAccessControl) -> SecureEnclaveValet {
public class func sharedAccessGroupValet(with identifier: SharedAccessGroupIdentifier, accessControl: SecureEnclaveAccessControl) -> SecureEnclaveValet {
let key = Service.sharedAccessGroup(identifier, .secureEnclave(accessControl)).description as NSString
if let existingValet = identifierToValetMap.object(forKey: key) {
return existingValet
Expand Down Expand Up @@ -84,10 +84,10 @@ public final class SecureEnclaveValet: NSObject {
accessControl: accessControl)
}

private convenience init(sharedAccess identifier: Identifier, accessControl: SecureEnclaveAccessControl) {
private convenience init(sharedAccess groupIdentifier: SharedAccessGroupIdentifier, accessControl: SecureEnclaveAccessControl) {
self.init(
identifier: identifier,
service: .sharedAccessGroup(identifier, .secureEnclave(accessControl)),
identifier: groupIdentifier.asIdentifier,
service: .sharedAccessGroup(groupIdentifier, .secureEnclave(accessControl)),
accessControl: accessControl)
}

Expand Down Expand Up @@ -116,7 +116,7 @@ public final class SecureEnclaveValet: NSObject {
/// - Note: Determined by writing a value to the keychain and then reading it back out. Will never prompt the user for Face ID, Touch ID, or password.
@objc
public func canAccessKeychain() -> Bool {
SecureEnclave.canAccessKeychain(with: service, identifier: identifier)
SecureEnclave.canAccessKeychain(with: service)
}

/// - Parameters:
Expand Down Expand Up @@ -249,12 +249,14 @@ extension SecureEnclaveValet {
}

/// - Parameters:
/// - identifier: A non-empty string that must correspond with the value for keychain-access-groups in your Entitlements file.
/// - appIDPrefix: The application's App ID prefix. This string can be found by inspecting the application's provisioning profile, or viewing the application's App ID Configuration on developer.apple.com. This string must not be empty.
/// - identifier: An identifier that cooresponds to a value in keychain-access-groups in the application's Entitlements file. This string must not be empty.
/// - accessControl: The desired access control for the SecureEnclaveValet.
/// - Returns: A SecureEnclaveValet that reads/writes keychain elements that can be shared across applications written by the same development team.
@objc(sharedAccessGroupValetWithIdentifier:accessControl:)
public class func 🚫swift_sharedAccessGroupValet(with identifier: String, accessControl: SecureEnclaveAccessControl) -> SecureEnclaveValet? {
guard let identifier = Identifier(nonEmpty: identifier) else {
/// - SeeAlso: https://developer.apple.com/documentation/security/keychain_services/keychain_items/sharing_access_to_keychain_items_among_a_collection_of_apps
@objc(sharedAccessGroupValetWithAppIDPrefix:sharedAccessGroupIdentifier:accessControl:)
public class func 🚫swift_sharedAccessGroupValet(appIDPrefix: String, nonEmptyIdentifier identifier: String, accessControl: SecureEnclaveAccessControl) -> SecureEnclaveValet? {
guard let identifier = SharedAccessGroupIdentifier(appIDPrefix: appIDPrefix, nonEmptyGroup: identifier) else {
return nil
}
return sharedAccessGroupValet(with: identifier, accessControl: accessControl)
Expand Down
57 changes: 57 additions & 0 deletions Sources/Valet/SharedAccessGroupIdentifier.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
//
// SharedAccessGroupIdentifier.swift
// Valet
//
// Created by Dan Federman on 2/25/20.
// Copyright © 2020 Square, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//    http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

import Foundation


public struct SharedAccessGroupIdentifier: CustomStringConvertible {

// MARK: Initialization

/// A representation of a shared access group identifier.
/// - Parameters:
/// - appIDPrefix: The application's App ID prefix. This string can be found by inspecting the application's provisioning profile, or viewing the application's App ID Configuration on developer.apple.com. This string must not be empty.
/// - groupIdentifier: An identifier that cooresponds to a value in keychain-access-groups in the application's Entitlements file. This string must not be empty.
/// - SeeAlso: https://developer.apple.com/documentation/security/keychain_services/keychain_items/sharing_access_to_keychain_items_among_a_collection_of_apps
public init?(appIDPrefix: String, nonEmptyGroup groupIdentifier: String?) {
guard !appIDPrefix.isEmpty, let groupIdentifier = groupIdentifier, !groupIdentifier.isEmpty else {
return nil
}

self.appIDPrefix = appIDPrefix
self.groupIdentifier = groupIdentifier
}

// MARK: CustomStringConvertible

public var description: String {
return appIDPrefix + "." + groupIdentifier
}

// MARK: Internal Properties

internal let appIDPrefix: String
internal let groupIdentifier: String

internal var asIdentifier: Identifier {
// It is safe to force unwrap because we've already validated that our description is non-empty.
Identifier(nonEmpty: description)!
}
}
22 changes: 12 additions & 10 deletions Sources/Valet/SinglePromptSecureEnclaveValet.swift
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,10 @@ public final class SinglePromptSecureEnclaveValet: NSObject {
}

/// - Parameters:
/// - identifier: A non-empty string that must correspond with the value for keychain-access-groups in your Entitlements file.
/// - identifier: A non-empty identifier that must correspond with the value for keychain-access-groups in your Entitlements file.
/// - accessControl: The desired access control for the SinglePromptSecureEnclaveValet.
/// - Returns: A SinglePromptSecureEnclaveValet that reads/writes keychain elements that can be shared across applications written by the same development team.
public class func sharedAccessGroupValet(with identifier: Identifier, accessControl: SecureEnclaveAccessControl) -> SinglePromptSecureEnclaveValet {
public class func sharedAccessGroupValet(with identifier: SharedAccessGroupIdentifier, accessControl: SecureEnclaveAccessControl) -> SinglePromptSecureEnclaveValet {
let key = Service.sharedAccessGroup(identifier, .singlePromptSecureEnclave(accessControl)).description as NSString
if let existingValet = identifierToValetMap.object(forKey: key) {
return existingValet
Expand Down Expand Up @@ -88,10 +88,10 @@ public final class SinglePromptSecureEnclaveValet: NSObject {
accessControl: accessControl)
}

private convenience init(sharedAccess identifier: Identifier, accessControl: SecureEnclaveAccessControl) {
private convenience init(sharedAccess groupIdentifier: SharedAccessGroupIdentifier, accessControl: SecureEnclaveAccessControl) {
self.init(
identifier: identifier,
service: .sharedAccessGroup(identifier, .singlePromptSecureEnclave(accessControl)),
identifier: groupIdentifier.asIdentifier,
service: .sharedAccessGroup(groupIdentifier, .singlePromptSecureEnclave(accessControl)),
accessControl: accessControl)
}

Expand Down Expand Up @@ -120,7 +120,7 @@ public final class SinglePromptSecureEnclaveValet: NSObject {
/// - Note: Determined by writing a value to the keychain and then reading it back out. Will never prompt the user for Face ID, Touch ID, or password.
@objc
public func canAccessKeychain() -> Bool {
SecureEnclave.canAccessKeychain(with: service, identifier: identifier)
SecureEnclave.canAccessKeychain(with: service)
}

/// - Parameters:
Expand Down Expand Up @@ -290,12 +290,14 @@ extension SinglePromptSecureEnclaveValet {
}

/// - Parameters:
/// - identifier: A non-empty string that must correspond with the value for keychain-access-groups in your Entitlements file.
/// - appIDPrefix: The application's App ID prefix. This string can be found by inspecting the application's provisioning profile, or viewing the application's App ID Configuration on developer.apple.com. This string must not be empty.
/// - identifier: An identifier that cooresponds to a value in keychain-access-groups in the application's Entitlements file. This string must not be empty.
/// - accessControl: The desired access control for the SinglePromptSecureEnclaveValet.
/// - Returns: A SinglePromptSecureEnclaveValet that reads/writes keychain elements that can be shared across applications written by the same development team.
@objc(sharedAccessGroupValetWithIdentifier:accessControl:)
public class func 🚫swift_sharedAccessGroupValet(with identifier: String, accessControl: SecureEnclaveAccessControl) -> SinglePromptSecureEnclaveValet? {
guard let identifier = Identifier(nonEmpty: identifier) else {
/// - SeeAlso: https://developer.apple.com/documentation/security/keychain_services/keychain_items/sharing_access_to_keychain_items_among_a_collection_of_apps
@objc(sharedAccessGroupValetWithAppIDPrefix:sharedAccessGroupIdentifier:accessControl:)
public class func 🚫swift_sharedAccessGroupValet(appIDPrefix: String, nonEmptyIdentifier identifier: String, accessControl: SecureEnclaveAccessControl) -> SinglePromptSecureEnclaveValet? {
guard let identifier = SharedAccessGroupIdentifier(appIDPrefix: appIDPrefix, nonEmptyGroup: identifier) else {
return nil
}
return sharedAccessGroupValet(with: identifier, accessControl: accessControl)
Expand Down
Loading

0 comments on commit 5078ffc

Please sign in to comment.