AFNetworking框架Https源码解析

公司项目网络框架一直使用AFNetworking,AFNetworking封装了对Https的处理策略和方法。今天来解读一下它的每一步操作是如何实现的。

客户端发送Https请求后,服务器会发送SSL证书给客户端,客户端在质询的代理方法中验证服务器发来的证书,决定信任证书或者取消网络请求。

一、NSURLSession处理质询的代理方法
/* 如果实现此代理,当发生连接级身份验证质询时,此代理将被给与机会提供身份验证证书给基础连接。某些身份验证类型将应用于与服务器的给定连接上的多个请求(SSL服务器信任质询)。如果未实现此委托消息,则行为将使用默认处理,这可能涉及用户交互。
 */
- (void)URLSession:(NSURLSession *)session
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
 completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler

这个代理方法中涉及到的三个参数
1.NSURLAuthenticationChallenge

@interface NSURLAuthenticationChallenge : NSObject <NSSecureCoding>
@property (readonly, copy) NSURLProtectionSpace *protectionSpace;
@property (nullable, readonly, copy) NSURLCredential *proposedCredential;
@property (readonly) NSInteger previousFailureCount;
@property (nullable, readonly, copy) NSURLResponse *failureResponse;
@property (nullable, readonly, copy) NSError *error;
@property (nullable, readonly, retain) id<NSURLAuthenticationChallengeSender> sender;
@end

2.NSURLSessionAuthChallengeDisposition

typedef NS_ENUM(NSInteger, NSURLSessionAuthChallengeDisposition) {
    NSURLSessionAuthChallengeUseCredential = 0,                                       /* 使用指定证书,证书也许为nil */
    NSURLSessionAuthChallengePerformDefaultHandling = 1,                              /* 对质询的默认处理-就像未实现此代理一样;忽略证书参数。 */
    NSURLSessionAuthChallengeCancelAuthenticationChallenge = 2,                       /* 整个请求将被取消,证书参数将被忽略。 */
    NSURLSessionAuthChallengeRejectProtectionSpace = 3,                               /* 此质询被拒绝,应尝试下一个身份验证保护空间;忽略证书参数 */
} NS_ENUM_AVAILABLE(NSURLSESSION_AVAILABLE, 7_0);

3.NSURLCredential
证书认证信息NSURLCredential官方文档阅读

@interface NSURLCredential : NSObject <NSSecureCoding, NSCopying>
@property (readonly) NSURLCredentialPersistence persistence;
@property (nullable, readonly, copy) NSString *user;
@property (nullable, readonly, copy) NSString *password;
@property (readonly) BOOL hasPassword;
@property (nullable, readonly) SecIdentityRef identity;
@property (readonly, copy) NSArray *certificates API_AVAILABLE(macos(10.6), ios(3.0), watchos(2.0), tvos(9.0));
@end
二、AFSecurityPolicy

首先我们来看使用AFNetworking发送Https请求时如何配置证书验证策略,AFNetworking中对HTTPS的策略配置和对证书的验证由AFSecurityPolicy进行处理,你只需要设置AFSecurityPolicy的属性配置Https证书验证策略。

@interface AFSecurityPolicy : NSObject <NSSecureCoding, NSCopying>
/*AFSSLPinningMode有三种验证模式:
AFSSLPinningModeNone
这个模式表示不做SSL pinning,只跟浏览器一样在系统的信任机构列表里验证服务端返回的证书。若证书是信任机构签发的就会通过,若是自己服务器生成的证书就不会通过。
AFSSLPinningModeCertificate
这个模式表示用证书绑定方式验证证书,需要客户端保存有服务端的证书拷贝,这里验证分两步,第一步验证证书的域名有效期等信息,第二步是对比服务端返回的证书跟客户端返回的是否一致。
AFSSLPinningModePublicKey
这个模式同样是用证书绑定方式验证,客户端要有服务端的证书拷贝,只是验证时只验证证书里的公钥,不验证证书的有效期等信息。只要公钥是正确的,就能保证通信不会被窃听,因为中间人没有私钥,无法解开通过公钥加密的数据。
*/
@property (readonly, nonatomic, assign) AFSSLPinningMode SSLPinningMode;
//保存在客户端中的证书
@property (nonatomic, strong, nullable) NSSet <NSData *> *pinnedCertificates;
//是否允许无效证书(自建证书和过期证书)
@property (nonatomic, assign) BOOL allowInvalidCertificates;
//是否验证域名
@property (nonatomic, assign) BOOL validatesDomainName;
//验证证书方法
- (BOOL)evaluateServerTrust:(SecTrustRef)serverTrust
                  forDomain:(nullable NSString *)domain;
@end
三、AFURLSessionManager中对质询代理的处理
- (void)URLSession:(NSURLSession *)session
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
 completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler
{
    NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling;
    __block NSURLCredential *credential = nil;
    if (self.sessionDidReceiveAuthenticationChallenge) {//如果自定义了质询处理方法,使用自定义处理方法处理
        disposition = self.sessionDidReceiveAuthenticationChallenge(session, challenge, &credential);
    } else {//没有自定义质询处理方法使用AFN对的处理策略
        if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {//如果质询认证方法是服务器信任(使用challenge.protectionSpace.serverTrust验证证书)
            if ([self.securityPolicy evaluateServerTrust:challenge.protectionSpace.serverTrust forDomain:challenge.protectionSpace.host]) {//如果验证证书为成功
                credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
                if (credential) {//如果服务器传来的证书认证信息
                    disposition = NSURLSessionAuthChallengeUseCredential;
                } else {//服务器证书认证信息为空时,对质询的默认处理-就像未实现此代理一样;忽略证书参数。
                    disposition = NSURLSessionAuthChallengePerformDefaultHandling;
                }
            } else {//证书验证失败时,整个请求将被取消,证书参数将被忽略。
                disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge;
            }
        } else {//不对服务器证书进行验证时,采用对质询的默认处理-就像未实现此代理一样;忽略证书参数。
            disposition = NSURLSessionAuthChallengePerformDefaultHandling;
        }
    }

    if (completionHandler) {
        completionHandler(disposition, credential);
    }
}
四、AFSecurityPolicy验证证书的方法

1.- (BOOL)evaluateServerTrust:(SecTrustRef)serverTrust
forDomain:(NSString )domain

// 根据severTrust和domain来检查服务器端发来的证书是否可信
// 其中SecTrustRef是一个CoreFoundation类型,用于对服务器端传来的X.509证书评估的
1.  // 而我们都知道,数字证书的签发机构CA,在接收到申请者的资料后进行核对并确定信息的真实有效,然后就会制作一份符合[X.509](http://tools.ietf.org/html/rfc5280)标准的文件。证书中的证书内容包含的持有者信息和公钥等都是由申请者提供的,而数字签名则是CA机构对证书内容进行hash加密后得到的,而这个数字签名就是我们验证证书是否是有可信CA签发的数据。
- (BOOL)evaluateServerTrust:(SecTrustRef)serverTrust
                  forDomain:(NSString *)domain
{
    /*
self.allowInvalidCertificates==YES表示如果此处允许使用自建证书(服务器自己弄的CA证书,非官方),并且还想验证domain是否有效(self.validatesDomainName == YES),也就是说你想验证自建证书的domain是否有效。那么你必须使用pinnedCertificates(就是在客户端保存服务器端颁发的证书拷贝)才可以。但是你的SSLPinningMode为AFSSLPinningModeNone,表示你不使用SSL pinning,只跟浏览器一样在系统的信任机构列表里验证服务端返回的证书。所以当然你的客户端上没有你导入的pinnedCertificates,同样表示你无法验证该自建证书。所以都返回NO。最终结论就是要使用服务器端自建证书,那么就得将对应的证书拷贝到iOS客户端,并使用AFSSLPinningMode或AFSSLPinningModePublicKey
*/
    if (domain && self.allowInvalidCertificates && self.validatesDomainName && (self.SSLPinningMode == AFSSLPinningModeNone || [self.pinnedCertificates count] == 0)) {
        // https://developer.apple.com/library/mac/documentation/NetworkingInternet/Conceptual/NetworkingTopics/Articles/OverridingSSLChainValidationCorrectly.html
        NSLog(@"In order to validate a domain name for self signed certificates, you MUST use pinning.");
        return NO;
    }
    // 此处设置验证证书的策略
    NSMutableArray *policies = [NSMutableArray array];
    
    if (self.validatesDomainName) {
        // 如果需要验证domain,那么就使用SecPolicyCreateSSL函数创建验证策略,其中第一个参数为true表示验证整个SSL证书链,第二个参数传入domain,用于判断整个证书链上叶子节点表示的那个domain是否和此处传入domain一致
        [policies addObject:(__bridge_transfer id)SecPolicyCreateSSL(true, (__bridge CFStringRef)domain)];
    } else {
        // 如果不需要验证domain,就使用默认的BasicX509验证策略
        [policies addObject:(__bridge_transfer id)SecPolicyCreateBasicX509()];
    }
    // 为serverTrust设置验证策略,即告诉客户端如何验证serverTrust
    SecTrustSetPolicies(serverTrust, (__bridge CFArrayRef)policies);
    // 如果SSLPinningMode为 AFSSLPinningModeNone,表示你不使用SSL pinning,但是我允许自建证书,那么返回YES,或者使用AFServerTrustIsValid函数看看serverTrust是否可信任,如果信任,也返回YES
    if (self.SSLPinningMode == AFSSLPinningModeNone) {
        return self.allowInvalidCertificates || AFServerTrustIsValid(serverTrust);
    } else if (!AFServerTrustIsValid(serverTrust) && !self.allowInvalidCertificates) {
        // 既不允许自建证书,而且使用AFServerTrustIsValid函数又返回NO,那么该serverTrust就真的不能通过验证了
        return NO;
    }
    switch (self.SSLPinningMode) {
        // 理论上,上面那个部分已经解决了self.SSLPinningMode)为AFSSLPinningModeNone)等情况,所以此处再遇到,就直接返回NO
        case AFSSLPinningModeNone:
        default:
            return NO;
       // 这个模式表示用证书绑定(SSL Pinning)方式验证证书,需要客户端保存有服务端的证书拷贝
        // 注意客户端保存的证书存放在self.pinnedCertificates中
        case AFSSLPinningModeCertificate: {
            NSMutableArray *pinnedCertificates = [NSMutableArray array];
            for (NSData *certificateData in self.pinnedCertificates) {
               // 这里使用SecCertificateCreateWithData函数对本地的pinnedCertificates做一些处理,保证返回的证书都是DER编码的X.509证书
                [pinnedCertificates addObject:(__bridge_transfer id)SecCertificateCreateWithData(NULL, (__bridge CFDataRef)certificateData)];
            }
            // 将pinnedCertificates设置成需要参与验证的Anchor Certificate(锚点证书,通过SecTrustSetAnchorCertificates设置了参与校验锚点证书之后,假如验证的数字证书是这个锚点证书的子节点,即验证的数字证书是由锚点证书对应CA或子CA签发的,或是该证书本身,则信任该证书),具体就是调用SecTrustEvaluate来验证。
            SecTrustSetAnchorCertificates(serverTrust, (__bridge CFArrayRef)pinnedCertificates);
            if (!AFServerTrustIsValid(serverTrust)) {
                return NO;
            }
            // 服务器端的证书链,注意此处返回的证书链顺序是从叶节点到根节点
            NSArray *serverCertificates = AFCertificateTrustChainForServerTrust(serverTrust);
            // 从服务器端证书链的根节点往下遍历,看看是否有与客户端的绑定证书一致的,有的话,就说明服务器端是可信的。因为遍历顺序正好相反,所以使用reverseObjectEnumerator
            for (NSData *trustChainCertificate in [serverCertificates reverseObjectEnumerator]){
                if ([self.pinnedCertificates containsObject:trustChainCertificate]) {
                    return YES;
                }
          }
            
            return NO;
        }
        // AFSSLPinningModePublicKey模式同样是用证书绑定(SSL Pinning)方式验证,客户端要有服务端的证书拷贝,只是验证时只验证证书里的公钥,不验证证书的有效期等信息。只要公钥是正确的,就能保证通信不会被窃听,因为中间人没有私钥,无法解开通过公钥加密的数据。
        case AFSSLPinningModePublicKey: {
            NSUInteger trustedPublicKeyCount = 0;
            // 从serverTrust中取出服务器端传过来的所有可用的证书,并依次得到相应的公钥
            NSArray *publicKeys = AFPublicKeyTrustChainForServerTrust(serverTrust);
            // 依次遍历这些公钥,如果和客户端绑定证书的公钥一致,那么就给trustedPublicKeyCount加一
            for (id trustChainPublicKey in publicKeys) {
                for (id pinnedPublicKey in self.pinnedPublicKeys) {
                    if (AFSecKeyIsEqualToKey((__bridge SecKeyRef)trustChainPublicKey, (__bridge SecKeyRef)pinnedPublicKey)) {
                        trustedPublicKeyCount += 1;
                    }
                }
            }
          // trustedPublicKeyCount大于0说明服务器端中的某个证书和客户端绑定的证书公钥一致,认为服务器端是可信的
            return trustedPublicKeyCount > 0;
        }
    }
    
    return NO;
}

上述函数实现中调用了AFSecurityPolicy的私有方法(注意evaluateServerTrust:forDomain:方法是AFSecurityPolicy比较重要的公开方法),下面我来逐个分析相应的函数实现。
static BOOL AFServerTrustIsValid(SecTrustRef serverTrust)

static BOOL AFServerTrustIsValid(SecTrustRef serverTrust) {
    BOOL isValid = NO;
    SecTrustResultType result;
    // 对照下面的Require_noErr_Quiet函数解释,此处errorCode指的就是SecTrustEvaluate(serverTrust, &result)函数的返回值。如果serverTrust评估出错,那么就直接执行return isValid,默认isValid为NO。
    __Require_noErr_Quiet(SecTrustEvaluate(serverTrust, &result), _out);
    // 如果SecTrustEvaluate函数评估没出错,那么就看result的结果
    // 只有当result为kSecTrustResultUnspecified(此标志表示serverTrust评估成功,此证书也被暗中信任了,但是用户并没有显示地决定信任该证书),或者当result为kSecTrustResultProceed(此标志表示评估成功,和上面不同的是该评估得到了用户认可),这两者取其一就可以认为对serverTrust评估成功
    // 在下面有对result类型(SecTrustResultType)的简单讲解
    isValid = (result == kSecTrustResultUnspecified || result == kSecTrustResultProceed);
_out:
    return isValid;
}
// Require_noErr_Quiet是一个宏定义函数,表示如果errorCode不为0(0表示没有错误),那么就使用C语言中的go语句,跳到对应exceptionLabel地方开始执行代码。
#ifndef __Require_noErr_Quiet
    #define __Require_noErr_Quiet(errorCode, exceptionLabel)                      \
      do                                                                          \
      {                                                                           \
          if ( __builtin_expect(0 != (errorCode), 0) )                            \
          {                                                                       \
              goto exceptionLabel;                                                \
          }                                                                       \
      } while ( 0 )
#endif
 
// kSecTrustResultUnspecified和kSecTrustResultProceed都是SecTrustResultType类型
/** 
  SecTrustResultType中枚举值的含义包括两个方面:一个是指评估是否成功,另一个是指该评估结果是不是由用户决定的。对于是不是由用户决定的这个问题,上面kSecTrustResultUnspecified和kSecTrustResultProceed就是一个很好的例子
 */

static NSArray * AFCertificateTrustChainForServerTrust(SecTrustRef serverTrust)

// 获取到serverTrust中证书链上的所有证书
static NSArray * AFCertificateTrustChainForServerTrust(SecTrustRef serverTrust) {
    // 使用SecTrustGetCertificateCount函数t获取到serverTrust中需要评估的证书链中的证书数目,并保存到certificateCount中
    CFIndex certificateCount = SecTrustGetCertificateCount(serverTrust);
    NSMutableArray *trustChain = [NSMutableArray arrayWithCapacity:(NSUInteger)certificateCount];
    // 使用SecTrustGetCertificateAtIndex函数获取到证书链中的每个证书,并添加到trustChain中,最后返回trustChain
    for (CFIndex i = 0; i < certificateCount; i++) {
        SecCertificateRef certificate = SecTrustGetCertificateAtIndex(serverTrust, i);
        [trustChain addObject:(__bridge_transfer NSData *)SecCertificateCopyData(certificate)];
    }
    return [NSArray arrayWithArray:trustChain];
}

2. + [AFSecurityPolicy policyWithPinningMode:withPinnedCertificates:]
AFSecurityPolicy中还有一些关于初始化的函数,比较重要的就数+ [AFSecurityPolicy policyWithPinningMode:withPinnedCertificates:]这个函数了。

// 初始化AFSecurityPolicy对象的SSLPinningMode和pinnedCertificates两个属性
+ (instancetype)policyWithPinningMode:(AFSSLPinningMode)pinningMode withPinnedCertificates:(NSSet *)pinnedCertificates {
    AFSecurityPolicy *securityPolicy = [[self alloc] init];
    securityPolicy.SSLPinningMode = pinningMode;
    [securityPolicy setPinnedCertificates:pinnedCertificates];
    return securityPolicy;
}
// 此函数设置securityPolicy中的pinnedCertificates属性
// 注意还将对应的self.pinnedPublicKeys属性也设置了,该属性表示的是对应证书的公钥(与pinnedCertificates中的证书是一一对应的)
- (void)setPinnedCertificates:(NSSet *)pinnedCertificates {
    _pinnedCertificates = pinnedCertificates;
    if (self.pinnedCertificates) {
        NSMutableSet *mutablePinnedPublicKeys = [NSMutableSet setWithCapacity:[self.pinnedCertificates count]];
        for (NSData *certificate in self.pinnedCertificates) {
            id publicKey = AFPublicKeyForCertificate(certificate);
            if (!publicKey) {
                continue;
            }
            [mutablePinnedPublicKeys addObject:publicKey];
        }
        self.pinnedPublicKeys = [NSSet setWithSet:mutablePinnedPublicKeys];
    } else {
        self.pinnedPublicKeys = nil;
    }
}

static id AFPublicKeyForCertificate(NSData certificate)

// 此函数没什么特别要提及的,和AFPublicKeyTrustChainForServerTrust实现的原理基本一致
// 区别仅仅在该函数是返回单个证书的公钥(所以传入的参数是一个证书),而AFPublicKeyTrustChainForServerTrust返回的是serverTrust的证书链中所有证书公钥
static id AFPublicKeyForCertificate(NSData *certificate) {
    id allowedPublicKey = nil;
    SecCertificateRef allowedCertificate;
    SecCertificateRef allowedCertificates[1];
    CFArrayRef tempCertificates = nil;
    SecPolicyRef policy = nil;
    SecTrustRef allowedTrust = nil;
    SecTrustResultType result;
    // 因为此处传入的certificate参数是NSData类型的,所以需要使用SecCertificateCreateWithData来将NSData对象转化为SecCertificateRef对象
    allowedCertificate = SecCertificateCreateWithData(NULL, (__bridge CFDataRef)certificate);
    __Require_Quiet(allowedCertificate != NULL, _out);
    allowedCertificates[0] = allowedCertificate;
    tempCertificates = CFArrayCreate(NULL, (const void **)allowedCertificates, 1, NULL);
    policy = SecPolicyCreateBasicX509();
    __Require_noErr_Quiet(SecTrustCreateWithCertificates(tempCertificates, policy, &allowedTrust), _out);
    __Require_noErr_Quiet(SecTrustEvaluate(allowedTrust, &result), _out);
    allowedPublicKey = (__bridge_transfer id)SecTrustCopyPublicKey(allowedTrust);
_out:
    if (allowedTrust) {
        CFRelease(allowedTrust);
    }
    if (policy) {
        CFRelease(policy);
    }
    if (tempCertificates) {
        CFRelease(tempCertificates);
    }
    if (allowedCertificate) {
        CFRelease(allowedCertificate);
    }
    return allowedPublicKey;
}

五、自定义证书验证
Security封装了很多获取证书信息的方法,开发者可以根据自己的需求对证书特定信息进行验证

/*检索证书主题的公用名*/
OSStatus SecCertificateCopyCommonName(SecCertificateRef certificate, CFStringRef * __nonnull CF_RETURNS_RETAINED commonName);
/*返回证书可读摘要*/
CFStringRef SecCertificateCopySubjectSummary(SecCertificateRef certificate);
/*获取证书邮箱*/
OSStatus SecCertificateCopyEmailAddresses(SecCertificateRef certificate, CFArrayRef * __nonnull CF_RETURNS_RETAINED emailAddresses);
/*从证书中检索规范化的主题序列*/
CFDataRef SecCertificateCopyNormalizedSubjectSequence(SecCertificateRef certificate);
/*从证书中检索规范化的颁发者序列*/
CFDataRef SecCertificateCopyNormalizedIssuerSequence(SecCertificateRef certificate);
/*返回证书的序列号*/
CFDataRef SecCertificateCopySerialNumberData(SecCertificateRef certificate, CFErrorRef *error);
/*返回证书的长描述的副本*/
CFStringRef SecCertificateCopyLongDescription(CFAllocatorRef __nullable alloc, SecCertificateRef certificate, CFErrorRef *error);

参考

AFNetworking源码阅读(六)

水平有限,写的不对的地方请留言,我会尽快修改,以免误人子弟。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,968评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,601评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,220评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,416评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,425评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,144评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,432评论 3 401
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,088评论 0 261
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,586评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,028评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,137评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,783评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,343评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,333评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,559评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,595评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,901评论 2 345