feat: v0.1.1

This commit is contained in:
2025-01-23 00:13:00 +08:00
parent ebcf7b83d6
commit 84f66ae736
4 changed files with 138 additions and 32 deletions

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "script-sign" name = "script-sign"
version = "0.1.0" version = "0.1.1"
edition = "2021" edition = "2021"
authors = ["Hatter Jiang <jht5945@gmail.com>"] authors = ["Hatter Jiang <jht5945@gmail.com>"]
description = "Script Sign" description = "Script Sign"

View File

@@ -1,25 +1,35 @@
use rust_util::XResult;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::collections::HashMap; use std::collections::HashMap;
#[derive(Debug, Serialize, Deserialize)]
pub struct KeyMeta {
pub public_key_point_hex: String,
}
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
pub struct KeyMap { pub struct KeyMap {
key_map: HashMap<String, String>, pub key_map: HashMap<String, KeyMeta>,
} }
impl KeyMap { impl KeyMap {
pub fn system() -> XResult<Self> { pub fn system() -> Self {
let signing_keys = r##" let signing_keys = r##"
{ {
"yk-r1": "04dd3eebd906c9cf00b08ec29f7ed61804d1cc1d1352d9257b628191e08fc3717c4fae3298cd5c4829cec8bf3a946e7db60b7857e1287f6a0bae6b3f2342f007d0" "yk-r1": {
"public_key_point_hex": "04dd3eebd906c9cf00b08ec29f7ed61804d1cc1d1352d9257b628191e08fc3717c4fae3298cd5c4829cec8bf3a946e7db60b7857e1287f6a0bae6b3f2342f007d0"
}
} }
"##; "##;
// unwrap should not happen // unwrap should not happen
let key_map: HashMap<String, String> = serde_json::from_str(signing_keys).unwrap(); let key_map: HashMap<String, KeyMeta> = serde_json::from_str(signing_keys).unwrap();
Ok(KeyMap { key_map }) KeyMap { key_map }
} }
pub fn find(&self, key_id: &str) -> Option<&String> { pub fn from(key_map: HashMap<String, KeyMeta>) -> Self {
Self { key_map }
}
pub fn find(&self, key_id: &str) -> Option<&KeyMeta> {
self.key_map.get(key_id) self.key_map.get(key_id)
} }
} }

View File

@@ -22,7 +22,7 @@ pub struct Script {
impl Script { impl Script {
pub fn verify_script_file_with_system_key_map(script_file: &str) -> XResult<bool> { pub fn verify_script_file_with_system_key_map(script_file: &str) -> XResult<bool> {
Self::verify_script_file(script_file, &KeyMap::system()?) Self::verify_script_file(script_file, &KeyMap::system())
} }
pub fn verify_script_file(script_file: &str, key_map: &KeyMap) -> XResult<bool> { pub fn verify_script_file(script_file: &str, key_map: &KeyMap) -> XResult<bool> {
@@ -42,28 +42,59 @@ impl Script {
pub fn parse(script: &str) -> XResult<Script> { pub fn parse(script: &str) -> XResult<Script> {
let lines = script.lines().collect::<Vec<_>>(); let lines = script.lines().collect::<Vec<_>>();
let last_non_empty_line = lines.iter().rev().find(|ln| !ln.is_empty());
match last_non_empty_line { let mut in_signature_section = false;
Some(last_non_empty_line) if last_non_empty_line.starts_with(SIGNATURE_PREFIX) => { let mut signature_lines = vec![];
let script_signature = ScriptSignature::parse(last_non_empty_line)?; let mut content_lines = vec![];
let final_lines = lines let mut signature_line = String::new();
.iter()
.rev() let mut push_signature_line = |signature_line: &mut String| {
.skip_while(|ln| ln.is_empty()) if !signature_line.is_empty() {
.skip(1) signature_lines.push(signature_line.clone());
.collect::<Vec<_>>() signature_line.clear();
.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(),
for line in &lines {
if in_signature_section {
if line.starts_with(SIGNATURE_PREFIX) {
push_signature_line(&mut signature_line);
signature_line.push_str(line);
} else if line.starts_with("//") {
signature_line.push_str(line.chars().skip(2).collect::<String>().trim());
} else if !line.trim().is_empty() {
return simple_error!("Bad signature section line, find: '{line}'");
}
} else {
if line.starts_with(SIGNATURE_PREFIX) {
in_signature_section = true;
push_signature_line(&mut signature_line);
signature_line.push_str(line);
} else {
content_lines.push(line.to_string());
}
}
}
push_signature_line(&mut signature_line);
if signature_lines.len() > 1 {
return simple_error!(
"Found {} signatures, only supports one signature.",
signature_lines.len()
);
}
if signature_lines.is_empty() {
Ok(Script {
content_lines,
signature: None, signature: None,
}), })
} else {
let script_signature = ScriptSignature::parse(&signature_lines[0])?;
Ok(Script {
content_lines,
signature: Some(script_signature),
})
} }
} }
@@ -79,8 +110,13 @@ impl Script {
} else { } else {
joined_content_liens.push_str("\n\n"); joined_content_liens.push_str("\n\n");
} }
joined_content_liens.push_str(&signature.as_string()); let signature_lines = signature.as_string_lines_default_width();
joined_content_liens.push('\n'); for signature_line in &signature_lines {
joined_content_liens.push_str(&signature_line);
joined_content_liens.push('\n');
}
// joined_content_liens.push_str(&signature.as_string());
// joined_content_liens.push('\n');
joined_content_liens joined_content_liens
} }
} }
@@ -99,7 +135,7 @@ impl Script {
None => return simple_error!("Sign key id: {} not found", &signature.key_id), None => return simple_error!("Sign key id: {} not found", &signature.key_id),
Some(key) => key, Some(key) => key,
}; };
let key_bytes = hex::decode(key)?; let key_bytes = hex::decode(&key.public_key_point_hex)?;
let digest_sha256 = self.normalize_content_lines_and_sha256(&signature.time); let digest_sha256 = self.normalize_content_lines_and_sha256(&signature.time);
match signature.algorithm { match signature.algorithm {
ScriptSignatureAlgorithm::ES256 => { ScriptSignatureAlgorithm::ES256 => {
@@ -203,3 +239,48 @@ fn test_script_parse_02() {
assert_eq!("2025-01-05T20:57:14+08:00", s.time); assert_eq!("2025-01-05T20:57:14+08:00", s.time);
assert_eq!(b"helloworld".to_vec(), s.signature); assert_eq!(b"helloworld".to_vec(), s.signature);
} }
#[test]
fn test_script_parse_03() {
let script =
Script::parse(r##"#!/usr/bin/env -S deno run --allow-env
console.log("Hello world.");
// @SCRIPT-SIGNATURE-V1: yk-r1.ES256.20250122T233410+08:00.MEQCIGogDudoVpCVfGiNPu8Wn6YPDtFX5OXC4bKtsN1nw414AiAq+5EVdvOuKAlXdVeeE1d91mKX9TaSTR25jliUx0km6A=="##)
.unwrap();
assert!(script.verify(&KeyMap::system()).unwrap());
}
#[test]
fn test_script_parse_04() {
let script =
Script::parse(r##"#!/usr/bin/env -S deno run --allow-env
console.log("Hello world.");
// @SCRIPT-SIGNATURE-V1: yk-r1.ES256.20250122T233410+08:00.MEQCIGogDudoVpCVfGiNPu8Wn6YPDtFX5OXC4bKtsN1nw414AiAq+5EVdvOuKAlXdVeeE1d91mKX9TaSTR25jliUx0km6A=="##)
.unwrap();
let script_str = script.as_string();
assert_eq!(r##"#!/usr/bin/env -S deno run --allow-env
console.log("Hello world.");
// @SCRIPT-SIGNATURE-V1: yk-r1.ES256.20250122T233410+08:00.MEQCIGogDudoVpCVfGiNPu8W
// n6YPDtFX5OXC4bKtsN1nw414AiAq+5EVdvOuKAlXdVeeE1d91mKX9TaSTR25jliUx0km6A==
"##, script_str);
}
#[test]
fn test_script_parse_05() {
let script = Script::parse(
r##"#!/usr/bin/env -S deno run --allow-env
console.log("Hello world.");
// @SCRIPT-SIGNATURE-V1: yk-r1.ES256.20250122T233410+08:00.MEQCIGogDudoVpCVfGiNPu
// 8Wn6YPDtFX5OXC4bKtsN1nw414AiAq+5EVdvOuKAlXdVeeE1d91mKX9TaSTR25jliUx0km6A=="##,
)
.unwrap();
assert!(script.verify(&KeyMap::system()).unwrap());
}

View File

@@ -79,6 +79,21 @@ impl ScriptSignature {
}) })
} }
pub fn as_string_lines_default_width(&self) -> Vec<String> {
self.as_string_lines(80)
}
pub fn as_string_lines(&self, width: usize) -> Vec<String> {
let mut lines = vec![];
let str = self.as_string();
let chars = str.chars().skip(3).collect::<Vec<_>>();
let chunks = chars.chunks(width);
for chunk in chunks {
lines.push(format!("// {}", chunk.iter().collect::<String>()));
}
lines
}
pub fn as_string(&self) -> String { pub fn as_string(&self) -> String {
let mut s = String::with_capacity(245); let mut s = String::with_capacity(245);
s.push_str(SIGNATURE_PREFIX); s.push_str(SIGNATURE_PREFIX);