forked from hhvm/hack-codegen
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathSignedSourceBase.hack
More file actions
136 lines (118 loc) · 4.13 KB
/
SignedSourceBase.hack
File metadata and controls
136 lines (118 loc) · 4.13 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
/*
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*/
namespace Facebook\HackCodegen;
use namespace HH\Lib\{C, Keyset, Regex, Str, Vec};
abstract class SignedSourceBase {
const TOKEN = '<<SignedSource::*O*zOeWoEQle#+L!plEphiEmie@I>>';
abstract protected static function getTokenName(): string;
/**
* Override this method to process the file data before signing a file
* or before checking the signature.
*/
protected static function preprocess(string $file_data): string {
return $file_data;
}
public static function getPattern(
): Regex\Pattern<shape('token_name' => string, 'signature' => string, ...)> {
return
re"/@(?<token_name>\\S+) (?:SignedSource<<(?<signature>[a-f0-9]{32})>>)/";
}
/**
* Get the signing token, which you must embed in the file you wish to sign.
* Generally, you should put this in a header comment.
*
* @return string Signing token.
*
*/
public static function getSigningToken(): string {
return '@'.static::getTokenName().' '.static::TOKEN;
}
/**
* Sign a source file into which you have previously embedded a signing
* token. Signing modifies only the signing token, so the semantics of
* the file will not change if the token is put in a comment.
*
* @param string Data with embedded token, to be signed.
* @return string Signed data.
*
*/
public static function signFile(string $file_data): string {
$signature = \md5(static::preprocess($file_data));
$replaced_data =
\str_replace(static::TOKEN, 'SignedSource<<'.$signature.'>>', $file_data);
if ($replaced_data === $file_data) {
throw new \Exception(
'Before signing a file, you must embed a signing token within it.',
);
}
return $replaced_data;
}
/**
* Determine if a file is signed or not. This does NOT verify the signature.
*
* @param string File data.
* @return bool True if the file has a signature.
*
*/
public static function isSigned(string $file_data): bool {
return C\any(
Regex\every_match($file_data, static::getPattern()),
$match ==> $match['token_name'] === static::getTokenName(),
);
}
/**
* Verify a file's signature. You should first use isSigned() to determine
* if a file is signed.
*
* @param string File data.
* @return bool True if the file's signature is correct.
*
*/
public static function verifySignature(string $file_data): bool {
$signatures =
Regex\every_match($file_data, static::getPattern())
|> Vec\filter($$, $m ==> $m['token_name'] === static::getTokenName())
|> Keyset\map($$, $m ==> $m['signature']);
if (C\is_empty($signatures)) {
throw new \Exception('Can not verify the signature of an unsigned file.');
} else if (C\count($signatures) > 1) {
throw new \Exception('File has multiple different signatures.');
}
$signature = C\onlyx($signatures);
$expected_signature = $file_data
|> Str\replace($$, 'SignedSource<<'.$signature.'>>', static::TOKEN)
|> static::preprocess($$)
|> \md5($$);
return $signature === $expected_signature;
}
/**
* Check if a file has a valid signature. Use this when you expect the file
* to be signed and valid.
*
* @param string File data.
* @return bool True if the file has a signature.
*
*/
public static function hasValidSignature(string $file_data): bool {
return static::isSigned($file_data) && static::verifySignature($file_data);
}
public static function isSignedByAnySigner(string $data): bool {
return SignedSource::isSigned($data) ||
PartiallyGeneratedSignedSource::isSigned($data);
}
public static function hasValidSignatureFromAnySigner(string $data): bool {
if (SignedSource::isSigned($data)) {
return SignedSource::verifySignature($data);
}
if (PartiallyGeneratedSignedSource::isSigned($data)) {
return PartiallyGeneratedSignedSource::verifySignature($data);
}
return false;
}
}