Skip to content
GitLab
Projects
Groups
Snippets
/
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Sign in
Toggle navigation
Menu
Open sidebar
ITS - Intelligent Transport Systems
ITS
Commits
a3c982c7
Commit
a3c982c7
authored
Feb 02, 2018
by
garciay
Browse files
Add certificates loader
parent
bc1f01cc
Changes
8
Expand all
Hide whitespace changes
Inline
Side-by-side
ccsrc/Externals/LibItsSecurity_externals.cc
View file @
a3c982c7
...
...
@@ -4,6 +4,8 @@
#include
"sha384.hh"
#include
"ec_keys.hh"
#include
"security_services.hh"
#include
<openssl/ec.h>
#include
<openssl/ecdsa.h>
...
...
@@ -56,7 +58,7 @@ namespace LibItsSecurity__Functions
OCTETSTRING
fx__signWithEcdsaNistp256WithSha256
(
const
OCTETSTRING
&
p__toBeSignedSecuredMessage
,
const
OCTETSTRING
&
p__privateKey
)
{
)
{
// Calculate the SHA256 of the data
sha256
hash
;
std
::
vector
<
unsigned
char
>
hashData
;
...
...
@@ -90,7 +92,7 @@ namespace LibItsSecurity__Functions
OCTETSTRING
fx__signWithEcdsaBrainpoolp256WithSha256
(
const
OCTETSTRING
&
p__toBeSignedSecuredMessage
,
const
OCTETSTRING
&
p__privateKey
)
{
)
{
// Calculate the SHA256 of the data
sha256
hash
;
std
::
vector
<
unsigned
char
>
hashData
;
...
...
@@ -124,7 +126,7 @@ namespace LibItsSecurity__Functions
OCTETSTRING
fx__signWithEcdsaBrainpoolp384WithSha384
(
const
OCTETSTRING
&
p__toBeSignedSecuredMessage
,
const
OCTETSTRING
&
p__privateKey
)
{
)
{
// Calculate the SHA384 of the data
sha384
hash
;
std
::
vector
<
unsigned
char
>
hashData
;
...
...
@@ -195,7 +197,7 @@ namespace LibItsSecurity__Functions
const
OCTETSTRING
&
p__signature
,
const
OCTETSTRING
&
p__ecdsaBrainpoolp256PublicKeyX
,
const
OCTETSTRING
&
p__ecdsaBrainpoolp256PublicKeyY
)
{
)
{
// Calculate the hash
sha256
hash
;
std
::
vector
<
unsigned
char
>
hashData
;
...
...
@@ -228,7 +230,7 @@ namespace LibItsSecurity__Functions
const
OCTETSTRING
&
p__signature
,
const
OCTETSTRING
&
p__ecdsaBrainpoolp384PublicKeyX
,
const
OCTETSTRING
&
p__ecdsaBrainpoolp384PublicKeyY
)
{
)
{
// Calculate the hash
sha384
hash
;
std
::
vector
<
unsigned
char
>
hashData
;
...
...
@@ -260,7 +262,7 @@ namespace LibItsSecurity__Functions
OCTETSTRING
&
p__privateKey
,
OCTETSTRING
&
p__publicKeyX
,
OCTETSTRING
&
p__publicKeyY
)
{
)
{
ec_keys
k
(
ec_elliptic_curves
::
nist_p_256
);
if
(
k
.
generate
()
!=
0
)
{
p__privateKey
=
OCTETSTRING
();
...
...
@@ -288,7 +290,7 @@ namespace LibItsSecurity__Functions
OCTETSTRING
&
p__privateKey
,
OCTETSTRING
&
p__publicKeyX
,
OCTETSTRING
&
p__publicKeyY
)
{
)
{
ec_keys
k
(
ec_elliptic_curves
::
brainpool_p_256_r1
);
if
(
k
.
generate
()
!=
0
)
{
p__privateKey
=
OCTETSTRING
();
...
...
@@ -316,7 +318,7 @@ namespace LibItsSecurity__Functions
OCTETSTRING
&
p__privateKey
,
OCTETSTRING
&
p__publicKeyX
,
OCTETSTRING
&
p__publicKeyY
)
{
)
{
ec_keys
k
(
ec_elliptic_curves
::
brainpool_p_384_r1
);
if
(
k
.
generate
()
!=
0
)
{
p__privateKey
=
OCTETSTRING
();
...
...
@@ -335,7 +337,8 @@ namespace LibItsSecurity__Functions
// group certificatesLoader
/* * @desc Load in memory cache the certificates available in the specified directory
/*
* @desc Load in memory cache the certificates available in the specified directory
* @param p_rootDirectory Root directory to access to the certificates identified by the certificate ID
* @param p_configId A configuration identifier
* @remark This method SHALL be call before any usage of certificates
...
...
@@ -346,10 +349,26 @@ namespace LibItsSecurity__Functions
const
CHARSTRING
&
p__rootDirectory
,
const
CHARSTRING
&
p__configId
)
{
loggers
::
get_instance
().
log
(
"GeoNetworkingLayer::GeoNetworkingLayer: ###################"
);
Params
params
;
params
.
insert
(
std
::
pair
<
std
::
string
,
std
::
string
>
(
std
::
string
(
"sec_db_path"
),
std
::
string
(
static_cast
<
const
char
*>
(
p__rootDirectory
))));
if
(
security_services
::
get_instance
().
setup
(
params
)
==
-
1
)
{
return
FALSE
;
}
return
TRUE
;
}
/* * @desc Unload from memory cache the certificates
BOOLEAN
fx__store__certificate
(
const
CHARSTRING
&
p__cert__id
,
const
OCTETSTRING
&
p__cert
,
const
OCTETSTRING
&
p__private__key
,
const
OCTETSTRING
&
p__public__key__x
,
const
OCTETSTRING
&
p__public__key__y
,
const
OCTETSTRING
&
p__hashid8
,
const
OCTETSTRING
&
p__issuer
)
{
if
(
security_services
::
get_instance
().
store_certificate
(
p__cert__id
,
p__cert
,
p__private__key
,
p__public__key__x
,
p__public__key__y
,
p__hashid8
,
p__issuer
)
==
-
1
)
{
return
FALSE
;
}
return
TRUE
;
}
/*
* @desc Unload from memory cache the certificates
* @return true on success, false otherwise
fx_unloadCertificates() return boolean;
*/
...
...
ccsrc/Protocols/GeoNetworking/GeoNetworkingLayer.cc
View file @
a3c982c7
...
...
@@ -91,7 +91,7 @@ GeoNetworkingLayer::GeoNetworkingLayer(const std::string & p_type, const std::st
if
(
it
==
_params
.
cend
())
{
_params
.
insert
(
std
::
pair
<
std
::
string
,
std
::
string
>
(
std
::
string
(
"signature"
),
"NISTP-256"
));
}
// Set up security services
// Set up security services
even if secured_mode is set to 0. Later, we can receive an AcEnableSecurity request, the sertificate caching will be ready to go
security_services
::
get_instance
().
setup
(
_params
);
Params
::
const_iterator
i
=
_params
.
find
(
Params
::
beaconing
);
...
...
ccsrc/Protocols/Security/certificates_loader.cc
0 → 100644
View file @
a3c982c7
#include
<iostream>
#include
<fstream>
#include
"certificates_loader.hh"
namespace
fs
=
std
::
experimental
::
filesystem
;
#include
"converter.hh"
#include
"loggers.hh"
certificates_loader
*
certificates_loader
::
instance
=
nullptr
;
certificates_loader
::
certificates_loader
()
:
_certificateExt
{
".crt"
},
_privateKeyExt
{
".pkey"
},
_publicKeysExt
{
".vkey"
},
_full_path
(),
_is_cache_initialized
{
false
},
_directory_filter
{
".svn"
,
"._.DS_Store"
,
".DS_Store"
}
{
loggers
::
get_instance
().
log
(
">>> certificates_loader::certificates_loader"
);
}
// End of ctor
int
certificates_loader
::
build_path
(
const
std
::
string
&
p_root_directory
)
{
loggers
::
get_instance
().
log
(
">>> certificates_loader::build_path: '%s'"
,
p_root_directory
.
c_str
());
// Build full path
if
(
!
p_root_directory
.
empty
())
{
_full_path
=
p_root_directory
;
}
else
{
_full_path
=
"./"
;
}
fs
::
canonical
(
_full_path
);
loggers
::
get_instance
().
log
(
"certificates_loader::build_path: full path: %s"
,
_full_path
.
string
().
c_str
());
if
(
!
fs
::
exists
(
_full_path
))
{
loggers
::
get_instance
().
warning
(
"certificates_loader::build_path: Invalid path"
);
_full_path
.
clear
();
return
-
1
;
}
return
0
;
}
// End of method build_path
int
certificates_loader
::
load_certificates
(
std
::
map
<
const
std
::
string
,
std
::
unique_ptr
<
security_db_record
>
>&
p_certificates
,
std
::
map
<
const
std
::
vector
<
unsigned
char
>
,
const
std
::
string
&>&
p_hashed_id8s
)
{
loggers
::
get_instance
().
log
(
">>> certificates_loader::load_certificates"
);
// Sanity check
if
(
_full_path
.
empty
())
{
return
-
1
;
}
if
(
_is_cache_initialized
)
{
return
0
;
}
// Retrieve the list of the files in the path
std
::
set
<
fs
::
path
>
files
;
if
(
retrieve_certificates_list
(
files
)
==
-
1
)
{
return
-
1
;
}
// Build the certificates cache
if
(
build_certificates_cache
(
files
,
p_certificates
,
p_hashed_id8s
)
==
-
1
)
{
return
-
1
;
}
_is_cache_initialized
=
true
;
return
0
;
}
// End of method load_certificates
int
certificates_loader
::
retrieve_certificates_list
(
std
::
set
<
std
::
experimental
::
filesystem
::
path
>&
p_files
)
{
loggers
::
get_instance
().
log
(
">>> certificates_loader::retrieve_certificates_list"
);
// Walk through directories
std
::
set
<
fs
::
path
>
folders
;
for
(
const
fs
::
directory_entry
it
:
fs
::
recursive_directory_iterator
(
_full_path
.
string
()))
{
loggers
::
get_instance
().
log
(
"certificates_loader::retrieve_certificates_list: Processing directory '%s'"
,
it
.
path
().
string
().
c_str
());
if
(
fs
::
is_directory
(
it
))
{
std
::
set
<
std
::
string
>::
const_iterator
i
=
_directory_filter
.
find
(
it
.
path
().
filename
());
if
(
i
!=
_directory_filter
.
cend
())
{
loggers
::
get_instance
().
log
(
"certificates_loader::retrieve_certificates_list: Exclude directory '%s'"
,
it
.
path
().
string
().
c_str
());
continue
;
}
loggers
::
get_instance
().
log
(
"certificates_loader::retrieve_certificates_list: Add directory '%s'"
,
it
.
path
().
string
().
c_str
());
folders
.
insert
(
it
.
path
());
}
}
// End of 'for' statement
if
(
folders
.
size
()
==
0
)
{
loggers
::
get_instance
().
warning
(
"certificates_loader::retrieve_certificates_list: No folder after filtering"
);
return
-
1
;
}
// Process files
p_files
.
clear
();
std
::
set
<
std
::
string
>
extensions_filter
{
_certificateExt
,
_privateKeyExt
,
_publicKeysExt
};
for
(
std
::
set
<
fs
::
path
>::
const_reverse_iterator
f
=
folders
.
crbegin
();
f
!=
folders
.
crend
();
++
f
)
{
loggers
::
get_instance
().
log
(
"certificates_loader::retrieve_certificates_list: Processing directory '%s'"
,
f
->
string
().
c_str
());
for
(
const
fs
::
directory_entry
it
:
fs
::
recursive_directory_iterator
(
*
f
))
{
loggers
::
get_instance
().
log
(
"certificates_loader::retrieve_certificates_list: Processing file '%s'"
,
it
.
path
().
filename
().
string
().
c_str
());
if
(
fs
::
is_regular_file
(
it
))
{
loggers
::
get_instance
().
log
(
"certificates_loader::retrieve_certificates_list: Check extension '%s'"
,
it
.
path
().
extension
().
string
().
c_str
());
std
::
set
<
std
::
string
>::
const_iterator
i
=
extensions_filter
.
find
(
it
.
path
().
extension
().
string
());
if
(
i
!=
extensions_filter
.
cend
())
{
loggers
::
get_instance
().
log
(
"certificates_loader::retrieve_certificates_list: Add file '%s'"
,
it
.
path
().
filename
().
string
().
c_str
());
p_files
.
insert
(
it
);
}
}
}
// End of 'for' statement
}
// End of 'for' statement
loggers
::
get_instance
().
log
(
"certificates_loader::retrieve_certificates_list: # of files to cache:"
,
p_files
.
size
());
if
(
p_files
.
size
()
==
0
)
{
loggers
::
get_instance
().
warning
(
"certificates_loader::retrieve_certificates_list: No certificate found"
);
return
-
1
;
}
return
0
;
}
// End of method retrieve_certificates_list
int
certificates_loader
::
build_certificates_cache
(
std
::
set
<
std
::
experimental
::
filesystem
::
path
>&
p_files
,
std
::
map
<
const
std
::
string
,
std
::
unique_ptr
<
security_db_record
>
>&
p_certificates
,
std
::
map
<
const
std
::
vector
<
unsigned
char
>
,
const
std
::
string
&>&
p_hashed_id8s
)
{
loggers
::
get_instance
().
log
(
">>> certificates_loader::build_certificates_cache"
);
std
::
set
<
std
::
experimental
::
filesystem
::
path
>::
const_iterator
it
=
p_files
.
cbegin
();
do
{
loggers
::
get_instance
().
log
(
"certificates_loader::build_certificates_cache: Caching '%s'"
,
it
->
string
().
c_str
());
fs
::
path
p
=
*
it
;
std
::
string
key
=
p
.
filename
();
// Load certificate file
it
=
p_files
.
find
(
p
.
replace_extension
(
_certificateExt
));
if
(
it
==
p_files
.
cend
())
{
loggers
::
get_instance
().
warning
(
"certificates_loader::build_certificates_cache: Certificate file not found for '%s'"
,
key
.
c_str
());
return
-
1
;
}
loggers
::
get_instance
().
log
(
"certificates_loader::build_certificates_cache: Caching certificate '%s'"
,
it
->
string
().
c_str
());
std
::
ifstream
is
(
it
->
string
(),
ios
::
in
|
ios
::
binary
);
std
::
vector
<
unsigned
char
>
certificate
(
fs
::
file_size
(
*
it
),
0x00
);
is
.
read
(
reinterpret_cast
<
char
*>
(
certificate
.
data
()),
certificate
.
size
());
is
.
close
();
// Remove items from the list
p_files
.
erase
(
it
);
// Load public key file
it
=
p_files
.
find
(
p
.
replace_extension
(
_publicKeysExt
));
if
(
it
==
p_files
.
cend
())
{
loggers
::
get_instance
().
warning
(
"certificates_loader::build_certificates_cache: Public keys file not found for '%s'"
,
key
.
c_str
());
return
-
1
;
}
loggers
::
get_instance
().
log
(
"certificates_loader::build_certificates_cache: Caching public keys '%s'"
,
it
->
string
().
c_str
());
is
.
open
(
it
->
string
(),
ios
::
in
|
ios
::
binary
);
int
size
=
fs
::
file_size
(
*
it
);
if
((
size
!=
64
)
&&
(
size
!=
96
))
{
loggers
::
get_instance
().
warning
(
"certificates_loader::build_certificates_cache: Public keys file not found for '%s'"
,
key
.
c_str
());
return
-
1
;
}
std
::
vector
<
unsigned
char
>
public_key_x
(
size
/
2
,
0x00
);
is
.
read
(
reinterpret_cast
<
char
*>
(
public_key_x
.
data
()),
public_key_x
.
size
());
std
::
vector
<
unsigned
char
>
public_key_y
(
size
/
2
,
0x00
);
is
.
read
(
reinterpret_cast
<
char
*>
(
public_key_y
.
data
()),
public_key_y
.
size
());
is
.
close
();
// Remove items from the list
p_files
.
erase
(
it
);
// FIXME Caching private key, digest and issuer
// FIXME create new record
/*p_certificates.insert(std::pair<const std::string, std::unique_ptr<security_db_record> >(key, std::unique_ptr<security_db_record>(
new security_db_record(
key,
certificate, // Certificate
issuer, // Hashed ID fo the issuer, empty for CA
h, // Hashed ID
private_key, // Private key
public_key_x, // Public key X
public_key_y // Public key Y
))
));
std::map<const std::string, std::unique_ptr<security_db_record> >::const_iterator i = p_certificates.find(key);
if (i == _certificates.cend()) {
loggers::get_instance().warning("certificates_loader::build_certificates_cache: Failed to insert new record '%s'", key.c_str());
return -1;
}
p_hashed_id8s.insert(std::pair<const std::vector<unsigned char>, const std::string&>(i->second.get()->hashed_id(), i->first));*/
// Reset pointer
it
=
p_files
.
cbegin
();
}
while
(
it
!=
p_files
.
cend
());
return
0
;
}
// End of method build_certificates_cache
ccsrc/Protocols/Security/certificates_loader.hh
0 → 100644
View file @
a3c982c7
#pragma once
#include
<set>
#include
<map>
#include
<string>
#include
<experimental/filesystem>
#include
"security_db_record.hh"
/*!
* \class certificates_loader
* \brief This class provides mechanism to load the certificates from the filesystem according the struecture defined in ETSI TS 103 099
* \remark Singleton pattern
*/
class
certificates_loader
{
std
::
string
_certificateExt
;
std
::
string
_privateKeyExt
;
std
::
string
_publicKeysExt
;
/*!
* \brief The full folder path to load certificates
*/
std
::
experimental
::
filesystem
::
path
_full_path
;
/*!
* \brief Set to true when certificates are successfully loaded from file system
*/
bool
_is_cache_initialized
;
/*!
* \brief Directory filter (for local development purposes only
*/
std
::
set
<
std
::
string
>
_directory_filter
;
/*!
* \brief Unique static object reference of this class
*/
static
certificates_loader
*
instance
;
/*!
* \brief Default private ctor
*/
certificates_loader
();
/*!
* \brief Default private dtor
*/
~
certificates_loader
()
{
if
(
instance
!=
NULL
)
{
delete
instance
;
instance
=
NULL
;
}
};
public:
/*! \publicsection */
/*!
* \brief Public accessor to the single object reference
*/
inline
static
certificates_loader
&
get_instance
()
{
if
(
instance
==
NULL
)
instance
=
new
certificates_loader
();
return
*
instance
;
};
int
build_path
(
const
std
::
string
&
p_root_directory
);
int
load_certificates
(
std
::
map
<
const
std
::
string
,
std
::
unique_ptr
<
security_db_record
>
>&
p_certificates
,
std
::
map
<
const
std
::
vector
<
unsigned
char
>
,
const
std
::
string
&>&
p_hashed_id8s
);
private:
int
retrieve_certificates_list
(
std
::
set
<
std
::
experimental
::
filesystem
::
path
>&
p_files
);
int
build_certificates_cache
(
std
::
set
<
std
::
experimental
::
filesystem
::
path
>&
p_files
,
std
::
map
<
const
std
::
string
,
std
::
unique_ptr
<
security_db_record
>
>&
p_certificates
,
std
::
map
<
const
std
::
vector
<
unsigned
char
>
,
const
std
::
string
&>&
p_hashed_id8s
);
};
// End of class certificates_loader
ccsrc/Protocols/Security/security_db.cc
View file @
a3c982c7
This diff is collapsed.
Click to expand it.
ccsrc/Protocols/Security/security_db.hh
View file @
a3c982c7
...
...
@@ -8,6 +8,7 @@
#include
"security_db_record.hh"
class
OCTETSTRING
;
class
CHARSTRING
;
/*!
* \class security_db
...
...
@@ -37,7 +38,11 @@ public: /*! \publicsection */
int
get_hashed_id
(
const
std
::
string
&
p_certifcate_id
,
OCTETSTRING
&
p_hashed_id
)
const
;
int
get_private_key
(
const
std
::
string
&
p_certifcate_id
,
OCTETSTRING
&
p_private_key
)
const
;
int
get_public_keys
(
const
std
::
string
&
p_certifcate_id
,
OCTETSTRING
&
p_public_key_x
,
OCTETSTRING
&
p_public_key_y
)
const
;
int
store_certificate
(
const
CHARSTRING
&
p_cert_id
,
const
OCTETSTRING
&
p_cert
,
const
OCTETSTRING
&
p_private_key
,
const
OCTETSTRING
&
p_public_key_x
,
const
OCTETSTRING
&
p_public_key_y
,
const
OCTETSTRING
&
p_hashid8
,
const
OCTETSTRING
&
p_issuer
);
void
dump
()
const
;
int
clear
();
private:
int
load_from_files
(
const
std
::
string
&
p_db_path
);
...
...
ccsrc/Protocols/Security/security_services.cc
View file @
a3c982c7
...
...
@@ -18,13 +18,13 @@
#include
"converter.hh"
security_services
*
security_services
::
instance
=
NULL
;
security_services
*
security_services
::
instance
=
nullptr
;
security_services
::
security_services
()
:
_ec_keys
(
nullptr
),
_security_db
(
nullptr
)
{
loggers
::
get_instance
().
log
(
">>> security_services::security_services"
);
}
// End of ctor
int
security_services
::
setup
(
Params
&
p_params
)
{
int
security_services
::
setup
(
Params
&
p_params
)
{
// FIXME Rename this method
loggers
::
get_instance
().
log
(
"security_services::setup"
);
p_params
.
log
();
...
...
@@ -36,6 +36,16 @@ int security_services::setup(Params& p_params) {
return
0
;
}
int
security_services
::
store_certificate
(
const
CHARSTRING
&
p_cert_id
,
const
OCTETSTRING
&
p_cert
,
const
OCTETSTRING
&
p_private_key
,
const
OCTETSTRING
&
p_public_key_x
,
const
OCTETSTRING
&
p_public_key_y
,
const
OCTETSTRING
&
p_hashid8
,
const
OCTETSTRING
&
p_issuer
)
{
loggers
::
get_instance
().
log_msg
(
">>> security_services::store_certificate: "
,
p_cert_id
);
// Sanity checks
if
(
_security_db
.
get
()
==
nullptr
)
{
// Setup not called
return
-
1
;
}
return
_security_db
.
get
()
->
store_certificate
(
p_cert_id
,
p_cert
,
p_private_key
,
p_public_key_x
,
p_public_key_y
,
p_hashid8
,
p_issuer
);
}
int
security_services
::
verify_and_extract_gn_payload
(
const
OCTETSTRING
&
p_secured_gn_payload
,
const
bool
p_verify
,
OCTETSTRING
&
p_unsecured_gn_payload
,
Params
&
p_params
)
{
loggers
::
get_instance
().
log_msg
(
">>> security_services::verify_and_extract_gn_payload: "
,
p_secured_gn_payload
);
...
...
ccsrc/Protocols/Security/security_services.hh
View file @
a3c982c7
...
...
@@ -35,8 +35,7 @@ class security_services {
/*!
* \brief Unique static object reference of this class
*/
static
security_services
*
instance
;
static
security_services
*
instance
;
std
::
unique_ptr
<
ec_keys
>
_ec_keys
;
std
::
unique_ptr
<
security_db
>
_security_db
;
...
...
@@ -61,13 +60,11 @@ public: /*! \publicsection */
/*!
* \brief Public accessor to the single object reference
*/
inline
static
security_services
&
get_instance
()
{
inline
static
security_services
&
get_instance
()
{
if
(
instance
==
NULL
)
instance
=
new
security_services
();
return
*
instance
;
};
int
setup
(
Params
&
p_params
);
/*!
* \brief Decrypt (if required), verify and extract the unsecured payload from the provided secured payload
* \param[in] p_secured_gn_payload The secured payload to be processed
...
...
@@ -79,6 +76,11 @@ public: /*! \publicsection */
int
secure_gn_payload
(
const
OCTETSTRING
&
p_unsecured_gn_payload
,
const
bool
p_add_certificate
,
OCTETSTRING
&
p_secured_gn_payload
,
Params
&
p_params
);
int
setup
(
Params
&
p_params
);
int
store_certificate
(
const
CHARSTRING
&
p_cert_id
,
const
OCTETSTRING
&
p_cert
,
const
OCTETSTRING
&
p_private_key
,
const
OCTETSTRING
&
p_public_key_x
,
const
OCTETSTRING
&
p_public_key_y
,
const
OCTETSTRING
&
p_hashid8
,
const
OCTETSTRING
&
p_issuer
);
private:
/*!
* \brief Decrypt (if required), verify and extract the unsecured payload from the IEEE1609dot2::Ieee1609Dot2Content data structure
...
...
Write
Preview
Supports
Markdown
0%
Try again
or
attach a new 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