/// <summary> /// Sign an existing package /// </summary> public int Sign() { try { AppletPackage pkg = null; using (FileStream fs = File.OpenRead(this.m_parms.Source)) pkg = AppletPackage.Load(fs); Emit.Message("INFO", "Will sign package {0}", pkg.Meta); pkg = this.CreateSignedPackage(pkg.Unpack()); using (FileStream fs = File.Create(this.m_parms.Output ?? Path.ChangeExtension(this.m_parms.Source, ".signed.pak"))) pkg.Save(fs); return(0); } catch (Exception e) { Emit.Message("ERROR", "Cannot sign package: {0}", e); return(-0232); } }
/// <summary> /// Sign an existing package /// </summary> private static int Sign(ConsoleParameters parameters) { try { AppletPackage pkg = null; using (FileStream fs = File.OpenRead(parameters.Source)) pkg = AppletPackage.Load(fs); Console.WriteLine("Will sign package {0}", pkg.Meta); pkg = CreateSignedPackage(pkg.Unpack(), parameters); using (FileStream fs = File.Create(parameters.Output ?? Path.ChangeExtension(parameters.Source, ".signed.pak"))) pkg.Save(fs); return(0); } catch (Exception e) { Console.Error.WriteLine("Cannot sign package: {0}", e); return(-0232); } }
/// <summary> /// Compose multiple PAK files into a solution /// </summary> public int Compose() { try { AppletManifest mfst = null; using (FileStream fs = File.OpenRead(this.m_parms.Source)) mfst = AppletManifest.Load(fs); var slnPak = mfst.CreatePackage(); AppletSolution sln = new AppletSolution(); sln.Meta = slnPak.Meta; sln.PublicKey = slnPak.PublicKey; sln.Manifest = slnPak.Manifest; if (sln.Meta.Uuid == Guid.Empty) { Emit.Message("WARN", "The package does not carry a UUID! You should add a UUID to your solution manifest"); } sln.Include = new List <AppletPackage>(); foreach (var pfile in sln.Meta.Dependencies.ToArray()) { AppletPackage pkg = null; if (!String.IsNullOrEmpty(pfile.Version)) // specific version { pkg = PackageRepositoryUtil.GetFromAny(pfile.Id, new Version(pfile.Version)); } else if (!String.IsNullOrEmpty(m_parms.Version)) { pkg = PackageRepositoryUtil.GetFromAny(pfile.Id, new Version(m_parms.Version)) ?? PackageRepositoryUtil.GetFromAny(pfile.Id, null); } else { pkg = PackageRepositoryUtil.GetFromAny(pfile.Id, null); } if (pkg == null) { throw new KeyNotFoundException($"Package {pfile.Id} ({pfile.Version ?? m_parms.Version ?? "latest"}) not found"); } else { Emit.Message("INFO", "Including {0} version {1}..", pkg.Meta.Id, pkg.Meta.Version); sln.Meta.Dependencies.RemoveAll(o => o.Id == pkg.Meta.Id); if (this.m_parms.Sign && pkg.Meta.Signature == null) { Emit.Message("WARN", "Package {0} is not signed, but you're signing your package. We'll sign it using your key", pkg.Meta.Id); pkg = new Signer(this.m_parms).CreateSignedPackage(pkg.Unpack()); } sln.Include.Add(pkg); } } // Emit i18n file? if (!String.IsNullOrEmpty(this.m_parms.InternationalizationFile)) { Emit.Message("INFO", $"Writing string manifest to {this.m_parms.InternationalizationFile}"); using (var fs = File.Create(this.m_parms.InternationalizationFile)) using (var tw = new StreamWriter(fs, System.Text.Encoding.UTF8)) { // tx translations var mfsts = sln.Include.Select(o => o.Unpack()).ToList(); var appletStrings = mfsts.SelectMany(o => o.Strings).ToArray(); var stringKeys = appletStrings.SelectMany(o => o.String).Select(o => o.Key).Distinct(); var langs = appletStrings.Select(o => o.Language).Distinct().ToArray(); tw.Write("key,"); tw.WriteLine(String.Join(",", langs)); foreach (var str in stringKeys) { tw.Write($"{str},"); foreach (var lang in langs) { tw.Write($"\"{appletStrings.Where(o => o.Language == lang).SelectMany(s => s.String).FirstOrDefault(o => o.Key == str)?.Value}\","); } tw.WriteLine(); } } } sln.Meta.Hash = SHA256.Create().ComputeHash(sln.Include.SelectMany(o => o.Manifest).ToArray()); // Sign the signature package if (this.m_parms.Sign) { new Signer(this.m_parms).CreateSignedSolution(sln); } // Now save using (FileStream fs = File.Create(this.m_parms.Output ?? Path.ChangeExtension(sln.Meta.Id, ".sln.pak"))) sln.Save(fs); return(0); } catch (System.Exception e) { Emit.Message("ERROR", e.Message); //Console.Error.WriteLine("Cannot compose solution {0}: {1}", this.m_parms.Source, e); return(-1); } }
/// <summary> /// Performs an installation /// </summary> public virtual bool Install(AppletPackage package, bool isUpgrade = false) { this.m_tracer.TraceWarning("Installing {0}", package.Meta); // TODO: Verify package hash / signature if (!this.VerifyPackage(package)) { throw new SecurityException("Applet failed validation"); } else if (!this.m_appletCollection.VerifyDependencies(package.Meta)) { this.m_tracer.TraceWarning($"Applet {package.Meta} depends on : [{String.Join(", ", package.Meta.Dependencies.Select(o => o.ToString()))}] which are missing or incompatible"); } var appletSection = ApplicationContext.Current.Configuration.GetSection <AppletConfigurationSection>(); String appletPath = Path.Combine(appletSection.AppletDirectory, package.Meta.Id); try { // Desearialize an prep for install this.m_tracer.TraceInfo("Installing applet {0} (IsUpgrade={1})", package.Meta, isUpgrade); ApplicationContext.Current.SetProgress(package.Meta.GetName("en"), 0.0f); // TODO: Verify the package // Copy if (!Directory.Exists(appletSection.AppletDirectory)) { Directory.CreateDirectory(appletSection.AppletDirectory); } if (File.Exists(appletPath)) { if (!isUpgrade) { throw new InvalidOperationException(Strings.err_duplicate_package_name); } // Unload the loaded applet version var existingApplet = this.m_appletCollection.FirstOrDefault(o => o.Info.Id == package.Meta.Id); if (existingApplet != null) { this.UnInstallInternal(existingApplet); } } var mfst = package.Unpack(); // Migrate data. if (mfst.DataSetup != null) { foreach (var itm in mfst.DataSetup.Action) { Type idpType = typeof(IDataPersistenceService <>); idpType = idpType.MakeGenericType(new Type[] { itm.Element.GetType() }); var svc = ApplicationContext.Current.GetService(idpType); idpType.GetMethod(itm.ActionName).Invoke(svc, new object[] { itm.Element, TransactionMode.Commit, AuthenticationContext.SystemPrincipal }); } } // Now export all the binary files out var assetDirectory = Path.Combine(appletSection.AppletDirectory, "assets", mfst.Info.Id); if (!Directory.Exists(assetDirectory)) { Directory.CreateDirectory(assetDirectory); } else { Directory.Delete(assetDirectory, true); } for (int i = 0; i < mfst.Assets.Count; i++) { var itm = mfst.Assets[i]; var itmPath = Path.Combine(assetDirectory, itm.Name); ApplicationContext.Current.SetProgress($"Installing {package.Meta.GetName("en")}", 0.1f + (float)(0.8 * (float)i / mfst.Assets.Count)); // Get dir name and create if (!Directory.Exists(Path.GetDirectoryName(itmPath))) { Directory.CreateDirectory(Path.GetDirectoryName(itmPath)); } // Extract content if (itm.Content is byte[]) { if (Encoding.UTF8.GetString(itm.Content as byte[], 0, 4) == "LZIP") { using (var fs = File.Create(itmPath)) using (var ms = new MemoryStream(itm.Content as byte[])) using (var lzs = new LZipStream(new NonDisposingStream(ms), SharpCompress.Compressors.CompressionMode.Decompress)) lzs.CopyTo(fs); } else { File.WriteAllBytes(itmPath, itm.Content as byte[]); } itm.Content = null; } else if (itm.Content is String) { File.WriteAllText(itmPath, itm.Content as String); itm.Content = null; } } // Serialize the data to disk using (FileStream fs = File.Create(appletPath)) mfst.Save(fs); // For now sign with SHA256 SHA256 sha = SHA256.Create(); package.Meta.Hash = sha.ComputeHash(File.ReadAllBytes(appletPath)); // HACK: Re-re-remove appletSection.Applets.RemoveAll(o => o.Id == package.Meta.Id); appletSection.Applets.Add(package.Meta.AsReference()); ApplicationContext.Current.SetProgress(package.Meta.GetName("en"), 0.98f); if (ApplicationContext.Current.ConfigurationPersister.IsConfigured) { ApplicationContext.Current.ConfigurationPersister.Save(ApplicationContext.Current.Configuration); } this.LoadApplet(mfst); } catch (Exception e) { this.m_tracer.TraceError("Error installing applet {0} : {1}", package.Meta.ToString(), e); // Remove if (File.Exists(appletPath)) { File.Delete(appletPath); } throw; } return(true); }
/// <summary> /// Load external references /// </summary> private static void LoadReferences(MiniApplicationContext context, StringCollection references) { var appService = context.GetService <IAppletManagerService>(); // Load references foreach (var appletInfo in references)// Directory.GetFiles(this.m_configuration.GetSection<AppletConfigurationSection>().AppletDirectory)) { { try { context.m_tracer.TraceInfo("Loading applet {0}", appletInfo); String appletPath = appletInfo; // Is there a pak extension? if (Path.GetExtension(appletPath) != ".pak") { appletPath += ".pak"; } if (!Path.IsPathRooted(appletInfo)) { appletPath = Path.Combine(Environment.CurrentDirectory, appletPath); } // Does the reference exist in the current directory? if (!File.Exists(appletPath)) { appletPath = Path.Combine(Path.GetDirectoryName(Assembly.GetEntryAssembly().Location), Path.GetFileName(appletPath)); } AppletPackage package = null; if (!File.Exists(appletPath)) // Fetch from Repo { Console.WriteLine("Attempting to locate {0}", appletInfo); var data = appletInfo.Split(';'); if (data.Length == 1) { package = PakMan.Repository.PackageRepositoryUtil.GetFromAny(appletInfo, null); } else { package = PakMan.Repository.PackageRepositoryUtil.GetFromAny(data[0], new Version(data[1])); } } else { Console.WriteLine("Including {0}...", appletPath); using (var fs = File.OpenRead(appletPath)) { package = AppletPackage.Load(fs); } } if (package == null) { throw new InvalidOperationException($"Cannot find reference {appletInfo}"); } if (package is AppletSolution) { // Look for other applets with this foreach (var itm in (package as AppletSolution).Include) { context.m_tracer.TraceInfo("Loading solution content project {0}", itm.Meta.Id); appService.LoadApplet(itm.Unpack()); } } else { context.m_tracer.TraceInfo("Loading {0} v{1}", package.Meta.Id, package.Meta.Version); // Is this applet in the allowed applets appService.LoadApplet(package.Unpack()); } } catch (Exception e) { context.m_tracer.TraceError("Loading applet {0} failed: {1}", appletInfo, e.ToString()); throw; } } }
/// <summary> /// Verify package signature /// </summary> private bool VerifyPackage(AppletPackage package) { byte[] verifyBytes = package.Manifest; // First check: Hash - Make sure the HASH is ok if (package is AppletSolution) { verifyBytes = (package as AppletSolution).Include.SelectMany(o => o.Manifest).ToArray(); if (BitConverter.ToString(SHA256.Create().ComputeHash(verifyBytes)) != BitConverter.ToString(package.Meta.Hash)) { throw new InvalidOperationException($"Package contents of {package.Meta.Id} appear to be corrupt!"); } } else if (BitConverter.ToString(SHA256.Create().ComputeHash(package.Manifest)) != BitConverter.ToString(package.Meta.Hash)) { throw new InvalidOperationException($"Package contents of {package.Meta.Id} appear to be corrupt!"); } if (package.Meta.Signature != null) { this.m_tracer.TraceInfo("Will verify package {0}", package.Meta.Id.ToString()); // Get the public key var x509Store = new X509Store(StoreName.TrustedPublisher, StoreLocation.LocalMachine); try { x509Store.Open(OpenFlags.ReadOnly); var cert = x509Store.Certificates.Find(X509FindType.FindByThumbprint, package.Meta.PublicKeyToken, false); if (cert.Count == 0) { if (package.PublicKey != null) { // Embedded cert and trusted CA X509Certificate2 embCert = new X509Certificate2(package.PublicKey); // Not explicitly trusted to we need to build a chain if (!this.m_caIssuerCert.Subject.Equals(embCert.Issuer) && !this.m_configuration.TrustedPublishers.Contains(embCert.Thumbprint)) { // Build the certificate chain var embChain = new X509Chain(); embChain.Build(embCert); // Validate the chain elements if possible bool isTrusted = false; foreach (var itm in embChain.ChainElements) { isTrusted |= this.m_configuration.TrustedPublishers.Contains(itm.Certificate.Thumbprint); } if (!isTrusted || embChain.ChainStatus.Any(o => o.Status != X509ChainStatusFlags.RevocationStatusUnknown) && !this.m_configuration.TrustedPublishers.Contains(embCert.Issuer)) { throw new SecurityException($"Cannot verify identity of publisher {embCert.Subject}"); } else { cert = new X509Certificate2Collection(embCert); } } else { cert = new X509Certificate2Collection(embCert); } } else { throw new SecurityException($"Cannot find public key of publisher information for {package.Meta.PublicKeyToken} or the local certificate is invalid"); } } // Verify signature RSACryptoServiceProvider rsa = cert[0].PublicKey.Key as RSACryptoServiceProvider; var retVal = rsa.VerifyData(verifyBytes, CryptoConfig.MapNameToOID("SHA1"), package.Meta.Signature); // Verify timestamp var timestamp = package.Unpack().Info.TimeStamp; if (timestamp > DateTime.Now) { throw new SecurityException($"Package {package.Meta.Id} was published in the future! Something's fishy, refusing to load"); } else if (cert[0].NotAfter <timestamp || cert[0].NotBefore> timestamp) { throw new SecurityException($"Cannot find public key of publisher information for {package.Meta.PublicKeyToken} or the local certificate is invalid"); } if (retVal == true) { this.m_tracer.TraceEvent(EventLevel.Informational, "SUCCESSFULLY VALIDATED: {0} v.{1}\r\n" + "\tKEY TOKEN: {2}\r\n" + "\tSIGNED BY: {3}\r\n" + "\tVALIDITY: {4:yyyy-MMM-dd} - {5:yyyy-MMM-dd}\r\n" + "\tISSUER: {6}", package.Meta.Id, package.Meta.Version, cert[0].Thumbprint, cert[0].Subject, cert[0].NotBefore, cert[0].NotAfter, cert[0].Issuer); } else { this.m_tracer.TraceEvent(EventLevel.Critical, ">> SECURITY ALERT : {0} v.{1} <<\r\n" + "\tPACKAGE HAS BEEN TAMPERED WITH\r\n" + "\tKEY TOKEN (CLAIMED): {2}\r\n" + "\tSIGNED BY (CLAIMED): {3}\r\n" + "\tVALIDITY: {4:yyyy-MMM-dd} - {5:yyyy-MMM-dd}\r\n" + "\tISSUER: {6}\r\n\tSERVICE WILL HALT", package.Meta.Id, package.Meta.Version, cert[0].Thumbprint, cert[0].Subject, cert[0].NotBefore, cert[0].NotAfter, cert[0].Issuer); } return(retVal); } finally { x509Store.Close(); } } else if (this.m_configuration.AllowUnsignedApplets) { this.m_tracer.TraceEvent(EventLevel.Warning, "Package {0} v.{1} (publisher: {2}) is not signed. To prevent unsigned applets from being installed disable the configuration option", package.Meta.Id, package.Meta.Version, package.Meta.Author); return(true); } else { this.m_tracer.TraceEvent(EventLevel.Critical, "Package {0} v.{1} (publisher: {2}) is not signed and cannot be installed", package.Meta.Id, package.Meta.Version, package.Meta.Author); return(false); } }
/// <summary> /// Performs an installation /// </summary> public bool Install(AppletPackage package, bool isUpgrade, AppletSolution owner) { this.m_tracer.TraceInfo("Installing {0}", package.Meta); var appletScope = owner?.Meta.Id ?? String.Empty; // TODO: Verify package hash / signature if (!this.VerifyPackage(package)) { throw new SecurityException("Applet failed validation"); } else if (!this.m_appletCollection[appletScope].VerifyDependencies(package.Meta)) { this.m_tracer.TraceWarning($"Applet {package.Meta} depends on : [{String.Join(", ", package.Meta.Dependencies.Select(o => o.ToString()))}] which are missing or incompatible"); } // Save the applet var appletDir = this.m_configuration.AppletDirectory; if (!Path.IsPathRooted(appletDir)) { appletDir = Path.Combine(Path.GetDirectoryName(Assembly.GetEntryAssembly().Location), this.m_configuration.AppletDirectory); } if (owner != null) { appletDir = Path.Combine(appletDir, owner.Meta.Id); } if (!Directory.Exists(appletDir)) { Directory.CreateDirectory(appletDir); } // Install var pakFile = Path.Combine(appletDir, package.Meta.Id + ".pak"); if (this.m_appletCollection[appletScope].Any(o => o.Info.Id == package.Meta.Id) && File.Exists(pakFile) && !isUpgrade) { throw new InvalidOperationException($"Cannot replace {package.Meta} unless upgrade is specifically specified"); } using (var fs = File.Create(pakFile)) { package.Save(fs); } lock (this.m_fileDictionary) if (!this.m_fileDictionary.ContainsKey($"{appletScope}{package.Meta.Id}")) { this.m_fileDictionary.Add($"{appletScope}{package.Meta.Id}", pakFile); } var pkg = package.Unpack(); // remove the package from the collection if this is an upgrade if (isUpgrade) { this.m_appletCollection[appletScope].Remove(pkg); } this.m_appletCollection[appletScope].Add(pkg); // We want to install the templates & protocols into the DB this.m_tracer.TraceInfo("Installing templates..."); // Install templates var idp = ApplicationServiceContext.Current.GetService <ITemplateDefinitionRepositoryService>(); if (idp != null) { foreach (var itm in pkg.Templates) { if (idp.GetTemplateDefinition(itm.Mnemonic) == null) { this.m_tracer.TraceInfo("Installing {0}...", itm.Mnemonic); idp.Insert(new TemplateDefinition() { Oid = itm.Oid, Mnemonic = itm.Mnemonic, Description = itm.Description, Name = itm.Mnemonic }); } } } AppletCollection.ClearCaches(); return(true); }