Skip to content

Commit e617f98

Browse files
authored
Update NewExtension() to build CRX3 formatted Chrome extensions (#232)
* Update NewExtension (and friends) to build Chrome extensions using the now required CRX3 headers
1 parent b0b28ba commit e617f98

File tree

4 files changed

+180
-26
lines changed

4 files changed

+180
-26
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,4 @@ vendor/*
88
!vendor/*.go
99
coverage.txt
1010
coverage.out
11+
.idea/*

chrome/capabilities.go

Lines changed: 70 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,15 @@ import (
77
"crypto"
88
"crypto/rand"
99
"crypto/rsa"
10-
"crypto/sha1"
10+
"crypto/sha256"
1111
"crypto/x509"
1212
"encoding/base64"
1313
"encoding/binary"
1414
"io"
1515
"os"
1616

17+
"github.com/golang/protobuf/proto"
18+
"github.com/mediabuyerbot/go-crx3/pb"
1719
"github.com/tebeka/selenium/internal/zip"
1820
)
1921

@@ -179,23 +181,12 @@ func NewExtension(basePath string) ([]byte, *rsa.PrivateKey, error) {
179181
// NewExtensionWithKey creates the payload of a Chrome extension file which is
180182
// signed by the provided private key.
181183
func NewExtensionWithKey(basePath string, key *rsa.PrivateKey) ([]byte, error) {
182-
zip, err := zip.New(basePath)
184+
archiveBuf, err := zip.New(basePath)
183185
if err != nil {
184186
return nil, err
185187
}
186188

187-
h := sha1.New()
188-
if _, err := io.Copy(h, bytes.NewReader(zip.Bytes())); err != nil {
189-
return nil, err
190-
}
191-
hashed := h.Sum(nil)
192-
193-
signature, err := rsa.SignPKCS1v15(rand.Reader, key, crypto.SHA1, hashed[:])
194-
if err != nil {
195-
return nil, err
196-
}
197-
198-
pubKey, err := x509.MarshalPKIXPublicKey(key.Public())
189+
header, err := crx3Header(archiveBuf.Bytes(), key)
199190
if err != nil {
200191
return nil, err
201192
}
@@ -207,33 +198,86 @@ func NewExtensionWithKey(basePath string, key *rsa.PrivateKey) ([]byte, error) {
207198
}
208199

209200
// Version.
210-
if err := binary.Write(buf, binary.LittleEndian, uint32(2)); err != nil {
201+
if err := binary.Write(buf, binary.LittleEndian, uint32(3)); err != nil {
211202
return nil, err
212203
}
213204

214-
// Public key length.
215-
if err := binary.Write(buf, binary.LittleEndian, uint32(len(pubKey))); err != nil {
205+
// header length.
206+
if err := binary.Write(buf, binary.LittleEndian, uint32(len(header))); err != nil {
216207
return nil, err
217208
}
218-
// Signature length.
219-
if err := binary.Write(buf, binary.LittleEndian, uint32(len(signature))); err != nil {
209+
// header payload.
210+
if err := binary.Write(buf, binary.LittleEndian, header); err != nil {
220211
return nil, err
221212
}
222213

223-
// Public key payload.
224-
if err := binary.Write(buf, binary.LittleEndian, pubKey); err != nil {
214+
// Zipped extension directory payload.
215+
if err := binary.Write(buf, binary.LittleEndian, archiveBuf.Bytes()); err != nil {
225216
return nil, err
226217
}
218+
return buf.Bytes(), nil
219+
}
227220

228-
// Signature payload.
229-
if err := binary.Write(buf, binary.LittleEndian, signature); err != nil {
221+
func crx3Header(archiveData []byte, key *rsa.PrivateKey) ([]byte, error) {
222+
// Public Key
223+
pubKey, err := x509.MarshalPKIXPublicKey(key.Public())
224+
if err != nil {
230225
return nil, err
231226
}
232227

233-
// Zipped extension directory payload.
234-
if err := binary.Write(buf, binary.LittleEndian, zip.Bytes()); err != nil {
228+
// Signed Header
229+
230+
// From chromium / crx3.proto:
231+
//
232+
// In the common case of a developer key proof, the first 128 bits of
233+
// the SHA-256 hash of the public key must equal the crx_id.
234+
hash := sha256.New()
235+
hash.Write(pubKey)
236+
sdpb := &pb.SignedData{
237+
CrxId: hash.Sum(nil)[0:16],
238+
}
239+
signedHeaderData, err := proto.Marshal(sdpb)
240+
if err != nil {
235241
return nil, err
236242
}
237243

238-
return buf.Bytes(), nil
244+
// Signature
245+
signature, err := crx3Signature(archiveData, signedHeaderData, key)
246+
if err != nil {
247+
return nil, err
248+
}
249+
250+
header := &pb.CrxFileHeader{
251+
Sha256WithRsa: []*pb.AsymmetricKeyProof{
252+
&pb.AsymmetricKeyProof{
253+
PublicKey: pubKey,
254+
Signature: signature,
255+
},
256+
},
257+
SignedHeaderData: signedHeaderData,
258+
}
259+
return proto.Marshal(header)
260+
}
261+
262+
func crx3Signature(archiveData, signedHeaderData []byte, key *rsa.PrivateKey) ([]byte, error) {
263+
// From chromium / crx3.proto:
264+
//
265+
// All proofs in this CrxFile message are on the value
266+
// "CRX3 SignedData\x00" + signed_header_size + signed_header_data +
267+
// archive, where "\x00" indicates an octet with value 0, "CRX3 SignedData"
268+
// is encoded using UTF-8, signed_header_size is the size in octets of the
269+
// contents of this field and is encoded using 4 octets in little-endian
270+
// order, signed_header_data is exactly the content of this field, and
271+
// archive is the remaining contents of the file following the header.
272+
273+
sign := sha256.New()
274+
sign.Write([]byte("CRX3 SignedData\x00"))
275+
if err := binary.Write(sign, binary.LittleEndian, uint32(len(signedHeaderData))); err != nil {
276+
return nil, err
277+
}
278+
sign.Write(signedHeaderData)
279+
if _, err := io.Copy(sign, bytes.NewReader(archiveData)); err != nil {
280+
return nil, err
281+
}
282+
return rsa.SignPKCS1v15(rand.Reader, key, crypto.SHA256, sign.Sum(nil))
239283
}

go.mod

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@ require (
88
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5
99
github.com/blang/semver v3.5.1+incompatible
1010
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b
11+
github.com/golang/protobuf v1.3.4
1112
github.com/google/go-cmp v0.3.0
1213
github.com/google/go-github/v27 v27.0.4
14+
github.com/mediabuyerbot/go-crx3 v1.3.1
1315
google.golang.org/api v0.7.0
1416
)

0 commit comments

Comments
 (0)