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  }