def test_rsa_raw_verify(self): with open(os.path.join(fixtures_dir, 'message.txt'), 'rb') as f: original_data = f.read() with open(os.path.join(fixtures_dir, 'rsa_signature_raw'), 'rb') as f: signature = f.read() public = asymmetric.load_public_key(os.path.join(fixtures_dir, 'keys/test.crt')) asymmetric.rsa_pkcs1v15_verify(public, signature, original_data, 'raw')
def validate(self, msg, sig): if self._cert is None: raise CryptoException("Cannot validate: no certificate") try: oscrypto_asymmetric.rsa_pkcs1v15_verify(oscrypto_asymmetric.load_certificate(self._cert), sig, msg, "sha1") except oscrypto_errors.SignatureError as err: raise CryptoException("Cannot validate: %s" % (err))
def test_rsa_verify_key_size_mismatch(self): with open(os.path.join(fixtures_dir, 'message.txt'), 'rb') as f: original_data = f.read() with open(os.path.join(fixtures_dir, 'rsa_signature'), 'rb') as f: signature = f.read() public = asymmetric.load_public_key(os.path.join(fixtures_dir, 'keys/test-4096.crt')) with self.assertRaises(errors.SignatureError): asymmetric.rsa_pkcs1v15_verify(public, signature, original_data, 'sha1')
def test_rsa_raw_verify_fail(self): with open(os.path.join(fixtures_dir, 'message.txt'), 'rb') as f: original_data = f.read() with open(os.path.join(fixtures_dir, 'rsa_signature_raw'), 'rb') as f: signature = f.read() public = asymmetric.load_public_key(os.path.join(fixtures_dir, 'keys/test.crt')) with self.assertRaises(errors.SignatureError): asymmetric.rsa_pkcs1v15_verify(public, signature, original_data + b'1', 'raw')
def test_rsa_raw_sign(self): original_data = b'This is data to sign!' private = asymmetric.load_private_key(os.path.join(fixtures_dir, 'keys/test.key')) public = asymmetric.load_public_key(os.path.join(fixtures_dir, 'keys/test.crt')) signature = asymmetric.rsa_pkcs1v15_sign(private, original_data, 'raw') self.assertIsInstance(signature, byte_cls) asymmetric.rsa_pkcs1v15_verify(public, signature, original_data, 'raw')
def validate(self, msg, sig): if self._cert is None: raise CryptoException('Cannot validate: no certificate') try: oscrypto_asymmetric.rsa_pkcs1v15_verify( oscrypto_asymmetric.load_certificate(self._cert), sig, msg, 'sha1') except oscrypto_errors.SignatureError as err: raise CryptoException('Cannot validate: {}'.format(err))
def test_rsa_generate(self): public, private = asymmetric.generate_pair('rsa', bit_size=2048) self.assertEqual('rsa', public.algorithm) self.assertEqual(2048, public.bit_size) original_data = b'This is data to sign' signature = asymmetric.rsa_pkcs1v15_sign(private, original_data, 'sha1') self.assertIsInstance(signature, byte_cls) asymmetric.rsa_pkcs1v15_verify(public, signature, original_data, 'sha1')
def test_rsa_verify_key_size_mismatch(self): with open(os.path.join(fixtures_dir, 'message.txt'), 'rb') as f: original_data = f.read() with open(os.path.join(fixtures_dir, 'rsa_signature'), 'rb') as f: signature = f.read() public = asymmetric.load_public_key( os.path.join(fixtures_dir, 'keys/test-4096.crt')) with self.assertRaises(errors.SignatureError): asymmetric.rsa_pkcs1v15_verify(public, signature, original_data, 'sha1')
def test_rsa_verify_fail_each_byte(self): with open(os.path.join(fixtures_dir, 'message.txt'), 'rb') as f: original_data = f.read() with open(os.path.join(fixtures_dir, 'rsa_signature'), 'rb') as f: original_signature = f.read() public = asymmetric.load_public_key(os.path.join(fixtures_dir, 'keys/test.crt')) for i in range(0, len(original_signature)): if i == 0: signature = b'\xab' + original_signature[1:] elif i == len(original_signature) - 1: signature = original_signature[0:-1] + b'\xab' else: signature = original_signature[0:i] + b'\xab' + original_signature[i+1:] with self.assertRaises(errors.SignatureError): asymmetric.rsa_pkcs1v15_verify(public, signature, original_data+ b'1', 'sha1')
def _validate_cms_signature(sig_blob: SignatureBlob, cd_hash: bytes): assert sig_blob.cms signed_data = sig_blob.cms["content"] assert isinstance(signed_data, SignedData) assert len(signed_data["signer_infos"]) == 1 # Get certificates cert_chain = [] for cert in signed_data["certificates"]: c = cert.chosen assert isinstance(c, Certificate) cert_chain.append(c) # Get algorithms used signer_info = signed_data["signer_infos"][0] digest_alg = signer_info["digest_algorithm"]["algorithm"].native sig_alg = signer_info["signature_algorithm"]["algorithm"].native # Get message and signature signed_attrs = signer_info["signed_attrs"] sig = signer_info["signature"].contents # Check the hash of CodeDirectory matches what is in the signature message_digest = None for attr in sig_blob.cms["content"]["signer_infos"][0]["signed_attrs"]: if attr["type"].native == "message_digest": message_digest = attr["values"][0].native if message_digest != cd_hash: raise Exception( f"CodeDirectory Hash mismatch. Expected {message_digest.hex()}, Calculated {cd_hash.hex()}" ) if message_digest is None: raise Exception("message_digest not found in signature") # Validate the certificate chain validation_context = ValidationContext( trust_roots=APPLE_ROOTS, allow_fetching=False, additional_critical_extensions=APPLE_CERT_CRIT_EXTS, ) validator = CertificateValidator(cert_chain[-1], cert_chain[0:-1], validation_context) validator.validate_usage({"digital_signature"}, {"code_signing"}) # Check the signature pubkey = asymmetric.load_public_key(cert_chain[-1].public_key) signed_msg = _sort_attributes(signed_attrs).dump() asymmetric.rsa_pkcs1v15_verify(pubkey, sig, signed_msg, digest_alg)
def test_rsa_verify_fail_each_byte(self): with open(os.path.join(fixtures_dir, 'message.txt'), 'rb') as f: original_data = f.read() with open(os.path.join(fixtures_dir, 'rsa_signature'), 'rb') as f: original_signature = f.read() public = asymmetric.load_public_key( os.path.join(fixtures_dir, 'keys/test.crt')) for i in range(0, len(original_signature)): if i == 0: signature = b'\xab' + original_signature[1:] elif i == len(original_signature) - 1: signature = original_signature[0:-1] + b'\xab' else: signature = original_signature[ 0:i] + b'\xab' + original_signature[i + 1:] with self.assertRaises(errors.SignatureError): asymmetric.rsa_pkcs1v15_verify(public, signature, original_data + b'1', 'sha1')
def verify_signature(self, signature, data, algorithm, allow_legacy=False): """Verifies whether the signature bytes match the data using the hashing algorithm. Supports RSA and EC keys. Note that not all hashing algorithms are supported. :param bytes signature: The signature to verify :param bytes data: The data that must be verified :type algorithm: a hashlib function :param algorithm: The hashing algorithm to use :param bool allow_legacy: If True, allows a legacy signature verification. This method is intended for the case where the encryptedDigest does not contain an ASN.1 structure, but a raw hash value instead. It is attempted automatically when verification of the RSA signature fails. This case is described in more detail on https://mta.openssl.org/pipermail/openssl-users/2015-September/002053.html """ public_key = asymmetric.load_public_key(self.to_asn1crypto.public_key) if public_key.algorithm == 'rsa': verify_func = asymmetric.rsa_pkcs1v15_verify elif public_key.algorithm == 'dsa': verify_func = asymmetric.dsa_verify elif public_key.algorithm == 'ec': verify_func = asymmetric.ecdsa_verify else: raise CertificateVerificationError( "Signature algorithm %s is unsupported for %s" % (public_key.algorithm, self)) try: verify_func(public_key, signature, data, algorithm().name) except Exception as e: if not allow_legacy or public_key.algorithm != 'rsa': raise CertificateVerificationError( "Invalid signature for %s: %s" % (self, e)) else: return try: asymmetric.rsa_pkcs1v15_verify(public_key, signature, algorithm(data).digest(), 'raw') except Exception as e: raise CertificateVerificationError( "Invalid signature for %s (legacy attempted): %s" % (self, e))
def verify(self, public_key_modulus: int, public_key_exponent: int) -> bool: """Use given public key to verify the certificate is signed. :param public_key_modulus: modulus of the public key to be verified :param public_key_exponent: exponent of the public key to be verified :return: True if verification pass; False otherwise """ public_key = internal_backend.rsa_public_key(public_key_modulus, public_key_exponent) key_object = load_public_key(public_key.export_key()) try: rsa_pkcs1v15_verify( key_object, self._cert["signature_value"].native, self._cert["tbs_certificate"].dump(), self.hash_algo, ) except SignatureError: return False return True
def test_rsa_generate(self): public, private = asymmetric.generate_pair('rsa', bit_size=2048) self.assertEqual('rsa', public.algorithm) self.assertEqual(2048, public.bit_size) original_data = b'This is data to sign' signature = asymmetric.rsa_pkcs1v15_sign(private, original_data, 'sha1') self.assertIsInstance(signature, byte_cls) asymmetric.rsa_pkcs1v15_verify(public, signature, original_data, 'sha1') raw_public = asymmetric.dump_public_key(public) asymmetric.load_public_key(raw_public) raw_private = asymmetric.dump_private_key(private, None) asymmetric.load_private_key(raw_private, None) self.assertIsInstance(private.fingerprint, byte_cls) self.assertIsInstance(public.fingerprint, byte_cls) self.assertEqual(private.fingerprint, public.fingerprint)
def verify(self, pubkey): self.check_valid() with open(self.filepath, 'rb') as zipfile: zipfile.seek(0, os.SEEK_SET) message = zipfile.read(self.signed_len) zipfile.seek(-self.signature_start, os.SEEK_END) signature_size = self.signature_start - FOOTER_SIZE signature_raw = zipfile.read(signature_size) sig = ContentInfo.load(signature_raw)['content']['signer_infos'][0] sig_contents = sig['signature'].contents sig_type = DigestAlgorithmId.map( sig['digest_algorithm']['algorithm'].dotted) with open(pubkey, 'rb') as keyfile: keydata = load_public_key(keyfile.read()) return rsa_pkcs1v15_verify(keydata, sig_contents, message, sig_type)
def test_2k_signverify(): k1, k2 = setup_keys_2k() ha = 'sha256' s = crypto.pkcs1v15_sign(k1, pytest.tbs_str) print('[{}]'.format(', '.join(hex(x) for x in list(s.signature)))) pubkey_info = keys.PublicKeyInfo.load(pytest.twok) # Load a public key into the oscrypto engine to using it in the verify function public = load_public_key(pubkey_info) rsa_pkcs1v15_verify(public, s.signature, pytest.tbs_str, ha) # Assert wrong text with pytest.raises(SignatureError): rsa_pkcs1v15_verify(public, s.signature, pytest.tbs_str_fail, ha) # Assert wrong key with pytest.raises(SignatureError): pubkey_info = keys.PublicKeyInfo.load(pytest.twok_fail) public = load_public_key(pubkey_info) rsa_pkcs1v15_verify(public, s.signature, pytest.tbs_str, ha)
def test_1k_signverify(): LOGGER.info('Sign data with newly generated RSA1k key and verify result') setup_keys_1k() ha = 'sha256' s = rsassa.sign(pytest.onek, pytest.tbs_str) print('[{}]'.format(', '.join(hex(x) for x in list(s.signature)))) pubkey_info = keys.PublicKeyInfo.load(pytest.onek.pkey) # Load a public key into the oscrypto engine to using it in the verify function public = load_public_key(pubkey_info) rsa_pkcs1v15_verify(public, s.signature, pytest.tbs_str, ha) # Assert wrong text with pytest.raises(SignatureError): rsa_pkcs1v15_verify(public, s.signature, pytest.tbs_str_fail, ha) # Assert wrong key with pytest.raises(SignatureError): pubkey_info = keys.PublicKeyInfo.load(pytest.onek_fail.pkey) public = load_public_key(pubkey_info) rsa_pkcs1v15_verify(public, s.signature, pytest.tbs_str, ha)
def on_get(self, req, resp): operation = req.get_param("operation", required=True) if operation.lower() == "getcacert": resp.body = keys.parse_certificate( self.authority.certificate_buf).dump() resp.append_header("Content-Type", "application/x-x509-ca-cert") return # If we bump into exceptions later encrypted_container = b"" attr_list = [ cms.CMSAttribute({ 'type': "message_type", 'values': ["3"] }), cms.CMSAttribute({ 'type': "pki_status", 'values': ["2"] # rejected }) ] try: info = cms.ContentInfo.load( b64decode(req.get_param("message", required=True))) ############################################### ### Verify signature of the outer container ### ############################################### signed_envelope = info['content'] encap_content_info = signed_envelope['encap_content_info'] encap_content = encap_content_info['content'] # TODO: try except current_certificate, = signed_envelope["certificates"] signer, = signed_envelope["signer_infos"] # TODO: compare cert to current one if we are renewing assert signer["digest_algorithm"]["algorithm"].native == "md5" assert signer["signature_algorithm"][ "algorithm"].native == "rsassa_pkcs1v15" message_digest = None transaction_id = None sender_nonce = None for attr in signer["signed_attrs"]: if attr["type"].native == "sender_nonce": sender_nonce, = attr["values"] elif attr["type"].native == "trans_id": transaction_id, = attr["values"] elif attr["type"].native == "message_digest": message_digest, = attr["values"] if hashlib.md5(encap_content.native).digest( ) != message_digest.native: raise SCEPBadMessageCheck() assert message_digest msg = signer["signed_attrs"].dump(force=True) assert msg[0] == 160 # Verify signature try: asymmetric.rsa_pkcs1v15_verify( asymmetric.load_certificate(current_certificate.dump()), signer["signature"].native, b"\x31" + msg[1:], # wtf?! "md5") except SignatureError: raise SCEPBadMessageCheck() ############################### ### Decrypt inner container ### ############################### info = cms.ContentInfo.load(encap_content.native) encrypted_envelope = info['content'] encrypted_content_info = encrypted_envelope[ 'encrypted_content_info'] iv = encrypted_content_info['content_encryption_algorithm'][ 'parameters'].native if encrypted_content_info['content_encryption_algorithm'][ "algorithm"].native != "des": raise SCEPBadAlgo() encrypted_content = encrypted_content_info[ 'encrypted_content'].native recipient, = encrypted_envelope['recipient_infos'] if recipient.native["rid"][ "serial_number"] != self.authority.certificate.serial_number: raise SCEPBadCertId() # Since CA private key is not directly readable here, we'll redirect it to signer socket key = asymmetric.rsa_pkcs1v15_decrypt( self.authority.private_key, recipient.native["encrypted_key"]) if len(key) == 8: key = key * 3 # Convert DES to 3DES buf = symmetric.tripledes_cbc_pkcs5_decrypt( key, encrypted_content, iv) _, _, common_name = self.authority.store_request(buf, overwrite=True) cert, buf = self.authority.sign(common_name, overwrite=True) signed_certificate = asymmetric.load_certificate(buf) content = signed_certificate.asn1.dump() except SCEPError as e: attr_list.append( cms.CMSAttribute({ 'type': "fail_info", 'values': ["%d" % e.code] })) else: ################################## ### Degenerate inner container ### ################################## degenerate = cms.ContentInfo({ 'content_type': "signed_data", 'content': cms.SignedData({ 'version': "v1", 'certificates': [signed_certificate.asn1], 'digest_algorithms': [cms.DigestAlgorithm({'algorithm': "md5"})], 'encap_content_info': { 'content_type': "data", 'content': cms.ContentInfo({ 'content_type': "signed_data", 'content': None }).dump() }, 'signer_infos': [] }) }) ################################ ### Encrypt middle container ### ################################ key = os.urandom(8) iv, encrypted_content = symmetric.des_cbc_pkcs5_encrypt( key, degenerate.dump(), os.urandom(8)) assert degenerate.dump() == symmetric.tripledes_cbc_pkcs5_decrypt( key * 3, encrypted_content, iv) ri = cms.RecipientInfo({ 'ktri': cms.KeyTransRecipientInfo({ 'version': "v0", 'rid': cms.RecipientIdentifier({ 'issuer_and_serial_number': cms.IssuerAndSerialNumber({ 'issuer': current_certificate.chosen["tbs_certificate"] ["issuer"], 'serial_number': current_certificate.chosen["tbs_certificate"] ["serial_number"], }), }), 'key_encryption_algorithm': { 'algorithm': "rsa" }, 'encrypted_key': asymmetric.rsa_pkcs1v15_encrypt( asymmetric.load_certificate( current_certificate.chosen.dump()), key) }) }) encrypted_container = cms.ContentInfo({ 'content_type': "enveloped_data", 'content': cms.EnvelopedData({ 'version': "v1", 'recipient_infos': [ri], 'encrypted_content_info': { 'content_type': "data", 'content_encryption_algorithm': { 'algorithm': "des", 'parameters': iv }, 'encrypted_content': encrypted_content } }) }).dump() attr_list = [ cms.CMSAttribute({ 'type': "message_digest", 'values': [hashlib.sha1(encrypted_container).digest()] }), cms.CMSAttribute({ 'type': "message_type", 'values': ["3"] }), cms.CMSAttribute({ 'type': "pki_status", 'values': ["0"] # ok }) ] finally: ############################## ### Signed outer container ### ############################## attrs = cms.CMSAttributes(attr_list + [ cms.CMSAttribute({ 'type': "recipient_nonce", 'values': [sender_nonce] }), cms.CMSAttribute({ 'type': "trans_id", 'values': [transaction_id] }) ]) signer = cms.SignerInfo({ "signed_attrs": attrs, 'version': "v1", 'sid': cms.SignerIdentifier({ 'issuer_and_serial_number': cms.IssuerAndSerialNumber({ 'issuer': self.authority.certificate.issuer, 'serial_number': self.authority.certificate.serial_number, }), }), 'digest_algorithm': algos.DigestAlgorithm({'algorithm': "sha1"}), 'signature_algorithm': algos.SignedDigestAlgorithm({'algorithm': "rsassa_pkcs1v15"}), 'signature': asymmetric.rsa_pkcs1v15_sign(self.authority.private_key, b"\x31" + attrs.dump()[1:], "sha1") }) resp.append_header("Content-Type", "application/x-pki-message") resp.body = cms.ContentInfo({ 'content_type': "signed_data", 'content': cms.SignedData({ 'version': "v1", 'certificates': [self.authority.certificate], 'digest_algorithms': [cms.DigestAlgorithm({'algorithm': "sha1"})], 'encap_content_info': { 'content_type': "data", 'content': encrypted_container }, 'signer_infos': [signer] }) }).dump()
def verify_message(data_to_verify, signature, verify_cert): """Function parses an ASN.1 encrypted message and extracts/decrypts the original message. :param data_to_verify: A byte string of the data to be verified against the signature. :param signature: A CMS ASN.1 byte string containing the signature. :param verify_cert: The certificate to be used for verifying the signature. :return: The digest algorithm that was used in the signature. """ cms_content = cms.ContentInfo.load(signature) digest_alg = None if cms_content['content_type'].native == 'signed_data': for signer in cms_content['content']['signer_infos']: digest_alg = signer['digest_algorithm']['algorithm'].native if digest_alg not in DIGEST_ALGORITHMS: raise Exception('Unsupported Digest Algorithm') sig_alg = signer['signature_algorithm']['algorithm'].native sig = signer['signature'].native signed_data = data_to_verify if signer['signed_attrs']: attr_dict = {} for attr in signer['signed_attrs']: try: attr_dict[attr.native['type']] = attr.native['values'] except (ValueError, KeyError): continue message_digest = bytes() for d in attr_dict['message_digest']: message_digest += d digest_func = hashlib.new(digest_alg) digest_func.update(data_to_verify) calc_message_digest = digest_func.digest() if message_digest != calc_message_digest: raise IntegrityError( 'Failed to verify message signature: Message Digest does not match.' ) signed_data = signer['signed_attrs'].untag().dump() try: if sig_alg == 'rsassa_pkcs1v15': asymmetric.rsa_pkcs1v15_verify(verify_cert, sig, signed_data, digest_alg) elif sig_alg == 'rsassa_pss': asymmetric.rsa_pss_verify(verify_cert, sig, signed_data, digest_alg) else: raise AS2Exception('Unsupported Signature Algorithm') except Exception as e: raise IntegrityError( 'Failed to verify message signature: {}'.format(e)) else: raise IntegrityError('Signed data not found in ASN.1 ') return digest_alg
def verify(self, certificate, message, signature): """ :param expected_measurement: Hex-String of the expected measurement, e.g. "4ff505f350698c78e8b3b49b8e479146ce3896a06cd9e5109dfec8f393f14025" :param certificate: A byte string of the certificate :param message: A byte string of the data the signature is for :param signature: A byte string of the data the signature is for :raises: CertificateValidationError SignatureValidationError MessageParsingError ISVEnclaveQuoteStatusError EnclaveQuoteFlagsError MeasurementMismatchError :return: the QuoteBody inside the message """ # Step 1: validate certificate with Intel SGX attestation root CA try: validator = CertificateValidator(certificate, validation_context=self.context) validator.validate_usage(set(["digital_signature"])) except: raise CertificateValidationError # Step 2: verify message signature try: pubk = asymmetric.load_public_key(certificate) asymmetric.rsa_pkcs1v15_verify(pubk, signature, message, "sha256") except: raise SignatureValidationError # Step 3: parse message and get quote body try: message_dic = json.loads(message) quote_body_encoded = message_dic["isvEnclaveQuoteBody"] isv_enclave_quote_status = message_dic["isvEnclaveQuoteStatus"] quote_body = _QuoteBody.from_base64_string(quote_body_encoded) except: raise MessageParsingError # Step 4: check isv enclave quote status if isv_enclave_quote_status == "OK": pass elif isv_enclave_quote_status == "CONFIGURATION_NEEDED": if not self.accept_configuration_needed: raise ISVEnclaveQuoteStatusError elif isv_enclave_quote_status == "GROUP_OUT_OF_DATE": if not self.accept_group_out_of_date: raise ISVEnclaveQuoteStatusError else: raise ISVEnclaveQuoteStatusError # Step 5: check enclave quote flags flags = quote_body.flags if not (flags & Verification.IASAttributeFlags.INIT): raise EnclaveQuoteFlagsError if not (flags & Verification.IASAttributeFlags.MODE64BIT): raise EnclaveQuoteFlagsError if flags & Verification.IASAttributeFlags.DEBUG: if not self.accept_debug: raise EnclaveQuoteFlagsError # Step 6: assert expected measurement if self.expected_measurement: measurement = bytes(quote_body.mrenclave).hex() if not (measurement == self.expected_measurement): raise MeasurementMismatchError( f"Expected: {self.expected_measurement}. Actual: {measurement}." ) return QuoteBody(quote_body)
def main(): if len(sys.argv) < 2: sys.exit("Not enough arguments") path = sys.argv[1] # TODO Add flag to override detection based on filetype # Try to determine type based on extension file, ext = os.path.splitext(path) ext = ext.lower() if ext == '.roa': ext_class = ContentInfo elif ext == '.mft': ext_class = ContentInfo elif ext == '.crl': ext_class = CertificateList elif ext == '.cer': ext_class = RPKICertificate else: sys.exit("Unknown filetype: " + ext) # Read file try: file = open(path, "rb") der_byte_string = file.read() except Exception as e: sys.exit("Could not read file.\n" + str(e)) # Parse ASN.1 data using previously picked type try: parsed = ext_class.load(der_byte_string) except Exception as e: sys.exit("Could not parse file.\n" + str(e)) # TODO Sanity check of resulting data try: # Convert to readable JSON output data = parsed.native if not type(parsed) is ContentInfo: raise Exception("Invalid content type (not ROA or RPKI manifest)") # Verification according to RFC 5652 - Cryptographic Message Syntax (CMS) # Compute message digest hash_data = bytes(parsed['content']['encap_content_info']['content']) h = hashlib.sha256() h.update(hash_data) computed_digest = h.digest() # Get digest and content type from message message_digest = None message_content_type = None for attr in data['content']['signer_infos'][0]['signed_attrs']: if attr['type'] == 'message_digest': message_digest = attr['values'][0] elif attr['type'] == 'content_type': message_content_type = attr['values'][0] # Compare message content types if data['content']['encap_content_info']['content_type'] == message_content_type: print('Content types equal (' + message_content_type + ')') else: print('Content types not equal') # Compare computed digest with digest from message if computed_digest == message_digest: print('Digests equal') else: print('Digests not equal') # Get certificate and algorithm to be used for verification cert = asymmetric.load_certificate(parsed['content']['certificates'][0].chosen) hash_algo = 'sha256' # Rewrite signed attributes to EXPLICIT SET OF signed_data = SetOf(spec=CMSAttribute) for child in parsed['content']['signer_infos'][0]['signed_attrs'].children: signed_data.append(child) # Verify signature result = asymmetric.rsa_pkcs1v15_verify(cert, data['content']['signer_infos'][0]['signature'], signed_data.dump(), hash_algo) if result is None: print("Signature is valid") # TODO Verify included certificate is part of a valid chain except Exception as e: sys.exit("Something went wrong:\n" + str(e))
def on_get(self, req, resp): operation = req.get_param("operation", required=True) if operation == "GetCACert": resp.body = keys.parse_certificate( self.authority.certificate_buf).dump() resp.append_header("Content-Type", "application/x-x509-ca-cert") return elif operation == "GetCACaps": # TODO: return renewal flag based on renewal subnets config option resp.body = "Renewal\nMD5\nSHA-1\nSHA-256\nSHA-512\nDES3\n" return elif operation == "PKIOperation": pass else: raise falcon.HTTPBadRequest("Bad request", "Unknown operation %s" % operation) # If we bump into exceptions later encrypted_container = b"" attr_list = [ cms.CMSAttribute({ 'type': "message_type", 'values': ["3"] }), cms.CMSAttribute({ 'type': "pki_status", 'values': ["2"] # rejected }) ] try: info = cms.ContentInfo.load( b64decode(req.get_param("message", required=True))) ############################################### ### Verify signature of the outer container ### ############################################### signed_envelope = info['content'] encap_content_info = signed_envelope['encap_content_info'] encap_content = encap_content_info['content'] # TODO: try except current_certificate, = signed_envelope["certificates"] signer, = signed_envelope["signer_infos"] # TODO: compare cert to current one if we are renewing digest_algorithm = signer["digest_algorithm"]["algorithm"].native signature_algorithm = signer["signature_algorithm"][ "algorithm"].native if digest_algorithm not in ("md5", "sha1", "sha256", "sha512"): raise SCEPBadAlgo() if signature_algorithm != "rsassa_pkcs1v15": raise SCEPBadAlgo() message_digest = None transaction_id = None sender_nonce = None for attr in signer["signed_attrs"]: if attr["type"].native == "sender_nonce": sender_nonce, = attr["values"] elif attr["type"].native == "trans_id": transaction_id, = attr["values"] elif attr["type"].native == "message_digest": message_digest, = attr["values"] if getattr(hashlib, digest_algorithm)(encap_content.native).digest( ) != message_digest.native: raise SCEPDigestMismatch() if not sender_nonce: raise SCEPBadRequest() if not transaction_id: raise SCEPBadRequest() assert message_digest msg = signer["signed_attrs"].dump(force=True) assert msg[0] == 160 # Verify signature try: asymmetric.rsa_pkcs1v15_verify( asymmetric.load_certificate(current_certificate.dump()), signer["signature"].native, b"\x31" + msg[1:], # wtf?! "md5") except SignatureError: raise SCEPSignatureMismatch() ############################### ### Decrypt inner container ### ############################### info = cms.ContentInfo.load(encap_content.native) encrypted_envelope = info['content'] encrypted_content_info = encrypted_envelope[ 'encrypted_content_info'] iv = encrypted_content_info['content_encryption_algorithm'][ 'parameters'].native if encrypted_content_info['content_encryption_algorithm'][ "algorithm"].native != "des": raise SCEPBadAlgo() encrypted_content = encrypted_content_info[ 'encrypted_content'].native recipient, = encrypted_envelope['recipient_infos'] if recipient.native["rid"][ "serial_number"] != self.authority.certificate.serial_number: raise SCEPBadCertId() key = asymmetric.rsa_pkcs1v15_decrypt( self.authority.private_key, recipient.native["encrypted_key"]) if len(key) == 8: key = key * 3 # Convert DES to 3DES buf = symmetric.tripledes_cbc_pkcs5_decrypt( key, encrypted_content, iv) _, _, common_name = self.authority.store_request(buf, overwrite=True) logger.info( "SCEP client from %s requested with %s digest algorithm, %s signature", req.context["remote_addr"], digest_algorithm, signature_algorithm) cert, buf = self.authority.sign(common_name, profile=config.PROFILES["gw"], overwrite=True) signed_certificate = asymmetric.load_certificate(buf) content = signed_certificate.asn1.dump() except SCEPError as e: attr_list.append( cms.CMSAttribute({ 'type': "fail_info", 'values': ["%d" % e.code] })) logger.info("Failed to sign SCEP request due to: %s" % e.explaination) else: ################################## ### Degenerate inner container ### ################################## degenerate = cms.ContentInfo({ 'content_type': "signed_data", 'content': cms.SignedData({ 'version': "v1", 'certificates': [signed_certificate.asn1], 'digest_algorithms': [cms.DigestAlgorithm({'algorithm': digest_algorithm})], 'encap_content_info': { 'content_type': "data", 'content': cms.ContentInfo({ 'content_type': "signed_data", 'content': None }).dump() }, 'signer_infos': [] }) }) ################################ ### Encrypt middle container ### ################################ key = os.urandom(8) iv, encrypted_content = symmetric.des_cbc_pkcs5_encrypt( key, degenerate.dump(), os.urandom(8)) assert degenerate.dump() == symmetric.tripledes_cbc_pkcs5_decrypt( key * 3, encrypted_content, iv) ri = cms.RecipientInfo({ 'ktri': cms.KeyTransRecipientInfo({ 'version': "v0", 'rid': cms.RecipientIdentifier({ 'issuer_and_serial_number': cms.IssuerAndSerialNumber({ 'issuer': current_certificate.chosen["tbs_certificate"] ["issuer"], 'serial_number': current_certificate.chosen["tbs_certificate"] ["serial_number"], }), }), 'key_encryption_algorithm': { 'algorithm': "rsa" }, 'encrypted_key': asymmetric.rsa_pkcs1v15_encrypt( asymmetric.load_certificate( current_certificate.chosen.dump()), key) }) }) encrypted_container = cms.ContentInfo({ 'content_type': "enveloped_data", 'content': cms.EnvelopedData({ 'version': "v1", 'recipient_infos': [ri], 'encrypted_content_info': { 'content_type': "data", 'content_encryption_algorithm': { 'algorithm': "des", 'parameters': iv }, 'encrypted_content': encrypted_content } }) }).dump() attr_list = [ cms.CMSAttribute({ 'type': "message_digest", 'values': [ getattr( hashlib, digest_algorithm)(encrypted_container).digest() ] }), cms.CMSAttribute({ 'type': "message_type", 'values': ["3"] }), cms.CMSAttribute({ 'type': "pki_status", 'values': ["0"] # ok }) ] finally: ############################## ### Signed outer container ### ############################## attrs = cms.CMSAttributes(attr_list + [ cms.CMSAttribute({ 'type': "recipient_nonce", 'values': [sender_nonce] }), cms.CMSAttribute({ 'type': "trans_id", 'values': [transaction_id] }) ]) signer = cms.SignerInfo({ "signed_attrs": attrs, 'version': "v1", 'sid': cms.SignerIdentifier({ 'issuer_and_serial_number': cms.IssuerAndSerialNumber({ 'issuer': self.authority.certificate.issuer, 'serial_number': self.authority.certificate.serial_number, }), }), 'digest_algorithm': algos.DigestAlgorithm({'algorithm': digest_algorithm}), 'signature_algorithm': algos.SignedDigestAlgorithm({'algorithm': "rsassa_pkcs1v15"}), 'signature': asymmetric.rsa_pkcs1v15_sign(self.authority.private_key, b"\x31" + attrs.dump()[1:], digest_algorithm) }) resp.append_header("Content-Type", "application/x-pki-message") resp.body = cms.ContentInfo({ 'content_type': "signed_data", 'content': cms.SignedData({ 'version': "v1", 'certificates': [self.authority.certificate], 'digest_algorithms': [cms.DigestAlgorithm({'algorithm': digest_algorithm})], 'encap_content_info': { 'content_type': "data", 'content': encrypted_container }, 'signer_infos': [signer] }) }).dump()
def validate_ocsp_response(cert, issuer, ocsp_request_obj, ocsp_response_objs, current_time): #print(cert.ocsp_urls) # Just to see how many urls there are subject = cert['tbs_certificate']['subject'].native errors = {} warnings = {} lints_list = [] count = 0 lints = {} for (ocsp_url, ocsp_response_obj) in ocsp_response_objs: count += 1 lints['domain'] = subject['common_name'] lints['ocsp_url'] = ocsp_url lints['response_count'] = count #print(ocsp_response_obj.native) errors = {} warnings = {} if isinstance(ocsp_response_obj, urllib.error.HTTPError): errors['HTTPError'] = str(ocsp_response_obj) lints['errors'] = errors lints['warnings'] = warnings lints_list.append(lints) return lints_list if isinstance(ocsp_response_obj, ValueError): errors['ValueError'] = str(ocsp_response_obj) lints['errors'] = errors lints['warnings'] = warnings lints_list.append(lints) return lints_list if (ocsp_response_obj['response_status'].native == 'unauthorized'): errors['Unauthorized'] = 'Responder returned unauthorized' lints['errors'] = errors lints['warnings'] = warnings lints_list.append(lints) continue if (ocsp_response_obj['response_status'].native == 'malformed_request' ): errors['ResponseFailure'] = 'Responder returned malformed request' lints['errors'] = errors lints['warnings'] = warnings lints_list.append(lints) continue request_nonce = ocsp_request_obj.nonce_value #print (ocsp_response_obj.native) response_nonce = ocsp_response_obj.nonce_value if request_nonce and response_nonce and request_nonce.native != response_nonce.native: errors[ 'NonceVerificationFailure'] = 'Unable to verify OCSP response since the request and response nonces do not match' if ocsp_response_obj['response_status'].native != 'successful': errors['OCSPCheckFailure'] = 'OCSP check returned as failed' response_bytes = ocsp_response_obj['response_bytes'] if response_bytes['response_type'].native != 'basic_ocsp_response': errors[ 'ResponseTypeFailure'] = 'OCSP response is not Basic OCSP Response' parsed_response = response_bytes['response'].parsed tbs_response = parsed_response['tbs_response_data'] certificate_response = tbs_response['responses'][0] certificate_id = certificate_response['cert_id'] algo = certificate_id['hash_algorithm']['algorithm'].native certificate_issuer_name_hash = certificate_id[ 'issuer_name_hash'].native certificate_issuer_key_hash = certificate_id['issuer_key_hash'].native certificate_serial_number = certificate_id['serial_number'].native certificate_issuer_name_hash_from_file = getattr(cert.issuer, algo) certificate_issuer_key_hash_from_file = getattr( issuer.public_key, algo) certificate_serial_number_from_file = cert.serial_number if certificate_serial_number != certificate_serial_number_from_file: errors['CertificateSerialMismatchFailure'] = \ 'OCSP response certificate serial number does not match request certificate serial number' if certificate_issuer_key_hash != certificate_issuer_key_hash_from_file: errors[ 'IssuerKeyMismatchFailure'] = 'OCSP response issuer key hash does not match request certificate issuer key hash' if certificate_issuer_name_hash != certificate_issuer_name_hash_from_file: errors['IssuerNameHashMismatchFailure'] = \ 'OCSP response issuer name hash does not match request certificate issuer name hash' this_update_time = certificate_response['this_update'].native if current_time < this_update_time: errors[ 'ThisUpdateTimeError'] = 'OCSP reponse update time is from the future' if "next_update" not in certificate_response or certificate_response[ 'next_update'].native is None: warnings[ 'NextUpdateTimeMissing'] = 'OCSP response does not contain next update time' else: next_update_time = certificate_response['next_update'].native if current_time > next_update_time: errors[ 'NextUpdateTimeFailure'] = 'OCSP reponse next update time is in the past' registry = CertificateRegistry(trust_roots=[issuer]) if tbs_response['responder_id'].name == 'by_key': key_identifier = tbs_response['responder_id'].native signing_cert = registry.retrieve_by_key_identifier(key_identifier) elif tbs_response['responder_id'].name == 'by_name': signing_certs = registry.retrieve_by_name( tbs_response['responder_id'].chosen, None) if signing_certs is not None and len(signing_certs) > 0: signing_cert = signing_certs[0] else: signing_cert = None if signing_cert is None: errors[ 'SigningCetificateNotFoundFailure'] = 'OCSP response signing certificate not found' lints['errors'] = errors lints['warnings'] = warnings lints_list.append(lints) continue if issuer.issuer_serial != signing_cert.issuer_serial: if signing_cert_issuer.issuer_serial != issuer.issuer_serial: errors[ 'UnauthorizedSigningCertificateFailure'] = 'OCSP response signed by unauthorized certificate' extended_key_usage = signing_cert.extended_key_usage_value if 'ocsp_signing' not in extended_key_usage.native: errors['ExtendedKeyUsageExtensionValueFailure'] = \ 'OCSP response signing certificate is not the issuing certificate and it does not have value "ocsp_signing"\ for the extended key usage extension' sig_algo = parsed_response['signature_algorithm'].signature_algo hash_algo = parsed_response['signature_algorithm'].hash_algo try: check_cert = asymmetric.load_certificate(signing_cert) if sig_algo == 'rsassa_pkcs1v15': asymmetric.rsa_pkcs1v15_verify( check_cert, parsed_response['signature'].native, tbs_response.dump(), hash_algo) elif sig_algo == 'dsa': asymmetric.dsa_verify(check_cert, parsed_response['signature'].native, tbs_response.dump(), hash_algo) elif sig_algo == 'ecdsa': asymmetric.ecdsa_verify(check_cert, parsed_response['signature'].native, tbs_response.dump(), hash_algo) else: errors[ 'UnsupportedAlgorithmFailure'] = 'OCSP response signature uses unsupported algorithm' except (oscrypto.errors.SignatureError): errors[ 'SignatureVerificationFailure'] = 'OCSP response signature could not be verified' if certificate_response['cert_status'].name == 'revoked': revocation_data = certificate_response['cert_status'].chosen if revocation_data['revocation_reason'].native is None: errors[ 'CertificateValidityFailure'] = 'Certificate revoked due to unknown reason' else: errors[ 'CertificateValidityFailure'] = 'Certicate revoked due to ' + revocation_data[ 'revocation_reason'].human_friendly if 'certs' in parsed_response and parsed_response[ 'certs'].native != None: #TODO Check for legit certs pass # print(parsed_response['certs'].native) if len(errors) == 0: errors['NoFailure'] = 'No errors in OCSP response' if len(warnings) == 0: warnings['NoWarning'] = 'No warnings in OCSP response' lints['errors'] = errors lints['warnings'] = warnings lints_list.append(lints) return lints_list