keycard-go/globalplatform/load.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),
}
}