ct.h 18.7 KB
Newer Older
1
/*
R
Rich Salz 已提交
2 3 4 5 6 7 8
 * Copyright 2016 The OpenSSL Project Authors. All Rights Reserved.
 *
 * Licensed under the OpenSSL license (the "License").  You may not use
 * this file except in compliance with the License.  You can obtain a copy
 * in the file LICENSE in the source distribution or at
 * https://www.openssl.org/source/license.html
 */
9 10 11 12

#ifndef HEADER_CT_H
# define HEADER_CT_H

R
Rich Salz 已提交
13 14 15
# include <openssl/opensslconf.h>

# ifndef OPENSSL_NO_CT
16
# include <openssl/ossl_typ.h>
17 18 19 20 21 22
# include <openssl/safestack.h>
# include <openssl/x509.h>
# ifdef  __cplusplus
extern "C" {
# endif

R
Rich Salz 已提交
23

24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
/* Minimum RSA key size, from RFC6962 */
# define SCT_MIN_RSA_BITS 2048

/* All hashes are SHA256 in v1 of Certificate Transparency */
# define CT_V1_HASHLEN SHA256_DIGEST_LENGTH

typedef enum {
    CT_LOG_ENTRY_TYPE_NOT_SET = -1,
    CT_LOG_ENTRY_TYPE_X509 = 0,
    CT_LOG_ENTRY_TYPE_PRECERT = 1
} ct_log_entry_type_t;

typedef enum {
    SCT_VERSION_NOT_SET = -1,
    SCT_VERSION_V1 = 0
} sct_version_t;

R
Rob Percival 已提交
41 42 43 44 45 46 47
typedef enum {
    SCT_SOURCE_UNKNOWN,
    SCT_SOURCE_TLS_EXTENSION,
    SCT_SOURCE_X509V3_EXTENSION,
    SCT_SOURCE_OCSP_STAPLED_RESPONSE
} sct_source_t;

R
Rob Percival 已提交
48 49 50 51 52 53 54 55 56
typedef enum {
    SCT_VALIDATION_STATUS_NOT_SET,
    SCT_VALIDATION_STATUS_UNKNOWN_LOG,
    SCT_VALIDATION_STATUS_VALID,
    SCT_VALIDATION_STATUS_INVALID,
    SCT_VALIDATION_STATUS_UNVERIFIED,
    SCT_VALIDATION_STATUS_UNKNOWN_VERSION
} sct_validation_status_t;

57
DEFINE_STACK_OF(SCT)
R
Rob Percival 已提交
58
DEFINE_STACK_OF(CTLOG)
59

R
Rob Percival 已提交
60 61 62 63 64 65 66 67 68 69 70
/******************************************
 * CT policy evaluation context functions *
 ******************************************/

/* Creates a new, empty policy evaluation context */
CT_POLICY_EVAL_CTX *CT_POLICY_EVAL_CTX_new(void);

/* Deletes a policy evaluation context */
void CT_POLICY_EVAL_CTX_free(CT_POLICY_EVAL_CTX *ctx);

/* Gets the peer certificate that the SCTs are for */
71
X509* CT_POLICY_EVAL_CTX_get0_cert(const CT_POLICY_EVAL_CTX *ctx);
R
Rob Percival 已提交
72 73 74 75 76

/* Sets the certificate associated with the received SCTs */
void CT_POLICY_EVAL_CTX_set0_cert(CT_POLICY_EVAL_CTX *ctx, X509 *cert);

/* Gets the issuer of the aforementioned certificate */
77
X509* CT_POLICY_EVAL_CTX_get0_issuer(const CT_POLICY_EVAL_CTX *ctx);
R
Rob Percival 已提交
78 79 80 81 82

/* Sets the issuer of the certificate associated with the received SCTs */
void CT_POLICY_EVAL_CTX_set0_issuer(CT_POLICY_EVAL_CTX *ctx, X509 *issuer);

/* Gets the CT logs that are trusted sources of SCTs */
83
const CTLOG_STORE *CT_POLICY_EVAL_CTX_get0_log_store(const CT_POLICY_EVAL_CTX *ctx);
R
Rob Percival 已提交
84 85 86 87 88

/* Sets the log store that is in use */
void CT_POLICY_EVAL_CTX_set0_log_store(CT_POLICY_EVAL_CTX *ctx,
                                       CTLOG_STORE *log_store);

89 90 91 92 93 94 95 96 97 98
/*****************
 * SCT functions *
 *****************/

/*
 * Creates a new, blank SCT.
 * The caller is responsible for calling SCT_free when finished with the SCT.
 */
SCT *SCT_new(void);

R
Rob Percival 已提交
99 100 101 102 103 104 105 106 107 108 109
/*
 * Creates a new SCT from some base64-encoded strings.
 * The caller is responsible for calling SCT_free when finished with the SCT.
 */
SCT *SCT_new_from_base64(unsigned char version,
                         const char *logid_base64,
                         ct_log_entry_type_t entry_type,
                         uint64_t timestamp,
                         const char *extensions_base64,
                         const char *signature_base64);

110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129
/*
 * Frees the SCT and the underlying data structures.
 */
void SCT_free(SCT *sct);

/*
 * Free a stack of SCTs, and the underlying SCTs themselves.
 * Intended to be compatible with X509V3_EXT_FREE.
 */
void SCT_LIST_free(STACK_OF(SCT) *a);

/*
 * Returns the version of the SCT.
 */
sct_version_t SCT_get_version(const SCT *sct);

/*
 * Set the version of an SCT.
 * Returns 1 on success, 0 if the version is unrecognized.
 */
130
__owur int SCT_set_version(SCT *sct, sct_version_t version);
131 132 133 134 135 136 137 138

/*
 * Returns the log entry type of the SCT.
 */
ct_log_entry_type_t SCT_get_log_entry_type(const SCT *sct);

/*
 * Set the log entry type of an SCT.
139
 * Returns 1 on success, 0 otherwise.
140
 */
141
__owur int SCT_set_log_entry_type(SCT *sct, ct_log_entry_type_t entry_type);
142 143 144 145 146 147 148 149 150 151 152

/*
 * Gets the ID of the log that an SCT came from.
 * Ownership of the log ID remains with the SCT.
 * Returns the length of the log ID.
 */
size_t SCT_get0_log_id(const SCT *sct, unsigned char **log_id);

/*
 * Set the log ID of an SCT to point directly to the *log_id specified.
 * The SCT takes ownership of the specified pointer.
153
 * Returns 1 on success, 0 otherwise.
154
 */
155
__owur int SCT_set0_log_id(SCT *sct, unsigned char *log_id, size_t log_id_len);
156 157 158 159

/*
 * Set the log ID of an SCT.
 * This makes a copy of the log_id.
160
 * Returns 1 on success, 0 otherwise.
161
 */
162 163
__owur int SCT_set1_log_id(SCT *sct, const unsigned char *log_id,
                           size_t log_id_len);
164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185

/*
 * Returns the timestamp for the SCT (epoch time in milliseconds).
 */
uint64_t SCT_get_timestamp(const SCT *sct);

/*
 * Set the timestamp of an SCT (epoch time in milliseconds).
 */
void SCT_set_timestamp(SCT *sct, uint64_t timestamp);

/*
 * Return the NID for the signature used by the SCT.
 * For CT v1, this will be either NID_sha256WithRSAEncryption or
 * NID_ecdsa_with_SHA256 (or NID_undef if incorrect/unset).
 */
int SCT_get_signature_nid(const SCT *sct);

/*
 * Set the signature type of an SCT
 * For CT v1, this should be either NID_sha256WithRSAEncryption or
 * NID_ecdsa_with_SHA256.
186
 * Returns 1 on success, 0 otherwise.
187
 */
188
__owur int SCT_set_signature_nid(SCT *sct, int nid);
189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205

/*
 * Set *ext to point to the extension data for the SCT. ext must not be NULL.
 * The SCT retains ownership of this pointer.
 * Returns length of the data pointed to.
 */
size_t SCT_get0_extensions(const SCT *sct, unsigned char **ext);

/*
 * Set the extensions of an SCT to point directly to the *ext specified.
 * The SCT takes ownership of the specified pointer.
 */
void SCT_set0_extensions(SCT *sct, unsigned char *ext, size_t ext_len);

/*
 * Set the extensions of an SCT.
 * This takes a copy of the ext.
206
 * Returns 1 on success, 0 otherwise.
207
 */
208 209
__owur int SCT_set1_extensions(SCT *sct, const unsigned char *ext,
                               size_t ext_len);
210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225

/*
 * Set *sig to point to the signature for the SCT. sig must not be NULL.
 * The SCT retains ownership of this pointer.
 * Returns length of the data pointed to.
 */
size_t SCT_get0_signature(const SCT *sct, unsigned char **sig);

/*
 * Set the signature of an SCT to point directly to the *sig specified.
 * The SCT takes ownership of the specified pointer.
 */
void SCT_set0_signature(SCT *sct, unsigned char *sig, size_t sig_len);

/*
 * Set the signature of an SCT to be a copy of the *sig specified.
226
 * Returns 1 on success, 0 otherwise.
227
 */
228 229
__owur int SCT_set1_signature(SCT *sct, const unsigned char *sig,
                              size_t sig_len);
230

R
Rob Percival 已提交
231 232 233 234 235 236 237 238 239
/*
 * The origin of this SCT, e.g. TLS extension, OCSP response, etc.
 */
sct_source_t SCT_get_source(const SCT *sct);

/*
 * Set the origin of this SCT, e.g. TLS extension, OCSP response, etc.
 * Returns 1 on success, 0 otherwise.
 */
240
__owur int SCT_set_source(SCT *sct, sct_source_t source);
R
Rob Percival 已提交
241

242 243 244 245 246
/*
 * Returns a text string describing the validation status of |sct|.
 */
const char *SCT_validation_status_string(const SCT *sct);

247 248 249
/*
 * Pretty-prints an |sct| to |out|.
 * It will be indented by the number of spaces specified by |indent|.
R
Rob Percival 已提交
250 251
 * If |logs| is not NULL, it will be used to lookup the CT log that the SCT came
 * from, so that the log name can be printed.
252
 */
R
Rob Percival 已提交
253
void SCT_print(const SCT *sct, BIO *out, int indent, const CTLOG_STORE *logs);
254 255 256 257 258

/*
 * Pretty-prints an |sct_list| to |out|.
 * It will be indented by the number of spaces specified by |indent|.
 * SCTs will be delimited by |separator|.
R
Rob Percival 已提交
259 260
 * If |logs| is not NULL, it will be used to lookup the CT log that each SCT
 * came from, so that the log names can be printed.
261 262
 */
void SCT_LIST_print(const STACK_OF(SCT) *sct_list, BIO *out, int indent,
R
Rob Percival 已提交
263
                    const char *separator, const CTLOG_STORE *logs);
264

R
Rob Percival 已提交
265 266
/*
 * Verifies an SCT with the given context.
267
 * Returns 1 if the SCT verifies successfully, 0 otherwise.
R
Rob Percival 已提交
268
 */
269
__owur int SCT_verify(const SCT_CTX *sctx, const SCT *sct);
R
Rob Percival 已提交
270 271 272

/*
 * Verifies an SCT against the provided data.
273
 * Returns 1 if the SCT verifies successfully, 0 otherwise.
R
Rob Percival 已提交
274
 */
275
__owur int SCT_verify_v1(SCT *sct, X509 *cert, X509 *preissuer,
R
Rob Percival 已提交
276 277
                  X509_PUBKEY *log_pubkey, X509 *issuer_cert);

R
Rob Percival 已提交
278 279 280 281 282 283 284 285 286 287 288 289 290
/*
 * Gets the last result of validating this SCT.
 * If it has not been validated yet, returns SCT_VALIDATION_STATUS_NOT_SET.
 */
sct_validation_status_t SCT_get_validation_status(const SCT *sct);

/*
 * Validates the given SCT with the provided context.
 * Sets the "validation_status" field of the SCT.
 * Returns 1 if the SCT is valid and the signature verifies.
 * Returns 0 if the SCT is invalid or could not be verified.
 * Returns -1 if an error occurs.
 */
291
__owur int SCT_validate(SCT *sct, const CT_POLICY_EVAL_CTX *ctx);
R
Rob Percival 已提交
292 293 294 295 296 297 298 299

/*
 * Validates the given list of SCTs with the provided context.
 * Populates the "good_scts" and "bad_scts" of the evaluation context.
 * Returns 1 if there are no invalid SCTs and all signatures verify.
 * Returns 0 if at least one SCT is invalid or could not be verified.
 * Returns a negative integer if an error occurs.
 */
300 301
__owur int SCT_LIST_validate(const STACK_OF(SCT) *scts,
                             CT_POLICY_EVAL_CTX *ctx);
R
Rob Percival 已提交
302 303


304 305 306 307 308 309 310 311 312 313 314 315
/*********************************
 * SCT parsing and serialisation *
 *********************************/

/*
 * Serialize (to TLS format) a stack of SCTs and return the length.
 * "a" must not be NULL.
 * If "pp" is NULL, just return the length of what would have been serialized.
 * If "pp" is not NULL and "*pp" is null, function will allocate a new pointer
 * for data that caller is responsible for freeing (only if function returns
 * successfully).
 * If "pp" is NULL and "*pp" is not NULL, caller is responsible for ensuring
F
FdaSilvaYY 已提交
316
 * that "*pp" is large enough to accept all of the serialized data.
317 318 319
 * Returns < 0 on error, >= 0 indicating bytes written (or would have been)
 * on success.
 */
320
__owur int i2o_SCT_LIST(const STACK_OF(SCT) *a, unsigned char **pp);
321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342

/*
 * Convert TLS format SCT list to a stack of SCTs.
 * If "a" or "*a" is NULL, a new stack will be created that the caller is
 * responsible for freeing (by calling SCT_LIST_free).
 * "**pp" and "*pp" must not be NULL.
 * Upon success, "*pp" will point to after the last bytes read, and a stack
 * will be returned.
 * Upon failure, a NULL pointer will be returned, and the position of "*pp" is
 * not defined.
 */
STACK_OF(SCT) *o2i_SCT_LIST(STACK_OF(SCT) **a, const unsigned char **pp,
                            size_t len);

/*
 * Serialize (to DER format) a stack of SCTs and return the length.
 * "a" must not be NULL.
 * If "pp" is NULL, just returns the length of what would have been serialized.
 * If "pp" is not NULL and "*pp" is null, function will allocate a new pointer
 * for data that caller is responsible for freeing (only if function returns
 * successfully).
 * If "pp" is NULL and "*pp" is not NULL, caller is responsible for ensuring
F
FdaSilvaYY 已提交
343
 * that "*pp" is large enough to accept all of the serialized data.
344 345 346
 * Returns < 0 on error, >= 0 indicating bytes written (or would have been)
 * on success.
 */
347
__owur int i2d_SCT_LIST(const STACK_OF(SCT) *a, unsigned char **pp);
348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370

/*
 * Parses an SCT list in DER format and returns it.
 * If "a" or "*a" is NULL, a new stack will be created that the caller is
 * responsible for freeing (by calling SCT_LIST_free).
 * "**pp" and "*pp" must not be NULL.
 * Upon success, "*pp" will point to after the last bytes read, and a stack
 * will be returned.
 * Upon failure, a NULL pointer will be returned, and the position of "*pp" is
 * not defined.
 */
STACK_OF(SCT) *d2i_SCT_LIST(STACK_OF(SCT) **a, const unsigned char **pp,
                            long len);

/*
 * Serialize (to TLS format) an |sct| and write it to |out|.
 * If |out| is null, no SCT will be output but the length will still be returned.
 * If |out| points to a null pointer, a string will be allocated to hold the
 * TLS-format SCT. It is the responsibility of the caller to free it.
 * If |out| points to an allocated string, the TLS-format SCT will be written
 * to it.
 * The length of the SCT in TLS format will be returned.
 */
371
__owur int i2o_SCT(const SCT *sct, unsigned char **out);
372 373 374 375 376

/*
 * Parses an SCT in TLS format and returns it.
 * If |psct| is not null, it will end up pointing to the parsed SCT. If it
 * already points to a non-null pointer, the pointer will be free'd.
F
FdaSilvaYY 已提交
377
 * |in| should be a pointer to a string containing the TLS-format SCT.
378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393
 * |in| will be advanced to the end of the SCT if parsing succeeds.
 * |len| should be the length of the SCT in |in|.
 * Returns NULL if an error occurs.
 * If the SCT is an unsupported version, only the SCT's 'sct' and 'sct_len'
 * fields will be populated (with |in| and |len| respectively).
 */
SCT *o2i_SCT(SCT **psct, const unsigned char **in, size_t len);

/*
* Serialize (to TLS format) an |sct| signature and write it to |out|.
* If |out| is null, no signature will be output but the length will be returned.
* If |out| points to a null pointer, a string will be allocated to hold the
* TLS-format signature. It is the responsibility of the caller to free it.
* If |out| points to an allocated string, the signature will be written to it.
* The length of the signature in TLS format will be returned.
*/
394
__owur int i2o_SCT_signature(const SCT *sct, unsigned char **out);
395 396 397

/*
* Parses an SCT signature in TLS format and populates the |sct| with it.
F
FdaSilvaYY 已提交
398
* |in| should be a pointer to a string containing the TLS-format signature.
399 400 401 402
* |in| will be advanced to the end of the signature if parsing succeeds.
* |len| should be the length of the signature in |in|.
* Returns the number of bytes parsed, or a negative integer if an error occurs.
*/
403
__owur int o2i_SCT_signature(SCT *sct, const unsigned char **in, size_t len);
404

R
Rob Percival 已提交
405 406 407 408 409 410 411 412 413 414 415
/********************
 * CT log functions *
 ********************/

/*
 * Creates a new CT log instance with the given |public_key| and |name|.
 * Should be deleted by the caller using CTLOG_free when no longer needed.
 */
CTLOG *CTLOG_new(EVP_PKEY *public_key, const char *name);

/*
416
 * Creates a new CT |ct_log| instance with the given base64 public_key and |name|.
R
Rob Percival 已提交
417 418
 * Should be deleted by the caller using CTLOG_free when no longer needed.
 */
419 420
int CTLOG_new_from_base64(CTLOG ** ct_log,
                          const char *pkey_base64, const char *name);
R
Rob Percival 已提交
421 422 423 424 425 426 427

/*
 * Deletes a CT log instance and its fields.
 */
void CTLOG_free(CTLOG *log);

/* Gets the name of the CT log */
428
const char *CTLOG_get0_name(const CTLOG *log);
R
Rob Percival 已提交
429
/* Gets the ID of the CT log */
430 431
void CTLOG_get0_log_id(const CTLOG *log, const uint8_t **log_id,
                       size_t *log_id_len);
R
Rob Percival 已提交
432
/* Gets the public key of the CT log */
433
EVP_PKEY *CTLOG_get0_public_key(const CTLOG *log);
R
Rob Percival 已提交
434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453

/**************************
 * CT log store functions *
 **************************/

/*
 * Creates a new CT log store.
 * Should be deleted by the caller using CTLOG_STORE_free when no longer needed.
 */
CTLOG_STORE *CTLOG_STORE_new(void);

/*
 * Deletes a CT log store and all of the CT log instances held within.
 */
void CTLOG_STORE_free(CTLOG_STORE *store);

/*
 * Finds a CT log in the store based on its log ID.
 * Returns the CT log, or NULL if no match is found.
 */
454 455 456
const CTLOG *CTLOG_STORE_get0_log_by_id(const CTLOG_STORE *store,
                                        const uint8_t *log_id,
                                        size_t log_id_len);
R
Rob Percival 已提交
457 458 459

/*
 * Loads a CT log list into a |store| from a |file|.
460
 * Returns 1 if loading is successful, or 0 otherwise.
R
Rob Percival 已提交
461
 */
462
__owur int CTLOG_STORE_load_file(CTLOG_STORE *store, const char *file);
R
Rob Percival 已提交
463 464 465 466 467

/*
 * Loads the default CT log list into a |store|.
 * See internal/cryptlib.h for the environment variable and file path that are
 * consulted to find the default file.
468
 * Returns 1 if loading is successful, or 0 otherwise.
R
Rob Percival 已提交
469
 */
470
__owur int CTLOG_STORE_load_default_file(CTLOG_STORE *store);
R
Rob Percival 已提交
471

472 473 474 475 476
/* BEGIN ERROR CODES */
/*
 * The following lines are auto generated by the script mkerr.pl. Any changes
 * made after this point may be overwritten when the script is next run.
 */
R
Rich Salz 已提交
477

478
int ERR_load_CT_strings(void);
479 480 481 482

/* Error codes for the CT functions. */

/* Function codes. */
R
Rob Percival 已提交
483 484 485 486 487 488
# define CT_F_CTLOG_NEW                                   117
# define CT_F_CTLOG_NEW_FROM_BASE64                       118
# define CT_F_CTLOG_NEW_FROM_CONF                         119
# define CT_F_CTLOG_NEW_NULL                              120
# define CT_F_CTLOG_STORE_LOAD_CTX_NEW                    122
# define CT_F_CTLOG_STORE_LOAD_FILE                       123
489 490
# define CT_F_CTLOG_STORE_LOAD_LOG                        130
# define CT_F_CTLOG_STORE_NEW                             131
R
Rob Percival 已提交
491
# define CT_F_CT_BASE64_DECODE                            124
R
Rob Percival 已提交
492
# define CT_F_CT_POLICY_EVAL_CTX_NEW                      133
R
Rob Percival 已提交
493
# define CT_F_CT_V1_LOG_ID_FROM_PKEY                      125
494 495 496 497 498 499
# define CT_F_I2O_SCT                                     107
# define CT_F_I2O_SCT_LIST                                108
# define CT_F_I2O_SCT_SIGNATURE                           109
# define CT_F_O2I_SCT                                     110
# define CT_F_O2I_SCT_LIST                                111
# define CT_F_O2I_SCT_SIGNATURE                           112
R
Rob Percival 已提交
500
# define CT_F_SCT_CTX_NEW                                 126
501
# define CT_F_SCT_NEW                                     100
R
Rob Percival 已提交
502
# define CT_F_SCT_NEW_FROM_BASE64                         127
503 504 505 506 507 508 509
# define CT_F_SCT_SET0_LOG_ID                             101
# define CT_F_SCT_SET1_EXTENSIONS                         114
# define CT_F_SCT_SET1_LOG_ID                             115
# define CT_F_SCT_SET1_SIGNATURE                          116
# define CT_F_SCT_SET_LOG_ENTRY_TYPE                      102
# define CT_F_SCT_SET_SIGNATURE_NID                       103
# define CT_F_SCT_SET_VERSION                             104
R
Rob Percival 已提交
510 511
# define CT_F_SCT_VERIFY                                  128
# define CT_F_SCT_VERIFY_V1                               129
512 513

/* Reason codes. */
R
Rob Percival 已提交
514
# define CT_R_BASE64_DECODE_ERROR                         108
515
# define CT_R_INVALID_LOG_ID_LENGTH                       100
R
Rob Percival 已提交
516 517 518 519 520
# define CT_R_LOG_CONF_INVALID                            109
# define CT_R_LOG_CONF_INVALID_KEY                        110
# define CT_R_LOG_CONF_MISSING_DESCRIPTION                111
# define CT_R_LOG_CONF_MISSING_KEY                        112
# define CT_R_LOG_KEY_INVALID                             113
521 522 523
# define CT_R_SCT_INVALID                                 104
# define CT_R_SCT_INVALID_SIGNATURE                       107
# define CT_R_SCT_LIST_INVALID                            105
R
Rob Percival 已提交
524
# define CT_R_SCT_LOG_ID_MISMATCH                         114
525
# define CT_R_SCT_NOT_SET                                 106
R
Rob Percival 已提交
526
# define CT_R_SCT_UNSUPPORTED_VERSION                     115
527 528 529 530
# define CT_R_UNRECOGNIZED_SIGNATURE_NID                  101
# define CT_R_UNSUPPORTED_ENTRY_TYPE                      102
# define CT_R_UNSUPPORTED_VERSION                         103

R
Rich Salz 已提交
531
#  ifdef  __cplusplus
532
}
R
Rich Salz 已提交
533
#  endif
R
Rich Salz 已提交
534
# endif
535
#endif