php - page - SSL sobre Javascript
inject javascript into url (2)
He visto algunas preguntas similares que no parecen abordar mi caso de uso exacto, y CREO que encontré la respuesta, pero soy un completo novato en lo que se refiere a seguridad, RSA y casi todo asociado con eso. Tengo una familiaridad básica con los conceptos, pero todas las implementaciones reales que he hecho hasta este momento tenían que ver con editar el código de otra persona en lugar de generar el mío. De todos modos, aquí es donde estoy:
Sé que Javascript es un lugar inherentemente malo para hacer encriptación. Alguien podría responder a Man-in-the-Middle y destruir el JS para que termine enviando datos no encriptados a través del cable. DEBE hacerse a través de una conexión HTTPS SSL / TLS, pero ese tipo de hosting cuesta dinero y también lo hacen los certificados oficiales firmados que deberían ir realmente con la conexión.
Dicho esto, creo que la forma en que voy a hacer esto evita la debilidad del hombre en el medio del cifrado JS en virtud del hecho de que solo estoy encriptando una cosa (un hash de contraseña) para un REST llamada de servicio y luego solo usar ese hash de contraseña para firmar solicitudes del cliente con el fin de autenticarlas como provenientes del usuario que reclaman las solicitudes. Esto significa que JS solo es responsable de encriptar un hash de contraseña una vez en la creación de la cuenta de usuario y si el servidor no puede decodificar ese cifrado, entonces sabe que lo tiene.
También voy a guardar cierta información del cliente, en particular el $_SERVER[''REMOTE_ADDR'']
para garantizar que alguien no intercepta el intercambio de registro.
Estoy usando las funciones openssl_pkey_
de PHP para generar una clave asimétrica y la biblioteca Cryptico en el lado del cliente. Mi plan es que el usuario envíe una solicitud de "preinscripción" al servicio REST, lo que hará que el servidor genere una clave, almacene la clave privada y la información del cliente en una base de datos indexada por la dirección de correo electrónico, y luego responda con la clave pública.
El cliente encriptaría el hash de contraseña del usuario usando la clave pública y lo enviaría al servicio REST como otro tipo de solicitud para completar el registro. El servidor descifraría y guardaría el hash de la contraseña, invalidaría la información del cliente y la clave privada para que no pudieran realizarse más registros con esa información, y luego respondería con un código de estado de 200
.
Para iniciar sesión, un usuario escribiría su dirección de correo electrónico y contraseña, la contraseña se codificaría como durante el registro, se agregaría al cuerpo de una solicitud y se censuraría nuevamente para firmar una solicitud a un punto de inicio de sesión que intentaría agregar el hash almacenado a el cuerpo de la solicitud y hash it para validar la firma contra la de la solicitud y así autenticar al usuario. Las solicitudes de datos adicionales al servicio seguirían el mismo proceso de autenticación.
¿Me falta algún agujero evidente? ¿Es posible suplantar el $_SERVER[''REMOTE_ADDR'']
a algo específico? No necesito que la dirección IP sea precisa o la misma que cuando el usuario inicia sesión, solo necesito saber que la misma máquina que ''preinscribió'' y obtuvo una clave pública siguió y completó el registro en lugar de una secuestrador completar el registro para ellos utilizando una clave pública fisgoneada. Por supuesto, supongo que si pueden hacer eso, han secuestrado la cuenta más allá de la recuperación en la creación y el usuario legítimo no podría completar el registro con su propia contraseña, lo cual también está bien.
En pocas palabras , ¿alguien puede seguir pirateando mi servicio a menos que desembolse un servidor SSL real? ¿Evité las debilidades del Javascript como herramienta de encriptación?
Mientras escribo y depuro mi código, lo publicaré aquí si alguien quiere usarlo. Avíseme si estoy dejando mi sitio abierto a cualquier tipo de ataque.
Estas son las funciones que validan las solicitudes de los clientes contra el hash en los encabezados, generan la clave privada, la guardan en la base de datos, responden con la clave pública y descifran y comprueban el hash de la contraseña.
public function validate($requestBody = '''',$signature = '''',$url = '''',$timestamp = '''') {
if (is_array($requestBody)) {
if (empty($requestBody[''signature''])) { return false; }
if (empty($requestBody[''timestamp''])) { return false; }
if ($requestBody[''requestBody''] === null) { return false; }
$signature = $requestBody[''signature''];
$timestamp = $requestBody[''timestamp''];
$requestBody = $requestBody[''requestBody''];
}
if (($requestBody === null) || empty($signature) || empty($timestamp)) { return false; }
$user = $this->get();
if (count($user) !== 1 || empty($user)) { return false; }
$user = $user[0];
if ($signature !== md5("{$user[''pwHash'']}:{$this->primaryKey}:$requestBody:$url:$timestamp")) { return false; }
User::$isAuthenticated = $this->primaryKey;
return $requestBody;
}
public function register($emailAddress = '''',$cipher = '''') {
if (is_array($emailAddress)) {
if (empty($emailAddress[''cipher''])) { return false; }
if (empty($emailAddress[''email''])) { return false; }
$cipher = $emailAddress[''cipher''];
$emailAddress = $emailAddress[''email''];
}
if (empty($emailAddress) || empty($cipher)) { return false; }
$this->primaryKey = $emailAddress;
$user = $this->get();
if (count($user) !== 1 || empty($user)) { return false; }
$user = $user[0];
if (!openssl_private_decrypt(base64_decode($cipher),$user[''pwHash''],$user[''privateKey''])) { return false; }
if (md5($user[''pwHash''].":/api/preRegister") !== $user[''session'']) { return false; }
$user[''session''] = 0;
if ($this->put($user) !== 1) { return false; }
$this->primaryKey = $emailAddress;
User::$isAuthenticated = $this->primaryKey;
return $this->getProfile();
}
public function preRegister($emailAddress = '''',$signature = '''') {
if (is_array($emailAddress)) {
if (empty($emailAddress[''signature''])) { return false; }
if (empty($emailAddress[''email''])) { return false; }
$signature = $emailAddress[''signature''];
$emailAddress = $emailAddress[''email''];
}
if (empty($emailAddress) || empty($signature)) { return false; }
$this->primaryKey = $emailAddress;
$response = $this->makeUserKey($signature);
if (empty($response)) { return false; }
$response[''emailAddress''] = $emailAddress;
return $response;
}
private function makeUserKey($signature = '''') {
if (empty($signature)) { return false; }
$config = array();
$config[''digest_alg''] = ''sha256'';
$config[''private_key_bits''] = 1024;
$config[''private_key_type''] = OPENSSL_KEYTYPE_RSA;
$key = openssl_pkey_new($config);
if (!openssl_pkey_export($key,$privateKey)) { return false; }
if (!$keyDetails = openssl_pkey_get_details($key)) { return false; }
$keyData = array();
$keyData[''publicKey''] = $keyDetails[''key''];
$keyData[''privateKey''] = $privateKey;
$keyData[''session''] = $signature;
if (!$this->post($keyData)) { return false; }
$publicKey = openssl_get_publickey($keyData[''publicKey'']);
$publicKeyHash = md5($keyData[''publicKey'']);
if (!openssl_sign($publicKeyHash,$signedKey,$privateKey)) { return false; }
if (openssl_verify($publicKeyHash,$signedKey,$publicKey) !== 1) { return false; }
$keyData[''signedKey''] = base64_encode($signedKey);
$keyData[''rsa''] = base64_encode($keyDetails[''rsa''][''n'']).''|''.bin2hex($keyDetails[''rsa''][''e'']);
unset($keyData[''privateKey'']);
unset($keyData[''session'']);
return $keyData;
}
Tengo curiosidad por saber por qué un certificado SSL relativamente económico (como el de 1 año de Digicert a $ 175 USD) está fuera de discusión. Especialmente si esto es para un negocio, $ 175 / año es un gasto razonable (funciona hasta alrededor de $ 12.60 USD / mes).
Lo que intenta hacer es reemplazar la necesidad de certificados SSL firmados por una autoridad de certificación con JavaScript personalizado. No soy un experto en seguridad, pero hasta donde sé, la respuesta simple es que esto no es posible.
El hecho básico es que en Internet público, el servidor no puede confiar en lo que dice un cliente, y un cliente no puede confiar en lo que dice el servidor, exactamente por el hombre en los ataques intermedios. La razón por la cual las autoridades de certificación son necesarias para empezar es establecer algún tipo de base de confianza imparcial. Los proveedores de navegadores examinan cuidadosamente las CA, y es la única confianza actualmente disponible en Internet público, aunque ciertamente no es perfecta .