├── certificate-sync.xcodeproj
├── project.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ └── IDEWorkspaceChecks.plist
├── xcshareddata
│ └── xcschemes
│ │ └── certificate-sync.xcscheme
└── project.pbxproj
├── certificate-sync
├── acl_entry.swift
├── main.swift
├── Info.plist
├── extensions.swift
├── configuration.swift
├── export_configuration_item.swift
├── acl_configuration_item.swift
├── import_item.swift
├── configuration_item.swift
└── syncronizer.swift
├── test-suite
├── bridging-header.h
├── Info.plist
├── rsa_key.cer
├── rsa_key.pem
├── test_configuration.plist
└── test_suite.swift
├── .gitignore
├── LICENSE
└── README.md
/certificate-sync.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/certificate-sync/acl_entry.swift:
--------------------------------------------------------------------------------
1 | //
2 | // acl_entry.swift
3 | // certificate-sync
4 | //
5 | // Created by Rick Mark on 11/28/18.
6 | // Copyright © 2018 Dropbox. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | class ACLEntry {
12 | var
13 | }
14 |
--------------------------------------------------------------------------------
/certificate-sync.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/test-suite/bridging-header.h:
--------------------------------------------------------------------------------
1 | //
2 | // bridging-header.h
3 | // certificate-sync
4 | //
5 | // Created by Rick Mark on 11/29/18.
6 | // Copyright © 2018 Dropbox. All rights reserved.
7 | //
8 |
9 | #ifndef bridging_header_h
10 | #define bridging_header_h
11 |
12 | #define OPENSSL_TOO_OLD
13 |
14 | #include
15 | #include
16 | #include
17 | #include
18 |
19 | #endif /* bridging_header_h */
20 |
--------------------------------------------------------------------------------
/certificate-sync/main.swift:
--------------------------------------------------------------------------------
1 | //
2 | // main.swift
3 | // certificate-sync
4 | //
5 | // Created by Rick Mark on 11/14/18.
6 | // Copyright © 2018 Dropbox. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | let configurationPath = (CommandLine.arguments[1] as NSString).expandingTildeInPath
12 |
13 | let configuration = Configuration.read(path: configurationPath.toFileURL())
14 |
15 | let syncronizer = Syncronizer(configuration: configuration)
16 |
17 | syncronizer.run()
18 |
--------------------------------------------------------------------------------
/certificate-sync/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleVersion
6 | 1.0
7 | CFBundleName
8 | certificate-sync
9 | CFBundleInfoDictionaryVersion
10 | 6.0
11 | CFBundleIdentifier
12 | com.dropbox.corpeng.certificate-sync
13 |
14 |
15 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | # Xcode
3 | #
4 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
5 |
6 | ## User settings
7 | xcuserdata/
8 |
9 | ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9)
10 | *.xcscmblueprint
11 | *.xccheckout
12 |
13 | ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4)
14 | build/
15 | DerivedData/
16 | *.moved-aside
17 | *.pbxuser
18 | !default.pbxuser
19 | *.mode1v3
20 | !default.mode1v3
21 | *.mode2v3
22 | !default.mode2v3
23 | *.perspectivev3
24 | !default.perspectivev3
25 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2014-2016 Dropbox, Inc.
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 |
7 | http://www.apache.org/licenses/LICENSE-2.0
8 |
9 | Unless required by applicable law or agreed to in writing, software
10 | distributed under the License is distributed on an "AS IS" BASIS,
11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | See the License for the specific language governing permissions and
13 | limitations under the License.
14 |
--------------------------------------------------------------------------------
/test-suite/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | BNDL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 |
22 |
23 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # `certificate-sync` for macOS Keychain
2 |
3 | macOS provides the Keychain as a way to store and retrieve credentials.
4 | Unfortunately, there are many programs that may not be designed to use
5 | the keychain. This leads to problems keeping consistent identity.
6 | `certificate-sync` was built to solve problems of device identity at Dropbox.
7 | It can either import a certificate / private-key from disk and set the ACL,
8 | or it can take an item in the keychain and export it to disk (while also
9 | adjusting the ACL).
10 |
11 | Combining this with SCEP profiles allows for certificates to be used by
12 | other daemons on the box which are not keychain aware.
13 |
14 |
15 | ## LICENSE
16 |
17 | This project is licensed under the Apache License, Version 2.0
18 |
19 | ## Contributing
20 |
21 | Dropbox accepts contributions to open source projects. Before submitting a
22 | pull request, please have filed out a Dropbox Contributor License Agreement
23 | form.
24 |
25 | https://opensource.dropbox.com/cla/
26 |
--------------------------------------------------------------------------------
/test-suite/rsa_key.cer:
--------------------------------------------------------------------------------
1 | -----BEGIN CERTIFICATE-----
2 | MIICxzCCAa+gAwIBAgIBATANBgkqhkiG9w0BAQsFADAnMSUwIwYDVQQDFhxTZWxm
3 | IFNpZ25lZCBUZXN0IENlcnRpZmljYXRlMB4XDTY5MTIzMTIzNTk1OVoXDTcwMTIz
4 | MTIzNTk1OVowJzElMCMGA1UEAxYcU2VsZiBTaWduZWQgVGVzdCBDZXJ0aWZpY2F0
5 | ZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANCKVngfijP4Ssjh3r+W
6 | M8A4zYz0dj80NtM3oSxMDbABVaY75uW0CuebEVlwfJ5yQ3HPENyrcdF3UQMn1lPA
7 | OvfzNCJUh4y1nSRAzBod+uH7Zt3hm/Tgx2q9Tr9rwzIg0XuZyuxWp3TmIr+5U1Fm
8 | 8WT1OFGA3pWxadlgjPtJwsKwPlloVi/zPPm8MLGkBKhifbTs8AYMn4FaTaH+EzKa
9 | HBYENYlvIH65f7BMav706qiwcG0qk/+gEa2U6r6PVaDTK/sOoLD6D8+RnD/PjFBX
10 | ixdpq+dgI98Zbtz9YKgC++L+OGpAcIB2aoldF4BeQbRDuD3THL2XSdi/PgINJuFj
11 | qncCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAOcMGolcIBJOqb1p0IQhFHWkO0Ek3
12 | DRRs5BV3Tt4VU+CaP4nq+beNCLcglKrd7kXf2t3NgSc2GTH5IMXOOTeYB2IEmqQX
13 | o/d96F352WDbpGG2l61nbGb8mlHfDdKVHIaR6/M2kolCA7MkvTjAipWnXYekVxN2
14 | xOFI3gmCn5jP4Qjhh/bQ5vL0N1g1w8E7YVgYSIi0v3MS1WSJklXCj/3yQNtxj8ig
15 | QTPR6QR+Su8YEk44fW1MMNQTpYAe1mwiimA6Jegc7IXsdrMXPOUFSV76HwcibkOq
16 | HscqG194eZDaMQ3BYncBfTrZyfFKCYKy/youwtvRWGSStR05Qlqx7dka2A==
17 | -----END CERTIFICATE-----
18 |
--------------------------------------------------------------------------------
/certificate-sync/extensions.swift:
--------------------------------------------------------------------------------
1 | //
2 | // string_extensions.swift
3 | // certificate-sync
4 | //
5 | // Created by Rick Mark on 11/22/18.
6 | // Copyright © 2018 Dropbox. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | extension String {
12 | func toUnixPath() -> String {
13 | if self.starts(with: "file:") {
14 | let fileUrl = URL(string: self)
15 | return fileUrl!.path
16 | }
17 | else {
18 | return self
19 | }
20 | }
21 |
22 | func toFileURL() -> URL {
23 | return URL(fileURLWithPath: self)
24 | }
25 |
26 | func standardizePath() -> String {
27 | return (self as NSString).standardizingPath
28 | }
29 | }
30 |
31 |
32 | extension IteratorProtocol {
33 | mutating func toDictionary(functor: (T) -> (key: K, value: V)) -> [ K: V ] {
34 | var result = [ K: V]()
35 |
36 | let element = self.next()
37 |
38 | while (element != nil) {
39 | let mapping = functor(element as! T)
40 |
41 | result.updateValue(mapping.1, forKey: mapping.0)
42 | }
43 |
44 | return result
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/certificate-sync/configuration.swift:
--------------------------------------------------------------------------------
1 | //
2 | // configuration.swift
3 | // certificate-sync
4 | //
5 | // Created by Rick Mark on 11/22/18.
6 | // Copyright © 2018 Dropbox. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | class Configuration {
12 | let aclName: String
13 | let existing: [ ConfigurationItem ]
14 | let imports: [ ImportItem ]
15 |
16 | init(aclName: String, existing : [ ConfigurationItem ], imports: [ ImportItem ]) {
17 | self.aclName = aclName
18 | self.existing = existing
19 | self.imports = imports
20 | }
21 |
22 | static func read(path: URL) -> Configuration {
23 | let configuration = NSDictionary.init(contentsOf: path)
24 |
25 | assert(configuration != nil)
26 |
27 | let items = configuration!.value(forKey: "existing") as? [ [ String : Any ] ] ?? []
28 | let aclName = configuration!.value(forKey: "acl_name") as? String ?? "com.dropbox.certificate-sync.acl"
29 |
30 | let existing = items.map({ (item) -> ConfigurationItem in
31 | return ConfigurationItem.parse(configuration: item)
32 | })
33 |
34 | let imports = (configuration!.value(forKey: "import") as? [ [ String : Any ] ] ?? []).map { (item) -> ImportItem in
35 | return ImportItem.parse(configuration: item)
36 | }
37 |
38 | return Configuration(aclName: aclName, existing: existing, imports: imports)
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/test-suite/rsa_key.pem:
--------------------------------------------------------------------------------
1 | -----BEGIN RSA PRIVATE KEY-----
2 | MIIEowIBAAKCAQEAzbqkDwKRL4UhS54lG8hYVhoH5zHkt/1+YTYRsz/8jEPyN30x
3 | gfZv5NcQrUpqYCiEpcAEc0j9SVsCo1WMLzL05iQrdWB8gxVlM7l1Jo41G1H82yEI
4 | gYuvd8WN8ItKFSFiCRSZYKWt/TUkO5zZfeJQZdggzIUsopBUkNEUhJX1fgz1AlkZ
5 | UGfpstThW65bXHwTeYRX/LQNFgA5k47vFf4zeROoMUQPnIktQCobdm3hxXIQPGUt
6 | u95wUR/ZbVyrNdnGhFy69StOca4A6aHb/9UiOZOgT4AUh3VEqwQMjDmMrlzlxgP4
7 | JmKceKHkqY2WOjF3bdCh8EYAi/O4ky6uBEVJHQIDAQABAoIBAENWYtqS4YinT7Wb
8 | htPoKJnOHWYcG5vpehNBiluchwI2PzLQZIMAcbF6f3fpEpZ0zK4gApTlgBohkuR5
9 | XNAaIrSthBgAQ+D4AM8EVgcPpuxRUq06y7jzSrSuUf446T+vVJJaTrWmpSNq4Hgu
10 | p7WffOdI7btjn696BGP+O3QGTXs7Fin1S9CdVmZ5J+zX2glcpnyTHDssW0xO29eA
11 | WmIMHicMG5TjgS3LPlWBarbnRCoRaMRqNsNcHGLTJhTWTwsNr4hM3XQy+EFHaa9d
12 | PCmS5TyVEhWWi8c0IFTPHH5sSZK3W66AbmPOWsriiK3qyAR7FQzwMnKUE9zG1hv0
13 | wRhMcEECgYEA7URFkLceOqFHYvBlkzQoLy8sFJzXXWowbSwDrOJnCkRYIOjW7J/O
14 | bqTu6D/o0qw7Z+lDm3dIcgXHGUbnQ8kIgd8i0vdXlMdw9RHKFSpWboukIETiGHg0
15 | cw95wQ1uO0pk9bE2NZdTQu5KibGg1HTCJVClpVtlru6QN/w+qe+nfxECgYEA3fjr
16 | EUgubRAaHxOzubbx4NCb65tuMamEuw9P8nbqyAYmfKlaywRQh2PS55geIDti2gtr
17 | PSiQ43eRf6uILYo9CefTemYyw3vdwJbBPRqa5IUzVobL8FHAZpIbYAQEnSwnN12i
18 | /TyAnCFXKFy3Iy/QnR63ZkkEMAuzKg6vzCsVAU0CgYEA6OlSGMOJqyhS+W7fZ2eV
19 | 4uu9q+ZjC9KIA0NnwDjnyY5ZrMSk5tfhP4As/lRuaxCjqFe2BFFODpBZaBjQQRyB
20 | e2vdtZcUpHo8CncSF0DeY9o0nXDBOiAV7LVOYhKbGRzoir8RZf5Mv7FH/m8eYDDV
21 | vOe0E+TqppSNgkfeA9e8ZuECgYBWmSYcN+zk38NRHH6hlZ4H91rdMGZtOZVu5Spr
22 | jblLEBszx16ElcpVB7zgXZhjOsf2CT5tPcZI9/zrsiAOpD/r/WWj+vsTCOAaorYB
23 | AMAtJ0pAF5a4yywk2BuV+ftS1kb2crGD2nbScq9uL73DHIRmx/UFErdvhyD+53vu
24 | S2CGgQKBgDNtM9XNZD7k83Ller2qNIvxE5q9TxoOE2mzkvZtWBD5+9gHRNchU5fb
25 | 8QDfoVB7xMEtYK9zadfumRgXbBfWcZkCguewMTWD9BtXrGdjnTjK0QVc+1LYJjOs
26 | S9HtFmz46ENMOztf3mTGmfC+mY+19t+1N1Eb02z9MjkfxV0OD7NV
27 | -----END RSA PRIVATE KEY-----
28 |
--------------------------------------------------------------------------------
/test-suite/test_configuration.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | acl_name
6 | com.dropbox.certificate-sync.acl
7 | import
8 |
9 |
10 | path
11 | rsa_key.cer
12 | keychain
13 | demo.keychain
14 |
15 |
16 | label
17 | demo private key
18 | keychain
19 | demo.keychain
20 | claim_owner
21 |
22 | acl
23 |
24 | bundle:com.google.Chrome
25 |
26 | path
27 | rsa_key.pem
28 |
29 |
30 | existing
31 |
32 |
33 | claim_owner
34 |
35 | export
36 |
37 |
38 | format
39 | pem-cer
40 | path
41 | export-certificate-example.cer
42 |
43 |
44 | format
45 | pem
46 | path
47 | export-pem-example.pem
48 |
49 |
50 | acl
51 |
52 | bundle:com.google.Chrome
53 |
54 | issuer
55 | MHcxFzAVBgNVBAMMDmRlbW8ubG9jYWxob3N0MRkwFwYDVQQKDBBDZXJ0aWZpY2F0ZSBTeW5jMRIwEAYDVQQLDAlUZXN0IFVuaXQxCzAJBgNVBAgMAktTMQswCQYDVQQGEwJVUzETMBEGA1UEBwwKU21hbGx2aWxsZQ==
56 | keychain
57 | demo.keychain
58 |
59 |
60 |
61 |
62 |
--------------------------------------------------------------------------------
/certificate-sync/export_configuration_item.swift:
--------------------------------------------------------------------------------
1 | //
2 | // export_configuration_item.swift
3 | // certificate-sync
4 | //
5 | // Created by Rick Mark on 11/22/18.
6 | // Copyright © 2018 Dropbox. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | class ExportConfigurationItem {
12 |
13 | let format: SecExternalFormat
14 | let path: URL
15 | let owner: String?
16 | let mode: mode_t
17 | let pemEncode: Bool
18 | let password: String?
19 |
20 | init(format: SecExternalFormat, path: URL, owner: String?, mode: mode_t, pemArmor: Bool, password: String?) {
21 | self.format = format
22 | self.path = path
23 | self.owner = owner
24 | self.mode = mode
25 | self.pemEncode = pemArmor
26 | self.password = password
27 | }
28 |
29 | static func parse(configuration: [ String : Any ]) -> ExportConfigurationItem {
30 | var format: SecExternalFormat
31 | var pemArmor = false
32 | switch configuration["format"] as! String {
33 | case "pem":
34 | format = SecExternalFormat.formatPEMSequence
35 | case "pem-cer":
36 | format = SecExternalFormat.formatX509Cert
37 | pemArmor = true
38 | default:
39 | format = SecExternalFormat.formatX509Cert
40 | }
41 |
42 | let mode = (configuration["mode"] ?? UInt16(600)) as! mode_t
43 |
44 | let owner = configuration["owner"] as? String
45 |
46 | let password = configuration["password"] as? String
47 |
48 | let path = (configuration["path"] as! String).toFileURL()
49 |
50 | return ExportConfigurationItem(format: format, path: path, owner: owner, mode: mode, pemArmor: pemArmor, password: password)
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/certificate-sync/acl_configuration_item.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | // certificate-sync
4 | //
5 | // Created by Rick Mark on 11/22/18.
6 | // Copyright © 2018 Dropbox. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import AppKit
11 |
12 | class ACLConfigurationItem {
13 | static let bundlePrefix = "bundle:"
14 | let bundleId: String?
15 | let path: String
16 |
17 | init(fromPath : String) {
18 | bundleId = nil
19 | path = fromPath.toUnixPath()
20 | }
21 |
22 | init(fromPath : String, bundleId: String) {
23 | self.bundleId = bundleId
24 | path = fromPath.toUnixPath()
25 | }
26 |
27 | static func parse(configuration: String) -> [ ACLConfigurationItem ] {
28 | if configuration.starts(with: bundlePrefix) == false {
29 | return [ ACLConfigurationItem(fromPath: configuration) ]
30 | }
31 |
32 | let prefixIndex = configuration.index(after: configuration.firstIndex(of: ":")!)
33 | let bundleId = String(configuration[prefixIndex...])
34 |
35 | var error: Unmanaged?
36 |
37 | let appBundles = LSCopyApplicationURLsForBundleIdentifier(bundleId as CFString, &error)?.takeRetainedValue()
38 |
39 | if appBundles == nil {
40 | return []
41 | }
42 |
43 | return (appBundles as! [ URL ]).map { (appBundle) -> ACLConfigurationItem in
44 | ACLConfigurationItem(fromPath: appBundle.path, bundleId: bundleId)
45 | }
46 | }
47 |
48 | lazy var trustedAppliction: SecTrustedApplication = {
49 | var trustedApplication: SecTrustedApplication?
50 |
51 | let createResult = SecTrustedApplicationCreateFromPath(self.path, &trustedApplication)
52 |
53 | assert(createResult == kOSReturnSuccess)
54 | assert(trustedApplication != nil)
55 |
56 | return trustedApplication!
57 | }()
58 | }
59 |
--------------------------------------------------------------------------------
/certificate-sync/import_item.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ImportItem.swift
3 | // certificate-sync
4 | //
5 | // Created by Rick Mark on 12/11/18.
6 | // Copyright © 2018 Dropbox. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | class ImportItem {
12 | let path: URL
13 | let acls: [ ACLConfigurationItem ]
14 | let claimOwner: Bool
15 | let keychainPath: String
16 | let password: String?
17 | let label: String?
18 |
19 | init(label: String?, path: URL, aclEntries: [ ACLConfigurationItem ], claimOwner: Bool, keychainPath: String, password: String?) {
20 | self.label = label
21 | self.path = path
22 | self.acls = aclEntries
23 | self.claimOwner = claimOwner
24 | self.keychainPath = keychainPath
25 | self.password = password
26 | }
27 |
28 | static func parse(configuration: [ String : Any ]) -> ImportItem {
29 | let label = configuration["label"] as? String
30 | let path = URL(fileURLWithPath: configuration["path"] as! String)
31 | let claimOwner = configuration["claim_owner"] as? Bool ?? false
32 | var keychainPath: String!
33 | let keychainValue = configuration["keychain"] as? String ?? "system"
34 | let password = configuration["password"] as? String
35 |
36 | switch keychainValue {
37 | case "system":
38 | keychainPath = "/Library/Keychains/System.keychain"
39 | case "user":
40 | keychainPath = ("~/Library/Keychains/login.keychain" as NSString).expandingTildeInPath
41 | default:
42 | keychainPath = keychainValue
43 | }
44 |
45 | let acls = (configuration["acl"] as? [ String ] ?? []).flatMap { (item) -> [ ACLConfigurationItem ] in
46 | return ACLConfigurationItem.parse(configuration: item)
47 | }
48 |
49 | return ImportItem(label: label, path: path, aclEntries: acls, claimOwner: claimOwner, keychainPath: keychainPath, password: password)
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/certificate-sync/configuration_item.swift:
--------------------------------------------------------------------------------
1 | //
2 | // configuration_item.swift
3 | // certificate-sync
4 | //
5 | // Created by Rick Mark on 11/21/18.
6 | // Copyright © 2018 Dropbox. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | class ConfigurationItem {
12 | let issuer: Data
13 | let exports: [ExportConfigurationItem]
14 | let acls: [ACLConfigurationItem]
15 | let keychainPath: String
16 | let password: String?
17 | let claimOwner: Bool
18 |
19 | /// You should really never use the password form, this is for unit testing only
20 | /// Will attempt to make debug pramgma later TODO
21 | init(issuer: Data, exports: [ExportConfigurationItem], acls: [ACLConfigurationItem], keychainPath: String, password: String?, claimOwner: Bool) {
22 | self.issuer = issuer
23 | self.exports = exports
24 | self.acls = acls
25 | self.keychainPath = keychainPath
26 | self.password = password
27 | self.claimOwner = claimOwner
28 | }
29 |
30 | static func parse(configuration : [ String : Any ]) -> ConfigurationItem {
31 | let issuer = Data(base64Encoded: configuration["issuer"] as! String)!
32 | var keychainPath: String!
33 | let keychainValue = configuration["keychain"] as! String
34 | let password = configuration["password"] as? String
35 | let claimOwner = configuration["claim_owner"] as? Bool ?? true
36 |
37 | switch keychainValue {
38 | case "system":
39 | keychainPath = "/Library/Keychains/System.keychain"
40 | case "user":
41 | keychainPath = ("~/Library/Keychains/login.keychain" as NSString).expandingTildeInPath
42 | default:
43 | keychainPath = keychainValue
44 | }
45 |
46 |
47 | let exports = (configuration["export"] as! [ [ String : Any ] ]).map { (item) -> ExportConfigurationItem in
48 | return ExportConfigurationItem.parse(configuration: item)
49 | }
50 |
51 | let acls = (configuration["acl"] as! [ String ]).flatMap { (item) -> [ ACLConfigurationItem ] in
52 | return ACLConfigurationItem.parse(configuration: item)
53 | }
54 |
55 | return ConfigurationItem(issuer: issuer, exports: exports, acls: acls, keychainPath: keychainPath, password: password, claimOwner: claimOwner)
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/certificate-sync.xcodeproj/xcshareddata/xcschemes/certificate-sync.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
32 |
33 |
39 |
40 |
41 |
42 |
43 |
44 |
55 |
57 |
63 |
64 |
65 |
66 |
69 |
70 |
71 |
72 |
73 |
74 |
80 |
82 |
88 |
89 |
90 |
91 |
93 |
94 |
97 |
98 |
99 |
--------------------------------------------------------------------------------
/test-suite/test_suite.swift:
--------------------------------------------------------------------------------
1 | //
2 | // test_suite.swift
3 | // test-suite
4 | //
5 | // Created by Rick Mark on 11/26/18.
6 | // Copyright © 2018 Dropbox. All rights reserved.
7 | //
8 |
9 | import XCTest
10 |
11 |
12 | class test_suite: XCTestCase {
13 |
14 | var testConfiguration: URL!
15 | let tempDirectoryURL = NSURL.fileURL(withPath: NSTemporaryDirectory(), isDirectory: true)
16 | let tag = "com.dropbox.entsec.certificate-sync.tests"
17 |
18 | override func setUp() {
19 | let selfBundle = Bundle(for: type(of: self))
20 | testConfiguration = selfBundle.url(forResource: "test_configuration", withExtension: "plist")
21 |
22 | FileManager.default.changeCurrentDirectoryPath(tempDirectoryURL.absoluteString.toUnixPath())
23 | }
24 |
25 | override func tearDown() {
26 | // Put teardown code here. This method is called after the invocation of each test method in the class.
27 | }
28 |
29 | private func getKeychainCopy() -> (URL, SecKeychain) {
30 | let tempKeychainFile = tempDirectoryURL.appendingPathComponent("\(UUID().uuidString)-demo.keychain")
31 | print(tempKeychainFile)
32 |
33 | var keychain: SecKeychain?
34 | assert(SecKeychainCreate(tempKeychainFile.absoluteString.toUnixPath(), 0, "", false, nil, &keychain) == kOSReturnSuccess)
35 |
36 | var privateKey: SecKey!
37 | var publicKey: SecKey!
38 |
39 | let generationAttributes: [String: Any] = [
40 | kSecAttrKeyType as String: kSecAttrKeyTypeRSA,
41 | kSecAttrKeySizeInBits as String: 2048,
42 | kSecPrivateKeyAttrs as String: [
43 | kSecAttrIsPermanent as String: false
44 | ],
45 | kSecPublicKeyAttrs as String: [
46 | kSecAttrIsPermanent as String: false
47 | ]
48 | ]
49 |
50 | assert(SecKeyGeneratePair(generationAttributes as CFDictionary, &publicKey, &privateKey) == kOSReturnSuccess)
51 |
52 | let storePublicKey: [String: Any] = [
53 | kSecClass as String: kSecClassKey,
54 | kSecAttrApplicationTag as String: "\(tag).public".data(using: .utf8)!,
55 | kSecValueRef as String: publicKey!,
56 | kSecUseKeychain as String: keychain!
57 | ]
58 |
59 | let storePrivateKey: [String: Any] = [
60 | kSecClass as String: kSecClassKey,
61 | kSecAttrApplicationTag as String: tag,
62 | kSecValueRef as String: privateKey!,
63 | kSecUseKeychain as String: keychain!
64 | ]
65 |
66 | var publicKeyResult: CFTypeRef?
67 | var privateKeyResult: CFTypeRef?
68 |
69 | assert(SecItemAdd(storePublicKey as CFDictionary, &publicKeyResult) == kOSReturnSuccess)
70 | assert(SecItemAdd(storePrivateKey as CFDictionary, &privateKeyResult) == kOSReturnSuccess)
71 |
72 | privateKey = (privateKeyResult! as! SecKey)
73 | publicKey = (publicKeyResult! as! SecKey)
74 |
75 | var privateKeyPem: CFData?
76 | var exportKeyParameters = SecItemImportExportKeyParameters()
77 |
78 | assert(SecItemExport(privateKey, SecExternalFormat.formatPEMSequence, SecItemImportExportFlags(), &exportKeyParameters, &privateKeyPem) == kOSReturnSuccess)
79 |
80 | var privateKeyMarshaledData = privateKeyPem! as Data
81 |
82 | let keyBasicIO = privateKeyMarshaledData.withUnsafeBytes { (data) -> UnsafeMutablePointer? in
83 | return BIO_new_mem_buf(data, Int32(privateKeyMarshaledData.count))
84 | }
85 |
86 | let openSSLPrivateKey = PEM_read_bio_PrivateKey(keyBasicIO!, nil, nil, nil)
87 |
88 | BIO_free(keyBasicIO)
89 |
90 | assert(openSSLPrivateKey != nil)
91 |
92 | let certificate = X509_new()
93 |
94 | assert(X509_set_version(certificate, 2) == 1)
95 | assert(ASN1_INTEGER_set(X509_get_serialNumber(certificate), 1) == 1)
96 |
97 | let currentTime = timegm(nil)
98 | var notBefore = ASN1_TIME()
99 | var notAfter = ASN1_TIME()
100 |
101 | assert(ASN1_TIME_set(¬Before, currentTime) != nil)
102 | assert(ASN1_TIME_adj(¬After, currentTime, 365, 0) != nil)
103 |
104 | assert(X509_set_notBefore(certificate!, ¬Before) == 1)
105 | assert(X509_set_notAfter(certificate!, ¬After) == 1)
106 |
107 | let name = X509_NAME_new()
108 |
109 | assert(X509_NAME_add_entry_by_txt(name, "CN", V_ASN1_IA5STRING, "Self Signed Test Certificate", -1, -1, 0) == 1)
110 |
111 | assert(X509_set_subject_name(certificate!, name) == 1)
112 | assert(X509_set_issuer_name(certificate!, name) == 1)
113 |
114 | assert(X509_set_pubkey(certificate!, openSSLPrivateKey) == 1)
115 |
116 | assert(X509_sign(certificate!, openSSLPrivateKey, EVP_sha256()) != 0)
117 |
118 | let certificatePEM = BIO_new(BIO_s_mem())
119 |
120 | assert(PEM_write_bio_X509(certificatePEM, certificate!) != 0)
121 |
122 | var data: UnsafeMutableRawPointer?
123 | let dataSize = BIO_ctrl(certificatePEM, BIO_CTRL_INFO, 0, &data)
124 |
125 | assert(dataSize > 0)
126 | assert(data != nil)
127 |
128 | let certificateData = Data(bytes: data!, count: dataSize)
129 |
130 | X509_NAME_free(name)
131 | X509_free(certificate)
132 | EVP_PKEY_free(openSSLPrivateKey)
133 |
134 | BIO_free(certificatePEM)
135 |
136 | var certificateImportResults: CFArray?
137 | var importItemType = SecExternalItemType.itemTypeCertificate
138 | assert(SecItemImport(certificateData as CFData, nil, nil, &importItemType, SecItemImportExportFlags(rawValue: 0), nil, keychain!, &certificateImportResults) == kOSReturnSuccess)
139 |
140 | var identity: SecIdentity?
141 | let certificateKeychainItem = (certificateImportResults as! [ SecCertificate ]).first!
142 | assert(SecIdentityCreateWithCertificate(keychain!, certificateKeychainItem, &identity) == kOSReturnSuccess)
143 |
144 | return (tempKeychainFile, keychain!)
145 | }
146 |
147 | private func modifyConfigurationForDemoKeychain(configuration: Configuration, demoKeychainPath: URL, issuer: Data) -> Configuration {
148 | let mapping = configuration.existing.map { (item) -> ConfigurationItem in
149 | if item.keychainPath == "demo.keychain" {
150 | return ConfigurationItem(issuer: issuer, exports: item.exports, acls: item.acls, keychainPath: demoKeychainPath.absoluteString, password: item.password, claimOwner: item.claimOwner)
151 | }
152 | else {
153 | return item
154 | }
155 | }
156 |
157 | let selfBundle = Bundle(for: type(of: self))
158 | let imports = configuration.imports.map { (item) -> ImportItem in
159 | let path = selfBundle.resourceURL?.appendingPathComponent(item.path.lastPathComponent)
160 | let label = item.label ?? "certificate-sync imported item"
161 |
162 | if item.keychainPath == "demo.keychain" {
163 | return ImportItem(label: label, path: path!, aclEntries: item.acls, claimOwner: item.claimOwner, keychainPath: demoKeychainPath.absoluteString, password: item.password)
164 | }
165 | else {
166 | return item
167 | }
168 | }
169 |
170 | return Configuration(aclName: configuration.aclName, existing: mapping, imports: imports)
171 | }
172 |
173 | private func getCertificateIssuer(keychain: SecKeychain) -> Data {
174 | let certificateQuery: [String : Any] = [
175 | kSecClass as String: kSecClassIdentity,
176 | kSecMatchSearchList as String: [ keychain ],
177 | kSecMatchLimit as String: kSecMatchLimitOne,
178 | kSecReturnAttributes as String: true
179 | ]
180 |
181 | var resultItem: CFTypeRef?
182 | assert(SecItemCopyMatching(certificateQuery as CFDictionary, &resultItem) == kOSReturnSuccess)
183 | assert(resultItem != nil)
184 |
185 | let resultDictionaryArray = resultItem as! [ String: Any ]
186 |
187 | return resultDictionaryArray[kSecAttrIssuer as String] as! Data
188 | }
189 |
190 | func testLoadConfiguration() {
191 | let _ = Configuration.read(path: testConfiguration)
192 | }
193 |
194 | func testImportItems() {
195 | let (keychainCopy, keychain) = getKeychainCopy()
196 | let issuer = getCertificateIssuer(keychain: keychain)
197 |
198 | let configuration = modifyConfigurationForDemoKeychain(configuration: Configuration.read(path: testConfiguration), demoKeychainPath: keychainCopy, issuer: issuer)
199 |
200 | let syncronizer = Syncronizer(configuration: configuration)
201 |
202 | let itemCount = countKeychainItems(keychain: keychain, type: kSecClassKey)
203 |
204 | syncronizer.importKeychainItems(items: configuration.imports)
205 |
206 | assert(countKeychainItems(keychain: keychain, type: kSecClassKey) == itemCount + 1)
207 | }
208 |
209 | private func countKeychainItems(keychain: SecKeychain, type: CFString) -> Int {
210 | var result: CFTypeRef?
211 |
212 | let query = [ kSecClass : kSecClassKey,
213 | kSecMatchLimit : kSecMatchLimitAll,
214 | kSecMatchSearchList : [ keychain ],
215 | kSecReturnAttributes : true ] as [CFString : Any]
216 |
217 | assert(SecItemCopyMatching(query as CFDictionary, &result) == kOSReturnSuccess)
218 |
219 | let resultsArray = result as! [ SecKey ]
220 |
221 | return resultsArray.count
222 | }
223 |
224 | func testMapIdentities() {
225 | let (keychainCopy, keychain) = getKeychainCopy()
226 |
227 | let issuer = getCertificateIssuer(keychain: keychain)
228 | let configuration = modifyConfigurationForDemoKeychain(configuration: Configuration.read(path: testConfiguration), demoKeychainPath: keychainCopy, issuer: issuer)
229 |
230 | let syncronizer = Syncronizer(configuration: configuration)
231 |
232 | let results = syncronizer.mapIdentities()
233 |
234 | assert(results.count == 1)
235 |
236 | let itemCount = countKeychainItems(keychain: keychain, type: kSecClassKey)
237 |
238 | assert(itemCount == 2)
239 | }
240 |
241 | func testKeychainPathValid() {
242 | let (keychainCopy, keychain) = getKeychainCopy()
243 |
244 | var pathLength: UInt32 = 0
245 | var pathBuffer = UnsafeMutablePointer.allocate(capacity: 0)
246 |
247 | assert(SecKeychainGetPath(keychain, &pathLength, pathBuffer) == errSecBufferTooSmall)
248 | pathLength += 1
249 | pathBuffer = UnsafeMutablePointer.allocate(capacity: Int(pathLength))
250 | assert(SecKeychainGetPath(keychain, &pathLength, pathBuffer) == kOSReturnSuccess)
251 |
252 | let path = String(cString: pathBuffer)
253 |
254 | assert(path.standardizePath() == keychainCopy.absoluteString.toUnixPath().standardizePath())
255 | }
256 |
257 | func testMapSetOwnerACL() {
258 | let (keychainCopy, keychain) = getKeychainCopy()
259 | let issuer = getCertificateIssuer(keychain: keychain)
260 |
261 | let configuration = modifyConfigurationForDemoKeychain(configuration: Configuration.read(path: testConfiguration), demoKeychainPath: keychainCopy, issuer: issuer)
262 |
263 | let syncronizer = Syncronizer(configuration: configuration)
264 |
265 | let results = syncronizer.mapIdentities()
266 |
267 | assert(results.count == 1)
268 |
269 | syncronizer.ensureSelfInOwnerACL(identity: (results.first?.identity)!)
270 | }
271 |
272 | func testExportItems() {
273 | let (keychainCopy, keychain) = getKeychainCopy()
274 | let issuer = getCertificateIssuer(keychain: keychain)
275 |
276 | let configuration = modifyConfigurationForDemoKeychain(configuration: Configuration.read(path: testConfiguration), demoKeychainPath: keychainCopy, issuer: issuer)
277 |
278 | let syncronizer = Syncronizer(configuration: configuration)
279 |
280 | let results = syncronizer.mapIdentities()
281 |
282 | assert(results.count == 1)
283 |
284 | syncronizer.exportKeychainItems(items: results)
285 |
286 | for item in configuration.existing {
287 | for export in item.exports {
288 | assert(FileManager.default.fileExists(atPath: export.path.absoluteString.toUnixPath()))
289 | }
290 | }
291 | }
292 |
293 | func testSetACLForApplications() {
294 | let (keychainCopy, keychain) = getKeychainCopy()
295 | let issuer = getCertificateIssuer(keychain: keychain)
296 |
297 | let configuration = modifyConfigurationForDemoKeychain(configuration: Configuration.read(path: testConfiguration), demoKeychainPath: keychainCopy, issuer: issuer)
298 |
299 | let syncronizer = Syncronizer(configuration: configuration)
300 |
301 | syncronizer.ensureACLContainsApps(aclName: configuration.aclName, items: syncronizer.mapIdentities())
302 | }
303 |
304 | func testSetupAllACLs() {
305 | let (keychainCopy, keychain) = getKeychainCopy()
306 | let issuer = getCertificateIssuer(keychain: keychain)
307 |
308 | let configuration = modifyConfigurationForDemoKeychain(configuration: Configuration.read(path: testConfiguration), demoKeychainPath: keychainCopy, issuer: issuer)
309 |
310 | let syncronizer = Syncronizer(configuration: configuration)
311 | let items = syncronizer.mapIdentities()
312 |
313 | for item in items {
314 | syncronizer.ensureSelfInOwnerACL(identity: item.identity)
315 | }
316 | syncronizer.ensureACLContainsApps(aclName: configuration.aclName, items: items)
317 | }
318 |
319 | func testSetupAllACLsIdempotent() {
320 | let (keychainCopy, keychain) = getKeychainCopy()
321 | let issuer = getCertificateIssuer(keychain: keychain)
322 |
323 | let configuration = modifyConfigurationForDemoKeychain(configuration: Configuration.read(path: testConfiguration), demoKeychainPath: keychainCopy, issuer: issuer)
324 |
325 | let syncronizer = Syncronizer(configuration: configuration)
326 | let items = syncronizer.mapIdentities()
327 |
328 | for item in items {
329 | syncronizer.ensureSelfInOwnerACL(identity: item.identity)
330 | }
331 | syncronizer.ensureACLContainsApps(aclName: configuration.aclName, items: items)
332 |
333 | for item in items {
334 | syncronizer.ensureSelfInOwnerACL(identity: item.identity)
335 | }
336 | syncronizer.ensureACLContainsApps(aclName: configuration.aclName, items: items)
337 | }
338 | }
339 |
--------------------------------------------------------------------------------
/certificate-sync/syncronizer.swift:
--------------------------------------------------------------------------------
1 | //
2 | // syncronizer.swift
3 | // certificate-sync
4 | //
5 | // Created by Rick Mark on 11/22/18.
6 | // Copyright © 2018 Dropbox. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | class Syncronizer {
12 | let requiredAuthorization = [ kSecACLAuthorizationSign,
13 | kSecACLAuthorizationMAC,
14 | kSecACLAuthorizationDerive,
15 | kSecACLAuthorizationDecrypt ]
16 |
17 | let configuration: Configuration
18 |
19 | var keychains: [ String : SecKeychain ]?
20 |
21 | init(configuration: Configuration) {
22 | self.configuration = configuration
23 | }
24 |
25 | func run() {
26 | let identities = mapIdentities()
27 |
28 | for (item, identity) in identities {
29 | if item.claimOwner {
30 | ensureSelfInOwnerACL(identity: identity)
31 | }
32 | }
33 |
34 | ensureACLContainsApps(aclName: configuration.aclName, items: identities)
35 |
36 | exportKeychainItems(items: identities)
37 |
38 | importKeychainItems(items: configuration.imports)
39 | }
40 |
41 | func openKeychain(path: String, password: String?) -> SecKeychain {
42 | var keychain: SecKeychain?
43 | assert(SecKeychainOpen(path, &keychain) == kOSReturnSuccess)
44 |
45 | assert(SecKeychainOpen(path.toUnixPath(), &keychain) == kOSReturnSuccess)
46 |
47 | var keychainStatus = SecKeychainStatus()
48 | assert(SecKeychainGetStatus(keychain!, &keychainStatus) == kOSReturnSuccess)
49 |
50 | if ((keychainStatus & kSecUnlockStateStatus) == 0) {
51 | if password != nil {
52 | assert(SecKeychainUnlock(keychain!, UInt32(password!.count), password!, true) == kOSReturnSuccess)
53 | }
54 | else {
55 | assert(SecKeychainUnlock(keychain!, 0, "", false) == kOSReturnSuccess)
56 | }
57 | }
58 |
59 | assert(SecKeychainGetStatus(keychain!, &keychainStatus) == kOSReturnSuccess)
60 | assert((keychainStatus & kSecUnlockStateStatus > 0) && (keychainStatus & kSecReadPermStatus > 0) && (keychainStatus & kSecWritePermStatus > 0))
61 |
62 | return keychain!
63 | }
64 |
65 | func getKeychains() -> [ String : SecKeychain ] {
66 | if keychains != nil {
67 | return self.keychains!
68 | }
69 |
70 | self.keychains = [ String : SecKeychain ]()
71 |
72 | for item in configuration.existing {
73 | if self.keychains!.keys.contains(item.keychainPath) == false {
74 | self.keychains![item.keychainPath] = openKeychain(path: item.keychainPath, password: item.password)
75 | }
76 | }
77 |
78 | for item in configuration.imports {
79 | if self.keychains!.keys.contains(item.keychainPath) == false {
80 | self.keychains![item.keychainPath] = openKeychain(path: item.keychainPath, password: item.password)
81 | }
82 | }
83 |
84 | return self.keychains!
85 | }
86 |
87 | func mapIdentities() -> [ ( configuration: ConfigurationItem, identity: SecIdentity ) ] {
88 | var results = [ (ConfigurationItem, SecIdentity ) ]()
89 |
90 | for (path, keychain) in getKeychains() {
91 |
92 | let query = [kSecClass: kSecClassIdentity,
93 | kSecReturnRef: true,
94 | kSecMatchLimit: kSecMatchLimitAll,
95 | kSecReturnAttributes: true,
96 | kSecMatchSearchList: [ keychain ]
97 | ] as CFDictionary
98 |
99 | var items: CFTypeRef?
100 |
101 | assert(SecItemCopyMatching(query, &items) == kOSReturnSuccess)
102 |
103 | for identityItem in (items! as! [[String: Any]]) {
104 | let identityIssuer = identityItem[kSecAttrIssuer as String] as! NSData
105 |
106 | let configurationItems = configuration.existing.filter { (item) -> Bool in
107 | return item.issuer == identityIssuer as Data
108 | }
109 |
110 | for configurationItem in configurationItems.filter({ (configurationItem) -> Bool in
111 | configurationItem.keychainPath == path
112 | }) {
113 | let tuple = ( configurationItem, identityItem[kSecValueRef as String] as! SecIdentity )
114 | results.append(tuple)
115 | }
116 | }
117 | }
118 |
119 | return results
120 | }
121 |
122 | func trustedApplicationForSelf() -> SecTrustedApplication {
123 | var trustedApplication: SecTrustedApplication?
124 |
125 | let createResult = SecTrustedApplicationCreateFromPath(CommandLine.arguments.first!, &trustedApplication)
126 |
127 | assert(createResult == kOSReturnSuccess)
128 | assert(trustedApplication != nil)
129 |
130 | return trustedApplication!
131 | }
132 |
133 | func ensureSelfInOwnerACL(identity: SecIdentity) {
134 | var privateKey: SecKey?
135 |
136 | assert(SecIdentityCopyPrivateKey(identity, &privateKey) == kOSReturnSuccess)
137 | assert(privateKey != nil)
138 |
139 | let _ = ensureSelfInKeyOwnerACL(privateKey: privateKey!)
140 | }
141 |
142 | func ensureSelfInKeyOwnerACL(privateKey: SecKey, saveResult: Bool = true) -> SecAccess? {
143 | var access: SecAccess?
144 |
145 | let privateKeyItem = (privateKey as Any) as! SecKeychainItem
146 |
147 | assert(SecKeychainItemCopyAccess(privateKeyItem, &access) == kOSReturnSuccess)
148 | assert(access != nil)
149 |
150 |
151 | let selfTrustedApplication = trustedApplicationForSelf()
152 |
153 | let aclResults = SecAccessCopyMatchingACLList(access!, kSecACLAuthorizationChangeACL)
154 |
155 | let acls = aclResults as! [ SecACL ]
156 |
157 | for acl in acls {
158 | var applicationList: CFArray?
159 | var description: CFString?
160 | var promptSelector = SecKeychainPromptSelector()
161 |
162 | assert(SecACLCopyContents(acl, &applicationList, &description, &promptSelector) == kOSReturnSuccess)
163 |
164 | if description == nil {
165 | description = "" as CFString
166 | }
167 |
168 | applicationList = updateApplicationList(existing: applicationList, applications: [ selfTrustedApplication ])
169 |
170 | assert(SecACLSetContents(acl, applicationList! as CFArray, description!, promptSelector) == kOSReturnSuccess)
171 |
172 | if saveResult == true {
173 | let setAccessResult = SecKeychainItemSetAccess(privateKeyItem, access!)
174 | assert(setAccessResult == kOSReturnSuccess)
175 | }
176 | return access
177 | }
178 |
179 | return nil
180 | }
181 |
182 | private func updateApplicationList(existing: CFArray?, applications: [ SecTrustedApplication ]) -> CFArray {
183 | var applicationListArray = existing == nil ? [ SecTrustedApplication ]() : existing as! [ SecTrustedApplication ]
184 |
185 | let existingApplicationData = applicationListArray.map { (item) -> Data in
186 | var data: CFData?
187 |
188 | assert(SecTrustedApplicationCopyData(item, &data) == kOSReturnSuccess)
189 |
190 | return data! as Data
191 | }
192 |
193 | for applicationToAdd in applications {
194 | var data: CFData?
195 |
196 | assert(SecTrustedApplicationCopyData(applicationToAdd, &data) == kOSReturnSuccess)
197 |
198 | let castData = data! as Data
199 |
200 | if existingApplicationData.contains(castData) == false {
201 | applicationListArray.append(applicationToAdd)
202 | }
203 | }
204 |
205 | return applicationListArray as CFArray
206 | }
207 |
208 | func ensureItemHasACL(aclName: String, identity: SecIdentity, acl: [ ACLConfigurationItem ]) {
209 | var privateKey: SecKey?
210 |
211 | assert(SecIdentityCopyPrivateKey(identity, &privateKey) == kOSReturnSuccess)
212 | assert(privateKey != nil)
213 |
214 | ensureItemKeyHasACL(aclName: aclName, privateKey: privateKey!, acl: acl)
215 | }
216 |
217 | func ensureItemKeyHasACL(aclName: String, privateKey: SecKey, acl: [ ACLConfigurationItem ], access existingAccess: SecAccess? = nil, saveResult: Bool = true) {
218 |
219 | var aclListArray: CFArray?
220 | var access = existingAccess
221 |
222 | let privateKeyItem = (privateKey as Any) as! SecKeychainItem
223 |
224 | if access == nil {
225 | assert(SecKeychainItemCopyAccess(privateKeyItem, &access) == kOSReturnSuccess)
226 | assert(access != nil)
227 | }
228 |
229 | assert(SecAccessCopyACLList(access!, &aclListArray) == kOSReturnSuccess)
230 | assert(aclListArray != nil)
231 | let acls = aclListArray as! [ SecACL ]
232 |
233 | var foundACL = false
234 |
235 | for existingACL in acls {
236 | var applicationListArray: CFArray?
237 | var descriptionString: CFString?
238 | var promptSelector = SecKeychainPromptSelector()
239 |
240 | assert(SecACLCopyContents(existingACL, &applicationListArray, &descriptionString, &promptSelector) == kOSReturnSuccess)
241 |
242 | let description = descriptionString as String?
243 |
244 | if description != aclName { continue }
245 |
246 | foundACL = true
247 |
248 | let applications = acl.map { (aclEntry) -> SecTrustedApplication in
249 | return aclEntry.trustedAppliction
250 | }
251 |
252 | applicationListArray = updateApplicationList(existing: applicationListArray, applications: applications)
253 |
254 | var authorizations = SecACLCopyAuthorizations(existingACL) as! [ CFString ]
255 |
256 | for authorization in requiredAuthorization {
257 | if authorizations.contains(authorization) == false {
258 | authorizations.append(authorization)
259 | }
260 | }
261 |
262 | assert(SecACLUpdateAuthorizations(existingACL, authorizations as CFArray) == kOSReturnSuccess)
263 |
264 | assert(SecACLSetContents(existingACL, applicationListArray, description! as CFString, promptSelector) == kOSReturnSuccess)
265 | }
266 |
267 | if foundACL == false {
268 | var newACL: SecACL?
269 | let promptSelector = SecKeychainPromptSelector()
270 |
271 | let applications = acl.map { (item) -> SecTrustedApplication in
272 | item.trustedAppliction
273 | } as CFArray
274 |
275 | assert(SecACLCreateWithSimpleContents(access!, applications, aclName as CFString, promptSelector, &newACL) == kOSReturnSuccess)
276 | assert(newACL != nil)
277 |
278 | let authorizations = requiredAuthorization as CFArray
279 |
280 | assert(SecACLUpdateAuthorizations(newACL!, authorizations) == kOSReturnSuccess)
281 | }
282 |
283 | if saveResult == true {
284 | assert(SecKeychainItemSetAccess(privateKeyItem, access!) == kOSReturnSuccess)
285 | }
286 | }
287 |
288 | func ensureACLContainsApps(aclName: String, items: [ ( configuration: ConfigurationItem, identity: SecIdentity ) ]) {
289 | for item in items {
290 | ensureItemHasACL(aclName: aclName, identity: item.identity, acl: item.configuration.acls)
291 | }
292 | }
293 |
294 | func importKeychainItems(items: [ ImportItem ]) {
295 | for importItem in items {
296 |
297 | let keychain = getKeychains()[importItem.keychainPath]
298 | let path = importItem.path.absoluteString.toUnixPath()
299 |
300 | let data = NSData.init(contentsOf: importItem.path)
301 | var externalFormat = SecExternalFormat.formatUnknown
302 | var itemType = SecExternalItemType.itemTypeUnknown
303 | let importFlags = SecItemImportExportFlags()
304 |
305 | var importParameters = SecItemImportExportKeyParameters()
306 |
307 | var resultItems: CFArray? = NSMutableArray()
308 |
309 | let result = SecItemImport(data!, path as CFString, &externalFormat, &itemType, importFlags, &importParameters, nil, &resultItems)
310 | assert(result == kOSReturnSuccess || result == errSecDuplicateItem)
311 |
312 | for importedItem in resultItems as! [ SecKeychainItem ] {
313 | var access: SecAccess?
314 |
315 | if CFGetTypeID(importedItem) == SecKeyGetTypeID() {
316 | let key = (importedItem as Any) as! SecKey
317 |
318 |
319 | if importItem.claimOwner {
320 | access = ensureSelfInKeyOwnerACL(privateKey: key, saveResult: false)
321 | }
322 |
323 | ensureItemKeyHasACL(aclName: configuration.aclName, privateKey: key, acl: importItem.acls, access: access, saveResult: false)
324 | }
325 |
326 | var itemClass: CFString?
327 | switch CFGetTypeID(importedItem) {
328 | case SecKeyGetTypeID():
329 | itemClass = kSecClassKey
330 | break
331 | case SecCertificateGetTypeID():
332 | itemClass = kSecClassCertificate
333 | break
334 | default:
335 | assert(false, "Unsupported import item classs")
336 | }
337 |
338 | var importQuery: [ String : Any ] = [
339 | kSecClassKey as String: itemClass!,
340 | kSecValueRef as String: importedItem,
341 | kSecUseKeychain as String: keychain!
342 | ]
343 |
344 | if access != nil {
345 | importQuery[kSecAttrAccess as String] = access!
346 | }
347 | if importItem.label != nil {
348 | importQuery[kSecAttrLabel as String] = importItem.label!
349 | }
350 |
351 | var addReturnValue: CFTypeRef?
352 |
353 | let addResult = SecItemAdd(importQuery as CFDictionary, &addReturnValue)
354 | assert(addResult == kOSReturnSuccess || addResult == errSecDuplicateItem)
355 | }
356 | }
357 | }
358 |
359 | func exportKeychainItems(items: [ ( configuration: ConfigurationItem, identity: SecIdentity ) ]) {
360 | for item in items {
361 | for export in item.configuration.exports {
362 | var exportParameters = SecItemImportExportKeyParameters.init()
363 | let exportFlags = export.pemEncode ? SecItemImportExportFlags.pemArmour : SecItemImportExportFlags()
364 |
365 | var exportData: CFData?
366 |
367 | if export.password != nil {
368 | let passwordData = Unmanaged.passRetained(export.password! as CFTypeRef)
369 | exportParameters.passphrase = passwordData
370 | }
371 |
372 | if export.format == .formatPEMSequence {
373 | var privateKey: SecKey?
374 |
375 | assert(SecIdentityCopyPrivateKey(item.identity, &privateKey) == kOSReturnSuccess)
376 | let result = SecItemExport(privateKey!, export.format, exportFlags, &exportParameters, &exportData)
377 | assert(result == kOSReturnSuccess)
378 | }
379 | if export.format == .formatX509Cert {
380 | var certificate: SecCertificate?
381 |
382 | assert(SecIdentityCopyCertificate(item.identity, &certificate) == kOSReturnSuccess)
383 | let result = SecItemExport(certificate!, export.format, exportFlags, &exportParameters, &exportData)
384 | assert(result == kOSReturnSuccess)
385 | }
386 |
387 | let exportedData = exportData as NSData?
388 | exportedData!.write(to: export.path, atomically: true)
389 |
390 | // TODO: Set owner
391 | // TODO: Set mode
392 | }
393 | }
394 | }
395 | }
396 |
--------------------------------------------------------------------------------
/certificate-sync.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 51;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | C913728321C2F0E800F8A54C /* rsa_key.pem in Resources */ = {isa = PBXBuildFile; fileRef = C913728221C2F0E700F8A54C /* rsa_key.pem */; };
11 | C9BB23EC219CE3C800CCE007 /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9BB23EB219CE3C800CCE007 /* main.swift */; };
12 | C9BB23F421A628B200CCE007 /* configuration_item.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9BB23F321A628B200CCE007 /* configuration_item.swift */; };
13 | C9BB23F621A73E4E00CCE007 /* configuration.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9BB23F521A73E4E00CCE007 /* configuration.swift */; };
14 | C9BB23F821A73E9400CCE007 /* export_configuration_item.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9BB23F721A73E9400CCE007 /* export_configuration_item.swift */; };
15 | C9BB23FA21A73E9C00CCE007 /* acl_configuration_item.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9BB23F921A73E9C00CCE007 /* acl_configuration_item.swift */; };
16 | C9BB23FC21A76F5000CCE007 /* extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9BB23FB21A76F5000CCE007 /* extensions.swift */; };
17 | C9BB23FE21A77DA200CCE007 /* syncronizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9BB23FD21A77DA200CCE007 /* syncronizer.swift */; };
18 | C9BB240621ABE6A000CCE007 /* test_suite.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9BB240521ABE6A000CCE007 /* test_suite.swift */; };
19 | C9BB240E21AE707200CCE007 /* test_configuration.plist in Resources */ = {isa = PBXBuildFile; fileRef = C9BB240D21AE707200CCE007 /* test_configuration.plist */; };
20 | C9BB241421AF2BD800CCE007 /* syncronizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9BB23FD21A77DA200CCE007 /* syncronizer.swift */; };
21 | C9BB241521AF2BDB00CCE007 /* extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9BB23FB21A76F5000CCE007 /* extensions.swift */; };
22 | C9BB241621AF2BDE00CCE007 /* acl_configuration_item.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9BB23F921A73E9C00CCE007 /* acl_configuration_item.swift */; };
23 | C9BB241721AF2BE200CCE007 /* configuration.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9BB23F521A73E4E00CCE007 /* configuration.swift */; };
24 | C9BB241821AF2BE500CCE007 /* export_configuration_item.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9BB23F721A73E9400CCE007 /* export_configuration_item.swift */; };
25 | C9BB241921AF2BE900CCE007 /* configuration_item.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9BB23F321A628B200CCE007 /* configuration_item.swift */; };
26 | C9BB241A21AF2BEE00CCE007 /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9BB23EB219CE3C800CCE007 /* main.swift */; };
27 | C9BB241D21B072A300CCE007 /* libcrypto.a in Frameworks */ = {isa = PBXBuildFile; fileRef = C9BB241C21B072A300CCE007 /* libcrypto.a */; };
28 | C9BB241F21B072BD00CCE007 /* libssl.a in Frameworks */ = {isa = PBXBuildFile; fileRef = C9BB241E21B072BD00CCE007 /* libssl.a */; };
29 | C9BFF29E21C057D700A4C582 /* import_item.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9BFF29D21C057D700A4C582 /* import_item.swift */; };
30 | C9BFF29F21C06EF100A4C582 /* import_item.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9BFF29D21C057D700A4C582 /* import_item.swift */; };
31 | C9E9607F21C3194E0005739F /* rsa_key.cer in Resources */ = {isa = PBXBuildFile; fileRef = C9E9607E21C3194D0005739F /* rsa_key.cer */; };
32 | /* End PBXBuildFile section */
33 |
34 | /* Begin PBXCopyFilesBuildPhase section */
35 | C9BB23E6219CE3C800CCE007 /* CopyFiles */ = {
36 | isa = PBXCopyFilesBuildPhase;
37 | buildActionMask = 2147483647;
38 | dstPath = /usr/share/man/man1/;
39 | dstSubfolderSpec = 0;
40 | files = (
41 | );
42 | runOnlyForDeploymentPostprocessing = 1;
43 | };
44 | /* End PBXCopyFilesBuildPhase section */
45 |
46 | /* Begin PBXFileReference section */
47 | C913728221C2F0E700F8A54C /* rsa_key.pem */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = rsa_key.pem; sourceTree = ""; };
48 | C9A71D8821CD827D00BA1D50 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
49 | C9BB23E8219CE3C800CCE007 /* certificate-sync */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = "certificate-sync"; sourceTree = BUILT_PRODUCTS_DIR; };
50 | C9BB23EB219CE3C800CCE007 /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = ""; };
51 | C9BB23F321A628B200CCE007 /* configuration_item.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = configuration_item.swift; sourceTree = ""; };
52 | C9BB23F521A73E4E00CCE007 /* configuration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = configuration.swift; sourceTree = ""; };
53 | C9BB23F721A73E9400CCE007 /* export_configuration_item.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = export_configuration_item.swift; sourceTree = ""; };
54 | C9BB23F921A73E9C00CCE007 /* acl_configuration_item.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = acl_configuration_item.swift; sourceTree = ""; };
55 | C9BB23FB21A76F5000CCE007 /* extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = extensions.swift; sourceTree = ""; };
56 | C9BB23FD21A77DA200CCE007 /* syncronizer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = syncronizer.swift; sourceTree = ""; };
57 | C9BB240321ABE6A000CCE007 /* test-suite.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "test-suite.xctest"; sourceTree = BUILT_PRODUCTS_DIR; };
58 | C9BB240521ABE6A000CCE007 /* test_suite.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = test_suite.swift; sourceTree = ""; };
59 | C9BB240721ABE6A000CCE007 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
60 | C9BB240D21AE707200CCE007 /* test_configuration.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = test_configuration.plist; sourceTree = ""; };
61 | C9BB241C21B072A300CCE007 /* libcrypto.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libcrypto.a; path = ../../../../../usr/local/Cellar/openssl/1.0.2p/lib/libcrypto.a; sourceTree = ""; };
62 | C9BB241E21B072BD00CCE007 /* libssl.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libssl.a; path = ../../../../../usr/local/Cellar/openssl/1.0.2p/lib/libssl.a; sourceTree = ""; };
63 | C9BB242021B0737A00CCE007 /* bridging-header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "bridging-header.h"; sourceTree = ""; };
64 | C9BFF29D21C057D700A4C582 /* import_item.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = import_item.swift; sourceTree = ""; };
65 | C9E9607E21C3194D0005739F /* rsa_key.cer */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = rsa_key.cer; sourceTree = ""; };
66 | /* End PBXFileReference section */
67 |
68 | /* Begin PBXFrameworksBuildPhase section */
69 | C9BB23E5219CE3C800CCE007 /* Frameworks */ = {
70 | isa = PBXFrameworksBuildPhase;
71 | buildActionMask = 2147483647;
72 | files = (
73 | );
74 | runOnlyForDeploymentPostprocessing = 0;
75 | };
76 | C9BB240021ABE6A000CCE007 /* Frameworks */ = {
77 | isa = PBXFrameworksBuildPhase;
78 | buildActionMask = 2147483647;
79 | files = (
80 | C9BB241F21B072BD00CCE007 /* libssl.a in Frameworks */,
81 | C9BB241D21B072A300CCE007 /* libcrypto.a in Frameworks */,
82 | );
83 | runOnlyForDeploymentPostprocessing = 0;
84 | };
85 | /* End PBXFrameworksBuildPhase section */
86 |
87 | /* Begin PBXGroup section */
88 | C9BB23DF219CE3C800CCE007 = {
89 | isa = PBXGroup;
90 | children = (
91 | C9BB23EA219CE3C800CCE007 /* certificate-sync */,
92 | C9BB240421ABE6A000CCE007 /* test-suite */,
93 | C9BB23E9219CE3C800CCE007 /* Products */,
94 | C9BB241B21B072A300CCE007 /* Frameworks */,
95 | );
96 | sourceTree = "";
97 | };
98 | C9BB23E9219CE3C800CCE007 /* Products */ = {
99 | isa = PBXGroup;
100 | children = (
101 | C9BB23E8219CE3C800CCE007 /* certificate-sync */,
102 | C9BB240321ABE6A000CCE007 /* test-suite.xctest */,
103 | );
104 | name = Products;
105 | sourceTree = "";
106 | };
107 | C9BB23EA219CE3C800CCE007 /* certificate-sync */ = {
108 | isa = PBXGroup;
109 | children = (
110 | C9BB23EB219CE3C800CCE007 /* main.swift */,
111 | C9BB23F321A628B200CCE007 /* configuration_item.swift */,
112 | C9BB23F521A73E4E00CCE007 /* configuration.swift */,
113 | C9BB23F721A73E9400CCE007 /* export_configuration_item.swift */,
114 | C9BB23F921A73E9C00CCE007 /* acl_configuration_item.swift */,
115 | C9BB23FB21A76F5000CCE007 /* extensions.swift */,
116 | C9BB23FD21A77DA200CCE007 /* syncronizer.swift */,
117 | C9BFF29D21C057D700A4C582 /* import_item.swift */,
118 | C9A71D8821CD827D00BA1D50 /* Info.plist */,
119 | );
120 | path = "certificate-sync";
121 | sourceTree = "";
122 | };
123 | C9BB240421ABE6A000CCE007 /* test-suite */ = {
124 | isa = PBXGroup;
125 | children = (
126 | C9E9607E21C3194D0005739F /* rsa_key.cer */,
127 | C913728221C2F0E700F8A54C /* rsa_key.pem */,
128 | C9BB240521ABE6A000CCE007 /* test_suite.swift */,
129 | C9BB240721ABE6A000CCE007 /* Info.plist */,
130 | C9BB240D21AE707200CCE007 /* test_configuration.plist */,
131 | C9BB242021B0737A00CCE007 /* bridging-header.h */,
132 | );
133 | path = "test-suite";
134 | sourceTree = "";
135 | };
136 | C9BB241B21B072A300CCE007 /* Frameworks */ = {
137 | isa = PBXGroup;
138 | children = (
139 | C9BB241E21B072BD00CCE007 /* libssl.a */,
140 | C9BB241C21B072A300CCE007 /* libcrypto.a */,
141 | );
142 | name = Frameworks;
143 | sourceTree = "";
144 | };
145 | /* End PBXGroup section */
146 |
147 | /* Begin PBXNativeTarget section */
148 | C9BB23E7219CE3C800CCE007 /* certificate-sync */ = {
149 | isa = PBXNativeTarget;
150 | buildConfigurationList = C9BB23EF219CE3C800CCE007 /* Build configuration list for PBXNativeTarget "certificate-sync" */;
151 | buildPhases = (
152 | C9BB23E4219CE3C800CCE007 /* Sources */,
153 | C9BB23E5219CE3C800CCE007 /* Frameworks */,
154 | C9BB23E6219CE3C800CCE007 /* CopyFiles */,
155 | );
156 | buildRules = (
157 | );
158 | dependencies = (
159 | );
160 | name = "certificate-sync";
161 | productName = "certificate-sync";
162 | productReference = C9BB23E8219CE3C800CCE007 /* certificate-sync */;
163 | productType = "com.apple.product-type.tool";
164 | };
165 | C9BB240221ABE6A000CCE007 /* test-suite */ = {
166 | isa = PBXNativeTarget;
167 | buildConfigurationList = C9BB240A21ABE6A000CCE007 /* Build configuration list for PBXNativeTarget "test-suite" */;
168 | buildPhases = (
169 | C9BB23FF21ABE6A000CCE007 /* Sources */,
170 | C9BB240021ABE6A000CCE007 /* Frameworks */,
171 | C9BB240121ABE6A000CCE007 /* Resources */,
172 | );
173 | buildRules = (
174 | );
175 | dependencies = (
176 | );
177 | name = "test-suite";
178 | productName = "test-suite";
179 | productReference = C9BB240321ABE6A000CCE007 /* test-suite.xctest */;
180 | productType = "com.apple.product-type.bundle.unit-test";
181 | };
182 | /* End PBXNativeTarget section */
183 |
184 | /* Begin PBXProject section */
185 | C9BB23E0219CE3C800CCE007 /* Project object */ = {
186 | isa = PBXProject;
187 | attributes = {
188 | LastSwiftUpdateCheck = 1010;
189 | LastUpgradeCheck = 1010;
190 | ORGANIZATIONNAME = Dropbox;
191 | TargetAttributes = {
192 | C9BB23E7219CE3C800CCE007 = {
193 | CreatedOnToolsVersion = 10.1;
194 | };
195 | C9BB240221ABE6A000CCE007 = {
196 | CreatedOnToolsVersion = 10.1;
197 | };
198 | };
199 | };
200 | buildConfigurationList = C9BB23E3219CE3C800CCE007 /* Build configuration list for PBXProject "certificate-sync" */;
201 | compatibilityVersion = "Xcode 10.0";
202 | developmentRegion = en;
203 | hasScannedForEncodings = 0;
204 | knownRegions = (
205 | en,
206 | );
207 | mainGroup = C9BB23DF219CE3C800CCE007;
208 | productRefGroup = C9BB23E9219CE3C800CCE007 /* Products */;
209 | projectDirPath = "";
210 | projectRoot = "";
211 | targets = (
212 | C9BB23E7219CE3C800CCE007 /* certificate-sync */,
213 | C9BB240221ABE6A000CCE007 /* test-suite */,
214 | );
215 | };
216 | /* End PBXProject section */
217 |
218 | /* Begin PBXResourcesBuildPhase section */
219 | C9BB240121ABE6A000CCE007 /* Resources */ = {
220 | isa = PBXResourcesBuildPhase;
221 | buildActionMask = 2147483647;
222 | files = (
223 | C9BB240E21AE707200CCE007 /* test_configuration.plist in Resources */,
224 | C9E9607F21C3194E0005739F /* rsa_key.cer in Resources */,
225 | C913728321C2F0E800F8A54C /* rsa_key.pem in Resources */,
226 | );
227 | runOnlyForDeploymentPostprocessing = 0;
228 | };
229 | /* End PBXResourcesBuildPhase section */
230 |
231 | /* Begin PBXSourcesBuildPhase section */
232 | C9BB23E4219CE3C800CCE007 /* Sources */ = {
233 | isa = PBXSourcesBuildPhase;
234 | buildActionMask = 2147483647;
235 | files = (
236 | C9BB23FE21A77DA200CCE007 /* syncronizer.swift in Sources */,
237 | C9BB23FC21A76F5000CCE007 /* extensions.swift in Sources */,
238 | C9BB23FA21A73E9C00CCE007 /* acl_configuration_item.swift in Sources */,
239 | C9BB23EC219CE3C800CCE007 /* main.swift in Sources */,
240 | C9BB23F821A73E9400CCE007 /* export_configuration_item.swift in Sources */,
241 | C9BFF29E21C057D700A4C582 /* import_item.swift in Sources */,
242 | C9BB23F621A73E4E00CCE007 /* configuration.swift in Sources */,
243 | C9BB23F421A628B200CCE007 /* configuration_item.swift in Sources */,
244 | );
245 | runOnlyForDeploymentPostprocessing = 0;
246 | };
247 | C9BB23FF21ABE6A000CCE007 /* Sources */ = {
248 | isa = PBXSourcesBuildPhase;
249 | buildActionMask = 2147483647;
250 | files = (
251 | C9BB241621AF2BDE00CCE007 /* acl_configuration_item.swift in Sources */,
252 | C9BFF29F21C06EF100A4C582 /* import_item.swift in Sources */,
253 | C9BB241721AF2BE200CCE007 /* configuration.swift in Sources */,
254 | C9BB241A21AF2BEE00CCE007 /* main.swift in Sources */,
255 | C9BB241421AF2BD800CCE007 /* syncronizer.swift in Sources */,
256 | C9BB240621ABE6A000CCE007 /* test_suite.swift in Sources */,
257 | C9BB241521AF2BDB00CCE007 /* extensions.swift in Sources */,
258 | C9BB241921AF2BE900CCE007 /* configuration_item.swift in Sources */,
259 | C9BB241821AF2BE500CCE007 /* export_configuration_item.swift in Sources */,
260 | );
261 | runOnlyForDeploymentPostprocessing = 0;
262 | };
263 | /* End PBXSourcesBuildPhase section */
264 |
265 | /* Begin XCBuildConfiguration section */
266 | C9BB23ED219CE3C800CCE007 /* Debug */ = {
267 | isa = XCBuildConfiguration;
268 | buildSettings = {
269 | ALWAYS_SEARCH_USER_PATHS = NO;
270 | CLANG_ANALYZER_NONNULL = YES;
271 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
272 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
273 | CLANG_CXX_LIBRARY = "libc++";
274 | CLANG_ENABLE_MODULES = YES;
275 | CLANG_ENABLE_OBJC_ARC = YES;
276 | CLANG_ENABLE_OBJC_WEAK = YES;
277 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
278 | CLANG_WARN_BOOL_CONVERSION = YES;
279 | CLANG_WARN_COMMA = YES;
280 | CLANG_WARN_CONSTANT_CONVERSION = YES;
281 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
282 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
283 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
284 | CLANG_WARN_EMPTY_BODY = YES;
285 | CLANG_WARN_ENUM_CONVERSION = YES;
286 | CLANG_WARN_INFINITE_RECURSION = YES;
287 | CLANG_WARN_INT_CONVERSION = YES;
288 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
289 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
290 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
291 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
292 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
293 | CLANG_WARN_STRICT_PROTOTYPES = YES;
294 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
295 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
296 | CLANG_WARN_UNREACHABLE_CODE = YES;
297 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
298 | CODE_SIGN_IDENTITY = "Mac Developer";
299 | COPY_PHASE_STRIP = NO;
300 | DEBUG_INFORMATION_FORMAT = dwarf;
301 | ENABLE_STRICT_OBJC_MSGSEND = YES;
302 | ENABLE_TESTABILITY = YES;
303 | GCC_C_LANGUAGE_STANDARD = gnu11;
304 | GCC_DYNAMIC_NO_PIC = NO;
305 | GCC_NO_COMMON_BLOCKS = YES;
306 | GCC_OPTIMIZATION_LEVEL = 0;
307 | GCC_PREPROCESSOR_DEFINITIONS = (
308 | "DEBUG=1",
309 | "$(inherited)",
310 | );
311 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
312 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
313 | GCC_WARN_UNDECLARED_SELECTOR = YES;
314 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
315 | GCC_WARN_UNUSED_FUNCTION = YES;
316 | GCC_WARN_UNUSED_VARIABLE = YES;
317 | MACOSX_DEPLOYMENT_TARGET = 10.14;
318 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
319 | MTL_FAST_MATH = YES;
320 | ONLY_ACTIVE_ARCH = YES;
321 | SDKROOT = macosx;
322 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
323 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
324 | };
325 | name = Debug;
326 | };
327 | C9BB23EE219CE3C800CCE007 /* Release */ = {
328 | isa = XCBuildConfiguration;
329 | buildSettings = {
330 | ALWAYS_SEARCH_USER_PATHS = NO;
331 | CLANG_ANALYZER_NONNULL = YES;
332 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
333 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
334 | CLANG_CXX_LIBRARY = "libc++";
335 | CLANG_ENABLE_MODULES = YES;
336 | CLANG_ENABLE_OBJC_ARC = YES;
337 | CLANG_ENABLE_OBJC_WEAK = YES;
338 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
339 | CLANG_WARN_BOOL_CONVERSION = YES;
340 | CLANG_WARN_COMMA = YES;
341 | CLANG_WARN_CONSTANT_CONVERSION = YES;
342 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
343 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
344 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
345 | CLANG_WARN_EMPTY_BODY = YES;
346 | CLANG_WARN_ENUM_CONVERSION = YES;
347 | CLANG_WARN_INFINITE_RECURSION = YES;
348 | CLANG_WARN_INT_CONVERSION = YES;
349 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
350 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
351 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
352 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
353 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
354 | CLANG_WARN_STRICT_PROTOTYPES = YES;
355 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
356 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
357 | CLANG_WARN_UNREACHABLE_CODE = YES;
358 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
359 | CODE_SIGN_IDENTITY = "Mac Developer";
360 | COPY_PHASE_STRIP = NO;
361 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
362 | ENABLE_NS_ASSERTIONS = NO;
363 | ENABLE_STRICT_OBJC_MSGSEND = YES;
364 | GCC_C_LANGUAGE_STANDARD = gnu11;
365 | GCC_NO_COMMON_BLOCKS = YES;
366 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
367 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
368 | GCC_WARN_UNDECLARED_SELECTOR = YES;
369 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
370 | GCC_WARN_UNUSED_FUNCTION = YES;
371 | GCC_WARN_UNUSED_VARIABLE = YES;
372 | MACOSX_DEPLOYMENT_TARGET = 10.14;
373 | MTL_ENABLE_DEBUG_INFO = NO;
374 | MTL_FAST_MATH = YES;
375 | SDKROOT = macosx;
376 | SWIFT_COMPILATION_MODE = wholemodule;
377 | SWIFT_OPTIMIZATION_LEVEL = "-O";
378 | };
379 | name = Release;
380 | };
381 | C9BB23F0219CE3C800CCE007 /* Debug */ = {
382 | isa = XCBuildConfiguration;
383 | buildSettings = {
384 | CODE_SIGN_IDENTITY = "Developer ID Application";
385 | CODE_SIGN_STYLE = Manual;
386 | CREATE_INFOPLIST_SECTION_IN_BINARY = YES;
387 | DEVELOPMENT_TEAM = VNH7T7682X;
388 | INFOPLIST_FILE = "certificate-sync/Info.plist";
389 | PRODUCT_NAME = "$(TARGET_NAME)";
390 | PROVISIONING_PROFILE_SPECIFIER = "";
391 | SWIFT_VERSION = 4.2;
392 | };
393 | name = Debug;
394 | };
395 | C9BB23F1219CE3C800CCE007 /* Release */ = {
396 | isa = XCBuildConfiguration;
397 | buildSettings = {
398 | CODE_SIGN_IDENTITY = "Developer ID Application";
399 | CODE_SIGN_STYLE = Manual;
400 | CREATE_INFOPLIST_SECTION_IN_BINARY = YES;
401 | DEVELOPMENT_TEAM = VNH7T7682X;
402 | INFOPLIST_FILE = "certificate-sync/Info.plist";
403 | PRODUCT_NAME = "$(TARGET_NAME)";
404 | PROVISIONING_PROFILE_SPECIFIER = "";
405 | SWIFT_VERSION = 4.2;
406 | };
407 | name = Release;
408 | };
409 | C9BB240821ABE6A000CCE007 /* Debug */ = {
410 | isa = XCBuildConfiguration;
411 | buildSettings = {
412 | CODE_SIGN_STYLE = Automatic;
413 | COMBINE_HIDPI_IMAGES = YES;
414 | DEVELOPMENT_TEAM = DWVXMLB45Y;
415 | HEADER_SEARCH_PATHS = /usr/local/opt/openssl/include;
416 | INFOPLIST_FILE = "test-suite/Info.plist";
417 | LD_RUNPATH_SEARCH_PATHS = (
418 | "$(inherited)",
419 | "@executable_path/../Frameworks",
420 | "@loader_path/../Frameworks",
421 | );
422 | LIBRARY_SEARCH_PATHS = (
423 | "$(inherited)",
424 | /usr/local/Cellar/openssl/1.0.2p/lib,
425 | );
426 | PRODUCT_BUNDLE_IDENTIFIER = "com.dropbox.corp.test-suite";
427 | PRODUCT_NAME = "$(TARGET_NAME)";
428 | SWIFT_OBJC_BRIDGING_HEADER = "test-suite/bridging-header.h";
429 | SWIFT_VERSION = 4.2;
430 | };
431 | name = Debug;
432 | };
433 | C9BB240921ABE6A000CCE007 /* Release */ = {
434 | isa = XCBuildConfiguration;
435 | buildSettings = {
436 | CODE_SIGN_STYLE = Automatic;
437 | COMBINE_HIDPI_IMAGES = YES;
438 | DEVELOPMENT_TEAM = DWVXMLB45Y;
439 | HEADER_SEARCH_PATHS = /usr/local/opt/openssl/include;
440 | INFOPLIST_FILE = "test-suite/Info.plist";
441 | LD_RUNPATH_SEARCH_PATHS = (
442 | "$(inherited)",
443 | "@executable_path/../Frameworks",
444 | "@loader_path/../Frameworks",
445 | );
446 | LIBRARY_SEARCH_PATHS = (
447 | "$(inherited)",
448 | /usr/local/Cellar/openssl/1.0.2p/lib,
449 | );
450 | PRODUCT_BUNDLE_IDENTIFIER = "com.dropbox.corp.test-suite";
451 | PRODUCT_NAME = "$(TARGET_NAME)";
452 | SWIFT_OBJC_BRIDGING_HEADER = "test-suite/bridging-header.h";
453 | SWIFT_VERSION = 4.2;
454 | };
455 | name = Release;
456 | };
457 | /* End XCBuildConfiguration section */
458 |
459 | /* Begin XCConfigurationList section */
460 | C9BB23E3219CE3C800CCE007 /* Build configuration list for PBXProject "certificate-sync" */ = {
461 | isa = XCConfigurationList;
462 | buildConfigurations = (
463 | C9BB23ED219CE3C800CCE007 /* Debug */,
464 | C9BB23EE219CE3C800CCE007 /* Release */,
465 | );
466 | defaultConfigurationIsVisible = 0;
467 | defaultConfigurationName = Release;
468 | };
469 | C9BB23EF219CE3C800CCE007 /* Build configuration list for PBXNativeTarget "certificate-sync" */ = {
470 | isa = XCConfigurationList;
471 | buildConfigurations = (
472 | C9BB23F0219CE3C800CCE007 /* Debug */,
473 | C9BB23F1219CE3C800CCE007 /* Release */,
474 | );
475 | defaultConfigurationIsVisible = 0;
476 | defaultConfigurationName = Release;
477 | };
478 | C9BB240A21ABE6A000CCE007 /* Build configuration list for PBXNativeTarget "test-suite" */ = {
479 | isa = XCConfigurationList;
480 | buildConfigurations = (
481 | C9BB240821ABE6A000CCE007 /* Debug */,
482 | C9BB240921ABE6A000CCE007 /* Release */,
483 | );
484 | defaultConfigurationIsVisible = 0;
485 | defaultConfigurationName = Release;
486 | };
487 | /* End XCConfigurationList section */
488 | };
489 | rootObject = C9BB23E0219CE3C800CCE007 /* Project object */;
490 | }
491 |
--------------------------------------------------------------------------------