Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
S
sgxwallet
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
董子豪
sgxwallet
Commits
83599fbf
Unverified
Commit
83599fbf
authored
Sep 01, 2020
by
Stan Kladko
Committed by
GitHub
Sep 01, 2020
Browse files
Options
Browse Files
Download
Plain Diff
Merge branch 'develop' into SKALE-3189-fix-build
parents
54bd23a9
06f17ec4
Changes
11
Hide whitespace changes
Inline
Side-by-side
Showing
11 changed files
with
278 additions
and
190 deletions
+278
-190
Dockerfile
Dockerfile
+1
-1
DockerfileBase
DockerfileBase
+1
-1
DockerfileRelease
DockerfileRelease
+1
-1
DockerfileSimulation
DockerfileSimulation
+1
-1
Makefile.am
Makefile.am
+1
-1
SEKManager.cpp
SEKManager.cpp
+200
-167
Makefile.in
secure_enclave/Makefile.in
+2
-3
secure_enclave.c
secure_enclave/secure_enclave.c
+51
-11
sgxwall.cpp
sgxwall.cpp
+1
-1
testw.cpp
testw.cpp
+17
-2
testw.py
testw.py
+2
-1
No files found.
Dockerfile
View file @
83599fbf
FROM
skalenetwork/sgxwallet_base:latest
FROM
skalenetwork/sgxwallet_base:latest
COPY
. /usr/src/sdk
COPY
. /usr/src/sdk
RUN
apt update
&&
apt
install
-y
curl
RUN
apt update
&&
apt
install
-y
curl
secure-delete
WORKDIR
/usr/src/sdk
WORKDIR
/usr/src/sdk
RUN
touch
/var/hwmode
RUN
touch
/var/hwmode
RUN
./autoconf.bash
RUN
./autoconf.bash
...
...
DockerfileBase
View file @
83599fbf
FROM ubuntu:
bionic
FROM ubuntu:
xenial
COPY . /usr/src/sdk
COPY . /usr/src/sdk
...
...
DockerfileRelease
View file @
83599fbf
...
@@ -2,7 +2,7 @@ FROM skalenetwork/sgxwallet_base:latest
...
@@ -2,7 +2,7 @@ FROM skalenetwork/sgxwallet_base:latest
COPY . /usr/src/sdk
COPY . /usr/src/sdk
RUN cp -f secure_enclave/secure_enclave.config.xml.release secure_enclave/secure_enclave.config.xml
RUN cp -f secure_enclave/secure_enclave.config.xml.release secure_enclave/secure_enclave.config.xml
RUN apt update && apt install -y curl
RUN apt update && apt install -y curl
secure-delete
WORKDIR /usr/src/sdk
WORKDIR /usr/src/sdk
#Test signing key generation
#Test signing key generation
RUN cd scripts && ./generate_signing_key.bash
RUN cd scripts && ./generate_signing_key.bash
...
...
DockerfileSimulation
View file @
83599fbf
FROM skalenetwork/sgxwallet_base:latest
FROM skalenetwork/sgxwallet_base:latest
RUN apt update && apt install -y curl
RUN apt update && apt install -y curl
secure-delete
RUN ccache -sz
RUN ccache -sz
...
...
Makefile.am
View file @
83599fbf
...
@@ -102,7 +102,7 @@ sgxwallet_LDADD=-l$(SGX_URTS_LIB) -l$(SGX_UAE_SERVICE_LIB) -LlibBLS/deps/deps_in
...
@@ -102,7 +102,7 @@ sgxwallet_LDADD=-l$(SGX_URTS_LIB) -l$(SGX_UAE_SERVICE_LIB) -LlibBLS/deps/deps_in
-l
:libbls.a
-l
:libleveldb.a
\
-l
:libbls.a
-l
:libleveldb.a
\
-l
:libff.a
-lgmp
-ldl
-l
:libsgx_capable.a
-l
:libsgx_tprotected_fs.a
\
-l
:libff.a
-lgmp
-ldl
-l
:libsgx_capable.a
-l
:libsgx_tprotected_fs.a
\
-ljsonrpccpp-stub
-ljsonrpccpp-server
-ljsonrpccpp-client
-ljsonrpccpp-common
-ljsoncpp
-lmicrohttpd
\
-ljsonrpccpp-stub
-ljsonrpccpp-server
-ljsonrpccpp-client
-ljsonrpccpp-common
-ljsoncpp
-lmicrohttpd
\
-lboost_system
-lboost_thread
-lgnutls
-lgcrypt
-lcurl
-lssl
-lcrypto
-lz
-lpthread
-lboost_system
-lboost_thread
-lgnutls
-lgcrypt
-lcurl
-lssl
-lcrypto
-lz
-lpthread
-lstdc
++fs
testw_SOURCES
=
testw.cpp
$(COMMON_SRC)
testw_SOURCES
=
testw.cpp
$(COMMON_SRC)
...
...
SEKManager.cpp
View file @
83599fbf
...
@@ -37,213 +37,246 @@
...
@@ -37,213 +37,246 @@
#include "ServerDataChecker.h"
#include "ServerDataChecker.h"
#include "third_party/spdlog/spdlog.h"
#include "third_party/spdlog/spdlog.h"
using
namespace
std
;
#define BACKUP_PATH "./sgx_data/sgxwallet_backup_key.txt"
bool
case_insensitive_match
(
string
s1
,
string
s2
)
{
bool
case_insensitive_match
(
string
s1
,
string
s2
)
{
//convert s1 and s2 into lower case strings
//convert s1 and s2 into lower case strings
transform
(
s1
.
begin
(),
s1
.
end
(),
s1
.
begin
(),
::
tolower
);
transform
(
s1
.
begin
(),
s1
.
end
(),
s1
.
begin
(),
::
tolower
);
transform
(
s2
.
begin
(),
s2
.
end
(),
s2
.
begin
(),
::
tolower
);
transform
(
s2
.
begin
(),
s2
.
end
(),
s2
.
begin
(),
::
tolower
);
return
s1
.
compare
(
s2
);
return
s1
.
compare
(
s2
);
}
}
void
create_test_key
()
{
void
create_test_key
()
{
int
errStatus
=
0
;
int
errStatus
=
0
;
vector
<
char
>
errMsg
(
1024
,
0
);
vector
<
char
>
errMsg
(
1024
,
0
);
uint32_t
enc_len
;
uint32_t
enc_len
;
uint8_t
encrypted_key
[
BUF_LEN
];
uint8_t
encrypted_key
[
BUF_LEN
];
memset
(
encrypted_key
,
0
,
BUF_LEN
);
memset
(
encrypted_key
,
0
,
BUF_LEN
);
std
::
string
key
=
TEST_VALUE
;
string
key
=
TEST_VALUE
;
status
=
trustedEncryptKeyAES
(
eid
,
&
errStatus
,
errMsg
.
data
(),
key
.
c_str
(),
encrypted_key
,
&
enc_len
);
status
=
trustedEncryptKeyAES
(
eid
,
&
errStatus
,
errMsg
.
data
(),
key
.
c_str
(),
encrypted_key
,
&
enc_len
);
if
(
status
!=
SGX_SUCCESS
)
{
if
(
status
!=
SGX_SUCCESS
)
{
std
::
cerr
<<
"encrypt test key failed with status "
<<
status
<<
std
::
endl
;
cerr
<<
"encrypt test key failed with status "
<<
status
<<
endl
;
throw
SGXException
(
status
,
errMsg
.
data
())
;
throw
SGXException
(
status
,
errMsg
.
data
())
;
}
}
if
(
errStatus
!=
0
)
{
if
(
errStatus
!=
0
)
{
std
::
cerr
<<
"encrypt test key failed with status "
<<
errStatus
<<
std
::
endl
;
cerr
<<
"encrypt test key failed with status "
<<
errStatus
<<
endl
;
throw
SGXException
(
errStatus
,
errMsg
.
data
())
;
throw
SGXException
(
errStatus
,
errMsg
.
data
())
;
}
}
vector
<
char
>
hexEncrKey
(
2
*
enc_len
+
1
,
0
);
vector
<
char
>
hexEncrKey
(
2
*
enc_len
+
1
,
0
);
carray2Hex
(
encrypted_key
,
enc_len
,
hexEncrKey
.
data
());
carray2Hex
(
encrypted_key
,
enc_len
,
hexEncrKey
.
data
());
uint64_t
test_len
;
uint64_t
test_len
;
vector
<
uint8_t
>
test_encr_key
(
1024
,
0
);
vector
<
uint8_t
>
test_encr_key
(
1024
,
0
);
if
(
!
hex2carray
(
hexEncrKey
.
data
(),
&
test_len
,
test_encr_key
.
data
()))
{
if
(
!
hex2carray
(
hexEncrKey
.
data
(),
&
test_len
,
test_encr_key
.
data
()))
{
std
::
cerr
<<
"wrong encrypted test key"
<<
std
::
endl
;
cerr
<<
"wrong encrypted test key"
<<
endl
;
}
}
LevelDB
::
getLevelDb
()
->
writeDataUnique
(
"TEST_KEY"
,
hexEncrKey
.
data
());
LevelDB
::
getLevelDb
()
->
writeDataUnique
(
"TEST_KEY"
,
hexEncrKey
.
data
());
}
}
bool
check_SEK
(
const
std
::
string
&
SEK
)
{
std
::
shared_ptr
<
std
::
string
>
test_key_ptr
=
LevelDB
::
getLevelDb
()
->
readString
(
"TEST_KEY"
);
shared_ptr
<
vector
<
uint8_t
>>
check_and_set_SEK
(
const
string
&
SEK
)
{
vector
<
uint8_t
>
encr_test_key
(
BUF_LEN
,
0
);
shared_ptr
<
string
>
test_key_ptr
=
LevelDB
::
getLevelDb
()
->
readString
(
"TEST_KEY"
);
uint64_t
len
;
vector
<
uint8_t
>
encr_test_key
(
BUF_LEN
,
0
);
if
(
!
hex2carray
(
test_key_ptr
->
c_str
(),
&
len
,
encr_test_key
.
data
()))
{
uint64_t
len
;
spdlog
::
error
(
"wrong test key"
);
if
(
!
hex2carray
(
test_key_ptr
->
c_str
(),
&
len
,
encr_test_key
.
data
()))
{
exit
(
-
1
);
spdlog
::
error
(
"wrong test key"
);
}
exit
(
-
1
);
}
vector
<
char
>
decr_key
(
1024
,
0
);
vector
<
char
>
errMsg
(
1024
,
0
);
vector
<
char
>
decr_key
(
1024
,
0
);
int
err_status
=
0
;
vector
<
char
>
errMsg
(
1024
,
0
);
int
err_status
=
0
;
vector
<
uint8_t
>
encr_SEK
(
1024
,
0
);
auto
encrypted_SEK
=
make_shared
<
vector
<
uint8_t
>>
(
1024
,
0
);
uint32_t
l
=
len
;
uint32_t
l
=
len
;
status
=
trustedSetSEK_backup
(
eid
,
&
err_status
,
errMsg
.
data
(),
encr_SEK
.
data
(),
&
l
,
SEK
.
c_str
()
);
if
(
status
!=
SGX_SUCCESS
)
{
status
=
trustedSetSEK_backup
(
eid
,
&
err_status
,
errMsg
.
data
(),
encrypted_SEK
->
data
(),
&
l
,
SEK
.
c_str
());
cerr
<<
"RPCException thrown with status "
<<
status
<<
endl
;
throw
SGXException
(
status
,
errMsg
.
data
());
if
(
status
!=
SGX_SUCCESS
)
{
}
spdlog
::
error
(
"trustedSetSEK_backup failed with error code {}"
,
status
);
exit
(
-
1
);
if
(
err_status
!=
0
)
{
}
cerr
<<
"RPCException thrown with status "
<<
err_status
<<
endl
;
throw
SGXException
(
err_status
,
errMsg
.
data
());
if
(
err_status
!=
0
)
{
}
spdlog
::
error
(
"trustedSetSEK_backup failed with error status {}"
,
status
);
exit
(
-
1
);
status
=
trustedDecryptKeyAES
(
eid
,
&
err_status
,
errMsg
.
data
(),
encr_test_key
.
data
(),
len
,
decr_key
.
data
());
}
if
(
status
!=
SGX_SUCCESS
||
err_status
!=
0
)
{
spdlog
::
error
(
"failed to decrypt test key"
);
status
=
trustedDecryptKeyAES
(
eid
,
&
err_status
,
errMsg
.
data
(),
encr_test_key
.
data
(),
len
,
decr_key
.
data
());
spdlog
::
error
(
errMsg
.
data
());
if
(
status
!=
SGX_SUCCESS
||
err_status
!=
0
)
{
exit
(
-
1
);
spdlog
::
error
(
"Failed to decrypt test key"
);
}
spdlog
::
error
(
errMsg
.
data
());
exit
(
-
1
);
std
::
string
test_key
=
TEST_VALUE
;
}
if
(
test_key
.
compare
(
decr_key
.
data
())
!=
0
)
{
std
::
cerr
<<
"decrypted key is "
<<
decr_key
.
data
()
<<
std
::
endl
;
string
test_key
=
TEST_VALUE
;
spdlog
::
error
(
"Invalid SEK"
);
if
(
test_key
.
compare
(
decr_key
.
data
())
!=
0
)
{
return
false
;
spdlog
::
error
(
"Invalid SEK"
);
}
exit
(
-
1
);
return
true
;
}
encrypted_SEK
->
resize
(
l
);
return
encrypted_SEK
;
}
}
void
gen_SEK
()
{
void
gen_SEK
()
{
vector
<
char
>
errMsg
(
1024
,
0
);
vector
<
char
>
errMsg
(
1024
,
0
);
int
err_status
=
0
;
int
err_status
=
0
;
vector
<
uint8_t
>
encr_SEK
(
1024
,
0
);
vector
<
uint8_t
>
encrypted_SEK
(
1024
,
0
);
uint32_t
enc_len
=
0
;
uint32_t
enc_len
=
0
;
char
SEK
[
65
];
memset
(
SEK
,
0
,
65
);
spdlog
::
info
(
"Generating backup key. Will be stored in backup_key.txt ... "
);
status
=
trustedGenerateSEK
(
eid
,
&
err_status
,
errMsg
.
data
(),
encrypted_SEK
.
data
(),
&
enc_len
,
SEK
);
if
(
status
!=
SGX_SUCCESS
)
{
throw
SGXException
(
status
,
errMsg
.
data
());
}
if
(
err_status
!=
0
)
{
throw
SGXException
(
err_status
,
errMsg
.
data
());
}
if
(
strnlen
(
SEK
,
33
)
!=
32
)
{
throw
SGXException
(
-
1
,
"strnlen(SEK,33) != 32"
);
}
vector
<
char
>
hexEncrKey
(
2
*
enc_len
+
1
,
0
);
char
SEK
[
65
];
carray2Hex
(
encrypted_SEK
.
data
(),
enc_len
,
hexEncrKey
.
data
());
memset
(
SEK
,
0
,
65
);
status
=
trustedGenerateSEK
(
eid
,
&
err_status
,
errMsg
.
data
(),
encr_SEK
.
data
(),
&
enc_len
,
SEK
);
ofstream
sek_file
(
BACKUP_PATH
);
if
(
status
!=
SGX_SUCCESS
)
{
sek_file
.
clear
();
throw
SGXException
(
status
,
errMsg
.
data
())
;
}
if
(
err_status
!=
0
)
{
sek_file
<<
SEK
;
throw
SGXException
(
err_status
,
errMsg
.
data
())
;
}
vector
<
char
>
hexEncrKey
(
2
*
enc_len
+
1
,
0
);
carray2Hex
(
encr_SEK
.
data
(),
enc_len
,
hexEncrKey
.
data
());
cout
<<
"ATTENTION! YOUR BACKUP KEY HAS BEEN WRITTEN INTO sgx_data/backup_key.txt
\n
"
<<
"PLEASE COPY IT TO THE SAFE PLACE AND THEN DELETE THE FILE MANUALLY BY RUNNING THE FOLLOWING COMMAND:
\n
"
<<
"apt-get install secure-delete && srm -vz sgx_data/backup_key.txt"
<<
endl
;
std
::
ofstream
sek_file
(
"backup_key.txt"
);
sek_file
.
clear
();
cout
<<
"ATTENTION! YOUR BACKUP KEY WILL BE WRITTEN INTO backup_key.txt.
\n
"
<<
if
(
!
autoconfirm
)
{
"PLEASE COPY IT TO THE SAFE PLACE AND THEN DELETE THE FILE MANUALLY BY RUNNING THE FOLLOWING COMMAND:
\n
"
<<
string
confirm_str
=
"I confirm"
;
"`docker exec -it <SGX_CONTAINER_NAME> bash && apt-get install secure-delete && srm -vz backup_key.txt`"
<<
endl
;
string
buffer
;
sek_file
<<
SEK
;
do
{
cout
<<
" DO YOU CONFIRM THAT YOU COPIED THE KEY? (if you confirm type - I confirm)"
<<
endl
;
getline
(
cin
,
buffer
);
}
while
(
case_insensitive_match
(
confirm_str
,
buffer
));
}
if
(
!
autoconfirm
)
{
std
::
string
confirm_str
=
"I confirm"
;
std
::
string
buffer
;
do
{
std
::
cout
<<
" DO YOU CONFIRM THAT YOU COPIED THE KEY? (if you confirm type - I confirm)"
<<
std
::
endl
;
std
::
getline
(
std
::
cin
,
buffer
);
}
while
(
case_insensitive_match
(
confirm_str
,
buffer
));
}
LevelDB
::
getLevelDb
()
->
writeDataUnique
(
"SEK"
,
hexEncrKey
.
data
());
LevelDB
::
getLevelDb
()
->
writeDataUnique
(
"SEK"
,
hexEncrKey
.
data
());
create_test_key
();
create_test_key
();
}
}
void
trustedSetSEK
(
s
td
::
shared_ptr
<
std
::
string
>
hex_encr
_SEK
)
{
void
trustedSetSEK
(
s
hared_ptr
<
string
>
hex_encrypted
_SEK
)
{
vector
<
char
>
errMsg
(
1024
,
0
);
vector
<
char
>
errMsg
(
1024
,
0
);
int
err_status
=
0
;
int
err_status
=
0
;
uint8_t
encr
_SEK
[
BUF_LEN
];
uint8_t
encrypted
_SEK
[
BUF_LEN
];
memset
(
encr
_SEK
,
0
,
BUF_LEN
);
memset
(
encrypted
_SEK
,
0
,
BUF_LEN
);
uint64_t
len
;
uint64_t
len
;
if
(
!
hex2carray
(
hex_encr_SEK
->
c_str
(),
&
len
,
encr
_SEK
))
{
if
(
!
hex2carray
(
hex_encrypted_SEK
->
c_str
(),
&
len
,
encrypted
_SEK
))
{
throw
SGXException
(
INVALID_HEX
,
"Invalid encrypted SEK Hex"
);
throw
SGXException
(
INVALID_HEX
,
"Invalid encrypted SEK Hex"
);
}
}
status
=
trustedSetSEK
(
eid
,
&
err_status
,
errMsg
.
data
(),
encr_SEK
);
status
=
trustedSetSEK
(
eid
,
&
err_status
,
errMsg
.
data
(),
encrypted_SEK
);
if
(
status
!=
SGX_SUCCESS
)
{
if
(
status
!=
SGX_SUCCESS
)
{
cerr
<<
"RPCException thrown"
<<
endl
;
cerr
<<
"RPCException thrown"
<<
endl
;
throw
SGXException
(
status
,
errMsg
.
data
())
;
throw
SGXException
(
status
,
errMsg
.
data
())
;
}
}
if
(
err_status
!=
0
)
{
if
(
err_status
!=
0
)
{
cerr
<<
"RPCException thrown"
<<
endl
;
cerr
<<
"RPCException thrown"
<<
endl
;
throw
SGXException
(
err_status
,
errMsg
.
data
())
;
throw
SGXException
(
err_status
,
errMsg
.
data
())
;
}
}
}
}
#include "experimental/filesystem"
#include <boost/algorithm/string.hpp>
void
enter_SEK
()
{
void
enter_SEK
()
{
vector
<
char
>
errMsg
(
1024
,
0
);
int
err_status
=
0
;
vector
<
uint8_t
>
encr_SEK
(
BUF_LEN
,
0
);
shared_ptr
<
string
>
test_key_ptr
=
LevelDB
::
getLevelDb
()
->
readString
(
"TEST_KEY"
);
uint32_t
enc_len
;
if
(
test_key_ptr
==
nullptr
)
{
spdlog
::
error
(
"Error: corrupt or empty LevelDB database"
);
std
::
shared_ptr
<
std
::
string
>
test_key_ptr
=
LevelDB
::
getLevelDb
()
->
readString
(
"TEST_KEY"
);
exit
(
-
1
);
if
(
test_key_ptr
==
nullptr
)
{
}
spdlog
::
error
(
"empty db"
);
exit
(
-
1
);
}
if
(
!
experimental
::
filesystem
::
is_regular_file
(
BACKUP_PATH
))
{
spdlog
::
error
(
"File does not exist: "
BACKUP_PATH
);
std
::
string
SEK
;
exit
(
-
1
);
std
::
cout
<<
"ENTER BACKUP KEY"
<<
std
::
endl
;
}
std
::
cin
>>
SEK
;
while
(
!
checkHex
(
SEK
,
16
)
||
!
check_SEK
(
SEK
))
{
ifstream
sek_file
(
BACKUP_PATH
);
std
::
cout
<<
"KEY IS INVALID.TRY ONCE MORE"
<<
std
::
endl
;
SEK
=
""
;
spdlog
::
info
(
"Reading backup key from file ..."
);
std
::
cin
>>
SEK
;
}
string
sek
((
istreambuf_iterator
<
char
>
(
sek_file
)),
istreambuf_iterator
<
char
>
());
status
=
trustedSetSEK_backup
(
eid
,
&
err_status
,
errMsg
.
data
(),
encr_SEK
.
data
(),
&
enc_len
,
SEK
.
c_str
());
if
(
status
!=
SGX_SUCCESS
)
{
boost
::
trim
(
sek
);
cerr
<<
"RPCException thrown with status "
<<
status
<<
endl
;
throw
SGXException
(
status
,
errMsg
.
data
());
spdlog
::
info
(
"Setting backup key ..."
);
}
while
(
!
checkHex
(
sek
,
16
))
{
if
(
err_status
!=
0
)
{
spdlog
::
error
(
"Invalid hex in key"
);
cerr
<<
"RPCException thrown"
<<
endl
;
exit
(
-
1
);
throw
SGXException
(
err_status
,
errMsg
.
data
())
;
}
}
auto
encrypted_SEK
=
check_and_set_SEK
(
sek
);
vector
<
char
>
hexEncrKey
(
2
*
enc_len
+
1
,
0
);
vector
<
char
>
hexEncrKey
(
BUF_LEN
,
0
);
carray2Hex
(
encr_SEK
.
data
(),
enc_len
,
hexEncrKey
.
data
());
carray2Hex
(
encrypted_SEK
->
data
(),
encrypted_SEK
->
size
(),
hexEncrKey
.
data
());
LevelDB
::
getLevelDb
()
->
deleteKey
(
"SEK"
);
LevelDB
::
getLevelDb
()
->
writeDataUnique
(
"SEK"
,
hexEncrKey
.
data
());
spdlog
::
info
(
"Got sealed storage encryption key."
);
LevelDB
::
getLevelDb
()
->
deleteKey
(
"SEK"
);
spdlog
::
info
(
"Storing sealed storage encryption key in LevelDB ..."
);
LevelDB
::
getLevelDb
()
->
writeDataUnique
(
"SEK"
,
hexEncrKey
.
data
());
spdlog
::
info
(
"Stored storage encryption key in LevelDB."
);
}
}
void
initSEK
()
{
void
initSEK
()
{
std
::
shared_ptr
<
std
::
string
>
encr
_SEK_ptr
=
LevelDB
::
getLevelDb
()
->
readString
(
"SEK"
);
shared_ptr
<
string
>
encrypted
_SEK_ptr
=
LevelDB
::
getLevelDb
()
->
readString
(
"SEK"
);
if
(
enterBackupKey
)
{
if
(
enterBackupKey
)
{
enter_SEK
();
enter_SEK
();
}
else
{
}
else
{
if
(
encr
_SEK_ptr
==
nullptr
)
{
if
(
encrypted
_SEK_ptr
==
nullptr
)
{
spdlog
::
warn
(
"SEK was not created yet. Going to create SEK"
);
spdlog
::
warn
(
"SEK was not created yet. Going to create SEK"
);
gen_SEK
();
gen_SEK
();
}
else
{
}
else
{
trustedSetSEK
(
encr
_SEK_ptr
);
trustedSetSEK
(
encrypted
_SEK_ptr
);
}
}
}
}
}
}
//a002e7ca685d46a32771d16fe2518e58
//a002e7ca685d46a32771d16fe2518e58
secure_enclave/Makefile.in
View file @
83599fbf
...
@@ -323,8 +323,7 @@ AM_CFLAGS = @SGX_ENCLAVE_CFLAGS@
...
@@ -323,8 +323,7 @@ AM_CFLAGS = @SGX_ENCLAVE_CFLAGS@
AM_CPPFLAGS
=
@SGX_ENCLAVE_CPPFLAGS@
-Wall
\
AM_CPPFLAGS
=
@SGX_ENCLAVE_CPPFLAGS@
-Wall
\
-Wno-implicit-function-declaration
$(TGMP_CPPFLAGS)
\
-Wno-implicit-function-declaration
$(TGMP_CPPFLAGS)
\
-I
./third_party/SCIPR
-I
../third_party/SCIPR
\
-I
./third_party/SCIPR
-I
../third_party/SCIPR
\
-I
../sgx-sdk-build/sgxsdk/include/libcxx
\
-I
../sgx-sdk-build/sgxsdk/include/libcxx
-I
../intel-sgx-ssl/Linux/package/include
AM_CXXFLAGS
=
@SGX_ENCLAVE_CXXFLAGS@ @SGX_ENCLAVE_CFLAGS@
-fno-builtin
\
AM_CXXFLAGS
=
@SGX_ENCLAVE_CXXFLAGS@ @SGX_ENCLAVE_CFLAGS@
-fno-builtin
\
-fstack-protector-strong
-fstack-protector-strong
AM_LDFLAGS
=
@SGX_ENCLAVE_LDFLAGS@
$(TGMP_LDFLAGS)
-L
./tgmp-build/lib
\
AM_LDFLAGS
=
@SGX_ENCLAVE_LDFLAGS@
$(TGMP_LDFLAGS)
-L
./tgmp-build/lib
\
...
@@ -344,7 +343,7 @@ secure_enclave_SOURCES = secure_enclave_t.c secure_enclave_t.h \
...
@@ -344,7 +343,7 @@ secure_enclave_SOURCES = secure_enclave_t.c secure_enclave_t.h \
../third_party/SCIPR/libff/algebra/curves/alt_bn128/alt_bn128_g1.cpp
$(ENCLAVE_KEY)
$(ENCLAVE_CONFIG)
../third_party/SCIPR/libff/algebra/curves/alt_bn128/alt_bn128_g1.cpp
$(ENCLAVE_KEY)
$(ENCLAVE_CONFIG)
secure_enclave_LDADD
=
@SGX_ENCLAVE_LDADD@
secure_enclave_LDADD
=
@SGX_ENCLAVE_LDADD@
SGX_EXTRA_TLIBS
=
-lsgx_tgmp
-lsgx_tservice
-lsgx_urts
-lsgx_tcxx
../intel-sgx-ssl/Linux/package/lib64/libsgx_tsgxssl_crypto.a
SGX_EXTRA_TLIBS
=
-lsgx_tgmp
-lsgx_tservice
-lsgx_urts
-lsgx_tcxx
all
:
all-am
all
:
all-am
.SUFFIXES
:
.SUFFIXES
:
...
...
secure_enclave/secure_enclave.c
View file @
83599fbf
...
@@ -84,7 +84,8 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
...
@@ -84,7 +84,8 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
goto clean;}
goto clean;}
#define CHECK_STATUS(__ERRMESSAGE__) if (status != SGX_SUCCESS) { \
#define CHECK_STATUS(__ERRMESSAGE__) if (status != SGX_SUCCESS) { \
snprintf(errString, BUF_LEN, __ERRMESSAGE__); \
LOG_ERROR(__FUNCTION__); \
snprintf(errString, BUF_LEN, "failed with status %d : %s", status, __ERRMESSAGE__); \
LOG_ERROR(errString); \
LOG_ERROR(errString); \
*errStatus = status; \
*errStatus = status; \
goto clean; \
goto clean; \
...
@@ -112,7 +113,17 @@ void free_function(void *, size_t);
...
@@ -112,7 +113,17 @@ void free_function(void *, size_t);
unsigned
char
*
globalRandom
;
unsigned
char
*
globalRandom
;
#define CALL_ONCE \
static volatile bool called = false;\
if (called) { \
LOG_ERROR(__FUNCTION__); \
LOG_ERROR("This function shouldnt be called twice. Aborting!"); \
abort(); \
} else {called = true;};
void
trustedEnclaveInit
(
uint32_t
_logLevel
)
{
void
trustedEnclaveInit
(
uint32_t
_logLevel
)
{
CALL_ONCE
LOG_INFO
(
__FUNCTION__
);
LOG_INFO
(
__FUNCTION__
);
globalLogLevel_
=
_logLevel
;
globalLogLevel_
=
_logLevel
;
...
@@ -130,7 +141,7 @@ void trustedEnclaveInit(uint32_t _logLevel) {
...
@@ -130,7 +141,7 @@ void trustedEnclaveInit(uint32_t _logLevel) {
globalRandom
=
calloc
(
32
,
1
);
globalRandom
=
calloc
(
32
,
1
);
auto
ret
=
sgx_read_rand
(
globalRandom
,
32
);
int
ret
=
sgx_read_rand
(
globalRandom
,
32
);
if
(
ret
!=
SGX_SUCCESS
)
if
(
ret
!=
SGX_SUCCESS
)
{
{
...
@@ -200,7 +211,6 @@ void *reallocate_function(void *ptr, size_t osize, size_t nsize) {
...
@@ -200,7 +211,6 @@ void *reallocate_function(void *ptr, size_t osize, size_t nsize) {
}
}
void
get_global_random
(
unsigned
char
*
_randBuff
,
uint64_t
_size
)
{
void
get_global_random
(
unsigned
char
*
_randBuff
,
uint64_t
_size
)
{
char
errString
[
BUF_LEN
];
char
errString
[
BUF_LEN
];
int
status
;
int
status
;
int
*
errStatus
=
&
status
;
int
*
errStatus
=
&
status
;
...
@@ -223,11 +233,13 @@ void get_global_random(unsigned char *_randBuff, uint64_t _size) {
...
@@ -223,11 +233,13 @@ void get_global_random(unsigned char *_randBuff, uint64_t _size) {
void
sealHexSEK
(
int
*
errStatus
,
char
*
errString
,
void
sealHexSEK
(
int
*
errStatus
,
char
*
errString
,
uint8_t
*
encrypted_sek
,
uint32_t
*
enc_len
,
char
*
sek_hex
)
{
uint8_t
*
encrypted_sek
,
uint32_t
*
enc_len
,
char
*
sek_hex
)
{
CALL_ONCE
LOG_INFO
(
__FUNCTION__
);
LOG_INFO
(
__FUNCTION__
);
INIT_ERROR_STATE
INIT_ERROR_STATE
CHECK_STATE
(
encrypted_sek
);
CHECK_STATE
(
encrypted_sek
);
CHECK_STATE
(
sek_hex
);
CHECK_STATE
(
sek_hex
);
CHECK_STATE
(
strnlen
(
sek_hex
,
33
)
==
32
)
uint64_t
plaintextLen
=
strlen
(
sek_hex
+
1
);
uint64_t
plaintextLen
=
strlen
(
sek_hex
+
1
);
...
@@ -262,15 +274,17 @@ void sealHexSEK(int *errStatus, char *errString,
...
@@ -262,15 +274,17 @@ void sealHexSEK(int *errStatus, char *errString,
SET_SUCCESS
SET_SUCCESS
clean:
clean:
;
LOG_INFO
(
__FUNCTION__
)
;
LOG_INFO
(
"SGX call completed"
);
LOG_INFO
(
"SGX call completed"
);
}
}
void
trustedGenerateSEK
(
int
*
errStatus
,
char
*
errString
,
void
trustedGenerateSEK
(
int
*
errStatus
,
char
*
errString
,
uint8_t
*
encrypted_sek
,
uint32_t
*
enc_len
,
char
*
sek_hex
)
{
uint8_t
*
encrypted_sek
,
uint32_t
*
enc_len
,
char
*
sek_hex
)
{
CALL_ONCE
LOG_INFO
(
__FUNCTION__
);
LOG_INFO
(
__FUNCTION__
);
INIT_ERROR_STATE
INIT_ERROR_STATE
CHECK_STATE
(
encrypted_sek
);
CHECK_STATE
(
encrypted_sek
);
CHECK_STATE
(
sek_hex
);
CHECK_STATE
(
sek_hex
);
...
@@ -281,18 +295,19 @@ void trustedGenerateSEK(int *errStatus, char *errString,
...
@@ -281,18 +295,19 @@ void trustedGenerateSEK(int *errStatus, char *errString,
sealHexSEK
(
errStatus
,
errString
,
encrypted_sek
,
enc_len
,
sek_hex
);
sealHexSEK
(
errStatus
,
errString
,
encrypted_sek
,
enc_len
,
sek_hex
);
if
(
errStatus
!=
0
)
{
if
(
*
errStatus
!=
0
)
{
LOG_ERROR
(
"sealHexSEK failed"
);
LOG_ERROR
(
"sealHexSEK failed"
);
goto
clean
;
goto
clean
;
}
}
SET_SUCCESS
SET_SUCCESS
clean:
clean:
;
LOG_INFO
(
__FUNCTION__
)
;
LOG_INFO
(
"SGX call completed"
);
LOG_INFO
(
"SGX call completed"
);
}
}
void
trustedSetSEK
(
int
*
errStatus
,
char
*
errString
,
uint8_t
*
encrypted_sek
)
{
void
trustedSetSEK
(
int
*
errStatus
,
char
*
errString
,
uint8_t
*
encrypted_sek
)
{
CALL_ONCE
LOG_INFO
(
__FUNCTION__
);
LOG_INFO
(
__FUNCTION__
);
INIT_ERROR_STATE
INIT_ERROR_STATE
CHECK_STATE
(
encrypted_sek
);
CHECK_STATE
(
encrypted_sek
);
...
@@ -319,12 +334,13 @@ void trustedSetSEK(int *errStatus, char *errString, uint8_t *encrypted_sek) {
...
@@ -319,12 +334,13 @@ void trustedSetSEK(int *errStatus, char *errString, uint8_t *encrypted_sek) {
SET_SUCCESS
SET_SUCCESS
clean:
clean:
;
LOG_INFO
(
__FUNCTION__
)
;
LOG_INFO
(
"SGX call completed"
);
LOG_INFO
(
"SGX call completed"
);
}
}
void
trustedSetSEK_backup
(
int
*
errStatus
,
char
*
errString
,
void
trustedSetSEK_backup
(
int
*
errStatus
,
char
*
errString
,
uint8_t
*
encrypted_sek
,
uint32_t
*
enc_len
,
const
char
*
sek_hex
)
{
uint8_t
*
encrypted_sek
,
uint32_t
*
enc_len
,
const
char
*
sek_hex
)
{
CALL_ONCE
LOG_INFO
(
__FUNCTION__
);
LOG_INFO
(
__FUNCTION__
);
INIT_ERROR_STATE
INIT_ERROR_STATE
...
@@ -336,7 +352,7 @@ void trustedSetSEK_backup(int *errStatus, char *errString,
...
@@ -336,7 +352,7 @@ void trustedSetSEK_backup(int *errStatus, char *errString,
sealHexSEK
(
errStatus
,
errString
,
encrypted_sek
,
enc_len
,
(
char
*
)
sek_hex
);
sealHexSEK
(
errStatus
,
errString
,
encrypted_sek
,
enc_len
,
(
char
*
)
sek_hex
);
if
(
errStatus
!=
0
)
{
if
(
*
errStatus
!=
0
)
{
LOG_ERROR
(
"sealHexSEK failed"
);
LOG_ERROR
(
"sealHexSEK failed"
);
goto
clean
;
goto
clean
;
}
}
...
@@ -344,6 +360,7 @@ void trustedSetSEK_backup(int *errStatus, char *errString,
...
@@ -344,6 +360,7 @@ void trustedSetSEK_backup(int *errStatus, char *errString,
SET_SUCCESS
SET_SUCCESS
clean:
clean:
;
;
LOG_INFO
(
__FUNCTION__
);
LOG_INFO
(
"SGX call completed"
);
LOG_INFO
(
"SGX call completed"
);
}
}
...
@@ -414,6 +431,7 @@ void trustedGenerateEcdsaKeyAES(int *errStatus, char *errString,
...
@@ -414,6 +431,7 @@ void trustedGenerateEcdsaKeyAES(int *errStatus, char *errString,
mpz_clear
(
seed
);
mpz_clear
(
seed
);
mpz_clear
(
skey
);
mpz_clear
(
skey
);
point_clear
(
Pkey
);
point_clear
(
Pkey
);
LOG_INFO
(
__FUNCTION__
);
LOG_INFO
(
"SGX call completed"
);
LOG_INFO
(
"SGX call completed"
);
}
}
...
@@ -480,7 +498,16 @@ void trustedGetPublicEcdsaKeyAES(int *errStatus, char *errString,
...
@@ -480,7 +498,16 @@ void trustedGetPublicEcdsaKeyAES(int *errStatus, char *errString,
mpz_clear
(
privateKeyMpz
);
mpz_clear
(
privateKeyMpz
);
point_clear
(
pKey
);
point_clear
(
pKey
);
point_clear
(
pKey_test
);
point_clear
(
pKey_test
);
LOG_DEBUG
(
"SGX call completed"
);
static
uint64_t
counter
=
0
;
if
(
counter
%
1000
==
0
)
{
LOG_INFO
(
__FUNCTION__
);
LOG_INFO
(
"Thousand SGX calls completed"
);
}
counter
++
;
}
}
static
uint64_t
sigCounter
=
0
;
static
uint64_t
sigCounter
=
0
;
...
@@ -564,6 +591,7 @@ void trustedEcdsaSignAES(int *errStatus, char *errString, uint8_t *encryptedPriv
...
@@ -564,6 +591,7 @@ void trustedEcdsaSignAES(int *errStatus, char *errString, uint8_t *encryptedPriv
mpz_clear
(
privateKeyMpz
);
mpz_clear
(
privateKeyMpz
);
mpz_clear
(
msgMpz
);
mpz_clear
(
msgMpz
);
signature_free
(
sign
);
signature_free
(
sign
);
LOG_DEBUG
(
__FUNCTION__
);
LOG_DEBUG
(
"SGX call completed"
);
LOG_DEBUG
(
"SGX call completed"
);
}
}
...
@@ -647,6 +675,7 @@ void trustedEncryptKeyAES(int *errStatus, char *errString, const char *key,
...
@@ -647,6 +675,7 @@ void trustedEncryptKeyAES(int *errStatus, char *errString, const char *key,
SET_SUCCESS
SET_SUCCESS
clean:
clean:
;
;
LOG_INFO
(
__FUNCTION__
);
LOG_INFO
(
"SGX call completed"
);
LOG_INFO
(
"SGX call completed"
);
}
}
...
@@ -730,6 +759,7 @@ trustedGenDkgSecretAES(int *errStatus, char *errString, uint8_t *encrypted_dkg_s
...
@@ -730,6 +759,7 @@ trustedGenDkgSecretAES(int *errStatus, char *errString, uint8_t *encrypted_dkg_s
SET_SUCCESS
SET_SUCCESS
clean:
clean:
;
;
LOG_INFO
(
__FUNCTION__
);
LOG_INFO
(
"SGX call completed"
);
LOG_INFO
(
"SGX call completed"
);
}
}
...
@@ -752,6 +782,7 @@ trustedDecryptDkgSecretAES(int *errStatus, char *errString, uint8_t *encrypted_d
...
@@ -752,6 +782,7 @@ trustedDecryptDkgSecretAES(int *errStatus, char *errString, uint8_t *encrypted_d
clean:
clean:
;
;
LOG_INFO
(
__FUNCTION__
);
LOG_INFO
(
"SGX call completed"
);
LOG_INFO
(
"SGX call completed"
);
}
}
...
@@ -772,6 +803,7 @@ void trustedSetEncryptedDkgPolyAES(int *errStatus, char *errString, uint8_t *enc
...
@@ -772,6 +803,7 @@ void trustedSetEncryptedDkgPolyAES(int *errStatus, char *errString, uint8_t *enc
SET_SUCCESS
SET_SUCCESS
clean:
clean:
;
;
LOG_INFO
(
__FUNCTION__
);
LOG_INFO
(
"SGX call completed"
);
LOG_INFO
(
"SGX call completed"
);
}
}
...
@@ -836,6 +868,7 @@ void trustedGetEncryptedSecretShareAES(int *errStatus, char *errString, uint8_t
...
@@ -836,6 +868,7 @@ void trustedGetEncryptedSecretShareAES(int *errStatus, char *errString, uint8_t
clean:
clean:
;
;
LOG_INFO
(
__FUNCTION__
);
LOG_INFO
(
"SGX call completed"
);
LOG_INFO
(
"SGX call completed"
);
}
}
...
@@ -912,6 +945,7 @@ void trustedDkgVerifyAES(int *errStatus, char *errString, const char *public_sha
...
@@ -912,6 +945,7 @@ void trustedDkgVerifyAES(int *errStatus, char *errString, const char *public_sha
clean:
clean:
mpz_clear
(
s
);
mpz_clear
(
s
);
LOG_INFO
(
__FUNCTION__
);
LOG_INFO
(
"SGX call completed"
);
LOG_INFO
(
"SGX call completed"
);
}
}
...
@@ -1013,6 +1047,7 @@ void trustedCreateBlsKeyAES(int *errStatus, char *errString, const char *s_share
...
@@ -1013,6 +1047,7 @@ void trustedCreateBlsKeyAES(int *errStatus, char *errString, const char *s_share
mpz_clear
(
bls_key
);
mpz_clear
(
bls_key
);
mpz_clear
(
sum
);
mpz_clear
(
sum
);
mpz_clear
(
q
);
mpz_clear
(
q
);
LOG_INFO
(
__FUNCTION__
);
LOG_INFO
(
"SGX call completed"
);
LOG_INFO
(
"SGX call completed"
);
}
}
...
@@ -1039,7 +1074,12 @@ trustedGetBlsPubKeyAES(int *errStatus, char *errString, uint8_t *encryptedPrivat
...
@@ -1039,7 +1074,12 @@ trustedGetBlsPubKeyAES(int *errStatus, char *errString, uint8_t *encryptedPrivat
CHECK_STATUS
(
"could not calculate bls public key"
);
CHECK_STATUS
(
"could not calculate bls public key"
);
SET_SUCCESS
SET_SUCCESS
static
uint64_t
counter
=
0
;
clean:
clean:
;
if
(
counter
%
1000
==
0
)
{
LOG_DEBUG
(
"SGX call completed"
);
LOG_INFO
(
__FUNCTION__
);
LOG_INFO
(
"Thousand SGX calls completed"
);
}
counter
++
;
}
}
sgxwall.cpp
View file @
83599fbf
...
@@ -49,7 +49,7 @@ void SGXWallet::printUsage() {
...
@@ -49,7 +49,7 @@ void SGXWallet::printUsage() {
cerr
<<
" -v Verbose mode: turn on debug output
\n
"
;
cerr
<<
" -v Verbose mode: turn on debug output
\n
"
;
cerr
<<
" -vv Detailed verbose mode: turn on debug and trace outputs
\n
"
;
cerr
<<
" -vv Detailed verbose mode: turn on debug and trace outputs
\n
"
;
cerr
<<
"
\n
Backup, restore, update flags:
\n\n
"
;
cerr
<<
"
\n
Backup, restore, update flags:
\n\n
"
;
cerr
<<
" -b
Restore from back up or software update. You will need to type in the backup key
.
\n
"
;
cerr
<<
" -b
filename Restore from back up or software update. You will need to put backup key into a file in sgx_data dir
.
\n
"
;
cerr
<<
" -y Do not ask user to acknowledge receipt of the backup key
\n
"
;
cerr
<<
" -y Do not ask user to acknowledge receipt of the backup key
\n
"
;
cerr
<<
"
\n
HTTPS flags:
\n\n
"
;
cerr
<<
"
\n
HTTPS flags:
\n\n
"
;
cerr
<<
" -n Launch sgxwallet using http. Default is to use https with a selg-signed server cert.
\n
"
;
cerr
<<
" -n Launch sgxwallet using http. Default is to use https with a selg-signed server cert.
\n
"
;
...
...
testw.cpp
View file @
83599fbf
...
@@ -95,6 +95,19 @@ public:
...
@@ -95,6 +95,19 @@ public:
}
}
};
};
class
TestFixtureNoResetFromBackup
{
public
:
TestFixtureNoResetFromBackup
()
{
setFullOptions
(
L_INFO
,
false
,
true
,
true
);
initAll
(
L_INFO
,
false
,
true
);
}
~
TestFixtureNoResetFromBackup
()
{
TestUtils
::
destroyEnclave
();
}
};
class
TestFixtureNoReset
{
class
TestFixtureNoReset
{
public
:
public
:
TestFixtureNoReset
()
{
TestFixtureNoReset
()
{
...
@@ -107,7 +120,6 @@ public:
...
@@ -107,7 +120,6 @@ public:
}
}
};
};
TEST_CASE_METHOD
(
TestFixture
,
"ECDSA AES keygen and signature test"
,
"[ecdsa-aes-key-sig-gen]"
)
{
TEST_CASE_METHOD
(
TestFixture
,
"ECDSA AES keygen and signature test"
,
"[ecdsa-aes-key-sig-gen]"
)
{
vector
<
char
>
errMsg
(
BUF_LEN
,
0
);
vector
<
char
>
errMsg
(
BUF_LEN
,
0
);
int
errStatus
=
0
;
int
errStatus
=
0
;
...
@@ -439,7 +451,7 @@ TEST_CASE_METHOD(TestFixture, "Delete Bls Key", "[delete-bls-key]") {
...
@@ -439,7 +451,7 @@ TEST_CASE_METHOD(TestFixture, "Delete Bls Key", "[delete-bls-key]") {
TEST_CASE_METHOD
(
TestFixture
,
"Backup Key"
,
"[backup-key]"
)
{
TEST_CASE_METHOD
(
TestFixture
,
"Backup Key"
,
"[backup-key]"
)
{
HttpClient
client
(
RPC_ENDPOINT
);
HttpClient
client
(
RPC_ENDPOINT
);
StubClient
c
(
client
,
JSONRPC_CLIENT_V2
);
StubClient
c
(
client
,
JSONRPC_CLIENT_V2
);
std
::
ifstream
sek_file
(
"backup_key.txt"
);
std
::
ifstream
sek_file
(
"
sgx_data/sgxwallet_
backup_key.txt"
);
REQUIRE
(
sek_file
.
good
());
REQUIRE
(
sek_file
.
good
());
std
::
string
sek
;
std
::
string
sek
;
...
@@ -721,3 +733,6 @@ TEST_CASE_METHOD(TestFixture, "First run", "[first-run]") {
...
@@ -721,3 +733,6 @@ TEST_CASE_METHOD(TestFixture, "First run", "[first-run]") {
TEST_CASE_METHOD
(
TestFixtureNoReset
,
"Second run"
,
"[second-run]"
)
{
TEST_CASE_METHOD
(
TestFixtureNoReset
,
"Second run"
,
"[second-run]"
)
{
}
}
TEST_CASE_METHOD
(
TestFixtureNoResetFromBackup
,
"Backup restore"
,
"[backup-restore]"
)
{
}
testw.py
View file @
83599fbf
...
@@ -29,7 +29,8 @@ username = getpass.getuser()
...
@@ -29,7 +29,8 @@ username = getpass.getuser()
topDir
=
os
.
getcwd
()
+
"/sgxwallet"
topDir
=
os
.
getcwd
()
+
"/sgxwallet"
print
(
"Top directory is:"
+
topDir
)
print
(
"Top directory is:"
+
topDir
)
testList
=
[
"[first-run]"
,
testList
=
[
"[first-run]"
,
"[second-run]"
,
"[second-run]"
,
"[backup-restore]"
,
"[cert-sign]"
,
"[cert-sign]"
,
"[get-server-status]"
,
"[get-server-status]"
,
"[get-server-version]"
,
"[get-server-version]"
,
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment