Newer
Older
/**
* @author ETSI / STF481
* @version $URL$
* $Id$
* @desc Module containing functions for Security Protocol
*
*/
module LibItsSecurity_Functions {
garciay
committed
import from LibCommon_DataStrings all;
garciay
committed
// LibItsCommon
import from LibItsCommon_Functions all;
import from LibItsSecurity_TypesAndValues all;
garciay
committed
import from LibItsSecurity_Templates all;
import from LibItsSecurity_Pixits all;
/**
* @desc Produces a 256-bit (32-byte) hash value
* @param p_toBeHashedData Data to be used to calculate the hash value
* @return The hash value
function f_hashWithSha256(in octetstring p_toBeHashedData) return Oct32 {
return fx_hashWithSha256(p_toBeHashedData);
} // End of function f_hashWithSha256
/**
* @desc Produces a Elliptic Curve Digital Signature Algorithm (ECDSA) signaturee
* @param p_toBeSignedSecuredMessage The data to be signed
* @return The signature value
function f_signWithEcdsaNistp256WithSha256(in Oct32 p_toBeSignedSecuredMessage) return octetstring {
return fx_signWithEcdsaNistp256WithSha256(
p_toBeSignedSecuredMessage,
PX_TA_CONFIGS[PX_CERTIFICATE_CONFIG_IDX].signingPrivateKey
} // End of function f_signWithEcdsaNistp256WithSha256
function f_HashedId8FromSha256(in Oct32 p_hash)
return HashedId8 {
return substr(p_hash, 0, 8);
} // End of function f_HashedId8FromSha256
function f_HashedId3FromHashedId8 (in HashedId8 p_hash)
return HashedId3 {
return substr(p_hash, 0, 3);
} // End of function f_HashedId3FromHashedId8
/**
* @desc Verify the signature of the specified data
* @param p_toBeVerifiedData The data to be verified
* @param p_signature The signature
* @param p_ecdsaNistp256PublicKeyX The public key (x coordinate)
* @param p_ecdsaNistp256PublicKeyY The public key (y coordinate)
* @return true on success, false otherwise
*/
garciay
committed
function f_verifyWithEcdsaNistp256WithSha256(
in octetstring p_toBeVerifiedData,
in octetstring p_signature,
in octetstring p_ecdsaNistp256PublicKeyX,
in octetstring p_ecdsaNistp256PublicKeyY
) return boolean {
// log("p_toBeVerifiedData= ", p_toBeVerifiedData);
// log("p_signature= ", p_signature);
// log("p_ecdsaNistp256PublicKeyX= ", p_ecdsaNistp256PublicKeyX);
// log("p_ecdsaNistp256PublicKeyY= ", p_ecdsaNistp256PublicKeyY);
return fx_verifyWithEcdsaNistp256WithSha256(
p_toBeVerifiedData,
p_signature,
p_ecdsaNistp256PublicKeyX,
p_ecdsaNistp256PublicKeyY);
} // End of function f_verifyWithEcdsaNistp256WithSha256
/**
* @desc Calculate digest over the certificate
* @return the digest
* @see Draft ETSI TS 103 097 V1.1.6 Clause 4.2.13 HashedId8
function f_calculateDigest(in Certificate p_cert) return HashedId8 {
var octetstring v_toBeHashedData;
var octetstring v_hash;
var integer v_counter;
// Search for digest in the signer_infos field first
for (v_counter := 0; v_counter < lengthof(p_cert.signer_infos); v_counter := v_counter + 1) {
if (p_cert.signer_infos[v_counter].type_ == e_certificate_digest_with_ecdsap256) {
return p_cert.signer_infos[v_counter].signerInfo.digest;
}
} // End of 'for' statement
// digest not found, compute it
v_toBeHashedData := bit2oct(encvalue(p_cert));
v_hash := f_hashWithSha256(v_toBeHashedData);
return substr(v_hash, lengthof(v_hash) - 8, 8);
/**
* @desc Build a template of a secured beacon to be used for the Test Adapter secured beaconing processing
*/
function f_buildSecuredMessagePayloadToBeSigned()
return ToBeSignedSecuredMessage {
var template (value) ToBeSignedSecuredMessage v_toBeSignedSecuredMessage;
v_toBeSignedSecuredMessage := m_toBeSignedSecuredMessage(
c_security_profileOthers,
{ // Field HeaderFields
m_header_field_signer_info(
m_signerInfo_certificate(
PX_TA_CONFIGS[PX_CERTIFICATE_CONFIG_IDX].atCertificate
) // End of template m_signerInfo_certificate
), // End of template m_header_field_signer_info
m_header_field_generation_time(oct2int('BBBBBBBB'O)), // To be replaced by TA with current time
m_header_field_generation_location(
PX_TA_CONFIGS[PX_CERTIFICATE_CONFIG_IDX].location
)
}, // End of field HeaderFields
{
m_payload_unsecured(
'AAAAAAAAAA'O // To be replaced by TA with real payload
)
}, // End of field HeaderFields
e_signature
);
/**
* @desc This function build and sign the SecureMessage part covered by the signature process
* @param p_securedMessage The signed SecureMessage part
* @param p_unsecuredPayload The unsigned payload (e.g. a beacon)
* @param p_threeDLocation The ThreeDLocation value
* @param p_headerFields Additional HeaderFields
* @return true on success, false otherwise
* @verdict Unchanged
*/
function f_buildGnSecuredCam(
out template (value) SecuredMessage p_securedMessage,
in octetstring p_unsecuredPayload,
in ThreeDLocation p_threeDLocation,
in template (omit) HeaderFields p_headerFields := omit
) return boolean {
// Local variables
var octetstring v_secPayload, v_signature;
var Oct32 v_hash;
var template (value) ToBeSignedSecuredMessage v_toBeSignedSecuredMessage;
// Create SecuredMessage payload to be signed
v_toBeSignedSecuredMessage := m_toBeSignedSecuredMessage(
{ // Field HeaderFields
m_header_field_signer_info(
m_signerInfo_certificate(
PX_TA_CONFIGS[PX_CERTIFICATE_CONFIG_IDX].atCertificate
) // End of template m_signerInfo_certificate
), // End of template m_header_field_signer_info
m_header_field_generation_time(f_getCurrentTime()),
m_header_field_generation_location(
p_threeDLocation
),
m_header_field_message_type(c_messageType_CAM)
}, // End of field HeaderFields
{
m_payload_unsecured(
p_unsecuredPayload
)
}, // End of field HeaderFields
e_signature
);
log("v_toBeSignedSecuredMessage=", v_toBeSignedSecuredMessage);
// Add additional header fields if any
if (ispresent(p_headerFields) == true) {
var integer v_addItemIndex := lengthof(v_toBeSignedSecuredMessage.header_fields);
var integer v_counter;
for (v_counter := 0; v_counter < lengthof(p_headerFields); v_counter := v_counter + 1) {
v_toBeSignedSecuredMessage.header_fields[v_addItemIndex] := p_headerFields[v_counter];
v_addItemIndex := v_addItemIndex + 1;
} // End of 'for' statement
}
v_secPayload := bit2oct(encvalue(v_toBeSignedSecuredMessage));
log("v_secPayload= ", v_secPayload);
// Calculate the hash of the SecuredMessage payload to be signed
v_hash := f_hashWithSha256(v_secPayload);
// Signed payload
v_signature := f_signWithEcdsaNistp256WithSha256(
v_hash
);
log("v_signature= ", v_signature);
p_securedMessage := md_secureMessage_profileCam( // See Clause 7.1 Security profile for CAMs
v_toBeSignedSecuredMessage.header_fields,
v_toBeSignedSecuredMessage.payload_fields,
{
m_trailer_field_signature(
m_signature(
m_ecdsaSignature(
m_eccPointecdsa_nistp256_with_sha256_y_coordinate_only(
substr(v_signature, 2, 32)
),
substr(v_signature, 34, 32)
)
); // End of template m_securedMessage_profileCam
/**
* @desc This function build and sign the SecureMessage part covered by the signature process
* @param p_securedMessage The signed SecureMessage part
* @param p_unsecuredPayload The unsigned payload (e.g. a beacon)
* @param p_threeDLocation The ThreeDLocation value
* @param p_headerFields Additional HeaderFields
* @return true on success, false otherwise
* @verdict Unchanged
*/
function f_buildGnSecuredDenm(
out template (value) SecuredMessage p_securedMessage,
in octetstring p_unsecuredPayload,
in ThreeDLocation p_threeDLocation,
in template (omit) HeaderFields p_headerFields := omit
) return boolean {
// Local variables
var octetstring v_secPayload, v_signature;
var Oct32 v_hash;
var template (value) ToBeSignedSecuredMessage v_toBeSignedSecuredMessage;
// Create SecuredMessage payload to be signed
v_toBeSignedSecuredMessage := m_toBeSignedSecuredMessage(
{ // Field HeaderFields
m_header_field_signer_info(
m_signerInfo_certificate(
PX_TA_CONFIGS[PX_CERTIFICATE_CONFIG_IDX].atCertificate
) // End of template m_signerInfo_certificate
), // End of template m_header_field_signer_info
m_header_field_generation_time(f_getCurrentTime()),
m_header_field_generation_location(
p_threeDLocation
),
m_header_field_message_type(c_messageType_DENM)
}, // End of field HeaderFields
{
m_payload_unsecured(
p_unsecuredPayload
)
}, // End of field HeaderFields
e_signature
);
// Add additional header fields if any
if (ispresent(p_headerFields) == true) {
var integer v_addItemIndex := lengthof(v_toBeSignedSecuredMessage.header_fields);
var integer v_counter;
for (v_counter := 0; v_counter < lengthof(p_headerFields); v_counter := v_counter + 1) {
v_toBeSignedSecuredMessage.header_fields[v_addItemIndex] := p_headerFields[v_counter];
v_addItemIndex := v_addItemIndex + 1;
} // End of 'for' statement
}
log("v_toBeSignedSecuredMessage= ", v_toBeSignedSecuredMessage);
v_secPayload := bit2oct(encvalue(v_toBeSignedSecuredMessage));
// Calculate the hash of the SecuredMessage payload to be signed
v_hash := f_hashWithSha256(v_secPayload);
// Signed payload
v_signature := f_signWithEcdsaNistp256WithSha256(
v_hash
);
p_securedMessage := md_secureMessage_profileDenm( // See Clause 7.2 Security profile for DENMs
v_toBeSignedSecuredMessage.header_fields,
v_toBeSignedSecuredMessage.payload_fields,
{
m_trailer_field_signature(
m_signature(
m_ecdsaSignature(
m_eccPointecdsa_nistp256_with_sha256_y_coordinate_only(
substr(v_signature, 2, 32)
),
substr(v_signature, 34, 32)
)
); // End of template m_securedMessage_profileDenm
return true;
} // End of function f_buildGnSecuredDenm
garciay
committed
/**
* @desc This function build and sign the SecureMessage part covered by the signature process
* @param p_securedMessage The signed SecureMessage part
* @param p_unsecuredPayload The unsigned payload (e.g. a beacon)
* @param p_threeDLocation The ThreeDLocation value
* @param p_headerFields Additional HeaderFields
* @return true on success, false otherwise
* @verdict Unchanged
*/
function f_buildGnSecuredOtherMessage(
out template (value) SecuredMessage p_securedMessage,
in octetstring p_unsecuredPayload,
in ThreeDLocation p_threeDLocation,
in template (omit) HeaderFields p_headerFields := omit
) return boolean {
// Local variables
var octetstring v_secPayload, v_signature;
var Oct32 v_hash;
var template (value) ToBeSignedSecuredMessage v_toBeSignedSecuredMessage;
// Create SecuredMessage payload to be signed
v_toBeSignedSecuredMessage := m_toBeSignedSecuredMessage(
c_security_profileOthers,
{ // Field HeaderFields
m_header_field_signer_info(
m_signerInfo_certificate(
PX_TA_CONFIGS[PX_CERTIFICATE_CONFIG_IDX].atCertificate
) // End of template m_signerInfo_certificate
), // End of template m_header_field_signer_info
m_header_field_generation_time(f_getCurrentTime()),
m_header_field_generation_location(
p_threeDLocation
)
}, // End of field HeaderFields
{
m_payload_unsecured(
p_unsecuredPayload
)
}, // End of field HeaderFields
e_signature
);
// Add additional header fields if any
if (ispresent(p_headerFields) == true) {
var integer v_addItemIndex := lengthof(v_toBeSignedSecuredMessage.header_fields);
var integer v_counter;
for (v_counter := 0; v_counter < lengthof(p_headerFields); v_counter := v_counter + 1) {
v_toBeSignedSecuredMessage.header_fields[v_addItemIndex] := p_headerFields[v_counter];
v_addItemIndex := v_addItemIndex + 1;
} // End of 'for' statement
}
log("v_toBeSignedSecuredMessage= ", v_toBeSignedSecuredMessage);
v_secPayload := bit2oct(encvalue(v_toBeSignedSecuredMessage));
// Calculate the hash of the SecuredMessage payload to be signed
v_hash := f_hashWithSha256(v_secPayload);
// Signed payload
v_signature := f_signWithEcdsaNistp256WithSha256(
v_hash
);
p_securedMessage := md_securedMessage_profileOther( // See Clause 7.3 Generic security profile for other signed messages
v_toBeSignedSecuredMessage.header_fields,
v_toBeSignedSecuredMessage.payload_fields,
{
m_trailer_field_signature(
m_signature(
m_ecdsaSignature(
m_eccPointecdsa_nistp256_with_sha256_y_coordinate_only(
substr(v_signature, 2, 32)
),
substr(v_signature, 34, 32)
)
garciay
committed
)
)
); // End of template md_securedMessage_profileOther
return true;
} // End of function f_buildGnSecuredOtherMessage
} // End of group hostSignatureHelpers
group deviceSignatureHelpers {
/**
* @desc Verify the signature of the provided certificate
* @param p_certificateToBeVerified Certificate to be verified
* @param p_publicKey Public key to verify the certificate signature
*/
function f_verifyCertificateSignatureWithPublicKey(
in template (value) Certificate p_certificateToBeVerified,
in template (value) PublicKey p_publicKey
var octetstring toBeVerifiedData;
var octetstring v_signature;
var template (value) ToBeSignedCertificate v_toBeSignedCertificate;
// Create Certificate payload to be verified
v_toBeSignedCertificate := m_toBeSignedCertificate(p_certificateToBeVerified);
toBeVerifiedData := bit2oct(encvalue(v_toBeSignedCertificate));
// Build the signature
v_signature :=
valueof(p_certificateToBeVerified.signature_.signature_.ecdsa_signature.r.x) &
valueof(p_certificateToBeVerified.signature_.signature_.ecdsa_signature.s);
// Verify the certificate
v_result := f_verifyWithEcdsaNistp256WithSha256(
toBeVerifiedData,
v_signature,
valueof(p_publicKey.public_key.eccPoint.x),
valueof(p_publicKey.public_key.eccPoint.y.y)
} // End of finction f_verifyCertificateSignatureWithPublicKey
/**
* @desc Verify the signature of the provided secured message
* @param p_certificateToBeVerified Certificate to be verified
* @param p_issuingCertificate Issuing certificate
* @return true on success, false otherwise
* @verdict
*/
function f_verifyCertificateSignatureWithIssuingCertificate(
in template (value) Certificate p_certificateToBeVerified,
in template (value) Certificate p_issuingCertificate
var integer v_counter;
for (v_counter := 0; v_counter < lengthof(p_issuingCertificate.subject_attributes); v_counter := v_counter + 1) {
if (valueof(p_issuingCertificate.subject_attributes[v_counter].type_) == e_verification_key) {
return f_verifyCertificateSignatureWithPublicKey(
p_certificateToBeVerified,
p_issuingCertificate.subject_attributes[v_counter].attribute.key);
}
} // End of 'for' statement
return false;
} // End of function f_verifyCertificateSignatureWithIssuingCertificate
/**
* @desc Verify the signature of the provided secured message
* @param p_securedMessage The message to be verified
* @param p_publicKey The ECDSA public key to verify a signature
* @param p_certificate Certificate to be used to verify the message
* @return true on success, false otherwise
function f_verifyGnSecuredMessageSignatureWithPublicKey(
in template (value) SecuredMessage p_securedMessage,
in template (value) PublicKey p_publicKey
) return boolean {
// Local variables
var octetstring v_secPayload;
var octetstring v_signedData;
var Oct32 v_hash;
var integer v_counter;
var boolean v_result := false;
var template (value) ToBeSignedSecuredMessage v_toBeSignedSecuredMessage;
log(">>> f_verifyGnSecuredMessageSignatureWithPublicKey: ", p_securedMessage);
// Create SecuredMessage payload to be signed
v_toBeSignedSecuredMessage := m_toBeSignedSecuredMessage(
p_securedMessage.header_fields,
p_securedMessage.payload_fields,
e_signature
);
v_secPayload := bit2oct(encvalue(v_toBeSignedSecuredMessage));
log("f_verifyGnSecuredMessageSignatureWithPublicKey: v_secPayload=", v_secPayload);
// Calculate the hash of the SecuredMessage payload to be signed
v_hash := fx_hashWithSha256(v_secPayload);
log("f_verifyGnSecuredMessageSignatureWithPublicKey: v_hash=", v_hash);
// Verify payload
for (v_counter := 0; v_counter < lengthof(p_securedMessage.trailer_fields); v_counter := v_counter + 1) {
if (
(valueof(p_securedMessage).trailer_fields[v_counter].type_ == e_signature) and
(valueof(p_securedMessage).trailer_fields[v_counter].trailerField.signature_.algorithm == e_ecdsa_nistp256_with_sha256)
) {
v_signedData :=
'0000'O &
valueof(p_securedMessage).trailer_fields[v_counter].trailerField.signature_.signature_.ecdsa_signature.r.x &
valueof(p_securedMessage).trailer_fields[v_counter].trailerField.signature_.signature_.ecdsa_signature.s;
v_result := f_verifyWithEcdsaNistp256WithSha256(
v_hash,
v_signedData,
valueof(p_publicKey).public_key.eccPoint.x,
valueof(p_publicKey).public_key.eccPoint.y.y
);
}
} // End of 'for' statement
return v_result;
} // End of function f_verifyCertificateSignatureWithPublicKey
/**
* @desc Verify the signature of the provided secured message
* @param p_securedMessage
* @param p_certificate Certificate to be used to verify the message
* @return true on success, false otherwise
* @verdict
*/
function f_verifyGnSecuredMessageSignatureWithCertificate(
in template (value) SecuredMessage p_securedMessage,
in template (value) Certificate p_certificate
var integer v_counter;
for (v_counter := 0; v_counter < lengthof(p_certificate.subject_attributes); v_counter := v_counter + 1) {
log("f_verifyGnSecuredMessageSignatureWithCertificate: processing ", p_certificate.subject_attributes[v_counter]);
if (valueof(p_certificate).subject_attributes[v_counter].type_ == e_verification_key) {
return f_verifyGnSecuredMessageSignatureWithPublicKey(
p_securedMessage,
p_certificate.subject_attributes[v_counter].attribute.key);
}
} // End of 'for' statement
return false;
} // End of function f_verifyGnSecuredOtherMessageWithDeviceCertificate
} // End of group deviceSignatureHelpers
garciay
committed
* @desc return SecuredMessage header field of given type or null if none
* @param p_msg the SecuredMessage
* @param p_type header field type
* @return HeaderField of given type if any or null
*/
in template (value) SecuredMessage p_securedMessage,
in HeaderFieldType p_headerFieldType,
out HeaderField p_return
) return boolean {
var integer v_length := lengthof(p_securedMessage.header_fields);
for (var integer i := 0; i < v_length; i := i + 1) {
if (valueof(p_securedMessage).header_fields[i].type_ == p_headerFieldType) {
p_return := valueof(p_securedMessage).header_fields[i];
return true;
}
/**
* @desc return SignerInfo SecuredMessage field
*/
function f_getMsgSignerInfo (
) return boolean {
var HeaderField v_hf;
if(f_getMsgHeaderField(p_securedMessage, e_signer_info, v_hf)){
if(isbound(v_hf.headerField.signer)){
p_signerInfo := v_hf.headerField.signer;
return true;
}
}// End of group messageGetters
group certificateGetters {
function f_getCertificateValidityRestriction(
in template (value) Certificate p_cert,
in ValidityRestrictionType p_type,
out ValidityRestriction p_return
) return boolean {
for (var integer i := 0; i < lengthof(p_cert.validity_restrictions); i := i + 1) {
if (valueof(p_cert).validity_restrictions[i].type_ == p_type ) {
p_return := valueof(p_cert).validity_restrictions[i];
return true;
function f_getCertificateSignerInfo (
in template (value) Certificate p_cert,
out SignerInfo p_si
) return boolean {
if( isbound(valueof(p_cert).signer_infos)
and lengthof(p_cert.signer_infos) > 0) {
p_si := valueof(p_cert).signer_infos[0];
return true;
function f_askForCertificate (in template (value) HashedId8 p_digest) {
// Send CAM message with insert the request_unrecognized_certificate
// header containing specified digest
/* TODO: Waiting Alex to understand how to send message
var SecuredMessage v_msg;
if ( f_buildGnSecuredCam (v_msg,
in octetstring p_unsecuredPayload,
in ThreeDLocation p_threeDLocation,
in template (omit) HeaderFields p_headerFileds := omit
)) {
}
*/
} // End of group helpersFunctions
group externalFunctions {
/**
* @desc Produces a 256-bit (32-byte) hash value
* @param p_toBeHashedData Data to be used to calculate the hash value
* @return The hash value
*/
external function fx_hashWithSha256(in octetstring p_toBeHashedData) return Oct32;
/**
* @desc Produces a Elliptic Curve Digital Signature Algorithm (ECDSA) signaturee
* @param p_toBeSignedSecuredMessage The data to be signed
* @param p_privateKey The private key
* @return The signature value
*/
external function fx_signWithEcdsaNistp256WithSha256(in octetstring p_toBeSignedSecuredMessage, in octetstring/*UInt64*/ p_privateKey) return octetstring;
/**
* @desc Verify the signature of the specified data
* @param p_toBeVerifiedData The data to be verified
* @param p_signature The signature
* @param p_ecdsaNistp256PublicKeyX The public key (x coordinate)
* @param p_ecdsaNistp256PublicKeyY The public key (y coordinate)
* @return true on success, false otherwise
*/
external function fx_verifyWithEcdsaNistp256WithSha256(in octetstring p_toBeVerifiedData, in octetstring p_signature, in octetstring p_ecdsaNistp256PublicKeyX, in octetstring p_ecdsaNistp256PublicKeyY) return boolean;
* @desc Produce a new public/private key pair based on Elliptic Curve Digital Signature Algorithm (ECDSA) algorithm.
* This function should not be used by the ATS
* @param p_privateKey The new private key value
* @param p_publicKeyX The new public key value (x coordinate)
* @param p_publicKeyX The new public key value (y coordinate)
* @return true on success, false otherwise
*/
external function fx_generateKeyPair(out octetstring/*UInt64*/ p_privateKey, out octetstring p_publicKeyX, out octetstring p_publicKeyY) return boolean;
} // End of group externalFunctions
group geometryFunctions {
/**
* @desc Check that given location is valid
* @param p_location location to be checked
* @return true on success, false otherwise
*/
function f_isValidTwoDLocation(in template (value) TwoDLocation p_location
) return boolean {
return valueof(p_location).longitude != c_maxLongitude+1
and valueof(p_location).latitude != c_maxLatitude+1;
/**
* @desc Check that two given rectanlular regions are intersected
* Note: Regions must be normalized(northwest.latitude >= southeast.latitude)
* TODO: Add case when
* @param p_r1 Region 1
* @param p_r2 Region 2
*
* @return true on success, false otherwise
*/
function f_isRectangularRegionsIntersected(in template (value) RectangularRegion p_r1,
in template (value) RectangularRegion p_r2
) return boolean {
return not ( valueof(p_r2).northwest.longitude > valueof(p_r1).southeast.longitude
or valueof(p_r2).southeast.longitude < valueof(p_r1).northwest.longitude
or valueof(p_r2).southeast.latitude > valueof(p_r1).northwest.latitude
or valueof(p_r2).northwest.latitude < valueof(p_r1).southeast.latitude );
}
function f_isContinuousRectangularRegions(in template (value) RectangularRegions regions
) return boolean {
function f_isRectangularRegionsInside(in template (value) RectangularRegions p_parent,
in template (value) RectangularRegions p_region
) return boolean {
// TODO: convert rectangular regions to polygons and check polygons
return true;
}
/**
* @desc Check that given polygon doesn't have neither self-intersections no holes.
* @param p_region Polygonal Region
* @return true on success, false otherwise
*/
function f_isValidPolygonalRegion(in template (value) PolygonalRegion p_region
) return boolean {
// TODO: call external function
function f_isPolygonalRegionInside(in template (value) PolygonalRegion p_parent,
in template (value) PolygonalRegion p_region
) return boolean {
// TODO: call external function
return true;
}