def test_login(): '''Performs a basic login intended to be successful''' dbconn = setup_test() dbdata = init_server(dbconn) conn = ServerConnection() assert conn.connect('localhost', 2001), "Connection to server at localhost:2001 failed" # password is 'SandstoneAgendaTricycle' pwhash = '$argon2id$v=19$m=65536,t=2,p=1$ew5lqHA5z38za+257DmnTA$0LWVrI2r7XCq' \ 'dcCYkJLok65qussSyhN5TTZP+OTgzEI' devid = '22222222-2222-2222-2222-222222222222' devpair = EncryptionPair( CryptoString(r'CURVE25519:@X~msiMmBq0nsNnn0%~x{M|NU_{?<Wj)cYybdh&Z'), CryptoString(r'CURVE25519:W30{oJ?w~NBbj{F8Ag4~<bcWy6_uQ{i{X?NDq4^l')) dbdata['pwhash'] = pwhash dbdata['devid'] = devid dbdata['devpair'] = devpair # Most of the code which was originally written for this test is needed for other tests # because commands like PREREG require being logged in as the administrator. Both of these # functions still perform all the necessary tests that were originally done here. regcode_admin(dbdata, conn) login_admin(dbdata, conn) conn.send_message({'Action': "QUIT"})
def test_setpassword(): '''Tests the SETPASSWORD command''' dbconn = setup_test() dbdata = init_server(dbconn) conn = ServerConnection() assert conn.connect('localhost', 2001), "Connection to server at localhost:2001 failed" # password is 'SandstoneAgendaTricycle' pwhash = '$argon2id$v=19$m=65536,t=2,p=1$ew5lqHA5z38za+257DmnTA$0LWVrI2r7XCq' \ 'dcCYkJLok65qussSyhN5TTZP+OTgzEI' devid = '22222222-2222-2222-2222-222222222222' devpair = EncryptionPair( CryptoString(r'CURVE25519:@X~msiMmBq0nsNnn0%~x{M|NU_{?<Wj)cYybdh&Z'), CryptoString(r'CURVE25519:W30{oJ?w~NBbj{F8Ag4~<bcWy6_uQ{i{X?NDq4^l')) dbdata['pwhash'] = pwhash dbdata['devid'] = devid dbdata['devpair'] = devpair regcode_admin(dbdata, conn) login_admin(dbdata, conn) badpwhash = '$argon2id$v=19$m=65536,t=2,' \ 'p=1$l0nvPkQDxJDKIMZsA96x4A$c5+XHmZjzSJIg3/vXXr33YNB1Jl52mdXtbMASLP1abs' newpwhash = '$argon2id$v=19$m=65536,t=2,' \ 'p=1$4WOWeLn7oOmrkp41zMzMcQ$8G51UCiIC/eETPJf0RZ5cNkX7jr3NkT92etTwNEO+f0' # Subtest #1: Failure because of wrong password sent conn.send_message({ 'Action': "SETPASSWORD", 'Data': { 'Password-Hash': badpwhash, 'NewPassword-Hash': newpwhash } }) response = conn.read_response(None) assert response['Code'] == 402 and response['Status'] == 'AUTHENTICATION FAILURE', \ 'test_setpassword(): Failed to catch bad password' # Subtest #2: Successful password change conn.send_message({ 'Action': "SETPASSWORD", 'Data': { 'Password-Hash': pwhash, 'NewPassword-Hash': newpwhash } }) response = conn.read_response(None) assert response['Code'] == 200 and response['Status'] == 'OK', \ 'test_setpassword(): Failed to update admin password' conn.send_message({'Action': "QUIT"})
def test_overflow(): '''Tests the server's command handling for commands greater than 8K''' dbconn = setup_test() dbdata = init_server(dbconn) conn = serverconn.ServerConnection() assert conn.connect('localhost', 2001), "Connection to server at localhost:2001 failed" # password is 'SandstoneAgendaTricycle' pwhash = '$argon2id$v=19$m=65536,t=2,p=1$ew5lqHA5z38za+257DmnTA$0LWVrI2r7XCq' \ 'dcCYkJLok65qussSyhN5TTZP+OTgzEI' devid = '22222222-2222-2222-2222-222222222222' devpair = EncryptionPair( CryptoString(r'CURVE25519:@X~msiMmBq0nsNnn0%~x{M|NU_{?<Wj)cYybdh&Z'), CryptoString(r'CURVE25519:W30{oJ?w~NBbj{F8Ag4~<bcWy6_uQ{i{X?NDq4^l')) dbdata['pwhash'] = pwhash dbdata['devid'] = devid dbdata['devpair'] = devpair regcode_admin(dbdata, conn) login_admin(dbdata, conn) conn.send_message({ 'Action': "REGISTER", 'Data': { 'Workspace-ID': 'A' * 10240, 'Password-Hash': pwhash, 'Device-ID': '11111111-1111-1111-1111-111111111111', 'Device-Key': 'CURVE25519:@X~msiMmBq0nsNnn0%~x{M|NU_{?<Wj)cYybdh&Z' } }) response = conn.read_response(server_response) assert response['Code'] == 400 and response['Status'] == 'BAD REQUEST', \ 'test_overflow: failed to catch overflow' conn.send_message({'Action': "QUIT"})
def test_addentry_usercard(): '''Tests the ADDENTRY and USERCARD command processes''' dbconn = setup_test() dbdata = init_server(dbconn) conn = ServerConnection() assert conn.connect('localhost', 2001), "Connection to server at localhost:2001 failed" # password is 'SandstoneAgendaTricycle' pwhash = '$argon2id$v=19$m=65536,t=2,p=1$ew5lqHA5z38za+257DmnTA$0LWVrI2r7XCq' \ 'dcCYkJLok65qussSyhN5TTZP+OTgzEI' devid = '22222222-2222-2222-2222-222222222222' devpair = EncryptionPair( CryptoString(r'CURVE25519:@X~msiMmBq0nsNnn0%~x{M|NU_{?<Wj)cYybdh&Z'), CryptoString(r'CURVE25519:W30{oJ?w~NBbj{F8Ag4~<bcWy6_uQ{i{X?NDq4^l')) dbdata['pwhash'] = pwhash dbdata['devid'] = devid dbdata['devpair'] = devpair regcode_admin(dbdata, conn) login_admin(dbdata, conn) # Test setup is complete. Create a test keycard and do ADDENTRY # 1) Client sends the `ADDENTRY` command, attaching the entry data between the # `----- BEGIN USER KEYCARD -----` header and the `----- END USER KEYCARD -----` footer. # 2) The server then checks compliance of the entry data. Assuming that it complies, the server # generates a cryptographic signature and responds with `100 CONTINUE`, returning the # signature, the hash of the data, and the hash of the previous entry in the database. # 3) The client verifies the signature against the organization’s verification key. This has # the added benefit of ensuring that none of the fields were altered by the server and that # the signature is valid. # 4) The client appends the hash from the previous entry as the `Previous-Hash` field # 5) The client verifies the hash value for the entry from the server and sets the `Hash` field # 6) The client signs the entry as the `User-Signature` field and then uploads the result to # the server. # 7) Once uploaded, the server validates the `Hash` and `User-Signature` fields, and, # assuming that all is well, adds it to the keycard database and returns `200 OK`. # # Once that monster of a process completes, the card should be successfully added, but we're not # done yet. From there, create a new user entry, chain it to the first, and upload it. This will # test the chaining code in the AddEntry command processing. # # Finally, once that entire mess is complete, send a USERCARD command and verify what is # returned against the data set up so far. It doesn't seem like piggybacking a test for a # different command off another is the right thing to do, but in this case, it's the perfect # setup to confirm that a user's entire keycard is handled properly. usercard = keycard.UserEntry() usercard.set_fields({ 'Name': 'Test Administrator', 'Workspace-ID': dbdata['admin_wid'], 'User-ID': 'admin', 'Domain': 'example.com', 'Contact-Request-Verification-Key': 'ED25519:d0-oQb;{QxwnO{=!|^62+E=UYk2Y3mr2?XKScF4D', 'Contact-Request-Encryption-Key': 'CURVE25519:yBZ0{1fE9{2<b~#i^R+JT-yh-y5M(Wyw_)}_SZOn', 'Public-Encryption-Key': 'CURVE25519:_`UC|vltn_%P5}~vwV^)oY){#uvQSSy(dOD_l(yE' }) conn.send_message({ 'Action': "ADDENTRY", 'Data': { 'Base-Entry': usercard.make_bytestring(0).decode() } }) response = conn.read_response(server_response) assert response['Code'] == 100 and \ response['Status'] == 'CONTINUE' and \ 'Organization-Signature' in response['Data'] and \ 'Hash' in response['Data'] and \ 'Previous-Hash' in response['Data'], 'test_addentry(): server did return all needed fields' usercard.signatures['Organization'] = response['Data'][ 'Organization-Signature'] # A regular client will check the entry cache, pull updates to the org card, and get the # verification key. Because this is just an integration test, we skip all that and just use # the known verification key from earlier in the test. status = usercard.verify_signature(CryptoString(dbdata['ovkey']), 'Organization') assert not status.error( ), f"test_addentry(): org signature didn't verify: {status.info()}" assert response['Data']['Previous-Hash'] == dbdata['second_org_entry'].hash, \ "test_addentry(): server did not attach correct Previous Hash to root user entry" usercard.prev_hash = response['Data']['Previous-Hash'] usercard.hash = response['Data']['Hash'] status = usercard.verify_hash() assert not status.error( ), f"test_addentry(): hash didn't verify: {status.info()}" # User sign and verify skey = CryptoString('ED25519:ip52{ps^jH)t$k-9bc_RzkegpIW?}FFe~BX&<V}9') assert skey.is_valid( ), "test_addentry(): failed to set user's cr signing key" status = usercard.sign(skey, 'User') assert not status.error(), "test_addentry(): failed to user sign" vkey = CryptoString('ED25519:d0-oQb;{QxwnO{=!|^62+E=UYk2Y3mr2?XKScF4D') assert vkey.is_valid( ), "test_addentry(): failed to set user's cr verification key" status = usercard.verify_signature(vkey, 'User') status = usercard.is_compliant() assert not status.error( ), f"test_addentry(): compliance error: {str(status)}" conn.send_message({ 'Action': "ADDENTRY", 'Data': { 'User-Signature': usercard.signatures['User'] } }) response = conn.read_response(server_response) assert response['Code'] == 200 and \ response['Status'] == 'OK', f"test_addentry(): final upload server error {response}" # Now to test ADDENTRY with a chained entry. Hold on tight! # Submit the chained entry newkeys = usercard.chain(skey, True) assert not newkeys.error( ), f"test_addentry(): new entry failed to chain: {newkeys.info()}" second_user_entry = newkeys['entry'] conn.send_message({ 'Action': "ADDENTRY", 'Data': { 'Base-Entry': second_user_entry.make_bytestring(1).decode() } }) response = conn.read_response(server_response) assert response['Code'] == 100 and \ response['Status'] == 'CONTINUE' and \ 'Organization-Signature' in response['Data'] and \ 'Hash' in response['Data'] and \ 'Previous-Hash' in response['Data'], 'test_addentry(): server did return all needed fields' second_user_entry.signatures['Organization'] = response['Data'][ 'Organization-Signature'] # Verify the server's signature, add and verify the hashes, sign, and upload status = second_user_entry.verify_signature(CryptoString(dbdata['ovkey']), 'Organization') assert not status.error( ), f"test_addentry(): org signature didn't verify: {status.info()}" second_user_entry.prev_hash = response['Data']['Previous-Hash'] assert response['Data']['Previous-Hash'] == usercard.hash, \ "Server did not return correct chained user entry hash" second_user_entry.hash = response['Data']['Hash'] status = second_user_entry.verify_hash() assert not status.error( ), f"test_addentry(): hash didn't verify: {status.info()}" skey = CryptoString(newkeys['crsign.private']) assert skey.is_valid(), "test_addentry(): failed to set user signing key" status = second_user_entry.sign(skey, 'User') assert not status.error(), "test_addentry(): failed to user sign" vkey = CryptoString(newkeys['crsign.public']) assert vkey.is_valid( ), "test_addentry(): failed to set user verification key" status = second_user_entry.verify_signature(vkey, 'User') status = second_user_entry.is_compliant() assert not status.error( ), f"test_addentry(): compliance error: {str(status)}" conn.send_message({ 'Action': "ADDENTRY", 'Data': { 'User-Signature': second_user_entry.signatures['User'] } }) response = conn.read_response(server_response) assert response['Code'] == 200 and \ response['Status'] == 'OK', f"test_addentry(): final upload server error {response}" conn.send_message({ 'Action': "USERCARD", 'Data': { 'Owner': 'admin/example.com', 'Start-Index': '1' } }) response = conn.read_response(server_response) assert response['Code'] == 104 and response['Status'] == 'TRANSFER', \ 'test_addentry.usercard: server returned %s' % response['Status'] assert response['Data']['Item-Count'] == '2', \ 'test_addentry.usercard: server returned wrong number of items' data_size = int(response['Data']['Total-Size']) conn.send_message({'Action': 'TRANSFER'}) chunks = list() tempstr = conn.read() data_read = len(tempstr) chunks.append(tempstr) while data_read < data_size: tempstr = conn.read() data_read = data_read + len(tempstr) chunks.append(tempstr) assert data_read == data_size, 'test_orgcard.usercard: size mismatch' # Now that the data has been downloaded, we put it together and split it properly. We should # have two entries entries = ''.join(chunks).split('----- END USER ENTRY -----\r\n') if entries[-1] == '': entries.pop() # These entries are added to the database in init_server(). The insert operations are not # done here because the two org entries are needed for other tests, as well. assert len( entries ) == 2, "test_orgcard.usercard: server did not send the expected entry" assert entries[1] == '----- BEGIN USER ENTRY -----\r\n' + \ second_user_entry.make_bytestring(-1).decode(), \ "test_orgcard.usercard: entry didn't match" conn.send_message({'Action': "QUIT"})
def test_set_status(): '''Tests the SETSTATUS command''' dbconn = setup_test() dbdata = init_server(dbconn) uid = 'csimons' domain = 'example.net' conn = ServerConnection() assert conn.connect('localhost', 2001), "Connection to server at localhost:2001 failed" # password is 'SandstoneAgendaTricycle' pwhash = '$argon2id$v=19$m=65536,t=2,p=1$ew5lqHA5z38za+257DmnTA$0LWVrI2r7XCq' \ 'dcCYkJLok65qussSyhN5TTZP+OTgzEI' devid = '22222222-2222-2222-2222-222222222222' devpair = EncryptionPair( CryptoString(r'CURVE25519:@X~msiMmBq0nsNnn0%~x{M|NU_{?<Wj)cYybdh&Z'), CryptoString(r'CURVE25519:W30{oJ?w~NBbj{F8Ag4~<bcWy6_uQ{i{X?NDq4^l')) dbdata['pwhash'] = pwhash dbdata['devid'] = devid dbdata['devpair'] = devpair regcode_admin(dbdata, conn) login_admin(dbdata, conn) # Prereg with a test user conn.send_message({ 'Action': "PREREG", 'Data': { 'User-ID': uid, 'Domain': domain } }) response = conn.read_response(server_response) assert response['Code'] == 200 and response['Status'] == 'OK', \ 'test_set_status: failed to prereg test user' # Call REGCODE to actually register the user regdata = response pwd = Password() status = pwd.Set('ShrivelCommuteGottenAgonizingElbowQuiver') assert not status.error(), 'test_set_status: Failed to set password' devid = '0e6406e3-1831-4352-9fbe-0de8faebf0f0' devkey = EncryptionPair() # Subtest #2: Regcode with User ID and domain conn.send_message({ 'Action': "REGCODE", 'Data': { 'User-ID': regdata['Data']['User-ID'], 'Domain': regdata['Data']['Domain'], 'Reg-Code': regdata['Data']['Reg-Code'], 'Password-Hash': pwd.hashstring, 'Device-ID': devid, 'Device-Key': devkey.get_public_key() } }) response = conn.read_response(server_response) assert response['Code'] == 201 and response['Status'] == 'REGISTERED', \ 'test_set_status: failed to register test user' conn.send_message({ 'Action': 'SETSTATUS', 'Data': { 'Workspace-ID': regdata['Data']['Workspace-ID'], 'Status': 'disabled' } }) response = conn.read_response(server_response) assert response['Code'] == 200 and response['Status'] == 'OK', \ 'test_set_status: failed to disable test user'
def test_devkey(): '''Tests the DEVKEY command''' dbconn = setup_test() dbdata = init_server(dbconn) conn = ServerConnection() assert conn.connect('localhost', 2001), "Connection to server at localhost:2001 failed" # password is 'SandstoneAgendaTricycle' pwhash = '$argon2id$v=19$m=65536,t=2,p=1$ew5lqHA5z38za+257DmnTA$0LWVrI2r7XCq' \ 'dcCYkJLok65qussSyhN5TTZP+OTgzEI' devid = '22222222-2222-2222-2222-222222222222' devpair = EncryptionPair( CryptoString(r'CURVE25519:@X~msiMmBq0nsNnn0%~x{M|NU_{?<Wj)cYybdh&Z'), CryptoString(r'CURVE25519:W30{oJ?w~NBbj{F8Ag4~<bcWy6_uQ{i{X?NDq4^l')) dbdata['pwhash'] = pwhash dbdata['devid'] = devid dbdata['devpair'] = devpair # Most of the code which was originally written for this test is needed for other tests # because commands like PREREG require being logged in as the administrator. Both of these # functions still perform all the necessary tests that were originally done here. regcode_admin(dbdata, conn) login_admin(dbdata, conn) newdevpair = EncryptionPair( CryptoString(r'CURVE25519:F0HjK;dB8tr<*2UkaHP?e1-tWAohWJ?IP+oP@o@C'), CryptoString(r'CURVE25519:|Cs(42=7FEwCoKG|5fVPC%MR6gD)_{h}}?ah%cIn')) conn.send_message({ 'Action': 'DEVKEY', 'Data': { 'Device-ID': devid, 'Old-Key': dbdata['devpair'].get_public_key(), 'New-Key': newdevpair.get_public_key() } }) response = conn.read_response(None) assert response != 100 and response['Status'] == 'CONTINUE' and \ 'Challenge' in response['Data'] and 'New-Challenge' in response['Data'], \ "test_devkey(): server failed to return new and old key challenge" msg = {'Action': "DEVKEY", 'Data': {}} status = devpair.decrypt(response['Data']['Challenge']) assert not status.error( ), 'login_devkey(): failed to decrypt device challenge for old key' msg['Data']['Response'] = status['data'] status = newdevpair.decrypt(response['Data']['New-Challenge']) assert not status.error( ), 'login_devkey(): failed to decrypt device challenge for new key' msg['Data']['New-Response'] = status['data'] conn.send_message(msg) response = conn.read_response(None) assert response['Code'] == 200 and response['Status'] == 'OK', \ 'Server challenge-response phase failed' conn.send_message({'Action': "QUIT"})
def test_resetpassword(): '''Tests the RESETPASSWORD command''' dbconn = setup_test() dbdata = init_server(dbconn) conn = ServerConnection() assert conn.connect('localhost', 2001), "Connection to server at localhost:2001 failed" # password is 'SandstoneAgendaTricycle' pwhash = '$argon2id$v=19$m=65536,t=2,p=1$ew5lqHA5z38za+257DmnTA$0LWVrI2r7XCq' \ 'dcCYkJLok65qussSyhN5TTZP+OTgzEI' devid = '22222222-2222-2222-2222-222222222222' devpair = EncryptionPair( CryptoString(r'CURVE25519:@X~msiMmBq0nsNnn0%~x{M|NU_{?<Wj)cYybdh&Z'), CryptoString(r'CURVE25519:W30{oJ?w~NBbj{F8Ag4~<bcWy6_uQ{i{X?NDq4^l')) dbdata['pwhash'] = pwhash dbdata['devid'] = devid dbdata['devpair'] = devpair regcode_admin(dbdata, conn) login_admin(dbdata, conn) init_user(dbdata, conn) # Subtest #1: Reset user password, server generates passcode and expiration conn.send_message({ 'Action': "RESETPASSWORD", 'Data': { 'Workspace-ID': dbdata['user_wid'] } }) response = conn.read_response(None) assert response['Code'] == 200 and response['Status'] == "OK", \ "test_resetpassword(): subtest #1: server returned an error" assert 'Expires' in response['Data'] and 'Reset-Code' in response['Data'], \ "test_resetpassword(): subtest #1: server didn't return all required fields" # Subtest #2: Reset user password, admin generates passcode and expiration expiration = datetime.datetime.utcnow() + datetime.timedelta(minutes=45) conn.send_message({ 'Action': "RESETPASSWORD", 'Data': { 'Workspace-ID': dbdata['user_wid'], 'Reset-Code': 'barbarian-chivalry-dungeon', 'Expires': expiration.strftime(r'%Y%m%dT%H%M%SZ') } }) response = conn.read_response(None) assert response['Code'] == 200 and response['Status'] == "OK", \ "test_resetpassword(): subtest #2: server returned an error" assert 'Expires' in response['Data'] and 'Reset-Code' in response['Data'], \ "test_resetpassword(): subtest #2: server didn't return all required fields" conn.send_message({'Action': 'LOGOUT', 'Data': {}}) response = conn.read_response(None) assert response[ 'Code'] == 200, 'test_resetpassword(): failed to log admin out' # Subtest #3: Failed attempt to complete the password reset newpassword = Password('SomeOth3rPassw*rd') conn.send_message({ 'Action': "PASSCODE", 'Data': { 'Workspace-ID': dbdata['user_wid'], 'Reset-Code': 'wrong-reset-code', 'Password-Hash': newpassword.hashstring } }) response = conn.read_response(None) assert response['Code'] == 402 and response['Status'] == "AUTHENTICATION FAILURE", \ "test_resetpassword(): subtest #3: server failed to catch a bad password" # Subtest #4: Successfully complete the password reset conn.send_message({ 'Action': "PASSCODE", 'Data': { 'Workspace-ID': dbdata['user_wid'], 'Reset-Code': 'barbarian-chivalry-dungeon', 'Password-Hash': newpassword.hashstring } }) response = conn.read_response(None) assert response['Code'] == 200 and response['Status'] == "OK", \ "test_resetpassword(): subtest #4: server returned an error"
def test_prereg(): '''Tests the server's PREREG command with failure conditions''' dbconn = setup_test() dbdata = init_server(dbconn) uid = 'TestUserID' wid = '11111111-1111-1111-1111-111111111111' domain = 'acme.com' conn = ServerConnection() assert conn.connect('localhost', 2001), "Connection to server at localhost:2001 failed" # password is 'SandstoneAgendaTricycle' pwhash = '$argon2id$v=19$m=65536,t=2,p=1$ew5lqHA5z38za+257DmnTA$0LWVrI2r7XCq' \ 'dcCYkJLok65qussSyhN5TTZP+OTgzEI' devid = '22222222-2222-2222-2222-222222222222' devpair = EncryptionPair( CryptoString(r'CURVE25519:@X~msiMmBq0nsNnn0%~x{M|NU_{?<Wj)cYybdh&Z'), CryptoString(r'CURVE25519:W30{oJ?w~NBbj{F8Ag4~<bcWy6_uQ{i{X?NDq4^l')) dbdata['pwhash'] = pwhash dbdata['devid'] = devid dbdata['devpair'] = devpair regcode_admin(dbdata, conn) login_admin(dbdata, conn) # Subtest #1: Prereg with user ID and domain conn.send_message({ 'Action': "PREREG", 'Data': { 'User-ID': uid, 'Domain': domain } }) response = conn.read_response(server_response) assert response['Code'] == 200 and response['Status'] == 'OK', \ 'test_prereg: subtest #1 returned an error' assert response['Data']['User-ID'] == uid, \ 'test_prereg: wrong user ID in subtest #1' assert response['Data']['Domain'] == domain, \ 'test_prereg: wrong domain in subtest #1' assert validate_uuid( response['Data']['Workspace-ID']), 'Server returned a bad WID' assert len(response['Data']['Workspace-ID']) <= 128, \ 'Server returned a regcode longer than allowed' # REGCODE subtest setup regdata = response pwd = Password() status = pwd.Set('ShrivelCommuteGottenAgonizingElbowQuiver') assert not status.error(), 'test_prereg: Failed to set password' devid = '0e6406e3-1831-4352-9fbe-0de8faebf0f0' devkey = EncryptionPair() # Subtest #2: Regcode with User ID and domain conn.send_message({ 'Action': "REGCODE", 'Data': { 'User-ID': regdata['Data']['User-ID'], 'Domain': regdata['Data']['Domain'], 'Reg-Code': regdata['Data']['Reg-Code'], 'Password-Hash': pwd.hashstring, 'Device-ID': devid, 'Device-Key': devkey.get_public_key() } }) response = conn.read_response(server_response) assert response['Code'] == 201 and response['Status'] == 'REGISTERED', \ 'test_prereg: subtest #2 returned an error' # Subtest #3: Plain prereg conn.send_message({'Action': "PREREG", 'Data': {}}) response = conn.read_response(server_response) assert response['Code'] == 200 and response['Status'] == 'OK', \ 'test_prereg: subtest #2 returned an error' assert validate_uuid( response['Data']['Workspace-ID']), 'Server returned a bad WID' assert len(response['Data']['Workspace-ID']) <= 128, \ 'Server returned a regcode longer than allowed' # Subtest #4: Plain regcode regdata = response conn.send_message({ 'Action': "REGCODE", 'Data': { 'Workspace-ID': regdata['Data']['Workspace-ID'], 'Reg-Code': regdata['Data']['Reg-Code'], 'Password-Hash': pwd.hashstring, 'Device-ID': devid, 'Device-Key': devkey.get_public_key() } }) response = conn.read_response(server_response) assert response['Code'] == 201 and response['Status'] == 'REGISTERED', \ 'test_prereg: subtest #4 returned an error' # Subtest #5: duplicate user ID conn.send_message({ 'Action': "PREREG", 'Data': { 'User-ID': uid, 'Domain': domain } }) response = conn.read_response(server_response) assert response['Code'] == 408 and response['Status'] == 'RESOURCE EXISTS', \ 'test_prereg: subtest #3 failed to catch duplicate user' # Subtest #6: WID as user ID conn.send_message({'Action': "PREREG", 'Data': {'User-ID': wid}}) response = conn.read_response(server_response) assert response['Code'] == 200 and response['Status'] == 'OK', \ 'test_prereg: subtest #4 returned an error' assert response['Data']['Workspace-ID'] == wid, 'Server returned a bad WID' assert len(response['Data']['Workspace-ID']) <= 128, \ 'Server returned a regcode longer than allowed' # Subtest #7: Specify WID wid = '22222222-2222-2222-2222-222222222222' conn.send_message({'Action': "PREREG", 'Data': {'Workspace-ID': wid}}) response = conn.read_response(server_response) assert response['Code'] == 200 and response['Status'] == 'OK', \ 'test_prereg: subtest #4 returned an error' assert response['Data']['Workspace-ID'] == wid, 'Server returned a bad WID' assert len(response['Data']['Workspace-ID']) <= 128, \ 'Server returned a regcode longer than allowed' # Subtest #8: Specify User ID only. uid = 'TestUserID2' conn.send_message({'Action': "PREREG", 'Data': {'User-ID': uid}}) response = conn.read_response(server_response) assert response['Code'] == 200 and response['Status'] == 'OK', \ 'test_prereg: subtest #6 returned an error' assert response['Data'][ 'User-ID'] == uid, 'Server returned the wrong user ID' assert validate_uuid( response['Data']['Workspace-ID']), 'Server returned a bad WID' assert len(response['Data']['Workspace-ID']) <= 128, \ 'Server returned a regcode longer than allowed' conn.send_message({'Action': "QUIT"})
def test_register_failures(): '''Tests the server's REGISTER command with failure conditions''' # Testing the REGISTER command only works when the server uses either network or public mode serverconfig = load_server_config_file() if serverconfig['global']['registration'] not in ['network', 'public']: return dbconn = setup_test() dbdata = init_server(dbconn) conn = serverconn.ServerConnection() assert conn.connect('localhost', 2001), "Connection to server at localhost:2001 failed" # password is 'SandstoneAgendaTricycle' pwhash = '$argon2id$v=19$m=65536,t=2,p=1$ew5lqHA5z38za+257DmnTA$0LWVrI2r7XCq' \ 'dcCYkJLok65qussSyhN5TTZP+OTgzEI' devid = '22222222-2222-2222-2222-222222222222' devpair = EncryptionPair( CryptoString(r'CURVE25519:@X~msiMmBq0nsNnn0%~x{M|NU_{?<Wj)cYybdh&Z'), CryptoString(r'CURVE25519:W30{oJ?w~NBbj{F8Ag4~<bcWy6_uQ{i{X?NDq4^l')) dbdata['pwhash'] = pwhash dbdata['devid'] = devid dbdata['devpair'] = devpair regcode_admin(dbdata, conn) login_admin(dbdata, conn) # Test #1: Attempt registration with unsupported encryption type conn.send_message({ 'Action': "REGISTER", 'Data': { 'Workspace-ID': '11111111-1111-1111-1111-222222222222', 'Password-Hash': pwhash, 'Device-ID': '11111111-1111-1111-1111-111111111111', 'Device-Key': '3DES:@X~msiMmBq0nsNnn0%~x{M|NU_{?<Wj)cYybdh&Z' } }) response = conn.read_response(server_response) assert response['Code'] == 309 and response['Status'] == 'ENCRYPTION TYPE NOT SUPPORTED', \ 'test_register_failures: subtest #1 failed to catch unsupported encryption' # Test #2: Send bad WID conn.send_message({ 'Action': "REGISTER", 'Data': { 'Workspace-ID': 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA', 'Password-Hash': pwhash, 'Device-ID': '11111111-1111-1111-1111-111111111111', 'Device-Key': 'CURVE25519:@X~msiMmBq0nsNnn0%~x{M|NU_{?<Wj)cYybdh&Z' } }) response = conn.read_response(server_response) assert response['Code'] == 400 and response['Status'] == 'BAD REQUEST', \ 'test_register_failures: subtest #2 failed to catch a bad WID' conn.send_message({'Action': "QUIT"})
def test_register(): '''Tests the server's REGISTER command - success and duplicate WID condition''' # Testing the REGISTER command only works when the server uses either network or public mode serverconfig = load_server_config_file() if serverconfig['global']['registration'] not in ['network', 'public']: return dbconn = setup_test() dbdata = init_server(dbconn) conn = serverconn.ServerConnection() assert conn.connect('localhost', 2001), "Connection to server at localhost:2001 failed" # password is 'SandstoneAgendaTricycle' pwhash = '$argon2id$v=19$m=65536,t=2,p=1$ew5lqHA5z38za+257DmnTA$0LWVrI2r7XCq' \ 'dcCYkJLok65qussSyhN5TTZP+OTgzEI' devid = '22222222-2222-2222-2222-222222222222' devpair = EncryptionPair( CryptoString(r'CURVE25519:@X~msiMmBq0nsNnn0%~x{M|NU_{?<Wj)cYybdh&Z'), CryptoString(r'CURVE25519:W30{oJ?w~NBbj{F8Ag4~<bcWy6_uQ{i{X?NDq4^l')) dbdata['pwhash'] = pwhash dbdata['devid'] = devid dbdata['devpair'] = devpair regcode_admin(dbdata, conn) login_admin(dbdata, conn) wid = '11111111-1111-1111-1111-111111111111' # password is 'SandstoneAgendaTricycle' pwhash = '$argon2id$v=19$m=65536,t=2,p=1$ew5lqHA5z38za+257DmnTA$0LWVrI2r7XCq' \ 'dcCYkJLok65qussSyhN5TTZP+OTgzEI' devkey = 'CURVE25519:@X~msiMmBq0nsNnn0%~x{M|NU_{?<Wj)cYybdh&Z' # Subtest #1: Regular registration that is supposed to succeed conn.send_message({ 'Action': "REGISTER", 'Data': { 'Workspace-ID': wid, 'Password-Hash': pwhash, 'Device-ID': '11111111-1111-1111-1111-111111111111', 'Device-Key': devkey } }) response = conn.read_response(server_response) assert response['Code'] == 201 and response['Status'] == 'REGISTERED', \ 'test_register: subtest #1 returned an error' # Subtest #2: Attempt registration of existing WID conn.send_message({ 'Action': "REGISTER", 'Data': { 'Workspace-ID': wid, 'Password-Hash': pwhash, 'Device-ID': '11111111-1111-1111-1111-111111111111', 'Device-Key': devkey } }) response = conn.read_response(server_response) assert response['Code'] == 408 and response['Status'] == 'RESOURCE EXISTS', \ 'test_register: subtest #2 failed to catch duplicate registration' conn.send_message({'Action': "QUIT"})
def test_unregister(): '''Test the UNREGISTER command''' # Testing the UNREGISTER command only works when the server uses either network or public mode serverconfig = load_server_config_file() if serverconfig['global']['registration'] not in ['network', 'public']: return dbconn = setup_test() dbdata = init_server(dbconn) conn = serverconn.ServerConnection() assert conn.connect('localhost', 2001), "Connection to server at localhost:2001 failed" # password is 'SandstoneAgendaTricycle' pwhash = '$argon2id$v=19$m=65536,t=2,p=1$ew5lqHA5z38za+257DmnTA$0LWVrI2r7XCq' \ 'dcCYkJLok65qussSyhN5TTZP+OTgzEI' devid = '22222222-2222-2222-2222-222222222222' devpair = EncryptionPair( CryptoString(r'CURVE25519:@X~msiMmBq0nsNnn0%~x{M|NU_{?<Wj)cYybdh&Z'), CryptoString(r'CURVE25519:W30{oJ?w~NBbj{F8Ag4~<bcWy6_uQ{i{X?NDq4^l')) dbdata['pwhash'] = pwhash dbdata['devid'] = devid dbdata['devpair'] = devpair regcode_admin(dbdata, conn) login_admin(dbdata, conn) userwid = '11111111-1111-1111-1111-111111111111' # password is 'SandstoneAgendaTricycle' pwhash = '$argon2id$v=19$m=65536,t=2,p=1$ew5lqHA5z38za+257DmnTA$0LWVrI2r7XCq' \ 'dcCYkJLok65qussSyhN5TTZP+OTgzEI' devkey = 'CURVE25519:@X~msiMmBq0nsNnn0%~x{M|NU_{?<Wj)cYybdh&Z' conn.send_message({ 'Action': "REGISTER", 'Data': { 'Workspace-ID': userwid, 'Password-Hash': pwhash, 'Device-ID': devid, 'Device-Key': devkey } }) response = conn.read_response(server_response) assert response['Code'] == 201 and response['Status'] == 'REGISTERED', \ f"test_unregister: test user registration failed: {response['Status']}" # Subtest #1: Try to unregister the admin account conn.send_message({ 'Action': "UNREGISTER", 'Data': { 'Password-Hash': pwhash } }) response = conn.read_response(server_response) assert response['Code'] == 403 and response['Status'] == 'FORBIDDEN', \ "test_unregister(): failed to properly handle trying to unregister admin account" conn.send_message({'Action': "LOGOUT", 'Data': {}}) response = conn.read_response(server_response) assert response['Code'] == 200 and response['Status'] == 'OK' # Set up for subtest #2: log in as the user status = serverconn.login(conn, userwid, CryptoString(dbdata['oekey'])) assert not status.error( ), f"test_unregister(): user login phase failed: {status.info()}" status = serverconn.password(conn, userwid, pwhash) assert not status.error( ), f"test_unregister(): password phase failed: {status.info()}" status = serverconn.device(conn, devid, devpair) assert not status.error( ), f"test_unregister(): device phase failed: {status.info()}" # As a general rule, these anselusd integration tests don't call the regular pyanselus client # library calls because we do extra validation. However, we're going to make an exception in # this test because LOGIN and ADDENTRY are both really big. usercard = keycard.UserEntry() usercard.set_fields({ 'Name': 'Corbin Simons', 'Workspace-ID': userwid, 'User-ID': 'csimons', 'Domain': 'example.com', 'Contact-Request-Verification-Key': 'ED25519:E?_z~5@+tkQz!iXK?oV<Zx(ec;=27C8Pjm((kRc|', 'Contact-Request-Encryption-Key': 'CURVE25519:yBZ0{1fE9{2<b~#i^R+JT-yh-y5M(Wyw_)}_SZOn', 'Public-Encryption-Key': 'CURVE25519:_`UC|vltn_%P5}~vwV^)oY){#uvQSSy(dOD_l(yE' }) crspair = SigningPair( CryptoString(r'ED25519:E?_z~5@+tkQz!iXK?oV<Zx(ec;=27C8Pjm((kRc|'), CryptoString(r'ED25519:u4#h6LEwM6Aa+f<++?lma4Iy63^}V$JOP~ejYkB;')) status = usercard.is_data_compliant() assert not status.error( ), f"test_unregister: user card not compliant: {status.info()}" status = serverconn.addentry(conn, usercard, CryptoString(dbdata['ovkey']), crspair) assert not status.error(), f"test_unregister: addentry() failed: {status.info()}\n" \ f"Server Info: {status['Info']}" # Subtest #2: Unregister regular user from admin account conn.send_message({ 'Action': "UNREGISTER", 'Data': { 'Workspace-ID': userwid, 'Password-Hash': pwhash } }) response = conn.read_response(server_response) assert response['Code'] == 202 and response['Status'] == 'UNREGISTERED', \ f"test_unregister(): user unregistration failed: {response['Status']}" # Check to make sure the database end was handled correctly cur = dbconn.cursor() cur.execute('SELECT password,status FROM workspaces WHERE wid = %s ', (userwid, )) row = cur.fetchone() assert row, "test_unregister(): cleanup check query found no rows" assert row[0] == '-' and row[1] == 'deleted', \ "test_unregister(): server failed to clean up database properly" conn.send_message({'Action': "QUIT"})