using System;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Linq;
using Newtonsoft.Json;
using Jd.ACES.Utils;
using Jd.ACES.Common.Exceptions;
namespace Jd.ACES.Common
{
public class Token : ITokenCipher, ITokenSignature
{
private string label; // label, could be create, update, and other types
private long effectiveTs; // token active timestamp, unix time format
private long expiredTs; // token expired timesatmp, unix time format
private string id; // token identifier, encoded in Base64
private byte[] key; // token credential, symmetric key for HMAC
private string service = "Unknown"; // token major service
private Origin stype; // service type, 0 for IDC, 1 for Beta,
private bool isVerify = false; // token is verified or not
private string zone = "CN-0"; // zone field, default value is CN-0 if not assigned
private static X509Certificate2 scert = null;
private DataEncryption de;
///
/// Token constructor to initialize itself by given certain fields,
/// including label, identifier, key, service name, effective and
/// expired time stamp, and issue type (stype, online or offline).
///
private Token(string label, string id, byte[] key, long effectiveTs, long expiredTs,
Origin stype, string service, string zone)
{
this.label = label;
this.effectiveTs = effectiveTs;
this.expiredTs = expiredTs;
this.id = id;
this.key = key;
this.service = service;
this.stype = stype;
this.isVerify = true;
if (zone != null) this.zone = zone;
// init cipher and sign objects
this.de = new DataEncryption(this.key);
}
///
/// Attemps to load certificate from hardcode SDK to validate token.
///
/// True if it is production mode; otherwise, false.
private static void LoadCert()
{
try
{
scert = new X509Certificate2(Encoding.Default.GetBytes(Constants.TMS_PROD_TOKEN_CERT));
}
catch (Exception)
{
scert = null;
}
}
///
/// Attemps to parse a new instance of Token from the specified token and platform mode.
///
/// The base64 encoding string of token to parse.
/// True if it is production mode; otherwise, false.
/// The corresponding instance of Token if no exception occur.
///
///
public static Token ParseFromString(string base64Token)
{
TokenStruct token;
string data;
byte[] sigBytes;
try
{
var input = Convert.FromBase64String(base64Token);
token = JsonHelper.FromJson(input);
data = JsonHelper.ToJson(token.Data);
sigBytes = Convert.FromBase64String(token.Sig);
}
catch (Exception)
{
throw new InvalidTokenException(TDEStatus.SDK_USE_INVALID_TOKEN.GetMessage());
}
string label = token.Data.Act;
long startTs = token.Data.Effective;
long endTs = token.Data.Expired;
string id = token.Data.Id;
byte[] key = Convert.FromBase64String(token.Data.Key);
string service = token.Data.Service;
Origin sType = EnumHelper.FromValue(token.Data.Stype);
string zone = null;
if (token.ExternalData != null)
{
zone = token.ExternalData.Zone;
}
LoadCert();
if (scert == null) { throw new SystemException("No Trust Anchor Certificate Available"); }
SHA256 sha256 = SHA256.Create();
byte[] hash = sha256.ComputeHash(Encoding.UTF8.GetBytes(data));
RSAPKCS1SignatureDeformatter rsaDeformatter = new RSAPKCS1SignatureDeformatter(scert.PublicKey.Key);
rsaDeformatter.SetHashAlgorithm(Constants.DEFAULT_TOKEN_VERIFY_ALGO);
if (!rsaDeformatter.VerifySignature(hash, sigBytes))
{
throw new InvalidTokenException("Token Signature Validation Failed.");
}
// assign parsing fields back to Token t and return it
return new Token(label, id, key, startTs, endTs, sType, service, zone);
}
public string GetId() { return this.id; }
public string GetServiceName() { return this.service; }
public Origin GetOriginType() { return this.stype; }
///
/// Checks token is effective (active) or not.
///
/// true if token is active; otherwise, false.
public bool CheckEffective()
{
long now = EnvironmentHelper.GetCurrentMillis();
return now >= this.effectiveTs;
}
///
/// Checks token is expired (inactive) or not.
///
/// delta value for extended tolerance (in millisecond).
/// Enum type indicating token state.
/// VALID indicates the token is still valid(not expired).
/// EXPIREWARNING indicates the token is expired within delta millisecond.
/// EXPIRED means the token is expired more than delta millisecond.
public State CheckExpired(long delta)
{
long now = EnvironmentHelper.GetCurrentMillis();
if (expiredTs >= now)
return State.VALID;
else if (expiredTs + delta >= now)
return State.EXPIREWARNING;
else
return State.EXPIRED;
}
public string GetExpiredDate()
{
return EnvironmentHelper.FormatMsDateToString(this.expiredTs);
}
public long GetExpiredDateInLong() { return expiredTs; }
public string GetEffectiveDate()
{
return EnvironmentHelper.FormatMsDateToString(this.effectiveTs);
}
public string GetZone() { return this.zone; }
public string GetTokenOrigin() { return stype.ToString(); }
///
/// Attemps to encrypt the specified plaintext.
///
/// The byte array of plaintext to encrypt.
/// The byte array of encrypted data.
///
///
public byte[] DoEncrypt(byte[] plaintext)
{
if (!isVerify)
throw new InvalidTokenException("Not a verified token.");
return de.Encrypt(plaintext);
}
///
/// Attemps to decrypt the specified ciphertext.
///
/// The byte array of ciphertext to decrypt.
/// The byte array of decrypted data.
///
///
public byte[] DoDecrypt(byte[] ciphertext)
{
if (!isVerify)
throw new InvalidTokenException("Not a verified token.");
return de.Decrypt(ciphertext);
}
///
/// Computes the signature for the specified input data.
///
/// The byte array of input to compute hash value for.
/// The computed byte array.
///
public byte[] DoSign(byte[] input)
{
if (!isVerify)
throw new InvalidTokenException("Not a verified token.");
byte[] sig = null;
using (HMACSHA256 hmac = new HMACSHA256(this.key))
{
sig = hmac.ComputeHash(input);
}
return sig;
}
///
/// Attemps to verify the signature for the specified input data.
///
/// The byte array of input data.
/// The byte array of signature to verify.
/// True if verify successfully; otherwise, false.
///
public bool DoVerify(byte[] input, byte[] sig)
{
if (!isVerify)
throw new InvalidTokenException("Not a verified token.");
// call doSign on the input
byte[] csig = DoSign(input);
return csig.SequenceEqual(sig);
}
// Token status
public enum State
{
VALID,
EXPIREWARNING,
EXPIRED
}
public enum Origin
{
UNDEFINED = 0,
IDC = 1,
BETA = 2,
DEV = 3
}
}
public class TokenStruct
{
[JsonProperty("sig")]
public string Sig { get; set; }
[JsonProperty("data")]
public InnerStruct Data { get; set; }
[JsonProperty("externalData")]
public ExternalStruct ExternalData { get; set; }
public class ExternalStruct
{
[JsonProperty("zone")]
public string Zone { get; set; }
}
public class InnerStruct
{
[JsonProperty("act")]
public string Act { get; set; }
[JsonProperty("effective")]
public long Effective { get; set; }
[JsonProperty("expired")]
public long Expired { get; set; }
[JsonProperty("id")]
public string Id { get; set; }
[JsonProperty("key")]
public string Key { get; set; }
[JsonProperty("service")]
public string Service { get; set; }
[JsonProperty("stype")]
public int Stype { get; set; }
}
}
public interface ITokenCipher
{
byte[] DoEncrypt(byte[] plaintext);
byte[] DoDecrypt(byte[] ciphertext);
}
public interface ITokenSignature
{
byte[] DoSign(byte[] input);
bool DoVerify(byte[] input, byte[] sig);
}
}