-
Notifications
You must be signed in to change notification settings - Fork 1
Authentication and Verification
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.
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.
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.
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:
- host?... [Test
notificationSettings
: no credentials] - host?deviceHash=... [Test
notificationSettings
:deviceHash: THEDEVICEHASH
] - host?deviceHash=...&phoneHash=... [Test
notificationSettings
:deviceHash: THEDEVICEHASH
,phoneHash: h(123)
]
In this mode the Server will also set cookies for the tokens
- debugRefreshToken
- debugToken
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 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
.
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)
This is a list of functionality you can only access if you have logged in:
- activityIndex [Query]
- notificationSettings [Query]
- requestCode [Mutation - see below]
- requestVerification [Mutation - see below]
- addToken [Mutation]
- updateNotificationSettings [Mutation]
- toggleNotification [Mutation]
- notifiedProcedures (deprecated) [Query]
- finishSearch [Mutation]
- votes [Query]
- me [Query]
If not logged in those queries will throw a "Permission denied" Error. The queries listed above do not contain credentials.
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.
-
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 ofnewPhone
and theoldPhoneHash
cannot be the same) -
Provided oldPhoneHash is invalid
(oldPhoneHash
and unverified user OR no oldPhone ORoldPhoneHash
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)
For the following tests to work the server config needs to set DEBUG=true
.
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.
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,
}
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
For the following tests to work the server config needs to set DEBUG=true
.
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.
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.
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
# | 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 |
# | 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 |
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 |
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 |
DEMOCRACY Deutschland e.V.
IBAN: DE33 5003 1000 1049 7560 00
BIC: TRODDEF1
Mail: [email protected]
mobil +49 176 470 40 213
- DEMOCRACY
- Stimmanonymität
- Wahl-⦻-Meter
- Anforderungen Wahlverfahren
- Democracy Font
- Authentication & Verification
- Fragebögen
-
Userstories
- US 01 Closed Beta App Installation
- US 02a Erster Start der App Mobilgerät
- US 02b Erster Start via Browser
- US 02c Erster Start via Handy Browser
- US 03a Benachrichtigung Neue Abstimmung
- US 03b Benachrichtigung Neues Papier
- US 04 Papier finden (Handy Browser)
- US 05 Gesetz Suchen
- US 06 Nur bestimmte Gesetze sehen
- US 07 Abstimmung teilen
- US 08 Externer Link
- US 09 App mit schlechter Internetverbindung verwenden
- US 10 Benachrichtigung löschen
- US 11 Bei Google nach Gesetz suchen
- US 12 Account löschen
- US 13 Account ausloggen
- US 17 Abstimmen
- US 20 Nutzer möchte weiterführende Infos einbringen
- US 22 – Beratungszustand verstehen
- US 23 – Verständnisprobleme
- US 24 – Fraktionsergebnisse sichten
- US 25 – Sicherheitseinstellungen wählen
- Anderes
- Veraltet