2016-10-17 1 views
0

Я пытаюсь создать безопасный (например, SSL/HTTPS) сервер XML-RPC-клиента. Часть клиент-сервер работает отлично, когда требуемые сертификаты присутствуют в моей системе; Однако, когда я пытаюсь создать сертификаты во время выполнения, я получаю FileNotFoundError при открытии Ssl обернутого гнезда, даже если сертификаты явно присутствуют (поскольку предшествующая функция создала их.)FileNotFoundError Когда файл существует (при создании в текущем скрипте)

Почему FileNotFoundError, если файлы присутствуют? (Если я просто закрываю и перезапускает скрипт python, при открытии сокета не возникает ошибки, и все работает без каких-либо проблем.)

Я искал в другом месте решения, но самый лучший/ближайший ответ, который я нашел, , возможно, «условия гонки» между созданием сертификатов и их открытием. Тем не менее, я попытался добавить «сон», чтобы облегчить возможность гонки (а также запустить каждую функцию отдельно через меню ввода пользователя) с одинаковой ошибкой каждый раз.

Что мне не хватает?

Вот отрывок из моего кода:

import os 
import threading 
import ssl 
from xmlrpc.server import SimpleXMLRPCServer 
import certs.gencert as gencert # <---- My python module for generating certs 

... 

rootDomain = "mydomain" 
CERTFILE = "certs/mydomain.cert" 
KEYFILE = "certs/mydomain.key" 

... 

def listenNow(ipAdd, portNum, serverCert, serverKey): 

    # Create XMLRPC Server, based on ipAdd/port received 
    server = SimpleXMLRPCServer((ipAdd, portNum)) 

    # **THIS** is what causes the FileNotFoundError ONLY if 
    # the certificates are created during THE SAME execution 
    # of the program. 
    server.socket = ssl.wrap_socket(server.socket, 
            certfile=serverCert, 
            keyfile=serverKey, 
            do_handshake_on_connect=True, 
            server_side=True) 
    ... 
    # Start server listening [forever] 
    server.serve_forever() 

... 

# Verify Certificates are present; if not present, 
# create new certificates 
def verifyCerts(): 

    # If cert or key file not present, create new certs 
    if not os.path.isfile(CERTFILE) or not os.path.isfile(KEYFILE): 

     # NOTE: This [genert] will create certificates matching 
     # the file names listed in CERTFILE and KEYFILE at the top 
     gencert.gencert(rootDomain) 
     print("Certfile(s) NOT present; new certs created.") 

    else: 
     print("Certfiles Verified Present") 


# Start a thread to run server connection as a daemon 
def startServer(hostIP, serverPort): 

    # Verify certificates present prior to starting server 
    verifyCerts() 

    # Now, start thread 
    t = threading.Thread(name="ServerDaemon", 
         target=listenNow, 
         args=(hostIP, 
           serverPort, 
           CERTFILE, 
           KEYFILE 
           ) 
         ) 
    t.daemon = True 
    t.start() 


if __name__ == '__main__': 
    startServer("127.0.0.1", 12345) 
    time.sleep(60) # <--To allow me to connect w/client before closing 

Когда я бегу выше, с NO сертификатов, присутствующих, это ошибка я получаю:

$ python3 test.py 
Certfile(s) NOT present; new certs created. 
Exception in thread ServerDaemon: 
Traceback (most recent call last): 
File "/usr/lib/python3.5/threading.py", line 914, in _bootstrap_inner 
self.run() 
File "/usr/lib/python3.5/threading.py", line 862, in run 
self._target(*self._args, **self._kwargs) 
File "test.py", line 41, in listenNow 
server_side=True) 
File "/usr/lib/python3.5/ssl.py", line 1069, in wrap_socket 
ciphers=ciphers) 
File "/usr/lib/python3.5/ssl.py", line 691, in __init__ 
self._context.load_cert_chain(certfile, keyfile) 
FileNotFoundError: [Errno 2] No such file or directory 

Когда я просто повторно запустить сценарий второй раз (т. е. файлы сертификатов уже присутствуют при запуске, все работает так, как ожидалось, с ошибками NO, и я могу просто подключить моего клиента.

$ python3 test.py 
Certfiles Verified Present 

Что мешает функции ssl.wrap_socket видеть/получать доступ к только что созданным файлам (и, таким образом, создавать исключение FileNotFoundError)?

EDIT 1: Спасибо за комментарии Джон Гордон. Вот копия gencert.py, любезно Атулу Varm, здесь https://gist.github.com/toolness/3073310

import os 
import sys 
import hashlib 
import subprocess 
import datetime 

OPENSSL_CONFIG_TEMPLATE = """ 
prompt = no 
distinguished_name = req_distinguished_name 
req_extensions = v3_req 
[ req_distinguished_name ] 
C      = US 
ST      = IL 
L      = Chicago 
O      = Toolness 
OU      = Experimental Software Authority 
CN      = %(domain)s 
emailAddress   = [email protected] 
[ v3_req ] 
# Extensions to add to a certificate request 
basicConstraints = CA:FALSE 
keyUsage = nonRepudiation, digitalSignature, keyEncipherment 
subjectAltName = @alt_names 
[ alt_names ] 
DNS.1 = %(domain)s 
DNS.2 = *.%(domain)s 
""" 

MYDIR = os.path.abspath(os.path.dirname(__file__)) 
OPENSSL = '/usr/bin/openssl' 
KEY_SIZE = 1024 
DAYS = 3650 
CA_CERT = 'ca.cert' 
CA_KEY = 'ca.key' 

# Extra X509 args. Consider using e.g. ('-passin', 'pass:blah') if your 
# CA password is 'blah'. For more information, see: 
# 
# http://www.openssl.org/docs/apps/openssl.html#PASS_PHRASE_ARGUMENTS 
X509_EXTRA_ARGS =() 

def openssl(*args): 
    cmdline = [OPENSSL] + list(args) 
    subprocess.check_call(cmdline) 

def gencert(domain, rootdir=MYDIR, keysize=KEY_SIZE, days=DAYS, 
      ca_cert=CA_CERT, ca_key=CA_KEY): 
    def dfile(ext): 
     return os.path.join('domains', '%s.%s' % (domain, ext)) 

    os.chdir(rootdir) 

    if not os.path.exists('domains'): 
     os.mkdir('domains') 

    if not os.path.exists(dfile('key')): 
     openssl('genrsa', '-out', dfile('key'), str(keysize)) 

    # EDIT 3: mydomain.key gets output here during execution 

    config = open(dfile('config'), 'w') 
    config.write(OPENSSL_CONFIG_TEMPLATE % {'domain': domain}) 
    config.close() 

    # EDIT 3: mydomain.config gets output here during execution 

    openssl('req', '-new', '-key', dfile('key'), '-out', dfile('request'), 
      '-config', dfile('config')) 

    # EDIT 3: mydomain.request gets output here during execution  

    openssl('x509', '-req', '-days', str(days), '-in', dfile('request'), 
      '-CA', ca_cert, '-CAkey', ca_key, 
      '-set_serial', 
      '0x%s' % hashlib.md5(domain + 
           str(datetime.datetime.now())).hexdigest(), 
      '-out', dfile('cert'), 
      '-extensions', 'v3_req', '-extfile', dfile('config'), 
      *X509_EXTRA_ARGS) 

    # EDIT 3: mydomain.cert gets output here during execution 

    print "Done. The private key is at %s, the cert is at %s, and the " \ 
      "CA cert is at %s." % (dfile('key'), dfile('cert'), ca_cert) 

if __name__ == "__main__": 
    if len(sys.argv) < 2: 
     print "usage: %s <domain-name>" % sys.argv[0] 
     sys.exit(1) 
    gencert(sys.argv[1]) 

EDIT 2: Что касается комментария Джона, «это может означать, что эти файлы создаются, но не в каталоге [ I] ожидать «:

Когда у меня есть каталог, открытый в другом окне, я вижу, что файлы появляются в правильном месте во время выполнения. Кроме того, при запуске скрипта test.py второй раз без изменений файлы идентифицируются как присутствующие в правильном (одинаковом) месте. Это заставляет меня думать, что расположение файлов не является проблемой. Спасибо за предложение. Я буду продолжать смотреть.

EDIT 3: Я прошел через программу gencert.py, и каждый из файлов правильно выводится в нужное время во время выполнения. Я указал, когда именно они были выведены в указанном выше файле с меткой «EDIT 3»

Хотя gencert приостановлен в ожидании моего ввода (raw_input), я могу без проблем открывать/просматривать/редактировать указанные файлы в другой программе.

Кроме того, с запущенным экземпляром test.py (приостановлено, ожидая ввода пользователя, сразу после mydomain.cert появляется), я могу запустить второй экземпляр test.py в другом терминале, и он видит/использует файлы просто отлично.

В первом случае, однако, если я продолжу программу, она выведет «FileNotFoundError».

+1

Вы не показали нам нужен код для 'gencert', поэтому возможно, что функция просто не делает то, что она должна. Как очень простая проверка, попробуйте добавить еще один, если не os.path.isfile (...)' call inside' verifyCerts() ', _after_ вызов на 'gencert()'. –

+1

Делает ли 'gencert()' _close_ файлы после их создания? Если нет, это может объяснить поведение, которое вы видите, - файлы, возможно, не будут записаны на диск до тех пор, пока программа не будет существовать. –

+0

Спасибо за комментарии, @John Вот ссылка на функцию gencert, которую я использовал, о которой я расскажу более подробно и посмотрю, решает ли она мою проблему: https://gist.github.com/toolness/3073310 – JLR

ответ

0

Проблема, содержащаяся в вышеупомянутых случаях, связана с использованием os.chdir(rootdir), как это предложил Джон; однако специфика немного отличается от созданных файлов, находящихся в неправильном месте. Проблема заключается в том, что текущий рабочий каталог (cwd) текущей программы изменяется на gencert(). Вот специфика:

  1. Программа запускается с test.py, который вызывает verifyCerts(). На этом этапе программа запускается в текущем каталоге какой-либо папки test.py работает внутри. Используйте os.getcwd(), чтобы найти текущий каталог на этом этапе. В этом случае (в качестве примера), он работает в:

    /home/name/testfolder/

  2. Далее, os.path.isfile() ищет файлы "сертификаты/mydomain.cert" и "сертификаты/mydomain.key"; на основе пути к файлу выше [например, УХО], он ищет следующие файлы:

    /home/name/testfolder/certs/mydomain.cert
    /home/name/testfolder/certs/mydomain.key

  3. Вышеуказанные файлы нет, поэтому программа выполняет gencert.gencert(rootDomain) которая правильно создает оба файла, как ожидается, в строго определенных местах, указанных выше в количестве 2.

  4. проблема действительно os.chdir() вызов: Когда gencert() выполняет, он использует os.chdir() для изменения cwd на «rootdir», который является os.path.abspath(os.path.dirname(__file__)), который является каталогом текущего файла (gencert.py). Поскольку этот файл находится в папке глубже, новый УХО становится:

    /home/name/testfolder/certs/

  5. Когда gencert() выполнение заканчивается и управление возвращается к test.py, то УХО никогда не меняется; cwd остается как /home/name/testfolder/certs/, даже когда выполнение возвращается к test.py.

  6. Позже, когда ssl.wrap_socket() пытается найти serverCert и serverKey, он ищет «CERTS/mydomain.cert» и «CERTS/mydomain.key» в Дов, поэтому здесь полный путь к чему бы это ищет:

    /home/name/testfolder/certs/certs/mydomain.cert /home/name/testfolder/certs/certs/mydomain.key

  7. Эти два файла НЕ присутствует, так что программа корректно возвращает "FileNotFoundError".

Решение

A) Переместить файл "gencert.py" в тот же каталог, как «тест.р»

B) В начале„gencert.py“добавить cwd = os.getcwd() записать оригинальное УХО программы, а затем, в самом конце добавить os.chdir(cwd), чтобы вернуться к исходному Д перед завершением и передачи управления назад вызывающей программе.

Я пошел с опцией «B», и моя программа теперь работает безупречно. Я ценю помощь от Джона Гордона, чтобы указать мне на поиск источника моей проблемы.

Смежные вопросы