Commit bbaa3f82 authored by Yann Garcia's avatar Yann Garcia
Browse files

AtsPki validation: major bugs fixed

parent a4ba1556
......@@ -25,6 +25,7 @@ module LibItsHttp_TestSystem {
type component HttpComponent extends SelfSyncComp { // FIXME To be rename into HttpTest
port HttpPort httpPort;
timer tc_ac := PX_TAC;
timer tc_noac := PX_TNOAC;
} // End of component HttpComponent
type component HttpTestAdapter { // FIXME To be rename into HttpTestSystem
......
......@@ -43,6 +43,7 @@ module LibItsPki_Functions {
import from LibItsSecurity_TypesAndValues all;
import from LibItsSecurity_Templates all;
import from LibItsSecurity_Functions all;
import from LibItsSecurity_Pics all;
import from LibItsSecurity_Pixits all;
import from LibItsSecurity_TestSystem all;
......@@ -285,10 +286,15 @@ module LibItsPki_Functions {
group http {
function f_http_build_inner_ec_request(
function f_http_build_inner_ec_request( // TODO Cleanup parameters
out octetstring p_private_key,
out octetstring p_publicKeyCompressed,
out integer p_compressedMode,
out Oct16 p_aes_sym_key,
out Oct16 p_encrypted_sym_key,
out Oct16 p_authentication_vector,
out Oct12 p_nonce,
out octetstring p_salt,
out Ieee1609Dot2Data p_ieee1609dot2_signed_and_encrypted_data,
out Oct32 p_hash_inner_ec_request_signed_for_pop
) runs on ItsPkiHttp {
......@@ -322,6 +328,7 @@ module LibItsPki_Functions {
log("*** f_http_build_inner_ec_request: Public encryption key: ", v_public_enc_key);
log("*** f_http_build_inner_ec_request: Public encryption key comp: ", v_compressed_enc_key_mode);
log("*** f_http_build_inner_ec_request: First enrolment: ", PX_FIRST_ENROLMENT);
p_salt := vc_eaWholeHash;
if (PX_FIRST_ENROLMENT == true) { // This is the first enrolment, we used Factory keys
v_ret_code := f_build_pki_secured_message(PICS_ITS_S_SIGN_NITSP256_PRIVATE_KEY, valueof(m_signerIdentifier_self), vc_eaHashedId8/*recipientId*/, v_public_enc_key, v_compressed_enc_key_mode, vc_eaWholeHash, bit2oct(v_inner_ec_request_signed_for_pop_msg), p_ieee1609dot2_signed_and_encrypted_data);
} else { // We use last valid EC certificate
......@@ -340,6 +347,11 @@ module LibItsPki_Functions {
out octetstring p_private_key,
out octetstring p_publicKeyCompressed,
out integer p_compressedMode,
out Oct16 p_aes_sym_key,
out Oct16 p_encrypted_sym_key,
out Oct16 p_authentication_vector,
out Oct12 p_nonce,
out octetstring p_salt,
out Ieee1609Dot2Data p_ieee1609dot2_signed_and_encrypted_data,
out Oct32 p_hash_inner_ec_request_signed_for_pop
) runs on ItsPkiHttp {
......@@ -368,6 +380,7 @@ module LibItsPki_Functions {
}
log("*** f_http_build_inner_ec_request: Public encryption key: ", v_public_enc_key);
log("*** f_http_build_inner_ec_request: Public encryption key comp: ", v_compressed_enc_key_mode);
p_salt := vc_eaWholeHash;
if (PX_FIRST_ENROLMENT == true) { // This is the first enrolment, we used Factory keys
v_ret_code := f_build_pki_secured_message(PICS_ITS_S_SIGN_NITSP256_PRIVATE_KEY, valueof(m_signerIdentifier_self), vc_eaHashedId8/*recipientId*/, v_public_enc_key, v_compressed_enc_key_mode, vc_eaWholeHash, bit2oct(encvalue(m_etsiTs102941Data_inner_ec_request_signed_for_pop(v_inner_ec_request_signed_for_pop))), p_ieee1609dot2_signed_and_encrypted_data);
} else { // We use last valid EC certificate
......@@ -800,6 +813,7 @@ module LibItsPki_Functions {
var integer v_compressed_mode;
var Oct12 v_nonce;
var Oct16 v_authentication_vector;
var Oct16 v_aes_sym_key;
var Oct16 v_encrypted_sym_key;
var HashedId8 v_recipientId;
var octetstring v_publicEphemeralKeyCompressed;
......@@ -815,7 +829,7 @@ module LibItsPki_Functions {
} else {
return false;
}
v_enc_signed_ec_signature := f_encryptWithEciesNistp256WithSha256(bit2oct(encvalue(v_signed_ec_signature)), v_public_enc_key, v_compressed_mode, ''O, v_publicEphemeralKeyCompressed, v_ephemeralKeyModeCompressed, v_encrypted_sym_key, v_authentication_vector, v_nonce);
v_enc_signed_ec_signature := f_encryptWithEciesNistp256WithSha256(bit2oct(encvalue(v_signed_ec_signature)), v_public_enc_key, v_compressed_mode, ''O, v_publicEphemeralKeyCompressed, v_ephemeralKeyModeCompressed, v_aes_sym_key, v_encrypted_sym_key, v_authentication_vector, v_nonce);
v_recipientId := p_eaHashedId8;
log("v_recipientId=", v_recipientId);
// Fill Certificate template with the public compressed keys (canonical form)
......@@ -954,7 +968,11 @@ module LibItsPki_Functions {
in integer p_compressedMode,
in octetstring p_salt,
in octetstring p_pki_message,
out Ieee1609Dot2Data p_ieee1609dot2_signed_and_encrypted_data
out Ieee1609Dot2Data p_ieee1609dot2_signed_and_encrypted_data,
out Oct16 p_aes_sym_key,
out Oct16 p_encrypted_sym_key,
out Oct16 p_authentication_vector,
out Oct12 p_nonce
) return boolean {
// Local variables
var template (value) EccP256CurvePoint v_eccP256_curve_point;
......@@ -962,9 +980,6 @@ module LibItsPki_Functions {
var octetstring v_tbs_signed;
var template (value) Ieee1609Dot2Data v_ieee1609dot2_signed_data;
var octetstring v_encoded_inner_ec_request;
var Oct12 v_nonce;
var Oct16 v_authentication_vector;
var Oct16 v_encrypted_sym_key;
var HashedId8 v_recipientId;
var octetstring v_publicEphemeralKeyCompressed;
var integer v_ephemeralKeyModeCompressed;
......@@ -1005,12 +1020,20 @@ module LibItsPki_Functions {
// Encode EtsiTs103097Data-Signed data structure
v_encoded_inner_ec_request := bit2oct(encvalue(v_ieee1609dot2_signed_data));
// Encrypt encode EtsiTs103097Data-Signed data structure
v_encrypted_inner_ec_request := f_encryptWithEciesNistp256WithSha256(v_encoded_inner_ec_request, p_publicKeyCompressed, p_compressedMode, p_salt, v_publicEphemeralKeyCompressed, v_ephemeralKeyModeCompressed, v_encrypted_sym_key, v_authentication_vector, v_nonce);
if (PICS_SEC_FIXED_KEYS) {
p_publicKeyCompressed := '8C5E20FE31935F6FA682A1F6D46E4468534FFEA1A698B14B0B12513EED8DEB11'O;
p_compressedMode := 0;
p_salt := '9169155B08B07674CBADF75FB46A7B0D'O;
}
v_encrypted_inner_ec_request := f_encryptWithEciesNistp256WithSha256(v_encoded_inner_ec_request, p_publicKeyCompressed, p_compressedMode, p_salt, v_publicEphemeralKeyCompressed, v_ephemeralKeyModeCompressed, p_aes_sym_key, p_encrypted_sym_key, p_authentication_vector, p_nonce, PICS_SEC_FIXED_KEYS);
log("p_aes_sym_key=", p_aes_sym_key);
log("p_encrypted_sym_key=", p_encrypted_sym_key);
log("p_authentication_vector=", p_authentication_vector);
log("p_nonce=", p_nonce);
log("p_recipientId=", p_recipientId);
if (p_recipientId == int2oct(0, 8)) {
log("v_encrypted_sym_key=", v_encrypted_sym_key);
log("f_hashWithSha256(v_encrypted_sym_key=", f_hashWithSha256(v_encrypted_sym_key));
v_recipientId := f_HashedId8FromSha256(f_hashWithSha256(v_encrypted_sym_key));
log("f_hashWithSha256(v_encrypted_sym_key)=", f_hashWithSha256(p_encrypted_sym_key));
v_recipientId := f_HashedId8FromSha256(f_hashWithSha256(p_encrypted_sym_key));
} else {
v_recipientId := p_recipientId;
}
......@@ -1031,13 +1054,13 @@ module LibItsPki_Functions {
m_encryptedDataEncryptionKey_eciesNistP256(
m_evciesP256EncryptedKey(
v_eccP256_curve_point,
v_encrypted_sym_key,
v_authentication_vector
p_encrypted_sym_key,
p_authentication_vector
))))
},
m_SymmetricCiphertext_aes128ccm(
m_aesCcmCiphertext(
v_nonce,
p_nonce,
v_encrypted_inner_ec_request
)
)
......@@ -1059,51 +1082,82 @@ module LibItsPki_Functions {
* @return true on success, false otherwise
*/
function f_verify_pki_message(
in octetstring v_private_enc_key,
in octetstring p_private_enc_key,
// in octetstring p_publicEphemeralCompressedKey, // TODO Useless, to be removed
// in integer p_publicEphemeralCompressedKeyMode, // TODO Useless, to be removed
in Oct16 p_aes_sym_key,
in Oct16 p_authentication_vector, // TODO Tobe removed
// in Oct12 p_nonce, // TODO Tobe removed
// in octetstring p_salt,
in octetstring p_issuer,
in Certificate p_peer_certificate,
in Certificate p_certificate, // TODO Perhaps the peer certificate will be required for signature
in Ieee1609Dot2Data p_ieee1609dot2_encrypted_and_signed_data,
in boolean p_check_security := true,
out EtsiTs102941Data p_etsi_ts_102941_data
) return boolean {
// Local variables
var octetstring v_public_enc_key;
var integer v_compressed_enc_key_mode;
var octetstring v_plain_message;
var Ieee1609Dot2Data v_ieee1609dot2_signed_data;
var bitstring v_etsi_ts_102941_data_msg;
var bitstring v_tbs;
var boolean v_ret;
//var octetstring v_cyphered_text;
var Oct16 v_authentication_vector;
log(">>> f_verify_pki_message: p_private_enc_key= ", p_private_enc_key);
//log(">>> f_verify_pki_message: p_publicEphemeralCompressedKey= ", p_publicEphemeralCompressedKey);
log(">>> f_verify_pki_message: p_aes_sym_key=", p_aes_sym_key);
log(">>> f_verify_pki_message: p_authentication_vector=", p_authentication_vector);
log(">>> f_verify_pki_message: p_issuer=", p_issuer);
//log(">>> f_verify_pki_message: p_salt=", p_salt);
// TODO Check p_ieee1609dot2_encrypted_and_signed_data.content.encryptedData.recipients[0].pskRecipInfo. See IEEE Std 1609.2-2017 Clause 6.3.34 PreSharedKeyRecipientInfo
// 1. Decrypt the data
if (f_decrypt(v_private_enc_key, p_ieee1609dot2_encrypted_and_signed_data, ''O, v_ieee1609dot2_signed_data) == false) {
v_plain_message := fx_test_decrypt_aes_128_ccm_test(p_aes_sym_key, p_ieee1609dot2_encrypted_and_signed_data.content.encryptedData.ciphertext.aes128ccm.nonce, p_ieee1609dot2_encrypted_and_signed_data.content.encryptedData.ciphertext.aes128ccm.ccmCiphertext);
if (isbound(v_plain_message) == false) {
return false;
}
log("v_plain_message= ", v_plain_message);
// 2. Decode it
v_tbs := oct2bit(v_plain_message);
if (decvalue(v_tbs, v_ieee1609dot2_signed_data) != 0) {
if (p_check_security == true) {
return false;
}
}
log("v_ieee1609dot2_signed_data= ", v_ieee1609dot2_signed_data);
// 2. Check the signature
// 3. Check the signature
log("v_ieee1609dot2_signed_data.content.signedData.tbsData= ", v_ieee1609dot2_signed_data.content.signedData.tbsData);
v_tbs := encvalue(v_ieee1609dot2_signed_data.content.signedData.tbsData);
if (ischosen(p_peer_certificate.toBeSigned.verifyKeyIndicator.verificationKey.ecdsaNistP256.compressed_y_0)) {
/*if (ischosen(p_certificate.toBeSigned.verifyKeyIndicator.verificationKey.ecdsaNistP256.compressed_y_0)) {
v_ret := f_verifyWithEcdsaNistp256WithSha256(
bit2oct(v_tbs),
p_issuer,
v_ieee1609dot2_signed_data.content.signedData.signature_.ecdsaNistP256Signature.rSig.x_only & v_ieee1609dot2_signed_data.content.signedData.signature_.ecdsaNistP256Signature.sSig,
p_peer_certificate.toBeSigned.verifyKeyIndicator.verificationKey.ecdsaNistP256.compressed_y_0,
p_certificate.toBeSigned.verifyKeyIndicator.verificationKey.ecdsaNistP256.compressed_y_0, // TODO Use the signer digest to get the EA certificate
0);
} else {
v_ret := f_verifyWithEcdsaNistp256WithSha256(
bit2oct(v_tbs),
p_issuer,
v_ieee1609dot2_signed_data.content.signedData.signature_.ecdsaNistP256Signature.rSig.x_only & v_ieee1609dot2_signed_data.content.signedData.signature_.ecdsaNistP256Signature.sSig,
p_peer_certificate.toBeSigned.verifyKeyIndicator.verificationKey.ecdsaNistP256.compressed_y_1,
p_certificate.toBeSigned.verifyKeyIndicator.verificationKey.ecdsaNistP256.compressed_y_1, // TODO Use the signer digest to get the EA certificate
1);
}
if (v_ret == false) {
if (p_check_security == true) {
return false;
}
}
}*/
// 3. Retrun the PKI message
// 4. Return the PKI message
log("v_ieee1609dot2_signed_data.content.signedData.tbsData.payload.data.content.unsecuredData= ", v_ieee1609dot2_signed_data.content.signedData.tbsData.payload.data.content.unsecuredData);
v_etsi_ts_102941_data_msg := oct2bit(v_ieee1609dot2_signed_data.content.signedData.tbsData.payload.data.content.unsecuredData);
if (decvalue(v_etsi_ts_102941_data_msg, p_etsi_ts_102941_data) != 0) {
return false;
......
......@@ -38,7 +38,7 @@ module LibItsPki_Pics {
/**
* @desc Certificate used by the Test System acting as EA
*/
modulepar charstring PICS_TS_EA_CERTIFICATE_ID := "CERT_TS_A_EA";
modulepar charstring PICS_TS_EA_CERTIFICATE_ID := "CERT_EA";
/**
* @desc Certificate used by the Test System acting as AA
......
......@@ -124,6 +124,7 @@ module LibItsSecurity_Functions {
if (ischosen(p_encrypedSecuredMessage.content.encryptedData)) {
var PKRecipientInfo v_pKRecipientInfo;
var RecipientInfo v_recipientInfo := p_encrypedSecuredMessage.content.encryptedData.recipients[0];
var octetstring v_decryptedSecuredMessage;
// Check the private encryption key
if (not(isbound(p_encryptPrivateKey))) {
......@@ -142,8 +143,8 @@ module LibItsSecurity_Functions {
return false;
}
if (isbound(v_pKRecipientInfo)) {
if (ischosen(v_pKRecipientInfo.encKey.eciesNistP256)) {
var octetstring v_decryptedSecuredMessage;
var SymmetricCiphertext v_ciphertext := p_encrypedSecuredMessage.content.encryptedData.ciphertext;
if (ischosen(v_pKRecipientInfo.encKey.eciesNistP256.v.compressed_y_0)) {
v_decryptedSecuredMessage := f_decryptWithEciesNistp256WithSha256(
......@@ -180,7 +181,6 @@ module LibItsSecurity_Functions {
}
}
} else if (ischosen(v_pKRecipientInfo.encKey.eciesBrainpoolP256r1)) {
var octetstring v_decryptedSecuredMessage;
var SymmetricCiphertext v_ciphertext := p_encrypedSecuredMessage.content.encryptedData.ciphertext;
if (ischosen(v_pKRecipientInfo.encKey.eciesBrainpoolP256r1.v.compressed_y_0)) {
v_decryptedSecuredMessage := f_decryptWithEciesBrainpoolp256WithSha256(
......@@ -206,6 +206,15 @@ module LibItsSecurity_Functions {
log("*** " & testcasename() & ":ERROR: Non canonical ephemeral encryption keys ***");
return false;
}
} else {
log("*** " & testcasename() & ":ERROR: Non canonical ephemeral encryption keys ***");
return false;
}
// TODO else, other variants shall be processed here if
} else {
log("*** " & testcasename() & ":ERROR: Invalid recipient info ***");
return false;
}
if (isbound(v_decryptedSecuredMessage)) {
var bitstring v_decode := oct2bit(v_decryptedSecuredMessage);
if (decvalue(v_decode, p_decrypedSecuredMessage) == 0) {
......@@ -214,7 +223,6 @@ module LibItsSecurity_Functions {
log("*** " & testcasename() & ":ERROR: Faild to decode secured message ***");
}
}
}
} else {
log("*** " & testcasename() & ":ERROR: Message not encrypted ***");
}
......@@ -244,9 +252,11 @@ module LibItsSecurity_Functions {
in octetstring p_salt,
out Oct32 p_publicEphemeralKeyCompressed,
out integer p_ephemeralKeyModeCompressed,
out Oct16 p_aes_sym_key,
out Oct16 p_encrypted_sym_key,
out Oct16 p_authentication_vector,
out Oct12 p_nonce
out Oct12 p_nonce,
in boolean p_use_hardcoded_values := false
) return octetstring {
return fx_encryptWithEciesNistp256WithSha256(
p_toBeEncryptedSecuredMessage,
......@@ -255,9 +265,11 @@ module LibItsSecurity_Functions {
p_salt,
p_publicEphemeralKeyCompressed,
p_ephemeralKeyModeCompressed,
p_aes_sym_key,
p_encrypted_sym_key,
p_authentication_vector,
p_nonce
p_nonce,
p_use_hardcoded_values
);
} // End of function f_encryptWithEciesNistp256WithSha256
......@@ -317,6 +329,7 @@ module LibItsSecurity_Functions {
in integer p_compressedMode,
out Oct32 p_publicEphemeralKeyCompressed,
out integer p_ephemeralKeyModeCompressed,
out Oct16 p_aes_sym_key,
out Oct16 p_encrypted_sym_key,
out Oct16 p_authentication_vector,
out Oct12 p_nonce
......@@ -327,6 +340,7 @@ module LibItsSecurity_Functions {
p_compressedMode,
p_publicEphemeralKeyCompressed,
p_ephemeralKeyModeCompressed,
p_aes_sym_key,
p_encrypted_sym_key,
p_authentication_vector,
p_nonce
......@@ -595,11 +609,11 @@ module LibItsSecurity_Functions {
out integer p_compressedMode
) return boolean {
if (PICS_SEC_FIXED_KEYS) {
p_privateKey := 'F8EB15C001A03623A5B5E44D73869621877710712A498C98FF9E60EE10F390F8'O;
p_publicKeyX := '7029A9B20D22AE37B1344B7FCC2322C8F1E5ECE09C39CC289E500A9487298B9B'O;
p_publicKeyY := 'F9EFA8BCC4129BC43B640566A59AE5CED7106BBA76E5DC828AF37D315634D3DC'O;
p_publicKeyCompressed := '7029A9B20D22AE37B1344B7FCC2322C8F1E5ECE09C39CC289E500A9487298B9B'O;
p_compressedMode := 0;
p_privateKey := 'D418760F0CB2DCB856BC3C7217AD3AA36DB6742AE1DB655A3D28DF88CBBF84E1'O;
p_publicKeyX := 'EE9CC7FBD9EDECEA41F7C8BD258E8D2E988E75BD069ADDCA1E5A38E534AC6818'O;
p_publicKeyY := '5AE3C8D9FE0B1FC7438F29417C240F8BF81C358EC1A4D0C6E98D8EDBCC714017'O;
p_publicKeyCompressed := 'EE9CC7FBD9EDECEA41F7C8BD258E8D2E988E75BD069ADDCA1E5A38E534AC6818'O;
p_compressedMode := 1;
return true;
}
......@@ -2332,9 +2346,9 @@ module LibItsSecurity_Functions {
* @param p_nonce The nonce vector of the AES 128 CCM symmetric key encryption
* @return The encrypted message
*/
external function fx_encryptWithEciesNistp256WithSha256(in octetstring p_toBeEncryptedSecuredMessage, in Oct32 p_recipientsPublicKeyCompressed, in integer p_compressedMode, in octetstring p_salt, out Oct32 p_publicEphemeralKeyCompressed, out integer p_ephemeralKeyModeCompressed, out Oct16 p_encrypted_sym_key, out Oct16 p_authentication_vector, out Oct12 p_nonce) return octetstring;
external function fx_encryptWithEciesNistp256WithSha256(in octetstring p_toBeEncryptedSecuredMessage, in Oct32 p_recipientsPublicKeyCompressed, in integer p_compressedMode, in octetstring p_salt, out Oct32 p_publicEphemeralKeyCompressed, out integer p_ephemeralKeyModeCompressed, out Oct16 p_aes_sym_key, out Oct16 p_encrypted_sym_key, out Oct16 p_authentication_vector, out Oct12 p_nonce, in boolean p_use_hardcoded_values := false) return octetstring;
external function fx_test_encryptWithEciesNistp256WithSha256(in octetstring p_toBeEncryptedSecuredMessage, in Oct32 p_privateEphemeralKey, in Oct32 p_recipientPublicKeyX, in Oct32 p_recipientPublicKeyY, in octetstring p_salt, out Oct32 p_publicEphemeralKeyX, out Oct32 p_publicEphemeralKeyY, out Oct16 p_encrypted_sym_key, out Oct16 p_authentication_vector, out Oct12 p_nonce) return octetstring;
external function fx_test_encryptWithEciesNistp256WithSha256(in octetstring p_toBeEncryptedSecuredMessage, in Oct32 p_privateEphemeralKey, in Oct32 p_recipientPublicKeyX, in Oct32 p_recipientPublicKeyY, in octetstring p_salt, out Oct32 p_publicEphemeralKeyX, out Oct32 p_publicEphemeralKeyY, out Oct16 p_aes_sym_key, out Oct16 p_encrypted_sym_key, out Oct16 p_authentication_vector, out Oct12 p_nonce) return octetstring;
/**
* @desc Produces a Elliptic Curve Digital Encrytion Algorithm (ECIES) decryption using Nist-P256 algorithm
* @param p_encryptedSecuredMessage The data to be decrypted
......@@ -2359,7 +2373,7 @@ module LibItsSecurity_Functions {
* @param p_nonce The nonce vector of the AES 128 CCM symmetric key encryption
* @return The encrypted message
*/
external function fx_encryptWithEciesBrainpoolp256WithSha256(in octetstring p_toBeEncryptedSecuredMessage, in Oct32 p_recipientsPublicKeyCompressed, in integer p_compressedMode, out Oct32 p_publicEphemeralKeyCompressed, out integer p_ephemeralKeyModeCompressed, out Oct16 p_encrypted_sym_key, out Oct16 p_authentication_vector, out Oct12 p_nonce) return octetstring;
external function fx_encryptWithEciesBrainpoolp256WithSha256(in octetstring p_toBeEncryptedSecuredMessage, in Oct32 p_recipientsPublicKeyCompressed, in integer p_compressedMode, out Oct32 p_publicEphemeralKeyCompressed, out integer p_ephemeralKeyModeCompressed, out Oct16 p_aes_sym_key, out Oct16 p_encrypted_sym_key, out Oct16 p_authentication_vector, out Oct12 p_nonce) return octetstring;
/**
* @desc Produces a Elliptic Curve Digital Encrytion Algorithm (ECIES) decryption using Brainpool-P256 algorithm
......
......@@ -665,6 +665,12 @@ module LibItsSecurity_Templates {
pskRecipInfo := p_pskRecipInfo
} // End of template m_recipientInfo_pskRecipInfo
template (present) RecipientInfo mw_recipientInfo_pskRecipInfo(
template (present) PreSharedKeyRecipientInfo p_pskRecipInfo := ?
) := {
pskRecipInfo := p_pskRecipInfo
} // End of template mw_recipientInfo_pskRecipInfo
template (value) RecipientInfo m_recipientInfo_symmRecipInfo(
in template (value) SymmRecipientInfo p_symmRecipInfo
) := {
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment