Esempio n. 1
0
    def _generate_lti_1p3_keys_if_missing(self):
        """
        Generate LTI 1.3 RSA256 keys if missing.

        If either the public or private key are missing, regenerate them.
        The LMS provides a keyset endpoint, so key rotations don't cause any issues
        for LTI launches (as long as they have a different kid).
        """
        # Generate new private key if not present
        if not self.lti_1p3_internal_private_key:
            # Private key
            private_key = RSA.generate(2048)
            self.lti_1p3_internal_private_key_id = str(uuid.uuid4())
            self.lti_1p3_internal_private_key = private_key.export_key(
                'PEM').decode('utf-8')

            # Clear public key if any to allow regeneration
            # in the code below
            self.lti_1p3_internal_public_jwk = ''

        if not self.lti_1p3_internal_public_jwk:
            # Public key
            key_handler = PlatformKeyHandler(
                key_pem=self.lti_1p3_internal_private_key,
                kid=self.lti_1p3_internal_private_key_id,
            )
            self.lti_1p3_internal_public_jwk = json.dumps(
                key_handler.get_public_jwk())

        # Doesn't do anything if model didn't change
        self.save()
Esempio n. 2
0
    def setUp(self):
        super().setUp()

        self.rsa_key_id = "1"
        self.rsa_key = RSA.generate(2048).export_key('PEM')

        # Set up consumer
        self.key_handler = PlatformKeyHandler(key_pem=self.rsa_key,
                                              kid=self.rsa_key_id)
Esempio n. 3
0
    def test_empty_rsa_key(self):
        """
        Check that class doesn't fail instancing when not using a key.
        """
        empty_key_handler = PlatformKeyHandler(key_pem='')

        # Trying to encode a message should fail
        with self.assertRaises(exceptions.RsaKeyNotSet):
            empty_key_handler.encode_and_sign({})

        # Public JWK should return an empty value
        self.assertEqual(empty_key_handler.get_public_jwk(), {'keys': []})
Esempio n. 4
0
 def test_invalid_rsa_key(self):
     """
     Check that class raises when trying to import invalid RSA Key.
     """
     with self.assertRaises(exceptions.InvalidRsaKey):
         PlatformKeyHandler(key_pem="invalid PEM input")
Esempio n. 5
0
class TestPlatformKeyHandler(TestCase):
    """
    Unit tests for PlatformKeyHandler
    """
    def setUp(self):
        super(TestPlatformKeyHandler, self).setUp()

        self.rsa_key_id = "1"
        self.rsa_key = RSA.generate(2048).export_key('PEM')

        # Set up consumer
        self.key_handler = PlatformKeyHandler(
            key_pem=self.rsa_key,
            kid=self.rsa_key_id
        )

    def _decode_token(self, token):
        """
        Checks for a valid signarute and decodes JWT signed LTI message

        This also touches the public keyset method.
        """
        public_keyset = self.key_handler.get_public_jwk()
        key_set = load_jwks(json.dumps(public_keyset))

        return JWS().verify_compact(token, keys=key_set)

    def test_encode_and_sign(self):
        """
        Test if a message was correctly signed with RSA key.
        """
        message = {
            "test": "test"
        }
        signed_token = self.key_handler.encode_and_sign(message)
        self.assertEqual(
            self._decode_token(signed_token),
            message
        )

    # pylint: disable=unused-argument
    @patch('time.time', return_value=1000)
    def test_encode_and_sign_with_exp(self, mock_time):
        """
        Test if a message was correctly signed and has exp and iat parameters.
        """
        message = {
            "test": "test"
        }

        signed_token = self.key_handler.encode_and_sign(
            message,
            expiration=1000
        )

        self.assertEqual(
            self._decode_token(signed_token),
            {
                "test": "test",
                "iat": 1000,
                "exp": 2000
            }
        )

    def test_invalid_rsa_key(self):
        """
        Check that class raises when trying to import invalid RSA Key.
        """
        with self.assertRaises(exceptions.InvalidRsaKey):
            PlatformKeyHandler(key_pem="invalid PEM input")

    def test_empty_rsa_key(self):
        """
        Check that class doesn't fail instancing when not using a key.
        """
        empty_key_handler = PlatformKeyHandler(key_pem='')

        # Trying to encode a message should fail
        with self.assertRaises(exceptions.RsaKeyNotSet):
            empty_key_handler.encode_and_sign({})

        # Public JWK should return an empty value
        self.assertEqual(
            empty_key_handler.get_public_jwk(),
            {'keys': []}
        )

    # pylint: disable=unused-argument
    @patch('time.time', return_value=1000)
    def test_validate_and_decode(self, mock_time):
        """
        Test validate and decode with all parameters.
        """
        signed_token = self.key_handler.encode_and_sign(
            {
                "iss": "test-issuer",
                "aud": "test-aud",
            },
            expiration=1000
        )

        self.assertEqual(
            self.key_handler.validate_and_decode(signed_token),
            {
                "iss": "test-issuer",
                "aud": "test-aud",
                "iat": 1000,
                "exp": 2000
            }
        )

    # pylint: disable=unused-argument
    @patch('time.time', return_value=1000)
    def test_validate_and_decode_expired(self, mock_time):
        """
        Test validate and decode with all parameters.
        """
        signed_token = self.key_handler.encode_and_sign(
            {},
            expiration=-10
        )

        with self.assertRaises(exceptions.TokenSignatureExpired):
            self.key_handler.validate_and_decode(signed_token)

    def test_validate_and_decode_invalid_iss(self):
        """
        Test validate and decode with invalid iss.
        """
        signed_token = self.key_handler.encode_and_sign({"iss": "wrong"})

        with self.assertRaises(exceptions.InvalidClaimValue):
            self.key_handler.validate_and_decode(signed_token, iss="right")

    def test_validate_and_decode_invalid_aud(self):
        """
        Test validate and decode with invalid aud.
        """
        signed_token = self.key_handler.encode_and_sign({"aud": "wrong"})

        with self.assertRaises(exceptions.InvalidClaimValue):
            self.key_handler.validate_and_decode(signed_token, aud="right")

    def test_validate_and_decode_no_jwt(self):
        """
        Test validate and decode with invalid JWT.
        """
        with self.assertRaises(exceptions.MalformedJwtToken):
            self.key_handler.validate_and_decode("1.2.3")

    def test_validate_and_decode_no_keys(self):
        """
        Test validate and decode when no keys are available.
        """
        signed_token = self.key_handler.encode_and_sign({})
        # Changing the KID so it doesn't match
        self.key_handler.key.kid = "invalid_kid"

        with self.assertRaises(exceptions.NoSuitableKeys):
            self.key_handler.validate_and_decode(signed_token)
Esempio n. 6
0
class TestPlatformKeyHandler(TestCase):
    """
    Unit tests for PlatformKeyHandler
    """
    def setUp(self):
        super(TestPlatformKeyHandler, self).setUp()

        self.rsa_key_id = "1"
        self.rsa_key = RSA.generate(2048).export_key('PEM')

        # Set up consumer
        self.key_handler = PlatformKeyHandler(key_pem=self.rsa_key,
                                              kid=self.rsa_key_id)

    def _decode_token(self, token):
        """
        Checks for a valid signarute and decodes JWT signed LTI message

        This also touches the public keyset method.
        """
        public_keyset = self.key_handler.get_public_jwk()
        key_set = load_jwks(json.dumps(public_keyset))

        return JWS().verify_compact(token, keys=key_set)

    def test_encode_and_sign(self):
        """
        Test if a message was correctly signed with RSA key.
        """
        message = {"test": "test"}
        signed_token = self.key_handler.encode_and_sign(message)
        self.assertEqual(self._decode_token(signed_token), message)

    # pylint: disable=unused-argument
    @patch('time.time', return_value=1000)
    def test_encode_and_sign_with_exp(self, mock_time):
        """
        Test if a message was correctly signed and has exp and iat parameters.
        """
        message = {"test": "test"}

        signed_token = self.key_handler.encode_and_sign(message,
                                                        expiration=1000)

        self.assertEqual(self._decode_token(signed_token), {
            "test": "test",
            "iat": 1000,
            "exp": 2000
        })

    def test_invalid_rsa_key(self):
        """
        Check that class raises when trying to import invalid RSA Key.
        """
        with self.assertRaises(exceptions.InvalidRsaKey):
            PlatformKeyHandler(key_pem="invalid PEM input")

    def test_empty_rsa_key(self):
        """
        Check that class doesn't fail instancing when not using a key.
        """
        empty_key_handler = PlatformKeyHandler(key_pem='')

        # Trying to encode a message should fail
        with self.assertRaises(exceptions.RsaKeyNotSet):
            empty_key_handler.encode_and_sign({})

        # Public JWK should return an empty value
        self.assertEqual(empty_key_handler.get_public_jwk(), {'keys': []})