Automate stdin with Python using stdin.write()

2019-06-13 15:40发布

问题:

I am trying to automate the setup of generating self-signed SSL certificate. This is my code:

#!/usr/bin/env python   
import subprocess

pass_phrase = 'example'
common_name = 'example.com'
webmaster_email = 'webmaster@example.com'

proc = subprocess.Popen(['openssl', 'req', '-x509', '-newkey', 'rsa:2048', '-rand', '/dev/urandom', '-keyout', '/etc/pki/tls/private/server.key', '-out', '/etc/pki/tls/certs/server.crt', '-days', '180'], stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.PIPE)

for i in range(2):
    proc.stdin.write(pass_phrase)
for i in range(5):
    proc.stdin.write('.')
proc.stdin.write(common_name)
proc.stdin.write(webmaster_email)
proc.stdin.flush()

stdout, stderr = proc.communicate() 

When I run it, it still prompts me for the PEM passphrase, then returns this error:

Country Name (2 letter code) [XX]:weird input :-(
problems making Certificate Request

It should feed in the passphrase above and not prompt me for anything. Any ideas what's going wrong?

PS. I know about pexpect. Please don't suggest it to me.

Edit: Upon further investigation, I've figured it out. If you don't specify -nodes, the private key will be encrypted. So, OpenSSL will prompt for a PEM passphrase immediately. This means the order of my stdin.write() gets messed up. I guess the alternative is to use -nodes and encrypt the private key later.

回答1:

There are several errors in your code e.g., no newlines are sent to the child process.

The main issue is that openssl expects the pass phrase directly from the terminal (like getpass.getpass() in Python). See the first reason in Why not just use a pipe (popen())?:

First an application may bypass stdout and print directly to its controlling TTY. Something like SSH will do this when it asks you for a password. This is why you cannot redirect the password prompt because it does not go through stdout or stderr.

pexpect that provides pseudo-tty works fine in this case:

#!/usr/bin/env python
import sys
from pexpect import spawn, EOF

pass_phrase = "dummy pass Phr6se"
common_name = "example.com"
email = "username@example.com"
keyname, certname = 'server.key', 'server.crt'

cmd = 'openssl req -x509 -newkey rsa:2048 -rand /dev/urandom '.split()
cmd += ['-keyout', keyname, '-out', certname, '-days', '180']

child = spawn(cmd[0], cmd[1:], timeout=10)    
child.logfile_read = sys.stdout # show openssl output for debugging

for _ in range(2):
    child.expect('pass phrase:')
    child.sendline(pass_phrase)
for _ in range(5):
    child.sendline('.')
child.sendline(common_name)
child.sendline(email)
child.expect(EOF)
child.close()
sys.exit(child.status)

An alternative is to try to use -passin option to instruct openssl to get the pass phrase from a different source (stdin, a file, pipe, envvar, command-line). I don't know whether it works with openssl req command.



回答2:

Two problems:

  1. You are not giving it the data it expects in the order it expects. At some point it is expecting a country code and you are giving it some other data instead.
  2. The write() method of file objects does not automatically insert a newline. You need to add "\n" to your strings or write() separate "\n" strings out after each line of input you want to feed to the program. For example: proc.stdin.write(pass_phrase + "\n")