github.com/mvdan/u-root-coreutils@v0.0.0-20230122170626-c2eef2898555/pkg/hsskey/hsskey.go (about)

     1  // Copyright 2022 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  // Package hsskey provides functionality for generating a key for unlocking
     6  // drives based on the following procedure:
     7  //  1. Via BMC, read a 32-byte secret seed known as the Host Secret Seed (HSS)
     8  //     using the OpenBMC IPMI blob transfer protocol
     9  //  2. Compute a password as follows:
    10  //     We get the deterministically computed 32-byte HDKF-SHA256 using:
    11  //     - salt: "SKM PROD_V2 ACCESS" (default)
    12  //     - hss: 32-byte HSS
    13  //     - device identity: strings formed by concatenating the assembly serial
    14  //     number, the _ character, and the assembly part number.
    15  package hsskey
    16  
    17  import (
    18  	"crypto/sha256"
    19  	"fmt"
    20  	"io"
    21  	"log"
    22  	"strings"
    23  
    24  	"github.com/mvdan/u-root-coreutils/pkg/ipmi"
    25  	"github.com/mvdan/u-root-coreutils/pkg/ipmi/blobs"
    26  	"golang.org/x/crypto/hkdf"
    27  )
    28  
    29  type blobReader interface {
    30  	BlobOpen(id string, flags int16) (blobs.SessionID, error)
    31  	BlobRead(sid blobs.SessionID, offset, size uint32) ([]uint8, error)
    32  	BlobClose(sid blobs.SessionID) error
    33  }
    34  
    35  const (
    36  	hostSecretSeedLen = 32
    37  
    38  	DefaultPasswordSalt = "SKM PROD_V2 ACCESS"
    39  )
    40  
    41  // readHssBlob reads a host secret seed from the given blob id.
    42  func readHssBlob(id string, h blobReader) (data []uint8, rerr error) {
    43  	sessionID, err := h.BlobOpen(id, blobs.BMC_BLOB_OPEN_FLAG_READ)
    44  	if err != nil {
    45  		return nil, fmt.Errorf("IPMI BlobOpen for %s failed: %v", id, err)
    46  	}
    47  	defer func() {
    48  		// If the function returned successfully but failed to close the blob,
    49  		// return an error.
    50  		if err := h.BlobClose(sessionID); err != nil && rerr == nil {
    51  			rerr = fmt.Errorf("IPMI BlobClose %s failed: %v", id, err)
    52  		}
    53  	}()
    54  
    55  	data, err = h.BlobRead(sessionID, 0, hostSecretSeedLen)
    56  	if err != nil {
    57  		return nil, fmt.Errorf("IPMI BlobRead %s failed: %v", id, err)
    58  	}
    59  
    60  	if len(data) != hostSecretSeedLen {
    61  		return nil, fmt.Errorf("HSS size incorrect: got %d for %s", len(data), id)
    62  	}
    63  
    64  	return data, nil
    65  }
    66  
    67  // GetAllHss reads all host secret seeds over IPMI.
    68  func GetAllHss(verbose bool, verboseDangerous bool) ([][]uint8, error) {
    69  	i, err := ipmi.Open(0)
    70  	if err != nil {
    71  		return nil, err
    72  	}
    73  	h := blobs.NewBlobHandler(i)
    74  
    75  	blobCount, err := h.BlobGetCount()
    76  	if err != nil {
    77  		return nil, fmt.Errorf("failed to get blob count: %v", err)
    78  	}
    79  
    80  	hssList := [][]uint8{}
    81  	seen := make(map[string]bool)
    82  	skmSubstr := "/skm/hss/"
    83  
    84  	// Read from all */skm/hss/* blobs.
    85  	for j := 0; j < blobCount; j++ {
    86  		id, err := h.BlobEnumerate(j)
    87  		if err != nil {
    88  			return nil, fmt.Errorf("failed to enumerate blob %d: %v", j, err)
    89  		}
    90  
    91  		if !strings.Contains(id, skmSubstr) {
    92  			continue
    93  		}
    94  
    95  		hss, err := readHssBlob(id, h)
    96  		if err != nil {
    97  			log.Printf("failed to read HSS of id %s: %v", id, err)
    98  			continue
    99  		}
   100  
   101  		if verbose {
   102  			msg := fmt.Sprintf("HSS Entry: Id=%s", id)
   103  			if verboseDangerous {
   104  				msg = msg + fmt.Sprintf(",Seed=%x", hss)
   105  			}
   106  			log.Print(msg)
   107  		}
   108  
   109  		hssStr := fmt.Sprint(hss)
   110  		if !seen[hssStr] {
   111  			seen[hssStr] = true
   112  			hssList = append(hssList, hss)
   113  		}
   114  	}
   115  
   116  	return hssList, nil
   117  }
   118  
   119  // GenPassword computes the password deterministically as the 32-byte HDKF-SHA256 of the
   120  // HSS plus the device identity.
   121  func GenPassword(hss []byte, salt string, identifiers ...string) ([]byte, error) {
   122  	hash := sha256.New
   123  	devID := strings.Join(identifiers, "_")
   124  
   125  	r := hkdf.New(hash, hss, ([]byte)(salt), ([]byte)(devID))
   126  	key := make([]byte, 32)
   127  
   128  	if _, err := io.ReadFull(r, key); err != nil {
   129  		return nil, err
   130  	}
   131  	return key, nil
   132  }