feat: v0.1.1
This commit is contained in:
@@ -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"
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
125
src/lib.rs
125
src/lib.rs
@@ -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,29 +42,60 @@ 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<_>>();
|
|
||||||
|
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 {
|
Ok(Script {
|
||||||
content_lines: final_lines.iter().map(ToString::to_string).collect(),
|
content_lines,
|
||||||
|
signature: None,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
let script_signature = ScriptSignature::parse(&signature_lines[0])?;
|
||||||
|
Ok(Script {
|
||||||
|
content_lines,
|
||||||
signature: Some(script_signature),
|
signature: Some(script_signature),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
_ => Ok(Script {
|
|
||||||
content_lines: lines.iter().map(ToString::to_string).collect(),
|
|
||||||
signature: None,
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn as_string(&self) -> String {
|
pub fn as_string(&self) -> String {
|
||||||
@@ -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();
|
||||||
|
for signature_line in &signature_lines {
|
||||||
|
joined_content_liens.push_str(&signature_line);
|
||||||
joined_content_liens.push('\n');
|
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());
|
||||||
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
Reference in New Issue
Block a user