feat: vendor bodgit/sevenzip package to remove go4.org dependency
Vendored the sevenzip package to eliminate dependency chain: - sevenzip -> go4.org -> 25+ Google/Cloud/telemetry packages Changes: - Added internal/sevenzip/ with full package source - Inlined go4.org/readerutil into multireaderat.go - Updated all internal imports to use bookhoard/internal/sevenzip - Preserved .cb7 comic archive support This reduces bloat by ~4.9 MB and removes unused telemetry dependencies while maintaining all functionality.
This commit is contained in:
@@ -0,0 +1,69 @@
|
||||
package aes7z
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/sha256"
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
lru "github.com/hashicorp/golang-lru/v2"
|
||||
"golang.org/x/text/encoding/unicode"
|
||||
"golang.org/x/text/transform"
|
||||
)
|
||||
|
||||
type cacheKey struct {
|
||||
password string
|
||||
cycles int
|
||||
salt string // []byte isn't comparable
|
||||
}
|
||||
|
||||
const cacheSize = 10
|
||||
|
||||
//nolint:gochecknoglobals
|
||||
var once = sync.OnceValues(func() (*lru.Cache[cacheKey, []byte], error) {
|
||||
return lru.New[cacheKey, []byte](cacheSize)
|
||||
})
|
||||
|
||||
func calculateKey(password string, cycles int, salt []byte) ([]byte, error) {
|
||||
cache, err := once()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("aes7z: error creating cache: %w", err)
|
||||
}
|
||||
|
||||
ck := cacheKey{
|
||||
password: password,
|
||||
cycles: cycles,
|
||||
salt: hex.EncodeToString(salt),
|
||||
}
|
||||
|
||||
if key, ok := cache.Get(ck); ok {
|
||||
return key, nil
|
||||
}
|
||||
|
||||
b := bytes.NewBuffer(salt)
|
||||
|
||||
// Convert password to UTF-16LE
|
||||
utf16le := unicode.UTF16(unicode.LittleEndian, unicode.IgnoreBOM)
|
||||
t := transform.NewWriter(b, utf16le.NewEncoder())
|
||||
_, _ = t.Write([]byte(password))
|
||||
|
||||
key := make([]byte, sha256.Size)
|
||||
if cycles == 0x3f {
|
||||
copy(key, b.Bytes())
|
||||
} else {
|
||||
h := sha256.New()
|
||||
for i := uint64(0); i < 1<<cycles; i++ {
|
||||
// These will never error
|
||||
_, _ = h.Write(b.Bytes())
|
||||
_ = binary.Write(h, binary.LittleEndian, i)
|
||||
}
|
||||
|
||||
copy(key, h.Sum(nil))
|
||||
}
|
||||
|
||||
_ = cache.Add(ck, key)
|
||||
|
||||
return key, nil
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
// Package aes7z implements the 7-zip AES decryption.
|
||||
package aes7z
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
var (
|
||||
errAlreadyClosed = errors.New("aes7z: already closed")
|
||||
errNeedOneReader = errors.New("aes7z: need exactly one reader")
|
||||
errInsufficientProperties = errors.New("aes7z: not enough properties")
|
||||
errNoPasswordSet = errors.New("aes7z: no password set")
|
||||
errUnsupportedMethod = errors.New("aes7z: unsupported compression method")
|
||||
)
|
||||
|
||||
type readCloser struct {
|
||||
rc io.ReadCloser
|
||||
salt, iv []byte
|
||||
cycles int
|
||||
cbc cipher.BlockMode
|
||||
buf bytes.Buffer
|
||||
}
|
||||
|
||||
func (rc *readCloser) Close() error {
|
||||
if rc.rc == nil {
|
||||
return errAlreadyClosed
|
||||
}
|
||||
|
||||
if err := rc.rc.Close(); err != nil {
|
||||
return fmt.Errorf("aes7z: error closing: %w", err)
|
||||
}
|
||||
|
||||
rc.rc = nil
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (rc *readCloser) Password(p string) error {
|
||||
key, err := calculateKey(p, rc.cycles, rc.salt)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
block, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
return fmt.Errorf("aes7z: error creating cipher: %w", err)
|
||||
}
|
||||
|
||||
rc.cbc = cipher.NewCBCDecrypter(block, rc.iv)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (rc *readCloser) Read(p []byte) (int, error) {
|
||||
if rc.rc == nil {
|
||||
return 0, errAlreadyClosed
|
||||
}
|
||||
|
||||
if rc.cbc == nil {
|
||||
return 0, errNoPasswordSet
|
||||
}
|
||||
|
||||
var block [aes.BlockSize]byte
|
||||
|
||||
for rc.buf.Len() < len(p) {
|
||||
if _, err := io.ReadFull(rc.rc, block[:]); err != nil {
|
||||
if errors.Is(err, io.EOF) {
|
||||
break
|
||||
}
|
||||
|
||||
return 0, fmt.Errorf("aes7z: error reading block: %w", err)
|
||||
}
|
||||
|
||||
rc.cbc.CryptBlocks(block[:], block[:])
|
||||
|
||||
_, _ = rc.buf.Write(block[:])
|
||||
}
|
||||
|
||||
n, err := rc.buf.Read(p)
|
||||
if err != nil && !errors.Is(err, io.EOF) {
|
||||
err = fmt.Errorf("aes7z: error reading: %w", err)
|
||||
}
|
||||
|
||||
return n, err
|
||||
}
|
||||
|
||||
// NewReader returns a new AES-256-CBC & SHA-256 io.ReadCloser. The Password
|
||||
// method must be called before attempting to call Read so that the block
|
||||
// cipher is correctly initialised.
|
||||
func NewReader(p []byte, _ uint64, readers []io.ReadCloser) (io.ReadCloser, error) {
|
||||
if len(readers) != 1 {
|
||||
return nil, errNeedOneReader
|
||||
}
|
||||
|
||||
// Need at least two bytes initially
|
||||
if len(p) < 2 {
|
||||
return nil, errInsufficientProperties
|
||||
}
|
||||
|
||||
if p[0]&0xc0 == 0 {
|
||||
return nil, errUnsupportedMethod
|
||||
}
|
||||
|
||||
rc := new(readCloser)
|
||||
|
||||
salt := p[0]>>7&1 + p[1]>>4
|
||||
iv := p[0]>>6&1 + p[1]&0x0f
|
||||
|
||||
if len(p) != int(2+salt+iv) {
|
||||
return nil, errInsufficientProperties
|
||||
}
|
||||
|
||||
rc.salt = p[2 : 2+salt]
|
||||
rc.iv = make([]byte, aes.BlockSize)
|
||||
copy(rc.iv, p[2+salt:])
|
||||
|
||||
rc.cycles = int(p[0] & 0x3f)
|
||||
rc.rc = readers[0]
|
||||
|
||||
return rc, nil
|
||||
}
|
||||
Reference in New Issue
Block a user