步步为盈
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.

300 lines
11 KiB

3 years ago
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;
/// <summary>
/// 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).
/// </summary>
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);
}
/// <summary>
/// Attemps to load certificate from hardcode SDK to validate token.
/// </summary>
/// <param name="isProd">True if it is production mode; otherwise, false.</param>
private static void LoadCert()
{
try
{
scert = new X509Certificate2(Encoding.Default.GetBytes(Constants.TMS_PROD_TOKEN_CERT));
}
catch (Exception)
{
scert = null;
}
}
/// <summary>
/// Attemps to parse a new instance of Token from the specified token and platform mode.
/// </summary>
/// <param name="base64Token">The base64 encoding string of token to parse.</param>
/// <param name="isProd">True if it is production mode; otherwise, false.</param>
/// <returns>The corresponding instance of Token if no exception occur.</returns>
/// <exception cref="InvalidTokenException"/>
/// <exception cref="MalformedException"/>
public static Token ParseFromString(string base64Token)
{
TokenStruct token;
string data;
byte[] sigBytes;
try
{
var input = Convert.FromBase64String(base64Token);
token = JsonHelper.FromJson<TokenStruct>(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<Origin>(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; }
/// <summary>
/// Checks token is effective (active) or not.
/// </summary>
/// <returns>true if token is active; otherwise, false.</returns>
public bool CheckEffective()
{
long now = EnvironmentHelper.GetCurrentMillis();
return now >= this.effectiveTs;
}
/// <summary>
/// Checks token is expired (inactive) or not.
/// </summary>
/// <param name="delta">delta value for extended tolerance (in millisecond).</param>
/// <returns>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.</returns>
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(); }
/// <summary>
/// Attemps to encrypt the specified plaintext.
/// </summary>
/// <param name="plaintext">The byte array of plaintext to encrypt.</param>
/// <returns>The byte array of encrypted data.</returns>
/// <exception cref="InvalidTokenException"/>
/// <exception cref="ArgumentNullException"/>
public byte[] DoEncrypt(byte[] plaintext)
{
if (!isVerify)
throw new InvalidTokenException("Not a verified token.");
return de.Encrypt(plaintext);
}
/// <summary>
/// Attemps to decrypt the specified ciphertext.
/// </summary>
/// <param name="ciphertext">The byte array of ciphertext to decrypt.</param>
/// <returns>The byte array of decrypted data.</returns>
/// <exception cref="InvalidTokenException"/>
/// <exception cref="ArgumentNullException"/>
public byte[] DoDecrypt(byte[] ciphertext)
{
if (!isVerify)
throw new InvalidTokenException("Not a verified token.");
return de.Decrypt(ciphertext);
}
/// <summary>
/// Computes the signature for the specified input data.
/// </summary>
/// <param name="input">The byte array of input to compute hash value for.</param>
/// <returns>The computed byte array.</returns>
/// <exception cref="InvalidTokenException"/>
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;
}
/// <summary>
/// Attemps to verify the signature for the specified input data.
/// </summary>
/// <param name="input">The byte array of input data.</param>
/// <param name="sig">The byte array of signature to verify.</param>
/// <returns>True if verify successfully; otherwise, false.</returns>
/// <exception cref="InvalidTokenException"/>
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);
}
}