diff --git a/go.mod b/go.mod index 74063e5..f59dfe2 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/google/go-attestation go 1.12 require ( + github.com/golang/protobuf v1.3.1 github.com/google/certificate-transparency-go v1.0.22-0.20190403155334-84853901c6b8 github.com/google/go-tpm v0.1.2-0.20190430183152-dcb1ada1f875 github.com/google/go-tpm-tools v0.0.0-20190328013357-5d2fd7f4b3e5 diff --git a/go.sum b/go.sum index c74bc7b..59d2ae4 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,5 @@ +github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/google/certificate-transparency-go v1.0.21 h1:Yf1aXowfZ2nuboBsg7iYGLmwsOARdV86pfH3g95wXmE= github.com/google/certificate-transparency-go v1.0.21/go.mod h1:QeJfpSbVSfYc7RgB3gJFj9cbuQMMchQxrWXz8Ruopmg= github.com/google/certificate-transparency-go v1.0.22-0.20190403155334-84853901c6b8 h1:pZtGL2P6rU7wOnemTcvTgoH9s+QB646LB5dBcZ1w5yE= diff --git a/proto/tpm.pb.go b/proto/tpm.pb.go new file mode 100644 index 0000000..814b8f3 --- /dev/null +++ b/proto/tpm.pb.go @@ -0,0 +1,746 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// source: tpm.proto + +package go_attestation_proto + +import ( + fmt "fmt" + proto "github.com/golang/protobuf/proto" + math "math" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package + +type TpmVersion int32 + +const ( + TpmVersion_TPM_VERSION_UNSPECIFIED TpmVersion = 0 + TpmVersion_TPM_12 TpmVersion = 1 + TpmVersion_TPM_20 TpmVersion = 2 +) + +var TpmVersion_name = map[int32]string{ + 0: "TPM_VERSION_UNSPECIFIED", + 1: "TPM_12", + 2: "TPM_20", +} + +var TpmVersion_value = map[string]int32{ + "TPM_VERSION_UNSPECIFIED": 0, + "TPM_12": 1, + "TPM_20": 2, +} + +func (x TpmVersion) String() string { + return proto.EnumName(TpmVersion_name, int32(x)) +} + +func (TpmVersion) EnumDescriptor() ([]byte, []int) { + return fileDescriptor_63ac7bc02f9d1279, []int{0} +} + +type TpmInterface int32 + +const ( + TpmInterface_TPM_INTERFACE_UNSPECIFIED TpmInterface = 0 + TpmInterface_DIRECT TpmInterface = 1 + TpmInterface_KERNEL_MANAGED TpmInterface = 2 + TpmInterface_DAEMON_MANAGED TpmInterface = 3 +) + +var TpmInterface_name = map[int32]string{ + 0: "TPM_INTERFACE_UNSPECIFIED", + 1: "DIRECT", + 2: "KERNEL_MANAGED", + 3: "DAEMON_MANAGED", +} + +var TpmInterface_value = map[string]int32{ + "TPM_INTERFACE_UNSPECIFIED": 0, + "DIRECT": 1, + "KERNEL_MANAGED": 2, + "DAEMON_MANAGED": 3, +} + +func (x TpmInterface) String() string { + return proto.EnumName(TpmInterface_name, int32(x)) +} + +func (TpmInterface) EnumDescriptor() ([]byte, []int) { + return fileDescriptor_63ac7bc02f9d1279, []int{1} +} + +type EndorsementKey_DataType int32 + +const ( + EndorsementKey_DATA_TYPE_UNSPECIFIED EndorsementKey_DataType = 0 + EndorsementKey_PUBLIC_BLOB EndorsementKey_DataType = 1 + EndorsementKey_X509_CERT_BLOB EndorsementKey_DataType = 2 +) + +var EndorsementKey_DataType_name = map[int32]string{ + 0: "DATA_TYPE_UNSPECIFIED", + 1: "PUBLIC_BLOB", + 2: "X509_CERT_BLOB", +} + +var EndorsementKey_DataType_value = map[string]int32{ + "DATA_TYPE_UNSPECIFIED": 0, + "PUBLIC_BLOB": 1, + "X509_CERT_BLOB": 2, +} + +func (x EndorsementKey_DataType) String() string { + return proto.EnumName(EndorsementKey_DataType_name, int32(x)) +} + +func (EndorsementKey_DataType) EnumDescriptor() ([]byte, []int) { + return fileDescriptor_63ac7bc02f9d1279, []int{1, 0} +} + +type ChallengeInfo_ChallengeType int32 + +const ( + ChallengeInfo_CHALLENGE_UNSPECIFIED ChallengeInfo_ChallengeType = 0 + ChallengeInfo_CHALLENGE_CA ChallengeInfo_ChallengeType = 1 +) + +var ChallengeInfo_ChallengeType_name = map[int32]string{ + 0: "CHALLENGE_UNSPECIFIED", + 1: "CHALLENGE_CA", +} + +var ChallengeInfo_ChallengeType_value = map[string]int32{ + "CHALLENGE_UNSPECIFIED": 0, + "CHALLENGE_CA": 1, +} + +func (x ChallengeInfo_ChallengeType) String() string { + return proto.EnumName(ChallengeInfo_ChallengeType_name, int32(x)) +} + +func (ChallengeInfo_ChallengeType) EnumDescriptor() ([]byte, []int) { + return fileDescriptor_63ac7bc02f9d1279, []int{5, 0} +} + +type StatusReport_ReportType int32 + +const ( + StatusReport_REPORT_UNSPECIFIED StatusReport_ReportType = 0 + StatusReport_REPORT_TPM_UNSUITABLE StatusReport_ReportType = 1 + StatusReport_REPORT_TPM_OPERATION_FAILURE StatusReport_ReportType = 2 + StatusReport_REPORT_LOG_UNAVAILABLE StatusReport_ReportType = 3 +) + +var StatusReport_ReportType_name = map[int32]string{ + 0: "REPORT_UNSPECIFIED", + 1: "REPORT_TPM_UNSUITABLE", + 2: "REPORT_TPM_OPERATION_FAILURE", + 3: "REPORT_LOG_UNAVAILABLE", +} + +var StatusReport_ReportType_value = map[string]int32{ + "REPORT_UNSPECIFIED": 0, + "REPORT_TPM_UNSUITABLE": 1, + "REPORT_TPM_OPERATION_FAILURE": 2, + "REPORT_LOG_UNAVAILABLE": 3, +} + +func (x StatusReport_ReportType) String() string { + return proto.EnumName(StatusReport_ReportType_name, int32(x)) +} + +func (StatusReport_ReportType) EnumDescriptor() ([]byte, []int) { + return fileDescriptor_63ac7bc02f9d1279, []int{7, 0} +} + +// TpmInfo encapsulates version / device information +// about the TPM, and how the attestation client interfaces +// with it. +type TpmInfo struct { + TpmVersion TpmVersion `protobuf:"varint,1,opt,name=tpm_version,json=tpmVersion,proto3,enum=go_attestation.proto.TpmVersion" json:"tpm_version,omitempty"` + Manufacturer string `protobuf:"bytes,2,opt,name=manufacturer,proto3" json:"manufacturer,omitempty"` + TpmInterface TpmInterface `protobuf:"varint,3,opt,name=tpm_interface,json=tpmInterface,proto3,enum=go_attestation.proto.TpmInterface" json:"tpm_interface,omitempty"` + // This number represents the version of the support code which + // interfaces with the TPM. + TpmInterfaceVersion uint32 `protobuf:"varint,4,opt,name=tpm_interface_version,json=tpmInterfaceVersion,proto3" json:"tpm_interface_version,omitempty"` // Deprecated: Do not use. + // This is the string provided by the TPM. + TpmOpaqueInfo string `protobuf:"bytes,5,opt,name=tpm_opaque_info,json=tpmOpaqueInfo,proto3" json:"tpm_opaque_info,omitempty"` + // This is set if challenges must be generated + // in TrouSerS format for TPM 1.2 devices. + TrousersFormat bool `protobuf:"varint,6,opt,name=trousers_format,json=trousersFormat,proto3" json:"trousers_format,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *TpmInfo) Reset() { *m = TpmInfo{} } +func (m *TpmInfo) String() string { return proto.CompactTextString(m) } +func (*TpmInfo) ProtoMessage() {} +func (*TpmInfo) Descriptor() ([]byte, []int) { + return fileDescriptor_63ac7bc02f9d1279, []int{0} +} + +func (m *TpmInfo) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_TpmInfo.Unmarshal(m, b) +} +func (m *TpmInfo) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_TpmInfo.Marshal(b, m, deterministic) +} +func (m *TpmInfo) XXX_Merge(src proto.Message) { + xxx_messageInfo_TpmInfo.Merge(m, src) +} +func (m *TpmInfo) XXX_Size() int { + return xxx_messageInfo_TpmInfo.Size(m) +} +func (m *TpmInfo) XXX_DiscardUnknown() { + xxx_messageInfo_TpmInfo.DiscardUnknown(m) +} + +var xxx_messageInfo_TpmInfo proto.InternalMessageInfo + +func (m *TpmInfo) GetTpmVersion() TpmVersion { + if m != nil { + return m.TpmVersion + } + return TpmVersion_TPM_VERSION_UNSPECIFIED +} + +func (m *TpmInfo) GetManufacturer() string { + if m != nil { + return m.Manufacturer + } + return "" +} + +func (m *TpmInfo) GetTpmInterface() TpmInterface { + if m != nil { + return m.TpmInterface + } + return TpmInterface_TPM_INTERFACE_UNSPECIFIED +} + +// Deprecated: Do not use. +func (m *TpmInfo) GetTpmInterfaceVersion() uint32 { + if m != nil { + return m.TpmInterfaceVersion + } + return 0 +} + +func (m *TpmInfo) GetTpmOpaqueInfo() string { + if m != nil { + return m.TpmOpaqueInfo + } + return "" +} + +func (m *TpmInfo) GetTrousersFormat() bool { + if m != nil { + return m.TrousersFormat + } + return false +} + +type EndorsementKey struct { + Datatype EndorsementKey_DataType `protobuf:"varint,1,opt,name=datatype,proto3,enum=go_attestation.proto.EndorsementKey_DataType" json:"datatype,omitempty"` + Data []byte `protobuf:"bytes,2,opt,name=data,proto3" json:"data,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *EndorsementKey) Reset() { *m = EndorsementKey{} } +func (m *EndorsementKey) String() string { return proto.CompactTextString(m) } +func (*EndorsementKey) ProtoMessage() {} +func (*EndorsementKey) Descriptor() ([]byte, []int) { + return fileDescriptor_63ac7bc02f9d1279, []int{1} +} + +func (m *EndorsementKey) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_EndorsementKey.Unmarshal(m, b) +} +func (m *EndorsementKey) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_EndorsementKey.Marshal(b, m, deterministic) +} +func (m *EndorsementKey) XXX_Merge(src proto.Message) { + xxx_messageInfo_EndorsementKey.Merge(m, src) +} +func (m *EndorsementKey) XXX_Size() int { + return xxx_messageInfo_EndorsementKey.Size(m) +} +func (m *EndorsementKey) XXX_DiscardUnknown() { + xxx_messageInfo_EndorsementKey.DiscardUnknown(m) +} + +var xxx_messageInfo_EndorsementKey proto.InternalMessageInfo + +func (m *EndorsementKey) GetDatatype() EndorsementKey_DataType { + if m != nil { + return m.Datatype + } + return EndorsementKey_DATA_TYPE_UNSPECIFIED +} + +func (m *EndorsementKey) GetData() []byte { + if m != nil { + return m.Data + } + return nil +} + +// Tpm20AikInfo describes an AIK using TPM 2.0 structures. +type Tpm20AikInfo struct { + // This is a TPMT_PUBLIC structure. + PublicBlob []byte `protobuf:"bytes,1,opt,name=public_blob,json=publicBlob,proto3" json:"public_blob,omitempty"` + // This is a TPMS_CREATION_DATA structure. + CreationData []byte `protobuf:"bytes,2,opt,name=creation_data,json=creationData,proto3" json:"creation_data,omitempty"` + // This is a TPMU_ATTEST structure, with the dynamic section + // containing a CREATION_INFO structure. + AttestationData []byte `protobuf:"bytes,3,opt,name=attestation_data,json=attestationData,proto3" json:"attestation_data,omitempty"` + // This is a TPMT_SIGNATURE structure. + SignatureData []byte `protobuf:"bytes,4,opt,name=signature_data,json=signatureData,proto3" json:"signature_data,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *Tpm20AikInfo) Reset() { *m = Tpm20AikInfo{} } +func (m *Tpm20AikInfo) String() string { return proto.CompactTextString(m) } +func (*Tpm20AikInfo) ProtoMessage() {} +func (*Tpm20AikInfo) Descriptor() ([]byte, []int) { + return fileDescriptor_63ac7bc02f9d1279, []int{2} +} + +func (m *Tpm20AikInfo) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_Tpm20AikInfo.Unmarshal(m, b) +} +func (m *Tpm20AikInfo) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_Tpm20AikInfo.Marshal(b, m, deterministic) +} +func (m *Tpm20AikInfo) XXX_Merge(src proto.Message) { + xxx_messageInfo_Tpm20AikInfo.Merge(m, src) +} +func (m *Tpm20AikInfo) XXX_Size() int { + return xxx_messageInfo_Tpm20AikInfo.Size(m) +} +func (m *Tpm20AikInfo) XXX_DiscardUnknown() { + xxx_messageInfo_Tpm20AikInfo.DiscardUnknown(m) +} + +var xxx_messageInfo_Tpm20AikInfo proto.InternalMessageInfo + +func (m *Tpm20AikInfo) GetPublicBlob() []byte { + if m != nil { + return m.PublicBlob + } + return nil +} + +func (m *Tpm20AikInfo) GetCreationData() []byte { + if m != nil { + return m.CreationData + } + return nil +} + +func (m *Tpm20AikInfo) GetAttestationData() []byte { + if m != nil { + return m.AttestationData + } + return nil +} + +func (m *Tpm20AikInfo) GetSignatureData() []byte { + if m != nil { + return m.SignatureData + } + return nil +} + +// Tpm12AikInfo describes an AIK using TPM 1.2 structures. +type Tpm12AikInfo struct { + // This is a TPM_PUBKEY structure. + PublicBlob []byte `protobuf:"bytes,1,opt,name=public_blob,json=publicBlob,proto3" json:"public_blob,omitempty"` + // This is auxillary data, provided for the purpose of debugging. + // on Windows devices, this represents the contents of PCP_ID_BINDING. + Aux []byte `protobuf:"bytes,2,opt,name=aux,proto3" json:"aux,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *Tpm12AikInfo) Reset() { *m = Tpm12AikInfo{} } +func (m *Tpm12AikInfo) String() string { return proto.CompactTextString(m) } +func (*Tpm12AikInfo) ProtoMessage() {} +func (*Tpm12AikInfo) Descriptor() ([]byte, []int) { + return fileDescriptor_63ac7bc02f9d1279, []int{3} +} + +func (m *Tpm12AikInfo) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_Tpm12AikInfo.Unmarshal(m, b) +} +func (m *Tpm12AikInfo) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_Tpm12AikInfo.Marshal(b, m, deterministic) +} +func (m *Tpm12AikInfo) XXX_Merge(src proto.Message) { + xxx_messageInfo_Tpm12AikInfo.Merge(m, src) +} +func (m *Tpm12AikInfo) XXX_Size() int { + return xxx_messageInfo_Tpm12AikInfo.Size(m) +} +func (m *Tpm12AikInfo) XXX_DiscardUnknown() { + xxx_messageInfo_Tpm12AikInfo.DiscardUnknown(m) +} + +var xxx_messageInfo_Tpm12AikInfo proto.InternalMessageInfo + +func (m *Tpm12AikInfo) GetPublicBlob() []byte { + if m != nil { + return m.PublicBlob + } + return nil +} + +func (m *Tpm12AikInfo) GetAux() []byte { + if m != nil { + return m.Aux + } + return nil +} + +// AikInfo describes the public key, parameters, and creation information +// of an attestation identity key. +type AikInfo struct { + // Types that are valid to be assigned to TpmAikInfo: + // *AikInfo_Tpm20 + // *AikInfo_Tpm12 + TpmAikInfo isAikInfo_TpmAikInfo `protobuf_oneof:"tpm_aik_info"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *AikInfo) Reset() { *m = AikInfo{} } +func (m *AikInfo) String() string { return proto.CompactTextString(m) } +func (*AikInfo) ProtoMessage() {} +func (*AikInfo) Descriptor() ([]byte, []int) { + return fileDescriptor_63ac7bc02f9d1279, []int{4} +} + +func (m *AikInfo) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_AikInfo.Unmarshal(m, b) +} +func (m *AikInfo) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_AikInfo.Marshal(b, m, deterministic) +} +func (m *AikInfo) XXX_Merge(src proto.Message) { + xxx_messageInfo_AikInfo.Merge(m, src) +} +func (m *AikInfo) XXX_Size() int { + return xxx_messageInfo_AikInfo.Size(m) +} +func (m *AikInfo) XXX_DiscardUnknown() { + xxx_messageInfo_AikInfo.DiscardUnknown(m) +} + +var xxx_messageInfo_AikInfo proto.InternalMessageInfo + +type isAikInfo_TpmAikInfo interface { + isAikInfo_TpmAikInfo() +} + +type AikInfo_Tpm20 struct { + Tpm20 *Tpm20AikInfo `protobuf:"bytes,1,opt,name=tpm20,proto3,oneof"` +} + +type AikInfo_Tpm12 struct { + Tpm12 *Tpm12AikInfo `protobuf:"bytes,2,opt,name=tpm12,proto3,oneof"` +} + +func (*AikInfo_Tpm20) isAikInfo_TpmAikInfo() {} + +func (*AikInfo_Tpm12) isAikInfo_TpmAikInfo() {} + +func (m *AikInfo) GetTpmAikInfo() isAikInfo_TpmAikInfo { + if m != nil { + return m.TpmAikInfo + } + return nil +} + +func (m *AikInfo) GetTpm20() *Tpm20AikInfo { + if x, ok := m.GetTpmAikInfo().(*AikInfo_Tpm20); ok { + return x.Tpm20 + } + return nil +} + +func (m *AikInfo) GetTpm12() *Tpm12AikInfo { + if x, ok := m.GetTpmAikInfo().(*AikInfo_Tpm12); ok { + return x.Tpm12 + } + return nil +} + +// XXX_OneofWrappers is for the internal use of the proto package. +func (*AikInfo) XXX_OneofWrappers() []interface{} { + return []interface{}{ + (*AikInfo_Tpm20)(nil), + (*AikInfo_Tpm12)(nil), + } +} + +// ChallengeInfo describes which challenge a nonce corresponds to. +type ChallengeInfo struct { + Type ChallengeInfo_ChallengeType `protobuf:"varint,1,opt,name=type,proto3,enum=go_attestation.proto.ChallengeInfo_ChallengeType" json:"type,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *ChallengeInfo) Reset() { *m = ChallengeInfo{} } +func (m *ChallengeInfo) String() string { return proto.CompactTextString(m) } +func (*ChallengeInfo) ProtoMessage() {} +func (*ChallengeInfo) Descriptor() ([]byte, []int) { + return fileDescriptor_63ac7bc02f9d1279, []int{5} +} + +func (m *ChallengeInfo) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_ChallengeInfo.Unmarshal(m, b) +} +func (m *ChallengeInfo) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_ChallengeInfo.Marshal(b, m, deterministic) +} +func (m *ChallengeInfo) XXX_Merge(src proto.Message) { + xxx_messageInfo_ChallengeInfo.Merge(m, src) +} +func (m *ChallengeInfo) XXX_Size() int { + return xxx_messageInfo_ChallengeInfo.Size(m) +} +func (m *ChallengeInfo) XXX_DiscardUnknown() { + xxx_messageInfo_ChallengeInfo.DiscardUnknown(m) +} + +var xxx_messageInfo_ChallengeInfo proto.InternalMessageInfo + +func (m *ChallengeInfo) GetType() ChallengeInfo_ChallengeType { + if m != nil { + return m.Type + } + return ChallengeInfo_CHALLENGE_UNSPECIFIED +} + +// ClientInfo is optional data sent from the client to identify what version +// of racc-client it is running. +type ClientInfo struct { + MachineTrack string `protobuf:"bytes,1,opt,name=machine_track,json=machineTrack,proto3" json:"machine_track,omitempty"` + ClRollup string `protobuf:"bytes,2,opt,name=cl_rollup,json=clRollup,proto3" json:"cl_rollup,omitempty"` + Version string `protobuf:"bytes,3,opt,name=version,proto3" json:"version,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *ClientInfo) Reset() { *m = ClientInfo{} } +func (m *ClientInfo) String() string { return proto.CompactTextString(m) } +func (*ClientInfo) ProtoMessage() {} +func (*ClientInfo) Descriptor() ([]byte, []int) { + return fileDescriptor_63ac7bc02f9d1279, []int{6} +} + +func (m *ClientInfo) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_ClientInfo.Unmarshal(m, b) +} +func (m *ClientInfo) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_ClientInfo.Marshal(b, m, deterministic) +} +func (m *ClientInfo) XXX_Merge(src proto.Message) { + xxx_messageInfo_ClientInfo.Merge(m, src) +} +func (m *ClientInfo) XXX_Size() int { + return xxx_messageInfo_ClientInfo.Size(m) +} +func (m *ClientInfo) XXX_DiscardUnknown() { + xxx_messageInfo_ClientInfo.DiscardUnknown(m) +} + +var xxx_messageInfo_ClientInfo proto.InternalMessageInfo + +func (m *ClientInfo) GetMachineTrack() string { + if m != nil { + return m.MachineTrack + } + return "" +} + +func (m *ClientInfo) GetClRollup() string { + if m != nil { + return m.ClRollup + } + return "" +} + +func (m *ClientInfo) GetVersion() string { + if m != nil { + return m.Version + } + return "" +} + +// StatusReport describes information from a client which is distinct to any +// attestation operation. +type StatusReport struct { + Type StatusReport_ReportType `protobuf:"varint,1,opt,name=type,proto3,enum=go_attestation.proto.StatusReport_ReportType" json:"type,omitempty"` + Code int64 `protobuf:"varint,2,opt,name=code,proto3" json:"code,omitempty"` + Message string `protobuf:"bytes,3,opt,name=message,proto3" json:"message,omitempty"` + Operation string `protobuf:"bytes,4,opt,name=operation,proto3" json:"operation,omitempty"` + ClientInfo *ClientInfo `protobuf:"bytes,5,opt,name=client_info,json=clientInfo,proto3" json:"client_info,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *StatusReport) Reset() { *m = StatusReport{} } +func (m *StatusReport) String() string { return proto.CompactTextString(m) } +func (*StatusReport) ProtoMessage() {} +func (*StatusReport) Descriptor() ([]byte, []int) { + return fileDescriptor_63ac7bc02f9d1279, []int{7} +} + +func (m *StatusReport) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_StatusReport.Unmarshal(m, b) +} +func (m *StatusReport) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_StatusReport.Marshal(b, m, deterministic) +} +func (m *StatusReport) XXX_Merge(src proto.Message) { + xxx_messageInfo_StatusReport.Merge(m, src) +} +func (m *StatusReport) XXX_Size() int { + return xxx_messageInfo_StatusReport.Size(m) +} +func (m *StatusReport) XXX_DiscardUnknown() { + xxx_messageInfo_StatusReport.DiscardUnknown(m) +} + +var xxx_messageInfo_StatusReport proto.InternalMessageInfo + +func (m *StatusReport) GetType() StatusReport_ReportType { + if m != nil { + return m.Type + } + return StatusReport_REPORT_UNSPECIFIED +} + +func (m *StatusReport) GetCode() int64 { + if m != nil { + return m.Code + } + return 0 +} + +func (m *StatusReport) GetMessage() string { + if m != nil { + return m.Message + } + return "" +} + +func (m *StatusReport) GetOperation() string { + if m != nil { + return m.Operation + } + return "" +} + +func (m *StatusReport) GetClientInfo() *ClientInfo { + if m != nil { + return m.ClientInfo + } + return nil +} + +func init() { + proto.RegisterEnum("go_attestation.proto.TpmVersion", TpmVersion_name, TpmVersion_value) + proto.RegisterEnum("go_attestation.proto.TpmInterface", TpmInterface_name, TpmInterface_value) + proto.RegisterEnum("go_attestation.proto.EndorsementKey_DataType", EndorsementKey_DataType_name, EndorsementKey_DataType_value) + proto.RegisterEnum("go_attestation.proto.ChallengeInfo_ChallengeType", ChallengeInfo_ChallengeType_name, ChallengeInfo_ChallengeType_value) + proto.RegisterEnum("go_attestation.proto.StatusReport_ReportType", StatusReport_ReportType_name, StatusReport_ReportType_value) + proto.RegisterType((*TpmInfo)(nil), "go_attestation.proto.TpmInfo") + proto.RegisterType((*EndorsementKey)(nil), "go_attestation.proto.EndorsementKey") + proto.RegisterType((*Tpm20AikInfo)(nil), "go_attestation.proto.Tpm20AikInfo") + proto.RegisterType((*Tpm12AikInfo)(nil), "go_attestation.proto.Tpm12AikInfo") + proto.RegisterType((*AikInfo)(nil), "go_attestation.proto.AikInfo") + proto.RegisterType((*ChallengeInfo)(nil), "go_attestation.proto.ChallengeInfo") + proto.RegisterType((*ClientInfo)(nil), "go_attestation.proto.ClientInfo") + proto.RegisterType((*StatusReport)(nil), "go_attestation.proto.StatusReport") +} + +func init() { proto.RegisterFile("tpm.proto", fileDescriptor_63ac7bc02f9d1279) } + +var fileDescriptor_63ac7bc02f9d1279 = []byte{ + // 848 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x54, 0xef, 0x6e, 0xe3, 0x44, + 0x10, 0x3f, 0x27, 0xbd, 0xb6, 0x99, 0xfc, 0xa9, 0xb5, 0xd0, 0x23, 0xe5, 0x0e, 0x11, 0xf9, 0x04, + 0x94, 0x93, 0x88, 0x9a, 0x20, 0x90, 0x40, 0x7c, 0xd9, 0x38, 0xdb, 0x9e, 0x39, 0x37, 0x89, 0xb6, + 0x4e, 0x05, 0x9f, 0xac, 0x8d, 0xbb, 0x49, 0xad, 0xfa, 0x1f, 0xf6, 0x06, 0xd1, 0x0f, 0x3c, 0x04, + 0x12, 0xcf, 0xc0, 0x4b, 0xf0, 0x8d, 0x17, 0xe1, 0x55, 0xd0, 0xae, 0xe3, 0xd8, 0x39, 0xda, 0xd3, + 0x7d, 0xca, 0xec, 0x6f, 0xe6, 0x37, 0xfe, 0xcd, 0x64, 0x66, 0xa0, 0x21, 0x92, 0xb0, 0x9f, 0xa4, + 0xb1, 0x88, 0xd1, 0x87, 0xab, 0xd8, 0x65, 0x42, 0xf0, 0x4c, 0x30, 0xe1, 0xc7, 0x51, 0x8e, 0x1a, + 0xff, 0xd4, 0xe0, 0xc0, 0x49, 0x42, 0x2b, 0x5a, 0xc6, 0x08, 0x43, 0x53, 0x24, 0xa1, 0xfb, 0x2b, + 0x4f, 0x33, 0x3f, 0x8e, 0xba, 0x5a, 0x4f, 0x3b, 0xed, 0x0c, 0x7b, 0xfd, 0x87, 0x78, 0x7d, 0x27, + 0x09, 0xaf, 0xf3, 0x38, 0x0a, 0x62, 0x6b, 0x23, 0x03, 0x5a, 0x21, 0x8b, 0xd6, 0x4b, 0xe6, 0x89, + 0x75, 0xca, 0xd3, 0x6e, 0xad, 0xa7, 0x9d, 0x36, 0xe8, 0x0e, 0x86, 0x2e, 0xa0, 0x2d, 0x3f, 0xe3, + 0x47, 0x82, 0xa7, 0x4b, 0xe6, 0xf1, 0x6e, 0x5d, 0x7d, 0xc8, 0x78, 0xf4, 0x43, 0x56, 0x11, 0x49, + 0x5b, 0xa2, 0xf2, 0x42, 0xdf, 0xc2, 0xf1, 0x4e, 0xa2, 0xad, 0xf2, 0xbd, 0x9e, 0x76, 0xda, 0x1e, + 0xd5, 0xba, 0x1a, 0xfd, 0xa0, 0x4a, 0x28, 0x44, 0x7e, 0x0e, 0x47, 0x92, 0x17, 0x27, 0xec, 0x97, + 0x35, 0x77, 0xfd, 0x68, 0x19, 0x77, 0x9f, 0x2a, 0x9d, 0x52, 0xd7, 0x54, 0xa1, 0xaa, 0x1f, 0x5f, + 0xc0, 0x91, 0x48, 0xe3, 0x75, 0xc6, 0xd3, 0xcc, 0x5d, 0xc6, 0x69, 0xc8, 0x44, 0x77, 0xbf, 0xa7, + 0x9d, 0x1e, 0xd2, 0x4e, 0x01, 0x9f, 0x2b, 0xd4, 0xf8, 0x5b, 0x83, 0x0e, 0x89, 0x6e, 0xe2, 0x34, + 0xe3, 0x21, 0x8f, 0xc4, 0x1b, 0x7e, 0x8f, 0x2c, 0x38, 0xbc, 0x61, 0x82, 0x89, 0xfb, 0x84, 0x6f, + 0x1a, 0xf9, 0xd5, 0xc3, 0xf5, 0xed, 0xf2, 0xfa, 0x63, 0x26, 0x98, 0x73, 0x9f, 0x70, 0xba, 0xa5, + 0x23, 0x04, 0x7b, 0xd2, 0x56, 0xbd, 0x6c, 0x51, 0x65, 0x1b, 0x3f, 0xc2, 0x61, 0x11, 0x89, 0x4e, + 0xe0, 0x78, 0x8c, 0x1d, 0xec, 0x3a, 0x3f, 0xcf, 0x88, 0x3b, 0x9f, 0x5c, 0xcd, 0x88, 0x69, 0x9d, + 0x5b, 0x64, 0xac, 0x3f, 0x41, 0x47, 0xd0, 0x9c, 0xcd, 0x47, 0xb6, 0x65, 0xba, 0x23, 0x7b, 0x3a, + 0xd2, 0x35, 0x84, 0xa0, 0xf3, 0xd3, 0x37, 0x67, 0xdf, 0xb9, 0x26, 0xa1, 0x4e, 0x8e, 0xd5, 0x8c, + 0xbf, 0x34, 0x68, 0x39, 0x49, 0x38, 0x3c, 0xc3, 0xfe, 0x9d, 0xaa, 0xfb, 0x53, 0x68, 0x26, 0xeb, + 0x45, 0xe0, 0x7b, 0xee, 0x22, 0x88, 0x17, 0x4a, 0x7e, 0x8b, 0x42, 0x0e, 0x8d, 0x82, 0x78, 0x81, + 0x5e, 0x42, 0xdb, 0x4b, 0xb9, 0xaa, 0xc2, 0xad, 0x48, 0x6b, 0x15, 0xa0, 0x94, 0x86, 0xbe, 0x04, + 0xbd, 0x52, 0x6d, 0x1e, 0x57, 0x57, 0x71, 0x47, 0x15, 0x5c, 0x85, 0x7e, 0x06, 0x9d, 0xcc, 0x5f, + 0x45, 0x4c, 0xce, 0x47, 0x1e, 0xb8, 0xa7, 0x02, 0xdb, 0x5b, 0x54, 0x86, 0x19, 0x58, 0xe9, 0x1c, + 0x0c, 0xdf, 0x5b, 0xa7, 0x0e, 0x75, 0xb6, 0xfe, 0x6d, 0xa3, 0x4e, 0x9a, 0xc6, 0x1f, 0x1a, 0x1c, + 0x14, 0xf4, 0xef, 0xe1, 0xa9, 0x90, 0x65, 0x2b, 0x62, 0xf3, 0x1d, 0xf3, 0xb7, 0xed, 0xcc, 0xeb, + 0x27, 0x34, 0xa7, 0x6c, 0xb8, 0x83, 0xa1, 0xca, 0xfd, 0x2e, 0xee, 0x56, 0xed, 0x86, 0x3b, 0x18, + 0x8e, 0x3a, 0x20, 0xc7, 0xd8, 0x65, 0xfe, 0x9d, 0x9a, 0x3d, 0xe3, 0x4f, 0x0d, 0xda, 0xe6, 0x2d, + 0x0b, 0x02, 0x1e, 0xad, 0xf2, 0xc1, 0x23, 0xb0, 0x57, 0x19, 0x9c, 0xc1, 0xc3, 0xc9, 0x77, 0x28, + 0xe5, 0x4b, 0x0d, 0x8f, 0xa2, 0x1b, 0x3f, 0x54, 0xf2, 0x16, 0x93, 0x62, 0xbe, 0xc6, 0xb6, 0x4d, + 0x26, 0x17, 0x6f, 0x4f, 0x8a, 0x0e, 0xad, 0xd2, 0x65, 0x62, 0x5d, 0x33, 0x6e, 0x01, 0xcc, 0xc0, + 0xe7, 0x91, 0x50, 0x92, 0x5e, 0x42, 0x3b, 0x64, 0xde, 0xad, 0x1f, 0x71, 0x57, 0xa4, 0xcc, 0xbb, + 0x53, 0xda, 0xd4, 0x66, 0x2b, 0xd0, 0x91, 0x18, 0x7a, 0x0e, 0x0d, 0x2f, 0x70, 0xd3, 0x38, 0x08, + 0xd6, 0xc9, 0x66, 0xf5, 0x0f, 0xbd, 0x80, 0xaa, 0x37, 0xea, 0xc2, 0x41, 0xb1, 0x9f, 0x75, 0xe5, + 0x2a, 0x9e, 0xc6, 0xbf, 0x35, 0x68, 0x5d, 0x09, 0x26, 0xd6, 0x19, 0xe5, 0x49, 0x9c, 0x0a, 0x84, + 0x77, 0xea, 0x7f, 0x64, 0x71, 0xaa, 0x8c, 0x7e, 0xfe, 0x53, 0xd6, 0x2e, 0x97, 0xc6, 0x8b, 0x6f, + 0xb8, 0x52, 0x51, 0xa7, 0xca, 0x96, 0x0a, 0x42, 0x9e, 0x65, 0x6c, 0xc5, 0x0b, 0x05, 0x9b, 0x27, + 0x7a, 0x01, 0x8d, 0x38, 0xe1, 0xa9, 0x4a, 0xaf, 0x66, 0xaf, 0x41, 0x4b, 0x40, 0xde, 0x45, 0x4f, + 0x75, 0xa2, 0xbc, 0x15, 0xcd, 0xc7, 0xee, 0x62, 0xd9, 0x32, 0x0a, 0xde, 0xd6, 0x36, 0x7e, 0x07, + 0x28, 0x25, 0xa2, 0x67, 0x80, 0x28, 0x99, 0x4d, 0xa9, 0xf3, 0xd6, 0x9f, 0x70, 0x02, 0xc7, 0x1b, + 0xdc, 0x99, 0x5d, 0x4a, 0xdf, 0xdc, 0x72, 0xf0, 0xc8, 0x26, 0xba, 0x86, 0x7a, 0xf0, 0xa2, 0xe2, + 0x9a, 0xce, 0x08, 0xc5, 0x8e, 0x35, 0x9d, 0xb8, 0xe7, 0xd8, 0xb2, 0xe7, 0x94, 0xe8, 0x35, 0xf4, + 0x31, 0x3c, 0xdb, 0x44, 0xd8, 0xd3, 0x0b, 0x77, 0x3e, 0xc1, 0xd7, 0xd8, 0xb2, 0x15, 0xbb, 0xfe, + 0x0a, 0x03, 0x94, 0x07, 0x1b, 0x3d, 0x87, 0x8f, 0x64, 0x92, 0x6b, 0x42, 0xaf, 0x64, 0x8a, 0x5d, + 0x0d, 0x00, 0xfb, 0xd2, 0x39, 0x18, 0xea, 0x5a, 0x61, 0x0f, 0xcf, 0xf4, 0xda, 0x2b, 0xa6, 0x96, + 0xaf, 0x3c, 0xbe, 0x9f, 0xc0, 0x89, 0xf4, 0x59, 0x13, 0x87, 0xd0, 0x73, 0x6c, 0x92, 0xff, 0xa7, + 0x19, 0x5b, 0x94, 0x98, 0x4e, 0x7e, 0x74, 0xde, 0x10, 0x3a, 0x21, 0xb6, 0x7b, 0x89, 0x27, 0xf8, + 0x82, 0x8c, 0xf5, 0x9a, 0xc4, 0xc6, 0x98, 0x5c, 0x4e, 0x27, 0x5b, 0xac, 0xbe, 0xd8, 0x57, 0x2d, + 0xfc, 0xfa, 0xbf, 0x00, 0x00, 0x00, 0xff, 0xff, 0x46, 0xe2, 0xe4, 0xeb, 0xb5, 0x06, 0x00, 0x00, +} diff --git a/proto/tpm.proto b/proto/tpm.proto new file mode 100644 index 0000000..110333c --- /dev/null +++ b/proto/tpm.proto @@ -0,0 +1,114 @@ +// (-- api-linter: forbidden-types=disabled --) +syntax = "proto3"; + +package go_attestation.proto; + +enum TpmVersion { + TPM_VERSION_UNSPECIFIED = 0; + TPM_12 = 1; + TPM_20 = 2; +} + +enum TpmInterface { + TPM_INTERFACE_UNSPECIFIED = 0; + DIRECT = 1; + KERNEL_MANAGED = 2; + DAEMON_MANAGED = 3; +} + +// TpmInfo encapsulates version / device information +// about the TPM, and how the attestation client interfaces +// with it. +message TpmInfo { + TpmVersion tpm_version = 1; + string manufacturer = 2; + TpmInterface tpm_interface = 3; + + // This number represents the version of the support code which + // interfaces with the TPM. + uint32 tpm_interface_version = 4 [deprecated = true]; + + // This is the string provided by the TPM. + string tpm_opaque_info = 5; + + // This is set if challenges must be generated + // in TrouSerS format for TPM 1.2 devices. + bool trousers_format = 6; +} + +message EndorsementKey { + enum DataType { + DATA_TYPE_UNSPECIFIED = 0; + PUBLIC_BLOB = 1; // Indicates data is encoded as a PKCS1 public key. + X509_CERT_BLOB = 2; + }; + + DataType datatype = 1; + bytes data = 2; +} + +// Tpm20AikInfo describes an AIK using TPM 2.0 structures. +message Tpm20AikInfo { + // This is a TPMT_PUBLIC structure. + bytes public_blob = 1; + // This is a TPMS_CREATION_DATA structure. + bytes creation_data = 2; + // This is a TPMU_ATTEST structure, with the dynamic section + // containing a CREATION_INFO structure. + bytes attestation_data = 3; + // This is a TPMT_SIGNATURE structure. + bytes signature_data = 4; +} + +// Tpm12AikInfo describes an AIK using TPM 1.2 structures. +message Tpm12AikInfo { + // This is a TPM_PUBKEY structure. + bytes public_blob = 1; + // This is auxillary data, provided for the purpose of debugging. + // on Windows devices, this represents the contents of PCP_ID_BINDING. + bytes aux = 2; +} + +// AikInfo describes the public key, parameters, and creation information +// of an attestation identity key. +message AikInfo { + oneof tpm_aik_info { + Tpm20AikInfo tpm20 = 1; + Tpm12AikInfo tpm12 = 2; + } +} + +// ChallengeInfo describes which challenge a nonce corresponds to. +message ChallengeInfo { + enum ChallengeType { + CHALLENGE_UNSPECIFIED = 0; + CHALLENGE_CA = 1; + }; + + ChallengeType type = 1; +} + +// ClientInfo is optional data sent from the client to identify what version +// of racc-client it is running. +message ClientInfo { + string machine_track = 1; + string cl_rollup = 2; + string version = 3; +} + +// StatusReport describes information from a client which is distinct to any +// attestation operation. +message StatusReport { + enum ReportType { + REPORT_UNSPECIFIED = 0; + REPORT_TPM_UNSUITABLE = 1; + REPORT_TPM_OPERATION_FAILURE = 2; + REPORT_LOG_UNAVAILABLE = 3; + }; + + ReportType type = 1; + int64 code = 2; + string message = 3; + string operation = 4; + ClientInfo client_info = 5; +} diff --git a/verifier/aik.go b/verifier/aik.go new file mode 100644 index 0000000..498a8d0 --- /dev/null +++ b/verifier/aik.go @@ -0,0 +1,130 @@ +// Package verifier implements high-level logic to check information +// provided by TPMs in client devices. +package verifier + +import ( + "bytes" + "crypto/rsa" + "fmt" + + tpb "github.com/google/go-attestation/proto" + pb "github.com/google/go-attestation/verifier/proto" + + tpm1 "github.com/google/go-tpm/tpm" + "github.com/google/go-tpm/tpm2" +) + +const ( + tpmGeneratedMagic = 0xff544347 +) + +// VerifyAIK examines properties of an AIK and a creation attestation, to determine +// if it is suitable for use as an attestation key. +func VerifyAIK(tpmVersion tpb.TpmVersion, aik *tpb.AikInfo) (*pb.AikVerificationResults, error) { + switch tpmVersion { + case tpb.TpmVersion_TPM_12: + aik12 := aik.GetTpm12() + return verifyAIK12(aik12.GetPublicBlob()) + + case tpb.TpmVersion_TPM_20: + aik20 := aik.GetTpm20() + return verifyAIK20(aik20.GetPublicBlob(), aik20.GetCreationData(), aik20.GetAttestationData(), aik20.GetSignatureData()) + + default: + return nil, fmt.Errorf("TPM version %d not supported", tpmVersion) + } +} + +func verifyAIK12(public []byte) (*pb.AikVerificationResults, error) { + pub, err := tpm1.UnmarshalPubRSAPublicKey(public) + if err != nil { + return nil, err + } + + var out pb.AikVerificationResults + out.RocaVulnerableKey = ROCAVulnerableKey(pub) + out.Succeeded = !out.RocaVulnerableKey + return &out, nil +} + +func verifyAIK20(public, creationData, attestationData, signature []byte) (*pb.AikVerificationResults, error) { + if len(signature) < 8 { + return nil, fmt.Errorf("signature is too short to be valid: only %d bytes", len(signature)) + } + + pub, err := tpm2.DecodePublic(public) + if err != nil { + return nil, err + } + _, err = tpm2.DecodeCreationData(creationData) + if err != nil { + return nil, err + } + att, err := tpm2.DecodeAttestationData(attestationData) + if err != nil { + return nil, err + } + if att.Type != tpm2.TagAttestCreation { + return nil, fmt.Errorf("attestation does apply to creation data, got tag %x", att.Type) + } + var out pb.AikVerificationResults + + switch pub.Type { + case tpm2.AlgRSA: + if pub.RSAParameters.KeyBits < 2048 { + out.KeyTooSmall = true + } + out.RocaVulnerableKey = ROCAVulnerableKey(&rsa.PublicKey{N: pub.RSAParameters.Modulus}) + default: + return nil, fmt.Errorf("public key of alg 0x%x not supported", pub.Type) + } + + // Compute & verify that the creation data matches the digest in the + // attestation structure. + nameHashConstructor, err := pub.NameAlg.HashConstructor() + if err != nil { + return nil, err + } + h := nameHashConstructor() + h.Write(creationData) + out.CreationAttestationMismatch = !bytes.Equal(att.AttestedCreationInfo.OpaqueDigest, h.Sum(nil)) + + // Make sure the AIK has sane key parameters (Attestation can be faked if an AIK + // can be used for arbitrary signatures). + // We verify the following: + // - Key is TPM backed. + // - Key is TPM generated. + // - Key is a restricted key (means it cannot do arbitrary signing/decrypt ops). + // - Key cannot be duplicated. + // - Key was generated by a call to TPM_Create*. + out.KeyNotTpmBound = (att.Magic != tpmGeneratedMagic) || ((pub.Attributes & tpm2.FlagFixedTPM) == 0) + out.KeyUsageOverlyBroad = ((pub.Attributes & tpm2.FlagRestricted) == 0) || ((pub.Attributes & tpm2.FlagFixedParent) == 0) || ((pub.Attributes & tpm2.FlagSensitiveDataOrigin) == 0) + + // Verify the attested creation name matches what is computed from + // the public key. + match, err := att.AttestedCreationInfo.Name.MatchesPublic(pub) + if err != nil { + return nil, err + } + out.NameAttestationMismatch = !match + + // Check the signature over the attestation data verifies correctly. + p := rsa.PublicKey{E: int(pub.RSAParameters.Exponent), N: pub.RSAParameters.Modulus} + signHashConstructor, err := pub.RSAParameters.Sign.Hash.HashConstructor() + if err != nil { + return nil, err + } + hsh := signHashConstructor() + hsh.Write(attestationData) + + verifyHash, err := cryptoHash(pub.RSAParameters.Sign.Hash) + if err != nil { + return nil, err + } + + //TODO(jsonp): Decode to tpm2.Signature & use that, once PR to expose DecodeSignature() is in. + out.SignatureMismatch = rsa.VerifyPKCS1v15(&p, verifyHash, hsh.Sum(nil), signature[6:]) != nil + + out.Succeeded = !(out.CreationAttestationMismatch || out.KeyTooSmall || out.KeyNotTpmBound || out.KeyUsageOverlyBroad || out.NameAttestationMismatch || out.SignatureMismatch) + return &out, nil +} diff --git a/verifier/ekcert.go b/verifier/ekcert.go new file mode 100644 index 0000000..ed42bcd --- /dev/null +++ b/verifier/ekcert.go @@ -0,0 +1,166 @@ +package verifier + +import ( + "fmt" + "io/ioutil" + "os" + "path/filepath" + "strings" + + "github.com/google/certificate-transparency-go/x509" + + pb "github.com/google/go-attestation/verifier/proto" +) + +var ( + // brokenCerts are a blacklist of certificate filenames which fail to parse + // due to being malformed. These certs are not processed and hence cannot + // form part of a trusted chain. + brokenCerts = map[string]bool{ + // A number of ST Microelectronics certificates have malformed + // serial number fields. + "STM_TPM_ECC_Intermediate_CA_01.crt": true, + "STM_TPM_EK_Intermediate_CA_01.crt": true, + "STM_TPM_EK_Intermediate_CA_02.crt": true, + "STM_TPM_EK_Intermediate_CA_03.crt": true, + "STM_TPM_EK_Intermediate_CA_04.crt": true, + "STM_TPM_EK_Intermediate_CA_05.crt": true, + } +) + +// EKVerifier verifies x509 EK certificates based on a pool of allowed +// parent certificates. +type EKVerifier struct { + roots, intermediates *x509.CertPool +} + +// VerifyEKCert verifies the properties and provenance of a given EK certificate. +func (v *EKVerifier) VerifyEKCert(certBytes []byte) (*pb.EkcertVerificationResults, error) { + c, err := x509.ParseCertificate(certBytes) + c.UnhandledCriticalExtensions = nil + if err != nil && x509.IsFatal(err) { + return nil, err + } + + chains, verificationErr := c.Verify(x509.VerifyOptions{ + Roots: v.roots, + Intermediates: v.intermediates, + + // Disable checking extensions & key usages, as their application + // appears to be inconsistent, and we only use the certificate + // chains as a means to determine provenance. + KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageAny}, + }) + + out := &pb.EkcertVerificationResults{ + Succeeded: verificationErr == nil, + ChainVerified: verificationErr == nil, + } + if verificationErr != nil { + out.VerificationError = verificationErr.Error() + } else { + for _, cert := range chains[0] { + out.Chain = append(out.Chain, &pb.EkcertVerificationResults_CertSummary{ + IssuerCn: cert.Issuer.CommonName, + IssuerOrg: strings.Join(cert.Issuer.Organization, " "), + Serial: cert.SerialNumber.String(), + }) + } + } + + return out, nil +} + +// NewEKVerifier returns an EKVerifier initialized using the certificates in the specified +// directory. Directories are resolved recursively. +// The specified directory should be structured in the forms: +// /RootCA/.{der,cer,crt) +// /IntermediateCA/.{der,cer,crt) +func NewEKVerifier(certsPath string) (*EKVerifier, error) { + roots := x509.NewCertPool() + intermediates := x509.NewCertPool() + + root, err := ioutil.ReadDir(certsPath) + if err != nil { + return nil, err + } + for _, f := range root { + if !f.IsDir() { + continue + } + if err := readCertificates(filepath.Join(certsPath, f.Name()), roots, intermediates); err != nil { + return nil, err + } + } + + return &EKVerifier{ + roots: roots, + intermediates: intermediates, + }, nil +} + +func readCertificates(dir string, roots, intermediates *x509.CertPool) error { + rootFiles, err := ioutil.ReadDir(filepath.Join(dir, "RootCA")) + if err != nil { + return err + } + if err := parseCertsToPool(filepath.Join(dir, "RootCA"), rootFiles, roots); err != nil { + return err + } + intermediateFiles, err := ioutil.ReadDir(filepath.Join(dir, "IntermediateCA")) + if err != nil { + if os.IsNotExist(err) { + // Not all manufacturers use intermediates certificates. + return nil + } + return err + } + return parseCertsToPool(filepath.Join(dir, "IntermediateCA"), intermediateFiles, intermediates) +} + +func parseCertsToPool(path string, files []os.FileInfo, pool *x509.CertPool) error { + for _, info := range files { + if info.IsDir() { + continue + } + + path := filepath.Join(path, info.Name()) + switch filepath.Ext(info.Name()) { + case ".der": + d, err := ioutil.ReadFile(path) + if err != nil { + return err + } + c, err := x509.ParseCertificate(d) + if err != nil && x509.IsFatal(err) { + if isBrokenCert(info.Name()) { + continue + } + return fmt.Errorf("%s parse failed: %v", info.Name(), err) + } + pool.AddCert(c) + + case ".crt", ".cer": + // Either DER or PEM. + d, err := ioutil.ReadFile(path) + if err != nil { + return err + } + c, err := x509.ParseCertificate(d) + if err != nil && x509.IsFatal(err) && !pool.AppendCertsFromPEM(d) { + if isBrokenCert(info.Name()) { + continue + } + return fmt.Errorf("%s parse failed: %v", info.Name(), err) + } + if err == nil { + pool.AddCert(c) + } + } + } + return nil +} + +func isBrokenCert(fname string) bool { + return brokenCerts[fname] +} diff --git a/verifier/proto/verification.pb.go b/verifier/proto/verification.pb.go new file mode 100644 index 0000000..5415164 --- /dev/null +++ b/verifier/proto/verification.pb.go @@ -0,0 +1,349 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// source: verification.proto + +package go_attestation_verifier + +import ( + fmt "fmt" + proto "github.com/golang/protobuf/proto" + math "math" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package + +type AikVerificationResults struct { + Succeeded bool `protobuf:"varint,1,opt,name=succeeded,proto3" json:"succeeded,omitempty"` + KeyTooSmall bool `protobuf:"varint,2,opt,name=key_too_small,json=keyTooSmall,proto3" json:"key_too_small,omitempty"` + CreationAttestationMismatch bool `protobuf:"varint,3,opt,name=creation_attestation_mismatch,json=creationAttestationMismatch,proto3" json:"creation_attestation_mismatch,omitempty"` + KeyNotTpmBound bool `protobuf:"varint,4,opt,name=key_not_tpm_bound,json=keyNotTpmBound,proto3" json:"key_not_tpm_bound,omitempty"` + KeyUsageOverlyBroad bool `protobuf:"varint,5,opt,name=key_usage_overly_broad,json=keyUsageOverlyBroad,proto3" json:"key_usage_overly_broad,omitempty"` + NameAttestationMismatch bool `protobuf:"varint,6,opt,name=name_attestation_mismatch,json=nameAttestationMismatch,proto3" json:"name_attestation_mismatch,omitempty"` + SignatureMismatch bool `protobuf:"varint,7,opt,name=signature_mismatch,json=signatureMismatch,proto3" json:"signature_mismatch,omitempty"` + RocaVulnerableKey bool `protobuf:"varint,8,opt,name=roca_vulnerable_key,json=rocaVulnerableKey,proto3" json:"roca_vulnerable_key,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *AikVerificationResults) Reset() { *m = AikVerificationResults{} } +func (m *AikVerificationResults) String() string { return proto.CompactTextString(m) } +func (*AikVerificationResults) ProtoMessage() {} +func (*AikVerificationResults) Descriptor() ([]byte, []int) { + return fileDescriptor_69b5d5d3b04d10d4, []int{0} +} + +func (m *AikVerificationResults) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_AikVerificationResults.Unmarshal(m, b) +} +func (m *AikVerificationResults) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_AikVerificationResults.Marshal(b, m, deterministic) +} +func (m *AikVerificationResults) XXX_Merge(src proto.Message) { + xxx_messageInfo_AikVerificationResults.Merge(m, src) +} +func (m *AikVerificationResults) XXX_Size() int { + return xxx_messageInfo_AikVerificationResults.Size(m) +} +func (m *AikVerificationResults) XXX_DiscardUnknown() { + xxx_messageInfo_AikVerificationResults.DiscardUnknown(m) +} + +var xxx_messageInfo_AikVerificationResults proto.InternalMessageInfo + +func (m *AikVerificationResults) GetSucceeded() bool { + if m != nil { + return m.Succeeded + } + return false +} + +func (m *AikVerificationResults) GetKeyTooSmall() bool { + if m != nil { + return m.KeyTooSmall + } + return false +} + +func (m *AikVerificationResults) GetCreationAttestationMismatch() bool { + if m != nil { + return m.CreationAttestationMismatch + } + return false +} + +func (m *AikVerificationResults) GetKeyNotTpmBound() bool { + if m != nil { + return m.KeyNotTpmBound + } + return false +} + +func (m *AikVerificationResults) GetKeyUsageOverlyBroad() bool { + if m != nil { + return m.KeyUsageOverlyBroad + } + return false +} + +func (m *AikVerificationResults) GetNameAttestationMismatch() bool { + if m != nil { + return m.NameAttestationMismatch + } + return false +} + +func (m *AikVerificationResults) GetSignatureMismatch() bool { + if m != nil { + return m.SignatureMismatch + } + return false +} + +func (m *AikVerificationResults) GetRocaVulnerableKey() bool { + if m != nil { + return m.RocaVulnerableKey + } + return false +} + +type QuoteVerificationResults struct { + Succeeded bool `protobuf:"varint,1,opt,name=succeeded,proto3" json:"succeeded,omitempty"` + SignatureMismatch bool `protobuf:"varint,2,opt,name=signature_mismatch,json=signatureMismatch,proto3" json:"signature_mismatch,omitempty"` + PcrDigest []byte `protobuf:"bytes,3,opt,name=pcr_digest,json=pcrDigest,proto3" json:"pcr_digest,omitempty"` + PcrDigestMismatch bool `protobuf:"varint,4,opt,name=pcr_digest_mismatch,json=pcrDigestMismatch,proto3" json:"pcr_digest_mismatch,omitempty"` + NonceMismatch bool `protobuf:"varint,5,opt,name=nonce_mismatch,json=nonceMismatch,proto3" json:"nonce_mismatch,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *QuoteVerificationResults) Reset() { *m = QuoteVerificationResults{} } +func (m *QuoteVerificationResults) String() string { return proto.CompactTextString(m) } +func (*QuoteVerificationResults) ProtoMessage() {} +func (*QuoteVerificationResults) Descriptor() ([]byte, []int) { + return fileDescriptor_69b5d5d3b04d10d4, []int{1} +} + +func (m *QuoteVerificationResults) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_QuoteVerificationResults.Unmarshal(m, b) +} +func (m *QuoteVerificationResults) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_QuoteVerificationResults.Marshal(b, m, deterministic) +} +func (m *QuoteVerificationResults) XXX_Merge(src proto.Message) { + xxx_messageInfo_QuoteVerificationResults.Merge(m, src) +} +func (m *QuoteVerificationResults) XXX_Size() int { + return xxx_messageInfo_QuoteVerificationResults.Size(m) +} +func (m *QuoteVerificationResults) XXX_DiscardUnknown() { + xxx_messageInfo_QuoteVerificationResults.DiscardUnknown(m) +} + +var xxx_messageInfo_QuoteVerificationResults proto.InternalMessageInfo + +func (m *QuoteVerificationResults) GetSucceeded() bool { + if m != nil { + return m.Succeeded + } + return false +} + +func (m *QuoteVerificationResults) GetSignatureMismatch() bool { + if m != nil { + return m.SignatureMismatch + } + return false +} + +func (m *QuoteVerificationResults) GetPcrDigest() []byte { + if m != nil { + return m.PcrDigest + } + return nil +} + +func (m *QuoteVerificationResults) GetPcrDigestMismatch() bool { + if m != nil { + return m.PcrDigestMismatch + } + return false +} + +func (m *QuoteVerificationResults) GetNonceMismatch() bool { + if m != nil { + return m.NonceMismatch + } + return false +} + +type EkcertVerificationResults struct { + Succeeded bool `protobuf:"varint,1,opt,name=succeeded,proto3" json:"succeeded,omitempty"` + ChainVerified bool `protobuf:"varint,2,opt,name=chain_verified,json=chainVerified,proto3" json:"chain_verified,omitempty"` + Chain []*EkcertVerificationResults_CertSummary `protobuf:"bytes,3,rep,name=chain,proto3" json:"chain,omitempty"` + VerificationError string `protobuf:"bytes,4,opt,name=verification_error,json=verificationError,proto3" json:"verification_error,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *EkcertVerificationResults) Reset() { *m = EkcertVerificationResults{} } +func (m *EkcertVerificationResults) String() string { return proto.CompactTextString(m) } +func (*EkcertVerificationResults) ProtoMessage() {} +func (*EkcertVerificationResults) Descriptor() ([]byte, []int) { + return fileDescriptor_69b5d5d3b04d10d4, []int{2} +} + +func (m *EkcertVerificationResults) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_EkcertVerificationResults.Unmarshal(m, b) +} +func (m *EkcertVerificationResults) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_EkcertVerificationResults.Marshal(b, m, deterministic) +} +func (m *EkcertVerificationResults) XXX_Merge(src proto.Message) { + xxx_messageInfo_EkcertVerificationResults.Merge(m, src) +} +func (m *EkcertVerificationResults) XXX_Size() int { + return xxx_messageInfo_EkcertVerificationResults.Size(m) +} +func (m *EkcertVerificationResults) XXX_DiscardUnknown() { + xxx_messageInfo_EkcertVerificationResults.DiscardUnknown(m) +} + +var xxx_messageInfo_EkcertVerificationResults proto.InternalMessageInfo + +func (m *EkcertVerificationResults) GetSucceeded() bool { + if m != nil { + return m.Succeeded + } + return false +} + +func (m *EkcertVerificationResults) GetChainVerified() bool { + if m != nil { + return m.ChainVerified + } + return false +} + +func (m *EkcertVerificationResults) GetChain() []*EkcertVerificationResults_CertSummary { + if m != nil { + return m.Chain + } + return nil +} + +func (m *EkcertVerificationResults) GetVerificationError() string { + if m != nil { + return m.VerificationError + } + return "" +} + +type EkcertVerificationResults_CertSummary struct { + IssuerCn string `protobuf:"bytes,1,opt,name=issuer_cn,json=issuerCn,proto3" json:"issuer_cn,omitempty"` + IssuerOrg string `protobuf:"bytes,2,opt,name=issuer_org,json=issuerOrg,proto3" json:"issuer_org,omitempty"` + Serial string `protobuf:"bytes,3,opt,name=serial,proto3" json:"serial,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *EkcertVerificationResults_CertSummary) Reset() { *m = EkcertVerificationResults_CertSummary{} } +func (m *EkcertVerificationResults_CertSummary) String() string { return proto.CompactTextString(m) } +func (*EkcertVerificationResults_CertSummary) ProtoMessage() {} +func (*EkcertVerificationResults_CertSummary) Descriptor() ([]byte, []int) { + return fileDescriptor_69b5d5d3b04d10d4, []int{2, 0} +} + +func (m *EkcertVerificationResults_CertSummary) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_EkcertVerificationResults_CertSummary.Unmarshal(m, b) +} +func (m *EkcertVerificationResults_CertSummary) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_EkcertVerificationResults_CertSummary.Marshal(b, m, deterministic) +} +func (m *EkcertVerificationResults_CertSummary) XXX_Merge(src proto.Message) { + xxx_messageInfo_EkcertVerificationResults_CertSummary.Merge(m, src) +} +func (m *EkcertVerificationResults_CertSummary) XXX_Size() int { + return xxx_messageInfo_EkcertVerificationResults_CertSummary.Size(m) +} +func (m *EkcertVerificationResults_CertSummary) XXX_DiscardUnknown() { + xxx_messageInfo_EkcertVerificationResults_CertSummary.DiscardUnknown(m) +} + +var xxx_messageInfo_EkcertVerificationResults_CertSummary proto.InternalMessageInfo + +func (m *EkcertVerificationResults_CertSummary) GetIssuerCn() string { + if m != nil { + return m.IssuerCn + } + return "" +} + +func (m *EkcertVerificationResults_CertSummary) GetIssuerOrg() string { + if m != nil { + return m.IssuerOrg + } + return "" +} + +func (m *EkcertVerificationResults_CertSummary) GetSerial() string { + if m != nil { + return m.Serial + } + return "" +} + +func init() { + proto.RegisterType((*AikVerificationResults)(nil), "go_attestation.verifier.AikVerificationResults") + proto.RegisterType((*QuoteVerificationResults)(nil), "go_attestation.verifier.QuoteVerificationResults") + proto.RegisterType((*EkcertVerificationResults)(nil), "go_attestation.verifier.EkcertVerificationResults") + proto.RegisterType((*EkcertVerificationResults_CertSummary)(nil), "go_attestation.verifier.EkcertVerificationResults.CertSummary") +} + +func init() { proto.RegisterFile("verification.proto", fileDescriptor_69b5d5d3b04d10d4) } + +var fileDescriptor_69b5d5d3b04d10d4 = []byte{ + // 494 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x93, 0x41, 0x6e, 0xdb, 0x3c, + 0x10, 0x85, 0x61, 0xfb, 0x8f, 0x7f, 0x6b, 0x5c, 0x1b, 0x30, 0x03, 0x38, 0x4a, 0xd3, 0x00, 0x81, + 0x81, 0x00, 0xe9, 0xa2, 0x5e, 0x34, 0xbb, 0x2e, 0x0a, 0xc4, 0x69, 0x56, 0x45, 0x1b, 0x54, 0x71, + 0xbd, 0x25, 0x68, 0x6a, 0xaa, 0x08, 0x92, 0x48, 0x63, 0x48, 0x19, 0xd0, 0x51, 0x7a, 0xa3, 0x5e, + 0xa2, 0x77, 0x29, 0x48, 0x29, 0xb6, 0x16, 0xf6, 0x22, 0x4b, 0xbd, 0xf7, 0xcd, 0x70, 0x86, 0x4f, + 0x04, 0xb6, 0x45, 0x4a, 0x7f, 0xa5, 0x52, 0xd8, 0x54, 0xab, 0xf9, 0x86, 0xb4, 0xd5, 0xec, 0x2c, + 0xd1, 0x5c, 0x58, 0x8b, 0xc6, 0xd6, 0x6a, 0x8d, 0x20, 0xcd, 0x7e, 0xf7, 0x60, 0x7a, 0x97, 0x66, + 0xab, 0x56, 0x49, 0x84, 0xa6, 0xcc, 0xad, 0x61, 0xef, 0x20, 0x30, 0xa5, 0x94, 0x88, 0x31, 0xc6, + 0x61, 0xe7, 0xaa, 0x73, 0x33, 0x88, 0xf6, 0x02, 0x9b, 0xc1, 0x28, 0xc3, 0x8a, 0x5b, 0xad, 0xb9, + 0x29, 0x44, 0x9e, 0x87, 0x5d, 0x4f, 0x0c, 0x33, 0xac, 0x96, 0x5a, 0x3f, 0x39, 0x89, 0x2d, 0xe0, + 0x52, 0x12, 0xfa, 0xa6, 0xed, 0xd3, 0x79, 0x91, 0x9a, 0x42, 0x58, 0xf9, 0x1c, 0xf6, 0x7c, 0xcd, + 0xc5, 0x0b, 0x74, 0xb7, 0x67, 0xbe, 0x35, 0x08, 0x7b, 0x0f, 0x13, 0x77, 0x8e, 0xd2, 0x96, 0xdb, + 0x4d, 0xc1, 0xd7, 0xba, 0x54, 0x71, 0xf8, 0x9f, 0xaf, 0x1b, 0x67, 0x58, 0x7d, 0xd7, 0x76, 0xb9, + 0x29, 0x16, 0x4e, 0x65, 0xb7, 0x30, 0x75, 0x68, 0x69, 0x44, 0x82, 0x5c, 0x6f, 0x91, 0xf2, 0x8a, + 0xaf, 0x49, 0x8b, 0x38, 0x3c, 0xf1, 0xfc, 0x69, 0x86, 0xd5, 0x4f, 0x67, 0x3e, 0x7a, 0x6f, 0xe1, + 0x2c, 0xf6, 0x09, 0xce, 0x95, 0x28, 0xf0, 0xf0, 0x7c, 0x7d, 0x5f, 0x77, 0xe6, 0x80, 0x43, 0xb3, + 0x7d, 0x00, 0x66, 0xd2, 0x44, 0x09, 0x5b, 0x12, 0xee, 0x8b, 0xfe, 0xf7, 0x45, 0x93, 0x9d, 0xb3, + 0xc3, 0xe7, 0x70, 0x4a, 0x5a, 0x0a, 0xbe, 0x2d, 0x73, 0x85, 0x24, 0xd6, 0x39, 0xf2, 0x0c, 0xab, + 0x70, 0x50, 0xf3, 0xce, 0x5a, 0xed, 0x9c, 0xaf, 0x58, 0xcd, 0xfe, 0x76, 0x20, 0xfc, 0x51, 0x6a, + 0x8b, 0xaf, 0x4f, 0xe7, 0xf0, 0x64, 0xdd, 0x63, 0x93, 0x5d, 0x02, 0x6c, 0x24, 0xf1, 0x38, 0x4d, + 0xd0, 0x58, 0x9f, 0xca, 0x9b, 0x28, 0xd8, 0x48, 0xfa, 0xe2, 0x05, 0x37, 0xf8, 0xde, 0xde, 0xb7, + 0xab, 0x53, 0x98, 0xec, 0xb8, 0x5d, 0xbb, 0x6b, 0x18, 0x2b, 0xad, 0x64, 0xeb, 0xe4, 0x3a, 0x80, + 0x91, 0x57, 0x5f, 0xb0, 0xd9, 0x9f, 0x2e, 0x9c, 0x3f, 0x64, 0x12, 0xc9, 0xbe, 0x7e, 0xc1, 0x6b, + 0x18, 0xcb, 0x67, 0x91, 0x2a, 0xde, 0xfc, 0xc9, 0x71, 0xb3, 0xdc, 0xc8, 0xab, 0xab, 0x46, 0x64, + 0x4b, 0x38, 0xf1, 0x42, 0xd8, 0xbb, 0xea, 0xdd, 0x0c, 0x3f, 0x7e, 0x9e, 0x1f, 0x79, 0x07, 0xf3, + 0xa3, 0x73, 0xcc, 0xef, 0x91, 0xec, 0x53, 0x59, 0x14, 0x82, 0xaa, 0xa8, 0x6e, 0xe6, 0x6e, 0xb7, + 0xfd, 0xc6, 0x38, 0x12, 0x69, 0xf2, 0xd7, 0x11, 0x44, 0x93, 0xb6, 0xf3, 0xe0, 0x8c, 0xb7, 0x02, + 0x86, 0xad, 0x26, 0xec, 0x02, 0x82, 0xd4, 0x98, 0x12, 0x89, 0x4b, 0xe5, 0x17, 0x0b, 0xa2, 0x41, + 0x2d, 0xdc, 0x2b, 0x97, 0x44, 0x63, 0x6a, 0x4a, 0xfc, 0x4e, 0x41, 0xd4, 0xe0, 0x8f, 0x94, 0xb0, + 0x29, 0xf4, 0x0d, 0x52, 0x2a, 0x72, 0x1f, 0x52, 0x10, 0x35, 0x5f, 0xeb, 0xbe, 0x7f, 0xe6, 0xb7, + 0xff, 0x02, 0x00, 0x00, 0xff, 0xff, 0xeb, 0x1a, 0x1f, 0x6c, 0xfc, 0x03, 0x00, 0x00, +} diff --git a/verifier/proto/verification.proto b/verifier/proto/verification.proto new file mode 100644 index 0000000..49b36d1 --- /dev/null +++ b/verifier/proto/verification.proto @@ -0,0 +1,40 @@ +syntax = "proto3"; + +package go_attestation.verifier; + +message AikVerificationResults { + bool succeeded = 1; + + bool key_too_small = 2; + bool creation_attestation_mismatch = 3; + + bool key_not_tpm_bound = 4; + bool key_usage_overly_broad = 5; + + bool name_attestation_mismatch = 6; + + bool signature_mismatch = 7; + + bool roca_vulnerable_key = 8; +} + +message QuoteVerificationResults { + bool succeeded = 1; + bool signature_mismatch = 2; + bytes pcr_digest = 3; + bool pcr_digest_mismatch = 4; + bool nonce_mismatch = 5; +} + +message EkcertVerificationResults { + message CertSummary { + string issuer_cn = 1; + string issuer_org = 2; + string serial = 3; + } + + bool succeeded = 1; + bool chain_verified = 2; + repeated CertSummary chain = 3; + string verification_error = 4; +} diff --git a/verifier/quote.go b/verifier/quote.go new file mode 100644 index 0000000..7ca036f --- /dev/null +++ b/verifier/quote.go @@ -0,0 +1,152 @@ +package verifier + +import ( + "bytes" + "crypto" + "crypto/rsa" + "crypto/sha1" + "fmt" + "sort" + + tpb "github.com/google/go-attestation/proto" + pb "github.com/google/go-attestation/verifier/proto" + tpm1 "github.com/google/go-tpm/tpm" + "github.com/google/go-tpm/tpm2" + "github.com/google/go-tpm/tpmutil" +) + +func cryptoHash(h tpm2.Algorithm) (crypto.Hash, error) { + switch h { + case tpm2.AlgSHA1: + return crypto.SHA1, nil + case tpm2.AlgSHA256: + return crypto.SHA256, nil + case tpm2.AlgSHA384: + return crypto.SHA384, nil + case tpm2.AlgSHA512: + return crypto.SHA512, nil + default: + return crypto.Hash(0), fmt.Errorf("unsupported signature digest: %v", h) + } +} + +// VerifyQuote returns information about the validity of a quote & signature. +func VerifyQuote(tpmVersion tpb.TpmVersion, public, attestationData, signature []byte, pcrs map[uint32][]byte, nonce []byte) (*pb.QuoteVerificationResults, error) { + var ( + pcrDigestMatched bool + nonceMatched bool + verifyErr error + digest []byte + ) + if len(signature) < 8 { + return nil, fmt.Errorf("signature is too short to be valid: only %d bytes", len(signature)) + } + + switch tpmVersion { + case tpb.TpmVersion_TPM_20: + var compositeHash crypto.Hash + var verifyHash crypto.Hash + pub, err := tpm2.DecodePublic(public) + if err != nil { + return nil, err + } + + att, err := tpm2.DecodeAttestationData(attestationData) + if err != nil { + return nil, err + } + if att.Type != tpm2.TagAttestQuote { + return nil, fmt.Errorf("attestation is tagged %x, want TagAttestQuote", att.Type) + } + digest = att.AttestedQuoteInfo.PCRDigest + + // Compute the digest of PCR values based on the provided individual PCR values. + compositeHash, err = cryptoHash(pub.RSAParameters.Sign.Hash) + if err != nil { + return nil, err + } + + var compositeData []byte + for _, pcr := range att.AttestedQuoteInfo.PCRSelection.PCRs { + digest, ok := pcrs[uint32(pcr)] + if !ok { + return nil, fmt.Errorf("PCR %d missing but used to compute PCR digest", pcr) + } + compositeData = append(compositeData, digest...) + } + compositeDigest := compositeHash.New() + compositeDigest.Write(compositeData) + pcrDigestMatched = bytes.Equal(compositeDigest.Sum(nil), digest) + + // Check the signature over the attestation data verifies correctly. + p := rsa.PublicKey{E: int(pub.RSAParameters.Exponent), N: pub.RSAParameters.Modulus} + signHashConstructor, err := pub.RSAParameters.Sign.Hash.HashConstructor() + if err != nil { + return nil, err + } + hsh := signHashConstructor() + hsh.Write(attestationData) + + verifyHash, err = cryptoHash(pub.RSAParameters.Sign.Hash) + if err != nil { + return nil, err + } + + nonceMatched = bytes.Equal(att.ExtraData, nonce) + + //TODO(jsonp): Decode to tpm2.Signature & use that, once PR to expose DecodeSignature() is in. + verifyErr = rsa.VerifyPKCS1v15(&p, verifyHash, hsh.Sum(nil), signature[6:]) + case tpb.TpmVersion_TPM_12: + p, err := tpm1.UnmarshalPubRSAPublicKey(public) + if err != nil { + return nil, err + } + digest = attestationData + pcrNums := sortPCRs(pcrs) + compositeData := []byte{} + for _, pcr := range pcrNums { + compositeData = append(compositeData, pcrs[uint32(pcr)]...) + } + composite, err := tpmutil.Pack(&struct { + Mask tpmutil.U16Bytes + Data tpmutil.U32Bytes + }{ + Mask: []byte{0xff, 0xff, 0xff}, + Data: compositeData, + }) + + info := struct { + Version [4]byte + QUOT [4]byte + Digest [20]byte + Nonce [20]byte + }{} + if _, err = tpmutil.Unpack(attestationData, &info); err != nil { + return nil, err + } + pcrDigestMatched = sha1.Sum(composite) == info.Digest + nonceMatched = sha1.Sum(nonce) == info.Nonce + + verifyErr = tpm1.VerifyQuote(p, nonce, signature, pcrNums, compositeData) + default: + return nil, fmt.Errorf("TPM version %d not supported", tpmVersion) + } + return &pb.QuoteVerificationResults{ + SignatureMismatch: verifyErr != nil, + Succeeded: verifyErr == nil && pcrDigestMatched && nonceMatched, + PcrDigest: digest, + PcrDigestMismatch: !pcrDigestMatched, + NonceMismatch: !nonceMatched, + }, nil +} + +func sortPCRs(pcrs map[uint32][]byte) []int { + pcrNums := []int{} + for pcr := range pcrs { + pcrNums = append(pcrNums, int(pcr)) + } + sort.Slice(pcrNums, func(i int, j int) bool { + return pcrNums[i] < pcrNums[j] + }) + return pcrNums +} diff --git a/verifier/roca.go b/verifier/roca.go new file mode 100644 index 0000000..c312933 --- /dev/null +++ b/verifier/roca.go @@ -0,0 +1,74 @@ +package verifier + +import ( + "crypto/rsa" + "math/big" +) + +var ( + // Derived from resources published as: + // "ROCA: Vulnerable RSA generation (CVE-2017-15361)". + // Czech Republic: Centre for Research on Cryptography and Security, Faculty of Informatics, Masaryk University. + + fingerprintPrimes = []int64{3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, + 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, + 151, 157, 163, 167} + fingerprintMasks = []string{ + "6", + "30", + "126", + "1026", + "5658", + "107286", + "199410", + "8388606", + "536870910", + "2147483646", + "67109890", + "2199023255550", + "8796093022206", + "140737488355326", + "5310023542746834", + "576460752303423486", + "1455791217086302986", + "147573952589676412926", + "20052041432995567486", + "6041388139249378920330", + "207530445072488465666", + "9671406556917033397649406", + "618970019642690137449562110", + "79228162521181866724264247298", + "2535301200456458802993406410750", + "1760368345969468176824550810518", + "50079290986288516948354744811034", + "473022961816146413042658758988474", + "10384593717069655257060992658440190", + "144390480366845522447407333004847678774", + "2722258935367507707706996859454145691646", + "174224571863520493293247799005065324265470", + "696898287454081973172991196020261297061886", + "713623846352979940529142984724747568191373310", + "1800793591454480341970779146165214289059119882", + "126304807362733370595828809000324029340048915994", + "11692013098647223345629478661730264157247460343806", + "187072209578355573530071658587684226515959365500926", + } +) + +// ROCAVulnerableKey returns true if the key is vulnerable to ROCA. +func ROCAVulnerableKey(k *rsa.PublicKey) bool { + for i := range fingerprintPrimes { + n := &big.Int{} + n.Mod(k.N, big.NewInt(fingerprintPrimes[i])) + n.Lsh(big.NewInt(1), uint(n.Uint64())) + + mask := &big.Int{} + mask.SetString(fingerprintMasks[i], 10) + n.And(n, mask) + + if n.Cmp(big.NewInt(0)) == 0 { + return false + } + } + return true +} diff --git a/verifier/verifier_test.go b/verifier/verifier_test.go new file mode 100644 index 0000000..d1f639f --- /dev/null +++ b/verifier/verifier_test.go @@ -0,0 +1,242 @@ +package verifier + +import ( + "crypto/rsa" + "encoding/base64" + "encoding/hex" + "fmt" + "io" + "math/big" + "testing" + + tpb "github.com/google/go-attestation/proto" + "github.com/google/go-tpm-tools/simulator" + "github.com/google/go-tpm/tpm2" + "github.com/google/go-tpm/tpmutil" +) + +func decodeBase64(in string, t *testing.T) []byte { + out, err := base64.StdEncoding.DecodeString(in) + if err != nil { + t.Fatal(err) + } + return out +} + +func TestVerifyAIK(t *testing.T) { + pub := decodeBase64("AAEACwAFBHIAIJ3/y/NsODrmmfuYaNxty4nXFTiEvigDkiwSQVi/rSKuABAAFAAECAAAAAAAAQC/08gj/04z4xGMIVTmr02lzhI5epufXgU831xEpf2qpXfvtNGUfqTcgWF2EUux2HDPqgcj59dtXRobQdlr4uCGNzfZIGAej4JusLa4MjpG6W2DtJPot6F1Mry63talzJ36U47niy9Iesd34CO2p9Xk3+86ZmBnQ6PQ2roUNK3l7bKz6cFLM9drOLwCqU0AUl6pHvzYPPz+xXsPl3iaA2cM97oneUiJNmJM7wtR9OcaKyIA4wVlX5TndB9NwWq5Iuj8q2Sp40Dg0noXXGSPliAtVD8flkXtAcuI9UHkQbzu9cGPRdSJPMn743GONg3bYalFtcgh2VpACXkPbXB32J7B", t) + creation := decodeBase64("AAAAAAAg47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFUBAAsAIgALWI9hwDRB3zYSkannqM5z0J1coQNA1Jz/oCRxJQwTaNwAIgALmyFYBhHeIU3FUKIAPgXFD3NXyasP3siQviDEyH7avu4AAA==", t) + attest := decodeBase64("/1RDR4AaACIAC41+jhmEOue1MZhJjIk79ENar6i15rBvamXLpQnGTBCOAAAAAAAAD3GRNfU4syzJ1jQGATDCDteFC5C4ACIAC3ToMYGy9GXxcf8A0HvOuLOHbU7HPEppM47C7CMcU8TtACBDmJFUFO1f5+BYevaYdd3VtfMCsxIuHhoTZJczzLP2BA==", t) + sig := decodeBase64("ABQABAEALVzJSnKRJU39gHjETaI89/sM1L6HwBPGNekw6NojSW8bwD5/W1cLRDakCsYKUQu68mmbjs8xaIVBRvVM2YWP10tbTWNB0iJc9b8rERhkk3QIIFm/XsiVZsb0mysTxfeh8zygaAKQ/50sYyzp+raD0Ho0mYIRKJOEdQ6chsBflM3eB8mCXGTugUfrET80q3iu0gncaKWbfxQaQUb9ZTPSJrTN64HQ9tlOfnGT+8++WA3hV0NqKMnoAqiI9GZnI5MPXs6XxEncu/GJLJpAYZakBiS74Jvlr34Pur32B4xjm1M25AUGHEIgb6r49S0sV+hzaKu45858lQRMXj01GcyBhw==", t) + + verificationResults, err := VerifyAIK(2, &tpb.AikInfo{ + TpmAikInfo: &tpb.AikInfo_Tpm20{ + Tpm20: &tpb.Tpm20AikInfo{ + PublicBlob: pub, + CreationData: creation, + AttestationData: attest, + SignatureData: sig, + }, + }, + }) + if err != nil { + t.Fatalf("VerifyAIK() returned err: %v", err) + } + if !verificationResults.GetSucceeded() { + t.Errorf("verification.Succeeded = %v, want true", verificationResults.GetSucceeded()) + } +} + +func setupSimulatedTPM(t *testing.T) *simulator.Simulator { + t.Helper() + tpm, err := simulator.Get() + if err != nil { + t.Fatal(err) + } + return tpm +} + +func allPCRs(tpm io.ReadWriter, hash tpm2.Algorithm) (map[uint32][]byte, error) { + numPCRs := 24 + out := map[uint32][]byte{} + + // The TPM 2.0 spec says that the TPM can partially fulfill the + // request. As such, we repeat the command up to 8 times to get all + // 24 PCRs. + for i := 0; i < numPCRs; i++ { + // Build a selection structure, specifying all PCRs we do + // not have the value for. + sel := tpm2.PCRSelection{Hash: hash} + for pcr := 0; pcr < numPCRs; pcr++ { + if _, present := out[uint32(pcr)]; !present { + sel.PCRs = append(sel.PCRs, pcr) + } + } + + // Ask the TPM for those PCR values. + ret, err := tpm2.ReadPCRs(tpm, sel) + if err != nil { + return nil, fmt.Errorf("tpm2.ReadPCRs(%+v) failed with err: %v", sel, err) + } + // Keep track of the PCRs we were actually given. + for pcr, digest := range ret { + out[uint32(pcr)] = digest + } + if len(out) == numPCRs { + break + } + } + + if len(out) != numPCRs { + return nil, fmt.Errorf("failed to read all PCRs, only read %d", len(out)) + } + + return out, nil +} + +func TestVerifyQuoteTPM20(t *testing.T) { + tpm := setupSimulatedTPM(t) + defer tpm.Close() + if err := tpm.ManufactureReset(); err != nil { + t.Fatalf("Failed to reset TPM: %v", err) + } + + // Create the attestation key. + keyHandle, pub, _, _, _, _, err := tpm2.CreatePrimaryEx(tpm, tpm2.HandleEndorsement, tpm2.PCRSelection{}, "", "", tpm2.Public{ + Type: tpm2.AlgRSA, + NameAlg: tpm2.AlgSHA256, + Attributes: tpm2.FlagSignerDefault | tpm2.FlagNoDA, + RSAParameters: &tpm2.RSAParams{ + Sign: &tpm2.SigScheme{ + Alg: tpm2.AlgRSASSA, + Hash: tpm2.AlgSHA256, + }, + KeyBits: 2048, + Modulus: big.NewInt(0), + }, + }) + if err != nil { + t.Fatalf("CreatePrimaryEx() failed: %v", err) + } + defer tpm2.FlushContext(tpm, keyHandle) + + for _, alg := range []tpm2.Algorithm{tpm2.AlgSHA1, tpm2.AlgSHA256} { + t.Run(fmt.Sprintf("Alg %x", alg), func(t *testing.T) { + // Generate the quote. + sel := tpm2.PCRSelection{Hash: alg} + numPCRs := 24 + for pcr := 0; pcr < numPCRs; pcr++ { + sel.PCRs = append(sel.PCRs, pcr) + } + nonce := []byte{1, 2, 3, 4} + quote, qSig, err := tpm2.Quote(tpm, keyHandle, "", "", nonce, sel, tpm2.AlgNull) + if err != nil { + t.Fatalf("tpm2.Quote() failed: %v", err) + } + sig, err := tpmutil.Pack(qSig.Alg, qSig.RSA.HashAlg, qSig.RSA.Signature) + if err != nil { + t.Fatalf("tpmutil.Pack() failed: %v", err) + } + + PCRs, err := allPCRs(tpm, alg) + if err != nil { + t.Fatalf("allPCRs() failed: %v", err) + } + + verificationResults, err := VerifyQuote(2, pub, quote, sig, PCRs, nonce) + if err != nil { + t.Errorf("VerifyQuote failed: %v", err) + } + if !verificationResults.Succeeded { + t.Logf("Verification results: %+v", verificationResults) + t.Errorf("verificationResults.succeeded = %v, expected true", verificationResults.Succeeded) + } + }) + } +} + +func TestRoca(t *testing.T) { + key := &rsa.PublicKey{N: &big.Int{}} + key.N.SetString("944e13208a280c37efc31c3114485e590192adbb8e11c87cad60cdef0037ce99278330d3f471a2538fa667802ed2a3c44a8b7dea826e888d0aa341fd664f7fa7", 16) + + if !ROCAVulnerableKey(key) { + t.Errorf("ROCAVulnerableKey() = %v, wanted true", ROCAVulnerableKey(key)) + } +} + +func TestVerifyQuoteTPM12(t *testing.T) { + tcs := []struct { + PublicHex string + QuoteHex string + SignatureHex string + Nonce []byte + PCRs map[int]string + }{ + { + PublicHex: "00000001000100020000000c00000800000000020000000000000100be855eadb504443ec1a85f5894cf9ae6b97fe75c39debe2376d13e49632ea34dc917c99f0ea29c52349eba9b1abfd2a92e814057568338ea68a32a45f92ae23944d0765805489414f9c588778220a3f384b7b2c4be8132515e276eefde7cb807303f7a7d57900f94dda27e6abe5e411026b8be7637483747073fa731643807e4c3d7e6fdad0ea297beaaeb208465aa4906447fcddf1955f5ac0a439295f7b43fbe38d018009456c17426e4ebf1581c99e3a97ff151a0c649335a46ec8189849b4efe932cb3a7d57e2ee45e67a7fcb64da5041604f24fd6153898fbe5d8432d95b2ad5d4b89088f6306f6b1a7d8c55c748838a96d106efc39ce119b11ac51211b", + QuoteHex: "0101000051554f54d45a9b15807eac8d85cd467ec0060815cc687c18a8b93257fea90bba13ede78f0db2d81ae4173c19", + SignatureHex: "33a4260f049c64f7539ab5a5f5adf1c87fc31d9ae5165340636ba96c88b82eb402b46902315c65d0d8b7a861ec8cd3f0d1bb7d264420cb7dca8e3d5862c5ecf5114f3bde50890cbf8c05b95fb0f2c70c816f9e86f247c4377aa58e84f24e6a910ea414664b9cdd1ff4a3522994ec1e1f419a2e1e3f503689e4eb0606c0b3e9a42f4f7b74c937d4d061161390e1790b6561b0d288e3534fd1b62af6a8c232174e1cb1586b863bd20e95e73e52e27a0781c7160672257831eb9b1d5192098495ad2170490c5e52693385e43aeab95069eecdd3f80529fdcc7ff2ef6086c24c06576e53b77e2f88eafb3e9b9fd40d954a7e0ab4c01e5b9a73d5d1841c49924beb64", + Nonce: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, + PCRs: map[int]string{ + 0: "b777654263752ed0bfe13b369cf512b4661eee04", + 1: "c2b93db7e6f705f98419a35cc6e46deb639c6a28", + 2: "b2a83b0ebf2f8374299a5b2bdfc31ea955ad7236", + 3: "b2a83b0ebf2f8374299a5b2bdfc31ea955ad7236", + 4: "8f3fe5e128e3f2186bfca0b804900ec9ded96e39", + 5: "45a323382bd933f08e7f0e256bc8249e4095b1ec", + 6: "ee1b0f997d7517b286bc9d73a4cf742c65a769be", + 7: "9d42f8fe8bfd73fba0274c7db891d91fb7861562", + 8: "0000000000000000000000000000000000000000", + 9: "0000000000000000000000000000000000000000", + 10: "0000000000000000000000000000000000000000", + 11: "ebb98df76613280f20dc38221143a9e727399486", + 12: "575e12d9ec16d6512648f25b65d38b4eb27a2d6d", + 13: "a9f1781a95d2e37970d1d73c463157fa016d9858", + 14: "fc76feaf714c844cc888ea454ddf97c0ed220b61", + 15: "0000000000000000000000000000000000000000", + 16: "0000000000000000000000000000000000000000", + 17: "ffffffffffffffffffffffffffffffffffffffff", + 18: "ffffffffffffffffffffffffffffffffffffffff", + 19: "ffffffffffffffffffffffffffffffffffffffff", + 20: "ffffffffffffffffffffffffffffffffffffffff", + 21: "ffffffffffffffffffffffffffffffffffffffff", + 22: "ffffffffffffffffffffffffffffffffffffffff", + 23: "0000000000000000000000000000000000000000", + }, + }, + } + + for i, testcase := range tcs { + tc := testcase + t.Run(fmt.Sprintf("case %d", i), func(t *testing.T) { + pub, err := hex.DecodeString(tc.PublicHex) + if err != nil { + t.Fatal(err) + } + quote, err := hex.DecodeString(tc.QuoteHex) + if err != nil { + t.Fatal(err) + } + sig, err := hex.DecodeString(tc.SignatureHex) + if err != nil { + t.Fatal(err) + } + + pcrs := make(map[uint32][]byte) + for idx, h := range tc.PCRs { + pcrs[uint32(idx)], err = hex.DecodeString(h) + if err != nil { + t.Fatal(err) + } + } + + verificationResults, err := VerifyQuote(1, pub, quote, sig, pcrs, tc.Nonce) + if err != nil { + t.Errorf("VerifyQuote failed: %v", err) + } + if !verificationResults.Succeeded { + t.Errorf("verificationResults.Succeeded = %v, want %v", verificationResults.Succeeded, true) + } + }) + } +}