class XMLSecurityKey in SAML SP 2.0 Single Sign On (SSO) - SAML Service Provider 8
Hierarchy
- class \Drupal\miniorange_saml\XMLSecurityKey
Expanded class hierarchy of XMLSecurityKey
File
- src/
XMLSecurityKey.php, line 50
Namespace
Drupal\miniorange_samlView source
class XMLSecurityKey {
const TRIPLEDES_CBC = 'http://www.w3.org/2001/04/xmlenc#tripledes-cbc';
const AES128_CBC = 'http://www.w3.org/2001/04/xmlenc#aes128-cbc';
const AES192_CBC = 'http://www.w3.org/2001/04/xmlenc#aes192-cbc';
const AES256_CBC = 'http://www.w3.org/2001/04/xmlenc#aes256-cbc';
const RSA_1_5 = 'http://www.w3.org/2001/04/xmlenc#rsa-1_5';
const RSA_OAEP_MGF1P = 'http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p';
const DSA_SHA1 = 'http://www.w3.org/2000/09/xmldsig#dsa-sha1';
const RSA_SHA1 = 'http://www.w3.org/2000/09/xmldsig#rsa-sha1';
const RSA_SHA256 = 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256';
const RSA_SHA384 = 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha384';
const RSA_SHA512 = 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha512';
const HMAC_SHA1 = 'http://www.w3.org/2000/09/xmldsig#hmac-sha1';
/** @var array */
private $cryptParams = array();
/** @var int|string */
public $type = 0;
/** @var mixed|null */
public $key = null;
/** @var string */
public $passphrase = "";
/** @var string|null */
public $iv = null;
/** @var string|null */
public $name = null;
/** @var mixed|null */
public $keyChain = null;
/** @var bool */
public $isEncrypted = false;
/** @var XMLSecEnc|null */
public $encryptedCtx = null;
/** @var mixed|null */
public $guid = null;
/**
* This variable contains the certificate as a string if this key represents an X509-certificate.
* If this key doesn't represent a certificate, this will be null.
* @var string|null
*/
private $x509Certificate = null;
/**
* This variable contains the certificate thumbprint if we have loaded an X509-certificate.
* @var string|null
*/
private $X509Thumbprint = null;
/**
* @param string $type
* @param null|array $params
* @throws Exception
*/
public function __construct($type, $params = null) {
switch ($type) {
case self::TRIPLEDES_CBC:
$this->cryptParams['library'] = 'mcrypt';
$this->cryptParams['cipher'] = MCRYPT_TRIPLEDES;
$this->cryptParams['mode'] = MCRYPT_MODE_CBC;
$this->cryptParams['method'] = 'http://www.w3.org/2001/04/xmlenc#tripledes-cbc';
$this->cryptParams['keysize'] = 24;
break;
case self::AES128_CBC:
$this->cryptParams['library'] = 'mcrypt';
$this->cryptParams['cipher'] = MCRYPT_RIJNDAEL_128;
$this->cryptParams['mode'] = MCRYPT_MODE_CBC;
$this->cryptParams['method'] = 'http://www.w3.org/2001/04/xmlenc#aes128-cbc';
$this->cryptParams['keysize'] = 16;
break;
case self::AES192_CBC:
$this->cryptParams['library'] = 'mcrypt';
$this->cryptParams['cipher'] = MCRYPT_RIJNDAEL_128;
$this->cryptParams['mode'] = MCRYPT_MODE_CBC;
$this->cryptParams['method'] = 'http://www.w3.org/2001/04/xmlenc#aes192-cbc';
$this->cryptParams['keysize'] = 24;
break;
case self::AES256_CBC:
$this->cryptParams['library'] = 'mcrypt';
$this->cryptParams['cipher'] = MCRYPT_RIJNDAEL_128;
$this->cryptParams['mode'] = MCRYPT_MODE_CBC;
$this->cryptParams['method'] = 'http://www.w3.org/2001/04/xmlenc#aes256-cbc';
$this->cryptParams['keysize'] = 32;
break;
case self::RSA_1_5:
$this->cryptParams['library'] = 'openssl';
$this->cryptParams['padding'] = OPENSSL_PKCS1_PADDING;
$this->cryptParams['method'] = 'http://www.w3.org/2001/04/xmlenc#rsa-1_5';
if (is_array($params) && !empty($params['type'])) {
if ($params['type'] == 'public' || $params['type'] == 'private') {
$this->cryptParams['type'] = $params['type'];
break;
}
}
throw new Exception('Certificate "type" (private/public) must be passed via parameters');
case self::RSA_OAEP_MGF1P:
$this->cryptParams['library'] = 'openssl';
$this->cryptParams['padding'] = OPENSSL_PKCS1_OAEP_PADDING;
$this->cryptParams['method'] = 'http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p';
$this->cryptParams['hash'] = null;
if (is_array($params) && !empty($params['type'])) {
if ($params['type'] == 'public' || $params['type'] == 'private') {
$this->cryptParams['type'] = $params['type'];
break;
}
}
throw new Exception('Certificate "type" (private/public) must be passed via parameters');
case self::RSA_SHA1:
$this->cryptParams['library'] = 'openssl';
$this->cryptParams['method'] = 'http://www.w3.org/2000/09/xmldsig#rsa-sha1';
$this->cryptParams['padding'] = OPENSSL_PKCS1_PADDING;
if (is_array($params) && !empty($params['type'])) {
if ($params['type'] == 'public' || $params['type'] == 'private') {
$this->cryptParams['type'] = $params['type'];
break;
}
}
throw new Exception('Certificate "type" (private/public) must be passed via parameters');
case self::RSA_SHA256:
$this->cryptParams['library'] = 'openssl';
$this->cryptParams['method'] = 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256';
$this->cryptParams['padding'] = OPENSSL_PKCS1_PADDING;
$this->cryptParams['digest'] = 'SHA256';
if (is_array($params) && !empty($params['type'])) {
if ($params['type'] == 'public' || $params['type'] == 'private') {
$this->cryptParams['type'] = $params['type'];
break;
}
}
throw new Exception('Certificate "type" (private/public) must be passed via parameters');
case self::RSA_SHA384:
$this->cryptParams['library'] = 'openssl';
$this->cryptParams['method'] = 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha384';
$this->cryptParams['padding'] = OPENSSL_PKCS1_PADDING;
$this->cryptParams['digest'] = 'SHA384';
if (is_array($params) && !empty($params['type'])) {
if ($params['type'] == 'public' || $params['type'] == 'private') {
$this->cryptParams['type'] = $params['type'];
break;
}
}
throw new Exception('Certificate "type" (private/public) must be passed via parameters');
case self::RSA_SHA512:
$this->cryptParams['library'] = 'openssl';
$this->cryptParams['method'] = 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha512';
$this->cryptParams['padding'] = OPENSSL_PKCS1_PADDING;
$this->cryptParams['digest'] = 'SHA512';
if (is_array($params) && !empty($params['type'])) {
if ($params['type'] == 'public' || $params['type'] == 'private') {
$this->cryptParams['type'] = $params['type'];
break;
}
}
throw new Exception('Certificate "type" (private/public) must be passed via parameters');
case self::HMAC_SHA1:
$this->cryptParams['library'] = $type;
$this->cryptParams['method'] = 'http://www.w3.org/2000/09/xmldsig#hmac-sha1';
break;
default:
throw new Exception('Invalid Key Type');
}
$this->type = $type;
}
/**
* Retrieve the key size for the symmetric encryption algorithm..
*
* If the key size is unknown, or this isn't a symmetric encryption algorithm,
* null is returned.
*
* @return int|null The number of bytes in the key.
*/
public function getSymmetricKeySize() {
if (!isset($this->cryptParams['keysize'])) {
return null;
}
return $this->cryptParams['keysize'];
}
/**
* Generates a session key using the openssl-extension or using the mcrypt-extension as a fallback.
* In case of using DES3-CBC the key is checked for a proper parity bits set - Mcrypt doesn't care about the parity bits,
* but others may care.
* @return string
* @throws Exception
*/
public function generateSessionKey() {
if (!isset($this->cryptParams['keysize'])) {
throw new Exception('Unknown key size for type "' . $this->type . '".');
}
$keysize = $this->cryptParams['keysize'];
if (function_exists('openssl_random_pseudo_bytes')) {
/* We have PHP >= 5.3 - use openssl to generate session key. */
$key = openssl_random_pseudo_bytes($keysize);
}
else {
/* Generating random key using iv generation routines */
$key = mcrypt_create_iv($keysize, MCRYPT_RAND);
}
if ($this->type === self::TRIPLEDES_CBC) {
/* Make sure that the generated key has the proper parity bits set.
* Mcrypt doesn't care about the parity bits, but others may care.
*/
for ($i = 0; $i < strlen($key); $i++) {
$byte = ord($key[$i]) & 0xfe;
$parity = 1;
for ($j = 1; $j < 8; $j++) {
$parity ^= $byte >> $j & 1;
}
$byte |= $parity;
$key[$i] = chr($byte);
}
}
$this->key = $key;
return $key;
}
/**
* Get the raw thumbprint of a certificate
*
* @param string $cert
* @return null|string
*/
public static function getRawThumbprint($cert) {
$arCert = explode("\n", $cert);
$data = '';
$inData = false;
foreach ($arCert as $curData) {
if (!$inData) {
if (strncmp($curData, '-----BEGIN CERTIFICATE', 22) == 0) {
$inData = true;
}
}
else {
if (strncmp($curData, '-----END CERTIFICATE', 20) == 0) {
break;
}
$data .= trim($curData);
}
}
if (!empty($data)) {
return strtolower(sha1(base64_decode($data)));
}
return null;
}
/**
* Loads the given key, or - with isFile set true - the key from the keyfile.
*
* @param string $key
* @param bool $isFile
* @param bool $isCert
* @throws Exception
*/
public function loadKey($key, $isFile = false, $isCert = false) {
if ($isFile) {
$this->key = file_get_contents($key);
}
else {
$this->key = $key;
}
if ($isCert) {
$this->key = openssl_x509_read($this->key);
openssl_x509_export($this->key, $str_cert);
$this->x509Certificate = $str_cert;
$this->key = $str_cert;
}
else {
$this->x509Certificate = null;
}
if ($this->cryptParams['library'] == 'openssl') {
if ($this->cryptParams['type'] == 'public') {
if ($isCert) {
/* Load the thumbprint if this is an X509 certificate. */
$this->X509Thumbprint = self::getRawThumbprint($this->key);
}
$this->key = openssl_get_publickey($this->key);
if (!$this->key) {
throw new Exception('Unable to extract public key');
}
}
else {
$this->key = openssl_get_privatekey($this->key, $this->passphrase);
}
}
else {
if ($this->cryptParams['cipher'] == MCRYPT_RIJNDAEL_128) {
/* Check key length */
switch ($this->type) {
case self::AES256_CBC:
if (strlen($this->key) < 25) {
throw new Exception('Key must contain at least 25 characters for this cipher');
}
break;
case self::AES192_CBC:
if (strlen($this->key) < 17) {
throw new Exception('Key must contain at least 17 characters for this cipher');
}
break;
}
}
}
}
/**
* Encrypts the given data (string) using the mcrypt-extension
*
* @param string $data
* @return string
*/
private function encryptMcrypt($data) {
$td = mcrypt_module_open($this->cryptParams['cipher'], '', $this->cryptParams['mode'], '');
$this->iv = mcrypt_create_iv(mcrypt_enc_get_iv_size($td), MCRYPT_RAND);
mcrypt_generic_init($td, $this->key, $this->iv);
if ($this->cryptParams['mode'] == MCRYPT_MODE_CBC) {
$bs = mcrypt_enc_get_block_size($td);
for ($datalen0 = $datalen = strlen($data); $datalen % $bs != $bs - 1; $datalen++) {
$data .= chr(mt_rand(1, 127));
}
$data .= chr($datalen - $datalen0 + 1);
}
$encrypted_data = $this->iv . mcrypt_generic($td, $data);
mcrypt_generic_deinit($td);
mcrypt_module_close($td);
return $encrypted_data;
}
/**
* Decrypts the given data (string) using the mcrypt-extension
*
* @param string $data
* @return string
*/
private function decryptMcrypt($data) {
$td = mcrypt_module_open($this->cryptParams['cipher'], '', $this->cryptParams['mode'], '');
$iv_length = mcrypt_enc_get_iv_size($td);
$this->iv = substr($data, 0, $iv_length);
$data = substr($data, $iv_length);
mcrypt_generic_init($td, $this->key, $this->iv);
$decrypted_data = mdecrypt_generic($td, $data);
mcrypt_generic_deinit($td);
mcrypt_module_close($td);
if ($this->cryptParams['mode'] == MCRYPT_MODE_CBC) {
$dataLen = strlen($decrypted_data);
$paddingLength = substr($decrypted_data, $dataLen - 1, 1);
$decrypted_data = substr($decrypted_data, 0, $dataLen - ord($paddingLength));
}
return $decrypted_data;
}
/**
* Encrypts the given data (string) using the openssl-extension
*
* @param string $data
* @return string
* @throws Exception
*/
private function encryptOpenSSL($data) {
if ($this->cryptParams['type'] == 'public') {
if (!openssl_public_encrypt($data, $encrypted_data, $this->key, $this->cryptParams['padding'])) {
throw new Exception('Failure encrypting Data');
}
}
else {
if (!openssl_private_encrypt($data, $encrypted_data, $this->key, $this->cryptParams['padding'])) {
throw new Exception('Failure encrypting Data');
}
}
return $encrypted_data;
}
/**
* Decrypts the given data (string) using the openssl-extension
*
* @param string $data
* @return string
* @throws Exception
*/
private function decryptOpenSSL($data) {
if ($this->cryptParams['type'] == 'public') {
if (!openssl_public_decrypt($data, $decrypted, $this->key, $this->cryptParams['padding'])) {
throw new Exception('Failure decrypting Data');
}
}
else {
if (!openssl_private_decrypt($data, $decrypted, $this->key, $this->cryptParams['padding'])) {
throw new Exception('Failure decrypting Data');
}
}
return $decrypted;
}
/**
* Signs the given data (string) using the openssl-extension
*
* @param string $data
* @return string
* @throws Exception
*/
private function signOpenSSL($data) {
$algo = OPENSSL_ALGO_SHA1;
if (!empty($this->cryptParams['digest'])) {
$algo = $this->cryptParams['digest'];
}
if (!openssl_sign($data, $signature, $this->key, $algo)) {
throw new Exception('Failure Signing Data: ' . openssl_error_string() . ' - ' . $algo);
}
return $signature;
}
/**
* Verifies the given data (string) belonging to the given signature using the openssl-extension
*
* Returns:
* 1 on succesful signature verification,
* 0 when signature verification failed,
* -1 if an error occurred during processing.
*
* NOTE: be very careful when checking the return value, because in PHP,
* -1 will be cast to True when in boolean context. So always check the
* return value in a strictly typed way, e.g. "$obj->verify(...) === 1".
*
* @param string $data
* @param string $signature
* @return int
*/
private function verifyOpenSSL($data, $signature) {
$algo = OPENSSL_ALGO_SHA1;
if (!empty($this->cryptParams['digest'])) {
$algo = $this->cryptParams['digest'];
}
return openssl_verify($data, $signature, $this->key, $algo);
}
/**
* Encrypts the given data (string) using the regarding php-extension, depending on the library assigned to algorithm in the contructor.
*
* @param string $data
* @return mixed|string
*/
public function encryptData($data) {
switch ($this->cryptParams['library']) {
case 'mcrypt':
return $this
->encryptMcrypt($data);
case 'openssl':
return $this
->encryptOpenSSL($data);
}
}
/**
* Decrypts the given data (string) using the regarding php-extension, depending on the library assigned to algorithm in the contructor.
*
* @param string $data
* @return mixed|string
*/
public function decryptData($data) {
switch ($this->cryptParams['library']) {
case 'mcrypt':
return $this
->decryptMcrypt($data);
case 'openssl':
return $this
->decryptOpenSSL($data);
}
}
/**
* Signs the data (string) using the extension assigned to the type in the constructor.
*
* @param string $data
* @return mixed|string
*/
public function signData($data) {
switch ($this->cryptParams['library']) {
case 'openssl':
return $this
->signOpenSSL($data);
case self::HMAC_SHA1:
return hash_hmac("sha1", $data, $this->key, true);
}
}
/**
* Verifies the data (string) against the given signature using the extension assigned to the type in the constructor.
*
* Returns in case of openSSL:
* 1 on succesful signature verification,
* 0 when signature verification failed,
* -1 if an error occurred during processing.
*
* NOTE: be very careful when checking the return value, because in PHP,
* -1 will be cast to True when in boolean context. So always check the
* return value in a strictly typed way, e.g. "$obj->verify(...) === 1".
*
* @param string $data
* @param string $signature
* @return bool|int
*/
public function verifySignature($data, $signature) {
switch ($this->cryptParams['library']) {
case 'openssl':
return $this
->verifyOpenSSL($data, $signature);
case self::HMAC_SHA1:
$expectedSignature = hash_hmac("sha1", $data, $this->key, true);
return strcmp($signature, $expectedSignature) == 0;
}
}
/**
* @return mixed
* @see getAlgorithm()
* @deprecated
*/
public function getAlgorith() {
return $this
->getAlgorithm();
}
/**
* @return mixed
*/
public function getAlgorithm() {
return $this->cryptParams['method'];
}
/**
*
* @param int $type
* @param string $string
* @return null|string
*/
public static function makeAsnSegment($type, $string) {
switch ($type) {
case 0x2:
if (ord($string) > 0x7f) {
$string = chr(0) . $string;
}
break;
case 0x3:
$string = chr(0) . $string;
break;
}
$length = strlen($string);
if ($length < 128) {
$output = sprintf("%c%c%s", $type, $length, $string);
}
else {
if ($length < 0x100) {
$output = sprintf("%c%c%c%s", $type, 0x81, $length, $string);
}
else {
if ($length < 0x10000) {
$output = sprintf("%c%c%c%c%s", $type, 0x82, $length / 0x100, $length % 0x100, $string);
}
else {
$output = null;
}
}
}
return $output;
}
/**
*
* Hint: Modulus and Exponent must already be base64 decoded
* @param string $modulus
* @param string $exponent
* @return string
*/
public static function convertRSA($modulus, $exponent) {
/* make an ASN publicKeyInfo */
$exponentEncoding = self::makeAsnSegment(0x2, $exponent);
$modulusEncoding = self::makeAsnSegment(0x2, $modulus);
$sequenceEncoding = self::makeAsnSegment(0x30, $modulusEncoding . $exponentEncoding);
$bitstringEncoding = self::makeAsnSegment(0x3, $sequenceEncoding);
$rsaAlgorithmIdentifier = pack("H*", "300D06092A864886F70D0101010500");
$publicKeyInfo = self::makeAsnSegment(0x30, $rsaAlgorithmIdentifier . $bitstringEncoding);
/* encode the publicKeyInfo in base64 and add PEM brackets */
$publicKeyInfoBase64 = base64_encode($publicKeyInfo);
$encoding = "-----BEGIN PUBLIC KEY-----\n";
$offset = 0;
while ($segment = substr($publicKeyInfoBase64, $offset, 64)) {
$encoding = $encoding . $segment . "\n";
$offset += 64;
}
return $encoding . "-----END PUBLIC KEY-----\n";
}
/**
* @param mixed $parent
*/
public function serializeKey($parent) {
}
/**
* Retrieve the X509 certificate this key represents.
*
* Will return the X509 certificate in PEM-format if this key represents
* an X509 certificate.
*
* @return string The X509 certificate or null if this key doesn't represent an X509-certificate.
*/
public function getX509Certificate() {
return $this->x509Certificate;
}
/**
* Get the thumbprint of this X509 certificate.
*
* Returns:
* The thumbprint as a lowercase 40-character hexadecimal number, or null
* if this isn't a X509 certificate.
*
* @return string Lowercase 40-character hexadecimal number of thumbprint
*/
public function getX509Thumbprint() {
return $this->X509Thumbprint;
}
/**
* Create key from an EncryptedKey-element.
*
* @param DOMElement $element The EncryptedKey-element.
* @return XMLSecurityKey The new key.
* @throws Exception
*
*/
public static function fromEncryptedKeyElement(DOMElement $element) {
$objenc = new XMLSecEnc();
$objenc
->setNode($element);
if (!($objKey = $objenc
->locateKey())) {
throw new Exception("Unable to locate algorithm for this Encrypted Key");
}
$objKey->isEncrypted = true;
$objKey->encryptedCtx = $objenc;
XMLSecEnc::staticLocateKeyInfo($objKey, $element);
return $objKey;
}
}
Members
Name | Modifiers | Type | Description | Overrides |
---|---|---|---|---|
XMLSecurityKey:: |
private | property | @var array | |
XMLSecurityKey:: |
public | property | @var XMLSecEnc|null | |
XMLSecurityKey:: |
public | property | @var mixed|null | |
XMLSecurityKey:: |
public | property | @var bool | |
XMLSecurityKey:: |
public | property | @var string|null | |
XMLSecurityKey:: |
public | property | @var mixed|null | |
XMLSecurityKey:: |
public | property | @var mixed|null | |
XMLSecurityKey:: |
public | property | @var string|null | |
XMLSecurityKey:: |
public | property | @var string | |
XMLSecurityKey:: |
public | property | @var int|string | |
XMLSecurityKey:: |
private | property | This variable contains the certificate as a string if this key represents an X509-certificate. If this key doesn't represent a certificate, this will be null. | |
XMLSecurityKey:: |
private | property | This variable contains the certificate thumbprint if we have loaded an X509-certificate. | |
XMLSecurityKey:: |
constant | |||
XMLSecurityKey:: |
constant | |||
XMLSecurityKey:: |
constant | |||
XMLSecurityKey:: |
public static | function | Hint: Modulus and Exponent must already be base64 decoded | |
XMLSecurityKey:: |
public | function | Decrypts the given data (string) using the regarding php-extension, depending on the library assigned to algorithm in the contructor. | |
XMLSecurityKey:: |
private | function | Decrypts the given data (string) using the mcrypt-extension | |
XMLSecurityKey:: |
private | function | Decrypts the given data (string) using the openssl-extension | |
XMLSecurityKey:: |
constant | |||
XMLSecurityKey:: |
public | function | Encrypts the given data (string) using the regarding php-extension, depending on the library assigned to algorithm in the contructor. | |
XMLSecurityKey:: |
private | function | Encrypts the given data (string) using the mcrypt-extension | |
XMLSecurityKey:: |
private | function | Encrypts the given data (string) using the openssl-extension | |
XMLSecurityKey:: |
public static | function | Create key from an EncryptedKey-element. | |
XMLSecurityKey:: |
public | function | Generates a session key using the openssl-extension or using the mcrypt-extension as a fallback. In case of using DES3-CBC the key is checked for a proper parity bits set - Mcrypt doesn't care about the parity bits, but others may care. | |
XMLSecurityKey:: |
public | function | ||
XMLSecurityKey:: |
public | function | ||
XMLSecurityKey:: |
public static | function | Get the raw thumbprint of a certificate | |
XMLSecurityKey:: |
public | function | Retrieve the key size for the symmetric encryption algorithm.. | |
XMLSecurityKey:: |
public | function | Retrieve the X509 certificate this key represents. | |
XMLSecurityKey:: |
public | function | Get the thumbprint of this X509 certificate. | |
XMLSecurityKey:: |
constant | |||
XMLSecurityKey:: |
public | function | Loads the given key, or - with isFile set true - the key from the keyfile. | |
XMLSecurityKey:: |
public static | function | ||
XMLSecurityKey:: |
constant | |||
XMLSecurityKey:: |
constant | |||
XMLSecurityKey:: |
constant | |||
XMLSecurityKey:: |
constant | |||
XMLSecurityKey:: |
constant | |||
XMLSecurityKey:: |
constant | |||
XMLSecurityKey:: |
public | function | ||
XMLSecurityKey:: |
public | function | Signs the data (string) using the extension assigned to the type in the constructor. | |
XMLSecurityKey:: |
private | function | Signs the given data (string) using the openssl-extension | |
XMLSecurityKey:: |
constant | |||
XMLSecurityKey:: |
private | function | Verifies the given data (string) belonging to the given signature using the openssl-extension | |
XMLSecurityKey:: |
public | function | Verifies the data (string) against the given signature using the extension assigned to the type in the constructor. | |
XMLSecurityKey:: |
public | function |