class APNDevice(db.Model): """ An APN device to send Push Notifications to. """ __tablename__ = 'apn_devices' __table_args__ = (db.UniqueConstraint('device_id', 'provider'), { 'extend_existing': True }) id = db.Column(db.Integer, primary_key=True, autoincrement=True) uuid = db.Column(db.String(36), unique=True, nullable=False, default=lambda: str(UUID(bytes=rand_bytes(16)))) device_id = db.Column(db.String(64), nullable=True) provider = db.Column(db.Enum(APNProvider), nullable=False) user_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False) user = db.relationship('User', backref='apn_devices', lazy=True) def __repr__(self): return "<APNDevice {} for {}>".format(self.device_id, self.provider.name)
def rand_bytes(num): # type: (int) -> bytes """ Return n cryptographically strong pseudo-random bytes. An error occurs if the PRNG has not been seeded with enough randomness to ensure an unpredictable byte sequence. :param num: number of bytes to be returned :return: random bytes """ return m2.rand_bytes(num) # pylint: disable=no-member
def signin(self): # Set the label early if we're about to import the rest of Digsby, # because it may take awhile with a cold cache. my_id = identity(self._get_window_username()) my_id.password = self.window.GetPassword() hooks.first('digsby.identity.activate', my_id, raise_hook_exceptions = True) if not 'digsbyprofile' in sys.modules: sys.util_allowed = True self.set_status(_('Loading...')) self.window.EnableControls(False, SIGN_IN, False) self.window.Update() from M2Crypto import m2 #yeah, we're actually Loading... m2.rand_bytes(16) #just in case this chunk of dll isn't in memory, preload import digsbyprofile if (digsbyprofile.profile and digsbyprofile.profile.is_connected): self.window.EnableControls(True, SIGN_IN, True) self.disconnect_prof() self.cancelling = True else: self.login()
def generate_temporary_id(): """ For an authorized user. This generates a temporary (5 min lifetime) that identifies the user. This """ webapn_id = str(UUID(bytes=rand_bytes(16))) redis_key = pn_redis_id_prefix + webapn_id redis_db.set(redis_key, g.user.id) redis_db.expire(redis_key, pn_redis_id_time) return webapn_id
def signin(self): # Set the label early if we're about to import the rest of Digsby, # because it may take awhile with a cold cache. my_id = identity(self._get_window_username()) my_id.password = self.window.GetPassword() hooks.first('digsby.identity.activate', my_id, raise_hook_exceptions=True) if not 'digsbyprofile' in sys.modules: sys.util_allowed = True self.set_status(_('Loading...')) self.window.EnableControls(False, SIGN_IN, False) self.window.Update() from M2Crypto import m2 #yeah, we're actually Loading... m2.rand_bytes( 16) #just in case this chunk of dll isn't in memory, preload import digsbyprofile if (digsbyprofile.profile and digsbyprofile.profile.is_connected): self.window.EnableControls(True, SIGN_IN, True) self.disconnect_prof() self.cancelling = True else: self.login()
def rand_bytes(num): # type: (int) -> bytes """ Return n cryptographically strong pseudo-random bytes. An error occurs if the PRNG has not been seeded with enough randomness to ensure an unpredictable byte sequence. :param num: number of bytes to be returned :return: random bytes """ out = m2.rand_bytes(num) # pylint: disable=no-member if out is None: raise ValueError('Not enough randomness.') elif out == -1: raise ValueError('Not supported by the current RAND method.') else: return out
def setup_csrf(): if csrf_token_name not in session: session[csrf_token_name] = str(UUID(bytes=rand_bytes(16)))
def generate_sid(self, size=16): return base64.b64encode(m2.rand_bytes(size))
def encrypt_payload(message, client_pub_key, auth): """ This is quite a bit but for more information I wrote a blog post https://blog.vihan.org/the-push-protocol/ Brief overview is we take our private and client public key which are ECC keys on the P-256 NIST curve and we perform a diffie-hellman (ECDH) key exchange to obtain a shared secret to generate a HKDF key which is derived with various content types to obtain the encryption parameters for an AES-128-GCM cipher. :param bytes message: Binary message :param bytes client_pub_key: :param bytes auth: """ # const salt = crypto.randomBytes(16); salt = rand_bytes(16) # const localKeysCurve = crypto.createECDH('prime256v1'); # localKeysCurve.generateKeys(); # const localPrivateKey = localKeysCurve.getPrivateKey(); local_private_key = ec.generate_private_key(ec.SECP256R1, default_backend()) # const localPublicKey = localKeysCurve.getPublicKey(); local_public_key = local_private_key.public_key() local_public_key_bytes = local_public_key.public_numbers().encode_point() # const sharedSecret = localKeysCurve.computeSecret(subscription.keys.p256dh, 'base64') shared_secret = local_private_key.exchange( ec.ECDH(), ec.EllipticCurvePublicNumbers.from_encoded_point( ec.SECP256R1(), client_pub_key).public_key(default_backend())) # const authEncBuff = new Buffer('Content-Encoding: auth\0', 'utf8'); # const prk = hkdf(subscription.keys.auth, sharedSecret, authEncBuff, 32); # salt ikm info length prk = HKDF(algorithm=hashes.SHA256(), length=32, salt=auth, info=b'Content-Encoding: auth\0', backend=default_backend()).derive(shared_secret) server_pub_key = get_public_key() # This is context which happens to be appended to end # of all the things context = b'P-256\0' + pack('!H', len(client_pub_key)) + client_pub_key +\ pack('!H', len(local_public_key_bytes)) + local_public_key_bytes # This is something that cipher needs idk im not # crypto genius nonce = HKDF(algorithm=hashes.SHA256(), length=12, salt=salt, info=b'Content-Encoding: nonce\0' + context, backend=default_backend()).derive(prk) # CEK = Content Encryption Key # this goes into AES cek = HKDF(algorithm=hashes.SHA256(), length=16, salt=salt, info=b'Content-Encoding: aesgcm\0' + context, backend=default_backend()).derive(prk) cipher = Cipher(algorithms.AES(cek), modes.GCM(nonce), default_backend()) encryptor = cipher.encryptor() payload = pack('!H', 0) + message encrypted_payload = encryptor.update( payload) + encryptor.finalize() + encryptor.tag return encrypted_payload, local_public_key_bytes, salt
def challenge(self, challenge): if self.password is None: self.password, pformat = self.password_manager.get_password( self.username) if not self.password or pformat != "plain": self.__logger.debug("Couldn't retrieve plain password") return Failure("password-unavailable") if self.step == 0: self.step = 1 return Response(''.join( pstrIlist([to_utf8(self.authzid), to_utf8(self.username)]))) elif self.step == 1: self.step = 2 srv_rsa_key = None self.__logger.critical("loading server certificate") try: srv_cert = M2Crypto.X509.load_cert_string(challenge) if srv_cert is not None: self.__logger.critical("retrieving server pubkey") srv_key = srv_cert.get_pubkey() if srv_key is not None: self.__logger.critical("retrieving server RSA pubkey") srv_rsa_key = srv_key.get_rsa() except Exception: traceback.print_exc() if srv_rsa_key is None: return Failure("bad-server-cert") if not srv_cert.verify(ROOT_CERT.get_pubkey()): return Failure("bad-server-cert") self.srv_rsa_key = srv_rsa_key self.__logger.critical("generating Nonce") from M2Crypto import m2 nonce = m2.rand_bytes(16) self.__logger.critical("encrypting Nonce") enonce = digsbyrsa.DIGSBY_RSA_public_encrypt( nonce, srv_rsa_key, M2Crypto.RSA.pkcs1_oaep_padding) self.__logger.critical("loading key") try: self.key = _get_client_key_pem(self.username, self.password) except Exception: self.key = None if self.key is None: self.__logger.critical("generating new key") self.key = M2Crypto.RSA.gen_key(2048, 0x10001) self.__logger.critical("saving new key") if not self.key.check_key(): raise ValueError("failed to generate key") try: _save_client_key_pem(self.key, self.username, self.password) except Exception: traceback.print_exc() self.__logger.critical("creating buffer") buff = M2Crypto.BIO.MemoryBuffer() self.__logger.critical("serializing client public key to buffer") self.key.save_pub_key_bio(buff) self.__logger.critical_s("Nonce: %r", nonce) genkey = buff.getvalue() self.__logger.critical_s("Key: %r", genkey) eKey = pstrAES(nonce, genkey) self.__logger.critical("returning response") return Response(''.join(pstrIlist([enonce, eKey]))) elif self.step == 2: self.step = 3 package_nonce_C_userpub, package_C_AES_C_serverpriv = unpackpstrlist( challenge) package_nonce = digsbyrsa.DIGSBY_RSA_private_decrypt( package_nonce_C_userpub, self.key, M2Crypto.RSA.pkcs1_oaep_padding) package_C_serverpriv = util.cryptography.decrypt( package_nonce, package_C_AES_C_serverpriv, padchar=None, mode=util.cryptography.Mode.CBC) nonce = digsbyrsa.DIGSBY_RSA_public_decrypt( package_C_serverpriv, self.srv_rsa_key, M2Crypto.RSA.pkcs1_padding) return Response(pstrAES(nonce, self.password)) else: return Failure("extra-challenge")
def challenge(self, challenge): if self.password is None: self.password,pformat=self.password_manager.get_password(self.username) if not self.password or pformat!="plain": self.__logger.debug("Couldn't retrieve plain password") return Failure("password-unavailable") if self.step == 0: self.step = 1 return Response(''.join(pstrIlist([ to_utf8(self.authzid), to_utf8(self.username)]))) elif self.step == 1: self.step = 2 srv_rsa_key = None self.__logger.critical("loading server certificate") try: srv_cert = M2Crypto.X509.load_cert_string(challenge) if srv_cert is not None: self.__logger.critical("retrieving server pubkey") srv_key = srv_cert.get_pubkey() if srv_key is not None: self.__logger.critical("retrieving server RSA pubkey") srv_rsa_key = srv_key.get_rsa() except Exception: traceback.print_exc() if srv_rsa_key is None: return Failure("bad-server-cert") if not srv_cert.verify(ROOT_CERT.get_pubkey()): return Failure("bad-server-cert") self.srv_rsa_key = srv_rsa_key self.__logger.critical("generating Nonce") from M2Crypto import m2 nonce = m2.rand_bytes(16) self.__logger.critical("encrypting Nonce") enonce = digsbyrsa.DIGSBY_RSA_public_encrypt(nonce, srv_rsa_key, M2Crypto.RSA.pkcs1_oaep_padding) self.__logger.critical("loading key") try: self.key = _get_client_key_pem(self.username, self.password) except Exception: self.key = None if self.key is None: self.__logger.critical("generating new key") self.key = M2Crypto.RSA.gen_key(2048, 0x10001) self.__logger.critical("saving new key") if not self.key.check_key(): raise ValueError("failed to generate key") try: _save_client_key_pem(self.key, self.username, self.password) except Exception: traceback.print_exc() self.__logger.critical("creating buffer") buff = M2Crypto.BIO.MemoryBuffer() self.__logger.critical("serializing client public key to buffer") self.key.save_pub_key_bio(buff) self.__logger.critical_s("Nonce: %r", nonce) genkey = buff.getvalue() self.__logger.critical_s("Key: %r", genkey) eKey = pstrAES(nonce, genkey) self.__logger.critical("returning response") return Response(''.join(pstrIlist([ enonce, eKey ]))) elif self.step == 2: self.step = 3 package_nonce_C_userpub, package_C_AES_C_serverpriv = unpackpstrlist(challenge) package_nonce = digsbyrsa.DIGSBY_RSA_private_decrypt(package_nonce_C_userpub, self.key, M2Crypto.RSA.pkcs1_oaep_padding) package_C_serverpriv = util.cryptography.decrypt(package_nonce, package_C_AES_C_serverpriv, padchar=None, mode = util.cryptography.Mode.CBC) nonce = digsbyrsa.DIGSBY_RSA_public_decrypt(package_C_serverpriv, self.srv_rsa_key, M2Crypto.RSA.pkcs1_padding) return Response(pstrAES(nonce, self.password)) else: return Failure("extra-challenge")
def nonce(len_=16): return m2.rand_bytes(len_)
class Notification(db.Model): """ Represents a notification sent to a user """ __tablename__ = 'notifications' __table_args__ = {'extend_existing': True} id = db.Column(db.Integer, primary_key=True, autoincrement=True) uuid = db.Column(db.String(36), unique=True, nullable=False, default=lambda: str(UUID(bytes=rand_bytes(16)))) recipient_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False) recipient = db.relationship('User', foreign_keys=[recipient_id], backref='notifications', lazy='joined') sender_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False) sender = db.relationship('User', foreign_keys=[sender_id], backref='sent_notifications', lazy='joined') # The type of notification see the NotificationType enum class notification_type = db.Column(db.Enum(NotificationType), nullable=False) # An ID referencing the item which dispatches the notification target_id = db.Column(db.Integer, nullable=True) # The id of the subscription source source_id = db.Column(db.Integer, nullable=True) date_created = db.Column(db.DateTime, default=datetime.datetime.now) read = db.Column(db.Enum(NotificationStatus), default=NotificationStatus.UNSEEN) def get_target_descriptor(self): """ Returns the 'category' of the type's associated payload. This is used in the responder router """ return { NotificationType.STATUS_UPDATE: 'status', NotificationType.NEW_ANSWER: 'answer', NotificationType.OUTGOLFED: 'answer', NotificationType.NEW_POST_COMMENT: 'post_comment', NotificationType.NEW_ANSWER_COMMENT: 'answer_comment', NotificationType.ANSWER_VOTE: 'answer', NotificationType.POST_VOTE: 'answer' }[self.notification_type] def get_title(self): # TODO: more descriptive titles return { NotificationType.STATUS_UPDATE: lambda: "Status Update", NotificationType.NEW_ANSWER: lambda: get_title.new_answer(self), NotificationType.OUTGOLFED: lambda: get_title.outgolfed(self), NotificationType.NEW_POST_COMMENT: lambda: get_title.post_comment(self), NotificationType.NEW_ANSWER_COMMENT: lambda: get_title.answer_comment(self), NotificationType.ANSWER_VOTE: lambda: "Someone voted on your answer", NotificationType.POST_VOTE: lambda: "Someone voted on your post" }[self.notification_type]() def get_body(self): # TODO: more descriptive bodies return { NotificationType.STATUS_UPDATE: lambda: "You're received a brand new status update.", NotificationType.NEW_ANSWER: lambda: get_body.new_answer(self), NotificationType.OUTGOLFED: lambda: get_body.outgolfed(self), NotificationType.NEW_POST_COMMENT: lambda: "A new comment has been posted on your challenge.", NotificationType.NEW_ANSWER_COMMENT: lambda: "A new comment has been posted on your answer.", NotificationType.ANSWER_VOTE: lambda: "A new vote has come upon your answer.", NotificationType.POST_VOTE: lambda: "A new vote has come upon your challenge." }[self.notification_type]() def get_plural(self): """ Gets plural of notification type """ return { NotificationType.STATUS_UPDATE: lambda: "updates", NotificationType.NEW_ANSWER: lambda: "new answers", NotificationType.OUTGOLFED: lambda: "outgolfs", NotificationType.NEW_POST_COMMENT: lambda: "new comments", NotificationType.NEW_ANSWER_COMMENT: lambda: "new comments", NotificationType.ANSWER_VOTE: lambda: "votes", NotificationType.POST_VOTE: lambda: "votes" }[self.notification_type]() def is_overwriting(self): """ Gets if this notification type should be grouped and have older instances discarded. """ return { NotificationType.STATUS_UPDATE: False, NotificationType.NEW_ANSWER: False, NotificationType.OUTGOLFED: True, NotificationType.NEW_POST_COMMENT: False, NotificationType.NEW_ANSWER_COMMENT: False, NotificationType.ANSWER_VOTE: True, NotificationType.POST_VOTE: True }[self.notification_type] def to_apns_json(self): """ Returns APNS compliant JSON payload """ target = str(self.target_id) if self.target_id is not None else '_' return { 'aps': { 'alert': { 'title': self.get_title(), 'body': self.get_body(), 'action': 'View' }, 'url-args': [self.uuid, self.get_target_descriptor(), target] } } def to_push_json(self): return { 'id': self.uuid, 'title': self.get_title(), 'body': self.get_body(), 'category': self.get_target_descriptor(), 'source': self.source_id, 'target': self.target_id, 'overwriting': self.is_overwriting(), 'date': self.date_created.isoformat() + 'Z' } def to_json(self): return { 'id': self.uuid, 'title': self.get_title(), 'body': self.get_body(), 'plural': self.get_plural(), 'recipient': self.recipient.to_json(), 'source_id': self.source_id, 'sender': self.sender.to_json(), 'target_id': self.target_id, 'category': self.get_target_descriptor(), 'date_created': self.date_created.isoformat() + 'Z', 'type': self.notification_type.value, 'status': self.read.value } def __repr__(self): return "<Notification about {} for {}>".format( self.notification_type.name, self.recipient.name)