BottledPeer.swift   [plain text]


/*
 * Copyright (c) 2019 Apple Inc. All Rights Reserved.
 *
 * @APPLE_LICENSE_HEADER_START@
 *
 * This file contains Original Code and/or Modifications of Original Code
 * as defined in and that are subject to the Apple Public Source License
 * Version 2.0 (the 'License'). You may not use this file except in
 * compliance with the License. Please obtain a copy of the License at
 * http://www.opensource.apple.com/apsl/ and read it before using this
 * file.
 *
 * The Original Code and all software distributed under the License are
 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
 * Please see the License for the specific language governing rights and
 * limitations under the License.
 *
 * @APPLE_LICENSE_HEADER_END@
 */

import Foundation
import SecurityFoundation

class BottledPeer: NSObject {

    public var escrowKeys: EscrowKeys
    public var secret: Data
    public var peerID: String
    public var bottleID: String
    public var peerKeys: OctagonSelfPeerKeys

    public var signatureUsingEscrowKey: Data
    public var signatureUsingPeerKey: Data
    public var escrowSigningPublicKey: Data
    public var escrowSigningSPKI: Data
    public var peersigningSPKI: Data

    public var contents: Data

    public class func encryptionOperation() -> (_SFAuthenticatedEncryptionOperation) {
        let keySpecifier = _SFAESKeySpecifier.init(bitSize: TPHObjectiveC.aes256BitSize())
        return _SFAuthenticatedEncryptionOperation.init(keySpecifier: keySpecifier)
    }

    // Given a peer's details including private key material, and
    // the keys generated from the escrow secret, encrypt the peer private keys,
    // make a bottled peer object and serialize it into data.
    public init (peerID: String, bottleID: String, peerSigningKey: _SFECKeyPair, peerEncryptionKey: _SFECKeyPair, bottleSalt: String) throws {

        let secret = try BottledPeer.makeMeSomeEntropy(requiredLength: Int(OTMasterSecretLength))

        self.secret = secret
        self.escrowKeys = try EscrowKeys(secret: secret, bottleSalt: bottleSalt)

        // Serialize the peer private keys into "contents"
        guard let contentsObj = OTBottleContents() else {
            throw Error.OTErrorBottleCreation
        }
        guard let signingPK = OTPrivateKey() else {
            throw Error.OTErrorPrivateKeyCreation
        }
        signingPK.keyType = OTPrivateKey_KeyType.EC_NIST_CURVES
        signingPK.keyData = peerSigningKey.keyData

        guard let encryptionPK = OTPrivateKey() else {
            throw Error.OTErrorPrivateKeyCreation
        }
        encryptionPK.keyType = OTPrivateKey_KeyType.EC_NIST_CURVES
        encryptionPK.keyData = peerEncryptionKey.keyData

        contentsObj.peerSigningPrivKey = signingPK
        contentsObj.peerEncryptionPrivKey = encryptionPK
        guard let clearContentsData = contentsObj.data else {
            throw Error.OTErrorBottleCreation
        }

        // Encrypt the contents
        let op = BottledPeer.encryptionOperation()
        let cipher = try op.encrypt(clearContentsData, with: escrowKeys.symmetricKey)

        let escrowSigningECPubKey: _SFECPublicKey = (escrowKeys.signingKey.publicKey as! _SFECPublicKey)
        let escrowEncryptionECPubKey: _SFECPublicKey = escrowKeys.encryptionKey.publicKey as! _SFECPublicKey

        let peerSigningECPublicKey: _SFECPublicKey = peerSigningKey.publicKey as! _SFECPublicKey
        let peerEncryptionECPublicKey: _SFECPublicKey = peerEncryptionKey.publicKey as! _SFECPublicKey

        // Serialize the whole thing
        guard let obj = OTBottle() else {
            throw Error.OTErrorBottleCreation
        }
        obj.peerID = peerID
        obj.bottleID = bottleID
        obj.escrowedSigningSPKI = escrowSigningECPubKey.encodeSubjectPublicKeyInfo()
        obj.escrowedEncryptionSPKI = escrowEncryptionECPubKey.encodeSubjectPublicKeyInfo()
        obj.peerSigningSPKI = peerSigningECPublicKey.encodeSubjectPublicKeyInfo()
        obj.peerEncryptionSPKI = peerEncryptionECPublicKey.encodeSubjectPublicKeyInfo()

        guard let authObj = OTAuthenticatedCiphertext() else {
            throw Error.OTErrorAuthCipherTextCreation
        }
        authObj.ciphertext = cipher.ciphertext
        authObj.authenticationCode = cipher.authenticationCode
        authObj.initializationVector = cipher.initializationVector

        obj.contents = authObj

        self.peerID = peerID
        self.bottleID = bottleID

        try self.peerKeys = OctagonSelfPeerKeys(peerID: peerID, signingKey: peerSigningKey, encryptionKey: peerEncryptionKey)
        self.contents = obj.data

        let escrowedSigningECPublicKey = escrowKeys.signingKey.publicKey as! _SFECPublicKey

        self.escrowSigningPublicKey = escrowedSigningECPublicKey.keyData
        self.escrowSigningSPKI = escrowedSigningECPublicKey.encodeSubjectPublicKeyInfo()
        self.peersigningSPKI = peerSigningECPublicKey.encodeSubjectPublicKeyInfo()

        let xso = BottledPeer.signingOperation()

        let signedByEscrowKey = try xso.sign(self.contents, with: escrowKeys.signingKey)
        self.signatureUsingEscrowKey = signedByEscrowKey.signature

        let signedByPeerKey = try xso.sign(self.contents, with: peerSigningKey)
        self.signatureUsingPeerKey = signedByPeerKey.signature
    }

    // Deserialize a bottle (data) and decrypt the contents (peer keys)
    // using the keys generated from the escrow secret, and signatures from signing keys
    public init (contents: Data, secret: Data, bottleSalt: String, signatureUsingEscrow: Data, signatureUsingPeerKey: Data) throws {

        self.secret = secret
        self.escrowKeys = try EscrowKeys(secret: self.secret, bottleSalt: bottleSalt)

        guard let escrowSigningECKey: _SFECPublicKey = escrowKeys.signingKey.publicKey() as? _SFECPublicKey else {
            os_log("escrow key not an SFECPublicKey?", log: tplogDebug, type: .default)
            throw Error.OTErrorBottleCreation
        }
        self.escrowSigningSPKI = escrowSigningECKey.encodeSubjectPublicKeyInfo()

        // Deserialize the whole thing
        guard let obj = OTBottle(data: contents) else {
            os_log("Unable to deserialize bottle", log: tplogDebug, type: .default)
            throw Error.OTErrorDeserializationFailure
        }

        // First, the easy check: did the entropy create the keys that are supposed to be in the bottle?
        guard obj.escrowedSigningSPKI == self.escrowSigningSPKI else {
            os_log("Bottled SPKI does not match re-created SPKI", log: tplogDebug, type: .default)
            throw Error.OTErrorEntropyKeyMismatch
        }

        // Second, does the signature verify on the given data?
        let xso = BottledPeer.signingOperation()

        let escrowSigned = _SFSignedData.init(data: contents, signature: signatureUsingEscrow)
        try xso.verify(escrowSigned, with: escrowSigningECKey)

        // Now, decrypt contents
        let op = BottledPeer.encryptionOperation()
        let ac: OTAuthenticatedCiphertext = obj.contents as OTAuthenticatedCiphertext

        let ciphertext = _SFAuthenticatedCiphertext.init(ciphertext: ac.ciphertext, authenticationCode: ac.authenticationCode, initializationVector: ac.initializationVector)

        let clearContentsData = try op.decrypt(ciphertext, with: escrowKeys.symmetricKey)
        if clearContentsData.isEmpty {
            throw Error.OTErrorDecryptionFailure
        }

        // Deserialize contents into private peer keys
        guard let contentsObj = OTBottleContents(data: clearContentsData) else {
            throw Error.OTErrorDeserializationFailure
        }

        self.peerID = obj.peerID
        self.bottleID = obj.bottleID

        try self.peerKeys = OctagonSelfPeerKeys(peerID: peerID,
                                                signingKey: try contentsObj.peerSigningPrivKey.asECKeyPair(),
                                                encryptionKey: try contentsObj.peerEncryptionPrivKey.asECKeyPair())
        self.contents = contents

        self.peersigningSPKI = obj.peerSigningSPKI

        let peerSigningPubKey = _SFECPublicKey(subjectPublicKeyInfo: obj.peerSigningSPKI)
        let peerEncryptionPubKey = _SFECPublicKey(subjectPublicKeyInfo: obj.peerEncryptionSPKI)

        // Check the private keys match the public keys
        if self.peerKeys.signingKey.publicKey != peerSigningPubKey {
            throw Error.OTErrorKeyMismatch
        }
        if self.peerKeys.encryptionKey.publicKey != peerEncryptionPubKey {
            throw Error.OTErrorKeyMismatch
        }

        self.escrowSigningSPKI = escrowSigningECKey.encodeSubjectPublicKeyInfo()

        self.signatureUsingPeerKey = signatureUsingPeerKey
        self.signatureUsingEscrowKey = signatureUsingEscrow

        self.escrowSigningPublicKey = escrowSigningECKey.keyData

        let peerSigned = _SFSignedData.init(data: self.contents, signature: signatureUsingPeerKey)
        guard let peerPublicKey = self.peerKeys.publicSigningKey else {
            throw Error.OTErrorKeyMismatch
        }
        try xso.verify(peerSigned, with: peerPublicKey)
    }

    public func escrowSigningPublicKeyHash() -> String {
        return TPHObjectiveC.digest(usingSha384: self.escrowSigningPublicKey)
    }

    public class func signingOperation() -> (_SFEC_X962SigningOperation) {
        let keySpecifier = _SFECKeySpecifier.init(curve: SFEllipticCurve.nistp384)
        let digestOperation = _SFSHA384DigestOperation.init()
        return _SFEC_X962SigningOperation.init(keySpecifier: keySpecifier, digestOperation: digestOperation)
    }

    public class func verifyBottleSignature(data: Data, signature: Data, pubKey: _SFECPublicKey) throws -> (Bool) {
        let xso = BottledPeer.signingOperation()
        let peerSigned = _SFSignedData.init(data: data, signature: signature)
        try xso.verify(peerSigned, with: pubKey)
        return true
    }

    public class func makeMeSomeEntropy(requiredLength: Int) throws -> Data {
        let bytesPointer = UnsafeMutableRawPointer.allocate(byteCount: requiredLength, alignment: 1)

        if SecRandomCopyBytes(kSecRandomDefault, requiredLength, bytesPointer) != 0 {
            throw Error.OTErrorEntropyCreation
        }
        return Data(bytes: bytesPointer, count: requiredLength)
    }
}

extension BottledPeer {
    enum Error: Swift.Error {
        case OTErrorDeserializationFailure
        case OTErrorDecryptionFailure
        case OTErrorKeyInstantiation
        case OTErrorKeyMismatch
        case OTErrorBottleCreation
        case OTErrorAuthCipherTextCreation
        case OTErrorPrivateKeyCreation
        case OTErrorEscrowKeyCreation
        case OTErrorEntropyCreation
        case OTErrorEntropyKeyMismatch
    }
}

extension BottledPeer.Error: LocalizedError {
    public var errorDescription: String? {
        switch self {
        case .OTErrorDeserializationFailure:
            return "Failed to deserialize bottle peer"
        case .OTErrorDecryptionFailure:
            return "could not decrypt bottle contents"
        case .OTErrorKeyInstantiation:
            return "Failed to instantiate octagon peer keys"
        case .OTErrorKeyMismatch:
            return "public and private peer signing keys do not match"
        case .OTErrorBottleCreation:
            return "failed to create bottle"
        case .OTErrorAuthCipherTextCreation:
            return "failed to create authenticated ciphertext"
        case .OTErrorPrivateKeyCreation:
            return "failed to create private key"
        case .OTErrorEscrowKeyCreation:
            return "failed to create escrow keys"
        case .OTErrorEntropyCreation:
            return "failed to create entropy"
        case .OTErrorEntropyKeyMismatch:
            return "keys generated by the entropy+salt do not match the bottle contents"
        }
    }
}