@@ -2,33 +2,59 @@ use crate::tests::util::MockRequestExt;
2
2
use crate :: tests:: { RequestHelper , TestApp } ;
3
3
use crate :: util:: token:: HashedToken ;
4
4
use crate :: { models:: ApiToken , schema:: api_tokens} ;
5
+ use base64:: { Engine as _, engine:: general_purpose} ;
5
6
use crates_io_github:: { GitHubPublicKey , MockGitHubClient } ;
6
7
use diesel:: prelude:: * ;
7
8
use diesel_async:: RunQueryDsl ;
8
9
use googletest:: prelude:: * ;
9
10
use insta:: { assert_json_snapshot, assert_snapshot} ;
11
+ use p256:: ecdsa:: { Signature , SigningKey , signature:: Signer } ;
12
+ use p256:: pkcs8:: DecodePrivateKey ;
13
+ use std:: sync:: LazyLock ;
10
14
11
15
static URL : & str = "/api/github/secret-scanning/verify" ;
12
16
13
- // Test request and signature from https://docs.github.com/en/developers/overview/ secret- scanning-partner-program#create-a-secret-alert-service
17
+ // Test request payload for GitHub secret scanning
14
18
static GITHUB_ALERT : & [ u8 ] =
15
19
br#"[{"token":"some_token","type":"some_type","url":"some_url","source":"some_source"}]"# ;
16
20
17
- static GITHUB_PUBLIC_KEY_IDENTIFIER : & str =
18
- "f9525bf080f75b3506ca1ead061add62b8633a346606dc5fe544e29231c6ee0d" ;
19
-
20
- /// Test key from https://docs.github.com/en/developers/overview/secret-scanning-partner-program#create-a-secret-alert-service
21
- static GITHUB_PUBLIC_KEY : & str = "-----BEGIN PUBLIC KEY-----\n MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEsz9ugWDj5jK5ELBK42ynytbo38gP\n HzZFI03Exwz8Lh/tCfL3YxwMdLjB+bMznsanlhK0RwcGP3IDb34kQDIo3Q==\n -----END PUBLIC KEY-----" ;
22
-
23
- static GITHUB_PUBLIC_KEY_SIGNATURE : & str = "MEUCIFLZzeK++IhS+y276SRk2Pe5LfDrfvTXu6iwKKcFGCrvAiEAhHN2kDOhy2I6eGkOFmxNkOJ+L2y8oQ9A2T9GGJo6WJY=" ;
21
+ /// Private key for signing payloads (ECDSA P-256)
22
+ ///
23
+ /// Generated specifically for testing - do not use in production.
24
+ ///
25
+ /// This corresponds to the public key below and is used to generate valid signatures
26
+ static PRIVATE_KEY : & str = r#"-----BEGIN PRIVATE KEY-----
27
+ MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgV64BdEFXg9aT/m4p
28
+ wOQ/o9WUHxZ6qfBaP3D7Km1TOWuhRANCAARYKkbkTbIr//8klg1CMYGQIwtlfNd4
29
+ JQYV5+q0s3+JnBSLb1/sx/lEDzmMVZQIZQrACUHFW4UVdmox2NvmNWyy
30
+ -----END PRIVATE KEY-----"# ;
31
+
32
+ /// Public key (corresponds to the private key above)
33
+ static PUBLIC_KEY : & str = r#"-----BEGIN PUBLIC KEY-----
34
+ MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEWCpG5E2yK///JJYNQjGBkCMLZXzX
35
+ eCUGFefqtLN/iZwUi29f7Mf5RA85jFWUCGUKwAlBxVuFFXZqMdjb5jVssg==
36
+ -----END PUBLIC KEY-----"# ;
37
+
38
+ /// Public key identifier (SHA256 hash of the DER-encoded public key)
39
+ static KEY_IDENTIFIER : & str = "2aafbbe2d329af78d875cd2dd0291048799176466844315b6a846d6e12aa26ca" ;
40
+
41
+ /// Signing key derived from the private key
42
+ static SIGNING_KEY : LazyLock < SigningKey > =
43
+ LazyLock :: new ( || SigningKey :: from_pkcs8_pem ( PRIVATE_KEY ) . unwrap ( ) ) ;
44
+
45
+ /// Generate a signature for the payload using our private key
46
+ fn sign_payload ( payload : & [ u8 ] ) -> String {
47
+ let signature: Signature = SIGNING_KEY . sign ( payload) ;
48
+ general_purpose:: STANDARD . encode ( signature. to_der ( ) )
49
+ }
24
50
25
51
fn github_mock ( ) -> MockGitHubClient {
26
52
let mut mock = MockGitHubClient :: new ( ) ;
27
53
28
54
mock. expect_public_keys ( ) . returning ( |_, _| {
29
55
let key = GitHubPublicKey {
30
- key_identifier : GITHUB_PUBLIC_KEY_IDENTIFIER . to_string ( ) ,
31
- key : GITHUB_PUBLIC_KEY . to_string ( ) ,
56
+ key_identifier : KEY_IDENTIFIER . to_string ( ) ,
57
+ key : PUBLIC_KEY . to_string ( ) ,
32
58
is_current : true ,
33
59
} ;
34
60
@@ -70,8 +96,8 @@ async fn github_secret_alert_revokes_token() {
70
96
71
97
let mut request = anon. post_request ( URL ) ;
72
98
* request. body_mut ( ) = GITHUB_ALERT . into ( ) ;
73
- request. header ( "GITHUB-PUBLIC-KEY-IDENTIFIER" , GITHUB_PUBLIC_KEY_IDENTIFIER ) ;
74
- request. header ( "GITHUB-PUBLIC-KEY-SIGNATURE" , GITHUB_PUBLIC_KEY_SIGNATURE ) ;
99
+ request. header ( "GITHUB-PUBLIC-KEY-IDENTIFIER" , KEY_IDENTIFIER ) ;
100
+ request. header ( "GITHUB-PUBLIC-KEY-SIGNATURE" , & sign_payload ( GITHUB_ALERT ) ) ;
75
101
let response = anon. run :: < ( ) > ( request) . await ;
76
102
assert_snapshot ! ( response. status( ) , @"200 OK" ) ;
77
103
assert_json_snapshot ! ( response. json( ) ) ;
@@ -134,8 +160,8 @@ async fn github_secret_alert_for_revoked_token() {
134
160
135
161
let mut request = anon. post_request ( URL ) ;
136
162
* request. body_mut ( ) = GITHUB_ALERT . into ( ) ;
137
- request. header ( "GITHUB-PUBLIC-KEY-IDENTIFIER" , GITHUB_PUBLIC_KEY_IDENTIFIER ) ;
138
- request. header ( "GITHUB-PUBLIC-KEY-SIGNATURE" , GITHUB_PUBLIC_KEY_SIGNATURE ) ;
163
+ request. header ( "GITHUB-PUBLIC-KEY-IDENTIFIER" , KEY_IDENTIFIER ) ;
164
+ request. header ( "GITHUB-PUBLIC-KEY-SIGNATURE" , & sign_payload ( GITHUB_ALERT ) ) ;
139
165
let response = anon. run :: < ( ) > ( request) . await ;
140
166
assert_snapshot ! ( response. status( ) , @"200 OK" ) ;
141
167
assert_json_snapshot ! ( response. json( ) ) ;
@@ -187,8 +213,8 @@ async fn github_secret_alert_for_unknown_token() {
187
213
188
214
let mut request = anon. post_request ( URL ) ;
189
215
* request. body_mut ( ) = GITHUB_ALERT . into ( ) ;
190
- request. header ( "GITHUB-PUBLIC-KEY-IDENTIFIER" , GITHUB_PUBLIC_KEY_IDENTIFIER ) ;
191
- request. header ( "GITHUB-PUBLIC-KEY-SIGNATURE" , GITHUB_PUBLIC_KEY_SIGNATURE ) ;
216
+ request. header ( "GITHUB-PUBLIC-KEY-IDENTIFIER" , KEY_IDENTIFIER ) ;
217
+ request. header ( "GITHUB-PUBLIC-KEY-SIGNATURE" , & sign_payload ( GITHUB_ALERT ) ) ;
192
218
let response = anon. run :: < ( ) > ( request) . await ;
193
219
assert_snapshot ! ( response. status( ) , @"200 OK" ) ;
194
220
assert_json_snapshot ! ( response. json( ) ) ;
@@ -225,30 +251,30 @@ async fn github_secret_alert_invalid_signature_fails() {
225
251
226
252
// Headers but no request body
227
253
let mut request = anon. post_request ( URL ) ;
228
- request. header ( "GITHUB-PUBLIC-KEY-IDENTIFIER" , GITHUB_PUBLIC_KEY_IDENTIFIER ) ;
229
- request. header ( "GITHUB-PUBLIC-KEY-SIGNATURE" , GITHUB_PUBLIC_KEY_SIGNATURE ) ;
254
+ request. header ( "GITHUB-PUBLIC-KEY-IDENTIFIER" , KEY_IDENTIFIER ) ;
255
+ request. header ( "GITHUB-PUBLIC-KEY-SIGNATURE" , & sign_payload ( GITHUB_ALERT ) ) ;
230
256
let response = anon. run :: < ( ) > ( request) . await ;
231
257
assert_snapshot ! ( response. status( ) , @"400 Bad Request" ) ;
232
258
233
259
// Request body but only key identifier header
234
260
let mut request = anon. post_request ( URL ) ;
235
261
* request. body_mut ( ) = GITHUB_ALERT . into ( ) ;
236
- request. header ( "GITHUB-PUBLIC-KEY-IDENTIFIER" , GITHUB_PUBLIC_KEY_IDENTIFIER ) ;
262
+ request. header ( "GITHUB-PUBLIC-KEY-IDENTIFIER" , KEY_IDENTIFIER ) ;
237
263
let response = anon. run :: < ( ) > ( request) . await ;
238
264
assert_snapshot ! ( response. status( ) , @"400 Bad Request" ) ;
239
265
240
266
// Invalid signature
241
267
let mut request = anon. post_request ( URL ) ;
242
268
* request. body_mut ( ) = GITHUB_ALERT . into ( ) ;
243
- request. header ( "GITHUB-PUBLIC-KEY-IDENTIFIER" , GITHUB_PUBLIC_KEY_IDENTIFIER ) ;
269
+ request. header ( "GITHUB-PUBLIC-KEY-IDENTIFIER" , KEY_IDENTIFIER ) ;
244
270
request. header ( "GITHUB-PUBLIC-KEY-SIGNATURE" , "bad signature" ) ;
245
271
let response = anon. run :: < ( ) > ( request) . await ;
246
272
assert_snapshot ! ( response. status( ) , @"400 Bad Request" ) ;
247
273
248
274
// Invalid signature that is valid base64
249
275
let mut request = anon. post_request ( URL ) ;
250
276
* request. body_mut ( ) = GITHUB_ALERT . into ( ) ;
251
- request. header ( "GITHUB-PUBLIC-KEY-IDENTIFIER" , GITHUB_PUBLIC_KEY_IDENTIFIER ) ;
277
+ request. header ( "GITHUB-PUBLIC-KEY-IDENTIFIER" , KEY_IDENTIFIER ) ;
252
278
request. header ( "GITHUB-PUBLIC-KEY-SIGNATURE" , "YmFkIHNpZ25hdHVyZQ==" ) ;
253
279
let response = anon. run :: < ( ) > ( request) . await ;
254
280
assert_snapshot ! ( response. status( ) , @"400 Bad Request" ) ;
0 commit comments