Skip to content
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

Fulcio API v2. #209

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
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
3 changes: 3 additions & 0 deletions src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -214,4 +214,7 @@ pub enum SigstoreError {

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

#[error("Failed to request the certificate")]
CertificateRequestError,
}
138 changes: 99 additions & 39 deletions src/fulcio/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use base64::{engine::general_purpose::STANDARD as BASE64_STD_ENGINE, Engine as _
use openidconnect::core::CoreIdToken;
use reqwest::Body;
use serde::ser::SerializeStruct;
use serde::{Serialize, Serializer};
use serde::{Deserialize, Serialize, Serializer};
use std::convert::{TryFrom, TryInto};
use std::fmt::{Debug, Display, Formatter};
use url::Url;
Expand All @@ -17,19 +17,63 @@ use url::Url;
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_PATH: &str = "api/v2/signingCert";

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

#[derive(Serialize, Debug)]
#[serde(rename_all = "camelCase")]
struct Credentials {
oidc_identity_token: String,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would add the following comment:

/// The OIDC token that identifies the caller

This is taken from here

}

#[derive(Debug)]
struct PublicKey {
algorithm: Option<SigningScheme>,
content: String,
}
impl Serialize for PublicKey {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit, empty line missing

fn serialize<S: Serializer>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut pk = serializer.serialize_struct("PublicKey", 2)?;
pk.serialize_field("content", &self.content)?;
pk.serialize_field(
"algorithm",
match self.algorithm {
Some(SigningScheme::ECDSA_P256_SHA256_ASN1)
| Some(SigningScheme::ECDSA_P384_SHA384_ASN1) => "ECDSA",
Some(SigningScheme::ED25519) => "ED25519",
Some(SigningScheme::RSA_PSS_SHA256(_))
| Some(SigningScheme::RSA_PSS_SHA384(_))
| Some(SigningScheme::RSA_PSS_SHA512(_))
| Some(SigningScheme::RSA_PKCS1_SHA256(_))
| Some(SigningScheme::RSA_PKCS1_SHA384(_))
| Some(SigningScheme::RSA_PKCS1_SHA512(_)) => "RSA",
_ => "PUBLIC_KEY_ALGORITHM_UNSPECIFIED",
},
)?;
pk.end()
}
}

#[derive(Serialize, Debug)]
#[serde(rename_all = "camelCase")]
struct PublicKeyRequest {
public_key: PublicKey,
proof_of_possession: String,
Comment on lines +64 to +65
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please, copy the inline docs from here

}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit, no empty line after the }

/// Fulcio certificate signing request
///
/// Used to present a public key and signed challenge/proof-of-key in exchange
/// for a signed X509 certificate in return.
#[derive(Serialize, Debug)]
#[serde(rename_all = "camelCase")]
struct Csr {
public_key: Option<PublicKey>,
signed_email_address: Option<String>,
credentials: Credentials,
public_key_request: PublicKeyRequest,
certificate_signing_request: Option<String>,
Comment on lines +75 to +76
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not correct, according to the protobuf spec public_key_request and certificate_signing_request are declared as oneof.

I'm not the author of this Rust implementation, but I think this code would be generated in another way using something like protobuf-codegen. I guess the final code would put these fields under something like an enum?

}

impl TryFrom<Csr> for Body {
Expand All @@ -40,37 +84,33 @@ impl TryFrom<Csr> for Body {
}
}

/// Internal newtype to control serde jsonification.
#[derive(Debug)]
struct PublicKey(String, SigningScheme);
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
struct Chain {
certificates: Vec<FulcioCert>,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please add the inline comment from here

}

impl Serialize for PublicKey {
fn serialize<S: Serializer>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut pk = serializer.serialize_struct("PublicKey", 2)?;
pk.serialize_field("content", &self.0)?;
pk.serialize_field(
"algorithm",
match self.1 {
SigningScheme::ECDSA_P256_SHA256_ASN1 | SigningScheme::ECDSA_P384_SHA384_ASN1 => {
"ecdsa"
}
SigningScheme::ED25519 => "ed25519",
SigningScheme::RSA_PSS_SHA256(_)
| SigningScheme::RSA_PSS_SHA384(_)
| SigningScheme::RSA_PSS_SHA512(_)
| SigningScheme::RSA_PKCS1_SHA256(_)
| SigningScheme::RSA_PKCS1_SHA384(_)
| SigningScheme::RSA_PKCS1_SHA512(_) => "rsa",
},
)?;
pk.end()
}
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
struct SignedCertificateDetachedSct {
chain: Chain,
}
Comment on lines +95 to +97
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing inline comments and signed_certificate_timestamp, according to the definition


#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
struct SignedCertificateEmbeddedSct {
chain: Chain,
}
Comment on lines +101 to +103
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing inline comment, please copy from here


#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
struct CsrResponse {
signed_certificate_detached_sct: Option<SignedCertificateDetachedSct>,
signed_certificate_embedded_sct: Option<SignedCertificateEmbeddedSct>,
Comment on lines +107 to +109
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also in this case, these attributes are mutually exclusive because protobuf defines them as oneof as seen here

}

/// The PEM-encoded certificate chain returned by Fulcio.
#[derive(Deserialize, Clone)]
pub struct FulcioCert(String);

impl AsRef<[u8]> for FulcioCert {
Expand Down Expand Up @@ -164,12 +204,19 @@ impl FulcioClient {
let signature = BASE64_STD_ENGINE.encode(signature);

let key_pair = signer.to_sigstore_keypair()?;
let public_key = key_pair.public_key_to_der()?;
let public_key = BASE64_STD_ENGINE.encode(public_key);

let public_key = key_pair.public_key_to_pem()?;
let csr = Csr {
public_key: Some(PublicKey(public_key, signing_scheme)),
signed_email_address: Some(signature),
credentials: Credentials {
oidc_identity_token: token.to_string(),
},
public_key_request: PublicKeyRequest {
public_key: PublicKey {
algorithm: Some(signing_scheme),
content: public_key,
},
proof_of_possession: signature,
},
certificate_signing_request: None,
};

let csr = TryInto::<Body>::try_into(csr)?;
Expand All @@ -184,11 +231,24 @@ impl FulcioClient {
.await
.map_err(|_| SigstoreError::SigstoreFulcioCertificatesNotProvidedError)?;

let cert = response
.text()
let cert_response = response
.json::<CsrResponse>()
.await
.map_err(|_| SigstoreError::SigstoreFulcioCertificatesNotProvidedError)?;

Ok((signer, FulcioCert(cert)))
let cert: FulcioCert;

if let Some(signed_certificate_detached_sct) = cert_response.signed_certificate_detached_sct
{
cert = signed_certificate_detached_sct.chain.certificates[0].clone();
} else if let Some(signed_certificate_embedded_sct) =
cert_response.signed_certificate_embedded_sct
{
cert = signed_certificate_embedded_sct.chain.certificates[0].clone();
} else {
return Err(SigstoreError::CertificateRequestError);
}

Ok((signer, cert))
}
}