i don't know if this question is very easy and I just didn't figure it out how to sign with HashiCorp-Vault´s Api VaultSharp, but I am despairing.
The entire Documentation with examples can be found here: https://github.com/rajanadar/VaultSharp
Encryption and Decryption works fine. Only Signing is a problem.
Code for Encryption:
public byte[] EncryptData(byte[] data, string keyName)
{
SecretsEngine transitSecretsEngine = new SecretsEngine
{
Type = SecretsEngineType.Transit,
Path = path
};
Client.V1.System.MountSecretBackendAsync(transitSecretsEngine).Wait();
Client.V1.Secrets.Transit.CreateEncryptionKeyAsync(keyName, new CreateKeyRequestOptions()
{
Exportable = true
}, path).Wait();
EncryptRequestOptions encryptOptions = new EncryptRequestOptions
{
Base64EncodedPlainText = Convert.ToBase64String(data),
ConvergentEncryption = true,
};
Secret<EncryptionResponse> encryptionResponse = Client.V1.Secrets.Transit.EncryptAsync(keyName,
encryptOptions, path).Result;
string cipherText = encryptionResponse.Data.CipherText;
return Encoding.Unicode.GetBytes(cipherText);
}
Code for Decryption:
public byte[] DecryptData(string ciphertext, string keyName)
{
DecryptRequestOptions decryptOptions = new DecryptRequestOptions
{
CipherText = ciphertext,
};
Secret<DecryptionResponse> decryptionResponse = Client.V1.Secrets.Transit.DecryptAsync(keyName,
decryptOptions, path).Result;
return Convert.FromBase64String(decryptionResponse.Data.Base64EncodedPlainText);
}
Here is my Code Trial for signing:
public byte[] Sign(byte[] plaintextBytes, string keyName)
{
byte[] hash = ComputeHash(plaintextBytes,SHA256.Create());
GCKMS.SignatureOptions options = new GCKMS.SignatureOptions()
{
Digest = Convert.ToBase64String(hash),
};
Secret<GCKMS.SignatureResponse> result = Client.V1.Secrets.GoogleCloudKMS.SignAsync(keyName,
options).Result;
return Encoding.Unicode.GetBytes(result.Data.Signature);
}
The Error is:
VaultSharp.Core.VaultApiException: {"errors":["no handler for route
'gcpkms/sign/Manuel'"]}
Last but not least my Code for validating the signature:
public bool ValidateSignature(byte[] plaintextByte, byte[] signature, string keyName)
{
GCKMS.VerificationOptions option = new GCKMS.VerificationOptions
{
Digest = Encoding.Unicode.GetString(ComputeHash(plaintextByte)),
Signature = Encoding.Unicode.GetString(signature)
};
Secret<GCKMS.VerificationResponse> result =
Client.V1.Secrets.GoogleCloudKMS.VerifyAsync(keyName, option).Result;
return result.Data.Valid;
}
I am not sure but this could be because I don't use a SecretsEngine with a Path. I could not find any SecretsEngine for GoogleCloudKms.
Useful information:
I generate the Path with Guid.NewGuid().ToString();.
ComputeHash is a self written Function that computes the Hash with a give Algorithm. The
default algorithm is SHA256.
GCMS is a short version of the Namespace VaultSharp.V1.SecretsEngines.GoogleCloudKMS
Any ideas and suggestions are very welcome.
Thanks in advance!
Although Vault offers convenient signature with Transit, the C# wrapper you are using does not support it.
Google KMS does offer signature, but its interface is more complex: you have to do the hash yourself and keep track of the key versions.
What I suggest is that you play a trick on your API wrapper:
Leave your encryption and decryption code as-is
Write to the the Transit backend as if it was a KV store version 1
Get your signature by sending your payload as the input parameter
You still have to base64 your data before sending it to Vault, to avoid binary encoding issues.
So assuming that:
You want to sign the text StackOverflow
The transit back-end is mounted under transit
Your signature key is named my-key
This should get you started:
var value = new Dictionary<string, object> { "input", Convert.ToBase64String(Encoding.UTF8.GetBytes("StackOverflow")) } };
var writtenValue = await vaultClient.V1.Secrets.KeyValue.V1.WriteSecretAsync("sign/my-key", value, "transit");
I have spent several hours trying to have a valid Encrypt/Decrypt function that uses Asymmetric encryption, and the best option seems to be RSA.
But The thing is that I want to be able to provide my own public/private key to the function as a string myself in the form : "769de1f1a9dd6e114f81b9490ea42a2967840353edd358a35c84e2c831dd40a2"
something very very similar to the 'eth-crypto' npm library for javascript.
but I haven't found any article or documentation that explains that.
so has anyone implemented it before or have an article that explains it.
keep in mind that when using asp.net core the FromXmlString doesn't work even when using 3.0
Here is my Encrypt function so far :
public static string EncryptAsymmetric(string textToEncrypt, string publicKeyString)
{
var bytesToEncrypt = Encoding.UTF8.GetBytes(textToEncrypt);
using (var rsa = new RSACryptoServiceProvider(2048))
{
try
{
rsa.FromXmlString(publicKeyString);
var encryptedData = rsa.Encrypt(bytesToEncrypt, true);
var base64Encrypted = Convert.ToBase64String(encryptedData);
return base64Encrypted;
}
finally
{
rsa.PersistKeyInCsp = false;
}
}
}
but it throws an error on the FromXmlString ( operation not supported on this platform )
so if there is any other way maybe......
any help is very appreciated because I have been looking into it for many hours with no result.
I'm trying to hit the Coinspot REST API, but I'm getting an error returned. I'm having no trouble talking to Bittrex and Independent Reserve, but Coinspot is a bit different. This is my code:
protected override RESTClient RESTClient { get; } = new RESTClient(new NewtonsoftSerializationAdapter(), new Uri("https://www.coinspot.com.au/api"));
public class postdata
{
public string nonce { get; set; }
}
public string CalculateMD5Hash(string input)
{
//step 1, calculate MD5 hash from input
MD5 md5 = MD5.Create();
var inputBytes = Encoding.ASCII.GetBytes(input);
var hash = md5.ComputeHash(inputBytes);
// step 2, convert byte array to hex string
var sb = new StringBuilder();
for (int i = 0; i < hash.Length; i++)
{
sb.Append(hash[i].ToString("X2"));
}
return sb.ToString();
}
/// <summary>
/// Private IR Call: GetAccounts
/// </summary>
/// <returns></returns>
private async Task<List<AccountHolding>> Balances()
{
//https://github.com/geekpete/py-coinspot-api/blob/master/coinspot/coinspot.py
//var nonce = new Date().getTime();
//var postdata = postdata || { };
//postdata.nonce = nonce;
//var stringmessage = JSON.stringify(postdata);
//var signedMessage = new hmac("sha512", self.secret);
//signedMessage.update(stringmessage);
// 'sign': sign,
//'key': self.key
var nonce = APIHelpers.GetNonce();
var postdata = new postdata { nonce = nonce };
var json = JsonConvert.SerializeObject(postdata);
System.Diagnostics.Debug.WriteLine(json);
var sign = APIHelpers.GetHMACSHAHash(ApiSecret, json, APIHelpers.HMACSHAType.NineBit);
//Do we do this?
//The JavaScript samples seem to hash with MD5 afterwards for double encryption?
sign = CalculateMD5Hash(sign);
RESTClient.Headers.Clear();
RESTClient.Headers.Add("sign", sign);
RESTClient.Headers.Add("key", ApiKey);
try
{
var retVal = await RESTClient.PostAsync<string, postdata>(postdata, "/my/balances");
System.Diagnostics.Debug.WriteLine(retVal);
}
catch (Exception ex)
{
}
throw new NotImplementedException();
}
The doco is very scant! I'm stuck.
https://www.coinspot.com.au/api
I don't have the error handy right now, but it was a completely non-descript error with information about what went wrong. It was something like "invalid call". But, I know that it is accepted my posted data to some extent, because if I change the name of the property "nonce" to "noncey", I get a meaningful error back that says "no nonce".
Did you ever manage to get this API working. CoinSpot are not very supportive of this. I can only get 3 of the coins API working which isn't much help
I managed to get it working recently and put together a simple SDK in .NET
https://github.com/QuintinHumphreys/CoinspotAPI
tl:dr It's undocumented but you need to use port 443, I found it by digging through their node SDK.
I was having the same issue, getting the very non-descriptive {status: invalid} response, in my case using Elixir not C#. I got it to work by peeking into their node SDK - my details worked using their SDK so I knew it had to be something I wasn't doing properly (although their documentation is pretty shocking). They use port 443 and as soon as I set that it worked.
I tried 2 things, I'm 90% sure it was the port number but half way through my getting it to work I printed the sha512 sign created by their node sdk and compared it to the one I generating using Cryptex I saw that they were generating the same sha512 signature, but my one was in capital letters while the node one was in lowercase - this may or may not end up mattering but I did use String.downcase() on mine in the end.
I'm working with the Google DoubleClick ad exchange API. Their examples are in C++ and well, I'm pretty awful at C++. I'm trying to convert this to C# for something I'm working on and really, I think I just need some explanation of what is actually happening in certain blocks of this code sample. Honestly I kind of know what should happen over all but I'm not sure I am getting it 'right' and with encryption/decryption there isn't a 'sort of right'.
This is the full example from their API site:
bool DecryptByteArray(
const string& ciphertext, const string& encryption_key,
const string& integrity_key, string* cleartext) {
// Step 1. find the length of initialization vector and clear text.
const int cleartext_length =
ciphertext.size() - kInitializationVectorSize - kSignatureSize;
if (cleartext_length < 0) {
// The length can't be correct.
return false;
}
string iv(ciphertext, 0, kInitializationVectorSize);
// Step 2. recover clear text
cleartext->resize(cleartext_length, '\0');
const char* ciphertext_begin = string_as_array(ciphertext) + iv.size();
const char* const ciphertext_end = ciphertext_begin + cleartext->size();
string::iterator cleartext_begin = cleartext->begin();
bool add_iv_counter_byte = true;
while (ciphertext_begin < ciphertext_end) {
uint32 pad_size = kHashOutputSize;
uchar encryption_pad[kHashOutputSize];
if (!HMAC(EVP_sha1(), string_as_array(encryption_key),
encryption_key.length(), (uchar*)string_as_array(iv),
iv.size(), encryption_pad, &pad_size)) {
printf("Error: encryption HMAC failed.\n");
return false;
}
for (int i = 0;
i < kBlockSize && ciphertext_begin < ciphertext_end;
++i, ++cleartext_begin, ++ciphertext_begin) {
*cleartext_begin = *ciphertext_begin ^ encryption_pad[i];
}
if (!add_iv_counter_byte) {
char& last_byte = *iv.rbegin();
++last_byte;
if (last_byte == '\0') {
add_iv_counter_byte = true;
}
}
if (add_iv_counter_byte) {
add_iv_counter_byte = false;
iv.push_back('\0');
}
}
Step 1 is quite obvious. This block is what I am really not sure how to interpret:
if (!HMAC(EVP_sha1(), string_as_array(encryption_key),
encryption_key.length(), (uchar*)string_as_array(iv),
iv.size(), encryption_pad, &pad_size)) {
printf("Error: encryption HMAC failed.\n");
return false;
}
What exactly is happening in that if body? What would that look like in C#? There are a lot of parameters that do SOMETHING but it seems like an awful lot crammed in a small spot. Is there some stdlib HMAC class? If I knew more about that I might better understand what's happening.
The equivalent C# code for that block is:
using (var hmac = new HMACSHA1(encryption_key))
{
var encryption_pad = hmac.ComputeHash(iv);
}
It's computing the SHA1 HMAC of the initialization vector (IV), using the given encryption key.
The HMAC function is actually a macro from OpenSSL.
Just as a comment, I think it would be easier to implement this from their pseudocode description rather than from their C++ code.
I have devices with unique serial number (string incremetation) ex : AS1002 and AS1003.
I need to figure out an algorithm to produce a unique activation key for each serial number.
What would be the best approach for this ?
Thanks !
(This has to be done offline)
You have two things to consider here:
- Whatever key you generate must be able to be entered easily, so this eliminates some weird hash which may produce characters which will be cumbersome to type, although this can be overcome, it’s something you should consider.
- The operation as you stated must be done online
Firstly, there will be no way to say with absolute certainty that someone will not be able to decipher your key generation routine, no matter how much you attempt to obfuscate. Just do a search engine query for “Crack for Xyz software”.
This has been a long battle that will never end, hence the move to deliver software as services, i.e. online where the producer has more control over their content and can explicitly authorize and authenticate a user. In your case you want to do this offline. So in your scenario someone will attach your device to some system, and the accompanying software that you intend to write this routine on will make a check against the serial number of the device v/s user input.
Based on #sll’s answer, given the offline nature of your request. Your best, unfortunately would be to generate a set of random codes, and validate them when user’s call in.
Here is a method borrowed from another SO answer, I've added digits as well
private readonly Random _rng = new Random();
private const string _chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ123456789"; //Added 1-9
private string RandomString(int size)
{
char[] buffer = new char[size];
for (int i = 0; i < size; i++)
{
buffer[i] = _chars[_rng.Next(_chars.Length)];
}
return new string(buffer);
}
So, generating one for each of your devices and storing them somewhere might be your only option because of the offline considerations.
This routine will produce strings like this when set to create a 10 digit string, which is reasonably random.
3477KXFBDQ
ROT6GRA39O
40HTLJPFCL
5M2F44M5CH
CAVAO780NR
8XBQ44WNUA
IA02WEWOCM
EG11L4OGFO
LP2UOGKKLA
H0JB0BA4NJ
KT8AN18KFA
Activation Key
Here is a simple structure of the activation key:
Part
Description
Data
A part of the key encrypted with a password. Contains the key expiration date and application options.
Hash
Checksum of the key expiration date, password, options and environment parameters.
Tail
The initialization vector that used to decode the data (so-called "salt").
class ActivationKey
{
public byte[] Data { get; set; } // Encrypted part.
public byte[] Hash { get; set; } // Hashed part.
public byte[] Tail { get; set; } // Initialization vector.
}
The key could represent as text format: DATA-HASH-TAIL.
For example:
KCATBZ14Y-VGDM2ZQ-ATSVYMI.
The folowing tool will use cryptographic transformations to generate and verify the key.
Generating
The algorithm for obtaining a unique activation key for a data set consists of several steps:
data collection,
getting the hash and data encryption,
converting activation key to string.
Data collection
At this step, you need to get an array of data such as serial number, device ID, expiration date, etc. This purpose can be achieved using the following
method:
unsafe byte[] Serialize(params object[] objects)
{
using (MemoryStream memory = new MemoryStream())
using (BinaryWriter writer = new BinaryWriter(memory))
{
foreach (object obj in objects)
{
if (obj == null) continue;
switch (obj)
{
case string str:
if (str.Length > 0)
writer.Write(str.ToCharArray());
continue;
case DateTime date:
writer.Write(date.Ticks);
continue;
case bool #bool:
writer.Write(#bool);
continue;
case short #short:
writer.Write(#short);
continue;
case ushort #ushort:
writer.Write(#ushort);
continue;
case int #int:
writer.Write(#int);
continue;
case uint #uint:
writer.Write(#uint);
continue;
case long #long:
writer.Write(#long);
continue;
case ulong #ulong:
writer.Write(#ulong);
continue;
case float #float:
writer.Write(#float);
continue;
case double #double:
writer.Write(#double);
continue;
case decimal #decimal:
writer.Write(#decimal);
continue;
case byte[] buffer:
if (buffer.Length > 0)
writer.Write(buffer);
continue;
case Array array:
if (array.Length > 0)
foreach (var a in array) writer.Write(Serialize(a));
continue;
case IConvertible conv:
writer.Write(conv.ToString(CultureInfo.InvariantCulture));
continue;
case IFormattable frm:
writer.Write(frm.ToString(null, CultureInfo.InvariantCulture));
continue;
case Stream stream:
stream.CopyTo(stream);
continue;
default:
try
{
int rawsize = Marshal.SizeOf(obj);
byte[] rawdata = new byte[rawsize];
GCHandle handle = GCHandle.Alloc(rawdata, GCHandleType.Pinned);
Marshal.StructureToPtr(obj, handle.AddrOfPinnedObject(), false);
writer.Write(rawdata);
handle.Free();
}
catch(Exception e)
{
// Place debugging tools here.
}
continue;
}
}
writer.Flush();
byte[] bytes = memory.ToArray();
return bytes;
}
}
Getting the hash and data encryption
This step contains the following substeps:
create an encryption engine using a password and stores the initialization vector in the Tail property.
next step, expiration date and options are encrypted and the encrypted data is saved into the Data property.
finally, the hashing engine calculates a hash based on the expiration date, password, options and environment and puts it in the Hash property.
ActivationKey Create<TAlg, THash>(DateTime expirationDate,
object password,
object options = null,
params object[] environment)
where TAlg : SymmetricAlgorithm
where THash : HashAlgorithm
{
ActivationKey activationKey = new ActivationKey();
using (SymmetricAlgorithm cryptoAlg = Activator.CreateInstance<TAlg>())
{
if (password == null)
{
password = new byte[0];
}
activationKey.Tail = cryptoAlg.IV;
using (DeriveBytes deriveBytes =
new PasswordDeriveBytes(Serialize(password), activationKey.Tail))
{
cryptoAlg.Key = deriveBytes.GetBytes(cryptoAlg.KeySize / 8);
}
expirationDate = expirationDate.Date;
long expirationDateStamp = expirationDate.ToBinary();
using (ICryptoTransform transform = cryptoAlg.CreateEncryptor())
{
byte[] data = Serialize(expirationDateStamp, options);
activationKey.Data = transform.TransformFinalBlock(data, 0, data.Length);
}
using (HashAlgorithm hashAlg = Activator.CreateInstance<THash>())
{
byte[] data = Serialize(expirationDateStamp,
cryptoAlg.Key,
options,
environment,
activationKey.Tail);
activationKey.Hash = hashAlg.ComputeHash(data);
}
}
return activationKey;
}
Converting to string
Use the ToString method to get a string containing the key text, ready to be transfering to the end user.
N-based encoding (where N is the base of the number system) was often used to convert binary data into a human-readable text. The most commonly used in
activation key is base32. The advantage of this encoding is a large alphabet consisting of numbers and letters that case insensitive. The downside is that this encoding is not implemented in the .NET standard library and you should implement it yourself. You can also use the hex encoding and base64 built into mscorlib. In my example base32 is used, but I will not give its source code here. There are many examples of base32 implementation on this site.
string ToString(ActivationKey activationKey)
{
if (activationKey.Data == null
|| activationKey.Hash == null
|| activationKey.Tail == null)
{
return string.Empty;
}
using (Base32 base32 = new Base32())
{
return base32.Encode(activationKey.Data)
+ "-" + base32.Encode(activationKey.Hash)
+ "-" + base32.Encode(activationKey.Tail);
}
}
To restore use the folowing method:
ActivationKey Parse(string text)
{
ActivationKey activationKey;
string[] items = text.Split('-');
if (items.Length >= 3)
{
using (Base32 base32 = new Base32())
{
activationKey.Data = base32.Decode(items[0]);
activationKey.Hash = base32.Decode(items[1]);
activationKey.Tail = base32.Decode(items[2]);
}
}
return activationKey;
}
Checking
Key verification is carried out using methodes GetOptions an Verify.
GetOptions checks the key and restores embeded data as byte array or null if key is not valid.
Verify just checks the key.
byte[] GetOptions<TAlg, THash>(object password = null, params object[] environment)
where TAlg : SymmetricAlgorithm
where THash : HashAlgorithm
{
if (Data == null || Hash == null || Tail == null)
{
return null;
}
try
{
using (SymmetricAlgorithm cryptoAlg = Activator.CreateInstance<TAlg>())
{
cryptoAlg.IV = Tail;
using (DeriveBytes deriveBytes =
new PasswordDeriveBytes(Serialize(password), Tail))
{
cryptoAlg.Key = deriveBytes.GetBytes(cryptoAlg.KeySize / 8);
}
using (ICryptoTransform transform = cryptoAlg.CreateDecryptor())
{
byte[] data = transform.TransformFinalBlock(Data, 0, Data.Length);
int optionsLength = data.Length - 8;
if (optionsLength < 0)
{
return null;
}
byte[] options;
if (optionsLength > 0)
{
options = new byte[optionsLength];
Buffer.BlockCopy(data, 8, options, 0, optionsLength);
}
else
{
options = new byte[0];
}
long expirationDateStamp = BitConverter.ToInt64(data, 0);
DateTime expirationDate = DateTime.FromBinary(expirationDateStamp);
if (expirationDate < DateTime.Today)
{
return null;
}
using (HashAlgorithm hashAlg =
Activator.CreateInstance<THash>())
{
byte[] hash =
hashAlg.ComputeHash(
Serialize(expirationDateStamp,
cryptoAlg.Key,
options,
environment,
Tail));
return ByteArrayEquals(Hash, hash) ? options : null;
}
}
}
}
catch
{
return null;
}
}
bool Verify<TAlg, THash>(object password = null, params object[] environment)
where TAlg : SymmetricAlgorithm
where THash : HashAlgorithm
{
try
{
byte[] key = Serialize(password);
return Verify<TAlg, THash>(key, environment);
}
catch
{
return false;
}
}
Example
Here is a full example of generating the activation key using your own combination of any amount of data - text, strings, numbers, bytes, etc.
Example of usage:
string serialNumber = "0123456789"; // The serial number.
const string appName = "myAppName"; // The application name.
// Generating the key. All the parameters passed to the costructor can be omitted.
ActivationKey activationKey = new ActivationKey(
//expirationDate:
DateTime.Now.AddMonths(1), // Expiration date 1 month later.
// Pass DateTime.Max for unlimited use.
//password:
null, // Password protection;
// this parameter can be null.
//options:
null // Pass here numbers, flags, text or other
// that you want to restore
// or null if no necessary.
//environment:
appName, serialNumber // Application name and serial number.
);
// Thus, a simple check of the key for validity is carried out.
bool checkKey = activationKey.Verify((byte[])null, appName, serialNumber);
if (!checkKey)
{
MessageBox.Show("Your copy is not activated! Please get a valid activation key.");
Application.Exit();
}
By far the most secure way to do it is to have a centralized database of (serial number, activation key) pairs and have the user activate over the internet so you can check the key locally (on the server).
In this implementation, the activation key can be completely random since it doesn't need to depend on the serial number.
You want it to be easy to check, and hard to "go backwards". You'll see a lot of suggestions for using hashing functions, those functions are easy to go one way, but hard to go backwards. Previously, I phrased that as "it is easy to turn a cow into a hamburger, but hard to turn a hamburger into a cow". In this case, a device should know its own serial number and be able to "add" (or append) some secret (usually called "salt") to the serial and then hash or encrypt it.
If you are using reversible encryption, you want to add some sort of "check digit" to the serial numbers so that if someone does figure your encryption scheme out, there is another layer for them to figure out.
An example of a function that is easy enough to "go backwards" was one I solved with Excel while trying to avoid homework.
And you probably want to make things easier for your customers by making the encoding less likely to be messed up when the activation codes are handwritten (such as you write it down from the email then walk over to where the device is and punch the letters/digits in). In many fonts, I and 1, and 0 and O are similar enough that many encodings, such as your car's VIN do not use the letters i and o (and I remember older typewriters that lacked a key for the digit 1 because you were expected to use lowercase L). In such cases, Y, 4 and 7 can appear the same depending on some handwriting. So know your audience and what are their limits.
If your device has some secured memory which can not be read by connecting an programmator or an other device -you can store some key-code and then use any hashing algorithm like MD5 or SHA-1/2 to generate hash by:
HASH(PUBLIC_SERIALNUMBER + PRIVATE_KEYCODE)
And pairs of SERIALNUMBER + KEYCODE should be stored in local DB.
In this way: (offline)
Client calling you and asking for the Activation Code
You asking for a SERIALNUMBER of particular device
Then you search for a KEYCODE by a given SERIALNUMBER in your local DB and generate Activation Code (even using MD5 this will be sacure as long KEYCODE is privately stored in your DB)
Client enter Activation Code into the device, device able to generate hash
by own SERIALNUMBER and KEYCODE and then compare to Activation Code entered by user
This could be simplified by storing activation code itself if device has a secured memory onboard (like SmartCards has). In this way you can just keep own database of SerialCode - ActivationCode pairs.
How about: Invent a password that is not revealed to the user. Then concatenate this password with the serial number and hash the combination.
Anything you do can be broken by a dedicated enough hacker. The question is not, "Can I create absolutely unbreakable security?" but "Can I create security good enough to protect against unskilled hackers and to make it not worth the effort for the skilled hackers?" If you reasonably expect to sell 10 million copies of your product, you'll be a big target and there may be lots of hackers out there who will try to break it. If you expect to sell a few hundred or maybe a few thousand copies, not so much.