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