-
Notifications
You must be signed in to change notification settings - Fork 55
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
verify: init #311
verify: init #311
Conversation
bdb9d95
to
7cdc30a
Compare
3246492
to
b3dc1ea
Compare
Add ability to sign artifacts à la sigstore-python, towards Bundle signing and verification. Follow-up from #305, and the prerequisite motivation to #311. Signed-off-by: Jack Leightcap <jack.leightcap@trailofbits.com> Signed-off-by: Andrew Pan <andrew.pan@trailofbits.com> Signed-off-by: Andrew Pan <3821575+tnytown@users.noreply.github.com> Signed-off-by: Jack Leightcap <30168080+jleightcap@users.noreply.github.com> Co-authored-by: Andrew Pan <a@tny.town> Co-authored-by: Andrew Pan <3821575+tnytown@users.noreply.github.com> Co-authored-by: Andrew Pan <andrew.pan@trailofbits.com> Co-authored-by: Flavio Castelli <flavio@castelli.me>
dfecc13
to
5c1792a
Compare
src/crypto/certificate.rs
Outdated
} | ||
|
||
// A certificate that is its own issuer and signer is considered a root CA. | ||
// TODO(jl): verify_directly_issued_by |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Need to find a Rust equivalent of https://cryptography.io/en/latest/x509/reference/#cryptography.x509.Certificate.verify_directly_issued_by
src/verify/models.rs
Outdated
|
||
if inclusion_proof.is_some() && !has_checkpoint { | ||
// TODO(tnytown): Act here. | ||
// NOTE(jl): in sigstore-python, this is a no-op that prints a warning log. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure how best to flag a warning of invalid state in this case, AFAIK there isn't a logging framework to hook into given this is a library crate.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i think we depend on tracing
, so we could potentially use that. a good follow-on task might be to go around the new bundle code and add traces + define a reasonable tracing
span hierarchy.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
thanks for fixing this up! i have a few comments and clarifications :)
src/crypto/verification.rs
Outdated
use rustls_pki_types::CertificateDer; | ||
use webpki::TrustAnchor; | ||
|
||
/// Machinery for Sigstore end entity certificate verification. | ||
struct CertificateVerificationContext<'a> { | ||
pub trust_anchors: Vec<TrustAnchor<'a>>, | ||
pub intermediate_certs: Vec<CertificateDer<'a>>, | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is chaff from an older iteration of the API, we can probably remove it!
src/verify/models.rs
Outdated
|
||
if inclusion_proof.is_some() && !has_checkpoint { | ||
// TODO(tnytown): Act here. | ||
// NOTE(jl): in sigstore-python, this is a no-op that prints a warning log. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i think we depend on tracing
, so we could potentially use that. a good follow-on task might be to go around the new bundle code and add traces + define a reasonable tracing
span hierarchy.
src/tuf/constants.rs
Outdated
@@ -23,3 +23,4 @@ macro_rules! tuf_resource { | |||
} | |||
|
|||
pub(crate) const SIGSTORE_ROOT: &[u8] = tuf_resource!("prod/root.json"); | |||
pub(crate) const _SIGSTORE_TRUST_BUNDLE: &[u8] = tuf_resource!("prod/trusted_root.json"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we may want to investigate taking advantage of this in the tuf
module
src/verify/verifier.rs
Outdated
let (_, extended_key_usage_ext): (bool, ExtendedKeyUsage) = materials | ||
.certificate | ||
.tbs_certificate | ||
.get() | ||
.expect("Malformed certificate") | ||
.expect("Malformed certificate"); | ||
|
||
if !extended_key_usage_ext.0.contains(&ID_KP_CODE_SIGNING) { | ||
return Err(VerificationError::CertificateTypeError( | ||
"Extended key usage does not contain `code signing`".into(), | ||
)); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is already covered by store.verify_cert_with_time
above, so I think it can be removed outright.
src/verify/verifier.rs
Outdated
// TODO(tnytown): How likely is a malformed certificate in this position? Do we want to | ||
// account for it and create an error type as opposed to unwrapping? | ||
let (_, key_usage_ext): (bool, KeyUsage) = materials | ||
.certificate | ||
.tbs_certificate | ||
.get() | ||
.expect("Malformed certificate") | ||
.expect("Malformed certificate"); | ||
|
||
if !key_usage_ext.digital_signature() { | ||
return Err(VerificationError::CertificateTypeError( | ||
"Key usage is not of type `digital signature`".into(), | ||
)); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This check should be unnecessary, since chain building (above) should assert that the leaf contains a keyUsage.digitalSignature
.
src/verify/models.rs
Outdated
let rekor_entry = if let Some(rekor_entry) = rekor_entry { | ||
let cell = OnceCell::new(); | ||
|
||
// TODO(tnytown): Switch to setting if offline when Rekor fetching is implemented. | ||
#[allow(clippy::unwrap_used)] | ||
cell.set(rekor_entry).unwrap(); | ||
|
||
cell | ||
} else { | ||
Default::default() | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm having a little trouble following this: why do we put LogEntry
into a OnceCell
, rather than just keeping it as Option<LogEntry>
with a None
variant when we're missing an entry?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
no good reason, I think I originally used OnceCell
to allow for interior mutability when we lazy-init the entry; I don't think that's ever done in practice (and we shouldn't need to cache the online entry anyways.)
src/bundle/mod.rs
Outdated
macro_rules! required { | ||
($($base:expr )? ; $first_attr:ident $( . $rest_attrs:ident)* $( , $else_err:expr)?) => { | ||
$( $base . )? $first_attr.as_ref() | ||
$( | ||
.and_then(|v| v.$rest_attrs.as_ref()) | ||
)* | ||
$( .ok_or($else_err) )? | ||
} | ||
} | ||
pub(crate) use required; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IMO this macro's usages are pretty hard to read -- I think we'd possibly be better off unfolding these inline (or using more bindings to minimize the number of error sites), plus implementing TryInto
or another conversion trait where appropriate.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this wouldn't be necessary with better protobuf codegen. we can put this PR on hold until we figure that out
Signed-off-by: Jack Leightcap <jack.leightcap@trailofbits.com>
Signed-off-by: Jack Leightcap <jack.leightcap@trailofbits.com>
@flavio I'll continue to flush out documentation and cleanup this afternoon, but this should be feature complete. let me know if there's anything I can do to help facilitate review here. |
@jleightcap, I skimmed through the PR and I've the following questions:
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was playing around with these changes as I'm excited to see this functionality merged and noticed a couple little issues in my testing.
src/verify/models.rs
Outdated
return None; | ||
} | ||
|
||
for chain_cert in chain_certs { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should the chain_certs
be added to the VerificationMaterials
? It appears they are thrown away right now and chain verification does not currently work if intermediates are present.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IIRC, intermediates should never be included in materials produced against the public-good instance; if they are present, the spec says we should ignore them. Fulcio's intermediates, if any, are distributed via TUF. For non-PGI verifications, we would want to plumb chain_certs
through.
src/verify/models.rs
Outdated
log_index: required!(tlog_entry; log_index)?.parse().ok()?, | ||
verification: Verification { | ||
inclusion_proof: parsed_inclusion_proof, | ||
signed_entry_timestamp: required!(; inclusion_promise.signed_entry_timestamp)? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe this is not required for v0.2+ bundles? https://github.com/sigstore/protobuf-specs/blob/aa56b9a4929364918688fc7eeec3724c9941ddf9/protos/sigstore_rekor.proto#L105
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Correct! I think the newer revisions I've pushed up handle this correctly.
src/crypto/certificate.rs
Outdated
Some((false, _)) => { | ||
// BasicConstraints must be marked as critical, per RFC 5280 4.2.1.9. | ||
return Err(SigstoreError::InvalidCertError( | ||
"invalid X.509 certificate: non-critical BasicConstraints in CA".to_string(), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In is_root_ca
, you're assuming an Ok
result from this function, to determine whether something isn't a CA cert:
if !is_ca(certificate)? {
return Ok(false);
}
However this situation is valid for an end certificate, and not an error, meaning that current logic doesn't quite work:
https://www.rfc-editor.org/rfc/rfc5280#section-4.2.1.9
This extension MAY appear as a critical or non-critical extension in end entity certificates.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch!
src/verify/models.rs
Outdated
} | ||
|
||
for chain_cert in chain_certs { | ||
if is_root_ca(chain_cert).is_ok() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you need to specifically check the bool here, not just for Ok
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, you're right. I don't know how I feel about the Result<bool>
return type, it feels easy to misuse. Maybe it'd be better to switch to bool
and collapse the error states into false
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I changed is_ca
, is_leaf
, and is_root_ca
to return Result<(), _>
.
1748f68
to
b16136d
Compare
Signed-off-by: Andrew Pan <andrew.pan@trailofbits.com>
Signed-off-by: Andrew Pan <andrew.pan@trailofbits.com>
Signed-off-by: Andrew Pan <andrew.pan@trailofbits.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Programmatic note: we @trailofbits are out of time on sigstore-rs
. If this is not reviewed and merged by the end of the month, we will have to drop this changeset. I've fixed up what I can this cycle, but we don't have capacity for too many further review cycles.
src/crypto/verification_key.rs
Outdated
|
||
/// Verify the signature provided has been actually generated by the given key | ||
/// when signing the provided prehashed message. | ||
pub fn verify_prehash(&self, signature: Signature, msg: &[u8]) -> Result<()> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've changed the visibility to pub(crate)
for now.
src/errors.rs
Outdated
@@ -109,6 +109,9 @@ pub enum SigstoreError { | |||
#[error("Certificate pool error: {0}")] | |||
CertificatePoolError(String), | |||
|
|||
#[error("Certificate invalid: {0}")] | |||
InvalidCertError(String), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removed :)
serde_json::from_slice(&buf).map_err(serde::de::Error::custom) | ||
let buf: Vec<u8> = Base64::<Standard, Padded>::deserialize_as(de)?; | ||
|
||
// The first two bytes indicate the signature and hash algorithms so let's skip those. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is not a huge deal since we are getting this info from the trustroot CTFE keys. We could certainly assert on an expected value here if you'd prefer :)
|
||
// The first two bytes indicate the signature and hash algorithms so let's skip those. | ||
// The next two bytes indicate the size of the signature. | ||
let signature_size = u16::from_be_bytes(buf[2..4].try_into().expect("unexpected length")); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IIRC usize::from_be_bytes
accepts exactly 4 bytes
src/lib.rs
Outdated
@@ -285,8 +285,13 @@ pub mod rekor; | |||
pub mod tuf; | |||
|
|||
// Don't export yet -- these types should only be useful internally. | |||
#[cfg(feature = "bundle")] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It was supposed to gate our sigstore_protobuf_specs
dep, but apparently I didn't do so. I've updated it now :)
src/tuf/mod.rs
Outdated
pub fn prefetch(self) -> Result<Self> { | ||
let _ = self.trusted_root()?; | ||
Ok(self) | ||
pub fn new(checkout_dir: Option<&Path>) -> Result<Self> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fair point, updated :)
src/tuf/mod.rs
Outdated
Ok(Self { trusted_root }) | ||
} | ||
|
||
fn fetch_target<N>( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Noted, interesting food for thought :) I think we'd need to put some further thought into supporting those kinds of environments (a pure AsyncRead + AsyncWrite
wouldn't work because we support multiple targets.)
src/tuf/mod.rs
Outdated
let local_path = checkout_dir.as_ref().map(|d| d.join(name.raw())); | ||
|
||
// Try reading the target from disk cache. | ||
let data = if let Some(Ok(local_data)) = local_path.as_ref().map(std::fs::read) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hm, we wouldn't be able to naively return the error here: the file potentially doesn't exist in the cache yet.
src/tuf/mod.rs
Outdated
// Write the up-to-date data back to the disk. This doesn't need to succeed, as we can | ||
// always fetch the target again later. | ||
if let Some(local_path) = local_path { | ||
let _ = std::fs::write(local_path, &data); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done!
src/verify/policy.rs
Outdated
|
||
// Parse raw string without DER encoding. | ||
let val = std::str::from_utf8(ext.extn_value.as_bytes()) | ||
.expect("failed to parse constructed Extension!"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice catch! I didn't realize that this was user input.
CC @flavio, this is ready for review :) |
Signed-off-by: Andrew Pan <andrew.pan@trailofbits.com>
Signed-off-by: Andrew Pan <andrew.pan@trailofbits.com>
Signed-off-by: Andrew Pan <andrew.pan@trailofbits.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM, I left some comments.
Aside from my comments, it would be great to see the other comments addressed. For TODO stuff/nodes the outcome of the conversation can be added into the source code as a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm happy about the changes, it's also great to see the TUF contents are now synchronized with the local checkout.
Can you please add some unit tests to cover that? The repository_helper
file (removed by this PR) had some tests you might be able to reuse
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I switched to using the online TUF repo in tests and deleted the copy we had checked in.
src/verify/verifier.rs
Outdated
/// A synchronous Sigstore verifier. | ||
/// | ||
/// Async callers must use [`AsyncVerifier`]. Async usage of [`Verifier`] will result in a deadlock. | ||
pub struct Verifier { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like @tannaurus' suggestion
Signed-off-by: Andrew Pan <andrew.pan@trailofbits.com>
Signed-off-by: Andrew Pan <andrew.pan@trailofbits.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I've addressed all previous comments at this point (I reacted when only action was taken and commented in all other cases); let me know if I've missed any. I don't have permission to dismiss conversations on this PR because I'm not the author.
CC @flavio
src/verify/verifier.rs
Outdated
/// A synchronous Sigstore verifier. | ||
/// | ||
/// Async callers must use [`AsyncVerifier`]. Async usage of [`Verifier`] will result in a deadlock. | ||
pub struct Verifier { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is done: 1e73783
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I switched to using the online TUF repo in tests and deleted the copy we had checked in.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I left a minor comment, I'm fine with this PR.
src/trust/sigstore/mod.rs
Outdated
); | ||
} | ||
|
||
macro_rules! impl_test { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I understand why you're using this macro, but it makes the tests less readable. You can achieve the same result using the rstest crate, which is already a dev dependency.
Finally, you can use the tokio::test
macro to decorate the unit test, which removes more boilerplate from the test.
I can do the changes afterwards, I understand this PR has been ongoing since a long time
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@tannaurus: can you take one last look at the PR before I merge it? |
Signed-off-by: Andrew Pan <andrew.pan@trailofbits.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we're finally ready to merge it. Sorry for the long time it took!
I'm so happy to see this merged! Thanks for all the work @tnytown and @jleightcap ! ❤️ 🚀 Sorry for my delayed response here. I was out of town for team offsite and then came down with a bug when I got back. |
Summary
Complementary half of #310, enable the verification of bundles:
Release Note
Documentation