$30 off During Our Annual Pro Sale. View Details »

Doing CI like a boss

Doing CI like a boss

iOS continuous integration details with the new Jenkins iOSBuilder plugin presenting.
cocoaheadsNL@Hilversum/Xebia

0xc010d

June 19, 2013
Tweet

More Decks by 0xc010d

Other Decks in Programming

Transcript

  1. Doing CI like a boss
    Eugene Solodovnykov
    @0xc010d

    View Slide

  2. I...
    • work for Service2Media
    • mainly do iOS stuff
    • have a wife and two cats
    • like ukrainian borsch

    View Slide

  3. 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

    View Slide

  4. In three words
    It is important

    View Slide

  5. Simple CI flow
    • Checkout
    • Build
    • Publish
    • Notify

    View Slide

  6. Code signing is the most
    interesting part of iOS CI

    View Slide

  7. Code signing requirements
    • Private key
    • Certificate
    • Mobileprovision

    View Slide

  8. 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

    View Slide

  9. 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

    View Slide

  10. .p12 file
    • PKCS#12 archive
    • Designed to store multiple cryptographic objects
    • Could contain more than one private key with
    corresponding certificate

    View Slide

  11. Reading .p12 file
    • In bash (using openssl)
    • In Cocoa (using Security.framework)
    • In Java (using java.security)

    View Slide

  12. 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: //'

    View Slide

  13. Reading .p12 file (Cocoa)
    #import
    #import
    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);
    }
    }

    View Slide

  14. $ clang -framework Foundation -framework Security -fobjc-arc -o pkcs12-read main.m
    $ ./pkcs12-read -f archive.p12 -p mycoolpassword
    (
    {
    chain = (
    ""
    );
    identity = "";
    keyid = ;
    label = "iPhone Developer: Eugene Solodovnykov (F2JSRW3HY6)";
    trust = "";
    }
    )
    Reading .p12 file (Cocoa)

    View Slide

  15. Reading .p12 file (Java)
    public class PKCS12Archive {
    private final Map content;
    public Map getContent() { return content; }
    PKCS12Archive(byte[] data, char[] pwd) throws IOException {
    try {
    content = new HashMap();
    KeyStore keyStore = KeyStore.getInstance("PKCS12");
    keyStore.load(new ByteArrayInputStream(data), pwd);
    Enumeration 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");
    }
    }
    }

    View Slide

  16. Certificate file
    • .cer file in X.509 format
    • Could be:
    • binary (ASN.1 DER)
    • Base64 encoded DER

    View Slide

  17. • 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[



    AppIDName
    ...
    https://developer.apple.com/library/mac/#documentation/security/Reference/CryptoMessageRef/Reference/reference.html
    .mobileprovision

    View Slide

  18. • Get clean plist
    • Deal with it
    Reading .mobileprovision

    View Slide

  19. Reading .mobileprovision
    • In bash (using /usr/bin/security)
    • In Cocoa (using Security.framework)
    • In Java (using bouncycastle library)

    View Slide

  20. 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

    View Slide

  21. 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

    View Slide

  22. .mobileprovision plist
    $ mobileprovision-read -f enterprise.mobileprovision
    ...
    " DeveloperCertificates
    "
    " "
    " " MIIFijCCBHKgAwIBAgIIIQQjJjKc67owDQYJKoZIhvcNAQEFBQAwgZYxCzAJ
    ...
    " " agHQH4Ewt6xE6ZIVPjMy2tvs4DSmMq9ugKfE
    " "
    "
    ...
    " UUID
    " 41DB29AE-F161-4FCE-8342-47C2CC733FBB
    ...

    View Slide

  23. .mobileprovision plist
    • Certificate is embedded there!
    • UUID is used to specify the profile in xcodebuild

    View Slide

  24. 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/.mobileprovision

    View Slide

  25. 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"

    View Slide

  26. 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

    View Slide

  27. 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

    View Slide

  28. 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 -

    View Slide

  29. 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)
    • ...

    View Slide

  30. 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

    View Slide

  31. View Slide

  32. iOSBuilder plugin for Jenkins
    • CocoaPods
    • Signing
    • Packaging
    • Easier configuration
    • Beta state
    https://github.com/0xc010d/iosbuilder-plugin

    View Slide

  33. View Slide

  34. iOSBuilder signing
    • .p12 and .mobileprovision are attached to the job
    • Automatic developer identity choosing
    • Custom (created by the plugin) keychain

    View Slide

  35. Demo
    Wish me luck

    View Slide

  36. 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!

    View Slide

  37. Thank you!
    Questions?
    @0xc010d
    [email protected]

    View Slide