Какво означава, че SSL_library_init не е reentrant за практически цели?

Използвам библиотека openssl, за да отворя TLS връзка към някакъв сървър. Четейки документацията на библиотеката (да, някои хора все още четат документация и man страници) се натъкнах на изречението „SSL_libary_init() не е reentrant".

Разбирам най-общо казано какво е функция без повторно влизане: т.е. някаква функция, която поддържа вътрешно състояние по такъв начин, че извикването й два пъти по едно и също време или прекъсването й, докато се изпълнява, може да причини хаос (функцията не прави това, което очакват повикващите) .

Но в конкретния случай на SSL_library_init() се чудя какво всъщност означава.

  • означава ли, че ако възникне някакво прекъсване при извикване на SSL_library_init(), то няма да инициализира правилно SSL библиотеката? Следователно трябва ли да деактивирам всички достъпни прекъсвания, преди да го извикам, и да активирам отново необходимите след това?

  • означава ли това, че не е безопасен за нишка и че трябва да се уверя, че две нишки не могат да го извикат едновременно? (изглежда вероятно, дори ако безопасността на нишките не означава точно същото като reentrant).

  • означава ли, че не трябва да го извиквам два пъти през живота на програмата, или че извикването му, докато SSL връзките са отворени, ще предизвика хаос?

ТЪЙ КАТО работя върху прокси, като единият край е клиент, а другият е сървър, и двата края биха могли потенциално да използват TLS услуги (но аз също мога да бъда само един край или нито един). Трябва ли да управлявам SSL библиотека като сингълтон за цялата система? Ако случаят е такъв, той е достатъчно лесен за управление, но не е точно проблем с повторното влизане, както разбирам думата.

Не знам кратката дума за функция, която трябва да се извика само веднъж...

Аз също имам подобен въпрос за SSL_CTX_new(). В документацията се посочва, че трябва да се извиква само веднъж за живота на програмата. Това е досадно, тъй като изглежда ограничава както сървъра, така и клиента (или няколко независими сървърни или клиентски екземпляра, работещи в един и същ процес) да използват един и същ SSL_METHOD и не се чувства правилно, но все пак се надявам в този случай да е просто някаква неточност в документацията .

Някой има ли достатъчно опит с openSSL, за да обясни какво трябва или не трябва да правя с кода за инициализиране на OpenSSL, за да остана на сигурно място?


person kriss    schedule 28.03.2013    source източник
comment
Не познавам SSL, така че не ме дръжте за това, което казвам по-нататък, но най-често, когато казват, че тази функция не е повторно влизане, те го казват, защото имат достъп до някаква глобална променлива. Тъй като това е функция init, има смисъл просто да я извикате веднъж в main и да приключите с нея (така че да не се интересувате от безопасността на нишките или възможността за повторно влизане!). SSL_CTX_new също звучи така, сякаш инициализира глобални променливи, така че извикването му многократно не постига нищо (освен може би изтичане на памет). Отново, не знам SSL, така че всичко това са само предположения.   -  person Shahbaz    schedule 28.03.2013
comment
@Shahbaz: да, вероятно си прав. Няма много общо с повторното влизане, но вероятно това означава. Второто е по-досадно, ако всъщност е gloabl, защото задава някаква таблица за изпращане (но все пак се надявам, че наистина няма да е глобално: ако е къде, защо трябва да разпределя памет?   -  person kriss    schedule 28.03.2013
comment
отново, нямам представа какво е SSL, но гледам документацията на SSL_CTX_new, не виждам да се споменава, че трябва да се извиква само веднъж за целия живот на програмата. Къде го видя това?   -  person Shahbaz    schedule 28.03.2013
comment
Просто се надявам, че това означава, че трябва да се извика поне веднъж на програма. Ако означава веднъж и само веднъж, това е проблем, защото тогава всички отворени SSL връзки ще трябва да използват едно и също ниво на протокол (таблица за изпращане SSL_METHOD).   -  person kriss    schedule 29.03.2013
comment
Виждам. Всъщност, тъй като връща SSL_CTX *, подозирам, че е добре, ако го извикате няколко пъти; Ще получите множество контексти. Казва се веднъж за живот на програмата, вероятно защото не са подозирали, че някой би искал да има множество контексти в една и съща програма, така че казват, че един е достатъчен (не че повече е вредно). Може да получите по-добра представа, ако попитате в техния пощенски списък (ако имат такъв) или погледнете изходния код.   -  person Shahbaz    schedule 29.03.2013


Отговори (1)


Мога да ви подскажа въз основа на това, което виждам. Имам уеб сървър CentOS 7 / Django 1.9.3 / Apache 2.4 / mod_ssl / Python 3.4, който получава https: връзки от потребители. Докато обработва заявки, сървърът трябва да направи запитване към бекенд за информация, необходима за удовлетворяване на заявката. Заявката за задната част работи добре, когато се изпълнява самостоятелно, но генерира:

OSError(0, 'Error')
Line 810, /lib64/python3.4/ssl.py

803  def do_handshake(self, block=False):
804      """Perform a TLS/SSL handshake."""
805      self._check_connected()
806      timeout = self.gettimeout()
807      try:
808          if timeout == 0.0 and block:
809              self.settimeout(None)
810          self._sslobj.do_handshake()          # <<<-------

когато се изпълнява в контекста на Apache WSGI.

Ако в действителност mod_ssl причинява грешката ми, това означава, че библиотеката е безопасна за нишки (тъй като Apache разбира се може да обслужва множество уеб заявки едновременно), но не е повторно навлизане (тъй като SSL не може да се използва два пъти -- например за преден и бек- крайни връзки -- в една нишка).

Със сигурност двустепенните уеб сървъри са зрял модел на проектиране, така че трябва да има заобиколно решение. Благодаря, че публикувахте малко относно невлизането - това беше първата следа, която открих и която може да е причината за моя проблем.

За справка, моят код извиква RESTful услуга, която взема отличителното име на потребителя, извлечено от SSL_CLIENT_CERT, и връща потребителски атрибути във формат JSON:

import requests, urllib

URL = 'https://example.com/rest/user_info/'

def get_user_info(dn)
    query = URL + urllib.parse.quote(dn)
    return requests.get(query, cert=('server.crt', 'server.key'), verify='ca_bundle.crt').json()

Този код работи перфектно, когато се стартира от командния ред на уеб сървъра (в WSGI директорията като потребителски apache), но се раздува, когато се извика от самия Apache.

person Dave    schedule 05.04.2016