From 3b8fc6227f968bd279696a60d9c44a3b7a50ccd2 Mon Sep 17 00:00:00 2001 From: Laura Abbott Date: Tue, 23 Jul 2024 16:29:01 -0400 Subject: [PATCH 1/2] Add attest messages These are designed to be sent over IPCC (communication channel between Host OS and SP) --- attest-data/src/lib.rs | 2 + attest-data/src/messages.rs | 631 ++++++++++++++++++++++++++++++++++++ dice-cert-tmpl/src/csr.rs | 12 +- dice-cert-tmpl/src/lib.rs | 2 +- dice-mfg-msgs/src/lib.rs | 4 +- 5 files changed, 642 insertions(+), 9 deletions(-) create mode 100644 attest-data/src/messages.rs diff --git a/attest-data/src/lib.rs b/attest-data/src/lib.rs index c5cef73..3d8c04f 100644 --- a/attest-data/src/lib.rs +++ b/attest-data/src/lib.rs @@ -13,6 +13,8 @@ use sha3::{ Sha3_256Core, }; +pub mod messages; + #[cfg(feature = "std")] use thiserror::Error; diff --git a/attest-data/src/messages.rs b/attest-data/src/messages.rs new file mode 100644 index 0000000..fcdec01 --- /dev/null +++ b/attest-data/src/messages.rs @@ -0,0 +1,631 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +use crate::NONCE_SIZE; +use hubpack::SerializedSize; +use serde::{de::DeserializeOwned, Deserialize, Serialize}; + +use hubpack::error::Error as HubpackError; + +/// Magic value for [`Header::magic`] +pub const ATTEST_MAGIC: u32 = 0xA77E5700; + +/// Right now `Attest` is the only command that takes data (nonce) +pub const MAX_DATA_LEN: usize = NONCE_SIZE; + +pub const MAX_REQUEST_SIZE: usize = + HostRotHeader::MAX_SIZE + HostToRotCommand::MAX_SIZE + MAX_DATA_LEN; + +pub mod version { + pub const V1: u32 = 1; +} + +#[derive( + Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize, SerializedSize, +)] +pub struct HostRotHeader { + magic: u32, + version: u32, +} + +impl Default for HostRotHeader { + fn default() -> Self { + Self::new() + } +} + +impl HostRotHeader { + pub fn new() -> Self { + Self { + magic: ATTEST_MAGIC, + version: version::V1, + } + } +} + +#[derive( + Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize, SerializedSize, +)] +#[repr(u32)] +pub enum HostToRotCommand { + /// Returns the certificate chain associated with the RoT + GetCertificates, + /// Returns the measurement log + GetMeasurementLog, + /// Calculates sign(sha3_256(hubpack(measurement_log) | nonce)) + /// and returns the result. + Attest, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)] +//#[repr(u32)] +pub enum HostToRotError { + _Unused, + /// Header magic was incorrect + MagicMismatch, + /// Mismatch of protocol versions + VersionMismatch, + /// Message failed to deserialize + Deserialize, + /// Wrong length of data arguments (expected no data or incorrect length) + IncorrectDataLen, + /// Unexpected command returned + UnexpectedCommand, + /// Error return from the sprot command + SprotError(SprotError), +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)] +#[repr(u32)] +// Errors returned from the hubris side. This is _so many_ +pub enum SprotError { + // protocol + /// CRC check failed. + ProtocolInvalidCrc, + /// FIFO overflow/underflow + ProtocolFlowError, + /// Unsupported protocol version + ProtocolUnsupportedProtocol, + /// Unknown message + ProtocolBadMessageType, + /// Transfer size is outside of maximum and minimum lenghts for message type. + ProtocolBadMessageLength, + // We cannot assert chip select + ProtocolCannotAssertCSn, + // The request timed out + ProtocolTimeout, + // Hubpack error + ProtocolDeserialization, + // The RoT has not de-asserted ROT_IRQ + ProtocolRotIrqRemainsAsserted, + // An unexpected response was received. + // This should basically be impossible. We only include it so we can + // return this error when unpacking a RspBody in idol calls. + ProtocolUnexpectedResponse, + // Failed to load update status + ProtocolBadUpdateStatus, + // Used for mapping From + ProtocolTaskRestarted, + // The SP and RoT did not agree on whether the SP is sending + // a request or waiting for a reply. + ProtocolDesynchronized, + + // Spi + SpiBadTransferSize, + SpiTaskRestarted, + + // Update -- this should not get returned + UpdateError, + // Sprockets is deprecated but we still keep the error type + SprocketsError, + // Watchdog error, we should not get this + WatchdogError, + + // Attest errors + AttestCertTooBig, + AttestInvalidCertIndex, + AttestNoCerts, + AttestOutOfRange, + AttestLogFull, + AttestLogTooBig, + AttestTaskRestarted, + AttestBadLease, + AttestUnsupportedAlgorithm, + AttestSerializeLog, + AttestSerializeSignature, + AttestSignatureTooBig, + // Handle some host-sp-comms errors + CommsBufTooSmall, +} + +impl From for HostToRotError { + fn from(_: HubpackError) -> Self { + Self::Deserialize + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)] +pub enum RotToHost { + HostToRotError(HostToRotError), + RotCertificates, + RotMeasurementLog, + RotAttestation, +} + +impl From for RotToHost { + fn from(e: SprotError) -> Self { + RotToHost::HostToRotError(HostToRotError::SprotError(e)) + } +} + +fn deserialize( + data: &[u8], +) -> Result<(HostRotHeader, T, &[u8]), HostToRotError> { + let (header, leftover) = hubpack::deserialize::(data)?; + let (command, leftover) = hubpack::deserialize::(leftover)?; + + Ok((header, command, leftover)) +} + +/// Parse a message sent from the Host to the SP +pub fn parse_message( + buf: &[u8], +) -> Result<(HostToRotCommand, &[u8]), HostToRotError> { + let (header, command, leftover) = deserialize::(buf)?; + + if header.magic != ATTEST_MAGIC { + return Err(HostToRotError::MagicMismatch); + } + + if header.version != version::V1 { + return Err(HostToRotError::VersionMismatch); + } + + match command { + // These commands don't take data + HostToRotCommand::GetCertificates + | HostToRotCommand::GetMeasurementLog => { + if !leftover.is_empty() { + return Err(HostToRotError::IncorrectDataLen); + } + } + HostToRotCommand::Attest => { + if leftover.len() != NONCE_SIZE { + return Err(HostToRotError::IncorrectDataLen); + } + } + } + + Ok((command, leftover)) +} + +/// Parse a response from the SP to the Host +pub fn parse_response( + buf: &[u8], + expected: RotToHost, +) -> Result<&[u8], HostToRotError> { + let (header, command, leftover) = deserialize::(buf)?; + + if header.magic != ATTEST_MAGIC { + return Err(HostToRotError::MagicMismatch); + } + + if header.version != version::V1 { + return Err(HostToRotError::VersionMismatch); + } + + match command { + RotToHost::HostToRotError(e) => return Err(e), + c => { + if c != expected { + return Err(HostToRotError::UnexpectedCommand); + } + } + } + Ok(leftover) +} + +fn raw_serialize( + out: &mut [u8], + header: &HostRotHeader, + command: &S, + fill_data: F, +) -> Result +where + F: FnOnce(&mut [u8]) -> Result, + S: Serialize, +{ + let header_len = hubpack::serialize(out, header)?; + let mut n = header_len; + + let out_data_end = out.len(); + + n += hubpack::serialize(&mut out[n..out_data_end], command)?; + + match fill_data(&mut out[n..out_data_end]) { + Ok(data_this_message) => { + assert!(data_this_message <= out_data_end - n); + n += data_this_message; + } + Err(e) => { + n = header_len; + n += hubpack::serialize(&mut out[n..out_data_end], &e)?; + } + } + + Ok(n) +} + +pub fn try_serialize( + out: &mut [u8], + command: &S, + fill_data: F, +) -> Result +where + F: FnOnce(&mut [u8]) -> Result, + S: Serialize, +{ + let header = HostRotHeader { + magic: ATTEST_MAGIC, + version: version::V1, + }; + + raw_serialize(out, &header, command, fill_data) +} + +pub fn serialize( + out: &mut [u8], + command: &impl Serialize, + fill_data: F, +) -> Result +where + F: FnOnce(&mut [u8]) -> usize, +{ + let header = HostRotHeader { + magic: ATTEST_MAGIC, + version: version::V1, + }; + + raw_serialize(out, &header, command, |buf| Ok(fill_data(buf))) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn host_to_rot_cmd() { + let mut out = [0; 3]; + + let command: [HostToRotCommand; 3] = [ + HostToRotCommand::GetCertificates, + HostToRotCommand::GetMeasurementLog, + HostToRotCommand::Attest, + ]; + + let expected = vec![0, 1, 2]; + + let n = hubpack::serialize(&mut out, &command).unwrap(); + assert_eq!( + expected, + &out[..n], + "incorrect serialization for HostToRotCommand" + ); + } + + #[test] + fn host_to_rot_error() { + let mut out = [0; 4]; + + let command: [(HostToRotError, [u8; 4]); 37] = [ + (HostToRotError::_Unused, [0, 0, 0, 0]), + (HostToRotError::MagicMismatch, [1, 0, 0, 0]), + (HostToRotError::VersionMismatch, [2, 0, 0, 0]), + (HostToRotError::Deserialize, [3, 0, 0, 0]), + (HostToRotError::IncorrectDataLen, [4, 0, 0, 0]), + (HostToRotError::UnexpectedCommand, [5, 0, 0, 0]), + ( + HostToRotError::SprotError(SprotError::ProtocolInvalidCrc), + [6, 0, 0, 0], + ), + ( + HostToRotError::SprotError(SprotError::ProtocolFlowError), + [6, 1, 0, 0], + ), + ( + HostToRotError::SprotError( + SprotError::ProtocolUnsupportedProtocol, + ), + [6, 2, 0, 0], + ), + ( + HostToRotError::SprotError(SprotError::ProtocolBadMessageType), + [6, 3, 0, 0], + ), + ( + HostToRotError::SprotError( + SprotError::ProtocolBadMessageLength, + ), + [6, 4, 0, 0], + ), + ( + HostToRotError::SprotError(SprotError::ProtocolCannotAssertCSn), + [6, 5, 0, 0], + ), + ( + HostToRotError::SprotError(SprotError::ProtocolTimeout), + [6, 6, 0, 0], + ), + ( + HostToRotError::SprotError(SprotError::ProtocolDeserialization), + [6, 7, 0, 0], + ), + ( + HostToRotError::SprotError( + SprotError::ProtocolRotIrqRemainsAsserted, + ), + [6, 8, 0, 0], + ), + ( + HostToRotError::SprotError( + SprotError::ProtocolUnexpectedResponse, + ), + [6, 9, 0, 0], + ), + ( + HostToRotError::SprotError(SprotError::ProtocolBadUpdateStatus), + [6, 10, 0, 0], + ), + ( + HostToRotError::SprotError(SprotError::ProtocolTaskRestarted), + [6, 11, 0, 0], + ), + ( + HostToRotError::SprotError(SprotError::ProtocolDesynchronized), + [6, 12, 0, 0], + ), + ( + HostToRotError::SprotError(SprotError::SpiBadTransferSize), + [6, 13, 0, 0], + ), + ( + HostToRotError::SprotError(SprotError::SpiTaskRestarted), + [6, 14, 0, 0], + ), + ( + HostToRotError::SprotError(SprotError::UpdateError), + [6, 15, 0, 0], + ), + ( + HostToRotError::SprotError(SprotError::SprocketsError), + [6, 16, 0, 0], + ), + ( + HostToRotError::SprotError(SprotError::WatchdogError), + [6, 17, 0, 0], + ), + ( + HostToRotError::SprotError(SprotError::AttestCertTooBig), + [6, 18, 0, 0], + ), + ( + HostToRotError::SprotError(SprotError::AttestInvalidCertIndex), + [6, 19, 0, 0], + ), + ( + HostToRotError::SprotError(SprotError::AttestNoCerts), + [6, 20, 0, 0], + ), + ( + HostToRotError::SprotError(SprotError::AttestOutOfRange), + [6, 21, 0, 0], + ), + ( + HostToRotError::SprotError(SprotError::AttestLogFull), + [6, 22, 0, 0], + ), + ( + HostToRotError::SprotError(SprotError::AttestLogTooBig), + [6, 23, 0, 0], + ), + ( + HostToRotError::SprotError(SprotError::AttestTaskRestarted), + [6, 24, 0, 0], + ), + ( + HostToRotError::SprotError(SprotError::AttestBadLease), + [6, 25, 0, 0], + ), + ( + HostToRotError::SprotError( + SprotError::AttestUnsupportedAlgorithm, + ), + [6, 26, 0, 0], + ), + ( + HostToRotError::SprotError(SprotError::AttestSerializeLog), + [6, 27, 0, 0], + ), + ( + HostToRotError::SprotError( + SprotError::AttestSerializeSignature, + ), + [6, 28, 0, 0], + ), + ( + HostToRotError::SprotError(SprotError::AttestSignatureTooBig), + [6, 29, 0, 0], + ), + ( + HostToRotError::SprotError(SprotError::CommsBufTooSmall), + [6, 30, 0, 0], + ), + ]; + + for (c, e) in command { + let n = hubpack::serialize(&mut out, &c).unwrap(); + + assert_eq!(&e[..n], &out[..n], "incorrect serialization on {c:?}"); + } + } + + #[test] + fn rot_to_host_result() { + let mut out = [0; 4]; + + let command: [(RotToHost, [u8; 4]); 4] = [ + // The HostToRotErrors are tested elsewhere + ( + RotToHost::HostToRotError(HostToRotError::_Unused), + [0, 0, 0, 0], + ), + (RotToHost::RotCertificates, [1, 0, 0, 0]), + (RotToHost::RotMeasurementLog, [2, 0, 0, 0]), + (RotToHost::RotAttestation, [3, 0, 0, 0]), + ]; + + for (c, e) in command { + let n = hubpack::serialize(&mut out, &c).unwrap(); + + assert_eq!(&e[..n], &out[..n], "incorrect serialization on {c:?}"); + } + } + + #[test] + fn host_to_rot_messages() { + // Test bad magic + let bad_magic = + [0x1, 0x2, 0x3, 0x4, 0x1, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0]; + + assert_eq!( + parse_message(&bad_magic), + Err(HostToRotError::MagicMismatch) + ); + + // Test bad version -- right now we only support v1 + let bad_version = [ + 0x00, 0x57, 0x7e, 0xa7, 0xff, 0xff, 0xff, 0xff, 0x1, 0x0, 0x0, 0x0, + ]; + + assert_eq!( + parse_message(&bad_version), + Err(HostToRotError::VersionMismatch) + ); + + // Test wrong data len + let bad_get_cert = [ + 0x00, 0x57, 0x7e, 0xa7, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, + ]; + + assert_eq!( + parse_message(&bad_get_cert), + Err(HostToRotError::IncorrectDataLen) + ); + + let bad_get_cert = [ + 0x00, 0x57, 0x7e, 0xa7, 0x1, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, + ]; + + assert_eq!( + parse_message(&bad_get_cert), + Err(HostToRotError::IncorrectDataLen) + ); + + let bad_get_cert = [ + 0x00, 0x57, 0x7e, 0xa7, 0x1, 0x0, 0x0, 0x0, 0x2, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, + ]; + + assert_eq!( + parse_message(&bad_get_cert), + Err(HostToRotError::IncorrectDataLen) + ); + } + + #[test] + fn rot_to_host_messages() { + // Test bad magic + let bad_magic = + [0x1, 0x2, 0x3, 0x4, 0x1, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0]; + + assert_eq!( + parse_response(&bad_magic, RotToHost::RotAttestation), + Err(HostToRotError::MagicMismatch) + ); + + // Test bad version -- right now we only support v1 + let bad_version = [ + 0x00, 0x57, 0x7e, 0xa7, 0xff, 0xff, 0xff, 0xff, 0x1, 0x0, 0x0, 0x0, + ]; + + assert_eq!( + parse_response(&bad_version, RotToHost::RotAttestation), + Err(HostToRotError::VersionMismatch) + ); + + // didn't get the command we expected + let bad_version = [ + 0x00, 0x57, 0x7e, 0xa7, 0x1, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, + ]; + + assert_eq!( + parse_response(&bad_version, RotToHost::RotAttestation), + Err(HostToRotError::UnexpectedCommand) + ); + } + + #[test] + fn round_trip() { + // GetCertificates + let mut out: [u8; MAX_REQUEST_SIZE] = [0; MAX_REQUEST_SIZE]; + let n = serialize(&mut out, &HostToRotCommand::GetCertificates, |_| 0) + .unwrap(); + assert_eq!( + parse_message(&out[..n]), + Ok((HostToRotCommand::GetCertificates, [].as_slice())) + ); + + // GetMeasurementLog + let n = + serialize(&mut out, &HostToRotCommand::GetMeasurementLog, |_| 0) + .unwrap(); + assert_eq!( + parse_message(&out[..n]), + Ok((HostToRotCommand::GetMeasurementLog, [].as_slice())) + ); + + // Attest + let n = serialize(&mut out, &HostToRotCommand::Attest, |_| 32).unwrap(); + assert_eq!( + parse_message(&out[..n]), + Ok((HostToRotCommand::Attest, [0; 32].as_slice())) + ); + + // Responses + let n = + serialize(&mut out, &RotToHost::RotCertificates, |_| 32).unwrap(); + assert_eq!( + parse_response(&out[..n], RotToHost::RotCertificates), + Ok([0; 32].as_slice()) + ); + + // GetMeasurementLog + let n = + serialize(&mut out, &RotToHost::RotMeasurementLog, |_| 32).unwrap(); + assert_eq!( + parse_response(&out[..n], RotToHost::RotMeasurementLog), + Ok([0; 32].as_slice()) + ); + + // Attest + let n = + serialize(&mut out, &RotToHost::RotAttestation, |_| 32).unwrap(); + assert_eq!( + parse_response(&out[..n], RotToHost::RotAttestation), + Ok([0; 32].as_slice()) + ); + } +} diff --git a/dice-cert-tmpl/src/csr.rs b/dice-cert-tmpl/src/csr.rs index a3a357c..6f5bdb1 100644 --- a/dice-cert-tmpl/src/csr.rs +++ b/dice-cert-tmpl/src/csr.rs @@ -166,7 +166,7 @@ mod tests { ]; #[rustfmt::skip] - const PUB: &'static [u8] = &[ + const PUB: &[u8] = &[ 0x27, 0xfb, 0x87, 0x77, 0x77, 0x36, 0x54, 0xfb, 0x78, 0xb3, 0x46, 0x6b, 0x95, 0x0e, 0x15, 0x2b, 0x8b, 0xcd, 0x0c, 0x9b, 0x8a, 0x08, 0xfc, 0x7a, @@ -174,7 +174,7 @@ mod tests { ]; #[rustfmt::skip] - const SIG: &'static [u8] = &[ + const SIG: &[u8] = &[ 0xf5, 0xf5, 0xcf, 0xde, 0x58, 0x87, 0x6a, 0x0e, 0xa6, 0xb3, 0x3f, 0x23, 0x98, 0xd6, 0x97, 0x0c, 0x3a, 0xaa, 0xb2, 0xdf, 0xa0, 0x6e, 0x5b, 0xf7, @@ -186,7 +186,7 @@ mod tests { ]; #[rustfmt::skip] - const SIGNDATA: &'static [u8] = &[ + const SIGNDATA: &[u8] = &[ 0x30, 0x81, 0x90, 0x02, 0x01, 0x00, 0x30, 0x5d, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x47, 0x42, 0x31, 0x10, 0x30, @@ -210,7 +210,7 @@ mod tests { #[test] fn get_pub_offsets() -> Result { - let mut csr = CSR.clone(); + let mut csr = CSR; let csr = Csr::from_slice(&mut csr); let (start, end) = csr.get_pub_offsets()?; assert_eq!(&csr.as_bytes()[start..end], PUB); @@ -219,7 +219,7 @@ mod tests { #[test] fn get_sig_offsets() -> Result { - let mut csr = CSR.clone(); + let mut csr = CSR; let csr = Csr::from_slice(&mut csr); let (start, end) = csr.get_sig_offsets()?; assert_eq!(&csr.as_bytes()[start..end], SIG); @@ -228,7 +228,7 @@ mod tests { #[test] fn get_signdata_offsets() -> Result { - let mut csr = CSR.clone(); + let mut csr = CSR; let csr = Csr::from_slice(&mut csr); let (start, end) = csr.get_signdata_offsets()?; assert_eq!(&csr.as_bytes()[start..end], SIGNDATA); diff --git a/dice-cert-tmpl/src/lib.rs b/dice-cert-tmpl/src/lib.rs index 7e06912..2f1a505 100644 --- a/dice-cert-tmpl/src/lib.rs +++ b/dice-cert-tmpl/src/lib.rs @@ -180,7 +180,7 @@ pub fn write_range( mod tests { use super::*; - const DATA: &'static [u8] = &[0xca, 0xfe, 0xba, 0xbe, 0xca, 0xfe]; + const DATA: &[u8] = &[0xca, 0xfe, 0xba, 0xbe, 0xca, 0xfe]; #[test] fn get_pattern_offset_none() { diff --git a/dice-mfg-msgs/src/lib.rs b/dice-mfg-msgs/src/lib.rs index 256fc4b..85acb15 100644 --- a/dice-mfg-msgs/src/lib.rs +++ b/dice-mfg-msgs/src/lib.rs @@ -404,14 +404,14 @@ mod tests { #[test] fn rfd308_v2_good() -> Result { - assert!(!validate_0xv2(RFD308_V2_GOOD).is_err()); + assert!(validate_0xv2(RFD308_V2_GOOD).is_ok()); Ok(()) } #[test] fn pid_v1_good() -> Result { - assert!(!validate_pdv1(PID_V1_GOOD).is_err()); + assert!(validate_pdv1(PID_V1_GOOD).is_ok()); Ok(()) } From 83ca8f5bb9d3b5c728c4b6519a016a1995905486 Mon Sep 17 00:00:00 2001 From: Laura Abbott Date: Mon, 26 Aug 2024 09:13:31 -0400 Subject: [PATCH 2/2] clippy fix --- dice-cert-tmpl/src/encoding.rs | 1 + dice-mfg/src/lib.rs | 1 + 2 files changed, 2 insertions(+) diff --git a/dice-cert-tmpl/src/encoding.rs b/dice-cert-tmpl/src/encoding.rs index 77afa67..ab91218 100644 --- a/dice-cert-tmpl/src/encoding.rs +++ b/dice-cert-tmpl/src/encoding.rs @@ -83,6 +83,7 @@ fn decode_obj( } /// Decode the key file at `path` based on provided encoding. +/// /// This code doesn't parse the DER, and doesn't validate the key type. /// We assume it's an Ed25519 key & use known offsets that we think will work. /// Or not. diff --git a/dice-mfg/src/lib.rs b/dice-mfg/src/lib.rs index b6cae66..8477d4f 100644 --- a/dice-mfg/src/lib.rs +++ b/dice-mfg/src/lib.rs @@ -123,6 +123,7 @@ pub fn check_csr(csr: &PathBuf, pid: &PlatformId) -> Result { Ok(false) } +#[allow(clippy::too_long_first_doc_paragraph)] /// The MfgDriver is used to send commands to the RoT as part of programming /// identity credentials. The structure holds SerialPort instance. The /// associated functions map 1:1 to members of the MfgMessage enum from