using System; using System.IO; using System.Linq; using System.Security.Cryptography; using Newtonsoft.Json; using Jd.ACES.Utils; using Jd.ACES.Common.Exceptions; using static Jd.ACES.Common.Constants; namespace Jd.ACES.Common { public class MKey { private string service_identifier; private byte[] id; private byte[] key; private byte[] skey; private uint ver; private KeyUsage keyUsage; private KeyStatus keyStatus; private KeyType keyType; private long expired; // v2.0.x effective date private long effective; private bool validFlag; private string key_digest; private const int MEGABYTE = 1024 * 1024; private Random r; /// /// Constructor of MKey. /// /// /// public MKey(string service, byte[] kid, byte[] rawkey, string kdigest, uint kver, long effectiveTs, long expTs, string ktype, string kusage, int kstatus) { if (kid == null || service == null) { throw new MalformedException("ID and App fields cannot be null."); } this.service_identifier = service; this.id = kid; this.ver = kver; if (!EnumHelper.TryParse(kusage, out this.keyUsage)) { throw new MalformedException("unknown key usage."); } // kstatus: 0->array(keystatus)-1 if (kstatus >= Enum.GetValues(typeof(KeyStatus)).Length) { throw new MalformedException("unknown key status."); } // pass the key status check this.keyStatus = (KeyStatus)kstatus; if (!EnumHelper.TryParse(ktype, out this.keyType)) { throw new MalformedException("unknown key type."); } this.validFlag = false; if (rawkey != null) { this.expired = expTs; this.effective = effectiveTs; if (rawkey.Length == DEFAULT_MSKEY_LEN) { this.key = new byte[DEFAULT_MEKEY_LEN]; Array.Copy(rawkey, 0, this.key, 0, DEFAULT_MEKEY_LEN); this.skey = rawkey; } else { throw new ArgumentException("Key is too short for MKey Object."); } } // verify key itself by comparing key digest this.key_digest = kdigest; byte[] digest = Convert.FromBase64String(this.key_digest); using (SHA256 hash = SHA256.Create()) { // has to compute over the whole key byte[] cdigest = hash.ComputeHash(rawkey); if (digest.SequenceEqual(cdigest)) { this.validFlag = true; } } r = new Random(Environment.TickCount); } public bool IsValid() { return validFlag; } public string GetName() { return service_identifier; } public uint GetVersion() { return ver; } public byte[] GetID() { return id; } public long GetExpiredTime() { return expired; } public long GetEffectiveTime() { return effective; } public KeyType GetKeyType() { return keyType; } public KeyStatus GetKeyStatus() { return keyStatus; } public KeyUsage GetKeyUsage() { return keyUsage; } public byte[] GetRawKey() { return key; } /// /// Attemps to encrypt specified plaintext with weak version. /// /// The byte array of plaintext to encrypt. /// The byte array of encrypted data. /// public byte[] Encrypt(byte[] pt) { byte[] ct = null; // use default dkey constructor byte[] dataCipher = KeyEncryption.Encrypt(this, pt); // construct the header using (MemoryStream buf = new MemoryStream()) { buf.WriteByte((byte)CipherType.WEAK); buf.WriteByte((byte)AlgoType.AES_CBC_128); buf.Write(id, 0, id.Length); buf.Write(dataCipher, 0, dataCipher.Length); ct = buf.ToArray(); buf.Close(); }; return ct; } /// /// Attemps to decrypt specified ciphertext with weak version. /// /// The byte array of ciphertext to decrypt. /// The byte array of decrypted data. /// /// public byte[] Decrypt(byte[] ct) { byte[] pt = null; using (MemoryStream buf = new MemoryStream(ct)) { byte ctype = (byte)buf.ReadByte(); if (ctype != (byte)CipherType.WEAK) throw new MalformedException($"Invalid CipherText Type:{ctype}"); byte atype = (byte)buf.ReadByte(); if (atype != (byte)AlgoType.AES_CBC_128) throw new MalformedException($"Invalid Encryption Algorithm Type:{atype}"); byte[] keyid = new byte[DEFAULT_KEYID_LEN]; buf.Read(keyid, 0, DEFAULT_KEYID_LEN); if (!keyid.SequenceEqual(this.id)) { // error, wrong key id throw new MalformedException("Invalid MKey ID:" + BitConverterHelper.ToString(keyid)); } byte[] cipher = new byte[ct.Length - buf.Position]; buf.Read(cipher, 0, cipher.Length); pt = KeyEncryption.Decrypt(this, cipher); buf.Close(); } return pt; } /// /// Attemps to sign specified input. /// /// The array byte of input to sign. /// The base64 encoding string of signature. /// public string Sign(byte[] input) { if (input == null) { throw new MalformedException("Illegal input."); } if (this.id == null || this.id.Length != DEFAULT_KEYID_LEN) { throw new MalformedException("Illegal Signing Key."); } byte[] random = new byte[DEFAULT_SEED_LEN]; r.NextBytes(random); byte[] sig = DoSign(input, random); string base64Sig = null; using (MemoryStream m = new MemoryStream(DEFAULT_KEYID_LEN + DEFAULT_SEED_LEN + sig.Length)) { m.Write(id, 0, DEFAULT_KEYID_LEN); m.Write(random, 0, DEFAULT_SEED_LEN); m.Write(sig, 0, sig.Length); base64Sig = Convert.ToBase64String(m.ToArray(), Base64FormattingOptions.None); } return base64Sig; } /// /// Attemps to verify the signature with specified input. /// /// The byte array of input. /// The base64 encoding string of signature to verify. /// True if verify successfully; otherwise, false. /// public bool Verify(byte[] input, string sig) { return Verify(input, Convert.FromBase64String(sig)); } /// /// Attemps to verify the signature with specified input. /// /// The byte array of input. /// The byte array of signature to verify. /// True if verify successfully; otherwise, false. /// private bool Verify(byte[] input, byte[] sig) { if (input == null) { throw new MalformedException("Illegal input."); } if (sig == null || sig.Length <= DEFAULT_KEYID_LEN + DEFAULT_SEED_LEN) { throw new MalformedException("Illegal Signature."); } bool ret = false; using (MemoryStream buf = new MemoryStream(sig)) { byte[] keyID = new byte[DEFAULT_KEYID_LEN]; buf.Read(keyID, 0, DEFAULT_KEYID_LEN); // check keyid if (!keyID.SequenceEqual(this.id)) throw new MalformedException("Corrupted ciphertext header with illegal key id."); byte[] random = new byte[DEFAULT_SEED_LEN]; buf.Read(random, 0, DEFAULT_SEED_LEN); byte[] sigCarried = new byte[sig.Length - DEFAULT_SEED_LEN - DEFAULT_KEYID_LEN]; buf.Read(sigCarried, 0, sigCarried.Length); byte[] computed = DoSign(input, random); ret = computed.SequenceEqual(sigCarried); } return ret; } /// /// Attemps to sign specified input and random data. /// /// The array byte of input to sign. /// The array byte of random data to sign. /// The array byte of signature. private byte[] DoSign(byte[] input, byte[] random) { byte[] dataToSign = new byte[random.Length + input.Length]; Array.Copy(input, 0, dataToSign, 0, input.Length); Array.Copy(random, 0, dataToSign, input.Length, random.Length); byte[] sig = null; using (HMACSHA256 hmac = new HMACSHA256(this.skey)) { sig = hmac.ComputeHash(dataToSign); } return sig; } /// /// Attemps to encrypt a specified file and output into another specified file. /// /// The input file stream to read plaintext. /// The output file stream to write ciphertext. /// The size of input file. /// The size of encrypted data. /// /// /// public long Encrypt(FileStream fin, FileStream fout, long filesize) { DataEncryption de = new DataEncryption(); byte[] keyCipher = KeyEncryption.Wrap(this, de.ExportKey()); long mod16 = filesize % 16; // + iv length long cipherlen = filesize + 16 + (mod16 == 0 ? 16 : 16 - mod16); byte[] header = null; // header calculation using (MemoryStream headerS = new MemoryStream()) { headerS.WriteByte((byte)CipherType.LARGE); byte[] idlen = BitConverterHelper.GetBytes((ushort)id.Length); headerS.Write(idlen, 0, idlen.Length); headerS.Write(id, 0, id.Length); headerS.WriteByte((byte)AlgoType.AES_CBC_128); byte[] keylen = BitConverterHelper.GetBytes((ushort)keyCipher.Length); headerS.Write(keylen, 0, keylen.Length); headerS.Write(keyCipher, 0, keyCipher.Length); headerS.WriteByte((byte)AlgoType.AES_CBC_128); byte[] clen = BitConverterHelper.GetBytes(cipherlen); headerS.Write(clen, 0, clen.Length); header = headerS.ToArray(); } CryptoStream encryptor = null; try { // seek to the original fout.Write(header, 0, header.Length); // write continuous cipher encryptor = de.PrepareCipherOut(fout); // iterative encrypt block with CipherOutputStream byte[] chunk = new byte[MEGABYTE]; int read; while ((read = fin.Read(chunk, 0, MEGABYTE)) > 0) { encryptor.Write(chunk, 0, read); } } catch (Exception e) { throw new Exception($"File encryption error occurred: {e.Message}"); } finally { encryptor.Flush(); encryptor.Close(); fin.Close(); fout.Close(); } return (long)cipherlen + header.Length; } /// /// Attemps to decrypt a specified file and output into another specified file. /// /// The input file stream to read ciphertext. /// The output file stream to write plaintext. /// The size of input file. /// The size of decrypted data. /// /// /// public long Decrypt(FileStream fin, FileStream fout, long filesize) { // parsing header, fix 16 for aes block size int hdlen = 1 * 3 + 2 * 2 + 8 + id.Length + 16; byte[] header = new byte[hdlen]; if (fin.Read(header, 0, hdlen) != hdlen) throw new MalformedException("ciphertext has no sufficient length for header structure."); byte[] dkey = null; ulong dcipherLen = 0L; long plainLen = 0L; CryptoStream cis = null; // paring header using (MemoryStream buf = new MemoryStream(header)) { byte ctype = (byte)buf.ReadByte(); if (ctype != (byte)CipherType.LARGE) throw new MalformedException("Invalid CipherText Type."); // id len byte[] len = new byte[2]; buf.Read(len, 0, 2); ushort id_len = BitConverterHelper.ToUInt16(len, 0); if (id_len != DEFAULT_KEYID_LEN) throw new MalformedException("Corrupted ciphertext header with illegal key id length."); byte[] keyid = new byte[DEFAULT_KEYID_LEN]; buf.Read(keyid, 0, DEFAULT_KEYID_LEN); if (!keyid.SequenceEqual(this.id)) { // error, wrong key id throw new MalformedException("Invalid MKey ID:" + BitConverterHelper.ToString(keyid)); } byte atype = (byte)buf.ReadByte(); if (atype != (byte)AlgoType.AES_CBC_128) throw new MalformedException($"Invalid Encryption Algorithm Type:{atype}"); buf.Read(len, 0, 2); ushort key_len = BitConverterHelper.ToUInt16(len, 0); if (key_len < DEFAULT_CIPHERBLK_LEN || key_len > (buf.Capacity - buf.Position)) throw new MalformedException($"Corrupted ciphertext with invalid key cipher length:{key_len}"); byte[] keyCipher = new byte[key_len]; buf.Read(keyCipher, 0, keyCipher.Length); // decrypt the key dkey = KeyEncryption.Unwrap(this, keyCipher); atype = (byte)buf.ReadByte(); if (atype != (byte)AlgoType.AES_CBC_128) throw new MalformedException($"Invalid Encryption Algorithm Type:{atype}"); // cipher len len = new byte[8]; buf.Read(len, 0, 8); dcipherLen = BitConverterHelper.ToUInt64(len, 0); } try { cis = new DataEncryption(dkey).PrepareCipherIn(fin); if (dcipherLen != (ulong)(filesize - header.Length)) { throw new MalformedException("Corrupted encrypted file: illegal data cipher length."); } // iterative encrypt block with CipherOutputStream byte[] chunk = new byte[MEGABYTE]; int read; while ((read = cis.Read(chunk, 0, MEGABYTE)) > 0) { plainLen += read; fout.Write(chunk, 0, read); } } catch (Exception e) { throw new Exception($"File decryption error occurred: {e.Message}"); } finally { cis.Close(); fout.Flush(); fout.Close(); } return plainLen; } /// /// Computes the minimum header based on specified key identifier for tracing master key. /// /// The byte array of key identifier. /// The base64 encoding string of computed header. public static string GenerateMinHeaderByKeyID(byte[] keyID) { byte[] header = new byte[2 + keyID.Length]; header[0] = 0; header[1] = 0; Array.Copy(keyID, 0, header, 2, keyID.Length); return Convert.ToBase64String(header); } }// end of MKey public class MKData { [JsonProperty("id")] public string Id { get; set; } [JsonProperty("key_string")] public string KeyString { get; set; } [JsonProperty("key_type")] public string KeyType { get; set; } [JsonProperty("key_exp")] public long KeyExp { get; set; } [JsonProperty("key_effective")] public long KeyEffective { get; set; } [JsonProperty("version")] public uint Version { get; set; } [JsonProperty("key_status")] public int KeyStatus { get; set; } [JsonProperty("key_digest")] public string KeyDigest { get; set; } }// end of MKeyData }