/// <summary> /// Конструктор для создания копии контейнера /// </summary> /// <param name="c"></param> public CertInfo(CertInfo c) { this._OwnerC = c.OwnerC; this._FullInfo = c.FullInfo; this._OwnerCN = c.OwnerCN; this._OwnerEmail = c.OwnerEmail; this._OwnerL = c.OwnerL; this._OwnerO = c.OwnerO; this._OwnerOU = c.OwnerOU; this._OwnerST = c.OwnerST; this._pemPath = c.pemPath; this._pk8Path = c.pk8Path; this._SerialNumber = c.SerialNumber; }
/// <summary> /// Считывает информацию о текущей подписи из ZIP-архива /// </summary> /// <param name="fPath">Путь до файла (*.zip, *.apk, *.jar)</param> /// <returns></returns> public static CertInfo ReadZip(string fPath) { string cFile = Path.Combine(Environment.TempPath, Path.GetFileName(fPath) + "_CERT.RSA"); ZipFile zf = null; FileStream fs = null; try { fs = File.OpenRead(fPath); zf = new ZipFile(fs); } catch { return(null); } //ищем файл подписи bool IsExtracted = false; foreach (ZipEntry zipEntry in zf) { if (!zipEntry.IsFile) { continue; } if (zipEntry.Name == "META-INF/CERT.RSA") { Environment.Log(string.Format("Найден файл подписи, извлекаем его в \"{0}\"...", cFile)); //Если нашли, извлекаем его во временную директорию byte[] buffer = new byte[4096]; Stream zipStream = zf.GetInputStream(zipEntry); using (FileStream streamWriter = File.Create(cFile)) StreamUtils.Copy(zipStream, streamWriter, buffer); IsExtracted = true; } } if (!IsExtracted) { Environment.Log("Файл подписи не найден"); return(null); } CertInfo cInfo = ReadCert(cFile, false); try { File.Delete(cFile); } catch (Exception ex) { Environment.Log(string.Format("Не удалось удалить временный файл \"{0}\". Ошибка: {1}", cFile, ex.Message)); } return(cInfo); }
/// <summary> /// Парсинг информации о подписи из keytool /// </summary> /// <param name="Data">Выходные данные утилиты keytool</param> /// <returns>Структура с информацией о подписи</returns> public static CertInfo Parse(string Data) { //Извлекаем серийный номер string sNum = null; foreach (string line in Data.Split('\n')) if (line.Contains(keyToolLine)) { sNum = line.Replace(keyToolLine, string.Empty); break; } if (sNum == null) return null; //Если не нашли, создаем новый и добавляем его в коллекцию CertInfo cInfo = new CertInfo() { SerialNumber = sNum, FullInfo = Data }; //Извлекаем инфу о владельце Regex rx_EMAIL = new Regex("(EMAILADDRESS=)(.*?)(,|$)", RegexOptions.IgnoreCase | RegexOptions.Singleline); Regex rx_CN = new Regex("(CN=)(.*?)(,|$)", RegexOptions.IgnoreCase | RegexOptions.Singleline); Regex rx_OU = new Regex("(OU=)(.*?)(,|$)", RegexOptions.IgnoreCase | RegexOptions.Singleline); Regex rx_O = new Regex("(O=)(.*?)(,|$)", RegexOptions.IgnoreCase | RegexOptions.Singleline); Regex rx_L = new Regex("(L=)(.*?)(,|$)", RegexOptions.IgnoreCase | RegexOptions.Singleline); Regex rx_ST = new Regex("(ST=)(.*?)(,|$)", RegexOptions.IgnoreCase | RegexOptions.Singleline); Regex rx_C = new Regex("(C=)(.*?)(,|$)", RegexOptions.IgnoreCase | RegexOptions.Singleline); Match m; foreach (string line in cInfo.FullInfo.Split('\n')) { if (line.Contains("Owner: ")) { m = rx_EMAIL.Match(line); if (m.Success) cInfo.OwnerEmail = m.Groups[2].ToString(); m = rx_CN.Match(line); if (m.Success) cInfo.OwnerCN = m.Groups[2].ToString(); m = rx_OU.Match(line); if (m.Success) cInfo.OwnerOU = m.Groups[2].ToString(); m = rx_O.Match(line); if (m.Success) cInfo.OwnerO = m.Groups[2].ToString(); m = rx_L.Match(line); if (m.Success) cInfo.OwnerL = m.Groups[2].ToString(); m = rx_ST.Match(line); if (m.Success) cInfo.OwnerST = m.Groups[2].ToString(); m = rx_C.Match(line); if (m.Success) cInfo.OwnerC = m.Groups[2].ToString(); } } return cInfo; }
/// <summary> /// Парсинг информации о подписи из keytool /// </summary> /// <param name="Data">Выходные данные утилиты keytool</param> /// <returns>Структура с информацией о подписи</returns> public static CertInfo Parse(string Data) { //Извлекаем серийный номер string sNum = null; foreach (string line in Data.Split('\n')) { if (line.Contains(keyToolLine)) { sNum = line.Replace(keyToolLine, string.Empty); break; } } if (sNum == null) { return(null); } //Если не нашли, создаем новый и добавляем его в коллекцию CertInfo cInfo = new CertInfo() { SerialNumber = sNum, FullInfo = Data }; //Извлекаем инфу о владельце Regex rx_EMAIL = new Regex("(EMAILADDRESS=)(.*?)(,|$)", RegexOptions.IgnoreCase | RegexOptions.Singleline); Regex rx_CN = new Regex("(CN=)(.*?)(,|$)", RegexOptions.IgnoreCase | RegexOptions.Singleline); Regex rx_OU = new Regex("(OU=)(.*?)(,|$)", RegexOptions.IgnoreCase | RegexOptions.Singleline); Regex rx_O = new Regex("(O=)(.*?)(,|$)", RegexOptions.IgnoreCase | RegexOptions.Singleline); Regex rx_L = new Regex("(L=)(.*?)(,|$)", RegexOptions.IgnoreCase | RegexOptions.Singleline); Regex rx_ST = new Regex("(ST=)(.*?)(,|$)", RegexOptions.IgnoreCase | RegexOptions.Singleline); Regex rx_C = new Regex("(C=)(.*?)(,|$)", RegexOptions.IgnoreCase | RegexOptions.Singleline); Match m; foreach (string line in cInfo.FullInfo.Split('\n')) { if (line.Contains("Owner: ")) { m = rx_EMAIL.Match(line); if (m.Success) { cInfo.OwnerEmail = m.Groups[2].ToString(); } m = rx_CN.Match(line); if (m.Success) { cInfo.OwnerCN = m.Groups[2].ToString(); } m = rx_OU.Match(line); if (m.Success) { cInfo.OwnerOU = m.Groups[2].ToString(); } m = rx_O.Match(line); if (m.Success) { cInfo.OwnerO = m.Groups[2].ToString(); } m = rx_L.Match(line); if (m.Success) { cInfo.OwnerL = m.Groups[2].ToString(); } m = rx_ST.Match(line); if (m.Success) { cInfo.OwnerST = m.Groups[2].ToString(); } m = rx_C.Match(line); if (m.Success) { cInfo.OwnerC = m.Groups[2].ToString(); } } } return(cInfo); }
/// <summary> /// Считывает информацию о текущей подписи из файла сертификата (*.pem или *.RSA) /// </summary> /// <param name="fPath">Путь до файла (*.pem или *.RSA)</param> /// <returns></returns> public static CertInfo ReadCert(string fPath, bool IsPk8Required) { if (!File.Exists(fPath)) { return(null); } //Получаем вывод keytool string ktOutput = null; try { Environment.ktProc.StartInfo.Arguments = string.Format(Environment.keytoolArgs, fPath); Environment.Log(string.Format("Запускаем \"{0}\" с аргументами \"{1}\"", Environment.ktProc.StartInfo.FileName, Environment.ktProc.StartInfo.Arguments)); Environment.ktProc.Start(); ktOutput = Environment.ktProc.StandardOutput.ReadToEnd(); Environment.ktProc.WaitForExit(); } catch (Exception ex) { Environment.Log(string.Format("Запуск не удался. Ошибка: \"{0}\"", ex.Message)); return(null); } //Если вывод пуст - фейл if (string.IsNullOrEmpty(ktOutput)) { return(null); } //Парсим CertInfo cInfo = CertInfo.Parse(ktOutput); if (cInfo == null) { return(null); } //Проверяем наличие рядом файла pk8. Если есть - добавляем пути, тем самым помечаем пригодным для подписания string pk8File = Path.Combine(Path.GetDirectoryName(fPath), fPath.Substring(0, fPath.IndexOf('.')) + ".pk8"); if (File.Exists(pk8File)) { Environment.Log(string.Format("Найден pk8 \"{0}\". Добавляем как пригодный для подписи", pk8File)); cInfo.pemPath = fPath; cInfo.pk8Path = pk8File; CertInfo cnInfoStored = SignCerts.Find(c => (c.SerialNumber == cInfo.SerialNumber && cInfo.pemPath == cInfo.pemPath && c.pk8Path == cInfo.pk8Path)); if (cnInfoStored == null) { SignCerts.Add(cInfo); return(cInfo); } else { return(cnInfoStored); } } else { if (IsPk8Required) { Environment.Log(string.Format("Требуется pk8 \"{0}\", однако он не был найден.", pk8File)); return(null); } } //Ищем подпись в коллекции CertInfo cInfoStored = Certs.Find(c => (c.SerialNumber == cInfo.SerialNumber)); if (cInfoStored == null) { Certs.Add(cInfo); return(cInfo); } else { return(cInfoStored); } }
/// <summary> /// Основная процедура подписывания файлов /// </summary> /// <param name="sender"></param> /// <param name="e"></param> void bwSignFiles_DoWork(object sender, DoWorkEventArgs e) { //Блокируем интерфейс Block(true); StartButtonMode(true); int ErrorCounter = 0; int FileNum = 0; int FileCount = 0; foreach (PackageInfo pInfo in pReader.Packages) if (pInfo.NewCert.CanSign) FileCount++; foreach (PackageInfo pInfo in pReader.Packages) { // ================================================================================== //Показываем статусную строку и скроллим к итему this.Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Normal, new Action(delegate() { PackagesLW.ScrollIntoView(pInfo); pInfo.IsComboBoxVisible = false; })); //Пропускаем файлы с явно неуказанными сертификатами if (!pInfo.NewCert.CanSign) { Environment.Log(string.Format("У файла \"{0}\" не выбран пригодный для подписи сертификат. Пропуск...", pInfo.Name)); this.Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Normal, new Action(delegate() { pInfo.StatusText = STR_INLINE_SKIP; })); continue; } // ================================================================================== //Устанавливаем статус подписи pInfo.StatusText = STR_INLINE_SIGNING; SetStatus(string.Format(STR_STATUS_SIGNING_FILE, pInfo.Path), FileNum, FileCount, false); //Подписываем string SignedFile = Signer.SignFile(pInfo); //Если нулл или пустая - фейл if (string.IsNullOrEmpty(SignedFile)) { ErrorCounter++; this.Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Normal, new Action(delegate() { pInfo.StatusText = STR_INLINE_SIGNING_ERROR; })); continue; } //Также если файла нет - фейл if (!File.Exists(SignedFile)) { ErrorCounter++; this.Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Normal, new Action(delegate() { pInfo.StatusText = STR_INLINE_SIGNING_ERROR; })); continue; } // ================================================================================== //Устанавливаем статус алигнинга pInfo.StatusText = STR_INLINE_ALIGNING; SetStatus(string.Format(STR_STATUS_ALIGNING_FILE, pInfo.Path), FileNum, FileCount, false); //Зипалигним string SignedAlignedFile = Aligner.AlignFile(SignedFile); //Если нулл или пустая - фейл if (string.IsNullOrEmpty(SignedAlignedFile)) { ErrorCounter++; this.Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Normal, new Action(delegate() { pInfo.StatusText = STR_INLINE_ALIGNING_ERROR; })); continue; } //Также если файла нет - фейл if (!File.Exists(SignedAlignedFile)) { ErrorCounter++; this.Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Normal, new Action(delegate() { pInfo.StatusText = STR_INLINE_ALIGNING_ERROR; })); continue; } // ================================================================================== //Устанавливаем статус копирования pInfo.StatusText = STR_INLINE_COPYING; SetStatus(string.Format(STR_STATUS_COPYING_FILE, pInfo.Path), FileNum, FileCount, false); Environment.Log(string.Format("Копируем временный файл \"{0}\" на исходное место \"{1}\"...", SignedAlignedFile, pInfo.Path)); //Копируем на место try { File.Copy(SignedAlignedFile, pInfo.Path, true); } catch (Exception ex) { ErrorCounter++; Environment.Log(string.Format("Не удается скопировать файл \"{0}\" Ошибка: {1}", SignedAlignedFile, ex.Message)); } // ================================================================================== //Устанавливаем статус очистки pInfo.StatusText = STR_INLINE_CLEANING; //удаляем мусор try { File.Delete(SignedFile); File.Delete(SignedAlignedFile); } catch (Exception ex) { Environment.Log(string.Format("Не удается удалить временные файлы. Ошибка: {0}", ex.Message)); } this.Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Normal, new Action(delegate() { pInfo.StatusText = STR_INLINE_DONE; })); FileNum++; if (bwSignFiles.CancellationPending) { Environment.Log("Вызвана остановка операции"); break; } } if (ErrorCounter > 0) { if (File.Exists(Environment.logFile)) { if (MessageBox.Show("Обработка некоторых файлов потерпела неудачу. Хотите посмотреть логи?", "Обработка некоторых файлов потерпела неудачу", MessageBoxButton.YesNo, MessageBoxImage.Question) == MessageBoxResult.Yes) { this.Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Normal, new Action(delegate() { CertInfoWindow ErrorInfo = new CertInfoWindow(File.ReadAllText(Environment.logFile), "Лог"); ErrorInfo.Show(); })); } } } else if (IsZip && !bwSignFiles.CancellationPending) //Если указан зип и не сказано остановиться - пакуемся { //Спрашиваем куда сохранить bool result = false; this.Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Normal, new Action(delegate() { SaveZipBrowser.FileName = Path.GetFileNameWithoutExtension(ZipFile) + "_signed"; result = SaveZipBrowser.ShowDialog() == System.Windows.Forms.DialogResult.OK; })); if (result) { //Получаем выбранный сертификат CertInfo SignCert = new CertInfo(); this.Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Normal, new Action(delegate() { SignCert = ((CertInfo)ZipSign.SelectedValue); })); //И смотри требуется ли подпись if (SignCert.CanSign) { //Если нужно, запаковываем сперва во временную папку string tempZip = Path.Combine(Environment.TempPath, Path.GetFileNameWithoutExtension(SaveZipBrowser.FileName) + "_zipunsigned.zip"); PackZIP(ZipRootPath, tempZip); //Создаем экземпляр пакета PackageInfo zipPackage = new PackageInfo(); zipPackage.Path = tempZip; zipPackage.IsChangeAllowed = true; zipPackage.NewCert = SignCert; //Устанавливаем статус подписи SetStatus(string.Format(STR_STATUS_SIGNING_FILE, SaveZipBrowser.FileName), 100, 100, true); //Подписываем пакет уже по указанному ранее пути Signer.SignFile(zipPackage, SaveZipBrowser.FileName); //Устанавливаем статус очистки SetStatus(STR_INLINE_CLEANING, 100, 100, true); //Удаляем мусор try { File.Delete(tempZip); } catch { } } else //Если подписывать не нужно, сразу запаковываем по указанному пути PackZIP(ZipRootPath, SaveZipBrowser.FileName); } } this.Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Normal, new Action(delegate() { pReader.ShowComboBoxes(); })); SetStatus(STR_STATUS_DONE, 0, 100, false); Block(false); StartButtonMode(false); }