using System; using System.Collections; using System.Collections.Generic; using LipingShare.LCLib.Asn1Processor; using System.Security.Cryptography; namespace UnityEngine.Purchasing.Security { internal class DistinguishedName { public string Country { get; set; } public string Organization { get; set; } public string OrganizationalUnit { get; set; } public string Dnq { get; set; } public string State { get; set; } public string CommonName { get; set; } public string SerialNumber { get; set; } public DistinguishedName(Asn1Node n) { /* Name: * SET * SEQ (attr) * Object Identifier * Printable String || UTF8String */ if (n.MaskedTag == Asn1Tag.SEQUENCE) { for (int i = 0; i < n.ChildNodeCount; i++) { Asn1Node tt = n.GetChildNode(i); if (tt.MaskedTag != Asn1Tag.SET || tt.ChildNodeCount != 1) throw new InvalidX509Data(); tt = tt.GetChildNode(0); if (tt.MaskedTag != Asn1Tag.SEQUENCE || tt.ChildNodeCount != 2) throw new InvalidX509Data(); Asn1Node oi = tt.GetChildNode(0); Asn1Node txt = tt.GetChildNode(1); if (oi.MaskedTag != Asn1Tag.OBJECT_IDENTIFIER || !( (txt.MaskedTag == Asn1Tag.PRINTABLE_STRING) || (txt.MaskedTag == Asn1Tag.UTF8_STRING) || (txt.MaskedTag == Asn1Tag.IA5_STRING))) { throw new InvalidX509Data(); } var xoid = new LipingShare.LCLib.Asn1Processor.Oid(); string oiName = xoid.Decode(oi.Data); var enc = new System.Text.UTF8Encoding(); switch (oiName) { case "2.5.4.6": // countryName Country = enc.GetString(txt.Data); break; case "2.5.4.10": // organizationName Organization = enc.GetString(txt.Data); break; case "2.5.4.11": // organizationalUnit OrganizationalUnit = enc.GetString(txt.Data); break; case "2.5.4.3": // commonName CommonName = enc.GetString(txt.Data); break; case "2.5.4.5": // serial number SerialNumber = Asn1Util.ToHexString(txt.Data); break; case "2.5.4.46": // dnq Dnq = enc.GetString(txt.Data); break; case "2.5.4.8": // state State = enc.GetString(txt.Data); break; } } } } public bool Equals(DistinguishedName n2) { return this.Organization == n2.Organization && this.OrganizationalUnit == n2.OrganizationalUnit && this.Dnq == n2.Dnq && this.Country == n2.Country && this.State == n2.State && this.CommonName == n2.CommonName; } public override string ToString() { return "CN: " + CommonName + "\n" + "ON: " + Organization + "\n" + "Unit Name: " + OrganizationalUnit + "\n" + "Country: " + Country; } } internal class X509Cert { public string SerialNumber { get; private set; } public DateTime ValidAfter { get; private set; } public DateTime ValidBefore { get; private set; } public RSAKey PubKey { get; private set; } public bool SelfSigned { get; private set; } public DistinguishedName Subject { get; private set; } public DistinguishedName Issuer { get; private set; } private Asn1Node TbsCertificate; public Asn1Node Signature { get; private set; } public byte[] rawTBSCertificate; public X509Cert(Asn1Node n) { ParseNode(n); } public X509Cert(byte[] data) { using (var stm = new System.IO.MemoryStream(data)) { Asn1Parser parser = new Asn1Parser(); parser.LoadData(stm); ParseNode(parser.RootNode); } } public bool CheckCertTime(DateTime time) { return time.CompareTo(ValidAfter) >= 0 && time.CompareTo(ValidBefore) <= 0; } public bool CheckSignature(X509Cert signer) { if (Issuer.Equals(signer.Subject)) { return VerifySignatureWithSha256OrSha1(signer); } return false; } bool VerifySignatureWithSha256OrSha1(X509Cert signer) { if (signer.PubKey.VerifySha256(rawTBSCertificate, Signature.Data)) { return true; } return signer.PubKey.VerifySha1(rawTBSCertificate, Signature.Data); } public bool CheckSignatureSha256(X509Cert signer) { if (Issuer.Equals(signer.Subject)) { return signer.PubKey.VerifySha256(rawTBSCertificate, Signature.Data); } return false; } private void ParseNode(Asn1Node root) { if ((root.Tag & Asn1Tag.TAG_MASK) != Asn1Tag.SEQUENCE || root.ChildNodeCount != 3) throw new InvalidX509Data(); // TBS cert TbsCertificate = root.GetChildNode(0); if (TbsCertificate.ChildNodeCount < 7) throw new InvalidX509Data(); rawTBSCertificate = new byte[TbsCertificate.DataLength + 4]; Array.Copy(root.Data, 0, rawTBSCertificate, 0, rawTBSCertificate.Length); // get the serial number Asn1Node sn = TbsCertificate.GetChildNode(1); if ((sn.Tag & Asn1Tag.TAG_MASK) != Asn1Tag.INTEGER) throw new InvalidX509Data(); SerialNumber = Asn1Util.ToHexString(sn.Data); // get the issuer Issuer = new DistinguishedName(TbsCertificate.GetChildNode(3)); // get the subject Subject = new DistinguishedName(TbsCertificate.GetChildNode(5)); // get the dates Asn1Node validTimes = TbsCertificate.GetChildNode(4); if ((validTimes.Tag & Asn1Tag.TAG_MASK) != Asn1Tag.SEQUENCE || validTimes.ChildNodeCount != 2) throw new InvalidX509Data(); ValidAfter = ParseTime(validTimes.GetChildNode(0)); ValidBefore = ParseTime(validTimes.GetChildNode(1)); // is this self signed? SelfSigned = Subject.Equals(Issuer); // get the pub key PubKey = new RSAKey(TbsCertificate.GetChildNode(6)); // set the tbs cert & signature data for signature verification Signature = root.GetChildNode(2); } /** * According to rfc5280, time should be specified in GMT: * https://tools.ietf.org/html/rfc5280#section-4.1.2.5 */ private DateTime ParseTime(Asn1Node n) { string time = (new System.Text.UTF8Encoding()).GetString(n.Data); if (!(time.Length == 13 || time.Length == 15)) throw new InvalidTimeFormat(); // only accept Zulu time if (time[time.Length - 1] != 'Z') throw new InvalidTimeFormat(); int curIdx = 0; int year = 0; if (time.Length == 13) { year = Int32.Parse(time.Substring(0, 2)); if (year >= 50) year += 1900; else if (year < 50) year += 2000; curIdx += 2; } else { year = Int32.Parse(time.Substring(0, 4)); curIdx += 4; } int month = Int32.Parse(time.Substring(curIdx, 2)); curIdx += 2; int dom = Int32.Parse(time.Substring(curIdx, 2)); curIdx += 2; int hour = Int32.Parse(time.Substring(curIdx, 2)); curIdx += 2; int min = Int32.Parse(time.Substring(curIdx, 2)); curIdx += 2; int secs = Int32.Parse(time.Substring(curIdx, 2)); curIdx += 2; return new DateTime(year, month, dom, hour, min, secs, DateTimeKind.Utc); } } /// /// An IAP Security exception indicating some invalid time format. /// public class InvalidTimeFormat : IAPSecurityException { } /// /// An IAP Security exception indicating some invalid data for X509 certification checks. /// public class InvalidX509Data : IAPSecurityException { } }