Raw
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 }