./0000775000000000000000000000000000000000000007344 5ustar0000000000000000./github.com/0000775000000000000000000000000000000000000011403 5ustar0000000000000000./github.com/canonical/0000775000000000000000000000000000000000000013332 5ustar0000000000000000./github.com/canonical/go-efilib/0000775000000000000000000000000000000000000015167 5ustar0000000000000000./github.com/canonical/go-efilib/.gitignore0000664000000000000000000000004200000000000017153 0ustar0000000000000000cmd/efi_devicepath/efi_devicepath ./github.com/canonical/go-efilib/LICENSE0000664000000000000000000002150100000000000016173 0ustar0000000000000000All files in this repository are licensed as follows. If you contribute to this repository, it is assumed that you license your contribution under the same license unless you state otherwise. All files Copyright (C) 2020 Canonical Ltd. unless otherwise specified in the file. This software is licensed under the LGPLv3, included below. As a special exception to the GNU Lesser General Public License version 3 ("LGPL3"), the copyright holders of this Library give you permission to convey to a third party a Combined Work that links statically or dynamically to this Library without providing any Minimal Corresponding Source or Minimal Application Code as set out in 4d or providing the installation information set out in section 4e, provided that you comply with the other provisions of LGPL3 and provided that you meet, for the Application the terms and conditions of the license(s) which apply to the Application. Except as stated in this special exception, the provisions of LGPL3 will continue to comply in full to this Library. If you modify this Library, you may apply this exception to your version of this Library, but you are not obliged to do so. If you do not wish to do so, delete this exception statement from your version. This exception does not (and cannot) modify any license terms which apply to the Application, with which you must still comply. GNU LESSER GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. This version of the GNU Lesser General Public License incorporates the terms and conditions of version 3 of the GNU General Public License, supplemented by the additional permissions listed below. 0. Additional Definitions. As used herein, "this License" refers to version 3 of the GNU Lesser General Public License, and the "GNU GPL" refers to version 3 of the GNU General Public License. "The Library" refers to a covered work governed by this License, other than an Application or a Combined Work as defined below. An "Application" is any work that makes use of an interface provided by the Library, but which is not otherwise based on the Library. Defining a subclass of a class defined by the Library is deemed a mode of using an interface provided by the Library. A "Combined Work" is a work produced by combining or linking an Application with the Library. The particular version of the Library with which the Combined Work was made is also called the "Linked Version". The "Minimal Corresponding Source" for a Combined Work means the Corresponding Source for the Combined Work, excluding any source code for portions of the Combined Work that, considered in isolation, are based on the Application, and not on the Linked Version. The "Corresponding Application Code" for a Combined Work means the object code and/or source code for the Application, including any data and utility programs needed for reproducing the Combined Work from the Application, but excluding the System Libraries of the Combined Work. 1. Exception to Section 3 of the GNU GPL. You may convey a covered work under sections 3 and 4 of this License without being bound by section 3 of the GNU GPL. 2. Conveying Modified Versions. If you modify a copy of the Library, and, in your modifications, a facility refers to a function or data to be supplied by an Application that uses the facility (other than as an argument passed when the facility is invoked), then you may convey a copy of the modified version: a) under this License, provided that you make a good faith effort to ensure that, in the event an Application does not supply the function or data, the facility still operates, and performs whatever part of its purpose remains meaningful, or b) under the GNU GPL, with none of the additional permissions of this License applicable to that copy. 3. Object Code Incorporating Material from Library Header Files. The object code form of an Application may incorporate material from a header file that is part of the Library. You may convey such object code under terms of your choice, provided that, if the incorporated material is not limited to numerical parameters, data structure layouts and accessors, or small macros, inline functions and templates (ten or fewer lines in length), you do both of the following: a) Give prominent notice with each copy of the object code that the Library is used in it and that the Library and its use are covered by this License. b) Accompany the object code with a copy of the GNU GPL and this license document. 4. Combined Works. You may convey a Combined Work under terms of your choice that, taken together, effectively do not restrict modification of the portions of the Library contained in the Combined Work and reverse engineering for debugging such modifications, if you also do each of the following: a) Give prominent notice with each copy of the Combined Work that the Library is used in it and that the Library and its use are covered by this License. b) Accompany the Combined Work with a copy of the GNU GPL and this license document. c) For a Combined Work that displays copyright notices during execution, include the copyright notice for the Library among these notices, as well as a reference directing the user to the copies of the GNU GPL and this license document. d) Do one of the following: 0) Convey the Minimal Corresponding Source under the terms of this License, and the Corresponding Application Code in a form suitable for, and under terms that permit, the user to recombine or relink the Application with a modified version of the Linked Version to produce a modified Combined Work, in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source. 1) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (a) uses at run time a copy of the Library already present on the user's computer system, and (b) will operate properly with a modified version of the Library that is interface-compatible with the Linked Version. e) Provide Installation Information, but only if you would otherwise be required to provide such information under section 6 of the GNU GPL, and only to the extent that such information is necessary to install and execute a modified version of the Combined Work produced by recombining or relinking the Application with a modified version of the Linked Version. (If you use option 4d0, the Installation Information must accompany the Minimal Corresponding Source and Corresponding Application Code. If you use option 4d1, you must provide the Installation Information in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source.) 5. Combined Libraries. You may place library facilities that are a work based on the Library side by side in a single library together with other library facilities that are not Applications and are not covered by this License, and convey such a combined library under terms of your choice, if you do both of the following: a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities, conveyed under the terms of this License. b) Give prominent notice with the combined library that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. 6. Revised Versions of the GNU Lesser General Public License. The Free Software Foundation may publish revised and/or new versions of the GNU Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Library as you received it specifies that a certain numbered version of the GNU Lesser General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that published version or of any later version published by the Free Software Foundation. If the Library as you received it does not specify a version number of the GNU Lesser General Public License, you may choose any version of the GNU Lesser General Public License ever published by the Free Software Foundation. If the Library as you received it specifies that a proxy can decide whether future versions of the GNU Lesser General Public License shall apply, that proxy's public statement of acceptance of any version is permanent authorization for you to choose that version for the Library. ./github.com/canonical/go-efilib/authvars.go0000664000000000000000000002425600000000000017364 0ustar0000000000000000// Copyright 2020 Canonical Ltd. // Licensed under the LGPLv3 with static-linking exception. // See LICENCE file for details. package efi import ( "bytes" "encoding/binary" "errors" "io" "time" "github.com/canonical/go-efilib/internal/ioerr" "github.com/canonical/go-efilib/internal/uefi" ) // VariableAuthentication correspond to the EFI_VARIABLE_AUTHENTICATION type and is provided as a header when updating a variable with // the EFI_VARIABLE_AUTHENTICATED_WRITE_ACCESS attribute set. type VariableAuthentication struct { MonotonicCount uint64 AuthInfo WinCertificateGUID } func (a *VariableAuthentication) Write(w io.Writer) error { desc := uefi.EFI_VARIABLE_AUTHENTICATION{ MonotonicCount: a.MonotonicCount, AuthInfo: *a.AuthInfo.toUefiType()} return binary.Write(w, binary.LittleEndian, desc) } // ReadVariableAuthentication decodes a header for updating a variable with the EFI_VARIABLE_AUTHENTICATED_WRITE_ACCESS attribute // set. func ReadVariableAuthentication(r io.Reader) (*VariableAuthentication, error) { desc, err := uefi.Read_EFI_VARIABLE_AUTHENTICATION(r) if err != nil { return nil, err } return &VariableAuthentication{ MonotonicCount: desc.MonotonicCount, AuthInfo: *newWinCertificateGUID(&desc.AuthInfo)}, nil } // VariableAuthentication2 correspond to the EFI_VARIABLE_AUTHENTICATION_2 type and is provided as a header when updating a variable // with the EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS attribute set. type VariableAuthentication2 struct { TimeStamp time.Time AuthInfo WinCertificateGUID } func (a *VariableAuthentication2) Write(w io.Writer) error { desc := uefi.EFI_VARIABLE_AUTHENTICATION_2{ TimeStamp: *uefi.New_EFI_TIME(a.TimeStamp), AuthInfo: *a.AuthInfo.toUefiType()} return binary.Write(w, binary.LittleEndian, desc) } // ReadTimeBasedVariableAuthentication decodes the header for updating a variable with the // EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS attribute set. func ReadTimeBasedVariableAuthentication(r io.Reader) (*VariableAuthentication2, error) { desc, err := uefi.Read_EFI_VARIABLE_AUTHENTICATION_2(r) if err != nil { return nil, err } return &VariableAuthentication2{ TimeStamp: desc.TimeStamp.GoTime(), AuthInfo: *newWinCertificateGUID(&desc.AuthInfo)}, nil } // VariableAuthentication3 represents the header for updating a variable with the EFI_VARIABLE_ENHANCED_AUTHENTICATED_ACCESS // attribute set. type VariableAuthentication3 interface{} // VariableAuthentication3Timestamp corresponds to the header for updating a variable with the // EFI_VARIABLE_ENHANCED_AUTHENTICATED_ACCESS attribute set, and a type of EFI_VARIABLE_AUTHENTICATION_3_TIMESTAMP_TYPE. type VariableAuthentication3Timestamp struct { TimeStamp time.Time NewCert *WinCertificateGUID SigningCert WinCertificateGUID } func (a *VariableAuthentication3Timestamp) Write(w io.Writer) error { var buf bytes.Buffer t := uefi.New_EFI_TIME(a.TimeStamp) if err := binary.Write(&buf, binary.LittleEndian, &t); err != nil { panic(err) } if a.NewCert != nil { if err := binary.Write(&buf, binary.LittleEndian, a.NewCert.toUefiType()); err != nil { panic(err) } } if err := binary.Write(&buf, binary.LittleEndian, a.SigningCert.toUefiType()); err != nil { panic(err) } hdr := uefi.EFI_VARIABLE_AUTHENTICATION_3{ Version: 1, Type: uefi.EFI_VARIABLE_AUTHENTICATION_3_TIMESTAMP_TYPE} hdr.MetadataSize = uint32(binary.Size(hdr) + buf.Len()) if a.NewCert != nil { hdr.Flags = 1 } if err := binary.Write(w, binary.LittleEndian, &hdr); err != nil { return err } _, err := buf.WriteTo(w) return err } // VariableAuthentication3Nonce corresponds to the header for updating a variable with the // EFI_VARIABLE_ENHANCED_AUTHENTICATED_ACCESS attribute set, and a type of EFI_VARIABLE_AUTHENTICATION_3_NONCE_TYPE. type VariableAuthentication3Nonce struct { Nonce []byte NewCert *WinCertificateGUID SigningCert WinCertificateGUID } func (a *VariableAuthentication3Nonce) Write(w io.Writer) error { var buf bytes.Buffer n := uefi.EFI_VARIABLE_AUTHENTICATION_3_NONCE{ NonceSize: uint32(len(a.Nonce)), Nonce: a.Nonce} if err := binary.Write(&buf, binary.LittleEndian, &n); err != nil { panic(err) } if a.NewCert != nil { if err := binary.Write(&buf, binary.LittleEndian, a.NewCert.toUefiType()); err != nil { panic(err) } } if err := binary.Write(&buf, binary.LittleEndian, a.SigningCert.toUefiType()); err != nil { panic(err) } hdr := uefi.EFI_VARIABLE_AUTHENTICATION_3{ Version: 1, Type: uefi.EFI_VARIABLE_AUTHENTICATION_3_NONCE_TYPE} hdr.MetadataSize = uint32(binary.Size(hdr) + buf.Len()) if a.NewCert != nil { hdr.Flags = 1 } if err := binary.Write(w, binary.LittleEndian, &hdr); err != nil { return err } _, err := buf.WriteTo(w) return err } // ReadEnhancedVariableAuthentication decodes the header for updating a variable with the // EFI_VARIABLE_ENHANCED_AUTHENTICATED_ACCESS attribute set. func ReadEnhancedVariableAuthentication(r io.Reader) (VariableAuthentication3, error) { var hdr uefi.EFI_VARIABLE_AUTHENTICATION_3 if err := binary.Read(r, binary.LittleEndian, &hdr); err != nil { return nil, err } if hdr.Version != 1 { return nil, errors.New("unexpected version") } lr := io.LimitReader(r, int64(hdr.MetadataSize)-int64(binary.Size(hdr))) switch hdr.Type { case uefi.EFI_VARIABLE_AUTHENTICATION_3_TIMESTAMP_TYPE: var t uefi.EFI_TIME if err := binary.Read(lr, binary.LittleEndian, &t); err != nil { return nil, ioerr.EOFIsUnexpected("cannot read timestamp authentication: %w", err) } var newCert *uefi.WIN_CERTIFICATE_UEFI_GUID if hdr.Flags&1 > 0 { cert, err := uefi.Read_WIN_CERTIFICATE_UEFI_GUID(r) if err != nil { return nil, ioerr.EOFIsUnexpected("cannot read timestamp authentication: %w", err) } newCert = cert } signingCert, err := uefi.Read_WIN_CERTIFICATE_UEFI_GUID(r) if err != nil { return nil, ioerr.EOFIsUnexpected("cannot read timestamp authentication: %w", err) } out := &VariableAuthentication3Timestamp{ TimeStamp: t.GoTime(), SigningCert: *newWinCertificateGUID(signingCert)} if newCert != nil { out.NewCert = newWinCertificateGUID(newCert) } return out, nil case uefi.EFI_VARIABLE_AUTHENTICATION_3_NONCE_TYPE: n, err := uefi.Read_EFI_VARIABLE_AUTHENTICATION_3_NONCE(r) if err != nil { return nil, ioerr.EOFIsUnexpected("cannot read nonce authentication: %w", err) } var newCert *uefi.WIN_CERTIFICATE_UEFI_GUID if hdr.Flags&1 > 0 { cert, err := uefi.Read_WIN_CERTIFICATE_UEFI_GUID(r) if err != nil { return nil, ioerr.EOFIsUnexpected("cannot read nonce authentication: %w", err) } newCert = cert } signingCert, err := uefi.Read_WIN_CERTIFICATE_UEFI_GUID(r) if err != nil { return nil, ioerr.EOFIsUnexpected("cannot read nonce authentication: %w", err) } out := &VariableAuthentication3Nonce{ Nonce: n.Nonce, SigningCert: *newWinCertificateGUID(signingCert)} if newCert != nil { out.NewCert = newWinCertificateGUID(newCert) } return out, nil default: return nil, errors.New("unexpected type") } } // VariableAuthentication3Descriptor corresponds to the authentication descriptor provided when reading the payload of a variable // with the EFI_VARIABLE_ENHANCED_AUTHENTICATED_ACCESS attribute set. type VariableAuthentication3Descriptor interface{} const ( VariableAuthentication3CertIDSHA256 = uefi.EFI_VARIABLE_AUTHENTICATION_3_CERT_ID_SHA256 ) type VariableAuthentication3CertId struct { Type uint8 Id []byte } func newVariableAuthentication3CertId(id *uefi.EFI_VARIABLE_AUTHENTICATION_3_CERT_ID) *VariableAuthentication3CertId { return &VariableAuthentication3CertId{ Type: id.Type, Id: id.Id} } // VariableAuthentication3TimestampDescriptor corresponds to the authentication descriptor provided when reading the payload of a // variable with the EFI_VARIABLE_ENHANCED_AUTHENTICATED_ACCESS attribute set, and a type of // EFI_VARIABLE_AUTHENTICATION_3_TIMESTAMP_TYPE. type VariableAuthentication3TimestampDescriptor struct { TimeStamp time.Time VariableAuthentication3CertId } // VariableAuthentication3NonceDescriptor corresponds to the authentication descriptor provided when reading the payload of a // variable with the EFI_VARIABLE_ENHANCED_AUTHENTICATED_ACCESS attribute set, and a type of // EFI_VARIABLE_AUTHENTICATION_3_NONCE_TYPE. type VariableAuthentication3NonceDescriptor struct { Nonce []byte VariableAuthentication3CertId } // ReadEnhancedAuthenticationDescriptor decodes the enhanced authentication descriptor from the supplied io.Reader. The supplied // reader will typically read from the payload area of a variable with the EFI_VARIABLE_ENHANCED_AUTHENTICATION_ACCESS attribute // set. func ReadEnhancedAuthenticationDescriptor(r io.Reader) (VariableAuthentication3Descriptor, error) { var hdr uefi.EFI_VARIABLE_AUTHENTICATION_3 if err := binary.Read(r, binary.LittleEndian, &hdr); err != nil { return nil, err } if hdr.Version != 1 { return nil, errors.New("unexpected version") } lr := io.LimitReader(r, int64(hdr.MetadataSize)-int64(binary.Size(hdr))) switch hdr.Type { case uefi.EFI_VARIABLE_AUTHENTICATION_3_TIMESTAMP_TYPE: var t uefi.EFI_TIME if err := binary.Read(lr, binary.LittleEndian, &t); err != nil { return nil, ioerr.EOFIsUnexpected("cannot read timestamp descriptor: %w", err) } id, err := uefi.Read_EFI_VARIABLE_AUTHENTICATION_3_CERT_ID(r) if err != nil { return nil, ioerr.EOFIsUnexpected("cannot read timestamp descriptor: %w", err) } return &VariableAuthentication3TimestampDescriptor{ TimeStamp: t.GoTime(), VariableAuthentication3CertId: *newVariableAuthentication3CertId(id)}, nil case uefi.EFI_VARIABLE_AUTHENTICATION_3_NONCE_TYPE: n, err := uefi.Read_EFI_VARIABLE_AUTHENTICATION_3_NONCE(r) if err != nil { return nil, ioerr.EOFIsUnexpected("cannot read nonce descriptor: %w", err) } id, err := uefi.Read_EFI_VARIABLE_AUTHENTICATION_3_CERT_ID(r) if err != nil { return nil, ioerr.EOFIsUnexpected("cannot read nonce descriptor: %w", err) } return &VariableAuthentication3NonceDescriptor{ Nonce: n.Nonce, VariableAuthentication3CertId: *newVariableAuthentication3CertId(id)}, nil default: return nil, errors.New("unexpected type") } } ./github.com/canonical/go-efilib/db.go0000664000000000000000000001077000000000000016110 0ustar0000000000000000// Copyright 2020 Canonical Ltd. // Licensed under the LGPLv3 with static-linking exception. // See LICENCE file for details. package efi import ( "bytes" "crypto" "crypto/x509" "encoding/binary" "fmt" "io" "github.com/canonical/go-efilib/internal/uefi" "golang.org/x/xerrors" ) // SignatureData corresponds to the EFI_SIGNATURE_DATA type. type SignatureData struct { Owner GUID Data []byte } func (d *SignatureData) toUefiType() *uefi.EFI_SIGNATURE_DATA { return &uefi.EFI_SIGNATURE_DATA{ SignatureOwner: uefi.EFI_GUID(d.Owner), SignatureData: d.Data} } // Write serializes this signature data to w. func (d *SignatureData) Write(w io.Writer) error { return d.toUefiType().Write(w) } // Equal determines whether other is equal to this SignatureData func (d *SignatureData) Equal(other *SignatureData) bool { if d.Owner != other.Owner { return false } return bytes.Equal(d.Data, other.Data) } // SignatureList corresponds to the EFI_SIGNATURE_LIST type. type SignatureList struct { Type GUID Header []byte Signatures []*SignatureData } func (l *SignatureList) toUefiType() (out *uefi.EFI_SIGNATURE_LIST, err error) { out = &uefi.EFI_SIGNATURE_LIST{ SignatureType: uefi.EFI_GUID(l.Type), SignatureHeaderSize: uint32(len(l.Header)), SignatureHeader: l.Header} for i, s := range l.Signatures { sig := s.toUefiType() sz := uint32(binary.Size(sig.SignatureOwner) + len(sig.SignatureData)) if i == 0 { out.SignatureSize = sz } if sz != out.SignatureSize { // EFI_SIGNATURE_LIST cannot contain EFI_SIGNATURE_DATA entries with different // sizes - they must go in their own list. return nil, fmt.Errorf("signature %d contains the wrong size", i) } out.Signatures = append(out.Signatures, *sig) } out.SignatureListSize = uefi.ESLHeaderSize + out.SignatureHeaderSize + (out.SignatureSize * uint32(len(out.Signatures))) return out, nil } func (l *SignatureList) String() string { var b bytes.Buffer fmt.Fprintf(&b, "EFI_SIGNATURE_LIST{ SignatureType: %v, SignatureHeader: %x, Signatures: [", l.Type, l.Header) for _, d := range l.Signatures { fmt.Fprintf(&b, "\n\tEFI_SIGNATURE_DATA{ SignatureOwner: %v, Details: {", d.Owner) switch l.Type { case CertSHA1Guid, CertSHA256Guid, CertSHA224Guid, CertSHA384Guid, CertSHA512Guid: fmt.Fprintf(&b, "\n\t\tHash: %x", d.Data) case CertX509Guid: cert, err := x509.ParseCertificate(d.Data) if err != nil { fmt.Fprintf(&b, "%v", err) } h := crypto.SHA256.New() h.Write(cert.RawTBSCertificate) fmt.Fprintf(&b, "\n\t\tSubject: %v\n\t\tIssuer: %v\n\t\tSHA256 fingerprint: %x", cert.Subject, cert.Issuer, h.Sum(nil)) default: fmt.Fprintf(&b, "") } fmt.Fprintf(&b, "}}") } fmt.Fprintf(&b, "]") return b.String() } // Write serializes this signature list to w. func (l *SignatureList) Write(w io.Writer) error { list, err := l.toUefiType() if err != nil { return err } return list.Write(w) } // ReadSignatureList decodes a single EFI_SIGNATURE_LIST from r. func ReadSignatureList(r io.Reader) (*SignatureList, error) { l, err := uefi.Read_EFI_SIGNATURE_LIST(r) if err != nil { return nil, err } list := &SignatureList{Type: GUID(l.SignatureType), Header: l.SignatureHeader} for _, s := range l.Signatures { list.Signatures = append(list.Signatures, &SignatureData{Owner: GUID(s.SignatureOwner), Data: s.SignatureData}) } return list, nil } // SignatureDatabase corresponds to a list of EFI_SIGNATURE_LIST structures. type SignatureDatabase []*SignatureList func (db SignatureDatabase) String() string { var s string for _, l := range db { s = s + "\n" + l.String() + "\n" } return s } // Bytes returns the serialized form of this signature database. func (db SignatureDatabase) Bytes() ([]byte, error) { w := new(bytes.Buffer) if err := db.Write(w); err != nil { return nil, err } return w.Bytes(), nil } // Write serializes this signature database to w. func (db SignatureDatabase) Write(w io.Writer) error { for i, l := range db { if err := l.Write(w); err != nil { return xerrors.Errorf("cannot encode signature list %d: %w", i, err) } } return nil } // ReadSignatureDatabase decodes a list of EFI_SIGNATURE_LIST structures from r. func ReadSignatureDatabase(r io.Reader) (SignatureDatabase, error) { var db SignatureDatabase for i := 0; ; i++ { l, err := ReadSignatureList(r) if err != nil { if err == io.EOF { break } return nil, xerrors.Errorf("cannot read EFI_SIGNATURE_LIST %d: %w", i, err) } db = append(db, l) } return db, nil } ./github.com/canonical/go-efilib/devicepath.go0000664000000000000000000011054600000000000017641 0ustar0000000000000000// Copyright 2020 Canonical Ltd. // Licensed under the LGPLv3 with static-linking exception. // See LICENCE file for details. package efi import ( "bytes" "encoding/binary" "errors" "fmt" "io" "io/ioutil" "math" "os" "path/filepath" "strconv" "strings" "github.com/canonical/go-efilib/internal/ioerr" "github.com/canonical/go-efilib/internal/uefi" "github.com/canonical/go-efilib/mbr" "golang.org/x/xerrors" ) // DevicePathType is the type of a device path node. type DevicePathType uint8 func (t DevicePathType) String() string { switch t { case HardwareDevicePath: return "HardwarePath" case ACPIDevicePath: return "AcpiPath" case MessagingDevicePath: return "Msg" case MediaDevicePath: return "MediaPath" case BBSDevicePath: return "BbsPath" default: return fmt.Sprintf("Path[%02x]", uint8(t)) } } const ( HardwareDevicePath DevicePathType = uefi.HARDWARE_DEVICE_PATH ACPIDevicePath DevicePathType = uefi.ACPI_DEVICE_PATH MessagingDevicePath DevicePathType = uefi.MESSAGING_DEVICE_PATH MediaDevicePath DevicePathType = uefi.MEDIA_DEVICE_PATH BBSDevicePath DevicePathType = uefi.BBS_DEVICE_PATH ) // DevicePathSubType is the sub-type of a device path node. type DevicePathSubType uint8 // DevicePathToStringFlags defines flags for DevicePath.ToString and // DevicePathNode.ToString. type DevicePathToStringFlags int func (f DevicePathToStringFlags) DisplayOnly() bool { return f&DevicePathDisplayOnly > 0 } const ( // DevicePathDisplayOnly indicates that each node is converted // to the shorter text representation. DevicePathDisplayOnly DevicePathToStringFlags = 1 << 0 ) // DevicePathNode represents a single node in a device path. type DevicePathNode interface { fmt.Stringer ToString(flags DevicePathToStringFlags) string Write(w io.Writer) error } // DevicePath represents a complete device path with the first node // representing the root. type DevicePath []DevicePathNode // ToString returns a string representation of this device path with the // supplied flags. func (p DevicePath) ToString(flags DevicePathToStringFlags) string { s := new(bytes.Buffer) for _, node := range p { fmt.Fprintf(s, "\\%s", node.ToString(flags)) } return s.String() } func (p DevicePath) String() string { return p.ToString(DevicePathDisplayOnly) } // Bytes returns the serialized form of this device path. func (p DevicePath) Bytes() ([]byte, error) { w := new(bytes.Buffer) if err := p.Write(w); err != nil { return nil, err } return w.Bytes(), nil } // Write serializes the complete device path to w. func (p DevicePath) Write(w io.Writer) error { for i, node := range p { if err := node.Write(w); err != nil { return xerrors.Errorf("cannot write node %d: %w", i, err) } } end := uefi.EFI_DEVICE_PATH_PROTOCOL{ Type: uefi.END_DEVICE_PATH_TYPE, SubType: uefi.END_ENTIRE_DEVICE_PATH_SUBTYPE, Length: 4} return binary.Write(w, binary.LittleEndian, &end) } // GenericDevicePathNode corresponds to a device path nodes with an unhandled type. type GenericDevicePathNode struct { Type DevicePathType SubType DevicePathSubType Data []byte } func (d *GenericDevicePathNode) ToString(_ DevicePathToStringFlags) string { var builder bytes.Buffer switch d.Type { case HardwareDevicePath, ACPIDevicePath, MessagingDevicePath, MediaDevicePath, BBSDevicePath: fmt.Fprintf(&builder, "%s(", d.Type) default: fmt.Fprintf(&builder, "Path(%d,", d.Type) } fmt.Fprintf(&builder, "%d", d.SubType) if len(d.Data) > 0 { fmt.Fprintf(&builder, ",%x", d.Data) } fmt.Fprintf(&builder, ")") return builder.String() } func (d *GenericDevicePathNode) String() string { return d.ToString(DevicePathDisplayOnly) } func (d *GenericDevicePathNode) Write(w io.Writer) error { data := uefi.EFI_DEVICE_PATH_PROTOCOL{ Type: uint8(d.Type), SubType: uint8(d.SubType)} if len(d.Data) > math.MaxUint16-binary.Size(data) { return errors.New("Data too large") } data.Length = uint16(binary.Size(data) + len(d.Data)) if err := binary.Write(w, binary.LittleEndian, &data); err != nil { return err } _, err := w.Write(d.Data) return err } // PCIDevicePathNode corresponds to a PCI device path node. type PCIDevicePathNode struct { Function uint8 Device uint8 } func (d *PCIDevicePathNode) ToString(_ DevicePathToStringFlags) string { return fmt.Sprintf("Pci(0x%x,0x%x)", d.Device, d.Function) } func (d *PCIDevicePathNode) String() string { return d.ToString(DevicePathDisplayOnly) } func (d *PCIDevicePathNode) Write(w io.Writer) error { data := uefi.PCI_DEVICE_PATH{ Header: uefi.EFI_DEVICE_PATH_PROTOCOL{ Type: uint8(uefi.HARDWARE_DEVICE_PATH), SubType: uint8(uefi.HW_PCI_DP)}, Function: d.Function, Device: d.Device} data.Header.Length = uint16(binary.Size(data)) return binary.Write(w, binary.LittleEndian, &data) } type VendorDevicePathNode struct { Type DevicePathType GUID GUID Data []byte } func (d *VendorDevicePathNode) ToString(_ DevicePathToStringFlags) string { var t string switch d.Type { case HardwareDevicePath: t = "Hw" case MessagingDevicePath: t = "Msg" case MediaDevicePath: t = "Media" default: t = "?" } var s bytes.Buffer fmt.Fprintf(&s, "Ven%s(%s", t, d.GUID) if len(d.Data) > 0 { fmt.Fprintf(&s, ",%x", d.Data) } fmt.Fprintf(&s, ")") return s.String() } func (d *VendorDevicePathNode) String() string { return d.ToString(DevicePathDisplayOnly) } func (d *VendorDevicePathNode) Write(w io.Writer) error { var subType uint8 switch d.Type { case HardwareDevicePath: subType = uefi.HW_VENDOR_DP case MessagingDevicePath: subType = uefi.MSG_VENDOR_DP case MediaDevicePath: subType = uefi.MEDIA_VENDOR_DP default: return errors.New("invalid device path type") } data := uefi.VENDOR_DEVICE_PATH{ Header: uefi.EFI_DEVICE_PATH_PROTOCOL{ Type: uint8(d.Type), SubType: subType}, Guid: uefi.EFI_GUID(d.GUID)} if len(d.Data) > math.MaxUint16-binary.Size(data) { return errors.New("Data too large") } data.Header.Length = uint16(binary.Size(data) + len(d.Data)) if err := binary.Write(w, binary.LittleEndian, &data); err != nil { return err } _, err := w.Write(d.Data) return err } func readVendorDevicePathNode(r io.Reader) (out *VendorDevicePathNode, err error) { var n uefi.VENDOR_DEVICE_PATH if err := binary.Read(r, binary.LittleEndian, &n); err != nil { return nil, err } out = &VendorDevicePathNode{ Type: DevicePathType(n.Header.Type), GUID: GUID(n.Guid)} data, _ := ioutil.ReadAll(r) out.Data = data return out, nil } // EISAID represents a compressed EISA PNP ID type EISAID uint32 // Vendor returns the 3-letter vendor ID. func (id EISAID) Vendor() string { return fmt.Sprintf("%c%c%c", ((id>>10)&0x1f)+'A'-1, ((id>>5)&0x1f)+'A'-1, (id&0x1f)+'A'-1) } // Product returns the product ID. func (id EISAID) Product() uint16 { return uint16(id >> 16) } func (id EISAID) String() string { if id == 0 { return "0" } return fmt.Sprintf("%s%04x", id.Vendor(), id.Product()) } func NewEISAID(vendor string, product uint16) (EISAID, error) { if len(vendor) != 3 { return 0, errors.New("invalid vendor length") } var out EISAID out |= EISAID((vendor[0]-'A'+1)&0x1f) << 10 out |= EISAID((vendor[1]-'A'+1)&0x1f) << 5 out |= EISAID((vendor[2] - 'A' + 1) & 0x1f) out |= EISAID(product) << 16 return out, nil } // ACPIDevicePathNode corresponds to an ACPI device path node. type ACPIDevicePathNode struct { HID EISAID UID uint32 } func (d *ACPIDevicePathNode) ToString(_ DevicePathToStringFlags) string { if d.HID.Vendor() == "PNP" { switch d.HID.Product() { case 0x0a03: return fmt.Sprintf("PciRoot(0x%x)", d.UID) case 0x0a08: return fmt.Sprintf("PcieRoot(0x%x)", d.UID) case 0x0604: return fmt.Sprintf("Floppy(0x%x)", d.UID) case 0x0301: return fmt.Sprintf("Keyboard(0x%x)", d.UID) case 0x0501: return fmt.Sprintf("Serial(0x%x)", d.UID) case 0x0401: return fmt.Sprintf("ParallelPort(0x%x)", d.UID) } } return fmt.Sprintf("Acpi(%s,0x%x)", d.HID, d.UID) } func (d *ACPIDevicePathNode) String() string { return d.ToString(DevicePathDisplayOnly) } func (d *ACPIDevicePathNode) Write(w io.Writer) error { data := uefi.ACPI_HID_DEVICE_PATH{ Header: uefi.EFI_DEVICE_PATH_PROTOCOL{ Type: uint8(uefi.ACPI_DEVICE_PATH), SubType: uint8(uefi.ACPI_DP)}, HID: uint32(d.HID), UID: uint32(d.UID)} data.Header.Length = uint16(binary.Size(data)) return binary.Write(w, binary.LittleEndian, &data) } type ACPIExtendedDevicePathNode struct { HID EISAID UID uint32 CID EISAID HIDStr string UIDStr string CIDStr string } func (d *ACPIExtendedDevicePathNode) ToString(flags DevicePathToStringFlags) string { switch { case d.HIDStr == "" && d.CIDStr == "" && d.UIDStr != "": return fmt.Sprintf("AcpiExp(%s,%s,%s)", d.HID, d.CID, d.UIDStr) case flags.DisplayOnly() && d.HID.Vendor() == "PNP" && (d.HID.Product() == 0x0a03 || (d.CID.Product() == 0x0a03 && d.HID.Product() != 0x0a08)): if d.UIDStr != "" { return fmt.Sprintf("PciRoot(%s)", d.UIDStr) } return fmt.Sprintf("PciRoot(0x%x)", d.UID) case flags.DisplayOnly() && d.HID.Vendor() == "PNP" && (d.HID.Product() == 0x0a08 || d.CID.Product() == 0x0a08): if d.UIDStr != "" { return fmt.Sprintf("PcieRoot(%s)", d.UIDStr) } return fmt.Sprintf("PcieRoot(0x%x)", d.UID) } if !flags.DisplayOnly() { hidStr := d.HIDStr if hidStr == "" { hidStr = "" } cidStr := d.CIDStr if cidStr == "" { cidStr = "" } uidStr := d.UIDStr if uidStr == "" { uidStr = "" } return fmt.Sprintf("AcpiEx(%s,%s,0x%x,%s,%s,%s)", d.HID, d.CID, d.UID, hidStr, cidStr, uidStr) } hidText := d.HID.String() if d.HIDStr != "" { hidText = d.HIDStr } cidText := d.CID.String() if d.CIDStr != "" { cidText = d.CIDStr } if d.UIDStr != "" { return fmt.Sprintf("AcpiEx(%s,%s,%s)", hidText, cidText, d.UIDStr) } return fmt.Sprintf("AcpiEx(%s,%s,0x%x)", hidText, cidText, d.UID) } func (d *ACPIExtendedDevicePathNode) String() string { return d.ToString(DevicePathDisplayOnly) } func (d *ACPIExtendedDevicePathNode) Write(w io.Writer) error { data := uefi.ACPI_EXTENDED_HID_DEVICE_PATH{ Header: uefi.EFI_DEVICE_PATH_PROTOCOL{ Type: uint8(uefi.ACPI_DEVICE_PATH), SubType: uint8(uefi.ACPI_EXTENDED_DP)}, HID: uint32(d.HID), UID: d.UID, CID: uint32(d.CID)} // Set a reasonable limit on each string field for _, s := range []string{d.HIDStr, d.UIDStr, d.CIDStr} { if len(s) > math.MaxUint16-(binary.Size(data)+3) { return errors.New("string field too large") } } // This can't overflow int length := binary.Size(data) + len(d.HIDStr) + len(d.UIDStr) + len(d.CIDStr) + 3 if length > math.MaxUint16 { return errors.New("too large") } data.Header.Length = uint16(length) if err := binary.Write(w, binary.LittleEndian, &data); err != nil { return err } for _, s := range []string{d.HIDStr, d.UIDStr, d.CIDStr} { if _, err := io.WriteString(w, s); err != nil { return err } w.Write([]byte{0x00}) } return nil } type ATAPIControllerRole uint8 func (r ATAPIControllerRole) String() string { switch r { case ATAPIControllerPrimary: return "Primary" case ATAPIControllerSecondary: return "Secondary" default: return strconv.FormatUint(uint64(r), 10) } } const ( ATAPIControllerPrimary ATAPIControllerRole = 0 ATAPIControllerSecondary ATAPIControllerRole = 1 ) type ATAPIDriveRole uint8 func (r ATAPIDriveRole) String() string { switch r { case ATAPIDriveMaster: return "Master" case ATAPIDriveSlave: return "Slave" default: return strconv.FormatUint(uint64(r), 10) } } const ( ATAPIDriveMaster ATAPIDriveRole = 0 ATAPIDriveSlave ATAPIDriveRole = 1 ) // ATAPIDevicePathNode corresponds to an ATA device path node. type ATAPIDevicePathNode struct { Controller ATAPIControllerRole Drive ATAPIDriveRole LUN uint16 } func (d *ATAPIDevicePathNode) ToString(flags DevicePathToStringFlags) string { if flags.DisplayOnly() { return fmt.Sprintf("Ata(0x%x)", d.LUN) } return fmt.Sprintf("Ata(%s,%s,0x%x)", d.Controller, d.Drive, d.LUN) } func (d *ATAPIDevicePathNode) String() string { return d.ToString(DevicePathDisplayOnly) } func (d *ATAPIDevicePathNode) Write(w io.Writer) error { data := uefi.ATAPI_DEVICE_PATH{ Header: uefi.EFI_DEVICE_PATH_PROTOCOL{ Type: uint8(uefi.MESSAGING_DEVICE_PATH), SubType: uint8(uefi.MSG_ATAPI_DP)}, PrimarySecondary: uint8(d.Controller), SlaveMaster: uint8(d.Drive), Lun: d.LUN} data.Header.Length = uint16(binary.Size(data)) return binary.Write(w, binary.LittleEndian, &data) } // SCSIDevicePathNode corresponds to a SCSI device path node. type SCSIDevicePathNode struct { PUN uint16 LUN uint16 } func (d *SCSIDevicePathNode) ToString(_ DevicePathToStringFlags) string { return fmt.Sprintf("Scsi(0x%x,0x%x)", d.PUN, d.LUN) } func (d *SCSIDevicePathNode) String() string { return d.ToString(DevicePathDisplayOnly) } func (d *SCSIDevicePathNode) Write(w io.Writer) error { data := uefi.SCSI_DEVICE_PATH{ Header: uefi.EFI_DEVICE_PATH_PROTOCOL{ Type: uint8(uefi.MESSAGING_DEVICE_PATH), SubType: uint8(uefi.MSG_SCSI_DP)}, Pun: d.PUN, Lun: d.LUN} data.Header.Length = uint16(binary.Size(data)) return binary.Write(w, binary.LittleEndian, &data) } // USBDevicePathNode corresponds to a USB device path node. type USBDevicePathNode struct { ParentPortNumber uint8 InterfaceNumber uint8 } func (d *USBDevicePathNode) ToString(_ DevicePathToStringFlags) string { return fmt.Sprintf("USB(0x%x,0x%x)", d.ParentPortNumber, d.InterfaceNumber) } func (d *USBDevicePathNode) String() string { return d.ToString(DevicePathDisplayOnly) } func (d *USBDevicePathNode) Write(w io.Writer) error { data := uefi.USB_DEVICE_PATH{ Header: uefi.EFI_DEVICE_PATH_PROTOCOL{ Type: uint8(uefi.MESSAGING_DEVICE_PATH), SubType: uint8(uefi.MSG_USB_DP)}, ParentPortNumber: d.ParentPortNumber, InterfaceNumber: d.InterfaceNumber} data.Header.Length = uint16(binary.Size(data)) return binary.Write(w, binary.LittleEndian, &data) } type USBClass uint8 const ( USBClassAudio USBClass = 0x01 USBClassCDCControl USBClass = 0x02 USBClassHID USBClass = 0x03 USBClassImage USBClass = 0x06 USBClassPrinter USBClass = 0x07 USBClassMassStorage USBClass = 0x08 USBClassHub USBClass = 0x09 USBClassCDCData USBClass = 0x0a USBClassSmartCard USBClass = 0x0b USBClassVideo USBClass = 0x0e USBClassDiagnostic USBClass = 0xdc USBClassWireless USBClass = 0xe0 ) // USBClassDevicePathNode corresponds to a USB class device path node. type USBClassDevicePathNode struct { VendorId uint16 ProductId uint16 DeviceClass USBClass DeviceSubClass uint8 DeviceProtocol uint8 } func (d *USBClassDevicePathNode) ToString(_ DevicePathToStringFlags) string { var builder bytes.Buffer switch d.DeviceClass { case USBClassAudio: fmt.Fprintf(&builder, "UsbAudio") case USBClassCDCControl: fmt.Fprintf(&builder, "UsbCDCControl") case USBClassHID: fmt.Fprintf(&builder, "UsbHID") case USBClassImage: fmt.Fprintf(&builder, "UsbImage") case USBClassPrinter: fmt.Fprintf(&builder, "UsbPrinter") case USBClassMassStorage: fmt.Fprintf(&builder, "UsbMassStorage") case USBClassHub: fmt.Fprintf(&builder, "UsbHub") case USBClassCDCData: fmt.Fprintf(&builder, "UsbCDCData") case USBClassSmartCard: fmt.Fprintf(&builder, "UsbSmartCard") case USBClassVideo: fmt.Fprintf(&builder, "UsbVideo") case USBClassDiagnostic: fmt.Fprintf(&builder, "UsbDiagnostic") case USBClassWireless: fmt.Fprintf(&builder, "UsbWireless") default: return fmt.Sprintf("UsbClass(0x%x,0x%x,0x%x,0x%x,0x%x)", d.VendorId, d.ProductId, d.DeviceClass, d.DeviceSubClass, d.DeviceProtocol) } fmt.Fprintf(&builder, "(0x%x,0x%x,0x%x,0x%x)", d.VendorId, d.ProductId, d.DeviceSubClass, d.DeviceProtocol) return builder.String() } func (d *USBClassDevicePathNode) String() string { return d.ToString(DevicePathDisplayOnly) } func (d *USBClassDevicePathNode) Write(w io.Writer) error { data := uefi.USB_CLASS_DEVICE_PATH{ Header: uefi.EFI_DEVICE_PATH_PROTOCOL{ Type: uint8(uefi.MESSAGING_DEVICE_PATH), SubType: uint8(uefi.MSG_USB_CLASS_DP)}, VendorId: d.VendorId, ProductId: d.ProductId, DeviceClass: uint8(d.DeviceClass), DeviceSubClass: d.DeviceSubClass, DeviceProtocol: d.DeviceProtocol} data.Header.Length = uint16(binary.Size(data)) return binary.Write(w, binary.LittleEndian, &data) } // USBWWIDDevicePathNode corresponds to a USB WWID device path node. type USBWWIDDevicePathNode struct { InterfaceNumber uint16 VendorId uint16 ProductId uint16 SerialNumber string } func (d *USBWWIDDevicePathNode) ToString(_ DevicePathToStringFlags) string { return fmt.Sprintf("UsbWwid(0x%x,0x%x,0x%x,\"%s\"", d.VendorId, d.ProductId, d.InterfaceNumber, d.SerialNumber) } func (d *USBWWIDDevicePathNode) String() string { return d.ToString(DevicePathDisplayOnly) } func (d *USBWWIDDevicePathNode) Write(w io.Writer) error { data := uefi.USB_WWID_DEVICE_PATH{ Header: uefi.EFI_DEVICE_PATH_PROTOCOL{ Type: uint8(uefi.MESSAGING_DEVICE_PATH), SubType: uint8(uefi.MSG_USB_WWID_DP)}, InterfaceNumber: d.InterfaceNumber, VendorId: d.VendorId, ProductId: d.ProductId, SerialNumber: ConvertUTF8ToUTF16(d.SerialNumber)} l := binary.Size(data.Header) + binary.Size(data.InterfaceNumber) + binary.Size(data.VendorId) + binary.Size(data.ProductId) if binary.Size(data.SerialNumber) > math.MaxUint16-l { return errors.New("SerialNumber too long") } data.Header.Length = uint16(l + binary.Size(data.SerialNumber)) return binary.Write(w, binary.LittleEndian, &data) } type DeviceLogicalUnitDevicePathNode struct { LUN uint8 } func (d *DeviceLogicalUnitDevicePathNode) ToString(_ DevicePathToStringFlags) string { return fmt.Sprintf("Unit(0x%x)", d.LUN) } func (d *DeviceLogicalUnitDevicePathNode) String() string { return d.ToString(DevicePathDisplayOnly) } func (d *DeviceLogicalUnitDevicePathNode) Write(w io.Writer) error { data := uefi.DEVICE_LOGICAL_UNIT_DEVICE_PATH{ Header: uefi.EFI_DEVICE_PATH_PROTOCOL{ Type: uint8(uefi.MESSAGING_DEVICE_PATH), SubType: uint8(uefi.MSG_DEVICE_LOGICAL_UNIT_DP)}, Lun: d.LUN} data.Header.Length = uint16(binary.Size(data)) return binary.Write(w, binary.LittleEndian, &data) } // SATADevicePathNode corresponds to a SATA device path node. type SATADevicePathNode struct { HBAPortNumber uint16 PortMultiplierPortNumber uint16 LUN uint16 } func (d *SATADevicePathNode) ToString(_ DevicePathToStringFlags) string { return fmt.Sprintf("Sata(0x%x,0x%x,0x%x)", d.HBAPortNumber, d.PortMultiplierPortNumber, d.LUN) } func (d *SATADevicePathNode) String() string { return d.ToString(DevicePathDisplayOnly) } func (d *SATADevicePathNode) Write(w io.Writer) error { data := uefi.SATA_DEVICE_PATH{ Header: uefi.EFI_DEVICE_PATH_PROTOCOL{ Type: uint8(uefi.MESSAGING_DEVICE_PATH), SubType: uint8(uefi.MSG_SATA_DP)}, HBAPortNumber: d.HBAPortNumber, PortMultiplierPortNumber: d.PortMultiplierPortNumber, Lun: d.LUN} data.Header.Length = uint16(binary.Size(data)) return binary.Write(w, binary.LittleEndian, &data) } // NVMENamespaceDevicePathNode corresponds to a NVME namespace device path node. type NVMENamespaceDevicePathNode struct { NamespaceID uint32 NamespaceUUID uint64 } func (d *NVMENamespaceDevicePathNode) ToString(_ DevicePathToStringFlags) string { var uuid [8]uint8 binary.BigEndian.PutUint64(uuid[:], d.NamespaceUUID) return fmt.Sprintf("NVMe(0x%x,%02x-%02x-%02x-%02x-%02x-%02x-%02x-%02x)", d.NamespaceID, uuid[0], uuid[1], uuid[2], uuid[3], uuid[4], uuid[5], uuid[6], uuid[7]) } func (d *NVMENamespaceDevicePathNode) String() string { return d.ToString(DevicePathDisplayOnly) } func (d *NVMENamespaceDevicePathNode) Write(w io.Writer) error { data := uefi.NVME_NAMESPACE_DEVICE_PATH{ Header: uefi.EFI_DEVICE_PATH_PROTOCOL{ Type: uint8(uefi.MESSAGING_DEVICE_PATH), SubType: uint8(uefi.MSG_NVME_NAMESPACE_DP)}, NamespaceId: d.NamespaceID, NamespaceUuid: d.NamespaceUUID} data.Header.Length = uint16(binary.Size(data)) return binary.Write(w, binary.LittleEndian, &data) } type MBRType uint8 func (t MBRType) String() string { switch t { case LegacyMBR: return "MBR" case GPT: return "GPT" default: return strconv.FormatUint(uint64(t), 10) } } const ( LegacyMBR MBRType = 1 GPT = 2 ) type HardDriveSignatureType uint8 func (t HardDriveSignatureType) String() string { switch t { case uefi.SIGNATURE_TYPE_MBR: return "MBR" case uefi.SIGNATURE_TYPE_GUID: return "GPT" default: return strconv.FormatUint(uint64(t), 10) } } type HardDriveSignature interface { fmt.Stringer Data() [16]uint8 Type() HardDriveSignatureType } type GUIDHardDriveSignature GUID func (s GUIDHardDriveSignature) String() string { return GUID(s).String() } func (s GUIDHardDriveSignature) Data() (out [16]uint8) { copy(out[:], s[:]) return out } func (GUIDHardDriveSignature) Type() HardDriveSignatureType { return HardDriveSignatureType(uefi.SIGNATURE_TYPE_GUID) } type MBRHardDriveSignature uint32 func (s MBRHardDriveSignature) String() string { return fmt.Sprintf("0x%08x", uint32(s)) } func (s MBRHardDriveSignature) Data() (out [16]uint8) { binary.LittleEndian.PutUint32(out[:], uint32(s)) return out } func (s MBRHardDriveSignature) Type() HardDriveSignatureType { return HardDriveSignatureType(uefi.SIGNATURE_TYPE_MBR) } type genericHardDriveSignature struct { typ HardDriveSignatureType data [16]uint8 } func (s *genericHardDriveSignature) String() string { return fmt.Sprintf("%x", s.data) } func (s *genericHardDriveSignature) Data() [16]uint8 { return s.data } func (s *genericHardDriveSignature) Type() HardDriveSignatureType { return s.typ } // HardDriveDevicePathNode corresponds to a hard drive device path node. type HardDriveDevicePathNode struct { PartitionNumber uint32 PartitionStart uint64 PartitionSize uint64 Signature HardDriveSignature MBRType MBRType } func (d *HardDriveDevicePathNode) ToString(flags DevicePathToStringFlags) string { var builder bytes.Buffer switch d.Signature.Type() { default: fmt.Fprintf(&builder, "HD(%d,%d,0", d.PartitionNumber, d.MBRType) case uefi.SIGNATURE_TYPE_MBR, uefi.SIGNATURE_TYPE_GUID: fmt.Fprintf(&builder, "HD(%d,%s,%s", d.PartitionNumber, d.Signature.Type(), d.Signature) } if !flags.DisplayOnly() { fmt.Fprintf(&builder, ",0x%x,0x%x", d.PartitionStart, d.PartitionSize) } fmt.Fprintf(&builder, ")") return builder.String() } func (d *HardDriveDevicePathNode) String() string { return d.ToString(DevicePathDisplayOnly) } func (d *HardDriveDevicePathNode) Write(w io.Writer) error { data := uefi.HARDDRIVE_DEVICE_PATH{ Header: uefi.EFI_DEVICE_PATH_PROTOCOL{ Type: uint8(uefi.MEDIA_DEVICE_PATH), SubType: uint8(uefi.MEDIA_HARDDRIVE_DP)}, PartitionNumber: d.PartitionNumber, PartitionStart: d.PartitionStart, PartitionSize: d.PartitionSize, MBRType: uint8(d.MBRType)} if d.Signature != nil { data.SignatureType = uint8(d.Signature.Type()) switch d.Signature.Type() { case uefi.NO_DISK_SIGNATURE: if d.Signature.Data() != data.Signature { return errors.New("inconsistent signature and signature type") } default: data.Signature = d.Signature.Data() } } data.Header.Length = uint16(binary.Size(data)) return binary.Write(w, binary.LittleEndian, &data) } // NewHardDriveDevicePathNodeFromDevice constructs a HardDriveDevicePathNode for the // specified partition on the supplied device reader. The device's total size and // logical block size must be supplied. func NewHardDriveDevicePathNodeFromDevice(r io.ReaderAt, totalSz, blockSz int64, part int) (*HardDriveDevicePathNode, error) { if part < 1 { return nil, errors.New("invalid partition number") } table, err := ReadPartitionTable(r, totalSz, blockSz, PrimaryPartitionTable, true) switch { case err == ErrNoProtectiveMBR: record, err := mbr.ReadRecord(io.NewSectionReader(r, 0, totalSz)) if err != nil { return nil, err } if part > 4 { return nil, fmt.Errorf("invalid partition number %d for MBR", part) } entry := record.Partitions[part-1] return &HardDriveDevicePathNode{ PartitionNumber: uint32(part), PartitionStart: uint64(entry.StartingLBA), PartitionSize: uint64(entry.NumberOfSectors), Signature: MBRHardDriveSignature(record.UniqueSignature), MBRType: LegacyMBR}, nil case err != nil: return nil, err default: if part > len(table.Entries) { return nil, fmt.Errorf("invalid partition number %d: device only has %d partitions", part, len(table.Entries)) } entry := table.Entries[part-1] if entry.PartitionTypeGUID == UnusedPartitionType { return nil, errors.New("requested partition is unused") } return &HardDriveDevicePathNode{ PartitionNumber: uint32(part), PartitionStart: uint64(entry.StartingLBA), PartitionSize: uint64(entry.EndingLBA - entry.StartingLBA + 1), Signature: GUIDHardDriveSignature(entry.UniquePartitionGUID), MBRType: GPT}, nil } } // CDROMDevicePathNode corresponds to a CDROM device path node. type CDROMDevicePathNode struct { BootEntry uint32 PartitionStart uint64 PartitionSize uint64 } func (d *CDROMDevicePathNode) ToString(flags DevicePathToStringFlags) string { if flags.DisplayOnly() { return fmt.Sprintf("CDROM(0x%x)", d.BootEntry) } return fmt.Sprintf("CDROM(0x%x,0x%x,0x%x)", d.BootEntry, d.PartitionStart, d.PartitionSize) } func (d *CDROMDevicePathNode) String() string { return d.ToString(DevicePathDisplayOnly) } func (d *CDROMDevicePathNode) Write(w io.Writer) error { data := uefi.CDROM_DEVICE_PATH{ Header: uefi.EFI_DEVICE_PATH_PROTOCOL{ Type: uint8(uefi.MEDIA_DEVICE_PATH), SubType: uint8(uefi.MEDIA_CDROM_DP)}, BootEntry: d.BootEntry, PartitionStart: d.PartitionStart, PartitionSize: d.PartitionSize} data.Header.Length = uint16(binary.Size(data)) return binary.Write(w, binary.LittleEndian, &data) } // FilePathDevicePathNode corresponds to a file path device path node. type FilePathDevicePathNode string func (d FilePathDevicePathNode) ToString(_ DevicePathToStringFlags) string { return string(d) } func (d FilePathDevicePathNode) String() string { return d.ToString(DevicePathDisplayOnly) } func (d FilePathDevicePathNode) Write(w io.Writer) error { data := uefi.FILEPATH_DEVICE_PATH{ Header: uefi.EFI_DEVICE_PATH_PROTOCOL{ Type: uint8(uefi.MEDIA_DEVICE_PATH), SubType: uint8(uefi.MEDIA_FILEPATH_DP)}, PathName: ConvertUTF8ToUTF16(string(d) + "\x00")} if binary.Size(data.PathName) > math.MaxUint16-binary.Size(data.Header) { return errors.New("PathName too large") } data.Header.Length = uint16(binary.Size(data.Header) + binary.Size(data.PathName)) return data.Write(w) } // NewFilePathDevicePathNode constructs a new FilePathDevicePathNode from the supplied // path, converting the OS native separators to EFI separators ("\") and prepending // a separator to the start of the path if one doesn't already exist. func NewFilePathDevicePathNode(path string) (out FilePathDevicePathNode) { components := strings.Split(path, string(os.PathSeparator)) if !filepath.IsAbs(path) { out = FilePathDevicePathNode("\\") } return out + FilePathDevicePathNode(strings.Join(components, "\\")) } // MediaFvFileDevicePathNode corresponds to a firmware volume file device path node. type MediaFvFileDevicePathNode GUID func (d MediaFvFileDevicePathNode) ToString(_ DevicePathToStringFlags) string { return fmt.Sprintf("FvFile(%s)", GUID(d)) } func (d MediaFvFileDevicePathNode) String() string { return d.ToString(DevicePathDisplayOnly) } func (d MediaFvFileDevicePathNode) Write(w io.Writer) error { data := uefi.MEDIA_FW_VOL_FILEPATH_DEVICE_PATH{ Header: uefi.EFI_DEVICE_PATH_PROTOCOL{ Type: uint8(uefi.MEDIA_DEVICE_PATH), SubType: uint8(uefi.MEDIA_PIWG_FW_FILE_DP)}, FvFileName: uefi.EFI_GUID(d)} data.Header.Length = uint16(binary.Size(data)) return binary.Write(w, binary.LittleEndian, &data) } // MediaFvDevicePathNode corresponds to a firmware volume device path node. type MediaFvDevicePathNode GUID func (d MediaFvDevicePathNode) ToString(_ DevicePathToStringFlags) string { return fmt.Sprintf("Fv(%s)", GUID(d)) } func (d MediaFvDevicePathNode) String() string { return d.ToString(DevicePathDisplayOnly) } func (d MediaFvDevicePathNode) Write(w io.Writer) error { data := uefi.MEDIA_FW_VOL_DEVICE_PATH{ Header: uefi.EFI_DEVICE_PATH_PROTOCOL{ Type: uint8(uefi.MEDIA_DEVICE_PATH), SubType: uint8(uefi.MEDIA_PIWG_FW_VOL_DP)}, FvName: uefi.EFI_GUID(d)} data.Header.Length = uint16(binary.Size(data)) return binary.Write(w, binary.LittleEndian, &data) } type MediaRelOffsetRangeDevicePathNode struct { StartingOffset uint64 EndingOffset uint64 } func (d *MediaRelOffsetRangeDevicePathNode) ToString(_ DevicePathToStringFlags) string { return fmt.Sprintf("Offset(0x%x,0x%x)", d.StartingOffset, d.EndingOffset) } func (d *MediaRelOffsetRangeDevicePathNode) String() string { return d.ToString(DevicePathDisplayOnly) } func (d *MediaRelOffsetRangeDevicePathNode) Write(w io.Writer) error { data := uefi.MEDIA_RELATIVE_OFFSET_RANGE_DEVICE_PATH{ Header: uefi.EFI_DEVICE_PATH_PROTOCOL{ Type: uint8(uefi.MEDIA_DEVICE_PATH), SubType: uint8(uefi.MEDIA_RELATIVE_OFFSET_RANGE_DP)}, StartingOffset: d.StartingOffset, EndingOffset: d.EndingOffset} data.Header.Length = uint16(binary.Size(data)) return binary.Write(w, binary.LittleEndian, &data) } func decodeDevicePathNode(r io.Reader) (out DevicePathNode, err error) { buf := new(bytes.Buffer) r2 := io.TeeReader(r, buf) var h uefi.EFI_DEVICE_PATH_PROTOCOL if err := binary.Read(r2, binary.LittleEndian, &h); err != nil { return nil, err } if h.Length < 4 { return nil, fmt.Errorf("invalid length %d bytes (too small)", h.Length) } if _, err := io.CopyN(buf, r, int64(h.Length-4)); err != nil { return nil, ioerr.EOFIsUnexpected(err) } defer func() { switch { case err == io.EOF: fallthrough case xerrors.Is(err, io.ErrUnexpectedEOF): err = fmt.Errorf("invalid length %d bytes (too small)", h.Length) case err != nil: case buf.Len() > 0: err = fmt.Errorf("invalid length %d bytes (too large)", h.Length) } }() switch h.Type { case uefi.HARDWARE_DEVICE_PATH: switch h.SubType { case uefi.HW_PCI_DP: var n uefi.PCI_DEVICE_PATH if err := binary.Read(buf, binary.LittleEndian, &n); err != nil { return nil, err } return &PCIDevicePathNode{Function: n.Function, Device: n.Device}, nil case uefi.HW_VENDOR_DP: return readVendorDevicePathNode(buf) } case uefi.ACPI_DEVICE_PATH: switch h.SubType { case uefi.ACPI_DP: var n uefi.ACPI_HID_DEVICE_PATH if err := binary.Read(buf, binary.LittleEndian, &n); err != nil { return nil, err } return &ACPIDevicePathNode{HID: EISAID(n.HID), UID: n.UID}, nil case uefi.ACPI_EXTENDED_DP: var n uefi.ACPI_EXTENDED_HID_DEVICE_PATH if err := binary.Read(buf, binary.LittleEndian, &n); err != nil { return nil, err } node := &ACPIExtendedDevicePathNode{HID: EISAID(n.HID), UID: n.UID, CID: EISAID(n.CID)} for _, s := range []*string{&node.HIDStr, &node.UIDStr, &node.CIDStr} { v, err := buf.ReadString('\x00') if err != nil { return nil, err } *s = v[:len(v)-1] } return node, nil } case uefi.MESSAGING_DEVICE_PATH: switch h.SubType { case uefi.MSG_ATAPI_DP: var n uefi.ATAPI_DEVICE_PATH if err := binary.Read(buf, binary.LittleEndian, &n); err != nil { return nil, err } return &ATAPIDevicePathNode{ Controller: ATAPIControllerRole(n.PrimarySecondary), Drive: ATAPIDriveRole(n.SlaveMaster), LUN: n.Lun}, nil case uefi.MSG_SCSI_DP: var n uefi.SCSI_DEVICE_PATH if err := binary.Read(buf, binary.LittleEndian, &n); err != nil { return nil, err } return &SCSIDevicePathNode{PUN: n.Pun, LUN: n.Lun}, nil case uefi.MSG_USB_DP: var n uefi.USB_DEVICE_PATH if err := binary.Read(buf, binary.LittleEndian, &n); err != nil { return nil, err } return &USBDevicePathNode{ParentPortNumber: n.ParentPortNumber, InterfaceNumber: n.InterfaceNumber}, nil case uefi.MSG_USB_CLASS_DP: var n uefi.USB_CLASS_DEVICE_PATH if err := binary.Read(buf, binary.LittleEndian, &n); err != nil { return nil, err } return &USBClassDevicePathNode{ VendorId: n.VendorId, ProductId: n.ProductId, DeviceClass: USBClass(n.DeviceClass), DeviceSubClass: n.DeviceSubClass, DeviceProtocol: n.DeviceProtocol}, nil case uefi.MSG_VENDOR_DP: return readVendorDevicePathNode(buf) case uefi.MSG_USB_WWID_DP: n, err := uefi.Read_USB_WWID_DEVICE_PATH(buf) if err != nil { return nil, err } return &USBWWIDDevicePathNode{ InterfaceNumber: n.InterfaceNumber, VendorId: n.VendorId, ProductId: n.ProductId, SerialNumber: ConvertUTF16ToUTF8(n.SerialNumber)}, nil case uefi.MSG_DEVICE_LOGICAL_UNIT_DP: var n uefi.DEVICE_LOGICAL_UNIT_DEVICE_PATH if err := binary.Read(buf, binary.LittleEndian, &n); err != nil { return nil, err } return &DeviceLogicalUnitDevicePathNode{LUN: n.Lun}, nil case uefi.MSG_SATA_DP: var n uefi.SATA_DEVICE_PATH if err := binary.Read(buf, binary.LittleEndian, &n); err != nil { return nil, err } return &SATADevicePathNode{ HBAPortNumber: n.HBAPortNumber, PortMultiplierPortNumber: n.PortMultiplierPortNumber, LUN: n.Lun}, nil case uefi.MSG_NVME_NAMESPACE_DP: var n uefi.NVME_NAMESPACE_DEVICE_PATH if err := binary.Read(buf, binary.LittleEndian, &n); err != nil { return nil, err } return &NVMENamespaceDevicePathNode{ NamespaceID: n.NamespaceId, NamespaceUUID: n.NamespaceUuid}, nil } case uefi.MEDIA_DEVICE_PATH: switch h.SubType { case uefi.MEDIA_HARDDRIVE_DP: var n uefi.HARDDRIVE_DEVICE_PATH if err := binary.Read(buf, binary.LittleEndian, &n); err != nil { return nil, err } var signature HardDriveSignature switch n.SignatureType { case uefi.NO_DISK_SIGNATURE: case uefi.SIGNATURE_TYPE_MBR: signature = MBRHardDriveSignature(binary.LittleEndian.Uint32(n.Signature[:])) case uefi.SIGNATURE_TYPE_GUID: signature = GUIDHardDriveSignature(n.Signature) default: signature = &genericHardDriveSignature{ typ: HardDriveSignatureType(n.SignatureType), data: n.Signature} } return &HardDriveDevicePathNode{ PartitionNumber: n.PartitionNumber, PartitionStart: n.PartitionStart, PartitionSize: n.PartitionSize, Signature: signature, MBRType: MBRType(n.MBRType)}, nil case uefi.MEDIA_CDROM_DP: var n uefi.CDROM_DEVICE_PATH if err := binary.Read(buf, binary.LittleEndian, &n); err != nil { return nil, err } return &CDROMDevicePathNode{ BootEntry: n.BootEntry, PartitionStart: n.PartitionStart, PartitionSize: n.PartitionSize}, nil case uefi.MEDIA_VENDOR_DP: return readVendorDevicePathNode(buf) case uefi.MEDIA_FILEPATH_DP: n, err := uefi.Read_FILEPATH_DEVICE_PATH(buf) if err != nil { return nil, err } return FilePathDevicePathNode(ConvertUTF16ToUTF8(n.PathName)), nil case uefi.MEDIA_PIWG_FW_FILE_DP: var n uefi.MEDIA_FW_VOL_FILEPATH_DEVICE_PATH if err := binary.Read(buf, binary.LittleEndian, &n); err != nil { return nil, err } return MediaFvFileDevicePathNode(GUID(n.FvFileName)), nil case uefi.MEDIA_PIWG_FW_VOL_DP: var n uefi.MEDIA_FW_VOL_DEVICE_PATH if err := binary.Read(buf, binary.LittleEndian, &n); err != nil { return nil, err } return MediaFvDevicePathNode(GUID(n.FvName)), nil case uefi.MEDIA_RELATIVE_OFFSET_RANGE_DP: var n uefi.MEDIA_RELATIVE_OFFSET_RANGE_DEVICE_PATH if err := binary.Read(buf, binary.LittleEndian, &n); err != nil { return nil, err } return &MediaRelOffsetRangeDevicePathNode{StartingOffset: n.StartingOffset, EndingOffset: n.EndingOffset}, nil } case uefi.END_DEVICE_PATH_TYPE: buf.Reset() return nil, nil } var n uefi.EFI_DEVICE_PATH_PROTOCOL if err := binary.Read(buf, binary.LittleEndian, &n); err != nil { return nil, err } data, _ := ioutil.ReadAll(buf) return &GenericDevicePathNode{Type: DevicePathType(n.Type), SubType: DevicePathSubType(n.SubType), Data: data}, nil } // ReadDevicePath decodes a device path from the supplied io.Reader. func ReadDevicePath(r io.Reader) (out DevicePath, err error) { for i := 0; ; i++ { node, err := decodeDevicePathNode(r) switch { case err != nil && i == 0: return nil, ioerr.PassRawEOF("cannot decode node %d: %w", i, err) case err != nil: return nil, ioerr.EOFIsUnexpected("cannot decode node: %d: %w", i, err) } if node == nil { break } out = append(out, node) } return out, nil } ./github.com/canonical/go-efilib/go.mod0000664000000000000000000000047600000000000016304 0ustar0000000000000000module github.com/canonical/go-efilib go 1.13 require ( github.com/jessevdk/go-flags v1.5.0 go.mozilla.org/pkcs7 v0.0.0-20200128120323-432b2356ecb1 golang.org/x/sys v0.0.0-20210324051608-47abb6519492 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c ) ./github.com/canonical/go-efilib/go.sum0000664000000000000000000000276200000000000016331 0ustar0000000000000000github.com/jessevdk/go-flags v1.5.0 h1:1jKYvbxEjfUl0fmqTCOfonvskHHXMjBySTLW4y9LFvc= github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4= github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= go.mozilla.org/pkcs7 v0.0.0-20200128120323-432b2356ecb1 h1:A/5uWzF44DlIgdm/PQFwfMkW0JX+cIcQi/SwLAmZP5M= go.mozilla.org/pkcs7 v0.0.0-20200128120323-432b2356ecb1/go.mod h1:SNgMg+EgDFwmvSmLRTNKC5fegJjB7v23qTQ0XLGUNHk= golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210324051608-47abb6519492 h1:Paq34FxTluEPvVyayQqMPgHm+vTOrIifmcYxFBx9TLg= golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= ./github.com/canonical/go-efilib/gpt.go0000664000000000000000000002373300000000000016320 0ustar0000000000000000// Copyright 2020 Canonical Ltd. // Licensed under the LGPLv3 with static-linking exception. // See LICENCE file for details. package efi import ( "bytes" "encoding/binary" "errors" "fmt" "hash/crc32" "io" "golang.org/x/xerrors" "github.com/canonical/go-efilib/internal/uefi" "github.com/canonical/go-efilib/mbr" ) var ( ErrCRCCheck = errors.New("CRC check failed") ErrNoProtectiveMBR = errors.New("no protective master boot record found") // UnusedPartitionType is the type GUID of an unused partition entry. UnusedPartitionType GUID ) type InvalidGPTHeaderError string func (e InvalidGPTHeaderError) Error() string { return "invalid GPT header: " + string(e) } // PartitionTableHeader correponds to the EFI_PARTITION_TABLE_HEADER type. type PartitionTableHeader struct { HeaderSize uint32 MyLBA LBA AlternateLBA LBA FirstUsableLBA LBA LastUsableLBA LBA DiskGUID GUID PartitionEntryLBA LBA NumberOfPartitionEntries uint32 SizeOfPartitionEntry uint32 PartitionEntryArrayCRC32 uint32 } // Write serializes this PartitionTableHeader to w. The CRC field is // computed automatically. func (h *PartitionTableHeader) Write(w io.Writer) error { hdr := uefi.EFI_PARTITION_TABLE_HEADER{ Hdr: uefi.EFI_TABLE_HEADER{ Signature: uefi.EFI_PTAB_HEADER_ID, Revision: 0x10000, HeaderSize: h.HeaderSize}, MyLBA: uefi.EFI_LBA(h.MyLBA), AlternateLBA: uefi.EFI_LBA(h.AlternateLBA), FirstUsableLBA: uefi.EFI_LBA(h.FirstUsableLBA), LastUsableLBA: uefi.EFI_LBA(h.LastUsableLBA), DiskGUID: uefi.EFI_GUID(h.DiskGUID), PartitionEntryLBA: uefi.EFI_LBA(h.PartitionEntryLBA), NumberOfPartitionEntries: h.NumberOfPartitionEntries, SizeOfPartitionEntry: h.SizeOfPartitionEntry, PartitionEntryArrayCRC32: h.PartitionEntryArrayCRC32} hdrSize := binary.Size(hdr) if h.HeaderSize < uint32(hdrSize) { return errors.New("invalid HeaderSize") } reserved := make([]byte, int(h.HeaderSize)-hdrSize) crc := crc32.NewIEEE() binary.Write(crc, binary.LittleEndian, &hdr) crc.Write(reserved) hdr.Hdr.CRC = crc.Sum32() if err := binary.Write(w, binary.LittleEndian, &hdr); err != nil { return err } _, err := w.Write(reserved) return err } func (h *PartitionTableHeader) String() string { return fmt.Sprintf("EFI_PARTITION_TABLE_HEADER{ MyLBA: 0x%x, AlternateLBA: 0x%x, FirstUsableLBA: 0x%x, "+ "LastUsableLBA: 0x%x, DiskGUID: %v, PartitionEntryLBA: 0x%x, NumberOfPartitionEntries: %d, "+ "SizeOfPartitionEntry: 0x%x, PartitionEntryArrayCRC32: 0x%08x }", h.MyLBA, h.AlternateLBA, h.FirstUsableLBA, h.LastUsableLBA, h.DiskGUID, h.PartitionEntryLBA, h.NumberOfPartitionEntries, h.SizeOfPartitionEntry, h.PartitionEntryArrayCRC32) } // ReadPartitionTableHeader reads a EFI_PARTITION_TABLE_HEADER from the supplied io.Reader. // If the header signature or revision is incorrect, an error will be returned. If // checkCrc is true and the header has an invalid CRC, an error will be returned. // If checkCrc is false, then a CRC check is not performed. func ReadPartitionTableHeader(r io.Reader, checkCrc bool) (*PartitionTableHeader, error) { hdr, crc, err := uefi.Read_EFI_PARTITION_TABLE_HEADER(r) if err != nil { return nil, err } if hdr.Hdr.Signature != uefi.EFI_PTAB_HEADER_ID { return nil, InvalidGPTHeaderError("invalid signature") } if hdr.Hdr.Revision != 0x10000 { return nil, InvalidGPTHeaderError("unexpected revision") } if checkCrc && hdr.Hdr.CRC != crc { return nil, ErrCRCCheck } return &PartitionTableHeader{ HeaderSize: hdr.Hdr.HeaderSize, MyLBA: LBA(hdr.MyLBA), AlternateLBA: LBA(hdr.AlternateLBA), FirstUsableLBA: LBA(hdr.FirstUsableLBA), LastUsableLBA: LBA(hdr.LastUsableLBA), DiskGUID: GUID(hdr.DiskGUID), PartitionEntryLBA: LBA(hdr.PartitionEntryLBA), NumberOfPartitionEntries: hdr.NumberOfPartitionEntries, SizeOfPartitionEntry: hdr.SizeOfPartitionEntry, PartitionEntryArrayCRC32: hdr.PartitionEntryArrayCRC32}, nil } // PartitionEntry corresponds to the EFI_PARTITION_ENTRY type. type PartitionEntry struct { PartitionTypeGUID GUID UniquePartitionGUID GUID StartingLBA LBA EndingLBA LBA Attributes uint64 PartitionName string } func (e *PartitionEntry) String() string { return fmt.Sprintf("EFI_PARTITION_ENTRY{ PartitionTypeGUID: %s, UniquePartitionGUID: %s, StartingLBA: 0x%x, "+ "EndingLBA: 0x%x, Attributes: 0x%016x, PartitionName: \"%s\" }", e.PartitionTypeGUID, e.UniquePartitionGUID, e.StartingLBA, e.EndingLBA, e.Attributes, e.PartitionName) } // Write serializes this PartitionEntry to w. Note that it doesn't write // any bytes beyond the end of the EFI_PARTITION_ENTRY structure, so if the // caller is writing several entries and the partition table header defines // an entry size of greater than 128 bytes, the caller is responsible for // inserting the 0 padding bytes. func (e *PartitionEntry) Write(w io.Writer) error { entry := uefi.EFI_PARTITION_ENTRY{ PartitionTypeGUID: uefi.EFI_GUID(e.PartitionTypeGUID), UniquePartitionGUID: uefi.EFI_GUID(e.UniquePartitionGUID), StartingLBA: uefi.EFI_LBA(e.StartingLBA), EndingLBA: uefi.EFI_LBA(e.EndingLBA), Attributes: e.Attributes} partitionName := ConvertUTF8ToUTF16(e.PartitionName) if len(partitionName) > len(entry.PartitionName) { return errors.New("PartitionName is too long") } copy(entry.PartitionName[:], partitionName) return binary.Write(w, binary.LittleEndian, &entry) } // ReadPartitionEntry reads a single EFI_PARTITION_ENTRY from r. func ReadPartitionEntry(r io.Reader) (*PartitionEntry, error) { var e uefi.EFI_PARTITION_ENTRY if err := binary.Read(r, binary.LittleEndian, &e); err != nil { return nil, err } return &PartitionEntry{ PartitionTypeGUID: GUID(e.PartitionTypeGUID), UniquePartitionGUID: GUID(e.UniquePartitionGUID), StartingLBA: LBA(e.StartingLBA), EndingLBA: LBA(e.EndingLBA), Attributes: e.Attributes, PartitionName: ConvertUTF16ToUTF8(e.PartitionName[:])}, nil } func readPartitionEntries(r io.Reader, num, sz, expectedCrc uint32, checkCrc bool) (out []*PartitionEntry, err error) { crc := crc32.NewIEEE() r2 := io.TeeReader(r, crc) b := new(bytes.Buffer) for i := uint32(0); i < num; i++ { b.Reset() if _, err := io.CopyN(b, r2, int64(sz)); err != nil { switch { case err == io.EOF && i == 0: return nil, err case err == io.EOF: err = io.ErrUnexpectedEOF } return nil, xerrors.Errorf("cannot read entry %d: %w", i, err) } e, err := ReadPartitionEntry(b) if err != nil { return nil, err } out = append(out, e) } if checkCrc && crc.Sum32() != expectedCrc { return nil, ErrCRCCheck } return out, nil } // ReadPartitionEntries reads the specified number of EFI_PARTITION_ENTRY structures // of the specified size from the supplied io.Reader. The number and size are typically // defined by the partition table header. func ReadPartitionEntries(r io.Reader, num, sz uint32) ([]*PartitionEntry, error) { return readPartitionEntries(r, num, sz, 0, false) } var emptyPartitionType GUID // PartitionTableRole describes the role of a partition table. type PartitionTableRole int const ( PrimaryPartitionTable PartitionTableRole = iota BackupPartitionTable ) // PartitionTable describes a complete GUID partition table. type PartitionTable struct { Hdr *PartitionTableHeader Entries []*PartitionEntry } func (t *PartitionTable) String() string { b := new(bytes.Buffer) fmt.Fprintf(b, "GPT{\n\tHdr: %s,\n\tEntries: [", t.Hdr) for _, entry := range t.Entries { fmt.Fprintf(b, "\n\t\t%s", entry) } fmt.Fprintf(b, "\n\t]\n}") return b.String() } // ReadPartitionTable reads a complete GUID partition table from the supplied // io.Reader. The total size and logical block size of the device must be // supplied - the logical block size is 512 bytes for a file, but must be // obtained from the kernel for a block device. // // This function expects the device to have a valid protective MBR. // // If role is PrimaryPartitionTable, this will read the primary partition // table that is located immediately after the protective MBR. If role is // BackupPartitionTable, this will read the backup partition table that is // located at the end of the device. // // If checkCrc is true and either CRC check fails, an error will be returned. // Setting checkCrc to false disables the CRC checks. func ReadPartitionTable(r io.ReaderAt, totalSz, blockSz int64, role PartitionTableRole, checkCrc bool) (*PartitionTable, error) { r2 := io.NewSectionReader(r, 0, totalSz) record, err := mbr.ReadRecord(r2) if err != nil { return nil, err } validPmbr := false for _, p := range record.Partitions { if p.Type == 0xee { validPmbr = true break } } if !validPmbr { return nil, ErrNoProtectiveMBR } var offset int64 var whence int switch role { case PrimaryPartitionTable: offset = blockSz whence = io.SeekStart case BackupPartitionTable: if _, err := r2.Seek(blockSz, io.SeekStart); err != nil { return nil, err } hdr, err := ReadPartitionTableHeader(r2, checkCrc) if err != nil { offset = -blockSz whence = io.SeekEnd } else { offset = int64(hdr.AlternateLBA) * blockSz whence = io.SeekStart } default: panic("invalid role") } if _, err := r2.Seek(offset, whence); err != nil { return nil, err } hdr, err := ReadPartitionTableHeader(r2, checkCrc) switch { case err == io.EOF: return nil, io.ErrUnexpectedEOF case err != nil: return nil, err } if _, err := r2.Seek(int64(hdr.PartitionEntryLBA)*blockSz, io.SeekStart); err != nil { return nil, err } entries, err := readPartitionEntries(r2, hdr.NumberOfPartitionEntries, hdr.SizeOfPartitionEntry, hdr.PartitionEntryArrayCRC32, checkCrc) switch { case err == io.EOF: return nil, io.ErrUnexpectedEOF case err != nil: return nil, err } return &PartitionTable{hdr, entries}, nil } ./github.com/canonical/go-efilib/guid.go0000664000000000000000000000443500000000000016454 0ustar0000000000000000// Copyright 2020 Canonical Ltd. // Licensed under the LGPLv3 with static-linking exception. // See LICENCE file for details. package efi import ( "encoding/binary" "encoding/hex" "errors" "fmt" "io" "regexp" "github.com/canonical/go-efilib/internal/uefi" ) // GUID corresponds to the EFI_GUID type. type GUID [16]byte func (guid GUID) A() uint32 { return binary.LittleEndian.Uint32(guid[0:4]) } func (guid GUID) B() uint16 { return binary.LittleEndian.Uint16(guid[4:6]) } func (guid GUID) C() uint16 { return binary.LittleEndian.Uint16(guid[6:8]) } func (guid GUID) D() uint16 { return binary.BigEndian.Uint16(guid[8:10]) } func (guid GUID) E() [6]uint8 { var out [6]uint8 copy(out[:], guid[10:16]) return out } func (guid GUID) String() string { return fmt.Sprintf("%08x-%04x-%04x-%04x-%012x", guid.A(), guid.B(), guid.C(), guid.D(), guid.E()) } // MakeGUID makes a new GUID from the supplied arguments. func MakeGUID(a uint32, b, c, d uint16, e [6]uint8) GUID { return GUID(uefi.New_EFI_GUID(a, b, c, d, e)) } // ReadGUID reads a EFI_GUID from the supplied io.Reader. func ReadGUID(r io.Reader) (out GUID, err error) { _, err = io.ReadFull(r, out[:]) return } var guidRe = regexp.MustCompile(`\{?([[:xdigit:]]{8})-([[:xdigit:]]{4})-([[:xdigit:]]{4})-([[:xdigit:]]{4})-([[:xdigit:]]{12})\}?`) func decodeStringUint32(s string) (uint32, error) { h, err := hex.DecodeString(s) if err != nil { return 0, err } if len(h) > 4 { return 0, errors.New("invalid length") } return binary.BigEndian.Uint32(h), nil } func decodeStringUint16(s string) (uint16, error) { h, err := hex.DecodeString(s) if err != nil { return 0, err } if len(h) > 2 { return 0, errors.New("invalid length") } return binary.BigEndian.Uint16(h), nil } // DecodeGUIDString decodes the supplied GUID string. The string must have // the format "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" and may be surrounded // by curly braces. func DecodeGUIDString(s string) (GUID, error) { m := guidRe.FindStringSubmatch(s) if m == nil { return GUID{}, errors.New("invalid format") } a, _ := decodeStringUint32(m[1]) b, _ := decodeStringUint16(m[2]) c, _ := decodeStringUint16(m[3]) d, _ := decodeStringUint16(m[4]) e, _ := hex.DecodeString(m[5]) var e2 [6]uint8 copy(e2[:], e) return MakeGUID(a, b, c, d, e2), nil } ./github.com/canonical/go-efilib/ids.go0000664000000000000000000000272300000000000016301 0ustar0000000000000000// Copyright 2020 Canonical Ltd. // Licensed under the LGPLv3 with static-linking exception. // See LICENCE file for details. package efi import ( "github.com/canonical/go-efilib/internal/uefi" ) var ( HashAlgorithmSHA1Guid = GUID(uefi.EFI_HASH_ALGORITHM_SHA1_GUID) HashAlgorithmSHA256Guid = GUID(uefi.EFI_HASH_ALGORITHM_SHA256_GUID) HashAlgorithmSHA224Guid = GUID(uefi.EFI_HASH_ALGORITHM_SHA224_GUID) HashAlgorithmSHA384Guid = GUID(uefi.EFI_HASH_ALGORITHM_SHA384_GUID) HashAlgorithmSHA512Guid = GUID(uefi.EFI_HASH_ALGORITHM_SHA512_GUID) CertTypeRSA2048SHA256Guid = GUID(uefi.EFI_CERT_TYPE_RSA2048_SHA256_GUID) CertTypePKCS7Guid = GUID(uefi.EFI_CERT_TYPE_PKCS7_GUID) CertSHA1Guid = GUID(uefi.EFI_CERT_SHA1_GUID) CertSHA256Guid = GUID(uefi.EFI_CERT_SHA256_GUID) CertSHA224Guid = GUID(uefi.EFI_CERT_SHA224_GUID) CertSHA384Guid = GUID(uefi.EFI_CERT_SHA384_GUID) CertSHA512Guid = GUID(uefi.EFI_CERT_SHA512_GUID) CertRSA2048Guid = GUID(uefi.EFI_CERT_RSA2048_GUID) CertRSA2048SHA1Guid = GUID(uefi.EFI_CERT_RSA2048_SHA1_GUID) CertRSA2048SHA256Guid = GUID(uefi.EFI_CERT_RSA2048_SHA256_GUID) CertX509Guid = GUID(uefi.EFI_CERT_X509_GUID) CertX509SHA256Guid = GUID(uefi.EFI_CERT_X509_SHA256_GUID) CertX509SHA384Guid = GUID(uefi.EFI_CERT_X509_SHA384_GUID) CertX509SHA512Guid = GUID(uefi.EFI_CERT_X509_SHA512_GUID) GlobalVariable = GUID(uefi.EFI_GLOBAL_VARIABLE) ImageSecurityDatabaseGuid = GUID(uefi.EFI_IMAGE_SECURITY_DATABASE_GUID) ) ./github.com/canonical/go-efilib/internal/0000775000000000000000000000000000000000000017003 5ustar0000000000000000./github.com/canonical/go-efilib/internal/ioerr/0000775000000000000000000000000000000000000020123 5ustar0000000000000000./github.com/canonical/go-efilib/internal/ioerr/ioerr.go0000664000000000000000000000473400000000000021602 0ustar0000000000000000// Copyright 2021 Canonical Ltd. // Licensed under the LGPLv3 with static-linking exception. // See LICENCE file for details. package ioerr import ( "io" "unicode" "unicode/utf8" "golang.org/x/xerrors" ) // Return the index of the first %w in format, or -1 if none. // TODO: handle "%[N]w". func parsePercentW(format string) int { // Loosely copied from golang.org/x/xerrors/fmt.go. n := 0 sz := 0 var isW bool for i := 0; i < len(format); i += sz { if format[i] != '%' { sz = 1 continue } // "%%" is not a format directive. if i+1 < len(format) && format[i+1] == '%' { sz = 2 continue } sz, isW = parsePrintfVerb(format[i:]) if isW { return n } n++ } return -1 } // Parse the printf verb starting with a % at s[0]. // Return how many bytes it occupies and whether the verb is 'w'. func parsePrintfVerb(s string) (int, bool) { // Assume only that the directive is a sequence of non-letters followed by a single letter. sz := 0 var r rune for i := 1; i < len(s); i += sz { r, sz = utf8.DecodeRuneInString(s[i:]) if unicode.IsLetter(r) { return i + sz, r == 'w' } } return len(s), false } // EOFIsUnexpected converts io.EOF errors into io.ErrUnexpected, which is // useful when using binary.Read to decode aprts of a structure that aren't // at the start and when a io.EOF error is not expected. // // It can be called in one of 2 ways - either with a single argument which // must be an error, or with a format string and an arbitrary number of // arguments. In this second mode, the function is a wrapper around // xerrors.Errorf. // // This only works on raw io.EOF errors - ie, it won't work on errors that // have been wrapped. func EOFIsUnexpected(args ...interface{}) error { switch { case len(args) > 1: format := args[0].(string) idx := parsePercentW(format) if idx >= 0 { if err, isErr := args[idx+1].(error); isErr && err == io.EOF { args[idx+1] = io.ErrUnexpectedEOF } } return xerrors.Errorf(format, args[1:]...) case len(args) == 1: switch err := args[0].(type) { case error: if err == io.EOF { err = io.ErrUnexpectedEOF } return err case nil: return nil default: panic("invalid type") } default: panic("no arguments") } } // PassRawEOF is a wrapper around xerrors.Errorf that will return a raw // io.EOF if this is the error. func PassRawEOF(format string, args ...interface{}) error { err := xerrors.Errorf(format, args...) if xerrors.Is(err, io.EOF) { return io.EOF } return err } ./github.com/canonical/go-efilib/internal/pe1.14/0000775000000000000000000000000000000000000017713 5ustar0000000000000000./github.com/canonical/go-efilib/internal/pe1.14/README0000664000000000000000000000067200000000000020600 0ustar0000000000000000This is a copy of debug/pe from go 1.14.2 (96745b980cfde139e8611772e2bc0c59a8e6cdf7) It exists here because debug/pe in go versions before 1.14 did not handle variable length optional headers (where the data directory is less than 16 entries) and therefore doesn't correctly decode kernel EFI images. Copying the code here avoids a hard dependency on go 1.14. See https://github.com/golang/go/commit/3b92f36d15c868e856be71c0fadfc7ff97039b96 ./github.com/canonical/go-efilib/internal/pe1.14/file.go0000664000000000000000000004104100000000000021161 0ustar0000000000000000// Copyright 2009 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // Package pe implements access to PE (Microsoft Windows Portable Executable) files. package pe import ( "bytes" "compress/zlib" "debug/dwarf" "encoding/binary" "fmt" "io" "os" "strings" ) // Avoid use of post-Go 1.4 io features, to make safe for toolchain bootstrap. const seekStart = 0 // A File represents an open PE file. type File struct { FileHeader OptionalHeader interface{} // of type *OptionalHeader32 or *OptionalHeader64 Sections []*Section Symbols []*Symbol // COFF symbols with auxiliary symbol records removed COFFSymbols []COFFSymbol // all COFF symbols (including auxiliary symbol records) StringTable StringTable closer io.Closer } // Open opens the named file using os.Open and prepares it for use as a PE binary. func Open(name string) (*File, error) { f, err := os.Open(name) if err != nil { return nil, err } ff, err := NewFile(f) if err != nil { f.Close() return nil, err } ff.closer = f return ff, nil } // Close closes the File. // If the File was created using NewFile directly instead of Open, // Close has no effect. func (f *File) Close() error { var err error if f.closer != nil { err = f.closer.Close() f.closer = nil } return err } // TODO(brainman): add Load function, as a replacement for NewFile, that does not call removeAuxSymbols (for performance) // NewFile creates a new File for accessing a PE binary in an underlying reader. func NewFile(r io.ReaderAt) (*File, error) { f := new(File) sr := io.NewSectionReader(r, 0, 1<<63-1) var dosheader [96]byte if _, err := r.ReadAt(dosheader[0:], 0); err != nil { return nil, err } var base int64 if dosheader[0] == 'M' && dosheader[1] == 'Z' { signoff := int64(binary.LittleEndian.Uint32(dosheader[0x3c:])) var sign [4]byte r.ReadAt(sign[:], signoff) if !(sign[0] == 'P' && sign[1] == 'E' && sign[2] == 0 && sign[3] == 0) { return nil, fmt.Errorf("Invalid PE COFF file signature of %v.", sign) } base = signoff + 4 } else { base = int64(0) } sr.Seek(base, seekStart) if err := binary.Read(sr, binary.LittleEndian, &f.FileHeader); err != nil { return nil, err } switch f.FileHeader.Machine { case IMAGE_FILE_MACHINE_UNKNOWN, IMAGE_FILE_MACHINE_ARMNT, IMAGE_FILE_MACHINE_AMD64, IMAGE_FILE_MACHINE_I386: default: return nil, fmt.Errorf("Unrecognised COFF file header machine value of 0x%x.", f.FileHeader.Machine) } var err error // Read string table. f.StringTable, err = readStringTable(&f.FileHeader, sr) if err != nil { return nil, err } // Read symbol table. f.COFFSymbols, err = readCOFFSymbols(&f.FileHeader, sr) if err != nil { return nil, err } f.Symbols, err = removeAuxSymbols(f.COFFSymbols, f.StringTable) if err != nil { return nil, err } // Seek past file header. _, err = sr.Seek(base+int64(binary.Size(f.FileHeader)), seekStart) if err != nil { return nil, fmt.Errorf("failure to seek past the file header: %v", err) } // Read optional header. f.OptionalHeader, err = readOptionalHeader(sr, f.FileHeader.SizeOfOptionalHeader) if err != nil { return nil, err } // Process sections. f.Sections = make([]*Section, f.FileHeader.NumberOfSections) for i := 0; i < int(f.FileHeader.NumberOfSections); i++ { sh := new(SectionHeader32) if err := binary.Read(sr, binary.LittleEndian, sh); err != nil { return nil, err } name, err := sh.fullName(f.StringTable) if err != nil { return nil, err } s := new(Section) s.SectionHeader = SectionHeader{ Name: name, VirtualSize: sh.VirtualSize, VirtualAddress: sh.VirtualAddress, Size: sh.SizeOfRawData, Offset: sh.PointerToRawData, PointerToRelocations: sh.PointerToRelocations, PointerToLineNumbers: sh.PointerToLineNumbers, NumberOfRelocations: sh.NumberOfRelocations, NumberOfLineNumbers: sh.NumberOfLineNumbers, Characteristics: sh.Characteristics, } r2 := r if sh.PointerToRawData == 0 { // .bss must have all 0s r2 = zeroReaderAt{} } s.sr = io.NewSectionReader(r2, int64(s.SectionHeader.Offset), int64(s.SectionHeader.Size)) s.ReaderAt = s.sr f.Sections[i] = s } for i := range f.Sections { var err error f.Sections[i].Relocs, err = readRelocs(&f.Sections[i].SectionHeader, sr) if err != nil { return nil, err } } return f, nil } // zeroReaderAt is ReaderAt that reads 0s. type zeroReaderAt struct{} // ReadAt writes len(p) 0s into p. func (w zeroReaderAt) ReadAt(p []byte, off int64) (n int, err error) { for i := range p { p[i] = 0 } return len(p), nil } // getString extracts a string from symbol string table. func getString(section []byte, start int) (string, bool) { if start < 0 || start >= len(section) { return "", false } for end := start; end < len(section); end++ { if section[end] == 0 { return string(section[start:end]), true } } return "", false } // Section returns the first section with the given name, or nil if no such // section exists. func (f *File) Section(name string) *Section { for _, s := range f.Sections { if s.Name == name { return s } } return nil } func (f *File) DWARF() (*dwarf.Data, error) { dwarfSuffix := func(s *Section) string { switch { case strings.HasPrefix(s.Name, ".debug_"): return s.Name[7:] case strings.HasPrefix(s.Name, ".zdebug_"): return s.Name[8:] default: return "" } } // sectionData gets the data for s and checks its size. sectionData := func(s *Section) ([]byte, error) { b, err := s.Data() if err != nil && uint32(len(b)) < s.Size { return nil, err } if 0 < s.VirtualSize && s.VirtualSize < s.Size { b = b[:s.VirtualSize] } if len(b) >= 12 && string(b[:4]) == "ZLIB" { dlen := binary.BigEndian.Uint64(b[4:12]) dbuf := make([]byte, dlen) r, err := zlib.NewReader(bytes.NewBuffer(b[12:])) if err != nil { return nil, err } if _, err := io.ReadFull(r, dbuf); err != nil { return nil, err } if err := r.Close(); err != nil { return nil, err } b = dbuf } return b, nil } // There are many other DWARF sections, but these // are the ones the debug/dwarf package uses. // Don't bother loading others. var dat = map[string][]byte{"abbrev": nil, "info": nil, "str": nil, "line": nil, "ranges": nil} for _, s := range f.Sections { suffix := dwarfSuffix(s) if suffix == "" { continue } if _, ok := dat[suffix]; !ok { continue } b, err := sectionData(s) if err != nil { return nil, err } dat[suffix] = b } d, err := dwarf.New(dat["abbrev"], nil, nil, dat["info"], dat["line"], nil, dat["ranges"], dat["str"]) if err != nil { return nil, err } // Look for DWARF4 .debug_types sections. for i, s := range f.Sections { suffix := dwarfSuffix(s) if suffix != "types" { continue } b, err := sectionData(s) if err != nil { return nil, err } err = d.AddTypes(fmt.Sprintf("types-%d", i), b) if err != nil { return nil, err } } return d, nil } // TODO(brainman): document ImportDirectory once we decide what to do with it. type ImportDirectory struct { OriginalFirstThunk uint32 TimeDateStamp uint32 ForwarderChain uint32 Name uint32 FirstThunk uint32 dll string } // ImportedSymbols returns the names of all symbols // referred to by the binary f that are expected to be // satisfied by other libraries at dynamic load time. // It does not return weak symbols. func (f *File) ImportedSymbols() ([]string, error) { if f.OptionalHeader == nil { return nil, nil } pe64 := f.Machine == IMAGE_FILE_MACHINE_AMD64 // grab the number of data directory entries var dd_length uint32 if pe64 { dd_length = f.OptionalHeader.(*OptionalHeader64).NumberOfRvaAndSizes } else { dd_length = f.OptionalHeader.(*OptionalHeader32).NumberOfRvaAndSizes } // check that the length of data directory entries is large // enough to include the imports directory. if dd_length < IMAGE_DIRECTORY_ENTRY_IMPORT+1 { return nil, nil } // grab the import data directory entry var idd DataDirectory if pe64 { idd = f.OptionalHeader.(*OptionalHeader64).DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT] } else { idd = f.OptionalHeader.(*OptionalHeader32).DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT] } // figure out which section contains the import directory table var ds *Section ds = nil for _, s := range f.Sections { if s.VirtualAddress <= idd.VirtualAddress && idd.VirtualAddress < s.VirtualAddress+s.VirtualSize { ds = s break } } // didn't find a section, so no import libraries were found if ds == nil { return nil, nil } d, err := ds.Data() if err != nil { return nil, err } // seek to the virtual address specified in the import data directory d = d[idd.VirtualAddress-ds.VirtualAddress:] // start decoding the import directory var ida []ImportDirectory for len(d) >= 20 { var dt ImportDirectory dt.OriginalFirstThunk = binary.LittleEndian.Uint32(d[0:4]) dt.TimeDateStamp = binary.LittleEndian.Uint32(d[4:8]) dt.ForwarderChain = binary.LittleEndian.Uint32(d[8:12]) dt.Name = binary.LittleEndian.Uint32(d[12:16]) dt.FirstThunk = binary.LittleEndian.Uint32(d[16:20]) d = d[20:] if dt.OriginalFirstThunk == 0 { break } ida = append(ida, dt) } // TODO(brainman): this needs to be rewritten // ds.Data() returns contents of section containing import table. Why store in variable called "names"? // Why we are retrieving it second time? We already have it in "d", and it is not modified anywhere. // getString does not extracts a string from symbol string table (as getString doco says). // Why ds.Data() called again and again in the loop? // Needs test before rewrite. names, _ := ds.Data() var all []string for _, dt := range ida { dt.dll, _ = getString(names, int(dt.Name-ds.VirtualAddress)) d, _ = ds.Data() // seek to OriginalFirstThunk d = d[dt.OriginalFirstThunk-ds.VirtualAddress:] for len(d) > 0 { if pe64 { // 64bit va := binary.LittleEndian.Uint64(d[0:8]) d = d[8:] if va == 0 { break } if va&0x8000000000000000 > 0 { // is Ordinal // TODO add dynimport ordinal support. } else { fn, _ := getString(names, int(uint32(va)-ds.VirtualAddress+2)) all = append(all, fn+":"+dt.dll) } } else { // 32bit va := binary.LittleEndian.Uint32(d[0:4]) d = d[4:] if va == 0 { break } if va&0x80000000 > 0 { // is Ordinal // TODO add dynimport ordinal support. //ord := va&0x0000FFFF } else { fn, _ := getString(names, int(va-ds.VirtualAddress+2)) all = append(all, fn+":"+dt.dll) } } } } return all, nil } // ImportedLibraries returns the names of all libraries // referred to by the binary f that are expected to be // linked with the binary at dynamic link time. func (f *File) ImportedLibraries() ([]string, error) { // TODO // cgo -dynimport don't use this for windows PE, so just return. return nil, nil } // FormatError is unused. // The type is retained for compatibility. type FormatError struct { } func (e *FormatError) Error() string { return "unknown error" } // readOptionalHeader accepts a io.ReadSeeker pointing to optional header in the PE file // and its size as seen in the file header. // It parses the given size of bytes and returns optional header. It infers whether the // bytes being parsed refer to 32 bit or 64 bit version of optional header. func readOptionalHeader(r io.ReadSeeker, sz uint16) (interface{}, error) { // If optional header size is 0, return empty optional header. if sz == 0 { return nil, nil } var ( // First couple of bytes in option header state its type. // We need to read them first to determine the type and // validity of optional header. ohMagic uint16 ohMagicSz = binary.Size(ohMagic) ) // If optional header size is greater than 0 but less than its magic size, return error. if sz < uint16(ohMagicSz) { return nil, fmt.Errorf("optional header size is less than optional header magic size") } // read reads from io.ReadSeeke, r, into data. var err error read := func(data interface{}) bool { err = binary.Read(r, binary.LittleEndian, data) return err == nil } if !read(&ohMagic) { return nil, fmt.Errorf("failure to read optional header magic: %v", err) } switch ohMagic { case 0x10b: // PE32 var ( oh32 OptionalHeader32 // There can be 0 or more data directories. So the minimum size of optional // header is calculated by subtracting oh32.DataDirectory size from oh32 size. oh32MinSz = binary.Size(oh32) - binary.Size(oh32.DataDirectory) ) if sz < uint16(oh32MinSz) { return nil, fmt.Errorf("optional header size(%d) is less minimum size (%d) of PE32 optional header", sz, oh32MinSz) } // Init oh32 fields oh32.Magic = ohMagic if !read(&oh32.MajorLinkerVersion) || !read(&oh32.MinorLinkerVersion) || !read(&oh32.SizeOfCode) || !read(&oh32.SizeOfInitializedData) || !read(&oh32.SizeOfUninitializedData) || !read(&oh32.AddressOfEntryPoint) || !read(&oh32.BaseOfCode) || !read(&oh32.BaseOfData) || !read(&oh32.ImageBase) || !read(&oh32.SectionAlignment) || !read(&oh32.FileAlignment) || !read(&oh32.MajorOperatingSystemVersion) || !read(&oh32.MinorOperatingSystemVersion) || !read(&oh32.MajorImageVersion) || !read(&oh32.MinorImageVersion) || !read(&oh32.MajorSubsystemVersion) || !read(&oh32.MinorSubsystemVersion) || !read(&oh32.Win32VersionValue) || !read(&oh32.SizeOfImage) || !read(&oh32.SizeOfHeaders) || !read(&oh32.CheckSum) || !read(&oh32.Subsystem) || !read(&oh32.DllCharacteristics) || !read(&oh32.SizeOfStackReserve) || !read(&oh32.SizeOfStackCommit) || !read(&oh32.SizeOfHeapReserve) || !read(&oh32.SizeOfHeapCommit) || !read(&oh32.LoaderFlags) || !read(&oh32.NumberOfRvaAndSizes) { return nil, fmt.Errorf("failure to read PE32 optional header: %v", err) } dd, err := readDataDirectories(r, sz-uint16(oh32MinSz), oh32.NumberOfRvaAndSizes) if err != nil { return nil, err } copy(oh32.DataDirectory[:], dd) return &oh32, nil case 0x20b: // PE32+ var ( oh64 OptionalHeader64 // There can be 0 or more data directories. So the minimum size of optional // header is calculated by subtracting oh64.DataDirectory size from oh64 size. oh64MinSz = binary.Size(oh64) - binary.Size(oh64.DataDirectory) ) if sz < uint16(oh64MinSz) { return nil, fmt.Errorf("optional header size(%d) is less minimum size (%d) for PE32+ optional header", sz, oh64MinSz) } // Init oh64 fields oh64.Magic = ohMagic if !read(&oh64.MajorLinkerVersion) || !read(&oh64.MinorLinkerVersion) || !read(&oh64.SizeOfCode) || !read(&oh64.SizeOfInitializedData) || !read(&oh64.SizeOfUninitializedData) || !read(&oh64.AddressOfEntryPoint) || !read(&oh64.BaseOfCode) || !read(&oh64.ImageBase) || !read(&oh64.SectionAlignment) || !read(&oh64.FileAlignment) || !read(&oh64.MajorOperatingSystemVersion) || !read(&oh64.MinorOperatingSystemVersion) || !read(&oh64.MajorImageVersion) || !read(&oh64.MinorImageVersion) || !read(&oh64.MajorSubsystemVersion) || !read(&oh64.MinorSubsystemVersion) || !read(&oh64.Win32VersionValue) || !read(&oh64.SizeOfImage) || !read(&oh64.SizeOfHeaders) || !read(&oh64.CheckSum) || !read(&oh64.Subsystem) || !read(&oh64.DllCharacteristics) || !read(&oh64.SizeOfStackReserve) || !read(&oh64.SizeOfStackCommit) || !read(&oh64.SizeOfHeapReserve) || !read(&oh64.SizeOfHeapCommit) || !read(&oh64.LoaderFlags) || !read(&oh64.NumberOfRvaAndSizes) { return nil, fmt.Errorf("failure to read PE32+ optional header: %v", err) } dd, err := readDataDirectories(r, sz-uint16(oh64MinSz), oh64.NumberOfRvaAndSizes) if err != nil { return nil, err } copy(oh64.DataDirectory[:], dd) return &oh64, nil default: return nil, fmt.Errorf("optional header has unexpected Magic of 0x%x", ohMagic) } } // readDataDirectories accepts a io.ReadSeeker pointing to data directories in the PE file, // its size and number of data directories as seen in optional header. // It parses the given size of bytes and returns given number of data directories. func readDataDirectories(r io.ReadSeeker, sz uint16, n uint32) ([]DataDirectory, error) { ddSz := binary.Size(DataDirectory{}) if uint32(sz) != n*uint32(ddSz) { return nil, fmt.Errorf("size of data directories(%d) is inconsistent with number of data directories(%d)", sz, n) } dd := make([]DataDirectory, n) if err := binary.Read(r, binary.LittleEndian, dd); err != nil { return nil, fmt.Errorf("failure to read data directories: %v", err) } return dd, nil } ./github.com/canonical/go-efilib/internal/pe1.14/pe.go0000664000000000000000000001030200000000000020642 0ustar0000000000000000// Copyright 2009 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package pe type FileHeader struct { Machine uint16 NumberOfSections uint16 TimeDateStamp uint32 PointerToSymbolTable uint32 NumberOfSymbols uint32 SizeOfOptionalHeader uint16 Characteristics uint16 } type DataDirectory struct { VirtualAddress uint32 Size uint32 } type OptionalHeader32 struct { Magic uint16 MajorLinkerVersion uint8 MinorLinkerVersion uint8 SizeOfCode uint32 SizeOfInitializedData uint32 SizeOfUninitializedData uint32 AddressOfEntryPoint uint32 BaseOfCode uint32 BaseOfData uint32 ImageBase uint32 SectionAlignment uint32 FileAlignment uint32 MajorOperatingSystemVersion uint16 MinorOperatingSystemVersion uint16 MajorImageVersion uint16 MinorImageVersion uint16 MajorSubsystemVersion uint16 MinorSubsystemVersion uint16 Win32VersionValue uint32 SizeOfImage uint32 SizeOfHeaders uint32 CheckSum uint32 Subsystem uint16 DllCharacteristics uint16 SizeOfStackReserve uint32 SizeOfStackCommit uint32 SizeOfHeapReserve uint32 SizeOfHeapCommit uint32 LoaderFlags uint32 NumberOfRvaAndSizes uint32 DataDirectory [16]DataDirectory } type OptionalHeader64 struct { Magic uint16 MajorLinkerVersion uint8 MinorLinkerVersion uint8 SizeOfCode uint32 SizeOfInitializedData uint32 SizeOfUninitializedData uint32 AddressOfEntryPoint uint32 BaseOfCode uint32 ImageBase uint64 SectionAlignment uint32 FileAlignment uint32 MajorOperatingSystemVersion uint16 MinorOperatingSystemVersion uint16 MajorImageVersion uint16 MinorImageVersion uint16 MajorSubsystemVersion uint16 MinorSubsystemVersion uint16 Win32VersionValue uint32 SizeOfImage uint32 SizeOfHeaders uint32 CheckSum uint32 Subsystem uint16 DllCharacteristics uint16 SizeOfStackReserve uint64 SizeOfStackCommit uint64 SizeOfHeapReserve uint64 SizeOfHeapCommit uint64 LoaderFlags uint32 NumberOfRvaAndSizes uint32 DataDirectory [16]DataDirectory } const ( IMAGE_FILE_MACHINE_UNKNOWN = 0x0 IMAGE_FILE_MACHINE_AM33 = 0x1d3 IMAGE_FILE_MACHINE_AMD64 = 0x8664 IMAGE_FILE_MACHINE_ARM = 0x1c0 IMAGE_FILE_MACHINE_ARMNT = 0x1c4 IMAGE_FILE_MACHINE_ARM64 = 0xaa64 IMAGE_FILE_MACHINE_EBC = 0xebc IMAGE_FILE_MACHINE_I386 = 0x14c IMAGE_FILE_MACHINE_IA64 = 0x200 IMAGE_FILE_MACHINE_M32R = 0x9041 IMAGE_FILE_MACHINE_MIPS16 = 0x266 IMAGE_FILE_MACHINE_MIPSFPU = 0x366 IMAGE_FILE_MACHINE_MIPSFPU16 = 0x466 IMAGE_FILE_MACHINE_POWERPC = 0x1f0 IMAGE_FILE_MACHINE_POWERPCFP = 0x1f1 IMAGE_FILE_MACHINE_R4000 = 0x166 IMAGE_FILE_MACHINE_SH3 = 0x1a2 IMAGE_FILE_MACHINE_SH3DSP = 0x1a3 IMAGE_FILE_MACHINE_SH4 = 0x1a6 IMAGE_FILE_MACHINE_SH5 = 0x1a8 IMAGE_FILE_MACHINE_THUMB = 0x1c2 IMAGE_FILE_MACHINE_WCEMIPSV2 = 0x169 ) // IMAGE_DIRECTORY_ENTRY constants const ( IMAGE_DIRECTORY_ENTRY_EXPORT = 0 IMAGE_DIRECTORY_ENTRY_IMPORT = 1 IMAGE_DIRECTORY_ENTRY_RESOURCE = 2 IMAGE_DIRECTORY_ENTRY_EXCEPTION = 3 IMAGE_DIRECTORY_ENTRY_SECURITY = 4 IMAGE_DIRECTORY_ENTRY_BASERELOC = 5 IMAGE_DIRECTORY_ENTRY_DEBUG = 6 IMAGE_DIRECTORY_ENTRY_ARCHITECTURE = 7 IMAGE_DIRECTORY_ENTRY_GLOBALPTR = 8 IMAGE_DIRECTORY_ENTRY_TLS = 9 IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG = 10 IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT = 11 IMAGE_DIRECTORY_ENTRY_IAT = 12 IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT = 13 IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR = 14 ) ./github.com/canonical/go-efilib/internal/pe1.14/section.go0000664000000000000000000000560400000000000021713 0ustar0000000000000000// Copyright 2016 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package pe import ( "encoding/binary" "fmt" "io" "strconv" ) // SectionHeader32 represents real PE COFF section header. type SectionHeader32 struct { Name [8]uint8 VirtualSize uint32 VirtualAddress uint32 SizeOfRawData uint32 PointerToRawData uint32 PointerToRelocations uint32 PointerToLineNumbers uint32 NumberOfRelocations uint16 NumberOfLineNumbers uint16 Characteristics uint32 } // fullName finds real name of section sh. Normally name is stored // in sh.Name, but if it is longer then 8 characters, it is stored // in COFF string table st instead. func (sh *SectionHeader32) fullName(st StringTable) (string, error) { if sh.Name[0] != '/' { return cstring(sh.Name[:]), nil } i, err := strconv.Atoi(cstring(sh.Name[1:])) if err != nil { return "", err } return st.String(uint32(i)) } // TODO(brainman): copy all IMAGE_REL_* consts from ldpe.go here // Reloc represents a PE COFF relocation. // Each section contains its own relocation list. type Reloc struct { VirtualAddress uint32 SymbolTableIndex uint32 Type uint16 } func readRelocs(sh *SectionHeader, r io.ReadSeeker) ([]Reloc, error) { if sh.NumberOfRelocations <= 0 { return nil, nil } _, err := r.Seek(int64(sh.PointerToRelocations), seekStart) if err != nil { return nil, fmt.Errorf("fail to seek to %q section relocations: %v", sh.Name, err) } relocs := make([]Reloc, sh.NumberOfRelocations) err = binary.Read(r, binary.LittleEndian, relocs) if err != nil { return nil, fmt.Errorf("fail to read section relocations: %v", err) } return relocs, nil } // SectionHeader is similar to SectionHeader32 with Name // field replaced by Go string. type SectionHeader struct { Name string VirtualSize uint32 VirtualAddress uint32 Size uint32 Offset uint32 PointerToRelocations uint32 PointerToLineNumbers uint32 NumberOfRelocations uint16 NumberOfLineNumbers uint16 Characteristics uint32 } // Section provides access to PE COFF section. type Section struct { SectionHeader Relocs []Reloc // Embed ReaderAt for ReadAt method. // Do not embed SectionReader directly // to avoid having Read and Seek. // If a client wants Read and Seek it must use // Open() to avoid fighting over the seek offset // with other clients. io.ReaderAt sr *io.SectionReader } // Data reads and returns the contents of the PE section s. func (s *Section) Data() ([]byte, error) { dat := make([]byte, s.sr.Size()) n, err := s.sr.ReadAt(dat, 0) if n == len(dat) { err = nil } return dat[0:n], err } // Open returns a new ReadSeeker reading the PE section s. func (s *Section) Open() io.ReadSeeker { return io.NewSectionReader(s.sr, 0, 1<<63-1) } ./github.com/canonical/go-efilib/internal/pe1.14/string.go0000664000000000000000000000343300000000000021553 0ustar0000000000000000// Copyright 2016 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package pe import ( "bytes" "encoding/binary" "fmt" "io" ) // cstring converts ASCII byte sequence b to string. // It stops once it finds 0 or reaches end of b. func cstring(b []byte) string { i := bytes.IndexByte(b, 0) if i == -1 { i = len(b) } return string(b[:i]) } // StringTable is a COFF string table. type StringTable []byte func readStringTable(fh *FileHeader, r io.ReadSeeker) (StringTable, error) { // COFF string table is located right after COFF symbol table. if fh.PointerToSymbolTable <= 0 { return nil, nil } offset := fh.PointerToSymbolTable + COFFSymbolSize*fh.NumberOfSymbols _, err := r.Seek(int64(offset), seekStart) if err != nil { return nil, fmt.Errorf("fail to seek to string table: %v", err) } var l uint32 err = binary.Read(r, binary.LittleEndian, &l) if err != nil { return nil, fmt.Errorf("fail to read string table length: %v", err) } // string table length includes itself if l <= 4 { return nil, nil } l -= 4 buf := make([]byte, l) _, err = io.ReadFull(r, buf) if err != nil { return nil, fmt.Errorf("fail to read string table: %v", err) } return StringTable(buf), nil } // TODO(brainman): decide if start parameter should be int instead of uint32 // String extracts string from COFF string table st at offset start. func (st StringTable) String(start uint32) (string, error) { // start includes 4 bytes of string table length if start < 4 { return "", fmt.Errorf("offset %d is before the start of string table", start) } start -= 4 if int(start) > len(st) { return "", fmt.Errorf("offset %d is beyond the end of string table", start) } return cstring(st[start:]), nil } ./github.com/canonical/go-efilib/internal/pe1.14/symbol.go0000664000000000000000000000465600000000000021562 0ustar0000000000000000// Copyright 2016 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package pe import ( "encoding/binary" "fmt" "io" ) const COFFSymbolSize = 18 // COFFSymbol represents single COFF symbol table record. type COFFSymbol struct { Name [8]uint8 Value uint32 SectionNumber int16 Type uint16 StorageClass uint8 NumberOfAuxSymbols uint8 } func readCOFFSymbols(fh *FileHeader, r io.ReadSeeker) ([]COFFSymbol, error) { if fh.PointerToSymbolTable == 0 { return nil, nil } if fh.NumberOfSymbols <= 0 { return nil, nil } _, err := r.Seek(int64(fh.PointerToSymbolTable), seekStart) if err != nil { return nil, fmt.Errorf("fail to seek to symbol table: %v", err) } syms := make([]COFFSymbol, fh.NumberOfSymbols) err = binary.Read(r, binary.LittleEndian, syms) if err != nil { return nil, fmt.Errorf("fail to read symbol table: %v", err) } return syms, nil } // isSymNameOffset checks symbol name if it is encoded as offset into string table. func isSymNameOffset(name [8]byte) (bool, uint32) { if name[0] == 0 && name[1] == 0 && name[2] == 0 && name[3] == 0 { return true, binary.LittleEndian.Uint32(name[4:]) } return false, 0 } // FullName finds real name of symbol sym. Normally name is stored // in sym.Name, but if it is longer then 8 characters, it is stored // in COFF string table st instead. func (sym *COFFSymbol) FullName(st StringTable) (string, error) { if ok, offset := isSymNameOffset(sym.Name); ok { return st.String(offset) } return cstring(sym.Name[:]), nil } func removeAuxSymbols(allsyms []COFFSymbol, st StringTable) ([]*Symbol, error) { if len(allsyms) == 0 { return nil, nil } syms := make([]*Symbol, 0) aux := uint8(0) for _, sym := range allsyms { if aux > 0 { aux-- continue } name, err := sym.FullName(st) if err != nil { return nil, err } aux = sym.NumberOfAuxSymbols s := &Symbol{ Name: name, Value: sym.Value, SectionNumber: sym.SectionNumber, Type: sym.Type, StorageClass: sym.StorageClass, } syms = append(syms, s) } return syms, nil } // Symbol is similar to COFFSymbol with Name field replaced // by Go string. Symbol also does not have NumberOfAuxSymbols. type Symbol struct { Name string Value uint32 SectionNumber int16 Type uint16 StorageClass uint8 } ./github.com/canonical/go-efilib/internal/uefi/0000775000000000000000000000000000000000000017733 5ustar0000000000000000./github.com/canonical/go-efilib/internal/uefi/authvars.go0000664000000000000000000000513500000000000022123 0ustar0000000000000000// Copyright 2021 Canonical Ltd. // Licensed under the LGPLv3 with static-linking exception. // See LICENCE file for details. package uefi import ( "encoding/binary" "io" "github.com/canonical/go-efilib/internal/ioerr" ) const ( EFI_VARIABLE_AUTHENTICATION_3_CERT_ID_SHA256 = 1 EFI_VARIABLE_AUTHENTICATION_3_TIMESTAMP_TYPE = 1 EFI_VARIABLE_AUTHENTICATION_3_NONCE_TYPE = 2 ) type EFI_VARIABLE_AUTHENTICATION struct { MonotonicCount uint64 AuthInfo WIN_CERTIFICATE_UEFI_GUID } func Read_EFI_VARIABLE_AUTHENTICATION(r io.Reader) (out *EFI_VARIABLE_AUTHENTICATION, err error) { out = &EFI_VARIABLE_AUTHENTICATION{} if err := binary.Read(r, binary.LittleEndian, &out.MonotonicCount); err != nil { return nil, err } cert, err := Read_WIN_CERTIFICATE_UEFI_GUID(r) if err != nil { return nil, ioerr.EOFIsUnexpected(err) } out.AuthInfo = *cert return out, nil } type EFI_VARIABLE_AUTHENTICATION_2 struct { TimeStamp EFI_TIME AuthInfo WIN_CERTIFICATE_UEFI_GUID } func Read_EFI_VARIABLE_AUTHENTICATION_2(r io.Reader) (out *EFI_VARIABLE_AUTHENTICATION_2, err error) { out = &EFI_VARIABLE_AUTHENTICATION_2{} if err := binary.Read(r, binary.LittleEndian, &out.TimeStamp); err != nil { return nil, err } cert, err := Read_WIN_CERTIFICATE_UEFI_GUID(r) if err != nil { return nil, ioerr.EOFIsUnexpected(err) } out.AuthInfo = *cert return out, nil } type EFI_VARIABLE_AUTHENTICATION_3 struct { Version uint8 Type uint8 MetadataSize uint32 Flags uint32 } type EFI_VARIABLE_AUTHENTICATION_3_CERT_ID struct { Type uint8 IdSize uint32 Id []byte } func Read_EFI_VARIABLE_AUTHENTICATION_3_CERT_ID(r io.Reader) (out *EFI_VARIABLE_AUTHENTICATION_3_CERT_ID, err error) { out = &EFI_VARIABLE_AUTHENTICATION_3_CERT_ID{} if err := binary.Read(r, binary.LittleEndian, &out.Type); err != nil { return nil, err } if err := binary.Read(r, binary.LittleEndian, &out.IdSize); err != nil { return nil, ioerr.EOFIsUnexpected(err) } out.Id = make([]byte, out.IdSize) if _, err := io.ReadFull(r, out.Id); err != nil { return nil, ioerr.EOFIsUnexpected(err) } return out, nil } type EFI_VARIABLE_AUTHENTICATION_3_NONCE struct { NonceSize uint32 Nonce []byte } func Read_EFI_VARIABLE_AUTHENTICATION_3_NONCE(r io.Reader) (out *EFI_VARIABLE_AUTHENTICATION_3_NONCE, err error) { out = &EFI_VARIABLE_AUTHENTICATION_3_NONCE{} if err := binary.Read(r, binary.LittleEndian, &out.NonceSize); err != nil { return nil, err } out.Nonce = make([]byte, out.NonceSize) if _, err := io.ReadFull(r, out.Nonce); err != nil { return nil, ioerr.EOFIsUnexpected(err) } return out, nil } ./github.com/canonical/go-efilib/internal/uefi/base.go0000664000000000000000000000116100000000000021173 0ustar0000000000000000// Copyright 2021 Canonical Ltd. // Licensed under the LGPLv3 with static-linking exception. // See LICENCE file for details. package uefi import ( "encoding/binary" ) type EFI_GUID [16]byte func New_EFI_GUID(a uint32, b, c, d uint16, e [6]uint8) (out EFI_GUID) { binary.LittleEndian.PutUint32(out[0:4], a) binary.LittleEndian.PutUint16(out[4:6], b) binary.LittleEndian.PutUint16(out[6:8], c) binary.BigEndian.PutUint16(out[8:10], d) copy(out[10:], e[:]) return } type EFI_LBA uint64 type EFI_TABLE_HEADER struct { Signature uint64 Revision uint32 HeaderSize uint32 CRC uint32 Reserved uint32 } ./github.com/canonical/go-efilib/internal/uefi/db.go0000664000000000000000000000637100000000000020656 0ustar0000000000000000// Copyright 2021 Canonical Ltd. // Licensed under the LGPLv3 with static-linking exception. // See LICENCE file for details. package uefi import ( "encoding/binary" "errors" "io" "math" "github.com/canonical/go-efilib/internal/ioerr" ) const ESLHeaderSize = 28 type EFI_SIGNATURE_DATA struct { SignatureOwner EFI_GUID SignatureData []byte } func (d *EFI_SIGNATURE_DATA) Write(w io.Writer) error { if _, err := w.Write(d.SignatureOwner[:]); err != nil { return err } if _, err := w.Write(d.SignatureData); err != nil { return err } return nil } type EFI_SIGNATURE_LIST struct { SignatureType EFI_GUID SignatureListSize uint32 SignatureHeaderSize uint32 SignatureSize uint32 SignatureHeader []byte Signatures []EFI_SIGNATURE_DATA } func (l *EFI_SIGNATURE_LIST) Write(w io.Writer) error { if _, err := w.Write(l.SignatureType[:]); err != nil { return err } if err := binary.Write(w, binary.LittleEndian, l.SignatureListSize); err != nil { return err } if err := binary.Write(w, binary.LittleEndian, l.SignatureHeaderSize); err != nil { return err } if err := binary.Write(w, binary.LittleEndian, l.SignatureSize); err != nil { return err } if _, err := w.Write(l.SignatureHeader); err != nil { return err } for _, s := range l.Signatures { if err := s.Write(w); err != nil { return err } } return nil } func Read_EFI_SIGNATURE_LIST(r io.Reader) (out *EFI_SIGNATURE_LIST, err error) { out = &EFI_SIGNATURE_LIST{} if err := binary.Read(r, binary.LittleEndian, &out.SignatureType); err != nil { return nil, err } if err := binary.Read(r, binary.LittleEndian, &out.SignatureListSize); err != nil { return nil, ioerr.EOFIsUnexpected(err) } if err := binary.Read(r, binary.LittleEndian, &out.SignatureHeaderSize); err != nil { return nil, ioerr.EOFIsUnexpected(err) } if err := binary.Read(r, binary.LittleEndian, &out.SignatureSize); err != nil { return nil, ioerr.EOFIsUnexpected(err) } if out.SignatureHeaderSize > math.MaxUint32-ESLHeaderSize { return nil, errors.New("signature header size too large") } if out.SignatureHeaderSize+ESLHeaderSize > out.SignatureListSize { return nil, errors.New("inconsistent size fields: total signatures payload size underflows") } signaturesSize := out.SignatureListSize - out.SignatureHeaderSize - ESLHeaderSize if out.SignatureSize < uint32(binary.Size(EFI_GUID{})) { return nil, errors.New("invalid SignatureSize") } if signaturesSize%out.SignatureSize != 0 { return nil, errors.New("inconsistent size fields: total signatures payload size not a multiple of the individual signature size") } numOfSignatures := int(signaturesSize / out.SignatureSize) out.SignatureHeader = make([]byte, out.SignatureHeaderSize) if _, err := io.ReadFull(r, out.SignatureHeader); err != nil { return nil, ioerr.EOFIsUnexpected(err) } for i := 0; i < numOfSignatures; i++ { var s EFI_SIGNATURE_DATA if _, err := io.ReadFull(r, s.SignatureOwner[:]); err != nil { return nil, ioerr.EOFIsUnexpected(err) } s.SignatureData = make([]byte, int(out.SignatureSize)-binary.Size(s.SignatureOwner)) if _, err := io.ReadFull(r, s.SignatureData); err != nil { return nil, ioerr.EOFIsUnexpected(err) } out.Signatures = append(out.Signatures, s) } return out, nil } ./github.com/canonical/go-efilib/internal/uefi/devicepath.go0000664000000000000000000001330300000000000022376 0ustar0000000000000000// Copyright 2021 Canonical Ltd. // Licensed under the LGPLv3 with static-linking exception. // See LICENCE file for details. package uefi import ( "encoding/binary" "io" "github.com/canonical/go-efilib/internal/ioerr" ) const ( NO_DISK_SIGNATURE = 0x00 SIGNATURE_TYPE_MBR = 0x01 SIGNATURE_TYPE_GUID = 0x02 HARDWARE_DEVICE_PATH = 0x01 ACPI_DEVICE_PATH = 0x02 MESSAGING_DEVICE_PATH = 0x03 MEDIA_DEVICE_PATH = 0x04 BBS_DEVICE_PATH = 0x05 END_DEVICE_PATH_TYPE = 0x7f HW_PCI_DP = 0x01 HW_VENDOR_DP = 0x04 ACPI_DP = 0x01 ACPI_EXTENDED_DP = 0x02 MSG_ATAPI_DP = 0x01 MSG_SCSI_DP = 0x02 MSG_USB_DP = 0x05 MSG_USB_CLASS_DP = 0x0f MSG_VENDOR_DP = 0x0a MSG_USB_WWID_DP = 0x10 MSG_DEVICE_LOGICAL_UNIT_DP = 0x11 MSG_SATA_DP = 0x12 MSG_NVME_NAMESPACE_DP = 0x17 MEDIA_HARDDRIVE_DP = 0x01 MEDIA_CDROM_DP = 0x02 MEDIA_VENDOR_DP = 0x03 MEDIA_FILEPATH_DP = 0x04 MEDIA_PIWG_FW_FILE_DP = 0x06 MEDIA_PIWG_FW_VOL_DP = 0x07 MEDIA_RELATIVE_OFFSET_RANGE_DP = 0x08 END_ENTIRE_DEVICE_PATH_SUBTYPE = 0xff ) type EFI_DEVICE_PATH_PROTOCOL struct { Type uint8 SubType uint8 Length uint16 } type PCI_DEVICE_PATH struct { Header EFI_DEVICE_PATH_PROTOCOL Function uint8 Device uint8 } type VENDOR_DEVICE_PATH struct { Header EFI_DEVICE_PATH_PROTOCOL Guid EFI_GUID } type ACPI_HID_DEVICE_PATH struct { Header EFI_DEVICE_PATH_PROTOCOL HID uint32 UID uint32 } type ACPI_EXTENDED_HID_DEVICE_PATH struct { Header EFI_DEVICE_PATH_PROTOCOL HID uint32 UID uint32 CID uint32 } type ATAPI_DEVICE_PATH struct { Header EFI_DEVICE_PATH_PROTOCOL PrimarySecondary uint8 SlaveMaster uint8 Lun uint16 } type SCSI_DEVICE_PATH struct { Header EFI_DEVICE_PATH_PROTOCOL Pun uint16 Lun uint16 } type USB_DEVICE_PATH struct { Header EFI_DEVICE_PATH_PROTOCOL ParentPortNumber uint8 InterfaceNumber uint8 } type USB_CLASS_DEVICE_PATH struct { Header EFI_DEVICE_PATH_PROTOCOL VendorId uint16 ProductId uint16 DeviceClass uint8 DeviceSubClass uint8 DeviceProtocol uint8 } type USB_WWID_DEVICE_PATH struct { Header EFI_DEVICE_PATH_PROTOCOL InterfaceNumber uint16 VendorId uint16 ProductId uint16 SerialNumber []uint16 } func (p *USB_WWID_DEVICE_PATH) Write(w io.Writer) error { if err := binary.Write(w, binary.LittleEndian, &p.Header); err != nil { return err } if err := binary.Write(w, binary.LittleEndian, p.InterfaceNumber); err != nil { return err } if err := binary.Write(w, binary.LittleEndian, p.VendorId); err != nil { return err } if err := binary.Write(w, binary.LittleEndian, p.ProductId); err != nil { return err } if err := binary.Write(w, binary.LittleEndian, p.SerialNumber); err != nil { return err } return nil } func Read_USB_WWID_DEVICE_PATH(r io.Reader) (out *USB_WWID_DEVICE_PATH, err error) { out = &USB_WWID_DEVICE_PATH{} if err := binary.Read(r, binary.LittleEndian, &out.Header); err != nil { return nil, err } if err := binary.Read(r, binary.LittleEndian, &out.InterfaceNumber); err != nil { return nil, ioerr.EOFIsUnexpected(err) } if err := binary.Read(r, binary.LittleEndian, &out.VendorId); err != nil { return nil, ioerr.EOFIsUnexpected(err) } if err := binary.Read(r, binary.LittleEndian, &out.ProductId); err != nil { return nil, ioerr.EOFIsUnexpected(err) } out.SerialNumber = make([]uint16, (int(out.Header.Length)-binary.Size(out.Header)-binary.Size(out.InterfaceNumber)-binary.Size(out.VendorId)-binary.Size(out.ProductId))/2) if err := binary.Read(r, binary.LittleEndian, out.SerialNumber); err != nil { return nil, ioerr.EOFIsUnexpected(err) } return out, nil } type DEVICE_LOGICAL_UNIT_DEVICE_PATH struct { Header EFI_DEVICE_PATH_PROTOCOL Lun uint8 } type SATA_DEVICE_PATH struct { Header EFI_DEVICE_PATH_PROTOCOL HBAPortNumber uint16 PortMultiplierPortNumber uint16 Lun uint16 } type NVME_NAMESPACE_DEVICE_PATH struct { Header EFI_DEVICE_PATH_PROTOCOL NamespaceId uint32 NamespaceUuid uint64 } type HARDDRIVE_DEVICE_PATH struct { Header EFI_DEVICE_PATH_PROTOCOL PartitionNumber uint32 PartitionStart uint64 PartitionSize uint64 Signature [16]uint8 MBRType uint8 SignatureType uint8 } type CDROM_DEVICE_PATH struct { Header EFI_DEVICE_PATH_PROTOCOL BootEntry uint32 PartitionStart uint64 PartitionSize uint64 } type FILEPATH_DEVICE_PATH struct { Header EFI_DEVICE_PATH_PROTOCOL PathName []uint16 } func (p *FILEPATH_DEVICE_PATH) Write(w io.Writer) error { if err := binary.Write(w, binary.LittleEndian, &p.Header); err != nil { return err } if err := binary.Write(w, binary.LittleEndian, p.PathName); err != nil { return err } return nil } func Read_FILEPATH_DEVICE_PATH(r io.Reader) (out *FILEPATH_DEVICE_PATH, err error) { out = &FILEPATH_DEVICE_PATH{} if err := binary.Read(r, binary.LittleEndian, &out.Header); err != nil { return nil, err } out.PathName = make([]uint16, (int(out.Header.Length)-binary.Size(out.Header))/2) if err := binary.Read(r, binary.LittleEndian, &out.PathName); err != nil { return nil, ioerr.EOFIsUnexpected(err) } return out, nil } type MEDIA_FW_VOL_FILEPATH_DEVICE_PATH struct { Header EFI_DEVICE_PATH_PROTOCOL FvFileName EFI_GUID } type MEDIA_FW_VOL_DEVICE_PATH struct { Header EFI_DEVICE_PATH_PROTOCOL FvName EFI_GUID } type MEDIA_RELATIVE_OFFSET_RANGE_DEVICE_PATH struct { Header EFI_DEVICE_PATH_PROTOCOL Reserved uint32 StartingOffset uint64 EndingOffset uint64 } ./github.com/canonical/go-efilib/internal/uefi/gpt.go0000664000000000000000000000333100000000000021054 0ustar0000000000000000// Copyright 2021 Canonical Ltd. // Licensed under the LGPLv3 with static-linking exception. // See LICENCE file for details. package uefi import ( "bytes" "encoding/binary" "errors" "hash/crc32" "io" "github.com/canonical/go-efilib/internal/ioerr" ) const EFI_PTAB_HEADER_ID uint64 = 0x5452415020494645 type EFI_PARTITION_ENTRY struct { PartitionTypeGUID EFI_GUID UniquePartitionGUID EFI_GUID StartingLBA EFI_LBA EndingLBA EFI_LBA Attributes uint64 PartitionName [36]uint16 } type EFI_PARTITION_TABLE_HEADER struct { Hdr EFI_TABLE_HEADER MyLBA EFI_LBA AlternateLBA EFI_LBA FirstUsableLBA EFI_LBA LastUsableLBA EFI_LBA DiskGUID EFI_GUID PartitionEntryLBA EFI_LBA NumberOfPartitionEntries uint32 SizeOfPartitionEntry uint32 PartitionEntryArrayCRC32 uint32 } func Read_EFI_PARTITION_TABLE_HEADER(r io.Reader) (out *EFI_PARTITION_TABLE_HEADER, crc uint32, err error) { var hdr EFI_TABLE_HEADER if err := binary.Read(r, binary.LittleEndian, &hdr); err != nil { return nil, 0, err } if hdr.HeaderSize < uint32(binary.Size(hdr)) { return nil, 0, errors.New("invalid header size") } origCrc := hdr.CRC hdr.CRC = 0 b := new(bytes.Buffer) if err := binary.Write(b, binary.LittleEndian, &hdr); err != nil { return nil, 0, err } if _, err := io.CopyN(b, r, int64(hdr.HeaderSize-uint32(binary.Size(hdr)))); err != nil { return nil, 0, ioerr.EOFIsUnexpected(err) } crc = crc32.ChecksumIEEE(b.Bytes()) out = &EFI_PARTITION_TABLE_HEADER{} if err := binary.Read(b, binary.LittleEndian, out); err != nil { return nil, 0, err } out.Hdr.CRC = origCrc return out, crc, nil } ./github.com/canonical/go-efilib/internal/uefi/ids.go0000664000000000000000000000542600000000000021050 0ustar0000000000000000// Copyright 2021 Canonical Ltd. // Licensed under the LGPLv3 with static-linking exception. // See LICENCE file for details. package uefi var ( EFI_HASH_ALGORITHM_SHA1_GUID = New_EFI_GUID(0x2ae9d80f, 0x3fb2, 0x4095, 0xb7b1, [...]uint8{0xe9, 0x31, 0x57, 0xb9, 0x46, 0xb6}) EFI_HASH_ALGORITHM_SHA256_GUID = New_EFI_GUID(0x51aa59de, 0xfdf2, 0x4ea3, 0xbc63, [...]uint8{0x87, 0x5f, 0xb7, 0x84, 0x2e, 0xe9}) EFI_HASH_ALGORITHM_SHA224_GUID = New_EFI_GUID(0x8df01a06, 0x9bd5, 0x4bf7, 0xb021, [...]uint8{0xdb, 0x4f, 0xd9, 0xcc, 0xf4, 0x5b}) EFI_HASH_ALGORITHM_SHA384_GUID = New_EFI_GUID(0xefa96432, 0xde33, 0x4dd2, 0xaee6, [...]uint8{0x32, 0x8c, 0x33, 0xdf, 0x77, 0x7a}) EFI_HASH_ALGORITHM_SHA512_GUID = New_EFI_GUID(0xcaa4381e, 0x750c, 0x4770, 0xb870, [...]uint8{0x7a, 0x23, 0xb4, 0xe4, 0x21, 0x30}) EFI_CERT_TYPE_RSA2048_SHA256_GUID = New_EFI_GUID(0xa7717414, 0xc616, 0x4977, 0x9420, [...]uint8{0x84, 0x47, 0x12, 0xa7, 0x35, 0xbf}) EFI_CERT_TYPE_PKCS7_GUID = New_EFI_GUID(0x4aafd29d, 0x68df, 0x49ee, 0x8aa9, [...]uint8{0x34, 0x7d, 0x37, 0x56, 0x65, 0xa7}) EFI_CERT_SHA1_GUID = New_EFI_GUID(0x826ca512, 0xcf10, 0x4ac9, 0xb187, [...]uint8{0xbe, 0x01, 0x49, 0x66, 0x31, 0xbd}) EFI_CERT_SHA256_GUID = New_EFI_GUID(0xc1c41626, 0x504c, 0x4092, 0xaca9, [...]uint8{0x41, 0xf9, 0x36, 0x93, 0x43, 0x28}) EFI_CERT_SHA224_GUID = New_EFI_GUID(0xb6e5233, 0xa65c, 0x44c9, 0x9407, [...]uint8{0xd9, 0xab, 0x83, 0xbf, 0xc8, 0xbd}) EFI_CERT_SHA384_GUID = New_EFI_GUID(0xff3e5307, 0x9fd0, 0x48c9, 0x85f1, [...]uint8{0x8a, 0xd5, 0x6c, 0x70, 0x1e, 0x01}) EFI_CERT_SHA512_GUID = New_EFI_GUID(0x093e0fae, 0xa6c4, 0x4f50, 0x9f1b, [...]uint8{0xd4, 0x1e, 0x2b, 0x89, 0xc1, 0x9a}) EFI_CERT_RSA2048_GUID = New_EFI_GUID(0x3c5766e8, 0x269c, 0x4e34, 0xaa14, [...]uint8{0xed, 0x77, 0x6e, 0x85, 0xb3, 0xb6}) EFI_CERT_RSA2048_SHA1_GUID = New_EFI_GUID(0x67f8444f, 0x8743, 0x48f1, 0xa328, [...]uint8{0x1e, 0xaa, 0xb8, 0x73, 0x60, 0x80}) EFI_CERT_RSA2048_SHA256_GUID = New_EFI_GUID(0xe2b36190, 0x879b, 0x4a3d, 0xad8d, [...]uint8{0xf2, 0xe7, 0xbb, 0xa3, 0x27, 0x84}) EFI_CERT_X509_GUID = New_EFI_GUID(0xa5c059a1, 0x94e4, 0x4aa7, 0x87b5, [...]uint8{0xab, 0x15, 0x5c, 0x2b, 0xf0, 0x72}) EFI_CERT_X509_SHA256_GUID = New_EFI_GUID(0x3bd2a492, 0x96c0, 0x4079, 0xb420, [...]uint8{0xfc, 0xf9, 0x8e, 0xf1, 0x03, 0xed}) EFI_CERT_X509_SHA384_GUID = New_EFI_GUID(0x7076876e, 0x80c2, 0x4ee6, 0xaad2, [...]uint8{0x28, 0xb3, 0x49, 0xa6, 0x86, 0x5b}) EFI_CERT_X509_SHA512_GUID = New_EFI_GUID(0x446dbf63, 0x2502, 0x4cda, 0xbcfa, [...]uint8{0x24, 0x65, 0xd2, 0xb0, 0xfe, 0x9d}) EFI_GLOBAL_VARIABLE = New_EFI_GUID(0x8be4df61, 0x93ca, 0x11d2, 0xaa0d, [...]uint8{0x00, 0xe0, 0x98, 0x03, 0x2b, 0x8c}) EFI_IMAGE_SECURITY_DATABASE_GUID = New_EFI_GUID(0xd719b2cb, 0x3d3a, 0x4596, 0xa3bc, [...]uint8{0xda, 0xd0, 0x0e, 0x67, 0x65, 0x6f}) ) ./github.com/canonical/go-efilib/internal/uefi/loadoption.go0000664000000000000000000000373500000000000022442 0ustar0000000000000000// Copyright 2021 Canonical Ltd. // Licensed under the LGPLv3 with static-linking exception. // See LICENCE file for details. package uefi import ( "encoding/binary" "io" "io/ioutil" "github.com/canonical/go-efilib/internal/ioerr" ) const ( LOAD_OPTION_ACTIVE = 0x00000001 LOAD_OPTION_FORCE_RECONNECT = 0x00000002 LOAD_OPTION_HIDDEN = 0x00000008 LOAD_OPTION_CATEGORY = 0x00001f00 LOAD_OPTION_CATEGORY_BOOT = 0x00000000 LOAD_OPTION_CATEGORY_APP = 0x00000100 ) type EFI_LOAD_OPTION struct { Attributes uint32 FilePathListLength uint16 Description []uint16 FilePathList []byte OptionalData []byte } func (o *EFI_LOAD_OPTION) Write(w io.Writer) error { if err := binary.Write(w, binary.LittleEndian, o.Attributes); err != nil { return err } if err := binary.Write(w, binary.LittleEndian, o.FilePathListLength); err != nil { return err } if err := binary.Write(w, binary.LittleEndian, o.Description); err != nil { return err } if _, err := w.Write(o.FilePathList); err != nil { return err } if _, err := w.Write(o.OptionalData); err != nil { return err } return nil } func Read_EFI_LOAD_OPTION(r io.Reader) (out *EFI_LOAD_OPTION, err error) { out = &EFI_LOAD_OPTION{} if err := binary.Read(r, binary.LittleEndian, &out.Attributes); err != nil { return nil, err } if err := binary.Read(r, binary.LittleEndian, &out.FilePathListLength); err != nil { return nil, ioerr.EOFIsUnexpected(err) } for i := 0; ; i++ { var c uint16 if err := binary.Read(r, binary.LittleEndian, &c); err != nil { return nil, ioerr.EOFIsUnexpected(err) } out.Description = append(out.Description, c) if c == 0 { break } } out.FilePathList = make([]byte, out.FilePathListLength) if _, err := io.ReadFull(r, out.FilePathList); err != nil { return nil, ioerr.EOFIsUnexpected(err) } optionalData, err := ioutil.ReadAll(r) if err != nil { return nil, err } out.OptionalData = optionalData return out, nil } ./github.com/canonical/go-efilib/internal/uefi/time.go0000664000000000000000000000166400000000000021227 0ustar0000000000000000// Copyright 2021 Canonical Ltd. // Licensed under the LGPLv3 with static-linking exception. // See LICENCE file for details. package uefi import ( "time" ) type EFI_TIME struct { Year uint16 Month uint8 Day uint8 Hour uint8 Minute uint8 Second uint8 Pad1 uint8 Nanosecond uint32 Timezone int16 Daylight uint8 Pad2 uint8 } func (t *EFI_TIME) GoTime() time.Time { return time.Date(int(t.Year), time.Month(t.Month), int(t.Day), int(t.Hour), int(t.Minute), int(t.Second), int(t.Nanosecond), time.FixedZone("", -int(t.Timezone)*60)) } func New_EFI_TIME(t time.Time) *EFI_TIME { _, offset := t.Zone() return &EFI_TIME{ Year: uint16(t.Year()), Month: uint8(t.Month()), Day: uint8(t.Day()), Hour: uint8(t.Hour()), Minute: uint8(t.Minute()), Second: uint8(t.Second()), Nanosecond: uint32(t.Nanosecond()), Timezone: -int16(offset / 60)} } ./github.com/canonical/go-efilib/internal/uefi/vars.go0000664000000000000000000000117700000000000021243 0ustar0000000000000000// Copyright 2021 Canonical Ltd. // Licensed under the LGPLv3 with static-linking exception. // See LICENCE file for details. package uefi const ( EFI_VARIABLE_NON_VOLATILE = 1 << 0 EFI_VARIABLE_BOOTSERVICE_ACCESS = 1 << 1 EFI_VARIABLE_RUNTIME_ACCESS = 1 << 2 EFI_VARIABLE_HARDWARE_ERROR_RECORD = 1 << 3 EFI_VARIABLE_AUTHENTICATED_WRITE_ACCESS = 1 << 4 EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS = 1 << 5 EFI_VARIABLE_APPEND_WRITE = 1 << 6 EFI_VARIABLE_ENHANCED_AUTHENTICATED_ACCESS = 1 << 7 ) ./github.com/canonical/go-efilib/internal/uefi/wincert.go0000664000000000000000000000300700000000000021735 0ustar0000000000000000// Copyright 2021 Canonical Ltd. // Licensed under the LGPLv3 with static-linking exception. // See LICENCE file for details. package uefi import ( "encoding/binary" "errors" "io" "github.com/canonical/go-efilib/internal/ioerr" ) const ( WIN_CERT_TYPE_PKCS_SIGNED_DATA = 0x0002 WIN_CERT_TYPE_EFI_PKCS115 = 0x0ef0 WIN_CERT_TYPE_EFI_GUID = 0x0ef1 ) type WIN_CERTIFICATE struct { Length uint32 Revision uint16 CertificateType uint16 } type WIN_CERTIFICATE_EFI_PKCS1_15 struct { Hdr WIN_CERTIFICATE HashAlgorithm EFI_GUID Signature []byte } type WIN_CERTIFICATE_UEFI_GUID struct { Hdr WIN_CERTIFICATE CertType EFI_GUID CertData []byte } func Read_WIN_CERTIFICATE_UEFI_GUID(r io.Reader) (out *WIN_CERTIFICATE_UEFI_GUID, err error) { out = &WIN_CERTIFICATE_UEFI_GUID{} if err := binary.Read(r, binary.LittleEndian, &out.Hdr); err != nil { return nil, err } if out.Hdr.Revision != 0x0200 { return nil, errors.New("unexpected Hdr.Revision") } if out.Hdr.CertificateType != WIN_CERT_TYPE_EFI_GUID { return nil, errors.New("unexpected Hdr.CertificateType") } if _, err := io.ReadFull(r, out.CertType[:]); err != nil { return nil, ioerr.EOFIsUnexpected(err) } out.CertData = make([]byte, int(out.Hdr.Length)-binary.Size(out.Hdr)-binary.Size(out.CertType)) if _, err := io.ReadFull(r, out.CertData); err != nil { return nil, ioerr.EOFIsUnexpected(err) } return out, nil } type WIN_CERTIFICATE_EFI_PKCS struct { Hdr WIN_CERTIFICATE CertData []byte } ./github.com/canonical/go-efilib/internal/unix/0000775000000000000000000000000000000000000017766 5ustar0000000000000000./github.com/canonical/go-efilib/internal/unix/ioctl.go0000664000000000000000000000154200000000000021431 0ustar0000000000000000// Copyright 2021 Canonical Ltd. // Licensed under the LGPLv3 with static-linking exception. // See LICENCE file for details. package unix import ( "syscall" "unsafe" ) func IoctlGetUint(fd int, req uint) (uint, error) { var value uint _, _, err := syscall.Syscall(syscall.SYS_IOCTL, uintptr(fd), uintptr(req), uintptr(unsafe.Pointer(&value))) if err != 0 { return 0, err } return value, nil } func IoctlGetUint64(fd int, req uint) (uint64, error) { var value uint64 _, _, err := syscall.Syscall(syscall.SYS_IOCTL, uintptr(fd), uintptr(req), uintptr(unsafe.Pointer(&value))) if err != 0 { return 0, err } return value, nil } func IoctlSetPointerUint(fd int, req, value uint) error { v := value _, _, err := syscall.Syscall(syscall.SYS_IOCTL, uintptr(fd), uintptr(req), uintptr(unsafe.Pointer(&v))) if err != 0 { return err } return nil } ./github.com/canonical/go-efilib/linux/0000775000000000000000000000000000000000000016326 5ustar0000000000000000./github.com/canonical/go-efilib/linux/disk.go0000664000000000000000000000404000000000000017605 0ustar0000000000000000// Copyright 2021 Canonical Ltd. // Licensed under the LGPLv3 with static-linking exception. // See LICENCE file for details. package linux import ( "errors" "math" "os" "syscall" "golang.org/x/sys/unix" "golang.org/x/xerrors" "github.com/canonical/go-efilib" internal_unix "github.com/canonical/go-efilib/internal/unix" ) func getSectorSize(f *os.File) (int64, error) { fi, err := f.Stat() if err != nil { return 0, err } if fi.Mode().IsRegular() { return 512, nil } if fi.Mode()&os.ModeDevice == 0 { return 0, errors.New("not a regular file or device") } sz, err := unix.IoctlGetInt(int(f.Fd()), unix.BLKSSZGET) if err != nil { return 0, err } return int64(sz), nil } func getDeviceSize(f *os.File) (int64, error) { fi, err := f.Stat() if err != nil { return 0, err } if fi.Mode().IsRegular() { return fi.Size(), nil } if fi.Mode()&os.ModeDevice == 0 { return 0, errors.New("not a regular file or device") } sz, err := internal_unix.IoctlGetUint64(int(f.Fd()), unix.BLKGETSIZE64) switch { case err == syscall.ENOTTY: n, err := internal_unix.IoctlGetUint(int(f.Fd()), unix.BLKGETSIZE) if err != nil { return 0, err } if int64(n) > int64(math.MaxInt64>>9) { return 0, errors.New("overflow") } return int64(n << 9), nil case err != nil: return 0, err case sz > math.MaxInt64: return 0, errors.New("overflow") default: return int64(sz), nil } } // NewHardDriveDevicePathNodeFromDevice constructs a HardDriveDevicePathNode for the // specified partition on the device or file at the supplied path. func NewHardDriveDevicePathNodeFromDevice(dev string, part int) (*efi.HardDriveDevicePathNode, error) { f, err := osOpen(dev) if err != nil { return nil, err } defer f.Close() sz, err := getDeviceSize(f) if err != nil { return nil, xerrors.Errorf("cannot determine device size: %w", err) } ssz, err := getSectorSize(f) if err != nil { return nil, xerrors.Errorf("cannot determine logical sector size: %w", err) } return efi.NewHardDriveDevicePathNodeFromDevice(f, sz, ssz, part) } ./github.com/canonical/go-efilib/linux/dp_acpi.go0000664000000000000000000000550600000000000020262 0ustar0000000000000000// Copyright 2021 Canonical Ltd. // Licensed under the LGPLv3 with static-linking exception. // See LICENCE file for details. package linux import ( "encoding/binary" "encoding/hex" "errors" "fmt" "io/ioutil" "os" "path/filepath" "regexp" "strconv" "strings" "github.com/canonical/go-efilib" ) // acpiIdRE matches a ACPI or PNP ID, capturing the vendor and product. var acpiIdRE = regexp.MustCompile(`^([[:upper:][:digit:]]{3,4})([[:xdigit:]]{4})$`) // acpiModaliasRE matches a modalias for an ACPI node, capturing the CID. var acpiModaliasRE = regexp.MustCompile(`^acpi:[[:alnum:]]+:([[:alnum:]]*)`) func maybeUseSimpleACPIDevicePathNode(node *efi.ACPIExtendedDevicePathNode) efi.DevicePathNode { if node.HIDStr != "" || node.UIDStr != "" || node.CIDStr != "" { return node } if node.CID != 0 && node.CID != node.HID { return node } return &efi.ACPIDevicePathNode{HID: node.HID, UID: node.UID} } func decodeACPIOrPNPId(str string) (efi.EISAID, string) { m := acpiIdRE.FindStringSubmatch(str) if len(m) == 0 { return 0, str } vendor := m[1] p, _ := hex.DecodeString(m[2]) product := binary.BigEndian.Uint16(p) if len(vendor) != 3 { return 0, fmt.Sprintf("%s%04x", vendor, product) } id, _ := efi.NewEISAID(vendor, product) return id, "" } func newACPIExtendedDevicePathNode(path string) (*efi.ACPIExtendedDevicePathNode, error) { node := new(efi.ACPIExtendedDevicePathNode) hidBytes, err := ioutil.ReadFile(filepath.Join(path, "hid")) if err != nil { return nil, err } hid, hidStr := decodeACPIOrPNPId(strings.TrimSpace(string(hidBytes))) node.HID = hid node.HIDStr = hidStr modalias, err := ioutil.ReadFile(filepath.Join(path, "modalias")) switch { case os.IsNotExist(err): case err != nil: return nil, err default: m := acpiModaliasRE.FindSubmatch(modalias) if len(m) == 0 { return nil, errors.New("invalid modalias") } if len(m[1]) > 0 { cid, cidStr := decodeACPIOrPNPId(string(m[1])) node.CID = cid node.CIDStr = cidStr } } uidBytes, err := ioutil.ReadFile(filepath.Join(path, "uid")) switch { case os.IsNotExist(err): case err != nil: return nil, err default: uidStr := strings.TrimSpace(string(uidBytes)) uid, err := strconv.ParseUint(uidStr, 10, 32) if err != nil { node.UIDStr = uidStr } else { node.UID = uint32(uid) } } return node, nil } func handleACPIDevicePathNode(builder devicePathBuilder) error { component := builder.next(1) subsystem, err := filepath.EvalSymlinks(filepath.Join(builder.absPath(component), "subsystem")) switch { case os.IsNotExist(err): return errSkipDevicePathNodeHandler case err != nil: return err } if subsystem != filepath.Join(sysfsPath, "bus", "acpi") { return errSkipDevicePathNodeHandler } builder.advance(1) return nil } func init() { registerDevicePathNodeHandler("acpi", handleACPIDevicePathNode, 0) } ./github.com/canonical/go-efilib/linux/dp_ata.go0000664000000000000000000000316000000000000020105 0ustar0000000000000000// Copyright 2021 Canonical Ltd. // Licensed under the LGPLv3 with static-linking exception. // See LICENCE file for details. package linux import ( "errors" "fmt" "io/ioutil" "os" "path/filepath" "regexp" "strconv" "strings" "golang.org/x/xerrors" ) // ataRE matches an ATA path component, capturing the ATA print ID. var ataRE = regexp.MustCompile(`^ata([[:digit:]]+)$`) type ataParams struct { printId uint32 port uint32 *scsiParams } func handleATAPath(path string) (*ataParams, error) { components := strings.Split(path, string(os.PathSeparator)) if len(components) < 6 { return nil, errors.New("invalid path: insufficient components") } ata := components[len(components)-6] m := ataRE.FindStringSubmatch(ata) if len(m) == 0 { return nil, fmt.Errorf("invalid path component: %s", ata) } scsiParams, err := handleSCSIPath(path) if err != nil { return nil, err } printId, err := strconv.ParseUint(m[1], 10, 32) if err != nil { return nil, xerrors.Errorf("invalid print ID: %w", err) } // Obtain the ATA port number local to this ATA controller. The kernel // creates one ata%d device per port (see drivers/ata/libata-core.c:ata_host_register). portBytes, err := ioutil.ReadFile(filepath.Join(path, "../../../../..", "ata_port", ata, "port_no")) if err != nil { return nil, xerrors.Errorf("cannot obtain port ID: %w", err) } port, err := strconv.ParseUint(strings.TrimSpace(string(portBytes)), 10, 16) if err != nil { return nil, xerrors.Errorf("invalid port ID: %w", err) } return &ataParams{ printId: uint32(printId), port: uint32(port), scsiParams: scsiParams}, nil } ./github.com/canonical/go-efilib/linux/dp_hv.go0000664000000000000000000000255000000000000017757 0ustar0000000000000000// Copyright 2022 Canonical Ltd. // Licensed under the LGPLv3 with static-linking exception. // See LICENCE file for details. package linux import ( "io/ioutil" "path/filepath" "github.com/canonical/go-efilib" ) var ( hvVendorGuid = efi.MakeGUID(0x9b17e5a2, 0x0891, 0x42dd, 0xb653, [...]uint8{0x80, 0xb5, 0xc2, 0x28, 0x09, 0xba}) hvSCSIGuid = efi.MakeGUID(0xba6163d9, 0x04a1, 0x4d29, 0xb605, [...]uint8{0x72, 0xe2, 0xff, 0xb1, 0xdc, 0x7f}) ) func handleHVDevicePathNode(builder devicePathBuilder) error { component := builder.next(1) deviceId, err := efi.DecodeGUIDString(component) if err != nil { return err } classIdStr, err := ioutil.ReadFile(filepath.Join(builder.absPath(component), "class_id")) if err != nil { return err } builder.advance(1) classId, err := efi.DecodeGUIDString(string(classIdStr)) if err != nil { return err } switch classId { case hvSCSIGuid: builder.setInterfaceType(interfaceTypeSCSI) default: return errUnsupportedDevice("unhandled device class: " + classId.String()) } data := make([]byte, len(deviceId)+len(classId)) copy(data, classId[:]) copy(data[len(classId):], deviceId[:]) builder.append(&efi.VendorDevicePathNode{ Type: efi.HardwareDevicePath, GUID: hvVendorGuid, Data: data}) return nil } func init() { registerDevicePathNodeHandler("hv", handleHVDevicePathNode, 0, interfaceTypeVMBus) } ./github.com/canonical/go-efilib/linux/dp_ide.go0000664000000000000000000000254700000000000020111 0ustar0000000000000000// Copyright 2021 Canonical Ltd. // Licensed under the LGPLv3 with static-linking exception. // See LICENCE file for details. package linux import ( "errors" "fmt" "math" "github.com/canonical/go-efilib" ) func handleIDEDevicePathNode(builder devicePathBuilder) error { if builder.numRemaining() < 6 { return errors.New("invalid path: insufficient components") } params, err := handleATAPath(builder.absPath(builder.next(6))) if err != nil { return err } builder.advance(6) // PATA has a maximum of 2 ports. if params.port < 1 || params.port > 2 { return fmt.Errorf("invalid port: %d", params.port) } // Each PATA device is represented in the SCSI layer by setting the // target to the drive number, and the LUN as the LUN (see // drivers/ata/libata-scsi.c:ata_scsi_scan_host). // The channel is always 0 for PATA devices (no port multiplier). if params.channel != 0 { return errors.New("invalid SCSI channel") } if params.target > 1 { return errors.New("invalid drive") } if params.lun > math.MaxUint16 { return errors.New("invalid LUN") } builder.append(&efi.ATAPIDevicePathNode{ Controller: efi.ATAPIControllerRole(params.port - 1), Drive: efi.ATAPIDriveRole(params.target), LUN: uint16(params.lun)}) return nil } func init() { registerDevicePathNodeHandler("ide", handleIDEDevicePathNode, 0, interfaceTypeIDE) } ./github.com/canonical/go-efilib/linux/dp_nvme.go0000664000000000000000000000347400000000000020315 0ustar0000000000000000// Copyright 2021 Canonical Ltd. // Licensed under the LGPLv3 with static-linking exception. // See LICENCE file for details. package linux import ( "encoding/binary" "errors" "fmt" "io/ioutil" "os" "path/filepath" "regexp" "strconv" "golang.org/x/xerrors" "github.com/canonical/go-efilib" ) // nvmeNSRe matches "nvme/nvme/nvmen", capturing ns_id var nvmeNSRe = regexp.MustCompile(`^nvme\/nvme[[:digit:]]+\/nvme[[:digit:]]+n([[:digit:]]+)$`) func handleNVMEDevicePathNode(builder devicePathBuilder) error { if builder.numRemaining() < 3 { return errors.New("invalid path: not enough components") } components := builder.next(3) m := nvmeNSRe.FindStringSubmatch(components) if len(m) == 0 { return errors.New("invalid path") } builder.advance(3) nsid, err := strconv.ParseUint(m[1], 10, 32) if err != nil { return xerrors.Errorf("cannot parse nsid: %w", err) } var euid [8]uint8 euidBuf, err := ioutil.ReadFile(filepath.Join(builder.absPath(components), "eui")) if os.IsNotExist(err) { euidBuf, err = ioutil.ReadFile(filepath.Join(builder.absPath(components), "device", "eui")) } switch { case os.IsNotExist(err): // Nothing to do case err != nil: return xerrors.Errorf("cannot determine euid: %w", err) default: n, err := fmt.Sscanf(string(euidBuf), "%02x %02x %02x %02x %02x %02x %02x %02x", &euid[0], &euid[1], &euid[2], &euid[3], &euid[4], &euid[5], &euid[6], &euid[7]) if err != nil { return xerrors.Errorf("cannot parse euid: %w", err) } if n != 8 { return errors.New("invalid euid") } } builder.append(&efi.NVMENamespaceDevicePathNode{ NamespaceID: uint32(nsid), NamespaceUUID: uint64(binary.LittleEndian.Uint64(euid[:]))}) return nil } func init() { registerDevicePathNodeHandler("nvme", handleNVMEDevicePathNode, 0, interfaceTypeNVME) } ./github.com/canonical/go-efilib/linux/dp_pci.go0000664000000000000000000000402600000000000020115 0ustar0000000000000000// Copyright 2021 Canonical Ltd. // Licensed under the LGPLv3 with static-linking exception. // See LICENCE file for details. package linux import ( "bytes" "errors" "fmt" "io/ioutil" "path/filepath" "regexp" "strconv" "golang.org/x/xerrors" "github.com/canonical/go-efilib" ) var classRE = regexp.MustCompile(`^0x([[:xdigit:]]+)$`) // pciRE matches "nnnn:bb:dd:f" where "nnnn" is the domain, "bb" is the bus number, // "dd" is the device number and "f" is the function. It captures the device and // function. var pciRE = regexp.MustCompile(`^[[:xdigit:]]{4}:[[:xdigit:]]{2}:([[:xdigit:]]{2})\.([[:digit:]]{1})$`) func handlePCIDevicePathNode(builder devicePathBuilder) error { component := builder.next(1) m := pciRE.FindStringSubmatch(component) if len(m) == 0 { return fmt.Errorf("invalid component: %s", component) } devNum, _ := strconv.ParseUint(m[1], 16, 8) fun, _ := strconv.ParseUint(m[2], 10, 8) classBytes, err := ioutil.ReadFile(filepath.Join(builder.absPath(component), "class")) if err != nil { return xerrors.Errorf("cannot read device class: %w", err) } var class []byte if n, err := fmt.Sscanf(string(classBytes), "0x%x", &class); err != nil || n != 1 { return errors.New("cannot decode device class") } builder.advance(1) switch { case bytes.HasPrefix(class, []byte{0x01, 0x00}): builder.setInterfaceType(interfaceTypeSCSI) case bytes.HasPrefix(class, []byte{0x01, 0x01}): builder.setInterfaceType(interfaceTypeIDE) case bytes.HasPrefix(class, []byte{0x01, 0x06}): builder.setInterfaceType(interfaceTypeSATA) case bytes.HasPrefix(class, []byte{0x01, 0x08}): builder.setInterfaceType(interfaceTypeNVME) case bytes.HasPrefix(class, []byte{0x06, 0x04}): builder.setInterfaceType(interfaceTypePCI) default: return errUnsupportedDevice("unhandled device class: " + string(classBytes)) } builder.append(&efi.PCIDevicePathNode{ Function: uint8(fun), Device: uint8(devNum)}) return nil } func init() { registerDevicePathNodeHandler("pci", handlePCIDevicePathNode, 0, interfaceTypePCI) } ./github.com/canonical/go-efilib/linux/dp_pci_root.go0000664000000000000000000000231100000000000021153 0ustar0000000000000000// Copyright 2021 Canonical Ltd. // Licensed under the LGPLv3 with static-linking exception. // See LICENCE file for details. package linux import ( "fmt" "path/filepath" "regexp" ) // pcirootRE matches a pcixxxx.xx path component. var pcirootRE = regexp.MustCompile(`^pci[[:xdigit:]]{4}:[[:xdigit:]]{2}$`) func handlePCIRootDevicePathNode(builder devicePathBuilder) error { component := builder.next(1) if !pcirootRE.MatchString(component) { return errSkipDevicePathNodeHandler } node, err := newACPIExtendedDevicePathNode(filepath.Join(builder.absPath(component), "firmware_node")) if err != nil { return err } if node.HID.Vendor() != "PNP" || (node.HID.Product() != 0x0a03 && node.HID.Product() != 0x0a08) { return fmt.Errorf("unexpected hid: %v", node.HID) } node.HID = 0x0a0341d0 if node.CID != 0 && (node.CID.Vendor() != "PNP" || (node.CID.Product() != 0x0a03 && node.CID.Product() != 0x0a08)) { return fmt.Errorf("unexpected cid: %v", node.CID) } builder.advance(1) builder.setInterfaceType(interfaceTypePCI) builder.append(maybeUseSimpleACPIDevicePathNode(node)) return nil } func init() { registerDevicePathNodeHandler("pci-root", handlePCIRootDevicePathNode, prependHandler) } ./github.com/canonical/go-efilib/linux/dp_sata.go0000664000000000000000000000410000000000000020263 0ustar0000000000000000// Copyright 2021 Canonical Ltd. // Licensed under the LGPLv3 with static-linking exception. // See LICENCE file for details. package linux import ( "errors" "fmt" "os" "path/filepath" "github.com/canonical/go-efilib" ) func handleSATADevicePathNode(builder devicePathBuilder) error { if builder.numRemaining() < 6 { return errors.New("invalid path: insufficient components") } params, err := handleATAPath(builder.absPath(builder.next(6))) if err != nil { return err } // Each SATA device is represented in the SCSI layer by setting the // channel to the port multiplier port number and the LUN as the LUN (see // drivers/ata/libata-scsi.c:ata_scsi_scan_host). pmp := params.channel if pmp > 0x7fff { return errors.New("invalid PMP") } // The target is always zero for SATA devices, as each port only has // a single device. if params.target != 0 { return errors.New("invalid SCSI target") } // We need to determine if the device is connected via a port // multiplier because we have to set the PMP address to 0xffff // if it isn't. Unfortunately, it is zero indexed so checking // that it is zero isn't sufficient. // // The kernel will expose a single host link%d device if there // is no port multiplier, or one of more PMP link%d.%d devices // if there is a port multiplier attached (see // drivers/ata/libata-pmp.c:sata_pmp_init_links and // drivers/ata/libata-transport.c:ata_tlink_add). _, err = os.Stat(filepath.Join(builder.next(1), fmt.Sprintf("link%d.%d", params.printId, pmp))) switch { case os.IsNotExist(err): // No port multiplier is connected. pmp = 0xffff case err != nil: return err default: // A port multiplier is connected. } builder.advance(6) builder.append(&efi.SATADevicePathNode{ // The kernel provides a one-indexed number and the firmware is zero-indexed. HBAPortNumber: uint16(params.port) - 1, PortMultiplierPortNumber: uint16(pmp), LUN: uint16(params.lun)}) return nil } func init() { registerDevicePathNodeHandler("sata", handleSATADevicePathNode, 0, interfaceTypeSATA) } ./github.com/canonical/go-efilib/linux/dp_scsi.go0000664000000000000000000000424200000000000020303 0ustar0000000000000000// Copyright 2022 Canonical Ltd. // Licensed under the LGPLv3 with static-linking exception. // See LICENCE file for details. package linux import ( "errors" "fmt" "math" "os" "path/filepath" "regexp" "strconv" "strings" "github.com/canonical/go-efilib" "golang.org/x/xerrors" ) // scsiRE matches a SCSI path, capturing the channel, target and LUN. var scsiRE = regexp.MustCompile(`^host[[:digit:]]+\/target[[:digit:]]+\:[[:digit:]]+\:[[:digit:]]+\/[[:digit:]]+\:([[:digit:]]+)\:([[:digit:]]+)\:([[:digit:]]+)\/block\/s[dr][[:alpha:]]$`) type scsiParams struct { channel uint32 target uint32 lun uint64 } func handleSCSIPath(path string) (*scsiParams, error) { components := strings.Split(path, string(os.PathSeparator)) if len(components) < 5 { return nil, errors.New("invalid path: insufficient components") } path = filepath.Join(components[len(components)-5:]...) m := scsiRE.FindStringSubmatch(path) if len(m) == 0 { return nil, fmt.Errorf("invalid path components: %s", path) } channel, err := strconv.ParseUint(m[1], 10, 32) if err != nil { return nil, xerrors.Errorf("invalid channel: %w", err) } target, err := strconv.ParseUint(m[2], 10, 32) if err != nil { return nil, xerrors.Errorf("invalid target: %w", err) } lun, err := strconv.ParseUint(m[3], 10, 64) if err != nil { return nil, xerrors.Errorf("invalid lun: %w", err) } return &scsiParams{ channel: uint32(channel), target: uint32(target), lun: lun}, nil } func handleSCSIDevicePathNode(builder devicePathBuilder) error { if builder.numRemaining() < 5 { return errors.New("invalid path: insufficient components") } params, err := handleSCSIPath(builder.absPath(builder.next(5))) if err != nil { return err } builder.advance(5) if params.channel != 0 { return errors.New("invalid channel") } if params.target > math.MaxUint16 { return errors.New("invalid target") } if params.lun > math.MaxUint16 { return errors.New("invalid LUN") } builder.append(&efi.SCSIDevicePathNode{ PUN: uint16(params.target), LUN: uint16(params.lun)}) return nil } func init() { registerDevicePathNodeHandler("scsi", handleSCSIDevicePathNode, 0, interfaceTypeSCSI) } ./github.com/canonical/go-efilib/linux/dp_virtio.go0000664000000000000000000000102200000000000020647 0ustar0000000000000000// Copyright 2021 Canonical Ltd. // Licensed under the LGPLv3 with static-linking exception. // See LICENCE file for details. package linux import ( "regexp" ) var virtioRE = regexp.MustCompile(`^virtio[[:digit:]]`) func handleVirtioDevicePathNode(builder devicePathBuilder) error { if !virtioRE.MatchString(builder.next(1)) { return errSkipDevicePathNodeHandler } builder.advance(1) return nil } func init() { registerDevicePathNodeHandler("virtio", handleVirtioDevicePathNode, prependHandler, interfaceTypeSCSI) } ./github.com/canonical/go-efilib/linux/dp_virtual.go0000664000000000000000000000067500000000000021036 0ustar0000000000000000// Copyright 2022 Canonical Ltd. // Licensed under the LGPLv3 with static-linking exception. // See LICENCE file for details. package linux func handleVirtualDevicePathNode(builder devicePathBuilder) error { if builder.next(1) == "virtual" { return errUnsupportedDevice("virtual devices are not supported") } return errSkipDevicePathNodeHandler } func init() { registerDevicePathNodeHandler("virtual", handleVirtualDevicePathNode, 0) } ./github.com/canonical/go-efilib/linux/dp_vmbus_root.go0000664000000000000000000000246400000000000021545 0ustar0000000000000000// Copyright 2022 Canonical Ltd. // Licensed under the LGPLv3 with static-linking exception. // See LICENCE file for details. package linux import ( "fmt" "regexp" ) // vmbusrootRE matches a VMBUS:XX component. var vmbusrootRE = regexp.MustCompile(`^VMBUS:[[:xdigit:]]{2}$`) func handleVMBusRootDevicePathNode(builder devicePathBuilder) error { component := builder.next(1) if !vmbusrootRE.MatchString(component) { return errSkipDevicePathNodeHandler } node, err := newACPIExtendedDevicePathNode(builder.absPath(component)) if err != nil { return err } if node.HID != 0 || node.CID != 0 || node.HIDStr != "VMBUS" || node.CIDStr != "" { return fmt.Errorf("unexpected node properties: %v", node) } // The hardware ID exposed by the kernel seems to be capitalized, but the // one exposed from the firmware on an instance I've tested on isn't. Fix // up here - I'm not sure if this is right (is it always "VMBus"?), but the // device path does need to be an exact match for lookups because the firmware // essentially just does a memcmp. node.HIDStr = "VMBus" builder.advance(1) builder.setInterfaceType(interfaceTypeVMBus) builder.append(maybeUseSimpleACPIDevicePathNode(node)) return nil } func init() { registerDevicePathNodeHandler("vmbus-root", handleVMBusRootDevicePathNode, prependHandler) } ./github.com/canonical/go-efilib/linux/filepath.go0000664000000000000000000003035100000000000020453 0ustar0000000000000000// Copyright 2021 Canonical Ltd. // Licensed under the LGPLv3 with static-linking exception. // See LICENCE file for details. package linux import ( "bufio" "errors" "fmt" "io/ioutil" "os" "path/filepath" "strconv" "strings" "golang.org/x/sys/unix" "golang.org/x/xerrors" "github.com/canonical/go-efilib" ) // FileDevicePathMode specifies the mode for NewFileDevicePath type FileDevicePathMode int const ( // FullPath indicates that only a full device path should be created. FullPath FileDevicePathMode = iota // ShortFormPathHD indicates that a short-form device path beginning // with a HD() node should be created. ShortFormPathHD // ShortFormPathFile indicates that a short-form device path consisting // of only the file path relative to the device should be created. ShortFormPathFile ) // ErrNoDevicePath is returned from NewFileDevicePath if the device in // which a file is stored cannot be mapped to a device path with the // specified mode. type ErrNoDevicePath string func (e ErrNoDevicePath) Error() string { return "cannot map file path to a UEFI device path: " + string(e) } type interfaceType int const ( interfaceTypeUnknown interfaceType = iota interfaceTypePCI interfaceTypeUSB interfaceTypeSCSI interfaceTypeIDE interfaceTypeSATA interfaceTypeNVME interfaceTypeVMBus ) const ( // prependHandler indicates that a handler wants to be tried // before handlers registered without this flag. These handlers // should use errSkipDevicePathNodeHandler on unhandled nodes. prependHandler = 1 << 0 ) var ( // errSkipDevicePathNodeHandler is returned from a handler when it // wants to defer handling to another handler. errSkipDevicePathNodeHandler = errors.New("") ) // errUnsupportedDevice is returned from a handler when it cannot // determine the interface. type errUnsupportedDevice string func (e errUnsupportedDevice) Error() string { return "unsupported device: " + string(e) } type devicePathNodeHandler func(devicePathBuilder) error type registeredDpHandler struct { name string fn devicePathNodeHandler } var devicePathNodeHandlers = make(map[interfaceType][]registeredDpHandler) func registerDevicePathNodeHandler(name string, fn devicePathNodeHandler, flags int, interfaces ...interfaceType) { if len(interfaces) == 0 { interfaces = []interfaceType{interfaceTypeUnknown} } for _, i := range interfaces { if flags&prependHandler > 0 { devicePathNodeHandlers[i] = append([]registeredDpHandler{{name, fn}}, devicePathNodeHandlers[i]...) } else { devicePathNodeHandlers[i] = append(devicePathNodeHandlers[i], registeredDpHandler{name, fn}) } } } type devicePathBuilder interface { // numRemaining returns the number of remaining sysfs components // to process. numRemaining() int // next returns the next n sysfs components to process. -1 returns // all remaining components. next(n int) string // absPath turns the supplied sysfs path components into an // absolute path. absPath(path string) string // advance marks the specified number of sysfs components // as handled and advances to the next ones. advance(n int) // interfaceType returns the type of the interface detected // by the last handler. interfaceType() interfaceType // setInterfaceType allows a handler to set the detected interface // type. setInterfaceType(iface interfaceType) // append allows a handler to append device path nodes to the current // path. append(nodes ...efi.DevicePathNode) } type devicePathBuilderImpl struct { iface interfaceType devPath efi.DevicePath processed []string remaining []string } func (b *devicePathBuilderImpl) numRemaining() int { return len(b.remaining) } func (b *devicePathBuilderImpl) next(n int) string { if n < 0 { return filepath.Join(b.remaining...) } return filepath.Join(b.remaining[:n]...) } func (b *devicePathBuilderImpl) absPath(path string) string { return filepath.Join(sysfsPath, "devices", filepath.Join(b.processed...), path) } func (b *devicePathBuilderImpl) advance(n int) { b.processed = append(b.processed, b.remaining[:n]...) b.remaining = b.remaining[n:] } func (b *devicePathBuilderImpl) interfaceType() interfaceType { return b.iface } func (b *devicePathBuilderImpl) setInterfaceType(iface interfaceType) { b.iface = iface } func (b *devicePathBuilderImpl) append(nodes ...efi.DevicePathNode) { b.devPath = append(b.devPath, nodes...) } func (b *devicePathBuilderImpl) done() bool { return len(b.remaining) == 0 } func (b *devicePathBuilderImpl) processNextComponent() error { nProcessed := len(b.processed) remaining := b.remaining iface := b.iface handlers := devicePathNodeHandlers[b.iface] if len(handlers) == 0 { // There should always be at least one handler registered for an interface. panic(fmt.Sprintf("no handlers registered for interface type %v", b.iface)) } for _, handler := range handlers { err := handler.fn(b) if err != nil { // Roll back changes b.processed = b.processed[:nProcessed] b.remaining = remaining b.iface = iface } if err == errSkipDevicePathNodeHandler { // Try the next handler. continue } if err != nil { return xerrors.Errorf("[handler %s]: %w", handler.name, err) } if iface != interfaceTypeUnknown && b.iface == interfaceTypeUnknown { // The handler set the interface type back to unknown. Turn this // in to a errUnsupportedDevice error. return errUnsupportedDevice("[handler " + handler.name + "]: unrecognized interface") } return nil } // If we get here, then all handlers returned errSkipDevicePathNodeHandler. if b.iface != interfaceTypeUnknown { // If the interface has already been determined, require at least one // handler to handle this node or return an error. panic(fmt.Sprintf("all handlers skipped handling interface type %v", b.iface)) } return errUnsupportedDevice("unhandled root node") } func newDevicePathBuilder(dev *dev) (*devicePathBuilderImpl, error) { path, err := filepath.Rel(filepath.Join(sysfsPath, "devices"), dev.sysfsPath) if err != nil { return nil, err } return &devicePathBuilderImpl{remaining: strings.Split(path, string(os.PathSeparator))}, nil } type mountPoint struct { dev uint64 root string mountDir string mountSource string } func scanBlockDeviceMounts() (mounts []*mountPoint, err error) { f, err := os.Open(mountsPath) if err != nil { return nil, err } defer f.Close() scanner := bufio.NewScanner(f) for scanner.Scan() { fields := strings.Fields(scanner.Text()) if len(fields) < 10 || len(fields) > 11 { return nil, errors.New("invalid mount info: incorrect number of fields") } devStr := strings.Split(fields[2], ":") if len(devStr) != 2 { return nil, errors.New("invalid mount info: invalid device number") } devMajor, err := strconv.ParseUint(devStr[0], 10, 32) if err != nil { return nil, xerrors.Errorf("invalid mount info: invalid device number: %w", err) } devMinor, err := strconv.ParseUint(devStr[1], 10, 32) if err != nil { return nil, xerrors.Errorf("invalid mount info: invalid device number: %w", err) } var mountSource string if len(fields) == 10 { mountSource = fields[8] } else { mountSource = fields[9] } if !filepath.IsAbs(mountSource) { continue } mounts = append(mounts, &mountPoint{ dev: unix.Mkdev(uint32(devMajor), uint32(devMinor)), root: fields[3], mountDir: fields[4], mountSource: mountSource}) } if scanner.Err() != nil { return nil, xerrors.Errorf("cannot parse mount info: %w", err) } return mounts, nil } func getFileMountPoint(path string) (*mountPoint, error) { mounts, err := scanBlockDeviceMounts() if err != nil { return nil, xerrors.Errorf("cannot obtain list of block device mounts: %w", err) } var candidate *mountPoint for _, mount := range mounts { if !strings.HasPrefix(path, mount.mountDir) { continue } if candidate == nil { candidate = mount } if len(mount.mountDir) > len(candidate.mountDir) { candidate = mount } } if candidate == nil { return nil, errors.New("not found") } return candidate, nil } type dev struct { sysfsPath string devPath string part int } type filePath struct { dev path string } func newFilePath(path string) (*filePath, error) { path, err := filepathEvalSymlinks(path) if err != nil { return nil, xerrors.Errorf("cannot evaluate symbolic links: %w", err) } mount, err := getFileMountPoint(path) if err != nil { return nil, xerrors.Errorf("cannot obtain mount information for path: %w", err) } rel, err := filepath.Rel(mount.mountDir, path) if err != nil { return nil, err } out := &filePath{path: filepath.Join(mount.root, rel)} childDev, err := filepath.EvalSymlinks(filepath.Join(sysfsPath, "dev/block", fmt.Sprintf("%d:%d", unix.Major(mount.dev), unix.Minor(mount.dev)))) if err != nil { return nil, err } parentDev := filepath.Dir(childDev) parentSubsystem, err := filepath.EvalSymlinks(filepath.Join(parentDev, "subsystem")) switch { case os.IsNotExist(err): // No subsystem link, could be the block/ directory case err != nil: return nil, err } if parentSubsystem != filepath.Join(sysfsPath, "class", "block") { // Parent device is not a block device out.dev.sysfsPath = childDev out.dev.devPath = filepath.Join("/dev", filepath.Base(childDev)) } else { // Parent device is a block device, so this is a partitioned // device. out.dev.sysfsPath = parentDev out.dev.devPath = filepath.Join("/dev", filepath.Base(parentDev)) b, err := ioutil.ReadFile(filepath.Join(childDev, "partition")) if err != nil { return nil, xerrors.Errorf("cannot obtain partition number for %s: %w", mount.dev, err) } part, err := strconv.Atoi(strings.TrimSpace(string(b))) if err != nil { return nil, xerrors.Errorf("cannot determine partition number for %s: %w", mount.dev, err) } out.dev.part = part } return out, nil } // NewFileDevicePath creates an EFI device path from the supplied filepath. // // If mode is FullPath, this will attempt to create a full device path which // requires the use of sysfs. If the device in which the file is stored cannot be // mapped to a device path, a ErrNoDevicePath error is returned. This could be // because the device is not recognized by this package, or because the device // genuinely cannot be mapped to a device path (eg, it is a device-mapper or loop // device). In this case, one of the ShortForm modes can be used. // // If mode is ShortFormPathHD, this will attempt to create a short-form device // path beginning with a HD() component. If the file is stored inside an // unpartitioned device, a ErrNoDevicePath error will be returned. In this case, // ShortFormPathFile can be used. // // When mode is ShortFormPathHD or FullPath and the file is stored inside a // partitoned device, read access is required on the underlying block device // in order to decode the partition table. // // If mode is ShortFormPathFile, this will attempt to create a short-form device // path consisting only of the file path relative to the device. // // In all modes, read access to the file's directory is required. func NewFileDevicePath(path string, mode FileDevicePathMode) (out efi.DevicePath, err error) { fp, err := newFilePath(path) if err != nil { return nil, err } if mode == ShortFormPathHD && fp.part == 0 { return nil, ErrNoDevicePath("file is not inside partitioned media - use linux.ShortFormPathFile") } builder, err := newDevicePathBuilder(&fp.dev) if err != nil { return nil, err } if mode == FullPath { for !builder.done() { var e errUnsupportedDevice err := builder.processNextComponent() switch { case xerrors.As(err, &e): return nil, ErrNoDevicePath("encountered an error when handling components " + builder.next(-1) + " from device path " + builder.absPath(builder.next(-1)) + ": " + err.Error()) case err != nil: return nil, xerrors.Errorf("cannot process components %s from device path %s: %w", builder.next(-1), builder.absPath(builder.next(-1)), err) } } } out = builder.devPath if mode != ShortFormPathFile && fp.part > 0 { node, err := NewHardDriveDevicePathNodeFromDevice(fp.devPath, fp.part) if err != nil { return nil, xerrors.Errorf("cannot construct hard drive device path node: %w", err) } out = append(out, node) } out = append(out, efi.NewFilePathDevicePathNode(fp.path)) return out, err } ./github.com/canonical/go-efilib/linux/mockable.go0000664000000000000000000000050200000000000020427 0ustar0000000000000000// Copyright 2021 Canonical Ltd. // Licensed under the LGPLv3 with static-linking exception. // See LICENCE file for details. package linux import ( "os" "path/filepath" ) var ( mountsPath = "/proc/self/mountinfo" sysfsPath = "/sys" filepathEvalSymlinks = filepath.EvalSymlinks osOpen = os.Open ) ./github.com/canonical/go-efilib/loadoption.go0000664000000000000000000000524200000000000017671 0ustar0000000000000000// Copyright 2021 Canonical Ltd. // Licensed under the LGPLv3 with static-linking exception. // See LICENCE file for details. package efi import ( "bytes" "errors" "fmt" "io" "math" "golang.org/x/xerrors" "github.com/canonical/go-efilib/internal/uefi" ) type LoadOptionAttributes uint32 func (a LoadOptionAttributes) Category() LoadOptionAttributes { return a & LoadOptionAttributes(uefi.LOAD_OPTION_CATEGORY) } const ( LoadOptionActive LoadOptionAttributes = uefi.LOAD_OPTION_ACTIVE LoadOptionForceReconnect LoadOptionAttributes = uefi.LOAD_OPTION_FORCE_RECONNECT LoadOptionHidden LoadOptionAttributes = uefi.LOAD_OPTION_HIDDEN LoadOptionCategoryBoot LoadOptionAttributes = uefi.LOAD_OPTION_CATEGORY_BOOT LoadOptionCategoryApp LoadOptionAttributes = uefi.LOAD_OPTION_CATEGORY_APP ) // LoadOption corresponds to the EFI_LOAD_OPTION type. type LoadOption struct { Attributes LoadOptionAttributes Description string FilePath DevicePath OptionalData []byte } func (o *LoadOption) String() string { return fmt.Sprintf("EFI_LOAD_OPTION{ Attributes: %d, Description: \"%s\", FilePath: %s, OptionalData: %x }", o.Attributes, o.Description, o.FilePath, o.OptionalData) } // Bytes returns the serialized form of this load option. func (o *LoadOption) Bytes() ([]byte, error) { w := new(bytes.Buffer) if err := o.Write(w); err != nil { return nil, err } return w.Bytes(), nil } // Write serializes this load option to the supplied io.Writer. func (o *LoadOption) Write(w io.Writer) error { opt := uefi.EFI_LOAD_OPTION{ Attributes: uint32(o.Attributes), Description: ConvertUTF8ToUCS2(o.Description + "\x00"), OptionalData: o.OptionalData} dp := new(bytes.Buffer) if err := o.FilePath.Write(dp); err != nil { return err } if dp.Len() > math.MaxUint16 { return errors.New("FilePath too long") } opt.FilePathList = dp.Bytes() opt.FilePathListLength = uint16(dp.Len()) return opt.Write(w) } // ReadLoadOption reads a LoadOption from the supplied io.Reader. Due to the // way that EFI_LOAD_OPTION is defined, where there is no size encoded for the // OptionalData field, this function will consume all of the bytes available // from the supplied reader. func ReadLoadOption(r io.Reader) (out *LoadOption, err error) { opt, err := uefi.Read_EFI_LOAD_OPTION(r) if err != nil { return nil, err } out = &LoadOption{ Attributes: LoadOptionAttributes(opt.Attributes), Description: ConvertUTF16ToUTF8(opt.Description), OptionalData: opt.OptionalData} dp, err := ReadDevicePath(bytes.NewReader(opt.FilePathList)) if err != nil { return nil, xerrors.Errorf("cannot read device path: %w", err) } out.FilePath = dp return out, nil } ./github.com/canonical/go-efilib/mbr/0000775000000000000000000000000000000000000015747 5ustar0000000000000000./github.com/canonical/go-efilib/mbr/mbr.go0000664000000000000000000000302000000000000017051 0ustar0000000000000000// Copyright 2020 Canonical Ltd. // Licensed under the LGPLv3 with static-linking exception. // See LICENCE file for details. package mbr import ( "encoding/binary" "errors" "io" ) const mbrSignature = 0xaa55 var ErrInvalidSignature = errors.New("invalid master boot record signature") // Address is a CHS address. type Address [3]uint8 func (a Address) Head() uint8 { return a[0] } func (a Address) Sector() uint8 { return a[1] & 0x3f } func (a Address) Cylinder() uint16 { c := uint16(a[2]) c |= uint16(a[1]&0xc0) << 2 return c } // PartitionEntry corresponds to a partition entry from a MBR. type PartitionEntry struct { BootIndicator uint8 StartAddress Address Type uint8 EndAddress Address StartingLBA uint32 NumberOfSectors uint32 } // Record corresponds to a MBR. type Record struct { BootstrapCode [440]byte UniqueSignature uint32 Partitions [4]PartitionEntry } type record struct { BootstrapCode [440]byte UniqueSignature uint32 Unknown [2]uint8 Partitions [4]PartitionEntry Signature uint16 } // ReadRecord reads a MBR from r. It returns ErrInvalidSignature if the // MBR has an invalid signature. func ReadRecord(r io.Reader) (*Record, error) { var rec record if err := binary.Read(r, binary.LittleEndian, &rec); err != nil { return nil, err } if rec.Signature != mbrSignature { return nil, ErrInvalidSignature } return &Record{BootstrapCode: rec.BootstrapCode, UniqueSignature: rec.UniqueSignature, Partitions: rec.Partitions}, nil } ./github.com/canonical/go-efilib/mockable_linux.go0000664000000000000000000000043100000000000020510 0ustar0000000000000000// Copyright 2021 Canonical Ltd. // Licensed under the LGPLv3 with static-linking exception. // See LICENCE file for details. package efi import ( "os" "golang.org/x/sys/unix" ) var ( openVarFile = realOpenVarFile removeVarFile = os.Remove unixStatfs = unix.Statfs ) ./github.com/canonical/go-efilib/pe.go0000664000000000000000000001450200000000000016124 0ustar0000000000000000// Copyright 2020 Canonical Ltd. // Licensed under the LGPLv3 with static-linking exception. // See LICENCE file for details. package efi import ( "crypto" "encoding/binary" "errors" "fmt" "hash" "io" "sort" "golang.org/x/xerrors" "github.com/canonical/go-efilib/internal/ioerr" "github.com/canonical/go-efilib/internal/pe1.14" ) const ( certTableIndex = 4 // Index of the Certificate Table entry in the Data Directory of a PE image optional header ) type eofIsUnexpectedReaderAt struct { r io.ReaderAt } func (r *eofIsUnexpectedReaderAt) ReadAt(p []byte, off int64) (n int, err error) { n, err = r.r.ReadAt(p, off) return n, ioerr.EOFIsUnexpected(err) } // ComputePeImageDigest computes the digest of the supplied PE image in accordance with the // Authenticode specification, using the specified digest algorithm. func ComputePeImageDigest(alg crypto.Hash, r io.ReaderAt, sz int64) ([]byte, error) { var dosheader [96]byte if n, err := r.ReadAt(dosheader[0:], 0); err != nil { if n > 0 && err == io.EOF { err = io.ErrUnexpectedEOF } return nil, err } var coffHeaderOffset int64 if dosheader[0] == 'M' && dosheader[1] == 'Z' { signoff := int64(binary.LittleEndian.Uint32(dosheader[0x3c:])) var sign [4]byte r.ReadAt(sign[:], signoff) if !(sign[0] == 'P' && sign[1] == 'E' && sign[2] == 0 && sign[3] == 0) { return nil, fmt.Errorf("invalid PE COFF file signature: %v", sign) } coffHeaderOffset = signoff + 4 } p, err := pe.NewFile(r) if err != nil { return nil, xerrors.Errorf("cannot decode PE binary: %w", err) } var isPe32Plus bool var sizeOfHeaders int64 var dd []pe.DataDirectory switch oh := p.OptionalHeader.(type) { case *pe.OptionalHeader32: sizeOfHeaders = int64(oh.SizeOfHeaders) dd = oh.DataDirectory[0:oh.NumberOfRvaAndSizes] case *pe.OptionalHeader64: isPe32Plus = true sizeOfHeaders = int64(oh.SizeOfHeaders) dd = oh.DataDirectory[0:oh.NumberOfRvaAndSizes] default: return nil, errors.New("PE binary doesn't contain an optional header") } // 1) Load the image header in to memory. hr := io.NewSectionReader(&eofIsUnexpectedReaderAt{r}, 0, sizeOfHeaders) // 2) Initialize a hash algorithm context. h := alg.New() // 3) Hash the image header from its base to immediately before the start of the checksum address in the optional header. // This includes the DOS header, 4-byte PE signature, COFF header, and the first 64 bytes of the optional header. b := make([]byte, int(coffHeaderOffset)+binary.Size(p.FileHeader)+64) if _, err := io.ReadFull(hr, b); err != nil { return nil, xerrors.Errorf("cannot read from image to start to checksum: %w", err) } h.Write(b) // 4) Skip over the checksum, which is a 4-byte field. hr.Seek(4, io.SeekCurrent) var certTable *pe.DataDirectory if len(dd) > certTableIndex { // 5) Hash everything from the end of the checksum field to immediately before the start of the Certificate Table entry in the // optional header data directory. // This is 60 bytes for PE32 format binaries, or 76 bytes for PE32+ format binaries. sz := 60 if isPe32Plus { sz = 76 } b = make([]byte, sz) if _, err := io.ReadFull(hr, b); err != nil { return nil, xerrors.Errorf("cannot read from checksum to certificate table data directory entry: %w", err) } h.Write(b) // 6) Get the Attribute Certificate Table address and size from the Certificate Table entry. certTable = &dd[certTableIndex] } // 7) Exclude the Certificate Table entry from the calculation and hash everything from the end of the Certificate Table entry // to the end of image header, including the Section Table. The Certificate Table entry is 8 bytes long. if certTable != nil { hr.Seek(8, io.SeekCurrent) } chunkedHashAll := func(r io.Reader, h hash.Hash) error { b := make([]byte, 4096) for { n, err := r.Read(b) h.Write(b[:n]) if err == io.EOF { return nil } if err != nil { return err } } } if err := chunkedHashAll(hr, h); err != nil { return nil, xerrors.Errorf("cannot hash remainder of headers and section table: %w", err) } // 8) Create a counter called sumOfBytesHashed, which is not part of the signature. Set this counter to the SizeOfHeaders field. sumOfBytesHashed := sizeOfHeaders // 9) Build a temporary table of pointers to all of the section headers in the image. Do not include any section headers in the // table whose Size field is zero. var sections []*pe.SectionHeader for _, section := range p.Sections { if section.Size == 0 { continue } sections = append(sections, §ion.SectionHeader) } // 10) Using the Offset field in the referenced SectionHeader structure as a key, arrange the table's elements in ascending order. // In other words, sort the section headers in ascending order according to the disk-file offset of the sections. sort.Slice(sections, func(i, j int) bool { return sections[i].Offset < sections[j].Offset }) for _, section := range sections { // 11) Walk through the sorted table, load the corresponding section into memory, and hash the entire section. Use the // Size field in the SectionHeader structure to determine the amount of data to hash. sr := io.NewSectionReader(&eofIsUnexpectedReaderAt{r}, int64(section.Offset), int64(section.Size)) if err := chunkedHashAll(sr, h); err != nil { return nil, xerrors.Errorf("cannot hash section %s: %w", section.Name, err) } // 12) Add the section’s Size value to sumOfBytesHashed. sumOfBytesHashed += int64(section.Size) // 13) Repeat steps 11 and 12 for all of the sections in the sorted table. } // 14) Create a value called fileSize, which is not part of the signature. Set this value to the image’s file size. If fileSize is // greater than sumOfBytesHashed, the file contains extra data that must be added to the hash. This data begins at the // sumOfBytesHashed file offset, and its length is: // fileSize – (certTable.Size + sumOfBytesHashed) fileSize := sz if fileSize > sumOfBytesHashed { var certSize int64 if certTable != nil { certSize = int64(certTable.Size) } if fileSize < (sumOfBytesHashed + certSize) { return nil, errors.New("image too short") } sr := io.NewSectionReader(&eofIsUnexpectedReaderAt{r}, sumOfBytesHashed, fileSize-sumOfBytesHashed-certSize) if err := chunkedHashAll(sr, h); err != nil { return nil, xerrors.Errorf("cannot hash extra data: %w", err) } } return h.Sum(nil), nil } ./github.com/canonical/go-efilib/string.go0000664000000000000000000000261300000000000017026 0ustar0000000000000000// Copyright 2021 Canonical Ltd. // Licensed under the LGPLv3 with static-linking exception. // See LICENCE file for details. package efi import ( "unicode" "unicode/utf16" "unicode/utf8" ) // ConvertUTF16ToUTF8 converts the supplied UTF-16 or UCS2 string // to a UTF-8 string. If the supplied string is NULL-terminated, // then the NULL termination is removed from the string. func ConvertUTF16ToUTF8(in []uint16) string { var u8 []byte for _, r := range utf16.Decode(in) { if r == 0 { break } u8Char := make([]byte, utf8.RuneLen(r)) utf8.EncodeRune(u8Char, r) u8 = append(u8, u8Char...) } return string(u8) } // ConvertUTF8ToUTF16 converts the supplied UTF-8 string to a // UTF-16 string. func ConvertUTF8ToUTF16(in string) []uint16 { var unicodeStr []rune for len(in) > 0 { r, sz := utf8.DecodeRuneInString(in) unicodeStr = append(unicodeStr, r) in = in[sz:] } return utf16.Encode(unicodeStr) } // ConvertUTF8ToUCS2 converts the supplied UTF-8 string to a // UCS2 string. Any code point outside of the Basic Multilingual // Plane cannot be represented by UCS2 and is converted to the // replacement character. func ConvertUTF8ToUCS2(in string) []uint16 { var unicodeStr []rune for len(in) > 0 { r, sz := utf8.DecodeRuneInString(in) if r >= 0x10000 { r = unicode.ReplacementChar } unicodeStr = append(unicodeStr, r) in = in[sz:] } return utf16.Encode(unicodeStr) } ./github.com/canonical/go-efilib/types.go0000664000000000000000000000034100000000000016660 0ustar0000000000000000// Copyright 2020 Canonical Ltd. // Licensed under the LGPLv3 with static-linking exception. // See LICENCE file for details. package efi type PhysicalAddress uint64 // LBA corresponds to the EFI_LBA type. type LBA uint64 ./github.com/canonical/go-efilib/vars.go0000664000000000000000000000555400000000000016502 0ustar0000000000000000// Copyright 2020-2021 Canonical Ltd. // Licensed under the LGPLv3 with static-linking exception. // See LICENCE file for details. package efi import ( "errors" "github.com/canonical/go-efilib/internal/uefi" ) type VariableAttributes uint32 const ( AttributeNonVolatile VariableAttributes = uefi.EFI_VARIABLE_NON_VOLATILE AttributeBootserviceAccess VariableAttributes = uefi.EFI_VARIABLE_BOOTSERVICE_ACCESS AttributeRuntimeAccess VariableAttributes = uefi.EFI_VARIABLE_RUNTIME_ACCESS AttributeHardwareErrorRecord VariableAttributes = uefi.EFI_VARIABLE_HARDWARE_ERROR_RECORD AttributeAuthenticatedWriteAccess VariableAttributes = uefi.EFI_VARIABLE_AUTHENTICATED_WRITE_ACCESS AttributeTimeBasedAuthenticatedWriteAccess VariableAttributes = uefi.EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS AttributeAppendWrite VariableAttributes = uefi.EFI_VARIABLE_APPEND_WRITE AttributeEnhancedAuthenticatedAccess VariableAttributes = uefi.EFI_VARIABLE_ENHANCED_AUTHENTICATED_ACCESS ) var ( ErrVarsUnavailable = errors.New("no variable backend is available") ErrVarNotExist = errors.New("variable does not exist") ErrVarPermission = errors.New("permission denied") ) // VariableDescriptor represents the identity of a variable. type VariableDescriptor struct { Name string GUID GUID } type varsBackend interface { Get(name string, guid GUID) (VariableAttributes, []byte, error) Set(name string, guid GUID, attrs VariableAttributes, data []byte) error List() ([]VariableDescriptor, error) } type nullVarsBackend struct{} func (v nullVarsBackend) Get(name string, guid GUID) (VariableAttributes, []byte, error) { return 0, nil, ErrVarsUnavailable } func (v nullVarsBackend) Set(name string, guid GUID, attrs VariableAttributes, data []byte) error { return ErrVarsUnavailable } func (v nullVarsBackend) List() ([]VariableDescriptor, error) { return nil, ErrVarsUnavailable } var vars varsBackend = nullVarsBackend{} // ReadVariable returns the value and attributes of the EFI variable with the specified // name and GUID. func ReadVariable(name string, guid GUID) ([]byte, VariableAttributes, error) { attrs, data, err := vars.Get(name, guid) return data, attrs, err } // WriteVariable writes the supplied data value with the specified attributes to the // EFI variable with the specified name and GUID. // // If the variable already exists, the specified attributes must match the existing // attributes with the exception of AttributeAppendWrite. // // If the variable does not exist, it will be created. func WriteVariable(name string, guid GUID, attrs VariableAttributes, data []byte) error { return vars.Set(name, guid, attrs, data) } // ListVariables returns a list of variables that can be accessed. func ListVariables() ([]VariableDescriptor, error) { return vars.List() } ./github.com/canonical/go-efilib/vars_linux.go0000664000000000000000000001527000000000000017715 0ustar0000000000000000// Copyright 2020-2021 Canonical Ltd. // Licensed under the LGPLv3 with static-linking exception. // See LICENCE file for details. package efi import ( "bytes" "encoding/binary" "fmt" "io" "io/ioutil" "os" "path/filepath" "sort" "syscall" "golang.org/x/sys/unix" "golang.org/x/xerrors" internal_unix "github.com/canonical/go-efilib/internal/unix" ) func efivarfsPath() string { return "/sys/firmware/efi/efivars" } type varFile interface { io.ReadWriteCloser Readdir(n int) ([]os.FileInfo, error) GetInodeFlags() (uint, error) SetInodeFlags(flags uint) error } func makeVarFileMutable(f varFile) (restore func() error, err error) { const immutableFlag = 0x00000010 flags, err := f.GetInodeFlags() if err != nil { return nil, err } if flags&immutableFlag == 0 { // Nothing to do return func() error { return nil }, nil } if err := f.SetInodeFlags(flags &^ immutableFlag); err != nil { return nil, err } return func() error { return f.SetInodeFlags(flags) }, nil } type realVarFile struct { *os.File } func (f *realVarFile) GetInodeFlags() (uint, error) { flags, err := internal_unix.IoctlGetUint(int(f.Fd()), unix.FS_IOC_GETFLAGS) if err != nil { return 0, &os.PathError{Op: "ioctl", Path: f.Name(), Err: err} } return flags, nil } func (f *realVarFile) SetInodeFlags(flags uint) error { if err := internal_unix.IoctlSetPointerUint(int(f.Fd()), unix.FS_IOC_SETFLAGS, flags); err != nil { return &os.PathError{Op: "ioctl", Path: f.Name(), Err: err} } return nil } func realOpenVarFile(path string, flags int, perm os.FileMode) (varFile, error) { f, err := os.OpenFile(path, flags, perm) if err != nil { return nil, err } return &realVarFile{f}, nil } var guidLength = len("xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx") func probeEfivarfs() bool { var st unix.Statfs_t if err := unixStatfs(efivarfsPath(), &st); err != nil { return false } if uint(st.Type) != uint(unix.EFIVARFS_MAGIC) { return false } return true } func maybeRetry(n int, fn func() (bool, error)) error { for i := 1; ; i++ { retry, err := fn() switch { case i > n: return err case !retry: return err case err == nil: return nil } } } func processEfivarfsFileAccessError(err error) (retry bool, errOut error) { if os.IsPermission(err) { var se syscall.Errno if !xerrors.As(err, &se) { // This shouldn't happen, but just return ErrVarPermission // in this case and don't retry. return false, ErrVarPermission } if se == syscall.EACCES { // open will fail with EACCES if we lack the privileges // to write to the file. // open will fail with EACCES if we lack the privileges // to write to the parent directory in the case where we // need to create a new file. // unlink will fail with EACCES if we lack the privileges // to write to the parent directory. // Don't retry in these cases. return false, ErrVarPermission } // open and unlink will fail with EPERM if the file exists but // it is immutable. This might happen as a result of a race with // another process that might have been writing to the variable // or may have deleted and recreated it, making the underlying // inode immutable again. // Retry in this case. return true, ErrVarPermission } // Don't retry for any other error. return false, err } func writeEfivarfsFile(path string, attrs VariableAttributes, data []byte) (retry bool, err error) { // Open for reading to make the inode mutable r, err := openVarFile(path, os.O_RDONLY, 0) switch { case os.IsNotExist(err): case os.IsPermission(err): return false, ErrVarPermission case err != nil: return false, err default: defer r.Close() restoreImmutable, err := makeVarFileMutable(r) switch { case os.IsPermission(err): return false, ErrVarPermission case err != nil: return false, err } defer restoreImmutable() } if len(data) == 0 { return processEfivarfsFileAccessError(removeVarFile(path)) } flags := os.O_WRONLY | os.O_CREATE if attrs&AttributeAppendWrite != 0 { flags |= os.O_APPEND } w, err := openVarFile(path, flags, 0644) if err != nil { return processEfivarfsFileAccessError(err) } defer w.Close() var buf bytes.Buffer binary.Write(&buf, binary.LittleEndian, attrs) buf.Write(data) _, err = buf.WriteTo(w) return false, err } type efivarfsVarsBackend struct{} func (v efivarfsVarsBackend) Get(name string, guid GUID) (VariableAttributes, []byte, error) { path := filepath.Join(efivarfsPath(), fmt.Sprintf("%s-%s", name, guid)) f, err := openVarFile(path, os.O_RDONLY, 0) switch { case os.IsNotExist(err): return 0, nil, ErrVarNotExist case os.IsPermission(err): return 0, nil, ErrVarPermission case err != nil: return 0, nil, err } defer f.Close() var attrs VariableAttributes if err := binary.Read(f, binary.LittleEndian, &attrs); err != nil { if err == io.EOF { return 0, nil, ErrVarNotExist } return 0, nil, err } data, err := ioutil.ReadAll(f) if err != nil { return 0, nil, err } return attrs, data, nil } func (v efivarfsVarsBackend) Set(name string, guid GUID, attrs VariableAttributes, data []byte) error { path := filepath.Join(efivarfsPath(), fmt.Sprintf("%s-%s", name, guid)) return maybeRetry(4, func() (bool, error) { return writeEfivarfsFile(path, attrs, data) }) } func (v efivarfsVarsBackend) List() ([]VariableDescriptor, error) { f, err := openVarFile(efivarfsPath(), os.O_RDONLY, 0) switch { case os.IsNotExist(err): return nil, ErrVarsUnavailable case os.IsPermission(err): return nil, ErrVarPermission case err != nil: return nil, err } defer f.Close() dirents, err := f.Readdir(-1) if err != nil { return nil, err } var entries []VariableDescriptor for _, dirent := range dirents { if !dirent.Mode().IsRegular() { // Skip non-regular files continue } if len(dirent.Name()) < guidLength+1 { // Skip files with a basename that isn't long enough // to contain a GUID and a hyphen continue } if dirent.Name()[len(dirent.Name())-guidLength-1] != '-' { // Skip files where the basename doesn't contain a // hyphen between the name and GUID continue } if dirent.Size() == 0 { // Skip files with zero size. These are variables that // have been deleted by writing an empty payload continue } name := dirent.Name()[:len(dirent.Name())-guidLength-1] guid, err := DecodeGUIDString(dirent.Name()[len(name)+1:]) if err != nil { continue } entries = append(entries, VariableDescriptor{Name: name, GUID: guid}) } sort.Slice(entries, func(i, j int) bool { return fmt.Sprintf("%s-%v", entries[i].Name, entries[i].GUID) < fmt.Sprintf("%s-%v", entries[j].Name, entries[j].GUID) }) return entries, nil } func init() { if !probeEfivarfs() { return } vars = efivarfsVarsBackend{} } ./github.com/canonical/go-efilib/wincert.go0000664000000000000000000001105300000000000017171 0ustar0000000000000000// Copyright 2020 Canonical Ltd. // Licensed under the LGPLv3 with static-linking exception. // See LICENCE file for details. package efi import ( "encoding/binary" "errors" "io" "github.com/canonical/go-efilib/internal/ioerr" "github.com/canonical/go-efilib/internal/uefi" ) // WinCertificate is an interface type corresponding to implementations of WIN_CERTIFICATE. type WinCertificate interface { Write(w io.Writer) error // Encode this certificate to the supplied io.Writer } // WinCertificatePKCS1v15 corresponds to the WIN_CERTIFICATE_EFI_PKCS1_15 type. type WinCertificatePKCS1v15 struct { HashAlgorithm GUID Signature []byte } func (c *WinCertificatePKCS1v15) Write(w io.Writer) error { cert := uefi.WIN_CERTIFICATE_EFI_PKCS1_15{ HashAlgorithm: uefi.EFI_GUID(c.HashAlgorithm), Signature: c.Signature} cert.Hdr = uefi.WIN_CERTIFICATE{ Length: uint32(binary.Size(cert.Hdr) + binary.Size(cert.HashAlgorithm) + len(c.Signature)), Revision: 0x0200, CertificateType: uefi.WIN_CERT_TYPE_EFI_PKCS115} return binary.Write(w, binary.LittleEndian, &cert) } // WinCertificateGUID corresponds to the WIN_CERTIFICATE_UEFI_GUID type. type WinCertificateGUID struct { Type GUID Data []byte } func (c *WinCertificateGUID) Write(w io.Writer) error { return binary.Write(w, binary.LittleEndian, c.toUefiType()) } func (c *WinCertificateGUID) toUefiType() *uefi.WIN_CERTIFICATE_UEFI_GUID { cert := &uefi.WIN_CERTIFICATE_UEFI_GUID{ CertType: uefi.EFI_GUID(c.Type), CertData: c.Data} cert.Hdr = uefi.WIN_CERTIFICATE{ Length: uint32(binary.Size(cert.Hdr) + binary.Size(cert.CertType) + len(c.Data)), Revision: 0x0200, CertificateType: uefi.WIN_CERT_TYPE_EFI_GUID} return cert } func newWinCertificateGUID(cert *uefi.WIN_CERTIFICATE_UEFI_GUID) *WinCertificateGUID { return &WinCertificateGUID{Type: GUID(cert.CertType), Data: cert.CertData} } // WinCertificateAuthenticode corresponds to an Authenticode signature. type WinCertificateAuthenticode []byte func (c WinCertificateAuthenticode) Write(w io.Writer) error { cert := uefi.WIN_CERTIFICATE_EFI_PKCS{CertData: c} cert.Hdr = uefi.WIN_CERTIFICATE{ Length: uint32(binary.Size(cert.Hdr) + len(c)), Revision: 0x0200, CertificateType: uefi.WIN_CERT_TYPE_PKCS_SIGNED_DATA} return binary.Write(w, binary.LittleEndian, &cert) } // ReadWinCertificate decodes a signature (something that is confusingly represented by types with "certificate" in the name in both // the UEFI and PE/COFF specifications) from the supplied io.Reader and returns a WinCertificate of the appropriate type. The type // returned is dependent on the data, and will be one of *WinCertificateAuthenticode, *WinCertificatePKCS1_15 or *WinCertificateGUID. func ReadWinCertificate(r io.Reader) (WinCertificate, error) { var hdr uefi.WIN_CERTIFICATE if err := binary.Read(r, binary.LittleEndian, &hdr); err != nil { return nil, err } if hdr.Revision != 0x0200 { return nil, errors.New("unexpected revision") } switch hdr.CertificateType { case uefi.WIN_CERT_TYPE_PKCS_SIGNED_DATA: cert := uefi.WIN_CERTIFICATE_EFI_PKCS{Hdr: hdr} cert.CertData = make([]byte, int(cert.Hdr.Length)-binary.Size(cert.Hdr)) if _, err := io.ReadFull(r, cert.CertData); err != nil { return nil, ioerr.EOFIsUnexpected("cannot read WIN_CERTIFICATE_EFI_PKCS: %w", err) } return WinCertificateAuthenticode(cert.CertData), nil case uefi.WIN_CERT_TYPE_EFI_PKCS115: cert := uefi.WIN_CERTIFICATE_EFI_PKCS1_15{Hdr: hdr} cert.Signature = make([]byte, int(cert.Hdr.Length)-binary.Size(cert.Hdr)-binary.Size(cert.HashAlgorithm)) if _, err := io.ReadFull(r, cert.HashAlgorithm[:]); err != nil { return nil, ioerr.EOFIsUnexpected("cannot read WIN_CERTIFICATE_EFI_PKCS1_15: %w", err) } if _, err := io.ReadFull(r, cert.Signature); err != nil { return nil, ioerr.EOFIsUnexpected("cannot read WIN_CERTIFICATE_EFI_PKCS1_15: %w", err) } return &WinCertificatePKCS1v15{HashAlgorithm: GUID(cert.HashAlgorithm), Signature: cert.Signature}, nil case uefi.WIN_CERT_TYPE_EFI_GUID: cert := uefi.WIN_CERTIFICATE_UEFI_GUID{Hdr: hdr} cert.CertData = make([]byte, int(cert.Hdr.Length)-binary.Size(cert.Hdr)-binary.Size(cert.CertType)) if _, err := io.ReadFull(r, cert.CertType[:]); err != nil { return nil, ioerr.EOFIsUnexpected("cannot read WIN_CERTIFICATE_UEFI_GUID: %w", err) } if _, err := io.ReadFull(r, cert.CertData); err != nil { return nil, ioerr.EOFIsUnexpected("cannot read WIN_CERTIFICATE_UEFI_GUID: %w", err) } return newWinCertificateGUID(&cert), nil default: return nil, errors.New("unexpected type") } } ./github.com/canonical/go-sp800.108-kdf/0000775000000000000000000000000000000000000015660 5ustar0000000000000000./github.com/canonical/go-sp800.108-kdf/LICENSE0000664000000000000000000002150100000000000016664 0ustar0000000000000000All files in this repository are licensed as follows. If you contribute to this repository, it is assumed that you license your contribution under the same license unless you state otherwise. All files Copyright (C) 2021 Canonical Ltd. unless otherwise specified in the file. This software is licensed under the LGPLv3, included below. As a special exception to the GNU Lesser General Public License version 3 ("LGPL3"), the copyright holders of this Library give you permission to convey to a third party a Combined Work that links statically or dynamically to this Library without providing any Minimal Corresponding Source or Minimal Application Code as set out in 4d or providing the installation information set out in section 4e, provided that you comply with the other provisions of LGPL3 and provided that you meet, for the Application the terms and conditions of the license(s) which apply to the Application. Except as stated in this special exception, the provisions of LGPL3 will continue to comply in full to this Library. If you modify this Library, you may apply this exception to your version of this Library, but you are not obliged to do so. If you do not wish to do so, delete this exception statement from your version. This exception does not (and cannot) modify any license terms which apply to the Application, with which you must still comply. GNU LESSER GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. This version of the GNU Lesser General Public License incorporates the terms and conditions of version 3 of the GNU General Public License, supplemented by the additional permissions listed below. 0. Additional Definitions. As used herein, "this License" refers to version 3 of the GNU Lesser General Public License, and the "GNU GPL" refers to version 3 of the GNU General Public License. "The Library" refers to a covered work governed by this License, other than an Application or a Combined Work as defined below. An "Application" is any work that makes use of an interface provided by the Library, but which is not otherwise based on the Library. Defining a subclass of a class defined by the Library is deemed a mode of using an interface provided by the Library. A "Combined Work" is a work produced by combining or linking an Application with the Library. The particular version of the Library with which the Combined Work was made is also called the "Linked Version". The "Minimal Corresponding Source" for a Combined Work means the Corresponding Source for the Combined Work, excluding any source code for portions of the Combined Work that, considered in isolation, are based on the Application, and not on the Linked Version. The "Corresponding Application Code" for a Combined Work means the object code and/or source code for the Application, including any data and utility programs needed for reproducing the Combined Work from the Application, but excluding the System Libraries of the Combined Work. 1. Exception to Section 3 of the GNU GPL. You may convey a covered work under sections 3 and 4 of this License without being bound by section 3 of the GNU GPL. 2. Conveying Modified Versions. If you modify a copy of the Library, and, in your modifications, a facility refers to a function or data to be supplied by an Application that uses the facility (other than as an argument passed when the facility is invoked), then you may convey a copy of the modified version: a) under this License, provided that you make a good faith effort to ensure that, in the event an Application does not supply the function or data, the facility still operates, and performs whatever part of its purpose remains meaningful, or b) under the GNU GPL, with none of the additional permissions of this License applicable to that copy. 3. Object Code Incorporating Material from Library Header Files. The object code form of an Application may incorporate material from a header file that is part of the Library. You may convey such object code under terms of your choice, provided that, if the incorporated material is not limited to numerical parameters, data structure layouts and accessors, or small macros, inline functions and templates (ten or fewer lines in length), you do both of the following: a) Give prominent notice with each copy of the object code that the Library is used in it and that the Library and its use are covered by this License. b) Accompany the object code with a copy of the GNU GPL and this license document. 4. Combined Works. You may convey a Combined Work under terms of your choice that, taken together, effectively do not restrict modification of the portions of the Library contained in the Combined Work and reverse engineering for debugging such modifications, if you also do each of the following: a) Give prominent notice with each copy of the Combined Work that the Library is used in it and that the Library and its use are covered by this License. b) Accompany the Combined Work with a copy of the GNU GPL and this license document. c) For a Combined Work that displays copyright notices during execution, include the copyright notice for the Library among these notices, as well as a reference directing the user to the copies of the GNU GPL and this license document. d) Do one of the following: 0) Convey the Minimal Corresponding Source under the terms of this License, and the Corresponding Application Code in a form suitable for, and under terms that permit, the user to recombine or relink the Application with a modified version of the Linked Version to produce a modified Combined Work, in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source. 1) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (a) uses at run time a copy of the Library already present on the user's computer system, and (b) will operate properly with a modified version of the Library that is interface-compatible with the Linked Version. e) Provide Installation Information, but only if you would otherwise be required to provide such information under section 6 of the GNU GPL, and only to the extent that such information is necessary to install and execute a modified version of the Combined Work produced by recombining or relinking the Application with a modified version of the Linked Version. (If you use option 4d0, the Installation Information must accompany the Minimal Corresponding Source and Corresponding Application Code. If you use option 4d1, you must provide the Installation Information in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source.) 5. Combined Libraries. You may place library facilities that are a work based on the Library side by side in a single library together with other library facilities that are not Applications and are not covered by this License, and convey such a combined library under terms of your choice, if you do both of the following: a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities, conveyed under the terms of this License. b) Give prominent notice with the combined library that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. 6. Revised Versions of the GNU Lesser General Public License. The Free Software Foundation may publish revised and/or new versions of the GNU Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Library as you received it specifies that a certain numbered version of the GNU Lesser General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that published version or of any later version published by the Free Software Foundation. If the Library as you received it does not specify a version number of the GNU Lesser General Public License, you may choose any version of the GNU Lesser General Public License ever published by the Free Software Foundation. If the Library as you received it specifies that a proxy can decide whether future versions of the GNU Lesser General Public License shall apply, that proxy's public statement of acceptance of any version is permanent authorization for you to choose that version for the Library. ./github.com/canonical/go-sp800.108-kdf/go.mod0000664000000000000000000000016300000000000016766 0ustar0000000000000000module github.com/canonical/go-sp800.108-kdf go 1.9 require gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c ./github.com/canonical/go-sp800.108-kdf/go.sum0000664000000000000000000000124400000000000017014 0ustar0000000000000000github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pty v1.1.1 h1:VkoXIwSboBpnk99O/KFauAEILuNHv5DVFKZMBN/gUgw= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= ./github.com/canonical/go-sp800.108-kdf/kdf.go0000664000000000000000000000757000000000000016764 0ustar0000000000000000// Copyright 2021 Canonical Ltd. // Licensed under the LGPLv3 with static-linking exception. // See LICENCE file for details. /* Package kdf implements the key derivation functions described in NIST SP-800-108 (see https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-108.pdf). All 3 modes are implemented - counter, feedback and pipeline. */ package kdf import ( "bytes" "crypto" "crypto/hmac" "encoding/binary" "hash" ) // PRF represents a pseudorandom function, required by the key derivation functions. type PRF interface { // Len returns the length of this PRF. Len() uint32 // Run computes bytes for the supplied seed and input value. Run(s, x []byte) []byte } type hmacPRF crypto.Hash func (p hmacPRF) Len() uint32 { return uint32(crypto.Hash(p).Size()) } func (p hmacPRF) Run(s, x []byte) []byte { h := hmac.New(func() hash.Hash { return crypto.Hash(p).New() }, s) h.Write(x) return h.Sum(nil) } // NewHMACPRF creates a new HMAC based PRF using the supplied digest algorithm. func NewHMACPRF(h crypto.Hash) PRF { return hmacPRF(h) } func fixedBytes(label, context []byte, bitLength uint32) []byte { var res bytes.Buffer res.Write(label) res.Write([]byte{0}) res.Write(context) binary.Write(&res, binary.BigEndian, bitLength) return res.Bytes() } func commonKDF(prfLen uint32, fixed []byte, bitLength uint32, fn func(uint32) []byte) []byte { n := (bitLength + prfLen - 1) / prfLen var res bytes.Buffer for i := uint32(1); i <= n; i++ { res.Write(fn(i)) } return res.Bytes()[:(bitLength+7)/8] } func counterModeKeyInternal(prf PRF, key, fixed []byte, bitLength uint32) []byte { return commonKDF(prf.Len(), fixed, bitLength, func(i uint32) []byte { var x bytes.Buffer binary.Write(&x, binary.BigEndian, i) x.Write(fixed) return prf.Run(key, x.Bytes()) }) } // CounterModeKey derives a key of the specified length using the counter mode // function described in NIST SP-800-108, using the supplied PRF, secret key and // other input parameters. func CounterModeKey(prf PRF, key, label, context []byte, bitLength uint32) []byte { return counterModeKeyInternal(prf, key, fixedBytes(label, context, bitLength), bitLength) } func feedbackModeKeyInternal(prf PRF, key, fixed, iv []byte, bitLength uint32, useCounter bool) []byte { k := iv return commonKDF(prf.Len(), fixed, bitLength, func(i uint32) []byte { var x bytes.Buffer x.Write(k) if useCounter { binary.Write(&x, binary.BigEndian, i) } x.Write(fixed) k = prf.Run(key, x.Bytes()) return k }) } // FeebackModeKey derives a key of the specified length using the feedback mode // function described in NIST SP-800-108, using the supplied PRF, secret key and // other input parameters. // // The useCounter argument specifies whether the iteration counter should be // included as an input to the PRF. func FeedbackModeKey(prf PRF, key, label, context, iv []byte, bitLength uint32, useCounter bool) []byte { return feedbackModeKeyInternal(prf, key, fixedBytes(label, context, bitLength), iv, bitLength, useCounter) } func pipelineModeKeyInternal(prf PRF, key, fixed []byte, bitLength uint32, useCounter bool) []byte { a := fixed return commonKDF(prf.Len(), fixed, bitLength, func(i uint32) []byte { a = prf.Run(key, a) var x bytes.Buffer x.Write(a) if useCounter { binary.Write(&x, binary.BigEndian, i) } x.Write(fixed) return prf.Run(key, x.Bytes()) }) } // PipelineModeKey derives a key of the specified length using the double-pipeline // iteration mode function described in NIST SP-800-108, using the supplied PRF, // secret key and other input parameters. // // The useCounter argument specifies whether the iteration counter should be // included as an input to the PRF. func PipelineModeKey(prf PRF, key, label, context []byte, bitLength uint32, useCounter bool) []byte { return pipelineModeKeyInternal(prf, key, fixedBytes(label, context, bitLength), bitLength, useCounter) } ./github.com/canonical/go-sp800.90a-drbg/0000775000000000000000000000000000000000000016113 5ustar0000000000000000./github.com/canonical/go-sp800.90a-drbg/.gitignore0000664000000000000000000000001200000000000020074 0ustar0000000000000000vendor/*/ ./github.com/canonical/go-sp800.90a-drbg/LICENSE0000664000000000000000000002150100000000000017117 0ustar0000000000000000All files in this repository are licensed as follows. If you contribute to this repository, it is assumed that you license your contribution under the same license unless you state otherwise. All files Copyright (C) 2021 Canonical Ltd. unless otherwise specified in the file. This software is licensed under the LGPLv3, included below. As a special exception to the GNU Lesser General Public License version 3 ("LGPL3"), the copyright holders of this Library give you permission to convey to a third party a Combined Work that links statically or dynamically to this Library without providing any Minimal Corresponding Source or Minimal Application Code as set out in 4d or providing the installation information set out in section 4e, provided that you comply with the other provisions of LGPL3 and provided that you meet, for the Application the terms and conditions of the license(s) which apply to the Application. Except as stated in this special exception, the provisions of LGPL3 will continue to comply in full to this Library. If you modify this Library, you may apply this exception to your version of this Library, but you are not obliged to do so. If you do not wish to do so, delete this exception statement from your version. This exception does not (and cannot) modify any license terms which apply to the Application, with which you must still comply. GNU LESSER GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. This version of the GNU Lesser General Public License incorporates the terms and conditions of version 3 of the GNU General Public License, supplemented by the additional permissions listed below. 0. Additional Definitions. As used herein, "this License" refers to version 3 of the GNU Lesser General Public License, and the "GNU GPL" refers to version 3 of the GNU General Public License. "The Library" refers to a covered work governed by this License, other than an Application or a Combined Work as defined below. An "Application" is any work that makes use of an interface provided by the Library, but which is not otherwise based on the Library. Defining a subclass of a class defined by the Library is deemed a mode of using an interface provided by the Library. A "Combined Work" is a work produced by combining or linking an Application with the Library. The particular version of the Library with which the Combined Work was made is also called the "Linked Version". The "Minimal Corresponding Source" for a Combined Work means the Corresponding Source for the Combined Work, excluding any source code for portions of the Combined Work that, considered in isolation, are based on the Application, and not on the Linked Version. The "Corresponding Application Code" for a Combined Work means the object code and/or source code for the Application, including any data and utility programs needed for reproducing the Combined Work from the Application, but excluding the System Libraries of the Combined Work. 1. Exception to Section 3 of the GNU GPL. You may convey a covered work under sections 3 and 4 of this License without being bound by section 3 of the GNU GPL. 2. Conveying Modified Versions. If you modify a copy of the Library, and, in your modifications, a facility refers to a function or data to be supplied by an Application that uses the facility (other than as an argument passed when the facility is invoked), then you may convey a copy of the modified version: a) under this License, provided that you make a good faith effort to ensure that, in the event an Application does not supply the function or data, the facility still operates, and performs whatever part of its purpose remains meaningful, or b) under the GNU GPL, with none of the additional permissions of this License applicable to that copy. 3. Object Code Incorporating Material from Library Header Files. The object code form of an Application may incorporate material from a header file that is part of the Library. You may convey such object code under terms of your choice, provided that, if the incorporated material is not limited to numerical parameters, data structure layouts and accessors, or small macros, inline functions and templates (ten or fewer lines in length), you do both of the following: a) Give prominent notice with each copy of the object code that the Library is used in it and that the Library and its use are covered by this License. b) Accompany the object code with a copy of the GNU GPL and this license document. 4. Combined Works. You may convey a Combined Work under terms of your choice that, taken together, effectively do not restrict modification of the portions of the Library contained in the Combined Work and reverse engineering for debugging such modifications, if you also do each of the following: a) Give prominent notice with each copy of the Combined Work that the Library is used in it and that the Library and its use are covered by this License. b) Accompany the Combined Work with a copy of the GNU GPL and this license document. c) For a Combined Work that displays copyright notices during execution, include the copyright notice for the Library among these notices, as well as a reference directing the user to the copies of the GNU GPL and this license document. d) Do one of the following: 0) Convey the Minimal Corresponding Source under the terms of this License, and the Corresponding Application Code in a form suitable for, and under terms that permit, the user to recombine or relink the Application with a modified version of the Linked Version to produce a modified Combined Work, in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source. 1) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (a) uses at run time a copy of the Library already present on the user's computer system, and (b) will operate properly with a modified version of the Library that is interface-compatible with the Linked Version. e) Provide Installation Information, but only if you would otherwise be required to provide such information under section 6 of the GNU GPL, and only to the extent that such information is necessary to install and execute a modified version of the Combined Work produced by recombining or relinking the Application with a modified version of the Linked Version. (If you use option 4d0, the Installation Information must accompany the Minimal Corresponding Source and Corresponding Application Code. If you use option 4d1, you must provide the Installation Information in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source.) 5. Combined Libraries. You may place library facilities that are a work based on the Library side by side in a single library together with other library facilities that are not Applications and are not covered by this License, and convey such a combined library under terms of your choice, if you do both of the following: a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities, conveyed under the terms of this License. b) Give prominent notice with the combined library that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. 6. Revised Versions of the GNU Lesser General Public License. The Free Software Foundation may publish revised and/or new versions of the GNU Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Library as you received it specifies that a certain numbered version of the GNU Lesser General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that published version or of any later version published by the Free Software Foundation. If the Library as you received it does not specify a version number of the GNU Lesser General Public License, you may choose any version of the GNU Lesser General Public License ever published by the Free Software Foundation. If the Library as you received it specifies that a proxy can decide whether future versions of the GNU Lesser General Public License shall apply, that proxy's public statement of acceptance of any version is permanent authorization for you to choose that version for the Library. ./github.com/canonical/go-sp800.90a-drbg/ctr.go0000664000000000000000000002324000000000000017233 0ustar0000000000000000// Copyright 2021 Canonical Ltd. // Licensed under the LGPLv3 with static-linking exception. // See LICENCE file for details. package drbg import ( "bytes" "crypto/aes" "encoding/binary" "errors" "fmt" "io" "math/big" "golang.org/x/xerrors" ) var ( dfKey = []byte{ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f} ) type blockCipher interface { encrypt(key, data []byte) []byte blockSize() int } type aesBlockCipherImpl struct{} var aesBlockCipher = aesBlockCipherImpl{} func (b aesBlockCipherImpl) encrypt(key, data []byte) (out []byte) { c, err := aes.NewCipher(key) if err != nil { panic(fmt.Sprintf("cannot create cipher: %v", err)) } out = make([]byte, len(data)) c.Encrypt(out, data) return } func (b aesBlockCipherImpl) blockSize() int { return aes.BlockSize } // bcc implements BCC, described in section 10.3.3 of SP800-90A. func bcc(b blockCipher, key, data []byte) []byte { if len(data)%b.blockSize() != 0 { panic("data length must be a multiple of the block length") } // 1) chaining_value = 0(x outlen) chainingValue := make([]byte, b.blockSize()) // 2) n = len (data)/outlen. n := len(data) / b.blockSize() // 3) Starting with the leftmost bits of data, split data into n blocks of // outlen bits each, forming block[1] to block[n]. blocks := make([][]byte, n) for i := 0; i < n; i++ { blocks[i] = data[i*b.blockSize() : (i+1)*b.blockSize()] } inputBlock := make([]byte, b.blockSize()) // 4) For i = 1 to n do for i := 0; i < n; i++ { for j := 0; j < len(inputBlock); j++ { // 4.1) input_block = chaining_value ⊕ block[i]. inputBlock[j] = chainingValue[j] ^ blocks[i][j] } // 4.2) chaining_value = Block_Encrypt (Key, input_block). chainingValue = b.encrypt(key, inputBlock) } return chainingValue } // block_cipher_df implements Block_Cipher_df, described in section 10.3.2 of SP800-90A. func block_cipher_df(b blockCipher, keyLen int, input []byte, requestedBytes int) []byte { // 2) L = len (input_string)/8. l := uint32(len(input)) // 3) N = number_of_bits_to_return/8. n := uint32(requestedBytes) // 4) S = L || N || input_string || 0x80. var s bytes.Buffer binary.Write(&s, binary.BigEndian, l) binary.Write(&s, binary.BigEndian, n) s.Write(input) s.Write([]byte{0x80}) // 5) While (len (S) mod outlen) ≠ 0, do // S = S || 0x00. for s.Len()%b.blockSize() != 0 { s.Write([]byte{0x00}) } // 6) temp = the Null string. var temp bytes.Buffer // 7) i = 0. i := uint32(0) // 8) K = leftmost (0x00010203...1D1E1F, keylen). k := dfKey[:keyLen] iv := make([]byte, b.blockSize()) // 9) While len(temp) < keylen + outlen, do for temp.Len() < (keyLen + b.blockSize()) { // 9.1) IV = i || 0(x (outlen - len (i))). binary.BigEndian.PutUint32(iv, i) // 9.2) temp = temp || BCC (K, (IV || S)). var data bytes.Buffer data.Write(iv) data.Write(s.Bytes()) temp.Write(bcc(b, k, data.Bytes())) // 9.3) i = i + 1. i += 1 } // 10) K = leftmost (temp, keylen). k = make([]byte, keyLen) copy(k, temp.Bytes()) // 11) X = select (temp, keylen+1, keylen+outlen). x := make([]byte, b.blockSize()) copy(x, temp.Bytes()[keyLen:]) // 12) temp = the Null string. temp.Reset() // 13) While len (temp) < number_of_bits_to_return, do for temp.Len() < requestedBytes { // 13.1) = Block_Encrypt (K, X). x = b.encrypt(k, x) // 13.2) temp = temp || X. temp.Write(x) } // 14) requested_bits = leftmost (temp, number_of_bits_to_return). return temp.Bytes()[:requestedBytes] } type ctrDRBG struct { b blockCipher v []byte key []byte reseedCounter uint64 } func (d *ctrDRBG) keyLen() int { return len(d.key) } func (d *ctrDRBG) blockSize() int { return d.b.blockSize() } func (d *ctrDRBG) seedLength() int { return d.keyLen() + d.b.blockSize() } // update implements CTR_DRBG_Update, described in section 10.2.1.2 of // SP800-90A. func (d *ctrDRBG) update(providedData []byte) { if len(providedData) != d.seedLength() { panic("provided data has the wrong length") } // 1) temp = Null var temp bytes.Buffer mod := twoExp(uint(d.blockSize() * 8)) v := new(big.Int) // 2) While (len(temp) < seedLen) do for temp.Len() < d.seedLength() { // 2.1) V = (V+1) mod 2^blocklen v.SetBytes(d.v) v.Add(v, one) v.Mod(v, mod) d.v = zeroExtendBytes(v, d.blockSize()) // 2.2) output_block = Block_Encrypt (Key, V). // 2.3) temp = temp || output_block. temp.Write(d.b.encrypt(d.key, d.v)) } // 3) temp = leftmost(temp, seedLen) temp.Truncate(d.seedLength()) // 4) temp = temp ⊕ provided_data. for i := 0; i < temp.Len(); i++ { temp.Bytes()[i] ^= providedData[i] } // 5) Key = leftmost (temp, keylen). d.key = temp.Bytes()[:d.keyLen()] // 6) V = rightmost (temp, blocklen). d.v = temp.Bytes()[d.keyLen():] } // instantiate implements CTR_DRBG_Instantiate_algorithm, described in section 10.2.1.3.2 of // SP800-90A. func (d *ctrDRBG) instantiate(entropyInput, nonce, personalization []byte, securityStrength int) { var tmp bytes.Buffer // 1) seed_material = entropy_input || nonce || personalization_string. tmp.Write(entropyInput) tmp.Write(nonce) tmp.Write(personalization) seedMaterial := tmp.Bytes() // 2) seed_material = df (seed_material, seedlen). seedMaterial = block_cipher_df(d.b, d.keyLen(), seedMaterial, d.seedLength()) // 3) Key = 0(x keylen) is done in NewCTR. // 4) V = 0(x blocklen). d.v = make([]byte, d.blockSize()) // 5) (Key, V) = CTR_DRBG_Update (seed_material, Key, V). d.update(seedMaterial) // 6) reseed_counter = 1. d.reseedCounter = 1 } // reseed implements CTR_DRBG_Reseed_algorithm, described in section 10.2.1.4.2 of // SP800-90A. func (d *ctrDRBG) reseed(entropyInput, additionalInput []byte) { var tmp bytes.Buffer // 1) seed_material = entropy_input || additional_input. tmp.Write(entropyInput) tmp.Write(additionalInput) seedMaterial := tmp.Bytes() // 2) seed_material = df (seed_material, seedlen). seedMaterial = block_cipher_df(d.b, d.keyLen(), seedMaterial, d.seedLength()) // 3) (Key, V) = CTR_DRBG_Update (seed_material, Key, V). d.update(seedMaterial) // 4) reseed_counter = 1. d.reseedCounter = 1 } // generate implements CTR_DRBG_Generate_algorithm, described in section 10.2.1.5.2 of // SP800-90A. func (d *ctrDRBG) generate(additionalInput, data []byte) error { // 1) If reseed_counter > reseed_interval, then return an indication that a // reseed is required. if d.reseedCounter > 1<<48 { return ErrReseedRequired } // 2) If (additional_input ≠ Null), then if len(additionalInput) > 0 { // 2.1) additional_input = Block_Cipher_df (additional_input, seedlen). additionalInput = block_cipher_df(d.b, d.keyLen(), additionalInput, d.seedLength()) // 2.2) (Key, V) = CTR_DRBG_Update (additional_input, Key, V). d.update(additionalInput) // Else additional_input = 0(x seedlen). } else { additionalInput = make([]byte, d.seedLength()) } // 3) temp = Null. var temp bytes.Buffer mod := twoExp(uint(d.blockSize() * 8)) v := new(big.Int) // 4) While (len (temp) < requested_number_of_bits) do: for temp.Len() < len(data) { // 4.1.2) V = (V+1) mod 2^blocklen. v.SetBytes(d.v) v.Add(v, one) v.Mod(v, mod) d.v = zeroExtendBytes(v, d.blockSize()) // 4.2) output_block = Block_Encrypt (Key, V). outputBlock := d.b.encrypt(d.key, d.v) // 4.3) temp = temp || output_block. temp.Write(outputBlock) } // 5) returned_bits = leftmost (temp, requested_number_of_bits). copy(data, temp.Bytes()) // 6) (Key, V) = CTR_DRBG_Update (additional_input, Key, V). d.update(additionalInput) // 7) reseed_counter = reseed_counter + 1. d.reseedCounter += 1 return nil } // NewCTR creates a new block cipher based DRBG as specified in section 10.2 of SP-800-90A. // The DRBG uses the AES block cipher. // // The optional personalization argument is combined with entropy input to derive the // initial seed. This argument can be used to differentiate this instantiation from others. // // The optional entropySource argument allows the default entropy source (rand.Reader from // the crypto/rand package) to be overridden. The supplied entropy source must be truly // random. func NewCTR(keyLen int, personalization []byte, entropySource io.Reader) (*DRBG, error) { switch keyLen { case 16, 24, 32: default: return nil, errors.New("invalid key size") } d := &DRBG{impl: &ctrDRBG{b: aesBlockCipher, key: make([]byte, keyLen)}} if err := d.instantiate(personalization, entropySource, keyLen); err != nil { return nil, xerrors.Errorf("cannot instantiate: %w", err) } return d, nil } // NewCTRWithExternalEntropy creates a new block cipher based DRBG as specified in // section 10.2 of SP-800-90A. The DRBG uses the AES block cipher. The entropyInput and // nonce arguments provide the initial entropy to seed the created DRBG. // // The optional personalization argument is combined with entropy input to derive the // initial seed. This argument can be used to differentiate this instantiation from others. // // The optional entropySource argument provides the entropy source for future reseeding. If // it is not supplied, then the DRBG can only be reseeded with externally supplied entropy. // The supplied entropy source must be truly random. func NewCTRWithExternalEntropy(keyLen int, entropyInput, nonce, personalization []byte, entropySource io.Reader) (*DRBG, error) { switch keyLen { case 16, 24, 32: default: return nil, errors.New("invalid key size") } d := &DRBG{impl: &ctrDRBG{b: aesBlockCipher, key: make([]byte, keyLen)}} if err := d.instantiateWithExternalEntropy(entropyInput, nonce, personalization, entropySource, keyLen); err != nil { return nil, err } return d, nil } ./github.com/canonical/go-sp800.90a-drbg/drbg.go0000664000000000000000000002045600000000000017367 0ustar0000000000000000// Copyright 2021 Canonical Ltd. // Licensed under the LGPLv3 with static-linking exception. // See LICENCE file for details. /* Package drbg implements several DRBGs as recommended by NIST SP-800-90A (see http://csrc.nist.gov/publications/nistpubs/800-90A/SP800-90A.pdf). The hash, HMAC and block cipher mode DRBGs are implemented. DRBG instances are automatically reseeded once the current seed period expires. All DRBGs are instantiated with the maximum security strength associated with the requested configuration. The security strength cannot be specified via the API. DRBGs are instantiated by default using the platform's default entropy source (via the crypto/rand package). This entropy source can be overridden, but it must provide truly random data in order to achieve the selected security strength. Note that prediction resistance is not implemented. Prediction resistance requires that the supplied entropy source is non-deterministic. */ package drbg import ( "crypto/rand" "errors" "io" "math/big" "golang.org/x/xerrors" ) // ErrReseedRequired indicates that the DRBG must be reseeded before // it can generate random bytes. var ErrReseedRequired = errors.New("the DRGB must be reseeded") var one = big.NewInt(1) func twoExp(n uint) (out *big.Int) { d := make([]byte, n/8+1) d[0] = byte(1 << (n % 8)) out = new(big.Int) out.SetBytes(d) return } func zeroExtendBytes(x *big.Int, l int) (out []byte) { out = make([]byte, l) tmp := x.Bytes() copy(out[len(out)-len(tmp):], tmp) return } func selectSecurityStrength(requested int) int { switch { case requested <= 14: return 14 case requested <= 16: return 16 case requested <= 24: return 24 default: return 32 } } type drbgImpl interface { instantiate(entropyInput, nonce, personalization []byte, securityStrength int) reseed(entropyInput, additionalInput []byte) generate(additionalInput, data []byte) error } // DRBG corresponds to an instantiated DRBG based on one of the mechanisms specified // in SP-800-90A. type DRBG struct { entropySource io.Reader securityStrength int impl drbgImpl } // instantiate implements the steps described in section 9.1 of SP800-90A. func (d *DRBG) instantiate(personalization []byte, entropySource io.Reader, securityStrength int) error { // 3) If the length of the personalization_string > max_personalization_string_length, // return (ERROR_FLAG, Invalid). if int64(len(personalization)) > 1<<32 { return errors.New("personalization too large") } d.entropySource = rand.Reader if entropySource != nil { d.entropySource = entropySource } // 4) Set security_strength to the lowest security strength greater than or equal // to requested_instantiation_security_strength from the set {112, 128, 192, 256} d.securityStrength = selectSecurityStrength(securityStrength) // 6) (status, entropy_input) = Get_entropy_input (security_strength, min_length, // max_length, prediction_resistance_request). // 7) If (status ≠ SUCCESS), return (status, Invalid). entropyInput := make([]byte, securityStrength) if _, err := d.entropySource.Read(entropyInput); err != nil { return xerrors.Errorf("cannot get entropy: %w", err) } // 8) Obtain a nonce. nonce := make([]byte, securityStrength/2) if _, err := d.entropySource.Read(nonce); err != nil { return xerrors.Errorf("cannot get nonce: %w", err) } // 9) initial_working_state = Instantiate_algorithm (entropy_input, nonce, // personalization_string, security_strength). d.impl.instantiate(entropyInput, nonce, personalization, securityStrength) return nil } func (d *DRBG) instantiateWithExternalEntropy(entropyInput, nonce, personalization []byte, entropySource io.Reader, securityStrength int) error { if len(entropyInput) < securityStrength { return errors.New("entropyInput too small") } if int64(len(entropyInput)) > 1<<32 { return errors.New("entropyInput too large") } if int64(len(personalization)) > 1<<32 { return errors.New("personalization too large") } d.entropySource = entropySource d.securityStrength = selectSecurityStrength(securityStrength) d.impl.instantiate(entropyInput, nonce, personalization, securityStrength) return nil } // ReseedWithExternalEntropy will reseed the DRBG with the supplied entropy. func (d *DRBG) ReseedWithExternalEntropy(entropyInput, additionalInput []byte) error { if int64(len(additionalInput)) > 1<<32 { return errors.New("additionalInput too large") } if len(entropyInput) < d.securityStrength { return errors.New("entropyInput too small") } if int64(len(entropyInput)) > 1<<32 { return errors.New("entropyInput too large") } d.impl.reseed(entropyInput, additionalInput) return nil } // Reseed will reseed the DRBG with additional entropy using the entropy source // it was initialized with. func (d *DRBG) Reseed(additionalInput []byte) error { // 3) If the length of the additional_input > max_additional_input_length, // return (ERROR_FLAG). if int64(len(additionalInput)) > 1<<32 { return errors.New("additionalInput too large") } if d.entropySource == nil { return errors.New("cannot reseed without external entropy") } // 4) (status, entropy_input) = Get_entropy_input (security_strength, min_length, // max_length, prediction_resistance_request). entropyInput := make([]byte, d.securityStrength) if _, err := d.entropySource.Read(entropyInput); err != nil { // 5) If (status ≠ SUCCESS), return (status). return xerrors.Errorf("cannot get entropy: %w", err) } // 6) new_working_state = Reseed_algorithm (working_state, entropy_input, // additional_input). d.impl.reseed(entropyInput, additionalInput) return nil } // Generate will fill the supplied data buffer with random bytes. // // If the DRBG needs to be reseeded before it can generate random bytes and it // has been initialized with a source of entropy, the reseed operation will be // performed automatically. If the DRBG hasn't been initialized with a source of // entropy and it needs to be reseeded, ErrNeedsReseed will be returned. // // If the length of data is greater than 65536 bytes, an error will be returned. func (d *DRBG) Generate(additionalInput, data []byte) error { // 2) If requested_number_of_bits > max_number_of_bits_per_request, then // return (ERROR_FLAG, Null). if len(data) > 65536 { return errors.New("too many bytes requested") } // 4) If the length of the additional_input > max_additional_input_length, // then return (ERROR_FLAG, Null). if int64(len(additionalInput)) > 1<<32 { return errors.New("additionalInput too large") } // 6) Clear the reseed_required_flag. reseedRequired := false for { // 7) If reseed_required_flag is set, or if prediction_resistance_request // is set, then if reseedRequired { // 7.1) status = Reseed_function (state_handle, prediction_resistance_request, // additional_input). if err := d.Reseed(additionalInput); err != nil { // 7.2) If (status ≠ SUCCESS), then return (status, Null). return xerrors.Errorf("cannot reseed: %w", err) } // 7.4) additional_input = the Null string. additionalInput = nil // 7.5) Clear the reseed_required_flag. reseedRequired = false } // 8) (status, pseudorandom_bits, new_working_state) = Generate_algorithm ( // working_state, requested_number_of_bits, additional_input). err := d.impl.generate(additionalInput, data) switch { case err == ErrReseedRequired && d.entropySource != nil: // 9) If status indicates that a reseed is required before the requested bits // can be generated, then // 9.1) Set the reseed_required_flag. // 9.3) Go to step 7. reseedRequired = true case err == ErrReseedRequired: return err case err != nil: return xerrors.Errorf("cannot generate random data: %w", err) default: return nil } } } // Read will read len(data) random bytes in to data. // // If the DRBG needs to be reseeded in order to generate all of the random bytes // and it has been initialized with a source of entropy, the reseed operation will // be performed automatically. If the DRBG hasn't been initialized with a source of // entropy and it needs to be reseeded, ErrNeedsReseed will be returned. func (d *DRBG) Read(data []byte) (int, error) { total := 0 for len(data) > 0 { b := data if len(data) > 65536 { b = data[:65536] } if err := d.Generate(nil, b); err != nil { return total, err } total += len(b) data = data[len(b):] } return total, nil } ./github.com/canonical/go-sp800.90a-drbg/go.mod0000664000000000000000000000035200000000000017221 0ustar0000000000000000module github.com/canonical/go-sp800.90a-drbg go 1.9 require ( github.com/kr/pretty v0.2.1 github.com/kr/text v0.1.0 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c ) ./github.com/canonical/go-sp800.90a-drbg/go.sum0000664000000000000000000000160500000000000017250 0ustar0000000000000000github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= ./github.com/canonical/go-sp800.90a-drbg/hash.go0000664000000000000000000001611200000000000017366 0ustar0000000000000000// Copyright 2021 Canonical Ltd. // Licensed under the LGPLv3 with static-linking exception. // See LICENCE file for details. package drbg import ( "bytes" "crypto" "encoding/binary" "fmt" "io" "math/big" "golang.org/x/xerrors" ) func seedLength(h crypto.Hash) (int, error) { switch h { case crypto.SHA1, crypto.SHA224, crypto.SHA256, crypto.SHA512_224, crypto.SHA512_256: return 55, nil case crypto.SHA384, crypto.SHA512: return 111, nil default: return 0, fmt.Errorf("unsupported digest algorithm: %v", h) } } // hashgen implements Hashgen, described in section 10.1.1.4 of // SP800-90A. func hashgen(alg crypto.Hash, v []byte, requestedBytes int) []byte { // 1) m = requested_no_of_bits / outlen. m := (requestedBytes + (alg.Size() - 1)) / alg.Size() // 2) data = V. data := v // 3) W = the Null string. var W bytes.Buffer mod := twoExp(uint(len(v) * 8)) h := alg.New() tmp := new(big.Int) // 4) For i = 1 to m for i := 1; i <= m; i++ { // 4.1) w = Hash (data). h.Reset() h.Write(data) w := h.Sum(nil) // 4.2) W = W || w. W.Write(w) // 4.3) data = (data + 1) mod 2^seedlen. tmp.SetBytes(data) tmp.Add(tmp, one) tmp.Mod(tmp, mod) data = zeroExtendBytes(tmp, len(v)) } // 5) returned_bits = leftmost (W, requested_no_of_bits). return W.Bytes()[:requestedBytes] } // hash_df implements the Hash_df function described in section 10.3.1 of // SP800-90A. func hash_df(alg crypto.Hash, input []byte, requestedBytes int) []byte { // 1) temp = the Null string. var temp bytes.Buffer // 2) len = no_of_bits_to_return / outlen. n := (requestedBytes + (alg.Size() - 1)) / alg.Size() if n > 0xff { panic("invalid requested bytes") } // 3) counter = 0x01. counter := uint8(1) h := alg.New() requestedBits := uint32(requestedBytes * 8) // 4) For i = 1 to len do for i := 1; i <= n; i++ { // 4.1) temp = temp || Hash (counter || no_of_bits_to_return || input_string). h.Reset() h.Write([]byte{counter}) binary.Write(h, binary.BigEndian, requestedBits) h.Write(input) temp.Write(h.Sum(nil)) // 4.2) counter = counter + 1. counter += 1 } // 5) requested_bits = leftmost (temp, no_of_bits_to_return). return temp.Bytes()[:requestedBytes] } type hashDRBG struct { h crypto.Hash v []byte c []byte reseedCounter uint64 } func (d *hashDRBG) seedLen() int { return len(d.v) } // instantiate implements Hash_DRBG_Instantiate_algorithm, described in section 10.1.1.2 of // SP800-90A. func (d *hashDRBG) instantiate(entropyInput, nonce, personalization []byte, securityStrength int) { // 1) seed_material = entropy_input || nonce || personalization_string. var seedMaterial bytes.Buffer seedMaterial.Write(entropyInput) seedMaterial.Write(nonce) seedMaterial.Write(personalization) // 2) seed = Hash_df (seed_material, seedlen). seed := hash_df(d.h, seedMaterial.Bytes(), d.seedLen()) // 3) V = seed. d.v = seed // 4) C = Hash_df ((0x00 || V), seedlen). d.c = hash_df(d.h, append([]byte{0x00}, d.v...), d.seedLen()) // 5) reseed_counter = 1. d.reseedCounter = 1 } // reseed implements Hash_DRBG_Reseed_algorithm, described in section 10.1.1.3 of // SP800-90A. func (d *hashDRBG) reseed(entropyInput, additionalInput []byte) { // 1) seed_material = 0x01 || V || entropy_input || additional_input. var seedMaterial bytes.Buffer seedMaterial.Write([]byte{0x01}) seedMaterial.Write(d.v) seedMaterial.Write(entropyInput) seedMaterial.Write(additionalInput) // 2) seed = Hash_df (seed_material, seedlen). seed := hash_df(d.h, seedMaterial.Bytes(), d.seedLen()) // 3) V = seed. d.v = seed // 4) C = Hash_df ((0x00 || V), seedlen). d.c = hash_df(d.h, append([]byte{0x00}, seed...), d.seedLen()) // 5) reseed_counter = 1. d.reseedCounter = 1 } // generate implements Hash_DRBG_Generate_algorithm, described in section 10.1.1.4 of // SP800-90A. func (d *hashDRBG) generate(additionalInput, data []byte) error { // 1) If reseed_counter > reseed_interval, then return an indication that a reseed // is required. if d.reseedCounter > 1<<48 { return ErrReseedRequired } mod := twoExp(uint(d.seedLen() * 8)) // 2) If (additional_input ≠ Null), then do if len(additionalInput) > 0 { // 2.1) w = Hash (0x02 || V || additional_input). h := d.h.New() h.Write([]byte{0x02}) h.Write(d.v) h.Write(additionalInput) w := new(big.Int).SetBytes(h.Sum(nil)) // 2.2) V = (V + w) mod 2^seedlen. v := new(big.Int).SetBytes(d.v) v.Add(v, w) v.Mod(v, mod) d.v = zeroExtendBytes(v, d.seedLen()) } // 3) (returned_bits) = Hashgen (requested_number_of_bits, V). returnedBytes := hashgen(d.h, d.v, len(data)) copy(data, returnedBytes) // 4) H = Hash (0x03 || V). hash := d.h.New() hash.Write([]byte{0x03}) hash.Write(d.v) h := hash.Sum(nil) // 5) V = (V + H + C + reseed_counter) mod 2^seedlen. v := new(big.Int).SetBytes(d.v) v.Add(v, new(big.Int).SetBytes(h)) v.Add(v, new(big.Int).SetBytes(d.c)) v.Add(v, big.NewInt(int64(d.reseedCounter))) v.Mod(v, mod) d.v = zeroExtendBytes(v, d.seedLen()) // 6) reseed_counter = reseed_counter + 1. d.reseedCounter += 1 return nil } // NewHash creates a new hash based DRBG as specified in section 10.1.1 of SP-800-90A. // The DRBG uses the supplied hash algorithm. // // The optional personalization argument is combined with entropy input to derive the // initial seed. This argument can be used to differentiate this instantiation from others. // // The optional entropySource argument allows the default entropy source (rand.Reader from // the crypto/rand package) to be overridden. The supplied entropy source must be truly // random. func NewHash(h crypto.Hash, personalization []byte, entropySource io.Reader) (*DRBG, error) { seedLen, err := seedLength(h) if err != nil { return nil, xerrors.Errorf("cannot compute seed length: %w", err) } d := &DRBG{impl: &hashDRBG{h: h, v: make([]byte, seedLen)}} if err := d.instantiate(personalization, entropySource, h.Size()/2); err != nil { return nil, xerrors.Errorf("cannot instantiate: %w", err) } return d, nil } // NewHashWithExternalEntropy creates a new hash based DRBG as specified in section // 10.1.1 of SP-800-90A. The DRBG uses the supplied hash algorithm. The entropyInput and // nonce arguments provide the initial entropy to seed the created DRBG. // // The optional personalization argument is combined with entropy input to derive the // initial seed. This argument can be used to differentiate this instantiation from others. // // The optional entropySource argument provides the entropy source for future reseeding. If // it is not supplied, then the DRBG can only be reseeded with externally supplied entropy. // The supplied entropy source must be truly random. func NewHashWithExternalEntropy(h crypto.Hash, entropyInput, nonce, personalization []byte, entropySource io.Reader) (*DRBG, error) { seedLen, err := seedLength(h) if err != nil { return nil, xerrors.Errorf("cannot compute seed length: %w", err) } d := &DRBG{impl: &hashDRBG{h: h, v: make([]byte, seedLen)}} if err := d.instantiateWithExternalEntropy(entropyInput, nonce, personalization, entropySource, h.Size()/2); err != nil { return nil, err } return d, nil } ./github.com/canonical/go-sp800.90a-drbg/hmac.go0000664000000000000000000001224500000000000017356 0ustar0000000000000000// Copyright 2021 Canonical Ltd. // Licensed under the LGPLv3 with static-linking exception. // See LICENCE file for details. package drbg import ( "bytes" "crypto" "crypto/hmac" "hash" "io" "golang.org/x/xerrors" ) type hmacDRBG struct { h crypto.Hash v []byte key []byte reseedCounter uint64 } // update implements HMAC_DRBG_Update, described in section 10.1.2.2 of // SP800-90A. func (d *hmacDRBG) update(providedData []byte) { // 1) K = HMAC (K, V || 0x00 || provided_data). h := hmac.New(func() hash.Hash { return d.h.New() }, d.key) h.Write(d.v) h.Write([]byte{0x00}) h.Write(providedData) d.key = h.Sum(nil) // 2) V = HMAC (K, V). h = hmac.New(func() hash.Hash { return d.h.New() }, d.key) h.Write(d.v) d.v = h.Sum(nil) // 3) If (provided_data = Null), then return K and V. if len(providedData) == 0 { return } // 4) K = HMAC (K, V || 0x01 || provided_data). h = hmac.New(func() hash.Hash { return d.h.New() }, d.key) h.Write(d.v) h.Write([]byte{0x01}) h.Write(providedData) d.key = h.Sum(nil) // 5) V = HMAC (K, V). h = hmac.New(func() hash.Hash { return d.h.New() }, d.key) h.Write(d.v) d.v = h.Sum(nil) } // instantiate implements HMAC_DRBG_Instantiate_algorithm, described in section 10.1.2.3 of // SP800-90A. func (d *hmacDRBG) instantiate(entropyInput, nonce, personalization []byte, securityStrength int) { // 1) seed_material = entropy_input || nonce || personalization_string. var seedMaterial bytes.Buffer seedMaterial.Write(entropyInput) seedMaterial.Write(nonce) seedMaterial.Write(personalization) // 2) Key = 0x00 00...00. Comment: outlen bits. d.key = make([]byte, d.h.Size()) // 3) V = 0x01 01...01. Comment: outlen bits. d.v = make([]byte, d.h.Size()) for i := range d.v { d.v[i] = 0x01 } // 4) (Key, V) = HMAC_DRBG_Update (seed_material, Key, V). d.update(seedMaterial.Bytes()) // 5) reseed_counter = 1. d.reseedCounter = 1 } // reseed implements HMAC_DRBG_Reseed_algorithm, described in section 10.1.2.4 of // SP800-90A. func (d *hmacDRBG) reseed(entropyInput, additionalInput []byte) { // 1) seed_material = entropy_input || additional_input. var seedMaterial bytes.Buffer seedMaterial.Write(entropyInput) seedMaterial.Write(additionalInput) // 2) (Key, V) = HMAC_DRBG_Update (seed_material, Key, V). d.update(seedMaterial.Bytes()) // 3) reseed_counter = 1. d.reseedCounter = 1 } // generate implements HMAC_DRBG_Generate_algorithm, described in section 10.1.2.5 of // SP800-90A. func (d *hmacDRBG) generate(additionalInput, data []byte) error { // 1) If reseed_counter > reseed_interval, then return an indication that a // reseed is required. if d.reseedCounter > 1<<48 { return ErrReseedRequired } // 2) If additional_input ≠ Null, then // (Key, V) = HMAC_DRBG_Update (additional_input, Key, V). if len(additionalInput) > 0 { d.update(additionalInput) } // 3) temp = Null. var temp bytes.Buffer h := hmac.New(func() hash.Hash { return d.h.New() }, d.key) // 4) While (len (temp) < requested_number_of_bits) do: for temp.Len() < len(data) { // 4.1) V = HMAC (Key , V). h.Reset() h.Write(d.v) d.v = h.Sum(nil) // 4.2) temp = temp || V. temp.Write(d.v) } // 5) returned_bits = leftmost (temp, requested_number_of_bits). copy(data, temp.Bytes()) // 6) (Key, V) = HMAC_DRBG_Update (additional_input, Key, V). d.update(additionalInput) // 7) reseed_counter = reseed_counter + 1. d.reseedCounter += 1 return nil } // NewHMAC creates a new HMAC based DRBG as specified in section 10.1.2 of SP-800-90A. // The DRBG uses the supplied hash algorithm. // // The optional personalization argument is combined with entropy input to derive the // initial seed. This argument can be used to differentiate this instantiation from others. // // The optional entropySource argument allows the default entropy source (rand.Reader from // the crypto/rand package) to be overridden. The supplied entropy source must be truly // random. func NewHMAC(h crypto.Hash, personalization []byte, entropySource io.Reader) (*DRBG, error) { d := &DRBG{impl: &hmacDRBG{h: h}} if err := d.instantiate(personalization, entropySource, h.Size()/2); err != nil { return nil, xerrors.Errorf("cannot instantiate: %w", err) } return d, nil } // NewHMACWithExternalEntropy creates a new hash based DRBG as specified in section // 10.1.2 of SP-800-90A. The DRBG uses the supplied hash algorithm. The entropyInput and // nonce arguments provide the initial entropy to seed the created DRBG. // // The optional personalization argument is combined with entropy input to derive the // initial seed. This argument can be used to differentiate this instantiation from others. // // The optional entropySource argument provides the entropy source for future reseeding. If // it is not supplied, then the DRBG can only be reseeded with externally supplied entropy. // The supplied entropy source must be truly random. func NewHMACWithExternalEntropy(h crypto.Hash, entropyInput, nonce, personalization []byte, entropySource io.Reader) (*DRBG, error) { d := &DRBG{impl: &hmacDRBG{h: h}} if err := d.instantiateWithExternalEntropy(entropyInput, nonce, personalization, entropySource, h.Size()/2); err != nil { return nil, err } return d, nil } ./github.com/canonical/go-tpm2/0000775000000000000000000000000000000000000014617 5ustar0000000000000000./github.com/canonical/go-tpm2/.gitignore0000664000000000000000000000003600000000000016606 0ustar0000000000000000go-tpm2.test NVChip vendor/*/ ./github.com/canonical/go-tpm2/.travis.yml0000664000000000000000000000030400000000000016725 0ustar0000000000000000language: go dist: xenial install: - sudo snap install core core18 - sudo snap install --edge tpm2-simulator-chrisccoulson - ./get-deps script: - ./run-tests --with-mssim - go vet ./... ./github.com/canonical/go-tpm2/LICENSE0000664000000000000000000002150100000000000015623 0ustar0000000000000000All files in this repository are licensed as follows. If you contribute to this repository, it is assumed that you license your contribution under the same license unless you state otherwise. All files Copyright (C) 2019 Canonical Ltd. unless otherwise specified in the file. This software is licensed under the LGPLv3, included below. As a special exception to the GNU Lesser General Public License version 3 ("LGPL3"), the copyright holders of this Library give you permission to convey to a third party a Combined Work that links statically or dynamically to this Library without providing any Minimal Corresponding Source or Minimal Application Code as set out in 4d or providing the installation information set out in section 4e, provided that you comply with the other provisions of LGPL3 and provided that you meet, for the Application the terms and conditions of the license(s) which apply to the Application. Except as stated in this special exception, the provisions of LGPL3 will continue to comply in full to this Library. If you modify this Library, you may apply this exception to your version of this Library, but you are not obliged to do so. If you do not wish to do so, delete this exception statement from your version. This exception does not (and cannot) modify any license terms which apply to the Application, with which you must still comply. GNU LESSER GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. This version of the GNU Lesser General Public License incorporates the terms and conditions of version 3 of the GNU General Public License, supplemented by the additional permissions listed below. 0. Additional Definitions. As used herein, "this License" refers to version 3 of the GNU Lesser General Public License, and the "GNU GPL" refers to version 3 of the GNU General Public License. "The Library" refers to a covered work governed by this License, other than an Application or a Combined Work as defined below. An "Application" is any work that makes use of an interface provided by the Library, but which is not otherwise based on the Library. Defining a subclass of a class defined by the Library is deemed a mode of using an interface provided by the Library. A "Combined Work" is a work produced by combining or linking an Application with the Library. The particular version of the Library with which the Combined Work was made is also called the "Linked Version". The "Minimal Corresponding Source" for a Combined Work means the Corresponding Source for the Combined Work, excluding any source code for portions of the Combined Work that, considered in isolation, are based on the Application, and not on the Linked Version. The "Corresponding Application Code" for a Combined Work means the object code and/or source code for the Application, including any data and utility programs needed for reproducing the Combined Work from the Application, but excluding the System Libraries of the Combined Work. 1. Exception to Section 3 of the GNU GPL. You may convey a covered work under sections 3 and 4 of this License without being bound by section 3 of the GNU GPL. 2. Conveying Modified Versions. If you modify a copy of the Library, and, in your modifications, a facility refers to a function or data to be supplied by an Application that uses the facility (other than as an argument passed when the facility is invoked), then you may convey a copy of the modified version: a) under this License, provided that you make a good faith effort to ensure that, in the event an Application does not supply the function or data, the facility still operates, and performs whatever part of its purpose remains meaningful, or b) under the GNU GPL, with none of the additional permissions of this License applicable to that copy. 3. Object Code Incorporating Material from Library Header Files. The object code form of an Application may incorporate material from a header file that is part of the Library. You may convey such object code under terms of your choice, provided that, if the incorporated material is not limited to numerical parameters, data structure layouts and accessors, or small macros, inline functions and templates (ten or fewer lines in length), you do both of the following: a) Give prominent notice with each copy of the object code that the Library is used in it and that the Library and its use are covered by this License. b) Accompany the object code with a copy of the GNU GPL and this license document. 4. Combined Works. You may convey a Combined Work under terms of your choice that, taken together, effectively do not restrict modification of the portions of the Library contained in the Combined Work and reverse engineering for debugging such modifications, if you also do each of the following: a) Give prominent notice with each copy of the Combined Work that the Library is used in it and that the Library and its use are covered by this License. b) Accompany the Combined Work with a copy of the GNU GPL and this license document. c) For a Combined Work that displays copyright notices during execution, include the copyright notice for the Library among these notices, as well as a reference directing the user to the copies of the GNU GPL and this license document. d) Do one of the following: 0) Convey the Minimal Corresponding Source under the terms of this License, and the Corresponding Application Code in a form suitable for, and under terms that permit, the user to recombine or relink the Application with a modified version of the Linked Version to produce a modified Combined Work, in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source. 1) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (a) uses at run time a copy of the Library already present on the user's computer system, and (b) will operate properly with a modified version of the Library that is interface-compatible with the Linked Version. e) Provide Installation Information, but only if you would otherwise be required to provide such information under section 6 of the GNU GPL, and only to the extent that such information is necessary to install and execute a modified version of the Combined Work produced by recombining or relinking the Application with a modified version of the Linked Version. (If you use option 4d0, the Installation Information must accompany the Minimal Corresponding Source and Corresponding Application Code. If you use option 4d1, you must provide the Installation Information in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source.) 5. Combined Libraries. You may place library facilities that are a work based on the Library side by side in a single library together with other library facilities that are not Applications and are not covered by this License, and convey such a combined library under terms of your choice, if you do both of the following: a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities, conveyed under the terms of this License. b) Give prominent notice with the combined library that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. 6. Revised Versions of the GNU Lesser General Public License. The Free Software Foundation may publish revised and/or new versions of the GNU Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Library as you received it specifies that a certain numbered version of the GNU Lesser General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that published version or of any later version published by the Free Software Foundation. If the Library as you received it does not specify a version number of the GNU Lesser General Public License, you may choose any version of the GNU Lesser General Public License ever published by the Free Software Foundation. If the Library as you received it specifies that a proxy can decide whether future versions of the GNU Lesser General Public License shall apply, that proxy's public statement of acceptance of any version is permanent authorization for you to choose that version for the Library. ./github.com/canonical/go-tpm2/README.md0000664000000000000000000000445600000000000016107 0ustar0000000000000000# go-tpm2 [![Tests](https://github.com/canonical/go-tpm2/workflows/Tests/badge.svg)](https://github.com/canonical/go-tpm2/actions?query=workflow%3ATests) [![GoDoc](https://godoc.org/github.com/canonical/go-tpm2?status.svg)](https://godoc.org/github.com/canonical/go-tpm2) This repository contains a go library for interacting with TPM 2.0 devices. Some currently supported features are: - All authorization modes: cleartext password, HMAC session based and policy session based. - All session configurations: salted or unsalted + bound or unbound. - Session-based command and response parameter encryption using AES-CFB or XOR obfuscation. - Session-based command auditing. - Backends for Linux TPM character devices and TPM simulators implementing the Microsoft TPM 2.0 simulator interface. The current support status for each command group is detailed below. Command group | Support | Comment --- | --- | --- Start-up | Full | Testing | Full | Session Commands | Full | Object Commands | Full | Duplication Commands | Partial | TPM2_Duplicate and TPM2_Import are supported Asymmetric Primitives | None | Symmetric Primitives | None | Random Number Generator | Full | Hash/HMAC/Event Sequences | Full | Attestation Commands | Full | Ephemeral EC Keys | None | Signing and Signature Verification | Full | Command Audit | Full | Integrity Collection (PCR) | Partial | TPM2_PCR_Extend, TPM2_PCR_Event, TPM2_PCR_Read and TPM2_PCR_Reset are supported Enhanced Authorization (EA) Commands | Partial | All commands are supported except for TPM2_PolicyLocality, TPM2_PolicyPhysicalPresence, TPM2_PolicyTemplate and TPM2_PolicyAuthorizeNV Hierarchy Commands | Partial | TPM2_CreatePrimary, TPM2_HierarchyControl, TPM2_Clear, TPM2_ClearControl and TPM2_HierarchyChangeAuth are supported Dictionary Attack Functions | Full | Miscellaneous Management Functions | None | Field Upgrade | None | Context Management | Full | Clocks and Timers | Partial | TPM2_ReadClock is supported Capability Commands | Full | Non-Volatile Storage | Partial | All commands are supported except for TPM2_NV_Certify Vendor Specific | None | ## Relevant links - [TPM 2.0 Library Specification](https://trustedcomputinggroup.org/resource/tpm-library-specification/) - [IBM's Software TPM 2.0](https://sourceforge.net/projects/ibmswtpm2/) ./github.com/canonical/go-tpm2/auth.go0000664000000000000000000002116000000000000016107 0ustar0000000000000000// Copyright 2019 Canonical Ltd. // Licensed under the LGPLv3 with static-linking exception. // See LICENCE file for details. package tpm2 import ( "bytes" "crypto/hmac" "errors" "fmt" "hash" ) type policyHMACType uint8 const ( policyHMACTypeNoAuth policyHMACType = iota policyHMACTypeAuth policyHMACTypePassword policyHMACTypeMax = policyHMACTypePassword ) type sessionParam struct { session *sessionContext // The session instance used for this session parameter - will be nil for a password authorization associatedContext ResourceContext // The resource associated with an authorization - can be nil includeAuthValue bool // Whether the authorization value of associatedContext is included in the HMAC key decryptNonce Nonce encryptNonce Nonce } func (s *sessionParam) IsAuth() bool { return s.associatedContext != nil } func (s *sessionParam) ComputeSessionHMACKey() []byte { var key []byte key = append(key, s.session.Data().SessionKey...) if s.includeAuthValue { key = append(key, s.associatedContext.(resourceContextPrivate).GetAuthValue()...) } return key } func (s *sessionParam) computeHMAC(pHash []byte, nonceNewer, nonceOlder, nonceDecrypt, nonceEncrypt Nonce, attrs SessionAttributes) ([]byte, bool) { key := s.ComputeSessionHMACKey() h := hmac.New(func() hash.Hash { return s.session.Data().HashAlg.NewHash() }, key) h.Write(pHash) h.Write(nonceNewer) h.Write(nonceOlder) h.Write(nonceDecrypt) h.Write(nonceEncrypt) h.Write([]byte{uint8(attrs)}) return h.Sum(nil), len(key) > 0 } func (s *sessionParam) computeCommandHMAC(commandCode CommandCode, commandHandles []Name, cpBytes []byte) []byte { data := s.session.Data() cpHash := ComputeCpHash(data.HashAlg, commandCode, commandHandles, cpBytes) h, _ := s.computeHMAC(cpHash, data.NonceCaller, data.NonceTPM, s.decryptNonce, s.encryptNonce, s.session.attrs.canonicalize()) return h } func (s *sessionParam) buildCommandSessionAuth(commandCode CommandCode, commandHandles []Name, cpBytes []byte) *AuthCommand { data := s.session.Data() var hmac []byte if data.SessionType == SessionTypePolicy && data.PolicyHMACType == policyHMACTypePassword { // Policy session that contains a TPM2_PolicyPassword assertion. The HMAC is just the authorization value // of the resource being authorized. if s.IsAuth() { hmac = s.associatedContext.(resourceContextPrivate).GetAuthValue() } } else { hmac = s.computeCommandHMAC(commandCode, commandHandles, cpBytes) } return &AuthCommand{ SessionHandle: s.session.Handle(), Nonce: data.NonceCaller, SessionAttributes: s.session.attrs.canonicalize(), HMAC: hmac} } func (s *sessionParam) buildCommandPasswordAuth() *AuthCommand { return &AuthCommand{SessionHandle: HandlePW, SessionAttributes: AttrContinueSession, HMAC: s.associatedContext.(resourceContextPrivate).GetAuthValue()} } func (s *sessionParam) buildCommandAuth(commandCode CommandCode, commandHandles []Name, cpBytes []byte) *AuthCommand { if s.session == nil { // Cleartext password session return s.buildCommandPasswordAuth() } // HMAC or policy session return s.buildCommandSessionAuth(commandCode, commandHandles, cpBytes) } func (s *sessionParam) computeResponseHMAC(resp AuthResponse, responseCode ResponseCode, commandCode CommandCode, rpBytes []byte) ([]byte, bool) { data := s.session.Data() rpHash := cryptComputeRpHash(data.HashAlg, responseCode, commandCode, rpBytes) return s.computeHMAC(rpHash, data.NonceTPM, data.NonceCaller, nil, nil, resp.SessionAttributes) } func (s *sessionParam) processResponseAuth(resp AuthResponse, responseCode ResponseCode, commandCode CommandCode, rpBytes []byte) error { if s.session == nil { return nil } data := s.session.Data() data.NonceTPM = resp.Nonce data.IsAudit = resp.SessionAttributes&AttrAudit > 0 data.IsExclusive = resp.SessionAttributes&AttrAuditExclusive > 0 if data.SessionType == SessionTypePolicy && data.PolicyHMACType == policyHMACTypePassword { if len(resp.HMAC) != 0 { return errors.New("non-zero length HMAC for policy session with PolicyPassword assertion") } return nil } hmac, hmacRequired := s.computeResponseHMAC(resp, responseCode, commandCode, rpBytes) if (hmacRequired || len(resp.HMAC) > 0) && !bytes.Equal(hmac, resp.HMAC) { return errors.New("incorrect HMAC") } return nil } func computeBindName(name Name, auth Auth) Name { if len(auth) > len(name) { auth = auth[0:len(name)] } r := make(Name, len(name)) copy(r, name) j := 0 for i := len(name) - len(auth); i < len(name); i++ { r[i] ^= auth[j] j++ } return r } type sessionParams struct { commandCode CommandCode sessions []*sessionParam } func (p *sessionParams) findSessionWithAttr(attr SessionAttributes) (*sessionParam, int) { for i, session := range p.sessions { if session.session == nil { continue } if session.session.attrs.canonicalize()&attr > 0 { return session, i } } return nil, 0 } func (p *sessionParams) validateAndAppend(s *sessionParam) error { if len(p.sessions) >= 3 { return errors.New("too many session parameters") } if s.session != nil { data := s.session.Data() if data == nil { return errors.New("invalid context for session: incomplete session can only be used in TPMContext.FlushContext") } switch data.SessionType { case SessionTypeHMAC: switch { case !s.IsAuth(): // HMAC session not used for authorization case !data.IsBound: // A non-bound HMAC session used for authorization. Include the auth value of the associated // ResourceContext in the HMAC key s.includeAuthValue = true default: // A bound HMAC session used for authorization. Include the auth value of the associated // ResourceContext only if it is not the bind entity. bindName := computeBindName(s.associatedContext.Name(), s.associatedContext.(resourceContextPrivate).GetAuthValue()) s.includeAuthValue = !bytes.Equal(bindName, data.BoundEntity) } case SessionTypePolicy: // A policy session that includes a TPM2_PolicyAuthValue assertion. Include the auth value of the associated // ResourceContext. switch { case !s.IsAuth(): // This is actually an invalid case, but just let the TPM return the appropriate error default: s.includeAuthValue = data.PolicyHMACType == policyHMACTypeAuth } } } p.sessions = append(p.sessions, s) return nil } func (p *sessionParams) validateAndAppendAuth(in ResourceContextWithSession) error { sc, _ := in.Session.(*sessionContext) associatedContext := in.Context if associatedContext == nil { associatedContext = makePermanentContext(HandleNull) } s := &sessionParam{associatedContext: associatedContext, session: sc} return p.validateAndAppend(s) } func (p *sessionParams) validateAndAppendExtra(in []SessionContext) error { for _, s := range in { if s == nil { continue } if err := p.validateAndAppend(&sessionParam{session: s.(*sessionContext)}); err != nil { return err } } return nil } func (p *sessionParams) computeCallerNonces() error { for _, s := range p.sessions { if s.session == nil { continue } if err := cryptComputeNonce(s.session.Data().NonceCaller); err != nil { return fmt.Errorf("cannot compute new caller nonce: %v", err) } } return nil } func (p *sessionParams) buildCommandAuthArea(commandCode CommandCode, commandHandles []Name, cpBytes []byte) ([]AuthCommand, error) { if err := p.computeCallerNonces(); err != nil { return nil, fmt.Errorf("cannot compute caller nonces: %v", err) } if err := p.encryptCommandParameter(cpBytes); err != nil { return nil, fmt.Errorf("cannot encrypt first command parameter: %v", err) } p.computeEncryptNonce() p.commandCode = commandCode var area []AuthCommand for _, s := range p.sessions { a := s.buildCommandAuth(commandCode, commandHandles, cpBytes) area = append(area, *a) } return area, nil } func (p *sessionParams) invalidateSessionContexts(authResponses []AuthResponse) { for i, resp := range authResponses { session := p.sessions[i].session if session == nil { continue } if resp.SessionAttributes&AttrContinueSession != 0 { continue } session.invalidate() } } func (p *sessionParams) processResponseAuthArea(authResponses []AuthResponse, responseCode ResponseCode, rpBytes []byte) error { defer p.invalidateSessionContexts(authResponses) for i, resp := range authResponses { if err := p.sessions[i].processResponseAuth(resp, responseCode, p.commandCode, rpBytes); err != nil { return fmt.Errorf("encountered an error for session at index %d: %v", i, err) } } if err := p.decryptResponseParameter(rpBytes); err != nil { return fmt.Errorf("cannot decrypt first response parameter: %v", err) } return nil } ./github.com/canonical/go-tpm2/cmds_attestation.go0000664000000000000000000003565600000000000020532 0ustar0000000000000000// Copyright 2019 Canonical Ltd. // Licensed under the LGPLv3 with static-linking exception. // See LICENCE file for details. package tpm2 import ( "github.com/canonical/go-tpm2/mu" ) // Section 18 - Attestation Commands // Certify executes the TPM2_Certify command, which is used to prove that an object with a specific name is loaded in to the TPM. // By producing an attestation, the TPM certifies that the object with a given name is loaded in to the TPM and consistent with a // valid sensitive area. // // The objectContext parameter corresponds to the object for which to produce an attestation. The command requires authorization with // the admin role for objectContext, with session based authorization provided via objectContextAuthSession. // // If signContext is not nil, the returned attestation will be signed by the key associated with it. This command requires // authorization with the user auth role for signContext, with session based authorization provided via signContextAuthSession. // // If signContext is not nil and the object associated with signContext is not a signing key, a *TPMHandleError error with an error // code of ErrorKey will be returned for handle index 2. // // If signContext is not nil and if the scheme of the key associated with signContext is AsymSchemeNull, then inScheme must be // provided to specify a valid signing scheme for the key. If it isn't, a *TPMParameterError error with an error code of ErrorScheme // will be returned for parameter index 2. // // If signContext is not nil and the scheme of the key associated with signContext is not AsymSchemeNull, then inScheme may be nil. If // it is provided, then the specified scheme must match that of the signing key, else a *TPMParameterError error with an error code of // ErrorScheme will be returned for parameter index 2. // // On successful, it returns an attestation structure detailing the name of the object associated with objectContext. If signContext // is not nil, the attestation structure will be signed by the associated key and returned too. func (t *TPMContext) Certify(objectContext, signContext ResourceContext, qualifyingData Data, inScheme *SigScheme, objectContextAuthSession, signContextAuthSession SessionContext, sessions ...SessionContext) (certifyInfo *Attest, signature *Signature, err error) { if inScheme == nil { inScheme = &SigScheme{Scheme: SigSchemeAlgNull} } if err := t.RunCommand(CommandCertify, sessions, ResourceContextWithSession{Context: objectContext, Session: objectContextAuthSession}, ResourceContextWithSession{Context: signContext, Session: signContextAuthSession}, Delimiter, qualifyingData, inScheme, Delimiter, Delimiter, mu.Sized(&certifyInfo), &signature); err != nil { return nil, nil, err } return certifyInfo, signature, nil } // CertifyCreation executes the TPM2_CertifyCreation command, which is used to prove the association between the object represented // by objectContext and its creation data represented by creationHash. It does this by computing a ticket from creationHash and the // name of the object represented by objectContext and then verifying that it matches the provided creationTicket, which was provided // by the TPM at object creation time. // // If signContext is not nil, the returned attestation will be signed by the key associated with it. This command requires // authorization with the user auth role for signContext, with session based authorization provided via signContextAuthSession. // // If signContext is not nil and the object associated with signContext is not a signing key, a *TPMHandleError error with an error // code of ErrorKey will be returned for handle index 1. // // If signContext is not nil and if the scheme of the key associated with signContext is AsymSchemeNull, then inScheme must be // provided to specify a valid signing scheme for the key. If it isn't, a *TPMParameterError error with an error code of ErrorScheme // will be returned for parameter index 3. // // If signContext is not nil and the scheme of the key associated with signContext is not AsymSchemeNull, then inScheme may be nil. If // it is provided, then the specified scheme must match that of the signing key, else a *TPMParameterError error with an error code of // ErrorScheme will be returned for parameter index 3. // // If creationTicket corresponds to an invalid ticket, a *TPMParameterError error with an error code of ErrorTicket will be returned // for parameter index 4. // // If the digest generated for signing is greater than or has a larger size than the modulus of the key associated with signContext, a // *TPMError with an error code of ErrorValue will be returned. // // If successful, it returns an attestation structure. If signContext is not nil, the attestation structure will be signed by the // associated key and returned too. func (t *TPMContext) CertifyCreation(signContext, objectContext ResourceContext, qualifyingData Data, creationHash Digest, inScheme *SigScheme, creationTicket *TkCreation, signContextAuthSession SessionContext, sessions ...SessionContext) (certifyInfo *Attest, signature *Signature, err error) { if inScheme == nil { inScheme = &SigScheme{Scheme: SigSchemeAlgNull} } if err := t.RunCommand(CommandCertifyCreation, sessions, ResourceContextWithSession{Context: signContext, Session: signContextAuthSession}, objectContext, Delimiter, qualifyingData, creationHash, inScheme, creationTicket, Delimiter, Delimiter, mu.Sized(&certifyInfo), &signature); err != nil { return nil, nil, err } return certifyInfo, signature, nil } // Quote executes the TPM2_Quote command in order to quote a set of PCR values. The TPM will hash the set of PCRs specified by the // pcrs parameter. // // If signContext is not nil, the returned attestation will be signed by the key associated with it. This command requires // authorization with the user auth role for signContext, with session based authorization provided via signContextAuthSession. // // If signContext is not nil and the object associated with signContext is not a signing key, a *TPMHandleError error with an error // code of ErrorKey will be returned for handle index 1. // // If signContext is not nil and if the scheme of the key associated with signContext is AsymSchemeNull, then inScheme must be // provided to specify a valid signing scheme for the key. If it isn't, a *TPMParameterError error with an error code of ErrorScheme // will be returned for parameter index 2. // // If signContext is not nil and the scheme of the key associated with signContext is not AsymSchemeNull, then inScheme may be nil. If // it is provided, then the specified scheme must match that of the signing key, else a *TPMParameterError error with an error code of // ErrorScheme will be returned for parameter index 2. // // On successful, it returns an attestation structure containing the hash of the PCRs selected by the pcrs parameter. If signContext // is not nil, the attestation structure will be signed by the associated key and returned too. func (t *TPMContext) Quote(signContext ResourceContext, qualifyingData Data, inScheme *SigScheme, pcrs PCRSelectionList, signContextAuthSession SessionContext, sessions ...SessionContext) (quoted *Attest, signature *Signature, err error) { if inScheme == nil { inScheme = &SigScheme{Scheme: SigSchemeAlgNull} } if err := t.RunCommand(CommandQuote, sessions, ResourceContextWithSession{Context: signContext, Session: signContextAuthSession}, Delimiter, qualifyingData, inScheme, pcrs, Delimiter, Delimiter, mu.Sized("ed), &signature); err != nil { return nil, nil, err } return quoted, signature, nil } // GetSessionAuditDigest executes the TPM2_GetSessionAuditDigest to obtain the current digest of the audit session corresponding to // sessionContext. // // The privacyAdminContext argument must be a ResourceContext that corresponds to HandleEndorsement. This command requires authorization // with the user auth role for privacyAdminContext, with session based authorization provided via privacyAdminContextAuthSession. // // If signContext is not nil, the returned attestation will be signed by the key associated with it. This command requires // authorization with the user auth role for signContext, with session based authorization provided via signContextAuthSession. // // If signContext is not nil and the object associated with signContext is not a signing key, a *TPMHandleError error with an error // code of ErrorKey will be returned for handle index 2. // // If signContext is not nil and if the scheme of the key associated with signContext is AsymSchemeNull, then inScheme must be // provided to specify a valid signing scheme for the key. If it isn't, a *TPMParameterError error with an error code of ErrorScheme // will be returned for parameter index 2. // // If signContext is not nil and the scheme of the key associated with signContext is not AsymSchemeNull, then inScheme may be nil. If // it is provided, then the specified scheme must match that of the signing key, else a *TPMParameterError error with an error code of // ErrorScheme will be returned for parameter index 2. // // On success, it returns an attestation structure detailing the current audit digest for sessionContext. If signContext is not nil, // the attestation structure will be signed by the associated key and returned too. func (t *TPMContext) GetSessionAuditDigest(privacyAdminContext, signContext ResourceContext, sessionContext SessionContext, qualifyingData Data, inScheme *SigScheme, privacyAdminContextAuthSession, signContextAuthSession SessionContext, sessions ...SessionContext) (auditInfo *Attest, signature *Signature, err error) { if inScheme == nil { inScheme = &SigScheme{Scheme: SigSchemeAlgNull} } if err := t.RunCommand(CommandGetSessionAuditDigest, sessions, ResourceContextWithSession{Context: privacyAdminContext, Session: privacyAdminContextAuthSession}, ResourceContextWithSession{Context: signContext, Session: signContextAuthSession}, sessionContext, Delimiter, qualifyingData, inScheme, Delimiter, Delimiter, mu.Sized(&auditInfo), &signature); err != nil { return nil, nil, err } return auditInfo, signature, nil } // GetCommandAuditDigest executes the TPM2_GetCommandAuditDigest command to obtain the current command audit digest, the current // audit digest algorithm and a digest of the list of commands being audited. // // The privacyContext argument must be a ResourceContext corresponding to HandleEndorsement. This command requires authorization with // the user auth role for privacyContext, with session based authorization provided via privacyContextAuthSession. // // If signContext is not nil, the returned attestation will be signed by the key associated with it. This command requires // authorization with the user auth role for signContext, with session based authorization provided via provided via // signContextAuthSession. // // If signContext is not nil and the object associated with signContext is not a signing key, a *TPMHandleError error with an error // code of ErrorKey will be returned for handle index 2. // // If signContext is not nil and if the scheme of the key associated with signContext is AsymSchemeNull, then inScheme must be // provided to specify a valid signing scheme for the key. If it isn't, a *TPMParameterError error with an error code of ErrorScheme // will be returned for parameter index 2. // // If signContext is not nil and the scheme of the key associated with signContext is not AsymSchemeNull, then inScheme may be nil. If // it is provided, then the specified scheme must match that of the signing key, else a *TPMParameterError error with an error code of // ErrorScheme will be returned for parameter index 2. // // On success, it returns an attestation structure detailing the current command audit digest, digest algorithm and a digest of the // list of commands being audited. If signContext is not nil, the attestation structure will be signed by the associated key and // returned too. func (t *TPMContext) GetCommandAuditDigest(privacyContext, signContext ResourceContext, qualifyingData Data, inScheme *SigScheme, privacyContextAuthSession, signContextAuthSession SessionContext, sessions ...SessionContext) (auditInfo *Attest, signature *Signature, err error) { if inScheme == nil { inScheme = &SigScheme{Scheme: SigSchemeAlgNull} } if err := t.RunCommand(CommandGetCommandAuditDigest, sessions, ResourceContextWithSession{Context: privacyContext, Session: privacyContextAuthSession}, ResourceContextWithSession{Context: signContext, Session: signContextAuthSession}, Delimiter, qualifyingData, inScheme, Delimiter, Delimiter, mu.Sized(&auditInfo), &signature); err != nil { return nil, nil, err } return auditInfo, signature, nil } // GetTime executes the TPM2_GetTime command in order to obtain the current values of time and clock. // // The privacyAdminContext argument must be a ResourceContext that corresponds to HandleEndorsement. The command requires authorization // with the user auth role for privacyAdminContext, with session based authorization provided via privacyAdminContextAuthSession. // // If signContext is not nil, the returned attestation will be signed by the key associated with it. This command requires // authorization with the user auth role for signContext, with session based authorization provided via signContextAuthSession. // // If signContext is not nil and the object associated with signContext is not a signing key, a *TPMHandleError error with an error // code of ErrorKey will be returned for handle index 2. // // If signContext is not nil and if the scheme of the key associated with signContext is AsymSchemeNull, then inScheme must be // provided to specify a valid signing scheme for the key. If it isn't, a *TPMParameterError error with an error code of ErrorScheme // will be returned for parameter index 2. // // If signContext is not nil and the scheme of the key associated with signContext is not AsymSchemeNull, then inScheme may be nil. If // it is provided, then the specified scheme must match that of the signing key, else a *TPMParameterError error with an error code of // ErrorScheme will be returned for parameter index 2. // // On success, it returns an attestation structure detailing the current values of time and clock. If signContext is not nil, the // attestation structure will be signed by the associated key and returned too. func (t *TPMContext) GetTime(privacyAdminContext, signContext ResourceContext, qualifyingData Data, inScheme *SigScheme, privacyAdminContextAuthSession, signContextAuthSession SessionContext, sessions ...SessionContext) (timeInfo *Attest, signature *Signature, err error) { if inScheme == nil { inScheme = &SigScheme{Scheme: SigSchemeAlgNull} } if err := t.RunCommand(CommandGetTime, sessions, ResourceContextWithSession{Context: privacyAdminContext, Session: privacyAdminContextAuthSession}, ResourceContextWithSession{Context: signContext, Session: signContextAuthSession}, Delimiter, qualifyingData, inScheme, Delimiter, Delimiter, mu.Sized(&timeInfo), &signature); err != nil { return nil, nil, err } return timeInfo, signature, nil } ./github.com/canonical/go-tpm2/cmds_cap.go0000664000000000000000000004623200000000000016726 0ustar0000000000000000// Copyright 2019 Canonical Ltd. // Licensed under the LGPLv3 with static-linking exception. // See LICENCE file for details. package tpm2 import ( "encoding/binary" "fmt" ) // Section 30 - Capability Commands // GetCapability executes the TPM2_GetCapability command, which returns various properties of the TPM and its current state. The // capability parameter indicates the category of data to be returned. The property parameter indicates the first value of the // selected category to be returned. The propertyCount parameter indicates the number of values to be returned. // // If no property in the TPM corresponds to the value of property, then the next property is returned. // // The underlying implementation of TPM2_GetCapability is not required to (or may not be able to) return all of the requested // values in a single request. This function will re-execute the TPM2_GetCapability command until all of the requested properties // have been returned. As a consequence, any SessionContext instances provided should have the AttrContinueSession attribute defined. // // If capability is CapabilityHandles and property does not correspond to a valid handle type, a *TPMParameterError error with // an error code of ErrorHandle is returned for parameter index 2. func (t *TPMContext) GetCapability(capability Capability, property, propertyCount uint32, sessions ...SessionContext) (capabilityData *CapabilityData, err error) { capabilityData = &CapabilityData{Capability: capability, Data: &CapabilitiesU{}} nextProperty := property remaining := propertyCount for { var moreData bool var data CapabilityData if err := t.RunCommand(CommandGetCapability, sessions, Delimiter, capability, nextProperty, remaining, Delimiter, Delimiter, &moreData, &data); err != nil { return nil, err } if data.Capability != capability { return nil, &InvalidResponseError{CommandGetCapability, fmt.Sprintf("TPM responded with data for the wrong capability (got %s)", data.Capability)} } var l int var p uint32 switch data.Capability { case CapabilityAlgs: capabilityData.Data.Algorithms = append(capabilityData.Data.Algorithms, data.Data.Algorithms...) l = len(data.Data.Algorithms) if l > 0 { p = uint32(data.Data.Algorithms[l-1].Alg) } case CapabilityHandles: capabilityData.Data.Handles = append(capabilityData.Data.Handles, data.Data.Handles...) l = len(data.Data.Handles) if l > 0 { p = uint32(data.Data.Handles[l-1]) } case CapabilityCommands: capabilityData.Data.Command = append(capabilityData.Data.Command, data.Data.Command...) l = len(data.Data.Command) if l > 0 { p = uint32(data.Data.Command[l-1].CommandCode()) } case CapabilityPPCommands: capabilityData.Data.PPCommands = append(capabilityData.Data.PPCommands, data.Data.PPCommands...) l = len(data.Data.PPCommands) if l > 0 { p = uint32(data.Data.PPCommands[l-1]) } case CapabilityAuditCommands: capabilityData.Data.AuditCommands = append(capabilityData.Data.AuditCommands, data.Data.AuditCommands...) l = len(data.Data.AuditCommands) if l > 0 { p = uint32(data.Data.AuditCommands[l-1]) } case CapabilityPCRs: if moreData { return nil, &InvalidResponseError{CommandGetCapability, fmt.Sprintf("TPM did not respond with all requested properties for capability %s", data.Capability)} } return &data, nil case CapabilityTPMProperties: capabilityData.Data.TPMProperties = append(capabilityData.Data.TPMProperties, data.Data.TPMProperties...) l = len(data.Data.TPMProperties) if l > 0 { p = uint32(data.Data.TPMProperties[l-1].Property) } case CapabilityPCRProperties: capabilityData.Data.PCRProperties = append(capabilityData.Data.PCRProperties, data.Data.PCRProperties...) l = len(data.Data.PCRProperties) if l > 0 { p = uint32(data.Data.PCRProperties[l-1].Tag) } case CapabilityECCCurves: capabilityData.Data.ECCCurves = append(capabilityData.Data.ECCCurves, data.Data.ECCCurves...) l = len(data.Data.ECCCurves) if l > 0 { p = uint32(data.Data.ECCCurves[l-1]) } case CapabilityAuthPolicies: capabilityData.Data.AuthPolicies = append(capabilityData.Data.AuthPolicies, data.Data.AuthPolicies...) l = len(data.Data.AuthPolicies) if l > 0 { p = uint32(data.Data.AuthPolicies[l-1].Handle) } } nextProperty += p + 1 remaining -= uint32(l) if !moreData || remaining <= 0 { break } } return capabilityData, nil } // GetCapabilityAlgs is a convenience function for TPMContext.GetCapability, and // returns properties of the algorithms supported by the TPM. The first parameter // indicates the first algorithm for which to return properties. If this algorithm // isn't supported, then the properties of the next supported algorithm are returned // instead. The propertyCount parameter indicates the number of algorithms for which // to return properties. func (t *TPMContext) GetCapabilityAlgs(first AlgorithmId, propertyCount uint32, sessions ...SessionContext) (algs AlgorithmPropertyList, err error) { data, err := t.GetCapability(CapabilityAlgs, uint32(first), propertyCount, sessions...) if err != nil { return nil, err } return data.Data.Algorithms, nil } // GetCapabilityAlg is a convenience function for TPMContext.GetCapability that // returns the properties of the specified algorithm if it is supported by the TPM. // If it isn't supported, an error is returned. func (t *TPMContext) GetCapabilityAlg(alg AlgorithmId, sessions ...SessionContext) (AlgorithmProperty, error) { algs, err := t.GetCapabilityAlgs(alg, 1, sessions...) if err != nil { return AlgorithmProperty{}, err } if len(algs) == 0 || algs[0].Alg != alg { return AlgorithmProperty{}, fmt.Errorf("algorithm %v does not exist", alg) } return algs[0], nil } // IsAlgorithmSupported is a convenience function for TPMContext.GetCapability that // determines if the specified algorithm is supported by the TPM. Note that this will // indicate that the algorithm is unsupported if the TPM returns an error. func (t *TPMContext) IsAlgorithmSupported(alg AlgorithmId, sessions ...SessionContext) bool { if _, err := t.GetCapabilityAlg(alg, sessions...); err != nil { return false } return true } // GetCapabilityCommands is a convenience function for TPMContext.GetCapability, and // returns attributes of the commands supported by the TPM. The first parameter indicates // the first command for which to return attributes. If this command isn't supported, // then the attributes of the next supported command are returned instead. The propertyCount // parameter indicates the number of commands for which to return attributes. func (t *TPMContext) GetCapabilityCommands(first CommandCode, propertyCount uint32, sessions ...SessionContext) (commands CommandAttributesList, err error) { data, err := t.GetCapability(CapabilityCommands, uint32(first), propertyCount, sessions...) if err != nil { return nil, err } return data.Data.Command, nil } // GetCapabilityCommand is a convenience function for TPMContext.GetCapability that // returns the attributes of the specified command if it is supported by the TPM. If // it isn't supported, an error is returned. func (t *TPMContext) GetCapabilityCommand(code CommandCode, sessions ...SessionContext) (CommandAttributes, error) { commands, err := t.GetCapabilityCommands(code, 1, sessions...) if err != nil { return 0, err } if len(commands) == 0 || commands[0].CommandCode() != code { return 0, fmt.Errorf("command %v does not exist", code) } return commands[0], nil } // IsCommandSupported is a convenience function for TPMContext.GetCapability that // determines if the specified command is supported by the TPM. Note that this will // indicate that the command is unsupported if the TPM returns an error. func (t *TPMContext) IsCommandSupported(code CommandCode, sessions ...SessionContext) bool { if _, err := t.GetCapabilityCommand(code, sessions...); err != nil { return false } return true } // GetCapabilityPPCommands is a convenience function for TPMContext.GetCapability, and // returns a list of commands that require physical presence for platform authorization. // The first parameter indicates the command code at which the returned list should start. // The propertyCount parameter indicates the maximum number of command codes to return. func (t *TPMContext) GetCapabilityPPCommands(first CommandCode, propertyCount uint32, sessions ...SessionContext) (ppCommands CommandCodeList, err error) { data, err := t.GetCapability(CapabilityPPCommands, uint32(first), propertyCount, sessions...) if err != nil { return nil, err } return data.Data.PPCommands, nil } // GetCapabilityAuditCommands is a convenience function for TPMContext.GetCapability, and // returns a list of commands that are currently set for command audit. The first parameter // indicates the command code at which the returned list should start. The propertyCount // parameter indicates the maximum number of command codes to return. func (t *TPMContext) GetCapabilityAuditCommands(first CommandCode, propertyCount uint32, sessions ...SessionContext) (auditCommands CommandCodeList, err error) { data, err := t.GetCapability(CapabilityAuditCommands, uint32(first), propertyCount, sessions...) if err != nil { return nil, err } return data.Data.AuditCommands, nil } // GetCapabilityHandles is a convenience function for TPMContext.GetCapability, and returns // a list of handles of resources on the TPM. The firstHandle parameter indicates the type of // handles to be returned (represented by the most-significant byte), and also the handle at // which the list should start. The propertyCount parameter indicates the maximum number of // handles to return. func (t *TPMContext) GetCapabilityHandles(firstHandle Handle, propertyCount uint32, sessions ...SessionContext) (handles HandleList, err error) { data, err := t.GetCapability(CapabilityHandles, uint32(firstHandle), propertyCount, sessions...) if err != nil { return nil, err } return data.Data.Handles, nil } // DoesHandleExist is a convenience function for TPMContext.GetCapability that determines // if a resource with the specified handle exists on the TPM. This will indicate that the // resource does not exist if the TPM returns an error. func (t *TPMContext) DoesHandleExist(handle Handle, sessions ...SessionContext) bool { handles, err := t.GetCapabilityHandles(handle, 1, sessions...) if err != nil { return false } if len(handles) == 0 || handles[0] != handle { return false } return true } // GetCapabilityPCRs is a convenience function for TPMContext.GetCapability, and returns the // current allocation of PCRs on the TPM. func (t *TPMContext) GetCapabilityPCRs(sessions ...SessionContext) (pcrs PCRSelectionList, err error) { data, err := t.GetCapability(CapabilityPCRs, 0, CapabilityMaxProperties, sessions...) if err != nil { return nil, err } return data.Data.AssignedPCR, nil } // GetCapabilityTPMProperties is a convenience function for TPMContext.GetCapability, and // returns the values of properties of the TPM. The first parameter indicates the first // property for which to return a value. If the property does not exist, then the value of // the next available property is returned. The propertyCount parameter indicates the number // of properties for which to return values. func (t *TPMContext) GetCapabilityTPMProperties(first Property, propertyCount uint32, sessions ...SessionContext) (tpmProperties TaggedTPMPropertyList, err error) { data, err := t.GetCapability(CapabilityTPMProperties, uint32(first), propertyCount, sessions...) if err != nil { return nil, err } return data.Data.TPMProperties, nil } // GetCapabilityTPMProperty is a convenience function for TPMContext.GetCapability that returns // the value of the specified property if it exists. If it doesn't exist, an error is returned. func (t *TPMContext) GetCapabilityTPMProperty(property Property, sessions ...SessionContext) (uint32, error) { props, err := t.GetCapabilityTPMProperties(property, 1, sessions...) if err != nil { return 0, err } if len(props) == 0 || props[0].Property != property { return 0, fmt.Errorf("property %v does not exist", property) } return props[0].Value, nil } // GetManufacturer is a convenience function for TPMContext.GetCapability that returns the ID of // the TPM manufacturer. func (t *TPMContext) GetManufacturer(sessions ...SessionContext) (manufacturer TPMManufacturer, err error) { m, err := t.GetCapabilityTPMProperty(PropertyManufacturer, sessions...) if err != nil { return 0, err } return TPMManufacturer(m), nil } // GetInputBuffer is a convenience function for TPMContext.GetCapability that returns the value of // the PropertyInputBuffer property, which indicates the maximum size of arguments of the MaxBuffer // type in bytes. The size is TPM implementation specific, but required to be at least 1024 bytes. func (t *TPMContext) GetInputBuffer(sessions ...SessionContext) int { n, err := t.GetCapabilityTPMProperty(PropertyInputBuffer, sessions...) if err != nil { return 1024 } return int(n) } // GetMaxDigest is a convenience function for TPMContext.GetCapability that returns the value of // the PropertyMaxDigest property, which indicates the size of the largest digest algorithm // supported by the TPM in bytes. func (t *TPMContext) GetMaxDigest(sessions ...SessionContext) (int, error) { n, err := t.GetCapabilityTPMProperty(PropertyMaxDigest, sessions...) if err != nil { return 0, err } return int(n), nil } // GetMaxData is a convenience function for TPMContext.GetCapability that returns the maximum // size of arguments of the Data type supported by the TPM in bytes. func (t *TPMContext) GetMaxData(sessions ...SessionContext) (int, error) { n, err := t.GetMaxDigest(sessions...) if err != nil { return 0, err } return n + binary.Size(AlgorithmId(0)), nil } // GetNVBufferMax is a convenience function for TPMContext.GetCapability that returns the value // of the PropertyNVBufferMax property, which indicates the maximum buffer size supported by // the TPM in bytes for TPMContext.NVReadRaw and TPMContext.NVWriteRaw. func (t *TPMContext) GetNVBufferMax(sessions ...SessionContext) (int, error) { n, err := t.GetCapabilityTPMProperty(PropertyNVBufferMax, sessions...) if err != nil { return 0, err } return int(n), nil } // GetNVIndexMax is a convenience function for TPMContext.GetCapability that returns // the value of the PropertyNVIndexMax property, which indicates the maximum size of a // single NV index. func (t *TPMContext) GetNVIndexMax(sessions ...SessionContext) (int, error) { n, err := t.GetCapabilityTPMProperty(PropertyNVIndexMax, sessions...) if err != nil { return 0, err } return int(n), nil } // GetCapabilityPCRProperties is a convenience function for TPMContext.GetCapability, and returns // the values of PCR properties. The first parameter indicates the first property for which to // return a value. If the property does not exist, then the value of the next available property // is returned. The propertyCount parameter indicates the number of properties for which to // return values. Each returned property value is a list of PCR indexes associated with a property. func (t *TPMContext) GetCapabilityPCRProperties(first PropertyPCR, propertyCount uint32, sessions ...SessionContext) (pcrProperties TaggedPCRPropertyList, err error) { data, err := t.GetCapability(CapabilityPCRProperties, uint32(first), propertyCount, sessions...) if err != nil { return nil, err } return data.Data.PCRProperties, nil } // GetCapabilityECCCurves is a convenience function for TPMContext.GetCapability, and returns a // list of ECC curves supported by the TPM. func (t *TPMContext) GetCapabilityECCCurves(sessions ...SessionContext) (eccCurves ECCCurveList, err error) { data, err := t.GetCapability(CapabilityECCCurves, uint32(ECCCurveFirst), CapabilityMaxProperties, sessions...) if err != nil { return nil, err } return data.Data.ECCCurves, nil } // IsECCCurveSupported is a convenience function for TPMContext.GetCapability that determines // if the specified curve is supported. This will indicate that the specified curve is unsupported // if the TPM returns an error. func (t *TPMContext) IsECCCurveSupported(curve ECCCurve, sessions ...SessionContext) bool { curves, err := t.GetCapabilityECCCurves(sessions...) if err != nil { return false } for _, supported := range curves { if supported == curve { return true } } return false } // GetCapabilityAuthPolicies is a convenience function for TPMContext.GetCapability, and returns // auth policy digests associated with permanent handles. The first parameter indicates the first // handle for which to return an auth policy. If the handle doesn't exist, then the auth policy // for the next available handle is returned. The propertyCount parameter indicates the number // of permanent handles for which to return an auth policy. func (t *TPMContext) GetCapabilityAuthPolicies(first Handle, propertyCount uint32, sessions ...SessionContext) (authPolicies TaggedPolicyList, err error) { data, err := t.GetCapability(CapabilityAuthPolicies, uint32(first), propertyCount, sessions...) if err != nil { return nil, err } return data.Data.AuthPolicies, nil } // IsTPM2 determines whether this TPMContext is connected to a TPM2 device. It does this by // attempting to execute a TPM2_GetCapability command, and verifying that the response packet // has the expected tag. // // On success, this will return true if TPMContext is connected to a TPM2 device, or false if // it is connected to a TPM1.2 device. It will return false if communication with the device // fails of if the response is badly formed. func (t *TPMContext) IsTPM2() (isTpm2 bool) { _, err := t.GetCapabilityTPMProperties(PropertyTotalCommands, 0) if e, ok := err.(*TPMError); ok && e.Code == ErrorBadTag { return false } return true } // TestParms executes the TPM2_TestParms command to check if the specified combination of // algorithm parameters is supported. func (t *TPMContext) TestParms(parameters *PublicParams, sessions ...SessionContext) error { return t.RunCommand(CommandTestParms, sessions, Delimiter, parameters) } // IsRSAKeySizeSupporters is a convenience function around TestParms that determines whether // the specified RSA key size is supported. func (t *TPMContext) IsRSAKeySizeSupported(keyBits uint16, sessions ...SessionContext) bool { params := PublicParams{ Type: ObjectTypeRSA, Parameters: &PublicParamsU{ RSADetail: &RSAParams{ Symmetric: SymDefObject{Algorithm: SymObjectAlgorithmNull}, Scheme: RSAScheme{Scheme: RSASchemeNull}, KeyBits: keyBits, Exponent: 0}}} if err := t.TestParms(¶ms, sessions...); err != nil { return false } return true } // IsSymmetricAlgorithmSupported is a convenience function around TestParms that determines whether // the specified symmetric algorithm and key size combination is supported. func (t *TPMContext) IsSymmetricAlgorithmSupported(algorithm SymObjectAlgorithmId, keyBits uint16, sessions ...SessionContext) bool { params := PublicParams{ Type: ObjectTypeSymCipher, Parameters: &PublicParamsU{ SymDetail: &SymCipherParams{ Sym: SymDefObject{ Algorithm: algorithm, KeyBits: &SymKeyBitsU{Sym: keyBits}, Mode: &SymModeU{Sym: SymModeCFB}}}}} if err := t.TestParms(¶ms, sessions...); err != nil { return false } return true } ./github.com/canonical/go-tpm2/cmds_clock.go0000664000000000000000000000150500000000000017250 0ustar0000000000000000// Copyright 2019 Canonical Ltd. // Licensed under the LGPLv3 with static-linking exception. // See LICENCE file for details. package tpm2 // Section 29 - Clocks and Timers // ReadClock executes the TPM2_ReadClock command. On succesful completion, it will return a TimeInfo struct that contains the current // value of time, clock, reset and restart counts. func (t *TPMContext) ReadClock(sessions ...SessionContext) (currentTime *TimeInfo, err error) { if err := t.RunCommand(CommandReadClock, sessions, Delimiter, Delimiter, Delimiter, ¤tTime); err != nil { return nil, err } return currentTime, nil } // func (t *TPMContext) ClockSet(auth Handle, newTime uint64, authAuth interface{}) error { // } // func (t *TPMContext) ClockRateAdjust(auth Handle, rateAdjust ClockAdjust, authAuth interface{}) error { // } ./github.com/canonical/go-tpm2/cmds_command_audit.go0000664000000000000000000000311700000000000020762 0ustar0000000000000000// Copyright 2019 Canonical Ltd. // Licensed under the LGPLv3 with static-linking exception. // See LICENCE file for details. package tpm2 // Section 21 - Command Audit // SetCommandCodeAuditStatus executes the TPM2_SetCommandCodeAuditStatus command to allow the privacy administrator or platform to // change the audit status of a command, or change the digest algorithm used for command auditing (but not both at the same time). // // The auth parameter should be a ResourceContext corresponding to either HandlePlatform or HandleOwner. This command requires // authorization of auth with the user auth role, with session based authorization provided via authAuthSession. // // The auditAlg argument specifies the digest algorithm for command auditing. The setList argument is used to specify which commands // should be added to the list of commands to be audited. The clearList argument is used to specify which commands should be removed // from the list of commands to be audited. // // If auditAlg is not HashAlgorithmNull or the current audit digest algorith, and the length of setList or clearList is greater than // zero, a *TPMParameterError error with an error code of ErrorValue will be returned for parameter index 1. func (t *TPMContext) SetCommandCodeAuditStatus(auth ResourceContext, auditAlg HashAlgorithmId, setList, clearList CommandCodeList, authAuthSession SessionContext, sessions ...SessionContext) error { return t.RunCommand(CommandSetCommandCodeAuditStatus, sessions, ResourceContextWithSession{Context: auth, Session: authAuthSession}, Delimiter, auditAlg, setList, clearList) } ./github.com/canonical/go-tpm2/cmds_context.go0000664000000000000000000002374600000000000017654 0ustar0000000000000000// Copyright 2019 Canonical Ltd. // Licensed under the LGPLv3 with static-linking exception. // See LICENCE file for details. package tpm2 // Section 28 - Context Management import ( "errors" "fmt" "github.com/canonical/go-tpm2/mu" "golang.org/x/xerrors" ) // ContextSave executes the TPM2_ContextSave command on the handle referenced by saveContext, in order to save the context associated // with that handle outside of the TPM. The TPM encrypts and integrity protects the context with a key derived from the hierarchy // proof. If saveContext does not correspond to a transient object or a session, then it will return an error. // // On successful completion, it returns a Context instance that can be passed to TPMContext.ContextLoad. Note that this function // wraps the context data returned from the TPM with some host-side state associated with the resource, so that it can be restored // fully in TPMContext.ContextLoad. If saveContext corresponds to a session, the host-side state that is added to the returned context // blob includes the session key. // // If saveContext corresponds to a session, then TPM2_ContextSave also removes resources associated with the session from the TPM // (it becomes a saved session rather than a loaded session). In this case, saveContext is marked as not loaded and can only be used // as an argument to TPMContext.FlushContext. // // If saveContext corresponds to a session and no more contexts can be saved, a *TPMError error will be returned with an error code // of ErrorTooManyContexts. If a context ID cannot be assigned for the session, a *TPMWarning error with a warning code of // WarningContextGap will be returned. func (t *TPMContext) ContextSave(saveContext HandleContext) (context *Context, err error) { switch c := saveContext.(type) { case *handleContext: if c.Type == handleContextTypePartial { return nil, makeInvalidArgError("saveContext", "unusable partial HandleContext") } } if err := t.RunCommand(CommandContextSave, nil, saveContext, Delimiter, Delimiter, Delimiter, &context); err != nil { return nil, err } blob := mu.MustMarshalToBytes(saveContext.SerializeToBytes(), context.Blob) context.Blob = blob switch c := saveContext.(type) { case *sessionContext: c.handleContext.Data.Session = nil if t.exclusiveSession == c { t.exclusiveSession = nil } } return context, nil } // ContextLoad executes the TPM2_ContextLoad command with the supplied Context, in order to restore a context previously saved from // TPMContext.ContextSave. // // If the size field of the integrity HMAC in the context blob is greater than the size of the largest digest algorithm, a *TPMError // with an error code of ErrorSize is returned. If the context blob is shorter than the size indicated for the integrity HMAC, a // *TPMError with an error code of ErrorInsufficient is returned. // // If the size of the context's integrity HMAC does not match the context integrity digest algorithm for the TPM, or the context // blob is too short, a *TPMParameterError error with an error code of ErrorSize will be returned. If the integrity HMAC check fails, // a *TPMParameterError with an error code of ErrorIntegrity will be returned. // // If the hierarchy that the context is part of is disabled, a *TPMParameterError error with an error code of ErrorHierarchy will be // returned. // // If the context corresponds to a session but the handle doesn't reference a saved session or the sequence number is invalid, a // *TPMParameterError error with an error code of ErrorHandle will be returned. // // If the context corresponds to a session and no more sessions can be created until the oldest session is context loaded, and context // doesn't correspond to the oldest session, a *TPMWarning error with a warning code of WarningContextGap will be returned. // // If there are no more slots available for objects or loaded sessions, a *TPMWarning error with a warning code of either // WarningSessionMemory or WarningObjectMemory will be returned. // // On successful completion, it returns a HandleContext which corresponds to the resource loaded in to the TPM. If the context // corresponds to an object, this will be a new ResourceContext. If context corresponds to a session, then this will be a new // SessionContext. func (t *TPMContext) ContextLoad(context *Context) (loadedContext HandleContext, err error) { if context == nil { return nil, makeInvalidArgError("context", "nil value") } var contextData []byte var blob ContextData if _, err := mu.UnmarshalFromBytes(context.Blob, &contextData, &blob); err != nil { return nil, xerrors.Errorf("cannot unmarshal context blob: %w", err) } hc, _, err := CreateHandleContextFromBytes(contextData) if err != nil { return nil, xerrors.Errorf("cannot unmarshal handle context: %w", err) } switch hc.Handle().Type() { case HandleTypeHMACSession, HandleTypePolicySession: if hc.Handle() != context.SavedHandle { return nil, errors.New("host and TPM context blobs have inconsistent handles") } case HandleTypeTransient: default: return nil, errors.New("unexpected context type") } tpmContext := Context{ Sequence: context.Sequence, SavedHandle: context.SavedHandle, Hierarchy: context.Hierarchy, Blob: blob} var loadedHandle Handle if err := t.RunCommand(CommandContextLoad, nil, Delimiter, tpmContext, Delimiter, &loadedHandle); err != nil { return nil, err } switch hc.Handle().Type() { case HandleTypeTransient: if loadedHandle.Type() != HandleTypeTransient { return nil, &InvalidResponseError{CommandContextLoad, fmt.Sprintf("handle %v returned from TPM is the wrong type", loadedHandle)} } hc.(*objectContext).H = loadedHandle case HandleTypeHMACSession, HandleTypePolicySession: if loadedHandle != context.SavedHandle { return nil, &InvalidResponseError{CommandContextLoad, fmt.Sprintf("handle %v returned from TPM is incorrect", loadedHandle)} } hc.(*sessionContext).Data().IsExclusive = false default: panic("not reached") } return hc, nil } // FlushContext executes the TPM2_FlushContext command on the handle referenced by flushContext, in order to flush resources // associated with it from the TPM. If flushContext does not correspond to a transient object or a session, then it will return // with an error. // // On successful completion, flushContext is invalidated. If flushContext corresponded to a session, then it will no longer be // possible to restore that session with TPMContext.ContextLoad, even if it was previously saved with TPMContext.ContextSave. func (t *TPMContext) FlushContext(flushContext HandleContext) error { if flushContext == nil { return makeInvalidArgError("flushContext", "nil value") } if err := t.RunCommand(CommandFlushContext, nil, Delimiter, flushContext.Handle()); err != nil { return err } flushContext.(handleContextPrivate).invalidate() return nil } // EvictControl executes the TPM2_EvictControl command on the handle referenced by object. To persist a transient object, // object should correspond to the transient object and persistentHandle should specify the persistent handle to which the // resource associated with object should be persisted. To evict a persistent object, object should correspond to the // persistent object and persistentHandle should be the handle associated with that resource. // // The auth parameter should be a ResourceContext that corresponds to a hierarchy - it should be HandlePlatform for objects within // the platform hierarchy, or HandleOwner for objects within the storage or endorsement hierarchies. If auth is a ResourceContext // corresponding to HandlePlatform but object corresponds to an object outside of the platform hierarchy, or auth is a ResourceContext // corresponding to HandleOwner but object corresponds to an object inside of the platform hierarchy, a *TPMHandleError error with // an error code of ErrorHierarchy will be returned for handle index 2. The auth handle requires authorization with the user auth // role, with session based authorization provided via authAuthSession. // // If object corresponds to a transient object that only has a public part loaded, or which has the AttrStClear attribute set, // then a *TPMHandleError error with an error code of ErrorAttributes will be returned for handle index 2. // // If object corresponds to a persistent object and persistentHandle is not the handle for that object, a *TPMHandleError error // with an error code of ErrorHandle will be returned for handle index 2. // // If object corresponds to a transient object and persistentHandle is not in the correct range determined by the value of // auth, a *TPMParameterError error with an error code of ErrorRange will be returned. // // If there is insuffient space to persist a transient object, a *TPMError error with an error code of ErrorNVSpace will be returned. // If a persistent object already exists at the specified handle, a *TPMError error with an error code of ErrorNVDefined will be // returned. // // On successful completion of persisting a transient object, it returns a ResourceContext that corresponds to the persistent object. // On successful completion of evicting a persistent object, it returns a nil ResourceContext, and object will be invalidated. func (t *TPMContext) EvictControl(auth, object ResourceContext, persistentHandle Handle, authAuthSession SessionContext, sessions ...SessionContext) (ResourceContext, error) { if object == nil { return nil, makeInvalidArgError("object", "nil value") } var public *Public if object.Handle() != persistentHandle { if err := mu.CopyValue(&public, object.(*objectContext).GetPublic()); err != nil { return nil, fmt.Errorf("cannot copy public area of object: %v", err) } } if err := t.RunCommand(CommandEvictControl, sessions, ResourceContextWithSession{Context: auth, Session: authAuthSession}, object, Delimiter, persistentHandle); err != nil { return nil, err } if object.Handle() == persistentHandle { object.(handleContextPrivate).invalidate() return nil, nil } return makeObjectContext(persistentHandle, object.Name(), public), nil } ./github.com/canonical/go-tpm2/cmds_da.go0000664000000000000000000000476100000000000016550 0ustar0000000000000000// Copyright 2019 Canonical Ltd. // Licensed under the LGPLv3 with static-linking exception. // See LICENCE file for details. package tpm2 // Section 25 - Dictionary Attack Functions // DictionaryAttackLockReset executes the TPM2_DictionaryAttackLockReset command to cancel the effect of a TPM lockout. The lockContext // parameter must always be a ResourceContext corresponding to HandleLockout. The command requires authorization with the user auth role for // lockContext, with session based authorization provided via lockContextAuthSession. // // On successful completion, the lockout counter will be reset to zero. func (t *TPMContext) DictionaryAttackLockReset(lockContext ResourceContext, lockContextAuthSession SessionContext, sessions ...SessionContext) error { return t.RunCommand(CommandDictionaryAttackLockReset, sessions, ResourceContextWithSession{Context: lockContext, Session: lockContextAuthSession}) } // DictionaryAttackParameters executes the TPM2_DictionaryAttackParameters command to change the dictionary attack lockout settings. // The newMaxTries parameter sets the maximum value of the lockout counter before the TPM enters lockout mode. If it is set to zero, // then the TPM will enter lockout mode and the use of dictionary attack protected entities will be disabled. The newRecoveryTime // parameter specifies the amount of time in seconds it takes for the lockout counter to decrement by one. If it is set to zero, then // dictionary attack protection is disabled. The lockoutRecovery parameter specifies the amount of time in seconds that the lockout // hierarchy authorization cannot be used after an authorization failure. If it is set to zero, then the lockout hierarchy can be used // again after a TPM reset, restart or resume. The newRecoveryTime and lockoutRecovery parameters are measured against powered on time // rather than clock time. // // The lockContext parameter must be a ResourceContext corresponding to HandleLockout. The command requires authorization with the user // auth role for lockContext, with session based authorization provided via lockContextAuthSession. func (t *TPMContext) DictionaryAttackParameters(lockContext ResourceContext, newMaxTries, newRecoveryTime, lockoutRecovery uint32, lockContextAuthSession SessionContext, sessions ...SessionContext) error { return t.RunCommand(CommandDictionaryAttackParameters, sessions, ResourceContextWithSession{Context: lockContext, Session: lockContextAuthSession}, Delimiter, newMaxTries, newRecoveryTime, lockoutRecovery) } ./github.com/canonical/go-tpm2/cmds_duplication.go0000664000000000000000000002251300000000000020472 0ustar0000000000000000// Copyright 2019 Canonical Ltd. // Licensed under the LGPLv3 with static-linking exception. // See LICENCE file for details. package tpm2 import ( "github.com/canonical/go-tpm2/mu" ) // Section 13 - Duplication Commands // Duplicate executes the TPM2_Duplicate command in order to duplicate the object associated with objectContext so that it may be // used in a different hierarchy. The new parent is specified by the newParentContext argument, which may correspond to an object // on the same or a different TPM, or may be nil for no parent. // // This command requires authorization for objectContext with the duplication role, with the session provided via // objectContextAuthSession. // // If symmetricAlg is provided, it defines the symmetric algorithm used for the inner duplication wrapper (see section 23.3 - // "Protected Storage Hierarchy - Duplication" of Part 1 of the Trusted Platform Module Library specification). If symmetricAlg // is provided and symmetricAlg.Algorithm is not SymObjectAlgorithmNull, a symmetric key for the inner duplication wrapper may be // provided via encryptionKeyIn. // // If the object associated with objectContext has the AttrFixedParent atttribute set, a *TPMHandleError error with an error code // of ErrorAttributes will be returned for handle index 1. // // If the object associated with objectContext has a name algorithm of HashAlgorithmNull, a *TPMHandleError error with an error code // of ErrorType will be returned for handle index 1. // // If newParentContext is provided and it does not correspond to a storage parent, a *TPMHandleError error with an error code of // ErrorType will be returned for handle index 2. // // If the object associated with objectContext has the AttrEncryptedDuplication attribute set and no symmetricAlg is provided or // symmetricAlg.Algorithm is SymObjectAlgorithmNull, a *TPMParameterError error with an error code of ErrorSymmetric will be returned // for parameter index 2. // // If the object associated with objectContext has the AttrEncryptedDuplication attribute set and newParentContext is not provided, // a *TPMHandleError error with an error code of ErrorHierarchy will be returned for handle index 2. // // If the length of encryptionKeyIn is not consistent with symmetricAlg, *TPMParameterError error with an error code of ErrorSize // will be returned for parameter index 1. // // If newParentContext corresponds to an ECC key and the public point of the key is not on the curve specified by the key, a *TPMError // error with an error code of ErrorKey will be returned. // // On success, the function returns a randomly generated symmetric key as Data for the inner duplication wrapper if symmetricAlg was // provided, symmetricAlg.Algorithm was not SymObjectAlgorithmNull and encryptionKeyIn was not provided. It also returns the sensitive // area associated with objectContext protected with an inner duplication wrapper (if specified by symmetricAlg) and an outer // duplication wrapper (if newParentContext was provided). If newParentContext was provided, the seed used to generate the symmetric // key and the HMAC key for the outer duplication wrapper is encrypted using the methods defined by newParentContext and returned as // an EncryptedSecret. func (t *TPMContext) Duplicate(objectContext, newParentContext ResourceContext, encryptionKeyIn Data, symmetricAlg *SymDefObject, objectContextAuthSession SessionContext, sessions ...SessionContext) (encryptionKeyOut Data, duplicate Private, outSymSeed EncryptedSecret, err error) { if symmetricAlg == nil { symmetricAlg = &SymDefObject{Algorithm: SymObjectAlgorithmNull} } if err := t.RunCommand(CommandDuplicate, sessions, ResourceContextWithSession{Context: objectContext, Session: objectContextAuthSession}, newParentContext, Delimiter, encryptionKeyIn, symmetricAlg, Delimiter, Delimiter, &encryptionKeyOut, &duplicate, &outSymSeed); err != nil { return nil, nil, nil, err } return encryptionKeyOut, duplicate, outSymSeed, nil } // func (t *TPMContext) Rewrap(oldParent, newParent HandleContext, inDuplicate Private, name Name, inSymSeed EncryptedSecret, oldParentAuth interface{}, sessions ...SessionContext) (Private, EncryptedSecret, error) { // } // Import executes the TPM2_Import command in order to encrypt the sensitive area of the object associated with the objectPublic and // duplicate arguments with the symmetric algorithm of the storage parent associated with parentContext, so that it can be loaded and // used in the new hierarchy. If the object to be imported has an inner duplication wrapper (see section 23.3 - "Protected Storage // Hierarchy - Duplication" of Part 1 of the Trusted Platform Module Library specification), then symmetricAlg must be provided with // the algorithm of the inner duplication wrapper, and encryptionKey must be provided with the symmetric key for the inner duplication // wrapper. If the object to be imported has an outer duplication wrapper, the seed used to generate the symmetric key and HMAC key // for the outer duplication wrapper, encrypted using the methods defined by parentContext, must be provided via the inSymSeed // argument. // // This command requires authorization with the user auth role for parentContext, with session based authorization provided via // parentContextAuthSession. // // If objectPublic has the AttrFixedTPM or AttrFixedParent attributes set, a *TPMParameterError error with an error code of // ErrorAttributes will be returned for parameter index 2. // // If parentContext is not associated with a storage parent, a *TPMHandleError error with an error code of ErrorType will be returned. // // If the length of encryptionKey is not consistent with symmetricAlg, a *TPMParameterError error with an error code of ErrorSize // will be returned for parameter index 1. // // If symmetricAlg is not provided or symmetricAlg.Algorithm is SymObjectAlgorithmNull and objectPublic has the // AttrEncryptedDuplication attribute set, a *TPMParameterError error with an error code of ErrorAttributes will be returned for // parameter index 1. // // If the length of inSymSeed is not zero and the object associated with parentContext is not an asymmetric key, a *TPMHandleError // error with an error code of ErrorType will be returned. // // If parentContext is associated with a RSA key and the size of inSymSeed does not match the size of the key's public modulus, a // *TPMParameterError error with an error code of ErrorSize will be returned for parameter index 4. // // If parentContext is associated with a RSA key and the plaintext size of inSymSeed is larger than the name algorithm, a // *TPMParameterError error with an error code of ErrorValue will be returned for parameter index 4. // // If parentContext is associated with a ECC key and inSymSeed does not contain enough data to unmarshal a ECC point, a // *TPMParameterError error with an error code of ErrorInsufficient will be returned for parameter index 4. // // If parentContext is associated with a ECC key and the ECC point in inSymSeed is not on the curve specified by the parent key, a // *TPMParameterError error with an error code of ErrorECCPoint will be returned for parameter index 4. // // If parentContext is associated with a ECC key and multiplication of the ECC point in inSymSeed results in a point at infinity, a // *TPMParameterError error with an error code of ErrorNoResult will be returned for parameter index 4. // // If the name of the object associated with objectPublic cannot be computed, a *TPMParameterError error with an error code of // ErrorHash will be returned for parameter index 2. // // If the object has an outer duplication wrapper and the integrity value of duplicate cannot be unmarshalled correctly, a // *TPMParameterError error with an error code of either ErrorSize or ErrorInsufficient will be returned for parameter index 3. // If the integrity check fails, a *TPMParameterError error with an error code of ErrorIntegrity will be returned for parameter // index 3. // // If the object has an inner duplication wrapper and the integrity value of duplicate cannot be unmarshalled correctly after // decrypting the inner wrapper, a *TPMParameterError error with an error code of either ErrorSize or ErrorInsufficient will be // returned for parameter index 3. If the integrity check fails, a *TPMParameterError error with an error code of ErrorIntegrity // will be returned for parameter index 3. // // If, after removing the duplication wrappers, the sensitive area does not unmarshal correctly, a *TPMParameterError error with // an error code of either ErrorSize or ErrorInsufficient will be returned for parameter index 3. // // On success, a new private area encrypted with the symmetric algorithm defined by the object associated with parentContext is // returned. func (t *TPMContext) Import(parentContext ResourceContext, encryptionKey Data, objectPublic *Public, duplicate Private, inSymSeed EncryptedSecret, symmetricAlg *SymDefObject, parentContextAuthSession SessionContext, sessions ...SessionContext) (outPrivate Private, err error) { if symmetricAlg == nil { symmetricAlg = &SymDefObject{Algorithm: SymObjectAlgorithmNull} } if err := t.RunCommand(CommandImport, sessions, ResourceContextWithSession{Context: parentContext, Session: parentContextAuthSession}, Delimiter, encryptionKey, mu.Sized(objectPublic), duplicate, inSymSeed, symmetricAlg, Delimiter, Delimiter, &outPrivate); err != nil { return nil, err } return outPrivate, nil } ./github.com/canonical/go-tpm2/cmds_ea.go0000664000000000000000000007045300000000000016552 0ustar0000000000000000// Copyright 2019 Canonical Ltd. // Licensed under the LGPLv3 with static-linking exception. // See LICENCE file for details. package tpm2 // Section 23 - Enhanced Authorization (EA) Commands // PolicySigned executes the TPM2_PolicySigned command to include a signed authorization in a policy. This is a combined assertion // that binds a policy to the signing key associated with authContext. // // An authorizing entity signs a digest of authorization qualifiers with the key associated with authContext. The digest is computed as: // digest := H(nonceTPM||expiration||cpHashA||policyRef) // ... where H is the digest algorithm associated with the auth parameter. Where there are no restrictions, the digest is computed // from 4 zero bytes, which corresponds to an expiration time of zero. The authorization qualifiers must match the arguments passed // to this command. The signature is provided via the auth parameter. // // If includeNonceTPM is set to true, this function includes the most recently received TPM nonce value for the session associated // with policySession in the command. In this case, the nonce value must be included in the digest that is signed by the authorizing // entity. // // The cpHashA parameter allows the session to be bound to a specific command and set of command parameters by providing a command // parameter digest. Command parameter digests can be computed using ComputeCpHash, using the digest algorithm for the session. If // provided, the cpHashA value must be included in the digest that is signed by the authorizing entity. // // If policySession does not correspond to a trial session, a *TPMError error with an error code of ErrorCpHash will be returned if // the session context already has a command parameter digest, name digest or template digest recorded on it and cpHashA does not // match it. // // If policySession does not correspond to a trial session and the length of cpHashA does not match the digest algorithm for the // session, a *TPMParameterError error with an error code of ErrorSize will be returned for parameter index 2. // // If the expiration parameter is not 0, it sets a timeout based on the absolute value of expiration in seconds since the start of // the session by which the authorization will expire. If the session associated with policySession is not a trial session and // expiration corresponds to a time in the past, or the TPM's time epoch has changed since the session was started, a // *TPMParameterError error with an error code of ErrorExpired will be returned for parameter index 4. // // If the session associated with policySession is not a trial session and the signing scheme or digest algorithm associated with // the auth parameter is not supported by the TPM, a *TPMParameterError error with an error code of ErrorScheme will be returned for // parameter index 5. // // If the session associated with policySession is not a trial session, the signature will be validated against a digest computed from // the provided arguments, using the key associated with authContext. If the signature is invalid, a *TPMParameterError error with an // error code of ErrorSignature will be returned for parameter index 5. // // On successful completion, the policy digest of the session associated with policySession will be extended to include the name of // authContext and the value of policyRef. If provided, the value of cpHashA will be recorded on the session context to restrict the // session's usage. If expiration is non-zero, the expiration time of the session context will be updated unless it already has an // expiration time that is earlier. If expiration is less than zero, a timeout value and corresponding *TkAuth ticket will be // returned if policySession does not correspond to a trial session. func (t *TPMContext) PolicySigned(authContext ResourceContext, policySession SessionContext, includeNonceTPM bool, cpHashA Digest, policyRef Nonce, expiration int32, auth *Signature, sessions ...SessionContext) (timeout Timeout, policyTicket *TkAuth, err error) { var nonceTPM Nonce if includeNonceTPM { nonceTPM = policySession.NonceTPM() } if err := t.RunCommand(CommandPolicySigned, sessions, authContext, policySession, Delimiter, nonceTPM, cpHashA, policyRef, expiration, auth, Delimiter, Delimiter, &timeout, &policyTicket); err != nil { return nil, nil, err } return timeout, policyTicket, nil } // PolicySecret executes the TPM2_PolicySecret command to include a secret-based authorization to the policy session associated // with policySession, and is a combined assertion. The command requires authorization with the user auth role for authContext, with // session based authorization provided via authContextAuthSession. If authContextAuthSession corresponds a policy session, and that // session does not include a TPM2_PolicyPassword or TPM2_PolicyAuthValue assertion, a *TPMSessionError error with an error code of // ErrorMode will be returned for session index 1. // // The cpHashA parameter allows the session to be bound to a specific command and set of command parameters by providing a command // parameter digest. Command parameter digests can be computed using ComputeCpHash, using the digest algorithm for the session. If // provided, the cpHashA value must be included in the digest that is signed by the authorizing entity. // // If policySession does not correspond to a trial session, a *TPMError error with an error code of ErrorCpHash will be returned if // the session context already has a command parameter digest, name digest or template digest recorded on it and cpHashA does not // match it. // // If policySession does not correspond to a trial session and the length of cpHashA does not match the digest algorithm for the // session, a *TPMParameterError error with an error code of ErrorSize will be returned for parameter index 2. // // If the expiration parameter is not 0, it sets a timeout based on the absolute value of expiration in seconds since the start of // the session by which the authorization will expire. If the session associated with policySession is not a trial session and // expiration corresponds to a time in the past, or the TPM's time epoch has changed since the session was started, a // *TPMParameterError error with an error code of ErrorExpired will be returned for parameter index 4. // // On successful completion, knowledge of the authorization value associated with authContext is proven. The policy digest of the // session associated with olicySession will be extended to include the name of authContext and the value of policyRef. If provided, // the value of cpHashA will be recorded on the session context to restrict the session's usage. If expiration is non-zero, the // expiration time of the session context will be updated unless it already has an expiration time that is earlier. If expiration is // less than zero, a timeout value and corresponding *TkAuth ticket will be returned if policySession does not correspond to a trial // session. func (t *TPMContext) PolicySecret(authContext ResourceContext, policySession SessionContext, cpHashA Digest, policyRef Nonce, expiration int32, authContextAuthSession SessionContext, sessions ...SessionContext) (timeout Timeout, policyTicket *TkAuth, err error) { if err := t.RunCommand(CommandPolicySecret, sessions, ResourceContextWithSession{Context: authContext, Session: authContextAuthSession}, policySession, Delimiter, policySession.NonceTPM(), cpHashA, policyRef, expiration, Delimiter, Delimiter, &timeout, &policyTicket); err != nil { return nil, nil, err } return timeout, policyTicket, nil } // PolicyTicket executes the TPM2_PolicyTicket command, and behaves similarly to TPMContext.PolicySigned with the exception that it // takes an authorization ticket rather than a signed authorization. The ticket parameter represents a valid authorization with an // expiration time, and will have been returned from a previous call to TPMContext.PolicySigned or TPMContext.PolicySecret when called // with an expiration time of less than zero. // // If policySession corresponds to a trial session, a *TPMHandleError error with an error code of ErrorAttributes will be returned. // // If the size of timeout is not the expected size, a *TPMParameterError with an error code of ErrorSize will be returned for // parameter index 1. // // A *TPMError error with an error code of ErrorCpHash will be returned if the session context already has a command parameter digest, // name digest or template digest recorded on it and cpHashA does not match it. // // The cpHashA and policyRef arguments must match the values passed to the command that originally produced the ticket. If the command // that produced the ticket was TPMContext.PolicySecret, authName must correspond to the name of the entity of which knowledge of the // authorization value was proven. If the command that produced the ticket was TPMContext.PolicySigned, authName must correspond to // the name of the key that produced the signed authorization. // // If the ticket is invalid, a *TPMParameterError error with an error code of ErrorTicket will be returned for parameter index 5. If // the ticket corresponds to an authorization that has expired, a *TPMParameterError error with an error code of ErrorExpired will // be returned for parameter index 1. // // On successful verification of the ticket, the policy digest of the session context associated with policySession will be extended // with the same values that the command that produced the ticket would extend it with. If provided, the value of cpHashA will be // recorded on the session context to restrict the session's usage. The expiration time of the session context will be updated with // the value of timeout, unless it already has an expiration time that is earlier. func (t *TPMContext) PolicyTicket(policySession SessionContext, timeout Timeout, cpHashA Digest, policyRef Nonce, authName Name, ticket *TkAuth, sessions ...SessionContext) error { return t.RunCommand(CommandPolicyTicket, sessions, policySession, Delimiter, timeout, cpHashA, policyRef, authName, ticket) } // PolicyOR executes the TPM2_PolicyOR command to allow a policy to be satisfied by different sets of conditions, and is an immediate // assertion. If policySession does not correspond to a trial session, it determines if the current policy digest of the session // context associated with policySession is contained in the list of digests specified via pHashList. If it is not, then a // *TPMParameterError error with an error code of ErrorValue is returned without making any changes to the session context. // // On successful completion, the policy digest of the session context associated with policySession is cleared, and then extended to // include the concatenation of all of the digests contained in pHashList. func (t *TPMContext) PolicyOR(policySession SessionContext, pHashList DigestList, sessions ...SessionContext) error { return t.RunCommand(CommandPolicyOR, sessions, policySession, Delimiter, pHashList) } // PolicyPCR executes the TPM2_PolicyPCR command to gate a policy based on the values of the PCRs selected via the pcrs parameter. If // no digest has been specified via the pcrDigest parameter, then it is a deferred assertion and the policy digest of the session // context associated with policySession will be extended to include the value of the PCR selection and a digest computed from the // selected PCR contents. // // If pcrDigest is provided, then it is a combined assertion. If policySession does not correspond to a trial session, the digest // computed from the selected PCRs will be compared to the value of pcrDigest and a *TPMParameterError error with an error code of // ErrorValue will be returned for parameter index 1 if they don't match, without making any changes to the session context. If // policySession corresponds to a trial session, the digest computed from the selected PCRs is not compared to the value of pcrDigest; // instead, the policy digest of the session is extended to include the value of the PCR selection and the value of pcrDigest. // // If the PCR contents have changed since the last time this command was executed for this session, a *TPMError error will be returned // with an error code of ErrorPCRChanged. func (t *TPMContext) PolicyPCR(policySession SessionContext, pcrDigest Digest, pcrs PCRSelectionList, sessions ...SessionContext) error { return t.RunCommand(CommandPolicyPCR, sessions, policySession, Delimiter, pcrDigest, pcrs) } // func (t *TPMContext) PolicyLocality(policySession HandleContext, locality Locality, sessions ...SessionContext) error { // } // PolicyNV executes the TPM2_PolicyNV command to gate a policy based on the contents of the NV index associated with nvIndex, and is // an immediate assertion. The caller specifies a value to be used for the comparison via the operandB argument, an offset from the // start of the NV index data from which to start the comparison via the offset argument, and a comparison operator via the operation // argument. // // The command requires authorization to read the NV index, defined by the state of the AttrNVPPRead, AttrNVOwnerRead, AttrNVAuthRead // and AttrNVPolicyRead attributes. The handle used for authorization is specified via authContext. If the NV index has the // AttrNVPPRead attribute, authorization can be satisfied with HandlePlatform. If the NV index has the AttrNVOwnerRead attribute, // authorization can be satisfied with HandleOwner. If the NV index has the AttrNVAuthRead or AttrNVPolicyRead attribute, // authorization can be satisfied with nvIndex. The command requires authorization with the user auth role for authContext, with // session based authorization provided via authContextAuthSession. If the resource associated with authContext is not permitted to // authorize this access and policySession does not correspond to a trial session, a *TPMError error with an error code of // ErrorNVAuthorization will be returned. // // If nvIndex is being used for authorization and the AttrNVAuthRead attribute is defined, the authorization can be satisfied by // demonstrating knowledge of the authorization value, either via cleartext or HMAC authorization. If nvIndex is being used for // authorization and the AttrNVPolicyRead attribute is defined, the authorization can be satisfied using a policy session with a // digest that matches the authorization policy for the index. // // If the index associated with nvIndex has the AttrNVReadLocked attribute set and policySession does not correspond to a trial // session, a *TPMError error with an error code of ErrorNVLocked will be returned. // // If the index associated with nvIndex has not been initialized (ie, the AttrNVWritten attribute is not set) and policySession does // not correspond to a trial session, a *TPMError with an error code of ErrorNVUninitialized will be returned. // // If the session associated with policySession is not a trial session and offset is outside of the bounds of the NV index, a // *TPMParameterError error with an error code of ErrorValue is returned for paramter index 2. // // If the session associated with policySession is not a trial session and the size of operandB in combination with the value of // offset would result in a read outside of the bounds of the NV index, a *TPMParameterError error with an error code of ErrorSize // is returned for paramter index 1. // // If the comparison fails and policySession does not correspond to a trial session, a *TPMError error will be returned with an error // code of ErrorPolicy. // // On successful completion, the policy digest of the session context associated with policySession is extended to include the values // of operandB, offset, operation and the name of nvIndex. func (t *TPMContext) PolicyNV(authContext, nvIndex ResourceContext, policySession SessionContext, operandB Operand, offset uint16, operation ArithmeticOp, authContextAuthSession SessionContext, sessions ...SessionContext) error { return t.RunCommand(CommandPolicyNV, sessions, ResourceContextWithSession{Context: authContext, Session: authContextAuthSession}, nvIndex, policySession, Delimiter, operandB, offset, operation) } // PolicyCounterTimer executes the TPM2_PolicyCounterTimer command to gate a policy based on the contents of the TimeInfo structure, // and is an immediate assertion. The caller specifies a value to be used for the comparison via the operandB argument, an offset from // the start of the TimeInfo structure from which to start the comparison via the offset argument, and a comparison operator via the // operation argument. // // If the comparison fails and policySession does not correspond to a trial session, a *TPMError error will be returned with an error // code of ErrorPolicy. // // On successful completion, the policy digest of the session context associated with policySession is extended to include the values // of operandB, offset and operation. func (t *TPMContext) PolicyCounterTimer(policySession SessionContext, operandB Operand, offset uint16, operation ArithmeticOp, sessions ...SessionContext) error { return t.RunCommand(CommandPolicyCounterTimer, sessions, policySession, Delimiter, operandB, offset, operation) } // PolicyCommandCode executes the TPM2_PolicyCommandCode command to indicate that an authorization policy should be limited to a // specific command. Ths is a deferred assertion. // // If the command code is not implemented, a *TPMParameterError error with an error code of ErrorPolicyCC will be returned. If // the session associated with policySession has already been limited to a different command code, a *TPMParameterError error with // an error code of ErrorValue will be returned. // // On successful completion, the policy digest of the session context associated with policySession will be extended to // include the value of the specified command code, and the command code will be recorded on the session context to limit usage of // the session. func (t *TPMContext) PolicyCommandCode(policySession SessionContext, code CommandCode, sessions ...SessionContext) error { return t.RunCommand(CommandPolicyCommandCode, sessions, policySession, Delimiter, code) } // func (t *TPMContext) PolicyPhysicalPresence(policySession HandleContext, sessions ...SessionContext) error { // } // PolicyCpHash executes the TPM2_PolicyCpHash command to bind a policy to a specific command and set of command parameters. This is // a deferred assertion. // // TPMContext.PolicySigned, TPMContext.PolicySecret and TPMContext.PolicyTicket allow an authorizing entity to execute an arbitrary // command as the cpHashA parameter is not included in the session's policy digest. TPMContext.PolicyCommandCode allows the policy // to be limited to a specific command. This command allows the policy to be limited further to a specific command set of command // parameters. // // Command parameter digests can be computed using ComputeCpHash, using the digest algorithm for the session. // // If the size of cpHashA is inconsistent with the digest algorithm for the session, a *TPMParameterError error with an error code // of ErrorSize will be returned. // // If the session associated with policySession already has a command parameter digest, name digest or template digest defined, a // *TPMError error with an error code of ErrorCpHash will be returned if cpHashA does not match the digest already recorded on the // session context. // // On successful completion, the policy digest of the session context associated with policySession will be extended to include the // value of cpHashA, and the value of cpHashA will be recorded on the session context to limit usage of the session to the specific // command and set of command parameters. func (t *TPMContext) PolicyCpHash(policySession SessionContext, cpHashA Digest, sessions ...SessionContext) error { return t.RunCommand(CommandPolicyCpHash, sessions, policySession, Delimiter, cpHashA) } // PolicyNameHash executes the TPM2_PolicyNameHash command to bind a policy to a specific set of TPM entities, without being bound // to the parameters of the command. This is a deferred assertion. // // If the size of nameHash is inconsistent with the digest algorithm for the session, a *TPMParameterError error with an error code // of ErrorSize will be returned. // // If the session associated with policySession already has a name digest, command parameter digest or template digest defined, a // *TPMError error with an error code of ErrorCpHash will be returned. // // On successful completion, the policy digest of the session context associated with policySession will be extended to include the // value of nameHash, and the value of nameHash will be recorded on the session context to limit usage of the session to the specific // set of TPM entities. func (t *TPMContext) PolicyNameHash(policySession SessionContext, nameHash Digest, sessions ...SessionContext) error { return t.RunCommand(CommandPolicyNameHash, sessions, policySession, Delimiter, nameHash) } // PolicyDuplicationSelect executes the TPM2_PolicyDuplicationSelect command to allow the policy to be restricted to duplication // and to allow duplication to a specific new parent. The objectName argument corresponds to the name of the object to be duplicated. // The newParentName argument corresponds to the name of the new parent object. This is a deferred assertion. // // If the session associated with policySession already has a command parameter digest, name digest or template digest defined, a // *TPMError error with an error code of ErrorCpHash will be returned. // // If the session associated with policySession has already been limited to a specific command code, a *TPMError error with an error // code of ErrorCommandCode will be returned. // // On successful completion, the policy digest of the session context associated with policySession will be extended to include the // value of newParentName and includeObject. If includeObject is true, the policy digest of the session will be extended to also // include the value of objectName. A digest of objectName and newParentName will be recorded as the name hash on the session context // to limit usage of the session to those entities, and the CommandDuplicate command code will be recorded to limit usage of the // session to TPMContext.Duplicate. func (t *TPMContext) PolicyDuplicationSelect(policySession SessionContext, objectName, newParentName Name, includeObject bool, sessions ...SessionContext) error { return t.RunCommand(CommandPolicyDuplicationSelect, sessions, policySession, Delimiter, objectName, newParentName, includeObject) } // PolicyAuthorize executes the TPM2_PolicyAuthorize command, which allows policies to change. This is an immediate assertion. The // command allows an authorizing entity to sign a new policy that can be used in an existing policy. The authorizing party signs a // digest that is computed as follows: // digest := H(approvedPolicy||policyRef) // ... where H is the name algorithm of the key used to sign the digest. // // The signature is then verified by TPMContext.VerifySignature, which provides a ticket that is used by this function. // // If the name algorithm of the signing key is not supported, a *TPMParameterError error with an error code of ErrorHash will be // returned for parameter index 3. // // If the length of keySign does not match the length of the name algorithm, a *TPMParameterError error with an error code of // ErrorSize will be returned for parameter index 3. // // If policySession is not associated with a trial session, the current digest of the session associated with policySession will be // compared with approvedPolicy. If they don't match, then a *TPMParameterError error with an error code of ErrorValue will be // returned for parameter index 1. // // If policySession is not associated with a trial session and checkTicket is invalid, a *TPMParameterError error with an error // code of ErrorValue will be returned for parameter index 4. // // On successful completion, the policy digest of the session context associated with policySession is cleared, and then extended to // include the value of keySign and policyRef. func (t *TPMContext) PolicyAuthorize(policySession SessionContext, approvedPolicy Digest, policyRef Nonce, keySign Name, checkTicket *TkVerified, sessions ...SessionContext) error { if checkTicket == nil { checkTicket = &TkVerified{Tag: TagVerified, Hierarchy: HandleNull} } return t.RunCommand(CommandPolicyAuthorize, sessions, policySession, Delimiter, approvedPolicy, policyRef, keySign, checkTicket) } // PolicyAuthValue executes the TPM2_PolicyAuthValue command to bind the policy to the authorization value of the entity on which the // authorization is used. This is a deferred assertion. On successful completion, the policy digest of the session context associated // with policySession will be extended to record that this assertion has been executed, and a flag will be set on the session context // to indicate that the authorization value of the entity on which the authorization is used must be included in the key for computing // the command HMAC when the session is used. // // When using policySession in a subsequent authorization, the authorization value of the entity being authorized must be provided by // calling ResourceContext.SetAuthValue. func (t *TPMContext) PolicyAuthValue(policySession SessionContext, sessions ...SessionContext) error { sessionData := policySession.(*sessionContext).Data() if sessionData == nil { return makeInvalidArgError("policySession", "incomplete session can only be used in TPMContext.FlushContext") } if err := t.RunCommand(CommandPolicyAuthValue, sessions, policySession); err != nil { return err } sessionData.PolicyHMACType = policyHMACTypeAuth return nil } // PolicyPassword executes the TPM2_PolicyPassword command to bind the policy to the authorization value of the entity on which the // authorization is used. This is a deferred assertion. On successful completion, the policy digest of the session context associated // with policySession will be extended to record that this assertion has been executed, and a flag will be set on the session context // to indicate that the authorization value of the entity on which the authorization is used must be included in cleartext in the // command authorization when the session is used. // // When using policySession in a subsequent authorization, the authorization value of the entity being authorized must be provided by // calling ResourceContext.SetAuthValue. func (t *TPMContext) PolicyPassword(policySession SessionContext, sessions ...SessionContext) error { sessionData := policySession.(*sessionContext).Data() if sessionData == nil { return makeInvalidArgError("policySession", "incomplete session can only be used in TPMContext.FlushContext") } if err := t.RunCommand(CommandPolicyPassword, sessions, policySession); err != nil { return err } sessionData.PolicyHMACType = policyHMACTypePassword return nil } // PolicyGetDigest executes the TPM2_PolicyGetDigest command to return the current policy digest of the session context associated // with policySession. func (t *TPMContext) PolicyGetDigest(policySession SessionContext, sessions ...SessionContext) (policyDigest Digest, err error) { if err := t.RunCommand(CommandPolicyGetDigest, sessions, policySession, Delimiter, Delimiter, Delimiter, &policyDigest); err != nil { return nil, err } return policyDigest, nil } // PolicyNvWritten executes the TPM2_PolicyNvWritten command to bind a policy to the value of the AttrNVWritten attribute of the // NV index being authorized, and is a deferred assertion. // // If this command has been executed previously in this session, and the value of writtenSet doesn't match the value provided // previously, a *TPMParameterError error with an error code of ErrorValue will be returned. // // On successful completion, the policy digest of the session associated with policySession will be extended to include the value of // writtenSet. A flag will be set on the session context so that the value of the AttrNVWritten attribute of the NV index being // authorized will be compared to writtenSet when the session is used. func (t *TPMContext) PolicyNvWritten(policySession SessionContext, writtenSet bool, sessions ...SessionContext) error { return t.RunCommand(CommandPolicyNvWritten, sessions, policySession, Delimiter, writtenSet) } // func (t *TPMContext) PolicyTemplate(policySession HandleContext, templateHash Digest, sessions ...SessionContext) error { // } // func (t *TPMContext) PolicyAuthorizeNV(authContext, nvIndex, policySession HandleContext, authContextAuth interface{}, sessions ...SessionContext) error { // } ./github.com/canonical/go-tpm2/cmds_hashhmac.go0000664000000000000000000003043000000000000017730 0ustar0000000000000000// Copyright 2019 Canonical Ltd. // Licensed under the LGPLv3 with static-linking exception. // See LICENCE file for details. package tpm2 // Section 17 - Hash/HMAC/Event Sequences // HMACStart executes the TPM2_HMAC_Start command to begin a HMAC sequence. The context argument corresponds to a loaded HMAC // key. This command requires authorization with the user auth role for context, with session based authorization provided via // contextAuthSession. The command creates a new HMAC sequence object on the TPM. The auth argument defines the authorization value // for the newly created sequence object, which is required for subsequent use of it. // // If context does not correspond to an object with the type ObjectTypeKeyedHash, a *TPMHandleError error with an error code of // ErrorType will be returned. // // If context corresponds to an object with the AttrRestricted attribute set, a *TPMHandleError error with an error code of // ErrorAttributes will be returned. // // If context does not correspond to a signing key, a *TPMHandleError error with an error code of ErrorKey will be returned. // // The hashAlg argument specifies the HMAC algorithm. If the default scheme of the key associated with context is KeyedHashSchemeNull, // then hashAlg must not be HashAlgorithmNull. If the default scheme of the key associated with context is not KeyedHashSchemeNull, // then hashAlg must either be HashAlgorithmNull or must match the key's default scheme, else a *TPMParameterError error with an error // code of ErrorValue will be returned for parameter index 2. // // On success, a ResourceContext corresponding to the newly created HMAC sequence object will be returned. It will not be necessary // to call ResourceContext.SetAuthValue on it - this function sets the correct authorization value so that it can be used in // subsequent commands that require knowledge of the authorization value. func (t *TPMContext) HMACStart(context ResourceContext, auth Auth, hashAlg HashAlgorithmId, contextAuthSession SessionContext, sessions ...SessionContext) (sequenceContext ResourceContext, err error) { var sequenceHandle Handle if err := t.RunCommand(CommandHMACStart, sessions, ResourceContextWithSession{Context: context, Session: contextAuthSession}, Delimiter, auth, hashAlg, Delimiter, &sequenceHandle); err != nil { return nil, err } rc := makeObjectContext(sequenceHandle, nil, nil) rc.authValue = make([]byte, len(auth)) copy(rc.authValue, auth) return rc, nil } // HashSequenceStart executes the TPM2_HashSequenceStart command to begin a hash or event sequence. The command creates a new // sequence object on the TPM. The auth argument defines the authorization value for the newly created sequence object, which is // required for subsequent use of it. // // If hashAlg is HashAlgorithmNull, this function will return a ResourceContext corresponding to a newly created event sequence // object. If hashAlg is not HashAlgorithmNull, this function will return a ResourceContext corresponding to a newly created hash // sequence object. It will not be necessary to call ResourceContext.SetAuthValue on it - this function sets the correct authorization // value so that it can be used in subsequent commands that require knowledge of the authorization value. func (t *TPMContext) HashSequenceStart(auth Auth, hashAlg HashAlgorithmId, sessions ...SessionContext) (sequenceContext ResourceContext, err error) { var sequenceHandle Handle if err := t.RunCommand(CommandHashSequenceStart, sessions, Delimiter, auth, hashAlg, Delimiter, &sequenceHandle); err != nil { return nil, err } rc := makeObjectContext(sequenceHandle, nil, nil) rc.authValue = make([]byte, len(auth)) copy(rc.authValue, auth) return rc, nil } // SequenceUpdate executes the TPM2_SequenceUpdate command to add data to the HMAC, hash or event sequence associated with // sequenceContext. This command requires authorization with the user auth role for sequenceContext, with session based authorization // provided via sequenceContextAuthSession. // // If sequenceContext does not correspond to a sequence object, then a *TPMHandleError error with an error code of ErrorMode will be // returned. // // If sequenceContext corresponds to a hash sequence and the hash sequence is intended to produce a digest that will be signed with // a restricted signing key, the first block of data added to this sequence must be 4 bytes and not the value of TPMGeneratedValue. func (t *TPMContext) SequenceUpdate(sequenceContext ResourceContext, buffer MaxBuffer, sequenceContextAuthSession SessionContext, sessions ...SessionContext) error { return t.RunCommand(CommandSequenceUpdate, sessions, ResourceContextWithSession{Context: sequenceContext, Session: sequenceContextAuthSession}, Delimiter, buffer) } // SequenceComplete executes the TPM2_SequenceComplete command to add the last part of the data the HMAC or hash sequence associated // with sequenceContext, and returns the result. This command requires authorization with the user auth role for sequenceContext, with // session based authorization provided via sequenceContextAuthSession. // // If sequenceContext does not correspond to a HMAC or hash sequence object, then a *TPMHandleError error with an error code of // ErrorMode will be returned. // // If sequenceContext corresponds to a hash sequence and the hash sequence is intended to produce a digest that will be signed with // a restricted signing key, the first block of data added to this sequence must be 4 bytes and not the value of TPMGeneratedValue. // If the returned digest is safe to sign with a restricted signing key, then a ticket that can be passed to TPMContext.Sign will be // returned. In this case, the hierarchy argument is used to specify the hierarchy for the ticket. // // On success, the sequence object associated with sequenceContext will be evicted, and sequenceContext will become invalid. func (t *TPMContext) SequenceComplete(sequenceContext ResourceContext, buffer MaxBuffer, hierarchy Handle, sequenceContextAuthSession SessionContext, sessions ...SessionContext) (result Digest, validation *TkHashcheck, err error) { if err := t.RunCommand(CommandSequenceComplete, sessions, ResourceContextWithSession{Context: sequenceContext, Session: sequenceContextAuthSession}, Delimiter, buffer, hierarchy, Delimiter, Delimiter, &result, &validation); err != nil { return nil, nil, err } if validation.Hierarchy == HandleNull && len(validation.Digest) == 0 { validation = nil } sequenceContext.(handleContextPrivate).invalidate() return result, validation, nil } // EventSequenceComplete executes the TPM2_EventSequenceComplete command to add the last part of the data to the event sequence // associated with sequenceContext, and return the result. This command requires authorization with the user auth role for // sequenceContext, with session based authorization provided via sequenceContextAuthSession. // // If pcrContext is not nil, the result will be extended to the corresponding PCR in the same manner as TPMContext.PCRExtend. // Authorization with the user auth role is required for pcrContext, with session based authorization provided via // pcrContextAuthSession. // // If sequenceContext does not correspond to an event sequence object, then a *TPMHandleError error with an error code of ErrorMode // will be returned for handle index 2. // // If pcrContext is not nil and the corresponding PCR can not be extended from the current locality, a *TPMError error with an // error code of ErrorLocality will be returned. // // On success, the sequence object associated with sequenceContext will be evicted, and sequenceContext will become invalid. func (t *TPMContext) EventSequenceComplete(pcrContext, sequenceContext ResourceContext, buffer MaxBuffer, pcrContextAuthSession, sequenceContextAuthSession SessionContext, sessions ...SessionContext) (results TaggedHashList, err error) { if err := t.RunCommand(CommandEventSequenceComplete, sessions, ResourceContextWithSession{Context: pcrContext, Session: pcrContextAuthSession}, ResourceContextWithSession{Context: sequenceContext, Session: sequenceContextAuthSession}, Delimiter, buffer, Delimiter, Delimiter, &results); err != nil { return nil, err } sequenceContext.(handleContextPrivate).invalidate() return results, nil } // SequenceExecute executes a hash or HMAC sequence to completion and returns the result by adding the provided data to the sequence // with a number of TPM2_SequenceUpdate commands appropriate for the size of buffer, and executing a final TPM2_SequenceComplete // command. This command requires authorization with the user auth role for sequenceContext, with session based authorization provided // via sequenceContextAuthSession. As this function executes multiple commands, any SessionContext instances provided should have the // AttrContinueSession attribute defined. // // If sequenceContext does not correspond to a hash or HMAC sequence object, then a *TPMHandleError error with an error code of // ErrorMode will be returned. // // If sequenceContext corresponds to a hash sequence and the hash sequence is intended to produce a digest that will be signed with // a restricted signing key, the first block of data added to this sequence must be 4 bytes and not the value of TPMGeneratedValue. // If the returned digest is safe to sign with a restricted signing key, then a ticket that can be passed to TPMContext.Sign will be // returned. In this case, the hierarchy argument is used to specify the hierarchy for the ticket. // // On success, the sequence object associated with sequenceContext will be evicted, and sequenceContext will become invalid. func (t *TPMContext) SequenceExecute(sequenceContext ResourceContext, buffer []byte, hierarchy Handle, sequenceContextAuthSession SessionContext, sessions ...SessionContext) (result Digest, validation *TkHashcheck, err error) { if err := t.initPropertiesIfNeeded(); err != nil { return nil, nil, err } total := 0 for len(buffer)-total > t.maxBufferSize { b := buffer[total:] b = b[:t.maxBufferSize] if err := t.SequenceUpdate(sequenceContext, b, sequenceContextAuthSession, sessions...); err != nil { return nil, nil, err } total += len(b) } return t.SequenceComplete(sequenceContext, buffer[total:], hierarchy, sequenceContextAuthSession, sessions...) } // EventSequenceExecute executes an event sequence to completion and returns the result by adding the provided data to the sequence // with a number of TPM2_SequenceUpdate commands appropriate for the size of buffer, and executing a final TPM2_EventSequenceComplete // command. This command requires authorization with the user auth role for sequenceContext, with session based authorization provided // via sequenceContextAuthSession. // // If pcrContext is not nil, the result will be extended to the corresponding PCR in the same manner as TPMContext.PCRExtend. // Authorization with the user auth role is required for pcrContext, with session based authorization provided via // pcrContextAuthSession. // // As this function executes multiple commands, any SessionContext instances provided should have the AttrContinueSession attribute // defined. // // If sequenceContext does not correspond to an event sequence object, then a *TPMHandleError error with an error code of ErrorMode // will be returned for handle index 1 if the command is CommandSequenceUpdate, or handle index 2 if the command is // CommandEventSequenceComplete. // // If pcrContext is not nil and the corresponding PCR can not be extended from the current locality, a *TPMError error with an // error code of ErrorLocality will be returned. // // On success, the sequence object associated with sequenceContext will be evicted, and sequenceContext will become invalid. func (t *TPMContext) EventSequenceExecute(pcrContext, sequenceContext ResourceContext, buffer []byte, pcrContextAuthSession, sequenceContextAuthSession SessionContext, sessions ...SessionContext) (results TaggedHashList, err error) { if err := t.initPropertiesIfNeeded(); err != nil { return nil, err } total := 0 for len(buffer)-total > t.maxBufferSize { b := buffer[total:] b = b[:t.maxBufferSize] if err := t.SequenceUpdate(sequenceContext, b, sequenceContextAuthSession, sessions...); err != nil { return nil, err } total += len(b) } return t.EventSequenceComplete(pcrContext, sequenceContext, buffer[total:], pcrContextAuthSession, sequenceContextAuthSession, sessions...) } ./github.com/canonical/go-tpm2/cmds_hierarchy.go0000664000000000000000000003616300000000000020143 0ustar0000000000000000// Copyright 2019 Canonical Ltd. // Licensed under the LGPLv3 with static-linking exception. // See LICENCE file for details. package tpm2 // Section 24 - Hierarchy Commands import ( "fmt" "github.com/canonical/go-tpm2/mu" ) // CreatePrimary executes the TPM2_CreatePrimary command to create a new primary object in the hierarchy corresponding to // primaryObject. // // The primaryObject parameter should correspond to a hierarchy. The command requires authorization with the user auth role for // primaryObject, with session based authorization provided via primaryObjectAuthSession. // // A template for the object is provided via the inPublic parameter. The Type field of inPublic defines the algorithm for the object. // The NameAlg field defines the digest algorithm for computing the name of the object. The Attrs field defines the attributes of // the object. The AuthPolicy field allows an authorization policy to be defined for the new object. // // Data that will form part of the sensitive area of the object can be provided via inSensitive, which is optional. // // If the Attrs field of inPublic does not have the AttrSensitiveDataOrigin attribute set, then the sensitive data in the created // object is initialized with the data provided via the Data field of inSensitive. // // If the Attrs field of inPublic has the AttrSensitiveDataOrigin attribute set and Type is ObjectTypeSymCipher, then the sensitive // data in the created object is initialized with a TPM generated key. The size of this key is determined by the value of the Params // field of inPublic. If Type is ObjectTypeKeyedHash, then the sensitive data in the created object is initialized with a TPM // generated value that is the same size as the name algorithm selected by the NameAlg field of inPublic. // // If the Type field of inPublic is ObjectTypeRSA or ObjectTypeECC, then the sensitive data in the created object is initialized with // a TPM generated private key. The size of this is determined by the value of the Params field of inPublic. // // If the Type field of inPublic is ObjectTypeKeyedHash and the Attrs field has AttrSensitiveDataOrigin, AttrSign and AttrDecrypt all // clear, then the created object is a sealed data object. // // If the Attrs field of inPublic has the AttrRestricted and AttrDecrypt attributes set, and the Type field is not // ObjectTypeKeyedHash, then the newly created object will be a storage parent. // // If the Attrs field of inPublic has the AttrRestricted and AttrDecrypt attributes set, and the Type field is ObjectTypeKeyedHash, // then the newly created object will be a derivation parent. // // The authorization value for the created object is initialized to the value of the UserAuth field of inSensitive. // // If there are no available slots for new objects on the TPM, a *TPMWarning error with a warning code of WarningObjectMemory will // be returned. // // If the attributes in the Attrs field of inPublic are inconsistent or inappropriate for the usage, a *TPMParameterError error with // an error code of ErrorAttributes will be returned for parameter index 2. // // If the NameAlg field of inPublic is HashAlgorithmNull, then a *TPMParameterError error with an error code of ErrorHash will be // returned for parameter index 2. // // If an authorization policy is defined via the AuthPolicy field of inPublic then the length of the digest must match the name // algorithm selected via the NameAlg field, else a *TPMParameterError error with an error code of ErrorSize is returned for parameter // index 2. // // If the scheme in the Params field of inPublic is inappropriate for the usage, a *TPMParameterError errow with an error code of // ErrorScheme will be returned for parameter index 2. // // If the digest algorithm specified by the scheme in the Params field of inPublic is inappropriate for the usage, a // *TPMParameterError error with an error code of ErrorHash will be returned for parameter index 2. // // If the Type field of inPublic is not ObjectTypeKeyedHash, a *TPMParameterError error with an error code of ErrorSymmetric will be // returned for parameter index 2 if the symmetric algorithm specified in the Params field of inPublic is inappropriate for the // usage. // // If the Type field of inPublic is ObjectTypeECC and the KDF scheme specified in the Params field of inPublic is not // KDFAlgorithmNull, a *TPMParameterError error with an error code of ErrorKDF will be returned for parameter index 2. // // If the length of the UserAuth field of inSensitive is longer than the name algorithm selected by the NameAlg field of inPublic, a // *TPMParameterError error with an error code of ErrorSize will be returned for parameter index 1. // // If the Type field of inPublic is ObjectTypeRSA and the Params field specifies an unsupported exponent, a *TPMError with an error // code of ErrorRange will be returned. If the specified key size is an unsupported value, a *TPMError with an error code of // ErrorValue will be returned. // // If the Type field of inPublic is ObjectTypeSymCipher and the key size is an unsupported value, a *TPMError with an error code of // ErrorKeySize will be returned. If the AttrSensitiveDataOrigin attribute is not set and the length of the Data field of inSensitive // does not match the key size specified in the Params field of inPublic, a *TPMError with an error code of ErrorKeySize will be // returned. // // If the Type field of inPublic is ObjectTypeKeyedHash and the AttrSensitiveDataOrigin attribute is not set, a *TPMError with an // error code of ErrorSize will be returned if the length of the Data field of inSensitive is longer than permitted for the digest // algorithm selected by the specified scheme. // // On success, a ResourceContext instance will be returned that corresponds to the newly created object on the TPM. It will not be // necessary to call ResourceContext.SetAuthValue on it - this function sets the correct authorization value so that it can be used in // subsequent commands that require knowledge of the authorization value. If the Type field of inPublic is ObjectTypeKeyedHash or // ObjectTypeSymCipher, then the returned *Public object will have a Unique field that is the digest of the sensitive data and the // value of the object's seed in the sensitive area, computed using the object's name algorithm. If the Type field of inPublic is // ObjectTypeECC or ObjectTypeRSA, then the returned *Public object will have a Unique field containing details about the public part // of the key, computed from the private part of the key. // // The returned *CreationData will contain a digest computed from the values of PCRs selected by the creationPCR parameter at creation // time in the PCRDigest field. It will also contain the provided outsideInfo in the OutsideInfo field. The returned *TkCreation // ticket can be used to prove the association between the created object and the returned *CreationData via the // TPMContext.CertifyCreation method. func (t *TPMContext) CreatePrimary(primaryObject ResourceContext, inSensitive *SensitiveCreate, inPublic *Public, outsideInfo Data, creationPCR PCRSelectionList, primaryObjectAuthSession SessionContext, sessions ...SessionContext) (objectContext ResourceContext, outPublic *Public, creationData *CreationData, creationHash Digest, creationTicket *TkCreation, err error) { if inSensitive == nil { inSensitive = &SensitiveCreate{} } var objectHandle Handle var name Name if err := t.RunCommand(CommandCreatePrimary, sessions, ResourceContextWithSession{Context: primaryObject, Session: primaryObjectAuthSession}, Delimiter, mu.Sized(inSensitive), mu.Sized(inPublic), outsideInfo, creationPCR, Delimiter, &objectHandle, Delimiter, mu.Sized(&outPublic), mu.Sized(&creationData), &creationHash, &creationTicket, &name); err != nil { return nil, nil, nil, nil, nil, err } if objectHandle.Type() != HandleTypeTransient { return nil, nil, nil, nil, nil, &InvalidResponseError{CommandCreatePrimary, fmt.Sprintf("handle 0x%08x returned from TPM is the wrong type", objectHandle)} } if outPublic == nil || !outPublic.compareName(name) { return nil, nil, nil, nil, nil, &InvalidResponseError{CommandCreatePrimary, "name and public area returned from TPM are not consistent"} } var public *Public if err := mu.CopyValue(&public, outPublic); err != nil { return nil, nil, nil, nil, nil, &InvalidResponseError{CommandCreatePrimary, fmt.Sprintf("cannot copy returned public area from TPM: %v", err)} } rc := makeObjectContext(objectHandle, name, public) rc.authValue = make([]byte, len(inSensitive.UserAuth)) copy(rc.authValue, inSensitive.UserAuth) return rc, outPublic, creationData, creationHash, creationTicket, nil } // HierarchyControl executes the TPM2_HierarchyControl command in order to enable or disable the hierarchy associated with the // enable argument. If state is true, the hierarchy associated with the enable argument will be enabled. If state is false, the // hierarchy associated with the enable argument will be disabled. This command requires authorization with the user auth role // for authContext, with session based authorization provided via authContextAuthSession. // // If enable is HandlePlatform and state is false, then this will disable use of the platform hierarchy. In this case, authContext // must correspond to HandlePlatform. // // If enable is HandlePlatformNV and state is false, then this will disable the use of NV indices with the AttrNVPlatformCreate // attribute set, indicating that they were created by the platform owner. In this case, authContext must correspond to // HandlePlatform. // // If enable is HandleOwner and state is false, then this will disable the use of the storage hierarchy and any NV indices with // the AttrNVPlatformCreate attribute clear. In this case, authContext must correspond to HandleOwner or HandlePlatform. // // If enable is HandleEndorsement and state is false, then this will disable the use of the endorsment hierarchy. In this case, // authContext must correspond to HandleEndorsement or HandlePlatform. // // When a hierarchy is disabled, persistent objects associated with it become unavailable, and transient objects associated with it // are flushed from the TPM. // // If state is true, then authContext must correspond to HandlePlatform. Note that the platform hierarchy can't be re-enabled by // this command. func (t *TPMContext) HierarchyControl(authContext ResourceContext, enable Handle, state bool, authContextAuthSession SessionContext, sessions ...SessionContext) error { return t.RunCommand(CommandHierarchyControl, sessions, ResourceContextWithSession{Context: authContext, Session: authContextAuthSession}, Delimiter, enable, state) } // Clear executes the TPM2_Clear command to remove all context associated with the current owner. The command requires knowledge of // the authorization value for either the platform or lockout hierarchy. The hierarchy is specified by passing a ResourceContext // corresponding to either HandlePlatform or HandleLockout to authContext. The command requires authorization with the user auth // role for authContext, with session based authorization provided via authContextAuthSession. // // On successful completion, all NV indices and objects associated with the current owner will have been evicted and subsequent use of // ResourceContext instances associated with these resources will fail. The authorization values of the storage, endorsement and // lockout hierarchies will have been cleared. It isn't necessary to update the corresponding ResourceContext instances for these // by calling ResourceContext.SetAuthValue in order to use them in subsequent commands that require knowledge of the authorization // value for those permanent resources. // // If the TPM2_Clear command has been disabled, a *TPMError error will be returned with an error code of ErrorDisabled. func (t *TPMContext) Clear(authContext ResourceContext, authContextAuthSession SessionContext, sessions ...SessionContext) error { return t.RunCommandWithResponseCallback(CommandClear, sessions, func() { // Clear auth values for the owner, endorsement and lockout hierarchies. If the supplied session is not // bound to authContext, the TPM will response with a HMAC generated with a key derived from the empty // auth value. for _, h := range []Handle{HandleOwner, HandleEndorsement, HandleLockout} { if rc, exists := t.permanentResources[h]; exists { rc.SetAuthValue(nil) } } }, ResourceContextWithSession{Context: authContext, Session: authContextAuthSession}) } // ClearControl executes the TPM2_ClearControl command to enable or disable execution of the TPM2_Clear command (via the // TPMContext.Clear function). // // If disable is true, then this command will disable the execution of TPM2_Clear. In this case, the command requires knowledge of // the authorization value for the platform or lockout hierarchy. The hierarchy is specified via the authContext parameter by // passing a ResourceContext corresponding to either HandlePlatform or HandleLockout. // // If disable is false, then this command will enable execution of TPM2_Clear. In this case, the command requires knowledge of the // authorization value for the platform hierarchy, and authContext must be a ResourceContext corresponding to HandlePlatform. If // authContext is a HandleContext corresponding to HandleOwner, a *TPMError error with an error code of ErrorAuthFail will be // returned. // // The command requires the authorization with the user auth role for authContext, with session based authorization provided via // authContextAuthSession. func (t *TPMContext) ClearControl(authContext ResourceContext, disable bool, authContextAuthSession SessionContext, sessions ...SessionContext) error { return t.RunCommand(CommandClearControl, sessions, ResourceContextWithSession{Context: authContext, Session: authContextAuthSession}, Delimiter, disable) } // HierarchyChangeAuth executes the TPM2_HierarchyChangeAuth command to change the authorization value for the hierarchy associated // with the authContext parameter. The command requires authorization with the user auth role for authContext, with session based // authorization provided via authContextAuthSession. // // If the value of newAuth is longer than the context integrity digest algorithm for the TPM, a *TPMParameterError error with an // error code of ErrorSize will be returned. // // On successful completion, the authorization value of the hierarchy associated with authContext will be set to the value of // newAuth, and authContext will be updated to reflect this - it isn't necessary to update authContext with // ResourceContext.SetAuthValue in order to use it in subsequent commands that require knowledge of the authorization value for the // resource. func (t *TPMContext) HierarchyChangeAuth(authContext ResourceContext, newAuth Auth, authContextAuthSession SessionContext, sessions ...SessionContext) error { return t.RunCommandWithResponseCallback(CommandHierarchyChangeAuth, sessions, func() { // If the HMAC key for this command includes the auth value for authHandle, the TPM will respond with a HMAC generated with a key // that includes newAuth instead. authContext.SetAuthValue(newAuth) }, ResourceContextWithSession{Context: authContext, Session: authContextAuthSession}, Delimiter, newAuth) } ./github.com/canonical/go-tpm2/cmds_nv.go0000664000000000000000000013466600000000000016617 0ustar0000000000000000// Copyright 2019 Canonical Ltd. // Licensed under the LGPLv3 with static-linking exception. // See LICENCE file for details. package tpm2 // Section 31 - Non-volatile Storage import ( "encoding/binary" "errors" "fmt" "github.com/canonical/go-tpm2/mu" ) // NVDefineSpace executes the TPM2_NV_DefineSpace command to reserve space to hold the data associated with a NV index described by // the publicInfo parameter. The Index field of publicInfo defines the handle at which the index should be reserved. The NameAlg // field defines the digest algorithm for computing the name of the NV index. The Attrs field is used to describe attributes for // the index, as well as its type. An authorization policy for the index can be defined using the AuthPolicy field of publicInfo. // The Size field defines the size of the index. // // The auth parameter specifies an authorization value for the NV index. // // The authContext parameter specifies the hierarchy used for authorization, and should correspond to HandlePlatform or HandleOwner. // The command requires authorization with the user auth role for the specified hierarchy, with session based authorization provided // via authContextAuthSession. // // If the Attrs field of publicInfo has AttrNVPolicyDelete set but TPM2_NV_UndefineSpaceSpecial isn't supported, or the Attrs field // defines a type that is unsupported, a *TPMParameterError error with an error code of ErrorAttributes will be returned for parameter // index 2. // // If the AuthPolicy field of publicInfo defines an authorization policy digest then the digest length must match the size of the // name algorithm defined by the NameAlg field of publicInfo, else a *TPMParameterError error with an error code of ErrorSize will // be returned for parameter index 2. // // If the length of auth is greater than the name algorithm selected by the NameAlg field of the publicInfo parameter, a // *TPMParameterError error with an error code of ErrorSize will be returned for parameter index 1. // // If authContext corresponds to HandlePlatform but the AttrPhEnableNV attribute is clear, a *TPMHandleError error with an error code // of ErrorHierarchy will be returned. // // If the type indicated by the Attrs field of publicInfo isn't supported by the TPM, a *TPMParameterError error with an error code of // ErrorAttributes will be returned for parameter index 2. // // If the type defined by publicInfo is NVTypeCounter, NVTypeBits, NVTypePinPass or NVTypePinFail, the Size field of publicInfo must // be 8. If the type defined by publicInfo is NVTypeExtend, the Size field of publicInfo must match the size of the name algorithm // defined by the NameAlg field. If the size is unexpected, or the size for an index of type NVTypeOrdinary is too large, a // *TPMParameterError error with an error code of ErrorSize will be returned for parameter index 2. // // If the type defined by publicInfo is NVTypeCounter, then the Attrs field must not have the AttrNVClearStClear attribute set, else // a *TPMParameterError error with an error code of ErrorAttributes will be returned for parameter index 2. // // If the type defined by publicInfo is NVTypePinFail, then the Attrs field must have the AttrNVNoDA attribute set. If the type is // either NVTypePinPass or NVTypePinFail, then the Attrs field must have the AttrNVAuthWrite, AttrNVGlobalLock and AttrNVWriteDefine // attributes clear, else a *TPMParameterError error with an error code of ErrorAttributes will be returned for parameter index 2. // // If the Attrs field of publicInfo has either AttrNVWriteLocked, AttrNVReadLocked or AttrNVWritten set, a *TPMParameterError error // with an error code of ErrorAttributes will be returned for parameter index 2. // // The Attrs field of publicInfo must have one of either AttrNVPPWrite, AttrNVOwnerWrite, AttrNVAuthWrite or AttrNVPolicyWrite set, // and must also have one of either AttrNVPPRead, AttrNVOwnerRead, AttrNVAuthRead or AttrNVPolicyRead set. If there is no way to read // or write an index, a *TPMParameterError error with an error code of ErrorAttributes will be returned for parameter index 2. // // If the Attrs field of publicInfo has AttrNVClearStClear set, a *TPMParameterError error with an error code of ErrorAttributes will // be returned for parameter index 2 if AttrNVWriteDefine is set. // // If authContext corresponds to HandlePlatform, then the Attrs field of publicInfo must have the AttrNVPlatformCreate attribute set. // If authContext corresponds to HandleOwner, then the AttrNVPlatformCreate attributes must be clear, else a *TPMHandleError error // with an error code of ErrorAttributes will be returned. // // If the Attrs field of publicInfo has the AttrNVPolicyDelete attribute set, then HandlePlatform must be used for authorization via // authContext, else a *TPMParameterError error with an error code of ErrorAttributes will be returned for parameter index 2. // // If an index is already defined at the location specified by the Index field of publicInfo, a *TPMError error with an error code of // ErrorNVDefined will be returned. // // If there is insufficient space for the index, a *TPMError error with an error code of ErrorNVSpace will be returned. // // On successful completion, the NV index will be defined and a ResourceContext corresponding to the new NV index will be returned. // It will not be necessary to call ResourceContext.SetAuthValue on the returned ResourceContext - this function sets the correct // authorization value so that it can be used in subsequent commands that require knowledge of it. func (t *TPMContext) NVDefineSpace(authContext ResourceContext, auth Auth, publicInfo *NVPublic, authContextAuthSession SessionContext, sessions ...SessionContext) (ResourceContext, error) { if publicInfo == nil { return nil, makeInvalidArgError("publicInfo", "nil value") } name, err := publicInfo.Name() if err != nil { return nil, fmt.Errorf("cannot compute name from public info: %v", err) } if err := t.RunCommand(CommandNVDefineSpace, sessions, ResourceContextWithSession{Context: authContext, Session: authContextAuthSession}, Delimiter, auth, mu.Sized(publicInfo)); err != nil { return nil, err } var public *NVPublic // publicInfo already marshalled correctly, so this can't fail. mu.MustCopyValue(&public, publicInfo) rc := makeNVIndexContext(name, public) rc.authValue = make([]byte, len(auth)) copy(rc.authValue, auth) return rc, nil } // NVUndefineSpace executes the TPM2_NV_UndefineSpace command to remove the NV index associated with nvIndex, and free the resources // used by it. If the index has the AttrNVPolicyDelete attribute set, then a *TPMHandleError error with an error code of // ErrorAttributes will be returned for handle index 2. // // The authContext parameter specifies the hierarchy used for authorization and should correspond to either HandlePlatform or // HandleOwner. The command requires authorization with the user auth role for the specified hierarchy, with session based // authorization provided via authContextAuthSession. // // If authContext corresponds to HandleOwner and the NV index has the AttrNVPlatformCreate attribute set, then a *TPMError error with // an error code of ErrorNVAuthorization will be returned. // // On successful completion, nvIndex will be invalidated. func (t *TPMContext) NVUndefineSpace(authContext, nvIndex ResourceContext, authContextAuthSession SessionContext, sessions ...SessionContext) error { if err := t.RunCommand(CommandNVUndefineSpace, sessions, ResourceContextWithSession{Context: authContext, Session: authContextAuthSession}, nvIndex); err != nil { return err } nvIndex.(handleContextPrivate).invalidate() return nil } // NVUndefineSpaceSpecial executes the TPM2_NV_UndefineSpaceSpecial command to remove the NV index associated with nvIndex, and free // the resources used by it. If the NV index does not have the AttrNVPolicyDelete attribute set, then a *TPMHandleError error with an // error code of ErrorAttributes will be returned for handle index 1. // // The platform parameter must correspond to HandlePlatform. The command requires authorization with the user auth role for the // platform hierarchy, with session based authorization provided via platformAuthSession. The command requires authorization with the // admin role for nvIndex, with the session provided via nvIndexAuthSession. // // On successful completion, nvIndex will be invalidated. func (t *TPMContext) NVUndefineSpaceSpecial(nvIndex, platform ResourceContext, nvIndexAuthSession, platformAuthSession SessionContext, sessions ...SessionContext) error { if err := t.RunCommandWithResponseCallback(CommandNVUndefineSpaceSpecial, sessions, func() { // If the HMAC key for this command includes the authorization value for nvIndex (eg, because the PolicyAuthValue assertion was // executed), the TPM will respond with a HMAC generated with a key based on an empty auth value. nvIndex.SetAuthValue(nil) }, ResourceContextWithSession{Context: nvIndex, Session: nvIndexAuthSession}, ResourceContextWithSession{Context: platform, Session: platformAuthSession}); err != nil { return err } nvIndex.(handleContextPrivate).invalidate() return nil } // NVReadPublic executes the TPM2_NV_ReadPublic command to read the public area of the NV index associated with nvIndex. func (t *TPMContext) NVReadPublic(nvIndex HandleContext, sessions ...SessionContext) (nvPublic *NVPublic, nvName Name, err error) { if err := t.RunCommand(CommandNVReadPublic, sessions, nvIndex, Delimiter, Delimiter, Delimiter, mu.Sized(&nvPublic), &nvName); err != nil { return nil, nil, err } return nvPublic, nvName, nil } // NVWriteRaw executes the TPM2_NV_Write command to write data to the NV index associated with nvIndex, at the specified offset. // // The command requires authorization, defined by the state of the AttrNVPPWrite, AttrNVOwnerWrite, AttrNVAuthWrite and // AttrNVPolicyWrite attributes. The handle used for authorization is specified via authContext. If the NV index has the AttrNVPPWrite // attribute, authorization can be satisfied with HandlePlatform. If the NV index has the AttrNVOwnerWrite attribute, authorization // can be satisfied with HandleOwner. If the NV index has the AttrNVAuthWrite or AttrNVPolicyWrite attribute, authorization can be // satisfied with nvIndex. The command requires authorization with the user auth role for authContext, with session based // authorization provided via authContextAuthSession. If the resource associated with authContext is not permitted to authorize this // access, a *TPMError error with an error code of ErrorNVAuthorization will be returned. // // If nvIndex is being used for authorization and the AttrNVAuthWrite attribute is defined, the authorization can be satisfied by // demonstrating knowledge of the authorization value, either via cleartext or HMAC authorization. If nvIndex is being used for // authorization and the AttrNVPolicyWrite attribute is defined, the authorization can be satisfied using a policy session with a // digest that matches the authorization policy for the index. // // If the index has the AttrNVWriteLocked attribute set, a *TPMError error with an error code of ErrorNVLocked will be returned. // // If the type of the index is NVTypeCounter, NVTypeBits or NVTypeExtend, a *TPMError error with an error code fo ErrorAttributes // will be returned. // // If the value of offset is outside of the bounds of the index, a *TPMParameterError error with an error code of ErrorValue will be // returned for parameter index 2. // // If the length of the data and the specified offset would result in a write outside of the bounds of the index, or if the index // has the AttrNVWriteAll attribute set and the size of the data doesn't match the size of the index, a *TPMError error with an error // code of ErrorNVRange will be returned. // // On successful completion, the AttrNVWritten flag will be set if this is the first time that the index has been written to. func (t *TPMContext) NVWriteRaw(authContext, nvIndex ResourceContext, data MaxNVBuffer, offset uint16, authContextAuthSession SessionContext, sessions ...SessionContext) error { if err := t.RunCommand(CommandNVWrite, sessions, ResourceContextWithSession{Context: authContext, Session: authContextAuthSession}, nvIndex, Delimiter, data, offset); err != nil { return err } nvIndex.(*nvIndexContext).SetAttr(AttrNVWritten) return nil } // NVWrite executes the TPM2_NV_Write command to write data to the NV index associated with nvIndex, at the specified offset. // // The command requires authorization, defined by the state of the AttrNVPPWrite, AttrNVOwnerWrite, AttrNVAuthWrite and // AttrNVPolicyWrite attributes. The handle used for authorization is specified via authContext. If the NV index has the AttrNVPPWrite // attribute, authorization can be satisfied with HandlePlatform. If the NV index has the AttrNVOwnerWrite attribute, authorization // can be satisfied with HandleOwner. If the NV index has the AttrNVAuthWrite or AttrNVPolicyWrite attribute, authorization can be // satisfied with nvIndex. The command requires authorization with the user auth role for authContext, with session based // authorization provided via authContextAuthSession. If the resource associated with authContext is not permitted to authorize this // access, a *TPMError error with an error code of ErrorNVAuthorization will be returned. // // If nvIndex is being used for authorization and the AttrNVAuthWrite attribute is defined, the authorization can be satisfied by // demonstrating knowledge of the authorization value, either via cleartext or HMAC authorization. If nvIndex is being used for // authorization and the AttrNVPolicyWrite attribute is defined, the authorization can be satisfied using a policy session with a // digest that matches the authorization policy for the index. // // If data is too large to be written in a single command, this function will re-execute the TPM2_NV_Write command until all data is // written. In this case, any SessionContext instances provided must have the AttrContinueSession attribute defined and // authContextAuthSession must not be a policy session. // // If the index has the AttrNVWriteLocked attribute set, a *TPMError error with an error code of ErrorNVLocked will be returned. // // If the type of the index is NVTypeCounter, NVTypeBits or NVTypeExtend, a *TPMError error with an error code fo ErrorAttributes // will be returned. // // If the value of offset is outside of the bounds of the index, a *TPMParameterError error with an error code of ErrorValue will be // returned for parameter index 2. // // If the length of the data and the specified offset would result in a write outside of the bounds of the index, or if the index // has the AttrNVWriteAll attribute set and the size of the data doesn't match the size of the index, a *TPMError error with an error // code of ErrorNVRange will be returned. // // On successful completion, the AttrNVWritten flag will be set if this is the first time that the index has been written to. func (t *TPMContext) NVWrite(authContext, nvIndex ResourceContext, data []byte, offset uint16, authContextAuthSession SessionContext, sessions ...SessionContext) error { if err := t.initPropertiesIfNeeded(); err != nil { return err } if len(data) > t.maxNVBufferSize { if authContextAuthSession != nil { sessionPrivate := authContextAuthSession.(*sessionContext) if sessionPrivate.attrs&AttrContinueSession == 0 { return makeInvalidArgError("authContextAuthSession", fmt.Sprintf("the AttrContinueSession attribute is required for authorization sessions for writes larger than %d bytes", t.maxNVBufferSize)) } sessionData := sessionPrivate.Data() if sessionData == nil { return makeInvalidArgError("authContextAuthSession", "unusable session context") } if sessionData.SessionType == SessionTypePolicy { return makeInvalidArgError("authContextAuthSession", fmt.Sprintf("a policy authorization session cannot be used for writes larger than %d bytes", t.maxNVBufferSize)) } } for i, s := range sessions { if s.(*sessionContext).attrs&AttrContinueSession == 0 { return makeInvalidArgError("sessions", fmt.Sprintf("the AttrContinueSession attribute is required for session at index %d for writes larger than %d bytes", i, t.maxNVBufferSize)) } } } total := 0 for { d := data[total:] if len(d) > t.maxNVBufferSize { d = d[:t.maxNVBufferSize] } if err := t.NVWriteRaw(authContext, nvIndex, d, offset+uint16(total), authContextAuthSession, sessions...); err != nil { return err } total += len(d) if len(data)-total == 0 { break } } return nil } // NVSetPinCounterParams is a convenience function for NVWrite for updating the contents of the NV pin pass or NV pin fail index associated // with nvIndex. If the type of nvIndex is not NVTypePinPass of NVTypePinFail, an error will be returned. // // The command requires authorization, defined by the state of the AttrNVPPWrite, AttrNVOwnerWrite, AttrNVAuthWrite and // AttrNVPolicyWrite attributes. The handle used for authorization is specified via authContext. If the NV index has the AttrNVPPWrite // attribute, authorization can be satisfied with HandlePlatform. If the NV index has the AttrNVOwnerWrite attribute, authorization // can be satisfied with HandleOwner. If the NV index has the AttrNVAuthWrite or AttrNVPolicyWrite attribute, authorization can be // satisfied with nvIndex. The command requires authorization with the user auth role for authContext, with session based // authorization provided via authContextAuthSession. If the resource associated with authContext is not permitted to authorize this // access, a *TPMError error with an error code of ErrorNVAuthorization will be returned. // // If nvIndex is being used for authorization and the AttrNVAuthWrite attribute is defined, the authorization can be satisfied by // demonstrating knowledge of the authorization value, either via cleartext or HMAC authorization. If nvIndex is being used for // authorization and the AttrNVPolicyWrite attribute is defined, the authorization can be satisfied using a policy session with a // digest that matches the authorization policy for the index. // // If the index has the AttrNVWriteLocked attribute set, a *TPMError error with an error code of ErrorNVLocked will be returned. // // On successful completion, the AttrNVWritten flag will be set if this is the first time that the index has been written to. func (t *TPMContext) NVSetPinCounterParams(authContext, nvIndex ResourceContext, params *NVPinCounterParams, authContextAuthSession SessionContext, sessions ...SessionContext) error { context, isNv := nvIndex.(*nvIndexContext) if !isNv { return errors.New("nvIndex does not correspond to a NV index") } if context.Attrs().Type() != NVTypePinPass && context.Attrs().Type() != NVTypePinFail { return errors.New("nvIndex does not correspond to a PIN pass or PIN fail index") } data := mu.MustMarshalToBytes(params) return t.NVWrite(authContext, nvIndex, data, 0, authContextAuthSession, sessions...) } // NVIncrement executes the TPM2_NV_Increment command to increment the counter associated with nvIndex. // // The command requires authorization, defined by the state of the AttrNVPPWrite, AttrNVOwnerWrite, AttrNVAuthWrite and // AttrNVPolicyWrite attributes. The handle used for authorization is specified via authContext. If the NV index has the AttrNVPPWrite // attribute, authorization can be satisfied with HandlePlatform. If the NV index has the AttrNVOwnerWrite attribute, authorization // can be satisfied with HandleOwner. If the NV index has the AttrNVAuthWrite or AttrNVPolicyWrite attribute, authorization can be // satisfied with nvIndex. The command requires authorization with the user auth role for authContext, with session based // authorization provided via authContextAuthSession. If the resource associated with authContext is not permitted to authorize this // access, a *TPMError error with an error code of ErrorNVAuthorization will be returned. // // If nvIndex is being used for authorization and the AttrNVAuthWrite attribute is defined, the authorization can be satisfied by // demonstrating knowledge of the authorization value, either via cleartext or HMAC authorization. If nvIndex is being used for // authorization and the AttrNVPolicyWrite attribute is defined, the authorization can be satisfied using a policy session with a // digest that matches the authorization policy for the index. // // If the index has the AttrNVWriteLocked attribute set, a *TPMError error with an error code of ErrorNVLocked will be returned. // // If the type of the index is not NVTypeCounter, a *TPMHandleError error with an error code of ErrorAttributes will be returned for // handle index 2. // // On successful completion, the AttrNVWritten flag will be set if this is the first time that the index has been written to. func (t *TPMContext) NVIncrement(authContext, nvIndex ResourceContext, authContextAuthSession SessionContext, sessions ...SessionContext) error { if err := t.RunCommand(CommandNVIncrement, sessions, ResourceContextWithSession{Context: authContext, Session: authContextAuthSession}, nvIndex); err != nil { return err } nvIndex.(*nvIndexContext).SetAttr(AttrNVWritten) return nil } // NVExtend executes the TPM2_NV_Extend command to extend data to the NV index associated with nvIndex, using the index's name // algorithm. // // The command requires authorization, defined by the state of the AttrNVPPWrite, AttrNVOwnerWrite, AttrNVAuthWrite and // AttrNVPolicyWrite attributes. The handle used for authorization is specified via authContext. If the NV index has the AttrNVPPWrite // attribute, authorization can be satisfied with HandlePlatform. If the NV index has the AttrNVOwnerWrite attribute, authorization // can be satisfied with HandleOwner. If the NV index has the AttrNVAuthWrite or AttrNVPolicyWrite attribute, authorization can be // satisfied with nvIndex. The command requires authorization with the user auth role for authContext, with session based // authorization provided via authContextAuthSession. If the resource associated with authContext is not permitted to authorize this // access, a *TPMError error with an error code of ErrorNVAuthorization will be returned. // // If nvIndex is being used for authorization and the AttrNVAuthWrite attribute is defined, the authorization can be satisfied by // demonstrating knowledge of the authorization value, either via cleartext or HMAC authorization. If nvIndex is being used for // authorization and the AttrNVPolicyWrite attribute is defined, the authorization can be satisfied using a policy session with a // digest that matches the authorization policy for the index. // // If the index has the AttrNVWriteLocked attribute set, a *TPMError error with an error code of ErrorNVLocked will be returned. // // If the type of the index is not NVTypeExtend, a *TPMHandleError error with an error code of ErrorAttributes will be returned for // handle index 2. // // On successful completion, the AttrNVWritten flag will be set if this is the first time that the index has been written to. func (t *TPMContext) NVExtend(authContext, nvIndex ResourceContext, data MaxNVBuffer, authContextAuthSession SessionContext, sessions ...SessionContext) error { if err := t.RunCommand(CommandNVExtend, sessions, ResourceContextWithSession{Context: authContext, Session: authContextAuthSession}, nvIndex, Delimiter, data); err != nil { return err } nvIndex.(*nvIndexContext).SetAttr(AttrNVWritten) return nil } // NVSetBits executes the TPM2_NV_SetBits command to OR the value of bits with the contents of the NV index associated with nvIndex. // // The command requires authorization, defined by the state of the AttrNVPPWrite, AttrNVOwnerWrite, AttrNVAuthWrite and // AttrNVPolicyWrite attributes. The handle used for authorization is specified via authContext. If the NV index has the AttrNVPPWrite // attribute, authorization can be satisfied with HandlePlatform. If the NV index has the AttrNVOwnerWrite attribute, authorization // can be satisfied with HandleOwner. If the NV index has the AttrNVAuthWrite or AttrNVPolicyWrite attribute, authorization can be // satisfied with nvIndex. The command requires authorization with the user auth role for authContext, with session based // authorization provided via authContextAuthSession. If the resource associated with authContext is not permitted to authorize this // access, a *TPMError error with an error code of ErrorNVAuthorization will be returned. // // If nvIndex is being used for authorization and the AttrNVAuthWrite attribute is defined, the authorization can be satisfied by // demonstrating knowledge of the authorization value, either via cleartext or HMAC authorization. If nvIndex is being used for // authorization and the AttrNVPolicyWrite attribute is defined, the authorization can be satisfied using a policy session with a // digest that matches the authorization policy for the index. // // If the index has the AttrNVWriteLocked attribute set, a *TPMError error with an error code of ErrorNVLocked will be returned. // // If the type of the index is not NVTypeBits, a *TPMHandleError error with an error code of ErrorAttributes will be returned for // handle index 2. // // On successful completion, the AttrNVWritten flag will be set if this is the first time that the index has been written to. func (t *TPMContext) NVSetBits(authContext, nvIndex ResourceContext, bits uint64, authContextAuthSession SessionContext, sessions ...SessionContext) error { if err := t.RunCommand(CommandNVSetBits, sessions, ResourceContextWithSession{Context: authContext, Session: authContextAuthSession}, nvIndex, Delimiter, bits); err != nil { return err } nvIndex.(*nvIndexContext).SetAttr(AttrNVWritten) return nil } // NVWriteLock executes the TPM2_NV_WriteLock command to inhibit further writes to the NV index associated with nvIndex. // // The command requires authorization, defined by the state of the AttrNVPPWrite, AttrNVOwnerWrite, AttrNVAuthWrite and // AttrNVPolicyWrite attributes. The handle used for authorization is specified via authContext. If the NV index has the AttrNVPPWrite // attribute, authorization can be satisfied with HandlePlatform. If the NV index has the AttrNVOwnerWrite attribute, authorization // can be satisfied with HandleOwner. If the NV index has the AttrNVAuthWrite or AttrNVPolicyWrite attribute, authorization can be // satisfied with nvIndex. The command requires authorization with the user auth role for authContext, with session based // authorization provided via authContextAuthSession. If the resource associated with authContext is not permitted to authorize this // command, a *TPMError error with an error code of ErrorNVAuthorization will be returned. // // If nvIndex is being used for authorization and the AttrNVAuthWrite attribute is defined, the authorization can be satisfied by // demonstrating knowledge of the authorization value, either via cleartext or HMAC authorization. If nvIndex is being used for // authorization and the AttrNVPolicyWrite attribute is defined, the authorization can be satisfied using a policy session with a // digest that matches the authorization policy for the index. // // If the index has neither the AttrNVWriteDefine or AttrNVWriteStClear attributes set, then a *TPMHandleError error with an error // code of ErrorAttributes will be returned for handle index 2. // // On successful completion, the AttrNVWriteLocked attribute will be set. It will be cleared again (and writes will be reenabled) on // the next TPM reset or TPM restart unless the index has the AttrNVWriteDefine attribute set and AttrNVWritten attribute is set. func (t *TPMContext) NVWriteLock(authContext, nvIndex ResourceContext, authContextAuthSession SessionContext, sessions ...SessionContext) error { if err := t.RunCommand(CommandNVWriteLock, sessions, ResourceContextWithSession{Context: authContext, Session: authContextAuthSession}, nvIndex); err != nil { return err } nvIndex.(*nvIndexContext).SetAttr(AttrNVWriteLocked) return nil } // NVGlobalWriteLock executes the TPM2_NV_GlobalWriteLock command to inhibit further writes for all NV indexes that have the // AttrNVGlobalLock attribute set. // // The authContext parameter specifies a hierarchy, and should correspond to either HandlePlatform or HandleOwner. The command // requires the user auth role for authContext, with session based authorization provided via authContextAuthSession. // // On successful completion, the AttrNVWriteLocked attribute will be set for all NV indexes that have the AttrNVGlobalLock attribute // set. If an index also has the AttrNVWriteDefine attribute set, this will permanently inhibit further writes unless AttrNVWritten // is clear. ResourceContext instances associated with NV indices that are updated as a consequence of this function will no longer // be able to be used because the name will be incorrect. func (t *TPMContext) NVGlobalWriteLock(authContext ResourceContext, authContextAuthSession SessionContext, sessions ...SessionContext) error { return t.RunCommand(CommandNVGlobalWriteLock, sessions, ResourceContextWithSession{Context: authContext, Session: authContextAuthSession}) } // NVReadRaw executes the TPM2_NV_Read command to read the contents of the NV index associated with nvIndex. The amount of data to read, // and the offset within the index are defined by the size and offset parameters. // // The command requires authorization, defined by the state of the AttrNVPPRead, AttrNVOwnerRead, AttrNVAuthRead and AttrNVPolicyRead // attributes. The handle used for authorization is specified via authContext. If the NV index has the AttrNVPPRead attribute, // authorization can be satisfied with HandlePlatform. If the NV index has the AttrNVOwnerRead attribute, authorization can be // satisfied with HandleOwner. If the NV index has the AttrNVAuthRead or AttrNVPolicyRead attribute, authorization can be satisfied // with nvIndex. The command requires authorization with the user auth role for authContext, with session based authorization provided // via authContextAuthSession. If the resource associated with authContext is not permitted to authorize this access, a *TPMError // error with an error code of ErrorNVAuthorization will be returned. // // If nvIndex is being used for authorization and the AttrNVAuthRead attribute is defined, the authorization can be satisfied by // demonstrating knowledge of the authorization value, either via cleartext or HMAC authorization. If nvIndex is being used for // authorization and the AttrNVPolicyRead attribute is defined, the authorization can be satisfied using a policy session with a // digest that matches the authorization policy for the index. // // If the index has the AttrNVReadLocked attribute set, a *TPMError error with an error code of ErrorNVLocked will be returned. // // If the index has not been initialized (ie, the AttrNVWritten attribute is not set), a *TPMError error with an error code of // ErrorNVUninitialized will be returned. // // If the value of size is too large, a *TPMParameterError error with an error code of ErrorValue will be returned for parameter // index 1. // // If the value of offset falls outside of the bounds of the index, a *TPMParameterError error with an error code of ErrorValue will // be returned for parameter index 2. // // If the data selection falls outside of the bounds of the index, a *TPMError error with an error code of ErrorNVRange will be // returned. // // On successful completion, the requested data will be returned. func (t *TPMContext) NVReadRaw(authContext, nvIndex ResourceContext, size, offset uint16, authContextAuthSession SessionContext, sessions ...SessionContext) (data MaxNVBuffer, err error) { if err := t.RunCommand(CommandNVRead, sessions, ResourceContextWithSession{Context: authContext, Session: authContextAuthSession}, nvIndex, Delimiter, size, offset, Delimiter, Delimiter, &data); err != nil { return nil, err } return data, nil } // NVRead executes the TPM2_NV_Read command to read the contents of the NV index associated with nvIndex. The amount of data to read, // and the offset within the index are defined by the size and offset parameters. // // The command requires authorization, defined by the state of the AttrNVPPRead, AttrNVOwnerRead, AttrNVAuthRead and AttrNVPolicyRead // attributes. The handle used for authorization is specified via authContext. If the NV index has the AttrNVPPRead attribute, // authorization can be satisfied with HandlePlatform. If the NV index has the AttrNVOwnerRead attribute, authorization can be // satisfied with HandleOwner. If the NV index has the AttrNVAuthRead or AttrNVPolicyRead attribute, authorization can be satisfied // with nvIndex. The command requires authorization with the user auth role for authContext, with session based authorization provided // via authContextAuthSession. If the resource associated with authContext is not permitted to authorize this access, a *TPMError // error with an error code of ErrorNVAuthorization will be returned. // // If nvIndex is being used for authorization and the AttrNVAuthRead attribute is defined, the authorization can be satisfied by // demonstrating knowledge of the authorization value, either via cleartext or HMAC authorization. If nvIndex is being used for // authorization and the AttrNVPolicyRead attribute is defined, the authorization can be satisfied using a policy session with a // digest that matches the authorization policy for the index. // // If the requested data can not be read in a single command, this function will re-execute the TPM2_NV_Read command until all data // is read. In this case, any SessionContext instances provided should have the AttrContinueSession attribute defined and // authContextAuth should not correspond to a policy session. // // If the index has the AttrNVReadLocked attribute set, a *TPMError error with an error code of ErrorNVLocked will be returned. // // If the index has not been initialized (ie, the AttrNVWritten attribute is not set), a *TPMError error with an error code of // ErrorNVUninitialized will be returned. // // If the value of size is too large, a *TPMParameterError error with an error code of ErrorValue will be returned for parameter // index 1. // // If the value of offset falls outside of the bounds of the index, a *TPMParameterError error with an error code of ErrorValue will // be returned for parameter index 2. // // If the data selection falls outside of the bounds of the index, a *TPMError error with an error code of ErrorNVRange will be // returned. // // On successful completion, the requested data will be returned. func (t *TPMContext) NVRead(authContext, nvIndex ResourceContext, size, offset uint16, authContextAuthSession SessionContext, sessions ...SessionContext) (data []byte, err error) { if err := t.initPropertiesIfNeeded(); err != nil { return nil, err } data = make([]byte, size) total := 0 remaining := size for { sz := remaining if remaining > uint16(t.maxNVBufferSize) { sz = uint16(t.maxNVBufferSize) } tmpData, err := t.NVReadRaw(authContext, nvIndex, sz, offset+uint16(total), authContextAuthSession, sessions...) if err != nil { return nil, err } copy(data[total:], tmpData) total += int(sz) remaining -= sz if remaining == 0 { break } } return data, nil } func (t *TPMContext) nvReadUint64(authContext, nvIndex ResourceContext, authContextAuthSession SessionContext, sessions ...SessionContext) (uint64, error) { data, err := t.NVRead(authContext, nvIndex, 8, 0, authContextAuthSession, sessions...) if err != nil { return 0, err } if len(data) != binary.Size(uint64(0)) { return 0, &InvalidResponseError{CommandNVRead, fmt.Sprintf("unexpected number of bytes returned (got %d)", len(data))} } return binary.BigEndian.Uint64(data), nil } // NVReadBits is a convenience function for NVRead for reading the contents of the NV bit field index associated with nvIndex. If // the type of nvIndex is not NVTypeBits, an error will be returned. // // The command requires authorization, defined by the state of the AttrNVPPRead, AttrNVOwnerRead, AttrNVAuthRead and AttrNVPolicyRead // attributes. The handle used for authorization is specified via authContext. If the NV index has the AttrNVPPRead attribute, // authorization can be satisfied with HandlePlatform. If the NV index has the AttrNVOwnerRead attribute, authorization can be // satisfied with HandleOwner. If the NV index has the AttrNVAuthRead or AttrNVPolicyRead attribute, authorization can be satisfied // with nvIndex. The command requires authorization with the user auth role for authContext, with session based authorization provided // via authContextAuthSession. If the resource associated with authContext is not permitted to authorize this access, a *TPMError // error with an error code of ErrorNVAuthorization will be returned. // // If nvIndex is being used for authorization and the AttrNVAuthRead attribute is defined, the authorization can be satisfied by // demonstrating knowledge of the authorization value, either via cleartext or HMAC authorization. If nvIndex is being used for // authorization and the AttrNVPolicyRead attribute is defined, the authorization can be satisfied using a policy session with a // digest that matches the authorization policy for the index. // // If the index has the AttrNVReadLocked attribute set, a *TPMError error with an error code of ErrorNVLocked will be returned. // // If the index has not been initialized (ie, the AttrNVWritten attribute is not set), a *TPMError error with an error code of // ErrorNVUninitialized will be returned. // // On successful completion, the current bitfield value will be returned. func (t *TPMContext) NVReadBits(authContext, nvIndex ResourceContext, authContextAuthSession SessionContext, sessions ...SessionContext) (uint64, error) { context, isNv := nvIndex.(*nvIndexContext) if !isNv { return 0, errors.New("nvIndex does not correspond to a NV index") } if context.Attrs().Type() != NVTypeBits { return 0, errors.New("nvIndex does not correspond to a bit field") } return t.nvReadUint64(authContext, nvIndex, authContextAuthSession, sessions...) } // NVReadCounter is a convenience function for NVRead for reading the contents of the NV counter index associated with nvIndex. If the // type of nvIndex is not NVTypeCounter, an error will be returned. // // The command requires authorization, defined by the state of the AttrNVPPRead, AttrNVOwnerRead, AttrNVAuthRead and AttrNVPolicyRead // attributes. The handle used for authorization is specified via authContext. If the NV index has the AttrNVPPRead attribute, // authorization can be satisfied with HandlePlatform. If the NV index has the AttrNVOwnerRead attribute, authorization can be // satisfied with HandleOwner. If the NV index has the AttrNVAuthRead or AttrNVPolicyRead attribute, authorization can be satisfied // with nvIndex. The command requires authorization with the user auth role for authContext, with session based authorization provided // via authContextAuthSession. If the resource associated with authContext is not permitted to authorize this access, a *TPMError // error with an error code of ErrorNVAuthorization will be returned. // // If nvIndex is being used for authorization and the AttrNVAuthRead attribute is defined, the authorization can be satisfied by // demonstrating knowledge of the authorization value, either via cleartext or HMAC authorization. If nvIndex is being used for // authorization and the AttrNVPolicyRead attribute is defined, the authorization can be satisfied using a policy session with a // digest that matches the authorization policy for the index. // // If the index has the AttrNVReadLocked attribute set, a *TPMError error with an error code of ErrorNVLocked will be returned. // // If the index has not been initialized (ie, the AttrNVWritten attribute is not set), a *TPMError error with an error code of // ErrorNVUninitialized will be returned. // // On successful completion, the current counter value will be returned. func (t *TPMContext) NVReadCounter(authContext, nvIndex ResourceContext, authContextAuthSession SessionContext, sessions ...SessionContext) (uint64, error) { context, isNv := nvIndex.(*nvIndexContext) if !isNv { return 0, errors.New("nvIndex does not correspond to a NV index") } if context.Attrs().Type() != NVTypeCounter { return 0, errors.New("nvIndex does not correspond to a counter") } return t.nvReadUint64(authContext, nvIndex, authContextAuthSession, sessions...) } // NVReadPinCounterParams is a convenienc function for NVRead for reading the contents of the NV pin pass or NV pin fail index associated // with nvIndex. If the type of nvIndex is not NVTypePinPass of NVTypePinFail, an error will be returned. // // The command requires authorization, defined by the state of the AttrNVPPRead, AttrNVOwnerRead, AttrNVAuthRead and AttrNVPolicyRead // attributes. The handle used for authorization is specified via authContext. If the NV index has the AttrNVPPRead attribute, // authorization can be satisfied with HandlePlatform. If the NV index has the AttrNVOwnerRead attribute, authorization can be // satisfied with HandleOwner. If the NV index has the AttrNVAuthRead or AttrNVPolicyRead attribute, authorization can be satisfied // with nvIndex. The command requires authorization with the user auth role for authContext, with session based authorization provided // via authContextAuthSession. If the resource associated with authContext is not permitted to authorize this access, a *TPMError // error with an error code of ErrorNVAuthorization will be returned. // // If nvIndex is being used for authorization and the AttrNVAuthRead attribute is defined, the authorization can be satisfied by // demonstrating knowledge of the authorization value, either via cleartext or HMAC authorization. If nvIndex is being used for // authorization and the AttrNVPolicyRead attribute is defined, the authorization can be satisfied using a policy session with a // digest that matches the authorization policy for the index. // // If the index has the AttrNVReadLocked attribute set, a *TPMError error with an error code of ErrorNVLocked will be returned. // // If the index has not been initialized (ie, the AttrNVWritten attribute is not set), a *TPMError error with an error code of // ErrorNVUninitialized will be returned. // // On successful completion, the current PIN count and limit will be returned. func (t *TPMContext) NVReadPinCounterParams(authContext, nvIndex ResourceContext, authContextAuthSession SessionContext, sessions ...SessionContext) (*NVPinCounterParams, error) { context, isNv := nvIndex.(*nvIndexContext) if !isNv { return nil, errors.New("nvIndex does not correspond to a NV index") } if context.Attrs().Type() != NVTypePinPass && context.Attrs().Type() != NVTypePinFail { return nil, errors.New("nvIndex does not correspond to a PIN pass or PIN fail index") } data, err := t.NVRead(authContext, nvIndex, 8, 0, authContextAuthSession, sessions...) if err != nil { return nil, err } var res NVPinCounterParams if _, err := mu.UnmarshalFromBytes(data, &res); err != nil { return nil, &InvalidResponseError{CommandNVRead, fmt.Sprintf("cannot unmarshal response bytes: %v", err)} } return &res, nil } // NVReadLock executes the TPM2_NV_ReadLock command to inhibit further reads of the NV index associated with nvIndex. // // The command requires authorization, defined by the state of the AttrNVPPRead, AttrNVOwnerRead, AttrNVAuthRead and AttrNVPolicyRead // attributes. The handle used for authorization is specified via authContext. If the NV index has the AttrNVPPRead attribute, // authorization can be satisfied with HandlePlatform. If the NV index has the AttrNVOwnerRead attribute, authorization can be // satisfied with HandleOwner. If the NV index has the AttrNVAuthRead or AttrNVPolicyRead attribute, authorization can be satisfied // with nvIndex. The command requires authorization with the user auth role for authContext, with session based authorization provided // via authContextAuthSession. If the resource associated with authContext is not permitted to authorize this access, a *TPMError // error with an error code of ErrorNVAuthorization will be returned. // // If nvIndex is being used for authorization and the AttrNVAuthRead attribute is defined, the authorization can be satisfied by // demonstrating knowledge of the authorization value, either via cleartext or HMAC authorization. If nvIndex is being used for // authorization and the AttrNVPolicyRead attribute is defined, the authorization can be satisfied using a policy session with a // digest that matches the authorization policy for the index. // // If the index doesn't have the AttrNVReadStClear attribute set, then a *TPMHandleError error with an error code of ErrorAttributes // will be returned for handle index 2. // // On successful completion, the AttrNVReadLocked attribute will be set. It will be cleared again (and reads will be reenabled) on // the next TPM reset or TPM restart. func (t *TPMContext) NVReadLock(authContext, nvIndex ResourceContext, authContextAuthSession SessionContext, sessions ...SessionContext) error { if err := t.RunCommand(CommandNVReadLock, sessions, ResourceContextWithSession{Context: authContext, Session: authContextAuthSession}, nvIndex); err != nil { return err } nvIndex.(*nvIndexContext).SetAttr(AttrNVReadLocked) return nil } // NVChangeAuth executes the TPM2_NV_ChangeAuth command to change the authorization value for the NV index associated with nvIndex, // setting it to the new value defined by newAuth. The command requires the admin auth role for nvIndex, with the session provided // via nvIndexAuthSession. // // If the size of newAuth is greater than the name algorithm for the index, a *TPMParameterError error with an error code of ErrorSize // will be returned. // // On successful completion, the authorization value of the NV index associated with nvIndex will be set to the value of newAuth, // and nvIndex will be updated to reflect this - it isn't necessary to update nvIndex with ResourceContext.SetAuthValue in order to // use it in authorization roles that require knowledge of the authorization value for the index. func (t *TPMContext) NVChangeAuth(nvIndex ResourceContext, newAuth Auth, nvIndexAuthSession SessionContext, sessions ...SessionContext) error { return t.RunCommandWithResponseCallback(CommandNVChangeAuth, sessions, func() { // If the session is not bound to nvIndex, the TPM will respond with a HMAC generated with a key derived from newAuth. If the // session is bound, the TPM will respond with a HMAC generated from the original key nvIndex.SetAuthValue(newAuth) }, ResourceContextWithSession{Context: nvIndex, Session: nvIndexAuthSession}, Delimiter, newAuth) } // func (t *TPMContext) NVCertify(signContext, authContext, nvIndex HandleContext, qualifyingData Data, // inScheme *SigScheme, size, offset uint16, signContextAuth, authContextAuth interface{}, // sessions ...SessionContext) (*Attest, *Signature, error) { // } ./github.com/canonical/go-tpm2/cmds_object.go0000664000000000000000000011762100000000000017432 0ustar0000000000000000// Copyright 2019 Canonical Ltd. // Licensed under the LGPLv3 with static-linking exception. // See LICENCE file for details. package tpm2 // Section 12 - Object Commands import ( "fmt" "github.com/canonical/go-tpm2/mu" ) // Create executes the TPM2_Create command to create a new ordinary object as a child of the storage parent associated with // parentContext. // // The command requires authorization with the user auth role for parentContext, with session based authorization provided via // parentContextAuthSession. // // A template for the object is provided via the inPublic parameter. The Type field of inPublic defines the algorithm for the object. // The NameAlg field defines the digest algorithm for computing the name of the object. The Attrs field defines the attributes of // the object. The AuthPolicy field allows an authorization policy to be defined for the new object. // // Data that will form part of the sensitive area of the object can be provided via inSensitive, which is optional. // // If the Attrs field of inPublic does not have the AttrSensitiveDataOrigin attribute set, then the sensitive data in the created // object is initialized with the data provided via the Data field of inSensitive. // // If the Attrs field of inPublic has the AttrSensitiveDataOrigin attribute set and Type is ObjectTypeSymCipher, then the sensitive // data in the created object is initialized with a TPM generated key. The size of this key is determined by the value of the Params // field of inPublic. If Type is ObjectTypeKeyedHash, then the sensitive data in the created object is initialized with a TPM // generated value that is the same size as the name algorithm selected by the NameAlg field of inPublic. // // If the Type field of inPublic is ObjectTypeRSA or ObjectTypeECC, then the sensitive data in the created object is initialized with // a TPM generated private key. The size of this is determined by the value of the Params field of inPublic. // // If the Type field of inPublic is ObjectTypeKeyedHash and the Attrs field has AttrSensitiveDataOrigin, AttrSign and AttrDecrypt all // clear, then the created object is a sealed data object. // // If the Attrs field of inPublic has the AttrRestricted and AttrDecrypt attributes set, and the Type field is not // ObjectTypeKeyedHash, then the newly created object will be a storage parent. // // If the Attrs field of inPublic has the AttrRestricted and AttrDecrypt attributes set, and the Type field is ObjectTypeKeyedHash, // then the newly created object will be a derivation parent. // // The authorization value for the created object is initialized to the value of the UserAuth field of inSensitive. // // If the object associated with parentContext is not a valid storage parent object, a *TPMHandleError error with an error code of // ErrorType will be returned for handle index 1. // // If there are no available slots for new objects on the TPM, a *TPMWarning error with a warning code of WarningObjectMemory will // be returned. // // If the Attrs field of inPublic as the AttrSensitiveDataOrigin attribute set and the Data field of inSensitive has a non-zero size, // or the AttrSensitiveDataOrigin attribute is clear and the Data field of inSensitive has a zero size, a *TPMParameterError error // with an error code of ErrorAttributes will be returned for parameter index 1. // // If the attributes in the Attrs field of inPublic are inconsistent or inappropriate for the usage, a *TPMParameterError error with // an error code of ErrorAttributes will be returned for parameter index 2. // // If the NameAlg field of inPublic is HashAlgorithmNull, then a *TPMParameterError error with an error code of ErrorHash will be // returned for parameter index 2. // // If an authorization policy is defined via the AuthPolicy field of inPublic then the length of the digest must match the name // algorithm selected via the NameAlg field, else a *TPMParameterError error with an error code of ErrorSize is returned for parameter // index 2. // // If the scheme in the Params field of inPublic is inappropriate for the usage, a *TPMParameterError errow with an error code of // ErrorScheme will be returned for parameter index 2. // // If the digest algorithm specified by the scheme in the Params field of inPublic is inappropriate for the usage, a // *TPMParameterError error with an error code of ErrorHash will be returned for parameter index 2. // // If the Type field of inPublic is not ObjectTypeKeyedHash, a *TPMParameterError error with an error code of ErrorSymmetric will be // returned for parameter index 2 if the symmetric algorithm specified in the Params field of inPublic is inappropriate for the // usage. // // If the Type field of inPublic is ObjectTypeECC and the KDF scheme specified in the Params field of inPublic is not // KDFAlgorithmNull, a *TPMParameterError error with an error code of ErrorKDF will be returned for parameter index 2. // // If the Type field of inPublic is not ObjectTypeKeyedHash and the AttrRestricted, AttrFixedParent and AttrDecrypt attributes of // Attrs are set, a *TPMParameterError error with an error code of ErrorHash will be returned for parameter index 2 if the NameAlg // field of inPublic does not select the same name algorithm as the parent object. A *TPMParameterError error with an error code // of ErrorSymmetric will be returned for parameter index 2 if the symmetric algorithm specified in the Params field of inPublic // does not match the symmetric algorithm of the parent object. // // If the length of the UserAuth field of inSensitive is longer than the name algorithm selected by the NameAlg field of inPublic, a // *TPMParameterError error with an error code of ErrorSize will be returned for parameter index 1. // // If the Type field of inPublic is ObjectTypeRSA and the Params field specifies an unsupported exponent, a *TPMError with an error // code of ErrorRange will be returned. If the specified key size is an unsupported value, a *TPMError with an error code of // ErrorValue will be returned. // // If the Type field of inPublic is ObjectTypeSymCipher and the key size is an unsupported value, a *TPMError with an error code of // ErrorKeySize will be returned. If the AttrSensitiveDataOrigin attribute is not set and the length of the Data field of inSensitive // does not match the key size specified in the Params field of inPublic, a *TPMError with an error code of ErrorKeySize will be // returned. // // If the Type field of inPublic is ObjectTypeKeyedHash and the AttrSensitiveDataOrigin attribute is not set, a *TPMError with an // error code of ErrorSize will be returned if the length of the Data field of inSensitive is longer than permitted for the digest // algorithm selected by the specified scheme. // // On success, the private and public parts of the newly created object will be returned. The newly created object will not exist on // the TPM. If the Type field of inPublic is ObjectTypeKeyedHash or ObjectTypeSymCipher, then the returned *Public object will have a // Unique field that is the digest of the sensitive data and the value of the object's seed in the sensitive area, computed using the // object's name algorithm. If the Type field of inPublic is ObjectTypeECC or ObjectTypeRSA, then the returned *Public object will // have a Unique field containing details about the public part of the key, computed from the private part of the key. // // The returned *CreationData will contain a digest computed from the values of PCRs selected by the creationPCR parameter at creation // time in the PCRDigest field. It will also contain the provided outsideInfo in the OutsideInfo field. The returned *TkCreation ticket // can be used to prove the association between the created object and the returned *CreationData via the TPMContext.CertifyCreation // method. func (t *TPMContext) Create(parentContext ResourceContext, inSensitive *SensitiveCreate, inPublic *Public, outsideInfo Data, creationPCR PCRSelectionList, parentContextAuthSession SessionContext, sessions ...SessionContext) (outPrivate Private, outPublic *Public, creationData *CreationData, creationHash Digest, creationTicket *TkCreation, err error) { if inSensitive == nil { inSensitive = &SensitiveCreate{} } if err := t.RunCommand(CommandCreate, sessions, ResourceContextWithSession{Context: parentContext, Session: parentContextAuthSession}, Delimiter, mu.Sized(inSensitive), mu.Sized(inPublic), outsideInfo, creationPCR, Delimiter, Delimiter, &outPrivate, mu.Sized(&outPublic), mu.Sized(&creationData), &creationHash, &creationTicket); err != nil { return nil, nil, nil, nil, nil, err } return outPrivate, outPublic, creationData, creationHash, creationTicket, nil } // Load executes the TPM2_Load command in order to load both the public and private parts of an object in to the TPM. // // The parentContext parameter corresponds to the parent key. The command requires authorization with the user auth role for // parentContext, with session based authorization provided via parentContextAuthSession. // // The object to load is specified by providing the inPrivate and inPublic arguments. // // If there are no available slots for new objects on the TPM, a *TPMWarning error with a warning code of WarningObjectMemory will // be returned. // // If inPrivate is empty, a *TPMParameterError error with an error code of ErrorSize will be returned for parameter index 1. // // If parentContext does not correspond to a storage parent, a *TPMHandleError error with an error code of ErrorType will be returned. // // If the name algorithm associated with inPublic is invalid, a *TPMParameterError error with an error code of ErrorHash will be // returned for parameter index 2. // // If the integrity value or IV for inPrivate cannot be unmarshalled correctly, a *TPMParameterError error with an error code of // either ErrorSize or ErrorInsufficient will be returned for parameter index 1. If the integrity check of inPrivate fails, a // *TPMParameterError error with an error code of ErrorIntegrity will be returned for parameter index 1. If the size of the IV // for inPrivate doesn't match the block size for the encryption algorithm, a *TPMParameterError error with an error code of // ErrorValue will be returned for parameter index 1. // // TPM2_Load performs many of the same validations of the public attributes as TPM2_Create, and may return similar error codes as // *TPMParameterError for parameter index 2. // // If the object associated with parentContext has the AttrFixedTPM attribute clear, some additional validation of the decrypted // sensitive data is performed as detailed below. // // If the Type field of inPublic does not match the type specified in the sensitive data, a *TPMParameterError error with an error // code of ErrorType is returned for parameter index 1. If the authorization value in the sensitive area is larger than the name // algorithm, a *TPMParameterError error with an error code of ErrorSize is returned for parameter index 1. // // If the Type field of inPublic is ObjectTypeRSA and the size of the modulus in the Unique field is inconsistent with the size // specified in the Params field, a *TPMParameterError error with an error code of ErrorKey will be returned for parameter index 2. // If the value of the exponent in the Params field is invalid, a *TPMParameterError error with an error code of ErrorValue will // be returned for parameter index 2. If the size of private key in the sensitive area is not the correct size, a *TPMParameterError // error with an error code of ErrorKeySize will be returned for parameter index 1. // // If the Type field of inPublic is ObjectTypeECC and the private key in the sensitive area is invalid, a *TPMParameterError error // with an error code of ErrorKeySize will be returned for parameter index 1. If the public point specified in the Unique field of // inPublic does not belong to the private key, a *TPMError with an error code of ErrorBinding will be returned. // // If the Type field of inPublic is ObjectTypeSymCipher and the size of the symmetric key in the sensitive area is inconsistent with // the symmetric algorithm specified in the Params field of inPublic, a *TPMParameterError error with an error code of ErrorKeySize // will be returned for parameter index 1. // // If the Type field of inPublic is ObjectTypeKeyedHash and the size of the sensitive data is larger than permitted for the digest // algorithm selected by the scheme defined in the Params field of inPublic, a *TPMParameterError error with an error code of // ErrorKeySize will be returned for parameter index 1. // // If the Type field of inPublic is ObjectTypeSymCipher or ObjectTypeKeyedHash and the size of seed value in the sensitive area does // not match the name algorithm, a *TPMError error with an error code of ErrorKeySize will be returned. If the digest in the Unique // field of inPublic is inconsistent with the value of the sensitive data and the seed value, a *TPMError with an error code of // ErrorBinding will be returned. // // If the loaded object is a storage parent and the size of the seed value in the sensitive area isn't sufficient for the selected // name algorithm, a *TPMParameterError error with an error code of ErrorSize will be returned for parameter index 1. // // On success, a ResourceContext corresponding to the newly loaded transient object will be returned. If subsequent use of the // returned ResourceContext requires knowledge of the authorization value of the corresponding TPM resource, this should be provided // by calling ResourceContext.SetAuthValue. func (t *TPMContext) Load(parentContext ResourceContext, inPrivate Private, inPublic *Public, parentContextAuthSession SessionContext, sessions ...SessionContext) (objectContext ResourceContext, err error) { var objectHandle Handle var name Name if err := t.RunCommand(CommandLoad, sessions, ResourceContextWithSession{Context: parentContext, Session: parentContextAuthSession}, Delimiter, inPrivate, mu.Sized(inPublic), Delimiter, &objectHandle, Delimiter, &name); err != nil { return nil, err } if objectHandle.Type() != HandleTypeTransient { return nil, &InvalidResponseError{CommandLoad, fmt.Sprintf("handle 0x%08x returned from TPM is the wrong type", objectHandle)} } if inPublic == nil || !inPublic.compareName(name) { return nil, &InvalidResponseError{CommandLoad, "name returned from TPM not consistent with loaded public area"} } var public *Public // inPublic already marshalled successfully, so this can't fail. mu.MustCopyValue(&public, inPublic) return makeObjectContext(objectHandle, name, public), nil } // LoadExternal executes the TPM2_LoadExternal command in order to load an object that is not a protected object in to the TPM. // The object is specified by providing the inPrivate and inPublic arguments, although inPrivate is optional. If only the public // part is to be loaded, the hierarchy parameter must specify a hierarchy to associate the loaded object with so that tickets can // be created properly. If both the public and private parts are to be loaded, then hierarchy should be HandleNull. // // If there are no available slots for new objects on the TPM, a *TPMWarning error with a warning code of WarningObjectMemory will // be returned. // // If the hierarchy specified by the hierarchy parameter is disabled, a *TPMParameterError error with an error code of ErrorHierarchy // will be returned for parameter index 3. // // If inPrivate is provided and hierarchy is not HandleNull, a *TPMParameterError error with an error code of ErrorHierarchy will be // returned for parameter index 3. // // If inPrivate is provided and the Attrs field of inPublic has either AttrFixedTPM, AttrFixedParent or AttrRestricted attribute set, // a *TPMParameterError error with an error code of ErrorAttributes will be returned for parameter index 2. // // TPM2_LoadExternal performs many of the same validations of the public attributes as TPM2_Create, and may return similar error // codes as *TPMParameterError for parameter index 2. // // If inPrivate is provided and the Type field of inPublic does not match the type specified in the sensitive data, a // *TPMParameterError error with an error code of ErrorType is returned for parameter index 1. If the authorization value in the // sensitive area is larger than the name algorithm, a *TPMParameterError error with an error code of ErrorSize is returned for // parameter index 1. // // If the Type field of inPublic is ObjectTypeRSA and the size of the modulus in the Unique field is inconsistent with the size // specified in the Params field, a *TPMParameterError error with an error code of ErrorKey will be returned for parameter index 2. // If the value of the exponent in the Params field is invalid, a *TPMParameterError error with an error code of ErrorValue will // be returned for parameter index 2. If inPrivate is provided and the size of private key in the sensitive area is not the correct // size, a *TPMParameterError error with an error code of ErrorKeySize will be returned for parameter index 1. // // If the Type field of inPublic is ObjectTypeECC, inPrivate is provided and the private key in the sensitive area is invalid, a // *TPMParameterError error with an error code of ErrorKeySize will be returned for parameter index 1. If the public point specified // in the Unique field of inPublic does not belong to the private key, a *TPMError with an error code of ErrorBinding will be // returned. // // If the Type field of inPublic is ObjectTypeECC, inPrivate is not provided and the size of the public key in the Unique field of // inPublic is inconsistent with the value of the Params field of inPublic, a *TPMParameterError error with an error code of ErrorKey // is returned for parameter index 2. If the public point is not on the curve specified in the Params field of inPublic, a // *TPMParameterError error with an error code of ErrorECCPoint will be returned for parameter index 2. // // If the Type field of inPublic is ObjectTypeSymCipher, inPrivate is provided and the size of the symmetric key in the sensitive area // is inconsistent with the symmetric algorithm specified in the Params field of inPublic, a *TPMParameterError error with an error // code of ErrorKeySize will be returned for parameter index 1. // // If the Type field of inPublic is ObjectTypeKeyedHash, inPrivate is provided and the size of the sensitive data is larger than // permitted for the digest algorithm selected by the scheme defined in the Params field of inPublic, a *TPMParameterError error // with an error code of ErrorKeySize will be returned for parameter index 1. // // If the Type field of inPublic is ObjectTypeSymCipher or ObjectTypeKeyedHash and inPrivate has not been provided, a // *TPMParameterError error with an error code of ErrorKey will be returned for parameter index 2 if the size of the digest in the // Unique field of inPublic does not match the selected name algorithm. // // If the Type field of inPublic is ObjectTypeSymCipher or ObjectTypeKeyedHash, inPrivate has been provided and the size of seed value // in the sensitive area does not match the name algorithm, a *TPMError error with an error code of ErrorKeySize will be returned. // If the digest in the Unique field of inPublic is inconsistent with the value of the sensitive data and the seed value, a // *TPMError with an error code of ErrorBinding will be returned. // // On success, a ResourceContext corresponding to the newly loaded transient object will be returned. If inPrivate has been provided, // it will not be necessary to call ResourceContext.SetAuthValue on it - this function sets the correct authorization value so that it // can be used in subsequent commands that require knowledge of the authorization value. func (t *TPMContext) LoadExternal(inPrivate *Sensitive, inPublic *Public, hierarchy Handle, sessions ...SessionContext) (objectContext ResourceContext, err error) { var objectHandle Handle var name Name if err := t.RunCommand(CommandLoadExternal, sessions, Delimiter, mu.Sized(inPrivate), mu.Sized(inPublic), hierarchy, Delimiter, &objectHandle, Delimiter, &name); err != nil { return nil, err } if objectHandle.Type() != HandleTypeTransient { return nil, &InvalidResponseError{CommandLoadExternal, fmt.Sprintf("handle 0x%08x returned from TPM is the wrong type", objectHandle)} } if inPublic == nil || !inPublic.compareName(name) { return nil, &InvalidResponseError{CommandLoadExternal, "name returned from TPM not consistent with loaded public area"} } var public *Public // inPublic already marshalled succesfully, so this can't fail. mu.MustCopyValue(&public, inPublic) rc := makeObjectContext(objectHandle, name, public) if inPrivate != nil { rc.authValue = make([]byte, len(inPrivate.AuthValue)) copy(rc.authValue, inPrivate.AuthValue) } return rc, nil } // ReadPublic executes the TPM2_ReadPublic command to read the public area of the object associated with objectContext. // // If objectContext corresponds to a sequence object, a *TPMError with an error code of ErrorSequence will be returned. // // On success, the public part of the object is returned, along with the object's name and qualified name. func (t *TPMContext) ReadPublic(objectContext HandleContext, sessions ...SessionContext) (outPublic *Public, name Name, qualifiedName Name, err error) { if err := t.RunCommand(CommandReadPublic, sessions, objectContext, Delimiter, Delimiter, Delimiter, mu.Sized(&outPublic), &name, &qualifiedName); err != nil { return nil, nil, nil, err } return outPublic, name, qualifiedName, nil } // ActivateCredential executes the TPM2_ActivateCredential command to associate a certificate with the object associated with // activateContext. // // The activateContext parameter corresponds to an object to which credentialBlob is to be associated. It would typically be an // attestation key, and the issusing certificate authority would have validated that this object has the expected properties of an // attestation key (it is a restricted, non-duplicable signing key) before issuing the credential. Authorization with the admin role // is required for activateContext, with session based authorization provided via activateContextAuthSession. // // The credentialBlob is an encrypted and integrity protected credential issued by a certificate authority. It is encrypted with a key // derived from a seed generated by the certificate authority, and the name of the object associated with activateContext. It is // integrity protected by prepending a HMAC of the encrypted data and the name of the object associated with activateContext, using // the same seed as the HMAC key. // // The keyContext parameter corresponds to an asymmetric restricted decrypt that was used to encrypt the seed value, which is provided // via the secret parameter in encrypted form. It is typically an endorsement key, and the issuing certificate authority would have // verified that it is a valid endorsement key by verifying the associated endorsement certificate. Authorization with the user auth // role is required for keyContext, with session based authorization provided via keyContextAuthSession. // // If keyContext does not correspond to an asymmetric restricted decrypt key, a *TPMHandleError error with an error code of ErrorType // is returned for handle index 2. // // If recovering the seed from secret fails, a *TPMParameterError error with an error code of ErrorScheme, ErrorValue, ErrorSize or // ErrorECCPoint may be returned for parameter index 2. // // If the integrity value of IV for credentialBlob cannot be unmarshalled correctly or any other errors occur during unmarshalling // of credentialBlob, a *TPMParameterError error with an error code of either ErrorSize or ErrorInsufficient will be returned for // parameter index 1. If the integrity check of credentialBlob fails, a *TPMParameterError error with an error code of ErrorIntegrity // will be returned for parameter index 1. If the size of the IV for credentialBlob doesn't match the block size for the encryption // algorithm, a *TPMParameterError error with an error code of ErrorValue will be returned for parameter index 1. // // On success, the decrypted credential is returned. This is typically used to decrypt a certificate associated with activateContext, // which was issued by a certificate authority. func (t *TPMContext) ActivateCredential(activateContext, keyContext ResourceContext, credentialBlob IDObjectRaw, secret EncryptedSecret, activateContextAuthSession, keyContextAuthSession SessionContext, sessions ...SessionContext) (certInfo Digest, err error) { if err := t.RunCommand(CommandActivateCredential, sessions, ResourceContextWithSession{Context: activateContext, Session: activateContextAuthSession}, ResourceContextWithSession{Context: keyContext, Session: keyContextAuthSession}, Delimiter, credentialBlob, secret, Delimiter, Delimiter, &certInfo); err != nil { return nil, err } return certInfo, nil } // MakeCredential executes the TPM2_MakeCredential command to allow the TPM to perform the actions of a certificate authority, in // order to create an activation credential. // // The object associated with context must be the public part of a storage key, which would typically be the endorsement key of the // TPM from which the request originates. The certificate authority would normally be in receipt of the TPM manufacturer issued // endorsement certificate corresponding to this key and would have validated this. The certificate is an assertion from the // manufacturer that the key is a valid endorsement key (a restricted, non-duplicable decrypt key) that is resident on a genuine TPM. // // The credential parameter is the activation credential, which would typically be used to protect the generated certificate. The // objectName parameter is the name of object for which a certificate is requested. The public part of this object would normally be // validated by the certificate authority to ensure that it has the properties expected of an attestation key (it is a restricted, // non-duplicable signing key). // // If context does not correspond to an asymmetric restricted decrypt key, a *TPMHandleError error with an error code of ErrorType is // returned. // // If the size of credential is larger than the name algorithm associated with context, a *TPMParameterError error with an error code // of ErrorSize will be returned for parameter index 1. // // If the algorithm of the object associated with context is ObjectTypeECC, a *TPMError with an error code of ErrorKey will be returned // if the ECC key is invalid. If the algorithm of the object associated with context is ObjectTypeRSA, a *TPMError with an error code // of ErrorScheme will be returned if the padding scheme is invalid or not supported. // // On success, the encrypted activation credential is returned as IDObjectRaw. The activation credential is encrypted with a key // derived from a randomly generated seed and objectName, and an integrity HMAC of the encrypted credential and objectName is // prepended using a HMAC key derived from the same seed. The seed is encrypted using the public key associated with context, and // returned as EncryptedSecret. // // The certificate authority would typically protect the certificate it generates with the unencrypted credential, and then return the // protected certificate, the encrypted credential blob and the encrypted seed to the requesting party. The seed and credential values // can only be recovered on the TPM associated with the endorsement certificate that the requesting party provided if the object // associated with objectName is resident on it. func (t *TPMContext) MakeCredential(context ResourceContext, credential Digest, objectName Name, sessions ...SessionContext) (credentialBlob IDObjectRaw, secret EncryptedSecret, err error) { if err := t.RunCommand(CommandMakeCredential, sessions, context, Delimiter, credential, objectName, Delimiter, Delimiter, &credentialBlob, &secret); err != nil { return nil, nil, err } return credentialBlob, secret, nil } // Unseal executes the TPM2_Unseal command to decrypt the sealed data object associated with itemContext and retrieve its sensitive // data. The command requires authorization with the user auth role for itemContext, with session based authorization provided via // itemContextAuthSession. // // If the type of object associated with itemContext is not ObjectTypeKeyedHash, a *TPMHandleError error with an error code of // ErrorType will be returned. If the object associated with itemContext has either the AttrDecrypt, AttrSign or AttrRestricted // attributes set, a *TPMHandlerError error with an error code of ErrorAttributes will be returned. // // On success, the object's sensitive data is returned in decrypted form. func (t *TPMContext) Unseal(itemContext ResourceContext, itemContextAuthSession SessionContext, sessions ...SessionContext) (outData SensitiveData, err error) { if err := t.RunCommand(CommandUnseal, sessions, ResourceContextWithSession{Context: itemContext, Session: itemContextAuthSession}, Delimiter, Delimiter, Delimiter, &outData); err != nil { return nil, err } return outData, nil } // ObjectChangeAuth executes the TPM2_ObjectChangeAuth to change the authorization value of the object associated with objectContext. // This command requires authorization with the admin role for objectContext, with sessio based authorization provided via // objectContextAuthSession. // // The new authorization value is provided via newAuth. The parentContext parameter must correspond to the parent object for // objectContext. No authorization is required for parentContext. // // If the object associated with objectContext is a sequence object, a *TPMHandleError error with an error code of ErrorType will // be returned for handle index 1. // // If the length of newAuth is longer than the name algorithm for objectContext, a *TPMParameterError error with an error code of // ErrorSize will be returned. // // If the object associated with parentContext is not the parent object of objectContext, a *TPMHandleError error with an error code // of ErrorType will be returned for handle index 2. // // On success, this returns a new private area for the object associated with objectContext. This function does not make any changes // to the version of the object that is currently loaded in to the TPM. func (t *TPMContext) ObjectChangeAuth(objectContext, parentContext ResourceContext, newAuth Auth, objectContextAuthSession SessionContext, sessions ...SessionContext) (outPrivate Private, err error) { if err := t.RunCommand(CommandObjectChangeAuth, sessions, ResourceContextWithSession{Context: objectContext, Session: objectContextAuthSession}, parentContext, Delimiter, newAuth, Delimiter, Delimiter, &outPrivate); err != nil { return nil, err } return outPrivate, nil } // CreateLoaded executes the TPM2_CreateLoaded command to create a new primary, ordinary or derived object. To create a new primary // object, parentContext should correspond to a hierarchy. To create a new ordinary object, parentContext should correspond to a // storage parent. To create a new derived object, parentContext should correspond to a derivation parent. // // The command requires authorization with the user auth role for parentContext, with session based authorization provided via // parentContextAuthSession. // // A template for the object is provided via the inPublic parameter. The Type field of inPublic defines the algorithm for the object. // The NameAlg field defines the digest algorithm for computing the name of the object. The Attrs field defines the attributes of // the object. The AuthPolicy field allows an authorization policy to be defined for the new object. // // Data that will form part of the sensitive area of the object can be provided via inSensitive, which is optional. // // If parentContext does not correspond to a derivation parent and the Attrs field of inPublic does not have the // AttrSensitiveDataOrigin attribute set, then the sensitive data in the created object is initialized with the data provided via the // Data field of inSensitive. // // If the Attrs field of inPublic has the AttrSensitiveDataOrigin attribute set and Type is ObjectTypeSymCipher, then the sensitive // data in the created object is initialized with a TPM generated key. The size of this key is determined by the value of the Params // field of inPublic. If Type is ObjectTypeKeyedHash, then the sensitive data in the created object is initialized with a TPM // generated value that is the same size as the name algorithm selected by the NameAlg field of inPublic. // // If the Type field of inPublic is ObjectTypeRSA then the sensitive data in the created object is initialized with a TPM generated // private key. The size of this is determined by the value of the Params field of inPublic. // // If the Type field of inPublic is ObjectTypeECC and parentContext does not correspond to a derivation parent, then the sensitive // data in the created object is initialized with a TPM generated private key. The size of this is determined by the value of the // Params field of inPublic. // // If parentContext corresponds to a derivation parent, the sensitive data in the created object is initialized with a value derived // from the parent object's private seed, and the derivation values specified in either the Unique field of inPublic or the Data // field of inSensitive. // // If the Type field of inPublic is ObjectTypeKeyedHash, the Attrs field has AttrSensitiveDataOrigin, AttrSign and AttrDecrypt all // clear, then the created object is a sealed data object. // // If the Attrs field of inPublic has the AttrRestricted and AttrDecrypt attributes set, and the Type field is not // ObjectTypeKeyedHash, then the newly created object will be a storage parent. // // If the Attrs field of inPublic has the AttrRestricted and AttrDecrypt attributes set, and the Type field is ObjectTypeKeyedHash, // then the newly created object will be a derivation parent. // // The authorization value for the created object is initialized to the value of the UserAuth field of inSensitive. // // If parentContext corresponds to an object and it isn't a valid storage parent or derivation parent, *TPMHandleError error with an // error code of ErrorType will be returned for handle index 1. // // If there are no available slots for new objects on the TPM, a *TPMWarning error with a warning code of WarningObjectMemory will // be returned. // // If the attributes in the Attrs field of inPublic are inconsistent or inappropriate for the usage, a *TPMParameterError error with // an error code of ErrorAttributes will be returned for parameter index 2. // // If the NameAlg field of inPublic is HashAlgorithmNull, then a *TPMParameterError error with an error code of ErrorHash will be // returned for parameter index 2. // // If an authorization policy is defined via the AuthPolicy field of inPublic then the length of the digest must match the name // algorithm selected via the NameAlg field, else a *TPMParameterError error with an error code of ErrorSize is returned for parameter // index 2. // // If the scheme in the Params field of inPublic is inappropriate for the usage, a *TPMParameterError errow with an error code of // ErrorScheme will be returned for parameter index 2. // // If the digest algorithm specified by the scheme in the Params field of inPublic is inappropriate for the usage, a // *TPMParameterError error with an error code of ErrorHash will be returned for parameter index 2. // // If the Type field of inPublic is not ObjectTypeKeyedHash, a *TPMParameterError error with an error code of ErrorSymmetric will be // returned for parameter index 2 if the symmetric algorithm specified in the Params field of inPublic is inappropriate for the // usage. // // If the Type field of inPublic is ObjectTypeECC and the KDF scheme specified in the Params field of inPublic is not // KDFAlgorithmNull, a *TPMParameterError error with an error code of ErrorKDF will be returned for parameter index 2. // // If the Type field of inPublic is not ObjectTypeKeyedHash and the AttrRestricted, AttrFixedParent and AttrDecrypt attributes of // Attrs are set, a *TPMParameterError error with an error code of ErrorHash will be returned for parameter index 2 if the NameAlg // field of inPublic does not select the same name algorithm as the parent object. A *TPMParameterError error with an error code // of ErrorSymmetric will be returned for parameter index 2 if the symmetric algorithm specified in the Params field of inPublic // does not match the symmetric algorithm of the parent object. // // If the length of the UserAuth field of inSensitive is longer than the name algorithm selected by the NameAlg field of inPublic, a // *TPMParameterError error with an error code of ErrorSize will be returned for parameter index 1. // // If the Type field of inPublic is ObjectTypeRSA and the Params field specifies an unsupported exponent, a *TPMError with an error // code of ErrorRange will be returned. If the specified key size is an unsupported value, a *TPMError with an error code of // ErrorValue will be returned. // // If the Type field of inPublic is ObjectTypeSymCipher and the key size is an unsupported value, a *TPMError with an error code of // ErrorKeySize will be returned. If the AttrSensitiveDataOrigin attribute is not set and the length of the Data field of inSensitive // does not match the key size specified in the Params field of inPublic, a *TPMError with an error code of ErrorKeySize will be // returned. // // If the Type field of inPublic is ObjectTypeKeyedHash and the AttrSensitiveDataOrigin attribute is not set, a *TPMError with an // error code of ErrorSize will be returned if the length of the Data field of inSensitive is longer than permitted for the digest // algorithm selected by the specified scheme. // // On success, a ResourceContext instance will be returned that corresponds to the newly created object on the TPM, along with the // private and public parts. It will not be necessary to call ResourceContext.SetAuthValue on the returned ResourceContext - this // function sets the correct authorization value so that it can be used in subsequent commands that require knowledge of the // authorization value. If the Type field of inPublic is ObjectTypeKeyedHash or ObjectTypeSymCipher, then the returned *Public object // will have a Unique field that is the digest of the sensitive data and the value of the object's seed in the sensitive area, // computed using the object's name algorithm. If the Type field of inPublic is ObjectTypeECC or ObjectTypeRSA, then the returned // *Public object will have a Unique field containing details about the public part of the key, computed from the private part of the // key. func (t *TPMContext) CreateLoaded(parentContext ResourceContext, inSensitive *SensitiveCreate, inPublic PublicTemplate, parentContextAuthSession SessionContext, sessions ...SessionContext) (objectContext ResourceContext, outPrivate Private, outPublic *Public, err error) { if inSensitive == nil { inSensitive = &SensitiveCreate{} } if inPublic == nil { return nil, nil, nil, makeInvalidArgError("inPublic", "nil value") } inTemplate, err := inPublic.ToTemplate() if err != nil { return nil, nil, nil, fmt.Errorf("cannot marshal public template: %v", err) } var objectHandle Handle var name Name if err := t.RunCommand(CommandCreateLoaded, sessions, ResourceContextWithSession{Context: parentContext, Session: parentContextAuthSession}, Delimiter, mu.Sized(inSensitive), inTemplate, Delimiter, &objectHandle, Delimiter, &outPrivate, mu.Sized(&outPublic), &name); err != nil { return nil, nil, nil, err } if objectHandle.Type() != HandleTypeTransient { return nil, nil, nil, &InvalidResponseError{CommandCreateLoaded, fmt.Sprintf("handle 0x%08x returned from TPM is the wrong type", objectHandle)} } if outPublic == nil || !outPublic.compareName(name) { return nil, nil, nil, &InvalidResponseError{CommandCreateLoaded, "name and public area returned from TPM are not consistent"} } var public *Public if err := mu.CopyValue(&public, outPublic); err != nil { return nil, nil, nil, &InvalidResponseError{CommandCreateLoaded, fmt.Sprintf("cannot copy returned public area from TPM: %v", err)} } rc := makeObjectContext(objectHandle, name, public) rc.authValue = make([]byte, len(inSensitive.UserAuth)) copy(rc.authValue, inSensitive.UserAuth) return rc, outPrivate, outPublic, nil } ./github.com/canonical/go-tpm2/cmds_pcr.go0000664000000000000000000001157700000000000016753 0ustar0000000000000000// Copyright 2019 Canonical Ltd. // Licensed under the LGPLv3 with static-linking exception. // See LICENCE file for details. package tpm2 // Section 22 - Integrity Collection (PCR) // PCRExtend executes the TPM2_PCR_Extend command to extend the PCR associated with the pcrContext parameter with the tagged digests // provided via the digests argument. If will iterate over the digests and extend the PCR with each one for the PCR bank associated // with the algorithm for each digest. // // If pcrContext is nil, this function will do nothing. The command requires authorization with the user auth role for pcrContext, // with session based authorization provided via pcrContextAuthSession. // // If the PCR associated with pcrContext can not be extended from the current locality, a *TPMError error with an error code of // ErrorLocality will be returned. func (t *TPMContext) PCRExtend(pcrContext ResourceContext, digests TaggedHashList, pcrContextAuthSession SessionContext, sessions ...SessionContext) error { return t.RunCommand(CommandPCRExtend, sessions, ResourceContextWithSession{Context: pcrContext, Session: pcrContextAuthSession}, Delimiter, digests) } // PCREvent executes the TPM2_PCR_Event command to extend the PCR associated with the pcrContext parameter with a digest of the // provided eventData, hashed with the algorithm for each supported PCR bank. // // If pcrContext is nil, this function will do nothing. The command requires authorization with the user auth role for pcrContext, // with session based authorization provided via pcrContextAuthSession. // // If the PCR associated with pcrContext can not be extended from the current locality, a *TPMError error with an error code of // ErrorLocality will be returned. // // On success, this function will return a list of tagged digests that the PCR associated with pcrContext was extended with. func (t *TPMContext) PCREvent(pcrContext ResourceContext, eventData Event, pcrContextAuthSession SessionContext, sessions ...SessionContext) (digests TaggedHashList, err error) { if err := t.RunCommand(CommandPCREvent, sessions, ResourceContextWithSession{Context: pcrContext, Session: pcrContextAuthSession}, Delimiter, eventData, Delimiter, Delimiter, &digests); err != nil { return nil, err } return digests, nil } // PCRRead executes the TPM2_PCR_Read command to return the values of the PCRs defined in the pcrSelectionIn parameter. The // underlying command may not be able to read all of the specified PCRs in a single transaction, so this function will // re-execute the TPM2_PCR_Read command until all requested values have been read. As a consequence, any SessionContext instances // provided should have the AttrContinueSession attribute defined. // // On success, the current value of pcrUpdateCounter is returned, as well as the requested PCR values. func (t *TPMContext) PCRRead(pcrSelectionIn PCRSelectionList, sessions ...SessionContext) (pcrUpdateCounter uint32, pcrValues PCRValues, err error) { var remaining PCRSelectionList for _, s := range pcrSelectionIn { c := PCRSelection{Hash: s.Hash, Select: make([]int, len(s.Select))} copy(c.Select, s.Select) remaining = append(remaining, c) } pcrValues = make(PCRValues) for i := 0; ; i++ { var updateCounter uint32 var pcrSelectionOut PCRSelectionList var values DigestList if err := t.RunCommand(CommandPCRRead, sessions, Delimiter, remaining, Delimiter, Delimiter, &updateCounter, &pcrSelectionOut, &values); err != nil { return 0, nil, err } if i == 0 { pcrUpdateCounter = updateCounter } else if updateCounter != pcrUpdateCounter { return 0, nil, &InvalidResponseError{CommandPCRRead, "PCR update counter changed between commands"} } else if len(values) == 0 && pcrSelectionOut.IsEmpty() { return 0, nil, makeInvalidArgError("pcrSelectionIn", "unimplemented PCRs specified") } if n, err := pcrValues.SetValuesFromListAndSelection(pcrSelectionOut, values); err != nil { return 0, nil, &InvalidResponseError{CommandPCRRead, err.Error()} } else if n != len(values) { return 0, nil, &InvalidResponseError{CommandPCRRead, "too many digests"} } remaining = remaining.Remove(pcrSelectionOut) if remaining.IsEmpty() { break } } return pcrUpdateCounter, pcrValues, nil } // PCRReset executes the TPM2_PCR_Reset command to reset the PCR associated with pcrContext in all banks. This command requires // authorization with the user auth role for pcrContext, with session based authorization provided via pcrContextAuthSession. // // If the PCR associated with pcrContext can not be reset from the current locality, a *TPMError error with an error code of // ErrorLocality will be returned. func (t *TPMContext) PCRReset(pcrContext ResourceContext, pcrContextAuthSession SessionContext, sessions ...SessionContext) error { return t.RunCommand(CommandPCRReset, sessions, ResourceContextWithSession{Context: pcrContext, Session: pcrContextAuthSession}) } ./github.com/canonical/go-tpm2/cmds_rng.go0000664000000000000000000000247000000000000016745 0ustar0000000000000000// Copyright 2019 Canonical Ltd. // Licensed under the LGPLv3 with static-linking exception. // See LICENCE file for details. package tpm2 // Section 16 - Random Number Generator // GetRandom executes the TPM2_GetRandom command to return the next bytesRequested number of bytes from the TPM's // random number generator. If the requested bytes cannot be read in a single command, this function will reexecute // the TPM2_GetRandom command until all requested bytes have been read. func (t *TPMContext) GetRandom(bytesRequested uint16, sessions ...SessionContext) (randomBytes []byte, err error) { if err := t.initPropertiesIfNeeded(); err != nil { return nil, err } randomBytes = make([]byte, bytesRequested) total := 0 remaining := bytesRequested for { sz := remaining if sz > uint16(t.maxDigestSize) { sz = uint16(t.maxDigestSize) } var tmpBytes Digest if err := t.RunCommand(CommandGetRandom, sessions, Delimiter, sz, Delimiter, Delimiter, &tmpBytes); err != nil { return nil, err } copy(randomBytes[total:], tmpBytes) total += int(sz) remaining -= sz if remaining == 0 { break } } return randomBytes, nil } func (t *TPMContext) StirRandom(inData SensitiveData, sessions ...SessionContext) error { return t.RunCommand(CommandStirRandom, sessions, Delimiter, inData) } ./github.com/canonical/go-tpm2/cmds_session.go0000664000000000000000000001733400000000000017647 0ustar0000000000000000// Copyright 2019 Canonical Ltd. // Licensed under the LGPLv3 with static-linking exception. // See LICENCE file for details. package tpm2 // Section 11 - Session Commands import ( "fmt" "github.com/canonical/go-tpm2/internal" ) // StartAuthSession executes the TPM2_StartAuthSession command to start an authorization session. On successful completion, it will // return a SessionContext that corresponds to the new session. // // The type of session is defined by the sessionType parameter. If sessionType is SessionTypeHMAC or SessionTypePolicy, then the // created session may be used for authorization. If sessionType is SessionTypeTrial, then the created session can only be used for // computing an authorization policy digest. // // The authHash parameter defines the algorithm used for computing command and response parameter digests, command and response // HMACs, and derivation of the session key and symmetric keys for parameter encryption where used. The size of the digest algorithm // is used to determine the nonce size used for the session. // // If tpmKey is provided, it must correspond to an asymmetric decrypt key in the TPM. In this case, a random salt value will // contribute to the session key derivation, and the salt will be encrypted using the method specified by tpmKey before being sent to // the TPM. If tpmKey is provided but does not correspond to an asymmetric key, a *TPMHandleError error with an error code of ErrorKey // will be returned for handle index 1. If tpmKey is provided but corresponds to an object with only its public part loaded, a // *TPMHandleError error with an error code of ErrorHandle will be returned for handle index 1. If tpmKey is provided but does not // correspond to a decrypt key, a *TPMHandleError error with an error code of ErrorAttributes will be returned for handle index 1. // // If tpmkey is provided but decryption of the salt fails on the TPM, a *TPMParameterError error with an error code of ErrorValue or // ErrorKey may be returned for parameter index 2. // // If bind is specified, then the auhorization value for the corresponding resource must be known, by calling // ResourceContext.SetAuthValue on bind before calling this function - the authorization value will contribute to the session key // derivation. The created session will be bound to the resource associated with bind, unless the authorization value of that resource // is subsequently changed. If bind corresponds to a transient object and only the public part of the object is loaded, or if bind // corresponds to a NV index with a type of NVTypePinPass or NVTypePinFail, a *TPMHandleError error with an error code of ErrorHandle // will be returned for handle index 2. // // If a session key is computed, this will be used (along with the authorization value of resources that the session is being used // for authorization of if the session is not bound to them) to derive a HMAC key for generating command and response HMACs. If both // tpmKey and bind are nil, no session key is created. // // If symmetric is provided, it defines the symmetric algorithm to use if the session is subsequently used for session based command // or response parameter encryption. Session based parameter encryption allows the first command and/or response parameter for a // command to be encrypted between the TPM and host CPU for supported parameter types (go types that correspond to TPM2B prefixed // types). If symmetric is provided and corresponds to a symmetric block cipher (ie, the Algorithm field is not SymAlgorithmXOR) then // the value of symmetric.Mode.Sym() must be SymModeCFB, else a *TPMParameterError error with an error code of ErrorMode is returned // for parameter index 4. // // If a SessionContext instance with the AttrCommandEncrypt attribute set is provided in the variable length sessions parameter, then // the initial caller nonce will be encrypted as this is the first command parameter, despite not being exposed via this API. If a // SessionContext instance with the AttrResponseEncrypt attribute set is provided, then the initial TPM nonce will be encrypted in the // response. // // If sessionType is SessionTypeHMAC and the session is subsequently used for authorization of a resource to which the session is not // bound, the authorization value of that resource must be known as it is used to derive the key for computing command and response // HMACs. // // If no more sessions can be created without first context loading the oldest saved session, then a *TPMWarning error with a warning // code of WarningContextGap will be returned. If there are no more slots available for loaded sessions, a *TPMWarning error with a // warning code of WarningSessionMemory will be returned. If there are no more session handles available, a *TPMwarning error with // a warning code of WarningSessionHandles will be returned. func (t *TPMContext) StartAuthSession(tpmKey, bind ResourceContext, sessionType SessionType, symmetric *SymDef, authHash HashAlgorithmId, sessions ...SessionContext) (sessionContext SessionContext, err error) { if symmetric == nil { symmetric = &SymDef{Algorithm: SymAlgorithmNull} } if !authHash.Available() { return nil, makeInvalidArgError("authHash", fmt.Sprintf("unsupported digest algorithm or algorithm not linked in to binary (%v)", authHash)) } digestSize := authHash.Size() var salt []byte var encryptedSalt EncryptedSecret tpmKeyHandle := HandleNull if tpmKey != nil { object, isObject := tpmKey.(*objectContext) if !isObject { return nil, makeInvalidArgError("tpmKey", "resource context is not an object") } tpmKeyHandle = tpmKey.Handle() var err error encryptedSalt, salt, err = CryptSecretEncrypt(object.GetPublic(), []byte(SecretKey)) if err != nil { return nil, fmt.Errorf("cannot compute encrypted salt: %v", err) } } var authValue []byte bindHandle := HandleNull if bind != nil { bindHandle = bind.Handle() authValue = bind.(resourceContextPrivate).GetAuthValue() } var isBound bool = false var boundEntity Name if bindHandle != HandleNull && sessionType == SessionTypeHMAC { boundEntity = computeBindName(bind.Name(), authValue) isBound = true } nonceCaller := make([]byte, digestSize) if err := cryptComputeNonce(nonceCaller); err != nil { return nil, fmt.Errorf("cannot compute initial nonceCaller: %v", err) } var sessionHandle Handle var nonceTPM Nonce if err := t.RunCommand(CommandStartAuthSession, sessions, tpmKey, bind, Delimiter, Nonce(nonceCaller), encryptedSalt, sessionType, symmetric, authHash, Delimiter, &sessionHandle, Delimiter, &nonceTPM); err != nil { return nil, err } switch sessionHandle.Type() { case HandleTypeHMACSession, HandleTypePolicySession: default: return nil, &InvalidResponseError{CommandStartAuthSession, fmt.Sprintf("handle 0x%08x returned from TPM is the wrong type", sessionHandle)} } data := &sessionContextData{ HashAlg: authHash, SessionType: sessionType, PolicyHMACType: policyHMACTypeNoAuth, IsBound: isBound, BoundEntity: boundEntity, NonceCaller: nonceCaller, NonceTPM: nonceTPM, Symmetric: symmetric} if tpmKeyHandle != HandleNull || bindHandle != HandleNull { key := make([]byte, len(authValue)+len(salt)) copy(key, authValue) copy(key[len(authValue):], salt) data.SessionKey = internal.KDFa(authHash.GetHash(), key, []byte(SessionKey), []byte(nonceTPM), nonceCaller, digestSize*8) } return makeSessionContext(sessionHandle, data), nil } // PolicyRestart executes the TPM2_PolicyRestart command on the policy session associated with sessionContext, to reset the policy // authorization session to its initial state. func (t *TPMContext) PolicyRestart(sessionContext SessionContext, sessions ...SessionContext) error { return t.RunCommand(CommandPolicyRestart, sessions, sessionContext) } ./github.com/canonical/go-tpm2/cmds_signature.go0000664000000000000000000000762200000000000020164 0ustar0000000000000000// Copyright 2019 Canonical Ltd. // Licensed under the LGPLv3 with static-linking exception. // See LICENCE file for details. package tpm2 // Secion 20 - Signing and Signature Verification // VerifySignature executes the TPM2_VerifySignature command to validate the provided signature against a message with the provided // digest, using the key associated with keyContext. If keyContext corresponds to an object that isn't a signing key, a // *TPMHandleError error with an error code of ErrorAttributes will be returned. // // If the signature is invalid, a *TPMParameterError error with an error code of ErrorSignature will be returned for parameter index // 2. If the signature references an unsupported signature scheme, a *TPMParameterError error with an error code of ErrorScheme will // be returned for parameter index 2. // // If keyContext corresponds to a HMAC key but only the public part is loaded, a *TPMParameterError error with an error code of // ErrorHandle will be returned for parameter index 2. // // On success, a valid TkVerified structure will be returned. func (t *TPMContext) VerifySignature(keyContext ResourceContext, digest Digest, signature *Signature, sessions ...SessionContext) (validation *TkVerified, err error) { if err := t.RunCommand(CommandVerifySignature, sessions, keyContext, Delimiter, digest, signature, Delimiter, Delimiter, &validation); err != nil { return nil, err } return validation, nil } // Sign executes the TPM2_Sign command to sign the provided digest with the key associated with keyContext. The function requires // authorization with the user auth role for keyContext, with session based authorization provided via keyContextAuthSession. // // If the object associated with keyContext is not a signing key, a *TPMHandleError error with an error code of ErrorKey will be // returned. // // If the scheme of the key associated with keyContext is AsymSchemeNull, then inScheme must be provided to specify a valid signing // scheme for the key. If it isn't, a *TPMParameterError error with an error code of ErrorScheme will be returned for parameter index // 2. // // If the scheme of the key associated with keyContext is not AsymSchemeNull, then inScheme may be nil. If it is provided, then the // specified scheme must match that of the signing key, else a *TPMParameterError error with an error code of ErrorScheme will be // returned for parameter index 2. // // If the chosen scheme is unsupported, a *TPMError error with an error code of ErrorScheme will be returned. // // If the length of digest does not match the size of the digest associated with the selected signing scheme, a *TPMParameterError // error with an error code of ErrorSize will be returned for parameter index 1. // // If the key associated with keyContext has the AttrRestricted attribute, then the validation parameter must be provided as proof // that the supplied digest was created by the TPM. If the key associated with keyContext does not have the AttrRestricted attribute, // then validation may be nil. If validation is not nil and doesn't correspond to a valid ticket, or it is nil and the key associated // with keyContext has the AttrRestricted attribute set, a *TPMParameterError error with an error code of ErrorTicket will be returned // for parameter index 3. func (t *TPMContext) Sign(keyContext ResourceContext, digest Digest, inScheme *SigScheme, validation *TkHashcheck, keyContextAuthSession SessionContext, sessions ...SessionContext) (signature *Signature, err error) { if inScheme == nil { inScheme = &SigScheme{Scheme: SigSchemeAlgNull} } if validation == nil { validation = &TkHashcheck{Tag: TagHashcheck, Hierarchy: HandleNull} } if err := t.RunCommand(CommandSign, sessions, ResourceContextWithSession{Context: keyContext, Session: keyContextAuthSession}, Delimiter, digest, inScheme, validation, Delimiter, Delimiter, &signature); err != nil { return nil, err } return signature, nil } ./github.com/canonical/go-tpm2/cmds_startup.go0000664000000000000000000000470500000000000017664 0ustar0000000000000000// Copyright 2019 Canonical Ltd. // Licensed under the LGPLv3 with static-linking exception. // See LICENCE file for details. package tpm2 // Section 10 - Testing // Startup executes the TPM2_Startup command with the specified StartupType. If this isn't preceded by _TPM_Init then it will return // a *TPMError error with an error code of ErrorInitialize. The shutdown and startup sequence determines how the TPM responds to this // call: // * A call with startupType == StartupClear preceded by a call to TPMContext.Shutdown with shutdownType == StartupClear or without // a preceding call to TPMContext.Shutdown will cause a TPM reset. // * A call with startupType == StartupClear preceded by a call to TPMContext.Shutdown with shutdownType == StartupState will cause // a TPM restart. // * A call with startupType == StartupState preceded by a call to TPMContext.Shutdown with shutdownType == StartupState will cause // a TPM resume. // * A call with startupType == StartupState that isn't preceded by a call to TPMContext.Shutdown with shutdownType == StartupState // will fail with a *TPMParameterError error with an error code of ErrorValue. // // If called with startupType == StartupState, a *TPMError error with an error code of ErrorNVUninitialized will be returned if the // saved state cannot be recovered. In this case, the function must be called with startupType == StartupClear. // // Subsequent use of HandleContext instances corresponding to entities that are evicted as a consequence of this function will no // longer work. func (t *TPMContext) Startup(startupType StartupType) error { return t.RunCommand(CommandStartup, nil, Delimiter, startupType) } // Shutdown executes the TPM2_Shutdown command with the specified StartupType, and is used to prepare the TPM for a power cycle. // Calling this with shutdownType == StartupClear prepares the TPM for a TPM reset. Calling it with shutdownType == StartupState // prepares the TPM for either a TPM restart or TPM resume, depending on how TPMContext.Startup is called. Some commands executed // after TPMContext.Shutdown but before a power cycle will nullify the effect of this function. // // If a PCR bank has been reconfigured and shutdownType == StartupState, a *TPMParameterError error with an error code of // ErrorType will be returned. func (t *TPMContext) Shutdown(shutdownType StartupType, sessions ...SessionContext) error { return t.RunCommand(CommandShutdown, sessions, Delimiter, shutdownType) } ./github.com/canonical/go-tpm2/cmds_testing.go0000664000000000000000000000166700000000000017643 0ustar0000000000000000// Copyright 2019 Canonical Ltd. // Licensed under the LGPLv3 with static-linking exception. // See LICENCE file for details. package tpm2 // Section 9 - Start-up func (t *TPMContext) SelfTest(fullTest bool, sessions ...SessionContext) error { return t.RunCommand(CommandSelfTest, sessions, Delimiter, fullTest) } func (t *TPMContext) IncrementalSelfTest(toTest AlgorithmList, sessions ...SessionContext) (AlgorithmList, error) { var toDoList AlgorithmList if err := t.RunCommand(CommandIncrementalSelfTest, sessions, Delimiter, toTest, Delimiter, Delimiter, &toDoList); err != nil { return nil, err } return toDoList, nil } func (t *TPMContext) GetTestResult(sessions ...SessionContext) (outData MaxBuffer, testResult ResponseCode, err error) { if err := t.RunCommand(CommandGetTestResult, sessions, Delimiter, Delimiter, Delimiter, &outData, &testResult); err != nil { return nil, 0, err } return outData, testResult, nil } ./github.com/canonical/go-tpm2/command.go0000664000000000000000000001547400000000000016577 0ustar0000000000000000// Copyright 2021 Canonical Ltd. // Licensed under the LGPLv3 with static-linking exception. // See LICENCE file for details. package tpm2 import ( "bytes" "encoding/binary" "fmt" "io" "io/ioutil" "github.com/canonical/go-tpm2/mu" "golang.org/x/xerrors" ) const ( maxResponseSize int = 4096 ) // CommandHeader is the header for a TPM command. type CommandHeader struct { Tag StructTag CommandSize uint32 CommandCode CommandCode } // CommandPacket corresponds to a complete command packet including header and payload. type CommandPacket []byte // GetCommandCode returns the command code contained within this packet. func (p CommandPacket) GetCommandCode() (CommandCode, error) { var header CommandHeader if _, err := mu.UnmarshalFromBytes(p, &header); err != nil { return 0, xerrors.Errorf("cannot unmarshal header: %w", err) } return header.CommandCode, nil } // Unmarshal unmarshals this command packet, returning the handles, auth area and // parameters. The parameters will still be in the TPM wire format. The number of command // handles associated with the command must be supplied by the caller. func (p CommandPacket) Unmarshal(numHandles int) (handles HandleList, authArea []AuthCommand, parameters []byte, err error) { buf := bytes.NewReader(p) var header CommandHeader if _, err := mu.UnmarshalFromReader(buf, &header); err != nil { return nil, nil, nil, xerrors.Errorf("cannot unmarshal header: %w", err) } if header.CommandSize != uint32(len(p)) { return nil, nil, nil, fmt.Errorf("invalid commandSize value (got %d, packet length %d)", header.CommandSize, len(p)) } for i := 0; i < numHandles; i++ { var handle Handle if _, err := mu.UnmarshalFromReader(buf, &handle); err != nil { return nil, nil, nil, xerrors.Errorf("cannot unmarshal handles: %w", err) } handles = append(handles, handle) } switch header.Tag { case TagSessions: var authSize uint32 if _, err := mu.UnmarshalFromReader(buf, &authSize); err != nil { return nil, nil, nil, xerrors.Errorf("cannot unmarshal auth area size: %w", err) } r := &io.LimitedReader{R: buf, N: int64(authSize)} for r.N > 0 { if len(authArea) >= 3 { return nil, nil, nil, fmt.Errorf("%d trailing byte(s) in auth area", r.N) } var auth AuthCommand if _, err := mu.UnmarshalFromReader(r, &auth); err != nil { return nil, nil, nil, xerrors.Errorf("cannot unmarshal auth: %w", err) } authArea = append(authArea, auth) } case TagNoSessions: default: return nil, nil, nil, fmt.Errorf("invalid tag: %v", header.Tag) } parameters, err = ioutil.ReadAll(buf) if err != nil { return nil, nil, nil, xerrors.Errorf("cannot read parameters: %w", err) } return handles, authArea, parameters, nil } // MarshalCommandPacket serializes a complete TPM packet from the provided arguments. The // parameters argument must already be serialized to the TPM wire format. func MarshalCommandPacket(command CommandCode, handles HandleList, authArea []AuthCommand, parameters []byte) CommandPacket { header := CommandHeader{CommandCode: command} var payload []byte hBytes := new(bytes.Buffer) for _, h := range handles { mu.MustMarshalToWriter(hBytes, h) } switch { case len(authArea) > 0: header.Tag = TagSessions aBytes := new(bytes.Buffer) for _, auth := range authArea { mu.MustMarshalToWriter(aBytes, auth) } payload = mu.MustMarshalToBytes(mu.RawBytes(hBytes.Bytes()), uint32(aBytes.Len()), mu.RawBytes(aBytes.Bytes()), mu.RawBytes(parameters)) case len(authArea) == 0: header.Tag = TagNoSessions payload = mu.MustMarshalToBytes(mu.RawBytes(hBytes.Bytes()), mu.RawBytes(parameters)) } header.CommandSize = uint32(binary.Size(header) + len(payload)) return mu.MustMarshalToBytes(header, mu.RawBytes(payload)) } // ResponseHeader is the header for the TPM's response to a command. type ResponseHeader struct { Tag StructTag ResponseSize uint32 ResponseCode ResponseCode } // ResponsePacket corresponds to a complete response packet including header and payload. type ResponsePacket []byte // Unmarshal deserializes the response packet and returns the response code, handle, parameters // and auth area. The parameters will still be in the TPM wire format. The caller supplies a // pointer to which the response handle will be written. The pointer must be supplied if the // command returns a handle, and must be nil if the command does not return a handle, else // the response will be incorrectly unmarshalled. func (p ResponsePacket) Unmarshal(handle *Handle) (rc ResponseCode, parameters []byte, authArea []AuthResponse, err error) { if len(p) > maxResponseSize { return 0, nil, nil, fmt.Errorf("packet too large (%d bytes)", len(p)) } buf := bytes.NewReader(p) var header ResponseHeader if _, err := mu.UnmarshalFromReader(buf, &header); err != nil { return 0, nil, nil, xerrors.Errorf("cannot unmarshal header: %w", err) } if header.ResponseSize != uint32(buf.Size()) { return 0, nil, nil, fmt.Errorf("invalid responseSize value (got %d, packet length %d)", header.ResponseSize, len(p)) } if header.ResponseCode != ResponseSuccess && buf.Len() != 0 { return header.ResponseCode, nil, nil, fmt.Errorf("%d trailing byte(s) in unsuccessful response", buf.Len()) } switch header.Tag { case TagRspCommand: if header.ResponseCode != ResponseBadTag { return 0, nil, nil, fmt.Errorf("unexpected TPM1.2 response code 0x%08x", header.ResponseCode) } case TagSessions: if header.ResponseCode != ResponseSuccess { return 0, nil, nil, fmt.Errorf("unexpcted response code 0x%08x for TPM_ST_SESSIONS response", header.ResponseCode) } fallthrough case TagNoSessions: if header.ResponseCode == ResponseSuccess && handle != nil { if _, err := mu.UnmarshalFromReader(buf, handle); err != nil { return 0, nil, nil, xerrors.Errorf("cannot unmarshal handle: %w", err) } } default: return 0, nil, nil, fmt.Errorf("invalid tag: %v", header.Tag) } switch header.Tag { case TagRspCommand: case TagSessions: var parameterSize uint32 if _, err := mu.UnmarshalFromReader(buf, ¶meterSize); err != nil { return 0, nil, nil, xerrors.Errorf("cannot unmarshal parameterSize: %w", err) } parameters = make([]byte, parameterSize) if _, err := io.ReadFull(buf, parameters); err != nil { return 0, nil, nil, xerrors.Errorf("cannot read parameters: %w", err) } for buf.Len() > 0 { if len(authArea) >= 3 { return 0, nil, nil, fmt.Errorf("%d trailing byte(s)", buf.Len()) } var auth AuthResponse if _, err := mu.UnmarshalFromReader(buf, &auth); err != nil { return 0, nil, nil, xerrors.Errorf("cannot unmarshal auth: %w", err) } authArea = append(authArea, auth) } case TagNoSessions: parameters, err = ioutil.ReadAll(buf) if err != nil { return 0, nil, nil, xerrors.Errorf("cannot read parameters: %w", err) } } return header.ResponseCode, parameters, authArea, nil } ./github.com/canonical/go-tpm2/constants.go0000664000000000000000000000213200000000000017160 0ustar0000000000000000// Copyright 2019 Canonical Ltd. // Licensed under the LGPLv3 with static-linking exception. // See LICENCE file for details. package tpm2 import ( "math" ) const ( DefaultRSAExponent = 65537 ) const ( CapabilityMaxProperties uint32 = math.MaxUint32 ) const ( // CFBKey is used as the label for the symmetric key derivation used // in parameter encryption. CFBKey = "CFB" // DuplicateString is used as the label for secret sharing used by // object duplication. DuplicateString = "DUPLICATE" // IdentityKey is used as the label for secret sharing used by // when issuing and using credentials. IdentityKey = "IDENTITY" // IntegrityKey is used as the label for the HMAC key derivation // used for outer wrappers. IntegrityKey = "INTEGRITY" // SecretKey is used as the label for secret sharing used by // TPM2_StartAuthSession. SecretKey = "SECRET" // SessionKey is used as the label for the session key derivation. SessionKey = "ATH" // StorageKey is used as the label for the symmetric key derivation // used for encrypting and decrypting outer wrappers. StorageKey = "STORAGE" ) ./github.com/canonical/go-tpm2/crypto.go0000664000000000000000000001365000000000000016473 0ustar0000000000000000// Copyright 2019 Canonical Ltd. // Licensed under the LGPLv3 with static-linking exception. // See LICENCE file for details. package tpm2 import ( "crypto/aes" "crypto/cipher" "crypto/ecdsa" "crypto/elliptic" "crypto/rand" "crypto/rsa" "encoding/binary" "errors" "fmt" "math/big" "github.com/canonical/go-tpm2/internal" "github.com/canonical/go-tpm2/mu" "golang.org/x/xerrors" ) type NewCipherFunc func([]byte) (cipher.Block, error) var ( eccCurves = map[ECCCurve]elliptic.Curve{ ECCCurveNIST_P224: elliptic.P224(), ECCCurveNIST_P256: elliptic.P256(), ECCCurveNIST_P384: elliptic.P384(), ECCCurveNIST_P521: elliptic.P521(), } symmetricAlgs = map[SymAlgorithmId]NewCipherFunc{ SymAlgorithmAES: aes.NewCipher, } ) // RegisterCipher allows a go block cipher implementation to be registered for the // specified algorithm, so binaries don't need to link against every implementation. func RegisterCipher(alg SymAlgorithmId, fn NewCipherFunc) { symmetricAlgs[alg] = fn } func eccCurveToGoCurve(curve ECCCurve) elliptic.Curve { switch curve { case ECCCurveNIST_P224: return elliptic.P224() case ECCCurveNIST_P256: return elliptic.P256() case ECCCurveNIST_P384: return elliptic.P384() case ECCCurveNIST_P521: return elliptic.P521() } return nil } // ComputeCpHash computes a command parameter digest from the specified command code, the supplied // handles (identified by their names) and parameter buffer using the specified digest algorithm. // // The result of this is useful for extended authorization commands that bind an authorization to // a command and set of command parameters, such as TPMContext.PolicySigned, TPMContext.PolicySecret, // TPMContext.PolicyTicket and TPMContext.PolicyCpHash. // // This will panic if alg is not available. func ComputeCpHash(alg HashAlgorithmId, command CommandCode, handles []Name, parameters []byte) Digest { hash := alg.NewHash() binary.Write(hash, binary.BigEndian, command) for _, name := range handles { hash.Write([]byte(name)) } hash.Write(parameters) return hash.Sum(nil) } func cryptComputeRpHash(hashAlg HashAlgorithmId, responseCode ResponseCode, commandCode CommandCode, rpBytes []byte) []byte { hash := hashAlg.NewHash() binary.Write(hash, binary.BigEndian, responseCode) binary.Write(hash, binary.BigEndian, commandCode) hash.Write(rpBytes) return hash.Sum(nil) } func cryptComputeNonce(nonce []byte) error { _, err := rand.Read(nonce) return err } // CryptSymmetricEncrypt performs in place symmetric encryption of the supplied // data with the specified algorithm using CFB mode. func CryptSymmetricEncrypt(alg SymAlgorithmId, key, iv, data []byte) error { switch alg { case SymAlgorithmXOR, SymAlgorithmNull: return errors.New("unsupported symmetric algorithm") default: c, err := alg.NewCipher(key) if err != nil { return xerrors.Errorf("cannot create cipher: %w", err) } // The TPM uses CFB cipher mode for all secret sharing s := cipher.NewCFBEncrypter(c, iv) s.XORKeyStream(data, data) return nil } } // CryptSymmetricDecrypt performs in place symmetric decryption of the supplied // data with the specified algorithm using CFB mode. func CryptSymmetricDecrypt(alg SymAlgorithmId, key, iv, data []byte) error { switch alg { case SymAlgorithmXOR, SymAlgorithmNull: return errors.New("unsupported symmetric algorithm") default: c, err := alg.NewCipher(key) if err != nil { return xerrors.Errorf("cannot create cipher: %w", err) } // The TPM uses CFB cipher mode for all secret sharing s := cipher.NewCFBDecrypter(c, iv) s.XORKeyStream(data, data) return nil } } func zeroExtendBytes(x *big.Int, l int) (out []byte) { out = make([]byte, l) tmp := x.Bytes() copy(out[len(out)-len(tmp):], tmp) return } // CryptSecretEncrypt creates a secret value and its associated secret structure using // the asymmetric algorithm defined by public. This is useful for sharing secrets with // the TPM via the TPMContext.Import and TPMContext.ActivateCredential functions. // // It is also used internally by TPMContext.StartAuthSession. func CryptSecretEncrypt(public *Public, label []byte) (EncryptedSecret, []byte, error) { if !public.NameAlg.Available() { return nil, nil, fmt.Errorf("nameAlg %v is not available", public.NameAlg) } digestSize := public.NameAlg.Size() switch public.Type { case ObjectTypeRSA: if public.Params.RSADetail.Scheme.Scheme != RSASchemeNull && public.Params.RSADetail.Scheme.Scheme != RSASchemeOAEP { return nil, nil, errors.New("unsupported RSA scheme") } pub := public.Public().(*rsa.PublicKey) secret := make([]byte, digestSize) if _, err := rand.Read(secret); err != nil { return nil, nil, fmt.Errorf("cannot read random bytes for secret: %v", err) } h := public.NameAlg.NewHash() label0 := make([]byte, len(label)+1) copy(label0, label) encryptedSecret, err := rsa.EncryptOAEP(h, rand.Reader, pub, secret, label0) return encryptedSecret, secret, err case ObjectTypeECC: pub := public.Public().(*ecdsa.PublicKey) if pub.Curve == nil { return nil, nil, fmt.Errorf("unsupported curve: %v", public.Params.ECCDetail.CurveID.GoCurve()) } if !pub.Curve.IsOnCurve(pub.X, pub.Y) { return nil, nil, fmt.Errorf("public key is not on curve") } ephPriv, ephX, ephY, err := elliptic.GenerateKey(pub.Curve, rand.Reader) if err != nil { return nil, nil, fmt.Errorf("cannot generate ephemeral ECC key: %v", err) } sz := pub.Curve.Params().BitSize / 8 encryptedSecret, err := mu.MarshalToBytes(&ECCPoint{ X: zeroExtendBytes(ephX, sz), Y: zeroExtendBytes(ephY, sz)}) if err != nil { panic(fmt.Sprintf("failed to marshal secret: %v", err)) } mulX, _ := pub.Curve.ScalarMult(pub.X, pub.Y, ephPriv) secret := internal.KDFe(public.NameAlg.GetHash(), zeroExtendBytes(mulX, sz), label, zeroExtendBytes(ephX, sz), zeroExtendBytes(pub.X, sz), digestSize*8) return encryptedSecret, secret, nil default: return nil, nil, fmt.Errorf("unsupported key type %v", public.Type) } } ./github.com/canonical/go-tpm2/doc.go0000664000000000000000000000711700000000000015721 0ustar0000000000000000/* Package tpm2 implements an API for communicating with TPM 2.0 devices. This documentation refers to TPM commands and types that are described in more detail in the TPM 2.0 Library Specification, which can be found at https://trustedcomputinggroup.org/resource/tpm-library-specification/. Knowledge of this specification is assumed in this documentation. Communication with Linux TPM character devices and TPM simulators implementing the Microsoft TPM2 simulator interface is supported. The core type by which consumers of this package communicate with a TPM is TPMContext. Quick start In order to create a new TPMContext that can be used to communicate with a Linux TPM character device: tcti, err := linux.OpenDevice("/dev/tpm0") if err != nil { return err } tpm := tpm2.NewTPMContext(tcti) In order to create and persist a new storage primary key: tcti, err := linux.OpenDevice("/dev/tpm0") if err != nil { return err } tpm := tpm2.NewTPMContext(tcti) template = tpm2.Public{ Type: tpm2.ObjectTypeRSA, NameAlg: tpm2.HashAlgorithmSHA256, Attrs: tpm2.AttrFixedTPM | tpm2.AttrFixedParent | tpm2.AttrSensitiveDataOrigin | tpm2.AttrUserWithAuth | tpm2.AttrNoDA | tpm2.AttrRestricted | tpm2.AttrDecrypt, Params: &tpm2.PublicParamsU{ RSADetail: &tpm2.RSAParams{ Symmetric: tpm2.SymDefObject{ Algorithm: tpm2.SymObjectAlgorithmAES, KeyBits: &tpm2.SymKeyBitsU{Sym: 128}, Mode: &tpm2.SymModeU{Sym: tpm2.SymModeCFB}}, Scheme: tpm2.RSAScheme{Scheme: tpm2.RSASchemeNull}, KeyBits: 2048, Exponent: 0}}, Unique: &tpm2.PublicIDU{RSA: make(tpm2.PublicKeyRSA, 256)}} context, _, _, _, _, err := tpm.CreatePrimary(tpm.OwnerHandleContext(), nil, &template, nil, nil, nil) if err != nil { return err } persistentContext, err := tpm.EvictControl(tpm.OwnerHandleContext(), context, tpm2.Handle(0x81000001), nil) if err != nil { return err } // persistentContext is a ResourceContext corresponding to the new persistent storage primary key. In order to evict a persistent object: tcti, err := linux.OpenDevice("/dev/tpm0") if err != nil { return err } tpm := tpm2.NewTPMContext(tcti) context, err := tpm.CreateResourceContextFromTPM(tpm2.Handle(0x81000001)) if err != nil { return err } if _, err := tpm.EvictControl(tpm.OwnerHandleContext(), context, context.Handle(), nil); err != nil { return err } // The resource associated with context is now unavailable. Authorization types Some TPM resources require authorization in order to use them in some commands. There are 3 main types of authorization supported by this package: * Cleartext password: A cleartext authorization value is sent to the TPM by calling ResourceContext.SetAuthValue and supplying the ResourceContext to a function requiring authorization. Authorization succeeds if the correct value is sent. * HMAC session: Knowledge of an authorization value is demonstrated by calling ResourceContext.SetAuthValue and supplying the ResourceContext to a function requiring authorization, along with a session with the type SessionTypeHMAC. Authorization succeeds if the computed HMAC matches that expected by the TPM. * Policy session: A ResourceContext is supplied to a function requiring authorization along with a session with the type SessionTypePolicy, containing a record of and the result of a sequence of assertions. Authorization succeeds if the conditions required by the resource's authorization policy are satisfied. The type of authorizations permitted for a resource is dependent on the authorization role (user, admin or duplication), the type of resource and the resource's attributes. */ package tpm2 ./github.com/canonical/go-tpm2/errors.go0000664000000000000000000007560400000000000016476 0ustar0000000000000000// Copyright 2019 Canonical Ltd. // Licensed under the LGPLv3 with static-linking exception. // See LICENCE file for details. package tpm2 import ( "bytes" "fmt" "golang.org/x/xerrors" ) const ( // AnyCommandCode is used to match any command code when using IsTPMError, // IsTPMHandleError, IsTPMParameterError, IsTPMSessionError and IsTPMWarning. AnyCommandCode CommandCode = 0xc0000000 // AnyErrorCode is used to match any error code when using IsTPMError, // IsTPMHandleError, IsTPMParameterError and IsTPMSessionError. AnyErrorCode ErrorCode = 0xff // AnyHandle is used to match any handle when using IsResourceUnavailableError. AnyHandle Handle = 0xffffffff // AnyHandleIndex is used to match any handle when using IsTPMHandleError. AnyHandleIndex int = -1 // AnyParameterIndex is used to match any parameter when using IsTPMParameterError. AnyParameterIndex int = -1 // AnySessionIndex is used to match any session when using IsTPMSessionError. AnySessionIndex int = -1 // AnyWarningCode is used to match any warning code when using IsTPMWarning. AnyWarningCode WarningCode = 0xff ) // ResourceUnavailableError is returned from TPMContext.CreateResourceContextFromTPM if // it is called with a handle that does not correspond to a resource that is available // on the TPM. This could be because the resource doesn't exist on the TPM, or it lives within // a hierarchy that is disabled. type ResourceUnavailableError struct { Handle Handle } func (e ResourceUnavailableError) Error() string { return fmt.Sprintf("a resource at handle 0x%08x is not available on the TPM", e.Handle) } func (e ResourceUnavailableError) Is(target error) bool { t, ok := target.(ResourceUnavailableError) if !ok { return false } return t.Handle == AnyHandle || t.Handle == e.Handle } // InvalidResponseError is returned from any TPMContext method that executes a TPM command // if the TPM's response is invalid. An invalid response could be one that is shorter than // the response header, one with an invalid responseSize field, a payload size that doesn't // match what the responseSize field indicates, a payload that unmarshals incorrectly or an // invalid response authorization. // // Any sessions used in the command that caused this error should be considered invalid. // // If any function that executes a command which allocates objects on the TPM returns this // error, it is possible that these objects were allocated and now exist on the TPM without // a corresponding HandleContext being created or any knowledge of the handle of the object // created. // // If any function that executes a command which removes objects from the TPM returns this // error, it is possible that these objects were removed from the TPM. Any associated // HandleContexts should be considered stale after this error. type InvalidResponseError struct { Command CommandCode msg string } func (e *InvalidResponseError) Error() string { return fmt.Sprintf("TPM returned an invalid response for command %s: %v", e.Command, e.msg) } // TctiError is returned from any TPMContext method if the underlying TCTI returns an error. type TctiError struct { Op string // The operation that caused the error err error } func (e *TctiError) Error() string { return fmt.Sprintf("cannot complete %s operation on TCTI: %v", e.Op, e.err) } func (e *TctiError) Unwrap() error { return e.err } // TPM1Error is returned from DecodeResponseCode and any TPMContext method that executes a // command on the TPM if the TPM response code indicates an error from a TPM 1.2 device. type TPM1Error struct { Command CommandCode // Command code associated with this error Code ResponseCode // Response code } func (e *TPM1Error) Error() string { return fmt.Sprintf("TPM returned a 1.2 error whilst executing command %s: 0x%08x", e.Command, e.Code) } // TPMVendorError is returned from DecodeResponseCode and and TPMContext method that executes // a command on the TPM if the TPM response code indicates a vendor-specific error. type TPMVendorError struct { Command CommandCode // Command code associated with this error Code ResponseCode // Response code } func (e *TPMVendorError) Error() string { return fmt.Sprintf("TPM returned a vendor defined error whilst executing command %s: 0x%08x", e.Command, e.Code) } // WarningCode represents a response from the TPM that is not necessarily an // error. It represents TCG defined format 0 errors that are warnings // (represented by response codes 0x900 to 0x97f). type WarningCode uint8 const ( WarningContextGap WarningCode = 0x01 // TPM_RC_CONTEXT_GAP WarningObjectMemory WarningCode = 0x02 // TPM_RC_OBJECT_MEMORY WarningSessionMemory WarningCode = 0x03 // TPM_RC_SESSION_MEMORY WarningMemory WarningCode = 0x04 // TPM_RC_MEMORY WarningSessionHandles WarningCode = 0x05 // TPM_RC_SESSION_HANDLES WarningObjectHandles WarningCode = 0x06 // TPM_RC_OBJECT_HANDLES // WarningLocality corresponds to TPM_RC_LOCALITY and is returned for a command if a policy session is used for authorization and the // session includes a TPM2_PolicyLocality assertion, but the command isn't executed with the authorized locality. WarningLocality WarningCode = 0x07 // WarningYielded corresponds to TPM_RC_YIELDED and is returned for any command that is suspended as a hint that the command can be // retried. This is handled automatically by all methods on TPMContext that execute commands via TPMContext.RunCommand by // resubmitting the command. WarningYielded WarningCode = 0x08 // WarningCanceled corresponds to TPM_RC_CANCELED and is returned for any command that is canceled before being able to complete. WarningCanceled WarningCode = 0x09 WarningTesting WarningCode = 0x0a // TPM_RC_TESTING WarningReferenceH0 WarningCode = 0x10 // TPM_RC_REFERENCE_H0 WarningReferenceH1 WarningCode = 0x11 // TPM_RC_REFERENCE_H1 WarningReferenceH2 WarningCode = 0x12 // TPM_RC_REFERENCE_H2 WarningReferenceH3 WarningCode = 0x13 // TPM_RC_REFERENCE_H3 WarningReferenceH4 WarningCode = 0x14 // TPM_RC_REFERENCE_H4 WarningReferenceH5 WarningCode = 0x15 // TPM_RC_REFERENCE_H5 WarningReferenceH6 WarningCode = 0x16 // TPM_RC_REFERENCE_H6 WarningReferenceS0 WarningCode = 0x18 // TPM_RC_REFERENCE_S0 WarningReferenceS1 WarningCode = 0x19 // TPM_RC_REFERENCE_S1 WarningReferenceS2 WarningCode = 0x1a // TPM_RC_REFERENCE_S2 WarningReferenceS3 WarningCode = 0x1b // TPM_RC_REFERENCE_S3 WarningReferenceS4 WarningCode = 0x1c // TPM_RC_REFERENCE_S4 WarningReferenceS5 WarningCode = 0x1d // TPM_RC_REFERENCE_S5 WarningReferenceS6 WarningCode = 0x1e // TPM_RC_REFERENCE_S6 // WarningNVRate corresponds to TPM_RC_NV_RATE and is returned for any command that requires NV access if NV access is currently // rate limited to prevent the NV memory from wearing out. WarningNVRate WarningCode = 0x20 // WarningLockout corresponds to TPM_RC_LOCKOUT and is returned for any command that requires authorization for an entity that is // subject to dictionary attack protection, and the TPM is in dictionary attack lockout mode. WarningLockout WarningCode = 0x21 // WarningRetry corresponds to TPM_RC_RETRY and is returned for any command if the TPM was not able to start the command. This is // handled automatically by all methods on TPMContext that execute commands via TPMContext.RunCommand by resubmitting the command. WarningRetry WarningCode = 0x22 // WarningNVUnavailable corresponds to TPM_RC_NV_UNAVAILABLE and is returned for any command that requires NV access but NV memory // is currently not available. WarningNVUnavailable WarningCode = 0x23 ) // TPMWarning is returned from DecodeResponseCode and any TPMContext method that executes // a command on the TPM if the TPM response code indicates a condition that is not necessarily // an error. type TPMWarning struct { Command CommandCode // Command code associated with this error Code WarningCode // Warning code } func (e *TPMWarning) ResponseCode() ResponseCode { return responseCodeS | responseCodeV | (ResponseCode(e.Code) & responseCodeE0) } func (e *TPMWarning) Error() string { var builder bytes.Buffer fmt.Fprintf(&builder, "TPM returned a warning whilst executing command %s: %s", e.Command, e.Code) if desc, hasDesc := warningCodeDescriptions[e.Code]; hasDesc { fmt.Fprintf(&builder, " (%s)", desc) } return builder.String() } func (e *TPMWarning) Is(target error) bool { t, ok := target.(*TPMWarning) if !ok { return false } return (t.Code == AnyWarningCode || t.Code == e.Code) && (t.Command == AnyCommandCode || t.Command == e.Command) } // ErrorCode represents an error code from the TPM. This type represents // TCG defined format 0 errors with the exception of warnings (represented // by response codes 0x100 to 0x17f), and format 1 errors (represented by // response codes with bit 7 set). Format 0 error numbers are 7 bits wide // and are represented by codes 0x00 to 0x7f. Format 1 errors numbers are // 6 bits wide and are represented by codes 0x80 to 0xbf. type ErrorCode uint8 const ( // ErrorInitialize corresponds to TPM_RC_INITIALIZE and is returned for any command executed between a _TPM_Init event and a // TPM2_Startup command. ErrorInitialize ErrorCode = 0x00 // ErrorFailure corresponds to TPM_RC_FAILURE and is returned for any command if the TPM is in failure mode. ErrorFailure ErrorCode = 0x01 ErrorSequence ErrorCode = 0x03 // TPM_RC_SEQUENCE ErrorDisabled ErrorCode = 0x20 // TPM_RC_DISABLED ErrorExclusive ErrorCode = 0x21 // TPM_RC_EXCLUSIVE // ErrorAuthType corresponds to TPM_RC_AUTH_TYPE and is returned for a command where an authorization is required and the // authorization type is expected to be a policy session, but another authorization type has been provided. ErrorAuthType ErrorCode = 0x24 // ErrorAuthMissing corresponds to TPM_RC_AUTH_MISSING and is returned for a command that accepts a HandleContext or Handle // argument that requires authorization, but no authorization session has been provided in the command payload. ErrorAuthMissing ErrorCode = 0x25 ErrorPolicy ErrorCode = 0x26 // TPM_RC_POLICY ErrorPCR ErrorCode = 0x27 // TPM_RC_PCR // ErrorPCRChanged corresponds to TPM_RC_PCR_CHANGED and is returned for a command where a policy session is used for authorization // and the PCR contents have been updated since the last time that they were checked in the session with a TPM2_PolicyPCR assertion. ErrorPCRChanged ErrorCode = 0x28 // ErrorUpgrade corresponds to TPM_RC_UPGRADE and is returned for any command that isn't TPM2_FieldUpgradeData if the TPM is in // field upgrade mode. ErrorUpgrade ErrorCode = 0x2d ErrorTooManyContexts ErrorCode = 0x2e // TPM_RC_TOO_MANY_CONTEXTS // ErrorAuthUnavailable corresponds to TPM_RC_AUTH_UNAVAILABLE and is returned for a command where the provided authorization // requires the use of the authorization value for an entity, but the authorization value cannot be used. For example, if the entity // is an object and the command requires the user auth role but the object does not have the AttrUserWithAuth attribute. ErrorAuthUnavailable ErrorCode = 0x2f // ErrorReboot corresponds to TPM_RC_REBOOT and is returned for any command if the TPM requires a _TPM_Init event before it will // execute any more commands. ErrorReboot ErrorCode = 0x30 ErrorUnbalanced ErrorCode = 0x31 // TPM_RC_UNBALANCED // ErrorCommandSize corresponds to TPM_RC_COMMAND_SIZE and indicates that the value of the commandSize field in the command header // does not match the size of the command packet transmitted to the TPM. ErrorCommandSize ErrorCode = 0x42 // ErrorCommandCode corresponds to TPM_RC_COMMAND_CODE and is returned for any command that is not implemented by the TPM. ErrorCommandCode ErrorCode = 0x43 ErrorAuthsize ErrorCode = 0x44 // TPM_RC_AUTHSIZE // ErrorAuthContext corresponds to TPM_RC_AUTH_CONTEXT and is returned for any command that does not accept any sessions if // sessions have been provided in the command payload. ErrorAuthContext ErrorCode = 0x45 ErrorNVRange ErrorCode = 0x46 // TPM_RC_NV_RANGE ErrorNVSize ErrorCode = 0x47 // TPM_RC_NV_SIZE ErrorNVLocked ErrorCode = 0x48 // TPM_RC_NV_LOCKED ErrorNVAuthorization ErrorCode = 0x49 // TPM_RC_NV_AUTHORIZATION ErrorNVUninitialized ErrorCode = 0x4a // TPM_RC_NV_UNINITIALIZED ErrorNVSpace ErrorCode = 0x4b // TPM_RC_NV_SPACE ErrorNVDefined ErrorCode = 0x4c // TPM_RC_NV_DEFINED ErrorBadContext ErrorCode = 0x50 // TPM_RC_BAD_CONTEXT ErrorCpHash ErrorCode = 0x51 // TPM_RC_CPHASH ErrorParent ErrorCode = 0x52 // TPM_RC_PARENT ErrorNeedsTest ErrorCode = 0x53 // TPM_RC_NEEDS_TEST // ErrorNoResult corresponds to TPM_RC_NO_RESULT and is returned for any command if the TPM cannot process a request due to an // unspecified problem. ErrorNoResult ErrorCode = 0x54 ErrorSensitive ErrorCode = 0x55 // TPM_RC_SENSITIVE errorCode1Start ErrorCode = 0x80 ErrorAsymmetric ErrorCode = errorCode1Start + 0x01 // TPM_RC_ASYMMETRIC // ErrorAttributes corresponds to TPM_RC_ATTRIBUTES and is returned as a *TPMSessionError for a command in the following // circumstances: // * More than one SessionContext instance with the AttrCommandEncrypt attribute has been provided. // * More than one SessionContext instance with the AttrResponseEncrypt attribute has been provided. // * A SessionContext instance referencing a trial session has been provided for authorization. ErrorAttributes ErrorCode = errorCode1Start + 0x02 // ErrorHash corresponds to TPM_RC_HASH and is returned as a *TPMParameterError error for any command that accepts a AlgorithmId // parameter that corresponds to the TPMI_ALG_HASH interface type if the parameter value is not a valid digest algorithm. ErrorHash ErrorCode = errorCode1Start + 0x03 // ErrorValue corresponds to TPM_RC_VALUE and is returned as a *TPMParameterError or *TPMHandleError for any command where an // argument value is incorrect or out of range for the command. ErrorValue ErrorCode = errorCode1Start + 0x04 // TPM_RC_VALUE // ErrorHierarchy corresponds to TPM_RC_HIERARCHY and is returned as a *TPMHandleError error for any command that accepts a // HandleContext or Handle argument if that argument corresponds to a hierarchy on the TPM that has been disabled. ErrorHierarchy ErrorCode = errorCode1Start + 0x05 ErrorKeySize ErrorCode = errorCode1Start + 0x07 // TPM_RC_KEY_SIZE ErrorMGF ErrorCode = errorCode1Start + 0x08 // TPM_RC_MGF // ErrorMode corresponds to TPM_RC_MODE and is returned as a *TPMParameterError error for any command that accepts a AlgorithmId // parameter that corresponds to the TPMI_ALG_SYM_MODE interface type if the parameter value is not a valid symmetric mode. ErrorMode ErrorCode = errorCode1Start + 0x09 // ErrorType corresponds to TPM_RC_TYPE and is returned as a *TPMParameterError error for any command that accepts a AlgorithmId // parameter that corresponds to the TPMI_ALG_PUBLIC interface type if the parameter value is not a valid public type. ErrorType ErrorCode = errorCode1Start + 0x0a ErrorHandle ErrorCode = errorCode1Start + 0x0b // TPM_RC_HANDLE // ErrorKDF corresponds to TPM_RC_KDF and is returned as a *TPMParameterError error for any command that accepts a AlgorithmId // parameter that corresponds to the TPMI_ALG_KDF interface type if the parameter value is not a valid key derivation function. ErrorKDF ErrorCode = errorCode1Start + 0x0c ErrorRange ErrorCode = errorCode1Start + 0x0d // TPM_RC_RANGE // ErrorAuthFail corresponds to TPM_RC_AUTH_FAIL and is returned as a *TPMSessionError error for a command if an authorization // check fails. The dictionary attack counter is incremented when this error is returned. ErrorAuthFail ErrorCode = errorCode1Start + 0x0e // ErrorNonce corresponds to TPM_RC_NONCE and is returned as a *TPMSessionError error for any command where a password authorization // has been provided and the authorization session in the command payload contains a non-zero sized nonce field. ErrorNonce ErrorCode = errorCode1Start + 0x0f // ErrorPP corresponds to TPM_RC_PP and is returned as a *TPMSessionError for a command in the following circumstances: // * Authorization of the platform hierarchy is provided and the command requires an assertion of physical presence that hasn't been // provided. // * Authorization is provided with a policy session that includes the TPM2_PolicyPhysicalPresence assertion, and an assertion of // physical presence hasn't been provided. ErrorPP ErrorCode = errorCode1Start + 0x10 // ErrorScheme corresponds to TPM_RC_SCHEME and is returned as a *TPMParameterError error for any command that accepts a AlgorithmId // parameter that corresponds to the TPMI_ALG_SIG_SCHEME or TPMI_ALG_ECC_SCHEME interface types if the parameter value is not a valid // signature or ECC key exchange scheme. ErrorScheme ErrorCode = errorCode1Start + 0x12 // ErrorSize corresponds to TPM_RC_SIZE and is returned for a command in the following circumstances: // * As a *TPMParameterError if the command accepts a parameter type corresponding to TPM2B or TPML prefixed types and the size or // length field has an invalid value. // * As a *TPMHandleError with an unspecified handle if the TPM's parameter unmarshalling doesn't consume all of the bytes in the // input buffer. // * As a *TPMHandleError with an unspecified handle if the size field of the command's authorization area is an invalid value. // * As a *TPMSessionError if the authorization area for a command payload contains more than 3 sessions. ErrorSize ErrorCode = errorCode1Start + 0x15 // ErrorSymmetric corresponds to TPM_RC_SYMMETRIC and is returned for a command in the following circumstances: // * As a *TPMParameterError if the command accepts a AlgorithmId parameter that corresponds to the TPMI_ALG_SYM interface type // and the parameter value is not a valid symmetric algorithm. // * As a *TPMSessionError if a SessionContext instance is provided with the AttrCommandEncrypt attribute set but the session has no // symmetric algorithm. // * As a *TPMSessionError if a SessionContext instance is provided with the AttrResponseEncrypt attribute set but the session has no // symmetric algorithm. ErrorSymmetric ErrorCode = errorCode1Start + 0x16 // ErrorTag corresponds to TPM_RC_TAG and is returned as a *TPMParameterError error for a command that accepts a StructTag parameter // if the parameter value is not the correct value. ErrorTag ErrorCode = errorCode1Start + 0x17 // ErrorSelector corresponds to TPM_RC_SELECTOR and is returned as a *TPMParameterError error for a command that accepts a parameter // type corresponding to a TPMU prefixed type if the value of the selector field in the surrounding TPMT prefixed type is incorrect. ErrorSelector ErrorCode = errorCode1Start + 0x18 // ErrorInsufficient corresponds to TPM_RC_INSUFFICIENT and is returned as a *TPMParameterError for a command if there is // insufficient data in the TPM's input buffer to complete unmarshalling of the command parameters. ErrorInsufficient ErrorCode = errorCode1Start + 0x1a ErrorSignature ErrorCode = errorCode1Start + 0x1b // TPM_RC_SIGNATURE ErrorKey ErrorCode = errorCode1Start + 0x1c // TPM_RC_KEY // ErrorPolicyFail corresponds to TPM_RC_POLICY_FAIL and is returned as a *TPMSessionError error for a command in the following // circumstances: // * A policy session is used for authorization and the policy session digest does not match the authorization policy digest for // the entity being authorized. // * A policy session is used for authorization and the digest algorithm of the session does not match the name algorithm of the // entity being authorized. // * A policy session is used for authorization but the authorization is for the admin or DUP role and the policy session does not // include a TPM2_PolicyCommandCode assertion. // * A policy session is used for authorization and the policy session includes a TPM2_PolicyNvWritten assertion but the entity // being authorized is not a NV index. // * A policy session is used for authorization, the policy session includes the TPM2_PolicyNvWritten assertion, but the NV index // being authorized does not have the AttrNVWritten attribute set. ErrorPolicyFail ErrorCode = errorCode1Start + 0x1d ErrorIntegrity ErrorCode = errorCode1Start + 0x1f // TPM_RC_INTEGRITY ErrorTicket ErrorCode = errorCode1Start + 0x20 // TPM_RC_TICKET // ErroReservedBits corresponds to TPM_RC_RESERVED_BITS and is returned as a *TPMParameterError error for a command that accepts // a parameter type corresponding to a TPMA prefixed type if the parameter value has reserved bits set. ErrorReservedBits ErrorCode = errorCode1Start + 0x21 // ErrorBadAuth corresponds to TPM_RC_BAD_AUTH and is returned as a *TPMSessionError error for a command if an authorization // check fails and the authorized entity is excempt from dictionary attack protections. ErrorBadAuth ErrorCode = errorCode1Start + 0x22 // ErrorExpired corresponds to TPM_RC_EXPIRED and is returned as a *TPMSessionError error for a command if a policy session is used // for authorization, and the session has expired. ErrorExpired ErrorCode = errorCode1Start + 0x23 // ErrorPolicyCC corresponds to TPM_RC_POLICY_CC and is returned as a *TPMSessionError error for a command if a policy session is // used for authorization, the session includes a TPM2_PolicyCommandCode assertion, but the command code doesn't match the command // for which the authorization is being used for. ErrorPolicyCC ErrorCode = errorCode1Start + 0x24 ErrorBinding ErrorCode = errorCode1Start + 0x25 // TPM_RC_BINDING // ErrorCurve corresponds to TPM_RC_CURVE and is returned as a *TPMParameterError for a command that accepts a ECCCurve parameter // if the parameter value is incorrect. ErrorCurve ErrorCode = errorCode1Start + 0x26 ErrorECCPoint ErrorCode = errorCode1Start + 0x27 // TPM_RC_ECC_POINT // ErrorBadTag corresponds to TPM_RC_BAD_TAG and is returned from any TPM command if the command tag is invalid. // This will be the error when trying to execute a TPM2 command on a TPM1.2 device. ErrorBadTag ErrorCode = 0xde ) // TPMError is returned from DecodeResponseCode and any TPMContext method that // executes a command on the TPM if the TPM response code indicates an error that // is not associated with a handle, parameter or session. type TPMError struct { Command CommandCode // Command code associated with this error Code ErrorCode // Error code } func (e *TPMError) ResponseCode() ResponseCode { switch { case e.Code == ErrorBadTag: return ResponseBadTag case e.Code >= 0x80: return responseCodeF | (ResponseCode(e.Code) & responseCodeE1) default: return responseCodeV | (ResponseCode(e.Code) & responseCodeE0) } } func (e *TPMError) Error() string { var builder bytes.Buffer fmt.Fprintf(&builder, "TPM returned an error whilst executing command %s: %s", e.Command, e.Code) if desc, hasDesc := errorCodeDescriptions[e.Code]; hasDesc { fmt.Fprintf(&builder, " (%s)", desc) } return builder.String() } func (e *TPMError) Is(target error) bool { t, ok := target.(*TPMError) if !ok { return false } return (t.Code == AnyErrorCode || t.Code == e.Code) && (t.Command == AnyCommandCode || t.Command == e.Command) } // TPMParameterError is returned from DecodeResponseCode and any TPMContext method // that executes a command on the TPM if the TPM response code indicates an error // that is associated with a command parameter. It wraps a *TPMError. type TPMParameterError struct { *TPMError Index int // Index of the parameter associated with this error in the command parameter area, starting from 1 } func (e *TPMParameterError) ResponseCode() ResponseCode { return (ResponseCode(uint8(e.Index)&responseCodeIndex) << responseCodeIndexShift) | responseCodeF | responseCodeP | (ResponseCode(e.Code) & responseCodeE0) } func (e *TPMParameterError) Error() string { var builder bytes.Buffer fmt.Fprintf(&builder, "TPM returned an error for parameter %d whilst executing command %s: %s", e.Index, e.Command, e.Code) if desc, hasDesc := errorCodeDescriptions[e.Code]; hasDesc { fmt.Fprintf(&builder, " (%s)", desc) } return builder.String() } func (e *TPMParameterError) Is(target error) bool { t, ok := target.(*TPMParameterError) if !ok { return false } return e.TPMError.Is(t.TPMError) && (t.Index == AnyParameterIndex || t.Index == e.Index) } func (e *TPMParameterError) Unwrap() error { return e.TPMError } // TPMSessionError is returned from DecodeResponseCode and any TPMContext method // that executes a command on the TPM if the TPM response code indicates an error // that is associated with a session. It wraps a *TPMError. type TPMSessionError struct { *TPMError Index int // Index of the session associated with this error in the authorization area, starting from 1 } const ( responseCodeHandleIndex uint8 = 0x7 responseCodeIsSession uint8 = 0x8 ) func (e *TPMSessionError) ResponseCode() ResponseCode { return (ResponseCode(responseCodeIsSession|(uint8(e.Index)&responseCodeHandleIndex)) << responseCodeIndexShift) | responseCodeF | (ResponseCode(e.Code) & responseCodeE0) } func (e *TPMSessionError) Error() string { var builder bytes.Buffer fmt.Fprintf(&builder, "TPM returned an error for session %d whilst executing command %s: %s", e.Index, e.Command, e.Code) if desc, hasDesc := errorCodeDescriptions[e.Code]; hasDesc { fmt.Fprintf(&builder, " (%s)", desc) } return builder.String() } func (e *TPMSessionError) Is(target error) bool { t, ok := target.(*TPMSessionError) if !ok { return false } return e.TPMError.Is(t.TPMError) && (t.Index == AnySessionIndex || t.Index == e.Index) } func (e *TPMSessionError) Unwrap() error { return e.TPMError } // TPMHandleError is returned from DecodeResponseCode and any TPMContext method that // executes a command on the TPM if the TPM response code indicates an error that is // associated with a command handle. It wraps a *TPMError. type TPMHandleError struct { *TPMError // Index is the index of the handle associated with this error in the command handle area, starting from 1. An index of 0 corresponds // to an unspecified handle Index int } func (e *TPMHandleError) ResponseCode() ResponseCode { return (ResponseCode(uint8(e.Index)&responseCodeHandleIndex) << responseCodeIndexShift) | responseCodeF | (ResponseCode(e.Code) & responseCodeE0) } func (e *TPMHandleError) Error() string { var builder bytes.Buffer fmt.Fprintf(&builder, "TPM returned an error for handle %d whilst executing command %s: %s", e.Index, e.Command, e.Code) if desc, hasDesc := errorCodeDescriptions[e.Code]; hasDesc { fmt.Fprintf(&builder, " (%s)", desc) } return builder.String() } func (e *TPMHandleError) Is(target error) bool { t, ok := target.(*TPMHandleError) if !ok { return false } return e.TPMError.Is(t.TPMError) && (t.Index == AnyHandleIndex || t.Index == e.Index) } func (e *TPMHandleError) Unwrap() error { return e.TPMError } // IsResourceUnavailableError indicates whether an error is a ResourceUnavailableError with // the specified handle. To test for any handle, use AnyHandle. func IsResourceUnavailableError(err error, handle Handle) bool { return xerrors.Is(err, ResourceUnavailableError{Handle: handle}) } // IsTPMError indicates whether the error or any error within its chain is a *TPMError with // the specified ErrorCode and CommandCode. To test for any error code, use AnyErrorCode. To // test for any command code, use AnyCommandCode. func IsTPMError(err error, code ErrorCode, command CommandCode) bool { return xerrors.Is(err, &TPMError{Command: command, Code: code}) } // IsTPMHandleError indicates whether the error or any error within its chain is a // *TPMHandleError with the specified ErrorCode, CommandCode and handle index. To test for // any error code, use AnyErrorCode. To test for any command code, use AnyCommandCode. To // test for any handle index, use AnyHandleIndex. func IsTPMHandleError(err error, code ErrorCode, command CommandCode, handle int) bool { return xerrors.Is(err, &TPMHandleError{TPMError: &TPMError{Command: command, Code: code}, Index: handle}) } // IsTPMParameterError indicates whether the error or any error within its chain is a // *TPMParameterError with the specified ErrorCode, CommandCode and parameter index. To test // for any error code, use AnyErrorCode. To test for any command code, use AnyCommandCode. // To test for any parameter index, use AnyParameterIndex. func IsTPMParameterError(err error, code ErrorCode, command CommandCode, param int) bool { return xerrors.Is(err, &TPMParameterError{TPMError: &TPMError{Command: command, Code: code}, Index: param}) } // IsTPMSessionError indicates whether the error or any error within its chain is a // *TPMSessionError with the specified ErrorCode, CommandCode and session index. To test for any // error code, use AnyErrorCode. To test for any command code, use AnyCommandCode. To test for // any session index, use AnySessionIndex. func IsTPMSessionError(err error, code ErrorCode, command CommandCode, session int) bool { return xerrors.Is(err, &TPMSessionError{TPMError: &TPMError{Command: command, Code: code}, Index: session}) } // IsTPMWarning indicates whether the error or any error within its chain is a *TPMWarning with the // specified WarningCode and CommandCode. To test for any warning code, use AnyWarningCode. To test // for any command code, use AnyCommandCode. func IsTPMWarning(err error, code WarningCode, command CommandCode) bool { return xerrors.Is(err, &TPMWarning{Command: command, Code: code}) } type InvalidResponseCodeError ResponseCode func (e InvalidResponseCodeError) Error() string { return fmt.Sprintf("invalid response code 0x%08x", ResponseCode(e)) } // DecodeResponseCode decodes the ResponseCode provided via resp. If the specified response code is // Success, it returns no error, else it returns an error that is appropriate for the response code. // The command code is used for adding context to the returned error. // // If the response code is invalid, an InvalidResponseCodeError error will be returned. func DecodeResponseCode(command CommandCode, resp ResponseCode) error { switch { case resp == ResponseSuccess: return nil case resp == ResponseBadTag: return &TPMError{Command: command, Code: ErrorBadTag} case resp.F(): // Format-one error codes err := &TPMError{Command: command, Code: ErrorCode(resp.E()) + errorCode1Start} switch { case resp.P(): // Associated with a parameter return &TPMParameterError{TPMError: err, Index: int(resp.N())} case resp.N()&0x8 != 0: // Associated with a session return &TPMSessionError{TPMError: err, Index: int(resp.N() & 0x7)} case resp.N() != 0: // Associated with a handle return &TPMHandleError{TPMError: err, Index: int(resp.N())} default: // Not associated with a specific parameter, session or handle return err } default: // Format-zero error codes switch { case !resp.V(): // A TPM1.2 error return InvalidResponseCodeError(resp) case resp.T(): // An error defined by the TPM vendor return &TPMVendorError{Command: command, Code: resp} case resp.S(): // A warning return &TPMWarning{Command: command, Code: WarningCode(resp.E())} default: return &TPMError{Command: command, Code: ErrorCode(resp.E())} } } } ./github.com/canonical/go-tpm2/go.mod0000664000000000000000000000120500000000000015723 0ustar0000000000000000module github.com/canonical/go-tpm2 go 1.9 require ( github.com/canonical/go-sp800.108-kdf v0.0.0-20210314145419-a3359f2d21b9 github.com/kr/text v0.1.0 github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e github.com/snapcore/go-gettext v0.0.0-20191107141714-82bbea49e785 github.com/snapcore/snapd v0.0.0-20201005140838-501d14ac146e golang.org/x/net v0.0.0-20201002202402-0a1ea396d57c golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c gopkg.in/tomb.v2 v2.0.0-20161208151619-d5d1b5820637 gopkg.in/yaml.v2 v2.3.0 ) ./github.com/canonical/go-tpm2/go.sum0000664000000000000000000000661700000000000015764 0ustar0000000000000000github.com/canonical/go-sp800.108-kdf v0.0.0-20210314145419-a3359f2d21b9 h1:USzKjrfWo/ESzozv2i3OMM7XDgxrZRvaHFrKkIKRtwU= github.com/canonical/go-sp800.108-kdf v0.0.0-20210314145419-a3359f2d21b9/go.mod h1:Zrs3YjJr+w51u0R/dyLh/oWt/EcBVdLPCVFYC4daW5s= github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/snapcore/go-gettext v0.0.0-20191107141714-82bbea49e785 h1:PaunR+BhraKSLxt2awQ42zofkP+NKh/VjQ0PjIMk/y4= github.com/snapcore/go-gettext v0.0.0-20191107141714-82bbea49e785/go.mod h1:D3SsWAXK7wCCBZu+Vk5hc1EuKj/L3XN1puEMXTU4LrQ= github.com/snapcore/snapd v0.0.0-20201005140838-501d14ac146e h1:vqDZWKPBL9RKPA8KyOuTaSuXXlmRwn/ndvOkZaAmLJs= github.com/snapcore/snapd v0.0.0-20201005140838-501d14ac146e/go.mod h1:3xrn7QDDKymcE5VO2rgWEQ5ZAUGb9htfwlXnoel6Io8= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20201002202402-0a1ea396d57c/go.mod h1:iQL9McJNjoIa5mjH6nYTCTZXUN6RP+XW3eib7Ya3XcI= golang.org/x/sys v0.0.0-20180302081741-dd2ff4accc09/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f h1:+Nyd8tzPX9R7BWHguqsrbFdRx3WQ/1ib8I44HXV5yTA= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898 h1:/atklqdjdhuosWIl6AIbOeHJjicWYPqR9bpxqxYG2pA= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b h1:QRR6H1YWRnHb4Y/HeNFCTJLFVxaq6wH4YuVdsUOr75U= gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/tomb.v2 v2.0.0-20161208151619-d5d1b5820637 h1:yiW+nvdHb9LVqSHQBXfZCieqV4fzYhNBql77zY0ykqs= gopkg.in/tomb.v2 v2.0.0-20161208151619-d5d1b5820637/go.mod h1:BHsqpu/nsuzkT5BpiH1EMZPLyqSMM8JbIavyFACoFNk= gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= ./github.com/canonical/go-tpm2/internal/0000775000000000000000000000000000000000000016433 5ustar0000000000000000./github.com/canonical/go-tpm2/internal/crypto.go0000664000000000000000000000304100000000000020300 0ustar0000000000000000// Copyright 2019 Canonical Ltd. // Licensed under the LGPLv3 with static-linking exception. // See LICENCE file for details. package internal import ( "bytes" "crypto" "encoding/binary" "github.com/canonical/go-sp800.108-kdf" ) func KDFa(hashAlg crypto.Hash, key, label, contextU, contextV []byte, sizeInBits int) []byte { context := make([]byte, len(contextU)+len(contextV)) copy(context, contextU) copy(context[len(contextU):], contextV) return kdf.CounterModeKey(kdf.NewHMACPRF(hashAlg), key, label, context, uint32(sizeInBits)) } func KDFe(hashAlg crypto.Hash, z, label, partyUInfo, partyVInfo []byte, sizeInBits int) []byte { digestSize := hashAlg.Size() counter := 0 var res bytes.Buffer for bytes := (sizeInBits + 7) / 8; bytes > 0; bytes -= digestSize { if bytes < digestSize { digestSize = bytes } counter++ h := hashAlg.New() binary.Write(h, binary.BigEndian, uint32(counter)) h.Write(z) h.Write(label) h.Write([]byte{0}) h.Write(partyUInfo) h.Write(partyVInfo) res.Write(h.Sum(nil)[0:digestSize]) } outKey := res.Bytes() if sizeInBits%8 != 0 { outKey[0] &= ((1 << uint(sizeInBits%8)) - 1) } return outKey } func XORObfuscation(hashAlg crypto.Hash, key []byte, contextU, contextV, data []byte) { context := make([]byte, len(contextU)+len(contextV)) copy(context, contextU) copy(context[len(contextU):], contextV) dataSize := len(data) mask := kdf.CounterModeKey(kdf.NewHMACPRF(hashAlg), key, []byte("XOR"), context, uint32(dataSize*8)) for i := 0; i < dataSize; i++ { data[i] ^= mask[i] } } ./github.com/canonical/go-tpm2/linux/0000775000000000000000000000000000000000000015756 5ustar0000000000000000./github.com/canonical/go-tpm2/linux/linux.go0000664000000000000000000000436600000000000017455 0ustar0000000000000000// Copyright 2019 Canonical Ltd. // Licensed under the LGPLv3 with static-linking exception. // See LICENCE file for details. /* Package linux provides an interface for communicating with TPMs using a Linux TPM character device */ package linux import ( "bytes" "errors" "fmt" "io" "os" "golang.org/x/sys/unix" "golang.org/x/xerrors" "github.com/canonical/go-tpm2" ) const ( maxCommandSize int = 4096 ) // TctiDevice represents a connection to a Linux TPM character device. type TctiDevice struct { f *os.File buf *bytes.Reader } func (d *TctiDevice) readMoreData() error { fds := []unix.PollFd{unix.PollFd{Fd: int32(d.f.Fd()), Events: unix.POLLIN}} _, err := unix.Ppoll(fds, nil, nil) if err != nil { return xerrors.Errorf("polling device failed: %w", err) } if fds[0].Events != fds[0].Revents { return fmt.Errorf("invalid poll events returned: %d", fds[0].Revents) } buf := make([]byte, maxCommandSize) n, err := d.f.Read(buf) if err != nil { return xerrors.Errorf("reading from device failed: %w", err) } d.buf = bytes.NewReader(buf[:n]) return nil } func (d *TctiDevice) Read(data []byte) (int, error) { if d.buf == nil { if err := d.readMoreData(); err != nil { return 0, err } } n, err := d.buf.Read(data) if err == io.EOF { d.buf = nil } return n, err } func (d *TctiDevice) Write(data []byte) (int, error) { return d.f.Write(data) } func (d *TctiDevice) Close() error { return d.f.Close() } func (d *TctiDevice) SetLocality(locality uint8) error { return errors.New("not implemented") } func (d *TctiDevice) MakeSticky(handle tpm2.Handle, sticky bool) error { return errors.New("not implemented") } // OpenDevice attempts to open a connection to the Linux TPM character device at // the specified path. If successful, it returns a new TctiDevice instance which // can be passed to tpm2.NewTPMContext. Failure to open the TPM character device // will result in a *os.PathError being returned. func OpenDevice(path string) (*TctiDevice, error) { f, err := os.OpenFile(path, os.O_RDWR, 0) if err != nil { return nil, err } s, err := f.Stat() if err != nil { return nil, err } if s.Mode()&os.ModeDevice == 0 { return nil, fmt.Errorf("unsupported file mode %v", s.Mode()) } return &TctiDevice{f: f}, nil } ./github.com/canonical/go-tpm2/mu/0000775000000000000000000000000000000000000015240 5ustar0000000000000000./github.com/canonical/go-tpm2/mu/doc.go0000664000000000000000000000506500000000000016342 0ustar0000000000000000/* Package mu provides helpers to marshalling to and unmarshalling from the TPM wire format. Go types are marshalled to and from the TPM wire format according to the following rules: * UINT8 <-> uint8 * BYTE <-> byte * INT8 <-> int8 * BOOL <-> bool * UINT16 <-> uint16 * INT16 <-> int16 * UINT32 <-> uint32 * INT32 <-> int32 * UINT64 <-> uint64 * INT64 <-> int64 * TPM2B prefixed types (sized buffers with a 2-byte size field) fall in to 2 categories: * Byte buffer <-> []byte, or any type with an identical underlying type. * Sized structure <-> struct referenced from a pointer field in another structure, where the field has the `tpm2:"sized"` tag. A zero sized struct is represented as a nil pointer. * TPMA prefixed types (attributes) <-> whichever go type corresponds to the underlying TPM type (UINT8, UINT16, or UINT32). * TPM_ALG_ID (algorithm enum) <-> tpm2.AlgorithmId * TPML prefixed types (lists with a 4-byte length field) <-> slice of whichever go type corresponds to the underlying TPM type. * TPMS prefixed types (structures) <-> struct * TPMT prefixed types (structures with a tag field used as a union selector) <-> struct * TPMU prefixed types (unions) <-> struct which implements the Union interface. These must be contained in another structure and referenced from a pointer field. The first field of the enclosing structure is used as the selector value, although this can be overridden by using the `tpm2:"selector:"` tag. TPMI prefixed types (interface types) are generally not explicitly supported. These are used by the TPM for type checking during unmarshalling, but this package doesn't distinguish between TPMI prefixed types with the same underlying type. The marshalling code parses the "tpm2" tag on struct fields, the value of which is a comma separated list of options. These options are: * selector: - used on fields that are pointers to structs that implement the Union interface to specify the field used as the selector value. The default behaviour without this option is to use the first field as the selector. * sized - used on fields that are pointers to indicate that it should be marshalled and unmarshalled as a sized value. A nil pointer represents a zero-sized value. This is used to implement sized structures. * raw - used on slice fields to indicate that it should be marshalled and unmarshalled without a length (if it represents a list) or size (if it represents a sized buffer) field. The slice must be pre-allocated to the correct length by the caller during unmarshalling. */ package mu ./github.com/canonical/go-tpm2/mu/mu.go0000664000000000000000000007243700000000000016225 0ustar0000000000000000// Copyright 2019 Canonical Ltd. // Licensed under the LGPLv3 with static-linking exception. // See LICENCE file for details. package mu import ( "bytes" "encoding/binary" "errors" "fmt" "io" "math" "os" "reflect" "runtime" "strings" "golang.org/x/xerrors" ) var ( customMuType reflect.Type = reflect.TypeOf((*customMuIface)(nil)).Elem() emptyIfaceType reflect.Type = reflect.TypeOf((*interface{})(nil)).Elem() unionType reflect.Type = reflect.TypeOf((*Union)(nil)).Elem() nilValueType reflect.Type = reflect.TypeOf(NilUnionValue) rawBytesType reflect.Type = reflect.TypeOf(RawBytes(nil)) ) // InvalidSelectorError may be returned as a wrapped error from UnmarshalFromBytes or UnmarshalFromReader when a union type indicates // that a selector value is invalid. type InvalidSelectorError struct { Selector reflect.Value } func (e *InvalidSelectorError) Error() string { return fmt.Sprintf("invalid selector value: %v", e.Selector) } type customMuIface interface { CustomMarshaller CustomUnmarshaller } // CustomMarshaller is implemented by types that require custom marshalling behaviour because they are non-standard and not // directly supported by the marshalling code. This interface should be implemented by types with a value receiver if you want // to be able to pass it directly by value to MarshalToBytes or MarshalToWriter. Implementations must also implement the // CustomUnmarshaller interface. // // If the custom implementation makes a recursive call in the MarshalToWriter, it should propagate errors from // this without wrapping. This allows the full context of the error to be surfaced from the originating call. type CustomMarshaller interface { Marshal(w io.Writer) error } // CustomUnmarshaller is implemented by types that require custom unmarshalling behaviour because they are non-standard and not // directly supported by the marshalling code. This interface must be implemented by types with a pointer receiver, and types // must also implement the CustomMarshaller interface. // // If the custom implementation makes a recursive call in the UnmarshalFromReader, it should propagate errors from // this without wrapping. This allows the full context of the error to be surfaced from the originating call. type CustomUnmarshaller interface { Unmarshal(r Reader) error } type empty struct{} // NilUnionValue is a special value, the type of which should be returned from implementations of Union.Select to indicate // that a union contains no data for a particular selector value. var NilUnionValue empty // RawBytes is a special byte slice type which is marshalled and unmarshalled without a size field. The slice must be pre-allocated to // the correct length by the caller during unmarshalling. type RawBytes []byte func Raw(val interface{}) interface{} { return &struct { Value interface{} `tpm2:"raw"` }{val} } // Sized converts the supplied value to a sized value. // // To marshal a sized value, the supplied value must be a pointer to the actual // value. // // To unmarshal a sized value, the supplied value must be a pointer to the // destionation pointer that will point to the unmarshalled value. func Sized(val interface{}) interface{} { return &struct { Value interface{} `tpm2:"sized"` }{val} } // Union is implemented by structure types that correspond to TPMU prefixed TPM types. type Union interface { // Select is called by the marshalling code to map the supplied selector to a field. The returned value must be a pointer to // the field to be marshalled or unmarshalled. To work correctly during marshalling and unmarshalling, implementations must // take a pointer receiver. If no data should be marshalled or unmarshalled, it should return NilUnionValue. Select(selector reflect.Value) interface{} } type containerNode struct { value reflect.Value index int entry [1]uintptr } type containerStack []containerNode func (s containerStack) push(node containerNode) containerStack { return append(s, node) } func (s containerStack) pop() containerStack { return s[:len(s)-1] } func (s containerStack) top() containerNode { return s[len(s)-1] } func (s containerStack) String() string { str := new(bytes.Buffer) str.WriteString("=== BEGIN STACK ===\n") for i := len(s) - 1; i >= 0; i-- { switch { case reflect.PtrTo(s[i].value.Type()).Implements(customMuType): frames := runtime.CallersFrames(s[i].entry[:]) frame, _ := frames.Next() fmt.Fprintf(str, "... %s custom type, call from %s:%d argument %d\n", s[i].value.Type(), frame.File, frame.Line, s[i].index) case s[i].value.Kind() == reflect.Struct: fmt.Fprintf(str, "... %s field %s\n", s[i].value.Type(), s[i].value.Type().Field(s[i].index).Name) case s[i].value.Kind() == reflect.Slice: fmt.Fprintf(str, "... %s index %d\n", s[i].value.Type(), s[i].index) default: panic("unsupported kind") } } str.WriteString("=== END STACK ===\n") return str.String() } // Error is returned from any function in this package to provide context // of where an error occurred. type Error struct { // Index indicates the argument on which this error occurred. Index int Op string total int entry [1]uintptr stack containerStack leafType reflect.Type err error } func (e *Error) Error() string { s := new(bytes.Buffer) fmt.Fprintf(s, "cannot %s argument ", e.Op) if e.total > 1 { fmt.Fprintf(s, "%d ", e.Index) } fmt.Fprintf(s, "whilst processing element of type %s: %v", e.leafType, e.err) if len(e.stack) != 0 { fmt.Fprintf(s, "\n\n%s", e.stack) } return s.String() } func (e *Error) Unwrap() error { return e.err } // Type returns the type of the value on which this error occurred. func (e *Error) Type() reflect.Type { return e.leafType } // Depth returns the depth of the value on which this error occurred. func (e *Error) Depth() int { return len(e.stack) } // Container returns the type of the container at the specified depth. // // If the returned type is a structure, the returned index corresponds // to the index of the field in that structure. // // If the returned type is a slice, the returned index corresponds to // the index in that slice. // // If the returned type implements the CustomMarshaller and // CustomUnmarshaller interfaces, the returned index corresponds to // the argument index in the recursive call in to one of the marshalling // or unmarshalling APIs. The returned frame indicates where this // recursive call originated from. func (e *Error) Container(depth int) (containerType reflect.Type, index int, entry runtime.Frame) { var frame runtime.Frame if reflect.PtrTo(e.stack[depth].value.Type()).Implements(customMuType) { frames := runtime.CallersFrames(e.stack[depth].entry[:]) frame, _ = frames.Next() } return e.stack[depth].value.Type(), e.stack[depth].index, frame } func newError(value reflect.Value, c *context, err error) error { if err == io.EOF { // All io.EOF is unexpected err = io.ErrUnexpectedEOF } muErr, isMuErr := err.(*Error) stack := make(containerStack, len(c.stack)) copy(stack, c.stack) var leafType reflect.Type if isMuErr { // This is an error returned from a custom type. // Preserve the original error err = muErr.err // Copy the leaf type to the new error leafType = muErr.leafType // Append the original error stack to the new error. stack = stack.push(containerNode{value: value, index: muErr.Index, entry: muErr.entry}) stack = append(stack, muErr.stack...) } else { leafType = value.Type() } return &Error{ Index: c.index, Op: c.mode, total: c.total, entry: c.caller, stack: stack, leafType: leafType, err: err} } type options struct { selector string sized bool raw bool } func parseStructFieldMuOptions(f reflect.StructField) (out options) { s := f.Tag.Get("tpm2") for _, part := range strings.Split(s, ",") { switch { case strings.HasPrefix(part, "selector:"): out.selector = part[9:] case part == "sized": out.sized = true case part == "raw": out.raw = true } } return } type context struct { caller [1]uintptr mode string index int total int stack containerStack options options } func (c *context) enterStructField(s reflect.Value, i int) (f reflect.Value, exit func()) { origOptions := c.options c.options = parseStructFieldMuOptions(s.Type().Field(i)) c.stack = c.stack.push(containerNode{value: s, index: i}) return s.Field(i), func() { c.stack = c.stack.pop() c.options = origOptions } } func (c *context) enterListElem(l reflect.Value, i int) (elem reflect.Value, exit func()) { origOptions := c.options c.options = options{} c.stack = c.stack.push(containerNode{value: l, index: i}) return l.Index(i), func() { c.stack = c.stack.pop() c.options = origOptions } } func (c *context) enterUnionElem(u reflect.Value) (elem reflect.Value, exit func(), err error) { if len(c.stack) == 0 { panic(fmt.Sprintf("union type %s is not inside a container", u.Type())) } var selectorVal reflect.Value if c.options.selector == "" { selectorVal = c.stack.top().value.Field(0) } else { selectorVal = c.stack.top().value.FieldByName(c.options.selector) if !selectorVal.IsValid() { panic(fmt.Sprintf("selector name %s for union type %s does not reference a valid field\n%s", c.options.selector, u.Type(), c.stack)) } } p := u.Addr().Interface().(Union).Select(selectorVal) switch { case p == nil: return reflect.Value{}, nil, &InvalidSelectorError{selectorVal} case p == NilUnionValue: return reflect.Value{}, nil, nil } pv := reflect.ValueOf(p) index := -1 for i := 0; i < u.NumField(); i++ { if u.Field(i).Addr().Interface() == pv.Interface() { index = i break } } if index == -1 { panic(fmt.Sprintf("Union.Select implementation for type %s returned a non-member pointer\n%s", u.Type(), c.stack)) } origOptions := c.options c.options.selector = "" c.stack = c.stack.push(containerNode{value: u, index: index}) return pv.Elem(), func() { c.stack = c.stack.pop() c.options = origOptions }, nil } func (c *context) enterSizedType(v reflect.Value) (exit func()) { switch { case v.Kind() == reflect.Ptr: case v.Kind() == reflect.Slice && v.Type().Elem().Kind() == reflect.Uint8: default: panic(fmt.Sprintf("invalid sized type: %v", v.Type())) } origOptions := c.options c.options.sized = false if v.Kind() == reflect.Slice { c.options.raw = true } return func() { c.options = origOptions } } // TPMKind indicates the TPM type class associated with a Go type type TPMKind int const ( // TPMKindUnsupported indicates that a go type has no corresponding // TPM type class. TPMKindUnsupported TPMKind = iota // TPMKindPrimitive indicates that a go type corresponds to one // of the primitive TPM types (UINT8, BYTE, INT8, BOOL, UINT16, // INT16, UINT32, INT32, UINT64, INT64, TPM_ALG_ID, any TPMA_ // prefixed type). TPMKindPrimitive // TPMKindSized indicates that a go type corresponds to a // TPM2B prefixed TPM type. TPMKindSized // TPMKindList indicates that a go type corresponds to a // TPML prefixed TPM type. TPMKindList // TPMKindStruct indicates that a go type corresponds to a // TPMS or TPMT prefixed TPM type - this package doesn't // distinguish between the two. TPMKindStruct // TPMKindUnion indicates that a go type corresponds to a // TPMU prefixed TPM type. TPMKindUnion // TPMKindCustom correponds to a go type that defines its own // marshalling behaviour. TPMKindCustom // TPMKindRaw corresponds to a go slice that is marshalled // without a size field (it is not TPMKindSized or TPMKindList). TPMKindRaw ) func tpmKind(t reflect.Type, opts *options) TPMKind { if opts.sized { return TPMKindSized } if t == emptyIfaceType { t = t.Elem() } if t.Kind() == reflect.Ptr { t = t.Elem() } if reflect.PtrTo(t).Implements(customMuType) { return TPMKindCustom } switch t.Kind() { case reflect.Bool, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: return TPMKindPrimitive case reflect.Slice: switch { case t == rawBytesType: return TPMKindRaw case opts.raw: return TPMKindRaw case t.Elem().Kind() == reflect.Uint8: return TPMKindSized default: return TPMKindList } case reflect.Struct: if reflect.PtrTo(t).Implements(unionType) { return TPMKindUnion } return TPMKindStruct default: return TPMKindUnsupported } } // DetermineTPMKind returns the TPMKind associated with the supplied go value. It will automatically dereference pointer types. // Single field structures will be unwrapped and the TPMKind associated with the structure field will be returned. func DetermineTPMKind(i interface{}) TPMKind { t := reflect.TypeOf(i) var opts options for { k := tpmKind(t, &opts) if k != TPMKindStruct { return k } if t.Kind() == reflect.Ptr { t = t.Elem() } if t.NumField() != 1 { return k } f := t.Field(0) opts = parseStructFieldMuOptions(f) t = f.Type } } type marshaller struct { *context w io.Writer nbytes int } func (m *marshaller) Write(p []byte) (n int, err error) { n, err = m.w.Write(p) m.nbytes += n return } func (m *marshaller) marshalSized(v reflect.Value) error { exit := m.enterSizedType(v) defer exit() if v.IsNil() { if err := binary.Write(m, binary.BigEndian, uint16(0)); err != nil { return newError(v, m.context, err) } return nil } tmpBuf := new(bytes.Buffer) sm := &marshaller{context: m.context, w: tmpBuf} if err := sm.marshalValue(v); err != nil { return err } if tmpBuf.Len() > math.MaxUint16 { return newError(v, m.context, errors.New("sized value size greater than 2^16-1")) } if err := binary.Write(m, binary.BigEndian, uint16(tmpBuf.Len())); err != nil { return newError(v, m.context, err) } if _, err := tmpBuf.WriteTo(m); err != nil { return newError(v, m.context, err) } return nil } func (m *marshaller) marshalRawList(v reflect.Value) error { for i := 0; i < v.Len(); i++ { elem, exit := m.enterListElem(v, i) if err := m.marshalValue(elem); err != nil { exit() return err } exit() } return nil } func (m *marshaller) marshalRaw(v reflect.Value) error { switch v.Type().Elem().Kind() { case reflect.Uint8: if _, err := m.Write(v.Bytes()); err != nil { return newError(v, m.context, err) } return nil default: return m.marshalRawList(v) } } func (m *marshaller) marshalPtr(v reflect.Value) error { p := v if v.IsNil() { p = reflect.New(v.Type().Elem()) } return m.marshalValue(p.Elem()) } func (m *marshaller) marshalPrimitive(v reflect.Value) error { if err := binary.Write(m, binary.BigEndian, v.Interface()); err != nil { return newError(v, m.context, err) } return nil } func (m *marshaller) marshalList(v reflect.Value) error { // int is either 32-bits or 64-bits. We can't compare slice.Len() to math.MaxUint32 when int is 32-bits and it isn't // necessary anyway. For the case where int is 64-bits, truncate to uint32 then zero extend it again to int to make // sure the original number was preserved. if int(uint32(v.Len())) != v.Len() { return newError(v, m.context, errors.New("slice length greater than 2^32-1")) } // Marshal length field if err := binary.Write(m, binary.BigEndian, uint32(v.Len())); err != nil { return newError(v, m.context, err) } return m.marshalRawList(v) } func (m *marshaller) marshalStruct(v reflect.Value) error { for i := 0; i < v.NumField(); i++ { f, exit := m.enterStructField(v, i) if err := m.marshalValue(f); err != nil { exit() return err } exit() } return nil } func (m *marshaller) marshalUnion(v reflect.Value) error { // Ignore during marshalling - let the TPM unmarshalling catch it elem, exit, _ := m.enterUnionElem(v) if !elem.IsValid() { return nil } defer exit() return m.marshalValue(elem) } func (m *marshaller) marshalCustom(v reflect.Value) error { if err := v.Interface().(CustomMarshaller).Marshal(m); err != nil { return newError(v, m.context, err) } return nil } func (m *marshaller) marshalValue(v reflect.Value) error { kind := tpmKind(v.Type(), &m.options) if v.Type() == emptyIfaceType { v = v.Elem() } if v.Kind() == reflect.Ptr && kind != TPMKindSized { return m.marshalPtr(v) } switch kind { case TPMKindPrimitive: return m.marshalPrimitive(v) case TPMKindSized: return m.marshalSized(v) case TPMKindList: return m.marshalList(v) case TPMKindStruct: return m.marshalStruct(v) case TPMKindUnion: return m.marshalUnion(v) case TPMKindCustom: return m.marshalCustom(v) case TPMKindRaw: return m.marshalRaw(v) } panic(fmt.Sprintf("cannot marshal unsupported type %s", v.Type())) } func (m *marshaller) marshal(vals ...interface{}) (int, error) { m.total = len(vals) m.nbytes = 0 for i, v := range vals { m.index = i if err := m.marshalValue(reflect.ValueOf(v)); err != nil { return m.nbytes, err } } return m.nbytes, nil } // Reader is an interface that groups the io.Reader interface with an additional method to // obtain the remaining number of bytes that can be read for implementations that support this. type Reader interface { io.Reader Len() int } type unmarshaller struct { *context r io.Reader sz int64 nbytes int } func (u *unmarshaller) Read(p []byte) (n int, err error) { n, err = u.r.Read(p) u.nbytes += n return } func (u *unmarshaller) Len() int { return int(u.sz - int64(u.nbytes)) } func startingSizeOfReader(r io.Reader) (int64, error) { switch rImpl := r.(type) { case *os.File: fi, err := rImpl.Stat() if err != nil { return 0, err } if fi.Mode().IsRegular() { start, err := rImpl.Seek(0, io.SeekCurrent) if err != nil { return 0, err } return fi.Size() - start, nil } case *bytes.Reader: return int64(rImpl.Len()), nil case *bytes.Buffer: return int64(rImpl.Len()), nil case *io.SectionReader: start, _ := rImpl.Seek(0, io.SeekCurrent) return rImpl.Size() - start, nil case *io.LimitedReader: sz, err := startingSizeOfReader(rImpl.R) if err != nil { return 0, err } if rImpl.N < sz { sz = rImpl.N } return sz, nil } return 1<<63 - 1, nil } func makeUnmarshaller(ctx *context, r io.Reader) (*unmarshaller, error) { sz, err := startingSizeOfReader(r) if err != nil { return nil, err } return &unmarshaller{context: ctx, r: r, sz: sz}, nil } func (u *unmarshaller) unmarshalSized(v reflect.Value) error { exit := u.enterSizedType(v) defer exit() var size uint16 if err := binary.Read(u, binary.BigEndian, &size); err != nil { return newError(v, u.context, err) } switch { case size == 0 && !v.IsNil() && v.Kind() == reflect.Ptr: return newError(v, u.context, errors.New("sized value is zero sized, but destination value has been pre-allocated")) case size == 0: return nil case int(size) > u.Len(): return newError(v, u.context, errors.New("sized value has a size larger than the remaining bytes")) case v.Kind() == reflect.Slice: v.Set(reflect.MakeSlice(v.Type(), int(size), int(size))) } su, err := makeUnmarshaller(u.context, io.LimitReader(u, int64(size))) if err != nil { return newError(v, u.context, xerrors.Errorf("cannot create new reader for sized payload: %w", err)) } return su.unmarshalValue(v) } func (u *unmarshaller) unmarshalRawList(v reflect.Value, n int) (reflect.Value, error) { for i := 0; i < n; i++ { v = reflect.Append(v, reflect.Zero(v.Type().Elem())) elem, exit := u.enterListElem(v, i) if err := u.unmarshalValue(elem); err != nil { exit() return reflect.Value{}, err } exit() } return v, nil } func (u *unmarshaller) unmarshalRaw(v reflect.Value) error { switch v.Type().Elem().Kind() { case reflect.Uint8: if _, err := io.ReadFull(u, v.Bytes()); err != nil { return newError(v, u.context, err) } return nil default: _, err := u.unmarshalRawList(v.Slice(0, 0), v.Len()) return err } } func (u *unmarshaller) unmarshalPtr(v reflect.Value) error { if v.IsNil() { v.Set(reflect.New(v.Type().Elem())) } return u.unmarshalValue(v.Elem()) } func (u *unmarshaller) unmarshalPrimitive(v reflect.Value) error { if err := binary.Read(u, binary.BigEndian, v.Addr().Interface()); err != nil { return newError(v, u.context, err) } return nil } func (u *unmarshaller) unmarshalList(v reflect.Value) error { // Unmarshal the length var length uint32 if err := binary.Read(u, binary.BigEndian, &length); err != nil { return newError(v, u.context, err) } if v.IsNil() || v.Cap() < int(length) { v.Set(reflect.MakeSlice(v.Type(), 0, int(length))) } s, err := u.unmarshalRawList(v.Slice(0, 0), int(length)) if err != nil { return err } v.Set(s) return nil } func (u *unmarshaller) unmarshalStruct(v reflect.Value) error { for i := 0; i < v.NumField(); i++ { elem, exit := u.enterStructField(v, i) if err := u.unmarshalValue(elem); err != nil { exit() return err } exit() } return nil } func (u *unmarshaller) unmarshalUnion(v reflect.Value) error { elem, exit, err := u.enterUnionElem(v) if err != nil { return newError(v, u.context, err) } if !elem.IsValid() { return nil } defer exit() return u.unmarshalValue(elem) } func (u *unmarshaller) unmarshalCustom(v reflect.Value) error { if err := v.Addr().Interface().(CustomUnmarshaller).Unmarshal(u); err != nil { return newError(v, u.context, err) } return nil } func (u *unmarshaller) unmarshalValue(v reflect.Value) error { kind := tpmKind(v.Type(), &u.options) if v.Type() == emptyIfaceType { v = v.Elem() } if v.Kind() == reflect.Ptr && kind != TPMKindSized { return u.unmarshalPtr(v) } switch kind { case TPMKindPrimitive: return u.unmarshalPrimitive(v) case TPMKindSized: return u.unmarshalSized(v) case TPMKindList: return u.unmarshalList(v) case TPMKindStruct: return u.unmarshalStruct(v) case TPMKindUnion: return u.unmarshalUnion(v) case TPMKindCustom: return u.unmarshalCustom(v) case TPMKindRaw: return u.unmarshalRaw(v) } panic(fmt.Sprintf("cannot unmarshal unsupported type %s", v.Type())) } func (u *unmarshaller) unmarshal(vals ...interface{}) (int, error) { u.total = len(vals) u.nbytes = 0 for i, v := range vals { u.index = i if err := u.unmarshalValue(reflect.ValueOf(v).Elem()); err != nil { return u.nbytes, err } } return u.nbytes, nil } func marshalToWriter(skip int, w io.Writer, vals ...interface{}) (int, error) { var caller [1]uintptr runtime.Callers(skip+1, caller[:]) m := &marshaller{context: &context{caller: caller, mode: "marshal"}, w: w} return m.marshal(vals...) } // MarshalToWriter marshals vals to w in the TPM wire format, according to the rules specified in the package description. // // Pointers are automatically dereferenced. Nil pointers are marshalled to the zero value for the pointed to type, unless // the pointer is to a sized structure (a struct field with the 'tpm2:"sized"` tag pointing to another struct), in which case // a value of zero size is marshalled. // // The number of bytes written to w are returned. If this function does not complete successfully, it will return an error and // the number of bytes written. // // This function only returns an error if a sized value (sized buffer, sized structure or list) is too large for its corresponding // size field, or if the supplied io.Writer returns an error. func MarshalToWriter(w io.Writer, vals ...interface{}) (int, error) { return marshalToWriter(2, w, vals...) } // MustMarshalToWriter is the same as MarshalToWriter, except that it panics if it encounters an error. func MustMarshalToWriter(w io.Writer, vals ...interface{}) int { n, err := marshalToWriter(2, w, vals...) if err != nil { panic(err) } return n } func marshalToBytes(skip int, vals ...interface{}) ([]byte, error) { buf := new(bytes.Buffer) if _, err := marshalToWriter(skip+1, buf, vals...); err != nil { return nil, err } return buf.Bytes(), nil } // MarshalToBytes marshals vals to TPM wire format, according to the rules specified in the package description. // // Pointers are automatically dereferenced. Nil pointers are marshalled to the zero value for the pointed to type, unless // the pointer is to a sized structure (a struct field with the 'tpm2:"sized"` tag pointing to another struct), in which case // a value of zero size is marshalled. // // The number of bytes written to w are returned. If this function does not complete successfully, it will return an error and // the number of bytes written. // // This function only returns an error if a sized value (sized buffer, sized structure or list) is too large for its corresponding // size field. func MarshalToBytes(vals ...interface{}) ([]byte, error) { return marshalToBytes(2, vals...) } // MustMarshalToBytes is the same as MarshalToBytes, except that it panics if it encounters an error. func MustMarshalToBytes(vals ...interface{}) []byte { b, err := marshalToBytes(2, vals...) if err != nil { panic(err) } return b } func unmarshalFromReader(skip int, r io.Reader, vals ...interface{}) (int, error) { var caller [1]uintptr runtime.Callers(skip+1, caller[:]) for _, val := range vals { v := reflect.ValueOf(val) if v.Kind() != reflect.Ptr { panic(fmt.Sprintf("cannot unmarshal to non-pointer type %s", v.Type())) } if v.IsNil() { panic(fmt.Sprintf("cannot unmarshal to nil pointer of type %s", v.Type())) } } u, err := makeUnmarshaller(&context{caller: caller, mode: "unmarshal"}, r) if err != nil { return 0, err } return u.unmarshal(vals...) } // UnmarshalFromReader unmarshals data in the TPM wire format from r to vals, according to the rules specified in the package // description. The values supplied to this function must be pointers to the destination values. // // Pointers are automatically dererefenced. If a pointer is nil, then memory is allocated for the values and the pointer // is initialized accordingly, unless the pointer is to a sized structure (a struct field with the 'tpm2:"sized"' tag pointing // to another struct) and the values being unmarshalled has a zero size, in which case the pointer is not initialized. If // a pointer is already initialized by the caller, then this function will unmarshal to the already allocated memory. // // Slices are allocated automatically, unless the caller has already allocated a slice that has a large enough capacity // to hold the unmarshalled values, in which case the already allocated slice will be used and its length set accordingly. // // This can unmarshal raw slices (those without a corresponding size or length fields, represented by the RawBytes type or // a slice value referenced from a struct field with the 'tpm2:"raw"' tag), but the caller must pre-allocate a slice of the // correct size first. This function cannot allocate a slice because it doesn't have a way to determine the size to allocate. // // The number of bytes read from r are returned. If this function does not complete successfully, it will return an error and // the number of bytes read. In this case, partial results may have been unmarshalled to the supplied destination values. func UnmarshalFromReader(r io.Reader, vals ...interface{}) (int, error) { return unmarshalFromReader(2, r, vals...) } // UnmarshalFromReader unmarshals data in the TPM wire format from b to vals, according to the rules specified in the package // description. The values supplied to this function must be pointers to the destination values. // // Pointers are automatically dererefenced. If a pointer is nil, then memory is allocated for the value and the pointer // is initialized accordingly, unless the pointer is to a sized structure (a struct field with the 'tpm2:"sized"' tag pointing // to another struct) and the value being unmarshalled has a zero size, in which case the pointer is not initialized. If // a pointer is already initialized by the caller, then this function will unmarshal to the already allocated memory. // // Slices are allocated automatically, unless the caller has already allocated a slice that has a large enough capacity // to hold the unmarshalled values, in which case the already allocated slice will be used and its length set accordingly. // // This can unmarshal raw slices (those without a corresponding size or length fields, represented by the RawBytes type or // a slice value referenced from a struct field with the 'tpm2:"raw"' tag), but the caller must pre-allocate a slice of the // correct size first. This function cannot allocate a slice because it doesn't have a way to determine the size to allocate. // // The number of bytes consumed from b are returned. If this function does not complete successfully, it will return an error and // the number of bytes consumed. In this case, partial results may have been unmarshalled to the supplied destination values. func UnmarshalFromBytes(b []byte, vals ...interface{}) (int, error) { buf := bytes.NewReader(b) return unmarshalFromReader(2, buf, vals...) } func copyValue(skip int, dst, src interface{}) error { buf := new(bytes.Buffer) if _, err := marshalToWriter(skip+1, buf, src); err != nil { return err } _, err := unmarshalFromReader(skip+1, buf, dst) return err } // CopyValue copies the value of src to dst. The destination must be a pointer to the actual // destination value. This works by serializing the source value in the TPM wire format // and the deserializing it again into the destination. // // This will return an error for any reason that would cause MarshalToBytes or // UnmarshalFromBytes to return an error. func CopyValue(dst, src interface{}) error { return copyValue(2, dst, src) } // MustCopyValue is the same as CopyValue except that it panics if it encounters an error. func MustCopyValue(dst, src interface{}) { if err := copyValue(2, dst, src); err != nil { panic(err) } } ./github.com/canonical/go-tpm2/paramcrypt.go0000664000000000000000000000703500000000000017335 0ustar0000000000000000// Copyright 2019 Canonical Ltd. // Licensed under the LGPLv3 with static-linking exception. // See LICENCE file for details. package tpm2 import ( "crypto/aes" "encoding/binary" "errors" "fmt" "github.com/canonical/go-tpm2/internal" "github.com/canonical/go-tpm2/mu" ) func isParamEncryptable(param interface{}) bool { return mu.DetermineTPMKind(param) == mu.TPMKindSized } func (s *sessionParam) computeSessionValue() []byte { var key []byte key = append(key, s.session.Data().SessionKey...) if s.IsAuth() { key = append(key, s.associatedContext.(resourceContextPrivate).GetAuthValue()...) } return key } func (p *sessionParams) findDecryptSession() (*sessionParam, int) { return p.findSessionWithAttr(AttrCommandEncrypt) } func (p *sessionParams) findEncryptSession() (*sessionParam, int) { return p.findSessionWithAttr(AttrResponseEncrypt) } func (p *sessionParams) hasDecryptSession() bool { s, _ := p.findDecryptSession() return s != nil } func (p *sessionParams) computeEncryptNonce() { s, i := p.findEncryptSession() if s == nil || i == 0 || !p.sessions[0].IsAuth() { return } ds, di := p.findDecryptSession() if ds != nil && di == i { return } p.sessions[0].encryptNonce = s.session.NonceTPM() } func (p *sessionParams) encryptCommandParameter(cpBytes []byte) error { s, i := p.findDecryptSession() if s == nil { return nil } sessionData := s.session.Data() hashAlg := sessionData.HashAlg sessionValue := s.computeSessionValue() size := binary.BigEndian.Uint16(cpBytes) data := cpBytes[2 : size+2] symmetric := sessionData.Symmetric switch symmetric.Algorithm { case SymAlgorithmAES: if symmetric.Mode.Sym != SymModeCFB { return errors.New("unsupported cipher mode") } k := internal.KDFa(hashAlg.GetHash(), sessionValue, []byte(CFBKey), sessionData.NonceCaller, sessionData.NonceTPM, int(symmetric.KeyBits.Sym)+(aes.BlockSize*8)) offset := (symmetric.KeyBits.Sym + 7) / 8 symKey := k[0:offset] iv := k[offset:] if err := CryptSymmetricEncrypt(symmetric.Algorithm, symKey, iv, data); err != nil { return fmt.Errorf("AES encryption failed: %v", err) } case SymAlgorithmXOR: internal.XORObfuscation(hashAlg.GetHash(), sessionValue, sessionData.NonceCaller, sessionData.NonceTPM, data) default: return fmt.Errorf("unknown symmetric algorithm: %v", symmetric.Algorithm) } if i > 0 && p.sessions[0].IsAuth() { p.sessions[0].decryptNonce = sessionData.NonceTPM } return nil } func (p *sessionParams) decryptResponseParameter(rpBytes []byte) error { s, _ := p.findEncryptSession() if s == nil { return nil } sessionData := s.session.Data() hashAlg := sessionData.HashAlg sessionValue := s.computeSessionValue() size := binary.BigEndian.Uint16(rpBytes) data := rpBytes[2 : size+2] symmetric := sessionData.Symmetric switch symmetric.Algorithm { case SymAlgorithmAES: if symmetric.Mode.Sym != SymModeCFB { return errors.New("unsupported cipher mode") } k := internal.KDFa(hashAlg.GetHash(), sessionValue, []byte(CFBKey), sessionData.NonceTPM, sessionData.NonceCaller, int(symmetric.KeyBits.Sym)+(aes.BlockSize*8)) offset := (symmetric.KeyBits.Sym + 7) / 8 symKey := k[0:offset] iv := k[offset:] if err := CryptSymmetricDecrypt(symmetric.Algorithm, symKey, iv, data); err != nil { return fmt.Errorf("AES encryption failed: %v", err) } case SymAlgorithmXOR: internal.XORObfuscation(hashAlg.GetHash(), sessionValue, sessionData.NonceTPM, sessionData.NonceCaller, data) default: return fmt.Errorf("unknown symmetric algorithm: %v", symmetric.Algorithm) } return nil } ./github.com/canonical/go-tpm2/resources.go0000664000000000000000000005142500000000000017167 0ustar0000000000000000// Copyright 2019 Canonical Ltd. // Licensed under the LGPLv3 with static-linking exception. // See LICENCE file for details. package tpm2 import ( "bytes" "crypto" _ "crypto/sha256" "encoding/binary" "errors" "fmt" "io" "reflect" "github.com/canonical/go-tpm2/mu" "golang.org/x/xerrors" ) // HandleContext corresponds to an entity that resides on the TPM. Implementations of HandleContext maintain some host-side // state in order to be able to participate in HMAC sessions. They are invalidated when used in a command that results in the // entity being flushed or evicted from the TPM. Once invalidated, they can no longer be used. type HandleContext interface { // Handle returns the handle of the corresponding entity on the TPM. If the HandleContext has been invalidated then this will // return HandleUnassigned. Handle() Handle Name() Name // The name of the entity SerializeToBytes() []byte // Return a byte slice containing the serialized form of this HandleContext SerializeToWriter(io.Writer) error // Write the serialized form of this HandleContext to the supplied io.Writer } type handleContextPrivate interface { invalidate() } // SessionContext is a HandleContext that corresponds to a session on the TPM. type SessionContext interface { HandleContext NonceTPM() Nonce // The most recent TPM nonce value IsAudit() bool // Whether the session has been used for audit IsExclusive() bool // Whether the most recent response from the TPM indicated that the session is exclusive for audit purposes SetAttrs(attrs SessionAttributes) // Set the attributes that will be used for this SessionContext WithAttrs(attrs SessionAttributes) SessionContext // Return a duplicate of this SessionContext with the specified attributes // IncludeAttrs returns a duplicate of this SessionContext and its attributes with the specified attributes included. IncludeAttrs(attrs SessionAttributes) SessionContext // ExcludeAttrs returns a duplicate of this SessionContext and its attributes with the specified attributes excluded. ExcludeAttrs(attrs SessionAttributes) SessionContext } // ResourceContext is a HandleContext that corresponds to a non-session entity on the TPM. type ResourceContext interface { HandleContext // SetAuthValue sets the authorization value that will be used in authorization roles where knowledge of the authorization // value is required. Functions that create resources on the TPM and return a ResourceContext will set this automatically, // else it will need to be set manually. SetAuthValue([]byte) } type resourceContextPrivate interface { GetAuthValue() []byte } type handleContextType uint8 const ( handleContextTypePartial handleContextType = iota handleContextTypePermanent handleContextTypeObject handleContextTypeNvIndex handleContextTypeSession ) type sessionContextData struct { IsAudit bool IsExclusive bool HashAlg HashAlgorithmId SessionType SessionType PolicyHMACType policyHMACType IsBound bool BoundEntity Name SessionKey []byte NonceCaller Nonce NonceTPM Nonce Symmetric *SymDef } type handleContextU struct { Object *Public NV *NVPublic Session *sessionContextData } func (d *handleContextU) Select(selector reflect.Value) interface{} { switch selector.Interface().(handleContextType) { case handleContextTypePartial, handleContextTypePermanent: return mu.NilUnionValue case handleContextTypeObject: return &d.Object case handleContextTypeNvIndex: return &d.NV case handleContextTypeSession: return &d.Session default: return nil } } type handleContext struct { Type handleContextType H Handle N Name Data *handleContextU } func (h *handleContext) Handle() Handle { return h.H } func (h *handleContext) Name() Name { return h.N } func (h *handleContext) SerializeToBytes() []byte { data := mu.MustMarshalToBytes(h) hash := crypto.SHA256.New() hash.Write(data) return mu.MustMarshalToBytes(HashAlgorithmSHA256, hash.Sum(nil), data) } func (h *handleContext) SerializeToWriter(w io.Writer) error { data := mu.MustMarshalToBytes(h) hash := crypto.SHA256.New() hash.Write(data) _, err := mu.MarshalToWriter(w, HashAlgorithmSHA256, hash.Sum(nil), data) return err } func (h *handleContext) invalidate() { h.H = HandleUnassigned h.N = make(Name, binary.Size(Handle(0))) binary.BigEndian.PutUint32(h.N, uint32(h.H)) } func (h *handleContext) checkConsistency() error { switch h.Type { case handleContextTypePermanent: switch h.Handle().Type() { case HandleTypePCR, HandleTypePermanent: default: return errors.New("inconsistent handle type for permanent context") } if h.Name().Type() != NameTypeHandle || h.Name().Handle() != h.Handle() { return errors.New("name inconsistent with handle for permanent context") } case handleContextTypeObject: switch h.Handle().Type() { case HandleTypeTransient, HandleTypePersistent: default: return errors.New("inconsistent handle type for object context") } if h.Data.Object == nil { return errors.New("no public area for object context") } if !h.Data.Object.compareName(h.Name()) { return errors.New("name inconsistent with public area for object context") } case handleContextTypeNvIndex: if h.Handle().Type() != HandleTypeNVIndex { return errors.New("inconsistent handle type for NV context") } if h.Data.NV == nil { return errors.New("no public area for NV context") } if !h.Data.NV.compareName(h.Name()) { return errors.New("name inconsistent with public area for NV context") } case handleContextTypeSession: switch h.Handle().Type() { case HandleTypeHMACSession, HandleTypePolicySession: default: return errors.New("inconsistent handle type for session context") } if h.Name().Type() != NameTypeHandle || h.Name().Handle() != h.Handle() { return errors.New("name inconsistent with handle for session context") } scData := h.Data.Session if scData != nil { if !scData.IsAudit && scData.IsExclusive { return errors.New("inconsistent audit attributes for session context") } if !scData.HashAlg.Available() { return errors.New("invalid digest algorithm for session context") } switch scData.SessionType { case SessionTypeHMAC, SessionTypePolicy, SessionTypeTrial: default: return errors.New("invalid session type for session context") } if scData.PolicyHMACType > policyHMACTypeMax { return errors.New("invalid policy session HMAC type for session context") } if (scData.IsBound && len(scData.BoundEntity) == 0) || (!scData.IsBound && len(scData.BoundEntity) > 0) { return errors.New("invalid bind properties for session context") } digestSize := scData.HashAlg.Size() if len(scData.SessionKey) != digestSize && len(scData.SessionKey) != 0 { return errors.New("unexpected session key size for session context") } if len(scData.NonceCaller) != digestSize || len(scData.NonceTPM) != digestSize { return errors.New("unexpected nonce size for session context") } switch scData.Symmetric.Algorithm { case SymAlgorithmAES, SymAlgorithmXOR, SymAlgorithmNull: default: return errors.New("invalid symmetric algorithm for session context") } if scData.Symmetric.Algorithm == SymAlgorithmAES && scData.Symmetric.Mode.Sym != SymModeCFB { return errors.New("invalid symmetric mode for session context") } } default: return errors.New("unrecognized context type") } return nil } func makePartialHandleContext(handle Handle) *handleContext { name := make(Name, binary.Size(Handle(0))) binary.BigEndian.PutUint32(name, uint32(handle)) return &handleContext{ Type: handleContextTypePartial, H: handle, N: name} } type resourceContext struct { handleContext authValue []byte } func (r *resourceContext) SetAuthValue(authValue []byte) { r.authValue = authValue } func (r *resourceContext) GetAuthValue() []byte { return bytes.TrimRight(r.authValue, "\x00") } type permanentContext struct { resourceContext } func (r *permanentContext) invalidate() {} func makePermanentContext(handle Handle) *permanentContext { name := make(Name, binary.Size(Handle(0))) binary.BigEndian.PutUint32(name, uint32(handle)) return &permanentContext{ resourceContext: resourceContext{ handleContext: handleContext{ Type: handleContextTypePermanent, H: handle, N: name}}} } type objectContext struct { resourceContext } func (r *objectContext) GetPublic() *Public { return r.Data.Object } func makeObjectContext(handle Handle, name Name, public *Public) *objectContext { return &objectContext{ resourceContext: resourceContext{ handleContext: handleContext{ Type: handleContextTypeObject, H: handle, N: name, Data: &handleContextU{Object: public}}}} } func (t *TPMContext) makeObjectContextFromTPM(context HandleContext, sessions ...SessionContext) (ResourceContext, error) { pub, name, _, err := t.ReadPublic(context, sessions...) if err != nil { return nil, err } if n, err := pub.Name(); err != nil { return nil, &InvalidResponseError{CommandReadPublic, fmt.Sprintf("cannot compute name of returned public area: %v", err)} } else if !bytes.Equal(n, name) { return nil, &InvalidResponseError{CommandReadPublic, "name and public area don't match"} } return makeObjectContext(context.Handle(), name, pub), nil } type nvIndexContext struct { resourceContext } func (r *nvIndexContext) GetPublic() *NVPublic { return r.Data.NV } func (r *nvIndexContext) SetAttr(a NVAttributes) { r.Data.NV.Attrs |= a name, _ := r.Data.NV.Name() r.N = name } func (r *nvIndexContext) ClearAttr(a NVAttributes) { r.Data.NV.Attrs &= ^a name, _ := r.Data.NV.Name() r.N = name } func (r *nvIndexContext) Attrs() NVAttributes { return r.Data.NV.Attrs } func makeNVIndexContext(name Name, public *NVPublic) *nvIndexContext { return &nvIndexContext{ resourceContext: resourceContext{ handleContext: handleContext{ Type: handleContextTypeNvIndex, H: public.Index, N: name, Data: &handleContextU{NV: public}}}} } func (t *TPMContext) makeNVIndexContextFromTPM(context HandleContext, sessions ...SessionContext) (ResourceContext, error) { pub, name, err := t.NVReadPublic(context, sessions...) if err != nil { return nil, err } if n, err := pub.Name(); err != nil { return nil, &InvalidResponseError{CommandNVReadPublic, fmt.Sprintf("cannot compute name of returned public area: %v", err)} } else if !bytes.Equal(n, name) { return nil, &InvalidResponseError{CommandNVReadPublic, "name and public area don't match"} } if pub.Index != context.Handle() { return nil, &InvalidResponseError{CommandNVReadPublic, "unexpected index in public area"} } return makeNVIndexContext(name, pub), nil } type sessionContext struct { *handleContext attrs SessionAttributes } func (r *sessionContext) NonceTPM() Nonce { d := r.Data() if d == nil { return nil } return d.NonceTPM } func (r *sessionContext) IsAudit() bool { d := r.Data() if d == nil { return false } return d.IsAudit } func (r *sessionContext) IsExclusive() bool { d := r.Data() if d == nil { return false } return d.IsExclusive } func (r *sessionContext) SetAttrs(attrs SessionAttributes) { r.attrs = attrs } func (r *sessionContext) WithAttrs(attrs SessionAttributes) SessionContext { return &sessionContext{handleContext: r.handleContext, attrs: attrs} } func (r *sessionContext) IncludeAttrs(attrs SessionAttributes) SessionContext { return &sessionContext{handleContext: r.handleContext, attrs: r.attrs | attrs} } func (r *sessionContext) ExcludeAttrs(attrs SessionAttributes) SessionContext { return &sessionContext{handleContext: r.handleContext, attrs: r.attrs &^ attrs} } func (r *sessionContext) Data() *sessionContextData { return r.handleContext.Data.Session } func makeSessionContext(handle Handle, data *sessionContextData) *sessionContext { name := make(Name, binary.Size(Handle(0))) binary.BigEndian.PutUint32(name, uint32(handle)) return &sessionContext{ handleContext: &handleContext{ Type: handleContextTypeSession, H: handle, N: name, Data: &handleContextU{Session: data}}} } func (t *TPMContext) makeResourceContextFromTPM(handle HandleContext, sessions ...SessionContext) (rc ResourceContext, err error) { switch handle.Handle().Type() { case HandleTypeNVIndex: rc, err = t.makeNVIndexContextFromTPM(handle, sessions...) case HandleTypeTransient, HandleTypePersistent: rc, err = t.makeObjectContextFromTPM(handle, sessions...) default: panic("invalid handle type") } switch { case IsTPMWarning(err, WarningReferenceH0, AnyCommandCode): return nil, ResourceUnavailableError{handle.Handle()} case IsTPMHandleError(err, ErrorHandle, AnyCommandCode, AnyHandleIndex): return nil, ResourceUnavailableError{handle.Handle()} case err != nil: return nil, err } return rc, nil } // CreateResourceContextFromTPM creates and returns a new ResourceContext for the // specified handle. It will execute a command to read the public area from the TPM // in order to initialize state that is maintained on the host side. A // ResourceUnavailableError error will be returned if the specified handle references. // // The public area and name returned from the TPM are checked for consistency. // // If any sessions are supplied, the public area is read from the TPM twice. The // second time uses the supplied sessions. // // This function will panic if handle doesn't correspond to a NV index, transient object // or persistent object. // // If subsequent use of the returned ResourceContext requires knowledge of the authorization // value of the corresponding TPM resource, this should be provided by calling // ResourceContext.SetAuthValue. func (t *TPMContext) CreateResourceContextFromTPM(handle Handle, sessions ...SessionContext) (ResourceContext, error) { rc, err := t.makeResourceContextFromTPM(makePartialHandleContext(handle)) if err != nil { return nil, err } if len(sessions) == 0 { return rc, nil } return t.makeResourceContextFromTPM(rc, sessions...) } // CreatePartialHandleContext creates a new HandleContext for the specified handle. The // returned HandleContext is partial and cannot be used in any command other than // TPMContext.FlushContext, TPMContext.ReadPublic or TPMContext.NVReadPublic. // // This function will panic if handle doesn't correspond to a session, transient or // persistent object, or NV index. func CreatePartialHandleContext(handle Handle) HandleContext { switch handle.Type() { case HandleTypeNVIndex, HandleTypeHMACSession, HandleTypePolicySession, HandleTypeTransient, HandleTypePersistent: return makePartialHandleContext(handle) default: panic("invalid handle type") } } // GetPermanentContext returns a ResourceContext for the specified permanent handle or PCR handle. // // This function will panic if handle does not correspond to a permanent or PCR handle. // // If subsequent use of the returned ResourceContext requires knowledge of the authorization value of the corresponding TPM resource, // this should be provided by calling ResourceContext.SetAuthValue. func (t *TPMContext) GetPermanentContext(handle Handle) ResourceContext { switch handle.Type() { case HandleTypePermanent, HandleTypePCR: if rc, exists := t.permanentResources[handle]; exists { return rc } rc := makePermanentContext(handle) t.permanentResources[handle] = rc return rc default: panic("invalid handle type") } } // OwnerHandleContext returns the ResouceContext corresponding to the owner hiearchy. func (t *TPMContext) OwnerHandleContext() ResourceContext { return t.GetPermanentContext(HandleOwner) } // NulHandleContext returns the ResourceContext corresponding to the null hiearchy. func (t *TPMContext) NullHandleContext() ResourceContext { return t.GetPermanentContext(HandleNull) } // LockoutHandleContext returns the ResourceContext corresponding to the lockout hiearchy. func (t *TPMContext) LockoutHandleContext() ResourceContext { return t.GetPermanentContext(HandleLockout) } // EndorsementHandleContext returns the ResourceContext corresponding to the endorsement hiearchy. func (t *TPMContext) EndorsementHandleContext() ResourceContext { return t.GetPermanentContext(HandleEndorsement) } // PlatformHandleContext returns the ResourceContext corresponding to the platform hiearchy. func (t *TPMContext) PlatformHandleContext() ResourceContext { return t.GetPermanentContext(HandlePlatform) } // PlatformNVHandleContext returns the ResourceContext corresponding to the platform hiearchy. func (t *TPMContext) PlatformNVHandleContext() ResourceContext { return t.GetPermanentContext(HandlePlatformNV) } // PCRHandleContext returns the ResourceContext corresponding to the PCR at the specified index. It will panic if pcr is not a valid // PCR index. func (t *TPMContext) PCRHandleContext(pcr int) ResourceContext { h := Handle(pcr) if h.Type() != HandleTypePCR { panic("invalid PCR index") } return t.GetPermanentContext(h) } // CreateHandleContextFromReader returns a new HandleContext created from the serialized data read from the supplied io.Reader. This // should contain data that was previously created by HandleContext.SerializeToBytes or HandleContext.SerializeToWriter. // // If the supplied data corresponds to a session then a SessionContext will be returned, else a ResourceContext will be returned. // // If a ResourceContext is returned and subsequent use of it requires knowledge of the authorization value of the corresponding TPM // resource, this should be provided by calling ResourceContext.SetAuthValue. func CreateHandleContextFromReader(r io.Reader) (HandleContext, error) { var integrityAlg HashAlgorithmId var integrity []byte var b []byte if _, err := mu.UnmarshalFromReader(r, &integrityAlg, &integrity, &b); err != nil { return nil, xerrors.Errorf("cannot unpack context blob and checksum: %w", err) } if !integrityAlg.Available() { return nil, errors.New("invalid checksum algorithm") } h := integrityAlg.NewHash() h.Write(b) if !bytes.Equal(h.Sum(nil), integrity) { return nil, errors.New("invalid checksum") } var data *handleContext n, err := mu.UnmarshalFromBytes(b, &data) if err != nil { return nil, xerrors.Errorf("cannot unmarshal context data: %w", err) } if n < len(b) { return nil, errors.New("context blob contains trailing bytes") } if data.Type == handleContextTypePermanent { return nil, errors.New("cannot create a permanent context from serialized data") } if err := data.checkConsistency(); err != nil { return nil, err } var hc HandleContext switch data.Type { case handleContextTypeObject: hc = &objectContext{resourceContext: resourceContext{handleContext: *data}} case handleContextTypeNvIndex: hc = &nvIndexContext{resourceContext: resourceContext{handleContext: *data}} case handleContextTypeSession: hc = &sessionContext{handleContext: data} default: panic("not reached") } return hc, nil } // CreateHandleContextFromBytes returns a new HandleContext created from the serialized data read from the supplied byte slice. This // should contain data that was previously created by HandleContext.SerializeToBytes or HandleContext.SerializeToWriter. // // If the supplied data corresponds to a session then a SessionContext will be returned, else a ResourceContext will be returned. // // If a ResourceContext is returned and subsequent use of it requires knowledge of the authorization value of the corresponding TPM // resource, this should be provided by calling ResourceContext.SetAuthValue. func CreateHandleContextFromBytes(b []byte) (HandleContext, int, error) { buf := bytes.NewReader(b) rc, err := CreateHandleContextFromReader(buf) if err != nil { return nil, 0, err } return rc, len(b) - buf.Len(), nil } // CreateNVIndexResourceContextFromPublic returns a new ResourceContext created from the provided public area. If subsequent use of // the returned ResourceContext requires knowledge of the authorization value of the corresponding TPM resource, this should be // provided by calling ResourceContext.SetAuthValue. func CreateNVIndexResourceContextFromPublic(pub *NVPublic) (ResourceContext, error) { name, err := pub.Name() if err != nil { return nil, fmt.Errorf("cannot compute name from public area: %v", err) } rc := makeNVIndexContext(name, pub) if err := rc.checkConsistency(); err != nil { return nil, err } return rc, nil } // CreateObjectResourceContextFromPublic returns a new ResourceContext created from the provided public area. If subsequent use of // the returned ResourceContext requires knowledge of the authorization value of the corresponding TPM resource, this should be // provided by calling ResourceContext.SetAuthValue. func CreateObjectResourceContextFromPublic(handle Handle, pub *Public) (ResourceContext, error) { name, err := pub.Name() if err != nil { return nil, fmt.Errorf("cannot compute name from public area: %v", err) } rc := makeObjectContext(handle, name, pub) if err := rc.checkConsistency(); err != nil { return nil, err } return rc, nil } ./github.com/canonical/go-tpm2/run-tests0000664000000000000000000000007400000000000016507 0ustar0000000000000000#!/bin/sh -e go test -v -race -p 1 ./... -check.v -args $@ ./github.com/canonical/go-tpm2/strings.go0000664000000000000000000006242100000000000016644 0ustar0000000000000000// Copyright 2019 Canonical Ltd. // Licensed under the LGPLv3 with static-linking exception. // See LICENCE file for details. package tpm2 import ( "bytes" "fmt" ) func makeDefaultFormatter(s fmt.State, f rune) string { var builder bytes.Buffer builder.WriteString("%") for _, flag := range [...]int{'+', '-', '#', ' ', '0'} { if s.Flag(flag) { fmt.Fprintf(&builder, "%c", flag) } } if width, ok := s.Width(); ok { fmt.Fprintf(&builder, "%d", width) } if prec, ok := s.Precision(); ok { fmt.Fprintf(&builder, ".%d", prec) } builder.WriteRune(f) return builder.String() } func (m TPMManufacturer) String() string { switch m { case TPMManufacturerAMD: return "AMD" case TPMManufacturerATML: return "Atmel" case TPMManufacturerBRCM: return "Broadcom" case TPMManufacturerHPE: return "HPE" case TPMManufacturerIBM: return "IBM" case TPMManufacturerIFX: return "Infineon" case TPMManufacturerINTC: return "Intel" case TPMManufacturerLEN: return "Lenovo" case TPMManufacturerMSFT: return "Microsoft" case TPMManufacturerNSM: return "National Semiconductor" case TPMManufacturerNTZ: return "Nationz" case TPMManufacturerNTC: return "Nuvoton Technology" case TPMManufacturerQCOM: return "Qualcomm" case TPMManufacturerSMSC: return "SMSC" case TPMManufacturerSTM: return "ST Microelectronics" case TPMManufacturerSMSN: return "Samsung" case TPMManufacturerSNS: return "Sinosun" case TPMManufacturerTXN: return "Texas Instruments" case TPMManufacturerWEC: return "Winbond" case TPMManufacturerROCC: return "Fuzhou Rockchip" case TPMManufacturerGOOG: return "Google" default: return fmt.Sprintf("0x%08x", uint32(m)) } } func (m TPMManufacturer) Format(s fmt.State, f rune) { switch f { case 's', 'v': fmt.Fprintf(s, "%s", m.String()) default: fmt.Fprintf(s, makeDefaultFormatter(s, f), uint32(m)) } } func (c CommandCode) String() string { switch c { case CommandNVUndefineSpaceSpecial: return "TPM_CC_NV_UndefineSpaceSpecial" case CommandEvictControl: return "TPM_CC_EvictControl" case CommandHierarchyControl: return "TPM_CC_HierarchyControl" case CommandNVUndefineSpace: return "TPM_CC_NV_UndefineSpace" case CommandClear: return "TPM_CC_Clear" case CommandClearControl: return "TPM_CC_ClearControl" case CommandClockSet: return "TPM_CC_ClockSet" case CommandHierarchyChangeAuth: return "TPM_CC_HierarchyChangeAuth" case CommandNVDefineSpace: return "TPM_CC_NV_DefineSpace" case CommandPCRAllocate: return "TPM_CC_PCR_Allocate" case CommandSetPrimaryPolicy: return "TPM_CC_SetPrimaryPolicy" case CommandClockRateAdjust: return "TPM_CC_ClockRateAdjust" case CommandCreatePrimary: return "TPM_CC_CreatePrimary" case CommandNVGlobalWriteLock: return "TPM_CC_NV_GlobalWriteLock" case CommandGetCommandAuditDigest: return "TPM_CC_GetCommandAuditDigest" case CommandNVIncrement: return "TPM_CC_NV_Increment" case CommandNVSetBits: return "TPM_CC_NV_SetBits" case CommandNVExtend: return "TPM_CC_NV_Extend" case CommandNVWrite: return "TPM_CC_NV_Write" case CommandNVWriteLock: return "TPM_CC_NV_WriteLock" case CommandDictionaryAttackLockReset: return "TPM_CC_DictionaryAttackLockReset" case CommandDictionaryAttackParameters: return "TPM_CC_DictionaryAttackParameters" case CommandNVChangeAuth: return "TPM_CC_NV_ChangeAuth" case CommandPCREvent: return "TPM_CC_PCR_Event" case CommandPCRReset: return "TPM_CC_PCR_Reset" case CommandSequenceComplete: return "TPM_CC_SequenceComplete" case CommandSetCommandCodeAuditStatus: return "TPM_CC_SetCommandCodeAuditStatus" case CommandIncrementalSelfTest: return "TPM_CC_IncrementalSelfTest" case CommandSelfTest: return "TPM_CC_SelfTest" case CommandStartup: return "TPM_CC_Startup" case CommandShutdown: return "TPM_CC_Shutdown" case CommandStirRandom: return "TPM_CC_StirRandom" case CommandActivateCredential: return "TPM_CC_ActivateCredential" case CommandCertify: return "TPM_CC_Certify" case CommandPolicyNV: return "TPM_CC_PolicyNV" case CommandCertifyCreation: return "TPM_CC_CertifyCreation" case CommandDuplicate: return "TPM_CC_Duplicate" case CommandGetTime: return "TPM_CC_GetTime" case CommandGetSessionAuditDigest: return "TPM_CC_GetSessionAuditDigest" case CommandNVRead: return "TPM_CC_NV_Read" case CommandNVReadLock: return "TPM_CC_NV_ReadLock" case CommandObjectChangeAuth: return "TPM_CC_ObjectChangeAuth" case CommandPolicySecret: return "TPM_CC_PolicySecret" case CommandCreate: return "TPM_CC_Create" case CommandECDHZGen: return "TPM_CC_ECDH_ZGen" case CommandHMAC: return "TPM_CC_HMAC" case CommandImport: return "TPM_CC_Import" case CommandLoad: return "TPM_CC_Load" case CommandQuote: return "TPM_CC_Quote" case CommandRSADecrypt: return "TPM_CC_RSA_Decrypt" case CommandHMACStart: return "TPM_CC_HMAC_Start" case CommandSequenceUpdate: return "TPM_CC_SequenceUpdate" case CommandSign: return "TPM_CC_Sign" case CommandUnseal: return "TPM_CC_Unseal" case CommandPolicySigned: return "TPM_CC_PolicySigned" case CommandContextLoad: return "TPM_CC_ContextLoad" case CommandContextSave: return "TPM_CC_ContextSave" case CommandECDHKeyGen: return "TPM_CC_ECDH_KeyGen" case CommandFlushContext: return "TPM_CC_FlushContext" case CommandLoadExternal: return "TPM_CC_LoadExternal" case CommandMakeCredential: return "TPM_CC_MakeCredential" case CommandNVReadPublic: return "TPM_CC_NV_ReadPublic" case CommandPolicyAuthorize: return "TPM_CC_PolicyAuthorize" case CommandPolicyAuthValue: return "TPM_CC_PolicyAuthValue" case CommandPolicyCommandCode: return "TPM_CC_PolicyCommandCode" case CommandPolicyCounterTimer: return "TPM_CC_PolicyCounterTimer" case CommandPolicyCpHash: return "TPM_CC_PolicyCpHash" case CommandPolicyLocality: return "TPM_CC_PolicyLocality" case CommandPolicyNameHash: return "TPM_CC_PolicyNameHash" case CommandPolicyOR: return "TPM_CC_PolicyOR" case CommandPolicyTicket: return "TPM_CC_PolicyTicket" case CommandReadPublic: return "TPM_CC_ReadPublic" case CommandRSAEncrypt: return "TPM_CC_RSA_Encrypt" case CommandStartAuthSession: return "TPM_CC_StartAuthSession" case CommandVerifySignature: return "TPM_CC_VerifySignature" case CommandECCParameters: return "TPM_CC_ECC_Parameters" case CommandGetCapability: return "TPM_CC_GetCapability" case CommandGetRandom: return "TPM_CC_GetRandom" case CommandGetTestResult: return "TPM_CC_GetTestResult" case CommandHash: return "TPM_CC_Hash" case CommandPCRRead: return "TPM_CC_PCR_Read" case CommandPolicyPCR: return "TPM_CC_PolicyPCR" case CommandPolicyRestart: return "TPM_CC_PolicyRestart" case CommandReadClock: return "TPM_CC_ReadClock" case CommandPCRExtend: return "TPM_CC_PCR_Extend" case CommandNVCertify: return "TPM_CC_NV_Certify" case CommandEventSequenceComplete: return "TPM_CC_EventSequenceComplete" case CommandHashSequenceStart: return "TPM_CC_HashSequenceStart" case CommandPolicyDuplicationSelect: return "TPM_CC_PolicyDuplicationSelect" case CommandPolicyGetDigest: return "TPM_CC_PolicyGetDigest" case CommandTestParms: return "TPM_CC_TestParms" case CommandCommit: return "TPM_CC_Commit" case CommandPolicyPassword: return "TPM_CC_PolicyPassword" case CommandPolicyNvWritten: return "TPM_CC_PolicyNvWritten" case CommandPolicyTemplate: return "TPM_CC_PolicyTemplate" case CommandCreateLoaded: return "TPM_CC_CreateLoaded" case CommandPolicyAuthorizeNV: return "TPM_CC_PolicyAuthorizeNV" default: return fmt.Sprintf("0x%08x", uint32(c)) } } func (c CommandCode) Format(s fmt.State, f rune) { switch f { case 's', 'v': fmt.Fprintf(s, "%s", c.String()) default: fmt.Fprintf(s, makeDefaultFormatter(s, f), uint32(c)) } } func (e ErrorCode) String() string { switch e { case ErrorInitialize: return "TPM_RC_INITIALIZE" case ErrorFailure: return "TPM_RC_FAILURE" case ErrorSequence: return "TPM_RC_SEQUENCE" case ErrorDisabled: return "TPM_RC_DISABLED" case ErrorExclusive: return "TPM_RC_EXCLUSIVE" case ErrorAuthType: return "TPM_RC_AUTH_TYPE" case ErrorAuthMissing: return "TPM_RC_AUTH_MISSING" case ErrorPolicy: return "TPM_RC_POLICY" case ErrorPCR: return "TPM_RC_PCR" case ErrorPCRChanged: return "TPM_RC_PCR_CHANGED" case ErrorUpgrade: return "TPM_RC_UPGRADE" case ErrorTooManyContexts: return "TPM_RC_TOO_MANY_CONTEXTS" case ErrorAuthUnavailable: return "TPM_RC_AUTH_UNAVAILABLE" case ErrorReboot: return "TPM_RC_REBOOT" case ErrorUnbalanced: return "TPM_RC_UNBALANCED" case ErrorCommandSize: return "TPM_RC_COMMAND_SIZE" case ErrorCommandCode: return "TPM_RC_COMMAND_CODE" case ErrorAuthsize: return "TPM_RC_AUTHSIZE" case ErrorAuthContext: return "TPM_RC_AUTH_CONTEXT" case ErrorNVRange: return "TPM_RC_NV_RANGE" case ErrorNVSize: return "TPM_RC_NV_SIZE" case ErrorNVLocked: return "TPM_RC_NV_LOCKED" case ErrorNVAuthorization: return "TPM_RC_NV_AUTHORIZATION" case ErrorNVUninitialized: return "TPM_RC_NV_UNINITIALIZED" case ErrorNVSpace: return "TPM_RC_NV_SPACE" case ErrorNVDefined: return "TPM_RC_NV_DEFINED" case ErrorBadContext: return "TPM_RC_BAD_CONTEXT" case ErrorCpHash: return "TPM_RC_CPHASH" case ErrorParent: return "TPM_RC_PARENT" case ErrorNeedsTest: return "TPM_RC_NEEDS_TEST" case ErrorNoResult: return "TPM_RC_NO_RESULT" case ErrorSensitive: return "TPM_RC_SENSITIVE" // Format 1 error codes start here case ErrorAsymmetric: return "TPM_RC_ASYMMETRIC" case ErrorAttributes: return "TPM_RC_ATTRIBUTES" case ErrorHash: return "TPM_RC_HASH" case ErrorValue: return "TPM_RC_VALUE" case ErrorHierarchy: return "TPM_RC_HIERARCHY" case ErrorKeySize: return "TPM_RC_KEY_SIZE" case ErrorMGF: return "TPM_RC_MGF" case ErrorMode: return "TPM_RC_MODE" case ErrorType: return "TPM_RC_TYPE" case ErrorHandle: return "TPM_RC_HANDLE" case ErrorKDF: return "TPM_RC_KDF" case ErrorRange: return "TPM_RC_RANGE" case ErrorAuthFail: return "TPM_RC_AUTH_FAIL" case ErrorNonce: return "TPM_RC" case ErrorPP: return "TPM_RC_PP" case ErrorScheme: return "TPM_RC_SCHEME" case ErrorSize: return "TPM_RC_SIZE" case ErrorSymmetric: return "TPM_RC_SYMMETRIC" case ErrorTag: return "TPM_RC_TAG" case ErrorSelector: return "TPM_RC_SELECTOR" case ErrorInsufficient: return "TPM_RC_INSUFFICIENT" case ErrorSignature: return "TPM_RC_SIGNATURE" case ErrorKey: return "TPM_RC_KEY" case ErrorPolicyFail: return "TPM_RC_POLICY_FAIL" case ErrorIntegrity: return "TPM_RC_INTEGRITY" case ErrorTicket: return "TPM_RC_TICKET" case ErrorReservedBits: return "TPM_RC_RESERVED_BITS" case ErrorBadAuth: return "TPM_RC_BAD_AUTH" case ErrorExpired: return "TPM_RC_EXPIRED" case ErrorPolicyCC: return "TPM_RC_POLICY_CC" case ErrorBinding: return "TPM_RC_BINDING" case ErrorCurve: return "TPM_RC_CURVE" case ErrorECCPoint: return "TPM_RC_ECC_POINT" case ErrorBadTag: return "TPM_RC_BAD_TAG" default: return fmt.Sprintf("0x%02x", uint8(e)) } } func (e ErrorCode) Format(s fmt.State, f rune) { switch f { case 's', 'v': fmt.Fprintf(s, "%s", e.String()) default: fmt.Fprintf(s, makeDefaultFormatter(s, f), uint8(e)) } } func (e WarningCode) String() string { switch e { case WarningContextGap: return "TPM_RC_CONTEXT_GAP" case WarningObjectMemory: return "TPM_RC_OBJECT_MEMORY" case WarningSessionMemory: return "TPM_RC_SESSION_MEMORY" case WarningMemory: return "TPM_RC_MEMORY" case WarningSessionHandles: return "TPM_RC_SESSION_HANDLES" case WarningObjectHandles: return "TPM_RC_OBJECT_HANDLES" case WarningLocality: return "TPM_RC_LOCALITY" case WarningYielded: return "TPM_RC_YIELDED" case WarningCanceled: return "TPM_RC_CANCELED" case WarningTesting: return "TPM_RC_TESTING" case WarningReferenceH0: return "TPM_RC_REFERENCE_H0" case WarningReferenceH1: return "TPM_RC_REFERENCE_H1" case WarningReferenceH2: return "TPM_RC_REFERENCE_H2" case WarningReferenceH3: return "TPM_RC_REFERENCE_H3" case WarningReferenceH4: return "TPM_RC_REFERENCE_H4" case WarningReferenceH5: return "TPM_RC_REFERENCE_H5" case WarningReferenceH6: return "TPM_RC_REFERENCE_H6" case WarningReferenceS0: return "TPM_RC_REFERENCE_S0" case WarningReferenceS1: return "TPM_RC_REFERENCE_S1" case WarningReferenceS2: return "TPM_RC_REFERENCE_S2" case WarningReferenceS3: return "TPM_RC_REFERENCE_S3" case WarningReferenceS4: return "TPM_RC_REFERENCE_S4" case WarningReferenceS5: return "TPM_RC_REFERENCE_S5" case WarningReferenceS6: return "TPM_RC_REFERENCE_S6" case WarningNVRate: return "TPM_RC_NV_RATE" case WarningLockout: return "TPM_RC_LOCKOUT" case WarningRetry: return "TPM_RC_RETRY" case WarningNVUnavailable: return "TPM_RC_NV_UNAVAILABLE" default: return fmt.Sprintf("0x%02x", uint8(e)) } } func (e WarningCode) Format(s fmt.State, f rune) { switch f { case 's', 'v': fmt.Fprintf(s, "%s", e.String()) default: fmt.Fprintf(s, makeDefaultFormatter(s, f), uint8(e)) } } func (h Handle) String() string { switch h { case HandleOwner: return "TPM_RH_OWNER" case HandleNull: return "TPM_RH_NULL" case HandleUnassigned: return "TPM_RH_UNASSIGNED" case HandlePW: return "TPM_RS_PW" case HandleLockout: return "TPM_RH_LOCKOUT" case HandleEndorsement: return "TPM_RH_ENDORSEMENT" case HandlePlatform: return "TPM_RH_PLATFORM" case HandlePlatformNV: return "TPM_RH_PLATFORM_NV" default: return fmt.Sprintf("0x%08x", uint32(h)) } } func (h Handle) Format(s fmt.State, f rune) { switch f { case 's', 'v': fmt.Fprintf(s, "%s", h.String()) default: fmt.Fprintf(s, makeDefaultFormatter(s, f), uint32(h)) } } func (a AlgorithmId) String() string { switch a { case AlgorithmRSA: return "TPM_ALG_RSA" case AlgorithmTDES: return "TPM_ALG_TDES" case AlgorithmSHA1: return "TPM_ALG_SHA1" case AlgorithmHMAC: return "TPM_ALG_HMAC" case AlgorithmAES: return "TPM_ALG_AES" case AlgorithmMGF1: return "TPM_ALG_MGF1" case AlgorithmKeyedHash: return "TPM_ALG_KEYEDHASH" case AlgorithmXOR: return "TPM_ALG_XOR" case AlgorithmSHA256: return "TPM_ALG_SHA256" case AlgorithmSHA384: return "TPM_ALG_SHA384" case AlgorithmSHA512: return "TPM_ALG_SHA512" case AlgorithmNull: return "TPM_ALG_NULL" case AlgorithmSM3_256: return "TPM_ALG_SM3_256" case AlgorithmSM4: return "TPM_ALG_SM4" case AlgorithmRSASSA: return "TPM_ALG_RSASSA" case AlgorithmRSAES: return "TPM_ALG_RSAES" case AlgorithmRSAPSS: return "TPM_ALG_RSAPSS" case AlgorithmOAEP: return "TPM_ALG_OAEP" case AlgorithmECDSA: return "TPM_ALG_ECDSA" case AlgorithmECDH: return "TPM_ALG_ECDH" case AlgorithmECDAA: return "TPM_ALG_ECDAA" case AlgorithmSM2: return "TPM_ALG_SM2" case AlgorithmECSchnorr: return "TPM_ALG_ECSCHNORR" case AlgorithmECMQV: return "TPM_ALG_ECMQV" case AlgorithmKDF1_SP800_56A: return "TPM_ALG_KDF1_SP800_56A" case AlgorithmKDF2: return "TPM_ALG_KDF2" case AlgorithmKDF1_SP800_108: return "TPM_ALG_KDF1_SP800_108" case AlgorithmECC: return "TPM_ALG_ECC" case AlgorithmSymCipher: return "TPM_ALG_SYMCIPHER" case AlgorithmCamellia: return "TPM_ALG_CAMELLIA" case AlgorithmSHA3_256: return "TPM_ALG_SHA3_256" case AlgorithmSHA3_384: return "TPM_ALG_SHA3_384" case AlgorithmSHA3_512: return "TPM_ALG_SHA3_512" case AlgorithmCTR: return "TPM_ALG_CTR" case AlgorithmOFB: return "TPM_ALG_OFB" case AlgorithmCBC: return "TPM_ALG_CBC" case AlgorithmCFB: return "TPM_ALG_CFB" case AlgorithmECB: return "TPM_ALG_ECB" default: return fmt.Sprintf("0x%04x", uint16(a)) } } func (a AlgorithmId) Format(s fmt.State, f rune) { switch f { case 's', 'v': fmt.Fprintf(s, "%s", a.String()) default: fmt.Fprintf(s, makeDefaultFormatter(s, f), uint16(a)) } } func (a HashAlgorithmId) Format(s fmt.State, f rune) { AlgorithmId(a).Format(s, f) } func (a SymAlgorithmId) Format(s fmt.State, f rune) { AlgorithmId(a).Format(s, f) } func (a SymObjectAlgorithmId) Format(s fmt.State, f rune) { AlgorithmId(a).Format(s, f) } func (a SymModeId) Format(s fmt.State, f rune) { AlgorithmId(a).Format(s, f) } func (a KDFAlgorithmId) Format(s fmt.State, f rune) { AlgorithmId(a).Format(s, f) } func (a SigSchemeId) Format(s fmt.State, f rune) { AlgorithmId(a).Format(s, f) } func (a KeyedHashSchemeId) Format(s fmt.State, f rune) { AlgorithmId(a).Format(s, f) } func (a AsymSchemeId) Format(s fmt.State, f rune) { AlgorithmId(a).Format(s, f) } func (a RSASchemeId) Format(s fmt.State, f rune) { AlgorithmId(a).Format(s, f) } func (a ECCSchemeId) Format(s fmt.State, f rune) { AlgorithmId(a).Format(s, f) } func (a ObjectTypeId) Format(s fmt.State, f rune) { AlgorithmId(a).Format(s, f) } func (c Capability) String() string { switch c { case CapabilityAlgs: return "TPM_CAP_ALGS" case CapabilityHandles: return "TPM_CAP_HANDLES" case CapabilityCommands: return "TPM_CAP_COMMANDS" case CapabilityPPCommands: return "TPM_CAP_PP_COMMANDS" case CapabilityAuditCommands: return "TPM_CAP_AUDIT_COMMANDS" case CapabilityPCRs: return "TPM_CAP_PCRS" case CapabilityTPMProperties: return "TPM_CAP_TPM_PROPERTIES" case CapabilityPCRProperties: return "TPM_CAP_PCR_PROPERTIES" case CapabilityECCCurves: return "TPM_CAP_ECC_CURVES" case CapabilityAuthPolicies: return "TPM_CAP_AUTH_POLICIES" default: return fmt.Sprintf("0x%08x", uint32(c)) } } func (c Capability) Format(s fmt.State, f rune) { switch f { case 's', 'v': fmt.Fprintf(s, "%s", c.String()) default: fmt.Fprintf(s, makeDefaultFormatter(s, f), uint32(c)) } } var ( errorCodeDescriptions = map[ErrorCode]string{ ErrorInitialize: "TPM not initialized by TPM2_Startup or already initialized", ErrorFailure: "commands not being accepted because of a TPM failure", ErrorSequence: "improper use of a sequence handle", ErrorDisabled: "the command is disabled", ErrorExclusive: "command failed because audit sequence required exclusivity", ErrorAuthType: "authorization handle is not correct for command", ErrorAuthMissing: "command requires an authorization session for handle and it is not present", ErrorPolicy: "policy failure in math operation or an invalid authPolicy value", ErrorPCR: "PCR check fail", ErrorPCRChanged: "PCR have changed since checked", ErrorTooManyContexts: "context ID counter is at maximum", ErrorAuthUnavailable: "authValue or authPolicy is not available for selected entity", ErrorReboot: "a _TPM_Init and Startup(CLEAR) is required before the TPM can resume operation", ErrorUnbalanced: "the protection algorithms (hash and symmetric) are not reasonably balanced. The digest size of the hash must be " + "larger than the key size of the symmetric algorithm", ErrorCommandSize: "command commandSize value is inconsistent with contents of the command buffer; either the size is not the same " + "as the octets loaded by the hardware interface layer or the value is not large enough to hold a command header", ErrorCommandCode: "command code not supported", ErrorAuthsize: "the value of authorizationSize is out of range or the number of octets in the Authorization Area is greater than " + "required", ErrorAuthContext: "use of an authorization session with a context command or another command that cannot have an authorization " + "session", ErrorNVRange: "NV offset+size is out of range", ErrorNVSize: "Requested allocation size is larger than allowed", ErrorNVLocked: "NV access locked", ErrorNVAuthorization: "NV access authorization fails in command actions (this failure does not affect lockout.action)", ErrorNVUninitialized: "an NV Index is used before being initialized or the state saved by TPM2_Shutdown(STATE) could not be " + "restored", ErrorNVSpace: "insufficient space for NV allocation", ErrorNVDefined: "NV Index or persistent object already defined", ErrorBadContext: "context in TPM2_ContextLoad() is not valid", ErrorCpHash: "cpHash value already set or not correct for use", ErrorParent: "handle for parent is not a valid parent", ErrorNeedsTest: "some function needs testing", ErrorNoResult: "returned when an internal function cannot process a request due to an unspecified problem. This code is usually " + "related to invalid parameters that are not properly filtered by the input unmarshaling code", ErrorSensitive: "the sensitive area did not unmarshal correctly after decryption", ErrorAsymmetric: "asymmetric algorithm not supported or not correct", ErrorAttributes: "inconsistent attributes", ErrorHash: "hash algorithm not supported or not appropriate", ErrorValue: "value is out of range or is not correct for the context", ErrorHierarchy: "hierarchy is not enabled or is not correct for the use", ErrorKeySize: "key size is not supported", ErrorMGF: "mask generation function not supported", ErrorMode: "mode of operation not supported", ErrorType: "the type of the value is not appropriate for the use", ErrorHandle: "the handle is not correct for the use", ErrorKDF: "unsupported key derivation function or function not appropriate for use", ErrorRange: "value was out of allowed range", ErrorAuthFail: "the authorization HMAC check failed and DA counter incremented", ErrorNonce: "invalid nonce size or nonce value mismatch", ErrorPP: "authorization requires assertion of PP", ErrorScheme: "unsupported or incompatible scheme", ErrorSize: "structure is the wrong size", ErrorSymmetric: "unsupported symmetric algorithm or key size, or not appropriate for instance", ErrorTag: "incorrect structure tag", ErrorSelector: "union selector is incorrect", ErrorInsufficient: "the TPM was unable to unmarshal a value because there were not enough octets in the input buffer", ErrorSignature: "the signature is not valid", ErrorKey: "key fields are not compatible with the selected use", ErrorPolicyFail: "a policy check failed", ErrorIntegrity: "integrity check failed", ErrorTicket: "invalid ticket", ErrorReservedBits: "reserved bits not set to zero as required", ErrorBadAuth: "authorization failure without DA implications", ErrorExpired: "the policy has expired", ErrorPolicyCC: "the commandCode in the policy is not the commandCode of the command or the command code in a policy command " + "references a command that is not implemented", ErrorBinding: "public and sensitive portions of an object are not cryptographically bound", ErrorCurve: "curve not supported", ErrorECCPoint: "point is not on the required curve"} warningCodeDescriptions = map[WarningCode]string{ WarningContextGap: "gap for context ID is too large", WarningObjectMemory: "out of memory for object contexts", WarningSessionMemory: "out of memory for session contexts", WarningMemory: "out of shared object/session memory or need space for internal operations", WarningSessionHandles: "out of session handles – a session must be flushed before a new session may be created", WarningObjectHandles: "out of object handles – the handle space for objects is depleted and a reboot is required", WarningLocality: "bad locality", WarningYielded: "the TPM has suspended operation on the command; forward progress was made and the command may be retried", WarningCanceled: "the command was canceled", WarningTesting: "TPM is performing self-tests", WarningReferenceH0: "the 1st handle in the handle area references a transient object or session that is not loaded", WarningReferenceH1: "the 2nd handle in the handle area references a transient object or session that is not loaded", WarningReferenceH2: "the 3rd handle in the handle area references a transient object or session that is not loaded", WarningReferenceH3: "the 4th handle in the handle area references a transient object or session that is not loaded", WarningReferenceH4: "the 5th handle in the handle area references a transient object or session that is not loaded", WarningReferenceH5: "the 6th handle in the handle area references a transient object or session that is not loaded", WarningReferenceH6: "the 7th handle in the handle area references a transient object or session that is not loaded", WarningReferenceS0: "the 1st authorization session handle references a session that is not loaded", WarningReferenceS1: "the 2nd authorization session handle references a session that is not loaded", WarningReferenceS2: "the 3rd authorization session handle references a session that is not loaded", WarningReferenceS3: "the 4th authorization session handle references a session that is not loaded", WarningReferenceS4: "the 5th authorization session handle references a session that is not loaded", WarningReferenceS5: "the 6th authorization session handle references a session that is not loaded", WarningReferenceS6: "the 7th authorization session handle references a session that is not loaded", WarningNVRate: "the TPM is rate-limiting accesses to prevent wearout of NV", WarningLockout: "authorizations for objects subject to DA protection are not allowed at this time because the TPM is in DA " + "lockout mode", WarningRetry: "the TPM was not able to start the command", WarningNVUnavailable: "the command may require writing of NV and NV is not current accessible"} ) ./github.com/canonical/go-tpm2/tcti.go0000664000000000000000000000351100000000000016111 0ustar0000000000000000// Copyright 2020 Canonical Ltd. // Licensed under the LGPLv3 with static-linking exception. // See LICENCE file for details. package tpm2 // XXX: Note that the "TCG TSS 2.0 TPM Command Transmission Interface (TCTI) API Specification" // defines the following callbacks: // - transmit, which is equivalent to io.Writer. // - receive, which is equivalent to io.Reader. // - finalize, which is equivalent to io.Closer. // - cancel, which we don't implement at the moment because there's not a mechanism to cancel // an operation in go-tpm2. Perhaps if we move the call to Read() to a separate go routine and pass // a context around, we could cancel using a deadline. Not today though, and the Linux character // device driver doesn't provide a mechanism to cancel so it probably wouldn't be worth the effort // anyway. // - getPollHandles, doesn't really make sense here because go's runtime does the polling on // Read. // - setLocality. // - makeSticky, not implemented yet by any TCTI implementation in tss2 AFAICT. // TCTI represents a communication channel to a TPM implementation. type TCTI interface { // Read is used to receive a response to a previously transmitted command. The // implementation must support partial reading of a response, and must return io.EOF // when there are no more bytes of a response left to read. Read(p []byte) (int, error) // Write is used to transmit a serialized command to the TPM implementation. // A command must be transmitted in a single write. Write(p []byte) (int, error) Close() error // SetLocality sets the locality that will be used for subsequent commands. SetLocality(locality uint8) error // MakeSticky requests that the underlying resource manager does not unload the resource // associated with the supplied handle between commands. MakeSticky(handle Handle, sticky bool) error } ./github.com/canonical/go-tpm2/templates/0000775000000000000000000000000000000000000016615 5ustar0000000000000000./github.com/canonical/go-tpm2/templates/templates.go0000664000000000000000000007476600000000000021166 0ustar0000000000000000// Copyright 2021 Canonical Ltd. // Licensed under the LGPLv3 with static-linking exception. // See LICENCE file for details. /* Package template contains helpers for constructing templates to create objects with go-tpm2. */ package templates import ( "github.com/canonical/go-tpm2" ) type KeyUsage int const ( KeyUsageSign KeyUsage = 1 << iota KeyUsageDecrypt KeyUsageEncrypt = KeyUsageSign ) // NewRSAStorageKey returns a template for a RSA storage parent with the specified // name algorithm, symmetric cipher, symmetric key size and RSA key size. If nameAlg // is HashAlgorithmNull, then HashAlgorithmSHA256 is used. If algorithm is // SymObjectAlgorithmNull, then SymObjectAlgorithmAES is used. If symKeyBits is zero, // then 128 is used. If asymKeyBits is zero, then 2048 is used. // // The template cannot be used to create a key in a duplication group. In order to create // a key in a duplication group, remove the AttrFixedTPM attribute. In order to create // a key that is a duplication root, remove both the AttrFixedTPM and AttrFixedParent // attributes. In this case, an authorization policy that permits duplication must // be added. // // The template has the AttrUserWithAuth set in order to permit authentication for // the user auth role using the created object's authorization value. In order to // require authentication for the user auth role using an authorization policy, // remove the AttrUserWithAuth attribute. func NewRSAStorageKey(nameAlg tpm2.HashAlgorithmId, algorithm tpm2.SymObjectAlgorithmId, symKeyBits, asymKeyBits uint16) *tpm2.Public { if nameAlg == tpm2.HashAlgorithmNull { nameAlg = tpm2.HashAlgorithmSHA256 } if algorithm == tpm2.SymObjectAlgorithmNull { algorithm = tpm2.SymObjectAlgorithmAES } if symKeyBits == 0 { symKeyBits = 128 } if asymKeyBits == 0 { asymKeyBits = 2048 } return &tpm2.Public{ Type: tpm2.ObjectTypeRSA, NameAlg: nameAlg, Attrs: tpm2.AttrFixedTPM | tpm2.AttrFixedParent | tpm2.AttrSensitiveDataOrigin | tpm2.AttrUserWithAuth | tpm2.AttrRestricted | tpm2.AttrDecrypt, Params: &tpm2.PublicParamsU{ RSADetail: &tpm2.RSAParams{ Symmetric: tpm2.SymDefObject{ Algorithm: algorithm, KeyBits: &tpm2.SymKeyBitsU{Sym: symKeyBits}, Mode: &tpm2.SymModeU{Sym: tpm2.SymModeCFB}}, Scheme: tpm2.RSAScheme{Scheme: tpm2.RSASchemeNull}, KeyBits: asymKeyBits, Exponent: 0}}} } // NewRSAStorageKeyWithDefaults returns a template for a RSA storage parent with // SHA256 as the name algorithm, AES-128 as the symmetric cipher and 2048 bits as // the RSA key size. // // The template cannot be used to create a key in a duplication group. In order to create // a key in a duplication group, remove the AttrFixedTPM attribute. In order to create // a key that is a duplication root, remove both the AttrFixedTPM and AttrFixedParent // attributes. In this case, an authorization policy that permits duplication must // be added. // // The template has the AttrUserWithAuth set in order to permit authentication for // the user auth role using the created object's authorization value. In order to // require authentication for the user auth role using an authorization policy, // remove the AttrUserWithAuth attribute. func NewRSAStorageKeyWithDefaults() *tpm2.Public { return NewRSAStorageKey(tpm2.HashAlgorithmNull, tpm2.SymObjectAlgorithmNull, 0, 0) } // NewRestrictedRSASigningKey returns a template for a restricted RSA signing // key with the specified name algorithm, RSA scheme and RSA key size. If nameAlg // is HashAlgorithmNull, then HashAlgorithmSHA256 is used. If scheme is nil, then // RSASSA is used with the digest algorithm set to the same as the name // algorithm. If keyBits is zero, then 2048 is used. // // The template cannot be used to create a key in a duplication group. In order to create // a key in a duplication group, remove the AttrFixedTPM attribute. In order to create // a key that is a duplication root, remove both the AttrFixedTPM and AttrFixedParent // attributes. In this case, an authorization policy that permits duplication must // be added. // // The template has the AttrUserWithAuth set in order to permit authentication for // the user auth role using the created object's authorization value. In order to // require authentication for the user auth role using an authorization policy, // remove the AttrUserWithAuth attribute. func NewRestrictedRSASigningKey(nameAlg tpm2.HashAlgorithmId, scheme *tpm2.RSAScheme, keyBits uint16) *tpm2.Public { if nameAlg == tpm2.HashAlgorithmNull { nameAlg = tpm2.HashAlgorithmSHA256 } if scheme == nil { scheme = &tpm2.RSAScheme{ Scheme: tpm2.RSASchemeRSASSA, Details: &tpm2.AsymSchemeU{ RSASSA: &tpm2.SigSchemeRSASSA{HashAlg: nameAlg}}} } if keyBits == 0 { keyBits = 2048 } return &tpm2.Public{ Type: tpm2.ObjectTypeRSA, NameAlg: nameAlg, Attrs: tpm2.AttrFixedTPM | tpm2.AttrFixedParent | tpm2.AttrSensitiveDataOrigin | tpm2.AttrUserWithAuth | tpm2.AttrSign | tpm2.AttrRestricted, Params: &tpm2.PublicParamsU{ RSADetail: &tpm2.RSAParams{ Symmetric: tpm2.SymDefObject{Algorithm: tpm2.SymObjectAlgorithmNull}, Scheme: *scheme, KeyBits: keyBits, Exponent: 0}}} } // NewRestrictedRSASigningKeyWithDefaults returns a template for a restricted RSA // signing key with SHA256 as the name algorithm, RSA-SSA with SHA256 as the scheme // and 2048 bits as the key size. // // The template cannot be used to create a key in a duplication group. In order to create // a key in a duplication group, remove the AttrFixedTPM attribute. In order to create // a key that is a duplication root, remove both the AttrFixedTPM and AttrFixedParent // attributes. In this case, an authorization policy that permits duplication must // be added. // // The template has the AttrUserWithAuth set in order to permit authentication for // the user auth role using the created object's authorization value. In order to // require authentication for the user auth role using an authorization policy, // remove the AttrUserWithAuth attribute. func NewRestrictedRSASigningKeyWithDefaults() *tpm2.Public { return NewRestrictedRSASigningKey(tpm2.HashAlgorithmNull, nil, 0) } // NewRSAKey returns a template for a general purpose RSA key for the specified // usage, with the specified name algorithm, RSA scheme and RSA key size. If nameAlg // is HashAlgorithmNull, then HashAlgorithmSHA256 is used. If keyBits is zero, then // 2048 is used. If no usage is specified, the template will include both sign and // decrypt attributes. // // The template cannot be used to create a key in a duplication group. In order to create // a key in a duplication group, remove the AttrFixedTPM attribute. In order to create // a key that is a duplication root, remove both the AttrFixedTPM and AttrFixedParent // attributes. In this case, an authorization policy that permits duplication must // be added. // // The template has the AttrUserWithAuth set in order to permit authentication for // the user auth role using the created object's authorization value. In order to // require authentication for the user auth role using an authorization policy, // remove the AttrUserWithAuth attribute. func NewRSAKey(nameAlg tpm2.HashAlgorithmId, usage KeyUsage, scheme *tpm2.RSAScheme, keyBits uint16) *tpm2.Public { attrs := tpm2.AttrFixedTPM | tpm2.AttrFixedParent | tpm2.AttrSensitiveDataOrigin | tpm2.AttrUserWithAuth if usage == 0 { usage = KeyUsageSign | KeyUsageDecrypt } if usage&KeyUsageSign != 0 { attrs |= tpm2.AttrSign } if usage&KeyUsageDecrypt != 0 { attrs |= tpm2.AttrDecrypt } if nameAlg == tpm2.HashAlgorithmNull { nameAlg = tpm2.HashAlgorithmSHA256 } if scheme == nil { scheme = &tpm2.RSAScheme{Scheme: tpm2.RSASchemeNull} } if keyBits == 0 { keyBits = 2048 } return &tpm2.Public{ Type: tpm2.ObjectTypeRSA, NameAlg: nameAlg, Attrs: attrs, Params: &tpm2.PublicParamsU{ RSADetail: &tpm2.RSAParams{ Symmetric: tpm2.SymDefObject{Algorithm: tpm2.SymObjectAlgorithmNull}, Scheme: *scheme, KeyBits: keyBits, Exponent: 0}}} } // NewRSAKeyWithDefaults returns a template for a general purpose RSA key for the // specified usage, with SHA256 as the name algorithm, the scheme unset and 2048 bits // as the key size. If no usage is specified, the template will include both sign and // decrypt attributes. // // The template cannot be used to create a key in a duplication group. In order to create // a key in a duplication group, remove the AttrFixedTPM attribute. In order to create // a key that is a duplication root, remove both the AttrFixedTPM and AttrFixedParent // attributes. In this case, an authorization policy that permits duplication must // be added. // // The template has the AttrUserWithAuth set in order to permit authentication for // the user auth role using the created object's authorization value. In order to // require authentication for the user auth role using an authorization policy, // remove the AttrUserWithAuth attribute. func NewRSAKeyWithDefaults(usage KeyUsage) *tpm2.Public { return NewRSAKey(tpm2.HashAlgorithmNull, usage, nil, 0) } // NewSealedObject returns a template for a sealed object with the specified name // algorithm. If nameAlg is HashAlgorithmNull, then HashAlgorithmSHA256 is used. // // The template cannot be used to create an object in a duplication group. In order to // create an object in a duplication group, remove the AttrFixedTPM attribute. In // order to create an object that can be moved to a new parent, remove both the // AttrFixedTPM and AttrFixedParent attributes. In this case, an authorization policy // that permits duplication must be added. // // The template has the AttrUserWithAuth set in order to permit authentication for // the user auth role using the created object's authorization value. In order to // require authentication for the user auth role using an authorization policy, // remove the AttrUserWithAuth attribute. func NewSealedObject(nameAlg tpm2.HashAlgorithmId) *tpm2.Public { if nameAlg == tpm2.HashAlgorithmNull { nameAlg = tpm2.HashAlgorithmSHA256 } return &tpm2.Public{ Type: tpm2.ObjectTypeKeyedHash, NameAlg: nameAlg, Attrs: tpm2.AttrFixedTPM | tpm2.AttrFixedParent | tpm2.AttrUserWithAuth, Params: &tpm2.PublicParamsU{ KeyedHashDetail: &tpm2.KeyedHashParams{ Scheme: tpm2.KeyedHashScheme{Scheme: tpm2.KeyedHashSchemeNull}}}} } // NewECCStorageKey returns a template for a ECC storage parent with the specified // name algorithm, symmetric cipher, symmetric key size and elliptic curve. If nameAlg // is HashAlgorithmNull, then HashAlgorithmSHA256 is used. If algorithm is // SymObjectAlgorithmNull, then SymObjectAlgorithmAES is used. If keyBits is zero, // then 128 is used. // // The template cannot be used to create a key in a duplication group. In order to create // a key in a duplication group, remove the AttrFixedTPM attribute. In order to create // a key that is a duplication root, remove both the AttrFixedTPM and AttrFixedParent // attributes. In this case, an authorization policy that permits duplication must // be added. // // The template has the AttrUserWithAuth set in order to permit authentication for // the user auth role using the created object's authorization value. In order to // require authentication for the user auth role using an authorization policy, // remove the AttrUserWithAuth attribute. func NewECCStorageKey(nameAlg tpm2.HashAlgorithmId, algorithm tpm2.SymObjectAlgorithmId, keyBits uint16, curve tpm2.ECCCurve) *tpm2.Public { if nameAlg == tpm2.HashAlgorithmNull { nameAlg = tpm2.HashAlgorithmSHA256 } if algorithm == tpm2.SymObjectAlgorithmNull { algorithm = tpm2.SymObjectAlgorithmAES } if keyBits == 0 { keyBits = 128 } return &tpm2.Public{ Type: tpm2.ObjectTypeECC, NameAlg: nameAlg, Attrs: tpm2.AttrFixedTPM | tpm2.AttrFixedParent | tpm2.AttrSensitiveDataOrigin | tpm2.AttrUserWithAuth | tpm2.AttrRestricted | tpm2.AttrDecrypt, Params: &tpm2.PublicParamsU{ ECCDetail: &tpm2.ECCParams{ Symmetric: tpm2.SymDefObject{ Algorithm: algorithm, KeyBits: &tpm2.SymKeyBitsU{Sym: keyBits}, Mode: &tpm2.SymModeU{Sym: tpm2.SymModeCFB}}, Scheme: tpm2.ECCScheme{Scheme: tpm2.ECCSchemeNull}, CurveID: curve, KDF: tpm2.KDFScheme{Scheme: tpm2.KDFAlgorithmNull}}}} } // NewECCStorageKeyWithDefaults returns a template for a ECC storage parent with // SHA256 as the name algorithm, AES-128 as the symmetric cipher and the NIST-P256 // curve. // // The template cannot be used to create a key in a duplication group. In order to create // a key in a duplication group, remove the AttrFixedTPM attribute. In order to create // a key that is a duplication root, remove both the AttrFixedTPM and AttrFixedParent // attributes. In this case, an authorization policy that permits duplication must // be added. // // The template has the AttrUserWithAuth set in order to permit authentication for // the user auth role using the created object's authorization value. In order to // require authentication for the user auth role using an authorization policy, // remove the AttrUserWithAuth attribute. func NewECCStorageKeyWithDefaults() *tpm2.Public { return NewECCStorageKey(tpm2.HashAlgorithmNull, tpm2.SymObjectAlgorithmNull, 0, tpm2.ECCCurveNIST_P256) } // NewRestrictedECCSigningKey returns a template for a restricted ECC signing // key with the specified name algorithm, ECC scheme and elliptic curve. If nameAlg // is HashAlgorithmNull, then HashAlgorithmSHA256 is used. If scheme is nil, then // ECDSA is used with the digest algorithm set to the same as the name algorithm. // // The template cannot be used to create a key in a duplication group. In order to create // a key in a duplication group, remove the AttrFixedTPM attribute. In order to create // a key that is a duplication root, remove both the AttrFixedTPM and AttrFixedParent // attributes. In this case, an authorization policy that permits duplication must // be added. // // The template has the AttrUserWithAuth set in order to permit authentication for // the user auth role using the created object's authorization value. In order to // require authentication for the user auth role using an authorization policy, // remove the AttrUserWithAuth attribute. func NewRestrictedECCSigningKey(nameAlg tpm2.HashAlgorithmId, scheme *tpm2.ECCScheme, curve tpm2.ECCCurve) *tpm2.Public { if nameAlg == tpm2.HashAlgorithmNull { nameAlg = tpm2.HashAlgorithmSHA256 } if scheme == nil { scheme = &tpm2.ECCScheme{ Scheme: tpm2.ECCSchemeECDSA, Details: &tpm2.AsymSchemeU{ ECDSA: &tpm2.SigSchemeECDSA{HashAlg: nameAlg}}} } return &tpm2.Public{ Type: tpm2.ObjectTypeECC, NameAlg: nameAlg, Attrs: tpm2.AttrFixedTPM | tpm2.AttrFixedParent | tpm2.AttrSensitiveDataOrigin | tpm2.AttrUserWithAuth | tpm2.AttrSign | tpm2.AttrRestricted, Params: &tpm2.PublicParamsU{ ECCDetail: &tpm2.ECCParams{ Symmetric: tpm2.SymDefObject{Algorithm: tpm2.SymObjectAlgorithmNull}, Scheme: *scheme, CurveID: curve, KDF: tpm2.KDFScheme{Scheme: tpm2.KDFAlgorithmNull}}}} } // NewRestrictedECCSigningKeyWithDefaults returns a template for a restricted ECC // signing key with SHA256 as the name algorithm, ECDSA with SHA256 as the scheme and // NIST-P256 as the curve. // // The template cannot be used to create a key in a duplication group. In order to create // a key in a duplication group, remove the AttrFixedTPM attribute. In order to create // a key that is a duplication root, remove both the AttrFixedTPM and AttrFixedParent // attributes. In this case, an authorization policy that permits duplication must // be added. // // The template has the AttrUserWithAuth set in order to permit authentication for // the user auth role using the created object's authorization value. In order to // require authentication for the user auth role using an authorization policy, // remove the AttrUserWithAuth attribute. func NewRestrictedECCSigningKeyWithDefaults() *tpm2.Public { return NewRestrictedECCSigningKey(tpm2.HashAlgorithmNull, nil, tpm2.ECCCurveNIST_P256) } // NewECCKey returns a template for a general purpose ECC key for the specified // usage, with the specified name algorithm, ECC scheme and elliptic curve. If nameAlg // is HashAlgorithmNull, then HashAlgorithmSHA256 is used. If no usage is specified, // the template will include both sign and decrypt attributes. // // The template cannot be used to create a key in a duplication group. In order to create // a key in a duplication group, remove the AttrFixedTPM attribute. In order to create // a key that is a duplication root, remove both the AttrFixedTPM and AttrFixedParent // attributes. In this case, an authorization policy that permits duplication must // be added. // // The template has the AttrUserWithAuth set in order to permit authentication for // the user auth role using the created object's authorization value. In order to // require authentication for the user auth role using an authorization policy, // remove the AttrUserWithAuth attribute. func NewECCKey(nameAlg tpm2.HashAlgorithmId, usage KeyUsage, scheme *tpm2.ECCScheme, curve tpm2.ECCCurve) *tpm2.Public { attrs := tpm2.AttrFixedTPM | tpm2.AttrFixedParent | tpm2.AttrSensitiveDataOrigin | tpm2.AttrUserWithAuth if usage == 0 { usage = KeyUsageSign | KeyUsageDecrypt } if usage&KeyUsageSign != 0 { attrs |= tpm2.AttrSign } if usage&KeyUsageDecrypt != 0 { attrs |= tpm2.AttrDecrypt } if nameAlg == tpm2.HashAlgorithmNull { nameAlg = tpm2.HashAlgorithmSHA256 } if scheme == nil { scheme = &tpm2.ECCScheme{Scheme: tpm2.ECCSchemeNull} } return &tpm2.Public{ Type: tpm2.ObjectTypeECC, NameAlg: nameAlg, Attrs: attrs, Params: &tpm2.PublicParamsU{ ECCDetail: &tpm2.ECCParams{ Symmetric: tpm2.SymDefObject{Algorithm: tpm2.SymObjectAlgorithmNull}, Scheme: *scheme, CurveID: curve, KDF: tpm2.KDFScheme{Scheme: tpm2.KDFAlgorithmNull}}}} } // NewECCKeyWithDefaults returns a template for a general purpose ECC key for the // specified usage, with SHA256 as the name algorithm, the scheme unset and NIST-P256 // as the curve. If no usage is specified, the template will include both sign and // decrypt attributes. // // The template cannot be used to create a key in a duplication group. In order to create // a key in a duplication group, remove the AttrFixedTPM attribute. In order to create // a key that is a duplication root, remove both the AttrFixedTPM and AttrFixedParent // attributes. In this case, an authorization policy that permits duplication must // be added. // // The template has the AttrUserWithAuth set in order to permit authentication for // the user auth role using the created object's authorization value. In order to // require authentication for the user auth role using an authorization policy, // remove the AttrUserWithAuth attribute. func NewECCKeyWithDefaults(usage KeyUsage) *tpm2.Public { return NewECCKey(tpm2.HashAlgorithmNull, usage, nil, tpm2.ECCCurveNIST_P256) } // NewSymmetricStorageKey returns a template for a symmetric storage parent with the // specified name algorithm, symmetric cipher and symmetric key size. If nameAlg // is HashAlgorithmNull, then HashAlgorithmSHA256 is used. If algorithm is // SymObjectAlgorithmNull, then SymObjectAlgorithmAES is used. If keyBits is zero, // then 128 is used. // // The template cannot be used to create a key in a duplication group. In order to create // a key in a duplication group, remove the AttrFixedTPM attribute. In order to create // a key that is a duplication root, remove both the AttrFixedTPM and AttrFixedParent // attributes. In this case, an authorization policy that permits duplication must // be added. // // The template will create a TPM generated key. In order to supply the key, remove // the AttrSensitiveDataOrigin attribute. // // The template has the AttrUserWithAuth set in order to permit authentication for // the user auth role using the created object's authorization value. In order to // require authentication for the user auth role using an authorization policy, // remove the AttrUserWithAuth attribute. func NewSymmetricStorageKey(nameAlg tpm2.HashAlgorithmId, algorithm tpm2.SymObjectAlgorithmId, keyBits uint16) *tpm2.Public { if nameAlg == tpm2.HashAlgorithmNull { nameAlg = tpm2.HashAlgorithmSHA256 } if algorithm == tpm2.SymObjectAlgorithmNull { algorithm = tpm2.SymObjectAlgorithmAES } if keyBits == 0 { keyBits = 128 } return &tpm2.Public{ Type: tpm2.ObjectTypeSymCipher, NameAlg: nameAlg, Attrs: tpm2.AttrFixedTPM | tpm2.AttrFixedParent | tpm2.AttrSensitiveDataOrigin | tpm2.AttrUserWithAuth | tpm2.AttrRestricted | tpm2.AttrDecrypt, Params: &tpm2.PublicParamsU{ SymDetail: &tpm2.SymCipherParams{ Sym: tpm2.SymDefObject{ Algorithm: algorithm, KeyBits: &tpm2.SymKeyBitsU{Sym: keyBits}, Mode: &tpm2.SymModeU{Sym: tpm2.SymModeCFB}}}}} } // NewSymmetricStorageKeyWithDefaults returns a template for a symmetric storage // parent with SHA256 as the name algorithm and AES-128 as the symmetric cipher. // // The template cannot be used to create a key in a duplication group. In order to create // a key in a duplication group, remove the AttrFixedTPM attribute. In order to create // a key that is a duplication root, remove both the AttrFixedTPM and AttrFixedParent // attributes. In this case, an authorization policy that permits duplication must // be added. // // The template will create a TPM generated key. In order to supply the key, remove // the AttrSensitiveDataOrigin attribute. // // The template has the AttrUserWithAuth set in order to permit authentication for // the user auth role using the created object's authorization value. In order to // require authentication for the user auth role using an authorization policy, // remove the AttrUserWithAuth attribute. func NewSymmetricStorageKeyWithDefaults() *tpm2.Public { return NewSymmetricStorageKey(tpm2.HashAlgorithmNull, tpm2.SymObjectAlgorithmNull, 0) } // NewSymmetricKey returns a template for a general purpose symmetric key with // the specified name algorithm, key usage, symmetic algorithm, symmetric key size // and symmetric mode. If nameAlg is HashAlgorithmNull, then HashAlgorithmSHA256 // is used. If algorithm is SymObjectAlgorithmNull, then SymObjectAlgorithmAES is // used. If keyBits is zero, then 128 is used. If no usage is specified, the template // will include both sign and decrypt attributes. // // The template cannot be used to create a key in a duplication group. In order to create // a key in a duplication group, remove the AttrFixedTPM attribute. In order to create // a key that is a duplication root, remove both the AttrFixedTPM and AttrFixedParent // attributes. In this case, an authorization policy that permits duplication must // be added. // // The template will create a TPM generated key. In order to supply the key, remove // the AttrSensitiveDataOrigin attribute. // // The template has the AttrUserWithAuth set in order to permit authentication for // the user auth role using the created object's authorization value. In order to // require authentication for the user auth role using an authorization policy, // remove the AttrUserWithAuth attribute. func NewSymmetricKey(nameAlg tpm2.HashAlgorithmId, usage KeyUsage, algorithm tpm2.SymObjectAlgorithmId, keyBits uint16, mode tpm2.SymModeId) *tpm2.Public { attrs := tpm2.AttrFixedTPM | tpm2.AttrFixedParent | tpm2.AttrSensitiveDataOrigin | tpm2.AttrUserWithAuth if usage == 0 { usage = KeyUsageEncrypt | KeyUsageDecrypt } if usage&KeyUsageEncrypt != 0 { attrs |= tpm2.AttrSign } if usage&KeyUsageDecrypt != 0 { attrs |= tpm2.AttrDecrypt } if nameAlg == tpm2.HashAlgorithmNull { nameAlg = tpm2.HashAlgorithmSHA256 } if algorithm == tpm2.SymObjectAlgorithmNull { algorithm = tpm2.SymObjectAlgorithmAES } if keyBits == 0 { keyBits = 128 } return &tpm2.Public{ Type: tpm2.ObjectTypeSymCipher, NameAlg: nameAlg, Attrs: attrs, Params: &tpm2.PublicParamsU{ SymDetail: &tpm2.SymCipherParams{ Sym: tpm2.SymDefObject{ Algorithm: algorithm, KeyBits: &tpm2.SymKeyBitsU{Sym: keyBits}, Mode: &tpm2.SymModeU{Sym: mode}}}}} } // NewSymmetricKeyWithDefaults returns a template for a general purpose symmetric // key for the specified usage with SHA256 as the name algorithm, AES-128 as the // cipher and CFB as the cipher mode. If no usage is specified, the template will // include both sign and decrypt attributes. // // The template cannot be used to create a key in a duplication group. In order to create // a key in a duplication group, remove the AttrFixedTPM attribute. In order to create // a key that is a duplication root, remove both the AttrFixedTPM and AttrFixedParent // attributes. In this case, an authorization policy that permits duplication must // be added. // // The template will create a TPM generated key. In order to supply the key, remove // the AttrSensitiveDataOrigin attribute. // // The template has the AttrUserWithAuth set in order to permit authentication for // the user auth role using the created object's authorization value. In order to // require authentication for the user auth role using an authorization policy, // remove the AttrUserWithAuth attribute. func NewSymmetricKeyWithDefaults(usage KeyUsage) *tpm2.Public { return NewSymmetricKey(tpm2.HashAlgorithmNull, usage, tpm2.SymObjectAlgorithmNull, 0, tpm2.SymModeCFB) } // NewHMACKey returns a template for a HMAC key with the specified name algorithm // and HMAC digest algorithm. If nameAlg is HashAlgorithmNull, then HashAlgorithmSHA256 // is used. If schemeAlg is HashAlgorithmNull, then nameAlg is used. // // The template cannot be used to create a key in a duplication group. In order to create // a key in a duplication group, remove the AttrFixedTPM attribute. In order to create // a key that is a duplication root, remove both the AttrFixedTPM and AttrFixedParent // attributes. In this case, an authorization policy that permits duplication must // be added. // // The template will create a TPM generated key. In order to supply the key, remove // the AttrSensitiveDataOrigin attribute. // // The template has the AttrUserWithAuth set in order to permit authentication for // the user auth role using the created object's authorization value. In order to // require authentication for the user auth role using an authorization policy, // remove the AttrUserWithAuth attribute. func NewHMACKey(nameAlg, schemeAlg tpm2.HashAlgorithmId) *tpm2.Public { if nameAlg == tpm2.HashAlgorithmNull { nameAlg = tpm2.HashAlgorithmSHA256 } if schemeAlg == tpm2.HashAlgorithmNull { schemeAlg = nameAlg } return &tpm2.Public{ Type: tpm2.ObjectTypeKeyedHash, NameAlg: nameAlg, Attrs: tpm2.AttrFixedTPM | tpm2.AttrFixedParent | tpm2.AttrSensitiveDataOrigin | tpm2.AttrUserWithAuth | tpm2.AttrSign, Params: &tpm2.PublicParamsU{ KeyedHashDetail: &tpm2.KeyedHashParams{ Scheme: tpm2.KeyedHashScheme{ Scheme: tpm2.KeyedHashSchemeHMAC, Details: &tpm2.SchemeKeyedHashU{ HMAC: &tpm2.SchemeHMAC{ HashAlg: schemeAlg}}}}}} } // NewHMACKeyWithDefaults returns a template for a HMAC key with SHA256 as the // name algorithm and the HMAC digest algorithm. // // The template cannot be used to create a key in a duplication group. In order to create // a key in a duplication group, remove the AttrFixedTPM attribute. In order to create // a key that is a duplication root, remove both the AttrFixedTPM and AttrFixedParent // attributes. In this case, an authorization policy that permits duplication must // be added. // // The template will create a TPM generated key. In order to supply the key, remove // the AttrSensitiveDataOrigin attribute. // // The template has the AttrUserWithAuth set in order to permit authentication for // the user auth role using the created object's authorization value. In order to // require authentication for the user auth role using an authorization policy, // remove the AttrUserWithAuth attribute. func NewHMACKeyWithDefaults() *tpm2.Public { return NewHMACKey(tpm2.HashAlgorithmNull, tpm2.HashAlgorithmNull) } // NewDerivationParentKey returns a template for derivation parent key with the // specified name algorithm and KDF digest algorithm. If nameAlg is HashAlgorithmNull, // then HashAlgorithmSHA256 is used. If schemeAlg is HashAlgorithmNull, then nameAlg // is used. // // The template cannot be used to create a key in a duplication group. In order to create // a key in a duplication group, remove the AttrFixedTPM attribute. In order to create // a key that is a duplication root, remove both the AttrFixedTPM and AttrFixedParent // attributes. In this case, an authorization policy that permits duplication must // be added. // // The template will create a key with a TPM generated seed. In order to supply the // seed, remove the AttrSensitiveDataOrigin attribute. // // The template has the AttrUserWithAuth set in order to permit authentication for // the user auth role using the created object's authorization value. In order to // require authentication for the user auth role using an authorization policy, // remove the AttrUserWithAuth attribute. func NewDerivationParentKey(nameAlg, schemeAlg tpm2.HashAlgorithmId) *tpm2.Public { if nameAlg == tpm2.HashAlgorithmNull { nameAlg = tpm2.HashAlgorithmSHA256 } if schemeAlg == tpm2.HashAlgorithmNull { schemeAlg = nameAlg } return &tpm2.Public{ Type: tpm2.ObjectTypeKeyedHash, NameAlg: nameAlg, Attrs: tpm2.AttrFixedTPM | tpm2.AttrFixedParent | tpm2.AttrSensitiveDataOrigin | tpm2.AttrUserWithAuth | tpm2.AttrDecrypt | tpm2.AttrRestricted, Params: &tpm2.PublicParamsU{ KeyedHashDetail: &tpm2.KeyedHashParams{ Scheme: tpm2.KeyedHashScheme{ Scheme: tpm2.KeyedHashSchemeXOR, Details: &tpm2.SchemeKeyedHashU{ XOR: &tpm2.SchemeXOR{ HashAlg: schemeAlg, KDF: tpm2.KDFAlgorithmKDF1_SP800_108}}}}}} } // NewDerivationParentKeyWithDefaults returns a template for derivation parent key // with SHA256 as the name algorithm and KDF digest algorithm. // // The template cannot be used to create a key in a duplication group. In order to create // a key in a duplication group, remove the AttrFixedTPM attribute. In order to create // a key that is a duplication root, remove both the AttrFixedTPM and AttrFixedParent // attributes. In this case, an authorization policy that permits duplication must // be added. // // The template will create a key with a TPM generated seed. In order to supply the // seed, remove the AttrSensitiveDataOrigin attribute. // // The template has the AttrUserWithAuth set in order to permit authentication for // the user auth role using the created object's authorization value. In order to // require authentication for the user auth role using an authorization policy, // remove the AttrUserWithAuth attribute. func NewDerivationParentKeyWithDefaults() *tpm2.Public { return NewDerivationParentKey(tpm2.HashAlgorithmNull, tpm2.HashAlgorithmNull) } ./github.com/canonical/go-tpm2/tpm.go0000664000000000000000000004121600000000000015752 0ustar0000000000000000// Copyright 2019 Canonical Ltd. // Licensed under the LGPLv3 with static-linking exception. // See LICENCE file for details. package tpm2 import ( "bytes" "errors" "fmt" "io/ioutil" "reflect" "github.com/canonical/go-tpm2/mu" "golang.org/x/xerrors" ) func makeInvalidArgError(name, msg string) error { return fmt.Errorf("invalid %s argument: %s", name, msg) } func isSessionAllowed(commandCode CommandCode) bool { switch commandCode { case CommandStartup: return false case CommandContextLoad: return false case CommandContextSave: return false case CommandFlushContext: return false default: return true } } type cmdContext struct { commandCode CommandCode sessionParams *sessionParams responseCode ResponseCode responseAuthArea []AuthResponse rpBytes []byte } type delimiterSentinel struct{} // Delimiter is a sentinel value used to delimit command handle, command parameter, response handle pointer and response // parameter pointer blocks in the variable length params argument in TPMContext.RunCommand. var Delimiter delimiterSentinel // ResourceContextWithAuth associates a ResourceContext with a session for authorization, and is provided to TPMContext.RunCommand in // the command handle area for any handles that require an authorization. type ResourceContextWithSession struct { Context ResourceContext Session SessionContext } // TODO: Implement commands from the following sections of part 3 of the TPM library spec: // Section 14 - Asymmetric Primitives // Section 15 - Symmetric Primitives // Section 17 - Hash/HMAC/Event Sequences // Section 19 - Ephemeral EC Keys // Section 26 - Miscellaneous Management Functions // Section 27 - Field Upgrade // TPMContext is the main entry point by which commands are executed on a TPM device using this package. It communicates with the // underlying device via a transmission interface, which is an implementation of io.ReadWriteCloser provided to NewTPMContext. // // Methods that execute commands on the TPM will return errors where the TPM responds with them. These are in the form of *TPMError, // *TPMWarning, *TPMHandleError, *TPMSessionError, *TPMParameterError and *TPMVendorError types. // // Some methods also accept a variable number of optional SessionContext arguments - these are for sessions that don't provide // authorization for a corresponding TPM resource. These sessions may be used for the purposes of session based parameter encryption // or command auditing. type TPMContext struct { tcti TCTI permanentResources map[Handle]*permanentContext maxSubmissions uint propertiesInitialized bool maxBufferSize int maxDigestSize int maxNVBufferSize int exclusiveSession *sessionContext } // Close calls Close on the transmission interface. func (t *TPMContext) Close() error { if err := t.tcti.Close(); err != nil { return &TctiError{"close", err} } return nil } // RunCommandBytes is a low-level interface for executing a command. The caller is responsible for supplying a properly // serialized command packet, which can be created with MarshalCommandPacket. // // If successful, this function will return the response packet. An error will only be returned if the transmission // interface returns an error. func (t *TPMContext) RunCommandBytes(packet CommandPacket) (ResponsePacket, error) { if _, err := t.tcti.Write(packet); err != nil { return nil, &TctiError{"write", err} } resp, err := ioutil.ReadAll(t.tcti) if err != nil { return nil, &TctiError{"read", err} } return ResponsePacket(resp), nil } func (t *TPMContext) runCommandWithoutProcessingAuthResponse(commandCode CommandCode, sessionParams *sessionParams, inHandles []HandleContext, params []interface{}, outHandle *Handle) (*cmdContext, error) { handles := make(HandleList, 0, len(inHandles)) handleNames := make([]Name, 0, len(inHandles)) for _, h := range inHandles { handles = append(handles, h.Handle()) handleNames = append(handleNames, h.Name()) } if sessionParams.hasDecryptSession() && (len(params) == 0 || !isParamEncryptable(params[0])) { return nil, fmt.Errorf("command %s does not support command parameter encryption", commandCode) } cpBytes, err := mu.MarshalToBytes(params...) if err != nil { return nil, xerrors.Errorf("cannot marshal parameters for command %s: %w", commandCode, err) } cAuthArea, err := sessionParams.buildCommandAuthArea(commandCode, handleNames, cpBytes) if err != nil { return nil, xerrors.Errorf("cannot build auth area for command %s: %w", commandCode, err) } cmd := MarshalCommandPacket(commandCode, handles, cAuthArea, cpBytes) var responseCode ResponseCode var rpBytes []byte var rAuthArea []AuthResponse for tries := uint(1); ; tries++ { var err error resp, err := t.RunCommandBytes(cmd) if err != nil { return nil, err } responseCode, rpBytes, rAuthArea, err = resp.Unmarshal(outHandle) if err != nil { return nil, &InvalidResponseError{commandCode, fmt.Sprintf("cannot unmarshal response packet: %v", err)} } err = DecodeResponseCode(commandCode, responseCode) if _, invalidRc := err.(InvalidResponseCodeError); invalidRc { return nil, &InvalidResponseError{commandCode, err.Error()} } if err == nil { if len(rAuthArea) != len(sessionParams.sessions) { return nil, &InvalidResponseError{commandCode, fmt.Sprintf("unexpected number of auth responses (got %d, expected %d)", len(rAuthArea), len(sessionParams.sessions))} } break } if tries >= t.maxSubmissions { return nil, err } if !(IsTPMWarning(err, WarningYielded, commandCode) || IsTPMWarning(err, WarningTesting, commandCode) || IsTPMWarning(err, WarningRetry, commandCode)) { return nil, err } } return &cmdContext{ commandCode: commandCode, sessionParams: sessionParams, responseCode: responseCode, responseAuthArea: rAuthArea, rpBytes: rpBytes}, nil } func (t *TPMContext) processAuthResponse(cmd *cmdContext, params []interface{}) error { if len(cmd.responseAuthArea) > 0 { if err := cmd.sessionParams.processResponseAuthArea(cmd.responseAuthArea, cmd.responseCode, cmd.rpBytes); err != nil { return &InvalidResponseError{cmd.commandCode, fmt.Sprintf("cannot process response auth area: %v", err)} } } if isSessionAllowed(cmd.commandCode) { if t.exclusiveSession != nil { t.exclusiveSession.Data().IsExclusive = false } var exclusive *sessionContext for _, s := range cmd.sessionParams.sessions { if s.session == nil { continue } if s.session.Data().IsExclusive { exclusive = s.session break } } t.exclusiveSession = exclusive if t.exclusiveSession != nil { t.exclusiveSession.Data().IsExclusive = true } } rpBuf := bytes.NewReader(cmd.rpBytes) if _, err := mu.UnmarshalFromReader(rpBuf, params...); err != nil { return &InvalidResponseError{cmd.commandCode, fmt.Sprintf("cannot unmarshal response parameters: %v", err)} } if rpBuf.Len() > 0 { return &InvalidResponseError{cmd.commandCode, fmt.Sprintf("response parameter area contains %d trailing bytes", rpBuf.Len())} } return nil } // RunCommandWithResponseCallback is a high-level generic interface for executing the command specified by commandCode. It differs // from RunCommand with the addition of an optional callback which is executed after receiving a response from the TPM, but before // the response is decoded and the session state is updated. This is useful for commands that change the authorization value of a // supplied entity, where the response HMAC may be generated based on the new authorization value. It takes care of marshalling // command handles and command parameters, as well as constructing and marshalling the authorization area and choosing the correct // StructTag value. It takes care of unmarshalling response handles and response parameters, as well as unmarshalling the response // authorization area and performing checks on the authorization response. // // The variable length params argument provides a mechanism for the caller to provide command handles, command parameters, response // handle pointers and response parameter pointers (in that order), with each group of arguments being separated by the Delimiter // sentinel value. // // Command handles are provided as HandleContext types if they do not require an authorization. For command handles that require an // authorization, they are provided using the ResourceContextWithSession type. This links the ResourceContext to an optional // authorization session. If the authorization value of the TPM entity is required as part of the authorization, this will be obtained // from the supplied ResourceContext. A nil HandleContext will automatically be converted to a handle with the value of HandleNull. // // Command parameters are provided as the go equivalent types for the types defined in the TPM Library Specification. // // Response handles are provided as pointers to Handle values. // // Response parameters are provided as pointers to values of the go equivalent types for the types defined in the TPM Library // Specification. // // If the TPM responds with a warning that indicates the command could not be started and should be retried, this function will // resubmit the command a finite number of times before returning an error. The maximum number of retries can be set via // TPMContext.SetMaxSubmissions. // // The caller can provide additional sessions that aren't associated with a TPM entity (and therefore not used for authorization) via // the sessions parameter, for the purposes of command auditing or session based parameter encryption. // // In addition to returning an error if any marshalling or unmarshalling fails, or if the transmission backend returns an error, // this function will also return an error if the TPM responds with any ResponseCode other than Success. func (t *TPMContext) RunCommandWithResponseCallback(commandCode CommandCode, sessions []SessionContext, responseCb func(), params ...interface{}) error { var commandHandles []HandleContext var commandParams []interface{} var responseHandle *Handle var responseParams []interface{} var sessionParams sessionParams sentinels := 0 for _, param := range params { if param == Delimiter { sentinels++ continue } switch sentinels { case 0: var handle HandleContext switch p := param.(type) { case ResourceContextWithSession: handle = p.Context if err := sessionParams.validateAndAppendAuth(p); err != nil { return fmt.Errorf("cannot process ResourceContextWithSession for command %s at index %d: %v", commandCode, len(commandHandles), err) } case HandleContext: handle = p case nil: default: return fmt.Errorf("cannot process command handle argument for command %s at index %d: invalid type (%s)", commandCode, len(commandHandles), reflect.TypeOf(param)) } if handle == nil { handle = makePermanentContext(HandleNull) } commandHandles = append(commandHandles, handle) case 1: commandParams = append(commandParams, param) case 2: if responseHandle != nil { return errors.New("only one response handle argument can be supplied") } handle, isHandle := param.(*Handle) if !isHandle { return fmt.Errorf("cannot process response handle argument for command %s: invalid type (%s)", commandCode, reflect.TypeOf(param)) } responseHandle = handle case 3: responseParams = append(responseParams, param) } } if err := sessionParams.validateAndAppendExtra(sessions); err != nil { return fmt.Errorf("cannot process non-auth SessionContext parameters for command %s: %v", commandCode, err) } ctx, err := t.runCommandWithoutProcessingAuthResponse(commandCode, &sessionParams, commandHandles, commandParams, responseHandle) if err != nil { return err } if responseCb != nil { responseCb() } return t.processAuthResponse(ctx, responseParams) } // RunCommand is the high-level generic interface for executing the command specified by commandCode. All of the methods on TPMContext // exported by this package that execute commands on the TPM are essentially wrappers around this function. It takes care of // marshalling command handles and command parameters, as well as constructing and marshalling the authorization area and choosing // the correct StructTag value. It takes care of unmarshalling response handles and response parameters, as well as unmarshalling the // response authorization area and performing checks on the authorization response. // // The variable length params argument provides a mechanism for the caller to provide command handles, command parameters, response // handle pointers and response parameter pointers (in that order), with each group of arguments being separated by the Delimiter // sentinel value. // // Command handles are provided as HandleContext types if they do not require an authorization. For command handles that require an // authorization, they are provided using the ResourceContextWithSession type. This links the ResourceContext to an optional // authorization session. If the authorization value of the TPM entity is required as part of the authorization, this will be obtained // from the supplied ResourceContext. A nil HandleContext will automatically be converted to a handle with the value of HandleNull. // // Command parameters are provided as the go equivalent types for the types defined in the TPM Library Specification. // // Response handles are provided as pointers to Handle values. // // Response parameters are provided as pointers to values of the go equivalent types for the types defined in the TPM Library // Specification. // // If the TPM responds with a warning that indicates the command could not be started and should be retried, this function will // resubmit the command a finite number of times before returning an error. The maximum number of retries can be set via // TPMContext.SetMaxSubmissions. // // The caller can provide additional sessions that aren't associated with a TPM entity (and therefore not used for authorization) via // the sessions parameter, for the purposes of command auditing or session based parameter encryption. // // In addition to returning an error if any marshalling or unmarshalling fails, or if the transmission backend returns an error, // this function will also return an error if the TPM responds with any ResponseCode other than Success. func (t *TPMContext) RunCommand(commandCode CommandCode, sessions []SessionContext, params ...interface{}) error { return t.RunCommandWithResponseCallback(commandCode, sessions, nil, params...) } // SetMaxSubmissions sets the maximum number of times that RunCommand will attempt to submit a command before failing with an error. // The default value is 5. func (t *TPMContext) SetMaxSubmissions(max uint) { t.maxSubmissions = max } // InitProperties executes a TPM2_GetCapability command to initialize properties used internally by TPMContext. This is normally done // automatically by functions that require these properties when they are used for the first time, but this function is provided so // that the command can be audited, and so the exclusivity of an audit session can be preserved. func (t *TPMContext) InitProperties(sessions ...SessionContext) error { props, err := t.GetCapabilityTPMProperties(PropertyFixed, CapabilityMaxProperties, sessions...) if err != nil { return err } for _, prop := range props { switch prop.Property { case PropertyInputBuffer: t.maxBufferSize = int(prop.Value) case PropertyMaxDigest: t.maxDigestSize = int(prop.Value) case PropertyNVBufferMax: t.maxNVBufferSize = int(prop.Value) } } if t.maxBufferSize == 0 { t.maxBufferSize = 1024 } if t.maxDigestSize == 0 { return &InvalidResponseError{Command: CommandGetCapability, msg: "missing or invalid TPM_PT_MAX_DIGEST property"} } if t.maxNVBufferSize == 0 { return &InvalidResponseError{Command: CommandGetCapability, msg: "missing or invalid TPM_PT_NV_BUFFER_MAX property"} } t.propertiesInitialized = true return nil } func (t *TPMContext) initPropertiesIfNeeded() error { if t.propertiesInitialized { return nil } return t.InitProperties() } func newTpmContext(tcti TCTI) *TPMContext { r := new(TPMContext) r.tcti = tcti r.permanentResources = make(map[Handle]*permanentContext) r.maxSubmissions = 5 return r } // NewTPMContext creates a new instance of TPMContext, which communicates with the // TPM using the transmission interface provided via the tcti parameter. The // transmission interface must not be nil - it is expected that the caller checks // the error returned from the function that is used to create it. func NewTPMContext(tcti TCTI) *TPMContext { if tcti == nil { panic("nil transmission interface") } t := new(TPMContext) t.tcti = tcti t.permanentResources = make(map[Handle]*permanentContext) t.maxSubmissions = 5 return t } ./github.com/canonical/go-tpm2/types.go0000664000000000000000000000721200000000000016314 0ustar0000000000000000// Copyright 2019 Canonical Ltd. // Licensed under the LGPLv3 with static-linking exception. // See LICENCE file for details. package tpm2 import ( "errors" "github.com/canonical/go-tpm2/mu" ) // TPMManufacturer corresponds to the TPM manufacturer and is returned when querying the value PropertyManufacturer with // TPMContext.GetCapabilityTPMProperties type TPMManufacturer uint32 const ( TPMManufacturerAMD TPMManufacturer = 0x414D4400 // AMD TPMManufacturerATML TPMManufacturer = 0x41544D4C // Atmel TPMManufacturerBRCM TPMManufacturer = 0x4252434D // Broadcom TPMManufacturerHPE TPMManufacturer = 0x48504500 // HPE TPMManufacturerIBM TPMManufacturer = 0x49424d00 // IBM TPMManufacturerIFX TPMManufacturer = 0x49465800 // Infineon TPMManufacturerINTC TPMManufacturer = 0x494E5443 // Intel TPMManufacturerLEN TPMManufacturer = 0x4C454E00 // Lenovo TPMManufacturerMSFT TPMManufacturer = 0x4D534654 // Microsoft TPMManufacturerNSM TPMManufacturer = 0x4E534D20 // National Semiconductor TPMManufacturerNTZ TPMManufacturer = 0x4E545A00 // Nationz TPMManufacturerNTC TPMManufacturer = 0x4E544300 // Nuvoton Technology TPMManufacturerQCOM TPMManufacturer = 0x51434F4D // Qualcomm TPMManufacturerSMSC TPMManufacturer = 0x534D5343 // SMSC TPMManufacturerSTM TPMManufacturer = 0x53544D20 // ST Microelectronics TPMManufacturerSMSN TPMManufacturer = 0x534D534E // Samsung TPMManufacturerSNS TPMManufacturer = 0x534E5300 // Sinosun TPMManufacturerTXN TPMManufacturer = 0x54584E00 // Texas Instruments TPMManufacturerWEC TPMManufacturer = 0x57454300 // Winbond TPMManufacturerROCC TPMManufacturer = 0x524F4343 // Fuzhou Rockchip TPMManufacturerGOOG TPMManufacturer = 0x474F4F47 // Google ) // PCRValues contains a collection of PCR values, keyed by HashAlgorithmId and PCR index. type PCRValues map[HashAlgorithmId]map[int]Digest // SelectionList computes a list of PCR selections corresponding to this set of PCR values. func (v PCRValues) SelectionList() PCRSelectionList { var out PCRSelectionList for h := range v { s := PCRSelection{Hash: h} for p := range v[h] { s.Select = append(s.Select, p) } out = append(out, s) } return out.Sort() } // ToListAndSelection converts this set of PCR values to a list of PCR selections and list of PCR // values, in a form that can be serialized. func (v PCRValues) ToListAndSelection() (pcrs PCRSelectionList, digests DigestList) { pcrs = v.SelectionList() for _, p := range pcrs { for _, s := range p.Select { digests = append(digests, v[p.Hash][s]) } } return } // SetValuesFromListAndSelection sets PCR values from the supplied list of PCR selections and list // of values. func (v PCRValues) SetValuesFromListAndSelection(pcrs PCRSelectionList, digests DigestList) (int, error) { // Copy the selections so that each selection is ordered correctly mu.MustCopyValue(&pcrs, pcrs) i := 0 for _, p := range pcrs { if _, ok := v[p.Hash]; !ok { v[p.Hash] = make(map[int]Digest) } for _, s := range p.Select { if len(digests) == 0 { return 0, errors.New("insufficient digests") } d := digests[0] digests = digests[1:] if len(d) != p.Hash.Size() { return 0, errors.New("incorrect digest size") } v[p.Hash][s] = d i++ } } return i, nil } // SetValue sets the PCR value for the specified PCR and PCR bank. func (v PCRValues) SetValue(alg HashAlgorithmId, pcr int, digest Digest) { if _, ok := v[alg]; !ok { v[alg] = make(map[int]Digest) } v[alg][pcr] = digest } // PublicTemplate exists to allow either Public or PublicDerived structures // to be used as the template value for TPMContext.CreateLoaded. type PublicTemplate interface { ToTemplate() (Template, error) } ./github.com/canonical/go-tpm2/types_algs.go0000664000000000000000000004403000000000000017321 0ustar0000000000000000// Copyright 2019 Canonical Ltd. // Licensed under the LGPLv3 with static-linking exception. // See LICENCE file for details. package tpm2 import ( "reflect" "github.com/canonical/go-tpm2/mu" ) // This file contains types defined in section 11 (Algorithm Parameters // and Structures) in part 2 of the library spec. // 11.1) Symmetric // SymKeyBitsU is a union type that corresponds to the TPMU_SYM_KEY_BITS type // and is used to specify symmetric encryption key sizes. The selector type is // AlgorithmId. Mapping of selector values to fields is as follows: // - AlgorithmAES: Sym // - AlgorithmSM4: Sym // - AlgorithmCamellia: Sym // - AlgorithmXOR: XOR // - AlgorithmNull: none type SymKeyBitsU struct { Sym uint16 XOR HashAlgorithmId } func (b *SymKeyBitsU) Select(selector reflect.Value) interface{} { switch selector.Convert(reflect.TypeOf(AlgorithmId(0))).Interface().(AlgorithmId) { case AlgorithmAES: fallthrough case AlgorithmSM4: fallthrough case AlgorithmCamellia: return &b.Sym case AlgorithmXOR: return &b.XOR case AlgorithmNull: return mu.NilUnionValue default: return nil } } // SymModeU is a union type that corresponds to the TPMU_SYM_MODE type. The selector // type is AlgorithmId. The mapping of selector values to fields is as follows: // - AlgorithmAES: Sym // - AlgorithmSM4: Sym // - AlgorithmCamellia: Sym // - AlgorithmXOR: none // - AlgorithmNull: none type SymModeU struct { Sym SymModeId } func (m *SymModeU) Select(selector reflect.Value) interface{} { switch selector.Convert(reflect.TypeOf(AlgorithmId(0))).Interface().(AlgorithmId) { case AlgorithmAES: fallthrough case AlgorithmSM4: fallthrough case AlgorithmCamellia: return &m.Sym case AlgorithmXOR: fallthrough case AlgorithmNull: return mu.NilUnionValue default: return nil } } // SymDef corresponds to the TPMT_SYM_DEF type, and is used to select the algorithm // used for parameter encryption. type SymDef struct { Algorithm SymAlgorithmId // Symmetric algorithm KeyBits *SymKeyBitsU // Symmetric key size Mode *SymModeU // Symmetric mode } // SymDefObject corresponds to the TPMT_SYM_DEF_OBJECT type, and is used to define an // object's symmetric algorithm. type SymDefObject struct { Algorithm SymObjectAlgorithmId // Symmetric algorithm KeyBits *SymKeyBitsU // Symmetric key size Mode *SymModeU // Symmetric mode } // SymKey corresponds to the TPM2B_SYM_KEY type. type SymKey []byte // SymCipherParams corresponds to the TPMS_SYMCIPHER_PARMS type, and contains the // parameters for a symmetric object. type SymCipherParams struct { Sym SymDefObject } // Label corresponds to the TPM2B_LABEL type. type Label []byte // Derive corresponds to the TPMS_DERIVE type. type Derive struct { Label Label Context Label } // SensitiveCreate corresponds to the TPMS_SENSITIVE_CREATE type and is used to define // the values to be placed in the sensitive area of a created object. type SensitiveCreate struct { UserAuth Auth // Authorization value Data SensitiveData // Secret data } // SensitiveData corresponds to the TPM2B_SENSITIVE_DATA type. type SensitiveData []byte // SchemeHash corresponds to the TPMS_SCHEME_HASH type, and is used for schemes that only // require a hash algorithm to complete their definition. type SchemeHash struct { HashAlg HashAlgorithmId // Hash algorithm used to digest the message } // SchemeECDAA corresponds to the TPMS_SCHEME_ECDAA type. type SchemeECDAA struct { HashAlg HashAlgorithmId // Hash algorithm used to digest the message Count uint16 } // KeyedHashSchemeId corresponds to the TPMI_ALG_KEYEDHASH_SCHEME type type KeyedHashSchemeId AlgorithmId const ( KeyedHashSchemeHMAC KeyedHashSchemeId = KeyedHashSchemeId(AlgorithmHMAC) // TPM_ALG_HMAC KeyedHashSchemeXOR KeyedHashSchemeId = KeyedHashSchemeId(AlgorithmXOR) // TPM_ALG_XOR KeyedHashSchemeNull KeyedHashSchemeId = KeyedHashSchemeId(AlgorithmNull) // TPM_ALG_NULL ) // SchemeHMAC corresponds to the TPMS_SCHEME_HMAC type. type SchemeHMAC SchemeHash // SchemeXOR corresponds to the TPMS_SCHEME_XOR type, and is used to define the XOR encryption // scheme. type SchemeXOR struct { HashAlg HashAlgorithmId // Hash algorithm used to digest the message KDF KDFAlgorithmId // Hash algorithm used for the KDF } // SchemeKeyedHashU is a union type that corresponds to the TPMU_SCHEME_KEYED_HASH type. // The selector type is KeyedHashSchemeId. The mapping of selector values to fields is // as follows: // - KeyedHashSchemeHMAC: HMAC // - KeyedHashSchemeXOR: XOR // - KeyedHashSchemeNull: none type SchemeKeyedHashU struct { HMAC *SchemeHMAC XOR *SchemeXOR } func (d *SchemeKeyedHashU) Select(selector reflect.Value) interface{} { switch selector.Interface().(KeyedHashSchemeId) { case KeyedHashSchemeHMAC: return &d.HMAC case KeyedHashSchemeXOR: return &d.XOR case KeyedHashSchemeNull: return mu.NilUnionValue default: return nil } } // KeyedHashScheme corresponds to the TPMT_KEYEDHASH_SCHEME type. type KeyedHashScheme struct { Scheme KeyedHashSchemeId // Scheme selector Details *SchemeKeyedHashU // Scheme specific parameters } // 11.2 Assymetric // 11.2.1 Signing Schemes type SigSchemeRSASSA SchemeHash type SigSchemeRSAPSS SchemeHash type SigSchemeECDSA SchemeHash type SigSchemeECDAA SchemeECDAA type SigSchemeSM2 SchemeHash type SigSchemeECSchnorr SchemeHash // SigSchemeU is a union type that corresponds to the TPMU_SIG_SCHEME type. The // selector type is SigSchemeId. The mapping of selector value to fields is as follows: // - SigSchemeAlgRSASSA: RSASSA // - SigSchemeAlgRSAPSS: RSAPSS // - SigSchemeAlgECDSA: ECDSA // - SigSchemeAlgECDAA: ECDAA // - SigSchemeAlgSM2: SM2 // - SigSchemeAlgECSchnorr: ECSchnorr // - SigSchemeAlgHMAC: HMAC // - SigSchemeAlgNull: none type SigSchemeU struct { RSASSA *SigSchemeRSASSA RSAPSS *SigSchemeRSAPSS ECDSA *SigSchemeECDSA ECDAA *SigSchemeECDAA SM2 *SigSchemeSM2 ECSchnorr *SigSchemeECSchnorr HMAC *SchemeHMAC } func (s *SigSchemeU) Select(selector reflect.Value) interface{} { switch selector.Interface().(SigSchemeId) { case SigSchemeAlgRSASSA: return &s.RSASSA case SigSchemeAlgRSAPSS: return &s.RSAPSS case SigSchemeAlgECDSA: return &s.ECDSA case SigSchemeAlgECDAA: return &s.ECDAA case SigSchemeAlgSM2: return &s.SM2 case SigSchemeAlgECSchnorr: return &s.ECSchnorr case SigSchemeAlgHMAC: return &s.HMAC case SigSchemeAlgNull: return mu.NilUnionValue default: return nil } } // Any returns the signature scheme associated with scheme as a *SchemeHash. // It panics if the specified scheme is invalid (SigSchemeId.IsValid returns // false), or the appropriate field isn't set. func (s SigSchemeU) Any(scheme SigSchemeId) *SchemeHash { if !scheme.IsValid() { panic("invalid scheme") } switch scheme { case SigSchemeAlgRSASSA: return (*SchemeHash)(&(*s.RSASSA)) case SigSchemeAlgRSAPSS: return (*SchemeHash)(&(*s.RSAPSS)) case SigSchemeAlgECDSA: return (*SchemeHash)(&(*s.ECDSA)) case SigSchemeAlgECDAA: return &SchemeHash{HashAlg: s.ECDAA.HashAlg} case SigSchemeAlgSM2: return (*SchemeHash)(&(*s.SM2)) case SigSchemeAlgECSchnorr: return (*SchemeHash)(&(*s.ECSchnorr)) case SigSchemeAlgHMAC: return (*SchemeHash)(&(*s.HMAC)) default: panic("not reached") } } // SigScheme corresponds to the TPMT_SIG_SCHEME type. type SigScheme struct { Scheme SigSchemeId // Scheme selector Details *SigSchemeU // Scheme specific parameters } // 11.2.2 Encryption Schemes type EncSchemeRSAES Empty type EncSchemeOAEP SchemeHash type KeySchemeECDH SchemeHash type KeySchemeECMQV SchemeHash // 11.2.3 Key Derivation Schemes type SchemeMGF1 SchemeHash type SchemeKDF1_SP800_56A SchemeHash type SchemeKDF2 SchemeHash type SchemeKDF1_SP800_108 SchemeHash // KDFSchemeU is a union type that corresponds to the TPMU_KDF_SCHEME // type. The selector type is KDFAlgorithmId. The mapping of selector // value to field is as follows: // - KDFAlgorithmMGF1: MGF1 // - KDFAlgorithmKDF1_SP800_56A: KDF1_SP800_56A // - KDFAlgorithmKDF2: KDF2 // - KDFAlgorithmKDF1_SP800_108: KDF1_SP800_108 // - KDFAlgorithmNull: none type KDFSchemeU struct { MGF1 *SchemeMGF1 KDF1_SP800_56A *SchemeKDF1_SP800_56A KDF2 *SchemeKDF2 KDF1_SP800_108 *SchemeKDF1_SP800_108 } func (s *KDFSchemeU) Select(selector reflect.Value) interface{} { switch selector.Interface().(KDFAlgorithmId) { case KDFAlgorithmMGF1: return &s.MGF1 case KDFAlgorithmKDF1_SP800_56A: return &s.KDF1_SP800_56A case KDFAlgorithmKDF2: return &s.KDF2 case KDFAlgorithmKDF1_SP800_108: return &s.KDF1_SP800_108 case KDFAlgorithmNull: return mu.NilUnionValue default: return nil } } // KDFScheme corresponds to the TPMT_KDF_SCHEME type. type KDFScheme struct { Scheme KDFAlgorithmId // Scheme selector Details *KDFSchemeU // Scheme specific parameters. } // AsymSchemeId corresponds to the TPMI_ALG_ASYM_SCHEME type type AsymSchemeId AlgorithmId // IsValid determines if the scheme is a valid asymmetric scheme. func (s AsymSchemeId) IsValid() bool { switch s { case AsymSchemeRSASSA: case AsymSchemeRSAES: case AsymSchemeRSAPSS: case AsymSchemeOAEP: case AsymSchemeECDSA: case AsymSchemeECDH: case AsymSchemeECDAA: case AsymSchemeSM2: case AsymSchemeECSchnorr: case AsymSchemeECMQV: default: return false } return true } // HasDigest determines if the asymmetric scheme is associated with // a digest algorithm. func (s AsymSchemeId) HasDigest() bool { switch s { case AsymSchemeRSASSA: case AsymSchemeRSAPSS: case AsymSchemeOAEP: case AsymSchemeECDSA: case AsymSchemeECDH: case AsymSchemeECDAA: case AsymSchemeSM2: case AsymSchemeECSchnorr: case AsymSchemeECMQV: default: return false } return true } const ( AsymSchemeNull AsymSchemeId = AsymSchemeId(AlgorithmNull) // TPM_ALG_NULL AsymSchemeRSASSA AsymSchemeId = AsymSchemeId(AlgorithmRSASSA) // TPM_ALG_RSASSA AsymSchemeRSAES AsymSchemeId = AsymSchemeId(AlgorithmRSAES) // TPM_ALG_RSAES AsymSchemeRSAPSS AsymSchemeId = AsymSchemeId(AlgorithmRSAPSS) // TPM_ALG_RSAPSS AsymSchemeOAEP AsymSchemeId = AsymSchemeId(AlgorithmOAEP) // TPM_ALG_OAEP AsymSchemeECDSA AsymSchemeId = AsymSchemeId(AlgorithmECDSA) // TPM_ALG_ECDSA AsymSchemeECDH AsymSchemeId = AsymSchemeId(AlgorithmECDH) // TPM_ALG_ECDH AsymSchemeECDAA AsymSchemeId = AsymSchemeId(AlgorithmECDAA) // TPM_ALG_ECDAA AsymSchemeSM2 AsymSchemeId = AsymSchemeId(AlgorithmSM2) // TPM_ALG_SM2 AsymSchemeECSchnorr AsymSchemeId = AsymSchemeId(AlgorithmECSchnorr) // TPM_ALG_ECSCHNORR AsymSchemeECMQV AsymSchemeId = AsymSchemeId(AlgorithmECMQV) // TPM_ALG_ECMQV ) // AsymSchemeU is a union type that corresponds to the TPMU_ASYM_SCHEME type. The // selector type is AsymSchemeId. The mapping of selector values to fields is as follows: // - AsymSchemeRSASSA: RSASSA // - AsymSchemeRSAES: RSAES // - AsymSchemeRSAPSS: RSAPSS // - AsymSchemeOAEP: OAEP // - AsymSchemeECDSA: ECDSA // - AsymSchemeECDH: ECDH // - AsymSchemeECDAA: ECDAA // - AsymSchemeSM2: SM2 // - AsymSchemeECSchnorr: ECSchnorr // - AsymSchemeECMQV: ECMQV // - AsymSchemeNull: none type AsymSchemeU struct { RSASSA *SigSchemeRSASSA RSAES *EncSchemeRSAES RSAPSS *SigSchemeRSAPSS OAEP *EncSchemeOAEP ECDSA *SigSchemeECDSA ECDH *KeySchemeECDH ECDAA *SigSchemeECDAA SM2 *SigSchemeSM2 ECSchnorr *SigSchemeECSchnorr ECMQV *KeySchemeECMQV } func (s *AsymSchemeU) Select(selector reflect.Value) interface{} { switch selector.Convert(reflect.TypeOf(AsymSchemeId(0))).Interface().(AsymSchemeId) { case AsymSchemeRSASSA: return &s.RSASSA case AsymSchemeRSAES: return &s.RSAES case AsymSchemeRSAPSS: return &s.RSAPSS case AsymSchemeOAEP: return &s.OAEP case AsymSchemeECDSA: return &s.ECDSA case AsymSchemeECDH: return &s.ECDH case AsymSchemeECDAA: return &s.ECDAA case AsymSchemeSM2: return &s.SM2 case AsymSchemeECSchnorr: return &s.ECSchnorr case AsymSchemeECMQV: return &s.ECMQV case AsymSchemeNull: return mu.NilUnionValue default: return nil } } // Any returns the asymmetric scheme associated with scheme as a *SchemeHash. // It panics if the specified scheme does not have an associated digest algorithm // (AsymSchemeId.HasDigest returns false), or if the appropriate field isn't set. func (s AsymSchemeU) Any(scheme AsymSchemeId) *SchemeHash { if !scheme.HasDigest() { panic("invalid asymmetric scheme") } switch scheme { case AsymSchemeRSASSA: return (*SchemeHash)(&(*s.RSASSA)) case AsymSchemeRSAPSS: return (*SchemeHash)(&(*s.RSAPSS)) case AsymSchemeOAEP: return (*SchemeHash)(&(*s.OAEP)) case AsymSchemeECDSA: return (*SchemeHash)(&(*s.ECDSA)) case AsymSchemeECDH: return (*SchemeHash)(&(*s.ECDH)) case AsymSchemeECDAA: return &SchemeHash{HashAlg: s.ECDAA.HashAlg} case AsymSchemeSM2: return (*SchemeHash)(&(*s.SM2)) case AsymSchemeECSchnorr: return (*SchemeHash)(&(*s.ECSchnorr)) case AsymSchemeECMQV: return (*SchemeHash)(&(*s.ECMQV)) default: panic("not reached") } } // AsymScheme corresponds to the TPMT_ASYM_SCHEME type. type AsymScheme struct { Scheme AsymSchemeId // Scheme selector Details *AsymSchemeU // Scheme specific parameters } // 11.2.4 RSA // RSASchemeId corresponds to the TPMI_ALG_RSA_SCHEME type. type RSASchemeId AsymSchemeId const ( RSASchemeNull RSASchemeId = RSASchemeId(AlgorithmNull) // TPM_ALG_NULL RSASchemeRSASSA RSASchemeId = RSASchemeId(AlgorithmRSASSA) // TPM_ALG_RSASSA RSASchemeRSAES RSASchemeId = RSASchemeId(AlgorithmRSAES) // TPM_ALG_RSAES RSASchemeRSAPSS RSASchemeId = RSASchemeId(AlgorithmRSAPSS) // TPM_ALG_RSAPSS RSASchemeOAEP RSASchemeId = RSASchemeId(AlgorithmOAEP) // TPM_ALG_OAEP ) // RSAScheme corresponds to the TPMT_RSA_SCHEME type. type RSAScheme struct { Scheme RSASchemeId // Scheme selector Details *AsymSchemeU // Scheme specific parameters. } // PublicKeyRSA corresponds to the TPM2B_PUBLIC_KEY_RSA type. type PublicKeyRSA []byte // PrivateKeyRSA corresponds to the TPM2B_PRIVATE_KEY_RSA type. type PrivateKeyRSA []byte // 11.2.5 ECC // ECCParameter corresponds to the TPM2B_ECC_PARAMETER type. type ECCParameter []byte // ECCPoint corresponds to the TPMS_ECC_POINT type, and contains the coordinates // that define an ECC point. type ECCPoint struct { X ECCParameter // X coordinate Y ECCParameter // Y coordinate } // ECCSchemeId corresponds to the TPMI_ALG_ECC_SCHEME type. type ECCSchemeId AsymSchemeId const ( ECCSchemeNull ECCSchemeId = ECCSchemeId(AlgorithmNull) // TPM_ALG_NULL ECCSchemeECDSA ECCSchemeId = ECCSchemeId(AlgorithmECDSA) // TPM_ALG_ECDSA ECCSchemeECDH ECCSchemeId = ECCSchemeId(AlgorithmECDH) // TPM_ALG_ECDH ECCSchemeECDAA ECCSchemeId = ECCSchemeId(AlgorithmECDAA) // TPM_ALG_ECDAA ECCSchemeSM2 ECCSchemeId = ECCSchemeId(AlgorithmSM2) // TPM_ALG_SM2 ECCSchemeECSchnorr ECCSchemeId = ECCSchemeId(AlgorithmECSchnorr) // TPM_ALG_ECSCHNORR ECCSchemeECMQV ECCSchemeId = ECCSchemeId(AlgorithmECMQV) // TPM_ALG_ECMQV ) // ECCScheme corresponds to the TPMT_ECC_SCHEME type. type ECCScheme struct { Scheme ECCSchemeId // Scheme selector Details *AsymSchemeU // Scheme specific parameters. } // 11.3 Signatures // SignatureRSA corresponds to the TPMS_SIGNATURE_RSA type. type SignatureRSA struct { Hash HashAlgorithmId // Hash algorithm used to digest the message Sig PublicKeyRSA // Signature, which is the same size as the public key } // SignatureECC corresponds to the TPMS_SIGNATURE_ECC type. type SignatureECC struct { Hash HashAlgorithmId // Hash is the digest algorithm used in the signature process SignatureR ECCParameter SignatureS ECCParameter } type SignatureRSASSA SignatureRSA type SignatureRSAPSS SignatureRSA type SignatureECDSA SignatureECC type SignatureECDAA SignatureECC type SignatureSM2 SignatureECC type SignatureECSchnorr SignatureECC // SignatureU is a union type that corresponds to TPMU_SIGNATURE. The selector // type is SigSchemeId. The mapping of selector values to fields is as follows: // - SigSchemeAlgRSASSA: RSASSA // - SigSchemeAlgRSAPSS: RSAPSS // - SigSchemeAlgECDSA: ECDSA // - SigSchemeAlgECDAA: ECDAA // - SigSchemeAlgSM2: SM2 // - SigSchemeAlgECSchnorr: ECSchnorr // - SigSchemeAlgHMAC: HMAC // - SigSchemeAlgNull: none type SignatureU struct { RSASSA *SignatureRSASSA RSAPSS *SignatureRSAPSS ECDSA *SignatureECDSA ECDAA *SignatureECDAA SM2 *SignatureSM2 ECSchnorr *SignatureECSchnorr HMAC *TaggedHash } func (s *SignatureU) Select(selector reflect.Value) interface{} { switch selector.Interface().(SigSchemeId) { case SigSchemeAlgRSASSA: return &s.RSASSA case SigSchemeAlgRSAPSS: return &s.RSAPSS case SigSchemeAlgECDSA: return &s.ECDSA case SigSchemeAlgECDAA: return &s.ECDAA case SigSchemeAlgSM2: return &s.SM2 case SigSchemeAlgECSchnorr: return &s.ECSchnorr case SigSchemeAlgHMAC: return &s.HMAC case SigSchemeAlgNull: return mu.NilUnionValue default: return nil } } // Any returns the signature associated with scheme as a *SchemeHash. It // panics if scheme is SigSchemeAlgNull or the appropriate field isn't // set. func (s SignatureU) Any(scheme SigSchemeId) *SchemeHash { if !scheme.IsValid() { panic("invalid signature scheme") } switch scheme { case SigSchemeAlgRSASSA: return &SchemeHash{HashAlg: s.RSASSA.Hash} case SigSchemeAlgRSAPSS: return &SchemeHash{HashAlg: s.RSAPSS.Hash} case SigSchemeAlgECDSA: return &SchemeHash{HashAlg: s.ECDSA.Hash} case SigSchemeAlgECDAA: return &SchemeHash{HashAlg: s.ECDAA.Hash} case SigSchemeAlgSM2: return &SchemeHash{HashAlg: s.SM2.Hash} case SigSchemeAlgECSchnorr: return &SchemeHash{HashAlg: s.ECSchnorr.Hash} case SigSchemeAlgHMAC: return &SchemeHash{HashAlg: s.HMAC.HashAlg} default: panic("not reached") } } // Signature corresponds to the TPMT_SIGNATURE type. It is returned by the attestation // commands, and is a parameter for TPMContext.VerifySignature and TPMContext.PolicySigned. type Signature struct { SigAlg SigSchemeId // Signature algorithm Signature *SignatureU // Actual signature } // 11.4) Key/Secret Exchange // EncryptedSecret corresponds to the TPM2B_ENCRYPTED_SECRET type. type EncryptedSecret []byte ./github.com/canonical/go-tpm2/types_attributes.go0000664000000000000000000001702100000000000020561 0ustar0000000000000000// Copyright 2019 Canonical Ltd. // Licensed under the LGPLv3 with static-linking exception. // See LICENCE file for details. package tpm2 // This file contains types defined in section 8 (Attributes) in // part 2 of the library spec. // AlgorithmAttributes corresponds to the TPMA_ALGORITHM type and // represents the attributes for an algorithm. type AlgorithmAttributes uint32 const ( AttrAsymmetric AlgorithmAttributes = 1 << 0 AttrSymmetric AlgorithmAttributes = 1 << 1 AttrHash AlgorithmAttributes = 1 << 2 AttrObject AlgorithmAttributes = 1 << 3 AttrSigning AlgorithmAttributes = 1 << 8 AttrEncrypting AlgorithmAttributes = 1 << 9 AttrMethod AlgorithmAttributes = 1 << 10 ) // ObjectAttributes corresponds to the TPMA_OBJECT type, and represents // the attributes for an object. type ObjectAttributes uint32 const ( AttrFixedTPM ObjectAttributes = 1 << 1 // fixedTPM AttrStClear ObjectAttributes = 1 << 2 // stClear AttrFixedParent ObjectAttributes = 1 << 4 // fixedParent AttrSensitiveDataOrigin ObjectAttributes = 1 << 5 // sensitiveDataOrigin AttrUserWithAuth ObjectAttributes = 1 << 6 // userWithAuth AttrAdminWithPolicy ObjectAttributes = 1 << 7 // adminWithPolicy AttrNoDA ObjectAttributes = 1 << 10 // noDA AttrEncryptedDuplication ObjectAttributes = 1 << 11 // encryptedDuplication AttrRestricted ObjectAttributes = 1 << 16 // restricted AttrDecrypt ObjectAttributes = 1 << 17 // decrypt AttrSign ObjectAttributes = 1 << 18 // sign ) // SessionAttributes corresponds to the TPMA_SESSION type, and represents // the attributes for a session. type SessionAttributes uint8 func (a SessionAttributes) canonicalize() SessionAttributes { if a&AttrAuditExclusive > 0 { a |= AttrAudit } if a&AttrAuditReset > 0 { a |= AttrAudit } return a } const ( // AttrContinueSession corresponds to continueSession and specifies that the session should not be flushed // from the TPM after it is used. If a session is used without this flag, it will be flushed from the TPM // after the command completes successfully. In this case, the HandleContext associated with the session // will be invalidated. AttrContinueSession SessionAttributes = 1 << iota // AttrAuditExclusive corresponds to auditExclusive and indicates that the command should only be executed // if the session is exclusive at the start of the command. A session becomes exclusive when it is used for // auditing for the first time, or if the AttrAuditReset attribute is provided. A session will remain // exclusive until the TPM executes any command where the exclusive session isn't used for auditing, if // that command allows for audit sessions to be provided. // // Setting this on SessionContext implies AttrAudit. AttrAuditExclusive // AttrAuditReset corresponds to auditReset and indicates that the audit digest of the session should be reset. // The session will subsequently become exclusive. A session will remain exclusive until the TPM executes any // command where the exclusive session isn't used for auditing, if that command allows for audit sessions to be // provided. // // Setting this on SessionContext implies AttrAudit. AttrAuditReset // AttrCommandEncrypt corresponds to decrypt and specifies that the session should be used for encryption of the // first command parameter before being sent from the host to the TPM. This can only be used for parameters that // have types corresponding to TPM2B prefixed TCG types, and requires a session that was configured with a valid // symmetric algorithm via the symmetric argument of TPMContext.StartAuthSession. AttrCommandEncrypt SessionAttributes = 1 << (iota + 2) // AttrResponseEncrypt corresponds to encrypt and specifies that the session should be used for encryption of the // first response parameter before being sent from the TPM to the host. This can only be used for parameters that // have types corresponding to TPM2B prefixed TCG types, and requires a session that was configured with a valid // symmetric algorithm via the symmetric argument of TPMContext.StartAuthSession. This package automatically // decrypts the received encrypted response parameter. AttrResponseEncrypt // AttrAudit corresponds to audit and indicates that the session should be used for auditing. If this is the first // time that the session is used for auditing, then this attribute will result in the session becoming exclusive. // A session will remain exclusive until the TPM executes any command where the exclusive session isn't used for // auditing, if that command allows for audit sessions to be provided. AttrAudit ) // Locality corresponds to the TPMA_LOCALITY type. type Locality uint8 const ( LocalityZero Locality = 0 // TPM_LOC_ZERO LocalityOne Locality = 1 // TPM_LOC_ONE LocalityTwo Locality = 2 // TPM_LOC_TWO LocalityThree Locality = 3 // TPM_LOC_THREE LocalityFour Locality = 4 // TPM_LOC_FOUR ) // PermanentAttributes corresponds to the TPMA_PERMANENT type and is returned // when querying the value of PropertyPermanent. type PermanentAttributes uint32 const ( AttrOwnerAuthSet PermanentAttributes = 1 << 0 // ownerAuthSet AttrEndorsementAuthSet PermanentAttributes = 1 << 1 // endorsementAuthSet AttrLockoutAuthSet PermanentAttributes = 1 << 2 // lockoutAuthSet AttrDisableClear PermanentAttributes = 1 << 8 // disableClear AttrInLockout PermanentAttributes = 1 << 9 // inLockout AttrTPMGeneratedEPS PermanentAttributes = 1 << 10 // tpmGeneratedEPS ) // StatupClearAttributes corresponds to the TPMA_STARTUP_CLEAR type and // is used to report details of properties that reset after a Startup(CLEAR). // It is returned when querying the value of PropertyStartupClear. type StartupClearAttributes uint32 const ( AttrPhEnable StartupClearAttributes = 1 << 0 // phEnable AttrShEnable StartupClearAttributes = 1 << 1 // shEnable AttrEhEnable StartupClearAttributes = 1 << 2 // ehEnable AttrPhEnableNV StartupClearAttributes = 1 << 3 // phEnableNV AttrOrderly StartupClearAttributes = 1 << 31 // orderly ) // MemoryAttributes corresponds to the TPMA_MEMORY type and is used to // report details about memory management. It is returned when querying // the value of PropertyMemory. type MemoryAttributes uint32 const ( AttrSharedRAM MemoryAttributes = 1 << 0 // sharedRAM AttrSharedNV MemoryAttributes = 1 << 1 // sharedNV AttrObjectCopiedToRAM MemoryAttributes = 1 << 2 // objectCopiedToRam ) // CommandAttributes corresponds to the TPMA_CC type and represents the // attributes of a command. It also encodes the command code to which these // attributes belong, and the number of command handles for the command. type CommandAttributes uint32 // CommandCode returns the command code that a set of attributes belongs to. func (a CommandAttributes) CommandCode() CommandCode { return CommandCode(a & (AttrV | 0xffff)) } // NumberOfCommandHandles returns the number of command handles for the // command that a set of attributes belong to. func (a CommandAttributes) NumberOfCommandHandles() int { return int((a & 0x0e000000) >> 25) } const ( AttrNV CommandAttributes = 1 << 22 AttrExtensive CommandAttributes = 1 << 23 AttrFlushed CommandAttributes = 1 << 24 AttrRHandle CommandAttributes = 1 << 28 AttrV CommandAttributes = 1 << 29 ) // ModeAttributes correspnds to TPMA_MODES and is returned when querying // the value of PropertyModes. type ModeAttributes uint32 const ( ModeFIPS140_2 ModeAttributes = 1 << 0 // FIPS_140_2 ) ./github.com/canonical/go-tpm2/types_constants.go0000664000000000000000000005766700000000000020433 0ustar0000000000000000// Copyright 2019 Canonical Ltd. // Licensed under the LGPLv3 with static-linking exception. // See LICENCE file for details. package tpm2 import ( "crypto/elliptic" ) // This file contains types defined in section 6 (Contants) in // part 2 of the library spec. // TPMGenerated corresponds to the TPM_GENERATED type. type TPMGenerated uint32 const ( TPMGeneratedValue TPMGenerated = 0xff544347 // TPM_GENERATED_VALUE ) // AlgorithmId corresponds to the TPM_ALG_ID type. type AlgorithmId uint16 const ( AlgorithmError AlgorithmId = 0x0000 // TPM_ALG_ERROR AlgorithmRSA AlgorithmId = 0x0001 // TPM_ALG_RSA AlgorithmTDES AlgorithmId = 0x0003 // TPM_ALG_TDES AlgorithmSHA1 AlgorithmId = 0x0004 // TPM_ALG_SHA1 AlgorithmHMAC AlgorithmId = 0x0005 // TPM_ALG_HMAC AlgorithmAES AlgorithmId = 0x0006 // TPM_ALG_AES AlgorithmMGF1 AlgorithmId = 0x0007 // TPM_ALG_MGF1 AlgorithmKeyedHash AlgorithmId = 0x0008 // TPM_ALG_KEYEDHASH AlgorithmXOR AlgorithmId = 0x000a // TPM_ALG_XOR AlgorithmSHA256 AlgorithmId = 0x000b // TPM_ALG_SHA256 AlgorithmSHA384 AlgorithmId = 0x000c // TPM_ALG_SHA384 AlgorithmSHA512 AlgorithmId = 0x000d // TPM_ALG_SHA512 AlgorithmNull AlgorithmId = 0x0010 // TPM_ALG_NULL AlgorithmSM3_256 AlgorithmId = 0x0012 // TPM_ALG_SM3_256 AlgorithmSM4 AlgorithmId = 0x0013 // TPM_ALG_SM4 AlgorithmRSASSA AlgorithmId = 0x0014 // TPM_ALG_RSASSA AlgorithmRSAES AlgorithmId = 0x0015 // TPM_ALG_RSAES AlgorithmRSAPSS AlgorithmId = 0x0016 // TPM_ALG_RSAPSS AlgorithmOAEP AlgorithmId = 0x0017 // TPM_ALG_OAEP AlgorithmECDSA AlgorithmId = 0x0018 // TPM_ALG_ECDSA AlgorithmECDH AlgorithmId = 0x0019 // TPM_ALG_ECDH AlgorithmECDAA AlgorithmId = 0x001a // TPM_ALG_ECDAA AlgorithmSM2 AlgorithmId = 0x001b // TPM_ALG_SM2 AlgorithmECSchnorr AlgorithmId = 0x001c // TPM_ALG_ECSCHNORR AlgorithmECMQV AlgorithmId = 0x001d // TPM_ALG_ECMQV AlgorithmKDF1_SP800_56A AlgorithmId = 0x0020 // TPM_ALG_KDF1_SP800_56A AlgorithmKDF2 AlgorithmId = 0x0021 // TPM_ALG_KDF2 AlgorithmKDF1_SP800_108 AlgorithmId = 0x0022 // TPM_ALG_KDF1_SP800_108 AlgorithmECC AlgorithmId = 0x0023 // TPM_ALG_ECC AlgorithmSymCipher AlgorithmId = 0x0025 // TPM_ALG_SYMCIPHER AlgorithmCamellia AlgorithmId = 0x0026 // TPM_ALG_CAMELLIA AlgorithmSHA3_256 AlgorithmId = 0x0027 // TPM_ALG_SHA3_256 AlgorithmSHA3_384 AlgorithmId = 0x0028 // TPM_ALG_SHA3_384 AlgorithmSHA3_512 AlgorithmId = 0x0029 // TPM_ALG_SHA3_512 AlgorithmCTR AlgorithmId = 0x0040 // TPM_ALG_CTR AlgorithmOFB AlgorithmId = 0x0041 // TPM_ALG_OFB AlgorithmCBC AlgorithmId = 0x0042 // TPM_ALG_CBC AlgorithmCFB AlgorithmId = 0x0043 // TPM_ALG_CFB AlgorithmECB AlgorithmId = 0x0044 // TPM_ALG_ECB AlgorithmFirst AlgorithmId = AlgorithmRSA ) // ECCCurve corresponds to the TPM_ECC_CURVE type. type ECCCurve uint16 // GoCurve returns the equivalent elliptic.Curve for this ECC curve. func (c ECCCurve) GoCurve() elliptic.Curve { return eccCurves[c] } const ( ECCCurveNIST_P192 ECCCurve = 0x0001 // TPM_ECC_NIST_P192 ECCCurveNIST_P224 ECCCurve = 0x0002 // TPM_ECC_NIST_P224 ECCCurveNIST_P256 ECCCurve = 0x0003 // TPM_ECC_NIST_P256 ECCCurveNIST_P384 ECCCurve = 0x0004 // TPM_ECC_NIST_P384 ECCCurveNIST_P521 ECCCurve = 0x0005 // TPM_ECC_NIST_P521 ECCCurveBN_P256 ECCCurve = 0x0010 // TPM_ECC_BN_P256 ECCCurveBN_P638 ECCCurve = 0x0011 // TPM_ECC_BN_P638 ECCCurveSM2_P256 ECCCurve = 0x0020 // TPM_ECC_SM2_P256 ECCCurveFirst ECCCurve = ECCCurveNIST_P192 ) // CommandCode corresponds to the TPM_CC type. type CommandCode uint32 const ( CommandFirst CommandCode = 0x0000011A CommandNVUndefineSpaceSpecial CommandCode = 0x0000011F // TPM_CC_NV_UndefineSpaceSpecial CommandEvictControl CommandCode = 0x00000120 // TPM_CC_EvictControl CommandHierarchyControl CommandCode = 0x00000121 // TPM_CC_HierarchyControl CommandNVUndefineSpace CommandCode = 0x00000122 // TPM_CC_NV_UndefineSpace CommandClear CommandCode = 0x00000126 // TPM_CC_Clear CommandClearControl CommandCode = 0x00000127 // TPM_CC_ClearControl CommandClockSet CommandCode = 0x00000128 // TPM_CC_ClockSet CommandHierarchyChangeAuth CommandCode = 0x00000129 // TPM_CC_HierarchyChangeAuth CommandNVDefineSpace CommandCode = 0x0000012A // TPM_CC_NV_DefineSpace CommandPCRAllocate CommandCode = 0x0000012B // TPM_CC_PCR_Allocate CommandSetPrimaryPolicy CommandCode = 0x0000012E // TPM_CC_SetPrimaryPolicy CommandClockRateAdjust CommandCode = 0x00000130 // TPM_CC_ClockRateAdjust CommandCreatePrimary CommandCode = 0x00000131 // TPM_CC_CreatePrimary CommandNVGlobalWriteLock CommandCode = 0x00000132 // TPM_CC_NV_GlobalWriteLock CommandGetCommandAuditDigest CommandCode = 0x00000133 // TPM_CC_GetCommandAuditDigest CommandNVIncrement CommandCode = 0x00000134 // TPM_CC_NV_Increment CommandNVSetBits CommandCode = 0x00000135 // TPM_CC_NV_SetBits CommandNVExtend CommandCode = 0x00000136 // TPM_CC_NV_Extend CommandNVWrite CommandCode = 0x00000137 // TPM_CC_NV_Write CommandNVWriteLock CommandCode = 0x00000138 // TPM_CC_NV_WriteLock CommandDictionaryAttackLockReset CommandCode = 0x00000139 // TPM_CC_DictionaryAttackLockReset CommandDictionaryAttackParameters CommandCode = 0x0000013A // TPM_CC_DictionaryAttackParameters CommandNVChangeAuth CommandCode = 0x0000013B // TPM_CC_NV_ChangeAuth CommandPCREvent CommandCode = 0x0000013C // TPM_CC_PCR_Event CommandPCRReset CommandCode = 0x0000013D // TPM_CC_PCR_Reset CommandSequenceComplete CommandCode = 0x0000013E // TPM_CC_SequenceComplete CommandSetCommandCodeAuditStatus CommandCode = 0x00000140 // TPM_CC_SetCommandCodeAuditStatus CommandIncrementalSelfTest CommandCode = 0x00000142 // TPM_CC_IncrementalSelfTest CommandSelfTest CommandCode = 0x00000143 // TPM_CC_SelfTest CommandStartup CommandCode = 0x00000144 // TPM_CC_Startup CommandShutdown CommandCode = 0x00000145 // TPM_CC_Shutdown CommandStirRandom CommandCode = 0x00000146 // TPM_CC_StirRandom CommandActivateCredential CommandCode = 0x00000147 // TPM_CC_ActivateCredential CommandCertify CommandCode = 0x00000148 // TPM_CC_Certify CommandPolicyNV CommandCode = 0x00000149 // TPM_CC_PolicyNV CommandCertifyCreation CommandCode = 0x0000014A // TPM_CC_CertifyCreation CommandDuplicate CommandCode = 0x0000014B // TPM_CC_Duplicate CommandGetTime CommandCode = 0x0000014C // TPM_CC_GetTime CommandGetSessionAuditDigest CommandCode = 0x0000014D // TPM_CC_GetSessionAuditDigest CommandNVRead CommandCode = 0x0000014E // TPM_CC_NV_Read CommandNVReadLock CommandCode = 0x0000014F // TPM_CC_NV_ReadLock CommandObjectChangeAuth CommandCode = 0x00000150 // TPM_CC_ObjectChangeAuth CommandPolicySecret CommandCode = 0x00000151 // TPM_CC_PolicySecret CommandCreate CommandCode = 0x00000153 // TPM_CC_Create CommandECDHZGen CommandCode = 0x00000154 // TPM_CC_ECDH_ZGen CommandHMAC CommandCode = 0x00000155 // TPM_CC_HMAC CommandImport CommandCode = 0x00000156 // TPM_CC_Import CommandLoad CommandCode = 0x00000157 // TPM_CC_Load CommandQuote CommandCode = 0x00000158 // TPM_CC_Quote CommandRSADecrypt CommandCode = 0x00000159 // TPM_CC_RSA_Decrypt CommandHMACStart CommandCode = 0x0000015B // TPM_CC_HMAC_Start CommandSequenceUpdate CommandCode = 0x0000015C // TPM_CC_SequenceUpdate CommandSign CommandCode = 0x0000015D // TPM_CC_Sign CommandUnseal CommandCode = 0x0000015E // TPM_CC_Unseal CommandPolicySigned CommandCode = 0x00000160 // TPM_CC_PolicySigned CommandContextLoad CommandCode = 0x00000161 // TPM_CC_ContextLoad CommandContextSave CommandCode = 0x00000162 // TPM_CC_ContextSave CommandECDHKeyGen CommandCode = 0x00000163 // TPM_CC_ECDH_KeyGen CommandFlushContext CommandCode = 0x00000165 // TPM_CC_FlushContext CommandLoadExternal CommandCode = 0x00000167 // TPM_CC_LoadExternal CommandMakeCredential CommandCode = 0x00000168 // TPM_CC_MakeCredential CommandNVReadPublic CommandCode = 0x00000169 // TPM_CC_NV_ReadPublic CommandPolicyAuthorize CommandCode = 0x0000016A // TPM_CC_PolicyAuthorize CommandPolicyAuthValue CommandCode = 0x0000016B // TPM_CC_PolicyAuthValue CommandPolicyCommandCode CommandCode = 0x0000016C // TPM_CC_PolicyCommandCode CommandPolicyCounterTimer CommandCode = 0x0000016D // TPM_CC_PolicyCounterTimer CommandPolicyCpHash CommandCode = 0x0000016E // TPM_CC_PolicyCpHash CommandPolicyLocality CommandCode = 0x0000016F // TPM_CC_PolicyLocality CommandPolicyNameHash CommandCode = 0x00000170 // TPM_CC_PolicyNameHash CommandPolicyOR CommandCode = 0x00000171 // TPM_CC_PolicyOR CommandPolicyTicket CommandCode = 0x00000172 // TPM_CC_PolicyTicket CommandReadPublic CommandCode = 0x00000173 // TPM_CC_ReadPublic CommandRSAEncrypt CommandCode = 0x00000174 // TPM_CC_RSA_Encrypt CommandStartAuthSession CommandCode = 0x00000176 // TPM_CC_StartAuthSession CommandVerifySignature CommandCode = 0x00000177 // TPM_CC_VerifySignature CommandECCParameters CommandCode = 0x00000178 // TPM_CC_ECC_Parameters CommandGetCapability CommandCode = 0x0000017A // TPM_CC_GetCapability CommandGetRandom CommandCode = 0x0000017B // TPM_CC_GetRandom CommandGetTestResult CommandCode = 0x0000017C // TPM_CC_GetTestResult CommandHash CommandCode = 0x0000017D // TPM_CC_Hash CommandPCRRead CommandCode = 0x0000017E // TPM_CC_PCR_Read CommandPolicyPCR CommandCode = 0x0000017F // TPM_CC_PolicyPCR CommandPolicyRestart CommandCode = 0x00000180 // TPM_CC_PolicyRestart CommandReadClock CommandCode = 0x00000181 // TPM_CC_ReadClock CommandPCRExtend CommandCode = 0x00000182 // TPM_CC_PCR_Extend CommandNVCertify CommandCode = 0x00000184 // TPM_CC_NV_Certify CommandEventSequenceComplete CommandCode = 0x00000185 // TPM_CC_EventSequenceComplete CommandHashSequenceStart CommandCode = 0x00000186 // TPM_CC_HashSequenceStart CommandPolicyDuplicationSelect CommandCode = 0x00000188 // TPM_CC_PolicyDuplicationSelect CommandPolicyGetDigest CommandCode = 0x00000189 // TPM_CC_PolicyGetDigest CommandTestParms CommandCode = 0x0000018A // TPM_CC_TestParms CommandCommit CommandCode = 0x0000018B // TPM_CC_Commit CommandPolicyPassword CommandCode = 0x0000018C // TPM_CC_PolicyPassword CommandPolicyNvWritten CommandCode = 0x0000018F // TPM_CC_PolicyNvWritten CommandPolicyTemplate CommandCode = 0x00000190 // TPM_CC_PolicyTemplate CommandCreateLoaded CommandCode = 0x00000191 // TPM_CC_CreateLoaded CommandPolicyAuthorizeNV CommandCode = 0x00000192 // TPM_CC_PolicyAuthorizeNV ) // ResponseCode corresponds to the TPM_RC type. type ResponseCode uint32 const ( // The lower 7-bits of format-zero error codes are the error number. responseCodeE0 ResponseCode = 0x7f // The lower 6-bits of format-one error codes are the error number. responseCodeE1 ResponseCode = 0x3f // Bit 6 of format-one errors is zero for errors associated with a handle // or session, or one for errors associated with a parameter. responseCodeP ResponseCode = 1 << 6 // Bit 7 indicates whether the error is a format-zero (0) or format-one code (1) responseCodeF ResponseCode = 1 << 7 // Bit 8 of format-zero errors is zero for TPM1.2 errors and one for TPM2 errors. responseCodeV ResponseCode = 1 << 8 // Bit 10 of format-zero errors is zero for TCG defined errors and one for vendor // defined error. responseCodeT ResponseCode = 1 << 10 // Bit 11 of format-zero errors is zero for errors and one for warnings. responseCodeS ResponseCode = 1 << 11 responseCodeIndex uint8 = 0xf responseCodeIndexShift uint8 = 8 // Bits 8 to 11 of format-one errors represent the parameter number if P is set // or the handle or session number otherwise. responseCodeN ResponseCode = ResponseCode(responseCodeIndex) << responseCodeIndexShift ) // E returns the E field of the response code, corresponding to the error number. func (rc ResponseCode) E() uint8 { if rc.F() { return uint8(rc & responseCodeE1) } return uint8(rc & responseCodeE0) } // F returns the F field of the response code, corresponding to the format. // If it is set, this is a format-one response code. If it is not set, this // is a format-zero response code. func (rc ResponseCode) F() bool { return rc&responseCodeF != 0 } // V returns the V field of the response code. If this is set in a format-zero // response code, then it is a TPM2 code returned when the response tag is // TPM_ST_NO_SESSIONS. If it is not set in a format-zero response code, then it // is a TPM1.2 code returned when the response tag is TPM_TAG_RSP_COMMAND. func (rc ResponseCode) V() bool { return rc&responseCodeV != 0 } // T returns the T field of the response code. If this is set in a format-zero // response code, then the code is defined by the TPM vendor. If it is not set // in a format-zero response code, then the code is defined by the TCG. func (rc ResponseCode) T() bool { return rc&responseCodeT != 0 } // S returns the S field of the response code. If this is set in a format-zero // response code, then the code indicates a warning. If it is not set in a // format-zero response code, then the code indicates an error. func (rc ResponseCode) S() bool { return rc&responseCodeS != 0 } // P returns the P field of the response code. If this is set in a format-one // response code, then the code is associated with a command parameter. If it is // not set in a format-one error code, then the code is associated with a command // handle or session. func (rc ResponseCode) P() bool { return rc&responseCodeP != 0 } // N returns the N field of the response code. If the P field is set in a // format-one response code, then this indicates the parameter number from 0x1 // to 0xf. If the P field is not set in a format-one response code, then the // lower 3 bits indicate the handle or session number (0x1 to 0x7 for handles // and 0x9 to 0xf for sessions). func (rc ResponseCode) N() uint8 { return uint8(rc & responseCodeN >> responseCodeIndexShift) } const ( ResponseSuccess ResponseCode = 0 ResponseBadTag ResponseCode = 0x1e ) // ArithmeticOp corresponds to the TPM_EO type. type ArithmeticOp uint16 const ( OpEq ArithmeticOp = 0x0000 // TPM_EO_EQ OpNeq ArithmeticOp = 0x0001 // TPM_EO_NEQ OpSignedGT ArithmeticOp = 0x0002 // TPM_EO_SIGNED_GT OpUnsignedGT ArithmeticOp = 0x0003 // TPM_EO_UNSIGNED_GT OpSignedLT ArithmeticOp = 0x0004 // TPM_EO_SIGNED_LT OpUnsignedLT ArithmeticOp = 0x0005 // TPM_EO_UNSIGNED_LT OpSignedGE ArithmeticOp = 0x0006 // TPM_EO_SIGNED_GE OpUnsignedGE ArithmeticOp = 0x0007 // TPM_EO_UNSIGNED_GE OpSignedLE ArithmeticOp = 0x0008 // TPM_EO_SIGNED_LE OpUnsignedLE ArithmeticOp = 0x0009 // TPM_EO_UNSIGNED_LE OpBitset ArithmeticOp = 0x000a // TPM_EO_BITSET OpBitclear ArithmeticOp = 0x000b // TPM_EO_BITCLEAR ) // StructTag corresponds to the TPM_ST type. type StructTag uint16 const ( TagRspCommand StructTag = 0x00c4 // TPM_ST_RSP_COMMAND TagNoSessions StructTag = 0x8001 // TPM_ST_NO_SESSIONS TagSessions StructTag = 0x8002 // TPM_ST_SESSIONS TagAttestNV StructTag = 0x8014 // TPM_ST_ATTEST_NV TagAttestCommandAudit StructTag = 0x8015 // TPM_ST_ATTEST_COMMAND_AUDIT TagAttestSessionAudit StructTag = 0x8016 // TPM_ST_ATTEST_SESSION_AUDIT TagAttestCertify StructTag = 0x8017 // TPM_ST_ATTEST_CERTIFY TagAttestQuote StructTag = 0x8018 // TPM_ST_ATTEST_QUOTE TagAttestTime StructTag = 0x8019 // TPM_ST_ATTEST_TIME TagAttestCreation StructTag = 0x801a // TPM_ST_ATTEST_CREATION TagCreation StructTag = 0x8021 // TPM_ST_CREATION TagVerified StructTag = 0x8022 // TPM_ST_VERIFIED TagAuthSecret StructTag = 0x8023 // TPM_ST_AUTH_SECRET TagHashcheck StructTag = 0x8024 // TPM_ST_HASHCHECK TagAuthSigned StructTag = 0x8025 // TPM_ST_AUTH_SIGNED ) // StartupType corresponds to the TPM_SU type. type StartupType uint16 const ( StartupClear StartupType = iota StartupState ) // SessionType corresponds to the TPM_SE type. type SessionType uint8 const ( SessionTypeHMAC SessionType = 0x00 // TPM_SE_HMAC SessionTypePolicy SessionType = 0x01 // TPM_SE_POLICY SessionTypeTrial SessionType = 0x03 // TPM_SE_TRIAL ) // Capability corresponds to the TPM_CAP type. type Capability uint32 const ( CapabilityAlgs Capability = 0 // TPM_CAP_ALGS CapabilityHandles Capability = 1 // TPM_CAP_HANDLES CapabilityCommands Capability = 2 // TPM_CAP_COMMANDS CapabilityPPCommands Capability = 3 // TPM_CAP_PP_COMMANDS CapabilityAuditCommands Capability = 4 // TPM_CAP_AUDIT_COMMANDS CapabilityPCRs Capability = 5 // TPM_CAP_PCRS CapabilityTPMProperties Capability = 6 // TPM_CAP_TPM_PROPERTIES CapabilityPCRProperties Capability = 7 // TPM_CAP_PCR_PROPERTIES CapabilityECCCurves Capability = 8 // TPM_CAP_ECC_CURVES CapabilityAuthPolicies Capability = 9 // TPM_CAP_AUTH_POLICIES ) // Property corresponds to the TPM_PT type. type Property uint32 const ( // These constants represent properties that only change when the firmware in the TPM changes. PropertyFamilyIndicator Property = 0x100 // TPM_PT_FAMILY_INDICATOR PropertyLevel Property = 0x101 // TPM_PT_LEVEL PropertyRevision Property = 0x102 // TPM_PT_REVISION PropertyDayOfYear Property = 0x103 // TPM_PT_DAY_OF_YEAR PropertyYear Property = 0x104 // TPM_PT_YEAR PropertyManufacturer Property = 0x105 // TPM_PT_MANUFACTURER PropertyVendorString1 Property = 0x106 // TPM_PT_VENDOR_STRING_1 PropertyVendorString2 Property = 0x107 // TPM_PT_VENDOR_STRING_2 PropertyVendorString3 Property = 0x108 // TPM_PT_VENDOR_STRING_3 PropertyVendorString4 Property = 0x109 // TPM_PT_VENDOR_STRING_4 PropertyVendorTPMType Property = 0x10a // TPM_PT_VENDOR_TPM_TYPE PropertyFirmwareVersion1 Property = 0x10b // TPM_PT_FIRMWARE_VERSION_1 PropertyFirmwareVersion2 Property = 0x10c // TPM_PT_FIRMWARE_VERSION_2 PropertyInputBuffer Property = 0x10d // TPM_PT_INPUT_BUFFER PropertyHRTransientMin Property = 0x10e // TPM_PT_HR_TRANSIENT_MIN PropertyHRPersistentMin Property = 0x10f // TPM_PT_HR_PERSISTENT_MIN PropertyHRLoadedMin Property = 0x110 // TPM_PT_HR_LOADED_MIN PropertyActiveSessionsMax Property = 0x111 // TPM_PT_ACTIVE_SESSIONS_MAX PropertyPCRCount Property = 0x112 // TPM_PT_PCR_COUNT PropertyPCRSelectMin Property = 0x113 // TPM_PT_PCR_SELECT_MIN PropertyContextGapMax Property = 0x114 // TPM_PT_CONTEXT_GAP_MAX PropertyNVCountersMax Property = 0x116 // TPM_PT_NV_COUNTERS_MAX PropertyNVIndexMax Property = 0x117 // TPM_PT_NV_INDEX_MAX PropertyMemory Property = 0x118 // TPM_PT_MEMORY PropertyClockUpdate Property = 0x119 // TPM_PT_CLOCK_UPDATE PropertyContextHash Property = 0x11a // TPM_PT_CONTEXT_HASH PropertyContextSym Property = 0x11b // TPM_PT_CONTEXT_SYM PropertyContextSymSize Property = 0x11c // TPM_PT_CONTEXT_SYM_SIZE PropertyOrderlyCount Property = 0x11d // TPM_PT_ORDERLY_COUNT PropertyMaxCommandSize Property = 0x11e // TPM_PT_MAX_COMMAND_SIZE PropertyMaxResponseSize Property = 0x11f // TPM_PT_MAX_RESPONSE_SIZE PropertyMaxDigest Property = 0x120 // TPM_PT_MAX_DIGEST PropertyMaxObjectContext Property = 0x121 // TPM_PT_MAX_OBJECT_CONTEXT PropertyMaxSessionContext Property = 0x122 // TPM_PT_MAX_SESSION_CONTEXT PropertyPSFamilyIndicator Property = 0x123 // TPM_PT_PS_FAMILY_INDICATOR PropertyPSLevel Property = 0x124 // TPM_PT_PS_LEVEL PropertyPSRevision Property = 0x125 // TPM_PT_PS_REVISION PropertyPSDayOfYear Property = 0x126 // TPM_PT_PS_DAY_OF_YEAR PropertyPSYear Property = 0x127 // TPM_PT_PS_YEAR PropertySplitMax Property = 0x128 // TPM_PT_SPLIT_MAX PropertyTotalCommands Property = 0x129 // TPM_PT_TOTAL_COMMANDS PropertyLibraryCommands Property = 0x12a // TPM_PT_LIBRARY_COMMANDS PropertyVendorCommands Property = 0x12b // TPM_PT_VENDOR_COMMANDS PropertyNVBufferMax Property = 0x12c // TPM_PT_NV_BUFFER_MAX PropertyModes Property = 0x12d // TPM_PT_MODES PropertyMaxCapBuffer Property = 0x12e // TPM_PT_MAX_CAP_BUFFER PropertyFixed Property = PropertyFamilyIndicator ) const ( // These constants represent properties that change for reasons other than a firmware upgrade. Some of // them may not persist across power cycles. PropertyPermanent Property = 0x200 // TPM_PT_PERMANENT PropertyStartupClear Property = 0x201 // TPM_PT_STARTUP_CLEAR PropertyHRNVIndex Property = 0x202 // TPM_PT_HR_NV_INDEX PropertyHRLoaded Property = 0x203 // TPM_PT_HR_LOADED PropertyHRLoadedAvail Property = 0x204 // TPM_PT_HR_LOADED_AVAIL PropertyHRActive Property = 0x205 // TPM_PT_HR_ACTIVE PropertyHRActiveAvail Property = 0x206 // TPM_PT_HR_ACTIVE_AVAIL PropertyHRTransientAvail Property = 0x207 // TPM_PT_HR_TRANSIENT_AVAIL PropertyHRPersistent Property = 0x208 // TPM_PT_HR_PERSISTENT PropertyHRPersistentAvail Property = 0x209 // TPM_PT_HR_PERSISTENT_AVAIL PropertyNVCounters Property = 0x20a // TPM_PT_NV_COUNTERS PropertyNVCountersAvail Property = 0x20b // TPM_PT_NV_COUNTERS_AVAIL PropertyAlgorithmSet Property = 0x20c // TPM_PT_ALGORITHM_SET PropertyLoadedCurves Property = 0x20d // TPM_PT_LOADED_CURVES PropertyLockoutCounter Property = 0x20e // TPM_PT_LOCKOUT_COUNTER PropertyMaxAuthFail Property = 0x20f // TPM_PT_MAX_AUTH_FAIL PropertyLockoutInterval Property = 0x210 // TPM_PT_LOCKOUT_INTERVAL PropertyLockoutRecovery Property = 0x211 // TPM_PT_LOCKOUT_RECOVERY PropertyNVWriteRecovery Property = 0x212 // TPM_PT_NV_WRITE_RECOVERY PropertyAuditCounter0 Property = 0x213 // TPM_PT_AUDIT_COUNTER_0 PropertyAuditCounter1 Property = 0x214 // TPM_PT_AUDIT_COUNTER_1 PropertyVar Property = PropertyPermanent ) // PropertyPCR corresponds to the TPM_PT_PCR type. type PropertyPCR uint32 const ( PropertyPCRSave PropertyPCR = 0x00 // TPM_PT_PCR_SAVE PropertyPCRExtendL0 PropertyPCR = 0x01 // TPM_PT_PCR_EXTEND_L0 PropertyPCRResetL0 PropertyPCR = 0x02 // TPM_PT_PCR_RESET_L0 PropertyPCRExtendL1 PropertyPCR = 0x03 // TPM_PT_PCR_EXTEND_L1 PropertyPCRResetL1 PropertyPCR = 0x04 // TPM_PT_PCR_RESET_L1 PropertyPCRExtendL2 PropertyPCR = 0x05 // TPM_PT_PCR_EXTEND_L2 PropertyPCRResetL2 PropertyPCR = 0x06 // TPM_PT_PCR_RESET_L2 PropertyPCRExtendL3 PropertyPCR = 0x07 // TPM_PT_PCR_EXTEND_L3 PropertyPCRResetL3 PropertyPCR = 0x08 // TPM_PT_PCR_RESET_L3 PropertyPCRExtendL4 PropertyPCR = 0x09 // TPM_PT_PCR_EXTEND_L4 PropertyPCRResetL4 PropertyPCR = 0x0a // TPM_PT_PCR_RESET_L4 PropertyPCRNoIncrement PropertyPCR = 0x11 // TPM_PT_PCR_NO_INCREMENT PropertyPCRDRTMReset PropertyPCR = 0x12 // TPM_PT_PCR_DRTM_RESET PropertyPCRPolicy PropertyPCR = 0x13 // TPM_PT_PCR_POLICY PropertyPCRAuth PropertyPCR = 0x14 // TPM_PT_PCR_AUTH PropertyPCRFirst PropertyPCR = PropertyPCRSave ) ./github.com/canonical/go-tpm2/types_context.go0000664000000000000000000000132200000000000020054 0ustar0000000000000000// Copyright 2019 Canonical Ltd. // Licensed under the LGPLv3 with static-linking exception. // See LICENCE file for details. package tpm2 // This file contains types defined in section 14 (Context Data) // in part 2 of the library spec. // ContextData corresponds to the TPM2B_CONTEXT_DATA type. type ContextData []byte // Context corresponds to the TPMS_CONTEXT type and is used in TPMContext.ContextLoad and TPMContext.ContextSave. type Context struct { Sequence uint64 // Sequence number of the context SavedHandle Handle // Handle indicating if this is a session or object Hierarchy Handle // Hierarchy of the context Blob ContextData // Encrypted context data and integrity HMAC } ./github.com/canonical/go-tpm2/types_creation.go0000664000000000000000000000164500000000000020204 0ustar0000000000000000// Copyright 2019 Canonical Ltd. // Licensed under the LGPLv3 with static-linking exception. // See LICENCE file for details. package tpm2 // This file contains types defined in section 15 (Creation Data) // in part 2 of the library spec. // CreationData corresponds to the TPMS_CREATION_DATA type, which provides // information about the creation environment of an object. type CreationData struct { PCRSelect PCRSelectionList // PCRs included in PCRDigest // Digest of the selected PCRs using the name algorithm of the object associated with this data. PCRDigest Digest Locality Locality // Locality at which the object was created ParentNameAlg AlgorithmId // Name algorithm of the parent ParentName Name // Name of the parent ParentQualifiedName Name // Qualified name of the parent OutsideInfo Data // External information provided by the caller } ./github.com/canonical/go-tpm2/types_handles.go0000664000000000000000000000336600000000000020020 0ustar0000000000000000// Copyright 2019 Canonical Ltd. // Licensed under the LGPLv3 with static-linking exception. // See LICENCE file for details. package tpm2 // This file contains types defined in section 7 (Handles) in // part 2 of the library spec. // Handle corresponds to the TPM_HANDLE type, and is a numeric // identifier that references a resource on the TPM. type Handle uint32 // Type returns the type of the handle. func (h Handle) Type() HandleType { return HandleType(h >> 24) } const ( HandleOwner Handle = 0x40000001 // TPM_RH_OWNER HandleNull Handle = 0x40000007 // TPM_RH_NULL HandleUnassigned Handle = 0x40000008 // TPM_RH_UNASSIGNED HandlePW Handle = 0x40000009 // TPM_RS_PW HandleLockout Handle = 0x4000000a // TPM_RH_LOCKOUT HandleEndorsement Handle = 0x4000000b // TPM_RH_ENDORSEMENT HandlePlatform Handle = 0x4000000c // TPM_RH_PLATFORM HandlePlatformNV Handle = 0x4000000d // TPM_RH_PLATFORM_NV ) // HandleType corresponds to the TPM_HT type, and is used to // identify the type of a Handle. type HandleType uint8 // BaseHandle returns the first handle for the handle type. func (h HandleType) BaseHandle() Handle { return Handle(h) << 24 } const ( HandleTypePCR HandleType = 0x00 // TPM_HT_PCR HandleTypeNVIndex HandleType = 0x01 // TPM_HT_NV_INDEX HandleTypeHMACSession HandleType = 0x02 // TPM_HT_HMAC_SESSION HandleTypeLoadedSession HandleType = 0x02 // TPM_HT_LOADED_SESSION HandleTypePolicySession HandleType = 0x03 // TPM_HT_POLICY_SESSION HandleTypeSavedSession HandleType = 0x03 // TPM_HT_SAVED_SESSION HandleTypePermanent HandleType = 0x40 // TPM_HT_PERMANENT HandleTypeTransient HandleType = 0x80 // TPM_HT_TRANSIENT HandleTypePersistent HandleType = 0x81 // TPM_HT_PERSISTENT ) ./github.com/canonical/go-tpm2/types_interface.go0000664000000000000000000002212000000000000020327 0ustar0000000000000000// Copyright 2019 Canonical Ltd. // Licensed under the LGPLv3 with static-linking exception. // See LICENCE file for details. package tpm2 import ( "crypto" "crypto/cipher" _ "crypto/sha1" _ "crypto/sha256" _ "crypto/sha512" "errors" "hash" ) // This file contains types defined in section 9 (Interface Types) in // part 2 of the library spec. Interface types are used by the TPM // implementation to check that a value is appropriate for the context // during unmarshalling. This package has limited support for some // algorithm interfaces by defining context specific algorithm types // based on the AlgorithmId type. Note that no interface types with // TPM_HANDLE as the underlying type are supported, as this package // doesn't use handles in most APIs. // HashAlgorithmId corresponds to the TPMI_ALG_HASH type type HashAlgorithmId AlgorithmId // GetHash returns the equivalent crypto.Hash value for this algorithm if one // exists, and 0 if one does not exist. func (a HashAlgorithmId) GetHash() crypto.Hash { switch a { case HashAlgorithmSHA1: return crypto.SHA1 case HashAlgorithmSHA256: return crypto.SHA256 case HashAlgorithmSHA384: return crypto.SHA384 case HashAlgorithmSHA512: return crypto.SHA512 case HashAlgorithmSHA3_256: return crypto.SHA3_256 case HashAlgorithmSHA3_384: return crypto.SHA3_384 case HashAlgorithmSHA3_512: return crypto.SHA3_512 default: return 0 } } // IsValid determines if the digest algorithm is valid. This // should be checked by code that deserializes an algorithm before // calling Size if it does not want to panic. func (a HashAlgorithmId) IsValid() bool { switch a { case HashAlgorithmSHA1: case HashAlgorithmSHA256: case HashAlgorithmSHA384: case HashAlgorithmSHA512: case HashAlgorithmSM3_256: case HashAlgorithmSHA3_256: case HashAlgorithmSHA3_384: case HashAlgorithmSHA3_512: default: return false } return true } // Available determines if the TPM digest algorithm has an equivalent go crypto.Hash // that is linked into the current binary. func (a HashAlgorithmId) Available() bool { return a.GetHash().Available() } // NewHash constructs a new hash.Hash implementation for this algorithm. It will panic if // HashAlgorithmId.Available returns false. func (a HashAlgorithmId) NewHash() hash.Hash { return a.GetHash().New() } // Size returns the size of the algorithm. It will panic if IsValid returns false. func (a HashAlgorithmId) Size() int { switch a { case HashAlgorithmSHA1: return 20 case HashAlgorithmSHA256: return 32 case HashAlgorithmSHA384: return 48 case HashAlgorithmSHA512: return 64 case HashAlgorithmSM3_256: return 32 case HashAlgorithmSHA3_256: return 32 case HashAlgorithmSHA3_384: return 48 case HashAlgorithmSHA3_512: return 64 default: panic("unknown hash algorithm") } } const ( HashAlgorithmNull HashAlgorithmId = HashAlgorithmId(AlgorithmNull) // TPM_ALG_NULL HashAlgorithmSHA1 HashAlgorithmId = HashAlgorithmId(AlgorithmSHA1) // TPM_ALG_SHA1 HashAlgorithmSHA256 HashAlgorithmId = HashAlgorithmId(AlgorithmSHA256) // TPM_ALG_SHA256 HashAlgorithmSHA384 HashAlgorithmId = HashAlgorithmId(AlgorithmSHA384) // TPM_ALG_SHA384 HashAlgorithmSHA512 HashAlgorithmId = HashAlgorithmId(AlgorithmSHA512) // TPM_ALG_SHA512 HashAlgorithmSM3_256 HashAlgorithmId = HashAlgorithmId(AlgorithmSM3_256) // TPM_ALG_SM3_256 HashAlgorithmSHA3_256 HashAlgorithmId = HashAlgorithmId(AlgorithmSHA3_256) // TPM_ALG_SHA3_256 HashAlgorithmSHA3_384 HashAlgorithmId = HashAlgorithmId(AlgorithmSHA3_384) // TPM_ALG_SHA3_384 HashAlgorithmSHA3_512 HashAlgorithmId = HashAlgorithmId(AlgorithmSHA3_512) // TPM_ALG_SHA3_512 ) // SymAlgorithmId corresponds to the TPMI_ALG_SYM type type SymAlgorithmId AlgorithmId // IsValidBlockCipher determines if this algorithm is a valid block cipher. // This should be checked by code that deserializes an algorithm before calling // BlockSize if it does not want to panic. func (a SymAlgorithmId) IsValidBlockCipher() bool { switch a { case SymAlgorithmTDES: case SymAlgorithmAES: case SymAlgorithmSM4: case SymAlgorithmCamellia: default: return false } return true } // Available indicates whether the TPM symmetric cipher has a registered go implementation. func (a SymAlgorithmId) Available() bool { _, ok := symmetricAlgs[a] return ok } // BlockSize indicates the block size of the symmetric cipher. This will panic if // IsValidBlockCipher returns false. func (a SymAlgorithmId) BlockSize() int { switch a { case SymAlgorithmTDES: return 8 case SymAlgorithmAES: return 16 case SymAlgorithmSM4: return 16 case SymAlgorithmCamellia: return 16 default: panic("invalid symmetric algorithm") } } // NewCipher constructs a new symmetric cipher with the supplied key, if there is a go // implementation registered. func (a SymAlgorithmId) NewCipher(key []byte) (cipher.Block, error) { fn, ok := symmetricAlgs[a] if !ok { return nil, errors.New("unavailable cipher") } return fn(key) } const ( SymAlgorithmTDES SymAlgorithmId = SymAlgorithmId(AlgorithmTDES) // TPM_ALG_TDES SymAlgorithmAES SymAlgorithmId = SymAlgorithmId(AlgorithmAES) // TPM_ALG_AES SymAlgorithmXOR SymAlgorithmId = SymAlgorithmId(AlgorithmXOR) // TPM_ALG_XOR SymAlgorithmNull SymAlgorithmId = SymAlgorithmId(AlgorithmNull) // TPM_ALG_NULL SymAlgorithmSM4 SymAlgorithmId = SymAlgorithmId(AlgorithmSM4) // TPM_ALG_SM4 SymAlgorithmCamellia SymAlgorithmId = SymAlgorithmId(AlgorithmCamellia) // TPM_ALG_CAMELLIA ) // SymObjectAlgorithmId corresponds to the TPMI_ALG_SYM_OBJECT type type SymObjectAlgorithmId AlgorithmId // IsValidBlockCipher determines if this algorithm is a valid block cipher. // This should be checked by code that deserializes an algorithm before calling // BlockSize if it does not want to panic. func (a SymObjectAlgorithmId) IsValidBlockCipher() bool { return SymAlgorithmId(a).IsValidBlockCipher() } // Available indicates whether the TPM symmetric cipher has a registered go implementation. func (a SymObjectAlgorithmId) Available() bool { return SymAlgorithmId(a).Available() } // BlockSize indicates the block size of the symmetric cipher. This will panic if // IsValidBlockCipher returns false. func (a SymObjectAlgorithmId) BlockSize() int { return SymAlgorithmId(a).BlockSize() } // NewCipher constructs a new symmetric cipher with the supplied key, if there is a go // implementation registered. func (a SymObjectAlgorithmId) NewCipher(key []byte) (cipher.Block, error) { return SymAlgorithmId(a).NewCipher(key) } const ( SymObjectAlgorithmAES SymObjectAlgorithmId = SymObjectAlgorithmId(AlgorithmAES) // TPM_ALG_AES SymObjectAlgorithmNull SymObjectAlgorithmId = SymObjectAlgorithmId(AlgorithmNull) // TPM_ALG_NULL SymObjectAlgorithmSM4 SymObjectAlgorithmId = SymObjectAlgorithmId(AlgorithmSM4) // TPM_ALG_SM4 SymObjectAlgorithmCamellia SymObjectAlgorithmId = SymObjectAlgorithmId(AlgorithmCamellia) // TPM_ALG_CAMELLIA ) // SymModeId corresponds to the TPMI_ALG_SYM_MODE type type SymModeId AlgorithmId const ( SymModeNull SymModeId = SymModeId(AlgorithmNull) // TPM_ALG_NULL SymModeCTR SymModeId = SymModeId(AlgorithmCTR) // TPM_ALG_CTR SymModeOFB SymModeId = SymModeId(AlgorithmOFB) // TPM_ALG_OFB SymModeCBC SymModeId = SymModeId(AlgorithmCBC) // TPM_ALG_CBC SymModeCFB SymModeId = SymModeId(AlgorithmCFB) // TPM_ALG_CFB SymModeECB SymModeId = SymModeId(AlgorithmECB) // TPM_ALG_ECB ) // KDFAlgorithmId corresppnds to the TPMI_ALG_KDF type type KDFAlgorithmId AlgorithmId const ( KDFAlgorithmMGF1 KDFAlgorithmId = KDFAlgorithmId(AlgorithmMGF1) // TPM_ALG_MGF1 KDFAlgorithmNull KDFAlgorithmId = KDFAlgorithmId(AlgorithmNull) // TPM_ALG_NULL KDFAlgorithmKDF1_SP800_56A KDFAlgorithmId = KDFAlgorithmId(AlgorithmKDF1_SP800_56A) // TPM_ALG_KDF1_SP800_56A KDFAlgorithmKDF2 KDFAlgorithmId = KDFAlgorithmId(AlgorithmKDF2) // TPM_ALG_KDF2 KDFAlgorithmKDF1_SP800_108 KDFAlgorithmId = KDFAlgorithmId(AlgorithmKDF1_SP800_108) // TPM_ALG_KDF1_SP800_108 ) // SigSchemeId corresponds to the TPMI_ALG_SIG_SCHEME type type SigSchemeId AlgorithmId // IsValid determines if the scheme is a valid signature scheme. func (s SigSchemeId) IsValid() bool { switch s { case SigSchemeAlgHMAC: case SigSchemeAlgRSASSA: case SigSchemeAlgRSAPSS: case SigSchemeAlgECDSA: case SigSchemeAlgECDAA: case SigSchemeAlgSM2: case SigSchemeAlgECSchnorr: default: return false } return true } const ( SigSchemeAlgHMAC SigSchemeId = SigSchemeId(AlgorithmHMAC) // TPM_ALG_HMAC SigSchemeAlgNull SigSchemeId = SigSchemeId(AlgorithmNull) // TPM_ALG_NULL SigSchemeAlgRSASSA SigSchemeId = SigSchemeId(AlgorithmRSASSA) // TPM_ALG_RSASSA SigSchemeAlgRSAPSS SigSchemeId = SigSchemeId(AlgorithmRSAPSS) // TPM_ALG_RSAPSS SigSchemeAlgECDSA SigSchemeId = SigSchemeId(AlgorithmECDSA) // TPM_ALG_ECDSA SigSchemeAlgECDAA SigSchemeId = SigSchemeId(AlgorithmECDAA) // TPM_ALG_ECDAA SigSchemeAlgSM2 SigSchemeId = SigSchemeId(AlgorithmSM2) // TPM_ALG_SM2 SigSchemeAlgECSchnorr SigSchemeId = SigSchemeId(AlgorithmECSchnorr) // TPM_ALG_ECSCHNORR ) ./github.com/canonical/go-tpm2/types_nv.go0000664000000000000000000000752400000000000017025 0ustar0000000000000000// Copyright 2019 Canonical Ltd. // Licensed under the LGPLv3 with static-linking exception. // See LICENCE file for details. package tpm2 import ( "bytes" "fmt" "github.com/canonical/go-tpm2/mu" ) // This file contains types defined in section 13 (NV Storage Structures) // in part 2 of the library spec. // NVType corresponds to the TPM_NT type. type NVType uint32 // WithAttrs returns NVAttributes for this type with the specified attributes set. func (t NVType) WithAttrs(attrs NVAttributes) NVAttributes { return NVAttributes(t<<4) | attrs } const ( NVTypeOrdinary NVType = 0 // TPM_NT_ORDINARY NVTypeCounter NVType = 1 // TPM_NT_COUNTER NVTypeBits NVType = 2 // TPM_NT_BITS NVTypeExtend NVType = 4 // TPM_NT_EXTEND NVTypePinFail NVType = 8 // TPM_NT_PIN_FAIL NVTypePinPass NVType = 9 // TPM_NT_PIN_PASS ) // NVPinCounterParams corresponds to the TPMS_NV_PIN_COUNTER_PARAMETERS type. type NVPinCounterParams struct { Count uint32 Limit uint32 } // NVAttributes corresponds to the TPMA_NV type, and represents the attributes of a NV index. When exchanged with the TPM, some bits // are reserved to encode the type of the NV index (NVType). type NVAttributes uint32 // Type returns the NVType encoded in a NVAttributes value. func (a NVAttributes) Type() NVType { return NVType((a & 0xf0) >> 4) } // AttrsOnly returns the NVAttributes without the encoded NVType. func (a NVAttributes) AttrsOnly() NVAttributes { return a & ^NVAttributes(0xf0) } const ( AttrNVPPWrite NVAttributes = 1 << 0 // TPMA_NV_PPWRITE AttrNVOwnerWrite NVAttributes = 1 << 1 // TPMA_NV_OWNERWRITE AttrNVAuthWrite NVAttributes = 1 << 2 // TPMA_NV_AUTHWRITE AttrNVPolicyWrite NVAttributes = 1 << 3 // TPMA_NV_POLICY_RITE AttrNVPolicyDelete NVAttributes = 1 << 10 // TPMA_NV_POLICY_DELETE AttrNVWriteLocked NVAttributes = 1 << 11 // TPMA_NV_WRITELOCKED AttrNVWriteAll NVAttributes = 1 << 12 // TPMA_NV_WRITEALL AttrNVWriteDefine NVAttributes = 1 << 13 // TPMA_NV_WRITEDEFINE AttrNVWriteStClear NVAttributes = 1 << 14 // TPMA_NV_WRITE_STCLEAR AttrNVGlobalLock NVAttributes = 1 << 15 // TPMA_NV_GLOBALLOCK AttrNVPPRead NVAttributes = 1 << 16 // TPMA_NV_PPREAD AttrNVOwnerRead NVAttributes = 1 << 17 // TPMA_NV_OWNERREAD AttrNVAuthRead NVAttributes = 1 << 18 // TPMA_NV_AUTHREAD AttrNVPolicyRead NVAttributes = 1 << 19 // TPMA_NV_POLICYREAD AttrNVNoDA NVAttributes = 1 << 25 // TPMA_NV_NO_DA AttrNVOrderly NVAttributes = 1 << 26 // TPMA_NV_ORDERLY AttrNVClearStClear NVAttributes = 1 << 27 // TPMA_NV_CLEAR_STCLEAR AttrNVReadLocked NVAttributes = 1 << 28 // TPMA_NV_READLOCKED AttrNVWritten NVAttributes = 1 << 29 // TPMA_NV_WRITTEN AttrNVPlatformCreate NVAttributes = 1 << 30 // TPMA_NV_PLATFORMCREATE AttrNVReadStClear NVAttributes = 1 << 31 // TPMA_NV_READ_STCLEAR ) // NVPublic corresponds to the TPMS_NV_PUBLIC type, which describes a NV index. type NVPublic struct { Index Handle // Handle of the NV index NameAlg HashAlgorithmId // NameAlg is the digest algorithm used to compute the name of the index Attrs NVAttributes // Attributes of this index AuthPolicy Digest // Authorization policy for this index Size uint16 // Size of this index } // Name computes the name of this NV index func (p *NVPublic) Name() (Name, error) { if !p.NameAlg.Available() { return nil, fmt.Errorf("unsupported name algorithm or algorithm not linked into binary: %v", p.NameAlg) } hasher := p.NameAlg.NewHash() if _, err := mu.MarshalToWriter(hasher, p); err != nil { return nil, fmt.Errorf("cannot marshal public object: %v", err) } return mu.MustMarshalToBytes(p.NameAlg, mu.RawBytes(hasher.Sum(nil))), nil } func (p *NVPublic) compareName(name Name) bool { n, err := p.Name() if err != nil { return false } return bytes.Equal(n, name) } ./github.com/canonical/go-tpm2/types_objects.go0000664000000000000000000002613100000000000020026 0ustar0000000000000000// Copyright 2019 Canonical Ltd. // Licensed under the LGPLv3 with static-linking exception. // See LICENCE file for details. package tpm2 import ( "bytes" "crypto" "crypto/ecdsa" "crypto/rsa" "fmt" "math/big" "reflect" "github.com/canonical/go-tpm2/mu" ) // This file contains types defined in section 12 (Key/Object Complex) // in part 2 of the library spec. // ObjectTypeId corresponds to the TPMI_ALG_PUBLIC type. type ObjectTypeId AlgorithmId // IsAsymmetric determines if the type corresponds to an asymmetric // object. func (t ObjectTypeId) IsAsymmetric() bool { return t == ObjectTypeRSA || t == ObjectTypeECC } const ( ObjectTypeRSA ObjectTypeId = ObjectTypeId(AlgorithmRSA) // TPM_ALG_RSA ObjectTypeKeyedHash ObjectTypeId = ObjectTypeId(AlgorithmKeyedHash) // TPM_ALG_KEYEDHASH ObjectTypeECC ObjectTypeId = ObjectTypeId(AlgorithmECC) // TPM_ALG_ECC ObjectTypeSymCipher ObjectTypeId = ObjectTypeId(AlgorithmSymCipher) // TPM_ALG_SYMCIPHER ) // PublicIDU is a union type that corresponds to the TPMU_PUBLIC_ID type. The selector type // is ObjectTypeId. The mapping of selector values to fields is as follows: // - ObjectTypeRSA: RSA // - ObjectTypeKeyedHash: KeyedHash // - ObjectTypeECC: ECC // - ObjectTypeSymCipher: Sym type PublicIDU struct { KeyedHash Digest Sym Digest RSA PublicKeyRSA ECC *ECCPoint } func (p *PublicIDU) Select(selector reflect.Value) interface{} { switch selector.Interface().(ObjectTypeId) { case ObjectTypeRSA: return &p.RSA case ObjectTypeKeyedHash: return &p.KeyedHash case ObjectTypeECC: return &p.ECC case ObjectTypeSymCipher: return &p.Sym default: return nil } } // KeyedHashParams corresponds to the TPMS_KEYEDHASH_PARMS type, and defines the public // parameters for a keyedhash object. type KeyedHashParams struct { Scheme KeyedHashScheme // Signing method for a keyed hash signing object } // AsymParams corresponds to the TPMS_ASYM_PARMS type, and defines the common public // parameters for an asymmetric key. type AsymParams struct { Symmetric SymDefObject // Symmetric algorithm for a restricted decrypt key. // For a key with the AttrSign attribute: a signing scheme. // For a key with the AttrDecrypt attribute: a key exchange protocol. // For a key with both AttrSign and AttrDecrypt attributes: AlgorithmNull. Scheme AsymScheme } // RSAParams corresponds to the TPMS_RSA_PARMS type, and defines the public parameters // for an RSA key. type RSAParams struct { Symmetric SymDefObject // Symmetric algorithm for a restricted decrypt key. // For an unrestricted signing key: AlgorithmRSAPSS, AlgorithmRSASSA or AlgorithmNull. // For a restricted signing key: AlgorithmRSAPSS or AlgorithmRSASSA. // For an unrestricted decrypt key: AlgorithmRSAES, AlgorithmOAEP or AlgorithmNull. // For a restricted decrypt key: AlgorithmNull. Scheme RSAScheme KeyBits uint16 // Number of bits in the public modulus Exponent uint32 // Public exponent. When the value is zero, the exponent is 65537 } // ECCParams corresponds to the TPMS_ECC_PARMS type, and defines the public parameters for an // ECC key. type ECCParams struct { Symmetric SymDefObject // Symmetric algorithm for a restricted decrypt key. // For a key with the AttrSign attribute: a signing scheme. // For a key with the AttrDecrypt attribute: a key exchange protocol or AlgorithmNull. // For a storage key: AlgorithmNull. Scheme ECCScheme CurveID ECCCurve // ECC curve ID KDF KDFScheme // Unused - always KDFAlgorithmNull } // PublicParamsU is a union type that corresponds to the TPMU_PUBLIC_PARMS type. The selector // type is ObjectTypeId. // The mapping of selector values to fields is as follows: // - ObjectTypeRSA: RSADetail // - ObjectTypeKeyedHash: KeyedHashDetail // - ObjectTypeECC: ECCDetail // - ObjectTypeSymCipher: SymDetail type PublicParamsU struct { KeyedHashDetail *KeyedHashParams SymDetail *SymCipherParams RSADetail *RSAParams ECCDetail *ECCParams } func (p *PublicParamsU) Select(selector reflect.Value) interface{} { switch selector.Interface().(ObjectTypeId) { case ObjectTypeRSA: return &p.RSADetail case ObjectTypeKeyedHash: return &p.KeyedHashDetail case ObjectTypeECC: return &p.ECCDetail case ObjectTypeSymCipher: return &p.SymDetail default: return nil } } // AsymDetail returns the parameters associated with the specified object type // as *AsymParams. It panics if the type is not ObjectTypeRSA or ObjectTypeECC, // or the appropriate field isn't set. func (p PublicParamsU) AsymDetail(t ObjectTypeId) *AsymParams { switch t { case ObjectTypeRSA: return &AsymParams{ Symmetric: p.RSADetail.Symmetric, Scheme: AsymScheme{ Scheme: AsymSchemeId(p.RSADetail.Scheme.Scheme), Details: p.RSADetail.Scheme.Details}} case ObjectTypeECC: return &AsymParams{ Symmetric: p.ECCDetail.Symmetric, Scheme: AsymScheme{ Scheme: AsymSchemeId(p.ECCDetail.Scheme.Scheme), Details: p.ECCDetail.Scheme.Details}} default: panic("invalid type") } } // PublicParams corresponds to the TPMT_PUBLIC_PARMS type. type PublicParams struct { Type ObjectTypeId // Type specifier Parameters *PublicParamsU // Algorithm details } // Public corresponds to the TPMT_PUBLIC type, and defines the public area for an object. type Public struct { Type ObjectTypeId // Type of this object NameAlg HashAlgorithmId // NameAlg is the algorithm used to compute the name of this object Attrs ObjectAttributes // Object attributes AuthPolicy Digest // Authorization policy for this object Params *PublicParamsU // Type specific parameters Unique *PublicIDU // Type specific unique identifier } // Name computes the name of this object func (p *Public) Name() (Name, error) { if !p.NameAlg.Available() { return nil, fmt.Errorf("unsupported name algorithm or algorithm not linked into binary: %v", p.NameAlg) } hasher := p.NameAlg.NewHash() if _, err := mu.MarshalToWriter(hasher, p); err != nil { return nil, fmt.Errorf("cannot marshal public object: %v", err) } return mu.MustMarshalToBytes(p.NameAlg, mu.RawBytes(hasher.Sum(nil))), nil } func (p *Public) compareName(name Name) bool { n, err := p.Name() if err != nil { return false } return bytes.Equal(n, name) } func (p *Public) ToTemplate() (Template, error) { b, err := mu.MarshalToBytes(p) if err != nil { return nil, fmt.Errorf("cannot marshal object: %v", err) } return b, nil } func (p *Public) isParent() bool { if !p.NameAlg.IsValid() { return false } return p.Attrs&(AttrRestricted|AttrDecrypt) == AttrRestricted|AttrDecrypt } // IsAsymmetric indicates that this public area is associated with an asymmetric // key. func (p *Public) IsAsymmetric() bool { return p.Type.IsAsymmetric() } // IsStorageParent indicates that this public area is associated with an object that can be // a storage parent. func (p *Public) IsStorageParent() bool { if !p.isParent() { return false } switch p.Type { case ObjectTypeRSA, ObjectTypeECC, ObjectTypeSymCipher: return true default: return false } } // IsDerivationParent indicates that this public area is associated with an object that can be // a derivation parent. func (p *Public) IsDerivationParent() bool { if !p.isParent() { return false } if p.Type != ObjectTypeKeyedHash { return false } return true } // Public returns a corresponding public key for the TPM public area. // This will panic if the public area does not correspond to an asymmetric // key. func (p *Public) Public() crypto.PublicKey { switch p.Type { case ObjectTypeRSA: exp := int(p.Params.RSADetail.Exponent) if exp == 0 { exp = DefaultRSAExponent } return &rsa.PublicKey{ N: new(big.Int).SetBytes(p.Unique.RSA), E: exp} case ObjectTypeECC: return &ecdsa.PublicKey{ Curve: p.Params.ECCDetail.CurveID.GoCurve(), X: new(big.Int).SetBytes(p.Unique.ECC.X), Y: new(big.Int).SetBytes(p.Unique.ECC.Y)} default: panic("object is not a public key") } } // PublicDerived is similar to Public but can be used as a template to create a derived object // with TPMContext.CreateLoaded type PublicDerived struct { Type ObjectTypeId // Type of this object NameAlg HashAlgorithmId // NameAlg is the algorithm used to compute the name of this object Attrs ObjectAttributes // Object attributes AuthPolicy Digest // Authorization policy for this object Params *PublicParamsU // Type specific parameters // Unique contains the derivation values. These take precedence over any values specified // in SensitiveCreate.Data when creating a derived object, Unique *Derive } // Name computes the name of this object func (p *PublicDerived) Name() (Name, error) { if !p.NameAlg.Available() { return nil, fmt.Errorf("unsupported name algorithm or algorithm not linked into binary: %v", p.NameAlg) } hasher := p.NameAlg.NewHash() if _, err := mu.MarshalToWriter(hasher, p); err != nil { return nil, fmt.Errorf("cannot marshal public object: %v", err) } return mu.MustMarshalToBytes(p.NameAlg, mu.RawBytes(hasher.Sum(nil))), nil } func (p *PublicDerived) ToTemplate() (Template, error) { b, err := mu.MarshalToBytes(p) if err != nil { return nil, fmt.Errorf("cannot marshal object: %v", err) } return b, nil } // Template corresponds to the TPM2B_TEMPLATE type type Template []byte // 12.3) Private Area Structures // PrivateVendorSpecific corresponds to the TPM2B_PRIVATE_VENDOR_SPECIFIC type. type PrivateVendorSpecific []byte // SensitiveCompositeU is a union type that corresponds to the TPMU_SENSITIVE_COMPOSITE // type. The selector type is ObjectTypeId. The mapping of selector values to fields is // as follows: // - ObjectTypeRSA: RSA // - ObjectTypeECC: ECC // - ObjectTypeKeyedHash: Bits // - ObjectTypeSymCipher: Sym type SensitiveCompositeU struct { RSA PrivateKeyRSA ECC ECCParameter Bits SensitiveData Sym SymKey } func (s *SensitiveCompositeU) Select(selector reflect.Value) interface{} { switch selector.Interface().(ObjectTypeId) { case ObjectTypeRSA: return &s.RSA case ObjectTypeECC: return &s.ECC case ObjectTypeKeyedHash: return &s.Bits case ObjectTypeSymCipher: return &s.Sym default: return nil } } // Any returns the value associated with the specified object type as // PrivateVendorSpecific. func (s SensitiveCompositeU) Any(t ObjectTypeId) PrivateVendorSpecific { switch t { case ObjectTypeRSA: return PrivateVendorSpecific(s.RSA) case ObjectTypeECC: return PrivateVendorSpecific(s.ECC) case ObjectTypeKeyedHash: return PrivateVendorSpecific(s.Bits) case ObjectTypeSymCipher: return PrivateVendorSpecific(s.Sym) default: return nil } } // Sensitive corresponds to the TPMT_SENSITIVE type. type Sensitive struct { Type ObjectTypeId // Same as the corresponding Type in the Public object AuthValue Auth // Authorization value SeedValue Digest // For a parent object, the seed value for protecting descendant objects Sensitive *SensitiveCompositeU // Type specific private data } // Private corresponds to the TPM2B_PRIVATE type. type Private []byte // 12.4) Identity Object // IDObjectRaw corresponds to the TPM2B_ID_OBJECT type. type IDObjectRaw []byte ./github.com/canonical/go-tpm2/types_structures.go0000664000000000000000000005234400000000000020625 0ustar0000000000000000// Copyright 2019 Canonical Ltd. // Licensed under the LGPLv3 with static-linking exception. // See LICENCE file for details. package tpm2 import ( "bytes" "encoding/binary" "errors" "fmt" "io" "math/big" "reflect" "sort" "golang.org/x/xerrors" "github.com/canonical/go-tpm2/mu" ) // This file contains types defined in section 10 (Structures) in // part 2 of the library spec. type Empty struct{} // 10.3) Hash/Digest structures // TaggedHash corresponds to the TPMT_HA type. type TaggedHash struct { HashAlg HashAlgorithmId // Algorithm of the digest contained with Digest Digest []byte // Digest data } // TaggedHash represents the TPMT_HA type in the TCG spec. In the spec, TPMT_HA.digest is a union type // (TPMU_HA), which is a union of all of the different hash algorithms. Each member of that union is an // array of raw bytes. As no length is encoded, we need a custom marshaller implementation that unmarshals the // correct number of bytes depending on the hash algorithm func (p TaggedHash) Marshal(w io.Writer) error { if err := binary.Write(w, binary.BigEndian, p.HashAlg); err != nil { return xerrors.Errorf("cannot marshal digest algorithm: %w", err) } if !p.HashAlg.IsValid() { return fmt.Errorf("cannot determine digest size for unknown algorithm %v", p.HashAlg) } if p.HashAlg.Size() != len(p.Digest) { return fmt.Errorf("invalid digest size %d", len(p.Digest)) } if _, err := w.Write(p.Digest); err != nil { return xerrors.Errorf("cannot write digest: %w", err) } return nil } func (p *TaggedHash) Unmarshal(r mu.Reader) error { if err := binary.Read(r, binary.BigEndian, &p.HashAlg); err != nil { return xerrors.Errorf("cannot unmarshal digest algorithm: %w", err) } if !p.HashAlg.IsValid() { return fmt.Errorf("cannot determine digest size for unknown algorithm %v", p.HashAlg) } p.Digest = make(Digest, p.HashAlg.Size()) if _, err := io.ReadFull(r, p.Digest); err != nil { return xerrors.Errorf("cannot read digest: %w", err) } return nil } // 10.4 Sized Buffers // Digest corresponds to the TPM2B_DIGEST type. The largest size of this supported // by the TPM can be determined by calling TPMContext.GetMaxDigest. type Digest []byte // Data corresponds to the TPM2B_DATA type. The largest size of this supported by // the TPM can be determined by calling TPMContext.GetMaxData. type Data []byte // Nonce corresponds to the TPM2B_NONCE type. type Nonce Digest // Auth corresponds to the TPM2B_AUTH type. type Auth Digest // Operand corresponds to the TPM2B_OPERAND type. type Operand Digest const ( // EventMaxSize indicates the maximum size of arguments of the Event type. EventMaxSize = 1024 ) // Event corresponds to the TPM2B_EVENT type. The largest size of this is indicated // by EventMaxSize. type Event []byte // MaxBuffer corresponds to the TPM2B_MAX_BUFFER type. The largest size of this supported // by the TPM can be determined by calling TPMContext.GetInputBuffer. type MaxBuffer []byte // MaxNVBuffer corresponds to the TPM2B_MAX_NV_BUFFER type. The largest size of this // supported by the TPM can be determined by calling TPMContext.GetNVBufferMax. type MaxNVBuffer []byte // Timeout corresponds to the TPM2B_TIMEOUT type. The spec defines this // as having a maximum size of 8 bytes. It is always 8 bytes in the // reference implementation and so could be represented as a uint64, // but we have to preserve the original buffer because there is no // guarantees that it is always 8 bytes, and the actual TPM buffer // must be recreated accurately in order for ticket validation to // work correctly in TPMContext.PolicyTicket. type Timeout []byte // Value returns the value as a uint64. The spec defines the TPM2B_TIMEOUT // type as having a size of up to 8 bytes. If an implementation creates a // larger value then the result of this is undefined. func (t Timeout) Value() uint64 { return new(big.Int).SetBytes(t).Uint64() } // 10.5) Names // Name corresponds to the TPM2B_NAME type. type Name []byte // NameType describes the type of a name. type NameType int const ( // NameTypeInvalid means that a Name is invalid. NameTypeInvalid NameType = iota // NameTypeHandle means that a Name is a handle. NameTypeHandle // NameTypeDigest means that a Name is a digest. NameTypeDigest ) // Type determines the type of this name. func (n Name) Type() NameType { if len(n) < binary.Size(HashAlgorithmId(0)) { return NameTypeInvalid } if len(n) == binary.Size(Handle(0)) { return NameTypeHandle } alg := HashAlgorithmId(binary.BigEndian.Uint16(n)) if !alg.IsValid() { return NameTypeInvalid } if len(n)-binary.Size(HashAlgorithmId(0)) != alg.Size() { return NameTypeInvalid } return NameTypeDigest } // Handle returns the handle of the resource that this name corresponds to. If // Type does not return NameTypeHandle, it will panic. func (n Name) Handle() Handle { if n.Type() != NameTypeHandle { panic("name is not a handle") } return Handle(binary.BigEndian.Uint32(n)) } // Algorithm returns the digest algorithm of this name. If Type does not return // NameTypeDigest, it will return HashAlgorithmNull. func (n Name) Algorithm() HashAlgorithmId { if n.Type() != NameTypeDigest { return HashAlgorithmNull } return HashAlgorithmId(binary.BigEndian.Uint16(n)) } // Digest returns the name as a digest without the algorithm identifier. If // Type does not return NameTypeDigest, it will panic. func (n Name) Digest() Digest { if n.Type() != NameTypeDigest { panic("name is not a valid digest") } return Digest(n[binary.Size(HashAlgorithmId(0)):]) } // 10.6) PCR Structures // PCRSelect is a slice of PCR indexes. It is marshalled to and from the // TPMS_PCR_SELECT type, which is a bitmap of the PCR indices contained within // this slice. type PCRSelect []int func (d PCRSelect) Marshal(w io.Writer) error { bytes := make([]byte, 3) for _, i := range d { octet := i / 8 for octet >= len(bytes) { bytes = append(bytes, byte(0)) } bit := uint(i % 8) bytes[octet] |= 1 << bit } if err := binary.Write(w, binary.BigEndian, uint8(len(bytes))); err != nil { return xerrors.Errorf("cannot write size of PCR selection bit mask: %w", err) } if _, err := w.Write(bytes); err != nil { return xerrors.Errorf("cannot write PCR selection bit mask: %w", err) } return nil } func (d *PCRSelect) Unmarshal(r mu.Reader) error { var size uint8 if err := binary.Read(r, binary.BigEndian, &size); err != nil { return xerrors.Errorf("cannot read size of PCR selection bit mask: %w", err) } if int(size) > r.Len() { return errors.New("size field is larger than the remaining bytes") } bytes := make([]byte, size) if _, err := io.ReadFull(r, bytes); err != nil { return xerrors.Errorf("cannot read PCR selection bit mask: %w", err) } *d = make(PCRSelect, 0) for i, octet := range bytes { for bit := uint(0); bit < 8; bit++ { if octet&(1<= pr { break } } if found { break } } if !found { added := false for i, so := range out { if so.Hash != sr.Hash { continue } out[i].Select = append(so.Select, pr) added = true break } if !added { out = append(out, PCRSelection{Hash: sr.Hash, Select: []int{pr}}) } } } } mu.MustCopyValue(&out, out) return out } // Remove will remove the PCR selections in r from the PCR selections in l, and return a // new set of selections. func (l PCRSelectionList) Remove(r PCRSelectionList) (out PCRSelectionList) { mu.MustCopyValue(&out, l) mu.MustCopyValue(&r, r) for _, sr := range r { for _, pr := range sr.Select { for i, so := range out { if so.Hash != sr.Hash { continue } for j, po := range so.Select { if po == pr { if j < len(so.Select)-1 { copy(out[i].Select[j:], so.Select[j+1:]) } out[i].Select = so.Select[:len(so.Select)-1] } if po >= pr { break } } } } } for i, so := range out { if len(so.Select) > 0 { continue } if i < len(out)-1 { copy(out[i:], out[i+1:]) } out = out[:len(out)-1] } return } // IsEmpty returns true if the list of PCR selections selects no PCRs. func (l PCRSelectionList) IsEmpty() bool { for _, s := range l { if len(s.Select) > 0 { return false } } return true } // AlgorithmPropertyList is a slice of AlgorithmProperty values, and corresponds to // the TPML_ALG_PROPERTY type. type AlgorithmPropertyList []AlgorithmProperty // TaggedTPMPropertyList is a slice of TaggedProperty values, and corresponds to the // TPML_TAGGED_TPM_PROPERTY type. type TaggedTPMPropertyList []TaggedProperty // TaggedPCRPropertyList is a slice of TaggedPCRSelect values, and corresponds to the // TPML_TAGGED_PCR_PROPERTY type. type TaggedPCRPropertyList []TaggedPCRSelect // ECCCurveList is a slice of ECCCurve values, and corresponds to the TPML_ECC_CURVE type. type ECCCurveList []ECCCurve // TaggedPolicyList is a slice of TaggedPolicy values, and corresponds to the // TPML_TAGGED_POLICY type. type TaggedPolicyList []TaggedPolicy // 10.10) Capabilities Structures // Capabilities is a union type that corresponds to the TPMU_CAPABILITIES type. The // selector type is Capability. Mapping of selector values to fields is as follows: // - CapabilityAlgs: Algorithms // - CapabilityHandles: Handles // - CapabilityCommands: Command // - CapabilityPPCommands: PPCommands // - CapabilityAuditCommands: AuditCommands // - CapabilityPCRs: AssignedPCR // - CapabilityTPMProperties: TPMProperties // - CapabilityPCRProperties: PCRProperties // - CapabilityECCCurves: ECCCurves // - CapabilityAuthPolicies: AuthPolicies type CapabilitiesU struct { Algorithms AlgorithmPropertyList Handles HandleList Command CommandAttributesList PPCommands CommandCodeList AuditCommands CommandCodeList AssignedPCR PCRSelectionList TPMProperties TaggedTPMPropertyList PCRProperties TaggedPCRPropertyList ECCCurves ECCCurveList AuthPolicies TaggedPolicyList } func (c *CapabilitiesU) Select(selector reflect.Value) interface{} { switch selector.Interface().(Capability) { case CapabilityAlgs: return &c.Algorithms case CapabilityHandles: return &c.Handles case CapabilityCommands: return &c.Command case CapabilityPPCommands: return &c.PPCommands case CapabilityAuditCommands: return &c.AuditCommands case CapabilityPCRs: return &c.AssignedPCR case CapabilityTPMProperties: return &c.TPMProperties case CapabilityPCRProperties: return &c.PCRProperties case CapabilityECCCurves: return &c.ECCCurves case CapabilityAuthPolicies: return &c.AuthPolicies default: return nil } } // CapabilityData corresponds to the TPMS_CAPABILITY_DATA type, and is returned by // TPMContext.GetCapability. type CapabilityData struct { Capability Capability // Capability Data *CapabilitiesU // Capability data } // 10.11 Clock/Counter Structures // ClockInfo corresponds to the TPMS_CLOCK_INFO type. type ClockInfo struct { Clock uint64 // Time value in milliseconds that increments whilst the TPM is powered ResetCount uint32 // Number of TPM resets since the TPM was last cleared // RestartCount is the number of TPM restarts or resumes since the last TPM reset or the last time the TPM was cleared. RestartCount uint32 // Safe indicates the the value reported by Clock is guaranteed to be unique for the current owner. Safe bool } // TimeInfo corresponds to the TPMS_TIME_INFO type. type TimeInfo struct { Time uint64 // Time value in milliseconds since the last TPM startup ClockInfo ClockInfo // Clock information } // 10.12 Attestation Structures // TimeAttestInfo corresponds to the TPMS_TIME_ATTEST_INFO type, and is returned by // TPMContext.GetTime. type TimeAttestInfo struct { Time TimeInfo // Time information FirmwareVersion uint64 // TPM vendor specific value indicating the version of the firmware } // CertifyInfo corresponds to the TPMS_CERTIFY_INFO type, and is returned by TPMContext.Certify. type CertifyInfo struct { Name Name // Name of the certified object QualifiedName Name // Qualified name of the certified object } // QuoteInfo corresponds to the TPMS_QUOTE_INFO type, and is returned by TPMContext.Quote. type QuoteInfo struct { PCRSelect PCRSelectionList // PCRs included in PCRDigest PCRDigest Digest // Digest of the selected PCRs, using the hash algorithm of the signing key } // CommandAuditInfo corresponds to the TPMS_COMMAND_AUDIT_INFO type, and is returned by // TPMContext.GetCommandAuditDigest. type CommandAuditInfo struct { AuditCounter uint64 // Monotonic audit counter DigestAlg AlgorithmId // Hash algorithm used for the command audit AuditDigest Digest // Current value of the audit digest CommandDigest Digest // Digest of command codes being audited, using DigestAlg } // SessionAuditInfo corresponds to the TPMS_SESSION_AUDIT_INFO type, and is returned by // TPMContext.GetSessionAuditDigest. type SessionAuditInfo struct { // ExclusiveSession indicates the current exclusive status of the session. It is true if all of the commands recorded in // SessionDigest were executed without any intervening commands that did not use // the audit session. ExclusiveSession bool SessionDigest Digest // Current value of the session audit digest } // CreationInfo corresponds to the TPMS_CREATION_INFO type, and is returned by TPMContext.CertifyCreation. type CreationInfo struct { ObjectName Name // Name of the object CreationHash Digest } // NVCertifyInfo corresponds to the TPMS_NV_CERTIFY_INFO type, and is returned by TPMContext.NVCertify. type NVCertifyInfo struct { IndexName Name // Name of the NV index Offset uint16 // Offset parameter of TPMContext.NVCertify NVContents MaxNVBuffer // Contents of the NV index } // AttestU is a union type that corresponds to the TPMU_ATTEST type. The selector type is StructTag. // Mapping of selector values to fields is as follows: // - TagAttestNV: NV // - TagAttestCommandAudit: CommandAudit // - TagAttestSessionAudit: SessionAudit // - TagAttestCertify: Certify // - TagAttestQuote: Quote // - TagAttestTime: Time // - TagAttestCreation: Creation type AttestU struct { Certify *CertifyInfo Creation *CreationInfo Quote *QuoteInfo CommandAudit *CommandAuditInfo SessionAudit *SessionAuditInfo Time *TimeAttestInfo NV *NVCertifyInfo } func (a *AttestU) Select(selector reflect.Value) interface{} { switch selector.Interface().(StructTag) { case TagAttestNV: return &a.NV case TagAttestCommandAudit: return &a.CommandAudit case TagAttestSessionAudit: return &a.SessionAudit case TagAttestCertify: return &a.Certify case TagAttestQuote: return &a.Quote case TagAttestTime: return &a.Time case TagAttestCreation: return &a.Creation default: return nil } } // Attest corresponds to the TPMS_ATTEST type, and is returned by the attestation commands. The // signature of the attestation is over this structure. type Attest struct { Magic TPMGenerated // Always TPMGeneratedValue Type StructTag // Type of the attestation structure QualifiedSigner Name // Qualified name of the signing key ExtraData Data // External information provided by the caller ClockInfo ClockInfo // Clock information FirmwareVersion uint64 // TPM vendor specific value indicating the version of the firmware Attested *AttestU `tpm2:"selector:Type"` // Type specific attestation data } // 10.13) Authorization Structures // AuthCommand corresppnds to the TPMS_AUTH_COMMAND type, and represents an authorization // for a command. type AuthCommand struct { SessionHandle Handle Nonce Nonce SessionAttributes SessionAttributes HMAC Auth } // AuthResponse corresponds to the TPMS_AUTH_RESPONSE type, and represents an authorization // response for a command. type AuthResponse struct { Nonce Nonce SessionAttributes SessionAttributes HMAC Auth } ./github.com/canonical/go-tpm2/util/0000775000000000000000000000000000000000000015574 5ustar0000000000000000./github.com/canonical/go-tpm2/util/cphash.go0000664000000000000000000000222100000000000017366 0ustar0000000000000000// Copyright 2019 Canonical Ltd. // Licensed under the LGPLv3 with static-linking exception. // See LICENCE file for details. package util import ( "errors" "github.com/canonical/go-tpm2" "github.com/canonical/go-tpm2/mu" ) // ComputeCpHash computes a command parameter digest from the specified command code, the supplied // handles (identified by their names) and parameters using the specified digest algorithm. // // The required parameters is defined in part 3 of the TPM 2.0 Library Specification for the // specific command. // // The result of this is useful for extended authorization commands that bind an authorization to // a command and set of command parameters, such as TPMContext.PolicySigned, TPMContext.PolicySecret, // TPMContext.PolicyTicket and TPMContext.PolicyCpHash. func ComputeCpHash(alg tpm2.HashAlgorithmId, command tpm2.CommandCode, handles []tpm2.Name, params ...interface{}) (tpm2.Digest, error) { if !alg.Available() { return nil, errors.New("algorithm is not available") } cpBytes, err := mu.MarshalToBytes(params...) if err != nil { return nil, err } return tpm2.ComputeCpHash(alg, command, handles, cpBytes), nil } ./github.com/canonical/go-tpm2/util/credential.go0000664000000000000000000000277500000000000020250 0ustar0000000000000000// Copyright 2021 Canonical Ltd. // Licensed under the LGPLv3 with static-linking exception. // See LICENCE file for details. package util import ( "errors" "github.com/canonical/go-tpm2" "github.com/canonical/go-tpm2/mu" ) // MakeCredential performs the duties of a certificate authority in order to // create an activation credential. It produces a seed and encrypts this with // the supplied public key. The seed and supplied object name is then used to // apply an outer wrapper to the credential. // // The encrypted credential blob and encrypted seed are returned, and these can // be passed to the TPM2_ActivateCredential on the TPM on which both the private // part of key and the object associated with objectName are loaded. func MakeCredential(key *tpm2.Public, credential tpm2.Digest, objectName tpm2.Name) (credentialBlob tpm2.IDObjectRaw, secret tpm2.EncryptedSecret, err error) { if !key.IsStorageParent() || !key.IsAsymmetric() { return nil, nil, errors.New("key must be an asymmetric storage parent") } if !key.NameAlg.Available() { return nil, nil, errors.New("name algorithm for key is not available") } secret, seed, err := tpm2.CryptSecretEncrypt(key, []byte(tpm2.IdentityKey)) if err != nil { return nil, nil, err } credentialBlob = mu.MustMarshalToBytes(credential) credentialBlob, err = ProduceOuterWrap(key.NameAlg, &key.Params.AsymDetail(key.Type).Symmetric, objectName, seed, false, credentialBlob) if err != nil { return nil, nil, err } return credentialBlob, secret, nil } ./github.com/canonical/go-tpm2/util/crypto.go0000664000000000000000000000346700000000000017455 0ustar0000000000000000// Copyright 2021 Canonical Ltd. // Licensed under the LGPLv3 with static-linking exception. // See LICENCE file for details. package util import ( "crypto" "crypto/ecdsa" "crypto/rand" "crypto/rsa" "errors" "math/big" "golang.org/x/xerrors" "github.com/canonical/go-tpm2" "github.com/canonical/go-tpm2/internal" "github.com/canonical/go-tpm2/mu" ) func zeroExtendBytes(x *big.Int, l int) (out []byte) { out = make([]byte, l) tmp := x.Bytes() copy(out[len(out)-len(tmp):], tmp) return } // CryptSecretDecrypt recovers a secret value from the supplied secret structure // using the private key. It can be used to recover secrets created by the TPM, // such as those created by the TPM2_Duplicate command. func CryptSecretDecrypt(priv crypto.PrivateKey, hashAlg tpm2.HashAlgorithmId, label []byte, secret tpm2.EncryptedSecret) ([]byte, error) { if !hashAlg.Available() { return nil, errors.New("digest algorithm is not available") } switch p := priv.(type) { case *rsa.PrivateKey: h := hashAlg.NewHash() label0 := make([]byte, len(label)+1) copy(label0, label) return rsa.DecryptOAEP(h, rand.Reader, p, secret, label0) case *ecdsa.PrivateKey: var ephPoint tpm2.ECCPoint if _, err := mu.UnmarshalFromBytes(secret, &ephPoint); err != nil { return nil, xerrors.Errorf("cannot unmarshal ephemeral point: %w", err) } ephX := new(big.Int).SetBytes(ephPoint.X) ephY := new(big.Int).SetBytes(ephPoint.Y) if !p.Curve.IsOnCurve(ephX, ephY) { return nil, errors.New("ephemeral point is not on curve") } sz := p.Curve.Params().BitSize / 8 mulX, _ := p.Curve.ScalarMult(ephX, ephY, p.D.Bytes()) return internal.KDFe(hashAlg.GetHash(), zeroExtendBytes(mulX, sz), label, ephPoint.X, zeroExtendBytes(p.X, sz), hashAlg.Size()*8), nil default: return nil, errors.New("unsupported key type") } } ./github.com/canonical/go-tpm2/util/doc.go0000664000000000000000000000013600000000000016670 0ustar0000000000000000/* Package util contains helper functions that are useful when using go-tpm2. */ package util ./github.com/canonical/go-tpm2/util/duplication.go0000664000000000000000000001057100000000000020442 0ustar0000000000000000// Copyright 2021 Canonical Ltd. // Licensed under the LGPLv3 with static-linking exception. // See LICENCE file for details. package util import ( "crypto" "errors" "golang.org/x/xerrors" "github.com/canonical/go-tpm2" ) // UnwrapDuplicationObjectToSensitive unwraps the supplied duplication object and returns the // corresponding sensitive area. If inSymSeed is supplied, then it is assumed that the object // has an outer wrapper. In this case, privKey, parentNameAlg and parentSymmetricAlg must be // supplied - privKey is the key with which inSymSeed is protected, parentNameAlg is the name // algorithm for the parent key (and must not be HashAlgorithmNull), and parentSymmetricAlg // defines the symmetric algorithm for the parent key (and the Algorithm field must not be // SymObjectAlgorithmNull). // // If symmetricAlg is supplied and the Algorithm field is not SymObjectAlgorithmNull, then it is // assumed that the object has an inner wrapper. In this case, the symmetric key for the inner // wrapper must be supplied using the encryptionKey argument. func UnwrapDuplicationObjectToSensitive(duplicate tpm2.Private, public *tpm2.Public, privKey crypto.PrivateKey, parentNameAlg tpm2.HashAlgorithmId, parentSymmetricAlg *tpm2.SymDefObject, encryptionKey tpm2.Data, inSymSeed tpm2.EncryptedSecret, symmetricAlg *tpm2.SymDefObject) (*tpm2.Sensitive, error) { var seed []byte if len(inSymSeed) > 0 { if privKey == nil { return nil, errors.New("parent private key is required for outer wrapper") } var err error seed, err = CryptSecretDecrypt(privKey, parentNameAlg, []byte(tpm2.DuplicateString), inSymSeed) if err != nil { return nil, xerrors.Errorf("cannot decrypt symmetric seed: %w", err) } } name, err := public.Name() if err != nil { return nil, xerrors.Errorf("cannot compute name: %w", err) } return DuplicateToSensitive(duplicate, name, parentNameAlg, parentSymmetricAlg, seed, symmetricAlg, encryptionKey) } // CreateDuplicationObjectFromSensitive creates a duplication object that can be imported in to a // TPM from the supplied sensitive area. // // If symmetricAlg is supplied and the Algorithm field is not SymObjectAlgorithmNull, this function // will apply an inner wrapper to the duplication object. If encryptionKeyIn is supplied, it will be // used as the symmetric key for the inner wrapper. It must have a size appropriate for the selected // symmetric algorithm. If encryptionKeyIn is not supplied, a symmetric key will be created and // returned // // If parentPublic is supplied, an outer wrapper will be applied to the duplication object. The // parentPublic argument should correspond to the public area of the storage key to which the // duplication object will be imported. When applying the outer wrapper, the seed used to derice the // symmetric key and HMAC key will be encrypted using parentPublic and returned. func CreateDuplicationObjectFromSensitive(sensitive *tpm2.Sensitive, public, parentPublic *tpm2.Public, encryptionKeyIn tpm2.Data, symmetricAlg *tpm2.SymDefObject) (encryptionKeyOut tpm2.Data, duplicate tpm2.Private, outSymSeed tpm2.EncryptedSecret, err error) { if public.Attrs&(tpm2.AttrFixedTPM|tpm2.AttrFixedParent) != 0 { return nil, nil, nil, errors.New("object must be a duplication root") } if public.Attrs&tpm2.AttrEncryptedDuplication != 0 { if symmetricAlg == nil || symmetricAlg.Algorithm == tpm2.SymObjectAlgorithmNull { return nil, nil, nil, errors.New("symmetric algorithm must be supplied for an object with AttrEncryptedDuplication") } if parentPublic == nil { return nil, nil, nil, errors.New("parent object must be supplied for an object with AttrEncryptedDuplication") } } name, err := public.Name() if err != nil { return nil, nil, nil, xerrors.Errorf("cannot compute name: %w", err) } var seed []byte if parentPublic != nil { if !parentPublic.IsStorageParent() || !parentPublic.IsAsymmetric() { return nil, nil, nil, errors.New("parent object must be an asymmetric storage key") } outSymSeed, seed, err = tpm2.CryptSecretEncrypt(parentPublic, []byte(tpm2.DuplicateString)) if err != nil { return nil, nil, nil, xerrors.Errorf("cannot create encrypted symmetric seed: %w", err) } } encryptionKeyOut, duplicate, err = SensitiveToDuplicate(sensitive, name, parentPublic, seed, symmetricAlg, encryptionKeyIn) if err != nil { return nil, nil, nil, err } return encryptionKeyOut, duplicate, outSymSeed, nil } ./github.com/canonical/go-tpm2/util/keys.go0000664000000000000000000001625000000000000017102 0ustar0000000000000000// Copyright 2021 Canonical Ltd. // Licensed under the LGPLv3 with static-linking exception. // See LICENCE file for details. package util import ( "crypto/ecdsa" "crypto/elliptic" "crypto/rand" "crypto/rsa" "github.com/canonical/go-tpm2" "github.com/canonical/go-tpm2/templates" ) // NewExternalRSAPublicKey creates a public area from the supplied RSA // public key with the specified name algorithm, key usage and scheme, // for use with the TPM2_LoadExternal command. If nameAlg is // HashAlgorithmNull, then HashAlgorithmSHA256 is used. If no usage is // specified, the public area will include both sign and decrypt attributes. func NewExternalRSAPublicKey(nameAlg tpm2.HashAlgorithmId, usage templates.KeyUsage, scheme *tpm2.RSAScheme, key *rsa.PublicKey) *tpm2.Public { pub := templates.NewRSAKey(nameAlg, usage, scheme, uint16(len(key.N.Bytes())*8)) pub.Attrs &^= (tpm2.AttrFixedTPM | tpm2.AttrFixedParent | tpm2.AttrSensitiveDataOrigin | tpm2.AttrUserWithAuth) pub.Params.RSADetail.Exponent = uint32(key.E) pub.Unique = &tpm2.PublicIDU{RSA: key.N.Bytes()} return pub } // NewExternalRSAPublicKeyWithDefaults creates a public area from the // supplied RSA with the specified key usage, SHA256 as the name algorithm // and the scheme unset, for use with the TPM2_LoadExternal command. If no // usage is specified, the public area will include both sign and decrypt // attributes. func NewExternalRSAPublicKeyWithDefaults(usage templates.KeyUsage, key *rsa.PublicKey) *tpm2.Public { return NewExternalRSAPublicKey(tpm2.HashAlgorithmNull, usage, nil, key) } // NewExternalECCPublicKey creates a public area from the supplied // elliptic public key with the specified name algorithm, key usage // and scheme, for use with the TPM2_LoadExternal command. If nameAlg // is HashAlgorithmNull, then HashAlgorithmSHA256 is used. If no usage is // specified, the public area will include both sign and decrypt attributes. func NewExternalECCPublicKey(nameAlg tpm2.HashAlgorithmId, usage templates.KeyUsage, scheme *tpm2.ECCScheme, key *ecdsa.PublicKey) *tpm2.Public { var curve tpm2.ECCCurve switch key.Curve { case elliptic.P224(): curve = tpm2.ECCCurveNIST_P224 case elliptic.P256(): curve = tpm2.ECCCurveNIST_P256 case elliptic.P384(): curve = tpm2.ECCCurveNIST_P384 case elliptic.P521(): curve = tpm2.ECCCurveNIST_P521 default: panic("unsupported curve") } pub := templates.NewECCKey(nameAlg, usage, scheme, curve) pub.Attrs &^= (tpm2.AttrFixedTPM | tpm2.AttrFixedParent | tpm2.AttrSensitiveDataOrigin | tpm2.AttrUserWithAuth) pub.Unique = &tpm2.PublicIDU{ ECC: &tpm2.ECCPoint{ X: zeroExtendBytes(key.X, key.Params().BitSize/8), Y: zeroExtendBytes(key.Y, key.Params().BitSize/8)}} return pub } // NewExternalECCPublicKeyWithDefaults creates a public area from the // supplied elliptic public key with the specified key usage, SHA256 // as the name algorithm and the scheme unset, for use with the // TPM2_LoadExternal command. If no usage is specified, the public area // will include both sign and decrypt attributes. func NewExternalECCPublicKeyWithDefaults(usage templates.KeyUsage, key *ecdsa.PublicKey) *tpm2.Public { return NewExternalECCPublicKey(tpm2.HashAlgorithmNull, usage, nil, key) } // NewExternalSealedObject creates both the public and sensitive areas for a // sealed object containing the supplied data, with the specified name // algorithm and authorization value. If nameAlgorithm is HashAlgorithmNull, // then HashAlgorithmSHA256 is used. // // It will panic if authValue is larger than the size of the name algorithm. // // The returned public and sensitive areas can be made into a duplication // object with CreateDuplicationObjectFromSensitive for importing into a TPM. // // The public area has the AttrUserWithAuth set in order to permit authentication // for the user auth role using the sensitive area's authorization value. In order // to require authentication for the user auth role using an authorization policy, // remove the AttrUserWithAuth attribute. func NewExternalSealedObject(nameAlg tpm2.HashAlgorithmId, authValue tpm2.Auth, data []byte) (*tpm2.Public, *tpm2.Sensitive) { pub := templates.NewSealedObject(nameAlg) pub.Attrs &^= (tpm2.AttrFixedTPM | tpm2.AttrFixedParent) if len(authValue) > pub.NameAlg.Size() { panic("authValue too large") } sensitive := &tpm2.Sensitive{ Type: tpm2.ObjectTypeKeyedHash, AuthValue: make(tpm2.Auth, pub.NameAlg.Size()), SeedValue: make(tpm2.Digest, pub.NameAlg.Size()), Sensitive: &tpm2.SensitiveCompositeU{Bits: data}} copy(sensitive.AuthValue, authValue) rand.Read(sensitive.SeedValue) h := nameAlg.NewHash() h.Write(sensitive.SeedValue) h.Write(sensitive.Sensitive.Bits) pub.Unique = &tpm2.PublicIDU{KeyedHash: h.Sum(nil)} return pub, sensitive } // NewExternalHMACKey creates both the public and sensitive areas for the // supplied HMAC key with the specified name algorithm, scheme algorithm // and auth value. If nameAlg is HashAlgorithmNull, then HashAlgorithmSHA256 // is used. If schemeAlg is HashAlgorithmNull, then nameAlg is used. // // It will panic if authValue is larger than the size of the name algorithm. // // The returned public and sensitive areas can be made into a duplication // object with CreateDuplicationObjectFromSensitive for importing into a TPM. // // The public area has the AttrUserWithAuth set in order to permit authentication // for the user auth role using the sensitive area's authorization value. In order // to require authentication for the user auth role using an authorization policy, // remove the AttrUserWithAuth attribute. func NewExternalHMACKey(nameAlg, schemeAlg tpm2.HashAlgorithmId, authValue tpm2.Auth, key []byte) (*tpm2.Public, *tpm2.Sensitive) { pub := templates.NewHMACKey(nameAlg, schemeAlg) pub.Attrs &^= (tpm2.AttrFixedTPM | tpm2.AttrFixedParent) if len(authValue) > pub.NameAlg.Size() { panic("authValue too large") } sensitive := &tpm2.Sensitive{ Type: tpm2.ObjectTypeKeyedHash, AuthValue: make(tpm2.Auth, pub.NameAlg.Size()), SeedValue: make(tpm2.Digest, pub.NameAlg.Size()), Sensitive: &tpm2.SensitiveCompositeU{Bits: key}} copy(sensitive.AuthValue, authValue) rand.Read(sensitive.SeedValue) h := pub.NameAlg.NewHash() h.Write(sensitive.SeedValue) h.Write(sensitive.Sensitive.Bits) pub.Unique = &tpm2.PublicIDU{KeyedHash: h.Sum(nil)} return pub, sensitive } // NewExternalHMACKeyWithDefaults creates both the public and sensitive // areas for the supplied HMAC key with the specified auth value and with // SHA256 as both the name and scheme algorithm. // // It will panic if authValue is larger than the size of the name algorithm. // // The returned public and sensitive areas can be made into a duplication // object with CreateDuplicationObjectFromSensitive for importing into a TPM. // // The public area has the AttrUserWithAuth set in order to permit authentication // for the user auth role using the sensitive area's authorization value. In order // to require authentication for the user auth role using an authorization policy, // remove the AttrUserWithAuth attribute. func NewExternalHMACKeyWithDefaults(authValue tpm2.Auth, key []byte) (*tpm2.Public, *tpm2.Sensitive) { return NewExternalHMACKey(tpm2.HashAlgorithmNull, tpm2.HashAlgorithmNull, authValue, key) } ./github.com/canonical/go-tpm2/util/object.go0000664000000000000000000002412600000000000017376 0ustar0000000000000000// Copyright 2021 Canonical Ltd. // Licensed under the LGPLv3 with static-linking exception. // See LICENCE file for details. package util import ( "bytes" "crypto/hmac" "crypto/rand" "errors" "hash" "io/ioutil" "golang.org/x/xerrors" "github.com/canonical/go-tpm2" "github.com/canonical/go-tpm2/internal" "github.com/canonical/go-tpm2/mu" ) // UnwrapOuter removes an outer wrapper from the supplied sensitive data blob. The // supplied name is associated with the data. // // It checks the integrity HMAC is valid using the specified digest algorithm and // a key derived from the supplied seed and returns an error if the check fails. // // It then decrypts the data blob using the specified symmetric algorithm and a // key derived from the supplied seed and name. func UnwrapOuter(hashAlg tpm2.HashAlgorithmId, symmetricAlg *tpm2.SymDefObject, name tpm2.Name, seed []byte, useIV bool, data []byte) ([]byte, error) { if !hashAlg.Available() { return nil, errors.New("digest algorithm is not available") } if !symmetricAlg.Algorithm.IsValidBlockCipher() { return nil, errors.New("symmetric algorithm is not a valid block cipher") } r := bytes.NewReader(data) var integrity []byte if _, err := mu.UnmarshalFromReader(r, &integrity); err != nil { return nil, xerrors.Errorf("cannot unmarshal integrity digest: %w", err) } data, _ = ioutil.ReadAll(r) hmacKey := internal.KDFa(hashAlg.GetHash(), seed, []byte(tpm2.IntegrityKey), nil, nil, hashAlg.Size()*8) h := hmac.New(func() hash.Hash { return hashAlg.NewHash() }, hmacKey) h.Write(data) h.Write(name) if !bytes.Equal(h.Sum(nil), integrity) { return nil, errors.New("integrity digest is invalid") } r = bytes.NewReader(data) iv := make([]byte, symmetricAlg.Algorithm.BlockSize()) if useIV { if _, err := mu.UnmarshalFromReader(r, &iv); err != nil { return nil, xerrors.Errorf("cannot unmarshal IV: %w", err) } if len(iv) != symmetricAlg.Algorithm.BlockSize() { return nil, errors.New("IV has the wrong size") } } data, _ = ioutil.ReadAll(r) symKey := internal.KDFa(hashAlg.GetHash(), seed, []byte(tpm2.StorageKey), name, nil, int(symmetricAlg.KeyBits.Sym)) if err := tpm2.CryptSymmetricDecrypt(tpm2.SymAlgorithmId(symmetricAlg.Algorithm), symKey, iv, data); err != nil { return nil, xerrors.Errorf("cannot decrypt: %w", err) } return data, nil } // ProduceOuterWrap adds an outer wrapper to the supplied data. The supplied name // is associated with the data. // // It encrypts the data using the specified symmetric algorithm and a key derived // from the supplied seed and name. // // It then prepends an integrity HMAC of the encrypted data and the supplied // name using the specified digest algorithm and a key derived from the supplied // seed. func ProduceOuterWrap(hashAlg tpm2.HashAlgorithmId, symmetricAlg *tpm2.SymDefObject, name tpm2.Name, seed []byte, useIV bool, data []byte) ([]byte, error) { if !hashAlg.Available() { return nil, errors.New("digest algorithm is not available") } if !symmetricAlg.Algorithm.IsValidBlockCipher() { return nil, errors.New("symmetric algorithm is not a valid block cipher") } iv := make([]byte, symmetricAlg.Algorithm.BlockSize()) if useIV { if _, err := rand.Read(iv); err != nil { return nil, xerrors.Errorf("cannot generate IV: %w", err) } } symKey := internal.KDFa(hashAlg.GetHash(), seed, []byte(tpm2.StorageKey), name, nil, int(symmetricAlg.KeyBits.Sym)) if err := tpm2.CryptSymmetricEncrypt(tpm2.SymAlgorithmId(symmetricAlg.Algorithm), symKey, iv, data); err != nil { return nil, xerrors.Errorf("cannot encrypt: %w", err) } if useIV { data = mu.MustMarshalToBytes(iv, mu.RawBytes(data)) } hmacKey := internal.KDFa(hashAlg.GetHash(), seed, []byte(tpm2.IntegrityKey), nil, nil, hashAlg.Size()*8) h := hmac.New(func() hash.Hash { return hashAlg.NewHash() }, hmacKey) h.Write(data) h.Write(name) integrity := h.Sum(nil) return mu.MustMarshalToBytes(integrity, mu.RawBytes(data)), nil } func PrivateToSensitive(private tpm2.Private, name tpm2.Name, hashAlg tpm2.HashAlgorithmId, symmetricAlg *tpm2.SymDefObject, seed []byte) (sensitive *tpm2.Sensitive, err error) { data, err := UnwrapOuter(hashAlg, symmetricAlg, name, seed, true, private) if err != nil { return nil, xerrors.Errorf("cannot unwrap outer wrapper: %w", err) } if _, err := mu.UnmarshalFromBytes(data, mu.Sized(&sensitive)); err != nil { return nil, xerrors.Errorf("cannot unmarhsal sensitive: %w", err) } return sensitive, nil } func SensitiveToPrivate(sensitive *tpm2.Sensitive, name tpm2.Name, hashAlg tpm2.HashAlgorithmId, symmetricAlg *tpm2.SymDefObject, seed []byte) (tpm2.Private, error) { private, err := mu.MarshalToBytes(mu.Sized(sensitive)) if err != nil { return nil, xerrors.Errorf("cannot marshal sensitive: %w", err) } private, err = ProduceOuterWrap(hashAlg, symmetricAlg, name, seed, true, private) if err != nil { return nil, xerrors.Errorf("cannot apply outer wrapper: %w", err) } return private, nil } // DuplicateToSensitive unwraps the supplied duplication blob. The supplied name // is the name of the duplication object. // // If a seed is supplied, it removes the outer wrapper using the specified parent // name algorithm and parent symmetric algorithm - these correspond to properties of // the new parent's public area. // // If symmetricAlg is supplied, it removes the inner wrapper - first by decrypting // it with the supplied innerSymKey, and then checking the inner integrity digest // is valid and returning an error if it isn't. func DuplicateToSensitive(duplicate tpm2.Private, name tpm2.Name, parentNameAlg tpm2.HashAlgorithmId, parentSymmetricAlg *tpm2.SymDefObject, seed []byte, symmetricAlg *tpm2.SymDefObject, innerSymKey tpm2.Data) (sensitive *tpm2.Sensitive, err error) { if len(seed) > 0 { // Remove outer wrapper if parentSymmetricAlg == nil { return nil, errors.New("missing parent symmetric algorithm") } var err error duplicate, err = UnwrapOuter(parentNameAlg, parentSymmetricAlg, name, seed, false, duplicate) if err != nil { return nil, xerrors.Errorf("cannot unwrap outer wrapper: %w", err) } } if symmetricAlg != nil && symmetricAlg.Algorithm != tpm2.SymObjectAlgorithmNull { // Remove inner wrapper if name.Algorithm() == tpm2.HashAlgorithmNull { return nil, errors.New("invalid name") } if !name.Algorithm().Available() { return nil, errors.New("name algorithm is not available") } if !symmetricAlg.Algorithm.IsValidBlockCipher() { return nil, errors.New("inner symmetric algorithm is not a valid block cipher") } if err := tpm2.CryptSymmetricDecrypt(tpm2.SymAlgorithmId(symmetricAlg.Algorithm), innerSymKey, make([]byte, symmetricAlg.Algorithm.BlockSize()), duplicate); err != nil { return nil, xerrors.Errorf("cannot decrypt inner wrapper: %w", err) } r := bytes.NewReader(duplicate) var innerIntegrity []byte if _, err := mu.UnmarshalFromReader(r, &innerIntegrity); err != nil { return nil, xerrors.Errorf("cannot unmarshal inner integrity digest: %w", err) } duplicate, _ = ioutil.ReadAll(r) h := name.Algorithm().NewHash() h.Write(duplicate) h.Write(name) if !bytes.Equal(h.Sum(nil), innerIntegrity) { return nil, errors.New("inner integrity digest is invalid") } } if _, err := mu.UnmarshalFromBytes(duplicate, mu.Sized(&sensitive)); err != nil { return nil, xerrors.Errorf("cannot unmarhsal sensitive: %w", err) } return sensitive, nil } // SensitiveToDuplicate creates a duplication blob from the supplied sensitive structure. // The supplied name is the name of the object associated with sensitive. // // If symmetricAlg is defined, an inner wrapper will be applied, first by prepending // an inner integrity digest computed with the object's name algorithm from the sensitive // data and its name, and then encrypting the data with innerSymKey. If innerSymKey isn't // supplied, a random key will be created and returned. // // If a seed is supplied, an outer wrapper will be applied using the name algorithm and // symmetric algorithm of parent. func SensitiveToDuplicate(sensitive *tpm2.Sensitive, name tpm2.Name, parent *tpm2.Public, seed []byte, symmetricAlg *tpm2.SymDefObject, innerSymKey tpm2.Data) (innerSymKeyOut tpm2.Data, duplicate tpm2.Private, err error) { applyInnerWrapper := false if symmetricAlg != nil && symmetricAlg.Algorithm != tpm2.SymObjectAlgorithmNull { applyInnerWrapper = true } applyOuterWrapper := false if len(seed) > 0 { applyOuterWrapper = true } duplicate, err = mu.MarshalToBytes(mu.Sized(sensitive)) if err != nil { return nil, nil, xerrors.Errorf("cannot marshal sensitive: %w", err) } if applyInnerWrapper { if name.Algorithm() == tpm2.HashAlgorithmNull { return nil, nil, errors.New("invalid name") } if !name.Algorithm().Available() { return nil, nil, errors.New("name algorithm is not available") } if !symmetricAlg.Algorithm.IsValidBlockCipher() { return nil, nil, errors.New("inner symmetric algorithm is not a valid block cipher") } // Apply inner wrapper h := name.Algorithm().NewHash() h.Write(duplicate) h.Write(name) innerIntegrity := h.Sum(nil) duplicate = mu.MustMarshalToBytes(innerIntegrity, mu.RawBytes(duplicate)) if len(innerSymKey) == 0 { innerSymKey = make([]byte, symmetricAlg.KeyBits.Sym/8) if _, err := rand.Read(innerSymKey); err != nil { return nil, nil, xerrors.Errorf("cannot obtain symmetric key for inner wrapper: %w", err) } innerSymKeyOut = innerSymKey } else if len(innerSymKey) != int(symmetricAlg.KeyBits.Sym/8) { return nil, nil, errors.New("the supplied symmetric key for inner wrapper has the wrong length") } if err := tpm2.CryptSymmetricEncrypt(tpm2.SymAlgorithmId(symmetricAlg.Algorithm), innerSymKey, make([]byte, symmetricAlg.Algorithm.BlockSize()), duplicate); err != nil { return nil, nil, xerrors.Errorf("cannot apply inner wrapper: %w", err) } } if applyOuterWrapper { // Apply outer wrapper var err error duplicate, err = ProduceOuterWrap(parent.NameAlg, &parent.Params.AsymDetail(parent.Type).Symmetric, name, seed, false, duplicate) if err != nil { return nil, nil, xerrors.Errorf("cannot apply outer wrapper: %w", err) } } return innerSymKeyOut, duplicate, nil } ./github.com/canonical/go-tpm2/util/pcr_digest.go0000664000000000000000000000354700000000000020257 0ustar0000000000000000// Copyright 2019 Canonical Ltd. // Licensed under the LGPLv3 with static-linking exception. // See LICENCE file for details. package util import ( "errors" "fmt" "github.com/canonical/go-tpm2" "github.com/canonical/go-tpm2/mu" ) // ComputePCRDigest computes a digest using the specified algorithm from the provided set of PCR // values and the provided PCR selections. The digest is computed the same way as PCRComputeCurrentDigest // as defined in the TPM reference implementation. It is most useful for computing an input to // TPMContext.PolicyPCR or TrialAuthPolicy.PolicyPCR, and for validating quotes and creation data. func ComputePCRDigest(alg tpm2.HashAlgorithmId, pcrs tpm2.PCRSelectionList, values tpm2.PCRValues) (tpm2.Digest, error) { if !alg.Available() { return nil, errors.New("algorithm is not available") } h := alg.NewHash() mu.MustCopyValue(&pcrs, pcrs) for _, s := range pcrs { if _, ok := values[s.Hash]; !ok { return nil, fmt.Errorf("the provided values don't contain digests for the selected PCR bank %v", s.Hash) } for _, i := range s.Select { d, ok := values[s.Hash][i] if !ok { return nil, fmt.Errorf("the provided values don't contain a digest for PCR%d in bank %v", i, s.Hash) } h.Write(d) } } return h.Sum(nil), nil } // ComputePCRDigestFromAllValues computes a digest using the specified algorithm from all of the // provided set of PCR values. The digest is computed the same way as PCRComputeCurrentDigest as // defined in the TPM reference implementation. It returns the PCR selection associated with the // computed digest. func ComputePCRDigestFromAllValues(alg tpm2.HashAlgorithmId, values tpm2.PCRValues) (tpm2.PCRSelectionList, tpm2.Digest, error) { pcrs := values.SelectionList() digest, err := ComputePCRDigest(alg, pcrs, values) if err != nil { return nil, nil, err } return pcrs, digest, nil } ./github.com/canonical/go-tpm2/util/policy.go0000664000000000000000000001657400000000000017437 0ustar0000000000000000// Copyright 2019 Canonical Ltd. // Licensed under the LGPLv3 with static-linking exception. // See LICENCE file for details. package util import ( "encoding/binary" "hash" "github.com/canonical/go-tpm2" "github.com/canonical/go-tpm2/mu" ) // TrialAuthPolicy provides a mechanism for computing authorization policy digests without // having to execute a trial authorization policy session on the TPM. An advantage of this // is that it is possible to compute digests for PolicySecret and PolicyNV assertions // without knowledge of the authorization value of the authorizing entities used for those // commands. type TrialAuthPolicy struct { alg tpm2.HashAlgorithmId digest tpm2.Digest // A policy can only contain one of TPM2_PolicyCpHash, TPM2_PolicyNameHash or // TPM2_PolicyTemplate hashOccupied bool } // ComputeAuthPolicy creates a new context for computing an authorization policy digest. // It will panic if the specified algorithm is not available. The caller should check // this beforehand. func ComputeAuthPolicy(alg tpm2.HashAlgorithmId) *TrialAuthPolicy { if !alg.Available() { panic("unsupported digest algorithm or algorithm not linked in to binary") } return &TrialAuthPolicy{alg: alg, digest: make(tpm2.Digest, alg.Size())} } func (p *TrialAuthPolicy) beginUpdate() (hash.Hash, func()) { h := p.alg.NewHash() h.Write(p.digest) return h, func() { p.digest = h.Sum(nil) } } func (p *TrialAuthPolicy) beginUpdateForCommand(commandCode tpm2.CommandCode) (hash.Hash, func()) { h, end := p.beginUpdate() binary.Write(h, binary.BigEndian, commandCode) return h, end } func (p *TrialAuthPolicy) update(commandCode tpm2.CommandCode, name tpm2.Name, ref tpm2.Nonce) { h, end := p.beginUpdateForCommand(commandCode) h.Write(name) end() h, end = p.beginUpdate() h.Write(ref) end() } func (p *TrialAuthPolicy) reset() { p.digest = make(tpm2.Digest, len(p.digest)) } // GetDigest returns the current digest computed for the policy assertions executed so far. func (p *TrialAuthPolicy) GetDigest() tpm2.Digest { return p.digest } // SetDigests overwrites the current digest. It will panic if the supplied digest is // not the correct size. func (p *TrialAuthPolicy) SetDigest(d tpm2.Digest) { if len(d) != p.alg.Size() { panic("invalid digest length") } p.digest = d } // Reset clears the current digest. func (p *TrialAuthPolicy) Reset() { p.reset() } // PolicySigned computes a TPM2_PolicySigned assertion executed for a key with // the specified name and the specified policyRef. func (p *TrialAuthPolicy) PolicySigned(authName tpm2.Name, policyRef tpm2.Nonce) { p.update(tpm2.CommandPolicySigned, authName, policyRef) } // PolicySecret computes a TPM2_PolicySecret assertion executed for an object // with the specified name and the specified policyRef. func (p *TrialAuthPolicy) PolicySecret(authName tpm2.Name, policyRef tpm2.Nonce) { p.update(tpm2.CommandPolicySecret, authName, policyRef) } // PolicyOR computes a TPM2_PolicyOR assertion executed for the specified // digests. It will panic if there are fewer than 2 or more than 8 digests, // or if any digest has the wrong size. func (p *TrialAuthPolicy) PolicyOR(pHashList tpm2.DigestList) { if len(pHashList) < 2 || len(pHashList) > 8 { panic("invalid number of digests") } p.reset() h, end := p.beginUpdateForCommand(tpm2.CommandPolicyOR) for _, digest := range pHashList { if len(digest) != p.alg.Size() { panic("invalid digest length") } h.Write(digest) } end() } // PolicyPCR computes a TPM2_PolicyPCR assertion executed for the specified // PCR selection and with PCR values associated with the specified PCR digest. func (p *TrialAuthPolicy) PolicyPCR(pcrDigest tpm2.Digest, pcrs tpm2.PCRSelectionList) { if len(pcrDigest) != p.alg.Size() { panic("invalid PCR digest length") } h, end := p.beginUpdateForCommand(tpm2.CommandPolicyPCR) mu.MustMarshalToWriter(h, pcrs) h.Write(pcrDigest) end() } // PolicyNV computes a TPM2_PolicyNV assertion executed for an index for the // specified name, with the specified comparison operation. func (p *TrialAuthPolicy) PolicyNV(nvIndexName tpm2.Name, operandB tpm2.Operand, offset uint16, operation tpm2.ArithmeticOp) { h := p.alg.NewHash() h.Write(operandB) binary.Write(h, binary.BigEndian, offset) binary.Write(h, binary.BigEndian, operation) args := h.Sum(nil) h, end := p.beginUpdateForCommand(tpm2.CommandPolicyNV) h.Write(args) h.Write(nvIndexName) end() } // PolicyCounterTimer computes a TPM2_PolicyCounterTimer assertion for the // specified comparison operation. func (p *TrialAuthPolicy) PolicyCounterTimer(operandB tpm2.Operand, offset uint16, operation tpm2.ArithmeticOp) { h := p.alg.NewHash() h.Write(operandB) binary.Write(h, binary.BigEndian, offset) binary.Write(h, binary.BigEndian, operation) args := h.Sum(nil) h, end := p.beginUpdateForCommand(tpm2.CommandPolicyCounterTimer) h.Write(args) end() } // PolicyCommandCode computes a TPM2_PolicyCommandCode assertion for the // specified command code. func (p *TrialAuthPolicy) PolicyCommandCode(code tpm2.CommandCode) { h, end := p.beginUpdateForCommand(tpm2.CommandPolicyCommandCode) binary.Write(h, binary.BigEndian, code) end() } // PolicyCpHash computes a TPM2_PolicyCpHash assertion for the command parameters // associated with the specified hash. func (p *TrialAuthPolicy) PolicyCpHash(cpHashA tpm2.Digest) { if len(cpHashA) != p.alg.Size() { panic("invalid digest length") } if p.hashOccupied { panic("policy already has a hash") } p.hashOccupied = true h, end := p.beginUpdateForCommand(tpm2.CommandPolicyCpHash) h.Write(cpHashA) end() } // PolicyNameHash computes a TPM2_PolicyNameHash assertion with for the entities // associated with the specified name digest. func (p *TrialAuthPolicy) PolicyNameHash(nameHash tpm2.Digest) { if len(nameHash) != p.alg.Size() { panic("invalid digest length") } if p.hashOccupied { panic("policy already has a hash") } p.hashOccupied = true h, end := p.beginUpdateForCommand(tpm2.CommandPolicyNameHash) h.Write(nameHash) end() } // PolicyDuplicationSelect computes a TPM2_PolicyDuplicationSelect assertion for // the object and parent object with the specified names. func (p *TrialAuthPolicy) PolicyDuplicationSelect(objectName, newParentName tpm2.Name, includeObject bool) { h, end := p.beginUpdateForCommand(tpm2.CommandPolicyDuplicationSelect) if includeObject { h.Write(objectName) } h.Write(newParentName) binary.Write(h, binary.BigEndian, includeObject) end() } // PolicyAuthorize computes a TPM2_PolicyAuthorize assertion for the key with the // specified name and the specified policyRef. func (p *TrialAuthPolicy) PolicyAuthorize(policyRef tpm2.Nonce, keySign tpm2.Name) { p.update(tpm2.CommandPolicyAuthorize, keySign, policyRef) } // PolicyAuthValue computes a TPM2_PolicyAuthValue assertion. func (p *TrialAuthPolicy) PolicyAuthValue() { _, end := p.beginUpdateForCommand(tpm2.CommandPolicyAuthValue) end() } // PolicyPassword computes a TPM2_PolicyPassword assertion. func (p *TrialAuthPolicy) PolicyPassword() { // This extends the same value as PolicyAuthValue - see section 23.18 of part 3 of the "TPM 2.0 Library // Specification" _, end := p.beginUpdateForCommand(tpm2.CommandPolicyAuthValue) end() } // PolicyNvWritten computes a TPM2_PolicyNvWritten assertion func (p *TrialAuthPolicy) PolicyNvWritten(writtenSet bool) { h, end := p.beginUpdateForCommand(tpm2.CommandPolicyNvWritten) binary.Write(h, binary.BigEndian, writtenSet) end() } ./github.com/canonical/go-tpm2/util/qn.go0000664000000000000000000000273400000000000016547 0ustar0000000000000000// Copyright 2021 Canonical Ltd. // Licensed under the LGPLv3 with static-linking exception. // See LICENCE file for details. package util import ( "errors" "github.com/canonical/go-tpm2" "github.com/canonical/go-tpm2/mu" ) // ComputeQualifiedName can compute the qualified name of an object with // the specified name that is protected by a parent with the specified // qualified name. func ComputeQualifiedName(name, parentQn tpm2.Name) (tpm2.Name, error) { if name.Algorithm() == tpm2.HashAlgorithmNull { return nil, errors.New("invalid name") } if !name.Algorithm().Available() { return nil, errors.New("name algorithm is not available") } h := name.Algorithm().NewHash() h.Write(parentQn) h.Write(name) return mu.MarshalToBytes(name.Algorithm(), mu.RawBytes(h.Sum(nil))) } // ComputeQualifiedNameFull can compute the qualified name of an object with // the specified name that is protected in the specified hierarchy by the chain // of parent objects with the specified names. The ancestor names are ordered // from the primary key towards the immediate parent. func ComputeQualifiedNameFull(name tpm2.Name, hierarchy tpm2.Handle, ancestors ...tpm2.Name) (tpm2.Name, error) { lastQn := tpm2.Name(mu.MustMarshalToBytes(hierarchy)) for len(ancestors) > 0 { current := ancestors[0] ancestors = ancestors[1:] var err error lastQn, err = ComputeQualifiedName(current, lastQn) if err != nil { return nil, err } } return ComputeQualifiedName(name, lastQn) } ./github.com/canonical/go-tpm2/util/signatures.go0000664000000000000000000002052400000000000020312 0ustar0000000000000000// Copyright 2021 Canonical Ltd. // Licensed under the LGPLv3 with static-linking exception. // See LICENCE file for details. package util import ( "bytes" "crypto" "crypto/ecdsa" "crypto/hmac" "crypto/rand" "crypto/rsa" "encoding/binary" "errors" "math/big" "github.com/canonical/go-tpm2" "github.com/canonical/go-tpm2/mu" ) // Sign creates a signature of the supplied digest using the supplied private key and // signature scheme. Note that only RSA-SSA, RSA-PSS, ECDSA and HMAC signatures can // be created. The returned signature can be verified on a TPM using the associated // public key. // // In order to create a HMAC, the supplied private key should be a byte slice containing // the HMAC key. func Sign(key crypto.PrivateKey, scheme *tpm2.SigScheme, digest []byte) (*tpm2.Signature, error) { hashAlg := scheme.Details.Any(scheme.Scheme).HashAlg if !hashAlg.Available() { return nil, errors.New("digest algorithm is not available") } if len(digest) != hashAlg.Size() { return nil, errors.New("invalid digest length") } switch k := key.(type) { case *rsa.PrivateKey: switch scheme.Scheme { case tpm2.SigSchemeAlgRSASSA: sig, err := rsa.SignPKCS1v15(rand.Reader, k, hashAlg.GetHash(), digest) if err != nil { return nil, err } return &tpm2.Signature{ SigAlg: tpm2.SigSchemeAlgRSASSA, Signature: &tpm2.SignatureU{ RSASSA: &tpm2.SignatureRSASSA{ Hash: hashAlg, Sig: sig}}}, nil case tpm2.SigSchemeAlgRSAPSS: options := rsa.PSSOptions{SaltLength: rsa.PSSSaltLengthEqualsHash} sig, err := rsa.SignPSS(rand.Reader, k, hashAlg.GetHash(), digest, &options) if err != nil { return nil, err } return &tpm2.Signature{ SigAlg: tpm2.SigSchemeAlgRSAPSS, Signature: &tpm2.SignatureU{ RSAPSS: &tpm2.SignatureRSAPSS{ Hash: hashAlg, Sig: sig}}}, nil default: return nil, errors.New("unsupported RSA signature scheme") } case *ecdsa.PrivateKey: switch scheme.Scheme { case tpm2.SigSchemeAlgECDSA: r, s, err := ecdsa.Sign(rand.Reader, k, digest) if err != nil { return nil, err } return &tpm2.Signature{ SigAlg: tpm2.SigSchemeAlgECDSA, Signature: &tpm2.SignatureU{ ECDSA: &tpm2.SignatureECDSA{ Hash: hashAlg, SignatureR: r.Bytes(), SignatureS: s.Bytes()}}}, nil default: return nil, errors.New("unsupported ECC signature scheme") } case []byte: switch scheme.Scheme { case tpm2.SigSchemeAlgHMAC: h := hmac.New(hashAlg.NewHash, k) h.Write(digest) return &tpm2.Signature{ SigAlg: tpm2.SigSchemeAlgHMAC, Signature: &tpm2.SignatureU{ HMAC: &tpm2.TaggedHash{ HashAlg: hashAlg, Digest: h.Sum(nil)}}}, nil default: return nil, errors.New("unsupported keyed hash scheme") } default: return nil, errors.New("unsupported private key type") } } // VerifySignature verifies a signature created by a TPM using the supplied public // key. Note that only RSA-SSA, RSA-PSS, ECDSA and HMAC signatures are supported. // // In order to verify a HMAC signature, the supplied public key should be a byte // slice containing the HMAC key. func VerifySignature(key crypto.PublicKey, digest []byte, signature *tpm2.Signature) (ok bool, err error) { if !signature.Signature.Any(signature.SigAlg).HashAlg.Available() { return false, errors.New("digest algorithm is not available") } switch k := key.(type) { case *rsa.PublicKey: switch signature.SigAlg { case tpm2.SigSchemeAlgRSASSA: if err := rsa.VerifyPKCS1v15(k, signature.Signature.RSASSA.Hash.GetHash(), digest, signature.Signature.RSASSA.Sig); err != nil { if err == rsa.ErrVerification { return false, nil } return false, err } return true, nil case tpm2.SigSchemeAlgRSAPSS: options := rsa.PSSOptions{SaltLength: rsa.PSSSaltLengthEqualsHash} if err := rsa.VerifyPSS(k, signature.Signature.RSAPSS.Hash.GetHash(), digest, signature.Signature.RSAPSS.Sig, &options); err != nil { if err == rsa.ErrVerification { return false, nil } return false, err } return true, nil default: return false, errors.New("unsupported RSA signature algorithm") } case *ecdsa.PublicKey: switch signature.SigAlg { case tpm2.SigSchemeAlgECDSA: ok = ecdsa.Verify(k, digest, new(big.Int).SetBytes(signature.Signature.ECDSA.SignatureR), new(big.Int).SetBytes(signature.Signature.ECDSA.SignatureS)) return ok, nil default: return false, errors.New("unsupported ECC signature algorithm") } case []byte: switch signature.SigAlg { case tpm2.SigSchemeAlgHMAC: scheme := &tpm2.SigScheme{ Scheme: tpm2.SigSchemeAlgHMAC, Details: &tpm2.SigSchemeU{ HMAC: &tpm2.SchemeHMAC{ HashAlg: signature.Signature.HMAC.HashAlg}}} test, err := Sign(k, scheme, digest) if err != nil { return false, err } return bytes.Equal(signature.Signature.HMAC.Digest, test.Signature.HMAC.Digest), nil default: return false, errors.New("unsupported keyed hash signature algorithm") } default: return false, errors.New("invalid public key type") } } // SignPolicyAuthorization creates a signed authorization using the supplied key and signature // scheme. The signed authorization can be used in a TPM2_PolicySigned assertion. The authorizing // party can apply contraints on how the session that includes this authorization can be used. // // If nonceTPM is supplied, then the signed authorization can only be used for the session // associated with the supplied nonce. // // If expiration is non-zero, then the signed authorization is only valid for the specified // number of seconds from when nonceTPM was generated. // // If cpHash is supplied, then the signed authorization is only valid for use in a command // with the associated set of command parameters. func SignPolicyAuthorization(key crypto.PrivateKey, scheme *tpm2.SigScheme, nonceTPM tpm2.Nonce, cpHashA tpm2.Digest, policyRef tpm2.Nonce, expiration int32) (*tpm2.Signature, error) { hashAlg := scheme.Details.Any(scheme.Scheme).HashAlg if !hashAlg.Available() { return nil, errors.New("digest algorithm is not available") } h := hashAlg.NewHash() h.Write(nonceTPM) binary.Write(h, binary.BigEndian, expiration) h.Write(cpHashA) h.Write(policyRef) return Sign(key, scheme, h.Sum(nil)) } // ComputePolicyAuthorizeDigest computes a digest to sign from the supplied // authorization policy digest and policy reference. The resulting digest // can be signed to authorize the supplied policy with the TPM2_PolicyAuthorize // assertion. func ComputePolicyAuthorizeDigest(alg tpm2.HashAlgorithmId, approvedPolicy tpm2.Digest, policyRef tpm2.Nonce) (tpm2.Digest, error) { if !alg.Available() { return nil, errors.New("digest algorithm is not available") } h := alg.NewHash() h.Write(approvedPolicy) h.Write(policyRef) return h.Sum(nil), nil } // PolicyAuthorize authorizes an authorization policy digest with the supplied key and // signature scheme. The resulting digest and signature can be verified by the TPM in // order to produce a ticket that can then be supplied to a TPM2_PolicyAuthorize assertion. // // The digest algorithm used for the signature must match the name algorithm in // the public area associated with the supplied private key. func PolicyAuthorize(key crypto.PrivateKey, scheme *tpm2.SigScheme, approvedPolicy tpm2.Digest, policyRef tpm2.Nonce) (tpm2.Digest, *tpm2.Signature, error) { hashAlg := scheme.Details.Any(scheme.Scheme).HashAlg if !hashAlg.Available() { return nil, nil, errors.New("digest algorithm is not available") } digest, _ := ComputePolicyAuthorizeDigest(hashAlg, approvedPolicy, policyRef) sig, err := Sign(key, scheme, digest) if err != nil { return nil, nil, err } return digest, sig, nil } // VerifyAttestationSignature verifies the signature for the supplied attestation // structure as generated by one of the TPM's attestation commands. Note that only // RSA-SSA, RSA-PSS, ECDSA and HMAC signatures are supported. // // In order to verify a HMAC signature, the supplied public key should be a byte // slice containing the HMAC key. func VerifyAttestationSignature(key crypto.PublicKey, attest *tpm2.Attest, signature *tpm2.Signature) (ok bool, err error) { hashAlg := signature.Signature.Any(signature.SigAlg).HashAlg if !hashAlg.Available() { return false, errors.New("digest algorithm is not available") } h := hashAlg.NewHash() mu.MustMarshalToWriter(h, attest) return VerifySignature(key, h.Sum(nil), signature) } ./github.com/canonical/tcglog-parser/0000775000000000000000000000000000000000000016103 5ustar0000000000000000./github.com/canonical/tcglog-parser/.gitignore0000664000000000000000000000011700000000000020072 0ustar0000000000000000tcglog-dump/tcglog-dump tcglog-parser.test tcglog-check/tcglog-check vendor/*/ ./github.com/canonical/tcglog-parser/LICENSE0000664000000000000000000002150100000000000017107 0ustar0000000000000000All files in this repository are licensed as follows. If you contribute to this repository, it is assumed that you license your contribution under the same license unless you state otherwise. All files Copyright (C) 2019 Canonical Ltd. unless otherwise specified in the file. This software is licensed under the LGPLv3, included below. As a special exception to the GNU Lesser General Public License version 3 ("LGPL3"), the copyright holders of this Library give you permission to convey to a third party a Combined Work that links statically or dynamically to this Library without providing any Minimal Corresponding Source or Minimal Application Code as set out in 4d or providing the installation information set out in section 4e, provided that you comply with the other provisions of LGPL3 and provided that you meet, for the Application the terms and conditions of the license(s) which apply to the Application. Except as stated in this special exception, the provisions of LGPL3 will continue to comply in full to this Library. If you modify this Library, you may apply this exception to your version of this Library, but you are not obliged to do so. If you do not wish to do so, delete this exception statement from your version. This exception does not (and cannot) modify any license terms which apply to the Application, with which you must still comply. GNU LESSER GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. This version of the GNU Lesser General Public License incorporates the terms and conditions of version 3 of the GNU General Public License, supplemented by the additional permissions listed below. 0. Additional Definitions. As used herein, "this License" refers to version 3 of the GNU Lesser General Public License, and the "GNU GPL" refers to version 3 of the GNU General Public License. "The Library" refers to a covered work governed by this License, other than an Application or a Combined Work as defined below. An "Application" is any work that makes use of an interface provided by the Library, but which is not otherwise based on the Library. Defining a subclass of a class defined by the Library is deemed a mode of using an interface provided by the Library. A "Combined Work" is a work produced by combining or linking an Application with the Library. The particular version of the Library with which the Combined Work was made is also called the "Linked Version". The "Minimal Corresponding Source" for a Combined Work means the Corresponding Source for the Combined Work, excluding any source code for portions of the Combined Work that, considered in isolation, are based on the Application, and not on the Linked Version. The "Corresponding Application Code" for a Combined Work means the object code and/or source code for the Application, including any data and utility programs needed for reproducing the Combined Work from the Application, but excluding the System Libraries of the Combined Work. 1. Exception to Section 3 of the GNU GPL. You may convey a covered work under sections 3 and 4 of this License without being bound by section 3 of the GNU GPL. 2. Conveying Modified Versions. If you modify a copy of the Library, and, in your modifications, a facility refers to a function or data to be supplied by an Application that uses the facility (other than as an argument passed when the facility is invoked), then you may convey a copy of the modified version: a) under this License, provided that you make a good faith effort to ensure that, in the event an Application does not supply the function or data, the facility still operates, and performs whatever part of its purpose remains meaningful, or b) under the GNU GPL, with none of the additional permissions of this License applicable to that copy. 3. Object Code Incorporating Material from Library Header Files. The object code form of an Application may incorporate material from a header file that is part of the Library. You may convey such object code under terms of your choice, provided that, if the incorporated material is not limited to numerical parameters, data structure layouts and accessors, or small macros, inline functions and templates (ten or fewer lines in length), you do both of the following: a) Give prominent notice with each copy of the object code that the Library is used in it and that the Library and its use are covered by this License. b) Accompany the object code with a copy of the GNU GPL and this license document. 4. Combined Works. You may convey a Combined Work under terms of your choice that, taken together, effectively do not restrict modification of the portions of the Library contained in the Combined Work and reverse engineering for debugging such modifications, if you also do each of the following: a) Give prominent notice with each copy of the Combined Work that the Library is used in it and that the Library and its use are covered by this License. b) Accompany the Combined Work with a copy of the GNU GPL and this license document. c) For a Combined Work that displays copyright notices during execution, include the copyright notice for the Library among these notices, as well as a reference directing the user to the copies of the GNU GPL and this license document. d) Do one of the following: 0) Convey the Minimal Corresponding Source under the terms of this License, and the Corresponding Application Code in a form suitable for, and under terms that permit, the user to recombine or relink the Application with a modified version of the Linked Version to produce a modified Combined Work, in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source. 1) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (a) uses at run time a copy of the Library already present on the user's computer system, and (b) will operate properly with a modified version of the Library that is interface-compatible with the Linked Version. e) Provide Installation Information, but only if you would otherwise be required to provide such information under section 6 of the GNU GPL, and only to the extent that such information is necessary to install and execute a modified version of the Combined Work produced by recombining or relinking the Application with a modified version of the Linked Version. (If you use option 4d0, the Installation Information must accompany the Minimal Corresponding Source and Corresponding Application Code. If you use option 4d1, you must provide the Installation Information in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source.) 5. Combined Libraries. You may place library facilities that are a work based on the Library side by side in a single library together with other library facilities that are not Applications and are not covered by this License, and convey such a combined library under terms of your choice, if you do both of the following: a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities, conveyed under the terms of this License. b) Give prominent notice with the combined library that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. 6. Revised Versions of the GNU Lesser General Public License. The Free Software Foundation may publish revised and/or new versions of the GNU Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Library as you received it specifies that a certain numbered version of the GNU Lesser General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that published version or of any later version published by the Free Software Foundation. If the Library as you received it does not specify a version number of the GNU Lesser General Public License, you may choose any version of the GNU Lesser General Public License ever published by the Free Software Foundation. If the Library as you received it specifies that a proxy can decide whether future versions of the GNU Lesser General Public License shall apply, that proxy's public statement of acceptance of any version is permanent authorization for you to choose that version for the Library. ./github.com/canonical/tcglog-parser/README.md0000664000000000000000000000164500000000000017370 0ustar0000000000000000# TCG Log Parser This repository contains a go library for parsing TCG event logs. Also included is a simple command line tool that prints details of log entries to the console. ## Relevant specifications * [TCG PC Client Platform Firmware Profile Specification](https://trustedcomputinggroup.org/wp-content/uploads/TCG_PCClientSpecPlat_TPM_2p0_1p04_pub.pdf) * [TCG EFI Platform Specification For TPM Family 1.1 or 1.2](https://trustedcomputinggroup.org/wp-content/uploads/TCG_EFI_Platform_1_22_Final_-v15.pdf) * [TCG PC Client Specific Implementation Specification for Conventional BIOS](https://trustedcomputinggroup.org/wp-content/uploads/TCG_PCClientImplementation_1-21_1_00.pdf) * [Unified Extensible Firmware Interface (UEFI) Specification](https://uefi.org/sites/default/files/resources/UEFI_Spec_2_8_final.pdf) * [Platform Initialization (PI) Specification](https://uefi.org/sites/default/files/resources/PI_Spec_1_6.pdf) ./github.com/canonical/tcglog-parser/constants.go0000664000000000000000000000721500000000000020453 0ustar0000000000000000// Copyright 2019 Canonical Ltd. // Licensed under the LGPLv3 with static-linking exception. // See LICENCE file for details. package tcglog import ( "math" ) const ( EventTypePrebootCert EventType = 0x00000000 // EV_PREBOOT_CERT EventTypePostCode EventType = 0x00000001 // EV_POST_CODE // EventTypeUnused = 0x00000002 EventTypeNoAction EventType = 0x00000003 // EV_NO_ACTION EventTypeSeparator EventType = 0x00000004 // EV_SEPARATOR EventTypeAction EventType = 0x00000005 // EV_ACTION EventTypeEventTag EventType = 0x00000006 // EV_EVENT_TAG EventTypeSCRTMContents EventType = 0x00000007 // EV_S_CRTM_CONTENTS EventTypeSCRTMVersion EventType = 0x00000008 // EV_S_CRTM_VERSION EventTypeCPUMicrocode EventType = 0x00000009 // EV_CPU_MICROCODE EventTypePlatformConfigFlags EventType = 0x0000000a // EV_PLATFORM_CONFIG_FLAGS EventTypeTableOfDevices EventType = 0x0000000b // EV_TABLE_OF_DEVICES EventTypeCompactHash EventType = 0x0000000c // EV_COMPACT_HASH EventTypeIPL EventType = 0x0000000d // EV_IPL EventTypeIPLPartitionData EventType = 0x0000000e // EV_IPL_PARTITION_DATA EventTypeNonhostCode EventType = 0x0000000f // EV_NONHOST_CODE EventTypeNonhostConfig EventType = 0x00000010 // EV_NONHOST_CONFIG EventTypeNonhostInfo EventType = 0x00000011 // EV_NONHOST_INFO EventTypeOmitBootDeviceEvents EventType = 0x00000012 // EV_OMIT_BOOT_DEVICE_EVENTS EventTypeEFIEventBase EventType = 0x80000000 // EV_EFI_EVENT_BASE EventTypeEFIVariableDriverConfig EventType = 0x80000001 // EV_EFI_VARIABLE_DRIVER_CONFIG EventTypeEFIVariableBoot EventType = 0x80000002 // EV_EFI_VARIABLE_BOOT EventTypeEFIBootServicesApplication EventType = 0x80000003 // EV_EFI_BOOT_SERVICES_APPLICATION EventTypeEFIBootServicesDriver EventType = 0x80000004 // EV_EFI_BOOT_SERVICES_DRIVER EventTypeEFIRuntimeServicesDriver EventType = 0x80000005 // EV_EFI_RUNTIME_SERVICES_DRIVER EventTypeEFIGPTEvent EventType = 0x80000006 // EV_EFI_GPT_EVENT EventTypeEFIAction EventType = 0x80000007 // EV_EFI_ACTION EventTypeEFIPlatformFirmwareBlob EventType = 0x80000008 // EV_EFI_PLATFORM_FIRMWARE_BLOB EventTypeEFIHandoffTables EventType = 0x80000009 // EV_EFI_HANDOFF_TABLES EventTypeEFIPlatformFirmwareBlob2 EventType = 0x8000000a // EV_EFI_PLATFORM_FIRMWARE_BLOB2 EventTypeEFIHandoffTables2 EventType = 0x8000000b // EV_EFI_HANDOFF_TABLES2 EventTypeEFIVariableBoot2 EventType = 0x8000000c // EV_EFI_VARIABLE_BOOT2 EventTypeEFIHCRTMEvent EventType = 0x80000010 // EV_EFI_HCRTM_EVENT EventTypeEFIVariableAuthority EventType = 0x800000e0 // EV_EFI_VARIABLE_AUTHORITY EventTypeEFISPDMFirmwareBlob EventType = 0x800000e1 // EV_EFI_SPDM_FIRMWARE_BLOB EventTypeEFISPDMFirmwareConfig EventType = 0x800000e2 // EV_EFI_SPDM_FIRMWARE_CONFIG ) const ( SeparatorEventNormalValue uint32 = 0 SeparatorEventErrorValue uint32 = 1 SeparatorEventAltNormalValue uint32 = math.MaxUint32 ) var ( EFICallingEFIApplicationEvent = StringEventData("Calling EFI Application from Boot Option") EFIReturningFromEFIApplicationEvent = StringEventData("Returning from EFI Application from Boot Option") EFIExitBootServicesInvocationEvent = StringEventData("Exit Boot Services Invocation") EFIExitBootServicesFailedEvent = StringEventData("Exit Boot Services Returned with Failure") EFIExitBootServicesSucceededEvent = StringEventData("Exit Boot Services Returned with Success") FirmwareDebuggerEvent = StringEventData("UEFI Debug Mode") ) ./github.com/canonical/tcglog-parser/event.go0000664000000000000000000001326700000000000017564 0ustar0000000000000000// Copyright 2019-2021 Canonical Ltd. // Licensed under the LGPLv3 with static-linking exception. // See LICENCE file for details. package tcglog import ( "bytes" "encoding/binary" "errors" "fmt" "io" "sort" "github.com/canonical/go-tpm2" "golang.org/x/xerrors" "github.com/canonical/tcglog-parser/internal/ioerr" ) type eventHeader struct { PCRIndex PCRIndex EventType EventType } type eventHeaderCryptoAgile struct { eventHeader Count uint32 } // Event corresponds to a single event in an event log. type Event struct { PCRIndex PCRIndex // PCR index to which this event was measured EventType EventType // The type of this event Digests DigestMap // The digests corresponding to this event for the supported algorithms Data EventData // The data recorded with this event } func (e *Event) Write(w io.Writer) error { digest, ok := e.Digests[tpm2.HashAlgorithmSHA1] if !ok { return errors.New("missing SHA-1 digest") } if len(digest) != tpm2.HashAlgorithmSHA1.Size() { return errors.New("invalid digest size") } data := new(bytes.Buffer) if err := e.Data.Write(data); err != nil { return xerrors.Errorf("cannot serialize event data: %w", err) } hdr := eventHeader{ PCRIndex: e.PCRIndex, EventType: e.EventType} if err := binary.Write(w, binary.LittleEndian, &hdr); err != nil { return err } if _, err := w.Write(digest); err != nil { return err } if err := binary.Write(w, binary.LittleEndian, uint32(data.Len())); err != nil { return err } _, err := w.Write(data.Bytes()) return err } func (e *Event) WriteCryptoAgile(w io.Writer) error { var algs []tpm2.HashAlgorithmId for alg, digest := range e.Digests { if !alg.IsValid() { continue } if len(digest) != alg.Size() { return fmt.Errorf("invalid digest size for %v", alg) } algs = append(algs, alg) } sort.Slice(algs, func(i, j int) bool { return algs[i] < algs[j] }) data := new(bytes.Buffer) if err := e.Data.Write(data); err != nil { return xerrors.Errorf("cannot serialize event data: %w", err) } hdr := eventHeaderCryptoAgile{ eventHeader: eventHeader{ PCRIndex: e.PCRIndex, EventType: e.EventType}, Count: uint32(len(algs))} if err := binary.Write(w, binary.LittleEndian, &hdr); err != nil { return err } for _, alg := range algs { if err := binary.Write(w, binary.LittleEndian, alg); err != nil { return err } if _, err := w.Write(e.Digests[alg]); err != nil { return err } } if err := binary.Write(w, binary.LittleEndian, uint32(data.Len())); err != nil { return err } _, err := w.Write(data.Bytes()) return err } func isPCRIndexInRange(index PCRIndex) bool { const maxPCRIndex PCRIndex = 31 return index <= maxPCRIndex } func ReadEvent(r io.Reader, options *LogOptions) (*Event, error) { var header eventHeader if err := binary.Read(r, binary.LittleEndian, &header); err != nil { return nil, err } if !isPCRIndexInRange(header.PCRIndex) { return nil, fmt.Errorf("log entry has an out-of-range PCR index (%d)", header.PCRIndex) } digest := make(Digest, tpm2.HashAlgorithmSHA1.Size()) if _, err := io.ReadFull(r, digest); err != nil { return nil, ioerr.EOFIsUnexpected(err) } digests := make(DigestMap) digests[tpm2.HashAlgorithmSHA1] = digest var eventSize uint32 if err := binary.Read(r, binary.LittleEndian, &eventSize); err != nil { return nil, ioerr.EOFIsUnexpected(err) } event := make([]byte, eventSize) if _, err := io.ReadFull(r, event); err != nil { return nil, ioerr.EOFIsUnexpected(err) } return &Event{ PCRIndex: header.PCRIndex, EventType: header.EventType, Digests: digests, Data: decodeEventData(event, header.PCRIndex, header.EventType, digests, options), }, nil } func ReadEventCryptoAgile(r io.Reader, digestSizes []EFISpecIdEventAlgorithmSize, options *LogOptions) (*Event, error) { var header eventHeaderCryptoAgile if err := binary.Read(r, binary.LittleEndian, &header); err != nil { return nil, err } if !isPCRIndexInRange(header.PCRIndex) { return nil, fmt.Errorf("log entry has an out-of-range PCR index (%d)", header.PCRIndex) } digests := make(DigestMap) for i := uint32(0); i < header.Count; i++ { var algorithmId tpm2.HashAlgorithmId if err := binary.Read(r, binary.LittleEndian, &algorithmId); err != nil { return nil, ioerr.EOFIsUnexpected(err) } var digestSize uint16 var j int for j = 0; j < len(digestSizes); j++ { if digestSizes[j].AlgorithmId == algorithmId { digestSize = digestSizes[j].DigestSize break } } if j == len(digestSizes) { return nil, fmt.Errorf("event contains a digest for an unrecognized algorithm (%v)", algorithmId) } digest := make(Digest, digestSize) if _, err := io.ReadFull(r, digest); err != nil { return nil, ioerr.EOFIsUnexpected("cannot read digest for algorithm %v: %w", algorithmId, err) } if _, exists := digests[algorithmId]; exists { return nil, fmt.Errorf("event contains more than one digest value for algorithm %v", algorithmId) } digests[algorithmId] = digest } for _, s := range digestSizes { if _, exists := digests[s.AlgorithmId]; !exists { return nil, fmt.Errorf("event is missing a digest value for algorithm %v", s.AlgorithmId) } } for alg, _ := range digests { if alg.IsValid() { continue } delete(digests, alg) } var eventSize uint32 if err := binary.Read(r, binary.LittleEndian, &eventSize); err != nil { return nil, ioerr.EOFIsUnexpected(err) } event := make([]byte, eventSize) if _, err := io.ReadFull(r, event); err != nil { return nil, ioerr.EOFIsUnexpected(err) } return &Event{ PCRIndex: header.PCRIndex, EventType: header.EventType, Digests: digests, Data: decodeEventData(event, header.PCRIndex, header.EventType, digests, options), }, nil } ./github.com/canonical/tcglog-parser/eventdata.go0000664000000000000000000000560100000000000020407 0ustar0000000000000000// Copyright 2019 Canonical Ltd. // Licensed under the LGPLv3 with static-linking exception. // See LICENCE file for details. package tcglog import ( "crypto" "fmt" "io" "unicode" "unicode/utf8" ) // EventData represents all event data types that appear in a log. Some implementations of this are exported so that event data // contents can be inspected programatically. // // If an error is encountered when decoding the data associated with an event, the event data will implement the error interface // which can be used for obtaining information about the decoding error. type EventData interface { fmt.Stringer // Bytes is the raw event data bytes as they appear in the event log. Bytes() []byte // Write will serialize this event data to the supplied io.Writer. Write(w io.Writer) error } type rawEventData []byte func (b rawEventData) Bytes() []byte { return []byte(b) } // invalidEventData corresponds to an event data blob that failed to decode correctly. type invalidEventData struct { rawEventData err error } func (e *invalidEventData) String() string { return fmt.Sprintf("Invalid event data: %v", e.err) } func (e *invalidEventData) Write(w io.Writer) error { _, err := w.Write(e.rawEventData) return err } func (e *invalidEventData) Error() string { return e.err.Error() } func (e *invalidEventData) Unwrap() error { return e.err } // OpaqueEventData is event data whose format is unknown or implementation defined. type OpaqueEventData []byte func (d OpaqueEventData) String() string { // This blob is opaque, but try to print something if it's filled // with printable characters. data := d var s []byte for len(data) > 0 { r, sz := utf8.DecodeRune(data) if r == 0 { break } if !unicode.IsPrint(r) { return "" } s = append(s, data[:sz]...) data = data[sz:] } return string(s) } func (d OpaqueEventData) Bytes() []byte { return []byte(d) } func (d OpaqueEventData) Write(w io.Writer) error { _, err := w.Write(d) return err } // ComputeEventDigest computes the digest associated with the supplied event data bytes, // for events where the digest is a tagged hash of the event data. func ComputeEventDigest(alg crypto.Hash, data []byte) []byte { h := alg.New() h.Write(data) return h.Sum(nil) } func decodeEventData(data []byte, pcrIndex PCRIndex, eventType EventType, digests DigestMap, options *LogOptions) EventData { if options.EnableGrub && (pcrIndex == 8 || pcrIndex == 9) { if out := decodeEventDataGRUB(data, pcrIndex, eventType); out != nil { return out } } if options.EnableSystemdEFIStub && pcrIndex == options.SystemdEFIStubPCR { if out := decodeEventDataSystemdEFIStub(data, eventType); out != nil { return out } } out, err := decodeEventDataTCG(data, pcrIndex, eventType, digests) if err != nil { return &invalidEventData{rawEventData: data, err: err} } if out != nil { return out } return OpaqueEventData(data) } ./github.com/canonical/tcglog-parser/go.mod0000664000000000000000000000200500000000000017206 0ustar0000000000000000module github.com/canonical/tcglog-parser go 1.13 require ( github.com/bsiegert/ranges v0.0.0-20111221115336-19303dc7aa63 github.com/canonical/go-efilib v0.3.1-0.20220314143719-95d50e8afc82 github.com/canonical/go-sp800.108-kdf v0.0.0-20210315104021-ead800bbf9a0 // indirect github.com/canonical/go-sp800.90a-drbg v0.0.0-20210314144037-6eeb1040d6c3 // indirect github.com/canonical/go-tpm2 v0.1.0 github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf // indirect github.com/godbus/dbus v4.1.0+incompatible // indirect github.com/gorilla/mux v1.8.0 // indirect github.com/jessevdk/go-flags v1.5.0 github.com/mvo5/goconfigparser v0.0.0-20201015074339-50f22f44deb5 // indirect github.com/snapcore/secboot v0.0.0-20211207204151-239d06c34009 // indirect github.com/snapcore/squashfuse v0.0.0-20171220165323-319f6d41a041 // indirect golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365 // indirect golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c ) ./github.com/canonical/tcglog-parser/go.sum0000664000000000000000000001511200000000000017236 0ustar0000000000000000github.com/bsiegert/ranges v0.0.0-20111221115336-19303dc7aa63 h1:FxdkNGQyRwwk94rJ+IMNrTjv864XzT93/cvk7UpMM38= github.com/bsiegert/ranges v0.0.0-20111221115336-19303dc7aa63/go.mod h1:8z71/aZjDHLs4ihK/5nD5wZVQxm/W4eRDnxQZcJmVD4= github.com/canonical/go-efilib v0.2.0 h1:6GtKMSPOM//K+rcR05eYeqHqfzoaiFyP3v+GdG5mqJw= github.com/canonical/go-efilib v0.2.0/go.mod h1:9Sr9kd7IhQPYqaU5nut8Ky97/CtlhHDzQncQnrULgDM= github.com/canonical/go-efilib v0.3.0 h1:9XRu3UwFkgap2DUolfbBNRGs+fha/KWiqW6y2M4H/XA= github.com/canonical/go-efilib v0.3.0/go.mod h1:9b2PNAuPcZsB76x75/uwH99D8CyH/A2y4rq1/+bvplg= github.com/canonical/go-efilib v0.3.1-0.20220314143719-95d50e8afc82 h1:2ch4ZZiiBiXFeu6hSjaezA2hay6atTSD4RO1yqU4WAU= github.com/canonical/go-efilib v0.3.1-0.20220314143719-95d50e8afc82/go.mod h1:9b2PNAuPcZsB76x75/uwH99D8CyH/A2y4rq1/+bvplg= github.com/canonical/go-sp800.108-kdf v0.0.0-20210314145419-a3359f2d21b9/go.mod h1:Zrs3YjJr+w51u0R/dyLh/oWt/EcBVdLPCVFYC4daW5s= github.com/canonical/go-sp800.108-kdf v0.0.0-20210315104021-ead800bbf9a0 h1:ZE2XMRFHcwlib3uU9is37+pKkkMloVoEPWmgQ6GK1yo= github.com/canonical/go-sp800.108-kdf v0.0.0-20210315104021-ead800bbf9a0/go.mod h1:Zrs3YjJr+w51u0R/dyLh/oWt/EcBVdLPCVFYC4daW5s= github.com/canonical/go-sp800.90a-drbg v0.0.0-20210314144037-6eeb1040d6c3 h1:oe6fCvaEpkhyW3qAicT0TnGtyht/UrgvOwMcEgLb7Aw= github.com/canonical/go-sp800.90a-drbg v0.0.0-20210314144037-6eeb1040d6c3/go.mod h1:qdP0gaj0QtgX2RUZhnlVrceJ+Qln8aSlDyJwelLLFeM= github.com/canonical/go-tpm2 v0.1.0 h1:2mXU+Hy+zYSxmuYys2NtPEO6NwT3Qr9Sygwtops7NYk= github.com/canonical/go-tpm2 v0.1.0/go.mod h1:vG41hdbBjV4+/fkubTT1ENBBqSkLwLr7mCeW9Y6kpZY= github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf h1:iW4rZ826su+pqaw19uhpSCzhj44qo35pNgKFGqzDKkU= github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/godbus/dbus v4.1.0+incompatible h1:WqqLRTsQic3apZUK9qC5sGNfXthmPXzUZ7nQPrNITa4= github.com/godbus/dbus v4.1.0+incompatible/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw= github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/jessevdk/go-flags v1.5.0 h1:1jKYvbxEjfUl0fmqTCOfonvskHHXMjBySTLW4y9LFvc= github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4= github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/mvo5/goconfigparser v0.0.0-20201015074339-50f22f44deb5 h1:IUtr2a2HkY+0BPb4bz7t7+p26kmp366dLSkuAodXE10= github.com/mvo5/goconfigparser v0.0.0-20201015074339-50f22f44deb5/go.mod h1:xmt4k1xLDl8Tdan+0S/jmMK2uSUBSzTc18+5GN5Vea8= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/snapcore/go-gettext v0.0.0-20191107141714-82bbea49e785 h1:PaunR+BhraKSLxt2awQ42zofkP+NKh/VjQ0PjIMk/y4= github.com/snapcore/go-gettext v0.0.0-20191107141714-82bbea49e785/go.mod h1:D3SsWAXK7wCCBZu+Vk5hc1EuKj/L3XN1puEMXTU4LrQ= github.com/snapcore/secboot v0.0.0-20211207204151-239d06c34009 h1:6aLcU6H6dgA/1EpvPW8ADbYjGqepNRZ+2Gi01/qhJlI= github.com/snapcore/secboot v0.0.0-20211207204151-239d06c34009/go.mod h1:72paVOkm4sJugXt+v9ItmnjXgO921D8xqsbH2OekouY= github.com/snapcore/snapd v0.0.0-20201005140838-501d14ac146e h1:vqDZWKPBL9RKPA8KyOuTaSuXXlmRwn/ndvOkZaAmLJs= github.com/snapcore/snapd v0.0.0-20201005140838-501d14ac146e/go.mod h1:3xrn7QDDKymcE5VO2rgWEQ5ZAUGb9htfwlXnoel6Io8= github.com/snapcore/squashfuse v0.0.0-20171220165323-319f6d41a041 h1:rzu8Xf5QoMJfOPf4giWLfbgvkt7dPbdTZCb80PPAALo= github.com/snapcore/squashfuse v0.0.0-20171220165323-319f6d41a041/go.mod h1:8loYitFPSdoeCXBs/XjO0fyGcpgLAybOHLUsGwgMq90= go.mozilla.org/pkcs7 v0.0.0-20200128120323-432b2356ecb1 h1:A/5uWzF44DlIgdm/PQFwfMkW0JX+cIcQi/SwLAmZP5M= go.mozilla.org/pkcs7 v0.0.0-20200128120323-432b2356ecb1/go.mod h1:SNgMg+EgDFwmvSmLRTNKC5fegJjB7v23qTQ0XLGUNHk= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20201002202402-0a1ea396d57c h1:dk0ukUIHmGHqASjP0iue2261isepFCC6XRCSd1nHgDw= golang.org/x/net v0.0.0-20201002202402-0a1ea396d57c/go.mod h1:iQL9McJNjoIa5mjH6nYTCTZXUN6RP+XW3eib7Ya3XcI= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365 h1:6wSTsvPddg9gc/mVEEyk9oOAoxn+bT4Z9q1zx+4RwA4= golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/tomb.v2 v2.0.0-20161208151619-d5d1b5820637 h1:yiW+nvdHb9LVqSHQBXfZCieqV4fzYhNBql77zY0ykqs= gopkg.in/tomb.v2 v2.0.0-20161208151619-d5d1b5820637/go.mod h1:BHsqpu/nsuzkT5BpiH1EMZPLyqSMM8JbIavyFACoFNk= gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= ./github.com/canonical/tcglog-parser/grubeventdata.go0000664000000000000000000000340700000000000021271 0ustar0000000000000000// Copyright 2019 Canonical Ltd. // Licensed under the LGPLv3 with static-linking exception. // See LICENCE file for details. package tcglog import ( "fmt" "io" "strings" ) var ( kernelCmdlinePrefix = "kernel_cmdline: " grubCmdPrefix = "grub_cmd: " ) // GrubStringEventType indicates the type of data measured by GRUB in to a log by GRUB. type GrubStringEventType string const ( // GrubCmd indicates that the data measured by GRUB is associated with a GRUB command. GrubCmd GrubStringEventType = "grub_cmd" // KernelCmdline indicates that the data measured by GRUB is associated with a kernel commandline. KernelCmdline = "kernel_cmdline" ) // GrubStringEventData represents the data associated with an event measured by GRUB. type GrubStringEventData struct { rawEventData Type GrubStringEventType Str string } func (e *GrubStringEventData) String() string { return fmt.Sprintf("%s{ %s }", string(e.Type), e.Str) } func (e *GrubStringEventData) Write(w io.Writer) error { _, err := io.WriteString(w, fmt.Sprintf("%s: %s\x00", string(e.Type), e.Str)) return err } func decodeEventDataGRUB(data []byte, pcrIndex PCRIndex, eventType EventType) EventData { if eventType != EventTypeIPL { return nil } switch pcrIndex { case 8: str := string(data) switch { case strings.HasPrefix(str, kernelCmdlinePrefix): return &GrubStringEventData{rawEventData: data, Type: KernelCmdline, Str: strings.TrimSuffix(strings.TrimPrefix(str, kernelCmdlinePrefix), "\x00")} case strings.HasPrefix(str, grubCmdPrefix): return &GrubStringEventData{rawEventData: data, Type: GrubCmd, Str: strings.TrimSuffix(strings.TrimPrefix(str, grubCmdPrefix), "\x00")} default: return nil } case 9: return StringEventData(data) default: panic("unhandled PCR index") } } ./github.com/canonical/tcglog-parser/internal/0000775000000000000000000000000000000000000017717 5ustar0000000000000000./github.com/canonical/tcglog-parser/internal/ioerr/0000775000000000000000000000000000000000000021037 5ustar0000000000000000./github.com/canonical/tcglog-parser/internal/ioerr/ioerr.go0000664000000000000000000000473400000000000022516 0ustar0000000000000000// Copyright 2021 Canonical Ltd. // Licensed under the LGPLv3 with static-linking exception. // See LICENCE file for details. package ioerr import ( "io" "unicode" "unicode/utf8" "golang.org/x/xerrors" ) // Return the index of the first %w in format, or -1 if none. // TODO: handle "%[N]w". func parsePercentW(format string) int { // Loosely copied from golang.org/x/xerrors/fmt.go. n := 0 sz := 0 var isW bool for i := 0; i < len(format); i += sz { if format[i] != '%' { sz = 1 continue } // "%%" is not a format directive. if i+1 < len(format) && format[i+1] == '%' { sz = 2 continue } sz, isW = parsePrintfVerb(format[i:]) if isW { return n } n++ } return -1 } // Parse the printf verb starting with a % at s[0]. // Return how many bytes it occupies and whether the verb is 'w'. func parsePrintfVerb(s string) (int, bool) { // Assume only that the directive is a sequence of non-letters followed by a single letter. sz := 0 var r rune for i := 1; i < len(s); i += sz { r, sz = utf8.DecodeRuneInString(s[i:]) if unicode.IsLetter(r) { return i + sz, r == 'w' } } return len(s), false } // EOFIsUnexpected converts io.EOF errors into io.ErrUnexpected, which is // useful when using binary.Read to decode aprts of a structure that aren't // at the start and when a io.EOF error is not expected. // // It can be called in one of 2 ways - either with a single argument which // must be an error, or with a format string and an arbitrary number of // arguments. In this second mode, the function is a wrapper around // xerrors.Errorf. // // This only works on raw io.EOF errors - ie, it won't work on errors that // have been wrapped. func EOFIsUnexpected(args ...interface{}) error { switch { case len(args) > 1: format := args[0].(string) idx := parsePercentW(format) if idx >= 0 { if err, isErr := args[idx+1].(error); isErr && err == io.EOF { args[idx+1] = io.ErrUnexpectedEOF } } return xerrors.Errorf(format, args[1:]...) case len(args) == 1: switch err := args[0].(type) { case error: if err == io.EOF { err = io.ErrUnexpectedEOF } return err case nil: return nil default: panic("invalid type") } default: panic("no arguments") } } // PassRawEOF is a wrapper around xerrors.Errorf that will return a raw // io.EOF if this is the error. func PassRawEOF(format string, args ...interface{}) error { err := xerrors.Errorf(format, args...) if xerrors.Is(err, io.EOF) { return io.EOF } return err } ./github.com/canonical/tcglog-parser/logreader.go0000664000000000000000000001004400000000000020375 0ustar0000000000000000// Copyright 2019 Canonical Ltd. // Licensed under the LGPLv3 with static-linking exception. // See LICENCE file for details. package tcglog import ( "io" "github.com/canonical/go-tpm2" ) // LogOptions allows the behaviour of Log to be controlled. type LogOptions struct { EnableGrub bool // Enable support for interpreting events recorded by GRUB EnableSystemdEFIStub bool // Enable support for interpreting events recorded by systemd's EFI linux loader stub SystemdEFIStubPCR PCRIndex // Specify the PCR that systemd's EFI linux loader stub measures to } func fixupSpecIdEvent(event *Event, algorithms AlgorithmIdList) { for _, alg := range algorithms { if alg == tpm2.HashAlgorithmSHA1 { continue } if _, ok := event.Digests[alg]; ok { continue } event.Digests[alg] = make(Digest, alg.Size()) } } type PlatformType int const ( PlatformTypeUnknown PlatformType = iota PlatformTypeBIOS PlatformTypeEFI ) // Spec corresponds to the TCG specification that an event log conforms to. type Spec struct { PlatformType PlatformType Major uint8 Minor uint8 Errata uint8 } // IsBIOS indicates that a log conforms to "TCG PC Client Specific Implementation Specification // for Conventional BIOS". // See https://www.trustedcomputinggroup.org/wp-content/uploads/TCG_PCClientImplementation_1-21_1_00.pdf func (s Spec) IsBIOS() bool { return s.PlatformType == PlatformTypeBIOS } // IsEFI_1_2 indicates that a log conforms to "TCG EFI Platform Specification For TPM Family 1.1 or // 1.2". // See https://trustedcomputinggroup.org/wp-content/uploads/TCG_EFI_Platform_1_22_Final_-v15.pdf func (s Spec) IsEFI_1_2() bool { return s.PlatformType == PlatformTypeEFI && s.Major == 1 && s.Minor == 2 } // IsEFI_2 indicates that a log conforms to "TCG PC Client Platform Firmware Profile Specification" // See https://trustedcomputinggroup.org/wp-content/uploads/TCG_PCClientSpecPlat_TPM_2p0_1p04_pub.pdf func (s Spec) IsEFI_2() bool { return s.PlatformType == PlatformTypeEFI && s.Major == 2 } // Log corresponds to a parsed event log. type Log struct { Spec Spec // The specification to which this log conforms Algorithms AlgorithmIdList // The digest algorithms that appear in the log Events []*Event // The list of events in the log } // ReadLog reads an event log read from r using the supplied options. The log must // be in the format defined in one of the PC Client Platform Firmware Profile // specifications. If an error occurs during parsing, this may return an incomplete // list of events with the error. func ReadLog(r io.Reader, options *LogOptions) (*Log, error) { event, err := ReadEvent(r, options) if err != nil { return nil, err } var spec Spec var digestSizes []EFISpecIdEventAlgorithmSize switch d := event.Data.(type) { case *SpecIdEvent00: spec = Spec{ PlatformType: PlatformTypeBIOS, Major: d.SpecVersionMajor, Minor: d.SpecVersionMinor, Errata: d.SpecErrata} case *SpecIdEvent02: spec = Spec{ PlatformType: PlatformTypeEFI, Major: d.SpecVersionMajor, Minor: d.SpecVersionMinor, Errata: d.SpecErrata} case *SpecIdEvent03: spec = Spec{ PlatformType: PlatformTypeEFI, Major: d.SpecVersionMajor, Minor: d.SpecVersionMinor, Errata: d.SpecErrata} digestSizes = d.DigestSizes } var algorithms AlgorithmIdList if spec.IsEFI_2() { for _, s := range digestSizes { if s.AlgorithmId.IsValid() { algorithms = append(algorithms, s.AlgorithmId) } } } else { algorithms = AlgorithmIdList{tpm2.HashAlgorithmSHA1} } if spec.IsEFI_2() { fixupSpecIdEvent(event, algorithms) } log := &Log{Spec: spec, Algorithms: algorithms, Events: []*Event{event}} for { var event *Event var err error if spec.IsEFI_2() { event, err = ReadEventCryptoAgile(r, digestSizes, options) } else { event, err = ReadEvent(r, options) } switch { case err == io.EOF: return log, nil case err != nil: return log, err default: log.Events = append(log.Events, event) } } } ./github.com/canonical/tcglog-parser/logwriter.go0000664000000000000000000000373500000000000020460 0ustar0000000000000000// Copyright 2021 Canonical Ltd. // Licensed under the LGPLv3 with static-linking exception. // See LICENCE file for details. package tcglog import ( "errors" "fmt" "io" "golang.org/x/xerrors" ) // WriteLog writes an event log to w from the supplied events, in a // format that can be read again by ReadLog. The first event must be // a Specification ID event. If the Specification ID event specifies // a crypto-agile log, the digest algorithms must all be supported by // go and the specified sizes must all be correct. Each of the // supplied events must contain a digest for each algorithm. For a // non crypto-agile log, each of the supplied events must contain // a SHA-1 digest. // // This function is only useful for generating reproducible data for // use in tests. func WriteLog(w io.Writer, events []*Event) error { if len(events) == 0 { return nil } var cryptoAgile bool var digestSizes []EFISpecIdEventAlgorithmSize switch d := events[0].Data.(type) { case *SpecIdEvent00: _ = d case *SpecIdEvent02: _ = d case *SpecIdEvent03: cryptoAgile = true digestSizes = d.DigestSizes for _, digest := range digestSizes { if !digest.AlgorithmId.IsValid() { return fmt.Errorf("unsupported algorithm %v", digest.AlgorithmId) } if digest.DigestSize != uint16(digest.AlgorithmId.Size()) { return fmt.Errorf("invalid size for algorithm %v", digest.AlgorithmId) } } default: return errors.New("first event must be a spec ID event") } for i, event := range events { if cryptoAgile && i > 0 { for _, digest := range digestSizes { if _, ok := event.Digests[digest.AlgorithmId]; !ok { return fmt.Errorf("event %d has missing digest for algorithm %v", i, digest.AlgorithmId) } } if err := event.WriteCryptoAgile(w); err != nil { return xerrors.Errorf("cannot write event %d: %w", i, err) } } else { if err := event.Write(w); err != nil { return xerrors.Errorf("cannot write event %d: %w", i, err) } } } return nil } ./github.com/canonical/tcglog-parser/sdefistub.go0000664000000000000000000000452200000000000020425 0ustar0000000000000000// Copyright 2019 Canonical Ltd. // Licensed under the LGPLv3 with static-linking exception. // See LICENCE file for details. package tcglog import ( "bytes" "crypto" "encoding/binary" "io" ) // SystemdEFIStubCommandline represents a kernel commandline measured by the // systemd EFI stub linux loader. type SystemdEFIStubCommandline struct { rawEventData Str string } func (e *SystemdEFIStubCommandline) String() string { return "kernel commandline: " + e.Str } func (e *SystemdEFIStubCommandline) Write(w io.Writer) error { if err := binary.Write(w, binary.LittleEndian, convertStringToUtf16(e.Str)); err != nil { return err } _, err := w.Write([]byte{0x00}) return err } // ComputeSystemdEFIStubCommandlineDigest computes the digest measured by the systemd EFI stub // linux loader for the specified kernel commandline. The commandline is supplied to the stub // via the LoadOptions as a UTF-16 or UCS-2 string and is measured as such before being converted // to ASCII and passed to the kernel. Note that it assumes that the calling bootloader includes // a UTF-16 NULL terminator at the end of LoadOptions and sets LoadOptionsSize to StrLen(LoadOptions)+1. func ComputeSystemdEFIStubCommandlineDigest(alg crypto.Hash, commandline string) []byte { h := alg.New() // Both GRUB's chainloader and systemd's EFI bootloader include a UTF-16 NULL terminator at // the end of LoadOptions and set LoadOptionsSize to StrLen(LoadOptions)+1. The EFI stub loader // measures LoadOptionsSize number of bytes, meaning that the 2 NULL bytes are measured. // Include those here. binary.Write(h, binary.LittleEndian, append(convertStringToUtf16(commandline), 0)) return h.Sum(nil) } func decodeEventDataSystemdEFIStub(data []byte, eventType EventType) *SystemdEFIStubCommandline { if eventType != EventTypeIPL { return nil } // data is a UTF-16 string in little-endian form terminated with a single zero byte, // so we should have an odd number of bytes. if len(data)%2 != 1 { return nil } if data[len(data)-1] != 0x00 { return nil } // Omit the zero byte added by the EFI stub and then convert to native byte order. reader := bytes.NewReader(data[:len(data)-1]) utf16Str := make([]uint16, len(data)/2) binary.Read(reader, binary.LittleEndian, &utf16Str) return &SystemdEFIStubCommandline{rawEventData: data, Str: convertUtf16ToString(utf16Str)} } ./github.com/canonical/tcglog-parser/tcgeventdata.go0000664000000000000000000001641600000000000021113 0ustar0000000000000000// Copyright 2019 Canonical Ltd. // Licensed under the LGPLv3 with static-linking exception. // See LICENCE file for details. package tcglog import ( "bytes" "crypto" "encoding/binary" "errors" "fmt" "io" "strings" "github.com/canonical/go-tpm2" "golang.org/x/xerrors" "github.com/canonical/tcglog-parser/internal/ioerr" ) var separatorErrorDigests = make(map[tpm2.HashAlgorithmId]tpm2.Digest) // StringEventData corresponds to event data that is an non-NULL terminated ASCII string. type StringEventData string func (d StringEventData) String() string { return string(d) } func (d StringEventData) Write(w io.Writer) error { _, err := io.WriteString(w, string(d)) return err } func (d StringEventData) Bytes() []byte { return []byte(d) } // ComputeStringEventDigest computes the digest associated with the supplied string, for // events where the digest is a tagged hash of the string. The function assumes that the // string is ASCII encoded and measured without a terminating NULL byte. func ComputeStringEventDigest(alg crypto.Hash, str string) []byte { h := alg.New() io.WriteString(h, str) return h.Sum(nil) } // https://trustedcomputinggroup.org/wp-content/uploads/TCG_PCClientImplementation_1-21_1_00.pdf // (section 11.3.4 "EV_NO_ACTION Event Types") // https://trustedcomputinggroup.org/wp-content/uploads/TCG_PCClientSpecPlat_TPM_2p0_1p04_pub.pdf // (section 9.4.5 "EV_NO_ACTION Event Types") func decodeEventDataNoAction(data []byte) (EventData, error) { r := bytes.NewReader(data) // Signature field var sig [16]byte if _, err := io.ReadFull(r, sig[:]); err != nil { return nil, ioerr.EOFIsUnexpected(err) } signature := strings.TrimRight(string(sig[:]), "\x00") switch signature { case "Spec ID Event00": out, err := decodeSpecIdEvent00(data, r) if err != nil { return nil, xerrors.Errorf("cannot decode Spec ID Event00 data: %w", err) } return out, nil case "Spec ID Event02": out, err := decodeSpecIdEvent02(data, r) if err != nil { return nil, xerrors.Errorf("cannot decode Spec ID Event02 data: %w", err) } return out, nil case "Spec ID Event03": out, err := decodeSpecIdEvent03(data, r) if err != nil { return nil, xerrors.Errorf("cannot decode Spec ID Event03 data: %w", err) } return out, nil case "SP800-155 Event": out, err := decodeBIMReferenceManifestEvent(data, r) if err != nil { return nil, xerrors.Errorf("cannot decode SP800-155 Event data: %w", err) } return out, nil case "StartupLocality": out, err := decodeStartupLocalityEvent(data, r) if err != nil { return nil, xerrors.Errorf("cannot decode StartupLocality data: %w", err) } return out, nil default: return nil, nil } } // https://trustedcomputinggroup.org/wp-content/uploads/TCG_PCClientImplementation_1-21_1_00.pdf (section 11.3.3 "EV_ACTION event types") // https://trustedcomputinggroup.org/wp-content/uploads/PC-ClientSpecific_Platform_Profile_for_TPM_2p0_Systems_v51.pdf (section 9.4.3 "EV_ACTION Event Types") func decodeEventDataAction(data []byte) StringEventData { return StringEventData(data) } func decodeEventDataHostPlatformSpecificCompactHash(data []byte) StringEventData { return StringEventData(data) } // SeparatorEventData is the event data associated with a EV_SEPARATOR event. type SeparatorEventData struct { rawEventData Value uint32 // The separator value measured to the TPM } func NewErrorSeparatorEventData(err []byte) *SeparatorEventData { return &SeparatorEventData{rawEventData: err, Value: SeparatorEventErrorValue} } // IsError indicates that this event was associated with an error condition. // The value returned from Bytes() contains an implementation defined indication // of the actual error. func (e *SeparatorEventData) IsError() bool { return e.Value == SeparatorEventErrorValue } func (e *SeparatorEventData) String() string { if !e.IsError() { return "" } return fmt.Sprintf("ERROR: 0x%x", e.rawEventData) } func (e *SeparatorEventData) Write(w io.Writer) error { switch e.Value { case SeparatorEventNormalValue, SeparatorEventAltNormalValue: return binary.Write(w, binary.LittleEndian, e.Value) case SeparatorEventErrorValue: _, err := w.Write(e.rawEventData) return err default: return errors.New("invalid value") } } // ComputeSeparatorEventDigest computes the digest associated with the separator event. The value // argument should be one of SeparatorEventNormalValue, SeparatorEventAltNormalValue or // SeparatorEventErrorValue. func ComputeSeparatorEventDigest(alg crypto.Hash, value uint32) []byte { h := alg.New() binary.Write(h, binary.LittleEndian, value) return h.Sum(nil) } // https://trustedcomputinggroup.org/wp-content/uploads/TCG_PCClientImplementation_1-21_1_00.pdf // (section 3.3.2.2 2 Error Conditions" , section 8.2.3 "Measuring Boot Events") // https://trustedcomputinggroup.org/wp-content/uploads/PC-ClientSpecific_Platform_Profile_for_TPM_2p0_Systems_v51.pdf: // (section 2.3.2 "Error Conditions", section 2.3.4 "PCR Usage", section 7.2 // "Procedure for Pre-OS to OS-Present Transition") func decodeEventDataSeparator(data []byte, digests DigestMap) (*SeparatorEventData, error) { var alg tpm2.HashAlgorithmId for a, _ := range digests { if !alg.IsValid() || a.Size() > alg.Size() { alg = a } } errorDigest, ok := separatorErrorDigests[alg] if !ok { h := alg.NewHash() binary.Write(h, binary.LittleEndian, SeparatorEventErrorValue) separatorErrorDigests[alg] = h.Sum(nil) errorDigest = separatorErrorDigests[alg] } if bytes.Equal(digests[alg], errorDigest) { return &SeparatorEventData{rawEventData: data, Value: SeparatorEventErrorValue}, nil } if len(data) != binary.Size(uint32(0)) { return nil, errors.New("data is the wrong size") } value := binary.LittleEndian.Uint32(data) switch value { case SeparatorEventNormalValue, SeparatorEventErrorValue, SeparatorEventAltNormalValue: default: return nil, fmt.Errorf("invalid separator value: %d", value) } return &SeparatorEventData{rawEventData: data, Value: value}, nil } // https://trustedcomputinggroup.org/wp-content/uploads/TCG_PCClientImplementation_1-21_1_00.pdf (section 11.3.1 "Event Types") // https://trustedcomputinggroup.org/wp-content/uploads/TCG_EFI_Platform_1_22_Final_-v15.pdf (section 7.2 "Event Types") // https://trustedcomputinggroup.org/wp-content/uploads/TCG_PCClientSpecPlat_TPM_2p0_1p04_pub.pdf (section 9.4.1 "Event Types") func decodeEventDataTCG(data []byte, pcrIndex PCRIndex, eventType EventType, digests DigestMap) (out EventData, err error) { switch eventType { case EventTypeNoAction: return decodeEventDataNoAction(data) case EventTypeSeparator: return decodeEventDataSeparator(data, digests) case EventTypeAction, EventTypeEFIAction: return decodeEventDataAction(data), nil case EventTypeCompactHash: if pcrIndex == 6 { return decodeEventDataHostPlatformSpecificCompactHash(data), nil } case EventTypeEFIVariableDriverConfig, EventTypeEFIVariableBoot, EventTypeEFIVariableAuthority, EventTypeEFIVariableBoot2: return decodeEventDataEFIVariable(data) case EventTypeEFIBootServicesApplication, EventTypeEFIBootServicesDriver, EventTypeEFIRuntimeServicesDriver: return decodeEventDataEFIImageLoad(data) case EventTypeEFIGPTEvent: return decodeEventDataEFIGPT(data) default: } if err != nil { err = xerrors.Errorf("cannot decode %v event data: %w", eventType, err) } return } ./github.com/canonical/tcglog-parser/tcgeventdata_bios.go0000664000000000000000000000461600000000000022126 0ustar0000000000000000// Copyright 2019-2021 Canonical Ltd. // Licensed under the LGPLv3 with static-linking exception. // See LICENCE file for details. package tcglog import ( "encoding/binary" "errors" "fmt" "io" "math" "github.com/canonical/tcglog-parser/internal/ioerr" ) type rawSpecIdEvent00Hdr struct { PlatformClass uint32 SpecVersionMinor uint8 SpecVersionMajor uint8 SpecErrata uint8 Reserved uint8 VendorInfoSize uint8 } // SpecIdEvent00 corresponds to the TCG_PCClientSpecIdEventStruct type and is the // event data for a Specification ID Version EV_NO_ACTION event for BIOS platforms. type SpecIdEvent00 struct { rawEventData PlatformClass uint32 SpecVersionMinor uint8 SpecVersionMajor uint8 SpecErrata uint8 VendorInfo []byte } func (e *SpecIdEvent00) String() string { return fmt.Sprintf("PCClientSpecIdEvent{ platformClass=%d, specVersionMinor=%d, specVersionMajor=%d, specErrata=%d }", e.PlatformClass, e.SpecVersionMinor, e.SpecVersionMajor, e.SpecErrata) } func (e *SpecIdEvent00) Write(w io.Writer) error { vendorInfoSize := len(e.VendorInfo) if vendorInfoSize > math.MaxUint8 { return errors.New("VendorInfo too large") } var signature [16]byte copy(signature[:], []byte("Spec ID Event00")) if _, err := w.Write(signature[:]); err != nil { return err } spec := rawSpecIdEvent00Hdr{ PlatformClass: e.PlatformClass, SpecVersionMinor: e.SpecVersionMinor, SpecVersionMajor: e.SpecVersionMajor, SpecErrata: e.SpecErrata, VendorInfoSize: uint8(vendorInfoSize)} if err := binary.Write(w, binary.LittleEndian, &spec); err != nil { return err } _, err := w.Write(e.VendorInfo) return err } // https://trustedcomputinggroup.org/wp-content/uploads/TCG_PCClientImplementation_1-21_1_00.pdf // (section 11.3.4.1 "Specification Event") func decodeSpecIdEvent00(data []byte, r io.Reader) (out *SpecIdEvent00, err error) { var spec rawSpecIdEvent00Hdr if err := binary.Read(r, binary.LittleEndian, &spec); err != nil { return nil, ioerr.EOFIsUnexpected(err) } out = &SpecIdEvent00{ rawEventData: data, PlatformClass: spec.PlatformClass, SpecVersionMinor: spec.SpecVersionMinor, SpecVersionMajor: spec.SpecVersionMajor, SpecErrata: spec.SpecErrata, VendorInfo: make([]byte, spec.VendorInfoSize)} if _, err := io.ReadFull(r, out.VendorInfo); err != nil { return nil, ioerr.EOFIsUnexpected(err) } return out, nil } ./github.com/canonical/tcglog-parser/tcgeventdata_efi.go0000664000000000000000000004103100000000000021725 0ustar0000000000000000// Copyright 2019 Canonical Ltd. // Licensed under the LGPLv3 with static-linking exception. // See LICENCE file for details. package tcglog import ( "bytes" "crypto" "encoding/binary" "encoding/hex" "errors" "fmt" "io" "math" "strings" "unicode/utf8" "github.com/canonical/go-efilib" "github.com/canonical/go-tpm2" "golang.org/x/xerrors" "github.com/canonical/tcglog-parser/internal/ioerr" ) var ( surr1 uint16 = 0xd800 surr2 uint16 = 0xdc00 surr3 uint16 = 0xe000 ) // UEFI_VARIABLE_DATA specifies the number of *characters* for a UTF-16 sequence rather than the size of // the buffer. Extract a UTF-16 sequence of the correct length, given a buffer and the number of characters. // The returned buffer can be passed to utf16.Decode. func extractUTF16Buffer(r io.ReadSeeker, nchars uint64) ([]uint16, error) { var out []uint16 for i := nchars; i > 0; i-- { var c uint16 if err := binary.Read(r, binary.LittleEndian, &c); err != nil { return nil, err } out = append(out, c) if c >= surr1 && c < surr2 { if err := binary.Read(r, binary.LittleEndian, &c); err != nil { return nil, err } if c < surr2 || c >= surr3 { // Invalid surrogate sequence. utf16.Decode doesn't consume this // byte when inserting the replacement char if _, err := r.Seek(-1, io.SeekCurrent); err != nil { return nil, err } continue } // Valid surrogate sequence out = append(out, c) } } return out, nil } type rawSpecIdEvent02Hdr struct { PlatformClass uint32 SpecVersionMinor uint8 SpecVersionMajor uint8 SpecErrata uint8 UintnSize uint8 VendorInfoSize uint8 } // SpecIdEvent02 corresponds to the TCG_EfiSpecIdEventStruct type and is the // event data for a Specification ID Version EV_NO_ACTION event on EFI platforms // for TPM family 1.2. type SpecIdEvent02 struct { rawEventData PlatformClass uint32 SpecVersionMinor uint8 SpecVersionMajor uint8 SpecErrata uint8 UintnSize uint8 VendorInfo []byte } func (e *SpecIdEvent02) String() string { return fmt.Sprintf("EfiSpecIdEvent{ platformClass=%d, specVersionMinor=%d, specVersionMajor=%d, specErrata=%d, uintnSize=%d }", e.PlatformClass, e.SpecVersionMinor, e.SpecVersionMajor, e.SpecErrata, e.UintnSize) } func (e *SpecIdEvent02) Write(w io.Writer) error { vendorInfoSize := len(e.VendorInfo) if vendorInfoSize > math.MaxUint8 { return errors.New("VendorInfo too large") } var signature [16]byte copy(signature[:], []byte("Spec ID Event02")) if _, err := w.Write(signature[:]); err != nil { return err } spec := rawSpecIdEvent02Hdr{ PlatformClass: e.PlatformClass, SpecVersionMinor: e.SpecVersionMinor, SpecVersionMajor: e.SpecVersionMajor, SpecErrata: e.SpecErrata, UintnSize: e.UintnSize, VendorInfoSize: uint8(vendorInfoSize)} if err := binary.Write(w, binary.LittleEndian, &spec); err != nil { return err } _, err := w.Write(e.VendorInfo) return err } // https://trustedcomputinggroup.org/wp-content/uploads/TCG_EFI_Platform_1_22_Final_-v15.pdf // (section 7.4 "EV_NO_ACTION Event Types") func decodeSpecIdEvent02(data []byte, r io.Reader) (out *SpecIdEvent02, err error) { var spec rawSpecIdEvent02Hdr if err := binary.Read(r, binary.LittleEndian, &spec); err != nil { return nil, ioerr.EOFIsUnexpected(err) } out = &SpecIdEvent02{ rawEventData: data, PlatformClass: spec.PlatformClass, SpecVersionMinor: spec.SpecVersionMinor, SpecVersionMajor: spec.SpecVersionMajor, SpecErrata: spec.SpecErrata, UintnSize: spec.UintnSize, VendorInfo: make([]byte, spec.VendorInfoSize)} if _, err := io.ReadFull(r, out.VendorInfo); err != nil { return nil, ioerr.EOFIsUnexpected(err) } return out, nil } // EFISpecIdEventAlgorithmSize represents a digest algorithm and its length and corresponds to the // TCG_EfiSpecIdEventAlgorithmSize type. type EFISpecIdEventAlgorithmSize struct { AlgorithmId tpm2.HashAlgorithmId DigestSize uint16 } type rawSpecIdEvent03Hdr struct { PlatformClass uint32 SpecVersionMinor uint8 SpecVersionMajor uint8 SpecErrata uint8 UintnSize uint8 NumberOfAlgorithms uint32 } // SpecIdEvent03 corresponds to the TCG_EfiSpecIdEvent type and is the // event data for a Specification ID Version EV_NO_ACTION event on EFI platforms // for TPM family 2.0. type SpecIdEvent03 struct { rawEventData PlatformClass uint32 SpecVersionMinor uint8 SpecVersionMajor uint8 SpecErrata uint8 UintnSize uint8 DigestSizes []EFISpecIdEventAlgorithmSize // The digest algorithms contained within this log VendorInfo []byte } func (e *SpecIdEvent03) String() string { var builder bytes.Buffer fmt.Fprintf(&builder, "EfiSpecIdEvent{ platformClass=%d, specVersionMinor=%d, specVersionMajor=%d, specErrata=%d, uintnSize=%d, digestSizes=[", e.PlatformClass, e.SpecVersionMinor, e.SpecVersionMajor, e.SpecErrata, e.UintnSize) for i, algSize := range e.DigestSizes { if i > 0 { builder.WriteString(", ") } fmt.Fprintf(&builder, "{ algorithmId=0x%04x, digestSize=%d }", uint16(algSize.AlgorithmId), algSize.DigestSize) } builder.WriteString("] }") return builder.String() } func (e *SpecIdEvent03) Write(w io.Writer) error { vendorInfoSize := len(e.VendorInfo) if vendorInfoSize > math.MaxUint8 { return errors.New("VendorInfo too large") } var signature [16]byte copy(signature[:], []byte("Spec ID Event03")) if _, err := w.Write(signature[:]); err != nil { return err } spec := rawSpecIdEvent03Hdr{ PlatformClass: e.PlatformClass, SpecVersionMinor: e.SpecVersionMinor, SpecVersionMajor: e.SpecVersionMajor, SpecErrata: e.SpecErrata, UintnSize: e.UintnSize, NumberOfAlgorithms: uint32(len(e.DigestSizes))} if err := binary.Write(w, binary.LittleEndian, &spec); err != nil { return err } if err := binary.Write(w, binary.LittleEndian, e.DigestSizes); err != nil { return err } if err := binary.Write(w, binary.LittleEndian, uint8(vendorInfoSize)); err != nil { return err } _, err := w.Write(e.VendorInfo) return err } // https://trustedcomputinggroup.org/wp-content/uploads/TCG_PCClientSpecPlat_TPM_2p0_1p04_pub.pdf // (secion 9.4.5.1 "Specification ID Version Event") func decodeSpecIdEvent03(data []byte, r io.Reader) (out *SpecIdEvent03, err error) { var spec rawSpecIdEvent03Hdr if err := binary.Read(r, binary.LittleEndian, &spec); err != nil { return nil, ioerr.EOFIsUnexpected(err) } out = &SpecIdEvent03{ rawEventData: data, PlatformClass: spec.PlatformClass, SpecVersionMinor: spec.SpecVersionMinor, SpecVersionMajor: spec.SpecVersionMajor, SpecErrata: spec.SpecErrata, UintnSize: spec.UintnSize} if spec.NumberOfAlgorithms < 1 { return nil, errors.New("numberOfAlgorithms is zero") } out.DigestSizes = make([]EFISpecIdEventAlgorithmSize, spec.NumberOfAlgorithms) if err := binary.Read(r, binary.LittleEndian, out.DigestSizes); err != nil { return nil, ioerr.EOFIsUnexpected(err) } for _, d := range out.DigestSizes { if d.AlgorithmId.IsValid() && d.AlgorithmId.Size() != int(d.DigestSize) { return nil, fmt.Errorf("digestSize for algorithmId %v does not match expected size", d.AlgorithmId) } } var vendorInfoSize uint8 if err := binary.Read(r, binary.LittleEndian, &vendorInfoSize); err != nil { return nil, ioerr.EOFIsUnexpected(err) } out.VendorInfo = make([]byte, vendorInfoSize) if _, err := io.ReadFull(r, out.VendorInfo); err != nil { return nil, ioerr.EOFIsUnexpected(err) } return out, nil } // StartupLocalityEventData is the event data for a StartupLocality EV_NO_ACTION event. type StartupLocalityEventData struct { rawEventData StartupLocality uint8 } func (e *StartupLocalityEventData) String() string { return fmt.Sprintf("EfiStartupLocalityEvent{ StartupLocality: %d }", e.StartupLocality) } func (e *StartupLocalityEventData) Write(w io.Writer) error { var signature [16]byte copy(signature[:], []byte("StartupLocality")) if _, err := w.Write(signature[:]); err != nil { return err } return binary.Write(w, binary.LittleEndian, e.StartupLocality) } // https://trustedcomputinggroup.org/wp-content/uploads/TCG_PCClientSpecPlat_TPM_2p0_1p04_pub.pdf // (section 9.4.5.3 "Startup Locality Event") func decodeStartupLocalityEvent(data []byte, r io.Reader) (*StartupLocalityEventData, error) { var locality uint8 if err := binary.Read(r, binary.LittleEndian, &locality); err != nil { return nil, ioerr.EOFIsUnexpected(err) } return &StartupLocalityEventData{rawEventData: data, StartupLocality: locality}, nil } // SP800_155_PlatformIdEventData corresponds to the event data for a SP800-155-Event // EV_NO_ACTION event type SP800_155_PlatformIdEventData struct { rawEventData VendorId uint32 ReferenceManifestGuid efi.GUID } func (e *SP800_155_PlatformIdEventData) String() string { return fmt.Sprintf("Sp800_155_PlatformId_Event{ VendorId: %d, ReferenceManifestGuid: %s }", e.VendorId, e.ReferenceManifestGuid) } func (e *SP800_155_PlatformIdEventData) Write(w io.Writer) error { var signature [16]byte copy(signature[:], []byte("SP800-155 Event")) if _, err := w.Write(signature[:]); err != nil { return err } if err := binary.Write(w, binary.LittleEndian, e.VendorId); err != nil { return err } _, err := w.Write(e.ReferenceManifestGuid[:]) return err } // https://trustedcomputinggroup.org/wp-content/uploads/TCG_PCClientSpecPlat_TPM_2p0_1p04_pub.pdf // (section 9.4.5.2 "BIOS Integrity Measurement Reference Manifest Event") // https://trustedcomputinggroup.org/wp-content/uploads/TCG_EFI_Platform_1_22_Final_-v15.pdf // (section 7.4 "EV_NO_ACTION Event Types") func decodeBIMReferenceManifestEvent(data []byte, r io.Reader) (*SP800_155_PlatformIdEventData, error) { var d struct { VendorId uint32 Guid efi.GUID } if err := binary.Read(r, binary.LittleEndian, &d); err != nil { return nil, ioerr.EOFIsUnexpected(err) } return &SP800_155_PlatformIdEventData{rawEventData: data, VendorId: d.VendorId, ReferenceManifestGuid: d.Guid}, nil } // EFIVariableData corresponds to the EFI_VARIABLE_DATA type and is the event data associated with the measurement of an // EFI variable. type EFIVariableData struct { rawEventData VariableName efi.GUID UnicodeName string VariableData []byte } func (e *EFIVariableData) String() string { return fmt.Sprintf("UEFI_VARIABLE_DATA{ VariableName: %s, UnicodeName: \"%s\", VariableData:\n\t%s}", e.VariableName, e.UnicodeName, strings.Replace(hex.Dump(e.VariableData), "\n", "\n\t", -1)) } func (e *EFIVariableData) Write(w io.Writer) error { if _, err := w.Write(e.VariableName[:]); err != nil { return err } if err := binary.Write(w, binary.LittleEndian, uint64(utf8.RuneCount([]byte(e.UnicodeName)))); err != nil { return err } if err := binary.Write(w, binary.LittleEndian, uint64(len(e.VariableData))); err != nil { return err } if err := binary.Write(w, binary.LittleEndian, convertStringToUtf16(e.UnicodeName)); err != nil { return err } _, err := w.Write(e.VariableData) return err } // ComputeEFIVariableDataDigest computes the EFI_VARIABLE_DATA digest associated with the supplied // parameters func ComputeEFIVariableDataDigest(alg crypto.Hash, name string, guid efi.GUID, data []byte) []byte { h := alg.New() varData := EFIVariableData{VariableName: guid, UnicodeName: name, VariableData: data} varData.Write(h) return h.Sum(nil) } // https://trustedcomputinggroup.org/wp-content/uploads/TCG_EFI_Platform_1_22_Final_-v15.pdf (section 7.8 "Measuring EFI Variables") // https://trustedcomputinggroup.org/wp-content/uploads/TCG_PCClientSpecPlat_TPM_2p0_1p04_pub.pdf (section 9.2.6 "Measuring UEFI Variables") func decodeEventDataEFIVariable(data []byte) (*EFIVariableData, error) { r := bytes.NewReader(data) d := &EFIVariableData{rawEventData: data} variableName, err := efi.ReadGUID(r) if err != nil { return nil, ioerr.EOFIsUnexpected(err) } d.VariableName = variableName var unicodeNameLength uint64 if err := binary.Read(r, binary.LittleEndian, &unicodeNameLength); err != nil { return nil, ioerr.EOFIsUnexpected(err) } var variableDataLength uint64 if err := binary.Read(r, binary.LittleEndian, &variableDataLength); err != nil { return nil, ioerr.EOFIsUnexpected(err) } utf16Name, err := extractUTF16Buffer(r, unicodeNameLength) if err != nil { return nil, ioerr.EOFIsUnexpected(err) } d.UnicodeName = convertUtf16ToString(utf16Name) d.VariableData = make([]byte, variableDataLength) if _, err := io.ReadFull(r, d.VariableData); err != nil { return nil, ioerr.EOFIsUnexpected(err) } return d, nil } type rawEFIImageLoadEventHdr struct { LocationInMemory efi.PhysicalAddress LengthInMemory uint64 LinkTimeAddress uint64 LengthOfDevicePath uint64 } type EFIImageLoadEvent struct { rawEventData LocationInMemory efi.PhysicalAddress LengthInMemory uint64 LinkTimeAddress uint64 DevicePath efi.DevicePath } func (e *EFIImageLoadEvent) String() string { return fmt.Sprintf("UEFI_IMAGE_LOAD_EVENT{ ImageLocationInMemory: 0x%016x, ImageLengthInMemory: %d, "+ "ImageLinkTimeAddress: 0x%016x, DevicePath: %s }", e.LocationInMemory, e.LengthInMemory, e.LinkTimeAddress, e.DevicePath) } func (e *EFIImageLoadEvent) Write(w io.Writer) error { dpw := new(bytes.Buffer) if err := e.DevicePath.Write(dpw); err != nil { return xerrors.Errorf("cannot write device path: %w", err) } ev := rawEFIImageLoadEventHdr{ LocationInMemory: e.LocationInMemory, LengthInMemory: e.LengthInMemory, LinkTimeAddress: e.LinkTimeAddress, LengthOfDevicePath: uint64(dpw.Len())} if err := binary.Write(w, binary.LittleEndian, &ev); err != nil { return err } _, err := dpw.WriteTo(w) return err } // https://trustedcomputinggroup.org/wp-content/uploads/TCG_EFI_Platform_1_22_Final_-v15.pdf (section 4 "Measuring PE/COFF Image Files") // https://trustedcomputinggroup.org/wp-content/uploads/TCG_PCClientSpecPlat_TPM_2p0_1p04_pub.pdf (section 9.2.3 "UEFI_IMAGE_LOAD_EVENT Structure") func decodeEventDataEFIImageLoad(data []byte) (*EFIImageLoadEvent, error) { r := bytes.NewReader(data) var e rawEFIImageLoadEventHdr if err := binary.Read(r, binary.LittleEndian, &e); err != nil { return nil, ioerr.EOFIsUnexpected(err) } lr := io.LimitReader(r, int64(e.LengthOfDevicePath)) path, err := efi.ReadDevicePath(lr) if err != nil { return nil, ioerr.EOFIsUnexpected(err) } return &EFIImageLoadEvent{ rawEventData: data, LocationInMemory: e.LocationInMemory, LengthInMemory: e.LengthInMemory, LinkTimeAddress: e.LinkTimeAddress, DevicePath: path}, nil } // EFIGPTData corresponds to UEFI_GPT_DATA and is the event data for EV_EFI_GPT_EVENT events. type EFIGPTData struct { rawEventData Hdr efi.PartitionTableHeader Partitions []*efi.PartitionEntry } func (e *EFIGPTData) String() string { var builder bytes.Buffer fmt.Fprintf(&builder, "UEFI_GPT_DATA{\n\tHdr: %s,\n\tPartitions: [", &e.Hdr) for _, part := range e.Partitions { fmt.Fprintf(&builder, "\n\t\t%s", part) } fmt.Fprintf(&builder, "\n\t]\n}") return builder.String() } func (e *EFIGPTData) Write(w io.Writer) error { if err := e.Hdr.Write(w); err != nil { return err } if err := binary.Write(w, binary.LittleEndian, uint64(len(e.Partitions))); err != nil { return err } for _, entry := range e.Partitions { w2 := new(bytes.Buffer) if err := entry.Write(w2); err != nil { return err } if w2.Len() > int(e.Hdr.SizeOfPartitionEntry) { return errors.New("SizeOfPartitionEntry too small") } b := make([]byte, e.Hdr.SizeOfPartitionEntry) copy(b, w2.Bytes()) if _, err := w.Write(b); err != nil { return err } } return nil } func decodeEventDataEFIGPT(data []byte) (*EFIGPTData, error) { r := bytes.NewReader(data) d := &EFIGPTData{rawEventData: data} // UEFI_GPT_DATA.UEFIPartitionHeader hdr, err := efi.ReadPartitionTableHeader(r, false) if err != nil { return nil, ioerr.EOFIsUnexpected(err) } d.Hdr = *hdr // UEFI_GPT_DATA.NumberOfPartitions var numberOfParts uint64 if err := binary.Read(r, binary.LittleEndian, &numberOfParts); err != nil { return nil, ioerr.EOFIsUnexpected(err) } if numberOfParts > math.MaxUint32 { return nil, errors.New("invalid EFI_GPT_DATA.NumberOfPartitons") } // UEFI_GPT_DATA.Partitions partitions, err := efi.ReadPartitionEntries(r, uint32(numberOfParts), hdr.SizeOfPartitionEntry) if err != nil { return nil, ioerr.EOFIsUnexpected(err) } d.Partitions = partitions return d, nil } // ComputeEFIGPTDataDigest computes a UEFI_GPT_DATA digest from the supplied data. func ComputeEFIGPTDataDigest(alg crypto.Hash, data *EFIGPTData) ([]byte, error) { h := alg.New() if err := data.Write(h); err != nil { return nil, err } return h.Sum(nil), nil } ./github.com/canonical/tcglog-parser/types.go0000664000000000000000000000622500000000000017603 0ustar0000000000000000// Copyright 2019 Canonical Ltd. // Licensed under the LGPLv3 with static-linking exception. // See LICENCE file for details. package tcglog import ( "fmt" "github.com/canonical/go-tpm2" ) // PCRIndex corresponds to the index of a PCR on the TPM. type PCRIndex uint32 // EventType corresponds to the type of an event in an event log. type EventType uint32 // Digest is the result of hashing some data. type Digest []byte // DigestMap is a map of algorithms to digests. type DigestMap map[tpm2.HashAlgorithmId]Digest func (e EventType) String() string { switch e { case EventTypePrebootCert: return "EV_PREBOOT_CERT" case EventTypePostCode: return "EV_POST_CODE" case EventTypeNoAction: return "EV_NO_ACTION" case EventTypeSeparator: return "EV_SEPARATOR" case EventTypeAction: return "EV_ACTION" case EventTypeEventTag: return "EV_EVENT_TAG" case EventTypeSCRTMContents: return "EV_S_CRTM_CONTENTS" case EventTypeSCRTMVersion: return "EV_S_CRTM_VERSION" case EventTypeCPUMicrocode: return "EV_CPU_MICROCODE" case EventTypePlatformConfigFlags: return "EV_PLATFORM_CONFIG_FLAGS" case EventTypeTableOfDevices: return "EV_TABLE_OF_DEVICES" case EventTypeCompactHash: return "EV_COMPACT_HASH" case EventTypeIPL: return "EV_IPL" case EventTypeIPLPartitionData: return "EV_IPL_PARTITION_DATA" case EventTypeNonhostCode: return "EV_NONHOST_CODE" case EventTypeNonhostConfig: return "EV_NONHOST_CONFIG" case EventTypeNonhostInfo: return "EV_NONHOST_INFO" case EventTypeOmitBootDeviceEvents: return "EV_OMIT_BOOT_DEVICE_EVENTS" case EventTypeEFIVariableDriverConfig: return "EV_EFI_VARIABLE_DRIVER_CONFIG" case EventTypeEFIVariableBoot: return "EV_EFI_VARIABLE_BOOT" case EventTypeEFIBootServicesApplication: return "EV_EFI_BOOT_SERVICES_APPLICATION" case EventTypeEFIBootServicesDriver: return "EV_EFI_BOOT_SERVICES_DRIVER" case EventTypeEFIRuntimeServicesDriver: return "EV_EFI_RUNTIME_SERVICES_DRIVER" case EventTypeEFIGPTEvent: return "EF_EFI_GPT_EVENT" case EventTypeEFIAction: return "EV_EFI_ACTION" case EventTypeEFIPlatformFirmwareBlob: return "EV_EFI_PLATFORM_FIRMWARE_BLOB" case EventTypeEFIHandoffTables: return "EV_EFI_HANDOFF_TABLES" case EventTypeEFIPlatformFirmwareBlob2: return "EV_EFI_PLATFORM_FIRMWARE_BLOB2" case EventTypeEFIHandoffTables2: return "EV_EFI_HANDOFF_TABLES2" case EventTypeEFIVariableBoot2: return "EV_EFI_VARIABLE_BOOT2" case EventTypeEFIHCRTMEvent: return "EV_EFI_HCRTM_EVENT" case EventTypeEFIVariableAuthority: return "EV_EFI_VARIABLE_AUTHORITY" case EventTypeEFISPDMFirmwareBlob: return "EV_EFI_SPDM_FIRMWARE_BLOB" case EventTypeEFISPDMFirmwareConfig: return "EV_EFI_SPDM_FIRMWARE_CONFIG" default: return fmt.Sprintf("%08x", uint32(e)) } } func (e EventType) Format(s fmt.State, f rune) { switch f { case 's', 'v': fmt.Fprintf(s, "%s", e.String()) default: fmt.Fprintf(s, makeDefaultFormatter(s, f), uint32(e)) } } // AlgorithmListId is a slice of tpm2.HashAlgorithmId values, type AlgorithmIdList []tpm2.HashAlgorithmId func (l AlgorithmIdList) Contains(a tpm2.HashAlgorithmId) bool { for _, alg := range l { if alg == a { return true } } return false } ./github.com/canonical/tcglog-parser/utils.go0000664000000000000000000000215000000000000017570 0ustar0000000000000000// Copyright 2019 Canonical Ltd. // Licensed under the LGPLv3 with static-linking exception. // See LICENCE file for details. package tcglog import ( "bytes" "fmt" "unicode/utf16" "unicode/utf8" ) func makeDefaultFormatter(s fmt.State, f rune) string { var builder bytes.Buffer builder.WriteString("%%") for _, flag := range [...]int{'+', '-', '#', ' ', '0'} { if s.Flag(flag) { fmt.Fprintf(&builder, "%c", flag) } } if width, ok := s.Width(); ok { fmt.Fprintf(&builder, "%d", width) } if prec, ok := s.Precision(); ok { fmt.Fprintf(&builder, ".%d", prec) } fmt.Fprintf(&builder, "%c", f) return builder.String() } func convertStringToUtf16(str string) []uint16 { var unicodePoints []rune for len(str) > 0 { r, s := utf8.DecodeRuneInString(str) unicodePoints = append(unicodePoints, r) str = str[s:] } return utf16.Encode(unicodePoints) } func convertUtf16ToString(u []uint16) string { var utf8Str []byte for _, r := range utf16.Decode(u) { utf8Char := make([]byte, utf8.RuneLen(r)) utf8.EncodeRune(utf8Char, r) utf8Str = append(utf8Str, utf8Char...) } return string(utf8Str) } ./github.com/godbus/0000775000000000000000000000000000000000000012666 5ustar0000000000000000./github.com/godbus/dbus/0000775000000000000000000000000000000000000013623 5ustar0000000000000000./github.com/godbus/dbus/.travis.yml0000664000000000000000000000076700000000000015746 0ustar0000000000000000dist: precise language: go go_import_path: github.com/godbus/dbus sudo: true go: - 1.6.3 - 1.7.3 - tip env: global: matrix: - TARGET=amd64 - TARGET=arm64 - TARGET=arm - TARGET=386 - TARGET=ppc64le matrix: fast_finish: true allow_failures: - go: tip exclude: - go: tip env: TARGET=arm - go: tip env: TARGET=arm64 - go: tip env: TARGET=386 - go: tip env: TARGET=ppc64le addons: apt: packages: - dbus - dbus-x11 before_install: ./github.com/godbus/dbus/CONTRIBUTING.md0000664000000000000000000000271400000000000016060 0ustar0000000000000000# How to Contribute ## Getting Started - Fork the repository on GitHub - Read the [README](README.markdown) for build and test instructions - Play with the project, submit bugs, submit patches! ## Contribution Flow This is a rough outline of what a contributor's workflow looks like: - Create a topic branch from where you want to base your work (usually master). - Make commits of logical units. - Make sure your commit messages are in the proper format (see below). - Push your changes to a topic branch in your fork of the repository. - Make sure the tests pass, and add any new tests as appropriate. - Submit a pull request to the original repository. Thanks for your contributions! ### Format of the Commit Message We follow a rough convention for commit messages that is designed to answer two questions: what changed and why. The subject line should feature the what and the body of the commit should describe the why. ``` scripts: add the test-cluster command this uses tmux to setup a test cluster that you can easily kill and start for debugging. Fixes #38 ``` The format can be described more formally as follows: ``` :