24/7 twenty-four seven

iOS/OS X application programing topics.

CocoaでWSSE認証

WSSE認証に必要なこと

リクエストのHTTPヘッダに、次の内容を含めて送信します。

Username
ユーザー名。(はてなアカウントのid)
Nonce
HTTPリクエスト毎に生成したセキュリティ・トークン*1
Created
Nonceが作成された日時をISO-8601表記で記述したもの
PasswordDigest
Nonce, Created, パスワード(はてなアカウントのパスワード)を文字列連結しSHA1アルゴリズムでダイジェスト化して生成された文字列を、Base64エンコードした文字列

はてなダイアリーAtomPubとは - はてなキーワード

ポイントとしては以下の3点をCocoa(Objective-C)でどう実現するかということになります。

  • SHA-1 ダイジェスト
  • Base64エンコード
  • ISO-8601表記の日時

SHA-1 ダイジェストを生成する

OpenSSLのライブラリを使用すると簡単です。

cocoacco: OpenSSL用フレームワーク
とりあえずAPIは叩けた : As Sloth As Possible

下記のコード(CocoaCryptoHashing)を使用しました。

Stoneship > Software
http://projects.stoneship.org/hg/cocoa_crypto_hashing/

CocoaCryptoHashingはNSStringとNSDataのカテゴリとして実装されています。sha1HexHashは戻り値がNSString、sha1HashはNSDataになっています。
パスワードダイジェストは最終的にBase64エンコードをするので、NSData型で返しています。(後述のBase64のライブラリはNSDataのカテゴリとして実装されているため)

NSString *nonce = 
    [[NSString stringWithFormat:@"%@%d", formattedDate, rand()] sha1HexHash];
NSString *passwordDigest =
    [[[NSString stringWithFormat:@"%@%@%@", 
     nonce, formattedDate, password] sha1Hash] stringEncodedWithBase64];

Base64エンコード

下記のサイトにあるコードを使用しました。

NSDataにBase64のエンコード・デコード機能を追加する
NSDataのカテゴリーとして実装されているので、Base64の文字列を得るためにはNSStringを一度NSDataに変換する必要があります。
簡単に - (NSData *)dataUsingEncoding:(NSStringEncoding)encoding メソッドを使いました。

NSString *nonce = 
    [[NSString stringWithFormat:@"%@%d", formattedDate, rand()] sha1HexHash];
NSString *base64 = 
    [[nonce dataUsingEncoding:NSASCIIStringEncoding] stringEncodedWithBase64];

別の方法も参考として挙げておきます。
MacBookでプログラミング : Cocoaプログラミングで文字列をBase64でエンコードしたい - livedoor Blog(ブログ)
http://www.cocoadev.com/index.pl?BaseSixtyFour

ISO-8601表記の日時

次のような表記になります。

2004-04-01T12:00:00+09:00

ISO 8601 - Wikipedia

日付のフォーマットはNSDateFormatterを使います。
書式の表記については、Mac OS X 10.3以前と10.4以降で変更があり、10.4以降ではUnicode standardの記法を使用します。
また、iPhone OS 2.1から、時刻の24時間表時をオフにした場合、午前・午後の文字列が含まれて返ってきます。
そのため、午前もしくは午後の文字列を取り去る処理が必要になります。

NSDate *now = [[NSDate date] retain];
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:@"yyyy-MM-dd'T'HH:mm:sszzz"];
NSString *dateString = [dateFormatter stringFromDate:now];
dateString = 
    [dateString stringByReplacingOccurrencesOfString:[NSString stringWithUTF8String:"午前"] withString:@""];
dateString = 
    [dateString stringByReplacingOccurrencesOfString:[NSString stringWithUTF8String:"午後"] withString:@""];

最終的に、以下のコードになりました。

srand(time(nil));
NSString *nonce = 
    [[NSString stringWithFormat:@"%@%d", formattedDate, rand()] sha1HexHash];
NSString *passwordDigest = 
    [[[NSString stringWithFormat:@"%@%@%@",
     nonce, formattedDate, password] sha1Hash] stringEncodedWithBase64];
NSString *base64 = 
    [[nonce dataUsingEncoding:NSASCIIStringEncoding] stringEncodedWithBase64];
NSString *credentials = 
    [NSString stringWithFormat:
     @"UsernameToken Username=\"%@\", "
     @"PasswordDigest=\"%@\", "
     @"Nonce=\"%@\", "
     @"Created=\"%@\"", userName, passwordDigest, base64, formattedDate];