github.com/u-root/u-root@v7.0.1-0.20200915234505-ad7babab0a8e+incompatible/cmds/exp/disk_unlock/disk_unlock.go (about) 1 // Copyright 2020 the u-root Authors. All rights reserved 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 // The disk_unlock command is used to unlock a disk drive as follows: 6 // 1. Via BMC, read a 32-byte secret seed known as the Host Secret Seed (HSS) 7 // using the OpenBMC IPMI blob transfer protocol 8 // 2. Compute a password as follows: 9 // We get the deterministically computed 32-byte HDKF-SHA256 using: 10 // - salt: "SKM PROD_V2 ACCESS" 11 // - hss: 32-byte HSS 12 // - device identity: strings formed by concatenating the assembly serial 13 // number, the _ character, and the assembly part number. 14 // 3. Unlock the drive with the given password 15 // 4. Update the partition table for the disk 16 package main 17 18 import ( 19 "crypto/sha256" 20 "flag" 21 "fmt" 22 "io" 23 "log" 24 "path/filepath" 25 "strings" 26 27 "github.com/u-root/u-root/pkg/ipmi" 28 "github.com/u-root/u-root/pkg/ipmi/blobs" 29 "github.com/u-root/u-root/pkg/mount/block" 30 "github.com/u-root/u-root/pkg/mount/scuzz" 31 "golang.org/x/crypto/hkdf" 32 ) 33 34 const ( 35 hostSecretSeedLen = 32 36 37 passwordSalt = "SKM PROD_V2 ACCESS" 38 ) 39 40 var ( 41 disk = flag.String("disk", "/dev/sda", "The disk to be unlocked") 42 verbose = flag.Bool("d", false, "print debug output") 43 verboseNoSanitize = flag.Bool("dangerously-disable-sanitize", false, "Print sensitive information - this should only be used for testing!") 44 noRereadPartitions = flag.Bool("no-reread-partitions", false, "Only attempt to unlock the disk, don't re-read the partition table.") 45 ) 46 47 func verboseLog(msg string) { 48 if *verbose { 49 log.Print(msg) 50 } 51 } 52 53 // readHssBlob reads a host secret seed from the given blob id. 54 func readHssBlob(id string, h *blobs.BlobHandler) (data []uint8, rerr error) { 55 sessionID, err := h.BlobOpen(id, blobs.BMC_BLOB_OPEN_FLAG_READ) 56 if err != nil { 57 return nil, fmt.Errorf("IPMI BlobOpen for %s failed: %v", id, err) 58 } 59 defer func() { 60 // If the function returned successfully but failed to close the blob, 61 // return an error. 62 if err := h.BlobClose(sessionID); err != nil && rerr == nil { 63 rerr = fmt.Errorf("IPMI BlobClose %s failed: %v", id, err) 64 } 65 }() 66 67 data, err = h.BlobRead(sessionID, 0, hostSecretSeedLen) 68 if err != nil { 69 return nil, fmt.Errorf("IPMI BlobRead %s failed: %v", id, err) 70 } 71 72 if len(data) != hostSecretSeedLen { 73 return nil, fmt.Errorf("HSS size incorrect: got %d for %s", len(data), id) 74 } 75 76 return data, nil 77 } 78 79 // getAllHss reads all host secret seeds over IPMI. 80 func getAllHss() ([][]uint8, error) { 81 i, err := ipmi.Open(0) 82 if err != nil { 83 return nil, err 84 } 85 h := blobs.NewBlobHandler(i) 86 87 blobCount, err := h.BlobGetCount() 88 if err != nil { 89 return nil, fmt.Errorf("failed to get blob count: %v", err) 90 } 91 92 hssList := [][]uint8{} 93 skmSubstr := "/skm/hss/" 94 95 // Read from all */skm/hss/* blobs. 96 for j := 0; j < blobCount; j++ { 97 id, err := h.BlobEnumerate(j) 98 if err != nil { 99 return nil, fmt.Errorf("failed to enumerate blob %d: %v", j, err) 100 } 101 102 if !strings.Contains(id, skmSubstr) { 103 continue 104 } 105 106 hss, err := readHssBlob(id, h) 107 if err != nil { 108 log.Printf("failed to read HSS of id %s: %v", id, err) 109 } else { 110 msg := fmt.Sprintf("HSS Entry: Id=%s", id) 111 if *verboseNoSanitize { 112 msg = msg + fmt.Sprintf(",Seed=%x", hss) 113 } 114 verboseLog(msg) 115 hssList = append(hssList, hss) 116 } 117 } 118 119 return hssList, nil 120 } 121 122 // Compute the password deterministically as the 32-byte HDKF-SHA256 of the 123 // HSS plus the device identity. 124 func genPassword(hss []byte, info *scuzz.Info) ([]byte, error) { 125 hash := sha256.New 126 devID := fmt.Sprintf("%s_%s", info.Serial, info.Model) 127 128 r := hkdf.New(hash, hss, ([]byte)(passwordSalt), ([]byte)(devID)) 129 key := make([]byte, 32) 130 131 if _, err := io.ReadFull(r, key); err != nil { 132 return nil, err 133 } 134 return key, nil 135 } 136 137 func main() { 138 flag.Parse() 139 140 // Obtain 32 byte Host Secret Seed (HSS) from IPMI. 141 hssList, err := getAllHss() 142 if err != nil { 143 log.Fatalf("error getting HSS: %v", err) 144 } 145 146 if len(hssList) == 0 { 147 log.Fatalf("no HSS found - can't unlock disk.") 148 } 149 150 verboseLog(fmt.Sprintf("Found %d Host Secret Seeds.", len(hssList))) 151 152 // Open the disk. Read its identity, and use it to unlock the disk. 153 sgdisk, err := scuzz.NewSGDisk(*disk) 154 if err != nil { 155 log.Fatalf("failed to open disk %v: %v", *disk, err) 156 } 157 158 info, err := sgdisk.Identify() 159 if err != nil { 160 log.Fatalf("failed to read disk %v identity: %v", *disk, err) 161 } 162 163 verboseLog(fmt.Sprintf("Disk info for %s: %s", *disk, info.String())) 164 165 // Try using each HSS to unlock the disk - only 1 should work. 166 unlocked := false 167 for i, hss := range hssList { 168 key, err := genPassword(hss, info) 169 if err != nil { 170 log.Printf("Couldn't generate password with HSS %d: %v", i, err) 171 continue 172 } 173 174 if err := sgdisk.Unlock((string)(key), false); err != nil { 175 log.Printf("Couldn't unlock disk with HSS %d: %v", i, err) 176 } else { 177 unlocked = true 178 break 179 } 180 } 181 182 if unlocked { 183 log.Printf("Successfully unlocked disk %s.", *disk) 184 } else { 185 log.Fatalf("Failed to unlock disk %s with any HSS.", *disk) 186 } 187 188 if *noRereadPartitions { 189 return 190 } 191 192 // Update partitions on the on the disk. 193 diskdev, err := block.Device(*disk) 194 if err != nil { 195 log.Fatalf("Could not find %s: %v", *disk, err) 196 } 197 198 if err := diskdev.ReadPartitionTable(); err != nil { 199 log.Fatalf("Could not re-read partition table: %v", err) 200 } 201 202 glob := filepath.Join("/sys/class/block", diskdev.Name+"*") 203 parts, err := filepath.Glob(glob) 204 if err != nil { 205 log.Fatalf("Could not find disk partitions: %v", err) 206 } 207 208 verboseLog(fmt.Sprintf("Found these %s unlocked partitions: %v", *disk, parts)) 209 210 }