目的
OAuth2認可を使ってオンラインサービスと連携させることができるコマンドラインツールを作成中です。ユーザー認証後に認可コードをこのツールで受け取りたいと思っているので、127.0.0.1
で走るHTTPSサーバーをリダイレクト先として用意したい。
その検証として、まずローカルHTTPSサーバーをNode.jsで走らせてみました。言語は実際の使う予定のTypeScriptとなっています。
何を使う?
- Node.js
- httpsモジュール
- TypeScript
準備
まずはHTTPで地ならし
私はNode.jsローカル環境ではHTTPサーバーすらを立てた経験がないので、まずはHTTPから試してみる。
import http from 'http';
const PORT = 9090;
const HOST = '127.0.0.1';
function startHttpServer(portNumber: number, hostName: string): Promise<http.IncomingMessage> {
return new Promise((resolve, reject)=>{
http.createServer((request, response)=>{
response.writeHead(200, {
'Content-Type': 'text/plain'
});
response.end(`welcome to local page!`);
resolve(request);
}).listen(portNumber, hostName);
});
}
(async (portNumber, hostName)=>{
const req = await startHttpServer(portNumber, hostName);
})(PORT, HOST);
単純にHTTP -> HTTPSで置き換えてやってみる
単純にhttpモジュールをhttpsモジュールに置き換えて見ました。結論から言いますと以下のコードは機能しません。
import https from 'https';
import { IncomingMessage } from 'http';
const PORT = 9090;
const HOST = '127.0.0.1';
function startHttpServer(portNumber: number, hostName: string): Promise<IncomingMessage> {
return new Promise((resolve, reject)=>{
https.createServer((request, response)=>{
response.writeHead(200, {
'Content-Type': 'text/plain'
});
response.end(`welcome to local HTTPS page!`);
resolve(request);
}).listen(portNumber, hostName);
});
}
(async (portNumber, hostName)=>{
const req = await startHttpServer(portNumber, hostName);
})(PORT, HOST);
何が必要なのか?
公式ドキュメントを読んでみます。目につくのがoptions
で指定している。keyとcertです。
const options = {
key: fs.readFileSync('test/fixtures/keys/agent2-key.pem'),
cert: fs.readFileSync('test/fixtures/keys/agent2-cert.pem')
};
keyとcertはそれぞれ何かと言うと、同じく公式ドキュメントのTLSモジュールをみると下のように書いてありました。PEMフォーマットのプライベートキーですね。
key <string> | <string[]> | <Buffer> | <Buffer[]> | <Object[]>
Private keys in PEM format. PEM allows the option of private keys being encrypted. Encrypted keys will be decrypted with options.passphrase. Multiple keys using different algorithms can be provided either as an array of unencrypted key strings or buffers, or an array of objects in the form {pem: [, passphrase: ]}. The object form can only occur in an array. object.passphrase is optional. Encrypted keys will be decrypted with object.passphrase if provided, or options.passphrase if it is not.
certについて上記のページに下の説明があります。PEMフォーマットの証明書ですね。
cert <string> | <string[]> | <Buffer> | <Buffer[]>
Cert chains in PEM format. One cert chain should be provided per private key. Each cert chain should consist of the PEM formatted certificate for a provided private key, followed by the PEM formatted intermediate certificates (if any), in order, and not including the root CA (the root CA must be pre-known to the peer, see ca). When providing multiple cert chains, they do not have to be in the same order as their private keys in key. If the intermediate certificates are not provided, the peer will not be able to validate the certificate, and the handshake will fail.
幸いMacには元々openssl
が入っていたので、これを使ってプライベートキーと証明書を作成します。
こちらのブログ、「秘密鍵、公開鍵、証明書、CSR生成のOpenSSLコマンドまとめ」 が大変参考になりました。このブログにあった「秘密鍵と自己署名証明書を一括で作成したい」を使います。
対話形式で入力する組織情報はどれが必須で、どれが省略可能なのかまでは調べていません。全部省略したらエラーになってしまいました。
$ openssl req -x509 -sha256 -nodes -days 9999 -newkey rsa:2048 -keyout localhttps_key.pem -out localhttps_cert.pem
Generating a 2048 bit RSA private key
............................................+++
...+++
writing new private key to 'localhttps_key.pem'
-----
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) []:JP
State or Province Name (full name) []:Tokyo
Locality Name (eg, city) []:Tokyo
Organization Name (eg, company) []:FiftyFifty
Organizational Unit Name (eg, section) []:FortyNine
Common Name (eg, fully qualified host name) []:localhost
Email Address []:.
このコマンドが成功すると、localhttps_key.pem
と言うプライベートキーとlocalhttps_cert.pem
と言う証明書ファイルが生成されます。
機能するコード例
プライベートキーと証明書とをオプションで指定したら、HTTPSでアクセスできるようになりました。
実際にアクセすると、Chromeブラウザは強い警告を出してきますが、許可すればなんとかアクセスはできます。
import fs from 'fs';
import https from 'https';
import { IncomingMessage } from 'http';
const PORT = 9090;
const HOST = '127.0.0.1';
const options = {
key: fs.readFileSync('./localhttps_key.pem'),
cert: fs.readFileSync('./localhttps_cert.pem')
}
function startHttpServer(portNumber: number, hostName: string): Promise<IncomingMessage> {
return new Promise((resolve, reject)=>{
https.createServer(options, (request, response)=>{
response.writeHead(200, {
'Content-Type': 'text/plain'
});
response.end(`welcome to local HTTPS page!`);
resolve(request);
}).listen(portNumber, hostName);
});
}
(async (portNumber, hostName)=>{
const req = await startHttpServer(portNumber, hostName);
console.log(`${JSON.stringify(req.headers)}`);
})(PORT, HOST);