author | Dave Townsend <dtownsend@oxymoronical.com> |
Tue, 31 Mar 2015 11:32:40 -0700 | |
changeset 241146 | 1cfe7aee45084049b9dc9c55c424f0665d27f154 |
parent 241145 | ebc84b792237ffe7ee2fb9640e8f3b6e37364783 |
child 241147 | 3a926d4e8ad006e152f1fb1ed07093890a83864b |
push id | 59036 |
push user | cbook@mozilla.com |
push date | Mon, 27 Apr 2015 10:37:48 +0000 |
treeherder | mozilla-inbound@ad388474898c [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | dveditz |
bugs | 1038068 |
milestone | 40.0a1 |
first release with | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
--- a/CLOBBER +++ b/CLOBBER @@ -17,9 +17,12 @@ # # Modifying this file will now automatically clobber the buildbot machines \o/ # # Are you updating CLOBBER because you think it's needed for your WebIDL # changes to stick? As of bug 928195, this shouldn't be necessary! Please # don't change CLOBBER for WebIDL changes any more. -Bug 1039866: Removing the metro browser +Bug 1038068: Check add-on signatures and refuse to install unsigned or broken add-ons + +Not sure why this needs a clobber but tests perma-failed when they don't on +try (2).
--- a/browser/app/profile/firefox.js +++ b/browser/app/profile/firefox.js @@ -64,16 +64,19 @@ pref("extensions.update.autoUpdateDefaul pref("extensions.hotfix.id", "firefox-hotfix@mozilla.org"); pref("extensions.hotfix.cert.checkAttributes", true); pref("extensions.hotfix.certs.1.sha1Fingerprint", "91:53:98:0C:C1:86:DF:47:8F:35:22:9E:11:C9:A7:31:04:49:A1:AA"); // Disable add-ons that are not installed by the user in all scopes by default. // See the SCOPE constants in AddonManager.jsm for values to use here. pref("extensions.autoDisableScopes", 15); +// Don't require signed add-ons by default +pref("xpinstall.signatures.required", false); + // Dictionary download preference pref("browser.dictionaries.download.url", "https://addons.mozilla.org/%LOCALE%/firefox/dictionaries/"); // At startup, should we check to see if the installation // date is older than some threshold pref("app.update.checkInstallTime", true); // The number of days a binary is permitted to be old without checking is defined in
--- a/browser/locales/en-US/chrome/browser/browser.properties +++ b/browser/locales/en-US/chrome/browser/browser.properties @@ -64,30 +64,32 @@ addonwatch.restart.accesskey=R # Semicolon-separated list of plural forms. See: # http://developer.mozilla.org/en/docs/Localization_and_Plurals # #1 first add-on's name, #2 number of add-ons, #3 application name addonsInstalled=#1 has been installed successfully.;#2 add-ons have been installed successfully. addonsInstalledNeedsRestart=#1 will be installed after you restart #3.;#2 add-ons will be installed after you restart #3. addonInstallRestartButton=Restart Now addonInstallRestartButton.accesskey=R -# LOCALIZATION NOTE (addonError-1, addonError-2, addonError-3, addonError-4): +# LOCALIZATION NOTE (addonError-1, addonError-2, addonError-3, addonError-4, addonError-5): # #1 is the add-on name, #2 is the host name, #3 is the application name # #4 is the application version addonError-1=The add-on could not be downloaded because of a connection failure on #2. addonError-2=The add-on from #2 could not be installed because it does not match the add-on #3 expected. addonError-3=The add-on downloaded from #2 could not be installed because it appears to be corrupt. addonError-4=#1 could not be installed because #3 cannot modify the needed file. +addonError-5=#3 has prevented this site from installing an unverified add-on. -# LOCALIZATION NOTE (addonLocalError-1, addonLocalError-2, addonLocalError-3, addonLocalError-4, addonErrorIncompatible, addonErrorBlocklisted): +# LOCALIZATION NOTE (addonLocalError-1, addonLocalError-2, addonLocalError-3, addonLocalError-4, addonLocalError-5, addonErrorIncompatible, addonErrorBlocklisted): # #1 is the add-on name, #3 is the application name, #4 is the application version addonLocalError-1=This add-on could not be installed because of a filesystem error. addonLocalError-2=This add-on could not be installed because it does not match the add-on #3 expected. addonLocalError-3=This add-on could not be installed because it appears to be corrupt. addonLocalError-4=#1 could not be installed because #3 cannot modify the needed file. +addonLocalError-5=This add-on could not be installed because it has not been verified. addonErrorIncompatible=#1 could not be installed because it is not compatible with #3 #4. addonErrorBlocklisted=#1 could not be installed because it has a high risk of causing stability or security problems. # LOCALIZATION NOTE (deveditionTheme.name): This should be nearly the brand name for aurora. # See browser/branding/aurora/locales/*/brand.properties deveditionTheme.name=Developer Edition # LOCALIZATION NOTE (lwthemeInstallRequest.message): %S will be replaced with
--- a/layout/tools/reftest/runreftest.py +++ b/layout/tools/reftest/runreftest.py @@ -218,16 +218,18 @@ class RefTest(object): prefs['browser.snippets.firstrunHomepage.enabled'] = False # And for useragent updates. prefs['general.useragent.updates.enabled'] = False # And for webapp updates. Yes, it is supposed to be an integer. prefs['browser.webapps.checkForUpdates'] = 0 # And for about:newtab content fetch and pings. prefs['browser.newtabpage.directory.source'] = 'data:application/json,{"reftest":1}' prefs['browser.newtabpage.directory.ping'] = '' + # Allow unsigned add-ons + prefs['xpinstall.signatures.required'] = False #Don't use auto-enabled e10s prefs['browser.tabs.remote.autostart.1'] = False prefs['browser.tabs.remote.autostart.2'] = False if options.e10s: prefs['browser.tabs.remote.autostart'] = True for v in options.extraPrefs:
--- a/modules/libpref/init/all.js +++ b/modules/libpref/init/all.js @@ -4101,16 +4101,18 @@ pref("html5.flushtimer.subsequentdelay", // Push/Pop/Replace State prefs pref("browser.history.allowPushState", true); pref("browser.history.allowReplaceState", true); pref("browser.history.allowPopState", true); pref("browser.history.maxStateObjectSize", 655360); // XPInstall prefs pref("xpinstall.whitelist.required", true); +// Only Firefox requires add-on signatures +pref("xpinstall.signatures.required", false); pref("extensions.alwaysUnpack", false); pref("extensions.minCompatiblePlatformVersion", "2.0"); pref("network.buffer.cache.count", 24); pref("network.buffer.cache.size", 32768); // Desktop Notification pref("notification.feature.enabled", false);
--- a/security/apps/AppTrustDomain.cpp +++ b/security/apps/AppTrustDomain.cpp @@ -18,16 +18,19 @@ #include "marketplace-prod-reviewers.inc" #include "marketplace-dev-public.inc" #include "marketplace-dev-reviewers.inc" #include "marketplace-stage.inc" #include "xpcshell.inc" // Trusted Hosted Apps Certificates #include "manifest-signing-root.inc" #include "manifest-signing-test-root.inc" +// Add-on signing Certificates +#include "addons-public.inc" +#include "addons-stage.inc" using namespace mozilla::pkix; #ifdef PR_LOGGING extern PRLogModuleInfo* gPIPNSSLog; #endif static const unsigned int DEFAULT_MIN_RSA_BITS = 2048; @@ -88,16 +91,26 @@ AppTrustDomain::SetTrustedRoot(AppTruste trustedDER.len = mozilla::ArrayLength(trustedAppPublicRoot); break; case nsIX509CertDB::TrustedHostedAppTestRoot: trustedDER.data = const_cast<uint8_t*>(trustedAppTestRoot); trustedDER.len = mozilla::ArrayLength(trustedAppTestRoot); break; + case nsIX509CertDB::AddonsPublicRoot: + trustedDER.data = const_cast<uint8_t*>(addonsPublicRoot); + trustedDER.len = mozilla::ArrayLength(addonsPublicRoot); + break; + + case nsIX509CertDB::AddonsStageRoot: + trustedDER.data = const_cast<uint8_t*>(addonsStageRoot); + trustedDER.len = mozilla::ArrayLength(addonsStageRoot); + break; + default: PR_SetError(SEC_ERROR_INVALID_ARGS, 0); return SECFailure; } mTrustedRoot = CERT_NewTempCertificate(CERT_GetDefaultCertDB(), &trustedDER, nullptr, false, true); if (!mTrustedRoot) {
new file mode 100644 index 0000000000000000000000000000000000000000..6ab711b996fc25b144995af21726b08bc399996d GIT binary patch literal 1637 zc$_n6VoNk=V)0zS%*4pV#K>sC%f_kI=F#?@mywZ&mBFCaklTQhjX9KsO_(V(*ignm z3dG?O7WU1r%FM}0RB+BOD#$NNEXmBzGt@WG1<7&?tHER)ef<>zit<xRlR*j;f-}?e zGV{_Ef>VpiGLusc<qc#(8kmK}it_VIbdwWx3*d@%6La$o<ivRmO%03<&5evqjZMv? z#CeTCToWjFpq<ymq<|b?jI0dIO-#HDKyePHCMHIPExQx1$L{)Yx^ADr_pbUG4;HZW z)Eq8;mlVeOoU`d?aD9in>ZZ>Izc~U|Z&tq8vSj=5(s>)s>a<$!V=#X3b+&+5+cVEQ z2M(w_|IQwg^O8$jZfDJ^mA+d(uBw{((quaK;!QbTt6J_{RePVTu%hu|qe;$6Z@V{} z_$A+cd!1@${^g6#H&NYp+UY6Ia&0qP+pM$i@A#H@PWgvfLO|VjwPfL^cbA2Hn;^mP z;J<ACj@`=M9^GwU*qy6RUslwAFfnD$#**-D2V|D0)>i19h|@Z3nr-+v<z1D>;_T?7 z4yr~^mdZHIIx%^@zx<p(&01;m(j9U)y-+H=v9fSh_&O!FS;wN4V^=-b-q5SaUFLB0 zrQ71gng4EcTkfnnf4_J7`_M&E0{ctiV$TPi+GY?t)6+1Mna}?B!mWi|Y}+!fbuZ@? zF}!B?&&$Pv#YdUFq-EQ_>%5751&>q~ObfGNn9s6dyUm2gD3b#xB=gE0cFr%@^lkeB z2F`Ozi%hr2wFPHJxyjv|GVg({aDo#LckHtmaT%*ae-;UzZ0dO^@Zz&gAjjL5*Id)i zoM{W%u+OGX!peA?*CclR_dmD|-#p#syz6P`r&(+Es=2tudK`aoy5@4zWl@f^`YkuB z&Yr&iq}X@SES569qMcP{MPjAPgX&hFn|&d#NL5(QKYx*Bn3*8&?=4lY0$tJzbaw1! zVq{+2_|~BDl>rYhA;}7}uo^HkGX6K<1M&EQEEX1EzGE{G1MyWsd>#WXHV$nzMpjmK zW@dxNNgxSjmc}lF#`cAcjZ5kWIK?*sO9+q(Sw0pq7Lm<+UPY9wewbIGwQ2PnWw{l} zI@7nK6(G!@Y>;tfVgg$W2YbIm%>2o*OQs*?*OOOdxiQy1@t3Au-=z;z*=*8`_Go|L z+Wg|uKR<T=87cleM(6n^ob<iAxy<fK3&+QmX^OipF!{c*tW1<*wrN?rX~Oj9d0M}N z+eFKjFRV%^D%<xu@4+IOj>lZ=$8)B1bvW%WQLLS^DdN>f>)4b3+gd$mom=#Ffh?QV zLm{ChKX#S=ir;-n>TQN@?3V4jD=Re57&iQRJ0bmIlgQtb`+rSa{(6P`I<tMI4~~hS zk;<79yJC?pr|!!;$)#r<*Qu;Jeaa^*=T-gfuulwG3h80X+U<H}-Q|T>Jn7!e^Je2? zyKdd!X<?g-kJN@Hw62d&_3&HM@aANpCTD8Y{eDxu0IfS2Qw)|>@`faS|C~R&xnQmN z!MmIS4ih%iR-gMeN8M(%hi9i`&<dATU!OcHNN~_(_So<^)~5VhStCo5sM3O5jnz~A z!uD2GJ}6ta_qOPNF6LbhQ?n1vnk!{KyMOYZr=KHj7>a%09J}k8^E9gNk&;*lBlCo{ z+Y3Fu7k_*x{CLGXpZLJnJCnmg@2&cE{oUFW7O&pjTm7C1$o`ez{BVPUy>Z2Mvon)^ z2b;5t^9a1S9`)ss@1w^DcIY3}T%>tK>GbVo-yZ)C%*qo|vs&4;cFT0lhe10hZ@afJ jC-=yes-n9)muyeCG4C<!0iUwQuV;(OE+4qPUTir4A9<cd
new file mode 100644 index 0000000000000000000000000000000000000000..73e48cadfe2da5373a20a88a54148dac695da7d4 GIT binary patch literal 1895 zc$_n6Vox?`V)b6Y%*4pV#K>sC%f_kI=F#?@mywZ&mBFBKg(0^ACmVAp3!5-gXfTY! zA<X3LXeeeN0^+a>^ZMqO=9MI7<|%|_rj{GZ7)XJ{xrBusQ&RHtiWNdqi%S%OGt=`j z^U@7f43t2!%)-(oKw-T^uoAsuh^$^wetwBwa-yMufgao?oQz_M`31$P$=bywiRq~z zB@kT>x%pL@IXQ`X$@#eka^k#(rUpia28O0a7N&+#;=D#cu7Np}JCwZA#H55A5sa)1 z%uP)E3_x)%rY0svhW(!>$;ssf9Olw`kodV$Y0{%Q);xI!#j9x_nA2oe$z1%iRINw& zo0dVh|I-pv^I6=hqXQk@{bpGCud?|tBd5aetD!$nSM1(5qv*QGKkmn!54)V*-|sN| z`jP2y+p--z70k0H%=*vYvf8}zL0N16_6=XeeoIb1zH|DMH`=wehpQ%3-V?GtRrTo6 zYLlrUDz4#If~Rej-FUV>oUwxW-bvqbbD8PWRNme?^U`(cq8f*aw8o;-GvsPzzR$ew zGXE=&sN|wK^Y?V`=JsHI!`yaW=|Eao5$BS*dCZesoO8LX_22)0<m&aaYt@CaWhq?G z_V^hJ+`fI_&Ao?ZDjaXmAL49Ubk$mW!uJhFMUFYoT6i;V#(Te&qI>pC?vyY4HA`f2 z*2@d-_m5f3do#U#!MfV{mSKr(pUu9-G;%D6uYIDhrCCO6dW+^dVRJckzpd-8%ung5 z%r|}NeJAG3nW-1;Vg<wZ#w4z^lU}&ox&6WYcaomUAN{_4aGAh&TWs+}mqJmsjE&{W z3HeI4CaZbZJbtjb?Du)I?JZ{iHcKq?GB~ht_MTbo-Mnj??>}6!^Q(62)=%G=_Cy$) zN?k5l*7;|};p^Kwr=PZ2@WI+oOS9$syz{zYXV@)f-VFC%VDogZnufuZ8~i`+b}rp! zEi_B#;*Fj6D{h<Ko~Xl_6BM*TXRe!|^-E7{<?J_!T~f!HnAt+j7dJ6ZF=%3(V88=R zgR;UbtOm@CjQ<VzKs<gRi-iT4r`Zg|KzvmYpT~fUjYFG_k(HI5nb|;xjWeOmgR$+0 z6C*E+yqK(iL26!bu#bX!YF=tlVo7R>f^%w7NoHDRGLU0nj8M(Oq9&%2QBqQ1rLPZ7 z@vyX;UzDz&oT#5%l%rRWnrk2nvYL-Yj76m7Rs7j}pGo?!E`IrXV)3R!JiC^CGibaD zl2>MFJZsQ+ssXE-g^lZ$G_D>(S+@zJ2xSIk*dpJpt_x)R%`WfDT2-LFnf0DqmEGd- zxyO~R-WT^<tF!08kwqfcqPXS%e!nbm*CJ<;>g_9x23O~w+V$)oSLWMU2Od|Qx;k_D zwJ#gRbEoVUTzpW>swL-))*{37)N7o_43}0{=gc|(JMhOZE@{C-?Wb;AUEETo>Zz=f z)U)C77N4*W9(KF!W=`Vuh^*?EXLoGX-t8i<n%}Q(sPQ*TG@W)Y=HrsT5A=7Xg-_hQ zaQ|Cn1^G1}86WfVZcjWi{YJHkwU^DQ>@|<hPugT#!5ZTDyyLEvgI4J|QBEz}=IcrU zQtGQVZf)DOHSyd^nMv1$mhabp7iFj$Tpgi4eTshkw*AJzr7Jm`+?-u6oNab3H|;Ub zUQnQaR@7GgRdL`i_XAU|z4$lD<SB=@hR>&a(K{`-zx0v`R=!Z?ZLYb1$1eV>m&bdJ zK8D7U^Q~PBr~lW-uAjsFVaDTkR+@q)ufNQ>-R;ulWj*&$`L?M~zB^u;9AtCj&dd0n z3cv0xVLIz1)Un)(ow<wc$Ij4jJ;A%D`HSr2&a&7x-95{lJYBpazoPt9>V@U`4;-hr z?7qq@k)SZS`XMvdA-3JB*Z4neYyEWiqw%$E!q=ZB=Xf9cFt2w`(aJj^TP$qn_$kPn z^8~og;J&;=q9HMB&-<?Dm8P$h3O`k7?fV>``t97eCDyDN>Fa0Zifq1iW7qS`-ctb& Cx%5H+
--- a/security/apps/gen_cert_header.py +++ b/security/apps/gen_cert_header.py @@ -30,13 +30,15 @@ array_names = [ 'marketplaceProdPublicRoot', 'marketplaceProdReviewersRoot', 'marketplaceDevPublicRoot', 'marketplaceDevReviewersRoot', 'marketplaceStageRoot', 'trustedAppPublicRoot', 'trustedAppTestRoot', 'xpcshellRoot', + 'addonsPublicRoot', + 'addonsStageRoot', ] for n in array_names: # Make sure the lambda captures the right string. globals()[n] = lambda header, cert_filename, name=n: header.write(_create_header(name, _file_byte_generator(cert_filename)))
--- a/security/apps/moz.build +++ b/security/apps/moz.build @@ -29,15 +29,17 @@ headers_arrays_certs = [ ('marketplace-prod-public.inc', 'marketplaceProdPublicRoot', 'marketplace-prod-public.crt'), ('marketplace-prod-reviewers.inc', 'marketplaceProdReviewersRoot', 'marketplace-prod-reviewers.crt'), ('marketplace-dev-public.inc', 'marketplaceDevPublicRoot', 'marketplace-dev-public.crt'), ('marketplace-dev-reviewers.inc', 'marketplaceDevReviewersRoot', 'marketplace-dev-reviewers.crt'), ('marketplace-stage.inc', 'marketplaceStageRoot', 'marketplace-stage.crt'), ('manifest-signing-root.inc', 'trustedAppPublicRoot', 'trusted-app-public.der'), ('manifest-signing-test-root.inc', 'trustedAppTestRoot', test_ssl_path + '/test_signed_manifest/trusted_ca1.der'), ('xpcshell.inc', 'xpcshellRoot', test_ssl_path + '/test_signed_apps/trusted_ca1.der'), + ('addons-public.inc', 'addonsPublicRoot', 'addons-public.crt'), + ('addons-stage.inc', 'addonsStageRoot', 'addons-stage.crt'), ] for header, array_name, cert in headers_arrays_certs: GENERATED_FILES += [header] h = GENERATED_FILES[header] h.script = 'gen_cert_header.py:' + array_name h.inputs = [cert]
--- a/security/manager/ssl/public/nsIX509CertDB.idl +++ b/security/manager/ssl/public/nsIX509CertDB.idl @@ -34,17 +34,17 @@ interface nsIVerifySignedManifestCallbac void verifySignedManifestFinished(in nsresult rv, in nsIX509Cert aSignerCert); }; /** * This represents a service to access and manipulate * X.509 certificates stored in a database. */ -[scriptable, uuid(8b01c2af-3a44-44d3-8ea5-51c2455e6c4b)] +[scriptable, uuid(560bc9ac-3e71-472e-9b08-2270d0c71878)] interface nsIX509CertDB : nsISupports { /** * Constants that define which usages a certificate * is trusted for. */ const unsigned long UNTRUSTED = 0; const unsigned long TRUSTED_SSL = 1 << 0; @@ -306,16 +306,18 @@ interface nsIX509CertDB : nsISupports { const AppTrustedRoot AppMarketplaceProdPublicRoot = 1; const AppTrustedRoot AppMarketplaceProdReviewersRoot = 2; const AppTrustedRoot AppMarketplaceDevPublicRoot = 3; const AppTrustedRoot AppMarketplaceDevReviewersRoot = 4; const AppTrustedRoot AppMarketplaceStageRoot = 5; const AppTrustedRoot AppXPCShellRoot = 6; const AppTrustedRoot TrustedHostedAppPublicRoot = 7; const AppTrustedRoot TrustedHostedAppTestRoot = 8; + const AppTrustedRoot AddonsPublicRoot = 9; + const AppTrustedRoot AddonsStageRoot = 10; void openSignedAppFileAsync(in AppTrustedRoot trustedRoot, in nsIFile aJarFile, in nsIOpenSignedAppFileCallback callback); /** * Given streams containing a signature and a manifest file, verifies * that the signature is valid for the manifest. The signature must * come from a certificate that is trusted for code signing and that
--- a/testing/profiles/prefs_general.js +++ b/testing/profiles/prefs_general.js @@ -71,16 +71,18 @@ user_pref("experiments.manifest.uri", "h // AddonManager.SCOPE_PROFILE + AddonManager.SCOPE_APPLICATION user_pref("extensions.enabledScopes", 5); // Disable metadata caching for installed add-ons by default user_pref("extensions.getAddons.cache.enabled", false); // Disable intalling any distribution add-ons user_pref("extensions.installDistroAddons", false); // XPI extensions are required for test harnesses to load user_pref("extensions.defaultProviders.enabled", true); +// Disable signature requirements where possible +user_pref("xpinstall.signatures.required", false); user_pref("geo.wifi.uri", "http://%(server)s/tests/dom/tests/mochitest/geolocation/network_geolocation.sjs"); user_pref("geo.wifi.timeToWaitBeforeSending", 2000); user_pref("geo.wifi.scan", false); user_pref("geo.wifi.logging.enabled", true); // Make url-classifier updates so rare that they won't affect tests user_pref("urlclassifier.updateinterval", 172800);
--- a/toolkit/mozapps/extensions/AddonManager.jsm +++ b/toolkit/mozapps/extensions/AddonManager.jsm @@ -2667,16 +2667,18 @@ this.AddonManager = { // The download failed due to network problems. ERROR_NETWORK_FAILURE: -1, // The downloaded file did not match the provided hash. ERROR_INCORRECT_HASH: -2, // The downloaded file seems to be corrupted in some way. ERROR_CORRUPT_FILE: -3, // An error occured trying to write to the filesystem. ERROR_FILE_ACCESS: -4, + // The add-on must be signed and isn't. + ERROR_SIGNEDSTATE_REQUIRED: -5, // These must be kept in sync with AddonUpdateChecker. // No error was encountered. UPDATE_STATUS_NO_ERROR: 0, // The update check timed out UPDATE_STATUS_TIMEOUT: -1, // There was an error while downloading the update information. UPDATE_STATUS_DOWNLOAD_ERROR: -2, @@ -2802,16 +2804,30 @@ this.AddonManager = { // an application change making an add-on incompatible. Doesn't include // add-ons that were pending being disabled the last time the application ran. STARTUP_CHANGE_DISABLED: "disabled", // Add-ons that were detected as enabled during startup, normally because of // an application change making an add-on compatible. Doesn't include // add-ons that were pending being enabled the last time the application ran. STARTUP_CHANGE_ENABLED: "enabled", + // Constants for Addon.signedState. Any states that should cause an add-on + // to be unusable in builds that require signing should have negative values. + // Add-on is signed but signature verification has failed. + SIGNEDSTATE_BROKEN: -2, + // Add-on may be signed but by an certificate that doesn't chain to our + // our trusted certificate. + SIGNEDSTATE_UNKNOWN: -1, + // Add-on is unsigned. + SIGNEDSTATE_MISSING: 0, + // Add-on is preliminarily reviewed. + SIGNEDSTATE_PRELIMINARY: 1, + // Add-on is fully reviewed. + SIGNEDSTATE_SIGNED: 2, + // Constants for the Addon.userDisabled property // Indicates that the userDisabled state of this add-on is currently // ask-to-activate. That is, it can be conditionally enabled on a // case-by-case basis. STATE_ASK_TO_ACTIVATE: "askToActivate", #ifdef MOZ_EM_DEBUG get __AddonManagerInternal__() {
--- a/toolkit/mozapps/extensions/internal/XPIProvider.jsm +++ b/toolkit/mozapps/extensions/internal/XPIProvider.jsm @@ -73,16 +73,19 @@ const PREF_EM_ENABLED_ADDONS = const PREF_EM_EXTENSION_FORMAT = "extensions."; const PREF_EM_ENABLED_SCOPES = "extensions.enabledScopes"; const PREF_EM_AUTO_DISABLED_SCOPES = "extensions.autoDisableScopes"; const PREF_EM_SHOW_MISMATCH_UI = "extensions.showMismatchUI"; const PREF_XPI_ENABLED = "xpinstall.enabled"; const PREF_XPI_WHITELIST_REQUIRED = "xpinstall.whitelist.required"; const PREF_XPI_DIRECT_WHITELISTED = "xpinstall.whitelist.directRequest"; const PREF_XPI_FILE_WHITELISTED = "xpinstall.whitelist.fileRequest"; +// xpinstall.signatures.required only supported in dev builds +const PREF_XPI_SIGNATURES_REQUIRED = "xpinstall.signatures.required"; +const PREF_XPI_SIGNATURES_DEV_ROOT = "xpinstall.signatures.dev-root"; const PREF_XPI_PERMISSIONS_BRANCH = "xpinstall."; const PREF_XPI_UNPACK = "extensions.alwaysUnpack"; const PREF_INSTALL_REQUIREBUILTINCERTS = "extensions.install.requireBuiltInCerts"; const PREF_INSTALL_REQUIRESECUREORIGIN = "extensions.install.requireSecureOrigin"; const PREF_INSTALL_DISTRO_ADDONS = "extensions.installDistroAddons"; const PREF_BRANCH_INSTALLED_ADDON = "extensions.installedDistroAddon."; const PREF_SHOWN_SELECTION_UI = "extensions.shownSelectionUI"; const PREF_INTERPOSITION_ENABLED = "extensions.interposition.enabled"; @@ -95,17 +98,16 @@ const PREF_CHECKCOMAT_THEMEOVERRIDE = const URI_EXTENSION_SELECT_DIALOG = "chrome://mozapps/content/extensions/selectAddons.xul"; const URI_EXTENSION_UPDATE_DIALOG = "chrome://mozapps/content/extensions/update.xul"; const URI_EXTENSION_STRINGS = "chrome://mozapps/locale/extensions/extensions.properties"; const STRING_TYPE_NAME = "type.%ID%.name"; const DIR_EXTENSIONS = "extensions"; const DIR_STAGE = "staged"; -const DIR_XPI_STAGE = "staged-xpis"; const DIR_TRASH = "trash"; const FILE_DATABASE = "extensions.json"; const FILE_OLD_CACHE = "extensions.cache"; const FILE_INSTALL_MANIFEST = "install.rdf"; const FILE_XPI_ADDONS_LIST = "extensions.ini"; const KEY_PROFILEDIR = "ProfD"; @@ -185,16 +187,29 @@ const TYPES = { }; const RESTARTLESS_TYPES = new Set([ "dictionary", "experiment", "locale", ]); +const SIGNED_TYPES = new Set([ + "extension", + "experiment", +]); + +// Whether add-on signing is required. +function mustSign(aType) { + if (!SIGNED_TYPES.has(aType)) + return false; + return REQUIRE_SIGNING || Preferences.get(PREF_XPI_SIGNATURES_REQUIRED, false); +} + + // Keep track of where we are in startup for telemetry // event happened during XPIDatabase.startup() const XPI_STARTING = "XPIStarting"; // event happened after startup() but before the final-ui-startup event const XPI_BEFORE_UI_STARTUP = "BeforeFinalUIStartup"; // event happened after final-ui-startup const XPI_AFTER_UI_STARTUP = "AfterFinalUIStartup"; @@ -625,16 +640,19 @@ function applyBlocklistChanges(aOldAddon * The add-on to check * @return true if the add-on should not be appDisabled */ function isUsableAddon(aAddon) { // Hack to ensure the default theme is always usable if (aAddon.type == "theme" && aAddon.internalName == XPIProvider.defaultSkin) return true; + if (mustSign(aAddon.type) && aAddon.signedState <= AddonManager.SIGNEDSTATE_MISSING) + return false; + if (aAddon.blocklistState == Blocklist.STATE_BLOCKED) return false; if (AddonManager.checkUpdateSecurity && !aAddon.providesUpdatesSecurely) return false; if (!aAddon.isPlatformCompatible) return false; @@ -994,17 +1012,17 @@ function loadManifestFromRDF(aUri, aStre /** * Loads an AddonInternal object from an add-on extracted in a directory. * * @param aDir * The nsIFile directory holding the add-on * @return an AddonInternal object * @throws if the directory does not contain a valid install manifest */ -function loadManifestFromDir(aDir) { +let loadManifestFromDir = Task.async(function* loadManifestFromDir(aDir) { function getFileSize(aFile) { if (aFile.isSymlink()) return 0; if (!aFile.isDirectory()) return aFile.fileSize; let size = 0; @@ -1035,34 +1053,39 @@ function loadManifestFromDir(aDir) { addon.size = getFileSize(aDir); file = aDir.clone(); file.append("chrome.manifest"); let chromeManifest = ChromeManifestParser.parseSync(Services.io.newFileURI(file)); addon.hasBinaryComponents = ChromeManifestParser.hasType(chromeManifest, "binary-component"); + if (SIGNED_TYPES.has(addon.type)) + addon.signedState = yield verifyDirSignedState(aDir, addon.id); + else + addon.signedState = AddonManager.SIGNEDSTATE_MISSING; + addon.appDisabled = !isUsableAddon(addon); return addon; } finally { bis.close(); fis.close(); } -} +}); /** * Loads an AddonInternal object from an nsIZipReader for an add-on. * * @param aZipReader * An open nsIZipReader for the add-on's files * @return an AddonInternal object * @throws if the XPI file does not contain a valid install manifest */ -function loadManifestFromZipReader(aZipReader) { +let loadManifestFromZipReader = Task.async(function* loadManifestFromZipReader(aZipReader) { let zis = aZipReader.getInputStream(FILE_INSTALL_MANIFEST); let bis = Cc["@mozilla.org/network/buffered-input-stream;1"]. createInstance(Ci.nsIBufferedInputStream); bis.init(zis, 4096); try { let uri = buildJarURI(aZipReader.file, FILE_INSTALL_MANIFEST); let addon = loadManifestFromRDF(uri, bis); @@ -1078,54 +1101,89 @@ function loadManifestFromZipReader(aZipR uri = buildJarURI(aZipReader.file, "chrome.manifest"); let chromeManifest = ChromeManifestParser.parseSync(uri); addon.hasBinaryComponents = ChromeManifestParser.hasType(chromeManifest, "binary-component"); } else { addon.hasBinaryComponents = false; } + if (SIGNED_TYPES.has(addon.type)) + addon.signedState = yield verifyZipSignedState(aZipReader.file, addon.id, addon.version); + else + addon.signedState = AddonManager.SIGNEDSTATE_MISSING; + addon.appDisabled = !isUsableAddon(addon); return addon; } finally { bis.close(); zis.close(); } -} +}); /** * Loads an AddonInternal object from an add-on in an XPI file. * * @param aXPIFile * An nsIFile pointing to the add-on's XPI file * @return an AddonInternal object * @throws if the XPI file does not contain a valid install manifest */ -function loadManifestFromZipFile(aXPIFile) { +let loadManifestFromZipFile = Task.async(function* loadManifestFromZipFile(aXPIFile) { let zipReader = Cc["@mozilla.org/libjar/zip-reader;1"]. createInstance(Ci.nsIZipReader); try { zipReader.open(aXPIFile); - return loadManifestFromZipReader(zipReader); + // Can't return this promise because that will make us close the zip reader + // before it has finished loading the manifest. Wait for the result and then + // return. + let manifest = yield loadManifestFromZipReader(zipReader); + return manifest; } finally { zipReader.close(); } -} +}); function loadManifestFromFile(aFile) { if (aFile.isFile()) return loadManifestFromZipFile(aFile); else return loadManifestFromDir(aFile); } /** + * A synchronous method for loading an add-on's manifest. This should only ever + * be used during startup or a sync load of the add-ons DB + */ +function syncLoadManifestFromFile(aFile) { + let success = undefined; + let result = null; + + loadManifestFromFile(aFile).then(val => { + success = true; + result = val; + }, val => { + success = false; + result = val + }); + + let thread = Services.tm.currentThread; + + while (success === undefined) + thread.processNextEvent(true); + + if (!success) + throw result; + return result; +} + +/** * Gets an nsIURI for a file within another file, either a directory or an XPI * file. If aFile is a directory then this will return a file: URI, if it is an * XPI file then it will return a jar: URI. * * @param aFile * The file containing the resources, must be either a directory or an * XPI file * @param aPath @@ -1222,16 +1280,91 @@ function verifyZipSigning(aZip, aCertifi if (!entryCertificate || !aCertificate.equals(entryCertificate)) { return false; } } return aZip.manifestEntriesCount == count; } /** + * Returns the signedState for a given return code and certificate by verifying + * it against the expected ID. + */ +function getSignedStatus(aRv, aCert, aExpectedID) { + switch (aRv) { + case Cr.NS_OK: + if (aExpectedID != aCert.commonName) + return AddonManager.SIGNEDSTATE_BROKEN; + + return /preliminary/i.test(aCert.organizationalUnit) + ? AddonManager.SIGNEDSTATE_PRELIMINARY + : AddonManager.SIGNEDSTATE_SIGNED; + break; + case Cr.NS_ERROR_SIGNED_JAR_NOT_SIGNED: + return AddonManager.SIGNEDSTATE_MISSING; + break; + case Cr.NS_ERROR_SIGNED_JAR_MANIFEST_INVALID: + case Cr.NS_ERROR_SIGNED_JAR_ENTRY_INVALID: + case Cr.NS_ERROR_SIGNED_JAR_ENTRY_MISSING: + case Cr.NS_ERROR_SIGNED_JAR_ENTRY_TOO_LARGE: + case Cr.NS_ERROR_SIGNED_JAR_UNSIGNED_ENTRY: + case Cr.NS_ERROR_SIGNED_JAR_MODIFIED_ENTRY: + return AddonManager.SIGNEDSTATE_BROKEN; + break; + default: + // Any other error indicates that either the add-on isn't signed or it + // is signed by a signature that doesn't chain to the trusted root. + return AddonManager.SIGNEDSTATE_UNKNOWN; + break; + } +} + +/** + * Verifies that a zip file's contents are all correctly signed by an + * AMO-issued certificate + * + * @param aFile + * the xpi file to check + * @param aExpectedID + * the expected ID of the signature + * @return a Promise that resolves to an AddonManager.SIGNEDSTATE_* constant. + */ +function verifyZipSignedState(aFile, aExpectedID, aVersion) { + let certDB = Cc["@mozilla.org/security/x509certdb;1"] + .getService(Ci.nsIX509CertDB); + + let root = Ci.nsIX509CertDB.AddonsPublicRoot; + if (!REQUIRE_SIGNING && Preferences.get(PREF_XPI_SIGNATURES_DEV_ROOT, false)) + root = Ci.nsIX509CertDB.AddonsStageRoot; + + return new Promise(resolve => { + certDB.openSignedAppFileAsync(root, aFile, (aRv, aZipReader, aCert) => { + if (aZipReader) + aZipReader.close(); + resolve(getSignedStatus(aRv, aCert, aExpectedID)); + }); + }); +} + +/** + * Verifies that a directory's contents are all correctly signed by an + * AMO-issued certificate + * + * @param aDir + * the directory to check + * @param aExpectedID + * the expected ID of the signature + * @return a Promise that resolves to an AddonManager.SIGNEDSTATE_* constant. + */ +function verifyDirSignedState(aDir, aExpectedID) { + // TODO: Get the certificate for an unpacked add-on (bug 1038072) + return Promise.resolve(AddonManager.SIGNEDSTATE_MISSING); +} + +/** * Replaces %...% strings in an addon url (update and updateInfo) with * appropriate values. * * @param aAddon * The AddonInternal representing the add-on * @param aUri * The uri to escape * @param aUpdateType @@ -2076,16 +2209,18 @@ this.XPIProvider = { this.minCompatibleAppVersion = Preferences.get(PREF_EM_MIN_COMPAT_APP_VERSION, null); this.minCompatiblePlatformVersion = Preferences.get(PREF_EM_MIN_COMPAT_PLATFORM_VERSION, null); this.enabledAddons = ""; Services.prefs.addObserver(PREF_EM_MIN_COMPAT_APP_VERSION, this, false); Services.prefs.addObserver(PREF_EM_MIN_COMPAT_PLATFORM_VERSION, this, false); + if (!REQUIRE_SIGNING) + Services.prefs.addObserver(PREF_XPI_SIGNATURES_REQUIRED, this, false); Services.obs.addObserver(this, NOTIFICATION_FLUSH_PERMISSIONS, false); if (Cu.isModuleLoaded("resource:///modules/devtools/ToolboxProcess.jsm")) { // If BrowserToolboxProcess is already loaded, set the boolean to true // and do whatever is needed this._toolboxProcessLoaded = true; BrowserToolboxProcess.on("connectionchange", this.onDebugConnectionChange.bind(this)); } @@ -2419,105 +2554,18 @@ this.XPIProvider = { processPendingFileChanges: function XPI_processPendingFileChanges(aManifests) { let changed = false; this.installLocations.forEach(function(aLocation) { aManifests[aLocation.name] = {}; // We can't install or uninstall anything in locked locations if (aLocation.locked) return; - let stagedXPIDir = aLocation.getXPIStagingDir(); let stagingDir = aLocation.getStagingDir(); - if (stagedXPIDir.exists() && stagedXPIDir.isDirectory()) { - let entries = stagedXPIDir.directoryEntries - .QueryInterface(Ci.nsIDirectoryEnumerator); - while (entries.hasMoreElements()) { - let stageDirEntry = entries.nextFile; - - if (!stageDirEntry.isDirectory()) { - logger.warn("Ignoring file in XPI staging directory: " + stageDirEntry.path); - continue; - } - - // Find the last added XPI file in the directory - let stagedXPI = null; - var xpiEntries = stageDirEntry.directoryEntries - .QueryInterface(Ci.nsIDirectoryEnumerator); - while (xpiEntries.hasMoreElements()) { - let file = xpiEntries.nextFile; - if (file.isDirectory()) - continue; - - let extension = file.leafName; - extension = extension.substring(extension.length - 4); - - if (extension != ".xpi" && extension != ".jar") - continue; - - stagedXPI = file; - } - xpiEntries.close(); - - if (!stagedXPI) - continue; - - let addon = null; - try { - addon = loadManifestFromZipFile(stagedXPI); - } - catch (e) { - logger.error("Unable to read add-on manifest from " + stagedXPI.path, e); - continue; - } - - logger.debug("Migrating staged install of " + addon.id + " in " + aLocation.name); - - if (addon.unpack || Preferences.get(PREF_XPI_UNPACK, false)) { - let targetDir = stagingDir.clone(); - targetDir.append(addon.id); - try { - targetDir.create(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY); - } - catch (e) { - logger.error("Failed to create staging directory for add-on " + addon.id, e); - continue; - } - - try { - ZipUtils.extractFiles(stagedXPI, targetDir); - } - catch (e) { - logger.error("Failed to extract staged XPI for add-on " + addon.id + " in " + - aLocation.name, e); - } - } - else { - try { - stagedXPI.moveTo(stagingDir, addon.id + ".xpi"); - } - catch (e) { - logger.error("Failed to move staged XPI for add-on " + addon.id + " in " + - aLocation.name, e); - } - } - } - entries.close(); - } - - if (stagedXPIDir.exists()) { - try { - recursiveRemove(stagedXPIDir); - } - catch (e) { - // Non-critical, just saves some perf on startup if we clean this up. - logger.debug("Error removing XPI staging dir " + stagedXPIDir.path, e); - } - } - try { if (!stagingDir || !stagingDir.exists() || !stagingDir.isDirectory()) return; } catch (e) { logger.warn("Failed to find staging directory", e); return; } @@ -2588,66 +2636,75 @@ this.XPIProvider = { aManifests[aLocation.name][id] = null; let existingAddonID = id; let jsonfile = stagingDir.clone(); jsonfile.append(id + ".json"); try { - aManifests[aLocation.name][id] = loadManifestFromFile(stageDirEntry); + aManifests[aLocation.name][id] = syncLoadManifestFromFile(stageDirEntry); } catch (e) { logger.error("Unable to read add-on manifest from " + stageDirEntry.path, e); // This add-on can't be installed so just remove it now seenFiles.push(stageDirEntry.leafName); seenFiles.push(jsonfile.leafName); continue; } + let addon = aManifests[aLocation.name][id]; + + if ((addon.signedState <= AddonManager.SIGNEDSTATE_MISSING) && mustSign(addon.type)) { + logger.warn("Refusing to install staged add-on " + id + " with signed state " + addon.signedState); + seenFiles.push(stageDirEntry.leafName); + seenFiles.push(jsonfile.leafName); + continue; + } + // Check for a cached metadata for this add-on, it may contain updated // compatibility information if (jsonfile.exists()) { logger.debug("Found updated metadata for " + id + " in " + aLocation.name); let fis = Cc["@mozilla.org/network/file-input-stream;1"]. createInstance(Ci.nsIFileInputStream); let json = Cc["@mozilla.org/dom/json;1"]. createInstance(Ci.nsIJSON); try { fis.init(jsonfile, -1, 0, 0); let metadata = json.decodeFromStream(fis, jsonfile.fileSize); - aManifests[aLocation.name][id].importMetadata(metadata); + addon.importMetadata(metadata); } catch (e) { // If some data can't be recovered from the cached metadata then it // is unlikely to be a problem big enough to justify throwing away // the install, just log and error and continue logger.error("Unable to read metadata from " + jsonfile.path, e); } finally { fis.close(); } } seenFiles.push(jsonfile.leafName); - existingAddonID = aManifests[aLocation.name][id].existingAddonID || id; + existingAddonID = addon.existingAddonID || id; var oldBootstrap = null; logger.debug("Processing install of " + id + " in " + aLocation.name); if (existingAddonID in this.bootstrappedAddons) { try { var existingAddon = aLocation.getLocationForID(existingAddonID); if (this.bootstrappedAddons[existingAddonID].descriptor == existingAddon.persistentDescriptor) { oldBootstrap = this.bootstrappedAddons[existingAddonID]; // We'll be replacing a currently active bootstrapped add-on so // call its uninstall method - let newVersion = aManifests[aLocation.name][id].version; + let newVersion = addon.version; let oldVersion = oldBootstrap.version; let uninstallReason = Services.vc.compare(oldVersion, newVersion) < 0 ? BOOTSTRAP_REASONS.ADDON_UPGRADE : BOOTSTRAP_REASONS.ADDON_DOWNGRADE; this.callBootstrapMethod(createAddonDetails(existingAddonID, oldBootstrap), existingAddon, "uninstall", uninstallReason, { newVersion: newVersion }); @@ -2655,27 +2712,25 @@ this.XPIProvider = { flushStartupCache(); } } catch (e) { } } try { - var addonInstallLocation = aLocation.installAddon(id, stageDirEntry, - existingAddonID); - if (aManifests[aLocation.name][id]) - aManifests[aLocation.name][id]._sourceBundle = addonInstallLocation; + addon._sourceBundle = aLocation.installAddon(id, stageDirEntry, + existingAddonID); } catch (e) { logger.error("Failed to install staged add-on " + id + " in " + aLocation.name, e); // Re-create the staged install AddonInstall.createStagedInstall(aLocation, stageDirEntry, - aManifests[aLocation.name][id]); + addon); // Make sure not to delete the cached manifest json file seenFiles.pop(); delete aManifests[aLocation.name][id]; if (oldBootstrap) { // Re-install the old add-on this.callBootstrapMethod(createAddonDetails(existingAddonID, oldBootstrap), @@ -2751,17 +2806,17 @@ this.XPIProvider = { if (!gIDTest.test(id)) { logger.debug("Ignoring distribution add-on whose name is not a valid add-on ID: " + entry.path); continue; } let addon; try { - addon = loadManifestFromFile(entry); + addon = syncLoadManifestFromFile(entry); } catch (e) { logger.warn("File entry " + entry.path + " contains an invalid add-on", e); continue; } if (addon.id != id) { logger.warn("File entry " + entry.path + " contains an add-on with an " + @@ -2774,17 +2829,17 @@ this.XPIProvider = { existingEntry = profileLocation.getLocationForID(id); } catch (e) { } if (existingEntry) { let existingAddon; try { - existingAddon = loadManifestFromFile(existingEntry); + existingAddon = syncLoadManifestFromFile(existingEntry); if (Services.vc.compare(addon.version, existingAddon.version) <= 0) continue; } catch (e) { // Bad add-on in the profile so just proceed and install over the top logger.warn("Profile contains an add-on with a bad or missing install " + "manifest at " + existingEntry.path + ", overwriting", e); @@ -2869,17 +2924,17 @@ this.XPIProvider = { // Check if there is an updated install manifest for this add-on let newAddon = aManifests[aInstallLocation.name][aOldAddon.id]; try { // If not load it if (!newAddon) { let file = aInstallLocation.getLocationForID(aOldAddon.id); - newAddon = loadManifestFromFile(file); + newAddon = syncLoadManifestFromFile(file); applyBlocklistChanges(aOldAddon, newAddon); // Carry over any pendingUninstall state to add-ons modified directly // in the profile. This is important when the attempt to remove the // add-on in processPendingFileChanges failed and caused an mtime // change to the add-ons files. newAddon.pendingUninstall = aOldAddon.pendingUninstall; } @@ -2912,16 +2967,21 @@ this.XPIProvider = { // Update the database let newDBAddon = XPIDatabase.updateAddonMetadata(aOldAddon, newAddon, aAddonState.descriptor); if (newDBAddon.visible) { visibleAddons[newDBAddon.id] = newDBAddon; // Remember add-ons that were changed during startup AddonManagerPrivate.addStartupChange(AddonManager.STARTUP_CHANGE_CHANGED, newDBAddon.id); + if (aOldAddon.active == newDBAddon.disabled) { + let change = aOldAddon.active ? AddonManager.STARTUP_CHANGE_DISABLED + : AddonManager.STARTUP_CHANGE_ENABLED; + AddonManagerPrivate.addStartupChange(change, newDBAddon.id); + } // If this was the active theme and it is now disabled then enable the // default theme if (aOldAddon.active && newDBAddon.disabled) XPIProvider.enableDefaultTheme(); // If the new add-on is bootstrapped and active then call its install method if (newDBAddon.active && newDBAddon.bootstrap) { @@ -3036,26 +3096,35 @@ this.XPIProvider = { } // App version changed, we may need to update the appDisabled property. if (aUpdateCompatibility) { let wasDisabled = aOldAddon.disabled; let wasAppDisabled = aOldAddon.appDisabled; let wasUserDisabled = aOldAddon.userDisabled; let wasSoftDisabled = aOldAddon.softDisabled; - + let updateDB = false; + + // If updating from a version of the app that didn't support signedState + // then fetch that property now + if (aOldAddon.signedState === undefined) { + let file = aInstallLocation.getLocationForID(aOldAddon.id); + let manifest = syncLoadManifestFromFile(file); + aOldAddon.signedState = manifest.signedState; + updateDB = true; + } // This updates the addon's JSON cached data in place applyBlocklistChanges(aOldAddon, aOldAddon, aOldAppVersion, aOldPlatformVersion); aOldAddon.appDisabled = !isUsableAddon(aOldAddon); let isDisabled = aOldAddon.disabled; // If either property has changed update the database. - if (wasAppDisabled != aOldAddon.appDisabled || + if (updateDB || wasAppDisabled != aOldAddon.appDisabled || wasUserDisabled != aOldAddon.userDisabled || wasSoftDisabled != aOldAddon.softDisabled) { logger.debug("Add-on " + aOldAddon.id + " changed appDisabled state to " + aOldAddon.appDisabled + ", userDisabled state to " + aOldAddon.userDisabled + " and softDisabled state to " + aOldAddon.softDisabled); XPIDatabase.saveChanges(); } @@ -3166,17 +3235,17 @@ this.XPIProvider = { // must be something dropped directly into the install location let isDetectedInstall = isNewInstall && !newAddon; // Load the manifest if necessary and sanity check the add-on ID try { if (!newAddon) { // Load the manifest from the add-on. let file = aInstallLocation.getLocationForID(aId); - newAddon = loadManifestFromFile(file); + newAddon = syncLoadManifestFromFile(file); } // The add-on in the manifest should match the add-on ID. if (newAddon.id != aId) { throw new Error("Invalid addon ID: expected addon ID " + aId + ", found " + newAddon.id + " in manifest"); } } catch (e) { @@ -4089,23 +4158,28 @@ this.XPIProvider = { this._toolboxProcessLoaded = true; BrowserToolboxProcess.on("connectionchange", this.onDebugConnectionChange.bind(this)); } if (aTopic == "nsPref:changed") { switch (aData) { case PREF_EM_MIN_COMPAT_APP_VERSION: - case PREF_EM_MIN_COMPAT_PLATFORM_VERSION: this.minCompatibleAppVersion = Preferences.get(PREF_EM_MIN_COMPAT_APP_VERSION, null); + this.updateAddonAppDisabledStates(); + break; + case PREF_EM_MIN_COMPAT_PLATFORM_VERSION: this.minCompatiblePlatformVersion = Preferences.get(PREF_EM_MIN_COMPAT_PLATFORM_VERSION, null); this.updateAddonAppDisabledStates(); break; + case PREF_XPI_SIGNATURES_REQUIRED: + this.updateAddonAppDisabledStates(); + break; } } }, /** * Tests whether enabling an add-on will require a restart. * * @param aAddon @@ -4909,59 +4983,55 @@ AddonInstall.prototype = { this.hash.data + ")"); this.state = AddonManager.STATE_DOWNLOAD_FAILED; this.error = AddonManager.ERROR_INCORRECT_HASH; aCallback(this); return; } } - try { - let self = this; - this.loadManifest(function initLocalInstall_loadManifest() { - XPIDatabase.getVisibleAddonForID(self.addon.id, function initLocalInstall_getVisibleAddon(aAddon) { - self.existingAddon = aAddon; - if (aAddon) - applyBlocklistChanges(aAddon, self.addon); - self.addon.updateDate = Date.now(); - self.addon.installDate = aAddon ? aAddon.installDate : self.addon.updateDate; - - if (!self.addon.isCompatible) { - // TODO Should we send some event here? - self.state = AddonManager.STATE_CHECKING; - new UpdateChecker(self.addon, { - onUpdateFinished: function updateChecker_onUpdateFinished(aAddon) { - self.state = AddonManager.STATE_DOWNLOADED; - XPIProvider.installs.push(self); - AddonManagerPrivate.callInstallListeners("onNewInstall", - self.listeners, - self.wrapper); - - aCallback(self); - } - }, AddonManager.UPDATE_WHEN_ADDON_INSTALLED); - } - else { - XPIProvider.installs.push(self); - AddonManagerPrivate.callInstallListeners("onNewInstall", - self.listeners, - self.wrapper); - - aCallback(self); - } - }); + let self = this; + this.loadManifest().then(() => { + XPIDatabase.getVisibleAddonForID(self.addon.id, function initLocalInstall_getVisibleAddon(aAddon) { + self.existingAddon = aAddon; + if (aAddon) + applyBlocklistChanges(aAddon, self.addon); + self.addon.updateDate = Date.now(); + self.addon.installDate = aAddon ? aAddon.installDate : self.addon.updateDate; + + if (!self.addon.isCompatible) { + // TODO Should we send some event here? + self.state = AddonManager.STATE_CHECKING; + new UpdateChecker(self.addon, { + onUpdateFinished: function updateChecker_onUpdateFinished(aAddon) { + self.state = AddonManager.STATE_DOWNLOADED; + XPIProvider.installs.push(self); + AddonManagerPrivate.callInstallListeners("onNewInstall", + self.listeners, + self.wrapper); + + aCallback(self); + } + }, AddonManager.UPDATE_WHEN_ADDON_INSTALLED); + } + else { + XPIProvider.installs.push(self); + AddonManagerPrivate.callInstallListeners("onNewInstall", + self.listeners, + self.wrapper); + + aCallback(self); + } }); - } - catch (e) { - logger.warn("Invalid XPI", e); + }, ([error, message]) => { + logger.warn("Invalid XPI", message); this.state = AddonManager.STATE_DOWNLOAD_FAILED; - this.error = AddonManager.ERROR_CORRUPT_FILE; + this.error = error; aCallback(this); - return; - } + }); }, /** * Initialises this install to be a download from a remote url. * * @param aCallback * The callback to pass the initialised AddonInstall to * @param aName @@ -5140,18 +5210,17 @@ AddonInstall.prototype = { * @param aZipReader * An open nsIZipReader for the multi-package XPI's files. This will * be closed before this method returns. * @param aCallback * A function to call when all of the add-on manifests have been * loaded. Because this loadMultipackageManifests is an internal API * we don't exception-wrap this callback */ - _loadMultipackageManifests: function AI_loadMultipackageManifests(aZipReader, - aCallback) { + _loadMultipackageManifests: Task.async(function* AI_loadMultipackageManifests(aZipReader) { let files = []; let entries = aZipReader.findEntries("(*.[Xx][Pp][Ii]|*.[Jj][Aa][Rr])"); while (entries.hasMore()) { let entryName = entries.getNext(); var target = getTemporaryFile(); try { aZipReader.extract(entryName, target); files.push(target); @@ -5174,28 +5243,27 @@ AddonInstall.prototype = { // Find the first file that has a valid install manifest and use it for // the add-on that this AddonInstall instance will install. while (files.length > 0) { this.removeTemporaryFile(); this.file = files.shift(); this.ownsTempFile = true; try { - addon = loadManifestFromZipFile(this.file); + addon = yield loadManifestFromZipFile(this.file); break; } catch (e) { logger.warn(this.file.leafName + " cannot be installed from multi-package " + "XPI", e); } } if (!addon) { // No valid add-on was found - aCallback(); return; } this.addon = addon; this.updateAddonURIs(); this.addon._install = this; @@ -5207,144 +5275,138 @@ AddonInstall.prototype = { // makes it impossible to delete on Windows. //let newIcon = createWrapper(this.addon).iconURL; //if (newIcon) // this.iconURL = newIcon; // Create new AddonInstall instances for every remaining file if (files.length > 0) { this.linkedInstalls = []; - let count = 0; let self = this; - files.forEach(function(file) { - AddonInstall.createInstall(function loadMultipackageManifests_createInstall(aInstall) { - // Ignore bad add-ons (createInstall will have logged the error) - if (aInstall.state == AddonManager.STATE_DOWNLOAD_FAILED) { - // Manually remove the temporary file - file.remove(true); - } - else { - // Make the new install own its temporary file - aInstall.ownsTempFile = true; - - self.linkedInstalls.push(aInstall) - - aInstall.sourceURI = self.sourceURI; - aInstall.releaseNotesURI = self.releaseNotesURI; - aInstall.updateAddonURIs(); - } - - count++; - if (count == files.length) - aCallback(); - }, file); - }, this); - } - else { - aCallback(); - } - }, + for (let file of files) { + let install = yield new Promise(resolve => AddonInstall.createInstall(resolve, file)); + + // Ignore bad add-ons (createInstall will have logged the error) + if (install.state == AddonManager.STATE_DOWNLOAD_FAILED) { + // Manually remove the temporary file + file.remove(true); + } + else { + // Make the new install own its temporary file + install.ownsTempFile = true; + + self.linkedInstalls.push(install) + + install.sourceURI = self.sourceURI; + install.releaseNotesURI = self.releaseNotesURI; + install.updateAddonURIs(); + } + } + } + }), /** * Called after the add-on is a local file and the signature and install * manifest can be read. * * @param aCallback * A function to call when the manifest has been loaded * @throws if the add-on does not contain a valid install manifest or the * XPI is incorrectly signed */ - loadManifest: function AI_loadManifest(aCallback) { - aCallback = makeSafe(aCallback); - let self = this; - function addRepositoryData(aAddon) { - // Try to load from the existing cache first - AddonRepository.getCachedAddonByID(aAddon.id, function loadManifest_getCachedAddonByID(aRepoAddon) { - if (aRepoAddon) { - aAddon._repositoryAddon = aRepoAddon; - self.name = self.name || aAddon._repositoryAddon.name; - aAddon.compatibilityOverrides = aRepoAddon.compatibilityOverrides; - aAddon.appDisabled = !isUsableAddon(aAddon); - aCallback(); - return; - } - - // It wasn't there so try to re-download it - AddonRepository.cacheAddons([aAddon.id], function loadManifest_cacheAddons() { - AddonRepository.getCachedAddonByID(aAddon.id, function loadManifest_getCachedAddonByID(aRepoAddon) { - aAddon._repositoryAddon = aRepoAddon; - self.name = self.name || aAddon._repositoryAddon.name; - aAddon.compatibilityOverrides = aRepoAddon ? - aRepoAddon.compatibilityOverrides : - null; - aAddon.appDisabled = !isUsableAddon(aAddon); - aCallback(); - }); - }); - }); - } - + loadManifest: Task.async(function* AI_loadManifest() { let zipreader = Cc["@mozilla.org/libjar/zip-reader;1"]. createInstance(Ci.nsIZipReader); try { zipreader.open(this.file); } catch (e) { zipreader.close(); - throw e; - } - - let x509 = zipreader.getSigningCert(null); - if (x509) { - logger.debug("Verifying XPI signature"); - if (verifyZipSigning(zipreader, x509)) { - this.certificate = x509; - if (this.certificate.commonName.length > 0) { - this.certName = this.certificate.commonName; - } else { - this.certName = this.certificate.organization; - } - } else { - zipreader.close(); - throw new Error("XPI is incorrectly signed"); - } + return Promise.reject([AddonManager.ERROR_CORRUPT_FILE, e]); } try { - this.addon = loadManifestFromZipReader(zipreader); + // loadManifestFromZipReader performs the certificate verification for us + this.addon = yield loadManifestFromZipReader(zipreader); } catch (e) { zipreader.close(); - throw e; - } - - if (this.addon.type == "multipackage") { - this._loadMultipackageManifests(zipreader, function loadManifest_loadMultipackageManifests() { - addRepositoryData(self.addon); - }); - return; - } + return Promise.reject([AddonManager.ERROR_CORRUPT_FILE, e]); + } + + if (mustSign(this.addon.type)) { + if (this.addon.signedState <= AddonManager.SIGNEDSTATE_MISSING) { + // This add-on isn't properly signed by a signature that chains to the + // trusted root. + let state = this.addon.signedState; + this.addon = null; + zipreader.close(); + + if (state == AddonManager.SIGNEDSTATE_MISSING) + return Promise.reject([AddonManager.ERROR_SIGNEDSTATE_REQUIRED, + "signature is required but missing"]) + + return Promise.reject([AddonManager.ERROR_CORRUPT_FILE, + "signature verification failed"]) + } + } + else if (this.addon.signedState == AddonManager.SIGNEDSTATE_UNKNOWN) { + // Check object signing certificate, if any + let x509 = zipreader.getSigningCert(null); + if (x509) { + logger.debug("Verifying XPI signature"); + if (verifyZipSigning(zipreader, x509)) { + this.certificate = x509; + if (this.certificate.commonName.length > 0) { + this.certName = this.certificate.commonName; + } else { + this.certName = this.certificate.organization; + } + } else { + zipreader.close(); + return Promise.reject([AddonManager.ERROR_CORRUPT_FILE, + "XPI is incorrectly signed"]); + } + } + } + + if (this.addon.type == "multipackage") + return this._loadMultipackageManifests(zipreader); zipreader.close(); this.updateAddonURIs(); this.addon._install = this; this.name = this.addon.selectedLocale.name; this.type = this.addon.type; this.version = this.addon.version; // Setting the iconURL to something inside the XPI locks the XPI and // makes it impossible to delete on Windows. //let newIcon = createWrapper(this.addon).iconURL; //if (newIcon) // this.iconURL = newIcon; - addRepositoryData(this.addon); - }, + // Try to load from the existing cache first + let repoAddon = yield new Promise(resolve => AddonRepository.getCachedAddonByID(this.addon.id, resolve)); + + // It wasn't there so try to re-download it + if (!repoAddon) { + yield new Promise(resolve => AddonRepository.cacheAddons([this.addon.id], resolve)); + repoAddon = yield new Promise(resolve => AddonRepository.getCachedAddonByID(this.addon.id, resolve)); + } + + this.addon._repositoryAddon = repoAddon; + this.name = this.name || this.addon._repositoryAddon.name; + this.addon.compatibilityOverrides = repoAddon ? + repoAddon.compatibilityOverrides : + null; + this.addon.appDisabled = !isUsableAddon(this.addon); + }), observe: function AI_observe(aSubject, aTopic, aData) { // Network is going offline this.cancel(); }, /** * Starts downloading the add-on's XPI file. @@ -5570,36 +5632,34 @@ AddonInstall.prototype = { let calculatedHash = getHashStringForCrypto(this.crypto); this.crypto = null; if (this.hash && calculatedHash != this.hash.data) { this.downloadFailed(AddonManager.ERROR_INCORRECT_HASH, "Downloaded file hash (" + calculatedHash + ") did not match provided hash (" + this.hash.data + ")"); return; } - try { - let self = this; - this.loadManifest(function onStopRequest_loadManifest() { - if (self.addon.isCompatible) { - self.downloadCompleted(); - } - else { - // TODO Should we send some event here (bug 557716)? - self.state = AddonManager.STATE_CHECKING; - new UpdateChecker(self.addon, { - onUpdateFinished: function onStopRequest_onUpdateFinished(aAddon) { - self.downloadCompleted(); - } - }, AddonManager.UPDATE_WHEN_ADDON_INSTALLED); - } - }); - } - catch (e) { - this.downloadFailed(AddonManager.ERROR_CORRUPT_FILE, e); - } + + let self = this; + this.loadManifest().then(() => { + if (self.addon.isCompatible) { + self.downloadCompleted(); + } + else { + // TODO Should we send some event here (bug 557716)? + self.state = AddonManager.STATE_CHECKING; + new UpdateChecker(self.addon, { + onUpdateFinished: function onStopRequest_onUpdateFinished(aAddon) { + self.downloadCompleted(); + } + }, AddonManager.UPDATE_WHEN_ADDON_INSTALLED); + } + }, ([error, message]) => { + this.downloadFailed(error, message); + }); } else { if (aRequest instanceof Ci.nsIHttpChannel) this.downloadFailed(AddonManager.ERROR_NETWORK_FAILURE, aRequest.responseStatus + " " + aRequest.responseStatusText); else this.downloadFailed(AddonManager.ERROR_NETWORK_FAILURE, aStatus); @@ -6614,17 +6674,17 @@ function AddonWrapper(aAddon) { return [objValue, false]; } ["id", "syncGUID", "version", "type", "isCompatible", "isPlatformCompatible", "providesUpdatesSecurely", "blocklistState", "blocklistURL", "appDisabled", "softDisabled", "skinnable", "size", "foreignInstall", "hasBinaryComponents", "strictCompatibility", "compatibilityOverrides", "updateURL", - "getDataDirectory", "multiprocessCompatible"].forEach(function(aProp) { + "getDataDirectory", "multiprocessCompatible", "signedState"].forEach(function(aProp) { this.__defineGetter__(aProp, function AddonWrapper_propertyGetter() aAddon[aProp]); }, this); ["fullDescription", "developerComments", "eula", "supportURL", "contributionURL", "contributionAmount", "averageRating", "reviewCount", "reviewURL", "totalDownloads", "weeklyDownloads", "dailyUsers", "repositoryStatus"].forEach(function(aProp) { this.__defineGetter__(aProp, function AddonWrapper_repoPropertyGetter() { @@ -7186,17 +7246,17 @@ DirectoryInstallLocation.prototype = { _readAddons: function DirInstallLocation__readAddons() { // Use a snapshot of the directory contents to avoid possible issues with // iterating over a directory while removing files from it (the YAFFS2 // embedded filesystem has this issue, see bug 772238). let entries = getDirectoryEntries(this._directory); for (let entry of entries) { let id = entry.leafName; - if (id == DIR_STAGE || id == DIR_XPI_STAGE || id == DIR_TRASH) + if (id == DIR_STAGE || id == DIR_TRASH) continue; let directLoad = false; if (entry.isFile() && id.substring(id.length - 4).toLowerCase() == ".xpi") { directLoad = true; id = id.substring(0, id.length - 4); } @@ -7330,28 +7390,16 @@ DirectoryInstallLocation.prototype = { } catch (e) { logger.warn("Failed to remove staging dir", e); // Failing to remove the staging directory is ignorable } }, /** - * Gets the directory used by old versions for staging XPI and JAR files ready - * to be installed. - * - * @return an nsIFile - */ - getXPIStagingDir: function DirInstallLocation_getXPIStagingDir() { - let dir = this._directory.clone(); - dir.append(DIR_XPI_STAGE); - return dir; - }, - - /** * Returns a directory that is normally on the same filesystem as the rest of * the install location and can be used for temporarily storing files during * safe move operations. Calling this method will delete the existing trash * directory and its contents. * * @return an nsIFile */ getTrashDir: function DirInstallLocation_getTrashDir() { @@ -7717,16 +7765,29 @@ WinRegInstallLocation.prototype = { * @see DirectoryInstallLocation */ isLinkedAddon: function RegInstallLocation_isLinkedAddon(aId) { return true; } }; #endif +// Make this a non-changable property so it can't be manipulated from other +// code in the app. +Object.defineProperty(this, "REQUIRE_SIGNING", { + configurable: false, + enumerable: false, + writable: false, +#ifdef MOZ_REQUIRE_SIGNING + value: true, +#else + value: false, +#endif +}); + let addonTypes = [ new AddonManagerPrivate.AddonType("extension", URI_EXTENSION_STRINGS, STRING_TYPE_NAME, AddonManager.VIEW_TYPE_LIST, 4000), new AddonManagerPrivate.AddonType("theme", URI_EXTENSION_STRINGS, STRING_TYPE_NAME, AddonManager.VIEW_TYPE_LIST, 5000), new AddonManagerPrivate.AddonType("dictionary", URI_EXTENSION_STRINGS,
--- a/toolkit/mozapps/extensions/internal/XPIProviderUtils.js +++ b/toolkit/mozapps/extensions/internal/XPIProviderUtils.js @@ -65,17 +65,17 @@ const PROP_JSON_FIELDS = ["id", "syncGUI "internalName", "updateURL", "updateKey", "optionsURL", "optionsType", "aboutURL", "iconURL", "icon64URL", "defaultLocale", "visible", "active", "userDisabled", "appDisabled", "pendingUninstall", "descriptor", "installDate", "updateDate", "applyBackgroundUpdates", "bootstrap", "skinnable", "size", "sourceURI", "releaseNotesURI", "softDisabled", "foreignInstall", "hasBinaryComponents", "strictCompatibility", "locales", "targetApplications", - "targetPlatforms", "multiprocessCompatible"]; + "targetPlatforms", "multiprocessCompatible", "signedState"]; // Time to wait before async save of XPI JSON database, in milliseconds const ASYNC_SAVE_DELAY_MS = 20; const PREFIX_ITEM_URI = "urn:mozilla:item:"; const RDFURI_ITEM_ROOT = "urn:mozilla:item:root" const PREFIX_NS_EM = "http://www.mozilla.org/2004/em-rdf#";
--- a/toolkit/mozapps/extensions/internal/moz.build +++ b/toolkit/mozapps/extensions/internal/moz.build @@ -23,13 +23,13 @@ if CONFIG['MOZ_WIDGET_TOOLKIT'] != 'andr EXTRA_PP_JS_MODULES.addons += [ 'XPIProvider.jsm', 'XPIProviderUtils.js', ] # This is used in multiple places, so is defined here to avoid it getting # out of sync. -DEFINES['MOZ_EXTENSIONS_DB_SCHEMA'] = 16 +DEFINES['MOZ_EXTENSIONS_DB_SCHEMA'] = 17 # Additional debugging info is exposed in debug builds if CONFIG['MOZ_EM_DEBUG']: DEFINES['MOZ_EM_DEBUG'] = 1
new file mode 100644 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/data/signing_checks/bootstrap_1/bootstrap.js @@ -0,0 +1,29 @@ +Components.utils.import("resource://gre/modules/Services.jsm"); + +const VERSION = 1; + +// Test steps chain from pref observers on *_reason, +// so always set that last +function install(data, reason) { + Services.prefs.setIntPref("bootstraptest.installed_version", VERSION); + Services.prefs.setIntPref("bootstraptest.install_oldversion", data.oldVersion); + Services.prefs.setIntPref("bootstraptest.install_reason", reason); +} + +function startup(data, reason) { + Services.prefs.setIntPref("bootstraptest.active_version", VERSION); + Services.prefs.setIntPref("bootstraptest.startup_oldversion", data.oldVersion); + Services.prefs.setIntPref("bootstraptest.startup_reason", reason); +} + +function shutdown(data, reason) { + Services.prefs.setIntPref("bootstraptest.active_version", 0); + Services.prefs.setIntPref("bootstraptest.shutdown_newversion", data.newVersion); + Services.prefs.setIntPref("bootstraptest.shutdown_reason", reason); +} + +function uninstall(data, reason) { + Services.prefs.setIntPref("bootstraptest.installed_version", 0); + Services.prefs.setIntPref("bootstraptest.uninstall_newversion", data.newVersion); + Services.prefs.setIntPref("bootstraptest.uninstall_reason", reason); +}
new file mode 100644 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/data/signing_checks/bootstrap_1/install.rdf @@ -0,0 +1,24 @@ +<?xml version="1.0"?> + +<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:em="http://www.mozilla.org/2004/em-rdf#"> + + <Description about="urn:mozilla:install-manifest"> + <em:id>test@tests.mozilla.org</em:id> + <em:version>1.0</em:version> + <em:bootstrap>true</em:bootstrap> + + <!-- Front End MetaData --> + <em:name>Test Add-on</em:name> + <em:updateURL>http://localhost:4444/update.rdf</em:updateURL> + + <em:targetApplication> + <Description> + <em:id>xpcshell@tests.mozilla.org</em:id> + <em:minVersion>2</em:minVersion> + <em:maxVersion>5</em:maxVersion> + </Description> + </em:targetApplication> + + </Description> +</RDF>
new file mode 100644 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/data/signing_checks/bootstrap_1/test.txt @@ -0,0 +1,1 @@ +This test file can be altered to break signing checks.
new file mode 100644 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/data/signing_checks/bootstrap_2/bootstrap.js @@ -0,0 +1,29 @@ +Components.utils.import("resource://gre/modules/Services.jsm"); + +const VERSION = 2; + +// Test steps chain from pref observers on *_reason, +// so always set that last +function install(data, reason) { + Services.prefs.setIntPref("bootstraptest.installed_version", VERSION); + Services.prefs.setIntPref("bootstraptest.install_oldversion", data.oldVersion); + Services.prefs.setIntPref("bootstraptest.install_reason", reason); +} + +function startup(data, reason) { + Services.prefs.setIntPref("bootstraptest.active_version", VERSION); + Services.prefs.setIntPref("bootstraptest.startup_oldversion", data.oldVersion); + Services.prefs.setIntPref("bootstraptest.startup_reason", reason); +} + +function shutdown(data, reason) { + Services.prefs.setIntPref("bootstraptest.active_version", 0); + Services.prefs.setIntPref("bootstraptest.shutdown_newversion", data.newVersion); + Services.prefs.setIntPref("bootstraptest.shutdown_reason", reason); +} + +function uninstall(data, reason) { + Services.prefs.setIntPref("bootstraptest.installed_version", 0); + Services.prefs.setIntPref("bootstraptest.uninstall_newversion", data.newVersion); + Services.prefs.setIntPref("bootstraptest.uninstall_reason", reason); +}
new file mode 100644 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/data/signing_checks/bootstrap_2/install.rdf @@ -0,0 +1,24 @@ +<?xml version="1.0"?> + +<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:em="http://www.mozilla.org/2004/em-rdf#"> + + <Description about="urn:mozilla:install-manifest"> + <em:id>test@tests.mozilla.org</em:id> + <em:version>2.0</em:version> + <em:bootstrap>true</em:bootstrap> + + <!-- Front End MetaData --> + <em:name>Test Add-on</em:name> + <em:updateURL>http://localhost:4444/update.rdf</em:updateURL> + + <em:targetApplication> + <Description> + <em:id>xpcshell@tests.mozilla.org</em:id> + <em:minVersion>4</em:minVersion> + <em:maxVersion>6</em:maxVersion> + </Description> + </em:targetApplication> + + </Description> +</RDF>
new file mode 100644 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/data/signing_checks/bootstrap_2/test.txt @@ -0,0 +1,1 @@ +This test file can be altered to break signing checks.
new file mode 100644 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/data/signing_checks/nonbootstrap_1/install.rdf @@ -0,0 +1,23 @@ +<?xml version="1.0"?> + +<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:em="http://www.mozilla.org/2004/em-rdf#"> + + <Description about="urn:mozilla:install-manifest"> + <em:id>test@tests.mozilla.org</em:id> + <em:version>1.0</em:version> + + <!-- Front End MetaData --> + <em:name>Test Add-on</em:name> + <em:updateURL>http://localhost:4444/update.rdf</em:updateURL> + + <em:targetApplication> + <Description> + <em:id>xpcshell@tests.mozilla.org</em:id> + <em:minVersion>2</em:minVersion> + <em:maxVersion>5</em:maxVersion> + </Description> + </em:targetApplication> + + </Description> +</RDF>
new file mode 100644 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/data/signing_checks/nonbootstrap_1/test.txt @@ -0,0 +1,1 @@ +This test file can be altered to break signing checks.
new file mode 100644 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/data/signing_checks/nonbootstrap_2/install.rdf @@ -0,0 +1,23 @@ +<?xml version="1.0"?> + +<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:em="http://www.mozilla.org/2004/em-rdf#"> + + <Description about="urn:mozilla:install-manifest"> + <em:id>test@tests.mozilla.org</em:id> + <em:version>2.0</em:version> + + <!-- Front End MetaData --> + <em:name>Test Add-on</em:name> + <em:updateURL>http://localhost:4444/update.rdf</em:updateURL> + + <em:targetApplication> + <Description> + <em:id>xpcshell@tests.mozilla.org</em:id> + <em:minVersion>4</em:minVersion> + <em:maxVersion>6</em:maxVersion> + </Description> + </em:targetApplication> + + </Description> +</RDF>
new file mode 100644 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/data/signing_checks/nonbootstrap_2/test.txt @@ -0,0 +1,1 @@ +This test file can be altered to break signing checks.
new file mode 100644 index 0000000000000000000000000000000000000000..a62faf1c52b3b5aadb1383d16b4cb5419eaedd1f GIT binary patch literal 5158 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
new file mode 100644 index 0000000000000000000000000000000000000000..5444b951a48a7b9114d69cf44fbe7eb45e7280ab GIT binary patch literal 5146 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
new file mode 100644 index 0000000000000000000000000000000000000000..122c4d56b3d37bb98458ae9055630ca1f9aed670 GIT binary patch literal 5150 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
new file mode 100644 index 0000000000000000000000000000000000000000..150918a8c157f6855b4d9ea6796965153af4fe17 GIT binary patch literal 5156 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
new file mode 100644 index 0000000000000000000000000000000000000000..3c6cd647031e61ea5d9c2f60419956a629185451 GIT binary patch literal 4628 zc$|$`cQoAFxBp@=7@~&=ksx{}h~7o-M2&8uk22a7z4z!PO7s$<C4$i+MhPJ%q9%k< zM(;ApFS&2M^?Sej?p^Plv(8#)pS90EXYbG6e|)yC1|EnS001z6^o>@*H8(28Kmfpi z8~}cwYAGAa^FGp6;SYp5**d^{_+Z{P3np-pf;d{S0q1^ls;<O5qcw&JBFab_w!4v; zN*~sH*Sp9l;;a-JAKq2EDGyP|U=BQ@bf8cwrFzdYv#_;;{c(9bCb@QbSh#z-aIg?G z-qjt%Cs&LL(*s9W#cb(EAbaRRW^AEy+&2iq1PlTEM8jADT*td?I8`AWVM5?Nl~6f# z5IA)n0;U5I^vmT<tZp9y$3<q$HB>KqvOKhzf9ldnn_A!KCul6dRSyj0?aXei=cO%A zgu{9yZ7l2dPv}ILVO-^<YuAc_dK!ejC?XGZyrhxb#$)qY^Eva<qtU|oIxjRGBveK; z^wagqUS)dhA)JlkwlTGfWAqz1Z7X(1i+a~Sj)L(06(kM!eb3s(LS{2~vG?(M#}z!N zT(-{BUotcz?G_78EMB@To!6tpBYt+>1CaU!8IBG_TJBWZZ}`ssl{B0H2p~qTSGVrH zI^i>Q3Caj-ZI>kQ(r@t)4YRyB!OGSlH+qL}3Kjs^H`HkmKJ*x$dSyyC8;HDT{#pIQ z*bI;Yz@ny&FbF4lLjFh@qFBipH`|OKea8D~xzjOf)XVCLr-bhbf@5VA0j-`W@ijvD zS-75|{gw6)@M@*`nw$LnC)#qq2jrEX!&cU%&j9U?_?&u^<_!x}vrPj~AsF%!c02e- z?tKMjTw{aS_`X0G>af%UyKXW4?adHUFb_&fsbV4-&)03)q?X!o_?5POOL=->14^Z$ zQ_eaDGv%4bR)uFa(<lXvvBem}Ig|`U^wm&tlcvR&1!(8FoZM@5R)s<c*SY6&y}a9F zO(}9**;5Zq9CZ=Y4qiXj>uPia&&yvL7G>vq2t2D5f6B!x&Fx+_7^muSFdRxvQ@qU7 zu1j^~*>?9{iIr$V_~JoySj8m$DOxa6ws)uQ)a$55;-s)J&HxW(IpCi;N2WLU;@uz1 zQ(rpAi6If49iu}ZRJ7iG+_uBFZ)-~}!DcnubF~CmB&K4Xo92?bbYCn?E&GBOT9WK` z>DG^hq#!J1D_S+8>nzz<?j@vi#|GeQkJ{&AWonOF?98YNOD7{StoAT2^iO4iNj}db z3I_A+<MyC0e(8Ku><dLuVSXc~5;RzuX%4UB$>DG`+=G$RQJ|*Z<jb6+c@TCpH$`Ak zzg!R5B(?!6`hs~(3{GkyKM-QfU4ljZ2qYt2o#B+wenA2&K*JU8XdQ0pC3j$X=Bo@8 zHN|8z3PFl;ti{hp*;ZNv>+*A^TW^$o({Zb}zooZPMuZKSDyZ7nG=c6%2;H<O!#Hun zz?R-Bse&|VI+BFqBtlkknI+nDdnut7#-}F@`%_Nx^o%^UDkz3yYW~A@O7NTN6W7BX zqNS5@&(U*k49T}>OM{Y%?_QEpuRO#HZm6Cdw$lybTQvs?6A0J(Kos44W$veL`O4^V zQj9MiT2kFI{9ONs)5w?VjkAe<rz7t#t)1AVjTLNk?}xt2N$v}Iv6OUovPk%{^+{;D zanr{{nFeY4@o!lb;Cn_Rb#l5Q*@;ox+o^|@WFX9*_0)PoW5F(Gv47g;xNMi})1hw* z0}YN&!+XekidV0UUSVGP4+3`RA78*&9a*P`FUJpUHWC}k*HjYO12uW)%39w^CJT<~ z&Zv_r;V-tUj0*tG?m6UDj<;rF${j`ViENses}qtVI1TG_9?5Wgzr|=2FXjFqL-^67 z+xYSw=9&Td4L23EUb974q`iB!#1ofL-l4^8KW3OOyNX;~+>D(8rCe)LM&aJ86C+5o z)Z8BxiSb3+dX1vlUUZRtFy}8_Xd!33lz~1SB}5r)9*dsG=9tr9+MexF%@FsmR9&j< ze%z+X(~VMf_1V6ywt!y?uP9<4!XjoD-=83q)vcr{?~HF1(EFaXehWRc!flBlxZG<C zUFfvy3a{8wr=W-s2EdJYp+F&4`#ZTA<(_p1D5P%@acy*P6*03;U$Ez&P4$lUoFvX} zd$0TXPC#PD*8n0xuh&$QG?G!v-Tl}3s|Q7Noz<8NXPUWOdUYmSAz?oFqg|N<@6MjU zzNgI&H9t$NO&Ap=1!rwdE;zA!K3c274E5oXvr4A})b;82OWr8X+wARE6>2~eV!qm+ zD+w6IQg^EuNW@*>R5MhJTF9-tgfP5V10KrD;(ClC5JLkt@ozJwSC}Yrm&y@ii^IXn zvYB1H<yRY)!9#=r8LB}mA0mG8=)NxU97=yt;ZPtw)-zqB5cSrch(ZU>NS&ZOeUGZk zqgHq><T7;n<HCw+R2*@n5bX=h+-z?CyEoXtWv9a-6&$wZ>5${sE+Ktn+b)!>mI{-C zwAp%{<Wa$qIILd|3^-Fv*pRV2UomQfpOR|DzQ2(>GPU+qO<yuU>@mZ<PGcdvzTbAY zqPFDdXF)Hv^(KJ@?dL3vW|>UFd*-g8DArC8vaMh}0G7bY4Ob{sS&R}t${U21ndX2S z&OUtd_deC+l{PTf2$d~JznuJm<xm?dZG2yKasEJ08s~C#*l3Vjf9E3%9$ewc<3_wx zHCTEEsM=BOO9c?(wdUhDR3Z7<NlEoy@<}_R!ZV<IgI!2499ujsv^l&MPM70xnq5Q` zRE1q0;Mj*AWBeYQ1gfX{>5l5PrRF!^P1}XamS)!1ep}lwAX0DSkUv=Z^jxv8K8XOO zIeTc8e<x)iri81bO3)pq38~yvA90`%09Ej3Y5_w4C=~?ALe>2OIXD5Fuw<M(gGlj! z2nC%FbOZtgwg>|VA&1S1f&e)uCq3xEZR#tu7@}L2kU}H`e<0@fs*fz|S&F58RGKg~ zcvLp?P!3Nxv}X#iS6*l;(O63Cig^;S)F5^HTzu*M&D^jsiK}Znb>>jHo&dUmLULbS z<RTk6_3(I)%j)tnbArk>R;1L+RAg+(c&c}P=g#{Rw@VsQv9sjFXYXT!&RSJbmd}a= zyjI!!ho&ul;*>*T*X<bQt@zh0Ihht_?jjwOU5D}8j2f!&%P&Ot1nshI8)3@brc9Xo z`H@@WOyrr%wi^lyVNEt^S*K2!d!1Yse(}Ny%`KB6@uQ5{!}R&tQO(7g$MdHfh(tdm z3EPIh%riYu630gTB3Pp3_%3mi_Z9{!*8W=k#-zd?)5R>*Wacn>tz|lCa^eBL`|e0v zl8gPF3Nq2c)^4t4nIf|S(h=Ie4RV-S?g#J-`HZJ+4+-zqN$B~2>d7w56i?5mye`%z ze6G-E2i5aZ2xLE=czxu&C`#S;dHr+<sRBu3*<&!=N6pblZDe&;D9)O`#JbNt-<&#m zgQ$*&w%0~=s^yr1)7+S=&rWY-c&~W8IDGS|E)@2$on-g%46RdKk*N%N(YYOUT%2C> z9L-~GkzV$L&A{#h-+~Zjt(u33C4w-x(EYj#2{XQWIo}eGg`XqE==Jz#()}PWR?y() zz|#uc+XmT1TAYrwF{=6Qo1Gq?bvK<Us>cF{={0PQI`!II-kHL(?|F}kDM>Iku{Og6 z7}RHZ*U&{DKCjfsRf=CZdiaQ5CN}P#q{qq(j%1lPh|QTx)#YwC>>h)(-ZoWu<J>h6 zC2o}B&@cV==AFDfo5Kjm@OU}S!oJh58o}ZUQVg9JZ?`_uCSzo7JiT_vy$FcCd^<1X zj-x4iTVCLq^a{{+hf*K1e8+^W=TMP&pulsm1bVwFXy5geE^~x^`fK9>@4GycYg0>I z+#IE6dQER$xj*1s#t0~JkM9`yO#PrHmNr&$7X0X^R{6x+#y{p0>*gn}M9Dkk)n+#@ z<pzVq`p_ht*gu*VO~R$eb1S_c;A`ARx2B-SrTK?szN>08u<o30CLL%B;2snt-GgN~ zqOuk?4JFJ)%Y#F1$*WkULA9y|-yjUDg>zdYbtX2tQl;E&zl-QAb<1xod|aT>EV?a! zQv2$sxn!lQiTW$E9?XsCm@NX|!((0pmhU!QW<OxK3%C!R43Z)~m3Ghfc|9uCWm8cV zOwt{ax5WBOt*VdJT?DIz433y@wk9*I1^cWyT1^BXH0IyVqnAP=^8VO9=a#TF<~g?* z$g-X_H<@+Nu6}~%*kp8mI2a2jcc=Ick#yo<ZlFEjDjMR?p0%T8|B8$p&}JJ?4!+!e z+3B$vL(<W0uiLczoKW&b%8Kj0%a4*Db6mXk{Ump)+uK?*sSQNJ4}3^A=uO<<@y}DL z_?u+MYv`@#cQCxB_ff9tNwop(-(oI(uzO<^ouL^>mx_L?ghJ;)77LRcIku({b{4K8 zIwge4{PCzk6o>MznTq99`<MddrM**#m6&R%Vt<yk++iBUZiG9|+Xy97tPK~3goS+N zQt9XSKKpnRCK_>MI2~b^#-F=;DLDvpaqV+cXr5-;XP5}>oGW=Gbd}ItdddQqV{4=) zd)<KUpX3Pl7dcwCj8I8_9cCbh0|4>306+rJ0Z?~u9~)O!KA63uks&^S$Nrtk$k(3) zzyY27()yECMd^=d_Y07BV!1T~{R*OqtM&5qtB*i8LGxP6ZCMwwTf9WU(#eO;wfB}H z*v7|3cj^in%-QyDzR{-Zibs1<){{gYOmCB6<Na#a!MaWv545PnPG7$!d}z&=E%FGO zM`^wthyBuHR=U8W@K#bj$IG|e2(79%6fU2~N-}Iz`9c}N_~AUOV>15M1zpY`c~o_! z&rvhQ!;yYHWvubg*K1^*HJA+V%(xmLManO`!}a-R#6!WD9OYZ=B@YmK<8N6&yLA(x zMLtp1;*Gp`(L@rG&K3UqXoe*%BZ`+1S^)cS^((r))9E!^Fu&H&%7i$Q_UaRY3A-*I zT=8-UH%Hp@>r^W>TZDkkR}3mk#nnu~JbMROe&Iy%Qi14FaK#$bhwrInza1-k^j=tC zOxc5gWK=f1n88o`(uw;M|8D?@e*pjhs9ym5(c0i5_G>TpCtsxld>p)e_<a0*{)|4w z|Bk+)Gt`^;w-U1>)YXC6&c>bD)`8i^)yDzmV9)I1!E6h2uyJAbhB~=J-JO{2oE_|3 zy!nWA|AP{9CwNJB{E}|q{Dl&`*|<X;fB(SEG3lc`Nw@$Iy@U-J*eSVhEZ!R-p#U1u zr|hWD^Imau<F2u`rtsaE+>Y1{6JtpiGh}el%0zcTid}Bt<WR}uA<G|>VO(210QA;C zY4UM=zVSNX=nqL?h4p%y?#hN9(=yh+9mMNGiNDKa=l81Mf%IyGplsme@W@VO$r86b zvi2v1I!X~YAD>m{SGIgaT@*f8jZNiF*5Q6&Fz7p}KUf_yi9psSS)lRn)hMjaw8#3V z-q-x12WI+DXJgiw?4iFT|6hyh@A$vBBgvTNxCRd=uR4lUgO_V;bi7$#U`qJ=fv+IS zcSuu*mv7=&Yji{tPpFQX$3plZQHpT{a#5%O!yD<-6rT6~IO*or9xi>GR4}uWhUUJb zqiJM7bR`=9<{307AoqSg)@d#CVe~8v=U4IJ;8NrLb8q6mM<@>P51oqN7MSq<xqyF< z-<Qk67)gQuPdWdW!GBK3e;a&32K=At`Bw-3nrZ)bkm|<Y=iOf|{1v|c5hC)x1yff8 TpWwFw{;z5O>)AZ=-_pMTmef^j
new file mode 100644 index 0000000000000000000000000000000000000000..cdf4325acde44b9ef829471ea730ba3c3ab90c72 GIT binary patch literal 4634 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 ZKKVbAsSCry|LuVHtK0rMHi!JT^<PN0O_2Zq
new file mode 100644 index 0000000000000000000000000000000000000000..9d50f0825046db67b679ca8c62b95200b111b73b GIT binary patch literal 1156 zc$^FHW@Zs#U}E54NXl$-tLJuotHa2^u$qN|frmkcAt^t<q`0Igu|O}YI5dQlfjNAU zY-lkMmsW5yFtU6DDh29hVAwj@-}|tGz|r?VMV$TCPU)@KQX#~@cSXcY)11kf!Z+Wv z%)QAbxPQ0stZfR5zVvUud;CoP{pt<huWWg8k$bkr*5u&VwkI@rPlz}-Zjao?H^Vf< zt$of}!<<)4H}@1y?py8|G$Fn5=;6~f-@b5^7c7pNc2IlOsU}_Lz?-)YYXvap9uU1S zNwlf4(I)08qweJSU%Y)1V$<(SO0X{3#B^?x%IX)~D@%7>+rFlF@ujZ02g-_h<T(%A z3;)j|eRqCk<+aZmu|k~Q8`iE+H&)nirkb0}SKa!%)bok}zjZbfs*>HN9u2eX-Ens6 zJ7&Mh=bv0?Jjc@c)>1Y?HTT0ECN^bpodowkiyO4OJm>DuV7~5dC2R9UZg>8}P)prE zd_Svyz4-r9Smx$|d&gO|Eez^kJ>U+2hj=V7#6SK1q2mh-#tlpi4BWsF&&(?>NzBR7 zD@sWN2Y3+n0B@aiI`5Eyfa~|4T;^+J+eJc^xE6X$3El0O`(4d2;bi}bc_x|r_w{{d za9LWFs9An)u4KGw^!A_y`e{1XvqaA=wupJ|IN|Mf>nB>L)*Ui-m@0J1#P^Dg+pRe# zYIep<vQYGF@~u4bEt)ZR<EG694tGBun{rZm{T2Hdj#ttug?kiy%HBo3IHouyccw1S z`U7hlUpQXhz?HrD(!r@ksf}EXs-b^Q=vaiCYbF~#P37~ee5Y4!tEOCAs??tT%v0p| ztsl0G>}wA;Eez#bmNsvvhhx8OLQk2SwuAmEmwcWV)0kW(;%{kaMgDtt&2jdoBT+xy z;^s`)rKP!{|8KUPi%N;((a2~I@plvU$?a^|^RtOPn0vvqBMHHwP2wWU%I~CEW(9}j zhTbq1el&lFTl<q&#U7at8?_&bEqS}b{_6u*iSJ6~-IbsE+gZv>-0tYlWZw^p5iVei zFfg<MW2AQL`fPI`8-zK4F;bFRT%uP}Q38&R*}$lnjTs#w8JWcjAcYEPnK`Kn$%%Oi zNvR5nIVGt@sVNF2`3gxzsfpPN#hK}OnR)37$r-81*~NNX0p5&Ea?H50p9BL40J#iH z8bK`7%*YDKjA#x)HW4#<A)8nPq)-wYD<rXDH4s<mAREZS%zzYDtdOw6FpZTBq=Oj< LKLY7mW)Kemx+<e{
new file mode 100644 index 0000000000000000000000000000000000000000..6ba1efd727a26604778641ad78abbb0027c0bb5d GIT binary patch literal 691 zc$^FHW@Zs#U}E54cof_0*0-@S*nyFOp^J%uftx{wAv3SIBrzvPuP7xgG=!6ZS^a@r zs45VbR&X;gvU~%o1ZrkrXbn2;ci2E+&u?v?s<Jt4Tq^@-1+DzTBF=Kh_tCa#e>&di za@N@Pea={|^srg={JAsl*34NSt@=@XyPw3~u4jcpYq?uKU4P5>yQ^&V3yy&FDQ3RH z=HEANWVedfn{MKnIZG(|ZRhW62cj0;)p6Qk?Kq>bbZO`_caM`zj<Zy_PlYa@=eke! z*w1OZFLg=%k(g0COL)zq$yaYKIMq^iV2Mgs=EmoIdse-kQarVD6+^#G+4S8xlP~YH zGHjpWDy6>AY+vB{9m*{4*IaCvU(y<Hu|b!)jnmRPV_C(=XVMRSLj@N%`!cAXTDeZ* z!}sgYlQ{H!Pd>S5aYF3hg?)0rUpUq*TYQpfhHb%(D_ccWn++0gyuLckEhow;diuK) zOaG(`E_UMlYxpFd<q&IuZ_3*a`!7d~4bHjdvvHrXZ(1N-=Jz*U`+|M|FcP?ck-)%k z6&MM%Ti0is1KA+V0gQx_)Z!Aol8O>=3<v^4Ul20}LNYRo6+j9V(lT>W6_OM46p~UE z5_3vYi&9e*O7azwic%A^6^b*{^D^_&6_PVjle3HUxB|QxndF#p#lHjt2mrYZOBz8e rl+?fqNeyW3fEb9&FUSVA0x5*gSRp>cFpZTBq=N|v=L6|~Agv4lkSX$=
--- a/toolkit/mozapps/extensions/test/xpcshell/head_addons.js +++ b/toolkit/mozapps/extensions/test/xpcshell/head_addons.js @@ -9,16 +9,17 @@ const XULAPPINFO_CONTRACTID = "@mozilla. const XULAPPINFO_CID = Components.ID("{c763b610-9d49-455a-bbd2-ede71682a1ac}"); const PREF_EM_CHECK_UPDATE_SECURITY = "extensions.checkUpdateSecurity"; const PREF_EM_STRICT_COMPATIBILITY = "extensions.strictCompatibility"; const PREF_EM_MIN_COMPAT_APP_VERSION = "extensions.minCompatibleAppVersion"; const PREF_EM_MIN_COMPAT_PLATFORM_VERSION = "extensions.minCompatiblePlatformVersion"; const PREF_GETADDONS_BYIDS = "extensions.getAddons.get.url"; const PREF_GETADDONS_BYIDS_PERFORMANCE = "extensions.getAddons.getWithPerformance.url"; +const PREF_XPI_SIGNATURES_REQUIRED = "xpinstall.signatures.required"; // Forcibly end the test if it runs longer than 15 minutes const TIMEOUT_MS = 900000; Components.utils.import("resource://gre/modules/addons/AddonRepository.jsm"); Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); Components.utils.import("resource://gre/modules/FileUtils.jsm"); Components.utils.import("resource://gre/modules/Services.jsm"); @@ -1448,16 +1449,19 @@ Services.prefs.setBoolPref("extensions.s // By default don't check for hotfixes Services.prefs.setCharPref("extensions.hotfix.id", ""); // By default, set min compatible versions to 0 Services.prefs.setCharPref(PREF_EM_MIN_COMPAT_APP_VERSION, "0"); Services.prefs.setCharPref(PREF_EM_MIN_COMPAT_PLATFORM_VERSION, "0"); +// Disable signature checks for most tests +Services.prefs.setBoolPref(PREF_XPI_SIGNATURES_REQUIRED, false); + // Register a temporary directory for the tests. const gTmpD = gProfD.clone(); gTmpD.append("temp"); gTmpD.create(AM_Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY); registerDirectory("TmpD", gTmpD); // Write out an empty blocklist.xml file to the profile to ensure nothing // is blocklisted by default @@ -1525,19 +1529,16 @@ do_register_cleanup(function addon_clean var testDir = gProfD.clone(); testDir.append("extensions"); testDir.append("trash"); pathShouldntExist(testDir); testDir.leafName = "staged"; pathShouldntExist(testDir); - testDir.leafName = "staged-xpis"; - pathShouldntExist(testDir); - shutdownManager(); // Clear commonly set prefs. try { Services.prefs.clearUserPref(PREF_EM_CHECK_UPDATE_SECURITY); } catch (e) {} try { Services.prefs.clearUserPref(PREF_EM_STRICT_COMPATIBILITY);
--- a/toolkit/mozapps/extensions/test/xpcshell/test_migrate1.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_migrate1.js @@ -192,45 +192,21 @@ function run_test() { // since, it should still be disabled but should be incompatible do_check_neq(a5, null); do_check_true(a5.userDisabled); do_check_true(a5.appDisabled); do_check_false(a5.isActive); do_check_false(isExtensionInAddonsList(profileDir, a5.id)); do_check_false(a5.hasBinaryComponents); - // addon6 should be installed and compatible and packed unless unpacking is - // forced - do_check_neq(a6, null); - do_check_false(a6.userDisabled); - do_check_false(a6.appDisabled); - do_check_true(a6.isActive); - do_check_true(isExtensionInAddonsList(profileDir, a6.id)); - if (Services.prefs.getBoolPref("extensions.alwaysUnpack")) - do_check_eq(a6.getResourceURI("install.rdf").scheme, "file"); - else - do_check_eq(a6.getResourceURI("install.rdf").scheme, "jar"); - do_check_false(a6.hasBinaryComponents); - - // addon7 should be installed and compatible and unpacked - do_check_neq(a7, null); - do_check_false(a7.userDisabled); - do_check_false(a7.appDisabled); - do_check_true(a7.isActive); - do_check_true(isExtensionInAddonsList(profileDir, a7.id)); - do_check_eq(a7.getResourceURI("install.rdf").scheme, "file"); - do_check_false(a7.hasBinaryComponents); - - // addon8 should be installed and compatible and have binary components - do_check_neq(a8, null); - do_check_false(a8.userDisabled); - do_check_false(a8.appDisabled); - do_check_true(a8.isActive); - do_check_true(isExtensionInAddonsList(profileDir, a8.id)); - do_check_true(a8.hasBinaryComponents); + // addon6, addon7 and addon8 will have been lost as they were staged in the + // pre-Firefox 4.0 directory + do_check_eq(a6, null); + do_check_eq(a7, null); + do_check_eq(a8, null); // Theme 1 was previously enabled do_check_neq(t1, null); do_check_false(t1.userDisabled); do_check_false(t1.appDisabled); do_check_true(t1.isActive); do_check_true(isThemeInAddonsList(profileDir, t1.id)); do_check_false(hasFlag(t1.permissions, AddonManager.PERM_CAN_ENABLE)); @@ -238,13 +214,11 @@ function run_test() { // Theme 2 was previously disabled do_check_neq(t1, null); do_check_true(t2.userDisabled); do_check_false(t2.appDisabled); do_check_false(t2.isActive); do_check_false(isThemeInAddonsList(profileDir, t2.id)); do_check_true(hasFlag(t2.permissions, AddonManager.PERM_CAN_ENABLE)); - do_check_false(stagedXPIs.exists()); - do_execute_soon(do_test_finished); }); }
--- a/toolkit/mozapps/extensions/test/xpcshell/test_migrate3.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_migrate3.js @@ -191,35 +191,20 @@ function run_test() { // addon5 was disabled and compatible but a new version has been installed // since, it should still be disabled but should be incompatible do_check_neq(a5, null); do_check_true(a5.userDisabled); do_check_true(a5.appDisabled); do_check_false(a5.isActive); do_check_false(isExtensionInAddonsList(profileDir, a5.id)); - // addon6 should be installed and compatible and packed unless unpacking is - // forced - do_check_neq(a6, null); - do_check_false(a6.userDisabled); - do_check_false(a6.appDisabled); - do_check_true(a6.isActive); - do_check_true(isExtensionInAddonsList(profileDir, a6.id)); - if (Services.prefs.getBoolPref("extensions.alwaysUnpack")) - do_check_eq(a6.getResourceURI("install.rdf").scheme, "file"); - else - do_check_eq(a6.getResourceURI("install.rdf").scheme, "jar"); - - // addon7 should be installed and compatible and unpacked - do_check_neq(a7, null); - do_check_false(a7.userDisabled); - do_check_false(a7.appDisabled); - do_check_true(a7.isActive); - do_check_true(isExtensionInAddonsList(profileDir, a7.id)); - do_check_eq(a7.getResourceURI("install.rdf").scheme, "file"); + // addon6 and addon7 will have been lost as they were staged in the + // pre-Firefox 4.0 directory + do_check_eq(a6, null); + do_check_eq(a7, null); // Theme 1 was previously disabled do_check_neq(t1, null); do_check_true(t1.userDisabled); do_check_false(t1.appDisabled); do_check_false(t1.isActive); do_check_true(isThemeInAddonsList(profileDir, t1.id)); do_check_true(hasFlag(t1.permissions, AddonManager.PERM_CAN_ENABLE)); @@ -227,13 +212,11 @@ function run_test() { // Theme 2 was previously disabled do_check_neq(t1, null); do_check_true(t2.userDisabled); do_check_false(t2.appDisabled); do_check_false(t2.isActive); do_check_false(isThemeInAddonsList(profileDir, t2.id)); do_check_true(hasFlag(t2.permissions, AddonManager.PERM_CAN_ENABLE)); - do_check_false(stagedXPIs.exists()); - do_execute_soon(do_test_finished); }); }
new file mode 100644 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/test_signed_inject.js @@ -0,0 +1,320 @@ +// Enable signature checks for these tests +Services.prefs.setBoolPref(PREF_XPI_SIGNATURES_REQUIRED, true); +// Disable update security +Services.prefs.setBoolPref(PREF_EM_CHECK_UPDATE_SECURITY, false); + +const DATA = "data/signing_checks/"; +const ADDONS = { + bootstrap: { + unsigned: "unsigned_bootstrap_2.xpi", + badid: "signed_bootstrap_badid_2.xpi", + signed: "signed_bootstrap_2.xpi", + }, + nonbootstrap: { + unsigned: "unsigned_nonbootstrap_2.xpi", + badid: "signed_nonbootstrap_badid_2.xpi", + signed: "signed_nonbootstrap_2.xpi", + } +}; +const ID = "test@tests.mozilla.org"; + +const profileDir = gProfD.clone(); +profileDir.append("extensions"); + +// Deletes a file from the test add-on in the profile +function breakAddon(file) { + if (TEST_UNPACKED) { + file.append("test.txt"); + file.remove(true); + } + else { + var zipW = AM_Cc["@mozilla.org/zipwriter;1"]. + createInstance(AM_Ci.nsIZipWriter); + zipW.open(file, FileUtils.MODE_RDWR | FileUtils.MODE_APPEND); + zipW.removeEntry("test.txt", false); + zipW.close(); + } +} + +function resetPrefs() { + Services.prefs.setIntPref("bootstraptest.active_version", -1); + Services.prefs.setIntPref("bootstraptest.installed_version", -1); + Services.prefs.setIntPref("bootstraptest.startup_reason", -1); + Services.prefs.setIntPref("bootstraptest.shutdown_reason", -1); + Services.prefs.setIntPref("bootstraptest.install_reason", -1); + Services.prefs.setIntPref("bootstraptest.uninstall_reason", -1); + Services.prefs.setIntPref("bootstraptest.startup_oldversion", -1); + Services.prefs.setIntPref("bootstraptest.shutdown_newversion", -1); + Services.prefs.setIntPref("bootstraptest.install_oldversion", -1); + Services.prefs.setIntPref("bootstraptest.uninstall_newversion", -1); +} + +function getActiveVersion() { + return Services.prefs.getIntPref("bootstraptest.active_version"); +} + +function run_test() { + createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "4", "4"); + + // Start and stop the manager to initialise everything in the profile before + // actual testing + startupManager(); + shutdownManager(); + resetPrefs(); + + run_next_test(); +} + +// Injecting into profile (bootstrap) +add_task(function*() { + manuallyInstall(do_get_file(DATA + ADDONS.bootstrap.unsigned), profileDir, ID); + + startupManager(); + + // Currently we leave the sideloaded add-on there but just don't run it + let addon = yield promiseAddonByID(ID); + do_check_neq(addon, null); + do_check_true(addon.appDisabled); + do_check_false(addon.isActive); + do_check_eq(addon.signedState, AddonManager.SIGNEDSTATE_MISSING); + do_check_eq(getActiveVersion(), -1); + + addon.uninstall(); + yield promiseShutdownManager(); + resetPrefs(); + + do_check_false(getFileForAddon(profileDir, ID).exists()); +}); + +add_task(function*() { + manuallyInstall(do_get_file(DATA + ADDONS.bootstrap.signed), profileDir, ID); + breakAddon(getFileForAddon(profileDir, ID)); + + startupManager(); + + // Currently we leave the sideloaded add-on there but just don't run it + let addon = yield promiseAddonByID(ID); + do_check_neq(addon, null); + do_check_true(addon.appDisabled); + do_check_false(addon.isActive); + do_check_eq(addon.signedState, AddonManager.SIGNEDSTATE_BROKEN); + do_check_eq(getActiveVersion(), -1); + + addon.uninstall(); + yield promiseShutdownManager(); + resetPrefs(); + + do_check_false(getFileForAddon(profileDir, ID).exists()); +}); + +add_task(function*() { + manuallyInstall(do_get_file(DATA + ADDONS.bootstrap.badid), profileDir, ID); + + startupManager(); + + // Currently we leave the sideloaded add-on there but just don't run it + let addon = yield promiseAddonByID(ID); + do_check_neq(addon, null); + do_check_true(addon.appDisabled); + do_check_false(addon.isActive); + do_check_eq(addon.signedState, AddonManager.SIGNEDSTATE_BROKEN); + do_check_eq(getActiveVersion(), -1); + + addon.uninstall(); + yield promiseShutdownManager(); + resetPrefs(); + + do_check_false(getFileForAddon(profileDir, ID).exists()); +}); + +// Installs a signed add-on then modifies it in place breaking its signing +add_task(function*() { + manuallyInstall(do_get_file(DATA + ADDONS.bootstrap.signed), profileDir, ID); + + // Make it appear to come from the past so when we modify it later it is + // detected during startup. Obviously malware can bypass this method of + // detection but the periodic scan will catch that + yield promiseSetExtensionModifiedTime(getFileForAddon(profileDir, ID).path, Date.now() - 600000); + + startupManager(); + let addon = yield promiseAddonByID(ID); + do_check_neq(addon, null); + do_check_false(addon.appDisabled); + do_check_true(addon.isActive); + do_check_eq(addon.signedState, AddonManager.SIGNEDSTATE_SIGNED); + do_check_eq(getActiveVersion(), 2); + + yield promiseShutdownManager(); + do_check_eq(getActiveVersion(), 0); + + breakAddon(getFileForAddon(profileDir, ID)); + resetPrefs(); + + startupManager(); + + addon = yield promiseAddonByID(ID); + do_check_neq(addon, null); + do_check_true(addon.appDisabled); + do_check_false(addon.isActive); + do_check_eq(addon.signedState, AddonManager.SIGNEDSTATE_BROKEN); + do_check_eq(getActiveVersion(), -1); + + let ids = AddonManager.getStartupChanges(AddonManager.STARTUP_CHANGE_DISABLED); + do_check_eq(ids.length, 1); + do_check_eq(ids[0], ID); + + addon.uninstall(); + yield promiseShutdownManager(); + resetPrefs(); + + do_check_false(getFileForAddon(profileDir, ID).exists()); +}); + +// Injecting into profile (non-bootstrap) +add_task(function*() { + manuallyInstall(do_get_file(DATA + ADDONS.nonbootstrap.unsigned), profileDir, ID); + + startupManager(); + + // Currently we leave the sideloaded add-on there but just don't run it + let addon = yield promiseAddonByID(ID); + do_check_neq(addon, null); + do_check_true(addon.appDisabled); + do_check_false(addon.isActive); + do_check_eq(addon.signedState, AddonManager.SIGNEDSTATE_MISSING); + do_check_false(isExtensionInAddonsList(profileDir, ID)); + + addon.uninstall(); + yield promiseRestartManager(); + yield promiseShutdownManager(); + + do_check_false(getFileForAddon(profileDir, ID).exists()); +}); + +add_task(function*() { + manuallyInstall(do_get_file(DATA + ADDONS.nonbootstrap.signed), profileDir, ID); + breakAddon(getFileForAddon(profileDir, ID)); + + startupManager(); + + // Currently we leave the sideloaded add-on there but just don't run it + let addon = yield promiseAddonByID(ID); + do_check_neq(addon, null); + do_check_true(addon.appDisabled); + do_check_false(addon.isActive); + do_check_eq(addon.signedState, AddonManager.SIGNEDSTATE_BROKEN); + do_check_false(isExtensionInAddonsList(profileDir, ID)); + + addon.uninstall(); + yield promiseRestartManager(); + yield promiseShutdownManager(); + + do_check_false(getFileForAddon(profileDir, ID).exists()); +}); + +add_task(function*() { + manuallyInstall(do_get_file(DATA + ADDONS.nonbootstrap.badid), profileDir, ID); + + startupManager(); + + // Currently we leave the sideloaded add-on there but just don't run it + let addon = yield promiseAddonByID(ID); + do_check_neq(addon, null); + do_check_true(addon.appDisabled); + do_check_false(addon.isActive); + do_check_eq(addon.signedState, AddonManager.SIGNEDSTATE_BROKEN); + do_check_false(isExtensionInAddonsList(profileDir, ID)); + + addon.uninstall(); + yield promiseRestartManager(); + yield promiseShutdownManager(); + + do_check_false(getFileForAddon(profileDir, ID).exists()); +}); + +// Installs a signed add-on then modifies it in place breaking its signing +add_task(function*() { + manuallyInstall(do_get_file(DATA + ADDONS.nonbootstrap.signed), profileDir, ID); + + // Make it appear to come from the past so when we modify it later it is + // detected during startup. Obviously malware can bypass this method of + // detection but the periodic scan will catch that + yield promiseSetExtensionModifiedTime(getFileForAddon(profileDir, ID).path, Date.now() - 60000); + + startupManager(); + let addon = yield promiseAddonByID(ID); + do_check_neq(addon, null); + do_check_false(addon.appDisabled); + do_check_true(addon.isActive); + do_check_eq(addon.signedState, AddonManager.SIGNEDSTATE_SIGNED); + do_check_true(isExtensionInAddonsList(profileDir, ID)); + + yield promiseShutdownManager(); + + breakAddon(getFileForAddon(profileDir, ID)); + + startupManager(); + + addon = yield promiseAddonByID(ID); + do_check_neq(addon, null); + do_check_true(addon.appDisabled); + do_check_false(addon.isActive); + do_check_eq(addon.signedState, AddonManager.SIGNEDSTATE_BROKEN); + do_check_false(isExtensionInAddonsList(profileDir, ID)); + + let ids = AddonManager.getStartupChanges(AddonManager.STARTUP_CHANGE_DISABLED); + do_check_eq(ids.length, 1); + do_check_eq(ids[0], ID); + + addon.uninstall(); + yield promiseRestartManager(); + yield promiseShutdownManager(); + + do_check_false(getFileForAddon(profileDir, ID).exists()); +}); + +// Stage install then modify before startup (non-bootstrap) +add_task(function*() { + startupManager(); + yield promiseInstallAllFiles([do_get_file(DATA + ADDONS.nonbootstrap.signed)]); + yield promiseShutdownManager(); + + let staged = profileDir.clone(); + staged.append("staged"); + staged.append(do_get_expected_addon_name(ID)); + do_check_true(staged.exists()); + + breakAddon(staged); + startupManager(); + + // Should have refused to install the broken staged version + let addon = yield promiseAddonByID(ID); + do_check_eq(addon, null); + + let install = getFileForAddon(profileDir, ID); + do_check_false(install.exists()); + + yield promiseShutdownManager(); +}); + +// Manufacture staged install (bootstrap) +add_task(function*() { + let stage = profileDir.clone(); + stage.append("staged"); + + let file = manuallyInstall(do_get_file(DATA + ADDONS.bootstrap.signed), stage, ID); + breakAddon(file); + + startupManager(); + + // Should have refused to install the broken staged version + let addon = yield promiseAddonByID(ID); + do_check_eq(addon, null); + do_check_eq(getActiveVersion(), -1); + + let install = getFileForAddon(profileDir, ID); + do_check_false(install.exists()); + + yield promiseShutdownManager(); + resetPrefs(); +});
new file mode 100644 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/test_signed_install.js @@ -0,0 +1,265 @@ +// Enable signature checks for these tests +Services.prefs.setBoolPref(PREF_XPI_SIGNATURES_REQUIRED, true); +// Disable update security +Services.prefs.setBoolPref(PREF_EM_CHECK_UPDATE_SECURITY, false); + +const DATA = "data/signing_checks/"; +const ADDONS = { + bootstrap: { + unsigned: "unsigned_bootstrap_2.xpi", + badid: "signed_bootstrap_badid_2.xpi", + preliminary: "preliminary_bootstrap_2.xpi", + signed: "signed_bootstrap_2.xpi", + }, +}; +const WORKING = "signed_bootstrap_1.xpi"; +const ID = "test@tests.mozilla.org"; + +Components.utils.import("resource://testing-common/httpd.js"); +var gServer = new HttpServer(); +gServer.start(4444); + +// Creates an add-on with a broken signature by changing an existing file +function createBrokenAddonModify(file) { + let brokenFile = gTmpD.clone(); + brokenFile.append("broken.xpi"); + file.copyTo(brokenFile.parent, brokenFile.leafName); + + var stream = AM_Cc["@mozilla.org/io/string-input-stream;1"]. + createInstance(AM_Ci.nsIStringInputStream); + stream.setData("FOOBAR", -1); + var zipW = AM_Cc["@mozilla.org/zipwriter;1"]. + createInstance(AM_Ci.nsIZipWriter); + zipW.open(brokenFile, FileUtils.MODE_RDWR | FileUtils.MODE_APPEND); + zipW.removeEntry("test.txt", false); + zipW.addEntryStream("test.txt", 0, AM_Ci.nsIZipWriter.COMPRESSION_NONE, + stream, false); + zipW.close(); + + return brokenFile; +} + +// Creates an add-on with a broken signature by adding a new file +function createBrokenAddonAdd(file) { + let brokenFile = gTmpD.clone(); + brokenFile.append("broken.xpi"); + file.copyTo(brokenFile.parent, brokenFile.leafName); + + var stream = AM_Cc["@mozilla.org/io/string-input-stream;1"]. + createInstance(AM_Ci.nsIStringInputStream); + stream.setData("FOOBAR", -1); + var zipW = AM_Cc["@mozilla.org/zipwriter;1"]. + createInstance(AM_Ci.nsIZipWriter); + zipW.open(brokenFile, FileUtils.MODE_RDWR | FileUtils.MODE_APPEND); + zipW.addEntryStream("test2.txt", 0, AM_Ci.nsIZipWriter.COMPRESSION_NONE, + stream, false); + zipW.close(); + + return brokenFile; +} + +// Creates an add-on with a broken signature by removing an existing file +function createBrokenAddonRemove(file) { + let brokenFile = gTmpD.clone(); + brokenFile.append("broken.xpi"); + file.copyTo(brokenFile.parent, brokenFile.leafName); + + var stream = AM_Cc["@mozilla.org/io/string-input-stream;1"]. + createInstance(AM_Ci.nsIStringInputStream); + stream.setData("FOOBAR", -1); + var zipW = AM_Cc["@mozilla.org/zipwriter;1"]. + createInstance(AM_Ci.nsIZipWriter); + zipW.open(brokenFile, FileUtils.MODE_RDWR | FileUtils.MODE_APPEND); + zipW.removeEntry("test.txt", false); + zipW.close(); + + return brokenFile; +} + +function createInstall(url) { + return new Promise(resolve => { + AddonManager.getInstallForURL(url, resolve, "application/x-xpinstall"); + }); +} + +function serveUpdateRDF(leafName) { + gServer.registerPathHandler("/update.rdf", function(request, response) { + let updateData = {}; + updateData[ID] = [{ + version: "2.0", + targetApplications: [{ + id: "xpcshell@tests.mozilla.org", + minVersion: "4", + maxVersion: "6", + updateLink: "http://localhost:4444/" + leafName + }] + }]; + + response.setStatusLine(request.httpVersion, 200, "OK"); + response.write(createUpdateRDF(updateData)); + }); +} + + +function* test_install_broken(file, expectedError) { + gServer.registerFile("/" + file.leafName, file); + + let install = yield createInstall("http://localhost:4444/" + file.leafName); + yield promiseCompleteAllInstalls([install]); + + do_check_eq(install.state, AddonManager.STATE_DOWNLOAD_FAILED); + do_check_eq(install.error, expectedError); + do_check_eq(install.addon, null); + + gServer.registerFile("/" + file.leafName, null); +} + +function* test_install_working(file, expectedSignedState) { + gServer.registerFile("/" + file.leafName, file); + + let install = yield createInstall("http://localhost:4444/" + file.leafName); + yield promiseCompleteAllInstalls([install]); + + do_check_eq(install.state, AddonManager.STATE_INSTALLED); + do_check_neq(install.addon, null); + do_check_eq(install.addon.signedState, expectedSignedState); + + gServer.registerFile("/" + file.leafName, null); + + install.addon.uninstall(); +} + +function* test_update_broken(file, expectedError) { + // First install the older version + yield promiseInstallAllFiles([do_get_file(DATA + WORKING)]); + + gServer.registerFile("/" + file.leafName, file); + serveUpdateRDF(file.leafName); + + let addon = yield promiseAddonByID(ID); + let install = yield promiseFindAddonUpdates(addon); + yield promiseCompleteAllInstalls([install]); + + do_check_eq(install.state, AddonManager.STATE_DOWNLOAD_FAILED); + do_check_eq(install.error, expectedError); + do_check_eq(install.addon, null); + + gServer.registerFile("/" + file.leafName, null); + gServer.registerPathHandler("/update.rdf", null); + + addon.uninstall(); +} + +function* test_update_working(file, expectedSignedState) { + // First install the older version + yield promiseInstallAllFiles([do_get_file(DATA + WORKING)]); + + gServer.registerFile("/" + file.leafName, file); + serveUpdateRDF(file.leafName); + + let addon = yield promiseAddonByID(ID); + let install = yield promiseFindAddonUpdates(addon); + yield promiseCompleteAllInstalls([install]); + + do_check_eq(install.state, AddonManager.STATE_INSTALLED); + do_check_neq(install.addon, null); + do_check_eq(install.addon.signedState, expectedSignedState); + + gServer.registerFile("/" + file.leafName, null); + gServer.registerPathHandler("/update.rdf", null); + + install.addon.uninstall(); +} + +function run_test() { + createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "4", "4"); + startupManager(); + + run_next_test(); +} + +// Try to install a broken add-on +add_task(function*() { + let file = createBrokenAddonModify(do_get_file(DATA + ADDONS.bootstrap.signed)); + yield test_install_broken(file, AddonManager.ERROR_CORRUPT_FILE); + file.remove(true); +}); + +add_task(function*() { + let file = createBrokenAddonAdd(do_get_file(DATA + ADDONS.bootstrap.signed)); + yield test_install_broken(file, AddonManager.ERROR_CORRUPT_FILE); + file.remove(true); +}); + +add_task(function*() { + let file = createBrokenAddonRemove(do_get_file(DATA + ADDONS.bootstrap.signed)); + yield test_install_broken(file, AddonManager.ERROR_CORRUPT_FILE); + file.remove(true); +}); + +// Try to install an add-on with an incorrect ID +add_task(function*() { + let file = do_get_file(DATA + ADDONS.bootstrap.badid); + yield test_install_broken(file, AddonManager.ERROR_CORRUPT_FILE); +}); + +// Try to install an unsigned add-on +add_task(function*() { + let file = do_get_file(DATA + ADDONS.bootstrap.unsigned); + yield test_install_broken(file, AddonManager.ERROR_SIGNEDSTATE_REQUIRED); +}); + +// Try to install a preliminarily reviewed add-on +add_task(function*() { + let file = do_get_file(DATA + ADDONS.bootstrap.preliminary); + yield test_install_working(file, AddonManager.SIGNEDSTATE_PRELIMINARY); +}); + +// Try to install a signed add-on +add_task(function*() { + let file = do_get_file(DATA + ADDONS.bootstrap.signed); + yield test_install_working(file, AddonManager.SIGNEDSTATE_SIGNED); +}); + +// Try to update to a broken add-on +add_task(function*() { + let file = createBrokenAddonModify(do_get_file(DATA + ADDONS.bootstrap.signed)); + yield test_update_broken(file, AddonManager.ERROR_CORRUPT_FILE); + file.remove(true); +}); + +add_task(function*() { + let file = createBrokenAddonAdd(do_get_file(DATA + ADDONS.bootstrap.signed)); + yield test_update_broken(file, AddonManager.ERROR_CORRUPT_FILE); + file.remove(true); +}); + +add_task(function*() { + let file = createBrokenAddonRemove(do_get_file(DATA + ADDONS.bootstrap.signed)); + yield test_update_broken(file, AddonManager.ERROR_CORRUPT_FILE); + file.remove(true); +}); + +// Try to update to an add-on with an incorrect ID +add_task(function*() { + let file = do_get_file(DATA + ADDONS.bootstrap.badid); + yield test_update_broken(file, AddonManager.ERROR_CORRUPT_FILE); +}); + +// Try to update to an unsigned add-on +add_task(function*() { + let file = do_get_file(DATA + ADDONS.bootstrap.unsigned); + yield test_update_broken(file, AddonManager.ERROR_SIGNEDSTATE_REQUIRED); +}); + +// Try to update to a preliminarily reviewed add-on +add_task(function*() { + let file = do_get_file(DATA + ADDONS.bootstrap.preliminary); + yield test_update_working(file, AddonManager.SIGNEDSTATE_PRELIMINARY); +}); + +// Try to update to a signed add-on +add_task(function*() { + let file = do_get_file(DATA + ADDONS.bootstrap.signed); + yield test_update_working(file, AddonManager.SIGNEDSTATE_SIGNED); +});
new file mode 100644 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/test_signed_migrate.js @@ -0,0 +1,164 @@ +// Enable signature checks for these tests +Services.prefs.setBoolPref(PREF_XPI_SIGNATURES_REQUIRED, true); +// Disable update security +Services.prefs.setBoolPref(PREF_EM_CHECK_UPDATE_SECURITY, false); + +const DATA = "data/signing_checks/"; +const ADDONS = { + bootstrap: { + unsigned: "unsigned_bootstrap_2.xpi", + badid: "signed_bootstrap_badid_2.xpi", + signed: "signed_bootstrap_2.xpi", + }, + nonbootstrap: { + unsigned: "unsigned_nonbootstrap_2.xpi", + badid: "signed_nonbootstrap_badid_2.xpi", + signed: "signed_nonbootstrap_2.xpi", + } +}; +const ID = "test@tests.mozilla.org"; + +const profileDir = gProfD.clone(); +profileDir.append("extensions"); + +function resetPrefs() { + Services.prefs.setIntPref("bootstraptest.active_version", -1); + Services.prefs.setIntPref("bootstraptest.installed_version", -1); + Services.prefs.setIntPref("bootstraptest.startup_reason", -1); + Services.prefs.setIntPref("bootstraptest.shutdown_reason", -1); + Services.prefs.setIntPref("bootstraptest.install_reason", -1); + Services.prefs.setIntPref("bootstraptest.uninstall_reason", -1); + Services.prefs.setIntPref("bootstraptest.startup_oldversion", -1); + Services.prefs.setIntPref("bootstraptest.shutdown_newversion", -1); + Services.prefs.setIntPref("bootstraptest.install_oldversion", -1); + Services.prefs.setIntPref("bootstraptest.uninstall_newversion", -1); +} + +function getActiveVersion() { + return Services.prefs.getIntPref("bootstraptest.active_version"); +} + +function run_test() { + createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "4", "4"); + + // Start and stop the manager to initialise everything in the profile before + // actual testing + startupManager(); + shutdownManager(); + resetPrefs(); + + run_next_test(); +} + +// Removes the signedState field from add-ons in the json database to make it +// look like the database was written with an older version of the application +function stripDB() { + let jData = loadJSON(gExtensionsJSON); + jData.schemaVersion--; + + for (let addon of jData.addons) + delete addon.signedState; + + saveJSON(jData, gExtensionsJSON); +} + +function* test_breaking_migrate(addons, test, expectedSignedState) { + // Startup as the old version + gAppInfo.version = "4"; + startupManager(true); + + // Install the signed add-on + yield promiseInstallAllFiles([do_get_file(DATA + addons.signed)]); + // Restart to let non-restartless add-ons install fully + yield promiseRestartManager(); + yield promiseShutdownManager(); + resetPrefs(); + stripDB(); + + // Now replace it with the version to test. Doing this so quickly shouldn't + // trigger the file modification code to detect the change by itself. + manuallyUninstall(profileDir, ID); + manuallyInstall(do_get_file(DATA + addons[test]), profileDir, ID); + + // Update the application + gAppInfo.version = "5"; + startupManager(true); + + let addon = yield promiseAddonByID(ID); + do_check_neq(addon, null); + do_check_true(addon.appDisabled); + do_check_false(addon.isActive); + do_check_eq(addon.signedState, expectedSignedState); + + // Add-on shouldn't be active + if (addons == ADDONS.bootstrap) + do_check_eq(getActiveVersion(), -1); + else + do_check_false(isExtensionInAddonsList(profileDir, ID)); + + // Should have flagged the change during startup + let changes = AddonManager.getStartupChanges(AddonManager.STARTUP_CHANGE_DISABLED); + do_check_eq(changes.length, 1); + do_check_eq(changes[0], ID); + + addon.uninstall(); + // Restart to let non-restartless add-ons uninstall fully + yield promiseRestartManager(); + yield shutdownManager(); + resetPrefs(); +} + +function* test_working_migrate(addons, test, expectedSignedState) { + // Startup as the old version + gAppInfo.version = "4"; + startupManager(true); + + // Install the signed add-on + yield promiseInstallAllFiles([do_get_file(DATA + addons.signed)]); + // Restart to let non-restartless add-ons install fully + yield promiseRestartManager(); + yield promiseShutdownManager(); + resetPrefs(); + stripDB(); + + // Now replace it with the version to test. Doing this so quickly shouldn't + // trigger the file modification code to detect the change by itself. + manuallyUninstall(profileDir, ID); + manuallyInstall(do_get_file(DATA + addons[test]), profileDir, ID); + + // Update the application + gAppInfo.version = "5"; + startupManager(true); + + let addon = yield promiseAddonByID(ID); + do_check_neq(addon, null); + do_check_false(addon.appDisabled); + do_check_true(addon.isActive); + do_check_eq(addon.signedState, expectedSignedState); + + if (addons == ADDONS.bootstrap) + do_check_eq(getActiveVersion(), 2); + else + do_check_true(isExtensionInAddonsList(profileDir, ID)); + + addon.uninstall(); + // Restart to let non-restartless add-ons uninstall fully + yield promiseRestartManager(); + yield shutdownManager(); + resetPrefs(); +} + +add_task(function*() { + yield test_breaking_migrate(ADDONS.bootstrap, "unsigned", AddonManager.SIGNEDSTATE_MISSING); + yield test_breaking_migrate(ADDONS.nonbootstrap, "unsigned", AddonManager.SIGNEDSTATE_MISSING); +}); + +add_task(function*() { + yield test_breaking_migrate(ADDONS.bootstrap, "badid", AddonManager.SIGNEDSTATE_BROKEN); + yield test_breaking_migrate(ADDONS.nonbootstrap, "badid", AddonManager.SIGNEDSTATE_BROKEN); +}); + +add_task(function*() { + yield test_working_migrate(ADDONS.bootstrap, "signed", AddonManager.SIGNEDSTATE_SIGNED); + yield test_working_migrate(ADDONS.nonbootstrap, "signed", AddonManager.SIGNEDSTATE_SIGNED); +});
--- a/toolkit/mozapps/extensions/test/xpcshell/xpcshell.ini +++ b/toolkit/mozapps/extensions/test/xpcshell/xpcshell.ini @@ -17,12 +17,17 @@ skip-if = appname != "firefox" [test_isReady.js] [test_metadata_update.js] [test_pluginInfoURL.js] [test_provider_markSafe.js] [test_provider_shutdown.js] [test_provider_unsafe_access_shutdown.js] [test_provider_unsafe_access_startup.js] [test_shutdown.js] +[test_signed_inject.js] +skip-if = true +[test_signed_install.js] +run-sequentially = Uses hardcoded ports in xpi files. +[test_signed_migrate.js] [test_XPIcancel.js] [test_XPIStates.js] [include:xpcshell-shared.ini]