Commit eea4e515 authored by Xan Lopez's avatar Xan Lopez

Import passwords from the ephy/gecko profile on first run.

This feature requires NSS, so NSS is added as an optional dependency,
enabled by default. Can be disabled with --disable-nss, but then
passwords won't be imported.
parent ec783faa
// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
......@@ -277,6 +277,26 @@ AM_CONDITIONAL([ENABLE_SEED],[test "$enable_seed" = "yes"])
AC_SUBST([EPIPHANY_FEATURES])
# ***
# NSS
# ***
AC_MSG_CHECKING([whether NSS support is requested])
AC_ARG_ENABLE([nss],
[AS_HELP_STRING([--enable-nss], [Enable NSS support (default: enabled)])],
[], [enable_nss=yes])
AC_MSG_RESULT([$enable_nss])
if test "$enable_nss" = "yes"; then
EPIPHANY_FEATURES="$EPIPHANY_FEATURES nss"
PKG_CHECK_MODULES([NSS], [nss])
AC_DEFINE([ENABLE_NSS], [1], [Define to compile with NSS support])
fi
AM_CONDITIONAL([ENABLE_NSS],[test "$enable_nss" = "yes"])
AC_SUBST([EPIPHANY_FEATURES])
# *******************
# Additional features
# *******************
......@@ -482,5 +502,6 @@ Epiphany was configured with the following options:
NetworkManager support : $enable_network_manager
GObject introspection : $enable_introspection
Seed support : $enable_seed
NSS support : $enable_nss
Build tests : $enable_tests
"
......@@ -140,6 +140,18 @@ libephymain_la_SOURCES += \
libephymain_la_CFLAGS += $(SEED_CFLAGS)
endif # ENABLE_SEED
if ENABLE_NSS
NOINST_H_FILES += \
ephy-nss-glue.h \
$(NULL)
libephymain_la_SOURCES += \
ephy-nss-glue.c
$(NULL)
libephymain_la_CFLAGS += $(NSS_CFLAGS)
endif # ENABLE_NSS
epiphany_SOURCES = ephy-main.c
epiphany_CPPFLAGS = \
......@@ -181,6 +193,10 @@ if ENABLE_SEED
epiphany_LDADD += $(SEED_LIBS)
endif # ENABLE_SEED
if ENABLE_NSS
epiphany_LDADD += $(NSS_LIBS)
endif # ENABLE_NSS
if ENABLE_NETWORK_MANAGER
epiphany_LDADD += \
$(NETWORK_MANAGER_LIBS)
......
/*
* Copyright © 2009 Igalia S.L.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
*/
/* Portions of this file based on Chromium code.
* License block as follows:
*
* Copyright (c) 2009 The Chromium Authors. All rights reserved.
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*
* The LICENSE file from Chromium can be found in the LICENSE.chromium
* file.
*/
#include "config.h"
#include "ephy-nss-glue.h"
#include "ephy-file-helpers.h"
#include <nss.h>
#include <pk11pub.h>
#include <pk11sdr.h>
static gboolean nss_initialized = FALSE;
static PK11SlotInfo *db_slot = NULL;
gboolean ephy_nss_glue_init ()
{
char *config_dir, *modspec;
SECStatus rv;
config_dir = g_build_filename (ephy_dot_dir (),
"mozilla", "epiphany",
NULL);
rv = NSS_Init (config_dir);
if (rv < 0) {
g_free (config_dir);
return FALSE;
}
modspec = g_strdup_printf ("configDir='%s' tokenDescription='Firefox NSS database' "
"flags=readOnly", config_dir);
db_slot = SECMOD_OpenUserDB (modspec);
g_free (config_dir);
g_free (modspec);
if (!db_slot)
return FALSE;
nss_initialized = TRUE;
return TRUE;
}
void ephy_nss_glue_close ()
{
if (db_slot) {
PK11_FreeSlot (db_slot);
db_slot = NULL;
}
NSS_Shutdown ();
nss_initialized = FALSE;
}
typedef struct SDRResult
{
SECItem keyid;
SECAlgorithmID alg;
SECItem data;
} SDRResult;
static SEC_ASN1Template g_template[] = {
{ SEC_ASN1_SEQUENCE, 0, NULL, sizeof (SDRResult) },
{ SEC_ASN1_OCTET_STRING, offsetof(SDRResult, keyid) },
{ SEC_ASN1_INLINE | SEC_ASN1_XTRN, offsetof(SDRResult, alg),
SEC_ASN1_SUB(SECOID_AlgorithmIDTemplate) },
{ SEC_ASN1_OCTET_STRING, offsetof(SDRResult, data) },
{ 0 }
};
static SECStatus
unpadBlock(SECItem *data, int blockSize, SECItem *result)
{
SECStatus rv = SECSuccess;
int padLength;
int i;
result->data = 0;
result->len = 0;
/* Remove the padding from the end if the input data */
if (data->len == 0 || data->len % blockSize != 0) { rv = SECFailure; goto loser; }
padLength = data->data[data->len-1];
if (padLength > blockSize) { rv = SECFailure; goto loser; }
/* verify padding */
for (i=data->len - padLength; (uint32)i < data->len; i++) {
if (data->data[i] != padLength) {
rv = SECFailure;
goto loser;
}
}
result->len = data->len - padLength;
result->data = (unsigned char *)PORT_Alloc(result->len);
if (!result->data) { rv = SECFailure; goto loser; }
PORT_Memcpy(result->data, data->data, result->len);
if (padLength < 2) {
return SECWouldBlock;
}
loser:
return rv;
}
static SECStatus
pk11Decrypt (PK11SlotInfo *slot, PLArenaPool *arena,
CK_MECHANISM_TYPE type, PK11SymKey *key,
SECItem *params, SECItem *in, SECItem *result)
{
PK11Context *ctx = 0;
SECItem paddedResult;
SECStatus rv;
paddedResult.len = 0;
paddedResult.data = 0;
ctx = PK11_CreateContextBySymKey (type, CKA_DECRYPT, key, params);
if (!ctx) {
rv = SECFailure;
goto loser;
}
paddedResult.len = in->len;
paddedResult.data = (unsigned char*)PORT_ArenaAlloc (arena, paddedResult.len);
rv = PK11_CipherOp (ctx, paddedResult.data,
(int*)&paddedResult.len, paddedResult.len,
in->data, in->len);
if (rv != SECSuccess)
goto loser;
PK11_Finalize(ctx);
/* Remove the padding */
rv = unpadBlock (&paddedResult, PK11_GetBlockSize(type, 0), result);
if (rv)
goto loser;
loser:
if (ctx)
PK11_DestroyContext (ctx, PR_TRUE);
return rv;
}
static SECStatus
PK11SDR_DecryptWithSlot (PK11SlotInfo *slot, SECItem *data, SECItem *result, void *cx)
{
SECStatus rv = SECSuccess;
PK11SymKey *key = NULL;
CK_MECHANISM_TYPE type;
SDRResult sdrResult;
SECItem *params = NULL;
SECItem possibleResult = { (SECItemType)0, NULL, 0 };
PLArenaPool *arena = NULL;
arena = PORT_NewArena (SEC_ASN1_DEFAULT_ARENA_SIZE);
if (!arena) {
rv = SECFailure;
goto loser;
}
/* Decode the incoming data */
memset (&sdrResult, 0, sizeof sdrResult);
rv = SEC_QuickDERDecodeItem (arena, &sdrResult, g_template, data);
if (rv != SECSuccess)
goto loser; /* Invalid format */
/* Get the parameter values from the data */
params = PK11_ParamFromAlgid (&sdrResult.alg);
if (!params) {
rv = SECFailure;
goto loser;
}
/* Use triple-DES (Should look up the algorithm) */
type = CKM_DES3_CBC;
key = PK11_FindFixedKey (slot, type, &sdrResult.keyid, cx);
if (!key) {
rv = SECFailure;
} else {
rv = pk11Decrypt (slot, arena, type, key, params,
&sdrResult.data, result);
}
loser:
if (arena)
PORT_FreeArena (arena, PR_TRUE);
if (key)
PK11_FreeSymKey(key);
if (params)
SECITEM_ZfreeItem(params, PR_TRUE);
if (possibleResult.data)
SECITEM_ZfreeItem(&possibleResult, PR_FALSE);
return rv;
}
char * ephy_nss_glue_decrypt (const unsigned char *data, gsize length)
{
char *plain = NULL;
SECItem request, reply;
SECStatus result;
result = PK11_Authenticate (db_slot, PR_TRUE, NULL);
if (result != SECSuccess)
return NULL;
request.data = (unsigned char*)data;
request.len = length;
reply.data = NULL;
reply.len = 0;
result = PK11SDR_DecryptWithSlot (db_slot, &request, &reply, NULL);
if (result == SECSuccess)
plain = g_strndup ((const char*)reply.data, reply.len);
SECITEM_FreeItem (&reply, PR_FALSE);
return plain;
}
/*
* Copyright © 2009 Igalia S.L.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
*/
#ifndef EPHY_NSS_GLUE_H
#define EPHY_NSS_GLUE_H
#include <glib.h>
gboolean ephy_nss_glue_init (void);
void ephy_nss_glue_close (void);
char * ephy_nss_glue_decrypt (const unsigned char *, gsize);
#endif
......@@ -17,12 +17,28 @@
*
*/
/* Portions of this file based on Chromium code.
* License block as follows:
*
* Copyright (c) 2009 The Chromium Authors. All rights reserved.
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*
* The LICENSE file from Chromium can be found in the LICENSE.chromium
* file.
*/
#include "config.h"
#include "ephy-file-helpers.h"
#include "ephy-profile-migration.h"
#include "ephy-file-helpers.h"
#ifdef ENABLE_NSS
#include "ephy-nss-glue.h"
#endif
#include <glib/gi18n.h>
#include <gnome-keyring.h>
#include <libsoup/soup-gnome.h>
/*
......@@ -31,7 +47,7 @@
* - Add your function at the end of the 'migrators' array
*/
#define PROFILE_MIGRATION_VERSION 1
#define PROFILE_MIGRATION_VERSION 2
typedef void (*EphyProfileMigrator) (void);
......@@ -91,8 +107,248 @@ migrate_cookies ()
g_free (dest);
}
#ifdef ENABLE_NSS
static gchar*
_g_utf8_substr(const gchar* string, gint start, gint end)
{
gchar *start_ptr, *output;
gsize len_in_bytes;
glong str_len = g_utf8_strlen (string, -1);
if (start > str_len || end > str_len)
return NULL;
start_ptr = g_utf8_offset_to_pointer (string, start);
len_in_bytes = g_utf8_offset_to_pointer (string, end) - start_ptr + 1;
output = g_malloc0 (len_in_bytes + 1);
return g_utf8_strncpy (output, start_ptr, end - start + 1);
}
static char*
decrypt (const char *data)
{
unsigned char *plain;
gsize out_len;
/* The old style password is encoded in base64. They are identified
* by a leading '~'. Otherwise, we should decrypt the text.
*/
plain = g_base64_decode (data, &out_len);
if (data[0] != '~') {
char *decrypted;
decrypted = ephy_nss_glue_decrypt (plain, out_len);
g_free (plain);
plain = (unsigned char*)decrypted;
}
return (char*)plain;
}
static void
parse_and_decrypt_signons (const char *signons)
{
int version;
gchar **lines;
int i;
lines = g_strsplit (signons, "\r\n", -1);
if (!g_ascii_strncasecmp (lines[0], "#2c", 3))
version = 1;
else if (!g_ascii_strncasecmp (lines[0], "#2d", 3))
version = 2;
else if (!g_ascii_strncasecmp (lines[0], "#2e", 3))
version = 3;
else
goto out;
/* Skip the never-saved list */
for (i = 1; lines[i] && !g_str_equal (lines[i], "."); i++) {
;
}
i++;
/*
* Read saved passwords. The information is stored in blocks
* separated by lines that only contain a dot. We find a block by
* the separator and parse them one by one.
*/
while (lines[i]) {
size_t begin = i;
size_t end = i + 1;
const char *realmBracketBegin = " (";
const char *realmBracketEnd = ")";
SoupURI *uri = NULL;
char *realm = NULL;
while (lines[end] && !g_str_equal (lines[end], "."))
end++;
i = end + 1;
/* A block has at least five lines */
if (end - begin < 5)
continue;
/* The first line is the site URL.
* For HTTP authentication logins, the URL may contain http realm,
* which will be in bracket:
* sitename:8080 (realm)
*/
if (g_strstr_len (lines[begin], -1, realmBracketBegin)) {
char *start_ptr, *end_ptr;
char *full_url, *url;
glong start, end;
/* In this case the scheme may not exist. We assume that the
* scheme is HTTP.
*/
if (!g_strstr_len (lines[begin], -1, "://"))
full_url = g_strconcat ("http://", lines[begin], NULL);
else
full_url = g_strdup (lines[begin]);
start_ptr = g_strstr_len (full_url, -1, realmBracketBegin);
start = g_utf8_pointer_to_offset (full_url, start_ptr);
url = _g_utf8_substr (full_url, 0, start);
uri = soup_uri_new (url);
g_free (url);
start += strlen (realmBracketBegin);
end_ptr = g_strstr_len (full_url, -1, realmBracketEnd);
end = g_utf8_pointer_to_offset (full_url, end_ptr);
realm = _g_utf8_substr (full_url, start, end);
g_free (full_url);
} else {
/* Don't have HTTP realm. It is the URL that the following
* password belongs to.
*/
uri = soup_uri_new (lines[begin]);
}
if (!SOUP_URI_VALID_FOR_HTTP (uri)) {
soup_uri_free (uri);
g_free (realm);
continue;
}
++begin;
/* There may be multiple username/password pairs for this site.
* In this case, they are saved in one block without a separated
* line (contains a dot).
*/
while (begin + 4 < end) {
char *username = NULL;
char *password = NULL;
guint32 item_id;
/* The username */
begin++; /* Skip username element */
username = decrypt (lines[begin++]);
/* The password */
/* The element name has a leading '*' */
if (lines[begin][0] == '*') {
begin++; /* Skip password element */
password = decrypt (lines[begin++]);
} else {
/* Maybe the file is broken, skip to the next block */
g_free (username);
break;
}
/* The action attribute for from the form element. This line
* exists in version 2 or above
*/
if (version >= 2) {
if (begin < end)
/* Skip it */ ;
begin ++;
/* Version 3 has an extra line for further use */
if (version == 3)
begin++;
}
if (username && password)
gnome_keyring_set_network_password_sync (NULL,
username,
realm,
uri->host,
NULL,
uri->scheme,
NULL,
uri->port,
password,
&item_id);
g_free (username);
g_free (password);
}
soup_uri_free (uri);
g_free (realm);
}
out:
g_strfreev (lines);
}
#endif
static void
migrate_passwords ()
{
#ifdef ENABLE_NSS
char *dest, *contents, *gecko_passwords_backup;
gsize length;
GError *error = NULL;
dest = g_build_filename (ephy_dot_dir (),
"mozilla", "epiphany", "signons3.txt",
NULL);
if (!g_file_test (dest, G_FILE_TEST_EXISTS)) {
g_free (dest);
dest = g_build_filename (ephy_dot_dir (),
"mozilla", "epiphany", "signons2.txt",
NULL);
if (!g_file_test (dest, G_FILE_TEST_EXISTS)) {
g_free (dest);
return;
}
}
if (!ephy_nss_glue_init ())
return;
if (!g_file_get_contents (dest, &contents, &length, &error)) {
g_free (dest);
}
parse_and_decrypt_signons (contents);
/* Save the contents in a backup directory for future data
extraction when we support more features */
gecko_passwords_backup = g_build_filename (ephy_dot_dir (),
"gecko-passwords.txt", NULL);
if (!g_file_set_contents (gecko_passwords_backup, contents,
-1, &error)) {
g_error_free (error);
}
g_free (gecko_passwords_backup);
g_free (contents);
ephy_nss_glue_close ();
#endif
}
const EphyProfileMigrator migrators[] = {
migrate_cookies
migrate_cookies,
migrate_passwords
};
#define PROFILE_MIGRATION_FILE ".migrated"
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment