From c6de8d51e2255cd737488aca1b0c241b417a6195 Mon Sep 17 00:00:00 2001 From: Simo Sorce Date: Tue, 26 Nov 2024 13:43:50 -0500 Subject: [PATCH] Handle compatibility for CKA_EC_POINT The PKCS#11 3.0 Spec was released with a bug in the description of the public point encoding for Edwards and Montgomery curves that foooled many implementations to assume these curves would return a DER encoded octet string as the value for CKA_EC_POINT. However an errata was issued and the 3.1 spec also confirms that the CKA_EC_POINT for these curves should be just a byte array (like the private value is). It is easy to accept either form and conver it for storage, the real tricky part is returning the point to applications. Applications that only handle one form and can't deal with different encodings may get an encoding they do not like and fail. Signed-off-by: Simo Sorce --- Cargo.toml | 2 + src/ec/eddsa.rs | 12 ++- src/ec/mod.rs | 95 ++++++++++++++----- src/ec/montgomery.rs | 12 ++- src/lib.rs | 214 ++++++++++++++++++++++++++++++++++++++----- src/tests/eddsa.rs | 48 ++++++++++ 6 files changed, 332 insertions(+), 51 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 8b885c46..83b860ca 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -82,3 +82,5 @@ fips = [ "rusqlite/bundled", "aes", "ecc_fips", "hash_all", "kdf_all", "rsa"] dynamic = [ ] # Builds against system libcrypto.so slow = [] # Enables slow tests + +pkcs11ver31 = [] # Changes defaults to 3.1 spec diff --git a/src/ec/eddsa.rs b/src/ec/eddsa.rs index f910d514..f2542b64 100644 --- a/src/ec/eddsa.rs +++ b/src/ec/eddsa.rs @@ -46,7 +46,7 @@ impl EDDSAPubFactory { impl ObjectFactory for EDDSAPubFactory { fn create(&self, template: &[CK_ATTRIBUTE]) -> Result { - let obj = self.default_object_create(template)?; + let mut obj = self.default_object_create(template)?; /* According to PKCS#11 v3.1 6.3.5: * CKA_EC_PARAMS, Byte array, @@ -77,8 +77,14 @@ impl ObjectFactory for EDDSAPubFactory { general_error(e) } })?; - if point.len() != ec_point_size(&oid)? { - return Err(CKR_ATTRIBUTE_VALUE_INVALID)?; + let rawsize = ec_point_size(&oid)?; + if point.len() != rawsize { + /* For compatibility with applications that use DER encoding */ + if point.len() == rawsize + 2 { + convert_ec_point_from_30(&mut obj)?; + } else { + return Err(CKR_ATTRIBUTE_VALUE_INVALID)?; + } } Ok(obj) diff --git a/src/ec/mod.rs b/src/ec/mod.rs index cee95fa2..4175bdde 100644 --- a/src/ec/mod.rs +++ b/src/ec/mod.rs @@ -1,7 +1,8 @@ // Copyright 2023 - 2024 Simo Sorce, Jakub Jelen // See LICENSE.txt file for terms -use crate::error::{device_error, Error, Result}; +use crate::attribute::Attribute; +use crate::error::{device_error, general_error, Error, Result}; use crate::interface::*; use crate::kasn1::oid::*; use crate::kasn1::pkcs::*; @@ -31,6 +32,22 @@ pub const BITS_ED448: usize = 456; pub const BITS_X25519: usize = 256; pub const BITS_X448: usize = 448; +const EC_POINT_BYTES_SECP256R1: usize = 2 * ((BITS_SECP256R1 + 7) / 8) + 1; +const EC_POINT_BYTES_SECP384R1: usize = 2 * ((BITS_SECP384R1 + 7) / 8) + 1; +const EC_POINT_BYTES_SECP521R1: usize = 2 * ((BITS_SECP521R1 + 7) / 8) + 1; +const EC_POINT_BYTES_ED25519: usize = (BITS_ED25519 + 7) / 8; +const EC_POINT_BYTES_ED448: usize = (BITS_ED448 + 7) / 8; +const EC_POINT_BYTES_X25519: usize = (BITS_X25519 + 7) / 8; +const EC_POINT_BYTES_X448: usize = (BITS_X448 + 7) / 8; + +const EC_KEY_BYTES_SECP256R1: usize = (BITS_SECP256R1 + 7) / 8; +const EC_KEY_BYTES_SECP384R1: usize = (BITS_SECP384R1 + 7) / 8; +const EC_KEY_BYTES_SECP521R1: usize = (BITS_SECP521R1 + 7) / 8; +const EC_KEY_BYTES_ED25519: usize = (BITS_ED25519 + 7) / 8; +const EC_KEY_BYTES_ED448: usize = (BITS_ED448 + 7) / 8; +const EC_KEY_BYTES_X25519: usize = (BITS_X25519 + 7) / 8; +const EC_KEY_BYTES_X448: usize = (BITS_X448 + 7) / 8; + /* Curve names as used in CurveName PrinableString */ pub const PRIME256V1: &str = "prime256v1"; pub const SECP384R1: &str = "secp384r1"; @@ -40,36 +57,28 @@ pub const EDWARDS448: &str = "edwards448"; pub const CURVE25519: &str = "curve25519"; pub const CURVE448: &str = "curve448"; -fn bits_to_bytes(bits: usize) -> usize { - (bits + 7) / 8 -} - -fn ec_secp_point_size(bits: usize) -> usize { - 2 * bits_to_bytes(bits) + 1 -} - pub fn ec_point_size(oid: &asn1::ObjectIdentifier) -> Result { match oid { - &EC_SECP256R1 => Ok(ec_secp_point_size(BITS_SECP256R1)), - &EC_SECP384R1 => Ok(ec_secp_point_size(BITS_SECP384R1)), - &EC_SECP521R1 => Ok(ec_secp_point_size(BITS_SECP521R1)), - &ED25519_OID => Ok(bits_to_bytes(BITS_ED25519)), - &ED448_OID => Ok(bits_to_bytes(BITS_ED448)), - &X25519_OID => Ok(bits_to_bytes(BITS_X25519)), - &X448_OID => Ok(bits_to_bytes(BITS_X448)), + &EC_SECP256R1 => Ok(EC_POINT_BYTES_SECP256R1), + &EC_SECP384R1 => Ok(EC_POINT_BYTES_SECP384R1), + &EC_SECP521R1 => Ok(EC_POINT_BYTES_SECP521R1), + &ED25519_OID => Ok(EC_POINT_BYTES_ED25519), + &ED448_OID => Ok(EC_POINT_BYTES_ED448), + &X25519_OID => Ok(EC_POINT_BYTES_X25519), + &X448_OID => Ok(EC_POINT_BYTES_X448), _ => Err(CKR_GENERAL_ERROR)?, } } pub fn ec_key_size(oid: &asn1::ObjectIdentifier) -> Result { match oid { - &EC_SECP256R1 => Ok(bits_to_bytes(BITS_SECP256R1)), - &EC_SECP384R1 => Ok(bits_to_bytes(BITS_SECP384R1)), - &EC_SECP521R1 => Ok(bits_to_bytes(BITS_SECP521R1)), - &ED25519_OID => Ok(bits_to_bytes(BITS_ED25519)), - &ED448_OID => Ok(bits_to_bytes(BITS_ED448)), - &X25519_OID => Ok(bits_to_bytes(BITS_X25519)), - &X448_OID => Ok(bits_to_bytes(BITS_X448)), + &EC_SECP256R1 => Ok(EC_KEY_BYTES_SECP256R1), + &EC_SECP384R1 => Ok(EC_KEY_BYTES_SECP384R1), + &EC_SECP521R1 => Ok(EC_KEY_BYTES_SECP521R1), + &ED25519_OID => Ok(EC_KEY_BYTES_ED25519), + &ED448_OID => Ok(EC_KEY_BYTES_ED448), + &X25519_OID => Ok(EC_KEY_BYTES_X25519), + &X448_OID => Ok(EC_KEY_BYTES_X448), _ => Err(CKR_GENERAL_ERROR)?, } } @@ -125,6 +134,17 @@ pub fn get_ec_point_from_obj(key: &Object) -> Result> { Ok(octet.to_vec()) } +pub fn convert_ec_point_from_30(key: &mut Object) -> Result<()> { + match key.get_attr_as_ulong(CKA_KEY_TYPE).map_err(general_error)? { + CKK_EC_EDWARDS | CKK_EC_MONTGOMERY => (), + _ => return Err(CKR_GENERAL_ERROR)?, + } + let point = key.get_attr_as_bytes(CKA_EC_POINT)?; + let decoded = asn1::parse_single::<&[u8]>(point).map_err(device_error)?; + key.set_attr(Attribute::from_bytes(CKA_EC_POINT, decoded.to_vec())) + .map_err(general_error) +} + #[cfg(test)] pub fn curvename_to_key_size(name: &str) -> Result { ec_key_size(&curvename_to_oid(name)?) @@ -135,3 +155,32 @@ pub fn curvename_to_ec_params(name: &str) -> Result> { let params = ECParameters::OId(curvename_to_oid(name)?); Ok(asn1::write_single(¶ms)?.to_vec()) } + +/* These functions are needed to fixup CKA_EC_POINT because of the different + * expected encoding in applications following the 3.0 spec vs applications + * following 3.1. It is not pretty, but it is the simplest way to handle + * this issue for now */ +#[cfg(any(feature = "eddsa", feature = "ec_montgomery"))] +pub fn point_len_to_der(len: usize) -> usize { + match len { + EC_POINT_BYTES_ED448 + | EC_POINT_BYTES_ED25519 /* matches also X25519 */ + | EC_POINT_BYTES_X448 => len + 2, + _ => len + } +} + +#[cfg(any(feature = "eddsa", feature = "ec_montgomery"))] +pub fn point_buf_to_der(buf: &[u8], bufsize: usize) -> Result>> { + match buf.len() { + EC_POINT_BYTES_ED448 + | EC_POINT_BYTES_ED25519 /* matches also X25519 */ + | EC_POINT_BYTES_X448 => { + if bufsize < buf.len() + 2 { + return Err(CKR_BUFFER_TOO_SMALL)?; + } + Ok(Some(asn1::write_single(&buf)?)) + } + _ => Ok(None), + } +} diff --git a/src/ec/montgomery.rs b/src/ec/montgomery.rs index bf1f6352..f35cc07d 100644 --- a/src/ec/montgomery.rs +++ b/src/ec/montgomery.rs @@ -47,7 +47,7 @@ impl ECMontgomeryPubFactory { impl ObjectFactory for ECMontgomeryPubFactory { fn create(&self, template: &[CK_ATTRIBUTE]) -> Result { - let obj = self.default_object_create(template)?; + let mut obj = self.default_object_create(template)?; /* According to PKCS#11 v3.1 6.3.7: * CKA_EC_PARAMS, Byte array, @@ -78,8 +78,14 @@ impl ObjectFactory for ECMontgomeryPubFactory { general_error(e) } })?; - if point.len() != ec_point_size(&oid)? { - return Err(CKR_ATTRIBUTE_VALUE_INVALID)?; + let rawsize = ec_point_size(&oid)?; + if point.len() != rawsize { + /* For compatibility with applications that use DER encoding */ + if point.len() == rawsize + 2 { + convert_ec_point_from_30(&mut obj)?; + } else { + return Err(CKR_ATTRIBUTE_VALUE_INVALID)?; + } } Ok(obj) diff --git a/src/lib.rs b/src/lib.rs index 72a2e200..cec60b6c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -892,6 +892,81 @@ extern "C" fn fn_get_attribute_value( unsafe { std::slice::from_raw_parts_mut(template, cnt) }; ret_to_rv!(token.get_object_attrs(o_handle, &mut tmpl)) } + +#[cfg(any(feature = "eddsa", feature = "ec_montgomery"))] +extern "C" fn fn_get_attribute_value_30( + s_handle: CK_SESSION_HANDLE, + o_handle: CK_OBJECT_HANDLE, + template: CK_ATTRIBUTE_PTR, + count: CK_ULONG, +) -> CK_RV { + use ec::{point_buf_to_der, point_len_to_der}; + + /* Ensure EC_POINT has enough space if there is an attempt to fecth */ + let mut ec_point_len: Option = None; + let cnt = cast_or_ret!(usize from count => CKR_ARGUMENTS_BAD); + let tmpl: &mut [CK_ATTRIBUTE] = + unsafe { std::slice::from_raw_parts_mut(template, cnt) }; + for a in tmpl.iter_mut() { + if a.type_ == CKA_EC_POINT { + let buflen = + cast_or_ret!(usize from a.ulValueLen => CKR_ARGUMENTS_BAD); + ec_point_len = Some(buflen); + } + } + + let ret = fn_get_attribute_value(s_handle, o_handle, template, count); + if let Some(bufsize) = ec_point_len { + for a in tmpl { + if a.type_ == CKA_EC_POINT + && a.ulValueLen != CK_UNAVAILABLE_INFORMATION + { + let buflen = + cast_or_ret!(usize from a.ulValueLen => CKR_GENERAL_ERROR); + if a.pValue == std::ptr::null_mut() { + let len = point_len_to_der(buflen); + if len != buflen { + a.ulValueLen = cast_or_ret!(CK_ULONG from len); + } + } else { + let buf: &mut [u8] = unsafe { + std::slice::from_raw_parts_mut( + a.pValue as *mut u8, + buflen, + ) + }; + let out = res_or_ret!(point_buf_to_der(buf, bufsize)); + if let Some(v) = out { + if v.len() > bufsize { + return CKR_GENERAL_ERROR; + } + unsafe { + /* update buffer with the DER encoded version */ + std::ptr::copy_nonoverlapping( + v.as_ptr(), + a.pValue as *mut u8, + v.len(), + ); + } + a.ulValueLen = cast_or_ret!(CK_ULONG from v.len()); + } + } + } + } + } + ret +} + +#[cfg(not(all(feature = "eddsa", feature = "ec_montgomery")))] +extern "C" fn fn_get_attribute_value_30( + s_handle: CK_SESSION_HANDLE, + o_handle: CK_OBJECT_HANDLE, + template: CK_ATTRIBUTE_PTR, + count: CK_ULONG, +) -> CK_RV { + fn_get_attribute_value(s_handle, o_handle, template, count) +} + extern "C" fn fn_set_attribute_value( s_handle: CK_SESSION_HANDLE, o_handle: CK_OBJECT_HANDLE, @@ -2331,7 +2406,7 @@ pub static FNLIST_240: CK_FUNCTION_LIST = CK_FUNCTION_LIST { C_CopyObject: Some(fn_copy_object), C_DestroyObject: Some(fn_destroy_object), C_GetObjectSize: Some(fn_get_object_size), - C_GetAttributeValue: Some(fn_get_attribute_value), + C_GetAttributeValue: Some(fn_get_attribute_value_30), C_SetAttributeValue: Some(fn_set_attribute_value), C_FindObjectsInit: Some(fn_find_objects_init), C_FindObjects: Some(fn_find_objects), @@ -3074,6 +3149,103 @@ pub static FNLIST_300: CK_FUNCTION_LIST_3_0 = CK_FUNCTION_LIST_3_0 { C_CopyObject: Some(fn_copy_object), C_DestroyObject: Some(fn_destroy_object), C_GetObjectSize: Some(fn_get_object_size), + C_GetAttributeValue: Some(fn_get_attribute_value_30), + C_SetAttributeValue: Some(fn_set_attribute_value), + C_FindObjectsInit: Some(fn_find_objects_init), + C_FindObjects: Some(fn_find_objects), + C_FindObjectsFinal: Some(fn_find_objects_final), + C_EncryptInit: Some(fn_encrypt_init), + C_Encrypt: Some(fn_encrypt), + C_EncryptUpdate: Some(fn_encrypt_update), + C_EncryptFinal: Some(fn_encrypt_final), + C_DecryptInit: Some(fn_decrypt_init), + C_Decrypt: Some(fn_decrypt), + C_DecryptUpdate: Some(fn_decrypt_update), + C_DecryptFinal: Some(fn_decrypt_final), + C_DigestInit: Some(fn_digest_init), + C_Digest: Some(fn_digest), + C_DigestUpdate: Some(fn_digest_update), + C_DigestKey: Some(fn_digest_key), + C_DigestFinal: Some(fn_digest_final), + C_SignInit: Some(fn_sign_init), + C_Sign: Some(fn_sign), + C_SignUpdate: Some(fn_sign_update), + C_SignFinal: Some(fn_sign_final), + C_SignRecoverInit: Some(fn_sign_recover_init), + C_SignRecover: Some(fn_sign_recover), + C_VerifyInit: Some(fn_verify_init), + C_Verify: Some(fn_verify), + C_VerifyUpdate: Some(fn_verify_update), + C_VerifyFinal: Some(fn_verify_final), + C_VerifyRecoverInit: Some(fn_verify_recover_init), + C_VerifyRecover: Some(fn_verify_recover), + C_DigestEncryptUpdate: Some(fn_digest_encrypt_update), + C_DecryptDigestUpdate: Some(fn_decrypt_digest_update), + C_SignEncryptUpdate: Some(fn_sign_encrypt_update), + C_DecryptVerifyUpdate: Some(fn_decrypt_verify_update), + C_GenerateKey: Some(fn_generate_key), + C_GenerateKeyPair: Some(fn_generate_key_pair), + C_WrapKey: Some(fn_wrap_key), + C_UnwrapKey: Some(fn_unwrap_key), + C_DeriveKey: Some(fn_derive_key), + C_SeedRandom: Some(fn_seed_random), + C_GenerateRandom: Some(fn_generate_random), + C_GetFunctionStatus: Some(fn_get_function_status), + C_CancelFunction: Some(fn_cancel_function), + C_WaitForSlotEvent: Some(fn_wait_for_slot_event), + C_GetInterfaceList: Some(C_GetInterfaceList), + C_GetInterface: Some(C_GetInterface), + C_LoginUser: Some(fn_login_user), + C_SessionCancel: Some(fn_session_cancel), + C_MessageEncryptInit: Some(fn_message_encrypt_init), + C_EncryptMessage: Some(fn_encrypt_message), + C_EncryptMessageBegin: Some(fn_encrypt_message_begin), + C_EncryptMessageNext: Some(fn_encrypt_message_next), + C_MessageEncryptFinal: Some(fn_message_encrypt_final), + C_MessageDecryptInit: Some(fn_message_decrypt_init), + C_DecryptMessage: Some(fn_decrypt_message), + C_DecryptMessageBegin: Some(fn_decrypt_message_begin), + C_DecryptMessageNext: Some(fn_decrypt_message_next), + C_MessageDecryptFinal: Some(fn_message_decrypt_final), + C_MessageSignInit: Some(fn_message_sign_init), + C_SignMessage: Some(fn_sign_message), + C_SignMessageBegin: Some(fn_sign_message_begin), + C_SignMessageNext: Some(fn_sign_message_next), + C_MessageSignFinal: Some(fn_message_sign_final), + C_MessageVerifyInit: Some(fn_message_verify_init), + C_VerifyMessage: Some(fn_verify_message), + C_VerifyMessageBegin: Some(fn_verify_message_begin), + C_VerifyMessageNext: Some(fn_verify_message_next), + C_MessageVerifyFinal: Some(fn_message_verify_final), +}; + +#[cfg(feature = "pkcs11ver31")] +pub static FNLIST_301: CK_FUNCTION_LIST_3_0 = CK_FUNCTION_LIST_3_0 { + version: CK_VERSION { major: 3, minor: 1 }, + C_Initialize: Some(fn_initialize), + C_Finalize: Some(fn_finalize), + C_GetInfo: Some(fn_get_info), + C_GetFunctionList: Some(C_GetFunctionList), + C_GetSlotList: Some(fn_get_slot_list), + C_GetSlotInfo: Some(fn_get_slot_info), + C_GetTokenInfo: Some(fn_get_token_info), + C_GetMechanismList: Some(fn_get_mechanism_list), + C_GetMechanismInfo: Some(fn_get_mechanism_info), + C_InitToken: Some(fn_init_token), + C_InitPIN: Some(fn_init_pin), + C_SetPIN: Some(fn_set_pin), + C_OpenSession: Some(fn_open_session), + C_CloseSession: Some(fn_close_session), + C_CloseAllSessions: Some(fn_close_all_sessions), + C_GetSessionInfo: Some(fn_get_session_info), + C_GetOperationState: Some(fn_get_operation_state), + C_SetOperationState: Some(fn_set_operation_state), + C_Login: Some(fn_login), + C_Logout: Some(fn_logout), + C_CreateObject: Some(fn_create_object), + C_CopyObject: Some(fn_copy_object), + C_DestroyObject: Some(fn_destroy_object), + C_GetObjectSize: Some(fn_get_object_size), C_GetAttributeValue: Some(fn_get_attribute_value), C_SetAttributeValue: Some(fn_set_attribute_value), C_FindObjectsInit: Some(fn_find_objects_init), @@ -3167,34 +3339,32 @@ struct InterfaceData { version: CK_VERSION, } unsafe impl Sync for CK_INTERFACE {} +unsafe impl Send for CK_INTERFACE {} unsafe impl Sync for InterfaceData {} +unsafe impl Send for InterfaceData {} -#[cfg(feature = "fips")] -static INTERFACE_SET: [InterfaceData; 3] = [ - InterfaceData { +static INTERFACE_SET: Lazy> = Lazy::new(|| { + let mut v = Vec::with_capacity(4); + #[cfg(feature = "pkcs11ver31")] + v.push(InterfaceData { + interface: std::ptr::addr_of!(INTERFACE_300), + version: FNLIST_301.version, + }); + v.push(InterfaceData { interface: std::ptr::addr_of!(INTERFACE_300), version: FNLIST_300.version, - }, - InterfaceData { + }); + v.push(InterfaceData { interface: std::ptr::addr_of!(INTERFACE_240), version: FNLIST_240.version, - }, - InterfaceData { + }); + #[cfg(feature = "fips")] + v.push(InterfaceData { interface: std::ptr::addr_of!(INTERFACE_VAL), version: FNLIST_VAL.version, - }, -]; -#[cfg(not(feature = "fips"))] -static INTERFACE_SET: [InterfaceData; 2] = [ - InterfaceData { - interface: std::ptr::addr_of!(INTERFACE_300), - version: FNLIST_300.version, - }, - InterfaceData { - interface: std::ptr::addr_of!(INTERFACE_240), - version: FNLIST_240.version, - }, -]; + }); + v +}); #[no_mangle] pub extern "C" fn C_GetInterfaceList( @@ -3257,7 +3427,7 @@ pub extern "C" fn C_GetInterface( interface_name }; - for intf in &INTERFACE_SET { + for intf in INTERFACE_SET.iter() { let found: bool = unsafe { let name = (*intf.interface).pInterfaceName as *const c_char; libc::strcmp(request_name as *const c_char, name) == 0 diff --git a/src/tests/eddsa.rs b/src/tests/eddsa.rs index 6269ccb9..e976ee1a 100644 --- a/src/tests/eddsa.rs +++ b/src/tests/eddsa.rs @@ -103,6 +103,54 @@ fn test_create_eddsa_objects() { ); assert_eq!(ret, CKR_OK); + /* Ensure 3.0 compatibility interface works as expected */ + /* Test Vectors for Ed25519ctx in DER format*/ + let point30 = hex::decode( + "0420dfc9425e4f968f7f0c29f0259cf5f9aed6851c2bb4ad8bfb860cfee0ab248292", + ) + .expect("Failed to decode hex point"); + let params = hex::decode("130c656477617264733235353139") + .expect("Failed to decode hex params"); + let public30_handle = ret_or_panic!(import_object( + session, + CKO_PUBLIC_KEY, + &[(CKA_KEY_TYPE, CKK_EC_EDWARDS)], + &[ + (CKA_LABEL, "Ed25519 with DER EC Point".as_bytes()), + (CKA_EC_POINT, point30.as_slice()), + (CKA_EC_PARAMS, params.as_slice()), + ], + &[(CKA_VERIFY, true)] + )); + + let mut value = vec![0u8; point30.len()]; + let mut extract_template = make_ptrs_template(&[( + CKA_EC_POINT, + void_ptr!(value.as_mut_ptr()), + value.len(), + )]); + + /* test both public_handle, and public30_handle, they should both + * return the same DER encoded point */ + let ret = fn_get_attribute_value_30( + session, + public30_handle, + extract_template.as_mut_ptr(), + extract_template.len() as CK_ULONG, + ); + assert_eq!(ret, CKR_OK); + assert_eq!(value, point30); + + value[0] = !value[0]; + let ret = fn_get_attribute_value_30( + session, + public_handle, + extract_template.as_mut_ptr(), + extract_template.len() as CK_ULONG, + ); + assert_eq!(ret, CKR_OK); + assert_eq!(value, point30); + testtokn.finalize(); }