mirror of
https://github.com/micromdm/micromdm/
synced 2026-08-06 01:06:26 +08:00
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:
committed by
Victor Vrantchan
parent
2b6639093f
commit
11d8321192
1
.gitignore
vendored
1
.gitignore
vendored
@@ -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
34
dep/activation_lock.go
Normal 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")
|
||||
}
|
||||
107
pkg/activationlock/activationlock.go
Normal file
107
pkg/activationlock/activationlock.go
Normal 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
|
||||
}
|
||||
51
pkg/activationlock/activationlock_test.go
Normal file
51
pkg/activationlock/activationlock_test.go
Normal 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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user