懂了,原來 OkHttp 是這樣建立 HTTPS 連接的

目錄:

  1. 概述
  2. 基礎
    2.1. 加密
    2.2. 數字簽名
    2.3. 數字證書
  3. TLS 原理
  4. 主要的類和接口
    4.1. JDK
    4.2. OkHttp
  5. 源碼分析
    5.1. 創建安全 Socket
    5.2. 配置
    5.3. 握手
    5.4. 驗證
    5.5. 完成
  6. 應用實例
    6.1. 信任所有證書
    6.2. 信任自簽名證書
    6.4. 自定義 TLS 連接規格
    6.5. 使用證書鎖定
  7. 資料

1. 概述

TLS 是進行 HTTPS 連接的重要環節,通過了 TLS 層進行協商,后續的 HTTP 請求就可以使用協商好的對稱密鑰進行加密

SSL 是 Netscape 開發的專門用來保護 Web 通訊,目前版本為 3.0。TLS 是 IETF 制定的新協議,建立在 SSL 3.0 之上。所以 TLS 1.0 可以認為是 SSL 3.1

TLS(Transport Layer Security Protocol) 協議分為兩部分

  • TLS 記錄協議
  • TLS 握手協議

2. 基礎

2.1. 加密

2.1.1. 對稱密鑰加密

編碼和解碼使用同一個密鑰,e = d

加密算法有

  • DES
  • Triple-DES
  • RC2
  • RC4(在 OkHttp 2.3 已經下降支持)

位數越多,枚舉攻擊花費的時間越長

痛點:發送者和接收者建立對話前,需要一個共享密鑰

2.1.2. 非對稱密鑰加密

兩個密鑰,一個加密,一個解密。私鑰持有,公鑰公開

  • RSA

破解私鑰的難度相當于對極大數進行因式分解

RSA 加密系統中,D 和 E 會相互抵消

E(D(stuff)) = stuff
D(E(stuff)) = stuff

所以具體哪個是私鑰,哪個是公鑰是由用戶選擇的

2.2 數字簽名

加了密的校驗和

  • 證明是原作者,只有原作者可以私鑰來進行加密
  • 證明沒有篡改,中途篡改校驗和就不再匹配

校驗和使用摘要算法生成,比如 MD5,SHA

2.3. 數字證書

受信任組織擔保的用戶或公司的信息,沒有統一的標準

服務端大部分使用 x509 v3 派生證書,主要信息有

字段 舉例
證書序列號 12:34:56:78
證書過期時間 Wed,Sep 17,2017
站點組織名 StevenLee
站點DNS主機名 steven-lee.me
站點公鑰 xxxx
證書頒發者 RSA Data Security
數字簽名 xxxx

服務端把證書(內含服務端的公鑰)發給客戶端,客戶端使用頒布證書的機構的公鑰來解密,檢查數字簽名,取出公鑰。取出服務端的公鑰,將后面請求用的對稱密鑰 X 傳遞給服務端,后面就用該密鑰進行加密傳輸信息

3. TLS 原理

HTTPS 是在 HTTP 和 TCP 之間加了一層 TLS,這個 TLS 協商了一個對稱密鑰來進行 HTTP 加密

TLS

同時,SSL/TLS 不僅僅可以用在 HTTP,也可以用在 FTP,Telnet 等應用層協議上。

SSL/TLS 實際上混合使用了對稱和非對稱密鑰,主要分成這幾步:

使用非對稱密鑰建立安全的通道

  • 客戶端請求 Https 連接,發送可用的 TLS 版本和可用的密碼套件
  • 服務端返回證書,密碼套件和 TLS 版本

用安全的通道產生并發送臨時的隨機對稱密鑰

  • 生成隨機對稱密鑰,使用證書中的服務端公鑰加密,發送給服務端
  • 服務端使用私鑰解密獲取對稱密鑰

使用對稱密鑰加密信息,進行交互

簡化后的流程圖如下:

TLS 握手

詳細的流程圖如下:

SSL Messages

4. 主要的類和接口

4.1. JDK

主要由 JDK 的 java.security,javax.net 和 javax.net.ssl 提供的

  • SSLSocketFactory
  • SSLSocket
  • SSLSession
  • TrustManager
    • X509TrustManager
  • Certificate
    • X509Certificate
  • HostNameVerifier

核心類的關系圖

核心類圖

4.2. OkHttp

  • RealConnection
  • ConnectionSpecSelector
  • ConnectionSpec
  • CipherSuite
  • CertificatePinner

5. 源碼分析

連接的所有實現,在 RealConnection 中。如果沒有從 ConnectionPool 復用,創建新的連接過程,見 RealConnection.buildConnection:

private void buildConnection(int connectTimeout, int readTimeout, int writeTimeout, ConnectionSpecSelector connectionSpecSelector) throws IOException {
    connectSocket(connectTimeout, readTimeout);
    establishProtocol(readTimeout, writeTimeout, connectionSpecSelector);
}
  • connectSocket ,三次握手,創建 TCP 連接。

  • establishProtocol ,在 TCP 連接的基礎上,開始根據不同版本的協議,來完成連接過程。主要有 HTTP/1.1,HTTP/2 和 SPDY 協議。如果是 HTTPS 類型的,則開始 TLS 建聯。

  private void establishProtocol(int readTimeout, int writeTimeout,
      ConnectionSpecSelector connectionSpecSelector) throws IOException {
    if (route.address().sslSocketFactory() != null) {
      connectTls(readTimeout, writeTimeout, connectionSpecSelector);
    } else {
      protocol = Protocol.HTTP_1_1;
      socket = rawSocket;
    }
    ... 
  }

只關注 TLS 連接過程

 private void connectTls(int readTimeout, int writeTimeout,
      ConnectionSpecSelector connectionSpecSelector) throws IOException {
    Address address = route.address();
    SSLSocketFactory sslSocketFactory = address.sslSocketFactory();
    boolean success = false;
    SSLSocket sslSocket = null;
    try {
      // Create the wrapper over the connected socket.
      sslSocket = (SSLSocket) sslSocketFactory.createSocket(
          rawSocket, address.url().host(), address.url().port(), true /* autoClose */);

      // Configure the socket's ciphers, TLS versions, and extensions.
      ConnectionSpec connectionSpec = connectionSpecSelector.configureSecureSocket(sslSocket);
      if (connectionSpec.supportsTlsExtensions()) {
        Platform.get().configureTlsExtensions(
            sslSocket, address.url().host(), address.protocols());
      }

      // Force handshake. This can throw!
      sslSocket.startHandshake();
      Handshake unverifiedHandshake = Handshake.get(sslSocket.getSession());

      // Verify that the socket's certificates are acceptable for the target host.
      if (!address.hostnameVerifier().verify(address.url().host(), sslSocket.getSession())) {
        X509Certificate cert = (X509Certificate) unverifiedHandshake.peerCertificates().get(0);
        throw new SSLPeerUnverifiedException("Hostname " + address.url().host() + " not verified:"
            + "\n    certificate: " + CertificatePinner.pin(cert)
            + "\n    DN: " + cert.getSubjectDN().getName()
            + "\n    subjectAltNames: " + OkHostnameVerifier.allSubjectAltNames(cert));
      }

      // Check that the certificate pinner is satisfied by the certificates presented.
      address.certificatePinner().check(address.url().host(),
          unverifiedHandshake.peerCertificates());

      // Success! Save the handshake and the ALPN protocol.
      String maybeProtocol = connectionSpec.supportsTlsExtensions()
          ? Platform.get().getSelectedProtocol(sslSocket)
          : null;
      socket = sslSocket;
      source = Okio.buffer(Okio.source(socket));
      sink = Okio.buffer(Okio.sink(socket));
      handshake = unverifiedHandshake;
      protocol = maybeProtocol != null
          ? Protocol.get(maybeProtocol)
          : Protocol.HTTP_1_1;
      success = true;
    } catch (AssertionError e) {
      if (Util.isAndroidGetsocknameError(e)) throw new IOException(e);
      throw e;
    } finally {
      if (sslSocket != null) {
        Platform.get().afterHandshake(sslSocket);
      }
      if (!success) {
        closeQuietly(sslSocket);
      }
    }
  }

5.1. 創建安全 Socket

這里的安全 Socket 就是 SSLSocket,是握手成功后的 TCP Socket 進行的封裝。

如果 SSLSocketFactory 沒有自定義配置的話,會使用 OkHttp 的默認創建。比如在 OkHttpClient 中有這樣的代碼來構造默認的 SSLSocketFactory

      X509TrustManager trustManager = systemDefaultTrustManager();
      this.sslSocketFactory = systemDefaultSslSocketFactory(trustManager);
      this.certificateChainCleaner = CertificateChainCleaner.get(trustManager);

systemDefaultSslSocketFactory 方法使用 SSLContext 來構造 SSLSocketFactory

  private SSLSocketFactory systemDefaultSslSocketFactory(X509TrustManager trustManager) {
    try {
      SSLContext sslContext = SSLContext.getInstance("TLS");
      sslContext.init(null, new TrustManager[] { trustManager }, null);
      return sslContext.getSocketFactory();
    } catch (GeneralSecurityException e) {
      throw new AssertionError(); // The system has no TLS. Just give up.
    }
  }

這樣就是用了系統默認的 X509TrustManager。

該 SSLSocketFactory 為系統 SDK 提供,包括它生產的 SSLSocket,所以和系統平臺版本強相關,底層為 OpenSSL 庫。對 TLS 版本的支持情況不一樣,接口也有所不同。

SSLSocket 配置信息有兩大類:

  • 支持的 TLS 協議
  • 支持的密碼套件(CipherSuite)

OkHttp 不包括自己的 SSL/TLS 庫,所以 SSLSocket 使用 Android 提供的標準 SSLSocket

5.2. 配置

經過上面創建過程后,SSLSocket 已經有了一些操作系統提供的默認配置。但不完全安全,OkHttp 會有自己的連接規格,來過濾掉過時的 TLS 版本和弱密碼套件。

OkHttp 內置了三套規格,

  • ConnectionSepc.MODEN_TLS, 現代的 TLS 配置。
  • ConnectionSpec.COMPATIABLE_TLS,不是現代的,但安全 TLS 配置。
  • ConnectionSpec.CLEARTEXT, 不安全的 TLS 配置。

這三套規格跟著版本走,例如,在OkHttp 2.2,下降支持響應POODLE攻擊的SSL 3.0。而在OkHttp 2.3 下降的支持RC4

所以與桌面Web瀏覽器,保持最新的OkHttp是保持安全的最好辦法

OkHttp 還會通過反射的方式,來對 SSLSocket 的 TLS 的擴展功能進行配置

  • SNI 和 Session tickets
  • ALPN

OkHttp 會先使用現代的規格(ConnectionSepc.MODEN_TLS)進行連接,如果失敗會采用回退策略選擇下一個。

5.2.1. TLS 連接規格選擇

該步驟選擇適合客戶端的 TLS連接規格。一個很大的作用,就是盡可能地使用高版本的 TLS,和最新的密碼套件,來提供最安全的連接。

連接規格都封裝在 ConnectionSpec 中,主要內容就是 TLS 版本和密碼套件

連接規格選擇的策略由 ConnectSpecSelector 進行,默認使用 OkHttp 的三套規格

最后會調用 ConnectionSpec 的 apply 方法,來配置 SSLSocket

/** Applies this spec to {@code sslSocket}. */
void apply(SSLSocket sslSocket, boolean isFallback) {
    ConnectionSpec specToApply = supportedSpec(sslSocket, isFallback);

    if (specToApply.tlsVersions != null) {
        sslSocket.setEnabledProtocols(specToApply.tlsVersions);
    }
    if (specToApply.cipherSuites != null) {
        sslSocket.setEnabledCipherSuites(specToApply.cipherSuites);
    }
}

在 supportedSpec 方法中,會對選擇好的規格,和 SSLSocket 可用的配置取中交集,過濾掉那些不安全的低版本的 TLS 和弱密碼套件和 SSLSocket 不支持的配置。

這個階段后,SSLSocket 中的一些不安全的 TLS 版本和弱密碼套件就被過濾了,將會使用 OkHttp 配置規范中認為的安全版本和強密碼套件開始正式的握手過程。

5.2.2. TLS 連接規格回退

最開始會嘗試現代的 TLS 規格,如果不支持的話,會有回退策略(Fallback Strategy),回退到非現代但安全的 TLS 規格

回退策略由 RealConnection 和 ConnectSpecSelector 一起配合提供。

比如它會先選擇最新的 ConnectionSpec.MODEN_TLS,不支持的話,再更換為 ConnectionSpec.COMPATIABLE_TLS,最后選擇 ConnectionSpec.CLEARTEXT

策略很簡單,就是連接失敗的時候,更換下一套規范重新進行連接。

5.2.3. TLS 擴展配置

Android 平臺,最終在 AndroidPlatform.configureTlsExtensions 來完成配置

@Override public void configureTlsExtensions(
    SSLSocket sslSocket, String hostname, List<Protocol> protocols) {
  // Enable SNI and session tickets.
  if (hostname != null) {
    setUseSessionTickets.invokeOptionalWithoutCheckedException(sslSocket, true);
    setHostname.invokeOptionalWithoutCheckedException(sslSocket, hostname);
  }

  // Enable ALPN.
  if (setAlpnProtocols != null && setAlpnProtocols.isSupported(sslSocket)) {
    Object[] parameters = {concatLengthPrefixed(protocols)};
    setAlpnProtocols.invokeWithoutCheckedException(sslSocket, parameters);
  }
}

因為某些手機機型是支持 TLS 擴展的,OkHttp 采用發射的方式嘗試加載擴展,讓這些機型的擴展配置生效。

如果 ConectionSpec 支持 TLS 的擴展,這里還會配置 SNI,session tickets 和 ALPN。

5.3. 握手

調用 SSLSocket.startHandShake 開始進行握手:

// Force handshake. This can throw!
sslSocket.startHandshake();
Handshake unverifiedHandshake = Handshake.get(sslSocket.getSession());

這里客戶端正式向服務端發出數據包,內容為可選擇的密碼和請求證書。服務端會返回相應的密碼套件,tls 版本,節點證書,本地證書等等,然后封裝在 Handshake 類中

主要內容有:

  • CipherSuite, 密碼套件。
  • TlsVersion, TLS 版本。
  • Certificate[] peerCertificates, 站點的證書。
  • Certificate[] localCertificates, 本地的證書。一些安全級別更高的應用,會使用雙向的證書認證。

該過程中,SSLSocket 內部會對服務端返回的 Certificate 進行判斷,是否是可信任的 CA 發布的。如果不是的話,會拋出異常

5.4. 驗證

到了這一步,服務端返回的證書已經被系統所信任,也就是頒發的機構 CA 在系統的可信任 CA 列表中了。但是為了更加安全,還會進行以下兩種驗證。

5.4.1. 站點身份驗證

使用 HostnameVerifier 來驗證 host 是否合法,如果不合法會拋出 SSLPeerUnverifiedException

默認的實現是 OkHostnameVerifier.verify :

  public boolean verify(String host, SSLSession session) {
    try {
      Certificate[] certificates = session.getPeerCertificates();
      return verify(host, (X509Certificate) certificates[0]);
    } catch (SSLException e) {
      return false;
    }
  }

具體的驗證策略比較簡單,主要是檢查證書里的 IP 和 hostname 是否是我們的目標地址

5.4.2. 證書鎖定(Certificate Pinner)

到了該階段,證書已經被信任,是屬于平臺的可信任證書授權機構(CA)的。但是這個會受到證書頒發機構的攻擊,比如 2011 DigiNotar 的攻擊。

所以,還可以使用 CertificatePinner 來鎖定,哪些證書和 CA 是可信任的。

缺點,限制了服務端更新 TLS 證書的能力,所以證書鎖定一定要經過服務端管理員的同意。

5.5. 完成

成功創建,保存這些信息:

  • Socket,安全的連接。
  • Handshake,握手信息。
  • Protocol,使用的 HTTP 協議。

后面和服務端的交互,都會被 TLS 過程中協商好的對稱密鑰進行加密。

6. 應用實例

6.1. 信任所有證書

  • 跳過系統檢驗,不再使用系統默認的 SSLSocketFactory
  • 自定義 TrustManager,信任所有證書
X509TrustManager trustManager = new X509TrustManager() {
    @Override
    public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
    }

    @Override
    public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
    }

    @Override
    public X509Certificate[] getAcceptedIssuers() {
        return new X509Certificate[0];
    }
};

SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, new TrustManager[]{trustManager}, null);
SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();

OkHttpClient client = new OkHttpClient.Builder()
                        .sslSocketFactory(sslSocketFactory, trustManager)
                        .build();

Request request = new Request.Builder()
                    .url("https://kyfw.12306.cn/otn/")                    
                    .build();

Call call = client.newCall(request);
Response response = call.execute();

Logger.d("response " + response.code());

response.close();

6.2. 信任自簽名證書

還是以 12306 來進行測試,先從官網上下載證書 srca.cer

  • 將自簽名證書,比如 12306 的 srca.cer,保存到 assets
  • 讀取自簽名證書集合,保存到 KeyStore 中
  • 使用 KeyStore 構建 X509TrustManager
  • 使用 X509TrustManager 初始化 SSLContext
  • 使用 SSLContext 創建 SSLSocketFactory
// 獲取自簽名證書集合,由證書工廠管理
InputStream inputStream = HttpsActivity.this.getAssets().open("srca.cer");
CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
Collection<? extends java.security.cert.Certificate> certificates = certificateFactory.generateCertificates(inputStream);
if (certificates.isEmpty()) {
    throw new IllegalArgumentException("expected non-empty set of trusted certificates");
}

// 將證書保存到 KeyStore 中
char[] password = "password".toCharArray();
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(null, password);
int index = 0;
for (Certificate certificate : certificates) {
    String certificateAlias = String.valueOf(index++);
    keyStore.setCertificateEntry(certificateAlias, certificate);
}

// 使用包含自簽名證書的 KeyStore 構建一個 X509TrustManager
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
keyManagerFactory.init(keyStore, password);
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init(keyStore);

TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
if (trustManagers.length != 1 || !(trustManagers[0] instanceof X509TrustManager)) {
    throw new IllegalStateException("Unexpected default trust managers:"
        + Arrays.toString(trustManagers));
}

// 使用 X509TrustManager 初始化 SSLContext
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, new TrustManager[]{trustManagers[0]}, null);
SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();

OkHttpClient client = new OkHttpClient.Builder()
                        .sslSocketFactory(sslSocketFactory, (X509TrustManager) trustManagers[0])
                        .build();

Request request = new Request.Builder()
                    .url("https://kyfw.12306.cn/otn/")
                    .build();

Call call = client.newCall(request);
Response response = call.execute();

Logger.d("response " + response.code());

response.close();

6.3. 自定義TLS連接規格

比如使用三個安全級別很高的密碼套件,并且限制 TLS 版本為 1_2

ConnectionSpec spec = new ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS)  
    .tlsVersions(TlsVersion.TLS_1_2)
    .cipherSuites(
          CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
          CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
          CipherSuite.TLS_DHE_RSA_WITH_AES_128_GCM_SHA256)
    .build();

OkHttpClient client = new OkHttpClient.Builder() 
    .connectionSpecs(Collections.singletonList(spec))
    .build();

該連接規格的配置是否能夠生效,還需要和 SSLSocket 的支持情況取交集,SSLSocket 不支持也就用不了

所以這三個密碼套件只能在 Android 5.0 以上的機子生效了

6.4. 使用證書鎖定

比如鎖定了指定 publicobject.com 的證書。

pin 的取值為,先對證書公鑰信息使用 SHA-256 或者 SHA-1 取哈希,然后進行 Base64 編碼,再加上 sha256 或者 sha1 的前綴。

這樣 publicobject.com 只能使用指定公鑰的證書了,安全性進一步提高,但靈活性降低:

CertificatePinner certificatePinner = new CertificatePinner.Builder()
    .add("publicobject.com", "sha256/afwiKY3RxoMmLkuRW1l7QsPZTJPwDS2pdDROQjXw8ig=")
    .add("publicobject.com", "sha256/klO23nT2ehFDXCfx3eHTDRESMz3asj1muO+4aIdjiuY=")
    .add("publicobject.com", "sha256/grX4Ta9HpZx6tSHkmCrvpApTQGo67CYDnvprLg5yRME=")
    .add("publicobject.com", "sha256/lCppFqbkrlJ3EcVFAkeip0+44VaoJUymbnOaEUk7tEU=")
    .build();

OkHttpClient client = new OkHttpClient.Builder()
    .certificatePinner(certificatePinner)
    .build();

7. 資料

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,546評論 6 533
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,570評論 3 418
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,505評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,017評論 1 313
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,786評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,219評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,287評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,438評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,971評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,796評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,995評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,540評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,230評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,662評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,918評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,697評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,991評論 2 374

推薦閱讀更多精彩內容