I have Just created a chrome extension for internal Use of company, I don't want to publish that and want to pass to my fellow colleagues when I have packed that extension in my local chrome it gives me ERROR saying that CRX_REQUIRED_PROOF_MISSING
.
I have Just created a chrome extension for internal Use of company, I don't want to publish that and want to pass to my fellow colleagues when I have packed that extension in my local chrome it gives me ERROR saying that CRX_REQUIRED_PROOF_MISSING
.
- 1 developer.chrome.com/extensions/external_extensions not useful chrome docs are really bad. – ThinkTank Commented Aug 13, 2019 at 11:15
- hey, did you managed to workaround this issue? – webdevbyjoss Commented Oct 28, 2019 at 21:31
- 1 no workout is available except pay google $5 and create your developer account i had tried that time but got no luck because of timeline $5 is compared to less,and now have a google dev account ! – ThinkTank Commented Oct 29, 2019 at 6:07
- 1 install-chrome-extension-form-outside-the-chrome-web-store – Pqqwetiqe Commented Feb 10, 2021 at 21:08
3 Answers
Reset to default 4You cannot distribute an extension witch isn't in the Chrome Extension Store. According to the official chrome docs, every extension distributed either from the chrome extension store or outside of it must be uploaded to the chrome extension store. If you want to distribute your extension outside of the store, after you have uploaded it, I think you should create a script that modifies the register and it will install it for you.
You need to modify your local Policies to allow installs from a custom URL base you need to specify. This info is saved in a JSON on Linux or the Registry on Windows. See this link here Set Chrome app and extension policies (Windows) and then click Extension Install Sources to learn how to whitelist your Extensions' URLs. Then use Extension Install Allowlist to enable specific Extension IDs.
Also to get stable extension IDs, use the Chrome packer which means execute chrome with command line chrome --pack-extension="path\to\extension\folder" --pack-extension-key="path\to\file.pem"
. As long as the .pem is reused, this will produce a proper .crx with a stable ID that you can whitelist and will stick as you update.
To read the ID from the .CRX this is my C# code:
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();
}
and also you can use this minimalistic Network Order Bytereader.
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;
}
}
You'll also need the Protobuf header definition:
// <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
You have a lot more here than I started with when I did this. Using this code and a Registry writer to add your details to registry you can have a Chrome Extension deployment/installation internal tool.
Since we have 2023 now, we have different solution for CRX_REQUIRED_PROOF_MISSING as well.
All you have to do is:
- Turn off Google Chrome
- Open Registry (open Run window [winkey+R] and type: regedit, then press ENTER)
- Go to
HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Google\Chrome\ExtensionInstallSources
- if some part not exists create it. - In
ExtensionInstallSources
key/folder create new string value with name: 1 and value*: <all_urls> - Run Google Chrome, type in address bar:
chrome://policy/
ang hit ENTER - if you did everything correctly you'll see your new policy in Chrome Policies section - Now you can install your extension just by drag and drop it on the brower.
Be aware that warning The extensions didn't come from the Chrome Web Store or were installed without your permission
will be added to your extension in extensions list (chrome://extensions/
).
That's all. You don't need extension id, nor private key file nor anything else. Just extension *.crx file.
`* - you don't have to allow all urls as you trusted install source. Whole list of allowed patterns you can find on official developer chrome documentation.
Source: Chrome Enterprise - ExtensionInstallSource