| 1 | // This program is free software; you can redistribute it and/or modify |
| 2 | // it under the terms of the GNU General Public License as published by |
| 3 | // the Free Software Foundation: version 2 of the License, dated June 1991. |
| 4 | // |
| 5 | // This program is distributed in the hope that it will be useful, |
| 6 | // but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 7 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 8 | // GNU General Public License for more details. |
| 9 | // |
| 10 | // You should have received a copy of the GNU General Public License along |
| 11 | // with this program; if not, see <https://www.gnu.org/licenses/>. |
| 12 | |
| 13 | use std::error::Error; |
| 14 | use std::fmt::{self, Debug, Display}; |
| 15 | use std::io::{self, Write}; |
| 16 | use std::os::raw::c_void; |
| 17 | |
| 18 | pub const GIT_MAX_RAWSZ: usize = 32; |
| 19 | |
| 20 | /// An error indicating an invalid hash algorithm. |
| 21 | /// |
| 22 | /// The contained `u32` is the same as the `algo` field in `ObjectID`. |
| 23 | #[derive(Debug, Copy, Clone)] |
| 24 | pub struct InvalidHashAlgorithm(pub u32); |
| 25 | |
| 26 | impl Display for InvalidHashAlgorithm { |
| 27 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
| 28 | write!(f, "invalid hash algorithm {}", self.0) |
| 29 | } |
| 30 | } |
| 31 | |
| 32 | impl Error for InvalidHashAlgorithm {} |
| 33 | |
| 34 | /// A binary object ID. |
| 35 | #[repr(C)] |
| 36 | #[derive(Clone, Ord, PartialOrd, Eq, PartialEq)] |
| 37 | pub struct ObjectID { |
| 38 | pub hash: [u8; GIT_MAX_RAWSZ], |
| 39 | pub algo: u32, |
| 40 | } |
| 41 | |
| 42 | #[allow(dead_code)] |
| 43 | impl ObjectID { |
| 44 | /// Return a new object ID with the given algorithm and hash. |
| 45 | /// |
| 46 | /// `hash` must be exactly the proper length for `algo` and this function panics if it is not. |
| 47 | /// The extra internal storage of `hash`, if any, is zero filled. |
| 48 | pub fn new(algo: HashAlgorithm, hash: &[u8]) -> Self { |
| 49 | let mut data = [0u8; GIT_MAX_RAWSZ]; |
| 50 | // This verifies that the length of `hash` is correct. |
| 51 | data[0..algo.raw_len()].copy_from_slice(hash); |
| 52 | Self { |
| 53 | hash: data, |
| 54 | algo: algo as u32, |
| 55 | } |
| 56 | } |
| 57 | |
| 58 | /// Return the algorithm for this object ID. |
| 59 | /// |
| 60 | /// If the algorithm set internally is not valid, this function panics. |
| 61 | pub fn algo(&self) -> Result<HashAlgorithm, InvalidHashAlgorithm> { |
| 62 | HashAlgorithm::from_u32(self.algo).ok_or(InvalidHashAlgorithm(self.algo)) |
| 63 | } |
| 64 | |
| 65 | pub fn as_slice(&self) -> Result<&[u8], InvalidHashAlgorithm> { |
| 66 | match HashAlgorithm::from_u32(self.algo) { |
| 67 | Some(algo) => Ok(&self.hash[0..algo.raw_len()]), |
| 68 | None => Err(InvalidHashAlgorithm(self.algo)), |
| 69 | } |
| 70 | } |
| 71 | |
| 72 | pub fn as_mut_slice(&mut self) -> Result<&mut [u8], InvalidHashAlgorithm> { |
| 73 | match HashAlgorithm::from_u32(self.algo) { |
| 74 | Some(algo) => Ok(&mut self.hash[0..algo.raw_len()]), |
| 75 | None => Err(InvalidHashAlgorithm(self.algo)), |
| 76 | } |
| 77 | } |
| 78 | } |
| 79 | |
| 80 | impl Display for ObjectID { |
| 81 | /// Format this object ID as a hex object ID. |
| 82 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
| 83 | let hash = self.as_slice().unwrap(); |
| 84 | for x in hash { |
| 85 | write!(f, "{:02x}", x)?; |
| 86 | } |
| 87 | Ok(()) |
| 88 | } |
| 89 | } |
| 90 | |
| 91 | impl Debug for ObjectID { |
| 92 | /// Format this object ID as a hex object ID with a colon and name appended to it. |
| 93 | /// |
| 94 | /// ``` |
| 95 | /// assert_eq!( |
| 96 | /// format!("{:?}", HashAlgorithm::SHA256.null_oid()), |
| 97 | /// "0000000000000000000000000000000000000000000000000000000000000000:sha256" |
| 98 | /// ); |
| 99 | /// ``` |
| 100 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
| 101 | let hash = match self.as_slice() { |
| 102 | Ok(hash) => hash, |
| 103 | Err(_) => &self.hash, |
| 104 | }; |
| 105 | for x in hash { |
| 106 | write!(f, "{:02x}", x)?; |
| 107 | } |
| 108 | match self.algo() { |
| 109 | Ok(algo) => write!(f, ":{}", algo.name()), |
| 110 | Err(e) => write!(f, ":invalid-hash-algo-{}", e.0), |
| 111 | } |
| 112 | } |
| 113 | } |
| 114 | |
| 115 | /// A trait to implement hashing with a cryptographic algorithm. |
| 116 | pub trait CryptoDigest { |
| 117 | /// Return true if this digest is safe for use with untrusted data, false otherwise. |
| 118 | fn is_safe(&self) -> bool; |
| 119 | |
| 120 | /// Update the digest with the specified data. |
| 121 | fn update(&mut self, data: &[u8]); |
| 122 | |
| 123 | /// Return an object ID, consuming the hasher. |
| 124 | fn into_oid(self) -> ObjectID; |
| 125 | |
| 126 | /// Return a hash as a `Vec`, consuming the hasher. |
| 127 | fn into_vec(self) -> Vec<u8>; |
| 128 | } |
| 129 | |
| 130 | /// A structure to hash data with a cryptographic hash algorithm. |
| 131 | /// |
| 132 | /// Instances of this class are safe for use with untrusted data, provided Git has been compiled |
| 133 | /// with a collision-detecting implementation of SHA-1. |
| 134 | pub struct CryptoHasher { |
| 135 | algo: HashAlgorithm, |
| 136 | ctx: *mut c_void, |
| 137 | } |
| 138 | |
| 139 | impl CryptoHasher { |
| 140 | /// Create a new hasher with the algorithm specified with `algo`. |
| 141 | /// |
| 142 | /// This hasher is safe to use on untrusted data. If SHA-1 is selected and Git was compiled |
| 143 | /// with a collision-detecting implementation of SHA-1, then this function will use that |
| 144 | /// implementation and detect any attempts at a collision. |
| 145 | pub fn new(algo: HashAlgorithm) -> Self { |
| 146 | let ctx = unsafe { c::git_hash_alloc() }; |
| 147 | unsafe { c::git_hash_init(ctx, algo.hash_algo_ptr()) }; |
| 148 | Self { algo, ctx } |
| 149 | } |
| 150 | } |
| 151 | |
| 152 | impl CryptoDigest for CryptoHasher { |
| 153 | /// Return true if this digest is safe for use with untrusted data, false otherwise. |
| 154 | fn is_safe(&self) -> bool { |
| 155 | true |
| 156 | } |
| 157 | |
| 158 | /// Update the hasher with the specified data. |
| 159 | fn update(&mut self, data: &[u8]) { |
| 160 | unsafe { c::git_hash_update(self.ctx, data.as_ptr() as *const c_void, data.len()) }; |
| 161 | } |
| 162 | |
| 163 | /// Return an object ID, consuming the hasher. |
| 164 | fn into_oid(self) -> ObjectID { |
| 165 | let mut oid = ObjectID { |
| 166 | hash: [0u8; 32], |
| 167 | algo: self.algo as u32, |
| 168 | }; |
| 169 | unsafe { c::git_hash_final_oid(&mut oid as *mut ObjectID as *mut c_void, self.ctx) }; |
| 170 | oid |
| 171 | } |
| 172 | |
| 173 | /// Return a hash as a `Vec`, consuming the hasher. |
| 174 | fn into_vec(self) -> Vec<u8> { |
| 175 | let mut v = vec![0u8; self.algo.raw_len()]; |
| 176 | unsafe { c::git_hash_final(v.as_mut_ptr(), self.ctx) }; |
| 177 | v |
| 178 | } |
| 179 | } |
| 180 | |
| 181 | impl Clone for CryptoHasher { |
| 182 | fn clone(&self) -> Self { |
| 183 | let ctx = unsafe { c::git_hash_alloc() }; |
| 184 | unsafe { c::git_hash_clone(ctx, self.ctx) }; |
| 185 | Self { |
| 186 | algo: self.algo, |
| 187 | ctx, |
| 188 | } |
| 189 | } |
| 190 | } |
| 191 | |
| 192 | impl Drop for CryptoHasher { |
| 193 | fn drop(&mut self) { |
| 194 | unsafe { c::git_hash_free(self.ctx) }; |
| 195 | } |
| 196 | } |
| 197 | |
| 198 | impl Write for CryptoHasher { |
| 199 | fn write(&mut self, data: &[u8]) -> io::Result<usize> { |
| 200 | self.update(data); |
| 201 | Ok(data.len()) |
| 202 | } |
| 203 | |
| 204 | fn flush(&mut self) -> io::Result<()> { |
| 205 | Ok(()) |
| 206 | } |
| 207 | } |
| 208 | |
| 209 | /// A hash algorithm, |
| 210 | #[repr(C)] |
| 211 | #[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq)] |
| 212 | pub enum HashAlgorithm { |
| 213 | SHA1 = 1, |
| 214 | SHA256 = 2, |
| 215 | } |
| 216 | |
| 217 | #[allow(dead_code)] |
| 218 | impl HashAlgorithm { |
| 219 | const SHA1_NULL_OID: ObjectID = ObjectID { |
| 220 | hash: [0u8; 32], |
| 221 | algo: Self::SHA1 as u32, |
| 222 | }; |
| 223 | const SHA256_NULL_OID: ObjectID = ObjectID { |
| 224 | hash: [0u8; 32], |
| 225 | algo: Self::SHA256 as u32, |
| 226 | }; |
| 227 | |
| 228 | const SHA1_EMPTY_TREE: ObjectID = ObjectID { |
| 229 | hash: *b"\x4b\x82\x5d\xc6\x42\xcb\x6e\xb9\xa0\x60\xe5\x4b\xf8\xd6\x92\x88\xfb\xee\x49\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", |
| 230 | algo: Self::SHA1 as u32, |
| 231 | }; |
| 232 | const SHA256_EMPTY_TREE: ObjectID = ObjectID { |
| 233 | hash: *b"\x6e\xf1\x9b\x41\x22\x5c\x53\x69\xf1\xc1\x04\xd4\x5d\x8d\x85\xef\xa9\xb0\x57\xb5\x3b\x14\xb4\xb9\xb9\x39\xdd\x74\xde\xcc\x53\x21", |
| 234 | algo: Self::SHA256 as u32, |
| 235 | }; |
| 236 | |
| 237 | const SHA1_EMPTY_BLOB: ObjectID = ObjectID { |
| 238 | hash: *b"\xe6\x9d\xe2\x9b\xb2\xd1\xd6\x43\x4b\x8b\x29\xae\x77\x5a\xd8\xc2\xe4\x8c\x53\x91\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", |
| 239 | algo: Self::SHA1 as u32, |
| 240 | }; |
| 241 | const SHA256_EMPTY_BLOB: ObjectID = ObjectID { |
| 242 | hash: *b"\x47\x3a\x0f\x4c\x3b\xe8\xa9\x36\x81\xa2\x67\xe3\xb1\xe9\xa7\xdc\xda\x11\x85\x43\x6f\xe1\x41\xf7\x74\x91\x20\xa3\x03\x72\x18\x13", |
| 243 | algo: Self::SHA256 as u32, |
| 244 | }; |
| 245 | |
| 246 | /// Return a hash algorithm based on the internal integer ID used by Git. |
| 247 | /// |
| 248 | /// Returns `None` if the algorithm doesn't indicate a valid algorithm. |
| 249 | pub const fn from_u32(algo: u32) -> Option<HashAlgorithm> { |
| 250 | match algo { |
| 251 | 1 => Some(HashAlgorithm::SHA1), |
| 252 | 2 => Some(HashAlgorithm::SHA256), |
| 253 | _ => None, |
| 254 | } |
| 255 | } |
| 256 | |
| 257 | /// Return a hash algorithm based on the internal integer ID used by Git. |
| 258 | /// |
| 259 | /// Returns `None` if the algorithm doesn't indicate a valid algorithm. |
| 260 | pub const fn from_format_id(algo: u32) -> Option<HashAlgorithm> { |
| 261 | match algo { |
| 262 | 0x73686131 => Some(HashAlgorithm::SHA1), |
| 263 | 0x73323536 => Some(HashAlgorithm::SHA256), |
| 264 | _ => None, |
| 265 | } |
| 266 | } |
| 267 | |
| 268 | /// The name of this hash algorithm as a string suitable for the configuration file. |
| 269 | pub const fn name(self) -> &'static str { |
| 270 | match self { |
| 271 | HashAlgorithm::SHA1 => "sha1", |
| 272 | HashAlgorithm::SHA256 => "sha256", |
| 273 | } |
| 274 | } |
| 275 | |
| 276 | /// The format ID of this algorithm for binary formats. |
| 277 | /// |
| 278 | /// Note that when writing this to a data format, it should be written in big-endian format |
| 279 | /// explicitly. |
| 280 | pub const fn format_id(self) -> u32 { |
| 281 | match self { |
| 282 | HashAlgorithm::SHA1 => 0x73686131, |
| 283 | HashAlgorithm::SHA256 => 0x73323536, |
| 284 | } |
| 285 | } |
| 286 | |
| 287 | /// The length of binary object IDs in this algorithm in bytes. |
| 288 | pub const fn raw_len(self) -> usize { |
| 289 | match self { |
| 290 | HashAlgorithm::SHA1 => 20, |
| 291 | HashAlgorithm::SHA256 => 32, |
| 292 | } |
| 293 | } |
| 294 | |
| 295 | /// The length of object IDs in this algorithm in hexadecimal characters. |
| 296 | pub const fn hex_len(self) -> usize { |
| 297 | self.raw_len() * 2 |
| 298 | } |
| 299 | |
| 300 | /// The number of bytes which is processed by one iteration of this algorithm's compression |
| 301 | /// function. |
| 302 | pub const fn block_size(self) -> usize { |
| 303 | match self { |
| 304 | HashAlgorithm::SHA1 => 64, |
| 305 | HashAlgorithm::SHA256 => 64, |
| 306 | } |
| 307 | } |
| 308 | |
| 309 | /// The object ID representing the empty blob. |
| 310 | pub const fn empty_blob(self) -> &'static ObjectID { |
| 311 | match self { |
| 312 | HashAlgorithm::SHA1 => &Self::SHA1_EMPTY_BLOB, |
| 313 | HashAlgorithm::SHA256 => &Self::SHA256_EMPTY_BLOB, |
| 314 | } |
| 315 | } |
| 316 | |
| 317 | /// The object ID representing the empty tree. |
| 318 | pub const fn empty_tree(self) -> &'static ObjectID { |
| 319 | match self { |
| 320 | HashAlgorithm::SHA1 => &Self::SHA1_EMPTY_TREE, |
| 321 | HashAlgorithm::SHA256 => &Self::SHA256_EMPTY_TREE, |
| 322 | } |
| 323 | } |
| 324 | |
| 325 | /// The object ID which is all zeros. |
| 326 | pub const fn null_oid(self) -> &'static ObjectID { |
| 327 | match self { |
| 328 | HashAlgorithm::SHA1 => &Self::SHA1_NULL_OID, |
| 329 | HashAlgorithm::SHA256 => &Self::SHA256_NULL_OID, |
| 330 | } |
| 331 | } |
| 332 | |
| 333 | /// A pointer to the C `struct git_hash_algo` for interoperability with C. |
| 334 | pub fn hash_algo_ptr(self) -> *const c_void { |
| 335 | unsafe { c::hash_algo_ptr_by_number(self as u32) } |
| 336 | } |
| 337 | |
| 338 | /// Create a hasher for this algorithm. |
| 339 | pub fn hasher(self) -> CryptoHasher { |
| 340 | CryptoHasher::new(self) |
| 341 | } |
| 342 | } |
| 343 | |
| 344 | pub mod c { |
| 345 | use std::os::raw::c_void; |
| 346 | |
| 347 | extern "C" { |
| 348 | pub fn hash_algo_ptr_by_number(n: u32) -> *const c_void; |
| 349 | pub fn unsafe_hash_algo(algop: *const c_void) -> *const c_void; |
| 350 | pub fn git_hash_alloc() -> *mut c_void; |
| 351 | pub fn git_hash_free(ctx: *mut c_void); |
| 352 | pub fn git_hash_init(dst: *mut c_void, algop: *const c_void); |
| 353 | pub fn git_hash_clone(dst: *mut c_void, src: *const c_void); |
| 354 | pub fn git_hash_update(ctx: *mut c_void, inp: *const c_void, len: usize); |
| 355 | pub fn git_hash_final(hash: *mut u8, ctx: *mut c_void); |
| 356 | pub fn git_hash_final_oid(hash: *mut c_void, ctx: *mut c_void); |
| 357 | } |
| 358 | } |
| 359 | |
| 360 | #[cfg(test)] |
| 361 | mod tests { |
| 362 | use super::{CryptoDigest, HashAlgorithm, ObjectID}; |
| 363 | use std::io::Write; |
| 364 | |
| 365 | fn all_algos() -> &'static [HashAlgorithm] { |
| 366 | &[HashAlgorithm::SHA1, HashAlgorithm::SHA256] |
| 367 | } |
| 368 | |
| 369 | #[test] |
| 370 | fn format_id_round_trips() { |
| 371 | for algo in all_algos() { |
| 372 | assert_eq!( |
| 373 | *algo, |
| 374 | HashAlgorithm::from_format_id(algo.format_id()).unwrap() |
| 375 | ); |
| 376 | } |
| 377 | } |
| 378 | |
| 379 | #[test] |
| 380 | fn offset_round_trips() { |
| 381 | for algo in all_algos() { |
| 382 | assert_eq!(*algo, HashAlgorithm::from_u32(*algo as u32).unwrap()); |
| 383 | } |
| 384 | } |
| 385 | |
| 386 | #[test] |
| 387 | fn slices_have_correct_length() { |
| 388 | for algo in all_algos() { |
| 389 | for oid in [algo.null_oid(), algo.empty_blob(), algo.empty_tree()] { |
| 390 | assert_eq!(oid.as_slice().unwrap().len(), algo.raw_len()); |
| 391 | } |
| 392 | } |
| 393 | } |
| 394 | |
| 395 | #[test] |
| 396 | fn object_ids_format_correctly() { |
| 397 | let entries = &[ |
| 398 | ( |
| 399 | HashAlgorithm::SHA1.null_oid(), |
| 400 | "0000000000000000000000000000000000000000", |
| 401 | "0000000000000000000000000000000000000000:sha1", |
| 402 | ), |
| 403 | ( |
| 404 | HashAlgorithm::SHA1.empty_blob(), |
| 405 | "e69de29bb2d1d6434b8b29ae775ad8c2e48c5391", |
| 406 | "e69de29bb2d1d6434b8b29ae775ad8c2e48c5391:sha1", |
| 407 | ), |
| 408 | ( |
| 409 | HashAlgorithm::SHA1.empty_tree(), |
| 410 | "4b825dc642cb6eb9a060e54bf8d69288fbee4904", |
| 411 | "4b825dc642cb6eb9a060e54bf8d69288fbee4904:sha1", |
| 412 | ), |
| 413 | ( |
| 414 | HashAlgorithm::SHA256.null_oid(), |
| 415 | "0000000000000000000000000000000000000000000000000000000000000000", |
| 416 | "0000000000000000000000000000000000000000000000000000000000000000:sha256", |
| 417 | ), |
| 418 | ( |
| 419 | HashAlgorithm::SHA256.empty_blob(), |
| 420 | "473a0f4c3be8a93681a267e3b1e9a7dcda1185436fe141f7749120a303721813", |
| 421 | "473a0f4c3be8a93681a267e3b1e9a7dcda1185436fe141f7749120a303721813:sha256", |
| 422 | ), |
| 423 | ( |
| 424 | HashAlgorithm::SHA256.empty_tree(), |
| 425 | "6ef19b41225c5369f1c104d45d8d85efa9b057b53b14b4b9b939dd74decc5321", |
| 426 | "6ef19b41225c5369f1c104d45d8d85efa9b057b53b14b4b9b939dd74decc5321:sha256", |
| 427 | ), |
| 428 | ]; |
| 429 | for (oid, display, debug) in entries { |
| 430 | assert_eq!(format!("{}", oid), *display); |
| 431 | assert_eq!(format!("{:?}", oid), *debug); |
| 432 | } |
| 433 | } |
| 434 | |
| 435 | #[test] |
| 436 | fn hasher_works_correctly() { |
| 437 | for algo in all_algos() { |
| 438 | let tests: &[(&[u8], &ObjectID)] = &[ |
| 439 | (b"blob 0\0", algo.empty_blob()), |
| 440 | (b"tree 0\0", algo.empty_tree()), |
| 441 | ]; |
| 442 | for (data, oid) in tests { |
| 443 | let mut h = algo.hasher(); |
| 444 | assert!(h.is_safe()); |
| 445 | // Test that this works incrementally. |
| 446 | h.update(&data[0..2]); |
| 447 | h.update(&data[2..]); |
| 448 | |
| 449 | let h2 = h.clone(); |
| 450 | |
| 451 | let actual_oid = h.into_oid(); |
| 452 | assert_eq!(**oid, actual_oid); |
| 453 | |
| 454 | let v = h2.into_vec(); |
| 455 | assert_eq!((*oid).as_slice().unwrap(), &v); |
| 456 | |
| 457 | let mut h = algo.hasher(); |
| 458 | h.write_all(&data[0..2]).unwrap(); |
| 459 | h.write_all(&data[2..]).unwrap(); |
| 460 | |
| 461 | let actual_oid = h.into_oid(); |
| 462 | assert_eq!(**oid, actual_oid); |
| 463 | } |
| 464 | } |
| 465 | } |
| 466 | } |