Exemple #1
0
    def Register(self, app_id, challenge, registered_keys):
        """Registers app_id with the security key.

    Executes the U2F registration flow with the security key.

    Args:
      app_id: The app_id to register the security key against.
      challenge: Server challenge passed to the security key.
      registered_keys: List of keys already registered for this app_id+user.

    Returns:
      RegisterResponse with key_handle and attestation information in it (
        encoded in FIDO U2F binary format within registration_data field).

    Raises:
      U2FError: There was some kind of problem with registration (e.g.
        the device was already registered or there was a timeout waiting
        for the test of user presence).
    """
        client_data = model.ClientData(model.ClientData.TYP_REGISTRATION,
                                       challenge, self.origin)
        challenge_param = self.InternalSHA256(client_data.GetJson())
        app_param = self.InternalSHA256(app_id)

        for key in registered_keys:
            try:
                # skip non U2F_V2 keys
                if key.version != u'U2F_V2':
                    continue
                resp = self.security_key.CmdAuthenticate(
                    challenge_param, app_param, key.key_handle, True)
                # check_only mode CmdAuthenticate should always raise some
                # exception
                raise errors.HardwareError('Should Never Happen')

            except errors.TUPRequiredError:
                # This indicates key was valid.  Thus, no need to register
                raise errors.U2FError(errors.U2FError.DEVICE_INELIGIBLE)
            except errors.InvalidKeyHandleError as e:
                # This is the case of a key for a different token, so we just ignore it.
                pass
            except errors.HardwareError as e:
                raise errors.U2FError(errors.U2FError.BAD_REQUEST, e)

        # Now register the new key
        for _ in range(30):
            try:
                resp = self.security_key.CmdRegister(challenge_param,
                                                     app_param)
                return model.RegisterResponse(resp, client_data)
            except errors.TUPRequiredError as e:
                self.security_key.CmdWink()
                time.sleep(0.5)
            except errors.HardwareError as e:
                raise errors.U2FError(errors.U2FError.BAD_REQUEST, e)

        raise errors.U2FError(errors.U2FError.TIMEOUT)
Exemple #2
0
    def Authenticate(self, app_id, challenge, registered_keys):
        """Authenticates app_id with the security key.

    Executes the U2F authentication/signature flow with the security key.

    Args:
      app_id: The app_id to register the security key against.
      challenge: Server challenge passed to the security key.
      registered_keys: List of keys already registered for this app_id+user.

    Returns:
      SignResponse with client_data, key_handle, and signature_data.  The client
      data is an object, while the signature_data is encoded in FIDO U2F binary
      format.

    Raises:
      U2FError: There was some kind of problem with registration (e.g.
        the device was already registered or there was a timeout while
        waiting for the test of user presence.)
    """
        client_data = model.ClientData(model.ClientData.TYP_AUTHENTICATION,
                                       challenge, self.origin)
        app_param = self.InternalSHA256(app_id)
        challenge_param = self.InternalSHA256(client_data.GetJson())
        num_invalid_keys = 0
        for key in registered_keys:
            try:
                if key.version != 'U2F_V2':
                    continue
                for _ in range(10):
                    try:
                        resp = self.security_key.CmdAuthenticate(
                            challenge_param, app_param, key.key_handle)
                        return model.SignResponse(key.key_handle, resp,
                                                  client_data)
                    except errors.TUPRequiredError:
                        self.security_key.CmdWink()
                        time.sleep(0.5)
            except errors.InvalidKeyHandleError:
                num_invalid_keys += 1
                continue
            except errors.HardwareError as e:
                raise errors.U2FError(errors.U2FError.BAD_REQUEST, e)

        if num_invalid_keys == len(registered_keys):
            # In this case, all provided keys were invalid.
            raise errors.U2FError(errors.U2FError.DEVICE_INELIGIBLE)

        # In this case, the TUP was not pressed.
        raise errors.U2FError(errors.U2FError.TIMEOUT)
    def testSignMultipleIneligible(self, mock_get_u2f_method):
        """Test signing with multiple keys registered, but none eligible."""
        # Prepare u2f mocks
        mock_u2f = mock.MagicMock()
        mock_get_u2f_method.return_value = mock_u2f

        mock_authenticate = mock.MagicMock()
        mock_u2f.Authenticate = mock_authenticate

        mock_authenticate.side_effect = errors.U2FError(
            errors.U2FError.DEVICE_INELIGIBLE)

        # Call LocalAuthenticator
        challenge_item = {
            'key': SIGN_SUCCESS['registered_key'],
            'challenge': SIGN_SUCCESS['challenge']
        }
        challenge_data = [challenge_item, challenge_item]

        authenticator = localauthenticator.LocalAuthenticator('testorigin')

        with self.assertRaises(errors.U2FError) as cm:
            authenticator.Authenticate(SIGN_SUCCESS['app_id'], challenge_data)

        self.assertEquals(cm.exception.code, errors.U2FError.DEVICE_INELIGIBLE)
    def testSignMultipleSuccess(self, mock_get_u2f_method):
        """Test signing with multiple keys registered and one is eligible."""
        # Prepare u2f mocks
        mock_u2f = mock.MagicMock()
        mock_get_u2f_method.return_value = mock_u2f

        mock_authenticate = mock.MagicMock()
        mock_u2f.Authenticate = mock_authenticate

        return_value = model.SignResponse(
            base64.urlsafe_b64decode(SIGN_SUCCESS['key_handle_encoded']),
            base64.urlsafe_b64decode(SIGN_SUCCESS['signature_data_encoded']),
            SIGN_SUCCESS['client_data'])

        mock_authenticate.side_effect = [
            errors.U2FError(errors.U2FError.DEVICE_INELIGIBLE), return_value
        ]

        # Call LocalAuthenticator
        challenge_item = {
            'key': SIGN_SUCCESS['registered_key'],
            'challenge': SIGN_SUCCESS['challenge']
        }
        challenge_data = [challenge_item, challenge_item]

        authenticator = localauthenticator.LocalAuthenticator('testorigin')
        response = authenticator.Authenticate(SIGN_SUCCESS['app_id'],
                                              challenge_data)

        # Validate that u2f authenticate was called with the correct values
        self.assertTrue(mock_authenticate.called)
        authenticate_args = mock_authenticate.call_args[0]
        self.assertEqual(len(authenticate_args), 3)
        self.assertEqual(authenticate_args[0], SIGN_SUCCESS['app_id'])
        self.assertEqual(authenticate_args[1], SIGN_SUCCESS['challenge'])
        registered_keys = authenticate_args[2]
        self.assertEqual(len(registered_keys), 1)
        self.assertEqual(registered_keys[0], SIGN_SUCCESS['registered_key'])

        # Validate authenticator response
        self.assertEquals(response.get('clientData'),
                          SIGN_SUCCESS['client_data_encoded'])
        self.assertEquals(response.get('signatureData'),
                          SIGN_SUCCESS['signature_data_encoded'])
        self.assertEquals(response.get('applicationId'),
                          SIGN_SUCCESS['app_id'])
        self.assertEquals(response.get('keyHandle'),
                          SIGN_SUCCESS['key_handle_encoded'])
Exemple #5
0
    def Authenticate(self,
                     app_id,
                     challenge_data,
                     print_callback=sys.stderr.write):
        """See base class."""
        # If authenticator is not plugged in, prompt
        try:
            device = u2f.GetLocalU2FInterface(origin=self.origin)
        except errors.NoDeviceFoundError:
            print_callback(
                'Please insert your security key and press enter...')
            raw_input()
            device = u2f.GetLocalU2FInterface(origin=self.origin)

        print_callback('Please touch your security key.\n')

        for challenge_item in challenge_data:
            raw_challenge = challenge_item['challenge']
            key = challenge_item['key']

            try:
                result = device.Authenticate(app_id, raw_challenge, [key])
            except errors.U2FError as e:
                if e.code == errors.U2FError.DEVICE_INELIGIBLE:
                    continue
                else:
                    raise

            client_data = base64.urlsafe_b64encode(
                result.client_data.GetJson())
            signature_data = base64.urlsafe_b64encode(result.signature_data)
            key_handle = base64.urlsafe_b64encode(result.key_handle)

            return {
                'clientData': client_data,
                'signatureData': signature_data,
                'applicationId': app_id,
                'keyHandle': key_handle,
            }

        raise errors.U2FError(errors.U2FError.DEVICE_INELIGIBLE)
Exemple #6
0
  def _CallPlugin(self, cmd, input_json):
    """Calls the plugin and validates the response."""
    # Calculate length of input
    input_length = len(input_json)
    length_bytes_le = struct.pack('<I', input_length)
    request = length_bytes_le + input_json.encode()

    # Call plugin
    sign_process = subprocess.Popen(cmd,
                                    stdin=subprocess.PIPE,
                                    stdout=subprocess.PIPE)

    stdout = sign_process.communicate(request)[0]
    exit_status = sign_process.wait()

    # Parse and validate response size
    response_len_le = stdout[:4]
    response_len = struct.unpack('<I', response_len_le)[0]
    response = stdout[4:]
    if response_len != len(response):
      raise errors.PluginError(
          'Plugin response length {} does not match data {} (exit_status={})'
          .format(response_len, len(response), exit_status))

    # Ensure valid json
    try:
      json_response = json.loads(response.decode())
    except ValueError:
      raise errors.PluginError('Plugin returned invalid output (exit_status={})'
                               .format(exit_status))

    # Ensure response type
    if json_response.get('type') != 'sign_helper_reply':
      raise errors.PluginError('Plugin returned invalid response type '
                               '(exit_status={})'
                               .format(exit_status))

    # Parse response codes
    result_code = json_response.get('code')
    if result_code is None:
      raise errors.PluginError('Plugin missing result code (exit_status={})'
                               .format(exit_status))

    # Handle errors
    if result_code == SK_SIGNING_PLUGIN_TOUCH_REQUIRED:
      raise errors.U2FError(errors.U2FError.TIMEOUT)
    elif result_code == SK_SIGNING_PLUGIN_WRONG_DATA:
      raise errors.U2FError(errors.U2FError.DEVICE_INELIGIBLE)
    elif result_code != SK_SIGNING_PLUGIN_NO_ERROR:
      raise errors.PluginError(
          'Plugin failed with error {} - {} (exit_status={})'
          .format(result_code,
                  json_response.get('errorDetail'),
                  exit_status))

    # Ensure response data is present
    response_data = json_response.get('responseData')
    if response_data is None:
      raise errors.PluginErrors(
          'Plugin returned output with missing responseData (exit_status={})'
          .format(exit_status))

    return response_data