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

Reply via email to