Lorenz Brun | ae0d90d | 2019-09-05 17:53:56 +0200 | [diff] [blame] | 1 | // Copyright 2020 The Monogon Project Authors. |
| 2 | // |
| 3 | // SPDX-License-Identifier: Apache-2.0 |
| 4 | // |
| 5 | // Licensed under the Apache License, Version 2.0 (the "License"); |
| 6 | // you may not use this file except in compliance with the License. |
| 7 | // You may obtain a copy of the License at |
| 8 | // |
| 9 | // http://www.apache.org/licenses/LICENSE-2.0 |
| 10 | // |
| 11 | // Unless required by applicable law or agreed to in writing, software |
| 12 | // distributed under the License is distributed on an "AS IS" BASIS, |
| 13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 14 | // See the License for the specific language governing permissions and |
| 15 | // limitations under the License. |
| 16 | |
| 17 | package tpm |
| 18 | |
| 19 | import ( |
| 20 | "crypto/rand" |
| 21 | "fmt" |
| 22 | "io" |
| 23 | "os" |
| 24 | "path/filepath" |
| 25 | "smalltown/pkg/sysfs" |
| 26 | "strconv" |
| 27 | "sync" |
| 28 | |
| 29 | "github.com/gogo/protobuf/proto" |
| 30 | tpmpb "github.com/google/go-tpm-tools/proto" |
| 31 | "github.com/google/go-tpm-tools/tpm2tools" |
| 32 | "github.com/google/go-tpm/tpm2" |
| 33 | "github.com/pkg/errors" |
| 34 | "go.uber.org/zap" |
| 35 | "golang.org/x/sys/unix" |
| 36 | ) |
| 37 | |
| 38 | var ( |
| 39 | // SecureBootPCRs are all PCRs that measure the current Secure Boot configuration |
| 40 | SecureBootPCRs = []int{7} |
| 41 | |
| 42 | // FirmwarePCRs are alle PCRs that contain the firmware measurements |
| 43 | // See https://trustedcomputinggroup.org/wp-content/uploads/TCG_EFI_Platform_1_22_Final_-v15.pdf |
| 44 | FirmwarePCRs = []int{0, 2, 3} |
| 45 | |
| 46 | // FullSystemPCRs are all PCRs that contain any measurements up to the currently running EFI |
| 47 | // payload. |
| 48 | FullSystemPCRs = []int{0, 1, 2, 3, 4} |
| 49 | ) |
| 50 | |
| 51 | var ( |
| 52 | // ErrNotExists is returned when no TPMs are available in the system |
| 53 | ErrNotExists = errors.New("no TPMs found") |
| 54 | // ErrNotInitialized is returned when this package was not initialized successfully |
| 55 | ErrNotInitialized = errors.New("no TPM was initialized") |
| 56 | ) |
| 57 | |
| 58 | // Singleton since the TPM is too |
| 59 | var tpm *TPM |
| 60 | |
| 61 | // We're serializing all TPM operations since it has a limited number of handles and recovering |
| 62 | // if it runs out is difficult to implement correctly. Might also be marginally more secure. |
| 63 | var lock sync.Mutex |
| 64 | |
| 65 | // TPM represents a high-level interface to a connected TPM 2.0 |
| 66 | type TPM struct { |
| 67 | logger *zap.Logger |
| 68 | device io.ReadWriteCloser |
| 69 | } |
| 70 | |
| 71 | // Initialize finds and opens the TPM (if any). If there is no TPM available it returns |
| 72 | // ErrNotExists |
| 73 | func Initialize(logger *zap.Logger) error { |
| 74 | lock.Lock() |
| 75 | defer lock.Unlock() |
| 76 | tpmDir, err := os.Open("/sys/class/tpm") |
| 77 | if err != nil { |
| 78 | return errors.Wrap(err, "failed to open sysfs TPM class") |
| 79 | } |
| 80 | defer tpmDir.Close() |
| 81 | |
| 82 | tpms, err := tpmDir.Readdirnames(2) |
| 83 | if err != nil { |
| 84 | return errors.Wrap(err, "failed to read TPM device class") |
| 85 | } |
| 86 | |
| 87 | if len(tpms) == 0 { |
| 88 | return ErrNotExists |
| 89 | } |
| 90 | if len(tpms) > 1 { |
| 91 | logger.Warn("Found more than one TPM, using the first one") |
| 92 | } |
| 93 | tpmName := tpms[0] |
| 94 | ueventData, err := sysfs.ReadUevents(filepath.Join("/sys/class/tpm", tpmName, "uevent")) |
| 95 | majorDev, err := strconv.Atoi(ueventData["MAJOR"]) |
| 96 | if err != nil { |
| 97 | return fmt.Errorf("failed to convert uevent: %w", err) |
| 98 | } |
| 99 | minorDev, err := strconv.Atoi(ueventData["MINOR"]) |
| 100 | if err != nil { |
| 101 | return fmt.Errorf("failed to convert uevent: %w", err) |
| 102 | } |
| 103 | if err := unix.Mknod("/dev/tpm", 0600|unix.S_IFCHR, int(unix.Mkdev(uint32(majorDev), uint32(minorDev)))); err != nil { |
| 104 | return errors.Wrap(err, "failed to create TPM device node") |
| 105 | } |
| 106 | device, err := tpm2.OpenTPM("/dev/tpm") |
| 107 | if err != nil { |
| 108 | return errors.Wrap(err, "failed to open TPM") |
| 109 | } |
| 110 | tpm = &TPM{ |
| 111 | device: device, |
| 112 | logger: logger, |
| 113 | } |
| 114 | return nil |
| 115 | } |
| 116 | |
| 117 | // GenerateSafeKey uses two sources of randomness (Kernel & TPM) to generate the key |
| 118 | func GenerateSafeKey(size uint16) ([]byte, error) { |
| 119 | lock.Lock() |
| 120 | defer lock.Unlock() |
| 121 | if tpm == nil { |
| 122 | return []byte{}, ErrNotInitialized |
| 123 | } |
| 124 | encryptionKeyHost := make([]byte, size) |
| 125 | if _, err := io.ReadFull(rand.Reader, encryptionKeyHost); err != nil { |
| 126 | return []byte{}, errors.Wrap(err, "failed to generate host portion of new key") |
| 127 | } |
| 128 | var encryptionKeyTPM []byte |
| 129 | for i := 48; i > 0; i-- { |
| 130 | tpmKeyPart, err := tpm2.GetRandom(tpm.device, size-uint16(len(encryptionKeyTPM))) |
| 131 | if err != nil { |
| 132 | return []byte{}, errors.Wrap(err, "failed to generate TPM portion of new key") |
| 133 | } |
| 134 | encryptionKeyTPM = append(encryptionKeyTPM, tpmKeyPart...) |
| 135 | if len(encryptionKeyTPM) >= int(size) { |
| 136 | break |
| 137 | } |
| 138 | } |
| 139 | |
| 140 | if len(encryptionKeyTPM) != int(size) { |
| 141 | return []byte{}, fmt.Errorf("got incorrect amount of TPM randomess: %v, requested %v", len(encryptionKeyTPM), size) |
| 142 | } |
| 143 | |
| 144 | encryptionKey := make([]byte, size) |
| 145 | for i := uint16(0); i < size; i++ { |
| 146 | encryptionKey[i] = encryptionKeyHost[i] ^ encryptionKeyTPM[i] |
| 147 | } |
| 148 | return encryptionKey, nil |
| 149 | } |
| 150 | |
| 151 | // Seal seals sensitive data and only allows access if the current platform configuration in |
| 152 | // matches the one the data was sealed on. |
| 153 | func Seal(data []byte, pcrs []int) ([]byte, error) { |
| 154 | lock.Lock() |
| 155 | defer lock.Unlock() |
| 156 | if tpm == nil { |
| 157 | return []byte{}, ErrNotInitialized |
| 158 | } |
| 159 | srk, err := tpm2tools.StorageRootKeyRSA(tpm.device) |
| 160 | if err != nil { |
| 161 | return []byte{}, errors.Wrap(err, "failed to load TPM SRK") |
| 162 | } |
| 163 | defer srk.Close() |
| 164 | sealedKey, err := srk.Seal(pcrs, data) |
| 165 | sealedKeyRaw, err := proto.Marshal(sealedKey) |
| 166 | if err != nil { |
| 167 | return []byte{}, errors.Wrapf(err, "failed to marshal sealed data") |
| 168 | } |
| 169 | return sealedKeyRaw, nil |
| 170 | } |
| 171 | |
| 172 | // Unseal unseals sensitive data if the current platform configuration allows and sealing constraints |
| 173 | // allow it. |
| 174 | func Unseal(data []byte) ([]byte, error) { |
| 175 | lock.Lock() |
| 176 | defer lock.Unlock() |
| 177 | if tpm == nil { |
| 178 | return []byte{}, ErrNotInitialized |
| 179 | } |
| 180 | srk, err := tpm2tools.StorageRootKeyRSA(tpm.device) |
| 181 | if err != nil { |
| 182 | return []byte{}, errors.Wrap(err, "failed to load TPM SRK") |
| 183 | } |
| 184 | defer srk.Close() |
| 185 | |
| 186 | var sealedKey tpmpb.SealedBytes |
| 187 | if err := proto.Unmarshal(data, &sealedKey); err != nil { |
| 188 | return []byte{}, errors.Wrap(err, "failed to decode sealed data") |
| 189 | } |
| 190 | // Logging this for auditing purposes |
| 191 | tpm.logger.Info("Attempting to unseal data protected with PCRs", zap.Int32s("pcrs", sealedKey.Pcrs)) |
| 192 | unsealedData, err := srk.Unseal(&sealedKey) |
| 193 | if err != nil { |
| 194 | return []byte{}, errors.Wrap(err, "failed to unseal data") |
| 195 | } |
| 196 | return unsealedData, nil |
| 197 | } |