165 lines
3.2 KiB
Go
165 lines
3.2 KiB
Go
package globalplatform
|
|
|
|
import (
|
|
"archive/zip"
|
|
"bytes"
|
|
"io/ioutil"
|
|
"math"
|
|
"os"
|
|
"strings"
|
|
|
|
"github.com/status-im/keycard-go/apdu"
|
|
)
|
|
|
|
var internalFiles = []string{
|
|
"Header", "Directory", "Import", "Applet", "Class",
|
|
"Method", "StaticField", "Export", "ConstantPool", "RefLocation",
|
|
}
|
|
|
|
const blockSize = 247 // 255 - 8 bytes for MAC
|
|
|
|
// LoadCommandStream implement a struct that generates multiple Load commands used to load files to smartcards.
|
|
type LoadCommandStream struct {
|
|
data *bytes.Reader
|
|
currentIndex uint8
|
|
currentData []byte
|
|
p1 uint8
|
|
blocksCount int
|
|
}
|
|
|
|
// NewLoadCommandStream returns a new LoadCommandStream to load the specified file.
|
|
func NewLoadCommandStream(file *os.File) (*LoadCommandStream, error) {
|
|
files, err := loadFiles(file)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
data, err := encodeFilesData(files)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &LoadCommandStream{
|
|
data: bytes.NewReader(data),
|
|
p1: P1LoadMoreBlocks,
|
|
blocksCount: int(math.Ceil(float64(len(data)) / float64(blockSize))),
|
|
}, nil
|
|
}
|
|
|
|
// BlocksCount returns the total number of blocks based on data length and blockSize
|
|
func (lcs *LoadCommandStream) BlocksCount() int {
|
|
return lcs.blocksCount
|
|
}
|
|
|
|
// Next returns initialize the data for the next Load command.
|
|
// TODO:@gravityblast update blockSize when using encrypted data
|
|
func (lcs *LoadCommandStream) Next() bool {
|
|
if lcs.data.Len() == 0 {
|
|
return false
|
|
}
|
|
|
|
buf := make([]byte, blockSize)
|
|
n, err := lcs.data.Read(buf)
|
|
if err != nil {
|
|
return false
|
|
}
|
|
|
|
lcs.currentData = buf[:n]
|
|
lcs.currentIndex++
|
|
|
|
if lcs.data.Len() == 0 {
|
|
lcs.p1 = P1LoadLastBlock
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
// Index returns the command index.
|
|
func (lcs *LoadCommandStream) Index() uint8 {
|
|
return lcs.currentIndex - 1
|
|
}
|
|
|
|
// GetCommand returns the current apdu command.
|
|
func (lcs *LoadCommandStream) GetCommand() *apdu.Command {
|
|
return apdu.NewCommand(ClaGp, InsLoad, lcs.p1, lcs.Index(), lcs.currentData)
|
|
}
|
|
|
|
func loadFiles(f *os.File) (map[string][]byte, error) {
|
|
fi, err := f.Stat()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
z, err := zip.NewReader(f, fi.Size())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
files := make(map[string][]byte)
|
|
|
|
for _, item := range z.File {
|
|
name := strings.Split(item.FileInfo().Name(), ".")[0]
|
|
f, err := item.Open()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
data, err := ioutil.ReadAll(f)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
files[name] = data
|
|
}
|
|
|
|
return files, nil
|
|
}
|
|
|
|
func encodeFilesData(files map[string][]byte) ([]byte, error) {
|
|
var buf bytes.Buffer
|
|
|
|
for _, name := range internalFiles {
|
|
if data, ok := files[name]; ok {
|
|
buf.Write(data)
|
|
}
|
|
}
|
|
|
|
filesData := buf.Bytes()
|
|
length := encodeLength(len(filesData))
|
|
|
|
data := make([]byte, 0)
|
|
data = append(data, tagLoadFileDataBlock)
|
|
data = append(data, length...)
|
|
data = append(data, filesData...)
|
|
|
|
return data, nil
|
|
}
|
|
|
|
func encodeLength(length int) []byte {
|
|
if length < 0x80 {
|
|
return []byte{byte(length)}
|
|
}
|
|
|
|
if length < 0xFF {
|
|
return []byte{
|
|
byte(0x81),
|
|
byte(length),
|
|
}
|
|
}
|
|
|
|
if length < 0xFFFF {
|
|
return []byte{
|
|
byte(0x82),
|
|
byte((length & 0xFF00) >> 8),
|
|
byte(length & 0xFF),
|
|
}
|
|
}
|
|
|
|
return []byte{
|
|
byte(0x83),
|
|
byte((length & 0xFF0000) >> 16),
|
|
byte((length & 0xFF00) >> 8),
|
|
byte(length & 0xFF),
|
|
}
|
|
}
|