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 {
185 c::git_hash_init(ctx, self.algo.hash_algo_ptr());
186 c::git_hash_clone(ctx, self.ctx)
187 };
188 Self {
189 algo: self.algo,
190 ctx,
191 }
192 }
193 }
194
195 impl Drop for CryptoHasher {
196 fn drop(&mut self) {
197 unsafe {
198 c::git_hash_discard(self.ctx);
199 c::git_hash_free(self.ctx);
200 };
201 }
202 }
203
204 impl Write for CryptoHasher {
205 fn write(&mut self, data: &[u8]) -> io::Result<usize> {
206 self.update(data);
207 Ok(data.len())
208 }
209
210 fn flush(&mut self) -> io::Result<()> {
211 Ok(())
212 }
213 }
214
215 /// A hash algorithm,
216 #[repr(C)]
217 #[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq)]
218 pub enum HashAlgorithm {
219 SHA1 = 1,
220 SHA256 = 2,
221 }
222
223 #[allow(dead_code)]
224 impl HashAlgorithm {
225 const SHA1_NULL_OID: ObjectID = ObjectID {
226 hash: [0u8; 32],
227 algo: Self::SHA1 as u32,
228 };
229 const SHA256_NULL_OID: ObjectID = ObjectID {
230 hash: [0u8; 32],
231 algo: Self::SHA256 as u32,
232 };
233
234 const SHA1_EMPTY_TREE: ObjectID = ObjectID {
235 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",
236 algo: Self::SHA1 as u32,
237 };
238 const SHA256_EMPTY_TREE: ObjectID = ObjectID {
239 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",
240 algo: Self::SHA256 as u32,
241 };
242
243 const SHA1_EMPTY_BLOB: ObjectID = ObjectID {
244 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",
245 algo: Self::SHA1 as u32,
246 };
247 const SHA256_EMPTY_BLOB: ObjectID = ObjectID {
248 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",
249 algo: Self::SHA256 as u32,
250 };
251
252 /// Return a hash algorithm based on the internal integer ID used by Git.
253 ///
254 /// Returns `None` if the algorithm doesn't indicate a valid algorithm.
255 pub const fn from_u32(algo: u32) -> Option<HashAlgorithm> {
256 match algo {
257 1 => Some(HashAlgorithm::SHA1),
258 2 => Some(HashAlgorithm::SHA256),
259 _ => None,
260 }
261 }
262
263 /// Return a hash algorithm based on the internal integer ID used by Git.
264 ///
265 /// Returns `None` if the algorithm doesn't indicate a valid algorithm.
266 pub const fn from_format_id(algo: u32) -> Option<HashAlgorithm> {
267 match algo {
268 0x73686131 => Some(HashAlgorithm::SHA1),
269 0x73323536 => Some(HashAlgorithm::SHA256),
270 _ => None,
271 }
272 }
273
274 /// The name of this hash algorithm as a string suitable for the configuration file.
275 pub const fn name(self) -> &'static str {
276 match self {
277 HashAlgorithm::SHA1 => "sha1",
278 HashAlgorithm::SHA256 => "sha256",
279 }
280 }
281
282 /// The format ID of this algorithm for binary formats.
283 ///
284 /// Note that when writing this to a data format, it should be written in big-endian format
285 /// explicitly.
286 pub const fn format_id(self) -> u32 {
287 match self {
288 HashAlgorithm::SHA1 => 0x73686131,
289 HashAlgorithm::SHA256 => 0x73323536,
290 }
291 }
292
293 /// The length of binary object IDs in this algorithm in bytes.
294 pub const fn raw_len(self) -> usize {
295 match self {
296 HashAlgorithm::SHA1 => 20,
297 HashAlgorithm::SHA256 => 32,
298 }
299 }
300
301 /// The length of object IDs in this algorithm in hexadecimal characters.
302 pub const fn hex_len(self) -> usize {
303 self.raw_len() * 2
304 }
305
306 /// The number of bytes which is processed by one iteration of this algorithm's compression
307 /// function.
308 pub const fn block_size(self) -> usize {
309 match self {
310 HashAlgorithm::SHA1 => 64,
311 HashAlgorithm::SHA256 => 64,
312 }
313 }
314
315 /// The object ID representing the empty blob.
316 pub const fn empty_blob(self) -> &'static ObjectID {
317 match self {
318 HashAlgorithm::SHA1 => &Self::SHA1_EMPTY_BLOB,
319 HashAlgorithm::SHA256 => &Self::SHA256_EMPTY_BLOB,
320 }
321 }
322
323 /// The object ID representing the empty tree.
324 pub const fn empty_tree(self) -> &'static ObjectID {
325 match self {
326 HashAlgorithm::SHA1 => &Self::SHA1_EMPTY_TREE,
327 HashAlgorithm::SHA256 => &Self::SHA256_EMPTY_TREE,
328 }
329 }
330
331 /// The object ID which is all zeros.
332 pub const fn null_oid(self) -> &'static ObjectID {
333 match self {
334 HashAlgorithm::SHA1 => &Self::SHA1_NULL_OID,
335 HashAlgorithm::SHA256 => &Self::SHA256_NULL_OID,
336 }
337 }
338
339 /// A pointer to the C `struct git_hash_algo` for interoperability with C.
340 pub fn hash_algo_ptr(self) -> *const c_void {
341 unsafe { c::hash_algo_ptr_by_number(self as u32) }
342 }
343
344 /// Create a hasher for this algorithm.
345 pub fn hasher(self) -> CryptoHasher {
346 CryptoHasher::new(self)
347 }
348 }
349
350 pub mod c {
351 use std::os::raw::c_void;
352
353 extern "C" {
354 pub fn hash_algo_ptr_by_number(n: u32) -> *const c_void;
355 pub fn unsafe_hash_algo(algop: *const c_void) -> *const c_void;
356 pub fn git_hash_alloc() -> *mut c_void;
357 pub fn git_hash_free(ctx: *mut c_void);
358 pub fn git_hash_init(dst: *mut c_void, algop: *const c_void);
359 pub fn git_hash_clone(dst: *mut c_void, src: *const c_void);
360 pub fn git_hash_update(ctx: *mut c_void, inp: *const c_void, len: usize);
361 pub fn git_hash_final(hash: *mut u8, ctx: *mut c_void);
362 pub fn git_hash_discard(ctx: *mut c_void);
363 pub fn git_hash_final_oid(hash: *mut c_void, ctx: *mut c_void);
364 }
365 }
366
367 #[cfg(test)]
368 mod tests {
369 use super::{CryptoDigest, HashAlgorithm, ObjectID};
370 use std::io::Write;
371
372 fn all_algos() -> &'static [HashAlgorithm] {
373 &[HashAlgorithm::SHA1, HashAlgorithm::SHA256]
374 }
375
376 #[test]
377 fn format_id_round_trips() {
378 for algo in all_algos() {
379 assert_eq!(
380 *algo,
381 HashAlgorithm::from_format_id(algo.format_id()).unwrap()
382 );
383 }
384 }
385
386 #[test]
387 fn offset_round_trips() {
388 for algo in all_algos() {
389 assert_eq!(*algo, HashAlgorithm::from_u32(*algo as u32).unwrap());
390 }
391 }
392
393 #[test]
394 fn slices_have_correct_length() {
395 for algo in all_algos() {
396 for oid in [algo.null_oid(), algo.empty_blob(), algo.empty_tree()] {
397 assert_eq!(oid.as_slice().unwrap().len(), algo.raw_len());
398 }
399 }
400 }
401
402 #[test]
403 fn object_ids_format_correctly() {
404 let entries = &[
405 (
406 HashAlgorithm::SHA1.null_oid(),
407 "0000000000000000000000000000000000000000",
408 "0000000000000000000000000000000000000000:sha1",
409 ),
410 (
411 HashAlgorithm::SHA1.empty_blob(),
412 "e69de29bb2d1d6434b8b29ae775ad8c2e48c5391",
413 "e69de29bb2d1d6434b8b29ae775ad8c2e48c5391:sha1",
414 ),
415 (
416 HashAlgorithm::SHA1.empty_tree(),
417 "4b825dc642cb6eb9a060e54bf8d69288fbee4904",
418 "4b825dc642cb6eb9a060e54bf8d69288fbee4904:sha1",
419 ),
420 (
421 HashAlgorithm::SHA256.null_oid(),
422 "0000000000000000000000000000000000000000000000000000000000000000",
423 "0000000000000000000000000000000000000000000000000000000000000000:sha256",
424 ),
425 (
426 HashAlgorithm::SHA256.empty_blob(),
427 "473a0f4c3be8a93681a267e3b1e9a7dcda1185436fe141f7749120a303721813",
428 "473a0f4c3be8a93681a267e3b1e9a7dcda1185436fe141f7749120a303721813:sha256",
429 ),
430 (
431 HashAlgorithm::SHA256.empty_tree(),
432 "6ef19b41225c5369f1c104d45d8d85efa9b057b53b14b4b9b939dd74decc5321",
433 "6ef19b41225c5369f1c104d45d8d85efa9b057b53b14b4b9b939dd74decc5321:sha256",
434 ),
435 ];
436 for (oid, display, debug) in entries {
437 assert_eq!(format!("{}", oid), *display);
438 assert_eq!(format!("{:?}", oid), *debug);
439 }
440 }
441
442 #[test]
443 fn hasher_works_correctly() {
444 for algo in all_algos() {
445 let tests: &[(&[u8], &ObjectID)] = &[
446 (b"blob 0\0", algo.empty_blob()),
447 (b"tree 0\0", algo.empty_tree()),
448 ];
449 for (data, oid) in tests {
450 let mut h = algo.hasher();
451 assert!(h.is_safe());
452 // Test that this works incrementally.
453 h.update(&data[0..2]);
454 h.update(&data[2..]);
455
456 let h2 = h.clone();
457 let h3 = h2.clone();
458
459 let actual_oid = h.into_oid();
460 assert_eq!(**oid, actual_oid);
461
462 let v = h2.into_vec();
463 assert_eq!((*oid).as_slice().unwrap(), &v);
464
465 let mut h = algo.hasher();
466 h.write_all(&data[0..2]).unwrap();
467 h.write_all(&data[2..]).unwrap();
468
469 let actual_oid = h.into_oid();
470 assert_eq!(**oid, actual_oid);
471 std::mem::drop(h3);
472 }
473 }
474 }
475 }