') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); })(); spki: streaming fingerprint builder by tarcieri · Pull Request #616 · RustCrypto/formats · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions spki/src/fingerprint.rs
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
//! SPKI fingerprint support.

use der::Writer;
use sha2::{Digest, Sha256};

/// Size of a SHA-256 SPKI fingerprint in bytes.
pub(crate) const SIZE: usize = 32;

/// Raw bytes of a SPKI fingerprint i.e. SHA-256 digest of
/// `SubjectPublicKeyInfo`'s DER encoding.
///
/// See [RFC7469 § 2.1.1] for more information.
///
/// [RFC7469 § 2.1.1]: https://datatracker.ietf.org/doc/html/rfc7469#section-2.1.1
#[cfg_attr(docsrs, doc(cfg(feature = "fingerprint")))]
pub type FingerprintBytes = [u8; SIZE];

/// Writer newtype which accepts DER being serialized on-the-fly and computes a
/// hash of the contents.
#[derive(Clone, Default)]
pub(crate) struct Builder {
/// In-progress digest being computed from streaming DER.
digest: Sha256,
}

impl Builder {
/// Create a new fingerprint builder.
pub fn new() -> Self {
Self::default()
}

/// Finish computing a fingerprint, returning the computed digest.
pub fn finish(self) -> FingerprintBytes {
self.digest.finalize().into()
}
}

impl Writer for Builder {
fn write(&mut self, der_bytes: &[u8]) -> der::Result<()> {
self.digest.update(der_bytes);
Ok(())
}
}
6 changes: 6 additions & 0 deletions spki/src/lib.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -38,6 +38,9 @@ mod error;
mod spki;
mod traits;

#[cfg(feature = "fingerprint")]
mod fingerprint;

pub use crate::{
algorithm::AlgorithmIdentifier,
error::{Error, Result},
Expand All@@ -48,3 +51,6 @@ pub use der::{self, asn1::ObjectIdentifier};

#[cfg(feature = "alloc")]
pub use {crate::traits::EncodePublicKey, der::Document};

#[cfg(feature = "fingerprint")]
pub use crate::fingerprint::FingerprintBytes;
42 changes: 25 additions & 17 deletions spki/src/spki.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -8,7 +8,7 @@ use der::{asn1::BitString, Decode, Decoder, DerOrd, Encode, Sequence, ValueOrd};
use der::Document;

#[cfg(feature = "fingerprint")]
use sha2::{digest, Digest, Sha256};
use crate::{fingerprint, FingerprintBytes};

#[cfg(all(feature = "alloc", feature = "fingerprint"))]
use {
Expand All@@ -19,7 +19,7 @@ use {
#[cfg(feature = "pem")]
use der::pem::PemLabel;

/// X.509 `SubjectPublicKeyInfo` (SPKI) as defined in [RFC 5280 Section 4.1.2.7].
/// X.509 `SubjectPublicKeyInfo` (SPKI) as defined in [RFC 5280 § 4.1.2.7].
///
/// ASN.1 structure containing an [`AlgorithmIdentifier`] and public key
/// data in an algorithm specific format.
Expand All@@ -30,7 +30,7 @@ use der::pem::PemLabel;
/// subjectPublicKey BIT STRING }
/// ```
///
/// [RFC 5280 Section 4.1.2.7]: https://tools.ietf.org/html/rfc5280#section-4.1.2.7
/// [RFC 5280 § 4.1.2.7]: https://tools.ietf.org/html/rfc5280#section-4.1.2.7
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub struct SubjectPublicKeyInfo<'a> {
/// X.509 [`AlgorithmIdentifier`] for the public key type
Expand All@@ -41,24 +41,34 @@ pub struct SubjectPublicKeyInfo<'a> {
}

impl<'a> SubjectPublicKeyInfo<'a> {
/// Calculate the SHA-256 fingerprint of this [`SubjectPublicKeyInfo`].
#[cfg(feature = "fingerprint")]
#[cfg_attr(docsrs, doc(cfg(feature = "fingerprint")))]
pub fn fingerprint(&self) -> Result<digest::Output<Sha256>> {
let mut buf = [0u8; 4096];
Ok(Sha256::digest(self.encode_to_slice(&mut buf)?))
}

/// Calculate the SHA-256 fingerprint of this [`SubjectPublicKeyInfo`] and
/// encode it as a Base64 string.
///
/// See [RFC7469 § 2.1.1] for more information.
///
/// [RFC7469 § 2.1.1]: https://datatracker.ietf.org/doc/html/rfc7469#section-2.1.1
#[cfg(all(feature = "fingerprint", feature = "alloc"))]
#[cfg_attr(docsrs, doc(cfg(all(feature = "fingerprint", feature = "alloc"))))]
pub fn fingerprint_base64(&self) -> Result<String> {
Ok(Base64::encode_string(self.fingerprint()?.as_slice()))
Ok(Base64::encode_string(&self.fingerprint_bytes()?))
}

/// Calculate the SHA-256 fingerprint of this [`SubjectPublicKeyInfo`] as
/// a raw byte array.
///
/// See [RFC7469 § 2.1.1] for more information.
///
/// [RFC7469 § 2.1.1]: https://datatracker.ietf.org/doc/html/rfc7469#section-2.1.1
#[cfg(feature = "fingerprint")]
#[cfg_attr(docsrs, doc(cfg(feature = "fingerprint")))]
pub fn fingerprint_bytes(&self) -> Result<FingerprintBytes> {
let mut builder = fingerprint::Builder::new();
self.encode(&mut builder)?;
Ok(builder.finish())
}

/// Get a [`BitString`] representing the `subject_public_key`
fn subject_public_key_bitstring(&self) -> der::Result<BitString<'a>> {
fn bitstring(&self) -> der::Result<BitString<'a>> {
BitString::from_bytes(self.subject_public_key)
}
}
Expand All@@ -85,7 +95,7 @@ impl<'a> Sequence<'a> for SubjectPublicKeyInfo<'a> {
where
F: FnOnce(&[&dyn Encode]) -> der::Result<T>,
{
f(&[&self.algorithm, &self.subject_public_key_bitstring()?])
f(&[&self.algorithm, &self.bitstring()?])
}
}

Expand All@@ -100,9 +110,7 @@ impl<'a> TryFrom<&'a [u8]> for SubjectPublicKeyInfo<'a> {
impl ValueOrd for SubjectPublicKeyInfo<'_> {
fn value_cmp(&self, other: &Self) -> der::Result<Ordering> {
match self.algorithm.der_cmp(&other.algorithm)? {
Ordering::Equal => self
.subject_public_key_bitstring()?
.der_cmp(&other.subject_public_key_bitstring()?),
Ordering::Equal => self.bitstring()?.der_cmp(&other.bitstring()?),
other => Ok(other),
}
}
Expand Down
2 changes: 1 addition & 1 deletion spki/tests/spki.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -73,7 +73,7 @@ fn decode_ed25519_and_fingerprint_spki() {

// Check the fingerprint
assert_eq!(
spki.fingerprint().unwrap().as_slice(),
spki.fingerprint_bytes().unwrap().as_slice(),
ED25519_SPKI_FINGERPRINT
);
}
Expand Down