Skip to content

Instantly share code, notes, and snippets.

@kevincharm
Created November 8, 2022 13:51
Show Gist options
  • Save kevincharm/46359652273c519661dab7f5f9a92fae to your computer and use it in GitHub Desktop.
Save kevincharm/46359652273c519661dab7f5f9a92fae to your computer and use it in GitHub Desktop.
ASN.1 (DER) Parser
// 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);
}
}
// 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