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
2017-08-11 12:24:12 +08:00
// files in the repository. We'll decrypt and import our certificates,
2016-11-22 03:53:29 +08:00
// 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:
//
2017-08-11 12:24:12 +08:00
// codesign -dvvv /path/to/N1.app
2016-11-22 03:53:29 +08:00
//
// Which should return "accepted"
2017-09-27 02:33:08 +08:00
module . exports = grunt => {
2016-11-22 03:53:29 +08:00
let getCertData ;
2017-09-27 02:33:08 +08:00
const { spawnP } = grunt . config ( 'taskHelpers' ) ;
const tmpKeychain = 'n1-build.keychain' ;
2016-11-22 03:53:29 +08:00
const unlockKeychain = ( keychain , keychainPass ) => {
const args = [ 'unlock-keychain' , '-p' , keychainPass , keychain ] ;
2017-09-27 02:33:08 +08:00
return spawnP ( { cmd : 'security' , args } ) ;
2016-11-22 03:53:29 +08:00
} ;
const cleanupKeychain = ( ) => {
2017-09-27 02:33:08 +08:00
if ( fs . existsSync ( path . join ( process . env . HOME , 'Library' , 'Keychains' , tmpKeychain ) ) ) {
return spawnP ( { cmd : 'security' , args : [ 'delete-keychain' , tmpKeychain ] } ) ;
2016-11-22 03:53:29 +08:00
}
2017-09-27 02:33:08 +08:00
return Promise . resolve ( ) ;
2016-11-22 03:53:29 +08:00
} ;
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' ) ;
2017-09-27 02:33:08 +08:00
const { appleCert , nylasCert , nylasPrivateKey , keyPass } = getCertData ( ) ;
const codesignBin = path . join ( '/' , 'usr' , 'bin' , 'codesign' ) ;
2016-11-22 03:53:29 +08:00
// Create a custom, temporary keychain
2017-09-27 02:33:08 +08:00
return (
cleanupKeychain ( )
. then ( ( ) =>
spawnP ( { cmd : 'security' , args : [ 'create-keychain' , '-p' , tmpPass , tmpKeychain ] } )
)
// 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 ] } ) )
// Make the custom keychain default, so xcodebuild will use it for signing
. then ( ( ) => spawnP ( { cmd : 'security' , args : [ 'default-keychain' , '-s' , tmpKeychain ] } ) )
// Unlock the keychain
. then ( ( ) => unlockKeychain ( tmpKeychain , tmpPass ) )
// Set keychain timeout to 1 hour for long builds
. then ( ( ) =>
spawnP ( {
cmd : 'security' ,
args : [ 'set-keychain-settings' , '-t' , '3600' , '-l' , tmpKeychain ] ,
} )
)
// Add certificates to keychain and allow codesign to access them
. then ( ( ) =>
spawnP ( {
cmd : 'security' ,
args : [ 'import' , appleCert , '-k' , tmpKeychain , '-T' , codesignBin ] ,
} )
)
. then ( ( ) =>
spawnP ( {
cmd : 'security' ,
args : [ 'import' , nylasCert , '-k' , tmpKeychain , '-T' , codesignBin ] ,
} )
)
// Load the password for the private key from environment variables
. then ( ( ) =>
spawnP ( {
cmd : 'security' ,
args : [ 'import' , nylasPrivateKey , '-k' , tmpKeychain , '-P' , keyPass , '-T' , codesignBin ] ,
} )
)
// mark that the codesign utility should be allowed to access the keychain without
// prompting for access. (Needed for Mac OS Sierra and above)
// https://stackoverflow.com/questions/39868578/security-codesign-in-sierra-keychain-ignores-access-control-settings-and-ui-p
. then ( ( ) =>
spawnP ( {
cmd : 'security' ,
args : [
'set-key-partition-list' ,
'-S' ,
'apple-tool:,apple:,codesign:' ,
'-k' ,
tmpPass ,
tmpKeychain ,
] ,
} )
)
) ;
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' ) ;
2017-09-06 04:37:40 +08:00
const nylasCert = path . join ( certs , 'mac-codesigning.cer' ) ;
const nylasPrivateKey = path . join ( certs , 'mac-codesigning.p12' ) ;
2016-11-22 03:53:29 +08:00
const keyPass = process . env . APPLE _CODESIGN _KEY _PASSWORD ;
if ( ! keyPass ) {
2017-09-27 02:33:08 +08:00
throw new Error ( 'APPLE_CODESIGN_KEY_PASSWORD must be set' ) ;
2016-11-22 03:53:29 +08:00
}
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 ` ) ;
}
2017-09-27 02:33:08 +08:00
return { appleCert , nylasCert , nylasPrivateKey , keyPass } ;
2016-11-22 03:53:29 +08:00
} ;
const shouldRun = ( ) => {
if ( process . platform !== 'darwin' ) {
grunt . log . writeln ( ` Skipping keychain setup since ${ process . platform } is not darwin ` ) ;
2017-09-27 02:33:08 +08:00
return false ;
2016-11-22 03:53:29 +08:00
}
2017-09-27 02:33:08 +08:00
return ! ! process . env . SIGN _BUILD ;
} ;
2016-11-22 03:53:29 +08:00
2017-09-27 02:33:08 +08:00
grunt . registerTask (
'setup-mac-keychain' ,
'Setup Mac Keychain to sign the app' ,
function setupMacKeychain ( ) {
const done = this . async ( ) ;
if ( ! shouldRun ( ) ) return done ( ) ;
2016-11-22 03:53:29 +08:00
2017-09-27 02:33:08 +08:00
return buildMacKeychain ( )
. then ( done )
. catch ( grunt . fail . fatal ) ;
}
) ;
} ;