2012年7月13日金曜日

AppleScript や Automator の CodeSign

Mac OS X 10.8 (Mountain Lion)からは、Appleから認証されたDeveloper IDのCode Sign のないアプリケーションをダウンロードするとデフォルトの状態では起動できない。

通常、Developer IDのCode Signは、Xcode のbuild 時に付加される。


ところで、AppleScript のアプリケーションや Automatorのワークフローも Developer IDのCode Sign がついていないと同じように起動できない。
ところが、これらのアプリケーションの作成には Xcode は使わない。AppleScript のアプリケーションは AppleScript Editor で、Automatorのワークフローは Automator.app で作成する。

では、どうやってAppleScript や Automator に CodeSigin を付加するのか。以下のようにすると Xcodeを使わず codesign コマンドを使って直接アプリケーションに CodeSign を付加することができる。

AppleScript アプリケーション

1) CodeSignのためには、アプリケーションパッケージ内の info.plist のなかに Bundle Identifier が定義されていなければならない。AppleScriptアプリケーションには一般にBundle Identifierが定義されていないので、以下のようなBundle Identifierを追加する。

           <key>CFBundleIdentifier</key>
            <string>jp.co.yourCompany.applescript.foo</string>

Bundle Identifierの追加はテキストエディタとかplistエディタを使う。なお、Mountain LionのAppleScriptエディタには新たにBundle Identifierの編集機能が追加されているので、この機能を使っても行える。

2) アプリケーションパッケージ内のmain.scpt を chmod コマンドで書き込み禁止にする。

$ chmod a-w foo.app/Contents/Resources/Scripts/main.scpt

3 ) codesign コマンドで CodeSign を書き込む。

$ codesign --sign 'Developer ID Application: YourCompany.' 
-i jp.co.yourCompany.applescript.foo -f foo.app 

4 )うまく CodeSign が付加されたかどうかは以下のコマンドで確認できる。

$ codesign --display -vvv foo.app

Automator ワークフロー

1) Bundle Identifier の修正。Automator のワークフローのパッケージ内の info.plist には、すでに Bundle Identifier が備わっているので、それを希望の文字列、たとえば
jp.co.yourCompany.automator.foo
に書き換える。

2) codesign コマンドで CodeSign を書き込む。

$ codesign --sign 'Developer ID Application: YourCompany.' 
-i jp.co.yourCompany. automator.foo -f foo.app 

3)うまく CodeSign が付加されたかどうかは以下のコマンドで確認できる。

$ codesign --display -vvv foo.app


Xcode 3.2.6 と Gate Keeper

Mac OS X 10.8 (Mountain Lion)では、ダウンロードされた不正なアプリからシステムを防御するため Gate Keeper という機能が新設された。デフォルトのシステム設定では、Appleから認証されたCode Sign (Mac Developer ID)が付加されていないアプリをダウンロードし起動しようとすると、以下のようアラートが出て起動することができない。


というわけで、Mac OS X 10.8 からはデベロッパーは自分の製品を「Mac Developer ID」で Code Sign しないといけなくなった。


まず、Mac Developer ID をAppleから発行してもらう。Mac Developer ID をどうやって発行してもらうかは、この投稿の本題ではない。Mac Developer IDをまだもっていないひとは以下のページを参照して取得してください。
Developer Certificate Utility - Mac Developer Program - Support - Apple Developer

無事、Mac Developer ID が取得できたら Xcodeで目的の製品をbuildするときに、buildオプション CodeSign にMac Developer ID :Application を指定しbuildすればよい(詳しくは以下を参照)。
Tools Workflow Guide for Mac: Distributing Outside the Mac App Store

以上でめでたく Mac Developer ID でCode Sign された製品ができる(はずな)わけである。(だったら、なんでわざわざこんな投稿をする必要があるのか。)


それがどっこい、そうではないのだ。


Mac Developer ID で Code Sign をするときは、
Xcode 4.3 でないといけないのだ!!

Xcode 4.2 とか Xcode 3.2. でbuild してしまうと、付加される Code Sign の内容が異なり、Mac OS X 10.5あるいは Mac OS 10.6 で動作させたときMac Developer ID がシステムに正しく認識されないという問題が生じる。
Mac Developer ID が認識されなくとも、もともと、Mac OS X 10.5/10.6 では Gate Keeper が動作していないので問題ないではないか思うかもしれないが、そうではない。

たとえば KeyChain に登録されているベーシック認証のページを開こうとすると、初めて開くときだけ 開くかどうか許可ダイアログで聞いてくるが、2回目以降は許可ダイアログなしに直接開くことができる。


Xcode3.2.6やXcode4.2 で Developer ID のCodeSign でbuild したアプリでは、システムに正しく認証されていないので、ベーシック認証のページを開こうとすると、毎回、許可ダイアログが表示されてしまうのだ。このようなアプリのもとでは KeyChain が正しく動作しないのだ。


では、実際に Xcode4.3でbuildされた場合と、Xcode3.2でbuildされたときで、Mac Developer ID のCode Signのどこが違うのか、ターミナルから コマンド「codesign -d -r-」で直接みてみよう。


Xcode3.2.6 でビルドされた foo.app では
$ codesign -d -r- foo.app 
Executable= foo.app/Contents/MacOS/foo
library => identifier "com.apple.Cocoa" and anchor apple or identifier "com.apple.Carbon"  <--  略 --> and anchor apple or identifier "com.apple.Foundation" and anchor apple
# designated => identifier "jp.co.artman21.foo" and anchor apple generic and certificate 1[field.1.2.840.113635.100.6.2.6] /* exists */ and certificate leaf[field.1.2.840.113635.100.6.1.13] /* exists */ and certificate leaf[subject.OU] = F8FSQZCDV7


緑の字の部分[ designated record ]は、ルート証明書「anchor apple generic」と中間証明書「certificate 1[field.1.2.840.113635.100.6.2.6] 」そして自分のDeveloper IDの証明書「certificate leaf[field.1.2.840.113635.100.6.1.13]が存在しなければならず、Developer IDのの値はF8FSQZCDV7 であると指定している。
ところが、なんと中間証明書 certificate1[field.1.2.840.113635.100.6.2.6]は、Mac OS 10.5/10.6 ではシステムに実装されていないだ。これが foo.app がシステムに正しく認証されない原因だ。

一方、Xcode 4.3 でビルドされた bar.app では

$ codesign -d -r- bar.app  
Executable= foo.app/Contents/MacOS/bar
library => identifier "com.apple.Cocoa" and anchor apple or identifier "com.apple.Carbon"  <--  略 --> and anchor apple or identifier "com.apple.Foundation" and anchor apple
designated => anchor apple generic and identifier "jp.co.artman21.bar" and (certificate leaf[field.1.2.840.113635.100.6.1.9] /* exists */ or certificate 1[field.1.2.840.113635.100.6.2.6] /* exists */ and certificate leaf[field.1.2.840.113635.100.6.1.13] /* exists */ and certificate leaf[subject.OU] = F8FSQZCDV7)

緑の字の部分[ designated record ] の内容が Xcode3.2.6 の場合とちがって、中間証明書が「(certificate leaf[field.1.2.840.113635.100.6.1.9] または certificate 1[field.1.2.840.113635.100.6.2.6] 」のどちらかとなっている。中間証明書 のどちらかが存在すればエラーにならない。すなわち、bar.app はMac OS 10.5/10.6 でもシステムに正しく認証される。

でも、Xcode 4.3 に移行できない
ときはどうすればいいのだ 

新製品はともかく、昔からある製品は、過去のしがらみがあって、Mac OS X 10.4 もサポートしなければいけなかったり、ppc もサポートしななればいけなかったりで、Xcode 4.3 に移行できない場合も多い。

そんなときは、とりあえず Xcode 3.2.6 でbuild して、CodeSign の[ designated record ]の部分だけ、以下のように Xcode4.3 の[ designated record ]に入れ替えるとうまくいく。

まず、Xcode4.3 でbuild した bar.app[ designated record ]をもとに foo.app の[ designated record ]を以下のように作成しファイル「fooDR.txt」として保存する。

designated => anchor apple generic and identifier "jp.co.artman21.foo" and (certificate leaf[field.1.2.840.113635.100.6.1.9] /* exists */ or certificate 1[field.1.2.840.113635.100.6.2.6] /* exists */ and certificate leaf[field.1.2.840.113635.100.6.1.13] /* exists */ and certificate leaf[subject.OU] = F8FSQZCDV7)

そして、ターミナルから以下のコマンドで

$ codesign -r fooDR.txt -s "Developer ID Application: Artman21 Inc." -f foo.app
foo.app: replacing existing signature

うまくかきかわったかどうかは、

$ codesign -d -r- foo.app 

で調べられる。
ー 以上 ー

<謝辞> 今回の投稿の作成にあたっては、BRadikoの作者 bui さんからいろいろ貴重な示唆をいただきました。また、Daniel Jalkut さんの Red Seater Blog の投稿 Developer ID Gotcha も非常に参考になりました。


2012年3月5日月曜日

Tweet 読み上げスクリプト つぶやき Kyoko


Twitterタイムラインに届いた新着ツイートをリアルタイムにKyokoの声でつぶやきます。

以下からダウンロードできます。


<<動作環境>>
Mac OS X 10.7 以上、Twitter.app が必要です。
システム環境設定「スピーチ>テキスト読み上げ」で日本語音声読み上げの"Kyoko"、英語は"Alex"をあらかじめオンにしてください。

<<特徴>>
日本語のtweetは"Kyoko"で、英語は"Alex"で読み上げます。
tweet中のURLやハッシュタグは除外して読み上げます。
AppleScripエディタで「つぶやき Kyoko」のソースリストを参照できます。

<<修正履歴>>
12.03.05 いくつかのバグを修正し、アプリケーション名を「つぶやき Kyoko」に変更。
12.02.11 ツイート末のにあるハッシュタグやアドレスの最後の1文字を発音してしまうバグを修正。
12.02.10 Twiittererを公開

<<制作/著作>>
松本慧 satoshi@mac.com  Twitter:@cometheart http://cometheart.blogspot.com/

ソースリスト

property enableF : true
property intervalSec : 1
property lastUpdate : 0

on run
set intervalSec to 1
tell application "Twitter" to activate
delay 3
set enableF to true
end run

on idle
if enableF = true then
set enableF to false
idleMain() of me
set enableF to true
end if
return intervalSec
end idle

on idleMain()
tell application "Twitter"
set ca to count of every account
if (ca is greater than 0) then
tell account 1
repeat
set ht to a reference to home timeline
--set mt to a reference to mentions timeline
set now to last update of ht
if now is not lastUpdate then
set lastUpdate to now
set n to name of ht
set ss to a reference to (every status of home timeline)
set s to a reference to item 1 of ss
set t to full name of author of s
smartSay(t) of me
set t to text of s
set t to filter(t) of me
smartSay(t) of me
else
exit repeat
end if
end repeat
end tell
end if
end tell
end idleMain

on smartSay(s)
set len to length of s
set i to 1
repeat while (ilen)
if character i of s > "z" then
say s using "Kyoko"
return
end if
set i to i + 1
end repeat
say s using "Alex"
return
end smartSay

on filter(s)
set len to length of s
set t to ""
set i to 1
repeat while (ilen)
if i < len - 6 then
set substring to (characters i thru (i + 6) of s) as string
else
set substring to ""
end if
--display dialog text of substring
if substring is "http://" then
set i to i + 6
repeat while (ilen)
if character i of s > "z" then exit repeat
set i to i + 1
end repeat
else if character i of s is "#" or character i of s is "@" then
set i to i + 1
repeat while (ilen)
if character i of s > "z" then exit repeat
set i to i + 1
end repeat
else if character i of s is "…" then
set i to i + 1
else
set t to t & character i of s
set i to i + 1
end if
end repeat
return t
end filter