def test_strings_differ(self):
     # We can't really test the timing-invariance, but
     # we can test that we actually compute equality!
     self.assertTrue(strings_differ("", "a"))
     self.assertTrue(strings_differ("b", "a"))
     self.assertTrue(strings_differ("cc", "a"))
     self.assertTrue(strings_differ("cc", "aa"))
     self.assertFalse(strings_differ("", ""))
     self.assertFalse(strings_differ("D", "D"))
     self.assertFalse(strings_differ("EEE", "EEE"))
コード例 #2
0
 def test_strings_differ(self):
     # We can't really test the timing-invariance, but
     # we can test that we actually compute equality!
     self.assertTrue(strings_differ("", "a"))
     self.assertTrue(strings_differ("b", "a"))
     self.assertTrue(strings_differ("cc", "a"))
     self.assertTrue(strings_differ("cc", "aa"))
     self.assertFalse(strings_differ("", ""))
     self.assertFalse(strings_differ("D", "D"))
     self.assertFalse(strings_differ("EEE", "EEE"))
コード例 #3
0
 def _authenticate_oauth(self, request, identity):
     # We can only authenticate if it has a valid oauth token.
     token = identity.get("oauth_consumer_key")
     if not token:
         return None
     try:
         data, secret = self.token_manager.parse_token(token)
     except ValueError:
         msg = "invalid oauth_consumer_key"
         return self._respond_unauthorized(request, msg)
     # Check the two-legged OAuth signature.
     sigdata = get_signature_base_string(request, identity)
     expected_sig = get_signature(sigdata, secret)
     if strings_differ(identity["oauth_signature"], expected_sig):
         msg = "invalid oauth_signature"
         return self._respond_unauthorized(request, msg)
     # Cache the nonce to avoid re-use.
     # We do this *after* successul auth to avoid DOS attacks.
     nonce = identity["oauth_nonce"]
     timestamp = int(identity["oauth_timestamp"])
     self.nonce_cache.add(nonce, timestamp)
     # Update the identity with the data from the token.
     identity.update(data)
     return identity["repoze.who.userid"]
コード例 #4
0
class SignedTokenManager(object):
    """Class managing signed MAC auth tokens.

    This class provides a TokenManager implementation based on signed
    timestamped tokens.  It should provide a good balance between speed,
    memory-usage and security for most applications.

    The token contains an embedded (unencrypted!) userid and timestamp.
    The secret key is derived from the token using HKDF.

    The following options customize the use of this class:

       * secret:  string key used for signing the token;
                  if not specified then a random bytestring is used.

       * timeout: the time after which a token will expire.

       * hashmod:  the hashing module to use for various HMAC operations;
                   if not specified then hashlib.sha1 will be used.

       * applications: If the request contains a matchdict with "application"
                       in it, it should be one of the ones provided by this
                       option, which is a mapping. The request also needs
                       to define a "version" for the application.

                       An application is composed of a name and a version,
                       like : sync-2.0.

                       The mapping keys are the applicatin names, and the
                       values are lists of versions.

                       If not specified then an empty list will be used (and
                       all the applications will be considered valid)
    """
    def __init__(self,
                 secret=None,
                 timeout=None,
                 hashmod=None,
                 applications=None):
        # Default hashmod is SHA1
        if hashmod is None:
            hashmod = hashlib.sha1
        elif isinstance(hashmod, basestring):
            hashmod = getattr(hashlib, hashmod)
        digest_size = hashmod().digest_size
        # Default secret is a random bytestring.
        if secret is None:
            secret = os.urandom(digest_size)
        # Default timeout is five minutes.
        if timeout is None:
            timeout = 5 * 60
        self.secret = secret
        self._sig_secret = HKDF(self.secret,
                                salt=None,
                                info="SIGNING",
                                size=digest_size)
        self.timeout = timeout
        self.hashmod = hashmod
        self.hashmod_digest_size = digest_size

        # Default list of applications is empty
        self.applications = defaultdict(list)

        if applications is not None:
            for element in applications.split(','):
                element = element.strip()
                if element == '':
                    continue
                element = element.split('-')
                if len(element) != 2:
                    continue
                app, version = element
                self.applications[app].append(version)

    def make_token(self, request, data):
        """Generate a new token for the given userid.

        In this implementation the token is a JSON dump of the given data,
        including an expiry time and salt.  It has a HMAC signature appended
        and is b64-encoded for transmission.
        """
        self._validate_request(request, data)

        data = data.copy()
        data["salt"] = os.urandom(3).encode("hex")
        data["expires"] = time.time() + self.timeout
        payload = json.dumps(data)
        sig = self._get_signature(payload)
        assert len(sig) == self.hashmod_digest_size
        token = b64encode(payload + sig)
        return token, self._get_secret(token, data), None

    def parse_token(self, token):
        """Extract the data and secret key from the token, if valid.

        In this implementation the token is valid is if has a valid signature
        and if the embedded expiry time has not passed.
        """
        # Parse the payload and signature from the token.
        try:
            decoded_token = b64decode(token)
        except TypeError, e:
            raise ValueError(str(e))
        payload = decoded_token[:-self.hashmod_digest_size]
        sig = decoded_token[-self.hashmod_digest_size:]
        # Carefully check the signature.
        # This is a deliberately slow string-compare to avoid timing attacks.
        # Read the docstring of strings_differ for more details.
        expected_sig = self._get_signature(payload)
        if strings_differ(sig, expected_sig):
            raise ValueError("token has invalid signature")
        # Only decode *after* we've confirmed the signature.
        data = json.loads(payload)
        # Check whether it has expired.
        if data["expires"] <= time.time():
            raise ValueError("token has expired")
        # Find something we can use as repoze.who.userid.
        if "repoze.who.userid" not in data:
            for key in ("username", "userid", "uid", "email"):
                if key in data:
                    data["repoze.who.userid"] = data[key]
                    break
            else:
                raise ValueError("token contains no userid")
        # Re-generate the secret key and return.
        return data, self._get_secret(token, data)