-
Notifications
You must be signed in to change notification settings - Fork 67
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Make registration token retrieval an auth provider
- Loading branch information
Showing
1 changed file
with
144 additions
and
116 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -408,42 +408,19 @@ def getUserId(self): | |
|
||
def getRegToken(self): | ||
""" | ||
Acquire a registration token. See :meth:`getMac256Hash` for the hash generation. | ||
Acquire a new registration token. | ||
Once successful, all tokens and expiry times are written to the token file (if specified on initialisation). | ||
""" | ||
self.verifyToken(self.Auth.SkypeToken) | ||
self.tokens.pop("reg", None) | ||
self.tokenExpiry.pop("reg", None) | ||
while "reg" not in self.tokens: | ||
secs = int(time.time()) | ||
hash = getMac256Hash(str(secs), "[email protected]", "Q1P7W2E4J9R8U3S5") | ||
headers = {"LockAndKey": "[email protected]; time={0}; lockAndKeyResponse={1}".format(secs, hash), | ||
"Authentication": "skypetoken=" + self.tokens["skype"]} | ||
endpointResp = self("POST", "{0}/users/ME/endpoints".format(self.msgsHost), codes=(200, 201, 404), | ||
headers=headers, json={"endpointFeatures": "Agent"}) | ||
regTokenHead = endpointResp.headers.get("Set-RegistrationToken") | ||
locHead = endpointResp.headers.get("Location") | ||
if regTokenHead: | ||
self.tokens["reg"] = re.search(r"(registrationToken=[a-z0-9\+/=]+)", regTokenHead, re.I).group(1) | ||
regExpiry = re.search(r"expires=(\d+)", regTokenHead).group(1) | ||
self.tokenExpiry["reg"] = datetime.fromtimestamp(int(regExpiry)) | ||
regEndMatch = re.search(r"endpointId=({[a-z0-9\-]+})", regTokenHead) | ||
if regEndMatch: | ||
self.endpoints["main"] = SkypeEndpoint(self, regEndMatch.group(1)) | ||
if locHead: | ||
locParts = re.search(r"(https://[^/]+/v1)/users/ME/endpoints(/(%7B[a-z0-9\-]+%7D))?", locHead).groups() | ||
if not locParts[0] == self.msgsHost: | ||
# Skype is requiring the use of a different hostname. | ||
self.msgsHost = locHead.rsplit("/", 4 if locParts[2] else 3)[0] | ||
if locParts[2]: | ||
self.endpoints["main"] = SkypeEndpoint(self, locParts[2].replace("%7B", "{").replace("%7D", "}")) | ||
if endpointResp.status_code == 200 and "main" not in self.endpoints and endpointResp.json(): | ||
# Use the most recent endpoint listed in the JSON response. | ||
self.endpoints["main"] = SkypeEndpoint(self, endpointResp.json()[0]["id"]) | ||
if "main" in self.endpoints: | ||
self.endpoints["main"].config() | ||
self.syncEndpoints() | ||
token, expiry, msgsHost, endpoint = SkypeRegistrationTokenProvider(self).auth(self.tokens["skype"]) | ||
self.tokens["reg"] = token | ||
self.tokenExpiry["reg"] = expiry | ||
self.msgsHost = msgsHost | ||
if endpoint: | ||
endpoint.config() | ||
self.endpoints["main"] = endpoint | ||
self.syncEndpoints() | ||
if self.tokenFile: | ||
self.writeToken() | ||
|
||
|
@@ -707,6 +684,141 @@ def getToken(self, t): | |
return (token, expiry) | ||
|
||
|
||
class SkypeRegistrationTokenProvider(SkypeAuthProvider): | ||
""" | ||
An authentication provider that handles the handshake for a registration token. | ||
""" | ||
|
||
def auth(self, skypeToken): | ||
""" | ||
Request a new registration token using a current Skype token. | ||
Args: | ||
skypeToken (str): existing Skype token | ||
Returns: | ||
(str, datetime.datetime, str, SkypeEndpoint) tuple: registration token, associated expiry if known, | ||
resulting endpoint hostname, endpoint if provided | ||
Raises: | ||
.SkypeAuthException: if the login request is rejected | ||
.SkypeApiException: if the login form can't be processed | ||
""" | ||
token = expiry = endpoint = None | ||
msgsHost = SkypeConnection.API_MSGSHOST | ||
while not token: | ||
secs = int(time.time()) | ||
hash = self.getMac256Hash(str(secs)) | ||
headers = {"LockAndKey": "[email protected]; time={0}; lockAndKeyResponse={1}".format(secs, hash), | ||
"Authentication": "skypetoken=" + skypeToken} | ||
endpointResp = self.conn("POST", "{0}/users/ME/endpoints".format(msgsHost), codes=(200, 201, 404), | ||
headers=headers, json={"endpointFeatures": "Agent"}) | ||
regTokenHead = endpointResp.headers.get("Set-RegistrationToken") | ||
locHead = endpointResp.headers.get("Location") | ||
if regTokenHead: | ||
token = re.search(r"(registrationToken=[a-z0-9\+/=]+)", regTokenHead, re.I).group(1) | ||
regExpiry = re.search(r"expires=(\d+)", regTokenHead).group(1) | ||
expiry = datetime.fromtimestamp(int(regExpiry)) | ||
regEndMatch = re.search(r"endpointId=({[a-z0-9\-]+})", regTokenHead) | ||
if regEndMatch: | ||
endpoint = SkypeEndpoint(self.conn, regEndMatch.group(1)) | ||
if locHead: | ||
locParts = re.search(r"(https://[^/]+/v1)/users/ME/endpoints(/(%7B[a-z0-9\-]+%7D))?", locHead).groups() | ||
if not locParts[0] == msgsHost: | ||
# Skype is requiring the use of a different hostname. | ||
msgsHost = locHead.rsplit("/", 4 if locParts[2] else 3)[0] | ||
if locParts[2]: | ||
endpoint = SkypeEndpoint(self.conn, locParts[2].replace("%7B", "{").replace("%7D", "}")) | ||
if not endpoint and endpointResp.status_code == 200 and endpointResp.json(): | ||
# Use the most recent endpoint listed in the JSON response. | ||
endpoint = SkypeEndpoint(self.conn, endpointResp.json()[0]["id"]) | ||
return token, expiry, msgsHost, endpoint | ||
|
||
@staticmethod | ||
def getMac256Hash(challenge, appId="[email protected]", key="Q1P7W2E4J9R8U3S5"): | ||
""" | ||
Generate the lock-and-key response, needed to acquire registration tokens. | ||
""" | ||
clearText = challenge + appId | ||
clearText += "0" * (8 - len(clearText) % 8) | ||
|
||
def int32ToHexString(n): | ||
hexChars = "0123456789abcdef" | ||
hexString = "" | ||
for i in range(4): | ||
hexString += hexChars[(n >> (i * 8 + 4)) & 15] | ||
hexString += hexChars[(n >> (i * 8)) & 15] | ||
return hexString | ||
|
||
def int64Xor(a, b): | ||
sA = "{0:b}".format(a) | ||
sB = "{0:b}".format(b) | ||
sC = "" | ||
sD = "" | ||
diff = abs(len(sA) - len(sB)) | ||
for i in range(diff): | ||
sD += "0" | ||
if len(sA) < len(sB): | ||
sD += sA | ||
sA = sD | ||
elif len(sB) < len(sA): | ||
sD += sB | ||
sB = sD | ||
for i in range(len(sA)): | ||
sC += "0" if sA[i] == sB[i] else "1" | ||
return int(sC, 2) | ||
|
||
def cS64(pdwData, pInHash): | ||
MODULUS = 2147483647 | ||
CS64_a = pInHash[0] & MODULUS | ||
CS64_b = pInHash[1] & MODULUS | ||
CS64_c = pInHash[2] & MODULUS | ||
CS64_d = pInHash[3] & MODULUS | ||
CS64_e = 242854337 | ||
pos = 0 | ||
qwDatum = 0 | ||
qwMAC = 0 | ||
qwSum = 0 | ||
for i in range(len(pdwData) // 2): | ||
qwDatum = int(pdwData[pos]) | ||
pos += 1 | ||
qwDatum *= CS64_e | ||
qwDatum = qwDatum % MODULUS | ||
qwMAC += qwDatum | ||
qwMAC *= CS64_a | ||
qwMAC += CS64_b | ||
qwMAC = qwMAC % MODULUS | ||
qwSum += qwMAC | ||
qwMAC += int(pdwData[pos]) | ||
pos += 1 | ||
qwMAC *= CS64_c | ||
qwMAC += CS64_d | ||
qwMAC = qwMAC % MODULUS | ||
qwSum += qwMAC | ||
qwMAC += CS64_b | ||
qwMAC = qwMAC % MODULUS | ||
qwSum += CS64_d | ||
qwSum = qwSum % MODULUS | ||
return [qwMAC, qwSum] | ||
|
||
cchClearText = len(clearText) // 4 | ||
pClearText = [] | ||
for i in range(cchClearText): | ||
pClearText = pClearText[:i] + [0] + pClearText[i:] | ||
for pos in range(4): | ||
pClearText[i] += ord(clearText[4 * i + pos]) * (256 ** pos) | ||
sha256Hash = [0, 0, 0, 0] | ||
hash = hashlib.sha256((challenge + key).encode("utf-8")).hexdigest().upper() | ||
for i in range(len(sha256Hash)): | ||
sha256Hash[i] = 0 | ||
for pos in range(4): | ||
dpos = 8 * i + pos * 2 | ||
sha256Hash[i] += int(hash[dpos:dpos + 2], 16) * (256 ** pos) | ||
macHash = cS64(pClearText, sha256Hash) | ||
macParts = [macHash[0], macHash[1], macHash[0], macHash[1]] | ||
return "".join(map(int32ToHexString, map(int64Xor, sha256Hash, macParts))) | ||
|
||
|
||
class SkypeEndpoint(SkypeObj): | ||
""" | ||
An endpoint represents a single point of presence within Skype. | ||
|
@@ -790,87 +902,3 @@ def getEvents(self): | |
self.subscribe() | ||
return self.conn("POST", "{0}/users/ME/endpoints/{1}/subscriptions/0/poll".format(self.conn.msgsHost, self.id), | ||
auth=SkypeConnection.Auth.RegToken).json().get("eventMessages", []) | ||
|
||
|
||
def getMac256Hash(challenge, appId, key): | ||
""" | ||
Method to generate the lock-and-key response, needed to acquire registration tokens. | ||
""" | ||
clearText = challenge + appId | ||
clearText += "0" * (8 - len(clearText) % 8) | ||
|
||
def int32ToHexString(n): | ||
hexChars = "0123456789abcdef" | ||
hexString = "" | ||
for i in range(4): | ||
hexString += hexChars[(n >> (i * 8 + 4)) & 15] | ||
hexString += hexChars[(n >> (i * 8)) & 15] | ||
return hexString | ||
|
||
def int64Xor(a, b): | ||
sA = "{0:b}".format(a) | ||
sB = "{0:b}".format(b) | ||
sC = "" | ||
sD = "" | ||
diff = abs(len(sA) - len(sB)) | ||
for i in range(diff): | ||
sD += "0" | ||
if len(sA) < len(sB): | ||
sD += sA | ||
sA = sD | ||
elif len(sB) < len(sA): | ||
sD += sB | ||
sB = sD | ||
for i in range(len(sA)): | ||
sC += "0" if sA[i] == sB[i] else "1" | ||
return int(sC, 2) | ||
|
||
def cS64(pdwData, pInHash): | ||
MODULUS = 2147483647 | ||
CS64_a = pInHash[0] & MODULUS | ||
CS64_b = pInHash[1] & MODULUS | ||
CS64_c = pInHash[2] & MODULUS | ||
CS64_d = pInHash[3] & MODULUS | ||
CS64_e = 242854337 | ||
pos = 0 | ||
qwDatum = 0 | ||
qwMAC = 0 | ||
qwSum = 0 | ||
for i in range(len(pdwData) // 2): | ||
qwDatum = int(pdwData[pos]) | ||
pos += 1 | ||
qwDatum *= CS64_e | ||
qwDatum = qwDatum % MODULUS | ||
qwMAC += qwDatum | ||
qwMAC *= CS64_a | ||
qwMAC += CS64_b | ||
qwMAC = qwMAC % MODULUS | ||
qwSum += qwMAC | ||
qwMAC += int(pdwData[pos]) | ||
pos += 1 | ||
qwMAC *= CS64_c | ||
qwMAC += CS64_d | ||
qwMAC = qwMAC % MODULUS | ||
qwSum += qwMAC | ||
qwMAC += CS64_b | ||
qwMAC = qwMAC % MODULUS | ||
qwSum += CS64_d | ||
qwSum = qwSum % MODULUS | ||
return [qwMAC, qwSum] | ||
|
||
cchClearText = len(clearText) // 4 | ||
pClearText = [] | ||
for i in range(cchClearText): | ||
pClearText = pClearText[:i] + [0] + pClearText[i:] | ||
for pos in range(4): | ||
pClearText[i] += ord(clearText[4 * i + pos]) * (256 ** pos) | ||
sha256Hash = [0, 0, 0, 0] | ||
hash = hashlib.sha256((challenge + key).encode("utf-8")).hexdigest().upper() | ||
for i in range(len(sha256Hash)): | ||
sha256Hash[i] = 0 | ||
for pos in range(4): | ||
dpos = 8 * i + pos * 2 | ||
sha256Hash[i] += int(hash[dpos:dpos + 2], 16) * (256 ** pos) | ||
macHash = cS64(pClearText, sha256Hash) | ||
macParts = [macHash[0], macHash[1], macHash[0], macHash[1]] | ||
return "".join(map(int32ToHexString, map(int64Xor, sha256Hash, macParts))) |