提交 700b4a4a 编写于 作者: R Rich Salz 提交者: Rich Salz

Remove more (rest?) of FIPS build stuff.

Reviewed-by: NDr. Stephen Henson <steve@openssl.org>
上级 0b0443af
Preliminary status and build information for FIPS module v2.0
NB: if you are cross compiling you now need to use the latest "incore" script
this can be found at util/incore in the tarballs.
If you have any object files from a previous build do:
make clean
To build the module do:
./config fipscanisteronly
make
Build should complete without errors.
Build test utilities:
make build_tests
Run test suite:
test/fips_test_suite
again should complete without errors.
Run test vectors:
1. Download an appropriate set of testvectors from www.openssl.org/docs/fips
only the fips-2.0 testvector files are usable for complete tests.
2. Extract the files to a suitable directory.
3. Run the test vector perl script, for example:
cd fips
perl fipsalgtest.pl --dir=/wherever/stuff/was/extracted
4. It should say "passed all tests" at the end. Report full details of any
failures.
If you wish to use the older 1.2.x testvectors (for example those from 2007)
you need the command line switch --disable-v2 to fipsalgtest.pl
Examine the external symbols in fips/fipscanister.o they should all begin
with FIPS or fips. One way to check with GNU nm is:
nm -g --defined-only fips/fipscanister.o | grep -v -i fips
If you get *any* output at all from this test (i.e. symbols not starting with
fips or FIPS) please report it.
Restricted tarball tests.
The validated module will have its own tarball containing sufficient code to
build fipscanister.o and the associated algorithm tests. You can create a
similar tarball yourself for testing purposes using the commands below.
Standard restricted tarball:
make -f Makefile.fips dist
Prime field field only ECC tarball:
make NOEC2M=1 -f Makefile.fips dist
Once you've created the tarball extract into a fresh directory and do:
./config
make
You can then run the algorithm tests as above. This build automatically uses
fipscanisterbuild and no-ec2m as appropriate.
FIPS capable OpenSSL test: WARNING PRELIMINARY INSTRUCTIONS, SUBJECT TO CHANGE.
At least initially the test module and FIPS capable OpenSSL may change and
by out of sync. You are advised to check for any changes and pull the latest
source from CVS if you have problems. See anon CVS and rsync instructions at:
http://www.openssl.org/source/repos.html
Make or download a restricted tarball from ftp://ftp.openssl.org/snapshot/
If required set the environment variable FIPSDIR to an appropriate location
to install the test module. If cross compiling set other environment
variables too.
In this restricted tarball on a Linux or U*ix like system run:
./config
make
make install
On Windows from a VC++ environment do:
ms\do_fips
This will build and install the test module and some associated files.
Now download the latest version of the OpenSSL 1.0.1 branch from either a
snapshot or preferably CVS. For Linux do:
./config fips [other args]
make
For Windows:
perl Configure VC-WIN32 fips [other args]
ms\do_nasm
nmake -f ms\ntdll.mak
(or ms\nt.mak for a static build).
Where [other args] can be any other arguments you use for an OpenSSL build
such as "shared" or "zlib".
This will build the fips capable OpenSSL and link it to the test module. You
can now try linking and testing applications against the FIPS capable OpenSSL.
Please report any problems to either the openssl-dev mailing list or directly
to me steve@openssl.org . Check the mailing lists regularly to avoid duplicate
reports.
Known issues:
Code needs extensively reviewing to ensure it builds correctly on
supported platforms and is compliant with FIPS 140-2.
The "FIPS capable OpenSSL" is still largely untested, it builds and runs
some simple tests OK on some systems but needs far more "real world" testing.
This release does not support a FIPS 140-2 validated module.
......@@ -80,7 +80,7 @@ typedef enum OPTION_choice {
OPT_C, OPT_R, OPT_RAND, OPT_OUT, OPT_SIGN, OPT_PASSIN, OPT_VERIFY,
OPT_PRVERIFY, OPT_SIGNATURE, OPT_KEYFORM, OPT_ENGINE, OPT_ENGINE_IMPL,
OPT_HEX, OPT_BINARY, OPT_DEBUG, OPT_FIPS_FINGERPRINT,
OPT_NON_FIPS_ALLOW, OPT_HMAC, OPT_MAC, OPT_SIGOPT, OPT_MACOPT,
OPT_HMAC, OPT_MAC, OPT_SIGOPT, OPT_MACOPT,
OPT_DIGEST
} OPTION_CHOICE;
......@@ -106,7 +106,6 @@ OPTIONS dgst_options[] = {
{"d", OPT_DEBUG, '-', "Print debug info"},
{"debug", OPT_DEBUG, '-'},
{"fips-fingerprint", OPT_FIPS_FINGERPRINT, '-'},
{"non-fips-allow", OPT_NON_FIPS_ALLOW, '-'},
{"hmac", OPT_HMAC, 's', "Create hashed MAC with key"},
{"mac", OPT_MAC, 's', "Create MAC (not neccessarily HMAC)"},
{"sigopt", OPT_SIGOPT, 's', "Signature parameter in n:v form"},
......@@ -133,8 +132,7 @@ int dgst_main(int argc, char **argv)
const char *sigfile = NULL, *randfile = NULL;
OPTION_CHOICE o;
int separator = 0, debug = 0, keyform = FORMAT_PEM, siglen = 0;
int i, ret = 1, out_bin = -1, want_pub = 0, do_verify =
0, non_fips_allow = 0;
int i, ret = 1, out_bin = -1, want_pub = 0, do_verify = 0;
unsigned char *buf = NULL, *sigbuf = NULL;
int engine_impl = 0;
......@@ -205,9 +203,6 @@ int dgst_main(int argc, char **argv)
case OPT_FIPS_FINGERPRINT:
hmac_key = "etaonrishdlcupfm";
break;
case OPT_NON_FIPS_ALLOW:
non_fips_allow = 1;
break;
case OPT_HMAC:
hmac_key = opt_arg();
break;
......@@ -323,12 +318,6 @@ int dgst_main(int argc, char **argv)
goto end;
}
if (non_fips_allow) {
EVP_MD_CTX *md_ctx;
BIO_get_md_ctx(bmd, &md_ctx);
EVP_MD_CTX_set_flags(md_ctx, EVP_MD_CTX_FLAG_NON_FIPS_ALLOW);
}
if (hmac_key) {
sigkey = EVP_PKEY_new_mac_key(EVP_PKEY_HMAC, impl,
(unsigned char *)hmac_key, -1);
......
......@@ -86,7 +86,7 @@ static int dsa_cb(int p, int n, BN_GENCB *cb);
typedef enum OPTION_choice {
OPT_ERR = -1, OPT_EOF = 0, OPT_HELP,
OPT_INFORM, OPT_OUTFORM, OPT_IN, OPT_OUT, OPT_TEXT, OPT_C,
OPT_NOOUT, OPT_GENKEY, OPT_RAND, OPT_NON_FIPS_ALLOW, OPT_ENGINE,
OPT_NOOUT, OPT_GENKEY, OPT_RAND, OPT_ENGINE,
OPT_TIMEBOMB
} OPTION_CHOICE;
......@@ -101,7 +101,6 @@ OPTIONS dsaparam_options[] = {
{"noout", OPT_NOOUT, '-', "No output"},
{"genkey", OPT_GENKEY, '-', "Generate a DSA key"},
{"rand", OPT_RAND, 's', "Files to use for random number input"},
{"non-fips-allow", OPT_NON_FIPS_ALLOW, '-'},
# ifdef GENCB_TEST
{"timebomb", OPT_TIMEBOMB, 'p', "Interrupt keygen after 'pnum' seconds"},
# endif
......@@ -116,7 +115,7 @@ int dsaparam_main(int argc, char **argv)
DSA *dsa = NULL;
BIO *in = NULL, *out = NULL;
BN_GENCB *cb = NULL;
int numbits = -1, num = 0, genkey = 0, need_rand = 0, non_fips_allow = 0;
int numbits = -1, num = 0, genkey = 0, need_rand = 0;
int informat = FORMAT_PEM, outformat = FORMAT_PEM, noout = 0, C = 0;
int ret = 1, i, text = 0, private = 0;
# ifdef GENCB_TEST
......@@ -175,9 +174,6 @@ int dsaparam_main(int argc, char **argv)
case OPT_NOOUT:
noout = 1;
break;
case OPT_NON_FIPS_ALLOW:
non_fips_allow = 1;
break;
}
}
argc = opt_num_rest();
......@@ -219,8 +215,6 @@ int dsaparam_main(int argc, char **argv)
BIO_printf(bio_err, "Error allocating DSA object\n");
goto end;
}
if (non_fips_allow)
dsa->flags |= DSA_FLAG_NON_FIPS_ALLOW;
BIO_printf(bio_err, "Generating DSA parameters, %d bit long prime\n",
num);
BIO_printf(bio_err, "This could take some time\n");
......@@ -309,8 +303,6 @@ int dsaparam_main(int argc, char **argv)
assert(need_rand);
if ((dsakey = DSAparams_dup(dsa)) == NULL)
goto end;
if (non_fips_allow)
dsakey->flags |= DSA_FLAG_NON_FIPS_ALLOW;
if (!DSA_generate_key(dsakey)) {
ERR_print_errors(bio_err);
DSA_free(dsakey);
......
......@@ -84,7 +84,7 @@ typedef enum OPTION_choice {
OPT_E, OPT_IN, OPT_OUT, OPT_PASS, OPT_ENGINE, OPT_D, OPT_P, OPT_V,
OPT_NOPAD, OPT_SALT, OPT_NOSALT, OPT_DEBUG, OPT_UPPER_P, OPT_UPPER_A,
OPT_A, OPT_Z, OPT_BUFSIZE, OPT_K, OPT_KFILE, OPT_UPPER_K, OPT_NONE,
OPT_UPPER_S, OPT_IV, OPT_MD, OPT_NON_FIPS_ALLOW, OPT_CIPHER
OPT_UPPER_S, OPT_IV, OPT_MD, OPT_CIPHER
} OPTION_CHOICE;
OPTIONS enc_options[] = {
......@@ -111,7 +111,6 @@ OPTIONS enc_options[] = {
{"S", OPT_UPPER_S, 's', "Salt, in hex"},
{"iv", OPT_IV, 's', "IV in hex"},
{"md", OPT_MD, 's', "Use specified digest to create key from passphrase"},
{"non-fips-allow", OPT_NON_FIPS_ALLOW, '-'},
{"none", OPT_NONE, '-', "Don't encrypt"},
{"", OPT_CIPHER, '-', "Any supported cipher"},
#ifdef ZLIB
......@@ -140,7 +139,7 @@ int enc_main(int argc, char **argv)
int bsize = BSIZE, verbose = 0, debug = 0, olb64 = 0, nosalt = 0;
int enc = 1, printkey = 0, i, k;
int base64 = 0, informat = FORMAT_BINARY, outformat = FORMAT_BINARY;
int ret = 1, inl, nopad = 0, non_fips_allow = 0;
int ret = 1, inl, nopad = 0;
unsigned char key[EVP_MAX_KEY_LENGTH], iv[EVP_MAX_IV_LENGTH];
unsigned char *buff = NULL, salt[PKCS5_SALT_LEN];
unsigned long n;
......@@ -279,9 +278,6 @@ int enc_main(int argc, char **argv)
if (!opt_md(opt_arg(), &dgst))
goto opthelp;
break;
case OPT_NON_FIPS_ALLOW:
non_fips_allow = 1;
break;
case OPT_CIPHER:
if (!opt_cipher(opt_unknown(), &c))
goto opthelp;
......@@ -501,9 +497,6 @@ int enc_main(int argc, char **argv)
BIO_get_cipher_ctx(benc, &ctx);
if (non_fips_allow)
EVP_CIPHER_CTX_set_flags(ctx, EVP_CIPH_FLAG_NON_FIPS_ALLOW);
if (!EVP_CipherInit_ex(ctx, cipher, NULL, NULL, NULL, enc)) {
BIO_printf(bio_err, "Error setting cipher %s\n",
EVP_CIPHER_name(cipher));
......
......@@ -78,7 +78,7 @@ static int genrsa_cb(int p, int n, BN_GENCB *cb);
typedef enum OPTION_choice {
OPT_ERR = -1, OPT_EOF = 0, OPT_HELP,
OPT_3, OPT_F4, OPT_NON_FIPS_ALLOW, OPT_ENGINE,
OPT_3, OPT_F4, OPT_ENGINE,
OPT_OUT, OPT_RAND, OPT_PASSOUT, OPT_CIPHER
} OPTION_CHOICE;
......@@ -87,7 +87,6 @@ OPTIONS genrsa_options[] = {
{"3", OPT_3, '-', "Use 3 for the E value"},
{"F4", OPT_F4, '-', "Use F4 (0x10001) for the E value"},
{"f4", OPT_F4, '-', "Use F4 (0x10001) for the E value"},
{"non-fips-allow", OPT_NON_FIPS_ALLOW, '-'},
{"out", OPT_OUT, 's', "Output the key to specified file"},
{"rand", OPT_RAND, 's',
"Load the file(s) into the random number generator"},
......@@ -108,7 +107,7 @@ int genrsa_main(int argc, char **argv)
BIO *out = NULL;
RSA *rsa = NULL;
const EVP_CIPHER *enc = NULL;
int ret = 1, non_fips_allow = 0, num = DEFBITS, private = 0;
int ret = 1, num = DEFBITS, private = 0;
unsigned long f4 = RSA_F4;
char *outfile = NULL, *passoutarg = NULL, *passout = NULL;
char *inrand = NULL, *prog, *hexe, *dece;
......@@ -136,9 +135,6 @@ int genrsa_main(int argc, char **argv)
case OPT_F4:
f4 = RSA_F4;
break;
case OPT_NON_FIPS_ALLOW:
non_fips_allow = 1;
break;
case OPT_OUT:
outfile = opt_arg();
break;
......@@ -188,9 +184,6 @@ int genrsa_main(int argc, char **argv)
if (rsa == NULL)
goto end;
if (non_fips_allow)
rsa->flags |= RSA_FLAG_NON_FIPS_ALLOW;
if (!BN_set_word(bn, f4) || !RSA_generate_key_ex(rsa, num, bn, cb))
goto end;
......
......@@ -176,92 +176,3 @@ int RAND_status(void)
return meth->status();
return 0;
}
#ifdef OPENSSL_FIPS
/*
* FIPS DRBG initialisation code. This sets up the DRBG for use by the rest
* of OpenSSL.
*/
/*
* Entropy gatherer: use standard OpenSSL PRNG to seed (this will gather
* entropy internally through RAND_poll().
*/
static size_t drbg_get_entropy(DRBG_CTX *ctx, unsigned char **pout,
int entropy, size_t min_len, size_t max_len)
{
/* Round up request to multiple of block size */
min_len = ((min_len + 19) / 20) * 20;
*pout = OPENSSL_malloc(min_len);
if (*pout == NULL)
return 0;
if (RAND_OpenSSL()->bytes(*pout, min_len) <= 0) {
OPENSSL_free(*pout);
*pout = NULL;
return 0;
}
return min_len;
}
static void drbg_free_entropy(DRBG_CTX *ctx, unsigned char *out, size_t olen)
{
OPENSSL_clear_free(out, olen);
}
/*
* Set "additional input" when generating random data. This uses the current
* PID, a time value and a counter.
*/
static size_t drbg_get_adin(DRBG_CTX *ctx, unsigned char **pout)
{
/* Use of static variables is OK as this happens under a lock */
static unsigned char buf[16];
static unsigned long counter;
FIPS_get_timevec(buf, &counter);
rand_hw_xor(buf, sizeof(buf));
*pout = buf;
return sizeof(buf);
}
/*
* RAND_add() and RAND_seed() pass through to OpenSSL PRNG so it is
* correctly seeded by RAND_poll().
*/
static int drbg_rand_add(DRBG_CTX *ctx, const void *in, int inlen,
double entropy)
{
return RAND_OpenSSL()->add(in, inlen, entropy);
}
static int drbg_rand_seed(DRBG_CTX *ctx, const void *in, int inlen)
{
return RAND_OpenSSL()->seed(in, inlen);
}
int RAND_init_fips(void)
{
DRBG_CTX *dctx;
size_t plen;
unsigned char pers[32], *p;
dctx = FIPS_get_default_drbg();
FIPS_drbg_init(dctx, NID_aes_256_ctr, DRBG_FLAG_CTR_USE_DF);
FIPS_drbg_set_callbacks(dctx,
drbg_get_entropy, drbg_free_entropy, 20,
drbg_get_entropy, drbg_free_entropy);
FIPS_drbg_set_rand_callbacks(dctx, drbg_get_adin, 0,
drbg_rand_seed, drbg_rand_add);
/* Personalisation string: a string followed by date time vector */
strcpy((char *)pers, "OpenSSL DRBG2.0");
plen = drbg_get_adin(dctx, &p);
memcpy(pers + 16, p, plen);
FIPS_drbg_instantiate(dctx, pers, sizeof(pers));
FIPS_rand_set_method(FIPS_drbg_method());
return 1;
}
#endif
......@@ -13,7 +13,6 @@ B<openssl> B<dgst>
[B<-hex>]
[B<-binary>]
[B<-r>]
[B<-non-fips-allow>]
[B<-out filename>]
[B<-sign filename>]
[B<-keyform arg>]
......@@ -22,7 +21,6 @@ B<openssl> B<dgst>
[B<-prverify filename>]
[B<-signature filename>]
[B<-hmac key>]
[B<-non-fips-allow>]
[B<-fips-fingerprint>]
[B<file...>]
......@@ -70,11 +68,6 @@ output the digest or signature in binary form.
output the digest in the "coreutils" format used by programs like B<sha1sum>.
=item B<-non-fips-allow>
Allow use of non FIPS digest when in FIPS mode. This has no effect when not in
FIPS mode.
=item B<-out filename>
filename to output to, or standard output by default.
......@@ -159,10 +152,6 @@ Multiple files can be specified separated by a OS-dependent character.
The separator is B<;> for MS-Windows, B<,> for OpenVMS, and B<:> for
all others.
=item B<-non-fips-allow>
enable use of non-FIPS algorithms such as MD5 even in FIPS mode.
=item B<-fips-fingerprint>
compute HMAC using a specific key
......@@ -219,5 +208,6 @@ prior to verification.
=head1 HISTORY
The default digest was changed from MD5 to SHA256 in Openssl 1.1.
The FIPS-related options were removed in OpenSSL 1.1
=cut
......@@ -119,10 +119,6 @@ int RAND_event(UINT, WPARAM, LPARAM);
# endif
# ifdef OPENSSL_FIPS
int RAND_init_fips(void);
# endif
/* BEGIN ERROR CODES */
/*
* The following lines are auto generated by the script mkerr.pl. Any changes
......
# FIPS distribution filter.
# Takes tarball listing and removes unnecessary files and directories.
#
my $objs = "";
foreach (split / /, "FIPS_EX_OBJ AES_ENC BN_ASM DES_ENC SHA1_ASM_OBJ MODES_ASM_OBJ")
{
$objs .= " $ENV{$_}";
}
my $noec2m = 0;
my @objlist = split / /, $objs;
foreach (@objlist) { $tarobjs{"$1.c"} = 1 if /([^\/]+).o$/};
$tarobjs{"ncbc_enc.c"} = 1;
$tarobjs{"mem_clr.c"} = 1;
$tarobjs{"ppccap.c"} = 1;
$tarobjs{"sparcv9cap.c"} = 1;
$tarobjs{"armcap.c"} = 1;
foreach (split / /, $ENV{LINKDIRS} ) { $cdirs{$_} = 1 };
$cdirs{perlasm} = 1;
$noec2m = 1 if (exists $ENV{NOEC2M});
if ($noec2m)
{
delete $tarobjs{"bn_gf2m.c"};
delete $tarobjs{"ec2_mult.c"};
delete $tarobjs{"ec2_smpl.c"};
}
my %keep =
(
"Makefile.fips" => 1,
"Makefile.shared" => 1,
"README.FIPS" => 1,
"README.ECC" => 1,
"e_os.h" => 1,
"e_os2.h" => 1,
"Configure" => 1,
"config" => 1,
);
while (<STDIN>)
{
chomp;
# Keep top level files in list
if (!/\// && -f $_)
{
next unless exists $keep{$_};
}
else
{
next unless (/^(fips\/|crypto|util|test|include|ms)/);
}
if (/^crypto\/([^\/]+)/)
{
# Skip unused directories under crypto/
next if -d "crypto/$1" && !exists $cdirs{$1};
# Skip GF2m assembly language perl scripts
next if $noec2m && /gf2m\.pl/;
next if /vpaes-\w*\.pl/;
# Keep assembly language dir, Makefile or certain extensions
if (!/\/asm\// && !/\/Makefile$/ && !/\.(in|pl|h|S)$/)
{
# If C source file must be on list.
next if !/(\w+\.c)$/ || !exists $tarobjs{$1};
}
}
if (/^test\//)
{
next unless /Makefile/ || /dummytest.c/;
}
print "$_\n";
}
exit 1;
# Filter script. Take all FIPS object files from the environment
# and print out only those in the given directory.
my $dir = $ARGV[0];
my $asmobjs = "";
# Add any needed assembly language files.
$asmobjs = $ENV{AES_ENC} if $dir eq "aes";
$asmobjs = $ENV{BN_ASM} if $dir eq "bn";
$asmobjs = $ENV{DES_ENC} if $dir eq "des";
$asmobjs = $ENV{SHA1_ASM_OBJ} if $dir eq "sha";
$asmobjs = $ENV{MODES_ASM_OBJ} if $dir eq "modes";
# Get all other FIPS object files, filtered by directory.
my @objlist = grep {/crypto\/$dir\//} split / /, $ENV{FIPS_EX_OBJ};
push @objlist, split / /, $asmobjs;
# Fatal error if no matches
die "No objects in $dir!" if (scalar @objlist == 0);
# Output all matches removing pathname.
foreach (@objlist)
{
s|../crypto/$dir/||;
print "$_\n";
}
......@@ -4231,7 +4231,7 @@ private_SEED_set_key 4610 1_1_0 NOEXIST::FUNCTION:
EVP_aes_192_gcm 4611 1_1_0 EXIST::FUNCTION:AES
X509_ALGOR_set_md 4612 1_1_0 EXIST::FUNCTION:
private_SHA256_Init 4613 1_1_0 NOEXIST::FUNCTION:
RAND_init_fips 4614 1_1_0 EXIST:OPENSSL_FIPS:FUNCTION:
RAND_init_fips 4614 1_1_0 NOEXIST::FUNCTION:
EVP_aes_256_gcm 4615 1_1_0 EXIST::FUNCTION:AES
private_SHA384_Init 4616 1_1_0 NOEXIST::FUNCTION:
EVP_aes_192_ccm 4617 1_1_0 EXIST::FUNCTION:AES
......
......@@ -63,8 +63,8 @@ my $linux=0;
my $safe_stack_def = 0;
my @known_platforms = ( "__FreeBSD__", "PERL5",
"EXPORT_VAR_AS_FUNCTION", "ZLIB",
"OPENSSL_FIPS", "OPENSSL_FIPSCAPABLE" );
"EXPORT_VAR_AS_FUNCTION", "ZLIB"
);
my @known_ossl_platforms = ( "VMS", "WIN32", "WINNT", "OS2" );
my @known_algorithms = ( "RC2", "RC4", "RC5", "IDEA", "DES", "BF",
"CAST", "MD2", "MD4", "MD5", "SHA", "SHA0", "SHA1",
......@@ -137,8 +137,6 @@ my $no_jpake; my $no_srp; my $no_ec2m; my $no_nistp_gcc;
my $no_nextprotoneg; my $no_sctp; my $no_srtp; my $no_ssl_trace;
my $no_unit_test; my $no_ssl3_method; my $no_ocb;
my $fips;
my $zlib;
......@@ -164,7 +162,6 @@ foreach (@ARGV, split(/ /, $options))
}
$VMS=1 if $_ eq "VMS";
$OS2=1 if $_ eq "OS2";
$fips=1 if /^fips/;
if ($_ eq "zlib" || $_ eq "enable-zlib" || $_ eq "zlib-dynamic"
|| $_ eq "enable-zlib-dynamic") {
$zlib = 1;
......@@ -1165,12 +1162,6 @@ sub is_valid
if ($keyword eq "EXPORT_VAR_AS_FUNCTION" && ($VMSVAX || $W32)) {
return 1;
}
if ($keyword eq "OPENSSL_FIPSCAPABLE") {
return 0;
}
if ($keyword eq "OPENSSL_FIPS" && $fips) {
return 1;
}
if ($keyword eq "ZLIB" && $zlib) { return 1; }
return 0;
} else {
......@@ -1555,7 +1546,6 @@ sub update_numbers
next if defined($rsyms{$sym});
die "ERROR: Symbol $sym had no info attached to it."
if $i eq "";
next if $i =~ /OPENSSL_FIPSCAPABLE/;
if (!exists $nums{$s}) {
$new_syms++;
my $s2 = $s;
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册