11use std:: fs;
2+ use std:: io:: { BufRead , BufReader } ;
3+ use std:: path:: Path ;
24
35use libcnb:: build:: BuildContext ;
46use libcnb:: data:: layer_name;
@@ -10,6 +12,7 @@ use libherokubuildpack::download::download_file;
1012use libherokubuildpack:: log:: log_info;
1113use libherokubuildpack:: tar:: decompress_tarball;
1214use serde:: { Deserialize , Serialize } ;
15+ use sha2:: { Digest , Sha512 } ;
1316use tempfile:: NamedTempFile ;
1417
1518use crate :: o11y:: * ;
@@ -79,8 +82,18 @@ pub(crate) fn install_web_server(
7982 { INSTALLATION_WEB_SERVER_VERSION } = web_server_version,
8083 "downloading web server"
8184 ) ;
82- download_file ( artifact_url, web_server_tgz. path ( ) )
85+ download_file ( & artifact_url, web_server_tgz. path ( ) )
8386 . map_err ( StaticWebServerBuildpackError :: Download ) ?;
87+
88+ // Verify the checksum
89+ log_info ( "Verifying web server checksum" ) ;
90+ verify_caddy_checksum (
91+ web_server_version,
92+ & context. target . os ,
93+ & context. target . arch ,
94+ web_server_tgz. path ( ) ,
95+ ) ?;
96+
8497 decompress_tarball ( & mut web_server_tgz. into_file ( ) , & web_server_dir)
8598 . map_err ( StaticWebServerBuildpackError :: CannotUnpackCaddyTarball ) ?;
8699 }
@@ -117,3 +130,98 @@ pub(crate) struct WebServerLayerMetadata {
117130 arch : String ,
118131 os : String ,
119132}
133+
134+ /// Verifies the Caddy binary checksum against the official checksums file
135+ fn verify_caddy_checksum (
136+ version : & str ,
137+ os : & str ,
138+ arch : & str ,
139+ tarball_path : & Path ,
140+ ) -> Result < ( ) , libcnb:: Error < StaticWebServerBuildpackError > > {
141+ let base_url = format ! ( "https://github.com/caddyserver/caddy/releases/download/v{version}" ) ;
142+ let checksums_filename = format ! ( "caddy_{version}_checksums.txt" ) ;
143+ let artifact_filename = format ! ( "caddy_{version}_{os}_{arch}.tar.gz" ) ;
144+
145+ // Download checksums file
146+ let checksums_file = NamedTempFile :: new ( )
147+ . map_err ( StaticWebServerBuildpackError :: CannotCreateCaddyTarballFile ) ?;
148+ download_file (
149+ format ! ( "{base_url}/{checksums_filename}" ) ,
150+ checksums_file. path ( ) ,
151+ )
152+ . map_err ( StaticWebServerBuildpackError :: Download ) ?;
153+
154+ // Verify the tarball checksum against the checksums file
155+ verify_checksum ( tarball_path, checksums_file. path ( ) , & artifact_filename) ?;
156+
157+ tracing:: info!( "Successfully verified Caddy checksum for version {version}" ) ;
158+
159+ Ok ( ( ) )
160+ }
161+
162+ /// Verifies the checksum of a file against a checksums file
163+ fn verify_checksum (
164+ file_path : & Path ,
165+ checksums_path : & Path ,
166+ expected_filename : & str ,
167+ ) -> Result < ( ) , libcnb:: Error < StaticWebServerBuildpackError > > {
168+ // Calculate the SHA512 hash of the downloaded file
169+ let mut file = fs:: File :: open ( file_path) . map_err ( |error| {
170+ StaticWebServerBuildpackError :: CannotReadChecksums {
171+ filename : expected_filename. to_string ( ) ,
172+ error,
173+ }
174+ } ) ?;
175+ let mut hasher = Sha512 :: new ( ) ;
176+ std:: io:: copy ( & mut file, & mut hasher) . map_err ( |error| {
177+ StaticWebServerBuildpackError :: CannotReadChecksums {
178+ filename : expected_filename. to_string ( ) ,
179+ error,
180+ }
181+ } ) ?;
182+ let calculated_hash = format ! ( "{:x}" , hasher. finalize( ) ) ;
183+
184+ // Parse the checksums file to find the expected checksum
185+ let checksums_filename = checksums_path
186+ . file_name ( )
187+ . and_then ( |n| n. to_str ( ) )
188+ . unwrap_or ( "checksums.txt" ) ;
189+ let checksums_file = fs:: File :: open ( checksums_path) . map_err ( |error| {
190+ StaticWebServerBuildpackError :: CannotReadChecksums {
191+ filename : checksums_filename. to_string ( ) ,
192+ error,
193+ }
194+ } ) ?;
195+ let reader = BufReader :: new ( checksums_file) ;
196+
197+ let mut found_checksum = None ;
198+ for line in reader. lines ( ) {
199+ let line = line. map_err ( |error| StaticWebServerBuildpackError :: CannotReadChecksums {
200+ filename : checksums_filename. to_string ( ) ,
201+ error,
202+ } ) ?;
203+ let parts: Vec < & str > = line. split_whitespace ( ) . collect ( ) ;
204+ if parts. len ( ) >= 2 && parts[ 1 ] == expected_filename {
205+ found_checksum = Some ( parts[ 0 ] . to_string ( ) ) ;
206+ break ;
207+ }
208+ }
209+
210+ let expected_hash = found_checksum. ok_or_else ( || {
211+ StaticWebServerBuildpackError :: ChecksumVerificationFailed ( format ! (
212+ "Checksum for {expected_filename} not found in checksums file"
213+ ) )
214+ } ) ?;
215+
216+ // Compare checksums
217+ if calculated_hash != expected_hash {
218+ return Err (
219+ StaticWebServerBuildpackError :: ChecksumVerificationFailed ( format ! (
220+ "Checksum mismatch for {expected_filename}: expected {expected_hash}, got {calculated_hash}"
221+ ) )
222+ . into ( ) ,
223+ ) ;
224+ }
225+
226+ Ok ( ( ) )
227+ }
0 commit comments