Skip to content

Commit

Permalink
sign: init
Browse files Browse the repository at this point in the history
Signed-off-by: Jack Leightcap <jack.leightcap@trailofbits.com>
Signed-off-by: Andrew Pan <andrew.pan@trailofbits.com>
Co-authored-by: Jack Leightcap <jack.leightcap@trailofbits.com>
  • Loading branch information
tnytown and jleightcap committed Nov 16, 2023
1 parent f89751d commit 001a365
Show file tree
Hide file tree
Showing 11 changed files with 861 additions and 10 deletions.
17 changes: 9 additions & 8 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ readme = "README.md"
repository = "https://github.com/sigstore/sigstore-rs"

[features]
default = ["full-native-tls", "cached-client", "tuf"]
default = ["full-native-tls", "cached-client", "tuf", "sign"]
wasm = ["getrandom/js"]

full-native-tls = [
Expand Down Expand Up @@ -42,6 +42,8 @@ rekor = ["reqwest"]

tuf = ["tough", "regex"]

sign = []

cosign-native-tls = [
"oci-distribution/native-tls",
"cert",
Expand Down Expand Up @@ -72,7 +74,7 @@ async-trait = "0.1.52"
base64 = "0.21.0"
cached = { version = "0.46.0", optional = true, features = ["async"] }
cfg-if = "1.0.0"
chrono = { version = "0.4.27", default-features = false }
chrono = { version = "0.4.27", default-features = false, features = ["serde"] }
const-oid = "0.9.1"
digest = { version = "0.10.3", default-features = false }
ecdsa = { version = "0.16.7", features = ["pkcs8", "digest", "der", "signing"] }
Expand All @@ -88,10 +90,7 @@ openidconnect = { version = "3.0", default-features = false, features = [
p256 = "0.13.2"
p384 = "0.13"
webbrowser = "0.8.4"
pem = "3.0"
picky = { version = "7.0.0-rc.8", default-features = false, features = [
"x509",
] }
pem = { version = "3.0", features = ["serde"] }
pkcs1 = { version = "0.7.5", features = ["std"] }
pkcs8 = { version = "0.10.2", features = [
"pem",
Expand All @@ -113,17 +112,20 @@ serde_json = "1.0.79"
serde_with = { version = "3.4.0", features = ["base64"] }
sha2 = { version = "0.10.6", features = ["oid"] }
signature = { version = "2.0" }
sigstore_protobuf_specs = "0.1.0-rc.2"
thiserror = "1.0.30"
tokio = { version = "1.17.0", features = ["rt"] }
tough = { version = "0.14", features = ["http"], optional = true }
tracing = "0.1.31"
url = "2.2.2"
x509-cert = { version = "0.2.2", features = ["pem", "std"] }
x509-cert = { version = "0.2.2", features = ["builder", "pem", "std"] }
crypto_secretbox = "0.1.1"
zeroize = "1.5.7"
rustls-webpki = { version = "0.102.0-alpha.4", features = ["alloc"] }
rustls-pki-types = { version = "0.2.1", features = ["std"] }
serde_repr = "0.1.16"
hex = "0.4.3"
json-syntax = { version = "0.9.6", features = ["canonicalize", "serde"] }

[dev-dependencies]
anyhow = { version = "1.0", features = ["backtrace"] }
Expand All @@ -137,7 +139,6 @@ serial_test = "2.0.0"
tempfile = "3.3.0"
testcontainers = "0.15"
tracing-subscriber = { version = "0.3.9", features = ["env-filter"] }
hex = "0.4.3"

# cosign example mappings

Expand Down
54 changes: 54 additions & 0 deletions src/bundle/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// Copyright 2023 The Sigstore Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//! Useful types for Sigstore bundles.

use std::fmt::Display;

pub use sigstore_protobuf_specs::Bundle;

// Known Sigstore bundle media types.
#[derive(Clone, Copy, Debug)]
pub enum Version {
Bundle0_1,
Bundle0_2,
}

impl TryFrom<&str> for Version {
type Error = ();

fn try_from(value: &str) -> Result<Self, Self::Error> {
match value {
"application/vnd.dev.sigstore.bundle+json;version=0.1" => Ok(Version::Bundle0_1),
"application/vnd.dev.sigstore.bundle+json;version=0.2" => Ok(Version::Bundle0_2),
_ => Err(()),
}
}
}

impl From<Version> for &str {
fn from(value: Version) -> Self {
match value {
Version::Bundle0_1 => "application/vnd.dev.sigstore.bundle+json;version=0.1",
Version::Bundle0_2 => "application/vnd.dev.sigstore.bundle+json;version=0.2",
}
}
}

impl Display for Version {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str((*self).into())?;
Ok(())
}
}
30 changes: 30 additions & 0 deletions src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@ pub enum SigstoreError {
#[error("invalid key format: {error}")]
InvalidKeyFormat { error: String },

#[error("Unable to parse identity token: {0}")]
IdentityTokenError(&'static str),

#[error("unmatched key type {key_typ} and signing scheme {scheme}")]
UnmatchedKeyAndSigningScheme { key_typ: String, scheme: String },

Expand All @@ -70,6 +73,9 @@ pub enum SigstoreError {
#[error("Public key verification error")]
PublicKeyVerificationError,

#[error("X.509 certificate version is not V3")]
CertificateUnsupportedVersionError,

#[error("Certificate validity check failed: cannot be used before {0}")]
CertificateValidityError(String),

Expand Down Expand Up @@ -103,6 +109,12 @@ pub enum SigstoreError {
#[error("Certificate pool error: {0}")]
CertificatePoolError(&'static str),

#[error("Signing session expired")]
ExpiredSigningSession(),

#[error("Fulcio request unsuccessful: {0}")]
FulcioClientError(&'static str),

#[error("Cannot fetch manifest of {image}: {error}")]
RegistryFetchManifestError { image: String, error: String },

Expand All @@ -115,9 +127,18 @@ pub enum SigstoreError {
#[error("Cannot push {image}: {error}")]
RegistryPushError { image: String, error: String },

#[error("Rekor request unsuccessful: {0}")]
RekorClientError(String),

#[error(transparent)]
ReqwestError(#[from] reqwest::Error),

Check failure on line 134 in src/errors.rs

View workflow job for this annotation

GitHub Actions / Check WASM

failed to resolve: use of undeclared crate or module `reqwest`

#[error("OCI reference not valid: {reference}")]
OciReferenceNotValidError { reference: String },

#[error("Sigstore bundle malformed: {0}")]
SigstoreBundleMalformedError(String),

#[error("Layer doesn't have Sigstore media type")]
SigstoreMediaTypeNotFoundError,

Expand Down Expand Up @@ -155,6 +176,9 @@ pub enum SigstoreError {
#[error("{0}")]
VerificationConstraintError(String),

#[error("{0}")]
VerificationMaterialError(String),

#[error("{0}")]
ApplyConstraintError(String),

Expand Down Expand Up @@ -214,4 +238,10 @@ pub enum SigstoreError {

#[error(transparent)]
Ed25519PKCS8Error(#[from] ed25519_dalek::pkcs8::spki::Error),

#[error(transparent)]
X509ParseError(#[from] x509_cert::der::Error),

#[error(transparent)]
X509BuilderError(#[from] x509_cert::builder::Error),
}
91 changes: 90 additions & 1 deletion src/fulcio/mod.rs
Original file line number Diff line number Diff line change
@@ -1,23 +1,33 @@
mod models;

pub mod oauth;

use crate::crypto::signing_key::SigStoreSigner;
use crate::crypto::SigningScheme;
use crate::errors::{Result, SigstoreError};
use crate::fulcio::models::{CreateSigningCertificateRequest, SigningCertificate};
use crate::fulcio::oauth::OauthTokenProvider;
use crate::oauth::IdentityToken;
use base64::{engine::general_purpose::STANDARD as BASE64_STD_ENGINE, Engine as _};
use openidconnect::core::CoreIdToken;
use reqwest::Body;
use pkcs8::der::Decode;
use reqwest::{header, Body};
use serde::ser::SerializeStruct;
use serde::{Serialize, Serializer};
use std::convert::{TryFrom, TryInto};
use std::fmt::{Debug, Display, Formatter};
use tracing::debug;
use url::Url;
use x509_cert::Certificate;

pub use models::CertificateResponse;

/// Default public Fulcio server root.
pub const FULCIO_ROOT: &str = "https://fulcio.sigstore.dev/";

/// Path within Fulcio to obtain a signing certificate.
pub const SIGNING_CERT_PATH: &str = "api/v1/signingCert";
pub const SIGNING_CERT_V2_PATH: &str = "api/v2/signingCert";

const CONTENT_TYPE_HEADER_NAME: &str = "content-type";

Expand Down Expand Up @@ -191,4 +201,83 @@ impl FulcioClient {

Ok((signer, FulcioCert(cert)))
}

/// Request a certificate from Fulcio with the V2 endpoint.
///
/// TODO(tnytown): This (and other API clients) probably be autogenerated. See sigstore-rs#209.
///
/// https://github.com/sigstore/fulcio/blob/main/fulcio.proto
///
/// Additionally, it might not be reasonable to expect callers to correctly construct and pass
/// in an X509 CSR.
pub fn request_cert_v2(
&self,
request: x509_cert::request::CertReq,
identity: &IdentityToken,
) -> Result<CertificateResponse> {
let client = reqwest::blocking::Client::new();

macro_rules! headers {
($($key:expr => $val:expr),+) => {
{
let mut map = reqwest::header::HeaderMap::new();
$( map.insert($key, $val.parse().unwrap()); )+
map
}
}
}
let headers = headers!(
header::AUTHORIZATION => format!("Bearer {}", identity.to_string()),
header::CONTENT_TYPE => "application/json",
header::ACCEPT => "application/pem-certificate-chain"
);

let response: SigningCertificate = client
.post(self.root_url.join(SIGNING_CERT_V2_PATH)?)
.headers(headers)
.json(&CreateSigningCertificateRequest {
certificate_signing_request: request,
})
.send()?
.json()?;

let sct_embedded = matches!(
response,
SigningCertificate::SignedCertificateEmbeddedSct(_)
);
let certs = match response {
SigningCertificate::SignedCertificateDetachedSct(ref sc) => &sc.chain.certificates,
SigningCertificate::SignedCertificateEmbeddedSct(ref sc) => &sc.chain.certificates,
};

if certs.len() < 2 {
return Err(SigstoreError::FulcioClientError(
"Certificate chain too short: certs.len() < 2",
));
}

let mut chain = certs
.iter()
.map(|pem| Certificate::from_der(pem.contents()))
.collect::<std::result::Result<Vec<_>, _>>()?;
let cert = chain
.drain(..1)
.next()
.expect("failed to drain certificates of checked length!");

// TODO(tnytown): Implement SCT extraction.
// see: https://github.com/RustCrypto/formats/pull/1134
if sct_embedded {
debug!("PrecertificateSignedCertificateTimestamps isn't implemented yet in x509_cert.");
} else {
// No embedded SCT, Fulcio instance that provides detached SCT:
if let SigningCertificate::SignedCertificateDetachedSct(_sct) = response {}
};

Ok(CertificateResponse {
cert,
chain,
// sct,
})
}
}
Loading

0 comments on commit 001a365

Please sign in to comment.