Perfil de VPN en iOS: passwordReference, no la contraseña
Una referencia del llavero es un asa, no el secreto. Pasar bytes explica el error -50, el fallo tras reiniciar y la trampa de rotación en los perfiles de VPN guardados.
Instalas un perfil, el túnel se levanta y todo parece terminado. Luego el teléfono se reinicia, on-demand despierta la conexión sin ninguna app en primer plano y el túnel se niega a arrancar. El perfil sigue ahí y la contraseña sigue siendo correcta, y por eso este fallo manda a la gente a buscar en el sitio equivocado. Una Network Extension no puede leer tu secreto del perfil: necesita una referencia del llavero que pueda resolver por su cuenta.
El recorrido de abajo sigue un solo intento de conexión por esa entrega, desde la consulta que construye la referencia hasta el reinicio que la pone a prueba.
El perfil guarda un asa, no el secreto
NEVPNProtocol expone passwordReference y sharedSecretReference por un motivo. Cuando el demonio inicia un túnel puede que no se ejecute dentro de tu proceso, así que no puede llamar a tu código y pedir una contraseña. Lee una referencia persistente del llavero, la resuelve en su propio contexto y obtiene el elemento él mismo.
Construyes esa referencia pidiéndosela al llavero, que es una petición distinta de pedir los datos:
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrService as String: "vpn-credentials",
kSecAttrAccount as String: "user",
kSecReturnPersistentRef as String: true,
kSecMatchLimit as String: kSecMatchLimitOne
]Entrega el objeto devuelto al protocolo, no los datos del elemento. En la práctica el perfil registra dónde vive el secreto, y por eso una copia de seguridad de la configuración no duplica la contraseña en silencio dentro de un plist.
Por qué la API responde -50
errSecParam significa que la petición o el valor estaban mal formados para la operación, y es el error que más se ve cuando se mezclan las peticiones de referencia y de datos. Pedir datos mientras el código receptor espera una referencia, asignar bytes en bruto a passwordReference o consultar con un grupo de acceso que la app no puede usar, todos lo producen.
Las bibliotecas envoltorio invitan a esto. Un ayudante que devuelve bytes no es intercambiable con uno que devuelve una referencia, así que la reparación suele ser otra consulta y no otra contraseña.
La misma desavenencia explica un perfil que se guarda sin queja y luego no conecta: lo que se guardó es un blob, no un asa, y el demonio no tiene nada que resolver.
Guarda bien, falla al conectar
Guardar ejercita el contexto de tu app. Conectar ejercita el del demonio, y los dos no tienen el mismo acceso. Por eso un perfil puede pasar todas las pruebas del simulador y fallar en el primer arranque on-demand.
Esa es también la forma del síntoma del reinicio: funciona mientras la app está abierta y muere cuando el sistema inicia el túnel por su cuenta.
Prueba el camino en segundo plano a propósito antes de publicar. Dispara una conexión on-demand con la app cerrada y luego lee la consola del dispositivo en lugar del log de la app, porque el fallo lo registra el proceso que intentó resolver la referencia. Si la consola muestra que el demonio rechaza el elemento, la referencia está bien y el problema es la accesibilidad del elemento.
La accesibilidad decide si un reinicio funciona
El elemento tiene que ser legible por el proceso del sistema que arranque el túnel, y eso es un público más amplio que tu app mientras está en primer plano. Esa restricción elige el atributo de accesibilidad por ti. Un elemento protegido hasta el primer desbloqueo está disponible para un arranque en segundo plano después de que el dispositivo se haya desbloqueado una vez; un elemento que exige que el dispositivo esté desbloqueado ahora mismo no lo está.
Elegir la clase más estricta es tentador y es exactamente lo que rompe un perfil on-demand en un teléfono bloqueado.
Después de cambiar la clase, recrea el elemento en lugar de editar atributos en el existente, y vuelve a guardar el protocolo con una referencia nueva. Los cambios de atributos en un elemento existente no siempre producen el comportamiento que implica la documentación, y un elemento reconstruido elimina la duda.
La rotación es donde se pudren los perfiles guardados
Cambiar la contraseña en el llavero no cambia automáticamente aquello a lo que apunta el perfil guardado. Borrar el elemento es peor: el perfil conserva una referencia a algo que ya no existe, y el fallo parece un rechazo de autenticación.
Rota en un solo orden. Actualiza el elemento, consulta una referencia persistente nueva, vuelve a guardar el protocolo y luego conecta con la app cerrada para demostrar que el demonio todavía puede resolverla. Mantén la contraseña del usuario y el secreto compartido en elementos separados para que cada uno rote según su propio calendario, y no registres nunca el diccionario de la consulta ni la referencia misma. El asa no es un secreto, pero es un mapa hacia uno.