diff --git a/apdu/command.go b/apdu/command.go index 81398ec..4eec02a 100644 --- a/apdu/command.go +++ b/apdu/command.go @@ -6,24 +6,31 @@ import ( ) type Command struct { - Cla uint8 - Ins uint8 - P1 uint8 - P2 uint8 - Data []byte - Le uint8 + Cla uint8 + Ins uint8 + P1 uint8 + P2 uint8 + Data []byte + Le uint8 + requiresLe bool } func NewCommand(cla, ins, p1, p2 uint8, data []byte) *Command { return &Command{ - Cla: cla, - Ins: ins, - P1: p1, - P2: p2, - Data: data, + Cla: cla, + Ins: ins, + P1: p1, + P2: p2, + Data: data, + requiresLe: false, } } +func (c *Command) SetLE(le uint8) { + c.requiresLe = true + c.Le = le +} + func (c *Command) Serialize() ([]byte, error) { buf := new(bytes.Buffer) @@ -52,8 +59,10 @@ func (c *Command) Serialize() ([]byte, error) { } } - if err := binary.Write(buf, binary.BigEndian, c.Le); err != nil { - return nil, err + if c.requiresLe { + if err := binary.Write(buf, binary.BigEndian, c.Le); err != nil { + return nil, err + } } return buf.Bytes(), nil diff --git a/apdu/command_test.go b/apdu/command_test.go index 44eb7e4..ab036c4 100644 --- a/apdu/command_test.go +++ b/apdu/command_test.go @@ -15,9 +15,15 @@ func TestNewCommand(t *testing.T) { data := hexutils.HexToBytes("84762336c5187fe8") cmd := NewCommand(cla, ins, p1, p2, data) - expected := "80 50 01 02 08 84 76 23 36 C5 18 7F E8 00" + expected := "80 50 01 02 08 84 76 23 36 C5 18 7F E8" result, err := cmd.Serialize() assert.NoError(t, err) assert.Equal(t, expected, hexutils.BytesToHexWithSpaces(result)) + + cmd.SetLE(uint8(0x77)) + expected = "80 50 01 02 08 84 76 23 36 C5 18 7F E8 77" + result, err = cmd.Serialize() + assert.NoError(t, err) + assert.Equal(t, expected, hexutils.BytesToHexWithSpaces(result)) } diff --git a/globalplatform/apdu_wrapper.go b/globalplatform/apdu_wrapper.go new file mode 100644 index 0000000..379568b --- /dev/null +++ b/globalplatform/apdu_wrapper.go @@ -0,0 +1,75 @@ +package globalplatform + +import ( + "bytes" + "encoding/binary" + + "github.com/status-im/status-go/smartcard/apdu" + "github.com/status-im/status-go/smartcard/globalplatform/crypto" +) + +type APDUWrapper struct { + macKey []byte + icv []byte +} + +func NewAPDUWrapper(macKey []byte) *APDUWrapper { + return &APDUWrapper{ + macKey: macKey, + icv: crypto.NullBytes8, + } +} + +func (w *APDUWrapper) Wrap(cmd *apdu.Command) (*apdu.Command, error) { + macData := new(bytes.Buffer) + + cla := cmd.Cla | 0x04 + if err := binary.Write(macData, binary.BigEndian, cla); err != nil { + return nil, err + } + + if err := binary.Write(macData, binary.BigEndian, cmd.Ins); err != nil { + return nil, err + } + + if err := binary.Write(macData, binary.BigEndian, cmd.P1); err != nil { + return nil, err + } + + if err := binary.Write(macData, binary.BigEndian, cmd.P2); err != nil { + return nil, err + } + + if err := binary.Write(macData, binary.BigEndian, uint8(len(cmd.Data)+8)); err != nil { + return nil, err + } + + if err := binary.Write(macData, binary.BigEndian, cmd.Data); err != nil { + return nil, err + } + + var ( + icv []byte + err error + ) + + if bytes.Equal(w.icv, crypto.NullBytes8) { + icv = w.icv + } else { + icv, err = crypto.EncryptICV(w.macKey, w.icv) + if err != nil { + return nil, err + } + } + + mac, err := crypto.MacFull3DES(w.macKey, macData.Bytes(), icv) + if err != nil { + return nil, err + } + + newData := make([]byte, 0) + newData = append(newData, cmd.Data...) + newData = append(newData, mac...) + + return apdu.NewCommand(cla, cmd.Ins, cmd.P1, cmd.P2, newData), nil +} diff --git a/globalplatform/apdu_wrapper_test.go b/globalplatform/apdu_wrapper_test.go new file mode 100644 index 0000000..cc85715 --- /dev/null +++ b/globalplatform/apdu_wrapper_test.go @@ -0,0 +1,28 @@ +package globalplatform + +import ( + "testing" + + "github.com/status-im/status-go/smartcard/apdu" + "github.com/status-im/status-go/smartcard/globalplatform/crypto" + "github.com/status-im/status-go/smartcard/hexutils" + "github.com/stretchr/testify/assert" +) + +func TestAPDUWrapper_Wrap(t *testing.T) { + macKey := hexutils.HexToBytes("2983BA77D709C2DAA1E6000ABCCAC951") + data := hexutils.HexToBytes("1d4de92eaf7a2c9f") + + cmd := apdu.NewCommand(uint8(0x80), uint8(0x82), uint8(0x01), uint8(0x00), data) + w := NewAPDUWrapper(macKey) + + assert.Equal(t, crypto.NullBytes8, w.icv) + + wrappedCmd, err := w.Wrap(cmd) + assert.NoError(t, err) + raw, err := wrappedCmd.Serialize() + assert.NoError(t, err) + + expected := "84 82 01 00 10 1D 4D E9 2E AF 7A 2C 9F 8F 9B 0D F6 81 C1 D3 EC" + assert.Equal(t, expected, hexutils.BytesToHexWithSpaces(raw)) +} diff --git a/globalplatform/crypto/crypto.go b/globalplatform/crypto/crypto.go index da83b8d..a17121b 100644 --- a/globalplatform/crypto/crypto.go +++ b/globalplatform/crypto/crypto.go @@ -9,7 +9,7 @@ import ( var ( DerivationPurposeEnc = []byte{0x01, 0x82} DerivationPurposeMac = []byte{0x01, 0x01} - nullBytes8 = []byte{0, 0, 0, 0, 0, 0, 0, 0} + NullBytes8 = []byte{0, 0, 0, 0, 0, 0, 0, 0} ) func DeriveKey(cardKey []byte, seq []byte, purpose []byte) ([]byte, error) { @@ -26,7 +26,7 @@ func DeriveKey(cardKey []byte, seq []byte, purpose []byte) ([]byte, error) { ciphertext := make([]byte, 16) - mode := cipher.NewCBCEncrypter(block, nullBytes8) + mode := cipher.NewCBCEncrypter(block, NullBytes8) mode.CryptBlocks(ciphertext, derivation) return ciphertext, nil @@ -37,7 +37,7 @@ func VerifyCryptogram(encKey, hostChallenge, cardChallenge, cardCryptogram []byt data = append(data, hostChallenge...) data = append(data, cardChallenge...) paddedData := appendDESPadding(data) - calculated, err := mac3des(encKey, paddedData, nullBytes8) + calculated, err := mac3des(encKey, paddedData, NullBytes8) if err != nil { return false, err } @@ -76,6 +76,19 @@ func MacFull3DES(key, data, iv []byte) ([]byte, error) { return ciphertext, nil } +func EncryptICV(macKey, mac []byte) ([]byte, error) { + block, err := des.NewCipher(resizeKey8(macKey)) + if err != nil { + return nil, err + } + + ciphertext := make([]byte, 16) + mode := cipher.NewCBCEncrypter(block, NullBytes8) + mode.CryptBlocks(ciphertext, mac) + + return ciphertext, nil +} + func mac3des(key, data, iv []byte) ([]byte, error) { key24 := resizeKey24(key) diff --git a/globalplatform/crypto/crypto_test.go b/globalplatform/crypto/crypto_test.go index dc4c884..ec416a5 100644 --- a/globalplatform/crypto/crypto_test.go +++ b/globalplatform/crypto/crypto_test.go @@ -46,7 +46,7 @@ func TestVerifyCryptogram(t *testing.T) { func TestMac3des(t *testing.T) { key := hexutils.HexToBytes("16B5867FF50BE7239C2BF1245B83A362") data := hexutils.HexToBytes("32DA078D7AAC1CFF007284F64A7D64658000000000000000") - result, err := mac3des(key, data, nullBytes8) + result, err := mac3des(key, data, NullBytes8) assert.NoError(t, err) expected := "05C4BB8A86014E22" @@ -56,7 +56,7 @@ func TestMac3des(t *testing.T) { func TestMacFull3DES(t *testing.T) { key := hexutils.HexToBytes("5b02e75ad63190aece0622936f11abab") data := hexutils.HexToBytes("8482010010810b098a8fbb88da") - result, err := MacFull3DES(key, data, nullBytes8) + result, err := MacFull3DES(key, data, NullBytes8) assert.NoError(t, err) expected := "5271D7174A5A166A" assert.Equal(t, expected, hexutils.BytesToHex(result))