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.