-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathX509CRLHelper.sol
352 lines (310 loc) · 13.1 KB
/
X509CRLHelper.sol
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
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {Asn1Decode, NodePtr} from "../utils/Asn1Decode.sol";
import {BytesUtils} from "../utils/BytesUtils.sol";
import {DateTimeUtils} from "../utils/DateTimeUtils.sol";
/**
* @title Solidity Structure representing X509 CRL
* @notice This is a simplified structure of a DER-decoded X509 CRL
*/
struct X509CRLObj {
string issuerCommonName;
uint256 validityNotBefore;
uint256 validityNotAfter;
uint256[] serialNumbersRevoked;
bytes authorityKeyIdentifier;
// for signature verification in the cert chain
bytes signature;
bytes tbs;
}
/**
* @title X509 CRL Helper Contract
* @notice This is a standalone contract that can be used by off-chain applications and smart contracts
* to parse DER-encoded CRLs.
* @dev This parser is only valid for ECDSA signature algorithm and p256 key algorithm.
*/
contract X509CRLHelper {
using Asn1Decode for bytes;
using NodePtr for uint256;
using BytesUtils for bytes;
// 2.5.4.3
bytes constant COMMON_NAME_OID = hex"550403";
// 2.5.29.20
bytes constant CRL_NUMBER_OID = hex"551d14";
// 2.5.29.35
bytes constant AUTHORITY_KEY_IDENTIFIER_OID = hex"551D23";
/// =================================================================================
/// USE THE GETTERS BELOW IF YOU DON'T WANT TO PARSE THE ENTIRE X509 CRL
/// =================================================================================
function getTbsAndSig(bytes calldata der) external pure returns (bytes memory tbs, bytes memory sig) {
uint256 root = der.root();
uint256 tbsParentPtr = der.firstChildOf(root);
uint256 sigPtr = der.nextSiblingOf(tbsParentPtr);
sigPtr = der.nextSiblingOf(sigPtr);
tbs = der.allBytesAt(tbsParentPtr);
sig = _getSignature(der, sigPtr);
}
function getIssuerCommonName(bytes calldata der) external pure returns (string memory issuerCommonName) {
uint256 root = der.root();
uint256 tbsParentPtr = der.firstChildOf(root);
uint256 tbsPtr = der.firstChildOf(tbsParentPtr);
tbsPtr = der.nextSiblingOf(tbsPtr);
tbsPtr = der.nextSiblingOf(tbsPtr);
issuerCommonName = _getCommonName(der, tbsPtr);
}
function getCrlValidity(bytes calldata der) external pure returns (uint256 validityNotBefore, uint256 validityNotAfter) {
uint256 root = der.root();
uint256 tbsParentPtr = der.firstChildOf(root);
uint256 tbsPtr = der.firstChildOf(tbsParentPtr);
tbsPtr = der.nextSiblingOf(tbsPtr);
tbsPtr = der.nextSiblingOf(tbsPtr);
tbsPtr = der.nextSiblingOf(tbsPtr);
(validityNotBefore, validityNotAfter) = _getValidity(der, tbsPtr);
}
function serialNumberIsRevoked(uint256 serialNumber, bytes calldata der) external pure returns (bool revoked) {
uint256 root = der.root();
uint256 tbsParentPtr = der.firstChildOf(root);
uint256 tbsPtr = der.firstChildOf(tbsParentPtr);
tbsPtr = der.nextSiblingOf(tbsPtr);
tbsPtr = der.nextSiblingOf(tbsPtr);
tbsPtr = der.nextSiblingOf(tbsPtr);
tbsPtr = der.nextSiblingOf(tbsPtr);
tbsPtr = der.nextSiblingOf(tbsPtr);
if (bytes1(der[tbsPtr.ixs()]) == 0x30) {
uint256[] memory ret = _getRevokedSerialNumbers(
der,
tbsPtr,
true,
serialNumber
);
revoked = ret[0] == serialNumber;
}
}
/// @dev according to RFC 5280, the Authority Key Identifier is mandatory for CA certificates
/// @dev if not present, this method returns 0x00
function getAuthorityKeyIdentifier(bytes calldata der) external pure returns (bytes memory akid) {
uint256 extensionPtr = _getExtensionPtr(der);
uint256 extnValuePtr = _findExtensionValuePtr(der, extensionPtr, AUTHORITY_KEY_IDENTIFIER_OID);
if (extnValuePtr != 0) {
akid = _getAuthorityKeyIdentifier(der, extnValuePtr);
}
}
/// x509 CRL generally contain a sequence of elements in the following order:
/// 1. tbs
/// - 1a. serial number
/// - 1b. signature algorithm
/// - 1c. issuer
/// - - 1c(a). common name
/// - - 1c(b). organization name
/// - - 1c(c). locality name
/// - - 1c(d). state or province name
/// - - 1c(e). country name
/// - 1d. not before
/// - 1e. not after
/// - 1f. revoked certificates
/// - - A list consists of revoked serial numbers and reasons.
/// - 1g. CRL extensions
/// - - 1g(a) CRL number
/// - - 1g(b) Authority Key Identifier
/// 2. Signature Algorithm
/// 3. Signature
function parseCRLDER(bytes calldata der) external pure returns (X509CRLObj memory crl) {
uint256 root = der.root();
uint256 tbsParentPtr = der.firstChildOf(root);
crl.tbs = der.allBytesAt(tbsParentPtr);
uint256 tbsPtr = der.firstChildOf(tbsParentPtr);
tbsPtr = der.nextSiblingOf(tbsPtr);
tbsPtr = der.nextSiblingOf(tbsPtr);
crl.issuerCommonName = _getCommonName(der, tbsPtr);
tbsPtr = der.nextSiblingOf(tbsPtr);
(crl.validityNotBefore, crl.validityNotAfter) = _getValidity(der, tbsPtr);
tbsPtr = der.nextSiblingOf(tbsPtr);
tbsPtr = der.nextSiblingOf(tbsPtr);
if (bytes1(der[tbsPtr.ixs()]) == 0x30) {
// the revoked certificates field is present
crl.serialNumbersRevoked = _getRevokedSerialNumbers(
der,
tbsPtr,
false,
0
);
tbsPtr = der.nextSiblingOf(tbsPtr);
}
if (bytes1(der[tbsPtr.ixs()]) == 0xA0) {
uint256 authorityKeyIdentifierPtr = _findExtensionValuePtr(der, tbsPtr, AUTHORITY_KEY_IDENTIFIER_OID);
if (authorityKeyIdentifierPtr != 0) {
crl.authorityKeyIdentifier = _getAuthorityKeyIdentifier(der, authorityKeyIdentifierPtr);
}
} else {
revert("Extension is missing");
}
// tbs iteration completed
// now we just need to look for the signature
uint256 sigPtr = der.nextSiblingOf(tbsParentPtr);
sigPtr = der.nextSiblingOf(sigPtr);
crl.signature = _getSignature(der, sigPtr);
}
function _getCommonName(bytes calldata der, uint256 rdnParentPtr)
private
pure
returns (string memory)
{
// All we are doing here is iterating through a sequence of
// one or many RelativeDistinguishedName (RDN) sets
// which consists of one or many AttributeTypeAndValue sequences
// we are only interested in the sequence with the CommonName type
uint256 rdnPtr = der.firstChildOf(rdnParentPtr);
bool commonNameFound = false;
while (rdnPtr != 0) {
uint256 sequencePtr = der.firstChildOf(rdnPtr);
while (sequencePtr.ixl() <= rdnPtr.ixl()) {
uint256 oidPtr = der.firstChildOf(sequencePtr);
if (BytesUtils.compareBytes(der.bytesAt(oidPtr), COMMON_NAME_OID)) {
commonNameFound = true;
return string(der.bytesAt(der.nextSiblingOf(oidPtr)));
} else if (sequencePtr.ixl() == rdnPtr.ixl()) {
break;
} else {
sequencePtr = der.nextSiblingOf(sequencePtr);
}
}
if (rdnPtr.ixl() < rdnParentPtr.ixl()) {
rdnPtr = der.nextSiblingOf(rdnPtr);
} else {
rdnPtr = 0;
}
}
if (!commonNameFound) {
revert("Missing common name");
}
}
function _getValidity(bytes calldata der, uint256 validityPtr)
private
pure
returns (uint256 notBefore, uint256 notAfter)
{
uint256 notBeforePtr = validityPtr;
uint256 notAfterPtr = der.nextSiblingOf(notBeforePtr);
notBefore = DateTimeUtils.fromDERToTimestamp(der.bytesAt(notBeforePtr));
notAfter = DateTimeUtils.fromDERToTimestamp(der.bytesAt(notAfterPtr));
}
function _getRevokedSerialNumbers(
bytes calldata der,
uint256 revokedParentPtr,
bool breakIfFound,
uint256 filter
)
private
pure
returns (uint256[] memory serialNumbers)
{
uint256 revokedPtr = der.firstChildOf(revokedParentPtr);
bytes memory serials;
while (revokedPtr.ixl() <= revokedParentPtr.ixl()) {
uint256 serialPtr = der.firstChildOf(revokedPtr);
bytes memory serialBytes = der.bytesAt(serialPtr);
uint256 serialNumber = _parseSerialNumber(serialBytes);
serials = abi.encodePacked(serials, serialNumber);
if (breakIfFound && filter == serialNumber) {
serialNumbers = new uint256[](1);
serialNumbers[0] = filter;
return serialNumbers;
}
revokedPtr = der.nextSiblingOf(revokedPtr);
}
uint256 count = serials.length / 32;
// ABI encoding format for a dynamic uint256[] value
serials = abi.encodePacked(abi.encode(0x20), abi.encode(count), serials);
serialNumbers = new uint256[](count);
serialNumbers = abi.decode(serials, (uint256[]));
}
function _parseSerialNumber(bytes memory serialBytes) private pure returns (uint256 serial) {
uint256 shift = 8 * (32 - serialBytes.length);
serial = uint256(bytes32(serialBytes) >> shift);
}
function _getSignature(bytes calldata der, uint256 sigPtr) private pure returns (bytes memory sig) {
sigPtr = der.rootOfBitStringAt(sigPtr);
sigPtr = der.firstChildOf(sigPtr);
bytes memory r = _trimBytes(der.bytesAt(sigPtr), 32);
sigPtr = der.nextSiblingOf(sigPtr);
bytes memory s = _trimBytes(der.bytesAt(sigPtr), 32);
sig = abi.encodePacked(r, s);
}
function _getAuthorityKeyIdentifier(bytes calldata der, uint256 extnValuePtr) private pure returns (bytes memory akid) {
bytes memory extValue = der.bytesAt(extnValuePtr);
// The AUTHORITY_KEY_IDENTIFIER consists of a SEQUENCE with the following elements
// [0] - keyIdentifier (ESSENTIAL, but OPTIONAL as per RFC 5280)
// [1] - authorityCertIssuer (OPTIONAL as per RFC 5280)
// [2] - authorityCertSerialNumber (OPTIONAL as per RFC 5280)
// since we are interested in only the key identifier
// we iterate through the sequence until we find a tag matches with [0]
uint256 parentPtr = extValue.root();
uint256 ptr = extValue.firstChildOf(parentPtr);
bytes1 contextTag = 0x80;
while (true) {
bytes1 tag = bytes1(extValue[ptr.ixs()]);
if (tag == contextTag) {
akid = extValue.bytesAt(ptr);
break;
}
if (ptr.ixl() < parentPtr.ixl()) {
ptr = extValue.nextSiblingOf(ptr);
} else {
break;
}
}
}
/// @dev remove unnecessary prefix from the input
function _trimBytes(bytes memory input, uint256 expectedLength) private pure returns (bytes memory output) {
uint256 n = input.length;
if (n == expectedLength) {
output = input;
} else if (n < expectedLength) {
output = new bytes(expectedLength);
uint256 padLength = expectedLength - n;
for (uint256 i = 0; i < n; i++) {
output[padLength + i] = input[i];
}
} else {
uint256 lengthDiff = n - expectedLength;
output = input.substring(lengthDiff, expectedLength);
}
}
function _getExtensionPtr(bytes calldata der) private pure returns (uint256 extensionPtr) {
uint256 root = der.root();
uint256 tbsParentPtr = der.firstChildOf(root);
extensionPtr = der.firstChildOf(tbsParentPtr);
// iterate through the sequence until we find the extension tag (0xA3)
while (extensionPtr.ixl() <= tbsParentPtr.ixl()) {
bytes1 tag = bytes1(der[extensionPtr.ixs()]);
if (tag == 0xA0) {
return extensionPtr;
} else {
if (extensionPtr.ixl() == tbsParentPtr.ixl()) {
revert("Extension is missing");
} else {
extensionPtr = der.nextSiblingOf(extensionPtr);
}
}
}
}
function _findExtensionValuePtr(bytes calldata der, uint256 extensionPtr, bytes memory oid) private pure returns (uint256) {
uint256 parentPtr = der.firstChildOf(extensionPtr);
uint256 ptr = der.firstChildOf(parentPtr);
while (ptr != 0) {
uint256 oidPtr = der.firstChildOf(ptr);
if (der[oidPtr.ixs()] != 0x06) {
revert("Missing OID");
}
if (BytesUtils.compareBytes(der.bytesAt(oidPtr), oid)) {
return der.nextSiblingOf(oidPtr);
}
if (ptr.ixl() < parentPtr.ixl()) {
ptr = der.nextSiblingOf(ptr);
} else {
ptr = 0;
}
}
return 0; // not found
}
}