add activation lock bypass code creation (#677)

add activation lock bypass code creation

https://developer.apple.com/documentation/devicemanagement/device_assignment/activation_lock_a_device/creating_and_using_bypass_codes
This commit is contained in:
Victor Vrantchan
2020-06-21 13:28:32 -04:00
committed by Victor Vrantchan
parent 2b6639093f
commit 11d8321192
4 changed files with 193 additions and 0 deletions

1
.gitignore vendored
View File

@@ -10,3 +10,4 @@ cmd/micromdm/micromdm
tools/ngrok/config_root
tools/ngrok/filerepo
tools/ngrok/env
mdm-files/

34
dep/activation_lock.go Normal file
View File

@@ -0,0 +1,34 @@
package dep
import "github.com/pkg/errors"
const (
activationLockPath = "device/activationlock"
)
type ActivationLockRequest struct {
Device string `json:"device"`
// If the escrow key is not provided, the device will be locked to the person who created the MDM server in the portal.
// https://developer.apple.com/documentation/devicemanagement/device_assignment/activation_lock_a_device/creating_and_using_bypass_codes
// The EscrowKey is a hex-encoded PBKDF2 derivation of the bypass code. See activationlock.BypassCode.
EscrowKey string `json:"escrow_key"`
LostMessage string `json:"lost_message"`
}
type ActivationLockResponse struct {
SerialNumber string `json:"serial_number"`
Status string `json:"response_status"`
}
func (c *Client) ActivationLock(alr ActivationLockRequest) (*ActivationLockResponse, error) {
req, err := c.newRequest("POST", activationLockPath, &alr)
if err != nil {
return nil, errors.Wrap(err, "create activation lock request")
}
var response ActivationLockResponse
err = c.do(req, &response)
return &response, errors.Wrap(err, "activation lock")
}

View File

@@ -0,0 +1,107 @@
// Package activationlock supports Apple device activation locking and unlocking.
// https://developer.apple.com/documentation/devicemanagement/activation_lock_a_device
package activationlock
import (
"crypto/rand"
"crypto/sha256"
"encoding/hex"
"fmt"
"strings"
"golang.org/x/crypto/pbkdf2"
)
// The available character set for a bypass code string.
const charset = "0123456789ACDEFGHJKLMNPQRTUVWXYZ"
var (
// Positions to insert "-" in the bypass code string.
dashPositions = []int{5, 10, 14, 18, 22}
// The salt for the hash is always the same.
salt = []uint8{0, 0, 0, 0}
)
// BypassCode is the ActivationLock code.
type BypassCode struct {
Key [16]byte
format string
}
// Create generates a usable bypass code.
func Create(key []byte) (BypassCode, error) {
var code BypassCode
if key == nil {
key = make([]byte, 16)
if _, err := rand.Read(key); err != nil {
return code, err
}
}
copy(code.Key[:], key)
// Format human readable version of key.
values, err := convertBits(key, 8, 5)
if err != nil {
return code, err
}
dashIdx := 0
var str strings.Builder
for i, p := range values {
if dashIdx < len(dashPositions) && i == dashPositions[dashIdx] {
str.WriteString("-")
dashIdx++
}
str.WriteByte(charset[p])
}
code.format = str.String()
return code, nil
}
// Hash returns a PBKKDF2 derived hash of the bypass code.
// The hex encoded string is sent to Apple to lock a device.
func (c BypassCode) Hash() string {
return hex.EncodeToString(pbkdf2.Key(c.Key[:], salt, 50000, sha256.Size, sha256.New))
}
// String returns the bypass code in human readable format.
func (c BypassCode) String() string {
return c.format
}
// convert binary data from one bits-per-byte arrangement to another.
// Ex: re-arrange 8 bit bytes to groups of 5 when converting to base32.
// This is a modified helper from a Go implementation of the bech32 format.
// https://github.com/FiloSottile/age/blob/c9a35c072716b5ac6cd815366999c9e189b0c317/internal/bech32/bech32.go#L79-L105
func convertBits(data []byte, frombits, tobits byte) ([]byte, error) {
var ret []byte
acc := uint32(0)
bits := byte(0)
maxv := byte(1<<tobits - 1)
for idx, value := range data {
if value>>frombits != 0 {
return nil, fmt.Errorf("invalid data range: data[%d]=%d (frombits=%d)", idx, value, frombits)
}
acc = acc<<frombits | uint32(value)
bits += frombits
for bits >= tobits {
bits -= tobits
ret = append(ret, byte(acc>>bits)&maxv)
}
}
// zero out most significant bits of the last value, until we get to remaining bits
if bits > 0 {
for bit := frombits; bit >= bits; bit-- {
acc = acc &^ (1 << bit)
}
ret = append(ret, byte(acc)&maxv)
}
return ret, nil
}

View File

@@ -0,0 +1,51 @@
package activationlock
import (
"encoding/hex"
"testing"
)
func TestBypassCode(t *testing.T) {
tests := []struct {
key string
humanReadable string
hash string
}{
{
key: "1ea841db5edfafe6075b5ae0d845d254",
humanReadable: "3UM43-PUYVY-QYD1-UVCC-HEHJ-FKA4",
hash: "6ab40d5eabe7218ec04182f461005600c7e3426bddd82cdb405bde9a1e0014b5",
},
{
key: "44ebe63375969fec2da67e87e7317946",
humanReadable: "8LNYD-DVNKU-GYRC-E6GU-3YFD-CT86",
hash: "c1968cb4c013ea893f1922bb5c39f81e35012c0bd9ce3c01cc2a05873a2499e6",
},
{
key: "cb84798c3ca85a674194550a2e96aed8",
humanReadable: "TF27L-31WN1-E6FH-DMAM-52X5-NFV0",
hash: "23cf8b7873425fd8efe31dc5b6ab9c357eb98a2a59c82ea1084ca8af58cc480a",
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.key, func(t *testing.T) {
t.Parallel()
key, _ := hex.DecodeString(tt.key)
code, err := Create(key)
if err != nil {
t.Fatal(err)
}
if got, want := code.Hash(), tt.hash; got != want {
t.Errorf("Hash(): got %q, want %q", got, want)
}
if got, want := code.String(), tt.humanReadable; got != want {
t.Errorf("Human Readable String: got %q, want %q", got, want)
}
})
}
}