initial commit
This commit is contained in:
commit
372febe620
|
@ -0,0 +1,2 @@
|
||||||
|
.idea
|
||||||
|
*.iml
|
|
@ -0,0 +1,53 @@
|
||||||
|
package commands
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
CommandMessage struct {
|
||||||
|
UUID uint8
|
||||||
|
CommandType CommandType
|
||||||
|
SessionID *uint8
|
||||||
|
Data []byte
|
||||||
|
MAC []byte
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func (c *CommandMessage) BodyLength() uint16 {
|
||||||
|
length := len(c.Data)
|
||||||
|
|
||||||
|
if c.MAC != nil {
|
||||||
|
length += len(c.MAC)
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.SessionID != nil {
|
||||||
|
length += 1
|
||||||
|
}
|
||||||
|
|
||||||
|
return uint16(length)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CommandMessage) Serialize() ([]byte, error) {
|
||||||
|
buffer := new(bytes.Buffer)
|
||||||
|
|
||||||
|
// Write command type
|
||||||
|
binary.Write(buffer, binary.BigEndian, c.CommandType)
|
||||||
|
|
||||||
|
// Write length
|
||||||
|
binary.Write(buffer, binary.BigEndian, uint16(c.BodyLength()))
|
||||||
|
|
||||||
|
// Write sessionID
|
||||||
|
if c.SessionID != nil {
|
||||||
|
binary.Write(buffer, binary.BigEndian, *c.SessionID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write data
|
||||||
|
buffer.Write(c.Data)
|
||||||
|
|
||||||
|
// Write MAC
|
||||||
|
buffer.Write(c.MAC)
|
||||||
|
|
||||||
|
return buffer.Bytes(), nil
|
||||||
|
}
|
|
@ -0,0 +1,105 @@
|
||||||
|
package commands
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
func CreateCreateSessionCommand(keySetID uint16, hostChallenge []byte) (*CommandMessage, error) {
|
||||||
|
command := &CommandMessage{
|
||||||
|
CommandType: CommandTypeCreateSession,
|
||||||
|
}
|
||||||
|
|
||||||
|
payload := bytes.NewBuffer([]byte{})
|
||||||
|
binary.Write(payload, binary.BigEndian, keySetID)
|
||||||
|
payload.Write(hostChallenge)
|
||||||
|
|
||||||
|
command.Data = payload.Bytes()
|
||||||
|
|
||||||
|
return command, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func CreateAuthenticateSessionCommand(hostCryptogram []byte) (*CommandMessage, error) {
|
||||||
|
command := &CommandMessage{
|
||||||
|
CommandType: CommandTypeAuthenticateSession,
|
||||||
|
Data: hostCryptogram,
|
||||||
|
}
|
||||||
|
|
||||||
|
return command, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Authenticated
|
||||||
|
|
||||||
|
func CreateResetCommand() (*CommandMessage, error) {
|
||||||
|
command := &CommandMessage{
|
||||||
|
CommandType: CommandTypeReset,
|
||||||
|
}
|
||||||
|
|
||||||
|
return command, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func CreateGenerateAsymmetricKeyCommand(keyID uint16, label []byte, domains uint16, capabilities uint64, algorithm Algorithm) (*CommandMessage, error) {
|
||||||
|
if len(label) > LabelLength {
|
||||||
|
return nil, errors.New("label is too long")
|
||||||
|
}
|
||||||
|
if len(label) < LabelLength {
|
||||||
|
label = append(label, bytes.Repeat([]byte{0x00}, LabelLength-len(label))...)
|
||||||
|
}
|
||||||
|
|
||||||
|
command := &CommandMessage{
|
||||||
|
CommandType: CommandTypeGenerateAsymmetricKey,
|
||||||
|
}
|
||||||
|
|
||||||
|
payload := bytes.NewBuffer([]byte{})
|
||||||
|
binary.Write(payload, binary.BigEndian, keyID)
|
||||||
|
payload.Write(label)
|
||||||
|
binary.Write(payload, binary.BigEndian, domains)
|
||||||
|
binary.Write(payload, binary.BigEndian, capabilities)
|
||||||
|
binary.Write(payload, binary.BigEndian, algorithm)
|
||||||
|
|
||||||
|
command.Data = payload.Bytes()
|
||||||
|
|
||||||
|
return command, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func CreateSignDataEddsaCommand(keyID uint16, data []byte) (*CommandMessage, error) {
|
||||||
|
command := &CommandMessage{
|
||||||
|
CommandType: CommandTypeSignDataEddsa,
|
||||||
|
}
|
||||||
|
|
||||||
|
payload := bytes.NewBuffer([]byte{})
|
||||||
|
binary.Write(payload, binary.BigEndian, keyID)
|
||||||
|
payload.Write(data)
|
||||||
|
|
||||||
|
command.Data = payload.Bytes()
|
||||||
|
|
||||||
|
return command, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func CreatePutAsymmetricKeyCommand(keyID uint16, label []byte, domains uint16, capabilities uint64, algorithm Algorithm, keyPart1 []byte, keyPart2 []byte) (*CommandMessage, error) {
|
||||||
|
if len(label) > LabelLength {
|
||||||
|
return nil, errors.New("label is too long")
|
||||||
|
}
|
||||||
|
if len(label) < LabelLength {
|
||||||
|
label = append(label, bytes.Repeat([]byte{0x00}, LabelLength-len(label))...)
|
||||||
|
}
|
||||||
|
command := &CommandMessage{
|
||||||
|
CommandType: CommandTypePutAsymmetric,
|
||||||
|
}
|
||||||
|
|
||||||
|
payload := bytes.NewBuffer([]byte{})
|
||||||
|
binary.Write(payload, binary.BigEndian, keyID)
|
||||||
|
payload.Write(label)
|
||||||
|
binary.Write(payload, binary.BigEndian, domains)
|
||||||
|
binary.Write(payload, binary.BigEndian, capabilities)
|
||||||
|
binary.Write(payload, binary.BigEndian, algorithm)
|
||||||
|
payload.Write(keyPart1)
|
||||||
|
if keyPart2 != nil {
|
||||||
|
payload.Write(keyPart2)
|
||||||
|
}
|
||||||
|
|
||||||
|
command.Data = payload.Bytes()
|
||||||
|
|
||||||
|
return command, nil
|
||||||
|
}
|
|
@ -0,0 +1,187 @@
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
SignDataEddsaResponse struct {
|
||||||
|
Signature []byte
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
case CommandTypePutAsymmetric:
|
||||||
|
return parsePutAsymmetricKeyResponse(payload)
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
func parsePutAsymmetricKeyResponse(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 &PutAsymmetricKeyResponse{
|
||||||
|
KeyID: keyID,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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"
|
||||||
|
case ErrorCodeIDIllegal:
|
||||||
|
message = "ID illegal"
|
||||||
|
case ErrorCodeCommandUnexecuted:
|
||||||
|
message = "Command unexecuted"
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Sprintf("card responded with error: %s", message)
|
||||||
|
}
|
|
@ -0,0 +1,148 @@
|
||||||
|
package commands
|
||||||
|
|
||||||
|
type (
|
||||||
|
CommandType uint8
|
||||||
|
ErrorCode uint8
|
||||||
|
Algorithm uint8
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
ResponseCommandOffset = 0x80
|
||||||
|
ErrorResponseCode = 0xff
|
||||||
|
|
||||||
|
// LabelLength is the max length of a label
|
||||||
|
LabelLength = 40
|
||||||
|
|
||||||
|
CommandTypeEcho CommandType = 0x01
|
||||||
|
CommandTypeCreateSession CommandType = 0x03
|
||||||
|
CommandTypeAuthenticateSession CommandType = 0x04
|
||||||
|
CommandTypeSessionMessage CommandType = 0x05
|
||||||
|
CommandTypeDeviceInfo CommandType = 0x06
|
||||||
|
CommandTypeReset CommandType = 0x08
|
||||||
|
CommandTypeCloseSession CommandType = 0x40
|
||||||
|
CommandTypeStorageStatus CommandType = 0x41
|
||||||
|
CommandTypePutOpaque CommandType = 0x42
|
||||||
|
CommandTypeGetOpaque CommandType = 0x43
|
||||||
|
CommandTypePutAuthKey CommandType = 0x44
|
||||||
|
CommandTypePutAsymmetric CommandType = 0x45
|
||||||
|
CommandTypeGenerateAsymmetricKey CommandType = 0x46
|
||||||
|
CommandTypeSignDataPkcs1 CommandType = 0x47
|
||||||
|
CommandTypeListObjects CommandType = 0x48
|
||||||
|
CommandTypeDecryptPkcs1 CommandType = 0x49
|
||||||
|
CommandTypeExportWrapped CommandType = 0x4a
|
||||||
|
CommandTypeImportWrapped CommandType = 0x4b
|
||||||
|
CommandTypePutWrapKey CommandType = 0x4c
|
||||||
|
CommandTypeGetLogs CommandType = 0x4d
|
||||||
|
CommandTypeGetObjectInfo CommandType = 0x4e
|
||||||
|
CommandTypePutOption CommandType = 0x4f
|
||||||
|
CommandTypeGetOption CommandType = 0x50
|
||||||
|
CommandTypeGetPseudoRandom CommandType = 0x51
|
||||||
|
CommandTypePutHMACKey CommandType = 0x52
|
||||||
|
CommandTypeHMACData CommandType = 0x53
|
||||||
|
CommandTypeGetPubKey CommandType = 0x54
|
||||||
|
CommandTypeSignDataPss CommandType = 0x55
|
||||||
|
CommandTypeSignDataEcdsa CommandType = 0x56
|
||||||
|
CommandTypeDecryptEcdh CommandType = 0x57
|
||||||
|
CommandTypeDeleteObject CommandType = 0x58
|
||||||
|
CommandTypeDecryptOaep CommandType = 0x59
|
||||||
|
CommandTypeGenerateHMACKey CommandType = 0x5a
|
||||||
|
CommandTypeGenerateWrapKey CommandType = 0x5b
|
||||||
|
CommandTypeVerifyHMAC CommandType = 0x5c
|
||||||
|
CommandTypeOTPDecrypt CommandType = 0x60
|
||||||
|
CommandTypeOTPAeadCreate CommandType = 0x61
|
||||||
|
CommandTypeOTPAeadRandom CommandType = 0x62
|
||||||
|
CommandTypeOTPAeadRewrap CommandType = 0x63
|
||||||
|
CommandTypeAttestAsymmetric CommandType = 0x64
|
||||||
|
CommandTypePutOTPAeadKey CommandType = 0x65
|
||||||
|
CommandTypeGenerateOTPAeadKey CommandType = 0x66
|
||||||
|
CommandTypeSetLogIndex CommandType = 0x67
|
||||||
|
CommandTypeWrapData CommandType = 0x68
|
||||||
|
CommandTypeUnwrapData CommandType = 0x69
|
||||||
|
CommandTypeSignDataEddsa CommandType = 0x6a
|
||||||
|
CommandTypeSetBlink CommandType = 0x6b
|
||||||
|
|
||||||
|
// Errors
|
||||||
|
ErrorCodeOK ErrorCode = 0x00
|
||||||
|
ErrorCodeInvalidCommand ErrorCode = 0x01
|
||||||
|
ErrorCodeInvalidData ErrorCode = 0x02
|
||||||
|
ErrorCodeInvalidSession ErrorCode = 0x03
|
||||||
|
ErrorCodeAuthFail ErrorCode = 0x04
|
||||||
|
ErrorCodeSessionFull ErrorCode = 0x05
|
||||||
|
ErrorCodeSessionFailed ErrorCode = 0x06
|
||||||
|
ErrorCodeStorageFailed ErrorCode = 0x07
|
||||||
|
ErrorCodeWrongLength ErrorCode = 0x08
|
||||||
|
ErrorCodeInvalidPermission ErrorCode = 0x09
|
||||||
|
ErrorCodeLogFull ErrorCode = 0x0a
|
||||||
|
ErrorCodeObjectNotFound ErrorCode = 0x0b
|
||||||
|
ErrorCodeIDIllegal ErrorCode = 0x0c
|
||||||
|
ErrorCodeCommandUnexecuted ErrorCode = 0xff
|
||||||
|
|
||||||
|
// Algorithms
|
||||||
|
AlgorighmED25519 Algorithm = 46
|
||||||
|
|
||||||
|
// Capabilities
|
||||||
|
CapabilityGetOpaque uint64 = 0x0000000000000001
|
||||||
|
CapabilityPutOpaque uint64 = 0x0000000000000002
|
||||||
|
CapabilityPutAuthKey uint64 = 0x0000000000000004
|
||||||
|
CapabilityPutAsymmetric uint64 = 0x0000000000000008
|
||||||
|
CapabilityAsymmetricGen uint64 = 0x0000000000000010
|
||||||
|
CapabilityAsymmetricSignPkcs uint64 = 0x0000000000000020
|
||||||
|
CapabilityAsymmetricSignPss uint64 = 0x0000000000000040
|
||||||
|
CapabilityAsymmetricSignEcdsa uint64 = 0x0000000000000080
|
||||||
|
CapabilityAsymmetricSignEddsa uint64 = 0x0000000000000100
|
||||||
|
CapabilityAsymmetricDecryptPkcs uint64 = 0x0000000000000200
|
||||||
|
CapabilityAsymmetricDecryptOaep uint64 = 0x0000000000000400
|
||||||
|
CapabilityAsymmetricDecryptEcdh uint64 = 0x0000000000000800
|
||||||
|
CapabilityExportWrapped uint64 = 0x0000000000001000
|
||||||
|
CapabilityImportWrapped uint64 = 0x0000000000002000
|
||||||
|
CapabilityPutWrapKey uint64 = 0x0000000000004000
|
||||||
|
CapabilityGenerateWrapKey uint64 = 0x0000000000008000
|
||||||
|
CapabilityExportUnderWrap uint64 = 0x0000000000010000
|
||||||
|
CapabilityPutOption uint64 = 0x0000000000020000
|
||||||
|
CapabilityGetOption uint64 = 0x0000000000040000
|
||||||
|
CapabilityGetRandomness uint64 = 0x0000000000080000
|
||||||
|
CapabilityPutHmacKey uint64 = 0x0000000000100000
|
||||||
|
CapabilityHmacKeyGenerate uint64 = 0x0000000000200000
|
||||||
|
CapabilityHmacData uint64 = 0x0000000000400000
|
||||||
|
CapabilityHmacVerify uint64 = 0x0000000000800000
|
||||||
|
CapabilityAudit uint64 = 0x0000000001000000
|
||||||
|
CapabilitySshCertify uint64 = 0x0000000002000000
|
||||||
|
CapabilityGetTemplate uint64 = 0x0000000004000000
|
||||||
|
CapabilityPutTemplate uint64 = 0x0000000008000000
|
||||||
|
CapabilityReset uint64 = 0x0000000010000000
|
||||||
|
CapabilityOtpDecrypt uint64 = 0x0000000020000000
|
||||||
|
CapabilityOtpAeadCreate uint64 = 0x0000000040000000
|
||||||
|
CapabilityOtpAeadRandom uint64 = 0x0000000080000000
|
||||||
|
CapabilityOtpAeadRewrapFrom uint64 = 0x0000000100000000
|
||||||
|
CapabilityOtpAeadRewrapTo uint64 = 0x0000000200000000
|
||||||
|
CapabilityAttest uint64 = 0x0000000400000000
|
||||||
|
CapabilityPutOtpAeadKey uint64 = 0x0000000800000000
|
||||||
|
CapabilityGenerateOtpAeadKey uint64 = 0x0000001000000000
|
||||||
|
CapabilityWrapData uint64 = 0x0000002000000000
|
||||||
|
CapabilityUnwrapData uint64 = 0x0000004000000000
|
||||||
|
CapabilityDeleteOpaque uint64 = 0x0000008000000000
|
||||||
|
CapabilityDeleteAuthKey uint64 = 0x0000010000000000
|
||||||
|
CapabilityDeleteAsymmetric uint64 = 0x0000020000000000
|
||||||
|
CapabilityDeleteWrapKey uint64 = 0x0000040000000000
|
||||||
|
CapabilityDeleteHmacKey uint64 = 0x0000080000000000
|
||||||
|
CapabilityDeleteTemplate uint64 = 0x0000100000000000
|
||||||
|
CapabilityDeleteOtpAeadKey uint64 = 0x0000200000000000
|
||||||
|
|
||||||
|
// Domains
|
||||||
|
Domain1 uint16 = 0x0001
|
||||||
|
Domain2 uint16 = 0x0002
|
||||||
|
Domain3 uint16 = 0x0004
|
||||||
|
Domain4 uint16 = 0x0008
|
||||||
|
Domain5 uint16 = 0x0010
|
||||||
|
Domain6 uint16 = 0x0020
|
||||||
|
Domain7 uint16 = 0x0040
|
||||||
|
Domain8 uint16 = 0x0080
|
||||||
|
Domain9 uint16 = 0x0100
|
||||||
|
Domain10 uint16 = 0x0200
|
||||||
|
Domain11 uint16 = 0x0400
|
||||||
|
Domain12 uint16 = 0x0800
|
||||||
|
Domain13 uint16 = 0x1000
|
||||||
|
Domain14 uint16 = 0x2000
|
||||||
|
Domain15 uint16 = 0x4000
|
||||||
|
Domain16 uint16 = 0x8000
|
||||||
|
)
|
|
@ -0,0 +1,10 @@
|
||||||
|
package connector
|
||||||
|
|
||||||
|
import "aiakos/commands"
|
||||||
|
|
||||||
|
type (
|
||||||
|
Connector interface {
|
||||||
|
Request(command *commands.CommandMessage) ([]byte, error)
|
||||||
|
GetStatus() (*StatusResponse, error)
|
||||||
|
}
|
||||||
|
)
|
|
@ -0,0 +1,84 @@
|
||||||
|
package connector
|
||||||
|
|
||||||
|
import (
|
||||||
|
"aiakos/commands"
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
HTTPConnector struct {
|
||||||
|
URL string
|
||||||
|
}
|
||||||
|
|
||||||
|
Status string
|
||||||
|
StatusResponse struct {
|
||||||
|
Status Status
|
||||||
|
Serial string
|
||||||
|
Version string
|
||||||
|
Pid string
|
||||||
|
Address string
|
||||||
|
Port string
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewHTTPConnector(url string) *HTTPConnector {
|
||||||
|
return &HTTPConnector{
|
||||||
|
URL: url,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *HTTPConnector) Request(command *commands.CommandMessage) ([]byte, error) {
|
||||||
|
requestData, err := command.Serialize()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := http.DefaultClient.Post("http://"+c.URL+"/connector/api", "application/octet-stream", bytes.NewReader(requestData))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if res.StatusCode != http.StatusOK {
|
||||||
|
return nil, fmt.Errorf("server returned non OK status code %d", res.StatusCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := ioutil.ReadAll(res.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return data, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *HTTPConnector) GetStatus() (*StatusResponse, error) {
|
||||||
|
res, err := http.DefaultClient.Get("http://" + c.URL + "/connector/status")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := ioutil.ReadAll(res.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
bodyString := string(data)
|
||||||
|
pairs := strings.Split(bodyString, "\n")
|
||||||
|
|
||||||
|
var values []string
|
||||||
|
for _, pair := range pairs {
|
||||||
|
values = append(values, strings.Split(pair, "=")...)
|
||||||
|
}
|
||||||
|
|
||||||
|
status := &StatusResponse{}
|
||||||
|
status.Status = Status(values[1])
|
||||||
|
status.Serial = values[3]
|
||||||
|
status.Version = values[5]
|
||||||
|
status.Pid = values[7]
|
||||||
|
status.Address = values[9]
|
||||||
|
status.Port = values[11]
|
||||||
|
|
||||||
|
return status, nil
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
module aiakos
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/enceve/crypto v0.0.0-20160707101852-34d48bb93815
|
||||||
|
golang.org/x/crypto v0.0.0-20180830192347-182538f80094
|
||||||
|
)
|
|
@ -0,0 +1,4 @@
|
||||||
|
github.com/enceve/crypto v0.0.0-20160707101852-34d48bb93815 h1:D22EM5TeYZJp43hGDx6dUng8mvtyYbB9BnE3+BmJR1Q=
|
||||||
|
github.com/enceve/crypto v0.0.0-20160707101852-34d48bb93815/go.mod h1:wYFFK4LYXbX7j+76mOq7aiC/EAw2S22CrzPHqgsisPw=
|
||||||
|
golang.org/x/crypto v0.0.0-20180830192347-182538f80094 h1:rVTAlhYa4+lCfNxmAIEOGQRoD23UqP72M3+rSWVGDTg=
|
||||||
|
golang.org/x/crypto v0.0.0-20180830192347-182538f80094/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
|
@ -0,0 +1,43 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"aiakos/commands"
|
||||||
|
"aiakos/connector"
|
||||||
|
"aiakos/securechannel"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
|
||||||
|
channel, err := securechannel.NewSecureChannel(connector.NewHTTPConnector("127.0.0.1:12345"), 1, "password")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = channel.Authenticate()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd, _ := commands.CreateGenerateAsymmetricKeyCommand(2, []byte("myKey"), commands.Domain1, commands.CapabilityAsymmetricSignEddsa, commands.AlgorighmED25519)
|
||||||
|
res, err := channel.SendEncryptedCommand(cmd)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("%v\n", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("%v\n", res)
|
||||||
|
|
||||||
|
cmd, _ = commands.CreateSignDataEddsaCommand(2, []byte("my test message"))
|
||||||
|
res, err = channel.SendEncryptedCommand(cmd)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("%v\n", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("signature: %v\n", res)
|
||||||
|
|
||||||
|
cmd, _ = commands.CreateResetCommand()
|
||||||
|
_, err = channel.SendEncryptedCommand(cmd)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
package securechannel
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/sha256"
|
||||||
|
"golang.org/x/crypto/pbkdf2"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
// AuthKey is a key to authenticate with the HSM
|
||||||
|
AuthKey []byte
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
authKeyLength = 32
|
||||||
|
authKeyIterations = 10000
|
||||||
|
yubicoSeed = "Yubico"
|
||||||
|
)
|
||||||
|
|
||||||
|
// deriveAuthKeyFromPwd derives an AuthKey using pkdf2 as specified in the HSM documentation
|
||||||
|
func deriveAuthKeyFromPwd(password string) AuthKey {
|
||||||
|
return pbkdf2.Key([]byte(password), []byte(yubicoSeed), authKeyIterations, authKeyLength, sha256.New)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetEncKey returns the EncryptionKey part of the AuthKey
|
||||||
|
func (k AuthKey) GetEncKey() []byte {
|
||||||
|
return k[:KeyLength]
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetEncKey returns the MACKey part of the AuthKey
|
||||||
|
func (k AuthKey) GetMacKey() []byte {
|
||||||
|
return k[KeyLength:]
|
||||||
|
}
|
|
@ -0,0 +1,372 @@
|
||||||
|
package securechannel
|
||||||
|
|
||||||
|
import (
|
||||||
|
"aiakos/commands"
|
||||||
|
"aiakos/connector"
|
||||||
|
"bytes"
|
||||||
|
"crypto/aes"
|
||||||
|
"crypto/cipher"
|
||||||
|
"crypto/rand"
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
"github.com/enceve/crypto/cmac"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
// SecureChannel implements a communication channel with a YubiHSM2 as specified in the SCP03 standard
|
||||||
|
SecureChannel struct {
|
||||||
|
// connector is used to communicate with the card
|
||||||
|
connector connector.Connector
|
||||||
|
// authKeySlot is the slot of the used authKey on the HSM
|
||||||
|
authKeySlot uint16
|
||||||
|
// keyChain holds the keys generated in the authentication ceremony
|
||||||
|
keyChain *KeyChain
|
||||||
|
|
||||||
|
// ID is the ID of the session with the HSM
|
||||||
|
ID uint8
|
||||||
|
// Counter of commands performed on the session
|
||||||
|
Counter uint32
|
||||||
|
// SecurityLevel is the authentication state of the session
|
||||||
|
SecurityLevel SecurityLevel
|
||||||
|
|
||||||
|
// HostChallenge is the auth challenge of the host
|
||||||
|
HostChallenge []byte
|
||||||
|
// DeviceChallenge is the auth challenge of the device
|
||||||
|
DeviceChallenge []byte
|
||||||
|
|
||||||
|
// AuthKey to authenticate against the HSM; must match authKeySlot
|
||||||
|
AuthKey AuthKey
|
||||||
|
|
||||||
|
// MACChainValue is the last MAC to allow MAC chaining
|
||||||
|
MACChainValue []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// KeyDerivationConstant used to derive keys using KDF
|
||||||
|
KeyDerivationConstant byte
|
||||||
|
|
||||||
|
// SecurityLevel indicates an auth state of a session/channel
|
||||||
|
SecurityLevel byte
|
||||||
|
|
||||||
|
// KeyChain holds session keys
|
||||||
|
KeyChain struct {
|
||||||
|
EncKey []byte
|
||||||
|
MACKey []byte
|
||||||
|
RMACKey []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// MessageType indicates whether a message is a command or response
|
||||||
|
MessageType byte
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
MACLength = 8
|
||||||
|
ChallengeLength = 8
|
||||||
|
CryptogramLength = 8
|
||||||
|
KeyLength = 16
|
||||||
|
|
||||||
|
DerivationConstantEncKey KeyDerivationConstant = 0x04
|
||||||
|
DerivationConstantMACKey KeyDerivationConstant = 0x06
|
||||||
|
DerivationConstantRMACKey KeyDerivationConstant = 0x07
|
||||||
|
|
||||||
|
DerivationConstantDeviceCryptogram KeyDerivationConstant = 0x00
|
||||||
|
DerivationConstantHostCryptogram KeyDerivationConstant = 0x01
|
||||||
|
|
||||||
|
SecurityLevelUnauthenticated SecurityLevel = 0
|
||||||
|
SecurityLevelAuthenticated SecurityLevel = 1
|
||||||
|
|
||||||
|
MessageTypeCommand MessageType = 0
|
||||||
|
MessageTypeResponse MessageType = 1
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewSecureChannel initiates a new secure channel to communicate with an HSM using the given authKey
|
||||||
|
// Call Authenticate next to establish a session.
|
||||||
|
func NewSecureChannel(connector connector.Connector, authKeySlot uint16, password string) (*SecureChannel, error) {
|
||||||
|
channel := &SecureChannel{
|
||||||
|
ID: 0,
|
||||||
|
AuthKey: deriveAuthKeyFromPwd(password),
|
||||||
|
MACChainValue: make([]byte, 16),
|
||||||
|
SecurityLevel: SecurityLevelUnauthenticated,
|
||||||
|
authKeySlot: authKeySlot,
|
||||||
|
connector: connector,
|
||||||
|
}
|
||||||
|
|
||||||
|
hostChallenge := make([]byte, 8)
|
||||||
|
_, err := rand.Read(hostChallenge)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
channel.HostChallenge = hostChallenge
|
||||||
|
|
||||||
|
return channel, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Authenticate establishes an authenticated session with the HSM
|
||||||
|
func (s *SecureChannel) Authenticate() error {
|
||||||
|
|
||||||
|
command, _ := commands.CreateCreateSessionCommand(s.authKeySlot, s.HostChallenge)
|
||||||
|
response, err := s.SendCommand(command)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
createSessionResp, match := response.(*commands.CreateSessionResponse)
|
||||||
|
if !match {
|
||||||
|
return errors.New("invalid response type")
|
||||||
|
}
|
||||||
|
|
||||||
|
s.ID = createSessionResp.SessionID
|
||||||
|
s.DeviceChallenge = createSessionResp.CardChallenge
|
||||||
|
|
||||||
|
// Update keychain
|
||||||
|
err = s.updateKeychain()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate device cryptogram
|
||||||
|
deviceCryptogram, err := s.deriveKDF(s.keyChain.MACKey, DerivationConstantDeviceCryptogram, CryptogramLength)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !bytes.Equal(deviceCryptogram, createSessionResp.CardCryptogram) {
|
||||||
|
return errors.New("authentication failed: device sent wrong cryptogram")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create host cryptogram
|
||||||
|
hostCryptogram, err := s.deriveKDF(s.keyChain.MACKey, DerivationConstantHostCryptogram, CryptogramLength)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Authenticate session
|
||||||
|
authenticateCommand, err := commands.CreateAuthenticateSessionCommand(hostCryptogram)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = s.SendMACCommand(authenticateCommand)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set counter to 1 as specified by the protocol
|
||||||
|
s.Counter = 1
|
||||||
|
|
||||||
|
s.SecurityLevel = SecurityLevelAuthenticated
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SendMACCommand sends a MAC authenticated command to the HSM and returns a parsed response
|
||||||
|
func (s *SecureChannel) SendMACCommand(c *commands.CommandMessage) (commands.Response, error) {
|
||||||
|
|
||||||
|
// Set command sessionID to this session
|
||||||
|
c.SessionID = &s.ID
|
||||||
|
|
||||||
|
// Calculate MAC for the command
|
||||||
|
sum, err := s.calculateMAC(c, MessageTypeCommand)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update chain value
|
||||||
|
s.MACChainValue = sum
|
||||||
|
|
||||||
|
// Set command MAC to calculated mac
|
||||||
|
c.MAC = sum[:MACLength]
|
||||||
|
|
||||||
|
return s.SendCommand(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SendCommand sends an unauthenticated command to the HSM and returns the parsed response
|
||||||
|
func (s *SecureChannel) SendCommand(c *commands.CommandMessage) (commands.Response, error) {
|
||||||
|
resp, err := s.connector.Request(c)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return commands.ParseResponse(resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SendEncryptedCommand sends an encrypted & authenticated command to the HSM
|
||||||
|
// and returns the decrypted and parsed response.
|
||||||
|
func (s *SecureChannel) SendEncryptedCommand(c *commands.CommandMessage) (commands.Response, error) {
|
||||||
|
|
||||||
|
// Create the cipher using the session encryption key
|
||||||
|
block, err := aes.NewCipher(s.keyChain.EncKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pad the counter by 12 bytes
|
||||||
|
icv := new(bytes.Buffer)
|
||||||
|
icv.Write(bytes.Repeat([]byte{0}, 12))
|
||||||
|
binary.Write(icv, binary.BigEndian, s.Counter)
|
||||||
|
|
||||||
|
// Encrypt the padded counter to generate the IV
|
||||||
|
iv := make([]byte, KeyLength)
|
||||||
|
block.Encrypt(iv, icv.Bytes())
|
||||||
|
|
||||||
|
// Setup the CBC encrypter
|
||||||
|
encrypter := cipher.NewCBCEncrypter(block, iv)
|
||||||
|
|
||||||
|
// Serialize and encrypt the wrapped command
|
||||||
|
commandData, _ := c.Serialize()
|
||||||
|
encryptedCommand := make([]byte, len(pad(commandData)))
|
||||||
|
encrypter.CryptBlocks(encryptedCommand, pad(commandData))
|
||||||
|
|
||||||
|
// Send the wrapped command in a SessionMessage
|
||||||
|
resp, err := s.SendMACCommand(&commands.CommandMessage{
|
||||||
|
CommandType: commands.CommandTypeSessionMessage,
|
||||||
|
Data: encryptedCommand,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cast and check the response
|
||||||
|
sessionMessage, match := resp.(*commands.SessionMessageResponse)
|
||||||
|
if !match {
|
||||||
|
return nil, errors.New("invalid response type")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify MAC
|
||||||
|
expectedMac, err := s.calculateMAC(&commands.CommandMessage{
|
||||||
|
CommandType: commands.CommandTypeSessionMessage + commands.ResponseCommandOffset,
|
||||||
|
SessionID: &sessionMessage.SessionID,
|
||||||
|
Data: sessionMessage.EncryptedData,
|
||||||
|
}, MessageTypeResponse)
|
||||||
|
|
||||||
|
if !bytes.Equal(expectedMac[:MACLength], sessionMessage.MAC) {
|
||||||
|
return nil, errors.New("invalid response MAC")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update session state
|
||||||
|
s.Counter++
|
||||||
|
|
||||||
|
// Init the CBC decrypter
|
||||||
|
decrypter := cipher.NewCBCDecrypter(block, iv)
|
||||||
|
|
||||||
|
// Decrypt the wrapped response
|
||||||
|
decryptedResponse := make([]byte, len(sessionMessage.EncryptedData))
|
||||||
|
decrypter.CryptBlocks(decryptedResponse, sessionMessage.EncryptedData)
|
||||||
|
|
||||||
|
// Parse and return the wrapped response
|
||||||
|
return commands.ParseResponse(unpad(decryptedResponse))
|
||||||
|
}
|
||||||
|
|
||||||
|
// calculateMAC calculates the authenticated MAC for a command or response.
|
||||||
|
// This is stateful since it uses the MACChainValue.
|
||||||
|
func (s *SecureChannel) calculateMAC(c *commands.CommandMessage, messageType MessageType) ([]byte, error) {
|
||||||
|
|
||||||
|
// Select the right key
|
||||||
|
var key []byte
|
||||||
|
switch messageType {
|
||||||
|
case MessageTypeCommand:
|
||||||
|
key = s.keyChain.MACKey
|
||||||
|
case MessageTypeResponse:
|
||||||
|
key = s.keyChain.RMACKey
|
||||||
|
default:
|
||||||
|
return nil, errors.New("invalid messageType")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setup CMAC using aes
|
||||||
|
block, err := aes.NewCipher(key)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
mac, err := cmac.New(block)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setup a buffer for the cmac data
|
||||||
|
buffer := new(bytes.Buffer)
|
||||||
|
|
||||||
|
// Write the MacChainValue
|
||||||
|
buffer.Write(s.MACChainValue)
|
||||||
|
|
||||||
|
// Write command type
|
||||||
|
binary.Write(buffer, binary.BigEndian, c.CommandType)
|
||||||
|
|
||||||
|
// Write length
|
||||||
|
binary.Write(buffer, binary.BigEndian, uint16(1+len(c.Data)+MACLength))
|
||||||
|
|
||||||
|
// Write sessionID
|
||||||
|
binary.Write(buffer, binary.BigEndian, c.SessionID)
|
||||||
|
|
||||||
|
// Write data
|
||||||
|
buffer.Write(c.Data)
|
||||||
|
|
||||||
|
// Write buffer to MAC
|
||||||
|
mac.Write(buffer.Bytes())
|
||||||
|
|
||||||
|
return mac.Sum([]byte{}), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// updateKeychain derives and stores the session keys.
|
||||||
|
func (s *SecureChannel) updateKeychain() error {
|
||||||
|
keyChain := &KeyChain{}
|
||||||
|
|
||||||
|
encKey, err := s.deriveKDF(s.AuthKey.GetEncKey(), DerivationConstantEncKey, KeyLength)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
keyChain.EncKey = encKey
|
||||||
|
|
||||||
|
macKey, err := s.deriveKDF(s.AuthKey.GetMacKey(), DerivationConstantMACKey, KeyLength)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
keyChain.MACKey = macKey
|
||||||
|
|
||||||
|
rmacKey, err := s.deriveKDF(s.AuthKey.GetMacKey(), DerivationConstantRMACKey, KeyLength)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
keyChain.RMACKey = rmacKey
|
||||||
|
|
||||||
|
s.keyChain = keyChain
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// deriveKDF derives a key using SCP03's KDF.
|
||||||
|
// derivationConstant and keyLen define which key to derive.
|
||||||
|
func (s *SecureChannel) deriveKDF(key []byte, derivationConstant KeyDerivationConstant, keyLen uint8) ([]byte, error) {
|
||||||
|
if len(key) != KeyLength {
|
||||||
|
return nil, errors.New("invalid macKey length; should be 16")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(s.HostChallenge) != ChallengeLength {
|
||||||
|
return nil, errors.New("invalid HostChallenge length; should be 8")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(s.DeviceChallenge) != ChallengeLength {
|
||||||
|
return nil, errors.New("invalid DeviceChallenge length; should be 8")
|
||||||
|
}
|
||||||
|
|
||||||
|
derivationData := new(bytes.Buffer)
|
||||||
|
derivationData.Write([]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, byte(derivationConstant)})
|
||||||
|
|
||||||
|
derivationData.WriteByte(0x00)
|
||||||
|
|
||||||
|
binary.Write(derivationData, binary.BigEndian, uint16(keyLen*8))
|
||||||
|
|
||||||
|
derivationData.WriteByte(0x01)
|
||||||
|
derivationData.Write(s.HostChallenge)
|
||||||
|
derivationData.Write(s.DeviceChallenge)
|
||||||
|
|
||||||
|
block, err := aes.NewCipher(key)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
mac, err := cmac.New(block)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
mac.Write(derivationData.Bytes())
|
||||||
|
kdf := mac.Sum([]byte{})
|
||||||
|
|
||||||
|
return kdf[:keyLen], nil
|
||||||
|
}
|
|
@ -0,0 +1,40 @@
|
||||||
|
package securechannel
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/aes"
|
||||||
|
)
|
||||||
|
|
||||||
|
// pad adds a padding to src until using the mechanism specified in SCP03 until it has a len that is a multiple of
|
||||||
|
// aes.BlockSize and returns the result
|
||||||
|
func pad(src []byte) []byte {
|
||||||
|
if aes.BlockSize-len(src)%aes.BlockSize == 0 {
|
||||||
|
return src
|
||||||
|
}
|
||||||
|
|
||||||
|
padding := aes.BlockSize - len(src)%aes.BlockSize - 1
|
||||||
|
padtext := bytes.Repeat([]byte{0}, padding)
|
||||||
|
padtext = append([]byte{0x80}, padtext...)
|
||||||
|
return append(src, padtext...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// unpad removes the padding from src using the mechanism specified in SCP03 and returns the result
|
||||||
|
func unpad(src []byte) []byte {
|
||||||
|
if src[len(src)-1] != 0x00 && src[len(src)-1] != 0x80 {
|
||||||
|
return src
|
||||||
|
}
|
||||||
|
|
||||||
|
padLen := 0
|
||||||
|
for i := len(src) - 1; i >= 0; i-- {
|
||||||
|
if src[i] == 0x00 {
|
||||||
|
padLen++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if src[i] == 0x80 {
|
||||||
|
padLen++
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return src[:len(src)-padLen]
|
||||||
|
}
|
Loading…
Reference in New Issue