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