في مقال سابق ، تم وصف عملية دمج شهادات GOST لـ CryptoPro مع mono. في الوقت نفسه ، نحن نتحدث عن ربط شهادات RSA.
واصلنا نقل أحد أنظمة الخادم الخاصة بنا المكتوبة بلغة C # إلى Linux ، ووصلت قائمة الانتظار إلى الجزء المتعلق بـ RSA. إذا كانت المرة الأخيرة التي تم فيها شرح صعوبات الاتصال بسهولة من خلال تفاعل نظامين لم يكنا متصلين ببعضهما في الأصل ، فعند توصيل شهادات RSA "العادية" من أحادية ، لم يكن أحد يتوقع حدوث صيد.
لم يتسبب تثبيت الشهادة والمفتاح في حدوث مشكلات ، بل وقد رآها النظام في وحدة التخزين القياسية. ومع ذلك ، لم يعد من الممكن توقيع البيانات أو تشفيرها أو استخراجها من توقيع تم إنشاؤه سابقًا - فقد حدث خطأ أحادي بشكل ثابت. اضطررت ، كما هو الحال في CryptoPro ، إلى الاتصال مباشرة بمكتبة التشفير. بالنسبة لشهادات RSA في Linux ، فإن المرشح الرئيسي لمثل هذا الاتصال هو OpenSSL.
تثبيت الشهادة
لحسن الحظ ، يحتوي Centos 7 على إصدار مضمن من OpenSSL - 1.0.2k. من أجل عدم إدخال صعوبات إضافية في النظام ، قررنا الاتصال بهذا الإصدار. يسمح لك OpenSSL بإنشاء مخازن شهادات ملفات خاصة ، ومع ذلك:
- يحتوي هذا المتجر على شهادات و CRLs ، وليس مفاتيح خاصة ، لذا في هذه الحالة يجب تخزينها بشكل منفصل ؛
- تخزين الشهادات والمفاتيح الخاصة في Windows على قرص في شكل غير آمن هو "غير آمن للغاية" (المسؤولون عن الأمن الرقمي عادة ما يصفونه بشكل أكثر قوة وأقل رقابة) ، لنكون صادقين ، هذا ليس آمنًا جدًا في Linux ، ولكن في الواقع ، من الشائع الممارسة ؛
- إن تنسيق موقع هذه المستودعات على نظامي التشغيل Windows و Linux يمثل مشكلة كبيرة ؛
- في حالة التنفيذ اليدوي للتخزين ، ستكون هناك حاجة إلى أداة لإدارة مجموعة الشهادات ؛
- يستخدم mono نفسه تخزين القرص بهيكل OpenSSL ، ويقوم أيضًا بتخزين المفاتيح الخاصة في شكل مفتوح قريب ؛
لهذه الأسباب ، سنستخدم مخازن الشهادات .Net و mono القياسية لتوصيل OpenSSL. للقيام بذلك ، على Linux ، يجب أولاً وضع الشهادة والمفتاح الخاص في المستودع الأحادي.
تثبيت الشهادةسنستخدم الأداة certmgr القياسية لهذا الغرض. أولاً ، قم بتثبيت المفتاح الخاص من pfx:
certmgr -importKey -c -p {password} My {pfx file}
ثم نضع الشهادة من هذا pfx ، سيتصل بها المفتاح الخاص تلقائيًا:
certmgr -add -c My {cer file}
إذا كنت ترغب في تثبيت المفتاح في التخزين للجهاز ، يجب عليك إضافة الخيار -m.
بعد ذلك يمكن رؤية الشهادة في المستودع:
certmgr -list -c -v My
انتبه إلى الإصدار. وتجدر الإشارة إلى أن الشهادة مرئية للنظام وترتبط بالمفتاح الخاص الذي تم تنزيله سابقًا. بعد ذلك ، يمكنك المتابعة إلى الاتصال في الرمز.
الاتصال في التعليمات البرمجية
تمامًا مثل آخر مرة ، كان يجب أن يستمر النظام في بيئة Windows ، على الرغم من نقله إلى Linux. لذلك ، يجب أن يتم العمل باستخدام التشفير ظاهريًا من خلال الطرق العامة للنموذج "byte [] SignData (byte [] _arData، X509Certificate2 _pCert)" ، والتي يجب أن تعمل بنفس الطريقة في كل من Linux و Windows.
من الناحية المثالية ، يجب أن تكون هناك طرق تعمل تمامًا مثل Windows - بغض النظر عن نوع الشهادة (على Linux من خلال OpenSSL أو CryptoPro اعتمادًا على الشهادة ، وعلى Windows من خلال crypt32).
أظهر تحليل مكتبات OpenSSL أنه في نظام التشغيل Windows فإن المكتبة الرئيسية هي "libeay32.dll" ، وعلى Linux "libcrypto.so.10". بالإضافة إلى المرة الأخيرة ، قمنا بتشكيل فئتين WOpenSSLAPI و LOpenSSLAPI ، تحتوي على قائمة بأساليب مكتبة المكتبة:
[DllImport(CTRYPTLIB, CharSet = CharSet.Auto, SetLastError = true, CallingConvention = CallingConvention.Cdecl)] internal static extern void OPENSSL_init();
انتبه إلى اتفاقية الاتصال ، على عكس CryptoPro - هنا يجب تحديدها بشكل صريح. يجب أن يتم إنشاء بناء الجملة لتوصيل كل من هذه الطرق بشكل مستقل بناءً على ملفات مصدر * .h OpenSSL.
القواعد الأساسية لإنشاء بنية استدعاء في C # استناداً إلى البيانات من ملفات .h هي كما يلي:
- أي روابط إلى الهياكل والسلاسل وما إلى ذلك - IntPtr ، بما في ذلك الروابط داخل الهياكل نفسها ؛
- روابط إلى روابط - المرجع IntPtr ، إذا لم يعمل هذا الخيار ، فعندئذ فقط IntPtr. في هذه الحالة ، يجب وضع الرابط وإزالته يدويًا ؛
- صفائف - بايت [] ؛
- طويل في C (OpenSSL) هو عدد صحيح في C # (للوهلة الأولى ، يمكن أن يتحول الخطأ إلى ساعات من البحث عن مصدر الأخطاء غير المتوقعة) ؛
في الإعلان ، بدافع العادة ، يمكنك تحديد SetLastError = true ، لكن المكتبة ستتجاهل ذلك - لن تكون الأخطاء متاحة من خلال Marshal.GetLastWin32Error (). OpenSSL له طرقه الخاصة للوصول إلى الأخطاء.
ثم نقوم بتشكيل الفئة الثابتة المألوفة بالفعل "UOpenSSLAPI" والتي ، بناءً على النظام ، ستطلق على طريقة واحدة من فئتين:
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); } }
على الفور نلاحظ وجود قسم حاسم. يوفر OpenSSL نظريًا العمل في بيئة متعددة الخيوط. ولكن ، أولاً ، على الفور في الوصف يقال أن هذا غير مضمون:
ولكن لا يزال يتعذر عليك استخدام معظم الكائنات في سلاسل رسائل متعددة في نفس الوقت.
وثانيًا ، طريقة الاتصال ليست الأكثر تافهة. يمنح جهاز VM المعتاد ثنائي النواة (خادم مزود بمعالج Intel Xeon E5649 في وضع Hyper-Threading) حوالي 100 دورة كاملة (انظر خوارزمية الاختبار من المقالة السابقة) أو 600 توقيع في الثانية ، وهو ما يكفي بشكل أساسي لمعظم المهام ( تحت الأحمال الثقيلة ، سيتم استخدام الخدمات المصغرة أو بنية العقد للنظام على أي حال).
تهيئة وتفريغ OpenSSL
على عكس CryptoPro OpenSSL يتطلب إجراءات معينة قبل البدء في استخدامه وبعد الانتهاء من العمل مع المكتبة:
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(); }
معلومات الخطأ
يقوم OpenSSL بتخزين معلومات الخطأ في الهياكل الداخلية التي توجد بها طرق خاصة في المكتبة. لسوء الحظ ، بعض الطرق البسيطة ، مثل ERR_error_string ، غير مستقرة ، لذا عليك استخدام طرق أكثر تعقيدًا:
الحصول على معلومات الخطأ public static string GetErrStrPart(ulong _iErr, int _iPart) {
يحتوي خطأ في OpenSSL على معلومات حول المكتبة التي حدثت فيها ، والطريقة والسبب. لذلك ، بعد تلقي رمز الخطأ نفسه ، من الضروري استخراج جميع هذه الأجزاء الثلاثة بشكل منفصل وجمعها معًا في سطر نصي. لا يتجاوز طول كل سطر ، وفقًا لوثائق OpenSSL ، 120 حرفًا ، ولأننا نستخدم التعليمات البرمجية المُدارة ، يجب علينا استخراج الخط بعناية من الرابط:
الحصول على سلسلة بواسطة 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 ""; } }
الأخطاء التي تحدث عند فحص الشهادات لا تندرج في القائمة العامة ، ويجب استخراجها بطريقة منفصلة ، وفقًا لسياق التحقق:
تلقي خطأ التحقق من الشهادة public static string GetCertVerifyErr(IntPtr _hStoreCtx) { int iErr = UOpenSSLAPI.X509_STORE_CTX_get_error(_hStoreCtx); return PtrToFirstStr(UOpenSSLAPI.X509_verify_cert_error_string(iErr)); }
بحث الشهادة
كما هو الحال دائمًا ، يبدأ التشفير بالبحث عن الشهادة. نستخدم سعة تخزين بدوام كامل ، لذلك سنبحث باستخدام طرق عادية:
بحث الشهادة 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) {
انتبه إلى القسم الحاسم. يعمل Mono مع الشهادات أيضًا من خلال OpenSSL ، ولكن ليس من خلال UOpenSSLAPI. إذا لم تفعل ذلك هنا ، يمكنك الحصول على تسرب للذاكرة وأخطاء عائمة غير مفهومة تحت الحمل.
السمة الرئيسية هي إنشاء شهادة. على عكس إصدار CryptoPro ، في هذه الحالة نحصل على الشهادة نفسها (X509Certificate2) من المتجر ، والارتباط الموجود في المقبض موجّه بالفعل إلى بنية OpenSSL X509_st. يبدو أنه من الضروري ، ولكن لا يوجد مؤشر لـ EVP_PKEY (ارتباط بهيكل المفتاح الخاص في OpenSSL) في الشهادة.
يتم تخزين المفتاح الخاص نفسه ، كما اتضح ، في المسح في الحقل الداخلي للشهادة - impl / Fallback / _cert / _rsa / rsa. هذه هي فئة RSAManaged ، ويظهر نظرة سريعة على رمزها (على سبيل المثال ، طريقة DecryptValue ) مدى سوء أحادية مع التشفير. بدلاً من استخدام تقنيات التشفير OpenSSL بصدق ، يبدو أنهم قاموا بتنفيذ العديد من الخوارزميات يدويًا. هذا الافتراض مدعوم بنتيجة بحث فارغة لمشروعهم لطرق OpenSSL مثل CMS_final أو CMS_sign أو CMS_ContentInfo_new. وبدونها ، من الصعب تخيل تشكيل هيكل توقيع CMS قياسي. في الوقت نفسه ، يتم العمل على الشهادات جزئيًا من خلال OpenSSL.
وهذا يعني أنه يجب إلغاء تحميل المفتاح الخاص من mono وتحميله إلى EVP_PKEY عبر pem. وبسبب هذا ، نحتاج مرة أخرى إلى وراثة الفصل من X509Certificate ، التي ستخزن جميع الروابط الإضافية.
فيما يلي مجرد محاولات ، كما هو الحال في CryptoPro لإنشاء شهادة جديدة من المقبض - أيضًا لا تؤدي إلى النجاح (تعطل أحادي مع وجود خطأ) ، ويؤدي الإنشاء على أساس الشهادة المستلمة إلى حدوث تسرب للذاكرة. لذلك ، الخيار الوحيد هو إنشاء شهادة تستند إلى صفيف بايت يحتوي على pem. يمكن الحصول على شهادة PEM على النحو التالي:
الحصول على شهادة 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);
يتم الحصول على الشهادة بدون مفتاح خاص ونقوم بتوصيلها بأنفسنا ، لتشكيل حقل منفصل للارتباط بـ ENV_PKEY:
إنشاء ENV_PKEY بناءً على مفتاح PEM الخاص internal static IntPtr GetENV_PKEYOS(byte[] _arData) { IntPtr hBIOPem = IntPtr.Zero; try {
عند تحميل المفتاح الخاص في PEM ، تكون المهمة أكثر تعقيدًا بكثير من شهادة PEM ، ولكنها موصوفة بالفعل هنا . لاحظ أن تفريغ المفتاح الخاص هو عمل "غير آمن للغاية" ، ويجب تجنب ذلك بكل طريقة. وبما أن هذا التفريغ مطلوب للعمل مع OpenSSL ، فمن الأفضل في Windows استخدام أساليب crypt32.dll أو فئات .Net العادية عند استخدام هذه المكتبة. في Linux ، في الوقت الحالي ، عليك العمل بهذه الطريقة.
من الجدير بالذكر أيضًا أن الروابط التي تم إنشاؤها تشير إلى منطقة ذاكرة غير مُدارة ويجب تحريرها. لأن في .Net 4.5 X509Certificate2 غير القابل للتصرف ، فأنت بحاجة إلى القيام بذلك في المدمر
التوقيع
لتسجيل OpenSSL ، يمكنك استخدام طريقة CMS_sign المبسطة ، ومع ذلك ، فإنه يعتمد على ملف تكوين في اختيار خوارزمية ، والتي ستكون هي نفسها لجميع الشهادات. لذلك ، من الأفضل الاعتماد على كود هذه الطريقة لتنفيذ جيل توقيع مماثل:
توقيع البيانات 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 {
تقدم الخوارزمية على النحو التالي. أولاً ، قم بتحويل الشهادة الواردة (إذا كانت X509Certificate2) إلى نوعنا. لأن نظرًا لأننا نعمل مع الروابط إلى منطقة ذاكرة غير مُدارة ، يجب علينا مراقبتها بعناية. بعد مرور بعض الوقت من خروج ارتباط الشهادة عن النطاق ، سيتم تشغيل المدمر. وفيه قمنا بالفعل بتحديد الأساليب الضرورية لمسح كل الذاكرة غير المُدارة المرتبطة بها بدقة. يسمح لنا هذا النهج بعدم إضاعة الوقت في تتبع هذه الروابط مباشرة داخل الطريقة.
بعد التعامل مع الشهادة ، نشكل سيرة ذاتية مع البيانات وهيكل التوقيع. ثم نضيف بيانات الموقّع ، ونضع علامة لفصل التوقيع وبدء التشكيل النهائي للتوقيع. يتم نقل النتيجة إلى BIO. يبقى فقط لاستخراج مجموعة من البايتات من BIO. غالبًا ما يتم استخدام تحويل BIO إلى مجموعة من وحدات البايت والعكس ، لذلك من الأفضل وضعها في طريقة منفصلة:
BIO بالبايت [] والعكس بالعكس 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 {
كما هو الحال في CryptoPro ، من الضروري استخراج معلومات حول خوارزمية تجزئة التوقيع من الشهادة. ولكن في حالة OpenSSL ، يتم تخزينها مباشرة في الشهادة:
جلب خوارزمية التجزئة 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); }
اتضح أن الطريقة صعبة للغاية ، لكنها تعمل. يمكنك العثور على طريقة EVP_get_digestbynid في الوثائق 1.0.2 ، ولكن مكتبات الإصدار التي نستخدمها لا تقوم بتصديرها. لذلك ، نشكل أولاً nid ، وعلى أساسه اسم قصير. وبواسطة اسم قصير ، يمكنك استخراج الخوارزمية بالطريقة العادية للبحث بالاسم.
التحقق من التوقيع
يحتاج التوقيع المستلم إلى التحقق. يتحقق OpenSSL من التوقيع على النحو التالي:
التحقق من التوقيع 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 {
أولاً ، يتم تحويل بيانات التوقيع من صفيف البايت إلى بنية CMS:
تشكيل هيكل 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;
, .
الخلاصة
, . X509Certificate2 (mono) . .
, Windows . . Linux , , .
CSP 5.0 , RSA . , , , RSA, , .
المراجع
- OpenSSL 1.0.2 ManPages ;
- OpenSSL 1 2 ;
- OpenSSL :
- cms_smime.c;
- Wiki OpenSSL ;
- mono:
- RSAManaged ;