1
1
#!/usr/bin/env python
2
2
"""Functions that interface with rcodesign"""
3
3
import asyncio
4
+ from collections import namedtuple
4
5
import logging
6
+ import os
5
7
import re
8
+ from glob import glob
9
+
10
+ from scriptworker_client .aio import download_file , raise_future_exceptions , retry_async
11
+ from scriptworker_client .exceptions import DownloadError
6
12
from signingscript .exceptions import SigningScriptError
7
13
8
14
log = logging .getLogger (__name__ )
@@ -41,13 +47,14 @@ async def _execute_command(command):
41
47
stderr = (await proc .stderr .readline ()).decode ("utf-8" ).rstrip ()
42
48
if stderr :
43
49
# Unfortunately a lot of outputs from rcodesign come out to stderr
44
- log .warn (stderr )
50
+ log .warning (stderr )
45
51
output_lines .append (stderr )
46
52
47
53
exitcode = await proc .wait ()
48
54
log .info ("exitcode {}" .format (exitcode ))
49
55
return exitcode , output_lines
50
56
57
+
51
58
def find_submission_id (logs ):
52
59
"""Given notarization logs, find and return the submission id
53
60
Args:
@@ -128,6 +135,7 @@ async def rcodesign_check_result(logs):
128
135
raise RCodesignError ("Notarization failed!" )
129
136
return
130
137
138
+
131
139
async def rcodesign_staple (path ):
132
140
"""Staples a given app
133
141
Args:
@@ -146,3 +154,134 @@ async def rcodesign_staple(path):
146
154
if exitcode > 0 :
147
155
raise RCodesignError (f"Error stapling notarization. Exit code { exitcode } " )
148
156
return
157
+
158
+
159
+ def _create_empty_entitlements_file (dest ):
160
+ contents = """<?xml version="1.0" encoding="UTF-8"?>
161
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
162
+ <plist version="1.0">
163
+ <dict>
164
+ </dict>
165
+ </plist>
166
+ """ .lstrip ()
167
+ with open (dest , "wt" ) as fd :
168
+ fd .writelines (contents )
169
+
170
+
171
+ async def _download_entitlements (hardened_sign_config , workdir ):
172
+ """Download entitlements listed in the hardened signing config
173
+ Args:
174
+ hardened_sign_config (list): hardened signing configs
175
+ workdir (str): current work directory where entitlements will be saved
176
+
177
+ Returns:
178
+ Map of url -> local file location
179
+ """
180
+ empty_file = os .path .join (workdir , "0-empty.xml" )
181
+ _create_empty_entitlements_file (empty_file )
182
+ # rcodesign requires us to specify an "empty" entitlements file
183
+ url_map = {None : empty_file }
184
+
185
+ # Unique urls to be downloaded
186
+ urls_to_download = set ([i ["entitlements" ] for i in hardened_sign_config if "entitlements" in i ])
187
+ # If nothing found, skip
188
+ if not urls_to_download :
189
+ log .warn ("No entitlements urls provided! Skipping download." )
190
+ return url_map
191
+
192
+ futures = []
193
+ for index , url in enumerate (urls_to_download , start = 1 ):
194
+ # Prefix filename with an index in case filenames are the same
195
+ filename = "{}-{}" .format (index , url .split ("/" )[- 1 ])
196
+ dest = os .path .join (workdir , filename )
197
+ url_map [url ] = dest
198
+ log .info (f"Downloading resource: { filename } from { url } " )
199
+ futures .append (
200
+ asyncio .ensure_future (
201
+ retry_async (
202
+ download_file ,
203
+ retry_exceptions = (DownloadError , TimeoutError ),
204
+ args = (url , dest ),
205
+ attempts = 5 ,
206
+ )
207
+ )
208
+ )
209
+ await raise_future_exceptions (futures )
210
+ return url_map
211
+
212
+
213
+ EntitlementEntry = namedtuple (
214
+ "EntitlementEntry" ,
215
+ ["file" , "entitlement" , "runtime" ],
216
+ )
217
+
218
+ def _get_entitlements_args (hardened_sign_config , path , entitlements_map ):
219
+ """Builds the list of entitlements based on files in path
220
+
221
+ Args:
222
+ hardened_sign_config (list): hardened signing configuration
223
+ path (str): path to app
224
+ """
225
+ entries = []
226
+
227
+ for config in hardened_sign_config :
228
+ entitlement_path = entitlements_map .get (config .get ("entitlements" ))
229
+ for path_glob in config ["globs" ]:
230
+ separator = ""
231
+ if not path_glob .startswith ("/" ):
232
+ separator = "/"
233
+ # Join incoming glob with root of app path
234
+ full_path_glob = path + separator + path_glob
235
+ for binary_path in glob (full_path_glob , recursive = True ):
236
+ # Get relative path
237
+ relative_path = os .path .relpath (binary_path , path )
238
+ # Append "<binary path>:<entitlement>" to list of args
239
+ entries .append (
240
+ EntitlementEntry (
241
+ file = relative_path ,
242
+ entitlement = entitlement_path ,
243
+ runtime = config .get ("runtime" ),
244
+ )
245
+ )
246
+
247
+ return entries
248
+
249
+
250
+ async def rcodesign_sign (workdir , path , creds_path , creds_pass_path , hardened_sign_config = []):
251
+ """Signs a given app
252
+ Args:
253
+ workdir (str): Path to work directory
254
+ path (str): Path to be signed
255
+ creds_path (str): Path to credentials file
256
+ creds_pass_path (str): Path to credentials password file
257
+ hardened_sign_config (list): Hardened signing configuration
258
+
259
+ Returns:
260
+ (Tuple) exit code, log lines
261
+ """
262
+ # TODO: Validate and sanitize input
263
+ command = [
264
+ "rcodesign" ,
265
+ "sign" ,
266
+ "--code-signature-flags=runtime" ,
267
+ f"--p12-file={ creds_path } " ,
268
+ f"--p12-password-file={ creds_pass_path } " ,
269
+ ]
270
+
271
+ entitlements_map = await _download_entitlements (hardened_sign_config , workdir )
272
+ file_entitlements = _get_entitlements_args (hardened_sign_config , path , entitlements_map )
273
+
274
+ def _scoped_arg (arg , basepath , value ):
275
+ if basepath == "." :
276
+ return f"--{ arg } ={ value } "
277
+ return f"--{ arg } ={ basepath } :{ value } "
278
+
279
+ for entry in file_entitlements :
280
+ if entry .runtime :
281
+ flags_arg = _scoped_arg ("code-signature-flags" , entry .file , "runtime" )
282
+ command .append (flags_arg )
283
+ entitlement_arg = _scoped_arg ("entitlements-xml-path" , entry .file , entry .entitlement )
284
+ command .append (entitlement_arg )
285
+
286
+ command .append (path )
287
+ await _execute_command (command )
0 commit comments