1import {
2  convertKeyPairPEMToKeyPair,
3  convertCertificatePEMToCertificate,
4  validateSelfSignedCertificate,
5} from '@expo/code-signing-certificates';
6import { getConfig } from '@expo/config';
7import assert from 'assert';
8import { promises as fs } from 'fs';
9import path from 'path';
10
11import { log } from './utils/log';
12import { attemptModification } from './utils/modifyConfigAsync';
13
14type Options = { input?: string };
15
16export async function configureCodeSigningAsync(projectRoot: string, { input }: Options) {
17  assert(typeof input === 'string', '--input must be a string');
18
19  const inputDir = path.resolve(projectRoot, input);
20
21  const [certificatePEM, privateKeyPEM, publicKeyPEM] = await Promise.all(
22    ['certificate.pem', 'private-key.pem', 'public-key.pem'].map((fname) =>
23      fs.readFile(path.join(inputDir, fname), 'utf8')
24    )
25  );
26
27  const certificate = convertCertificatePEMToCertificate(certificatePEM);
28  const keyPair = convertKeyPairPEMToKeyPair({ privateKeyPEM, publicKeyPEM });
29  validateSelfSignedCertificate(certificate, keyPair);
30
31  const { exp } = getConfig(projectRoot, { skipSDKVersionRequirement: true });
32
33  // TODO(wschurman) type as ExpoConfig['updates'] when typedefs are updated
34  const fields: any = {
35    codeSigningCertificate: `./${path.relative(projectRoot, inputDir)}/certificate.pem`,
36    codeSigningMetadata: {
37      keyid: 'main',
38      alg: 'rsa-v1_5-sha256',
39    },
40  };
41  await attemptModification(
42    projectRoot,
43    {
44      updates: {
45        ...exp.updates,
46        ...fields,
47      },
48    },
49    {
50      updates: {
51        ...fields,
52      },
53    }
54  );
55
56  log(`Code signing configured for expo-updates (configuration written to app.json)`);
57}
58