2016-11-22 03:53:29 +08:00
/* eslint global-require: 0 */
const path = require ( 'path' ) ;
const fs = require ( 'fs-plus' ) ;
// Codesigning is a Mac-only process that requires a valid Apple
// certificate, the private key, and access to the Mac keychain.
//
// We can only codesign from keys in the keychain. At the end of the day
// we need the certificate and private key to exist in the keychain
//
// In the case of Travis, we need to setup a temp keychain from encrypted
// files in the repository. # We'll decrypt and import our certificates,
// put them in a temporary keychain, and use that.
//
// If you want to verify the app was signed you can run the commands:
//
// spctl -a -t exec -vv /path/to/N1.app
//
// Which should return "satisfies its Designated Requirement"
//
// And:
//
// codesign --verify --deep --verbose=2 /path/to/N1.app
//
// Which should return "accepted"
module . exports = ( grunt ) => {
let getCertData ;
2017-03-14 06:48:45 +08:00
const { spawnP } = grunt . config ( 'taskHelpers' )
2016-11-22 03:53:29 +08:00
const tmpKeychain = "n1-build.keychain" ;
const unlockKeychain = ( keychain , keychainPass ) => {
const args = [ 'unlock-keychain' , '-p' , keychainPass , keychain ] ;
2016-11-22 06:28:41 +08:00
return spawnP ( { cmd : "security" , args } ) ;
2016-11-22 03:53:29 +08:00
} ;
const cleanupKeychain = ( ) => {
if ( fs . existsSync ( path . join ( process . env . HOME , "Library" , "Keychains" , tmpKeychain ) ) ) {
2016-11-22 06:28:41 +08:00
return spawnP ( { cmd : "security" , args : [ "delete-keychain" , tmpKeychain ] } ) ;
2016-11-22 03:53:29 +08:00
}
return Promise . resolve ( )
} ;
2017-03-14 06:48:45 +08:00
const buildMacKeychain = ( ) => {
2016-11-22 03:53:29 +08:00
const crypto = require ( 'crypto' ) ;
const tmpPass = crypto . randomBytes ( 32 ) . toString ( 'hex' ) ;
const { appleCert , nylasCert , nylasPrivateKey , keyPass } = getCertData ( ) ;
const codesignBin = path . join ( "/" , "usr" , "bin" , "codesign" ) ;
// Create a custom, temporary keychain
return cleanupKeychain ( )
2016-11-22 06:28:41 +08:00
. then ( ( ) => spawnP ( { cmd : "security" , args : [ "create-keychain" , '-p' , tmpPass , tmpKeychain ] } ) )
2016-11-22 03:53:29 +08:00
2017-03-03 09:56:09 +08:00
// Due to a bug in OSX, you must list-keychain with -s in order for it
// to actually add it to the list of keychains. See http://stackoverflow.com/questions/20391911/os-x-keychain-not-visible-to-keychain-access-app-in-mavericks
. then ( ( ) => spawnP ( { cmd : "security" , args : [ "list-keychains" , "-s" , tmpKeychain ] } ) )
2016-11-22 03:53:29 +08:00
// Make the custom keychain default, so xcodebuild will use it for signing
2016-11-22 06:28:41 +08:00
. then ( ( ) => spawnP ( { cmd : "security" , args : [ "default-keychain" , "-s" , tmpKeychain ] } ) )
2016-11-22 03:53:29 +08:00
// Unlock the keychain
. then ( ( ) => unlockKeychain ( tmpKeychain , tmpPass ) )
// Set keychain timeout to 1 hour for long builds
2016-11-22 06:28:41 +08:00
. then ( ( ) => spawnP ( { cmd : "security" , args : [ "set-keychain-settings" , "-t" , "3600" , "-l" , tmpKeychain ] } ) )
2016-11-22 03:53:29 +08:00
// Add certificates to keychain and allow codesign to access them
2016-11-22 06:28:41 +08:00
. then ( ( ) => spawnP ( { cmd : "security" , args : [ "import" , appleCert , "-k" , tmpKeychain , "-T" , codesignBin ] } ) )
2016-11-22 03:53:29 +08:00
2016-11-22 06:28:41 +08:00
. then ( ( ) => spawnP ( { cmd : "security" , args : [ "import" , nylasCert , "-k" , tmpKeychain , "-T" , codesignBin ] } ) )
2016-11-22 03:53:29 +08:00
// Load the password for the private key from environment variables
2016-11-22 06:28:41 +08:00
. then ( ( ) => spawnP ( { cmd : "security" , args : [ "import" , nylasPrivateKey , "-k" , tmpKeychain , "-P" , keyPass , "-T" , codesignBin ] } ) ) ;
2016-11-22 03:53:29 +08:00
} ;
getCertData = ( ) => {
2017-02-23 05:19:45 +08:00
const certs = path . resolve ( path . join ( grunt . config ( 'buildDir' ) , 'resources' , 'certs' , 'mac' ) ) ;
2016-11-22 03:53:29 +08:00
const appleCert = path . join ( certs , 'AppleWWDRCA.cer' ) ;
const nylasCert = path . join ( certs , 'mac-nylas-n1.cer' ) ;
const nylasPrivateKey = path . join ( certs , 'mac-nylas-n1.p12' ) ;
const keyPass = process . env . APPLE _CODESIGN _KEY _PASSWORD ;
if ( ! keyPass ) {
throw new Error ( "APPLE_CODESIGN_KEY_PASSWORD must be set" ) ;
}
if ( ! fs . existsSync ( appleCert ) ) {
throw new Error ( ` ${ appleCert } doesn't exist ` ) ;
}
if ( ! fs . existsSync ( nylasCert ) ) {
throw new Error ( ` ${ nylasCert } doesn't exist ` ) ;
}
if ( ! fs . existsSync ( nylasPrivateKey ) ) {
throw new Error ( ` ${ nylasPrivateKey } doesn't exist ` ) ;
}
return { appleCert , nylasCert , nylasPrivateKey , keyPass } ;
} ;
const shouldRun = ( ) => {
if ( process . platform !== 'darwin' ) {
grunt . log . writeln ( ` Skipping keychain setup since ${ process . platform } is not darwin ` ) ;
return false
}
2017-03-14 06:48:45 +08:00
return ! ! process . env . SIGN _BUILD
2016-11-22 03:53:29 +08:00
}
2017-03-14 06:48:45 +08:00
grunt . registerTask ( 'setup-mac-keychain' , 'Setup Mac Keychain to sign the app' , function setupMacKeychain ( ) {
2016-11-22 03:53:29 +08:00
const done = this . async ( ) ;
if ( ! shouldRun ( ) ) return done ( ) ;
2017-03-14 06:48:45 +08:00
return buildMacKeychain ( ) . then ( done ) . catch ( grunt . fail . fatal ) ;
2016-11-22 03:53:29 +08:00
} ) ;
}