Skip to content

Instantly share code, notes, and snippets.

@mhofman
Created October 13, 2017 09:48
Show Gist options
  • Star 9 You must be signed in to star a gist
  • Fork 5 You must be signed in to fork a gist
  • Save mhofman/f8e1fdd5dce49abacf5fd776fb3727ce to your computer and use it in GitHub Desktop.
Save mhofman/f8e1fdd5dce49abacf5fd776fb3727ce to your computer and use it in GitHub Desktop.
Generate a signed certificate valid to connect locally to a Lutron Caseta Smart Bridge
#!/bin/sh
# Usage: get_lutron_cert.sh [bridge_ip] | tee cert.pem
function error() {
echo "Error: $1" >&2
exit 1
}
login_server="device-login.lutron.com"
app_client_id="e001a4471eb6152b7b3f35e549905fd8589dfcf57eb680b6fb37f20878c28e5a"
app_client_secret="b07fee362538d6df3b129dc3026a72d27e1005a3d1e5839eed5ed18c63a89b27"
app_oauth_redirect_page="lutron_app_oauth_redirect"
cert_subject="/C=US/ST=Pennsylvania/L=Coopersburg/O=Lutron Electronics Co., Inc./CN=Lutron Caseta App"
base_url="https://${login_server}/"
redirect_uri_param="https%3A%2F%2F${login_server}%2F${app_oauth_redirect_page}"
authorize_url="${base_url}oauth/authorize?client_id=${app_client_id}&redirect_uri=${redirect_uri_param}&response_type=code"
openssl version >/dev/null || error "openssl required"
jq --version >/dev/null || error "jq required"
echo "Open Browser and login at ${authorize_url}" >&2
echo "Enter the URL (of the \"error\" page you got redirected to (or the code in the URL): " >&2
read -r redirected_url
oauth_code=`echo ${redirected_url} | sed -e's/^\(.*\?code=\)\{0,1\}\([0-9a-f]*\)\s*$/\2/;t;d'`
[ -n "$oauth_code" ] || error "Invalid code"
private_key="`openssl genrsa 2048 2>/dev/null`"
escaped_csr="`echo \"$private_key\" | openssl req -new -key /proc/self/fd/0 -subj \"${cert_subject}\" | awk 'NF {sub(/\r/, \"\"); printf \"%s\\\\n\",$0;}'`"
[ -n "$escaped_csr" ] || error "Couldn't generate CSR"
token="`curl -s -X POST -d \"code=${oauth_code}&client_id=${app_client_id}&client_secret=${app_client_secret}&redirect_uri=${redirect_uri_param}&grant_type=authorization_code\" ${base_url}oauth/token`"
[ "bearer" == "`echo \"$token\" | jq -r '.token_type'`" ] || error "Received invalid token $token. Try generating a new code (one time use)."
access_token="`echo \"$token\" | jq -r '.access_token'`"
pairing_request_content="{\"remote_signs_app_certificate_signing_request\":\"${escaped_csr}\"}"
pairing_response="`echo \"$pairing_request_content\" | curl -s -X POST -H \"X-DeviceType: Caseta,RA2Select\" -H \"Content-Type: application/json\" -H \"Authorization: Bearer ${access_token}\" -d \"@-\" ${base_url}api/v1/remotepairing/application/user`"
#echo "$pairing_response"
app_cert="`echo \"$pairing_response\" | jq -r '.remote_signs_app_certificate'`"
remote_cert="`echo \"$pairing_response\" | jq -r '.local_signs_remote_certificate'`"
echo "$app_cert" | openssl x509 -noout || error "Received invalid app cert in pairing response $pairing_response"
echo "$remote_cert" | openssl x509 -noout || error "Received invalid remote cert in pairing response $pairing_response"
echo -e "$private_key\n$app_cert\n$remote_cert"
server_addr=$1
[ -n "$server_addr" ] || exit 0
echo "$app_cert" | { echo "$private_key" | { echo "$remote_cert" | {
leap_response=`(echo '{"CommuniqueType":"ReadRequest","Header":{"Url":"/server/1/status/ping"}}'; sleep 3) 3<&- 4<&- 5<&- | openssl s_client -connect "$server_addr:8081" -cert /proc/self/fd/3 -key /proc/self/fd/4 -CAfile /proc/self/fd/5 -quiet -no_ign_eof 2>/dev/null`
[ "$?" -eq "0" ] || error "Could not connect to bridge"
echo "Successfully connected to bridge, running LEAP Server version `echo \"$leap_response\" | jq -r '.Body.PingResponse.LEAPVersion'`" >&2
} 5<&0; } 4<&0; } 3<&0
@nriley
Copy link

nriley commented Oct 14, 2017

This script has quite a few OS dependencies. I fixed some of them at https://gist.github.com/nriley/e0b7ddeb3f3dfd48bb77bfc91ff6d96f but it's still failing on macOS getting {} back as a pairing_response and I don't have further time to troubleshoot.

Thanks for working on this.

@mhofman
Copy link
Author

mhofman commented Oct 15, 2017

tried to minimize dependencies and I thought it only required openssl, curl, sed, awk and jq. Tried to make it as Posix compliant as possible and tested both in Bash and BusyBox's sh, but I don't have a Mac so I couldn't test it there.

The problem is most likely in the use of input redirection to pass around data, and the fact there's no standard way to access a process file descriptors (/proc/self on some systems, /dev on others). I wanted to avoid creating temporary files (which requires cleanup). I didn't want to use Bash's process substitution as most embedded systems don't have bash.

I'm open to any improvements that don't require Bash, correctly handle temporary files and work on embedded systems running Busybox (try your router for example). I'm also open to getting rid of jq as it's the least standard package used here. It could probably be done with some creative regular expressions.

@scottocs11
Copy link

scottocs11 commented Oct 26, 2017

I tried this and pasted in my code, but it gave me an error:

sed: 2: "s/^\(.*\?code=\)\{0,1\} ...": undefined label ';d'
Error: Invalid code

My code is (couple of # in middle for privacy if it matters, I'm not sure)
4cfea4fdca36a489fc7095ab99b96bd7079671#####27a0dd96e384a9df8f6d

Edit: Nriley's code worked.

@grahamr
Copy link

grahamr commented Oct 26, 2017

I'll second i was getting invalid code, but @nriley's mods worked

@slinger987
Copy link

@nriley Were you able to ever determine with you were getting an empty pairing_response?

@JaronrH
Copy link

JaronrH commented Apr 17, 2018

FYI, it looks like the error page (line 24-27) no longer works -- Lutron redirects you to their login page now. Stinks because it looks like Lutron refreshed their certs so the ones I pulled in November no longer work!

@sberryman
Copy link

Confirmed, also getting invalid config with the certs generated.

@sjakub
Copy link

sjakub commented May 9, 2018

I also cannot get the certificate.
I modified the script to just run curl in verbose mode, and that's what I'm getting (replaced long keys with (...) ):

Open Browser and login at https://device-login.lutron.com/oauth/authorize?client_id=e0(...)5a&redirect_uri=https%3A%2F%2Fdevice-login.lutron.com%2Flutron_app_oauth_redirect&response_type=code
Enter the URL (of the "error" page you got redirected to (or the code in the URL): 
67(...)8a4
Request: '{"remote_signs_app_certificate_signing_request":"-----BEGIN CERTIFICATE REQUEST-----\nMIICwjCCAaoCAQAwfTELMAkGA1UEBhMCVVMxFTATBgNVBAgMDFBlbm5zeWx2YW5p\n(...)\nXgTCob8JM8NCuzKWkeIFpnLWXVoIIv2qEk2MVrFUg8dX8+B/cMU=\n-----END CERTIFICATE REQUEST-----\n"}'
Note: Unnecessary use of -X or --request, POST is already inferred.
*   Trying 54.243.113.58...
* TCP_NODELAY set
* Connected to device-login.lutron.com (54.243.113.58) port 443 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
* successfully set certificate verify locations:
  CAfile: /etc/ssl/certs/ca-certificates.crt
  CApath: none
* TLSv1.2 (OUT), TLS handshake, Client hello (1):
* TLSv1.2 (IN), TLS handshake, Server hello (2):
* TLSv1.2 (IN), TLS handshake, Certificate (11):
* TLSv1.2 (IN), TLS handshake, Server key exchange (12):
* TLSv1.2 (IN), TLS handshake, Server finished (14):
* TLSv1.2 (OUT), TLS handshake, Client key exchange (16):
* TLSv1.2 (OUT), TLS change cipher, Client hello (1):
* TLSv1.2 (OUT), TLS handshake, Finished (20):
* TLSv1.2 (IN), TLS handshake, Finished (20):
* SSL connection using TLSv1.2 / ECDHE-RSA-AES128-GCM-SHA256
* ALPN, server did not agree to a protocol
* Server certificate:
*  subject: C=US; ST=Pennsylvania; L=Coopersburg; O=Lutron Electronics Co., Inc.; OU=IT; CN=device-login.lutron.com
*  start date: Feb 13 00:00:00 2018 GMT
*  expire date: Feb 12 12:00:00 2021 GMT
*  subjectAltName: host "device-login.lutron.com" matched cert's "device-login.lutron.com"
*  issuer: C=US; O=DigiCert Inc; OU=www.digicert.com; CN=Thawte RSA CA 2018
*  SSL certificate verify ok.
> POST /api/v1/remotepairing/application/user HTTP/1.1
> Host: device-login.lutron.com
> User-Agent: curl/7.59.0
> Accept: */*
> X-DeviceType: Caseta,RA2Select
> Content-Type: application/json
> Authorization: Bearer 3859(...)df6c
> Content-Length: 1103
> Expect: 100-continue
> 
< HTTP/1.1 100 Continue
* We are completely uploaded and fine
< HTTP/1.1 400 Bad Request
< Server: Cowboy
< Date: Wed, 09 May 2018 01:14:12 GMT
< Connection: keep-alive
< Content-Type: text/html; charset=utf-8
< X-Request-Id: ec2fb587-d1ab-4aa9-ac13-4b82c5d10e33
< X-Runtime: 0.004386
< Content-Length: 0
< Via: 1.1 vegur
< 
* Connection #0 to host device-login.lutron.com left intact

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment