diff --git a/config/config.example.yml b/config/config.example.yml index 8d3e6212..2f572643 100644 --- a/config/config.example.yml +++ b/config/config.example.yml @@ -568,6 +568,21 @@ hmac_key: "CHANGE_ME!!" ## #playlist_length_limit: 500 +## +## Encrypts query params 'ip' and 'pot' +## This is useful if you don't want to leak the IP address +## and PoToken used when fetching a Youtube video. +## +## Note: This will only work if the 'Proxy videos' preference is enabled +## on the Player preferences. Users that do not have 'Proxy videos' enabled +## will be able to see the IP address and PoToken used on videoplayback +## requests. +## +## Accepted values: true, false +## Default: false +## +#encrypt_query_params: false + ######################################### # # Default user preferences diff --git a/src/invidious/config.cr b/src/invidious/config.cr index 4d69854c..96b4f25c 100644 --- a/src/invidious/config.cr +++ b/src/invidious/config.cr @@ -183,6 +183,8 @@ class Config # Playlist length limit property playlist_length_limit : Int32 = 500 + property encrypt_query_params : Bool = false + def disabled?(option) case disabled = CONFIG.disable_proxy when Bool diff --git a/src/invidious/helpers/utils.cr b/src/invidious/helpers/utils.cr index 5637e533..1ac8ed09 100644 --- a/src/invidious/helpers/utils.cr +++ b/src/invidious/helpers/utils.cr @@ -1,3 +1,5 @@ +require "uri/params/serializable" + # See http://www.evanmiller.org/how-not-to-sort-by-average-rating.html def ci_lower_bound(pos, n) if n == 0 @@ -384,12 +386,17 @@ def parse_link_endpoint(endpoint : JSON::Any, text : String, video_id : String) return text end -def encrypt_ecb_without_salt(data, key) +def ecb_without_salt(data : String, key : String, mode : Symbol) cipher = OpenSSL::Cipher.new("aes-128-ecb") - cipher.encrypt + case mode + when :encrypt + cipher.encrypt + when :decrypt + cipher.decrypt + end cipher.key = key - io = IO::Memory.new + io = IO::Memory.new(data.bytesize + 16) io.write(cipher.update(data)) io.write(cipher.final) io.rewind @@ -399,6 +406,27 @@ end def invidious_companion_encrypt(data) timestamp = Time.utc.to_unix - encrypted_data = encrypt_ecb_without_salt("#{timestamp}|#{data}", CONFIG.invidious_companion_key) + encrypted_data = ecb_without_salt("#{timestamp}|#{data}", CONFIG.invidious_companion_key, :encrypt) return Base64.urlsafe_encode(encrypted_data) end + +struct PrivateParams + include URI::Params::Serializable + include JSON::Serializable + + property ip : String = "" + property pot : String = "" +end + +def encrypt_query_params(query_params : URI::Params) : String + private_params = PrivateParams.from_www_form(query_params.to_s).to_json + encrypted_data = ecb_without_salt(private_params, CONFIG.hmac_key, :encrypt) + return Base64.urlsafe_encode(encrypted_data) +end + +def decrypt_query_params(query_param_data : String) : PrivateParams + query_param_data = Base64.decode_string(query_param_data) + decrypted_data = ecb_without_salt(query_param_data, CONFIG.hmac_key, :decrypt) + private_params = PrivateParams.from_json(decrypted_data) + return private_params +end diff --git a/src/invidious/http_server/utils.cr b/src/invidious/http_server/utils.cr index 623a9177..d4b90bb2 100644 --- a/src/invidious/http_server/utils.cr +++ b/src/invidious/http_server/utils.cr @@ -9,6 +9,13 @@ module Invidious::HttpServer # Add some URL parameters params = url.query_params + if CONFIG.encrypt_query_params + encrypted_data = encrypt_query_params(params) + params["enc"] = "true" + params["data"] = encrypted_data + params.delete("ip") + params.delete("pot") + end params["host"] = url.host.not_nil! # Should never be nil, in theory params["region"] = region if !region.nil? url.query_params = params diff --git a/src/invidious/routes/video_playback.cr b/src/invidious/routes/video_playback.cr index 083087a9..bf4d798f 100644 --- a/src/invidious/routes/video_playback.cr +++ b/src/invidious/routes/video_playback.cr @@ -4,6 +4,14 @@ module Invidious::Routes::VideoPlayback locale = env.get("preferences").as(Preferences).locale query_params = env.params.query + if query_params["enc"]? == "true" + decrypted_data = decrypt_query_params(query_params["data"]) + query_params["ip"] = decrypted_data.ip + query_params["pot"] = decrypted_data.pot + query_params.delete("enc") + query_params.delete("data") + end + fvip = query_params["fvip"]? || "3" mns = query_params["mn"]?.try &.split(",") mns ||= [] of String