code.pfad.fr/gohmekit@v0.2.1/pairing/verify_client_controller.go (about)

     1  package pairing
     2  
     3  import (
     4  	"crypto/ed25519"
     5  	"fmt"
     6  	"io"
     7  
     8  	"code.pfad.fr/gohmekit/pairing/crypto"
     9  	"code.pfad.fr/gohmekit/tlv8"
    10  )
    11  
    12  // NewVerifyClientController implements the client logic for the pairing-verify step.
    13  func NewVerifyClientController(client Device, database Database) (*VerifyClientController, error) {
    14  	key, err := crypto.NewKeyOnCurve25519()
    15  	if err != nil {
    16  		return nil, err
    17  	}
    18  	return &VerifyClientController{
    19  		key:      key,
    20  		client:   client,
    21  		database: database,
    22  	}, nil
    23  }
    24  
    25  // VerifyClientController implements the client logic for the pairing-verify step.
    26  type VerifyClientController struct {
    27  	key      crypto.KeyOnCurve25519
    28  	client   Device
    29  	database Database
    30  }
    31  
    32  // StartRequest is the initial pairing-verify request.
    33  func (c VerifyClientController) StartRequest() []byte {
    34  	buf, err := tlv8.Marshal(struct {
    35  		ktlvState
    36  		PublicKey []byte `tlv8:"kTLVType_PublicKey"`
    37  	}{
    38  		ktlvState: ktlvState{1},
    39  		PublicKey: c.key.PublicKey(),
    40  	})
    41  	if err != nil {
    42  		panic(err)
    43  	}
    44  	return buf
    45  }
    46  
    47  func paddedNonce(s string) []byte {
    48  	n := make([]byte, 12)
    49  	copy(n[4:], s)
    50  	return n
    51  }
    52  
    53  // FinishRequest checks the accessory initial response and generate the finish request.
    54  func (c VerifyClientController) FinishRequest(r io.Reader) (response []byte, sharedSecret []byte, err error) {
    55  	var data struct {
    56  		ktlvState
    57  		PublicKey     []byte `tlv8:"kTLVType_PublicKey"`
    58  		EncryptedData []byte `tlv8:"kTLVType_EncryptedData"`
    59  	}
    60  	err = tlv8.NewDecoder(r).Decode(&data)
    61  	if err != nil {
    62  		return nil, nil, err
    63  	}
    64  
    65  	if data.State != 2 {
    66  		return nil, nil, fmt.Errorf("unexpected state: %d", data.State)
    67  	}
    68  
    69  	accessoryPublicKey := data.PublicKey
    70  	if len(accessoryPublicKey) != 32 {
    71  		return nil, nil, fmt.Errorf("unexpected accessory key lenth: %d", len(accessoryPublicKey))
    72  	}
    73  	sharedSecret, err = c.key.PairVerifySharedSecret(accessoryPublicKey)
    74  	if err != nil {
    75  		return nil, nil, fmt.Errorf("could not compute sharedSecret: %w", err)
    76  	}
    77  
    78  	aead, err := crypto.PairVerifyAEAD(sharedSecret)
    79  	if err != nil {
    80  		return nil, nil, err
    81  	}
    82  
    83  	{
    84  		encryptedData := data.EncryptedData
    85  		decryptedData, err := aead.Open(encryptedData[:0], paddedNonce("PV-Msg02"), encryptedData, nil)
    86  		if err != nil {
    87  			return nil, nil, fmt.Errorf("could not verify data: %w", err)
    88  		}
    89  
    90  		var decodedData struct {
    91  			Identifier []byte `tlv8:"kTLVType_Identifier"`
    92  			Signature  []byte `tlv8:"kTLVType_Signature"`
    93  		}
    94  		err = tlv8.Unmarshal(decryptedData, &decodedData)
    95  		if err != nil {
    96  			return nil, nil, fmt.Errorf("could not decode encrypted data: %w", err)
    97  		}
    98  		accessoryPairingID := decodedData.Identifier
    99  		accessorySignature := decodedData.Signature
   100  
   101  		accessoryLTPK, err := c.database.GetLongTermPublicKey(accessoryPairingID)
   102  		if err != nil {
   103  			return nil, nil, err
   104  		}
   105  
   106  		var accessoryInfo []byte
   107  		accessoryInfo = append(accessoryInfo, accessoryPublicKey...)
   108  		accessoryInfo = append(accessoryInfo, accessoryPairingID...)
   109  		accessoryInfo = append(accessoryInfo, c.key.PublicKey()...)
   110  		if !ed25519.Verify(accessoryLTPK, accessoryInfo, accessorySignature) {
   111  			return nil, nil, fmt.Errorf("could not validate signature of AccessoryInfo")
   112  		}
   113  	}
   114  
   115  	{
   116  		// construct response
   117  		var iOSDeviceInfo []byte
   118  		iOSDeviceInfo = append(iOSDeviceInfo, c.key.PublicKey()...)
   119  		iOSDeviceInfo = append(iOSDeviceInfo, c.client.PairingID()...)
   120  		iOSDeviceInfo = append(iOSDeviceInfo, accessoryPublicKey...)
   121  
   122  		iOSDeviceSignature, err := c.client.Ed25519Sign(iOSDeviceInfo)
   123  		if err != nil {
   124  			return nil, nil, fmt.Errorf("could not sign iOSDeviceInfo: %w", err)
   125  		}
   126  
   127  		cleartextData, err := tlv8.Marshal(struct {
   128  			Identifier []byte `tlv8:"kTLVType_Identifier"`
   129  			Signature  []byte `tlv8:"kTLVType_Signature"`
   130  		}{
   131  			Identifier: c.client.PairingID(),
   132  			Signature:  iOSDeviceSignature,
   133  		})
   134  		if err != nil {
   135  			return nil, nil, err
   136  		}
   137  
   138  		encryptedData := aead.Seal(cleartextData[:0], paddedNonce("PV-Msg03"), cleartextData, nil)
   139  
   140  		response, err = tlv8.Marshal(struct {
   141  			ktlvState
   142  			EncryptedData []byte `tlv8:"kTLVType_EncryptedData"`
   143  		}{
   144  			ktlvState:     ktlvState{3},
   145  			EncryptedData: encryptedData,
   146  		})
   147  		return response, sharedSecret, err
   148  	}
   149  }
   150  
   151  // FinishResponse checks the response of the accessory. From now on, the connection must be encrypted
   152  // using the sharedSecret computed in the FinishRequest step.
   153  func (c VerifyClientController) FinishResponse(r io.Reader) error {
   154  	var data struct {
   155  		ktlvState
   156  		Error byte `tlv8:"kTLVType_Error"`
   157  	}
   158  	err := tlv8.NewDecoder(r).Decode(&data)
   159  	if err != nil {
   160  		return err
   161  	}
   162  
   163  	if data.State != 4 {
   164  		return fmt.Errorf("unexpected state: %d", data.State)
   165  	}
   166  	if data.Error != 0 {
   167  		return fmt.Errorf("unexpected error: %d", data.Error)
   168  	}
   169  	return nil
   170  }