def test_read_armored(): """ Make sure the modulus PGP message is read correctly ProtonMail's response is NOT a valid OpenPGP message! """ # ProtonMail's signed modulus message is NOT a valid PGP message modulus = auth.read_armored(AUTH_INFO['Modulus']) assert modulus == 'c5fkhlTPZsqb/ujEbCVdeUaCQUhDPeVgA5dN3q2jLoQcRrif5/LLp+BosQo4fiVVzZCnGHPGIvFsGf0n/bojbiizO+OfnnuHl+FYK0Qno83FJQ7GXebF4VghRHxEyuokdo4r9QozB9FBBm0M0vcElG0qyDG5p9HP+dXvd7OzcHChPmnJCweALSu7TfUQ4R1ADFVG909XxS9V4G3YEHRo1xGSyzK41YCCG3LYEhwM5I/ygcGLOxNFmGcq63afhVd7JAi2XD0YgzsPm5sM/aAVBd6RvWiKRV8vIDCOMlbax4ZitH4dABKXzO6Tdh9Je5fZcsuYlmN3wScUg6se7QYZsA==' # Convert to int m = auth.to_bn(base64.b64decode(modulus)) assert m.bit_length() in (2048, 2047) # TODO: Why is it -1 ?
def _default_ClientProof(self): """ Computes the ClientProof from the AuthInfo """ info = self.AuthInfo proofs = auth.generate_proofs(self.KeySize, b64d(auth.read_armored(info.Modulus)), self.HashedPassword, b64d(info.ServerEphemeral)) self.ClientEphemeral = proofs['client_ephemeral'] self.ExpectedServerProof = proofs['server_proof'] return proofs['client_proof']
def test_hashed_pwd(): """ Make sure the hashed password matches """ modulus = base64.b64decode(auth.read_armored(AUTH_INFO['Modulus'])) # Check salt at different stages salt = base64.b64decode(AUTH_INFO['Salt']) assert base64.b64encode(salt+'proton') == "Mc/pO/VHMVR/y3Byb3Rvbg==" assert '$2y$10$'+auth.bcrypt_encode_base64(salt+'proton') == "$2y$10$Ka9nM9TFKTP9w1/wZ1PtZe" # Make sure it's what we expect p = auth.hash_password(3, 'protonmail', salt, 'notused', modulus) assert base64.b64encode(p) == 'CFX3pYh45jr8hmODKF35o2XsCqj3ZgJT4goskAq59B4XNk6Ut5NYLl74SxOYkncQTTAG5exhgDTiIuKe6KKSh6ORJDBBXgT8WCsR9OgaD5FO9FNwRKIOxDBZqledH/lmFdzg2q56qCcLbAPY7cS0TVJax3khIGcd4eBKvoS5PA1ReoO1p7H5sZzj1xzGpuLTyG/LYruV6mXUuYTKVU+K/ZBTEktYmjDTgczJzkZsXKRD3Bx8g/5SIMkfpSndz/lRJR2rOmeJ5fSiv7esFQ8VIkSLxYOVLcb4y1U95q5luo7e79ZV4wm+IHIg1ywi5zF4SFe5i4cICI94Kzf9OxQntA=='
def test_client_proofs(): b64e, b64d = base64.b64encode, base64.b64decode modulus = b64d(auth.read_armored(AUTH_INFO['Modulus'])) salt = b64d(AUTH_INFO['Salt']) hashed_password = auth.hash_password(3, 'protonmail', salt, 'notused', modulus) server_ephemeral = b64d(AUTH_INFO['ServerEphemeral']) assert b64e(server_ephemeral) == "6dIgR0DzZxEUM/6+IJbetYfb/O7IIlhX2Q6kKvkBN0SL1cAWGvY35O6P/x5LDsnh1HmVtMS/LcBAZW5z1c1a4O0XFGbZ5PwSXHbN4VNVnmjxCioT3B2KCj/O1kbLXYsiTPs0zPnEORPguGHy13UeRmZJw4QdiPqIkzTWLdO9k5WuOCqW2WJeLOr5Kt2Rb/GADSnJca9MWo3CkXskEptd0i24QNjKpe47nBA6Ycz10bYPaGkiQ6Mi5eFE3CfEDARkDIPW910+rH3YCMtI1oqLxEGSF9FBaKqg7F8Q07Tf5LW+/8DjYI5AUReendBKyvpcYhVEgapeYYpQIiV/gyp6ew==" assert b64e(auth.from_bn(2)+modulus) == "AgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHOX5IZUz2bKm/7oxGwlXXlGgkFIQz3lYAOXTd6toy6EHEa4n+fyy6fgaLEKOH4lVc2QpxhzxiLxbBn9J/26I24oszvjn557h5fhWCtEJ6PNxSUOxl3mxeFYIUR8RMrqJHaOK/UKMwfRQQZtDNL3BJRtKsgxuafRz/nV73ezs3BwoT5pyQsHgC0ru031EOEdQAxVRvdPV8UvVeBt2BB0aNcRkssyuNWAghty2BIcDOSP8oHBizsTRZhnKut2n4VXeyQItlw9GIM7D5ubDP2gFQXekb1oikVfLyAwjjJW2seGYrR+HQASl8zuk3YfSXuX2XLLmJZjd8EnFIOrHu0GGbA=" multiplier = auth.hash(auth.from_bn(2)+modulus) assert b64e(multiplier) == "DpI3RvLj80Kbb95hfNyYyS96MHVVNaoQv2zZQLgAihr7X8B/K7qy+kI+CmqAruoODpNxovaDo65FhoO3+KC1b0G++O7Ej5o03UEZWsAQ9NJ5AB6qE0dPJnxKRWvcJzXrZ4tkjTEp0MfMUo0Bw+qOrelOp6hLaQaD67bZHoJmyenuv0ApYgdwy6Cux05Xmf47FgNcW0t1v4XNU9EyfzC9mqZkxhbQq9roVleDtTYZESkl1DrsVN95LCc6VncmeNX9bfA1O8w897GHQLP85kNZfkmFkJb2n0BYX6JqDLD/aCkT80G4P3jx8DFXQrGsvqN6VLO6x6FLf+Y2NyGNG3Q/wA==" multiplier = auth.from_bn(auth.mod_reduce(auth.to_bn(multiplier), auth.to_bn(modulus))) assert b64e(multiplier) == "m/pSv50UjXj/cPWcD7c7UOn37iwS+MSvu9WLYgpdW5beGQjgQ8fmUmLVWF9IMMW5QALKiYO9gL3YbIaP++WRARkLvQsl8R6tRWDALnzpUAW02g/ktWCJRCMpAe+XXUrG8fw4mCb2yPaKTCD18PKJGXwk33aSwTSz8eDpps6yWHlNgddfVgDwnXXzeVlGuOD7Ca4VZPsd+lV4c2NabrxUw5TS+uMX1llmO+WqohoNLZkyUnlgGcw0lL8PawCH8n2CSeh/3o4kdHZ4pRfw6aJDeWvz0i1sWuEoP3Lc2VkloaKwPsOaP2ZaJEPDy5FjQwyh4echMT7Uvb4itHVuLm0mEA==" g = 2 p = auth.to_bn(modulus) hpw = auth.to_bn(hashed_password) m = auth.to_bn(multiplier) # Hard code for test client_secret = b64d("xBm+aaHCvnIHupRLuNB33xZdmb1iS1uErD8wyGaRX5IGs1gBXTbINbhJ5qTfydiy1PgVIzsXwfHG7X0MRQ0Ote7mpUE/2J6xPQ9aF09QRbixAHpZ6x+fX1vo4XpXJgBM0YVtA7RF+ln/Dm5vBiW0NQlG6+JpGyKTeUXpQiypgTZOZw0Bonvo8z/uwAlKLr3c2q+hVClK7+WcbF5UdC3ijddAtV5UIrUei9iaL91k5Dq+hxw76jJAme8w9Qz8NPxc3t0JDdw4eyqUMNwP1PIzlbvtmn6BkbTGBFOdNfQYg6oYwh93ESAnVzL1p/nrgf89ch4kohjIGDgXqnQEvVcC7g==") client_ephemeral = auth.from_bn(pow(g, auth.to_bn(client_secret), p)) assert b64e(client_ephemeral) == "XTaTsmCVqIns4E8dmMNOHpGIvh0j6zXD2cFtRKpNaFNV1zgRyo1CtxwcZO1l3sSk6FqhdBpDZrafSoF4mJIlYhrI4pIxvKiTTVWH50pPDRP6pCA64sHsUgKazHGnM09u29FocjqcewN6Oe+ZIm+A8TnssDmGI80XSy1nhbbPcQCn+VeTGR7g2f3l/7AbEMKnzWg3sZ2NAuNSYQaG2Kd211ZuLgEPQzWxPS+8ePACoWYnS3bzDDi8JXEKM6aXIqcY1+WG6Y1mks7k+bLyt62YfuFnjUzygzmXx6cuaw6UHmuHnfoiObDU5qwM+/RPpvvOCQ7pqB3ufvhgpAm+YiSVdg==" scrambling_param = auth.hash(client_ephemeral+server_ephemeral) assert b64e(scrambling_param) == "rEtrIOzSKNdsdWgrNAShlFA7oj7O1nt6OaDcW0RZqeExwhT2oU72ieaRy+kMFWMXeGPWCMiaueunGX2eJX0a2mSPKkFQsgXF61xeREgrkDTs2UNKSTXMHOZ0xftbCbmp0+nA/P2nv678wpcDJoN8eWEbvBdl8j7sUOIZ9xZcTp934ff9jqCUNc0sRl8122lgIRKkzAKNyeCOsm5eD16NDpCDp1P3VOGPmhz7bGBit5SoT6NImVyi1Y49h/7T9WQ6IG+gFqihbOZjMNR7E8u2Cbj7loiMSmIPvixonPbTgQI/GkQVT3Nu8RkKMg6nvQTArz/YucS6JmRcqMMzcrq9tg==" subtracted = auth.to_bn(server_ephemeral) - auth.mod_reduce(pow(g, hpw, p)*m, p) assert b64e(auth.from_bn(subtracted)) == "lRcHTWAmTnndNWVYOOqTjN/BXB6JyQNcbHwMiteJ0/Iw50rGSoDUfc9u+ggUDqhlth7n/fAT7ZrVZdgp9VBPfyvsyYX2UsOOs3OfJ7+ZnZ1O9GCLjgOo7zaok88FIcKmnzXRdkB1DBlEdIRvrQjDAsSar/FCbugoCPC/04xBkUYkWAj05O+pdq3NFz6/6O3NF06bCw2k8PlZF3VXs/F3M/7WkY3MFqNDClNFPnJXP1f0MDP8aHbMQ6D4VypASydZepHcubrklb8kB/e+W3I5HWKqgAisMZgw+PQTb+UTFFZxRihtOD1rDuuifnHpNvZIGwVv54DLEcA2RUsg3tiNLg==" if subtracted < 0: subtracted += p assert b64e(auth.from_bn(subtracted)) == "lRcHTWAmTnndNWVYOOqTjN/BXB6JyQNcbHwMiteJ0/Iw50rGSoDUfc9u+ggUDqhlth7n/fAT7ZrVZdgp9VBPfyvsyYX2UsOOs3OfJ7+ZnZ1O9GCLjgOo7zaok88FIcKmnzXRdkB1DBlEdIRvrQjDAsSar/FCbugoCPC/04xBkUYkWAj05O+pdq3NFz6/6O3NF06bCw2k8PlZF3VXs/F3M/7WkY3MFqNDClNFPnJXP1f0MDP8aHbMQ6D4VypASydZepHcubrklb8kB/e+W3I5HWKqgAisMZgw+PQTb+UTFFZxRihtOD1rDuuifnHpNvZIGwVv54DLEcA2RUsg3tiNLg==" exponent = (auth.to_bn(scrambling_param) * hpw + auth.to_bn(client_secret))%(p-1) assert b64e(auth.from_bn(exponent)) == "sD+rPM/unfrYzwtTbm/sNfaEyaEzwEcTD1Kth6p7sT+0AEYZ8+A1vPrSQ7DjUqmyGlw2KCQk3mZ6iBgjkoPisHxLlZ+xgb/cEgloF8OkBNv/oOckk2wwNW1j2SNyja5H1NBzIQZ+yB2LJdcZVmdy27kbEMGHqYxJ40HAZTNPHhBmcZC9CD/pXQSdvAE2qFTi43xJF3fDWJARTtNDyLUxp4RG33ikYGsNFVML1dUMVhFYSIc914TSZtcITSC1uD1GMAMUMHRGEn8n2lgBXpRBQTNmAVSANnKeJoocIgV1FCGCN1n42YhcGS8HlD30GsKgQR6HqxkbqmSY/+BioZbrQw==" shared_session = auth.from_bn(pow(subtracted, exponent, p)) assert b64e(shared_session) == "MzmNFkOYBsWjwcOXii9mw7vA+Fbot6e581UI8V7QwbTa6gNkpT+p4mQA7CkhCTu9y9KdKvx/Z0xE/aW06R7CLzCjP9oggkEGH32Q5sfhMsdbaaZYUu81Mh9pMyzpvUGADpDBBHZmN1EkYn3By6hbpAmlyloR/uMA3Piev102j5PMnjKXB99Ady7s7HTfCh4IobV4hjPEfvhsFnem+TrNRFvGnPYJ9wJ0paYVgPH5/Fk18zNBRESsdJSReIN8ocEAdT0sRYfF+4C1g6YtRqxszNkCWlAn+hJunR6WFmYkKLMbNkVQLDPgkFek31h0THJNIdpXnL2hxqRsM6Hycli1ng==" # Don't randomly gen in the test auth.generate_random_bytes = lambda n: client_secret proofs = auth.generate_proofs(2048, modulus, hashed_password, server_ephemeral) assert b64e(proofs['client_ephemeral']) == "XTaTsmCVqIns4E8dmMNOHpGIvh0j6zXD2cFtRKpNaFNV1zgRyo1CtxwcZO1l3sSk6FqhdBpDZrafSoF4mJIlYhrI4pIxvKiTTVWH50pPDRP6pCA64sHsUgKazHGnM09u29FocjqcewN6Oe+ZIm+A8TnssDmGI80XSy1nhbbPcQCn+VeTGR7g2f3l/7AbEMKnzWg3sZ2NAuNSYQaG2Kd211ZuLgEPQzWxPS+8ePACoWYnS3bzDDi8JXEKM6aXIqcY1+WG6Y1mks7k+bLyt62YfuFnjUzygzmXx6cuaw6UHmuHnfoiObDU5qwM+/RPpvvOCQ7pqB3ufvhgpAm+YiSVdg==" assert b64e(proofs['client_proof']) == "hd3KIdpaF1//ZFchpVDsq9FpW5/XyXALcWfQiZplAmsr704RbbEUb2fa4O+icpPNXgcZeMk63bN9DFQRykoGzjTooiMPobJWHF5JNrHwBhSKNJnCqTCqIrP5vddzMJCE17+C4ITi4QMJOOFcX3SWP0Capm7U18dSQfHDtaTVtdpr+i3zUd+gU4KykQUtjhZ8j/9JAw24bECaQl1gLPuyULpfFaSZc5wzdKdW3hznYX9rQBJAaFSvqRcN06HhsyDxaswTunUbVAlMrTVMl0+L2HPQpAGp2c0e5KGoYDYwDP3+k4Qs/igQIngEu535m6hPTyvL4qqgKxlqErWucjiOAw==" assert b64e(proofs['server_proof']) == "PE6/te1IEulNJPsxRxo+g1RI0czOighWhZSHGHE1kojwysj6n/HE1YSkzV9fkWpKFaxWlQbunoeX2rXqbCPnBiUIDWVWc0ps5tB1uRTOuG249mtX+nXPpwTbN85E7NxERv1MBhaluQQ9CZkfa/B90Sjba4i7K3Ln+pYZ24ZXZnqipHixdDees6XS2Y02ZKYDrlNEJiSDXNUVRGlGatHY5halLWMnKe5jLnLfWZuaAvtFg+FOBLk9tqMf+8cohubjavDFv113a8bn+Sqg2R5rx18b3l/tvUCtEKpb8jDNis5D/xCQpHjVwl7+NhMHIWz6Dp0Fi8Mf9RmQRYe3TECc/w=="
def test_access_token(): auth.read_armored(AUTH['AccessToken'])
def _login(self, password, unlock=True): client = self.client host = self.host headers = self.headers.copy() headers.update({ 'Accept': 'application/vnd.protonmail.v1+json', 'Referer': host + "login", }) # Get the auth info r = yield requests.post(url=host + "api/auth/info", json=client.to_json('Username', 'ClientID'), headers=headers) if r.code != 200: try: data = yield r.json() except: data = {} raise LoginError("Unexpected info response: " "{} - {}".format(r.code, data)) data = yield r.json() # Parses the response and computes the proof resp = client.AuthInfo = responses.AuthInfoResponse.from_json(**data) if resp.Code != 1000: raise LoginError("Unexpected info code: {}".format(resp.Code)) if resp.TwoFactor: raise NotImplementedError("Two factor auth is not implemented") # Compute the hashed password client.HashedPassword = auth.hash_password( int(client.ApiVersion), password, b64d(resp.Salt), client.Username, b64d(auth.read_armored(resp.Modulus))) # Update headers headers.update({ 'Accept': '*/*', }) # Authenticate r = yield requests.post( url=host + "api/auth", json=client.to_json('Username', 'ClientID', 'ClientSecret', 'TwoFactorCode', SRPSession=client.AuthInfo.SRPSession, ClientProof=b64e(client.ClientProof), ClientEphemeral=b64e(client.ClientEphemeral)), headers=headers) if r.code != 200: try: data = yield r.json() except: data = {} raise LoginError("Unexpected auth response: " "{} - {}".format(r.code, data)) data = yield r.json() resp = client.Auth = responses.AuthResponse.from_json(**data) if resp.Code != 1000: del client.Auth raise LoginError("Unexpected auth code: {}".format(resp.Code)) if b64d(resp.ServerProof) != client.ExpectedServerProof: del client.Auth raise AuthError("Invalid server authentication") # Login success if not unlock: return_value(resp) # And unlock unlocked = yield self._unlock(password) return_value((resp, unlocked))