Skip to content

Commit

Permalink
Add device pairing with Trust Pop Up (#27)
Browse files Browse the repository at this point in the history
* copy over code from go-ios-old

* fix buid package

* fix pair and pair_save, add close method

* pairing works now

* fix console output

* delete pair_read.go

* add formatting to log.Info,  add comment to pair function, fix formatting directive
  • Loading branch information
danielpaulus authored Apr 18, 2021
1 parent 992c060 commit a805086
Show file tree
Hide file tree
Showing 6 changed files with 424 additions and 8 deletions.
49 changes: 49 additions & 0 deletions ios/buid.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package ios

import (
"bytes"

plist "howett.net/plist"
)

type readBuid struct {
BundleID string
ClientVersionString string
MessageType string
ProgName string
LibUSBMuxVersion uint32 `plist:"kLibUSBMuxVersion"`
}

type readBuidResponse struct {
BUID string
}

func newReadBuid() *readBuid {
data := &readBuid{
BundleID: "go.ios.control",
ClientVersionString: "go-usbmux-0.0.1",
MessageType: "ReadBUID",
ProgName: "go-usbmux",
LibUSBMuxVersion: 3,
}
return data
}

func readBuidResponsefromBytes(plistBytes []byte) readBuidResponse {
decoder := plist.NewDecoder(bytes.NewReader(plistBytes))
var data readBuidResponse
_ = decoder.Decode(&data)
return data
}

//ReadBuid requests the BUID of the host
//It returns the deserialized BUID as a string.
func (muxConn *UsbMuxConnection) ReadBuid() (string, error) {
muxConn.Send(newReadBuid())
resp, err := muxConn.ReadMessage()
if err != nil {
return "", err
}
buidResponse := readBuidResponsefromBytes(resp.Payload)
return buidResponse.BUID, nil
}
159 changes: 159 additions & 0 deletions ios/crypto_utils.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
package ios

import (
"crypto/rand"
"crypto/rsa"
"crypto/sha1"
"crypto/x509"
"crypto/x509/pkix"
"encoding/asn1"
"encoding/pem"
"errors"
"fmt"
"math/big"
"regexp"
"strings"
"time"
)

//This code could be a little nicer
func createRootCertificate(publicKeyBytes []byte) ([]byte, []byte, []byte, []byte, []byte, error) {
reader := rand.Reader
bitSize := 2048

rootKeyPair, err := rsa.GenerateKey(reader, bitSize)
if err != nil {
return nil, nil, nil, nil, nil, err
}
var b big.Int
b.SetInt64(0)
rootCertTemplate := x509.Certificate{
SerialNumber: &b,
Subject: pkix.Name{},
NotBefore: time.Now(),
NotAfter: time.Now().AddDate(10, 0, 0),
SignatureAlgorithm: x509.SHA1WithRSA,
BasicConstraintsValid: true,
IsCA: true,
}

digestString, _ := computeSKIKey(&rootKeyPair.PublicKey)

rootCertTemplate.ExtraExtensions = []pkix.Extension{
{
Id: []int{2, 5, 29, 14},
Value: []byte(digestString),
}}

rootCert, err := x509.CreateCertificate(rand.Reader, &rootCertTemplate, &rootCertTemplate, &rootKeyPair.PublicKey, rootKeyPair)
if err != nil {
return nil, nil, nil, nil, nil, err
}

hostCertTemplate := x509.Certificate{
SerialNumber: &b,
Subject: pkix.Name{},
NotBefore: time.Now(),
NotAfter: time.Now().AddDate(10, 0, 0),
SignatureAlgorithm: x509.SHA1WithRSA,
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
//ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
BasicConstraintsValid: true,
IsCA: false,
}

hostCertTemplate.ExtraExtensions = []pkix.Extension{
{
Id: []int{2, 5, 29, 14},
Value: (digestString),
}}
block, _ := pem.Decode([]byte(publicKeyBytes))

if block == nil {
return nil, nil, nil, nil, nil, errors.New("failed to parse PEM block containing the public key")
}

var devicePublicKey rsa.PublicKey
_, err1 := asn1.Unmarshal(block.Bytes, &devicePublicKey)
if err1 != nil {
return nil, nil, nil, nil, nil, err1
}

deviceCertTemplate := x509.Certificate{
SerialNumber: &b,
Subject: pkix.Name{},
NotBefore: time.Now(),
NotAfter: time.Now().AddDate(10, 0, 0),
SignatureAlgorithm: x509.SHA1WithRSA,
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
BasicConstraintsValid: true,
IsCA: false,
}
digestString2, _ := computeSKIKey(&devicePublicKey)
deviceCertTemplate.ExtraExtensions = []pkix.Extension{
{
Id: []int{2, 5, 29, 14},
Value: (digestString2),
}}

hostCert, err := x509.CreateCertificate(rand.Reader, &hostCertTemplate, &hostCertTemplate, &rootKeyPair.PublicKey, rootKeyPair)
if err != nil {
return nil, nil, nil, nil, nil, err
}

deviceCert, err := x509.CreateCertificate(rand.Reader, &deviceCertTemplate, &deviceCertTemplate, &devicePublicKey, rootKeyPair)
if err != nil {
return nil, nil, nil, nil, nil, err
}
return certBytesToPEM(rootCert), certBytesToPEM(hostCert), certBytesToPEM(deviceCert), savePEMKey(rootKeyPair), savePEMKey(rootKeyPair), nil

}

func computeSKIKey(pub *rsa.PublicKey) ([]byte, error) {

encodedPub, err := x509.MarshalPKIXPublicKey(pub)
if err != nil {
return nil, err
}

var subPKI subjectPublicKeyInfo
_, err = asn1.Unmarshal(encodedPub, &subPKI)
if err != nil {
return nil, err
}

pubHash := sha1.Sum(subPKI.SubjectPublicKey.Bytes)

digestString := toHexString(pubHash[:])

return []byte(digestString), nil
}

func toHexString(bytes []byte) string {
digestString := fmt.Sprintf("%x", bytes)
if len(digestString)%2 == 1 {
digestString = "0" + digestString
}
re := regexp.MustCompile("..")
digestString = strings.TrimRight(re.ReplaceAllString(digestString, "$0:"), ":")
digestString = strings.ToUpper(digestString)
return digestString
}

type subjectPublicKeyInfo struct {
Algorithm pkix.AlgorithmIdentifier
SubjectPublicKey asn1.BitString
}

func certBytesToPEM(certBytes []byte) []byte {
pemCert := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certBytes})
return pemCert
}

func savePEMKey(key *rsa.PrivateKey) []byte {
var privateKey = &pem.Block{
Type: "PRIVATE KEY",
Bytes: x509.MarshalPKCS1PrivateKey(key),
}
return pem.EncodeToMemory(privateKey)
}
132 changes: 132 additions & 0 deletions ios/pair.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
package ios

import (
"bytes"
"errors"
"fmt"
"strings"

"github.com/google/uuid"
plist "howett.net/plist"
)

//Pair tries to pair with a device. The first time usually
//fails because the user has to accept a trust pop up on the iOS device.
// What you have to do to pair is:
// 1. run the Pair() function
// 2. accept the trust pop up on the device
// 3. run the Pair() function a second time
func Pair(device DeviceEntry) error {
usbmuxConn, err := NewUsbMuxConnectionSimple()
if err != nil {
return err
}
defer usbmuxConn.Close()
buid, err := usbmuxConn.ReadBuid()
if err != nil {
return err
}
lockdown, err := usbmuxConn.ConnectLockdown(device.DeviceID)
if err != nil {
return err
}
publicKey, err := lockdown.GetValue("DevicePublicKey")
if err != nil {
return err
}
wifiMac, err := lockdown.GetValue("WiFiAddress")
if err != nil {
return err
}
rootCert, hostCert, deviceCert, rootPrivateKey, hostPrivateKey, err := createRootCertificate(publicKey.([]byte))
if err != nil {
return fmt.Errorf("Failed creating pair record with error: %v", err)
}

pairRecordData := newFullPairRecordData(buid, hostCert, rootCert, deviceCert)
request := newLockDownPairRequest(pairRecordData)

err = lockdown.Send(request)
if err != nil {
return err
}
resp, err := lockdown.ReadMessage()
if err != nil {
return err
}
response := getLockdownPairResponsefromBytes(resp)
if isPairingDialogOpen(response) {
return fmt.Errorf("Please accept the PairingDialog on the device and run pairing again!")
}
if response.Error != "" {
return fmt.Errorf("Lockdown error: %s", response.Error)
}
usbmuxConn, err = NewUsbMuxConnectionSimple()
defer usbmuxConn.Close()
if err != nil {
return err
}
success, err := usbmuxConn.savePair(device.Properties.SerialNumber, deviceCert, hostPrivateKey, hostCert, rootPrivateKey, rootCert, response.EscrowBag, wifiMac.(string), pairRecordData.HostID, buid)
if !success || err != nil {
return errors.New("Saving the PairRecord to usbmux failed")
}
return nil
}

type FullPairRecordData struct {
DeviceCertificate []byte
HostCertificate []byte
RootCertificate []byte
SystemBUID string
HostID string
}

type PairingOptions struct {
ExtendedPairingErrors bool
}

type LockDownPairRequest struct {
Label string
PairRecord FullPairRecordData
Request string
ProtocolVersion string
PairingOptions PairingOptions
}

type LockdownPairResponse struct {
Error string
Request string
EscrowBag []byte
}

func getLockdownPairResponsefromBytes(plistBytes []byte) *LockdownPairResponse {
decoder := plist.NewDecoder(bytes.NewReader(plistBytes))
var data LockdownPairResponse
_ = decoder.Decode(&data)
return &data
}

func isPairingDialogOpen(resp *LockdownPairResponse) bool {
return resp.Error == "PairingDialogResponsePending"
}

func newLockDownPairRequest(pairRecord FullPairRecordData) LockDownPairRequest {
var req LockDownPairRequest
req.Label = "go-ios"
req.PairingOptions = PairingOptions{true}
req.Request = "Pair"
req.ProtocolVersion = "2"
req.PairRecord = (pairRecord)
return req
}

func newFullPairRecordData(systemBuid string, hostCert []byte, rootCert []byte, deviceCert []byte) FullPairRecordData {
var data FullPairRecordData
data.SystemBUID = systemBuid
data.HostID = strings.ToUpper(uuid.New().String())
data.RootCertificate = rootCert
data.HostCertificate = hostCert
data.DeviceCertificate = deviceCert

return data
}
Loading

0 comments on commit a805086

Please sign in to comment.