2018-09-02 05:46:37 -07:00
|
|
|
package commands
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"encoding/binary"
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
|
|
|
)
|
|
|
|
|
|
|
|
type (
|
|
|
|
Response interface {
|
|
|
|
}
|
|
|
|
|
|
|
|
Error struct {
|
|
|
|
Code ErrorCode
|
|
|
|
}
|
|
|
|
|
|
|
|
CreateSessionResponse struct {
|
|
|
|
SessionID uint8
|
|
|
|
CardChallenge []byte
|
|
|
|
CardCryptogram []byte
|
|
|
|
}
|
|
|
|
|
|
|
|
SessionMessageResponse struct {
|
|
|
|
SessionID uint8
|
|
|
|
EncryptedData []byte
|
|
|
|
MAC []byte
|
|
|
|
}
|
|
|
|
|
|
|
|
CreateAsymmetricKeyResponse struct {
|
|
|
|
KeyID uint16
|
|
|
|
}
|
|
|
|
|
|
|
|
PutAsymmetricKeyResponse struct {
|
|
|
|
KeyID uint16
|
|
|
|
}
|
|
|
|
|
2019-06-18 13:18:13 -07:00
|
|
|
ObjectInfoResponse struct {
|
|
|
|
Capabilities uint64
|
|
|
|
ObjectID uint16
|
|
|
|
Length uint16
|
|
|
|
Domains uint16
|
|
|
|
Type uint8
|
|
|
|
Algorithm Algorithm
|
|
|
|
Sequence uint8
|
|
|
|
Origin uint8
|
|
|
|
Label [40]byte
|
|
|
|
DelegatedCapabilites uint64
|
|
|
|
}
|
|
|
|
|
|
|
|
Object struct {
|
|
|
|
ObjectID uint16
|
|
|
|
ObjectType uint8
|
|
|
|
Sequence uint8
|
|
|
|
}
|
|
|
|
|
|
|
|
ListObjectsResponse struct {
|
|
|
|
Objects []Object
|
|
|
|
}
|
|
|
|
|
2018-09-02 05:46:37 -07:00
|
|
|
SignDataEddsaResponse struct {
|
|
|
|
Signature []byte
|
|
|
|
}
|
2018-09-08 11:43:08 -07:00
|
|
|
|
2021-04-07 15:05:46 -07:00
|
|
|
SignDataPkcs1Response struct {
|
|
|
|
Signature []byte
|
|
|
|
}
|
|
|
|
|
2018-11-30 07:49:18 -08:00
|
|
|
SignDataEcdsaResponse struct {
|
|
|
|
Signature []byte
|
|
|
|
}
|
|
|
|
|
2018-09-08 11:43:08 -07:00
|
|
|
GetPubKeyResponse struct {
|
|
|
|
Algorithm Algorithm
|
|
|
|
// KeyData can contain different formats depending on the algorithm according to the YubiHSM2 documentation.
|
|
|
|
KeyData []byte
|
|
|
|
}
|
2018-09-14 03:21:34 -07:00
|
|
|
|
|
|
|
EchoResponse struct {
|
|
|
|
Data []byte
|
|
|
|
}
|
2019-08-27 14:08:25 -07:00
|
|
|
|
|
|
|
DeriveEcdhResponse struct {
|
|
|
|
XCoordinate []byte
|
|
|
|
}
|
2020-02-28 09:40:45 -08:00
|
|
|
|
2021-04-07 15:05:46 -07:00
|
|
|
ChangeAuthenticationKeyResponse struct {
|
2020-02-28 09:40:45 -08:00
|
|
|
ObjectID uint16
|
|
|
|
}
|
2021-03-04 07:46:36 -08:00
|
|
|
|
2021-04-07 15:05:46 -07:00
|
|
|
PutWrapkeyResponse struct {
|
|
|
|
ObjectID uint16
|
|
|
|
}
|
|
|
|
|
|
|
|
PutAuthenticationKeyResponse struct {
|
2021-03-04 07:46:36 -08:00
|
|
|
ObjectID uint16
|
|
|
|
}
|
2021-03-05 04:41:57 -08:00
|
|
|
|
2021-04-07 14:22:09 -07:00
|
|
|
PutOpaqueResponse struct {
|
2021-03-05 04:41:57 -08:00
|
|
|
ObjectID uint16
|
|
|
|
}
|
2021-04-07 14:22:09 -07:00
|
|
|
|
|
|
|
GetOpaqueResponse struct {
|
|
|
|
Data []byte
|
|
|
|
}
|
|
|
|
|
|
|
|
SignAttestationCertResponse struct {
|
|
|
|
Cert []byte
|
|
|
|
}
|
2018-09-02 05:46:37 -07:00
|
|
|
)
|
|
|
|
|
|
|
|
// ParseResponse parses the binary response from the card to the relevant Response type.
|
|
|
|
// If the response is an error zu parses the Error type response and returns an error of the
|
|
|
|
// type commands.Error with the parsed error message.
|
|
|
|
func ParseResponse(data []byte) (Response, error) {
|
|
|
|
if len(data) < 3 {
|
|
|
|
return nil, errors.New("invalid response")
|
|
|
|
}
|
|
|
|
|
|
|
|
transactionType := CommandType(data[0] + ResponseCommandOffset)
|
|
|
|
|
|
|
|
var payloadLength uint16
|
|
|
|
err := binary.Read(bytes.NewReader(data[1:3]), binary.BigEndian, &payloadLength)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
payload := data[3:]
|
|
|
|
if len(payload) != int(payloadLength) {
|
|
|
|
return nil, errors.New("response payload length does not equal the given length")
|
|
|
|
}
|
|
|
|
|
|
|
|
switch transactionType {
|
|
|
|
case CommandTypeCreateSession:
|
|
|
|
return parseCreateSessionResponse(payload)
|
|
|
|
case CommandTypeAuthenticateSession:
|
|
|
|
return nil, nil
|
|
|
|
case CommandTypeSessionMessage:
|
|
|
|
return parseSessionMessage(payload)
|
|
|
|
case CommandTypeGenerateAsymmetricKey:
|
|
|
|
return parseCreateAsymmetricKeyResponse(payload)
|
|
|
|
case CommandTypeSignDataEddsa:
|
|
|
|
return parseSignDataEddsaResponse(payload)
|
2018-12-05 05:42:38 -08:00
|
|
|
case CommandTypeSignDataEcdsa:
|
|
|
|
return parseSignDataEcdsaResponse(payload)
|
2021-04-07 15:05:46 -07:00
|
|
|
case CommandTypeSignDataPkcs1:
|
|
|
|
return parseSignDataPkcs1Response(payload)
|
2018-09-02 05:46:37 -07:00
|
|
|
case CommandTypePutAsymmetric:
|
|
|
|
return parsePutAsymmetricKeyResponse(payload)
|
2019-06-18 13:18:13 -07:00
|
|
|
case CommandTypeListObjects:
|
|
|
|
return parseListObjectsResponse(payload)
|
|
|
|
case CommandTypeGetObjectInfo:
|
|
|
|
return parseGetObjectInfoResponse(payload)
|
2018-09-02 10:26:39 -07:00
|
|
|
case CommandTypeCloseSession:
|
|
|
|
return nil, nil
|
2018-09-08 11:43:08 -07:00
|
|
|
case CommandTypeGetPubKey:
|
|
|
|
return parseGetPubKeyResponse(payload)
|
2018-12-05 05:42:38 -08:00
|
|
|
case CommandTypeDeleteObject:
|
|
|
|
return nil, nil
|
2018-09-14 03:21:34 -07:00
|
|
|
case CommandTypeEcho:
|
|
|
|
return parseEchoResponse(payload)
|
2019-08-27 14:08:25 -07:00
|
|
|
case CommandTypeDeriveEcdh:
|
|
|
|
return parseDeriveEcdhResponse(payload)
|
2020-02-28 09:40:45 -08:00
|
|
|
case CommandTypeChangeAuthenticationKey:
|
|
|
|
return parseChangeAuthenticationKeyResponse(payload)
|
2021-04-07 15:05:46 -07:00
|
|
|
case CommandTypeGetPseudoRandom:
|
|
|
|
return parseGetPseudoRandomResponse(payload), nil
|
|
|
|
case CommandTypePutWrapKey:
|
|
|
|
return parsePutWrapkeyResponse(payload)
|
|
|
|
case CommandTypePutAuthenticationKey:
|
|
|
|
return parsePutAuthenticationKeyResponse(payload)
|
2021-04-07 14:22:09 -07:00
|
|
|
case CommandTypePutOpaque:
|
|
|
|
return parsePutOpaqueResponse(payload)
|
|
|
|
case CommandTypeGetOpaque:
|
|
|
|
return parseGetOpaqueResponse(payload)
|
|
|
|
case CommandTypeAttestAsymmetric:
|
|
|
|
return parseAttestationCertResponse(payload)
|
2018-09-02 05:46:37 -07:00
|
|
|
case ErrorResponseCode:
|
|
|
|
return nil, parseErrorResponse(payload)
|
|
|
|
default:
|
|
|
|
return nil, errors.New("response type unknown / not implemented")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func parseErrorResponse(payload []byte) error {
|
|
|
|
if len(payload) != 1 {
|
|
|
|
return errors.New("invalid response payload length")
|
|
|
|
}
|
|
|
|
|
|
|
|
return &Error{
|
|
|
|
Code: ErrorCode(payload[0]),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func parseSessionMessage(payload []byte) (Response, error) {
|
|
|
|
return &SessionMessageResponse{
|
|
|
|
SessionID: payload[0],
|
|
|
|
EncryptedData: payload[1 : len(payload)-8],
|
|
|
|
MAC: payload[len(payload)-8:],
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func parseCreateSessionResponse(payload []byte) (Response, error) {
|
|
|
|
if len(payload) != 17 {
|
|
|
|
return nil, errors.New("invalid response payload length")
|
|
|
|
}
|
|
|
|
|
|
|
|
return &CreateSessionResponse{
|
|
|
|
SessionID: uint8(payload[0]),
|
|
|
|
CardChallenge: payload[1:9],
|
|
|
|
CardCryptogram: payload[9:],
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func parseCreateAsymmetricKeyResponse(payload []byte) (Response, error) {
|
|
|
|
if len(payload) != 2 {
|
|
|
|
return nil, errors.New("invalid response payload length")
|
|
|
|
}
|
|
|
|
|
|
|
|
var keyID uint16
|
|
|
|
err := binary.Read(bytes.NewReader(payload[1:3]), binary.BigEndian, &keyID)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return &CreateAsymmetricKeyResponse{
|
|
|
|
KeyID: keyID,
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func parseSignDataEddsaResponse(payload []byte) (Response, error) {
|
|
|
|
return &SignDataEddsaResponse{
|
|
|
|
Signature: payload,
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
2021-04-07 15:05:46 -07:00
|
|
|
func parseSignDataPkcs1Response(payload []byte) (Response, error) {
|
|
|
|
if len(payload) < 1 {
|
|
|
|
return nil, errors.New("invalid response payload length")
|
|
|
|
}
|
|
|
|
|
|
|
|
return &SignDataPkcs1Response{
|
|
|
|
Signature: payload,
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
2018-12-05 05:42:38 -08:00
|
|
|
func parseSignDataEcdsaResponse(payload []byte) (Response, error) {
|
|
|
|
return &SignDataEcdsaResponse{
|
|
|
|
Signature: payload,
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
2018-09-02 05:46:37 -07:00
|
|
|
func parsePutAsymmetricKeyResponse(payload []byte) (Response, error) {
|
|
|
|
if len(payload) != 2 {
|
|
|
|
return nil, errors.New("invalid response payload length")
|
|
|
|
}
|
|
|
|
|
|
|
|
var keyID uint16
|
2018-09-17 12:18:05 -07:00
|
|
|
err := binary.Read(bytes.NewReader(payload), binary.BigEndian, &keyID)
|
2018-09-02 05:46:37 -07:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return &PutAsymmetricKeyResponse{
|
|
|
|
KeyID: keyID,
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
2019-06-18 13:18:13 -07:00
|
|
|
func parseListObjectsResponse(payload []byte) (Response, error) {
|
|
|
|
if len(payload)%4 != 0 {
|
|
|
|
return nil, errors.New("invalid response payload length")
|
|
|
|
}
|
|
|
|
|
|
|
|
response := ListObjectsResponse{
|
|
|
|
Objects: make([]Object, len(payload)/4),
|
|
|
|
}
|
|
|
|
|
|
|
|
err := binary.Read(bytes.NewReader(payload), binary.BigEndian, &response.Objects)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return &response, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func parseGetObjectInfoResponse(payload []byte) (Response, error) {
|
|
|
|
response := ObjectInfoResponse{}
|
|
|
|
|
|
|
|
err := binary.Read(bytes.NewReader(payload), binary.BigEndian, &response)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return &response, nil
|
|
|
|
}
|
|
|
|
|
2018-09-08 11:43:08 -07:00
|
|
|
func parseGetPubKeyResponse(payload []byte) (Response, error) {
|
|
|
|
if len(payload) < 1 {
|
|
|
|
return nil, errors.New("invalid response payload length")
|
|
|
|
}
|
|
|
|
return &GetPubKeyResponse{
|
|
|
|
Algorithm: Algorithm(payload[0]),
|
|
|
|
KeyData: payload[1:],
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
2018-09-14 03:21:34 -07:00
|
|
|
func parseEchoResponse(payload []byte) (Response, error) {
|
|
|
|
return &EchoResponse{
|
|
|
|
Data: payload,
|
|
|
|
}, nil
|
2019-08-27 14:08:25 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
func parseDeriveEcdhResponse(payload []byte) (Response, error) {
|
|
|
|
return &DeriveEcdhResponse{
|
|
|
|
XCoordinate: payload,
|
|
|
|
}, nil
|
2020-02-28 09:40:45 -08:00
|
|
|
}
|
|
|
|
|
2021-04-07 15:05:46 -07:00
|
|
|
func parseChangeAuthenticationKeyResponse(payload []byte) (Response, error) {
|
2020-02-28 09:40:45 -08:00
|
|
|
if len(payload) != 2 {
|
|
|
|
return nil, errors.New("invalid response payload length")
|
|
|
|
}
|
|
|
|
|
|
|
|
var objectID uint16
|
|
|
|
err := binary.Read(bytes.NewReader(payload), binary.BigEndian, &objectID)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2021-04-07 15:05:46 -07:00
|
|
|
return &ChangeAuthenticationKeyResponse{ObjectID: objectID}, nil
|
2021-03-01 03:47:30 -08:00
|
|
|
}
|
|
|
|
|
2021-04-07 15:05:46 -07:00
|
|
|
func parseGetPseudoRandomResponse(payload []byte) Response {
|
|
|
|
return payload
|
|
|
|
}
|
|
|
|
|
|
|
|
func parsePutWrapkeyResponse(payload []byte) (Response, error) {
|
2021-03-04 07:46:36 -08:00
|
|
|
if len(payload) != 2 {
|
|
|
|
return nil, errors.New("invalid response payload length")
|
|
|
|
}
|
|
|
|
|
|
|
|
var objectID uint16
|
|
|
|
err := binary.Read(bytes.NewReader(payload), binary.BigEndian, &objectID)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2021-04-07 15:05:46 -07:00
|
|
|
return &PutWrapkeyResponse{ObjectID: objectID}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func parsePutAuthenticationKeyResponse(payload []byte) (Response, error) {
|
|
|
|
if len(payload) != 2 {
|
|
|
|
return nil, errors.New("invalid response payload length")
|
|
|
|
}
|
2021-04-07 14:22:09 -07:00
|
|
|
|
2021-04-07 15:05:46 -07:00
|
|
|
var objectID uint16
|
|
|
|
err := binary.Read(bytes.NewReader(payload), binary.BigEndian, &objectID)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return &PutAuthenticationKeyResponse{ObjectID: objectID}, nil
|
2021-03-05 04:41:57 -08:00
|
|
|
}
|
|
|
|
|
2021-04-07 14:22:09 -07:00
|
|
|
func parsePutOpaqueResponse(payload []byte) (Response, error) {
|
2021-03-05 04:41:57 -08:00
|
|
|
if len(payload) != 2 {
|
|
|
|
return nil, errors.New("invalid response payload length")
|
|
|
|
}
|
|
|
|
|
|
|
|
var objectID uint16
|
|
|
|
err := binary.Read(bytes.NewReader(payload), binary.BigEndian, &objectID)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2021-04-07 14:22:09 -07:00
|
|
|
|
|
|
|
return &PutOpaqueResponse{
|
|
|
|
ObjectID: objectID,
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func parseGetOpaqueResponse(payload []byte) (Response, error) {
|
|
|
|
if len(payload) < 1 {
|
|
|
|
return nil, errors.New("invalid response payload length")
|
|
|
|
}
|
|
|
|
|
|
|
|
return &GetOpaqueResponse{
|
|
|
|
Data: payload,
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func parseAttestationCertResponse(payload []byte) (Response, error) {
|
|
|
|
if len(payload) < 1 {
|
|
|
|
return nil, errors.New("invalid response payload length")
|
|
|
|
}
|
|
|
|
|
|
|
|
return &SignAttestationCertResponse{
|
|
|
|
Cert: payload,
|
|
|
|
}, nil
|
2021-03-04 07:46:36 -08:00
|
|
|
}
|
|
|
|
|
2018-09-02 05:46:37 -07:00
|
|
|
// Error formats a card error message into a human readable format
|
|
|
|
func (e *Error) Error() string {
|
|
|
|
message := ""
|
|
|
|
switch e.Code {
|
|
|
|
case ErrorCodeOK:
|
|
|
|
message = "OK"
|
|
|
|
case ErrorCodeInvalidCommand:
|
|
|
|
message = "Invalid command"
|
|
|
|
case ErrorCodeInvalidData:
|
|
|
|
message = "Invalid data"
|
|
|
|
case ErrorCodeInvalidSession:
|
|
|
|
message = "Invalid session"
|
|
|
|
case ErrorCodeAuthFail:
|
|
|
|
message = "Auth fail"
|
|
|
|
case ErrorCodeSessionFull:
|
|
|
|
message = "Session full"
|
|
|
|
case ErrorCodeSessionFailed:
|
|
|
|
message = "Session failed"
|
|
|
|
case ErrorCodeStorageFailed:
|
|
|
|
message = "Storage failed"
|
|
|
|
case ErrorCodeWrongLength:
|
|
|
|
message = "Wrong length"
|
|
|
|
case ErrorCodeInvalidPermission:
|
|
|
|
message = "Invalid permission"
|
|
|
|
case ErrorCodeLogFull:
|
|
|
|
message = "Log full"
|
|
|
|
case ErrorCodeObjectNotFound:
|
|
|
|
message = "Object not found"
|
2021-04-07 14:22:09 -07:00
|
|
|
case ErrorCodeInvalidID:
|
|
|
|
message = "Invalid ID"
|
2021-04-07 15:05:46 -07:00
|
|
|
case ErrorCodeCommandUnexecuted:
|
|
|
|
message = "Command unexecuted"
|
2021-04-07 14:22:09 -07:00
|
|
|
case ErrorCodeSSHCAConstraintViolation:
|
|
|
|
message = "SSH CA constraint violation"
|
|
|
|
case ErrorCodeInvalidOTP:
|
|
|
|
message = "Invalid OTP"
|
|
|
|
case ErrorCodeDemoMode:
|
|
|
|
message = "Demo mode"
|
|
|
|
case ErrorCodeObjectExists:
|
|
|
|
message = "Object exists"
|
2018-09-17 11:14:11 -07:00
|
|
|
default:
|
2021-04-07 14:22:09 -07:00
|
|
|
message = "Unknown"
|
2018-09-02 05:46:37 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
return fmt.Sprintf("card responded with error: %s", message)
|
|
|
|
}
|