为侧载Chrome插件生成持久ID

背景

出于菜鸡的摸鱼需求,希望用插件的形式为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固定不再变更,收工!

另,上文提到的浏览器拓展开源在这里(尚未完工)

Edit with markdown