/// <summary> /// Downloads all new files and replaces old files in directory. /// </summary> /// <param name="info">Update information generated by <c>FetchUpdateInfoAsync</c></param> /// <returns>True on success, false on failure.</returns> /// <exception cref="KangarupException">On a file checksum error.</exception> public async Task <bool> UpdateNewFilesAsync(UpdateInfo info) { if (UpdateUri == null) { throw new ArgumentNullException("UpdateUri cannot be null"); } var httpClient = new HttpClient(); var tempPath = Path.GetTempPath(); var tempFolder = Directory.CreateDirectory(Path.Combine(tempPath, TempFolderName)); foreach (var infoFile in info.Files) { var downloadStream = await httpClient.GetStreamAsync(UpdateUri.Append(infoFile.RelativeUrl)).ConfigureAwait(false); var fileToReplace = Path.GetFileName(infoFile.RelativeUrl); try { // download file var tmpFile = Path.GetTempFileName(); using (var fileStream = File.Create(tmpFile)) { await downloadStream.CopyToAsync(fileStream).ConfigureAwait(false); } // verify file var sha256 = SHA256.Create(); using (var inputStream = File.OpenRead(tmpFile)) { var hash = sha256.ComputeHash(inputStream); var hashHex = BitConverter.ToString(hash).Replace("-", string.Empty); if (infoFile.Checksum != hashHex) { // checksum wrong throw new KangarupException($"Checksum mismatch of file '{fileToReplace}'."); } } // place file File.Move(fileToReplace, Path.Combine(tempFolder.FullName, $"{fileToReplace}")); File.Move(tmpFile, fileToReplace); } catch (IOException e) { Logger?.Error($"Unable to update file '{fileToReplace}': {e.Message}", e); return(false); } //File.Delete($"{fileToReplace}.old"); // TODO not possible on running application } return(true); }
/// <summary> /// Retrieves latest update information from server and verifies cryptographic signature. /// </summary> /// <exception cref="KangarupException">Cryptographic signature does not match update manifest</exception> /// <exception cref="ArgumentNullException">UpdateUri is null</exception> /// <returns>Latest update information.</returns> public async Task <UpdateInfo> FetchUpdateInfoAsync() { if (UpdateUri == null) { throw new ArgumentNullException("UpdateUri cannot be null"); } if (_rsa == null) { LoadCertificate(); } // download update manifest var httpClient = new HttpClient(); try { var rawUpdateInfo = await httpClient.GetByteArrayAsync( UpdateUri.Append("update.yml")); // download update manifest signature var signature = await httpClient.GetByteArrayAsync(UpdateUri.Append("update.sig")); // verify manifest if (!_rsa.VerifyData(rawUpdateInfo, signature, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1)) { throw new KangarupException("Update manifest signature is wrong."); } // construct UpdateInfo object from data var deserializer = new Deserializer(); return(deserializer.Deserialize <UpdateInfo>(Encoding.UTF8.GetString(rawUpdateInfo))); } catch (HttpRequestException e) { Logger?.Error("Unable to contact update server.", e); return(null); } }