summaryrefslogtreecommitdiffstats
path: root/install/macos_packages/readme.md
blob: c56f84c33873f9625280e0c5a7b7edd9e0b685cd (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
# MacOS Codesigning Scripts

Well, here we are. The Apple notarization procedure is complex enough that I
need an actual pile of scripts and writeup to be able to remember how to do it.

The basic procedure is as follows:

- Build code
- Build docs
- Sign binary with Developer Application ID
- Upload binary to notarization service to get notarized
- Use code + docs to generate a component package
- Use component package to generate a distribution package
- Sign distribution package with Developer Installer ID
- Upload distribution package to notarization service to get notarized

Et. voila, you have a notarized distribution package which can be installed.

I fully anticipate that this procedure will break in the future, so here is the
(scant) documentation that I have been able to scrape together on these procedures,
along with some commentary on things that I've found and requirements for these
scripts.

You will need XCode installed.

## Short-form Command Line Invocation

If you have the prerequisites set up (including the environment variables as
described below, built docs, and built release binary), you can generate the
package file with the following command:

```
./install/macos_packages/build_and_notarize.sh target/release/starship docs x64
```

or `arm64` if building on Apple silicon.

## Setting Up Credentials

### Apple Developer Account

In order to get the signing keys, you need to have a developer account. You can
buy one at https://developer.apple.com/programs/ for $100 a year (at time of
writing).

There is no other way to acquire an account, which is needed to obtain
non-self-signed keys and to be able to notarize files.

### Signing Keys

To generate the signing keys, I went through the [XcodeGUI](https://help.apple.com/xcode/mac/current/#/dev154b28f09), though there are
several other methods to do this. You will need at least one Application signing
key and one Installer signing key.

To check what signing keys are available, you can use the following command:

```
security find-identity -p basic -v
```

### Notarization Credentials

To be able to notarize objects, you will need an app-specific password. You will
need to set it up using the instructions [on this page](https://support.apple.com/en-us/HT204397).
You will also need your team ID, which can be found at https://developer.apple.com/account/#/membership
(if it goes to the home page, click on "Membership" on the left panel), and your
Apple ID (usually an email address).

If you want to enter everything manually, most commands that require these values
accept the `--apple-id`, `--team-id`, and `--password` flags. However, I find it
simpler to store the credentials in the keychain. You can do so with the following
command:

```
xcrun notarytool store-credentials "<AUTH_ITEM_NAME>" --apple-id "<apple-id>" --password "<password>" --team-id "<team-id>"
```

where `<AUTH_ITEM_NAME>` is a name you will use later to refer to the credentials,
and the other three items are the Apple ID, the Team ID, and the app-specific password,
respectively. For the rest of this document, I will assume that its value is
`AC_PASSWORD` for compatibility with Apple's website, though you may choose
whatever you like.

### Script Assumptions

The scripts in this directory assume that the signing keys and the notarization
credentials are unlocked and available within a specific keychain file, stored
in a file at `$RUNNER_TEMP/$KEYCHAIN_FILENAME`. Additionally, it assumes that
the `AUTH_ITEM_NAME` used to refer to the notarization credentials is found in
the environment under the variable `KEYCHAIN_ENTRY`.

The CI environment ensures that the keychain file exists at the appropriate
locations and is destroyed after use. If you are running these scripts locally,
the values that correspond to what Apple uses in their tutorials are:

```
KEYCHAIN_ENTRY=AC_PASSWORD   # Or whatever you picked for <AUTH_ITEM_NAME> above
RUNNER_TEMP=~/Library/Keychains
KEYCHAIN_FILENAME=login.keychain
```

Note to developers: because the keychain file may be a user's personal keychain,
you MUST NEVER WRITE TO THE KEYCHAIN FILE in these scripts. On the CI, CI actions
will ensure that the keychain file is shredded after use.

## Codesigning a Binary

This is actually fairly simple. Run

```
codesign --timestamp --sign "<Key ID>" --verbose -f -o runtime <binary>
```

to sign the binary file. `--timestamp` is not required for signing, but will be
required for notarization. `<Key ID>` can be one of two things: the name of the
signing key (on the right of `security find-identity -p basic -v`), or the key
hash (the hex string on the left of the command).

Usually you can use name of the key, but if you have multiple keys like me, you
may need to use the hex string to specify.

## Notarizing a Binary

Once the binary has been signed, you need to package it into a .zip file in order
to be able to send it to Apple for notarization. The simplest way to do this is
to run `zip <archive.zip> <binary>`.

Then, run `xcrun notarytool submit <archive.zip> --keychain-profile "AC_PASSWORD" --wait`
to submit the binary for notarization. The `--wait` flag will cause the tool to
block until the notarialization is complete. If you want to be able to leave and
check the results later, omit `--wait` (though starship notarization usually takes
no more than 60s).

Finally, you should check the submission logs. To get a record of all notarization
attempts, run

```
xcrun notarytool history --keychain-profile "AC_PASSWORD"
```

Find the `id` of the attempt you wish to view, then run one of these commmands:

```
xcrun notarytool info <run-id> --keychain-profile "AC_PASSWORD"
xcrun notarytool log <run-id> --keychain-profile "AC_PASSWORD"
```

The `log` command downloads a JSON log of the notarization attempt, and can reveal
warnings that should be fixed before the next submission.

Additional details on the notarization process can be found at https://developer.apple.com/documentation/security/notarizing_macos_software_before_distribution/customizing_the_notarization_workflow.
Note that while Apple has a lot of requirements on their pages, including stuff
like Hardened Runtime requirements and listing entitlements, as far as I can tell,
starship does not require any of these even though we do things like send
notifications and access the network via an HTTP client. Nonetheless, I'm
linking the [entitlements page](https://developer.apple.com/documentation/bundleresources/entitlements)
here in case it becomes important later.

## Creating a Component Package

Since I'm only dealing with one binary, we will make one Component package and
one Distribution package. Surprisingly, the flat package (.pkg) format is not
documented by Apple. [This guide](https://matthew-brett.github.io/docosx/flat_packages.html)
and many of the links within it are the best documentation available on the subject.

To build a component package, we first need to create a temporary directory and
create a pseudo-filesystem within it (similar to makepkg on Arch). For example,
if we place the directory at `$TEMP_DIR/usr/local/bin/starship`, the binary
will be installed at `/usr/local/bin/starship` once the installer runs.

An aside on docs: We would also like to include documentation in the pkg.
Unfortunately, Vuepress currently cannot build with relative paths, and any
attempt at hacking this in seems to create even more problems. Instead, the
scripts do the dumbest thing imaginable: build the documentation, serve it with
a simple HTTP server, and then use `wget` to make a local copy which can be
viewed offline.

Once everything is placed in the correct locations, we can run the following
command to generate the component package:

```
pkgbuild --identifier com.starshipprompt.starship --version "<version>" --root <pkgdir> output.pkg
```

## Notarizing the Component Package (and why we don't need to)

Fortunately for us, Apple has confirmed that we only need to notarize the
[outermost installer mechanism](https://developer.apple.com/forums/thread/122045).

Therefore, if we are sending the component package on its own, we should notarize
it now. However, for starship, we will bundle this into a distribution package,
so we don't need to notarize this pkg file.

## Creating a Distribution Package

To create a distribution, we do the following steps:

- Use `productbuild` to generate a skeleton distribution file.
- Insert custom welcome/license/conclusion and icon files into the installer.
- Build the installer with `productbuild`.

I have elected not to make a fat binary due to concerns over startup cost, so
there are two .plist files that can be used to specify the architecture required.

## Signing the Distribution package

This is also fairly simple, and analagous to signing the binary.

```
productsign --timestamp --sign "<Key ID>" <input.pkg> <output.pkg>
```

## Notarizing the Distribution Package

Also analagous to notarizing the binary. We run

```
xcrun notarytool submit <package.pkg> --keychain-profile "AC_PASSWORD" --wait
```

and also check the submission logs.

Note: you may need to enter your password a ridiculous number of times (like 4+)
in order to successfully notarize this.

## Stapling the Result

Finally, we staple the notarization ticket to the package, ensuring that anyone
who downloads the file can see that the installer was notarized:

```
xcrun stapler staple <package>
```

Note that `.dmg`, `.app`, and `.pkg` files can be stapled, but `.zip` and
binary files cannot. Distributing the latter files alone will require that the
installing computer can access the internet to verify notarization of the app.

## Putting It All Together

If you don't want to run these commands, a full workflow is available in
`build_and_notarize` script. Check the documentation at the top of the script
for environment variables and arguments that need to be set--it is a fairly
complicated script, but this is a fairly complicated procedure.

# Testing Notarization

To test if a particular item is notarized, run one of the following commands:

```
codesign --test-requirement="=notarized" --verify --verbose <file>
spctl -a -vvv -t install <file>
```

# External Links

https://developer.apple.com/documentation/security/notarizing_macos_software_before_distribution

https://developer.apple.com/documentation/security/notarizing_macos_software_before_distribution/customizing_the_notarization_workflow

https://github.com/akeru-inc/xcnotary

https://www.reddit.com/r/rust/comments/q8r90b/notarization_of_rust_binary_for_distribution_on/