-
Notifications
You must be signed in to change notification settings - Fork 1
/
resign.sh
executable file
·861 lines (759 loc) · 40.4 KB
/
resign.sh
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
#!/bin/bash
# shellcheck disable=SC2155
# Copyright (c) 2011 Float Mobile Learning
# http://www.floatlearning.com/
# Extension Copyright (c) 2013 Weptun Gmbh
# http://www.weptun.de
#
# Extended by Ronan O Ciosoig January 2012
#
# Extended by Patrick Blitz, April 2013
#
# Extended by John Turnipseed and Matthew Nespor, November 2014
# http://nanonation.net/
#
# Extended by Nicolas Bachschmidt, October 2015
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the "Software"),
# to deal in the Software without restriction, including without limitation
# the rights to use, copy, modify, merge, publish, distribute, sublicense,
# and/or sell copies of the Software, and to permit persons to whom the
# Software is furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#
# Please let us know about any improvements you make to this script!
# ./floatsign source "iPhone Distribution: Name" -p "path/to/profile" [-d "display name"] [-e entitlements] [-k keychain] [-b "BundleIdentifier"] outputIpa
#
#
# Modifed 26th January 2012
#
# new features January 2012:
# 1. change the app display name
#
# new features April 2013
# 1. specify the target bundleId on the command line
# 2. correctly handles entitlements for keychain-enabled resigning
#
# new features November 2014
# 1. now re-signs embedded iOS frameworks, if present, prior to re-signing the application itself
# 2. extracts the team-identifier from provisioning profile and uses it to update previous entitlements
# 3. fixed bug in packaging if -e flag is used
# 4. renamed 'temp' directory and made it a variable so it can be easily modified
# 5. various code formatting and logging adjustments
#
# new features October 2015
# 1. now re-signs nested applications and app extensions, if present, prior to re-signing the application itself
# 2. enables the -p option to be used more than once
# 3. ensures the provisioning profile's bundle-identifier matches the app's bundle identifier
# 4. extracts the entitlements from the provisioning profile
# 5. copy the entitlements as archived-expanded-entitlements.xcent inside the app bundle (because Xcode does too)
#
# new features November 2018
# 1. only create the archived-expanded-entitlements.xcent file if the version of Xcode < 9.3 as Xcode 10 does not create it.
#
# Logging functions
log() {
# Make sure it returns 0 code even when verose mode is off (test 1)
# To use like [[ condition ]] && log "x" && something
if [[ -n "$VERBOSE" ]]; then echo -e "$@"; else test 1; fi
}
error() {
echo "$@" >&2
exit 1
}
warning() {
echo "$@" >&2
}
function checkStatus {
# shellcheck disable=SC2181
if [ $? -ne 0 ]; then
error "Encountered an error, aborting!"
fi
}
usage() {
echo -e "Usage: $(basename "$0") source identity -p|--provisioning provisioning" >&2
echo -e "\t\t[-e|--entitlements entitlements]" >&2
echo -e "\t\t[-k|--keychain keychain]" >&2
echo -e "\t\t[-d|--display-name displayName]" >&2
echo -e "\t\t[-n|--version-number version]" >&2
echo -e "\t\t[--short-version shortVersion]" >&2
echo -e "\t\t[--bundle-version bundleVersion]" >&2
echo -e "\t\t[-b|--bundle-id bundleId]" >&2
echo -e "\t\t[--use-app-entitlements]" >&2
echo -e "\t\toutputIpa" >&2
echo "Usage: $(basename "$0") -h|--help" >&2
echo "Options:" >&2
echo -e "\t-p, --provisioning provisioning\t\tProvisioning profile option, may be provided multiple times." >&2
echo -e "\t\t\t\t\t\tYou can specify provisioning profile file name." >&2
echo -e "\t\t\t\t\t\t\t-p xxx.mobileprovision" >&2
echo "" >&2
echo -e "\t\t\t\t\t\tAlternatively you may provide multiple provisioning profiles if the application contains" >&2
echo -e "\t\t\t\t\t\tnested applications or app extensions, which need their own provisioning" >&2
echo -e "\t\t\t\t\t\tprofile. You can do so by providing -p option multiple times specifying" >&2
echo -e "\t\t\t\t\t\told bundle identifier and new provisioning profile for that bundle id joined with '='." >&2
echo -e "\t\t\t\t\t\t\t-p com.main-app=main-app.mobileprovision" >&2
echo -e "\t\t\t\t\t\t\t-p com.nested-app=nested-app.mobileprovision" >&2
echo -e "\t\t\t\t\t\t\t-p com.nested-extension=nested-extension.mobileprovision" >&2
echo "" >&2
echo -e "\t-e, --entitlements entitlements\t\tSpecify entitlements file path for code signing." >&2
echo -e "\t-k, --keychain keychain\t\t\tSpecify keychain for code signing." >&2
echo -e "\t-d, --display-name displayName\t\tSpecify new display name." >&2
echo -e "\t\t\t\t\t\t\tWarning: will apply for all nested apps and extensions." >&2
echo -e "\t-n, --version-number version\t\tSpecify new version number." >&2
echo -e "\t\t\t\t\t\t\tWill set CFBundleShortVersionString and CFBundleVersion values in Info.plist." >&2
echo -e "\t\t\t\t\t\t\tWill apply for all nested apps and extensions." >&2
echo -e "\t --short-version shortVersion\tSpecify new short version string (CFBundleShortVersionString)." >&2
echo -e "\t\t\t\t\t\t\tWill apply for all nested apps and extensions." >&2
echo -e "\t\t\t\t\t\t\tCan't use together with '-n, --version-number' option." >&2
echo -e "\t --bundle-version bundleVersion\tSpecify new bundle version (CFBundleVersion) number." >&2
echo -e "\t\t\t\t\t\t\tWill apply for all nested apps and extensions." >&2
echo -e "\t\t\t\t\t\t\tCan't use together with '-n, --version-number' option." >&2
echo -e "\t-b, --bundle-id bundleId\t\tSpecify new bundle identifier (CFBundleIdentifier)." >&2
echo -e "\t\t\t\t\t\t\tWarning: will NOT apply for nested apps and extensions." >&2
echo -e "\t --use-app-entitlements\t\tExtract app bundle codesigning entitlements and combine with entitlements from new provisionin profile." >&2
echo -e "\t\t\t\t\t\t\tCan't use together with '-e, --entitlements' option." >&2
echo -e "\t--keychain-path path\t\t\tSpecify the path to a keychain that /usr/bin/codesign should use." >&2
echo -e "\t-v, --verbose\t\t\t\tVerbose output." >&2
echo -e "\t-h, --help\t\t\t\tDisplay help message." >&2
exit 2
}
if [ $# -lt 3 ]; then
usage
fi
ORIGINAL_FILE="$1"
CERTIFICATE="$2"
ENTITLEMENTS=
BUNDLE_IDENTIFIER=""
DISPLAY_NAME=""
KEYCHAIN=""
VERSION_NUMBER=""
SHORT_VERSION=
BUNDLE_VERSION=
KEYCHAIN_PATH=
RAW_PROVISIONS=()
PROVISIONS_BY_ID=()
DEFAULT_PROVISION=""
TEMP_DIR="_floatsignTemp"
USE_APP_ENTITLEMENTS=""
XCODE_VERSION="$(xcodebuild -version | grep "Xcode" | /usr/bin/cut -f 2 -d ' ')"
# List of plist keys used for reference to and from nested apps and extensions
NESTED_APP_REFERENCE_KEYS=(":WKCompanionAppBundleIdentifier" ":NSExtension:NSExtensionAttributes:WKAppBundleIdentifier")
# options start index
shift 2
# Parse args
while [ "$1" != "" ]; do
case $1 in
-p | --provisioning )
shift
RAW_PROVISIONS+=("$1")
;;
-e | --entitlements )
shift
ENTITLEMENTS="$1"
;;
-d | --display-name )
shift
DISPLAY_NAME="$1"
;;
-b | --bundle-id )
shift
BUNDLE_IDENTIFIER="$1"
;;
-k | --keychain )
shift
KEYCHAIN="$1"
;;
-n | --version-number )
shift
VERSION_NUMBER="$1"
;;
--short-version )
shift
SHORT_VERSION="$1"
;;
--bundle-version )
shift
BUNDLE_VERSION="$1"
;;
--use-app-entitlements )
USE_APP_ENTITLEMENTS="YES"
;;
--keychain-path )
shift
KEYCHAIN_PATH="$1"
;;
-v | --verbose )
VERBOSE="--verbose"
;;
-h | --help )
usage
;;
* )
[[ -n "$NEW_FILE" ]] && error "Multiple output file names specified!"
[[ -z "$NEW_FILE" ]] && NEW_FILE="$1"
;;
esac
# Next arg
shift
done
KEYCHAIN_FLAG=
if [ -n "$KEYCHAIN_PATH" ]; then
KEYCHAIN_FLAG="--keychain $KEYCHAIN_PATH"
fi
# Log the options
for provision in "${RAW_PROVISIONS[@]}"; do
if [[ "$provision" =~ .+=.+ ]]; then
log "Specified provisioning profile: '${provision#*=}' for bundle identifier: '${provision%%=*}'"
else
log "Specified provisioning profile: '$provision'"
fi
done
log "Original file: '$ORIGINAL_FILE'"
log "Certificate: '$CERTIFICATE'"
[[ -n "${DISPLAY_NAME}" ]] && log "Specified display name: '$DISPLAY_NAME'"
[[ -n "${ENTITLEMENTS}" ]] && log "Specified signing entitlements: '$ENTITLEMENTS'"
[[ -n "${BUNDLE_IDENTIFIER}" ]] && log "Specified bundle identifier: '$BUNDLE_IDENTIFIER'"
[[ -n "${KEYCHAIN}" ]] && log "Specified keychain to use: '$KEYCHAIN'"
[[ -n "${VERSION_NUMBER}" ]] && log "Specified version number to use: '$VERSION_NUMBER'"
[[ -n "${SHORT_VERSION}" ]] && log "Specified short version to use: '$SHORT_VERSION'"
[[ -n "${BUNDLE_VERSION}" ]] && log "Specified bundle version to use: '$BUNDLE_VERSION'"
[[ -n "${KEYCHAIN_FLAG}" ]] && log "Specified keychain to use: '$KEYCHAIN_PATH'"
[[ -n "${NEW_FILE}" ]] && log "Output file name: '$NEW_FILE'"
[[ -n "${USE_APP_ENTITLEMENTS}" ]] && log "Extract app entitlements: YES"
# Check that version number option is not clashing with short or bundle version options
[[ -n "$VERSION_NUMBER" && (-n "$SHORT_VERSION" || -n "$BUNDLE_VERSION") ]] && error "versionNumber option cannot be used in combination with shortVersion or bundleVersion options"
# Check that --use-app-entitlements and -e, --entitlements are not used at the same time
[[ -n "${USE_APP_ENTITLEMENTS}" && -n ${ENTITLEMENTS} ]] && error "--use-app-entitlements option cannot be used in combination with -e, --entitlements option."
# Check output file name
if [ -z "$NEW_FILE" ]; then
error "Output file name required"
fi
if [[ "${#RAW_PROVISIONS[*]}" == "0" ]]; then
error "-p 'xxxx.mobileprovision' argument is required"
fi
# Check for and remove the temporary directory if it already exists
if [ -d "$TEMP_DIR" ]; then
log "Removing previous temporary directory: '$TEMP_DIR'"
rm -Rf "$TEMP_DIR"
fi
filename=$(basename "$ORIGINAL_FILE")
extension="${filename##*.}"
filename="${filename%.*}"
# Check if the supplied file is an ipa or an app file
if [ "${extension}" = "ipa" ]; then
# Unzip the old ipa quietly
unzip -q "$ORIGINAL_FILE" -d $TEMP_DIR
checkStatus
elif [ "${extension}" = "app" ]; then
# Copy the app file into an ipa-like structure
mkdir -p "$TEMP_DIR/Payload"
cp -Rf "${ORIGINAL_FILE}" "$TEMP_DIR/Payload/${filename}.app"
checkStatus
else
error "Error: Only can resign .app files and .ipa files."
fi
# check the keychain
if [ "${KEYCHAIN}" != "" ]; then
security list-keychains -s "$KEYCHAIN"
security unlock "$KEYCHAIN"
security default-keychain -s "$KEYCHAIN"
fi
# Set the app name
# In Payload directory may be another file except .app file, such as StoreKit folder.
# Search the first .app file within the Payload directory
# shellcheck disable=SC2010
APP_NAME=$(ls "$TEMP_DIR/Payload/" | grep ".app$" | head -1)
# Make sure that PATH includes the location of the PlistBuddy helper tool as its location is not standard
export PATH=$PATH:/usr/libexec
# Test whether two bundle identifiers match
# The first one may contain the wildcard character '*', in which case pattern matching will be used unless the third parameter is "STRICT"
function does_bundle_id_match {
# shellcheck disable=SC2049
if [[ "$1" == "$2" ]]; then
return 0
elif [[ "$3" != STRICT && "$1" =~ \* ]]; then
local PATTERN0="${1//\./\\.}" # com.example.* -> com\.example\.*
local PATTERN1="${PATTERN0//\*/.*}" # com\.example\.* -> com\.example\..*
if [[ "$2" =~ ^$PATTERN1$ ]]; then
return 0
fi
fi
return 1
}
# Find the provisioning profile for a given bundle identifier
function provision_for_bundle_id {
for ARG in "${PROVISIONS_BY_ID[@]}"; do
if does_bundle_id_match "${ARG%%=*}" "$1" "$2"; then
echo "${ARG#*=}"
break
fi
done
}
# Find the bundle identifier contained inside a provisioning profile
function bundle_id_for_provison {
local FULL_BUNDLE_ID=$(PlistBuddy -c 'Print :Entitlements:application-identifier' /dev/stdin <<< "$(security cms -D -i "$1")")
checkStatus
echo "${FULL_BUNDLE_ID#*.}"
}
# Add given provisioning profile and bundle identifier to the search list
function add_provision_for_bundle_id {
local PROVISION="$1"
local BUNDLE_ID="$2"
local CURRENT_PROVISION=$(provision_for_bundle_id "$BUNDLE_ID" STRICT)
if [[ "$CURRENT_PROVISION" != "" && "$CURRENT_PROVISION" != "$PROVISION" ]]; then
error "Conflicting provisioning profiles '$PROVISION' and '$CURRENT_PROVISION' for bundle identifier '$BUNDLE_ID'."
fi
PROVISIONS_BY_ID+=("$BUNDLE_ID=$PROVISION")
}
# Add given provisioning profile to the search list
function add_provision {
local PROVISION="$1"
if [[ "$1" =~ .+=.+ ]]; then
PROVISION="${1#*=}"
add_provision_for_bundle_id "$PROVISION" "${1%%=*}"
elif [[ "$DEFAULT_PROVISION" == "" ]]; then
DEFAULT_PROVISION="$PROVISION"
fi
if [[ ! -e "$PROVISION" ]]; then
error "Provisioning profile '$PROVISION' file does not exist"
fi
local BUNDLE_ID=$(bundle_id_for_provison "$PROVISION")
add_provision_for_bundle_id "$PROVISION" "$BUNDLE_ID"
}
# Load bundle identifiers from provisioning profiles
for ARG in "${RAW_PROVISIONS[@]}"; do
add_provision "$ARG"
done
# Resign the given application
function resign {
local APP_PATH="$1"
local NESTED="$2"
local BUNDLE_IDENTIFIER="$BUNDLE_IDENTIFIER"
local NEW_PROVISION="$NEW_PROVISION"
local APP_IDENTIFIER_PREFIX=""
local TEAM_IDENTIFIER=""
if [[ "$NESTED" == NESTED ]]; then
# Ignore bundle identifier for nested applications
BUNDLE_IDENTIFIER=""
fi
# Make sure that the Info.plist file is where we expect it
if [ ! -e "$APP_PATH/Info.plist" ]; then
error "Expected file does not exist: '$APP_PATH/Info.plist'"
fi
# Make a copy of old Info.plist, it will come handy later to extract some old values
cp -f "$APP_PATH/Info.plist" "$TEMP_DIR/oldInfo.plist"
# Read in current values from the app
local CURRENT_NAME=$(PlistBuddy -c "Print :CFBundleDisplayName" "$APP_PATH/Info.plist")
local CURRENT_BUNDLE_IDENTIFIER=$(PlistBuddy -c "Print :CFBundleIdentifier" "$APP_PATH/Info.plist")
local NEW_PROVISION=$(provision_for_bundle_id "${BUNDLE_IDENTIFIER:-$CURRENT_BUNDLE_IDENTIFIER}")
if [[ "$NEW_PROVISION" == "" && "$NESTED" != NESTED ]]; then
NEW_PROVISION="$DEFAULT_PROVISION"
fi
if [[ "$NEW_PROVISION" == "" ]]; then
if [[ "$NESTED" == NESTED ]]; then
warning "No provisioning profile for nested application: '$APP_PATH' with bundle identifier '${BUNDLE_IDENTIFIER:-$CURRENT_BUNDLE_IDENTIFIER}'"
else
warning "No provisioning profile for application: '$APP_PATH' with bundle identifier '${BUNDLE_IDENTIFIER:-$CURRENT_BUNDLE_IDENTIFIER}'"
fi
error "Use the -p option (example: -p com.example.app=xxxx.mobileprovision)"
fi
local PROVISION_BUNDLE_IDENTIFIER=$(bundle_id_for_provison "$NEW_PROVISION")
# Use provisioning profile's bundle identifier
if [ "$BUNDLE_IDENTIFIER" == "" ]; then
# shellcheck disable=SC2049
if [[ "$PROVISION_BUNDLE_IDENTIFIER" =~ \* ]]; then
log "Bundle Identifier contains a *, using the current bundle identifier"
BUNDLE_IDENTIFIER="$CURRENT_BUNDLE_IDENTIFIER"
else
BUNDLE_IDENTIFIER="$PROVISION_BUNDLE_IDENTIFIER"
fi
fi
if ! does_bundle_id_match "$PROVISION_BUNDLE_IDENTIFIER" "$BUNDLE_IDENTIFIER"; then
error "Bundle Identifier '$PROVISION_BUNDLE_IDENTIFIER' in provisioning profile '$NEW_PROVISION' does not match the Bundle Identifier '$BUNDLE_IDENTIFIER' for application '$APP_PATH'."
fi
log "Current bundle identifier is: '$CURRENT_BUNDLE_IDENTIFIER'"
log "New bundle identifier will be: '$BUNDLE_IDENTIFIER'"
# Update the CFBundleDisplayName property in the Info.plist if a new name has been provided
if [ "${DISPLAY_NAME}" != "" ]; then
if [ "${DISPLAY_NAME}" != "${CURRENT_NAME}" ]; then
log "Changing display name from '$CURRENT_NAME' to '$DISPLAY_NAME'"
PlistBuddy -c "Set :CFBundleDisplayName $DISPLAY_NAME" "$APP_PATH/Info.plist"
fi
fi
# Replace the embedded mobile provisioning profile
log "Validating the new provisioning profile: $NEW_PROVISION"
security cms -D -i "$NEW_PROVISION" > "$TEMP_DIR/profile.plist"
checkStatus
APP_IDENTIFIER_PREFIX=$(PlistBuddy -c "Print :Entitlements:application-identifier" "$TEMP_DIR/profile.plist" | grep -E '^[A-Z0-9]*' -o | tr -d '\n')
if [ "$APP_IDENTIFIER_PREFIX" == "" ];
then
APP_IDENTIFIER_PREFIX=$(PlistBuddy -c "Print :ApplicationIdentifierPrefix:0" "$TEMP_DIR/profile.plist")
if [ "$APP_IDENTIFIER_PREFIX" == "" ]; then
error "Failed to extract any app identifier prefix from '$NEW_PROVISION'"
else
warning "WARNING: extracted an app identifier prefix '$APP_IDENTIFIER_PREFIX' from '$NEW_PROVISION', but it was not found in the profile's entitlements"
fi
else
log "Profile app identifier prefix is '$APP_IDENTIFIER_PREFIX'"
fi
# Set new app identifer prefix if such entry exists in plist file
PlistBuddy -c "Set :AppIdentifierPrefix $APP_IDENTIFIER_PREFIX." "$APP_PATH/Info.plist" 2>/dev/null
TEAM_IDENTIFIER=$(PlistBuddy -c "Print :Entitlements:com.apple.developer.team-identifier" "$TEMP_DIR/profile.plist" | tr -d '\n')
if [ "$TEAM_IDENTIFIER" == "" ]; then
TEAM_IDENTIFIER=$(PlistBuddy -c "Print :TeamIdentifier:0" "$TEMP_DIR/profile.plist")
if [ "$TEAM_IDENTIFIER" == "" ]; then
warning "Failed to extract team identifier from '$NEW_PROVISION', resigned ipa may fail on iOS 8 and higher"
else
warning "WARNING: extracted a team identifier '$TEAM_IDENTIFIER' from '$NEW_PROVISION', but it was not found in the profile's entitlements, resigned ipa may fail on iOS 8 and higher"
fi
else
log "Profile team identifier is '$TEAM_IDENTIFIER'"
fi
# Make a copy of old embedded provisioning profile for further use
cp -f "$APP_PATH/embedded.mobileprovision" "$TEMP_DIR/old-embedded.mobileprovision"
# Replace embedded provisioning profile with new file
cp -f "$NEW_PROVISION" "$APP_PATH/embedded.mobileprovision"
#if the current bundle identifier is different from the new one in the provisioning profile, then change it.
if [ "$CURRENT_BUNDLE_IDENTIFIER" != "$BUNDLE_IDENTIFIER" ]; then
log "Updating the bundle identifier from '$CURRENT_BUNDLE_IDENTIFIER' to '$BUNDLE_IDENTIFIER'"
PlistBuddy -c "Set :CFBundleIdentifier $BUNDLE_IDENTIFIER" "$APP_PATH/Info.plist"
checkStatus
fi
# Update the version number properties in the Info.plist if a version number has been provided
if [ "$VERSION_NUMBER" != "" ]; then
CURRENT_VERSION_NUMBER=$(PlistBuddy -c "Print :CFBundleVersion" "$APP_PATH/Info.plist")
if [ "$VERSION_NUMBER" != "$CURRENT_VERSION_NUMBER" ]; then
log "Updating the version from '$CURRENT_VERSION_NUMBER' to '$VERSION_NUMBER'"
PlistBuddy -c "Set :CFBundleVersion $VERSION_NUMBER" "$APP_PATH/Info.plist"
PlistBuddy -c "Set :CFBundleShortVersionString $VERSION_NUMBER" "$APP_PATH/Info.plist"
fi
fi
# Update short version string in the Info.plist if provided
if [[ -n "$SHORT_VERSION" ]]; then
CURRENT_VALUE="$(PlistBuddy -c "Print :CFBundleShortVersionString" "$APP_PATH/Info.plist")"
# Even if the old value is same - just update, less code, less debugging
log "Updating the short version string (CFBundleShortVersionString) from '$CURRENT_VALUE' to '$SHORT_VERSION'"
PlistBuddy -c "Set :CFBundleShortVersionString $SHORT_VERSION" "$APP_PATH/Info.plist"
fi
# Update bundle version in the Info.plist if provided
if [[ -n "$BUNDLE_VERSION" ]]; then
CURRENT_VALUE="$(PlistBuddy -c "Print :CFBundleVersion" "$APP_PATH/Info.plist")"
# Even if the old value is same - just update, less code, less debugging
log "Updating the bundle version (CFBundleVersion) from '$CURRENT_VALUE' to '$BUNDLE_VERSION'"
PlistBuddy -c "Set :CFBundleVersion $BUNDLE_VERSION" "$APP_PATH/Info.plist"
fi
# Check for and resign any embedded frameworks (new feature for iOS 8 and above apps)
FRAMEWORKS_DIR="$APP_PATH/Frameworks"
if [ -d "$FRAMEWORKS_DIR" ]; then
if [ "$TEAM_IDENTIFIER" == "" ]; then
error "ERROR: embedded frameworks detected, re-signing iOS 8 (or higher) applications wihout a team identifier in the certificate/profile does not work"
fi
log "Resigning embedded frameworks using certificate: '$CERTIFICATE'"
for framework in "$FRAMEWORKS_DIR"/*
do
if [[ "$framework" == *.framework || "$framework" == *.dylib ]]; then
log "Resigning '$framework'"
# Must not qote KEYCHAIN_FLAG because it needs to be unwrapped and passed to codesign with spaces
# shellcheck disable=SC2086
/usr/bin/codesign ${VERBOSE} ${KEYCHAIN_FLAG} -f -s "$CERTIFICATE" "$framework"
checkStatus
else
log "Ignoring non-framework: $framework"
fi
done
fi
# Check for and update bundle identifiers for extensions and associated nested apps
log "Fixing nested app and extension references"
for key in "${NESTED_APP_REFERENCE_KEYS[@]}"; do
# Check if Info.plist has a reference to another app or extension
REF_BUNDLE_ID=$(PlistBuddy -c "Print ${key}" "$APP_PATH/Info.plist" 2>/dev/null)
if [ -n "$REF_BUNDLE_ID" ]; then
# Found a reference bundle id, now get the corresponding provisioning profile for this bundle id
REF_PROVISION=$(provision_for_bundle_id "$REF_BUNDLE_ID")
# Map to the new bundle id
NEW_REF_BUNDLE_ID=$(bundle_id_for_provison "$REF_PROVISION")
# Change if not the same and if doesn't contain wildcard
# shellcheck disable=SC2049
if [[ "$REF_BUNDLE_ID" != "$NEW_REF_BUNDLE_ID" ]] && ! [[ "$NEW_REF_BUNDLE_ID" =~ \* ]]; then
log "Updating nested app or extension reference for ${key} key from ${REF_BUNDLE_ID} to ${NEW_REF_BUNDLE_ID}"
PlistBuddy -c "Set ${key} $NEW_REF_BUNDLE_ID" "$APP_PATH/Info.plist"
fi
fi
done
if [ "$ENTITLEMENTS" != "" ]; then
if [ -n "$APP_IDENTIFIER_PREFIX" ]; then
# sanity check the 'application-identifier' is present in the provided entitlements and matches the provisioning profile value
ENTITLEMENTS_APP_ID_PREFIX=$(PlistBuddy -c "Print :application-identifier" "$ENTITLEMENTS" | grep -E '^[A-Z0-9]*' -o | tr -d '\n')
if [ "$ENTITLEMENTS_APP_ID_PREFIX" == "" ]; then
error "Provided entitlements file is missing a value for the required 'application-identifier' key"
elif [ "$ENTITLEMENTS_APP_ID_PREFIX" != "$APP_IDENTIFIER_PREFIX" ]; then
error "Provided entitlements file's app identifier prefix value '$ENTITLEMENTS_APP_ID_PREFIX' does not match the provided provisioning profile's value '$APP_IDENTIFIER_PREFIX'"
fi
fi
if [ -n "$TEAM_IDENTIFIER" ]; then
# sanity check the 'com.apple.developer.team-identifier' is present in the provided entitlements and matches the provisioning profile value
ENTITLEMENTS_TEAM_IDENTIFIER=$(PlistBuddy -c "Print :com.apple.developer.team-identifier" "$ENTITLEMENTS" | tr -d '\n')
if [ "$ENTITLEMENTS_TEAM_IDENTIFIER" == "" ]; then
error "Provided entitlements file is missing a value for the required 'com.apple.developer.team-identifier' key"
elif [ "$ENTITLEMENTS_TEAM_IDENTIFIER" != "$TEAM_IDENTIFIER" ]; then
error "Provided entitlements file's 'com.apple.developer.team-identifier' '$ENTITLEMENTS_TEAM_IDENTIFIER' does not match the provided provisioning profile's value '$TEAM_IDENTIFIER'"
fi
fi
log "Resigning application using certificate: '$CERTIFICATE'"
log "and entitlements: $ENTITLEMENTS"
cp -f "$ENTITLEMENTS" "$APP_PATH/archived-expanded-entitlements.xcent"
/usr/bin/codesign ${VERBOSE} -f -s "$CERTIFICATE" --entitlements "$ENTITLEMENTS" "$APP_PATH"
checkStatus
elif [[ -n "${USE_APP_ENTITLEMENTS}" ]]; then
# Extract entitlements from provisioning profile and from the app binary
# then combine them together
log "Extracting entitlements from provisioning profile"
PROFILE_ENTITLEMENTS="$TEMP_DIR/profileEntitlements"
PlistBuddy -x -c "Print Entitlements" "$TEMP_DIR/profile.plist" > "$PROFILE_ENTITLEMENTS"
checkStatus
log "Extracting entitlements from the app"
APP_ENTITLEMENTS="$TEMP_DIR/appEntitlements"
/usr/bin/codesign -d --entitlements :"$APP_ENTITLEMENTS" "$APP_PATH"
checkStatus
log "\nApp entitlements for ${APP_PATH}:"
log "$(cat "$APP_ENTITLEMENTS")"
log "Patching profile entitlements with values from app entitlements"
PATCHED_ENTITLEMENTS="$TEMP_DIR/patchedEntitlements"
# Start with using what comes in provisioning profile entitlements before patching
cp -f "$PROFILE_ENTITLEMENTS" "$PATCHED_ENTITLEMENTS"
log "Removing blacklisted keys from patched profile"
# See https://github.com/facebook/buck/issues/798 and https://github.com/facebook/buck/pull/802/files
# Update in https://github.com/facebook/buck/commit/99c0fbc3ab5ecf04d186913374f660683deccdef
# Update in https://github.com/facebook/buck/commit/36db188da9f6acbb9df419dc1904315ab00c4e19
# Buck changes referenced above are not self-explanatory and do not seem exhaustive or up-to-date
# Comments below explain the rules applied to each key in order to make realignment with future Xcode export logic easier
BLACKLISTED_KEYS=(\
# PP list identifiers inconsistent with app-defined ones and this key does not seem to appear in IPA entitlements, so ignore it
"com.apple.developer.icloud-container-development-container-identifiers" \
# This key has an invalid generic value in PP (actual value is set by Xcode during export), see dedicated processing a few blocks below
"com.apple.developer.icloud-container-environment" \
# PP list identifiers inconsistent with app-defined ones, must use App entitlements value
"com.apple.developer.icloud-container-identifiers" \
# PP enable all available services and not app-defined ones, must use App entitlements value
"com.apple.developer.icloud-services" \
# Was already blacklisted in previous version, but has someone ever seen this key in a PP?
"com.apple.developer.restricted-resource-mode" \
# If actually used by the App, this value will be set in its entitlements
"com.apple.developer.nfc.readersession.formats" \
# PP list a single TeamID.* identifier and not app-defined ones, must use App entitlements value
"com.apple.developer.pass-type-identifiers" \
# If actually used by the App, this value will be set in its entitlements
"com.apple.developer.siri" \
# PP list identifiers inconsistent with app-defined ones, must use App entitlements value
"com.apple.developer.ubiquity-container-identifiers" \
# PP define a generic TeamID.* identifier and not the app-defined one, must use App entitlements value
"com.apple.developer.ubiquity-kvstore-identifier" \
# If actually used by the App, this value will be set in its entitlements
"inter-app-audio" \
# PP define a generic TeamID.* identifier and not the app-defined one, must use App entitlements value
"keychain-access-groups" \
# If actually used by the App, this value will be set in its entitlements
"com.apple.developer.homekit" \
# If actually used by the App, this value will be set in its entitlements
"com.apple.developer.healthkit" \
# PP list identifiers inconsistent with app-defined ones, must use App entitlements value
"com.apple.developer.in-app-payments" \
# If actually used by the App, this value will be set in its entitlements
"com.apple.developer.networking.vpn.api" \
# If actually used by the App, this value will be set in its entitlements
"com.apple.developer.networking.HotspotConfiguration" \
# PP list all available extensions and not app-defined ones, must use App entitlements value
"com.apple.developer.networking.networkextension" \
# If actually used by the App, this value will be set in its entitlements
"com.apple.developer.networking.multipath" \
# PP enable all domains via a non-AppStore-compliant '*' value, must use App entitlements value
"com.apple.developer.associated-domains" \
# If actually used by the App, this value will be set in its entitlements
"com.apple.developer.default-data-protection" \
# PP seem to list the same groups as the App, but use App entitlements value to be sure
"com.apple.security.application-groups" \
# Was already blacklisted in previous version, seems to be an artifact from an old Xcode release
"com.apple.developer.maps" \
# If actually used by the App, this value will be set in its entitlements
"com.apple.external-accessory.wireless-configuration"
)
# Blacklisted keys must not be included into new profile, so remove them from patched profile
for KEY in "${BLACKLISTED_KEYS[@]}"; do
log "Removing blacklisted key: $KEY"
PlistBuddy -c "Delete $KEY" "$PATCHED_ENTITLEMENTS" 2>/dev/null
done
# Get the old and new app identifier (prefix)
APP_ID_KEY="application-identifier"
# Extract just the identifier from the value
# Use the fact that we are after some identifer, which is always at the start of the string
OLD_APP_ID=$(PlistBuddy -c "Print $APP_ID_KEY" "$APP_ENTITLEMENTS" | grep -E '^[A-Z0-9]*' -o | tr -d '\n')
NEW_APP_ID=$(PlistBuddy -c "Print $APP_ID_KEY" "$PROFILE_ENTITLEMENTS" | grep -E '^[A-Z0-9]*' -o | tr -d '\n')
# Get the old and the new team ID
# Old team ID is not part of app entitlements, have to get it from old embedded provisioning profile
security cms -D -i "$TEMP_DIR/old-embedded.mobileprovision" > "$TEMP_DIR/old-embedded-profile.plist"
OLD_TEAM_ID=$(PlistBuddy -c "Print :TeamIdentifier:0" "$TEMP_DIR/old-embedded-profile.plist")
# New team ID is part of profile entitlements
NEW_TEAM_ID=$(PlistBuddy -c "Print com.apple.developer.team-identifier" "$PROFILE_ENTITLEMENTS" | grep -E '^[A-Z0-9]*' -o | tr -d '\n')
# List of rules for transferring entitlements from app to profile plist
# The format for each enty is "KEY[|ID_TYPE]"
# Where KEY is the plist key, e.g. "keychain-access-groups"
# and ID_TYPE is optional part separated by '|' that specifies what value to patch:
# TEAM_ID - patch the TeamIdentifierPrefix
# APP_ID - patch the AppIdentifierPrefix
# Patching means replacing old value from app entitlements with new value from provisioning profile
# For example, for KEY=keychain-access-groups the ID_TYPE=APP_ID
# Which means that old app ID prefix in keychain-access-groups will be replaced with new app ID prefix
# There can be only one ID_TYPE specified
# If entitlements use more than one ID type for single entitlement, then this way of resigning will not work
# instead an entitlements file must be provided explicitly
ENTITLEMENTS_TRANSFER_RULES=(\
"com.apple.developer.associated-domains" \
"com.apple.developer.default-data-protection" \
"com.apple.developer.healthkit" \
"com.apple.developer.homekit" \
"com.apple.developer.icloud-container-environment" \
"com.apple.developer.icloud-container-identifiers" \
"com.apple.developer.icloud-services" \
"com.apple.developer.in-app-payments" \
"com.apple.developer.networking.HotspotConfiguration" \
"com.apple.developer.networking.multipath" \
"com.apple.developer.networking.networkextension" \
"com.apple.developer.networking.vpn.api" \
"com.apple.developer.nfc.readersession.formats" \
"com.apple.developer.pass-type-identifiers|TEAM_ID" \
"com.apple.developer.siri" \
"com.apple.developer.ubiquity-container-identifiers" \
"com.apple.developer.ubiquity-kvstore-identifier|TEAM_ID" \
"com.apple.external-accessory.wireless-configuration" \
"com.apple.security.application-groups" \
"inter-app-audio" \
"keychain-access-groups|APP_ID")
# Loop over all the entitlement keys that need to be transferred from app entitlements
for RULE in "${ENTITLEMENTS_TRANSFER_RULES[@]}"; do
KEY=$(echo "$RULE" | cut -d'|' -f1)
ID_TYPE=$(echo "$RULE" | cut -d'|' -f2)
# Get the entry from app's entitlements
# Read it with PlistBuddy as XML, then strip the header and <plist></plist> part
ENTITLEMENTS_VALUE="$(PlistBuddy -x -c "Print $KEY" "$APP_ENTITLEMENTS" 2>/dev/null | sed -e 's,.*<plist[^>]*>\(.*\)</plist>,\1,g')"
if [[ -z "$ENTITLEMENTS_VALUE" ]]; then
log "No value for '$KEY'"
continue
fi
if [[ "$KEY" == "com.apple.developer.icloud-container-environment" ]]; then
# Add specific iCloud Environment key to patched entitlements
# This value is set by Xcode during export (manually selected for Development and AdHoc, automatically set to Production for Store)
# Would need an additional dedicated option to specify the iCloud environment to be used (Development or Production)
# For now, we assume Production is to be used when signing with a Distribution certificate, Development if not
if [[ "$CERTIFICATE" =~ "Distribution:" ]]; then
ICLOUD_ENV="Production"
else
ICLOUD_ENV="Development"
fi
log "Overriding value for $KEY"
log "Old value: $ENTITLEMENTS_VALUE"
log "New value: $ICLOUD_ENV"
ENTITLEMENTS_VALUE="$ICLOUD_ENV"
fi
log "App entitlements value for key '$KEY':"
log "$ENTITLEMENTS_VALUE"
# Remove the entry for current key from profisioning profile entitlements (if exists)
PlistBuddy -c "Delete $KEY" "$PATCHED_ENTITLEMENTS" 2>/dev/null
# Add new entry to patched entitlements
# plutil needs dots in the key path to be escaped (e.g. com\.apple\.security\.application-groups)
# otherwise it interprets they key path as nested keys
# TODO: Should be able to replace with echo ${KEY//\./\\\\.} and remove shellcheck disable directive
# shellcheck disable=SC2001
PLUTIL_KEY=$(echo "$KEY" | sed 's/\./\\\./g')
plutil -insert "$PLUTIL_KEY" -xml "$ENTITLEMENTS_VALUE" "$PATCHED_ENTITLEMENTS"
# Patch the ID value if specified
if [[ "$ID_TYPE" == "APP_ID" ]]; then
# Replace old value with new value in patched entitlements
log "Replacing old app identifier prefix '$OLD_APP_ID' with new value '$NEW_APP_ID'"
sed -i .bak "s/$OLD_APP_ID/$NEW_APP_ID/g" "$PATCHED_ENTITLEMENTS"
elif [[ "$ID_TYPE" == "TEAM_ID" ]]; then
# Replace old team identifier with new value
log "Replacing old team ID '$OLD_TEAM_ID' with new team ID: '$NEW_TEAM_ID'"
sed -i .bak "s/$OLD_TEAM_ID/$NEW_TEAM_ID/g" "$PATCHED_ENTITLEMENTS"
else
continue
fi
done
# Replace old bundle ID with new bundle ID in patched entitlements
# Read old bundle ID from the old Info.plist which was saved for this purpose
OLD_BUNDLE_ID="$(PlistBuddy -c "Print :CFBundleIdentifier" "$TEMP_DIR/oldInfo.plist")"
NEW_BUNDLE_ID="$(bundle_id_for_provison "$NEW_PROVISION")"
log "Replacing old bundle ID '$OLD_BUNDLE_ID' with new bundle ID '$NEW_BUNDLE_ID' in patched entitlements"
# Note: ideally we'd match against the opening <string> tag too, but this isn't possible
# because $OLD_BUNDLE_ID and $NEW_BUNDLE_ID do not include the team ID prefix which is
# present in the entitlements file.
# e.g. <string>AB1GP98Q19.com.example.foo</string>
# vs
# com.example.foo
sed -i .bak "s!${OLD_BUNDLE_ID}</string>!${NEW_BUNDLE_ID}</string>!g" "$PATCHED_ENTITLEMENTS"
log "Resigning application using certificate: '$CERTIFICATE'"
log "and patched entitlements:"
log "$(cat "$PATCHED_ENTITLEMENTS")"
cp -f "$PATCHED_ENTITLEMENTS" "$APP_PATH/archived-expanded-entitlements.xcent"
/usr/bin/codesign ${VERBOSE} -f -s "$CERTIFICATE" --entitlements "$PATCHED_ENTITLEMENTS" "$APP_PATH"
checkStatus
else
log "Extracting entitlements from provisioning profile"
PlistBuddy -x -c "Print Entitlements" "$TEMP_DIR/profile.plist" > "$TEMP_DIR/newEntitlements"
checkStatus
log "Resigning application using certificate: '$CERTIFICATE'"
log "and entitlements from provisioning profile: $NEW_PROVISION"
if [[ "${XCODE_VERSION/.*/}" -lt 10 ]]; then
log "Creating an archived-expanded-entitlements.xcent file for Xcode 9 builds or earlier"
cp -- "$TEMP_DIR/newEntitlements" "$APP_PATH/archived-expanded-entitlements.xcent"
fi
# Must not qote KEYCHAIN_FLAG because it needs to be unwrapped and passed to codesign with spaces
# shellcheck disable=SC2086
/usr/bin/codesign ${VERBOSE} ${KEYCHAIN_FLAG} -f -s "$CERTIFICATE" --entitlements "$TEMP_DIR/newEntitlements" "$APP_PATH"
checkStatus
fi
# Remove the temporary files if they were created before generating ipa
rm -f "$TEMP_DIR/newEntitlements"
rm -f "$PROFILE_ENTITLEMENTS"
rm -f "$APP_ENTITLEMENTS"
rm -f "$PATCHED_ENTITLEMENTS"
rm -f "$PATCHED_ENTITLEMENTS.bak"
rm -f "$TEMP_DIR/old-embedded-profile.plist"
rm -f "$TEMP_DIR/profile.plist"
rm -f "$TEMP_DIR/old-embedded.mobileprovision"
rm -f "$TEMP_DIR/oldInfo.plist"
}
# Sign nested applications and app extensions
while IFS= read -d '' -r app;
do
log "Resigning nested application: '$app'"
resign "$app" NESTED
done < <(find "$TEMP_DIR/Payload/$APP_NAME" -d -mindepth 1 \( -name "*.app" -or -name "*.appex" \) -print0)
# Resign the application
resign "$TEMP_DIR/Payload/$APP_NAME"
# Repackage quietly
log "Repackaging as $NEW_FILE"
# Zip up the contents of the "$TEMP_DIR" folder
# Navigate to the temporary directory (sending the output to null)
# Zip all the contents, saving the zip file in the above directory
# Navigate back to the orignating directory (sending the output to null)
pushd "$TEMP_DIR" > /dev/null
# TODO: Fix shellcheck warning and remove directive
# shellcheck disable=SC2035
zip -qry "../$TEMP_DIR.ipa" *
popd > /dev/null
# Move the resulting ipa to the target destination
mv "$TEMP_DIR.ipa" "$NEW_FILE"
# Remove the temp directory
rm -rf "$TEMP_DIR"
log "Process complete"