Created
November 8, 2022 13:51
-
-
Save kevincharm/46359652273c519661dab7f5f9a92fae to your computer and use it in GitHub Desktop.
ASN.1 (DER) Parser
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// SPDX-License-Identifier: Apache-2.0 | |
pragma solidity ^0.8; | |
import {BufferSlice} from "./BufferSlice.sol"; | |
/// @title ASN1 | |
/// @author kevincharm | |
/// @notice ASN.1 (DER) decoding utils | |
library ASN1 { | |
using BufferSlice for bytes; | |
using BufferSlice for BufferSlice.Slice; | |
using ASN1 for BufferSlice.Slice; | |
/// @param self Buffer slice containing ASN.1 object | |
/// @return No. of object header bytes | |
/// @return Value length (in bytes) | |
function getObjectLength(BufferSlice.Slice memory self) | |
internal | |
pure | |
returns (uint256, uint256) | |
{ | |
uint256 cur; | |
uint256 contentLength; | |
uint256 buf = self.ptr; | |
assembly { | |
function mload8(p) -> val { | |
val := and(shr(248, mload(p)), 0xff) | |
} | |
let header := mload(buf) | |
let ptr := buf | |
ptr := add(ptr, 2) | |
let tag := and(shr(248, header), 0xff) // uint8(header[0]) | |
let lenBytes := and(shr(240, header), 0xff) // uint8(header[1]) | |
switch eq(and(lenBytes, 0x80), 0x80) | |
case 1 { | |
// 1bbbbbbb | |
// ^^^^^^^ number of following bytes that describe the value length | |
lenBytes := and(lenBytes, 0x7f) | |
for { | |
let i := 0 | |
} lt(i, lenBytes) { | |
i := add(i, 1) | |
} { | |
let b := mload8(ptr) | |
ptr := add(ptr, 1) | |
let x := shl(mul(8, sub(sub(lenBytes, i), 1)), b) | |
contentLength := or(contentLength, x) | |
} | |
} | |
default { | |
contentLength := lenBytes | |
} | |
cur := sub(ptr, buf) | |
if eq(tag, 0x03) { | |
// BITSTRING: 1B follows the last "content length" byte, and represents | |
// number of unused bits in the last content byte | |
cur := add(cur, 1) | |
// We *eat* this byte from the content bytes | |
contentLength := sub(contentLength, 1) | |
} | |
} | |
return (cur, contentLength); | |
} | |
/// @notice Get bytes representing the value part of the ASN.1 object | |
/// @param self buffer containing ASN.1 object | |
function getObjectValue(BufferSlice.Slice memory self) | |
internal | |
pure | |
returns (BufferSlice.Slice memory) | |
{ | |
(uint256 nHeaderBytes, uint256 contentLength) = getObjectLength(self); | |
return self.getSlice(nHeaderBytes, contentLength); | |
} | |
/// @notice Get bytes representing next ASN.1 object (skip current one) | |
/// @param self buffer containing ASN.1 object | |
function getNextObject(BufferSlice.Slice memory self) | |
internal | |
pure | |
returns (BufferSlice.Slice memory) | |
{ | |
(uint256 lenBytes, uint256 contentLength) = getObjectLength(self); | |
uint256 nextObjectOffset = lenBytes + contentLength; | |
return self.getSlice(nextObjectOffset, self.length - nextObjectOffset); | |
} | |
/// @notice Parse RSA public key | |
/// | |
/// Sequence PublicKeyInfo | |
/// ├─ Sequence AlgorithmIdentifier | |
/// | ├─ ObjectIdentifier | |
/// | └─ NULL (optional algorithm parameters) | |
/// └─ BitString PublicKey (also encoded as another ASN.1 struct) | |
/// └─ Sequence | |
/// ├─ Integer Modulus | |
/// └─ Integer Exponent | |
/// | |
/// @param self buffer containing ASN.1 object | |
/// @return exponent | |
/// @return modulus | |
function getRSAPubKey(bytes memory self) | |
internal | |
pure | |
returns (bytes memory, bytes memory) | |
{ | |
// Skip to the Seq object inside PublicKey BitString data: | |
// 1. Get object value of PublicKeyInfo | |
// 2. Skip to PublicKeyInfo[1] | |
// 3. Get object value of PublicKeyInfo[1] -> PublicKey | |
// 4. Get object value of PublicKey (i.e., a Sequence) | |
BufferSlice.Slice memory pubKeyBitString = self | |
.toSlice() | |
.getObjectValue() | |
.getNextObject() | |
.getObjectValue() | |
.getObjectValue(); | |
// Get modulus (first child of PublicKey BitString's Sequence) | |
bytes memory modulus = pubKeyBitString.getObjectValue().toBuffer(); | |
// Get exponent (next child) | |
bytes memory exponent = pubKeyBitString | |
.getNextObject() | |
.getObjectValue() | |
.toBuffer(); | |
return (exponent, modulus); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// SPDX-License-Identifier: Apache-2.0 | |
pragma solidity ^0.8; | |
/// @title BufferSlice | |
/// @author kevincharm | |
/// @notice Create views into buffer slices | |
library BufferSlice { | |
/// @notice View into a buffer | |
struct Slice { | |
uint256 length; | |
uint256 ptr; | |
} | |
/// @notice Get a view into a buffer slice without copying the source buffer | |
/// @param self Source buffer | |
/// @return slice Mutable buffer slice | |
function toSlice(bytes memory self) | |
internal | |
pure | |
returns (Slice memory slice) | |
{ | |
uint256 len; | |
uint256 ptr; | |
assembly { | |
len := mload(self) | |
ptr := add(self, 0x20) | |
} | |
return Slice({length: len, ptr: ptr}); | |
} | |
/// @notice Create a buffer from a slice (creates a copy) | |
/// @param self Slice | |
/// @return ret Copy of slice as a new buffer | |
function toBuffer(Slice memory self) | |
internal | |
pure | |
returns (bytes memory ret) | |
{ | |
// Adapted from {BytesUtils#memcpy} from: | |
// @ensdomains/ens-contracts/contracts/dnssec-oracle/BytesUtils.sol | |
uint256 len = self.length; | |
ret = new bytes(len); | |
uint256 src = self.ptr; | |
uint256 dest; | |
assembly { | |
dest := add(ret, 0x20) | |
// Copy word-length chunks while possible | |
for { | |
// | |
} lt(32, len) { | |
len := sub(len, 32) | |
} { | |
mstore(dest, mload(src)) | |
dest := add(dest, 32) | |
src := add(src, 32) | |
} | |
// Copy remaining bytes | |
let mask := sub(exp(256, sub(32, len)), 1) | |
let srcpart := and(mload(src), not(mask)) | |
let destpart := and(mload(dest), mask) | |
mstore(dest, or(destpart, srcpart)) | |
} | |
} | |
/// @notice Get a new slice of a buffer from an existing slice and some offset | |
/// @param offset Offset into the slice | |
/// @param length Length of slice after the offset | |
/// @return new slice | |
function getSlice( | |
Slice memory self, | |
uint256 offset, | |
uint256 length | |
) internal pure returns (Slice memory) { | |
require( | |
self.ptr + self.length >= self.ptr + offset + length, | |
"Slice out-of-bounds" | |
); | |
return Slice({length: length, ptr: self.ptr + offset}); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment