Keep the colleague's microbu-esp32c5 tree in this repository
obu-firmware builds against vanetza-idf from microbu-esp32c5/external, but that tree was gitignored, so a clone of this repository could not build the firmware it ships. It is now committed here as ordinary files in its own folder, microbu-esp32c5/: the colleague's commit cf4b99f plus the V2X2MAP bridge's signature verification (--trust) used on the bench. Nothing is fetched from or pushed to the colleague's repository; this repository and its remotes carry everything. The folder's own .gitignore keeps build output, downloaded components and private key material out, as it did there; the committed file set is identical to that repository's tracked files. The ESP32-C5 is still flashed from obu-firmware/, which only takes vanetza-idf from microbu-esp32c5/, so the two stay separate folders. FLASHING.md says how to take a newer version of the colleague's tree (copy it over the folder, rebuild, test, commit).
This commit is contained in:
@@ -0,0 +1,179 @@
|
||||
use std::io::{self, Read};
|
||||
use std::path::PathBuf;
|
||||
|
||||
use sha2::{Digest, Sha256};
|
||||
|
||||
const EXPECTED_TEMPLATE_SHA256: &str =
|
||||
"7168E341764188E140EF591634A9D2488D7FDACDA667EE53AFD566A8034ED5DB";
|
||||
|
||||
fn err(msg: impl Into<String>) -> io::Error {
|
||||
io::Error::new(io::ErrorKind::InvalidData, msg.into())
|
||||
}
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let template_path = PathBuf::from(
|
||||
std::env::var("UML_TEMPLATE")
|
||||
.unwrap_or_else(|_| "registered_rca.oer".to_string()),
|
||||
);
|
||||
let output_dir = PathBuf::from(
|
||||
std::env::var("UML_OUTPUT_DIR").unwrap_or_else(|_| "uml_output".to_string()),
|
||||
);
|
||||
|
||||
let template_bytes = std::fs::read(&template_path)?;
|
||||
let template_sha256 = hex::encode_upper(Sha256::digest(&template_bytes));
|
||||
if template_sha256 != EXPECTED_TEMPLATE_SHA256 {
|
||||
return Err(err(format!(
|
||||
"Template SHA-256 mismatch. Expected {}, got {}",
|
||||
EXPECTED_TEMPLATE_SHA256, template_sha256
|
||||
))
|
||||
.into());
|
||||
}
|
||||
|
||||
// Decode the registered certificate only as the reviewed profile template.
|
||||
let template: rasn_its::ts103097::EtsiTs103097Certificate =
|
||||
rasn::oer::decode(&template_bytes)?;
|
||||
|
||||
// The wrapper sends a newly generated P-256 scalar over stdin. It is never
|
||||
// written to disk unencrypted.
|
||||
let mut scalar_hex = String::new();
|
||||
std::io::stdin().read_to_string(&mut scalar_hex)?;
|
||||
let scalar = hex::decode(scalar_hex.trim())?;
|
||||
if scalar.len() != 32 {
|
||||
return Err(err(format!(
|
||||
"Expected a 32-byte NIST P-256 private scalar, received {} bytes",
|
||||
scalar.len()
|
||||
))
|
||||
.into());
|
||||
}
|
||||
|
||||
let secret_key = p256::SecretKey::from_slice(&scalar)
|
||||
.map_err(|_| err("Invalid NIST P-256 private scalar"))?;
|
||||
let private_key =
|
||||
c_its::security::crypto::PrivateKey::P256(secret_key.into());
|
||||
|
||||
let mut tbs_cert = template.to_be_signed.clone();
|
||||
tbs_cert.verify_key_indicator =
|
||||
rasn_its::ieee1609dot2::VerificationKeyIndicator::VerificationKey(
|
||||
private_key.public_key().encode(),
|
||||
);
|
||||
|
||||
// This is deliberately the exact encoding/signing route introduced by
|
||||
// TheEnbyperor/c-its commit e3bb3b82480d6df4237e2a8c35ea0dd7eade25b4.
|
||||
let tbs_bytes = rasn::oer::encode(&tbs_cert)?;
|
||||
let hash_alg = c_its::security::crypto::HashAlgorithm::Sha256;
|
||||
let signature = private_key
|
||||
.sign(hash_alg, &tbs_bytes, None)
|
||||
.ok_or_else(|| err("c-its signing failed"))?;
|
||||
|
||||
let (r_hex, s_hex) = match &signature {
|
||||
c_its::security::crypto::Signature::P256(sig) => (
|
||||
hex::encode_upper(sig.r().to_bytes()),
|
||||
hex::encode_upper(sig.s().to_bytes()),
|
||||
),
|
||||
_ => return Err(err("Unexpected signature algorithm").into()),
|
||||
};
|
||||
|
||||
// Construct the certificate exactly like the upstream root generator.
|
||||
let raw_cert = rasn_its::ts103097::EtsiTs103097Certificate::try_from(
|
||||
rasn_its::ieee1609dot2::Certificate::from(
|
||||
rasn_its::ieee1609dot2::ExplicitCertificate::new(
|
||||
rasn_its::ieee1609dot2::CertificateBase {
|
||||
version: 3,
|
||||
r#type: rasn_its::ieee1609dot2::CertificateType::Explicit,
|
||||
issuer: rasn_its::ieee1609dot2::IssuerIdentifier::VSelf(
|
||||
hash_alg.into(),
|
||||
),
|
||||
to_be_signed: tbs_cert,
|
||||
signature: Some(signature.as_signature()),
|
||||
},
|
||||
)
|
||||
.map_err(|_| err("Could not construct ExplicitCertificate"))?,
|
||||
),
|
||||
)
|
||||
.map_err(|_| err("Certificate does not satisfy EtsiTs103097Certificate constraints"))?;
|
||||
|
||||
let cert = c_its::security::certs::Certificate::new(&raw_cert);
|
||||
let encoded = cert.to_bytes();
|
||||
|
||||
// Parse the actual final bytes from scratch and make the upstream c-its verifier
|
||||
// validate the self-signature.
|
||||
let reparsed = c_its::security::certs::Certificate::parse(&encoded)
|
||||
.map_err(err)?;
|
||||
let report = reparsed.report().map_err(err)?;
|
||||
|
||||
match report.signature() {
|
||||
c_its::security::certs::CertificateSignature::SelfSigned { verifies: true } => {}
|
||||
other => {
|
||||
return Err(err(format!(
|
||||
"FINAL CERTIFICATE SELF-SIGNATURE DID NOT VERIFY: {:?}",
|
||||
other
|
||||
))
|
||||
.into())
|
||||
}
|
||||
}
|
||||
|
||||
let reencoded = reparsed.to_bytes();
|
||||
if reencoded != encoded {
|
||||
return Err(err("Final certificate is not stable across parse/re-encode").into());
|
||||
}
|
||||
|
||||
let id8 = hex::encode_upper(report.id8());
|
||||
let cert_sha256 = hex::encode_upper(Sha256::digest(&encoded));
|
||||
if &cert_sha256[cert_sha256.len() - 16..] != id8 {
|
||||
return Err(err("HashedID8 does not match low-order 8 bytes of SHA-256").into());
|
||||
}
|
||||
|
||||
// Compute the IEEE/ETSI self-signed certificate signing prehash independently
|
||||
// from the signature object:
|
||||
// SHA256( SHA256(COER/OER-TBS) || SHA256(empty signer certificate) )
|
||||
let tbs_hash = Sha256::digest(&tbs_bytes);
|
||||
let empty_hash = Sha256::digest([]);
|
||||
let mut outer = Sha256::new();
|
||||
outer.update(&tbs_hash);
|
||||
outer.update(&empty_hash);
|
||||
let signing_prehash = outer.finalize();
|
||||
|
||||
std::fs::create_dir_all(&output_dir)?;
|
||||
std::fs::write(output_dir.join(format!("{}.oer", id8)), &encoded)?;
|
||||
std::fs::write(output_dir.join("tbs.oer"), &tbs_bytes)?;
|
||||
std::fs::write(
|
||||
output_dir.join("certificate_report.json"),
|
||||
serde_json::to_vec_pretty(&report)?,
|
||||
)?;
|
||||
|
||||
let signing_details = format!(
|
||||
concat!(
|
||||
"UPSTREAM_COMMIT=e3bb3b82480d6df4237e2a8c35ea0dd7eade25b4\n",
|
||||
"TEMPLATE_SHA256={}\n",
|
||||
"CERTIFICATE_SHA256={}\n",
|
||||
"HASHEDID8={}\n",
|
||||
"TBS_LENGTH={}\n",
|
||||
"TBS_SHA256={}\n",
|
||||
"EMPTY_SHA256={}\n",
|
||||
"SIGNING_PREHASH_SHA256={}\n",
|
||||
"ECDSA_R={}\n",
|
||||
"ECDSA_S={}\n",
|
||||
"CITS_SELF_SIGNATURE_VERIFIES=true\n",
|
||||
"PARSE_REENCODE_STABLE=true\n"
|
||||
),
|
||||
template_sha256,
|
||||
cert_sha256,
|
||||
id8,
|
||||
tbs_bytes.len(),
|
||||
hex::encode_upper(tbs_hash),
|
||||
hex::encode_upper(empty_hash),
|
||||
hex::encode_upper(signing_prehash),
|
||||
r_hex,
|
||||
s_hex,
|
||||
);
|
||||
std::fs::write(output_dir.join("signing_details.txt"), signing_details)?;
|
||||
|
||||
println!("RESULT_ID8={}", id8);
|
||||
println!(
|
||||
"RESULT_CERT={}",
|
||||
output_dir.join(format!("{}.oer", id8)).display()
|
||||
);
|
||||
println!("CITS_SELF_SIGNATURE_VERIFIES=true");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,176 @@
|
||||
use std::io::{self, Read};
|
||||
use std::path::PathBuf;
|
||||
|
||||
use sha2::{Digest, Sha256};
|
||||
|
||||
const EXPECTED_TEMPLATE_SHA256: &str =
|
||||
"7168E341764188E140EF591634A9D2488D7FDACDA667EE53AFD566A8034ED5DB";
|
||||
|
||||
fn err(msg: impl Into<String>) -> io::Error {
|
||||
io::Error::new(io::ErrorKind::InvalidData, msg.into())
|
||||
}
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let template_path = PathBuf::from(
|
||||
std::env::var("UML_TEMPLATE")
|
||||
.unwrap_or_else(|_| "registered_rca.oer".to_string()),
|
||||
);
|
||||
let output_dir = PathBuf::from(
|
||||
std::env::var("UML_OUTPUT_DIR").unwrap_or_else(|_| "uml_output".to_string()),
|
||||
);
|
||||
|
||||
let template_bytes = std::fs::read(&template_path)?;
|
||||
let template_sha256 = hex::encode_upper(Sha256::digest(&template_bytes));
|
||||
if template_sha256 != EXPECTED_TEMPLATE_SHA256 {
|
||||
return Err(err(format!(
|
||||
"Template SHA-256 mismatch. Expected {}, got {}",
|
||||
EXPECTED_TEMPLATE_SHA256, template_sha256
|
||||
))
|
||||
.into());
|
||||
}
|
||||
|
||||
// Decode the registered certificate. Its TBS, including its public key, is kept
|
||||
// unchanged so this tool cannot silently create a different root identity.
|
||||
let template: rasn_its::ts103097::EtsiTs103097Certificate =
|
||||
rasn::oer::decode(&template_bytes)?;
|
||||
|
||||
// The wrapper unlocks the registered P-256 key and sends its scalar over stdin.
|
||||
// It is never written to disk unencrypted.
|
||||
let mut scalar_hex = String::new();
|
||||
std::io::stdin().read_to_string(&mut scalar_hex)?;
|
||||
let scalar = hex::decode(scalar_hex.trim())?;
|
||||
if scalar.len() != 32 {
|
||||
return Err(err(format!(
|
||||
"Expected a 32-byte NIST P-256 private scalar, received {} bytes",
|
||||
scalar.len()
|
||||
))
|
||||
.into());
|
||||
}
|
||||
|
||||
let secret_key = p256::SecretKey::from_slice(&scalar)
|
||||
.map_err(|_| err("Invalid NIST P-256 private scalar"))?;
|
||||
let private_key =
|
||||
c_its::security::crypto::PrivateKey::P256(secret_key.into());
|
||||
|
||||
let tbs_cert = template.to_be_signed.clone();
|
||||
|
||||
// This is deliberately the exact encoding/signing route introduced by
|
||||
// TheEnbyperor/c-its commit e3bb3b82480d6df4237e2a8c35ea0dd7eade25b4.
|
||||
let tbs_bytes = rasn::oer::encode(&tbs_cert)?;
|
||||
let hash_alg = c_its::security::crypto::HashAlgorithm::Sha256;
|
||||
let signature = private_key
|
||||
.sign(hash_alg, &tbs_bytes, None)
|
||||
.ok_or_else(|| err("c-its signing failed"))?;
|
||||
|
||||
let (r_hex, s_hex) = match &signature {
|
||||
c_its::security::crypto::Signature::P256(sig) => (
|
||||
hex::encode_upper(sig.r().to_bytes()),
|
||||
hex::encode_upper(sig.s().to_bytes()),
|
||||
),
|
||||
_ => return Err(err("Unexpected signature algorithm").into()),
|
||||
};
|
||||
|
||||
// Construct the certificate exactly like the upstream root generator.
|
||||
let raw_cert = rasn_its::ts103097::EtsiTs103097Certificate::try_from(
|
||||
rasn_its::ieee1609dot2::Certificate::from(
|
||||
rasn_its::ieee1609dot2::ExplicitCertificate::new(
|
||||
rasn_its::ieee1609dot2::CertificateBase {
|
||||
version: 3,
|
||||
r#type: rasn_its::ieee1609dot2::CertificateType::Explicit,
|
||||
issuer: rasn_its::ieee1609dot2::IssuerIdentifier::VSelf(
|
||||
hash_alg.into(),
|
||||
),
|
||||
to_be_signed: tbs_cert,
|
||||
signature: Some(signature.as_signature()),
|
||||
},
|
||||
)
|
||||
.map_err(|_| err("Could not construct ExplicitCertificate"))?,
|
||||
),
|
||||
)
|
||||
.map_err(|_| err("Certificate does not satisfy EtsiTs103097Certificate constraints"))?;
|
||||
|
||||
let cert = c_its::security::certs::Certificate::new(&raw_cert);
|
||||
let encoded = cert.to_bytes();
|
||||
|
||||
// Parse the actual final bytes from scratch and make the upstream c-its verifier
|
||||
// validate the self-signature.
|
||||
let reparsed = c_its::security::certs::Certificate::parse(&encoded)
|
||||
.map_err(err)?;
|
||||
let report = reparsed.report().map_err(err)?;
|
||||
|
||||
match report.signature() {
|
||||
c_its::security::certs::CertificateSignature::SelfSigned { verifies: true } => {}
|
||||
other => {
|
||||
return Err(err(format!(
|
||||
"FINAL CERTIFICATE SELF-SIGNATURE DID NOT VERIFY: {:?}",
|
||||
other
|
||||
))
|
||||
.into())
|
||||
}
|
||||
}
|
||||
|
||||
let reencoded = reparsed.to_bytes();
|
||||
if reencoded != encoded {
|
||||
return Err(err("Final certificate is not stable across parse/re-encode").into());
|
||||
}
|
||||
|
||||
let id8 = hex::encode_upper(report.id8());
|
||||
let cert_sha256 = hex::encode_upper(Sha256::digest(&encoded));
|
||||
if &cert_sha256[cert_sha256.len() - 16..] != id8 {
|
||||
return Err(err("HashedID8 does not match low-order 8 bytes of SHA-256").into());
|
||||
}
|
||||
|
||||
// Compute the IEEE/ETSI self-signed certificate signing prehash independently
|
||||
// from the signature object:
|
||||
// SHA256( SHA256(COER/OER-TBS) || SHA256(empty signer certificate) )
|
||||
let tbs_hash = Sha256::digest(&tbs_bytes);
|
||||
let empty_hash = Sha256::digest([]);
|
||||
let mut outer = Sha256::new();
|
||||
outer.update(&tbs_hash);
|
||||
outer.update(&empty_hash);
|
||||
let signing_prehash = outer.finalize();
|
||||
|
||||
std::fs::create_dir_all(&output_dir)?;
|
||||
std::fs::write(output_dir.join(format!("{}.oer", id8)), &encoded)?;
|
||||
std::fs::write(output_dir.join("tbs.oer"), &tbs_bytes)?;
|
||||
std::fs::write(
|
||||
output_dir.join("certificate_report.json"),
|
||||
serde_json::to_vec_pretty(&report)?,
|
||||
)?;
|
||||
|
||||
let signing_details = format!(
|
||||
concat!(
|
||||
"UPSTREAM_COMMIT=e3bb3b82480d6df4237e2a8c35ea0dd7eade25b4\n",
|
||||
"TEMPLATE_SHA256={}\n",
|
||||
"CERTIFICATE_SHA256={}\n",
|
||||
"HASHEDID8={}\n",
|
||||
"TBS_LENGTH={}\n",
|
||||
"TBS_SHA256={}\n",
|
||||
"EMPTY_SHA256={}\n",
|
||||
"SIGNING_PREHASH_SHA256={}\n",
|
||||
"ECDSA_R={}\n",
|
||||
"ECDSA_S={}\n",
|
||||
"CITS_SELF_SIGNATURE_VERIFIES=true\n",
|
||||
"PARSE_REENCODE_STABLE=true\n"
|
||||
),
|
||||
template_sha256,
|
||||
cert_sha256,
|
||||
id8,
|
||||
tbs_bytes.len(),
|
||||
hex::encode_upper(tbs_hash),
|
||||
hex::encode_upper(empty_hash),
|
||||
hex::encode_upper(signing_prehash),
|
||||
r_hex,
|
||||
s_hex,
|
||||
);
|
||||
std::fs::write(output_dir.join("signing_details.txt"), signing_details)?;
|
||||
|
||||
println!("RESULT_ID8={}", id8);
|
||||
println!(
|
||||
"RESULT_CERT={}",
|
||||
output_dir.join(format!("{}.oer", id8)).display()
|
||||
);
|
||||
println!("CITS_SELF_SIGNATURE_VERIFIES=true");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
Reference in New Issue
Block a user