com.github.horrorho.inflatabledonkey.cloud.escrow.EscrowedKeys.java Source code

Java tutorial

Introduction

Here is the source code for com.github.horrorho.inflatabledonkey.cloud.escrow.EscrowedKeys.java

Source

/*
 * The MIT License
 *
 * Copyright 2016 Ahseya.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
package com.github.horrorho.inflatabledonkey.cloud.escrow;

import com.dd.plist.NSArray;
import com.dd.plist.NSData;
import com.dd.plist.NSDictionary;
import com.dd.plist.NSObject;
import com.dd.plist.NSString;
import com.github.horrorho.inflatabledonkey.args.Property;
import com.github.horrorho.inflatabledonkey.cloud.accounts.Account;
import com.github.horrorho.inflatabledonkey.cloud.accounts.Token;
import com.github.horrorho.inflatabledonkey.data.der.DERUtils;
import com.github.horrorho.inflatabledonkey.data.der.KeySet;
import com.github.horrorho.inflatabledonkey.pcs.service.ServiceKeySet;
import com.github.horrorho.inflatabledonkey.pcs.service.ServiceKeySetBuilder;
import com.github.horrorho.inflatabledonkey.requests.EscrowProxyRequestFactory;
import com.github.horrorho.inflatabledonkey.util.PLists;
import java.io.IOException;
import java.util.Base64;
import java.util.stream.Stream;
import org.apache.http.client.HttpClient;
import org.bouncycastle.util.encoders.Hex;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * EscrowedKeys.
 *
 * @author Ahseya
 */
public final class EscrowedKeys {

    private static final Logger logger = LoggerFactory.getLogger(EscrowedKeys.class);

    private static final int REMAINING_ATTEMPTS_THRESHOLD = Property.SRP_REMAINING_ATTEMPTS_THRESHOLD.intValue()
            .orElse(3);

    public static EscrowProxyRequestFactory requestFactory(Account account) {
        return new EscrowProxyRequestFactory(account.accountInfo().dsPrsID(),
                account.tokens().get(Token.MMEAUTHTOKEN),
                account.mobileMe().get("com.apple.Dataclass.KeychainSync", "escrowProxyUrl"));
    }

    public static ServiceKeySet keys(HttpClient httpClient, Account account) throws IOException {
        EscrowProxyRequestFactory escrowProxyRequestFactory = new EscrowProxyRequestFactory(
                account.accountInfo().dsPrsID(), account.tokens().get(Token.MMEAUTHTOKEN),
                account.mobileMe().get("com.apple.Dataclass.KeychainSync", "escrowProxyUrl"));

        return keys(httpClient, escrowProxyRequestFactory);
    }

    public static ServiceKeySet keys(HttpClient httpClient, EscrowProxyRequestFactory requests) throws IOException {
        return keys(httpClient, requests, REMAINING_ATTEMPTS_THRESHOLD);
    }

    static ServiceKeySet keys(HttpClient httpClient, EscrowProxyRequestFactory requests,
            int remainingAttemptsThreshold) throws IOException {

        // TODO match BackupKeybagDigest, we are just assuming they will match.
        NSDictionary records = EscrowOperationsRecords.records(httpClient, requests);
        NSDictionary record = pcsRecord(records);

        validateRemainingAttempts(record, remainingAttemptsThreshold);

        NSDictionary escrowedData = EscrowOperationsRecover.recover(httpClient, requests);

        ServiceKeySet backupBagPassword = backupBagPassword(escrowedData);

        return escrowedKeys(record, backupBagPassword);
    }

    static void validateRemainingAttempts(NSDictionary record, int threshold) {
        int remainingAttempts = remainingAttempts(record);

        if (remainingAttempts < threshold) {
            throw new IllegalStateException("srp minimum remaining attempts threshold exceeded: " + threshold);
        }
        ;
    }

    static int remainingAttempts(NSDictionary record) {
        String remainingAttempts = PLists.getAs(record, "remainingAttempts", NSString.class).getContent();
        return Integer.parseInt(remainingAttempts);
    }

    static NSDictionary pcsRecord(NSDictionary records) {
        NSArray metadataList = PLists.getAs(records, "metadataList", NSArray.class);
        return Stream.of(metadataList.getArray()).filter(EscrowedKeys::isPCSRecord)
                .map(nsObject -> (NSDictionary) nsObject).findFirst()
                .orElseThrow(() -> new IllegalArgumentException("no escrow PCS record found"));
    }

    static boolean isPCSRecord(NSObject metadata) {
        return PLists.cast(metadata, NSDictionary.class).flatMap(d -> PLists.optionalAs(d, "label", NSString.class))
                .map(NSString::getContent).map(label -> label.equals("com.apple.protectedcloudstorage.record"))
                .orElse(false);
    }

    static ServiceKeySet backupBagPassword(NSDictionary escrowedData) {
        byte[] backupBagPassword = PLists.getAs(escrowedData, "BackupBagPassword", NSData.class).bytes();
        logger.debug("-- backupBagPassword() - BackupBagPassword: 0x{}", Hex.toHexString(backupBagPassword));

        return DERUtils.parse(backupBagPassword, KeySet::new).flatMap(ServiceKeySetBuilder::build)
                .orElseThrow(() -> new IllegalArgumentException("failed to create backup bag key set"));
    }

    static ServiceKeySet escrowedKeys(NSDictionary record, ServiceKeySet backupBagPassword) {
        String metadataBase64 = PLists.getAs(record, "metadata", NSString.class).getContent();
        byte[] metadata = Base64.getDecoder().decode(metadataBase64);

        byte[] data = ProtectedRecord.unlockData(metadata, backupBagPassword::key);

        return DERUtils.parse(data, KeySet::new).flatMap(ServiceKeySetBuilder::build)
                .orElseThrow(() -> new IllegalArgumentException("failed to create escrowed key set"));
    }
}