def Authenticate(self,
                     app_id,
                     challenge_data,
                     print_callback=sys.stderr.write):
        """See base class."""

        # Ensure environment variable is present
        plugin_cmd = os.environ.get(SK_SIGNING_PLUGIN_ENV_VAR)
        if plugin_cmd is None:
            raise errors.PluginError(
                '{} env var is not set'.format(SK_SIGNING_PLUGIN_ENV_VAR))

        # Prepare input to signer
        client_data_map, signing_input = self._BuildPluginRequest(
            app_id, challenge_data, self.origin)

        # Call plugin
        print_callback('Please insert and touch your security key\n')
        response = self._CallPlugin([plugin_cmd], signing_input)

        # Handle response
        key_challenge_pair = (response['keyHandle'], response['challengeHash'])
        client_data_json = client_data_map[key_challenge_pair]
        client_data = client_data_json.encode()
        return self._BuildAuthenticatorResponse(app_id, client_data, response)
Ejemplo n.º 2
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