Skip to content

Commit f48a181

Browse files
authored
fix: remove now-unusable "legacy" notarization (#187)
* Remove now-unusable "legacy" notarization * Make `notarize` function overloaded * Update docs more
1 parent ba33a3d commit f48a181

File tree

5 files changed

+59
-203
lines changed

5 files changed

+59
-203
lines changed

README.md

Lines changed: 9 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ Apple has made this a hard requirement as of 10.15 (Catalina).
3030

3131
For notarization, you need the following things:
3232

33-
1. Xcode 10 or later installed on your Mac.
33+
1. Xcode 13 or later installed on your Mac.
3434
2. An [Apple Developer](https://developer.apple.com/) account.
3535
3. [An app-specific password for your ADC account’s Apple ID](https://support.apple.com/HT204397).
3636
4. Your app may need to be signed with `hardened-runtime`, including the following entitlement:
@@ -44,29 +44,20 @@ For notarization, you need the following things:
4444
### Method: `notarize(opts): Promise<void>`
4545

4646
* `options` Object
47-
* `tool` String - The notarization tool to use, default is `notarytool`. Can be `legacy` or `notarytool`. `notarytool` is substantially (10x) faster and `legacy` is deprecated and will **stop working** on November 1st 2023.
47+
* `tool` String - The notarization tool to use, default is `notarytool`. Previously, the value `legacy` used `altool`, which [**stopped working** on November 1st 2023](https://developer.apple.com/news/?id=y5mjxqmn).
4848
* `appPath` String - The absolute path to your `.app` file
49-
* There are different options for each tool: Notarytool
50-
* There are three authentication methods available: user name with password:
51-
* `appleId` String - The username of your apple developer account
49+
* There are three authentication methods available:
50+
* user name with password:
51+
* `appleId` String - The username of your Apple Developer account
5252
* `appleIdPassword` String - The [app-specific password](https://support.apple.com/HT204397) (not your Apple ID password).
53-
* `teamId` String - The team ID you want to notarize under.
53+
* `teamId` String - The [team ID](https://developer.apple.com/help/account/manage-your-team/locate-your-team-id/) you want to notarize under.
5454
* ... or apiKey with apiIssuer:
5555
* `appleApiKey` String - Absolute path to the `.p8` file containing the key. Required for JWT authentication. See Note on JWT authentication below.
5656
* `appleApiKeyId` String - App Store Connect API key ID, for example, `T9GPZ92M7K`. Required for JWT authentication. See Note on JWT authentication below.
5757
* `appleApiIssuer` String - Your App Store Connect API key issuer, for example, `c055ca8c-e5a8-4836-b61d-aa5794eeb3f4`. Required if `appleApiKey` is specified.
5858
* ... or keychain with keychainProfile:
5959
* `keychain` String (optional) - The name of the keychain or path to the keychain you stored notarization credentials in. If omitted, iCloud keychain is used by default.
6060
* `keychainProfile` String - The name of the profile you provided when storing notarization credentials.
61-
* ... or Legacy
62-
* `appBundleId` String - The app bundle identifier your Electron app is using. E.g. `com.github.electron`
63-
* `ascProvider` String (optional) - Your [Team Short Name](#notes-on-your-team-short-name).
64-
* There are two authentication methods available: user name with password:
65-
* `appleId` String - The username of your apple developer account
66-
* `appleIdPassword` String - The [app-specific password](https://support.apple.com/HT204397) (not your Apple ID password).
67-
* ... or apiKey with apiIssuer:
68-
* `appleApiKey` String - Required for JWT authentication. See Note on JWT authentication below.
69-
* `appleApiIssuer` String - Issuer ID. Required if `appleApiKey` is specified.
7061

7162
## Safety when using `appleIdPassword`
7263

@@ -92,34 +83,11 @@ const password = `@keychain:AC_PASSWORD`;
9283

9384
## Notes on JWT authentication
9485

95-
You can obtain an API key from [Appstore Connect](https://appstoreconnect.apple.com/access/api). Create a key with _App Manager_ access. Note down the Issuer ID and download the `.p8` file. This file is your API key and comes with the name of `AuthKey_<appleApiKeyId>.p8`. This is the string you have to supply when calling `notarize`.
96-
97-
Based on the `ApiKey`, the legacy `altool` will look in the following places for that file:
98-
99-
* `./private_keys`
100-
* `~/private_keys`
101-
* `~/.private_keys`
102-
* `~/.appstoreconnect/private_keys`
103-
104-
`notarytool` will not look for the key, and you must instead provide its path as the `appleApiKey` argument.
105-
106-
## Notes on your Team Short Name
107-
108-
If you are a member of multiple teams or organizations, you have to tell Apple on behalf of which organization you're uploading. To find your [team's short name](https://forums.developer.apple.com/thread/113798)), you can ask `iTMSTransporter`, which is part of the now deprecated `Application Loader` as well as the newer [`Transporter`](https://apps.apple.com/us/app/transporter/id1450874784?mt=12).
109-
110-
With `Transporter` installed, run:
111-
```sh
112-
/Applications/Transporter.app/Contents/itms/bin/iTMSTransporter -m provider -u APPLE_DEV_ACCOUNT -p APP_PASSWORD
113-
```
114-
115-
Alternatively, with older versions of Xcode, run:
116-
```sh
117-
/Applications/Xcode.app/Contents/Applications/Application Loader.app/Contents/itms/bin/iTMSTransporter -m provider -u APPLE_DEV_ACCOUNT -p APP_PASSWORD
118-
```
86+
You can obtain an API key from [App Store Connect](https://appstoreconnect.apple.com/access/api). Create a _Team Key_ (not an _Individual Key_) with _App Manager_ access. Note down the Issuer ID and download the `.p8` file. This file is your API key and comes with the name of `AuthKey_<appleApiKeyId>.p8`. Provide the path to this file as the `appleApiKey` argument.
11987

12088
## Notes on your teamId
12189

122-
If you use the new Notary Tool method with `appleId`/`appleIdPassword` you will need to set the `teamId` option. To get this ID, go to your [Apple Developer Account](https://developer.apple.com/account), then click on "Membership details", and there you will find your Team ID. This link should get you there directly: https://developer.apple.com/account#MembershipDetailsCard
90+
To get your `teamId` value, go to your [Apple Developer Account](https://developer.apple.com/account), then click on "Membership details", and there you will find your Team ID.
12391

12492
## Debug
12593

@@ -133,11 +101,10 @@ import { notarize } from '@electron/notarize';
133101
async function packageTask () {
134102
// Package your app here, and code sign with hardened runtime
135103
await notarize({
136-
appBundleId,
137104
appPath,
138105
appleId,
139106
appleIdPassword,
140-
ascProvider, // This parameter is optional
107+
teamId,
141108
});
142109
}
143110
```

src/index.ts

Lines changed: 27 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -2,58 +2,47 @@ import debug from 'debug';
22
import retry from 'promise-retry';
33

44
import { checkSignatures } from './check-signature';
5-
import { delay } from './helpers';
6-
import { startLegacyNotarize, waitForLegacyNotarize } from './legacy';
75
import { isNotaryToolAvailable, notarizeAndWaitForNotaryTool } from './notarytool';
86
import { stapleApp } from './staple';
9-
import { NotarizeOptions, NotaryToolStartOptions } from './types';
7+
import {
8+
NotarizeOptions,
9+
NotaryToolStartOptions,
10+
NotarizeOptionsLegacy,
11+
NotarizeOptionsNotaryTool,
12+
} from './types';
1013

1114
const d = debug('electron-notarize');
1215

1316
export { NotarizeOptions };
1417

15-
export { validateLegacyAuthorizationArgs as validateAuthorizationArgs } from './validate-args';
18+
export { validateNotaryToolAuthorizationArgs as validateAuthorizationArgs } from './validate-args';
1619

17-
export async function notarize({ appPath, ...otherOptions }: NotarizeOptions) {
18-
await checkSignatures({ appPath });
20+
async function notarize(args: NotarizeOptionsNotaryTool): Promise<void>;
21+
/** @deprecated */
22+
async function notarize(args: NotarizeOptionsLegacy): Promise<void>;
1923

24+
async function notarize({ appPath, ...otherOptions }: NotarizeOptions) {
2025
if (otherOptions.tool === 'legacy') {
21-
console.warn(
22-
'Notarizing using the legacy altool system. The altool system will be disabled on November 1 2023. Please switch to the notarytool system before then.',
23-
);
24-
console.warn(
25-
'You can do this by setting "tool: notarytool" in your "@electron/notarize" options. Please note that the credentials options may be slightly different between tools.',
26+
throw new Error(
27+
'Notarization with the legacy altool system was decommisioned as of November 2023',
2628
);
27-
d('notarizing using the legacy notarization system, this will be slow');
28-
const { uuid } = await startLegacyNotarize({
29-
appPath,
30-
...otherOptions,
31-
});
32-
/**
33-
* Wait for Apples API to initialize the status UUID
34-
*
35-
* If we start checking too quickly the UUID is not ready yet
36-
* and this step will fail. It takes Apple a number of minutes
37-
* to actually complete the job so an extra 10 second delay here
38-
* is not a big deal
39-
*/
40-
d('notarization started, waiting for 10 seconds before pinging Apple for status');
41-
await delay(10000);
42-
d('starting to poll for notarization status');
43-
await waitForLegacyNotarize({ uuid, ...otherOptions });
44-
} else {
45-
d('notarizing using the new notarytool system');
46-
if (!(await isNotaryToolAvailable())) {
47-
throw new Error('notarytool is not available, you must be on at least Xcode 13');
48-
}
49-
50-
await notarizeAndWaitForNotaryTool({
51-
appPath,
52-
...otherOptions,
53-
} as NotaryToolStartOptions);
5429
}
5530

31+
await checkSignatures({ appPath });
32+
33+
d('notarizing using notarytool');
34+
if (!(await isNotaryToolAvailable())) {
35+
throw new Error('notarytool is not available, you must be on at least Xcode 13');
36+
}
37+
38+
await notarizeAndWaitForNotaryTool({
39+
appPath,
40+
...otherOptions,
41+
} as NotaryToolStartOptions);
42+
5643
await retry(() => stapleApp({ appPath }), {
5744
retries: 3,
5845
});
5946
}
47+
48+
export { notarize };

src/legacy.ts

Lines changed: 9 additions & 120 deletions
Original file line numberDiff line numberDiff line change
@@ -1,129 +1,18 @@
11
import debug from 'debug';
2-
import * as path from 'path';
32

4-
import { spawn } from './spawn';
5-
import { withTempDir, makeSecret, parseNotarizationInfo, delay } from './helpers';
6-
import { validateLegacyAuthorizationArgs, isLegacyPasswordCredentials } from './validate-args';
7-
import {
8-
NotarizeResult,
9-
LegacyNotarizeStartOptions,
10-
LegacyNotarizeWaitOptions,
11-
LegacyNotarizeCredentials,
12-
} from './types';
3+
import { LegacyNotarizeStartOptions, LegacyNotarizeWaitOptions } from './types';
134

145
const d = debug('electron-notarize:legacy');
156

16-
function authorizationArgs(rawOpts: LegacyNotarizeCredentials): string[] {
17-
const opts = validateLegacyAuthorizationArgs(rawOpts);
18-
if (isLegacyPasswordCredentials(opts)) {
19-
return ['-u', makeSecret(opts.appleId), '-p', makeSecret(opts.appleIdPassword)];
20-
} else {
21-
return [
22-
'--apiKey',
23-
makeSecret(opts.appleApiKey),
24-
'--apiIssuer',
25-
makeSecret(opts.appleApiIssuer),
26-
];
27-
}
28-
}
29-
30-
export async function startLegacyNotarize(
31-
opts: LegacyNotarizeStartOptions,
32-
): Promise<NotarizeResult> {
7+
/** @deprecated */
8+
export async function startLegacyNotarize(opts: LegacyNotarizeStartOptions): Promise<never> {
339
d('starting notarize process for app:', opts.appPath);
34-
return await withTempDir<NotarizeResult>(async dir => {
35-
const zipPath = path.resolve(dir, `${path.basename(opts.appPath, '.app')}.zip`);
36-
d('zipping application to:', zipPath);
37-
const zipResult = await spawn(
38-
'ditto',
39-
['-c', '-k', '--sequesterRsrc', '--keepParent', path.basename(opts.appPath), zipPath],
40-
{
41-
cwd: path.dirname(opts.appPath),
42-
},
43-
);
44-
if (zipResult.code !== 0) {
45-
throw new Error(
46-
`Failed to zip application, exited with code: ${zipResult.code}\n\n${zipResult.output}`,
47-
);
48-
}
49-
d('zip succeeded, attempting to upload to Apple');
50-
51-
const notarizeArgs = [
52-
'altool',
53-
'--notarize-app',
54-
'-f',
55-
zipPath,
56-
'--primary-bundle-id',
57-
opts.appBundleId,
58-
...authorizationArgs(opts),
59-
];
60-
61-
if (opts.ascProvider) {
62-
notarizeArgs.push('-itc_provider', opts.ascProvider);
63-
}
64-
65-
const result = await spawn('xcrun', notarizeArgs);
66-
if (result.code !== 0) {
67-
throw new Error(`Failed to upload app to Apple's notarization servers\n\n${result.output}`);
68-
}
69-
d('upload success');
70-
71-
const uuidMatch = /\nRequestUUID = (.+?)\n/g.exec(result.output);
72-
if (!uuidMatch) {
73-
throw new Error(`Failed to find request UUID in output:\n\n${result.output}`);
74-
}
75-
76-
d('found UUID:', uuidMatch[1]);
77-
78-
return {
79-
uuid: uuidMatch[1],
80-
};
81-
});
10+
throw new Error('Cannot start notarization. Legacy notarization (altool) is no longer available');
8211
}
8312

84-
export async function waitForLegacyNotarize(opts: LegacyNotarizeWaitOptions): Promise<void> {
85-
d('checking notarization status:', opts.uuid);
86-
const result = await spawn('xcrun', [
87-
'altool',
88-
'--notarization-info',
89-
opts.uuid,
90-
...authorizationArgs(opts),
91-
]);
92-
if (result.code !== 0) {
93-
// These checks could fail for all sorts of reasons, including:
94-
// * The status of a request isn't available as soon as the upload request returns, so
95-
// it may just not be ready yet.
96-
// * If using keychain password, user's mac went to sleep and keychain locked.
97-
// * Regular old connectivity failure.
98-
d(
99-
`Failed to check status of notarization request, retrying in 30 seconds: ${opts.uuid}\n\n${result.output}`,
100-
);
101-
await delay(30000);
102-
return waitForLegacyNotarize(opts);
103-
}
104-
const notarizationInfo = parseNotarizationInfo(result.output);
105-
106-
if (notarizationInfo.status === 'in progress') {
107-
d('still in progress, waiting 30 seconds');
108-
await delay(30000);
109-
return waitForLegacyNotarize(opts);
110-
}
111-
112-
d('notarzation done with info:', notarizationInfo);
113-
114-
if (notarizationInfo.status === 'invalid') {
115-
d('notarization failed');
116-
throw new Error(`Apple failed to notarize your application, check the logs for more info
117-
118-
Status Code: ${notarizationInfo.statusCode || 'No Code'}
119-
Message: ${notarizationInfo.statusMessage || 'No Message'}
120-
Logs: ${notarizationInfo.logFileUrl}`);
121-
}
122-
123-
if (notarizationInfo.status !== 'success') {
124-
throw new Error(`Unrecognized notarization status: "${notarizationInfo.status}"`);
125-
}
126-
127-
d('notarization was successful');
128-
return;
13+
/** @deprecated */
14+
export async function waitForLegacyNotarize(opts: LegacyNotarizeWaitOptions): Promise<never> {
15+
throw new Error(
16+
'Cannot wait for notarization. Legacy notarization (altool) is no longer available',
17+
);
12918
}

src/types.ts

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
/** @deprecated */
12
export interface LegacyNotarizePasswordCredentials {
23
appleId: string;
34
appleIdPassword: string;
@@ -9,6 +10,7 @@ export interface NotaryToolPasswordCredentials {
910
teamId: string;
1011
}
1112

13+
/** @deprecated */
1214
export interface LegacyNotarizeApiKeyCredentials {
1315
appleApiKey: string;
1416
appleApiIssuer: string;
@@ -25,6 +27,7 @@ export interface NotaryToolKeychainCredentials {
2527
keychain?: string;
2628
}
2729

30+
/** @deprecated */
2831
export type LegacyNotarizeCredentials =
2932
| LegacyNotarizePasswordCredentials
3033
| LegacyNotarizeApiKeyCredentials;
@@ -34,6 +37,7 @@ export type NotaryToolCredentials =
3437
| NotaryToolKeychainCredentials;
3538
export type NotarizeCredentials = LegacyNotarizeCredentials | NotaryToolCredentials;
3639

40+
/** @deprecated */
3741
export interface LegacyNotarizeAppOptions {
3842
appPath: string;
3943
appBundleId: string;
@@ -51,12 +55,16 @@ export interface NotarizeResult {
5155
uuid: string;
5256
}
5357

58+
/** @deprecated */
5459
export type LegacyNotarizeStartOptions = LegacyNotarizeAppOptions &
5560
LegacyNotarizeCredentials &
5661
TransporterOptions;
5762
export type NotaryToolStartOptions = NotaryToolNotarizeAppOptions & NotaryToolCredentials;
63+
/** @deprecated */
5864
export type LegacyNotarizeWaitOptions = NotarizeResult & LegacyNotarizeCredentials;
5965
export type NotarizeStapleOptions = Pick<LegacyNotarizeAppOptions, 'appPath'>;
60-
export type NotarizeOptions =
61-
| ({ tool?: 'legacy' } & LegacyNotarizeStartOptions)
62-
| ({ tool: 'notarytool' } & NotaryToolStartOptions);
66+
67+
/** @deprecated */
68+
export type NotarizeOptionsLegacy = { tool: 'legacy' } & LegacyNotarizeStartOptions;
69+
export type NotarizeOptionsNotaryTool = { tool?: 'notarytool' } & NotaryToolStartOptions;
70+
export type NotarizeOptions = NotarizeOptionsLegacy | NotarizeOptionsNotaryTool;

src/validate-args.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,20 +8,23 @@ import {
88
NotaryToolPasswordCredentials,
99
} from './types';
1010

11+
/** @deprecated */
1112
export function isLegacyPasswordCredentials(
1213
opts: LegacyNotarizeCredentials,
1314
): opts is LegacyNotarizePasswordCredentials {
1415
const creds = opts as LegacyNotarizePasswordCredentials;
1516
return creds.appleId !== undefined || creds.appleIdPassword !== undefined;
1617
}
1718

19+
/** @deprecated */
1820
export function isLegacyApiKeyCredentials(
1921
opts: LegacyNotarizeCredentials,
2022
): opts is LegacyNotarizeApiKeyCredentials {
2123
const creds = opts as LegacyNotarizeApiKeyCredentials;
2224
return creds.appleApiKey !== undefined || creds.appleApiIssuer !== undefined;
2325
}
2426

27+
/** @deprecated */
2528
export function validateLegacyAuthorizationArgs(
2629
opts: LegacyNotarizeCredentials,
2730
): LegacyNotarizeCredentials {

0 commit comments

Comments
 (0)