Git Product home page Git Product logo

acme-nosudo's People

Contributors

101100 avatar bryant1410 avatar diafygi avatar groestl avatar johnlunney avatar nylen avatar simonbru avatar surr avatar taylormonacelli avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

acme-nosudo's Issues

Do not abort everything when a challenge verification failed

This is extremely annoying. 😠 I had a validation for 4 domains running and was running the wrong python server for one of them. Now I need to start everything from scratch, which includes temporarily taking down the proper webserver.

It would be really great if your script offered to retry in case the validation failed.

Error running sign_csr.py

...
Requesting signature...
Error: csr_data:
POST https://acme-v01.api.letsencrypt.org/acme/new-cert
{
    "header": {
        "alg": "RS256", 
        "jwk": {
            "e": "AQAB", 
            "kty": "RSA", 
            "n": "SNIP"
        }
    }, 
    "payload": "SNIP", 
    "protected": "SNIP", 
    "signature": "SNIP"
}
{"type":"urn:acme:error:malformed","detail":"Error unmarshaling certificate request","status":400}
Traceback (most recent call last):
  File "./sign_csr.py", line 441, in <module>
    signed_crt = sign_csr(args.public_key, args.csr_path, email=args.email, file_based=args.file_based)
  File "./sign_csr.py", line 386, in sign_csr
    resp = urllib2.urlopen(csr_url, csr_data)
  File "/usr/lib/python2.7/urllib2.py", line 127, in urlopen
    return _opener.open(url, data, timeout)
  File "/usr/lib/python2.7/urllib2.py", line 410, in open
    response = meth(req, response)
  File "/usr/lib/python2.7/urllib2.py", line 523, in http_response
    'http', request, response, code, msg, hdrs)
  File "/usr/lib/python2.7/urllib2.py", line 448, in error
    return self._call_chain(*args)
  File "/usr/lib/python2.7/urllib2.py", line 382, in _call_chain
    result = func(*args)
  File "/usr/lib/python2.7/urllib2.py", line 531, in http_error_default
    raise HTTPError(req.get_full_url(), code, msg, hdrs, fp)
urllib2.HTTPError: HTTP Error 400: Bad Request

Behind a load balancer the verification request fails

Press Enter when you've run the above commands in a new terminal window...
STEP 4: You need to run this command on domain.com (don't stop the python command until the next step).

sudo python -c "import BaseHTTPServer; \
    h = BaseHTTPServer.BaseHTTPRequestHandler; \
    h.do_GET = lambda r: r.send_response(200) or r.end_headers() or r.wfile.write('cu9iNGw0OyYzNJen0qLKhS8nXfYx9GRgt_YJZVfthZ8.mOKrxpIJxDEEW190JjtXbUZ385PCg0U_xpRJbQZCHq4'); \
    s = BaseHTTPServer.HTTPServer(('0.0.0.0', 80), h); \
    s.serve_forever()"

Press Enter when you've got the python command running on your server...
Requesting verification for domain.com...
Waiting for domain.com challenge to pass...
Traceback (most recent call last):
  File "sign_csr.py", line 441, in <module>
    signed_crt = sign_csr(args.public_key, args.csr_path, email=args.email, file_based=args.file_based)
  File "sign_csr.py", line 372, in sign_csr
    raise KeyError("'{0}' challenge did not pass: {1}".format(i['domain'], challenge_status))
KeyError: "'domain.com' challenge did not pass: {u'status': u'invalid', u'validationRecord': [{u'url': u'domain.com/.well-known/acme-challenge/cu9iNGw0OyYzNJen0qLKhS8nXfYx9GRgt_YJZVfthZ8', u'hostname': u'domain.com', u'addressUsed': u'52.30.135.131', u'port': u'80', u'addressesResolved': [u'52.30.135.131', u'52.19.83.189']}], u'keyAuthorization': u'cu9iNGw0OyYzNJen0qLKhS8nXfYx9GRgt_YJZVfthZ8.mOKrxpIJxDEEW190JjtXbUZ385PCg0U_xpRJbQZCHq4', u'uri': u'https://acme-v01.api.letsencrypt.org/acme/challenge/LFDuAzyge5nTj6Gn79vjIM4mrL0oGXxbAUCzJhiUjuc/1814396', u'token': u'cu9iNGw0OyYzNJen0qLKhS8nXfYx9GRgt_YJZVfthZ8', u'error': {u'type': u'urn:acme:error:unauthorized', u'detail': u'Invalid response from http://domain.com/.well-known/acme-challenge/cu9iNGw0OyYzNJen0qLKhS8nXfYx9GRgt_YJZVfthZ8 [52.30.135.131]: 503'}, u'type': u'http-01'}"

This is the error I get, you notice the IP addresses vary presumably because it's connecting to the load balancer which is sitting in front of the Python script listening on port 80.

Is there a way around that or do I have to temporarily mess with my DNS records in order to return a valid IP?

(PS. Not sure if this is the correct place to file this issue, perhaps the main letsencrypt repo would be better)

Works great with Hiawatha Webserver

Thanks for writing this script, I like that one can read through it quickly and it doesn't contain code to change my configs. (That does not mean, that the official client is bad - it is just not as suited for power users as your script is.)

Now I have Let's Encrypt with Hiawatha working 👍

Here's a quick tutorial I wrote for other Hiawatha users. The only real difference is in creating the final certificate file, which also contains domain.key:

cat domain.key signed.crt lets-encryt-x1-cross-signed.pem > letsencrypt_hiawatha.crt

Thanks for your work @diafygi !

Error when running sign_csr.py

I followed all the example steps to test and the outcome is:

$ python sign_csr.py --public-key user.pub domain.csr > domain.crt
Traceback (most recent call last):
File "sign_csr.py", line 441, in
signed_crt = sign_csr(args.public_key, args.csr_path, email=args.email, file_based=args.file_based)
File "sign_csr.py", line 26, in sign_csr
nonce_req = urllib2.Request("{}/directory".format(CA))
ValueError: zero length field name in format

'NoneType' object has no attribute 'groups'

Following the instructions with generating user key and pub as well as domain key and csr I got this error when invoking your script:

Reading pubkey file...
Traceback (most recent call last):
  File "sign_csr.py", line 429, in <module>
    signed_crt = sign_csr(args.public_key, args.csr_path, email=args.email)
  File "sign_csr.py", line 39, in sign_csr
    pub_hex, pub_exp = re.search("Modulus\:\s+00:([a-f0-9\:\s]+?)Exponent\: ([0-9]+)", out, re.MULTILINE|re.DOTALL).groups()
AttributeError: 'NoneType' object has no attribute 'groups'

I am on Mac OS X 10.10.5 with Python 2.7.10. I tried re-generating user / domain keys several times... Any ideas?

fails to generate csr

just was able to do my 1st test after LE went public.

When teh script is generating the CSR it fails

Requesting signature...
Error: csr_data:
POST https://acme-v01.api.letsencrypt.org/acme/new-cert
{
"header": {
"alg": "RS256",
"jwk": {
"e": "AQAB",
"kty": "RSA",

encoded data

{"type":"urn:acme:error:malformed","detail":"Error unmarshaling certificate request","status":400}
Traceback (most recent call last):
File "letsencrypt-nosudo-master/sign_csr.py", line 441, in
signed_crt = sign_csr(args.public_key, args.csr_path, email=args.email, file_based=args.file_based)
File "letsencrypt-nosudo-master/sign_csr.py", line 386, in sign_csr
resp = urllib2.urlopen(csr_url, csr_data)
File "/usr/lib/python2.7/urllib2.py", line 127, in urlopen
return _opener.open(url, data, timeout)
File "/usr/lib/python2.7/urllib2.py", line 407, in open
response = meth(req, response)
File "/usr/lib/python2.7/urllib2.py", line 520, in http_response
'http', request, response, code, msg, hdrs)
File "/usr/lib/python2.7/urllib2.py", line 445, in error
return self._call_chain(_args)
File "/usr/lib/python2.7/urllib2.py", line 379, in _call_chain
result = func(_args)
File "/usr/lib/python2.7/urllib2.py", line 528, in http_error_default
raise HTTPError(req.get_full_url(), code, msg, hdrs, fp)
urllib2.HTTPError: HTTP Error 400: Bad Request

Add alternative to HTTPServer?

Thanks for this - I was able to get running in about ten minutes on FreeBSD shared hosting. :)

I made a few tweaks, and I'm hoping to get approval to publish them from my employer.

It'd be nice to have alternatives to HTTPServer - for example, offering commands like:

echo -n "{\"header\": {\"alg\": \"RS256\"},...}" > /var/www/.well-known/acme-challenge/qlWnoQpvBvNvOyZlAGLvsD

would work for most people (and doesn't require stopping your HTTP server.)

Many Errors when using this script

At first , i encountered some...

Value Error: Zero length field name in format

...errors (for example in line 21 / 44).

After fixing all that, i got this one:

Reading pubkey file...
Traceback (most recent call last):
File "sign_csr.py", line 429, in
signed_crt = sign_csr(args.public_key, args.csr_path, email=args.email)
File "sign_csr.py", line 45, in sign_csr
pub_exp = binascii.unhexlify(pub_exp)
TypeError: Odd-length string

Where is the problem, i don't get it! I also tried pub_exp.strip() to fix it, but it didn't work out that fine!
(I followed your tutorial for creating all certificates!)

Add note about using cross-signed Intermediate Certificates to README

What follows are the frustrations I had while setting up letsencrypt using your script, which I realize are my own fault. I suggest clarifying in your README the confusions I had in order to help the next poor soul with more ambition than brains in getting set up with letsencrypt :)

While using your tool I didn't realize I needed to append the cross-signed certificate to my certificate for the nginx config, and by the time I realized I needed that step, I had a hard time finding the cross-signed certificate. I wasn't even sure if the cross-signed certificate was something I needed to download or generate unique to my certificate (because I'm a TLS newbie).

I suggest a quick mention that the signed crt file will work in the future when all browsers trust the letsencrypt root, but currently one needs to grab the pem file labeled "Let’s Encrypt Authority X1 (IdenTrust cross-signed)" from https://letsencrypt.org/certificates/ and cat it to the back of the signed crt file. The reason I figured this out is because of the HN post from lolware has a link to the certificates page (sad aside: I had looked at that letsencrypt certificates page so many times and somehow missed that battery of useful pem files at the top).

Thanks for the useful tool. I think you did a great job and soem added notes will hopefully make some people's use of it go more smoothly.

Works with 2008R2 and above.

The only problem was tempfile.NamedTemporaryFile() opens file with non-shared access, so openssl can neither read .json nor write to .sig files.
Quick fix was to close file handle and reopen it before seek(0) so openssl can do its job

openssl errors
.sig
openssl dgst Error opening output file system library:fopen:Broken pipe:
.json
register_bpl9wi.json: Permission denied

Validate that the target domain is serving the challenge

Since letsencrypt-nosudo already requires network access it seems like step 4-N (when HTTPServer is run) could trivially validate that the correct challenge is being served and give better feedback / give the user a chance to fix things.

Common-ish errors off the top of my head:

  1. Challenge served is from a previous subdomain - try again with the updated command
  2. Challenge is returning a non-200 code from $WEB_SERVER - stop this web server and try again
  3. Challenge is not valid JSON - is the domain behind a proxy? (iirc some Cloudflare settings rewrite things in flight.)

acme-v01.api.letsencrypt.org says "Unable to read/verify body :: JWS verification error"

I'm following the readme and when I start up the python webserver this happens (There's a bunch of cryptology stuff here that I don't understand so I replaced it all with varying numbers of n's):

Press Enter when you've got the python command running on your server...
Requesting verification for my.totallyawesomesite.com...
Error: test_data:
POST https://acme-v01.api.letsencrypt.org/acme/challenge/nnnnnnnnnnnnnnnnnnnnnnnnn…
{
    "header": {
        "alg": "RS256",
        "jwk": {
            "e": "nnnn",
            "kty": "RSA",
            "n": "nnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnn…"
        }
    },
    "payload": "nnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnn…",
    "protected": "nnnnnnnnnnnnnnnnnnnnnnnnnnnnn…",
    "signature": ""
}
{"type":"urn:acme:error:malformed","detail":"Unable to read/verify body :: JWS verification error","status":400}
Traceback (most recent call last):
  File "sign_csr.py", line 441, in <module>
    signed_crt = sign_csr(args.public_key, args.csr_path, email=args.email, file_based=args.file_based)
  File "sign_csr.py", line 341, in sign_csr
    resp = urllib2.urlopen(test_url, test_data)
  File "/usr/lib64/python2.6/urllib2.py", line 126, in urlopen
    return _opener.open(url, data, timeout)
  File "/usr/lib64/python2.6/urllib2.py", line 397, in open
    response = meth(req, response)
  File "/usr/lib64/python2.6/urllib2.py", line 510, in http_response
    'http', request, response, code, msg, hdrs)
  File "/usr/lib64/python2.6/urllib2.py", line 435, in error
    return self._call_chain(*args)
  File "/usr/lib64/python2.6/urllib2.py", line 369, in _call_chain
    result = func(*args)
  File "/usr/lib64/python2.6/urllib2.py", line 518, in http_error_default
    raise HTTPError(req.get_full_url(), code, msg, hdrs, fp)
urllib2.HTTPError: HTTP Error 400: Bad Request

Is the signature supposed to be blank?

It's a nosudo script then why "sudo python..." ?

Is there any other way executing the challenge passing?
Provided that I have no sudo access, and that's the reason I'm here in first place, it's highly unlikely that the STEP 4 command which is the domain challenge step will work.

Please suggest.
Thanks in advance for the help.

Demo link has an unverifed cert

"This Connection is Untrusted

You have asked ******** to connect securely to letsencrypt.daylightpirates.org, but we can't confirm that your connection is secure.

Normally, when you try to connect securely, sites will present trusted identification to prove that you are going to the right place. However, this site's identity can't be verified.
What Should I Do?

If you usually connect to this site without problems, this error could mean that someone is trying to impersonate the site, and you shouldn't continue."

Ubuntu 14.04.2 LTS (GNU/Linux 3.13.0-45-generic x86_64) / Traceback included...

DISCLAIMER:

  1. I understand it's beta. I'm not a programmer. more the network guy :)
  2. My domain is whitelisted at letsencrypt.org
  3. I installed letsencrypt-nosudo on my webhoster's Ubuntu 14.04.2 LTS (using git, it was not installed), my unsecure area (http) is at /home/nines9/public_html/, my secure area (https) is at /home/nines9/secure_html/, I've used a .htaccess script to redirect all HTTP traffic to HTTPS:
<IfModule mod_rewrite.c>

RewriteEngine On
RewriteCond %{HTTPS} off
RewriteRule (.*) https://%{HTTP_HOST}%{REQUEST_URI}

</IfModule>

This feature is currently turned OFF, but it takes a few hours to reload the settings, hence the redirect is still in place :(

  1. I checked for openssl (OpenSSL 1.0.1f 6 Jan 2014) and python (Python 2.7.6), both there.
  2. cd to letsencrypt-nosudo and follow the instructions at: https://github.com/diafygi/letsencrypt-nosudo/#how-to-use-the-script
  3. Output: Before the start
nines9@magnesium:~/letsencrypt-nosudo$ ls -lha
total 92K
drwxr-xr-x 3 nines9 users 4.0K Nov 19 19:57 .
drwx---r-x 9 nines9 users 4.0K Nov 19 19:57 ..
drwxr-xr-x 8 nines9 users 4.0K Nov 19 19:57 .git
-rw-r--r-- 1 nines9 users  675 Nov 19 19:57 .gitignore
-rw-r--r-- 1 nines9 users  34K Nov 19 19:57 LICENSE
-rw-r--r-- 1 nines9 users  17K Nov 19 19:57 README.md
-rw-r--r-- 1 nines9 users  17K Nov 19 19:57 sign_csr.py
  1. Now, let's start..

First command (terminal 1):

nines9@magnesium:~/letsencrypt-nosudo$ openssl genrsa 4096 > user.key
Generating RSA private key, 4096 bit long modulus
.................................++
.................................................................................................................................................................++
e is 65537 (0x10001)

Second command (terminal 1):

nines9@magnesium:~/letsencrypt-nosudo$ openssl rsa -in user.key -pubout > user.pub
writing RSA key

Third command (terminal 1):

nines9@magnesium:~/letsencrypt-nosudo$ #Alternatively, I want both 9nines.net and www.9nines.net
nines9@magnesium:~/letsencrypt-nosudo$ openssl genrsa 4096 > domain.key
Generating RSA private key, 4096 bit long modulus
.......................................................................................................................................................................................++
.............................................................................++
e is 65537 (0x10001)
nines9@magnesium:~/letsencrypt-nosudo$ openssl req -new -sha256 -key domain.key -subj "/" -reqexts SAN -config <(cat /etc/ssl/openssl.cnf <(printf "[SAN]\nsubjectAltName=DNS:9nines.net,DNS:www.9nines.net")) > domain.csr

Fourth command: Terminal 1, switching to Terminal 2

nines9@magnesium:~/letsencrypt-nosudo$ python sign_csr.py --public-key user.pub domain.csr > signed.crt
Reading pubkey file...
Found public key!
Reading csr file...
Found domains www.9nines.net, 9nines.net
STEP 1: What is your contact email? ([email protected])     
Building request payloads...
Building request for www.9nines.net...
Building request for 9nines.net...
Building request for CSR...
STEP 2: You need to sign some files (replace 'user.key' with your user private key).

openssl dgst -sha256 -sign user.key -out register_gxKP4_.sig register_fFw75_.json
openssl dgst -sha256 -sign user.key -out domain_l3TNrL.sig domain_b0O1LU.json
openssl dgst -sha256 -sign user.key -out domain_VCB1zQ.sig domain_mjx5UI.json
openssl dgst -sha256 -sign user.key -out cert_J4eZU3.sig cert_WrHIam.json

Press Enter when you've run the above commands in a new terminal window...

First command: Terminal 2

nines9@magnesium:~/letsencrypt-nosudo$ openssl dgst -sha256 -sign user.key -out register_gxKP4_.sig register_fFw75_.json

Second command: Terminal 2

nines9@magnesium:~/letsencrypt-nosudo$ openssl dgst -sha256 -sign user.key -out domain_l3TNrL.sig domain_b0O1LU.json

Third command: Terminal 2

nines9@magnesium:~/letsencrypt-nosudo$ openssl dgst -sha256 -sign user.key -out domain_VCB1zQ.sig domain_mjx5UI.json

Fourth command: Terminal 2

nines9@magnesium:~/letsencrypt-nosudo$ openssl dgst -sha256 -sign user.key -out cert_J4eZU3.sig cert_WrHIam.json

Sixt command: Terminal 1

Registering [email protected]...
Requesting challenges for www.9nines.net...
Building challenge responses for www.9nines.net...
Requesting challenges for 9nines.net...
Building challenge responses for 9nines.net...
STEP 3: You need to sign some more files (replace 'user.key' with your user private key).

openssl dgst -sha256 -sign user.key -out challenge_fzPMHb.sig challenge_9hvDtS.json
openssl dgst -sha256 -sign user.key -out challenge_ahkiEM.sig challenge_tN2nj0.json

Press Enter when you've run the above commands in a new terminal window...

Fifth command: Terminal 2

nines9@magnesium:~/letsencrypt-nosudo$ openssl dgst -sha256 -sign user.key -out challenge_fzPMHb.sig challenge_9hvDtS.json

Sixt command: Terminal 2

nines9@magnesium:~/letsencrypt-nosudo$ openssl dgst -sha256 -sign user.key -out challenge_ahkiEM.sig challenge_tN2nj0.json

Seventh command: Terminal 1

STEP 4: You need to run this command on www.9nines.net (don't stop the python command until the next step).

sudo python -c "import BaseHTTPServer; \
    h = BaseHTTPServer.BaseHTTPRequestHandler; \
    h.do_GET = lambda r: r.send_response(200) or r.end_headers() or r.wfile.write('vcDQcSgQoxUI8ZBMkpZzAwnaNefIEQp5grxEtd-O-no.OFd93iap7pzVH0u9CGS6BL7qGaCscvDcA3uyxt-WO7M'); \
    s = BaseHTTPServer.HTTPServer(('0.0.0.0', 80), h); \
    s.serve_forever()"

Press Enter when you've got the python command running on your server...

Eight command: Terminal 1

Requesting verification for www.9nines.net...
Waiting for www.9nines.net challenge to pass...
Traceback (most recent call last):
  File "sign_csr.py", line 415, in <module>
    signed_crt = sign_csr(args.public_key, args.csr_path, email=args.email)
  File "sign_csr.py", line 347, in sign_csr
    raise KeyError("'{}' challenge did not pass: {}".format(i['domain'], challenge_status))
KeyError: "'www.9nines.net' challenge did not pass: {u'status': u'invalid', u'validationRecord': [{u'url': u'http://www.9nines.net/.well-known/acme-challenge/vcDQcSgQoxUI8ZBMkpZzAwnaNefIEQp5grxEtd-O-no', u'hostname': u'www.9nines.net', u'addressUsed': u'64.62.186.8', u'port': u'80', u'addressesResolved': [u'64.62.186.8']}, {u'url': u'https://www.9nines.net/.well-known/acme-challenge/vcDQcSgQoxUI8ZBMkpZzAwnaNefIEQp5grxEtd-O-no', u'hostname': u'www.9nines.net', u'addressUsed': u'64.62.186.8', u'port': u'443', u'addressesResolved': [u'64.62.186.8']}], u'keyAuthorization': u'vcDQcSgQoxUI8ZBMkpZzAwnaNefIEQp5grxEtd-O-no.OFd93iap7pzVH0u9CGS6BL7qGaCscvDcA3uyxt-WO7M', u'uri': u'https://acme-v01.api.letsencrypt.org/acme/challenge/ex_fOS2M1_l-3RY5-kZHgnKd6ZxBaXaHW5HcLkJX7XE/435535', u'token': u'vcDQcSgQoxUI8ZBMkpZzAwnaNefIEQp5grxEtd-O-no', u'error': {u'type': u'urn:acme:error:unauthorized', u'detail': u'Invalid response from http://www.9nines.net/.well-known/acme-challenge/vcDQcSgQoxUI8ZBMkpZzAwnaNefIEQp5grxEtd-O-no [64.62.186.8]: 404'}, u'type': u'http-01'}"

That's it.

OUTPUT of the directory, Terminal 1:

nines9@magnesium:~/letsencrypt-nosudo$ ls -lha
total 108K
drwxr-xr-x 3 nines9 users 4.0K Nov 19 20:31 .
drwx---r-x 9 nines9 users 4.0K Nov 19 19:57 ..
-rw-r--r-- 1 nines9 users 1.6K Nov 19 20:10 domain.csr
-rw-r--r-- 1 nines9 users 3.2K Nov 19 20:09 domain.key
drwxr-xr-x 8 nines9 users 4.0K Nov 19 19:57 .git
-rw-r--r-- 1 nines9 users  675 Nov 19 19:57 .gitignore
-rw-r--r-- 1 nines9 users  34K Nov 19 19:57 LICENSE
-rw-r--r-- 1 nines9 users  17K Nov 19 19:57 README.md
-rw-r--r-- 1 nines9 users  17K Nov 19 19:57 sign_csr.py
-rw-r--r-- 1 nines9 users    0 Nov 19 20:31 signed.crt
-rw-r--r-- 1 nines9 users 3.2K Nov 19 20:02 user.key
-rw-r--r-- 1 nines9 users  800 Nov 19 20:02 user.pub

I tryed this 2 times:
a) with www.9nines.net and 9nines.net (in the e-mail they confirmed both, but I tryed again)
b) with 9nines.net only. Same result.

suspect file-based is broken

I'm not completely sure as I'm not whitelisted I couldn't test it. I believe when using the file-based challenge line 308

.format(n + 4, i['domain'], challenge['token'], responses[n]['data']))

will fail because challenge is not available in that context.

Does not work in the wheezy hosts

Thanks for the very useful script.

Redirected from https://gist.github.com/kennwhite/aa74c164bcdf092a7a10. Tried on Debian Wheezy host.

Everything went alright except at STEP 4 running in the webserver.

Traceback (most recent call last):
  File "<string>", line 1, in <module>
  File "/usr/lib/python2.7/SocketServer.py", line 419, in __init__
    self.server_bind()
  File "/usr/lib/python2.7/BaseHTTPServer.py", line 108, in server_bind
    SocketServer.TCPServer.server_bind(self)
  File "/usr/lib/python2.7/SocketServer.py", line 430, in server_bind
    self.socket.bind(self.server_address)
  File "/usr/lib/python2.7/socket.py", line 224, in meth
    return getattr(self._sock,name)(*args)
socket.error: [Errno 98] Address already in use

Tried to revoke without success.

$ python revoke_crt.py --public-key user.pub domain.crt'
Reading pubkey file...
Found public key!
...
Traceback (most recent call last):
  File "revoke_crt.py", line 136, in <module>
    revoke_crt(args.public_key, args.crt_path)
  File "revoke_crt.py", line 97, in revoke_crt
    resp = urllib2.urlopen("{0}/acme/revoke-cert".format(CA), crt_data)
  File "/usr/lib/python2.7/urllib2.py", line 127, in urlopen
    return _opener.open(url, data, timeout)
  File "/usr/lib/python2.7/urllib2.py", line 410, in open
    response = meth(req, response)
  File "/usr/lib/python2.7/urllib2.py", line 523, in http_response
    'http', request, response, code, msg, hdrs)
  File "/usr/lib/python2.7/urllib2.py", line 448, in error
    return self._call_chain(*args)
  File "/usr/lib/python2.7/urllib2.py", line 382, in _call_chain
    result = func(*args)
  File "/usr/lib/python2.7/urllib2.py", line 531, in http_error_default
    raise HTTPError(req.get_full_url(), code, msg, hdrs, fp)
urllib2.HTTPError: HTTP Error 400: Bad Request

Use “LE official client” “user registration” files?

Guys,
Anyway to use “user registration” files created by “LE official client”?
I’d do it myself, but my python knowledge is not good enough to dig through official client script...
I’ll appreciate, if you can show an easy way to do it :)

code that automates openssl request

Hello, thank you for your work.
Here is some code taht i use to automate user keys and domain ones.
Hope it will help.

"""

!/usr/bin/env python

import argparse, subprocess, json, os, urllib2, sys, base64, binascii, time,
hashlib, tempfile, re, copy, textwrap, errno, shlex

def sign_csr(pubkey, csr, email=None, file_based=False, ssl_directory="./ssl_files"):

def sign_csr(domain_to_crt, email, ssl_directory, server_path, gen_keys):
"""Use the ACME protocol to get an ssl certificate signed by a
certificate authority.

:param string domain_to_crt: Domain to certificate.
:param string email: user account contact email
:param string ssl_directory: path to write keys and certificates
:param string server_path: Public path to write challenge files
:param bool gen_keys: Boolean True to generate User and domain keys
    if not generating names must be user.key, user.pub, domain.key, domain.csr
    in ssl_directory 
:returns: Signed Certificate (PEM format)
:rtype: string

"""
#CA = "https://acme-staging.api.letsencrypt.org"
CA = "https://acme-v01.api.letsencrypt.org"
TERMS = "https://letsencrypt.org/documents/LE-SA-v1.0.1-July-27-2015.pdf"
nonce_req = urllib2.Request("{0}/directory".format(CA))
nonce_req.get_method = lambda : 'HEAD'

#cert info modify to match your domain specs
# for '/CN=domain.com/O=orga LTD/C=ES/'
my_domain_CN = domain_to_crt
my_domain_C  = "XX" # country XX 
my_domain_O  = "My Company" 
my_domain_ST = "province"
my_domain_L  = "Locality"
my_subj = "/CN={0}/O={1}/C={2}/ST={3}/L={4}".format(my_domain_CN,my_domain_O,my_domain_C,my_domain_ST,my_domain_L)

#user keys names 
user_priv = "user.key"
user_pub = "user.pub"
user_priv = "{0}/{1}".format(ssl_directory, user_priv)
user_pub =  "{0}/{1}".format(ssl_directory, user_pub)

#domain keys
domain_priv = "domain.key"
domain_csr = "domain.csr"
domain_priv = "{0}/{1}".format(ssl_directory, domain_priv)
domain_csr = "{0}/{1}".format(ssl_directory, domain_csr)


#report
report_file="report.log" 


def _execute_command(command_line):
    sys.stderr.write("Executing {0}\n".format(command_line))
    args = shlex.split(command_line)  
    proc = subprocess.Popen(args, stdout=subprocess.PIPE,
                            stderr=subprocess.PIPE);
    out, err = proc.communicate()
    if proc.returncode != 0:
        _to_report("Error in {0} command".format(command_line))
        raise IOError("Error executing #{0}# \n".format(command_line))
    else:
        _to_report("Command #{0}# OK!".format(command_line))
    return 0

def _make_sure_path_exists(path):
    try:
        os.makedirs(path)
    except OSError as exception:
        if exception.errno != errno.EEXIST:
            raise  IOError("Error during path generation ERROR:{0} \n".format(exception.errno))

def _init_directorys_and_files():
    #ssl dir
    _make_sure_path_exists(ssl_directory)
    #report file
    target = open("{0}/{1}".format(ssl_directory,report_file), 'a+')
    target.write("Init Dirs and files: ok \n")
    target.close()
    return 0

def _to_report(text):
    target = open("{0}/{1}".format(ssl_directory,report_file), 'a+')
    target.write(text)
    target.write("\n")
    target.close()
    return 0

def _user_keys(user_priv, user_pub):
    "Initial User Keys with open ssl"
    sys.stderr.write("User keys generation \n")
    command_line = "openssl genrsa -out {0} 4096".format(user_priv)
    args = shlex.split(command_line)  
    proc = subprocess.Popen(args, stdout=subprocess.PIPE,
                            stderr=subprocess.PIPE);
    out, err = proc.communicate()

    if proc.returncode != 0:
        _to_report("Error in private key generation")
        raise IOError("Error generating private key {0} \n".format(user_priv))
    else:
        _to_report("Generation of user  private key -{0}- OK!".format(user_priv))

    command_line = "openssl rsa -out {0} -in {1} -pubout".format(user_pub, user_priv)
    args = shlex.split(command_line)  
    proc = subprocess.Popen(args, stdout=subprocess.PIPE,
                            stderr=subprocess.PIPE);
    out, err = proc.communicate()

    if proc.returncode != 0:
        _to_report("Error in public key generation")
        raise IOError("Error generating public key {0} \n".format(user_pub))
    else:
        _to_report("Generation of user public key -{0}- OK!".format(user_pub))

    return 0

def _domain_keys(domain_priv, domain_csr):
    "Initial Domain Private Key with open ssl"
    sys.stderr.write("Domain keys generation \n")
    command_line = "openssl genrsa -out {0} 4096".format(domain_priv)
    args = shlex.split(command_line)  
    proc = subprocess.Popen(args, stdout=subprocess.PIPE,
                            stderr=subprocess.PIPE);
    out, err = proc.communicate()

    if proc.returncode != 0:
        _to_report("Error in private key generation")
        raise IOError("Error generating private key {0} \n".format(domain_priv))
    else:
        _to_report("Generation of user private key -{0}- OK!".format(domain_priv))

    command_line = ("openssl req -new -sha256 -key {0} -out {1} "
                    "-subj '{2}'").format(domain_priv, domain_csr,my_subj)
    args = shlex.split(command_line)
    proc = subprocess.Popen(args, stdout=subprocess.PIPE,
                            stderr=subprocess.PIPE);
    out, err = proc.communicate()

    if proc.returncode != 0:
        _to_report("Error in domain CSR generation")
        raise IOError("Error generating domain CSR {0} \n".format(domain_csr))
    else:
        _to_report("Generation of domain CSR -{0}- OK!".format(domain_csr))

    return 0


def _b64(b):
    "Shortcut function to go from bytes to jwt base64 string"
    return base64.urlsafe_b64encode(b).replace("=", "")

if gen_keys:
    # Step 0: gen keys 
    _init_directorys_and_files()
    _user_keys(user_priv, user_pub)
    _domain_keys(domain_priv, domain_csr)
    sys.stderr.write("CHECK ? \n")
else:
    sys.stderr.write("NOT YET \n")
    raise 

# Step 1: Get account public key
sys.stderr.write("Reading User public Key file...\n")
proc = subprocess.Popen(["openssl", "rsa", "-pubin", "-in", user_pub, "-noout", "-text"],
    stdout=subprocess.PIPE, stderr=subprocess.PIPE)
out, err = proc.communicate()
if proc.returncode != 0:
    raise IOError("Error loading {0}".format(user_pub))
pub_hex, pub_exp = re.search(
    "Modulus(?: \((?:2048|4096) bit\)|)\:\s+00:([a-f0-9\:\s]+?)Exponent\: ([0-9]+)",
    out, re.MULTILINE|re.DOTALL).groups()
pub_mod = binascii.unhexlify(re.sub("(\s|:)", "", pub_hex))
pub_mod64 = _b64(pub_mod)
pub_exp = int(pub_exp)
pub_exp = "{0:x}".format(pub_exp)
pub_exp = "0{0}".format(pub_exp) if len(pub_exp) % 2 else pub_exp
pub_exp = binascii.unhexlify(pub_exp)
pub_exp64 = _b64(pub_exp)
header = {
    "alg": "RS256",
    "jwk": {
        "e": pub_exp64,
        "kty": "RSA",
        "n": pub_mod64,
    },
}
accountkey_json = json.dumps(header['jwk'], sort_keys=True, separators=(',', ':'))
thumbprint = _b64(hashlib.sha256(accountkey_json).digest())
sys.stderr.write("Found public key!\n")

# Step 2: Get the domain names to be certified
sys.stderr.write("Reading csr file...\n")
proc = subprocess.Popen(["openssl", "req", "-in", domain_csr, "-noout", "-text"],
    stdout=subprocess.PIPE, stderr=subprocess.PIPE)
out, err = proc.communicate()
if proc.returncode != 0:
    raise IOError("Error loading {0}".format(domain_csr))
domains = set([])
common_name = re.search("Subject:.*? CN=([^\s,;/]+)", out)
if common_name is not None:
    domains.add(common_name.group(1))
subject_alt_names = re.search("X509v3 Subject Alternative Name: \n +([^\n]+)\n", out, re.MULTILINE|re.DOTALL)
if subject_alt_names is not None:
    for san in subject_alt_names.group(1).split(", "):
        if san.startswith("DNS:"):
            domains.add(san[4:])
sys.stderr.write("Found domains {0}\n".format(", ".join(domains)))

# Step 3: contact email
# nothing just from cli 

# Step 4: Generate the payloads that need to be signed
# registration
sys.stderr.write("Building request payloads...\n")
reg_nonce = urllib2.urlopen(nonce_req).headers['Replay-Nonce']
reg_raw = json.dumps({
    "resource": "new-reg",
    "contact": ["mailto:{0}".format(email)],
    "agreement": TERMS,
}, sort_keys=True, indent=4)
reg_b64 = _b64(reg_raw)
reg_protected = copy.deepcopy(header)
reg_protected.update({"nonce": reg_nonce})
reg_protected64 = _b64(json.dumps(reg_protected, sort_keys=True, indent=4))
reg_file = tempfile.NamedTemporaryFile(dir=".", prefix="register_", suffix=".json")
reg_file.write("{0}.{1}".format(reg_protected64, reg_b64))
reg_file.flush()
reg_file_name = os.path.basename(reg_file.name)
reg_file_sig = tempfile.NamedTemporaryFile(dir=".", prefix="register_", suffix=".sig")
reg_file_sig_name = os.path.basename(reg_file_sig.name)

# need signature for each domain identifiers
ids = []
for domain in domains:
    sys.stderr.write("Building request for {0}...\n".format(domain))
    id_nonce = urllib2.urlopen(nonce_req).headers['Replay-Nonce']
    id_raw = json.dumps({
        "resource": "new-authz",
        "identifier": {
            "type": "dns",
            "value": domain,
        },
    }, sort_keys=True)
    id_b64 = _b64(id_raw)
    id_protected = copy.deepcopy(header)
    id_protected.update({"nonce": id_nonce})
    id_protected64 = _b64(json.dumps(id_protected, sort_keys=True, indent=4))
    id_file = tempfile.NamedTemporaryFile(dir=".", prefix="domain_", suffix=".json")
    id_file.write("{0}.{1}".format(id_protected64, id_b64))
    id_file.flush()
    id_file_name = os.path.basename(id_file.name)
    id_file_sig = tempfile.NamedTemporaryFile(dir=".", prefix="domain_", suffix=".sig")
    id_file_sig_name = os.path.basename(id_file_sig.name)
    ids.append({
        "domain": domain,
        "protected64": id_protected64,
        "data64": id_b64,
        "file": id_file,
        "file_name": id_file_name,
        "sig": id_file_sig,
        "sig_name": id_file_sig_name,
    })

# need signature for the final certificate issuance
sys.stderr.write("Building request for CSR...\n")
proc = subprocess.Popen(["openssl", "req", "-in", domain_csr, "-outform", "DER"],
    stdout=subprocess.PIPE, stderr=subprocess.PIPE)
csr_der, err = proc.communicate()
csr_der64 = _b64(csr_der)
csr_nonce = urllib2.urlopen(nonce_req).headers['Replay-Nonce']
csr_raw = json.dumps({
    "resource": "new-cert",
    "csr": csr_der64,
}, sort_keys=True, indent=4)
csr_b64 = _b64(csr_raw)
csr_protected = copy.deepcopy(header)
csr_protected.update({"nonce": csr_nonce})
csr_protected64 = _b64(json.dumps(csr_protected, sort_keys=True, indent=4))
csr_file = tempfile.NamedTemporaryFile(dir=".", prefix="cert_", suffix=".json")
csr_file.write("{0}.{1}".format(csr_protected64, csr_b64))
csr_file.flush()
csr_file_name = os.path.basename(csr_file.name)
csr_file_sig = tempfile.NamedTemporaryFile(dir=".", prefix="cert_", suffix=".sig")
csr_file_sig_name = os.path.basename(csr_file_sig.name)

# Step 5: Ask the user to sign the registration and requests

sys.stderr.write("""\

STEP 2: You need to sign some files (replace 'user.key' with your user private key).

openssl dgst -sha256 -sign user.key -out {0} {1}

{2}

openssl dgst -sha256 -sign user.key -out {3} {4}

""".format(

reg_file_sig_name, reg_file_name,

"\n".join("openssl dgst -sha256 -sign user.key -out {0} {1}".format(i['sig_name'], i['file_name']) for i in ids),

csr_file_sig_name, csr_file_name))

stdout = sys.stdout

sys.stdout = sys.stderr

raw_input("Press Enter when you've run the above commands in a new terminal window...")

sys.stdout = stdout

## MOD Esteban automatic generattion 
_execute_command("openssl dgst -sha256 -sign {0} -out {1} {2}".format(user_priv, reg_file_sig_name, reg_file_name))
for i in ids:
    _execute_command( "openssl dgst -sha256 -sign {0} -out {1} {2}".format( user_priv, i['sig_name'], i['file_name']))

_execute_command("openssl dgst -sha256 -sign {0} -out {1} {2}".format(user_priv, csr_file_sig_name, csr_file_name))
## MOD Esteban 

# Step 6: Load the signatures
reg_file_sig.seek(0)
reg_sig64 = _b64(reg_file_sig.read())
for n, i in enumerate(ids):
    i['sig'].seek(0)
    i['sig64'] = _b64(i['sig'].read())

# Step 7: Register the user
sys.stderr.write("Registering {0}...\n".format(email))
reg_data = json.dumps({
    "header": header,
    "protected": reg_protected64,
    "payload": reg_b64,
    "signature": reg_sig64,
}, sort_keys=True, indent=4)
reg_url = "{0}/acme/new-reg".format(CA)
try:
    resp = urllib2.urlopen(reg_url, reg_data)
    result = json.loads(resp.read())
except urllib2.HTTPError as e:
    err = e.read()
    # skip already registered accounts
    if "Registration key is already in use" in err:
        sys.stderr.write("Already registered. Skipping...\n")
    else:
        sys.stderr.write("Error: reg_data:\n")
        sys.stderr.write("POST {0}\n".format(reg_url))
        sys.stderr.write(reg_data)
        sys.stderr.write("\n")
        sys.stderr.write(err)
        sys.stderr.write("\n")
        raise

# Step 8: Request challenges for each domain
responses = []
tests = []
for n, i in enumerate(ids):
    sys.stderr.write("Requesting challenges for {0}...\n".format(i['domain']))
    id_data = json.dumps({
        "header": header,
        "protected": i['protected64'],
        "payload": i['data64'],
        "signature": i['sig64'],
    }, sort_keys=True, indent=4)
    id_url = "{0}/acme/new-authz".format(CA)
    try:
        resp = urllib2.urlopen(id_url, id_data)
        result = json.loads(resp.read())
    except urllib2.HTTPError as e:
        sys.stderr.write("Error: id_data:\n")
        sys.stderr.write("POST {0}\n".format(id_url))
        sys.stderr.write(id_data)
        sys.stderr.write("\n")
        sys.stderr.write(e.read())
        sys.stderr.write("\n")
        raise
    challenge = [c for c in result['challenges'] if c['type'] == "http-01"][0]
    keyauthorization = "{0}.{1}".format(challenge['token'], thumbprint)

    # challenge request
    sys.stderr.write("Building challenge responses for {0}...\n".format(i['domain']))
    test_nonce = urllib2.urlopen(nonce_req).headers['Replay-Nonce']
    test_raw = json.dumps({
        "resource": "challenge",
        "keyAuthorization": keyauthorization,
    }, sort_keys=True, indent=4)
    test_b64 = _b64(test_raw)
    test_protected = copy.deepcopy(header)
    test_protected.update({"nonce": test_nonce})
    test_protected64 = _b64(json.dumps(test_protected, sort_keys=True, indent=4))
    test_file = tempfile.NamedTemporaryFile(dir=".", prefix="challenge_", suffix=".json")
    test_file.write("{0}.{1}".format(test_protected64, test_b64))
    test_file.flush()
    test_file_name = os.path.basename(test_file.name)
    test_file_sig = tempfile.NamedTemporaryFile(dir=".", prefix="challenge_", suffix=".sig")
    test_file_sig_name = os.path.basename(test_file_sig.name)
    tests.append({
        "uri": challenge['uri'],
        "protected64": test_protected64,
        "data64": test_b64,
        "file": test_file,
        "file_name": test_file_name,
        "sig": test_file_sig,
        "sig_name": test_file_sig_name,
    })

    # challenge response for server
    responses.append({
        "uri": ".well-known/acme-challenge/{0}".format(challenge['token']),
        "data": keyauthorization,
    })

# Step 9: Ask the user to sign the challenge responses

sys.stderr.write("""\

STEP 3: You need to sign some more files (replace 'user.key' with your user private key).

{0}

""".format(

"\n".join("openssl dgst -sha256 -sign user.key -out {0} {1}".format(

i['sig_name'], i['file_name']) for i in tests)))

stdout = sys.stdout

sys.stdout = sys.stderr

raw_input("Press Enter when you've run the above commands in a new terminal window...")

sys.stdout = stdout

## MOD Esteban automate this
for i in tests:
    _execute_command("openssl dgst -sha256 -sign {0} -out {1} {2}".format(user_priv, i['sig_name'], i['file_name']))

## MOD Esteban 

# Step 10: Load the response signatures
for n, i in enumerate(ids):
    tests[n]['sig'].seek(0)
    tests[n]['sig64'] = _b64(tests[n]['sig'].read())

# Step 11: Ask the user to host the token on their server
for n, i in enumerate(ids):

    ## MOD Esteban only file based write files 
    requested_URL = "http://{0}/{1}".format( i['domain'], responses[n]['uri'])
    sys.stderr.write(requested_URL)

    subdomain = i['domain'].split('.')[0]
    if subdomain not in ["www" , domain_to_crt]: 
        requested_dir = "{0}/{1}".format( server_path, subdomain)
        _to_report("Checking directory: {0}".format(requested_dir))
        if not os.path.isdir(requested_dir):
            _to_report("FAIL \n")
            raise IOError("subdomain not in server path: {0}".format(requested_dir))
    else:
        requested_dir = server_path
        if not os.path.isdir(requested_dir):
            _to_report("FAIL \n")
            raise IOError("domain not in server path: {0}".format(requested_dir))

    _make_sure_path_exists("{0}/.well-known".format(requested_dir))
    _make_sure_path_exists("{0}/.well-known/acme-challenge".format(requested_dir))
    requested_file = "{0}/{1}".format(requested_dir, responses[n]['uri'])
    content_data = "{0}".format(responses[n]['data'])

    #write file to server
    target = open(requested_file, 'w+')
    target.write(content_data)
    target.close()

    ## MOD Esteban 

    # Step 12: Let the CA know you're ready for the challenge
    sys.stderr.write("Requesting verification for {0}...\n".format(i['domain']))
    test_data = json.dumps({
        "header": header,
        "protected": tests[n]['protected64'],
        "payload": tests[n]['data64'],
        "signature": tests[n]['sig64'],
    }, sort_keys=True, indent=4)
    test_url = tests[n]['uri']
    try:
        resp = urllib2.urlopen(test_url, test_data)
        test_result = json.loads(resp.read())
    except urllib2.HTTPError as e:
        sys.stderr.write("Error: test_data:\n")
        sys.stderr.write("POST {0}\n".format(test_url))
        sys.stderr.write(test_data)
        sys.stderr.write("\n")
        sys.stderr.write(e.read())
        sys.stderr.write("\n")
        raise

    # Step 13: Wait for CA to mark test as valid
    sys.stderr.write("Waiting for {0} challenge to pass...\n".format(i['domain']))
    while True:
        try:
            resp = urllib2.urlopen(test_url)
            challenge_status = json.loads(resp.read())
        except urllib2.HTTPError as e:
            sys.stderr.write("Error: test_data:\n")
            sys.stderr.write("GET {0}\n".format(test_url))
            sys.stderr.write(test_data)
            sys.stderr.write("\n")
            sys.stderr.write(e.read())
            sys.stderr.write("\n")
            raise
        if challenge_status['status'] == "pending":
            time.sleep(2)
        elif challenge_status['status'] == "valid":
            sys.stderr.write("Passed {0} challenge!\n".format(i['domain']))
            break
        else:
            raise KeyError("'{0}' challenge did not pass: {1}".format(i['domain'], challenge_status))

# Step 14: Get the certificate signed
sys.stderr.write("Requesting signature...\n")
csr_file_sig.seek(0)
csr_sig64 = _b64(csr_file_sig.read())
csr_data = json.dumps({
    "header": header,
    "protected": csr_protected64,
    "payload": csr_b64,
    "signature": csr_sig64,
}, sort_keys=True, indent=4)
csr_url = "{0}/acme/new-cert".format(CA)
try:
    resp = urllib2.urlopen(csr_url, csr_data)
    signed_der = resp.read()
except urllib2.HTTPError as e:
    sys.stderr.write("Error: csr_data:\n")
    sys.stderr.write("POST {0}\n".format(csr_url))
    sys.stderr.write(csr_data)
    sys.stderr.write("\n")
    sys.stderr.write(e.read())
    sys.stderr.write("\n")
    raise

# Step 15: Convert the signed cert from DER to PEM
sys.stderr.write("Certificate signed!\n")
signed_der64 = base64.b64encode(signed_der)
signed_pem = """\

-----BEGIN CERTIFICATE-----
{0}
-----END CERTIFICATE-----
""".format("\n".join(textwrap.wrap(signed_der64, 64)))

return signed_pem

if name == "main":
parser = argparse.ArgumentParser(
formatter_class=argparse.RawDescriptionHelpFormatter,
description="""
Get a SSL certificate signed by a Let's Encrypt (ACME) certificate authority and
output that signed certificate. You do NOT need to run this script on your
server and this script does not ask for your private keys. It will print out
commands that you need to run with your private key or on your server as root,
which gives you a chance to review the commands instead of trusting this script.

NOTE: YOUR ACCOUNT KEY NEEDS TO BE DIFFERENT FROM YOUR DOMAIN KEY.

Prerequisites:

  • openssl
  • python

Example: Generate an account keypair, a domain key and csr, and have the domain csr signed.

$ openssl genrsa 4096 > user.key
$ openssl rsa -in user.key -pubout > user.pub
$ openssl genrsa 4096 > domain.key
$ openssl req -new -sha256 -key domain.key -subj "/CN=example.com" > domain.csr

$ python sign_csr.py --public-key user.pub domain.csr > signed.crt

""")
parser.add_argument("-d", "--domain-to-crt", required=True, help="Domain name to cert")
parser.add_argument("-e", "--email", required=True, help="contact email for cert request")
parser.add_argument("-g", "--gen-keys", action='store_true', help="if set, a file-based response is used")
parser.add_argument("-sd", "--ssl-directory", default="./ssl_files", help="Directory to hold SSL keys")
parser.add_argument("-sp", "--server-path", default="~/public_html", help="Server path")

args = parser.parse_args()
signed_crt = sign_csr(args.domain_to_crt, args.email, "{0}_{1}".format(args.ssl_directory,args.domain_to_crt), args.server_path, args.gen_keys)
#signed_crt = sign_csr(args.public_key, args.csr_path, email=args.email, file_based=args.file_based, ssl_directory=args.ssl_directory)
sys.stdout.write(signed_crt)
signed_crt_file = open("{0}/{1}".format("{0}_{1}".format(args.ssl_directory,args.domain_to_crt),"signed.crt"), 'w+')
signed_crt_file.write(signed_crt)
signed_crt_file.close()

"""

Renewal process?

What is the renewal process like? Do you just run again against the same CSR?

Error: Manual Commands

Hello!

I stumbled across a problem occuring at Step 2. I successfully did:
python sign_csr.py --public-key user.pub domain.csr > signed.crt

After that I entered my email. But if I run the manual commands in a seperate command window this error occurs:

Error opening output file register_0h6rzr.sig
1384:error:02001020:system library:fopen:Broken pipe:.\crypto\bio\bss_file.c:168:fopen('register_0h6rzr.sig','wb')
1384:error:2006D002:BIO routines:BIO_new_file:system lib:.\crypto\bio\bss_file.c:173:

I'm all new to this so if I forgot to mention some important sidenotes of my project, please tell me. And thanks in advance! :)

Add a mode that will sign the keys.

Would it be possible to add a mode to LNS to run the commands to generate and sign the keys itself? It's not really that big a deal, but it would be faster if it I could just say "okay, you run and output a private key and a signed certificate at the end of the process". I would still need to run the web server, sure, but I usually just proxy the .well-known file to a specific port beforehand.

So, the process would be:

letsencrypt-nosudo --port=27318 <csr details>

Port 27318 would already be an HTTP proxy for the well-known file to the machine where this command would run, and the process would result to just two files, the private key and signed cert, which is much more plug-and-play.

python version >= 2.7 is required

great client but It should be mentioned, that python 2.7 is required.

$ python --version
Python 2.6.6
$ python letsencrypt-nosudo-master/sign_csr.py -p user.pub -e [email protected] www.domain.tld.csr
Traceback (most recent call last):
File "letsencrypt-nosudo-master/sign_csr.py", line 441, in
signed_crt = sign_csr(args.public_key, args.csr_path, email=args.email, file_based=args.file_based)
File "letsencrypt-nosudo-master/sign_csr.py", line 26, in sign_csr
nonce_req = urllib2.Request("{}/directory".format(CA))
ValueError: zero length field name in format

Signed Cert Hang

When I get to the point (Step 4 of: python sign_csr.py --public-key user.pub domain.csr > signed.crt) where I run on a separate terminal:
sudo python -c "import .....

It ends with:
socket.error: [Errno 98] Address already in use

Consider driving donations to ISRG instead of EFF?

I love EFF dearly, and obviously they're supporting Let's Encrypt, but I think that the Internet Security Research Group, as the 501(c)(3) that operates Let's Encrypt and is responsible for its financials, software development, and strategic direction, is the best target for the donations this project drives on its README.

Their Become a Sponsor page has a PayPal button at the bottom. I'm not sure the best way to adapt it into a README-friendly link.

@jsha @jmhodges

Existing HTTP server support...

Guys,
I was wondering if it’s possible to add code to use existing HTTP server?
I.e. we have a set of server behind load balancer (LB).
It’ll be nice, if instead of running HTTP server, your script can pass content and path to another user defined script (UDS).
UDS distributes content to all servers behind LB.
Then your script runs domain ownership verification part and get certificate.
UDS can be written in any OS supported language.
I wish I can do it myself, but I don’t know python well enough...

{"type":"urn:acme:error:malformed","detail":"JWS verification error","status":400}

Hi, when i try to execute :

python sign_csr.py --public-key user.pub domain.csr > signed.crt

it run, and ask me for email ( all seem okey ) .. but after that i got this :

"signature": ""
}
{"type":"urn:acme:error:malformed","detail":"JWS verification error","status":400}
Traceback (most recent call last):
File "sign_csr.py", line 446, in
signed_crt = sign_csr(args.public_key, args.csr_path, email=args.email, file_based=args.file_based)
File "sign_csr.py", line 198, in sign_csr
resp = urllib2.urlopen(reg_url, reg_data)
File "/usr/local/lib/python2.7/urllib2.py", line 154, in urlopen
return opener.open(url, data, timeout)
File "/usr/local/lib/python2.7/urllib2.py", line 437, in open
response = meth(req, response)
File "/usr/local/lib/python2.7/urllib2.py", line 550, in http_response
'http', request, response, code, msg, hdrs)
File "/usr/local/lib/python2.7/urllib2.py", line 475, in error
return self._call_chain(_args)
File "/usr/local/lib/python2.7/urllib2.py", line 409, in _call_chain
result = func(_args)
File "/usr/local/lib/python2.7/urllib2.py", line 558, in http_error_default
raise HTTPError(req.get_full_url(), code, msg, hdrs, fp)
urllib2.HTTPError: HTTP Error 400: Bad Request

  • i had the same problem with gethttpsforfree.com, but i solved it from an issue published here ( the thing of cachebuster .. ). But with the python app i cant solve it, and i think it's the same issue with what was happening with the web app ! THNK YOU ALL !!

Official client supports CSRs and manual mode!

Now, that certbot/certbot#504 (CSRs) and certbot/certbot#502 (manual authenticator) are merged in, official Let's Encrypt client provides the same features as letsencrypt-nosudo! You can try it out by running letsencrypt --authenticator manual auth --csr csr.der. To use simpleHttp challenge without TLS use --no-simple-http-tls.

Please consider adding appropriate notice to your project. You are all more than welcome to contribute "upstream"! :)

cc: @diafygi, @jdkasten

Multiple domains?

Hello,

what would be the syntax to create a working certificate sign request for multiple domains?
For example: github.com, *.github.com

It would be nice if you could add this to the example usage.

Thanks 👍

Error creating new cert "Too many certificates already issued for: ddns.net"

I believe I did everything correctly, but I retrieve the following error when issuing the new cert:

Press Enter when you've got the python command running on your server...
Requesting verification for secret4u.ddns.net...
Waiting for secret4u.ddns.net challenge to pass...
Passed secret4u.ddns.net challenge!
Requesting signature...
Error: csr_data:
POST https://acme-v01.api.letsencrypt.org/acme/new-cert
{
"header": {
"alg": "RS256",
"jwk": {
"e": "AQAB",
"kty": "RSA",
"n": "uihD9jAbqFuhRM2AcFfFpuMTouyTgC0bZw7HmA9d1_by_923QDYQvSb9z5t5u467bm4pLla5RxBg-xgPoKRZjhVD1AzfaXeQAMxREaO_BoxekWwLzt2AW8mu1cCX9a7UsvUCxt3NwCQ6PUkI2PuUpF0PSgKisUwWlMrf-qiOEdvyED85erSIvbN9uoZ2xQcAfbuShy_hvIQ4BUKfKw9UEWhxxn7t6t1t_A1Q_yWqND2uMtVpK9g3ujt5V-I9QIk_iLGrwlgPHuhp031j4JeeB9K-YE0PEjiAhzniz8GkMP2_-_yIsDZwkXlpb6g9aj5XesRrCUrLp8mx521V_7egLeFpv8hWyJe9Kx-5geIskIaD5ilfxsk_xbR8EGuvxommjR8AuvfaMUKac1MF3PUVgNiYSuSD1VSGd1lt3B3Rorv0BsdifyaUIaQpe0x_2XYNvZRMgnX889ZmCYpXE04BPUuyw2srGnVI8z0mOaGjKlVo47dkMH00NuYyu0VWUsV4PdURb8Oe7uuTKpcJEouzKUxjL2taXCHGDKemb9W7SYyFOZGnyajgg53uCT7pgamzBWcrsCWAPhl_1x8JmrJeJUP9HeudN19hEYutk9tshTZXdPQyeWckmenj6ZaUkS8QKCh3wUu2fzKFsc4FZbsvWW9bB1ebIKjW3J5pi9QHUYE"
}
},
"payload": "ewogICAgImNzciI6ICJNSUlFWVRDQ0Fra0NBUUF3SERFYU1CZ0dBMVVFQXd3UmMyVmpjbVYwTkhVdVpHUnVjeTV1WlhRd2dnSWlNQTBHQ1NxR1NJYjNEUUVCQVFVQUE0SUNEd0F3Z2dJS0FvSUNBUUNzVU5LOFJDNXd4WlV5VHpFclRsV0lrS0hMejJUYmE1eW85SFBLMUNvUWo4THV4VnIxc0ZqaDFpX1cwazk0NmZGMEVkMXB0RVpDbmVfaEdxMnNYc1BNNU5sV3UwQ0VQRllqam9RZ0hWWGNOV09qa1pwMGl4Umk2Q0J6N1NyQm5EcnR4SDY2dTE3X3JMTFRTMnVTbldKVm1XMXk1VVpxQkc2bE90RmN6c1BSQXlZdEUwS25jS0s0cEJtMTVZVWRZRGFhTWFFYVEwVk1nNENsd1FpampFV25YUV85RnhVd0I2TExYd01NTTdGS0NXY2YtcWZqSEVxU0RDS3V3QWRPamZ0SEh4dXJ4ZGlOaEZheVZvZGQyOFpsNWdGZ2psMnM5aF9hQkU0Ny1xcGIya3IzN0hQSE4tZlhDSXhjMlp3eHBzV3V6OHl4N05CY21JSUdYUmZNR3pFQXhpd3JVTmdRenhGUGY5UlZaLS16b0NENTd4aXd1SzhzVTFDWUVtbUk4dlVia2VocXZyVElNcy1zOTZJeGotWDA3WmFYbFpYSk1wYmhobXlaeURaajhaQUdKYk14UWxTYkxUcUJ5cEd4dlhpa2Nza2VrVEZ4Ym5VcmRnVlNSekxrQmtjeXF1VXJRbmptWkxJQUs0MXVnWk5LZ2I4eUpXenZoTEtTQ2tnMkU3MXF6cEZFcEQ1QnFOTmx3eUMwbUwxYUNrMG1Na1lvaUN4NTNWNnVCSTV5clZxOWR6cTJNbFMyeUFlZ1pIaTMyQlZVQmtMTm1Bd2JhMW9TSXBhYW9OMWY2MS10MzVwV2xaNXdLVm1xbzJDVm8yOC1LQ0d3NjZDODdGZjJqVjB2NExBSkpfaDg2aWRuclhZRkY3QTNwdkR3ZnhlZkdGWmM1QTB5Zk9rMUpuQUtNd0lEQVFBQm9BQXdEUVlKS29aSWh2Y05BUUVMQlFBRGdnSUJBRzByV2hhUTBVZXZWYTBuV0JpNHZvMWNsamNMUmNrTFNUQmZlR2xValBjTmVYbkRnVE92bXRlc2t4eFdRNmt4Vlc2UmVfdHBTaEQ3cDNWcUZyNW9ZQlJCNmpna24zUXV5aGJkVk1PcGVZTmpLemxHRml1dDJSd29GREhWX3NkMmdqbDZjdlZGZHJRQUFGdVpYbWg5SU5DSUVEWWZ4SUFwZkxNZkFSQzB5V3hpazhjVjROREJxUTdCY292ckFOM19BTEpVYlRxVmhlYVVOZ2dzTF9IZzNrbjQ2cDJpRTJEdWUzZTJCcUk3eVItRzVLVkhFaFYzRUcyTWhBY0o1SWpfRE4wLTE2dnVleUNPTU1XMUhOQTRocG9Ocy1KV291UDUxMWxKamFWVnpOdW9sUmhpZ0VoVVpsQzh3LUVaa2xuVFd1SEhtcy1CVU53QW1uZHdBMG9ONG91dTZpaC1pY2p1amxFTU9Hdk9OZmhFbmx0aTJFTWp1R2gyNHN4Q0wzdDdhRlgtcE9OZFlVTFgxZTNOa0U5c0ppdXd0eUF1OXgyT293WjFldTRvQm91anFxdXRreWI1dFVDWmNlbmFGaVNkY1RobFVZTThJbGViWU5yOTU0T2hkVjhWQ1hKdzZtR3lVVm1zUzNVdmVoajl6X3pSVWVNMDR3bUUyR21QdzU4aXdpTmR4NFkwN3QtSE03aGJwWUQ3a0VaUi1weUJvRUFTamsycEQtT2EtMERLU3BCVURSVXhLU0U4eDNQSGtkaWRaNF92aEU3a1JoT0xwazF3SGNUdDFVQWYydUEySlpoUDhBeTNUbnR5TEt3UWhUS2JQdUlVX2xoUFBTd2dELXVkeHp2U2trR0NsNmtkbjNSaWZyWjF3Rkg2WVZ3R1pQUmtzYjhod0p6RWJidVUiLCAKICAgICJyZXNvdXJjZSI6ICJuZXctY2VydCIKfQ",
"protected": "ewogICAgImFsZyI6ICJSUzI1NiIsIAogICAgImp3ayI6IHsKICAgICAgICAiZSI6ICJBUUFCIiwgCiAgICAgICAgImt0eSI6ICJSU0EiLCAKICAgICAgICAibiI6ICJ1aWhEOWpBYnFGdWhSTTJBY0ZmRnB1TVRvdXlUZ0MwYlp3N0htQTlkMV9ieV85MjNRRFlRdlNiOXo1dDV1NDY3Ym00cExsYTVSeEJnLXhnUG9LUlpqaFZEMUF6ZmFYZVFBTXhSRWFPX0JveGVrV3dMenQyQVc4bXUxY0NYOWE3VXN2VUN4dDNOd0NRNlBVa0kyUHVVcEYwUFNnS2lzVXdXbE1yZi1xaU9FZHZ5RUQ4NWVyU0l2Yk45dW9aMnhRY0FmYnVTaHlfaHZJUTRCVUtmS3c5VUVXaHh4bjd0NnQxdF9BMVFfeVdxTkQydU10VnBLOWczdWp0NVYtSTlRSWtfaUxHcndsZ1BIdWhwMDMxajRKZWVCOUstWUUwUEVqaUFoem5pejhHa01QMl8tX3lJc0Rad2tYbHBiNmc5YWo1WGVzUnJDVXJMcDhteDUyMVZfN2VnTGVGcHY4aFd5SmU5S3gtNWdlSXNrSWFENWlsZnhza194YlI4RUd1dnhvbW1qUjhBdXZmYU1VS2FjMU1GM1BVVmdOaVlTdVNEMVZTR2QxbHQzQjNSb3J2MEJzZGlmeWFVSWFRcGUweF8yWFlOdlpSTWduWDg4OVptQ1lwWEUwNEJQVXV5dzJzckduVkk4ejBtT2FHaktsVm80N2RrTUgwME51WXl1MFZXVXNWNFBkVVJiOE9lN3V1VEtwY0pFb3V6S1V4akwydGFYQ0hHREtlbWI5VzdTWXlGT1pHbnlhamdnNTN1Q1Q3cGdhbXpCV2Nyc0NXQVBobF8xeDhKbXJKZUpVUDlIZXVkTjE5aEVZdXRrOXRzaFRaWGRQUXllV2NrbWVuajZaYVVrUzhRS0NoM3dVdTJmektGc2M0Rlpic3ZXVzliQjFlYklLalczSjVwaTlRSFVZRSIKICAgIH0sIAogICAgIm5vbmNlIjogInpldTc2Z2IxZkdDT3FRSFJUMzlwUzFTZ29WQzJKYjRRcXBON2Uzd0JiMVEiCn0",
"signature": "kh_MaUJUw8X-4p5B0RPgDpJPUkT9hKAA8aEb0DwUKQMufNs7ZNIFq5wliKSaI3HMfaxJeZC5WeKXneWlSTOhBvgv4LiYhGFyXsy8V591ydbe-UwGApUR8fpW9H2ZD9-o6aKTGNc4uy7pE6ghPcCao2ftFSyejfqhfeZC0iJsh-1I9yOGCT1mOhymGZ-bMBGwCc1Cz6ch7gmu4b9zwV-GiWgAbE5YGAkZeX9jgrpfGEjstI7FlpMglhScnSBHyYust87MocxOhJQnWl6wOOWpM51d1rgqVfTQYSx2rj8-6hhMuAh_peie2_TFxnNo-Fi8Vk3UmUAQqmf6ed4EwOKO5D9Gszgqd2d_yBZ2t41t4dfAue1C_u9AjNle7aR2QxWEg0wvAhbxCbZjHYMy15crbM2o3Fj7vLdYHdzCODlur1L9CDhs0b74BXT6Io8C5gC5OBv0NwQG8Qj9DThogfOkbllXWqgYielXWxyhU1jSdFs-OhxlUmBM-WdznbvmZvtFwLqQHmYJ03EjOXR-BdYlzMvGBTpaaAEKPpDzmLIgVWx8qfMFxzHZaVML16Go7OxkRhRlA1F9wk9eabEHfH3Uskym3QMdVymJl4Bi99-gV4uXDV1kCeFx-3-hUv0LgfBKnJNLFs96TN9mugx4zgppLnfddWr8uQib3xDKB8kPDrM"
}
{"type":"urn:acme:error:rateLimited","detail":"Error creating new cert :: Too many certificates already issued for: ddns.net","status":429}
Traceback (most recent call last):
File "sign_csr.py", line 441, in
signed_crt = sign_csr(args.public_key, args.csr_path, email=args.email, file_based=args.file_based)
File "sign_csr.py", line 386, in sign_csr
resp = urllib2.urlopen(csr_url, csr_data)
File "/usr/lib/python2.7/urllib2.py", line 127, in urlopen
return _opener.open(url, data, timeout)
File "/usr/lib/python2.7/urllib2.py", line 410, in open
response = meth(req, response)
File "/usr/lib/python2.7/urllib2.py", line 523, in http_response
'http', request, response, code, msg, hdrs)
File "/usr/lib/python2.7/urllib2.py", line 448, in error
return self._call_chain(_args)
File "/usr/lib/python2.7/urllib2.py", line 382, in _call_chain
result = func(_args)
File "/usr/lib/python2.7/urllib2.py", line 531, in http_error_default
raise HTTPError(req.get_full_url(), code, msg, hdrs, fp)
urllib2.HTTPError: HTTP Error 429: Unknown

Seems like there's a limit on domains at least those broad dyndns ones. Is it at all possible to issue certs for free domains? I'll switch to any dyndns provider as long as it is supported by letsencrypt.

Thanks for any hint

php automatic validation file creation

Hello, will be interesting adding a parameter -p like -f that calls a simple php script in the server that creates the validation file in .well-known/acme-challenge/

A way to bypass sudo access.

Instead of using sudo python to complete the challenge, can you provide the file and contents to save?

I'm trying to create a certificate on a server that I don't have root/sudo access.

If instead of providing:
sudo python -c "import BaseHTTPServer; \

h = BaseHTTPServer.BaseHTTPRequestHandler; \
h.do_GET = lambda r: r.send_response(200) or r.end_headers() or r.wfile.write('{\"header\": {\"alg\": \"RS256\"}, \"protected\": \"eyJhbGciOiAiUlMyNTYifQ\", \"payload\": \"ewogICAgInRscyI6IGZhbHNlLCAKICAgICJ0b2tlbiI6ICJkbzVaWkMwMHVwZmNFN0tjeEhzOGNyS2FNaE02UFdBdTMtMnVwZ00zRG00IiwgCiAgICAidHlwZSI6ICJzaW1wbGVIdHRwIgp9\", \"signature\": \"Gp5V68da_XdC96piXs1YOhrv4USOQBNnhIL-CMmxvKSigmxAJ8z00xsgWS6nsYD8LPpMVa3GkXhb10qfbymPiWhtMpMYZ31kMLFwgpHrY9xkiNP-WK9Zljz6L-WAzxCOmF1Ov71z_75iEJij86E2f9EmTjDlmDmGAjP9lziII42uyyjjIZg9claU1GtFZUrfXd-uNHHEGHFUpoyLHQcyWCP1T04Xx4q4dY51VeOJNOmIv9csIjkbOma7EqFMAHwYAplAUE45FQ5N9lJvpymD49BoEgQj_kjH-UPnxO3q0QB0i-MJJCiwQYAhMKV618jV9rNE181zJ1FRkX48knMzqoE4oG3yEFUg2D_vAdFG3VCuotnuxrZ7BEzDPWyEm0z8XakxWQW-xHSADtKWRr1qsQCy7qVsoAKnVFQ_1b4rAzET1YfrmhSH4MVhMB5n9tOnjtPQ0OsJVbf0oVLh5AC1rbXe68weOQExDVJgsk56x3FvvwrmdaLe2TnbPJmzpkYUf1OK88e8KmhVYb34veuY1luDOBJQyQ9fOAGZC0F-g7SpWg1lp3hQzf5enkycHMK-fNAfFH7t1m1Ej_CvUuxfBVhI0W8ANpFWL4r8PxTZaZzE6NO38MYgB9nrICiKJuuTQQbsXdjOm22QuxrG1XpWA-vQCtbk-L891Ko6MdAUMzQ\"}'); \
s = BaseHTTPServer.HTTPServer(('0.0.0.0', 80), h); \
s.serve_forever()"

If you could provide an option to save a file on the webserver that would be good.

tempfolder instead of tempdirs

How about using one tempfolder which contains all the temporary files?

This would have the advantage that the requests and the signatures would have nearly identical names(just different suffixes) so you could just go to that directory and
for f in *.json; do openssl dgst -sha256 -sign user.key -out ${f%.json}.sig ${f} done
instead of calling openssl for every file on its own.

Use port >1024

It would be really useful to be able to run the Python HTTP server on a port above 1024, so that non-root users could still verify their request.

Wildcard sub-domains not supported readme

Hey,

I found #26 Multiple Domains in which you explained that wildcards do not work with Let's Encrypt but I would like to see this stated in the readme.

I have not looked at the Let's Encrypt specification but it makes sense. Would be nice if you could add it. I would do it myself and create a pull request but I thought it might be better if you do it in your writing style.

Thanks for the work on the script. Works incredibly well. I used it on 64-bit Ubuntu and OSX 10.10

python 2.6 compat

Compatibility to python 2.6 changing {} to {0} .. {n} in .format lines

here is code since you do not accep branching or pull request

sign_csr.txt

noob-isms

I shall post rev #3 of the code in a minute...ok i lie...soon. SLEEEP!

Anyways, are you a complete noob here?Youre telling by the code that you know how to mangle up http(S) requests and process them (advanced python) but you cant even produce a simple syscall to the underlying os to exec() something? Its os.system() btw in python. exec, I believe is pascal. I forget what they call it in C, because i WRITE PASCAL.

Take a line from Novell a few years back. Let the server maintain/watch the server. Linux is great at one thing very well, running. Running code, it doesnt matter. And by running, we AUTOMATE. Its like a McDonalds in here its so automated.

Seriously? The only part that cant be automated and requires user input is the random seed crap that needs to sit on the server. And I'll get to that part.

  1. ssh [email protected] (missing[new window])
  2. this info is in ACME and the python spews out the file required location on error
  3. seriously? RE-BIND (as root) an already RUNNING web service (apache/nginx)? Port 80 and 443 are ALREADY OPEN! Just put something THERE! And this kind of code prove that the script is not at all nosudo, but rather that it requires it.

Yes, I can do better. AS I said, changes forthcoming. Revision #2 is already posted online.

It took me a minute after fighting with firefox and my DNS settings to make sure things were in order and what was working.I would up on the waitlist with one working cert.
Jokes on the script because thats all I need. DNS shortcut took care of the rest.

Dont get me started on firefox.
This is BASIC PYTHON. Any BOOK can teach you the basics.Im even going on a limb here and saying that some places online teach python(OH WAIT, they DO!) for FREE. I applaud the effort but LEARN to CODE.

Renewal

Loving the client for getting certs, but after about 85 or so days...

Will there be a set of instructions on how to renew, or do we just get new certs again?

Add subjectAltName support

This script should be able to find and validate multiple subject alternative names in one csr.

user@hostname:~$ export SAN="subjectAltName=DNS:example.com,DNS:www.example.com"
user@hostname:~$ openssl req \
>     -new \
>     -key domain.key \
>     -sha256 \
>     -subj "/" \
>     -reqexts SAN -config <(cat /etc/ssl/openssl.cnf <(printf "[SAN]\n$SAN")) \
> | openssl req -text -noout
Certificate Request:
    Data:
        Version: 0 (0x0)
        Subject: 
        Subject Public Key Info:
            Public Key Algorithm: rsaEncryption
                Public-Key: (2048 bit)
                Modulus:
                    00:a8:05:50:86:49:98:c8:05:01:e9:50:18:7f:2f:
                    b4:89:09:29:d1:c1:58:d8:14:bb:58:1d:25:50:11:
                    bb:43:d8:28:03:a5:de:59:49:bb:d2:f7:d3:79:5c:
                    c6:99:2c:98:ff:99:23:8c:df:96:7c:ea:4b:62:2a:
                    a4:c2:84:f5:5d:62:7f:7d:c4:7c:e2:c3:db:e6:58:
                    03:c2:26:9d:02:da:bb:84:d9:11:82:fe:38:12:9b:
                    c7:b6:ff:b2:40:30:38:b1:44:d8:47:1d:43:4a:29:
                    58:6b:49:ec:33:d7:dc:a7:1b:90:05:3a:f5:e6:16:
                    98:08:5d:2d:7e:b4:ea:a2:a4:b1:84:89:f7:f1:c4:
                    67:a6:a1:06:70:dd:4e:6b:0c:f8:b5:9b:bc:3f:06:
                    ee:90:d6:86:29:52:d3:af:f6:d4:2f:c6:cf:4b:5a:
                    b8:cd:01:74:6d:5c:25:a8:02:1c:7c:e8:66:3d:46:
                    07:b1:9d:ef:cc:eb:90:b6:bf:7b:33:e0:5f:b2:9b:
                    e8:b4:12:67:2f:8d:0d:9b:54:9d:95:6e:09:83:cb:
                    f3:5b:1f:31:8e:3b:ca:4e:08:e0:40:c0:60:40:72:
                    dd:0d:3e:99:ec:7c:ac:c4:3c:ba:85:9d:d9:d9:6b:
                    02:2e:bf:a8:a3:02:1d:eb:c8:58:e3:04:b3:a5:f1:
                    67:37
                Exponent: 65537 (0x10001)
        Attributes:
        Requested Extensions:
            X509v3 Subject Alternative Name: 
                DNS:example.com, DNS:www.example.com
    Signature Algorithm: sha256WithRSAEncryption
         99:a5:f0:c3:6d:5c:fc:90:5a:de:3e:3e:15:f1:5c:90:18:48:
         04:26:bb:3e:3a:13:7b:14:93:fe:ff:d4:87:ee:06:8b:f3:82:
         83:54:d8:99:82:48:e3:27:d5:5e:d7:88:2d:c0:70:b6:34:93:
         57:16:c5:7a:8a:3d:38:43:b2:06:21:ee:82:e2:6b:6a:5a:22:
         e0:68:a0:ed:88:d1:a6:42:b7:d4:a0:f6:5d:18:7f:41:f1:3e:
         cf:5c:f8:36:8c:8f:53:db:eb:e6:ac:f9:0d:cd:11:f0:80:84:
         7d:68:14:a9:58:21:40:fb:3e:9c:c8:1d:b0:89:14:f2:a0:55:
         2a:ab:3a:0e:be:12:6b:23:d4:35:84:0c:40:13:c9:bd:cf:5d:
         bf:04:e1:20:01:99:28:fc:58:bf:08:64:ee:8d:b4:35:fc:e9:
         00:70:64:c8:5c:98:0b:6a:56:0e:44:9a:4a:2e:9f:0e:87:1a:
         43:06:0c:40:79:b2:92:34:85:72:d4:db:6b:2a:77:7c:40:02:
         9e:b0:88:2c:41:fc:0e:ed:1d:75:d7:de:4f:09:3c:84:82:ea:
         fe:24:69:5d:af:a4:a9:0f:c8:8f:56:69:13:fa:e4:94:3c:64:
         82:4f:60:e2:46:85:b3:80:a6:74:de:38:e5:75:ba:22:fc:2c:
         a6:9f:af:1b

Mis-typed input gives AttributeError: 'NoneType' object has no attribute 'groups'

If you mis-type the csr file path when executing the script, you get an unhelpful and nasty error. It should tell you that it can't find the file rather than giving this error:

paul@ubuntu:~/letsencrypt$ python sign_csr.py user.pub filethatdoesntexist.csr > signed.crt
Reading pubkey file...
Found public key!
Reading csr file...
Traceback (most recent call last):
  File "sign_csr.py", line 331, in <module>
    signed_crt = sign_csr(args.pubkey_path, args.csr_path)
  File "sign_csr.py", line 60, in sign_csr
    domain = re.search("Subject:.*? CN=([^\s,;/]+).*?", out, re.MULTILINE|re.DOTALL).groups()[0]
AttributeError: 'NoneType' object has no attribute 'groups'

Add optional parameter for user.key full path

If user.key is supplied
Either generate (and save) "ready to run" scripts:
openssl dgst -sha256 -sign my_secret_path/user.key -out register_jxcwae.sig register_gtnj7b.json
openssl dgst -sha256 -sign my_secret_path/user.key -out domain_5a64yt.sig domain_ieex7l.json
openssl dgst -sha256 -sign my_secret_path/user.key -out cert_ccna96.sig cert_pf6pwm.json

Or execute “openssl –sign {}” right away.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.