using System.Runtime.InteropServices; using ProtonVPN.OperatingSystems.WebAuthn.Enums; using ProtonVPN.OperatingSystems.WebAuthn.Interop.Enums; using ProtonVPN.OperatingSystems.WebAuthn.Interop.Marshalers; using ProtonVPN.OperatingSystems.WebAuthn.Interop.StructVersions; namespace ProtonVPN.OperatingSystems.WebAuthn.Interop.Structs.GetAssertion; /// /// A structure that contains the data needed to get an assertion. /// /// Corresponds to WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS. [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] public class AuthenticatorGetAssertionOptions : IDisposable { /// /// The version of this structure. /// private AuthenticatorGetAssertionOptionsVersion _version = AuthenticatorGetAssertionOptionsVersion.Version7; /// /// Time that the operation is expected to complete within. /// /// This is used as guidance, and can be overridden by the platform. public int TimeoutMilliseconds { get; set; } = ApiConstants.DefaultTimeoutMilliseconds; /// /// The list of allowed credentials to be used in the assertion. /// private Credentials _allowCredentials; /// /// A CBOR map from extension identifiers to their authenticator extension inputs, /// created by the client based on the extensions requested by the Relying Party. /// These are optional extensions to parse when performing the operation. /// private ExtensionsIn _extensions; /// /// Platform vs Cross-Platform Authenticators. (Optional) /// public AuthenticatorAttachment AuthenticatorAttachment { get; set; } /// /// The effective user verification requirement. /// public UserVerificationRequirement UserVerificationRequirement { get; set; } /// /// Flags /// private AssertionOptionsFlags _flags; /// /// Optional identifier for the U2F AppId. Converted to UTF8 before being hashed. Not lower-cased. /// /// This field has been added in WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS_VERSION_2. [MarshalAs(UnmanagedType.LPUTF8Str)] private string _u2fAppId; /// /// If the following is non-NULL, then, set to TRUE if the above U2fAppid was used instead of RpId /// Note that this value is modified by WebAuthNAuthenticatorGetAssertion /// /// This field has been added in WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS_VERSION_2. private nint _isU2fAppIdUsed = nint.Zero; /// /// Cancellation Id (Optional). /// /// This field has been added in WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS_VERSION_3. private nint _cancellationId = nint.Zero; /// /// An optional list of public key credential descriptors describing credentials acceptable to the Relying Party (possibly filtered by the client), if any. /// If present, CredentialList will be ignored. /// /// This field has been added in WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS_VERSION_4. private nint _allowCredentialList = nint.Zero; /// /// The large blob operation. /// /// This field has been added in WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS_VERSION_5. public CredentialLargeBlobOperation LargeBlobOperation { get; set; } /// /// Size of _largeBlob. /// /// This field has been added in WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS_VERSION_5. private int _largeBlobLength; /// /// A pointer to the large credential blob. /// /// This field has been added in WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS_VERSION_5. private ByteArrayIn _largeBlob; /// /// PRF values which will be converted into HMAC-SECRET values according to WebAuthn Specification. /// /// This field has been added in WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS_VERSION_6. private nint _hmacSecretSaltValues; /// /// Indicates whether the browser is in private mode. Defaulting to false. /// /// This field has been added in WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS_VERSION_6. public bool BrowserInPrivateMode { get; set; } /// /// Linked Device Connection Info. /// /// This field has been added in WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS_VERSION_7. private nint _linkedDevice { get; set; } /// /// Allowlist MUST contain 1 credential applicable for Hybrid transport. /// /// This field has been added in WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS_VERSION_7. bool AutoFill { get; set; } /// /// Size of JSON extension. /// /// This field has been added in WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS_VERSION_7. private int _jsonExtLength; /// /// JSON extension. /// /// This field has been added in WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS_VERSION_7. private ByteArrayIn _jsonExt; /// /// Optional identifier for the U2F AppId. Converted to UTF8 before being hashed. Not lower cased. /// public string U2fAppId { get { return _u2fAppId; } set { _u2fAppId = value; if (_isU2fAppIdUsed == nint.Zero) { _isU2fAppIdUsed = Marshal.AllocHGlobal(sizeof(int)); } Marshal.WriteInt32(_isU2fAppIdUsed, Convert.ToInt32(value != null)); } } /// /// Cancellation Id (Optional) /// public Guid? CancellationId { set { if (value.HasValue) { if (_cancellationId == nint.Zero) { _cancellationId = Marshal.AllocHGlobal(Marshal.SizeOf()); } Marshal.StructureToPtr(value.Value, _cancellationId, false); } else { FreeCancellationId(); } } } /// /// Allowed Credentials List. /// public Credentials AllowCredentials { set { _allowCredentials?.Dispose(); _allowCredentials = value; } } /// /// Allow Credential List. If present, "AllowCredentials" will be ignored. /// public CredentialList AllowCredentialsEx { set { if (value != null) { if (_allowCredentialList == nint.Zero) { _allowCredentialList = Marshal.AllocHGlobal(Marshal.SizeOf()); } Marshal.StructureToPtr(value, _allowCredentialList, false); } else { FreeAllowCredentialList(); } } } /// /// Version of this structure, to allow for modifications in the future. /// /// This is a V7 struct. If V8 arrives, new fields will need to be added. public AuthenticatorGetAssertionOptionsVersion Version { get { return _version; } set { if (value > AuthenticatorGetAssertionOptionsVersion.Version7) { // We only support older struct versions. throw new ArgumentOutOfRangeException(nameof(value), "The requested data structure version is not yet supported."); } _version = value; } } /// /// Extensions to parse when performing the operation. (Optional) /// public ExtensionsIn Extensions { set { _extensions?.Dispose(); _extensions = value; } } /// /// Credential Large Blob. /// public byte[] LargeBlob { get { return _largeBlob?.Read(_largeBlobLength); } set { // Get rid of any previous blob first _largeBlob?.Dispose(); // Now replace the previous value with the new one _largeBlobLength = value?.Length ?? 0; _largeBlob = new ByteArrayIn(value); } } /// /// PRF values which will be converted into HMAC-SECRET values according to WebAuthn Specification. /// /// This field has been added in WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS_VERSION_6. public HmacSecretSaltValuesIn HmacSecretSaltValues { set { if (value?.HasGlobalHmacSalt ?? false) { if (_hmacSecretSaltValues == nint.Zero) { _hmacSecretSaltValues = Marshal.AllocHGlobal(Marshal.SizeOf()); } Marshal.StructureToPtr(value, _hmacSecretSaltValues, false); // Set flag _flags |= AssertionOptionsFlags.AuthenticatorHmacSecretValues; } else { FreeHmacSecretSaltValues(); // Unset flag _flags &= ~AssertionOptionsFlags.AuthenticatorHmacSecretValues; } } } /// /// Linked Device Connection Info. /// /// This field has been added in WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS_VERSION_7. public HybridStorageLinkedData LinkedDevice { set { if (value != null) { if (_linkedDevice == nint.Zero) { _linkedDevice = Marshal.AllocHGlobal(Marshal.SizeOf()); } Marshal.StructureToPtr(value, _linkedDevice, false); } else { FreeLinkedDevice(); } } } /// /// JSON extension. /// /// This field has been added in WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS_VERSION_7. public byte[] JsonExt { get { return _jsonExt?.Read(_jsonExtLength); } set { // Get rid of any previous blob first _jsonExt?.Dispose(); // Now replace the previous value with a new one _jsonExtLength = value?.Length ?? 0; _jsonExt = new ByteArrayIn(value); } } public void Dispose() { _extensions?.Dispose(); _extensions = null; _allowCredentials?.Dispose(); _allowCredentials = null; _largeBlob?.Dispose(); _largeBlob = null; _jsonExt?.Dispose(); _jsonExt = null; FreeAllowCredentialList(); FreeHmacSecretSaltValues(); FreeLinkedDevice(); if (_isU2fAppIdUsed != nint.Zero) { Marshal.FreeHGlobal(_isU2fAppIdUsed); _isU2fAppIdUsed = nint.Zero; } FreeCancellationId(); } private void FreeLinkedDevice() { if (_linkedDevice != nint.Zero) { Marshal.FreeHGlobal(_linkedDevice); _linkedDevice = nint.Zero; } } private void FreeHmacSecretSaltValues() { if (_hmacSecretSaltValues != nint.Zero) { Marshal.FreeHGlobal(_hmacSecretSaltValues); _hmacSecretSaltValues = nint.Zero; } } private void FreeAllowCredentialList() { if (_allowCredentialList != nint.Zero) { Marshal.FreeHGlobal(_allowCredentialList); _allowCredentialList = nint.Zero; } } private void FreeCancellationId() { if (_cancellationId != nint.Zero) { Marshal.FreeHGlobal(_cancellationId); _cancellationId = nint.Zero; } } }