github.com/insomniacslk/u-root@v0.0.0-20200717035308-96b791510d76/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("v", false, "print verbose output") 43 verboseNoSanitize = flag.Bool("dangerous-disable-sanitize", false, "Print sensitive information - this should only be used for testing!") 44 ) 45 46 func verboseLog(msg string) { 47 if *verbose { 48 log.Print(msg) 49 } 50 } 51 52 // readHssBlob reads a host secret seed from the given blob id. 53 func readHssBlob(id string, h *blobs.BlobHandler) (data []uint8, rerr error) { 54 sessionID, err := h.BlobOpen(id, blobs.BMC_BLOB_OPEN_FLAG_READ) 55 if err != nil { 56 return nil, fmt.Errorf("IPMI BlobOpen for %s failed: %v", id, err) 57 } 58 defer func() { 59 // If the function returned successfully but failed to close the blob, 60 // return an error. 61 if err := h.BlobClose(sessionID); err != nil && rerr == nil { 62 rerr = fmt.Errorf("IPMI BlobClose %s failed: %v", id, err) 63 } 64 }() 65 66 data, err = h.BlobRead(sessionID, 0, hostSecretSeedLen) 67 if err != nil { 68 return nil, fmt.Errorf("IPMI BlobRead %s failed: %v", id, err) 69 } 70 71 if len(data) != hostSecretSeedLen { 72 return nil, fmt.Errorf("HSS size incorrect: got %d for %s", len(data), id) 73 } 74 75 return data, nil 76 } 77 78 // getAllHss reads all host secret seeds over IPMI. 79 func getAllHss() ([][]uint8, error) { 80 i, err := ipmi.Open(0) 81 if err != nil { 82 return nil, err 83 } 84 h := blobs.NewBlobHandler(i) 85 86 blobCount, err := h.BlobGetCount() 87 if err != nil { 88 return nil, fmt.Errorf("failed to get blob count: %v", err) 89 } 90 91 hssList := [][]uint8{} 92 skmPrefix := "/skm/hss/" 93 94 // Read from all /skm/hss/* blobs. 95 for j := 0; j < blobCount; j++ { 96 id, err := h.BlobEnumerate(j) 97 if err != nil { 98 return nil, err 99 } 100 101 if !strings.HasPrefix(id, skmPrefix) { 102 continue 103 } 104 105 hss, err := readHssBlob(id, h) 106 if err != nil { 107 log.Printf("failed to read HSS of id %s: %v", id, err) 108 } else { 109 msg := fmt.Sprintf("HSS Entry: Index=%s", strings.TrimPrefix(id, skmPrefix)) 110 if *verboseNoSanitize { 111 msg = msg + fmt.Sprintf(", Seed=%s", hss) 112 } 113 verboseLog(msg) 114 hssList = append(hssList, hss) 115 } 116 } 117 118 return hssList, nil 119 } 120 121 // Compute the password deterministically as the 32-byte HDKF-SHA256 of the 122 // HSS plus the device identity. 123 func genPassword(hss []byte, info *scuzz.Info) ([]byte, error) { 124 hash := sha256.New 125 devID := fmt.Sprintf("%s_%s", info.Serial, info.Model) 126 127 r := hkdf.New(hash, hss, ([]byte)(passwordSalt), ([]byte)(devID)) 128 key := make([]byte, 32) 129 130 if _, err := io.ReadFull(r, key); err != nil { 131 return nil, err 132 } 133 return key, nil 134 } 135 136 func main() { 137 // Obtain 32 byte Host Secret Seed (HSS) from IPMI. 138 hssList, err := getAllHss() 139 if err != nil { 140 log.Fatalf("error getting HSS: %v", err) 141 } 142 143 if len(hssList) == 0 { 144 log.Fatalf("no HSS found - can't unlock disk.") 145 } 146 147 verboseLog(fmt.Sprintf("Found %d Host Secret Seeds.", len(hssList))) 148 149 // Open the disk. Read its identity, and use it to unlock the disk. 150 sgdisk, err := scuzz.NewSGDisk(*disk) 151 if err != nil { 152 log.Fatalf("failed to open disk %v: %v", *disk, err) 153 } 154 155 info, err := sgdisk.Identify() 156 if err != nil { 157 log.Fatalf("failed to read disk %v identity: %v", *disk, err) 158 } 159 160 verboseLog(fmt.Sprintf("Disk info for %s: %s", *disk, info.String())) 161 162 // Try using each HSS to unlock the disk - only 1 should work. 163 unlocked := false 164 for i, hss := range hssList { 165 key, err := genPassword(hss, info) 166 if err != nil { 167 log.Printf("Couldn't generate password with HSS %d: %v", i, err) 168 continue 169 } 170 171 if err := sgdisk.Unlock((string)(key), false); err != nil { 172 log.Printf("Couldn't unlock disk with HSS %d: %v", i, err) 173 } else { 174 unlocked = true 175 break 176 } 177 } 178 179 if unlocked { 180 log.Printf("Successfully unlocked disk %s.", *disk) 181 } else { 182 log.Fatalf("Failed to unlock disk %s with any HSS.", *disk) 183 } 184 185 // Update partitions on the on the disk. 186 diskdev, err := block.Device(*disk) 187 if err != nil { 188 log.Fatalf("Could not find %s: %v", *disk, err) 189 } 190 191 if err := diskdev.ReadPartitionTable(); err != nil { 192 log.Fatalf("Could not re-read partition table: %v", err) 193 } 194 195 glob := filepath.Join("/sys/class/block", diskdev.Name+"*") 196 parts, err := filepath.Glob(glob) 197 if err != nil { 198 log.Fatalf("Could not find disk partitions: %v", err) 199 } 200 201 verboseLog(fmt.Sprintf("Found these %s unlocked partitions: %v", *disk, parts)) 202 203 }