Пример #1
0
def devkey(conn: ServerConnection, devid: str, oldpair: EncryptionPair, newpair: EncryptionPair):
	'''Replaces the specified device's key stored on the server'''
	if not utils.validate_uuid(devid):
		return RetVal(AnsBadRequest, 'Invalid device ID').set_value('status', 400)

	conn.send_message({
		'Action' : "DEVKEY",
		'Data' : { 
			'Device-ID': devid,
			'Old-Key': oldpair.public.as_string(),
			'New-Key': newpair.public.as_string()
		}
	})

	# Receive, decrypt, and return the server challenge
	response = conn.read_response(server_response)
	if response.error():
		return response
	
	if response['Code'] != 100:
		return wrap_server_error(response)

	if 'Challenge' not in response['Data'] or 'New-Challenge' not in response['Data']:
		return RetVal(ServerError, 'server did not return both device challenges')
	
	status = oldpair.decrypt(response['Data']['Challenge'])
	if status.error():
		cancel(conn)
		return RetVal(DecryptionFailure, 'failed to decrypt device challenge for old key')

	request = {
		'Action' : "DEVKEY",
		'Data' : { 
			'Response' : status['data']
		}
	}

	status = newpair.decrypt(response['Data']['New-Challenge'])
	if status.error():
		cancel(conn)
		return RetVal(DecryptionFailure, 'failed to decrypt device challenge for new key')
	request['Data']['New-Response'] = status['data']
	conn.send_message(request)

	response = conn.read_response(None)
	if response.error():
		return response
	
	if response['Code'] == 200:
		return RetVal()
	
	return wrap_server_error(response)
Пример #2
0
def test_preregister_regcode():
    '''Test the preregister and regcode commands'''
    dbconn = setup_test()
    dbdata = init_server(dbconn)

    conn = serverconn.ServerConnection()
    status = conn.connect('localhost', 2001)
    assert not status.error(
    ), f"test_login(): failed to connect to server: {status.info()}"

    password = Password('Linguini2Pegboard*Album')
    devid = '14142135-9c22-4d3e-84a3-2aa281f65714'
    keypair = EncryptionPair(
        CryptoString(r'CURVE25519:mO?WWA-k2B2O|Z%fA`~s3^$iiN{5R->#jxO@cy6{'),
        CryptoString(r'CURVE25519:2bLf2vMA?GA2?L~tv<PA9XOw6e}V~ObNi7C&qek>'))
    status = serverconn.regcode(conn, 'admin', dbdata['admin_regcode'],
                                password.hashstring, devid, keypair, '')
    assert not status.error(
    ), f"test_preregister_regcode(): regcode failed: {status.info()}"

    status = serverconn.login(conn, dbdata['admin_wid'],
                              CryptoString(dbdata['oekey']))
    assert not status.error(
    ), f"test_preregister_regcode(): login phase failed: {status.info()}"

    status = serverconn.password(conn, dbdata['admin_wid'],
                                 password.hashstring)
    assert not status.error(
    ), f"test_preregister_regcode(): password phase failed: {status.info()}"

    status = serverconn.device(conn, devid, keypair)
    assert not status.error(), "test_preregister_regcode(): device phase failed: " \
     f"{status.info()}"

    status = serverconn.preregister(conn, '', 'csimons', 'example.net')
    assert not status.error(
    ), "test_preregister_regcode(): uid preregistration failed"
    assert status['domain'] == 'example.net' and 'wid' in status and 'regcode' in status and \
     status['uid'] == 'csimons', "test_preregister_regcode(): failed to return expected data"

    regdata = status
    password = Password('MyS3cretPassw*rd')
    devpair = EncryptionPair()
    status = serverconn.regcode(conn, 'csimons', regdata['regcode'],
                                password.hashstring,
                                '11111111-1111-1111-1111-111111111111',
                                devpair, 'example.net')
    assert not status.error(), "test_preregister_regcode(): uid regcode failed"

    conn.disconnect()
Пример #3
0
def init_user(conn: serverconn.ServerConnection, config: dict) -> RetVal:
    '''Creates a test user for command testing'''

    userwid = '33333333-3333-3333-3333-333333333333'
    status = serverconn.preregister(conn, userwid, 'csimons', 'example.net')
    assert not status.error(), "init_user(): uid preregistration failed"
    assert status['domain'] == 'example.net' and 'wid' in status and 'regcode' in status and \
     status['uid'] == 'csimons', "init_user(): failed to return expected data"

    regdata = status
    password = Password('MyS3cretPassw*rd')
    devpair = EncryptionPair()
    devid = '11111111-1111-1111-1111-111111111111'
    status = serverconn.regcode(conn, 'csimons', regdata['regcode'],
                                password.hashstring, devid, devpair,
                                'example.net')
    assert not status.error(), "init_user(): uid regcode failed"

    config['user_wid'] = userwid
    config['user_uid'] = regdata['uid']
    config['user_domain'] = regdata['domain']
    config['user_devid'] = devid
    config['user_devpair'] = devpair
    config['user_password'] = password

    return RetVal()
Пример #4
0
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"})
Пример #5
0
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"})
Пример #6
0
def test_reset_password():
    '''Tests password reset code'''
    dbconn = setup_test()
    dbdata = init_server(dbconn)

    conn = serverconn.ServerConnection()
    status = conn.connect('localhost', 2001)
    assert not status.error(
    ), f"test_login(): failed to connect to server: {status.info()}"

    password = Password('Linguini2Pegboard*Album')
    devid = '14142135-9c22-4d3e-84a3-2aa281f65714'
    keypair = EncryptionPair(
        CryptoString(r'CURVE25519:mO?WWA-k2B2O|Z%fA`~s3^$iiN{5R->#jxO@cy6{'),
        CryptoString(r'CURVE25519:2bLf2vMA?GA2?L~tv<PA9XOw6e}V~ObNi7C&qek>'))
    status = serverconn.regcode(conn, 'admin', dbdata['admin_regcode'],
                                password.hashstring, devid, keypair, '')
    assert not status.error(
    ), f"test_reset_password(): regcode failed: {status.info()}"

    status = serverconn.login(conn, dbdata['admin_wid'],
                              CryptoString(dbdata['oekey']))
    assert not status.error(
    ), f"test_reset_password(): login phase failed: {status.info()}"

    status = serverconn.password(conn, dbdata['admin_wid'],
                                 password.hashstring)
    assert not status.error(
    ), f"test_reset_password(): password phase failed: {status.info()}"

    status = serverconn.device(conn, devid, keypair)
    assert not status.error(), "test_reset_password(): device phase failed: " \
     f"{status.info()}"

    status = init_user(conn, dbdata)
    assert not status.error(
    ), f"test_reset_password(): user init failed: {status.info()}"

    status = serverconn.reset_password(conn, dbdata['user_wid'])
    assert not status.error(
    ), f"test_reset_password(): password reset failed: {status.info()}"
    resetdata = status

    status = serverconn.logout(conn)
    assert not status.error(
    ), f"test_reset_password(): admin logout failed: {status.info()}"

    newpassword = Password('SomeOth3rPassw*rd')
    status = serverconn.passcode(conn, dbdata['user_wid'],
                                 resetdata['resetcode'],
                                 newpassword.hashstring)
    assert not status.error(
    ), f"test_reset_password(): passcode failed: {status.info()}"
Пример #7
0
def device(conn: ServerConnection, devid: str, devpair: EncryptionPair) -> RetVal:
	'''Completes the login process by submitting device ID and its session string.'''
	if not utils.validate_uuid(devid):
		return RetVal(AnsBadRequest, 'Invalid device ID').set_value('status', 400)

	conn.send_message({
		'Action' : "DEVICE",
		'Data' : { 
			'Device-ID' : devid,
			'Device-Key' : devpair.public.as_string()
		}
	})

	# Receive, decrypt, and return the server challenge
	response = conn.read_response(server_response)
	if response.error():
		return response
	
	if response['Code'] != 100:
		return wrap_server_error(response)

	if 'Challenge' not in response['Data']:
		return RetVal(ServerError, 'server did not return a device challenge')
	
	status = devpair.decrypt(response['Data']['Challenge'])
	if status.error():
		cancel(conn)
		return RetVal(DecryptionFailure, 'failed to decrypt device challenge')

	conn.send_message({
		'Action' : "DEVICE",
		'Data' : { 
			'Device-ID' : devid,
			'Device-Key' : devpair.public.as_string(),
			'Response' : status['data']
		}
	})

	response = conn.read_response(None)
	if response.error():
		return response
	
	if response['Code'] == 200:
		return RetVal()
	
	return wrap_server_error(response)
Пример #8
0
def keycard_admin(config, conn) -> dict:
    '''Uploads a keycard entry for the admin account'''

    conn = serverconn.ServerConnection()
    status = conn.connect('localhost', 2001)
    assert not status.error(
    ), f"test_addentry(): failed to connect to server: {status.info()}"

    crepair = EncryptionPair(
        CryptoString(r'CURVE25519:mO?WWA-k2B2O|Z%fA`~s3^$iiN{5R->#jxO@cy6{'),
        CryptoString(r'CURVE25519:2bLf2vMA?GA2?L~tv<PA9XOw6e}V~ObNi7C&qek>'))

    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;'))

    epair = EncryptionPair(
        CryptoString(r'CURVE25519:Umbw0Y<^cf1DN|>X38HCZO@Je(zSe6crC6X_C_0F'),
        CryptoString(r'CURVE25519:Bw`F@ITv#sE)2NnngXWm7RQkxg{TYhZQbebcF5b$'))

    entry = keycard.UserEntry()
    entry.set_fields({
        'Name':
        'Administrator',
        'Workspace-ID':
        config['admin_wid'],
        'User-ID':
        'admin',
        'Domain':
        'example.com',
        'Contact-Request-Verification-Key':
        crspair.get_public_key(),
        'Contact-Request-Encryption-Key':
        crepair.get_public_key(),
        'Public-Encryption-Key':
        epair.get_public_key()
    })

    status = serverconn.addentry(conn, entry, CryptoString(config['ovkey']),
                                 crspair)
    assert not status.error(
    ), f"test_addentry: failed to add entry: {status.info()}"

    return {
        'admin_crepair': crepair,
        'admin_crspair': crspair,
        'admin_epair': epair
    }
Пример #9
0
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"})
Пример #10
0
def test_devkey():
    '''Tests the devkey() command'''
    dbconn = setup_test()
    dbdata = init_server(dbconn)

    conn = serverconn.ServerConnection()
    status = conn.connect('localhost', 2001)
    assert not status.error(
    ), f"test_devkey(): failed to connect to server: {status.info()}"

    status = init_admin(conn, dbdata)
    assert not status.error(
    ), f"test_devkey(): init_admin failed: {status.info()}"

    newdevpair = EncryptionPair(
        CryptoString(r'CURVE25519:mO?WWA-k2B2O|Z%fA`~s3^$iiN{5R->#jxO@cy6{'),
        CryptoString(r'CURVE25519:2bLf2vMA?GA2?L~tv<PA9XOw6e}V~ObNi7C&qek>'))

    status = serverconn.devkey(conn, dbdata['admin_devid'],
                               dbdata['admin_devpair'], newdevpair)
    assert not status.error(
    ), f"test_devkey(): error returned: {status.info()}"

    conn.disconnect()
Пример #11
0
def test_register():
    '''Test worskpace registration'''

    # Registration testing only works when the server uses either network or public mode
    serverdbdata = load_server_config_file()
    if serverdbdata['global']['registration'] not in ['network', 'public']:
        return

    dbconn = setup_test()
    init_server(dbconn)

    conn = serverconn.ServerConnection()
    status = conn.connect('localhost', 2001)
    assert not status.error(
    ), f"test_login(): failed to connect to server: {status.info()}"

    password = Password('MyS3cretPassw*rd')
    devpair = EncryptionPair()
    status = serverconn.register(conn, 'csimons', password.hashstring,
                                 devpair.public)
    assert not status.error(
    ), f"test_register: failed to register test account: {status.info()}"

    conn.disconnect()
Пример #12
0
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"})
Пример #13
0
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"})
Пример #14
0
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"})
Пример #15
0
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'
Пример #16
0
    def chain(self, key: CryptoString, rotate_optional: bool) -> RetVal:
        '''Creates a new UserEntry object with new keys and a custody signature. It requires the 
		previous contact request signing key passed as an CryptoString. The new keys are returned in 
		CryptoString format using the following fields:
		entry
		sign.public / sign.private -- primary signing keypair
		crsign.public / crsign.private -- contact request signing keypair
		crencrypt.public / crencrypt.private -- contact request encryption keypair
		encrypt.public / encrypt.private -- general-purpose public encryption keypair
		altencrypt.public / altencrypt.private -- alternate public encryption keypair

		Note that the last two keys are not required to be updated during entry rotation so that 
		they can be rotated on a different schedule from the other keys. These fields are only 
		returned if there are no errors.
		'''

        if key.prefix != 'ED25519':
            return RetVal(BadParameterValue, f'wrong key type {key.prefix}')

        status = self.is_compliant()
        if status.error():
            return status

        new_entry = UserEntry()
        new_entry.fields = self.fields.copy()
        try:
            index = int(new_entry.fields['Index'])
            new_entry.fields['Index'] = str(index + 1)
        except Exception:
            return RetVal(BadData, 'invalid entry index')

        out = RetVal()

        skey = SigningPair()
        crskey = SigningPair()
        crekey = EncryptionPair()

        out['sign.public'] = skey.get_public_key()
        out['sign.private'] = skey.get_private_key()
        out['crsign.public'] = crskey.get_public_key()
        out['crsign.private'] = crskey.get_private_key()
        out['crencrypt.public'] = crekey.get_public_key()
        out['crencrypt.private'] = crekey.get_private_key()

        new_entry.fields['Contact-Request-Verification-Key'] = out[
            'crsign.public']
        new_entry.fields['Contact-Request-Encryption-Key'] = out[
            'crencrypt.public']

        if rotate_optional:
            ekey = EncryptionPair()
            out['encrypt.public'] = ekey.get_public_key()
            out['encrypt.private'] = ekey.get_private_key()

            aekey = EncryptionPair()
            out['altencrypt.public'] = aekey.get_public_key()
            out['altencrypt.private'] = aekey.get_private_key()

            new_entry.fields['Public-Encryption-Key'] = out['encrypt.public']
            new_entry.fields['Alternate-Encryption-Key'] = out[
                'altencrypt.public']
        else:
            out['encrypt.public'] = ''
            out['encrypt.private'] = ''
            out['altencrypt.public'] = ''
            out['altencrypt.private'] = ''

        status = new_entry.sign(key, 'Custody')
        if status.error():
            return status

        out['entry'] = new_entry
        return out
Пример #17
0
    def chain(self, key: CryptoString, rotate_optional: bool) -> RetVal:
        '''Creates a new OrgEntry object with new keys and a custody signature. The keys are 
		returned in CryptoString format using the following fields:
		entry
		sign.public / sign.private -- primary signing keypair
		sign.pubhash / sign.privhash -- hashes of the corresponding keys
		altsign.public / altsign.private -- contact request signing keypair
		altsign.pubhash / altsign.privhash -- hashes of the corresponding keys
		encrypt.public / encrypt.private -- general-purpose public encryption keypair
		encrypt.pubhash / encrypt.privhash -- hashes of the corresponding keys

		For organization entries, rotating optional keys works a little differently: the primary 
		signing key becomes the secondary signing key in the new entry. When rotation is False, 
		which is recommended only in instances of revocation, the secondary key is removed. Only 
		when rotate_optional is True is the field altsign.private returned.
		'''
        if key.prefix != 'ED25519':
            return RetVal(BadParameterValue, f'wrong key type {key.prefix}')

        status = self.is_compliant()
        if status.error():
            return status

        new_entry = OrgEntry()
        new_entry.fields = self.fields.copy()

        try:
            index = int(new_entry.fields['Index'])
            new_entry.fields['Index'] = str(index + 1)
        except Exception:
            return RetVal(BadData, 'invalid entry index')

        out = RetVal()

        skey = SigningPair()
        ekey = EncryptionPair()

        out['sign.public'] = skey.get_public_key()
        out['sign.pubhash'] = skey.get_public_hash()
        out['sign.private'] = skey.get_private_key()
        out['sign.privhash'] = skey.get_private_hash()
        out['encrypt.public'] = ekey.get_public_key()
        out['encrypt.pubhash'] = ekey.get_public_hash()
        out['encrypt.private'] = ekey.get_private_key()
        out['encrypt.privhash'] = ekey.get_private_hash()

        if rotate_optional:
            altskey = SigningPair()
            out['altsign.public'] = altskey.get_public_key()
            out['altsign.pubhash'] = altskey.get_public_hash()
            out['altsign.private'] = altskey.get_private_key()
            out['altsign.privhash'] = altskey.get_private_hash()
        else:
            out['altsign.public'] = self.fields['Primary-Verification-Key']
            out['altsign.pubhash'] = blake2hash(
                self.fields['Primary-Verification-Key'].as_string().encode())
            out['altsign.private'] = ''

        status = new_entry.sign(key, 'Custody')
        if status.error():
            return status

        out['entry'] = new_entry
        return out
Пример #18
0
    def register_account(self, server: str, userid: str,
                         userpass: str) -> RetVal:
        '''Create a new account on the specified server.'''

        # Process for registration of a new account:
        #
        # Check to see if we already have a workspace allocated on this profile. Because we don't
        # 	yet support shared workspaces, it means that there are only individual ones. Each
        #	profile can have only one individual workspace.
        #
        # Check active profile for an existing workspace entry
        # Get the password from the user
        # Check active workspace for device entries. Because we are registering, existing device
        #	entries should be removed.
        # Add a device entry to the workspace. This includes both an encryption keypair and
        #	a UUID for the device
        # Connect to requested server
        # Send registration request to server, which requires a hash of the user's supplied
        #	password
        # Close the connection to the server
        # If the server returns an error, such as 304 REGISTRATION CLOSED, then return an error.
        # If the server has returned anything else, including a 101 PENDING, begin the
        #	client-side workspace information to generate.
        # Call storage.generate_profile_data()
        # Add the device ID and session string to the profile
        # Create the necessary client-side folders
        # Generate the folder mappings

        # If the server returned 201 REGISTERED, we can proceed with the server-side setup
        #
        # Create the server-side folders based on the mappings on the client side
        # Save all encryption keys into an encrypted 7-zip archive which uses the hash of the
        # user's password has the archive encryption password and upload the archive to the server.

        if self.fs.pman.get_active_profile().domain:
            return RetVal(ResourceExists, 'a user workspace already exists')

        # Parse server string. Should be in the form of (ip/domain):portnum
        host = ''
        port = -1
        if ':' in server:
            addressparts = server.split(':')
            host = addressparts[0]
            try:
                port = int(addressparts[1])
            except ValueError:
                return RetVal(BadParameterValue, 'bad server string')
            serverstring = server
        else:
            host = server
            port = 2001

        # Password requirements aren't really set here, but we do have to draw the
        # line *somewhere*.
        pw = Password()
        status = pw.Set(userpass)
        if status.error():
            return status

        # Add the device to the workspace
        devpair = EncryptionPair()

        status = self.conn.connect(host, port)
        if status.error():
            return status

        regdata = serverconn.register(self.conn, userid, pw.hashstring,
                                      devpair.public)
        if regdata.error():
            return regdata
        self.conn.disconnect()

        # Possible status codes from register()
        # 304 - Registration closed
        # 406 - Payment required
        # 101 - Pending
        # 201 - Registered
        # 300 - Internal server error
        # 408 - Resource exists
        if regdata['status'] in [304, 406, 300, 408]:
            return regdata

        # Just a basic sanity check
        if 'wid' not in regdata:
            return RetVal(InternalError, 'BUG: bad data from serverconn.register()') \
              .set_value('status', 300)

        w = Workspace(self.fs.pman.get_active_profile().db,
                      self.fs.pman.get_active_profile().path)
        status = w.generate(self.fs.pman.get_active_profile(), server,
                            regdata['wid'], pw)
        if status.error():
            return status

        address = '/'.join([regdata['wid'], serverstring])
        status = auth.add_device_session(self.fs.pman.get_active_profile().db,
                                         address, regdata['devid'],
                                         devpair.enctype, devpair.public,
                                         devpair.private, socket.gethostname())
        if status.error():
            return status

        return regdata
Пример #19
0
def init_admin(conn: serverconn.ServerConnection, config: dict) -> RetVal:
    '''Finishes setting up the admin account by registering it, logging in, and uploading a 
	root keycard entry'''

    password = Password('Linguini2Pegboard*Album')
    config['admin_password'] = password

    devid = '14142135-9c22-4d3e-84a3-2aa281f65714'
    devpair = EncryptionPair(
        CryptoString(r'CURVE25519:mO?WWA-k2B2O|Z%fA`~s3^$iiN{5R->#jxO@cy6{'),
        CryptoString(r'CURVE25519:2bLf2vMA?GA2?L~tv<PA9XOw6e}V~ObNi7C&qek>'))
    config['admin_devid'] = devid
    config['admin_devpair'] = devpair

    crepair = EncryptionPair(
        CryptoString(r'CURVE25519:mO?WWA-k2B2O|Z%fA`~s3^$iiN{5R->#jxO@cy6{'),
        CryptoString(r'CURVE25519:2bLf2vMA?GA2?L~tv<PA9XOw6e}V~ObNi7C&qek>'))
    config['admin_crepair'] = devpair

    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;'))
    config['admin_crspair'] = devpair

    epair = EncryptionPair(
        CryptoString(r'CURVE25519:Umbw0Y<^cf1DN|>X38HCZO@Je(zSe6crC6X_C_0F'),
        CryptoString(r'CURVE25519:Bw`F@ITv#sE)2NnngXWm7RQkxg{TYhZQbebcF5b$'))
    config['admin_epair'] = devpair

    status = serverconn.regcode(conn, 'admin', config['admin_regcode'],
                                password.hashstring, devid, devpair, '')
    assert not status.error(), f"init_admin(): regcode failed: {status.info()}"

    status = serverconn.login(conn, config['admin_wid'],
                              CryptoString(config['oekey']))
    assert not status.error(
    ), f"init_admin(): login phase failed: {status.info()}"

    status = serverconn.password(conn, config['admin_wid'],
                                 password.hashstring)
    assert not status.error(
    ), f"init_admin(): password phase failed: {status.info()}"

    status = serverconn.device(conn, devid, devpair)
    assert not status.error(), "init_admin(): device phase failed: " \
     f"{status.info()}"

    entry = keycard.UserEntry()
    entry.set_fields({
        'Name':
        'Administrator',
        'Workspace-ID':
        config['admin_wid'],
        'User-ID':
        'admin',
        'Domain':
        'example.com',
        'Contact-Request-Verification-Key':
        crspair.get_public_key(),
        'Contact-Request-Encryption-Key':
        crepair.get_public_key(),
        'Public-Encryption-Key':
        epair.get_public_key()
    })

    status = serverconn.addentry(conn, entry, CryptoString(config['ovkey']),
                                 crspair)
    assert not status.error(
    ), f"init_admin: failed to add entry: {status.info()}"

    status = serverconn.iscurrent(conn, 1, config['admin_wid'])
    assert not status.error(), "init_admin(): admin iscurrent() success check failed: " \
     f"{status.info()}"

    status = serverconn.iscurrent(conn, 2, config['admin_wid'])
    assert not status.error(), "init_admin(): admin iscurrent() failure check failed: " \
     f"{status.info()}"

    return RetVal()
Пример #20
0
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"})
Пример #21
0
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"})
Пример #22
0
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"
Пример #23
0
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"})