fix(core): require certificate-only PEM inputs
Verify Data Plane / gateway (push) Successful in 4m36s
Verify Data Plane / gateway (push) Successful in 4m36s
This commit is contained in:
+70
-9
@@ -18,13 +18,6 @@ fn bounded(value: &str, minimum: usize, maximum: usize) -> bool {
|
||||
(minimum..=maximum).contains(&value.len())
|
||||
}
|
||||
|
||||
fn contains_private_key_pem(value: &str) -> bool {
|
||||
value.lines().any(|line| {
|
||||
let line = line.trim();
|
||||
line.starts_with("-----BEGIN ") && line.ends_with("-----") && line.contains("PRIVATE KEY")
|
||||
})
|
||||
}
|
||||
|
||||
const fn base64url_value(value: u8) -> Option<u8> {
|
||||
match value {
|
||||
b'A'..=b'Z' => Some(value - b'A'),
|
||||
@@ -36,6 +29,74 @@ const fn base64url_value(value: u8) -> Option<u8> {
|
||||
}
|
||||
}
|
||||
|
||||
const fn base64_value(value: u8) -> Option<u8> {
|
||||
match value {
|
||||
b'A'..=b'Z' => Some(value - b'A'),
|
||||
b'a'..=b'z' => Some(value - b'a' + 26),
|
||||
b'0'..=b'9' => Some(value - b'0' + 52),
|
||||
b'+' => Some(62),
|
||||
b'/' => Some(63),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn valid_base64(value: &str) -> bool {
|
||||
let bytes = value.as_bytes();
|
||||
if bytes.is_empty() || !bytes.len().is_multiple_of(4) {
|
||||
return false;
|
||||
}
|
||||
let data_length = bytes
|
||||
.iter()
|
||||
.position(|byte| *byte == b'=')
|
||||
.unwrap_or(bytes.len());
|
||||
let padding = bytes.len() - data_length;
|
||||
if data_length == 0
|
||||
|| padding > 2
|
||||
|| !bytes[..data_length]
|
||||
.iter()
|
||||
.all(|byte| base64_value(*byte).is_some())
|
||||
|| !bytes[data_length..].iter().all(|byte| *byte == b'=')
|
||||
{
|
||||
return false;
|
||||
}
|
||||
match padding {
|
||||
0 => true,
|
||||
1 => base64_value(bytes[data_length - 1]).is_some_and(|value| value.trailing_zeros() >= 2),
|
||||
2 => base64_value(bytes[data_length - 1]).is_some_and(|value| value.trailing_zeros() >= 4),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn certificate_only_pem(value: &str) -> bool {
|
||||
let mut lines = value.lines().peekable();
|
||||
let mut blocks = 0_u32;
|
||||
loop {
|
||||
while lines.next_if(|line| line.trim().is_empty()).is_some() {}
|
||||
let Some(begin) = lines.next() else {
|
||||
return blocks > 0;
|
||||
};
|
||||
if begin != "-----BEGIN CERTIFICATE-----" {
|
||||
return false;
|
||||
}
|
||||
blocks += 1;
|
||||
let mut body = String::new();
|
||||
let mut complete = false;
|
||||
for line in lines.by_ref() {
|
||||
if line == "-----END CERTIFICATE-----" {
|
||||
complete = true;
|
||||
break;
|
||||
}
|
||||
if line.is_empty() || line.trim() != line {
|
||||
return false;
|
||||
}
|
||||
body.push_str(line);
|
||||
}
|
||||
if !complete || !valid_base64(&body) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn raw_base64url_decoded_len(value: &str) -> Option<usize> {
|
||||
let bytes = value.as_bytes();
|
||||
if bytes.is_empty() || bytes.iter().any(|byte| base64url_value(*byte).is_none()) {
|
||||
@@ -395,8 +456,8 @@ impl NativeTunnelCredential {
|
||||
|| !bounded(&credential.device_key_id, 1, 128)
|
||||
|| !bounded(&credential.certificate_chain_pem, 1, 65_536)
|
||||
|| !bounded(&credential.trust_bundle_pem, 1, 65_536)
|
||||
|| contains_private_key_pem(&credential.certificate_chain_pem)
|
||||
|| contains_private_key_pem(&credential.trust_bundle_pem)
|
||||
|| !certificate_only_pem(&credential.certificate_chain_pem)
|
||||
|| !certificate_only_pem(&credential.trust_bundle_pem)
|
||||
|| timestamp(&credential.expires_at, false).is_none()
|
||||
{
|
||||
return Err(CoreError::InvalidArgument);
|
||||
|
||||
Reference in New Issue
Block a user