Skip to content
Open
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ Usage:
ls [domain]
restore domain dest
dumpkeys [outputfile]
encryptkeys [inputfile] [outputfile]
apps
```

Expand All @@ -30,6 +31,8 @@ The `restore` command will restore the files in a domain into a directory tree.

The `dumpkeys` command will dump the readable portions of the keychain to json.

The `encryptkeys` command will encrypt the dumped portions of the keychain back to a property list.

The `apps` command will list the installed apps.

_Changes to the database format in recent iOS releases:_
Expand Down
149 changes: 144 additions & 5 deletions cmd/irestore/irestore.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ package main
import (
"encoding/binary"
"encoding/json"
"encoding/base64"
"time"
"reflect"

"bytes"
"fmt"
Expand Down Expand Up @@ -106,6 +108,8 @@ func parseRecord(data []byte) map[string]interface{} {
ioutil.WriteFile("failed.bin", data, 0644)
}
// must(err)
keys := make([]string, 0, len(v))
types := make([]string, 0, len(v))
for _, entry := range v {
// Time values come through as nil, so we try again with a "DateEntry" structure.
if entry.Value == nil {
Expand All @@ -117,7 +121,12 @@ func parseRecord(data []byte) map[string]interface{} {
}

rval[entry.Key] = entry.Value
keys = append(keys, entry.Key)
types = append(types, reflect.TypeOf(entry.Value).String())
}

rval["_fieldOrder"] = strings.Join(keys, ",")
rval["_fieldTypes"] = strings.Join(types, ",")
return rval
}

Expand All @@ -139,13 +148,13 @@ func dumpKeyGroup(db *backup.MobileBackup, group []KCEntry) []interface{} {
continue
}

key := aeswrap.Unwrap(ckey, wkey)
if key == nil {
aesKey := aeswrap.Unwrap(ckey, wkey)
if aesKey == nil {
fmt.Println("unwrap failed for class", class)
continue
}
// Create a gcm cipher
c, err := aes.NewCipher(key)
c, err := aes.NewCipher(aesKey)
if err != nil {
log.Panic(err)
}
Expand All @@ -155,7 +164,14 @@ func dumpKeyGroup(db *backup.MobileBackup, group []KCEntry) []interface{} {
}
plain, err := gcm.Open(nil, nil, edata, nil)
must(err)

record := parseRecord(plain)
record["_class"] = class
record["_version"] = version
record["_wkey"] = wkey
record["_length"] = l
record["_ref"] = key.Ref

rval = append(rval, record)
default:
panic(fmt.Sprintf("Unhandled keychain blob version %d", version))
Expand Down Expand Up @@ -196,6 +212,125 @@ func dumpkeys(db *backup.MobileBackup, outfile string) {
}
}

func unparseRecord(record map[string]interface{}) []byte {
var v EntrySET

keys := strings.Split(fmt.Sprint(record["_fieldOrder"]), ",")
types := strings.Split(fmt.Sprint(record["_fieldTypes"]), ",")

for index, key := range keys {
if (strings.HasPrefix(key, "_")) {
continue
}

var entry Entry
entry.Key = key

switch types[index] {
case "int64":
entry.Value = int(record[key].(float64))
case "string":
entry.Value = record[key].(string)
case "time.Time":
const formatStr = "2006-01-02T15:04:05.999999999Z"
t, _ := time.Parse(formatStr, record[key].(string))
entry.Value = t
default:
value, _ := base64.StdEncoding.DecodeString(record[key].(string))
entry.Value = value
}

v = append(v, entry)
}

entries, err := asn1.Marshal(v)
if err != nil {
fmt.Println("Error marshaling record:", err)
}

return entries
}

func encryptKeyGroup(db *backup.MobileBackup, group interface {}, class string) []KCEntry {
var rval []KCEntry

if (group == nil) {
return rval
}

for _, record := range group.([]interface{}) {
var entry KCEntry

recordObject := record.(map[string]interface{})

ckey := db.Keybag.GetClassKey(uint32(recordObject["_class"].(float64)))
wkey, _ := base64.StdEncoding.DecodeString(recordObject["_wkey"].(string))
key := aeswrap.Unwrap(ckey, wkey)

c, err := aes.NewCipher(key)
must(err)

gcm, err := gcm.NewGCM(c)
must(err)

unparsed := unparseRecord(recordObject)

nonce := []byte{}
ciphertext := gcm.Seal(nil, nonce, unparsed, nil)

data := make([]byte, 12)
le.PutUint32(data, uint32(recordObject["_version"].(float64)))
le.PutUint32(data[4:], uint32(recordObject["_class"].(float64)))
le.PutUint32(data[8:], uint32(recordObject["_length"].(float64)))
data = append(data, wkey...)
data = append(data, ciphertext...)

entry.Data = data
ref, _ := base64.StdEncoding.DecodeString(recordObject["_ref"].(string))
entry.Ref = ref

rval = append(rval, entry)
}

return rval
}

func encryptkeys(db *backup.MobileBackup, keys string, outfile string) {
jsonFile, err := os.Open(keys)
must(err)

defer jsonFile.Close()

jsonBytes, _ := ioutil.ReadAll(jsonFile)
jsonMap := make(map[string](interface{}))
json.Unmarshal([]byte(jsonBytes), &jsonMap)

path := os.ExpandEnv(outfile)
plistFile, err := os.Create(path)
must(err)

defer plistFile.Close()

emptyPlist := []byte{98, 112, 108, 105, 115, 116, 48, 48, 208, 8, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9}
_, err = plistFile.Write(emptyPlist)
must(err)

var v Keychain
err = plist.Unmarshal(plistFile, v)
must(err)

v.General = encryptKeyGroup(db, jsonMap["General"], "genp")
v.Internet = encryptKeyGroup(db, jsonMap["Internet"], "inet")
v.Certs = encryptKeyGroup(db, jsonMap["Certs"], "cert")
v.Keys = encryptKeyGroup(db, jsonMap["Keys"], "keys")

out, err := plist.Marshal(v)
must(err)

err = ioutil.WriteFile(path, out, 0644)
must(err)
}

func restore(db *backup.MobileBackup, domain string, dest string) {
var err error
var total int64
Expand Down Expand Up @@ -290,8 +425,8 @@ func main() {
ls [domain]
restore domain dest
dumpkeys [outputfile]
apps
`)
encryptkeys [inputfile] [outputfile]
apps`)
}

var cmd string
Expand Down Expand Up @@ -319,6 +454,10 @@ func main() {
out = os.Args[3]
}
dumpkeys(db, out)
case "encryptkeys":
if len(os.Args) > 4 {
encryptkeys(db, os.Args[3], os.Args[4])
}
default:
help()
}
Expand Down
13 changes: 7 additions & 6 deletions crypto/gcm/gcm.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,16 +78,17 @@ func (*gcm) Overhead() int {
}

func (g *gcm) Seal(dst, nonce, plaintext, data []byte) []byte {
if len(nonce) != gcmNonceSize {
panic("cipher: incorrect nonce length given to GCM")
}

ret, out := sliceForAppend(dst, len(plaintext)+gcmTagSize)

// See GCM spec, section 7.1.
var counter, tagMask [gcmBlockSize]byte
copy(counter[:], nonce)
counter[gcmBlockSize-1] = 1
if len(nonce) != gcmNonceSize {
// counter is all zeros for apple's blank iv
// The python code seems to do the hash inside g.auth for non-12 byte IVs
} else {
copy(counter[:], nonce)
counter[gcmBlockSize-1] = 1
}

g.cipher.Encrypt(tagMask[:], counter[:])
gcmInc32(&counter)
Expand Down
2 changes: 1 addition & 1 deletion encoding/asn1/asn1.go
Original file line number Diff line number Diff line change
Expand Up @@ -342,7 +342,7 @@ func parseUTCTime(bytes []byte) (ret time.Time, err error) {
// parseGeneralizedTime parses the GeneralizedTime from the given byte slice
// and returns the resulting time.
func parseGeneralizedTime(bytes []byte) (ret time.Time, err error) {
const formatStr = "20060102150405Z0700"
const formatStr = "20060102150405.999999999Z0700"
s := string(bytes)

if ret, err = time.Parse(formatStr, s); err != nil {
Expand Down
4 changes: 4 additions & 0 deletions encoding/asn1/asn1_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,10 @@ func TestUTCTime(t *testing.T) {
var generalizedTimeTestData = []timeTest{
{"20100102030405Z", true, time.Date(2010, 01, 02, 03, 04, 05, 0, time.UTC)},
{"20100102030405", false, time.Time{}},
{"20100102030405.123456Z", true, time.Date(2010, 01, 02, 03, 04, 05, 123456e3, time.UTC)},
{"20100102030405.123456", false, time.Time{}},
{"20100102030405.Z", false, time.Time{}},
{"20100102030405.", false, time.Time{}},
{"20100102030405+0607", true, time.Date(2010, 01, 02, 03, 04, 05, 0, time.FixedZone("", 6*60*60+7*60))},
{"20100102030405-0607", true, time.Date(2010, 01, 02, 03, 04, 05, 0, time.FixedZone("", -6*60*60-7*60))},
/* These are invalid times. However, the time package normalises times
Expand Down
25 changes: 19 additions & 6 deletions encoding/asn1/marshal.go
Original file line number Diff line number Diff line change
Expand Up @@ -370,6 +370,14 @@ func marshalTimeCommon(out *forkableWriter, t time.Time) (err error) {
return
}

if t.Nanosecond() > 0 {
out.WriteByte('.')
date := fmt.Sprintf("%s", t)
for _, digit := range date[20:len(date)-10] {
out.WriteByte(byte(digit))
}
}

_, offset := t.Zone()

switch {
Expand Down Expand Up @@ -414,11 +422,11 @@ func marshalBody(out *forkableWriter, value reflect.Value, params fieldParameter
return nil
case timeType:
t := value.Interface().(time.Time)
if params.timeType == TagGeneralizedTime || outsideUTCRange(t) {
// if params.timeType == TagGeneralizedTime || outsideUTCRange(t) {
return marshalGeneralizedTime(out, t)
} else {
return marshalUTCTime(out, t)
}
// } else {
// return marshalUTCTime(out, t)
// }
case bitStringType:
return marshalBitString(out, value.Interface().(BitString))
case objectIdentifierType:
Expand Down Expand Up @@ -584,9 +592,9 @@ func marshalField(out *forkableWriter, v reflect.Value, params fieldParameters)
tag = params.stringType
}
case TagUTCTime:
if params.timeType == TagGeneralizedTime || outsideUTCRange(v.Interface().(time.Time)) {
// if params.timeType == TagGeneralizedTime || outsideUTCRange(v.Interface().(time.Time)) {
tag = TagGeneralizedTime
}
// }
}

if params.set {
Expand Down Expand Up @@ -616,6 +624,11 @@ func marshalField(out *forkableWriter, v reflect.Value, params fieldParameters)
class = ClassContextSpecific
}

// Replace TagPrintableString with TagUTF8String tags for idempotency
if (tag == 19) {
tag = 12
}

err = marshalTagAndLength(tags, tagAndLength{class, tag, bodyLen, isCompound})
if err != nil {
return
Expand Down
18 changes: 9 additions & 9 deletions encoding/asn1/marshal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,24 +102,24 @@ var marshalTests = []marshalTest{
{explicitTagTest{64}, "3005a503020140"},
{flagTest{true}, "30028000"},
{flagTest{false}, "3000"},
{time.Unix(0, 0).UTC(), "170d3730303130313030303030305a"},
{time.Unix(1258325776, 0).UTC(), "170d3039313131353232353631365a"},
{time.Unix(1258325776, 0).In(PST), "17113039313131353134353631362d30383030"},
{time.Unix(0, 0).UTC(), "180f31393730303130313030303030305a"},
{time.Unix(1258325776, 0).UTC(), "180f32303039313131353232353631365a"},
{time.Unix(1258325776, 0).In(PST), "181332303039313131353134353631362d30383030"},
{farFuture(), "180f32313030303430353132303130315a"},
{generalizedTimeTest{time.Unix(1258325776, 0).UTC()}, "3011180f32303039313131353232353631365a"},
{BitString{[]byte{0x80}, 1}, "03020780"},
{BitString{[]byte{0x81, 0xf0}, 12}, "03030481f0"},
{ObjectIdentifier([]int{1, 2, 3, 4}), "06032a0304"},
{ObjectIdentifier([]int{1, 2, 840, 133549, 1, 1, 5}), "06092a864888932d010105"},
{ObjectIdentifier([]int{2, 100, 3}), "0603813403"},
{"test", "130474657374"},
{"test", "0c0474657374"},
{
"" +
"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" +
"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" +
"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" +
"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", // This is 127 times 'x'
"137f" +
"0c7f" +
"7878787878787878787878787878787878787878787878787878787878787878" +
"7878787878787878787878787878787878787878787878787878787878787878" +
"7878787878787878787878787878787878787878787878787878787878787878" +
Expand All @@ -131,22 +131,22 @@ var marshalTests = []marshalTest{
"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" +
"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" +
"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", // This is 128 times 'x'
"138180" +
"0c8180" +
"7878787878787878787878787878787878787878787878787878787878787878" +
"7878787878787878787878787878787878787878787878787878787878787878" +
"7878787878787878787878787878787878787878787878787878787878787878" +
"7878787878787878787878787878787878787878787878787878787878787878",
},
{ia5StringTest{"test"}, "3006160474657374"},
{optionalRawValueTest{}, "3000"},
{printableStringTest{"test"}, "3006130474657374"},
{printableStringTest{"test*"}, "30071305746573742a"},
{printableStringTest{"test"}, "30060c0474657374"},
{printableStringTest{"test*"}, "30070c05746573742a"},
{rawContentsStruct{nil, 64}, "3003020140"},
{rawContentsStruct{[]byte{0x30, 3, 1, 2, 3}, 64}, "3003010203"},
{RawValue{Tag: 1, Class: 2, IsCompound: false, Bytes: []byte{1, 2, 3}}, "8103010203"},
{testSET([]int{10}), "310302010a"},
{omitEmptyTest{[]string{}}, "3000"},
{omitEmptyTest{[]string{"1"}}, "30053003130131"},
{omitEmptyTest{[]string{"1"}}, "300530030c0131"},
{"Σ", "0c02cea3"},
{defaultTest{0}, "3003020100"},
{defaultTest{1}, "3000"},
Expand Down