-
-
Save interfect/5f68381d55658d334e2bc4619d796476 to your computer and use it in GitHub Desktop.
#!/usr/bin/env bash | |
# castanet.sh: Script to connect a chromecast to a WiFi network. | |
# | |
# Allows you to put your Chromecast on WiFi and do Chromecast initial setup | |
# without using the Google Home app at all, just using a normal Linux computer. | |
# | |
# You do need your Chromecast to be on Ethernet, or (untested) to join its setup WiFi | |
# network with your PC, and you also need to find out its IP yourself with e.g. | |
# Wireshark. | |
set -e | |
if [[ -z "${CHROMECAST_IP}" || -z "${WIFI_SSID}" || -z "${WIFI_PASSWORD}" ]] ; then | |
echo 1>&2 "Usage: CHROMECAST_IP=\"XXX\" WIFI_SSID=\"XXX\" WIFI_PASSWORD=\"XXX\" ${0}" | |
exit 1 | |
fi | |
if ! which curl >/dev/null 2>/dev/null ; then | |
echo 1>&2 "Install curl to use this script!" | |
exit 1 | |
fi | |
if ! which jq >/dev/null 2>/dev/null ; then | |
echo 1>&2 "Install jq to use this script!" | |
exit 1 | |
fi | |
if ! which nodejs >/dev/null 2>/dev/null ; then | |
echo 1>&2 "Install nodejs to use this script!" | |
exit 1 | |
fi | |
# Set VERBOSITY=-vvv to see Curl traffic happening | |
if [[ -z "${VERBOSITY}" ]] ; then | |
VERBOSITY=-s | |
fi | |
echo "Connecting ${CHROMECAST_IP} to ${WIFI_SSID} with password ${WIFI_PASSWORD}" | |
# Get the device's public key | |
INFO_JSON="$(curl ${VERBOSITY} --insecure --tlsv1.2 --tls-max 1.2 https://${CHROMECAST_IP}:8443/setup/eureka_info)" | |
CHROMECAST_PUBKEY="$(echo "${INFO_JSON}" | jq -r '.public_key')" | |
# Scan for and find the network we want to get the encryption parameters | |
curl ${VERBOSITY} --insecure --tlsv1.2 --tls-max 1.2 -X POST https://${CHROMECAST_IP}:8443/setup/scan_wifi | |
sleep 20 | |
WIFI_JSON="$(curl ${VERBOSITY} --insecure --tlsv1.2 --tls-max 1.2 https://${CHROMECAST_IP}:8443/setup/scan_results)" | |
WIFI_NETWORK_JSON="$(echo "${WIFI_JSON}" | jq ".[] | select(.ssid == \"${WIFI_SSID}\")")" | |
WIFI_AUTH_NUMBER="$(echo "${WIFI_NETWORK_JSON}" | jq -r '.wpa_auth')" | |
WIFI_CIPHER_NUMBER="$(echo "${WIFI_NETWORK_JSON}" | jq -r '.wpa_cipher')" | |
echo "${WIFI_NETWORK_JSON}" | |
# Encrypt the password to the device | |
# Encryption kernel by @thorleifjaocbsen | |
# See <https://github.com/rithvikvibhu/GHLocalApi/issues/68#issue-766300901> | |
ENCRYPTED_KEY="$(nodejs <<EOF | |
let crypto = require('crypto'); | |
let cleartext = "${WIFI_PASSWORD}"; | |
let publicKey = "${CHROMECAST_PUBKEY}"; | |
publicKey = "-----BEGIN RSA PUBLIC KEY-----\n"+publicKey+"\n-----END RSA PUBLIC KEY-----" | |
const encryptedData = crypto.publicEncrypt({ | |
key: publicKey, | |
padding: crypto.constants.RSA_PKCS1_PADDING, | |
// This was in the original thorleifjaocbsen code but seems nonsensical/unneeded and upsest some Nodes | |
//oaepHash: "sha256", | |
}, Buffer.from(cleartext)); | |
console.log(encryptedData.toString("base64")); | |
EOF | |
)" | |
# Generate the command to connect. | |
CONNECT_COMMAND="{\"ssid\": \"${WIFI_SSID}\", \"wpa_auth\": ${WIFI_AUTH_NUMBER}, \"wpa_cipher\": ${WIFI_CIPHER_NUMBER}, \"enc_passwd\": \"${ENCRYPTED_KEY}\"}" | |
# And the command to save the connection. | |
# Include keep_hotspot_until_connected in case we are on the Chromecast's setup hotspot and not Ethernet. | |
# See <https://github.com/rithvikvibhu/GHLocalApi/issues/88#issuecomment-860538447> | |
SAVE_COMMAND="{\"keep_hotspot_until_connected\": true}" | |
# Send the commands | |
curl ${VERBOSITY} --insecure --tlsv1.2 --tls-max 1.2 -H "content-type: application/json" -d "${CONNECT_COMMAND}" https://${CHROMECAST_IP}:8443/setup/connect_wifi | |
# Hope this one gets there before it can actually disconnect if we're using the setup hotspot? | |
# Otherwise we have to use Ethernet or jump over to the target network and find the device again. | |
# See <http://blog.brokennetwork.ca/2019/05/setting-up-google-chromecast-without.html?m=1> for a script that knows how to swap wifi networks but needs to be ported to use the current API. | |
curl ${VERBOSITY} --insecure --tlsv1.2 --tls-max 1.2 -H "content-type: application/json" -d "${SAVE_COMMAND}" https://${CHROMECAST_IP}:8443/setup/save_wifi | |
# To see it working, if you aren't kicked off the hotspot (or if you set the new CHROMECAST_IP in your shell): | |
# | |
# curl --insecure --tlsv1.2 --tls-max 1.2 https://${CHROMECAST_IP}:8443/setup/eureka_info | jq . | |
# | |
# To list known networks: | |
# | |
# curl --insecure --tlsv1.2 --tls-max 1.2 https://${CHROMECAST_IP}:8443/setup/configured_networks | jq . | |
# | |
# To forget a newtwork: | |
# | |
# curl --insecure --tlsv1.2 --tls-max 1.2 -H "content-type: application/json" -d '{"wpa_id": 0}' https://${CHROMECAST_IP}:8443/setup/forget_wifi | |
# | |
# If you leave Ethernet plugged in, the Chromecast will ARP for its WiFi IP on | |
# Etherenet and drop the WiFi connection! Unplug the Chromecast, and plug it in | |
# again with no Ethernet, to get it to keep the WiFi connection up! | |
# | |
# Set Name and opt out of things: | |
# | |
# curl --insecure --tlsv1.2 --tls-max 1.2 -H "content-type: application/json" -d '{"name": "NovakCast5000", "opt_in": {"crash": false, "stats": false, "opencast": false}}' https://${CHROMECAST_IP}:8443/setup/set_eureka_info | |
On Linux I'm having some trouble. Any thoughts on this?
Error: error:0680007B:asn1 encoding routines::header too long
at Object.publicEncrypt (node:internal/crypto/cipher:79:12)
at [stdin]:5:30
at Script.runInThisContext (node:vm:129:12)
at Object.runInThisContext (node:vm:307:38)
at node:internal/process/execution:79:19
at [stdin]-wrapper:6:22
at evalScript (node:internal/process/execution:78:60)
at node:internal/main/eval_stdin:30:5
at Socket.<anonymous> (node:internal/process/execution:195:5)
at Socket.emit (node:events:525:35) {
opensslErrorStack: [
'error:0688000D:asn1 encoding routines::ASN1 lib',
'error:0688010A:asn1 encoding routines::nested asn1 error',
'error:06800066:asn1 encoding routines::bad object header'
],
library: 'asn1 encoding routines',
reason: 'header too long',
code: 'ERR_OSSL_ASN1_HEADER_TOO_LONG'
}
This works. Thank you.
One remark though, line 19 says ' echo 1>&2 "Install jq to use this script!"' but should say ' echo 1>&2 "Install curl to use this script!"'
@yann1420 Fixed!
I have a factory-reset chromecast, all it shows is a message telling me to download the app to setup.
I don't know which IP does it have, it doesn't show up on my router (which makes sense since it doesn't know the SSID/password). What am i missing? Is this because uPnP is off in the router?
After installing jq and nodejs, it worked great on a factory reset NC2-6A5 after Google killed their android apps talking to it.
Thanks for getting it back!
Thank you for this script. It seems really important nowadays!
I tried to remove the nodejs dependency here: https://gist.github.com/DavidePrincipi/e2007b998bb66ea69a1ff7832af6c417/revisions#diff-f602019a80ebff228e17d6515aa93d17d299f6c0dc3610962565f7d637091e33
It seems that 5GHz WiFi channels above 149 are not supported by some firmware versions, so disconnecting the device after calling setup/connect_wifi is not caused by an issue on this script
Good job! !!! Your research is the only way to configure a Chromecast since Google's CA is expired (205-03-09) for *cast (chromecast,nest,autiocast,...)
I rewrote the script in python3 :
https://gist.github.com/1mm0rt41PC/78e134d43e5badf3cc999bdf33e1fdd5
Thanks for posting the script, it's very helpful.
In case anyone else gets
connection refused
errors from curl when running the script, it seems older versions of the chromecast firmware only exposed wifi setup services over http on port 8008. I had this issue with a gen3 chromecast right out of the box (manufacture date 05/2021).Old firmware:
I changed the curl calls to use http on port 8008 and everything went smoothly. Once the chromecast was connected to wifi, it did an automatic firmware update and rebooted. Interestingly, after the firmware update the chromecast now exposed setup services on https port 8443. This port was then required to be used for setup on the new firmware, so the script above worked as is.
New firmware: