-
Notifications
You must be signed in to change notification settings - Fork 313
/
index.js
118 lines (106 loc) · 3.31 KB
/
index.js
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
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
*/
'use strict';
const crypto = require('crypto');
const GENERATED = '@' + 'generated';
const OLDTOKEN = '<<SignedSource::*O*zOeWoEQle#+L!plEphiEmie@I>>';
const NEWTOKEN = '<<SignedSource::*O*zOeWoEQle#+L!plEphiEmie@IsG>>';
const TOKENS = [NEWTOKEN, OLDTOKEN];
const PATTERN = new RegExp(`${GENERATED} (?:SignedSource<<([a-f0-9]{32})>>)`);
const TokenNotFoundError = new Error(
`SignedSource.signFile(...): Cannot sign file without token: ${NEWTOKEN}`
);
function hash(data, encoding) {
const md5sum = crypto.createHash('md5');
md5sum.update(data, encoding);
return md5sum.digest('hex');
}
/**
* Utility for signing and verifying the signature of a file. This is useful for
* ensuring that the contents of a generated file are not contaminated by manual
* changes. Example usage:
*
* const myFile = `
* // ${SignedSource.getSigningToken()}
*
* console.log('My generated file.');
* `;
* const mySignedFile = SignedSource.signFile(myFile);
*
*/
const SignedSource = {
TokenNotFoundError,
/**
* Gets the signing token to be embedded in the file you wish to be signed.
*/
getSigningToken() {
return `${GENERATED} ${NEWTOKEN}`;
},
/**
* Checks whether a file is signed *without* verifying the signature.
*/
isSigned(data) {
return PATTERN.exec(data) != null;
},
/**
* Signs a source file which contains a signing token. Signing modifies only
* the signing token, so the token should be placed inside a comment in order
* for signing to not change code semantics.
*/
signFile(data) {
if (!data.includes(NEWTOKEN)) {
if (SignedSource.isSigned(data)) {
// Signing a file that was previously signed.
data = data.replace(PATTERN, SignedSource.getSigningToken());
} else {
throw TokenNotFoundError;
}
}
return data.replace(NEWTOKEN, `SignedSource<<${hash(data, 'utf8')}>>`);
},
/**
* Verifies the signature in a signed file.
*/
verifySignature(data) {
const matches = PATTERN.exec(data);
if (!matches) {
throw new Error(
'SignedSource.verifySignature(...): Cannot verify signature of an ' +
'unsigned file.'
);
}
const actual = matches[1];
// Replace signature with `NEWTOKEN` and hash to see if it matches the hash
// in the file. For backwards compatibility, also try `OLDTOKEN`.
return TOKENS.some(token => {
const unsigned = data.replace(PATTERN, `${GENERATED} ${token}`);
return hash(unsigned, 'utf8') === actual;
});
},
};
// @deprecated
SignedSource.SIGN_OK = {message: 'ok'};
SignedSource.SIGN_INVALID = new Error('invalid');
SignedSource.SIGN_UNSIGNED = new Error('unsigned');
SignedSource.signing_token = SignedSource.getSigningToken;
SignedSource.is_signed = SignedSource.isSigned;
SignedSource.sign = data => ({
first_time: data.includes(NEWTOKEN),
signed_data: SignedSource.signFile(data),
});
SignedSource.verify_signature = data => {
try {
return SignedSource.verifySignature(data)
? SignedSource.SIGN_OK
: SignedSource.SIGN_INVALID;
} catch (_) {
return SignedSource.SIGN_UNSIGNED;
}
};
module.exports = SignedSource;