Exemple #1
0
def test_kspec():
    _ckey = pem_cert2rsa(CERT)
    _jwk = RSAKey(key=_ckey)
    _jwk.serialize()

    print _jwk
    assert _jwk.kty == "RSA"
    assert _jwk.e == JWK["keys"][0]["e"]
    assert _jwk.n == JWK["keys"][0]["n"]
Exemple #2
0
def test_extract_rsa_from_cert_2():
    _ckey = pem_cert2rsa(CERT)
    _jwk = RSAKey(key=_ckey)
    _jwk.serialize()

    print _jwk

    _n = base64_to_long(str(_jwk.n))

    assert _ckey.n == _n
Exemple #3
0
    def generate_jwks(self, mode):
        if "rotenc" in self.behavior_type:  # Rollover encryption keys
            rsa_key = RSAKey(kid="rotated_rsa_{}".format(time.time()),
                             use="enc").load_key(RSA.generate(2048))
            ec_key = ECKey(kid="rotated_ec_{}".format(time.time()),
                           use="enc").load_key(P256)

            keys = [rsa_key.serialize(private=True),
                    ec_key.serialize(private=True)]
            new_keys = {"keys": keys}
            #self.do_key_rollover(new_keys, "%d")

            signing_keys = [k.to_dict() for k in self.keyjar.get_signing_key()]
            new_keys["keys"].extend(signing_keys)
            return json.dumps(new_keys)
        elif "nokid1jwk" in self.behavior_type:
            alg = mode["sign_alg"]
            if not alg:
                alg = "RS256"
            keys = [k.to_dict() for kb in self.keyjar[""] for k in
                    list(kb.keys())]
            for key in keys:
                if key["use"] == "sig" and key["kty"].startswith(alg[:2]):
                    key.pop("kid", None)
                    jwk = dict(keys=[key])
                    return json.dumps(jwk)
            raise Exception(
                "Did not find sig {} key for nokid1jwk test ".format(alg))
        else:  # Return all keys
            keys = [k.to_dict() for kb in self.keyjar[""] for k in
                    list(kb.keys())]
            jwks = dict(keys=keys)
            return json.dumps(jwks)
Exemple #4
0
def test_serialize_rsa_priv_key():
    rsakey = RSAKey(key=import_rsa_key_from_file(full_path("rsa.key")))
    assert rsakey.d

    d_rsakey = rsakey.serialize(private=True)
    restored_key = RSAKey(**d_rsakey)

    assert rsa_eq(restored_key, rsakey)
Exemple #5
0
def test_serialize_rsa_priv_key():
    rsakey = RSAKey(key=import_rsa_key_from_file(full_path("rsa.key")))
    assert rsakey.d

    d_rsakey = rsakey.serialize(private=True)
    restored_key = RSAKey(**d_rsakey)

    assert rsa_eq(restored_key, rsakey)
Exemple #6
0
 def get_public_jwk(self):
     try:
         _rsakey = get_key_storage().public
     except FileNotFoundError:
         self.generate_keys()
         _rsakey = get_key_storage().public
     _rsakey = RSA.import_key(_rsakey)
     _rsajwk = RSAKey(kid="test", use="sig", alg="RS256", key=_rsakey)
     return _rsajwk.serialize(private=True)
Exemple #7
0
def test_kspec():
    _ckey = pem_cert2rsa(CERT)
    _key = RSAKey()
    _key.load_key(_ckey)

    print(_key)
    jwk = _key.serialize()
    assert jwk["kty"] == "RSA"
    assert jwk["e"] == JWK["keys"][0]["e"].encode("utf-8")
    assert jwk["n"] == JWK["keys"][0]["n"].encode("utf-8")
Exemple #8
0
def test_kspec():
    _ckey = pem_cert2rsa(CERT)
    _key = RSAKey()
    _key.load_key(_ckey)

    print(_key)
    jwk = _key.serialize()
    assert jwk["kty"] == "RSA"
    assert jwk["e"] == JWK["keys"][0]["e"]
    assert jwk["n"] == JWK["keys"][0]["n"]
Exemple #9
0
def create_and_store_rsa_key_pair(name="pyoidc", path=".", size=1024):
    key = RSA.generate(size)

    keyfile = os.path.join(path, name)

    f = open("%s.key" % keyfile, "w")
    f.write(key.exportKey("PEM"))
    f.close()
    f = open("%s.pub" % keyfile, "w")
    f.write(key.publickey().exportKey("PEM"))
    f.close()

    rsa_key = RSAKey(key=key)
    rsa_key.serialize()
    # This will create JWK from the public RSA key
    jwk_spec = json.dumps(rsa_key.to_dict(), "enc")
    f = open(keyfile + ".jwk", "w")
    f.write(str(jwk_spec))
    f.close()

    return key
Exemple #10
0
def create_and_store_rsa_key_pair(name="pyoidc", path=".", size=1024):
    key = RSA.generate(size)

    keyfile = os.path.join(path, name)

    f = open("%s.key" % keyfile, "w")
    f.write(key.exportKey("PEM"))
    f.close()
    f = open("%s.pub" % keyfile, "w")
    f.write(key.publickey().exportKey("PEM"))
    f.close()

    rsa_key = RSAKey(key=key)
    rsa_key.serialize()
    # This will create JWK from the public RSA key
    jwk_spec = json.dumps(rsa_key.to_dict(), "enc")
    f = open(keyfile + ".jwk", "w")
    f.write(str(jwk_spec))
    f.close()

    return key
Exemple #11
0
    def id_token_as_signed_jwt(self, session, loa="2", alg="", code=None,
                               access_token=None, user_info=None, auth_time=0,
                               exp=None, extra_claims=None, **kwargs):

        kwargs = {}

        if "rotsig" in self.behavior_type:  # Rollover signing keys
            if alg == "RS256":
                key = RSAKey(kid="rotated_rsa_{}".format(time.time()),
                             use="sig").load_key(RSA.generate(2048))
            else:  # alg == "ES256"
                key = ECKey(kid="rotated_ec_{}".format(time.time()),
                            use="sig").load_key(P256)

            new_keys = {"keys": [key.serialize(private=True)]}
            self.events.store("New signing keys", new_keys)
            self.do_key_rollover(new_keys, "%d")
            self.events.store("Rotated signing keys", '')

        if "nokid1jwks" in self.behavior_type:
            kwargs['keys'] = self.no_kid_keys()
            # found_key = None
            # for kb in self.keyjar.key_summary[""]:
            #     issuer_key = list(kb.keys())[0]
            #     if issuer_key.use == "sig" and \
            #             issuer_key.kty.startswith(
            #                 alg[:2]):
            #         issuer_key.kid = None
            #         found_key = key
            #         break
            # self.keyjar.key_summary[""] = [found_key]

        if "nokidmuljwks" in self.behavior_type:
            kwargs['keys'] = self.no_kid_keys()
            # for key in self.keyjar.key_summary[""]:
            #     for inner_key in list(key.keys()):
            #         inner_key.kid = None

        _jws = provider.Provider.id_token_as_signed_jwt(
            self, session, loa=loa, alg=alg, code=code,
            access_token=access_token, user_info=user_info,
            auth_time=auth_time,
            exp=exp, extra_claims=extra_claims, **kwargs)

        if "idts" in self.behavior_type:  # mess with the signature
            #
            p = _jws.split(".")
            p[2] = sort_string(p[2])
            _jws = ".".join(p)

        return _jws
Exemple #12
0
    def id_token_as_signed_jwt(self, session, loa="2", alg="", code=None,
                               access_token=None, user_info=None, auth_time=0,
                               exp=None, extra_claims=None, **kwargs):

        kwargs = {}

        if "rotsig" in self.behavior_type:  # Rollover signing keys
            if alg == "RS256":
                key = RSAKey(kid="rotated_rsa_{}".format(time.time()),
                             use="sig").load_key(RSA.generate(2048))
            else:  # alg == "ES256"
                key = ECKey(kid="rotated_ec_{}".format(time.time()),
                            use="sig").load_key(P256)

            new_keys = {"keys": [key.serialize(private=True)]}
            self.events.store("New signing keys", new_keys)
            self.do_key_rollover(new_keys, "%d")
            self.events.store("Rotated signing keys", '')

        if "nokid1jwks" in self.behavior_type:
            kwargs['keys'] = self.no_kid_keys()
            # found_key = None
            # for kb in self.keyjar.key_summary[""]:
            #     issuer_key = list(kb.keys())[0]
            #     if issuer_key.use == "sig" and \
            #             issuer_key.kty.startswith(
            #                 alg[:2]):
            #         issuer_key.kid = None
            #         found_key = key
            #         break
            # self.keyjar.key_summary[""] = [found_key]

        if "nokidmuljwks" in self.behavior_type:
            kwargs['keys'] = self.no_kid_keys()
            # for key in self.keyjar.key_summary[""]:
            #     for inner_key in list(key.keys()):
            #         inner_key.kid = None

        _jws = provider.Provider.id_token_as_signed_jwt(
            self, session, loa=loa, alg=alg, code=code,
            access_token=access_token, user_info=user_info,
            auth_time=auth_time,
            exp=exp, extra_claims=extra_claims, **kwargs)

        if "idts" in self.behavior_type:  # mess with the signature
            #
            p = _jws.split(".")
            p[2] = sort_string(p[2])
            _jws = ".".join(p)

        return _jws
Exemple #13
0
    def update(self, msg, state, key_size=0):
        """
        Used to 'update' the AccessToken Request

        :param msg:
        :param state: Used to map access token response to this request
        :param key_size:
        :return:
        """
        if not key_size:
            key_size = self.key_size

        key = RSAKey(key=RSA.generate(key_size))
        self.state2key[state] = key
        msg['key'] = json.dumps(key.serialize())
        return msg
Exemple #14
0
    def update(self, msg, state, key_size=0):
        """
        Use to 'update' the AccessToken Request.

        :param msg:
        :param state: Used to map access token response to this request
        :param key_size:
        :return:
        """
        if not key_size:
            key_size = self.key_size

        key = RSAKey(key=RSA.generate(key_size))
        self.state2key[state] = key
        msg["key"] = json.dumps(key.serialize())
        return msg
Exemple #15
0
    def generate_jwks(self, mode):
        if "rotenc" in self.behavior_type:  # Rollover encryption keys
            rsa_key = RSAKey(kid="rotated_rsa_{}".format(time.time()),
                             use="enc").load_key(RSA.generate(2048))
            ec_key = ECKey(kid="rotated_ec_{}".format(time.time()),
                           use="enc").load_key(P256)

            keys = [
                rsa_key.serialize(private=True),
                ec_key.serialize(private=True)
            ]
            new_keys = {"keys": keys}
            #self.do_key_rollover(new_keys, "%d")

            signing_keys = [k.to_dict() for k in self.keyjar.get_signing_key()]
            new_keys["keys"].extend(signing_keys)
            return json.dumps(new_keys)
        elif "nokid1jwk" in self.behavior_type:
            alg = mode["sign_alg"]
            if not alg:
                alg = "RS256"
            keys = [
                k.to_dict() for kb in self.keyjar[""] for k in list(kb.keys())
            ]
            for key in keys:
                if key["use"] == "sig" and key["kty"].startswith(alg[:2]):
                    key.pop("kid", None)
                    jwk = dict(keys=[key])
                    return json.dumps(jwk)
            raise Exception(
                "Did not find sig {} key for nokid1jwk test ".format(alg))
        else:  # Return all keys
            keys = [
                k.to_dict() for kb in self.keyjar[""] for k in list(kb.keys())
            ]
            jwks = dict(keys=keys)
            return json.dumps(jwks)
Exemple #16
0
    def authorization_endpoint(self, request="", cookie=None, **kwargs):
        if isinstance(request, dict):
            _req = request
        else:
            _req = {}
            for key, val in parse_qs(request).items():
                if len(val) == 1:
                    _req[key] = val[0]
                else:
                    _req[key] = val

        # self.events.store(EV_REQUEST, _req)

        try:
            _scope = _req["scope"]
        except KeyError:
            return error_response(
                error="incorrect_behavior",
                descr="No scope parameter"
            )
        else:
            # verify that openid is among the scopes
            _scopes = _scope.split(" ")
            if "openid" not in _scopes:
                return error_response(
                    error="incorrect_behavior",
                    descr="Scope does not contain 'openid'"
                )

        client_id = _req["client_id"]

        try:
            f = response_type_cmp(self.capabilities['response_types_supported'],
                                  _req['response_type'])
        except KeyError:
            pass
        else:
            if f is False:
                self.events.store(
                    EV_FAULT,
                    'Wrong response type: {}'.format(_req['response_type']))
                return error_response(error="incorrect_behavior",
                                      descr="Not supported response_type")

        _rtypes = _req['response_type'].split(' ')

        if 'id_token' in _rtypes:
            try:
                self._update_client_keys(client_id)
            except TestError:
                return error_response(error="incorrect_behavior",
                                      descr="No change in client keys")

        if isinstance(request, dict):
            request = urlencode(request)

        if "max_age" in _req and _req["max_age"] == "0" and "prompt" in _req and _req["prompt"] == "none":
            aresp = {
                "error": "login_required",
            }
            if "state" in _req:
                aresp['state'] = _req["state"]

            return self.response_mode(_req, False,
                                      aresp=aresp,
                                      redirect_uri=_req['redirect_uri'],
                                      headers={})
        else:
            _response = provider.Provider.authorization_endpoint(self, request,
                                                                 cookie,
                                                                 **kwargs)

        if "rotenc" in self.behavior_type:  # Rollover encryption keys
            rsa_key = RSAKey(kid="rotated_rsa_{}".format(time.time()),
                             use="enc").load_key(RSA.generate(2048))
            ec_key = ECKey(kid="rotated_ec_{}".format(time.time()),
                           use="enc").load_key(P256)

            keys = [rsa_key.serialize(private=True),
                    ec_key.serialize(private=True)]
            new_keys = {"keys": keys}
            self.events.store("New encryption keys", new_keys)
            self.do_key_rollover(new_keys, "%d")
            self.events.store("Rotated encryption keys", '')
            logger.info(
                'Rotated OP enc keys, new set: {}'.format(
                    key_summary(self.keyjar, '')))

        # This is just for logging purposes
        try:
            _resp = self.server.http_request(_req["request_uri"])
        except KeyError:
            pass
        except requests.ConnectionError as err:
            self.events.store(EV_EXCEPTION, err)
            err = unwrap_exception(err)
            return error_response(error="server_error", descr=err)
        else:
            if _resp.status_code == 200:
                self.events.store(EV_REQUEST,
                                  "Request from request_uri: {}".format(
                                      _resp.text))

        return _response
Exemple #17
0
class TestBearerTokenAuthentication:
    @pytest.fixture(autouse=True)
    def setup(self):
        httpretty.enable()

        self.key = RSAKey(kid='testkey').load(
            os.path.join(FIXTURE_ROOT, 'testkey.pem'))

        def jwks(_request, _uri, headers):  # noqa: E306
            ks = KEYS()
            ks.add(self.key.serialize())
            return 200, headers, ks.dump_jwks()

        httpretty.register_uri(httpretty.GET,
                               oidc_rp_settings.PROVIDER_JWKS_ENDPOINT,
                               status=200,
                               body=jwks)
        httpretty.register_uri(httpretty.POST,
                               oidc_rp_settings.PROVIDER_TOKEN_ENDPOINT,
                               body=json.dumps({
                                   'id_token': self.generate_jws(),
                                   'access_token': 'accesstoken',
                                   'refresh_token': 'refreshtoken',
                               }),
                               content_type='text/json')
        httpretty.register_uri(httpretty.GET,
                               oidc_rp_settings.PROVIDER_USERINFO_ENDPOINT,
                               body=json.dumps({
                                   'sub': '1234',
                                   'email': '*****@*****.**',
                               }),
                               content_type='text/json')

        yield

        httpretty.disable()

    def generate_jws(self, **kwargs):
        return JWS(self.generate_jws_dict(**kwargs), jwk=self.key,
                   alg='RS256').sign_compact()

    def generate_jws_dict(self, **kwargs):
        client_key = kwargs.get('client_key', oidc_rp_settings.CLIENT_ID)
        now_dt = dt.datetime.utcnow()
        expiration_dt = kwargs.get('expiration_dt',
                                   (now_dt + dt.timedelta(seconds=30)))
        issue_dt = kwargs.get('issue_dt', now_dt)
        nonce = kwargs.get('nonce', 'nonce')
        return {
            'iss': kwargs.get('iss', oidc_rp_settings.PROVIDER_ENDPOINT),
            'nonce': nonce,
            'aud': kwargs.get('aud', client_key),
            'azp': kwargs.get('azp', client_key),
            'exp': timegm(expiration_dt.utctimetuple()),
            'iat': timegm(issue_dt.utctimetuple()),
            'nbf': timegm(kwargs.get('nbf', now_dt).utctimetuple()),
            'sub': '1234',
        }

    def test_can_authenticate_a_new_user(self):
        rf = APIRequestFactory()
        request = rf.get('/', HTTP_AUTHORIZATION='Bearer accesstoken')
        SessionMiddleware().process_request(request)
        request.session.save()
        backend = BearerTokenAuthentication()
        user, _ = backend.authenticate(request)
        assert user.email == '*****@*****.**'
        assert user.oidc_user.sub == '1234'

    def test_can_authenticate_an_existing_user(self):
        rf = APIRequestFactory()
        request = rf.get('/', HTTP_AUTHORIZATION='Bearer accesstoken')
        SessionMiddleware().process_request(request)
        request.session.save()
        backend = BearerTokenAuthentication()
        user = get_user_model().objects.create_user('test', '*****@*****.**')
        OIDCUser.objects.create(user=user, sub='1234')
        user, _ = backend.authenticate(request)
        assert user.email == '*****@*****.**'
        assert user.oidc_user.sub == '1234'

    def test_cannot_authenticate_a_user_if_no_auth_header_is_present(self):
        rf = APIRequestFactory()
        request = rf.get('/')
        SessionMiddleware().process_request(request)
        request.session.save()
        backend = BearerTokenAuthentication()
        assert backend.authenticate(request) is None

    def test_cannot_authenticate_a_user_if_the_auth_header_is_not_a_bearer_authentication(
            self):
        rf = APIRequestFactory()
        request = rf.get('/', HTTP_AUTHORIZATION='DummyAuth accesstoken')
        SessionMiddleware().process_request(request)
        request.session.save()
        backend = BearerTokenAuthentication()
        assert backend.authenticate(request) is None

    def test_cannot_authenticate_a_user_if_the_auth_header_does_not_contain_the_access_token(
            self):
        rf = APIRequestFactory()
        request = rf.get('/', HTTP_AUTHORIZATION='Bearer')
        SessionMiddleware().process_request(request)
        request.session.save()
        backend = BearerTokenAuthentication()
        with pytest.raises(AuthenticationFailed):
            backend.authenticate(request)

    def test_cannot_authenticate_a_user_if_multiple_tokens_are_present_in_the_auth_header(
            self):
        rf = APIRequestFactory()
        request = rf.get('/', HTTP_AUTHORIZATION='Bearer token1 token2')
        SessionMiddleware().process_request(request)
        request.session.save()
        backend = BearerTokenAuthentication()
        with pytest.raises(AuthenticationFailed):
            backend.authenticate(request)

    def test_cannot_authenticate_a_user_if_the_userinfo_endpoint_raises_an_error(
            self):
        httpretty.register_uri(httpretty.GET,
                               oidc_rp_settings.PROVIDER_USERINFO_ENDPOINT,
                               body='Nop',
                               status=401)
        rf = APIRequestFactory()
        request = rf.get('/', HTTP_AUTHORIZATION='Bearer badtoken')
        SessionMiddleware().process_request(request)
        request.session.save()
        backend = BearerTokenAuthentication()
        with pytest.raises(AuthenticationFailed):
            backend.authenticate(request)
Exemple #18
0
    def authorization_endpoint(self, request="", cookie=None, **kwargs):
        if isinstance(request, dict):
            _req = request
        else:
            _req = {}
            for key, val in parse_qs(request).items():
                if len(val) == 1:
                    _req[key] = val[0]
                else:
                    _req[key] = val

        # self.events.store(EV_REQUEST, _req)

        try:
            _scope = _req["scope"]
        except KeyError:
            return error(
                error="incorrect_behavior",
                descr="No scope parameter"
            )
        else:
            # verify that openid is among the scopes
            _scopes = _scope.split(" ")
            if "openid" not in _scopes:
                return error(
                    error="incorrect_behavior",
                    descr="Scope does not contain 'openid'"
                )

        client_id = _req["client_id"]

        try:
            f = response_type_cmp(self.capabilities['response_types_supported'],
                                  _req['response_type'])
        except KeyError:
            pass
        else:
            if f is False:
                self.events.store(
                    EV_FAULT,
                    'Wrong response type: {}'.format(_req['response_type']))
                return error_response(error="incorrect_behavior",
                                      descr="Not supported response_type")

        _rtypes = _req['response_type'].split(' ')

        if 'id_token' in _rtypes:
            try:
                self._update_client_keys(client_id)
            except TestError:
                return error(error="incorrect_behavior",
                             descr="No change in client keys")

        if isinstance(request, dict):
            request = urlencode(request)

        _response = provider.Provider.authorization_endpoint(self, request,
                                                             cookie,
                                                             **kwargs)

        if "rotenc" in self.behavior_type:  # Rollover encryption keys
            rsa_key = RSAKey(kid="rotated_rsa_{}".format(time.time()),
                             use="enc").load_key(RSA.generate(2048))
            ec_key = ECKey(kid="rotated_ec_{}".format(time.time()),
                           use="enc").load_key(P256)

            keys = [rsa_key.serialize(private=True),
                    ec_key.serialize(private=True)]
            new_keys = {"keys": keys}
            self.events.store("New encryption keys", new_keys)
            self.do_key_rollover(new_keys, "%d")
            self.events.store("Rotated encryption keys", '')
            logger.info(
                'Rotated OP enc keys, new set: {}'.format(
                    key_summary(self.keyjar, '')))

        # This is just for logging purposes
        try:
            _resp = self.server.http_request(_req["request_uri"])
        except KeyError:
            pass
        except requests.ConnectionError as err:
            self.events.store(EV_EXCEPTION, err)
            err = unwrap_exception(err)
            return error_response(error="server_error", descr=err)
        else:
            if _resp.status_code == 200:
                self.events.store(EV_REQUEST,
                                  "Request from request_uri: {}".format(
                                      _resp.text))

        return _response
Exemple #19
0
    parser.add_argument("message", nargs="?", help="The message to encrypt")

    args = parser.parse_args()

    keys = {}
    if args.jwk_url:
        keys = load_jwks_from_url(args.jwk_url)
    elif args.jwk_file:
        keys = load_jwks(open(args.jwk_file).read())
    elif args.x509_url:
        keys = load_x509_cert(args.x509_url, {})
    elif args.x509_file:
        keys = [import_rsa_key_from_file(args.x509_file)]
    elif args.rsa_file:
        key = rsa_load(args.rsa_file)
        rsa_key = RSAKey(key=key)
        rsa_key.serialize()
        keys = [rsa_key]
    else:
        print("Needs encryption key")
        exit()

    if args.file:
        msg = open(args.file).read()
        msg = msg.strip("\n\r")
    else:
        msg = args.message

    jwe = JWE()
    print(jwe.decrypt(msg, keys))
Exemple #20
0
    keys = {}
    if args.jwk_url:
        keys = load_jwks_from_url(args.jwk_url, {})
    elif args.jwk_file:
        keys = load_jwks(open(args.jwk_file).read())
    elif args.x509_url:
        # load_x509_cert returns list of 2-tuples
        keys = [
            RSAKey(key=x) for x, y in load_x509_cert(lrequest, args.x509_url)
        ]
        for key in keys:
            key.serialize()
    elif args.x509_file:
        # import_rsa_key_from_file returns RSA key instance
        _key = RSAKey(key=import_rsa_key_from_file(args.x509_file))
        _key.serialize()
        keys = [_key]
    elif args.rsa_file:
        _key = RSAKey(key=rsa_load(args.rsa_file))
        _key.serialize()
        keys = [_key]
    else:
        print >> sys.stderr, "Needs encryption key"
        exit()

    if not args.enc or not args.alg:
        print >> sys.stderr, "There are no default encryption methods"
        exit()

    if args.enc not in SUPPORTED["enc"]:
        print >> sys.stderr, "Encryption method %s not supported" % args.enc
Exemple #21
0
    def authorization_endpoint(self, request="", cookie=None, **kwargs):
        _req = parse_qs(request)

        #self.events.store(EV_REQUEST, _req)

        try:
            _scope = _req["scope"]
        except KeyError:
            return self._error(
                error="incorrect_behavior",
                descr="No scope parameter"
            )
        else:
            # verify that openid is among the scopes
            _scopes = _scope[0].split(" ")
            if "openid" not in _scopes:
                return self._error(
                    error="incorrect_behavior",
                    descr="Scope does not contain 'openid'"
                )

        client_id = _req["client_id"][0]

        try:
            f = response_type_cmp(kwargs['test_cnf']['response_type'],
                                  _req['response_type'])
        except KeyError:
            pass
        else:
            if f is False:
                self.events.store(
                    EV_FAULT,
                    'Wrong response type: {}'.format(_req['response_type']))
                return self._error_response(error="incorrect_behavior",
                                            descr="Wrong response_type")

        _rtypes = []
        for rt in _req['response_type']:
            _rtypes.extend(rt.split(' '))

        if 'id_token' in _rtypes:
            try:
                self._update_client_keys(client_id)
            except TestError:
                return self._error(error="incorrect_behavior",
                                   descr="No change in client keys")

        _response = provider.Provider.authorization_endpoint(self, request,
                                                             cookie,
                                                             **kwargs)

        if "rotenc" in self.behavior_type:  # Rollover encryption keys
            rsa_key = RSAKey(kid="rotated_rsa_{}".format(time.time()),
                             use="enc").load_key(RSA.generate(2048))
            ec_key = ECKey(kid="rotated_ec_{}".format(time.time()),
                           use="enc").load_key(P256)

            keys = [rsa_key.serialize(private=True),
                    ec_key.serialize(private=True)]
            new_keys = {"keys": keys}
            self.events.store("New encryption keys", new_keys)
            self.do_key_rollover(new_keys, "%d")
            self.events.store("Rotated encryption keys", '')
            logger.info(
                'Rotated OP enc keys, new set: {}'.format(
                    key_summary(self.keyjar, '')))

        # This is just for logging purposes
        try:
            _resp = self.server.http_request(_req["request_uri"][0])
        except KeyError:
            pass
        else:
            if _resp.status_code == 200:
                self.events.store(EV_REQUEST,
                    "Request from request_uri: {}".format(_resp.text))

        return _response
def gen_public(private_key_jwk):
    rsak = RSAKey(**private_key_jwk)
    return(rsak.serialize())
Exemple #23
0
"""
import json
from oic.utils.keyio import create_and_store_rsa_key_pair
from oic.utils.keyio import build_keyjar
from jwkest.jwk import RSAKey
from jwkest.jwk import KEYS
from jwkest.jwk import keyitems2keyreps

# Will create 2 files on disc
# 'foo' will contain the private key
# 'foo.pub' will contain the public key
key = create_and_store_rsa_key_pair("foo", size=2048)

rsa = RSAKey().load_key(key)
# by default this will be the public part of the key
ser_rsa = rsa.serialize()

print("--- JWK (public) ----")
print(json.dumps(ser_rsa, sort_keys=True, indent=4, separators=(',', ': ')))
print()

# and this will give you the serialization of the private key
ser_rsa = rsa.serialize(private=True)

print("--- JWK (private) ----")
print(json.dumps(ser_rsa, sort_keys=True, indent=4, separators=(',', ': ')))
print()

# ============================================================================
# And now for the JWKS
Exemple #24
0
    keys = {}
    if args.jwk_url:
        keys = load_jwks_from_url(args.jwk_url, {})
    elif args.jwk_file:
        keys = load_jwks(open(args.jwk_file).read())
    elif args.x509_url:
        # load_x509_cert returns list of 2-tuples
        keys = [RSAKey(key=x) for x, y in load_x509_cert(lrequest,
                                                         args.x509_url)]
        for key in keys:
            key.serialize()
    elif args.x509_file:
        # import_rsa_key_from_file returns RSA key instance
        _key = RSAKey(key=import_rsa_key_from_file(args.x509_file))
        _key.serialize()
        keys = [_key]
    elif args.rsa_file:
        _key = RSAKey(key=rsa_load(args.rsa_file))
        _key.serialize()
        keys = [_key]
    else:
        print >> sys.stderr, "Needs encryption key"
        exit()

    if not args.enc or not args.alg:
        print >> sys.stderr, "There are no default encryption methods"
        exit()

    if args.enc not in SUPPORTED["enc"]:
        print >> sys.stderr, "Encryption method %s not supported" % args.enc
Exemple #25
0
class OpenIdConnectTestMixin(object):
    """
    Mixin to test OpenID Connect consumers. Inheriting classes should also
    inherit OAuth2Test.
    """
    client_key = 'a-key'
    client_secret = 'a-secret-key'
    issuer = None  # id_token issuer
    openid_config_body = None
    key = None

    def setUp(self):
        super(OpenIdConnectTestMixin, self).setUp()
        here = os.path.dirname(__file__)
        self.key = RSAKey(kid='testkey').load(
            os.path.join(here, '../testkey.pem'))
        HTTPretty.register_uri(HTTPretty.GET,
                               self.backend.OIDC_ENDPOINT +
                               '/.well-known/openid-configuration',
                               status=200,
                               body=self.openid_config_body)
        oidc_config = json.loads(self.openid_config_body)

        def jwks(_request, _uri, headers):
            ks = KEYS()
            ks.add(self.key.serialize())
            return 200, headers, ks.dump_jwks()

        HTTPretty.register_uri(HTTPretty.GET,
                               oidc_config.get('jwks_uri'),
                               status=200,
                               body=jwks)

    def extra_settings(self):
        settings = super(OpenIdConnectTestMixin, self).extra_settings()
        settings.update({
            'SOCIAL_AUTH_{0}_KEY'.format(self.name):
            self.client_key,
            'SOCIAL_AUTH_{0}_SECRET'.format(self.name):
            self.client_secret,
            'SOCIAL_AUTH_{0}_ID_TOKEN_DECRYPTION_KEY'.format(self.name):
            self.client_secret
        })
        return settings

    def access_token_body(self, request, _url, headers):
        """
        Get the nonce from the request parameters, add it to the id_token, and
        return the complete response.
        """
        nonce = self.backend.data['nonce'].encode('utf-8')
        body = self.prepare_access_token_body(nonce=nonce)
        return 200, headers, body

    def get_id_token(self,
                     client_key=None,
                     expiration_datetime=None,
                     issue_datetime=None,
                     nonce=None,
                     issuer=None):
        """
        Return the id_token to be added to the access token body.
        """
        return {
            'iss': issuer,
            'nonce': nonce,
            'aud': client_key,
            'azp': client_key,
            'exp': expiration_datetime,
            'iat': issue_datetime,
            'sub': '1234'
        }

    def prepare_access_token_body(self,
                                  client_key=None,
                                  tamper_message=False,
                                  expiration_datetime=None,
                                  issue_datetime=None,
                                  nonce=None,
                                  issuer=None):
        """
        Prepares a provider access token response. Arguments:

        client_id       -- (str) OAuth ID for the client that requested
                                 authentication.
        expiration_time -- (datetime) Date and time after which the response
                                      should be considered invalid.
        """

        body = {'access_token': 'foobar', 'token_type': 'bearer'}
        client_key = client_key or self.client_key
        now = datetime.datetime.utcnow()
        expiration_datetime = expiration_datetime or \
                              (now + datetime.timedelta(seconds=30))
        issue_datetime = issue_datetime or now
        nonce = nonce or 'a-nonce'
        issuer = issuer or self.issuer
        id_token = self.get_id_token(
            client_key, timegm(expiration_datetime.utctimetuple()),
            timegm(issue_datetime.utctimetuple()), nonce, issuer)

        body['id_token'] = JWS(id_token, jwk=self.key,
                               alg='RS256').sign_compact()
        if tamper_message:
            header, msg, sig = body['id_token'].split('.')
            id_token['sub'] = '1235'
            msg = b64encode_item(id_token).decode('utf-8')
            body['id_token'] = '.'.join([header, msg, sig])

        return json.dumps(body)

    def authtoken_raised(self, expected_message, **access_token_kwargs):
        self.access_token_body = self.prepare_access_token_body(
            **access_token_kwargs)
        with self.assertRaisesRegexp(AuthTokenError, expected_message):
            self.do_login()

    @unittest2.skipIf(NO_JWKEST, 'No Jwkest installed')
    def test_invalid_signature(self):
        self.authtoken_raised('Token error: Signature verification failed',
                              tamper_message=True)

    @unittest2.skipIf(NO_JWKEST, 'No Jwkest installed')
    def test_expired_signature(self):
        expiration_datetime = datetime.datetime.utcnow() - \
                              datetime.timedelta(seconds=30)
        self.authtoken_raised('Token error: Signature has expired',
                              expiration_datetime=expiration_datetime)

    @unittest2.skipIf(NO_JWKEST, 'No Jwkest installed')
    def test_invalid_issuer(self):
        self.authtoken_raised('Token error: Invalid issuer',
                              issuer='someone-else')

    @unittest2.skipIf(NO_JWKEST, 'No Jwkest installed')
    def test_invalid_audience(self):
        self.authtoken_raised('Token error: Invalid audience',
                              client_key='someone-else')

    @unittest2.skipIf(NO_JWKEST, 'No Jwkest installed')
    def test_invalid_issue_time(self):
        expiration_datetime = datetime.datetime.utcnow() - \
                              datetime.timedelta(hours=1)
        self.authtoken_raised('Token error: Incorrect id_token: iat',
                              issue_datetime=expiration_datetime)

    @unittest2.skipIf(NO_JWKEST, 'No Jwkest installed')
    def test_invalid_nonce(self):
        self.authtoken_raised('Token error: Incorrect id_token: nonce',
                              nonce='something-wrong')
Exemple #26
0
def setup_server_env(proxy_conf, conf_mod, key):
    """

    :param proxy_conf:
    :param conf_mod:
    :param key: RSA key
    :return:
    """
    global SERVER_ENV
    global logger
    #noinspection PyUnboundLocalVariable
    SERVER_ENV = dict([(k, v) for k, v in proxy_conf.__dict__.items()
                       if not k.startswith("__")])

    SERVER_ENV["sessions"] = {}

    SERVER_ENV["eptid"] = EptidShelve(proxy_conf.SECRET, proxy_conf.EPTID_DB)

    _idp = server.Server(conf_mod)

    if key:
        rsa_key = RSAKey(key=key)
        rsa_key.serialize()
    else:
        rsa_key = None

    args = {"metad": _idp.metadata, "dkeys": [rsa_key]}

    SERVER_ENV["consumer_info"] = utils.ConsumerInfo(proxy_conf.CONSUMER_INFO,
                                                     **args)
    SERVER_ENV["service"] = proxy_conf.SERVICE

    # add the service endpoints
    part = urlparse.urlparse(_idp.config.entityid)
    base = "%s://%s/" % (part.scheme, part.netloc)
    SERVER_ENV["SCHEME"] = part.scheme
    try:
        (host, port) = part.netloc.split(":")
        port = int(port)
    except ValueError:  # no port specification
        host = part.netloc
        if part.scheme == "http":
            port = 80
        elif part.scheme == "https":
            port = 443
        else:
            raise ValueError("Unsupported scheme")

    SERVER_ENV["HOST"] = host
    SERVER_ENV["PORT"] = port

    endpoints = {"single_sign_on_service": [], "single_logout_service": []}
    for key, _dict in proxy_conf.SERVICE.items():
        _sso = _dict["saml_endpoint"]
        endpoints["single_sign_on_service"].append("%s%s" % (base, _sso))
        endpoints["single_logout_service"].append(("%s%s/logout" % (base, _sso),
                                                   BINDING_HTTP_REDIRECT))

    _idp.config.setattr("idp", "endpoints", endpoints)

    SERVER_ENV["idp"] = _idp
    SERVER_ENV["template_lookup"] = LOOKUP
    SERVER_ENV["sid_generator"] = session_nr()
    SERVER_ENV["base_url"] = base
    SERVER_ENV["STATIC_DIR"] = proxy_conf.STATIC_DIR
    SERVER_ENV["METADATA_DIR"] = proxy_conf.METADATA_DIR
    SERVER_ENV["SIGN"] = proxy_conf.SIGN

    #print SERVER_ENV
    if proxy_conf.CACHE == "memory":
        SERVER_ENV["CACHE"] = cache.Cache(SERVER_ENV["SERVER_NAME"],
                                          SERVER_ENV["SECRET"])
    elif proxy_conf.CACHE.startswith("file:"):
        SERVER_ENV["CACHE"] = cache.Cache(SERVER_ENV["SERVER_NAME"],
                                          SERVER_ENV["SECRET"],
                                          filename=proxy_conf.CACHE[5:])

    logger = setup_logger(_idp.config)
    if proxy_conf.DEBUG:
        logger.setLevel(logging.DEBUG)

    logger.debug("SERVER_ENV: %s" % SERVER_ENV)
    return _idp
Exemple #27
0
    def id_token_as_signed_jwt(self,
                               session,
                               loa="2",
                               alg="",
                               code=None,
                               access_token=None,
                               user_info=None,
                               auth_time=0,
                               exp=None,
                               extra_claims=None,
                               **kwargs):

        kwargs = {}

        if "rotsig" in self.behavior_type:
            # Rollover signing keys
            if alg == "RS256":
                key = RSAKey(kid="rotated_rsa_{}".format(time.time()),
                             use="sig").load_key(RSA.generate(2048))
            else:  # alg == "ES256"
                key = ECKey(kid="rotated_ec_{}".format(time.time()),
                            use="sig").load_key(P256)

            new_keys = {"keys": [key.serialize(private=True)]}
            self.events.store("New signing keys", new_keys)
            self.do_key_rollover(new_keys, "%d")
            self.events.store("Rotated signing keys", '')

        if "nokid1jwks" in self.behavior_type:
            # Remove key ID from keys
            kwargs['keys'] = self.no_kid_keys()

        if "nokidmuljwks" in self.behavior_type:
            # Remove key ID from keys
            kwargs['keys'] = self.no_kid_keys()

        if "sid" in session:
            if extra_claims is None:
                extra_claims = {"sid": session["sid"]}
            else:
                extra_claims.update({"sid": session["sid"]})

        _jws = provider.Provider.id_token_as_signed_jwt(
            self,
            session,
            loa=loa,
            alg=alg,
            code=code,
            access_token=access_token,
            user_info=user_info,
            auth_time=auth_time,
            exp=exp,
            extra_claims=extra_claims,
            **kwargs)

        if "idts" in self.behavior_type:
            # Mess with the signature of the JWS
            p = _jws.split(".")
            p[2] = sort_string(p[2])
            _jws = ".".join(p)

        return _jws
def gen_jwk_keypair(kid):
    # Mint a new RSA key
    _rsakey = RSA.generate(2048)
    # Wrap it in a JWK class
    _rsajwk = RSAKey(kid=kid, use="sig", alg="RS256", key=_rsakey)
    return json.dumps(_rsajwk.serialize(private=True))
Exemple #29
0
def setup_server_env(proxy_conf, conf_mod, key):
    """

    :param proxy_conf:
    :param conf_mod:
    :param key: RSA key
    :return:
    """
    global SERVER_ENV
    global logger
    #noinspection PyUnboundLocalVariable
    SERVER_ENV = dict([(k, v) for k, v in proxy_conf.__dict__.items()
                       if not k.startswith("__")])

    SERVER_ENV["sessions"] = {}

    SERVER_ENV["eptid"] = EptidShelve(proxy_conf.SECRET, proxy_conf.EPTID_DB)

    _idp = server.Server(conf_mod)

    if key:
        rsa_key = RSAKey(key=key)
        rsa_key.serialize()
    else:
        rsa_key = None

    args = {"metad": _idp.metadata, "dkeys": [rsa_key]}

    SERVER_ENV["consumer_info"] = utils.ConsumerInfo(proxy_conf.CONSUMER_INFO,
                                                     **args)
    SERVER_ENV["service"] = proxy_conf.SERVICE

    # add the service endpoints
    part = urlparse.urlparse(_idp.config.entityid)
    base = "%s://%s/" % (part.scheme, part.netloc)
    SERVER_ENV["SCHEME"] = part.scheme
    try:
        (host, port) = part.netloc.split(":")
        port = int(port)
    except ValueError:  # no port specification
        host = part.netloc
        if part.scheme == "http":
            port = 80
        elif part.scheme == "https":
            port = 443
        else:
            raise ValueError("Unsupported scheme")

    SERVER_ENV["HOST"] = host
    SERVER_ENV["PORT"] = port

    endpoints = {"single_sign_on_service": [], "single_logout_service": []}
    for key, _dict in proxy_conf.SERVICE.items():
        _sso = _dict["saml_endpoint"]
        endpoints["single_sign_on_service"].append("%s%s" % (base, _sso))
        endpoints["single_logout_service"].append(
            ("%s%s/logout" % (base, _sso), BINDING_HTTP_REDIRECT))

    _idp.config.setattr("idp", "endpoints", endpoints)

    SERVER_ENV["idp"] = _idp
    SERVER_ENV["template_lookup"] = LOOKUP
    SERVER_ENV["sid_generator"] = session_nr()
    SERVER_ENV["base_url"] = base
    SERVER_ENV["STATIC_DIR"] = proxy_conf.STATIC_DIR
    SERVER_ENV["METADATA_DIR"] = proxy_conf.METADATA_DIR
    SERVER_ENV["SIGN"] = proxy_conf.SIGN

    #print SERVER_ENV
    if proxy_conf.CACHE == "memory":
        SERVER_ENV["CACHE"] = cache.Cache(SERVER_ENV["SERVER_NAME"],
                                          SERVER_ENV["SECRET"])
    elif proxy_conf.CACHE.startswith("file:"):
        SERVER_ENV["CACHE"] = cache.Cache(SERVER_ENV["SERVER_NAME"],
                                          SERVER_ENV["SECRET"],
                                          filename=proxy_conf.CACHE[5:])

    logger = setup_logger(_idp.config)
    if proxy_conf.DEBUG:
        logger.setLevel(logging.DEBUG)

    logger.debug("SERVER_ENV: %s" % SERVER_ENV)
    return _idp
Exemple #30
0
# - create a RSAKey object, and load the key with the load_key method
#
# A JWKS can instead be created as follow:
# - retrieve the rsa key
# - create a KEYS object and add the keys specifying the algorithm used for creation and the usage allowed for the key
#   (signature or encryption)
#
# A key jar can also be created with the method build_keyjar specifying a key_conf containing a list of keys to be
# created, with their type, name and usage (encryption of signature)

key = create_and_store_rsa_key_pair("foo", size=4096)
key2 = create_and_store_rsa_key_pair("foo2", size=4096)
rsa = RSAKey().load_key(key)

print "--- JWK ---"
print json.dumps(rsa.serialize(), sort_keys=True, indent=4, separators=(',', ': '))
print

########################################################

keys = KEYS()
keys.wrap_add(key, use="sig", kid="rsa1")
keys.wrap_add(key2, use="enc", kid="rsa1")

print "--- JWKS---"
print keys.dump_jwks()
print

########################################################

key_conf = [
Exemple #31
0
class TestValidateAndReturnIDTokenUtility:
    @pytest.fixture(autouse=True)
    def setup(self):
        httpretty.enable()

        self.key = RSAKey(kid='testkey').load(
            os.path.join(FIXTURE_ROOT, 'testkey.pem'))

        def jwks(_request, _uri, headers):  # noqa: E306
            ks = KEYS()
            ks.add(self.key.serialize())
            return 200, headers, ks.dump_jwks()

        httpretty.register_uri(httpretty.GET,
                               oidc_rp_settings.PROVIDER_JWKS_ENDPOINT,
                               status=200,
                               body=jwks)

        yield

        httpretty.disable()

    def generate_jws(self, **kwargs):
        return JWS(self.generate_jws_dict(**kwargs), jwk=self.key,
                   alg='RS256').sign_compact()

    def generate_jws_dict(self, **kwargs):
        client_key = kwargs.get('client_key', oidc_rp_settings.CLIENT_ID)
        now_dt = dt.datetime.utcnow()
        expiration_dt = kwargs.get('expiration_dt',
                                   (now_dt + dt.timedelta(seconds=30)))
        issue_dt = kwargs.get('issue_dt', now_dt)
        nonce = kwargs.get('nonce', 'nonce')
        return {
            'iss': kwargs.get('iss', oidc_rp_settings.PROVIDER_ENDPOINT),
            'nonce': nonce,
            'aud': kwargs.get('aud', client_key),
            'azp': kwargs.get('azp', client_key),
            'exp': timegm(expiration_dt.utctimetuple()),
            'iat': timegm(issue_dt.utctimetuple()),
            'nbf': timegm(kwargs.get('nbf', now_dt).utctimetuple()),
            'sub': '1234',
        }

    @unittest.mock.patch('oidc_rp.conf.settings.CLIENT_ID', 'client_id')
    @unittest.mock.patch('oidc_rp.conf.settings.CLIENT_SECRET',
                         'client_secret')
    def test_can_validate_and_decode_an_id_token(self):
        jws = self.generate_jws()
        id_token = validate_and_return_id_token(jws, 'nonce')
        assert id_token['iss'] == 'http://example.com/a/'
        assert id_token['nonce'] == 'nonce'
        assert id_token['aud'] == ['client_id']
        assert id_token['azp'] == 'client_id'
        assert id_token['sub'] == '1234'

    @unittest.mock.patch('oidc_rp.conf.settings.CLIENT_ID', 'client_id')
    @unittest.mock.patch('oidc_rp.conf.settings.CLIENT_SECRET',
                         'client_secret')
    @unittest.mock.patch('oidc_rp.conf.settings.USE_NONCE', False)
    def test_can_validate_and_decode_an_id_token_when_nonces_are_disabled(
            self):
        jws = self.generate_jws()
        id_token = validate_and_return_id_token(jws, validate_nonce=False)
        assert id_token['iss'] == 'http://example.com/a/'
        assert id_token['aud'] == ['client_id']
        assert id_token['azp'] == 'client_id'
        assert id_token['sub'] == '1234'

    @unittest.mock.patch('oidc_rp.conf.settings.CLIENT_ID', 'client_id')
    @unittest.mock.patch('oidc_rp.conf.settings.CLIENT_SECRET',
                         'client_secret')
    def test_cannot_validate_an_incorrect_id_token(self):
        id_token = validate_and_return_id_token('dummy')
        assert id_token is None

    @unittest.mock.patch('oidc_rp.conf.settings.CLIENT_ID', 'client_id')
    @unittest.mock.patch('oidc_rp.conf.settings.CLIENT_SECRET',
                         'client_secret')
    def test_cannot_validate_an_id_token_with_an_invalid_iss(self):
        jws = self.generate_jws(iss='http://dummy.com')
        with pytest.raises(SuspiciousOperation):
            validate_and_return_id_token(jws)

    @unittest.mock.patch('oidc_rp.conf.settings.CLIENT_ID', 'client_id')
    @unittest.mock.patch('oidc_rp.conf.settings.CLIENT_SECRET',
                         'client_secret')
    def test_cannot_validate_an_id_token_with_an_invalid_client_id(self):
        jws = self.generate_jws(client_key='unknown')
        with pytest.raises(SuspiciousOperation):
            validate_and_return_id_token(jws)

    @unittest.mock.patch('oidc_rp.conf.settings.CLIENT_ID', 'client_id')
    @unittest.mock.patch('oidc_rp.conf.settings.CLIENT_SECRET',
                         'client_secret')
    def test_cannot_validate_an_id_token_with_multiple_audiences_but_no_authorized_party(
            self):
        jws_dict = self.generate_jws_dict()
        jws_dict['aud'] = [oidc_rp_settings.CLIENT_ID, '2']
        jws_dict.pop('azp')
        jws = JWS(jws_dict, jwk=self.key, alg='RS256').sign_compact()
        with pytest.raises(SuspiciousOperation):
            validate_and_return_id_token(jws)

    @unittest.mock.patch('oidc_rp.conf.settings.CLIENT_ID', 'client_id')
    @unittest.mock.patch('oidc_rp.conf.settings.CLIENT_SECRET',
                         'client_secret')
    def test_cannot_validate_an_id_token_with_an_authorized_party(self):
        jws = self.generate_jws(azp='dummy')
        with pytest.raises(SuspiciousOperation):
            validate_and_return_id_token(jws)

    @unittest.mock.patch('oidc_rp.conf.settings.CLIENT_ID', 'client_id')
    @unittest.mock.patch('oidc_rp.conf.settings.CLIENT_SECRET',
                         'client_secret')
    def test_cannot_validate_an_id_token_whose_signature_has_expired(self):
        jws = self.generate_jws(expiration_dt=dt.datetime.utcnow() -
                                dt.timedelta(minutes=40))
        with pytest.raises(SuspiciousOperation):
            validate_and_return_id_token(jws)

    @unittest.mock.patch('oidc_rp.conf.settings.CLIENT_ID', 'client_id')
    @unittest.mock.patch('oidc_rp.conf.settings.CLIENT_SECRET',
                         'client_secret')
    def test_cannot_validate_an_id_token_with_an_invalid_nbf_value(self):
        jws = self.generate_jws(nbf=dt.datetime.utcnow() +
                                dt.timedelta(minutes=100))
        with pytest.raises(SuspiciousOperation):
            validate_and_return_id_token(jws)

    @unittest.mock.patch('oidc_rp.conf.settings.CLIENT_ID', 'client_id')
    @unittest.mock.patch('oidc_rp.conf.settings.CLIENT_SECRET',
                         'client_secret')
    def test_cannot_validate_an_id_token_which_is_too_aged(self):
        jws = self.generate_jws(issue_dt=dt.datetime.utcnow() -
                                dt.timedelta(minutes=100))
        with pytest.raises(SuspiciousOperation):
            validate_and_return_id_token(jws)

    @unittest.mock.patch('oidc_rp.conf.settings.CLIENT_ID', 'client_id')
    @unittest.mock.patch('oidc_rp.conf.settings.CLIENT_SECRET',
                         'client_secret')
    def test_cannot_validate_an_id_token_whose_nonce_is_not_valid(self):
        jws = self.generate_jws(nonce='invalidnonce')
        with pytest.raises(SuspiciousOperation):
            validate_and_return_id_token(jws)
Exemple #32
0
    def authorization_endpoint(self, request="", cookie=None, **kwargs):
        _req = parse_qs(request)

        #self.events.store(EV_REQUEST, _req)

        try:
            _scope = _req["scope"]
        except KeyError:
            return self._error(error="incorrect_behavior",
                               descr="No scope parameter")
        else:
            # verify that openid is among the scopes
            _scopes = _scope[0].split(" ")
            if "openid" not in _scopes:
                return self._error(error="incorrect_behavior",
                                   descr="Scope does not contain 'openid'")

        client_id = _req["client_id"][0]

        try:
            f = response_type_cmp(kwargs['test_cnf']['response_type'],
                                  _req['response_type'])
        except KeyError:
            pass
        else:
            if f is False:
                self.events.store(
                    EV_FAULT,
                    'Wrong response type: {}'.format(_req['response_type']))
                return self._error_response(error="incorrect_behavior",
                                            descr="Wrong response_type")

        _rtypes = []
        for rt in _req['response_type']:
            _rtypes.extend(rt.split(' '))

        if 'id_token' in _rtypes:
            try:
                self._update_client_keys(client_id)
            except TestError:
                return self._error(error="incorrect_behavior",
                                   descr="No change in client keys")

        _response = provider.Provider.authorization_endpoint(
            self, request, cookie, **kwargs)

        if "rotenc" in self.behavior_type:  # Rollover encryption keys
            rsa_key = RSAKey(kid="rotated_rsa_{}".format(time.time()),
                             use="enc").load_key(RSA.generate(2048))
            ec_key = ECKey(kid="rotated_ec_{}".format(time.time()),
                           use="enc").load_key(P256)

            keys = [
                rsa_key.serialize(private=True),
                ec_key.serialize(private=True)
            ]
            new_keys = {"keys": keys}
            self.events.store("New encryption keys", new_keys)
            self.do_key_rollover(new_keys, "%d")
            self.events.store("Rotated encryption keys", '')
            logger.info('Rotated OP enc keys, new set: {}'.format(
                key_summary(self.keyjar, '')))

        # This is just for logging purposes
        try:
            _resp = self.server.http_request(_req["request_uri"][0])
        except KeyError:
            pass
        else:
            if _resp.status_code == 200:
                self.events.store(
                    EV_REQUEST,
                    "Request from request_uri: {}".format(_resp.text))

        return _response
Exemple #33
0
 def serialize_rsa_key(key):
     kid = hashlib.md5(key.encode('utf-8')).hexdigest()
     key = RSAKey(kid=kid, key=RSA.importKey(key), use='sig', alg='RS512')
     return key.serialize(private=False)
class OpenIdConnectTestMixin(object):
    """
    Mixin to test OpenID Connect consumers. Inheriting classes should also
    inherit OAuth2Test.
    """
    client_key = 'a-key'
    client_secret = 'a-secret-key'
    issuer = None  # id_token issuer
    openid_config_body = None
    key = None

    def setUp(self):
        super(OpenIdConnectTestMixin, self).setUp()
        test_root = os.path.dirname(os.path.dirname(__file__))
        self.key = RSAKey(kid='testkey').load(os.path.join(test_root, 'testkey.pem'))
        HTTPretty.register_uri(HTTPretty.GET,
          self.backend.OIDC_ENDPOINT + '/.well-known/openid-configuration',
          status=200,
          body=self.openid_config_body
        )
        oidc_config = json.loads(self.openid_config_body)

        def jwks(_request, _uri, headers):
            ks = KEYS()
            ks.add(self.key.serialize())
            return 200, headers, ks.dump_jwks()

        HTTPretty.register_uri(HTTPretty.GET,
                               oidc_config.get('jwks_uri'),
                               status=200,
                               body=jwks)

    def extra_settings(self):
        settings = super(OpenIdConnectTestMixin, self).extra_settings()
        settings.update({
            'SOCIAL_AUTH_{0}_KEY'.format(self.name): self.client_key,
            'SOCIAL_AUTH_{0}_SECRET'.format(self.name): self.client_secret,
            'SOCIAL_AUTH_{0}_ID_TOKEN_DECRYPTION_KEY'.format(self.name):
                self.client_secret
        })
        return settings

    def access_token_body(self, request, _url, headers):
        """
        Get the nonce from the request parameters, add it to the id_token, and
        return the complete response.
        """
        nonce = self.backend.data['nonce'].encode('utf-8')
        body = self.prepare_access_token_body(nonce=nonce)
        return 200, headers, body

    def get_id_token(self, client_key=None, expiration_datetime=None,
                     issue_datetime=None, nonce=None, issuer=None):
        """
        Return the id_token to be added to the access token body.
        """
        return {
            'iss': issuer,
            'nonce': nonce,
            'aud': client_key,
            'azp': client_key,
            'exp': expiration_datetime,
            'iat': issue_datetime,
            'sub': '1234'
        }

    def prepare_access_token_body(self, client_key=None, tamper_message=False,
                                  expiration_datetime=None,
                                  issue_datetime=None, nonce=None,
                                  issuer=None):
        """
        Prepares a provider access token response. Arguments:

        client_id       -- (str) OAuth ID for the client that requested
                                 authentication.
        expiration_time -- (datetime) Date and time after which the response
                                      should be considered invalid.
        """

        body = {'access_token': 'foobar', 'token_type': 'bearer'}
        client_key = client_key or self.client_key
        now = datetime.datetime.utcnow()
        expiration_datetime = expiration_datetime or \
                              (now + datetime.timedelta(seconds=30))
        issue_datetime = issue_datetime or now
        nonce = nonce or 'a-nonce'
        issuer = issuer or self.issuer
        id_token = self.get_id_token(
            client_key, timegm(expiration_datetime.utctimetuple()),
            timegm(issue_datetime.utctimetuple()), nonce, issuer)

        body['id_token'] = JWS(id_token, jwk=self.key, alg='RS256').sign_compact()
        if tamper_message:
            header, msg, sig = body['id_token'].split('.')
            id_token['sub'] = '1235'
            msg = b64encode_item(id_token).decode('utf-8')
            body['id_token'] = '.'.join([header, msg, sig])

        return json.dumps(body)

    def authtoken_raised(self, expected_message, **access_token_kwargs):
        self.access_token_body = self.prepare_access_token_body(
            **access_token_kwargs
        )
        with self.assertRaisesRegexp(AuthTokenError, expected_message):
            self.do_login()

    @unittest2.skipIf(NO_JWKEST, 'No Jwkest installed')
    def test_invalid_signature(self):
        self.authtoken_raised(
            'Token error: Signature verification failed',
            tamper_message=True
        )

    @unittest2.skipIf(NO_JWKEST, 'No Jwkest installed')
    def test_expired_signature(self):
        expiration_datetime = datetime.datetime.utcnow() - \
                              datetime.timedelta(seconds=30)
        self.authtoken_raised('Token error: Signature has expired',
                              expiration_datetime=expiration_datetime)

    @unittest2.skipIf(NO_JWKEST, 'No Jwkest installed')
    def test_invalid_issuer(self):
        self.authtoken_raised('Token error: Invalid issuer',
                              issuer='someone-else')

    @unittest2.skipIf(NO_JWKEST, 'No Jwkest installed')
    def test_invalid_audience(self):
        self.authtoken_raised('Token error: Invalid audience',
                              client_key='someone-else')

    @unittest2.skipIf(NO_JWKEST, 'No Jwkest installed')
    def test_invalid_issue_time(self):
        expiration_datetime = datetime.datetime.utcnow() - \
                              datetime.timedelta(hours=1)
        self.authtoken_raised('Token error: Incorrect id_token: iat',
                              issue_datetime=expiration_datetime)

    @unittest2.skipIf(NO_JWKEST, 'No Jwkest installed')
    def test_invalid_nonce(self):
        self.authtoken_raised(
            'Token error: Incorrect id_token: nonce',
            nonce='something-wrong'
        )
Exemple #35
0
 def serialize_rsa_key(key):
     kid = hashlib.md5(key.encode('utf-8')).hexdigest()
     key = RSAKey(kid=kid, key=RSA.importKey(key), use='sig', alg='RS512')
     return key.serialize(private=False)
class TestOIDCAuthBackend:
    @pytest.fixture(autouse=True)
    def setup(self):
        httpretty.enable()

        self.key = RSAKey(kid='testkey').load(
            os.path.join(FIXTURE_ROOT, 'testkey.pem'))

        def jwks(_request, _uri, headers):  # noqa: E306
            ks = KEYS()
            ks.add(self.key.serialize())
            return 200, headers, ks.dump_jwks()

        httpretty.register_uri(httpretty.GET,
                               oidc_rp_settings.PROVIDER_JWKS_ENDPOINT,
                               status=200,
                               body=jwks)
        httpretty.register_uri(httpretty.POST,
                               oidc_rp_settings.PROVIDER_TOKEN_ENDPOINT,
                               body=json.dumps({
                                   'id_token': self.generate_jws(),
                                   'access_token': 'accesstoken',
                                   'refresh_token': 'refreshtoken',
                               }),
                               content_type='text/json')
        httpretty.register_uri(httpretty.GET,
                               oidc_rp_settings.PROVIDER_USERINFO_ENDPOINT,
                               body=json.dumps({
                                   'sub': '1234',
                                   'email': '*****@*****.**',
                               }),
                               content_type='text/json')

        yield

        httpretty.disable()

    def generate_jws(self, **kwargs):
        return JWS(self.generate_jws_dict(**kwargs), jwk=self.key,
                   alg='RS256').sign_compact()

    def generate_jws_dict(self, **kwargs):
        client_key = kwargs.get('client_key', oidc_rp_settings.CLIENT_ID)
        now_dt = dt.datetime.utcnow()
        expiration_dt = kwargs.get('expiration_dt',
                                   (now_dt + dt.timedelta(seconds=30)))
        issue_dt = kwargs.get('issue_dt', now_dt)
        nonce = kwargs.get('nonce', 'nonce')
        ret = kwargs
        ret.update({
            'iss': kwargs.get('iss', oidc_rp_settings.PROVIDER_ENDPOINT),
            'nonce': nonce,
            'aud': kwargs.get('aud', client_key),
            'azp': kwargs.get('azp', client_key),
            'exp': timegm(expiration_dt.utctimetuple()),
            'iat': timegm(issue_dt.utctimetuple()),
            'nbf': timegm(kwargs.get('nbf', now_dt).utctimetuple()),
            'sub': '1234',
        })
        return ret

    def test_can_authenticate_a_new_user(self, rf):
        request = rf.get('/oidc/cb/', {
            'state': 'state',
            'code': 'authcode',
        })
        SessionMiddleware().process_request(request)
        request.session.save()
        backend = OIDCAuthBackend()
        user = backend.authenticate(request, 'nonce')
        assert user.email == '*****@*****.**'
        assert user.oidc_user.sub == '1234'

    def test_can_authenticate_an_existing_user(self, rf):
        request = rf.get('/oidc/cb/', {
            'state': 'state',
            'code': 'authcode',
        })
        SessionMiddleware().process_request(request)
        request.session.save()
        backend = OIDCAuthBackend()
        user = get_user_model().objects.create_user('test', '*****@*****.**')
        OIDCUser.objects.create(user=user, sub='1234')
        user = backend.authenticate(request, 'nonce')
        assert user.email == '*****@*****.**'
        assert user.oidc_user.sub == '1234'

    def test_can_authenticate_a_new_user_even_if_no_email_is_in_userinfo_data(
            self, rf):
        httpretty.register_uri(
            httpretty.GET,
            oidc_rp_settings.PROVIDER_USERINFO_ENDPOINT,
            body=json.dumps({
                'sub': '1234',
            }),
            content_type='text/json',
        )
        request = rf.get('/oidc/cb/', {
            'state': 'state',
            'code': 'authcode',
        })
        SessionMiddleware().process_request(request)
        request.session.save()
        backend = OIDCAuthBackend()
        user = backend.authenticate(request, 'nonce')
        assert not user.email
        assert user.oidc_user.sub == '1234'

    def test_cannot_authenticate_a_user_if_the_nonce_is_not_provided_and_if_it_is_mandatory(
            self, rf):
        request = rf.get('/oidc/cb/', {
            'code': 'authcode',
        })
        SessionMiddleware().process_request(request)
        request.session.save()
        backend = OIDCAuthBackend()
        assert backend.authenticate(None, request) is None

    def test_cannot_authenticate_a_user_if_the_request_object_is_not_provided(
            self, rf):
        request = rf.get('/oidc/cb/', {
            'code': 'authcode',
        })
        SessionMiddleware().process_request(request)
        request.session.save()
        backend = OIDCAuthBackend()
        assert backend.authenticate('nonce', None) is None

    def test_cannot_authenticate_a_user_if_the_state_is_not_present_in_the_request_parameters(
            self, rf):
        request = rf.get('/oidc/cb/', {
            'code': 'authcode',
        })
        SessionMiddleware().process_request(request)
        request.session.save()
        backend = OIDCAuthBackend()
        with pytest.raises(SuspiciousOperation):
            backend.authenticate(request, 'nonce')

    def test_cannot_authenticate_a_user_if_the_code_is_not_present_in_the_request_parameters(
            self, rf):
        request = rf.get('/oidc/cb/', {
            'state': 'state',
        })
        SessionMiddleware().process_request(request)
        request.session.save()
        backend = OIDCAuthBackend()
        with pytest.raises(SuspiciousOperation):
            backend.authenticate(request, 'nonce')

    def test_cannot_authenticate_a_user_if_the_id_token_validation_shows_a_suspicious_operation(
            self, rf):
        request = rf.get('/oidc/cb/', {
            'state': 'state',
            'code': 'authcode',
        })
        SessionMiddleware().process_request(request)
        request.session.save()
        backend = OIDCAuthBackend()
        with pytest.raises(SuspiciousOperation):
            backend.authenticate(request, 'badnonce')

    def test_cannot_authenticate_a_user_if_the_id_token_validation_fails(
            self, rf):
        httpretty.register_uri(httpretty.POST,
                               oidc_rp_settings.PROVIDER_TOKEN_ENDPOINT,
                               body=json.dumps({
                                   'id_token': 'badidtoken',
                                   'access_token': 'accesstoken',
                                   'refresh_token': 'refreshtoken',
                               }),
                               content_type='text/json')
        request = rf.get('/oidc/cb/', {
            'state': 'state',
            'code': 'authcode',
        })
        SessionMiddleware().process_request(request)
        request.session.save()
        backend = OIDCAuthBackend()
        assert backend.authenticate(request, 'nonce') is None

    @unittest.mock.patch('oidc_rp.conf.settings.USER_DETAILS_HANDLER',
                         'tests.unit.test_backends.set_users_as_staff_members')
    def test_can_authenticate_a_new_user_and_update_its_details_with_a_specific_handler(
            self, rf):
        request = rf.get('/oidc/cb/', {
            'state': 'state',
            'code': 'authcode',
        })
        SessionMiddleware().process_request(request)
        request.session.save()
        backend = OIDCAuthBackend()
        user = backend.authenticate(request, 'nonce')
        assert user.email == '*****@*****.**'
        assert user.oidc_user.sub == '1234'
        assert user.is_staff

    @unittest.mock.patch('oidc_rp.conf.settings.ID_TOKEN_INCLUDE_USERINFO',
                         True)
    def test_can_process_userinfo_included_in_the_id_token_instead_of_calling_the_userinfo_endpoint(
            self, rf):
        httpretty.register_uri(
            httpretty.POST,
            oidc_rp_settings.PROVIDER_TOKEN_ENDPOINT,
            body=json.dumps({
                'id_token':
                self.generate_jws(email='*****@*****.**'),
                'access_token':
                'accesstoken',
                'refresh_token':
                'refreshtoken',
            }),
            content_type='text/json')
        request = rf.get('/oidc/cb/', {
            'state': 'state',
            'code': 'authcode',
        })
        SessionMiddleware().process_request(request)
        request.session.save()
        backend = OIDCAuthBackend()
        user = backend.authenticate(request, 'nonce')
        assert user.email == '*****@*****.**'
        assert user.oidc_user.sub == '1234'

    def test_oidc_user_created_signal_is_sent_during_new_user_authentication(
            self, rf):
        self.signal_was_called = False

        def handler(sender, request, oidc_user, **kwargs):
            self.request = request
            self.oidc_user = oidc_user
            self.signal_was_called = True

        oidc_user_created.connect(handler)

        request = rf.get('/oidc/cb/', {
            'state': 'state',
            'code': 'authcode',
        })
        SessionMiddleware().process_request(request)
        request.session.save()
        backend = OIDCAuthBackend()
        backend.authenticate(request, 'nonce')

        assert self.signal_was_called is True
        assert type(self.request) is WSGIRequest
        assert self.oidc_user.userinfo['email'] == '*****@*****.**'
        assert self.oidc_user.userinfo['sub'] == '1234'

        oidc_user_created.disconnect(handler)
Exemple #37
0
    parser.add_argument("message", nargs="?", help="The message to encrypt")

    args = parser.parse_args()

    keys = {}
    if args.jwk_url:
        keys = assign(load_jwks_from_url(lrequest, args.jwk_url))
    elif args.jwk_file:
        keys = load_jwks(open(args.jwk_file).read())
    elif args.x509_url:
        keys = load_x509_cert(lrequest, args.x509_url)
    elif args.x509_file:
        keys = [import_rsa_key_from_file(args.x509_file)]
    elif args.rsa_file:
        key = rsa_load(args.rsa_file)
        rsa_key = RSAKey(key=key)
        rsa_key.serialize()
        keys = [rsa_key]
    else:
        print("Needs encryption key")
        exit()

    if args.file:
        msg = open(args.file).read()
        msg = msg.strip("\n\r")
    else:
        msg = args.message

    jwe = JWE()
    print(jwe.decrypt(msg, keys))
Exemple #38
0
class TestOIDCRefreshIDTokenMiddleware:
    @pytest.fixture(autouse=True)
    def setup(self):
        httpretty.enable()

        self.key = RSAKey(kid='testkey').load(
            os.path.join(FIXTURE_ROOT, 'testkey.pem'))

        def jwks(_request, _uri, headers):  # noqa: E306
            ks = KEYS()
            ks.add(self.key.serialize())
            return 200, headers, ks.dump_jwks()

        httpretty.register_uri(httpretty.GET,
                               oidc_rp_settings.PROVIDER_JWKS_ENDPOINT,
                               status=200,
                               body=jwks)
        httpretty.register_uri(httpretty.POST,
                               oidc_rp_settings.PROVIDER_TOKEN_ENDPOINT,
                               body=json.dumps({
                                   'id_token': self.generate_jws(),
                                   'access_token': 'accesstoken',
                                   'refresh_token': 'refreshtoken',
                               }),
                               content_type='text/json')
        httpretty.register_uri(httpretty.GET,
                               oidc_rp_settings.PROVIDER_USERINFO_ENDPOINT,
                               body=json.dumps({
                                   'sub': '1234',
                                   'email': '*****@*****.**',
                               }),
                               content_type='text/json')

        yield

        httpretty.disable()

    def generate_jws(self, **kwargs):
        return JWS(self.generate_jws_dict(**kwargs), jwk=self.key,
                   alg='RS256').sign_compact()

    def generate_jws_dict(self, **kwargs):
        client_key = kwargs.get('client_key', oidc_rp_settings.CLIENT_ID)
        now_dt = dt.datetime.utcnow()
        expiration_dt = kwargs.get('expiration_dt',
                                   (now_dt + dt.timedelta(seconds=30)))
        issue_dt = kwargs.get('issue_dt', now_dt)
        nonce = kwargs.get('nonce', 'nonce')
        return {
            'iss': kwargs.get('iss', oidc_rp_settings.PROVIDER_ENDPOINT),
            'nonce': nonce,
            'aud': kwargs.get('aud', client_key),
            'azp': kwargs.get('azp', client_key),
            'exp': timegm(expiration_dt.utctimetuple()),
            'iat': timegm(issue_dt.utctimetuple()),
            'nbf': timegm(kwargs.get('nbf', now_dt).utctimetuple()),
            'sub': '1234',
        }

    def test_can_refresh_the_access_token_of_a_previously_authenticated_user(
            self, rf):
        request = rf.get('/oidc/cb/', {
            'state': 'state',
            'code': 'authcode',
        })
        SessionMiddleware().process_request(request)
        request.session.save()
        backend = OIDCAuthBackend()
        user = backend.authenticate('nonce', request)
        request.session['oidc_auth_id_token_exp_timestamp'] = \
            (tz.now() - dt.timedelta(minutes=1)).timestamp()
        request.session['oidc_auth_refresh_token'] = 'this_is_a_refresh_token'
        auth.login(request, user)
        request.user = user
        middleware = OIDCRefreshIDTokenMiddleware(lambda r: 'OK')
        middleware(request)
        assert request.session['oidc_auth_refresh_token'] == 'refreshtoken'

    def test_can_properly_handle_the_case_where_a_user_was_authenticated_using_the_model_backend(
            self, rf):
        request = rf.get('/')
        SessionMiddleware().process_request(request)
        request.session.save()
        user = get_user_model().objects.create_user('test', '*****@*****.**',
                                                    'insecure')
        request.user = user
        auth.authenticate(username='******', password='******')
        auth.login(request, user)
        middleware = OIDCRefreshIDTokenMiddleware(lambda r: 'OK')
        middleware(request)
        assert request.user == user
        assert request.user.is_authenticated

    def test_do_nothing_if_the_access_token_is_still_valid(self, rf):
        request = rf.get('/oidc/cb/', {
            'state': 'state',
            'code': 'authcode',
        })
        SessionMiddleware().process_request(request)
        request.session.save()
        backend = OIDCAuthBackend()
        user = backend.authenticate('nonce', request)
        request.session['oidc_auth_id_token_exp_timestamp'] = \
            (tz.now() + dt.timedelta(minutes=1)).timestamp()
        request.session['oidc_auth_refresh_token'] = 'this_is_a_refresh_token'
        auth.login(request, user)
        request.user = user
        middleware = OIDCRefreshIDTokenMiddleware(lambda r: 'OK')
        middleware(request)
        assert request.session[
            'oidc_auth_refresh_token'] == 'this_is_a_refresh_token'

    def test_log_out_the_user_if_the_id_token_is_not_valid(self, rf):
        request = rf.get('/oidc/cb/', {
            'state': 'state',
            'code': 'authcode',
        })
        SessionMiddleware().process_request(request)
        request.session.save()
        backend = OIDCAuthBackend()
        user = backend.authenticate('nonce', request)
        request.session['oidc_auth_id_token_exp_timestamp'] = \
            (tz.now() - dt.timedelta(minutes=1)).timestamp()
        request.session['oidc_auth_refresh_token'] = 'this_is_a_refresh_token'
        auth.login(request, user)
        request.user = user

        httpretty.register_uri(httpretty.POST,
                               oidc_rp_settings.PROVIDER_TOKEN_ENDPOINT,
                               body=json.dumps({
                                   'id_token': 'badidtoken',
                                   'access_token': 'accesstoken',
                                   'refresh_token': 'refreshtoken',
                               }),
                               content_type='text/json')

        middleware = OIDCRefreshIDTokenMiddleware(lambda r: 'OK')
        middleware(request)
        assert not request.user.is_authenticated

    def test_log_out_the_user_if_the_refresh_token_is_expired(self, rf):
        request = rf.get('/oidc/cb/', {
            'state': 'state',
            'code': 'authcode',
        })
        SessionMiddleware().process_request(request)
        request.session.save()
        backend = OIDCAuthBackend()
        user = backend.authenticate('nonce', request)
        request.session['oidc_auth_id_token_exp_timestamp'] = \
            (tz.now() - dt.timedelta(minutes=1)).timestamp()
        request.session['oidc_auth_refresh_token'] = 'this_is_a_refresh_token'
        auth.login(request, user)
        request.user = user

        httpretty.register_uri(httpretty.POST,
                               oidc_rp_settings.PROVIDER_TOKEN_ENDPOINT,
                               body=json.dumps({'error': 'yes'}),
                               content_type='text/json',
                               status=400)

        middleware = OIDCRefreshIDTokenMiddleware(lambda r: 'OK')
        middleware(request)
        assert not request.user.is_authenticated
Exemple #39
0
class MetadataGeneration(object):

    def __init__(self, conf, key, idp_conf, xmlsec_path):
        """
        Constructor.
        Initiates the class.
        :param conf: Specific metadata conf
        :param key: A RSA key to be used for encryption.
        :param idp_conf: idp_conf see IdpProxy/idp_conf.example.py
        :param xmlsec_path:
        :raise:
        """
        if (conf is None) or (key is None):
            raise ValueError(
                "A new instance must include a value for logger, conf and key.")

        #Key to be used for encryption.
        self.key = RSAKey(key=key)
        self.key.serialize()
        self.alg = 'RSA-OAEP'
        self.enc = 'A128CBC-HS256'

        #Used for presentation of mako files.
        self.lookup = TemplateLookup(
            directories=[CONST_STATIC_MAKO + 'templates',
                         CONST_STATIC_MAKO + 'htdocs'],
            module_directory='modules',
            input_encoding='utf-8',
            output_encoding='utf-8')

        #A list of all social services used by this IdPproxy.
        self.social_service_key_list = []
        for key in conf:
            self.social_service_key_list.append(conf[key]["name"])

        #A list of all service providers used by this sp.
        self.sp_key_list = idp_conf.metadata.service_providers()

        self.xmlsec_path = xmlsec_path

    @staticmethod
    def verify_handle_request(path):
        """
        Verifies if the given path should be handled by this class.
        :param path: A path.
        :return: True if the path should be handled by this class, otherwise
            false.
        """
        return re.match(CONST_METADATA + ".*", path)

    @staticmethod
    def get_query_dict(environ):
        """
        Retrieves a dictionary with query parameters.
        :param environ: The wsgi enviroment.
        :return: A dictionary with query parameters.
        """
        qs = {}
        query = environ.get("QUERY_STRING", "")
        if not query:
            post_env = environ.copy()
            post_env['QUERY_STRING'] = ''
            query = cgi.FieldStorage(fp=environ['wsgi.input'], environ=post_env,
                                     keep_blank_values=True)
            if query is not None:
                try:
                    for item in query:
                        qs[query[item].name] = query[item].value
                except:
                    qs[CONST_BODY] = query.file.read()

        else:
            qs = dict((k, v if len(v) > 1 else v[0]) for k, v in
                      parse_qs(query).iteritems())
        return qs

    def handle_request(self, environ, start_response, path):
        """
        Call this method from the wsgi application.
        Handles the request if the path matched by verify_handle_request
        and any static file or CONST_METADATA or CONST_METADATASAVE.

        :param environ: wsgi enviroment
        :param start_response: the start response
        :param path: the requested path
        :return: a response fitted for wsgi application.
        """
        try:
            if path == CONST_METADATA:
                return self.handle_metadata(environ, start_response)
            elif path == CONST_METADATAVERIFY:
                return self.handle_metadata_verify(environ, start_response)
            elif path == CONST_METADATAVERIFYJSON:
                return self.handle_metadata_verify_json(
                    environ, start_response, self.get_query_dict(environ))
            elif path == CONST_METADATASAVE:
                return self.handle_metadata_save(environ, start_response,
                                                 self.get_query_dict(environ))
            else:
                filename = CONST_STATIC_FILE + self.get_static_file_name(path)
                if self.verify_static(filename):
                    return self.handle_static(environ, start_response, filename)
                else:
                    return self.handle_static(environ, start_response,
                                              CONST_UNKNOWFILE)
        except Exception:
            _logger.fatal('Unknown error in handle_request.', exc_info=True)
            return self.handle_static(environ, start_response,
                                      CONST_UNKNOWERROR)

    def get_static_file_name(self, path):
        """
        Parses out the static file name from the path.
        :param path: The requested path.
        :return: The static file name.
        """
        if self.verify_handle_request(path):
            try:
                return path[len(CONST_METADATA) + 1:]
            except:
                pass
        return ""

    @staticmethod
    def verify_static(filename):
        """
        Verifies if a static file exists in the folder
        IdPproxy/src/idpproxy/metadata/files/static

        :param filename: The name of the file.
        :return: True if the file exists, otherwise false.
        """
        try:
            with open(filename):
                pass
        except IOError:
            return False
        return True

    def handle_metadata(self, environ, start_response):
        """
        Creates the response for the first page in the metadata generation.
        :param environ: wsgi enviroment
        :param start_response: wsgi start respons
        :return: wsgi response for the mako file metadata.mako.
        """
        resp = Response(mako_template="metadata.mako",
                        template_lookup=self.lookup, headers=[])

        argv = {
            "action": CONST_METADATASAVE,
            "sociallist": sorted(self.social_service_key_list),
            "spKeyList": sorted(self.sp_key_list),
            "verify": CONST_METADATAVERIFY,
        }
        return resp(environ, start_response, **argv)

    def handle_metadata_save(self, environ, start_response, qs):
        """
        Takes the input for the page metadata.mako.
        Encrypts entity id and secret information for the social services.
        Creates the partial xml to be added to the metadata for the service
        provider.
        :param environ: wsgi enviroment
        :param start_response: wsgi start respons
        :param qs: Query parameters in a dictionary.
        :return: wsgi response for the mako file metadatasave.mako.
        """
        resp = Response(mako_template="metadatasave.mako",
                        template_lookup=self.lookup,
                        headers=[])
        if "entityId" not in qs or "secret" not in qs:
            xml = ("Xml could not be generated because no entityId or secret"
                   "has been sent to the service.")
            _logger.warning(xml)
        else:
            try:
                secret_data = json.dumps({"entityId": json.loads(qs["entityId"]),
                                          "secret": json.loads(qs["secret"])})

                # create a JWE
                jwe = JWE(secret_data, alg=self.alg, enc=self.enc)
                secret_data_encrypted = jwe.encrypt([self.key])

                val = AttributeValue()
                val.set_text(secret_data_encrypted)
                attr = Attribute(
                    name_format=NAME_FORMAT_URI,
                    name="http://social2saml.nordu.net/customer",
                    attribute_value=[val])
                eattr = mdattr.EntityAttributes(attribute=[attr])
                nspair = {
                    "mdattr": "urn:oasis:names:tc:SAML:metadata:attribute",
                    "samla": "urn:oasis:names:tc:SAML:2.0:assertion",
                }
                xml = eattr.to_string(nspair)
                xml_list = xml.split("\n", 1)

                if len(xml_list) == 2:
                    xml = xml_list[1]

            except Exception:
                _logger.fatal('Unknown error in handle_metadata_save.',
                                  exc_info=True)
                xml = "Xml could not be generated."
        argv = {
            "home": CONST_METADATA,
            "action": CONST_METADATAVERIFY,
            "xml": xml
        }
        return resp(environ, start_response, **argv)

    def handle_metadata_verify(self, environ, start_response):
        """
        Will show the page for metadata verification (metadataverify.mako).
        :param environ: wsgi enviroment
        :param start_response: wsgi start respons
        :return: wsgi response for the mako file metadatasave.mako.
        """
        resp = Response(mako_template="metadataverify.mako",
                        template_lookup=self.lookup,
                        headers=[])
        argv = {
            "home": CONST_METADATA,
            "action": CONST_METADATAVERIFYJSON
        }
        return resp(environ, start_response, **argv)

    def handle_metadata_verify_json(self, environ, start_response, qs):
        """
        Handles JSON metadata verifications.
        The post body must contains a JSON message like
        { 'xml' : 'a metadata file'}

        :param environ: wsgi enviroment
        :param start_response: wsgi start respons
        :param qs: Query parameters in a dictionary.
        :return: wsgi response contaning a JSON response. The JSON message will
            contain the parameter ok and services.
            ok will contain true if the metadata file can be parsed, otherwise
            false.
            services will contain a list of all the service names contained in
            the metadata file.
        """
        ok = False
        services = "[]"
        try:
            if CONST_BODY in qs:
                json_message = json.loads(qs[CONST_BODY])
                if "xml" in json_message:
                    xml = json_message["xml"]
                    xml = xml.strip()
                    metadata_ok = False
                    ci = None
                    mds = MetadataStore(
                        CONST_ONTS.values(), CONST_ATTRCONV,
                        self.xmlsec_path,
                        disable_ssl_certificate_validation=True)

                    _md = MetaData(CONST_ONTS.values(), CONST_ATTRCONV,
                                  metadata=xml)
                    try:
                        _md.load()
                    except:
                        _logger.info(
                            'Could not parse the metadata file in handleMetadataVerifyJSON.',
                            exc_info=True)
                    else:
                        entity_id = _md.entity.keys()[0]
                        mds.metadata[entity_id] = _md
                        args = {"metad": mds, "dkeys": [self.key]}
                        ci = utils.ConsumerInfo(['metadata'], **args)
                        metadata_ok = True

                    services = "["
                    first = True
                    if ci is not None:
                        for item in ci.info:
                            if item.ava is not None and entity_id in item.ava:
                                for social in item.ava[entity_id]:
                                    if not first:
                                        services += ","
                                    else:
                                        first = False
                                    services += '"' + social + '"'
                    services += "]"
                    if metadata_ok:
                        ok = True
        except:
            _logger.fatal('Unknown error in handleMetadataVerifyJSON.',
                              exc_info=True)
        resp = Response('{"ok":"' + str(ok) + '", "services":' + services + '}',
                        headers=[('Content-Type', CONST_TYPEJSON)])
        return resp(environ, start_response)

    @staticmethod
    def handle_static(environ, start_response, path):
        """
        Creates a response for a static file.
        :param environ: wsgi enviroment
        :param start_response: wsgi start response
        :param path: the static file and path to the file.
        :return: wsgi response for the static file.
        """
        try:
            text = open(path).read()
            if path.endswith(".ico"):
                resp = Response(text,
                                headers=[('Content-Type', "image/x-icon")])
            elif path.endswith(".html"):
                resp = Response(text, headers=[('Content-Type', 'text/html')])
            elif path.endswith(".txt"):
                resp = Response(text, headers=[('Content-Type', 'text/plain')])
            elif path.endswith(".css"):
                resp = Response(text, headers=[('Content-Type', 'text/css')])
            else:
                resp = Response(text, headers=[('Content-Type', 'text/xml')])
        except IOError:
            resp = NotFound()
        return resp(environ, start_response)
Exemple #40
0
class MetadataGeneration(object):
    def __init__(self, conf, key, idp_conf, xmlsec_path):
        """
        Constructor.
        Initiates the class.
        :param conf: Specific metadata conf
        :param key: A RSA key to be used for encryption.
        :param idp_conf: idp_conf see IdpProxy/idp_conf.example.py
        :param xmlsec_path:
        :raise:
        """
        if (conf is None) or (key is None):
            raise ValueError(
                "A new instance must include a value for logger, conf and key."
            )

        #Key to be used for encryption.
        self.key = RSAKey(key=key)
        self.key.serialize()
        self.alg = 'RSA-OAEP'
        self.enc = 'A128CBC-HS256'

        #Used for presentation of mako files.
        self.lookup = TemplateLookup(directories=[
            CONST_STATIC_MAKO + 'templates', CONST_STATIC_MAKO + 'htdocs'
        ],
                                     module_directory='modules',
                                     input_encoding='utf-8',
                                     output_encoding='utf-8')

        #A list of all social services used by this IdPproxy.
        self.social_service_key_list = []
        for key in conf:
            self.social_service_key_list.append(conf[key]["name"])

        #A list of all service providers used by this sp.
        self.sp_key_list = idp_conf.metadata.service_providers()

        self.xmlsec_path = xmlsec_path

    @staticmethod
    def verify_handle_request(path):
        """
        Verifies if the given path should be handled by this class.
        :param path: A path.
        :return: True if the path should be handled by this class, otherwise
            false.
        """
        return re.match(CONST_METADATA + ".*", path)

    @staticmethod
    def get_query_dict(environ):
        """
        Retrieves a dictionary with query parameters.
        :param environ: The wsgi enviroment.
        :return: A dictionary with query parameters.
        """
        qs = {}
        query = environ.get("QUERY_STRING", "")
        if not query:
            post_env = environ.copy()
            post_env['QUERY_STRING'] = ''
            query = cgi.FieldStorage(fp=environ['wsgi.input'],
                                     environ=post_env,
                                     keep_blank_values=True)
            if query is not None:
                try:
                    for item in query:
                        qs[query[item].name] = query[item].value
                except:
                    qs[CONST_BODY] = query.file.read()

        else:
            qs = dict((k, v if len(v) > 1 else v[0])
                      for k, v in parse_qs(query).iteritems())
        return qs

    def handle_request(self, environ, start_response, path):
        """
        Call this method from the wsgi application.
        Handles the request if the path matched by verify_handle_request
        and any static file or CONST_METADATA or CONST_METADATASAVE.

        :param environ: wsgi enviroment
        :param start_response: the start response
        :param path: the requested path
        :return: a response fitted for wsgi application.
        """
        try:
            if path == CONST_METADATA:
                return self.handle_metadata(environ, start_response)
            elif path == CONST_METADATAVERIFY:
                return self.handle_metadata_verify(environ, start_response)
            elif path == CONST_METADATAVERIFYJSON:
                return self.handle_metadata_verify_json(
                    environ, start_response, self.get_query_dict(environ))
            elif path == CONST_METADATASAVE:
                return self.handle_metadata_save(environ, start_response,
                                                 self.get_query_dict(environ))
            else:
                filename = CONST_STATIC_FILE + self.get_static_file_name(path)
                if self.verify_static(filename):
                    return self.handle_static(environ, start_response,
                                              filename)
                else:
                    return self.handle_static(environ, start_response,
                                              CONST_UNKNOWFILE)
        except Exception:
            _logger.fatal('Unknown error in handle_request.', exc_info=True)
            return self.handle_static(environ, start_response,
                                      CONST_UNKNOWERROR)

    def get_static_file_name(self, path):
        """
        Parses out the static file name from the path.
        :param path: The requested path.
        :return: The static file name.
        """
        if self.verify_handle_request(path):
            try:
                return path[len(CONST_METADATA) + 1:]
            except:
                pass
        return ""

    @staticmethod
    def verify_static(filename):
        """
        Verifies if a static file exists in the folder
        IdPproxy/src/idpproxy/metadata/files/static

        :param filename: The name of the file.
        :return: True if the file exists, otherwise false.
        """
        try:
            with open(filename):
                pass
        except IOError:
            return False
        return True

    def handle_metadata(self, environ, start_response):
        """
        Creates the response for the first page in the metadata generation.
        :param environ: wsgi enviroment
        :param start_response: wsgi start respons
        :return: wsgi response for the mako file metadata.mako.
        """
        resp = Response(mako_template="metadata.mako",
                        template_lookup=self.lookup,
                        headers=[])

        argv = {
            "action": CONST_METADATASAVE,
            "sociallist": sorted(self.social_service_key_list),
            "spKeyList": sorted(self.sp_key_list),
            "verify": CONST_METADATAVERIFY,
        }
        return resp(environ, start_response, **argv)

    def handle_metadata_save(self, environ, start_response, qs):
        """
        Takes the input for the page metadata.mako.
        Encrypts entity id and secret information for the social services.
        Creates the partial xml to be added to the metadata for the service
        provider.
        :param environ: wsgi enviroment
        :param start_response: wsgi start respons
        :param qs: Query parameters in a dictionary.
        :return: wsgi response for the mako file metadatasave.mako.
        """
        resp = Response(mako_template="metadatasave.mako",
                        template_lookup=self.lookup,
                        headers=[])
        if "entityId" not in qs or "secret" not in qs:
            xml = ("Xml could not be generated because no entityId or secret"
                   "has been sent to the service.")
            _logger.warning(xml)
        else:
            try:
                secret_data = json.dumps({
                    "entityId": json.loads(qs["entityId"]),
                    "secret": json.loads(qs["secret"])
                })

                # create a JWE
                jwe = JWE(secret_data, alg=self.alg, enc=self.enc)
                secret_data_encrypted = jwe.encrypt([self.key])

                val = AttributeValue()
                val.set_text(secret_data_encrypted)
                attr = Attribute(name_format=NAME_FORMAT_URI,
                                 name="http://social2saml.nordu.net/customer",
                                 attribute_value=[val])
                eattr = mdattr.EntityAttributes(attribute=[attr])
                nspair = {
                    "mdattr": "urn:oasis:names:tc:SAML:metadata:attribute",
                    "samla": "urn:oasis:names:tc:SAML:2.0:assertion",
                }
                xml = eattr.to_string(nspair)
                xml_list = xml.split("\n", 1)

                if len(xml_list) == 2:
                    xml = xml_list[1]

            except Exception:
                _logger.fatal('Unknown error in handle_metadata_save.',
                              exc_info=True)
                xml = "Xml could not be generated."
        argv = {
            "home": CONST_METADATA,
            "action": CONST_METADATAVERIFY,
            "xml": xml
        }
        return resp(environ, start_response, **argv)

    def handle_metadata_verify(self, environ, start_response):
        """
        Will show the page for metadata verification (metadataverify.mako).
        :param environ: wsgi enviroment
        :param start_response: wsgi start respons
        :return: wsgi response for the mako file metadatasave.mako.
        """
        resp = Response(mako_template="metadataverify.mako",
                        template_lookup=self.lookup,
                        headers=[])
        argv = {"home": CONST_METADATA, "action": CONST_METADATAVERIFYJSON}
        return resp(environ, start_response, **argv)

    def handle_metadata_verify_json(self, environ, start_response, qs):
        """
        Handles JSON metadata verifications.
        The post body must contains a JSON message like
        { 'xml' : 'a metadata file'}

        :param environ: wsgi enviroment
        :param start_response: wsgi start respons
        :param qs: Query parameters in a dictionary.
        :return: wsgi response contaning a JSON response. The JSON message will
            contain the parameter ok and services.
            ok will contain true if the metadata file can be parsed, otherwise
            false.
            services will contain a list of all the service names contained in
            the metadata file.
        """
        ok = False
        services = "[]"
        try:
            if CONST_BODY in qs:
                json_message = json.loads(qs[CONST_BODY])
                if "xml" in json_message:
                    xml = json_message["xml"]
                    xml = xml.strip()
                    metadata_ok = False
                    ci = None
                    mds = MetadataStore(
                        CONST_ONTS.values(),
                        CONST_ATTRCONV,
                        self.xmlsec_path,
                        disable_ssl_certificate_validation=True)

                    _md = MetaData(CONST_ONTS.values(),
                                   CONST_ATTRCONV,
                                   metadata=xml)
                    try:
                        _md.load()
                    except:
                        _logger.info(
                            'Could not parse the metadata file in handleMetadataVerifyJSON.',
                            exc_info=True)
                    else:
                        entity_id = _md.entity.keys()[0]
                        mds.metadata[entity_id] = _md
                        args = {"metad": mds, "dkeys": [self.key]}
                        ci = utils.ConsumerInfo(['metadata'], **args)
                        metadata_ok = True

                    services = "["
                    first = True
                    if ci is not None:
                        for item in ci.info:
                            if item.ava is not None and entity_id in item.ava:
                                for social in item.ava[entity_id]:
                                    if not first:
                                        services += ","
                                    else:
                                        first = False
                                    services += '"' + social + '"'
                    services += "]"
                    if metadata_ok:
                        ok = True
        except:
            _logger.fatal('Unknown error in handleMetadataVerifyJSON.',
                          exc_info=True)
        resp = Response('{"ok":"' + str(ok) + '", "services":' + services +
                        '}',
                        headers=[('Content-Type', CONST_TYPEJSON)])
        return resp(environ, start_response)

    @staticmethod
    def handle_static(environ, start_response, path):
        """
        Creates a response for a static file.
        :param environ: wsgi enviroment
        :param start_response: wsgi start response
        :param path: the static file and path to the file.
        :return: wsgi response for the static file.
        """
        try:
            text = open(path).read()
            if path.endswith(".ico"):
                resp = Response(text,
                                headers=[('Content-Type', "image/x-icon")])
            elif path.endswith(".html"):
                resp = Response(text, headers=[('Content-Type', 'text/html')])
            elif path.endswith(".txt"):
                resp = Response(text, headers=[('Content-Type', 'text/plain')])
            elif path.endswith(".css"):
                resp = Response(text, headers=[('Content-Type', 'text/css')])
            else:
                resp = Response(text, headers=[('Content-Type', 'text/xml')])
        except IOError:
            resp = NotFound()
        return resp(environ, start_response)