Let's EncryptとISPmanagerの統合の例を使用して、rsaキーからJWSとJWKを生成する

みなさんこんにちは! 私の名前はDmitry Smirnovです。ISPsystemの開発者であり、ISPmanager 5パネルでLet's Encryptと統合を担当しています。 プラグインの開発がどのように進み、どのように変化し、現在の状態になったかを説明します。 テキストから、rsaキーからJWSとJWKを生成し、ACME v01の証明書を暗号化する方法を学びます。 興味があれば、猫へようこそ。



画像



1.0を暗号化しましょう



プラグインの最初のバージョンはあらゆる点で完璧でした。 彼女の成功は、彼女の記念碑的な崩壊に匹敵します(そう、マトリックスの脚本家もこの記事を読みました)。 それから、タスクは次のように設定されました: letsencrypt.orgがそれで何かをします。 まあ、私はやった。



最初は、プラグインは公式のLet's Encryptクライアントをgithubから引き出し、直接それと連携していました。 はい、そしてユーザーフレンドリーで、控えめに言っても、そうではありませんでした。 ドメインなし? 何も注文しません。 エイリアスは解決されませんか? 四捨五入。 証明書を発行するための準備作業はすべてユーザーの負担となり、エラーがあると証明書の受け取りに失敗します。



言うまでもなく、プラグインは改訂のために返されました。 こうして、インターネットセキュリティと顧客重視の素晴らしい世界への私の魅力的な旅が始まりました。



Let's Encrypt 2.0



2番目の開発アプローチの前に、いくつかのタスクを策定しました。





もちろん、私にとっての主要なポイントは最初のポイントでした。 公式文書を用意して、開発を始めました。



Let's Encrypt( LE )サービスは、Internet Security Research Group( ISRG )によって作成されました。 特に彼のために、 ISRGは自動証明書管理環境( ACME )プロトコルを開発しました。 それ自体で証明書を取得するプロセスは、 LEサービスへのPOSTリクエストであり、リクエスト本文はJSON Web Signature( JWS )でラップされたJSONとして表されます。



取得する手順は次のようになります。





順番に始めましょう。



ユーザー登録と承認



ユーザーを作成して認証するには、pem形式のrsaキーのペアが必要です。これは、後でJWSを構築するための基礎として機能します。



openssl genrsa -out private.pem 2048
      
      





ACME v01と通信するためのPOST要求データ構造:



 { "header": jws, //JSON Web Signature "protected": Base64Url(jws + Replay-Nonce), //Nonce —    "payload": Base64Url(payload), // "signature": Base64Url(sign(protected.payload, private.pem)) // }
      
      





ここでは、3つのことに注目する価値があります。 最初に、Replay-Nonceはacme-v01.api.letsencrypt.org/directory応答のヘッダーを返します。 第二に、ペイロードはJSONです。実際には、この特定のケースでACMEに何を望むかを説明します。 第三に、 JWSは次の形式のJSONです(他のアルゴリズムによって署名を取得する方法があることを規定します。ここに1つ、おそらく最も単純なものを示します)。



 { "alg" : "RS256", "jwk" : { //JSON Web Key "kty" : "RSA", // key type --        "e" : "...", //      HexToBase64UrlEnodedByte "n" : "..." // modulus    HexToBase64UrlEnodedByte } }
      
      





JWKのデータをどこで取得するかという疑問が生じました。 インターネットでの短い検索は報われ、私は解読された形式のペムキーのペア自体を見る簡単な方法を見つけました。 以下に例を示します。



 openssl rsa -text -noout < private.pem
      
      





最も短縮された形式のコマンド出力:



 Private-Key: (2048 bit) modulus: 00:a8:c5:cc:9c:24:9b:d1:8d:9a:67:81:4d:1f:57: ... 8c:45:51:9e:26:fc:12:35:9e:a0:10:fd:80:94:cc: 09:a5 publicExponent: 65537 (0x10001) privateExponent: ... prime1: ... prime2: ... exponent1: ... exponent2: ... coefficient: ...
      
      





ここにありますので、データが必要です、それを取ります-私はしたくありません。 私はそれを取り、適切な種類に持ち込み、 JWSを作成しました。 しかし、残酷な失望が私を待っていました:署名が間違っていました。 これはすべて、インターネット上の情報の検索、デバッグ、絶望に数時間かかりました。 それでも、答えは表面化した。



最初の2つのゼロは、ASN.1を使用して整数をエンコードするときに現れるアーティファクトであることが判明しましたが、 JWSでの処理と貼り付けの準備が整った形でモジュールを取得する別の方法があります。



 openssl rsa -noout -modulus < private.pem
      
      





出来上がり:



 Modulus=A8C5CC9C249BD18D9A67814D1F57...8C45519E26FC12359EA010FD8094CC09A5
      
      





証明書を取得する



それでは、リクエストとペイロードを見ていきましょう。 まず、 acme-v01.api.letsencrypt.org/directoryの GETを呼び出しましょう 。 結果のJSONから



 { "key-change": "https://acme-v01.api.letsencrypt.org/acme/key-change", "meta": { "terms-of-service": "https://letsencrypt.org/documents/LE-SA-v1.2-November-15-2017.pdf" }, "new-authz": "https://acme-v01.api.letsencrypt.org/acme/new-authz", "new-cert": "https://acme-v01.api.letsencrypt.org/acme/new-cert", "new-reg": "https://acme-v01.api.letsencrypt.org/acme/new-reg", "revoke-cert": "https://acme-v01.api.letsencrypt.org/acme/revoke-cert", "zH_Sr0qwmwM": "https://community.letsencrypt.org/t/adding-random-entries-to-the-directory/33417" }
      
      





ユーザー同意アドレスと証明書要求のアドレスを取得します。



登録



 url = directory["new-reg"] payload = { "resource": "new-reg", "agreement": directory["meta"]["terms-of-service"] }
      
      





ログイン



ここで、証明書を発行する各ドメイン名について、承認を行う必要があります。 応答では、使用可能な名前所有権チェックのリストを取得します。



 url = directory["new-authz"] payload = { "resource": "new-authz", "identifier": { "type":"dns", "value": "name" } }
      
      





チェック



ドメイン名の確認に進みましょう。 ACME v01では、http、dns、tlsの3つのオプションを使用できます。 私たちの選択は、最も簡単で手頃な価格である最初の方法に基づいています。 一番下の行は簡単です:.well-known / acme-challengeサブディレクトリは、検証トークンが配置されるドメインディレクトリに作成する必要があります-検証で指定された名前のファイル。



トークン自体には、文字列token_name。Base64Url(jwk_print)が含まれている必要があります-これは、いわゆる認証キーになります。 OpenSSLコマンドを使用して指紋を簡単に取得できます。



 echo jwk | openssl dgst -sha256 -binary | base64url
      
      





bashが怖いのは、base64url関数を自分で書く必要があるからです。



それがどれほど簡単だったとしても、私は何時間も動けなくなった。 子供の間違いは最悪です。 opensslでは、 改行文字がJWKの最後に渡されました。 このデータに注意してください、印刷はきれいである必要があります '' :)。



リクエストの本文は次のようになります。



 payload = { "resource": "challenges", "keyAuthorization":   }
      
      





そして、チェックのJSONから取得したURL。



私は、クラスターの必要なノードにトークンを配布し、その後それらを削除するトリッキーなメカニズムを作成しましたが、それは後に余分な作業であることが判明しました(これについては後で詳しく説明します)。



証明書の問題



 url = directory["new-cert"] payload = { "resource": "new-cert", "csr": csr }
      
      





そして、見よ、最初のLE証明書が正常に受信されました!



顧客重視



通常のユーザーが待っていた問題を解決するために残った。 ドメインエイリアスがまだ解決されていない場合、避けられない同期証明書の問題を回避するにはどうすればよいですか? ユーザーは注文時にすぐに証明書を受け取るが、自己署名する必要があると判断しました。



自己署名証明書を発行し、ドメインに接続して、LEから内部証明書の注文を登録します。 5分ごとに受信手順を開始します。 失敗した場合は、冷静に次の試行を待ちます。 考えられるすべての問題を解決するためにユーザーに24時間を与えた後、降伏して発行キューから証明書を取り消します。



LEからの既製の新鮮な証明書は、古い自己署名の代わりに残されます。 以上です。 これは、Let's Encryptを使用した統合プラグインがどのように光を見たのかです。



難しさ



証明書の発行中に発生する可能性のあるすべての問題を予測しようとしましたが、それでもなお、私たちの探求する視線を逃したいくつかのエラーがありました。 主な問題は、多数のユビキタスな.htaccess構成ファイルでした。 非常に多くの場合、ドメインディレクトリに慎重に配置された検証トークンが、単にアクセス不能であることが判明しただけです。 そして、ユーザーにとって唯一の方法は、一時的に設定を無効にすることでした。



数か月後、ドメインディレクトリにトークンを送信するメカニズム自体が正当化されないことが明らかになりました。 ツールによって作成されたすべてのWebドメインパネルについて、ディレクトリ/ usr / local / mgr5 / www / letsencryptにつながるエイリアス/.well-known/acme-challenge/の追加を開始しました。 検証のためにトークンが配置されるようになったのはその中にあり、その後アクセスエラーを最小限に抑えました。



DNS検証



ドメインゾーンのTXTレコードを確認するには、わずか6か月前に登場しました。 1つを除いて、実質的に準備のトリックは発生しませんでした。 TXTレコードの場合、トークンに書き込んだ行はコマンドで実行する必要があります



 echo _ | openssl dgst -sha256 -binary | base64url
      
      





今日は以上です。 プラグインのACME v02への移行および次のリリースでのワイルドカード証明書のサポートについてお読みください。



All Articles