33// SPDX-License-Identifier: MIT
44
55use std:: {
6+ ffi:: OsString ,
67 fs:: File ,
78 io:: prelude:: * ,
89 path:: PathBuf ,
@@ -12,181 +13,223 @@ use std::{
1213use crate :: { bundle:: common, Settings } ;
1314use regex:: Regex ;
1415
16+ const KEYCHAIN_ID : & str = "tauri-build.keychain" ;
17+ const KEYCHAIN_PWD : & str = "tauri-build" ;
18+
1519// Import certificate from ENV variables.
1620// APPLE_CERTIFICATE is the p12 certificate base64 encoded.
1721// By example you can use; openssl base64 -in MyCertificate.p12 -out MyCertificate-base64.txt
1822// Then use the value of the base64 in APPLE_CERTIFICATE env variable.
1923// You need to set APPLE_CERTIFICATE_PASSWORD to the password you set when youy exported your certificate.
2024// https://help.apple.com/xcode/mac/current/#/dev154b28f09 see: `Export a signing certificate`
21- pub fn setup_keychain_if_needed ( ) -> crate :: Result < ( ) > {
22- match (
23- std:: env:: var_os ( "APPLE_CERTIFICATE" ) ,
24- std:: env:: var_os ( "APPLE_CERTIFICATE_PASSWORD" ) ,
25- ) {
26- ( Some ( certificate_encoded) , Some ( certificate_password) ) => {
27- // we delete any previous version of our keychain if present
28- delete_keychain_if_needed ( ) ;
29- common:: print_info ( "setup keychain from environment variables..." ) ?;
30-
31- let key_chain_id = "tauri-build.keychain" ;
32- let key_chain_name = "tauri-build" ;
33- let tmp_dir = tempfile:: tempdir ( ) ?;
34- let cert_path = tmp_dir
35- . path ( )
36- . join ( "cert.p12" )
37- . to_string_lossy ( )
38- . to_string ( ) ;
39- let cert_path_tmp = tmp_dir
40- . path ( )
41- . join ( "cert.p12.tmp" )
42- . to_string_lossy ( )
43- . to_string ( ) ;
44- let certificate_encoded = certificate_encoded
45- . to_str ( )
46- . expect ( "failed to convert APPLE_CERTIFICATE to string" )
47- . as_bytes ( ) ;
25+ pub fn setup_keychain (
26+ certificate_encoded : OsString ,
27+ certificate_password : OsString ,
28+ ) -> crate :: Result < ( ) > {
29+ // we delete any previous version of our keychain if present
30+ delete_keychain ( ) ;
31+ common:: print_info ( "setup keychain from environment variables..." ) ?;
4832
49- let certificate_password = certificate_password
50- . to_str ( )
51- . expect ( "failed to convert APPLE_CERTIFICATE_PASSWORD to string" )
52- . to_string ( ) ;
33+ let keychain_list_output = Command :: new ( "security" )
34+ . args ( [ "list-keychain" , "-d" , "user" ] )
35+ . output ( ) ?;
5336
54- // as certificate contain whitespace decoding may be broken
55- // https://github.com/marshallpierce/rust-base64/issues/105
56- // we'll use builtin base64 command from the OS
57- let mut tmp_cert = File :: create ( cert_path_tmp. clone ( ) ) ?;
58- tmp_cert. write_all ( certificate_encoded) ?;
37+ let tmp_dir = tempfile:: tempdir ( ) ?;
38+ let cert_path = tmp_dir
39+ . path ( )
40+ . join ( "cert.p12" )
41+ . to_string_lossy ( )
42+ . to_string ( ) ;
43+ let cert_path_tmp = tmp_dir
44+ . path ( )
45+ . join ( "cert.p12.tmp" )
46+ . to_string_lossy ( )
47+ . to_string ( ) ;
48+ let certificate_encoded = certificate_encoded
49+ . to_str ( )
50+ . expect ( "failed to convert APPLE_CERTIFICATE to string" )
51+ . as_bytes ( ) ;
5952
60- let decode_certificate = Command :: new ( "base64" )
61- . args ( vec ! [ "--decode" , "-i" , & cert_path_tmp, "-o" , & cert_path] )
62- . stderr ( Stdio :: piped ( ) )
63- . status ( ) ?;
53+ let certificate_password = certificate_password
54+ . to_str ( )
55+ . expect ( "failed to convert APPLE_CERTIFICATE_PASSWORD to string" )
56+ . to_string ( ) ;
57+
58+ // as certificate contain whitespace decoding may be broken
59+ // https://github.com/marshallpierce/rust-base64/issues/105
60+ // we'll use builtin base64 command from the OS
61+ let mut tmp_cert = File :: create ( cert_path_tmp. clone ( ) ) ?;
62+ tmp_cert. write_all ( certificate_encoded) ?;
63+
64+ let decode_certificate = Command :: new ( "base64" )
65+ . args ( [ "--decode" , "-i" , & cert_path_tmp, "-o" , & cert_path] )
66+ . stderr ( Stdio :: piped ( ) )
67+ . status ( ) ?;
6468
65- if !decode_certificate. success ( ) {
66- return Err ( anyhow:: anyhow!( "failed to decode certificate" , ) . into ( ) ) ;
67- }
69+ if !decode_certificate. success ( ) {
70+ return Err ( anyhow:: anyhow!( "failed to decode certificate" , ) . into ( ) ) ;
71+ }
6872
69- let create_key_chain = Command :: new ( "security" )
70- . args ( vec ! [ "create-keychain" , "-p" , key_chain_name , key_chain_id ] )
71- . stdout ( Stdio :: piped ( ) )
72- . stderr ( Stdio :: piped ( ) )
73- . status ( ) ?;
73+ let create_key_chain = Command :: new ( "security" )
74+ . args ( [ "create-keychain" , "-p" , KEYCHAIN_PWD , KEYCHAIN_ID ] )
75+ . stdout ( Stdio :: piped ( ) )
76+ . stderr ( Stdio :: piped ( ) )
77+ . status ( ) ?;
7478
75- if !create_key_chain. success ( ) {
76- return Err ( anyhow:: anyhow!( "failed to create keychain" , ) . into ( ) ) ;
77- }
79+ if !create_key_chain. success ( ) {
80+ return Err ( anyhow:: anyhow!( "failed to create keychain" , ) . into ( ) ) ;
81+ }
7882
79- let set_default_keychain = Command :: new ( "security" )
80- . args ( vec ! [ "default -keychain", "-s " , key_chain_id ] )
81- . stdout ( Stdio :: piped ( ) )
82- . stderr ( Stdio :: piped ( ) )
83- . status ( ) ?;
83+ let unlock_keychain = Command :: new ( "security" )
84+ . args ( [ "unlock -keychain", "-p " , KEYCHAIN_PWD , KEYCHAIN_ID ] )
85+ . stdout ( Stdio :: piped ( ) )
86+ . stderr ( Stdio :: piped ( ) )
87+ . status ( ) ?;
8488
85- if !set_default_keychain . success ( ) {
86- return Err ( anyhow:: anyhow!( "failed to set default keychain" , ) . into ( ) ) ;
87- }
89+ if !unlock_keychain . success ( ) {
90+ return Err ( anyhow:: anyhow!( "failed to set unlock keychain" , ) . into ( ) ) ;
91+ }
8892
89- let unlock_keychain = Command :: new ( "security" )
90- . args ( vec ! [ "unlock-keychain" , "-p" , key_chain_name, key_chain_id] )
91- . stdout ( Stdio :: piped ( ) )
92- . stderr ( Stdio :: piped ( ) )
93- . status ( ) ?;
93+ let import_certificate = Command :: new ( "security" )
94+ . args ( [
95+ "import" ,
96+ & cert_path,
97+ "-k" ,
98+ KEYCHAIN_ID ,
99+ "-P" ,
100+ & certificate_password,
101+ "-T" ,
102+ "/usr/bin/codesign" ,
103+ "-T" ,
104+ "/usr/bin/pkgbuild" ,
105+ "-T" ,
106+ "/usr/bin/productbuild" ,
107+ ] )
108+ . stderr ( Stdio :: inherit ( ) )
109+ . output ( ) ?;
94110
95- if !unlock_keychain. success ( ) {
96- return Err ( anyhow:: anyhow!( "failed to set unlock keychain" , ) . into ( ) ) ;
97- }
111+ if !import_certificate. status . success ( ) {
112+ return Err (
113+ anyhow:: anyhow!( format!(
114+ "failed to import keychain certificate {:?}" ,
115+ std:: str :: from_utf8( & import_certificate. stdout)
116+ ) )
117+ . into ( ) ,
118+ ) ;
119+ }
98120
99- let import_certificate = Command :: new ( "security" )
100- . arg ( "import" )
101- . arg ( cert_path)
102- . arg ( "-k" )
103- . arg ( key_chain_id)
104- . arg ( "-P" )
105- . arg ( certificate_password)
106- . arg ( "-T" )
107- . arg ( "/usr/bin/codesign" )
108- . arg ( "-T" )
109- . arg ( "/usr/bin/pkgbuild" )
110- . arg ( "-T" )
111- . arg ( "/usr/bin/productbuild" )
112- . stderr ( Stdio :: inherit ( ) )
113- . output ( ) ?;
114-
115- if !import_certificate. status . success ( ) {
116- return Err (
117- anyhow:: anyhow!( format!(
118- "failed to import keychain certificate {:?}" ,
119- std:: str :: from_utf8( & import_certificate. stdout)
120- ) )
121- . into ( ) ,
122- ) ;
123- }
121+ let settings_keychain = Command :: new ( "security" )
122+ . args ( [ "set-keychain-settings" , "-t" , "3600" , "-u" , KEYCHAIN_ID ] )
123+ . stdout ( Stdio :: piped ( ) )
124+ . stderr ( Stdio :: piped ( ) )
125+ . status ( ) ?;
124126
125- let settings_keychain = Command :: new ( "security" )
126- . args ( vec ! [
127- "set-keychain-settings" ,
128- "-t" ,
129- "3600" ,
130- "-u" ,
131- key_chain_id,
132- ] )
133- . stdout ( Stdio :: piped ( ) )
134- . stderr ( Stdio :: piped ( ) )
135- . status ( ) ?;
136-
137- if !settings_keychain. success ( ) {
138- return Err ( anyhow:: anyhow!( "failed to set keychain settings" , ) . into ( ) ) ;
139- }
127+ if !settings_keychain. success ( ) {
128+ return Err ( anyhow:: anyhow!( "failed to set keychain settings" , ) . into ( ) ) ;
129+ }
140130
141- let partition_list = Command :: new ( "security" )
142- . args ( vec ! [
143- "set-key-partition-list" ,
144- "-S" ,
145- "apple-tool:,apple:,codesign:" ,
146- "-s" ,
147- "-k" ,
148- key_chain_name,
149- key_chain_id,
150- ] )
151- . stdout ( Stdio :: piped ( ) )
152- . stderr ( Stdio :: piped ( ) )
153- . status ( ) ?;
154-
155- if !partition_list. success ( ) {
156- return Err ( anyhow:: anyhow!( "failed to set keychain settings" , ) . into ( ) ) ;
157- }
131+ let partition_list = Command :: new ( "security" )
132+ . args ( [
133+ "set-key-partition-list" ,
134+ "-S" ,
135+ "apple-tool:,apple:,codesign:" ,
136+ "-s" ,
137+ "-k" ,
138+ KEYCHAIN_PWD ,
139+ KEYCHAIN_ID ,
140+ ] )
141+ . stdout ( Stdio :: piped ( ) )
142+ . stderr ( Stdio :: piped ( ) )
143+ . status ( ) ?;
158144
159- Ok ( ( ) )
160- }
161- // skip it
162- _ => Ok ( ( ) ) ,
145+ if !partition_list. success ( ) {
146+ return Err ( anyhow:: anyhow!( "failed to set keychain settings" , ) . into ( ) ) ;
147+ }
148+
149+ let current_keychains = String :: from_utf8_lossy ( & keychain_list_output. stdout )
150+ . split ( '\n' )
151+ . map ( |line| {
152+ line
153+ . trim_matches ( |c : char | c. is_whitespace ( ) || c == '"' )
154+ . to_string ( )
155+ } )
156+ . filter ( |l| !l. is_empty ( ) )
157+ . collect :: < Vec < String > > ( ) ;
158+
159+ let set_keychain_list_entry = Command :: new ( "security" )
160+ . args ( [ "list-keychain" , "-d" , "user" , "-s" ] )
161+ . args ( current_keychains)
162+ . arg ( KEYCHAIN_ID )
163+ . stdout ( Stdio :: piped ( ) )
164+ . stderr ( Stdio :: piped ( ) )
165+ . status ( ) ?;
166+
167+ if !set_keychain_list_entry. success ( ) {
168+ return Err ( anyhow:: anyhow!( "failed to list keychain" , ) . into ( ) ) ;
163169 }
170+
171+ Ok ( ( ) )
164172}
165173
166- pub fn delete_keychain_if_needed ( ) {
167- if let ( Some ( _cert) , Some ( _password) ) = (
174+ pub fn delete_keychain ( ) {
175+ // delete keychain if needed and skip any error
176+ let _ = Command :: new ( "security" )
177+ . arg ( "delete-keychain" )
178+ . arg ( KEYCHAIN_ID )
179+ . stdout ( Stdio :: piped ( ) )
180+ . stderr ( Stdio :: piped ( ) )
181+ . status ( ) ;
182+ }
183+
184+ pub fn sign (
185+ path_to_sign : PathBuf ,
186+ identity : & str ,
187+ settings : & Settings ,
188+ is_an_executable : bool ,
189+ ) -> crate :: Result < ( ) > {
190+ let setup_keychain = if let ( Some ( certificate_encoded) , Some ( certificate_password) ) = (
168191 std:: env:: var_os ( "APPLE_CERTIFICATE" ) ,
169192 std:: env:: var_os ( "APPLE_CERTIFICATE_PASSWORD" ) ,
170193 ) {
171- let key_chain_id = "tauri-build.keychain" ;
172- // delete keychain if needed and skip any error
173- let _result = Command :: new ( "security" )
174- . arg ( "delete-keychain" )
175- . arg ( key_chain_id)
176- . stdout ( Stdio :: piped ( ) )
177- . stderr ( Stdio :: piped ( ) )
178- . status ( ) ;
194+ // setup keychain allow you to import your certificate
195+ // for CI build
196+ setup_keychain ( certificate_encoded, certificate_password) ?;
197+ true
198+ } else {
199+ false
200+ } ;
201+
202+ let res = try_sign (
203+ path_to_sign,
204+ identity,
205+ settings,
206+ is_an_executable,
207+ setup_keychain,
208+ ) ;
209+
210+ if setup_keychain {
211+ // delete the keychain again after signing
212+ delete_keychain ( ) ;
179213 }
214+
215+ res
180216}
181217
182- pub fn sign (
218+ fn try_sign (
183219 path_to_sign : PathBuf ,
184220 identity : & str ,
185221 settings : & Settings ,
186222 is_an_executable : bool ,
223+ tauri_keychain : bool ,
187224) -> crate :: Result < ( ) > {
188225 common:: print_info ( format ! ( r#"signing with identity "{}""# , identity) . as_str ( ) ) ?;
189226 let mut args = vec ! [ "--force" , "-s" , identity] ;
227+
228+ if tauri_keychain {
229+ args. push ( "--keychain" ) ;
230+ args. push ( KEYCHAIN_ID ) ;
231+ }
232+
190233 if let Some ( entitlements_path) = & settings. macos ( ) . entitlements {
191234 common:: print_info ( format ! ( "using entitlements file at {}" , entitlements_path) . as_str ( ) ) ?;
192235 args. push ( "--entitlements" ) ;
0 commit comments