Skip to content

Instantly share code, notes, and snippets.

@1mm0rt41PC
Forked from interfect/castanet.sh
Last active March 12, 2025 14:25
Show Gist options
  • Save 1mm0rt41PC/78e134d43e5badf3cc999bdf33e1fdd5 to your computer and use it in GitHub Desktop.
Save 1mm0rt41PC/78e134d43e5badf3cc999bdf33e1fdd5 to your computer and use it in GitHub Desktop.
Set up a Chromecast from a Linux PC, without an Android or iOS mobile device and without Google Home
#!/usr/bin/env python3
# castanet.py: 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 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.
#
#
# Since 2025-03-09 the CA of Google for Chromecast is expired (see https://www.reddit.com/r/Chromecast/comments/1j7lhrs/comment/mgy1a88/ )
# To enable Cast from android:
# 1) enable adb
# 2) adb shell am start-activity -a com.google.android.gms.cast.settings.CastSettingsCollapsingDebugAction
import os
import sys
import json
import time
import subprocess
import requests
import base64
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives.asymmetric import padding
from cryptography.hazmat.primitives import serialization
# Disable SSL warnings for self-signed certificates
import urllib3
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
try:
import requests
import json
from cryptography.hazmat.primitives import serialization
except ImportError as e:
print(f"Error: Missing dependency - {e}")
print("Install required packages with: pip install requests cryptography")
sys.exit(1)
def main():
# STEP0 => Connect to the chromecast temporary hotspot wifi
chromecast_ip = '192.168.255.249'
wifi_ssid = 'IOT'
wifi_auth_number = 7 # PSK
wifi_cipher_number = 4 # WPA2
wifi_password = '******'
chromecast_name = 'MY-CAST'
print(f"Connecting {chromecast_ip} to {wifi_ssid} with password {wifi_password}")
# Base URL for API requests
base_url = f"https://{chromecast_ip}:8443/setup"
# Get the device's public key
info_response = requests.get(f"{base_url}/eureka_info", verify=False)
info_json = info_response.json()
chromecast_pubkey = info_json["public_key"]
if wifi_auth_number != '':
# Scan for WiFi networks
requests.post(f"{base_url}/scan_wifi", verify=False)
print("Scanning for WiFi networks...")
time.sleep(20) # Wait for scan to complete
# Get scan results
wifi_response = requests.get(f"{base_url}/scan_results", verify=False)
wifi_json = wifi_response.json()
# Find our network
wifi_network = None
for network in wifi_json:
if network["ssid"] == wifi_ssid:
wifi_network = network
break
if not wifi_network:
print(f"Error: Could not find WiFi network '{wifi_ssid}'", file=sys.stderr)
sys.exit(1)
wifi_auth_number = wifi_network["wpa_auth"]
wifi_cipher_number = wifi_network["wpa_cipher"]
print(json.dumps(wifi_network, indent=2))
# Encrypt the password for the device
encrypted_key = encrypt_password(wifi_password, chromecast_pubkey)
# 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
save_command = {"keep_hotspot_until_connected": True}
# Send the commands
headers = {"content-type": "application/json"}
connect_response = requests.post(
f"{base_url}/connect_wifi",
headers=headers,
data=json.dumps(connect_command),
verify=False
)
print("Connect response: %s", connect_response.status_code)
print("Connect Headers: %s", connect_response.headers)
save_response = requests.post(
f"{base_url}/save_wifi",
headers=headers,
data=json.dumps(save_command),
verify=False
)
print("Connect response: %s", save_response.status_code)
print("Connect Headers: %s", save_response.headers)
print("Connect Headers: %s", save_response.text)
# Set the Chromecast name if provided
if chromecast_name:
rename_command = {
"name": chromecast_name,
"opt_in": {
"crash": False,
"stats": False,
"opencast": False
}
}
rename_response = requests.post(
f"{base_url}/set_eureka_info",
headers=headers,
data=json.dumps(rename_command),
verify=False
)
print(f"Renamed Chromecast to '{chromecast_name}'. Response:", rename_response.status_code)
print("\nConnection commands sent successfully!")
print("""
Additional commands you can run:
- To see device info:
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 network:
curl --insecure --tlsv1.2 --tls-max 1.2 -H "content-type: application/json" -d '{"wpa_id": 0}' https://${CHROMECAST_IP}:8443/setup/forget_wifi
- To 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
""")
def encrypt_password(password, public_key_pem):
"""Encrypt the WiFi password with the Chromecast's public key"""
# Format the key properly
public_key_pem = f"-----BEGIN RSA PUBLIC KEY-----\n{public_key_pem}\n-----END RSA PUBLIC KEY-----"
# Load the public key
public_key = serialization.load_pem_public_key(
public_key_pem.encode('utf-8'),
backend=default_backend()
)
# Encrypt the password
encrypted_data = public_key.encrypt(
password.encode('utf-8'),
padding.PKCS1v15()
)
# Return the base64 encoded result
return base64.b64encode(encrypted_data).decode('utf-8')
if __name__ == "__main__":
check_dependencies()
main()
'''
# 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
'''
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment