//! Generated by `trust-tasks-codegen` — do not edit by hand.
//!
//! Spec slug: `device/register`. Version: `0.1`.
#![allow(deprecated)]
/// Why this specification version is deprecated (SPEC "Superseded and
/// Withdrawn Versions"), or `None` while it is current.
pub const DEPRECATION: Option<&str> = Some("superseded by device/register/0.2");
#[allow(unused_imports)]
use serde::{Deserialize, Serialize};
/// Error types.
pub mod error {
/// Error from a `TryFrom` or `FromStr` implementation.
pub struct ConversionError(::std::borrow::Cow<'static, str>);
impl ::std::error::Error for ConversionError {}
impl ::std::fmt::Display for ConversionError {
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> Result<(), ::std::fmt::Error> {
::std::fmt::Display::fmt(&self.0, f)
}
}
impl ::std::fmt::Debug for ConversionError {
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> Result<(), ::std::fmt::Error> {
::std::fmt::Debug::fmt(&self.0, f)
}
}
impl From<&'static str> for ConversionError {
fn from(value: &'static str) -> Self {
Self(value.into())
}
}
impl From<String> for ConversionError {
fn from(value: String) -> Self {
Self(value.into())
}
}
}
///Fine-grained capability flag scoped to the device's allowed contexts. See SPEC.md for the full semantics of each. Capability values are additive: a consumer MUST ignore a value it does not recognise rather than reject the binding, and MUST NOT treat an unrecognised value as conferring anything.
///
#[derive(
::serde::Deserialize,
::serde::Serialize,
Clone,
Copy,
Debug,
Eq,
Hash,
Ord,
PartialEq,
PartialOrd,
)]
#[non_exhaustive]
pub enum Capability {
#[serde(rename = "vault-read")]
VaultRead,
#[serde(rename = "vault-write")]
VaultWrite,
#[serde(rename = "proxy-login")]
ProxyLogin,
#[serde(rename = "fill-release")]
FillRelease,
#[serde(rename = "policy-admin")]
PolicyAdmin,
#[serde(rename = "device-admin")]
DeviceAdmin,
#[serde(rename = "sign")]
Sign,
#[serde(rename = "key-mint")]
KeyMint,
#[serde(rename = "sign-trust-task")]
SignTrustTask,
#[serde(rename = "credential-write")]
CredentialWrite,
#[serde(rename = "memory-read")]
MemoryRead,
#[serde(rename = "memory-write")]
MemoryWrite,
#[serde(rename = "room-present")]
RoomPresent,
#[serde(rename = "room-open")]
RoomOpen,
#[serde(rename = "key-export")]
KeyExport,
#[serde(rename = "sign-sshsig")]
SignSshsig,
#[serde(rename = "sign-jws")]
SignJws,
#[serde(rename = "sign-csr")]
SignCsr,
#[serde(rename = "sign-document")]
SignDocument,
#[serde(rename = "external-accounts-manage")]
ExternalAccountsManage,
#[serde(rename = "external-auth-use")]
ExternalAuthUse,
#[serde(rename = "pcs-vetter")]
PcsVetter,
#[serde(rename = "pcs-applicant")]
PcsApplicant,
#[serde(rename = "witness-request")]
WitnessRequest,
#[serde(rename = "pcs-voter")]
PcsVoter,
#[serde(rename = "pcs-trustee")]
PcsTrustee,
#[serde(rename = "credential-present")]
CredentialPresent,
}
impl ::std::fmt::Display for Capability {
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
match *self {
Self::VaultRead => f.write_str("vault-read"),
Self::VaultWrite => f.write_str("vault-write"),
Self::ProxyLogin => f.write_str("proxy-login"),
Self::FillRelease => f.write_str("fill-release"),
Self::PolicyAdmin => f.write_str("policy-admin"),
Self::DeviceAdmin => f.write_str("device-admin"),
Self::Sign => f.write_str("sign"),
Self::KeyMint => f.write_str("key-mint"),
Self::SignTrustTask => f.write_str("sign-trust-task"),
Self::CredentialWrite => f.write_str("credential-write"),
Self::MemoryRead => f.write_str("memory-read"),
Self::MemoryWrite => f.write_str("memory-write"),
Self::RoomPresent => f.write_str("room-present"),
Self::RoomOpen => f.write_str("room-open"),
Self::KeyExport => f.write_str("key-export"),
Self::SignSshsig => f.write_str("sign-sshsig"),
Self::SignJws => f.write_str("sign-jws"),
Self::SignCsr => f.write_str("sign-csr"),
Self::SignDocument => f.write_str("sign-document"),
Self::ExternalAccountsManage => f.write_str("external-accounts-manage"),
Self::ExternalAuthUse => f.write_str("external-auth-use"),
Self::PcsVetter => f.write_str("pcs-vetter"),
Self::PcsApplicant => f.write_str("pcs-applicant"),
Self::WitnessRequest => f.write_str("witness-request"),
Self::PcsVoter => f.write_str("pcs-voter"),
Self::PcsTrustee => f.write_str("pcs-trustee"),
Self::CredentialPresent => f.write_str("credential-present"),
}
}
}
impl ::std::str::FromStr for Capability {
type Err = self::error::ConversionError;
fn from_str(value: &str) -> ::std::result::Result<Self, self::error::ConversionError> {
match value {
"vault-read" => Ok(Self::VaultRead),
"vault-write" => Ok(Self::VaultWrite),
"proxy-login" => Ok(Self::ProxyLogin),
"fill-release" => Ok(Self::FillRelease),
"policy-admin" => Ok(Self::PolicyAdmin),
"device-admin" => Ok(Self::DeviceAdmin),
"sign" => Ok(Self::Sign),
"key-mint" => Ok(Self::KeyMint),
"sign-trust-task" => Ok(Self::SignTrustTask),
"credential-write" => Ok(Self::CredentialWrite),
"memory-read" => Ok(Self::MemoryRead),
"memory-write" => Ok(Self::MemoryWrite),
"room-present" => Ok(Self::RoomPresent),
"room-open" => Ok(Self::RoomOpen),
"key-export" => Ok(Self::KeyExport),
"sign-sshsig" => Ok(Self::SignSshsig),
"sign-jws" => Ok(Self::SignJws),
"sign-csr" => Ok(Self::SignCsr),
"sign-document" => Ok(Self::SignDocument),
"external-accounts-manage" => Ok(Self::ExternalAccountsManage),
"external-auth-use" => Ok(Self::ExternalAuthUse),
"pcs-vetter" => Ok(Self::PcsVetter),
"pcs-applicant" => Ok(Self::PcsApplicant),
"witness-request" => Ok(Self::WitnessRequest),
"pcs-voter" => Ok(Self::PcsVoter),
"pcs-trustee" => Ok(Self::PcsTrustee),
"credential-present" => Ok(Self::CredentialPresent),
_ => Err("invalid value".into()),
}
}
}
impl ::std::convert::TryFrom<&str> for Capability {
type Error = self::error::ConversionError;
fn try_from(value: &str) -> ::std::result::Result<Self, self::error::ConversionError> {
value.parse()
}
}
impl ::std::convert::TryFrom<&::std::string::String> for Capability {
type Error = self::error::ConversionError;
fn try_from(
value: &::std::string::String,
) -> ::std::result::Result<Self, self::error::ConversionError> {
value.parse()
}
}
impl ::std::convert::TryFrom<::std::string::String> for Capability {
type Error = self::error::ConversionError;
fn try_from(
value: ::std::string::String,
) -> ::std::result::Result<Self, self::error::ConversionError> {
value.parse()
}
}
///Discriminator: is this consumer a user-driven Companion or a headless Service?
///
#[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)]
#[serde(tag = "kind", deny_unknown_fields)]
#[non_exhaustive]
pub enum ConsumerKind {
///Companion
#[serde(rename = "companion")]
Companion {
#[serde(rename = "formFactor")]
form_factor: ConsumerKindFormFactor,
},
///Service
#[serde(rename = "service")]
Service {
#[serde(rename = "serviceKind")]
service_kind: ConsumerKindServiceKind,
},
}
///`ConsumerKindFormFactor`
///
#[derive(
::serde::Deserialize,
::serde::Serialize,
Clone,
Copy,
Debug,
Eq,
Hash,
Ord,
PartialEq,
PartialOrd,
)]
#[non_exhaustive]
pub enum ConsumerKindFormFactor {
#[serde(rename = "browser")]
Browser,
#[serde(rename = "mobile")]
Mobile,
#[serde(rename = "desktop")]
Desktop,
}
impl ::std::fmt::Display for ConsumerKindFormFactor {
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
match *self {
Self::Browser => f.write_str("browser"),
Self::Mobile => f.write_str("mobile"),
Self::Desktop => f.write_str("desktop"),
}
}
}
impl ::std::str::FromStr for ConsumerKindFormFactor {
type Err = self::error::ConversionError;
fn from_str(value: &str) -> ::std::result::Result<Self, self::error::ConversionError> {
match value {
"browser" => Ok(Self::Browser),
"mobile" => Ok(Self::Mobile),
"desktop" => Ok(Self::Desktop),
_ => Err("invalid value".into()),
}
}
}
impl ::std::convert::TryFrom<&str> for ConsumerKindFormFactor {
type Error = self::error::ConversionError;
fn try_from(value: &str) -> ::std::result::Result<Self, self::error::ConversionError> {
value.parse()
}
}
impl ::std::convert::TryFrom<&::std::string::String> for ConsumerKindFormFactor {
type Error = self::error::ConversionError;
fn try_from(
value: &::std::string::String,
) -> ::std::result::Result<Self, self::error::ConversionError> {
value.parse()
}
}
impl ::std::convert::TryFrom<::std::string::String> for ConsumerKindFormFactor {
type Error = self::error::ConversionError;
fn try_from(
value: ::std::string::String,
) -> ::std::result::Result<Self, self::error::ConversionError> {
value.parse()
}
}
///`ConsumerKindServiceKind`
///
#[derive(
::serde::Deserialize,
::serde::Serialize,
Clone,
Copy,
Debug,
Eq,
Hash,
Ord,
PartialEq,
PartialOrd,
)]
#[non_exhaustive]
pub enum ConsumerKindServiceKind {
#[serde(rename = "mediator")]
Mediator,
#[serde(rename = "ai-agent")]
AiAgent,
#[serde(rename = "daemon")]
Daemon,
}
impl ::std::fmt::Display for ConsumerKindServiceKind {
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
match *self {
Self::Mediator => f.write_str("mediator"),
Self::AiAgent => f.write_str("ai-agent"),
Self::Daemon => f.write_str("daemon"),
}
}
}
impl ::std::str::FromStr for ConsumerKindServiceKind {
type Err = self::error::ConversionError;
fn from_str(value: &str) -> ::std::result::Result<Self, self::error::ConversionError> {
match value {
"mediator" => Ok(Self::Mediator),
"ai-agent" => Ok(Self::AiAgent),
"daemon" => Ok(Self::Daemon),
_ => Err("invalid value".into()),
}
}
}
impl ::std::convert::TryFrom<&str> for ConsumerKindServiceKind {
type Error = self::error::ConversionError;
fn try_from(value: &str) -> ::std::result::Result<Self, self::error::ConversionError> {
value.parse()
}
}
impl ::std::convert::TryFrom<&::std::string::String> for ConsumerKindServiceKind {
type Error = self::error::ConversionError;
fn try_from(
value: &::std::string::String,
) -> ::std::result::Result<Self, self::error::ConversionError> {
value.parse()
}
}
impl ::std::convert::TryFrom<::std::string::String> for ConsumerKindServiceKind {
type Error = self::error::ConversionError;
fn try_from(
value: ::std::string::String,
) -> ::std::result::Result<Self, self::error::ConversionError> {
value.parse()
}
}
///Producer-supplied attestation at registration time, verifiable by the maintainer against the platform's attestation infrastructure. Tagged union over the discriminator `kind`.
///
#[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)]
#[serde(tag = "kind", deny_unknown_fields)]
#[non_exhaustive]
pub enum DeviceAttestation {
///WebAuthnAttestation
#[serde(rename = "webauthn")]
Webauthn {
///WebAuthn Authenticator AAGUID (UUID).
aaguid: DeviceAttestationAaguid,
///Base64url-encoded WebAuthn attestation statement, when supplied by the platform.
#[serde(
rename = "attestationStatement",
default,
skip_serializing_if = "::std::option::Option::is_none"
)]
attestation_statement: ::std::option::Option<::std::string::String>,
},
///AppleAppAttest
#[serde(rename = "apple-app-attest")]
AppleAppAttest {
attestation: ::std::string::String,
#[serde(rename = "keyId")]
key_id: DeviceAttestationKeyId,
},
///PlayIntegrity
#[serde(rename = "play-integrity")]
PlayIntegrity { token: DeviceAttestationToken },
///Tpm
#[serde(rename = "tpm")]
Tpm { quote: ::std::string::String },
///NitroEnclave
#[serde(rename = "nitro-enclave")]
NitroEnclave { quote: ::std::string::String },
#[serde(rename = "none")]
None,
}
///WebAuthn Authenticator AAGUID (UUID).
///
#[derive(::serde::Serialize, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[serde(transparent)]
pub struct DeviceAttestationAaguid(::std::string::String);
impl ::std::ops::Deref for DeviceAttestationAaguid {
type Target = ::std::string::String;
fn deref(&self) -> &::std::string::String {
&self.0
}
}
impl ::std::convert::From<DeviceAttestationAaguid> for ::std::string::String {
fn from(value: DeviceAttestationAaguid) -> Self {
value.0
}
}
impl ::std::str::FromStr for DeviceAttestationAaguid {
type Err = self::error::ConversionError;
fn from_str(value: &str) -> ::std::result::Result<Self, self::error::ConversionError> {
static PATTERN: ::std::sync::LazyLock<::regress::Regex> =
::std::sync::LazyLock::new(|| {
::regress::Regex::new(
"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
)
.unwrap()
});
if PATTERN.find(value).is_none() {
return Err(
"doesn't match pattern \"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$\""
.into(),
);
}
Ok(Self(value.to_string()))
}
}
impl ::std::convert::TryFrom<&str> for DeviceAttestationAaguid {
type Error = self::error::ConversionError;
fn try_from(value: &str) -> ::std::result::Result<Self, self::error::ConversionError> {
value.parse()
}
}
impl ::std::convert::TryFrom<&::std::string::String> for DeviceAttestationAaguid {
type Error = self::error::ConversionError;
fn try_from(
value: &::std::string::String,
) -> ::std::result::Result<Self, self::error::ConversionError> {
value.parse()
}
}
impl ::std::convert::TryFrom<::std::string::String> for DeviceAttestationAaguid {
type Error = self::error::ConversionError;
fn try_from(
value: ::std::string::String,
) -> ::std::result::Result<Self, self::error::ConversionError> {
value.parse()
}
}
impl<'de> ::serde::Deserialize<'de> for DeviceAttestationAaguid {
fn deserialize<D>(deserializer: D) -> ::std::result::Result<Self, D::Error>
where
D: ::serde::Deserializer<'de>,
{
::std::string::String::deserialize(deserializer)?
.parse()
.map_err(|e: self::error::ConversionError| {
<D::Error as ::serde::de::Error>::custom(e.to_string())
})
}
}
///`DeviceAttestationKeyId`
///
#[derive(::serde::Serialize, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[serde(transparent)]
pub struct DeviceAttestationKeyId(::std::string::String);
impl ::std::ops::Deref for DeviceAttestationKeyId {
type Target = ::std::string::String;
fn deref(&self) -> &::std::string::String {
&self.0
}
}
impl ::std::convert::From<DeviceAttestationKeyId> for ::std::string::String {
fn from(value: DeviceAttestationKeyId) -> Self {
value.0
}
}
impl ::std::str::FromStr for DeviceAttestationKeyId {
type Err = self::error::ConversionError;
fn from_str(value: &str) -> ::std::result::Result<Self, self::error::ConversionError> {
if value.chars().count() < 1usize {
return Err("shorter than 1 characters".into());
}
Ok(Self(value.to_string()))
}
}
impl ::std::convert::TryFrom<&str> for DeviceAttestationKeyId {
type Error = self::error::ConversionError;
fn try_from(value: &str) -> ::std::result::Result<Self, self::error::ConversionError> {
value.parse()
}
}
impl ::std::convert::TryFrom<&::std::string::String> for DeviceAttestationKeyId {
type Error = self::error::ConversionError;
fn try_from(
value: &::std::string::String,
) -> ::std::result::Result<Self, self::error::ConversionError> {
value.parse()
}
}
impl ::std::convert::TryFrom<::std::string::String> for DeviceAttestationKeyId {
type Error = self::error::ConversionError;
fn try_from(
value: ::std::string::String,
) -> ::std::result::Result<Self, self::error::ConversionError> {
value.parse()
}
}
impl<'de> ::serde::Deserialize<'de> for DeviceAttestationKeyId {
fn deserialize<D>(deserializer: D) -> ::std::result::Result<Self, D::Error>
where
D: ::serde::Deserializer<'de>,
{
::std::string::String::deserialize(deserializer)?
.parse()
.map_err(|e: self::error::ConversionError| {
<D::Error as ::serde::de::Error>::custom(e.to_string())
})
}
}
///`DeviceAttestationToken`
///
#[derive(::serde::Serialize, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[serde(transparent)]
pub struct DeviceAttestationToken(::std::string::String);
impl ::std::ops::Deref for DeviceAttestationToken {
type Target = ::std::string::String;
fn deref(&self) -> &::std::string::String {
&self.0
}
}
impl ::std::convert::From<DeviceAttestationToken> for ::std::string::String {
fn from(value: DeviceAttestationToken) -> Self {
value.0
}
}
impl ::std::str::FromStr for DeviceAttestationToken {
type Err = self::error::ConversionError;
fn from_str(value: &str) -> ::std::result::Result<Self, self::error::ConversionError> {
if value.chars().count() < 1usize {
return Err("shorter than 1 characters".into());
}
Ok(Self(value.to_string()))
}
}
impl ::std::convert::TryFrom<&str> for DeviceAttestationToken {
type Error = self::error::ConversionError;
fn try_from(value: &str) -> ::std::result::Result<Self, self::error::ConversionError> {
value.parse()
}
}
impl ::std::convert::TryFrom<&::std::string::String> for DeviceAttestationToken {
type Error = self::error::ConversionError;
fn try_from(
value: &::std::string::String,
) -> ::std::result::Result<Self, self::error::ConversionError> {
value.parse()
}
}
impl ::std::convert::TryFrom<::std::string::String> for DeviceAttestationToken {
type Error = self::error::ConversionError;
fn try_from(
value: ::std::string::String,
) -> ::std::result::Result<Self, self::error::ConversionError> {
value.parse()
}
}
impl<'de> ::serde::Deserialize<'de> for DeviceAttestationToken {
fn deserialize<D>(deserializer: D) -> ::std::result::Result<Self, D::Error>
where
D: ::serde::Deserializer<'de>,
{
::std::string::String::deserialize(deserializer)?
.parse()
.map_err(|e: self::error::ConversionError| {
<D::Error as ::serde::de::Error>::custom(e.to_string())
})
}
}
///`DeviceBinding`
///
#[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)]
#[serde(deny_unknown_fields)]
#[non_exhaustive]
pub struct DeviceBinding {
#[serde(default, skip_serializing_if = "::std::option::Option::is_none")]
pub attestation: ::std::option::Option<DeviceAttestation>,
///Capability bitset granted to this device (mirrors the ACL-side scope). Returned for inspection; mutated only via acl/change-role or device/disable.
#[serde(default, skip_serializing_if = "::std::option::Option::is_none")]
pub capabilities: ::std::option::Option<Vec<Capability>>,
///The long-term VTA-derived key (DID) the device authenticates with. Established via the ACL-swap pattern at registration.
#[serde(rename = "consumerDid")]
pub consumer_did: DeviceBindingConsumerDid,
#[serde(rename = "consumerKind")]
pub consumer_kind: ConsumerKind,
///Maintainer-assigned opaque id for this device. Stable across the device's lifetime — never re-used after disable or wipe.
#[serde(rename = "deviceId")]
pub device_id: DeviceBindingDeviceId,
///Present when the device has been disabled (device/disable). Disabled devices cannot authenticate but their record is retained for audit.
#[serde(
rename = "disabledAt",
default,
skip_serializing_if = "::std::option::Option::is_none"
)]
pub disabled_at: ::std::option::Option<::chrono::DateTime<::chrono::offset::Utc>>,
///Human-readable name (e.g. "Glenn's MacBook — Chrome", "iPhone 17").
#[serde(rename = "displayName")]
pub display_name: DeviceBindingDisplayName,
#[serde(default, skip_serializing_if = "::std::option::Option::is_none")]
pub ext: ::std::option::Option<Ext>,
///How the device custodies its private key material (tier + algorithms). Maintainer policy input, mirroring `attestation` — a maintainer MAY apply stricter policy to `software`-tier devices. See docs/design-notes/mobile-key-custody-profile.md.
#[serde(
rename = "keyCustody",
default,
skip_serializing_if = "::std::option::Option::is_none"
)]
pub key_custody: ::std::option::Option<KeyCustody>,
///Updated on every device/heartbeat and on any successful auth.
#[serde(
rename = "lastSeenAt",
default,
skip_serializing_if = "::std::option::Option::is_none"
)]
pub last_seen_at: ::std::option::Option<::chrono::DateTime<::chrono::offset::Utc>>,
///Free-form platform descriptor (e.g. "macOS 16 / Chrome 142", "iOS 19.1", "Android 16", "Linux/x86_64"). Producer-supplied at registration; consumer-supplied updates are accepted on heartbeat.
#[serde(default, skip_serializing_if = "::std::option::Option::is_none")]
pub platform: ::std::option::Option<::std::string::String>,
///Whether this device has a usable push channel — i.e. it has registered a push token with a push gateway and conveyed the resulting opaque WakeHandle to its VTA via device/set-wake (https://trusttasks.org/binding/push/0.1). Informational visibility for device/list only. The raw platform push token is held ONLY by the gateway; the maintainer/VTA holds the opaque WakeHandle and the VTA-owned trigger allowlist, never the token. Set from the presence of a current WakeHandle for this device.
#[serde(
rename = "pushCapable",
default,
skip_serializing_if = "::std::option::Option::is_none"
)]
pub push_capable: ::std::option::Option<bool>,
#[serde(rename = "registeredAt")]
pub registered_at: ::chrono::DateTime<::chrono::offset::Utc>,
///Present when a wipe has been issued (device/wipe). Distinct from disabledAt — a wiped device is also disabled, but wipe additionally communicates a wipe-cache instruction that the device may or may not have executed (see device/wipe).
#[serde(
rename = "wipedAt",
default,
skip_serializing_if = "::std::option::Option::is_none"
)]
pub wiped_at: ::std::option::Option<::chrono::DateTime<::chrono::offset::Utc>>,
}
impl DeviceBinding {
pub fn builder() -> builder::DeviceBinding {
Default::default()
}
}
///The long-term VTA-derived key (DID) the device authenticates with. Established via the ACL-swap pattern at registration.
///
#[derive(::serde::Serialize, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[serde(transparent)]
pub struct DeviceBindingConsumerDid(::std::string::String);
impl ::std::ops::Deref for DeviceBindingConsumerDid {
type Target = ::std::string::String;
fn deref(&self) -> &::std::string::String {
&self.0
}
}
impl ::std::convert::From<DeviceBindingConsumerDid> for ::std::string::String {
fn from(value: DeviceBindingConsumerDid) -> Self {
value.0
}
}
impl ::std::str::FromStr for DeviceBindingConsumerDid {
type Err = self::error::ConversionError;
fn from_str(value: &str) -> ::std::result::Result<Self, self::error::ConversionError> {
if value.chars().count() < 1usize {
return Err("shorter than 1 characters".into());
}
Ok(Self(value.to_string()))
}
}
impl ::std::convert::TryFrom<&str> for DeviceBindingConsumerDid {
type Error = self::error::ConversionError;
fn try_from(value: &str) -> ::std::result::Result<Self, self::error::ConversionError> {
value.parse()
}
}
impl ::std::convert::TryFrom<&::std::string::String> for DeviceBindingConsumerDid {
type Error = self::error::ConversionError;
fn try_from(
value: &::std::string::String,
) -> ::std::result::Result<Self, self::error::ConversionError> {
value.parse()
}
}
impl ::std::convert::TryFrom<::std::string::String> for DeviceBindingConsumerDid {
type Error = self::error::ConversionError;
fn try_from(
value: ::std::string::String,
) -> ::std::result::Result<Self, self::error::ConversionError> {
value.parse()
}
}
impl<'de> ::serde::Deserialize<'de> for DeviceBindingConsumerDid {
fn deserialize<D>(deserializer: D) -> ::std::result::Result<Self, D::Error>
where
D: ::serde::Deserializer<'de>,
{
::std::string::String::deserialize(deserializer)?
.parse()
.map_err(|e: self::error::ConversionError| {
<D::Error as ::serde::de::Error>::custom(e.to_string())
})
}
}
///Maintainer-assigned opaque id for this device. Stable across the device's lifetime — never re-used after disable or wipe.
///
#[derive(::serde::Serialize, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[serde(transparent)]
pub struct DeviceBindingDeviceId(::std::string::String);
impl ::std::ops::Deref for DeviceBindingDeviceId {
type Target = ::std::string::String;
fn deref(&self) -> &::std::string::String {
&self.0
}
}
impl ::std::convert::From<DeviceBindingDeviceId> for ::std::string::String {
fn from(value: DeviceBindingDeviceId) -> Self {
value.0
}
}
impl ::std::str::FromStr for DeviceBindingDeviceId {
type Err = self::error::ConversionError;
fn from_str(value: &str) -> ::std::result::Result<Self, self::error::ConversionError> {
if value.chars().count() < 1usize {
return Err("shorter than 1 characters".into());
}
Ok(Self(value.to_string()))
}
}
impl ::std::convert::TryFrom<&str> for DeviceBindingDeviceId {
type Error = self::error::ConversionError;
fn try_from(value: &str) -> ::std::result::Result<Self, self::error::ConversionError> {
value.parse()
}
}
impl ::std::convert::TryFrom<&::std::string::String> for DeviceBindingDeviceId {
type Error = self::error::ConversionError;
fn try_from(
value: &::std::string::String,
) -> ::std::result::Result<Self, self::error::ConversionError> {
value.parse()
}
}
impl ::std::convert::TryFrom<::std::string::String> for DeviceBindingDeviceId {
type Error = self::error::ConversionError;
fn try_from(
value: ::std::string::String,
) -> ::std::result::Result<Self, self::error::ConversionError> {
value.parse()
}
}
impl<'de> ::serde::Deserialize<'de> for DeviceBindingDeviceId {
fn deserialize<D>(deserializer: D) -> ::std::result::Result<Self, D::Error>
where
D: ::serde::Deserializer<'de>,
{
::std::string::String::deserialize(deserializer)?
.parse()
.map_err(|e: self::error::ConversionError| {
<D::Error as ::serde::de::Error>::custom(e.to_string())
})
}
}
///Human-readable name (e.g. "Glenn's MacBook — Chrome", "iPhone 17").
///
#[derive(::serde::Serialize, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[serde(transparent)]
pub struct DeviceBindingDisplayName(::std::string::String);
impl ::std::ops::Deref for DeviceBindingDisplayName {
type Target = ::std::string::String;
fn deref(&self) -> &::std::string::String {
&self.0
}
}
impl ::std::convert::From<DeviceBindingDisplayName> for ::std::string::String {
fn from(value: DeviceBindingDisplayName) -> Self {
value.0
}
}
impl ::std::str::FromStr for DeviceBindingDisplayName {
type Err = self::error::ConversionError;
fn from_str(value: &str) -> ::std::result::Result<Self, self::error::ConversionError> {
if value.chars().count() > 128usize {
return Err("longer than 128 characters".into());
}
if value.chars().count() < 1usize {
return Err("shorter than 1 characters".into());
}
Ok(Self(value.to_string()))
}
}
impl ::std::convert::TryFrom<&str> for DeviceBindingDisplayName {
type Error = self::error::ConversionError;
fn try_from(value: &str) -> ::std::result::Result<Self, self::error::ConversionError> {
value.parse()
}
}
impl ::std::convert::TryFrom<&::std::string::String> for DeviceBindingDisplayName {
type Error = self::error::ConversionError;
fn try_from(
value: &::std::string::String,
) -> ::std::result::Result<Self, self::error::ConversionError> {
value.parse()
}
}
impl ::std::convert::TryFrom<::std::string::String> for DeviceBindingDisplayName {
type Error = self::error::ConversionError;
fn try_from(
value: ::std::string::String,
) -> ::std::result::Result<Self, self::error::ConversionError> {
value.parse()
}
}
impl<'de> ::serde::Deserialize<'de> for DeviceBindingDisplayName {
fn deserialize<D>(deserializer: D) -> ::std::result::Result<Self, D::Error>
where
D: ::serde::Deserializer<'de>,
{
::std::string::String::deserialize(deserializer)?
.parse()
.map_err(|e: self::error::ConversionError| {
<D::Error as ::serde::de::Error>::custom(e.to_string())
})
}
}
///Vendor-namespaced extension object per SPEC.md §4.5.1. Each immediate key MUST be a reverse-DNS namespace; structure under each namespace is opaque to the framework.
///
#[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)]
#[serde(transparent)]
pub struct Ext(pub ::std::collections::HashMap<ExtKey, ::serde_json::Value>);
impl ::std::ops::Deref for Ext {
type Target = ::std::collections::HashMap<ExtKey, ::serde_json::Value>;
fn deref(&self) -> &::std::collections::HashMap<ExtKey, ::serde_json::Value> {
&self.0
}
}
impl ::std::convert::From<Ext> for ::std::collections::HashMap<ExtKey, ::serde_json::Value> {
fn from(value: Ext) -> Self {
value.0
}
}
impl ::std::convert::From<::std::collections::HashMap<ExtKey, ::serde_json::Value>> for Ext {
fn from(value: ::std::collections::HashMap<ExtKey, ::serde_json::Value>) -> Self {
Self(value)
}
}
///`ExtKey`
///
#[derive(::serde::Serialize, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[serde(transparent)]
pub struct ExtKey(::std::string::String);
impl ::std::ops::Deref for ExtKey {
type Target = ::std::string::String;
fn deref(&self) -> &::std::string::String {
&self.0
}
}
impl ::std::convert::From<ExtKey> for ::std::string::String {
fn from(value: ExtKey) -> Self {
value.0
}
}
impl ::std::str::FromStr for ExtKey {
type Err = self::error::ConversionError;
fn from_str(value: &str) -> ::std::result::Result<Self, self::error::ConversionError> {
static PATTERN: ::std::sync::LazyLock<::regress::Regex> =
::std::sync::LazyLock::new(|| {
::regress::Regex::new("^[a-z][a-z0-9-]*(\\.[a-z0-9-]+)+$").unwrap()
});
if PATTERN.find(value).is_none() {
return Err("doesn't match pattern \"^[a-z][a-z0-9-]*(\\.[a-z0-9-]+)+$\"".into());
}
Ok(Self(value.to_string()))
}
}
impl ::std::convert::TryFrom<&str> for ExtKey {
type Error = self::error::ConversionError;
fn try_from(value: &str) -> ::std::result::Result<Self, self::error::ConversionError> {
value.parse()
}
}
impl ::std::convert::TryFrom<&::std::string::String> for ExtKey {
type Error = self::error::ConversionError;
fn try_from(
value: &::std::string::String,
) -> ::std::result::Result<Self, self::error::ConversionError> {
value.parse()
}
}
impl ::std::convert::TryFrom<::std::string::String> for ExtKey {
type Error = self::error::ConversionError;
fn try_from(
value: ::std::string::String,
) -> ::std::result::Result<Self, self::error::ConversionError> {
value.parse()
}
}
impl<'de> ::serde::Deserialize<'de> for ExtKey {
fn deserialize<D>(deserializer: D) -> ::std::result::Result<Self, D::Error>
where
D: ::serde::Deserializer<'de>,
{
::std::string::String::deserialize(deserializer)?
.parse()
.map_err(|e: self::error::ConversionError| {
<D::Error as ::serde::de::Error>::custom(e.to_string())
})
}
}
///How a device custodies its private key material — maintainer policy input, mirroring `attestation`. See docs/design-notes/mobile-key-custody-profile.md.
///
#[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)]
#[serde(deny_unknown_fields)]
#[non_exhaustive]
pub struct KeyCustody {
///Curve of the holder's keyAgreement key, e.g. `P-256` (hardware-custodiable on mobile) or `X25519` (not).
#[serde(
rename = "keyAgreementCurve",
default,
skip_serializing_if = "::std::option::Option::is_none"
)]
pub key_agreement_curve: ::std::option::Option<::std::string::String>,
///JOSE `alg` of the holder's signing key, e.g. `ES256` (hardware-custodiable on mobile) or `EdDSA` (not).
#[serde(
rename = "signingAlg",
default,
skip_serializing_if = "::std::option::Option::is_none"
)]
pub signing_alg: ::std::option::Option<::std::string::String>,
///`hardware`: the key is non-exportable in the secure keystore (iOS Secure Enclave / Android StrongBox) and every signing / key-agreement operation runs in-chip — achievable only with P-256. `software`: the key is held in app memory during use, stored hardware-wrapped at rest. Maintainers MAY apply stricter policy (shorter sessions, more frequent step-up) to `software`-tier devices.
pub tier: KeyCustodyTier,
}
impl KeyCustody {
pub fn builder() -> builder::KeyCustody {
Default::default()
}
}
///`hardware`: the key is non-exportable in the secure keystore (iOS Secure Enclave / Android StrongBox) and every signing / key-agreement operation runs in-chip — achievable only with P-256. `software`: the key is held in app memory during use, stored hardware-wrapped at rest. Maintainers MAY apply stricter policy (shorter sessions, more frequent step-up) to `software`-tier devices.
///
#[derive(
::serde::Deserialize,
::serde::Serialize,
Clone,
Copy,
Debug,
Eq,
Hash,
Ord,
PartialEq,
PartialOrd,
)]
#[non_exhaustive]
pub enum KeyCustodyTier {
#[serde(rename = "hardware")]
Hardware,
#[serde(rename = "software")]
Software,
}
impl ::std::fmt::Display for KeyCustodyTier {
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
match *self {
Self::Hardware => f.write_str("hardware"),
Self::Software => f.write_str("software"),
}
}
}
impl ::std::str::FromStr for KeyCustodyTier {
type Err = self::error::ConversionError;
fn from_str(value: &str) -> ::std::result::Result<Self, self::error::ConversionError> {
match value {
"hardware" => Ok(Self::Hardware),
"software" => Ok(Self::Software),
_ => Err("invalid value".into()),
}
}
}
impl ::std::convert::TryFrom<&str> for KeyCustodyTier {
type Error = self::error::ConversionError;
fn try_from(value: &str) -> ::std::result::Result<Self, self::error::ConversionError> {
value.parse()
}
}
impl ::std::convert::TryFrom<&::std::string::String> for KeyCustodyTier {
type Error = self::error::ConversionError;
fn try_from(
value: &::std::string::String,
) -> ::std::result::Result<Self, self::error::ConversionError> {
value.parse()
}
}
impl ::std::convert::TryFrom<::std::string::String> for KeyCustodyTier {
type Error = self::error::ConversionError;
fn try_from(
value: ::std::string::String,
) -> ::std::result::Result<Self, self::error::ConversionError> {
value.parse()
}
}
///Public discovery surface that wraps the maintainer's existing two-phase enrolment (provision-integration → acl/swap-key). A new Companion or Service hands the maintainer its long-term VTA-derived key, its consumer kind, display name, and an optional device attestation; the maintainer creates the DeviceBinding and returns it. Phase 1 (provision-integration) is assumed to have already happened — this task is the post-bootstrap claim step.
///
#[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)]
#[serde(deny_unknown_fields)]
#[non_exhaustive]
#[deprecated(note = "superseded by device/register/0.2")]
pub struct Payload {
#[serde(default, skip_serializing_if = "::std::option::Option::is_none")]
pub attestation: ::std::option::Option<DeviceAttestation>,
#[serde(rename = "consumerKind")]
pub consumer_kind: ConsumerKind,
#[serde(rename = "displayName")]
pub display_name: PayloadDisplayName,
#[serde(default, skip_serializing_if = "::std::option::Option::is_none")]
pub ext: ::std::option::Option<Ext>,
///X25519 public key (did:key form) the maintainer will use to HPKE-seal sensitive payloads to this device (sealed secrets, session blobs, sync events). REQUIRED — every Companion/Service needs a recipient key.
#[serde(
rename = "hpkePublicKey",
default,
skip_serializing_if = "::std::option::Option::is_none"
)]
pub hpke_public_key: ::std::option::Option<PayloadHpkePublicKey>,
///OPTIONAL. How the device custodies its private keys (tier + algorithms). RECOMMENDED for mobile Companions. Maintainer policy input — see docs/design-notes/mobile-key-custody-profile.md.
#[serde(
rename = "keyCustody",
default,
skip_serializing_if = "::std::option::Option::is_none"
)]
pub key_custody: ::std::option::Option<KeyCustody>,
#[serde(default, skip_serializing_if = "::std::option::Option::is_none")]
pub platform: ::std::option::Option<::std::string::String>,
}
impl Payload {
pub fn builder() -> builder::Payload {
Default::default()
}
}
///`PayloadDisplayName`
///
#[derive(::serde::Serialize, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[serde(transparent)]
pub struct PayloadDisplayName(::std::string::String);
impl ::std::ops::Deref for PayloadDisplayName {
type Target = ::std::string::String;
fn deref(&self) -> &::std::string::String {
&self.0
}
}
impl ::std::convert::From<PayloadDisplayName> for ::std::string::String {
fn from(value: PayloadDisplayName) -> Self {
value.0
}
}
impl ::std::str::FromStr for PayloadDisplayName {
type Err = self::error::ConversionError;
fn from_str(value: &str) -> ::std::result::Result<Self, self::error::ConversionError> {
if value.chars().count() > 128usize {
return Err("longer than 128 characters".into());
}
if value.chars().count() < 1usize {
return Err("shorter than 1 characters".into());
}
Ok(Self(value.to_string()))
}
}
impl ::std::convert::TryFrom<&str> for PayloadDisplayName {
type Error = self::error::ConversionError;
fn try_from(value: &str) -> ::std::result::Result<Self, self::error::ConversionError> {
value.parse()
}
}
impl ::std::convert::TryFrom<&::std::string::String> for PayloadDisplayName {
type Error = self::error::ConversionError;
fn try_from(
value: &::std::string::String,
) -> ::std::result::Result<Self, self::error::ConversionError> {
value.parse()
}
}
impl ::std::convert::TryFrom<::std::string::String> for PayloadDisplayName {
type Error = self::error::ConversionError;
fn try_from(
value: ::std::string::String,
) -> ::std::result::Result<Self, self::error::ConversionError> {
value.parse()
}
}
impl<'de> ::serde::Deserialize<'de> for PayloadDisplayName {
fn deserialize<D>(deserializer: D) -> ::std::result::Result<Self, D::Error>
where
D: ::serde::Deserializer<'de>,
{
::std::string::String::deserialize(deserializer)?
.parse()
.map_err(|e: self::error::ConversionError| {
<D::Error as ::serde::de::Error>::custom(e.to_string())
})
}
}
///X25519 public key (did:key form) the maintainer will use to HPKE-seal sensitive payloads to this device (sealed secrets, session blobs, sync events). REQUIRED — every Companion/Service needs a recipient key.
///
#[derive(::serde::Serialize, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[serde(transparent)]
pub struct PayloadHpkePublicKey(::std::string::String);
impl ::std::ops::Deref for PayloadHpkePublicKey {
type Target = ::std::string::String;
fn deref(&self) -> &::std::string::String {
&self.0
}
}
impl ::std::convert::From<PayloadHpkePublicKey> for ::std::string::String {
fn from(value: PayloadHpkePublicKey) -> Self {
value.0
}
}
impl ::std::str::FromStr for PayloadHpkePublicKey {
type Err = self::error::ConversionError;
fn from_str(value: &str) -> ::std::result::Result<Self, self::error::ConversionError> {
if value.chars().count() < 1usize {
return Err("shorter than 1 characters".into());
}
Ok(Self(value.to_string()))
}
}
impl ::std::convert::TryFrom<&str> for PayloadHpkePublicKey {
type Error = self::error::ConversionError;
fn try_from(value: &str) -> ::std::result::Result<Self, self::error::ConversionError> {
value.parse()
}
}
impl ::std::convert::TryFrom<&::std::string::String> for PayloadHpkePublicKey {
type Error = self::error::ConversionError;
fn try_from(
value: &::std::string::String,
) -> ::std::result::Result<Self, self::error::ConversionError> {
value.parse()
}
}
impl ::std::convert::TryFrom<::std::string::String> for PayloadHpkePublicKey {
type Error = self::error::ConversionError;
fn try_from(
value: ::std::string::String,
) -> ::std::result::Result<Self, self::error::ConversionError> {
value.parse()
}
}
impl<'de> ::serde::Deserialize<'de> for PayloadHpkePublicKey {
fn deserialize<D>(deserializer: D) -> ::std::result::Result<Self, D::Error>
where
D: ::serde::Deserializer<'de>,
{
::std::string::String::deserialize(deserializer)?
.parse()
.map_err(|e: self::error::ConversionError| {
<D::Error as ::serde::de::Error>::custom(e.to_string())
})
}
}
///`Response`
///
#[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)]
#[serde(deny_unknown_fields)]
#[non_exhaustive]
#[deprecated(note = "superseded by device/register/0.2")]
pub struct Response {
pub binding: DeviceBinding,
#[serde(default, skip_serializing_if = "::std::option::Option::is_none")]
pub ext: ::std::option::Option<Ext>,
}
impl Response {
pub fn builder() -> builder::Response {
Default::default()
}
}
/// Types for composing complex structures.
pub mod builder {
#[derive(Clone, Debug)]
pub struct DeviceBinding {
attestation: ::std::result::Result<
::std::option::Option<super::DeviceAttestation>,
::std::string::String,
>,
capabilities: ::std::result::Result<
::std::option::Option<Vec<super::Capability>>,
::std::string::String,
>,
consumer_did: ::std::result::Result<super::DeviceBindingConsumerDid, ::std::string::String>,
consumer_kind: ::std::result::Result<super::ConsumerKind, ::std::string::String>,
device_id: ::std::result::Result<super::DeviceBindingDeviceId, ::std::string::String>,
disabled_at: ::std::result::Result<
::std::option::Option<::chrono::DateTime<::chrono::offset::Utc>>,
::std::string::String,
>,
display_name: ::std::result::Result<super::DeviceBindingDisplayName, ::std::string::String>,
ext: ::std::result::Result<::std::option::Option<super::Ext>, ::std::string::String>,
key_custody:
::std::result::Result<::std::option::Option<super::KeyCustody>, ::std::string::String>,
last_seen_at: ::std::result::Result<
::std::option::Option<::chrono::DateTime<::chrono::offset::Utc>>,
::std::string::String,
>,
platform: ::std::result::Result<
::std::option::Option<::std::string::String>,
::std::string::String,
>,
push_capable: ::std::result::Result<::std::option::Option<bool>, ::std::string::String>,
registered_at:
::std::result::Result<::chrono::DateTime<::chrono::offset::Utc>, ::std::string::String>,
wiped_at: ::std::result::Result<
::std::option::Option<::chrono::DateTime<::chrono::offset::Utc>>,
::std::string::String,
>,
}
impl ::std::default::Default for DeviceBinding {
fn default() -> Self {
Self {
attestation: Ok(Default::default()),
capabilities: Ok(Default::default()),
consumer_did: Err("no value supplied for consumer_did".to_string()),
consumer_kind: Err("no value supplied for consumer_kind".to_string()),
device_id: Err("no value supplied for device_id".to_string()),
disabled_at: Ok(Default::default()),
display_name: Err("no value supplied for display_name".to_string()),
ext: Ok(Default::default()),
key_custody: Ok(Default::default()),
last_seen_at: Ok(Default::default()),
platform: Ok(Default::default()),
push_capable: Ok(Default::default()),
registered_at: Err("no value supplied for registered_at".to_string()),
wiped_at: Ok(Default::default()),
}
}
}
impl DeviceBinding {
pub fn attestation<T>(mut self, value: T) -> Self
where
T: ::std::convert::TryInto<::std::option::Option<super::DeviceAttestation>>,
T::Error: ::std::fmt::Display,
{
self.attestation = value
.try_into()
.map_err(|e| format!("error converting supplied value for attestation: {e}"));
self
}
pub fn capabilities<T>(mut self, value: T) -> Self
where
T: ::std::convert::TryInto<::std::option::Option<Vec<super::Capability>>>,
T::Error: ::std::fmt::Display,
{
self.capabilities = value
.try_into()
.map_err(|e| format!("error converting supplied value for capabilities: {e}"));
self
}
pub fn consumer_did<T>(mut self, value: T) -> Self
where
T: ::std::convert::TryInto<super::DeviceBindingConsumerDid>,
T::Error: ::std::fmt::Display,
{
self.consumer_did = value
.try_into()
.map_err(|e| format!("error converting supplied value for consumer_did: {e}"));
self
}
pub fn consumer_kind<T>(mut self, value: T) -> Self
where
T: ::std::convert::TryInto<super::ConsumerKind>,
T::Error: ::std::fmt::Display,
{
self.consumer_kind = value
.try_into()
.map_err(|e| format!("error converting supplied value for consumer_kind: {e}"));
self
}
pub fn device_id<T>(mut self, value: T) -> Self
where
T: ::std::convert::TryInto<super::DeviceBindingDeviceId>,
T::Error: ::std::fmt::Display,
{
self.device_id = value
.try_into()
.map_err(|e| format!("error converting supplied value for device_id: {e}"));
self
}
pub fn disabled_at<T>(mut self, value: T) -> Self
where
T: ::std::convert::TryInto<
::std::option::Option<::chrono::DateTime<::chrono::offset::Utc>>,
>,
T::Error: ::std::fmt::Display,
{
self.disabled_at = value
.try_into()
.map_err(|e| format!("error converting supplied value for disabled_at: {e}"));
self
}
pub fn display_name<T>(mut self, value: T) -> Self
where
T: ::std::convert::TryInto<super::DeviceBindingDisplayName>,
T::Error: ::std::fmt::Display,
{
self.display_name = value
.try_into()
.map_err(|e| format!("error converting supplied value for display_name: {e}"));
self
}
pub fn ext<T>(mut self, value: T) -> Self
where
T: ::std::convert::TryInto<::std::option::Option<super::Ext>>,
T::Error: ::std::fmt::Display,
{
self.ext = value
.try_into()
.map_err(|e| format!("error converting supplied value for ext: {e}"));
self
}
pub fn key_custody<T>(mut self, value: T) -> Self
where
T: ::std::convert::TryInto<::std::option::Option<super::KeyCustody>>,
T::Error: ::std::fmt::Display,
{
self.key_custody = value
.try_into()
.map_err(|e| format!("error converting supplied value for key_custody: {e}"));
self
}
pub fn last_seen_at<T>(mut self, value: T) -> Self
where
T: ::std::convert::TryInto<
::std::option::Option<::chrono::DateTime<::chrono::offset::Utc>>,
>,
T::Error: ::std::fmt::Display,
{
self.last_seen_at = value
.try_into()
.map_err(|e| format!("error converting supplied value for last_seen_at: {e}"));
self
}
pub fn platform<T>(mut self, value: T) -> Self
where
T: ::std::convert::TryInto<::std::option::Option<::std::string::String>>,
T::Error: ::std::fmt::Display,
{
self.platform = value
.try_into()
.map_err(|e| format!("error converting supplied value for platform: {e}"));
self
}
pub fn push_capable<T>(mut self, value: T) -> Self
where
T: ::std::convert::TryInto<::std::option::Option<bool>>,
T::Error: ::std::fmt::Display,
{
self.push_capable = value
.try_into()
.map_err(|e| format!("error converting supplied value for push_capable: {e}"));
self
}
pub fn registered_at<T>(mut self, value: T) -> Self
where
T: ::std::convert::TryInto<::chrono::DateTime<::chrono::offset::Utc>>,
T::Error: ::std::fmt::Display,
{
self.registered_at = value
.try_into()
.map_err(|e| format!("error converting supplied value for registered_at: {e}"));
self
}
pub fn wiped_at<T>(mut self, value: T) -> Self
where
T: ::std::convert::TryInto<
::std::option::Option<::chrono::DateTime<::chrono::offset::Utc>>,
>,
T::Error: ::std::fmt::Display,
{
self.wiped_at = value
.try_into()
.map_err(|e| format!("error converting supplied value for wiped_at: {e}"));
self
}
}
impl ::std::convert::TryFrom<DeviceBinding> for super::DeviceBinding {
type Error = super::error::ConversionError;
fn try_from(
value: DeviceBinding,
) -> ::std::result::Result<Self, super::error::ConversionError> {
Ok(Self {
attestation: value.attestation?,
capabilities: value.capabilities?,
consumer_did: value.consumer_did?,
consumer_kind: value.consumer_kind?,
device_id: value.device_id?,
disabled_at: value.disabled_at?,
display_name: value.display_name?,
ext: value.ext?,
key_custody: value.key_custody?,
last_seen_at: value.last_seen_at?,
platform: value.platform?,
push_capable: value.push_capable?,
registered_at: value.registered_at?,
wiped_at: value.wiped_at?,
})
}
}
impl ::std::convert::From<super::DeviceBinding> for DeviceBinding {
fn from(value: super::DeviceBinding) -> Self {
Self {
attestation: Ok(value.attestation),
capabilities: Ok(value.capabilities),
consumer_did: Ok(value.consumer_did),
consumer_kind: Ok(value.consumer_kind),
device_id: Ok(value.device_id),
disabled_at: Ok(value.disabled_at),
display_name: Ok(value.display_name),
ext: Ok(value.ext),
key_custody: Ok(value.key_custody),
last_seen_at: Ok(value.last_seen_at),
platform: Ok(value.platform),
push_capable: Ok(value.push_capable),
registered_at: Ok(value.registered_at),
wiped_at: Ok(value.wiped_at),
}
}
}
#[derive(Clone, Debug)]
pub struct KeyCustody {
key_agreement_curve: ::std::result::Result<
::std::option::Option<::std::string::String>,
::std::string::String,
>,
signing_alg: ::std::result::Result<
::std::option::Option<::std::string::String>,
::std::string::String,
>,
tier: ::std::result::Result<super::KeyCustodyTier, ::std::string::String>,
}
impl ::std::default::Default for KeyCustody {
fn default() -> Self {
Self {
key_agreement_curve: Ok(Default::default()),
signing_alg: Ok(Default::default()),
tier: Err("no value supplied for tier".to_string()),
}
}
}
impl KeyCustody {
pub fn key_agreement_curve<T>(mut self, value: T) -> Self
where
T: ::std::convert::TryInto<::std::option::Option<::std::string::String>>,
T::Error: ::std::fmt::Display,
{
self.key_agreement_curve = value.try_into().map_err(|e| {
format!("error converting supplied value for key_agreement_curve: {e}")
});
self
}
pub fn signing_alg<T>(mut self, value: T) -> Self
where
T: ::std::convert::TryInto<::std::option::Option<::std::string::String>>,
T::Error: ::std::fmt::Display,
{
self.signing_alg = value
.try_into()
.map_err(|e| format!("error converting supplied value for signing_alg: {e}"));
self
}
pub fn tier<T>(mut self, value: T) -> Self
where
T: ::std::convert::TryInto<super::KeyCustodyTier>,
T::Error: ::std::fmt::Display,
{
self.tier = value
.try_into()
.map_err(|e| format!("error converting supplied value for tier: {e}"));
self
}
}
impl ::std::convert::TryFrom<KeyCustody> for super::KeyCustody {
type Error = super::error::ConversionError;
fn try_from(
value: KeyCustody,
) -> ::std::result::Result<Self, super::error::ConversionError> {
Ok(Self {
key_agreement_curve: value.key_agreement_curve?,
signing_alg: value.signing_alg?,
tier: value.tier?,
})
}
}
impl ::std::convert::From<super::KeyCustody> for KeyCustody {
fn from(value: super::KeyCustody) -> Self {
Self {
key_agreement_curve: Ok(value.key_agreement_curve),
signing_alg: Ok(value.signing_alg),
tier: Ok(value.tier),
}
}
}
#[derive(Clone, Debug)]
pub struct Payload {
attestation: ::std::result::Result<
::std::option::Option<super::DeviceAttestation>,
::std::string::String,
>,
consumer_kind: ::std::result::Result<super::ConsumerKind, ::std::string::String>,
display_name: ::std::result::Result<super::PayloadDisplayName, ::std::string::String>,
ext: ::std::result::Result<::std::option::Option<super::Ext>, ::std::string::String>,
hpke_public_key: ::std::result::Result<
::std::option::Option<super::PayloadHpkePublicKey>,
::std::string::String,
>,
key_custody:
::std::result::Result<::std::option::Option<super::KeyCustody>, ::std::string::String>,
platform: ::std::result::Result<
::std::option::Option<::std::string::String>,
::std::string::String,
>,
}
impl ::std::default::Default for Payload {
fn default() -> Self {
Self {
attestation: Ok(Default::default()),
consumer_kind: Err("no value supplied for consumer_kind".to_string()),
display_name: Err("no value supplied for display_name".to_string()),
ext: Ok(Default::default()),
hpke_public_key: Ok(Default::default()),
key_custody: Ok(Default::default()),
platform: Ok(Default::default()),
}
}
}
impl Payload {
pub fn attestation<T>(mut self, value: T) -> Self
where
T: ::std::convert::TryInto<::std::option::Option<super::DeviceAttestation>>,
T::Error: ::std::fmt::Display,
{
self.attestation = value
.try_into()
.map_err(|e| format!("error converting supplied value for attestation: {e}"));
self
}
pub fn consumer_kind<T>(mut self, value: T) -> Self
where
T: ::std::convert::TryInto<super::ConsumerKind>,
T::Error: ::std::fmt::Display,
{
self.consumer_kind = value
.try_into()
.map_err(|e| format!("error converting supplied value for consumer_kind: {e}"));
self
}
pub fn display_name<T>(mut self, value: T) -> Self
where
T: ::std::convert::TryInto<super::PayloadDisplayName>,
T::Error: ::std::fmt::Display,
{
self.display_name = value
.try_into()
.map_err(|e| format!("error converting supplied value for display_name: {e}"));
self
}
pub fn ext<T>(mut self, value: T) -> Self
where
T: ::std::convert::TryInto<::std::option::Option<super::Ext>>,
T::Error: ::std::fmt::Display,
{
self.ext = value
.try_into()
.map_err(|e| format!("error converting supplied value for ext: {e}"));
self
}
pub fn hpke_public_key<T>(mut self, value: T) -> Self
where
T: ::std::convert::TryInto<::std::option::Option<super::PayloadHpkePublicKey>>,
T::Error: ::std::fmt::Display,
{
self.hpke_public_key = value
.try_into()
.map_err(|e| format!("error converting supplied value for hpke_public_key: {e}"));
self
}
pub fn key_custody<T>(mut self, value: T) -> Self
where
T: ::std::convert::TryInto<::std::option::Option<super::KeyCustody>>,
T::Error: ::std::fmt::Display,
{
self.key_custody = value
.try_into()
.map_err(|e| format!("error converting supplied value for key_custody: {e}"));
self
}
pub fn platform<T>(mut self, value: T) -> Self
where
T: ::std::convert::TryInto<::std::option::Option<::std::string::String>>,
T::Error: ::std::fmt::Display,
{
self.platform = value
.try_into()
.map_err(|e| format!("error converting supplied value for platform: {e}"));
self
}
}
impl ::std::convert::TryFrom<Payload> for super::Payload {
type Error = super::error::ConversionError;
fn try_from(value: Payload) -> ::std::result::Result<Self, super::error::ConversionError> {
Ok(Self {
attestation: value.attestation?,
consumer_kind: value.consumer_kind?,
display_name: value.display_name?,
ext: value.ext?,
hpke_public_key: value.hpke_public_key?,
key_custody: value.key_custody?,
platform: value.platform?,
})
}
}
impl ::std::convert::From<super::Payload> for Payload {
fn from(value: super::Payload) -> Self {
Self {
attestation: Ok(value.attestation),
consumer_kind: Ok(value.consumer_kind),
display_name: Ok(value.display_name),
ext: Ok(value.ext),
hpke_public_key: Ok(value.hpke_public_key),
key_custody: Ok(value.key_custody),
platform: Ok(value.platform),
}
}
}
#[derive(Clone, Debug)]
pub struct Response {
binding: ::std::result::Result<super::DeviceBinding, ::std::string::String>,
ext: ::std::result::Result<::std::option::Option<super::Ext>, ::std::string::String>,
}
impl ::std::default::Default for Response {
fn default() -> Self {
Self {
binding: Err("no value supplied for binding".to_string()),
ext: Ok(Default::default()),
}
}
}
impl Response {
pub fn binding<T>(mut self, value: T) -> Self
where
T: ::std::convert::TryInto<super::DeviceBinding>,
T::Error: ::std::fmt::Display,
{
self.binding = value
.try_into()
.map_err(|e| format!("error converting supplied value for binding: {e}"));
self
}
pub fn ext<T>(mut self, value: T) -> Self
where
T: ::std::convert::TryInto<::std::option::Option<super::Ext>>,
T::Error: ::std::fmt::Display,
{
self.ext = value
.try_into()
.map_err(|e| format!("error converting supplied value for ext: {e}"));
self
}
}
impl ::std::convert::TryFrom<Response> for super::Response {
type Error = super::error::ConversionError;
fn try_from(value: Response) -> ::std::result::Result<Self, super::error::ConversionError> {
Ok(Self {
binding: value.binding?,
ext: value.ext?,
})
}
}
impl ::std::convert::From<super::Response> for Response {
fn from(value: super::Response) -> Self {
Self {
binding: Ok(value.binding),
ext: Ok(value.ext),
}
}
}
}
impl crate::Payload for Payload {
const TYPE_URI: &'static str = "https://trusttasks.org/spec/device/register/0.1";
const IS_PROOF_REQUIRED: bool = true;
const IS_ISSUED_AT_REQUIRED: bool = true;
const IS_RECIPIENT_REQUIRED: bool = true;
const PAYLOAD_SCHEMA: Option<&'static str> = Some(
"{\"$defs\":{\"Capability\":{\"description\":\"Fine-grained capability flag scoped to the device's allowed contexts. See SPEC.md for the full semantics of each. Capability values are additive: a consumer MUST ignore a value it does not recognise rather than reject the binding, and MUST NOT treat an unrecognised value as conferring anything.\",\"enum\":[\"vault-read\",\"vault-write\",\"proxy-login\",\"fill-release\",\"policy-admin\",\"device-admin\",\"sign\",\"key-mint\",\"sign-trust-task\",\"credential-write\",\"memory-read\",\"memory-write\",\"room-present\",\"room-open\",\"key-export\",\"sign-sshsig\",\"sign-jws\",\"sign-csr\",\"sign-document\",\"external-accounts-manage\",\"external-auth-use\",\"pcs-vetter\",\"pcs-applicant\",\"witness-request\",\"pcs-voter\",\"pcs-trustee\",\"credential-present\"],\"title\":\"Capability\",\"type\":\"string\"},\"ConsumerKind\":{\"description\":\"Discriminator: is this consumer a user-driven Companion or a headless Service?\",\"oneOf\":[{\"additionalProperties\":false,\"properties\":{\"formFactor\":{\"enum\":[\"browser\",\"mobile\",\"desktop\"],\"type\":\"string\"},\"kind\":{\"const\":\"companion\"}},\"required\":[\"kind\",\"formFactor\"],\"title\":\"Companion\",\"type\":\"object\"},{\"additionalProperties\":false,\"properties\":{\"kind\":{\"const\":\"service\"},\"serviceKind\":{\"enum\":[\"mediator\",\"ai-agent\",\"daemon\"],\"type\":\"string\"}},\"required\":[\"kind\",\"serviceKind\"],\"title\":\"Service\",\"type\":\"object\"}],\"title\":\"ConsumerKind\"},\"DeviceAttestation\":{\"description\":\"Producer-supplied attestation at registration time, verifiable by the maintainer against the platform's attestation infrastructure. Tagged union over the discriminator `kind`.\",\"oneOf\":[{\"additionalProperties\":false,\"properties\":{\"aaguid\":{\"description\":\"WebAuthn Authenticator AAGUID (UUID).\",\"pattern\":\"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$\",\"type\":\"string\"},\"attestationStatement\":{\"description\":\"Base64url-encoded WebAuthn attestation statement, when supplied by the platform.\",\"type\":\"string\"},\"kind\":{\"const\":\"webauthn\"}},\"required\":[\"kind\",\"aaguid\"],\"title\":\"WebAuthnAttestation\",\"type\":\"object\"},{\"additionalProperties\":false,\"properties\":{\"attestation\":{\"type\":\"string\"},\"keyId\":{\"minLength\":1,\"type\":\"string\"},\"kind\":{\"const\":\"apple-app-attest\"}},\"required\":[\"kind\",\"keyId\",\"attestation\"],\"title\":\"AppleAppAttest\",\"type\":\"object\"},{\"additionalProperties\":false,\"properties\":{\"kind\":{\"const\":\"play-integrity\"},\"token\":{\"minLength\":1,\"type\":\"string\"}},\"required\":[\"kind\",\"token\"],\"title\":\"PlayIntegrity\",\"type\":\"object\"},{\"additionalProperties\":false,\"properties\":{\"kind\":{\"const\":\"tpm\"},\"quote\":{\"type\":\"string\"}},\"required\":[\"kind\",\"quote\"],\"title\":\"Tpm\",\"type\":\"object\"},{\"additionalProperties\":false,\"properties\":{\"kind\":{\"const\":\"nitro-enclave\"},\"quote\":{\"type\":\"string\"}},\"required\":[\"kind\",\"quote\"],\"title\":\"NitroEnclave\",\"type\":\"object\"},{\"additionalProperties\":false,\"description\":\"No device-level attestation is available. Maintainers MAY still register the device but SHOULD apply stricter policy (shorter session TTL, more frequent step-up).\",\"properties\":{\"kind\":{\"const\":\"none\"}},\"required\":[\"kind\"],\"title\":\"NoAttestation\",\"type\":\"object\"}],\"title\":\"DeviceAttestation\"},\"DeviceBinding\":{\"additionalProperties\":false,\"properties\":{\"attestation\":{\"$ref\":\"#/$defs/DeviceAttestation\"},\"capabilities\":{\"description\":\"Capability bitset granted to this device (mirrors the ACL-side scope). Returned for inspection; mutated only via acl/change-role or device/disable.\",\"items\":{\"$ref\":\"#/$defs/Capability\"},\"type\":\"array\",\"uniqueItems\":true},\"consumerDid\":{\"description\":\"The long-term VTA-derived key (DID) the device authenticates with. Established via the ACL-swap pattern at registration.\",\"minLength\":1,\"type\":\"string\"},\"consumerKind\":{\"$ref\":\"#/$defs/ConsumerKind\"},\"deviceId\":{\"description\":\"Maintainer-assigned opaque id for this device. Stable across the device's lifetime — never re-used after disable or wipe.\",\"minLength\":1,\"type\":\"string\"},\"disabledAt\":{\"description\":\"Present when the device has been disabled (device/disable). Disabled devices cannot authenticate but their record is retained for audit.\",\"format\":\"date-time\",\"type\":\"string\"},\"displayName\":{\"description\":\"Human-readable name (e.g. \\\"Glenn's MacBook — Chrome\\\", \\\"iPhone 17\\\").\",\"maxLength\":128,\"minLength\":1,\"type\":\"string\"},\"ext\":{\"$ref\":\"#/$defs/Ext\"},\"keyCustody\":{\"$ref\":\"#/$defs/KeyCustody\",\"description\":\"How the device custodies its private key material (tier + algorithms). Maintainer policy input, mirroring `attestation` — a maintainer MAY apply stricter policy to `software`-tier devices. See docs/design-notes/mobile-key-custody-profile.md.\"},\"lastSeenAt\":{\"description\":\"Updated on every device/heartbeat and on any successful auth.\",\"format\":\"date-time\",\"type\":\"string\"},\"platform\":{\"description\":\"Free-form platform descriptor (e.g. \\\"macOS 16 / Chrome 142\\\", \\\"iOS 19.1\\\", \\\"Android 16\\\", \\\"Linux/x86_64\\\"). Producer-supplied at registration; consumer-supplied updates are accepted on heartbeat.\",\"type\":\"string\"},\"pushCapable\":{\"description\":\"Whether this device has a usable push channel — i.e. it has registered a push token with a push gateway and conveyed the resulting opaque WakeHandle to its VTA via device/set-wake (https://trusttasks.org/binding/push/0.1). Informational visibility for device/list only. The raw platform push token is held ONLY by the gateway; the maintainer/VTA holds the opaque WakeHandle and the VTA-owned trigger allowlist, never the token. Set from the presence of a current WakeHandle for this device.\",\"type\":\"boolean\"},\"registeredAt\":{\"format\":\"date-time\",\"type\":\"string\"},\"wipedAt\":{\"description\":\"Present when a wipe has been issued (device/wipe). Distinct from disabledAt — a wiped device is also disabled, but wipe additionally communicates a wipe-cache instruction that the device may or may not have executed (see device/wipe).\",\"format\":\"date-time\",\"type\":\"string\"}},\"required\":[\"deviceId\",\"consumerKind\",\"displayName\",\"registeredAt\",\"consumerDid\"],\"title\":\"DeviceBinding\",\"type\":\"object\"},\"Ext\":{\"additionalProperties\":true,\"description\":\"Vendor-namespaced extension object per SPEC.md §4.5.1. Each immediate key MUST be a reverse-DNS namespace; structure under each namespace is opaque to the framework.\",\"minProperties\":1,\"propertyNames\":{\"pattern\":\"^[a-z][a-z0-9-]*(\\\\.[a-z0-9-]+)+$\"},\"title\":\"Ext\",\"type\":\"object\"},\"KeyCustody\":{\"additionalProperties\":false,\"description\":\"How a device custodies its private key material — maintainer policy input, mirroring `attestation`. See docs/design-notes/mobile-key-custody-profile.md.\",\"properties\":{\"keyAgreementCurve\":{\"description\":\"Curve of the holder's keyAgreement key, e.g. `P-256` (hardware-custodiable on mobile) or `X25519` (not).\",\"type\":\"string\"},\"signingAlg\":{\"description\":\"JOSE `alg` of the holder's signing key, e.g. `ES256` (hardware-custodiable on mobile) or `EdDSA` (not).\",\"type\":\"string\"},\"tier\":{\"description\":\"`hardware`: the key is non-exportable in the secure keystore (iOS Secure Enclave / Android StrongBox) and every signing / key-agreement operation runs in-chip — achievable only with P-256. `software`: the key is held in app memory during use, stored hardware-wrapped at rest. Maintainers MAY apply stricter policy (shorter sessions, more frequent step-up) to `software`-tier devices.\",\"enum\":[\"hardware\",\"software\"],\"type\":\"string\"}},\"required\":[\"tier\"],\"title\":\"KeyCustody\",\"type\":\"object\"},\"Response\":{\"$anchor\":\"response\",\"additionalProperties\":false,\"properties\":{\"binding\":{\"$ref\":\"#/$defs/DeviceBinding\"},\"ext\":{\"$ref\":\"#/$defs/Ext\"}},\"required\":[\"binding\"],\"title\":\"Device Register — response payload\",\"type\":\"object\"}},\"$id\":\"https://trusttasks.org/spec/device/register/0.1\",\"$schema\":\"https://json-schema.org/draft/2020-12/schema\",\"additionalProperties\":false,\"description\":\"Public discovery surface that wraps the maintainer's existing two-phase enrolment (provision-integration → acl/swap-key). A new Companion or Service hands the maintainer its long-term VTA-derived key, its consumer kind, display name, and an optional device attestation; the maintainer creates the DeviceBinding and returns it. Phase 1 (provision-integration) is assumed to have already happened — this task is the post-bootstrap claim step.\",\"properties\":{\"attestation\":{\"$ref\":\"#/$defs/DeviceAttestation\"},\"consumerKind\":{\"$ref\":\"#/$defs/ConsumerKind\"},\"displayName\":{\"maxLength\":128,\"minLength\":1,\"type\":\"string\"},\"ext\":{\"$ref\":\"#/$defs/Ext\"},\"hpkePublicKey\":{\"description\":\"X25519 public key (did:key form) the maintainer will use to HPKE-seal sensitive payloads to this device (sealed secrets, session blobs, sync events). REQUIRED — every Companion/Service needs a recipient key.\",\"minLength\":1,\"type\":\"string\"},\"keyCustody\":{\"$ref\":\"#/$defs/KeyCustody\",\"description\":\"OPTIONAL. How the device custodies its private keys (tier + algorithms). RECOMMENDED for mobile Companions. Maintainer policy input — see docs/design-notes/mobile-key-custody-profile.md.\"},\"platform\":{\"type\":\"string\"}},\"required\":[\"consumerKind\",\"displayName\"],\"title\":\"Device Register — payload\",\"type\":\"object\"}\n",
);
}
impl crate::Payload for Response {
const TYPE_URI: &'static str = "https://trusttasks.org/spec/device/register/0.1#response";
const IS_PROOF_REQUIRED: bool = true;
const IS_ISSUED_AT_REQUIRED: bool = true;
const IS_RECIPIENT_REQUIRED: bool = true;
const PAYLOAD_SCHEMA: Option<&'static str> = Some(
"{\"$defs\":{\"Capability\":{\"description\":\"Fine-grained capability flag scoped to the device's allowed contexts. See SPEC.md for the full semantics of each. Capability values are additive: a consumer MUST ignore a value it does not recognise rather than reject the binding, and MUST NOT treat an unrecognised value as conferring anything.\",\"enum\":[\"vault-read\",\"vault-write\",\"proxy-login\",\"fill-release\",\"policy-admin\",\"device-admin\",\"sign\",\"key-mint\",\"sign-trust-task\",\"credential-write\",\"memory-read\",\"memory-write\",\"room-present\",\"room-open\",\"key-export\",\"sign-sshsig\",\"sign-jws\",\"sign-csr\",\"sign-document\",\"external-accounts-manage\",\"external-auth-use\",\"pcs-vetter\",\"pcs-applicant\",\"witness-request\",\"pcs-voter\",\"pcs-trustee\",\"credential-present\"],\"title\":\"Capability\",\"type\":\"string\"},\"ConsumerKind\":{\"description\":\"Discriminator: is this consumer a user-driven Companion or a headless Service?\",\"oneOf\":[{\"additionalProperties\":false,\"properties\":{\"formFactor\":{\"enum\":[\"browser\",\"mobile\",\"desktop\"],\"type\":\"string\"},\"kind\":{\"const\":\"companion\"}},\"required\":[\"kind\",\"formFactor\"],\"title\":\"Companion\",\"type\":\"object\"},{\"additionalProperties\":false,\"properties\":{\"kind\":{\"const\":\"service\"},\"serviceKind\":{\"enum\":[\"mediator\",\"ai-agent\",\"daemon\"],\"type\":\"string\"}},\"required\":[\"kind\",\"serviceKind\"],\"title\":\"Service\",\"type\":\"object\"}],\"title\":\"ConsumerKind\"},\"DeviceAttestation\":{\"description\":\"Producer-supplied attestation at registration time, verifiable by the maintainer against the platform's attestation infrastructure. Tagged union over the discriminator `kind`.\",\"oneOf\":[{\"additionalProperties\":false,\"properties\":{\"aaguid\":{\"description\":\"WebAuthn Authenticator AAGUID (UUID).\",\"pattern\":\"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$\",\"type\":\"string\"},\"attestationStatement\":{\"description\":\"Base64url-encoded WebAuthn attestation statement, when supplied by the platform.\",\"type\":\"string\"},\"kind\":{\"const\":\"webauthn\"}},\"required\":[\"kind\",\"aaguid\"],\"title\":\"WebAuthnAttestation\",\"type\":\"object\"},{\"additionalProperties\":false,\"properties\":{\"attestation\":{\"type\":\"string\"},\"keyId\":{\"minLength\":1,\"type\":\"string\"},\"kind\":{\"const\":\"apple-app-attest\"}},\"required\":[\"kind\",\"keyId\",\"attestation\"],\"title\":\"AppleAppAttest\",\"type\":\"object\"},{\"additionalProperties\":false,\"properties\":{\"kind\":{\"const\":\"play-integrity\"},\"token\":{\"minLength\":1,\"type\":\"string\"}},\"required\":[\"kind\",\"token\"],\"title\":\"PlayIntegrity\",\"type\":\"object\"},{\"additionalProperties\":false,\"properties\":{\"kind\":{\"const\":\"tpm\"},\"quote\":{\"type\":\"string\"}},\"required\":[\"kind\",\"quote\"],\"title\":\"Tpm\",\"type\":\"object\"},{\"additionalProperties\":false,\"properties\":{\"kind\":{\"const\":\"nitro-enclave\"},\"quote\":{\"type\":\"string\"}},\"required\":[\"kind\",\"quote\"],\"title\":\"NitroEnclave\",\"type\":\"object\"},{\"additionalProperties\":false,\"description\":\"No device-level attestation is available. Maintainers MAY still register the device but SHOULD apply stricter policy (shorter session TTL, more frequent step-up).\",\"properties\":{\"kind\":{\"const\":\"none\"}},\"required\":[\"kind\"],\"title\":\"NoAttestation\",\"type\":\"object\"}],\"title\":\"DeviceAttestation\"},\"DeviceBinding\":{\"additionalProperties\":false,\"properties\":{\"attestation\":{\"$ref\":\"#/$defs/DeviceAttestation\"},\"capabilities\":{\"description\":\"Capability bitset granted to this device (mirrors the ACL-side scope). Returned for inspection; mutated only via acl/change-role or device/disable.\",\"items\":{\"$ref\":\"#/$defs/Capability\"},\"type\":\"array\",\"uniqueItems\":true},\"consumerDid\":{\"description\":\"The long-term VTA-derived key (DID) the device authenticates with. Established via the ACL-swap pattern at registration.\",\"minLength\":1,\"type\":\"string\"},\"consumerKind\":{\"$ref\":\"#/$defs/ConsumerKind\"},\"deviceId\":{\"description\":\"Maintainer-assigned opaque id for this device. Stable across the device's lifetime — never re-used after disable or wipe.\",\"minLength\":1,\"type\":\"string\"},\"disabledAt\":{\"description\":\"Present when the device has been disabled (device/disable). Disabled devices cannot authenticate but their record is retained for audit.\",\"format\":\"date-time\",\"type\":\"string\"},\"displayName\":{\"description\":\"Human-readable name (e.g. \\\"Glenn's MacBook — Chrome\\\", \\\"iPhone 17\\\").\",\"maxLength\":128,\"minLength\":1,\"type\":\"string\"},\"ext\":{\"$ref\":\"#/$defs/Ext\"},\"keyCustody\":{\"$ref\":\"#/$defs/KeyCustody\",\"description\":\"How the device custodies its private key material (tier + algorithms). Maintainer policy input, mirroring `attestation` — a maintainer MAY apply stricter policy to `software`-tier devices. See docs/design-notes/mobile-key-custody-profile.md.\"},\"lastSeenAt\":{\"description\":\"Updated on every device/heartbeat and on any successful auth.\",\"format\":\"date-time\",\"type\":\"string\"},\"platform\":{\"description\":\"Free-form platform descriptor (e.g. \\\"macOS 16 / Chrome 142\\\", \\\"iOS 19.1\\\", \\\"Android 16\\\", \\\"Linux/x86_64\\\"). Producer-supplied at registration; consumer-supplied updates are accepted on heartbeat.\",\"type\":\"string\"},\"pushCapable\":{\"description\":\"Whether this device has a usable push channel — i.e. it has registered a push token with a push gateway and conveyed the resulting opaque WakeHandle to its VTA via device/set-wake (https://trusttasks.org/binding/push/0.1). Informational visibility for device/list only. The raw platform push token is held ONLY by the gateway; the maintainer/VTA holds the opaque WakeHandle and the VTA-owned trigger allowlist, never the token. Set from the presence of a current WakeHandle for this device.\",\"type\":\"boolean\"},\"registeredAt\":{\"format\":\"date-time\",\"type\":\"string\"},\"wipedAt\":{\"description\":\"Present when a wipe has been issued (device/wipe). Distinct from disabledAt — a wiped device is also disabled, but wipe additionally communicates a wipe-cache instruction that the device may or may not have executed (see device/wipe).\",\"format\":\"date-time\",\"type\":\"string\"}},\"required\":[\"deviceId\",\"consumerKind\",\"displayName\",\"registeredAt\",\"consumerDid\"],\"title\":\"DeviceBinding\",\"type\":\"object\"},\"Ext\":{\"additionalProperties\":true,\"description\":\"Vendor-namespaced extension object per SPEC.md §4.5.1. Each immediate key MUST be a reverse-DNS namespace; structure under each namespace is opaque to the framework.\",\"minProperties\":1,\"propertyNames\":{\"pattern\":\"^[a-z][a-z0-9-]*(\\\\.[a-z0-9-]+)+$\"},\"title\":\"Ext\",\"type\":\"object\"},\"KeyCustody\":{\"additionalProperties\":false,\"description\":\"How a device custodies its private key material — maintainer policy input, mirroring `attestation`. See docs/design-notes/mobile-key-custody-profile.md.\",\"properties\":{\"keyAgreementCurve\":{\"description\":\"Curve of the holder's keyAgreement key, e.g. `P-256` (hardware-custodiable on mobile) or `X25519` (not).\",\"type\":\"string\"},\"signingAlg\":{\"description\":\"JOSE `alg` of the holder's signing key, e.g. `ES256` (hardware-custodiable on mobile) or `EdDSA` (not).\",\"type\":\"string\"},\"tier\":{\"description\":\"`hardware`: the key is non-exportable in the secure keystore (iOS Secure Enclave / Android StrongBox) and every signing / key-agreement operation runs in-chip — achievable only with P-256. `software`: the key is held in app memory during use, stored hardware-wrapped at rest. Maintainers MAY apply stricter policy (shorter sessions, more frequent step-up) to `software`-tier devices.\",\"enum\":[\"hardware\",\"software\"],\"type\":\"string\"}},\"required\":[\"tier\"],\"title\":\"KeyCustody\",\"type\":\"object\"},\"Response\":{\"$anchor\":\"response\",\"additionalProperties\":false,\"properties\":{\"binding\":{\"$ref\":\"#/$defs/DeviceBinding\"},\"ext\":{\"$ref\":\"#/$defs/Ext\"}},\"required\":[\"binding\"],\"title\":\"Device Register — response payload\",\"type\":\"object\"}},\"$ref\":\"#/$defs/Response\",\"$schema\":\"https://json-schema.org/draft/2020-12/schema\"}\n",
);
}
impl crate::RequestPayload for Payload {
type Response = Response;
}
/// The earlier `MAJOR.MINOR` of this slug that this version declares it
/// extends (front matter `extends`, SPEC §5.2 "Declared extension"), or
/// `None`. Only the direct declaration; walk a chain with
/// [`crate::extension::extends_for`].
pub const EXTENDS: Option<&str> = None;
/// The extended error codes this specification declares (SPEC §7.3 item 9,
/// §8.5), in declaration order. Empty when it declares none.
pub const ERROR_CODES: &[crate::DeclaredErrorCode] = &[
error_codes::NO_PENDING_ENROLMENT,
error_codes::ATTESTATION_FAILED,
error_codes::ALREADY_REGISTERED,
error_codes::HPKE_KEY_INVALID,
];
/// One constant per extended error code this specification declares
/// (SPEC §7.3 item 9), named for its local part.
///
/// Emit these rather than a string literal: the code is read from the
/// specification, so it cannot name a code the specification never
/// declared.
pub mod error_codes {
/// `device/register:noPendingEnrolment`
///
/// The producer's DID is not the result of a recent provision-integration + acl/swap-key flow. Registration cannot proceed without first being granted via the maintainer's normal enrolment path.
///
/// Declared `retryable: false`.
pub const NO_PENDING_ENROLMENT: crate::DeclaredErrorCode = crate::DeclaredErrorCode {
code: "device/register:noPendingEnrolment",
retryable: false,
details_schema: ::core::option::Option::None,
};
/// `device/register:attestationFailed`
///
/// The supplied device attestation could not be verified against the platform's attestation infrastructure.
///
/// Declared `retryable: false`.
/// Declares a `detailsSchema` for the error's `details` member.
pub const ATTESTATION_FAILED: crate::DeclaredErrorCode = crate::DeclaredErrorCode {
code: "device/register:attestationFailed",
retryable: false,
details_schema: ::core::option::Option::Some(
"{\"additionalProperties\":false,\"properties\":{\"reason\":{\"enum\":[\"signature_invalid\",\"untrusted_root\",\"stale\",\"rooted_device\",\"unsupported_attestation_kind\"],\"type\":\"string\"}},\"type\":\"object\"}",
),
};
/// `device/register:alreadyRegistered`
///
/// A DeviceBinding for this consumer DID already exists. Re-registration is rejected; the consumer SHOULD swap to a fresh key via acl/swap-key and retry.
///
/// Declared `retryable: false`.
pub const ALREADY_REGISTERED: crate::DeclaredErrorCode = crate::DeclaredErrorCode {
code: "device/register:alreadyRegistered",
retryable: false,
details_schema: ::core::option::Option::None,
};
/// `device/register:hpkeKeyInvalid`
///
/// The `hpkePublicKey` is not a valid X25519 did:key.
///
/// Declared `retryable: false`.
pub const HPKE_KEY_INVALID: crate::DeclaredErrorCode = crate::DeclaredErrorCode {
code: "device/register:hpkeKeyInvalid",
retryable: false,
details_schema: ::core::option::Option::None,
};
}