Exemplo n.º 1
0
    def test_node_json_validation(self):
        node = {
            '@context': OPENBADGES_CONTEXT_V2_URI,
            'id': 'http://example.com/1',
            'type': 'Assertion',
            'schema:location': {
                '@context':
                'https://w3id.org/openbadges/extensions/geoCoordinatesExtension/context.json',
                'type': ['Extension', 'extensions:GeoCoordinates'],
                'description':
                'That place in the woods where we built the fort',
                'schema:geo': {
                    'schema:latitude': 44.580900,
                    'schema:longitude': -123.301815
                }
            }
        }

        loader = CachableDocumentLoader(use_cache=True)
        loader.session.cache.remove_old_entries(datetime.datetime.utcnow())
        loader.contexts = set()
        options = {'jsonld_options': {'documentLoader': loader}}

        set_up_context_mock()
        loader(OPENBADGES_CONTEXT_V2_URI)
        schema_url = list(GeoLocation.validation_schema)[0]
        responses.add(responses.GET,
                      GeoLocation.context_url,
                      status=200,
                      json=GeoLocation.context_json)
        loader(GeoLocation.context_url)
        responses.add(responses.GET,
                      schema_url,
                      status=200,
                      json=GeoLocation.validation_schema[schema_url])
        loader.session.get(schema_url)

        state = INITIAL_STATE
        task = add_task(INTAKE_JSON, data=json.dumps(node), node_id=node['id'])
        result, message, actions = task_named(INTAKE_JSON)(state, task,
                                                           **options)
        state = main_reducer(state, actions[0])
        result, message, actions = task_named(actions[1]['name'])(
            state, actions[1], **options)  # JSONLD_COMPACT_DATE
        state = main_reducer(state, actions[0])  # ADD_NODE
        task_meta = actions[1]  # VALIDATE_EXTENSION_NODE

        result, message, actions = validate_extension_node(state, task_meta)
        self.assertTrue(result,
                        "A valid expression of the extension should pass")
        self.assertIn('validated on node', message)
        self.assertEqual(len(actions), 0)

        del state['graph'][0]['schema:location']['schema:geo'][
            'schema:latitude']
        result, message, actions = validate_extension_node(state, task_meta)
        self.assertFalse(
            result,
            "A required property not present should be detected by JSON-schema."
        )
    def test_queue_validation_on_unknown_extension(self):
        set_up_context_mock()
        self.set_up_test_extension()

        first_node_json = {
            '@context': OPENBADGES_CONTEXT_V2_URI,
            'id': 'http://example.org/assertion',
            'extensions:exampleExtension': {
                '@context': self.extension_context_url,
                'type': ['Extension', 'extensions:UnknownExtension'],
                'unknownProperty': 'I\'m a property, short and sweet'
            },
            'evidence': 'http://example.org/evidence'
        }

        state = INITIAL_STATE

        task_meta = add_task(
            INTAKE_JSON, data=json.dumps(first_node_json), node_id=first_node_json['id'])

        result, message, actions = task_named(INTAKE_JSON)(state, task_meta)
        for action in actions:
            state = main_reducer(state, action)

        # Compact JSON
        result, message, actions = task_named(state['tasks'][0]['name'])(state, state['tasks'][0])

        self.assertEqual(len(actions), 3)

        state = main_reducer(state, actions[0])

        validation_action = actions[1]
        result, message, actions = validate_extension_node(state, validation_action)

        self.assertTrue(result)
Exemplo n.º 3
0
    def load_mocks(self):
        loader = CachableDocumentLoader(use_cache=True)
        loader.session.cache.remove_old_entries(datetime.datetime.utcnow())
        loader.contexts = set()
        self.options = {'jsonld_options': {'documentLoader': loader}}

        set_up_context_mock()
        loader(OPENBADGES_CONTEXT_V2_URI)
        schema_url = list(ExampleExtension.validation_schema)[0]
        responses.add(responses.GET,
                      ExampleExtension.context_url,
                      status=200,
                      json=ExampleExtension.context_json)
        loader(ExampleExtension.context_url)
        responses.add(responses.GET,
                      schema_url,
                      status=200,
                      json=ExampleExtension.validation_schema[schema_url])
        loader.session.get(schema_url)

        self.state = INITIAL_STATE
        task = add_task(INTAKE_JSON,
                        data=json.dumps(self.first_node),
                        node_id=self.first_node['id'])
        result, message, actions = task_named(INTAKE_JSON)(self.state, task,
                                                           **self.options)
        self.state = main_reducer(self.state, actions[0])
        result, message, actions = task_named(actions[1]['name'])(
            self.state, actions[1], **self.options)  # JSONLD_COMPACT_DATE
        self.state = main_reducer(self.state, actions[0])  # ADD_NODE
        self.validation_task = actions[1]  # VALIDATE_EXTENSION_NODE
Exemplo n.º 4
0
    def validate_basic_standalone_extension_node(self):
        set_up_context_mock()
        self.set_up_test_extension()

        extension_data = {
            '@context': self.extension_context_url,
            '@type': ['Extension', 'extensions:UnknownExtension'],
            'unknownProperty': 'I\'m a property, short and sweet'
        }

        store = extension_validation_store(extension_data)
        report = generate_report(store)

        self.assertTrue(report['report']['valid'])

        extension_data[
            'unknownProperty'] = 42  # provokes extension schema error: should be a string.

        store = extension_validation_store(extension_data)
        report = generate_report(store)

        self.assertFalse(report['report']['valid'])

        del extension_data['@type']
        extension_data['unknownProperty'] = "Ok, valid string again"

        store = extension_validation_store(extension_data)
        report = generate_report(store)

        self.assertFalse(
            report['report']['valid'],
            "Should report an error if there weren't any discoverable extension types to test."
        )
Exemplo n.º 5
0
    def test_extension_discovered_jsonld_compact(self):
        """
        Ensure an extension node is properly discovered and that the task runs without error.
        """
        node = {
            '@context': OPENBADGES_CONTEXT_V2_URI,
            'id': 'http://example.com/1',
            'type': 'Assertion',
            'schema:location': {
                '@context': GeoLocation.context_url,
                'type': ['Extension', 'extensions:GeoCoordinates'],
                'description':
                'That place in the woods where we built the fort',
                'schema:geo': {
                    'schema:latitude': 44.580900,
                    'schema:longitude': -123.301815
                }
            }
        }
        state = INITIAL_STATE

        set_up_context_mock()

        responses.add(responses.GET,
                      GeoLocation.context_url,
                      body=json.dumps(GeoLocation.context_json),
                      status=200,
                      content_type='application/ld+json')

        schema_url = 'https://w3id.org/openbadges/extensions/geoCoordinatesExtension/schema.json'
        responses.add(responses.GET,
                      schema_url,
                      body=json.dumps(
                          GeoLocation.validation_schema[schema_url]),
                      status=200,
                      content_type='application/ld+json')

        compact_task = add_task(JSONLD_COMPACT_DATA,
                                data=json.dumps(node),
                                jsonld_options=jsonld_no_cache,
                                context_urls=[GeoLocation.context_url])
        result, message, actions = task_named(JSONLD_COMPACT_DATA)(
            state, compact_task)
        self.assertTrue(result, "JSON-LD Compact is successful.")
        self.assertIn(VALIDATE_EXTENSION_NODE,
                      [i.get('name') for i in actions],
                      "Validation task queued.")
        state = main_reducer(state, actions[0])  # ADD_NODE

        validate_task = [
            i for i in actions if i.get('name') == VALIDATE_EXTENSION_NODE
        ][0]
        self.assertIsNotNone(validate_task['node_json'])

        result, message, actions = task_named(VALIDATE_EXTENSION_NODE)(
            state, validate_task)
        self.assertTrue(result, "Validation task is successful.")
Exemplo n.º 6
0
    def test_can_full_verify_with_revocation_check(self):
        input_assertion = json.loads(test_components['2_0_basic_assertion'])
        input_assertion['verification'] = {'type': 'signed', 'creator': 'http://example.org/key1'}
        set_up_image_mock(u'https://example.org/beths-robot-badge.png')

        input_badgeclass = json.loads(test_components['2_0_basic_badgeclass'])
        set_up_image_mock(input_badgeclass['image'])

        revocation_list = {
            '@context': OPENBADGES_CONTEXT_V2_URI,
            'id': 'http://example.org/revocationList',
            'type': 'RevocationList',
            'revokedAssertions': []}
        input_issuer = json.loads(test_components['2_0_basic_issuer'])
        input_issuer['revocationList'] = revocation_list['id']
        input_issuer['publicKey'] = input_assertion['verification']['creator']

        private_key = RSA.generate(2048)
        print("PKEY")
        print(private_key.publickey().exportKey('PEM').decode())
        cryptographic_key_doc = {
            '@context': OPENBADGES_CONTEXT_V2_URI,
            'id': input_assertion['verification']['creator'],
            'type': 'CryptographicKey',
            'owner': input_issuer['id'],
            'publicKeyPem': private_key.publickey().exportKey('PEM').decode()
        }

        set_up_context_mock()
        for doc in [input_assertion, input_badgeclass, input_issuer, cryptographic_key_doc, revocation_list]:
            responses.add(responses.GET, doc['id'], json=doc, status=200)

        header = json.dumps({'alg': 'RS256'})
        payload = json.dumps(input_assertion)

        encoded_separator = '.'
        if not sys.version[:3] < '3':
            encoded_separator = '.'.encode()
            encoded_header = b64encode(header.encode())
            encoded_payload = b64encode(payload.encode())
        else:
            encoded_header = b64encode(header)
            encoded_payload = b64encode(payload)

        signature = encoded_separator.join([
            encoded_header,
            encoded_payload,
            jws.sign(header, payload, private_key, is_json=True)
        ])

        response = verify(signature, use_cache=False)

        self.assertTrue(response['report']['valid'])
Exemplo n.º 7
0
    def test_detect_jws_signed_input_type(self):
        set_up_context_mock()
        # responses.add(responses.GET, badgeclass_data['id'], json=badgeclass_data, status=200)
        # responses.add(responses.GET, issuer_data['id'], json=issuer_data, status=200)
        # responses.add(responses.GET, signing_key['id'], json=signing_key, status=200)

        state = INITIAL_STATE.copy()
        state['input']['value'] = self.signed_assertion

        success, message, actions = detect_input_type(state)

        self.assertTrue(success)
        self.assertEqual(len(actions), 2)
        self.assertEqual(actions[0]['input_type'], 'jws')
Exemplo n.º 8
0
    def test_revoked_badge_marked_invalid(self):
        input_assertion = json.loads(test_components['2_0_basic_assertion'])
        input_assertion['verification'] = {'type': 'signed', 'creator': 'http://example.org/key1'}

        input_badgeclass = json.loads(test_components['2_0_basic_badgeclass'])

        revocation_list = {
            '@context': OPENBADGES_CONTEXT_V2_URI,
            'id': 'http://example.org/revocationList',
            'type': 'RevocationList',
            'revokedAssertions': [
                {'id': input_assertion['id'], 'revocationReason': 'A good reason, for sure'},
                {'id': 'urn:uuid:52e4c6b3-8c13-4fa8-8482-a5cf34ef37a9'},
                'urn:uuid:6deb4a00-ebce-4b28-8cc2-afa705ef7be4'
            ]
        }
        input_issuer = json.loads(test_components['2_0_basic_issuer'])
        input_issuer['revocationList'] = revocation_list['id']
        input_issuer['publicKey'] = input_assertion['verification']['creator']

        key = RSA.generate(2048)
        public_key_pem = key.publickey().export_key()

        cryptographic_key_doc = {
            '@context': OPENBADGES_CONTEXT_V2_URI,
            'id': input_assertion['verification']['creator'],
            'type': 'CryptographicKey',
            'owner': input_issuer['id'],
            'publicKeyPem': public_key_pem.decode()
        }

        set_up_context_mock()
        for doc in [input_assertion, input_badgeclass, input_issuer, cryptographic_key_doc, revocation_list]:
            responses.add(responses.GET, doc['id'], json=doc, status=200)

        signature = jws.sign(input_assertion, key, algorithm='RS256')

        response = verify(signature, use_cache=False)
        self.assertFalse(response['report']['valid'])
        msg = [a for a in response['report']['messages'] if a.get('name') == VERIFY_SIGNED_ASSERTION_NOT_REVOKED][0]
        self.assertIn('A good reason', msg['result'])

        # Assert pruning went well to eliminate revocationlist revokedAssertions except for the revoked one
        rev_list = [n for n in response['graph'] if n.get('id') == revocation_list['id']][0]
        self.assertEqual(len(rev_list['revokedAssertions']), 1)
        self.assertEqual(rev_list['revokedAssertions'], [revocation_list['revokedAssertions'][0]])
 def set_response_mocks():
     # Make sure to add @responses.activate decorator in calling method
     set_up_context_mock()
     responses.add(
         responses.GET, 'https://example.org/beths-robotics-badge.json',
         body=test_components['2_0_basic_assertion'], status=200,
         content_type='application/ld+json'
     )
     responses.add(
         responses.GET, 'https://example.org/robotics-badge.json',
         body=test_components['2_0_basic_badgeclass'], status=200,
         content_type='application/ld+json'
     )
     responses.add(
         responses.GET, 'https://example.org/organization.json',
         body=test_components['2_0_basic_issuer'], status=200,
         content_type='application/ld+json'
     )
    def test_that_pyld_accepts_caching_loader_for_compaction(self):
        assertion_data = json.loads(test_components['2_0_basic_assertion'])
        context_url = assertion_data['@context']
        loadurl = CachableDocumentLoader(use_cache=True)
        set_up_context_mock()

        first_compacted = jsonld.compact(assertion_data,
                                         context_url,
                                         options={'documentLoader': loadurl})
        second_compacted = jsonld.compact(assertion_data,
                                          context_url,
                                          options={'documentLoader': loadurl})
        # in order to have 'HostedBadge' as the verification type, the assertion_data
        # needs to have gone through compaction against the openbadges context document
        self.assertEqual(first_compacted['verification']['type'],
                         'HostedBadge')
        # second compaction should have built from the cache
        self.assertEqual(first_compacted['verification']['type'],
                         second_compacted['verification']['type'])
    def test_loader_with_session(self):
        session = CachedSession(backend='memory', expire_after=100000)
        loadurl = CachableDocumentLoader(use_cache=True, session=session)
        assertion_data = json.loads(test_components['2_0_basic_assertion'])
        context_url = assertion_data['@context']

        set_up_context_mock()
        session.get(context_url)  # precache response
        responses.reset()
        responses.add(responses.GET, context_url, json={'nothing': 'happenin'})

        first_compacted = jsonld.compact(assertion_data,
                                         context_url,
                                         options={'documentLoader': loadurl})
        second_compacted = jsonld.compact(assertion_data,
                                          context_url,
                                          options={'documentLoader': loadurl})

        # second compaction should have built from the cache
        self.assertEqual(first_compacted['verification']['type'],
                         second_compacted['verification']['type'])
Exemplo n.º 12
0
    def test_can_full_verify_jws_signed_assertion(self):
        """
        I can input a JWS string
        I can extract the Assertion from the input signature string and store it as the canonical version of the Assertion.
        I can discover and retrieve key information from the Assertion.
        I can Access the signing key
        I can verify the key is associated with the listed issuer Profile
        I can verify the JWS signature has been created by a key trusted to correspond to the issuer Profile
        Next: I can verify an assertion with an ephemeral embedded badgeclass as well
        """
        input_assertion = json.loads(test_components['2_0_basic_assertion'])
        input_assertion['verification'] = {'type': 'signed', 'creator': 'http://example.org/key1'}
        set_up_image_mock(u'https://example.org/beths-robot-badge.png')

        input_badgeclass = json.loads(test_components['2_0_basic_badgeclass'])
        set_up_image_mock(input_badgeclass['image'])

        input_issuer = json.loads(test_components['2_0_basic_issuer'])
        input_issuer['publicKey'] = input_assertion['verification']['creator']

        key = RSA.generate(2048)
        public_key_pem = key.publickey().export_key()

        cryptographic_key_doc = {
            '@context': OPENBADGES_CONTEXT_V2_URI,
            'id': input_assertion['verification']['creator'],
            'type': 'CryptographicKey',
            'owner': input_issuer['id'],
            'publicKeyPem': public_key_pem.decode()
        }

        set_up_context_mock()
        for doc in [input_badgeclass, input_issuer, cryptographic_key_doc]:
            responses.add(responses.GET, doc['id'], json=doc, status=200)

        signature = jws.sign(input_assertion, key, algorithm='RS256')

        response = verify(signature, use_cache=False)

        self.assertTrue(response['report']['valid'])
Exemplo n.º 13
0
    def test_can_full_verify_with_revocation_check(self):
        input_assertion = json.loads(test_components['2_0_basic_assertion'])
        input_assertion['verification'] = {'type': 'signed', 'creator': 'http://example.org/key1'}
        set_up_image_mock(u'https://example.org/beths-robot-badge.png')

        input_badgeclass = json.loads(test_components['2_0_basic_badgeclass'])
        set_up_image_mock(input_badgeclass['image'])

        revocation_list = {
            '@context': OPENBADGES_CONTEXT_V2_URI,
            'id': 'http://example.org/revocationList',
            'type': 'RevocationList',
            'revokedAssertions': []}
        input_issuer = json.loads(test_components['2_0_basic_issuer'])
        input_issuer['revocationList'] = revocation_list['id']
        input_issuer['publicKey'] = input_assertion['verification']['creator']

        key = RSA.generate(2048)
        public_key_pem = key.publickey().export_key()

        cryptographic_key_doc = {
            '@context': OPENBADGES_CONTEXT_V2_URI,
            'id': input_assertion['verification']['creator'],
            'type': 'CryptographicKey',
            'owner': input_issuer['id'],
            'publicKeyPem': public_key_pem.decode()
        }

        set_up_context_mock()
        for doc in [input_assertion, input_badgeclass, input_issuer, cryptographic_key_doc, revocation_list]:
            responses.add(responses.GET, doc['id'], json=doc, status=200)

        header = {'alg': 'RS256'}
        payload = json.dumps(input_assertion).encode()
        signature = jws.sign(input_assertion, key, algorithm='RS256')

        response = verify(signature, use_cache=False)

        self.assertTrue(response['report']['valid'])
Exemplo n.º 14
0
    def test_fetch_task_handles_potential_baked_input(self):
        set_up_context_mock()
        assertion_url = 'http://example.org/assertion/1'
        image_url = 'http://example.org/image'

        with open(
                os.path.join(os.path.dirname(__file__), 'testfiles',
                             'public_domain_heart.png'), 'rb') as f:
            baked_file = bake(f, assertion_url)

        responses.add(responses.GET,
                      image_url,
                      body=baked_file.read(),
                      status=200,
                      content_type='image/png')

        task = add_task(FETCH_HTTP_NODE,
                        url=image_url,
                        is_potential_baked_input=True)
        result, message, actions = run_task({}, task)

        self.assertTrue(result)
        store_resource_action = [
            a for a in actions if a.get('type') == STORE_ORIGINAL_RESOURCE
        ][0]
        process_baked_input_action = [
            a for a in actions if a.get('name') == PROCESS_BAKED_RESOURCE
        ][0]

        self.assertEqual(store_resource_action.get('node_id'), image_url)
        self.assertEqual(process_baked_input_action.get('node_id'), image_url)

        task = add_task(FETCH_HTTP_NODE,
                        url=image_url,
                        is_potential_baked_input=False)
        result, message, actions = run_task({}, task)
        self.assertTrue(result)
    def test_queue_validation_on_unknown_extension(self):
        set_up_context_mock()

        extension_schema = {
            "$schema": "http://json-schema.org/draft-04/schema#",
            "title": "1.1 Open Badge Example Extension for testing: Unknown Extension",
            "description": "An extension that allows you to add a single string unknownProperty to an extension object for unknown reasons.",
            "type": "object",
            "properties": {
                "unknownProperty": {
                    "type": "string"
                }
            },
            "required": ["unknownProperty"]
        }
        extension_schema_url = 'http://example.org/unkownSchema'
        extension_context = {
            '@context': {
                "obi": "https://w3id.org/openbadges#",
                "extensions": "https://w3id.org/openbadges/extensions#",
                'unknownProperty': 'http://schema.org/unknownProperty'
            },
            "obi:validation": [
                {
                    "obi:validatesType": "extensions:UnknownExtension",
                    "obi:validationSchema": extension_schema_url
                }
            ]
        }
        extension_context_url = 'http://example.org/unknownExtensionContext'

        first_node_json = {
            '@context': OPENBADGES_CONTEXT_V2_URI,
            'id': 'http://example.org/assertion',
            'extensions:exampleExtension': {
                '@context': extension_context_url,
                'type': ['Extension', 'extensions:UnknownExtension'],
                'unknownProperty': 'I\'m a property, short and sweet'
            },
            'evidence': 'http://example.org/evidence'
        }

        responses.add(
            responses.GET, extension_context_url,
            json=extension_context
        )
        responses.add(
            responses.GET, extension_schema_url,
            json=extension_schema
        )
        state = INITIAL_STATE

        task_meta = add_task(
            INTAKE_JSON, data=json.dumps(first_node_json), node_id=first_node_json['id'])

        result, message, actions = task_named(INTAKE_JSON)(state, task_meta)
        for action in actions:
            state = main_reducer(state, action)

        # Compact JSON
        result, message, actions = task_named(state['tasks'][0]['name'])(state, state['tasks'][0])

        self.assertEqual(len(actions), 3)

        state = main_reducer(state, actions[0])

        validation_action = actions[1]
        result, message, actions = validate_extension_node(state, validation_action)

        self.assertTrue(result)
Exemplo n.º 16
0
    def test_can_full_verify_jws_signed_assertion(self):
        """
        I can input a JWS string
        I can extract the Assertion from the input signature string and store it as the canonical version of the Assertion.
        I can discover and retrieve key information from the Assertion.
        I can Access the signing key
        I can verify the key is associated with the listed issuer Profile
        I can verify the JWS signature has been created by a key trusted to correspond to the issuer Profile
        Next: I can verify an assertion with an ephemeral embedded badgeclass as well
        """
        input_assertion = json.loads(test_components['2_0_basic_assertion'])
        input_assertion['verification'] = {'type': 'signed', 'creator': 'http://example.org/key1'}
        set_up_image_mock(u'https://example.org/beths-robot-badge.png')

        input_badgeclass = json.loads(test_components['2_0_basic_badgeclass'])
        set_up_image_mock(input_badgeclass['image'])

        input_issuer = json.loads(test_components['2_0_basic_issuer'])
        input_issuer['publicKey'] = input_assertion['verification']['creator']

        private_key = RSA.generate(2048)

        cryptographic_key_doc = {
            '@context': OPENBADGES_CONTEXT_V2_URI,
            'id': input_assertion['verification']['creator'],
            'type': 'CryptographicKey',
            'owner': input_issuer['id'],
            'publicKeyPem': private_key.publickey().exportKey('PEM').decode()
        }

        set_up_context_mock()
        for doc in [input_assertion, input_badgeclass, input_issuer, cryptographic_key_doc]:
            responses.add(responses.GET, doc['id'], json=doc, status=200)

        header = json.dumps({'alg': 'RS256'})
        payload = json.dumps(input_assertion)



        encoded_separator = '.'
        if not sys.version[:3] < '3':
            encoded_separator = '.'.encode()
            encoded_header = b64encode(header.encode())
            encoded_payload = b64encode(payload.encode())
        else:
            encoded_header = b64encode(header)
            encoded_payload = b64encode(payload)

        signature = encoded_separator.join([
            encoded_header,
            encoded_payload,
            jws.sign(header,payload,private_key, is_json=True)
        ])


        response = verify(signature, use_cache=False)

        print("TEST CAN FULLY VERIFY JWS SIGNED ASSERTION : response:")
        print(response['report'])

        self.assertTrue(response['report']['valid'])
Exemplo n.º 17
0
    def test_revoked_badge_marked_invalid(self):
        input_assertion = json.loads(test_components['2_0_basic_assertion'])
        input_assertion['verification'] = {'type': 'signed', 'creator': 'http://example.org/key1'}

        input_badgeclass = json.loads(test_components['2_0_basic_badgeclass'])

        revocation_list = {
            '@context': OPENBADGES_CONTEXT_V2_URI,
            'id': 'http://example.org/revocationList',
            'type': 'RevocationList',
            'revokedAssertions': [
                {'id': input_assertion['id'], 'revocationReason': 'A good reason, for sure'},
                {'id': 'urn:uuid:52e4c6b3-8c13-4fa8-8482-a5cf34ef37a9'},
                'urn:uuid:6deb4a00-ebce-4b28-8cc2-afa705ef7be4'
            ]
        }
        input_issuer = json.loads(test_components['2_0_basic_issuer'])
        input_issuer['revocationList'] = revocation_list['id']
        input_issuer['publicKey'] = input_assertion['verification']['creator']

        private_key = RSA.generate(2048)
        cryptographic_key_doc = {
            '@context': OPENBADGES_CONTEXT_V2_URI,
            'id': input_assertion['verification']['creator'],
            'type': 'CryptographicKey',
            'owner': input_issuer['id'],
            'publicKeyPem': make_string_from_bytes(private_key.publickey().exportKey('PEM'))
        }

        set_up_context_mock()
        for doc in [input_assertion, input_badgeclass, input_issuer, cryptographic_key_doc, revocation_list]:
            responses.add(responses.GET, doc['id'], json=doc, status=200)

        header = json.dumps({'alg': 'RS256'})
        payload = json.dumps(input_assertion)

        encoded_separator = '.'
        if not sys.version[:3] < '3':
            encoded_separator = '.'.encode()
            encoded_header = b64encode(header.encode())
            encoded_payload = b64encode(payload.encode())
        else:
            encoded_header = b64encode(header)
            encoded_payload = b64encode(payload)

        signature = encoded_separator.join([
            encoded_header,
            encoded_payload,
            jws.sign(header, payload, private_key, is_json=True)
        ])


        response = verify(signature, use_cache=False)
        self.assertFalse(response['report']['valid'])
        msg = [a for a in response['report']['messages'] if a.get('name') == VERIFY_SIGNED_ASSERTION_NOT_REVOKED][0]
        self.assertIn('A good reason', msg['result'])

        # Assert pruning went well to eliminate revocationlist revokedAssertions except for the revoked one
        rev_list = [n for n in response['graph'] if n.get('id') == revocation_list['id']][0]
        self.assertEqual(len(rev_list['revokedAssertions']), 1)
        self.assertEqual(rev_list['revokedAssertions'], [revocation_list['revokedAssertions'][0]])