Skip to content
体验新版
项目
组织
正在加载...
登录
切换导航
打开侧边栏
openanolis
dragonwell8_jdk
提交
6d43dca5
D
dragonwell8_jdk
项目概览
openanolis
/
dragonwell8_jdk
通知
4
Star
2
Fork
0
代码
文件
提交
分支
Tags
贡献者
分支图
Diff
Issue
0
列表
看板
标记
里程碑
合并请求
0
Wiki
0
Wiki
分析
仓库
DevOps
项目成员
Pages
D
dragonwell8_jdk
项目概览
项目概览
详情
发布
仓库
仓库
文件
提交
分支
标签
贡献者
分支图
比较
Issue
0
Issue
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
Pages
分析
分析
仓库分析
DevOps
Wiki
0
Wiki
成员
成员
收起侧边栏
关闭侧边栏
动态
分支图
创建新Issue
提交
Issue看板
体验新版 GitCode,发现更多精彩内容 >>
提交
6d43dca5
编写于
10月 04, 2013
作者:
V
vinnie
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
8008296: keytool utility doesn't support '-importpassword' command
Reviewed-by: weijun
上级
17f3c537
变更
4
显示空白变更内容
内联
并排
Showing
4 changed file
with
440 addition
and
12 deletion
+440
-12
src/share/classes/sun/security/tools/keytool/Main.java
src/share/classes/sun/security/tools/keytool/Main.java
+104
-11
src/share/classes/sun/security/tools/keytool/Resources.java
src/share/classes/sun/security/tools/keytool/Resources.java
+10
-1
test/sun/security/tools/keytool/StorePasswords.java
test/sun/security/tools/keytool/StorePasswords.java
+186
-0
test/sun/security/tools/keytool/StorePasswordsByShell.sh
test/sun/security/tools/keytool/StorePasswordsByShell.sh
+140
-0
未找到文件。
src/share/classes/sun/security/tools/keytool/Main.java
浏览文件 @
6d43dca5
...
...
@@ -72,6 +72,8 @@ import sun.security.provider.certpath.CertStoreHelper;
import
sun.security.util.Password
;
import
javax.crypto.KeyGenerator
;
import
javax.crypto.SecretKey
;
import
javax.crypto.SecretKeyFactory
;
import
javax.crypto.spec.PBEKeySpec
;
import
sun.security.pkcs.PKCS9Attribute
;
import
sun.security.tools.KeyStoreUtil
;
...
...
@@ -190,6 +192,10 @@ public final class Main {
KEYPASS
,
KEYSTORE
,
STOREPASS
,
STORETYPE
,
PROVIDERNAME
,
PROVIDERCLASS
,
PROVIDERARG
,
PROVIDERPATH
,
V
),
IMPORTPASS
(
"Imports.a.password"
,
ALIAS
,
KEYPASS
,
KEYALG
,
KEYSIZE
,
KEYSTORE
,
STOREPASS
,
STORETYPE
,
PROVIDERNAME
,
PROVIDERCLASS
,
PROVIDERARG
,
PROVIDERPATH
,
V
,
PROTECTED
),
IMPORTKEYSTORE
(
"Imports.one.or.all.entries.from.another.keystore"
,
SRCKEYSTORE
,
DESTKEYSTORE
,
SRCSTORETYPE
,
DESTSTORETYPE
,
SRCSTOREPASS
,
DESTSTOREPASS
,
...
...
@@ -409,6 +415,8 @@ public final class Main {
command
=
GENKEYPAIR
;
}
else
if
(
collator
.
compare
(
flags
,
"-import"
)
==
0
)
{
command
=
IMPORTCERT
;
}
else
if
(
collator
.
compare
(
flags
,
"-importpassword"
)
==
0
)
{
command
=
IMPORTPASS
;
}
/*
* Help
...
...
@@ -727,6 +735,7 @@ public final class Main {
command
!=
GENSECKEY
&&
command
!=
IDENTITYDB
&&
command
!=
IMPORTCERT
&&
command
!=
IMPORTPASS
&&
command
!=
IMPORTKEYSTORE
&&
command
!=
PRINTCRL
)
{
throw
new
Exception
(
rb
.
getString
...
...
@@ -808,6 +817,7 @@ public final class Main {
command
==
GENKEYPAIR
||
command
==
GENSECKEY
||
command
==
IMPORTCERT
||
command
==
IMPORTPASS
||
command
==
IMPORTKEYSTORE
||
command
==
KEYCLONE
||
command
==
CHANGEALIAS
||
...
...
@@ -958,6 +968,13 @@ public final class Main {
}
doGenSecretKey
(
alias
,
keyAlgName
,
keysize
);
kssave
=
true
;
}
else
if
(
command
==
IMPORTPASS
)
{
if
(
keyAlgName
==
null
)
{
keyAlgName
=
"PBE"
;
}
// password is stored as a secret key
doGenSecretKey
(
alias
,
keyAlgName
,
keysize
);
kssave
=
true
;
}
else
if
(
command
==
IDENTITYDB
)
{
if
(
filename
!=
null
)
{
try
(
InputStream
inStream
=
new
FileInputStream
(
filename
))
{
...
...
@@ -1419,6 +1436,43 @@ public final class Main {
}
return
null
;
// PKCS11, MSCAPI, or -protected
}
/*
* Prompt the user for the password credential to be stored.
*/
private
char
[]
promptForCredential
()
throws
Exception
{
// Handle password supplied via stdin
if
(
System
.
console
()
==
null
)
{
char
[]
importPass
=
Password
.
readPassword
(
System
.
in
);
passwords
.
add
(
importPass
);
return
importPass
;
}
int
count
;
for
(
count
=
0
;
count
<
3
;
count
++)
{
System
.
err
.
print
(
rb
.
getString
(
"Enter.the.password.to.be.stored."
));
System
.
err
.
flush
();
char
[]
entered
=
Password
.
readPassword
(
System
.
in
);
passwords
.
add
(
entered
);
System
.
err
.
print
(
rb
.
getString
(
"Re.enter.password."
));
char
[]
passAgain
=
Password
.
readPassword
(
System
.
in
);
passwords
.
add
(
passAgain
);
if
(!
Arrays
.
equals
(
entered
,
passAgain
))
{
System
.
err
.
println
(
rb
.
getString
(
"They.don.t.match.Try.again"
));
continue
;
}
return
entered
;
}
if
(
count
==
3
)
{
throw
new
Exception
(
rb
.
getString
(
"Too.many.failures.key.not.added.to.keystore"
));
}
return
null
;
}
/**
* Creates a new secret key.
*/
...
...
@@ -1436,24 +1490,63 @@ public final class Main {
throw
new
Exception
(
form
.
format
(
source
));
}
// Use the keystore's default PBE algorithm for entry protection
boolean
useDefaultPBEAlgorithm
=
true
;
SecretKey
secKey
=
null
;
if
(
keyAlgName
.
toUpperCase
().
startsWith
(
"PBE"
))
{
SecretKeyFactory
factory
=
SecretKeyFactory
.
getInstance
(
"PBE"
);
// User is prompted for PBE credential
secKey
=
factory
.
generateSecret
(
new
PBEKeySpec
(
promptForCredential
()));
// Check whether a specific PBE algorithm was specified
if
(!
"PBE"
.
equalsIgnoreCase
(
keyAlgName
))
{
useDefaultPBEAlgorithm
=
false
;
}
if
(
verbose
)
{
MessageFormat
form
=
new
MessageFormat
(
rb
.
getString
(
"Generated.keyAlgName.secret.key"
));
Object
[]
source
=
{
useDefaultPBEAlgorithm
?
"PBE"
:
secKey
.
getAlgorithm
()};
System
.
err
.
println
(
form
.
format
(
source
));
}
}
else
{
KeyGenerator
keygen
=
KeyGenerator
.
getInstance
(
keyAlgName
);
if
(
keysize
!=
-
1
)
{
keygen
.
init
(
keysize
);
}
else
if
(
"DES"
.
equalsIgnoreCase
(
keyAlgName
))
{
keygen
.
init
(
56
);
if
(
keysize
==
-
1
)
{
if
(
"DES"
.
equalsIgnoreCase
(
keyAlgName
))
{
keysize
=
56
;
}
else
if
(
"DESede"
.
equalsIgnoreCase
(
keyAlgName
))
{
keygen
.
init
(
168
)
;
keysize
=
168
;
}
else
{
throw
new
Exception
(
rb
.
getString
(
"Please.provide.keysize.for.secret.key.generation"
));
}
}
keygen
.
init
(
keysize
);
secKey
=
keygen
.
generateKey
();
if
(
verbose
)
{
MessageFormat
form
=
new
MessageFormat
(
rb
.
getString
(
"Generated.keysize.bit.keyAlgName.secret.key"
));
Object
[]
source
=
{
new
Integer
(
keysize
),
secKey
.
getAlgorithm
()};
System
.
err
.
println
(
form
.
format
(
source
));
}
}
if
(
keyPass
==
null
)
{
keyPass
=
promptForKeyPass
(
alias
,
null
,
storePass
);
}
if
(
useDefaultPBEAlgorithm
)
{
keyStore
.
setKeyEntry
(
alias
,
secKey
,
keyPass
,
null
);
}
else
{
keyStore
.
setEntry
(
alias
,
new
KeyStore
.
SecretKeyEntry
(
secKey
),
new
KeyStore
.
PasswordProtection
(
keyPass
,
keyAlgName
,
null
));
}
}
/**
...
...
src/share/classes/sun/security/tools/keytool/Resources.java
浏览文件 @
6d43dca5
/*
* Copyright (c) 2000, 201
2
, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2000, 201
3
, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
...
...
@@ -65,10 +65,16 @@ public class Resources extends java.util.ListResourceBundle {
{
"Generates.certificate.from.a.certificate.request"
,
"Generates certificate from a certificate request"
},
//-gencert
{
"Generates.CRL"
,
"Generates CRL"
},
//-gencrl
{
"Generated.keyAlgName.secret.key"
,
"Generated {0} secret key"
},
//-genseckey
{
"Generated.keysize.bit.keyAlgName.secret.key"
,
"Generated {0}-bit {1} secret key"
},
//-genseckey
{
"Imports.entries.from.a.JDK.1.1.x.style.identity.database"
,
"Imports entries from a JDK 1.1.x-style identity database"
},
//-identitydb
{
"Imports.a.certificate.or.a.certificate.chain"
,
"Imports a certificate or a certificate chain"
},
//-importcert
{
"Imports.a.password"
,
"Imports a password"
},
//-importpass
{
"Imports.one.or.all.entries.from.another.keystore"
,
"Imports one or all entries from another keystore"
},
//-importkeystore
{
"Clones.a.key.entry"
,
...
...
@@ -220,6 +226,8 @@ public class Resources extends java.util.ListResourceBundle {
{
"Must.specify.alias"
,
"Must specify alias"
},
{
"Keystore.password.must.be.at.least.6.characters"
,
"Keystore password must be at least 6 characters"
},
{
"Enter.the.password.to.be.stored."
,
"Enter the password to be stored: "
},
{
"Enter.keystore.password."
,
"Enter keystore password: "
},
{
"Enter.source.keystore.password."
,
"Enter source keystore password: "
},
{
"Enter.destination.keystore.password."
,
"Enter destination keystore password: "
},
...
...
@@ -328,6 +336,7 @@ public class Resources extends java.util.ListResourceBundle {
{
"New.prompt."
,
"New {0}: "
},
{
"Passwords.must.differ"
,
"Passwords must differ"
},
{
"Re.enter.new.prompt."
,
"Re-enter new {0}: "
},
{
"Re.enter.passpword."
,
"Re-enter password: "
},
{
"Re.enter.new.password."
,
"Re-enter new password: "
},
{
"They.don.t.match.Try.again"
,
"They don't match. Try again"
},
{
"Enter.prompt.alias.name."
,
"Enter {0} alias name: "
},
...
...
test/sun/security/tools/keytool/StorePasswords.java
0 → 100644
浏览文件 @
6d43dca5
/*
* Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/*
* @test
* @bug 8008296
* @summary Store and retrieve user passwords using PKCS#12 keystore
*/
import
java.io.*
;
import
java.security.*
;
import
java.util.*
;
import
javax.crypto.*
;
import
javax.crypto.spec.*
;
/*
* Store and retrieve passwords protected by a selection of PBE algorithms,
* using a PKCS#12 keystore.
*/
public
class
StorePasswords
{
private
static
final
String
[]
PBE_ALGORITHMS
=
new
String
[]
{
"default PBE algorithm"
,
"PBEWithMD5AndDES"
,
"PBEWithSHA1AndDESede"
,
"PBEWithSHA1AndRC2_40"
,
"PBEWithSHA1AndRC2_128"
,
"PBEWithSHA1AndRC4_40"
,
"PBEWithSHA1AndRC4_128"
,
"PBEWithHmacSHA1AndAES_128"
,
"PBEWithHmacSHA224AndAES_128"
,
"PBEWithHmacSHA256AndAES_128"
,
"PBEWithHmacSHA384AndAES_128"
,
"PBEWithHmacSHA512AndAES_128"
,
"PBEWithHmacSHA1AndAES_256"
,
"PBEWithHmacSHA224AndAES_256"
,
"PBEWithHmacSHA256AndAES_256"
,
"PBEWithHmacSHA384AndAES_256"
,
"PBEWithHmacSHA512AndAES_256"
};
private
static
final
String
KEYSTORE
=
"mykeystore.p12"
;
private
static
final
char
[]
KEYSTORE_PWD
=
"changeit"
.
toCharArray
();
private
static
final
char
[]
ENTRY_PWD
=
"protectit"
.
toCharArray
();
private
static
final
char
[]
USER_PWD
=
"hello1"
.
toCharArray
();
public
static
void
main
(
String
[]
args
)
throws
Exception
{
new
File
(
KEYSTORE
).
delete
();
int
storeCount
=
store
();
int
recoverCount
=
recover
();
if
(
recoverCount
!=
storeCount
)
{
throw
new
Exception
(
"Stored "
+
storeCount
+
" user passwords, "
+
"recovered "
+
recoverCount
+
" user passwords"
);
}
System
.
out
.
println
(
"\nStored "
+
storeCount
+
" user passwords, "
+
"recovered "
+
recoverCount
+
" user passwords"
);
}
private
static
int
store
()
throws
Exception
{
int
count
=
0
;
// Load an empty PKCS#12 keystore
KeyStore
keystore
=
KeyStore
.
getInstance
(
"PKCS12"
);
System
.
out
.
println
(
"\nLoading PKCS#12 keystore..."
);
keystore
.
load
(
null
,
null
);
// Derive a PBE key from the password
PBEKeySpec
keySpec
=
new
PBEKeySpec
(
USER_PWD
);
SecretKeyFactory
factory
=
SecretKeyFactory
.
getInstance
(
"PBE"
);
SecretKey
key
=
factory
.
generateSecret
(
keySpec
);
PBEParameterSpec
specWithEightByteSalt
=
new
PBEParameterSpec
(
"NaClNaCl"
.
getBytes
(),
1024
);
// Store the user password in a keystore entry (for each algorithm)
for
(
String
algorithm
:
PBE_ALGORITHMS
)
{
try
{
System
.
out
.
println
(
"Storing user password '"
+
new
String
(
USER_PWD
)
+
"' (protected by "
+
algorithm
+
")"
);
if
(
algorithm
.
equals
(
"default PBE algorithm"
))
{
keystore
.
setKeyEntry
(
"this entry is protected by "
+
algorithm
,
key
,
ENTRY_PWD
,
null
);
}
else
{
keystore
.
setEntry
(
"this entry is protected by "
+
algorithm
,
new
KeyStore
.
SecretKeyEntry
(
key
),
new
KeyStore
.
PasswordProtection
(
ENTRY_PWD
,
algorithm
,
null
));
}
count
++;
}
catch
(
KeyStoreException
e
)
{
Throwable
inner
=
e
.
getCause
();
if
(
inner
instanceof
UnrecoverableKeyException
)
{
Throwable
inner2
=
inner
.
getCause
();
if
(
inner2
instanceof
InvalidAlgorithmParameterException
)
{
System
.
out
.
println
(
"...re-trying due to: "
+
inner2
.
getMessage
());
// Some PBE algorithms demand an 8-byte salt
keystore
.
setEntry
(
"this entry is protected by "
+
algorithm
,
new
KeyStore
.
SecretKeyEntry
(
key
),
new
KeyStore
.
PasswordProtection
(
ENTRY_PWD
,
algorithm
,
specWithEightByteSalt
));
count
++;
}
else
if
(
inner2
instanceof
InvalidKeyException
)
{
System
.
out
.
println
(
"...skipping due to: "
+
inner2
.
getMessage
());
// Unsupported crypto keysize
continue
;
}
}
else
{
throw
e
;
}
}
}
// Store the PKCS#12 keystore
System
.
out
.
println
(
"Storing PKCS#12 keystore to: "
+
KEYSTORE
);
keystore
.
store
(
new
FileOutputStream
(
KEYSTORE
),
KEYSTORE_PWD
);
return
count
;
}
private
static
int
recover
()
throws
Exception
{
int
count
=
0
;
// Load the PKCS#12 keystore
KeyStore
keystore
=
KeyStore
.
getInstance
(
"PKCS12"
);
System
.
out
.
println
(
"\nLoading PKCS#12 keystore from: "
+
KEYSTORE
);
keystore
.
load
(
new
FileInputStream
(
KEYSTORE
),
KEYSTORE_PWD
);
SecretKey
key
;
SecretKeyFactory
factory
;
PBEKeySpec
keySpec
;
// Retrieve each user password from the keystore
for
(
String
algorithm
:
PBE_ALGORITHMS
)
{
key
=
(
SecretKey
)
keystore
.
getKey
(
"this entry is protected by "
+
algorithm
,
ENTRY_PWD
);
if
(
key
!=
null
)
{
count
++;
factory
=
SecretKeyFactory
.
getInstance
(
key
.
getAlgorithm
());
keySpec
=
(
PBEKeySpec
)
factory
.
getKeySpec
(
key
,
PBEKeySpec
.
class
);
char
[]
pwd
=
keySpec
.
getPassword
();
System
.
out
.
println
(
"Recovered user password '"
+
new
String
(
pwd
)
+
"' (protected by "
+
algorithm
+
")"
);
if
(!
Arrays
.
equals
(
USER_PWD
,
pwd
))
{
throw
new
Exception
(
"Failed to recover the user password "
+
"protected by "
+
algorithm
);
}
}
}
return
count
;
}
}
test/sun/security/tools/keytool/StorePasswordsByShell.sh
0 → 100644
浏览文件 @
6d43dca5
#
# Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved.
# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
#
# This code is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License version 2 only, as
# published by the Free Software Foundation.
#
# This code is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
# version 2 for more details (a copy is included in the LICENSE file that
# accompanied this code).
#
# You should have received a copy of the GNU General Public License version
# 2 along with this work; if not, write to the Free Software Foundation,
# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
#
# Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
# or visit www.oracle.com if you need additional information or have any
# questions.
#
# @test
# @bug 8008296
# @summary confirm that keytool correctly imports user passwords
#
# @run shell StorePasswordsByShell.sh
# set a few environment variables so that the shell-script can run stand-alone
# in the source directory
if
[
"
${
TESTSRC
}
"
=
""
]
;
then
TESTSRC
=
"."
fi
if
[
"
${
TESTCLASSES
}
"
=
""
]
;
then
TESTCLASSES
=
"."
fi
if
[
"
${
TESTJAVA
}
"
=
""
]
;
then
echo
"TESTJAVA not set. Test cannot execute."
echo
"FAILED!!!"
exit
1
fi
# set platform-dependent variables
OS
=
`
uname
-s
`
case
"
$OS
"
in
SunOS
)
PATHSEP
=
":"
FILESEP
=
"/"
;;
Linux
)
PATHSEP
=
":"
FILESEP
=
"/"
;;
Darwin
)
PATHSEP
=
":"
FILESEP
=
"/"
;;
CYGWIN
*
)
PATHSEP
=
";"
FILESEP
=
"/"
;;
Windows
*
)
PATHSEP
=
";"
FILESEP
=
"
\\
"
;;
*
)
echo
"Unrecognized system!"
exit
1
;
;;
esac
PBE_ALGORITHMS
=
"
\
default-PBE-algorithm
\
PBEWithMD5AndDES
\
PBEWithSHA1AndDESede
\
PBEWithSHA1AndRC2_40
\
PBEWithSHA1AndRC2_128
PBEWithSHA1AndRC4_40
\
PBEWithSHA1AndRC4_128
\
PBEWithHmacSHA1AndAES_128
\
PBEWithHmacSHA224AndAES_128
\
PBEWithHmacSHA256AndAES_128
\
PBEWithHmacSHA384AndAES_128
\
PBEWithHmacSHA512AndAES_128
\
PBEWithHmacSHA1AndAES_256
\
PBEWithHmacSHA224AndAES_256
\
PBEWithHmacSHA256AndAES_256
\
PBEWithHmacSHA384AndAES_256
\
PBEWithHmacSHA512AndAES_256"
USER_PWD
=
"hello1
\n
"
ALIAS_PREFIX
=
"this entry is protected by "
COUNTER
=
0
# cleanup
rm
mykeystore.p12
>
/dev/null 2>&1
echo
for
i
in
$PBE_ALGORITHMS
;
do
if
[
$i
=
"default-PBE-algorithm"
]
;
then
KEYALG
=
""
else
KEYALG
=
"-keyalg
${
i
}
"
fi
if
[
$COUNTER
-lt
5
]
;
then
IMPORTPASSWORD
=
"-importpassword"
else
IMPORTPASSWORD
=
"-importpass"
fi
echo
"Storing user password (protected by
${
i
}
)"
echo
"
${
USER_PWD
}
"
|
\
${
TESTJAVA
}${
FILESEP
}
bin
${
FILESEP
}
keytool
${
IMPORTPASSWORD
}
\
-storetype
pkcs12
-keystore
mykeystore.p12
-storepass
changeit
\
-alias
"
${
ALIAS_PREFIX
}${
i
}
"
${
KEYALG
}
>
/dev/null 2>&1
if
[
$?
-ne
0
]
;
then
echo
Error
else
echo
OK
COUNTER
=
`
expr
${
COUNTER
}
+ 1
`
fi
done
echo
COUNTER2
=
`
${
TESTJAVA
}${
FILESEP
}
bin
${
FILESEP
}
keytool
-list
-storetype
pkcs12
\
-keystore
mykeystore.p12
-storepass
changeit |
grep
-c
"
${
ALIAS_PREFIX
}
"
`
RESULT
=
"stored
${
COUNTER
}
user passwords, detected
${
COUNTER2
}
user passwords"
if
[
$COUNTER
-ne
$COUNTER2
-o
$COUNTER
-lt
11
]
;
then
echo
"ERROR:
$RESULT
"
exit
1
else
echo
"OK:
$RESULT
"
exit
0
fi
编辑
预览
Markdown
is supported
0%
请重试
或
添加新附件
.
添加附件
取消
You are about to add
0
people
to the discussion. Proceed with caution.
先完成此消息的编辑!
取消
想要评论请
注册
或
登录