google quic tls 握手原理(三)

一难菌、前言

  • 本文延續(xù)google quic tls握手原理(二)對客戶端接收到服務端的Initial+Handshake報文后繼續(xù)進行分析珠移。
  • 通過前面兩遍關于google quic tls握手相關的分析的畴,目前對google quiche項目握手模塊的代碼分布和設計模式已經(jīng)有較為深入的理解硫眨,本文依然按照前兩遍的分析思路和邏輯對收到服務端的握手包后足淆,客戶端究竟是如何處理的。

二礁阁、服務端握手報文分析

  • 首先通過如下流程圖來展示客戶端收到服務端的報文后解析巧号、派發(fā)處理的邏輯關系和代碼所在位置,這樣便于后面的分析姥闭。


    000.png
  • Quic數(shù)據(jù)包大致分成三類丹鸿,其一是Initial包,其次是handeshake包棚品,再者就是application data靠欢。
  • 對于Initialhandshake相關的如OnCryptoFrame廊敌、OnNewTokenFrame、OnHandshakeDoneFrame等门怪,在QuicSession模塊會透過QuicCryptoStream模塊最終將數(shù)據(jù)轉(zhuǎn)發(fā)到TLS引擎進行處理骡澈。
  • 而對于應用數(shù)據(jù)如StreamFrameQuicSession模塊會通過GetOrCreateStream()找到對應的StreamID,然后派發(fā)到相應的QuicStream進行處理。
  • 最后我們通過抓包文件結(jié)合代碼一步一步進行分析掷空。


    005_01.png
  • 當服務端收到客戶端發(fā)送的Client Hello消息后肋殴,服務端會進行配置文件協(xié)商、版本協(xié)商坦弟、各種加密套件疼电、秘鑰算法、應用層協(xié)議等各種擴展協(xié)商减拭,協(xié)商完后和客戶端一樣會驅(qū)動ssl引擎生成Server Hello消息,流程和客戶端類似区丑。
  • 和客戶端不同的是服務端發(fā)送的首包數(shù)據(jù)會將Initial包和HandShake包進行聚合打包成一個QUIC報文發(fā)送到客戶端拧粪。
  • 客戶端收到該握手報文后會對該報文進行循環(huán)解析,解析成兩個QuicCryptoFrame幀沧侥,其中第一個Frame為EncryptionLevelENCRYPTION_INITIAL,第二個Frame的EncryptionLevelENCRYPTION_HANDSHAKE可霎。
  • 上述報文中包含兩個QUIC IETF包,其中Server Hello對應的是Intial包宴杀,Server Hello中包含了服務端采用的加密算法(Cipher Suite)癣朗、key_share 擴展信息對應服務端的公鑰信息、以及TLS的版本支持1.3旺罢。
  • 而另外一個QUIC IETF包則是handshake包,其中包含加密擴展(應用協(xié)議旷余、傳輸參數(shù)和應用設置等等)。
  • 緊接著服務端還外發(fā)送一個handleshake包扁达,如下圖(2)
    005_02.png
  • google quic支持使用聚合包對報文進行打包,服務端收到客戶端的Initial報文后正卧,連續(xù)回了兩個handleshake報文,第一個包含Server Hello和加密擴展跪解,第二個握手包包含了證書和Finished信息
  • Handshake Protocol:Certificate(證書本身包含服務器的公鑰炉旷、證書頒發(fā)機構(gòu)(CA)的信息以及其他相關信息)
    005_03.png
  • Handshake Protocol:Certificate Verify(用于驗證服務端的身份。這個消息包含了證書簽名算法類型叉讥、服務端使用私鑰對握手消息進行數(shù)字簽名的結(jié)果窘行,客戶端可以通過服務端的證書公鑰來驗證數(shù)字簽名的有效性,從而確定服務端的身份图仓,并建立安全的通信連接)
    005_04.png
  • Handshake Protocol:Finished(表示握手完成)
    005_05.png
  • 以上抓包為QUICK RAW DATA,也就是不帶http擴展的報文罐盔。

三、客戶端處理服務端的Initial包

  • 本節(jié)分析客戶端是如何處理服務端發(fā)送過來的Initial包的,首先簡單看下客戶端收到服務端的包后的簡單處理流程如下圖:
    006.png
  • 無論是服務端還是客戶端收到對端的包后處理流程和上述基本一致救崔,對于握手流程的包都是上述流程翘骂,如果是非握手包那么經(jīng)歷的QuicStream模塊會有所變化壁熄。
  • 由于本文重點是分析握手,所以本文不分析上述流程中的代碼實現(xiàn)碳竟,圖(4)只是讓大家對Quic報文的讀取處理有一個直觀的認識草丧。
  • 本文分析重點是TlsHandshaker::ProcessInput(…)之后的流程,在google quic tls握手原理(一)一文中有提到莹桅,TlsHandshaker模塊由CryptoMessageParser派生而來昌执,顧名思義CryptoMessageParser是握手信息解析器,通過其提供的接口ProcessInput()來完成Server HelloClient Hello的解析,接下來看它的實現(xiàn)如下:
bool TlsHandshaker::ProcessInput(absl::string_view input,
                                 EncryptionLevel level) {
  if (parser_error_ != QUIC_NO_ERROR) {
    return false;
  }
  // TODO(nharper): Call SSL_quic_read_level(ssl()) and check whether the
  // encryption level BoringSSL expects matches the encryption level that we
  // just received input at. If they mismatch, should ProcessInput return true
  // or false? If data is for a future encryption level, it should be queued for
  // later?
  if (SSL_provide_quic_data(ssl(), TlsConnection::BoringEncryptionLevel(level),
                            reinterpret_cast<const uint8_t*>(input.data()),
                            input.size()) != 1) {
    // SSL_provide_quic_data can fail for 3 reasons:
    // - API misuse (calling it before SSL_set_custom_quic_method, which we
    //   call in the TlsHandshaker c'tor)
    // - Memory exhaustion when appending data to its buffer
    // - Data provided at the wrong encryption level
    //
    // Of these, the only sensible error to handle is data provided at the wrong
    // encryption level.
    //
    // Note: the error provided below has a good-sounding enum value, although
    // it doesn't match the description as it's a QUIC Crypto specific error.
    parser_error_ = QUIC_INVALID_CRYPTO_MESSAGE_TYPE;
    parser_error_detail_ = "TLS stack failed to receive data";
    return false;
  }
  AdvanceHandshake();
  return true;
}
  • 上述函數(shù)通過ssl引擎的SSL_provide_quic_data()Client HelloServer Hello輸入到ssl引擎诈泼,隨后調(diào)用AdvanceHandshake()函數(shù)觸發(fā)雙手握手操作懂拾。
  • 上述代碼最終觸發(fā)的流程圖如下:


    007.png
  • 至此為止,客戶端拿到了服務端的公鑰信息以及協(xié)商好的加密算法铐达,基于此為當前level創(chuàng)建對應的加密和解密引擎岖赋,注意圖(5)中ProcessInput(..,EncryptionLevel level)對應Initial包的levelENCRYPTION_INITIAL,而經(jīng)過ssl引擎握手處理后需要創(chuàng)建的加密和解密引擎的levelENCRYPTION_HANDSHAKE,也就是此階段創(chuàng)建的解密引擎為Handshake包解密提供服務。
  • 加解密引擎的創(chuàng)建在google quic項目中使用回調(diào)的方式瓮孙,通知上層進行創(chuàng)建,它們最終實現(xiàn)請參考google quic tls握手原理(一)

四唐断、客戶端處理服務端的Handshake證書以及證書驗證

  • Handshake包的處理流程會比Initial包的處理流程多一個步驟,就是證書校驗工作杭抠,大致流程如下:
    008.png
  • 在google quic tls握手原理(一)一文中有提到脸甘,客戶端創(chuàng)建SSL_CTX的時候通過SSL_CTX_set_custom_verify(ssl_ctx.get(), SSL_VERIFY_PEER, &VerifyCallback)自定了證書校驗函數(shù),其實現(xiàn)如下:
enum ssl_verify_result_t TlsHandshaker::VerifyCert(uint8_t* out_alert) {
  if (verify_result_ != ssl_verify_retry ||
      expected_ssl_error() == SSL_ERROR_WANT_CERTIFICATE_VERIFY) {
    enum ssl_verify_result_t result = verify_result_;
    verify_result_ = ssl_verify_retry;
    *out_alert = cert_verify_tls_alert_;
    return result;
  }
  const STACK_OF(CRYPTO_BUFFER)* cert_chain = SSL_get0_peer_certificates(ssl());
  if (cert_chain == nullptr) {
    *out_alert = SSL_AD_INTERNAL_ERROR;
    return ssl_verify_invalid;
  }
  // TODO(nharper): Pass the CRYPTO_BUFFERs into the QUIC stack to avoid copies.
  std::vector<std::string> certs;
  for (CRYPTO_BUFFER* cert : cert_chain) {
    certs.push_back(
        std::string(reinterpret_cast<const char*>(CRYPTO_BUFFER_data(cert)),
                    CRYPTO_BUFFER_len(cert)));
  }

  ProofVerifierCallbackImpl* proof_verify_callback =
      new ProofVerifierCallbackImpl(this);

  cert_verify_tls_alert_ = *out_alert;
  QuicAsyncStatus verify_result = VerifyCertChain(
      certs, &cert_verify_error_details_, &verify_details_,
      &cert_verify_tls_alert_,
      std::unique_ptr<ProofVerifierCallback>(proof_verify_callback));
  switch (verify_result) {
    case QUIC_SUCCESS:
      if (verify_details_) {
        OnProofVerifyDetailsAvailable(*verify_details_);
      }
      return ssl_verify_ok;
    case QUIC_PENDING:
      proof_verify_callback_ = proof_verify_callback;
      set_expected_ssl_error(SSL_ERROR_WANT_CERTIFICATE_VERIFY);
      return ssl_verify_retry;
    case QUIC_FAILURE:
    default:
      *out_alert = cert_verify_tls_alert_;
      QUIC_LOG(INFO) << "Cert chain verification failed: "
                     << cert_verify_error_details_;
      return ssl_verify_invalid;
  }
}
  • SSL_get0_peer_certificates()用于獲取與當前 SSL/TLS 連接關聯(lián)的對等端證書偏灿。
  • 然后調(diào)用VerifyCertChain(...)進行證書校驗丹诀,該函數(shù)是個抽象函數(shù),在子類TlsClientHandshaker中實現(xiàn)翁垂。
  • 當證書校驗成功后铆遭,握手成功,繼而繼續(xù)根據(jù)為ssl_encryption_application也就是ENCRYPTION_FORWARD_SECURE類型的leve創(chuàng)建對應的加密和解密引擎沿猜,為后續(xù)應用數(shù)據(jù)使用疚脐。
QuicAsyncStatus TlsClientHandshaker::VerifyCertChain(
    const std::vector<std::string>& certs, std::string* error_details,
    std::unique_ptr<ProofVerifyDetails>* details, uint8_t* out_alert,
    std::unique_ptr<ProofVerifierCallback> callback) {
  const uint8_t* ocsp_response_raw;
  size_t ocsp_response_len;
  SSL_get0_ocsp_response(ssl(), &ocsp_response_raw, &ocsp_response_len);
  std::string ocsp_response(reinterpret_cast<const char*>(ocsp_response_raw),
                            ocsp_response_len);
  const uint8_t* sct_list_raw;
  size_t sct_list_len;
  SSL_get0_signed_cert_timestamp_list(ssl(), &sct_list_raw, &sct_list_len);
  std::string sct_list(reinterpret_cast<const char*>(sct_list_raw),
                       sct_list_len);

  return proof_verifier_->VerifyCertChain(
      server_id_.host(), server_id_.port(), certs, ocsp_response, sct_list,
      verify_context_.get(), error_details, details, out_alert,
      std::move(callback));
}
  • proof_verifier_TlsClientHandshaker的成員變量,在其構(gòu)造函數(shù)中被初始化
class QUIC_EXPORT_PRIVATE TlsClientHandshaker
    : public TlsHandshaker,
      public QuicCryptoClientStream::HandshakerInterface,
      public TlsClientConnection::Delegate {
  ....
 private: 
  ....
  // Objects used for verifying the server's certificate chain.
  // |proof_verifier_| is owned by the caller of TlsHandshaker's constructor.
  ProofVerifier* proof_verifier_;
};
  • 對于客戶端可以不對服務端的證書進行校驗邢疙,此時可以使用google quiche項目中默認提供的FakeProofVerifier,該模塊實現(xiàn)VerifyCertChain接口棍弄,并且內(nèi)部什么都不做,直接返回成功疟游。
  • 到此為止,從客戶端的角度來看呼畸,握手就已經(jīng)基本完成了,并已為后續(xù)的application data 數(shù)據(jù)傳輸創(chuàng)建了加解密引擎颁虐。
  • 其中proof_verifier_成員在客戶端初始化的時候進行實現(xiàn),最終被markQuicCryptoClientConfig模塊蛮原,在全代碼上下文中使用。
  • 到此為止,如果是證書驗證成功,握手就算完成了,接下來看握手完成后做了哪些處理另绩?

五儒陨、客戶端完成握手后處理

  • 握手代碼邏輯如下:
void TlsHandshaker::AdvanceHandshake() {
  ...
  int rv = SSL_do_handshake(ssl());
  ....
  if (rv == 1) {
    FinishHandshake();
    return;
  }
 ....
}
  • 首先客戶端調(diào)用SSL_do_handshake()函數(shù)觸發(fā)握手花嘶,當收到服務端的Initial+handshake報文并相關信息驗證通過后,咱門分析假設握手成功蹦漠,那么最后會調(diào)用FinishHandshake()進行相關的參數(shù)配置處理椭员。
void TlsClientHandshaker::FinishHandshake() {
  //1) 填充握手參數(shù)
  FillNegotiatedParams();
  //2) 處理傳輸參數(shù)
  std::string error_details;
  if (!ProcessTransportParameters(&error_details)) {
    CloseConnection(QUIC_HANDSHAKE_FAILED, error_details);
    return;
  }
  //3) 從加密擴展中選擇應用層協(xié)議(第二節(jié)中的圖1)
  const uint8_t* alpn_data = nullptr;
  unsigned alpn_length = 0;
  SSL_get0_alpn_selected(ssl(), &alpn_data, &alpn_length);

  if (alpn_length == 0) {
    CloseConnection(QUIC_HANDSHAKE_FAILED, "Server did not select ALPN");
    return;
  }
  //4) 和客戶端已經(jīng)選擇應用協(xié)議進行比較
  std::string received_alpn_string(reinterpret_cast<const char*>(alpn_data),
                                   alpn_length);
  std::vector<std::string> offered_alpns = session()->GetAlpnsToOffer();
  if (std::find(offered_alpns.begin(), offered_alpns.end(),
                received_alpn_string) == offered_alpns.end()) {
    CloseConnection(QUIC_HANDSHAKE_FAILED, "Client received mismatched ALPN");
    return;
  }
  //5) 通知更上層應用層協(xié)議已選擇
  session()->OnAlpnSelected(received_alpn_string);

  // Parse ALPS extension.
  const uint8_t* alps_data;
  size_t alps_length;
  SSL_get0_peer_application_settings(ssl(), &alps_data, &alps_length);
  if (alps_length > 0) {
    // 6) 通知更上層應用設置參數(shù)
    auto error = session()->OnAlpsData(alps_data, alps_length);
    if (error) {
      // Calling CloseConnection() is safe even in case OnAlpsData() has
      // already closed the connection.
      CloseConnection(
          QUIC_HANDSHAKE_FAILED,
          absl::StrCat("Error processing ALPS data: ", error.value()));
      return;
    }
  }

  state_ = HANDSHAKE_COMPLETE;
  // 7)通知QuicSession握手已完成
  handshaker_delegate()->OnTlsHandshakeComplete();
}
  • 應用協(xié)議和相關的應用設置參數(shù)在服務端的第一個握手包的加密擴展中被攜帶,這里在握手完成后再對這些參數(shù)和客戶端進行適配笛园,如果適配不成功則直接關閉連接隘击。
  • 最后通過回調(diào)OnAlpnSelected()、OnAlpsData()研铆、OnTlsHandshakeComplete()進一步通知上層進行相應的邏輯處理埋同,本文分析的重點是tls范疇,所以對上層不做深入分析棵红。
  • 到此為止凶赁、tls引擎就緒,應用層也就緒逆甜、整個握手過程就算完成了虱肄,同時客戶端也會想服務端發(fā)送handshake(加密擴展+finished)包,告訴服務端握手完成忆绰,而服務端收到該報文后,會向客戶端發(fā)送一個New Session Ticket的報文可岂,該報文是0-RTT的基礎错敢,且看下文分析。

六缕粹、客戶端處理服務端的Handshake New Session Ticket

  • 首先看一下抓包文件


    009.png
  • 圖(9)為客戶端發(fā)送給服務端的finished
    010.png
  • New Session Ticket信息是0-RTT的基礎稚茅,在創(chuàng)建SSL_CTX的時候(靜態(tài)的),在google quiche項目中通過如下方法配置了客戶端會緩存SSL_SESSION平斩。
// static
bssl::UniquePtr<SSL_CTX> TlsClientConnection::CreateSslCtx(
    bool enable_early_data) {
  bssl::UniquePtr<SSL_CTX> ssl_ctx = TlsConnection::CreateSslCtx();
  ....
  // Configure session caching.
  SSL_CTX_set_session_cache_mode(
      ssl_ctx.get(), SSL_SESS_CACHE_CLIENT | SSL_SESS_CACHE_NO_INTERNAL);
  SSL_CTX_sess_set_new_cb(ssl_ctx.get(), NewSessionCallback);
  ....
  return ssl_ctx;
}
  • 而當客戶端收到服務端發(fā)送過來的New Session Ticket報文之后,和上述流程處理一樣亚享,最后經(jīng)過ssl引擎處理后會觸發(fā)該回調(diào),即NewSessionCallback()..函數(shù)被回調(diào)。
  • 而經(jīng)過一系列的調(diào)用绘面,該函數(shù)的最終實現(xiàn)如下:
void TlsClientHandshaker::InsertSession(bssl::UniquePtr<SSL_SESSION> session) {
  //1) 第5節(jié)已經(jīng)解析并處理
  if (!received_transport_params_) {
    QUIC_BUG(quic_bug_10576_8) << "Transport parameters isn't received";
    return;
  }
  // 2) 初始化的時候配置是否支持緩存
  if (session_cache_ == nullptr) {
    QUIC_DVLOG(1) << "No session cache, not inserting a session";
    return;
  }
  // 3) has_application_state_是個bool值,在構(gòu)造TlsClientHandshaker的時候會傳入,表示是否有應用狀態(tài)
  // received_application_state_當收到服務端的SettingFrame后會被初始化,由于本文分析的是RAW DATA流,所以抓包文件中未能體現(xiàn)
  if (has_application_state_ && !received_application_state_) {
    // Application state is not received yet. cache the sessions.
    if (cached_tls_sessions_[0] != nullptr) {
      cached_tls_sessions_[1] = std::move(cached_tls_sessions_[0]);
    }
    cached_tls_sessions_[0] = std::move(session);
    return;
  }
  // 4) 將SSL_SESSION插入到session_cache_集合進行緩存
  session_cache_->Insert(server_id_, std::move(session),
                         *received_transport_params_,
                         received_application_state_.get());
}
  • 上面的代碼注釋說得已經(jīng)很清晰欺税,需要注意的是第3)點的處理邏輯,本文以RAW DATA進行分析,所以在抓包文件中未能體驗0-RTT包,同時在#3會直接return掉,received_application_state_https3 demo中當收到Setting Frame的時候會被實例化揭璃。
  • 而對于https請求,google demo可以配置支持0-RTT晚凿,所以到這里會將SSL_SESSION插入到session_cache_
  • 至于0-RTT的詳細原理瘦馍,后續(xù)再寫一遍文章進行分析歼秽。
  • 服務端發(fā)送New Session Ticket包后會繼續(xù)發(fā)送一個HANDSHAKE_DONENEW_TOKEN的包,接下來我們繼續(xù)進行分析情组。

七燥筷、客戶端處理HANDSHAKE_DONE和NEW_TOKEN箩祥、NEW_CONNECTION_ID

  • HANDSHAKE_DONENEW_TOKENShort Header包,客戶端收到后依然需要通過QuicClientCryptoStream進行處理,結(jié)合第2節(jié)中的圖(0)進行分析肆氓。
  • 首先我們看一下抓包文件:


    011.png

HANDSHAKE_DONE處理

void QuicSession::OnHandshakeDoneReceived() {
  GetMutableCryptoStream()->OnHandshakeDoneReceived();
}

void QuicCryptoClientStream::OnHandshakeDoneReceived() {
  handshaker_->OnHandshakeDoneReceived();
}

void TlsClientHandshaker::OnHandshakeDoneReceived() {
  if (!one_rtt_keys_available()) {
    CloseConnection(QUIC_HANDSHAKE_FAILED,
                    "Unexpected handshake done received");
    return;
  }
  OnHandshakeConfirmed();
}

void TlsClientHandshaker::OnHandshakeConfirmed() {
  QUICHE_DCHECK(one_rtt_keys_available());
  if (state_ >= HANDSHAKE_CONFIRMED) {
    return;
  }
  state_ = HANDSHAKE_CONFIRMED;
  handshaker_delegate()->DiscardOldEncryptionKey(ENCRYPTION_HANDSHAKE);
  handshaker_delegate()->DiscardOldDecryptionKey(ENCRYPTION_HANDSHAKE);
}
  • HANDSHAKE_DONE的處理比較簡單,對于TLS引擎來說只是將state_設置成HANDSHAKE_CONFIRMED袍祖。
  • 當然在QuicConnection模塊中也會根據(jù)不同的Frame做出一些邏輯處理,本文不做分析做院。

NEW_TOKEN處理

void QuicSession::OnNewTokenReceived(absl::string_view token) {
  GetMutableCryptoStream()->OnNewTokenReceived(token);
}

void QuicCryptoClientStream::OnNewTokenReceived(absl::string_view token) {
  handshaker_->OnNewTokenReceived(token);
}

void TlsClientHandshaker::OnNewTokenReceived(absl::string_view token) {
  if (token.empty()) {
    return;
  }
  if (session_cache_ != nullptr) {
    session_cache_->OnNewTokenReceived(server_id_, token);
  }
}
  • 客戶端對TOKEN進行緩存,它是用來干嘛的盲泛?看上去是用于0-RTT用的。

NEW_CONNECTION_ID處理

  • NEW_CONNECTION_ID幀是屬于探測幀键耕,這個連接ID可用于連接遷移使用,本文不做詳細分析寺滚。

總結(jié)

  • 本文結(jié)合抓包文件以及配合代碼詳細分析quic握手流程,通過本文的學習相信大家對quic的握手已經(jīng)有了一個比較清晰的流程屈雄。
  • 本文都是圍繞客戶端端的代碼進行分析,并沒有對服務端的代碼進行分析村视,在服務端大部分流程是一樣的,只不過部分業(yè)務方面會有差異酒奶。
  • 本文也未詳細分析業(yè)務層的代碼蚁孔,只是了解到當握手成功后會觸發(fā)哪些回調(diào)(OnAlpnSelected()、OnAlpsData()惋嚎、OnTlsHandshakeComplete())杠氢,后續(xù)根據(jù)實際應用有需要的時候再進行分析。
  • 同時本文還遺留0-RTT的實現(xiàn)原理另伍、quic連接遷移的實現(xiàn)等都未進行分析鼻百。
  • 本文在開篇給出了一張客戶端對服務端數(shù)據(jù)報文讀取、解析摆尝、以及各模塊分發(fā)的流程圖温艇,通過該流程圖來清晰的定位google quiche項目的各模塊代碼設計模式等。

參考文獻

?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
禁止轉(zhuǎn)載,如需轉(zhuǎn)載請通過簡信或評論聯(lián)系作者讯检。
  • 序言:七十年代末琐鲁,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子人灼,更是在濱河造成了極大的恐慌绣否,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,270評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件挡毅,死亡現(xiàn)場離奇詭異蒜撮,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,489評論 3 395
  • 文/潘曉璐 我一進店門段磨,熙熙樓的掌柜王于貴愁眉苦臉地迎上來取逾,“玉大人,你說我怎么就攤上這事苹支±纾” “怎么了?”我有些...
    開封第一講書人閱讀 165,630評論 0 356
  • 文/不壞的土叔 我叫張陵债蜜,是天一觀的道長晴埂。 經(jīng)常有香客問我,道長寻定,這世上最難降的妖魔是什么儒洛? 我笑而不...
    開封第一講書人閱讀 58,906評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮狼速,結(jié)果婚禮上琅锻,老公的妹妹穿的比我還像新娘。我一直安慰自己向胡,他們只是感情好恼蓬,可當我...
    茶點故事閱讀 67,928評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著僵芹,像睡著了一般处硬。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上拇派,一...
    開封第一講書人閱讀 51,718評論 1 305
  • 那天荷辕,我揣著相機與錄音,去河邊找鬼攀痊。 笑死桐腌,一個胖子當著我的面吹牛拄显,可吹牛的內(nèi)容都是我干的苟径。 我是一名探鬼主播,決...
    沈念sama閱讀 40,442評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼躬审,長吁一口氣:“原來是場噩夢啊……” “哼棘街!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起承边,我...
    開封第一講書人閱讀 39,345評論 0 276
  • 序言:老撾萬榮一對情侶失蹤遭殉,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后博助,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體险污,經(jīng)...
    沈念sama閱讀 45,802評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,984評論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了蛔糯。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片拯腮。...
    茶點故事閱讀 40,117評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖蚁飒,靈堂內(nèi)的尸體忽然破棺而出动壤,到底是詐尸還是另有隱情,我是刑警寧澤淮逻,帶...
    沈念sama閱讀 35,810評論 5 346
  • 正文 年R本政府宣布琼懊,位于F島的核電站,受9級特大地震影響爬早,放射性物質(zhì)發(fā)生泄漏哼丈。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,462評論 3 331
  • 文/蒙蒙 一凸椿、第九天 我趴在偏房一處隱蔽的房頂上張望削祈。 院中可真熱鬧,春花似錦脑漫、人聲如沸髓抑。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,011評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽吨拍。三九已至,卻和暖如春网杆,著一層夾襖步出監(jiān)牢的瞬間羹饰,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,139評論 1 272
  • 我被黑心中介騙來泰國打工碳却, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留队秩,地道東北人。 一個月前我還...
    沈念sama閱讀 48,377評論 3 373
  • 正文 我出身青樓昼浦,卻偏偏與公主長得像馍资,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子关噪,可洞房花燭夜當晚...
    茶點故事閱讀 45,060評論 2 355

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