Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save vladiuz1/919ad7f79729eaec58be75489ad09a0c to your computer and use it in GitHub Desktop.
Save vladiuz1/919ad7f79729eaec58be75489ad09a0c to your computer and use it in GitHub Desktop.

A. User login process.

  1. User visits a website, and on every visit she/he is issued a unique session cookie (authToken).
  2. User enters email and password.
  3. A pbkdf2 key is generated using a formula privateKey = pbkdf2(concat(email, password)).
  4. authToken, ipAddress concatenation is signed by JavaScript ecdsa library with the resulting privateKey, and sent to server to authorize the token to access account.
  5. Server checks the signature against publicKey stored in database (publicKey is in turn signed by email server during email verification step when signing up or changing password and this sig is also checked). Among other things, the server checks if the token corresponds to IP address it was generated for, and the time it was generated for has not expired.
  6. If everything is ok, the authToken is authorized to log the user in - i.e. saved in local db along with important info. And during the login session this authToken is always passed to server and checked against db.

Now every request to sensitive data passes authToken in its Cookie: http header, and the server pulls all the technical infor from the database:

token created last ip_v4 ip_v6 signature
0x837463276297c6df239a847f6 2018-08-01 00:00:00 2018-08-01 00:02:00 123.23.12.4 0x23764a526528734aaabcdf5

The passwords are always stored in database as publicKey(pbkdf2(concat(email, password))). And signed by all servers/user who verified/created this password.

B. User withdraw request.

  1. When creating a withdraw request a json packet is generated by client side javascript containing all the request's details:
  • the account number that wishes to make the withdrawal (email? source adddress).
  • the amount of withdrawal
  • the crypto currency code
  • destination address
  • timestamp of withdrawal request
[payload: [email: 'sergey@crypto.one', amount: 123, symbol: 'ETH', address: '0x08aab5844d9fde025f581456b900a0fef8fe0913', '2018-06-07 00:00:01', nonce=123],
signature:'0278108436282764298467234']
  1. User enters his password in order to generate his pbkdf2 privateKey as described in A3. Email is taken from db.
  2. User signs the json request, and sends both the requst and signature to server, where withdrawal request is saved along with signature and timestamp.
  3. CRM managers verify the signature of the request (and if 2fa is enabled also the signatures of the email or sms servers who signed the request), also check that there was no request with the same timestamp (to avoid a case when same request is duplicated in compromised database).

C. Changing password.

Password can be changed in 2 ways. One when user knows his previous password, and two - when he forgot his password.

The basic principle is simple every password or email change needs to be signed. So an attacker can not just simply penetrate the central server, change the password and request withdrawal.

When user knows his previous password, he signs the password change by a privateKey derived from previous password, the public key is stored ofcourse.

If a user forgot his password, then we may want to request signatures of 2 additional servers: a) the email verification server, one that sends the password change request link, b) the sms (whatsapp, telegram) verification server, the one that sends sms verification code.

In central database server, we always keep a log of publicKey changes (Chain of Changes), and revocations:

timestamp key_owner operation public_key signature signatory_id
2017-01-02 00:00:00 user2334 change_key 0xabc676d2938f237ab084843cd 0x3467634 user2334

D. Changing email

Email change must also go through similar process, as new publicKey is stored and signed by old key and sms verification server.

To change email you need to remember you password, have access to new email, and to have access to your phone.

So basically it goes like this:

  1. You login through process with old email as described in A.
  2. You enter a new email and old password, generating a signed json with old pbkdf2 privateKey. JSON will contain a new publicKey derived from new pbkdf2 privateKey.
  3. SMS server sends a link to phone number containing signature of new publicKey. Once user gets SMS and clicks the link on central db server, the server adds the signature of the SMS server to the keystore db, checks validity of the sms server signature, and adds new publicKey as current publicKey for the user.

Key structure

             root
             key ----------------------------+
              |                              |
   +----------+----------+           +-------+-------+
   |          |          |           |               |
 email       sms        db          crm             crm
server      server    server      manager 1       manager 2
  key         key       key         key             key
   |          |          |
   +---- old user key ---+
              |
         new user key

Every time there is a key verification, the verification logic goes through all the chain of verifications. This chain goes all the way to root key, making sure a user key is signed old user keys, and old user keys (after password change) has been signed by all 3 servers, which in turn are signed by root key. Key revokations are checked along the way, and if a key is signed by a revoked key along the way, verification fails.

Root public keys and valid server public keys are kept in db, but may be downloaded by client, as this is pubic info, and all key verifications may be done on the client side. Things like withdrawal request approvals by crm managers may be verified on client side. And it is even advised to be done that way to make sure there is no man in the middle attack.

Server keys may be revoked if compromised, the db server keeps a list of compromised keys. But even if db server key is compromised CRM managers still have a way to check if withdrawal request is valid. Provided that:

  1. All client-side files (html, css, js) are kept on a separate server from interractive logic (db server). So access to db server does not let attacker read the privateKey of user as he logs in.
  2. CRM manager keeps a valid version of root publicKey.
  3. There is a way to sign/verify html, js, css being used by CRM managers.

This security measure is needed to make sure CRM manager can always confirm that request for withdrawal was requested by person who knows password, his/her request was verified by email server (or sms server) (not faked by getting into one of the servers)

Servers

  • DB/Logic server. It only services ajax/json rpc calls.
  • Static frontend server. (static frontend server)
  • Secure CRM server (may be written in PHP even). Protected by SSL certificate authentication, IP restricted access
  • SMS verification server (it sends a link via sms, with a request signature and request id as a parameters, by clicking this link user sends both parameters to DB/Logic server, which in turn verifies that signature against the requst).
  • Email verification server (it works the same as SMS verification)

Email Verification Server

Email verification server must be setup in a separate physical server. On a subdomain such as email.verifier.crypto.one. This server must only accept one API call: /api/email-verify. this json rpc call must accept the following JSON RPC parameters:

{
   "email": "myname@email.com",
   "language": "en",
   "payload":{
      "type":"change_key",
      "new_key":"02cdf28376cf2276321acdf23ab87276321acdf23ab87",
      "stamp":"2018-01-01 00:32:23"
   },
   "callback": "https://db.crypto.one/api/email/verification-callback",
   "redirect": "https://crypto.one/accounts/change-password/"
}

This api call basically tells the email server to put its signature on the payload, tell the central db, and to whoever is interested that the user has authorised the change of key by clicking a link in an email.

Ok, after this request is sent to email verification server, it basically does the following:

  • Records the request in its local db,
  • Generates a unique confirmation link (https://email.verifier.crypto.one/?938802cdf28376cf22767276321acdf23ab8727634, where query string is the unique identifier of the request, perhaps a hash of the json?),
  • Sends a link to user's email (in this case myname@email.com") saying that you need to click the link to complete the change of password that you have initiated.
  • Once the link is clicked, the server sends a an API request to callback url, with payload signed with email verfication server's private key. Now the central db has proof in the form of signature that email server indeed received request to sign the password change, and very likely has signed it.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment