示例#1
0
def cancel(cancelKeyb58):
    # convert url end to bytes, extract uuid (bytes 0-16) and cancel key (rest)
    cancelKeyBytes = En(cancelKeyb58)._by58()
    uuid, cancelKey = authso.AuthSo.ioUUID(
        cancelKeyBytes[0:16]), cancelKeyBytes[16:]

    # ensure uuid is valid - avoid SQL injections
    if not re.fullmatch(
            r'^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$',
            uuid):
        return {'success': False}

    # SQL obj to fetch user dict
    gsql = authso.GSQLBridge()
    query = gsql.SQL.fetch(f"SELECT nextkeys FROM users WHERE uuid = '{uuid}'")
    if True if not query else not query[0][0]:
        return {'success': False}

    # extract from 'nextkeys' the hashed cancelKey; seq of nextkeys is nix ts (4 bytes), hashed cancel key (32 bytes),
    # public key (32 bytes), encrypted private key (rest, usually 144 bytes)
    _, cancelKeyHashed, _ = query[0][0][0:4], query[0][0][4:36], query[0][0][
        36:]

    # ensure key provided once hashed matches the hash
    if not cancelKeyHashed == En(cancelKey)._sha256():
        return {'success': False}

    # remove the request
    gsql.SQL.wesc(f"UPDATE users SET nextkeys* WHERE uuid = '{uuid}'",
                  v=[(None, )])
    return {'success': True}
示例#2
0
  def parseCookie(self):
    # b64 → bytes, huuid (16 bytes) + AES 'secret' + time (UTC/*nix)
    cookieBytes = En(self.cookieIn)._by64()
    huuid, phKey, ts = cookieBytes[0:16], cookieBytes[16:48], cookieBytes[48:]

    # from bytes huuid get uuid len 36 which IDs user and get SQL dict (line) for this user
    self.userDict = self.sql.getOneDataDict('users', 'uuid', self.ioUUID(huuid))

    # in case login from cookie
    if not self.email:
      if False if not self.userDict else self.userDict.get('email'):
        self.email = self.userDict.get('email')
      else:
        return False

    # check that cookie not expired
    if int(time.time()) - gv.COOKIE.MAX > af.ifb(cookieBytes[48:]):
      return False

    if not self.userDict:
      return False

    # concatenate AES 'secret' with time and SHA256 hash to get AES key to decrypt 'saes' session vault and get
    # Ed25519 private key
    self.priv = CryW(cryptedObj = self.userDict['saes'], bytesPwrd = En(phKey + ts)._sha256()).decrypt()

    # keyring
    if self.priv:
      self.keyring = Crypt25519(self.priv, self.userDict['pub'])

    return bool(self.priv)
示例#3
0
def report(token, secret, since):
    # avoid injection, ensure token matches secret (hashed secret = token)
    if not re.fullmatch(r'^[0-9]{1,10}', since):
        return JSONResponse({'success': False})

    if not re.fullmatch(r'^[0-9a-zA-Z=/$]{1,50}', token):
        return JSONResponse({'success': False})

    if not re.fullmatch(r'^[0-9a-zA-Z=/$]{1,50}', secret):
        return JSONResponse({'success': False})

    if not En(secret).by58()._sha256() == En(token)._by58():
        return JSONResponse({'success': False})

    # extract data for this token; if since is 0 then all data otherwise all data since 'since' in days eg GET URL .../7
    # then since is 7 so all things in past 7 days will be retrieved (7 days in dhacess is 7 * 86400 s * 1000000)
    sql = WSQL(*CONF.sqlCredentials)
    if int(since):
        sqlQuery = sql.getDataDicts(
            'hits',
            where=
            f"token = '{token}' AND dhaccess > {(int(time.time() - (since * 86400))) * 1000000}"
        )
    else:
        sqlQuery = sql.getDataDicts('hits', where=f"token = '{token}'")

    # modify result with adding 'dh' key as date/time YYYY-MM-DDTHH:MM as more easy to read than dhaccess timestamps
    # also remove token as repetitive
    modQuery = [{
        **{k: v
           for k, v in hitDict.items() if not k == 'token'}, 'dt':
        af.mytime(hitDict['dhaccess'] / 1000000, 0)
    } for hitDict in sqlQuery]

    # the report is sent as attachment - file name base for JSON and zip
    fileNameBase = f"{token}_{af.mytime(time.time(), 1)}_{af.mytime(time.time(), 4)}"
    # dump the extracted/modified SQL dict result as JSON then zip it as a zip stream -> bytes ie zipped is a bytes
    # obj of a zipped file b'PK...'
    zipped = af.makeZipBytes(
        {f"{fileNameBase}.json": json.dumps(modQuery).encode('utf8')})

    # create the mailjet obj; API key in 'conf.yaml' format 'username:password' (no Basic, really username:password)
    # as sms.Mailjet takes care of make the auth header
    mail = sms.Mailjet(CONF.mailjetAPIkey, CONF.sender)

    # lookup all emails related to this token go 1 at the time
    for email in sql.getOneDataDict('su', 'token', token)['emails']:
        # as specified by sms.Mailjet which is based on mailjet API
        mail.send(message={
            'Subject':
            f'Visit counter summary for {token}',
            'TextPart':
            f'Your visit counter summary for {token} is attached as JSON in ZIP file.'
        },
                  emailTo=email,
                  attachments={f'{fileNameBase}.zip': zipped})

    WSQL.closeAll()

    return {'success': True}
示例#4
0
  def requestPwrdReset(self, uuid, newAccount=False):
    self.sql.reconnect()
    userDict = self.sql.getOneDataDict('users', 'uuid', uuid)

    password, priv, pub = self.newKeypair()

    cancel = En()._rnd(32)

    message = {
      'Subject': 'Signout password change request',
      'TextPart': f"Dear {tf.bj(userDict['dat'])['name']},\r\nA request to {'create an account' if newAccount else 'change password'} "
                  f"was made to signout.\r\n\r\nIf you did not make this request, you can cancel it with this link: "
                  f"https://so.alexhal.me/cancel/{En(AuthSo.ioUUID(uuid) + cancel)._b58()}."
                  f"\r\n\r\nOtherwise, your temporary password is: {password}.\r\n\r\nThe Signout team."
    }
    email = sms.Mailjet(af.iob('mailjetapi.txt').decode('utf8'), '*****@*****.**')
    # email.send(message, userDict['email'])
    print(password)

    userDict['nextkeys'] = af.itb(int(time.time())) + En(cancel)._sha256() + pub + priv
    if newAccount:
      userDict['pub'] = pub
    self.sql.replaceRows('users', userDict)

    return password, priv, pub
示例#5
0
  def renewCookie(self):
    # ensure the renewed cookie will be valid ie current cookie can actually decrypt the session vault
    if not CryW(cryptedObj=self.userDict['saes'], bytesPwrd=En(En(self.cookieIn)._by64()[16:])._sha256()).decrypt():
      return False

    # create a new session vault with known, same private Ed25519 key but change the 'secret' and time hence AES key too
    cookieBytes = self.ioUUID(self.userDict['uuid']) + En()._rnd(32) + af.itb(int(time.time()))
    self.cookieOut = En(cookieBytes)._b64()
    self.sql.replaceRows('users', {
      **self.userDict,
      'saes': CryW(bytesMessage = self.priv, bytesPwrd = En(cookieBytes[16:])._sha256()).encrypt(),
    })

    return True
示例#6
0
def create():
    # create token/secret
    secret = En()._rnd(32)
    tokenb58 = En(secret).sha256()._b58()
    secretb58 = En(secret)._b58()
    emails = []

    # save
    sql = WSQL(*CONF.sqlCredentials)
    sql.replaceRows('su', {'token': tokenb58, 'emails': emails})

    # show what to write
    print(f"Link will be 'https://count.alexhal.me/count/{tokenb58}'")
    print(
        f"Crontab will be '* * * * * curl 127.0.0.1:{CONF.port}/report/{tokenb58}/{secretb58}'"
    )
    print(
        f"or '* * * * * curl 127.0.0.1:{CONF.port}/report/{tokenb58}/{secretb58}/somedurationdays >> /some/file/log.log' for logging"
    )
示例#7
0
  def getChallenge(self):
    if not self.userDict:
      return False

    # none if never logged in + case where first login ever *or* reset email password
    privateKeys = {
      'current': self.userDict['priv'],
      'next': self.parseNextKeys(self.userDict['nextkeys'])[3]
    }


    self.response['challenge'] = {k: En(CryW.getChallenge(v))._b64() for k, v in privateKeys.items() if v}

    return self
示例#8
0
  def changePassword(self, newHash, pub, priv):
    testBytes = En()._rnd(32)
    assert( testBytes == Crypt25519(privateBytes=priv).decrypt(Crypt25519(publicBytes=pub).encrypt(testBytes)) )

    self.userDict.update({
      'nextkeys': None,
      'priv': CryW(bytesMessage = newHash + priv, bytesPwrd = newHash).encrypt(),
      'pub': pub,
      'pwrdchange': 0
    })

    self.sql.replaceRows('users', self.userDict)
    self.sql.reconnect()

    self.response['type'] = 'passwordchanged'
示例#9
0
    def createList(self):

        self.luid = tf.getUUID()
        self.aes = En()._rnd(32)

        self.shareList(1, uuids=[self.auth.userDict['uuid']], first=True)
        self.updateList(newDat={
            'name': f"(nameless list)",
            'cols': []
        },
                        newActive=1)

        # add empty col
        self.updateCol()

        # check if one list displayed else display this one
        if not self.auth.sql.fetch(
                f"SELECT luid FROM lists WHERE luid IN (SELECT luid FROM rights WHERE uuid = '{self.auth.userDict['uuid']}' AND disp = 1) AND active = 1"
        ):
            self.auth.sql.wesc('UPDATE rights SET disp* WHERE uuid* AND luid*',
                               v=[(True, self.auth.userDict['uuid'], self.luid)
                                  ])
示例#10
0
        af.itb(random.randint(15, 126))
        for x in range(random.randint(100, 1000))
    ]) for y in range(100)
]

asymKC = Crypt25519()

t = time.time()
for plain in plains:
    asym = asymKC.encrypt(plain)
    dasym = Crypt25519(asymKC.privateBytes, asymKC.publicBytes).decrypt(asym)
    assert (dasym == plain)

print(time.time() - t)

aes = En().rnd(16)._sha256()

t = time.time()
for plain in plains:
    sym = CryW(bytesMessage=plain, bytesPwrd=aes, slow=1).encrypt()
    dsym = CryW(cryptedObj=sym, bytesPwrd=aes, slow=1).decrypt()

    assert (dsym == plain)

print(time.time() - t)


# speed test
def makeNotes(n=1, z=1):
    generator = getattr(lipsum, {
        0: 'generate_words',
示例#11
0
文件: tf.py 项目: alexhalme/signover
def jb(obj, aes = None):
  compressed = zlib.compress(En(json.dumps(obj))._by())
  return CryW(bytesMessage = compressed, bytesPwrd = aes).encrypt() if aes else compressed
示例#12
0
 def newKeypair(self):
   password = En()._rnd58(8)
   keys25519 = Crypt25519()
   return password, CryW(bytesMessage=En(password)._sha512u() + keys25519.privateBytes,
                         bytesPwrd=En(password)._sha512u()).encrypt(), keys25519.publicBytes
示例#13
0
 def ioUUID(cls, val):
   if len(val) == 36:
     return En(val.replace('-', ''))._by16()
   if len(val) == 16:
     return f"{En(val)._b16().lower()[0:8]}-{En(val)._b16().lower()[8:12]}-{En(val)._b16().lower()[12:16]}-{En(val)._b16().lower()[16:20]}-{En(val)._b16().lower()[20:]}"
   return False
示例#14
0
  def getCookie(self, login):
    self.response['cookie'] = ''
    self.getChallenge()
    # get encrypted vault for this user and try to decrypt
    decrypted = {
      k: CryW(
        cryptedObj = self.userDict['priv'] if k == 'current' else self.parseNextKeys(self.userDict['nextkeys'])[3],
        pbkd = En(v)._by64()
      ).decrypt() for k, v in login.pbkdf2b64.items()
    }

    decrypted = {k: v for k, v in decrypted.items() if v}


    # analyze content of dict to figure out what type is to be sent to front end -> standard, reset, change, first
    # (cookie and forgot are other 'types' but not relevant here)
    if self.userDict['nextkeys']:
      type = 'reset' if self.userDict['priv'] else 'first'
      if not decrypted:
        self.response['type'] = type
        return self
      if not login.newhash and (not decrypted.get('current') or type == 'first'):
        self.response['type'] = type
        return self
    else:
      type = 'change' if self.userDict['pwrdchange'] else 'standard'
      if not decrypted:
        self.response['type'] = type
        return self
      if not login.newhash and type == 'change':
        self.response['type'] = 'change'
        return self

    self.response['type'] = 'standard'

    # we can cancel the request as user knows password
    if type == 'reset':
      if decrypted.get('current'):
        self.sql.wesc(f"UPDATE users SET nextkeys* WHERE uuid*", v = [(None, self.userDict['uuid'])])
        self.userDict['nextkeys'] = None
      else:
        if not login.newhash:
          self.response['type'] = 'reset'
          return self

    # case where 'nextkeys' needs to be moved to usual permanents vaults ie new user or pwrd reset (not change, reset)
    if set(decrypted.keys()) == {'next'}:
      _, _, pub, _ = self.parseNextKeys(self.userDict['nextkeys'])
      self.hashedpwrd, self.priv = af.En(login.newhash)._by64(), decrypted.get('next')[64:]
      self.changePassword(self.hashedpwrd, pub, self.priv)

      # enable lists where assigned
      if type == 'first':
        self.sql.wesc(f"UPDATE rights SET disp* WHERE uuid*", v = [(1, self.userDict['uuid'])])
      else:
      # otherwise if reset then all keys are lost :(
        self.sql.wesc(f"UPDATE rights SET aes* WHERE uuid*", v = [(None, self.userDict['uuid'])])

    else:
      # decrypt the user's vault which contains (1) the hashed pwrd and (2) the private Ed25519 key
      self.hashedpwrd, self.priv = decrypted.get('current')[0:64], decrypted.get('current')[64:]

    # case where we are just Δing password
    if type == 'change':

      self.hashedpwrd = af.En(login.newhash)._by64()
      self.changePassword(self.hashedpwrd, self.userDict['pub'], self.priv)


    # cookie content in bytes: 16 bytes for uuid, 32 bytes of session AES 'secret' (not key) and time UTC, make b64 cookie
    cookieBytes = self.ioUUID(self.userDict['uuid']) + En()._rnd(32) + af.itb(int(time.time()))
    self.cookieOut = En(cookieBytes)._b64()

    # then rotate user's vault (avoid replay attack if XSS attack getting client-generated PBKDF2) and generate session
    # AES vault which is a SHA256 hashed combination of AES 'secret' above with time UTC so even a corrupt browser
    # can't fake cookie time generation to server as the hash which decrypts the session vault would change
    self.sql.replaceRows('users', {
      **self.userDict,
      'priv': CryW(bytesMessage = self.hashedpwrd + self.priv, bytesPwrd = self.hashedpwrd).encrypt(),
      'saes': CryW(bytesMessage = self.priv, bytesPwrd = En(cookieBytes[16:])._sha256()).encrypt(),
    })

    self.keyring = Crypt25519(self.priv, self.userDict['pub'])

    self.response['cookie'] = self.cookieOut

    return self
示例#15
0
    self.response['challenge'] = {k: En(CryW.getChallenge(v))._b64() for k, v in privateKeys.items() if v}

    return self

  def parseNextKeys(self, nextKeys):
    if not nextKeys:
      return b'', b'', b'', b''
    return af.ifb(nextKeys[0:4]), nextKeys[4:36], nextKeys[36:68], nextKeys[68:]




password = '******'
msg= b'\x16C\xd8\xa6\xc1\xdb~\xcdY\xc3\xd77\xeb\xfe\xa1\x0e\x9c\xe4\xdc:m5\xe7\xf5|\xc1\xb2U\x15\xbb\xe9\xd6\x86\xc1\xd8\xdcM\xb2\xeez\xd3\xe5C\xb9o\x1eN\x040\x19_\xe6\xc8\x9b\xdee\x95\xe2t\xe9\x82\xdd\xf5\t\x80U\xeb/\x08\xab~\xe5\x86"\xdf\xf2\xf2\x04!P\xae\xc5\xe9tv\x1c\xb9|\xbd\x99\xd2\xf1V\x14\x04\xac'
cipher = CryW(bytesMessage = msg, bytesPwrd = En(password)._sha512u()).encrypt()

CryW(cryptedObj = cipher, bytesPwrd = En(password)._sha512u()).decrypt()