A special client certificate will be created on first start-up (after installation resp. update). This certificate can be used to identify and authenticate a user to web sites that ask for it. No personally identifiable information is included in the certificate; it only identifies a user account, not a physical person. Any binding between the user account(s) and an actual person would need to be established out-of-band. This is similar to using email addresses for identification and authentication purposes (an established practice for most sites that offer services to the public).
The client certificate will be signed by a fake CA. This allows servers to specifically ask for the client certificate created by Browse (by presenting an "accepted CAs" list with the fake CA in it) while preventing confusing "Select client certificate" pop-ups for other browsers. Publishing the CA private key is not a risk because it's only used to influence the selection of the client certificate, not for any kind of "trust" decision. One aspect that still needs some consideration are XSS (cross-site scripting) attacks. Content from a third party website could cause the browser to connect to a web site using SSO with the client certificate. The browser would automatically present the certificate to the server and thus potentially authorise any action that the remote site tries to trigger. Additional dependencies: - "openssl" executable - "certutil" and "pk12util" executables Signed-off-by: Sascha Silbe <sascha-...@silbe.org> --- browse-sso-pseudo-ca.cert.pem | 20 +++++++++ browse-sso-pseudo-ca.privkey.pem | 27 ++++++++++++ cert8.db | Bin 65536 -> 65536 bytes key3.db | Bin 0 -> 16384 bytes secmod.db | Bin 0 -> 16384 bytes webactivity.py | 85 +++++++++++++++++++++++++++++++++---- 6 files changed, 122 insertions(+), 10 deletions(-) diff --git a/browse-sso-pseudo-ca.cert.pem b/browse-sso-pseudo-ca.cert.pem new file mode 100644 index 0000000..fd9691a --- /dev/null +++ b/browse-sso-pseudo-ca.cert.pem @@ -0,0 +1,20 @@ +-----BEGIN CERTIFICATE----- +MIIDQjCCAiqgAwIBAgIJAOyLmiUICdRkMA0GCSqGSIb3DQEBBQUAMB8xHTAbBgNV +BAMTFEJyb3dzZS1TU08tUHNldWRvLUNBMB4XDTExMDEzMTE4MDMyMVoXDTM4MDYx +NzE4MDMyMVowHzEdMBsGA1UEAxMUQnJvd3NlLVNTTy1Qc2V1ZG8tQ0EwggEiMA0G +CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDHbsal0Ib96+Dg9v8zmGQwtczFOLyd +J2s5ifwF1VuUDVmm47ooDgycc6i6ULR3AoSOMu3/jvFCT5uV/wZq7sXvq2LUmiJ8 +W/fNbT9GaBoAphBoBalqrTsDROpZXsPkdkm5eN1ZtLeDDoGCX8+fYeqo61Hv2fSh +ee7DloCYZ7TvYT0+W2hBUOvf7MFEEl/8p8/FyevB6F9Qs0RCinbUwVDdlg961Sj6 +zEa7GzgFNK1UdiyY5iXthYFdQVW1MPCgNmaIOjelMfy9YOwQv4Bs6Z4Zisi4Y0U8 +lChunySRgtckVtJfrSgGoPbT4UuLBrwtP9G7WmdOb1gv3TC9KHaZnDJvAgMBAAGj +gYAwfjAdBgNVHQ4EFgQU2tNCUr0YkKsk+MImV5O794MKspUwTwYDVR0jBEgwRoAU +2tNCUr0YkKsk+MImV5O794MKspWhI6QhMB8xHTAbBgNVBAMTFEJyb3dzZS1TU08t +UHNldWRvLUNBggkA7IuaJQgJ1GQwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQUF +AAOCAQEAThyMLpwfu/9UKn6nBRgFdTTmZ2VRJARtiXkT0hPe3zHKSLUKmSjxhTSN +zcM7R+Dx3Wi2i0w8fNQcxtYima2HZQTKT4qhCPrggkXxgtnolPQ4yrFg+4DWxGFa +Ng6uajGJmcnzt5Q+0esaxohc79caELs7Y8yzp/JuZgnXfcKYCVAha8Cg6T5e+eZo +7XJHJPRIAuvgqobaQpX9iB8jace1UVf0h0ftnZ6FgZMjvtjONl6TqQrOWgEA3Yzb +7pO8fVfOlEtwQzKY5Ji+aqvStM/r7/B6dAIljqsqlnhVenlWzQDKpLa4VeA5/xaE +C8enZzxEi1IG76bB9dIELyKezcfIyQ== +-----END CERTIFICATE----- diff --git a/browse-sso-pseudo-ca.privkey.pem b/browse-sso-pseudo-ca.privkey.pem new file mode 100644 index 0000000..64c135a --- /dev/null +++ b/browse-sso-pseudo-ca.privkey.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpQIBAAKCAQEAx27GpdCG/evg4Pb/M5hkMLXMxTi8nSdrOYn8BdVblA1ZpuO6 +KA4MnHOoulC0dwKEjjLt/47xQk+blf8Gau7F76ti1JoifFv3zW0/RmgaAKYQaAWp +aq07A0TqWV7D5HZJuXjdWbS3gw6Bgl/Pn2HqqOtR79n0oXnuw5aAmGe072E9Plto +QVDr3+zBRBJf/KfPxcnrwehfULNEQop21MFQ3ZYPetUo+sxGuxs4BTStVHYsmOYl +7YWBXUFVtTDwoDZmiDo3pTH8vWDsEL+AbOmeGYrIuGNFPJQobp8kkYLXJFbSX60o +BqD20+FLiwa8LT/Ru1pnTm9YL90wvSh2mZwybwIDAQABAoIBAFowqLl4MnRG5i4W +xZbJxhWZQf+3BuVzXDRrSIRC1uV/nBmzRw6yO9XNIHMR6Gshwlykf6lhNY4tfkk5 +See2D+GxekJ3aj0fQlOuojzu+0Nr1mOUm+dYbCWwVuMPzjtVm0W7eS8GYS9tsfgJ +6FVF7N9zfFyiDtWv1OCzAXjq/ZJ7c+j1AsT0Tb9TnVO9jvwKoJa54LV6LIUf98+n +8czPaZadpGy4s7BoZ3L+yt6odnysmMs+98hpOj7rg2uGSrnU1IaKkp7CdjdIaBq4 +3vBZ8RsNlW5wa0WSQSj0hOUBn+2moJA1qUxmblkDtmOi1bRYmqPWHyuiOEUlnQM8 +ogmAfDECgYEA+Op577svjuz1o+EK8C/J87q976ZNGRWE2KwUT3O6WIQNgqY9RtF8 +4aZmkbGx0dFnMkGfW+TGXFcD3u8Hn9vP1ZdFhbP2hxdD1IF75U5D78IT0/eOfvFr +eCkbzsoL4tsxt40Ebv8KQrubAGbShVdwxHTKCfltWZFFGnxyuvNCsSMCgYEAzRvH +zWWb+c90Ah1v7XrvIRSCcfbaz+/tO6/kgxGezDYpHQXRUdXe2ULhRcNWuheQN7iF +LZQqWS07bCzqkfH1f1fjh+aDKQBafjuyJwDJxf/+qkwavcbsB4Ac7fFIgrmwAx07 +2/WbjSTrYTUWpReSkCpY2dYBofYTiJbmBfLs/EUCgYEAigLv4uTlhJiLxfZz6yKE +Fdg3kZWib8MHql8Wz8q5ynRFTWhA13A1jqOFgUiF8HDrh+jso7Xf6bjxU30yvAbT +YHkEOhLDILnciQnWGRjhACGafs9muam/zZr1aR2Lo5enJD9S8vwDw2ZjlcBs/zOx +OawLjaY3ZA7wwrO3pUJVewMCgYEAuaBaw1kiQIOvyfo6QgLwSE/3foyam3XYjhwy +Ayz/OnIJ42pQdm/Ir3gHyMtwwhpxOvyUCxv895goH07HPC5usEEqeuPz8DeRroz4 +PrRH6Bo2sIkP6pENK/yWMgIcTbMfyLG8auVtUeAH5oHVbLRYwICSyRMVhy7dntBy +fQ/NysECgYEApuEWZe38qyDAJdasWMiOnyKIc1kowgKlg3IHFmiK8ST+kTvVRbjE +E9rnOedR2Prn414fuf5ez5LinxXad7/O/KrO4NUhEjKwwlYzp/8TWbIziZWd+SBd +Yxb0/DwwKXLOKO18npF/n39UAjOF3nLXKpLP/iccH/FUTEQ1U486k7g= +-----END RSA PRIVATE KEY----- diff --git a/cert8.db b/cert8.db index 7b3d82faf7eb6646fa0021bebcaae5f8cbd5cda7..4ef537f088faed202171be9e98f5ffb5558ce51a 100644 GIT binary patch delta 1177 zcmZo@U}<PznPAPxztQH1J)_8EMh8Wni$<CH!TP%T|MhhBHyb+q*Q@7XVqj1-kT;Yy zkY-~JWnmT;aVp9$FHY4B4))g#C{8U+$=7vuWWpxMz`@AC-~fWmZVaLZP0UUPO-xz~ zn3))vm^c~Ubk9=d;JlJzz{|#|)#lOmotKf3m6ZX_s(MVj4CKUl4Gj$pjSUSg42+Eo zqr`cQEey;I&7oX06R;T2#HfU9D<dlda}y&!gFzD`7gG}>Bg65$V@ofz{eAu5!MFPV z#xqh3ww^g^v1hJ&wq@rZ)~nG|cq5lR-lf6EGpBgPu7EA&Of7vzZ~ymwbn>4)^*>wI zyQA+{CtaDPR1^LEY_7dqh7`jxfehA_S!=DCU0y}T9ez^gxwGPK<d*Hte2q=<=jSKB zTJbvY{mm~6E8iWS)-WS|%lkxIyXXwZfY<lm9IST{ivP3x{Lz!I559;G*zDreRd(fI zz};#5RaZ5BopIYOZNX}?Hl$2v#xvEot&Opcp<4|;EHF#!urgn2_-Ajz8-e`|IWOl) zcAeOf>}oSbBX7RS#HQ;iVVC09YOpQ%cKM-qH`^Xv`-{7y(*5!y^zRz%)hL@e$0(nP znUR5Uabtr)eVu_UFy3YPSj1RFZe4Z?+AA?(waSk}YT=W2e{bg6G}XW#B(2QiVc^z) zRl!2#C4|#Y6H<EM0U0RF!fL?G$oPM9$Zo-UV3hdD^ytlz-~B&Ct8O`~1Z%0uv-H$J z6_(u2O5sbw_wE~>^4Q8XQ{!W+N$=Ui*6t5J-p$z7?PF7OMdsKwrI~BnQ&~>=cP-@j z^`Oc1W7EwSQ@&W7+L-XW;o6bJC^NowS%#f6Pk!D$#qQ#3sbd{6@2^V<?6yumvw8Wa zyfn`1we^Q)a0V!5A6W3xF7D^EjJHMZDqlR9UO!mXcFSq%-wt`@%;Q@F!@sn<znwd; zwQ;iYz8mMv;wG=;Iv2&raJT37yUBZM!_Q6eE^s!Q@npuntksvcoPYiPLsbcrYTs(D zX%(SWm0@QYPA%ECBlLmgf3X(s<IB@+T)KnU-Y+}&^%9G|lIy&)$4{KZn!^|o*@#dU z-TbGBg;$D;!NT6ruE18>Y@TVVexUwYz14d0dagjc@uDj*w=**^FfxcTFkrQfL5qQb gcng^cW<(A)28L>&kqicvh5`nBY|NYQty2mF0EP;@^Z)<= delta 72 zcmV-O0Jr~ufCPYm1duxc46!`KKLHSv0YD)H)-wDuvoS#YF0(*D+5@xvas&+)2mm-g eK|XLj8#bObWiL@L%`Uet%`UOgw=T2It}{`H7aEQL diff --git a/key3.db b/key3.db new file mode 100644 index 0000000000000000000000000000000000000000..b0e3d3a7505bdf237e5dca676300af0db907025e GIT binary patch literal 16384 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 OAOHk_01)`U5cn73-h|@- literal 0 HcmV?d00001 diff --git a/secmod.db b/secmod.db new file mode 100644 index 0000000000000000000000000000000000000000..5e5f69659d2a3dc807fa6a4eea8734eb0556ed0b GIT binary patch literal 16384 zcmeI&u}Z^07{Kw*R!Rq{E^Z<R4!0Hh0KuUy5|l!Hfi$_4(3q4Y1s!}0N5N-ub8@J! zA$rMGv<MCkrO5w-<L<t@e0TZHC!NDUL|P)dPa+3)D0?D1wyT?TR|C7=B-%~iEV)hh z+1=9S>ZteBZR62(_u2Tl9Ebn{2q1s}0tg_000IagfB*srAb<b@2q1s}0tg_000Iag zfWWp0wB&Vv*?aCSd)sm&?tuUT2q1s}0tg_000IagfWY4bI-=xd?Txr^3q`iM<k97L ztj@E_6j_j}kv|-(lfgh;<k2EEcGp9gXK^x(lETw{twK{&qr$|=ji<GmncJ^PX~OF~ z^0e#w(L}2_4W^~17V{{mOjAOyCd0KlZiD~5noA|N-sdM*`OIWzrVNW@UL|=}uef*k okL^>>UiH|vAMxAoy}c6%Ab<b@2q1s}0tg_000Iag@ZSYK03j$ussI20 literal 0 HcmV?d00001 diff --git a/webactivity.py b/webactivity.py index d744ab1..eed2f44 100644 --- a/webactivity.py +++ b/webactivity.py @@ -15,15 +15,17 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -import os import logging from gettext import gettext as _ +import os +import subprocess import gobject gobject.threads_init() import gtk import base64 +import random import time import shutil import sqlite3 @@ -49,7 +51,7 @@ from sugar.graphics.icon import Icon from sugar.graphics.toolbarbox import ToolbarButton from sugar import mime -PROFILE_VERSION = 2 +PROFILE_VERSION = 3 _profile_version = 0 _profile_path = os.path.join(activity.get_activity_root(), 'data/gecko') @@ -64,8 +66,9 @@ if _profile_version < PROFILE_VERSION: if not os.path.exists(_profile_path): os.mkdir(_profile_path) - shutil.copy('cert8.db', _profile_path) - os.chmod(os.path.join(_profile_path, 'cert8.db'), 0660) + for file_name in ['cert8.db', 'key3.db', 'secmod.db']: + shutil.copy(file_name, _profile_path) + os.chmod(os.path.join(_profile_path, file_name), 0660) f = open(_version_file, 'w') f.write(str(PROFILE_VERSION)) @@ -129,6 +132,52 @@ def _seed_xs_cookie(): _logger.debug('seed_xs_cookie: Updated cookie successfully') +def _create_sso_certificate(): + """ + Ensure X.509 client certificate suitable for authentication exists. + + We ship the private key of a fake CA to sign the key with. This allows + us to specifically "ask" for this particular client certificate by adding + the public key of the fake CA on the server side, so it gets sent as part + of the "accepted / trusted CA" list during TLS handshake. Publishing the + CA private key is not a risk because it's only used to influence the + selection of the client certificate, not for any kind of "trust" decision. + """ + cert_nickname = 'Browse-SSO-User' + if subprocess.call(['certutil', '-L', '-d', _profile_path, '-n', + cert_nickname]) == 0: + return + + data_path = os.path.join(activity.get_activity_root(), 'data') + key_file_name = os.path.join(data_path, 'privkey.pem') + cert_file_name = os.path.join(data_path, 'cert.pem') + cert_req_file_name = os.path.join(data_path, 'certreq.pem') + p12_file_name = os.path.join(data_path, 'combined.p12') + user_name = profile.get_nick_name() + escaped_user_name = user_name.replace('/', '\\/').replace('=', '\\=') + + def tighten_umask(): + os.umask(0077) + + subprocess.check_call(['openssl', 'req', '-out', cert_req_file_name, + '-new', '-newkey', 'rsa:2048', '-nodes', '-keyout', + key_file_name, '-subj', '/CN=' + escaped_user_name, + '-utf8', '-batch'], + preexec_fn=tighten_umask) + subprocess.check_call(['certutil', '-C', '-m', + str(random.randint(1, 2**31-2)), '-a', '-i', + cert_req_file_name, '-o', cert_file_name, '-c', + 'Browse-SSO-Pseudo-CA', '-d', _profile_path, + '-v', '999']) + subprocess.check_call(['openssl', 'pkcs12', '-export', '-nodes', + '-password', 'pass:a', '-in', cert_file_name, + '-inkey', key_file_name, '-name', cert_nickname, + '-out', p12_file_name], preexec_fn=tighten_umask) + subprocess.check_call(['pk12util', '-i', p12_file_name, '-d', + _profile_path, '-K', '', '-W', 'a'], + preexec_fn=tighten_umask) + + import hulahop hulahop.set_app_version(os.environ['SUGAR_BUNDLE_VERSION']) hulahop.startup(_profile_path) @@ -136,9 +185,15 @@ hulahop.startup(_profile_path) from xpcom import components +def _set_char_preference(name, value): + cls = components.classes["@mozilla.org/preferences-service;1"] + prefService = cls.getService(components.interfaces.nsIPrefService) + branch = prefService.getBranch('') + branch.setCharPref(name, value) + + def _set_accept_languages(): - ''' Set intl.accept_languages based on the locale - ''' + """Set intl.accept_languages preference based on the locale""" lang = locale.getdefaultlocale()[0] if not lang: @@ -148,12 +203,20 @@ def _set_accept_languages(): # e.g. es-uy, es pref = lang[0] + "-" + lang[1].lower() + ", " + lang[0] - cls = components.classes["@mozilla.org/preferences-service;1"] - prefService = cls.getService(components.interfaces.nsIPrefService) - branch = prefService.getBranch('') - branch.setCharPref('intl.accept_languages', pref) + _set_char_preference('intl.accept_languages', pref) logging.debug('LANG set') + +def _set_client_certificate_policy(): + """Set preference to select client certificate automatically. + + Popping up a dialog that most users don't understand is bad UI design. + Instead present the "best match" to the server. + """ + _set_char_preference('security.default_personal_cert', + 'Select Automatically') + + from browser import TabbedView from browser import Browser from webtoolbar import PrimaryToolbar @@ -187,6 +250,8 @@ class WebActivity(activity.Activity): _set_accept_languages() _seed_xs_cookie() + _set_client_certificate_policy() + _create_sso_certificate() # don't pick up the sugar theme - use the native mozilla one instead cls = components.classes['@mozilla.org/preferences-service;1'] -- 1.7.2.3 _______________________________________________ Sugar-devel mailing list Sugar-devel@lists.sugarlabs.org http://lists.sugarlabs.org/listinfo/sugar-devel