Compare commits

1 Commits

Author SHA1 Message Date
5a80ff9870 feat: sign v2 2025-10-19 23:57:49 +08:00
2 changed files with 158 additions and 7 deletions

View File

@@ -6,7 +6,8 @@ mod util;
pub use crate::keymap::KeyMap;
use crate::sign::{ecdsaverify, EcdsaAlgorithm};
use crate::signature::{
CardEcSignResult, ScriptSignature, ScriptSignatureAlgorithm, SIGNATURE_PREFIX,
CardEcSignResult, ScriptSignature, ScriptSignatureAlgorithm, ScriptSignatureVersion,
SIGNATURE_PREFIX,
};
use crate::util::current_time;
use digest::Digest;
@@ -135,7 +136,48 @@ impl Script {
None => return simple_error!("Sign key id: {} not found", &signature.key_id),
Some(key) => key,
};
let key_bytes = hex::decode(&key.public_key_point_hex)?;
let mut verify_public_key = key.public_key_point_hex.clone();
if ScriptSignatureVersion::V2 == signature.ver {
match &signature.embed_signing_key {
Some(embed_signing_key) => {
let mut hasher = Sha256::new();
hasher.update(embed_signing_key.time.as_bytes());
hasher.update(&embed_signing_key.public_key);
let embed_digest_sha256 = hasher.finalize().to_vec();
let key_bytes = hex::decode(&key.public_key_point_hex)?;
match embed_signing_key.algorithm {
ScriptSignatureAlgorithm::ES256 => {
match ecdsaverify(
EcdsaAlgorithm::P256,
&key_bytes,
&embed_digest_sha256,
&embed_signing_key.signature,
) {
Ok(_) => {
verify_public_key = hex::encode(&embed_signing_key.public_key);
}
Err(e) => {
debugging!("Verify embed ecdsa signature failed: {}", e);
return Ok(false);
}
}
}
_ => {
return simple_error!(
"Not supported algorithm: {:?}",
signature.algorithm
)
}
}
}
None => {
return simple_error!("Embed signing key not found");
}
}
}
let key_bytes = hex::decode(&verify_public_key)?;
let digest_sha256 = self.normalize_content_lines_and_sha256(&signature.time);
match signature.algorithm {
ScriptSignatureAlgorithm::ES256 => {
@@ -169,7 +211,9 @@ impl Script {
);
if ecsign_result.algorithm == "ecdsa_p256_with_sha256" {
self.signature = Some(ScriptSignature {
ver: ScriptSignatureVersion::V1,
key_id: "yk-r1".to_string(),
embed_signing_key: None,
algorithm: ScriptSignatureAlgorithm::ES256,
time,
signature: hex::decode(&ecsign_result.signed_data_hex)?,
@@ -262,13 +306,16 @@ 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
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);
"##,
script_str
);
}
#[test]

View File

@@ -6,6 +6,7 @@ use serde::Deserialize;
pub const SIGNATURE_PREFIX: &str = "// @SCRIPT-SIGNATURE-";
pub const SIGNATURE_V1: &str = "V1";
pub const SIGNATURE_V2: &str = "V2";
#[derive(Debug, Eq, PartialEq)]
pub enum ScriptSignatureAlgorithm {
@@ -15,6 +16,12 @@ pub enum ScriptSignatureAlgorithm {
ES521,
}
#[derive(Debug, Eq, PartialEq)]
pub enum ScriptSignatureVersion {
V1,
V2,
}
impl ScriptSignatureAlgorithm {
pub fn try_from(algo: &str) -> XResult<Self> {
let upper_algo = algo.to_uppercase();
@@ -37,9 +44,19 @@ impl ScriptSignatureAlgorithm {
}
}
#[derive(Debug)]
pub struct EmbedSigningKey {
pub algorithm: ScriptSignatureAlgorithm,
pub time: String,
pub public_key: Vec<u8>,
pub signature: Vec<u8>,
}
#[derive(Debug)]
pub struct ScriptSignature {
pub ver: ScriptSignatureVersion,
pub key_id: String,
pub embed_signing_key: Option<EmbedSigningKey>,
pub algorithm: ScriptSignatureAlgorithm,
pub time: String,
pub signature: Vec<u8>,
@@ -57,22 +74,75 @@ pub struct CardEcSignResult {
impl ScriptSignature {
pub fn parse(script_signature_line: &str) -> XResult<ScriptSignature> {
// e.g. // @SCRIPT-SIGNATURE-V1: <key-id>.<algotirhm>.<time>.<signature-value-in-base64>
if script_signature_line.contains("SCRIPT-SIGNATURE-V1") {
Self::parse_v1(script_signature_line)
} else if script_signature_line.contains("SCRIPT-SIGNATURE-V2") {
Self::parse_v2(script_signature_line)
} else {
simple_error!("Invalid script signature: {}", script_signature_line)
}
}
fn parse_v1(script_signature_line: &str) -> XResult<ScriptSignature> {
// e.g. // @SCRIPT-SIGNATURE-V1: <key-id>.<algorithm>.<time>.<signature-value-in-base64>
let script_signature_v1_regex = Regex::new(
r##"^\s*//\s*@SCRIPT-SIGNATURE-V1:\s*([a-zA-Z0-9-_]+)\.([a-zA-Z0-9]+)\.([0-9a-zA-Z\-+:]+)\.([a-zA-Z0-9\-+_/=]+)\s*$"##).unwrap();
let script_signature_v1_captures = opt_value_result!(
script_signature_v1_regex.captures(script_signature_line),
"Parse script signature failed: {}",
"Parse script signature v1 failed: {}",
script_signature_line
);
let (_, [key_id, algorithm, time, signature]) = script_signature_v1_captures.extract();
let signature = opt_result!(
standard_base64.decode(signature),
"Parse script signature failed, decode signature failed: {}"
"Parse script signature v1 failed, decode signature failed: {}"
);
Ok(ScriptSignature {
ver: ScriptSignatureVersion::V1,
key_id: key_id.to_string(),
embed_signing_key: None,
algorithm: ScriptSignatureAlgorithm::try_from(algorithm)?,
time: time.to_string(),
signature,
})
}
fn parse_v2(script_signature_line: &str) -> XResult<ScriptSignature> {
// e.g. // @SCRIPT-SIGNATURE-V2: <key-id>.[<algorithm>.<time>.<public-key>.<signature-value-in-base64>].<algorithm>.<time>.<signature-value-in-base64>
let script_signature_v2_regex = Regex::new(
r##"^\s*//\s*@SCRIPT-SIGNATURE-V2:\s*([a-zA-Z0-9-_]+)\.\[([a-zA-Z0-9]+)\.([0-9a-zA-Z\-+:]+)\.([a-zA-Z0-9\-+_/=]+).([a-zA-Z0-9\-+_/=]+)\]\.([a-zA-Z0-9]+)\.([0-9a-zA-Z\-+:]+)\.([a-zA-Z0-9\-+_/=]+)\s*$"##).unwrap();
let script_signature_v2_captures = opt_value_result!(
script_signature_v2_regex.captures(script_signature_line),
"Parse script signature v2 failed: {}",
script_signature_line
);
let (
_,
[key_id, embed_algorithm, embed_time, embed_public_key, embed_signature, algorithm, time, signature],
) = script_signature_v2_captures.extract();
let embed_public_key = opt_result!(
hex::decode(embed_public_key),
"Parse script signature v2 failed, decode embed public key failed: {}"
);
let embed_signature = opt_result!(
standard_base64.decode(embed_signature),
"Parse script signature v2 failed, decode embed signature failed: {}"
);
let signature = opt_result!(
standard_base64.decode(signature),
"Parse script signature v2 failed, decode signature failed: {}"
);
Ok(ScriptSignature {
ver: ScriptSignatureVersion::V2,
key_id: key_id.to_string(),
embed_signing_key: Some(EmbedSigningKey {
algorithm: ScriptSignatureAlgorithm::try_from(embed_algorithm)?,
time: embed_time.to_string(),
public_key: embed_public_key,
signature: embed_signature,
}),
algorithm: ScriptSignatureAlgorithm::try_from(algorithm)?,
time: time.to_string(),
signature,
@@ -95,6 +165,13 @@ impl ScriptSignature {
}
pub fn as_string(&self) -> String {
match self.ver {
ScriptSignatureVersion::V1 => self.as_string_v1(),
ScriptSignatureVersion::V2 => self.as_string_v2(),
}
}
fn as_string_v1(&self) -> String {
let mut s = String::with_capacity(245);
s.push_str(SIGNATURE_PREFIX);
s.push_str(SIGNATURE_V1);
@@ -108,6 +185,33 @@ impl ScriptSignature {
s.push_str(&standard_base64.encode(&self.signature));
s
}
fn as_string_v2(&self) -> String {
let mut s = String::with_capacity(245);
s.push_str(SIGNATURE_PREFIX);
s.push_str(SIGNATURE_V2);
s.push_str(": ");
s.push_str(&self.key_id);
s.push('.');
s.push('[');
if let Some(embed_signing_key) = &self.embed_signing_key {
s.push_str(embed_signing_key.algorithm.as_str());
s.push('.');
s.push_str(&embed_signing_key.time);
s.push('.');
s.push_str(&hex::encode(&embed_signing_key.public_key));
s.push('.');
s.push_str(&standard_base64.encode(&embed_signing_key.signature));
}
s.push(']');
s.push('.');
s.push_str(self.algorithm.as_str());
s.push('.');
s.push_str(&self.time);
s.push('.');
s.push_str(&standard_base64.encode(&self.signature));
s
}
}
#[test]