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.
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.