diff --git a/README.md b/README.md index c0b1abc..2b5817e 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,10 @@ Currently the following commands are implemented: * DeriveEcdh * Echo * ChangeAuthenticationKey + * PutAuthenticationKey + * GetOpaque + * PutOpaque + * SignAttestationCertificate * Authentication & Session related commands * GetPseudoRandom diff --git a/commands/constructors.go b/commands/constructors.go index c200854..4ffccfd 100644 --- a/commands/constructors.go +++ b/commands/constructors.go @@ -151,6 +151,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, @@ -251,6 +271,43 @@ func CreateChangeAuthenticationKeyCommand(objID uint16, newPassword string) (*Co return command, nil } +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))...) + } + + command := &CommandMessage{ + CommandType: CommandTypePutOpaque, + } + + 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) + payload.Write(data) + + command.Data = payload.Bytes() + + return command, nil +} + +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 CreateGetPseudoRandomCommand(numBytes uint16) *CommandMessage { command := &CommandMessage{ CommandType: CommandTypeGetPseudoRandom, @@ -340,3 +397,22 @@ func CreatePutAuthkeyCommand(objID uint16, label []byte, domains uint16, capabil return command, nil } + +func CreatePutDerivedAuthenticationKeyCommand(objID uint16, label []byte, domains uint16, capabilities uint64, delegated uint64, password string) (*CommandMessage, error) { + authKey := authkey.NewFromPassword(password) + return CreatePutAuthkeyCommand(objID, label, domains, capabilities, delegated, authKey.GetEncKey(), authKey.GetMacKey()) +} + +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() + + return command, nil +} diff --git a/commands/response.go b/commands/response.go index e9aa55d..c2f6c60 100644 --- a/commands/response.go +++ b/commands/response.go @@ -95,6 +95,18 @@ type ( PutAuthkeyResponse struct { ObjectID uint16 } + + PutOpaqueResponse struct { + ObjectID uint16 + } + + GetOpaqueResponse struct { + Data []byte + } + + SignAttestationCertResponse struct { + Cert []byte + } ) // ParseResponse parses the binary response from the card to the relevant Response type. @@ -157,6 +169,12 @@ func ParseResponse(data []byte) (Response, error) { return parsePutWrapkeyResponse(payload) case CommandTypePutAuthKey: return parsePutAuthkeyResponse(payload) + case CommandTypePutOpaque: + return parsePutOpaqueResponse(payload) + case CommandTypeGetOpaque: + return parseGetOpaqueResponse(payload) + case CommandTypeAttestAsymmetric: + return parseAttestationCertResponse(payload) case ErrorResponseCode: return nil, parseErrorResponse(payload) default: @@ -217,6 +235,10 @@ func parseSignDataEddsaResponse(payload []byte) (Response, error) { } func parseSignDataPkcs1Response(payload []byte) (Response, error) { + if len(payload) < 1 { + return nil, errors.New("invalid response payload length") + } + return &SignDataPkcs1Response{ Signature: payload, }, nil @@ -335,9 +357,46 @@ func parsePutAuthkeyResponse(payload []byte) (Response, error) { if err != nil { return nil, err } + return &PutAuthkeyResponse{ObjectID: objectID}, nil } +func parsePutOpaqueResponse(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 &PutOpaqueResponse{ + ObjectID: objectID, + }, nil +} + +func parseGetOpaqueResponse(payload []byte) (Response, error) { + if len(payload) < 1 { + return nil, errors.New("invalid response payload length") + } + + return &GetOpaqueResponse{ + Data: payload, + }, nil +} + +func parseAttestationCertResponse(payload []byte) (Response, error) { + if len(payload) < 1 { + return nil, errors.New("invalid response payload length") + } + + return &SignAttestationCertResponse{ + Cert: payload, + }, nil +} + // Error formats a card error message into a human readable format func (e *Error) Error() string { message := "" @@ -366,12 +425,20 @@ func (e *Error) Error() string { message = "Log full" case ErrorCodeObjectNotFound: message = "Object not found" - case ErrorCodeIDIllegal: - message = "ID illegal" + case ErrorCodeInvalidID: + message = "Invalid ID" case ErrorCodeCommandUnexecuted: message = "Command unexecuted" + 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..ab76ada 100644 --- a/commands/types.go +++ b/commands/types.go @@ -64,78 +64,87 @@ 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 + ErrorCodeCommandUnexecuted ErrorCode = 0xff // 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 - 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 // here for backwards compatibility - CapabilityAsymmetricDeriveEcdh 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 + CapabilityNone uint64 = 0x0000000000000000 + CapabilityGetOpaque uint64 = 0x0000000000000001 + CapabilityPutOpaque uint64 = 0x0000000000000002 + CapabilityPutAuthenticationKey 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 // here for backwards compatibility + CapabilityAsymmetricDeriveEcdh uint64 = 0x0000000000000800 + CapabilityExportWrapped uint64 = 0x0000000000001000 + CapabilityImportWrapped uint64 = 0x0000000000002000 + CapabilityPutWrapKey uint64 = 0x0000000000004000 + CapabilityGenerateWrapKey uint64 = 0x0000000000008000 + CapabilityExportableUnderWrap 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 + CapabilityChangeAuthenticationKey uint64 = 0x0000400000000000 // Domains Domain1 uint16 = 0x0001 @@ -165,8 +174,12 @@ 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. 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