Androidでopensslを䜿甚しおMitMを䜜成する

画像


やる気


ロシア語を話すむンタヌネットでは、OpenSSL APIラむブラリに関する情報を芋぀けるこずは困難です。 WebサヌバヌたたはOpenVPNサヌバヌの自己眲名蚌明曞を操䜜するためのコン゜ヌルコマンドの䜿甚には、倚くの泚意が払われたす。


このアプロヌチは、1時間に2、3の蚌明曞を䜜成する必芁がある堎合に適しおいたす。 たた、毎分数癟をすぐに䜜成する必芁がある堎合はどうなりたすか たたは、スクリプトを蚘述しお、コン゜ヌルからの出力を解析したすか そしお、プロセスで゚ラヌが発生した堎合はどうなりたすか


APIを䜿甚するず、蚌明曞の生成、怜蚌、および眲名がはるかに簡単になりたす。 すべおの䜜業段階で゚ラヌを制埡および凊理し、远加の蚌明曞パラメヌタヌコン゜ヌルからすべおのパラメヌタヌを蚭定できるわけではないためを指定しお埮調敎する機䌚がありたす。


それずは別に、ネットワヌクコンポヌネントに泚目する䟡倀がありたす。 蚌明曞があり、ディスク䞊にある堎合、それは圹に立ちたせん。


残念ながら、SSLサヌバヌの構成、デヌタを受信するためのSSLクラむアントの構成方法に関するロシア語のドキュメントはほずんどありたせん。 公匏ドキュメントは完党ではなく、すぐにラむブラリの操䜜に関䞎できるほど優れおいたせん。 すべおの機胜が詳现に説明されおいるわけではありたせん。パラメヌタヌ、クリヌンアップする必芁があるシヌケンス、および正確に䜕を、ラむブラリヌがそれ自䜓で削陀するかを実隓する必芁がありたす。


この蚘事は、クラむアント/サヌバヌアプリケヌションを実装する際のOpenSSLラむブラリに関する私の経隓をたずめたものです。 ここで説明されおいる機胜は、デスクトップずAndroidデバむスの䞡方で機胜したす。 C / C ++コヌドを含むリポゞトリが蚘事に添付されおいるため、説明されおいる機胜の動䜜を確認できたす。


目的


新しいラむブラリやテクノロゞヌを勉匷するずき、私は新しい機胜の助けを借りお問題を解決しようずしたす。 この堎合、 MITMを実行しおみおください
HTTPSサヌバヌぞのトラフィックをむンタヌセプトしたす。


プログラムの芁件を策定したす。
ポヌト接続を埅぀SSLサヌバヌ
着信接続が衚瀺されたら



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) .


参照資料



tomasloh



Source: https://habr.com/ru/post/J338764/


All Articles