This is an automated email from the ASF dual-hosted git repository. matthiasblaesing pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/netbeans.git
The following commit(s) were added to refs/heads/master by this push: new 017f672 Allow ".external" files to define message digest values for download verification new ad77f83 Merge pull request #1843 from matthiasblaesing/external-files 017f672 is described below commit 017f6725b5c507b9cdc28ebd1b3170585b9b0e7a Author: Matthias Bläsing <mblaes...@doppel-helix.eu> AuthorDate: Sun Oct 6 19:54:04 2019 +0200 Allow ".external" files to define message digest values for download verification This formalises the format of the ".external" files and introduces a new content line, that allows specification of a message digest algorithm and the digest value, that results from applying the message digest to the referenced URL. At download time the message digest value is calculated from the downloaded data and compared. If it does not match, an exception is raised. Multiple message digest can be provided and message digests with unsupported algorithms are ignored. This allows for future evolution, when new algorithms are added and older ones are phased out. --- .../external/javafx-base-13-linux.jar.external | 2 + .../external/javafx-controls-13-linux.jar.external | 2 + .../external/javafx-graphics-13-linux.jar.external | 2 + .../external/javafx-media-13-linux.jar.external | 4 +- .../external/javafx-swing-13-linux.jar.external | 2 + .../external/javafx-web-13-linux.jar.external | 4 +- .../external/javafx-base-13-mac.jar.external | 2 + .../external/javafx-controls-13-mac.jar.external | 2 + .../external/javafx-graphics-13-mac.jar.external | 2 + .../external/javafx-media-13-mac.jar.external | 4 +- .../external/javafx-swing-13-mac.jar.external | 2 + .../external/javafx-web-13-mac.jar.external | 4 +- .../external/javafx-base-13-win.jar.external | 2 + .../external/javafx-controls-13-win.jar.external | 2 + .../external/javafx-graphics-13-win.jar.external | 2 + .../external/javafx-media-13-win.jar.external | 2 + .../external/javafx-swing-13-win.jar.external | 2 + .../external/javafx-web-13-win.jar.external | 2 + .../modules/ext/nb-javac-13-api.jar.external | 2 + .../modules/ext/nb-javac-13-impl.jar.external | 2 + .../modules/autoupdate/services/ExternalFile.java | 203 +++++++++++++++++++++ .../autoupdate/services/InstallSupportImpl.java | 160 +++++++--------- .../services/MessageChecksumValidator.java | 93 ++++++++++ .../autoupdate/services/MessageDigestChecker.java | 152 --------------- .../services/MessageDigestValidator.java | 104 +++++++++++ .../autoupdate/services/MessageMultiValidator.java | 68 +++++++ .../autoupdate/services/MessageValidator.java | 62 +++++++ .../modules/autoupdate/services/Utilities.java | 21 ++- .../autoupdate/services/NbmExternalTest.java | 117 +++++++++++- ...geDigestCheckerTest.java => UtilitiesTest.java} | 13 +- 30 files changed, 778 insertions(+), 263 deletions(-) diff --git a/extra/libs.javafx.linux/external/javafx-base-13-linux.jar.external b/extra/libs.javafx.linux/external/javafx-base-13-linux.jar.external index 0590fc6..7606aae 100644 --- a/extra/libs.javafx.linux/external/javafx-base-13-linux.jar.external +++ b/extra/libs.javafx.linux/external/javafx-base-13-linux.jar.external @@ -1,2 +1,4 @@ CRC: 2492020953 URL: m2:/org.openjfx:javafx-base:13:jar:linux +MessageDigest: SHA-256 a7f93cbc67bbf33cb7553b2b793637e69ccc3d6f9af30f5b03877f67aa1dcedb +MessageDigest: SHA-512 8388e08d7f513810ad5c3e5b5d28eeb6bfc112d8198369f84fa73e65dd3e882a2e9980906ed3be2feb5693bacb98320db2362106eea8561a0a7cc078a20e9a4c \ No newline at end of file diff --git a/extra/libs.javafx.linux/external/javafx-controls-13-linux.jar.external b/extra/libs.javafx.linux/external/javafx-controls-13-linux.jar.external index 752a89e..30e1ece 100644 --- a/extra/libs.javafx.linux/external/javafx-controls-13-linux.jar.external +++ b/extra/libs.javafx.linux/external/javafx-controls-13-linux.jar.external @@ -1,2 +1,4 @@ CRC: 1135077984 URL: m2:/org.openjfx:javafx-controls:13:jar:linux +MessageDigest: SHA-256 a9cf849eaa4be2a83f59eb107d402fbba7d916d39344b629d65f3a31efdd9154 +MessageDigest: SHA-512 569ae75159eec27f849448debe701dfdc2a4ff63a31171b082e20ad3a2b8a2913c01fd876b76279f3a941fb8fec1731bca774bf14bf653879bec0cfbd9013948 \ No newline at end of file diff --git a/extra/libs.javafx.linux/external/javafx-graphics-13-linux.jar.external b/extra/libs.javafx.linux/external/javafx-graphics-13-linux.jar.external index a0c77ca..4a8666f 100644 --- a/extra/libs.javafx.linux/external/javafx-graphics-13-linux.jar.external +++ b/extra/libs.javafx.linux/external/javafx-graphics-13-linux.jar.external @@ -1,2 +1,4 @@ CRC: 336589674 URL: m2:/org.openjfx:javafx-graphics:13:jar:linux +MessageDigest: SHA-256 5ec91067317628a7e268b69bf4a7c28047aeec7954589b8b39140e76bed5b976 +MessageDigest: SHA-512 9f3fe57c4f4ff130c842d01e0e8a09d6dffcb00c88304a03cf56883942e41bb9a97818a0b8383a85ef1a467874b63ebed5d6d932b2de5cfaf24bceb9567a3e63 \ No newline at end of file diff --git a/extra/libs.javafx.linux/external/javafx-media-13-linux.jar.external b/extra/libs.javafx.linux/external/javafx-media-13-linux.jar.external index 41e87bf..b7c6788 100644 --- a/extra/libs.javafx.linux/external/javafx-media-13-linux.jar.external +++ b/extra/libs.javafx.linux/external/javafx-media-13-linux.jar.external @@ -1,2 +1,4 @@ CRC: 1287643879 -URL: m2:/org.openjfx:javafx-media:13:jar:linux \ No newline at end of file +URL: m2:/org.openjfx:javafx-media:13:jar:linux +MessageDigest: SHA-256 8cef8e89c711fd4a72bcb34e3652e7b8bb504f2c436852d5ff96625b3daf5f86 +MessageDigest: SHA-512 94cd178ec8aa4c94dd23baef695f72118e01fa1b902a6a019bbd7534294c90d19372f5f8b09dd0d4c2f46f87623ebc57948ad8e2a96bdf1097f1937285828f63 \ No newline at end of file diff --git a/extra/libs.javafx.linux/external/javafx-swing-13-linux.jar.external b/extra/libs.javafx.linux/external/javafx-swing-13-linux.jar.external index 802403b..7586d50 100644 --- a/extra/libs.javafx.linux/external/javafx-swing-13-linux.jar.external +++ b/extra/libs.javafx.linux/external/javafx-swing-13-linux.jar.external @@ -1,2 +1,4 @@ CRC: 2544041311 URL: m2:/org.openjfx:javafx-swing:13:jar:linux +MessageDigest: SHA-256 9a134bd4050b09c28bfe59bc1500b5f1c9302f7ebc67126a8cb43c3711a0eff0 +MessageDigest: SHA-512 529bfb9cb1c68a612535141b4518848f00fc10339a1838fa517d03437bc0b6685b68e211a3650d2205dbcc08f3222c2efc33b9382906aa29af9e36f715685fe3 \ No newline at end of file diff --git a/extra/libs.javafx.linux/external/javafx-web-13-linux.jar.external b/extra/libs.javafx.linux/external/javafx-web-13-linux.jar.external index a4bf1ba..13d66b5 100644 --- a/extra/libs.javafx.linux/external/javafx-web-13-linux.jar.external +++ b/extra/libs.javafx.linux/external/javafx-web-13-linux.jar.external @@ -1,2 +1,4 @@ CRC: 1028977454 -URL: m2:/org.openjfx:javafx-web:13:jar:linux \ No newline at end of file +URL: m2:/org.openjfx:javafx-web:13:jar:linux +MessageDigest: SHA-256 8cc1b6f4c1772bc68116e95576e7dcaed4663bec42bf89cd5cdd673bd09d303e +MessageDigest: SHA-512 3a3261bf4d695727ab84864fa1d8df21c8892fc4ec42024dccf09c2e79b47dadfb9bf8c123f50c9c178b6d3e393aed627cab297a0f564c504062ac42d2fb85e3 \ No newline at end of file diff --git a/extra/libs.javafx.macosx/external/javafx-base-13-mac.jar.external b/extra/libs.javafx.macosx/external/javafx-base-13-mac.jar.external index 4243df1..7c7b1eb 100644 --- a/extra/libs.javafx.macosx/external/javafx-base-13-mac.jar.external +++ b/extra/libs.javafx.macosx/external/javafx-base-13-mac.jar.external @@ -1,2 +1,4 @@ CRC: 2557569889 URL: m2:/org.openjfx:javafx-base:13:jar:mac +MessageDigest: SHA-256 11667e0e703ca32ad98ef214070dfb19756d7c819418d9349fc3926fdb48160c +MessageDigest: SHA-512 48bb7e76ae1a37f247ef9f93ea535b5c7e833b72abdb23258712892870c8a14e7bb05868158e12db6b943395afd73d1d18c54f83771d538b14bbc23fd33e8d08 \ No newline at end of file diff --git a/extra/libs.javafx.macosx/external/javafx-controls-13-mac.jar.external b/extra/libs.javafx.macosx/external/javafx-controls-13-mac.jar.external index a89579f..0751464 100644 --- a/extra/libs.javafx.macosx/external/javafx-controls-13-mac.jar.external +++ b/extra/libs.javafx.macosx/external/javafx-controls-13-mac.jar.external @@ -1,2 +1,4 @@ CRC: 1838405604 URL: m2:/org.openjfx:javafx-controls:13:jar:mac +MessageDigest: SHA-256 f8cf247dc25f5e5ebe246ecc79337d91ad54c2052be375c0fa8accb478fb3e01 +MessageDigest: SHA-512 92a3e9e670bf0cf07aecce8fdb8db14d4eece9d86110d8fe2151be2b2b1ea17296692de2dd6efc7645b21f2fc455de112aab7eb690227564456e3835b9f7a38a \ No newline at end of file diff --git a/extra/libs.javafx.macosx/external/javafx-graphics-13-mac.jar.external b/extra/libs.javafx.macosx/external/javafx-graphics-13-mac.jar.external index 14d8a85..58456ed 100644 --- a/extra/libs.javafx.macosx/external/javafx-graphics-13-mac.jar.external +++ b/extra/libs.javafx.macosx/external/javafx-graphics-13-mac.jar.external @@ -1,2 +1,4 @@ CRC: 1786347828 URL: m2:/org.openjfx:javafx-graphics:13:jar:mac +MessageDigest: SHA-256 bccaecc90fb0d9847a02870e596bd62d270a4bc49e7b93d31572def6cbd0a51b +MessageDigest: SHA-512 7d24e30b33211a95000d875fe6cf9086518931ba6f38eff268280979de2a20bd05fc70b538e81c1d804fa1914ebe0567bc2d2254b422dbfea3e66e4dcebf4e84 \ No newline at end of file diff --git a/extra/libs.javafx.macosx/external/javafx-media-13-mac.jar.external b/extra/libs.javafx.macosx/external/javafx-media-13-mac.jar.external index 7ff49c6..18eb034 100644 --- a/extra/libs.javafx.macosx/external/javafx-media-13-mac.jar.external +++ b/extra/libs.javafx.macosx/external/javafx-media-13-mac.jar.external @@ -1,2 +1,4 @@ CRC: 1123366205 -URL: m2:/org.openjfx:javafx-media:13:jar:mac \ No newline at end of file +URL: m2:/org.openjfx:javafx-media:13:jar:mac +MessageDigest: SHA-256 b260d1c7c1efbbbe61d071f543fb438d965f04e8ff9b2228f59cf8dd32d151b0 +MessageDigest: SHA-512 41556aab661c3e61935f12586818533581755c05e293a6a01bb9596e5f8aa1c029f771d8291e3714be13109fd37046c593984c8662b98e70f85e342e95c189de \ No newline at end of file diff --git a/extra/libs.javafx.macosx/external/javafx-swing-13-mac.jar.external b/extra/libs.javafx.macosx/external/javafx-swing-13-mac.jar.external index d348928..2988ced 100644 --- a/extra/libs.javafx.macosx/external/javafx-swing-13-mac.jar.external +++ b/extra/libs.javafx.macosx/external/javafx-swing-13-mac.jar.external @@ -1,2 +1,4 @@ CRC: 4273544274 URL: m2:/org.openjfx:javafx-swing:13:jar:mac +MessageDigest: SHA-256 b1cbaf075e275a5da73e85f63b69e9113aca80ea5ba3d31a2c33f3bfb224ef05 +MessageDigest: SHA-512 f761943138a81d1d7442a48c967856ac4f1a63ed811b3a8423f3a1cd29db22a014c14b81f37d1fbe3a0c87e8f0c69892484adefcae25fd921a4fb4d3fd5eb63f \ No newline at end of file diff --git a/extra/libs.javafx.macosx/external/javafx-web-13-mac.jar.external b/extra/libs.javafx.macosx/external/javafx-web-13-mac.jar.external index a5b193f..db4b83c 100644 --- a/extra/libs.javafx.macosx/external/javafx-web-13-mac.jar.external +++ b/extra/libs.javafx.macosx/external/javafx-web-13-mac.jar.external @@ -1,2 +1,4 @@ CRC: 115429231 -URL: m2:/org.openjfx:javafx-web:13:jar:mac \ No newline at end of file +URL: m2:/org.openjfx:javafx-web:13:jar:mac +MessageDigest: SHA-256 b4f46f4111572fc414606f0edc4e276f20bae86801fddc496d7879d65c7e6274 +MessageDigest: SHA-512 7d3e5c0c6df619776cc25027039ca9f85b465a6b081d9e846ca9aca14acdc19dbb660ce4896f5731644a679754da4856d9251df4746ada591ebb8c4ff64e4d5a \ No newline at end of file diff --git a/extra/libs.javafx.win/external/javafx-base-13-win.jar.external b/extra/libs.javafx.win/external/javafx-base-13-win.jar.external index 66361c2..7148b79 100644 --- a/extra/libs.javafx.win/external/javafx-base-13-win.jar.external +++ b/extra/libs.javafx.win/external/javafx-base-13-win.jar.external @@ -1,2 +1,4 @@ CRC:414360450 URL: m2:/org.openjfx:javafx-base:13:jar:win +MessageDigest: SHA-256 daf917895f7de0da6d4923ec937441936f2cfcfd04a32829cb6ddf3c2d148fdc +MessageDigest: SHA-512 5fd8ac72d02564310b3486e0354f20245442950a707f792c216b726a68d241b161ca169c28a6783ccc50532ab9a3742f580b4bfbcfd4f11b4c25a40a9f82d167 \ No newline at end of file diff --git a/extra/libs.javafx.win/external/javafx-controls-13-win.jar.external b/extra/libs.javafx.win/external/javafx-controls-13-win.jar.external index 6e70953..676c081 100644 --- a/extra/libs.javafx.win/external/javafx-controls-13-win.jar.external +++ b/extra/libs.javafx.win/external/javafx-controls-13-win.jar.external @@ -1,2 +1,4 @@ CRC:3243990194 URL: m2:/org.openjfx:javafx-controls:13:jar:win +MessageDigest: SHA-256 702d38ce2901a48ca54f7d23fd4d0b9e828a0cab9620d48c0e2f9ccfbf4365a3 +MessageDigest: SHA-512 70e4e26c35102c1b123ddef53b23b4f418a1732982c879aa9783086e9f672b0c5706463b8a32f4345333e114748d5b966c964c62ee636e1b6f0957f432b12e65 \ No newline at end of file diff --git a/extra/libs.javafx.win/external/javafx-graphics-13-win.jar.external b/extra/libs.javafx.win/external/javafx-graphics-13-win.jar.external index a88bbee..3d7131a 100644 --- a/extra/libs.javafx.win/external/javafx-graphics-13-win.jar.external +++ b/extra/libs.javafx.win/external/javafx-graphics-13-win.jar.external @@ -1,2 +1,4 @@ CRC: 651621548 URL: m2:/org.openjfx:javafx-graphics:13:jar:win +MessageDigest: SHA-256 566daf009e075bacbfacec6a7225f9aa589d0bf4ef3ca09cc677ff013374e2b1 +MessageDigest: SHA-512 1ac9175abfa57f93424aa32e9b66c2e6b22728bae024d26ead20debb23196b8591bff09c35b31dc2ff7a6e75ec6657c212d5d3b04a55569a7dfa1890645646e2 \ No newline at end of file diff --git a/extra/libs.javafx.win/external/javafx-media-13-win.jar.external b/extra/libs.javafx.win/external/javafx-media-13-win.jar.external index cfeebd6..e9fcfae 100644 --- a/extra/libs.javafx.win/external/javafx-media-13-win.jar.external +++ b/extra/libs.javafx.win/external/javafx-media-13-win.jar.external @@ -1,2 +1,4 @@ CRC: 2392976199 URL: m2:/org.openjfx:javafx-media:13:jar:win +MessageDigest: SHA-256 f6c1727a58967617641f5cfefd5084f82b07e85f3cdcb7a927aab566f95ff963 +MessageDigest: SHA-512 dbc94c133b8eea5e390dc837ff94737527fb18d5eef09e72ef6f73cbca7d6abb8eefdc022d505f77cc0fb6a77296e0a66bfe6d9a67a33f020935a6e26531650e \ No newline at end of file diff --git a/extra/libs.javafx.win/external/javafx-swing-13-win.jar.external b/extra/libs.javafx.win/external/javafx-swing-13-win.jar.external index 2ccbb3f..f3e04bc 100644 --- a/extra/libs.javafx.win/external/javafx-swing-13-win.jar.external +++ b/extra/libs.javafx.win/external/javafx-swing-13-win.jar.external @@ -1,2 +1,4 @@ CRC:3322528332 URL: m2:/org.openjfx:javafx-swing:13:jar:win +MessageDigest: SHA-256 fceb5cf99ef322bb84b50782996cb6d6c0053bffca0d1cc6a29818856050b29c +MessageDigest: SHA-512 7c03a952d4bef05a0499b092e4c60641c7b0c489b391b76fb0b687f13b4da03e22a6c6d50883a780cd613fff917673e3d22c68fdeba8f8d89ef0f7a674fc7717 \ No newline at end of file diff --git a/extra/libs.javafx.win/external/javafx-web-13-win.jar.external b/extra/libs.javafx.win/external/javafx-web-13-win.jar.external index 3542f84..0bcec94 100644 --- a/extra/libs.javafx.win/external/javafx-web-13-win.jar.external +++ b/extra/libs.javafx.win/external/javafx-web-13-win.jar.external @@ -1,2 +1,4 @@ CRC: 1813955042 URL: m2:/org.openjfx:javafx-web:13:jar:win +MessageDigest: SHA-256 98f1a68338d3f611cb845e30440d2169612426940df4e7e9a35bbf9444714daa +MessageDigest: SHA-512 7e5c46f3fa651beda05dbecdf5234ca230895ab7abf6b65c2c3e2c3f571ff8ebd1c93989777dbe635180ee08ae750ab17de1cfb4c3a8bc8aeff3c5e4c8f57605 \ No newline at end of file diff --git a/nb/updatecenters/extras/nbjavac.api/release/modules/ext/nb-javac-13-api.jar.external b/nb/updatecenters/extras/nbjavac.api/release/modules/ext/nb-javac-13-api.jar.external index d5c52ed..09a444a 100644 --- a/nb/updatecenters/extras/nbjavac.api/release/modules/ext/nb-javac-13-api.jar.external +++ b/nb/updatecenters/extras/nbjavac.api/release/modules/ext/nb-javac-13-api.jar.external @@ -2,3 +2,5 @@ CRC:520567768 SIZE:230395 URL:https://netbeans.osuosl.org/binaries/FC7D04B1F1AE92A87F113096862112BE7E6970D4-nb-javac-13-api.jar URL:https://hg.netbeans.org/binaries/FC7D04B1F1AE92A87F113096862112BE7E6970D4-nb-javac-13-api.jar +MessageDigest: SHA-256 eb36f3a1714010028d0da5c6be22cdb7b7cc8cc04d618351403a57c9b10adbfc +MessageDigest: SHA-512 bdf48bef2578cd66df8c3a95e6ad1dc022ee136806674dcf791f43829d050f20b565b367b47a5524fb8d62c0f58187a1e0317429526fd219f324033830ac3e57 \ No newline at end of file diff --git a/nb/updatecenters/extras/nbjavac.impl/release/modules/ext/nb-javac-13-impl.jar.external b/nb/updatecenters/extras/nbjavac.impl/release/modules/ext/nb-javac-13-impl.jar.external index a9fe059..e0f65a5 100644 --- a/nb/updatecenters/extras/nbjavac.impl/release/modules/ext/nb-javac-13-impl.jar.external +++ b/nb/updatecenters/extras/nbjavac.impl/release/modules/ext/nb-javac-13-impl.jar.external @@ -2,3 +2,5 @@ CRC:3482904810 SIZE:3595235 URL:https://netbeans.osuosl.org/binaries/34E9F9C1BDC61FE7EFCCF305D70960B862DE7815-nb-javac-13-impl.jar URL:https://hg.netbeans.org/binaries/34E9F9C1BDC61FE7EFCCF305D70960B862DE7815-nb-javac-13-impl.jar +MessageDigest: SHA-256 cabea2a079534191d09cba7bfe55c0de45079a04c8ac88926b09d76ed7067879 +MessageDigest: SHA-512 55feeefd61fda43e4a9b461f405e806a751ea35c713b7fc762f149565fd8780828fcce9d73d38edfa89176f00087220bd8b99ee5135f9f551fe40e2a8768ee83 \ No newline at end of file diff --git a/platform/autoupdate.services/src/org/netbeans/modules/autoupdate/services/ExternalFile.java b/platform/autoupdate.services/src/org/netbeans/modules/autoupdate/services/ExternalFile.java new file mode 100644 index 0000000..f7631e0 --- /dev/null +++ b/platform/autoupdate.services/src/org/netbeans/modules/autoupdate/services/ExternalFile.java @@ -0,0 +1,203 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.autoupdate.services; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.zip.CRC32; +import static org.netbeans.modules.autoupdate.services.Utilities.hexDecode; + +public class ExternalFile { + + private static final Logger LOG = Logger.getLogger(ExternalFile.class.getName()); + + /** + * Parse an ".external" file from supplied input stream. + * + * <p>The expected format is a UTF-8 encoded text file. The format is line + * oriented and contain the following items:</p> + * + * <ul> + * <li>lines with the format {@code # <characters>} are treated as a + * comments</li> + * <li>lines with the format {@code CRC:<integer>} represent a CRC32 + * checksum</li> + * <li>lines with the format {@code URL:<url>} represents one possible URL + * for download</li> + * <li>lines with the format {@code SIZE:<integer>} represent the + * size of the target file (currently unused)</li> + * <li>lines with the format {@code URL:<url>} represents one possible URL + * for download</li> + * <li>lines with the format + * {@code MessageDigest: <algorithm> <hexvalue>} represents one message + * digest for the data. {@code <algorithm>} is the message digest algorithm + * used and {@code <hexvalue>} the result of the digest algorithm applied + * to the data referenced by the URL(s) and hex encoded.</li> + * </ul> + * + * <p>If multiple {@code CRC} lines are found, the last value will be used, + * if multiple {@code URL} lines are found, all URLs are considered as + * possible download source. Multiple {@code MessageDigest} lines with + * different {@code algorithm} values will all be considered, if the same + * {@code algorithm} is found twice, only the last entry is considered.</p> + * + * <p>This method will not close the inputStream.</p> + * + * @param is + * @return + */ + public static ExternalFile fromStream(String name, InputStream is) throws IOException { + ExternalFile ext = new ExternalFile(); + ext.setName(name); + BufferedReader br = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8)); + for(String line = br.readLine(); line != null; line = br.readLine()) { + if(line.startsWith("#")) { + // Comment + } else if (line.startsWith("SIZE:")) { + ext.setSize(Integer.parseInt(line.substring(5).trim())); + } else if (line.startsWith("CRC:")) { + ext.setCrc32(Long.parseLong(line.substring(4).trim())); + } else if (line.startsWith("URL:")) { + String url = line.substring(4).trim(); + for (;;) { + int index = url.indexOf("${"); + if (index == -1) { + break; + } + int end = url.indexOf("}", index); + String propName = url.substring(index + 2, end); + final String propVal = System.getProperty(propName); + if (propVal == null) { + throw new IOException("Can't find property " + propName); + } + url = url.substring(0, index) + propVal + url.substring(end + 1); + } + ext.getModifiableUrls().add(url); + } else if (line.startsWith("MessageDigest:")) { + // Assume format: <JSSE_Implementation_Name> HexEncodedHashValue> + String[] parts = line.substring(14).trim().split("\\s+"); + if(parts.length == 2) { + try { + ext.getModifiableMessageDigests().put( + parts[0], + hexDecode(parts[1])); + } catch (IllegalArgumentException ex) { + LOG.log(Level.INFO, MessageFormat.format( + "Invalidly formatted MessageDigest line found in {1}: {0}", + new Object[]{line, name}), ex); + } + } else { + LOG.log(Level.INFO, + "Invalidly formatted MessageDigest line found in {1}: {0}", + new Object[]{line, name}); + } + } else if (! line.trim().isEmpty()) { + LOG.log(Level.INFO, "Invalid content found in {1}: {0}", + new Object[]{line, name}); + } + } + return ext; + } + + private String name; + private final List<String> urls = new ArrayList<>(); + private Long crc32 = null; + private Integer size = null; + private final Map<String,byte[]> messageDigest = new HashMap<>(); + + private ExternalFile() { + } + + public List<String> getUrls() { + return Collections.unmodifiableList(urls); + } + + public Map<String,byte[]> getMessageDigests() { + return Collections.unmodifiableMap(this.messageDigest); + } + + @SuppressWarnings("ReturnOfCollectionOrArrayField") + private List<String> getModifiableUrls() { + return urls; + } + + @SuppressWarnings("ReturnOfCollectionOrArrayField") + private Map<String,byte[]> getModifiableMessageDigests() { + return this.messageDigest; + } + + public Long getCrc32() { + return crc32; + } + + private void setCrc32(Long crc32) { + this.crc32 = crc32; + } + + public Integer getSize() { + return size; + } + + public void setSize(Integer size) { + this.size = size; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + /** + * @return validator, that checks the CRC32 value and all message digest + * values, that can be verified by the runtime JRE. Unsupported + * message digest values will be ignored + */ + public MessageMultiValidator getValidator() { + List<MessageValidator> validators = new ArrayList<>(2); + validators.add(new MessageChecksumValidator(new CRC32(), getCrc32())); + for(Entry<String,byte[]> entry: getMessageDigests().entrySet()) { + try { + validators.add(new MessageDigestValidator( + MessageDigest.getInstance(entry.getKey()), entry.getValue())); + } catch (NoSuchAlgorithmException ex) { + LOG.log(Level.INFO, + "Requested message digest {0} not found for {1}", + new Object[] {entry.getKey(), getName()}); + } + } + return new MessageMultiValidator(validators); + } +} diff --git a/platform/autoupdate.services/src/org/netbeans/modules/autoupdate/services/InstallSupportImpl.java b/platform/autoupdate.services/src/org/netbeans/modules/autoupdate/services/InstallSupportImpl.java index 61f7ba9..01b6822 100644 --- a/platform/autoupdate.services/src/org/netbeans/modules/autoupdate/services/InstallSupportImpl.java +++ b/platform/autoupdate.services/src/org/netbeans/modules/autoupdate/services/InstallSupportImpl.java @@ -35,12 +35,10 @@ import java.security.cert.Certificate; import java.security.cert.TrustAnchor; import java.util.*; import java.util.concurrent.*; -import java.util.concurrent.atomic.AtomicLong; import java.util.jar.JarEntry; import java.util.jar.JarFile; import java.util.logging.Level; import java.util.logging.Logger; -import java.util.zip.CRC32; import org.netbeans.Module; import org.netbeans.api.autoupdate.*; import org.netbeans.api.autoupdate.InstallSupport.Installer; @@ -49,7 +47,6 @@ import org.netbeans.api.autoupdate.OperationContainer.OperationInfo; import org.netbeans.api.autoupdate.OperationSupport.Restarter; import org.netbeans.api.progress.ProgressHandle; import org.netbeans.modules.autoupdate.updateprovider.AutoupdateInfoParser; -import org.netbeans.modules.autoupdate.updateprovider.MessageDigestValue; import org.netbeans.modules.autoupdate.updateprovider.ModuleItem; import org.netbeans.modules.autoupdate.updateprovider.NetworkAccess; import org.netbeans.modules.autoupdate.updateprovider.NetworkAccess.Task; @@ -718,52 +715,56 @@ public class InstallSupportImpl { } c = copy (source, dest, progress, toUpdateImpl.getDownloadSize (), aggregateDownload, totalSize, label); boolean wasException = false; - JarFile nbm = new JarFile(dest); - try { + try (JarFile nbm = new JarFile(dest)) { Enumeration<JarEntry> en = nbm.entries(); while (en.hasMoreElements()) { JarEntry jarEntry = en.nextElement(); if (jarEntry.getName().endsWith(".external")) { - InputStream is = nbm.getInputStream(jarEntry); - try { - AtomicLong crc = new AtomicLong(); - InputStream real = externalDownload(is, crc, jarEntry.getName()); - if (crc.get() == -1L) { - throw new IOException(jarEntry.getName() + " does not contain CRC: line!"); - } - byte[] arr = new byte[4096]; - CRC32 check = new CRC32(); - File external = new File(dest.getPath() + "." + Long.toHexString(crc.get())); - FileOutputStream fos = new FileOutputStream(external); - try { - for (;;) { - int len = real.read(arr); - if (len == -1) { - break; - } - check.update(arr, 0, len); - fos.write(arr, 0, len); - if (progressRunning) { - if ((c += len) <= toUpdateImpl.getDownloadSize()) { - progress.progress(aggregateDownload + c); - } + ExternalFile externalFile; + try(InputStream is = nbm.getInputStream(jarEntry)) { + externalFile = ExternalFile.fromStream(jarEntry.getName(), is); + } + if (externalFile.getCrc32() == null) { + throw new IOException(jarEntry.getName() + " does not contain CRC: line!"); + } + + MessageMultiValidator check = externalFile.getValidator(); + File external = new File(dest.getPath() + "." + Long.toHexString(externalFile.getCrc32())); + try ( + InputStream real = externalDownload(externalFile); + FileOutputStream fos = new FileOutputStream(external)) { + byte[] buffer = new byte[4096]; + for (;;) { + int len = real.read(buffer); + if (len == -1) { + break; + } + check.update(buffer, 0, len); + fos.write(buffer, 0, len); + if (progressRunning) { + if ((c += len) <= toUpdateImpl.getDownloadSize()) { + progress.progress(aggregateDownload + c); } } - } finally { - fos.close(); } - real.close(); - if (check.getValue() != crc.get()) { - LOG.log(Level.INFO, "Deleting file with uncomplete external content(cause: wrong CRC) " + normalized); - dest.delete(); - synchronized(downloadedFiles) { - downloadedFiles.remove(normalized); + } + + if (! check.isValid()) { + List<String> failedChecks = new ArrayList<>(); + for(MessageValidator v: check.getValidators()) { + if(! v.isValid()) { + failedChecks.add(v.getName()); } - external.delete(); - throw new IOException("Wrong CRC for " + jarEntry.getName()); + LOG.log(Level.INFO, + "Deleting file with invalid check {0}, expected {1}, got {2}: {3}", + new Object[] {v.getName(), v.getExpectedValueAsString(), v.getRealValueAsString(), normalized}); + } + dest.delete(); + synchronized (downloadedFiles) { + downloadedFiles.remove(normalized); } - } finally { - is.close(); + external.delete(); + throw new IOException("Failed checks " + failedChecks.toString() + " for " + jarEntry.getName()); } } } @@ -776,7 +777,6 @@ public class InstallSupportImpl { wasException = true; throw new OperationException(OperationException.ERROR_TYPE.PROXY, x.getLocalizedMessage()); } finally { - nbm.close(); if (wasException) { dest.delete(); } @@ -1094,7 +1094,7 @@ public class InstallSupportImpl { } { - MessageDigestChecker mdChecker = new MessageDigestChecker(impl.getMessageDigests()); + MessageMultiValidator mdChecker = MessageValidator.createFromMessageDigestValues(impl.getMessageDigests()); byte[] buffer = new byte[102400]; int read; try(FileInputStream fis = new FileInputStream(nbmFile)) { @@ -1102,18 +1102,19 @@ public class InstallSupportImpl { mdChecker.update(buffer, 0, read); } } - if(!mdChecker.validate()) { - for (String algorithm : mdChecker.getFailingHashes()) { + if(!mdChecker.isValid()) { + for (MessageValidator mv: mdChecker.getValidators()) { LOG.log(Level.INFO, - "Failed to validate message digest for ''{0}'' expected ''{1}'' got ''{2}''", + "Failed to validate message digest {3} for ''{0}'' expected ''{1}'' got ''{2}''", new Object[]{ nbmFile.getAbsolutePath(), - mdChecker.getExpectedHashAsString(algorithm), - mdChecker.getCalculatedHashAsString(algorithm) + mv.getExpectedValueAsString(), + mv.getRealValueAsString(), + mv.getName() }); } res = Utilities.MODIFIED; - } else if (mdChecker.isDigestAvailable() && impl.isCatalogTrusted()) { + } else if ((! mdChecker.getValidators().isEmpty()) && impl.isCatalogTrusted()) { res = Utilities.TRUSTED; } } @@ -1361,61 +1362,32 @@ public class InstallSupportImpl { } return es; } - - // copied from nbbuild/antsrc/org/netbeans/nbbuild/AutoUpdate.java: - private static InputStream externalDownload(InputStream is, AtomicLong crc, String pathTo) throws IOException { - BufferedReader br = new BufferedReader(new InputStreamReader(is)); + + private static InputStream externalDownload(ExternalFile ef) throws IOException { URLConnection conn; - crc.set(-1L); - String url = null; - String externalUrl = null; IOException ioe = null; - for (;;) { - String line = br.readLine(); - if (line == null) { - break; - } - if (line.startsWith("CRC:")) { - crc.set(Long.parseLong(line.substring(4).trim())); - } - if (line.startsWith("URL:")) { - url = line.substring(4).trim(); - for (;;) { - int index = url.indexOf("${"); - if (index == -1) { - break; - } - int end = url.indexOf("}", index); - String propName = url.substring(index + 2, end); - final String propVal = System.getProperty(propName); - if (propVal == null) { - throw new IOException("Can't find property " + propName); - } - url = url.substring(0, index) + propVal + url.substring(end + 1); - } - LOG.log(Level.INFO, "Trying external URL: {0}", url); - try { - conn = new URL(url).openConnection(); - conn.setConnectTimeout(AutoupdateSettings.getOpenConnectionTimeout()); - conn.setReadTimeout(AutoupdateSettings.getOpenConnectionTimeout()); - return conn.getInputStream(); - } catch (IOException ex) { - LOG.log(Level.WARNING, "Cannot connect to {0}", url); - LOG.log(Level.INFO, "Details", ex); - if (ex instanceof UnknownHostException || ex instanceof ConnectException || ex instanceof SocketTimeoutException) { - ioe = ex; - externalUrl = url; - } + for (String url : ef.getUrls()) { + LOG.log(Level.INFO, "Trying external URL: {0}", url); + try { + conn = new URL(url).openConnection(); + conn.setConnectTimeout(AutoupdateSettings.getOpenConnectionTimeout()); + conn.setReadTimeout(AutoupdateSettings.getOpenConnectionTimeout()); + return conn.getInputStream(); + } catch (IOException ex) { + LOG.log(Level.WARNING, "Cannot connect to {0}", url); + LOG.log(Level.INFO, "Details", ex); + if (ex instanceof UnknownHostException || ex instanceof ConnectException || ex instanceof SocketTimeoutException) { + ioe = ex; } } } if (ioe == null) { - throw new FileNotFoundException("Cannot resolve external reference to " + (url == null ? pathTo : url)); + throw new FileNotFoundException("Cannot resolve external reference to " + ef.getUrls()); } else { - throw new IOException("resolving external reference to " + (externalUrl == null ? pathTo : externalUrl)); + throw new IOException("resolving external reference to " + ef.getUrls()); } } - + private static class UpdaterInfo { private final JarEntry updaterJarEntry; private final File zipFileWithUpdater; diff --git a/platform/autoupdate.services/src/org/netbeans/modules/autoupdate/services/MessageChecksumValidator.java b/platform/autoupdate.services/src/org/netbeans/modules/autoupdate/services/MessageChecksumValidator.java new file mode 100644 index 0000000..782845a --- /dev/null +++ b/platform/autoupdate.services/src/org/netbeans/modules/autoupdate/services/MessageChecksumValidator.java @@ -0,0 +1,93 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.autoupdate.services; + +import java.util.Objects; +import java.util.zip.Checksum; + +public class MessageChecksumValidator implements MessageValidator { + + private final Checksum checksum; + private final long expectedValue; + private Long digestValue; + + public MessageChecksumValidator(Checksum checksum, long expectedValue) { + Objects.requireNonNull(checksum, "messageDigenst must not be NULL"); + Objects.requireNonNull(expectedValue, "expectedValue must not be NULL"); + this.checksum = checksum; + this.expectedValue = expectedValue; + } + + @Override + public void update(byte b) { + if (digestValue != null) { + throw new IllegalStateException("isValid was already called"); + } + checksum.update(b); + } + + @Override + public void update(byte[] b, int off, int len) { + if (digestValue != null) { + throw new IllegalStateException("isValid was already called"); + } + checksum.update(b, off, len); + } + + @Override + public void update(byte[] input) { + if (digestValue != null) { + throw new IllegalStateException("isValid was already called"); + } + update(input, 0, input.length); + } + + @Override + public boolean isValid() { + return getDigestValue() == expectedValue; + } + + private long getDigestValue() { + if (digestValue == null) { + digestValue = checksum.getValue(); + } + return digestValue; + } + + @Override + public String getName() { + return "Checksum " + checksum.getClass().getSimpleName(); + } + + @Override + public String getExpectedValueAsString() { + return Long.toString(expectedValue); + } + + @Override + public String getRealValueAsString() { + return Long.toString(checksum.getValue()); + } + + @Override + public void reset() { + checksum.reset(); + digestValue = null; + } +} diff --git a/platform/autoupdate.services/src/org/netbeans/modules/autoupdate/services/MessageDigestChecker.java b/platform/autoupdate.services/src/org/netbeans/modules/autoupdate/services/MessageDigestChecker.java deleted file mode 100644 index 53d3eaf..0000000 --- a/platform/autoupdate.services/src/org/netbeans/modules/autoupdate/services/MessageDigestChecker.java +++ /dev/null @@ -1,152 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -package org.netbeans.modules.autoupdate.services; - -import java.io.IOException; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.logging.Level; -import java.util.logging.Logger; -import org.netbeans.modules.autoupdate.updateprovider.MessageDigestValue; - -public class MessageDigestChecker { - - private static final Logger LOG = Logger.getLogger(MessageDigestChecker.class.getName()); - - private final Map<String,MessageDigest> messageDigest = new HashMap<>(); - private final Map<String,byte[]> exptectedResult = new HashMap<>(); - private final Map<String,byte[]> calculatedResult = new HashMap<>(); - private Boolean overallValid = null; - - public MessageDigestChecker(Collection<MessageDigestValue> referenceHashes) { - if (referenceHashes != null) { - for (MessageDigestValue h : referenceHashes) { - try { - MessageDigest md = MessageDigest.getInstance(h.getAlgorithm()); - messageDigest.put(h.getAlgorithm(), md); - exptectedResult.put(h.getAlgorithm(), hexDecode(h.getValue())); - } catch (NoSuchAlgorithmException ex) { - LOG.log(Level.FINE, "Unsupported Hash Algorithm: {0}", h.getAlgorithm()); - } - } - } - } - - public void update(byte[] data) { - ensureValidateNotCalled(); - for(MessageDigest md: messageDigest.values()) { - md.update(data); - } - } - - public void update(byte[] data, int offset, int len) { - ensureValidateNotCalled(); - for(MessageDigest md: messageDigest.values()) { - md.update(data, offset, len); - } - } - - public void update(byte data) { - ensureValidateNotCalled(); - for(MessageDigest md: messageDigest.values()) { - md.update(data); - } - } - - private void ensureValidateNotCalled() throws IllegalStateException { - if(overallValid != null) { - throw new IllegalStateException("update must not be called after validate is invoked"); - } - } - - private void ensureValidateCalled() throws IllegalStateException { - if(overallValid == null) { - throw new IllegalStateException("This method must not be called before validate is invoked"); - } - } - - public boolean isDigestAvailable() { - return ! messageDigest.isEmpty(); - } - - public boolean validate() throws IOException { - if (overallValid == null) { - overallValid = true; - for (Entry<String, MessageDigest> e : messageDigest.entrySet()) { - String algorithm = e.getKey(); - calculatedResult.put(algorithm, e.getValue().digest()); - boolean localValid = Arrays.equals( - exptectedResult.get(algorithm), - calculatedResult.get(algorithm)); - overallValid &= localValid; - } - } - return overallValid; - } - - public List<String> getFailingHashes() { - ensureValidateCalled(); - List<String> result = new ArrayList<>(); - for(String algorithm: messageDigest.keySet()) { - if(! Arrays.equals( - exptectedResult.get(algorithm), - calculatedResult.get(algorithm))) { - result.add(algorithm); - } - } - return result; - } - - public String getExpectedHashAsString(String algorithm) { - ensureValidateCalled(); - return hexEncode(exptectedResult.get(algorithm)); - } - - public String getCalculatedHashAsString(String algorithm) { - ensureValidateCalled(); - return hexEncode(calculatedResult.get(algorithm)); - } - - public static String hexEncode(byte[] input) { - StringBuilder sb = new StringBuilder(input.length * 2); - for(byte b: input) { - sb.append(Character.forDigit((b & 0xF0) >> 4, 16)); - sb.append(Character.forDigit((b & 0x0F), 16)); - } - return sb.toString(); - } - - public static byte[] hexDecode(String input) { - int length = input.length() / 2; - byte[] result = new byte[length]; - for(int i = 0; i < length; i++) { - int b = Character.digit(input.charAt(i * 2), 16) << 4; - b |= Character.digit(input.charAt(i * 2 + 1), 16); - result[i] = (byte) (b & 0xFF); - } - return result; - } -} diff --git a/platform/autoupdate.services/src/org/netbeans/modules/autoupdate/services/MessageDigestValidator.java b/platform/autoupdate.services/src/org/netbeans/modules/autoupdate/services/MessageDigestValidator.java new file mode 100644 index 0000000..3b506e9 --- /dev/null +++ b/platform/autoupdate.services/src/org/netbeans/modules/autoupdate/services/MessageDigestValidator.java @@ -0,0 +1,104 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.autoupdate.services; + +import java.security.MessageDigest; +import java.util.Arrays; +import java.util.Objects; +import org.netbeans.modules.autoupdate.services.Utilities; + +public class MessageDigestValidator implements MessageValidator { + + private final MessageDigest messageDigest; + private final byte[] expectedValue; + private byte[] digestValue; + + public MessageDigestValidator(MessageDigest messageDigest, byte[] expectedValue) { + Objects.requireNonNull(messageDigest, "messageDigenst must not be NULL"); + Objects.requireNonNull(expectedValue, "expectedValue must not be NULL"); + this.messageDigest = messageDigest; + this.expectedValue = expectedValue; + } + + @Override + public void update(byte input) { + if (digestValue != null) { + throw new IllegalStateException("isValid was already called"); + } + messageDigest.update(input); + } + + @Override + public void update(byte[] input, int offset, int len) { + if (digestValue != null) { + throw new IllegalStateException("isValid was already called"); + } + messageDigest.update(input, offset, len); + } + + @Override + public void update(byte[] input) { + if (digestValue != null) { + throw new IllegalStateException("isValid was already called"); + } + messageDigest.update(input); + } + + @Override + public void reset() { + digestValue = null; + messageDigest.reset(); + } + + @Override + public boolean isValid() { + return Arrays.equals(getDigestValue(), expectedValue); + } + + @SuppressWarnings("ReturnOfCollectionOrArrayField") + private byte[] getDigestValue() { + if (digestValue == null) { + digestValue = messageDigest.digest(); + } + return digestValue; + } + + @Override + public String getName() { + return "Message Digest (" + messageDigest.getAlgorithm() + ")"; + } + + @Override + public String getExpectedValueAsString() { + if (expectedValue == null) { + return ""; + } else { + return Utilities.hexEncode(expectedValue); + } + } + + @Override + public String getRealValueAsString() { + if (expectedValue == null) { + return ""; + } else { + return Utilities.hexEncode(getDigestValue()); + } + } +} diff --git a/platform/autoupdate.services/src/org/netbeans/modules/autoupdate/services/MessageMultiValidator.java b/platform/autoupdate.services/src/org/netbeans/modules/autoupdate/services/MessageMultiValidator.java new file mode 100644 index 0000000..6d72e7e --- /dev/null +++ b/platform/autoupdate.services/src/org/netbeans/modules/autoupdate/services/MessageMultiValidator.java @@ -0,0 +1,68 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.autoupdate.services; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +/** + * MessageMultiValidator combines multiple validators and is only valid if all + referenced validators report their checks as valid. + */ +public class MessageMultiValidator { + + private final List<MessageValidator> validators; + + public MessageMultiValidator(List<MessageValidator> validators) { + this.validators = validators; + } + + public MessageMultiValidator(MessageValidator... validators) { + this.validators = Arrays.asList(validators); + } + + public void update(byte[] input, int offset, int len) { + this.validators.stream().forEach(v -> v.update(input, offset, len)); + } + + public void update(byte[] input) { + this.validators.stream().forEach(v -> v.update(input)); + } + + public void update(byte input) { + this.validators.stream().forEach(v -> v.update(input)); + } + + public void reset() { + this.validators.stream().forEach(v -> v.reset()); + } + + public boolean isValid() { + return this.validators.stream() + .map(a -> a.isValid()) + .reduce(Boolean::logicalAnd) + .orElse(true); + } + + public Collection<MessageValidator> getValidators() { + return Collections.unmodifiableList(this.validators); + } +} diff --git a/platform/autoupdate.services/src/org/netbeans/modules/autoupdate/services/MessageValidator.java b/platform/autoupdate.services/src/org/netbeans/modules/autoupdate/services/MessageValidator.java new file mode 100644 index 0000000..33a29b8 --- /dev/null +++ b/platform/autoupdate.services/src/org/netbeans/modules/autoupdate/services/MessageValidator.java @@ -0,0 +1,62 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.autoupdate.services; + +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; +import org.netbeans.modules.autoupdate.updateprovider.MessageDigestValue; +import static org.netbeans.modules.autoupdate.services.Utilities.hexDecode; + +public interface MessageValidator { + static Logger LOG = Logger.getLogger(MessageValidator.class.getName()); + + static MessageMultiValidator createFromMessageDigestValues(Collection<MessageDigestValue> messageDigestValue) { + List<MessageValidator> validatorList = new ArrayList<>(); + for (MessageDigestValue h : messageDigestValue) { + try { + MessageDigest md = MessageDigest.getInstance(h.getAlgorithm()); + validatorList.add(new MessageDigestValidator(md, hexDecode(h.getValue()))); + } catch (NoSuchAlgorithmException ex) { + LOG.log(Level.FINE, "Unsupported Hash Algorithm: {0}", h.getAlgorithm()); + } + } + return new MessageMultiValidator(validatorList); + } + + public void update(byte[] input, int offset, int len); + + public void update(byte[] input); + + public void update(byte input); + + public void reset(); + + public boolean isValid(); + + public String getName(); + + public String getExpectedValueAsString(); + + public String getRealValueAsString(); +} diff --git a/platform/autoupdate.services/src/org/netbeans/modules/autoupdate/services/Utilities.java b/platform/autoupdate.services/src/org/netbeans/modules/autoupdate/services/Utilities.java index fdef5f2..e81afe1 100644 --- a/platform/autoupdate.services/src/org/netbeans/modules/autoupdate/services/Utilities.java +++ b/platform/autoupdate.services/src/org/netbeans/modules/autoupdate/services/Utilities.java @@ -1613,5 +1613,24 @@ public class Utilities { private static Preferences getPreferences() { return NbPreferences.root ().node ("/org/netbeans/modules/autoupdate"); // NOI18N } - + + public static String hexEncode(byte[] input) { + StringBuilder sb = new StringBuilder(input.length * 2); + for(byte b: input) { + sb.append(Character.forDigit((b & 0xF0) >> 4, 16)); + sb.append(Character.forDigit((b & 0x0F), 16)); + } + return sb.toString(); + } + + public static byte[] hexDecode(String input) { + int length = input.length() / 2; + byte[] result = new byte[length]; + for(int i = 0; i < length; i++) { + int b = Character.digit(input.charAt(i * 2), 16) << 4; + b |= Character.digit(input.charAt(i * 2 + 1), 16); + result[i] = (byte) (b & 0xFF); + } + return result; + } } diff --git a/platform/autoupdate.services/test/unit/src/org/netbeans/modules/autoupdate/services/NbmExternalTest.java b/platform/autoupdate.services/test/unit/src/org/netbeans/modules/autoupdate/services/NbmExternalTest.java index c651d47..e3c4828 100644 --- a/platform/autoupdate.services/test/unit/src/org/netbeans/modules/autoupdate/services/NbmExternalTest.java +++ b/platform/autoupdate.services/test/unit/src/org/netbeans/modules/autoupdate/services/NbmExternalTest.java @@ -18,6 +18,7 @@ */ package org.netbeans.modules.autoupdate.services; +import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; @@ -25,6 +26,8 @@ import java.io.IOException; import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.net.URL; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; import java.util.ArrayList; import java.util.List; import java.util.jar.Attributes; @@ -49,6 +52,7 @@ import org.netbeans.updater.UpdateTracking; import org.openide.filesystems.FileUtil; import org.openide.util.Lookup; import org.openide.util.Utilities; +import static org.netbeans.modules.autoupdate.services.Utilities.hexEncode; public class NbmExternalTest extends NbTestCase { @@ -160,7 +164,7 @@ public class NbmExternalTest extends NbTestCase { + "\n"; } - private File prepareNBM(String codeName, String releaseVersion, String implVersion, String specVersion, boolean visible, String dependency) throws Exception { + private File prepareNBM(String codeName, String releaseVersion, String implVersion, String specVersion, boolean visible, String dependency, Long overriddenCRC32, String messageDigestLine) throws Exception { String moduleName = codeName.substring(codeName.lastIndexOf(".") + 1); String moduleFile = codeName.replace(".", "-"); String moduleDir = codeName.replace(".", "/") + "/"; @@ -185,8 +189,17 @@ public class NbmExternalTest extends NbTestCase { jos.close(); File ext = new File(jar.getParentFile(), jar.getName() + ".external"); FileOutputStream os = new FileOutputStream(ext); - os.write(("CRC: " + UpdateTracking.getFileCRC(jar) + "\n").getBytes()); + if(overriddenCRC32 != null) { + os.write(("CRC: " + overriddenCRC32 + "\n").getBytes()); + } else { + os.write(("CRC: " + UpdateTracking.getFileCRC(jar) + "\n").getBytes()); + } os.write(("URL: " + Utilities.toURI(jar).toString() + "\n").getBytes()); + if(messageDigestLine != null) { + os.write(("MessageDigest: " + messageDigestLine + "\n").getBytes()); + } else { + os.write(("MessageDigest: SHA-1 " + digest(jar, "SHA-1") + "\n").getBytes()); + } os.close(); Manifest mf = new Manifest(); @@ -263,7 +276,7 @@ public class NbmExternalTest extends NbTestCase { String moduleImplVersion = "2"; String moduleSpecVersion = "1.0"; - prepareNBM(moduleCNB, moduleReleaseVersion, moduleImplVersion, moduleSpecVersion, false, null); + prepareNBM(moduleCNB, moduleReleaseVersion, moduleImplVersion, moduleSpecVersion, false, null, null, null); writeCatalog(); UpdateUnitProviderFactory.getDefault().refreshProviders(null, true); @@ -302,7 +315,88 @@ public class NbmExternalTest extends NbTestCase { } } - + public void testNbmWithExternalFailMessageDigest() throws Exception { + String moduleCNB = "org.netbeans.modules.mymodule2"; + String moduleReleaseVersion = "1"; + String moduleImplVersion = "2"; + String moduleSpecVersion = "1.0"; + + // Override message digest with an invalid SHA-1 sum - this must prevent + // the module from being installed + prepareNBM(moduleCNB, moduleReleaseVersion, moduleImplVersion, moduleSpecVersion, false, null, null, "SHA-1 5a7d7df655ba40478fae80a6abafc6afc36f9b6a"); + + writeCatalog(); + UpdateUnitProviderFactory.getDefault().refreshProviders(null, true); + OperationContainer<InstallSupport> installContainer = OperationContainer.createForInstall(); + UpdateUnit moduleUnit = getUpdateUnit(moduleCNB); + assertNull("cannot be installed", moduleUnit.getInstalled()); + UpdateElement moduleElement = getAvailableUpdate(moduleUnit, 0); + assertEquals(moduleElement.getSpecificationVersion(), moduleSpecVersion); + OperationInfo<InstallSupport> independentInfo = installContainer.add(moduleElement); + assertNotNull(independentInfo); + + try { + doInstall(installContainer); + } catch (OperationException ex) { + assertTrue(ex.getMessage().contains("Failed checks [Message Digest (SHA-1)]")); + return; + } + + fail("Expected operation exception was not raised."); + } + + public void testNbmWithExternalFailCRC32() throws Exception { + String moduleCNB = "org.netbeans.modules.mymodule3"; + String moduleReleaseVersion = "1"; + String moduleImplVersion = "2"; + String moduleSpecVersion = "1.0"; + + // Override CRC32 with an invalid CRC32 check sum - this must prevent + // the module from being installed + prepareNBM(moduleCNB, moduleReleaseVersion, moduleImplVersion, moduleSpecVersion, false, null, 1L, null); + + writeCatalog(); + UpdateUnitProviderFactory.getDefault().refreshProviders(null, true); + OperationContainer<InstallSupport> installContainer = OperationContainer.createForInstall(); + UpdateUnit moduleUnit = getUpdateUnit(moduleCNB); + assertNull("cannot be installed", moduleUnit.getInstalled()); + UpdateElement moduleElement = getAvailableUpdate(moduleUnit, 0); + assertEquals(moduleElement.getSpecificationVersion(), moduleSpecVersion); + OperationInfo<InstallSupport> independentInfo = installContainer.add(moduleElement); + assertNotNull(independentInfo); + + try { + doInstall(installContainer); + } catch (OperationException ex) { + assertTrue(ex.getMessage().contains("Failed checks [Checksum CRC32]")); + return; + } + + fail("Expected operation exception was not raised."); + } + + public void testExternalFileWithMultipleMessageDigest() throws IOException { + String externalFileString = + "URL: https://netbeans.apache.org/dummy\n" + + "CRC: 1\n" + + "MessageDigest: SHA-1 5a7d7df655ba40478fae80a6abafc6afc36f9b6a\n" + + "MessageDigest: SHA-256 8c6db340475136df3c1201d458fa5755698eace76e510471ecc9d857d6083dac\n" + + "MessageDigest: UNKNOWN 8c6db340475136df3c1201d458fa5755698eace76e510471ecc9d857d6083dac\n" + + "MessageDigest: UNKNOWN2 8c6db340475136df3c1201d458fa5755698eace76e510471ecc9d857d6083dac\n"; + byte[] externalFileData = externalFileString.getBytes(); + ExternalFile externalFile = ExternalFile + .fromStream("dummy.external", new ByteArrayInputStream(externalFileData)); + + assertNotNull(externalFile); + assertNotNull(externalFile.getCrc32()); + assertEquals(1L, (long) externalFile.getCrc32()); + // There are 4 message digests defined + assertEquals(4, externalFile.getMessageDigests().size()); + // The JDK supports 2 of the 4 defined messaged digests (SHA-1 and SHA-256) + // one additional validator is added for the CRC32, expected are so 3 + // validators + assertEquals(3, externalFile.getValidator().getValidators().size()); + } public UpdateUnit getUpdateUnit(String codeNameBase) { UpdateUnit uu = UpdateManagerImpl.getInstance().getUpdateUnit(codeNameBase); @@ -316,4 +410,19 @@ public class NbmExternalTest extends NbTestCase { return available.get(idx); } + + @SuppressWarnings("NestedAssignment") + private static String digest(File file, String algorithm) throws IOException { + try(FileInputStream fis = new FileInputStream(file)) { + MessageDigest md = MessageDigest.getInstance(algorithm); + byte[] buffer = new byte[102400]; + int read; + while((read = fis.read(buffer)) >= 0) { + md.update(buffer, 0, read); + } + return hexEncode(md.digest()); + } catch (NoSuchAlgorithmException ex) { + throw new RuntimeException(ex); + } + } } diff --git a/platform/autoupdate.services/test/unit/src/org/netbeans/modules/autoupdate/services/MessageDigestCheckerTest.java b/platform/autoupdate.services/test/unit/src/org/netbeans/modules/autoupdate/services/UtilitiesTest.java similarity index 78% rename from platform/autoupdate.services/test/unit/src/org/netbeans/modules/autoupdate/services/MessageDigestCheckerTest.java rename to platform/autoupdate.services/test/unit/src/org/netbeans/modules/autoupdate/services/UtilitiesTest.java index 79af4ab..d39ca58 100644 --- a/platform/autoupdate.services/test/unit/src/org/netbeans/modules/autoupdate/services/MessageDigestCheckerTest.java +++ b/platform/autoupdate.services/test/unit/src/org/netbeans/modules/autoupdate/services/UtilitiesTest.java @@ -23,26 +23,23 @@ import java.security.NoSuchAlgorithmException; import org.junit.Test; import static org.junit.Assert.*; -public class MessageDigestCheckerTest { - - public MessageDigestCheckerTest() { - } +public class UtilitiesTest { @Test public void testHexEncode() throws NoSuchAlgorithmException { MessageDigest sha512 = MessageDigest.getInstance("SHA-512"); - byte[] hash = sha512.digest(new byte[] {}); + byte[] hash = sha512.digest(new byte[]{}); assertEquals( "cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e", - MessageDigestChecker.hexEncode(hash)); + Utilities.hexEncode(hash)); } @Test public void testHexDecode() throws NoSuchAlgorithmException { MessageDigest sha512 = MessageDigest.getInstance("SHA-512"); - byte[] hash = sha512.digest(new byte[] {}); + byte[] hash = sha512.digest(new byte[]{}); assertArrayEquals( hash, - MessageDigestChecker.hexDecode("cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e")); + Utilities.hexDecode("cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e")); } } --------------------------------------------------------------------- To unsubscribe, e-mail: commits-unsubscr...@netbeans.apache.org For additional commands, e-mail: commits-h...@netbeans.apache.org For further information about the NetBeans mailing lists, visit: https://cwiki.apache.org/confluence/display/NETBEANS/Mailing+lists