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  }