def __init__(self, skill, verify_signature=True, verify_timestamp=True, verifiers=None): # type: (CustomSkill, bool, bool, List[AbstractVerifier]) -> None """Instantiate the view and set the verifiers on the handler. :param skill: A :py:class:`ask_sdk_core.skill.CustomSkill` instance. If you are using the skill builder from ask-sdk, then you can use the ``create`` method under it, to create a skill instance :type skill: ask_sdk_core.skill.CustomSkill :param verify_signature: Enable request signature verification :type verify_signature: bool :param verify_timestamp: Enable request timestamp verification :type verify_timestamp: bool :param verifiers: An optional list of verifiers, that needs to be applied on the input request, before invoking the request handlers. For more information, look at `hosting the skill as webservice <https://developer.amazon.com/docs/custom-skills/host-a-custom-skill-as-a-web-service.html>`_ :type verifiers: list[ask_sdk_webservice_support.verifier.AbstractVerifier] :raises: :py:class:`TypeError` if the provided skill instance is not a :py:class:`ask_sdk_core.skill.CustomSkill` instance """ self._skill = skill if not isinstance(skill, CustomSkill): raise TypeError( "Invalid skill instance provided. Expected a custom " "skill instance.") if verifiers is None: verifiers = [] self._verifiers = verifiers if verify_signature: request_verifier = RequestVerifier( signature_cert_chain_url_key=SIGNATURE_CERT_CHAIN_URL_KEY, signature_key=SIGNATURE_KEY) self._verifiers.append(request_verifier) self._webservice_handler = WebserviceSkillHandler( skill=self._skill, verify_signature=False, verify_timestamp=verify_timestamp, verifiers=self._verifiers) self._webservice_handler._add_custom_user_agent("django-ask-sdk") super(SkillAdapter, self).__init__()
def test_request_verification_for_valid_request(self): with mock.patch.object(RequestVerifier, '_retrieve_and_validate_certificate_chain'): with mock.patch.object(RequestVerifier, '_valid_request_body'): self.headers[ SIGNATURE_CERT_CHAIN_URL_HEADER] = self.PREPOPULATED_CERT_URL self.headers[SIGNATURE_HEADER] = self.generate_private_key() try: RequestVerifier().verify( headers=self.headers, serialized_request_env="test", deserialized_request_env=RequestEnvelope()) except: # Should never reach here self.fail( "Request verifier couldn't verify a valid signed request" )
def test_request_verification_for_invalid_request(self): with mock.patch.object(RequestVerifier, '_retrieve_and_validate_certificate_chain'): with mock.patch.object(RequestVerifier, '_valid_request_body', side_effect=VerificationException( 'Request body is not valid')): self.headers[ SIGNATURE_CERT_CHAIN_URL_HEADER] = self.PREPOPULATED_CERT_URL self.headers[SIGNATURE_HEADER] = self.generate_private_key() with self.assertRaises(VerificationException) as exc: RequestVerifier().verify( headers=self.headers, serialized_request_env="test", deserialized_request_env=RequestEnvelope()) self.assertIn("Request body is not valid", str(exc.exception))
def setUp(self): self.headers = { SIGNATURE_CERT_CHAIN_URL_HEADER: "TestUrl", SIGNATURE_HEADER: "Test Signature" } self.request_verifier = RequestVerifier()
class TestRequestVerifier(unittest.TestCase): PREPOPULATED_CERT_URL = "https://s3.amazonaws.com/echo.api/echo-api-cert-8.pem" VALID_URL = "https://s3.amazonaws.com/echo.api/cert" VALID_URL_WITH_PORT = "https://s3.amazonaws.com:443/echo.api/cert" VALID_URL_WITH_PATH_TRAVERSAL = ( "https://s3.amazonaws.com/echo.api/../echo.api/cert") INVALID_URL_WITH_INVALID_HOST_NAME = "https://very.bad/echo.api/cert" INVALID_URL_WITH_INVALID_PORT = ( "https://s3.amazonaws.com:563/echo.api/cert") INVALID_URL_WITH_INVALID_PATH = "https://s3.amazonaws.com/cert" INVALID_URL_WITH_INVALID_PATH_TRAVERSAL = ( "https://s3.amazonaws.com/echo.api/../cert") INVALID_URL_WITH_INVALID_UPPER_CASE_PATH = ( "https://s3.amazonaws.com/ECHO.API/cert") MALFORMED_URL = "badUrl" def setUp(self): self.headers = { SIGNATURE_CERT_CHAIN_URL_HEADER: "TestUrl", SIGNATURE_HEADER: "Test Signature" } self.request_verifier = RequestVerifier() @staticmethod def generate_private_key(): return rsa.generate_private_key(public_exponent=65537, key_size=2048, backend=default_backend()) def create_self_signed_certificate(self): self.private_key = self.generate_private_key() subject = issuer = x509.Name([ x509.NameAttribute(NameOID.COUNTRY_NAME, u"US"), x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, u"WA"), x509.NameAttribute(NameOID.LOCALITY_NAME, u"Seattle"), x509.NameAttribute(NameOID.ORGANIZATION_NAME, u"Amazon Alexa"), x509.NameAttribute(NameOID.COMMON_NAME, u"{}".format(self.PREPOPULATED_CERT_URL)), ]) self.mock_certificate = x509.CertificateBuilder( ).subject_name(name=subject).issuer_name(name=issuer).public_key( key=self.private_key.public_key()).serial_number( number=x509.random_serial_number()).not_valid_before( time=datetime.utcnow() - timedelta(minutes=1)).not_valid_after( time=datetime.utcnow() + timedelta(minutes=1)).add_extension( x509.SubjectAlternativeName([ x509.DNSName(u"{}".format(CERT_CHAIN_DOMAIN)) ]), critical=False).sign( private_key=self.private_key, algorithm=SHA256(), backend=default_backend()) # type: Certificate self.request_verifier._cert_cache[ self.PREPOPULATED_CERT_URL] = self.mock_certificate def load_valid_certificate(self): with open( os.path.join(os.path.dirname(os.path.realpath(__file__)), 'data', 'echo-api-cert-7.pem'), 'rb') as cert_response: self.cert_bytes = cert_response.read() self.mock_certificate = load_pem_x509_certificate( data=self.cert_bytes, backend=default_backend()) self.request_verifier._cert_cache[ self.PREPOPULATED_CERT_URL] = self.cert_bytes def sign_data(self, data, private_key=None, padding=PKCS1v15(), hash_algorithm=SHA256()): if private_key is None: private_key = self.private_key return private_key.sign(data=data.encode(CHARACTER_ENCODING), padding=padding, algorithm=hash_algorithm) def test_no_cert_url_header_throw_exception(self): self.headers.pop(SIGNATURE_CERT_CHAIN_URL_HEADER) with self.assertRaises(VerificationException) as exc: self.request_verifier.verify(self.headers, None, None) self.assertIn("Missing Signature/Certificate for the skill request", str(exc.exception)) def test_no_signature_header_throw_exception(self): self.headers.pop(SIGNATURE_HEADER) with self.assertRaises(VerificationException) as exc: self.request_verifier.verify(self.headers, None, None) self.assertIn("Missing Signature/Certificate for the skill request", str(exc.exception)) def test_validate_cert_url_scheme_mismatch_throw_exception(self): with self.assertRaises(VerificationException) as exc: self.request_verifier._validate_certificate_url(self.MALFORMED_URL) self.assertIn("Signature Certificate URL has invalid protocol", str(exc.exception)) def test_validate_cert_url_hostname_mismatch_throw_exception(self): with self.assertRaises(VerificationException) as exc: self.request_verifier._validate_certificate_url( self.INVALID_URL_WITH_INVALID_HOST_NAME) self.assertIn("Signature Certificate URL has invalid hostname", str(exc.exception)) def test_validate_cert_url_start_path_mismatch_throw_exception(self): with self.assertRaises(VerificationException) as exc: self.request_verifier._validate_certificate_url( self.INVALID_URL_WITH_INVALID_PATH) self.assertIn("Signature Certificate URL has invalid path", str(exc.exception)) def test_validate_cert_url_normalized_start_path_mismatch_throw_exception( self): with self.assertRaises(VerificationException) as exc: self.request_verifier._validate_certificate_url( self.INVALID_URL_WITH_INVALID_PATH_TRAVERSAL) self.assertIn("Signature Certificate URL has invalid path", str(exc.exception)) def test_validate_cert_url_start_path_case_mismatch_throw_exception(self): with self.assertRaises(VerificationException) as exc: self.request_verifier._validate_certificate_url( self.INVALID_URL_WITH_INVALID_UPPER_CASE_PATH) self.assertIn("Signature Certificate URL has invalid path", str(exc.exception)) def test_validate_cert_url_port_mismatch_throw_exception(self): with self.assertRaises(VerificationException) as exc: self.request_verifier._validate_certificate_url( self.INVALID_URL_WITH_INVALID_PORT) self.assertIn("Signature Certificate URL has invalid port", str(exc.exception)) def test_validate_cert_url_for_valid_url(self): try: self.request_verifier._validate_certificate_url(self.VALID_URL) except: # Should never reach here self.fail( "Request Verifier couldn't validate a valid certificate URL") def test_validate_cert_url_for_valid_url_with_port(self): try: self.request_verifier._validate_certificate_url( self.VALID_URL_WITH_PORT) except: # Should never reach here self.fail("Request Verifier couldn't validate a valid certificate " "URL with valid port") def test_validate_cert_url_for_valid_url_with_path_traversal(self): try: self.request_verifier._validate_certificate_url( self.VALID_URL_WITH_PATH_TRAVERSAL) except: # Should never reach here self.fail("Request Verifier couldn't validate a valid certificate " "URL with path traversal") def test_load_cert_chain_invalid_cert_url_throw_exception(self): mocked_parsed_url = mock.MagicMock(spec=ParseResult) with mock.patch("ask_sdk_webservice_support.verifier.urlparse", return_value=mocked_parsed_url): with self.assertRaises(VerificationException) as exc: self.request_verifier._load_cert_chain(self.MALFORMED_URL) self.assertIn("Unable to load certificate from URL", str(exc.exception)) def test_load_cert_chain_load_and_cache_cert(self): self.assertIsNone( self.request_verifier._cert_cache.get(self.PREPOPULATED_CERT_URL, None), "Invalid Certificate cached in Request Verifier Certificate " "Cache") with mock.patch("ask_sdk_webservice_support.verifier.urlopen", autospec=True) as mock_url_open: if six.PY2: # Because of the way mocks work with context manager in py2.7 mock_url_open.return_value.read.return_value = "test" else: mock_url_open.return_value.__enter__.return_value.read.return_value = "test" self.request_verifier._load_cert_chain(self.PREPOPULATED_CERT_URL) self.assertEqual( self.request_verifier._cert_cache.get( self.PREPOPULATED_CERT_URL), "test", "Request Verifier loaded invalid certificate in certificate cache" ) @freeze_time('2001-01-01') def test_validate_cert_chain_invalid_path(self): self.load_valid_certificate() with self.assertRaises(VerificationException) as exc: self.request_verifier._validate_cert_chain( cert_chain=self.cert_bytes) self.assertIn("Certificate chain is not valid", str(exc.exception)) @freeze_time('2020-01-01') def test_validate_cert_chain_valid_path(self): self.load_valid_certificate() try: self.request_verifier._validate_cert_chain( cert_chain=self.cert_bytes) except: # Should never reach here self.fail( "Request verifier couldn't validate a valid certificate chain") def test_validate_end_cert_expired_before_cert_throw_exception(self): test_certificate = mock.MagicMock(spec=x509.Certificate) test_certificate.not_valid_before = datetime.utcnow() + timedelta( days=1) with self.assertRaises(VerificationException) as exc: self.request_verifier._validate_end_certificate(test_certificate) self.assertIn("Signing Certificate expired", str(exc.exception)) def test_validate_end_cert_expired_after_cert_throw_exception(self): test_certificate = mock.MagicMock(spec=x509.Certificate) test_certificate.not_valid_before = datetime.utcnow() test_certificate.not_valid_after = datetime.now() - timedelta(days=1) with self.assertRaises(VerificationException) as exc: self.request_verifier._validate_end_certificate(test_certificate) self.assertIn("Signing Certificate expired", str(exc.exception)) def test_validate_end_cert_domain_missing_throw_exception(self): test_certificate = mock.MagicMock(spec=x509.Certificate) test_certificate.not_valid_before = datetime.utcnow() - timedelta( minutes=1) test_certificate.not_valid_after = datetime.utcnow() + timedelta( minutes=1) with self.assertRaises(VerificationException) as exc: self.request_verifier._validate_end_certificate(test_certificate) self.assertIn("domain missing in Signature Certificate Chain", str(exc.exception)) def test_validate_end_cert_valid_cert(self): self.create_self_signed_certificate() try: self.request_verifier._validate_end_certificate( self.mock_certificate) except: # Should never reach here self.fail("Request Verifier certificate validation failed for a " "valid certificate chain") def test_validate_request_body_for_valid_request(self): test_content = "This is some test content" self.create_self_signed_certificate() signature = self.sign_data(data=test_content) try: self.request_verifier._valid_request_body( cert_chain=self.mock_certificate, signature=base64.b64encode(signature), serialized_request_env=test_content) except: # Should never reach here self.fail( "Request verifier validate request body failed for a valid " "signed request") def test_validate_request_body_for_invalid_request(self): test_content = "This is some test content" self.create_self_signed_certificate() different_private_key = self.generate_private_key() signature = self.sign_data(data=test_content, private_key=different_private_key) with self.assertRaises(VerificationException) as exc: self.request_verifier._valid_request_body( cert_chain=self.mock_certificate, signature=base64.b64encode(signature), serialized_request_env=test_content) self.assertIn("Request body is not valid", str(exc.exception)) def test_request_verification_for_valid_request(self): with mock.patch.object(RequestVerifier, '_retrieve_and_validate_certificate_chain'): with mock.patch.object(RequestVerifier, '_valid_request_body'): self.headers[ SIGNATURE_CERT_CHAIN_URL_HEADER] = self.PREPOPULATED_CERT_URL self.headers[SIGNATURE_HEADER] = self.generate_private_key() try: RequestVerifier().verify( headers=self.headers, serialized_request_env="test", deserialized_request_env=RequestEnvelope()) except: # Should never reach here self.fail( "Request verifier couldn't verify a valid signed request" ) def test_request_verification_for_invalid_request(self): with mock.patch.object(RequestVerifier, '_retrieve_and_validate_certificate_chain'): with mock.patch.object(RequestVerifier, '_valid_request_body', side_effect=VerificationException( 'Request body is not valid')): self.headers[ SIGNATURE_CERT_CHAIN_URL_HEADER] = self.PREPOPULATED_CERT_URL self.headers[SIGNATURE_HEADER] = self.generate_private_key() with self.assertRaises(VerificationException) as exc: RequestVerifier().verify( headers=self.headers, serialized_request_env="test", deserialized_request_env=RequestEnvelope()) self.assertIn("Request body is not valid", str(exc.exception))