背景
出于菜鸡的摸鱼需求,希望用插件的形式为Github增加用户备注标签功能,大概的设计如下:
- 浏览器插件注入ContentScript修改页面内容,增加标签显示和相关操作按钮
- 利用浏览器提供的Sync Storage功能在多设备同步标签数据
经过一天的摸鱼,插件功能已经基本完成:
But…跨设备同步功能却出现了问题:
对于未上架Chrome Store的拓展而言,拓展的ID安装时生成的,且在不同设备上安装时不一定一致。而Chrome提供给拓展 Sync Storage功能是与ID相绑定的,因此无法完成不同设备上的同步功能。
Extension Key的生成
在谷歌官方的文档里,我找到了Key
这个Manifest键值,但是官方要求必须在Developer Dashboard才能生成这个公钥属性。随后我发现,谷歌浏览器的开发者需要5刀乐注册费…还不支持银联卡。随手填了个Key值,加载报错Invalid.
穷鬼如我决定去翻下Chromium的相关代码,看看PublicKey到底是怎么验证的。好在Chromium的代码仓库很条理,还带有搜索功能,很快就找到了相关代码段:
//extensions/common/extension.cc
// Computes the |extension_id| from the given parameters. On success, returns
// true. On failure, populates |error| and returns false.
bool ComputeExtensionID(const base::Value::Dict& manifest,
const base::FilePath& path,
int creation_flags,
std::u16string* error,
ExtensionId* extension_id) {
if (const base::Value* public_key = manifest.Find(keys::kPublicKey)) {
std::string public_key_bytes;
if (!public_key->is_string() ||
!Extension::ParsePEMKeyBytes(public_key->GetString(),
&public_key_bytes)) {
*error = errors::kInvalidKey;
return false;
}
*extension_id = crx_file::id_util::GenerateId(public_key_bytes);
return true;
}
if (creation_flags & Extension::REQUIRE_KEY) {
*error = errors::kInvalidKey;
return false;
}
// If there is a path, we generate the ID from it. This is useful for
// development mode, because it keeps the ID stable across restarts and
// reloading the extension.
*extension_id = crx_file::id_util::GenerateIdForPath(path);
if (extension_id->empty()) {
NOTREACHED() << "Could not create ID from path.";
return false;
}
return true;
}
bool Extension::ParsePEMKeyBytes(const std::string& input,
std::string* output) {
DCHECK(output);
if (!output)
return false;
if (input.length() == 0)
return false;
std::string working = input;
if (base::StartsWith(working, kKeyBeginHeaderMarker,
base::CompareCase::SENSITIVE)) {
working = base::CollapseWhitespaceASCII(working, true);
size_t header_pos = working.find(kKeyInfoEndMarker,
sizeof(kKeyBeginHeaderMarker) - 1);
if (header_pos == std::string::npos)
return false;
size_t start_pos = header_pos + sizeof(kKeyInfoEndMarker) - 1;
size_t end_pos = working.rfind(kKeyBeginFooterMarker);
if (end_pos == std::string::npos)
return false;
if (start_pos >= end_pos)
return false;
working = working.substr(start_pos, end_pos - start_pos);
if (working.length() == 0)
return false;
}
return base::Base64Decode(working, output);
}
从相关代码段可以看出,ID的生成有两种来源,一种来自于拓展存放的路径(Path),其实就是把Path字符串做哈希之后生成ID,另一种就是来着于Key字段,通过ParsePEMKeyBytes
进行校验,而校验的逻辑仅仅是简单的Base64Encode(难绷…)
随便在Key字段中填写了一段Base64编码(如SUFtQUxvbmVseUhlbGljb3B0ZXI=
),成功导入,且ID固定不再变更,收工!
另,上文提到的浏览器拓展开源在这里(尚未完工)