using System; using System.Collections; using System.Collections.Generic; using LipingShare.LCLib.Asn1Processor; using System.Security.Cryptography; namespace UnityEngine.Purchasing.Security { internal class PKCS7 { private Asn1Node root; public Asn1Node data { get; private set; } public List sinfos { get; private set; } public List certChain { get; private set; } private bool validStructure; public static PKCS7 Load(byte[] data) { using (var stm = new System.IO.MemoryStream(data)) { Asn1Parser parser = new Asn1Parser(); parser.LoadData(stm); return new PKCS7(parser.RootNode); } } public PKCS7(Asn1Node node) { this.root = node; CheckStructure(); } public bool Verify(X509Cert cert, DateTime certificateCreationTime) { if (validStructure) { bool ok = true; foreach (var sinfo in sinfos) { X509Cert signCert = FindSignCert(sinfo); if (signCert != null && signCert.PubKey != null) { ok = ok && signCert.CheckCertTime(certificateCreationTime); if (IsStoreKitSimulatorData()) { ok = ok && signCert.PubKey.VerifySha256(data.GetChildNode(0).Data, sinfo.EncryptedDigest); ok = ok && ValidateStoreKitSimulatorCertRoot(cert, signCert); } else { ok = ok && VerifyPublicKeyWithSha256OrSha1(signCert, sinfo); ok = ok && ValidateChain(cert, signCert, certificateCreationTime); } } } return ok && sinfos.Count > 0; } return false; } X509Cert FindSignCert(SignerInfo sinfo) { foreach (var cert in certChain) { if (cert.SerialNumber == sinfo.IssuerSerialNumber) { return cert; } } return null; } bool IsStoreKitSimulatorData() { return data.IsIndefiniteLength && data.ChildNodeCount == 1; } bool VerifyPublicKeyWithSha256OrSha1(X509Cert signCert, SignerInfo sinfo) { if (signCert.PubKey.VerifySha256(data.Data, sinfo.EncryptedDigest)) { return true; } return signCert.PubKey.VerifySha1(data.Data, sinfo.EncryptedDigest); } static bool ValidateStoreKitSimulatorCertRoot(X509Cert root, X509Cert cert) { return cert.CheckSignatureSha256(root); } private bool ValidateChain(X509Cert root, X509Cert cert, DateTime certificateCreationTime) { if (cert.Issuer.Equals(root.Subject)) { return cert.CheckSignature(root); } /** * TODO: improve this logic */ foreach (var c in certChain) { if (c != cert && c.Subject.Equals(cert.Issuer) && c.CheckCertTime(certificateCreationTime)) { if (c.Issuer.Equals(root.Subject) && c.SerialNumber == root.SerialNumber) { return c.CheckSignature(root); } else { // cert was issued by c if (cert.CheckSignature(c)) { return ValidateChain(root, c, certificateCreationTime); } } } } return false; } private void CheckStructure() { validStructure = false; if ((root.Tag & Asn1Tag.TAG_MASK) == Asn1Tag.SEQUENCE && root.ChildNodeCount == 2) { Asn1Node tt = root.GetChildNode(0); if ((tt.Tag & Asn1Tag.TAG_MASK) != Asn1Tag.OBJECT_IDENTIFIER || tt.GetDataStr(false) != "1.2.840.113549.1.7.2") { throw new InvalidPKCS7Data(); } tt = root.GetChildNode(1); // [0] if (tt.ChildNodeCount != 1) throw new InvalidPKCS7Data(); int curChild = 0; tt = tt.GetChildNode(curChild++); // Seq if (tt.ChildNodeCount < 4 || (tt.Tag & Asn1Tag.TAG_MASK) != Asn1Tag.SEQUENCE) throw new InvalidPKCS7Data(); Asn1Node tt2 = tt.GetChildNode(0); // version if ((tt2.Tag & Asn1Tag.TAG_MASK) != Asn1Tag.INTEGER) throw new InvalidPKCS7Data(); tt2 = tt.GetChildNode(curChild++); // digest algo // TODO: check algo if ((tt2.Tag & Asn1Tag.TAG_MASK) != Asn1Tag.SET) throw new InvalidPKCS7Data(); tt2 = tt.GetChildNode(curChild++); // pkcs7 data if ((tt2.Tag & Asn1Tag.TAG_MASK) != Asn1Tag.SEQUENCE && tt2.ChildNodeCount != 2) throw new InvalidPKCS7Data(); data = tt2.GetChildNode(1).GetChildNode(0); if (tt.ChildNodeCount == 5) { // cert chain, this is optional certChain = new List(); tt2 = tt.GetChildNode(curChild++); if (tt2.ChildNodeCount == 0) throw new InvalidPKCS7Data(); for (int i = 0; i < tt2.ChildNodeCount; i++) { certChain.Add(new X509Cert(tt2.GetChildNode(i))); } } tt2 = tt.GetChildNode(curChild++); // signer's info if ((tt2.Tag & Asn1Tag.TAG_MASK) != Asn1Tag.SET || tt2.ChildNodeCount == 0) throw new InvalidPKCS7Data(); sinfos = new List(); for (int i = 0; i < tt2.ChildNodeCount; i++) { sinfos.Add(new SignerInfo(tt2.GetChildNode(i))); } validStructure = true; } } } internal class SignerInfo { public int Version { get; private set; } public string IssuerSerialNumber { get; private set; } public byte[] EncryptedDigest { get; private set; } public SignerInfo(Asn1Node n) { if (n.ChildNodeCount != 5) throw new InvalidPKCS7Data(); Asn1Node tt; // version tt = n.GetChildNode(0); if ((tt.Tag & Asn1Tag.TAG_MASK) != Asn1Tag.INTEGER) throw new InvalidPKCS7Data(); Version = tt.Data[0]; if (Version != 1 || tt.Data.Length != 1) throw new UnsupportedSignerInfoVersion(); // get the issuer SN tt = n.GetChildNode(1); if ((tt.Tag & Asn1Tag.TAG_MASK) != Asn1Tag.SEQUENCE || tt.ChildNodeCount != 2) throw new InvalidPKCS7Data(); tt = tt.GetChildNode(1); if ((tt.Tag & Asn1Tag.TAG_MASK) != Asn1Tag.INTEGER) throw new InvalidPKCS7Data(); IssuerSerialNumber = Asn1Util.ToHexString(tt.Data); // get the data tt = n.GetChildNode(4); if ((tt.Tag & Asn1Tag.TAG_MASK) != Asn1Tag.OCTET_STRING) throw new InvalidPKCS7Data(); EncryptedDigest = tt.Data; } } /// /// An IAP Security exception indicating some invalid data for PKCS7 checks. /// public class InvalidPKCS7Data : IAPSecurityException { } /// /// An IAP Security exception indicating unsupported signer information. /// public class UnsupportedSignerInfoVersion : IAPSecurityException { } }