步步为盈
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1368 lines
56 KiB

using Jd.ACES.Common;
using Jd.ACES.Common.Exceptions;
using Jd.ACES.Task;
using Jd.ACES.Utils;
using Jd.Api;
using System;
using System.Collections.Generic;
using System.IO;
using System.Security.Cryptography;
using System.Text;
using static Jd.ACES.Common.Constants;
using static Jd.ACES.Common.TDEStatus;
namespace Jd.ACES
{
public class TDEClient
{
//private static ILog LOGGER = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
// Key - base64 token string; Value - TDEClient instance
public static Dictionary<string, TDEClient> clientPool;
// used in JMQ reporting
private static string version = "1.0.0";
// MKeys in-memory cache
private CacheKeyStore cache_ks;
// token holder
private Token t;
// Monitor Client based on Https
private MonitorClient reporter = null;
// Pushing epoch, default is 1 hours, unit is second
private static long mqEpoch = 3600L;
// KM Client
private KMClient kmc = null;
// refresh epoch, default is 8 hours, unit is second
private static long kmEpoch = 28800L;
//Íø¹ØÏµÍ³²ÎÊý
public JosSystemParam josSystemParam;
// v2.0.x CipherResult
private static readonly CipherResult malformedCipher = new CipherResult(CipherStatus.Malformed, null, false);
// prepare Scheduler for KM client
private static ScheduledExecutor kmScheduler;
// prepare Scheduler for MQ client
private static ScheduledExecutor mqScheduler;
// mutex to prevent race condition in getInstance()
private static Object mutex = new Object();
// statistic record present in long array
// index: enccnt(0) deccnt(1) encerrcnt(2) decerrcnt(3)
// signcnt(4) verifycnt(5) signerrcnt(6) verifyerrcnt(7)
public enum StatisticType
{
ENCCNT = 0,
DECCNT,
ENCERRCNT,
DECERRCNT,
SIGNCNT,
VERIFYCNT,
SIGNERRCNT,
VERIFYERRCNT
}
private long[] statistic = null;
private static readonly byte[] SALT = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
private static readonly byte[] KEYWORDSALT = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F};
static TDEClient()
{
if (clientPool == null)
clientPool = new Dictionary<string, TDEClient>();
if (kmScheduler == null)
kmScheduler = new ScheduledExecutor(kmEpoch, FlushKeyCacheTask.Task);
if (mqScheduler == null)
mqScheduler = new ScheduledExecutor(mqEpoch, SendReportCacheTask.Task);
version = version + "-dotnet";
}
/// <summary>
/// Initializes a new instance of TDEClient with specified token, KMS endpoint and platform mode.
/// </summary>
/// <param name="serverURL">Íø¹ØÓòÃûµØÖ·.</param>
/// <param name="appKey">¿ØÖÆÌ¨Ó¦ÓÃappkey</param>
/// <param name="appSecret">¿ØÖÆÌ¨Ó¦ÓÃappSecret</param>
/// <param name="accessToken">Íø¹ØÊÚȨtoken</param>
/// <returns>The corresponding instance of TDEClient.</returns>
/// <exception cref="InvalidTokenException"/>
/// <exception cref="MalformedException"/>
/// <exception cref="ServiceErrorException"/>
/// <exception cref="CorruptKeyException"/>
/// <exception cref="NoValidKeyException"/>
/// <exception cref="ArgumentException"/>
public TDEClient(string serverURL, string appKey, string appSecret, string accessToken)
{
////LOGGER.Debug("Create client with a given new token string.");
josSystemParam = new JosSystemParam();
josSystemParam.accessToken = accessToken;
josSystemParam.appKey = appKey;
josSystemParam.appSecret = appSecret;
josSystemParam.serverURL = serverURL;
InitClient(josSystemParam);
}
/// <summary>
/// Initializes the instance of TDEClient with specified token, KMS endpoint and platform mode.
/// </summary>
/// <param name="josSystemParam">APIÍø¹ØÏµÍ³²ÎÊý.</param>
/// <exception cref="InvalidTokenException"/>
/// <exception cref="MalformedException"/>
/// <exception cref="ServiceErrorException"/>
/// <exception cref="CorruptKeyException"/>
/// <exception cref="NoValidKeyException"/>
/// <exception cref="ArgumentException"/>
private void InitClient(JosSystemParam josSystemParam)
{
try
{
// step 1: Build the HTTPS listener to the client
reporter = new MonitorClient(this);
// step 2: request voucher info
string tokenStr = RequestVoucher(josSystemParam);
// step 3: load single token
// one token can access only one app
// throws NoSuchAlgorithmException, InvalidKeyException, SignatureException,
// InvalidTokenException, MalformedException
t = Token.ParseFromString(tokenStr);
////LOGGER.InfoFormat("Token ID: {0}, origins from {1}", t.GetId(), t.GetTokenOrigin());
// step 4: prepare MKey cache and corrupt key list
cache_ks = new CacheKeyStore();
// step 5: prepare KM client (separate thread) with a given epoch
// setup necessary parameters like epoch, key cache file and backup file locations,
// token and version number
kmc = new KMClient(reporter, cache_ks, t, version,josSystemParam);
// step 6: adjust settings
reporter.SetProductionEnv();
// step 7: allocate some statistic structure
statistic = new long[Enum.GetNames(typeof(StatisticType)).Length];
// step 8: connect KMS for flash keys
kmc.FetchMKeys();
// FetchMKeys are blocking call, check key chain is ready for bootup
// if not keys here, throw exception here..
if (!kmc.IsKeyChainReady())
{
throw new SystemException(SDK_HAS_NO_AVAILABLE_KEYS.GetMessage());
}
}
catch (InvalidTokenException e)
{
////LOGGER.Fatal(e.Message);
// MQ client reports this severe exception
reporter.InsertErrReport(
SDK_USE_INVALID_TOKEN.GetValue(),
e.Message,
e.StackTrace,
MsgLevel.SEVERE);
throw new InvalidTokenException(e.Message);
}
catch (MalformedException e)
{
////LOGGER.Fatal(e.Message); ;
// MQ client reports this severe exception
reporter.InsertErrReport(
SDK_THROW_JDK_EXCEPTION.GetValue(),
e.Message,
e.StackTrace,
MsgLevel.ERROR);
throw new MalformedException(e.Message);
}
catch (ServiceErrorException e)
{
throw new ServiceErrorException(e.Message);
}
catch (NoValidKeyException e)
{
throw new NoValidKeyException(e.Message);
}
catch (Exception e)
{
////LOGGER.Fatal(e.Message); ;
// catch all exceptions and throw wrapped exception as SystemException
reporter.InsertErrReport(
SDK_INTERNAL_ERROR.GetValue(),
e.Message,
e.StackTrace,
MsgLevel.ERROR);
throw new SystemException(e.Message);
}
}
/// <summary>
/// Attempts to get a singleton instance of TDEClient class.
/// </summary>
/// <param name="serverURL">Íø¹ØÓòÃûµØÖ·.</param>
/// <param name="appKey">¿ØÖÆÌ¨Ó¦ÓÃappkey</param>
/// <param name="appSecret">¿ØÖÆÌ¨Ó¦ÓÃappSecret</param>
/// <param name="accessToken">Íø¹ØÊÚȨtoken</param>
/// The default is production mode.</param>
/// <returns>TDEClient instance if the token was available and the keys
/// for encryption were fetched successfully.</returns>
/// <exception cref="InvalidTokenException"/>
/// <exception cref="MalformedException"/>
/// <exception cref="ServiceErrorException"/>
/// <exception cref="CorruptKeyException"/>
/// <exception cref="NoValidKeyException"/>
/// <exception cref="ArgumentException"/>
public static TDEClient GetInstance(string serverURL, string appKey, string appSecret, string accessToken)
{
if (!clientPool.ContainsKey(accessToken))
{
lock (mutex)
{
if (!clientPool.ContainsKey(accessToken))
{
// cannot find it in the pool, create one for sure
clientPool.Add(accessToken, new TDEClient( serverURL,appKey,appSecret,accessToken));
}
}
}
return clientPool[accessToken];
}
public static TDEClient GetInstance(DefaultJdClient jdClient){
return GetInstance(jdClient.serverUrl, jdClient.appKey, jdClient.appSecret, jdClient.accessToken);
}
/// <summary>
/// Attempts to calculate index with given plaintext and salt value.
/// </summary>
/// <param name="pt">The given plaintext to calculate.</param>
/// <param name="salt">The given salt value.</param>
/// <returns>byte array of calculated index</returns>
/// <exception cref="ArgumentException"/>
/// <exception cref="InsufficientSaltLengthException"/>
public static byte[] CalculateIndex(byte[] pt, byte[] salt)
{
return IndexCalculator.Sha256Index(pt, salt);
}
/// <summary>
/// Attempts to calculate index with given plaintext and salt value.
/// </summary>
/// <param name="pt">The given plaintext to calculate.</param>
/// <param name="salt">The given salt value.</param>
/// <returns>base64 encoding string of calculated index</returns>
/// <exception cref="ArgumentNullException"/>
/// <exception cref="InsufficientSaltLengthException"/>
public static string CalculateStringIndex(byte[] pt, byte[] salt)
{
return Convert.ToBase64String(CalculateIndex(pt, salt));
}
/// <summary>
/// Attempts to calculate index with given plaintext and special salt value derived from master key.
/// </summary>
/// <param name="pt">The given plaintext to calculate.</param>
/// <returns>byte array of calculated index</returns>
/// <exception cref="NoValidKeyException"/>
/// <exception cref="IndexCalculateException"/>
public byte[] CalculateIndex(byte[] pt)
{
MKey k0 = this.GetEncKey(0, true);
byte[] index = null;
try
{
byte[] compute_salt = KeyEncryption.Wrap(k0, SALT);
index = IndexCalculator.Sha256Index(pt, compute_salt);
}
catch (Exception e)
{
// rewrap the exception
throw new IndexCalculateException(e.GetType().Name + ":" + e.Message);
}
return index;
}
/// <summary>
/// Attempts to calculate index with given plaintext and specified salt value derived from master key.
/// </summary>
/// <param name="pt">The given plaintext to calculate.</param>
/// <returns>base64 encoding string of calculated index</returns>
/// <exception cref="NoValidKeyException"/>
/// <exception cref="IndexCalculateException"/>
public string CalculateStringIndex(byte[] pt)
{
return Convert.ToBase64String(CalculateIndex(pt));
}
/// <summary>
/// Checks if the ciphertext can be decrypted.
/// </summary>
/// <param name="ct">The byte array of ciphertext to check.</param>
/// <returns></returns>
public static bool isEncryptData(byte[] ct)
{
try
{
byte ctype = ct[0];
// for weak
bool flag = false;
if (ctype == (byte)CipherType.LARGE || ctype == (byte)CipherType.REGULAR)
{
flag = true;
}
else if (ctype != (byte)CipherType.WEAK)
{
return false;
}
byte[] mkIdx = ExtractKeyId(ct, flag);
if (mkIdx == null && mkIdx.Length<=0)
return false;
else
return true;
}
catch (Exception)
{
// format error or other error
return false;
}
}
/**
* ÅжÏ×Ö·û´®ÊÇ·ñΪÃÜÎÄ
*/
public static bool isEncryptData(String base64ct)
{
try
{
return isEncryptData(Convert.FromBase64String(base64ct));
}
catch (Exception)
{
return false;
}
}
public CipherResult GetCipherResult(byte[] ct)
{
try
{
byte ctype = ct[0];
// for weak
bool flag = false;
if (ctype == (byte)CipherType.LARGE || ctype == (byte)CipherType.REGULAR)
{
flag = true;
}
else if (ctype != (byte)CipherType.WEAK)
{
return malformedCipher;
}
byte[] mkIdx = ExtractKeyId(ct, flag);
if (mkIdx == null)
return malformedCipher;
if (cache_ks.SearchDecKey(mkIdx) != null)
return new CipherResult(CipherStatus.Decryptable, mkIdx, flag);
else if (cache_ks.HasFutureKeyID(mkIdx))
return new CipherResult(CipherStatus.Feasible, mkIdx, flag);
else
return new CipherResult(CipherStatus.UnDecryptable, mkIdx, flag);
}
catch (Exception)
{
// format error or other error
return malformedCipher;
}
}
public CipherResult GetCipherResult(string base64ct)
{
CipherResult ret;
try
{
ret = GetCipherResult(Convert.FromBase64String(base64ct));
}
catch (Exception)
{
return malformedCipher;
}
// return Malformed by default
return ret;
}
/// <summary>
/// Attemps to encrypt the specified plaintext with regular version.
/// </summary>
/// <param name="pt">The byte array of plaintext to encrypt.</param>
/// <returns>The byte array of encrypted data.</returns>
/// <exception cref="InvalidTokenException"/>
/// <exception cref="NoValidKeyException"/>
/// <exception cref="InvalidKeyException"/>
/// <exception cref="InvalidKeyPermission"/>
/// <exception cref="ArgumentNullException"/>
public byte[] Encrypt(byte[] pt)
{
ValidateToken();
// fetch corresponding master key and check its status
MKey k = this.GetEncKey(this.kmc.GetMajorKeyVersion());
//LOGGER.DebugFormat("Weak encrypt with key version: {0}", k.GetVersion());
byte[] ct;
try
{
ct = k.Encrypt(pt);
// not catch all exceptions, we only increase error count for
// possible cases
statistic[StatisticType.ENCCNT.GetValue()]++;
}
catch (ArgumentNullException e)
{
// null input
statistic[StatisticType.ENCERRCNT.GetValue()]++;
throw e;
}
catch (Exception e)
{
// master key has some issue
statistic[StatisticType.ENCERRCNT.GetValue()]++;
throw new SystemException(e.GetType().Name + ":" + e.Message);
}
return ct;
}
/// <summary>
/// Attemps to decrypt the specified ciphertext and automatically recognition encryption version is regular or strong.
/// </summary>
/// <param name="ct">The byte array of ciphertext.</param>
/// <returns>The byte array of decrypted data.</returns>
/// <exception cref="InvalidTokenException"/>
/// <exception cref="NoValidKeyException"/>
/// <exception cref="InvalidKeyException"/>
/// <exception cref="InvalidKeyPermission"/>
/// <exception cref="MalformedException"/>
/// <exception cref="CorruptKeyException"/>
/// <exception cref="ArgumentNullException"/>
/// <exception cref="ArgumentException"/>
/// <exception cref="ServiceErrorException"/>
public byte[] Decrypt(byte[] ct)
{
ValidateToken();
MKey k = this.GetDecKey(ct);
//LOGGER.DebugFormat("Decrypt with key version: {0}", k.GetVersion());
byte[] pt;
// decrypt and then return plaintext
try
{
pt = k.Decrypt(ct);
// increase after decryption done
statistic[StatisticType.DECCNT.GetValue()]++;
}
catch (ArgumentNullException e)
{
statistic[StatisticType.DECERRCNT.GetValue()]++;
throw e;
}
catch (MalformedException e)
{
statistic[StatisticType.DECERRCNT.GetValue()]++;
throw e;
}
catch (Exception e)
{
statistic[StatisticType.DECERRCNT.GetValue()]++;
throw new SystemException(e.GetType().Name + ":" + e.Message);
}
return pt;
}
/// <summary>
/// Attemps to encrypt a specified plaintext.
/// </summary>
/// <param name="pt">The UTF-8 encoding string of plaintext.</param>
/// <returns>The base64 encoding string of encrypted data.</returns>
/// <exception cref="InvalidTokenException"/>
/// <exception cref="NoValidKeyException"/>
/// <exception cref="InvalidKeyException"/>
/// <exception cref="InvalidKeyPermission"/>
/// <exception cref="ArgumentNullException"/>
public string EncryptString(string pt)
{
return EncryptString(pt, Encoding.UTF8);
}
/// <summary>
/// Attemps to encrypt a specified encoding string of plaintext.
/// </summary>
/// <param name="pt">The string of plaintext.</param>
/// <param name="encoding">The encoding of plaintext.</param>
/// <returns>The base64 encoding string of encrypted data.</returns>
/// <exception cref="InvalidTokenException"/>
/// <exception cref="NoValidKeyException"/>
/// <exception cref="InvalidKeyException"/>
/// <exception cref="InvalidKeyPermission"/>
/// <exception cref="ArgumentNullException"/>
public string EncryptString(string pt, Encoding encoding)
{
if (pt == null)
throw new ArgumentNullException("Input string pt is null.");
byte[] input = encoding.GetBytes(pt);
byte[] ct = Encrypt(input);
return Convert.ToBase64String(ct);
}
/// <summary>
/// Attemps to decrypt the specified base64 encoding string.
/// </summary>
/// <param name="base64ct">The base64 encoding string to decrypt.</param>
/// <returns>The UTF-8 encoding string of decrypted data.</returns>
/// <exception cref="InvalidTokenException"/>
/// <exception cref="NoValidKeyException"/>
/// <exception cref="InvalidKeyException"/>
/// <exception cref="InvalidKeyPermission"/>
/// <exception cref="MalformedException"/>
/// <exception cref="CorruptKeyException"/>
/// <exception cref="ArgumentNullException"/>
/// <exception cref="ArgumentException"/>
/// <exception cref="ServiceErrorException"/>
public string DecryptString(string base64ct)
{
return DecryptString(base64ct, Encoding.UTF8);
}
/// <summary>
/// Attemps to decrypt the specified base64 encoding string and return decrypted data with speicified encoding.
/// </summary>
/// <param name="base64ct">The base64 encoding string to decrypt.</param>
/// <param name="encoding">The encoding of decrypted data.</param>
/// <returns>The specified encoding string of decrypted data.</returns>
/// <exception cref="InvalidTokenException"/>
/// <exception cref="NoValidKeyException"/>
/// <exception cref="InvalidKeyException"/>
/// <exception cref="InvalidKeyPermission"/>
/// <exception cref="MalformedException"/>
/// <exception cref="CorruptKeyException"/>
/// <exception cref="ArgumentNullException"/>
/// <exception cref="ArgumentException"/>
/// <exception cref="ServiceErrorException"/>
public string DecryptString(string base64ct, Encoding encoding)
{
if (base64ct == null)
throw new ArgumentNullException("Input cipher string base64ct is NULL.");
byte[] decoded = Convert.FromBase64String(base64ct);
return encoding.GetString(Decrypt(decoded));
}
/// <summary>
/// Attemps to encrypt specified source file and output encrypted data into another specified file.
/// </summary>
/// <param name="source">The file carrying plaintext.</param>
/// <param name="dest">The file carrying encrypted data.</param>
/// <exception cref="InvalidTokenException"/>
/// <exception cref="NoValidKeyException"/>
/// <exception cref="InvalidKeyException"/>
/// <exception cref="InvalidKeyPermission"/>
/// <exception cref="IOException"/>
public void EncryptFile(string source, string dest)
{
// validate token
ValidateToken();
// fetch master key and check key status
MKey k = this.GetEncKey(kmc.GetMajorKeyVersion());
////LOGGER.DebugFormat("Encrypt file with key version: {0}", k.GetVersion());
try
{
using (FileStream fout = File.OpenWrite(dest), fin = File.OpenRead(source))
{
k.Encrypt(fin, fout, fin.Length);
// increase counting after everything completes
statistic[StatisticType.ENCCNT.GetValue()]++;
}
}
catch (IOException e)
{
statistic[StatisticType.ENCERRCNT.GetValue()]++;
throw e;
}
catch (Exception e)
{
statistic[StatisticType.ENCERRCNT.GetValue()]++;
////LOGGER.Fatal(e.Message);
throw new SystemException(e.GetType().Name + ":" + e.Message);
}
}
/// <summary>
/// Attemps to decrypt specified source file and output decrypted data into another specified file.
/// </summary>
/// <param name="source">The file carrying ciphertext.</param>
/// <param name="dest">The file carrying decrypted data.</param>
/// <exception cref="InvalidTokenException"/>
/// <exception cref="IOException"/>
public void DecryptFile(string source, string dest)
{
// validate token
ValidateToken();
try
{
// check cipher and handle exceptions
byte[] minheader = new byte[STRONG_HDR_LEN];
using (FileStream fout = File.OpenWrite(dest), fin = File.OpenRead(source))
{
int rdlen = fin.Read(minheader, 0, minheader.Length);
// throw earlier
if (rdlen < STRONG_HDR_LEN)
{
string message = SDK_HAS_CORRUPTED_CIPHER.GetMessage() + Convert.ToBase64String(minheader);
reporter.InsertErrReport(
SDK_HAS_CORRUPTED_CIPHER.GetValue(),
message,
EMPTYSTR,
MsgLevel.SEVERE);
throw new MalformedException(message);
}
// try to extractID and check key status
MKey k = this.GetDecKey(minheader);
////LOGGER.DebugFormat("Decrypt with key version: {0}", k.GetVersion());
fin.Position = 0;
// decrypt it
k.Decrypt(fin, fout, fin.Length);
// increase counting after everything completes
statistic[StatisticType.DECCNT.GetValue()]++;
}
}
catch (IOException e)
{
statistic[StatisticType.DECERRCNT.GetValue()]++;
throw e;
}
catch (Exception e)
{
statistic[StatisticType.DECERRCNT.GetValue()]++;
////LOGGER.Fatal(e.Message);
throw new SystemException(e.GetType().Name + ":" + e.Message);
}
}
/**
* Éú³É×Ô¶¨ÒåToken£¬Ìṩ¸ø×ÔÓÐÕ˺ÅʹÓÃ
* @param userId
* @return
*/
public static string generateCustomerToken(long userId, String appKey)
{
return Constants.UNDERLINE + userId + Constants.UNDERLINE + appKey;
}
/// <summary>
/// »ñÈ¡token¶ÔÓ¦µÄƾ֤ÐÅÏ¢
/// </summary>
/// <param name="josSystemParam"></param>
/// <returns></returns>
public static string RequestVoucher(JosSystemParam josSystemParam)
{
string voucher = null;
try
{
string result = null;
string accessToken = josSystemParam.accessToken;
JosVoucherInfoGetRequest josVoucherInfoGetRequest = new JosVoucherInfoGetRequest();
if (!string.IsNullOrEmpty(accessToken) && accessToken.StartsWith(Constants.UNDERLINE))
{
//Ìí¼Ó×ÔÓÐÕ˺ÅÂß¼­£¬CustomerUserIdΪ×ÔÓÐÕ˺Åtoken
string customerUserId = accessToken.Substring(1, accessToken.LastIndexOf(Constants.UNDERLINE) - 1);
try
{
josVoucherInfoGetRequest.customerUserId = Convert.ToInt64(customerUserId);
}catch (Exception e)
{
//LOGGER.Error("token invalid", e);
}
}
else{
josVoucherInfoGetRequest.accessToken = accessToken;
}
josVoucherInfoGetRequest.appKey = josSystemParam.appKey;
josVoucherInfoGetRequest.appSecret = josSystemParam.appSecret;
result = HttpsClientWrapper.PostJson(josSystemParam.serverURL, josVoucherInfoGetRequest.getParameters());
JosVoucherInfoGetResponse josVoucherInfoGetResponse = JsonHelper.FromJson<JosVoucherInfoGetResponse>(result);
if (!"0".Equals(josVoucherInfoGetResponse.getResult.code))
{
throw new ServiceErrorException("gw platform error ->" + josVoucherInfoGetResponse.getResult.code);
}
if (!"0".Equals(josVoucherInfoGetResponse.getResult.responseData.errorCode))
{
//LOGGER.Error("request voucher api error"+ josVoucherInfoGetResponse.getResult.responseData.errorCode);
throw new InvalidTokenException("voucher api error ->" + josVoucherInfoGetResponse.getResult.code);
}
voucher = josVoucherInfoGetResponse.getResult.responseData.voucherEntity.voucher;
}
catch (Exception e)
{
//LOGGER.Error("request voucher failure ", e);
}
return voucher;
}
/// <summary>
/// Attemps to sign specified input.
/// </summary>
/// <param name="input">The array byte of input to sign.</param>
/// <returns>The base64 encoding string of signature.</returns>
/// <exception cref="InvalidTokenException"/>
/// <exception cref="NoValidKeyException"/>
/// <exception cref="InvalidKeyException"/>
/// <exception cref="InvalidKeyPermission"/>
/// <exception cref="MalformedException"/>
public string Sign(byte[] input)
{
// validate token first
ValidateToken();
// fetch sign key and check key status
MKey k = this.GetEncKey(kmc.GetMajorKeyVersion(), false);
//LOGGER.DebugFormat("Signing with key version: {0}", k.GetVersion());
string sig = null;
try
{
sig = k.Sign(input);
}
catch (MalformedException e)
{
statistic[StatisticType.SIGNERRCNT.GetValue()]++;
throw e;
}
catch (Exception e)
{
statistic[StatisticType.SIGNERRCNT.GetValue()]++;
throw new SystemException(e.GetType().Name + ":" + e.Message);
}
statistic[StatisticType.SIGNCNT.GetValue()]++;
return sig;
}
/// <summary>
/// Attemps to verify the signature with specified input data.
/// </summary>
/// <param name="data">The byte array of input data.</param>
/// <param name="sig">The base64 encoding string of signature to verify.</param>
/// <returns>True if verify successfully; otherwise, false.</returns>
/// <exception cref="InvalidTokenException"/>
/// <exception cref="MalformedException"/>
/// <exception cref="NoValidKeyException"/>
/// <exception cref="InvalidKeyException"/>
/// <exception cref="InvalidKeyPermission"/>
/// <exception cref="CorruptKeyException"/>
/// <exception cref="ServiceErrorException"/>
/// <exception cref="ArgumentException"/>
public bool Verify(byte[] input, string sig)
{
// validate token
ValidateToken();
byte[] sigBytes = Convert.FromBase64String(sig);
if (sigBytes.Length <= DEFAULT_KEYID_LEN + DEFAULT_SEED_LEN)
{
statistic[StatisticType.VERIFYERRCNT.GetValue()]++;
throw new MalformedException("Corrupted signature with illegal length.");
}
byte[] keyID = new byte[DEFAULT_KEYID_LEN];
using (MemoryStream buf = new MemoryStream(sigBytes))
{
buf.Read(keyID, 0, DEFAULT_KEYID_LEN);
}
MKey k = this.GetDecKeyByID(keyID, false);
//LOGGER.DebugFormat("Verifying with key version: {0}", k.GetVersion());
bool ret = false;
try
{
ret = k.Verify(input, sig);
statistic[StatisticType.VERIFYCNT.GetValue()]++;
}
catch (MalformedException e)
{
statistic[StatisticType.VERIFYERRCNT.GetValue()]++;
throw e;
}
catch (Exception e)
{
statistic[StatisticType.VERIFYERRCNT.GetValue()]++;
throw e;
}
return ret;
}
/// <summary>
/// Attemps to get encryption key with specified key version.
/// </summary>
/// <param name="keyVersion">The key version.</param>
/// <param name="isEncryption">True in encryption mode; otherwise, false.</param>
/// <returns>The master key for encryption.</returns>
/// <exception cref="NoValidKeyException"/>
/// <exception cref="InvalidKeyException"/>
/// <exception cref="InvalidKeyPermission"/>
private MKey GetEncKey(uint keyVersion, bool isEncryption = true)
{
// try to fetch corresponding master key from local key store
MKey k = this.cache_ks.GetEncKeyByVersion(keyVersion);
// check key status
if (k == null)
{
if (isEncryption)
{
//LOGGER.Fatal(SDK_HAS_NO_AVAILABLE_ENC_KEYS.GetMessage());
// should not happen, probably due to some internal error or other issues
reporter.InsertErrReport(
SDK_HAS_NO_AVAILABLE_ENC_KEYS.GetValue(),
SDK_HAS_NO_AVAILABLE_ENC_KEYS.GetMessage(),
EMPTYSTR,
MsgLevel.SEVERE);
statistic[StatisticType.ENCERRCNT.GetValue()]++;
throw new NoValidKeyException(SDK_HAS_NO_AVAILABLE_ENC_KEYS.GetMessage());
}
else
{
//LOGGER.Fatal(SDK_HAS_NO_AVAILABLE_SIGN_KEYS.GetMessage());
// should not happen, probably due to some internal error or other issues
reporter.InsertErrReport(
SDK_HAS_NO_AVAILABLE_SIGN_KEYS.GetValue(),
SDK_HAS_NO_AVAILABLE_SIGN_KEYS.GetMessage(),
EMPTYSTR,
MsgLevel.SEVERE);
statistic[StatisticType.SIGNERRCNT.GetValue()]++;
throw new NoValidKeyException(SDK_HAS_NO_AVAILABLE_SIGN_KEYS.GetMessage());
}
}
// encrypt only for ACTIVE key
if (k.GetKeyStatus() != KeyStatus.ACTIVE)
{
// Key might be revoke/suspended
//LOGGER.Fatal(SDK_OPERATE_WITH_INACTIVE_KEYS.GetMessage());
reporter.InsertErrReport(
SDK_OPERATE_WITH_INACTIVE_KEYS.GetValue(),
SDK_OPERATE_WITH_INACTIVE_KEYS.GetMessage(),
EMPTYSTR,
MsgLevel.ERROR);
if (isEncryption)
statistic[StatisticType.ENCERRCNT.GetValue()]++;
else
statistic[StatisticType.SIGNERRCNT.GetValue()]++;
throw new InvalidKeyException(SDK_OPERATE_WITH_INACTIVE_KEYS.GetMessage());
}
// check key permission
if (k.GetKeyUsage() != KeyUsage.E && k.GetKeyUsage() != KeyUsage.ED)
{
throw new InvalidKeyPermission("Key Permission Invalid.");
}
// check key timestamp
CheckExpiredKey(k);
return k;
}
/// <summary>
/// Attemps to get decryption key for specified ciphertext.
/// </summary>
/// <param name="ct">The byte array of key identifier.</param>
/// <param name="st">The output cipher status.</param>
/// <returns>The master key for decryption.</returns>
/// <exception cref="NoValidKeyException"/>
/// <exception cref="InvalidKeyException"/>
/// <exception cref="InvalidKeyPermission"/>
/// <exception cref="MalformedException"/>
/// <exception cref="ServiceErrorException"/>
/// <exception cref="CorruptKeyException"/>
/// <exception cref="ArgumentException"/>
private MKey GetDecKey(byte[] ct)
{
// check cipher and handle exceptions
CipherResult st = GetCipherResult(ct);
// MQ handler for different cases
if (st.status.Equals(CipherStatus.UnDecryptable))
{
int minLen = st.isStrong ? Math.Min(ct.Length, STRONG_HDR_LEN) : Math.Min(ct.Length, WEAK_HDR_LEN);
byte[] minHeader = new byte[minLen];
Array.Copy(ct, minHeader, minLen);
string header = Convert.ToBase64String(minHeader);
reporter.InsertErrReport(
SDK_HAS_NO_CORRESPONDING_DEC_KEYS.GetValue(),
SDK_HAS_NO_CORRESPONDING_DEC_KEYS.GetMessage() + header,
EMPTYSTR,
MsgLevel.SEVERE);
statistic[StatisticType.DECERRCNT.GetValue()]++;
throw new NoValidKeyException(SDK_HAS_NO_CORRESPONDING_DEC_KEYS.GetMessage() + header);
}
else if (st.status.Equals(CipherStatus.Feasible))
{
//LOGGER.Debug("Feasible case: KMS client needs to fetch keys from KMS.");
reporter.InsertEventReport(SDK_TRIGGER_ROTATED_KEY_FETCH.GetValue(),
SDK_TRIGGER_ROTATED_KEY_FETCH.GetMessage());
// fetch keys from KMS
// blocking call!!
kmc.FetchMKeys();
if (cache_ks.HasFutureKeyID(st.keyID))
{
int minLen = st.isStrong ? Math.Min(ct.Length, STRONG_HDR_LEN) : Math.Min(ct.Length, WEAK_HDR_LEN);
byte[] minHeader = new byte[minLen];
Array.Copy(ct, minHeader, minLen);
string header = Convert.ToBase64String(minHeader);
reporter.InsertErrReport(
SDK_FAILS_TO_FETCH_UPDATED_KEYS.GetValue(),
SDK_FAILS_TO_FETCH_UPDATED_KEYS.GetMessage() + header,
EMPTYSTR,
MsgLevel.SEVERE);
statistic[StatisticType.DECERRCNT.GetValue()]++;
throw new NoValidKeyException(SDK_FAILS_TO_FETCH_UPDATED_KEYS.GetMessage() + header);
}
}
else if (st.status.Equals(CipherStatus.Malformed))
{
statistic[StatisticType.DECERRCNT.GetValue()]++;
// fetch available ciphertext
string corrpted_cipher = "(NULL)";
if (ct != null)
{
int minLen = Math.Min(ct.Length, WEAK_HDR_LEN);
byte[] minHeader = new byte[minLen];
Array.Copy(ct, minHeader, minLen);
corrpted_cipher = minLen == 0 ? "(EMPTY)" : Convert.ToBase64String(minHeader);
}
reporter.InsertErrReport(
SDK_HAS_CORRUPTED_CIPHER.GetValue(),
SDK_HAS_CORRUPTED_CIPHER.GetMessage() + corrpted_cipher,
EMPTYSTR,
MsgLevel.SEVERE);
throw new MalformedException(SDK_HAS_CORRUPTED_CIPHER.GetMessage() + corrpted_cipher);
}
return this.GetDecKeyByID(st.keyID);
}
/// <summary>
/// Attemps to get decryption key with specified key identifier.
/// </summary>
/// <param name="keyID">The byte array of key identifier.</param>
/// <param name="isDecryption">True in decryption mode; otherwise, false.</param>
/// <returns>The master key for decryption.</returns>
/// <exception cref="NoValidKeyException"/>
/// <exception cref="InvalidKeyException"/>
/// <exception cref="InvalidKeyPermission"/>
/// <exception cref="ServiceErrorException"/>
/// <exception cref="CorruptKeyException"/>
/// <exception cref="MalformedException"/>
/// <exception cref="ArgumentException"/>
private MKey GetDecKeyByID(byte[] keyID, bool isDecryption = true)
{
// try to fetch corresponding master key from local key store
MKey k = this.cache_ks.SearchDecKey(keyID);
if (!isDecryption)
{
string header = MKey.GenerateMinHeaderByKeyID(keyID);
if (cache_ks.HasFutureKeyID(keyID))
{
//LOGGER.Debug("Feasible case: KMS client needs to fetch verify keys from KMS.");
reporter.InsertEventReport(SDK_TRIGGER_ROTATED_VERIFY_KEY_FETCH.GetValue(),
SDK_TRIGGER_ROTATED_VERIFY_KEY_FETCH.GetMessage());
kmc.FetchMKeys();
if (cache_ks.HasFutureKeyID(keyID))
{
reporter.InsertErrReport(
SDK_FAILS_TO_FETCH_UPDATED_VERIFY_KEYS.GetValue(),
SDK_FAILS_TO_FETCH_UPDATED_VERIFY_KEYS.GetMessage() + header,
EMPTYSTR,
MsgLevel.SEVERE);
statistic[StatisticType.VERIFYERRCNT.GetValue()]++;
throw new NoValidKeyException(SDK_FAILS_TO_FETCH_UPDATED_VERIFY_KEYS.GetMessage() + header);
}
k = this.cache_ks.SearchDecKey(keyID);
}
if (k == null)
{
string errorMessage = SDK_HAS_NO_CORRESPONDING_VERIFY_KEYS.GetMessage() + header;
//LOGGER.Fatal(errorMessage);
reporter.InsertErrReport(
SDK_HAS_NO_CORRESPONDING_VERIFY_KEYS.GetValue(),
errorMessage,
EMPTYSTR,
MsgLevel.SEVERE);
statistic[StatisticType.VERIFYERRCNT.GetValue()]++;
throw new NoValidKeyException(errorMessage);
}
}
// check if key status is revoked
if (k.GetKeyStatus() == KeyStatus.REVOKED)
{
// due to key rotation, decryption can use both active/suspend key but not for revoked one
//LOGGER.Fatal(SDK_OPERATE_WITH_INACTIVE_KEYS.GetMessage());
reporter.InsertErrReport(
SDK_OPERATE_WITH_INACTIVE_KEYS.GetValue(),
SDK_OPERATE_WITH_INACTIVE_KEYS.GetMessage(),
EMPTYSTR,
MsgLevel.SEVERE);
if (isDecryption)
{
statistic[StatisticType.DECERRCNT.GetValue()]++;
}
else
{
statistic[StatisticType.VERIFYERRCNT.GetValue()]++;
}
throw new InvalidKeyException(SDK_OPERATE_WITH_INACTIVE_KEYS.GetMessage());
}
// check key permission
if (k.GetKeyUsage() != KeyUsage.D && k.GetKeyUsage() != KeyUsage.ED)
{
throw new InvalidKeyPermission("Key Permission Invalid.");
}
// check key timestamp
this.CheckExpiredKey(k);
return k;
}
private void CheckExpiredKey(MKey key)
{
long now = EnvironmentHelper.GetCurrentMillis();
if (key.GetExpiredTime() < now)
{
reporter.InsertErrReport(
SDK_OPERATE_WITH_EXPIRED_KEYS.GetValue(),
SDK_OPERATE_WITH_EXPIRED_KEYS.GetMessage(),
EMPTYSTR,
MsgLevel.WARN);
//LOGGER.Debug(SDK_OPERATE_WITH_EXPIRED_KEYS.GetMessage());
}
}
/// <exception cref="InvalidTokenException"/>
private void ValidateToken()
{
if (!t.CheckEffective())
{
//LOGGER.FatalFormat("Please use this token after {0}", t.GetEffectiveDate());
// report via JMQ
reporter.InsertErrReport(
SDK_USE_INEFFECTIVE_TOKEN.GetValue(),
SDK_USE_INEFFECTIVE_TOKEN.GetMessage(),
EMPTYSTR,
MsgLevel.SEVERE);
// throw new exception
throw new InvalidTokenException(SDK_USE_INEFFECTIVE_TOKEN.GetMessage());
}
Token.State state = t.CheckExpired(TOKEN_EXP_DELTA);
if (state == Token.State.EXPIRED)
{
//LOGGER.Fatal("Please apply for a new token online. The current token is already expired for more than 30 days.");
// MQ client reports this severe exception
reporter.InsertErrReport(
SDK_USE_HARD_EXPIRED_TOKEN.GetValue(),
SDK_USE_HARD_EXPIRED_TOKEN.GetMessage(),
EMPTYSTR,
MsgLevel.SEVERE);
throw new InvalidTokenException(SDK_USE_HARD_EXPIRED_TOKEN.GetMessage());
}
else if (state == Token.State.EXPIREWARNING)
{
reporter.InsertErrReport(
SDK_USE_SOFT_EXPIRED_TOKEN.GetValue(),
SDK_USE_SOFT_EXPIRED_TOKEN.GetMessage(),
EMPTYSTR,
MsgLevel.WARN);
//LOGGER.Warn("Token is already expired but less than 30 days. We still allow it to be operated.");
//LOGGER.WarnFormat("Token expired date: {0}", t.GetExpiredDate());
}
}
/// <summary>
/// Computes a position-sensitive index for the specified plaintext.
/// <para>Suitable for fixed-length or fixed-format data, such as phone number and ID card number.</para>
/// </summary>
/// <param name="spt">The string of plaintext to compute index for.</param>
/// <returns>the hex string of computed index.</returns>
public string ObtainWildCardKeyWordIndex(string spt)
{
if (spt == null)
{
throw new ArgumentNullException("plaintext is null.");
}
// convert to unicode encoding if plaintext contains non-ASCII character
spt = IndexCalculator.FormatPlaintext(spt);
MKey k = this.GetEncKey(0, true);
byte[] key = KeyEncryption.Wrap(k, KEYWORDSALT);
byte[] iv = new byte[24];
Array.Copy(key, 0, iv, 0, iv.Length);
byte[] pt = Encoding.UTF8.GetBytes(spt);
int clen = pt.Length;
byte[] ct = new byte[clen];
using (SymmetricAlgorithm salsa20 = new Salsa20())
using (ICryptoTransform encrypt = salsa20.CreateEncryptor(key, iv))
{
ct = encrypt.TransformFinalBlock(pt, 0, pt.Length);
}
return BitConverterHelper.ToHexString(ct);
}
/// <summary>
/// Computes a query index for the specified keyword with wildcard character.
/// <para>Use the asterisk '*' as a wildcard to match any ASCII charachter.</para>
/// <para>Use the crosshatch '#' as a wildcard to match any non-ASCII charachter.</para>
/// </summary>
/// <param name="queryW">The string of keyword with wildcard to query.</param>
/// <returns>the hex string of computed query index.</returns>
public string CalculateWildCardKeyWord(string queryW)
{
if (queryW == null)
{
throw new ArgumentNullException("query keyword is null.");
}
// format if keyword contains wildcard for non-ASCII character
queryW = IndexCalculator.FormatQueryKeyword(queryW);
MKey k = this.GetEncKey(0, true);
Console.WriteLine(k.GetRawKey().Length);
byte[] key = KeyEncryption.Wrap(k, KEYWORDSALT);
byte[] nonce = new byte[24];
Array.Copy(key, 0, nonce, 0, nonce.Length);
byte[] pt = Encoding.UTF8.GetBytes(queryW);
// preallocate
byte[] ct = new byte[pt.Length];
using (SymmetricAlgorithm salsa20 = new Salsa20())
using (ICryptoTransform encrypt = salsa20.CreateEncryptor(key, nonce))
{
ct = encrypt.TransformFinalBlock(pt, 0, pt.Length);
}
int skip = 0;
for (int i = 0; i < queryW.Length; i++)
{
if (queryW[i] == WildcardPattern.ASCII)
skip++;
else break;
}
if (skip == queryW.Length)
{
// query string is all *
throw new Exception("keyword format does not match!");
}
byte[] rct = new byte[ct.Length - skip];
Array.Copy(ct, skip, rct, 0, rct.Length);
return BitConverterHelper.ToHexString(rct);
}
/// <summary>
/// Computes a query index for the specified keyword with wildcard.
/// </summary>
/// <param name="keyword">The string of keyword to query.</param>
/// <param name="asciiCharPrefixNumber">The number of ASCII wildcard character.</param>
/// <param name="nonAsciiCharPrefixNumber">The number of non-ASCII wildcard character.</param>
/// <returns>the hex string of computed query index.</returns>
public string CalculateWildCardKeyWord(string keyword, int asciiCharPrefixNumber, int nonAsciiCharPrefixNumber = 0)
{
string queryW = IndexCalculator.GenerateWildcardKeyword(keyword, asciiCharPrefixNumber, nonAsciiCharPrefixNumber);
return CalculateWildCardKeyWord(queryW);
}
/// <summary>
/// Computes a position-insensitive index for the specified plaintext.
/// <para>Suitable for variable length data, such as name and address.</para>
/// </summary>
/// <param name="spt">The string of plaintext to compute index for.</param>
/// <returns>base64 encoding string of computed index.</returns>
public string ObtainKeyWordIndex(string spt)
{
if (spt == null)
{
throw new ArgumentNullException("plaintext is null.");
}
MKey k = this.GetEncKey(0, true);
byte[] key = KeyEncryption.Wrap(k, KEYWORDSALT);
byte[] iv = new byte[24];
Array.Copy(key, 0, iv, 0, iv.Length);
byte[] ct = new byte[spt.Length * 4];
int offset = 0;
StringBuilder buffer = new StringBuilder();
using (SymmetricAlgorithm salsa20 = new Salsa20())
using (ICryptoTransform encrypt = salsa20.CreateEncryptor(key, iv))
{
for (int i = 0, l = spt.Length; i < l; i++)
{
byte[] wpt = Encoding.UTF8.GetBytes(spt.Substring(i, 1));
if (wpt.Length < 4)
{
// padding with key string
int padOffset = (wpt[0] > 127 ? 256 - wpt[0] : wpt[0]) % (key.Length - 8);
int padSize = 4 - wpt.Length;
byte[] wpt2 = new byte[4];
Array.Copy(wpt, wpt2, Math.Min(wpt.Length, 4));
wpt = wpt2;
Array.Copy(key, 4 + padOffset, wpt, wpt.Length - padSize, padSize);
}
encrypt.TransformBlock(wpt, 0, 4, ct, offset);
byte[] rct = new byte[4];
Array.Copy(ct, offset, rct, 0, rct.Length);
buffer.Append(Convert.ToBase64String(rct).Replace("==", string.Empty));
offset += 4;
}
}
return buffer.ToString();
}
/// <summary>
/// Computes a query index for the specified keyword.
/// </summary>
/// <param name="queryW">The string of keyword to query.</param>
/// <returns>base64 encoding string of computed query index.</returns>
public string CalculateKeyWord(string queryW)
{
return ObtainKeyWordIndex(queryW);
}
private static byte[] ExtractKeyId(byte[] ct, bool isStrong)
{
byte[] eid;
using (MemoryStream buf = new MemoryStream(ct))
{
// skip ciphertext type
byte ctype = (byte)buf.ReadByte();
if (isStrong)
{
byte[] len = new byte[2];
buf.Read(len, 0, 2);
ushort eidLen = BitConverterHelper.ToUInt16(len, 0);
// add length checking, not enough space
if (ct.Length - 3 < eidLen)
return null;
eid = new byte[eidLen];
buf.Read(eid, 0, eidLen);
}
else
{
// skip algorithm
byte atype = (byte)buf.ReadByte();
// add length checking, not enough space
if (ct.Length - 2 < DEFAULT_KEYID_LEN)
return null;
eid = new byte[DEFAULT_KEYID_LEN];
buf.Read(eid, 0, DEFAULT_KEYID_LEN);
}
}
return eid;
}
/// <summary>
/// Attempts to set the interval, in seconds, for repeated execution.
/// </summary>
/// <param name="epoch">The given time interval, unit is second.</param>
public void SetRefreshEpoch(long epoch)
{
//LOGGER.DebugFormat("Set epoch for MQ/KM threads. Value = {0} seconds.", epoch);
kmEpoch = mqEpoch = epoch;
kmScheduler.SetExecuteInterval(kmEpoch);
mqScheduler.SetExecuteInterval(mqEpoch);
}
/// <summary>
/// Delete all keys in memory and reset the key chain status flag
/// </summary>
public void ManuallyDeleteKeys()
{
cache_ks.RemoveAllMKeys();
kmc.ResetKeyChainFlag();
}
public bool IsKeyChainReady()
{
return kmc.IsKeyChainReady();
}
public static string GetSDKVersion()
{
return version;
}
public long[] GetStatistic()
{
long[] tmp = new long[this.statistic.Length];
Array.Copy(this.statistic, tmp, this.statistic.Length);
return tmp;
}
public string GetServiceIdentifier()
{
// encapsulate all details in attributes
return (t == null) ? "Unknown Service" : t.GetServiceName();
}
public string GetTokenIdentifier()
{
// encapsulate all details in attributes
return (t == null) ? "Unknown TID" : t.GetId();
}
public KMClient GetKMClient()
{
return kmc;
}
public MonitorClient GetMonitorClient()
{
return reporter;
}
}
public class CipherResult
{
public CipherStatus status;
public byte[] keyID;
public bool isStrong;
public CipherResult(CipherStatus status, byte[] keyID, bool isStrong)
{
this.keyID = keyID;
this.status = status;
this.isStrong = isStrong;
}
}
// CipherStatus (return from canDecrypt)
public enum CipherStatus
{
Decryptable, // valid cipher, can be decrypted
Malformed, // invalid cipher because the format is malformed (by checking cipher header)
Feasible, // valid cipher with future key id, could decrypt the cipher but required to pull keys
UnDecryptable // valid cipher but undecryptable (keyid is neither in key cache or future keyid list
}
}