blob: 21a7ee385050eadd311bc0532bbd630e0aec1c43 [file] [log] [blame]
// Copyright 2018 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "hermes/apdu.h"
#include <algorithm>
#include <utility>
#include <base/logging.h>
#include <base/strings/string_number_conversions.h>
namespace {
// Max data bytes for standard APDUs and extended APDUs.
// Note that the length limit for extended APDUs is not 65536 due
// to a limitation imposed by the Java Card platform.
constexpr size_t kMaxStandardDataSize = 255;
constexpr size_t kMaxExtendedDataSize = 32767;
// Number of bytes Lc and Le fields are in standard and extended APDUs.
// Note: Lc and Le must either both or neither be in extended form.
constexpr size_t kStandardLengthBytes = 1;
constexpr size_t kExtendedLengthBytes = 3;
constexpr size_t kHeaderSize = 4; // CLA + INS + P1 + P2
} // namespace
namespace hermes {
CommandApdu::CommandApdu(ApduClass cls,
ApduInstruction instruction,
bool is_extended_length,
uint16_t le)
: is_extended_length_(is_extended_length),
has_more_fragments_(true),
current_fragment_(0),
le_(le),
current_index_(0) {
max_data_size_ =
is_extended_length_ ? kMaxExtendedDataSize : kMaxStandardDataSize;
// Note that 256 is valid for standard APDUs because an Le field of 0 is
// interpreted to mean that Ne=256.
if (!is_extended_length_ && 256 < le_) {
LOG(INFO) << "CommandApdu created with Le of " << le_
<< ", but is not an extended length APDU. Setting Le to 256.";
le_ = 256;
} else if (kMaxExtendedDataSize < le_) {
LOG(INFO) << "CommandApdu created with Le of " << le_
<< " but restrictions imposed by the Java Card platform requires "
<< "Le to fit into a signed 16 bit integer. Setting Le to 32767.";
le_ = kMaxExtendedDataSize;
}
// Create APDU header.
data_.push_back(static_cast<uint8_t>(cls)); // CLS
data_.push_back(static_cast<uint8_t>(instruction)); // INS
data_.push_back(0); // P1
data_.push_back(0); // P2
}
void CommandApdu::AddData(const std::initializer_list<uint8_t>& data) {
DCHECK_EQ(current_index_, 0);
EnsureLcExists();
data_.insert(data_.end(), data.begin(), data.end());
}
void CommandApdu::AddData(const std::vector<uint8_t>& data) {
DCHECK_EQ(current_index_, 0);
EnsureLcExists();
data_.insert(data_.end(), data.begin(), data.end());
}
size_t CommandApdu::GetNextFragment(uint8_t** fragment) {
DCHECK(fragment);
if (!HasMoreFragments()) {
return 0;
}
size_t header_size = kHeaderSize;
size_t length_size =
is_extended_length_ ? kExtendedLengthBytes : kStandardLengthBytes;
size_t lc_size = 0;
// The APDU contains an Lc if it has any data.
if (data_.size() > kHeaderSize) {
lc_size += length_size;
}
header_size += lc_size;
bool is_first_fragment = (current_index_ == 0);
// Do not include APDU header in bytes_left calculation.
current_index_ += is_first_fragment ? header_size : 0;
size_t bytes_left = data_.size() - current_index_;
size_t current_size = std::min(bytes_left, max_data_size_);
bool is_last_fragment = (bytes_left == current_size);
has_more_fragments_ = !is_last_fragment;
// Set up APDU header in-place.
// If Lc is 0, the generated APDU should be either case 1 or 2.
current_index_ -= header_size;
data_[current_index_] = data_[0];
data_[current_index_ + 1] = data_[1];
data_[current_index_ + 2] =
is_last_fragment ? kApduP1LastBlock : kApduP1MoreBlocks;
data_[current_index_ + 3] = current_fragment_++;
if (is_extended_length_) {
data_[current_index_ + 4] = 0;
data_[current_index_ + 5] = static_cast<uint8_t>(current_size & 0xFF);
data_[current_index_ + 6] = static_cast<uint8_t>(current_size >> 8);
} else {
data_[current_index_ + 4] = static_cast<uint8_t>(current_size);
}
size_t le_size = 0;
// Last fragment is the only one that will potentially have an Le field, as we
// do not expect any response data until we send the entire command.
if (is_last_fragment && le_) {
le_size = length_size;
data_.reserve(data_.size() + length_size);
if (is_extended_length_) {
data_.push_back(0);
data_.push_back(static_cast<uint8_t>(le_ & 0xFF));
data_.push_back(static_cast<uint8_t>(le_ >> 8));
} else {
data_.push_back(static_cast<uint8_t>(le_));
}
}
// Add APDU header and (potentially) Le to size.
current_size += header_size + le_size;
*fragment = &data_[current_index_];
current_index_ += current_size;
VLOG(2) << "APDU fragment #" << current_fragment_ - 1 << " (" << current_size
<< " bytes): " << base::HexEncode(*fragment, current_size);
return current_size;
}
void CommandApdu::EnsureLcExists() {
if (data_.size() == kHeaderSize) {
if (is_extended_length_) {
data_.push_back(0);
data_.push_back(0);
data_.push_back(0);
} else {
data_.push_back(0);
}
}
}
//////////////////////////
// ResponseApdu Methods //
//////////////////////////
void ResponseApdu::AddData(const std::vector<uint8_t>& data) {
RemoveStatusBytes();
data_.insert(data_.end(), data.begin(), data.end());
}
void ResponseApdu::AddData(const uint8_t* data, size_t data_len) {
RemoveStatusBytes();
data_.insert(data_.end(), data, data + data_len);
}
std::vector<uint8_t> ResponseApdu::Release() {
RemoveStatusBytes();
return std::move(data_);
}
CommandApdu ResponseApdu::CreateGetMoreCommand(bool use_extended_length) const {
uint8_t sw2 = 0;
if (2 <= data_.size()) {
sw2 = data_[data_.size() - 1];
}
return CommandApdu(ApduClass::STORE_DATA, ApduInstruction::GET_MORE_RESPONSE,
use_extended_length, sw2);
}
bool ResponseApdu::IsSuccessful() const {
if (2 <= data_.size()) {
return data_[data_.size() - 2] == STATUS_OK;
}
LOG(WARNING) << "Called IsSuccessful() on an empty ResponseApdu";
return true;
}
bool ResponseApdu::WaitingForNextFragment() const {
return (data_.empty() || data_.size() == 2) && IsSuccessful();
}
bool ResponseApdu::MorePayloadIncoming() const {
if (2 <= data_.size()) {
return data_[data_.size() - 2] == STATUS_MORE_RESPONSE;
}
LOG(WARNING) << "Called MorePayloadIncoming() on an empty ResponseApdu";
return false;
}
void ResponseApdu::RemoveStatusBytes() {
if (2 <= data_.size()) {
data_.pop_back();
data_.pop_back();
}
}
} // namespace hermes