Module sequoia_openpgp::regex

source ·
Expand description

OpenPGP regex parser.

OpenPGP defines a regular expression language. It is used with trust signatures to scope the trust that they extend.

Compared with most regular expression languages, OpenPGP’s is quite simple. In particular, it only includes the following features:

  • Alternations using |,
  • Grouping using ( and ),
  • The *, +, and ? glob operators,
  • The ^, and $ anchors,
  • The ‘.’ operator, positive non-empty ranges (e.g. [a-zA-Z]) and negative non-empty ranges ([^@]), and
  • The backslash operator to escape special characters (except in ranges).

The regular expression engine defined in this module implements that language with two differences. The first difference is that the compiler only works on UTF-8 strings (not bytes). The second difference is that ranges in character classes are between UTF-8 characters, not just ASCII characters.

§Data Structures

This module defines two data structures. Regex encapsulates a valid regular expression, and provides methods to check whether the regular expression matches a string or a UserID. RegexSet is similar, but encapsulates zero or more regular expressions, which may or may not be valid. Its match methods return true if there are no regular expressions, or, if there is at least one regular expression, they return whether at least one of the regular expressions matches it. RegexSet’s matcher handles invalid regular expressions by considering them to be regular expressions that don’t match anything. These semantics are consistent with a trust signature’s scoping rules. Further, strings that contain control characters never match. This behavior can be overridden using Regex::disable_sanitizations and RegexSet::disable_sanitizations.

§Scoped Trust Signatures

To create a trust signature, you create a signature whose type is either GenericCertification, PersonaCertification, CasualCertification, or PositiveCertification, and add a Trust Signature subpacket using, for instance, the SignatureBuilder::set_trust_signature method.

To scope a trust signature, you add a Regular Expression subpacket to it using SignatureBuilder::set_regular_expression or SignatureBuilder::add_regular_expression.

To extract any regular expressions, you can use SubpacketAreas::regular_expressions.

§Caveat Emptor

Note: GnuPG has very limited regular expression support. In particular, it only recognizes regular expressions with the following form:

<[^>]+[@.]example\.com>$

Further, it escapes any operators between the <[^>]+[@.] and the >$ except . and \. Otherwise, GnuPG treats the regular expression as a literal domain (e.g., example.com).

Further, until version 2.2.22 (released in August 2020), GnuPG did not support regular expressions on Windows, and other systems that don’t include regcomp. On these systems, if a trust signature included a regular expression, GnuPG conservatively considered the whole trust signature to match nothing.

§Examples

A CA signs two certificates, one for Alice, who works at example.com, and one for Bob, who is associated with some.org. Carol then creates a trust signature for the CA, which she scopes to example.org and example.com. We then confirm that Carol can use the CA to authenticate Alice, but not Bob.

use sequoia_openpgp as openpgp;
use openpgp::cert::prelude::*;
use openpgp::packet::prelude::*;
use openpgp::policy::StandardPolicy;
use openpgp::regex::RegexSet;
use openpgp::types::SignatureType;

let p = &StandardPolicy::new();

let (ca, _)
    = CertBuilder::general_purpose(None, Some("OpenPGP CA <openpgp-ca@example.com>"))
        .generate()?;
let mut ca_signer = ca.primary_key().key().clone()
    .parts_into_secret()?.into_keypair()?;
let ca_userid = ca.with_policy(p, None)?
    .userids().nth(0).expect("Added a User ID").userid();

// The CA certifies "Alice <alice@example.com>".
let (alice, _)
    = CertBuilder::general_purpose(None, Some("Alice <alice@example.com>"))
        .generate()?;
let alice_userid = alice.with_policy(p, None)?
    .userids().nth(0).expect("Added a User ID").userid();
let alice_certification = SignatureBuilder::new(SignatureType::GenericCertification)
    .sign_userid_binding(
        &mut ca_signer,
        alice.primary_key().component(),
        alice_userid)?;
let alice = alice.insert_packets(alice_certification.clone())?;

// The CA certifies "Bob <bob@some.org>".
let (bob, _)
    = CertBuilder::general_purpose(None, Some("Bob <bob@some.org>"))
        .generate()?;
let bob_userid = bob.with_policy(p, None)?
    .userids().nth(0).expect("Added a User ID").userid();
let bob_certification = SignatureBuilder::new(SignatureType::GenericCertification)
    .sign_userid_binding(
        &mut ca_signer,
        bob.primary_key().component(),
        bob_userid)?;
let bob = bob.insert_packets(bob_certification.clone())?;


// Carol tsigns the CA's certificate.
let (carol, _)
    = CertBuilder::general_purpose(None, Some("Carol <carol@another.net>"))
        .generate()?;
let mut carol_signer = carol.primary_key().key().clone()
    .parts_into_secret()?.into_keypair()?;

let ca_tsig = SignatureBuilder::new(SignatureType::GenericCertification)
    .set_trust_signature(2, 120)?
    .set_regular_expression("<[^>]+[@.]example\\.org>$")?
    .add_regular_expression("<[^>]+[@.]example\\.com>$")?
    .sign_userid_binding(
        &mut carol_signer,
        ca.primary_key().component(),
        ca_userid)?;
let ca = ca.insert_packets(ca_tsig.clone())?;


// Carol now tries to authenticate Alice and Bob's certificates
// using the CA as a trusted introducer based on `ca_tsig`.
let res = RegexSet::from_signature(&ca_tsig)?;

// Should should be able to authenticate Alice.
let alice_ua = alice.with_policy(p, None)?
    .userids().nth(0).expect("Added a User ID");
let mut authenticated = false;
for c in alice_ua.certifications() {
    if c.get_issuers().into_iter().any(|h| h.aliases(ca.key_handle())) {
        if c.clone().verify_userid_binding(
            ca.primary_key().key(),
            alice.primary_key().key(),
            alice_ua.userid()).is_ok()
        {
            authenticated |= res.matches_userid(&alice_ua);
        }
    }
}
assert!(authenticated);

// But, although the CA has certified Bob's key, Carol doesn't rely
// on it, because Bob's email address ("bob@some.org") is out of
// scope (some.org, not example.com).
let bob_ua = bob.with_policy(p, None)?
    .userids().nth(0).expect("Added a User ID");
let mut have_certification = false;
let mut authenticated = false;
for c in bob_ua.certifications() {
    if c.get_issuers().into_iter().any(|h| h.aliases(ca.key_handle())) {
        if c.clone().verify_userid_binding(
            ca.primary_key().key(),
            bob.primary_key().key(),
            bob_ua.userid()).is_ok()
        {
            have_certification = true;
            authenticated |= res.matches_userid(&bob_ua);
        }
    }
}
assert!(have_certification);
assert!(! authenticated);

Structs§

  • A compiled OpenPGP regular expression for matching UTF-8 encoded strings.
  • A set of regular expressions.