Skip to content

Instantly share code, notes, and snippets.

@lukaskubanek
Last active November 3, 2024 17:20
Show Gist options
  • Save lukaskubanek/cbfcab29c0c93e0e9e0a16ab09586996 to your computer and use it in GitHub Desktop.
Save lukaskubanek/cbfcab29c0c93e0e9e0a16ab09586996 to your computer and use it in GitHub Desktop.
A code snippet for detecting the TestFlight environment for a macOS app at runtime
/// MIT License
///
/// Copyright (c) 2021 Lukas Kubanek, Structured Path GmbH
///
/// Permission is hereby granted, free of charge, to any person obtaining a copy
/// of this software and associated documentation files (the "Software"), to deal
/// in the Software without restriction, including without limitation the rights
/// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
/// copies of the Software, and to permit persons to whom the Software is
/// furnished to do so, subject to the following conditions:
///
/// The above copyright notice and this permission notice shall be included in all
/// copies or substantial portions of the Software.
///
/// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
/// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
/// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
/// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
/// SOFTWARE.
import Foundation
import Security
extension Bundle {
/// Returns whether the bundle was signed for TestFlight beta distribution by checking
/// the existence of a specific extension (marker OID) on the code signing certificate.
///
/// This routine is inspired by the source code from ProcInfo, the underlying library
/// of the WhatsYourSign code signature checking tool developed by Objective-See. Initially,
/// it checked the common name but was changed to an extension check to make it more
/// future-proof.
///
/// For more information, see the following references:
/// - https://github.com/objective-see/ProcInfo/blob/master/procInfo/Signing.m#L184-L247
/// - https://gist.github.com/lukaskubanek/cbfcab29c0c93e0e9e0a16ab09586996#gistcomment-3993808
internal var isTestFlight: Bool {
var status = noErr
var code: SecStaticCode?
status = SecStaticCodeCreateWithPath(bundleURL as CFURL, [], &code)
guard status == noErr, let code = code else { return false }
var requirement: SecRequirement?
status = SecRequirementCreateWithString(
"anchor apple generic and certificate leaf[field.1.2.840.113635.100.6.1.25.1]" as CFString,
[], // default
&requirement
)
guard status == noErr, let requirement = requirement else { return false }
status = SecStaticCodeCheckValidity(
code,
[], // default
requirement
)
return status == errSecSuccess
}
}
@jaanus
Copy link

jaanus commented Nov 10, 2022

Yep, that’s the project where I use it. I’ll some time try it out with iOS on TestFlight and report back.

@jaanus
Copy link

jaanus commented Nov 11, 2022

Verdict about iOS: cannot use the signature check, because the needed Security framework API-s are available only on macOS and not on iOS.

So as far as I know, the best way to do this across macOS and iOS is what I shared above. On macOS, do the signature check from the original post. On iOS, check the receipt file name.

@lukaskubanek
Copy link
Author

@jaanus: Thanks for sharing your findings!

@keeshux
Copy link

keeshux commented Nov 13, 2022

Months later, I wanted to add that I've been using this trick on my public macOS beta for a while, to restrict in-app features. It has worked like a charm since.

@chockenberry
Copy link

@lukaskubanek Thanks for figuring this out. Very handy to have while publicly testing new features!

@keeshux
Copy link

keeshux commented Sep 10, 2023

I feel like sharing what I ended up doing, as it might help other readers:

https://github.com/passepartoutvpn/passepartout-apple/blob/master/PassepartoutLibrary/Sources/PassepartoutCore/Reusable/SandboxChecker.swift

Beyond being a multiplatform check, I made this observable because Xcode has been complaining about SecStaticCodeCheckValidity for a while, as it is a potentially slow call.

Also notice that the Mac condition appears first, because #if os(iOS) is also met on Catalyst and would take over.

@pejrich
Copy link

pejrich commented Nov 20, 2023

@keeshux Thanks, but it's a shame it's GPL as I don't particularly want to open source my whole application just to check if I'm running on Testflight or not. I think for most people the one in this thread is a safer bet.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment