1+ from dataclasses import dataclass
12import json
23import pathlib
34from functools import cache
89from build import generate_sha
910from const import APPLE_TEAM_ID , CHAT_BINARY_NAME , CHAT_PACKAGE_NAME , LINUX_ARCHIVE_NAME
1011from signing import (
11- CdSigningData ,
12- CdSigningType ,
1312 load_gpg_signer ,
14- rebundle_dmg ,
15- cd_sign_file ,
16- apple_notarize_file ,
1713)
18- from util import debug , info , isDarwin , run_cmd , warn
14+ from util import debug , info , isDarwin , run_cmd , run_cmd_output , warn
1915from rust import cargo_cmd_name , rust_env , rust_targets
2016from importlib import import_module
2117
2420
2521REGION = "us-west-2"
2622SIGNING_API_BASE_URL = "https://api.signer.builder-tools.aws.dev"
23+ MACOS_BUNDLE_ID = "com.amazon.codewhisperer"
24+
25+
26+ @dataclass
27+ class CdSigningData :
28+ bucket_name : str
29+ '''The bucket hosting signing artifacts accessible by CD Signer.'''
30+ aws_account_id : str
31+ '''The id of the account hosting the build'''
32+ aws_region : str
33+ '''The AWS region where the app is built'''
34+ notarizing_secret_id : str
35+ signing_role_name : str
2736
2837
2938def run_cargo_tests ():
@@ -179,73 +188,45 @@ def cd_signer_status_request(request_id: str):
179188 return response_json ["signingRequest" ]["status" ]
180189
181190
182- def cd_build_signed_package (file_path : pathlib .Path ):
191+ def cd_build_signed_package (exe_path : pathlib .Path ):
183192 """
184193 Creates a tarball `package.tar.gz` with the following structure:
185194 ```
186195 package
187- ├─ manifest.yaml
188- ├─ artifact
189- | ├─ EXECUTABLES_TO_SIGN
190- | | ├─ qchat
191- ```
192-
193- Try #2:
194- ```
195- package
196196 ├─ EXECUTABLES_TO_SIGN
197197 | ├─ qchat
198198 ```
199199 """
200- # working_dir = BUILD_DIR / "package"
201- # shutil.rmtree(working_dir, ignore_errors=True)
202- # (BUILD_DIR / "package" / "artifact" / "EXECUTABLES_TO_SIGN").mkdir(parents=True)
203- #
204- # name = file_path.name
205- #
206- # # Write the manifest.yaml
207- # manifest_template_path = pathlib.Path.cwd() / "build-config" / "signing" / "qchat" / "manifest.yaml.template"
208- # (working_dir / "manifest.yaml").write_text(manifest_template_path.read_text().replace("__NAME__", name))
209- #
210- # shutil.copy2(file_path, working_dir / "artifact" / "EXECUTABLES_TO_SIGN" / file_path.name)
211- # file_path.unlink()
212- #
213- # run_cmd(
214- # ["gtar", "-czf", BUILD_DIR / "package.tar.gz", "manifest.yaml", "artifact"],
215- # cwd=working_dir,
216- # )
217-
218200 # Trying a different format without manifest.yaml and placing EXECUTABLES_TO_SIGN
219201 # at the root.
220202 # The docs contain conflicting information, idk what to even do here
221203 working_dir = BUILD_DIR / "package"
222204 shutil .rmtree (working_dir , ignore_errors = True )
223205 (BUILD_DIR / "package" / "EXECUTABLES_TO_SIGN" ).mkdir (parents = True )
224206
225- shutil .copy2 (file_path , working_dir / "EXECUTABLES_TO_SIGN" / file_path .name )
226- file_path .unlink ()
207+ shutil .copy2 (exe_path , working_dir / "EXECUTABLES_TO_SIGN" / exe_path .name )
208+ exe_path .unlink ()
227209
210+ run_cmd (["gtar" , "-czf" , "artifact.gz" , "EXECUTABLES_TO_SIGN" ], cwd = working_dir )
228211 run_cmd (
229- ["gtar" , "-czf" , BUILD_DIR / "package.tar.gz" , ". " ],
212+ ["gtar" , "-czf" , BUILD_DIR / "package.tar.gz" , "artifact.gz " ],
230213 cwd = working_dir ,
231214 )
232215
233216 return BUILD_DIR / "package.tar.gz"
234217
235218
236219def manifest (
237- name : str ,
238220 identifier : str ,
239221):
240222 """
241- Creates the required manifest argument when submitting the signing task. This has the same
242- structure as the manifest.yaml.template under `build-config/signing/qchat/manifest.yaml.template`
223+ Returns the manifest arguments required when creating a new CD Signer request.
243224 """
244225 return {
245226 "type" : "app" ,
246227 "os" : "osx" ,
247- "name" : name ,
248- "outputs" : [{"label" : "macos" , "path" : name }],
228+ "name" : "EXECUTABLES_TO_SIGN" ,
229+ "outputs" : [{"label" : "macos" , "path" : "EXECUTABLES_TO_SIGN" }],
249230 "app" : {
250231 "identifier" : identifier ,
251232 "signing_requirements" : {
@@ -256,7 +237,7 @@ def manifest(
256237 }
257238
258239
259- def sign_executable (signing_data : CdSigningData , chat_path : pathlib .Path ):
240+ def sign_executable (signing_data : CdSigningData , chat_path : pathlib .Path ) -> pathlib . Path :
260241 name = chat_path .name
261242 info (f"Signing { name } " )
262243
@@ -269,7 +250,7 @@ def sign_executable(signing_data: CdSigningData, chat_path: pathlib.Path):
269250 run_cmd (["aws" , "s3" , "cp" , package_path , f"s3://{ signing_data .bucket_name } /pre-signed/package.tar.gz" ])
270251
271252 info ("Sending request..." )
272- request_id = cd_signer_create_request (manifest (name , "com.amazon.codewhisperer" ))
253+ request_id = cd_signer_create_request (manifest ("com.amazon.codewhisperer" ))
273254 cd_signer_start_request (
274255 request_id = request_id ,
275256 source_key = "pre-signed/package.tar.gz" ,
@@ -303,18 +284,60 @@ def sign_executable(signing_data: CdSigningData, chat_path: pathlib.Path):
303284 info ("Signed!" )
304285
305286 info ("Downloading..." )
306- run_cmd (["aws" , "s3" , "cp" , f"s3://{ signing_data .bucket_name } /signed/signed.zip" , "signed.zip" ])
307- run_cmd (["unzip" , "signed.zip" ])
287+ # CD Signer should return the signed executable under "Payload/EXECUTABLES_TO_SIGN/{executable name}"
288+ run_cmd (["aws" , "s3" , "cp" , f"s3://{ signing_data .bucket_name } /signed/signed.zip" , "signed.zip" ], cwd = BUILD_DIR )
289+
290+ # Create a new directory for unzipping the signed executable.
291+ payload_path = BUILD_DIR / "signed"
292+ shutil .rmtree (payload_path , ignore_errors = True )
293+ run_cmd (["unzip" , "signed.zip" , "-d" , payload_path ], cwd = BUILD_DIR )
294+ signed_exe_path = BUILD_DIR / "signed" / "Payload" / "EXECUTABLES_TO_SIGN" / name
295+ # Verify that the exe is signed
296+ run_cmd (["codesign" , "--verify" , "--verbose=4" , signed_exe_path ])
297+ return signed_exe_path
298+
299+
300+ def notarize_executable (signing_data : CdSigningData , executable_path : pathlib .Path ):
301+ # Load the Apple id and password from secrets manager.
302+ secret_id = signing_data .notarizing_secret_id
303+ info (f"Loading secretmanager value: { secret_id } " )
304+ secret_value = run_cmd_output (["aws" , "--region" , signing_data .aws_region , "secretsmanager" , "get-secret-value" , "--secret-id" , secret_id ])
305+ secret_string = json .loads (secret_value )["SecretString" ]
306+ secrets = json .loads (secret_string )
307+
308+ # Submit the exe to Apple notary service. It must be zipped first.
309+ info (f"Submitting { executable_path } to Apple notary service" )
310+ zip_path = BUILD_DIR / f"{ executable_path .name } .zip"
311+ zip_path .unlink (missing_ok = True )
312+ run_cmd (["zip" , "-j" , zip_path , executable_path ], cwd = BUILD_DIR )
313+ submit_res = run_cmd_output (
314+ [
315+ "xcrun" ,
316+ "notarytool" ,
317+ "submit" ,
318+ zip_path ,
319+ "--team-id" ,
320+ APPLE_TEAM_ID ,
321+ "--apple-id" ,
322+ secrets ["appleId" ],
323+ "--password" ,
324+ secrets ["appleIdPassword" ],
325+ "--wait" ,
326+ "-f" , "json"
327+ ]
328+ )
329+ debug (f"Notary service response: { submit_res } " )
330+
331+ # Confirm notarization succeeded.
332+ assert (json .loads (submit_res )["status" ] == "Accepted" )
308333
309334
310335def sign_and_notarize (signing_data : CdSigningData , chat_path : pathlib .Path ):
311336 # First, sign the application
312- sign_executable (signing_data , chat_path )
337+ executable_path = sign_executable (signing_data , chat_path )
313338
314339 # Next, notarize the application
315-
316- # Last, staple the notarization to the application
317- pass
340+ notarize_executable (signing_data , executable_path )
318341
319342
320343def build_macos (chat_path : pathlib .Path , signing_data : CdSigningData | None ):
@@ -382,16 +405,18 @@ def build(
382405 output_bucket : str | None = None ,
383406 signing_bucket : str | None = None ,
384407 aws_account_id : str | None = None ,
408+ aws_region : str | None = None ,
385409 apple_id_secret : str | None = None ,
386410 signing_role_name : str | None = None ,
387411 stage_name : str | None = None ,
388412 run_lints : bool = True ,
389413 run_test : bool = True ,
390414):
391- if signing_bucket and aws_account_id and apple_id_secret and signing_role_name :
415+ if signing_bucket and aws_account_id and aws_region and apple_id_secret and signing_role_name :
392416 signing_data = CdSigningData (
393417 bucket_name = signing_bucket ,
394418 aws_account_id = aws_account_id ,
419+ aws_region = aws_region ,
395420 notarizing_secret_id = apple_id_secret ,
396421 signing_role_name = signing_role_name ,
397422 )
@@ -428,16 +453,6 @@ def build(
428453 )
429454
430455 if isDarwin ():
431- if signing_bucket and aws_account_id and apple_id_secret and signing_role_name :
432- signing_data = CdSigningData (
433- bucket_name = signing_bucket ,
434- aws_account_id = aws_account_id ,
435- notarizing_secret_id = apple_id_secret ,
436- signing_role_name = signing_role_name ,
437- )
438- else :
439- signing_data = None
440-
441456 chat_path = build_macos (chat_path , signing_data )
442457 sha_path = generate_sha (chat_path )
443458
0 commit comments