Skip to content

Commit 927f842

Browse files
authored
Merge branch 'main' into main
2 parents 45ea417 + 9cf1012 commit 927f842

25 files changed

Lines changed: 523 additions & 64 deletions

.github/workflows/appcast.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,15 @@ jobs:
1515
persist-credentials: false
1616

1717
- name: Cache 📦
18-
uses: actions/cache@v3.0.11
18+
uses: actions/cache@v3.3.1
1919
with:
2020
path: AppCast/vendor/bundle
2121
key: ${{ runner.os }}-gems-v1.0-${{ hashFiles('AppCast/Gemfile') }}
2222
restore-keys: |
2323
${{ runner.os }}-gems-
2424
25-
- name: Ruby ♦️
26-
uses: actions/setup-ruby@v1.1.3
25+
- name: Setup Ruby, JRuby and TruffleRuby
26+
uses: ruby/setup-ruby@v1.149.0
2727
with:
2828
ruby-version: '2.7'
2929

README.md

Lines changed: 22 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,17 @@
22

33
The easiest way to install and switch between multiple versions of Xcode.
44

5-
_If you're looking for a command-line version of Xcodes.app, try [`xcodes`](https://github.com/RobotsAndPencils/xcodes)._
5+
_If you're looking for a command-line version of Xcodes.app, try [`xcodes`](https://github.com/XcodesOrg/xcodes)._
66

7-
![CI](https://github.com/RobotsAndPencils/Xcodes.app/workflows/CI/badge.svg)
7+
![CI](https://github.com/XcodesOrg/XcodesApp/workflows/CI/badge.svg)
88

99
![](screenshot_light.png#gh-light-mode-only)
1010
![](screenshot_dark.png#gh-dark-mode-only)
1111

12+
### :tada: Announcment
13+
14+
XcodesApp is now part of the `XcodesOrg` - [read more here](nextstep.md)
15+
1216
## Features
1317

1418
- List all available Xcode versions from [Xcode Releases'](https://xcodereleases.com) data or the Apple Developer website.
@@ -21,52 +25,51 @@ _If you're looking for a command-line version of Xcodes.app, try [`xcodes`](http
2125

2226
## Experiments
2327

24-
- Thanks to the wonderful work of [https://github.com/saagarjha/unxip](https://github.com/saagarjha/unxip), turn on the experiment to increase your unxipping time by up to 70%! More can be found on his repo, but bugs, high memory may occur if used.
28+
- Thanks to the wonderful work of [https://github.com/saagarjha/unxip](https://github.com/saagarjha/unxip), turn on the experiment to increase your unxipping time by up to 70%! More can be found on his repo, but bugs, high memory may occur if used.
2529

26-
![](experiment_light.jpg#gh-light-mode-only)
27-
![](experiment_dark.jpg#gh-dark-mode-only)
30+
![](experiment_light.png#gh-light-mode-only)
31+
![](experiment_dark.png#gh-dark-mode-only)
2832

2933
## Localization
3034

31-
Xcodes supports localization in several languages.
35+
Xcodes supports localization in several languages.
3236

3337
The following languages are supported because of the following community users!
3438

3539
|||||
3640
|-|-|-|-|
3741
|French 🇫🇷 |[@dompepin](https://github.com/dompepin)|Italian 🇮🇹 |[gualtierofrigerio](https://github.com/gualtierofrigerio)|
3842
|Spanish 🇪🇸🇲 |[@cesartru88](https://github.com/cesartru88)|Korean 🇰🇷 |[@ryan-son](https://github.com/ryan-son)|
39-
|Russian 🇷🇺 |[@alexmazlov](https://github.com/alexmazlov)|Turkish 🇹🇷 |[@egrimo](https://github.com/egrimo)|
43+
|Russian 🇷🇺 |[@alexmazlov](https://github.com/alexmazlov)|Turkish 🇹🇷 |[@egesucu](https://github.com/egesucu)|
4044
|Hindi 🇮🇳 |[@KGurpreet](https://github.com/KGurpreet)|Chinese-Simplified 🇨🇳|[@megabitsenmzq](https://github.com/megabitsenmzq)|
4145
|Finnish 🇫🇮 |[@marcusziade](https://github.com/marcusziade)|Chinese-Traditional 🇹🇼|[@itszero](https://github.com/itszero)|
4246
|Ukranian 🇺🇦 |[@gelosi](https://github.com/gelosi)|Japanese 🇯🇵|[@tatsuz0u](https://github.com/tatsuz0u)|
4347
|German 🇩🇪|[@drct](https://github.com/drct)|Dutch 🇳🇱|[@jfversluis](https://github/com/jfversluis)|
44-
|Brazilian Portuguese 🇧🇷|[@brunomunizaf](https://github.com/brunomunizaf)|Catalan|[@ferranabello](https://github.com/ferranabello)||
48+
|Brazilian Portuguese 🇧🇷|[@brunomunizaf](https://github.com/brunomunizaf)|Polish 🇵🇱|[@jakex7](https://github.com/jakex7)|
49+
|Catalan|[@ferranabello](https://github.com/ferranabello)|
4550

46-
Want to add more languages? Simply create a PR with the updated strings file.
51+
Want to add more languages? Simply create a PR with the updated strings file.
4752
## Installation
4853

4954
Xcodes.app runs on macOS Big Sur 11.0 or later.
5055

51-
### Homebrew Cask
56+
### Install with Homebrew
57+
58+
Developer ID-signed and notarized release builds are available on Homebrew. These don't require Xcode to already be installed in order to use.
5259

5360
```sh
5461
brew install --cask xcodes
55-
56-
# These are Developer ID-signed and notarized release builds and don't require Xcode to already be installed in order to use.
5762
```
5863

59-
### Download a release
64+
### Manually install
6065

61-
1. Download the latest version [here](https://github.com/RobotsAndPencils/XcodesApp/releases/latest) using the **Xcodes.zip** asset. These are Developer ID-signed and notarized release builds and don't require Xcode to already be installed in order to use.
66+
1. Download the latest version [here](https://github.com/XcodesOrg/XcodesApp/releases/latest) using the **Xcodes.zip** asset. These are Developer ID-signed and notarized release builds and don't require Xcode to already be installed in order to use.
6267
2. Move the unzipped `Xcodes.app` to your `/Applications` directory
6368

6469
## Development
6570

6671
You'll need macOS 12 Big Sur and Xcode 13 in order to build and run Xcodes.app.
6772

68-
If you aren't a Robots and Pencils employee you'll need to change the CODE_SIGNING_SUBJECT_ORGANIZATIONAL_UNIT build setting to your Apple Developer team ID in order for code signing validation to succeed between the main app and the privileged helper.
69-
7073
`Unxip` and `aria2` must be compiled as a universal binary
7174
```
7275
# compile for Intel
@@ -77,7 +80,7 @@ If you aren't a Robots and Pencils employee you'll need to change the CODE_SIGNI
7780
# combine for universal binary
7881
lipo -create -output unxip unxip_intel unxip_m1
7982
# check it
80-
lipo -archs unxip
83+
lipo -archs unxip
8184
```
8285

8386
Notable design decisions are recorded in [DECISIONS.md](./DECISIONS.md). The Apple authentication flow is described in [Apple.paw](./Apple.paw), which will allow you to play with the API endpoints that are involved using the [Paw](https://paw.cloud) app.
@@ -133,7 +136,7 @@ pushd Product
133136
../scripts/sign_update Xcodes.zip
134137
popd
135138

136-
# Go to https://github.com/RobotsAndPencils/XcodesApp/releases
139+
# Go to https://github.com/XcodesOrg/XcodesApp/releases
137140
# If there are uncategorized PRs, add the appropriate label and run the Release Drafter action manually
138141
# Edit the latest draft release
139142
# Set its tag to the tag you just pushed
@@ -152,9 +155,4 @@ popd
152155

153156
[Matt Kiazyk](https://github.com/mattkiazyk) - [Twitter](https://www.twitter.com/mattkiazyk)
154157

155-
156-
<a href="http://www.robotsandpencils.com"><img src="R&PLogo.png" width="153" height="74" /></a>
157-
158-
Made with ❤️ by [Robots & Pencils](http://www.robotsandpencils.com)
159-
160-
[Twitter](https://twitter.com/xcodesApp) | [GitHub](https://github.com/robotsandpencils)
158+
[Twitter](https://twitter.com/xcodesApp) | [GitHub](https://github.com/xcodesOrg)

Xcodes.xcodeproj/project.pbxproj

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,7 @@
172172

173173
/* Begin PBXFileReference section */
174174
15FAD1652811D15600B63259 /* hi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hi; path = hi.lproj/Localizable.strings; sourceTree = "<group>"; };
175+
23703D6E29EBF63500DFA346 /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/Localizable.strings; sourceTree = "<group>"; };
175176
25E2FA26284769A00014A318 /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/Localizable.strings; sourceTree = "<group>"; };
176177
327DF109286ABE6B00D694D5 /* pt-BR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pt-BR"; path = "pt-BR.lproj/Localizable.strings"; sourceTree = "<group>"; };
177178
36741BFC291E4FDB00A85AAE /* DownloadPreferencePane.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DownloadPreferencePane.swift; sourceTree = "<group>"; };
@@ -736,6 +737,7 @@
736737
ca,
737738
"pt-BR",
738739
nl,
740+
pl,
739741
);
740742
mainGroup = CAD2E7952449574E00113D76;
741743
packageReferences = (
@@ -955,6 +957,7 @@
955957
5C3927ED28E4B486007B5119 /* ca */,
956958
327DF109286ABE6B00D694D5 /* pt-BR */,
957959
E2AFDCCA28F024D000864ADD /* nl */,
960+
23703D6E29EBF63500DFA346 /* pl */,
958961
);
959962
name = Localizable.strings;
960963
sourceTree = "<group>";
@@ -1033,7 +1036,7 @@
10331036
CODE_SIGN_IDENTITY = "-";
10341037
CODE_SIGN_STYLE = Manual;
10351038
COMBINE_HIDPI_IMAGES = YES;
1036-
CURRENT_PROJECT_VERSION = 17;
1039+
CURRENT_PROJECT_VERSION = 18;
10371040
DEVELOPMENT_ASSET_PATHS = "\"Xcodes/Preview Content\"";
10381041
DEVELOPMENT_TEAM = "";
10391042
ENABLE_HARDENED_RUNTIME = NO;
@@ -1043,7 +1046,7 @@
10431046
"$(inherited)",
10441047
"@executable_path/../Frameworks",
10451048
);
1046-
MARKETING_VERSION = 1.9.0;
1049+
MARKETING_VERSION = 1.10.0;
10471050
PRODUCT_BUNDLE_IDENTIFIER = com.robotsandpencils.XcodesApp;
10481051
PRODUCT_NAME = Xcodes;
10491052
PROVISIONING_PROFILE_SPECIFIER = "";
@@ -1274,7 +1277,7 @@
12741277
CODE_SIGN_ENTITLEMENTS = Xcodes/Resources/Xcodes.entitlements;
12751278
CODE_SIGN_STYLE = Automatic;
12761279
COMBINE_HIDPI_IMAGES = YES;
1277-
CURRENT_PROJECT_VERSION = 17;
1280+
CURRENT_PROJECT_VERSION = 18;
12781281
DEVELOPMENT_ASSET_PATHS = "\"Xcodes/Preview Content\"";
12791282
DEVELOPMENT_TEAM = PBH8V487HB;
12801283
ENABLE_HARDENED_RUNTIME = YES;
@@ -1284,7 +1287,7 @@
12841287
"$(inherited)",
12851288
"@executable_path/../Frameworks",
12861289
);
1287-
MARKETING_VERSION = 1.9.0;
1290+
MARKETING_VERSION = 1.10.0;
12881291
PRODUCT_BUNDLE_IDENTIFIER = com.robotsandpencils.XcodesApp;
12891292
PRODUCT_NAME = Xcodes;
12901293
SWIFT_VERSION = 5.0;
@@ -1298,7 +1301,7 @@
12981301
CODE_SIGN_ENTITLEMENTS = Xcodes/Resources/Xcodes.entitlements;
12991302
CODE_SIGN_STYLE = Automatic;
13001303
COMBINE_HIDPI_IMAGES = YES;
1301-
CURRENT_PROJECT_VERSION = 17;
1304+
CURRENT_PROJECT_VERSION = 18;
13021305
DEVELOPMENT_ASSET_PATHS = "\"Xcodes/Preview Content\"";
13031306
DEVELOPMENT_TEAM = PBH8V487HB;
13041307
ENABLE_HARDENED_RUNTIME = YES;
@@ -1308,7 +1311,7 @@
13081311
"$(inherited)",
13091312
"@executable_path/../Frameworks",
13101313
);
1311-
MARKETING_VERSION = 1.9.0;
1314+
MARKETING_VERSION = 1.10.0;
13121315
PRODUCT_BUNDLE_IDENTIFIER = com.robotsandpencils.XcodesApp;
13131316
PRODUCT_NAME = Xcodes;
13141317
SWIFT_VERSION = 5.0;

Xcodes/AppleAPI/Sources/AppleAPI/Client.swift

Lines changed: 54 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,22 @@ public class Client {
1414
return Current.network.dataTask(with: URLRequest.itcServiceKey)
1515
.map(\.data)
1616
.decode(type: ServiceKeyResponse.self, decoder: JSONDecoder())
17-
.flatMap { serviceKeyResponse -> AnyPublisher<URLSession.DataTaskPublisher.Output, Swift.Error> in
17+
.flatMap { serviceKeyResponse -> AnyPublisher<(String, String), Swift.Error> in
1818
serviceKey = serviceKeyResponse.authServiceKey
19-
return Current.network.dataTask(with: URLRequest.signIn(serviceKey: serviceKey, accountName: accountName, password: password))
20-
.mapError { $0 as Swift.Error }
19+
20+
// Fixes issue https://github.com/RobotsAndPencils/XcodesApp/issues/360
21+
// On 2023-02-23, Apple added a custom implementation of hashcash to their auth flow
22+
// Without this addition, Apple ID's would get set to locked
23+
return self.loadHashcash(accountName: accountName, serviceKey: serviceKey)
24+
.map { return (serviceKey, $0)}
2125
.eraseToAnyPublisher()
2226
}
27+
.flatMap { (serviceKey, hashcash) -> AnyPublisher<URLSession.DataTaskPublisher.Output, Swift.Error> in
28+
29+
return Current.network.dataTask(with: URLRequest.signIn(serviceKey: serviceKey, accountName: accountName, password: password, hashcash: hashcash))
30+
.mapError { $0 as Swift.Error }
31+
.eraseToAnyPublisher()
32+
}
2333
.flatMap { result -> AnyPublisher<AuthenticationState, Swift.Error> in
2434
let (data, response) = result
2535
return Just(data)
@@ -56,6 +66,44 @@ public class Client {
5666
.mapError { $0 as Swift.Error }
5767
.eraseToAnyPublisher()
5868
}
69+
70+
func loadHashcash(accountName: String, serviceKey: String) -> AnyPublisher<String, Swift.Error> {
71+
72+
Result {
73+
try URLRequest.federate(account: accountName, serviceKey: serviceKey)
74+
}
75+
.publisher
76+
.flatMap { request in
77+
Current.network.dataTask(with: request)
78+
.mapError { $0 as Error }
79+
.tryMap { (data, response) throws -> (String) in
80+
guard let urlResponse = response as? HTTPURLResponse else {
81+
throw AuthenticationError.invalidSession
82+
}
83+
switch urlResponse.statusCode {
84+
case 200..<300:
85+
86+
let httpResponse = response as! HTTPURLResponse
87+
guard let bitsString = httpResponse.allHeaderFields["X-Apple-HC-Bits"] as? String, let bits = UInt(bitsString) else {
88+
throw AuthenticationError.invalidHashcash
89+
}
90+
guard let challenge = httpResponse.allHeaderFields["X-Apple-HC-Challenge"] as? String else {
91+
throw AuthenticationError.invalidHashcash
92+
}
93+
guard let hashcash = Hashcash().mint(resource: challenge, bits: bits) else {
94+
throw AuthenticationError.invalidHashcash
95+
}
96+
return (hashcash)
97+
case 400, 401:
98+
throw AuthenticationError.invalidHashcash
99+
case let code:
100+
throw AuthenticationError.badStatusCode(statusCode: code, data: data, response: urlResponse)
101+
}
102+
}
103+
}
104+
.eraseToAnyPublisher()
105+
106+
}
59107

60108
func handleTwoStepOrFactor(data: Data, response: URLResponse, serviceKey: String) -> AnyPublisher<AuthenticationState, Swift.Error> {
61109
let httpResponse = response as! HTTPURLResponse
@@ -190,6 +238,7 @@ public enum AuthenticationState: Equatable {
190238

191239
public enum AuthenticationError: Swift.Error, LocalizedError, Equatable {
192240
case invalidSession
241+
case invalidHashcash
193242
case invalidUsernameOrPassword(username: String)
194243
case incorrectSecurityCode
195244
case unexpectedSignInResponse(statusCode: Int, message: String?)
@@ -206,6 +255,8 @@ public enum AuthenticationError: Swift.Error, LocalizedError, Equatable {
206255
switch self {
207256
case .invalidSession:
208257
return "Your authentication session is invalid. Try signing in again."
258+
case .invalidHashcash:
259+
return "Could not create a hashcash for the session."
209260
case .invalidUsernameOrPassword:
210261
return "Invalid username and password combination."
211262
case .incorrectSecurityCode:
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
//
2+
// Hashcash.swift
3+
//
4+
//
5+
// Created by Matt Kiazyk on 2023-02-23.
6+
//
7+
8+
import Foundation
9+
import CryptoKit
10+
import CommonCrypto
11+
12+
/*
13+
# This App Store Connect hashcash spec was generously donated by...
14+
#
15+
# __ _
16+
# __ _ _ __ _ __ / _|(_) __ _ _ _ _ __ ___ ___
17+
# / _` || '_ \ | '_ \ | |_ | | / _` || | | || '__|/ _ \/ __|
18+
# | (_| || |_) || |_) || _|| || (_| || |_| || | | __/\__ \
19+
# \__,_|| .__/ | .__/ |_| |_| \__, | \__,_||_| \___||___/
20+
# |_| |_| |___/
21+
#
22+
#
23+
*/
24+
public struct Hashcash {
25+
/// A function to returned a minted hash, using a bit and resource string
26+
///
27+
/**
28+
X-APPLE-HC: 1:11:20230223170600:4d74fb15eb23f465f1f6fcbf534e5877::6373
29+
^ ^ ^ ^ ^
30+
| | | | +-- Counter
31+
| | | +-- Resource
32+
| | +-- Date YYMMDD[hhmm[ss]]
33+
| +-- Bits (number of leading zeros)
34+
+-- Version
35+
36+
We can't use an off-the-shelf Hashcash because Apple's implementation is not quite the same as the spec/convention.
37+
1. The spec calls for a nonce called "Rand" to be inserted between the Ext and Counter. They don't do that at all.
38+
2. The Counter conventionally encoded as base-64 but Apple just uses the decimal number's string representation.
39+
40+
Iterate from Counter=0 to Counter=N finding an N that makes the SHA1(X-APPLE-HC) lead with Bits leading zero bits
41+
We get the "Resource" from the X-Apple-HC-Challenge header and Bits from X-Apple-HC-Bits
42+
*/
43+
/// - Parameters:
44+
/// - resource: a string to be used for minting
45+
/// - bits: grabbed from `X-Apple-HC-Bits` header
46+
/// - date: Default uses Date() otherwise used for testing to check.
47+
/// - Returns: A String hash to use in `X-Apple-HC` header on /signin
48+
public func mint(resource: String,
49+
bits: UInt = 10,
50+
date: String? = nil) -> String? {
51+
52+
let ver = "1"
53+
54+
var ts: String
55+
if let date = date {
56+
ts = date
57+
} else {
58+
let formatter = DateFormatter()
59+
formatter.dateFormat = "yyMMddHHmmss"
60+
ts = formatter.string(from: Date())
61+
}
62+
63+
let challenge = "\(ver):\(bits):\(ts):\(resource):"
64+
65+
var counter = 0
66+
67+
while true {
68+
guard let digest = ("\(challenge):\(counter)").sha1 else {
69+
print("ERROR: Can't generate SHA1 digest")
70+
return nil
71+
}
72+
73+
if digest == bits {
74+
return "\(challenge):\(counter)"
75+
}
76+
counter += 1
77+
}
78+
}
79+
}
80+
81+
extension String {
82+
var sha1: Int? {
83+
84+
let data = Data(self.utf8)
85+
var digest = [UInt8](repeating: 0, count:Int(CC_SHA1_DIGEST_LENGTH))
86+
data.withUnsafeBytes {
87+
_ = CC_SHA1($0.baseAddress, CC_LONG(data.count), &digest)
88+
}
89+
let bigEndianValue = digest.withUnsafeBufferPointer {
90+
($0.baseAddress!.withMemoryRebound(to: UInt32.self, capacity: 1) { $0 })
91+
}.pointee
92+
let value = UInt32(bigEndian: bigEndianValue)
93+
return value.leadingZeroBitCount
94+
}
95+
}
96+

0 commit comments

Comments
 (0)