diff --git a/modules/criteoBidAdapter.js b/modules/criteoBidAdapter.js index 0595fc890f0..284c3f57406 100755 --- a/modules/criteoBidAdapter.js +++ b/modules/criteoBidAdapter.js @@ -3,8 +3,10 @@ import { registerBidder } from 'src/adapters/bidderFactory'; import { parse } from 'src/url'; import * as utils from 'src/utils'; import find from 'core-js/library/fn/array/find'; +import JSEncrypt from 'jsencrypt/bin/jsencrypt'; +import sha256 from 'crypto-js/sha256'; -const ADAPTER_VERSION = 11; +const ADAPTER_VERSION = 14; const BIDDER_CODE = 'criteo'; const CDB_ENDPOINT = '//bidder.criteo.com/cdb'; const CRITEO_VENDOR_ID = 91; @@ -17,6 +19,13 @@ const PROFILE_ID_PUBLISHERTAG = 185; // Unminified source code can be found in: https://github.com/Prebid-org/prebid-js-external-js-criteo/blob/master/dist/prod.js const PUBLISHER_TAG_URL = '//static.criteo.net/js/ld/publishertag.prebid.js'; +export const FAST_BID_PUBKEY = `-----BEGIN PUBLIC KEY----- +MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDO1BjAITkFTtP0IMzmF7qsqhpu +y1dGaTPHnjMU9mRZsrnfR3C0sEN5pYEzEcFRPnkJjJuhH8Rnh5+CE+LcKg0Z8ZZ7 +OmOSj0/qnYTAYCu0cR5LiyWG79KlIgUyMbp92ulGg24gAyGrVn4+v/4c53WlOEUp +4YWvb82G0CD5NcDNpQIDAQAB +-----END PUBLIC KEY-----`; + /** @type {BidderSpec} */ export const spec = { code: BIDDER_CODE, @@ -251,6 +260,34 @@ function createNativeAd(id, payload, callback) { `; } +export function cryptoVerify(key, hash, code) { + var jse = new JSEncrypt(); + jse.setPublicKey(key); + return jse.verify(code, hash, sha256); +} + +function validateFastBid(fastBid) { + // The value stored must contain the file's encrypted hash as first line + const firstLineEnd = fastBid.indexOf('\n'); + const firstLine = fastBid.substr(0, firstLineEnd).trim(); + if (firstLine.substr(0, 9) !== '// Hash: ') { + utils.logWarn('No hash found in FastBid'); + return false; + } + + // Remove the hash part from the locally stored value + const fileEncryptedHash = firstLine.substr(9); + const publisherTag = fastBid.substr(firstLineEnd + 1); + + // Verify the hash using cryptography + try { + return cryptoVerify(FAST_BID_PUBKEY, fileEncryptedHash, publisherTag); + } catch (e) { + utils.logWarn('Failed to verify Criteo FastBid'); + return undefined; + } +} + /** * @return {boolean} */ @@ -258,13 +295,17 @@ function tryGetCriteoFastBid() { try { const fastBid = localStorage.getItem('criteo_fast_bid'); if (fastBid !== null) { - eval(fastBid); // eslint-disable-line no-eval - return true; + if (validateFastBid(fastBid) === false) { + utils.logWarn('Invalid Criteo FastBid found'); + localStorage.removeItem('criteo_fast_bid'); + } else { + utils.logInfo('Using Criteo FastBid'); + eval(fastBid); // eslint-disable-line no-eval + } } } catch (e) { // Unable to get fast bid } - return false; } registerBidder(spec); diff --git a/package-lock.json b/package-lock.json index 58d408a6be4..f28ecb283eb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2990,6 +2990,11 @@ "randomfill": "^1.0.3" } }, + "crypto-js": { + "version": "3.1.9-1", + "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-3.1.9-1.tgz", + "integrity": "sha1-/aGedh/Ad+Af+/3G6f38WeiAbNg=" + }, "css": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/css/-/css-2.2.3.tgz", @@ -8265,6 +8270,11 @@ "dev": true, "optional": true }, + "jsencrypt": { + "version": "3.0.0-rc.1", + "resolved": "https://registry.npmjs.org/jsencrypt/-/jsencrypt-3.0.0-rc.1.tgz", + "integrity": "sha512-gcvGaqerlUJy1Kq6tNgPYteVEoWNemu+9hBe2CdsCIz4rVcwjoTQ72iD1W76/PRMlnkzG0yVh7nwOOMOOUfKmg==" + }, "jsesc": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-1.3.0.tgz", diff --git a/package.json b/package.json old mode 100644 new mode 100755 index 1b28a54f72a..c27e0dd48b9 --- a/package.json +++ b/package.json @@ -103,7 +103,9 @@ "dependencies": { "babel-plugin-transform-object-assign": "^6.22.0", "core-js": "^2.4.1", + "crypto-js": "^3.1.9-1", "gulp-sourcemaps": "^2.6.0", + "jsencrypt": "^3.0.0-rc.1", "just-clone": "^1.0.2" } } diff --git a/test/spec/modules/criteoBidAdapter_spec.js b/test/spec/modules/criteoBidAdapter_spec.js index e232bf0e3d9..d124ebf3709 100755 --- a/test/spec/modules/criteoBidAdapter_spec.js +++ b/test/spec/modules/criteoBidAdapter_spec.js @@ -1,5 +1,5 @@ import { expect } from 'chai'; -import { spec } from 'modules/criteoBidAdapter'; +import { cryptoVerify, spec, FAST_BID_PUBKEY } from 'modules/criteoBidAdapter'; import * as utils from 'src/utils'; describe('The Criteo bidding adapter', function () { @@ -294,4 +294,21 @@ describe('The Criteo bidding adapter', function () { expect(bids[0].height).to.equal(90); }); }); + + describe('cryptoVerify', function () { + const TEST_HASH = 'vBeD8Q7GU6lypFbzB07W8hLGj7NL+p7dI9ro2tCxkrmyv0F6stNuoNd75Us33iNKfEoW+cFWypelr6OJPXxki2MXWatRhJuUJZMcK4VBFnxi3Ro+3a0xEfxE4jJm4eGe98iC898M+/YFHfp+fEPEnS6pEyw124ONIFZFrcejpHU='; + + it('should verify right signature', function () { + expect(cryptoVerify(FAST_BID_PUBKEY, TEST_HASH, 'test')).to.equal(true); + }); + + it('should verify wrong signature', function () { + expect(cryptoVerify(FAST_BID_PUBKEY, TEST_HASH, 'test wrong')).to.equal(false); + }); + + it('should return undefined with incompatible browsers', function () { + // Here use a null hash to make the call to crypto library fail and simulate a browser failure + expect(cryptoVerify(FAST_BID_PUBKEY, null, 'test')).to.equal.undefined; + }); + }); });