Skip to content

Authentication and Verification

Ulf Gebhardt edited this page Aug 15, 2018 · 45 revisions

This article describes the authentication process of the DEMOCRACY App and the SMS verification mechanism used to enable voting on the device.

The motivation for this article is to make it easy to explain the used processes, to provide tests to verify its working correctly and allows others to define attack vectors against the process.

All references in the document to a hash or the use of h() referrers to the sha256 hash function.

Authentication using JWT

The Client and Server communicate with a JWT-Token. If you do not provide a valid token or credentials you are prohibited to use some functionality. For login and token transmission the header of the corresponding requests are used. Therefore no explicit request for login is required. The whole process is purely passive.

Login

The login requires two values:

  • the device hash (refers to a hashed device identifier)
  • and the phone hash (refers to a hashed telephone number)

Both values are transmitted via Header with the following fields:

  • x-device-hash
  • x-phone-hash

Note: The device hash can be considered a persistent client password.
Note: The phone hash is allowed to be null if not available.
Note: You can also leave out authentication headers completely and still use some of the servers functionality

As response the Server will provide a JWT-Token and a JWT-Refresh-Token in the response header:

  • x-token
  • x-refresh-token

You should provide these tokens for all following requests to the Server to stay loggedin.

Note: It is wise to verify if the tokens are expired client side and login again if they did.
Note: If you provide credentials an user is created as long as it does not exist yet.

Debug Mode & Tests

If you specify DEBUG=true in the server config the server will also read phone and device hash values from the GET-query of the call:

In this mode the Server will also set cookies for the tokens

  • debugRefreshToken
  • debugToken

Attack Vector

If you know the DeviceID and the phone number of an user you can access the server as this user.

Possible attack on Android:

    • Know the phone number of the victim
    • Hash the phone number
    • Have your App on the victims phone
    • Use your App to read the DeviceID
    • Hash the DeviceID
    • You now have both credentials

This attack is not possible on iOS, since the DeviceID is dependent on the App-Vendor.

Possible solution would be to salt the DeviceHash.

Keep & refresh Session

Keep the two JWT-Tokens in the request header to stay logged in:

  • x-token
  • x-refresh-token

Note: You are allowed to leave out the x-refresh-token if you are sure the x-token does not expire while requesting.

If the x-token is expired and the x-refresh-token is still valid the server will send you new tokens with your next request.

If both tokens are expired you need to use the login process again.

Note: Check for the presence of the token headers client side and save those if present for your next request.
Note: The TTL for both tokens can be set in the Serer config via AUTH_JWT_TTL and AUTH_JWT_REFRESH_TTL.

Attack Vector

If the attacker can obtain a valid x-token he can authenticate as the victim against the server.

If the attacker is able to obtain the x-refresh-token he will be able to stay logged in as the victim indefinitely.

Possible solution would be to limit the amount of refreshs without reauthentication - e.g on refreshing do not send another x-refresh-token or to make sure the x-refresh-token is not sent by default (which it currently is)

Login protected functionality

This is a list of functionality you can only access if you have logged in:

If not logged in those queries will throw a "Permission denied" Error. The queries listed above do not contain credentials.

SMS Verification

Request Code

To request a verification code you mutate the graphql endpoint on requestCode with the following parameters:

  • newPhone (Cleartext Phone Number)
  • oldPhoneHash (optional) (Hash of your previously verified Phone Number)

Note: You need to be logged in to use requestCode
Note: the oldPhoneHash should only be passed if the user wants to change a previously verified Phone Number to a new one.
Note: Calling this mutation twice will generate another code and send it via SMS if enough time has passed. The default is SMS_VERIFICATION_CODE_RESEND_BASETIME=120s which is squared with every code request. Considering the default values of 120s for the second code request and 1d TTL for a verification attempt the following values are the result

# Time till request
1 0s
2 120s
3 120s * 120s = 4h

Decreasing the SMS_VERIFICATION_CODE_RESEND_BASETIME will result in more possible requests within one day. If you increase the code SMS_VERIFICATION_CODE_TTL will also increase the possible requests.

The Result of the query is either:

{
  succeeded: false,
  reason: 'Some reason'
  resendTime: Int
}

or

{
  succeeded: true,
  allowNewUser: true/false
  resendTime: Int
}

The allowNewUser field specifies if enough time has passed to reuse the phone number. This represents the change of ownership of a phone number (in Germany ~6 months) and is controlled by the server config SMS_VERIFICATION_NEW_USER_DELAY. If the field is true the user should be asked if he likes to keep the old Data or create a new account.

Note: For a user which does not exist in the database yet allowNewUser is false.

Once you called requestCode successfully the provided phone number will receive a 6-digit code via SMS.

Reasons for failure

  • SMS Verification is disabled! (SMS_VERIFICATION=false in server config)
  • newPhone is invalid - does not have the required length of min. 14 digits or does not start with countrycode 0049
  • newPhoneHash equals oldPhoneHash (The hash of newPhone and the oldPhoneHash cannot be the same)
  • Provided oldPhoneHash is invalid (oldPhoneHash and unverified user OR no oldPhone OR oldPhoneHash not the same as oldPhone.phoneHash)
  • You have to wait till you can request another Code (Wait for enough time to pass to request another Code for the Phone)
  • Your number seems incorrect, please correct it! (The SMS Provider could not send the previous Code to the given Phone)
  • Could not send SMS to given newPhone (SMS Provider sending process was not successful)

Debug Mode & Tests

For the following tests to work the server config needs to set DEBUG=true.

Attack Vectors

The banking sector lately removed the SMS-Tan procedure from there repertoire. The reason for this change was communicated as a security risk since SMS can be intercepted. If this is true or the banks are trying to avoid carrier costs is unknown. If the SMS communication can be intercepted this may pose a security risk to the SMS-Verification used by us aswell.

A possible solution would be to transmit a second value via normal client server communication which then is hashed together with the code received via SMS.

Request Verification

To request a verification you mutate the graphql endpoint on requestVerification with the following parameters:

  • code (String representing the Code received via SMS and requested via requestCode)
  • newPhoneHash (String representing the hashed phone number)
  • newUser (optional) (true)

Note: You need to be logged in to use requestVerification
Note: On success this call returns new JWT-Tokens
Note: You need to requestVerification from the same device as you requestCode
Note: newUser is the option to create a new user if the previous user did not login for a certain amount of time. This is for phone number ownership changes. If false or set when not possible to create a new User the flag is ignored.

The Result of the query is either:

{
  succeeded: false,
  reason: 'Some reason'
}

or

{
  succeeded: true,
}

Reasons for failure

  • SMS Verification is disabled!
  • You are already verified!
  • Could not find verification request
  • Invalid Code or Code expired
  • Code requested from another Device
  • User phoneHash and oldPhoneHash inconsistent

Debug Mode & Tests

For the following tests to work the server config needs to set DEBUG=true.

Attack Vectors

An attacker can generate random DeviceHashes and keep requesting verification for a random or his phone number. This will result in costs for the SMS Service.

Possible Solution: Rate-Limit for one number, Rate-Limit for one device. This has only limited effect since the attacker can vary phone number and deviceHash. An IP based Rate-Limit can also be bypassed by proxy.

Verification protected functionality

This is a list of functionality you can only access if you have verified:

If not verified those queries will throw a "Permission denied" Error.

Database

Flow

Extended Tests

The following defines extended Tests for the JWT-Authentication and SMS-Verififcation.

Note: For these tests to work enable DEBUG=true in the server config.
Note: Whenever a code is required to verify you need to look it up in the Database

TestCase: Login

# Query/Mutation Data/Header Expected Result Note Run
1 notificationSettings - failure (Permission denied) - Run
2 notificationSettings deviceHash: THEDEVICEHASH success (data) - Run
3 notificationSettings deviceHash: THEDEVICEHASH, phoneHash: h(123) success (data) - Run

TestCase: RequestCode

# Query/Mutation Data/Header Expected Result Note Run
1 vote deviceHash: THEDEVICEHASH, phoneHash: h(123), procedure: valid, selection: ABSTINATION failure (Permission denied) - Run
2 requestCode newPhone: 123 failure (Permission denied) - Run
3 requestCode deviceHash: THEDEVICEHASH, newPhone: 123, oldPhoneHash: h(123) failure (newPhoneHash equals oldPhoneHash) - Run
4 requestCode deviceHash: THEDEVICEHASH, newPhone: 123, oldPhoneHash: invalid failure (Provided oldPhoneHash is invalid) - Run
5 requestCode deviceHash: THEDEVICEHASH, newPhone: 123 success (data) - Run
6 requestVerification deviceHash: THEDEVICEHASH, code: invalid, newPhoneHash: invalid failure (Could not find verification request) - Run
7 requestVerification deviceHash: THEDEVICEHASH, code: invalid, newPhoneHash: h(123) failure (Invalid Code or Code expired) - Run
8 requestVerification deviceHash: THEDEVICEHASH, code: valid, newPhoneHash: h(123) success (data) Lookup the correct Code in the DB Run
9 requestVerification deviceHash: THEDEVICEHASH, code: valid, newPhoneHash: h(123), newUser: true success (data) Lookup the correct Code in the DB, newUser has no effect Run
10 vote deviceHash: THEDEVICEHASH, phoneHash: h(123), procedure: valid, selection: ABSTINATION success (data) - Run

TestCase: RequestCode with phone number change

This test requires to run the previous test beforehand to create required database values.

# Query/Mutation Data/Header Expected Result Note Run
1 requestCode deviceHash: THEDEVICEHASH, phoneHash: h(123), newPhone: 456, oldPhoneHash: h(123) success (data) - Run
2 requestVerification deviceHash: THEDEVICEHASH, phoneHash: h(123), code: valid, newPhoneHash: h(456) success (data) Lookup the correct Code in the DB Run

TestCase: RequestCode with new User

This test requires a clean Database. Also set SMS_VERIFICATION_NEW_USER_DELAY=0 in the server config to skip waiting for 6 Months.

If SMS_VERIFICATION_NEW_USER_DELAY=0 is set there should be 4 votes at the end of the test, otherwise there should be only 1 vote.

# Query/Mutation Data/Header Expected Result Note Run
1 requestCode deviceHash: THEDEVICEHASH, newPhone: 123 success (data) - Run
2 requestVerification deviceHash: THEDEVICEHASH, code: valid, newPhoneHash: h(123) success (data) - Run
3 vote deviceHash: THEDEVICEHASH, phoneHash: h(123), procedure: valid, selection: ABSTINATION success (data) - Run
3 vote deviceHash: THEDEVICEHASH, phoneHash: h(123), procedure: valid, selection: ABSTINATION success (data) The vote is not casted twice Run
4 requestCode deviceHash: THEDEVICEHASH2, newPhone: 123 success (data) Returns allowNewUser: true Run
5 requestVerification deviceHash: THEDEVICEHASH2, code: valid, newPhoneHash: h(123), newUser: true success (data) - Run
6 vote deviceHash: THEDEVICEHASH2, phoneHash: h(123), procedure: valid, selection: ABSTINATION success (data) - Run
7 requestCode deviceHash: THEDEVICEHASH2, phoneHash: h(123), newPhone: 456, oldPhoneHash: h(123) success (data) Returns allowNewUser: true Run
8 requestVerification deviceHash: THEDEVICEHASH2, phoneHash: h(123), code: valid, newPhoneHash: h(456), newUser: true success (data) - Run
9 vote deviceHash: THEDEVICEHASH2, phoneHash: h(456), procedure: valid, selection: ABSTINATION success (data) - Run
10 requestCode deviceHash: THEDEVICEHASH2, phoneHash: h(456), newPhone: 123, oldPhoneHash: h(456) success (data) Returns allowNewUser: true Run
11 requestVerification deviceHash: THEDEVICEHASH2, phoneHash: h(456), code: valid, newPhoneHash: h(123), newUser: true success (data) - Run
12 vote deviceHash: THEDEVICEHASH2, phoneHash: h(123), procedure: valid, selection: ABSTINATION success (data) - Run
Clone this wiki locally