@@ -20,23 +20,52 @@ use std::cmp::PartialEq;
2020use  crate :: crypto:: { CosignVerificationKey ,  Signature } ; 
2121use  crate :: errors:: { Result ,  SigstoreError } ; 
2222
23+ #[ derive( Serialize ,  Deserialize ,  Debug ,  PartialEq ,  Eq ) ]  
24+ pub  struct  Bundle  { 
25+     #[ serde( rename( deserialize = "base64Signature" ) ) ]  
26+     pub  base64_signature :  String , 
27+     pub  cert :  String , 
28+     #[ serde( rename( deserialize = "rekorBundle" ) ) ]  
29+     pub  rekor_bundle :  RekorBundle , 
30+ } 
31+ 
32+ impl  Bundle  { 
33+     #[ allow( dead_code) ]  
34+     pub ( crate )  fn  new_verified ( raw :  & str ,  rekor_pub_key :  & CosignVerificationKey )  -> Result < Self >  { 
35+         let  bundle:  Bundle  = serde_json:: from_str ( raw) . map_err ( |e| { 
36+             SigstoreError :: UnexpectedError ( format ! ( "Cannot parse bundle |{}|: {:?}" ,  raw,  e) ) 
37+         } ) ?; 
38+         RekorBundle :: verify_bundle ( & bundle. rekor_bundle ,  rekor_pub_key) . map ( |_| bundle) 
39+     } 
40+ } 
41+ 
2342#[ derive( Serialize ,  Deserialize ,  Debug ,  Clone ,  PartialEq ,  Eq ) ]  
2443#[ serde( rename_all = "PascalCase" ) ]  
25- pub  struct  Bundle  { 
44+ pub  struct  RekorBundle  { 
2645    pub  signed_entry_timestamp :  String , 
2746    pub  payload :  Payload , 
2847} 
2948
30- impl  Bundle  { 
31-     /// Create a new verified `Bundle ` 
49+ impl  RekorBundle  { 
50+     /// Create a new verified `RekorBundle ` 
3251     /// 
3352     /// **Note well:** The bundle will be returned only if it can be verified 
3453     /// using the supplied `rekor_pub_key` public key. 
3554     pub ( crate )  fn  new_verified ( raw :  & str ,  rekor_pub_key :  & CosignVerificationKey )  -> Result < Self >  { 
36-         let  bundle:  Bundle  = serde_json:: from_str ( raw) . map_err ( |e| { 
55+         let  bundle:  RekorBundle  = serde_json:: from_str ( raw) . map_err ( |e| { 
3756            SigstoreError :: UnexpectedError ( format ! ( "Cannot parse bundle |{}|: {:?}" ,  raw,  e) ) 
3857        } ) ?; 
58+         Self :: verify_bundle ( & bundle,  rekor_pub_key) . map ( |_| bundle) 
59+     } 
3960
61+     /// Verify a `RekorBundle`. 
62+      /// 
63+      /// **Note well:** The bundle will be returned only if it can be verified 
64+      /// using the supplied `rekor_pub_key` public key. 
65+      pub ( crate )  fn  verify_bundle ( 
66+         bundle :  & RekorBundle , 
67+         rekor_pub_key :  & CosignVerificationKey , 
68+     )  -> Result < ( ) >  { 
4069        let  mut  buf = Vec :: new ( ) ; 
4170        let  mut  ser = serde_json:: Serializer :: with_formatter ( & mut  buf,  CanonicalFormatter :: new ( ) ) ; 
4271        bundle. payload . serialize ( & mut  ser) . map_err ( |e| { 
@@ -50,7 +79,7 @@ impl Bundle {
5079            Signature :: Base64Encoded ( bundle. signed_entry_timestamp . as_bytes ( ) ) , 
5180            & buf, 
5281        ) ?; 
53-         Ok ( bundle ) 
82+         Ok ( ( ) ) 
5483    } 
5584} 
5685
@@ -72,7 +101,7 @@ mod tests {
72101    use  crate :: cosign:: tests:: get_rekor_public_key; 
73102    use  crate :: crypto:: SigningScheme ; 
74103
75-     fn  build_correct_bundle ( )  -> String  { 
104+     fn  build_correct_rekor_bundle ( )  -> String  { 
76105        let  bundle_json = json ! ( { 
77106          "SignedEntryTimestamp" :  "MEUCIDx9M+yRpD0O47/Mzm8NAPCbtqy4uiTkLWWexW0bo4jZAiEA1wwueIW8XzJWNkut5y9snYj7UOfbMmUXp7fH3CzJmWg=" , 
78107          "Payload" :  { 
@@ -86,17 +115,17 @@ mod tests {
86115    } 
87116
88117    #[ test]  
89-     fn  bundle_new_verified_success ( )  { 
118+     fn  rekor_bundle_new_verified_success ( )  { 
90119        let  rekor_pub_key = get_rekor_public_key ( ) ; 
91120
92-         let  bundle_json = build_correct_bundle ( ) ; 
93-         let  bundle = Bundle :: new_verified ( & bundle_json,  & rekor_pub_key) ; 
121+         let  bundle_json = build_correct_rekor_bundle ( ) ; 
122+         let  bundle = RekorBundle :: new_verified ( & bundle_json,  & rekor_pub_key) ; 
94123
95124        assert ! ( bundle. is_ok( ) ) ; 
96125    } 
97126
98127    #[ test]  
99-     fn  bundle_new_verified_failure ( )  { 
128+     fn  rekor_bundle_new_verified_failure ( )  { 
100129        let  public_key = r#"-----BEGIN PUBLIC KEY----- 
101130MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAENptdY/l3nB0yqkXLBWkZWQwo6+cu 
102131OSWS1X9vPavpiQOoTTGC0xX57OojUadxF1cdQmrsiReWg2Wn4FneJfa8xw== 
@@ -105,9 +134,24 @@ OSWS1X9vPavpiQOoTTGC0xX57OojUadxF1cdQmrsiReWg2Wn4FneJfa8xw==
105134            CosignVerificationKey :: from_pem ( public_key. as_bytes ( ) ,  & SigningScheme :: default ( ) ) 
106135                . expect ( "Cannot create CosignVerificationKey" ) ; 
107136
108-         let  bundle_json = build_correct_bundle ( ) ; 
109-         let  bundle = Bundle :: new_verified ( & bundle_json,  & not_rekor_pub_key) ; 
137+         let  bundle_json = build_correct_rekor_bundle ( ) ; 
138+         let  bundle = RekorBundle :: new_verified ( & bundle_json,  & not_rekor_pub_key) ; 
110139
111140        assert ! ( bundle. is_err( ) ) ; 
112141    } 
142+ 
143+     #[ test]  
144+     fn  bundle_new_verified_success ( )  { 
145+         // Bundle as generated by running the following command, and taking the 
146+         // content from the generated 'artifact.bundle` file: 
147+         // cosign sign-blob --bundle=artifact.bundle artifact.txt 
148+         let  bundle_raw = r#" 
149+ {"base64Signature":"MEQCIGp1XZP5zaImosrBhDPCdXn3f8xI9FHGLsGVx6UeRPCgAiAt5GrsdQhOKnZcA3EWecvgJSHzCIjWifFBQkD7Hdsymg==","cert":"LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNxRENDQWkrZ0F3SUJBZ0lVVFBXVGZPLzFOUmFTRmRlY2FBUS9wQkRHSnA4d0NnWUlLb1pJemowRUF3TXcKTnpFVk1CTUdBMVVFQ2hNTWMybG5jM1J2Y21VdVpHVjJNUjR3SEFZRFZRUURFeFZ6YVdkemRHOXlaUzFwYm5SbApjbTFsWkdsaGRHVXdIaGNOTWpJeE1USTFNRGN6TnpFeVdoY05Nakl4TVRJMU1EYzBOekV5V2pBQU1Ga3dFd1lICktvWkl6ajBDQVFZSUtvWkl6ajBEQVFjRFFnQUVKUVE0Vy81WFA5bTRZYldSQlF0SEdXd245dVVoYWUzOFVwY0oKcEVNM0RPczR6VzRNSXJNZlc0V1FEMGZ3cDhQVVVSRFh2UTM5NHBvcWdHRW1Ta3J1THFPQ0FVNHdnZ0ZLTUE0RwpBMVVkRHdFQi93UUVBd0lIZ0RBVEJnTlZIU1VFRERBS0JnZ3JCZ0VGQlFjREF6QWRCZ05WSFE0RUZnUVVvM0tuCmpKUVowWGZpZ2JENWIwT1ZOTjB4cVNvd0h3WURWUjBqQkJnd0ZvQVUzOVBwejFZa0VaYjVxTmpwS0ZXaXhpNFkKWkQ4d0p3WURWUjBSQVFIL0JCMHdHNEVaWkdGdWFXVnNMbUpsZG1WdWFYVnpRR2R0WVdsc0xtTnZiVEFzQmdvcgpCZ0VFQVlPL01BRUJCQjVvZEhSd2N6b3ZMMmRwZEdoMVlpNWpiMjB2Ykc5bmFXNHZiMkYxZEdnd2dZc0dDaXNHCkFRUUIxbmtDQkFJRWZRUjdBSGtBZHdEZFBUQnF4c2NSTW1NWkhoeVpaemNDb2twZXVONDhyZitIaW5LQUx5bnUKamdBQUFZU3R1Qkh5QUFBRUF3QklNRVlDSVFETTVZU1EvR0w2S0k1UjlPZGNuL3BTaytxVkQ2YnNMODMrRXA5UgoyaFdUYXdJaEFLMWppMWxaNTZEc2Z1TGZYN2JCQzluYlIzRWx4YWxCaHYxelFYTVU3dGx3TUFvR0NDcUdTTTQ5CkJBTURBMmNBTUdRQ01CSzh0c2dIZWd1aCtZaGVsM1BpakhRbHlKMVE1SzY0cDB4cURkbzdXNGZ4Zm9BUzl4clAKczJQS1FjZG9EOWJYd2dJd1g2ekxqeWJaa05IUDV4dEJwN3ZLMkZZZVp0ME9XTFJsVWxsY1VETDNULzdKUWZ3YwpHU3E2dlZCTndKMDB3OUhSCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K","rekorBundle":{"SignedEntryTimestamp":"MEUCIC3c+21v9pk6o4BpB/dRAM9lGnyWLi3Xnc+i8LmnNJmeAiEAiqZJbZHx3Idnw+zXv6yM0ipPw/p16R28YGuCJFQ1u8U=","Payload":{"body":"eyJhcGlWZXJzaW9uIjoiMC4wLjEiLCJraW5kIjoiaGFzaGVkcmVrb3JkIiwic3BlYyI6eyJkYXRhIjp7Imhhc2giOnsiYWxnb3JpdGhtIjoic2hhMjU2IiwidmFsdWUiOiI0YmM0NTNiNTNjYjNkOTE0YjQ1ZjRiMjUwMjk0MjM2YWRiYTJjMGUwOWZmNmYwMzc5Mzk0OWU3ZTM5ZmQ0Y2MxIn19LCJzaWduYXR1cmUiOnsiY29udGVudCI6Ik1FUUNJR3AxWFpQNXphSW1vc3JCaERQQ2RYbjNmOHhJOUZIR0xzR1Z4NlVlUlBDZ0FpQXQ1R3JzZFFoT0tuWmNBM0VXZWN2Z0pTSHpDSWpXaWZGQlFrRDdIZHN5bWc9PSIsInB1YmxpY0tleSI6eyJjb250ZW50IjoiTFMwdExTMUNSVWRKVGlCRFJWSlVTVVpKUTBGVVJTMHRMUzB0Q2sxSlNVTnhSRU5EUVdrclowRjNTVUpCWjBsVlZGQlhWR1pQTHpGT1VtRlRSbVJsWTJGQlVTOXdRa1JIU25BNGQwTm5XVWxMYjFwSmVtb3dSVUYzVFhjS1RucEZWazFDVFVkQk1WVkZRMmhOVFdNeWJHNWpNMUoyWTIxVmRWcEhWakpOVWpSM1NFRlpSRlpSVVVSRmVGWjZZVmRrZW1SSE9YbGFVekZ3WW01U2JBcGpiVEZzV2tkc2FHUkhWWGRJYUdOT1RXcEplRTFVU1RGTlJHTjZUbnBGZVZkb1kwNU5ha2w0VFZSSk1VMUVZekJPZWtWNVYycEJRVTFHYTNkRmQxbElDa3R2V2tsNmFqQkRRVkZaU1V0dldrbDZhakJFUVZGalJGRm5RVVZLVVZFMFZ5ODFXRkE1YlRSWllsZFNRbEYwU0VkWGQyNDVkVlZvWVdVek9GVndZMG9LY0VWTk0wUlBjelI2VnpSTlNYSk5abGMwVjFGRU1HWjNjRGhRVlZWU1JGaDJVVE01TkhCdmNXZEhSVzFUYTNKMVRIRlBRMEZWTkhkblowWkxUVUUwUndwQk1WVmtSSGRGUWk5M1VVVkJkMGxJWjBSQlZFSm5UbFpJVTFWRlJFUkJTMEpuWjNKQ1owVkdRbEZqUkVGNlFXUkNaMDVXU0ZFMFJVWm5VVlZ2TTB0dUNtcEtVVm93V0dacFoySkVOV0l3VDFaT1RqQjRjVk52ZDBoM1dVUldVakJxUWtKbmQwWnZRVlV6T1ZCd2VqRlphMFZhWWpWeFRtcHdTMFpYYVhocE5Ga0tXa1E0ZDBwM1dVUldVakJTUVZGSUwwSkNNSGRITkVWYVdrZEdkV0ZYVm5OTWJVcHNaRzFXZFdGWVZucFJSMlIwV1Zkc2MweHRUblppVkVGelFtZHZjZ3BDWjBWRlFWbFBMMDFCUlVKQ1FqVnZaRWhTZDJONmIzWk1NbVJ3WkVkb01WbHBOV3BpTWpCMllrYzVibUZYTkhaaU1rWXhaRWRuZDJkWmMwZERhWE5IQ2tGUlVVSXhibXREUWtGSlJXWlJVamRCU0d0QlpIZEVaRkJVUW5GNGMyTlNUVzFOV2tob2VWcGFlbU5EYjJ0d1pYVk9ORGh5Wml0SWFXNUxRVXg1Ym5VS2FtZEJRVUZaVTNSMVFraDVRVUZCUlVGM1FrbE5SVmxEU1ZGRVRUVlpVMUV2UjB3MlMwazFVamxQWkdOdUwzQlRheXR4VmtRMlluTk1PRE1yUlhBNVVnb3lhRmRVWVhkSmFFRkxNV3BwTVd4YU5UWkVjMloxVEdaWU4ySkNRemx1WWxJelJXeDRZV3hDYUhZeGVsRllUVlUzZEd4M1RVRnZSME5EY1VkVFRUUTVDa0pCVFVSQk1tTkJUVWRSUTAxQ1N6aDBjMmRJWldkMWFDdFphR1ZzTTFCcGFraFJiSGxLTVZFMVN6WTBjREI0Y1VSa2J6ZFhOR1o0Wm05QlV6bDRjbEFLY3pKUVMxRmpaRzlFT1dKWWQyZEpkMWcyZWt4cWVXSmFhMDVJVURWNGRFSndOM1pMTWtaWlpWcDBNRTlYVEZKc1ZXeHNZMVZFVEROVUx6ZEtVV1ozWXdwSFUzRTJkbFpDVG5kS01EQjNPVWhTQ2kwdExTMHRSVTVFSUVORlVsUkpSa2xEUVZSRkxTMHRMUzBLIn19fX0=","integratedTime":1669361833,"logIndex":7810348,"logID":"c0d23d6ad406973f9559f3ba2d1ca01f84147d8ffc5b8445c224f98b9591801d"}}} 
150+         "# ; 
151+         let  rekor_pub_key = get_rekor_public_key ( ) ; 
152+         let  result = Bundle :: new_verified ( & bundle_raw,  & rekor_pub_key) ; 
153+         assert ! ( result. is_ok( ) ) ; 
154+         let  bundle = result. unwrap ( ) ; 
155+         assert_eq ! ( bundle. rekor_bundle. payload. log_index,  7810348 ) ; 
156+     } 
113157} 
0 commit comments