def test_rsa_pss_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_pss_signature'), 'rb') as f: signature = f.read() public = asymmetric.load_public_key(os.path.join(fixtures_dir, 'keys/test.crt')) asymmetric.rsa_pss_verify(public, signature, original_data, 'sha1')
def test_rsa_pss_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_pss_signature'), '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_pss_verify(public, signature, original_data + b'1', 'sha1')
def test_rsa_pss_sha256_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_pss_sign(private, original_data, 'sha256') self.assertIsInstance(signature, byte_cls) asymmetric.rsa_pss_verify(public, signature, original_data, 'sha256')
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 on_post(self, req, resp): """ Validate and parse certificate signing request, the RESTful way """ reasons = [] body = req.stream.read(req.content_length).encode("ascii") header, _, der_bytes = pem.unarmor(body) csr = CertificationRequest.load(der_bytes) common_name = csr["certification_request_info"]["subject"].native["common_name"] """ Handle domain computer automatic enrollment """ machine = req.context.get("machine") if machine: if config.MACHINE_ENROLLMENT_ALLOWED: if common_name != machine: raise falcon.HTTPBadRequest( "Bad request", "Common name %s differs from Kerberos credential %s!" % (common_name, machine)) # Automatic enroll with Kerberos machine cerdentials resp.set_header("Content-Type", "application/x-pem-file") cert, resp.body = authority._sign(csr, body, overwrite=True) logger.info(u"Automatically enrolled Kerberos authenticated machine %s from %s", machine, req.context.get("remote_addr")) return else: reasons.append("Machine enrollment not allowed") """ Attempt to renew certificate using currently valid key pair """ try: path, buf, cert = authority.get_signed(common_name) except EnvironmentError: pass # No currently valid certificate for this common name else: cert_pk = cert["tbs_certificate"]["subject_public_key_info"].native csr_pk = csr["certification_request_info"]["subject_pk_info"].native if cert_pk == csr_pk: # Same public key, assume renewal expires = cert["tbs_certificate"]["validity"]["not_after"].native.replace(tzinfo=None) renewal_header = req.get_header("X-Renewal-Signature") if not renewal_header: # No header supplied, redirect to signed API call resp.status = falcon.HTTP_SEE_OTHER resp.location = os.path.join(os.path.dirname(req.relative_uri), "signed", common_name) return try: renewal_signature = b64decode(renewal_header) except TypeError, ValueError: logger.error(u"Renewal failed, bad signature supplied for %s", common_name) reasons.append("Renewal failed, bad signature supplied") else: try: asymmetric.rsa_pss_verify( asymmetric.load_certificate(cert), renewal_signature, buf + body, "sha512") except SignatureError: logger.error(u"Renewal failed, invalid signature supplied for %s", common_name) reasons.append("Renewal failed, invalid signature supplied") else: # At this point renewal signature was valid but we need to perform some extra checks if datetime.utcnow() > expires: logger.error(u"Renewal failed, current certificate for %s has expired", common_name) reasons.append("Renewal failed, current certificate expired") elif not config.CERTIFICATE_RENEWAL_ALLOWED: logger.error(u"Renewal requested for %s, but not allowed by authority settings", common_name) reasons.append("Renewal requested, but not allowed by authority settings") else: resp.set_header("Content-Type", "application/x-x509-user-cert") _, resp.body = authority._sign(csr, body, overwrite=True) logger.info(u"Renewed certificate for %s", common_name) return
def on_post(self, req, resp): """ Validate and parse certificate signing request, the RESTful way """ reasons = [] body = req.stream.read(req.content_length) try: header, _, der_bytes = pem.unarmor(body) csr = CertificationRequest.load(der_bytes) except ValueError: raise falcon.HTTPBadRequest( "Bad request", "Malformed certificate signing request") common_name = csr["certification_request_info"]["subject"].native[ "common_name"] """ Handle domain computer automatic enrollment """ machine = req.context.get("machine") if machine: if config.MACHINE_ENROLLMENT_ALLOWED: if common_name != machine: raise falcon.HTTPBadRequest( "Bad request", "Common name %s differs from Kerberos credential %s!" % (common_name, machine)) # Automatic enroll with Kerberos machine cerdentials resp.set_header("Content-Type", "application/x-pem-file") cert, resp.body = self.authority._sign(csr, body, overwrite=True) logger.info( "Automatically enrolled Kerberos authenticated machine %s from %s", machine, req.context.get("remote_addr")) return else: reasons.append("Machine enrollment not allowed") """ Attempt to renew certificate using currently valid key pair """ try: path, buf, cert, signed, expires = self.authority.get_signed( common_name) except EnvironmentError: pass # No currently valid certificate for this common name else: cert_pk = cert["tbs_certificate"]["subject_public_key_info"].native csr_pk = csr["certification_request_info"][ "subject_pk_info"].native if cert_pk == csr_pk: # Same public key, assume renewal expires = cert["tbs_certificate"]["validity"][ "not_after"].native.replace(tzinfo=None) renewal_header = req.get_header("X-Renewal-Signature") if not renewal_header: # No header supplied, redirect to signed API call resp.status = falcon.HTTP_SEE_OTHER resp.location = os.path.join( os.path.dirname(req.relative_uri), "signed", common_name) return try: renewal_signature = b64decode(renewal_header) except (TypeError, ValueError): logger.error( "Renewal failed, bad signature supplied for %s", common_name) reasons.append("Renewal failed, bad signature supplied") else: try: asymmetric.rsa_pss_verify( asymmetric.load_certificate(cert), renewal_signature, buf + body, "sha512") except SignatureError: logger.error( "Renewal failed, invalid signature supplied for %s", common_name) reasons.append( "Renewal failed, invalid signature supplied") else: # At this point renewal signature was valid but we need to perform some extra checks if datetime.utcnow() > expires: logger.error( "Renewal failed, current certificate for %s has expired", common_name) reasons.append( "Renewal failed, current certificate expired") elif not config.CERTIFICATE_RENEWAL_ALLOWED: logger.error( "Renewal requested for %s, but not allowed by authority settings", common_name) reasons.append( "Renewal requested, but not allowed by authority settings" ) else: resp.set_header("Content-Type", "application/x-x509-user-cert") _, resp.body = self.authority._sign(csr, body, overwrite=True) logger.info("Renewed certificate for %s", common_name) return """ Process automatic signing if the IP address is whitelisted, autosigning was requested and certificate can be automatically signed """ if req.get_param_as_bool("autosign"): if not self.authority.server_flags(common_name): for subnet in config.AUTOSIGN_SUBNETS: if req.context.get("remote_addr") in subnet: try: resp.set_header("Content-Type", "application/x-pem-file") _, resp.body = self.authority._sign(csr, body) logger.info("Autosigned %s as %s is whitelisted", common_name, req.context.get("remote_addr")) return except EnvironmentError: logger.info( "Autosign for %s from %s failed, signed certificate already exists", common_name, req.context.get("remote_addr")) reasons.append( "Autosign failed, signed certificate already exists" ) break else: reasons.append( "Autosign failed, IP address not whitelisted") else: reasons.append( "Autosign failed, only client certificates allowed to be signed automatically" ) # Attempt to save the request otherwise try: request_path, _, _ = self.authority.store_request( body, address=str(req.context.get("remote_addr"))) except errors.RequestExists: reasons.append("Same request already uploaded exists") # We should still redirect client to long poll URL below except errors.DuplicateCommonNameError: # TODO: Certificate renewal logger.warning( "Rejected signing request with overlapping common name from %s", req.context.get("remote_addr")) raise falcon.HTTPConflict( "CSR with such CN already exists", "Will not overwrite existing certificate signing request, explicitly delete CSR and try again" ) else: push.publish("request-submitted", common_name) # Wait the certificate to be signed if waiting is requested logger.info("Stored signing request %s from %s", common_name, req.context.get("remote_addr")) if req.get_param("wait"): # Redirect to nginx pub/sub url = config.LONG_POLL_SUBSCRIBE % hashlib.sha256(body).hexdigest() click.echo("Redirecting to: %s" % url) resp.status = falcon.HTTP_SEE_OTHER resp.set_header("Location", url) logger.debug("Redirecting signing request from %s to %s", req.context.get("remote_addr"), url) else: # Request was accepted, but not processed resp.status = falcon.HTTP_202 resp.body = ". ".join(reasons) if req.client_accepts("application/json"): resp.body = json.dumps( { "title": "Accepted", "description": resp.body }, cls=MyEncoder)