1import { 2 generateKeyPair, 3 generateSelfSignedCodeSigningCertificate, 4 convertCertificateToCertificatePEM, 5 convertKeyPairToPEM, 6} from '@expo/code-signing-certificates'; 7import assert from 'assert'; 8import { promises as fs } from 'fs'; 9import path from 'path'; 10 11import { ensureDirAsync } from './utils/dir'; 12import { log } from './utils/log'; 13 14type Options = { 15 certificateValidityDurationYears: number; 16 keyOutput: string; 17 certificateOutput: string; 18 certificateCommonName: string; 19}; 20 21export async function generateCodeSigningAsync( 22 projectRoot: string, 23 { certificateValidityDurationYears, keyOutput, certificateOutput, certificateCommonName }: Options 24) { 25 const validityDurationYears = Math.floor(certificateValidityDurationYears); 26 27 const certificateOutputDir = path.resolve(projectRoot, certificateOutput); 28 const keyOutputDir = path.resolve(projectRoot, keyOutput); 29 await Promise.all([ensureDirAsync(certificateOutputDir), ensureDirAsync(keyOutputDir)]); 30 31 const [certificateOutputDirContents, keyOutputDirContents] = await Promise.all([ 32 fs.readdir(certificateOutputDir), 33 fs.readdir(keyOutputDir), 34 ]); 35 assert(certificateOutputDirContents.length === 0, 'Certificate output directory must be empty'); 36 assert(keyOutputDirContents.length === 0, 'Key output directory must be empty'); 37 38 const keyPair = generateKeyPair(); 39 const validityNotBefore = new Date(); 40 const validityNotAfter = new Date(); 41 validityNotAfter.setFullYear(validityNotAfter.getFullYear() + validityDurationYears); 42 const certificate = generateSelfSignedCodeSigningCertificate({ 43 keyPair, 44 validityNotBefore, 45 validityNotAfter, 46 commonName: certificateCommonName, 47 }); 48 49 const keyPairPEM = convertKeyPairToPEM(keyPair); 50 const certificatePEM = convertCertificateToCertificatePEM(certificate); 51 52 await Promise.all([ 53 fs.writeFile(path.join(keyOutputDir, 'public-key.pem'), keyPairPEM.publicKeyPEM), 54 fs.writeFile(path.join(keyOutputDir, 'private-key.pem'), keyPairPEM.privateKeyPEM), 55 fs.writeFile(path.join(certificateOutputDir, 'certificate.pem'), certificatePEM), 56 ]); 57 58 log( 59 `Generated public and private keys output in ${keyOutputDir}. Remember to add them to .gitignore or to encrypt them. (e.g. with git-crypt)` 60 ); 61 log(`Generated code signing certificate output in ${certificateOutputDir}.`); 62 log( 63 `To automatically configure this project for code signing, run \`yarn expo-updates codesigning:configure --certificate-input-directory=${certificateOutput} --key-input-directory=${keyOutput}\`.` 64 ); 65} 66