Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 85ab1f5

Browse files
dioxKevinMind
authored andcommittedMar 19, 2025·
Consider Firefox < 128 and != 115 as incompatible due to root CA expiration (#13502)
* Consider Firefox < 128 and != 115 as incompatible due to root CA expiration For now, this only applies to compatibility checks, so this will cause the add-on detail pages to show the "You need an updated version of Firefox" warning and button to show up instead of the install button for those users.
1 parent 6b39343 commit 85ab1f5

File tree

5 files changed

+134
-49
lines changed

5 files changed

+134
-49
lines changed
 

‎src/amo/constants.js

+2
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,8 @@ export const VISIBLE_ADDON_TYPES_MAPPING: {|
113113

114114
// Incompatibility codes for clients that can't install an add-on.
115115
export const INCOMPATIBLE_FIREFOX_FOR_IOS = 'INCOMPATIBLE_FIREFOX_FOR_IOS';
116+
export const INCOMPATIBLE_OLD_ROOT_CERT_VERSION =
117+
'INCOMPATIBLE_OLD_ROOT_CERT_VERSION';
116118
export const INCOMPATIBLE_OVER_MAX_VERSION = 'INCOMPATIBLE_OVER_MAX_VERSION';
117119
export const INCOMPATIBLE_NOT_FIREFOX = 'INCOMPATIBLE_NOT_FIREFOX';
118120
export const INCOMPATIBLE_UNDER_MIN_VERSION = 'INCOMPATIBLE_UNDER_MIN_VERSION';

‎src/amo/utils/compatibility.js

+24
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {
1616
INCOMPATIBLE_ANDROID_UNSUPPORTED,
1717
INCOMPATIBLE_FIREFOX_FOR_IOS,
1818
INCOMPATIBLE_NOT_FIREFOX,
19+
INCOMPATIBLE_OLD_ROOT_CERT_VERSION,
1920
INCOMPATIBLE_OVER_MAX_VERSION,
2021
INCOMPATIBLE_UNDER_MIN_VERSION,
2122
INCOMPATIBLE_UNSUPPORTED_PLATFORM,
@@ -77,6 +78,25 @@ export const isFirefox = ({
7778
return userAgentInfo.browser.name === 'Firefox';
7879
};
7980

81+
export const isFirefoxWithOldRootCerts = ({
82+
userAgentInfo,
83+
}: {|
84+
userAgentInfo: UserAgentInfoType,
85+
|}): boolean => {
86+
// If the userAgent is false there was likely a programming error.
87+
invariant(userAgentInfo, 'userAgentInfo is required');
88+
89+
const majorAppVersion = parseInt(userAgentInfo.browser?.version, 10);
90+
91+
return (
92+
isFirefox({ userAgentInfo }) &&
93+
// Firefox 128 and higher or latest Firefox ESR 115 have the updated root.
94+
// We can't single out ESR but excluding 115 entirely is good enough.
95+
majorAppVersion < 128 &&
96+
majorAppVersion !== 115
97+
);
98+
};
99+
80100
export const isDesktop = ({
81101
userAgentInfo,
82102
}: {|
@@ -173,6 +193,10 @@ export function isCompatibleWithUserAgent({
173193
};
174194
}
175195

196+
if (isFirefoxWithOldRootCerts({ userAgentInfo })) {
197+
return { compatible: false, reason: INCOMPATIBLE_OLD_ROOT_CERT_VERSION };
198+
}
199+
176200
// Do version checks, if this add-on has minimum or maximum version
177201
// requirements.
178202
// The addons-moz-compare API is quite strange; a result of

‎tests/unit/amo/test_searchUtils.js

+3-3
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ describe(__filename, () => {
104104
describe('when clientApp is CLIENT_APP_ANDROID', () => {
105105
it('does not set promoted and does not override compatibleWithVersion when browser is Firefox for Android', () => {
106106
const { state } = dispatchClientMetadata({
107-
userAgent: userAgentsByPlatform.android.firefox70,
107+
userAgent: userAgentsByPlatform.android.firefox136,
108108
});
109109

110110
const newFilters = addVersionCompatibilityToFilters({
@@ -115,14 +115,14 @@ describe(__filename, () => {
115115
expect(newFilters).toEqual({
116116
addonType: ADDON_TYPE_EXTENSION,
117117
clientApp: CLIENT_APP_ANDROID,
118-
compatibleWithVersion: '70.0',
118+
compatibleWithVersion: '136.0',
119119
query: 'foo',
120120
});
121121
});
122122

123123
it.each([
124124
// This works because it is Firefox Desktop.
125-
userAgentsByPlatform.windows.firefox40,
125+
userAgentsByPlatform.windows.firefox115,
126126
userAgentsByPlatform.mac.chrome41,
127127
])(
128128
'does not set promoted but overrides compatibleWithVersion when browser is not Firefox for Android - userAgent: %s',

‎tests/unit/amo/utils/test_compatibility.js

+97-46
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
INCOMPATIBLE_ANDROID_UNSUPPORTED,
1010
INCOMPATIBLE_FIREFOX_FOR_IOS,
1111
INCOMPATIBLE_NOT_FIREFOX,
12+
INCOMPATIBLE_OLD_ROOT_CERT_VERSION,
1213
INCOMPATIBLE_OVER_MAX_VERSION,
1314
INCOMPATIBLE_UNDER_MIN_VERSION,
1415
INCOMPATIBLE_UNSUPPORTED_PLATFORM,
@@ -27,6 +28,7 @@ import {
2728
isFirefox,
2829
isFirefoxForAndroid,
2930
isFirefoxForIOS,
31+
isFirefoxWithOldRootCerts,
3032
} from 'amo/utils/compatibility';
3133
import {
3234
createFakeLocation,
@@ -90,6 +92,40 @@ describe(__filename, () => {
9092
});
9193
});
9294

95+
describe('isFirefoxWithOldRootCerts', () => {
96+
it.each([
97+
...userAgents.androidWebkit,
98+
...userAgents.chromeAndroid,
99+
...userAgents.chrome,
100+
])('returns false for %s', (userAgent) => {
101+
expect(
102+
isFirefoxWithOldRootCerts({ userAgentInfo: UAParser(userAgent) }),
103+
).toEqual(false);
104+
});
105+
106+
it.each([
107+
userAgentsByPlatform.windows.firefox115,
108+
userAgentsByPlatform.mac.firefox128,
109+
userAgentsByPlatform.mac.firefox136,
110+
userAgentsByPlatform.android.firefox136,
111+
])('returns false for %s', (userAgent) => {
112+
expect(
113+
isFirefoxWithOldRootCerts({ userAgentInfo: UAParser(userAgent) }),
114+
).toEqual(false);
115+
});
116+
117+
it.each([
118+
userAgentsByPlatform.windows.firefox40,
119+
userAgentsByPlatform.mac.firefox69,
120+
userAgentsByPlatform.linux.firefox10,
121+
userAgentsByPlatform.android.firefox70,
122+
])('returns true for %s', (userAgent) => {
123+
expect(
124+
isFirefoxWithOldRootCerts({ userAgentInfo: UAParser(userAgent) }),
125+
).toEqual(true);
126+
});
127+
});
128+
93129
describe('isDesktop', () => {
94130
it.each([...userAgents.chrome, ...userAgents.firefox])(
95131
'returns true for %s',
@@ -112,7 +148,7 @@ describe(__filename, () => {
112148
const _isCompatibleWithUserAgent = ({
113149
addon = createInternalAddonWithLang(fakeAddon),
114150
currentVersion = createInternalVersionWithLang(fakeAddon.current_version),
115-
userAgentInfo = UAParser(userAgentsByPlatform.windows.firefox40),
151+
userAgentInfo = UAParser(userAgentsByPlatform.windows.firefox115),
116152
...rest
117153
}) => {
118154
return isCompatibleWithUserAgent({
@@ -126,7 +162,7 @@ describe(__filename, () => {
126162
it('is compatible with Firefox', () => {
127163
expect(
128164
_isCompatibleWithUserAgent({
129-
userAgentInfo: UAParser(userAgents.firefox[0]),
165+
userAgentInfo: UAParser(userAgentsByPlatform.windows.firefox115),
130166
}),
131167
).toEqual({ compatible: true, reason: null });
132168
});
@@ -141,24 +177,10 @@ describe(__filename, () => {
141177
});
142178
});
143179

144-
it('is compatible with Firefox for Android, when promoted', () => {
145-
userAgents.firefoxAndroid.forEach((userAgent) => {
146-
expect(
147-
_isCompatibleWithUserAgent({
148-
addon: createInternalAddonWithLang({
149-
...fakeAddon,
150-
promoted: { category: RECOMMENDED, apps: [CLIENT_APP_ANDROID] },
151-
}),
152-
userAgentInfo: UAParser(userAgent),
153-
}),
154-
).toEqual({ compatible: true, reason: null });
155-
});
156-
});
157-
158-
it('is compatible with Firefox >= 69', () => {
180+
it('is compatible with Firefox >= 128', () => {
159181
expect(
160182
_isCompatibleWithUserAgent({
161-
userAgentInfo: UAParser(userAgentsByPlatform.mac.firefox69),
183+
userAgentInfo: UAParser(userAgentsByPlatform.mac.firefox128),
162184
}),
163185
).toEqual({ compatible: true, reason: null });
164186
});
@@ -185,23 +207,23 @@ describe(__filename, () => {
185207
).toEqual({ compatible: false, reason: INCOMPATIBLE_NOT_FIREFOX });
186208
});
187209

188-
it('should mark Firefox 10 as incompatible with a minVersion of 10.1', () => {
210+
it('should mark Firefox 128 as incompatible with a minVersion of 128.1', () => {
189211
const userAgentInfo = {
190-
browser: { name: 'Firefox', version: '10.0' },
212+
browser: { name: 'Firefox', version: '128.0' },
191213
os: { name: 'Windows' },
192214
};
193215
expect(
194216
_isCompatibleWithUserAgent({
195-
minVersion: '10.1',
217+
minVersion: '128.1',
196218
userAgentInfo,
197219
}),
198220
).toEqual({ compatible: false, reason: INCOMPATIBLE_UNDER_MIN_VERSION });
199221
});
200222

201-
it('should mark Firefox 24 as compatible with a maxVersion of 8', () => {
223+
it('should mark Firefox 115 as compatible with a maxVersion of 8', () => {
202224
// https://github.com/mozilla/addons-frontend/issues/2074
203225
const userAgentInfo = {
204-
browser: { name: 'Firefox', version: '24.0' },
226+
browser: { name: 'Firefox', version: '115.0' },
205227
os: { name: 'Windows' },
206228
};
207229
expect(
@@ -218,7 +240,7 @@ describe(__filename, () => {
218240

219241
it('should mark Firefox as compatible when no min or max version', () => {
220242
const userAgentInfo = {
221-
browser: { name: 'Firefox', version: '10.0' },
243+
browser: { name: 'Firefox', version: '128.0' },
222244
os: { name: 'Windows' },
223245
};
224246
expect(
@@ -232,7 +254,7 @@ describe(__filename, () => {
232254
// WebExtensions are marked as having a maxVersion of "*" by addons-server
233255
// if their manifests don't contain explicit version information.
234256
const userAgentInfo = {
235-
browser: { name: 'Firefox', version: '54.0' },
257+
browser: { name: 'Firefox', version: '128.0' },
236258
os: { name: 'Windows' },
237259
};
238260
expect(
@@ -324,13 +346,44 @@ describe(__filename, () => {
324346
promoted: { category: RECOMMENDED, apps: [CLIENT_APP_ANDROID] },
325347
}),
326348
currentVersion,
327-
userAgentInfo: UAParser(userAgentsByPlatform.android.firefox40Mobile),
349+
userAgentInfo: UAParser(userAgentsByPlatform.android.firefox136),
328350
}),
329351
).toEqual({
330352
compatible: false,
331353
reason: INCOMPATIBLE_ANDROID_UNSUPPORTED,
332354
});
333355
});
356+
357+
it('is incompatible with Firefox < 128 because of root cert expiration', () => {
358+
const userAgentInfo = {
359+
browser: { name: 'Firefox', version: '127.0' },
360+
os: { name: 'Windows' },
361+
};
362+
// Would normally be considered compatible, but because Firefox is < 128
363+
// (and not 115, see test below) it's not.
364+
expect(
365+
_isCompatibleWithUserAgent({
366+
minVersion: '57.0',
367+
userAgentInfo,
368+
}),
369+
).toEqual({
370+
compatible: false,
371+
reason: INCOMPATIBLE_OLD_ROOT_CERT_VERSION,
372+
});
373+
});
374+
375+
it('is still compatible with Firefox 115', () => {
376+
const userAgentInfo = {
377+
browser: { name: 'Firefox', version: '115.0' },
378+
os: { name: 'Windows' },
379+
};
380+
expect(
381+
_isCompatibleWithUserAgent({
382+
minVersion: '57.0',
383+
userAgentInfo,
384+
}),
385+
).toEqual({ compatible: true, reason: null });
386+
});
334387
});
335388

336389
describe('getCompatibleVersions', () => {
@@ -463,7 +516,7 @@ describe(__filename, () => {
463516
};
464517

465518
it('returns true for Firefox (reason undefined when compatibile)', () => {
466-
const { browser, os } = UAParser(userAgentsByPlatform.mac.firefox57);
519+
const { browser, os } = UAParser(userAgentsByPlatform.mac.firefox136);
467520
const userAgentInfo = { browser, os };
468521
const clientApp = CLIENT_APP_FIREFOX;
469522
const currentVersion = createInternalVersionWithLang({
@@ -491,7 +544,7 @@ describe(__filename, () => {
491544
});
492545

493546
it('returns maxVersion when set', () => {
494-
const { browser, os } = UAParser(userAgents.firefox[0]);
547+
const { browser, os } = UAParser(userAgentsByPlatform.mac.firefox136);
495548
const userAgentInfo = { browser, os };
496549

497550
expect(
@@ -514,7 +567,7 @@ describe(__filename, () => {
514567
});
515568

516569
it('returns minVersion when set', () => {
517-
const { browser, os } = UAParser(userAgents.firefox[0]);
570+
const { browser, os } = UAParser(userAgentsByPlatform.mac.firefox136);
518571
const userAgentInfo = { browser, os };
519572

520573
expect(
@@ -565,7 +618,7 @@ describe(__filename, () => {
565618
});
566619

567620
it('returns incompatible when currentVersion is null', () => {
568-
const { browser, os } = UAParser(userAgents.firefox[0]);
621+
const { browser, os } = UAParser(userAgentsByPlatform.mac.firefox136);
569622
const userAgentInfo = { browser, os };
570623
const clientApp = CLIENT_APP_FIREFOX;
571624

@@ -584,7 +637,7 @@ describe(__filename, () => {
584637
});
585638

586639
it('returns compatible if strict compatibility is off', () => {
587-
const { browser, os } = UAParser(userAgents.firefox[4]);
640+
const { browser, os } = UAParser(userAgentsByPlatform.mac.firefox136);
588641
const userAgentInfo = { browser, os };
589642

590643
expect(
@@ -595,7 +648,7 @@ describe(__filename, () => {
595648
compatibility: {
596649
...fakeAddon.current_version.compatibility,
597650
[CLIENT_APP_FIREFOX]: {
598-
max: '56.*',
651+
max: '135.*',
599652
min: '24.0',
600653
},
601654
},
@@ -608,7 +661,7 @@ describe(__filename, () => {
608661
});
609662

610663
it('returns incompatible if strict compatibility enabled', () => {
611-
const { browser, os } = UAParser(userAgents.firefox[5]);
664+
const { browser, os } = UAParser(userAgentsByPlatform.mac.firefox136);
612665
const userAgentInfo = { browser, os };
613666

614667
expect(
@@ -619,7 +672,7 @@ describe(__filename, () => {
619672
compatibility: {
620673
...fakeAddon.current_version.compatibility,
621674
[CLIENT_APP_FIREFOX]: {
622-
max: '56.*',
675+
max: '135.*',
623676
min: '24.0',
624677
},
625678
},
@@ -635,7 +688,7 @@ describe(__filename, () => {
635688
});
636689

637690
it('returns incompatible when add-on does not support client app', () => {
638-
const { browser, os } = UAParser(userAgentsByPlatform.mac.firefox57);
691+
const { browser, os } = UAParser(userAgentsByPlatform.mac.firefox136);
639692
const userAgentInfo = { browser, os };
640693

641694
expect(
@@ -656,9 +709,7 @@ describe(__filename, () => {
656709
});
657710

658711
it('returns correct reason when add-on is incompatible with android', () => {
659-
const { browser, os } = UAParser(
660-
userAgentsByPlatform.android.firefox40Mobile,
661-
);
712+
const { browser, os } = UAParser(userAgentsByPlatform.android.firefox136);
662713
const userAgentInfo = { browser, os };
663714

664715
expect(
@@ -715,7 +766,7 @@ describe(__filename, () => {
715766
_correctedLocationForPlatform({
716767
clientApp: CLIENT_APP_FIREFOX,
717768
lang,
718-
userAgentInfo: UAParser(userAgentsByPlatform.android.firefox40Mobile),
769+
userAgentInfo: UAParser(userAgentsByPlatform.android.firefox136),
719770
}),
720771
).toEqual(getMobileHomepageLink(lang));
721772
});
@@ -728,7 +779,7 @@ describe(__filename, () => {
728779
isHomePage: false,
729780
lang,
730781
location: createFakeLocation({ pathname: '/some/path' }),
731-
userAgentInfo: UAParser(userAgentsByPlatform.android.firefox40Mobile),
782+
userAgentInfo: UAParser(userAgentsByPlatform.android.firefox136),
732783
}),
733784
).toEqual(null);
734785
});
@@ -739,7 +790,7 @@ describe(__filename, () => {
739790
clientApp: CLIENT_APP_ANDROID,
740791
isHomePage: true,
741792
location: createFakeLocation({ pathname: '/some/path' }),
742-
userAgentInfo: UAParser(userAgentsByPlatform.android.firefox40Mobile),
793+
userAgentInfo: UAParser(userAgentsByPlatform.android.firefox136),
743794
}),
744795
).toEqual(null);
745796
});
@@ -750,7 +801,7 @@ describe(__filename, () => {
750801
clientApp: CLIENT_APP_ANDROID,
751802
isHomePage: false,
752803
location: createFakeLocation({ pathname: '/search/' }),
753-
userAgentInfo: UAParser(userAgentsByPlatform.android.firefox40Mobile),
804+
userAgentInfo: UAParser(userAgentsByPlatform.android.firefox136),
754805
}),
755806
).toEqual(null);
756807
});
@@ -763,7 +814,7 @@ describe(__filename, () => {
763814
_correctedLocationForPlatform({
764815
clientApp: CLIENT_APP_ANDROID,
765816
location: createFakeLocation({ pathname, search }),
766-
userAgentInfo: UAParser(userAgentsByPlatform.mac.firefox69),
817+
userAgentInfo: UAParser(userAgentsByPlatform.mac.firefox136),
767818
}),
768819
).toEqual(`/en-US/${CLIENT_APP_FIREFOX}/addon/slug/${search}`);
769820
});
@@ -779,7 +830,7 @@ describe(__filename, () => {
779830
clientApp: CLIENT_APP_ANDROID,
780831
lang: 'en-US',
781832
location: createFakeLocation({ pathname, search }),
782-
userAgentInfo: UAParser(userAgentsByPlatform.mac.firefox69),
833+
userAgentInfo: UAParser(userAgentsByPlatform.mac.firefox136),
783834
}),
784835
).toEqual(`/en-US/${CLIENT_APP_FIREFOX}/addon/slug/${search}`);
785836
});
@@ -791,7 +842,7 @@ describe(__filename, () => {
791842
_correctedLocationForPlatform({
792843
clientApp: CLIENT_APP_ANDROID,
793844
location: createFakeLocation({ pathname }),
794-
userAgentInfo: UAParser(userAgentsByPlatform.mac.firefox69),
845+
userAgentInfo: UAParser(userAgentsByPlatform.mac.firefox136),
795846
}),
796847
).toEqual(
797848
`/en-US/${CLIENT_APP_FIREFOX}/addon/awesome-android-extension/`,
@@ -802,7 +853,7 @@ describe(__filename, () => {
802853
expect(
803854
_correctedLocationForPlatform({
804855
clientApp: CLIENT_APP_FIREFOX,
805-
userAgentInfo: UAParser(userAgentsByPlatform.mac.firefox69),
856+
userAgentInfo: UAParser(userAgentsByPlatform.mac.firefox136),
806857
}),
807858
).toEqual(null);
808859
});

‎tests/unit/helpers.js

+8
Original file line numberDiff line numberDiff line change
@@ -806,6 +806,8 @@ export const userAgentsByPlatform = {
806806
Firefox/69.0`,
807807
firefox70: oneLine`Mozilla/5.0 (Android 9; Mobile; rv:70.0) Gecko/70.0
808808
Firefox/70.0`,
809+
firefox136: oneLine`Mozilla/5.0 (Android 15; Mobile; rv:136.0) Gecko/136.0
810+
Firefox/136.0`,
809811
},
810812
bsd: {
811813
firefox40FreeBSD: oneLine`Mozilla/5.0 (X11; FreeBSD amd64; rv:40.0)
@@ -842,6 +844,10 @@ export const userAgentsByPlatform = {
842844
Gecko/20100101 Firefox/61.0`,
843845
firefox69: oneLine`Mozilla/5.0 (Macintosh; Intel Mac OS X 10.13; rv:69.0)
844846
Gecko/20100101 Firefox/69.0`,
847+
firefox128: oneLine`Mozilla/5.0 (Macintosh; Intel Mac OS X 14.7; rv:128.0)
848+
Gecko/20100101 Firefox/128.0`,
849+
firefox136: oneLine`Mozilla/5.0 (Macintosh; Intel Mac OS X 14.7; rv:136.0)
850+
Gecko/20100101 Firefox/136.0`,
845851
},
846852
unix: {
847853
firefox51: oneLine`Mozilla/51.0.2 (X11; Unix x86_64; rv:29.0)
@@ -850,6 +856,8 @@ export const userAgentsByPlatform = {
850856
windows: {
851857
firefox40: oneLine`Mozilla/5.0 (Windows NT 6.1; WOW64; rv:40.0)
852858
Gecko/20100101 Firefox/40.1`,
859+
firefox115: oneLine`Mozilla/5.0 (Windows NT 6.1; WOW64; rv:115.0)
860+
Gecko/20100101 Firefox/115.0`,
853861
},
854862
};
855863

0 commit comments

Comments
 (0)
Please sign in to comment.