node.jsでPassbookサーバをつくってみる(3)

初回のポストで3回くらいと言っておきながら、3回では終わらないことが確実な状況に…

前回まででmanifest.jsonを生成する処理までは作りました。
今回はそれに加えて、署名をする処理、Zipで固める処理を追加して、一旦サーバとして作り切ってしまいます。

署名をする際に必要となるのが証明書となります。今回は処理を書く前に、必要な証明書ファイルたちをキーチェインから取り出しておきます。
まず、証明書はiPhoneアプリの作成時と同様にDeveloper Site の Provisioning Portal から作成できます。
作成方法はAppleのドキュメントを見て頂き、生成した証明書ファイルをMacのキーチェインにImportしておきます。

Importが完了したら、キーチェインに入っている証明書をp12形式で書き出します。キーチェインでImportした証明書を選択し、ファイルメニューから「書き出す」を行えばp12形式で出力できます。適当に pass.p12 というファイル名で出力してください。
なお、p12形式でファイルを保存する際にパスワードを設定しますが、このパスワードは後の処理で用いますので心の片隅にでも覚えておいてください。
p12ファイルのままではopensslコマンドで処理しにくいのでp12ファイルをpemファイルに変換します。以下のコマンドで pass.p12 を pass.pem に変換してください。
なお、このときもパスワードが聞かれますが、これはプログラム内で使うので控えておいてください。

rei:passbook tetsuo$ openssl pkcs12 -in pass.p12 -out pass.pem

次に中間証明書をファイルに保存します。これはProvisioning Portalで生成した証明書を検証するために必要なファイルとなります。iOS6 beta3くらいから必須になりました。

これは以下のコマンドでファイルに保存できます。

rei:~ tetsuo$ security find-certificate -p -c 'Apple Worldwide Developer Relations Certification Authority' login.keychain > wwdr.pem

これで wwdr.pem ファイルに中間証明書が保存できます。

2つの証明書ファイルが生成されたのでアプリケーションに組込みます。 keysディレクトリを作って証明書ファイルを格納すると、以下のようなディレクトリ構成になります。

rei:nodejs tetsuo$ tree
.
├── keys
│   ├── pass.pem
│   └── wwdr.pem
├── server.js
└── templates
    ├── icon.png
    ├── logo.png
    └── pass.json

2 directories, 6 files

これで署名をする準備は整いましたので、server.js に署名を行うコードを追加します。
ちょこちょこっと編集して、以下のようなコードとしました。

var fs = require('fs');
var crypto = require('crypto');
var util = require('util');
var path = require('path');
var child = require('child_process');

TEMPLATE_DIR = './templates/';
KEY_DIR = './keys/';

function createManifest() {
  var files = fs.readdirSync(TEMPLATE_DIR);
  var manifestHash = {};

  files.forEach(function(file) {
    var fileData = fs.readFileSync(TEMPLATE_DIR + file);

    // SHA1 ハッシュの計算
    var sha1sum = crypto.createHash('sha1');
    sha1sum.update(fileData);
    manifestHash[file] = sha1sum.digest('hex');
  });

  return manifestHash;
}

function createSignature(manifest, callback) {
  var args = [
    "smime",
    "-sign", "-binary",
    "-signer",    path.resolve(KEY_DIR, "pass.pem"),
    "-certfile",  path.resolve(KEY_DIR, "wwdr.pem"),
    "-passin",    "pass:pass"
  ];

  var sign = child.execFile("openssl", args, { stdio: "pipe" }, function(error, stdout, stderr) {
    if (error) {
      callback(new Error(stderr));
    } else {
      var signature = stdout.split(/\n\n/)[3];
      callback(null, new Buffer(signature, "base64"));
    }
  });
  sign.stdin.write(manifest);
  sign.stdin.end();
}

var manifest = JSON.stringify(createManifest());

createSignature(manifest, function(error, signature) {
  if (error) {
    throw err;
  } else {
    fs.writeFile('signature', signature, function(err) {
      if (err) throw err;
      console.log('signature created');
    });
  }
});

fs.writeFile('manifest.json', manifest, function (err) {
  if (err) throw err;
  console.log('manifest.json created');
});

これで manifest.json を生成する処理、署名(signature)を生成する処理ができました。
あとはこれらをzipファイルにまとめるだけです。node.jsでzipファイルを生成するためにnode-zipモジュールをインストールします。

rei:nodejs tetsuo$ npm install node-zip

これでzipファイルを生成するモジュールがインストールできたので、各ファイルを格納してPassファイル(pass.zip)を生成します。

var fs = require('fs');
var crypto = require('crypto');
var util = require('util');
var path = require('path');
var child = require('child_process');
require('node-zip');

TEMPLATE_DIR = './templates/';
KEY_DIR = './keys/';

function createManifest(zip) {
  var files = fs.readdirSync(TEMPLATE_DIR);
  var manifestHash = {};

  files.forEach(function(file) {
    var fileData = fs.readFileSync(TEMPLATE_DIR + file);
    zip.file(file, fileData.toString('binary'), {binary:true});

    // SHA1 ハッシュの計算
    var sha1sum = crypto.createHash('sha1');
    sha1sum.update(fileData);
    manifestHash[file] = sha1sum.digest('hex');
  });

  return manifestHash;
}

function createSignature(manifest, callback) {
  var args = [
    "smime",
    "-sign", "-binary",
    "-signer",    path.resolve(KEY_DIR, "pass.pem"),
    "-certfile",  path.resolve(KEY_DIR, "wwdr.pem"),
    "-passin",    "pass:pass"
  ];

  var sign = child.execFile("openssl", args, { stdio: "pipe" }, function(error, stdout, stderr) {
    if (error) {
      callback(new Error(stderr));
    } else {
      var signature = stdout.split(/\n\n/)[3];
      callback(null, new Buffer(signature, "base64"));
    }
  });
  sign.stdin.write(manifest);
  sign.stdin.end();
}

var zip = new JSZip();
var manifest = JSON.stringify(createManifest(zip));
zip.file('manifest', manifest);

createSignature(manifest, function(error, signature) {
  if (error) {
    throw err;
  } else {
    zip.file('signature', signature.toString('base64'), {base64:true});

    fs.writeFile('pass.zip', zip.generate({base64:false, compression: 'DEFLATE'}), 'binary', function(err) {
      if (err) throw err;
      console.log('pass.zip created');
    });
  }
});

あとはこの pass.zip をクライアントに返せばPassファイルの生成は完了です。
ここまで書けば、あとはあっさり書けると思うので適当にやってみてください。

とりあえず、Passファイルの生成までできたので、この次は「実際のサービスで使うためには…」ってことを書いてみたいと思います。

 

 

tetsuo

tetsuo の紹介

こう見えてテックファームの開発チームのえらい人。
カテゴリー: iOS タグ: , , , パーマリンク

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です