diff --git a/README.md b/README.md index c0b1abc..638373c 100644 --- a/README.md +++ b/README.md @@ -9,14 +9,17 @@ Currently the following commands are implemented: * Reset * GenerateAsymmetricKey * SignDataEddsa - * SignDataPkcs1 * PutAsymmetricKey * GetPubKey * DeriveEcdh * Echo * ChangeAuthenticationKey + * PutAuthenticationKey + * GetOpaque + * PutOpaque + * SignDataPkcs1 + * SignAttestationCertificate * Authentication & Session related commands - * GetPseudoRandom Implementing new commands is really easy. Please consult `commands/constructors.go` and `commands/response.go` for reference. diff --git a/commands/constructors.go b/commands/constructors.go index c200854..d269f9d 100644 --- a/commands/constructors.go +++ b/commands/constructors.go @@ -94,20 +94,6 @@ func CreateSignDataEcdsaCommand(keyID uint16, data []byte) (*CommandMessage, err return command, nil } -func CreateSignDataPkcs1Command(keyID uint16, data []byte) (*CommandMessage, error) { - command := &CommandMessage{ - CommandType: CommandTypeSignDataPkcs1, - } - - 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") @@ -151,6 +137,26 @@ func NewIDOption(id uint16) ListCommandOption { } } +func NewDomainOption(domain uint16) ListCommandOption { + return func(w io.Writer) { + binary.Write(w, binary.BigEndian, ListObjectParamDomains) + binary.Write(w, binary.BigEndian, domain) + } +} + +func NewLabelOption(label []byte) (ListCommandOption, 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))...) + } + return func(w io.Writer) { + binary.Write(w, binary.BigEndian, ListObjectParamLabel) + binary.Write(w, binary.BigEndian, label) + }, nil +} + func CreateListObjectsCommand(options ...ListCommandOption) (*CommandMessage, error) { command := &CommandMessage{ CommandType: CommandTypeListObjects, @@ -235,6 +241,33 @@ func CreateDeriveEcdhCommand(objID uint16, pubkey []byte) (*CommandMessage, erro return command, nil } +func CreatePutAuthenticationKeyCommand(objID uint16, label []byte, domains uint16, capabilities uint64, delegated uint64, password string) (*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: CommandTypePutAuthKey, + } + + authKey := authkey.NewFromPassword(password) + payload := bytes.NewBuffer([]byte{}) + binary.Write(payload, binary.BigEndian, objID) + payload.Write(label) + binary.Write(payload, binary.BigEndian, domains) + binary.Write(payload, binary.BigEndian, capabilities) + binary.Write(payload, binary.BigEndian, AlgorithmYubicoAESAuthentication) + binary.Write(payload, binary.BigEndian, delegated) + payload.Write(authKey.GetEncKey()) + payload.Write(authKey.GetMacKey()) + command.Data = payload.Bytes() + + return command, nil +} + func CreateChangeAuthenticationKeyCommand(objID uint16, newPassword string) (*CommandMessage, error) { command := &CommandMessage{ CommandType: CommandTypeChangeAuthenticationKey, @@ -251,90 +284,65 @@ func CreateChangeAuthenticationKeyCommand(objID uint16, newPassword string) (*Co return command, nil } -func CreateGetPseudoRandomCommand(numBytes uint16) *CommandMessage { - command := &CommandMessage{ - CommandType: CommandTypeGetPseudoRandom, - } - - payload := bytes.NewBuffer([]byte{}) - binary.Write(payload, binary.BigEndian, numBytes) - command.Data = payload.Bytes() - - return command -} - -func CreatePutWrapkeyCommand(objID uint16, label []byte, domains uint16, capabilities uint64, algorithm Algorithm, delegated uint64, wrapkey []byte) (*CommandMessage, error) { +func CreatePutOpaqueCommand(objID uint16, label []byte, domains uint16, capabilities uint64, algorithm Algorithm, data []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))...) } - switch algorithm { - case AlgorithmAES128CCMWrap: - if keyLen := len(wrapkey); keyLen != 16 { - return nil, errors.New("wrapkey is wrong length") - } - case AlgorithmAES192CCMWrap: - if keyLen := len(wrapkey); keyLen != 24 { - return nil, errors.New("wrapkey is wrong length") - } - case AlgorithmAES256CCMWrap: - if keyLen := len(wrapkey); keyLen != 32 { - return nil, errors.New("wrapkey is wrong length") - } - default: - return nil, errors.New("invalid algorithm") - } command := &CommandMessage{ - CommandType: CommandTypePutWrapKey, + CommandType: CommandTypePutOpaque, } - payload := bytes.NewBuffer([]byte{}) + payload := bytes.NewBuffer(nil) binary.Write(payload, binary.BigEndian, objID) payload.Write(label) binary.Write(payload, binary.BigEndian, domains) binary.Write(payload, binary.BigEndian, capabilities) binary.Write(payload, binary.BigEndian, algorithm) - binary.Write(payload, binary.BigEndian, delegated) - payload.Write(wrapkey) + payload.Write(data) command.Data = payload.Bytes() return command, nil } -func CreatePutAuthkeyCommand(objID uint16, label []byte, domains uint16, capabilities, delegated uint64, encKey, macKey []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))...) - } - algorithm := AlgorithmYubicoAESAuthentication - // TODO: support P256 Authentication when it is released - // https://github.com/Yubico/yubihsm-shell/blob/1c8e254603e72f3f39cf1c3910996dbfcdba2b12/lib/yubihsm.c#L3110 - if len(encKey) != 16 { - return nil, errors.New("invalid encryption key length") - } - if len(macKey) != 16 { - return nil, errors.New("invalid mac key length") +func CreateGetOpaqueCommand(objID uint16) (*CommandMessage, error) { + command := &CommandMessage{ + CommandType: CommandTypeGetOpaque, } + payload := bytes.NewBuffer(nil) + binary.Write(payload, binary.BigEndian, objID) + command.Data = payload.Bytes() + + return command, nil +} + +func CreateSignDataPkcs1Command(objID uint16, data []byte) (*CommandMessage, error) { command := &CommandMessage{ - CommandType: CommandTypePutAuthKey, + CommandType: CommandTypeSignDataPkcs1, } payload := bytes.NewBuffer([]byte{}) binary.Write(payload, binary.BigEndian, objID) - payload.Write(label) - binary.Write(payload, binary.BigEndian, domains) - binary.Write(payload, binary.BigEndian, capabilities) - binary.Write(payload, binary.BigEndian, algorithm) - binary.Write(payload, binary.BigEndian, delegated) - payload.Write(encKey) - payload.Write(macKey) + payload.Write(data) + + command.Data = payload.Bytes() + + return command, nil +} + +func CreateSignAttestationCertCommand(keyObjID, attestationObjID uint16) (*CommandMessage, error) { + command := &CommandMessage{ + CommandType: CommandTypeAttestAsymmetric, + } + + payload := bytes.NewBuffer([]byte{}) + binary.Write(payload, binary.BigEndian, keyObjID) + binary.Write(payload, binary.BigEndian, attestationObjID) command.Data = payload.Bytes() diff --git a/commands/response.go b/commands/response.go index e9aa55d..c8d5b73 100644 --- a/commands/response.go +++ b/commands/response.go @@ -62,10 +62,6 @@ type ( Signature []byte } - SignDataPkcs1Response struct { - Signature []byte - } - SignDataEcdsaResponse struct { Signature []byte } @@ -84,16 +80,28 @@ type ( XCoordinate []byte } + PutAuthenticationKeyResponse struct { + ObjectID uint16 + } + ChangeAuthenticationKeyResponse struct { ObjectID uint16 } - PutWrapkeyResponse struct { + PutOpaqueResponse struct { ObjectID uint16 } - PutAuthkeyResponse struct { - ObjectID uint16 + GetOpaqueResponse struct { + Data []byte + } + + SignDataPkcs1Response struct { + Signature []byte + } + + SignAttestationCertResponse struct { + Cert []byte } ) @@ -131,8 +139,6 @@ func ParseResponse(data []byte) (Response, error) { return parseSignDataEddsaResponse(payload) case CommandTypeSignDataEcdsa: return parseSignDataEcdsaResponse(payload) - case CommandTypeSignDataPkcs1: - return parseSignDataPkcs1Response(payload) case CommandTypePutAsymmetric: return parsePutAsymmetricKeyResponse(payload) case CommandTypeListObjects: @@ -149,14 +155,18 @@ func ParseResponse(data []byte) (Response, error) { return parseEchoResponse(payload) case CommandTypeDeriveEcdh: return parseDeriveEcdhResponse(payload) + case CommandTypePutAuthKey: + return parsePutAuthenticationKeyResponse(payload) case CommandTypeChangeAuthenticationKey: return parseChangeAuthenticationKeyResponse(payload) - case CommandTypeGetPseudoRandom: - return parseGetPseudoRandomResponse(payload), nil - case CommandTypePutWrapKey: - return parsePutWrapkeyResponse(payload) - case CommandTypePutAuthKey: - return parsePutAuthkeyResponse(payload) + case CommandTypePutOpaque: + return parsePutOpaqueResponse(payload) + case CommandTypeGetOpaque: + return parseGetOpaqueResponse(payload) + case CommandTypeSignDataPkcs1: + return parseSignDataPkcs1Response(payload) + case CommandTypeAttestAsymmetric: + return parseAttestationCertResponse(payload) case ErrorResponseCode: return nil, parseErrorResponse(payload) default: @@ -216,12 +226,6 @@ func parseSignDataEddsaResponse(payload []byte) (Response, error) { }, nil } -func parseSignDataPkcs1Response(payload []byte) (Response, error) { - return &SignDataPkcs1Response{ - Signature: payload, - }, nil -} - func parseSignDataEcdsaResponse(payload []byte) (Response, error) { return &SignDataEcdsaResponse{ Signature: payload, @@ -294,6 +298,20 @@ func parseDeriveEcdhResponse(payload []byte) (Response, error) { }, nil } +func parsePutAuthenticationKeyResponse(payload []byte) (Response, error) { + 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 + } + + return &PutAuthenticationKeyResponse{ObjectID: objectID}, nil +} + func parseChangeAuthenticationKeyResponse(payload []byte) (Response, error) { if len(payload) != 2 { return nil, errors.New("invalid response payload length") @@ -308,11 +326,7 @@ func parseChangeAuthenticationKeyResponse(payload []byte) (Response, error) { return &ChangeAuthenticationKeyResponse{ObjectID: objectID}, nil } -func parseGetPseudoRandomResponse(payload []byte) Response { - return payload -} - -func parsePutWrapkeyResponse(payload []byte) (Response, error) { +func parsePutOpaqueResponse(payload []byte) (Response, error) { if len(payload) != 2 { return nil, errors.New("invalid response payload length") } @@ -322,20 +336,40 @@ func parsePutWrapkeyResponse(payload []byte) (Response, error) { if err != nil { return nil, err } - return &PutWrapkeyResponse{ObjectID: objectID}, nil + + return &PutOpaqueResponse{ + ObjectID: objectID, + }, nil } -func parsePutAuthkeyResponse(payload []byte) (Response, error) { - if len(payload) != 2 { +func parseGetOpaqueResponse(payload []byte) (Response, error) { + if len(payload) < 1 { 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 + return &GetOpaqueResponse{ + Data: payload, + }, nil +} + +func parseSignDataPkcs1Response(payload []byte) (Response, error) { + if len(payload) < 1 { + return nil, errors.New("invalid response payload length") } - return &PutAuthkeyResponse{ObjectID: objectID}, nil + + return &SignDataPkcs1Response{ + Signature: 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 } // Error formats a card error message into a human readable format @@ -366,12 +400,18 @@ func (e *Error) Error() string { message = "Log full" case ErrorCodeObjectNotFound: message = "Object not found" - case ErrorCodeIDIllegal: - message = "ID illegal" - case ErrorCodeCommandUnexecuted: - message = "Command unexecuted" + case ErrorCodeInvalidID: + message = "Invalid ID" + case ErrorCodeSSHCAConstraintViolation: + message = "SSH CA constraint violation" + case ErrorCodeInvalidOTP: + message = "Invalid OTP" + case ErrorCodeDemoMode: + message = "Demo mode" + case ErrorCodeObjectExists: + message = "Object exists" default: - message = "unknown" + message = "Unknown" } return fmt.Sprintf("card responded with error: %s", message) diff --git a/commands/types.go b/commands/types.go index 7812a70..f65aac3 100644 --- a/commands/types.go +++ b/commands/types.go @@ -64,31 +64,35 @@ const ( CommandTypeChangeAuthenticationKey CommandType = 0x6c // 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 + 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 + ErrorCodeInvalidID ErrorCode = 0x0c + ErrorCodeSSHCAConstraintViolation ErrorCode = 0x0e + ErrorCodeInvalidOTP ErrorCode = 0x0f + ErrorCodeDemoMode ErrorCode = 0x10 + ErrorCodeObjectExists ErrorCode = 0x11 // Algorithms + AlgorithmRSA2048 Algorithm = 9 AlgorithmP256 Algorithm = 12 AlgorithmSecp256k1 Algorithm = 15 + AlgorithmOpaqueData Algorithm = 30 + AlgorithmOpaqueX509Certificate Algorithm = 31 AlgorithmYubicoAESAuthentication Algorithm = 38 - AlgorighmED25519 Algorithm = 46 - AlgorithmAES128CCMWrap Algorithm = 29 - AlgorithmAES192CCMWrap Algorithm = 41 - AlgorithmAES256CCMWrap Algorithm = 42 + AlgorithmED25519 Algorithm = 46 // Capabilities + CapabilityNone uint64 = 0x0000000000000000 CapabilityGetOpaque uint64 = 0x0000000000000001 CapabilityPutOpaque uint64 = 0x0000000000000002 CapabilityPutAuthKey uint64 = 0x0000000000000004 @@ -106,7 +110,7 @@ const ( CapabilityImportWrapped uint64 = 0x0000000000002000 CapabilityPutWrapKey uint64 = 0x0000000000004000 CapabilityGenerateWrapKey uint64 = 0x0000000000008000 - CapabilityExportUnderWrap uint64 = 0x0000000000010000 + CapabilityExportableUnderWrap uint64 = 0x0000000000010000 CapabilityPutOption uint64 = 0x0000000000020000 CapabilityGetOption uint64 = 0x0000000000040000 CapabilityGetRandomness uint64 = 0x0000000000080000 @@ -136,6 +140,7 @@ const ( CapabilityDeleteHmacKey uint64 = 0x0000080000000000 CapabilityDeleteTemplate uint64 = 0x0000100000000000 CapabilityDeleteOtpAeadKey uint64 = 0x0000200000000000 + CapabilityChangeAuthKey uint64 = 0x0000400000000000 // Domains Domain1 uint16 = 0x0001 @@ -165,15 +170,10 @@ const ( ObjectTypeOtpAeadKey uint8 = 0x07 // list objects params - ListObjectParamID uint8 = 0x01 - ListObjectParamType uint8 = 0x02 + ListObjectParamID uint8 = 0x01 + ListObjectParamType uint8 = 0x02 + ListObjectParamDomains uint8 = 0x03 + ListObjectParamCapabilities uint8 = 0x04 + ListObjectParamAlgorithm uint8 = 0x05 + ListObjectParamLabel uint8 = 0x06 ) - -// CapabilityPrimitiveFromSlice OR's all the capabilitites together. -func CapabilityPrimitiveFromSlice(capabilitites []uint64) uint64 { - var primitive uint64 - for _, c := range capabilitites { - primitive |= c - } - return primitive -} diff --git a/go.mod b/go.mod index ffc49e9..5485272 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,8 @@ module github.com/certusone/yubihsm-go +go 1.14 + require ( github.com/enceve/crypto v0.0.0-20160707101852-34d48bb93815 - golang.org/x/crypto v0.0.0-20180830192347-182538f80094 + golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 ) diff --git a/go.sum b/go.sum index d019521..7b4a9de 100644 --- a/go.sum +++ b/go.sum @@ -2,3 +2,10 @@ github.com/enceve/crypto v0.0.0-20160707101852-34d48bb93815 h1:D22EM5TeYZJp43hGD 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= +golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 h1:It14KIkyBFYkHkwZ7k45minvA9aorojkyjGk9KJ5B/w= +golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= diff --git a/securechannel/channel.go b/securechannel/channel.go index 9babf43..ff9d1bf 100644 --- a/securechannel/channel.go +++ b/securechannel/channel.go @@ -85,6 +85,8 @@ const ( MaxMessagesPerSession = 10000 ) +var ErrAuthCryptogram = errors.New("authentication failed: device sent wrong cryptogram") + // 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) { @@ -143,7 +145,7 @@ func (s *SecureChannel) Authenticate() error { } if !bytes.Equal(deviceCryptogram, createSessionResp.CardCryptogram) { - return errors.New("authentication failed: device sent wrong cryptogram") + return ErrAuthCryptogram } // Create host cryptogram