feat: add script-sign-rs, not ready
This commit is contained in:
204
script-sign-rs/src/main.rs
Normal file
204
script-sign-rs/src/main.rs
Normal file
@@ -0,0 +1,204 @@
|
||||
use base64::engine::general_purpose as general_base64;
|
||||
use base64::Engine;
|
||||
use digest::Digest;
|
||||
use regex::Regex;
|
||||
use rust_util::{opt_result, opt_value_result, simple_error, XResult};
|
||||
use sha2::Sha256;
|
||||
use std::fs;
|
||||
|
||||
const SIGNATURE_PREFIX: &str = "// @SCRIPT-SIGNATURE-";
|
||||
|
||||
fn main() {
|
||||
let test_file = "test.js";
|
||||
let content = fs::read_to_string(test_file).unwrap();
|
||||
|
||||
let mut script = Script::parse(&content).unwrap();
|
||||
let digest_sha256 = script.normalize_content_lines_and_sha256();
|
||||
|
||||
println!("{}", general_base64::STANDARD.encode(&digest_sha256));
|
||||
println!("{}", hex::encode(&digest_sha256));
|
||||
|
||||
if script.signature.is_some() {
|
||||
println!("File is signed.");
|
||||
return;
|
||||
}
|
||||
|
||||
script.signature = Some(ScriptSignature{
|
||||
key_id: "yk-r1".to_string(),
|
||||
algorithm: ScriptSignatureAlgorithm::ES256,
|
||||
signature: general_base64::STANDARD.decode("MEUCIQCRbIPTC7jTMiRYSLF+u4gt7JDcDO4m4Y5pqbk3NjTvogIgKYgbkeGXl6XTiChN7dWJZ04GmLeEnKF9xnnPN3o7W8w=").unwrap(),
|
||||
});
|
||||
fs::write(test_file, script.as_string()).unwrap();
|
||||
|
||||
// println!("{}", script.as_string());
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq)]
|
||||
enum ScriptSignatureAlgorithm {
|
||||
RS256,
|
||||
ES256,
|
||||
ES384,
|
||||
ES521,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct ScriptSignature {
|
||||
key_id: String,
|
||||
algorithm: ScriptSignatureAlgorithm,
|
||||
signature: Vec<u8>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Script {
|
||||
content_lines: Vec<String>,
|
||||
signature: Option<ScriptSignature>,
|
||||
}
|
||||
|
||||
impl ScriptSignatureAlgorithm {
|
||||
fn try_from(algo: &str) -> XResult<Self> {
|
||||
let upper_algo = algo.to_uppercase();
|
||||
Ok(match upper_algo.as_str() {
|
||||
"RS256" => Self::RS256,
|
||||
"ES256" => Self::ES256,
|
||||
"ES384" => Self::ES384,
|
||||
"ES521" => Self::ES521,
|
||||
_ => return simple_error!("Not valid algorithm: {}", algo),
|
||||
})
|
||||
}
|
||||
|
||||
fn as_str(&self) -> &'static str {
|
||||
match self {
|
||||
ScriptSignatureAlgorithm::RS256 => "RS256",
|
||||
ScriptSignatureAlgorithm::ES256 => "ES256",
|
||||
ScriptSignatureAlgorithm::ES384 => "ES384",
|
||||
ScriptSignatureAlgorithm::ES521 => "ES521",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ScriptSignature {
|
||||
fn parse(script_signature_line: &str) -> XResult<ScriptSignature> {
|
||||
// e.g. // @SCRIPT-SIGNATURE-V1: <key-id>.<algotirhm>.<signature-value-in-base64>
|
||||
let script_signature_v1_regex = Regex::new(r##"^//\s*@SCRIPT-SIGNATURE-V1:\s*([a-zA-Z0-9-_]+)\.([a-zA-Z0-9]+)\.([a-zA-Z0-9-+_/=]+)$"##).unwrap();
|
||||
let script_signature_v1_captures = opt_value_result!(
|
||||
script_signature_v1_regex.captures(script_signature_line),
|
||||
"Parse script signature failed: {}",
|
||||
script_signature_line
|
||||
);
|
||||
let (_, [key_id, algorithm, signature]) = script_signature_v1_captures.extract();
|
||||
|
||||
let signature = opt_result!(
|
||||
general_base64::STANDARD.decode(signature),
|
||||
"Parse script signature failed, decode signature failed: {}"
|
||||
);
|
||||
Ok(ScriptSignature {
|
||||
key_id: key_id.to_string(),
|
||||
algorithm: ScriptSignatureAlgorithm::try_from(algorithm)?,
|
||||
signature,
|
||||
})
|
||||
}
|
||||
|
||||
fn as_string(&self) -> String {
|
||||
let mut s = String::with_capacity(245);
|
||||
s.push_str(SIGNATURE_PREFIX);
|
||||
s.push_str("V1: ");
|
||||
s.push_str(&self.key_id);
|
||||
s.push('.');
|
||||
s.push_str(self.algorithm.as_str());
|
||||
s.push('.');
|
||||
s.push_str(&general_base64::STANDARD.encode(&self.signature));
|
||||
s
|
||||
}
|
||||
}
|
||||
|
||||
impl Script {
|
||||
fn parse(script: &str) -> XResult<Script> {
|
||||
let lines = script.lines().collect::<Vec<_>>();
|
||||
let last_non_empty_line = lines.iter().rev().skip_while(|ln| ln.is_empty()).next();
|
||||
match last_non_empty_line {
|
||||
Some(last_non_empty_line) if last_non_empty_line.starts_with(SIGNATURE_PREFIX) => {
|
||||
let script_signature = ScriptSignature::parse(last_non_empty_line)?;
|
||||
let final_lines = lines
|
||||
.iter()
|
||||
.rev()
|
||||
.skip_while(|ln| ln.is_empty())
|
||||
.skip(1)
|
||||
.collect::<Vec<_>>()
|
||||
.into_iter()
|
||||
.rev()
|
||||
.collect::<Vec<_>>();
|
||||
Ok(Script {
|
||||
content_lines: final_lines.iter().map(ToString::to_string).collect(),
|
||||
signature: Some(script_signature),
|
||||
})
|
||||
}
|
||||
_ => Ok(Script {
|
||||
content_lines: lines.iter().map(ToString::to_string).collect(),
|
||||
signature: None,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
fn as_string(&self) -> String {
|
||||
match &self.signature {
|
||||
None => self.content_lines.join("\n"),
|
||||
Some(signature) => self.content_lines.join("\n") + "\n" + &signature.as_string(),
|
||||
}
|
||||
}
|
||||
|
||||
fn normalize_content_lines(&self) -> Vec<String> {
|
||||
let mut normalized_content_lines = Vec::with_capacity(self.content_lines.len());
|
||||
for ln in &self.content_lines {
|
||||
let trimed_ln = ln.trim();
|
||||
if !trimed_ln.is_empty() {
|
||||
normalized_content_lines.push(trimed_ln.to_string());
|
||||
}
|
||||
}
|
||||
normalized_content_lines
|
||||
}
|
||||
|
||||
fn normalize_content_lines_and_sha256(&self) -> Vec<u8> {
|
||||
let normalized_content_lines = self.normalize_content_lines();
|
||||
let joined_normalized_content_lines = normalized_content_lines.join("\n");
|
||||
let mut hasher = Sha256::new();
|
||||
hasher.update(&joined_normalized_content_lines);
|
||||
hasher.finalize().to_vec()
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_script_signature_parse() {
|
||||
let v1 = "// @SCRIPT-SIGNATURE-V1: key-id.RS256.aGVsbG93b3JsZA==";
|
||||
let s = ScriptSignature::parse(v1).unwrap();
|
||||
assert_eq!("key-id", s.key_id);
|
||||
assert_eq!(ScriptSignatureAlgorithm::RS256, s.algorithm);
|
||||
assert_eq!(b"helloworld".to_vec(), s.signature);
|
||||
|
||||
assert_eq!(v1, s.as_string());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_script_parse_01() {
|
||||
let script = Script::parse("test script").unwrap();
|
||||
assert_eq!(1, script.content_lines.len());
|
||||
assert!(script.signature.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_script_parse_02() {
|
||||
let script =
|
||||
Script::parse("test script\n\n// @SCRIPT-SIGNATURE-V1: key-id.RS256.aGVsbG93b3JsZA==\n")
|
||||
.unwrap();
|
||||
assert_eq!(2, script.content_lines.len());
|
||||
assert_eq!("test script", script.content_lines[0]);
|
||||
assert_eq!("", script.content_lines[1]);
|
||||
assert_eq!(
|
||||
"Gfvd4W/64/WaSMaztA1nlq81vBLam8gcJokoOlGuGpc=",
|
||||
general_base64::STANDARD.encode(&script.normalize_content_lines_and_sha256())
|
||||
);
|
||||
assert!(script.signature.is_some());
|
||||
let s = script.signature.unwrap();
|
||||
assert_eq!("key-id", s.key_id);
|
||||
assert_eq!(ScriptSignatureAlgorithm::RS256, s.algorithm);
|
||||
assert_eq!(b"helloworld".to_vec(), s.signature);
|
||||
}
|
||||
Reference in New Issue
Block a user