plietar / librespot

Open Source Spotify client library
MIT License
1.14k stars 185 forks source link

Prevent MITM by authenticating Spotify servers #125

Open plietar opened 8 years ago

plietar commented 8 years ago

librespot doesn't have any way of authenticating Spotify's server, and will happily send the username and password to a MITM proxy. I don't know what the official client does about it.

plietar commented 8 years ago

The connection does not use TLS at all, so the certificate Spotify uses for their website is irrelevant. My best guess is https://github.com/plietar/librespot/blob/master/protocol/proto/keyexchange.proto, the gs_signature field in the LoginCryptoDiffieHellmanChallenge struct. We would need to find the public key and algorithm used to check that signature. Also relevant are the server_keys_known and server_signature_key fields in that same file.

s1lvester commented 8 years ago

The connection does not use TLS at all, so the certificate Spotify uses for their website is irrelevant.

openssl s_client -connect apresolve.spotify.com:443 presents a valid cert, but that's just *.spotify.com.

Did you eavesdrop on the connection and auth-negotiation of a certified device? Our lab just got an onkyo-avr that has built-in spotify-support - so if need be I could try to set up wireshark to have a look at the connections beeing made during zeroconf auth.

plietar commented 8 years ago

I'm not quite sure which part of the protocol you're referring to. There are basically 3 network connections :

Zeroconf authentication only runs on your local network, and is encrypted (not authenticated, but there's no way of doing that since you don't know the device anyway). Your password is never sent though, only a token.

Resolving the AP uses http, with optional TLS. librespot currently does not use https for reasons explained in #124. Encryption is pretty pointless, and like I explained there, there's no point authenticating that part if the connection to the AP in not authenticated, and similarily, authenticating it isn't necessary if we authenticate the next part.

Finally authenticating the connection to the AP is what I would like to implement, and is what this issue is about. This is a proprietary protocol, with it's own exotic encryption and authentication. It isn't based on SSL/TLS at all, and makes no use of certificates.

Encryption is already implemented (that was necessary to get it working), but authentication isn't, and I'm not 100% sure how it is done, but my comment just above describes my initial thoughts on it. The next steps would be disassemble the relevant part from Spotify's binaries, figure out the crypto algorithm, where the inputs come from, build a simple MITM and see how the client behaves, and implement it here.

timniederhausen commented 7 years ago

If you're still interested in this:

server_keys_known is a bitmask of all known signature keys (i.e. 1 << server_signature_key for all keys the client knows). So far there's only one key being used (key id 0).

gs_signature is the RSA (RSASSA-PKCS1-v1_5) signature of gs, signed using the key identified by server_signature_key. The public key (e, n) is hardcoded inside the Spotify client.

Here's a simplified version of the code I use to verify the signature:

static const unsigned char server_key_0_n[] = {
  0xac, 0xe0, 0x46, 0x0b, 0xff, 0xc2, 0x30, 0xaf, 0xf4, 0x6b, 0xfe, 0xc3,
  0xbf, 0xbf, 0x86, 0x3d, 0xa1, 0x91, 0xc6, 0xcc, 0x33, 0x6c, 0x93, 0xa1,
  0x4f, 0xb3, 0xb0, 0x16, 0x12, 0xac, 0xac, 0x6a, 0xf1, 0x80, 0xe7, 0xf6,
  0x14, 0xd9, 0x42, 0x9d, 0xbe, 0x2e, 0x34, 0x66, 0x43, 0xe3, 0x62, 0xd2,
  0x32, 0x7a, 0x1a, 0x0d, 0x92, 0x3b, 0xae, 0xdd, 0x14, 0x02, 0xb1, 0x81,
  0x55, 0x05, 0x61, 0x04, 0xd5, 0x2c, 0x96, 0xa4, 0x4c, 0x1e, 0xcc, 0x02,
  0x4a, 0xd4, 0xb2, 0x0c, 0x00, 0x1f, 0x17, 0xed, 0xc2, 0x2f, 0xc4, 0x35,
  0x21, 0xc8, 0xf0, 0xcb, 0xae, 0xd2, 0xad, 0xd7, 0x2b, 0x0f, 0x9d, 0xb3,
  0xc5, 0x32, 0x1a, 0x2a, 0xfe, 0x59, 0xf3, 0x5a, 0x0d, 0xac, 0x68, 0xf1,
  0xfa, 0x62, 0x1e, 0xfb, 0x2c, 0x8d, 0x0c, 0xb7, 0x39, 0x2d, 0x92, 0x47,
  0xe3, 0xd7, 0x35, 0x1a, 0x6d, 0xbd, 0x24, 0xc2, 0xae, 0x25, 0x5b, 0x88,
  0xff, 0xab, 0x73, 0x29, 0x8a, 0x0b, 0xcc, 0xcd, 0x0c, 0x58, 0x67, 0x31,
  0x89, 0xe8, 0xbd, 0x34, 0x80, 0x78, 0x4a, 0x5f, 0xc9, 0x6b, 0x89, 0x9d,
  0x95, 0x6b, 0xfc, 0x86, 0xd7, 0x4f, 0x33, 0xa6, 0x78, 0x17, 0x96, 0xc9,
  0xc3, 0x2d, 0x0d, 0x32, 0xa5, 0xab, 0xcd, 0x05, 0x27, 0xe2, 0xf7, 0x10,
  0xa3, 0x96, 0x13, 0xc4, 0x2f, 0x99, 0xc0, 0x27, 0xbf, 0xed, 0x04, 0x9c,
  0x3c, 0x27, 0x58, 0x04, 0xb6, 0xb2, 0x19, 0xf9, 0xc1, 0x2f, 0x02, 0xe9,
  0x48, 0x63, 0xec, 0xa1, 0xb6, 0x42, 0xa0, 0x9d, 0x48, 0x25, 0xf8, 0xb3,
  0x9d, 0xd0, 0xe8, 0x6a, 0xf9, 0x48, 0x4d, 0xa1, 0xc2, 0xba, 0x86, 0x30,
  0x42, 0xea, 0x9d, 0xb3, 0x08, 0x6c, 0x19, 0x0e, 0x48, 0xb3, 0x9d, 0x66,
  0xeb, 0x00, 0x06, 0xa2, 0x5a, 0xee, 0xa1, 0x1b, 0x13, 0x87, 0x3c, 0xd7,
  0x19, 0xe6, 0x55, 0xbd
};

bssl::UniquePtr<RSA> rsa(RSA_new());
rsa->n = BN_bin2bn(server_key_0_n, sizeof(server_key_0_n), nullptr);
rsa->e = BN_new();
BN_set_word(rsa->e, 65537);

std::uint8_t gs_hash[20];
SHA1(gs.data(), gs.size(), gs_hash);

if (1 != RSA_verify(NID_sha1, gs_hash, sizeof(gs_hash),
                    gs_sig.data(), gs_sig.size(), rsa.get())) {
  // failed
}