Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Doing CI like a boss

Doing CI like a boss

iOS continuous integration details with the new Jenkins iOSBuilder plugin presenting.


June 19, 2013

More Decks by 0xc010d

Other Decks in Programming


  1. I... • work for Service2Media • mainly do iOS stuff

    • have a wife and two cats • like ukrainian borsch
  2. Why do we need CI? • Early problems detection “Who

    has broken the repo??!!” • Independent sources check “I swear it works on my machine!!” • “Current” build availability “Hey! It’s already 16:55! Could you make a build?” • You could find your own reasons
  3. Code signing strategies • Setup everything on the CI server

    Nasty setup and maintenance • Keep private key/certificate/profile in the sources Every time you do so, God kills a kitten
  4. Private key/certificate file • Normally it’s a .pem file •

    Usually it’s a .p12 file from the Keychain • Certificate might be embedded in .p12 • Alternatively certificate might be given as .cer file
  5. .p12 file • PKCS#12 archive • Designed to store multiple

    cryptographic objects • Could contain more than one private key with corresponding certificate
  6. Reading .p12 file • In bash (using openssl) • In

    Cocoa (using Security.framework) • In Java (using java.security)
  7. Reading .p12 file (bash) • Getting identity from PKCS#12 archive

    $ openssl pkcs12 -in archive.p12 -nokeys -password pass:"mycoolpassword" MAC verified OK Bag Attributes friendlyName: iPhone Developer: Eugene Solodovnykov (F2JSRW3HY6) localKeyID: DD 9E ... issuer=/C=US/O=Apple Inc./OU=Apple Worldwide Developer Relations/CN=Apple Worldwide Developer Relations Certification Authority -----BEGIN CERTIFICATE----- MIIFijCCBHKgAwIBAgIIIQQjJjKc67owDQYJKoZIhvcNAQEFBQAwgZYxCzAJBgNV ... • | grep friendlyName | sed 's/^[ \t]*friendlyName: //'
  8. Reading .p12 file (Cocoa) #import <Foundation/Foundation.h> #import <Security/Security.h> int main()

    { NSString *file = [[NSUserDefaults standardUserDefaults] stringForKey:@"f"]; NSString *pwd = [[NSUserDefaults standardUserDefaults] stringForKey:@"p"] ?: @""; NSData *data = [NSData dataWithContentsOfFile:file]; NSDictionary *options = @{(__bridge NSString *)kSecImportExportPassphrase: pwd}; CFArrayRef items = nil; SecPKCS12Import((__bridge CFDataRef)data, (__bridge CFDictionaryRef)options, &items); NSLog(@"%@", items); if (items != nil) { CFRelease(items); } }
  9. $ clang -framework Foundation -framework Security -fobjc-arc -o pkcs12-read main.m

    $ ./pkcs12-read -f archive.p12 -p mycoolpassword ( { chain = ( "<SecCertificate 0x7fee312228a0 [0x7fff74588110]>" ); identity = "<SecIdentity 0x7fee31209a40 [0x7fff74588110]>"; keyid = <a09284bc 1202fd0a b0c0d928 65e68fa7 cc8026>; label = "iPhone Developer: Eugene Solodovnykov (F2JSRW3HY6)"; trust = "<SecTrust 0x7fee312330c0 [0x7fff74588110]>"; } ) Reading .p12 file (Cocoa)
  10. Reading .p12 file (Java) public class PKCS12Archive { private final

    Map<PrivateKey, Certificate> content; public Map<PrivateKey, Certificate> getContent() { return content; } PKCS12Archive(byte[] data, char[] pwd) throws IOException { try { content = new HashMap<PrivateKey, Certificate>(); KeyStore keyStore = KeyStore.getInstance("PKCS12"); keyStore.load(new ByteArrayInputStream(data), pwd); Enumeration<String> aliases = keyStore.aliases(); while (aliases.hasMoreElements()) { String alias = aliases.nextElement(); PrivateKey privateKey = PrivateKeyFactory.newInstance(alias, keyStore, pwd); X509Certificate x509Certificate = (X509Certificate)keyStore.getCertificate(alias); Certificate certificate = CertificateFactory.newInstance(x509Certificate); content.put(privateKey, certificate); } } catch (Exception e) { throw new IOException("Can not read from PKCS#12 archive"); } if (content.size() == 0) { throw new IOException("PKCS#12 archive does contain private keys"); } } }
  11. Certificate file • .cer file in X.509 format • Could

    be: • binary (ASN.1 DER) • Base64 encoded DER
  12. • Plist under-the-hood • PKCS#7-signed 0^\¨^F *H÷^M^A^G^B ^\0^\^B^A^A1^K0 ^F^E+^N^C^B^Z^E^@0^Ln^F *H÷^M^A^G^A

    ^L_^D^L[<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/ DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>AppIDName</key> ... https://developer.apple.com/library/mac/#documentation/security/Reference/CryptoMessageRef/Reference/reference.html .mobileprovision
  13. Reading .mobileprovision • In bash (using /usr/bin/security) • In Cocoa

    (using Security.framework) • In Java (using bouncycastle library)
  14. Reading .mobileprovision (bash) • Get clean plist $ /usr/bin/security cms

    -D -i profile.mobileprovision > profile.plist • Get UUID $ /usr/libexec/PlistBuddy -c "Print UUID" profile.plist 41DB29AE-F161-4FCE-8342-47C2CC733FBB
  15. Reading .mobileprovision (Cocoa) mobileprovision-read -- mobileprovision files querying tool. USAGE

    mobileprovision-read -f fileName [-o option] OPTIONS type – prints mobileprovision profile type (debug, ad-hoc, enterprise, appstore) appid – prints application identifier Will print raw provision's plist if option is not specified. You can also use key path as an option. EXAMPLES mobileprovision-read -f test.mobileprovision -o type Prints profile type mobileprovision-read -f test.mobileprovision -o UUID Prints profile UUID mobileprovision-read -f test.mobileprovision -o ProvisionedDevices Prints provisioned devices UDIDs mobileprovision-read -f test.mobileprovision -o Entitlements.get-task-allow Prints 0 if profile doesn't allow debugging 1 otherwise https://github.com/0xc010d/mobileprovision-read
  16. .mobileprovision plist $ mobileprovision-read -f enterprise.mobileprovision ... " <key>DeveloperCertificates</key> "

    <array> " " <data> " " MIIFijCCBHKgAwIBAgIIIQQjJjKc67owDQYJKoZIhvcNAQEFBQAwgZYxCzAJ ... " " agHQH4Ewt6xE6ZIVPjMy2tvs4DSmMq9ugKfE " " </data> " </array> ... " <key>UUID</key> " <string>41DB29AE-F161-4FCE-8342-47C2CC733FBB</string> ...
  17. xcodebuild(1) • Building Example.xcodeproj with specified mobileprovision and corresponding identity

    $ xcodebuild -project Example.xcodeproj PROVISIONING_PROFILE="41DB29AE- F161-4FCE-8342-47C2CC733FBB" CODE_SIGN_IDENTITY="iPhone Developer: Eugene Solodovnykov (F2JSRW3HY6)" • Uses ‘default’ keychain • Mobileprovision should be copied to ~/Library/MobileDevice/Provisioning Profiles/<UUID>.mobileprovision
  18. Build step improvements • Use custom keychain $ /usr/bin/security create-keychain

    -p $KEYCHAIN_PASSWORD $KEYCHAIN_NAME $ /usr/bin/security import "$P12_PATH" -k $KEYCHAIN_NAME -P "$KEY_PASSWORD" -A $ /usr/bin/security unlock-keychain -p $KEYCHAIN_PASSWORD $KEYCHAIN_NAME $ /usr/bin/security set-keychain-settings -u $KEYCHAIN_NAME $ /usr/bin/xcodebuild ... OTHER_CODE_SIGN_FLAGS="--keychain $KEYCHAIN_NAME" $ /usr/bin/security delete-keychain $KEYCHAIN_NAME • Change build directory $ /usr/bin/xcodebuild ... CONFIGURATION_BUILD_DIR="build"
  19. xctool • xcodebuild replacement from Facebook • Human-friendly output (json/XML/your

    own format are also supported) • Better tests support https://github.com/facebook/xctool $ brew install xctool
  20. IPA • Zipped Payload directory with your .app $ mkdir

    Payload $ cp -R "$APP_DIR" Payload $ zip --symlinks --recurse-paths "$IPA_NAME" Payload • /usr/bin/xcrun PackageApplication $ /usr/bin/xcrun -sdk iphoneos PackageApplication -v "$APP_FILENAME" -o "$IPA_PATH" -sign "$CODE_SIGN_IDENTITY" -embed "$MOBILEPROVISION_PATH" • /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/ Developer/usr/bin/PackageApplication • Does packaging and allows resigning • Not so useful in our case as doesn’t allow to use custom keychain
  21. PackageApplication patch • Luckily it’s just a Perl script •

    Diff (adding -keychain option): https://gist.github.com/0xc010d/5769626 • Apply the patch: $ curl https://raw.github.com/gist/5769626 | sudo patch -b /Applications/ Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/usr/bin/ PackageApplication -
  22. CI software • Jenkins (former Hudson) • Hudson (isn’t cool

    anymore) • TeamCity (cool but you have to pay if you have more then 20 build configs) • CruiseControl (bunch of XML configs, IMO uncool) • ...
  23. Xcode plugin for Jenkins • Versioning (agvtool) • Packaging (PackageApplication)

    • Signing (keychain should be created first) • Unit Testing + test report • v1.3.1 (27th March 2012) https://wiki.jenkins-ci.org/display/JENKINS/Xcode+Plugin
  24. iOSBuilder plugin for Jenkins • CocoaPods • Signing • Packaging

    • Easier configuration • Beta state https://github.com/0xc010d/iosbuilder-plugin
  25. iOSBuilder signing • .p12 and .mobileprovision are attached to the

    job • Automatic developer identity choosing • Custom (created by the plugin) keychain
  26. Future plans • Nearest future • Help texts • xcodebuild

    output parsing • OTA on the build page • Distant future • Code resigning • OCLint support • UI testing support Give your ideas!