Alamofire源碼解讀系列(八)之安全策略(ServerTrustPolicy)

本篇主要講解Alamofire中安全驗證代碼

前言

作為開發(fā)人員,理解HTTPS的原理和應用算是一項基本技能断医。HTTPS目前來說是非常安全的,但仍然有大量的公司還在使用HTTP妄讯。其實HTTPS也并不是很貴啊孩锡。

在網(wǎng)上可以找到大把的介紹HTTTPS的文章,在閱讀ServerTrustPolicy.swfit代碼前亥贸,我們先簡單的講一下HTTPS請求的過程:

上邊的圖片已經(jīng)標出了步驟躬窜,我們逐步的來分析:

  1. HTTPS請求以https開頭,我們首先向服務器發(fā)送一條請求炕置。

  2. 服務器需要一個證書荣挨,這個證書可以從某些機構(gòu)獲得,也可以自己通過工具生成朴摊,通過某些合法機構(gòu)生成的證書客戶端不需要進行驗證默垄,這樣的請求不會觸發(fā)Apple的@objc(URLSession:task:didReceiveChallenge:completionHandler:)代理方法,自己生成的證書則需要客戶端進行驗證甚纲。證書中包含公鑰和私鑰:

    • 公鑰是公開的口锭,任何人都可以使用該公鑰加密數(shù)據(jù),只有知道了私鑰才能解密數(shù)據(jù)
    • 私鑰是要求高度保密的介杆,只有知道了私鑰才能解密用公鑰加密的數(shù)據(jù)
    • 關(guān)于非對稱加密的知識鹃操,大家可以在網(wǎng)上找到
  1. 服務器會把公鑰發(fā)送給客戶端
  2. 客戶端此刻就拿到了公鑰。注意春哨,這里不是直接就拿公鑰加密數(shù)據(jù)發(fā)送了荆隘,因為這僅僅能滿足客戶端給服務器發(fā)加密數(shù)據(jù),那么服務器怎么給客戶端發(fā)送加密數(shù)據(jù)呢赴背?因此需要在客戶端和服務器間建立一條通道椰拒,通道的密碼只有客戶端和服務器知道。只能讓客戶端自己生成一個密碼凰荚,這個密碼就是一個隨機數(shù)潦匈,這個隨機數(shù)絕對是安全的势似,因為目前只有客戶端自己知道
  3. 客戶端把這個隨機數(shù)通過公鑰加密后發(fā)送給服務器覆致,就算被別人截獲了加密后的數(shù)據(jù)卤橄,在沒有私鑰的情況下裕膀,是根本無法解密的
  4. 服務器用私鑰把數(shù)據(jù)解密后勿锅,就獲得了這個隨機數(shù)
  5. 到這里客戶端和服務器的安全連接就已經(jīng)建立了,最主要的目的是交換隨機數(shù)灭美,然后服務器就用這個隨機數(shù)把數(shù)據(jù)加密后發(fā)給客戶端,使用的是對稱加密技術(shù)养盗。
  6. 客戶端獲得了服務器的加密數(shù)據(jù)缚陷,使用隨機數(shù)解密,到此往核,客戶端和服務器就能通過隨機數(shù)發(fā)送數(shù)據(jù)了

HTTPS前邊的幾次握手是需要時間開銷的箫爷,因此,不能每次連接都走一遍聂儒,這就是后邊使用對稱加密數(shù)據(jù)的原因虎锚。Alamofire中主要做的是對服務器的驗證,關(guān)于自定義的安全驗證應該也是模仿了上邊的整個過程衩婚。相對于Apple來說窜护,隱藏了發(fā)送隨機數(shù)這一過程。

對于服務器的驗證除了證書驗證之外一定要加上域名驗證非春,這樣才能更安全柱徙。服務器若要驗證客戶端則會使用簽名技術(shù)。如果偽裝成客戶端來獲取服務器的數(shù)據(jù)最大的問題就是不知道某個請求的參數(shù)是什么奇昙,這樣也就無法獲取數(shù)據(jù)护侮。

ServerTrustPolicyManager

ServerTrustPolicyManager是對ServerTrustPolicy的管理,我們可以暫時把ServerTrustPolicy當做是一個安全策略储耐,就是指對一個服務器采取的策略羊初。然而在真實的開發(fā)中,一個APP可能會用到很多不同的主機地址(host)什湘。因此就產(chǎn)生了這樣的需求长赞,為不同的host綁定一個特定的安全策略。

因此ServerTrustPolicyManager需要一個字典來存放這些有key禽炬,value對應關(guān)系的數(shù)據(jù)涧卵。我們看下邊的代碼:

/// Responsible for managing the mapping of `ServerTrustPolicy` objects to a given host.
open class ServerTrustPolicyManager {
    /// The dictionary of policies mapped to a particular host.
    open let policies: [String: ServerTrustPolicy]

    /// Initializes the `ServerTrustPolicyManager` instance with the given policies.
    ///
    /// Since different servers and web services can have different leaf certificates, intermediate and even root
    /// certficates, it is important to have the flexibility to specify evaluation policies on a per host basis. This
    /// allows for scenarios such as using default evaluation for host1, certificate pinning for host2, public key
    /// pinning for host3 and disabling evaluation for host4.
    ///
    /// - parameter policies: A dictionary of all policies mapped to a particular host.
    ///
    /// - returns: The new `ServerTrustPolicyManager` instance.
    public init(policies: [String: ServerTrustPolicy]) {
        self.policies = policies
    }

    /// Returns the `ServerTrustPolicy` for the given host if applicable.
    ///
    /// By default, this method will return the policy that perfectly matches the given host. Subclasses could override
    /// this method and implement more complex mapping implementations such as wildcards.
    ///
    /// - parameter host: The host to use when searching for a matching policy.
    ///
    /// - returns: The server trust policy for the given host if found.
    open func serverTrustPolicy(forHost host: String) -> ServerTrustPolicy? {
        return policies[host]
    }
}

出于優(yōu)秀代碼的設(shè)計問題,在后續(xù)的使用中肯定會有根據(jù)host讀取策略的要求腹尖,因此柳恐,在上邊的類中設(shè)計了最后一個函數(shù)。

我們是這么使用的:

let serverTrustPolicies: [String: ServerTrustPolicy] = [
    "test.example.com": .pinCertificates(
        certificates: ServerTrustPolicy.certificates(),
        validateCertificateChain: true,
        validateHost: true
    ),
    "insecure.expired-apis.com": .disableEvaluation
]

let sessionManager = SessionManager(
    serverTrustPolicyManager: ServerTrustPolicyManager(policies: serverTrustPolicies)
)

在Alamofire中這個ServerTrustPolicyManager會在SessionDelegate的收到服務器要求驗證的方法中會出現(xiàn)热幔,這個會在后續(xù)的文章中給出說明乐设。

把ServerTrustPolicyManager綁定到URLSession

ServerTrustPolicyManager作為URLSession的一個屬性,通過運行時的手段來實現(xiàn)绎巨。

extension URLSession {
    private struct AssociatedKeys {
        static var managerKey = "URLSession.ServerTrustPolicyManager"
    }

    var serverTrustPolicyManager: ServerTrustPolicyManager? {
        get {
            return objc_getAssociatedObject(self, &AssociatedKeys.managerKey) as? ServerTrustPolicyManager
        }
        set (manager) {
            objc_setAssociatedObject(self, &AssociatedKeys.managerKey, manager, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
        }
    }
}

上邊的代碼用到了運行時近尚,尤其是OBJC_ASSOCIATION_RETAIN_NONATOMIC這個選項,其中包含了強引用和若引用的問題场勤,我想在這里簡單的解釋一下引用問題戈锻。

我們可以這么理解歼跟,不管是類還是對象,或者是對象的屬性格遭,我們都稱之為一個object哈街。我們把這個object比作一個鐵盒子,當有其它的對象對他強引用的時候拒迅,就像給這個鐵盒子綁了一個繩子骚秦,弱引用就像一條虛幻的激光一樣連接這個盒子。當然璧微,在oc中作箍,很多對象默認的情況下就是strong的。

我們可以想象這個盒子是被繩子拉住了前硫,才能漂浮在空中胞得,如果沒有繩子就會掉到無底深淵,然后銷毀屹电。這里最重要的概念就是懒震,只要一個對象沒有了強引用,那么就會立刻銷毀嗤详。

我們舉個例子:

MyViewController *myController = [[MyViewController alloc] init…];

上邊的代碼是再平常不過的一段代碼个扰,創(chuàng)建了一個MyViewController實例,然后使用myController指向了這個實例葱色,因此這個實例就有了一個繩子递宅,他就不會立刻銷毀,如果我們把代碼改成這樣:

MyViewController * __weak myController = [[MyViewController alloc] init…];

把myController指向?qū)嵗O(shè)置為弱引用苍狰,那么即使在下一行代碼打印這個myController办龄,也會是nil。因為實例并沒有一個繩子讓他能不不銷毀淋昭。

所謂道理都是相通的俐填,只要理解了這個概念就能明白引用循環(huán)的問題,需要注意的是作用域的問題翔忽,如果上邊的myController在一個函數(shù)中英融,那么出了函數(shù)的作用域,也會銷毀歇式。

ServerTrustPolicy

接下來將是本篇文章最核心的內(nèi)容驶悟,得益于swift語言的強大,ServerTrustPolicy被設(shè)計成enum枚舉材失。既然本質(zhì)上只是個枚舉痕鳍,那么我們先不關(guān)心枚舉中的函數(shù),先單獨看看有哪些枚舉子選項:

case performDefaultEvaluation(validateHost: Bool)
    case performRevokedEvaluation(validateHost: Bool, revocationFlags: CFOptionFlags)
    case pinCertificates(certificates: [SecCertificate], validateCertificateChain: Bool, validateHost: Bool)
    case pinPublicKeys(publicKeys: [SecKey], validateCertificateChain: Bool, validateHost: Bool)
    case disableEvaluation
    case customEvaluation((_ serverTrust: SecTrust, _ host: String) -> Bool)

千萬別認為上邊的某些選項是個函數(shù),其實他們只是不同的類型加上關(guān)聯(lián)值而已笼呆。我們先不對這些選項做不解釋熊响,因為在下邊的方法中會根據(jù)這些選項做出不同的操作,到那時在說明這些選項的作用更好诗赌。

還有一點要明白耘眨,在swift中是像下邊代碼這樣初始化枚舉的:

ServerTrustPolicy.performDefaultEvaluation(validateHost: true)

我們用上帝視角來看作者的代碼,接下來就應該看看那些帶有static的函數(shù)了境肾,因為這些函數(shù)都是靜態(tài)函數(shù),可以直接用ServerTrustPolicy調(diào)用胆屿,雖然歸屬于ServerTrustPolicy奥喻,但相對比較獨立。

獲取證書

 /// Returns all certificates within the given bundle with a `.cer` file extension.
    ///
    /// - parameter bundle: The bundle to search for all `.cer` files.
    ///
    /// - returns: All certificates within the given bundle.
    public static func certificates(in bundle: Bundle = Bundle.main) -> [SecCertificate] {
        var certificates: [SecCertificate] = []

        let paths = Set([".cer", ".CER", ".crt", ".CRT", ".der", ".DER"].map { fileExtension in
            bundle.paths(forResourcesOfType: fileExtension, inDirectory: nil)
        }.joined())

        for path in paths {
            if
                let certificateData = try? Data(contentsOf: URL(fileURLWithPath: path)) as CFData,
                let certificate = SecCertificateCreateWithData(nil, certificateData)
            {
                certificates.append(certificate)
            }
        }

        return certificates
    }

在開發(fā)中非迹,如果和服務器的安全連接需要對服務器進行驗證环鲤,最好的辦法就是在本地保存一些證書,拿到服務器傳過來的證書憎兽,然后進行對比冷离,如果有匹配的,就表示可以信任該服務器纯命。從上邊的函數(shù)中可以看出西剥,Alamofire會在Bundle(默認為main)中查找?guī)в?code>[".cer", ".CER", ".crt", ".CRT", ".der", ".DER"]后綴的證書。

注意亿汞,上邊函數(shù)中的paths保存的是這些證書的路徑瞭空,map把這些后綴轉(zhuǎn)換成路徑,我們以.cer為例疗我。通過map后咆畏,原來的".cer"就變成了一個數(shù)組,也就是說通過map后吴裤,原來的數(shù)組變成了二維數(shù)組了旧找,然后再通過joined()函數(shù),把二維數(shù)組轉(zhuǎn)換成一維數(shù)組麦牺。

然后要做的就是根據(jù)這些路徑獲取證書數(shù)據(jù)了钮蛛,就不多做解釋了。

獲取公鑰

這個比較好理解剖膳,就是在本地證書中取出公鑰愿卒,至于證書是由什么組成的,大家可以網(wǎng)上自己查找相關(guān)內(nèi)容潮秘,

 /// Returns all public keys within the given bundle with a `.cer` file extension.
    ///
    /// - parameter bundle: The bundle to search for all `*.cer` files.
    ///
    /// - returns: All public keys within the given bundle.
    public static func publicKeys(in bundle: Bundle = Bundle.main) -> [SecKey] {
        var publicKeys: [SecKey] = []

        for certificate in certificates(in: bundle) {
            if let publicKey = publicKey(for: certificate) {
                publicKeys.append(publicKey)
            }
        }

        return publicKeys
    }

上邊的函數(shù)很簡單琼开,但是他用到了另外一個函數(shù)publicKey(for: certificate)

通過SecCertificate獲取SecKey

獲取SecKey可以通過SecCertificate也可以通過SecTrust,下邊的函數(shù)是第一種情況:

  private static func publicKey(for certificate: SecCertificate) -> SecKey? {
        var publicKey: SecKey?

        let policy = SecPolicyCreateBasicX509()
        var trust: SecTrust?
        let trustCreationStatus = SecTrustCreateWithCertificates(certificate, policy, &trust)

        if let trust = trust, trustCreationStatus == errSecSuccess {
            publicKey = SecTrustCopyPublicKey(trust)
        }

        return publicKey
    }

上邊的過程沒什么好說的枕荞,基本上這是固定寫法柜候,值得注意的是上邊默認是按照X509證書格式來解析的搞动,因此在生成證書的時候最好使用這個格式。否則可能無法獲取到publicKey渣刷。

最核心的方法evaluate

從函數(shù)設(shè)計的角度考慮鹦肿,evaluate應該接受兩個參數(shù),一個是服務器的證書辅柴,一個是host箩溃。返回一個布爾類型。

evaluate函數(shù)是枚舉中的一個函數(shù)碌嘀,因此它必然依賴枚舉的子選項涣旨。這就說明只有初始化枚舉才能使用這個函數(shù)。

舉一個現(xiàn)實生活中的一個小例子股冗。有一個管理員霹陡,他手下管理這3個員工,分別是廚師止状,前臺烹棉,行政,現(xiàn)在有一個任務需要想辦法弄明白這3個人會不會喊麥怯疤,有兩種方法可以得出結(jié)果浆洗,一種是管理員一個一個的去問,也就是得出結(jié)果的方法掌握在管理員手中集峦,只有通過管理員才能知道答案辅髓。有一個老板想知道廚師會不會喊麥。他必須要去問管理員才行少梁。這就造成了邏輯上的問題洛口。另一種方法,讓每一個人當場喊一個凯沪,任何人在任何場合都能得出結(jié)果第焰。

最近重新看了代碼大全這本書,對子程序的設(shè)計有了全新的認識妨马。重點還在于抽象類型是什么挺举?這個就不多說了,有興趣的朋友可以去看看那本書烘跺。

這個函數(shù)很長湘纵,但總體的思想是根據(jù)不同的策略做出不同的操作。我們先把該函數(shù)弄上來:

 /// Evaluates whether the server trust is valid for the given host.
    ///
    /// - parameter serverTrust: The server trust to evaluate.
    /// - parameter host:        The host of the challenge protection space.
    ///
    /// - returns: Whether the server trust is valid.
    public func evaluate(_ serverTrust: SecTrust, forHost host: String) -> Bool {
        var serverTrustIsValid = false

        switch self {
        case let .performDefaultEvaluation(validateHost):
            let policy = SecPolicyCreateSSL(true, validateHost ? host as CFString : nil)
            SecTrustSetPolicies(serverTrust, policy)

            serverTrustIsValid = trustIsValid(serverTrust)
        case let .performRevokedEvaluation(validateHost, revocationFlags):
            let defaultPolicy = SecPolicyCreateSSL(true, validateHost ? host as CFString : nil)
            let revokedPolicy = SecPolicyCreateRevocation(revocationFlags)
            SecTrustSetPolicies(serverTrust, [defaultPolicy, revokedPolicy] as CFTypeRef)

            serverTrustIsValid = trustIsValid(serverTrust)
        case let .pinCertificates(pinnedCertificates, validateCertificateChain, validateHost):
            if validateCertificateChain {
                let policy = SecPolicyCreateSSL(true, validateHost ? host as CFString : nil)
                SecTrustSetPolicies(serverTrust, policy)

                SecTrustSetAnchorCertificates(serverTrust, pinnedCertificates as CFArray)
                SecTrustSetAnchorCertificatesOnly(serverTrust, true)

                serverTrustIsValid = trustIsValid(serverTrust)
            } else {
                let serverCertificatesDataArray = certificateData(for: serverTrust)
                let pinnedCertificatesDataArray = certificateData(for: pinnedCertificates)

                outerLoop: for serverCertificateData in serverCertificatesDataArray {
                    for pinnedCertificateData in pinnedCertificatesDataArray {
                        if serverCertificateData == pinnedCertificateData {
                            serverTrustIsValid = true
                            break outerLoop
                        }
                    }
                }
            }
        case let .pinPublicKeys(pinnedPublicKeys, validateCertificateChain, validateHost):
            var certificateChainEvaluationPassed = true

            if validateCertificateChain {
                let policy = SecPolicyCreateSSL(true, validateHost ? host as CFString : nil)
                SecTrustSetPolicies(serverTrust, policy)

                certificateChainEvaluationPassed = trustIsValid(serverTrust)
            }

            if certificateChainEvaluationPassed {
                outerLoop: for serverPublicKey in ServerTrustPolicy.publicKeys(for: serverTrust) as [AnyObject] {
                    for pinnedPublicKey in pinnedPublicKeys as [AnyObject] {
                        if serverPublicKey.isEqual(pinnedPublicKey) {
                            serverTrustIsValid = true
                            break outerLoop
                        }
                    }
                }
            }
        case .disableEvaluation:
            serverTrustIsValid = true
        case let .customEvaluation(closure):
            serverTrustIsValid = closure(serverTrust, host)
        }

        return serverTrustIsValid
    }

不管選用那種策略滤淳,要完成驗證都需要3步:

  1. SecPolicyCreateSSL 創(chuàng)建策略梧喷,是否驗證host
  2. SecTrustSetPolicies 為待驗證的對象設(shè)置策略
  3. trustIsValid 進行驗證

到了這里就有必要介紹一下幾種策略的用法了:

  • performDefaultEvaluation 默認的策略,只有合法證書才能通過驗證
  • performRevokedEvaluation 對注銷證書做的一種額外設(shè)置,關(guān)于注銷證書驗證超過了本篇文章的范圍铺敌,有興趣的朋友可以查看官方文檔汇歹。
  • pinCertificates 驗證指定的證書,這里邊有一個參數(shù):是否驗證證書鏈偿凭,關(guān)于證書鏈的相關(guān)內(nèi)容可以看這篇文章iOS 中對 HTTPS 證書鏈的驗證.驗證證書鏈算是比較嚴格的驗證了产弹。這里邊設(shè)置錨點等等,這里就不做解釋了弯囊。如果不驗證證書鏈的話痰哨,只要對比指定的證書有沒有和服務器信任的證書匹配項,只要有一個能匹配上匾嘱,就驗證通過
  • pinPublicKeys 這個更上邊的那個差不多斤斧,就不做介紹了
  • disableEvaluation 該選項下,驗證一直都是通過的奄毡,也就是說無條件信任
  • customEvaluation 自定義驗證,需要返回一個布爾類型的結(jié)果

上邊的這些驗證選項中贝或,我們可能根據(jù)自己的需求進行驗證吼过,其中最安全的是證書鏈加host雙重驗證。而且在上邊的evaluate函數(shù)中用到了4個輔助函數(shù)咪奖,我們來看看:

func trustIsValid(_ trust: SecTrust) -> Bool

該函數(shù)用于判斷是否驗證成功

 private func trustIsValid(_ trust: SecTrust) -> Bool {
        var isValid = false

        var result = SecTrustResultType.invalid
        let status = SecTrustEvaluate(trust, &result)

        if status == errSecSuccess {
            let unspecified = SecTrustResultType.unspecified
            let proceed = SecTrustResultType.proceed


            isValid = result == unspecified || result == proceed
        }

        return isValid
    }

func certificateData(for trust: SecTrust) -> [Data]

該函數(shù)把服務器的SecTrust處理成證書二進制數(shù)組

 private func certificateData(for trust: SecTrust) -> [Data] {
        var certificates: [SecCertificate] = []

        for index in 0..<SecTrustGetCertificateCount(trust) {
            if let certificate = SecTrustGetCertificateAtIndex(trust, index) {
                certificates.append(certificate)
            }
        }

        return certificateData(for: certificates)
    }

func certificateData(for certificates: [SecCertificate]) -> [Data]

private func certificateData(for certificates: [SecCertificate]) -> [Data] {
        return certificates.map { SecCertificateCopyData($0) as Data }
    }

func publicKeys(for trust: SecTrust) -> [SecKey]

   private static func publicKeys(for trust: SecTrust) -> [SecKey] {
        var publicKeys: [SecKey] = []

        for index in 0..<SecTrustGetCertificateCount(trust) {
            if
                let certificate = SecTrustGetCertificateAtIndex(trust, index),
                let publicKey = publicKey(for: certificate)
            {
                publicKeys.append(publicKey)
            }
        }

        return publicKeys
    }

總結(jié)

其實在開發(fā)中盗忱,可以不必關(guān)心這些實現(xiàn)細節(jié),要想弄明白這些策略的詳情羊赵,還需要做很多的功課才行趟佃。

由于知識水平有限,如有錯誤昧捷,還望指出

鏈接

Alamofire源碼解讀系列(一)之概述和使用 簡書-----博客園

Alamofire源碼解讀系列(二)之錯誤處理(AFError) 簡書-----博客園

Alamofire源碼解讀系列(三)之通知處理(Notification) 簡書-----博客園

Alamofire源碼解讀系列(四)之參數(shù)編碼(ParameterEncoding) 簡書-----博客園

Alamofire源碼解讀系列(五)之結(jié)果封裝(Result) 簡書-----博客園

Alamofire源碼解讀系列(六)之Task代理(TaskDelegate) 簡書-----博客園

Alamofire源碼解讀系列(七)之網(wǎng)絡(luò)監(jiān)控(NetworkReachabilityManager) 簡書-----博客園

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末闲昭,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子靡挥,更是在濱河造成了極大的恐慌序矩,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,214評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件跋破,死亡現(xiàn)場離奇詭異簸淀,居然都是意外死亡,警方通過查閱死者的電腦和手機毒返,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評論 2 382
  • 文/潘曉璐 我一進店門租幕,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人拧簸,你說我怎么就攤上這事劲绪。” “怎么了?”我有些...
    開封第一講書人閱讀 152,543評論 0 341
  • 文/不壞的土叔 我叫張陵珠叔,是天一觀的道長蝎宇。 經(jīng)常有香客問我,道長祷安,這世上最難降的妖魔是什么姥芥? 我笑而不...
    開封第一講書人閱讀 55,221評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮汇鞭,結(jié)果婚禮上凉唐,老公的妹妹穿的比我還像新娘。我一直安慰自己霍骄,他們只是感情好台囱,可當我...
    茶點故事閱讀 64,224評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著读整,像睡著了一般簿训。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上米间,一...
    開封第一講書人閱讀 49,007評論 1 284
  • 那天强品,我揣著相機與錄音,去河邊找鬼屈糊。 笑死的榛,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的逻锐。 我是一名探鬼主播夫晌,決...
    沈念sama閱讀 38,313評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼昧诱!你這毒婦竟也來了晓淀?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,956評論 0 259
  • 序言:老撾萬榮一對情侶失蹤盏档,失蹤者是張志新(化名)和其女友劉穎要糊,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體妆丘,經(jīng)...
    沈念sama閱讀 43,441評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡锄俄,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,925評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了勺拣。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片奶赠。...
    茶點故事閱讀 38,018評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖药有,靈堂內(nèi)的尸體忽然破棺而出毅戈,到底是詐尸還是另有隱情苹丸,我是刑警寧澤,帶...
    沈念sama閱讀 33,685評論 4 322
  • 正文 年R本政府宣布苇经,位于F島的核電站赘理,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏扇单。R本人自食惡果不足惜商模,卻給世界環(huán)境...
    茶點故事閱讀 39,234評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望蜘澜。 院中可真熱鬧施流,春花似錦、人聲如沸鄙信。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽装诡。三九已至银受,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間鸦采,已是汗流浹背宾巍。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評論 1 261
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留赖淤,地道東北人蜀漆。 一個月前我還...
    沈念sama閱讀 45,467評論 2 352
  • 正文 我出身青樓谅河,卻偏偏與公主長得像咱旱,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子绷耍,可洞房花燭夜當晚...
    茶點故事閱讀 42,762評論 2 345

推薦閱讀更多精彩內(nèi)容