Example #1
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
    }
Example #2
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'
Example #3
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
Example #4
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
Example #5
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()
Example #6
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"})
Example #7
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"})