Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How does Device Authentication work ? #2

Open
thibauts opened this issue Jun 8, 2014 · 122 comments
Open

How does Device Authentication work ? #2

thibauts opened this issue Jun 8, 2014 · 122 comments

Comments

@thibauts
Copy link
Owner

thibauts commented Jun 8, 2014

I'm trying to understand device authentication right now and will post progress in this issue.

@thibauts
Copy link
Owner Author

thibauts commented Jun 8, 2014

The culprit is the VerifyCredentials function in cast_auth_util_nss.cc.

The Chromecast uses 2 certificates. The first one is used for the TLS connection, is self-signed and expires every 24 hours (let's call it the peer certificate). The second one is used to validate the first in the device authentication process (let's call it the platform certificate).

I added a few tools in the bin directory to help dumping and inspecting the various files.

The process seems to go as follows.

  1. Every 24 hours the dongle generates a new self-signed TLS peer certificate.
  2. A client sends a DeviceAuth challenge.
  3. The dongle generates a signature of its current peer certificate using the platform certificate private key and returns both the platform certificate and the signature to the client.
  4. First the client verifies that the platform certificate is signed by a trusted CA using its embedded public key (kCAPublicKeyDER)
  5. The client then extracts the platform certificate public key and uses it to check that the returned signature matches the peer certificate signed with the platform certificate.

Long story short, I'm not an expert in cryptography but it seems that without the platform certificate private key used to produce the signature, not much can be done.

@luke
Copy link

luke commented Aug 25, 2014

I stumbled across this repo. It seems to have tools related to the certs on the chromecast. Thought it migth be of some help. https://github.com/EiNSTeiN-/chromecast-widevine-tools

@thibauts
Copy link
Owner Author

Looks good indeed. Thanks a lot !

@leo-labs
Copy link

Are there some updates on this? :)

@thibauts
Copy link
Owner Author

Nothing yet. I don't have time nor interest in searching this direction but I'll help if I can.

@leo-labs
Copy link

Am I rigth that the only way to get authentiation is work is buying a chromecast and root it?

@thibauts
Copy link
Owner Author

I can't say for sure. A first step would be understanding the auth scheme and looking at the chromium source code.

@emersion
Copy link

Somme guys managed to make it work in a Python project: https://github.com/dz0ny/leapcast
I will try to add this to node-castv2 soon :-)

@thibauts
Copy link
Owner Author

Hmm, where exactly is the code that does it ?

@emersion
Copy link

I think sign_data is very interesting here: https://github.com/dz0ny/leapcast/blob/master/leapcast/services/dial.py

@leo-labs
Copy link

I do not think, that this is related to chromecast v2 protocol;

@thibauts
Copy link
Owner Author

I think @leo-labs is right, it doesn't look like the same process at all. Probably only relevant to the DIAL protocol.

@Afterster
Copy link

The chromecast protocol is new to me so sorry if this is a dummy question :).
If the 24h-generated certificate is only used for Chromecast device legacy authentication, an "authentication proxy" replaying the authentication request to a real Chromecast should work isn't it?

@thibauts
Copy link
Owner Author

thibauts commented Jan 2, 2015

This looks like it should work in principle, but in effect I fear the Chrome browser will be too sensitive. During discovery the browser does an mDNS lookup and immediately connects to and authenticates the devices it found. I'm not sure two devices with the same credentials will be accepted.

I may even have tried this and failed, can't remember exactly. I may have messed something up during my tests, too.

@JumpLink
Copy link

Did someone just ask google to get an certificate for custom applications? Google seems to allow chromecast devices from other manufacturers, like smartTV's or speakers, for example this: http://www.xperiablog.net/2015/01/07/sony-announces-speakers-with-google-cast-for-audio-support/ Maybe this is not as problematic as we think?

@thibauts
Copy link
Owner Author

I doubt they took the burden of creating an auth system only to deliver free keys later. But well, you should ask !

@JumpLink
Copy link

@HomerSp
Copy link
Contributor

HomerSp commented Jan 14, 2015

I've done some looking into this, and noticed that the AuthResponse structure of cast_channel.proto is slightly wrong. There is an additional repeated field at index 3 that contains the CA certificates that's used for verifying the client_auth_certificate on the client-side (on Android it's done through CertPathValidator).
So the correct struct would look like this:
message AuthResponse {
required bytes signature = 1;
required bytes client_auth_certificate = 2;
repeated bytes ca_certs = 3;
}

@thibauts
Copy link
Owner Author

Interesting. I have no immediate interest in pursuing that but I'll gladly accept PRs.

@friscoMad
Copy link

Checking the last version of the sources it is clear that there are several CAs and that now that field is repeated as a CA chain can be sent.
The legacy flow (only one CA) just checks agains the same CA public key and if a chain is sent then it seeks for the CA cert hast and check against the proper certificate (¿kPublicKeyICA1 and kPublicKeyICA2 are the same cert with different hash?) .

So to make all this work we need a device cert signed with one of those CAs or use a Chrome client that skips that validation. I am no aware of anyone but it should be simple to create one.

Probably there is one CA cert for every vendor, so they have to pay the fees to be included in the SDK and future ChromeCast clients.
Rooting any authorized device will provide us a valid cert, and unless they want to disable a lot of official devices they will not be able to revoke that cert, so that seems the only way to go if Google don't want to give a cert to the public and we want a server that works with any client.

@emersion
Copy link

emersion commented May 2, 2015

Has anyone tried to install this XPosed module to be able to skip Device Authentication?

https://github.com/HomerSp/XposedCastClientFix

(Source: http://forum.xda-developers.com/hardware-hacking/chromecast/app-cast-receiver-app-android-t2900726)

@katlogic
Copy link

katlogic commented May 3, 2015

@Afterster Indeed that appears to be the case. Simplest course of action would be to expose public internet service providing signatures via libGtvCa (ie everyone would essentialy see the very same device) running on a rooted dongle.

This is both simplest implementation and works against the worst case when signatures are provided by DRM hardware in the dongle (ie extracting root key will be very difficult).

EDIT: According to XDA, v2 proto seems to use widevine hardware DRM for key provisioning. Hey google, you promised to not be evil :/

@leo-labs
Copy link

leo-labs commented May 8, 2015

@JumpLink I pushed your qquestion into the chromecast help forum. Maybe this is the right forum. https://productforums.google.com/forum/#!category-topic/chromecast/discuss-and-give-feedback/setting-up-chromecast/other-os/hXZAvdvjk_w

@friscoMad
Copy link

It seems Google does not want receivers outside the oficial one
A XDA Developer managed to get a receiver working but Google sent a Cease & Desist
http://forum.xda-developers.com/hardware-hacking/chromecast/app-youmap-chromecast-receiver-android-t316185

@xmikos
Copy link

xmikos commented Sep 23, 2015

@frisco82 I have looked into YouMap Receiver apk and I don't blame Google for this Cease & Desist too much. That apk included big parts of proprietary Google Cast Receiver apk (and it was paid app on Google Play! Very daring and reckless...).

Also there was no magic, there has been only drm.json file in the apk full of pregenerated daily Chromecast V2 protocol keys (for one month period). I think author of YouMap Receiver just pregenerated them on rooted Chromecast. Maybe this can be solution for opensource implementation - someone with rooted Chromecast can pregenerate daily keys for a long period (something like many many years) and publish them anonymously on net.

@JumpLink
Copy link

JumpLink commented Oct 6, 2015

@leo-labs thank you!

@kangdd
Copy link

kangdd commented Feb 23, 2022

@sashahilton00 , but how so many apps can act as a chromcast device, like airreceiver?

@sieempi
Copy link

sieempi commented Feb 23, 2022

For example, Reflector4 generates an ssl certificate (peer cert) every 48h, the signature returned in the AuthResponse is valid. I don't think the response is pre-computed (am I wrong?) How do they generate the signature of their peer cert without the private key (theirs)?

I guess I don't really understand how certificates work at the moment...

Precomputed. Do a clean install and you'll see it has the same certificate.

@sashahilton00
Copy link

@sashahilton00 , but how so many apps can act as a chromcast device, like airreceiver?

The developer of the app will be running a rooted chromecast and pushing new peer certificates to the app every 24/48 hours would be my guess.

@schneid-l
Copy link

I can reproduce the certificate generation at each reinstallation of Reflector4, each time there is a new peer cert, and the signature sent in the AuthResponse is valid
Each time I proxied Reflector4, no requests were sent over internet (except usage statistics)
It also works if my computer is not connected to the internet

Reflector4, generated yesterday

peer_cert

-----BEGIN CERTIFICATE-----
MIIC6TCCAdECFF31aE7etviio4dzvr7p/A9vvJk4MA0GCSqGSIb3DQEBBQUAMC8x
LTArBgNVBAMMJDFmODViZDU0LWMxZTYtOTZmYy0yOWI1LTI4MTQ1NTA2NDg4MzAi
GA8yMDIyMDIyMjA2MDAwMFoYDzIwMjIwMjI0MDUwMDAwWjAvMS0wKwYDVQQDDCQx
Zjg1YmQ1NC1jMWU2LTk2ZmMtMjliNS0yODE0NTUwNjQ4ODMwggEiMA0GCSqGSIb3
DQEBAQUAA4IBDwAwggEKAoIBAQDM9baYKNuXGIdeOauoFEDCRIjNy1dlGhqy+ba9
Ax7fRWsuZaVpnjcPpZRDNyzz3TMtkHXqHHlHcvyQOqfrCzdNRxwnISq00VZdCTOb
gyjO5pC0u467vDyxVRl89JW8S0hIY81PmAT7fZG0bPHmpA+EP1uwAxoCdfhqQ7yu
/fWDF2Czd4aeorDSQQIywTMhZIEO1PG6d1QnH/nK7VKzW1/tH9JMTXqr8nhxvlkT
Ty+9Pt7seoBvR1+GkcgfIQrpWm67C5jRx4GiEOUpaSCZNTJ7hOaHXc++ZfCM0DlG
rGLjFAJLdaBlPpfQVV35c1b9UjbuAXyAN8UZMHJjoWp2SyRxAgMBAAEwDQYJKoZI
hvcNAQEFBQADggEBAKsOvA7iYmf1miEkir8b02KfXkrRQuSomsrNmH5wjbR65zJe
aBGeegcEviFna5CidNJB4ILJ6R7FCGK+PkXcqr4/XrKIolb1+PhdmKpOMWmNOOtT
Mcoe003Qn/rh6nPhNTPdept8OJB00fakkk0dPdLwjmc3hlT2QLIyGk3vvFnkqwOc
6l4tMTTd22eiGoHKX+AlKFx41st6olYk/nfY78l7elJv5amWnL6BenqfXmS8kgXC
ZHYFJXR9rZBtzQAH87ntNv8Rb9Ku2fuxpLU4QLeWe8P5nd/HEqYyjPUrC7cfQ1dB
ZOFPfi90waUUQS8tPxsq8R4EKv/LLpVE8/MeNlE=
-----END CERTIFICATE-----

platform_cert

-----BEGIN CERTIFICATE-----
MIIDqzCCApOgAwIBAgIEUwdGyzANBgkqhkiG9w0BAQUFADB9MQswCQYDVQQGEwJV
UzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNTW91bnRhaW4gVmlldzET
MBEGA1UECgwKR29vZ2xlIEluYzESMBAGA1UECwwJR29vZ2xlIFRWMRgwFgYDVQQD
DA9FdXJla2EgR2VuMSBJQ0EwHhcNMTQwMjIxMTIzMDAzWhcNMzQwMjE2MTIzMDAz
WjCBgDELMAkGA1UEBhMCVVMxFjAUBgNVBAcTDU1vdW50YWluIFZpZXcxEjAQBgNV
BAsTCUdvb2dsZSBUVjETMBEGA1UECBMKQ2FsaWZvcm5pYTETMBEGA1UEChMKR29v
Z2xlIEluYzEbMBkGA1UEAxMSUTlOTlkgRkE4RkNBM0QxNDNDMIIBIjANBgkqhkiG
9w0BAQEFAAOCAQ8AMIIBCgKCAQEApaS8dqiKGs6dfdRSE0kGGbKXmIM4J4rXlgeI
pIRDg4rJY6zmVnQnwEvrD0e8bQy0XVqnEj9nqWGrLMk83I7lxLyDDP27vzHqnpb8
gN/xJJwEt7r58n+gYtfrv5QvDeZaG2RJF6YimZkKp9iJ8NmJpXm8XscbZXp02kMo
j+YG0sfJ3Dmbj+6KhdkS6L/mJiY7ka8UQ/7Fy9Z64jXFQ8UqWOjGV1WUojcqz+Q0
IWu1dOiTqVObuZ0Nuk2iBklqNpanMvWYmj677Fr0S9yZMR836BwVbv5b8cHG+1US
DjeQwYnuDcq1Sp/uRoUPFP1Kvb8wgS2Eb+7tNwJ19ScBH30wrwIDAQABoy8wLTAJ
BgNVHRMEAjAAMAsGA1UdDwQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcDAjANBgkq
hkiG9w0BAQUFAAOCAQEAir7aYhDmCMCRDojVlAOsM4rZdqUrtHmJOAaVzyhIFyEG
Rj21kWew8Db2xOaNmPENPuBDR0K3nEL192PVUzVL0EDIdfoAyS08DkAHd4CY8P7v
Q2ujKJRGnqZM0E024i5dhw6u2W8/bPBuJYQzQ/DPmDwL3FPM+uQH+RKYQkPOyK5G
oi5Ugm9maca9RufTj3AGjYmhoeDwpi7t8ZA/LAE5GrRcXM6gqs0x7vj6hDYAlno8
/RmjaDobc6dzg24JrekVGmRLxb38nbm6W377GyOlvflAUFySbciabrrvNGXZAzaE
dZ0xkTi7WhknYlmNWjDOT+N8dDxFQwOgz1bx5Ioa/Q==
-----END CERTIFICATE-----

signature

jYeCX0JJkcZwynaQbnBMLjaRUY8BYOrVFMaaQ4Vvuw66/HzWafWstFxATBGNmWrc
ppLIuEm//9FTiGz0fV5r56Kk4tSFcdv8xo4BfBM8lhJ6k0SWHga7jMQXYmXWve97
kfAxF5WcQmLCjNXKwwy7eRquqTg7NwYJT/bF4WH24VQKEq+Rxumm0JcSVvbms+O1
9ohp3OxrGBuXiQVdJUmcwl6eL7Yf0dSnmmoQBn+HQ/JtSkd9Uiq9xYA3q3Ng2f7T
UzKTME6WWFbbr1HB2G1Octc813DmTFJtxmDUBv6SYo7Z93FFPWY4tsDdeuai3/A6
KLNSGbupLCgMJG2yuaHHww==

Reflector4, generated today

peer_cert

-----BEGIN CERTIFICATE-----
MIIC6TCCAdECFGq/AGp+BHdBQlpAw+hup0jrS6mnMA0GCSqGSIb3DQEBBQUAMC8x
LTArBgNVBAMMJDFmODViZDU0LWMxZTYtOTZmYy0yOWI1LTI4MTQ1NTA2NDg4MzAi
GA8yMDIyMDIyMzE0MDAwMFoYDzIwMjIwMjI1MTMwMDAwWjAvMS0wKwYDVQQDDCQx
Zjg1YmQ1NC1jMWU2LTk2ZmMtMjliNS0yODE0NTUwNjQ4ODMwggEiMA0GCSqGSIb3
DQEBAQUAA4IBDwAwggEKAoIBAQDKq2FATUG+uUf0Lwkv0klPXLm3oZeaJO8ZAaQN
yL9eO8egKkc1YbxHrbeWBGy3N7lncjIa5Dv0Q7VoQgLW0eY5ra8I2N584eDSp9QY
jpxhbgXnmDAxI2XVP1Ff4d71N9BysZdCaF3g09eW2RJwvchvSmtTEZISSuPrJwdW
dCTtOnglHlwZkMJIe4Hn4t3bYP3cKYJp5kKsqddTISKKDLpHcje5cVeRrZAmO0OP
o3HFhfJsWmQbMCmSEYhS0SSheBIJuiMlmH85km4TBXoMDb0SGWEFRsL5w/hTZc3m
DZquQwq9e7DFjVxGaY36YV1y5qDnTc9YrPvH5cjhzhZ8PSPZAgMBAAEwDQYJKoZI
hvcNAQEFBQADggEBABIeMJwkeNotoZS2G8x2nbKjhQ1u0GN0vVxVtmYXi6JOX4p9
1eXowLVYt48CHRYbSsN3fAWiWgUkEHMMrHxtBm9CqMfwcsTdoPLPMmix+xWRagnF
urdz6JvNV4+PcoMWaeGCIvGAcjpy9NVPPXtupl/1R07bNVpLPZWC6/BA787AmPbD
cPt3dPRZJigx9SmCUpx/wEEuK8L/Z6g77rf34XPjpqqCnT5y6FML4aDhyC03K7aa
+d5jfXGkcte+ZuFWSn1hfzB9oRxdC/U4UThsw0v8EPzSo/XdHcfFS2AzQgKaJXOx
q4hxtGUUF2U8X5kjEw0fpXhyo0yd+e9cmocsHsg=
-----END CERTIFICATE-----

platform_cert

-----BEGIN CERTIFICATE-----
MIIDqzCCApOgAwIBAgIEUwdGyzANBgkqhkiG9w0BAQUFADB9MQswCQYDVQQGEwJV
UzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNTW91bnRhaW4gVmlldzET
MBEGA1UECgwKR29vZ2xlIEluYzESMBAGA1UECwwJR29vZ2xlIFRWMRgwFgYDVQQD
DA9FdXJla2EgR2VuMSBJQ0EwHhcNMTQwMjIxMTIzMDAzWhcNMzQwMjE2MTIzMDAz
WjCBgDELMAkGA1UEBhMCVVMxFjAUBgNVBAcTDU1vdW50YWluIFZpZXcxEjAQBgNV
BAsTCUdvb2dsZSBUVjETMBEGA1UECBMKQ2FsaWZvcm5pYTETMBEGA1UEChMKR29v
Z2xlIEluYzEbMBkGA1UEAxMSUTlOTlkgRkE4RkNBM0QxNDNDMIIBIjANBgkqhkiG
9w0BAQEFAAOCAQ8AMIIBCgKCAQEApaS8dqiKGs6dfdRSE0kGGbKXmIM4J4rXlgeI
pIRDg4rJY6zmVnQnwEvrD0e8bQy0XVqnEj9nqWGrLMk83I7lxLyDDP27vzHqnpb8
gN/xJJwEt7r58n+gYtfrv5QvDeZaG2RJF6YimZkKp9iJ8NmJpXm8XscbZXp02kMo
j+YG0sfJ3Dmbj+6KhdkS6L/mJiY7ka8UQ/7Fy9Z64jXFQ8UqWOjGV1WUojcqz+Q0
IWu1dOiTqVObuZ0Nuk2iBklqNpanMvWYmj677Fr0S9yZMR836BwVbv5b8cHG+1US
DjeQwYnuDcq1Sp/uRoUPFP1Kvb8wgS2Eb+7tNwJ19ScBH30wrwIDAQABoy8wLTAJ
BgNVHRMEAjAAMAsGA1UdDwQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcDAjANBgkq
hkiG9w0BAQUFAAOCAQEAir7aYhDmCMCRDojVlAOsM4rZdqUrtHmJOAaVzyhIFyEG
Rj21kWew8Db2xOaNmPENPuBDR0K3nEL192PVUzVL0EDIdfoAyS08DkAHd4CY8P7v
Q2ujKJRGnqZM0E024i5dhw6u2W8/bPBuJYQzQ/DPmDwL3FPM+uQH+RKYQkPOyK5G
oi5Ugm9maca9RufTj3AGjYmhoeDwpi7t8ZA/LAE5GrRcXM6gqs0x7vj6hDYAlno8
/RmjaDobc6dzg24JrekVGmRLxb38nbm6W377GyOlvflAUFySbciabrrvNGXZAzaE
dZ0xkTi7WhknYlmNWjDOT+N8dDxFQwOgz1bx5Ioa/Q==
-----END CERTIFICATE-----

signature

eSKKXnW3/0z2qJ2UT3/fkF/NJvRIFYgECP1BXN7UdjSPYsIPU4v1Q6lR0H82iAVu
JEJmMXQ9jKoP96ZWzxv8HoJ0jpsnS/7pc069ztV0LpEi05Zqf3dB0NYEr9rc4gEK
f6DHxdHR9n0B31Fgxo5MZjaSH+bL/ZdUorgr0bGETUl/mxnGJc6nTidn8bvPWCUe
iVk6SE//DIVEckdSs5tChdPHfh1yuW22l/KUTqLj+iKAkTma8TKN9KsiTRpfg4rK
wkpkQyAKSiOQueWJU3fIBoK2ZC3TTBzHV4dfuJXUuwsLHm3gzRB9EfPsDjfiFj1t
UwHvx8AaZyI+SUjTzTSnDw==

The platform cert is the same, the peer cert is regenerated every 24h (and on reinstall)

@forlayo
Copy link

forlayo commented Feb 25, 2022

@si0ls about "chromecast_v1.6_content_shell.tgz" I found version 10 here (just in case it helps): https://github.com/githankH/chromecast-mirrored-source.chromium/blob/master/README

Direct link:
https://drive.google.com/open?id=0B3j4zj2IQp7MUThLWk5JRFNBT1k&authuser=2

@schneid-l
Copy link

@forlayo Thanks, I found the (new) Gdrive root link : https://drive.google.com/drive/folders/0B3j4zj2IQp7MV2NZOVFPTDNzSTA

All versions (v1.6 included) are present

@schneid-l
Copy link

@sashahilton00

The problem is that each device certificate has a serial, and when one releases the certificates signed by the device cert into the wild, Google revokes the device cert.

Have you observed a revocation of a device certificate?
Has it really happened so far?

@kangdd
Copy link

kangdd commented Feb 25, 2022

@si0ls This might help you: https://support.google.com/product-documentation/answer/10525328
You can find chromecast_media_shell.so all obj files.

I ever tried to install an chromecast apk that someone dumped from his rooted chromecast to my android phone, other phones can see it by pressing cast icon of youtube. but got failed when trying to connect to it. Check the log of my android phone, it said "no private key found" and other error messages.

You can find all error message strings in the obj files from the above link. unfortunately, they are not source code that i have searched for so long.

@sashahilton00
Copy link

@sashahilton00

The problem is that each device certificate has a serial, and when one releases the certificates signed by the device cert into the wild, Google revokes the device cert.

Have you observed a revocation of a device certificate?

Has it really happened so far?

I haven't observed it directly to be fair. I have spoken with people that claim that after rooting their device and providing a key server that their device stopped working, but I haven't been able to confirm if that was down to revocation or some other issue.

I'd be surprised if they didn't revoke it though - what would be the point of this walled garden approach if they didn't bother to keep it walled?

@sashahilton00
Copy link

@si0ls is the private key included in the app though? My guess is that it probably preloads a week of certificates or something - if it does indeed have a full keypair embedded, then it would be possible to make a cast receiver, on the assumption that Google didn't decide to revoke it.

@forlayo
Copy link

forlayo commented Sep 5, 2022

Decompiling Chromecast built-in (a.k.a. mediashell), which is an app intended to add Chromecast to Android TVs I saw these apps are producing a google's API url and asking for a certificate there.

Just in case this is helpful for someone, you can generate the URL in this way:

    private static MediaDrm sMediaDrm;

    private static final UUID WIDEVINE_UUID;
    static {
        UUID uuid = new UUID(-1301668207276963122L, -6645017420763422227L);
        WIDEVINE_UUID = uuid;
        try {
            sMediaDrm = new MediaDrm(uuid);
        } catch (UnsupportedSchemeException e) {
            Log.wtf(TAG, "Widevine UUID unsupported.", e);
        }
    }
[...]
 try
        {
            Class<MediaDrm> mediaDrmClass = MediaDrm.class;

            Method getCertificateRequest = mediaDrmClass.getMethod("getCertificateRequest", new Class[]{int.class, String.class});
            getCertificateRequest.setAccessible(true);
            Object certRequest = getCertificateRequest.invoke(sMediaDrm,1, "cast_creds");

            Class certRequestClass = Class.forName("android.media.MediaDrm$CertificateRequest");
            Method getDefaultUrl = certRequestClass.getMethod("getDefaultUrl");
            Method getData = certRequestClass.getMethod("getData");

            String defaultURl = (String) getDefaultUrl.invoke(certRequest);
            byte[] data = (byte[]) getData.invoke(certRequest);

            new Thread(() -> Log.d(TAG,"Result!->"+postRequest(defaultURl, data))).start();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }catch (Exception e){
            e.printStackTrace();
        }

The post is a regular post request done in this way:

    private static byte[] postRequest(String url, byte[] drmRequest) {
        String str = "?";
        HttpURLConnection connection = null;
        try {
            try {
                StringBuilder append = new StringBuilder().append(url);
                if (url.contains(str)) {
                    str = "&";
                }

                String url2 = append.append(str).append("signedRequest=").append(new String(drmRequest, "US-ASCII")).toString();
                Log.d(TAG, "PostRequest:" + url2);
                //java.net.URL loca = new URL(url2);
                HttpURLConnection connection2 = (HttpURLConnection) new URL(url2).openConnection();
                connection2.setDoOutput(true);
                connection2.setConnectTimeout(10000);
                connection2.setReadTimeout(10000);
                connection2.setRequestProperty("Accept", "*/*");
                connection2.setRequestProperty("Accept-Encoding", "identity");
                int responseCode = connection2.getResponseCode();
                if (responseCode != 200) {
                    Log.d(TAG, "Server returned HTTP error code " + responseCode);
                    ByteArrayOutputStream output = new ByteArrayOutputStream();
                    streamCopy(connection2.getErrorStream(), output);
                    Log.d(TAG,"Error: "+output.toString());
                    byte[] byteArray = output.toByteArray();
                    if (connection2 != null) {
                        connection2.disconnect();
                    }
                    return null;
                }
                ByteArrayOutputStream output = new ByteArrayOutputStream();
                streamCopy(connection2.getInputStream(), output);
                byte[] byteArray = output.toByteArray();
                if (connection2 != null) {
                    connection2.disconnect();
                }
                return byteArray;
            } catch (UnsupportedEncodingException e) {
                Log.wtf(TAG, "postRequest: US-ASCII unsupported", e);
                if (0 != 0) {
                    connection.disconnect();
                }
                return null;
            } catch (IOException e2) {
                Log.d(TAG, "postRequest: Connection to server failed: "+ e2.getMessage());
                if (0 != 0) {
                    connection.disconnect();
                }
                return null;
            }
        } catch (Throwable th) {
            if (0 != 0) {
                connection.disconnect();
            }
            throw th;
        }
    }

    private static void streamCopy(InputStream inStream, OutputStream outStream) throws IOException {
        byte[] temp = new byte[4096];
        int bytesRead = inStream.read(temp);
        while (bytesRead >= 0) {
            outStream.write(temp, 0, bytesRead);
            bytesRead = inStream.read(temp);
        }
        inStream.close();
        outStream.close();
    }

On an emulator this doesn't works, but it should works on an Android TV; I haven't tested it yet.

@Seb-135
Copy link

Seb-135 commented Jul 19, 2023

Bumping this issue again because after hours of searching, this is the single most promising lead I've been able to find for emulating a chromecast.
The above-mentioned apps still work perfectly fine - was their certificate never revoked? Or do the developers buy and root a new chromecast whenever their application breaks? Also, their apps are still up on google's play store. If google is not going after them, perhaps one of the solutions above would suffice - simply generate several years worth of keys for the open-source receiver reimplementation.
The code would still be useful if the certificate is revoked - a user, or developer of a new app, would be able to take the implementation and replace the keys with one from their own chromecast.

@schneid-l
Copy link

Hi Seb,

I'm glad to see that there are still some people interested in this topic.

I've been trying to get my hands on an unpatched Chromecast gen1 for over a year now so I can root it and extract keys. I can't find any that have never been used, so I suspect that the companies offering software alternatives to the Chromecast are not faced with invalidated keys, and have had to extract, by some means I don't understand, a root key to generate as many certificates as desired.

I sincerely believe that a good option would be to manage to reverse engine one of these apps to obtain their private Key and exploit it.
Unfortunately I have neither the skills nor the time to do this...

As I recall, the keys used were children of the Chromecast gen1 root certificate, which has been deprecated by Google, so I wouldn't be surprised to see the certificate revoked in the near future.

If you have any updates, don't hesitate to share them, I'm very curious to see how far we can go!

Louis

@bitstuffing
Copy link

Probably I 'm able to put a "different" point of view about certification.

Just for fun, I was able to recreate a simple pychromecast without "pychromecast" project from scratch. After that I said to me: why not do the inverse? And I'm doing the same inverting the process.

Next time, I was putting in the place of "this issue", I'm not able to get a auth message because for some reason it's rejected. I supposed that this was for Certification Auto-Signed, and like an AOSP, you have to be signed in Google like developer (using a compiled app by you).

Next time, I discovered castreceiver app and decompiling it I see that this certs could be signed in any Android device (with termux), so I do an script for that:

from cryptography import x509
from cryptography.x509.oid import NameOID
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.hazmat.primitives import serialization
import datetime

# Generate our key
key = rsa.generate_private_key(
    public_exponent=65537,
    key_size=2048
)

# Write our key to disk for safe keeping
with open("/sdcard/key.pem", "wb") as f:  # Modify the path to wherever you want
    f.write(key.private_bytes(
        encoding=serialization.Encoding.PEM,
        format=serialization.PrivateFormat.PKCS8,
        encryption_algorithm=serialization.NoEncryption()
    ))

# Various details about who we are. For a self-signed certificate the
# subject and issuer are always the same.
subject = issuer = x509.Name([
    x509.NameAttribute(NameOID.COMMON_NAME, u"localhost"),
])
cert = x509.CertificateBuilder().subject_name(
    subject
).issuer_name(
    issuer
).public_key(
    key.public_key()
).serial_number(
    x509.random_serial_number()
).not_valid_before(
    datetime.datetime.utcnow()
).not_valid_after(
    # Our certificate will be valid for 150 days
    datetime.datetime.utcnow() + datetime.timedelta(days=150)
).sign(key, hashes.SHA256())

# Write our certificate out to disk.
with open("/sdcard/cert.pem", "wb") as f:  # Modify the path to wherever you want
    f.write(cert.public_bytes(serialization.Encoding.PEM))

Et... voila. In any Android device you could obtain any valid signed certs. But for me this path is ended (because I don't know why you need this certifications).

To run it download termux, launch a pkg install python and install dependencies with pip install cryptography.

But... I'm very worried about other kind of issues, like I'm not able to get a right communication with Chromecast using the Chromium cast_channel.proto in python.

With my server I was able to capture a package (welcome) and send it to a chromecast, but the answer is weird (because python said Error: Error parsing message

A PoC can be found at my repo in develop branch:
https://github.com/bitstuffing/pycast/raw/develop/welcome.py

Today, I have a newer version with some PoC, but in this versions I not able to receive data from Chromecast if it's different from my "captured payload".

I could offer a "dev dev" rev. of this file:

import socket
import ssl
import traceback
import binascii
import cast_channel_pb2 as pb2

CHROMECAST_IP = "192.168.1.1"  # TODO: change this with the main scanner
CHROMECAST_PORT = 8009  

from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import padding
from cryptography.hazmat.backends import default_backend
import os

class AuthContext:
    def __init__(self, nonce):
        self._nonce = nonce

    def nonce(self):
        return self._nonce
    
def create_auth_challenge_message(auth_context):
    auth_message = pb2.DeviceAuthMessage()

    # Create an AuthChallenge message
    challenge = pb2.AuthChallenge()
    challenge.sender_nonce = auth_context.nonce()  # Need to define nonce in auth_context
    challenge.hash_algorithm = pb2.SHA256

    auth_message.challenge.CopyFrom(challenge)

    # Create a CastMessage and fill it with the AuthChallenge
    message = pb2.CastMessage()
    message.protocol_version = message.CASTV2_1_0
    message.source_id = "sender-0"
    message.destination_id = "receiver-0"
    message.namespace = "urn:x-cast:com.google.cast.tp.deviceauth"
    message.payload_type = message.BINARY
    message.payload_binary = auth_message.SerializeToString()

    return message

# connect to chromecast
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as raw_s:
    # ssl context with the cipher that chromecast supports
    context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
    context.check_hostname = False
    context.verify_mode = ssl.CERT_NONE
    context.set_ciphers('AES256-GCM-SHA384')

    # creates a SSL socket
    conn = context.wrap_socket(raw_s, server_hostname=CHROMECAST_IP)

    try:
        # connect and handshake
        conn.connect((CHROMECAST_IP, CHROMECAST_PORT))
        print("SSL handshake exitoso")

        nonce = os.urandom(16)  # 16 bytes of randomness
        auth_context = AuthContext(nonce)

        message = create_auth_challenge_message(auth_context)

        #data = message.SerializeToString()
        data = b'\x00\x00\x00\\\x08\x00\x12\x08sender-0\x1a\nreceiver-0"(urn:x-cast:com.google.cast.tp.deviceauth(\x01:\x16\n\x14\x12\x10-.R\xbf\x1b\xc6\xc4 |\xc6s7\x0b$\xcc\xb0\x18\x01'
        conn.sendall(data)

        print("sent auth challenge message")

        response = conn.recv(1024)

        if response:  # if there is a response
            print("received auth challenge response")
            print(binascii.hexlify(response))

            print(response)

            # Process response data
            response_message = pb2.DeviceAuthMessage()
            response_message.ParseFromString(response)
            response_challenge = response_message.challenge

            # Load private key
            with open("key.pem", "rb") as key_file:
                private_key = serialization.load_pem_private_key(
                    key_file.read(),
                    password=None,
                    backend=default_backend()
                )

            # Sign the nonce
            signature = private_key.sign(
                response_challenge.sender_nonce,
                padding.PSS(
                    mgf=padding.MGF1(hashes.SHA256()),
                    salt_length=padding.PSS.MAX_LENGTH
                ),
                hashes.SHA256()
            )

            # Load the certificate
            with open("cert.pem", "rb") as cert_file:
                certificate = cert_file.read()

            # Set up the auth response
            auth_response = pb2.AuthResponse()
            auth_response.signature = signature
            auth_response.client_auth_certificate = certificate

            # Set up the device auth message
            device_auth_message = pb2.DeviceAuthMessage()
            device_auth_message.response.CopyFrom(auth_response)

            # Set up the cast message
            message = pb2.CastMessage()
            message.protocol_version = message.CASTV2_1_0
            message.source_id = "sender-0"
            message.destination_id = "receiver-0"
            message.namespace = "urn:x-cast:com.google.cast.tp.deviceauth"
            message.payload_type = message.BINARY
            message.payload_binary = device_auth_message.SerializeToString()

            data = message.SerializeToString()
            conn.sendall(data)

            print("sent auth response message")

        else:
            print("no answer, sorry")

    except Exception as e:
        print("Error: ", str(e))
        traceback.print_exc()

    finally:
        conn.close()

Other interesting project that has "auth" message working fine is:

https://gitlab.gnome.org/GNOME/gnome-network-displays

but for me in my laptop doesn't work (I don't know why).

Just it's an investigation for a fun summer time.

Greetings!

@forlayo
Copy link

forlayo commented Aug 28, 2023

@bitstuffing creating a chromecast client, which is what your code would work I think is the inverse point of what this project (node-castv2) is trying to solve.

This projects aims to act as a server, to receiver for example the screenshare from a Chrome browser. And the issue with authentication is that the callenge you are receiving from a Chrome browser needs to be signed with a certificate that comes from same CA or otherwise that Chrome browser is going to reject you.

In this same thread you could see examples of having this working modifying the chrome browser to not validate the signature but obviously is not what we want.

There are many applications out there with this authentication working (AirServer, Reflector4, etc), then or the CA is exposed somehow or there is a trick we aren't seeing.

@bitstuffing
Copy link

@bitstuffing creating a chromecast client, which is what your code would work I think is the inverse point of what this project (node-castv2) is trying to solve.

This projects aims to act as a server, to receiver for example the screenshare from a Chrome browser. And the issue with authentication is that the callenge you are receiving from a Chrome browser needs to be signed with a certificate that comes from same CA or otherwise that Chrome browser is going to reject you.

In this same thread you could see examples of having this working modifying the chrome browser to not validate the signature but obviously is not what we want.

There are many applications out there with this authentication working (AirServer, Reflector4, etc), then or the CA is exposed somehow or there is a trick we aren't seeing.

Sorry if you understand me badly. I wanted to expose a case of use, but just to clarify what I did in my repository, my simple project has reversed the DIAL protocol, and it's working, like a server, with VLC Android clients, because VLC don't verify the CA server identification.

The next steps were make "mirror" implementation. There are more branches than master for that "investigation".

I suspected, because currently I don't investigate it anymore, that some applications has the "paid" ability (because it uses the Google API) to "generate" a valid CA like Cromecast or Google Home devices (because apparently they renew his CA each 48 hours).

So, at this moment, if there is available some code to use a CA certification "method", I could reimplement it. But... the reality is there aren't open ways to do that (because Android clients verify the CA entity that signed the certification, and close/kill the connection).

I just tried it.

@viclinex
Copy link

I'm working on it by referring to https://github.com/EiNSTeiN-/chromecast-widevine-tools, but an error is occurring because chromecast_v1.6_content_shell.tgz could not be imported during the build process. I looked for several links to download chromecast_v1.6_content_shell.tgz, but since they are all old Google Drive addresses, I cannot download them due to a 404 error. (Try all download link about https://github.com/Akheon23/chromecast-mirrored-source.chromium).
Does anyone know the path to download chromecast_v1.6_content_shell.tgz?

@shuax
Copy link

shuax commented Sep 27, 2023

I'm working on it by referring to https://github.com/EiNSTeiN-/chromecast-widevine-tools, but an error is occurring because chromecast_v1.6_content_shell.tgz could not be imported during the build process. I looked for several links to download chromecast_v1.6_content_shell.tgz, but since they are all old Google Drive addresses, I cannot download them due to a 404 error. (Try all download link about https://github.com/Akheon23/chromecast-mirrored-source.chromium). Does anyone know the path to download chromecast_v1.6_content_shell.tgz?

You can try https://github.com/tristanpenman/chromecast-tools

@godvino
Copy link

godvino commented Sep 27, 2023

I'm working on it by referring to https://github.com/EiNSTeiN-/chromecast-widevine-tools, but an error is occurring because chromecast_v1.6_content_shell.tgz could not be imported during the build process. I looked for several links to download chromecast_v1.6_content_shell.tgz, but since they are all old Google Drive addresses, I cannot download them due to a 404 error. (Try all download link about https://github.com/Akheon23/chromecast-mirrored-source.chromium). Does anyone know the path to download chromecast_v1.6_content_shell.tgz?

https://drive.google.com/drive/folders/0B3j4zj2IQp7Md2luZ0dFYUJhbnc

(from https://github.com/EiNSTeiN-/chromecast-widevine-tools/issues/2)

@rgerganov
Copy link

I have reverse engineered the AirReceiver application and made an open-source receiver which works with Google Chrome: https://github.com/rgerganov/shanocast

You can find more info about how it works in this blog post

@schneid-l
Copy link

schneid-l commented Oct 1, 2023

I have reverse engineered the AirReceiver application and made an open-source receiver which works with Google Chrome: https://github.com/rgerganov/shanocast

You can find more info about how it works in this blog post

Thank you for this clarification, which finally makes it possible to understand (and validate the hypotheses) how these apps work.

@bitstuffing
Copy link

Sorry for that, but... @rgerganov, your work is not useful because it doesn't touch the "thread"
I was playing in the past with openscreen and locally cast_receiver & cast_sender, your work unify the compilation, but any kind of work out of the localhost or simply a client different of "chrome" & "chromium" will not work with your repository.

Anyway, thanks for the repo and the terms (because you're able to see how it works with docker and no compilation is required).

The main trouble is you're not able to get valid ssl socket, because you're not able to get a valid certification, because Android client verify where you are signed your local certification, and close the connection.

Anyway, people using "reverse-engineering-API" from working Android APKs could be great because there is using "custom" unknown process to get this certification, but... this is not the case.

Thanks in advance for your work, but we're in a dead end :'(

@milankragujevic
Copy link

@bitstuffing Did you read his post? Didn't he succeed in getting regular Chrome to cast to this custom receiver?

@bitstuffing
Copy link

@bitstuffing Did you read his post? Didn't he succeed in getting regular Chrome to cast to this custom receiver?

yes, I read, did you read the main title of this thread?

The investigation focus should be in the reverse-engineering of Android apks, which has an .so file, which skip the problem

That is just my worry, how to skip this problem and get a right certification from valid CA, to not be rejected from a normal client (each Android device and app, not VLC because it doesn't use Google API and doesn't verify the cert)

@milankragujevic
Copy link

milankragujevic commented Oct 1, 2023

He literally reverse engineered and Android apk and it's native library.

quote from the post:

Reversing AirReceiver
Needless to say, AirReciever is heavily obfuscated. A significant part of its implementation is in the native libAirReceiver.so library which has OpenSSL statically linked. I found an excellent tool called jnitrace which is based on frida and can be used to trace JNI calls.

Are you sure you read his post?

@bitstuffing
Copy link

bitstuffing commented Oct 1, 2023

He literally reverse engineered and Android apk and it's native library.

quote from the post:

Reversing AirReceiver
Needless to say, AirReciever is heavily obfuscated. A significant part of its implementation is in the native libAirReceiver.so library which has OpenSSL statically linked. I found an excellent tool called jnitrace which is based on frida and can be used to trace JNI calls.

Are you sure you read his post?

Well, no offense, now I know where is the misunderstand. I will you clarify the doubts, before answer quickly and write the evident.

Chrome / Chromium doesn't verify the socket ssl CA.

His work just allows use a server / client, based on openscreen (from chromium project code), and works with Chrome/Chromium (Google just put a client/server code to work with a proper autosigned certification, he just patched it to open the way to use it out of "localhost" environment, but not to any Android client, because he doesn't patch that, and the development, based on cast_channel.proto, just works with Chrome, not with Android, because neither Android cast_channel.proto is available, it's compiled in privated coded GAPPS)

If you're sure that you're in a right thesis, test the repository final .bin, or test the docker. You could check how it works. Next check chrome code, check the openscreen code (it's refered in his repository), check how it works (there is attached a client and a server), and launch it. You could check than Android (S.O. and any apk using google api) will not detect anything.

The thread must be focused in that, how we're obtaining a valid certification, to not be rejected by Android clients, because if all clients reject you (not chrome), this protocol is useless.

Thanks for your big comprehension

@milankragujevic
Copy link

I understand, thank you for the clarification.

@viclinex
Copy link

I succeeded in rooting using the 1st generation Chromecast, and confirmed that it was searchable in the list of things that can be cast by passing authResponse based on Android. (Actual casting has not been implemented yet). However, in IOS, it is not searched in the list that can be cast with the same code. Is there any difference in authResponse between Android and IOS?

One more thing, Chromecast 1st generation is not listed for casting on iOS and Android devices.
I would appreciate it if you could let me know if you know anything.

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

No branches or pull requests