
やる気
ロシア語を話すインターネットでは、OpenSSL APIライブラリに関する情報を見つけることは困難です。 WebサーバーまたはOpenVPNサーバーの自己署名証明書を操作するためのコンソールコマンドの使用には、多くの注意が払われます。
このアプローチは、1時間に2、3の証明書を作成する必要がある場合に適しています。 また、毎分数百をすぐに作成する必要がある場合はどうなりますか? または、スクリプトを記述して、コンソールからの出力を解析しますか? そして、プロセスでエラーが発生した場合はどうなりますか?
APIを使用すると、証明書の生成、検証、および署名がはるかに簡単になります。 すべての作業段階でエラーを制御および処理し、追加の証明書パラメーター(コンソールからすべてのパラメーターを設定できるわけではないため)を指定して微調整する機会があります。
それとは別に、ネットワークコンポーネントに注目する価値があります。 証明書があり、ディスク上にある場合、それは役に立ちません。
残念ながら、SSLサーバーの構成、データを受信するためのSSLクライアントの構成方法に関するロシア語のドキュメントはほとんどありません。 公式ドキュメントは完全ではなく、すぐにライブラリの操作に関与できるほど優れていません。 すべての機能が詳細に説明されているわけではありません。パラメーター、クリーンアップする必要があるシーケンス、および正確に何を、ライブラリーがそれ自体で削除するかを実験する必要があります。
この記事は、クライアント/サーバーアプリケーションを実装する際のOpenSSLライブラリに関する私の経験をまとめたものです。 ここで説明されている機能は、デスクトップとAndroidデバイスの両方で機能します。 C / C ++コードを含むリポジトリが記事に添付されているため、説明されている機能の動作を確認できます。
目的
 新しいライブラリやテクノロジーを勉強するとき、私は新しい機能の助けを借りて問題を解決しようとします。 この場合、 MITMを実行してみてください 
      
        
        
        
      
      HTTPSサーバーへのトラフィックをインターセプトします。 
 プログラムの要件を策定します。 
      
        
        
        
      
     ポート接続を待つ(SSLサーバー) 
      
        
        
        
      
     着信接続が表示されたら: 
- HTTPSサーバーに接続する
- サーバーへのクライアントリクエストの読み取り
- クライアントから読み取ったデータをサーバーに転送する
- サーバー応答の読み取り
- サーバーへの応答をクライアントに送信
- 接続をリセット
  SSLサーバーがあるため、認証局の証明書とサーバーの証明書が必要になります。 
      
        
        
        
      
     このデータをプログラムで生成すると、CA証明書がプログラムの作業フォルダー内のファイルにアップロードされます。 
開発は、Ubuntu、他のツールで行われます:GCC 5.4.0コンパイラー、OpenSSL 1.0.2、curl 7.52.1、CMake 3.8.1(パッケージに含まれていないもののみ)。
アプリケーションにリクエストを送信するには、コンソールからcurlを使用します。 CA証明書を指定する必要があるため、コマンドは次のようになります。
curl --cacert ca.crt -v https://127.0.0.1:5566 -H "Host: taigasystem.com"
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
     curlがHTTP要求を正しく構成するには、 Hostヘッダーが必要です。 これがないと、サーバーはエラーで応答します。
作業の開始と終了
OpenSSLライブラリを使用するには、初期化する必要があります。 次のコードを使用します。
 #include <openssl/bio.h> #include <openssl/ssl.h> #include <openssl/err.h> ... void InitOpenSSL() { OpenSSL_add_all_algorithms(); ERR_load_BIO_strings(); ERR_load_crypto_strings(); SSL_load_error_strings(); SSL_library_init(); }
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
      
      アプリケーションを完了する前に、ライブラリを消去する必要があります。これには、次のコードを使用できます。
 void ClearOpenSSL() { EVP_cleanup(); CRYPTO_cleanup_all_ex_data(); ERR_remove_thread_state(NULL); ERR_free_strings(); }
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
      
      コンテキスト
ほとんどのOpenSSLライブラリ操作にはコンテキストが必要です。 この構造には、使用されるアルゴリズム、パラメーター、その他のデータが格納されます。 関数を使用して作成されます:
 SSL_CTX *SSL_CTX_new(const SSL_METHOD *method);
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
      
       この関数に渡すことができるメソッドのリストは非常に広範囲に渡りますが、 ドキュメントには、サーバーにはSSLv23_client_method()
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    を、クライアントにはSSLv23_server_method()
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    を使用する必要があるとSSLv23_client_method()
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    れています。 
ライブラリは、クライアントとサーバーがサポートする最も安全なプロトコルを自動的に選択します。
クライアントのコンテキストを作成する例を次に示します。
 SSL_CTX *ctx = NULL; ctx = SSL_CTX_new(SSLv23_client_method()); if (ctx == NULL) { //  }
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
      
       コンテキストを正しく削除するには、 SSL_CTX_free
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    関数を使用します。 
 コンテキストを削除する必要があるたびにSSL_CTX_free
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    を使用することはあまり好きではありません。  delete関数でスマートポインターを使用するか、 RAIIクラスで構造をラップすることができます。 
 std::shared_ptr<SSL_CTX> m_ctx(ctx, SSL_CTX_free);
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
      
      エラー処理
ほとんどのOpenSSLライブラリ関数は、成功の兆候として1を返します。 通常のエラーチェックコードは次のとおりです。
 if (SSL_CTX_load_verify_locations(ctx, fileName, NULL) != 1) { //  }
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
      
       ただし、これでは不十分な場合があり、問題のより詳細な説明が必要な場合があります。 これを行うために、 OpenSSLは各スレッドで個別のメッセージキューを使用します。 キューからエラーコードを取得するには、 ERR_get_error()
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    関数を使用します。 
 エラーコード自体はユーザーにはあまり明確ではないため、 ERR_error_string
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    関数を使用してエラーコードの文字列表現を取得できます。 関数が0を返す場合、エラーがないことを意味します。 
エラーコードによってエラーを説明する文字列を取得する例を次に示します。
 #include <openssl/err.h> ... // -   OpenSSL std::cerr << ERR_error_string(ERR_get_error(), NULL) << std::endl; ...
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
      
        ERR_error_string
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    関数の2番目のパラメーターは、少なくとも120文字の長さのバッファーへのポインターです。 指定しない場合、静的バッファが使用され、この関数が呼び出されるたびに上書きされます。 
個々のスレッドごとに個別のエラーメッセージキューが作成されることに注意してください。
キー
次に、OpenSSLサーバーを整理するために、証明機関の証明書とサーバー証明書を作成する必要があります。 それぞれについて、署名用のキーを作成する必要があります。
作成
  OpenSSLはEVP_PKEY
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    構造体を使用して、秘密/公開キーペアを格納します。 この構造は次のように作成されます。 
 EVP_PKEY *pkey = NULL; pkey = EVP_PKEY_new(); if (pkey == NULL) { //  }
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
      
      EVPの詳細については、 こちらをご覧ください 。
  EVP_PKEY_new
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    関数EVP_PKEY_new
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    関数EVP_PKEY_free
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    はメモリを解放し、構造EVP_PKEY
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    を削除します。 
 次に、 RSAを生成するためにBIGNUM
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    構造を準備する必要があります(この構造の詳細については、 こちらをご覧ください ) 
 BIGNUM *big = NULL; big = BN_new(); if (big == NULL) { //  } else if (BN_set_word(big, RSA_F4) != 1) { //  BN_free(big); }
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
      
        BN_set_word
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    関数は、 BIGNUM
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    構造のサイズを設定します。 有効な値はRSA_3
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    とRSA_F4
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    で、後者が望ましいです。 
 今こそ鍵生成の番です。 これを行うには、 RSA
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    構造を作成します。 
 RSA *rsa = NULL; rsa = RSA_new(); if (rsa == NULL) { //  }
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
      
      キー生成自体:
 if (RSA_generate_key_ex(rsa, 4096, big, NULL) != 1) { //  }
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
      
      4096は、受け取りたいキーのサイズです。
  EVP_PKEY
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    構造に新しいキーを書き込むことで、キーの生成を終了します。 
 if (EVP_PKEY_assign_RSA(pkey, rsa) !=1) { //  }
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
      
      PEMフォーマット
PEMは、キーと証明書を保存するためのかなり単純な形式です。 フォームのレコードが順番に保存されるテキストファイルです。
 -----BEGIN RSA PRIVATE KEY----- MIIJJwIBAAKCAgEAvNwgYmIyfvY6IsVZwRCkAHTOhwE3Rp/uNcUoTcPl5atOwPVW JLY3odYmILsa8se7B/aNNzO7AlvXwlzxinQ3AF7l37LqGzf8v16TFVN4kit8vrq0 V9bBXHpiWH+YQT4gBVmSkwqEMZ/wQlUOIxz4Q2M7cXRu4fRe3rt3kGHCPJ66Ybax yEp6nfdK8IKsyxqAXjBkqfC5rkdw2n7UAd/OnPRCDowyvythDb8jR1LkbJjlIatK .... yajhmBDpS11hzuWHhDmpjbrV79OMRzKQAWBKRubObtGIsFB2CzbabusV+oq/Y78y OxriZYqoRv3WB5GH/pPO9w1ptveddLU33NVBSRfFS1jyqyj/1CqXlE4gcQ== -----END RSA PRIVATE KEY----- -----BEGIN CERTIFICATE----- MIIFkTCCA3mgAwIBAgIJAMPIqA2oVd/SMA0GCSqGSIb3DQEBCwUAMF8xCzAJBgNV BAYTAlJVMQ8wDQYDVQQIDAZNb3Njb3cxDzANBgNVBAcMBk1vc2NvdzEUMBIGA1UE ... bt9NHGnCxYcParG+YqU5UTUrCUGUfnZhJAX+qkgsVSC5c81Tk0VXTQx3EiEvdzV+ wUX9LMRLIxjy1D5AO6a29LkzNAvw+iFm36VO+ssdkJW4Q6MAYA== -----END CERTIFICATE-----
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
      
      ヘッダーの先頭と末尾、および終了行の文字数-----が同じでなければならないことに注意してください。
 この形式については、ここで詳しく説明します。 
      
        
        
        
      
      -RFC1421パートI:メッセージの暗号化と認証の手順 
      
        
        
        
      
      -RFC1422パートII:証明書ベースのキー管理 
      
        
        
        
      
      -RFC1423パートIII:アルゴリズム、モード、および識別子 
      
        
        
        
      
      -RFC1424パートIV:主要な認証と関連サービス 
PEM形式でキーを書く
  EVP_PKEY構造に公開キーと秘密キーのペアがあり、それらをファイルに書き込むには、 PEM_write_PrivateKey
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    およびPEM_write_PUBKEY
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    使用するとしPEM_write_PUBKEY
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
     。 
これらの関数の使用例を次に示します。
 FILE *f = fopen("server.pem", "wb"); if (!PEM_write_PrivateKey(f, key, NULL, NULL, 0, 0, NULL)) { //  fclose(f); } else if (!PEM_write_PUBKEY(f, key)) { //  fclose(f); } fclose(f);
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
      
      機能について説明する価値があります。
 int PEM_write_PrivateKey(FILE *fp, EVP_PKEY *x, const EVP_CIPHER *enc, unsigned char *kstr, int klen, pem_password_cb *cb, void *u);
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
      
       ここで、 const EVP_CIPHER *enc
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    は、秘密キーを保存する前に暗号化するための暗号化アルゴリズムへのポインターです。 
      
        
        
        
      
     たとえば、 EVP_aes_256_cbc()
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    は、「CBCに256ビットキーを持つAES」を意味します。 
 暗号化アルゴリズムはたくさんあり 、いつでもできる 
      
        
        
        
      
     お好みに合わせて選択してください。 関連する定義はopenssl/evp.h
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
     
      
        
        
        
      
      unsigned char *kstr
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    は、キーの暗号化のためのパスワードを含む行へのポインター、およびint klen
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    この行の長さを受け取ることを想定しています。 
  kstr
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    とklen
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    場合、パラメーターcb
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    とu
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    無視されます。ここで、 klen
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    の形式の関数へのポインターです。 
 int cb(char *buf, int size, int rwflag, void *u);
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
      
        -buf-パスワードを書き込むためのバッファへのポインタ 
      
        
        
        
      
      -サイズ-最大パスワードサイズ(つまり、バッファーサイズ) 
      
        
        
        
      
      -rwflagは読み取り時は0、書き込み時は1 
関数の実行結果は、パスワードの長さ、またはエラーの場合は0です。
 両方の関数のvoid *u
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    パラメーターは、追加データを送信するために使用されます。 たとえば、 GUIアプリケーションのウィンドウへのポインタとして。 
.pemファイルからキーをロードする
 キーは、 PEM_read_PrivateKey
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    およびPEM_read_PUBKEY
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    を使用してPEM_read_PrivateKey
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
     PEM_read_PUBKEY
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
     。 両方の関数には同じパラメーターと戻り値があります。 
 EVP_PKEY *PEM_read_PUBKEY(FILE *fp, EVP_PKEY **x, pem_password_cb *cb, void *u); EVP_PKEY *PEM_read_PrivateKey(FILE *fp, EVP_PKEY **x, pem_password_cb *cb, void *u);
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
      
       ここで: 
      
        
        
        
      
      FILE *fp
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    ファイル記述子を開く 
      
        
        
        
      
      EVP_PKEY **x
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    上書きされる構造 
      
        
        
        
      
      pem_password_cb *cb
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    キー復号化パスワードを取得する関数 
      
        
        
        
      
      void *u
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
     \0
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    終わるキーパスワードの文字列 
キーを解読するためのパスワードを取得する関数の例を次に示します。
 int pass_cb(char *buf, int size, int rwflag, void *u) { int len; char *tmp; if (rwflag == 1) std::cout << "   " << (char*)u << ": "; else std::cout << "    : "; std::string pass; std::cin >> pass; if (pass.empty() || pass.length() <=0) return 0; len = pass.length(); if (len > size) len = size; memcpy(buf, pass.c_str(), len); return len; }
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
      
      以下は、ファイルから暗号化されていない秘密鍵をロードする方法の例です。
 FILE *f = NULL; f = fopen(fileName.c_str(), "rb"); if (f == NULL) { //  } EVP_PKEY *key = NULL; key = PEM_read_PrivateKey(f, NULL, NULL, NULL); if (key == NULL) { //  } fclose(f);
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
      
      メモリからキーをロードする
 キーまたは証明書を定数としてプログラムに保存すると便利な場合があります。 そのような場合、タイプBIOの構造を使用できます。 この構造とその関連機能は、 FILE
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
     I / O機能を繰り返します。 
これは、メモリからキーをロードする方法です。
 const char *key = "-----BEGIN RSA PRIVATE KEY-----\n" "MIIJKAIBAAKCAgEA40vjOGzVpuJv+wIfNBQSr9U/EeRyvSy/L6Idwh799LOPIwjF\n" ..... "zkxvkGMPBY3BcSPjipuydWTt8xE8MOe0SmEcytHZ/DifwF9qyToDlTFOUN8=\n" "-----END RSA PRIVATE KEY-----"; BIO *buf = NULL; buf = BIO_new_mem_buf(key, -1); if (buf == NULL) { //  } EVP_PKEY *pkey = NULL; pkey = PEM_read_bio_PrivateKey(buf, NULL, NULL, NULL); if (pkey == NULL) { //  }
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
      
      証明書リクエスト
キーを作成できるようになったので、証明書を作成する方法を見てみましょう。 証明書は、自己署名するか、証明機関によって署名することができます。 証明機関によって署名された証明書を取得するには、証明書要求( CSR )を作成し、それを証明機関に送信する必要があります。 応答で、彼は署名された証明書を送信します。
自己署名証明書または独自の証明機関の証明書を作成する場合は、 CSRを作成する必要はありません。 証明書セクションに直接アクセスできます。
作成
証明書署名要求 (CSR)は、証明書の作成者が証明機関(CA)に送信するメッセージまたは要求であり、公開キー、発行国、および作成者のデジタル署名に関する情報が含まれています。
  CSRを作成するには、以前に作成したEVP_PKEY
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    キーが必要です。 それはすべて、 CSR構造にメモリを割り当てることから始まります。 
 X509_REQ *req = NULL; req = X509_REQ_new(); if (req == NULL) { //  }
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
      
        X509_REQ_new
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    の逆関数はX509_REQ_free
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    です。 
次に、証明書のバージョンを設定する必要があります。 この場合、バージョンは2です。
 if (X509_REQ_set_version(req, 2) != 1) { //  X509_REQ_free(req); }
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
      
      X.509標準によれば、このバージョンは証明書バージョンよりも1つ小さい必要があります。 すなわち 証明書バージョン3には、番号2を使用します。
ここで、リクエスト作成者データを設定します。 次のフィールドを使用します。
  -C -2文字の国コード、たとえばRU 
      
        
        
        
      
      -ST-地域、この場合はモスクワ 
      
        
        
        
      
      -L-都市、再びモスクワ 
      
        
        
        
      
      -O-組織、例: Taigasystem 
      
        
        
        
      
      -CNはドメイン名です。taigasystem.comが私たちのためにあります 
これは、これらのフィールドがリクエストで設定される方法です。
 X509_NAME *name = X509_REQ_get_subject_name(req); if (X509_NAME_add_entry_by_txt(name, "C", MBSTRING_ASC, (const unsigned char *)"RU", -1, -1, 0) != 1) { //  X509_REQ_free(req); } if (X509_NAME_add_entry_by_txt(name, "ST", MBSTRING_ASC, (const unsigned char *)"Moscow", -1, -1, 0) != 1) { //  } if (X509_NAME_add_entry_by_txt(name, "L", MBSTRING_ASC, (const unsigned char *)"Moscow", -1, -1, 0) != 1) { //  } if (X509_NAME_add_entry_by_txt(name, "O", MBSTRING_ASC, (const unsigned char *)"Taigasystem", -1, -1, 0) != 1) { //  } if (X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_ASC, (const unsigned char *)"taigasystem.com", -1, -1, 0) != 1) { //  }
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
      
       最初に、CSR要求構造からX509_NAME
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    構造を取得し、その値を設定することに注意してください。 
次に、このリクエストの公開鍵を設定する必要があります。
 if (X509_REQ_set_pubkey(req, key) != 1) { //  }
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
      
      最後の仕上げは、リクエストの署名です。
 if (X509_REQ_sign(req, key, EVP_sha256()) <= 0) { //  }
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
      
       他のOpenSSL関数とは異なり、 X509_REQ_sign
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
     は 
      
        
        
        
      
     成功した場合は1ではなく、バイト単位での署名のサイズ、エラーの場合は0。 
これで証明書要求の準備ができました。
CSRをファイルに保存
  CSRをファイルに保存するのは非常に簡単です。 ファイルを開いてから、 PEM_write_X509_REQ
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    関数を呼び出す必要があります。 
 FILE *f = NULL; f = fopen("server.csr", "wb"); if (PEM_write_X509_REQ(f, csr) != 1) { //  fclose(f); } fclose(f);
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
      
      その結果、 server.csrファイルで次のテキストを取得します。
 -----BEGIN CERTIFICATE REQUEST----- MIICnzCCAYcCAQEwWjELMAkGA1UEBhMCUlUxDzANBgNVBAgMBlJ1c3NpYTEPMA0G ... fbzFJ6EM00mbyr472lEXZpvdZgBCfxpkNDyp9nsiIQf0EyC05MgufOAKDT/fGQfa 4gWK -----END CERTIFICATE REQUEST-----
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
     
      ファイルからCSRをダウンロード
CSRをロードするには、次の機能を使用する必要があります。
 X509_REQ *PEM_read_X509_REQ(FILE *fp, X509_REQ **x, pem_password_cb *cb, void *u); X509_REQ *PEM_read_bio_X509_REQ(BIO *bp, X509_REQ **x, pem_password_cb *cb, void *u);
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
      
      最初はファイルからCSRをロードし、2番目はメモリからCSRをロードできます。
 関数データパラメータは、PEMファイルからキーをロードするのと似ています 。 
      
        
        
        
      
     ただし、 pem_password_cb
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    とu
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    は例外として無視されます。 
認証
X.509証明書
X.509は、 rfc2459で標準化されたASN.1言語のバリエーションの1つです。
X.509形式の詳細については、 こちらとこちらをご覧ください 。
CSRを使用しない証明書の生成
CSRを使用せずに証明書を生成できます。 これは、 CAの作成に役立ちます。
  CSRなしで証明書を生成するには、 EVP_PKEY
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    構造内に公開キーと秘密キーのペアが必要です。 証明書構造にメモリを割り当てることから始めます。 
 X509 *x509 = X509_new(); if (!x509) // 
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
      
        X509_new
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    の逆はX509_free
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    です。 
証明書はCSRリクエストと同じ方法で作成されますが、1つだけ違いがあります。バージョンと発行者のデータに加えて、証明書のシリアル番号を指定する必要があります。
 証明書データにアクセスするには、他の機能も使用する必要があります。 
      
        
        
        
      
      X509_set_version
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    ではなくX509_REQ_set_version
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
     
      
        
        
        
      
      X509_get_subject_name
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    代わりにX509_REQ_get_subject_name
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
     
      
        
        
        
      
      X509_set_pubkey
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    代わりにX509_REQ_set_pubkey
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
     
      
        
        
        
      
      X509_sign
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    代わりにX509_REQ_sign
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
     
したがって、これらのオブジェクトまたはそれらの関数が対象とするオブジェクトの名前で区別することは非常に簡単になります。
これで、証明書のシリアル番号を設定できます。
 ASN1_INTEGER *aserial = NULL; aserial = M_ASN1_INTEGER_new(); ASN1_INTEGER_set(aserial, 1); if (X509_set_serialNumber(x509, aserial) != 1) { //  }
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
      
      新しい証明書ごとに、新しいシリアル番号を作成する必要があります。
次に、証明書の有効期間を設定します。 これには2つのパラメーターが設定されます-証明書の有効期間の開始と終了:
 if (!(X509_gmtime_adj(X509_get_notBefore(cert), 0))) { //  X509_free(cert); } // 31536000 * 3 = 3 year valid period if (!(X509_gmtime_adj(X509_get_notAfter(cert), 31536000 * 3))) { //  X509_free(cert); }
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
      
       証明書のX509_get_notBefore
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    は、発行された瞬間になりますX509_get_notBefore
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    関数の値は0です。 証明書の寿命はX509_get_notAfter
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    関数によって設定されます。 
最後の仕上げは、秘密鍵を使用した証明書の署名です。
 EVP_PKEY *key; // Not null if (X509_sign(cert, key, EVP_sha256()) <=0) { long e = ERR_get_error(); if (e != 0) { //  X509_free(cert); } }
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
      
       ここには興味深い機能があります: X509_sign
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    関数は、すべてがうまくいった場合はバイト単位で署名のサイズを返し、エラーの場合は0を返します。 エラーがない場合でも、関数はゼロを返すことがあります。 したがって、ここでは追加のエラーチェックを導入する必要があります。 
CSRを使用した証明書の生成
CSR証明書を生成するには、証明書に署名するためのCA秘密鍵、発行者データを指定するためのCA証明書、およびCSRリクエスト自体が必要です。
証明書自体の作成とバージョンと番号のインストールは、CSRなしの証明書の場合と同じです。 CSRリクエストからパブリッシャーデータを抽出し、証明書にインストールする必要がある場合に違いが現れます。
 X509_REQ *csr; //not null X509_NAME *name = NULL; name = X509_REQ_get_subject_name(csr); if (name == NULL) { //  X509_free(cert); } if (X509_set_subject_name(cert, name) != 1) { //  X509_free(cert); }
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
      
      その後、証明書発行者のデータを設定する必要があります。 これにはCA証明書が必要です。
 X509 *CAcert; //not null name = X509_get_subject_name(CAcert); if (name == NULL) { //  X509_free(cert); } if (X509_set_issuer_name(cert, name) != 1) { //  X509_free(cert); }
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
      
        X509_set_subject_name
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    を使用してCSRからデータを設定し、 X509_set_subject_name
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    を使用してCAデータをX509_set_issuer_name
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    していることがX509_set_issuer_name
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    ます。 
 次のステップは、 CSRから公開キーを取得して、新しい証明書にインストールすることです。 
      
        
        
        
      
     キーを設定することに加えて、 CSRがこのキーで署名されたかどうかをすぐに確認できます。 
 // Get pub key from CSR EVP_PKEY *csr_key = NULL; csr_key = X509_REQ_get_pubkey(csr); if (csr_key == NULL) { //  } // Verify CSR if (X509_REQ_verify(csr, csr_key) !=1) { //  X509_free(cert); } // Set pub key to new cert if (X509_set_pubkey(cert, csr_key) != 1) { //  X509_free(cert); }
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
      
      これで、証明書のシリアル番号を設定できます。
 ASN1_INTEGER *aserial = NULL; aserial = M_ASN1_INTEGER_new(); ASN1_INTEGER_set(aserial, 1); if (X509_set_serialNumber(cert, aserial) != 1) { //  }
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
      
      最後の仕上げは、 CA秘密鍵を使用して証明書に署名することです。
 EVP_PKEY *CAkey; // Not null if (X509_sign(cert, CAkey, EVP_sha256()) <=0) { long e = ERR_get_error(); if (e != 0) { //  X509_free(cert); } }
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
      
      新しい証明書に署名すると、準備が整います。
X.509証明書の保存
保存は非常に簡単です。
 X509 *cert; ... FILE *f = NULL; f = fopen("server.crt", "wb"); if (!PEM_write_X509(f, cert)) { //  fclose(f); } fclose(f);
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
      
      X.509証明書をダウンロードする
ダウンロードは2つの機能を使用して行われます。
 X509 *PEM_read_X509(FILE *fp, X509 **x, pem_password_cb *cb, void *u); X509 *PEM_read_bio_X509(BIO *bp, X509 **x, pem_password_cb *cb, void *u);
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
      
      パラメータは上で説明されます 。
ネットワーク部
お客様
SSLソケットを使用したホストへの接続は、通常のTCP接続と大差ありません。
まず、サーバーへのTCP接続を作成する必要があります。
 //  IP   struct hostent *ip = nullptr; ip = gethostbyname(host.c_str()); if (ip == nullptr) { //  } //  int sock = socket(AF_INET, SOCK_STREAM, 0); if (sock == -1) { //  } struct sockaddr_in dest_addr; memset(&dest_addr, 0, sizeof(struct sockaddr_in)); dest_addr.sin_family = AF_INET; dest_addr.sin_port = htons(port); dest_addr.sin_addr.s_addr = *(long *)(ip->h_addr); //: if (connect(sock, (struct sockaddr *)&dest_addr, sizeof(struct sockaddr)) == -1) { //   }
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
      
      次に、クライアントSSLコンテキストを作成する必要があります。
 const SSL_METHOD *method = SSLv23_client_method(); SSL_CTX *ctx = NULL; ctx = SSL_CTX_new(method); if (ctx == NULL) // 
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
      
      次に、受信したソケットをSSL構造にインストールする必要があります。 コンテキストから取得されます。
 SSL *ssl = SSL_new(ctx); if (ssl == NULL) { //  } if (SSL_set_fd(ssl, sock) != 1) { //  }
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
      
      最後の仕上げは接続そのものです。
 if (SSL_connect(ssl) != 1) { //  }
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
      
      データの読み取り
SSLソケットからデータを読み取るには、次の関数を使用する必要があります。
 int SSL_read(SSL *ssl, void *buf, int num);
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
      
      SSL構造にバインドされたソケットからデータをバッファに読み込みます。 ソケットバッファーに読み込むデータがあるかどうかを調べる必要がある場合は、関数を使用できます。
 int SSL_pending(const SSL *ssl);
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
      
      すなわち 読み取りには、次の構成を使用できます。
 const int size = SSL_pending(ssl); char *buf = new char[size]; memset(buf, 0, size); if (SSL_read(ssl, buf, size) <= 0) { //  }
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
      
      その結果、すでにデコードされたデータがバッファに格納されます。
データ記録
記録するには、次の関数を使用します。
 int SSL_write(SSL *ssl, const void *buf, int num);
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
      
      入力時に、彼女は記録するSSL構造体へのポインター、データ自体、およびそのサイズを受け取ります。
そのようなレコードの例を次に示します。
 char buf[] = "12345678" if (SSL_write(ssl, buf, strlen(buf)) <= 0) { //  }
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
      
      サーバー
サーバー部分はクライアント部分に似ています-SSLコンテキストも必要であり、レコードを読み取るための同じ機能を備えています。 違いは、サーバーがクライアントに証明書を提供するためにコンテキストを準備し、クライアントとのハンドシェイクを整理する必要があることです。
コンテキストを準備することから始めましょう。
 const SSL_METHOD *method = SSLv23_server_method(); SSL_CTX *ctx = NULL; ctx = SSL_CTX_new(method); if (ctx == NULL) { //  }
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
      
        このドキュメントでは、 SSLv23_server_method
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    を選択すると、クライアントがサポートするプロトコルの最も安全なバージョンをライブラリが個別に決定できることがSSLv23_server_method
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    ます。 
 特定のバージョンを有効または無効にする、または他の設定を変更する場合は、 SSL_set_options
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    関数を使用できます。 そのためのドキュメントはここにあります 。 
X.509証明書の ダウンロードとキーの読み込みについて少し前に確認したため、これらの構造が既にいくつかあると考えています。
サーバーコンテキストの証明書と証明書キーをインストールします。
 X509 *serverCert; //not null EVP_PKEY *serverKey; //not null if (SSL_CTX_use_certificate(ctx, serverCert) != 1) { //  } if (SSL_CTX_use_PrivateKey(ctx, serverKey) != 1) { //  }
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
      
       サーバーは着信接続を受け入れる準備ができています。 通常のaccept
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
     。 この関数から受け取ったソケットに興味があります。 
まず、そのようなソケットごとに、新しいSSL構造が必要です。
 SSL *ssl = NULL; ssl = SSL_new(ctx); if (ssl == NULL) { //  }
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
      
      次に、この構造にソケットをインストールします。
 int sock; //accepted tcp socket if (SSL_set_fd(ssl, sock) != 1) { //Hadle error }
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
      
      ハンドシェイクメカニズム自体:
 if (SSL_accept(ssl) != 1) { //  }
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
      
      次の点に注意する価値があります。使用しているソケットが非ブロックモードの場合、ハンドシェイクは最初は機能しません。 この場合、戻り値だけでなくエラーコードも確認する必要があります。
 int ret = SSL_accept(ssl); if (ret == 0) { //  } if (ret < 0) { unsigned long error = ERR_get_error(); if (error == SSL_ERROR_WANT_READ || error == SSL_ERROR_WANT_WRITE || error == 0) { // ,      //   } else { //  } } if (ret == 1) { // }
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
      
      - .
作業例
:
 mkdir build cd build cmake .. make
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
      
      打ち上げ
(build) :
./openssl_api
         ca.crt —   
      
        
        
        
      
     . 
,
 cppurl --cacert ca.crt -v https://127.0.0.1:5566 -H "Host: taigasystem.com"
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
      
      CA , -v . -H "Host: taigasystem.com" , GET - Host. , 404- .
curl
curl ( ):
 $ curl --cacert ca.crt -v https://127.0.0.1:5566 -H "Host: taigasystem.com" * Rebuilt URL to: https://127.0.0.1:5566/ * Trying 127.0.0.1... * Connected to 127.0.0.1 (127.0.0.1) port 5566 (#0) * found 1 certificates in ca.crt * found 700 certificates in /etc/ssl/certs * ALPN, offering http/1.1 * SSL connection using TLS1.2 / RSA_AES_128_GCM_SHA256 * server certificate verification OK * server certificate status verification SKIPPED * common name: 127.0.0.1 (matched) * server certificate expiration date OK * server certificate activation date OK * certificate public key: RSA * certificate version: #3 * subject: C=RU,CN=127.0.0.1,L=Moscow,O=Taigasystem,ST=Moscow * start date: Mon, 28 Aug 2017 07:36:42 GMT * expire date: Thu, 27 Aug 2020 07:36:42 GMT * issuer: C=RU,CN=127.0.0.1,L=Moscow,O=Taigasystem,ST=Moscow * compression: NULL * ALPN, server did not agree to a protocol > GET / HTTP/1.1 > Host: taigasystem.com > User-Agent: curl/7.47.0 > Accept: */* > < HTTP/1.1 200 OK < Server: nginx/1.4.6 (Ubuntu) < Date: Mon, 28 Aug 2017 07:39:18 GMT < Content-Type: text/html; charset=utf-8 < Transfer-Encoding: chunked < Connection: keep-alive < Vary: Accept-Language, Cookie < X-Frame-Options: SAMEORIGIN < Content-Language: ru < Strict-Transport-Security: max-age=604800 < ....
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
      
      : -, ; -, .
curl () :
 $ ./openssl_api  OpenSSL      'curl --cacert ca.crt -v https://127.0.0.1:5566 -H "Host: taigasystem.com" '       BIGNUM    BIGNUM  RSA       EVP  .  : 512       BIGNUM    BIGNUM  RSA       EVP CSR .  : 512  .  : 512          ca.crt SSL       5566       taigasystem.com   443    taigasystem.com[188.225.73.237]:443 SSL   SSL   taigasystem.com:443   /  #######################################  79    ####################################### GET / HTTP/1.1 Host: taigasystem.com User-Agent: curl/7.47.0 Accept: */* #######################################  4096    ####################################### HTTP/1.1 200 OK Server: nginx/1.4.6 (Ubuntu) Date: Mon, 28 Aug 2017 07:39:18 GMT Content-Type: text/html; charset=utf-8 Transfer-Encoding: chunked Connection: keep-alive Vary: Accept-Language, Cookie X-Frame-Options: SAMEORIGIN Content-Language: ru Strict-Transport-Security: max-age=604800 ...
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
      
      すなわち , GET - (curl) .