Dalam artikel sebelumnya, proses mengintegrasikan sertifikat GOST CryptoPro dengan mono dijelaskan. Dalam hal yang sama, kami berkutat menghubungkan sertifikat RSA.
Kami terus mem-port salah satu sistem server kami yang ditulis dalam C # ke Linux, dan antrian mencapai bagian terkait RSA. Jika terakhir kali kesulitan dalam menghubungkan dengan mudah dijelaskan oleh interaksi dua sistem yang awalnya tidak terhubung satu sama lain, maka ketika menghubungkan sertifikat RSA "biasa" dari mono, tidak ada yang mengharapkan tangkapan.
Memasang sertifikat dan kunci tidak menyebabkan masalah, dan sistem bahkan melihatnya dalam penyimpanan standar. Namun, itu tidak lagi mungkin untuk menandatangani, mengenkripsi, atau mengekstrak data dari tanda tangan yang dihasilkan sebelumnya - mono jatuh secara stabil dengan kesalahan. Saya harus, seperti dalam kasus CryptoPro, terhubung langsung ke perpustakaan enkripsi. Untuk sertifikat RSA di Linux, kandidat utama untuk koneksi semacam itu adalah OpenSSL.
Instalasi Sertifikat
Untungnya, Centos 7 memiliki versi bawaan OpenSSL - 1.0.2k. Agar tidak memperkenalkan kesulitan tambahan ke dalam sistem, kami memutuskan untuk terhubung ke versi ini. OpenSSL memungkinkan Anda membuat toko sertifikat file khusus, namun:
- toko semacam itu mengandung sertifikat dan CRL, bukan kunci pribadi, jadi dalam hal ini mereka harus disimpan secara terpisah;
- menyimpan sertifikat dan kunci pribadi di Windows pada disk dalam bentuk tidak aman adalah "sangat tidak aman" (mereka yang bertanggung jawab untuk keamanan digital biasanya menggambarkannya lebih luas dan lebih sedikit sensor), jujur ββsaja, ini tidak terlalu aman di Linux, tetapi, pada kenyataannya, umum latihan;
- mengoordinasi lokasi repositori semacam itu di Windows dan Linux cukup bermasalah;
- dalam hal implementasi penyimpanan secara manual, sebuah utilitas akan diperlukan untuk mengelola set sertifikat;
- mono sendiri menggunakan penyimpanan disk dengan struktur OpenSSL, dan juga menyimpan kunci pribadi dalam bentuk terbuka di dekatnya;
Untuk alasan ini, kami akan menggunakan toko .Net dan mono sertifikat standar untuk menghubungkan OpenSSL. Untuk melakukan ini, di Linux, sertifikat dan kunci pribadi pertama-tama harus ditempatkan di repositori mono.
Instalasi SertifikatKami akan menggunakan utilitas certmgr standar untuk ini. Pertama, instal kunci pribadi dari pfx:
certmgr -importKey -c -p {password} My {pfx file}
Kemudian kami menempatkan sertifikat dari pfx ini, kunci pribadi akan terhubung secara otomatis:
certmgr -add -c My {cer file}
Jika Anda ingin menginstal kunci di penyimpanan untuk mesin, Anda harus menambahkan opsi -m.
Setelah itu sertifikat dapat dilihat di repositori:
certmgr -list -c -v My
Perhatikan penerbitannya. Perlu dicatat bahwa sertifikat terlihat oleh sistem, dan terkait dengan kunci pribadi yang diunduh sebelumnya. Setelah itu, Anda dapat melanjutkan ke koneksi dalam kode.
Koneksi dalam kode
Sama seperti terakhir kali, sistem, meskipun porting ke Linux, seharusnya terus berfungsi di lingkungan Windows. Oleh karena itu, secara lahiriah, bekerja dengan kriptografi harus dilakukan melalui metode umum dari bentuk "byte [] SignData (byte [] _arData, X509Certificate2 _pCert)", yang harus bekerja sama di Linux dan Windows.
Idealnya, harus ada metode yang bekerja seperti di Windows - terlepas dari jenis sertifikat (di Linux melalui OpenSSL atau CryptoPro tergantung pada sertifikat, dan pada Windows melalui crypt32).
Analisis perpustakaan OpenSSL menunjukkan bahwa di Windows perpustakaan utama adalah "libeay32.dll", dan di Linux "libcrypto.so.10". Seperti yang terakhir kali, kami membentuk dua kelas WOpenSSLAPI dan LOpenSSLAPI, yang berisi daftar metode pustaka perpustakaan:
[DllImport(CTRYPTLIB, CharSet = CharSet.Auto, SetLastError = true, CallingConvention = CallingConvention.Cdecl)] internal static extern void OPENSSL_init();
Perhatikan konvensi pemanggilan, tidak seperti CryptoPro - ini harus secara eksplisit ditentukan. Sintaks untuk menghubungkan masing-masing metode kali ini harus dihasilkan secara independen berdasarkan file sumber * .h OpenSSL.
Aturan dasar untuk menghasilkan sintaks panggilan dalam C # berdasarkan data dari file .h adalah sebagai berikut:
- semua tautan ke struktur, string, dll. - IntPtr, termasuk tautan di dalam struktur itu sendiri;
- tautan ke tautan - ref IntPtr, jika opsi ini tidak berfungsi, maka cukup IntPtr. Dalam hal ini, tautan itu sendiri harus diletakkan dan dihapus secara manual;
- array - byte [];
- long in C (OpenSSL) adalah int di C # (kesalahan kecil, sekilas, dapat berubah menjadi jam mencari sumber kesalahan yang tidak dapat diprediksi);
Dalam deklarasi, karena kebiasaan, Anda dapat menentukan SetLastError = true, tetapi perpustakaan akan mengabaikan ini - kesalahan tidak akan tersedia melalui Marshal.GetLastWin32Error (). OpenSSL memiliki metode sendiri untuk mengakses kesalahan.
Dan kemudian kita membentuk kelas statis yang sudah akrab "UOpenSSLAPI" yang, tergantung pada sistem, akan memanggil metode salah satu dari dua kelas:
private static object fpOSSection = new object(); public static void OPENSSL_init() { lock (pOSSection) { if (fIsLinux) LOpenSSLAPI.OPENSSL_init(); else WOpenSSLAPI.OPENSSL_init(); } } public static object pOSSection { get { return fpOSSection; } } public static bool fIsLinux { get { int iPlatform = (int) Environment.OSVersion.Platform; return (iPlatform == 4) || (iPlatform == 6) || (iPlatform == 128); } }
Kami segera mencatat keberadaan bagian yang kritis. OpenSSL secara teoritis menyediakan pekerjaan di lingkungan multi-threaded. Tapi, pertama, segera dalam deskripsi dikatakan bahwa ini tidak dijamin:
Tetapi Anda masih tidak bisa secara bersamaan menggunakan sebagian besar objek di banyak utas.
Dan kedua, metode koneksi bukan yang paling sepele. VM dua-inti yang biasa (server dengan prosesor Intel Xeon E5649 dalam mode Hyper-Threading) saat menggunakan bagian kritis tersebut memberikan sekitar 100 siklus penuh (lihat algoritma pengujian dari artikel sebelumnya) atau 600 tanda tangan per detik, yang pada dasarnya cukup untuk sebagian besar tugas ( di bawah beban berat, arsitektur microservice atau nodal dari sistem akan tetap digunakan).
Menginisialisasi dan menurunkan OpenSSL
Tidak seperti CryptoPro OpenSSL membutuhkan tindakan tertentu sebelum Anda mulai menggunakannya dan setelah Anda selesai bekerja dengan perpustakaan:
public static void InitOpenSSL() { UOpenSSLAPI.OPENSSL_init(); UOpenSSLAPI.ERR_load_crypto_strings(); UOpenSSLAPI.ERR_load_RSA_strings(); UOpenSSLAPI.OPENSSL_add_all_algorithms_conf(); UOpenSSLAPI.OpenSSL_add_all_ciphers(); UOpenSSLAPI.OpenSSL_add_all_digests(); } public static void CleanupOpenSSL() { UOpenSSLAPI.EVP_cleanup(); UOpenSSLAPI.CRYPTO_cleanup_all_ex_data(); UOpenSSLAPI.ERR_free_strings(); }
Informasi Kesalahan
OpenSSL menyimpan informasi kesalahan dalam struktur internal yang ada metode khusus di perpustakaan. Sayangnya, beberapa metode sederhana, seperti ERR_error_string, tidak stabil, jadi Anda harus menggunakan metode yang lebih kompleks:
Mendapatkan Informasi Kesalahan public static string GetErrStrPart(ulong _iErr, int _iPart) {
Kesalahan dalam OpenSSL berisi informasi tentang perpustakaan tempat itu terjadi, metode dan alasannya. Oleh karena itu, setelah menerima kode kesalahan itu sendiri, perlu untuk mengekstrak ketiga bagian ini secara terpisah dan menyatukannya dalam satu baris teks. Panjang setiap baris, menurut dokumentasi OpenSSL , tidak melebihi 120 karakter, dan karena kami menggunakan kode terkelola, kami harus hati-hati mengekstrak baris dari tautan:
Mendapatkan string oleh IntPtr public static string PtrToFirstStr(IntPtr _hPtr, int _iLen = 256) { if(_hPtr == IntPtr.Zero) return ""; try { byte[] arStr = new byte[_iLen]; Marshal.Copy(_hPtr, arStr, 0, arStr.Length); string[] arRes = Encoding.ASCII.GetString(arStr).Split(new char[] { (char)0 }, StringSplitOptions.RemoveEmptyEntries); if (arRes.Length > 0) return arRes[0]; return ""; }catch { return ""; } }
Kesalahan saat memeriksa sertifikat tidak termasuk dalam daftar umum, dan itu harus diekstraksi dengan metode terpisah, sesuai dengan konteks verifikasi:
Terima kesalahan verifikasi sertifikat public static string GetCertVerifyErr(IntPtr _hStoreCtx) { int iErr = UOpenSSLAPI.X509_STORE_CTX_get_error(_hStoreCtx); return PtrToFirstStr(UOpenSSLAPI.X509_verify_cert_error_string(iErr)); }
Pencarian Sertifikat
Seperti biasa, kriptografi dimulai dengan pencarian sertifikat. Kami menggunakan penyimpanan penuh waktu, jadi kami akan mencari menggunakan metode biasa:
Pencarian Sertifikat internal static int FindCertificateOS(string _pFindValue, out X509Certificate2 _pCert, ref string _sError, StoreLocation _pLocation = StoreLocation.CurrentUser, StoreName _pName = StoreName.My, X509FindType _pFindType = X509FindType.FindByThumbprint, bool _fVerify = false) { lock (UOpenSSLAPI.pOSSection) {
Perhatikan bagian penting. Mono dengan sertifikat juga berfungsi melalui OpenSSL, tetapi tidak melalui UOpenSSLAPI. Jika Anda tidak melakukannya di sini, Anda bisa mendapatkan kebocoran memori dan kesalahan mengambang yang tidak dapat dipahami sedang dimuat.
Fitur utama adalah pembuatan sertifikat. Berbeda dengan versi untuk CryptoPro, dalam hal ini kami mendapatkan sertifikat itu sendiri (X509Certificate2) dari toko, dan tautan di Handle di dalamnya sudah diarahkan ke struktur OpenSSL X509_st. Tampaknya perlu, tetapi tidak ada pointer ke EVP_PKEY (tautan ke struktur kunci privat di OpenSSL) dalam sertifikat.
Kunci pribadi itu sendiri, ternyata, disimpan di tempat kosong di bidang internal sertifikat - impl / fallback / _cert / _rsa / rsa. Ini adalah kelas RSAManaged, dan melihat sekilas kodenya (misalnya, metode DecryptValue ) menunjukkan betapa buruknya mono dengan kriptografi. Alih-alih jujur ββmenggunakan teknik kriptografi OpenSSL, mereka tampaknya telah menerapkan beberapa algoritma secara manual. Asumsi ini didukung oleh hasil pencarian kosong untuk proyek mereka untuk metode OpenSSL seperti CMS_final, CMS_sign atau CMS_ContentInfo_new. Dan tanpa mereka, sulit untuk membayangkan pembentukan struktur tanda tangan CMS standar. Pada saat yang sama, bekerja dengan sertifikat sebagian dilakukan melalui OpenSSL.
Ini berarti bahwa kunci pribadi harus diturunkan dari mono dan dimuat ke EVP_PKEY melalui pem. Karena itu, kita perlu lagi pewaris kelas dari X509Certificate, yang akan menyimpan semua tautan tambahan.
Berikut ini hanya upaya, seperti dalam kasus CryptoPro untuk membuat sertifikat baru dari Handle - juga tidak mengarah pada kesuksesan (mono crash dengan kesalahan), dan membuat berdasarkan sertifikat yang diterima menyebabkan kebocoran memori. Oleh karena itu, satu-satunya pilihan adalah membuat sertifikat berdasarkan array byte yang berisi pem. Sertifikat PEM dapat diperoleh sebagai berikut:
Memperoleh sertifikat PEM public static int ToCerFile(this X509Certificate2 _pCert, out byte[] _arData, ref string _sError, bool _fBase64 = true) { _arData = new byte[0]; try { byte[] arData = _pCert.Export(X509ContentType.Cert);
Sertifikat diperoleh tanpa kunci pribadi dan kami menghubungkannya sendiri, membentuk bidang terpisah untuk tautan ke ENV_PKEY:
Menghasilkan ENV_PKEY Berdasarkan PEM Private Key internal static IntPtr GetENV_PKEYOS(byte[] _arData) { IntPtr hBIOPem = IntPtr.Zero; try {
Mengunggah kunci pribadi di PEM, tugasnya jauh lebih rumit daripada sertifikat PEM, tetapi sudah dijelaskan di sini . Perhatikan bahwa membongkar kunci pribadi adalah bisnis yang "sangat tidak aman", dan ini harus dihindari dalam segala hal. Dan karena pembongkaran seperti itu diperlukan untuk bekerja dengan OpenSSL, di Windows lebih baik menggunakan metode crypt32.dll atau kelas .Net biasa saat menggunakan perpustakaan ini. Di Linux, untuk saat ini, Anda harus bekerja seperti ini.
Perlu juga diingat bahwa tautan yang dibuat menunjukkan area memori yang tidak dikelola dan harus dibebaskan. Karena di .Net 4.5 X509Certificate2 bukan Disposable, maka Anda perlu melakukan ini di destructor
Menandatangani
Untuk menandatangani OpenSSL, Anda dapat menggunakan metode CMS_sign yang disederhanakan, namun, itu bergantung pada file konfigurasi dalam memilih algoritma, yang akan sama untuk semua sertifikat. Oleh karena itu, lebih baik mengandalkan kode metode ini untuk mengimplementasikan generasi tanda tangan serupa:
Tanda tangan data internal static int SignDataOS(byte[] _arData, X509Certificate2 _pCert, out byte[] _arRes, ref string _sError) { _arRes = new byte[0]; uint iFlags = UCConsts.CMS_DETACHED; IntPtr hData = IntPtr.Zero; IntPtr hBIORes = IntPtr.Zero; IntPtr hCMS = IntPtr.Zero; try {
Kemajuan algoritma adalah sebagai berikut. Pertama, konversikan sertifikat yang masuk (jika itu X509Certificate2) ke jenis kami. Karena Karena kami bekerja dengan tautan ke area memori yang tidak dikelola, kami harus memantaunya dengan cermat. Bersih beberapa waktu setelah tautan ke sertifikat di luar cakupan, ia akan meluncurkan destructor. Dan di dalamnya kita telah menentukan metode yang diperlukan untuk menghapus semua memori yang tidak dikelola yang terkait dengannya. Pendekatan ini memungkinkan kita untuk tidak membuang waktu melacak tautan ini langsung di dalam metode.
Setelah berurusan dengan sertifikat, kami membentuk BIO dengan data dan struktur tanda tangan. Kemudian kami menambahkan data dari penandatangan, mengatur bendera untuk memutuskan tanda tangan dan memulai formasi akhir tanda tangan. Hasilnya ditransfer ke BIO. Tetap hanya untuk mengekstrak array byte dari BIO. Mengubah BIO ke satu set byte dan sebaliknya sering digunakan, jadi lebih baik untuk menempatkannya dalam metode terpisah:
BIO dalam byte [] dan sebaliknya internal static int ReadFromBIO_OS(IntPtr _hBIO, out byte[] _arRes, ref string _sError, uint _iLen = 0) { _arRes = new byte[0]; IntPtr hRes = IntPtr.Zero; uint iLen = _iLen; if(iLen == 0) iLen = int.MaxValue; try {
Seperti dalam kasus CryptoPro, perlu untuk mengekstrak informasi tentang algoritma hashing tanda tangan dari sertifikat. Tetapi dalam kasus OpenSSL, disimpan langsung dalam sertifikat:
Mengambil Algoritma Hash public static IntPtr GetDigestAlgOS(IntPtr _hCert) { x509_st pCert = (x509_st)Marshal.PtrToStructure(_hCert, typeof(x509_st)); X509_algor_st pAlgInfo = (X509_algor_st)Marshal.PtrToStructure(pCert.sig_alg, typeof(X509_algor_st)); IntPtr hAlgSn = UOpenSSLAPI.OBJ_nid2sn(UOpenSSLAPI.OBJ_obj2nid(pAlgInfo.algorithm)); return UOpenSSLAPI.EVP_get_digestbyname(hAlgSn); }
Metode ini ternyata cukup rumit, tetapi berhasil. Anda dapat menemukan metode EVP_get_digestbynid dalam dokumentasi 1.0.2, namun pustaka versi yang kami gunakan tidak mengekspornya. Karena itu, pertama-tama kita membentuk nid, dan atas dasar itu nama pendek. Dan sudah dengan nama pendek, Anda dapat mengekstrak algoritme dengan cara biasa mencari berdasarkan nama.
Verifikasi Tanda Tangan
Tanda tangan yang diterima perlu verifikasi. OpenSSL memverifikasi tanda tangan sebagai berikut:
Verifikasi Tanda Tangan internal static int CheckSignOS(byte[] _arData, byte[] _arSign, out X509Certificate2 _pCert, ref string _sError, bool _fVerifyOnlySign = true, StoreLocation _pLocation = StoreLocation.CurrentUser){ _pCert = null; IntPtr hBIOData = IntPtr.Zero; IntPtr hCMS = IntPtr.Zero; IntPtr hTrStore = IntPtr.Zero; try {
Pertama, data tanda tangan dikonversi dari array byte ke struktur CMS:
Pembentukan struktur CMS internal static int GetCMSFromBytesOS(byte[] _arData, out IntPtr _hCMS, ref string _sError) { _hCMS = IntPtr.Zero; IntPtr hBIOCMS = IntPtr.Zero; IntPtr hCMS = IntPtr.Zero; try {
, BIO. , , ( ) :
internal static int GetTrustStoreOS(StoreLocation _pLocation, out IntPtr _hStore, ref string _sError) { _hStore = IntPtr.Zero; IntPtr hStore = IntPtr.Zero; try { List<X509Certificate2> pCerts = GetCertList(_pLocation, StoreName.Root, TCryptoPath.cpOpenSSL); pCerts.AddRange(GetCertList(_pLocation, StoreName.AuthRoot, TCryptoPath.cpOpenSSL));
, ( , ). CMS_Verify, .
(, CRL), iFlag .
. , , . .Net β SignedCms, .
( , ) . β , .
internal int DecodeOS(byte[] _arSign, byte[] _arContent, ref string _sError) { IntPtr hBIOData = IntPtr.Zero; IntPtr hCMS = IntPtr.Zero; IntPtr hCerts = IntPtr.Zero; try {
, BIO ( ) CMS, . , β .
(STACK_OF(X509)), sk_pop, . , sk_value.
, CMS_get0_signers CMS_get1_certs. , . , , :
CRYPTO_add(&cch->d.certificate->references, 1, CRYPTO_LOCK_X509)
1.1.0 X509_up_ref, .
:
public int DecodeOS(IntPtr _hSignerInfo, ref string _sError) { try {
, , . ASN.1. asn1_string_st Pkcs9SigningTime.
:
internal static int GetSignerInfoCertOS(IntPtr _hSignerInfo, X509Certificate2Collection _pCerts, out X509Certificate2 _pCert, ref string _sError) { _pCert = null; try {
, . asn1_string_st, hex :
hex ANS.1 internal static int GetBinaryHexFromASNOS(IntPtr _hASN, out string _sHexData, ref string _sError) { _sHexData = ""; try { asn1_string_st pSerial = (asn1_string_st)Marshal.PtrToStructure(_hASN, typeof(asn1_string_st)); byte[] arStr = new byte[pSerial.iLength]; Marshal.Copy(pSerial.hData, arStr, 0, (int)pSerial.iLength); _sHexData = arStr.ToHex().ToUpper(); return UConsts.S_OK; } catch (Exception E) { _sError = UCConsts.S_HEX_ASN_BINARY_ERR.Frm(E.Message); return UConsts.E_GEN_EXCEPTION; } }
, , .
OpenSSL :
internal static int EncryptDataOS(byte[] _arInput, List<X509Certificate2> _pReceipients, out byte[] _arRes, ref string _sError) { _arRes = new byte[0]; uint iFlags = UCConsts.CMS_BINARY; IntPtr hData = IntPtr.Zero; IntPtr hReceipts = IntPtr.Zero; IntPtr hBIORes = IntPtr.Zero; IntPtr hCMS = IntPtr.Zero; try {
BIO β . . , BIO . OpenSSL , , , . EVP_des_ede3_cbc, .
, . . OpenSSL:
public static int GetCertsStackOS(List<X509Certificate2> _pCerts, out IntPtr _hStack, ref string _sError) { _hStack = IntPtr.Zero; IntPtr hStack = IntPtr.Zero; try { hStack = UOpenSSLAPI.sk_new_null(); foreach (X509Certificate2 pCert in _pCerts) {
, . :
- , ;
- ;
- ;
- ;
- BIO ;
internal static int DecryptDataOS(byte[] _arInput, out X509Certificate2 _pCert, out byte[] _arRes, ref string _sError, StoreLocation _pLocation = StoreLocation.CurrentUser ) { _arRes = new byte[0]; _pCert = null; uint iFlag = UCConsts.CMS_BINARY; IntPtr hBIORes = IntPtr.Zero; IntPtr hCMS = IntPtr.Zero; X509Certificate2 pCert; try {
. , CMS_RecipientInfo_set0_pkey, CMS, .
, , . , . :
internal static int GetRecepInfoCertOS(IntPtr _hRecep, StoreLocation _pLocation, out X509Certificate2 _pCert, ref string _sError) { _pCert = null; try {
CMS_RecipientInfo_ktri_get0_signer_id , hSNO . .
C , , . ktri β . OpenSSL : CMS_RecipientInfo_kari_*, CMS_RecipientInfo_kekri_* CMS_RecipientInfo_set0_password pwri.
, . . , . . . OpenSSL . ( ), , .
, , , :
internal static int VerifyCertificateOS(IntPtr _hCert, X509RevocationMode _iRevMode, X509RevocationFlag _iRevFlag, StoreLocation _pLocation, DateTime _rOnDate, ref string _sError) { IntPtr hStore = IntPtr.Zero; IntPtr hStoreCtx = IntPtr.Zero; try {
(X509_STORE_CTX) . :
public static void SetStoreCtxCheckDate(IntPtr _hStoreCtx, DateTime _rDate) { uint iFlags = UCConsts.X509_V_FLAG_USE_CHECK_TIME | UCConsts.X509_V_FLAG_X509_STRICT | UCConsts.X509_V_FLAG_CRL_CHECK_ALL;
, .
Kesimpulan
, . X509Certificate2 (mono) . .
, Windows . . Linux , , .
CSP 5.0 , RSA . , , , RSA, , .
Referensi
- OpenSSL 1.0.2 ManPages ;
- OpenSSL 1 2 ;
- OpenSSL :
- cms_smime.c;
- Wiki OpenSSL ;
- mono:
- RSAManaged ;