diff --git a/aws-lc-rs/src/ec/key_pair.rs b/aws-lc-rs/src/ec/key_pair.rs index 8da00d2970b..a50fdfdc21e 100644 --- a/aws-lc-rs/src/ec/key_pair.rs +++ b/aws-lc-rs/src/ec/key_pair.rs @@ -126,7 +126,9 @@ impl EcdsaKeyPair { /// `error::Unspecified` on internal error. /// pub fn to_pkcs8v1(&self) -> Result { - self.evp_pkey.marshall_private_key(Version::V1) + Ok(Document::new( + self.evp_pkey.marshall_private_key(Version::V1)?, + )) } /// Constructs an ECDSA key pair from the private key and public key bytes diff --git a/aws-lc-rs/src/ed25519.rs b/aws-lc-rs/src/ed25519.rs index 1c0552c81b3..f9379f3bd40 100644 --- a/aws-lc-rs/src/ed25519.rs +++ b/aws-lc-rs/src/ed25519.rs @@ -13,12 +13,17 @@ use untrusted::Input; use zeroize::Zeroize; use aws_lc::{ - ED25519_keypair_from_seed, ED25519_sign, ED25519_verify, EVP_PKEY_CTX_new_id, - EVP_PKEY_get_raw_private_key, EVP_PKEY_get_raw_public_key, EVP_PKEY_keygen, - EVP_PKEY_keygen_init, EVP_PKEY_new_raw_private_key, EVP_PKEY, EVP_PKEY_ED25519, + CBS_init, EVP_DigestSign, EVP_DigestSignInit, EVP_DigestVerify, EVP_DigestVerifyInit, + EVP_MD_CTX_init, EVP_PKEY_CTX_new_id, EVP_PKEY_get_raw_private_key, + EVP_PKEY_get_raw_public_key, EVP_PKEY_id, EVP_PKEY_keygen, EVP_PKEY_keygen_init, + EVP_PKEY_new_raw_private_key, EVP_PKEY_new_raw_public_key, EVP_marshal_public_key, + EVP_parse_public_key, CBS, EVP_MD_CTX, EVP_PKEY, EVP_PKEY_ED25519, }; -use crate::encoding::{AsBigEndian, Curve25519SeedBin}; +use crate::cbb::LcCBB; +use crate::encoding::{ + AsBigEndian, AsDer, Curve25519SeedBin, Pkcs8V1Der, Pkcs8V2Der, PublicKeyX509Der, +}; use crate::error::{KeyRejected, Unspecified}; use crate::fips::indicator_check; use crate::pkcs8::{Document, Version}; @@ -63,25 +68,76 @@ impl VerificationAlgorithm for EdDSAParameters { msg: &[u8], signature: &[u8], ) -> Result<(), Unspecified> { + let public_key = try_ed25519_public_key_from_bytes(public_key)?; + + let mut evp_md_ctx = { + let mut evp_md_ctx = MaybeUninit::::uninit(); + unsafe { + EVP_MD_CTX_init(evp_md_ctx.as_mut_ptr()); + evp_md_ctx.assume_init() + } + }; + if 1 != unsafe { - ED25519_verify( + EVP_DigestVerifyInit( + &mut evp_md_ctx, + null_mut(), + null_mut(), + null_mut(), + *public_key, + ) + } { + return Err(Unspecified); + } + + if 1 != indicator_check!(unsafe { + EVP_DigestVerify( + &mut evp_md_ctx, + signature.as_ptr(), + signature.len(), msg.as_ptr(), msg.len(), - signature.as_ptr(), - public_key.as_ptr(), ) - } { + }) { return Err(Unspecified); } - crate::fips::set_fips_service_status_unapproved(); + Ok(()) } } +fn try_ed25519_public_key_from_bytes(key_bytes: &[u8]) -> Result, Unspecified> { + // If the length of key bytes matches the raw public key size then it has to be that + if key_bytes.len() == ED25519_PUBLIC_KEY_LEN { + return Ok(LcPtr::new(unsafe { + EVP_PKEY_new_raw_public_key( + EVP_PKEY_ED25519, + null_mut(), + key_bytes.as_ptr(), + key_bytes.len(), + ) + })?); + } + // Otherwise we support X.509 SubjectPublicKeyInfo formatted keys which are inherently larger + let mut cbs = { + let mut cbs = MaybeUninit::::uninit(); + unsafe { + CBS_init(cbs.as_mut_ptr(), key_bytes.as_ptr(), key_bytes.len()); + cbs.assume_init() + } + }; + let evp_pkey = LcPtr::new(unsafe { EVP_parse_public_key(&mut cbs) })?; + if EVP_PKEY_ED25519 != unsafe { EVP_PKEY_id(*evp_pkey.as_const()) } { + return Err(Unspecified); + } + Ok(evp_pkey) +} + /// An Ed25519 key pair, for signing. #[allow(clippy::module_name_repetitions)] pub struct Ed25519KeyPair { - private_key: Box<[u8; ED25519_PRIVATE_KEY_LEN]>, + evp_pkey: LcPtr, + private_key_bytes: Box<[u8; ED25519_PRIVATE_KEY_LEN]>, public_key: PublicKey, } @@ -96,7 +152,7 @@ impl Debug for Ed25519KeyPair { impl Drop for Ed25519KeyPair { fn drop(&mut self) { - self.private_key.zeroize(); + self.private_key_bytes.zeroize(); } } @@ -113,7 +169,7 @@ impl AsBigEndian> for Seed<'_> { /// # Errors /// `error::Unspecified` if serialization failed. fn as_be_bytes(&self) -> Result, Unspecified> { - let buffer = Vec::from(&self.0.private_key[..ED25519_PRIVATE_KEY_SEED_LEN]); + let buffer = Vec::from(&self.0.private_key_bytes[..ED25519_PRIVATE_KEY_SEED_LEN]); Ok(Curve25519SeedBin::new(buffer)) } } @@ -126,19 +182,43 @@ impl Debug for Seed<'_> { #[derive(Clone)] #[allow(clippy::module_name_repetitions)] -pub struct PublicKey([u8; ED25519_PUBLIC_KEY_LEN]); +pub struct PublicKey { + evp_pkey: LcPtr, + public_key_bytes: [u8; ED25519_PUBLIC_KEY_LEN], +} impl AsRef<[u8]> for PublicKey { #[inline] /// Returns the "raw" bytes of the ED25519 public key fn as_ref(&self) -> &[u8] { - &self.0 + &self.public_key_bytes } } impl Debug for PublicKey { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - f.write_str(&format!("PublicKey(\"{}\")", hex::encode(self.0))) + f.write_str(&format!( + "PublicKey(\"{}\")", + hex::encode(self.public_key_bytes) + )) + } +} + +unsafe impl Send for PublicKey {} +unsafe impl Sync for PublicKey {} + +impl AsDer> for PublicKey { + fn as_der(&self) -> Result, crate::error::Unspecified> { + // Initial size of 44 based on: + // 0:d=0 hl=2 l= 42 cons: SEQUENCE + // 2:d=1 hl=2 l= 5 cons: SEQUENCE + // 4:d=2 hl=2 l= 3 prim: OBJECT :ED25519 + // 9:d=1 hl=2 l= 33 prim: BIT STRING + let mut cbb = LcCBB::new(44); + if 1 != unsafe { EVP_marshal_public_key(cbb.as_mut_ptr(), *self.evp_pkey) } { + return Err(Unspecified); + } + Ok(PublicKeyX509Der::from(cbb.into_buffer()?)) } } @@ -150,6 +230,9 @@ impl KeyPair for Ed25519KeyPair { } } +unsafe impl Send for Ed25519KeyPair {} +unsafe impl Sync for Ed25519KeyPair {} + pub(crate) fn generate_key() -> Result, ()> { let pkey_ctx = LcPtr::new(unsafe { EVP_PKEY_CTX_new_id(EVP_PKEY_ED25519, null_mut()) })?; @@ -193,7 +276,7 @@ impl Ed25519KeyPair { /// `error::Unspecified` if `rng` cannot provide enough bits or if there's an internal error. pub fn generate_pkcs8(_rng: &dyn SecureRandom) -> Result { let evp_pkey = generate_key()?; - evp_pkey.marshall_private_key(Version::V2) + Ok(Document::new(evp_pkey.marshall_private_key(Version::V2)?)) } /// Serializes this `Ed25519KeyPair` into a PKCS#8 v2 document. @@ -202,16 +285,9 @@ impl Ed25519KeyPair { /// `error::Unspecified` on internal error. /// pub fn to_pkcs8(&self) -> Result { - unsafe { - let evp_pkey: LcPtr = LcPtr::new(EVP_PKEY_new_raw_private_key( - EVP_PKEY_ED25519, - null_mut(), - self.private_key.as_ref().as_ptr(), - ED25519_PRIVATE_KEY_SEED_LEN, - ))?; - - evp_pkey.marshall_private_key(Version::V2) - } + Ok(Document::new( + self.evp_pkey.marshall_private_key(Version::V2)?, + )) } /// Generates a `Ed25519KeyPair` using the `rng` provided, then serializes that key as a @@ -230,7 +306,7 @@ impl Ed25519KeyPair { /// `error::Unspecified` if `rng` cannot provide enough bits or if there's an internal error. pub fn generate_pkcs8v1(_rng: &dyn SecureRandom) -> Result { let evp_pkey = generate_key()?; - evp_pkey.marshall_private_key(Version::V1) + Ok(Document::new(evp_pkey.marshall_private_key(Version::V1)?)) } /// Serializes this `Ed25519KeyPair` into a PKCS#8 v1 document. @@ -239,16 +315,9 @@ impl Ed25519KeyPair { /// `error::Unspecified` on internal error. /// pub fn to_pkcs8v1(&self) -> Result { - let evp_pkey: LcPtr = LcPtr::new(unsafe { - EVP_PKEY_new_raw_private_key( - EVP_PKEY_ED25519, - null_mut(), - self.private_key.as_ref().as_ptr(), - ED25519_PRIVATE_KEY_SEED_LEN, - ) - })?; - - evp_pkey.marshall_private_key(Version::V1) + Ok(Document::new( + self.evp_pkey.marshall_private_key(Version::V1)?, + )) } /// Constructs an Ed25519 key pair from the private key seed `seed` and its @@ -269,24 +338,58 @@ impl Ed25519KeyPair { return Err(KeyRejected::inconsistent_components()); } - let mut derived_public_key = MaybeUninit::<[u8; ED25519_PUBLIC_KEY_LEN]>::uninit(); - let mut private_key = MaybeUninit::<[u8; ED25519_PRIVATE_KEY_LEN]>::uninit(); - unsafe { - ED25519_keypair_from_seed( + let evp_pkey = LcPtr::new(unsafe { + EVP_PKEY_new_raw_private_key(EVP_PKEY_ED25519, null_mut(), seed.as_ptr(), seed.len()) + })?; + + let mut derived_public_key = [0u8; ED25519_PUBLIC_KEY_LEN]; + let mut out_len: usize = derived_public_key.len(); + if 1 != unsafe { + EVP_PKEY_get_raw_public_key( + *evp_pkey.as_const(), derived_public_key.as_mut_ptr().cast(), - private_key.as_mut_ptr().cast(), - seed.as_ptr(), - ); + &mut out_len, + ) + } { + return Err(KeyRejected::unspecified()); + } + debug_assert_eq!(derived_public_key.len(), out_len); + + let mut private_key = [0u8; ED25519_PRIVATE_KEY_LEN]; + debug_assert_eq!( + ED25519_PRIVATE_KEY_LEN, + ED25519_PRIVATE_KEY_SEED_LEN + ED25519_PUBLIC_KEY_LEN + ); + let mut out_len: usize = private_key.len(); + if 1 != unsafe { + EVP_PKEY_get_raw_private_key( + *evp_pkey.as_const(), + private_key.as_mut_ptr(), + &mut out_len, + ) + } { + return Err(KeyRejected::unspecified()); } - let derived_public_key = unsafe { derived_public_key.assume_init() }; - let mut private_key = unsafe { private_key.assume_init() }; + debug_assert_eq!(ED25519_PRIVATE_KEY_SEED_LEN, out_len); // The output should be the 32-byte private seed length - constant_time::verify_slices_are_equal(public_key, &derived_public_key) - .map_err(|_| KeyRejected::inconsistent_components())?; + private_key + [ED25519_PRIVATE_KEY_SEED_LEN..ED25519_PRIVATE_KEY_SEED_LEN + ED25519_PUBLIC_KEY_LEN] + .copy_from_slice(&derived_public_key); + + constant_time::verify_slices_are_equal( + public_key, + &private_key[ED25519_PRIVATE_KEY_SEED_LEN + ..ED25519_PRIVATE_KEY_SEED_LEN + ED25519_PUBLIC_KEY_LEN], + ) + .map_err(|_| KeyRejected::inconsistent_components())?; let key_pair = Self { - private_key: Box::new(private_key), - public_key: PublicKey(derived_public_key), + private_key_bytes: Box::new(private_key), + public_key: PublicKey { + public_key_bytes: derived_public_key, + evp_pkey: evp_pkey.clone(), + }, + evp_pkey, }; private_key.zeroize(); Ok(key_pair) @@ -353,8 +456,12 @@ impl Ed25519KeyPair { private_key[ED25519_PRIVATE_KEY_SEED_LEN..].copy_from_slice(&public_key); let key_pair = Self { - private_key: Box::new(private_key), - public_key: PublicKey(public_key), + private_key_bytes: Box::new(private_key), + public_key: PublicKey { + public_key_bytes: public_key, + evp_pkey: evp_pkey.clone(), + }, + evp_pkey, }; private_key.zeroize(); @@ -376,21 +483,41 @@ impl Ed25519KeyPair { #[inline] fn try_sign(&self, msg: &[u8]) -> Result { - let mut sig_bytes = MaybeUninit::<[u8; ED25519_SIGNATURE_LEN]>::uninit(); + let mut sig_bytes = [0u8; ED25519_SIGNATURE_LEN]; + let mut evp_md_ctx = { + let mut evp_md_ctx = MaybeUninit::::uninit(); + unsafe { + EVP_MD_CTX_init(evp_md_ctx.as_mut_ptr()); + evp_md_ctx.assume_init() + } + }; + if 1 != unsafe { - ED25519_sign( + EVP_DigestSignInit( + &mut evp_md_ctx, + null_mut(), + null_mut(), + null_mut(), + *self.evp_pkey, + ) + } { + return Err(Unspecified); + } + + let mut out_sig_len = sig_bytes.len(); + if 1 != indicator_check!(unsafe { + EVP_DigestSign( + &mut evp_md_ctx, sig_bytes.as_mut_ptr().cast(), + &mut out_sig_len, msg.as_ptr(), msg.len(), - self.private_key.as_ptr(), ) - } { + }) { return Err(Unspecified); } - crate::fips::set_fips_service_status_unapproved(); - - let sig_bytes = unsafe { sig_bytes.assume_init() }; + debug_assert_eq!(out_sig_len, sig_bytes.len()); Ok(Signature::new(|slice| { slice[0..ED25519_SIGNATURE_LEN].copy_from_slice(&sig_bytes); @@ -409,28 +536,60 @@ impl Ed25519KeyPair { } } +impl AsDer> for Ed25519KeyPair { + fn as_der(&self) -> Result, crate::error::Unspecified> { + Ok(Pkcs8V1Der::new( + self.evp_pkey.marshall_private_key(Version::V1)?.into_vec(), + )) + } +} + +impl AsDer> for Ed25519KeyPair { + fn as_der(&self) -> Result, crate::error::Unspecified> { + Ok(Pkcs8V2Der::new( + self.evp_pkey.marshall_private_key(Version::V2)?.into_vec(), + )) + } +} + #[cfg(test)] mod tests { use crate::ed25519::Ed25519KeyPair; + use crate::encoding::{AsDer, Pkcs8V1Der, Pkcs8V2Der, PublicKeyX509Der}; use crate::rand::SystemRandom; - use crate::test; + use crate::signature::KeyPair; + use crate::{hex, test}; #[test] fn test_generate_pkcs8() { let rng = SystemRandom::new(); let document = Ed25519KeyPair::generate_pkcs8(&rng).unwrap(); let kp1: Ed25519KeyPair = Ed25519KeyPair::from_pkcs8(document.as_ref()).unwrap(); + assert_eq!( + document.as_ref(), + AsDer::::as_der(&kp1).unwrap().as_ref() + ); let kp2: Ed25519KeyPair = Ed25519KeyPair::from_pkcs8_maybe_unchecked(document.as_ref()).unwrap(); - assert_eq!(kp1.private_key.as_slice(), kp2.private_key.as_slice()); + assert_eq!( + kp1.private_key_bytes.as_slice(), + kp2.private_key_bytes.as_slice() + ); assert_eq!(kp1.public_key.as_ref(), kp2.public_key.as_ref()); let document = Ed25519KeyPair::generate_pkcs8v1(&rng).unwrap(); let kp1: Ed25519KeyPair = Ed25519KeyPair::from_pkcs8(document.as_ref()).unwrap(); + assert_eq!( + document.as_ref(), + AsDer::::as_der(&kp1).unwrap().as_ref() + ); let kp2: Ed25519KeyPair = Ed25519KeyPair::from_pkcs8_maybe_unchecked(document.as_ref()).unwrap(); - assert_eq!(kp1.private_key.as_slice(), kp2.private_key.as_slice()); + assert_eq!( + kp1.private_key_bytes.as_slice(), + kp2.private_key_bytes.as_slice() + ); assert_eq!(kp1.public_key.as_ref(), kp2.public_key.as_ref()); let seed = kp1.seed().unwrap(); assert_eq!("Ed25519Seed()", format!("{seed:?}")); @@ -479,4 +638,20 @@ mod tests { ); } } + + #[test] + fn test_public_key_as_der_x509() { + let key_pair = Ed25519KeyPair::from_pkcs8(&hex::decode("302e020100300506032b6570042204209d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60").unwrap()).unwrap(); + let public_key = key_pair.public_key(); + let x509der = AsDer::::as_der(public_key).unwrap(); + assert_eq!( + x509der.as_ref(), + &[ + 0x30, 0x2a, 0x30, 0x05, 0x06, 0x03, 0x2b, 0x65, 0x70, 0x03, 0x21, 0x00, 0xd7, 0x5a, + 0x98, 0x01, 0x82, 0xb1, 0x0a, 0xb7, 0xd5, 0x4b, 0xfe, 0xd3, 0xc9, 0x64, 0x07, 0x3a, + 0x0e, 0xe1, 0x72, 0xf3, 0xda, 0xa6, 0x23, 0x25, 0xaf, 0x02, 0x1a, 0x68, 0xf7, 0x07, + 0x51, 0x1a + ] + ); + } } diff --git a/aws-lc-rs/src/encoding.rs b/aws-lc-rs/src/encoding.rs index b561118dbc4..cf506219615 100644 --- a/aws-lc-rs/src/encoding.rs +++ b/aws-lc-rs/src/encoding.rs @@ -60,7 +60,8 @@ generated_encodings!( EcPrivateKeyRfc5915Der, PublicKeyX509Der, Curve25519SeedBin, - Pkcs8V1Der + Pkcs8V1Der, + Pkcs8V2Der ); /// Trait for types that can be serialized into a DER format. diff --git a/aws-lc-rs/src/evp_pkey.rs b/aws-lc-rs/src/evp_pkey.rs index 3c0d40e5b4b..669814eeb84 100644 --- a/aws-lc-rs/src/evp_pkey.rs +++ b/aws-lc-rs/src/evp_pkey.rs @@ -5,7 +5,7 @@ use crate::cbb::LcCBB; use crate::cbs; use crate::ec::PKCS8_DOCUMENT_MAX_LEN; use crate::error::{KeyRejected, Unspecified}; -use crate::pkcs8::{Document, Version}; +use crate::pkcs8::Version; use crate::ptr::LcPtr; use aws_lc::{ EVP_PKEY_bits, EVP_PKEY_get1_EC_KEY, EVP_PKEY_get1_RSA, EVP_PKEY_id, EVP_PKEY_up_ref, @@ -85,7 +85,7 @@ impl LcPtr { } } - pub(crate) fn marshall_private_key(&self, version: Version) -> Result { + pub(crate) fn marshall_private_key(&self, version: Version) -> Result, Unspecified> { let mut buffer = vec![0u8; PKCS8_DOCUMENT_MAX_LEN]; let out_len = { @@ -110,7 +110,7 @@ impl LcPtr { buffer.truncate(out_len); - Ok(Document::new(buffer.into_boxed_slice())) + Ok(buffer.into_boxed_slice()) } } diff --git a/aws-lc-rs/src/unstable/kdf.rs b/aws-lc-rs/src/unstable/kdf.rs index b3a07d85b35..01657d9cc17 100644 --- a/aws-lc-rs/src/unstable/kdf.rs +++ b/aws-lc-rs/src/unstable/kdf.rs @@ -240,7 +240,7 @@ mod tests { mod notfips { use crate::unstable::kdf::{ get_kbkdf_ctr_hmac_algorithm, get_sskdf_digest_algorithm, get_sskdf_hmac_algorithm, - kbkdf, kbkdf_ctr_hmac, sskdf::SskdfHmacAlgorithmId, sskdf_digest, sskdf_hmac, + kbkdf_ctr_hmac, sskdf::SskdfHmacAlgorithmId, sskdf_digest, sskdf_hmac, KbkdfCtrHmacAlgorithmId, SskdfDigestAlgorithmId, }; diff --git a/aws-lc-rs/tests/data/ed25519_verify_tests.txt b/aws-lc-rs/tests/data/ed25519_verify_tests.txt index d430484bdd0..f3a01b5c244 100644 --- a/aws-lc-rs/tests/data/ed25519_verify_tests.txt +++ b/aws-lc-rs/tests/data/ed25519_verify_tests.txt @@ -31,4 +31,38 @@ Result = P PUB = 100fdf47fb94f1536a4f7c3fda27383fa03375a8f527c537e6f1703c47f94f86 MESSAGE = 6a0bc2b0057cedfc0fa2e3f7f7d39279b30f454a69dfd1117c758d86b19d85e0 SIG = 0971f86d2c9c78582524a103cb9cf949522ae528f8054dc20107d999be673ff4e25ebf2f2928766b1248bec6e91697775f8446639ede46ad4df4053000000010 -Result = F \ No newline at end of file +Result = F + +# Previous Test Cases with PUB encoded as X.509 SubjectPublicKeyInfo + +# Control; S is in range. +MESSAGE = 54657374 +SIG = 7c38e026f29e14aabd059a0f2db8b0cd783040609a8be684db12f82a27774ab07a9155711ecfaf7f99f277bad0c6ae7e39d4eef676573336a5c51eb6f946b30d +PUB = 302a300506032b65700321007d4d0e7f6153a69b6242b522abbee685fda4420f8834b108c3bdae369ef549fa +Result = P + +# Same as above, but with the order L added to S so it is out of range. +# BoringSSL commit 472ba2c2dd52d06a657a63b7fbf02732a6649d21 +MESSAGE = 54657374 +SIG = 7c38e026f29e14aabd059a0f2db8b0cd783040609a8be684db12f82a27774ab067654bce3832c2d76f8f6f5dafc08d9339d4eef676573336a5c51eb6f946b31d +PUB = 302a300506032b65700321007d4d0e7f6153a69b6242b522abbee685fda4420f8834b108c3bdae369ef549fa +Result = F + +# BoringSSL commit 3094902fcdc2db2cc832fa854b9a6a8be383926c +MESSAGE = 124e583f8b8eca58bb29c271b41d36986bbc45541f8e51f9cb0133eca447601e +SIG = dac119d6ca87fc59ae611c157048f4d4fc932a149dbe20ec6effd1436abf83ea05c7df0fef06147241259113909bc71bd3c53ba4464ffcad3c0968f2ffffff0f +PUB = 302a300506032b6570032100100fdf47fb94f1536a4f7c3fda27383fa03375a8f527c537e6f1703c47f94f86 +Result = P + +# Control. Same key as above; same message and signature as below, except S is in range. +PUB = 302a300506032b6570032100100fdf47fb94f1536a4f7c3fda27383fa03375a8f527c537e6f1703c47f94f86 +MESSAGE = 6a0bc2b0057cedfc0fa2e3f7f7d39279b30f454a69dfd1117c758d86b19d85e0 +SIG = 0971f86d2c9c78582524a103cb9cf949522ae528f8054dc20107d999be673ff4f58ac9d20ec563133cabc6230b1db8625f8446639ede46ad4df4053000000000 +Result = P + +# Same key as above, but S is out of range. +# BoringSSL commit 472ba2c2dd52d06a657a63b7fbf02732a6649d21 +PUB = 302a300506032b6570032100100fdf47fb94f1536a4f7c3fda27383fa03375a8f527c537e6f1703c47f94f86 +MESSAGE = 6a0bc2b0057cedfc0fa2e3f7f7d39279b30f454a69dfd1117c758d86b19d85e0 +SIG = 0971f86d2c9c78582524a103cb9cf949522ae528f8054dc20107d999be673ff4e25ebf2f2928766b1248bec6e91697775f8446639ede46ad4df4053000000010 +Result = F