Esempio n. 1
0
 def _test_public_jwk(key):
     """
     Attempt to read in the key into a key object
     """
     keys = json.loads(key.decode())
     public_key_numbers = rsa.RSAPublicNumbers(
         long_from_bytes(keys['keys'][0]['e']),
         long_from_bytes(keys['keys'][0]['n']))
     return public_key_numbers.public_key(default_backend())
 def _test_ec_public_jwk(key):
     """
     Attempt to read in the key into a key object
     """
     keys = json.loads(key.decode('utf-8'))
     public_key_numbers = ec.EllipticCurvePublicNumbers(
         long_from_bytes(keys['keys'][0]['x']),
         long_from_bytes(keys['keys'][0]['y']), ec.SECP256R1())
     return public_key_numbers.public_key(default_backend())
Esempio n. 3
0
 def _test_private_jwk(key):
     """
     Attempt to read in the key into a private key object
     """
     keys = json.loads(key.decode())
     public_key_numbers = rsa.RSAPublicNumbers(
         long_from_bytes(keys['keys'][0]['e']),
         long_from_bytes(keys['keys'][0]['n']))
     private_key_numbers = rsa.RSAPrivateNumbers(
         long_from_bytes(keys['keys'][0]['p']),
         long_from_bytes(keys['keys'][0]['q']),
         long_from_bytes(keys['keys'][0]['d']),
         long_from_bytes(keys['keys'][0]['dp']),
         long_from_bytes(keys['keys'][0]['dq']),
         long_from_bytes(keys['keys'][0]['qi']), public_key_numbers)
     return private_key_numbers.private_key(default_backend())
Esempio n. 4
0
    def deserialize(serialized_token, require_key=False, insecure=False):
        """
        Given a serialized SciToken, load it into a SciTokens object.

        Verifies the claims pass the current set of validation scripts.
        
        :param str serialized_token: The serialized token.
        :param bool require_key: When True, require the key
        :param bool insecure: When True, allow insecure methods to verify the issuer,
                              including allowing "localhost" issuer (useful in testing).  Default=False
        """
        
        if require_key is not False:
            raise NotImplementedError()
        
        info = serialized_token.decode('utf8').split(".")

        if len(info) != 3 and len(info) != 4: # header, format, signature[, key]
            raise InvalidTokenFormat("Serialized token is not a readable format.")

        if (len(info) != 4) and require_key:
            raise MissingKeyException("No key present in serialized token")

        serialized_jwt = info[0] + "." + info[1] + "." + info[2]

        unverified_headers = jwt.get_unverified_header(serialized_jwt)
        unverified_payload = jwt.decode(serialized_jwt, verify=False)
        
        # Get the public key from the issuer
        keycache = scitokens.utils.keycache.KeyCache()
        issuer_public_key = keycache.getKeyInfo(unverified_payload['iss'], key_id=unverified_headers['kid'], insecure=insecure)
        
        claims = jwt.decode(serialized_token, issuer_public_key)
        # Do we have the private key?
        if len(info) == 4:
            to_return = SciToken(key = key)
        else:
            to_return = SciToken()
            
        to_return._verified_claims = claims
        to_return._serialized_token = serialized_token
        return to_return
        
        
        # Clean up all of the below

        key_decoded = base64.urlsafe_b64decode(key)
        jwk_dict = json.loads(key_decoded)
        # TODO: Full range of keytypes and curves from JWK RFC.
        if (jwk_dict['kty'] != 'EC') or (jwt_dict['crv'] != 'P-256'):
            raise UnsupportedKeyException("SciToken signed with an unsupported key type")
        elif 'd' not in jwk_dict:
            raise UnsupportedKeyException("SciToken key does not contain private number.")

        if 'pwt' in unverified_headers:
            pwt = unverified_headers['pwt']
            st = SciToken.clone()
            st.deserialize(pwt, require_key=False)
            headers = pwt.headers()
            if 'cwk' not in headers:
                raise InvalidParentToken("Parent token MUST specify a child JWK.")
            # Validate the key type / curve matches.  TODO: what other headers to check?
            if (jwk_dict['kty'] != headers['kty']) or (jwk_dict['crv'] != headers['crv']):
                if 'x' not in jwk_dict:
                    if 'x' in headers:
                        jwk_dict['x'] = headers['x']
                    else:
                        MissingPublicKeyException("JWK public key is missing 'x'")
                elif jwk_dict['x'] != headers['x']:
                    raise UnsupportedKeyException("Parent SciToken specifies an incompatible child JWK")
                if 'y' not in jwk_dict:
                    if 'y' in headers:
                        jwk_dict['y'] = headers['y']
                    else:
                        MissingPublicKeyException("JWK public key is missing 'y'")
                elif jwk_dict['y'] != headers['y']:
                    raise UnsupportedKeyException("Parent SciToken specifies an incompatible child JWK")
        # TODO: Handle non-chained case.
        elif 'x5u' in unverified_headers:
            raise NotImplementedError("Non-chained verification is not implemented.")
        else:
            raise UnableToValidate("No token validation method available.")

        public_key_numbers = ec.EllipticCurvePublicNumbers(
               long_from_bytes(jwk_dict['x']),
               long_from_bytes(jwk_dict['y']),
               ec.SECP256R1
           )
        private_key_numbers = ec.EllipticCurvePrivateNumbers(
           long_from_bytes(jwk_dict['d']),
           public_key_numbers
        )
        private_key = private_key_numbers.private_key(backends.default_backend())
        public_key  = public_key_numbers.public_key(backends.default_backend())

        # TODO: check that public and private key match?

        claims = jwt.decode(serialized_token, public_key, algorithm="EC256")
Esempio n. 5
0
    def _get_issuer_publickey(issuer, key_id=None, insecure=False):
        """
        :return: Tuple containing (public_key, cache_lifetime).  Cache_lifetime how
            the public key is valid
        """

        # Set the user agent so Cloudflare isn't mad at us
        headers = {'User-Agent': 'SciTokens/{}'.format(PKG_VERSION)}

        # Go to the issuer's website, and download the OAuth well known bits
        # https://tools.ietf.org/html/draft-ietf-oauth-discovery-07
        well_known_uri = ".well-known/openid-configuration"
        if not issuer.endswith("/"):
            issuer = issuer + "/"
        parsed_url = urlparse.urlparse(issuer)
        updated_url = urlparse.urljoin(parsed_url.path, well_known_uri)
        parsed_url_list = list(parsed_url)
        parsed_url_list[2] = updated_url
        meta_uri = urlparse.urlunparse(parsed_url_list)

        # Make sure the protocol is https
        if not insecure:
            parsed_url = urlparse.urlparse(meta_uri)
            if parsed_url.scheme != "https":
                raise NonHTTPSIssuer(
                    "Issuer is not over HTTPS.  RFC requires it to be over HTTPS"
                )
        response = request.urlopen(request.Request(meta_uri, headers=headers))
        data = json.loads(response.read().decode('utf-8'))

        # Get the keys URL from the openid-configuration
        jwks_uri = data['jwks_uri']

        # Now, get the keys
        if not insecure:
            parsed_url = urlparse.urlparse(jwks_uri)
            if parsed_url.scheme != "https":
                raise NonHTTPSIssuer("jwks_uri is not over HTTPS, insecure!")
        response = request.urlopen(request.Request(jwks_uri, headers=headers))

        # Get the cache data from the headers
        cache_timer = 0
        headers = response.info()
        if "Cache-Control" in headers:
            # Parse out the max-age, if it's there.
            if "max-age" in headers['Cache-Control']:
                match = re.search(r".*max-age=(\d+)", headers['Cache-Control'])
                if match:
                    cache_timer = int(match.group(1))
        # Minimum cache time of 10 minutes, no matter what the remote says
        cache_timer = max(cache_timer, config.get_int("cache_lifetime"))

        keys_data = json.loads(response.read().decode('utf-8'))
        # Loop through each key, looking for the right key id
        public_key = ""
        raw_key = None

        # If there is no kid in the header, then just take the first key?
        if key_id == None:
            if len(keys_data['keys']) != 1:
                raise NotImplementedError(
                    "No kid in header, but multiple keys in "
                    "response from certs server.  Don't know which key to use!"
                )
            else:
                raw_key = keys_data['keys'][0]
        else:
            # Find the right key
            for key in keys_data['keys']:
                if key['kid'] == key_id:
                    raw_key = key
                    break

        if raw_key == None:
            raise MissingKeyException(
                "Unable to find key at issuer {}".format(jwks_uri))

        if raw_key['kty'] == "RSA":
            public_key_numbers = rsa.RSAPublicNumbers(
                long_from_bytes(raw_key['e']), long_from_bytes(raw_key['n']))
            public_key = public_key_numbers.public_key(
                backends.default_backend())
        elif raw_key['kty'] == 'EC':
            public_key_numbers = ec.EllipticCurvePublicNumbers(
                long_from_bytes(raw_key['x']), long_from_bytes(raw_key['y']),
                ec.SECP256R1())
            public_key = public_key_numbers.public_key(
                backends.default_backend())
        else:
            raise UnsupportedKeyException(
                "SciToken signed with an unsupported key type")

        return public_key, cache_timer