private static string ReadExtensionIdFromCrx3(string path)
{
using var stream = File.Open(path, FileMode.Open, FileAccess.Read, FileShare.Read);
return ReadExtensionIdFromCrx3(stream);
}
private static string ReadExtensionIdFromCrx3(Stream stream)
{
using var reader = new BinaryReader(stream);
using var netreader = new BinaryReaderNetOrder(stream);
var magic = netreader.ReadInt32();
if (magic != 0x43723234)
{
throw new InvalidDataException();
}
var version = netreader.ReadInt32();
if (version != 0x03000000)
{
throw new InvalidDataException();
}
var headerSize = reader.ReadInt32();
var headerBytes = reader.ReadBytes(headerSize);
var header = CrxFileHeader.Parser.ParseFrom(headerBytes);
var first = header.Sha256WithRsa[0];
var halfHashed = new byte[16];
using (var hash = SHA256.Create())
{
var hashed = hash.ComputeHash(first.PublicKey.ToByteArray());
Array.Copy(hashed, 0, halfHashed, 0, halfHashed.Length);
}
var sb = new StringBuilder();
for (int it = 0; it < halfHashed.Length; ++it)
{
sb.Append((char)('a' + (halfHashed[it] / 16)));
sb.Append((char)('a' + (halfHashed[it] % 16)));
}
return sb.ToString();
}
你也可以使用这个极简的网络顺序字节阅读器。
public class BinaryReaderNetOrder : BinaryReader
{
public BinaryReaderNetOrder(Stream stream) : base(stream) { }
public override short ReadInt16()
{
return BitConverter.ToInt16(ReadNetOrder(2), 0);
}
public override int ReadInt32()
{
return BitConverter.ToInt32(ReadNetOrder(4), 0);
}
public override long ReadInt64()
{
return BitConverter.ToInt64(ReadNetOrder(8), 0);
}
public override ushort ReadUInt16()
{
return BitConverter.ToUInt16(ReadNetOrder(2), 0);
}
public override uint ReadUInt32()
{
return BitConverter.ToUInt32(ReadNetOrder(4), 0);
}
public override ulong ReadUInt64()
{
return BitConverter.ToUInt64(ReadNetOrder(8), 0);
}
public override float ReadSingle()
{
return BitConverter.ToSingle(ReadNetOrder(4), 0);
}
public override double ReadDouble()
{
return BitConverter.ToDouble(ReadNetOrder(8), 0);
}
public override byte[] ReadBytes(int count)
{
var data = base.ReadBytes(count);
if (data.Length < count)
{
throw new EndOfStreamException();
}
if (data.Length > count)
{
throw new IOException();
}
return data;
}
private byte[] ReadNetOrder(int count)
{
if (count < 2 || count > 8)
{
throw new ArgumentOutOfRangeException(nameof(count));
}
var data = ReadBytes(count);
if (BitConverter.IsLittleEndian)
{
Array.Reverse(data);
}
return data;
}
}
您还需要Protobuf头定义:
// <auto-generated>
// Generated by the protocol buffer compiler. DO NOT EDIT!
// source: test.proto
// </auto-generated>
#pragma warning disable 1591, 0612, 3021
#region Designer generated code
using pb = Google.Protobuf;
using pbc = Google.Protobuf.Collections;
using pbr = Google.Protobuf.Reflection;
using scg = System.Collections.Generic;
/// <summary>Holder for reflection information generated from test.proto</summary>
public static partial class TestReflection
{
#region Descriptor
/// <summary>File descriptor for test.proto</summary>
public static pbr::FileDescriptor Descriptor
{
get { return descriptor; }
}
private static pbr::FileDescriptor descriptor;
static TestReflection()
{
byte[] descriptorData = System.Convert.FromBase64String(
string.Concat(
"Cgp0ZXN0LnByb3RvIooBCg1DcnhGaWxlSGVhZGVyEiwKD3NoYTI1Nl93aXRo",
"X3JzYRgCIAMoCzITLkFzeW1tZXRyaWNLZXlQcm9vZhIuChFzaGEyNTZfd2l0",
"aF9lY2RzYRgDIAMoCzITLkFzeW1tZXRyaWNLZXlQcm9vZhIbChJzaWduZWRf",
"aGVhZGVyX2RhdGEYkE4gASgMIjsKEkFzeW1tZXRyaWNLZXlQcm9vZhISCgpw",
"dWJsaWNfa2V5GAEgASgMEhEKCXNpZ25hdHVyZRgCIAEoDCIcCgpTaWduZWRE",
"YXRhEg4KBmNyeF9pZBgBIAEoDGIGcHJvdG8z"));
descriptor = pbr::FileDescriptor.FromGeneratedCode(descriptorData,
new pbr::FileDescriptor[] { },
new pbr::GeneratedClrTypeInfo(null, null, new pbr::GeneratedClrTypeInfo[] {
new pbr::GeneratedClrTypeInfo(typeof(CrxFileHeader), CrxFileHeader.Parser, new[]{ "Sha256WithRsa", "Sha256WithEcdsa", "SignedHeaderData" }, null, null, null, null),
new pbr::GeneratedClrTypeInfo(typeof(AsymmetricKeyProof), AsymmetricKeyProof.Parser, new[]{ "PublicKey", "Signature" }, null, null, null, null),
new pbr::GeneratedClrTypeInfo(typeof(SignedData), SignedData.Parser, new[]{ "CrxId" }, null, null, null, null)
}));
}
#endregion
}
#region Messages
public sealed partial class CrxFileHeader : pb::IMessage<CrxFileHeader>
{
private static readonly pb::MessageParser<CrxFileHeader> _parser = new pb::MessageParser<CrxFileHeader>(() => new CrxFileHeader());
private pb::UnknownFieldSet _unknownFields;
[DebuggerNonUserCode]
public static pb::MessageParser<CrxFileHeader> Parser { get { return _parser; } }
[DebuggerNonUserCode]
public static pbr::MessageDescriptor Descriptor
{
get { return TestReflection.Descriptor.MessageTypes[0]; }
}
[DebuggerNonUserCode]
pbr::MessageDescriptor pb::IMessage.Descriptor
{
get { return Descriptor; }
}
[DebuggerNonUserCode]
public CrxFileHeader()
{
OnConstruction();
}
partial void OnConstruction();
[DebuggerNonUserCode]
public CrxFileHeader(CrxFileHeader other) : this()
{
sha256WithRsa_ = other.sha256WithRsa_.Clone();
sha256WithEcdsa_ = other.sha256WithEcdsa_.Clone();
signedHeaderData_ = other.signedHeaderData_;
_unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields);
}
[DebuggerNonUserCode]
public CrxFileHeader Clone()
{
return new CrxFileHeader(this);
}
/// <summary>Field number for the "sha256_with_rsa" field.</summary>
public const int Sha256WithRsaFieldNumber = 2;
private static readonly pb::FieldCodec<AsymmetricKeyProof> _repeated_sha256WithRsa_codec
= pb::FieldCodec.ForMessage(18, AsymmetricKeyProof.Parser);
private readonly pbc::RepeatedField<AsymmetricKeyProof> sha256WithRsa_ = new pbc::RepeatedField<AsymmetricKeyProof>();
/// <summary>
/// PSS signature with RSA public key. The public key is formatted as a
/// X.509 SubjectPublicKeyInfo block, as in CRXâ‚‚. In the common case of a
/// developer key proof, the first 128 bits of the SHA-256 hash of the
/// public key must equal the crx_id.
/// </summary>
[DebuggerNonUserCode]
public pbc::RepeatedField<AsymmetricKeyProof> Sha256WithRsa
{
get { return sha256WithRsa_; }
}
/// <summary>Field number for the "sha256_with_ecdsa" field.</summary>
public const int Sha256WithEcdsaFieldNumber = 3;
private static readonly pb::FieldCodec<AsymmetricKeyProof> _repeated_sha256WithEcdsa_codec
= pb::FieldCodec.ForMessage(26, AsymmetricKeyProof.Parser);
private readonly pbc::RepeatedField<AsymmetricKeyProof> sha256WithEcdsa_ = new pbc::RepeatedField<AsymmetricKeyProof>();
/// <summary>
/// ECDSA signature, using the NIST P-256 curve. Public key appears in
/// named-curve format.
/// The pinned algorithm will be this, at least on 2017-01-01.
/// </summary>
[DebuggerNonUserCode]
public pbc::RepeatedField<AsymmetricKeyProof> Sha256WithEcdsa
{
get { return sha256WithEcdsa_; }
}
/// <summary>Field number for the "signed_header_data" field.</summary>
public const int SignedHeaderDataFieldNumber = 10000;
private pb::ByteString signedHeaderData_ = pb::ByteString.Empty;
/// <summary>
/// The binary form of a SignedData message. We do not use a nested
/// SignedData message, as handlers of this message must verify the proofs
/// on exactly these bytes, so it is convenient to parse in two steps.
///
/// All proofs in this CrxFile message are on the value
/// "CRX3 SignedData\x00" + signed_header_size + signed_header_data +
/// archive, where "\x00" indicates an octet with value 0, "CRX3 SignedData"
/// is encoded using UTF-8, signed_header_size is the size in octets of the
/// contents of this field and is encoded using 4 octets in little-endian
/// order, signed_header_data is exactly the content of this field, and
/// archive is the remaining contents of the file following the header.
/// </summary>
[DebuggerNonUserCode]
public pb::ByteString SignedHeaderData
{
get { return signedHeaderData_; }
set
{
signedHeaderData_ = pb::ProtoPreconditions.CheckNotNull(value, "value");
}
}
[DebuggerNonUserCode]
public override bool Equals(object other)
{
return Equals(other as CrxFileHeader);
}
[DebuggerNonUserCode]
public bool Equals(CrxFileHeader other)
{
if (ReferenceEquals(other, null))
{
return false;
}
if (ReferenceEquals(other, this))
{
return true;
}
if (!sha256WithRsa_.Equals(other.sha256WithRsa_)) return false;
if (!sha256WithEcdsa_.Equals(other.sha256WithEcdsa_)) return false;
if (SignedHeaderData != other.SignedHeaderData) return false;
return Equals(_unknownFields, other._unknownFields);
}
[DebuggerNonUserCode]
public override int GetHashCode()
{
int hash = 1;
hash ^= sha256WithRsa_.GetHashCode();
hash ^= sha256WithEcdsa_.GetHashCode();
if (SignedHeaderData.Length != 0) hash ^= SignedHeaderData.GetHashCode();
if (_unknownFields != null)
{
hash ^= _unknownFields.GetHashCode();
}
return hash;
}
[DebuggerNonUserCode]
public override string ToString()
{
return pb::JsonFormatter.ToDiagnosticString(this);
}
[DebuggerNonUserCode]
public void WriteTo(pb::CodedOutputStream output)
{
sha256WithRsa_.WriteTo(output, _repeated_sha256WithRsa_codec);
sha256WithEcdsa_.WriteTo(output, _repeated_sha256WithEcdsa_codec);
if (SignedHeaderData.Length != 0)
{
output.WriteRawTag(130, 241, 4);
output.WriteBytes(SignedHeaderData);
}
if (_unknownFields != null)
{
_unknownFields.WriteTo(output);
}
}
[DebuggerNonUserCode]
public int CalculateSize()
{
int size = 0;
size += sha256WithRsa_.CalculateSize(_repeated_sha256WithRsa_codec);
size += sha256WithEcdsa_.CalculateSize(_repeated_sha256WithEcdsa_codec);
if (SignedHeaderData.Length != 0)
{
size += 3 + pb::CodedOutputStream.ComputeBytesSize(SignedHeaderData);
}
if (_unknownFields != null)
{
size += _unknownFields.CalculateSize();
}
return size;
}
[DebuggerNonUserCode]
public void MergeFrom(CrxFileHeader other)
{
if (other == null)
{
return;
}
sha256WithRsa_.Add(other.sha256WithRsa_);
sha256WithEcdsa_.Add(other.sha256WithEcdsa_);
if (other.SignedHeaderData.Length != 0)
{
SignedHeaderData = other.SignedHeaderData;
}
_unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields);
}
[DebuggerNonUserCode]
public void MergeFrom(pb::CodedInputStream input)
{
uint tag;
while ((tag = input.ReadTag()) != 0)
{
switch (tag)
{
default:
_unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input);
break;
case 18:
{
sha256WithRsa_.AddEntriesFrom(input, _repeated_sha256WithRsa_codec);
break;
}
case 26:
{
sha256WithEcdsa_.AddEntriesFrom(input, _repeated_sha256WithEcdsa_codec);
break;
}
case 80002:
{
SignedHeaderData = input.ReadBytes();
break;
}
}
}
}
}
public sealed partial class AsymmetricKeyProof : pb::IMessage<AsymmetricKeyProof>
{
private static readonly pb::MessageParser<AsymmetricKeyProof> _parser = new pb::MessageParser<AsymmetricKeyProof>(() => new AsymmetricKeyProof());
private pb::UnknownFieldSet _unknownFields;
[DebuggerNonUserCode]
public static pb::MessageParser<AsymmetricKeyProof> Parser { get { return _parser; } }
[DebuggerNonUserCode]
public static pbr::MessageDescriptor Descriptor
{
get { return TestReflection.Descriptor.MessageTypes[1]; }
}
[DebuggerNonUserCode]
pbr::MessageDescriptor pb::IMessage.Descriptor
{
get { return Descriptor; }
}
[DebuggerNonUserCode]
public AsymmetricKeyProof()
{
OnConstruction();
}
partial void OnConstruction();
[DebuggerNonUserCode]
public AsymmetricKeyProof(AsymmetricKeyProof other) : this()
{
publicKey_ = other.publicKey_;
signature_ = other.signature_;
_unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields);
}
[DebuggerNonUserCode]
public AsymmetricKeyProof Clone()
{
return new AsymmetricKeyProof(this);
}
/// <summary>Field number for the "public_key" field.</summary>
public const int PublicKeyFieldNumber = 1;
private pb::ByteString publicKey_ = pb::ByteString.Empty;
[DebuggerNonUserCode]
public pb::ByteString PublicKey
{
get { return publicKey_; }
set
{
publicKey_ = pb::ProtoPreconditions.CheckNotNull(value, "value");
}
}
/// <summary>Field number for the "signature" field.</summary>
public const int SignatureFieldNumber = 2;
private pb::ByteString signature_ = pb::ByteString.Empty;
[DebuggerNonUserCode]
public pb::ByteString Signature
{
get { return signature_; }
set
{
signature_ = pb::ProtoPreconditions.CheckNotNull(value, "value");
}
}
[DebuggerNonUserCode]
public override bool Equals(object other)
{
return Equals(other as AsymmetricKeyProof);
}
[DebuggerNonUserCode]
public bool Equals(AsymmetricKeyProof other)
{
if (ReferenceEquals(other, null))
{
return false;
}
if (ReferenceEquals(other, this))
{
return true;
}
if (PublicKey != other.PublicKey) return false;
if (Signature != other.Signature) return false;
return Equals(_unknownFields, other._unknownFields);
}
[DebuggerNonUserCode]
public override int GetHashCode()
{
int hash = 1;
if (PublicKey.Length != 0) hash ^= PublicKey.GetHashCode();
if (Signature.Length != 0) hash ^= Signature.GetHashCode();
if (_unknownFields != null)
{
hash ^= _unknownFields.GetHashCode();
}
return hash;
}
[DebuggerNonUserCode]
public override string ToString()
{
return pb::JsonFormatter.ToDiagnosticString(this);
}
[DebuggerNonUserCode]
public void WriteTo(pb::CodedOutputStream output)
{
if (PublicKey.Length != 0)
{
output.WriteRawTag(10);
output.WriteBytes(PublicKey);
}
if (Signature.Length != 0)
{
output.WriteRawTag(18);
output.WriteBytes(Signature);
}
if (_unknownFields != null)
{
_unknownFields.WriteTo(output);
}
}
[DebuggerNonUserCode]
public int CalculateSize()
{
int size = 0;
if (PublicKey.Length != 0)
{
size += 1 + pb::CodedOutputStream.ComputeBytesSize(PublicKey);
}
if (Signature.Length != 0)
{
size += 1 + pb::CodedOutputStream.ComputeBytesSize(Signature);
}
if (_unknownFields != null)
{
size += _unknownFields.CalculateSize();
}
return size;
}
[DebuggerNonUserCode]
public void MergeFrom(AsymmetricKeyProof other)
{
if (other == null)
{
return;
}
if (other.PublicKey.Length != 0)
{
PublicKey = other.PublicKey;
}
if (other.Signature.Length != 0)
{
Signature = other.Signature;
}
_unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields);
}
[DebuggerNonUserCode]
public void MergeFrom(pb::CodedInputStream input)
{
uint tag;
while ((tag = input.ReadTag()) != 0)
{
switch (tag)
{
default:
_unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input);
break;
case 10:
{
PublicKey = input.ReadBytes();
break;
}
case 18:
{
Signature = input.ReadBytes();
break;
}
}
}
}
}
public sealed partial class SignedData : pb::IMessage<SignedData>
{
private static readonly pb::MessageParser<SignedData> _parser = new pb::MessageParser<SignedData>(() => new SignedData());
private pb::UnknownFieldSet _unknownFields;
[DebuggerNonUserCode]
public static pb::MessageParser<SignedData> Parser { get { return _parser; } }
[DebuggerNonUserCode]
public static pbr::MessageDescriptor Descriptor
{
get { return TestReflection.Descriptor.MessageTypes[2]; }
}
[DebuggerNonUserCode]
pbr::MessageDescriptor pb::IMessage.Descriptor
{
get { return Descriptor; }
}
[DebuggerNonUserCode]
public SignedData()
{
OnConstruction();
}
partial void OnConstruction();
[DebuggerNonUserCode]
public SignedData(SignedData other) : this()
{
crxId_ = other.crxId_;
_unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields);
}
[DebuggerNonUserCode]
public SignedData Clone()
{
return new SignedData(this);
}
/// <summary>Field number for the "crx_id" field.</summary>
public const int CrxIdFieldNumber = 1;
private pb::ByteString crxId_ = pb::ByteString.Empty;
/// <summary>
/// This is simple binary, not UTF-8 encoded mpdecimal; i.e. it is exactly
/// 16 bytes long.
/// </summary>
[DebuggerNonUserCode]
public pb::ByteString CrxId
{
get { return crxId_; }
set
{
crxId_ = pb::ProtoPreconditions.CheckNotNull(value, "value");
}
}
[DebuggerNonUserCode]
public override bool Equals(object other)
{
return Equals(other as SignedData);
}
[DebuggerNonUserCode]
public bool Equals(SignedData other)
{
if (ReferenceEquals(other, null))
{
return false;
}
if (ReferenceEquals(other, this))
{
return true;
}
if (CrxId != other.CrxId) return false;
return Equals(_unknownFields, other._unknownFields);
}
[DebuggerNonUserCode]
public override int GetHashCode()
{
int hash = 1;
if (CrxId.Length != 0) hash ^= CrxId.GetHashCode();
if (_unknownFields != null)
{
hash ^= _unknownFields.GetHashCode();
}
return hash;
}
[DebuggerNonUserCode]
public override string ToString()
{
return pb::JsonFormatter.ToDiagnosticString(this);
}
[DebuggerNonUserCode]
public void WriteTo(pb::CodedOutputStream output)
{
if (CrxId.Length != 0)
{
output.WriteRawTag(10);
output.WriteBytes(CrxId);
}
if (_unknownFields != null)
{
_unknownFields.WriteTo(output);
}
}
[DebuggerNonUserCode]
public int CalculateSize()
{
int size = 0;
if (CrxId.Length != 0)
{
size += 1 + pb::CodedOutputStream.ComputeBytesSize(CrxId);
}
if (_unknownFields != null)
{
size += _unknownFields.CalculateSize();
}
return size;
}
[DebuggerNonUserCode]
public void MergeFrom(SignedData other)
{
if (other == null)
{
return;
}
if (other.CrxId.Length != 0)
{
CrxId = other.CrxId;
}
_unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields);
}
[DebuggerNonUserCode]
public void MergeFrom(pb::CodedInputStream input)
{
uint tag;
while ((tag = input.ReadTag()) != 0)
{
switch (tag)
{
default:
_unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input);
break;
case 10:
{
CrxId = input.ReadBytes();
break;
}
}
}
}
}
#endregion
#endregion Designer generated code
由于我们现在有2023,因此我们对CRX_REQUIRED_PROOF_MISSING也有不同的解决方案。 你要做的就是: 1.关闭Google Chrome 1.打开注册表(打开 * 运行 * 窗口[winkey+R]并键入:regedit,然后按Enter键) 1.转到HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Google\Chrome\ExtensionInstallSources-如果某些部分不存在,则创建它。 1.在ExtensionInstallSources键/文件夹中创建新的字符串值,名称为:1 和值 *:我的<all_urls>天 1.运行Google Chrome,在地址栏中键入:chrome://policy/和点击回车-如果你做的一切都是正确的,你会看到你的新政策在 *Chrome的政策 * 部分 1.现在你可以安装你的扩展只拖放到浏览器上。 请注意,警告The extensions didn't come from the Chrome Web Store or were installed without your permission将添加到扩展名列表中的扩展名(chrome://extensions/)。 这就是全部。你不需要扩展名ID,也不需要私钥文件或其他任何东西。只需要扩展名 .crx文件。 ` -你不必允许所有的url作为你信任的安装源。你可以在official developer chrome documentation上找到允许的模式的完整列表。 来源:Chrome Enterprise - ExtensionInstallSource
3条答案
按热度按时间izj3ouym1#
你不能发布不在Chrome扩展商店中的扩展。根据官方的Chrome文档,从Chrome扩展商店或外部发布的每个扩展都必须上传到Chrome扩展商店。如果你想在商店之外发布扩展,在你上传之后,我认为你应该创建一个修改注册表的脚本,它会为你安装它。
fdbelqdn2#
您需要修改本地策略以允许从您需要指定的自定义URL库进行安装。此信息在Linux上保存在JSON中,在Windows上保存在注册表中。请在此处查看此链接Set Chrome app and extension policies (Windows),然后单击Extension Install Sources以了解如何将扩展的URL列入白名单。然后使用Extension Install Allowlist启用特定的扩展ID。
同样要获得稳定的扩展ID,请使用Chrome打包器,这意味着使用命令行
chrome --pack-extension="path\to\extension\folder" --pack-extension-key="path\to\file.pem"
执行chrome。只要.pem被重用,这将产生一个具有稳定ID的适当的.crx,您可以将其列入白名单,并在更新时保持不变。要从.CRX读取ID,这是我的C#代码:
你也可以使用这个极简的网络顺序字节阅读器。
您还需要Protobuf头定义:
你在这里有很多比我开始时,我这样做。使用此代码和注册表作家添加您的详细信息注册表,你可以有一个Chrome扩展部署/安装内部工具。
p1iqtdky3#
由于我们现在有2023,因此我们对CRX_REQUIRED_PROOF_MISSING也有不同的解决方案。
你要做的就是:
1.关闭Google Chrome
1.打开注册表(打开 * 运行 * 窗口[winkey+R]并键入:regedit,然后按Enter键)
1.转到
HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Google\Chrome\ExtensionInstallSources
-如果某些部分不存在,则创建它。1.在
ExtensionInstallSources
键/文件夹中创建新的字符串值,名称为:1 和值 *:我的<all_urls>天1.运行Google Chrome,在地址栏中键入:
chrome://policy/
和点击回车-如果你做的一切都是正确的,你会看到你的新政策在 *Chrome的政策 * 部分1.现在你可以安装你的扩展只拖放到浏览器上。
请注意,警告
The extensions didn't come from the Chrome Web Store or were installed without your permission
将添加到扩展名列表中的扩展名(chrome://extensions/
)。这就是全部。你不需要扩展名ID,也不需要私钥文件或其他任何东西。只需要扩展名 .crx文件。
` -你不必允许所有的url作为你信任的安装源。你可以在official developer chrome documentation上找到允许的模式的完整列表。
来源:Chrome Enterprise - ExtensionInstallSource